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 {
order_type: OrderTypeRequest,
session: Session,
duration: 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>,
order_leg_collection: Vec<Leg>,
}
impl OrderBuilder {
pub fn equity_market(
symbol: impl Into<String>,
instruction: Instruction,
quantity: Number,
) -> Self {
Self::new(
OrderTypeRequest::Market,
symbol,
instruction,
quantity,
None,
None,
)
}
pub fn equity_limit(
symbol: impl Into<String>,
instruction: Instruction,
quantity: Number,
price: Number,
) -> Self {
Self::new(
OrderTypeRequest::Limit,
symbol,
instruction,
quantity,
Some(price),
None,
)
}
pub fn equity_stop(
symbol: impl Into<String>,
instruction: Instruction,
quantity: Number,
stop_price: Number,
) -> Self {
Self::new(
OrderTypeRequest::Stop,
symbol,
instruction,
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::new(
OrderTypeRequest::StopLimit,
symbol,
instruction,
quantity,
Some(price),
Some(stop_price),
)
}
pub fn session(mut self, session: Session) -> Self {
self.session = session;
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.duration = duration;
self
}
pub fn order_strategy_type(mut self, strategy: OrderStrategyType) -> Self {
self.order_strategy_type = strategy;
self
}
fn new(
order_type: OrderTypeRequest,
symbol: impl Into<String>,
instruction: Instruction,
quantity: Number,
price: Option<Number>,
stop_price: Option<Number>,
) -> Self {
Self {
order_type,
session: Session::Normal,
duration: Duration::Day,
order_strategy_type: OrderStrategyType::Single,
price,
stop_price,
order_leg_collection: vec![Leg {
instruction,
quantity,
instrument: LegInstrument {
symbol: symbol.into(),
asset_type: InstrumentAssetType::Equity,
},
}],
}
}
}
#[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");
}
}