Skip to main content

Trading

Trait Trading 

Source
pub trait Trading: ExchangeIdentity {
    // Required methods
    fn place_order<'life0, 'async_trait>(
        &'life0 self,
        req: OrderRequest,
    ) -> Pin<Box<dyn Future<Output = ExchangeResult<PlaceOrderResponse>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait;
    fn cancel_order<'life0, 'async_trait>(
        &'life0 self,
        req: CancelRequest,
    ) -> Pin<Box<dyn Future<Output = ExchangeResult<Order>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait;
    fn get_order<'life0, 'life1, 'life2, 'async_trait>(
        &'life0 self,
        symbol: &'life1 str,
        order_id: &'life2 str,
        account_type: AccountType,
    ) -> Pin<Box<dyn Future<Output = ExchangeResult<Order>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait;
    fn get_open_orders<'life0, 'life1, 'async_trait>(
        &'life0 self,
        symbol: Option<&'life1 str>,
        account_type: AccountType,
    ) -> Pin<Box<dyn Future<Output = ExchangeResult<Vec<Order>>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;
    fn get_order_history<'life0, 'async_trait>(
        &'life0 self,
        filter: OrderHistoryFilter,
        account_type: AccountType,
    ) -> Pin<Box<dyn Future<Output = ExchangeResult<Vec<Order>>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait;

    // Provided methods
    fn get_user_trades<'life0, 'async_trait>(
        &'life0 self,
        filter: UserTradeFilter,
        account_type: AccountType,
    ) -> Pin<Box<dyn Future<Output = ExchangeResult<Vec<UserTrade>>> + Send + 'async_trait>>
       where Self: Sync + 'async_trait,
             'life0: 'async_trait { ... }
    fn trading_capabilities(
        &self,
        account_type: AccountType,
    ) -> TradingCapabilities { ... }
}
Expand description

Core trading — 24/24 exchanges.

All order types go through place_order via OrderType enum. Connectors match the variants they support; unmatched variants return ExchangeError::UnsupportedOperation.

§Strict Non-Composition Rule

Connectors MUST NOT simulate unsupported variants by composing base methods.

  • A connector without native batch cancel MUST NOT loop cancel_order.
  • A connector without native Bracket MUST NOT submit 3 separate orders. If the exchange has no endpoint for it, return UnsupportedOperation.

Required Methods§

Source

