1use chrono::NaiveDateTime;
2use serde::Deserialize;
3use std::{collections::HashMap, str::FromStr, sync::Arc};
4
5use async_trait::async_trait;
6use num_enum::{IntoPrimitive, TryFromPrimitive};
7
8use crate::messages::ResponseMessage;
9
10pub const DELIMITER: &str = "\u{1}";
11
12#[async_trait]
13pub trait ConnectionHandler {
14 async fn on_connect(&self);
15 async fn on_logon(&self);
16 async fn on_disconnect(&self);
17}
18
19#[allow(unused_variables)]
20#[async_trait]
21pub trait MarketDataHandler {
22 async fn on_price_of(&self, symbol_id: u32, price: SpotPrice);
24
25 async fn on_market_depth_full_refresh(
27 &self,
28 symbol_id: u32,
29 full_depth: HashMap<String, DepthPrice>,
30 );
31
32 async fn on_market_depth_incremental_refresh(&self, refresh: Vec<IncrementalRefresh>);
34
35 async fn on_accpeted_spot_subscription(&self, symbol_id: u32) {}
38
39 async fn on_accpeted_depth_subscription(&self, symbol_id: u32) {}
42
43 async fn on_rejected_spot_subscription(&self, symbol_id: u32, err_msg: String) {}
46
47 async fn on_rejected_depth_subscription(&self, symbol_id: u32, err_msg: String) {}
50}
51
52#[derive(Clone)]
53pub struct CTraderLogin {
54 pub username: String,
55 pub password: String,
56 pub server: String,
57 pub sendercompid: String,
58 pub heartbeat_interval: Option<u32>,
59}
60
61impl CTraderLogin {
62 pub fn new(
63 username: String,
64 password: String,
65 server: String,
66 sendercompid: String,
67 heartbeat_interval: Option<u32>,
68 ) -> Self {
69 Self {
70 username,
71 password,
72 server,
73 sendercompid,
74 heartbeat_interval,
75 }
76 }
77}
78#[async_trait]
79pub trait TradeDataHandler {
80 async fn on_execution_report(&self, exec_report: ExecutionReport);
81}
82
83#[derive(Debug)]
85pub struct SymbolInformation {
86 pub id: u32,
87 pub name: String,
88 pub digits: u32,
89}
90
91#[derive(Debug)]
92pub struct PositionReport {
93 pub symbol_id: u32,
94 pub position_id: String,
95 pub long_qty: f64,
96 pub short_qty: f64,
97 pub settle_price: f64,
98 pub absolute_tp: Option<f64>,
99 pub absolute_sl: Option<f64>,
100 pub trailing_sl: Option<bool>,
101 pub trigger_method_sl: Option<u32>,
102 pub guaranteed_sl: Option<bool>,
103}
104
105#[derive(Debug, PartialEq, Eq)]
106pub enum ExecutionType {
107 OrderStatus,
108 New,
109 Canceled,
110 Replace,
111 Rejected,
112 Expired,
113 Trade,
114}
115
116impl FromStr for ExecutionType {
117 type Err = ParseError;
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
119 match s {
120 "0" => Ok(ExecutionType::New),
121 "4" => Ok(ExecutionType::Canceled),
122 "5" => Ok(ExecutionType::Replace),
123 "8" => Ok(ExecutionType::Rejected),
124 "C" => Ok(ExecutionType::Expired),
125 "F" => Ok(ExecutionType::Trade),
126 "I" => Ok(ExecutionType::OrderStatus),
127 _ => Err(ParseError(s.into())),
128 }
129 }
130}
131
132#[derive(Debug)]
133pub struct ExecutionReport {
134 pub exec_type: ExecutionType,
136 pub order_report: OrderReport,
137}
138
139#[derive(Debug)]
140pub struct OrderReport {
141 pub symbol: u32,
143
144 pub order_id: String,
146
147 pub cl_ord_id: String,
149
150 pub pos_main_rept_id: String,
152
153 pub designation: Option<String>,
155
156 pub order_status: OrderStatus,
158
159 pub order_type: OrderType,
161
162 pub side: Side,
164
165 pub price: Option<f64>,
168
169 pub stop_px: Option<f64>,
171
172 pub avx_px: Option<f64>,
174
175 pub absolute_tp: Option<f64>,
177
178 pub reltative_tp: Option<f64>,
180
181 pub absolute_sl: Option<f64>,
183
184 pub reltative_sl: Option<f64>,
186
187 pub trailing_sl: Option<bool>,
189
190 pub trigger_method_sl: Option<u32>,
197 pub guaranteed_sl: Option<bool>,
199
200 pub cum_qty: Option<f64>,
202 pub order_qty: f64,
204 pub leaves_qty: f64,
206 pub last_qty: Option<f64>,
208
209 pub time_in_force: String,
215
216 pub transact_time: NaiveDateTime,
218
219 pub expire_time: Option<NaiveDateTime>,
221
222 pub text: Option<String>,
224 }
229
230#[repr(u32)]
231#[derive(Debug, TryFromPrimitive)]
232pub enum TimeInForce {
233 GoodTillCancel = 1,
234 ImmediateOrCancel = 3,
235 GoodTillDate = 6,
236}
237
238#[derive(Debug)]
239pub enum OrderStatus {
240 New,
241 ParitallyFilled,
242 Filled,
243 Rejected,
244 Cancelled,
245 Expired,
246}
247
248#[derive(Debug, PartialEq, Eq)]
249pub struct ParseError(String);
250
251impl FromStr for OrderStatus {
252 type Err = ParseError;
253 fn from_str(s: &str) -> Result<OrderStatus, Self::Err> {
254 match s {
255 "0" => Ok(Self::New),
256 "1" => Ok(Self::ParitallyFilled),
257 "2" => Ok(Self::Filled),
258 "8" => Ok(Self::Rejected),
259 "4" => Ok(Self::Cancelled),
260 "C" => Ok(Self::Expired),
261 _ => Err(ParseError(s.into())),
262 }
263 }
264}
265
266#[derive(Debug)]
269pub enum MarketType {
270 Spot,
271 Depth,
272}
273
274impl std::fmt::Display for MarketType {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 let s = match self {
277 Self::Spot => "Spot",
278 Self::Depth => "Depth",
279 };
280 f.write_str(s)
281 }
282}
283
284#[derive(Debug, Clone)]
285pub enum PriceType {
286 Bid,
287 Ask,
288}
289
290impl FromStr for PriceType {
291 type Err = ();
292
293 fn from_str(s: &str) -> Result<Self, Self::Err> {
294 match s {
295 "0" => Ok(Self::Bid),
296 "1" => Ok(Self::Ask),
297 _ => Err(()),
298 }
299 }
300}
301
302#[derive(Debug, Clone)]
303pub struct SpotPrice {
304 pub bid: f64,
305 pub ask: f64,
306}
307
308#[derive(Debug, Clone)]
309pub struct DepthPrice {
310 pub price_type: PriceType,
311 pub price: f64,
312 pub size: f64,
313}
314
315#[derive(Debug, Clone)]
316pub enum IncrementalRefresh {
317 New {
318 symbol_id: u32,
319 entry_id: String,
320 data: DepthPrice,
321 },
322 Delete {
323 symbol_id: u32,
324 entry_id: String,
325 },
326}
327
328#[derive(thiserror::Error, Debug)]
329pub enum Error {
330 #[error("No connection")]
332 NotConnected,
333 #[error("logged out")]
334 LoggedOut,
335
336 #[error("Field not found : {0}")]
337 FieldNotFoundError(Field),
338
339 #[error("Missing argument error")]
340 MissingArgumentError,
341
342 #[error("Order request failed : {0}")]
345 OrderFailed(String), #[error("Order cancel rejected : {0}")]
347 OrderCancelRejected(String),
348
349 #[error("Failed to {2} subscription {0}: {1}")]
351 SubscriptionError(u32, String, MarketType),
352 #[error("Already subscribed {1} for symbol({0})")]
353 SubscribedAlready(u32, MarketType),
354 #[error("Waiting then response of {1} subscription for symbol({0})")]
355 RequestingSubscription(u32, MarketType),
356 #[error("Not susbscribed {1} for symbol({0})")]
357 NotSubscribed(u32, MarketType),
358
359 #[error("Timeout error")]
360 TimeoutError,
361
362 #[error("Request rejected - {0}")]
364 RequestRejected(String),
365 #[error("Failed to find the response")]
366 NoResponse,
367 #[error("Unknown errro")]
368 UnknownError,
369
370 #[error(transparent)]
372 SendError(#[from] async_std::channel::SendError<ResponseMessage>),
373
374 #[error(transparent)]
377 RecvError(#[from] async_std::channel::RecvError),
378 #[error(transparent)]
379 Io(#[from] std::io::Error),
380}
381
382pub type MarketCallback = Arc<dyn Fn(InternalMDResult) -> () + Send + Sync>;
385pub type TradeCallback = Arc<dyn Fn(ResponseMessage) -> () + Send + Sync>;
386pub enum InternalMDResult {
389 MD {
390 msg_type: char,
391 symbol_id: u32,
392 data: Vec<HashMap<Field, String>>,
393 },
394 MDReject {
395 symbol_id: u32,
396 md_req_id: String,
397 err_msg: String,
398 },
399}
400
401#[derive(Debug, Deserialize, Clone)]
402pub struct Config {
403 pub host: String,
404 pub username: String,
405 pub password: String,
406 pub sender_comp_id: String,
407 pub heart_beat: u32,
408}
409
410impl Config {
411 pub fn new(
412 host: String,
413 username: String,
414 password: String,
415 sender_comp_id: String,
416 heart_beat: u32,
417 ) -> Self {
418 Self {
419 host,
420 username,
421 password,
422 sender_comp_id,
423 heart_beat,
424 }
425 }
426}
427#[repr(u32)]
428#[derive(Debug, PartialEq, TryFromPrimitive, IntoPrimitive, Clone, Eq, Hash, Copy)]
429pub enum Field {
430 AvgPx = 6,
431 BeginSeqNo = 7,
432 BeginString = 8,
433 BodyLength = 9,
434 CheckSum = 10,
435 ClOrdId = 11,
436 CumQty = 14,
437 EndSeqNo = 16,
438 OrdQty = 32,
439 MsgSeqNum = 34,
440 MsgType = 35,
441 NewSeqNo = 36,
442 OrderID = 37,
443 OrderQty = 38,
444 OrdStatus = 39,
445 OrdType = 40,
446 OrigClOrdID = 41,
447 Price = 44,
448 RefSeqNum = 45,
449 SenderCompID = 49,
450 SenderSubID = 50,
451 SendingTime = 52,
452 Side = 54,
453 Symbol = 55,
454 TargetCompID = 56,
455 TargetSubID = 57,
456 Text = 58,
457 TimeInForce = 59,
458 TransactTime = 60,
459 EncryptMethod = 98,
460 StopPx = 99,
461 OrdRejReason = 103,
462 HeartBtInt = 108,
463 TestReqID = 112,
464 GapFillFlag = 123,
465 ExpireTime = 126,
466 ResetSeqNumFlag = 141,
467 NoRelatedSym = 146,
468 ExecType = 150,
469 LeavesQty = 151,
470 IssueDate = 225,
471 MDReqID = 262,
472 SubscriptionRequestType = 263,
473 MarketDepth = 264,
474 MDUpdateType = 265,
475 NoMDEntryTypes = 267,
476 NoMDEntries = 268,
477 MDEntryType = 269,
478 MDEntryPx = 270,
479 MDEntrySize = 271,
480 MDEntryID = 278,
481 MDUpdateAction = 279,
482 SecurityReqID = 320,
483 SecurityResponseID = 322,
484 EncodedTextLen = 354,
485 EncodedText = 355,
486 RefTagID = 371,
487 RefMsgType = 372,
488 SessionRejectReason = 373,
489 BusinessRejectRefID = 379,
490 BusinessRejectReason = 380,
491 CxlRejResponseTo = 434,
492 Designation = 494,
493 Username = 553,
494 Password = 554,
495 SecurityListRequestType = 559,
496 SecurityRequestResult = 560,
497 MassStatusReqID = 584,
498 MassStatusReqType = 585,
499 NoPositions = 702,
500 LongQty = 704,
501 ShortQty = 705,
502 PosReqID = 710,
503 PosMaintRptID = 721,
504 TotalNumPosReports = 727,
505 PosReqResult = 728,
506 SettlPrice = 730,
507 TotNumReports = 911,
508 AbsoluteTP = 1000,
509 RelativeTP = 1001,
510 AbsoluteSL = 1002,
511 RelativeSL = 1003,
512 TrailingSL = 1004,
513 TriggerMethodSL = 1005,
514 GuaranteedSL = 1006,
515 SymbolName = 1007,
516 SymbolDigits = 1008,
517}
518impl std::fmt::Display for Field {
519 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
520 f.write_str(&format!("{:?}", self))
521 }
522}
523
524#[derive(Debug, Clone, PartialEq, Eq, Copy)]
525pub enum SubID {
526 QUOTE,
527 TRADE,
528}
529
530impl std::fmt::Display for SubID {
531 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
532 let s = match self {
533 SubID::QUOTE => "QUOTE",
534 SubID::TRADE => "TRADE",
535 };
536 f.write_str(s)
537 }
538}
539
540impl FromStr for SubID {
541 type Err = ();
542
543 fn from_str(s: &str) -> Result<Self, Self::Err> {
544 match s {
545 "QUOTE" => Ok(SubID::QUOTE),
546 "TRADE" => Ok(SubID::TRADE),
547 _ => Err(()),
548 }
549 }
550}
551
552#[repr(u32)]
553#[derive(Debug, PartialEq, TryFromPrimitive, Clone, Copy)]
554pub enum Side {
555 BUY = 1,
556 SELL = 2,
557}
558
559impl Default for Side {
560 fn default() -> Self {
561 Side::BUY
562 }
563}
564
565#[repr(u32)]
566#[derive(Debug, PartialEq, TryFromPrimitive, Clone, Copy)]
567pub enum OrderType {
568 Market = 1,
569 Limit = 2,
570 Stop = 3,
571 StopLimit = 4,
572}
573
574impl FromStr for OrderType {
575 type Err = ParseError;
576 fn from_str(s: &str) -> Result<Self, Self::Err> {
577 match s {
578 "1" => Ok(Self::Market),
579 "2" => Ok(Self::Limit),
580 "3" => Ok(Self::Stop),
581 "4" => Ok(Self::StopLimit),
582 _ => Err(ParseError(s.into())),
583 }
584 }
585}
586
587impl Default for OrderType {
588 fn default() -> Self {
589 OrderType::Market
590 }
591}