use crate::trade::builder::BuilderSetting;
use crate::trade::orders::trigger::{Exit, exit_order, trigger_on};
use crate::trade::orders::{Cloid, Grouping, Order, PlaceOrder, Side, Tpsl};
use crate::trade::ticket::{Amount, Market, Refusal, at_least, routed};
use crate::utils::Dec;
use crate::utils::rounding::MINIMUM_ORDER_VALUE;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Covers {
Size(Amount),
Position,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TriggerIntent {
pub side: Side,
pub size: Covers,
pub tpsl: Tpsl,
pub exit: Exit,
pub cloid: Option<Cloid>,
}
impl TriggerIntent {
#[must_use]
pub const fn new(side: Side, size: Covers, tpsl: Tpsl, exit: Exit) -> TriggerIntent {
TriggerIntent { side, size, tpsl, exit, cloid: None }
}
#[must_use]
pub const fn with_cloid(mut self, cloid: Cloid) -> TriggerIntent {
self.cloid = Some(cloid);
self
}
}
fn on_its_side(side: Side, tpsl: Tpsl, trigger: Dec, entry: Dec) -> bool {
match (side, tpsl) {
(Side::Buy, Tpsl::TakeProfit) | (Side::Sell, Tpsl::StopLoss) => trigger > entry,
(Side::Buy, Tpsl::StopLoss) | (Side::Sell, Tpsl::TakeProfit) => trigger < entry,
}
}
pub(crate) fn attach(
market: &Market,
side: Side,
size: Dec,
entry: Dec,
take_profit: Option<Exit>,
stop_loss: Option<Exit>,
) -> Result<Vec<Order>, Refusal> {
let mut exits = Vec::new();
for (tpsl, exit) in [(Tpsl::TakeProfit, take_profit), (Tpsl::StopLoss, stop_loss)] {
let Some(exit) = exit else { continue };
if !on_its_side(side, tpsl, exit.trigger, entry) {
return Err(Refusal::ExitSide { tpsl, trigger: exit.trigger, entry });
}
at_least(size, trigger_on(market.grid(), exit.trigger)?, MINIMUM_ORDER_VALUE)?;
exits.push(exit_order(market, side.other(), size, tpsl, exit)?);
}
Ok(exits)
}
pub fn trigger(
market: &Market,
intent: &TriggerIntent,
builder: Option<&BuilderSetting>,
) -> Result<PlaceOrder, Refusal> {
if market.is_spot() {
return Err(Refusal::Spot("a take profit or stop loss"));
}
let (size, grouping) = match intent.size {
Covers::Position => (Dec::ZERO, Grouping::PositionTpsl),
Covers::Size(amount) => {
let trigger = trigger_on(market.grid(), intent.exit.trigger)?;
let size = amount.size_on(market.grid(), trigger)?;
at_least(size, trigger, MINIMUM_ORDER_VALUE)?;
(size, Grouping::Na)
}
};
let mut order = exit_order(market, intent.side, size, intent.tpsl, intent.exit)?;
if let Some(cloid) = intent.cloid {
order = order.with_cloid(cloid);
}
routed(PlaceOrder::one(order).grouped(grouping), builder)
}
#[cfg(test)]
#[path = "tests/tpsl.rs"]
mod tests;