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 tracing_subscriber as _;
34#[cfg(test)]
35use wiremock as _;
36
37use crate::{
38    balance::AssetBalance,
39    order::{Order, OrderSnapshot, request::OrderResponseCancel},
40    position::Position,
41    trade::Trade,
42};
43use chrono::{DateTime, Utc};
44use derive_more::{Constructor, From};
45use order::state::OrderState;
46use rustrade_instrument::{
47    asset::{AssetIndex, name::AssetNameExchange},
48    exchange::{ExchangeId, ExchangeIndex},
49    instrument::{InstrumentIndex, name::InstrumentNameExchange},
50};
51use rustrade_integration::collection::snapshot::Snapshot;
52use serde::{Deserialize, Serialize};
53
54pub mod balance;
55pub mod client;
56pub mod error;
57pub mod exchange;
58pub mod fee;
59pub use fee::{FeeModel, FeeModelConfig, PerContractFeeModel, PercentageFeeModel, ZeroFeeModel};
60pub mod fill;
61pub use fill::{BidAskFillModel, FillModel, LastPriceFillModel, MidpointFillModel, SimFillConfig};
62pub mod indexer;
63pub mod map;
64pub mod order;
65pub mod position;
66pub mod trade;
67
68/// Convenient type alias for an [`AccountEvent`] keyed with [`ExchangeId`],
69/// [`AssetNameExchange`], and [`InstrumentNameExchange`].
70pub type UnindexedAccountEvent =
71    AccountEvent<ExchangeId, AssetNameExchange, InstrumentNameExchange>;
72
73/// Convenient type alias for an [`AccountSnapshot`] keyed with [`ExchangeId`],
74/// [`AssetNameExchange`], and [`InstrumentNameExchange`].
75pub type UnindexedAccountSnapshot =
76    AccountSnapshot<ExchangeId, AssetNameExchange, InstrumentNameExchange>;
77
78#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
79pub struct AccountEvent<
80    ExchangeKey = ExchangeIndex,
81    AssetKey = AssetIndex,
82    InstrumentKey = InstrumentIndex,
83> {
84    pub exchange: ExchangeKey,
85    pub kind: AccountEventKind<ExchangeKey, AssetKey, InstrumentKey>,
86}
87
88impl<ExchangeKey, AssetKey, InstrumentKey> AccountEvent<ExchangeKey, AssetKey, InstrumentKey> {
89    pub fn new<K>(exchange: ExchangeKey, kind: K) -> Self
90    where
91        K: Into<AccountEventKind<ExchangeKey, AssetKey, InstrumentKey>>,
92    {
93        Self {
94            exchange,
95            kind: kind.into(),
96        }
97    }
98}
99
100#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, From)]
101#[non_exhaustive]
102pub enum AccountEventKind<ExchangeKey, AssetKey, InstrumentKey> {
103    /// Full [`AccountSnapshot`] - replaces all existing state.
104    Snapshot(AccountSnapshot<ExchangeKey, AssetKey, InstrumentKey>),
105
106    /// Single [`AssetBalance`] snapshot - replaces existing balance state.
107    BalanceSnapshot(Snapshot<AssetBalance<AssetKey>>),
108
109    /// Single [`Order`] snapshot - used to upsert existing order state if it's more recent.
110    ///
111    /// This variant covers general order updates, and open order responses.
112    OrderSnapshot(Snapshot<Order<ExchangeKey, InstrumentKey, OrderState<AssetKey, InstrumentKey>>>),
113
114    /// Response to an [`OrderRequestCancel<ExchangeKey, InstrumentKey>`](order::request::OrderRequestOpen).
115    OrderCancelled(OrderResponseCancel<ExchangeKey, AssetKey, InstrumentKey>),
116
117    /// [`Order<ExchangeKey, InstrumentKey, Open>`] partial or full-fill.
118    ///
119    /// The fee asset (`AssetKey`) may be the quote asset, base asset, or a third-party
120    /// asset (e.g., BNB on Binance). Use `fees.fees_quote` for quote-equivalent value
121    /// when available.
122    Trade(Trade<AssetKey, InstrumentKey>),
123
124    /// WebSocket-level error from exchange. Connection may have dropped.
125    ///
126    /// Implementations send this when the underlying stream encounters an error.
127    /// Consumers should treat this as a signal that events may have been missed
128    /// and consider re-syncing via REST (e.g., `fetch_trades`, `account_snapshot`).
129    StreamError(String),
130}
131
132impl<ExchangeKey, AssetKey, InstrumentKey> AccountEvent<ExchangeKey, AssetKey, InstrumentKey>
133where
134    AssetKey: Eq,
135    InstrumentKey: Eq,
136{
137    pub fn snapshot(self) -> Option<AccountSnapshot<ExchangeKey, AssetKey, InstrumentKey>> {
138        match self.kind {
139            AccountEventKind::Snapshot(snapshot) => Some(snapshot),
140            _ => None,
141        }
142    }
143}
144
145#[derive(
146    Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, Constructor,
147)]
148pub struct AccountSnapshot<
149    ExchangeKey = ExchangeIndex,
150    AssetKey = AssetIndex,
151    InstrumentKey = InstrumentIndex,
152> {
153    pub exchange: ExchangeKey,
154    pub balances: Vec<AssetBalance<AssetKey>>,
155    pub instruments: Vec<InstrumentAccountSnapshot<ExchangeKey, AssetKey, InstrumentKey>>,
156}
157
158#[derive(
159    Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, Constructor,
160)]
161pub struct InstrumentAccountSnapshot<
162    ExchangeKey = ExchangeIndex,
163    AssetKey = AssetIndex,
164    InstrumentKey = InstrumentIndex,
165> {
166    pub instrument: InstrumentKey,
167    #[serde(default = "Vec::new")]
168    pub orders: Vec<OrderSnapshot<ExchangeKey, AssetKey, InstrumentKey>>,
169    /// Open position for derivative instruments (perpetuals, futures, margin).
170    /// `None` for spot instruments where position is implicit in balances.
171    #[serde(default, skip_serializing_if = "Option::is_none")]
172    pub position: Option<Position>,
173}
174
175impl<ExchangeKey, AssetKey, InstrumentKey> AccountSnapshot<ExchangeKey, AssetKey, InstrumentKey> {
176    pub fn time_most_recent(&self) -> Option<DateTime<Utc>> {
177        let order_times = self.instruments.iter().flat_map(|instrument| {
178            instrument
179                .orders
180                .iter()
181                .filter_map(|order| order.state.time_exchange())
182        });
183        let balance_times = self.balances.iter().map(|balance| balance.time_exchange);
184
185        order_times.chain(balance_times).max()
186    }
187
188    pub fn assets(&self) -> impl Iterator<Item = &AssetKey> {
189        self.balances.iter().map(|balance| &balance.asset)
190    }
191
192    pub fn instruments(&self) -> impl Iterator<Item = &InstrumentKey> {
193        self.instruments.iter().map(|snapshot| &snapshot.instrument)
194    }
195}