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(
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}