use crate::errors::{ProtocolError, Result};
use bigdecimal::BigDecimal;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Clone, Debug, Copy, PartialEq, Hash, Eq)]
pub enum Blockchain {
NEO,
Ethereum,
Bitcoin,
}
impl Blockchain {
pub fn all() -> Vec<Blockchain> {
vec![Self::Bitcoin, Self::Ethereum, Self::NEO]
}
}
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Asset {
ETH,
BAT,
OMG,
USDC,
USDT,
ZRX,
LINK,
QNT,
RLC,
ANT,
BTC,
NEO,
GAS,
TRAC,
GUNTHY,
NNN,
}
impl Asset {
pub fn blockchain(&self) -> Blockchain {
match self {
Self::ETH => Blockchain::Ethereum,
Self::USDC => Blockchain::Ethereum,
Self::USDT => Blockchain::Ethereum,
Self::BAT => Blockchain::Ethereum,
Self::OMG => Blockchain::Ethereum,
Self::ZRX => Blockchain::Ethereum,
Self::LINK => Blockchain::Ethereum,
Self::QNT => Blockchain::Ethereum,
Self::RLC => Blockchain::Ethereum,
Self::ANT => Blockchain::Ethereum,
Self::TRAC => Blockchain::Ethereum,
Self::GUNTHY => Blockchain::Ethereum,
Self::BTC => Blockchain::Bitcoin,
Self::NEO => Blockchain::NEO,
Self::GAS => Blockchain::NEO,
Self::NNN => Blockchain::NEO,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::ETH => "eth",
Self::USDC => "usdc",
Self::USDT => "usdt",
Self::BAT => "bat",
Self::OMG => "omg",
Self::ZRX => "zrx",
Self::LINK => "link",
Self::QNT => "qnt",
Self::RLC => "rlc",
Self::ANT => "ant",
Self::BTC => "btc",
Self::NEO => "neo",
Self::GAS => "gas",
Self::TRAC => "trac",
Self::GUNTHY => "gunthy",
Self::NNN => "nnn",
}
}
pub fn from_str(asset_str: &str) -> Result<Self> {
match asset_str {
"eth" => Ok(Self::ETH),
"usdc" => Ok(Self::USDC),
"usdt" => Ok(Self::USDT),
"bat" => Ok(Self::BAT),
"omg" => Ok(Self::OMG),
"zrx" => Ok(Self::ZRX),
"link" => Ok(Self::LINK),
"qnt" => Ok(Self::QNT),
"rlc" => Ok(Self::RLC),
"ant" => Ok(Self::ANT),
"btc" => Ok(Self::BTC),
"neo" => Ok(Self::NEO),
"gas" => Ok(Self::GAS),
"trac" => Ok(Self::TRAC),
"gunthy" => Ok(Self::GUNTHY),
"nnn" => Ok(Self::NNN),
_ => Err(ProtocolError("Asset not known")),
}
}
pub fn assets() -> Vec<Self> {
vec![
Self::ETH,
Self::USDC,
Self::USDT,
Self::BAT,
Self::OMG,
Self::ZRX,
Self::LINK,
Self::QNT,
Self::ANT,
Self::BTC,
Self::NEO,
Self::GAS,
Self::TRAC,
Self::GUNTHY,
Self::NNN,
]
}
}
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct AssetofPrecision {
pub asset: Asset,
pub precision: u32,
}
impl Into<Asset> for AssetofPrecision {
fn into(self) -> Asset {
self.asset
}
}
impl AssetofPrecision {
pub fn with_amount(&self, amount_str: &str) -> Result<AssetAmount> {
let amount = Amount::new(amount_str, self.precision)?;
Ok(AssetAmount {
asset: *self,
amount,
})
}
}
impl Asset {
pub fn with_precision(&self, precision: u32) -> AssetofPrecision {
AssetofPrecision {
asset: *self,
precision,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AssetAmount {
pub asset: AssetofPrecision,
pub amount: Amount,
}
impl AssetAmount {
pub fn exchange_at(&self, rate: &Rate, into_asset: AssetofPrecision) -> Result<AssetAmount> {
let new_amount = self.amount.to_bigdecimal() * rate.to_bigdecimal()?;
Ok(AssetAmount {
asset: into_asset,
amount: Amount::from_bigdecimal(new_amount, into_asset.precision),
})
}
}
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Market {
pub asset_a: AssetofPrecision,
pub asset_b: AssetofPrecision,
}
impl Market {
pub fn new(asset_a: AssetofPrecision, asset_b: AssetofPrecision) -> Self {
Self { asset_a, asset_b }
}
pub fn market_name(&self) -> String {
format!(
"{}_{}",
self.asset_a.asset.name(),
self.asset_b.asset.name()
)
}
pub fn blockchains(&self) -> Vec<Blockchain> {
let chain_a = self.asset_a.asset.blockchain();
let chain_b = self.asset_b.asset.blockchain();
if chain_a == chain_b {
vec![chain_a]
} else {
vec![chain_a, chain_b]
}
}
pub fn get_asset(&self, asset_name: &str) -> Result<AssetofPrecision> {
if asset_name == self.asset_a.asset.name() {
Ok(self.asset_a.clone())
} else if asset_name == self.asset_b.asset.name() {
Ok(self.asset_b.clone())
} else {
Err(ProtocolError("Asset not associated with market"))
}
}
pub fn btc_usdc() -> Self {
Market::new(Asset::BTC.with_precision(8), Asset::USDC.with_precision(1))
}
pub fn eth_usdc() -> Self {
Market::new(Asset::ETH.with_precision(4), Asset::USDC.with_precision(2))
}
pub fn neo_usdc() -> Self {
Market::new(Asset::NEO.with_precision(3), Asset::USDC.with_precision(2))
}
pub fn eth_btc() -> Self {
Market::new(Asset::ETH.with_precision(6), Asset::BTC.with_precision(5))
}
pub fn from_str(market_str: &str) -> Result<Self> {
match market_str {
"btc_usdc" => Ok(Self::btc_usdc()),
"eth_usdc" => Ok(Self::eth_usdc()),
"neo_usdc" => Ok(Self::neo_usdc()),
"eth_btc" => Ok(Self::eth_btc()),
_ => Err(ProtocolError("Market not supported")),
}
}
}
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BuyOrSell {
Buy,
Sell,
}
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum OrderType {
Market,
Limit,
StopMarket,
StopLimit,
}
impl std::fmt::Display for OrderType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Rate {
OrderRate(OrderRate),
MaxOrderRate,
MinOrderRate,
FeeRate(FeeRate),
MaxFeeRate,
MinFeeRate,
}
impl From<OrderRate> for Rate {
fn from(rate: OrderRate) -> Self {
Self::OrderRate(rate)
}
}
impl Rate {
pub fn to_bigdecimal(&self) -> Result<BigDecimal> {
let num = match self {
Self::FeeRate(rate) | Self::OrderRate(rate) => rate.inner.clone(),
Self::MaxOrderRate | Self::MaxFeeRate => {
BigDecimal::from_str("0.0025").unwrap()
}
Self::MinOrderRate | Self::MinFeeRate => 0.into(),
};
Ok(num)
}
pub fn invert_rate(&self, precision: Option<u32>) -> Result<Self> {
match self {
Self::OrderRate(rate) => Ok(Self::OrderRate(rate.invert_rate(precision))),
_ => Err(ProtocolError(
"Cannot invert a Rate that is not an OrderRate",
)),
}
}
pub fn subtract_fee(&self, fee: BigDecimal) -> Result<OrderRate> {
let as_order_rate = OrderRate {
inner: self.to_bigdecimal()?,
};
Ok(as_order_rate.subtract_fee(fee))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct OrderRate {
inner: BigDecimal,
}
impl OrderRate {
pub fn new(str_num: &str) -> Result<Self> {
BigDecimal::from_str(str_num)
.map_err(|_| ProtocolError("String to BigDecimal failed in creating OrderRate"))
.map(|inner| Self { inner })
}
pub fn from_bigdecimal(decimal: BigDecimal) -> Self {
Self { inner: decimal }
}
pub fn invert_rate(&self, precision: Option<u32>) -> Self {
let mut inverse = self.inner.inverse();
if let Some(precision) = precision {
let scale_num = BigDecimal::from(u64::pow(10, precision));
inverse = (&self.inner * &scale_num).with_scale(0) / scale_num;
}
Self { inner: inverse }
}
pub fn to_bigdecimal(&self) -> BigDecimal {
self.inner.clone()
}
pub fn subtract_fee(&self, fee: BigDecimal) -> Self {
let fee_multiplier = BigDecimal::from(1) - fee;
let inner = &self.inner * &fee_multiplier;
OrderRate { inner }
}
}
type FeeRate = OrderRate;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Amount {
pub precision: u32,
pub value: BigDecimal,
}
impl Amount {
pub fn new(str_num: &str, precision: u32) -> Result<Self> {
let value = BigDecimal::from_str(str_num)
.map_err(|_| ProtocolError("String to BigDecimal failed in creating Amount"))?;
Ok(Self { value, precision })
}
pub fn from_bigdecimal(value: BigDecimal, precision: u32) -> Self {
Self { value, precision }
}
pub fn to_bigdecimal(&self) -> BigDecimal {
self.value.clone()
}
}
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
pub enum Nonce {
Value(u32),
Crosschain,
}
impl Nonce {
pub fn crosschain() -> u32 {
0xffff_ffff
}
}
impl Into<i64> for Nonce {
fn into(self) -> i64 {
match self {
Self::Value(value) => value as i64,
Self::Crosschain => Nonce::crosschain() as i64,
}
}
}
impl Into<u32> for Nonce {
fn into(self) -> u32 {
match self {
Self::Value(value) => value as u32,
Self::Crosschain => Nonce::crosschain() as u32,
}
}
}
impl From<u32> for Nonce {
fn from(val: u32) -> Self {
if val == Nonce::crosschain() {
Self::Crosschain
} else {
Self::Value(val)
}
}
}
impl From<&u32> for Nonce {
fn from(val: &u32) -> Self {
if val == &Nonce::crosschain() {
Self::Crosschain
} else {
Self::Value(*val)
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum CandleInterval {
FifteenMinute,
FiveMinute,
FourHour,
OneDay,
OneHour,
OneMinute,
OneMonth,
OneWeek,
SixHour,
ThirtyMinute,
ThreeHour,
TwelveHour,
}
#[derive(Debug)]
pub struct Candle {
pub a_volume: AssetAmount,
pub b_volume: AssetAmount,
pub close_price: AssetAmount,
pub high_price: AssetAmount,
pub low_price: AssetAmount,
pub open_price: AssetAmount,
pub interval: CandleInterval,
pub interval_start: DateTime<Utc>,
}
#[derive(Clone, Copy, Debug)]
pub struct DateTimeRange {
pub start: DateTime<Utc>,
pub stop: DateTime<Utc>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OrderStatus {
Pending,
Open,
Filled,
Canceled,
}
#[derive(Clone, Debug, PartialEq)]
pub enum AccountTradeSide {
Maker,
Taker,
None,
}
#[derive(Clone, Debug)]
pub struct Trade {
pub id: String,
pub taker_order_id: String,
pub maker_order_id: String,
pub amount: AssetAmount,
pub executed_at: DateTime<Utc>,
pub account_side: AccountTradeSide,
pub maker_fee: AssetAmount,
pub taker_fee: AssetAmount,
pub maker_recieved: AssetAmount,
pub taker_recieved: AssetAmount,
pub market: Market,
pub direction: BuyOrSell,
pub limit_price: AssetAmount,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum OrderCancellationPolicy {
FillOrKill,
GoodTilCancelled,
GoodTilTime(DateTime<Utc>),
ImmediateOrCancel,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum OrderCancellationReason {
AdminCancelled,
Expiration,
InvalidForOrderbookState,
NoFill,
User,
}
#[derive(Clone, Debug)]
pub struct Order {
pub id: String,
pub amount_placed: AssetAmount,
pub amount_remaining: AssetAmount,
pub amount_executed: AssetAmount,
pub limit_price: Option<AssetAmount>,
pub stop_price: Option<AssetAmount>,
pub placed_at: DateTime<Utc>,
pub buy_or_sell: BuyOrSell,
pub cancellation_policy: Option<OrderCancellationPolicy>,
pub cancellation_reason: Option<OrderCancellationReason>,
pub market: Market,
pub order_type: OrderType,
pub status: OrderStatus,
pub trades: Vec<Trade>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OrderbookOrder {
pub price: String,
pub amount: AssetAmount,
}
#[cfg(test)]
mod tests {
use super::{BigDecimal, FromStr, OrderRate};
#[test]
fn fee_rate_conversion_precision() {
let rate = OrderRate::new("150").unwrap();
let inverted_rate = rate.invert_rate(None);
let minus_fee = inverted_rate.subtract_fee(BigDecimal::from_str("0.0025").unwrap());
let payload = minus_fee.to_be_bytes().unwrap();
assert_eq!(665000, u64::from_be_bytes(payload));
}
}