use crate::core::HasTradeAmount;
use crate::param::TradeAmount;
use crate::pretrade::{CheckPreTradeStartPolicy, Reject, RejectCode, RejectScope};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct OrderValidationPolicy;
impl OrderValidationPolicy {
pub const NAME: &'static str = "OrderValidationPolicy";
pub fn new() -> Self {
Self
}
}
impl<O, R> CheckPreTradeStartPolicy<O, R> for OrderValidationPolicy
where
O: HasTradeAmount,
{
fn name(&self) -> &'static str {
Self::NAME
}
fn check_pre_trade_start(&self, order: &O) -> Result<(), Reject> {
match order.trade_amount() {
TradeAmount::Quantity(quantity) if quantity.is_zero() => {
return Err(Reject::new(
Self::NAME,
RejectScope::Order,
RejectCode::InvalidFieldValue,
"order quantity must be non-zero",
"requested quantity 0 is not allowed",
));
}
TradeAmount::Volume(volume) if volume.is_zero() => {
return Err(Reject::new(
Self::NAME,
RejectScope::Order,
RejectCode::InvalidFieldValue,
"order volume must be non-zero",
"requested volume 0 is not allowed",
));
}
_ => {}
}
Ok(())
}
fn apply_execution_report(&self, _report: &R) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use crate::core::{Instrument, OrderOperation};
use crate::param::{Asset, Price, Quantity, Side, TradeAmount, Volume};
use crate::pretrade::{CheckPreTradeStartPolicy, RejectCode, RejectScope};
use super::OrderValidationPolicy;
#[test]
fn rejects_zero_quantity() {
let policy = OrderValidationPolicy::new();
let order = OrderOperation {
instrument: Instrument::new(
Asset::new("AAPL").expect("asset code must be valid"),
Asset::new("USD").expect("asset code must be valid"),
),
side: Side::Buy,
trade_amount: TradeAmount::Quantity(Quantity::ZERO),
price: Some(Price::from_str("10").expect("price must be valid")),
};
let reject = <OrderValidationPolicy as CheckPreTradeStartPolicy<
OrderOperation,
(),
>>::check_pre_trade_start(&policy, &order)
.expect_err("zero quantity must be rejected");
assert_eq!(reject.scope, RejectScope::Order);
assert_eq!(reject.code, RejectCode::InvalidFieldValue);
assert_eq!(reject.reason, "order quantity must be non-zero");
assert_eq!(reject.details, "requested quantity 0 is not allowed");
}
#[test]
fn rejects_zero_volume() {
let policy = OrderValidationPolicy::new();
let order = OrderOperation {
instrument: Instrument::new(
Asset::new("AAPL").expect("asset code must be valid"),
Asset::new("USD").expect("asset code must be valid"),
),
side: Side::Buy,
trade_amount: TradeAmount::Volume(Volume::ZERO),
price: None,
};
let reject = <OrderValidationPolicy as CheckPreTradeStartPolicy<
OrderOperation,
(),
>>::check_pre_trade_start(&policy, &order)
.expect_err("zero volume must be rejected");
assert_eq!(reject.code, RejectCode::InvalidFieldValue);
assert_eq!(reject.reason, "order volume must be non-zero");
}
#[test]
fn allows_missing_price_when_volume_is_present() {
let policy = OrderValidationPolicy::new();
let order = OrderOperation {
instrument: Instrument::new(
Asset::new("AAPL").expect("asset code must be valid"),
Asset::new("USD").expect("asset code must be valid"),
),
side: Side::Buy,
trade_amount: TradeAmount::Volume(
Volume::from_str("100").expect("volume must be valid"),
),
price: None,
};
assert!(<OrderValidationPolicy as CheckPreTradeStartPolicy<
OrderOperation,
(),
>>::check_pre_trade_start(&policy, &order)
.is_ok());
}
#[test]
fn allows_zero_and_negative_price() {
let policy = OrderValidationPolicy::new();
let zero_price_order = OrderOperation {
instrument: Instrument::new(
Asset::new("AAPL").expect("asset code must be valid"),
Asset::new("USD").expect("asset code must be valid"),
),
side: Side::Buy,
trade_amount: TradeAmount::Quantity(
Quantity::from_str("10").expect("quantity must be valid"),
),
price: Some(Price::ZERO),
};
assert!(<OrderValidationPolicy as CheckPreTradeStartPolicy<
OrderOperation,
(),
>>::check_pre_trade_start(&policy, &zero_price_order)
.is_ok());
let negative_price_order = OrderOperation {
instrument: Instrument::new(
Asset::new("AAPL").expect("asset code must be valid"),
Asset::new("USD").expect("asset code must be valid"),
),
side: Side::Buy,
trade_amount: TradeAmount::Volume(
Volume::from_str("100").expect("volume must be valid"),
),
price: Some(Price::from_str("-5").expect("price must be valid")),
};
assert!(<OrderValidationPolicy as CheckPreTradeStartPolicy<
OrderOperation,
(),
>>::check_pre_trade_start(&policy, &negative_price_order)
.is_ok());
}
#[test]
fn policy_name_is_stable() {
let policy = OrderValidationPolicy::new();
assert_eq!(
<OrderValidationPolicy as CheckPreTradeStartPolicy<OrderOperation, ()>>::name(&policy),
OrderValidationPolicy::NAME
);
}
#[test]
fn apply_execution_report_returns_false() {
let policy = OrderValidationPolicy::new();
let order = OrderOperation {
instrument: Instrument::new(
Asset::new("AAPL").expect("asset code must be valid"),
Asset::new("USD").expect("asset code must be valid"),
),
side: Side::Buy,
trade_amount: TradeAmount::Quantity(
Quantity::from_str("1").expect("quantity must be valid"),
),
price: Some(Price::from_str("10").expect("price must be valid")),
};
assert!(!<OrderValidationPolicy as CheckPreTradeStartPolicy<
OrderOperation,
(),
>>::apply_execution_report(&policy, &()));
assert!(<OrderValidationPolicy as CheckPreTradeStartPolicy<
OrderOperation,
(),
>>::check_pre_trade_start(&policy, &order)
.is_ok());
}
}