use serde::{Deserialize, Serialize};
use super::macros::string_enum;
use super::{DateTime, DecimalNumber, GuaranteedStopLossOrderMode, InstrumentName, PriceValue};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Instrument {
#[serde(rename = "name", skip_serializing_if = "Option::is_none")]
pub name: Option<InstrumentName>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub r#type: Option<InstrumentType>,
#[serde(rename = "displayName", skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(rename = "pipLocation", skip_serializing_if = "Option::is_none")]
pub pip_location: Option<i64>,
#[serde(rename = "displayPrecision", skip_serializing_if = "Option::is_none")]
pub display_precision: Option<i64>,
#[serde(
rename = "tradeUnitsPrecision",
skip_serializing_if = "Option::is_none"
)]
pub trade_units_precision: Option<i64>,
#[serde(rename = "minimumTradeSize", skip_serializing_if = "Option::is_none")]
pub minimum_trade_size: Option<DecimalNumber>,
#[serde(
rename = "maximumTrailingStopDistance",
skip_serializing_if = "Option::is_none"
)]
pub maximum_trailing_stop_distance: Option<DecimalNumber>,
#[serde(
rename = "minimumTrailingStopDistance",
skip_serializing_if = "Option::is_none"
)]
pub minimum_trailing_stop_distance: Option<DecimalNumber>,
#[serde(
rename = "maximumPositionSize",
skip_serializing_if = "Option::is_none"
)]
pub maximum_position_size: Option<DecimalNumber>,
#[serde(rename = "maximumOrderUnits", skip_serializing_if = "Option::is_none")]
pub maximum_order_units: Option<DecimalNumber>,
#[serde(rename = "marginRate", skip_serializing_if = "Option::is_none")]
pub margin_rate: Option<DecimalNumber>,
#[serde(rename = "commission", skip_serializing_if = "Option::is_none")]
pub commission: Option<InstrumentCommission>,
#[serde(
rename = "guaranteedStopLossOrderMode",
skip_serializing_if = "Option::is_none"
)]
pub guaranteed_stop_loss_order_mode: Option<GuaranteedStopLossOrderMode>,
#[serde(
rename = "minimumGuaranteedStopLossDistance",
skip_serializing_if = "Option::is_none"
)]
pub minimum_guaranteed_stop_loss_distance: Option<DecimalNumber>,
#[serde(
rename = "guaranteedStopLossOrderExecutionPremium",
skip_serializing_if = "Option::is_none"
)]
pub guaranteed_stop_loss_order_execution_premium: Option<DecimalNumber>,
#[serde(
rename = "guaranteedStopLossOrderLevelRestriction",
skip_serializing_if = "Option::is_none"
)]
pub guaranteed_stop_loss_order_level_restriction:
Option<GuaranteedStopLossOrderLevelRestriction>,
#[serde(rename = "financing", skip_serializing_if = "Option::is_none")]
pub financing: Option<InstrumentFinancing>,
#[serde(rename = "tags", default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<Tag>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InstrumentCommission {
#[serde(rename = "commission", skip_serializing_if = "Option::is_none")]
pub commission: Option<DecimalNumber>,
#[serde(rename = "unitsTraded", skip_serializing_if = "Option::is_none")]
pub units_traded: Option<DecimalNumber>,
#[serde(rename = "minimumCommission", skip_serializing_if = "Option::is_none")]
pub minimum_commission: Option<DecimalNumber>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InstrumentFinancing {
#[serde(rename = "longRate", skip_serializing_if = "Option::is_none")]
pub long_rate: Option<DecimalNumber>,
#[serde(rename = "shortRate", skip_serializing_if = "Option::is_none")]
pub short_rate: Option<DecimalNumber>,
#[serde(
rename = "financingDaysOfWeek",
default,
skip_serializing_if = "Vec::is_empty"
)]
pub financing_days_of_week: Vec<FinancingDayOfWeek>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FinancingDayOfWeek {
#[serde(rename = "dayOfWeek", skip_serializing_if = "Option::is_none")]
pub day_of_week: Option<DayOfWeek>,
#[serde(rename = "daysCharged", skip_serializing_if = "Option::is_none")]
pub days_charged: Option<i64>,
}
string_enum! {
pub enum DayOfWeek {
Sunday => "SUNDAY",
Monday => "MONDAY",
Tuesday => "TUESDAY",
Wednesday => "WEDNESDAY",
Thursday => "THURSDAY",
Friday => "FRIDAY",
Saturday => "SATURDAY",
}
}
string_enum! {
pub enum InstrumentType {
Currency => "CURRENCY",
Cfd => "CFD",
Metal => "METAL",
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Tag {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
#[serde(rename = "name", skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct GuaranteedStopLossOrderLevelRestriction {
#[serde(rename = "volume", skip_serializing_if = "Option::is_none")]
pub volume: Option<DecimalNumber>,
#[serde(rename = "priceRange", skip_serializing_if = "Option::is_none")]
pub price_range: Option<DecimalNumber>,
}
string_enum! {
pub enum CandlestickGranularity {
S5 => "S5",
S10 => "S10",
S15 => "S15",
S30 => "S30",
M1 => "M1",
M2 => "M2",
M4 => "M4",
M5 => "M5",
M10 => "M10",
M15 => "M15",
M30 => "M30",
H1 => "H1",
H2 => "H2",
H3 => "H3",
H4 => "H4",
H6 => "H6",
H8 => "H8",
H12 => "H12",
D => "D",
W => "W",
M => "M",
}
}
string_enum! {
pub enum WeeklyAlignment {
Monday => "Monday",
Tuesday => "Tuesday",
Wednesday => "Wednesday",
Thursday => "Thursday",
Friday => "Friday",
Saturday => "Saturday",
Sunday => "Sunday",
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CandlestickData {
#[serde(rename = "o", skip_serializing_if = "Option::is_none")]
pub o: Option<PriceValue>,
#[serde(rename = "h", skip_serializing_if = "Option::is_none")]
pub h: Option<PriceValue>,
#[serde(rename = "l", skip_serializing_if = "Option::is_none")]
pub l: Option<PriceValue>,
#[serde(rename = "c", skip_serializing_if = "Option::is_none")]
pub c: Option<PriceValue>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Candlestick {
#[serde(rename = "time", skip_serializing_if = "Option::is_none")]
pub time: Option<DateTime>,
#[serde(rename = "bid", skip_serializing_if = "Option::is_none")]
pub bid: Option<CandlestickData>,
#[serde(rename = "ask", skip_serializing_if = "Option::is_none")]
pub ask: Option<CandlestickData>,
#[serde(rename = "mid", skip_serializing_if = "Option::is_none")]
pub mid: Option<CandlestickData>,
#[serde(rename = "volume", skip_serializing_if = "Option::is_none")]
pub volume: Option<i64>,
#[serde(rename = "complete", skip_serializing_if = "Option::is_none")]
pub complete: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InstrumentCandles {
#[serde(rename = "instrument", skip_serializing_if = "Option::is_none")]
pub instrument: Option<InstrumentName>,
#[serde(rename = "granularity", skip_serializing_if = "Option::is_none")]
pub granularity: Option<CandlestickGranularity>,
#[serde(rename = "candles", default, skip_serializing_if = "Vec::is_empty")]
pub candles: Vec<Candlestick>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct OrderBook {
#[serde(rename = "instrument", skip_serializing_if = "Option::is_none")]
pub instrument: Option<InstrumentName>,
#[serde(rename = "time", skip_serializing_if = "Option::is_none")]
pub time: Option<DateTime>,
#[serde(rename = "price", skip_serializing_if = "Option::is_none")]
pub price: Option<PriceValue>,
#[serde(rename = "bucketWidth", skip_serializing_if = "Option::is_none")]
pub bucket_width: Option<PriceValue>,
#[serde(rename = "buckets", default, skip_serializing_if = "Vec::is_empty")]
pub buckets: Vec<OrderBookBucket>,
#[serde(rename = "unixTime", skip_serializing_if = "Option::is_none")]
pub unix_time: Option<DateTime>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct OrderBookBucket {
#[serde(rename = "price", skip_serializing_if = "Option::is_none")]
pub price: Option<PriceValue>,
#[serde(rename = "longCountPercent", skip_serializing_if = "Option::is_none")]
pub long_count_percent: Option<DecimalNumber>,
#[serde(rename = "shortCountPercent", skip_serializing_if = "Option::is_none")]
pub short_count_percent: Option<DecimalNumber>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct PositionBook {
#[serde(rename = "instrument", skip_serializing_if = "Option::is_none")]
pub instrument: Option<InstrumentName>,
#[serde(rename = "time", skip_serializing_if = "Option::is_none")]
pub time: Option<DateTime>,
#[serde(rename = "price", skip_serializing_if = "Option::is_none")]
pub price: Option<PriceValue>,
#[serde(rename = "bucketWidth", skip_serializing_if = "Option::is_none")]
pub bucket_width: Option<PriceValue>,
#[serde(rename = "buckets", default, skip_serializing_if = "Vec::is_empty")]
pub buckets: Vec<PositionBookBucket>,
#[serde(rename = "unixTime", skip_serializing_if = "Option::is_none")]
pub unix_time: Option<DateTime>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct PositionBookBucket {
#[serde(rename = "price", skip_serializing_if = "Option::is_none")]
pub price: Option<PriceValue>,
#[serde(rename = "longCountPercent", skip_serializing_if = "Option::is_none")]
pub long_count_percent: Option<DecimalNumber>,
#[serde(rename = "shortCountPercent", skip_serializing_if = "Option::is_none")]
pub short_count_percent: Option<DecimalNumber>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct PricingComponent {
pub bid: bool,
pub mid: bool,
pub ask: bool,
}
impl PricingComponent {
pub const BID: PricingComponent = PricingComponent {
bid: true,
mid: false,
ask: false,
};
pub const MID: PricingComponent = PricingComponent {
bid: false,
mid: true,
ask: false,
};
pub const ASK: PricingComponent = PricingComponent {
bid: false,
mid: false,
ask: true,
};
pub fn with_bid(mut self) -> Self {
self.bid = true;
self
}
pub fn with_mid(mut self) -> Self {
self.mid = true;
self
}
pub fn with_ask(mut self) -> Self {
self.ask = true;
self
}
}
impl std::fmt::Display for PricingComponent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.bid {
f.write_str("B")?;
}
if self.mid {
f.write_str("M")?;
}
if self.ask {
f.write_str("A")?;
}
Ok(())
}
}
impl std::str::FromStr for PricingComponent {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut component = PricingComponent::default();
for c in s.chars() {
match c.to_ascii_uppercase() {
'B' => component.bid = true,
'M' => component.mid = true,
'A' => component.ask = true,
other => return Err(format!("invalid pricing component character: {other:?}")),
}
}
Ok(component)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CandleSpecification {
pub instrument: InstrumentName,
pub granularity: CandlestickGranularity,
pub price: Option<PricingComponent>,
}
impl CandleSpecification {
pub fn new(instrument: impl Into<InstrumentName>, granularity: CandlestickGranularity) -> Self {
CandleSpecification {
instrument: instrument.into(),
granularity,
price: None,
}
}
pub fn price(mut self, price: PricingComponent) -> Self {
self.price = Some(price);
self
}
}
impl std::fmt::Display for CandleSpecification {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.instrument, self.granularity)?;
if let Some(price) = &self.price {
write!(f, ":{price}")?;
}
Ok(())
}
}