#[cfg(feature = "mexc")]
pub mod mexc;
pub mod simulated;
use chrono::{DateTime, Utc};
use derive_getters::Getters;
#[cfg(test)]
use mockall::automock;
use rust_decimal::Decimal;
use std::fmt;
use std::future::Future;
use crate::clock::Clock;
#[cfg(test)]
use crate::clock::RunningCheatClock;
#[cfg(test)]
use crate::core::CoreError;
use crate::core::CoreResult;
use crate::data::{Crypto, KlineInterval, Order, SpecificOrderDetails};
#[cfg(test)]
use crate::market::__mock_MockMarket_Market::__source_name::Context;
pub use mexc_rs::spot::v3::{
cancel_order::CancelOrderOutput, enums as mexc_enums, query_order::QueryOrderOutput,
};
#[derive(Debug, Clone, Copy)]
pub struct Balance {
pub free: Decimal,
pub locked: Decimal,
}
#[derive(Debug, Clone, Getters, PartialEq)]
pub struct KlinesParams {
asset: Crypto,
quote: Crypto,
interval: KlineInterval,
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
}
impl KlinesParams {
pub fn new(
asset: Crypto,
quote: Crypto,
interval: KlineInterval,
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
) -> Self {
let (below_start, _) = interval.get_time_bounds(start_time).unwrap();
let (below_end, above_end) = interval.get_time_bounds(end_time).unwrap();
let final_end = if below_end == end_time {
end_time
} else {
above_end
};
Self {
asset,
quote,
interval,
start_time: below_start,
end_time: final_end,
}
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
pub struct Kline {
#[serde(with = "chrono::serde::ts_seconds")]
pub open_time: DateTime<Utc>,
pub open: Decimal,
pub high: Decimal,
pub low: Decimal,
pub close: Decimal,
pub volume: Decimal,
#[serde(with = "chrono::serde::ts_seconds")]
pub close_time: DateTime<Utc>,
pub quote_asset_volume: Decimal,
}
impl Kline {
#[cfg(test)]
pub fn vec_over(
interval: KlineInterval,
mut start: DateTime<Utc>,
end: DateTime<Utc>,
kline_model: Kline,
) -> CoreResult<Vec<Kline>> {
if interval > KlineInterval::OneDay {
return Err(CoreError::param_error("interval not implemented"));
}
let mut vec = vec![];
loop {
let next_end = start + interval.time_delta();
if next_end > end {
break;
}
let kline = Kline {
open_time: start,
close_time: next_end,
..kline_model
};
vec.push(kline);
start = next_end;
}
Ok(vec)
}
}
pub trait MarketFactory: Send + Sync + 'static {
type _Market: Market;
fn with_clock(self, clock: <Self::_Market as Market>::_Clock) -> Self;
fn build(self) -> impl Future<Output = CoreResult<Self::_Market>> + Send;
}
#[cfg_attr(test, automock(type _Clock = RunningCheatClock; type _OrderDetails = crate::data::EmptySpecificOrderDetails;))]
pub trait Market: Send + Sync + fmt::Debug + 'static {
type _Clock: Clock;
type _OrderDetails: SpecificOrderDetails;
fn source_name() -> String;
fn klines_limit(&self) -> i32;
fn klines(&self, params: KlinesParams) -> impl Future<Output = CoreResult<Vec<Kline>>> + Send;
fn get_balance(&self, crypto: Crypto) -> impl Future<Output = CoreResult<Balance>> + Send;
fn send_order(
&self,
order: &mut Order<Self::_OrderDetails>,
) -> impl Future<Output = CoreResult<()>> + Send;
fn cancel_order(
&self,
order: &Order<Self::_OrderDetails>,
) -> impl Future<Output = CoreResult<CancelOrderOutput>> + Send;
fn update_order(
&self,
order: &Order<Self::_OrderDetails>,
) -> impl Future<Output = CoreResult<QueryOrderOutput>> + Send;
}
#[cfg(test)]
pub fn mock_context() -> Context {
let ctx = MockMarket::source_name_context();
ctx.expect().returning(|| "mock_market".to_string());
ctx
}
#[cfg(test)]
mod tests {
use chrono::{TimeZone as _, Utc};
use crate::data::{Crypto, KlineInterval};
use super::KlinesParams;
#[test]
fn create_klines_params() {
let params = KlinesParams::new(
Crypto::BTC,
Crypto::USDT,
KlineInterval::OneMinute,
Utc.with_ymd_and_hms(2022, 1, 1, 0, 0, 59).unwrap(),
Utc.with_ymd_and_hms(2022, 1, 1, 0, 1, 1).unwrap(),
);
assert_eq!(params.asset, Crypto::BTC);
assert_eq!(params.quote, Crypto::USDT);
assert_eq!(params.interval, KlineInterval::OneMinute);
assert_eq!(
params.start_time,
Utc.with_ymd_and_hms(2022, 1, 1, 0, 0, 0).unwrap()
);
assert_eq!(
params.end_time,
Utc.with_ymd_and_hms(2022, 1, 1, 0, 2, 0).unwrap()
);
}
}