use serde::Serialize;
use crate::models::Number;
use crate::models::enums::{
Duration, Instruction, InstrumentAssetType, OrderStrategyType, OrderTypeRequest, Session,
};
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct LegInstrument {
symbol: String,
asset_type: InstrumentAssetType,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct Leg {
instruction: Instruction,
quantity: Number,
instrument: LegInstrument,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderBuilder {
#[serde(skip_serializing_if = "Option::is_none")]
order_type: Option<OrderTypeRequest>,
#[serde(skip_serializing_if = "Option::is_none")]
session: Option<Session>,
#[serde(skip_serializing_if = "Option::is_none")]
duration: Option<Duration>,
order_strategy_type: OrderStrategyType,
#[serde(skip_serializing_if = "Option::is_none")]
price: Option<Number>,
#[serde(skip_serializing_if = "Option::is_none")]
stop_price: Option<Number>,
#[serde(skip_serializing_if = "Vec::is_empty")]
order_leg_collection: Vec<Leg>,
#[serde(skip_serializing_if = "Vec::is_empty")]
child_order_strategies: Vec<OrderBuilder>,
}
impl OrderBuilder {
pub fn market_buy(symbol: impl Into<String>, quantity: Number) -> Self {
Self::equity_market(symbol, Instruction::Buy, quantity)
}
pub fn market_sell(symbol: impl Into<String>, quantity: Number) -> Self {
Self::equity_market(symbol, Instruction::Sell, quantity)
}
pub fn limit_buy(symbol: impl Into<String>, quantity: Number, price: Number) -> Self {
Self::equity_limit(symbol, Instruction::Buy, quantity, price)
}
pub fn limit_sell(symbol: impl Into<String>, quantity: Number, price: Number) -> Self {
Self::equity_limit(symbol, Instruction::Sell, quantity, price)
}
pub fn stop_buy(symbol: impl Into<String>, quantity: Number, stop_price: Number) -> Self {
Self::equity_stop(symbol, Instruction::Buy, quantity, stop_price)
}
pub fn stop_sell(symbol: impl Into<String>, quantity: Number, stop_price: Number) -> Self {
Self::equity_stop(symbol, Instruction::Sell, quantity, stop_price)
}
pub fn stop_limit_buy(
symbol: impl Into<String>,
quantity: Number,
price: Number,
stop_price: Number,
) -> Self {
Self::equity_stop_limit(symbol, Instruction::Buy, quantity, price, stop_price)
}
pub fn stop_limit_sell(
symbol: impl Into<String>,
quantity: Number,
price: Number,
stop_price: Number,
) -> Self {
Self::equity_stop_limit(symbol, Instruction::Sell, quantity, price, stop_price)
}
pub fn option_buy_to_open_market(symbol: impl Into<String>, quantity: Number) -> Self {
Self::option_market(symbol, Instruction::BuyToOpen, quantity)
}
pub fn option_buy_to_open_limit(
symbol: impl Into<String>,
quantity: Number,
price: Number,
) -> Self {
Self::option_limit(symbol, Instruction::BuyToOpen, quantity, price)
}
pub fn option_sell_to_open_market(symbol: impl Into<String>, quantity: Number) -> Self {
Self::option_market(symbol, Instruction::SellToOpen, quantity)
}
pub fn option_sell_to_open_limit(
symbol: impl Into<String>,
quantity: Number,
price: Number,
) -> Self {
Self::option_limit(symbol, Instruction::SellToOpen, quantity, price)
}
pub fn option_buy_to_close_market(symbol: impl Into<String>, quantity: Number) -> Self {
Self::option_market(symbol, Instruction::BuyToClose, quantity)
}
pub fn option_buy_to_close_limit(
symbol: impl Into<String>,
quantity: Number,
price: Number,
) -> Self {
Self::option_limit(symbol, Instruction::BuyToClose, quantity, price)
}
pub fn option_sell_to_close_market(symbol: impl Into<String>, quantity: Number) -> Self {
Self::option_market(symbol, Instruction::SellToClose, quantity)
}
pub fn option_sell_to_close_limit(
symbol: impl Into<String>,
quantity: Number,
price: Number,
) -> Self {
Self::option_limit(symbol, Instruction::SellToClose, quantity, price)
}
pub fn one_cancels_other(first_order: Self, second_order: Self) -> Self {
Self {
order_type: None,
session: None,
duration: None,
order_strategy_type: OrderStrategyType::Oco,
price: None,
stop_price: None,
order_leg_collection: Vec::new(),
child_order_strategies: vec![first_order, second_order],
}
}
pub fn first_triggers_second(mut first_order: Self, second_order: Self) -> Self {
first_order.order_strategy_type = OrderStrategyType::Trigger;
first_order.child_order_strategies.push(second_order);
first_order
}
pub fn equity_market(
symbol: impl Into<String>,
instruction: Instruction,
quantity: Number,
) -> Self {
Self::single_leg(
OrderTypeRequest::Market,
symbol,
instruction,
InstrumentAssetType::Equity,
quantity,
None,
None,
)
}
pub fn equity_limit(
symbol: impl Into<String>,
instruction: Instruction,
quantity: Number,
price: Number,
) -> Self {
Self::single_leg(
OrderTypeRequest::Limit,
symbol,
instruction,
InstrumentAssetType::Equity,
quantity,
Some(price),
None,
)
}
pub fn equity_stop(
symbol: impl Into<String>,
instruction: Instruction,
quantity: Number,
stop_price: Number,
) -> Self {
Self::single_leg(
OrderTypeRequest::Stop,
symbol,
instruction,
InstrumentAssetType::Equity,
quantity,
None,
Some(stop_price),
)
}
pub fn equity_stop_limit(
symbol: impl Into<String>,
instruction: Instruction,
quantity: Number,
price: Number,
stop_price: Number,
) -> Self {
Self::single_leg(
OrderTypeRequest::StopLimit,
symbol,
instruction,
InstrumentAssetType::Equity,
quantity,
Some(price),
Some(stop_price),
)
}
pub fn option_market(
symbol: impl Into<String>,
instruction: Instruction,
quantity: Number,
) -> Self {
Self::single_leg(
OrderTypeRequest::Market,
symbol,
instruction,
InstrumentAssetType::Option,
quantity,
None,
None,
)
}
pub fn option_limit(
symbol: impl Into<String>,
instruction: Instruction,
quantity: Number,
price: Number,
) -> Self {
Self::single_leg(
OrderTypeRequest::Limit,
symbol,
instruction,
InstrumentAssetType::Option,
quantity,
Some(price),
None,
)
}
pub fn session(mut self, session: Session) -> Self {
self.session = Some(session);
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.duration = Some(duration);
self
}
pub fn order_strategy_type(mut self, strategy: OrderStrategyType) -> Self {
self.order_strategy_type = strategy;
self
}
fn single_leg(
order_type: OrderTypeRequest,
symbol: impl Into<String>,
instruction: Instruction,
asset_type: InstrumentAssetType,
quantity: Number,
price: Option<Number>,
stop_price: Option<Number>,
) -> Self {
Self {
order_type: Some(order_type),
session: Some(Session::Normal),
duration: Some(Duration::Day),
order_strategy_type: OrderStrategyType::Single,
price,
stop_price,
order_leg_collection: vec![Leg {
instruction,
quantity,
instrument: LegInstrument {
symbol: symbol.into(),
asset_type,
},
}],
child_order_strategies: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::n;
cfg_select! {
feature = "decimal" => {
fn expected_number(value: f64) -> serde_json::Value {
serde_json::json!(n(value).to_string())
}
}
_ => {
fn expected_number(value: f64) -> serde_json::Value {
serde_json::json!(value)
}
}
}
#[test]
fn market_order_json() {
let order = OrderBuilder::equity_market("AAPL", Instruction::Buy, n(10.0));
let json: serde_json::Value = serde_json::to_value(&order).unwrap();
assert_eq!(json["orderType"], "MARKET");
assert_eq!(json["session"], "NORMAL");
assert_eq!(json["duration"], "DAY");
assert_eq!(json["orderStrategyType"], "SINGLE");
assert!(json.get("price").is_none());
assert!(json.get("stopPrice").is_none());
let legs = json["orderLegCollection"].as_array().unwrap();
assert_eq!(legs.len(), 1);
assert_eq!(legs[0]["instruction"], "BUY");
assert_eq!(legs[0]["quantity"], expected_number(10.0));
assert_eq!(legs[0]["instrument"]["symbol"], "AAPL");
assert_eq!(legs[0]["instrument"]["assetType"], "EQUITY");
}
#[test]
fn limit_order_json() {
let order = OrderBuilder::equity_limit("MSFT", Instruction::Sell, n(5.0), n(400.50));
let json: serde_json::Value = serde_json::to_value(&order).unwrap();
assert_eq!(json["orderType"], "LIMIT");
assert_eq!(json["price"], expected_number(400.50));
assert!(json.get("stopPrice").is_none());
assert_eq!(json["orderLegCollection"][0]["instruction"], "SELL");
assert_eq!(
json["orderLegCollection"][0]["quantity"],
expected_number(5.0)
);
}
#[test]
fn stop_order_json() {
let order = OrderBuilder::equity_stop("GOOG", Instruction::Sell, n(3.0), n(150.0));
let json: serde_json::Value = serde_json::to_value(&order).unwrap();
assert_eq!(json["orderType"], "STOP");
assert_eq!(json["stopPrice"], expected_number(150.0));
assert!(json.get("price").is_none());
}
#[test]
fn stop_limit_order_json() {
let order =
OrderBuilder::equity_stop_limit("TSLA", Instruction::Buy, n(2.0), n(200.0), n(195.0));
let json: serde_json::Value = serde_json::to_value(&order).unwrap();
assert_eq!(json["orderType"], "STOP_LIMIT");
assert_eq!(json["price"], expected_number(200.0));
assert_eq!(json["stopPrice"], expected_number(195.0));
}
#[test]
fn fluent_setters() {
let order = OrderBuilder::equity_market("SPY", Instruction::Buy, n(1.0))
.session(Session::Am)
.duration(Duration::GoodTillCancel)
.order_strategy_type(OrderStrategyType::Trigger);
let json: serde_json::Value = serde_json::to_value(&order).unwrap();
assert_eq!(json["session"], "AM");
assert_eq!(json["duration"], "GOOD_TILL_CANCEL");
assert_eq!(json["orderStrategyType"], "TRIGGER");
}
#[test]
fn buy_sell_convenience_constructors() {
let cases = [
(OrderBuilder::market_buy("AAPL", n(1.0)), "MARKET", "BUY"),
(OrderBuilder::market_sell("AAPL", n(1.0)), "MARKET", "SELL"),
(
OrderBuilder::limit_buy("AAPL", n(1.0), n(100.0)),
"LIMIT",
"BUY",
),
(
OrderBuilder::limit_sell("AAPL", n(1.0), n(100.0)),
"LIMIT",
"SELL",
),
(
OrderBuilder::stop_buy("AAPL", n(1.0), n(90.0)),
"STOP",
"BUY",
),
(
OrderBuilder::stop_sell("AAPL", n(1.0), n(90.0)),
"STOP",
"SELL",
),
(
OrderBuilder::stop_limit_buy("AAPL", n(1.0), n(91.0), n(90.0)),
"STOP_LIMIT",
"BUY",
),
(
OrderBuilder::stop_limit_sell("AAPL", n(1.0), n(91.0), n(90.0)),
"STOP_LIMIT",
"SELL",
),
];
for (order, expected_type, expected_instruction) in cases {
let json: serde_json::Value = serde_json::to_value(&order).unwrap();
assert_eq!(json["orderType"], expected_type);
assert_eq!(
json["orderLegCollection"][0]["instruction"],
expected_instruction
);
}
}
#[test]
fn option_convenience_constructors() {
let symbol = "AAPL 260116C00150000";
let cases = [
(
OrderBuilder::option_buy_to_open_market(symbol, n(1.0)),
"MARKET",
"BUY_TO_OPEN",
None,
),
(
OrderBuilder::option_buy_to_open_limit(symbol, n(1.0), n(2.5)),
"LIMIT",
"BUY_TO_OPEN",
Some(expected_number(2.5)),
),
(
OrderBuilder::option_sell_to_open_market(symbol, n(1.0)),
"MARKET",
"SELL_TO_OPEN",
None,
),
(
OrderBuilder::option_sell_to_open_limit(symbol, n(1.0), n(2.5)),
"LIMIT",
"SELL_TO_OPEN",
Some(expected_number(2.5)),
),
(
OrderBuilder::option_buy_to_close_market(symbol, n(1.0)),
"MARKET",
"BUY_TO_CLOSE",
None,
),
(
OrderBuilder::option_buy_to_close_limit(symbol, n(1.0), n(2.5)),
"LIMIT",
"BUY_TO_CLOSE",
Some(expected_number(2.5)),
),
(
OrderBuilder::option_sell_to_close_market(symbol, n(1.0)),
"MARKET",
"SELL_TO_CLOSE",
None,
),
(
OrderBuilder::option_sell_to_close_limit(symbol, n(1.0), n(2.5)),
"LIMIT",
"SELL_TO_CLOSE",
Some(expected_number(2.5)),
),
];
for (order, expected_type, expected_instruction, expected_price) in cases {
let json: serde_json::Value = serde_json::to_value(&order).unwrap();
assert_eq!(json["orderType"], expected_type);
assert_eq!(json["session"], "NORMAL");
assert_eq!(json["duration"], "DAY");
assert_eq!(json["orderStrategyType"], "SINGLE");
assert_eq!(
json["orderLegCollection"][0]["instruction"],
expected_instruction
);
assert_eq!(
json["orderLegCollection"][0]["quantity"],
expected_number(1.0)
);
assert_eq!(
json["orderLegCollection"][0]["instrument"]["symbol"],
symbol
);
assert_eq!(
json["orderLegCollection"][0]["instrument"]["assetType"],
"OPTION"
);
if let Some(price) = expected_price {
assert_eq!(json["price"], price);
} else {
assert!(json.get("price").is_none());
}
}
}
#[test]
fn option_lower_level_constructors() {
let order = OrderBuilder::option_limit(
"MSFT 260116P00300000",
Instruction::SellToClose,
n(2.0),
n(3.25),
);
let json: serde_json::Value = serde_json::to_value(&order).unwrap();
assert_eq!(json["orderType"], "LIMIT");
assert_eq!(json["price"], expected_number(3.25));
assert_eq!(
json["orderLegCollection"][0]["instruction"],
"SELL_TO_CLOSE"
);
assert_eq!(
json["orderLegCollection"][0]["quantity"],
expected_number(2.0)
);
assert_eq!(
json["orderLegCollection"][0]["instrument"]["assetType"],
"OPTION"
);
}
#[test]
fn one_cancels_other_json() {
let order = OrderBuilder::one_cancels_other(
OrderBuilder::limit_sell("AAPL", n(1.0), n(140.0)),
OrderBuilder::stop_sell("AAPL", n(1.0), n(120.0)),
);
let json: serde_json::Value = serde_json::to_value(&order).unwrap();
assert_eq!(json["orderStrategyType"], "OCO");
assert!(json.get("orderType").is_none());
assert!(json.get("session").is_none());
assert!(json.get("duration").is_none());
assert!(json.get("orderLegCollection").is_none());
let children = json["childOrderStrategies"].as_array().unwrap();
assert_eq!(children.len(), 2);
assert_eq!(children[0]["orderType"], "LIMIT");
assert_eq!(children[0]["orderLegCollection"][0]["instruction"], "SELL");
assert_eq!(children[1]["orderType"], "STOP");
assert_eq!(children[1]["orderLegCollection"][0]["instruction"], "SELL");
}
#[test]
fn first_triggers_second_json() {
let order = OrderBuilder::first_triggers_second(
OrderBuilder::market_buy("AAPL", n(1.0)),
OrderBuilder::limit_sell("AAPL", n(1.0), n(140.0)),
);
let json: serde_json::Value = serde_json::to_value(&order).unwrap();
assert_eq!(json["orderType"], "MARKET");
assert_eq!(json["orderStrategyType"], "TRIGGER");
assert_eq!(json["orderLegCollection"][0]["instruction"], "BUY");
let children = json["childOrderStrategies"].as_array().unwrap();
assert_eq!(children.len(), 1);
assert_eq!(children[0]["orderType"], "LIMIT");
assert_eq!(children[0]["orderStrategyType"], "SINGLE");
assert_eq!(children[0]["orderLegCollection"][0]["instruction"], "SELL");
}
#[test]
fn bracket_order_json() {
let order = OrderBuilder::first_triggers_second(
OrderBuilder::market_buy("AAPL", n(1.0)),
OrderBuilder::one_cancels_other(
OrderBuilder::limit_sell("AAPL", n(1.0), n(160.0)),
OrderBuilder::stop_sell("AAPL", n(1.0), n(140.0)),
),
);
let json: serde_json::Value = serde_json::to_value(&order).unwrap();
assert_eq!(json["orderType"], "MARKET");
assert_eq!(json["orderStrategyType"], "TRIGGER");
assert_eq!(json["orderLegCollection"][0]["instruction"], "BUY");
let trigger_children = json["childOrderStrategies"].as_array().unwrap();
assert_eq!(trigger_children.len(), 1);
assert_eq!(trigger_children[0]["orderStrategyType"], "OCO");
assert!(trigger_children[0].get("orderType").is_none());
assert!(trigger_children[0].get("orderLegCollection").is_none());
let oco_children = trigger_children[0]["childOrderStrategies"]
.as_array()
.unwrap();
assert_eq!(oco_children.len(), 2);
assert_eq!(oco_children[0]["orderType"], "LIMIT");
assert_eq!(oco_children[0]["price"], expected_number(160.0));
assert_eq!(
oco_children[0]["orderLegCollection"][0]["instruction"],
"SELL"
);
assert_eq!(oco_children[1]["orderType"], "STOP");
assert_eq!(oco_children[1]["stopPrice"], expected_number(140.0));
assert_eq!(
oco_children[1]["orderLegCollection"][0]["instruction"],
"SELL"
);
}
}