use crate::{
UnindexedAccountEvent, UnindexedAccountSnapshot,
balance::AssetBalance,
error::UnindexedClientError,
order::{
Order,
bracket::{BracketOrderRequest, BracketOrderResult},
request::{OrderRequestCancel, OrderRequestOpen, UnindexedOrderResponseCancel},
state::{Open, UnindexedOrderState},
},
trade::Trade,
};
use chrono::{DateTime, Utc};
use futures::Stream;
use rustrade_instrument::{
asset::name::AssetNameExchange,
exchange::ExchangeId,
instrument::{kind::InstrumentKindDiscriminant, name::InstrumentNameExchange},
};
use std::future::Future;
#[cfg(any(feature = "binance", feature = "hyperliquid", feature = "ibkr"))]
pub(crate) mod dedup;
#[cfg(feature = "alpaca")]
pub mod alpaca;
#[cfg(feature = "binance")]
pub mod binance;
#[cfg(feature = "hyperliquid")]
pub mod hyperliquid;
#[cfg(feature = "ibkr")]
pub mod ibkr;
pub mod mock;
pub trait ExecutionClient
where
Self: Clone,
{
const EXCHANGE: ExchangeId;
const SUPPORTED_KINDS: &'static [InstrumentKindDiscriminant];
type Config: Clone;
type AccountStream: Stream<Item = UnindexedAccountEvent> + Send;
fn new(config: Self::Config) -> Self;
fn account_snapshot(
&self,
assets: &[AssetNameExchange],
instruments: &[InstrumentNameExchange],
) -> impl Future<Output = Result<UnindexedAccountSnapshot, UnindexedClientError>> + Send;
fn account_stream(
&self,
assets: &[AssetNameExchange],
instruments: &[InstrumentNameExchange],
) -> impl Future<Output = Result<Self::AccountStream, UnindexedClientError>> + Send;
fn cancel_order(
&self,
request: OrderRequestCancel<ExchangeId, &InstrumentNameExchange>,
) -> impl Future<Output = Option<UnindexedOrderResponseCancel>> + Send;
fn cancel_orders<'a>(
&self,
requests: impl IntoIterator<Item = OrderRequestCancel<ExchangeId, &'a InstrumentNameExchange>>,
) -> impl Stream<Item = Option<UnindexedOrderResponseCancel>> + Send {
futures::stream::FuturesUnordered::from_iter(
requests
.into_iter()
.map(|request| self.cancel_order(request)),
)
}
fn open_order(
&self,
request: OrderRequestOpen<ExchangeId, &InstrumentNameExchange>,
) -> impl Future<Output = Option<Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>>>
+ Send;
fn open_orders<'a>(
&self,
requests: impl IntoIterator<Item = OrderRequestOpen<ExchangeId, &'a InstrumentNameExchange>>,
) -> impl Stream<Item = Option<Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>>> + Send
{
futures::stream::FuturesUnordered::from_iter(
requests.into_iter().map(|request| self.open_order(request)),
)
}
fn fetch_balances(
&self,
assets: &[AssetNameExchange],
) -> impl Future<Output = Result<Vec<AssetBalance<AssetNameExchange>>, UnindexedClientError>> + Send;
fn fetch_open_orders(
&self,
instruments: &[InstrumentNameExchange],
) -> impl Future<
Output = Result<Vec<Order<ExchangeId, InstrumentNameExchange, Open>>, UnindexedClientError>,
> + Send;
fn fetch_trades(
&self,
time_since: DateTime<Utc>,
instruments: &[InstrumentNameExchange],
) -> impl Future<
Output = Result<
Vec<Trade<AssetNameExchange, InstrumentNameExchange>>,
UnindexedClientError,
>,
> + Send;
}
pub trait BracketOrderClient: ExecutionClient {
fn open_bracket_order(
&self,
request: BracketOrderRequest<ExchangeId, &InstrumentNameExchange>,
) -> impl Future<Output = BracketOrderResult> + Send;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::mock::MockExecution;
#[test]
fn mock_supports_only_the_kinds_it_can_model() {
assert_eq!(
<MockExecution<fn() -> DateTime<Utc>> as ExecutionClient>::SUPPORTED_KINDS,
&[
InstrumentKindDiscriminant::Spot,
InstrumentKindDiscriminant::Cfd
]
);
}
#[cfg(feature = "binance")]
#[test]
fn binance_spot_and_margin_are_spot_only() {
use crate::client::binance::{BinanceMargin, BinanceSpot};
assert_eq!(
BinanceSpot::SUPPORTED_KINDS,
&[InstrumentKindDiscriminant::Spot]
);
assert_eq!(
BinanceMargin::SUPPORTED_KINDS,
&[InstrumentKindDiscriminant::Spot]
);
}
#[cfg(feature = "hyperliquid")]
#[test]
fn hyperliquid_splits_perpetual_and_spot_across_two_clients() {
use crate::client::hyperliquid::{HyperliquidClient, spot::HyperliquidSpotClient};
assert_eq!(
HyperliquidClient::SUPPORTED_KINDS,
&[InstrumentKindDiscriminant::Perpetual]
);
assert_eq!(
HyperliquidSpotClient::SUPPORTED_KINDS,
&[InstrumentKindDiscriminant::Spot]
);
}
#[cfg(feature = "alpaca")]
#[test]
fn alpaca_trades_equities_and_options() {
use crate::client::alpaca::AlpacaClient;
assert_eq!(
AlpacaClient::SUPPORTED_KINDS,
&[
InstrumentKindDiscriminant::Spot,
InstrumentKindDiscriminant::Option
]
);
}
#[cfg(feature = "ibkr")]
#[test]
fn ibkr_omits_cfd_because_it_builds_no_contract_for_one() {
use crate::client::ibkr::IbkrClient;
assert_eq!(
IbkrClient::SUPPORTED_KINDS,
&[
InstrumentKindDiscriminant::Spot,
InstrumentKindDiscriminant::Future,
InstrumentKindDiscriminant::Option
]
);
}
}