architect_api/orderflow/
fill.rs

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/// The ID of a fill
23#[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    /// This function will always generate the same UUID for the
42    /// identifier bytes provided; venue is split out as an input
43    /// to reduce the chance of accidental collision.
44    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    /// Corresponding order ID, if the order originated from Architect
135    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    /// When Architect received the fill, if realtime
143    pub recv_time: Option<DateTime<Utc>>,
144    /// When the cpty claims the trade happened
145    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/// Fills which we received but couldn't parse fully, return details
200/// best effort
201#[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    /// If sufficient fields on AberrantFill, upgrade it into a Fill
222    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}