use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::error::Error;
macro_rules! string_newtype {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name(pub String);
impl $name {
pub fn new(s: impl Into<String>) -> Self { Self(s.into()) }
pub fn as_str(&self) -> &str { &self.0 }
pub fn into_inner(self) -> String { self.0 }
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for $name {
fn from(s: String) -> Self { Self(s) }
}
impl From<&str> for $name {
fn from(s: &str) -> Self { Self(s.to_owned()) }
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str { &self.0 }
}
};
}
string_newtype! {
Epic
}
string_newtype! {
DealId
}
string_newtype! {
DealReference
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Currency(pub String);
impl Currency {
pub fn new(code: impl Into<String>) -> Self {
Self(code.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for Currency {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for Currency {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for Currency {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl AsRef<str> for Currency {
fn as_ref(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum Direction {
Buy,
Sell,
}
impl Direction {
pub fn opposite(self) -> Self {
match self {
Self::Buy => Self::Sell,
Self::Sell => Self::Buy,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum OrderType {
Limit,
Market,
Quote,
Stop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TimeInForce {
GoodTillCancelled,
GoodTillDate,
ExecuteAndEliminate,
FillOrKill,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum InstrumentType {
Currencies,
Shares,
Indices,
Commodities,
Options,
Bonds,
Rates,
Sectors,
Funds,
SprintMarkets,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum MarketStatus {
Tradeable,
EditsOnly,
Closed,
Offline,
OnAuction,
OnAuctionNoEdits,
Suspended,
#[serde(other)]
Unknown,
}
fn default_market_status() -> MarketStatus {
MarketStatus::Unknown
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MarketSnapshot {
#[serde(default)]
pub epic: Option<Epic>,
#[serde(default)]
pub instrument_name: Option<String>,
#[serde(default)]
pub expiry: Option<String>,
#[serde(default)]
pub instrument_type: Option<String>,
#[serde(default)]
pub lot_size: Option<f64>,
pub bid: Option<f64>,
pub offer: Option<f64>,
pub high: Option<f64>,
pub low: Option<f64>,
pub percentage_change: Option<f64>,
pub net_change: Option<f64>,
pub update_time: Option<String>,
pub update_time_utc: Option<String>,
#[serde(default = "default_market_status")]
pub market_status: MarketStatus,
pub delay_time: Option<i32>,
pub binary_odds: Option<f64>,
pub decimal_places_factor: Option<i32>,
pub scaling_factor: Option<i32>,
pub controlled_risk_extra_spread: Option<f64>,
}
impl FromStr for Direction {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s.to_ascii_uppercase().as_str() {
"BUY" => Ok(Self::Buy),
"SELL" => Ok(Self::Sell),
other => Err(Error::InvalidInput(format!("unknown direction '{other}'"))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn direction_serialises_uppercase() {
let s = serde_json::to_string(&Direction::Buy).unwrap();
assert_eq!(s, "\"BUY\"");
let d: Direction = serde_json::from_str("\"SELL\"").unwrap();
assert_eq!(d, Direction::Sell);
}
#[test]
fn epic_round_trips() {
let e: Epic = serde_json::from_str("\"CS.D.GBPUSD.TODAY.IP\"").unwrap();
assert_eq!(e.as_str(), "CS.D.GBPUSD.TODAY.IP");
assert_eq!(
serde_json::to_string(&e).unwrap(),
"\"CS.D.GBPUSD.TODAY.IP\""
);
}
}