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