sandbox_quant/execution/spot/
planner.rs1use 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}