use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use crate::models::common::{
Currency, DealId, Direction, Epic, MarketSnapshot, OrderType, TimeInForce,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkingOrderV1 {
#[serde(flatten)]
pub order_data: WorkingOrderDataV1,
pub market: MarketSnapshot,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkingOrderDataV1 {
pub created_date: Option<String>,
pub created_date_utc: Option<String>,
pub currency_code: Option<String>,
pub deal_id: DealId,
pub direction: Direction,
pub dma: Option<bool>,
pub epic: Epic,
pub good_till: Option<String>,
pub controlled_risk: Option<bool>,
pub order_level: f64,
pub order_size: f64,
pub order_type: OrderType,
pub contingent_limit: Option<f64>,
pub contingent_stop: Option<f64>,
pub trailing_stop_distance: Option<f64>,
pub trailing_stop_increment: Option<f64>,
pub trailing_trigger_distance: Option<f64>,
pub trailing_trigger_increment: Option<f64>,
pub request_type: Option<String>,
pub time_in_force: Option<TimeInForce>,
pub limit_distance: Option<f64>,
pub stop_distance: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkingOrderV2 {
#[serde(flatten)]
pub order_data: WorkingOrderDataV2,
pub market: MarketSnapshot,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkingOrderDataV2 {
pub created_date: Option<String>,
pub created_date_utc: Option<String>,
pub currency_code: Option<Currency>,
pub deal_id: DealId,
pub direction: Direction,
pub dma: Option<bool>,
pub epic: Epic,
pub good_till_date: Option<String>,
pub good_till_date_iso: Option<String>,
pub guaranteed_stop: bool,
pub limit_distance: Option<f64>,
pub limit_level: Option<f64>,
pub order_level: f64,
pub order_size: f64,
pub order_type: OrderType,
pub stop_distance: Option<f64>,
pub stop_level: Option<f64>,
pub time_in_force: TimeInForce,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct WorkingOrderEntryV1Raw {
pub working_order_data: WorkingOrderDataV1,
pub market_data: MarketSnapshot,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct WorkingOrderEntryV2Raw {
pub working_order_data: WorkingOrderDataV2,
pub market_data: MarketSnapshot,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateWorkingOrderRequest {
#[serde(
serialize_with = "serialize_good_till_date",
deserialize_with = "deserialize_good_till_date_opt"
)]
pub good_till_date: Option<NaiveDateTime>,
pub level: f64,
pub limit_distance: Option<f64>,
pub limit_level: Option<f64>,
pub stop_distance: Option<f64>,
pub stop_level: Option<f64>,
pub guaranteed_stop: bool,
pub time_in_force: TimeInForce,
#[serde(rename = "type")]
pub order_type: OrderType,
}
#[allow(clippy::ref_option)]
fn serialize_good_till_date<S>(dt: &Option<NaiveDateTime>, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match dt {
Some(dt) => s.serialize_str(&crate::time::format(*dt, crate::time::ApiVersion::V2)),
None => s.serialize_none(),
}
}
fn deserialize_good_till_date_opt<'de, D>(d: D) -> Result<Option<NaiveDateTime>, D::Error>
where
D: serde::Deserializer<'de>,
{
let opt: Option<String> = Option::deserialize(d)?;
match opt {
None => Ok(None),
Some(s) => crate::time::parse(&s, crate::time::ApiVersion::V2)
.map(Some)
.map_err(serde::de::Error::custom),
}
}
#[cfg(feature = "polars")]
impl crate::dataframe::IntoDataFrame for Vec<WorkingOrderV2> {
fn to_dataframe(&self) -> crate::Result<polars::prelude::DataFrame> {
use polars::prelude::*;
let deal_id: Vec<&str> = self.iter().map(|w| w.order_data.deal_id.as_str()).collect();
let epic: Vec<&str> = self.iter().map(|w| w.order_data.epic.as_str()).collect();
let direction: Vec<&str> = self
.iter()
.map(|w| match w.order_data.direction {
crate::models::common::Direction::Buy => "BUY",
crate::models::common::Direction::Sell => "SELL",
})
.collect();
let order_size: Vec<f64> = self.iter().map(|w| w.order_data.order_size).collect();
let order_level: Vec<f64> = self.iter().map(|w| w.order_data.order_level).collect();
let order_type: Vec<&str> = self
.iter()
.map(|w| order_type_str(w.order_data.order_type))
.collect();
let time_in_force: Vec<&str> = self
.iter()
.map(|w| time_in_force_str(w.order_data.time_in_force))
.collect();
let guaranteed_stop: Vec<bool> =
self.iter().map(|w| w.order_data.guaranteed_stop).collect();
let currency_code: Vec<Option<&str>> = self
.iter()
.map(|w| w.order_data.currency_code.as_ref().map(Currency::as_str))
.collect();
let dma: Vec<Option<bool>> = self.iter().map(|w| w.order_data.dma).collect();
let good_till_date: Vec<Option<&str>> = self
.iter()
.map(|w| w.order_data.good_till_date.as_deref())
.collect();
let limit_distance: Vec<Option<f64>> =
self.iter().map(|w| w.order_data.limit_distance).collect();
let limit_level: Vec<Option<f64>> = self.iter().map(|w| w.order_data.limit_level).collect();
let stop_distance: Vec<Option<f64>> =
self.iter().map(|w| w.order_data.stop_distance).collect();
let stop_level: Vec<Option<f64>> = self.iter().map(|w| w.order_data.stop_level).collect();
let market_bid: Vec<Option<f64>> = self.iter().map(|w| w.market.bid).collect();
let market_offer: Vec<Option<f64>> = self.iter().map(|w| w.market.offer).collect();
let market_status: Vec<&str> = self
.iter()
.map(|w| market_status_str(w.market.market_status))
.collect();
DataFrame::new(vec![
Column::new("deal_id".into(), deal_id),
Column::new("epic".into(), epic),
Column::new("direction".into(), direction),
Column::new("order_size".into(), order_size),
Column::new("order_level".into(), order_level),
Column::new("order_type".into(), order_type),
Column::new("time_in_force".into(), time_in_force),
Column::new("guaranteed_stop".into(), guaranteed_stop),
Column::new("currency_code".into(), currency_code),
Column::new("dma".into(), dma),
Column::new("good_till_date".into(), good_till_date),
Column::new("limit_distance".into(), limit_distance),
Column::new("limit_level".into(), limit_level),
Column::new("stop_distance".into(), stop_distance),
Column::new("stop_level".into(), stop_level),
Column::new("market_bid".into(), market_bid),
Column::new("market_offer".into(), market_offer),
Column::new("market_status".into(), market_status),
])
.map_err(|e| crate::Error::Config(format!("polars conversion failed: {e}")))
}
}
#[cfg(feature = "polars")]
fn order_type_str(t: crate::models::common::OrderType) -> &'static str {
use crate::models::common::OrderType;
match t {
OrderType::Limit => "LIMIT",
OrderType::Market => "MARKET",
OrderType::Quote => "QUOTE",
OrderType::Stop => "STOP",
}
}
#[cfg(feature = "polars")]
fn time_in_force_str(t: crate::models::common::TimeInForce) -> &'static str {
use crate::models::common::TimeInForce;
match t {
TimeInForce::GoodTillCancelled => "GOOD_TILL_CANCELLED",
TimeInForce::GoodTillDate => "GOOD_TILL_DATE",
TimeInForce::ExecuteAndEliminate => "EXECUTE_AND_ELIMINATE",
TimeInForce::FillOrKill => "FILL_OR_KILL",
}
}
#[cfg(feature = "polars")]
fn market_status_str(s: crate::models::common::MarketStatus) -> &'static str {
use crate::models::common::MarketStatus;
match s {
MarketStatus::Tradeable => "TRADEABLE",
MarketStatus::EditsOnly => "EDITS_ONLY",
MarketStatus::Closed => "CLOSED",
MarketStatus::Offline => "OFFLINE",
MarketStatus::OnAuction => "ON_AUCTION",
MarketStatus::OnAuctionNoEdits => "ON_AUCTION_NO_EDITS",
MarketStatus::Suspended => "SUSPENDED",
MarketStatus::Unknown => "UNKNOWN",
}
}