Skip to main content

trader_bot/exchange/
provider.rs

1//! Exchange provider trait definition.
2
3use async_trait::async_trait;
4
5use super::{ExchangeInfo, ExchangeResult, Instrument, MarketData, OrderBook};
6use crate::domain::{Order, OrderDirection, Wallet};
7
8/// Trait for exchange/broker API providers.
9///
10/// This abstraction allows the balancer to work with different exchanges
11/// (T-Bank, Binance, Interactive Brokers, etc.) through a unified interface.
12#[async_trait]
13pub trait ExchangeProvider: Send + Sync {
14    /// Returns information about this exchange.
15    fn info(&self) -> &ExchangeInfo;
16
17    /// Returns the exchange identifier.
18    fn id(&self) -> &str {
19        &self.info().id
20    }
21
22    /// Returns the exchange name.
23    fn name(&self) -> &str {
24        &self.info().name
25    }
26
27    /// Tests the connection and authentication.
28    async fn ping(&self) -> ExchangeResult<()>;
29
30    /// Gets account information and current positions (wallet).
31    async fn get_wallet(&self, account_id: &str) -> ExchangeResult<Wallet>;
32
33    /// Gets information about a specific instrument.
34    async fn get_instrument(&self, symbol: &str) -> ExchangeResult<Instrument>;
35
36    /// Gets a list of all available instruments.
37    async fn list_instruments(&self) -> ExchangeResult<Vec<Instrument>>;
38
39    /// Gets current market data for an instrument.
40    async fn get_market_data(&self, symbol: &str) -> ExchangeResult<MarketData>;
41
42    /// Gets market data for multiple instruments.
43    async fn get_market_data_batch(&self, symbols: &[String]) -> ExchangeResult<Vec<MarketData>> {
44        let mut results = Vec::with_capacity(symbols.len());
45        for symbol in symbols {
46            results.push(self.get_market_data(symbol).await?);
47        }
48        Ok(results)
49    }
50
51    /// Gets the order book for an instrument.
52    async fn get_order_book(&self, symbol: &str, depth: u32) -> ExchangeResult<OrderBook>;
53
54    /// Places an order.
55    async fn place_order(&self, account_id: &str, order: Order) -> ExchangeResult<Order>;
56
57    /// Cancels an order.
58    async fn cancel_order(&self, account_id: &str, order_id: &str) -> ExchangeResult<Order>;
59
60    /// Gets the current status of an order.
61    async fn get_order(&self, account_id: &str, order_id: &str) -> ExchangeResult<Order>;
62
63    /// Gets all active orders.
64    async fn get_active_orders(&self, account_id: &str) -> ExchangeResult<Vec<Order>>;
65
66    /// Checks if the market is currently open for a symbol.
67    async fn is_market_open(&self, symbol: &str) -> ExchangeResult<bool> {
68        let data = self.get_market_data(symbol).await?;
69        Ok(data.market_status.is_tradeable())
70    }
71
72    /// Creates a market buy order.
73    async fn market_buy(&self, account_id: &str, symbol: &str, lots: u32) -> ExchangeResult<Order> {
74        let order = Order::market(
75            generate_order_id(),
76            symbol,
77            self.id(),
78            OrderDirection::Buy,
79            lots,
80        );
81        self.place_order(account_id, order).await
82    }
83
84    /// Creates a market sell order.
85    async fn market_sell(
86        &self,
87        account_id: &str,
88        symbol: &str,
89        lots: u32,
90    ) -> ExchangeResult<Order> {
91        let order = Order::market(
92            generate_order_id(),
93            symbol,
94            self.id(),
95            OrderDirection::Sell,
96            lots,
97        );
98        self.place_order(account_id, order).await
99    }
100}
101
102/// Generates a unique order ID.
103fn generate_order_id() -> String {
104    use std::sync::atomic::{AtomicU64, Ordering};
105    use std::time::{SystemTime, UNIX_EPOCH};
106
107    // Atomic counter to ensure uniqueness even if called in same nanosecond
108    static COUNTER: AtomicU64 = AtomicU64::new(0);
109
110    let timestamp = SystemTime::now()
111        .duration_since(UNIX_EPOCH)
112        .unwrap_or_default()
113        .as_nanos();
114    let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
115    format!("ORD-{timestamp}-{counter}")
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn test_order_id_generation() {
124        let id1 = generate_order_id();
125        let id2 = generate_order_id();
126
127        assert!(id1.starts_with("ORD-"));
128        assert!(id2.starts_with("ORD-"));
129        // IDs should be unique (though not guaranteed in tests due to timing)
130        assert_ne!(id1, id2);
131    }
132}