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(
10        &self,
11        position: &PositionSnapshot,
12    ) -> Result<ExecutionPlan, ExecutionError> {
13        if position.is_flat() {
14            return Err(ExecutionError::NoOpenPosition);
15        }
16
17        let side = match position.side() {
18            Some(Side::Buy) => Side::Sell,
19            Some(Side::Sell) => Side::Buy,
20            None => return Err(ExecutionError::NoOpenPosition),
21        };
22        let qty = position.abs_qty();
23        if qty <= f64::EPSILON {
24            return Err(ExecutionError::CloseQtyTooSmall);
25        }
26
27        Ok(ExecutionPlan {
28            instrument: position.instrument.clone(),
29            side,
30            qty,
31            reduce_only: false,
32        })
33    }
34
35    pub fn plan_target_exposure(
36        &self,
37        position: &PositionSnapshot,
38        current_price: f64,
39        target_notional_usdt: f64,
40    ) -> Result<ExecutionPlan, ExecutionError> {
41        if current_price <= f64::EPSILON {
42            return Err(ExecutionError::MissingPriceContext);
43        }
44
45        let current_qty = position.signed_qty;
46        let target_qty = target_notional_usdt / current_price;
47        let delta_qty = target_qty - current_qty;
48        let side = if delta_qty >= 0.0 {
49            Side::Buy
50        } else {
51            Side::Sell
52        };
53
54        Ok(ExecutionPlan {
55            instrument: position.instrument.clone(),
56            side,
57            qty: delta_qty.abs(),
58            reduce_only: false,
59        })
60    }
61}