cfix/
types.rs

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    /// Called when a new price for a symbol is received.
23    async fn on_price_of(&self, symbol_id: u32, price: SpotPrice);
24
25    /// Called when a full refresh of the market depth is received.
26    async fn on_market_depth_full_refresh(
27        &self,
28        symbol_id: u32,
29        full_depth: HashMap<String, DepthPrice>,
30    );
31
32    /// Called when an incremental update to the market depth is received.
33    async fn on_market_depth_incremental_refresh(&self, refresh: Vec<IncrementalRefresh>);
34
35    /// Called when a spot subscription request has been accepted by the server.
36    /// This function has a default empty implementation and can be overridden by the struct implementing this trait.
37    async fn on_accpeted_spot_subscription(&self, symbol_id: u32) {}
38
39    /// Called when a depth subscription request has been accepted by the server.
40    /// This function has a default empty implementation and can be overridden by the struct implementing this trait.
41    async fn on_accpeted_depth_subscription(&self, symbol_id: u32) {}
42
43    /// Called when a spot subscription request has been rejected by the server.
44    /// This function has a default empty implementation and can be overridden by the struct implementing this trait.
45    async fn on_rejected_spot_subscription(&self, symbol_id: u32, err_msg: String) {}
46
47    /// Called when a depth subscription request has been rejected by the server.
48    /// This function has a default empty implementation and can be overridden by the struct implementing this trait.
49    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// == Trade type definitions
84#[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    /// 150.
135    pub exec_type: ExecutionType,
136    pub order_report: OrderReport,
137}
138
139#[derive(Debug)]
140pub struct OrderReport {
141    /// Instrument identificators are provided by Spotware. 55
142    pub symbol: u32,
143
144    /// cTrader order id. 37
145    pub order_id: String,
146
147    /// Unique identifier for the order, allocated by the client. 11
148    pub cl_ord_id: String,
149
150    /// Position ID. 72
151    pub pos_main_rept_id: String,
152
153    /// Client custom order label. 494
154    pub designation: Option<String>,
155
156    /// Order status. 39
157    pub order_status: OrderStatus,
158
159    /// Order type : Marekt, Limit, Stop, Stop limit. 40
160    pub order_type: OrderType,
161
162    /// 54.
163    pub side: Side,
164
165    //** price **//
166    /// If supplied in the NewOrderSingle, it is echoed back in this ExecutionReport. 44
167    pub price: Option<f64>,
168
169    /// If supplied in the NewOrderSingle, it is echoed back in this ExecutionReport. 99
170    pub stop_px: Option<f64>,
171
172    /// The price at which the deal was filled. For an IOC or GTD order, this is the VWAP (Volume Weighted Average Price) of the filled order. 6
173    pub avx_px: Option<f64>,
174
175    /// The absolute price at which Take Profit will be triggered. 1000
176    pub absolute_tp: Option<f64>,
177
178    /// The distance in pips from the entry price at which the Take Profit will be triggered. 1001
179    pub reltative_tp: Option<f64>,
180
181    /// The absolute price at which Stop Loss will be triggered. 1002
182    pub absolute_sl: Option<f64>,
183
184    /// The distance in pips from the entry price at which the Stop Loss will be triggered. 1003
185    pub reltative_sl: Option<f64>,
186
187    /// Indicates if Stop Loss is trailing. 1004
188    pub trailing_sl: Option<bool>,
189
190    /// Indicated trigger method of the Stop Loss. 1005
191    ///
192    /// 1 = The Stop Loss will be triggered by the trade side.
193    /// 2 = The stop loss will be triggered by the opposite side (Ask for Buy positions and by Bid for Sell positions),
194    /// 3 = Stop Loss will be triggered after two consecutive ticks according to the trade side.
195    /// 4 = Stop Loss will be triggered after two consecutive ticks according to the opposite side (second Ask tick for Buy positions and second Bid tick for Sell positions).
196    pub trigger_method_sl: Option<u32>,
197    /// Indicates if Stop Loss is guaranteed. 1006
198    pub guaranteed_sl: Option<bool>,
199
200    /// The total amount of the order which has been filled. 14
201    pub cum_qty: Option<f64>,
202    /// Number of shares ordered. This represents the number of shares for equities or based on normal convention the number of contracts for options, futures, convertible bonds, etc. 38
203    pub order_qty: f64,
204    /// The amount of the order still to be filled. This is a value between 0 (fully filled) and OrderQty (partially filled). 151
205    pub leaves_qty: f64,
206    /// The bought/sold amount of the order which has been filled on this (last) fill. 32
207    pub last_qty: Option<f64>,
208
209    // FIXME new type?
210    /// 59
211    /// 1 = Good Till Cancel (GTC);
212    /// 3 = Immediate Or Cancel (IOC);
213    /// 6 = Good Till Date (GTD).
214    pub time_in_force: String,
215
216    /// Time the transaction represented by this ExecutionReport occurred message (in UTC). 60
217    pub transact_time: NaiveDateTime,
218
219    /// If supplied in the NewOrderSingle, it is echoed back in this ExecutionReport. 126
220    pub expire_time: Option<NaiveDateTime>,
221
222    /// Where possible, message to explain execution report. 58
223    pub text: Option<String>,
224    // /// 103
225    // pub ord__rej_reason: Option<u32>,
226    // /// MassStatusReqID. 584
227    // pub masss_status_req_id: Option<String>,
228}
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// == Market type definition
267
268#[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    // connection errors
331    #[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("Request failed")]
343    // RequestFailed,
344    #[error("Order request failed : {0}")]
345    OrderFailed(String), // for "j"
346    #[error("Order cancel rejected : {0}")]
347    OrderCancelRejected(String),
348
349    // subscription errors for market client
350    #[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    // internal errors
363    #[error("Request rejected - {0}")]
364    RequestRejected(String),
365    #[error("Failed to find the response")]
366    NoResponse,
367    #[error("Unknown errro")]
368    UnknownError,
369
370    // reponse send error
371    #[error(transparent)]
372    SendError(#[from] async_std::channel::SendError<ResponseMessage>),
373
374    // #[error(transparent)]
375    // TriggerError(#[from] async_std::channel::SendError<String>),
376    #[error(transparent)]
377    RecvError(#[from] async_std::channel::RecvError),
378    #[error(transparent)]
379    Io(#[from] std::io::Error),
380}
381
382//
383// only for internal
384pub type MarketCallback = Arc<dyn Fn(InternalMDResult) -> () + Send + Sync>;
385pub type TradeCallback = Arc<dyn Fn(ResponseMessage) -> () + Send + Sync>;
386//
387
388pub 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}