Skip to main content

sandbox_quant/execution/spot/
planner.rs

1use crate::domain::position::{PositionSnapshot, Side};
2use crate::error::execution_error::ExecutionError;
3use crate::execution::planner::ExecutionPlan;
4
5#[derive(Debug, Default)]
6pub struct SpotExecutionPlanner;
7
8impl SpotExecutionPlanner {
9    pub fn plan_close(&self, position: &PositionSnapshot) -> Result<ExecutionPlan, ExecutionError> {
10        if position.is_flat() {
11            return Err(ExecutionError::NoOpenPosition);
12        }
13
14        let side = match position.side() {
15            Some(Side::Buy) => Side::Sell,
16            Some(Side::Sell) => Side::Buy,
17            None => return Err(ExecutionError::NoOpenPosition),
18        };
19        let qty = position.abs_qty();
20        if qty <= f64::EPSILON {
21            return Err(ExecutionError::CloseQtyTooSmall);
22        }
23
24        Ok(ExecutionPlan {
25            instrument: position.instrument.clone(),
26            side,
27            qty,
28            reduce_only: false,
29        })
30    }
31
32    pub fn plan_target_exposure(
33        &self,
34        position: &PositionSnapshot,
35        current_price: f64,
36        target_notional_usdt: f64,
37    ) -> Result<ExecutionPlan, ExecutionError> {
38        if current_price <= f64::EPSILON {
39            return Err(ExecutionError::MissingPriceContext);
40        }
41
42        let current_qty = position.signed_qty;
43        let target_qty = target_notional_usdt / current_price;
44        let delta_qty = target_qty - current_qty;
45        let side = if delta_qty >= 0.0 {
46            Side::Buy
47        } else {
48            Side::Sell
49        };
50
51        Ok(ExecutionPlan {
52            instrument: position.instrument.clone(),
53            side,
54            qty: delta_qty.abs(),
55            reduce_only: false,
56        })
57    }
58}