1use serde::{Deserialize, Serialize};
4
5use super::macros::string_enum;
6use super::{DateTime, DecimalNumber, GuaranteedStopLossOrderMode, InstrumentName, PriceValue};
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[non_exhaustive]
11pub struct Instrument {
12 #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
14 pub name: Option<InstrumentName>,
15
16 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
18 pub r#type: Option<InstrumentType>,
19
20 #[serde(rename = "displayName", skip_serializing_if = "Option::is_none")]
22 pub display_name: Option<String>,
23
24 #[serde(rename = "pipLocation", skip_serializing_if = "Option::is_none")]
29 pub pip_location: Option<i64>,
30
31 #[serde(rename = "displayPrecision", skip_serializing_if = "Option::is_none")]
35 pub display_precision: Option<i64>,
36
37 #[serde(
40 rename = "tradeUnitsPrecision",
41 skip_serializing_if = "Option::is_none"
42 )]
43 pub trade_units_precision: Option<i64>,
44
45 #[serde(rename = "minimumTradeSize", skip_serializing_if = "Option::is_none")]
47 pub minimum_trade_size: Option<DecimalNumber>,
48
49 #[serde(
52 rename = "maximumTrailingStopDistance",
53 skip_serializing_if = "Option::is_none"
54 )]
55 pub maximum_trailing_stop_distance: Option<DecimalNumber>,
56
57 #[serde(
60 rename = "minimumTrailingStopDistance",
61 skip_serializing_if = "Option::is_none"
62 )]
63 pub minimum_trailing_stop_distance: Option<DecimalNumber>,
64
65 #[serde(
68 rename = "maximumPositionSize",
69 skip_serializing_if = "Option::is_none"
70 )]
71 pub maximum_position_size: Option<DecimalNumber>,
72
73 #[serde(rename = "maximumOrderUnits", skip_serializing_if = "Option::is_none")]
76 pub maximum_order_units: Option<DecimalNumber>,
77
78 #[serde(rename = "marginRate", skip_serializing_if = "Option::is_none")]
80 pub margin_rate: Option<DecimalNumber>,
81
82 #[serde(rename = "commission", skip_serializing_if = "Option::is_none")]
84 pub commission: Option<InstrumentCommission>,
85
86 #[serde(
88 rename = "guaranteedStopLossOrderMode",
89 skip_serializing_if = "Option::is_none"
90 )]
91 pub guaranteed_stop_loss_order_mode: Option<GuaranteedStopLossOrderMode>,
92
93 #[serde(
97 rename = "minimumGuaranteedStopLossDistance",
98 skip_serializing_if = "Option::is_none"
99 )]
100 pub minimum_guaranteed_stop_loss_distance: Option<DecimalNumber>,
101
102 #[serde(
106 rename = "guaranteedStopLossOrderExecutionPremium",
107 skip_serializing_if = "Option::is_none"
108 )]
109 pub guaranteed_stop_loss_order_execution_premium: Option<DecimalNumber>,
110
111 #[serde(
113 rename = "guaranteedStopLossOrderLevelRestriction",
114 skip_serializing_if = "Option::is_none"
115 )]
116 pub guaranteed_stop_loss_order_level_restriction:
117 Option<GuaranteedStopLossOrderLevelRestriction>,
118
119 #[serde(rename = "financing", skip_serializing_if = "Option::is_none")]
121 pub financing: Option<InstrumentFinancing>,
122
123 #[serde(rename = "tags", default, skip_serializing_if = "Vec::is_empty")]
125 pub tags: Vec<Tag>,
126}
127
128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
130#[non_exhaustive]
131pub struct InstrumentCommission {
132 #[serde(rename = "commission", skip_serializing_if = "Option::is_none")]
135 pub commission: Option<DecimalNumber>,
136
137 #[serde(rename = "unitsTraded", skip_serializing_if = "Option::is_none")]
139 pub units_traded: Option<DecimalNumber>,
140
141 #[serde(rename = "minimumCommission", skip_serializing_if = "Option::is_none")]
144 pub minimum_commission: Option<DecimalNumber>,
145}
146
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
149#[non_exhaustive]
150pub struct InstrumentFinancing {
151 #[serde(rename = "longRate", skip_serializing_if = "Option::is_none")]
153 pub long_rate: Option<DecimalNumber>,
154
155 #[serde(rename = "shortRate", skip_serializing_if = "Option::is_none")]
157 pub short_rate: Option<DecimalNumber>,
158
159 #[serde(
163 rename = "financingDaysOfWeek",
164 default,
165 skip_serializing_if = "Vec::is_empty"
166 )]
167 pub financing_days_of_week: Vec<FinancingDayOfWeek>,
168}
169
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173#[non_exhaustive]
174pub struct FinancingDayOfWeek {
175 #[serde(rename = "dayOfWeek", skip_serializing_if = "Option::is_none")]
177 pub day_of_week: Option<DayOfWeek>,
178
179 #[serde(rename = "daysCharged", skip_serializing_if = "Option::is_none")]
181 pub days_charged: Option<i64>,
182}
183
184string_enum! {
185 pub enum DayOfWeek {
187 Sunday => "SUNDAY",
188 Monday => "MONDAY",
189 Tuesday => "TUESDAY",
190 Wednesday => "WEDNESDAY",
191 Thursday => "THURSDAY",
192 Friday => "FRIDAY",
193 Saturday => "SATURDAY",
194 }
195}
196
197string_enum! {
198 pub enum InstrumentType {
200 Currency => "CURRENCY",
201 Cfd => "CFD",
202 Metal => "METAL",
203 }
204}
205
206#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
208#[non_exhaustive]
209pub struct Tag {
210 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
212 pub r#type: Option<String>,
213
214 #[serde(rename = "name", skip_serializing_if = "Option::is_none")]
216 pub name: Option<String>,
217}
218
219#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
223#[non_exhaustive]
224pub struct GuaranteedStopLossOrderLevelRestriction {
225 #[serde(rename = "volume", skip_serializing_if = "Option::is_none")]
230 pub volume: Option<DecimalNumber>,
231
232 #[serde(rename = "priceRange", skip_serializing_if = "Option::is_none")]
234 pub price_range: Option<DecimalNumber>,
235}
236
237string_enum! {
238 pub enum CandlestickGranularity {
240 S5 => "S5",
241 S10 => "S10",
242 S15 => "S15",
243 S30 => "S30",
244 M1 => "M1",
245 M2 => "M2",
246 M4 => "M4",
247 M5 => "M5",
248 M10 => "M10",
249 M15 => "M15",
250 M30 => "M30",
251 H1 => "H1",
252 H2 => "H2",
253 H3 => "H3",
254 H4 => "H4",
255 H6 => "H6",
256 H8 => "H8",
257 H12 => "H12",
258 D => "D",
259 W => "W",
260 M => "M",
261 }
262}
263
264string_enum! {
265 pub enum WeeklyAlignment {
268 Monday => "Monday",
269 Tuesday => "Tuesday",
270 Wednesday => "Wednesday",
271 Thursday => "Thursday",
272 Friday => "Friday",
273 Saturday => "Saturday",
274 Sunday => "Sunday",
275 }
276}
277
278#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
280#[non_exhaustive]
281pub struct CandlestickData {
282 #[serde(rename = "o", skip_serializing_if = "Option::is_none")]
284 pub o: Option<PriceValue>,
285
286 #[serde(rename = "h", skip_serializing_if = "Option::is_none")]
288 pub h: Option<PriceValue>,
289
290 #[serde(rename = "l", skip_serializing_if = "Option::is_none")]
292 pub l: Option<PriceValue>,
293
294 #[serde(rename = "c", skip_serializing_if = "Option::is_none")]
297 pub c: Option<PriceValue>,
298}
299
300#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
302#[non_exhaustive]
303pub struct Candlestick {
304 #[serde(rename = "time", skip_serializing_if = "Option::is_none")]
306 pub time: Option<DateTime>,
307
308 #[serde(rename = "bid", skip_serializing_if = "Option::is_none")]
310 pub bid: Option<CandlestickData>,
311
312 #[serde(rename = "ask", skip_serializing_if = "Option::is_none")]
314 pub ask: Option<CandlestickData>,
315
316 #[serde(rename = "mid", skip_serializing_if = "Option::is_none")]
318 pub mid: Option<CandlestickData>,
319
320 #[serde(rename = "volume", skip_serializing_if = "Option::is_none")]
323 pub volume: Option<i64>,
324
325 #[serde(rename = "complete", skip_serializing_if = "Option::is_none")]
328 pub complete: Option<bool>,
329}
330
331#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
333#[non_exhaustive]
334pub struct InstrumentCandles {
335 #[serde(rename = "instrument", skip_serializing_if = "Option::is_none")]
337 pub instrument: Option<InstrumentName>,
338
339 #[serde(rename = "granularity", skip_serializing_if = "Option::is_none")]
341 pub granularity: Option<CandlestickGranularity>,
342
343 #[serde(rename = "candles", default, skip_serializing_if = "Vec::is_empty")]
345 pub candles: Vec<Candlestick>,
346}
347
348#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
350#[non_exhaustive]
351pub struct OrderBook {
352 #[serde(rename = "instrument", skip_serializing_if = "Option::is_none")]
354 pub instrument: Option<InstrumentName>,
355
356 #[serde(rename = "time", skip_serializing_if = "Option::is_none")]
358 pub time: Option<DateTime>,
359
360 #[serde(rename = "price", skip_serializing_if = "Option::is_none")]
363 pub price: Option<PriceValue>,
364
365 #[serde(rename = "bucketWidth", skip_serializing_if = "Option::is_none")]
368 pub bucket_width: Option<PriceValue>,
369
370 #[serde(rename = "buckets", default, skip_serializing_if = "Vec::is_empty")]
374 pub buckets: Vec<OrderBookBucket>,
375
376 #[serde(rename = "unixTime", skip_serializing_if = "Option::is_none")]
380 pub unix_time: Option<DateTime>,
381}
382
383#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
385#[non_exhaustive]
386pub struct OrderBookBucket {
387 #[serde(rename = "price", skip_serializing_if = "Option::is_none")]
390 pub price: Option<PriceValue>,
391
392 #[serde(rename = "longCountPercent", skip_serializing_if = "Option::is_none")]
395 pub long_count_percent: Option<DecimalNumber>,
396
397 #[serde(rename = "shortCountPercent", skip_serializing_if = "Option::is_none")]
400 pub short_count_percent: Option<DecimalNumber>,
401}
402
403#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
405#[non_exhaustive]
406pub struct PositionBook {
407 #[serde(rename = "instrument", skip_serializing_if = "Option::is_none")]
409 pub instrument: Option<InstrumentName>,
410
411 #[serde(rename = "time", skip_serializing_if = "Option::is_none")]
413 pub time: Option<DateTime>,
414
415 #[serde(rename = "price", skip_serializing_if = "Option::is_none")]
418 pub price: Option<PriceValue>,
419
420 #[serde(rename = "bucketWidth", skip_serializing_if = "Option::is_none")]
423 pub bucket_width: Option<PriceValue>,
424
425 #[serde(rename = "buckets", default, skip_serializing_if = "Vec::is_empty")]
429 pub buckets: Vec<PositionBookBucket>,
430
431 #[serde(rename = "unixTime", skip_serializing_if = "Option::is_none")]
435 pub unix_time: Option<DateTime>,
436}
437
438#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
440#[non_exhaustive]
441pub struct PositionBookBucket {
442 #[serde(rename = "price", skip_serializing_if = "Option::is_none")]
446 pub price: Option<PriceValue>,
447
448 #[serde(rename = "longCountPercent", skip_serializing_if = "Option::is_none")]
451 pub long_count_percent: Option<DecimalNumber>,
452
453 #[serde(rename = "shortCountPercent", skip_serializing_if = "Option::is_none")]
456 pub short_count_percent: Option<DecimalNumber>,
457}
458
459#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
469pub struct PricingComponent {
470 pub bid: bool,
472 pub mid: bool,
474 pub ask: bool,
476}
477
478impl PricingComponent {
479 pub const BID: PricingComponent = PricingComponent {
481 bid: true,
482 mid: false,
483 ask: false,
484 };
485 pub const MID: PricingComponent = PricingComponent {
487 bid: false,
488 mid: true,
489 ask: false,
490 };
491 pub const ASK: PricingComponent = PricingComponent {
493 bid: false,
494 mid: false,
495 ask: true,
496 };
497
498 pub fn with_bid(mut self) -> Self {
500 self.bid = true;
501 self
502 }
503
504 pub fn with_mid(mut self) -> Self {
506 self.mid = true;
507 self
508 }
509
510 pub fn with_ask(mut self) -> Self {
512 self.ask = true;
513 self
514 }
515}
516
517impl std::fmt::Display for PricingComponent {
518 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
519 if self.bid {
520 f.write_str("B")?;
521 }
522 if self.mid {
523 f.write_str("M")?;
524 }
525 if self.ask {
526 f.write_str("A")?;
527 }
528 Ok(())
529 }
530}
531
532impl std::str::FromStr for PricingComponent {
533 type Err = String;
534
535 fn from_str(s: &str) -> Result<Self, Self::Err> {
536 let mut component = PricingComponent::default();
537 for c in s.chars() {
538 match c.to_ascii_uppercase() {
539 'B' => component.bid = true,
540 'M' => component.mid = true,
541 'A' => component.ask = true,
542 other => return Err(format!("invalid pricing component character: {other:?}")),
543 }
544 }
545 Ok(component)
546 }
547}
548
549#[derive(Debug, Clone, PartialEq, Eq, Hash)]
552pub struct CandleSpecification {
553 pub instrument: InstrumentName,
555 pub granularity: CandlestickGranularity,
557 pub price: Option<PricingComponent>,
559}
560
561impl CandleSpecification {
562 pub fn new(instrument: impl Into<InstrumentName>, granularity: CandlestickGranularity) -> Self {
564 CandleSpecification {
565 instrument: instrument.into(),
566 granularity,
567 price: None,
568 }
569 }
570
571 pub fn price(mut self, price: PricingComponent) -> Self {
573 self.price = Some(price);
574 self
575 }
576}
577
578impl std::fmt::Display for CandleSpecification {
579 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
580 write!(f, "{}:{}", self.instrument, self.granularity)?;
581 if let Some(price) = &self.price {
582 write!(f, ":{price}")?;
583 }
584 Ok(())
585 }
586}