1use super::{Product, ProductId, Route, RouteId, Symbolic, Venue, VenueId};
6use crate::{cpty, marketdata, uuid_val, Amount, Str};
7use anyhow::Result;
8#[cfg(feature = "netidx")]
9use derive::FromValue;
10use derive_more::Display;
11use enum_dispatch::enum_dispatch;
12#[cfg(feature = "netidx")]
13use netidx_derive::Pack;
14use rust_decimal::Decimal;
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17use smallvec::SmallVec;
18use strum_macros::{EnumString, IntoStaticStr};
19use uuid::{uuid, Uuid};
20
21static MARKET_NS: Uuid = uuid!("0bfe858c-a749-43a9-a99e-6d1f31a760ad");
22uuid_val!(MarketId, MARKET_NS);
23
24#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
25#[cfg_attr(feature = "netidx", derive(Pack, FromValue))]
26pub struct Market {
27 pub id: MarketId,
28 pub name: Str,
29 pub kind: MarketKind,
30 pub venue: VenueId,
31 pub route: RouteId,
32 pub exchange_symbol: Str,
33 pub extra_info: MarketInfo,
34}
35
36impl Market {
37 fn new(
38 kind_name: &str,
39 kind: MarketKind,
40 venue: &Venue,
41 route: &Route,
42 exchange_symbol: &str,
43 extra_info: MarketInfo,
44 ) -> Result<Self> {
45 let name = format!("{kind_name}*{}/{}", venue.name, route.name);
46 Ok(Self {
47 id: MarketId::from(&name),
48 name: Str::try_from(name.as_str())?,
49 kind,
50 venue: venue.id,
51 route: route.id,
52 exchange_symbol: Str::try_from(exchange_symbol)?,
53 extra_info,
54 })
55 }
56
57 pub fn exchange(
58 base: &Product,
59 quote: &Product,
60 venue: &Venue,
61 route: &Route,
62 exchange_symbol: &str,
63 extra_info: MarketInfo,
64 ) -> Result<Self> {
65 Self::new(
66 &format!("{}/{}", base.name, quote.name),
67 MarketKind::Exchange(ExchangeMarketKind { base: base.id, quote: quote.id }),
68 venue,
69 route,
70 exchange_symbol,
71 extra_info,
72 )
73 }
74
75 pub fn pool(
76 products: impl Iterator<Item = Product>,
77 venue: &Venue,
78 route: &Route,
79 exchange_symbol: &str,
80 extra_info: MarketInfo,
81 ) -> Result<Self> {
82 let mut pool: SmallVec<[ProductId; 2]> = SmallVec::new();
83 let mut kind_name = String::new();
84 for p in products {
85 kind_name.push_str(p.name.as_str());
86 kind_name.push('/');
87 pool.push(p.id);
88 }
89 Self::new(
90 &kind_name,
91 MarketKind::Pool(PoolMarketKind { products: pool }),
92 venue,
93 route,
94 exchange_symbol,
95 extra_info,
96 )
97 }
98}
99
100impl Symbolic for Market {
101 type Id = MarketId;
102
103 fn type_name() -> &'static str {
104 "market"
105 }
106
107 fn id(&self) -> Self::Id {
108 self.id
109 }
110
111 fn name(&self) -> Str {
112 self.name
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
117#[cfg_attr(feature = "netidx", derive(Pack))]
118#[serde(tag = "type", content = "value")]
119pub enum MarketKind {
120 Exchange(ExchangeMarketKind),
122 Pool(PoolMarketKind),
125 #[cfg_attr(feature = "netidx", pack(other))]
126 Unknown,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
130#[cfg_attr(feature = "netidx", derive(Pack))]
131#[cfg_attr(feature = "juniper", derive(juniper::GraphQLObject))]
132pub struct ExchangeMarketKind {
133 pub base: ProductId,
134 pub quote: ProductId,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
138#[cfg_attr(feature = "netidx", derive(Pack))]
139pub struct PoolMarketKind {
140 pub products: SmallVec<[ProductId; 2]>,
141}
142
143#[cfg_attr(feature = "juniper", juniper::graphql_object)]
144impl PoolMarketKind {
145 pub fn products(&self) -> Vec<ProductId> {
146 self.products.iter().copied().collect()
147 }
148}
149
150#[derive(Debug, Display, Clone, Serialize, Deserialize, JsonSchema)]
152#[cfg_attr(feature = "netidx", derive(Pack))]
153#[enum_dispatch(NormalizedMarketInfo)]
154#[serde(tag = "type", content = "value")]
155#[rustfmt::skip]
156pub enum MarketInfo {
157 #[cfg_attr(feature = "netidx", pack(tag( 0)))] Test(TestMarketInfo),
158 #[cfg_attr(feature = "netidx", pack(tag( 1)))] External(ExternalMarketInfo),
159 #[cfg_attr(feature = "netidx", pack(tag(106)))] B2C2(cpty::b2c2::B2C2MarketInfo),
160 #[cfg_attr(feature = "netidx", pack(tag(114)))] Binance(cpty::binance::BinanceMarketInfo),
161 #[cfg_attr(feature = "netidx", pack(tag(116)))] Bybit(cpty::bybit::BybitMarketInfo),
162 #[cfg_attr(feature = "netidx", pack(tag(112)))] CboeDigital(cpty::cboe_digital::CboeDigitalMarketInfo),
163 #[cfg_attr(feature = "netidx", pack(tag(100)))] Coinbase(cpty::coinbase::CoinbaseMarketInfo),
164 #[cfg_attr(feature = "netidx", pack(tag(111)))] CoinbasePrime(cpty::coinbase_prime::CoinbasePrimeMarketInfo),
165 #[cfg_attr(feature = "netidx", pack(tag(104)))] Cqg(cpty::cqg::CqgMarketInfo),
166 #[cfg_attr(feature = "netidx", pack(tag(113)))] Cumberland(cpty::cumberland::CumberlandMarketInfo),
167 #[cfg_attr(feature = "netidx", pack(tag(110)))] DYDX(cpty::dydx::DYDXMarketInfo),
168 #[cfg_attr(feature = "netidx", pack(tag(105)))] Databento(marketdata::databento::DatabentoMarketInfo),
169 #[cfg_attr(feature = "netidx", pack(tag(115)))] Deltix(cpty::deltix::DeltixMarketInfo),
170 #[cfg_attr(feature = "netidx", pack(tag(101)))] Deribit(cpty::deribit::DeribitMarketInfo),
171 #[cfg_attr(feature = "netidx", pack(tag(109)))] FalconX(cpty::falconx::FalconXMarketInfo),
172 #[cfg_attr(feature = "netidx", pack(tag(108)))] Galaxy(cpty::galaxy::GalaxyMarketInfo),
173 #[cfg_attr(feature = "netidx", pack(tag(102)))] Kraken(cpty::kraken::KrakenMarketInfo),
174 #[cfg_attr(feature = "netidx", pack(tag(103)))] Okx(cpty::okx::OkxMarketInfo),
175 #[cfg_attr(feature = "netidx", pack(tag(107)))] Wintermute(cpty::wintermute::WintermuteMarketInfo),
176}
177
178#[enum_dispatch]
179pub trait NormalizedMarketInfo {
180 fn tick_size(&self) -> Decimal;
182
183 fn step_size(&self) -> Decimal;
185
186 fn min_order_quantity(&self) -> Amount<Decimal, MinOrderQuantityUnit> {
191 return Amount::new(self.step_size(), MinOrderQuantityUnit::Base);
192 }
193
194 fn is_delisted(&self) -> bool;
196
197 fn initial_margin(&self) -> Option<Decimal> {
200 None
201 }
202
203 fn maintenance_margin(&self) -> Option<Decimal> {
204 None
205 }
206}
207
208#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
209#[cfg_attr(feature = "netidx", derive(Pack))]
210pub struct ExternalMarketInfo {
211 pub tick_size: Decimal,
212 pub step_size: Decimal,
213 pub min_order_quantity: Decimal,
214 pub min_order_quantity_unit: MinOrderQuantityUnit,
215 pub is_delisted: bool,
216}
217
218impl std::fmt::Display for ExternalMarketInfo {
219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220 write!(f, "{}", serde_json::to_string_pretty(self).unwrap())?;
221 Ok(())
222 }
223}
224
225impl NormalizedMarketInfo for ExternalMarketInfo {
226 fn tick_size(&self) -> Decimal {
227 self.tick_size
228 }
229
230 fn step_size(&self) -> Decimal {
231 self.step_size
232 }
233
234 fn min_order_quantity(&self) -> Amount<Decimal, MinOrderQuantityUnit> {
235 Amount::new(self.min_order_quantity, self.min_order_quantity_unit)
236 }
237
238 fn is_delisted(&self) -> bool {
239 self.is_delisted
240 }
241}
242
243#[derive(
244 Default,
245 Debug,
246 Clone,
247 Copy,
248 EnumString,
249 IntoStaticStr,
250 Serialize,
251 Deserialize,
252 JsonSchema,
253)]
254#[cfg_attr(feature = "juniper", derive(juniper::GraphQLEnum))]
255#[cfg_attr(feature = "netidx", derive(Pack))]
256#[serde(tag = "unit", rename_all = "snake_case")]
257#[strum(serialize_all = "snake_case")]
258pub enum MinOrderQuantityUnit {
259 #[default]
260 Base,
261 Quote,
262}
263
264#[cfg(feature = "postgres")]
265crate::to_sql_str!(MinOrderQuantityUnit);
266
267#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
268#[cfg_attr(feature = "netidx", derive(Pack))]
269pub struct TestMarketInfo {
270 pub tick_size: Decimal,
271 pub step_size: Decimal,
272 pub is_delisted: bool,
273}
274
275impl std::fmt::Display for TestMarketInfo {
276 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277 write!(f, "{}", serde_json::to_string_pretty(self).unwrap())?;
278 Ok(())
279 }
280}
281
282impl NormalizedMarketInfo for TestMarketInfo {
283 fn tick_size(&self) -> Decimal {
284 self.tick_size
285 }
286
287 fn step_size(&self) -> Decimal {
288 self.step_size
289 }
290
291 fn is_delisted(&self) -> bool {
292 self.is_delisted
293 }
294}