Skip to main content

rustrade_execution/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(
3    clippy::unwrap_used,
4    clippy::expect_used,
5    clippy::cast_possible_truncation,
6    clippy::cast_sign_loss
7)]
8#![warn(
9    unused,
10    clippy::cognitive_complexity,
11    unused_crate_dependencies,
12    unused_extern_crates,
13    clippy::unused_self,
14    clippy::useless_let_if_seq,
15    missing_debug_implementations,
16    rust_2018_idioms
17)]
18#![allow(clippy::type_complexity, clippy::too_many_arguments, type_alias_bounds)]
19
20//! # Barter-Execution
21//! Stream private account data from financial venues, and execute (live or mock) orders. Also provides
22//! a feature rich MockExchange and MockExecutionClient to assist with backtesting and paper-trading.
23//!
24//! **It is:**
25//! * **Easy**: ExecutionClient trait provides a unified and simple language for interacting with exchanges.
26//! * **Normalised**: Allow your strategy to communicate with every real or MockExchange using the same interface.
27//! * **Extensible**: Barter-Execution is highly extensible, making it easy to contribute by adding new exchange integrations!
28//!
29//! See `README.md` for more information and examples.
30
31// Silence unused_crate_dependencies for dev-dependencies used only in tests
32#[cfg(test)]
33use serial_test as _;
34#[cfg(test)]
35use tracing_subscriber as _;
36#[cfg(test)]
37use wiremock as _;
38
39use crate::{
40    balance::AssetBalance,
41    order::{Order, OrderSnapshot, request::OrderResponseCancel},
42    position::Position,
43    trade::Trade,
44};
45use chrono::{DateTime, Utc};
46use derive_more::{Constructor, From};
47use order::state::OrderState;
48use rustrade_instrument::{
49    asset::{AssetIndex, name::AssetNameExchange},
50    exchange::{ExchangeId, ExchangeIndex},
51    instrument::{InstrumentIndex, name::InstrumentNameExchange},
52};
53use rustrade_integration::collection::snapshot::Snapshot;
54use serde::{Deserialize, Serialize};
55
56pub mod balance;
57pub mod client;
58pub mod error;
59pub mod exchange;
60pub mod fee;
61pub use fee::{FeeModel, FeeModelConfig, PerContractFeeModel, PercentageFeeModel, ZeroFeeModel};
62pub mod fill;
63pub use fill::{BidAskFillModel, FillModel, LastPriceFillModel, MidpointFillModel, SimFillConfig};
64pub mod indexer;
65pub mod map;
66pub mod order;
67pub mod position;
68pub mod trade;
69
70/// Convenient type alias for an [`AccountEvent`] keyed with [`ExchangeId`],
71/// [`AssetNameExchange`], and [`InstrumentNameExchange`].
72pub type UnindexedAccountEvent =
73    AccountEvent<ExchangeId, AssetNameExchange, InstrumentNameExchange>;
74
75/// Convenient type alias for an [`AccountSnapshot`] keyed with [`ExchangeId`],
76/// [`AssetNameExchange`], and [`InstrumentNameExchange`].
77pub type UnindexedAccountSnapshot =
78    AccountSnapshot<ExchangeId, AssetNameExchange, InstrumentNameExchange>;
79
80#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
81pub struct AccountEvent<
82    ExchangeKey = ExchangeIndex,
83    AssetKey = AssetIndex,
84    InstrumentKey = InstrumentIndex,
85> {
86    pub exchange: ExchangeKey,
87    pub kind: AccountEventKind<ExchangeKey, AssetKey, InstrumentKey>,
88}
89
90impl<ExchangeKey, AssetKey, InstrumentKey> AccountEvent<ExchangeKey, AssetKey, InstrumentKey> {
91    pub fn new<K>(exchange: ExchangeKey, kind: K) -> Self
92    where
93        K: Into<AccountEventKind<ExchangeKey, AssetKey, InstrumentKey>>,
94    {
95        Self {
96            exchange,
97            kind: kind.into(),
98        }
99    }
100}
101
102#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, From)]
103#[non_exhaustive]
104pub enum AccountEventKind<ExchangeKey, AssetKey, InstrumentKey> {
105    /// Full [`AccountSnapshot`] - replaces all existing state.
106    Snapshot(AccountSnapshot<ExchangeKey, AssetKey, InstrumentKey>),
107
108    /// Single [`AssetBalance`] snapshot - replaces existing balance state.
109    BalanceSnapshot(Snapshot<AssetBalance<AssetKey>>),
110
111    /// Single [`Order`] snapshot - used to upsert existing order state if it's more recent.
112    ///
113    /// This variant covers general order updates, and open order responses.
114    OrderSnapshot(Snapshot<Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>>),
115
116    /// Response to an [`OrderRequestCancel<ExchangeKey, InstrumentKey>`](order::request::OrderRequestOpen).
117    OrderCancelled(OrderResponseCancel<ExchangeKey, AssetKey, InstrumentKey>),
118
119    /// [`Order<ExchangeKey, InstrumentKey, Open>`] partial or full-fill.
120    ///
121    /// The fee asset (`AssetKey`) may be the quote asset, base asset, or a third-party
122    /// asset (e.g., BNB on Binance). Use `fees.fees_quote` for quote-equivalent value
123    /// when available.
124    Trade(Trade<AssetKey, InstrumentKey>),
125
126    /// WebSocket-level error from exchange. Connection may have dropped.
127    ///
128    /// Implementations send this when the underlying stream encounters an error.
129    /// Consumers should treat this as a signal that events may have been missed
130    /// and consider re-syncing via REST (e.g., `fetch_trades`, `account_snapshot`).
131    StreamError(String),
132}
133
134impl<ExchangeKey, AssetKey, InstrumentKey> AccountEvent<ExchangeKey, AssetKey, InstrumentKey>
135where
136    AssetKey: Eq,
137    InstrumentKey: Eq,
138{
139    pub fn snapshot(self) -> Option<AccountSnapshot<ExchangeKey, AssetKey, InstrumentKey>> {
140        match self.kind {
141            AccountEventKind::Snapshot(snapshot) => Some(snapshot),
142            _ => None,
143        }
144    }
145}
146
147#[derive(
148    Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, Constructor,
149)]
150pub struct AccountSnapshot<
151    ExchangeKey = ExchangeIndex,
152    AssetKey = AssetIndex,
153    InstrumentKey = InstrumentIndex,
154> {
155    pub exchange: ExchangeKey,
156    pub balances: Vec<AssetBalance<AssetKey>>,
157    pub instruments: Vec<InstrumentAccountSnapshot<ExchangeKey, AssetKey, InstrumentKey>>,
158}
159
160#[derive(
161    Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, Constructor,
162)]
163pub struct InstrumentAccountSnapshot<
164    ExchangeKey = ExchangeIndex,
165    AssetKey = AssetIndex,
166    InstrumentKey = InstrumentIndex,
167> {
168    pub instrument: InstrumentKey,
169    #[serde(default = "Vec::new")]
170    pub orders: Vec<OrderSnapshot<ExchangeKey, AssetKey, InstrumentKey>>,
171    /// Open position for derivative instruments (perpetuals, futures, margin).
172    /// `None` for spot instruments where position is implicit in balances.
173    #[serde(default, skip_serializing_if = "Option::is_none")]
174    pub position: Option<Position>,
175}
176
177impl<ExchangeKey, AssetKey, InstrumentKey> AccountSnapshot<ExchangeKey, AssetKey, InstrumentKey> {
178    pub fn time_most_recent(&self) -> Option<DateTime<Utc>> {
179        let order_times = self.instruments.iter().flat_map(|instrument| {
180            instrument
181                .orders
182                .iter()
183                .filter_map(|order| order.state.time_exchange())
184        });
185        let balance_times = self.balances.iter().map(|balance| balance.time_exchange);
186
187        order_times.chain(balance_times).max()
188    }
189
190    pub fn assets(&self) -> impl Iterator<Item = &AssetKey> {
191        self.balances.iter().map(|balance| &balance.asset)
192    }
193
194    pub fn instruments(&self) -> impl Iterator<Item = &InstrumentKey> {
195        self.instruments.iter().map(|snapshot| &snapshot.instrument)
196    }
197}