use async_trait::async_trait;
use super::{ExchangeInfo, ExchangeResult, Instrument, MarketData, OrderBook};
use crate::domain::{Order, OrderDirection, Wallet};
#[async_trait]
pub trait ExchangeProvider: Send + Sync {
fn info(&self) -> &ExchangeInfo;
fn id(&self) -> &str {
&self.info().id
}
fn name(&self) -> &str {
&self.info().name
}
async fn ping(&self) -> ExchangeResult<()>;
async fn get_wallet(&self, account_id: &str) -> ExchangeResult<Wallet>;
async fn get_instrument(&self, symbol: &str) -> ExchangeResult<Instrument>;
async fn list_instruments(&self) -> ExchangeResult<Vec<Instrument>>;
async fn get_market_data(&self, symbol: &str) -> ExchangeResult<MarketData>;
async fn get_market_data_batch(&self, symbols: &[String]) -> ExchangeResult<Vec<MarketData>> {
let mut results = Vec::with_capacity(symbols.len());
for symbol in symbols {
results.push(self.get_market_data(symbol).await?);
}
Ok(results)
}
async fn get_order_book(&self, symbol: &str, depth: u32) -> ExchangeResult<OrderBook>;
async fn place_order(&self, account_id: &str, order: Order) -> ExchangeResult<Order>;
async fn cancel_order(&self, account_id: &str, order_id: &str) -> ExchangeResult<Order>;
async fn get_order(&self, account_id: &str, order_id: &str) -> ExchangeResult<Order>;
async fn get_active_orders(&self, account_id: &str) -> ExchangeResult<Vec<Order>>;
async fn is_market_open(&self, symbol: &str) -> ExchangeResult<bool> {
let data = self.get_market_data(symbol).await?;
Ok(data.market_status.is_tradeable())
}
async fn market_buy(&self, account_id: &str, symbol: &str, lots: u32) -> ExchangeResult<Order> {
let order = Order::market(
generate_order_id(),
symbol,
self.id(),
OrderDirection::Buy,
lots,
);
self.place_order(account_id, order).await
}
async fn market_sell(
&self,
account_id: &str,
symbol: &str,
lots: u32,
) -> ExchangeResult<Order> {
let order = Order::market(
generate_order_id(),
symbol,
self.id(),
OrderDirection::Sell,
lots,
);
self.place_order(account_id, order).await
}
}
fn generate_order_id() -> String {
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
format!("ORD-{timestamp}-{counter}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_order_id_generation() {
let id1 = generate_order_id();
let id2 = generate_order_id();
assert!(id1.starts_with("ORD-"));
assert!(id2.starts_with("ORD-"));
assert_ne!(id1, id2);
}
}