architect_api/orderflow/
fill.rs

1use crate::{
2    symbology::{ExecutionVenue, TradableProduct},
3    AccountId, Dir, OrderId, UserId,
4};
5use chrono::{DateTime, Utc};
6use derive_more::{Display, FromStr};
7use rust_decimal::Decimal;
8use schemars::{JsonSchema, JsonSchema_repr};
9use serde::{Deserialize, Serialize};
10use serde_json::json;
11use serde_repr::{Deserialize_repr, Serialize_repr};
12use serde_with::{serde_as, BoolFromInt};
13use strum::FromRepr;
14use uuid::Uuid;
15
16#[derive(
17    Debug,
18    Display,
19    FromStr,
20    FromRepr,
21    Clone,
22    Copy,
23    Hash,
24    PartialEq,
25    Eq,
26    Serialize_repr,
27    Deserialize_repr,
28    JsonSchema_repr,
29)]
30#[cfg_attr(feature = "juniper", derive(juniper::GraphQLEnum))]
31#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
32#[repr(u8)]
33pub enum FillKind {
34    Normal = 0,
35    Reversal = 1,
36    Correction = 2,
37}
38
39#[serde_as]
40#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
41pub struct Fill {
42    #[serde(rename = "id")]
43    #[schemars(title = "fill_id")]
44    pub fill_id: Uuid,
45    #[serde(rename = "k")]
46    #[schemars(title = "fill_kind")]
47    pub fill_kind: FillKind,
48    #[serde(rename = "x")]
49    #[schemars(title = "execution_venue")]
50    pub execution_venue: ExecutionVenue,
51    #[serde(rename = "xid")]
52    #[schemars(title = "exchange_fill_id")]
53    pub exchange_fill_id: Option<String>,
54    #[serde(rename = "oid")]
55    #[schemars(title = "order_id")]
56    pub order_id: Option<OrderId>,
57    #[serde(rename = "u")]
58    #[schemars(title = "trader")]
59    pub trader: Option<UserId>,
60    #[serde(rename = "a")]
61    #[schemars(title = "account")]
62    pub account: Option<AccountId>,
63    #[serde(rename = "s")]
64    #[schemars(title = "symbol")]
65    pub symbol: TradableProduct,
66    #[serde(rename = "d")]
67    #[schemars(title = "direction")]
68    pub dir: Dir,
69    #[serde(rename = "q")]
70    #[schemars(title = "quantity")]
71    pub quantity: Decimal,
72    #[serde(rename = "p")]
73    #[schemars(title = "price")]
74    pub price: Decimal,
75    #[serde(rename = "agg")]
76    #[serde_as(as = "Option<BoolFromInt>")]
77    #[schemars(title = "is_taker", with = "isize")]
78    pub is_taker: Option<bool>,
79    #[serde(rename = "f")]
80    #[schemars(title = "fee")]
81    pub fee: Option<Decimal>,
82    /// Fee currency, if different from the price currency
83    #[serde(rename = "fu")]
84    #[schemars(title = "fee_currency")]
85    pub fee_currency: Option<String>,
86    /// When Architect received the fill, if realtime
87    #[serde(rename = "ats")]
88    #[schemars(title = "recv_time")]
89    pub recv_time: Option<i64>,
90    #[serde(rename = "atn")]
91    #[schemars(title = "recv_time_ns")]
92    pub recv_time_ns: Option<u32>,
93    /// When the cpty claims the trade happened
94    #[serde(rename = "ts")]
95    #[schemars(title = "trade_time")]
96    pub trade_time: i64,
97    #[serde(rename = "tn")]
98    #[schemars(title = "trade_time_ns")]
99    pub trade_time_ns: u32,
100}
101
102impl Fill {
103    pub fn recv_time(&self) -> Option<DateTime<Utc>> {
104        if let Some(recv_time) = self.recv_time {
105            DateTime::from_timestamp(recv_time, self.recv_time_ns.unwrap_or(0))
106        } else {
107            None
108        }
109    }
110
111    pub fn trade_time(&self) -> Option<DateTime<Utc>> {
112        DateTime::from_timestamp(self.trade_time, self.trade_time_ns)
113    }
114}
115
116/// Fills which we received but couldn't parse fully,
117/// return details best effort
118#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
119pub struct AberrantFill {
120    #[serde(rename = "id")]
121    #[schemars(title = "fill_id")]
122    pub fill_id: Uuid,
123    #[serde(rename = "k")]
124    #[schemars(title = "fill_kind")]
125    pub fill_kind: Option<FillKind>,
126    #[serde(rename = "x")]
127    #[schemars(title = "execution_venue")]
128    pub execution_venue: ExecutionVenue,
129    #[serde(rename = "xid")]
130    #[schemars(title = "exchange_fill_id")]
131    pub exchange_fill_id: Option<String>,
132    #[serde(rename = "oid")]
133    #[schemars(title = "order_id")]
134    pub order_id: Option<OrderId>,
135    #[serde(rename = "u")]
136    #[schemars(title = "trader")]
137    pub trader: Option<UserId>,
138    #[serde(rename = "a")]
139    #[schemars(title = "account")]
140    pub account: Option<AccountId>,
141    #[serde(rename = "s")]
142    #[schemars(title = "symbol")]
143    pub symbol: Option<String>,
144    #[serde(rename = "d")]
145    #[schemars(title = "direction")]
146    pub dir: Option<Dir>,
147    #[serde(rename = "q")]
148    #[schemars(title = "quantity")]
149    pub quantity: Option<Decimal>,
150    #[serde(rename = "p")]
151    #[schemars(title = "price")]
152    pub price: Option<Decimal>,
153    #[serde(rename = "f")]
154    #[schemars(title = "fee")]
155    pub fee: Option<Decimal>,
156    #[serde(rename = "fu")]
157    #[schemars(title = "fee_currency")]
158    pub fee_currency: Option<String>,
159    #[serde(rename = "ats")]
160    #[schemars(title = "recv_time")]
161    pub recv_time: Option<i64>,
162    #[serde(rename = "atn")]
163    #[schemars(title = "recv_time_ns")]
164    pub recv_time_ns: Option<u32>,
165    #[serde(rename = "ts")]
166    #[schemars(title = "trade_time")]
167    pub trade_time: Option<i64>,
168    #[serde(rename = "tn")]
169    #[schemars(title = "trade_time_ns")]
170    pub trade_time_ns: Option<u32>,
171}
172
173impl AberrantFill {
174    pub fn recv_time(&self) -> Option<DateTime<Utc>> {
175        if let Some(recv_time) = self.recv_time {
176            DateTime::from_timestamp(recv_time, self.recv_time_ns.unwrap_or(0))
177        } else {
178            None
179        }
180    }
181
182    pub fn trade_time(&self) -> Option<DateTime<Utc>> {
183        if let Some(trade_time) = self.trade_time {
184            DateTime::from_timestamp(trade_time, self.trade_time_ns.unwrap_or(0))
185        } else {
186            None
187        }
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use chrono::{DateTime, Utc};
195    use rust_decimal_macros::dec;
196    use uuid::uuid;
197
198    #[test]
199    fn test_fill_json() {
200        let recv_time: DateTime<Utc> = "2021-01-01T00:00:00Z".parse().unwrap();
201        let trade_time: DateTime<Utc> = "2021-01-01T00:00:01Z".parse().unwrap();
202        let fill = Fill {
203            fill_id: uuid!("550e8400-e29b-41d4-a716-446655440000"),
204            fill_kind: FillKind::Normal,
205            execution_venue: "BINANCE".into(),
206            exchange_fill_id: Some("123456".to_string()),
207            order_id: Some(OrderId::nil(100)),
208            trader: Some(UserId::anonymous()),
209            account: Some(AccountId::nil()),
210            symbol: "BTC-USD".to_string().into(),
211            dir: Dir::Buy,
212            quantity: dec!(1.5),
213            price: dec!(50000),
214            is_taker: Some(true),
215            fee: Some(dec!(0.001)),
216            fee_currency: Some("BTC".to_string()),
217            recv_time: Some(recv_time.timestamp()),
218            recv_time_ns: Some(recv_time.timestamp_subsec_nanos()),
219            trade_time: trade_time.timestamp(),
220            trade_time_ns: trade_time.timestamp_subsec_nanos(),
221        };
222        insta::assert_json_snapshot!(fill, @r###"
223        {
224          "id": "550e8400-e29b-41d4-a716-446655440000",
225          "k": 0,
226          "x": "BINANCE",
227          "xid": "123456",
228          "oid": "100",
229          "u": "00000000-0000-0000-0000-000000000000",
230          "a": "00000000-0000-0000-0000-000000000000",
231          "s": "BTC-USD",
232          "d": "BUY",
233          "q": "1.5",
234          "p": "50000",
235          "agg": 1,
236          "f": "0.001",
237          "fu": "BTC",
238          "ats": 1609459200,
239          "atn": 0,
240          "ts": 1609459201,
241          "tn": 0
242        }
243        "###);
244    }
245}