use super::{LimitOrder, MarketOrder};
use crate::{LevelId, Price, SequenceNumber, Side, Timestamp};
use std::ops::{Deref, DerefMut};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RestingPriceConditionalOrder {
time_priority: SequenceNumber,
level_id: LevelId,
inner: PriceConditionalOrder,
}
impl RestingPriceConditionalOrder {
pub fn new(
time_priority: SequenceNumber,
level_id: LevelId,
inner: PriceConditionalOrder,
) -> Self {
Self {
time_priority,
level_id,
inner,
}
}
pub fn time_priority(&self) -> SequenceNumber {
self.time_priority
}
pub(crate) fn update_time_priority(&mut self, new_time_priority: SequenceNumber) {
self.time_priority = new_time_priority;
}
pub fn level_id(&self) -> LevelId {
self.level_id
}
pub(crate) fn update_level_id(&mut self, new_level_id: LevelId) {
self.level_id = new_level_id;
}
pub fn inner(&self) -> &PriceConditionalOrder {
&self.inner
}
pub fn into_inner(self) -> PriceConditionalOrder {
self.inner
}
}
impl Deref for RestingPriceConditionalOrder {
type Target = PriceConditionalOrder;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for RestingPriceConditionalOrder {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PriceConditionalOrder {
price_condition: PriceCondition,
target_order: TriggerOrder,
}
impl PriceConditionalOrder {
pub fn stop_market(trigger_price: Price, order: MarketOrder) -> Self {
Self::new(
PriceCondition::new(trigger_price, TriggerDirection::stop(order.side())),
TriggerOrder::Market(order),
)
}
pub fn stop_limit(trigger_price: Price, order: LimitOrder) -> Self {
Self::new(
PriceCondition::new(trigger_price, TriggerDirection::stop(order.side())),
TriggerOrder::Limit(order),
)
}
pub fn take_profit_market(trigger_price: Price, order: MarketOrder) -> Self {
Self::new(
PriceCondition::new(trigger_price, TriggerDirection::take_profit(order.side())),
TriggerOrder::Market(order),
)
}
pub fn take_profit_limit(trigger_price: Price, order: LimitOrder) -> Self {
Self::new(
PriceCondition::new(trigger_price, TriggerDirection::take_profit(order.side())),
TriggerOrder::Limit(order),
)
}
}
impl PriceConditionalOrder {
pub fn new(price_condition: PriceCondition, target_order: TriggerOrder) -> Self {
Self {
price_condition,
target_order,
}
}
pub fn price_condition(&self) -> PriceCondition {
self.price_condition
}
pub(crate) fn update_price_condition(&mut self, new_price_condition: PriceCondition) {
self.price_condition = new_price_condition;
}
pub fn target_order(&self) -> &TriggerOrder {
&self.target_order
}
pub fn into_target_order(self) -> TriggerOrder {
self.target_order
}
pub(crate) fn update_target_order(&mut self, new_target_order: TriggerOrder) {
self.target_order = new_target_order;
}
pub fn is_expired(&self, timestamp: Timestamp) -> bool {
self.target_order.is_expired(timestamp)
}
pub fn is_ready(&self, price: Price) -> bool {
self.price_condition.is_met(price)
}
}
impl Deref for PriceConditionalOrder {
type Target = PriceCondition;
fn deref(&self) -> &Self::Target {
&self.price_condition
}
}
impl DerefMut for PriceConditionalOrder {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.price_condition
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PriceCondition {
trigger_price: Price,
direction: TriggerDirection,
}
impl PriceCondition {
pub fn new(trigger_price: Price, direction: TriggerDirection) -> Self {
Self {
trigger_price,
direction,
}
}
pub fn trigger_price(&self) -> Price {
self.trigger_price
}
pub fn direction(&self) -> TriggerDirection {
self.direction
}
pub fn is_met(&self, price: Price) -> bool {
match self.direction() {
TriggerDirection::AtOrAbove => price >= self.trigger_price(),
TriggerDirection::AtOrBelow => price <= self.trigger_price(),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TriggerDirection {
AtOrAbove,
AtOrBelow,
}
impl TriggerDirection {
pub fn stop(side: Side) -> Self {
match side {
Side::Buy => Self::AtOrAbove,
Side::Sell => Self::AtOrBelow,
}
}
pub fn take_profit(side: Side) -> Self {
match side {
Side::Buy => Self::AtOrBelow,
Side::Sell => Self::AtOrAbove,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TriggerOrder {
Market(MarketOrder),
Limit(LimitOrder),
}
impl TriggerOrder {
pub fn is_expired(&self, timestamp: Timestamp) -> bool {
match self {
TriggerOrder::Market(_) => false,
TriggerOrder::Limit(order) => order.is_expired(timestamp),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
#[test]
fn test_is_expired() {
let test_ts = 1771180000;
struct Case {
name: &'static str,
order: PriceConditionalOrder,
expected: bool,
}
let cases = [
Case {
name: "market order",
order: PriceConditionalOrder::new(
PriceCondition::new(Price(100), TriggerDirection::AtOrAbove),
TriggerOrder::Market(MarketOrder::new(Quantity(100), Side::Buy, true)),
),
expected: false,
},
Case {
name: "limit order (GTC)",
order: PriceConditionalOrder::new(
PriceCondition::new(Price(100), TriggerDirection::AtOrAbove),
TriggerOrder::Limit(LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(100),
},
OrderFlags::new(Side::Buy, false, TimeInForce::Gtc),
)),
),
expected: false,
},
Case {
name: "limit order (unexpired GTD)",
order: PriceConditionalOrder::new(
PriceCondition::new(Price(100), TriggerDirection::AtOrAbove),
TriggerOrder::Limit(LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(100),
},
OrderFlags::new(
Side::Buy,
false,
TimeInForce::Gtd(Timestamp(test_ts + 1000)),
),
)),
),
expected: false,
},
Case {
name: "limit order (expired GTD)",
order: PriceConditionalOrder::new(
PriceCondition::new(Price(100), TriggerDirection::AtOrAbove),
TriggerOrder::Limit(LimitOrder::new(
Price(100),
QuantityPolicy::Standard {
quantity: Quantity(100),
},
OrderFlags::new(Side::Buy, false, TimeInForce::Gtd(Timestamp(test_ts))),
)),
),
expected: true,
},
];
for case in cases {
assert_eq!(
case.order.is_expired(Timestamp(test_ts)),
case.expected,
"case: {}",
case.name
);
}
}
#[test]
fn test_is_met() {
struct Case {
name: &'static str,
price_condition: PriceCondition,
market_price: Price,
expected: bool,
}
let cases = [
Case {
name: "at or above trigger price",
price_condition: PriceCondition::new(Price(100), TriggerDirection::AtOrAbove),
market_price: Price(100),
expected: true,
},
Case {
name: "at or below trigger price",
price_condition: PriceCondition::new(Price(100), TriggerDirection::AtOrBelow),
market_price: Price(100),
expected: true,
},
Case {
name: "at or above trigger price (not ready)",
price_condition: PriceCondition::new(Price(100), TriggerDirection::AtOrAbove),
market_price: Price(99),
expected: false,
},
Case {
name: "at or below trigger price (not ready)",
price_condition: PriceCondition::new(Price(100), TriggerDirection::AtOrBelow),
market_price: Price(101),
expected: false,
},
];
for case in cases {
assert_eq!(
case.price_condition.is_met(case.market_price),
case.expected,
"case: {}",
case.name
);
}
}
}