1use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Default)]
24pub struct MarketUpdate {
25 pub epic: String,
27 pub bid: Option<f64>,
29 pub offer: Option<f64>,
31 pub high: Option<f64>,
33 pub low: Option<f64>,
35 pub mid_open: Option<f64>,
37 pub change: Option<f64>,
39 pub change_pct: Option<f64>,
41 pub update_time: Option<String>,
43 pub market_delay: Option<bool>,
45 pub market_state: Option<String>,
47}
48
49pub(crate) const MARKET_FIELDS: &[&str] = &[
51 "BID",
52 "OFFER",
53 "HIGH",
54 "LOW",
55 "MID_OPEN",
56 "CHANGE",
57 "CHANGE_PCT",
58 "UPDATE_TIME",
59 "MARKET_DELAY",
60 "MARKET_STATE",
61];
62
63impl MarketUpdate {
64 pub fn from_raw(epic: &str, state: &[Option<String>]) -> Self {
66 let get = |i: usize| state.get(i).and_then(|v| v.as_deref());
67 Self {
68 epic: epic.to_owned(),
69 bid: get(0).and_then(|s| s.parse().ok()),
70 offer: get(1).and_then(|s| s.parse().ok()),
71 high: get(2).and_then(|s| s.parse().ok()),
72 low: get(3).and_then(|s| s.parse().ok()),
73 mid_open: get(4).and_then(|s| s.parse().ok()),
74 change: get(5).and_then(|s| s.parse().ok()),
75 change_pct: get(6).and_then(|s| s.parse().ok()),
76 update_time: get(7).map(str::to_owned),
77 market_delay: get(8).and_then(|s| match s {
78 "0" | "false" => Some(false),
79 "1" | "true" => Some(true),
80 _ => None,
81 }),
82 market_state: get(9).map(str::to_owned),
83 }
84 }
85}
86
87#[derive(Debug, Clone, Default)]
93pub struct ChartTickUpdate {
94 pub epic: String,
96 pub bid: Option<f64>,
98 pub ofr: Option<f64>,
100 pub ltp: Option<f64>,
102 pub ltv: Option<f64>,
104 pub ttv: Option<f64>,
106 pub utm: Option<i64>,
108 pub day_open_mid: Option<f64>,
110 pub day_net_chg_mid: Option<f64>,
112 pub day_perc_chg_mid: Option<f64>,
114 pub day_high: Option<f64>,
116 pub day_low: Option<f64>,
118}
119
120pub(crate) const CHART_TICK_FIELDS: &[&str] = &[
122 "BID",
123 "OFR",
124 "LTP",
125 "LTV",
126 "TTV",
127 "UTM",
128 "DAY_OPEN_MID",
129 "DAY_NET_CHG_MID",
130 "DAY_PERC_CHG_MID",
131 "DAY_HIGH",
132 "DAY_LOW",
133];
134
135impl ChartTickUpdate {
136 pub fn from_raw(epic: &str, state: &[Option<String>]) -> Self {
137 let get = |i: usize| state.get(i).and_then(|v| v.as_deref());
138 Self {
139 epic: epic.to_owned(),
140 bid: get(0).and_then(|s| s.parse().ok()),
141 ofr: get(1).and_then(|s| s.parse().ok()),
142 ltp: get(2).and_then(|s| s.parse().ok()),
143 ltv: get(3).and_then(|s| s.parse().ok()),
144 ttv: get(4).and_then(|s| s.parse().ok()),
145 utm: get(5).and_then(|s| s.parse().ok()),
146 day_open_mid: get(6).and_then(|s| s.parse().ok()),
147 day_net_chg_mid: get(7).and_then(|s| s.parse().ok()),
148 day_perc_chg_mid: get(8).and_then(|s| s.parse().ok()),
149 day_high: get(9).and_then(|s| s.parse().ok()),
150 day_low: get(10).and_then(|s| s.parse().ok()),
151 }
152 }
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum CandleScale {
162 OneMinute,
164 FiveMinute,
166 Hour,
168}
169
170impl CandleScale {
171 pub fn as_str(self) -> &'static str {
173 match self {
174 Self::OneMinute => "1MINUTE",
175 Self::FiveMinute => "5MINUTE",
176 Self::Hour => "HOUR",
177 }
178 }
179}
180
181impl std::fmt::Display for CandleScale {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 f.write_str(self.as_str())
184 }
185}
186
187#[derive(Debug, Clone, Default)]
189pub struct ChartCandleUpdate {
190 pub epic: String,
192 pub scale: Option<CandleScale>,
194 pub ofr_open: Option<f64>,
196 pub ofr_high: Option<f64>,
198 pub ofr_low: Option<f64>,
200 pub ofr_close: Option<f64>,
202 pub bid_open: Option<f64>,
204 pub bid_high: Option<f64>,
206 pub bid_low: Option<f64>,
208 pub bid_close: Option<f64>,
210 pub ltp_open: Option<f64>,
212 pub ltp_high: Option<f64>,
214 pub ltp_low: Option<f64>,
216 pub ltp_close: Option<f64>,
218 pub cons_end: Option<bool>,
220 pub cons_tick_count: Option<i64>,
222 pub utm: Option<i64>,
224}
225
226pub(crate) const CHART_CANDLE_FIELDS: &[&str] = &[
228 "OFR_OPEN",
229 "OFR_HIGH",
230 "OFR_LOW",
231 "OFR_CLOSE",
232 "BID_OPEN",
233 "BID_HIGH",
234 "BID_LOW",
235 "BID_CLOSE",
236 "LTP_OPEN",
237 "LTP_HIGH",
238 "LTP_LOW",
239 "LTP_CLOSE",
240 "CONS_END",
241 "CONS_TICK_COUNT",
242 "UTM",
243];
244
245impl ChartCandleUpdate {
246 pub fn from_raw(epic: &str, scale: CandleScale, state: &[Option<String>]) -> Self {
247 let get = |i: usize| state.get(i).and_then(|v| v.as_deref());
248 let pf = |i: usize| get(i).and_then(|s| s.parse::<f64>().ok());
249 let pi = |i: usize| get(i).and_then(|s| s.parse::<i64>().ok());
250 Self {
251 epic: epic.to_owned(),
252 scale: Some(scale),
253 ofr_open: pf(0),
254 ofr_high: pf(1),
255 ofr_low: pf(2),
256 ofr_close: pf(3),
257 bid_open: pf(4),
258 bid_high: pf(5),
259 bid_low: pf(6),
260 bid_close: pf(7),
261 ltp_open: pf(8),
262 ltp_high: pf(9),
263 ltp_low: pf(10),
264 ltp_close: pf(11),
265 cons_end: get(12).and_then(|s| match s {
266 "1" => Some(true),
267 "0" => Some(false),
268 _ => None,
269 }),
270 cons_tick_count: pi(13),
271 utm: pi(14),
272 }
273 }
274}
275
276#[derive(Debug, Clone, Default)]
282pub struct AccountUpdate {
283 pub account_id: String,
285 pub pnl: Option<f64>,
287 pub deposit: Option<f64>,
289 pub available_cash: Option<f64>,
291 pub funds: Option<f64>,
293 pub margin: Option<f64>,
295 pub margin_lr: Option<f64>,
297 pub margin_nlr: Option<f64>,
299 pub available_to_deal: Option<f64>,
301 pub equity: Option<f64>,
303 pub equity_used: Option<f64>,
305}
306
307pub(crate) const ACCOUNT_FIELDS: &[&str] = &[
309 "PNL",
310 "DEPOSIT",
311 "AVAILABLE_CASH",
312 "FUNDS",
313 "MARGIN",
314 "MARGIN_LR",
315 "MARGIN_NLR",
316 "AVAILABLE_TO_DEAL",
317 "EQUITY",
318 "EQUITY_USED",
319];
320
321impl AccountUpdate {
322 pub fn from_raw(account_id: &str, state: &[Option<String>]) -> Self {
323 let get = |i: usize| state.get(i).and_then(|v| v.as_deref());
324 let pf = |i: usize| get(i).and_then(|s| s.parse::<f64>().ok());
325 Self {
326 account_id: account_id.to_owned(),
327 pnl: pf(0),
328 deposit: pf(1),
329 available_cash: pf(2),
330 funds: pf(3),
331 margin: pf(4),
332 margin_lr: pf(5),
333 margin_nlr: pf(6),
334 available_to_deal: pf(7),
335 equity: pf(8),
336 equity_used: pf(9),
337 }
338 }
339}
340
341#[derive(Debug, Clone, Deserialize, Serialize)]
347#[serde(rename_all = "camelCase")]
348pub struct TradeConfirm {
349 pub deal_reference: Option<String>,
351 pub deal_id: Option<String>,
353 pub epic: Option<String>,
355 pub status: Option<String>,
357 pub deal_status: Option<String>,
359 #[serde(flatten)]
361 pub extra: serde_json::Map<String, serde_json::Value>,
362}
363
364#[derive(Debug, Clone, Deserialize, Serialize)]
366#[serde(rename_all = "camelCase")]
367pub struct OpenPositionUpdate {
368 pub deal_id: Option<String>,
370 pub deal_status: Option<String>,
372 pub direction: Option<String>,
374 pub epic: Option<String>,
376 pub level: Option<f64>,
378 pub size: Option<f64>,
380 pub price: Option<f64>,
382 pub status: Option<String>,
384 #[serde(flatten)]
386 pub extra: serde_json::Map<String, serde_json::Value>,
387}
388
389#[derive(Debug, Clone, Deserialize, Serialize)]
391#[serde(rename_all = "camelCase")]
392pub struct WorkingOrderUpdate {
393 pub deal_id: Option<String>,
395 pub deal_status: Option<String>,
397 pub epic: Option<String>,
399 pub level: Option<f64>,
401 pub status: Option<String>,
403 #[serde(flatten)]
405 pub extra: serde_json::Map<String, serde_json::Value>,
406}
407
408#[derive(Debug, Clone)]
413pub struct TradeUpdate {
414 pub account_id: String,
416 pub confirms: Option<TradeConfirm>,
418 pub opu: Option<OpenPositionUpdate>,
420 pub wou: Option<WorkingOrderUpdate>,
422}
423
424pub(crate) const TRADE_FIELDS: &[&str] = &["CONFIRMS", "OPU", "WOU"];
426
427impl TradeUpdate {
428 pub fn from_raw(account_id: &str, state: &[Option<String>]) -> Self {
429 let parse_str = |i: usize| state.get(i).and_then(|v| v.as_deref());
430 Self {
431 account_id: account_id.to_owned(),
432 confirms: parse_str(0).and_then(|s| serde_json::from_str(s).ok()),
433 opu: parse_str(1).and_then(|s| serde_json::from_str(s).ok()),
434 wou: parse_str(2).and_then(|s| serde_json::from_str(s).ok()),
435 }
436 }
437}