use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default)]
pub struct MarketUpdate {
pub epic: String,
pub bid: Option<f64>,
pub offer: Option<f64>,
pub high: Option<f64>,
pub low: Option<f64>,
pub mid_open: Option<f64>,
pub change: Option<f64>,
pub change_pct: Option<f64>,
pub update_time: Option<String>,
pub market_delay: Option<bool>,
pub market_state: Option<String>,
}
pub(crate) const MARKET_FIELDS: &[&str] = &[
"BID",
"OFFER",
"HIGH",
"LOW",
"MID_OPEN",
"CHANGE",
"CHANGE_PCT",
"UPDATE_TIME",
"MARKET_DELAY",
"MARKET_STATE",
];
impl MarketUpdate {
pub fn from_raw(epic: &str, state: &[Option<String>]) -> Self {
let get = |i: usize| state.get(i).and_then(|v| v.as_deref());
Self {
epic: epic.to_owned(),
bid: get(0).and_then(|s| s.parse().ok()),
offer: get(1).and_then(|s| s.parse().ok()),
high: get(2).and_then(|s| s.parse().ok()),
low: get(3).and_then(|s| s.parse().ok()),
mid_open: get(4).and_then(|s| s.parse().ok()),
change: get(5).and_then(|s| s.parse().ok()),
change_pct: get(6).and_then(|s| s.parse().ok()),
update_time: get(7).map(str::to_owned),
market_delay: get(8).and_then(|s| match s {
"0" | "false" => Some(false),
"1" | "true" => Some(true),
_ => None,
}),
market_state: get(9).map(str::to_owned),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ChartTickUpdate {
pub epic: String,
pub bid: Option<f64>,
pub ofr: Option<f64>,
pub ltp: Option<f64>,
pub ltv: Option<f64>,
pub ttv: Option<f64>,
pub utm: Option<i64>,
pub day_open_mid: Option<f64>,
pub day_net_chg_mid: Option<f64>,
pub day_perc_chg_mid: Option<f64>,
pub day_high: Option<f64>,
pub day_low: Option<f64>,
}
pub(crate) const CHART_TICK_FIELDS: &[&str] = &[
"BID",
"OFR",
"LTP",
"LTV",
"TTV",
"UTM",
"DAY_OPEN_MID",
"DAY_NET_CHG_MID",
"DAY_PERC_CHG_MID",
"DAY_HIGH",
"DAY_LOW",
];
impl ChartTickUpdate {
pub fn from_raw(epic: &str, state: &[Option<String>]) -> Self {
let get = |i: usize| state.get(i).and_then(|v| v.as_deref());
Self {
epic: epic.to_owned(),
bid: get(0).and_then(|s| s.parse().ok()),
ofr: get(1).and_then(|s| s.parse().ok()),
ltp: get(2).and_then(|s| s.parse().ok()),
ltv: get(3).and_then(|s| s.parse().ok()),
ttv: get(4).and_then(|s| s.parse().ok()),
utm: get(5).and_then(|s| s.parse().ok()),
day_open_mid: get(6).and_then(|s| s.parse().ok()),
day_net_chg_mid: get(7).and_then(|s| s.parse().ok()),
day_perc_chg_mid: get(8).and_then(|s| s.parse().ok()),
day_high: get(9).and_then(|s| s.parse().ok()),
day_low: get(10).and_then(|s| s.parse().ok()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CandleScale {
OneMinute,
FiveMinute,
Hour,
}
impl CandleScale {
pub fn as_str(self) -> &'static str {
match self {
Self::OneMinute => "1MINUTE",
Self::FiveMinute => "5MINUTE",
Self::Hour => "HOUR",
}
}
}
impl std::fmt::Display for CandleScale {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Default)]
pub struct ChartCandleUpdate {
pub epic: String,
pub scale: Option<CandleScale>,
pub ofr_open: Option<f64>,
pub ofr_high: Option<f64>,
pub ofr_low: Option<f64>,
pub ofr_close: Option<f64>,
pub bid_open: Option<f64>,
pub bid_high: Option<f64>,
pub bid_low: Option<f64>,
pub bid_close: Option<f64>,
pub ltp_open: Option<f64>,
pub ltp_high: Option<f64>,
pub ltp_low: Option<f64>,
pub ltp_close: Option<f64>,
pub cons_end: Option<bool>,
pub cons_tick_count: Option<i64>,
pub utm: Option<i64>,
}
pub(crate) const CHART_CANDLE_FIELDS: &[&str] = &[
"OFR_OPEN",
"OFR_HIGH",
"OFR_LOW",
"OFR_CLOSE",
"BID_OPEN",
"BID_HIGH",
"BID_LOW",
"BID_CLOSE",
"LTP_OPEN",
"LTP_HIGH",
"LTP_LOW",
"LTP_CLOSE",
"CONS_END",
"CONS_TICK_COUNT",
"UTM",
];
impl ChartCandleUpdate {
pub fn from_raw(epic: &str, scale: CandleScale, state: &[Option<String>]) -> Self {
let get = |i: usize| state.get(i).and_then(|v| v.as_deref());
let pf = |i: usize| get(i).and_then(|s| s.parse::<f64>().ok());
let pi = |i: usize| get(i).and_then(|s| s.parse::<i64>().ok());
Self {
epic: epic.to_owned(),
scale: Some(scale),
ofr_open: pf(0),
ofr_high: pf(1),
ofr_low: pf(2),
ofr_close: pf(3),
bid_open: pf(4),
bid_high: pf(5),
bid_low: pf(6),
bid_close: pf(7),
ltp_open: pf(8),
ltp_high: pf(9),
ltp_low: pf(10),
ltp_close: pf(11),
cons_end: get(12).and_then(|s| match s {
"1" => Some(true),
"0" => Some(false),
_ => None,
}),
cons_tick_count: pi(13),
utm: pi(14),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct AccountUpdate {
pub account_id: String,
pub pnl: Option<f64>,
pub deposit: Option<f64>,
pub available_cash: Option<f64>,
pub funds: Option<f64>,
pub margin: Option<f64>,
pub margin_lr: Option<f64>,
pub margin_nlr: Option<f64>,
pub available_to_deal: Option<f64>,
pub equity: Option<f64>,
pub equity_used: Option<f64>,
}
pub(crate) const ACCOUNT_FIELDS: &[&str] = &[
"PNL",
"DEPOSIT",
"AVAILABLE_CASH",
"FUNDS",
"MARGIN",
"MARGIN_LR",
"MARGIN_NLR",
"AVAILABLE_TO_DEAL",
"EQUITY",
"EQUITY_USED",
];
impl AccountUpdate {
pub fn from_raw(account_id: &str, state: &[Option<String>]) -> Self {
let get = |i: usize| state.get(i).and_then(|v| v.as_deref());
let pf = |i: usize| get(i).and_then(|s| s.parse::<f64>().ok());
Self {
account_id: account_id.to_owned(),
pnl: pf(0),
deposit: pf(1),
available_cash: pf(2),
funds: pf(3),
margin: pf(4),
margin_lr: pf(5),
margin_nlr: pf(6),
available_to_deal: pf(7),
equity: pf(8),
equity_used: pf(9),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TradeConfirm {
pub deal_reference: Option<String>,
pub deal_id: Option<String>,
pub epic: Option<String>,
pub status: Option<String>,
pub deal_status: Option<String>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OpenPositionUpdate {
pub deal_id: Option<String>,
pub deal_status: Option<String>,
pub direction: Option<String>,
pub epic: Option<String>,
pub level: Option<f64>,
pub size: Option<f64>,
pub price: Option<f64>,
pub status: Option<String>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkingOrderUpdate {
pub deal_id: Option<String>,
pub deal_status: Option<String>,
pub epic: Option<String>,
pub level: Option<f64>,
pub status: Option<String>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone)]
pub struct TradeUpdate {
pub account_id: String,
pub confirms: Option<TradeConfirm>,
pub opu: Option<OpenPositionUpdate>,
pub wou: Option<WorkingOrderUpdate>,
}
pub(crate) const TRADE_FIELDS: &[&str] = &["CONFIRMS", "OPU", "WOU"];
impl TradeUpdate {
pub fn from_raw(account_id: &str, state: &[Option<String>]) -> Self {
let parse_str = |i: usize| state.get(i).and_then(|v| v.as_deref());
Self {
account_id: account_id.to_owned(),
confirms: parse_str(0).and_then(|s| serde_json::from_str(s).ok()),
opu: parse_str(1).and_then(|s| serde_json::from_str(s).ok()),
wou: parse_str(2).and_then(|s| serde_json::from_str(s).ok()),
}
}
}