use crate::constants::{DEFAULT_ORDER_BUY_LEVEL, DEFAULT_ORDER_SELL_LEVEL};
use crate::prelude::{Deserialize, Serialize, WorkingOrder};
use crate::presentation::order::{Direction, OrderType, TimeInForce};
use chrono::{Duration, Utc};
use pretty_simple_display::{DebugPretty, DisplaySimple};
use std::fmt;
use std::fmt::{Debug, Display};
#[derive(Clone, Default, Deserialize, Serialize)]
pub struct RecentPricesRequest<'a> {
pub epic: &'a str,
pub resolution: Option<&'a str>,
pub from: Option<&'a str>,
pub to: Option<&'a str>,
pub max_points: Option<i32>,
pub page_size: Option<i32>,
pub page_number: Option<i32>,
}
impl<'a> RecentPricesRequest<'a> {
#[must_use]
pub fn new(epic: &'a str) -> Self {
Self {
epic,
..Default::default()
}
}
#[must_use]
pub fn with_resolution(mut self, resolution: &'a str) -> Self {
self.resolution = Some(resolution);
self
}
#[must_use]
pub fn with_from(mut self, from: &'a str) -> Self {
self.from = Some(from);
self
}
#[must_use]
pub fn with_to(mut self, to: &'a str) -> Self {
self.to = Some(to);
self
}
#[must_use]
pub fn with_max_points(mut self, max_points: i32) -> Self {
self.max_points = Some(max_points);
self
}
#[must_use]
pub fn with_page_size(mut self, page_size: i32) -> Self {
self.page_size = Some(page_size);
self
}
#[must_use]
pub fn with_page_number(mut self, page_number: i32) -> Self {
self.page_number = Some(page_number);
self
}
}
impl Display for RecentPricesRequest<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let json = serde_json::to_string(self).unwrap_or_else(|_| "Invalid JSON".to_string());
write!(f, "{}", json)
}
}
impl Debug for RecentPricesRequest<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let json =
serde_json::to_string_pretty(self).unwrap_or_else(|_| "Invalid JSON".to_string());
write!(f, "{}", json)
}
}
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
pub struct CreateOrderRequest {
pub epic: String,
pub direction: Direction,
pub size: f64,
#[serde(rename = "orderType")]
pub order_type: OrderType,
#[serde(rename = "timeInForce")]
pub time_in_force: TimeInForce,
#[serde(skip_serializing_if = "Option::is_none")]
pub level: Option<f64>,
#[serde(rename = "guaranteedStop")]
pub guaranteed_stop: bool,
#[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
pub stop_level: Option<f64>,
#[serde(rename = "stopDistance", skip_serializing_if = "Option::is_none")]
pub stop_distance: Option<f64>,
#[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
pub limit_level: Option<f64>,
#[serde(rename = "limitDistance", skip_serializing_if = "Option::is_none")]
pub limit_distance: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expiry: Option<String>,
#[serde(rename = "dealReference", skip_serializing_if = "Option::is_none")]
pub deal_reference: Option<String>,
#[serde(rename = "forceOpen")]
pub force_open: bool,
#[serde(rename = "currencyCode")]
pub currency_code: String,
#[serde(rename = "quoteId", skip_serializing_if = "Option::is_none")]
pub quote_id: Option<String>,
#[serde(rename = "trailingStop", skip_serializing_if = "Option::is_none")]
pub trailing_stop: Option<bool>,
#[serde(
rename = "trailingStopIncrement",
skip_serializing_if = "Option::is_none"
)]
pub trailing_stop_increment: Option<f64>,
}
impl CreateOrderRequest {
#[must_use]
pub fn market(
epic: String,
direction: Direction,
size: f64,
currency_code: Option<String>,
deal_reference: Option<String>,
) -> Self {
let rounded_size = (size * 100.0).floor() / 100.0;
let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
Self {
epic,
direction,
size: rounded_size,
order_type: OrderType::Market,
time_in_force: TimeInForce::FillOrKill,
level: None,
guaranteed_stop: false,
stop_level: None,
stop_distance: None,
limit_level: None,
limit_distance: None,
expiry: Some("-".to_string()),
deal_reference,
force_open: true,
currency_code,
quote_id: None,
trailing_stop: Some(false),
trailing_stop_increment: None,
}
}
#[must_use]
pub fn limit(
epic: String,
direction: Direction,
size: f64,
level: f64,
currency_code: Option<String>,
deal_reference: Option<String>,
) -> Self {
let rounded_size = (size * 100.0).floor() / 100.0;
let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
Self {
epic,
direction,
size: rounded_size,
order_type: OrderType::Limit,
time_in_force: TimeInForce::GoodTillCancelled,
level: Some(level),
guaranteed_stop: false,
stop_level: None,
stop_distance: None,
limit_level: None,
limit_distance: None,
expiry: None,
deal_reference,
force_open: true,
currency_code,
quote_id: None,
trailing_stop: Some(false),
trailing_stop_increment: None,
}
}
#[must_use]
pub fn sell_option_to_market(
epic: String,
size: f64,
expiry: Option<String>,
deal_reference: Option<String>,
currency_code: Option<String>,
) -> Self {
let rounded_size = (size * 100.0).floor() / 100.0;
let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
let deal_reference =
deal_reference.or_else(|| Some(nanoid::nanoid!(30, &nanoid::alphabet::SAFE)));
Self {
epic,
direction: Direction::Sell,
size: rounded_size,
order_type: OrderType::Limit,
time_in_force: TimeInForce::FillOrKill,
level: Some(DEFAULT_ORDER_SELL_LEVEL),
guaranteed_stop: false,
stop_level: None,
stop_distance: None,
limit_level: None,
limit_distance: None,
expiry: expiry.clone(),
deal_reference: deal_reference.clone(),
force_open: true,
currency_code,
quote_id: None,
trailing_stop: Some(false),
trailing_stop_increment: None,
}
}
#[must_use]
pub fn sell_option_to_market_w_force(
epic: String,
size: f64,
expiry: Option<String>,
deal_reference: Option<String>,
currency_code: Option<String>,
force_open: bool, ) -> Self {
let rounded_size = (size * 100.0).floor() / 100.0;
let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
let deal_reference =
deal_reference.or_else(|| Some(nanoid::nanoid!(30, &nanoid::alphabet::SAFE)));
Self {
epic,
direction: Direction::Sell,
size: rounded_size,
order_type: OrderType::Limit,
time_in_force: TimeInForce::FillOrKill,
level: Some(DEFAULT_ORDER_SELL_LEVEL),
guaranteed_stop: false,
stop_level: None,
stop_distance: None,
limit_level: None,
limit_distance: None,
expiry: expiry.clone(),
deal_reference: deal_reference.clone(),
force_open,
currency_code,
quote_id: None,
trailing_stop: Some(false),
trailing_stop_increment: None,
}
}
#[must_use]
pub fn buy_option_to_market(
epic: String,
size: f64,
expiry: Option<String>,
deal_reference: Option<String>,
currency_code: Option<String>,
) -> Self {
let rounded_size = (size * 100.0).floor() / 100.0;
let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
let deal_reference =
deal_reference.or_else(|| Some(nanoid::nanoid!(30, &nanoid::alphabet::SAFE)));
Self {
epic,
direction: Direction::Buy,
size: rounded_size,
order_type: OrderType::Limit,
time_in_force: TimeInForce::FillOrKill,
level: Some(DEFAULT_ORDER_BUY_LEVEL),
guaranteed_stop: false,
stop_level: None,
stop_distance: None,
limit_level: None,
limit_distance: None,
expiry: expiry.clone(),
deal_reference: deal_reference.clone(),
force_open: true,
currency_code: currency_code.clone(),
quote_id: None,
trailing_stop: Some(false),
trailing_stop_increment: None,
}
}
#[must_use]
pub fn buy_option_to_market_w_force(
epic: String,
size: f64,
expiry: Option<String>,
deal_reference: Option<String>,
currency_code: Option<String>,
force_open: bool,
) -> Self {
let rounded_size = (size * 100.0).floor() / 100.0;
let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
let deal_reference =
deal_reference.or_else(|| Some(nanoid::nanoid!(30, &nanoid::alphabet::SAFE)));
Self {
epic,
direction: Direction::Buy,
size: rounded_size,
order_type: OrderType::Limit,
time_in_force: TimeInForce::FillOrKill,
level: Some(DEFAULT_ORDER_BUY_LEVEL),
guaranteed_stop: false,
stop_level: None,
stop_distance: None,
limit_level: None,
limit_distance: None,
expiry: expiry.clone(),
deal_reference: deal_reference.clone(),
force_open,
currency_code: currency_code.clone(),
quote_id: None,
trailing_stop: Some(false),
trailing_stop_increment: None,
}
}
#[must_use]
pub fn with_stop_loss(mut self, stop_level: f64) -> Self {
self.stop_level = Some(stop_level);
self
}
#[must_use]
pub fn with_take_profit(mut self, limit_level: f64) -> Self {
self.limit_level = Some(limit_level);
self
}
#[must_use]
pub fn with_trailing_stop_loss(mut self, trailing_stop_increment: f64) -> Self {
self.trailing_stop = Some(true);
self.trailing_stop_increment = Some(trailing_stop_increment);
self
}
#[must_use]
pub fn with_reference(mut self, reference: String) -> Self {
self.deal_reference = Some(reference);
self
}
#[must_use]
pub fn with_stop_distance(mut self, stop_distance: f64) -> Self {
self.stop_distance = Some(stop_distance);
self
}
#[must_use]
pub fn with_limit_distance(mut self, limit_distance: f64) -> Self {
self.limit_distance = Some(limit_distance);
self
}
#[must_use]
pub fn with_guaranteed_stop(mut self, guaranteed: bool) -> Self {
self.guaranteed_stop = guaranteed;
self
}
}
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
pub struct UpdatePositionRequest {
#[serde(rename = "guaranteedStop", skip_serializing_if = "Option::is_none")]
pub guaranteed_stop: Option<bool>,
#[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
pub limit_level: Option<f64>,
#[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
pub stop_level: Option<f64>,
#[serde(rename = "trailingStop", skip_serializing_if = "Option::is_none")]
pub trailing_stop: Option<bool>,
#[serde(
rename = "trailingStopDistance",
skip_serializing_if = "Option::is_none"
)]
pub trailing_stop_distance: Option<f64>,
#[serde(
rename = "trailingStopIncrement",
skip_serializing_if = "Option::is_none"
)]
pub trailing_stop_increment: Option<f64>,
}
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
pub struct ClosePositionRequest {
#[serde(rename = "dealId", skip_serializing_if = "Option::is_none")]
pub deal_id: Option<String>,
pub direction: Direction,
#[serde(skip_serializing_if = "Option::is_none")]
pub epic: Option<String>,
#[serde(rename = "expiry", skip_serializing_if = "Option::is_none")]
pub expiry: Option<String>,
#[serde(rename = "level", skip_serializing_if = "Option::is_none")]
pub level: Option<f64>,
#[serde(rename = "orderType")]
pub order_type: OrderType,
#[serde(rename = "quoteId", skip_serializing_if = "Option::is_none")]
pub quote_id: Option<String>,
pub size: f64,
#[serde(rename = "timeInForce")]
pub time_in_force: TimeInForce,
}
impl ClosePositionRequest {
#[must_use]
pub fn market(deal_id: String, direction: Direction, size: f64) -> Self {
Self {
deal_id: Some(deal_id),
direction,
size,
order_type: OrderType::Market,
time_in_force: TimeInForce::FillOrKill,
level: None,
expiry: None,
epic: None,
quote_id: None,
}
}
#[must_use]
pub fn limit(deal_id: String, direction: Direction, size: f64, level: f64) -> Self {
Self {
deal_id: Some(deal_id),
direction,
size,
order_type: OrderType::Limit,
time_in_force: TimeInForce::FillOrKill,
level: Some(level),
expiry: None,
epic: None,
quote_id: None,
}
}
#[must_use]
pub fn close_option_to_market_by_id(deal_id: String, direction: Direction, size: f64) -> Self {
let level = match direction {
Direction::Buy => Some(DEFAULT_ORDER_BUY_LEVEL),
Direction::Sell => Some(DEFAULT_ORDER_SELL_LEVEL),
};
Self {
deal_id: Some(deal_id),
direction,
size,
order_type: OrderType::Limit,
time_in_force: TimeInForce::FillOrKill,
level,
expiry: None,
epic: None,
quote_id: None,
}
}
#[must_use]
pub fn close_option_to_market_by_epic(
epic: String,
expiry: String,
direction: Direction,
size: f64,
) -> Self {
let level = match direction {
Direction::Buy => Some(DEFAULT_ORDER_BUY_LEVEL),
Direction::Sell => Some(DEFAULT_ORDER_SELL_LEVEL),
};
Self {
deal_id: None,
direction,
size,
order_type: OrderType::Limit,
time_in_force: TimeInForce::FillOrKill,
level,
expiry: Some(expiry),
epic: Some(epic),
quote_id: None,
}
}
}
#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize, Default)]
pub struct CreateWorkingOrderRequest {
pub epic: String,
pub direction: Direction,
pub size: f64,
pub level: f64,
#[serde(rename = "type")]
pub order_type: OrderType,
#[serde(rename = "timeInForce")]
pub time_in_force: TimeInForce,
#[serde(rename = "guaranteedStop", skip_serializing_if = "Option::is_none")]
pub guaranteed_stop: Option<bool>,
#[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
pub stop_level: Option<f64>,
#[serde(rename = "stopDistance", skip_serializing_if = "Option::is_none")]
pub stop_distance: Option<f64>,
#[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
pub limit_level: Option<f64>,
#[serde(rename = "limitDistance", skip_serializing_if = "Option::is_none")]
pub limit_distance: Option<f64>,
#[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
pub good_till_date: Option<String>,
#[serde(rename = "dealReference", skip_serializing_if = "Option::is_none")]
pub deal_reference: Option<String>,
#[serde(rename = "currencyCode")]
pub currency_code: String,
pub expiry: String,
}
impl From<WorkingOrder> for CreateWorkingOrderRequest {
fn from(value: WorkingOrder) -> Self {
let data = value.working_order_data;
Self {
epic: data.epic,
direction: data.direction,
size: data.order_size,
level: data.order_level,
order_type: data.order_type,
time_in_force: data.time_in_force,
guaranteed_stop: Some(data.guaranteed_stop),
stop_level: data.stop_level,
stop_distance: data.stop_distance,
limit_level: data.limit_level,
limit_distance: data.limit_distance,
good_till_date: data.good_till_date,
deal_reference: data.deal_reference,
currency_code: data.currency_code,
expiry: value.market_data.expiry,
}
}
}
impl CreateWorkingOrderRequest {
#[must_use]
pub fn limit(
epic: String,
direction: Direction,
size: f64,
level: f64,
currency_code: String,
expiry: String,
) -> Self {
Self {
epic,
direction,
size,
level,
order_type: OrderType::Limit,
time_in_force: TimeInForce::GoodTillCancelled,
guaranteed_stop: Some(false),
stop_level: None,
stop_distance: None,
limit_level: None,
limit_distance: None,
good_till_date: None,
deal_reference: None,
currency_code,
expiry,
}
}
#[must_use]
pub fn stop(
epic: String,
direction: Direction,
size: f64,
level: f64,
currency_code: String,
expiry: String,
) -> Self {
Self {
epic,
direction,
size,
level,
order_type: OrderType::Stop,
time_in_force: TimeInForce::GoodTillCancelled,
guaranteed_stop: Some(false),
stop_level: None,
stop_distance: None,
limit_level: None,
limit_distance: None,
good_till_date: None,
deal_reference: None,
currency_code,
expiry,
}
}
#[must_use]
pub fn with_stop_loss(mut self, stop_level: f64) -> Self {
self.stop_level = Some(stop_level);
self
}
#[must_use]
pub fn with_take_profit(mut self, limit_level: f64) -> Self {
self.limit_level = Some(limit_level);
self
}
#[must_use]
pub fn with_reference(mut self, reference: String) -> Self {
self.deal_reference = Some(reference);
self
}
#[must_use]
pub fn expires_at(mut self, date: String) -> Self {
self.time_in_force = TimeInForce::GoodTillDate;
self.good_till_date = Some(date);
self
}
#[must_use]
pub fn expires_tomorrow(mut self) -> Self {
self.time_in_force = TimeInForce::GoodTillDate;
let tomorrow = Utc::now() + Duration::days(1);
self.good_till_date = Some(tomorrow.format("%Y/%m/%d %H:%M:%S").to_string());
self
}
#[must_use]
pub fn expires_in(mut self, duration: Duration) -> Self {
self.time_in_force = TimeInForce::GoodTillDate;
let tomorrow = Utc::now() + duration;
self.good_till_date = Some(tomorrow.format("%Y/%m/%d %H:%M:%S").to_string());
self
}
}
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
pub struct CreateWatchlistRequest {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub epics: Option<Vec<String>>,
}
impl CreateWatchlistRequest {
#[must_use]
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
epics: None,
}
}
#[must_use]
pub fn with_epics(name: &str, epics: Vec<String>) -> Self {
Self {
name: name.to_string(),
epics: Some(epics),
}
}
}
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
pub struct AddToWatchlistRequest {
pub epic: String,
}
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
pub struct UpdateWorkingOrderRequest {
#[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
pub good_till_date: Option<String>,
pub level: f64,
#[serde(rename = "limitDistance", skip_serializing_if = "Option::is_none")]
pub limit_distance: Option<f64>,
#[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
pub limit_level: Option<f64>,
#[serde(rename = "stopDistance", skip_serializing_if = "Option::is_none")]
pub stop_distance: Option<f64>,
#[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
pub stop_level: Option<f64>,
#[serde(rename = "guaranteedStop")]
pub guaranteed_stop: bool,
#[serde(rename = "timeInForce")]
pub time_in_force: TimeInForce,
#[serde(rename = "type")]
pub order_type: OrderType,
}
impl UpdateWorkingOrderRequest {
#[must_use]
pub fn new(level: f64, order_type: OrderType, time_in_force: TimeInForce) -> Self {
Self {
level,
order_type,
time_in_force,
guaranteed_stop: false,
good_till_date: None,
limit_distance: None,
limit_level: None,
stop_distance: None,
stop_level: None,
}
}
#[must_use]
pub fn with_stop_level(mut self, stop_level: f64) -> Self {
self.stop_level = Some(stop_level);
self
}
#[must_use]
pub fn with_limit_level(mut self, limit_level: f64) -> Self {
self.limit_level = Some(limit_level);
self
}
#[must_use]
pub fn with_guaranteed_stop(mut self, guaranteed: bool) -> Self {
self.guaranteed_stop = guaranteed;
self
}
}
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
pub struct OpenCostsRequest {
pub epic: String,
pub direction: Direction,
pub size: f64,
#[serde(rename = "orderType")]
pub order_type: OrderType,
#[serde(rename = "currencyCode")]
pub currency_code: String,
#[serde(rename = "guaranteedStop", skip_serializing_if = "Option::is_none")]
pub guaranteed_stop: Option<bool>,
#[serde(rename = "stopDistance", skip_serializing_if = "Option::is_none")]
pub stop_distance: Option<f64>,
#[serde(rename = "limitDistance", skip_serializing_if = "Option::is_none")]
pub limit_distance: Option<f64>,
}
impl OpenCostsRequest {
#[must_use]
pub fn new(epic: &str, direction: Direction, size: f64, currency_code: &str) -> Self {
Self {
epic: epic.to_string(),
direction,
size,
order_type: OrderType::Market,
currency_code: currency_code.to_string(),
guaranteed_stop: None,
stop_distance: None,
limit_distance: None,
}
}
}
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
pub struct CloseCostsRequest {
#[serde(rename = "dealId")]
pub deal_id: String,
pub direction: Direction,
pub size: f64,
#[serde(rename = "orderType")]
pub order_type: OrderType,
}
impl CloseCostsRequest {
#[must_use]
pub fn new(deal_id: &str, direction: Direction, size: f64) -> Self {
Self {
deal_id: deal_id.to_string(),
direction,
size,
order_type: OrderType::Market,
}
}
}
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
pub struct EditCostsRequest {
#[serde(rename = "dealId")]
pub deal_id: String,
#[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
pub stop_level: Option<f64>,
#[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
pub limit_level: Option<f64>,
#[serde(rename = "guaranteedStop", skip_serializing_if = "Option::is_none")]
pub guaranteed_stop: Option<bool>,
}
impl EditCostsRequest {
#[must_use]
pub fn new(deal_id: &str) -> Self {
Self {
deal_id: deal_id.to_string(),
stop_level: None,
limit_level: None,
guaranteed_stop: None,
}
}
#[must_use]
pub fn with_stop_level(mut self, stop_level: f64) -> Self {
self.stop_level = Some(stop_level);
self
}
#[must_use]
pub fn with_limit_level(mut self, limit_level: f64) -> Self {
self.limit_level = Some(limit_level);
self
}
}
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, Default)]
pub struct UpdatePreferencesRequest {
#[serde(rename = "trailingStopsEnabled")]
pub trailing_stops_enabled: bool,
}