architect_api/algo/
spreader.rs

1use super::*;
2use crate::{
3    symbology::{ExecutionVenue, MarketdataVenue},
4    AccountIdOrName, Dir, HumanDuration,
5};
6use anyhow::{bail, Result};
7use rust_decimal::Decimal;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
11pub struct Spreader;
12
13impl Algo for Spreader {
14    const NAME: &'static str = "SPREADER";
15
16    type Params = SpreaderParams;
17    type Status = SpreaderStatus;
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
21pub struct SpreaderParams {
22    pub dir: Dir,
23    pub quantity: Decimal,
24    pub limit_price: Decimal,
25    pub order_lockout: HumanDuration,
26    pub leg1_symbol: String,
27    pub leg1_account: Option<AccountIdOrName>,
28    pub leg1_marketdata_venue: MarketdataVenue,
29    pub leg1_execution_venue: Option<ExecutionVenue>,
30    pub leg1_price_ratio: Decimal,
31    pub leg1_price_offset: Decimal,
32    pub leg1_quantity_ratio: Decimal,
33    pub leg2_symbol: String,
34    pub leg2_account: Option<AccountIdOrName>,
35    pub leg2_marketdata_venue: MarketdataVenue,
36    pub leg2_execution_venue: Option<ExecutionVenue>,
37    pub leg2_price_ratio: Decimal,
38    pub leg2_price_offset: Decimal,
39    pub leg2_quantity_ratio: Decimal,
40}
41
42impl DisplaySymbols for SpreaderParams {
43    fn display_symbols(&self) -> Option<Vec<String>> {
44        Some(vec![self.leg1_symbol.clone(), self.leg2_symbol.clone()])
45    }
46}
47
48impl Validate for SpreaderParams {
49    fn validate(&self) -> Result<()> {
50        // Must ensure that quantity and various ratios are non-zero or else we'd have division by zero.
51        if self.leg1_price_ratio.is_zero() {
52            bail!("leg1_price_ratio must not be zero");
53        }
54        if self.leg2_price_ratio.is_zero() {
55            bail!("leg2_price_ratio must not be zero");
56        }
57        if self.leg1_quantity_ratio.is_zero() {
58            bail!("leg1_quantity_ratio must not be zero");
59        }
60        if self.leg2_quantity_ratio.is_zero() {
61            bail!("leg2_quantity_ratio must not be zero");
62        }
63        if !self.quantity.is_sign_positive() {
64            bail!("quantity must be positive");
65        }
66        if self.order_lockout.is_zero() {
67            bail!("order_lockout must not be zero");
68        }
69        Ok(())
70    }
71}
72
73#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
74pub struct SpreaderStatus {
75    pub leg1_fill_quantity: Decimal,
76    pub leg2_fill_quantity: Decimal,
77    pub implied_spread_vwap: Option<Decimal>,
78    pub current_spreader_phase: SpreaderPhase,
79}
80
81#[derive(
82    Debug, Copy, Clone, Serialize, Deserialize, Default, JsonSchema, PartialEq, Eq,
83)]
84pub enum SpreaderPhase {
85    #[default]
86    ScanningForTakes,
87    AwaitingOrderResults,
88    OrderLockout,
89    NoBbo,
90    NotEnoughBboSize,
91    DoneOverfilled,
92    DoneAndFullyHedged,
93    DoneAndGivingUp,
94}