Skip to main content

apple_quant_algorithmic/order_manager/
client_pending.rs

1use smallvec::SmallVec;
2use thiserror::Error;
3use tracing::instrument;
4
5#[allow(unused_imports)]
6use tracing::trace;
7
8use crate::{
9	backend::{LocalOrderId, OrderIdError, OrdersBackend},
10	order::{
11		dependency::client::ClientDeployedRemoteDependency, ClientOrder,
12		ClientOrderTracker, ClientTimingCondition, DeferredOrderActions,
13		DesiredVolumeOrder, DesiredVolumeOrderError, MatchableOrder, OrderAction,
14		TriggerError,
15	},
16	timestamp::{TickTimestamp, Timestamp, Timestamped},
17	volume::{DirectionalExposure, ZeroableVolume},
18	aggregation::TradeTradeTimestamp, instrument::InstrumentSpec,
19	liquidity::LiquidityEstimation,
20};
21
22use super::{OrdersCapacitySpec, PendingRemoteSubmitError};
23
24#[derive(Debug, Error)]
25pub enum PendingClientCancelError {
26	#[error("{0:?}")]
27	OrderId(#[from] OrderIdError),
28
29	#[error("{0:?}")]
30	DesiredVolumeOrder(#[from] DesiredVolumeOrderError),
31}
32
33#[derive(Debug, Error)]
34pub enum PendingClientActivateError {
35	#[error("{0:?}")]
36	PendingClientCancel(#[from] PendingClientCancelError),
37
38	#[error("{0:?}")]
39	PendingRemoteSubmit(#[from] PendingRemoteSubmitError),
40
41	#[error("{0:?}")]
42	Trigger(#[from] TriggerError),
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Hash)]
46pub struct PendingClientOrder<IS: InstrumentSpec> {
47	pub(crate) submission_timestamp: Timestamp,
48	pub(crate) local_order_id: LocalOrderId,
49	pub(crate) client_deployed_remote_dependency: ClientDeployedRemoteDependency<IS>,
50	pub(crate) client_order: ClientOrder<IS>,
51	pub(crate) liquidity_estimation: LiquidityEstimation<IS>,
52
53	pub(crate) dependencies: Vec<(
54		ClientTimingCondition,
55		OrderAction<IS>,
56	)>,
57}
58
59impl<IS: InstrumentSpec> PendingClientOrder<IS> {
60	pub(crate) fn new(
61		submission_timestamp: Timestamp,
62		local_order_id: LocalOrderId,
63		client_deployed_remote_dependency: ClientDeployedRemoteDependency<IS>,
64		client_order: ClientOrder<IS>,
65		dependencies: Vec<(
66			ClientTimingCondition,
67			OrderAction<IS>,
68		)>,
69	) -> Self {
70		Self {
71			submission_timestamp,
72			local_order_id,
73			client_deployed_remote_dependency,
74			client_order,
75			liquidity_estimation: LiquidityEstimation::default(),
76			dependencies,
77		}
78	}
79}
80
81/// Unordered list of pending client orders (eg. stop orders), where only this client knows the order and its intent. When the condition is tripped, it will be converted into a pending remote order, the state transition will take place, and the [`OrdersBackend`] used will become aware of the order.
82pub struct PendingClientOrders<
83	IS: InstrumentSpec,
84	CS: OrdersCapacitySpec,
85>(SmallVec<[PendingClientOrder<IS>; core::direct_const_arg!(CS::PENDING_CLIENT)]>);
86
87impl<
88	IS: InstrumentSpec,
89	CS: OrdersCapacitySpec,
90> PendingClientOrders<IS, CS> {
91	#[instrument(skip_all)]
92	pub fn submit<OB: OrdersBackend<IS>>(
93		&mut self,
94		tick_timestamp: &TickTimestamp,
95		directional_exposure: &mut DirectionalExposure<IS>,
96		client_order_tracker: &ClientOrderTracker,
97		client_order: ClientOrder<IS>,
98		client_deployed_remote_dependency: ClientDeployedRemoteDependency<IS>,
99		dependencies: Vec<(
100			ClientTimingCondition,
101			OrderAction<IS>,
102		)>,
103	) -> Result<(), DesiredVolumeOrderError>
104	where
105		IS: Send,
106	{
107		let submission_timestamp = tick_timestamp.timestamp();
108		let local_order_id = *client_order_tracker.as_local_order_id();
109
110		#[cfg(feature = "log-trace-order-manager")]
111		trace!(
112			"Adding id: `{:?}` order: `{:?}`.",
113			local_order_id,
114			client_order
115		);
116
117		let desired_directional_intent_volume = client_deployed_remote_dependency.remote_order.desired_directional_intent_volume()?;
118
119		let pending_client_order = PendingClientOrder::new(
120			submission_timestamp,
121			local_order_id,
122			client_deployed_remote_dependency,
123			client_order,
124			dependencies,
125		);
126
127		self.0.push(pending_client_order);
128
129		directional_exposure.change_armed_directional_exposure(
130			&desired_directional_intent_volume.as_zeroable(),
131		);
132
133		Ok(())
134	}
135
136	#[instrument(skip_all)]
137	pub fn get(
138		&self,
139		local_order_id: &LocalOrderId,
140	) -> Result<&PendingClientOrder<IS>, OrderIdError> {
141		let Some(
142			pending_client_order,
143		) = self.0.iter().find(|
144			pending_client_order,
145		| &pending_client_order.local_order_id == local_order_id) else {
146			return local_order_id.err_invalid();
147		};
148
149		Ok(pending_client_order)
150	}
151
152	#[instrument(skip_all)]
153	pub(crate) fn get_mut(
154		&mut self,
155		local_order_id: &LocalOrderId,
156	) -> Result<&mut PendingClientOrder<IS>, OrderIdError> {
157		let Some(
158			pending_client_order,
159		) = self.0.iter_mut().find(|
160			pending_client_order,
161		| &pending_client_order.local_order_id == local_order_id) else {
162			return local_order_id.err_invalid();
163		};
164
165		Ok(pending_client_order)
166	}
167
168	#[instrument(skip_all)]
169	pub(crate) fn remove(
170		&mut self,
171		directional_exposure: &mut DirectionalExposure<IS>,
172		local_order_id: &LocalOrderId,
173	) -> Result<PendingClientOrder<IS>, PendingClientCancelError> {
174		#[cfg(feature = "log-trace-order-manager")]
175		trace!("Removing id: `{:?}`.", local_order_id);
176
177		let Some(
178			idx,
179		) = self.0.iter().position(|
180			pending_client_order,
181		| &pending_client_order.local_order_id == local_order_id) else {
182			return local_order_id.err_invalid();
183		};
184
185		let pending_client_order = self.0.swap_remove(idx);
186
187		let desired_directional_intent_volume = pending_client_order.client_deployed_remote_dependency.remote_order.desired_directional_intent_volume()?;
188
189		directional_exposure.change_armed_directional_exposure(
190			&desired_directional_intent_volume.as_flipped().as_zeroable(),
191		);
192
193		Ok(pending_client_order)
194	}
195
196	#[instrument(skip_all)]
197	pub(crate) async fn activate<OB: OrdersBackend<IS>>(
198		&mut self,
199		tick_timestamp: &TickTimestamp,
200		directional_exposure: &mut DirectionalExposure<IS>,
201		deferred_order_actions: &mut DeferredOrderActions<IS>,
202		local_order_id: &LocalOrderId,
203	) -> Result<(), PendingClientActivateError>
204	where
205		IS: Send,
206	{
207		#[cfg(feature = "log-trace-order-manager")]
208		trace!("Activating id: `{:?}`.", local_order_id);
209
210		let pending_client_order = self.remove(directional_exposure, local_order_id)?;
211
212		for (
213			client_timing_condition,
214			order_action,
215		) in pending_client_order.dependencies.iter() {
216			if client_timing_condition != &ClientTimingCondition::Activated {
217				continue;
218			}
219
220			deferred_order_actions.push(order_action.clone());
221		}
222
223		deferred_order_actions.push(
224			pending_client_order.client_deployed_remote_dependency.into_order_action(),
225		);
226
227		Ok(())
228	}
229
230	#[instrument(skip_all)]
231	pub fn tick_client_orders<'a>(
232		&mut self,
233		deferred_order_actions: &mut DeferredOrderActions<IS>,
234		directional_exposure: &mut DirectionalExposure<IS>,
235		just_added: impl ExactSizeIterator<Item = &'a TradeTradeTimestamp<IS>> + Clone,
236	)
237	where
238		IS: 'a,
239	{
240		let pending_client_orders = self.0.drain_filter(|
241			pending_client_order,
242		| {
243			trades_stream_drain_filter(
244				pending_client_order,
245				directional_exposure,
246				just_added.clone(),
247			)
248		});
249
250		for pending_client_order in pending_client_orders {
251			for (
252				client_timing_condition,
253				order_action,
254			) in pending_client_order.dependencies.iter() {
255				if client_timing_condition != &ClientTimingCondition::Activated {
256					continue;
257				}
258
259				deferred_order_actions.push(order_action.clone());
260			}
261
262			deferred_order_actions.push(
263				pending_client_order.client_deployed_remote_dependency.into_order_action(),
264			);
265		}
266	}
267}
268
269impl<
270	IS: InstrumentSpec,
271	CS: OrdersCapacitySpec,
272> Default for PendingClientOrders<IS, CS> {
273	fn default() -> Self {
274		Self(SmallVec::default())
275	}
276}
277
278impl<
279	IS: InstrumentSpec,
280	CS: OrdersCapacitySpec,
281> std::fmt::Debug for PendingClientOrders<IS, CS> {
282	fn fmt(
283		&self,
284		f: &mut std::fmt::Formatter<'_>,
285	) -> std::fmt::Result {
286		self.0.fmt(f)
287	}
288}
289
290#[instrument(skip_all)]
291fn trades_stream_drain_filter<
292	'a,
293	IS: InstrumentSpec,
294>(
295	pending_client_order: &mut PendingClientOrder<IS>,
296	directional_exposure: &mut DirectionalExposure<IS>,
297	just_added: impl ExactSizeIterator<Item = &'a TradeTradeTimestamp<IS>> + Clone,
298) -> bool
299where
300	IS: 'a,
301{
302	pending_client_order.liquidity_estimation.walk_trades(just_added);
303
304	let is_liquidable = pending_client_order.client_order.is_liquidable(
305		&pending_client_order.liquidity_estimation,
306	).is_some();
307
308	if is_liquidable {
309		#[cfg(feature = "log-trace-order-manager")]
310		trace!(
311			"Activating client order `{:?}`",
312			pending_client_order.local_order_id
313		);
314
315		let delta_directional_exposure = pending_client_order
316			.client_deployed_remote_dependency
317			.remote_order
318			.desired_directional_intent_volume()
319			.unwrap()
320			.as_flipped().as_zeroable();
321
322		directional_exposure.change_armed_directional_exposure(
323			&delta_directional_exposure,
324		);
325	}
326
327	is_liquidable
328}