fn place_order<'life0, 'async_trait>( &'life0 self, req: OrderRequest, ) -> Pin<Box<dyn Future<Output = ExchangeResult<PlaceOrderResponse>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Place an order of any type.

Connectors inspect req.order_type and match the variants they support.

Returns PlaceOrderResponse::Simple for scalar orders, or the appropriate composite variant for Bracket/OCO/Algo orders.

Source

fn cancel_order<'life0, 'async_trait>( &'life0 self, req: CancelRequest, ) -> Pin<Box<dyn Future<Output = ExchangeResult<Order>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Cancel one order, a batch, all orders, or all orders for a symbol.

The scope is determined by req.scope (CancelScope enum). Connectors that only support single-cancel MUST return UnsupportedOperation for Batch/All/BySymbol scopes — never loop.

Source

fn get_order<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, symbol: &'life1 str, order_id: &'life2 str, account_type: AccountType, ) -> Pin<Box<dyn Future<Output = ExchangeResult<Order>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Get the current state of a single order by ID.

symbol is required by most exchanges; provide it when available.

Source

fn get_open_orders<'life0, 'life1, 'async_trait>( &'life0 self, symbol: Option<&'life1 str>, account_type: AccountType, ) -> Pin<Box<dyn Future<Output = ExchangeResult<Vec<Order>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Get all currently open orders, optionally filtered to a single symbol.

symbol = None fetches open orders across all symbols. Not all exchanges support symbol-less open order queries — those that don’t MUST return UnsupportedOperation for None, not an empty vec.

Source

fn get_order_history<'life0, 'async_trait>( &'life0 self, filter: OrderHistoryFilter, account_type: AccountType, ) -> Pin<Box<dyn Future<Output = ExchangeResult<Vec<Order>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Get closed / filled / cancelled order history with filtering.

Provided Methods§

Source

fn get_user_trades<'life0, 'async_trait>( &'life0 self, filter: UserTradeFilter, account_type: AccountType, ) -> Pin<Box<dyn Future<Output = ExchangeResult<Vec<UserTrade>>> + Send + 'async_trait>>
where Self: Sync + 'async_trait, 'life0: 'async_trait,

Get the user’s own trade fills (executions).

Returns individual fill records for completed orders, optionally filtered by symbol, order ID, or time range.

Default implementation returns UnsupportedOperation — connectors that expose a native fills/trades endpoint should override this.

~20/24: all major CEX exchanges. DEX connectors without native trade records return UnsupportedOperation.

Examples found in repository?
examples/e2e_smoke.rs (lines 1225-1227)
1118async fn test_trading(id: ExchangeId) -> TradingRow {
1119    let harness = TestHarness::new();
1120    let conn = match harness.create_authenticated(id).await {
1121        None => {
1122            // Also check direct ENV vars as fallback
1123            match trading::load_credentials(id) {
1124                None => {
1125                    return TradingRow {
1126                        exchange: format!("{:?}", id),
1127                        balance: MethodResult::Skipped,
1128                        account_info: MethodResult::Skipped,
1129                        open_orders: MethodResult::Skipped,
1130                        user_trades: MethodResult::Skipped,
1131                        positions: MethodResult::Skipped,
1132                        fees: MethodResult::Skipped,
1133                        issues: vec!["no_credentials_in_env".into()],
1134                    };
1135                }
1136                Some(_) => {
1137                    // Has ENV creds but TestHarness didn't pick them up (.env file missing)
1138                    return TradingRow {
1139                        exchange: format!("{:?}", id),
1140                        balance: MethodResult::Skipped,
1141                        account_info: MethodResult::Skipped,
1142                        open_orders: MethodResult::Skipped,
1143                        user_trades: MethodResult::Skipped,
1144                        positions: MethodResult::Skipped,
1145                        fees: MethodResult::Skipped,
1146                        issues: vec!["creds_in_env_but_not_dotenv".into()],
1147                    };
1148                }
1149            }
1150        }
1151        Some(Err(e)) => {
1152            let msg = truncate(&e.to_string(), 70);
1153            return TradingRow {
1154                exchange: format!("{:?}", id),
1155                balance: MethodResult::Err(format!("auth_connect_fail: {}", msg)),
1156                account_info: MethodResult::Skipped,
1157                open_orders: MethodResult::Skipped,
1158                user_trades: MethodResult::Skipped,
1159                positions: MethodResult::Skipped,
1160                fees: MethodResult::Skipped,
1161                issues: vec![format!("auth_fail: {}", msg)],
1162            };
1163        }
1164        Some(Ok(c)) => c,
1165    };
1166
1167    let (_, raw_str, account_type) = raw_symbol_for(id);
1168    let futures_at = if matches!(account_type, AccountType::FuturesCross | AccountType::FuturesIsolated) {
1169        account_type
1170    } else {
1171        AccountType::FuturesCross
1172    };
1173
1174    // balance
1175    let balance = {
1176        let conn = conn.clone();
1177        match timeout(Duration::from_secs(10),
1178            conn.get_balance(BalanceQuery { asset: None, account_type })).await {
1179            Ok(Ok(bs)) => MethodResult::Ok(format!("assets={}", bs.len())),
1180            Ok(Err(e)) => {
1181                let msg = e.to_string();
1182                if msg.contains("UnsupportedOperation") || msg.contains("Not supported:") { MethodResult::Unsupported(truncate(&msg, 50)) }
1183                else { MethodResult::Err(truncate(&msg, 60)) }
1184            }
1185            Err(_) => MethodResult::Timeout,
1186        }
1187    };
1188
1189    // account_info
1190    let account_info = {
1191        let conn = conn.clone();
1192        match timeout(Duration::from_secs(10),
1193            conn.get_account_info(account_type)).await {
1194            Ok(Ok(_)) => MethodResult::Ok("ok".into()),
1195            Ok(Err(e)) => {
1196                let msg = e.to_string();
1197                if msg.contains("UnsupportedOperation") || msg.contains("Not supported:") { MethodResult::Unsupported(truncate(&msg, 50)) }
1198                else { MethodResult::Err(truncate(&msg, 60)) }
1199            }
1200            Err(_) => MethodResult::Timeout,
1201        }
1202    };
1203
1204    // open_orders
1205    let open_orders = {
1206        let conn = conn.clone();
1207        let s = raw_str.clone();
1208        match timeout(Duration::from_secs(10),
1209            conn.get_open_orders(Some(&s), account_type)).await {
1210            Ok(Ok(os)) => MethodResult::Ok(format!("count={}", os.len())),
1211            Ok(Err(e)) => {
1212                let msg = e.to_string();
1213                if msg.contains("UnsupportedOperation") || msg.contains("Not supported:") { MethodResult::Unsupported(truncate(&msg, 50)) }
1214                else { MethodResult::Err(truncate(&msg, 60)) }
1215            }
1216            Err(_) => MethodResult::Timeout,
1217        }
1218    };
1219
1220    // user_trades
1221    let user_trades = {
1222        let conn = conn.clone();
1223        let s = raw_str.clone();
1224        match timeout(Duration::from_secs(10),
1225            conn.get_user_trades(
1226                UserTradeFilter { symbol: Some(s), order_id: None, start_time: None, end_time: None, limit: Some(5) },
1227                account_type)).await {
1228            Ok(Ok(ts)) => MethodResult::Ok(format!("count={}", ts.len())),
1229            Ok(Err(e)) => {
1230                let msg = e.to_string();
1231                if msg.contains("UnsupportedOperation") || msg.contains("Not supported:") { MethodResult::Unsupported(truncate(&msg, 50)) }
1232                else { MethodResult::Err(truncate(&msg, 60)) }
1233            }
1234            Err(_) => MethodResult::Timeout,
1235        }
1236    };
1237
1238    // positions (futures)
1239    let positions = if !is_futures(id, account_type) {
1240        MethodResult::Skipped
1241    } else {
1242        let conn = conn.clone();
1243        match timeout(Duration::from_secs(10),
1244            conn.get_positions(PositionQuery { symbol: None, account_type: futures_at })).await {
1245            Ok(Ok(ps)) => MethodResult::Ok(format!("count={}", ps.len())),
1246            Ok(Err(e)) => {
1247                let msg = e.to_string();
1248                if msg.contains("UnsupportedOperation") || msg.contains("Not supported:") { MethodResult::Unsupported(truncate(&msg, 50)) }
1249                else { MethodResult::Err(truncate(&msg, 60)) }
1250            }
1251            Err(_) => MethodResult::Timeout,
1252        }
1253    };
1254
1255    // fees
1256    let fees = {
1257        let conn = conn.clone();
1258        let s = raw_str.clone();
1259        match timeout(Duration::from_secs(10), conn.get_fees(Some(&s))).await {
1260            Ok(Ok(f)) => MethodResult::Ok(format!("maker={:.6} taker={:.6}", f.maker_rate, f.taker_rate)),
1261            Ok(Err(e)) => {
1262                let msg = e.to_string();
1263                if msg.contains("UnsupportedOperation") || msg.contains("Not supported:") { MethodResult::Unsupported(truncate(&msg, 50)) }
1264                else { MethodResult::Err(truncate(&msg, 60)) }
1265            }
1266            Err(_) => MethodResult::Timeout,
1267        }
1268    };
1269
1270    let mut issues: Vec<String> = Vec::new();
1271    for (name, result) in [
1272        ("balance", &balance), ("account_info", &account_info),
1273        ("open_orders", &open_orders), ("user_trades", &user_trades),
1274        ("positions", &positions), ("fees", &fees),
1275    ] {
1276        if result.is_issue() {
1277            if let Some(d) = result.detail() {
1278                issues.push(format!("{}: {}", name, d));
1279            } else {
1280                issues.push(format!("{}: {:?}", name, result));
1281            }
1282        }
1283    }
1284
1285    TradingRow {
1286        exchange: format!("{:?}", id),
1287        balance, account_info, open_orders, user_trades, positions, fees,
1288        issues,
1289    }
1290}
Source

fn trading_capabilities(&self, account_type: AccountType) -> TradingCapabilities

Returns the connector’s trading capabilities.

The default implementation returns permissive defaults. Connectors with specific limitations should override this method.

Implementors§

Source§

impl Trading for FinnhubConnector

Source§

impl Trading for KrxConnector

Source§

impl Trading for YahooFinanceConnector

Source§

impl Trading for AlphaVantageConnector

Source§

impl Trading for JQuantsConnector

Source§

impl Trading for TiingoConnector

Source§

impl Trading for TwelvedataConnector

Source§

impl Trading for MoexConnector

Source§

impl Trading for CryptoCompareConnector

Source§

impl Trading for PolygonConnector

Source§

impl Trading for DukascopyConnector

Source§

impl Trading for OandaConnector

Source§

impl Trading for FutuConnector

Source§

impl Trading for AngelOneConnector

Source§

impl Trading for DhanConnector

Source§

impl Trading for FyersConnector

Source§

impl Trading for UpstoxConnector

Source§

impl Trading for ZerodhaConnector

Source§

impl Trading for TinkoffConnector

Source§

impl Trading for AlpacaConnector

Source§

impl Trading for BinanceConnector

Source§

impl Trading for BingxConnector

Source§

impl Trading for BitfinexConnector

Source§

impl Trading for BitgetConnector

Source§

impl Trading for BitstampConnector

Source§

impl Trading for BybitConnector

Source§

impl Trading for CoinbaseConnector

Source§

impl Trading for CryptoComConnector

Source§

impl Trading for DeribitConnector

Source§

impl Trading for GateioConnector

Source§

impl Trading for GeminiConnector

Source§

impl Trading for HtxConnector

Source§

impl Trading for HyperliquidConnector

Source§

impl Trading for KrakenConnector

Source§

impl Trading for KuCoinConnector

Source§

impl Trading for MexcConnector

Source§

impl Trading for OkxConnector

Source§

impl Trading for UpbitConnector

Source§

impl Trading for DydxConnector

Source§

impl Trading for LighterConnector

Source§

impl Trading for PolymarketConnector