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