1use crate::{
2 symbology::{MarketId, ProductId, VenueId},
3 AccountId, Dir, OrderId, UserId,
4};
5use anyhow::anyhow;
6#[cfg(feature = "tokio-postgres")]
7use bytes::BytesMut;
8use chrono::{DateTime, Utc};
9#[cfg(feature = "netidx")]
10use derive::FromValue;
11#[cfg(feature = "netidx")]
12use netidx_derive::Pack;
13use rust_decimal::Decimal;
14use schemars::{JsonSchema, JsonSchema_repr};
15use serde::{Deserialize, Serialize};
16use serde_json::json;
17#[cfg(feature = "tokio-postgres")]
18use std::error::Error;
19use std::{fmt::Display, str::FromStr};
20use uuid::Uuid;
21
22#[derive(
24 Debug,
25 Clone,
26 Copy,
27 PartialEq,
28 Eq,
29 PartialOrd,
30 Ord,
31 Hash,
32 Serialize,
33 Deserialize,
34 JsonSchema,
35)]
36#[cfg_attr(feature = "juniper", derive(juniper::GraphQLScalar), graphql(transparent))]
37#[cfg_attr(feature = "netidx", derive(Pack, FromValue))]
38pub struct FillId(pub Uuid);
39
40impl FillId {
41 pub fn from_id(venue: VenueId, id: &[u8]) -> Self {
45 FillId(Uuid::new_v5(&venue, id))
46 }
47
48 pub fn nil() -> Self {
49 FillId(Uuid::nil())
50 }
51}
52
53#[cfg(feature = "sqlx")]
54impl<'a> sqlx::Decode<'a, sqlx::Postgres> for FillId {
55 fn decode(
56 value: <sqlx::Postgres as sqlx::Database>::ValueRef<'a>,
57 ) -> Result<Self, sqlx::error::BoxDynError> {
58 Ok(FillId(Uuid::decode(value)?))
59 }
60}
61
62impl Default for FillId {
63 fn default() -> Self {
64 FillId(Uuid::new_v4())
65 }
66}
67
68impl Display for FillId {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 self.0.fmt(f)
71 }
72}
73
74impl FromStr for FillId {
75 type Err = uuid::Error;
76
77 fn from_str(s: &str) -> Result<Self, Self::Err> {
78 Uuid::from_str(s).map(FillId)
79 }
80}
81
82#[cfg(feature = "rusqlite")]
83impl rusqlite::ToSql for FillId {
84 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
85 use rusqlite::types::{ToSqlOutput, Value};
86 Ok(ToSqlOutput::Owned(Value::Text(self.to_string())))
87 }
88}
89
90#[cfg(feature = "tokio-postgres")]
91impl tokio_postgres::types::ToSql for FillId {
92 tokio_postgres::types::to_sql_checked!();
93
94 fn to_sql(
95 &self,
96 ty: &tokio_postgres::types::Type,
97 out: &mut BytesMut,
98 ) -> Result<tokio_postgres::types::IsNull, Box<dyn Error + Sync + Send>> {
99 self.0.to_sql(ty, out)
100 }
101
102 fn accepts(ty: &tokio_postgres::types::Type) -> bool {
103 Uuid::accepts(ty)
104 }
105}
106
107#[cfg(feature = "tokio-postgres")]
108impl<'a> tokio_postgres::types::FromSql<'a> for FillId {
109 fn from_sql(
110 ty: &tokio_postgres::types::Type,
111 raw: &'a [u8],
112 ) -> Result<Self, Box<dyn Error + Sync + Send>> {
113 Uuid::from_sql(ty, raw).map(FillId)
114 }
115
116 fn accepts(ty: &tokio_postgres::types::Type) -> bool {
117 Uuid::accepts(ty)
118 }
119}
120
121#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
122#[cfg_attr(feature = "netidx", derive(Pack))]
123#[cfg_attr(feature = "juniper", derive(juniper::GraphQLObject))]
124pub struct Fee {
125 pub amount: Decimal,
126 pub fee_currency: ProductId,
127}
128
129#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
130#[cfg_attr(feature = "netidx", derive(Pack))]
131pub struct Fill {
132 pub kind: FillKind,
133 pub fill_id: FillId,
134 pub order_id: Option<OrderId>,
136 pub account_id: Option<AccountId>,
137 pub market: MarketId,
138 pub quantity: Decimal,
139 pub price: Decimal,
140 pub dir: Dir,
141 pub is_maker: Option<bool>,
142 pub recv_time: Option<DateTime<Utc>>,
144 pub trade_time: DateTime<Utc>,
146 #[serde(default)]
147 #[cfg_attr(feature = "netidx", pack(default))]
148 pub trader: Option<UserId>,
149 pub fee: Option<Fee>,
150}
151
152#[cfg(feature = "netidx")]
153impl Fill {
154 pub fn into_aberrant(self) -> AberrantFill {
155 AberrantFill {
156 kind: Some(self.kind),
157 fill_id: self.fill_id,
158 order_id: self.order_id,
159 account_id: self.account_id,
160 market: Some(self.market),
161 quantity: Some(self.quantity),
162 price: Some(self.price),
163 dir: Some(self.dir),
164 is_maker: self.is_maker,
165 recv_time: self.recv_time,
166 trade_time: Some(self.trade_time),
167 trader: self.trader,
168 fee: self.fee,
169 }
170 }
171}
172
173#[derive(
174 Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema_repr,
175)]
176#[cfg_attr(feature = "juniper", derive(juniper::GraphQLEnum))]
177#[cfg_attr(feature = "netidx", derive(Pack))]
178#[serde(rename_all = "snake_case")]
179#[repr(u8)]
180pub enum FillKind {
181 Normal,
182 Reversal,
183 Correction,
184}
185
186#[cfg(feature = "rusqlite")]
187impl rusqlite::ToSql for FillKind {
188 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
189 use rusqlite::types::{ToSqlOutput, ValueRef};
190 let value_ref = match self {
191 FillKind::Normal => ValueRef::Text("Normal".as_ref()),
192 FillKind::Reversal => ValueRef::Text("Reversal".as_ref()),
193 FillKind::Correction => ValueRef::Text("Correction".as_ref()),
194 };
195 Ok(ToSqlOutput::Borrowed(value_ref))
196 }
197}
198
199#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
202#[cfg_attr(feature = "juniper", derive(juniper::GraphQLObject))]
203#[cfg_attr(feature = "netidx", derive(Pack))]
204pub struct AberrantFill {
205 pub kind: Option<FillKind>,
206 pub fill_id: FillId,
207 pub order_id: Option<OrderId>,
208 pub account_id: Option<AccountId>,
209 pub market: Option<MarketId>,
210 pub quantity: Option<Decimal>,
211 pub price: Option<Decimal>,
212 pub dir: Option<Dir>,
213 pub is_maker: Option<bool>,
214 pub recv_time: Option<DateTime<Utc>>,
215 pub trade_time: Option<DateTime<Utc>>,
216 pub trader: Option<UserId>,
217 pub fee: Option<Fee>,
218}
219
220impl AberrantFill {
221 pub fn try_into_fill(self) -> anyhow::Result<Fill> {
223 Ok(Fill {
224 kind: self.kind.ok_or_else(|| anyhow!("kind is required"))?,
225 fill_id: self.fill_id,
226 order_id: self.order_id,
227 account_id: self.account_id,
228 market: self.market.ok_or_else(|| anyhow!("market is required"))?,
229 quantity: self.quantity.ok_or_else(|| anyhow!("quantity is required"))?,
230 price: self.price.ok_or_else(|| anyhow!("price is required"))?,
231 dir: self.dir.ok_or_else(|| anyhow!("dir is required"))?,
232 is_maker: self.is_maker,
233 recv_time: self.recv_time,
234 trade_time: self
235 .trade_time
236 .ok_or_else(|| anyhow!("trade_time is required"))?,
237 trader: self.trader,
238 fee: self.fee,
239 })
240 }
241}