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 #[serde(rename = "fu")]
83 #[schemars(title = "fee_currency")]
84 pub fee_currency: Option<String>,
85 #[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 #[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#[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}