Skip to main content

apple_quant_algorithmic/order/dependency/client/
cancel.rs

1#[allow(unused_imports)]
2use apple_quant_core::log::trace;
3
4use apple_quant_core::log::{error, warn, CtxExt};
5
6use tracing::instrument;
7
8use crate::{
9	backend::{LocalOrderId, OrderIdGenerator, OrdersBackend},
10	order::{OrderDependency, RemoteOrderBuilder, StateGoal, TriggerError},
11	order_manager::{OrderManager, OrdersCapacitySpec, PendingClientCancelError},
12	volume::{AggressorSide, DirectionalExposure, DirectionalIntent, ZeroableExt},
13	instrument::InstrumentSpec, points::Subpoints, timestamp::TickTimestamp,
14};
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub struct CancelClientOrder {
18	pub local_order_id: LocalOrderId,
19	pub attempt_exposure_reversion: bool,
20}
21
22impl CancelClientOrder {
23	pub fn new(
24		local_order_id: LocalOrderId,
25		attempt_exposure_reversion: bool,
26	) -> Self {
27		Self {
28			local_order_id,
29			attempt_exposure_reversion,
30		}
31	}
32}
33
34impl<IS: InstrumentSpec> OrderDependency<IS> for CancelClientOrder {
35	// Includes the current implementation of exposure reversion. This is not accurate in all cases
36	// and should only be used for simple bracket order strategies.
37	#[instrument(skip_all)]
38	async fn trigger<
39		OB: OrdersBackend<IS>,
40		CS: OrdersCapacitySpec,
41	>(
42		&self,
43		tick_timestamp: &TickTimestamp,
44		order_manager: &mut OrderManager<IS, CS>,
45		orders_backend: &mut OB,
46		order_id_generator: &mut OrderIdGenerator,
47		directional_exposure: &mut DirectionalExposure<IS>,
48		state_goal: &mut StateGoal,
49	) -> Result<bool, TriggerError>
50	where
51		IS: Send,
52	{
53		#[cfg(feature = "log-trace-order-actions")]
54		trace!("Triggered order action: `{self:#?}`.");
55
56		match order_manager.pending_client_orders.remove(
57			directional_exposure,
58			&self.local_order_id,
59		) {
60			Ok(_) => return Ok(false),
61			Err(
62				error,
63			) => {
64				if !self.attempt_exposure_reversion {
65					return Err(error.into());
66				}
67
68				match error {
69					PendingClientCancelError::OrderId(_) => {},
70					_ => return Err(error.into()),
71				}
72			},
73		}
74
75		let Some(
76			effective_directional_intent_volume,
77		) = directional_exposure.combined().into_optional_nonzero() else {
78			warn!("Exposure reversion trigger failed with 0 combined directional exposure.");
79
80			return Ok(false);
81		};
82
83		// Market aggressive against the current effective exposure.
84		let aggressor_side = match effective_directional_intent_volume.directional_intent {
85			DirectionalIntent::Positive => AggressorSide::Bid,
86			DirectionalIntent::Negative => AggressorSide::Ask,
87		};
88
89		let directionless_volume = effective_directional_intent_volume.directionless_volume;
90
91		warn!(
92			"Exposure reversion triggered. Submitting detached market `{:?}` with `{:?}`.",
93			aggressor_side,
94			directionless_volume
95		);
96
97		let order = match RemoteOrderBuilder::<IS>::new_market_parts(
98			order_id_generator,
99			aggressor_side,
100			directionless_volume,
101		) {
102			Ok(
103				remote_order_builder,
104			) => remote_order_builder,
105			error_result => {
106				error!(
107					"{:?}",
108					error_result.ctx("While constructing exposure reversion market order.")
109				);
110
111				return Ok(false);
112			},
113		};
114
115		let order_action = order.into_condensed().into_order_action::<4>().0;
116
117		Box::pin(order_action.trigger(
118			tick_timestamp,
119			order_manager,
120			orders_backend,
121			order_id_generator,
122			directional_exposure,
123			state_goal,
124		)).await
125	}
126}