Skip to main content

xtb_client/schema/
data.rs

1use std::ops::{Deref, DerefMut};
2use derive_setters::Setters;
3use rust_decimal::Decimal;
4use serde::{Deserialize, Serialize};
5use crate::schema::enums::{ImpactLevel, MarginMode, ProfitMode, QuoteId, TimePeriod, TradeStatus, TradingAction, TradingCommand, TransactionStatus, TransactionType};
6
7/// Structure representing user's login data
8#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
9#[setters(into, strip_option, prefix = "with_")]
10#[serde(rename_all = "camelCase")]
11pub struct LoginRequest {
12    /// userId
13    pub user_id: String,
14    /// password
15    pub password: String,
16    /// (optional, deprecated)
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub app_id: Option<String>,
19    /// (optional) application name
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub app_name: Option<String>,
22}
23
24
25/// Only logic struct to keep symmetry.
26/// The login command has no response.
27#[derive(Default, Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
28pub struct LoginResponse;
29
30
31/// Only logic struct to keep symmetry.
32/// The getAllSymbols command has no request data.
33#[derive(Default, Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
34pub struct GetAllSymbolsRequest;
35
36
37/// Wrapper for symbols
38#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
39pub struct GetAllSymbolsResponse(pub Vec<SymbolRecord>);
40
41impl Deref for GetAllSymbolsResponse {
42    type Target = Vec<SymbolRecord>;
43
44    fn deref(&self) -> &Self::Target {
45        &self.0
46    }
47}
48
49
50impl DerefMut for GetAllSymbolsResponse {
51    fn deref_mut(&mut self) -> &mut Self::Target {
52        &mut self.0
53    }
54}
55
56
57/// Structure representing details of a financial symbol
58#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
59#[setters(into, strip_option, prefix = "with_")]
60#[serde(rename_all = "camelCase")]
61pub struct SymbolRecord {
62    /// Ask price in base currency
63    pub ask: Decimal,
64    /// Bid price in base currency
65    pub bid: Decimal,
66    /// Category name
67    pub category_name: String,
68    /// Size of 1 lot
69    pub contract_size: i64,
70    /// Currency
71    pub currency: String,
72    /// Indicates whether the symbol represents a currency pair
73    pub currency_pair: bool,
74    /// The currency of calculated profit
75    pub currency_profit: String,
76    /// Description
77    pub description: String,
78    /// Null if not applicable
79    pub expiration: Option<u64>,
80    /// Symbol group name
81    pub group_name: String,
82    /// The highest price of the day in base currency
83    pub high: Decimal,
84    /// Initial margin for 1 lot order, used for profit/margin calculation
85    pub initial_margin: i64,
86    /// Maximum instant volume multiplied by 100 (in lots)
87    pub instant_max_volume: i64,
88    /// Symbol leverage
89    pub leverage: Decimal,
90    /// Long only
91    pub long_only: bool,
92    /// Maximum size of trade
93    pub lot_max: Decimal,
94    /// Minimum size of trade
95    pub lot_min: Decimal,
96    /// A value of minimum step by which the size of trade can be changed (within lotMin - lotMax range)
97    pub lot_step: Decimal,
98    /// The lowest price of the day in base currency
99    pub low: Decimal,
100    /// Used for profit calculation
101    pub margin_hedged: i64,
102    /// For margin calculation
103    pub margin_hedged_strong: bool,
104    /// For margin calculation, null if not applicable
105    pub margin_maintenance: Option<i64>,
106    /// For margin calculation
107    pub margin_mode: MarginMode,
108    /// Percentage
109    pub percentage: Decimal,
110    /// Number of symbol's pip decimal places
111    pub pips_precision: i64,
112    /// Number of symbol's price decimal places
113    pub precision: i64,
114    /// For profit calculation
115    pub profit_mode: ProfitMode,
116    /// Source of price
117    pub quote_id: QuoteId,
118    /// Indicates whether short selling is allowed on the instrument
119    pub short_selling: bool,
120    /// The difference between raw ask and bid prices
121    pub spread_raw: Decimal,
122    /// Spread representation
123    pub spread_table: Decimal,
124    /// Null if not applicable
125    pub starting: Option<u64>,
126    /// Appropriate step rule ID from getStepRules command response
127    pub step_rule_id: i64,
128    /// Minimal distance (in pips) from the current price where the stopLoss/takeProfit can be set
129    pub stops_level: i64,
130    /// Time when additional swap is accounted for weekend
131    #[serde(rename = "swap_rollover3days")]
132    pub swap_rollover_3days: i64,
133    /// Indicates whether swap value is added to position on end of day
134    pub swap_enable: bool,
135    /// Swap value for long positions in pips
136    pub swap_long: Decimal,
137    /// Swap value for short positions in pips
138    pub swap_short: Decimal,
139    /// Type of swap calculated
140    pub swap_type: i64,
141    /// Symbol name
142    pub symbol: String,
143    /// Smallest possible price change, used for profit/margin calculation, null if not applicable
144    pub tick_size: Option<Decimal>,
145    /// Value of smallest possible price change (in base currency), used for profit/margin calculation, null if not applicable
146    pub tick_value: Option<Decimal>,
147    /// Ask & bid tick time
148    pub time: u64,
149    /// Time in String
150    pub time_string: String,
151    /// Indicates whether trailing stop (offset) is applicable to the instrument.
152    pub trailing_enabled: bool,
153    /// Instrument class number
154    pub type_: i64,
155}
156
157
158/// The request has no data. This struct exists only to keep symmetry
159#[derive(Default, Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
160pub struct GetCalendarRequest;
161
162
163#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
164pub struct GetCalendarResponse(pub Vec<CalendarRecord>);
165
166
167impl Deref for GetCalendarResponse {
168    type Target = Vec<CalendarRecord>;
169
170    fn deref(&self) -> &Self::Target {
171        &self.0
172    }
173}
174
175impl DerefMut for GetCalendarResponse {
176    fn deref_mut(&mut self) -> &mut Self::Target {
177        &mut self.0
178    }
179}
180
181
182/// Structure representing details of a calendar record
183#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
184#[setters(into, strip_option, prefix = "with_")]
185#[serde(rename_all = "camelCase")]
186pub struct CalendarRecord {
187    /// Two-letter country code
188    pub country: String,
189    /// Market value (current), empty before time of release of this value (time from "time" record)
190    pub current: String,
191    /// Forecasted value
192    pub forecast: String,
193    /// Impact on market
194    pub impact: ImpactLevel,
195    /// Information period
196    pub period: String,
197    /// Value from previous information release
198    pub previous: String,
199    /// Time, when the information will be released (in this time empty "current" value should be changed with exact released value)
200    pub time: u64,
201    /// Name of the indicator for which values will be released
202    pub title: String,
203}
204
205
206/// Get chart information
207/// Please note that this function can be usually replaced by its streaming equivalent getCandles
208/// which is the preferred way of retrieving current candle data.
209#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
210#[setters(into, strip_option, prefix = "with_")]
211#[serde(rename_all = "camelCase")]
212pub struct GetChartLastRequestRequest {
213    info: ChartLastInfoRecord
214}
215
216
217/// Specification and constraints for the requested data
218#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
219#[setters(into, strip_option, prefix = "with_")]
220#[serde(rename_all = "camelCase")]
221pub struct ChartLastInfoRecord {
222    /// Period code
223    pub period: TimePeriod,
224    /// Start of chart block (rounded down to the nearest interval and excluding)
225    pub start: u64,
226    /// Symbol
227    pub symbol: String,
228}
229
230
231/// Structure representing details of rate information
232#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
233#[setters(into, strip_option, prefix = "with_")]
234#[serde(rename_all = "camelCase")]
235pub struct GetChartLastRequestResponse {
236    /// Number of decimal places
237    pub digits: i64,
238    /// Array of RATE_INFO_RECORD
239    pub rate_infos: Vec<RateInfoRecord>,
240}
241
242
243/// Structure representing details of a rate
244#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
245#[setters(into, strip_option, prefix = "with_")]
246#[serde(rename_all = "camelCase")]
247pub struct RateInfoRecord {
248    /// Value of close price (shift from open price)
249    pub close: Decimal,
250    /// Candle start time in CET/CEST time zone (see Daylight Saving Time, DST)
251    pub ctm: u64,
252    /// String representation of the 'ctm' field
253    pub ctm_string: String,
254    /// Highest value in the given period (shift from open price)
255    pub high: Decimal,
256    /// Lowest value in the given period (shift from open price)
257    pub low: Decimal,
258    /// Open price (in base currency * 10 to the power of digits)
259    pub open: Decimal,
260    /// Volume in lots
261    pub vol: Decimal,
262}
263
264
265impl From<StreamGetCandlesData> for RateInfoRecord {
266    fn from(value: StreamGetCandlesData) -> Self {
267        Self {
268            close: value.close,
269            ctm: value.ctm,
270            ctm_string: value.ctm_string,
271            high: value.high,
272            low: value.low,
273            open: value.open,
274            vol: value.vol,
275        }
276    }
277}
278
279
280/// Structure representing chart range details
281#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
282#[setters(into, strip_option, prefix = "with_")]
283#[serde(rename_all = "camelCase")]
284pub struct GetChartRangeRequestRequest {
285    /// Chart range information
286    pub info: ChartRangeInfoRecord,
287}
288
289
290/// Structure representing chart range information
291#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
292#[setters(into, strip_option, prefix = "with_")]
293#[serde(rename_all = "camelCase")]
294pub struct ChartRangeInfoRecord {
295    /// End of chart block (rounded down to the nearest interval and excluding)
296    pub end: u64,
297    /// Period code
298    pub period: TimePeriod,
299    /// Start of chart block (rounded down to the nearest interval and excluding)
300    pub start: u64,
301    /// Symbol
302    pub symbol: String,
303    /// Number of ticks needed, this field is optional
304    pub ticks: Option<i64>,
305}
306
307
308/// Response for the `getChartRangeRequest` is same as `getChartLastRequest`
309pub type GetChartRangeRequestResponse = GetChartLastRequestResponse;
310
311
312/// Request for calculation of commission
313#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
314#[setters(into, strip_option, prefix = "with_")]
315#[serde(rename_all = "camelCase")]
316pub struct GetCommissionDefRequest {
317    /// Symbol
318    pub symbol: String,
319    /// Volume
320    pub volume: Decimal,
321}
322
323
324#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
325#[setters(into, strip_option, prefix = "with_")]
326#[serde(rename_all = "camelCase")]
327pub struct GetCommissionDefResponse {
328    /// Calculated commission in account currency, could be null if not applicable
329    commission: Option<Decimal>,
330    /// Rate of exchange between account currency and instrument base currency, could be null if not applicable
331    rate_of_exchange: Option<Decimal>,
332}
333
334
335#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
336pub struct GetCurrentUserDataRequest;
337
338
339/// Structure representing account details
340#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
341#[setters(into, strip_option, prefix = "with_")]
342#[serde(rename_all = "camelCase")]
343pub struct GetCurrentUserDataResponse {
344    /// Unit the account is assigned to
345    pub company_unit: i64,
346    /// Account currency
347    pub currency: String,
348    /// Group
349    pub group: String,
350    /// Indicates whether this account is an IB account
351    pub ib_account: bool,
352    /// This field should not be used. It is inactive and its value is always 1
353    pub leverage: i64,
354    /// The factor used for margin calculations. The actual value of leverage can be calculated by dividing this value by 100
355    pub leverage_multiplier: Decimal,
356    /// SpreadType, null if not applicable
357    pub spread_type: Option<String>,
358    /// Indicates whether this account is enabled to use trailing stop
359    pub trailing_stop: bool,
360}
361
362
363/// Structure representing IB's history block
364#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
365#[setters(into, strip_option, prefix = "with_")]
366#[serde(rename_all = "camelCase")]
367pub struct GetIbsHistoryRequest {
368    /// End of IBs history block
369    pub end: u64,
370    /// Start of IBs history block
371    pub start: u64,
372}
373
374
375#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
376pub struct GetIbsHistoryResponse(pub Vec<IBRecord>);
377
378
379impl Deref for GetIbsHistoryResponse {
380    type Target = Vec<IBRecord>;
381
382    fn deref(&self) -> &Self::Target {
383        &self.0
384    }
385}
386
387
388impl DerefMut for GetIbsHistoryResponse {
389    fn deref_mut(&mut self) -> &mut Self::Target {
390        &mut self.0
391    }
392}
393
394
395/// Structure representing IB data
396#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
397#[setters(into, strip_option, prefix = "with_")]
398#[serde(rename_all = "camelCase")]
399pub struct IBRecord {
400    /// IB close price or null if not allowed to view
401    pub close_price: Option<Decimal>,
402    /// IB user login or null if not allowed to view
403    pub login: Option<String>,
404    /// IB nominal or null if not allowed to view
405    pub nominal: Option<Decimal>,
406    /// IB open price or null if not allowed to view
407    pub open_price: Option<Decimal>,
408    /// Operation code or null if not allowed to view
409    pub side: Option<TradingAction>,
410    /// IB user surname or null if not allowed to view
411    pub surname: Option<String>,
412    /// Symbol or null if not allowed to view
413    pub symbol: Option<String>,
414    /// Time the record was created or null if not allowed to view
415    pub timestamp: Option<u64>,
416    /// Volume in lots or null if not allowed to view
417    pub volume: Option<Decimal>,
418}
419
420
421#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
422pub struct GetMarginLevelRequest;
423
424
425/// Structure representing account financial information
426#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
427#[setters(into, strip_option, prefix = "with_")]
428#[serde(rename_all = "camelCase")]
429pub struct GetMarginLevelResponse {
430    /// Balance in account currency
431    pub balance: Decimal,
432    /// credit
433    pub credit: Decimal,
434    /// User currency
435    pub currency: String,
436    /// Sum of balance and all profits in account currency
437    pub equity: Decimal,
438    /// Margin requirements in account currency
439    pub margin: Decimal,
440    /// Free margin in account currency
441    #[serde(rename = "margin_free")]
442    pub margin_free: Decimal,
443    /// Margin level percentage
444    #[serde(rename = "margin_level")]
445    pub margin_level: Decimal,
446}
447
448
449#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
450#[setters(into, strip_option, prefix = "with_")]
451#[serde(rename_all = "camelCase")]
452pub struct GetMarginTradeRequest {
453    /// Symbol
454    pub symbol: String,
455    /// volume
456    pub volume: Decimal,
457}
458
459
460/// Structure representing margin information
461#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
462#[setters(into, strip_option, prefix = "with_")]
463#[serde(rename_all = "camelCase")]
464pub struct GetMarginTradeResponse {
465    /// Calculated margin in account currency
466    pub margin: Decimal,
467}
468
469
470/// Structure representing a specific Time span
471#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
472#[setters(into, strip_option, prefix = "with_")]
473#[serde(rename_all = "camelCase")]
474pub struct GetNewsRequest {
475    /// End time. 0 indicates the current time for simplicity.
476    pub end: u64,
477    /// Start time.
478    pub start: u64,
479}
480
481
482#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
483pub struct GetNewsResponse(pub Vec<NewsBodyRecord>);
484
485impl Deref for GetNewsResponse {
486    type Target = Vec<NewsBodyRecord>;
487
488    fn deref(&self) -> &Self::Target {
489        &self.0
490    }
491}
492
493impl DerefMut for GetNewsResponse {
494    fn deref_mut(&mut self) -> &mut Self::Target {
495        &mut self.0
496    }
497}
498
499
500/// Structure representing a news item
501#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
502#[setters(into, strip_option, prefix = "with_")]
503#[serde(rename_all = "camelCase")]
504pub struct NewsBodyRecord {
505    /// News body content
506    pub body: String,
507    /// Length of the news body content
508    #[serde(rename = "bodylen")]
509    pub body_len: usize,
510    /// The key of the news item
511    pub key: String,
512    /// Time when the news was posted, represented as a UNIX timestamp
513    pub time: u64,
514    /// Time when the news was posted, presented in string format
515    pub time_string: String,
516    /// Title of the news item
517    pub title: String,
518}
519
520/// Structure representing the order with operation code, price and volume
521#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
522#[setters(into, strip_option, prefix = "with_")]
523#[serde(rename_all = "camelCase")]
524pub struct GetProfitCalculationRequest {
525    /// Theoretical close price of order
526    pub close_price: Decimal,
527    /// Operation code
528    pub cmd: TradingCommand,
529    /// Theoretical open price of order
530    pub open_price: Decimal,
531    /// Symbol
532    pub symbol: String,
533    /// Volume
534    pub volume: Decimal,
535}
536
537
538/// Structure representing profit information
539#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
540#[setters(into, strip_option, prefix = "with_")]
541#[serde(rename_all = "camelCase")]
542pub struct GetProfitCalculationResponse {
543    /// Profit in account currency
544    pub profit: Decimal,
545}
546
547
548#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
549pub struct GetServerTimeRequest;
550
551
552/// Structure representing time information
553#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
554#[setters(into, strip_option, prefix = "with_")]
555#[serde(rename_all = "camelCase")]
556pub struct GetServerTimeResponse {
557    /// Time represented as a UNIX timestamp
558    pub time: u64,
559    /// Time described in form set on server (local time of server)
560    pub time_string: String,
561}
562
563
564#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
565pub struct GetStepRulesRequest;
566
567#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
568pub struct GetStepRulesResponse(pub Vec<StepRuleRecord>);
569
570
571impl Deref for GetStepRulesResponse {
572    type Target = Vec<StepRuleRecord>;
573
574    fn deref(&self) -> &Self::Target {
575        &self.0
576    }
577}
578
579impl DerefMut for GetStepRulesResponse {
580    fn deref_mut(&mut self) -> &mut Self::Target {
581        &mut self.0
582    }
583}
584
585
586/// Structure representing a step rule
587#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
588#[setters(into, strip_option, prefix = "with_")]
589#[serde(rename_all = "camelCase")]
590pub struct StepRuleRecord {
591    /// The ID of the step rule
592    pub id: u32,
593    /// The name of the step rule
594    pub name: String,
595    /// Array of `STEP_RECORD`
596    // `STEP_RECORD` needs to be replaced with the actual type
597    pub steps: Vec<StepRecord>,
598}
599
600
601/// Structure representing a record step
602#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
603#[setters(into, strip_option, prefix = "with_")]
604#[serde(rename_all = "camelCase")]
605pub struct StepRecord {
606    /// Lower border of the volume range
607    pub from_value: Decimal,
608    /// LotStep value in the given volume range
609    pub step: Decimal,
610}
611
612
613/// Structure representing a symbol
614#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
615#[setters(into, strip_option, prefix = "with_")]
616#[serde(rename_all = "camelCase")]
617pub struct GetSymbolRequest {
618    /// Symbol for the record
619    pub symbol: String,
620}
621
622
623pub type GetSymbolResponse = SymbolRecord;
624
625
626/// Structure representing a level
627#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
628#[setters(into, strip_option, prefix = "with_")]
629#[serde(rename_all = "camelCase")]
630pub struct GetTickPricesRequest {
631    /// Price level
632    pub level: u32,
633    /// Array of symbol names
634    pub symbols: Vec<String>,
635    /// The time from which the most recent tick should be looked for
636    pub timestamp: u64,
637}
638
639
640#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
641#[setters(into, strip_option, prefix = "with_")]
642#[serde(rename_all = "camelCase")]
643pub struct GetTickPricesResponse {
644    pub quotations: Vec<TickRecord>,
645}
646
647
648/// Structure representing market price data
649#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
650#[setters(into, strip_option, prefix = "with_")]
651#[serde(rename_all = "camelCase")]
652pub struct TickRecord {
653    /// Ask price in base currency
654    pub ask: Decimal,
655    /// Number of available lots to buy at given price or None if not applicable
656    pub ask_volume: Option<u32>,
657    /// Bid price in base currency
658    pub bid: Decimal,
659    /// Number of available lots to sell at given price or None if not applicable
660    pub bid_volume: Option<u32>,
661    /// The highest price of the day in base currency
662    pub high: Decimal,
663    /// Price level
664    pub level: u32,
665    /// The lowest price of the day in base currency
666    pub low: Decimal,
667    /// The difference between raw ask and bid prices
668    pub spread_raw: Decimal,
669    /// Spread representation
670    pub spread_table: Decimal,
671    /// Symbol
672    pub symbol: String,
673    /// Timestamp in UNIX time
674    pub timestamp: u64,
675}
676
677
678#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
679#[setters(into, strip_option, prefix = "with_")]
680#[serde(rename_all = "camelCase")]
681pub struct GetTradeRecordsRequest {
682    /// Array of orders (position numbers)
683    pub orders: Vec<u32>,
684}
685
686
687#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
688pub struct GetTradeRecordsResponse(pub Vec<TradeRecord>);
689
690
691impl Deref for GetTradeRecordsResponse {
692    type Target = Vec<TradeRecord>;
693
694    fn deref(&self) -> &Self::Target {
695        &self.0
696    }
697}
698
699
700impl DerefMut for GetTradeRecordsResponse {
701    fn deref_mut(&mut self) -> &mut Self::Target {
702        &mut self.0
703    }
704}
705
706
707/// Structure representing a trade order
708#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
709#[setters(into, strip_option, prefix = "with_")]
710#[serde(rename_all = "camelCase")]
711pub struct TradeRecord {
712    /// Close price in base currency
713    #[serde(rename = "close_price")]
714    pub close_price: Decimal,
715    /// Null if order is not closed
716    #[serde(rename = "close_time")]
717    pub close_time: Option<u64>,
718    /// Null if order is not closed
719    #[serde(rename="close_timeString")]
720    pub close_time_string: Option<String>,
721    /// Closed
722    pub closed: bool,
723    /// Operation code
724    pub cmd: TradingCommand,
725    /// Comment
726    pub comment: String,
727    /// Commission in account currency, null if not applicable
728    pub commission: Option<Decimal>,
729    /// The value the customer may provide in order to retrieve it later.
730    pub custom_comment: String,
731    /// Number of decimal places
732    pub digits: u32,
733    /// Null if order is not closed
734    pub expiration: Option<u64>,
735    /// Null if order is not closed
736    pub expiration_string: Option<String>,
737    /// Margin rate
738    #[serde(rename = "margin_rate")]
739    pub margin_rate: Decimal,
740    /// Trailing offset
741    pub offset: u32,
742    /// Open price in base currency
743    #[serde(rename = "open_price")]
744    pub open_price: Decimal,
745    /// Open time
746    #[serde(rename = "open_time")]
747    pub open_time: u64,
748    /// Open time string
749    #[serde(rename = "open_timeString")]
750    pub open_time_string: String,
751    /// Order number for opened transaction
752    pub order: u32,
753    /// Order number for closed transaction
754    pub order2: u32,
755    /// Order number common both for opened and closed transaction
756    pub position: u32,
757    /// Profit in account currency
758    pub profit: Decimal,
759    /// Zero if stop loss is not set (in base currency)
760    pub sl: Decimal,
761    /// Order swaps in account currency
762    pub storage: Decimal,
763    /// Symbol name or null for deposit/withdrawal operations
764    pub symbol: Option<String>,
765    /// Timestamp
766    pub timestamp: u64,
767    /// Zero if take profit is not set (in base currency)
768    pub tp: Decimal,
769    /// Volume in lots
770    pub volume: Decimal,
771}
772
773
774#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
775#[setters(into, strip_option, prefix = "with_")]
776#[serde(rename_all = "camelCase")]
777pub struct GetTradesRequest {
778    /// Whether only opened trades will be returned
779    pub opened_only: bool,
780}
781
782
783/// List of trade records
784pub type GetTradesResponse = GetTradeRecordsResponse;
785
786
787/// Structure representing a time interval
788#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
789#[setters(into, strip_option, prefix = "with_")]
790#[serde(rename_all = "camelCase")]
791pub struct GetTradesHistoryRequest {
792    /// End time, equals to current time if set to 0
793    pub end: u64,
794    /// Start time, refers to the last month interval if set to 0
795    pub start: u64,
796}
797
798
799/// List of trade records
800pub type GetTradesHistoryResponse = GetTradeRecordsResponse;
801
802
803#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
804#[setters(into, strip_option, prefix = "with_")]
805#[serde(rename_all = "camelCase")]
806pub struct GetTradingHoursRequest {
807    /// Array of symbol names
808    pub symbols: Vec<String>,
809}
810
811
812#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
813pub struct GetTradingHoursResponse(pub Vec<TradingHoursRecord>);
814
815
816impl Deref for GetTradingHoursResponse {
817    type Target = Vec<TradingHoursRecord>;
818
819    fn deref(&self) -> &Self::Target {
820        &self.0
821    }
822}
823
824impl DerefMut for GetTradingHoursResponse {
825    fn deref_mut(&mut self) -> &mut Self::Target {
826        &mut self.0
827    }
828}
829
830
831#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
832#[setters(into, strip_option, prefix = "with_")]
833#[serde(rename_all = "camelCase")]
834pub struct TradingHoursRecord {
835    /// Array of QUOTES_RECORD
836    pub quotes: Vec<HoursRecord>,
837    /// Symbol
838    pub symbol: String,
839    /// Array of TRADING_RECORD
840    pub trading: Vec<HoursRecord>,
841}
842
843
844#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
845#[setters(into, strip_option, prefix = "with_")]
846#[serde(rename_all = "camelCase")]
847pub struct HoursRecord {
848    /// Day of the week
849    pub day: u8,
850    /// Start time in ms from 00:00 CET / CEST time zone
851    pub from_t: u64,
852    /// End time in ms from 00:00 CET / CEST time zone
853    pub to_t: u64,
854}
855
856
857#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
858pub struct GetVersionRequest;
859
860
861#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
862#[setters(into, strip_option, prefix = "with_")]
863#[serde(rename_all = "camelCase")]
864pub struct GetVersionResponse {
865    /// Current API version
866    pub version: String,
867}
868
869
870#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
871pub struct PingRequest;
872
873
874#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
875pub struct PingResponse;
876
877
878/// Structure embedding a TRADE_TRANS_INFO instance
879#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
880#[setters(into, strip_option, prefix = "with_")]
881#[serde(rename_all = "camelCase")]
882pub struct TradeTransactionRequest {
883    /// The embedded TRADE_TRANS_INFO
884    pub trade_trans_info: TradeTransInfo,
885}
886
887
888#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
889#[setters(into, strip_option, prefix = "with_")]
890#[serde(rename_all = "camelCase")]
891pub struct TradeTransInfo {
892    /// Operation code
893    pub cmd: TradingCommand,
894    /// The value the customer may provide in order to retrieve it later.
895    pub custom_comment: String,
896    /// Pending order expiration time
897    pub expiration: u64,
898    /// Trailing offset
899    pub offset: i32,
900    /// 0 or position number for closing/modifications
901    pub order: i32,
902    /// Trade price
903    pub price: f64,
904    /// Stop loss
905    pub sl: f64,
906    /// Trade symbol
907    pub symbol: String,
908    /// Take profit
909    pub tp: f64,
910    /// Trade transaction type
911    pub type_: TransactionType,
912    /// Trade volume
913    pub volume: f64,
914}
915
916
917#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
918#[setters(into, strip_option, prefix = "with_")]
919#[serde(rename_all = "camelCase")]
920pub struct TradeTransactionResponse {
921    /// order
922    pub order: i32,
923}
924
925
926#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
927#[setters(into, strip_option, prefix = "with_")]
928#[serde(rename_all = "camelCase")]
929pub struct TradeTransactionStatusRequest {
930    /// order
931    pub order: i32,
932}
933
934
935/// Structure representing the response
936#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
937#[setters(into, strip_option, prefix = "with_")]
938#[serde(rename_all = "camelCase")]
939pub struct TradeTransactionStatusResponse {
940    /// Price in base currency
941    pub ask: Decimal,
942    /// Price in base currency
943    pub bid: Decimal,
944    /// The value the customer may provide in order to retrieve it later
945    pub custom_comment: String,
946    /// Can be null
947    pub message: Option<String>,
948    /// Unique order number
949    pub order: i32,
950    /// Request status code
951    pub request_status: TransactionStatus,
952}
953
954
955#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
956pub struct StreamGetBalanceSubscribe;
957
958#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
959pub struct StreamGetBalanceUnsubscribe;
960
961
962#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
963#[setters(into, strip_option, prefix = "with_")]
964#[serde(rename_all = "camelCase")]
965pub struct StreamGetBalanceData {
966    /// Balance in account currency
967    pub balance: Decimal,
968    /// Credit in account currency
969    pub credit: Decimal,
970    /// Sum of balance and all profits in account currency
971    pub equity: Decimal,
972    /// Margin requirements
973    pub margin: Decimal,
974    /// Free margin
975    pub margin_free: Decimal,
976    /// Margin level percentage
977    pub margin_level: Decimal,
978}
979
980
981#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
982#[setters(into, strip_option, prefix = "with_")]
983#[serde(rename_all = "camelCase")]
984pub struct StreamGetCandlesSubscribe {
985    /// Symbol
986    pub symbol: String,
987}
988
989
990#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
991#[setters(into, strip_option, prefix = "with_")]
992#[serde(rename_all = "camelCase")]
993pub struct StreamGetCandlesUnsubscribe {
994    /// Symbol
995    pub symbol: String,
996}
997
998
999#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
1000#[setters(into, strip_option, prefix = "with_")]
1001#[serde(rename_all = "camelCase")]
1002pub struct StreamGetCandlesData {
1003    /// Close price in base currency
1004    pub close: Decimal,
1005    /// Candle start time in CET time zone (Central European Time)
1006    pub ctm: u64,
1007    /// String representation of the ctm field
1008    pub ctm_string: String,
1009    /// Highest value in the given period in base currency
1010    pub high: Decimal,
1011    /// Lowest value in the given period in base currency
1012    pub low: Decimal,
1013    /// Open price in base currency
1014    pub open: Decimal,
1015    /// Source of price
1016    pub quote_id: QuoteId,
1017    /// Symbol
1018    pub symbol: String,
1019    /// Volume in lots
1020    pub vol: Decimal,
1021}
1022
1023
1024#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
1025pub struct StreamGetKeepAliveSubscribe;
1026
1027#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
1028pub struct StreamGetKeepAliveUnsubscribe;
1029
1030
1031#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
1032#[setters(into, strip_option, prefix = "with_")]
1033#[serde(rename_all = "camelCase")]
1034pub struct StreamGetKeepAliveData {
1035    /// Current timestamp
1036    pub timestamp: u64,
1037}
1038
1039
1040#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
1041pub struct StreamGetNewsSubscribe;
1042
1043#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
1044pub struct StreamGetNewsUnsubscribe;
1045
1046
1047#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
1048#[setters(into, strip_option, prefix = "with_")]
1049#[serde(rename_all = "camelCase")]
1050pub struct StreamGetNewsData {
1051    /// Body content of the news article
1052    pub body: String,
1053    /// Unique identifier for the news article
1054    pub key: String,
1055    /// Time of the news article
1056    pub time: u64,
1057    /// Title of the news article
1058    pub title: String,
1059}
1060
1061
1062#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
1063pub struct StreamGetProfitSubscribe;
1064
1065
1066#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
1067pub struct StreamGetProfitUnsubscribe;
1068
1069
1070#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
1071#[setters(into, strip_option, prefix = "with_")]
1072#[serde(rename_all = "camelCase")]
1073pub struct StreamGetProfitData {
1074    /// Order number
1075    pub order: i32,
1076    /// Transaction ID
1077    pub order2: i32,
1078    /// Position number
1079    pub position: i32,
1080    /// Profit in account currency
1081    pub profit: Decimal,
1082}
1083
1084
1085#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
1086#[setters(into, strip_option, prefix = "with_")]
1087#[serde(rename_all = "camelCase")]
1088pub struct StreamGetTickPricesSubscribe {
1089    /// Financial instrument symbol
1090    pub symbol: String,
1091    /// Minimal interval in milliseconds between any two consecutive updates. It is optional.
1092    pub min_arrival_time: Option<u64>,
1093    /// Specifies the maximum level of the quote that the user is interested in. It is optional.
1094    pub max_level: Option<u64>,
1095}
1096
1097
1098#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
1099#[setters(into, strip_option, prefix = "with_")]
1100#[serde(rename_all = "camelCase")]
1101pub struct StreamGetTickPricesUnsubscribe {
1102    /// Financial instrument symbol
1103    pub symbol: String,
1104}
1105
1106
1107#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
1108#[setters(into, strip_option, prefix = "with_")]
1109#[serde(rename_all = "camelCase")]
1110pub struct StreamGetTickPricesData {
1111    /// Ask price in base currency
1112    pub ask: Decimal,
1113    /// Number of available lots to buy at given price
1114    pub ask_volume: Option<i32>,
1115    /// Bid price in base currency
1116    pub bid: Decimal,
1117    /// Number of available lots to sell at given price
1118    pub bid_volume: Option<i32>,
1119    /// The highest price of the day in base currency
1120    pub high: Decimal,
1121    /// Price level
1122    pub level: i32,
1123    /// The lowest price of the day in base currency
1124    pub low: Decimal,
1125    /// Source of price
1126    pub quote_id: QuoteId,
1127    /// The difference between raw ask and bid prices
1128    pub spread_raw: Decimal,
1129    /// Spread representation
1130    pub spread_table: Decimal,
1131    /// Financial instrument symbol
1132    pub symbol: String,
1133    /// Time when the information was updated
1134    pub timestamp: u64,
1135}
1136
1137
1138#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
1139pub struct StreamGetTradesSubscribe;
1140
1141#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
1142pub struct StreamGetTradesUnsubscribe;
1143
1144
1145#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
1146#[setters(into, strip_option, prefix = "with_")]
1147#[serde(rename_all = "camelCase")]
1148pub struct StreamGetTradesData {
1149    /// Close price in base currency
1150    #[serde(rename = "close_price")]
1151    pub close_price: Decimal,
1152    /// Close time, null if order is not closed
1153    #[serde(rename = "close_time")]
1154    pub close_time: Option<u64>,
1155    /// Is the order closed
1156    pub closed: bool,
1157    /// Operation code
1158    pub cmd: TradingCommand,
1159    /// Comment
1160    pub comment: String,
1161    /// Commission in account currency, null if not applicable
1162    pub commission: Option<Decimal>,
1163    /// Custom comment
1164    pub custom_comment: String,
1165    /// Number of decimal places
1166    pub digits: i32,
1167    /// Expiration time, null if order is not closed
1168    pub expiration: Option<u64>,
1169    /// Margin rate
1170    #[serde(rename = "margin_rate")]
1171    pub margin_rate: Decimal,
1172    /// Trailing offset
1173    pub offset: i32,
1174    /// Open price in base currency
1175    #[serde(rename = "open_price")]
1176    pub open_price: Decimal,
1177    /// Open time
1178    #[serde(rename = "open_time")]
1179    pub open_time: u64,
1180    /// Order number for opened transaction
1181    pub order: i32,
1182    /// Transaction id
1183    pub order2: i32,
1184    /// Position number (if type is 0 and 2) or transaction parameter (if type is 1)
1185    pub position: i32,
1186    /// Profit, null unless the trade is closed (type=2) or opened (type=0)
1187    pub profit: Option<Decimal>,
1188    /// Stop loss amount, zero if not set (in base currency)
1189    pub sl: Decimal,
1190    /// Trade state, should be used for detecting pending order's cancellation
1191    pub state: TradeStatus,
1192    /// Storage
1193    pub storage: Decimal,
1194    /// Financial instrument symbol
1195    pub symbol: String,
1196    /// Take profit amount, zero if not set (in base currency)
1197    pub tp: Decimal,
1198    /// Type
1199    pub type_: TransactionType,
1200    /// Volume in lots
1201    pub volume: Decimal,
1202}
1203
1204
1205#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
1206pub struct StreamGetTradeStatusSubscribe;
1207
1208
1209#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
1210pub struct StreamGetTradeStatusUnsubscribe;
1211
1212
1213#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize, Setters)]
1214#[setters(into, strip_option, prefix = "with_")]
1215#[serde(rename_all = "camelCase")]
1216pub struct StreamGetTradeStatusData {
1217    /// The value the customer may provide in order to retrieve it later
1218    pub custom_comment: String,
1219    /// Message, can be null
1220    pub message: Option<String>,
1221    /// Unique order number
1222    pub order: i32,
1223    /// Price in base currency
1224    pub price: Decimal,
1225    /// Request status code
1226    pub request_status: TransactionStatus,
1227}
1228
1229
1230#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
1231pub struct StreamPingSubscribe;
1232
1233
1234#[cfg(test)]
1235mod tests {
1236    use serde_json::Value;
1237
1238    fn assert_all_keys(value: &Value, keys: Vec<&str>) {
1239        assert!(value.is_object());
1240        let Value::Object(mapping) = value else { unreachable!() };
1241        let extra_keys: Vec<_> = mapping.keys().filter(|k| !keys.contains(&k.as_str())).map(|k| k.to_owned()).collect();
1242        let missing_keys: Vec<_> = keys.into_iter().filter(|k| !mapping.contains_key(k.to_string().as_str())).map(|k| k.to_owned()).collect();
1243
1244        if !extra_keys.is_empty() || !missing_keys.is_empty() {
1245            panic!("Keys in value does not match to expected keys.\nExtra keys: {:?}\nMissing keys: {:?}", extra_keys, missing_keys);
1246        }
1247    }
1248
1249    mod conversions {
1250        use crate::schema::{QuoteId, RateInfoRecord, StreamGetCandlesData};
1251
1252        fn make_sgcd() -> StreamGetCandlesData {
1253            StreamGetCandlesData::default()
1254                .with_close(20)
1255                .with_ctm(123u64)
1256                .with_ctm_string("some_string")
1257                .with_high(40)
1258                .with_low(10)
1259                .with_open(20)
1260                .with_quote_id(QuoteId::Cross)
1261                .with_symbol("EURUSD".to_owned())
1262                .with_vol(50)
1263        }
1264
1265        /// Convert `StreamGetCandlesData` to the `RateInfoRecord` using `From` trait
1266        #[test]
1267        fn convert_sgcd_to_rir_from() {
1268            let orig = make_sgcd();
1269            let rir = RateInfoRecord::from(orig.clone());
1270
1271            assert_eq!(rir.close, orig.close);
1272            assert_eq!(rir.ctm, orig.ctm);
1273            assert_eq!(rir.ctm_string, orig.ctm_string);
1274            assert_eq!(rir.high, orig.high);
1275            assert_eq!(rir.low, orig.low);
1276            assert_eq!(rir.open, orig.open);
1277            assert_eq!(rir.vol, orig.vol);
1278        }
1279
1280        /// Convert `StreamGetCandlesData` to the `RateInfoRecord` using `Into` trait
1281        #[test]
1282        fn convert_sgcd_to_rir_into() {
1283            let orig = make_sgcd();
1284            let rir: RateInfoRecord = orig.clone().into();
1285
1286            assert_eq!(rir.close, orig.close);
1287            assert_eq!(rir.ctm, orig.ctm);
1288            assert_eq!(rir.ctm_string, orig.ctm_string);
1289            assert_eq!(rir.high, orig.high);
1290            assert_eq!(rir.low, orig.low);
1291            assert_eq!(rir.open, orig.open);
1292            assert_eq!(rir.vol, orig.vol);
1293        }
1294    }
1295
1296    mod serialize_deserialize {
1297        use std::fmt::Debug;
1298        use std::ops::Deref;
1299        use rstest::rstest;
1300        use serde::{Deserialize, Serialize};
1301        use serde_json::{from_str, from_value, to_string, to_value};
1302        use crate::schema::data::{GetTickPricesResponse, StreamPingSubscribe, StreamGetTradeStatusSubscribe, StreamGetTradeStatusUnsubscribe, StreamGetTradesData, StreamGetTradesSubscribe, StreamGetTradesUnsubscribe, StreamGetTickPricesSubscribe, StreamGetTickPricesUnsubscribe, StreamGetTickPricesData, StreamGetProfitData, StreamGetProfitSubscribe, StreamGetProfitUnsubscribe, StreamGetKeepAliveData, StreamGetNewsSubscribe, StreamGetNewsData, StreamGetNewsUnsubscribe, StreamGetKeepAliveSubscribe, StreamGetKeepAliveUnsubscribe, StreamGetCandlesSubscribe, StreamGetCandlesUnsubscribe, StreamGetBalanceData, StreamGetCandlesData, StreamGetBalanceSubscribe, StreamGetBalanceUnsubscribe, TradeTransactionStatusResponse, TradeTransactionStatusRequest, TradeTransactionResponse, TradeTransInfo, TradeTransactionRequest, GetVersionResponse, PingRequest, PingResponse, GetVersionRequest, TradingHoursRecord, HoursRecord, GetTradingHoursResponse, GetTradingHoursRequest, GetTradesHistoryRequest, GetTradesRequest, GetTradeRecordsResponse, TradeRecord, GetTradeRecordsRequest, TickRecord, GetTickPricesRequest, GetSymbolRequest, GetStepRulesResponse, StepRuleRecord, StepRecord, GetStepRulesRequest, GetServerTimeRequest, GetServerTimeResponse, GetProfitCalculationRequest, GetProfitCalculationResponse, GetNewsRequest, GetNewsResponse, NewsBodyRecord, GetMarginTradeRequest, GetMarginTradeResponse, GetMarginLevelRequest, GetMarginLevelResponse, IBRecord, GetIbsHistoryResponse, GetCurrentUserDataResponse, GetCurrentUserDataRequest, GetCommissionDefRequest, GetCommissionDefResponse, GetChartRangeRequestRequest, ChartRangeInfoRecord, GetChartLastRequestResponse, RateInfoRecord, ChartLastInfoRecord, GetChartLastRequestRequest, GetCalendarResponse, CalendarRecord, GetAllSymbolsRequest, GetAllSymbolsResponse, LoginRequest, LoginResponse, SymbolRecord, GetCalendarRequest, StreamGetTradeStatusData};
1303        use crate::schema::data::tests::assert_all_keys;
1304
1305        #[rstest]
1306        #[case::LoginRequest_1(LoginRequest::default(), vec ! ["userId", "password"])]
1307        #[case::LoginRequest_2(LoginRequest::default().with_app_id("foo").with_app_name("bar"), vec ! ["userId", "password", "appId", "appName"])]
1308        #[case::SymbolRecord_1(SymbolRecord::default(), vec ! ["ask", "bid", "categoryName", "contractSize", "currency", "currencyPair", "currencyProfit", "description", "expiration", "groupName", "high", "initialMargin", "instantMaxVolume", "leverage", "longOnly", "lotMax", "lotMin", "lotStep", "low", "marginHedged", "marginHedgedStrong", "marginMaintenance", "marginMode", "percentage", "pipsPrecision", "precision", "profitMode", "quoteId", "shortSelling", "spreadRaw", "spreadTable", "starting", "stepRuleId", "stopsLevel", "swap_rollover3days", "swapEnable", "swapLong", "swapShort", "swapType", "symbol", "tickSize", "tickValue", "time", "timeString", "trailingEnabled", "type"])]
1309        #[case::CalendarRecord_1(CalendarRecord::default(), vec!["country", "current", "forecast", "impact", "period", "previous", "time", "title"])]
1310        #[case::GetChartLastRequestRequest_1(GetChartLastRequestRequest::default(), vec!["info"])]
1311        #[case::ChartLastInfoRecord_1(ChartLastInfoRecord::default(), vec!["period", "start", "symbol"])]
1312        #[case::GetChartLastRequestResponse_1(GetChartLastRequestResponse::default(), vec!["digits", "rateInfos"])]
1313        #[case::RateInfoRecord_1(RateInfoRecord::default(), vec!["close", "ctm", "ctmString", "high", "low", "open", "vol"])]
1314        #[case::GetChartRangeRequestRequest_1(GetChartRangeRequestRequest::default(), vec!["info"])]
1315        #[case::ChartRangeInfoRecord_1(ChartRangeInfoRecord::default(), vec!["end", "period", "start", "symbol", "ticks"])]
1316        #[case::GetCommissionDefRequest_1(GetCommissionDefRequest::default(), vec!["symbol", "volume"])]
1317        #[case::GetCommissionDefResponse_1(GetCommissionDefResponse::default(), vec!["commission", "rateOfExchange"])]
1318        #[case::GetCurrentUserDataResponse_1(GetCurrentUserDataResponse::default(), vec!["companyUnit", "currency", "group", "ibAccount", "leverage", "leverageMultiplier", "spreadType", "trailingStop"])]
1319        #[case::IBRecord_1(IBRecord::default(), vec!["closePrice", "login", "nominal", "openPrice", "side", "surname", "symbol", "timestamp", "volume"])]
1320        #[case::GetMarginLevelResponse_1(GetMarginLevelResponse::default(), vec!["balance", "credit", "currency", "equity", "margin", "margin_free", "margin_level"])]
1321        #[case::GetMarginTradeRequest_1(GetMarginTradeRequest::default(), vec!["symbol", "volume"])]
1322        #[case::GetMarginTradeResponse_1(GetMarginTradeResponse::default(), vec!["margin"])]
1323        #[case::GetNewsRequest_1(GetNewsRequest::default(), vec!["end", "start"])]
1324        #[case::NewsBodyRecord_1(NewsBodyRecord::default(), vec!["body", "bodylen", "key", "time", "timeString", "title"])]
1325        #[case::GetProfitCalculationRequest_1(GetProfitCalculationRequest::default(), vec!["closePrice", "cmd", "openPrice", "symbol", "volume"])]
1326        #[case::GetProfitCalculationResponse_1(GetProfitCalculationResponse::default(), vec!["profit"])]
1327        #[case::GetServerTimeResponse_1(GetServerTimeResponse::default(), vec!["time", "timeString"])]
1328        #[case::StepRuleRecord_1(StepRuleRecord::default(), vec!["id", "name", "steps"])]
1329        #[case::StepRecord_1(StepRecord::default(), vec!["fromValue", "step"])]
1330        #[case::GetSymbolRequest_1(GetSymbolRequest::default(), vec!["symbol"])]
1331        #[case::GetTickPricesRequest_1(GetTickPricesRequest::default(), vec!["level", "symbols", "timestamp"])]
1332        #[case::GetTickPricesResponse_1(GetTickPricesResponse::default(), vec!["quotations"])]
1333        #[case::TickRecord_1(TickRecord::default(), vec!["ask", "askVolume", "bid", "bidVolume", "high", "level", "low", "spreadRaw", "spreadTable", "symbol", "timestamp"])]
1334        #[case::GetTradeRecordsRequest_1(GetTradeRecordsRequest::default(), vec!["orders"])]
1335        #[case::TradeRecord_1(TradeRecord::default(), vec!["close_price", "close_time", "close_timeString", "closed", "cmd", "comment", "commission", "customComment", "digits", "expiration", "expirationString", "margin_rate", "offset", "open_price", "open_time", "open_timeString", "order", "order2", "position", "profit", "sl", "storage", "symbol", "timestamp", "tp", "volume"])]
1336        #[case::GetTradesRequest_1(GetTradesRequest::default(), vec!["openedOnly"])]
1337        #[case::GetTradesHistoryRequest_1(GetTradesHistoryRequest::default(), vec!["end", "start"])]
1338        #[case::GetTradingHoursRequest_1(GetTradingHoursRequest::default(), vec!["symbols"])]
1339        #[case::TradingHoursRecord_1(TradingHoursRecord::default(), vec!["quotes", "symbol", "trading"])]
1340        #[case::HoursRecord_1(HoursRecord::default(), vec!["day", "fromT", "toT"])]
1341        #[case::GetVersionResponse_1(GetVersionResponse::default(), vec!["version"])]
1342        #[case::TradeTransactionRequest_1(TradeTransactionRequest::default(), vec!["tradeTransInfo"])]
1343        #[case::TradeTransInfo_1(TradeTransInfo::default(), vec!["cmd", "customComment", "expiration", "offset", "order", "price", "sl", "symbol", "tp", "type", "volume"])]
1344        #[case::TradeTransactionResponse_1(TradeTransactionResponse::default(), vec!["order"])]
1345        #[case::TradeTransactionStatusRequest_1(TradeTransactionStatusRequest::default(), vec!["order"])]
1346        #[case::TradeTransactionStatusResponse_1(TradeTransactionStatusResponse::default(), vec!["ask", "bid", "customComment", "message", "order", "requestStatus"])]
1347        #[case::StreamGetBalanceData_1(StreamGetBalanceData::default(), vec!["balance", "credit", "equity", "margin", "marginFree", "marginLevel"])]
1348        #[case::StreamGetCandlesData_1(StreamGetCandlesData::default(), vec!["close", "ctm", "ctmString", "high", "low", "open", "quoteId", "symbol", "vol"])]
1349        #[case::StreamGetCandlesSubscribe_1(StreamGetCandlesSubscribe::default(), vec!["symbol"])]
1350        #[case::StreamGetCandlesUnsubscribe_1(StreamGetCandlesUnsubscribe::default(), vec!["symbol"])]
1351        #[case::StreamGetKeepAliveData_1(StreamGetKeepAliveData::default(), vec!["timestamp"])]
1352        #[case::StreamGetNewsData_1(StreamGetNewsData::default(), vec!["body", "key", "time", "title"])]
1353        #[case::StreamGetProfitData_1(StreamGetProfitData::default(), vec!["order", "order2", "position", "profit"])]
1354        #[case::StreamGetTickPricesSubscribe_1(StreamGetTickPricesSubscribe::default(), vec!["symbol", "minArrivalTime", "maxLevel"])]
1355        #[case::StreamGetTickPricesUnsubscribe_1(StreamGetTickPricesUnsubscribe::default(), vec!["symbol"])]
1356        #[case::StreamGetTickPricesData_1(StreamGetTickPricesData::default(), vec!["ask", "askVolume", "bid", "bidVolume", "high", "level", "low", "quoteId", "spreadRaw", "spreadTable", "symbol", "timestamp"])]
1357        #[case::StreamGetTradesData_1(StreamGetTradesData::default(), vec!["close_price", "close_time", "closed", "cmd", "comment", "commission", "customComment", "digits", "expiration", "margin_rate", "offset", "open_price", "open_time", "order", "order2", "position", "profit", "sl", "state", "storage", "symbol", "tp", "type", "volume"])]
1358        #[case::StreamGetTradeStatusData_1(StreamGetTradeStatusData::default(), vec!["customComment", "message", "order", "price", "requestStatus"])]
1359        fn serialize_deserialize_payload_struct<T: Serialize + Clone + Default + Debug + PartialEq + for<'de> Deserialize<'de>>(#[case] original: T, #[case] keys: Vec<&str>) {
1360            let serialized_value = to_value(original.clone()).unwrap();
1361            assert_all_keys(&serialized_value, keys);
1362            let new_value = from_value(serialized_value).unwrap();
1363            assert_eq!(original, new_value);
1364        }
1365
1366        #[rstest]
1367        #[case::LoginResponse_1(LoginResponse)]
1368        #[case::GetAllSymbolsRequest_1(GetAllSymbolsRequest)]
1369        #[case::GetCalendarRequest_1(GetCalendarRequest)]
1370        #[case::GetCalendarRequest_1(GetCurrentUserDataRequest)]
1371        #[case::GetMarginLevelRequest_1(GetMarginLevelRequest)]
1372        #[case::GetServerTimeRequest_1(GetServerTimeRequest)]
1373        #[case::GetStepRulesRequest_1(GetStepRulesRequest)]
1374        #[case::GetVersionRequest_1(GetVersionRequest)]
1375        #[case::PingRequest_1(PingRequest)]
1376        #[case::PingResponse_1(PingResponse)]
1377        #[case::StreamGetBalanceSubscribe_1(StreamGetBalanceSubscribe)]
1378        #[case::StreamGetBalanceUnsubscribe_1(StreamGetBalanceUnsubscribe)]
1379        #[case::StreamGetKeepAliveSubscribe_1(StreamGetKeepAliveSubscribe)]
1380        #[case::StreamGetKeepAliveUnsubscribe_1(StreamGetKeepAliveUnsubscribe)]
1381        #[case::StreamGetNewsSubscribe_1(StreamGetNewsSubscribe)]
1382        #[case::StreamGetNewsUnsubscribe_1(StreamGetNewsUnsubscribe)]
1383        #[case::StreamGetProfitSubscribe_1(StreamGetProfitSubscribe)]
1384        #[case::StreamGetProfitUnsubscribe_1(StreamGetProfitUnsubscribe)]
1385        #[case::StreamGetTradesSubscribe_1(StreamGetTradesSubscribe)]
1386        #[case::StreamGetTradesUnsubscribe_1(StreamGetTradesUnsubscribe)]
1387        #[case::StreamGetTradeStatusSubscribe_1(StreamGetTradeStatusSubscribe)]
1388        #[case::StreamGetTradeStatusUnsubscribe_1(StreamGetTradeStatusUnsubscribe)]
1389        #[case::StreamPingSubscribe_1(StreamPingSubscribe)]
1390        fn serialize_deserialize_logic_struct<T: Serialize + Clone + Default + Debug + PartialEq + for<'de> Deserialize<'de>>(#[case] original: T) {
1391            let serialized_value = to_value(original.clone()).unwrap();
1392            assert!(serialized_value.is_null());
1393            let new_value = from_value(serialized_value).unwrap();
1394            assert_eq!(original, new_value);
1395        }
1396
1397        #[rstest]
1398        #[case::GetAllSymbolsResponse_1(GetAllSymbolsResponse::default())]
1399        #[case::GetCalendarResponse_1(GetCalendarResponse::default())]
1400        #[case::GetIbsHistoryResponse_1(GetIbsHistoryResponse::default())]
1401        #[case::GetNewsResponse_1(GetNewsResponse::default())]
1402        #[case::GetStepRulesResponse_1(GetStepRulesResponse::default())]
1403        #[case::GetTradeRecordsResponse_1(GetTradeRecordsResponse::default())]
1404        #[case::GetTradingHoursResponse_1(GetTradingHoursResponse::default())]
1405        fn deserialize_empty_array_of_records<T: Serialize + for<'de> Deserialize<'de> + PartialEq + Debug>(#[case] reference: T) {
1406            let response: T = from_str("[]").unwrap();
1407            let serialized_reference = to_string(&reference).unwrap();
1408            assert_eq!(response, reference);
1409            assert_eq!(&serialized_reference, "[]");
1410        }
1411
1412        #[rstest]
1413        #[case::GetAllSymbolsResponse_1(GetAllSymbolsResponse::default())]
1414        #[case::GetCalendarResponse_1(GetCalendarResponse::default())]
1415        #[case::GetIbsHistoryResponse_1(GetIbsHistoryResponse::default())]
1416        #[case::GetNewsResponse_1(GetNewsResponse::default())]
1417        #[case::GetStepRulesResponse_1(GetStepRulesResponse::default())]
1418        #[case::GetTradeRecordsResponse_1(GetTradeRecordsResponse::default())]
1419        #[case::GetTradingHoursResponse_1(GetTradingHoursResponse::default())]
1420        fn deserialize_array_of_records_with_one_record<T, I>(#[case] inp: T)
1421        where
1422            T: Serialize + for<'de> Deserialize<'de> + PartialEq + Debug + Deref<Target = Vec<I>>,
1423            I: Default + PartialEq + Debug + Serialize + for<'de> Deserialize<'de>
1424        {
1425            let ref_val = SymbolRecord::default();
1426            let source_data = format!("[{}]", to_string(&ref_val).unwrap());
1427            let response: GetAllSymbolsResponse = from_str(&source_data).unwrap();
1428
1429            assert_eq!(response.len(), 1);
1430            assert_eq!(response[0], ref_val);
1431        }
1432    }
1433}