1use crate::{
2 folio::FolioMessage,
3 orderflow::{
4 AberrantFill, Ack, Cancel, CancelAll, Fill, Order, OrderStateFlags,
5 OrderflowMessage, Out, Reject, RejectReason,
6 },
7 symbology::{market::NormalizedMarketInfo, MarketId},
8 Dir, OrderId,
9};
10use arcstr::ArcStr;
11use chrono::{DateTime, Utc};
12#[cfg(feature = "netidx")]
13use derive::FromValue;
14use enumflags2::BitFlags;
15#[cfg(feature = "netidx")]
16use netidx_derive::Pack;
17use rust_decimal::Decimal;
18use schemars::JsonSchema;
19use serde_derive::{Deserialize, Serialize};
20use std::{ops::Deref, sync::Arc};
21
22#[derive(Clone, Debug, Deserialize)]
23pub struct CqgIcsFileRow {
24 #[serde(rename = "CQG Contract Symbol")]
25 pub contract_symbol: String,
26 #[serde(rename = "Exchange")]
27 pub exchange: String,
28 #[serde(rename = "CQG Instrument Group Symbol")]
29 pub cqg_instrument_group_symbol: String,
30 #[serde(rename = "SSL Underlying Group Symbol")]
31 pub ssl_underlying_group_symbol: String,
32 #[serde(rename = "OSL Underlying Group Symbol")]
33 pub osl_underlying_group_symbol: String,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
37#[cfg_attr(feature = "netidx", derive(Pack))]
38pub struct CqgMarketInfo {
39 pub deleted: bool,
40 pub symbol_id: Option<String>,
42 pub description: String,
43 pub cfi_code: Option<String>,
44 pub cqg_contract_symbol: Option<String>,
45 pub correct_price_scale: Decimal,
46 pub exchange_symbol: String,
47 pub exchange_group_symbol: String,
48 pub tick_size: Decimal, pub tick_value: Decimal, pub trade_size_increment: Decimal,
51 pub market_data_delay_ms: i64,
52 pub initial_margin: Option<Decimal>,
53 pub maintenance_margin: Option<Decimal>,
54 pub last_trading_date: DateTime<Utc>,
55 pub first_notice_date: Option<DateTime<Utc>>,
56}
57
58impl NormalizedMarketInfo for CqgMarketInfo {
59 fn tick_size(&self) -> Decimal {
60 self.tick_size
61 }
62
63 fn step_size(&self) -> Decimal {
64 self.trade_size_increment
65 }
66
67 fn is_delisted(&self) -> bool {
68 self.deleted
69 }
70
71 fn initial_margin(&self) -> Option<Decimal> {
72 self.initial_margin
73 }
74
75 fn maintenance_margin(&self) -> Option<Decimal> {
76 self.maintenance_margin
77 }
78}
79
80impl std::fmt::Display for CqgMarketInfo {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 write!(f, "{}", serde_json::to_string_pretty(self).unwrap())?;
83 Ok(())
84 }
85}
86
87#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
88#[cfg_attr(feature = "netidx", derive(Pack))]
89pub struct CqgOrder {
90 #[serde(flatten)]
91 pub order: Order,
92}
93
94impl Deref for CqgOrder {
95 type Target = Order;
96
97 fn deref(&self) -> &Self::Target {
98 &self.order
99 }
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
103#[cfg_attr(feature = "netidx", derive(Pack))]
104pub struct CqgTrade {
105 pub order_id: OrderId,
106 pub contract_symbol: Option<String>,
107 pub exec_id: String,
108 pub scaled_price: i64,
109 pub qty: Decimal,
110 pub time: DateTime<Utc>,
111 pub side: Dir,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
115#[cfg_attr(feature = "netidx", derive(Pack, FromValue))]
116pub struct CqgAccountSummary {
117 pub is_snapshot: Option<bool>,
121
122 pub account_id: Option<i32>,
125
126 pub currency: Option<String>,
129
130 pub cleared_fields: Vec<u32>,
133
134 pub total_margin: Option<f64>,
136
137 pub position_margin: Option<f64>,
139
140 pub purchasing_power: Option<f64>,
148
149 pub ote: Option<f64>,
153
154 pub mvo: Option<f64>,
158
159 pub mvf: Option<f64>,
163
164 pub margin_credit: Option<f64>,
166
167 pub cash_excess: Option<f64>,
169
170 pub current_balance: Option<f64>,
173
174 pub profit_loss: Option<f64>,
176
177 pub unrealized_profit_loss: Option<f64>,
179
180 pub yesterday_balance: Option<f64>,
182
183 pub yesterday_ote: Option<f64>,
185
186 pub yesterday_mvo: Option<f64>,
188
189 pub yesterday_collateral: Option<f64>,
191
192 pub net_change_pc: Option<f64>,
194
195 pub total_filled_qty: Option<Decimal>,
197
198 pub total_filled_orders: Option<u32>,
200
201 pub long_open_positions_qty: Option<Decimal>,
203
204 pub short_open_positions_qty: Option<Decimal>,
206
207 pub min_days_till_position_contract_expiration: Option<u32>,
211
212 pub purchasing_power_limit: Option<f64>,
216}
217
218impl CqgAccountSummary {
219 pub fn merge(&mut self, other: &CqgAccountSummary) {
220 let CqgAccountSummary {
221 is_snapshot: _,
222 account_id,
223 currency,
224 cleared_fields,
225 total_margin,
226 position_margin,
227 purchasing_power,
228 ote,
229 mvo,
230 mvf,
231 margin_credit,
232 cash_excess,
233 current_balance,
234 profit_loss,
235 unrealized_profit_loss,
236 yesterday_balance,
237 yesterday_ote,
238 yesterday_mvo,
239 yesterday_collateral,
240 net_change_pc,
241 total_filled_qty,
242 total_filled_orders,
243 long_open_positions_qty,
244 short_open_positions_qty,
245 min_days_till_position_contract_expiration,
246 purchasing_power_limit,
247 } = other;
248 self.account_id = account_id.or(self.account_id);
249 self.currency = currency.clone().or_else(|| self.currency.clone());
250 self.total_margin = total_margin.or(self.total_margin);
251 self.position_margin = position_margin.or(self.position_margin);
252 self.purchasing_power = purchasing_power.or(self.purchasing_power);
253 self.ote = ote.or(self.ote);
254 self.mvo = mvo.or(self.mvo);
255 self.mvf = mvf.or(self.mvf);
256 self.margin_credit = margin_credit.or(self.margin_credit);
257 self.cash_excess = cash_excess.or(self.cash_excess);
258 self.current_balance = current_balance.or(self.current_balance);
259 self.profit_loss = profit_loss.or(self.profit_loss);
260 self.unrealized_profit_loss =
261 unrealized_profit_loss.or(self.unrealized_profit_loss);
262 self.yesterday_balance = yesterday_balance.or(self.yesterday_balance);
263 self.yesterday_ote = yesterday_ote.or(self.yesterday_ote);
264 self.yesterday_mvo = yesterday_mvo.or(self.yesterday_mvo);
265 self.yesterday_collateral = yesterday_collateral.or(self.yesterday_collateral);
266 self.net_change_pc = net_change_pc.or(self.net_change_pc);
267 self.total_filled_qty = total_filled_qty.or(self.total_filled_qty);
268 self.total_filled_orders = total_filled_orders.or(self.total_filled_orders);
269 self.long_open_positions_qty =
270 long_open_positions_qty.or(self.long_open_positions_qty);
271 self.short_open_positions_qty =
272 short_open_positions_qty.or(self.short_open_positions_qty);
273 self.min_days_till_position_contract_expiration =
274 min_days_till_position_contract_expiration
275 .or(self.min_days_till_position_contract_expiration);
276 self.purchasing_power_limit =
277 purchasing_power_limit.or(self.purchasing_power_limit);
278 for field in cleared_fields {
279 match field {
280 2 => {
281 self.is_snapshot = None;
282 }
283 3 => {
284 self.account_id = None;
285 }
286 4 => {
287 self.currency = None;
288 }
289 6 => {
290 self.total_margin = None;
291 }
292 7 => {
293 self.position_margin = None;
294 }
295 8 => {
296 self.purchasing_power = None;
297 }
298 9 => {
299 self.ote = None;
300 }
301 10 => {
302 self.mvo = None;
303 }
304 11 => {
305 self.mvf = None;
306 }
307 12 => {
308 self.margin_credit = None;
309 }
310 13 => {
311 self.cash_excess = None;
312 }
313 15 => {
314 self.current_balance = None;
315 }
316 16 => {
317 self.profit_loss = None;
318 }
319 17 => {
320 self.unrealized_profit_loss = None;
321 }
322 18 => {
323 self.yesterday_balance = None;
324 }
325 24 => {
326 self.yesterday_ote = None;
327 }
328 25 => {
329 self.yesterday_mvo = None;
330 }
331 14 => {
332 self.yesterday_collateral = None;
333 }
334 26 => {
335 self.net_change_pc = None;
336 }
337 19 => {
338 self.total_filled_qty = None;
339 }
340 20 => {
341 self.total_filled_orders = None;
342 }
343 21 => {
344 self.long_open_positions_qty = None;
345 }
346 22 => {
347 self.short_open_positions_qty = None;
348 }
349 23 => {
350 self.min_days_till_position_contract_expiration = None;
351 }
352 27 => {
353 self.purchasing_power_limit = None;
354 }
355 _ => {}
356 }
357 }
358 }
359}
360
361#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
362#[cfg_attr(feature = "netidx", derive(Pack, FromValue))]
363pub struct CqgPositionStatus {
364 pub account: i32,
365 pub market: MarketId,
366 pub positions: Vec<CqgPosition>,
367 #[serde(default)]
368 #[cfg_attr(feature = "netidx", pack(default))]
369 pub is_snapshot: bool,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
373#[cfg_attr(feature = "netidx", derive(Pack, FromValue))]
374pub struct CqgPosition {
375 pub id: i32,
377
378 pub qty: Option<Decimal>,
382
383 pub price_correct: Decimal,
386
387 pub trade_date: i64,
389
390 pub statement_date: i64,
392
393 pub trade_utc_timestamp: Option<DateTime<Utc>>,
395
396 pub is_aggregated: bool,
398
399 pub is_short: bool,
402
403 pub is_yesterday: Option<bool>,
412
413 pub speculation_type: Option<u32>,
415}
416
417impl PartialOrd for CqgPosition {
418 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
419 self.id.partial_cmp(&other.id)
420 }
421}
422
423impl Ord for CqgPosition {
424 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
425 self.id.cmp(&other.id)
426 }
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
430#[cfg_attr(feature = "netidx", derive(Pack, FromValue))]
431pub struct CancelReject {
432 pub cancel_id: ArcStr,
433 pub order_id: OrderId,
434 pub reason: RejectReason,
435}
436
437#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
438#[cfg_attr(feature = "netidx", derive(Pack, FromValue))]
439pub enum CqgMessage {
440 Order(CqgOrder),
441 Cancel(Cancel),
442 CancelAll,
443 Ack(Ack),
444 Out(Out),
445 Fill(Result<Fill, AberrantFill>),
446 Reject(Reject),
447 CancelReject(CancelReject),
448 Folio(FolioMessage),
449 CqgTrades(Vec<CqgTrade>),
450 CqgAccountSummary(CqgAccountSummary),
451 CqgPositionStatus(CqgPositionStatus),
452 CqgOrderSnapshot(Arc<Vec<(i32, BitFlags<OrderStateFlags, u8>, Order)>>),
453}
454
455impl TryInto<OrderflowMessage> for &CqgMessage {
456 type Error = ();
457
458 fn try_into(self) -> Result<OrderflowMessage, ()> {
459 match self {
460 CqgMessage::Order(o) => Ok(OrderflowMessage::Order(**o)),
461 CqgMessage::Cancel(c) => Ok(OrderflowMessage::Cancel(*c)),
462 CqgMessage::CancelAll => {
463 Ok(OrderflowMessage::CancelAll(CancelAll { venue_id: None }))
464 }
465 CqgMessage::Ack(a) => Ok(OrderflowMessage::Ack(*a)),
466 CqgMessage::Out(o) => Ok(OrderflowMessage::Out(*o)),
467 CqgMessage::Fill(f) => Ok(OrderflowMessage::Fill(*f)),
468 CqgMessage::Reject(r) => Ok(OrderflowMessage::Reject(r.clone())),
469 CqgMessage::CancelReject(_)
470 | CqgMessage::Folio(_)
471 | CqgMessage::CqgTrades(_)
472 | CqgMessage::CqgAccountSummary(_)
473 | CqgMessage::CqgOrderSnapshot(..)
474 | CqgMessage::CqgPositionStatus(_) => Err(()),
475 }
476 }
477}
478
479impl TryInto<CqgMessage> for &OrderflowMessage {
480 type Error = ();
481
482 fn try_into(self) -> Result<CqgMessage, ()> {
483 match self {
484 OrderflowMessage::Order(o) => Ok(CqgMessage::Order(CqgOrder { order: *o })),
485 OrderflowMessage::Cancel(c) => Ok(CqgMessage::Cancel(*c)),
486 OrderflowMessage::CancelAll(_) => Ok(CqgMessage::CancelAll),
487 OrderflowMessage::Ack(a) => Ok(CqgMessage::Ack(*a)),
488 OrderflowMessage::Out(o) => Ok(CqgMessage::Out(*o)),
489 OrderflowMessage::Reject(r) => Ok(CqgMessage::Reject(r.clone())),
490 OrderflowMessage::Fill(f) => Ok(CqgMessage::Fill(*f)),
491 }
492 }
493}
494
495impl TryInto<FolioMessage> for &CqgMessage {
496 type Error = ();
497
498 fn try_into(self) -> Result<FolioMessage, ()> {
499 match self {
500 CqgMessage::Folio(f) => Ok(f.clone()),
501 _ => Err(()),
502 }
503 }
504}
505
506impl TryFrom<&FolioMessage> for CqgMessage {
507 type Error = ();
508
509 fn try_from(f: &FolioMessage) -> Result<Self, ()> {
510 Ok(Self::Folio(f.clone()))
511 }
512}