use std::convert::From;
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use crate::client::{DataStream, ResponseContext, Subscription};
use crate::contracts::{ComboLeg, ComboLegOpenClose, Contract, DeltaNeutralContract, SecurityType};
use crate::messages::{IncomingMessages, Notice, OutgoingMessages};
use crate::messages::{RequestMessage, ResponseMessage};
use crate::Client;
use crate::{encode_option_field, ToField};
use crate::{server_versions, Error};
mod decoders;
pub(crate) mod encoders;
#[cfg(test)]
mod tests;
pub mod order_builder;
pub use crate::contracts::TagValue;
const COMPETE_AGAINST_BEST_OFFSET_UP_TO_MID: Option<f64> = Some(f64::INFINITY);
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Order {
pub order_id: i32,
pub solicited: bool,
pub client_id: i32,
pub perm_id: i32,
pub action: Action,
pub total_quantity: f64,
pub order_type: String,
pub limit_price: Option<f64>,
pub aux_price: Option<f64>,
pub tif: String, pub oca_group: String,
pub oca_type: i32,
pub order_ref: String,
pub transmit: bool,
pub parent_id: i32,
pub block_order: bool,
pub sweep_to_fill: bool,
pub display_size: Option<i32>,
pub trigger_method: i32,
pub outside_rth: bool,
pub hidden: bool,
pub good_after_time: String,
pub good_till_date: String,
pub override_percentage_constraints: bool,
pub rule_80_a: Option<Rule80A>,
pub all_or_none: bool,
pub min_qty: Option<i32>,
pub percent_offset: Option<f64>,
pub trail_stop_price: Option<f64>,
pub trailing_percent: Option<f64>,
pub fa_group: String,
pub fa_profile: String,
pub fa_method: String,
pub fa_percentage: String,
pub open_close: Option<OrderOpenClose>,
pub origin: i32,
pub short_sale_slot: i32,
pub designated_location: String,
pub exempt_code: i32,
pub discretionary_amt: f64,
pub opt_out_smart_routing: bool,
pub auction_strategy: Option<i32>, pub starting_price: Option<f64>,
pub stock_ref_price: Option<f64>,
pub delta: Option<f64>,
pub stock_range_lower: Option<f64>,
pub stock_range_upper: Option<f64>,
pub volatility: Option<f64>,
pub volatility_type: Option<i32>, pub continuous_update: bool,
pub reference_price_type: Option<i32>,
pub delta_neutral_order_type: String,
pub delta_neutral_aux_price: Option<f64>,
pub delta_neutral_con_id: i32,
pub delta_neutral_settling_firm: String,
pub delta_neutral_clearing_account: String,
pub delta_neutral_clearing_intent: String,
pub delta_neutral_open_close: String,
pub delta_neutral_short_sale: bool,
pub delta_neutral_short_sale_slot: i32,
pub delta_neutral_designated_location: String,
pub basis_points: Option<f64>,
pub basis_points_type: Option<i32>,
pub scale_init_level_size: Option<i32>,
pub scale_subs_level_size: Option<i32>,
pub scale_price_increment: Option<f64>,
pub scale_price_adjust_value: Option<f64>,
pub scale_price_adjust_interval: Option<i32>,
pub scale_profit_offset: Option<f64>,
pub scale_auto_reset: bool,
pub scale_init_position: Option<i32>,
pub scale_init_fill_qty: Option<i32>,
pub scale_random_percent: bool,
pub hedge_type: String,
pub hedge_param: String,
pub account: String,
pub settling_firm: String,
pub clearing_account: String,
pub clearing_intent: String,
pub algo_strategy: String,
pub algo_params: Vec<TagValue>,
pub what_if: bool,
pub algo_id: String,
pub not_held: bool,
pub smart_combo_routing_params: Vec<TagValue>,
pub order_combo_legs: Vec<OrderComboLeg>,
pub order_misc_options: Vec<TagValue>,
pub active_start_time: String,
pub active_stop_time: String,
pub scale_table: String,
pub model_code: String,
pub ext_operator: String,
pub cash_qty: Option<f64>,
pub mifid2_decision_maker: String,
pub mifid2_decision_algo: String,
pub mifid2_execution_trader: String,
pub mifid2_execution_algo: String,
pub dont_use_auto_price_for_hedge: bool,
pub auto_cancel_date: String, pub filled_quantity: f64,
pub ref_futures_con_id: Option<i32>,
pub auto_cancel_parent: bool,
pub shareholder: String,
pub imbalance_only: bool,
pub route_marketable_to_bbo: bool,
pub parent_perm_id: Option<i64>,
pub advanced_error_override: String,
pub manual_order_time: String,
pub min_trade_qty: Option<i32>,
pub min_compete_size: Option<i32>,
pub compete_against_best_offset: Option<f64>,
pub mid_offset_at_whole: Option<f64>,
pub mid_offset_at_half: Option<f64>,
pub randomize_size: bool,
pub randomize_price: bool,
pub reference_contract_id: i32,
pub is_pegged_change_amount_decrease: bool,
pub pegged_change_amount: Option<f64>,
pub reference_change_amount: Option<f64>,
pub reference_exchange: String,
pub adjusted_order_type: String,
pub trigger_price: Option<f64>,
pub limit_price_offset: Option<f64>,
pub adjusted_stop_price: Option<f64>,
pub adjusted_stop_limit_price: Option<f64>,
pub adjusted_trailing_amount: Option<f64>,
pub adjustable_trailing_unit: i32,
pub conditions: Vec<OrderCondition>,
pub conditions_ignore_rth: bool,
pub conditions_cancel_order: bool,
pub soft_dollar_tier: SoftDollarTier,
pub is_oms_container: bool,
pub discretionary_up_to_limit_price: bool,
pub use_price_mgmt_algo: bool,
pub duration: Option<i32>, pub post_to_ats: Option<i32>,
}
impl Default for Order {
fn default() -> Self {
Self {
order_id: 0,
solicited: false,
client_id: 0,
perm_id: 0,
action: Action::Buy,
total_quantity: 0.0,
order_type: "".to_owned(),
limit_price: None,
aux_price: None,
tif: "".to_owned(),
oca_group: "".to_owned(),
oca_type: 0,
order_ref: "".to_owned(),
transmit: true,
parent_id: 0,
block_order: false,
sweep_to_fill: false,
display_size: Some(0), trigger_method: 0,
outside_rth: false,
hidden: false,
good_after_time: "".to_owned(),
good_till_date: "".to_owned(),
override_percentage_constraints: false,
rule_80_a: None,
all_or_none: false,
min_qty: None,
percent_offset: None,
trail_stop_price: None,
trailing_percent: None,
fa_group: "".to_owned(),
fa_profile: "".to_owned(),
fa_method: "".to_owned(),
fa_percentage: "".to_owned(),
open_close: None,
origin: 0,
short_sale_slot: 0,
designated_location: "".to_owned(),
exempt_code: -1,
discretionary_amt: 0.0,
opt_out_smart_routing: false,
auction_strategy: Some(0), starting_price: None,
stock_ref_price: None,
delta: None,
stock_range_lower: None,
stock_range_upper: None,
volatility: None,
volatility_type: None,
continuous_update: false,
reference_price_type: None,
delta_neutral_order_type: "".to_owned(),
delta_neutral_aux_price: None,
delta_neutral_con_id: 0,
delta_neutral_settling_firm: "".to_owned(),
delta_neutral_clearing_account: "".to_owned(),
delta_neutral_clearing_intent: "".to_owned(),
delta_neutral_open_close: "".to_owned(),
delta_neutral_short_sale: false,
delta_neutral_short_sale_slot: 0,
delta_neutral_designated_location: "".to_owned(),
basis_points: Some(0.0),
basis_points_type: Some(0),
scale_init_level_size: None,
scale_subs_level_size: None,
scale_price_increment: None,
scale_price_adjust_value: None,
scale_price_adjust_interval: None,
scale_profit_offset: None,
scale_auto_reset: false,
scale_init_position: None,
scale_init_fill_qty: None,
scale_random_percent: false,
hedge_type: "".to_owned(),
hedge_param: "".to_owned(),
account: "".to_owned(),
settling_firm: "".to_owned(),
clearing_account: "".to_owned(),
clearing_intent: "".to_owned(),
algo_strategy: "".to_owned(),
algo_params: vec![],
what_if: false,
algo_id: "".to_owned(),
not_held: false,
smart_combo_routing_params: vec![],
order_combo_legs: vec![],
order_misc_options: vec![],
active_start_time: "".to_owned(),
active_stop_time: "".to_owned(),
scale_table: "".to_owned(),
model_code: "".to_owned(),
ext_operator: "".to_owned(),
cash_qty: None,
mifid2_decision_maker: "".to_owned(),
mifid2_decision_algo: "".to_owned(),
mifid2_execution_trader: "".to_owned(),
mifid2_execution_algo: "".to_owned(),
dont_use_auto_price_for_hedge: false,
auto_cancel_date: "".to_owned(),
filled_quantity: 0.0,
ref_futures_con_id: Some(0),
auto_cancel_parent: false,
shareholder: "".to_owned(),
imbalance_only: false,
route_marketable_to_bbo: false,
parent_perm_id: None,
advanced_error_override: "".to_owned(),
manual_order_time: "".to_owned(),
min_trade_qty: None,
min_compete_size: None,
compete_against_best_offset: None,
mid_offset_at_whole: None,
mid_offset_at_half: None,
randomize_size: false,
randomize_price: false,
reference_contract_id: 0,
is_pegged_change_amount_decrease: false,
pegged_change_amount: Some(0.0),
reference_change_amount: Some(0.0),
reference_exchange: "".to_owned(),
adjusted_order_type: "".to_owned(),
trigger_price: None,
limit_price_offset: None,
adjusted_stop_price: None,
adjusted_stop_limit_price: None,
adjusted_trailing_amount: None,
adjustable_trailing_unit: 0,
conditions: vec![],
conditions_ignore_rth: false,
conditions_cancel_order: false,
soft_dollar_tier: SoftDollarTier::default(),
is_oms_container: false,
discretionary_up_to_limit_price: false,
use_price_mgmt_algo: false,
duration: None,
post_to_ats: None,
}
}
}
impl Order {
pub fn is_delta_neutral(&self) -> bool {
!self.delta_neutral_order_type.is_empty()
}
pub fn is_scale_order(&self) -> bool {
match self.scale_price_increment {
Some(price_increment) => price_increment > 0.0,
_ => false,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Copy, Serialize, Deserialize)]
pub enum Action {
#[default]
Buy,
Sell,
SellShort,
SellLong,
}
impl ToField for Action {
fn to_field(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for Action {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let text = match self {
Action::Buy => "BUY",
Action::Sell => "SELL",
Action::SellShort => "SSHORT",
Action::SellLong => "SLONG",
};
write!(f, "{text}")
}
}
impl Action {
pub fn reverse(self) -> Action {
match self {
Action::Buy => Action::Sell,
Action::Sell => Action::Buy,
Action::SellShort => Action::SellLong,
Action::SellLong => Action::SellShort,
}
}
pub fn from(name: &str) -> Self {
match name {
"BUY" => Self::Buy,
"SELL" => Self::Sell,
"SSHORT" => Self::SellShort,
"SLONG" => Self::SellLong,
&_ => todo!(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Rule80A {
Individual,
Agency,
AgentOtherMember,
IndividualPTIA,
AgencyPTIA,
AgentOtherMemberPTIA,
IndividualPT,
AgencyPT,
AgentOtherMemberPT,
}
impl ToField for Rule80A {
fn to_field(&self) -> String {
self.to_string()
}
}
impl ToField for Option<Rule80A> {
fn to_field(&self) -> String {
encode_option_field(self)
}
}
impl std::fmt::Display for Rule80A {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let text = match self {
Rule80A::Individual => "I",
Rule80A::Agency => "A",
Rule80A::AgentOtherMember => "W",
Rule80A::IndividualPTIA => "J",
Rule80A::AgencyPTIA => "U",
Rule80A::AgentOtherMemberPTIA => "M",
Rule80A::IndividualPT => "K",
Rule80A::AgencyPT => "Y",
Rule80A::AgentOtherMemberPT => "N",
};
write!(f, "{text}")
}
}
impl Rule80A {
pub fn from(source: &str) -> Option<Self> {
match source {
"I" => Some(Rule80A::Individual),
"A" => Some(Rule80A::Agency),
"W" => Some(Rule80A::AgentOtherMember),
"J" => Some(Rule80A::IndividualPTIA),
"U" => Some(Rule80A::AgencyPTIA),
"M" => Some(Rule80A::AgentOtherMemberPTIA),
"K" => Some(Rule80A::IndividualPT),
"Y" => Some(Rule80A::AgencyPT),
"N" => Some(Rule80A::AgentOtherMemberPT),
_ => None,
}
}
}
pub enum AuctionStrategy {
Match,
Improvement,
Transparent,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct OrderComboLeg {
pub price: Option<f64>,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum OrderCondition {
Price = 1,
Time = 3,
Margin = 4,
Execution = 5,
Volume = 6,
PercentChange = 7,
}
impl ToField for OrderCondition {
fn to_field(&self) -> String {
(*self as u8).to_string()
}
}
impl ToField for Option<OrderCondition> {
fn to_field(&self) -> String {
encode_option_field(self)
}
}
impl From<i32> for OrderCondition {
fn from(val: i32) -> Self {
match val {
1 => OrderCondition::Price,
3 => OrderCondition::Time,
4 => OrderCondition::Volume,
5 => OrderCondition::Execution,
6 => OrderCondition::Volume,
7 => OrderCondition::PercentChange,
_ => panic!("OrderCondition({val}) is unsupported"),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct SoftDollarTier {
pub name: String,
pub value: String,
pub display_name: String,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct OrderData {
pub order_id: i32,
pub contract: Contract,
pub order: Order,
pub order_state: OrderState,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct OrderState {
pub status: String,
pub initial_margin_before: Option<f64>,
pub maintenance_margin_before: Option<f64>,
pub equity_with_loan_before: Option<f64>,
pub initial_margin_change: Option<f64>,
pub maintenance_margin_change: Option<f64>,
pub equity_with_loan_change: Option<f64>,
pub initial_margin_after: Option<f64>,
pub maintenance_margin_after: Option<f64>,
pub equity_with_loan_after: Option<f64>,
pub commission: Option<f64>,
pub minimum_commission: Option<f64>,
pub maximum_commission: Option<f64>,
pub commission_currency: String,
pub warning_text: String,
pub completed_time: String,
pub completed_status: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum OrderOpenClose {
Open,
Close,
}
impl ToField for OrderOpenClose {
fn to_field(&self) -> String {
self.to_string()
}
}
impl ToField for Option<OrderOpenClose> {
fn to_field(&self) -> String {
encode_option_field(self)
}
}
impl std::fmt::Display for OrderOpenClose {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let text = match self {
OrderOpenClose::Open => "O",
OrderOpenClose::Close => "C",
};
write!(f, "{text}")
}
}
impl OrderOpenClose {
pub fn from(source: &str) -> Option<Self> {
match source {
"O" => Some(OrderOpenClose::Open),
"C" => Some(OrderOpenClose::Close),
_ => None,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct CommissionReport {
pub execution_id: String,
pub commission: f64,
pub currency: String,
pub realized_pnl: Option<f64>,
pub yields: Option<f64>,
pub yield_redemption_date: String,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub enum Liquidity {
#[default]
None = 0,
AddedLiquidity = 1,
RemovedLiquidity = 2,
LiquidityRoutedOut = 3,
}
impl From<i32> for Liquidity {
fn from(val: i32) -> Self {
match val {
1 => Liquidity::AddedLiquidity,
2 => Liquidity::RemovedLiquidity,
3 => Liquidity::LiquidityRoutedOut,
_ => Liquidity::None,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Execution {
pub order_id: i32,
pub client_id: i32,
pub execution_id: String,
pub time: String,
pub account_number: String,
pub exchange: String,
pub side: String,
pub shares: f64,
pub price: f64,
pub perm_id: i32,
pub liquidation: i32,
pub cumulative_quantity: f64,
pub average_price: f64,
pub order_reference: String,
pub ev_rule: String,
pub ev_multiplier: Option<f64>,
pub model_code: String,
pub last_liquidity: Liquidity,
}
#[derive(Clone, Debug, Default)]
pub struct ExecutionData {
pub request_id: i32,
pub contract: Contract,
pub execution: Execution,
}
#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum PlaceOrder {
OrderStatus(OrderStatus),
OpenOrder(OrderData),
ExecutionData(ExecutionData),
CommissionReport(CommissionReport),
Message(Notice),
}
#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum OrderUpdate {
OrderStatus(OrderStatus),
OpenOrder(OrderData),
ExecutionData(ExecutionData),
CommissionReport(CommissionReport),
Message(Notice),
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct OrderStatus {
pub order_id: i32,
pub status: String,
pub filled: f64,
pub remaining: f64,
pub average_fill_price: f64,
pub perm_id: i32,
pub parent_id: i32,
pub last_fill_price: f64,
pub client_id: i32,
pub why_held: String,
pub market_cap_price: f64,
}
pub(crate) fn order_update_stream<'a>(client: &'a Client) -> Result<Subscription<'a, OrderUpdate>, Error> {
let subscription = client.create_order_update_subscription()?;
Ok(Subscription::new(client, subscription, ResponseContext::default()))
}
pub(crate) fn submit_order(client: &Client, order_id: i32, contract: &Contract, order: &Order) -> Result<(), Error> {
verify_order(client, order, order_id)?;
verify_order_contract(client, contract, order_id)?;
let request = encoders::encode_place_order(client.server_version(), order_id, contract, order)?;
client.send_message(request)?;
Ok(())
}
pub(crate) fn place_order<'a>(client: &'a Client, order_id: i32, contract: &Contract, order: &Order) -> Result<Subscription<'a, PlaceOrder>, Error> {
verify_order(client, order, order_id)?;
verify_order_contract(client, contract, order_id)?;
let request = encoders::encode_place_order(client.server_version(), order_id, contract, order)?;
let subscription = client.send_order(order_id, request)?;
Ok(Subscription::new(client, subscription, ResponseContext::default()))
}
impl DataStream<PlaceOrder> for PlaceOrder {
fn decode(client: &Client, message: &mut ResponseMessage) -> Result<PlaceOrder, Error> {
match message.message_type() {
IncomingMessages::OpenOrder => Ok(PlaceOrder::OpenOrder(decoders::decode_open_order(
client.server_version,
message.clone(),
)?)),
IncomingMessages::OrderStatus => Ok(PlaceOrder::OrderStatus(decoders::decode_order_status(client.server_version, message)?)),
IncomingMessages::ExecutionData => Ok(PlaceOrder::ExecutionData(decoders::decode_execution_data(
client.server_version,
message,
)?)),
IncomingMessages::CommissionsReport => Ok(PlaceOrder::CommissionReport(decoders::decode_commission_report(
client.server_version,
message,
)?)),
IncomingMessages::Error => Ok(PlaceOrder::Message(Notice::from(message))),
_ => Err(Error::UnexpectedResponse(message.clone())),
}
}
}
impl DataStream<OrderUpdate> for OrderUpdate {
fn decode(client: &Client, message: &mut ResponseMessage) -> Result<OrderUpdate, Error> {
match message.message_type() {
IncomingMessages::OpenOrder => Ok(OrderUpdate::OpenOrder(decoders::decode_open_order(
client.server_version,
message.clone(),
)?)),
IncomingMessages::OrderStatus => Ok(OrderUpdate::OrderStatus(decoders::decode_order_status(client.server_version, message)?)),
IncomingMessages::ExecutionData => Ok(OrderUpdate::ExecutionData(decoders::decode_execution_data(
client.server_version,
message,
)?)),
IncomingMessages::CommissionsReport => Ok(OrderUpdate::CommissionReport(decoders::decode_commission_report(
client.server_version,
message,
)?)),
IncomingMessages::Error => Ok(OrderUpdate::Message(Notice::from(message))),
_ => Err(Error::UnexpectedResponse(message.clone())),
}
}
}
fn verify_order(client: &Client, order: &Order, _order_id: i32) -> Result<(), Error> {
let is_bag_order: bool = false;
if order.scale_init_level_size.is_some() || order.scale_price_increment.is_some() {
client.check_server_version(server_versions::SCALE_ORDERS, "It does not support Scale orders.")?
}
if order.what_if {
client.check_server_version(server_versions::WHAT_IF_ORDERS, "It does not support what-if orders.")?
}
if order.scale_subs_level_size.is_some() {
client.check_server_version(
server_versions::SCALE_ORDERS2,
"It does not support Subsequent Level Size for Scale orders.",
)?
}
if !order.algo_strategy.is_empty() {
client.check_server_version(server_versions::ALGO_ORDERS, "It does not support algo orders.")?
}
if order.not_held {
client.check_server_version(server_versions::NOT_HELD, "It does not support not_held parameter.")?
}
if order.exempt_code != -1 {
client.check_server_version(server_versions::SSHORTX, "It does not support exempt_code parameter.")?
}
if !order.hedge_type.is_empty() {
client.check_server_version(server_versions::HEDGE_ORDERS, "It does not support hedge orders.")?
}
if order.opt_out_smart_routing {
client.check_server_version(
server_versions::OPT_OUT_SMART_ROUTING,
"It does not support opt_out_smart_routing parameter.",
)?
}
if order.delta_neutral_con_id > 0
|| !order.delta_neutral_settling_firm.is_empty()
|| !order.delta_neutral_clearing_account.is_empty()
|| !order.delta_neutral_clearing_intent.is_empty()
{
client.check_server_version(
server_versions::DELTA_NEUTRAL_CONID,
"It does not support delta_neutral parameters: con_id, settling_firm, clearing_account, clearing_intent.",
)?
}
if !order.delta_neutral_open_close.is_empty()
|| order.delta_neutral_short_sale
|| order.delta_neutral_short_sale_slot > 0
|| !order.delta_neutral_designated_location.is_empty()
{
client.check_server_version(
server_versions::DELTA_NEUTRAL_OPEN_CLOSE,
"It does not support delta_neutral parameters: open_close, short_sale, short_saleSlot, designated_location",
)?
}
if (order.scale_price_increment > Some(0.0))
&& (order.scale_price_adjust_value.is_some()
|| order.scale_price_adjust_interval.is_some()
|| order.scale_profit_offset.is_some()
|| order.scale_auto_reset
|| order.scale_init_position.is_some()
|| order.scale_init_fill_qty.is_some()
|| order.scale_random_percent)
{
client.check_server_version(
server_versions::SCALE_ORDERS3,
"It does not support Scale order parameters: PriceAdjustValue, PriceAdjustInterval, ProfitOffset, AutoReset, InitPosition, InitFillQty and RandomPercent",
)?
}
if is_bag_order && order.order_combo_legs.iter().any(|combo_leg| combo_leg.price.is_some()) {
client.check_server_version(
server_versions::ORDER_COMBO_LEGS_PRICE,
"It does not support per-leg prices for order combo legs.",
)?
}
if order.trailing_percent.is_some() {
client.check_server_version(server_versions::TRAILING_PERCENT, "It does not support trailing percent parameter.")?
}
if !order.algo_id.is_empty() {
client.check_server_version(server_versions::ALGO_ID, "It does not support algo_id parameter")?
}
if !order.scale_table.is_empty() || !order.active_start_time.is_empty() || !order.active_stop_time.is_empty() {
client.check_server_version(
server_versions::SCALE_TABLE,
"It does not support scale_table, active_start_time nor active_stop_time parameters.",
)?
}
if !order.ext_operator.is_empty() {
client.check_server_version(server_versions::EXT_OPERATOR, "It does not support ext_operator parameter")?
}
if order.cash_qty.is_some() {
client.check_server_version(server_versions::CASH_QTY, "It does not support cash_qty parameter")?
}
if !order.mifid2_execution_trader.is_empty() || !order.mifid2_execution_algo.is_empty() {
client.check_server_version(server_versions::DECISION_MAKER, "It does not support MIFID II execution parameters")?
}
if order.dont_use_auto_price_for_hedge {
client.check_server_version(
server_versions::AUTO_PRICE_FOR_HEDGE,
"It does not support don't use auto price for hedge parameter",
)?
}
if order.is_oms_container {
client.check_server_version(server_versions::ORDER_CONTAINER, "It does not support oms container parameter")?
}
if order.discretionary_up_to_limit_price {
client.check_server_version(server_versions::D_PEG_ORDERS, "It does not support D-Peg orders")?
}
if order.use_price_mgmt_algo {
client.check_server_version(server_versions::PRICE_MGMT_ALGO, "It does not support Use Price Management Algo requests")?
}
if order.duration.is_some() {
client.check_server_version(server_versions::DURATION, "It does not support duration attribute")?
}
if order.post_to_ats.is_some() {
client.check_server_version(server_versions::POST_TO_ATS, "It does not support post_to_ats attribute")?
}
if order.auto_cancel_parent {
client.check_server_version(server_versions::AUTO_CANCEL_PARENT, "It does not support auto_cancel_parent attribute")?
}
if !order.advanced_error_override.is_empty() {
client.check_server_version(
server_versions::ADVANCED_ORDER_REJECT,
"It does not support advanced error override attribute",
)?
}
if !order.manual_order_time.is_empty() {
client.check_server_version(server_versions::MANUAL_ORDER_TIME, "It does not support manual order time attribute")?
}
if order.min_trade_qty.is_some()
|| order.min_compete_size.is_some()
|| order.compete_against_best_offset.is_some()
|| order.mid_offset_at_whole.is_some()
|| order.mid_offset_at_half.is_some()
{
client.check_server_version(
server_versions::PEGBEST_PEGMID_OFFSETS,
"It does not support PEG BEST / PEG MID order parameters: minTradeQty, minCompeteSize, competeAgainstBestOffset, midOffsetAtWhole and midOffsetAtHalf",
)?
}
Ok(())
}
fn verify_order_contract(client: &Client, contract: &Contract, _order_id: i32) -> Result<(), Error> {
if contract
.combo_legs
.iter()
.any(|combo_leg| combo_leg.short_sale_slot != 0 || !combo_leg.designated_location.is_empty())
{
client.check_server_version(server_versions::SSHORT_COMBO_LEGS, "It does not support SSHORT flag for combo legs")?
}
if contract.delta_neutral_contract.is_some() {
client.check_server_version(server_versions::DELTA_NEUTRAL, "It does not support delta-neutral orders")?
}
if contract.contract_id > 0 {
client.check_server_version(server_versions::PLACE_ORDER_CONID, "It does not support contract_id parameter")?
}
if !contract.security_id_type.is_empty() || !contract.security_id.is_empty() {
client.check_server_version(server_versions::SEC_ID_TYPE, "It does not support sec_id_type and sec_id parameters")?
}
if contract.combo_legs.iter().any(|combo_leg| combo_leg.exempt_code != -1) {
client.check_server_version(server_versions::SSHORTX, "It does not support exempt_code parameter")?
}
if !contract.trading_class.is_empty() {
client.check_server_version(
server_versions::TRADING_CLASS,
"It does not support trading_class parameters in place_order",
)?
}
Ok(())
}
pub(crate) fn cancel_order<'a>(client: &'a Client, order_id: i32, manual_order_cancel_time: &str) -> Result<Subscription<'a, CancelOrder>, Error> {
if !manual_order_cancel_time.is_empty() {
client.check_server_version(
server_versions::MANUAL_ORDER_TIME,
"It does not support manual order cancel time attribute",
)?
}
let request = encoders::encode_cancel_order(client.server_version(), order_id, manual_order_cancel_time)?;
let subscription = client.send_order(order_id, request)?;
Ok(Subscription::new(client, subscription, ResponseContext::default()))
}
#[derive(Debug)]
pub enum CancelOrder {
OrderStatus(OrderStatus),
Notice(Notice),
}
impl DataStream<CancelOrder> for CancelOrder {
fn decode(client: &Client, message: &mut ResponseMessage) -> Result<CancelOrder, Error> {
match message.message_type() {
IncomingMessages::OrderStatus => Ok(CancelOrder::OrderStatus(decoders::decode_order_status(client.server_version, message)?)),
IncomingMessages::Error => Ok(CancelOrder::Notice(Notice::from(message))),
_ => Err(Error::UnexpectedResponse(message.clone())),
}
}
}
pub(crate) fn global_cancel(client: &Client) -> Result<(), Error> {
client.check_server_version(server_versions::REQ_GLOBAL_CANCEL, "It does not support global cancel requests.")?;
let message = encoders::encode_global_cancel()?;
let request_id = client.next_request_id();
client.send_order(request_id, message)?;
Ok(())
}
pub(crate) fn next_valid_order_id(client: &Client) -> Result<i32, Error> {
let message = encoders::encode_next_valid_order_id()?;
let subscription = client.send_shared_request(OutgoingMessages::RequestIds, message)?;
if let Some(Ok(message)) = subscription.next() {
let order_id_index = 2;
let next_order_id = message.peek_int(order_id_index)?;
client.set_next_order_id(next_order_id);
Ok(next_order_id)
} else {
Err(Error::Simple("no response from server".into()))
}
}
pub(crate) fn completed_orders(client: &Client, api_only: bool) -> Result<Subscription<Orders>, Error> {
client.check_server_version(server_versions::COMPLETED_ORDERS, "It does not support completed orders requests.")?;
let request = encoders::encode_completed_orders(api_only)?;
let subscription = client.send_shared_request(OutgoingMessages::RequestCompletedOrders, request)?;
Ok(Subscription::new(client, subscription, ResponseContext::default()))
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Orders {
OrderData(OrderData),
OrderStatus(OrderStatus),
Notice(Notice),
}
impl DataStream<Orders> for Orders {
fn decode(client: &Client, message: &mut ResponseMessage) -> Result<Orders, Error> {
match message.message_type() {
IncomingMessages::CompletedOrder => Ok(Orders::OrderData(decoders::decode_completed_order(
client.server_version,
message.clone(),
)?)),
IncomingMessages::CommissionsReport => Ok(Orders::OrderData(decoders::decode_open_order(client.server_version, message.clone())?)),
IncomingMessages::OpenOrder => Ok(Orders::OrderData(decoders::decode_open_order(client.server_version, message.clone())?)),
IncomingMessages::OrderStatus => Ok(Orders::OrderStatus(decoders::decode_order_status(client.server_version, message)?)),
IncomingMessages::OpenOrderEnd | IncomingMessages::CompletedOrdersEnd => Err(Error::EndOfStream),
IncomingMessages::Error => Ok(Orders::Notice(Notice::from(message))),
_ => Err(Error::UnexpectedResponse(message.clone())),
}
}
}
pub(crate) fn open_orders(client: &Client) -> Result<Subscription<Orders>, Error> {
let request = encoders::encode_open_orders()?;
let subscription = client.send_shared_request(OutgoingMessages::RequestOpenOrders, request)?;
Ok(Subscription::new(client, subscription, ResponseContext::default()))
}
pub(crate) fn all_open_orders(client: &Client) -> Result<Subscription<Orders>, Error> {
let request = encoders::encode_all_open_orders()?;
let subscription = client.send_shared_request(OutgoingMessages::RequestAllOpenOrders, request)?;
Ok(Subscription::new(client, subscription, ResponseContext::default()))
}
pub(crate) fn auto_open_orders(client: &Client, auto_bind: bool) -> Result<Subscription<Orders>, Error> {
let request = encoders::encode_auto_open_orders(auto_bind)?;
let subscription = client.send_shared_request(OutgoingMessages::RequestAutoOpenOrders, request)?;
Ok(Subscription::new(client, subscription, ResponseContext::default()))
}
#[derive(Debug, Default)]
pub struct ExecutionFilter {
pub client_id: Option<i32>,
pub account_code: String,
pub time: String,
pub symbol: String,
pub security_type: String,
pub exchange: String,
pub side: String,
}
pub(crate) fn executions(client: &Client, filter: ExecutionFilter) -> Result<Subscription<Executions>, Error> {
let request_id = client.next_request_id();
let request = encoders::encode_executions(client.server_version(), request_id, &filter)?;
let subscription = client.send_request(request_id, request)?;
Ok(Subscription::new(client, subscription, ResponseContext::default()))
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Executions {
ExecutionData(ExecutionData),
CommissionReport(CommissionReport),
Notice(Notice),
}
impl DataStream<Executions> for Executions {
fn decode(client: &Client, message: &mut ResponseMessage) -> Result<Executions, Error> {
match message.message_type() {
IncomingMessages::ExecutionData => Ok(Executions::ExecutionData(decoders::decode_execution_data(
client.server_version,
message,
)?)),
IncomingMessages::CommissionsReport => Ok(Executions::CommissionReport(decoders::decode_commission_report(
client.server_version,
message,
)?)),
IncomingMessages::ExecutionDataEnd => Err(Error::EndOfStream),
IncomingMessages::Error => Ok(Executions::Notice(Notice::from(message))),
_ => Err(Error::UnexpectedResponse(message.clone())),
}
}
}
#[derive(Debug)]
pub enum ExerciseAction {
Exercise = 1,
Lapse = 2,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[allow(clippy::large_enum_variant)]
pub enum ExerciseOptions {
OpenOrder(OrderData),
OrderStatus(OrderStatus),
Notice(Notice),
}
impl DataStream<ExerciseOptions> for ExerciseOptions {
fn decode(client: &Client, message: &mut ResponseMessage) -> Result<ExerciseOptions, Error> {
match message.message_type() {
IncomingMessages::OpenOrder => Ok(ExerciseOptions::OpenOrder(decoders::decode_open_order(
client.server_version,
message.clone(),
)?)),
IncomingMessages::OrderStatus => Ok(ExerciseOptions::OrderStatus(decoders::decode_order_status(
client.server_version,
message,
)?)),
IncomingMessages::Error => Ok(ExerciseOptions::Notice(Notice::from(message))),
_ => Err(Error::UnexpectedResponse(message.clone())),
}
}
}
pub(crate) fn exercise_options<'a>(
client: &'a Client,
contract: &Contract,
exercise_action: ExerciseAction,
exercise_quantity: i32,
account: &str,
ovrd: bool,
manual_order_time: Option<OffsetDateTime>,
) -> Result<Subscription<'a, ExerciseOptions>, Error> {
let request_id = client.next_request_id();
let request = encoders::encode_exercise_options(
client.server_version(),
request_id,
contract,
exercise_action,
exercise_quantity,
account,
ovrd,
manual_order_time,
)?;
let subscription = client.send_request(request_id, request)?;
Ok(Subscription::new(client, subscription, ResponseContext::default()))
}