1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
use super::*;
use crate::{
    algo::generic_container::AlgoContainerMessage, symbology::MarketId, Dir, OrderId, Str,
};
use anyhow::{bail, Result};
use chrono::{DateTime, Utc};
use derive::FromValue;
use netidx_derive::Pack;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use std::time::Duration;

pub type TwapMessage = AlgoContainerMessage<TwapOrder, AlgoPreview, TwapStatus, AlgoLog>;

#[derive(Debug, Clone, Copy, Pack, FromValue, Serialize, Deserialize)]
pub struct TwapOrder {
    pub order_id: OrderId,
    pub market: MarketId,
    pub dir: Dir,
    pub quantity: Decimal,
    pub interval: Duration,
    pub end_time: DateTime<Utc>,
    pub trader: UserId,
    pub account: Option<AccountId>,
    pub reject_lockout: Duration,
    pub take_through_frac: Option<Decimal>,
}

impl Into<AlgoOrder> for &TwapOrder {
    fn into(self) -> AlgoOrder {
        AlgoOrder {
            order_id: self.order_id,
            trader: self.trader,
            algo: Str::try_from("TWAP").unwrap(), // won't panic
        }
    }
}

impl Validate for TwapOrder {
    fn validate(&self) -> Result<()> {
        if !self.quantity.is_sign_positive() {
            bail!("quantity must be positive");
        }
        if self.interval.as_secs() < 1 {
            bail!("interval must be >= 1 second");
        }
        if self.reject_lockout.as_millis() < 500 || self.reject_lockout.as_secs() > 300 {
            bail!("reject lockout must be between 0.5 seconds and 300 seconds");
        }
        if let Some(take_through_frac) = self.take_through_frac {
            if take_through_frac.is_sign_negative() || take_through_frac > dec!(0.05) {
                bail!("take_through_frac must be between 0 and 5%");
            }
        }
        Ok(())
    }
}

#[derive(Debug, Clone, Pack, FromValue, Serialize, Deserialize)]
pub struct TwapStatus {
    #[serde(flatten)]
    pub algo_status: AlgoStatus,
    pub realized_twap: Option<Decimal>,
    pub quantity_filled: Decimal,
}

impl TryInto<AlgoStatus> for &TwapStatus {
    type Error = ();

    fn try_into(self) -> Result<AlgoStatus, ()> {
        Ok(self.algo_status)
    }
}