Skip to main content

apple_quant_algorithmic/order_manager/
working.rs

1use apple_quant_core::{AddUnchecked, SubChecked, SubUnchecked};
2
3use smallvec::SmallVec;
4use thiserror::Error;
5use tracing::instrument;
6
7#[allow(unused_imports)]
8use tracing::trace;
9
10use crate::backend::LocalOrderId;
11use crate::order::OrderAction;
12use crate::order::dependency::remote::CancelRemoteOrder;
13use crate::volume::ZeroableExt;
14
15use crate::{
16	backend::{OrderId, OrderIdError, OrdersBackend, RemoteOrderId},
17	order::{
18		DeferredOrderActions, DesiredVolumeOrderError, PartialOrderFill,
19		RemoteTimingCondition,
20	},
21	price::{AbsolutePrice, BidAskPriceSpread},
22	timestamp::{TickTimestamp, Timestamp, Timestamped},
23	volume::{
24		DirectionalExposure, DirectionalIntentVolume, DirectionlessVolume, Zeroable,
25		ZeroableVolume,
26	},
27	instrument::InstrumentSpec,
28};
29
30use super::{CompletedRemoteOrders, OrdersCapacitySpec, PendingRemoteOrder};
31
32#[derive(Debug, Error)]
33pub enum WorkingRemoteOrderSubmitError {
34	#[error("{0:?}")]
35	DesiredVolumeOrder(#[from] DesiredVolumeOrderError),
36
37	#[error("{0:?}")]
38	OrderId(#[from] OrderIdError),
39}
40
41pub struct WorkingRemoteOrder<
42	IS: InstrumentSpec,
43	CS: OrdersCapacitySpec,
44> {
45	pub(crate) local_order_id: LocalOrderId,
46	pub(crate) remote_order_id: RemoteOrderId,
47
48	pub(crate) dependencies: SmallVec<[(
49		RemoteTimingCondition,
50		OrderAction<IS>,
51	); core::direct_const_arg!(CS::DEPENDENCY)]>,
52
53	pub(crate) desired_directional_intent_volume: DirectionalIntentVolume<IS>,
54	pub(crate) booking_timestamp: Timestamp,
55
56	pub(crate) partial_order_fills: SmallVec<[PartialOrderFill<IS>; core::direct_const_arg!(
57		CS::PARTIAL_FILL
58	)]>,
59
60	pub(crate) armed_directional_exposure: Zeroable<DirectionalIntentVolume<IS>>,
61	pub(crate) effective_directional_exposure: Zeroable<DirectionalIntentVolume<IS>>,
62	pub(crate) rest_at: Option<AbsolutePrice<IS>>,
63}
64
65impl<
66	IS: InstrumentSpec,
67	CS: OrdersCapacitySpec,
68> WorkingRemoteOrder<IS, CS> {
69	#[instrument(skip_all)]
70	pub(crate) fn get_fully_filled(
71		&self,
72	) -> Option<BidAskPriceSpread<IS>> {
73		let mut filled_volume = Zeroable::ZERO;
74		let mut iter_partial_order_fills = self.partial_order_fills.iter();
75
76		let Some(
77			mut bid_ask_price_spread,
78		) = iter_partial_order_fills
79			.next()
80			.map(|
81				partial_order_fill,
82			| {
83				filled_volume = filled_volume.add_unchecked(
84					partial_order_fill.directional_intent_volume.directionless_volume.as_zeroable(),
85				);
86
87				BidAskPriceSpread {
88					ask_price: partial_order_fill.price,
89					bid_price: partial_order_fill.price,
90				}
91			}) else {
92			return None;
93		};
94
95		for partial_order_fill in iter_partial_order_fills {
96			filled_volume = filled_volume.add_unchecked(
97				partial_order_fill.directional_intent_volume.directionless_volume.as_zeroable(),
98			);
99
100			if partial_order_fill.price > bid_ask_price_spread.ask_price {
101				bid_ask_price_spread.ask_price = partial_order_fill.price;
102			}
103
104			if partial_order_fill.price < bid_ask_price_spread.bid_price {
105				bid_ask_price_spread.bid_price = partial_order_fill.price;
106			}
107		}
108
109		if (
110			filled_volume <
111			self.desired_directional_intent_volume.directionless_volume.as_zeroable()
112		) {
113			return None;
114		}
115
116		Some(bid_ask_price_spread)
117	}
118
119	#[instrument(skip_all)]
120	pub(crate) fn modify_volume(
121		&mut self,
122		directional_exposure: &mut DirectionalExposure<IS>,
123		volume: &DirectionlessVolume<IS>,
124	) {
125		let Ok(
126			desired_volume_delta,
127		) = self.desired_directional_intent_volume.directionless_volume.sub_checked(
128			*volume,
129		) else {
130			return;
131		};
132
133		let desired_volume_delta = desired_volume_delta.with_directional_intent(
134			self.desired_directional_intent_volume.directional_intent,
135		).as_zeroable();
136
137		directional_exposure.change_armed_directional_exposure(&desired_volume_delta);
138
139		self.desired_directional_intent_volume.directionless_volume = *volume;
140
141		self.armed_directional_exposure = self.armed_directional_exposure.add_unchecked(
142			desired_volume_delta,
143		);
144
145		#[cfg(feature = "log-trace-order-manager")]
146		trace!(
147			"Modifing working order volume. New desired: `{:?}` armed: `{:?}`",
148			self.desired_directional_intent_volume,
149			self.armed_directional_exposure
150		);
151
152		if let Some(
153			directional_intent_volume,
154		) = self.armed_directional_exposure.as_optional_nonzero_ref() {
155			if (
156				directional_intent_volume.directional_intent !=
157				self.desired_directional_intent_volume.directional_intent
158			) {
159				unimplemented!()
160			}
161		} else {
162			unimplemented!()
163		}
164	}
165}
166
167impl<
168	IS: InstrumentSpec,
169	CS: OrdersCapacitySpec,
170> WorkingRemoteOrder<IS, CS> {
171	pub(crate) fn new(
172		local_order_id: LocalOrderId,
173		remote_order_id: RemoteOrderId,
174		dependencies: impl IntoIterator<Item = (
175			RemoteTimingCondition,
176			OrderAction<IS>,
177		)>,
178		desired_directional_intent_volume: DirectionalIntentVolume<IS>,
179		booking_timestamp: Timestamp,
180		rest_at: Option<AbsolutePrice<IS>>,
181	) -> Self {
182		Self {
183			local_order_id,
184			remote_order_id,
185			dependencies: dependencies.into_iter().collect(),
186			desired_directional_intent_volume,
187			booking_timestamp,
188			partial_order_fills: SmallVec::default(),
189			armed_directional_exposure: desired_directional_intent_volume.as_zeroable(),
190			effective_directional_exposure: Zeroable::ZERO,
191			rest_at,
192		}
193	}
194}
195
196/// Unordered list of working remote orders. These include all unfilled resting orders, partially filled resting orders, and market orders that have not been matched. All orders in `working_remote_orders` have been recognized by the matching engine and the client has received that the matching engine knows these orders. Only when the client is aware that the order is 100% filled will it state transition to [`filled_remote_orders`].
197pub struct WorkingRemoteOrders<
198	IS: InstrumentSpec,
199	CS: OrdersCapacitySpec,
200>(SmallVec<[WorkingRemoteOrder<IS, CS>; core::direct_const_arg!(CS::WORKING_REMOTE)]>);
201
202impl<
203	IS: InstrumentSpec,
204	CS: OrdersCapacitySpec,
205> WorkingRemoteOrders<IS, CS> {
206	#[instrument(skip_all)]
207	pub(crate) fn submit<OB: OrdersBackend<IS>>(
208		&mut self,
209		directional_exposure: &mut DirectionalExposure<IS>,
210		deferred_order_actions: &mut DeferredOrderActions<IS>,
211		tick_timestamp: &TickTimestamp,
212		pending_remote_order: PendingRemoteOrder<IS, CS>,
213		remote_order_id: RemoteOrderId,
214	) -> Result<(), WorkingRemoteOrderSubmitError>
215	where
216		IS: Send,
217	{
218		#[cfg(feature = "log-trace-order-manager")]
219		trace!("Adding id: `{:?}`.", remote_order_id);
220
221		let PendingRemoteOrder {
222			submission_timestamp: _,
223			local_order_id,
224			dependencies,
225			desired_directional_intent_volume,
226			rest_at,
227			cancel_on_process,
228		} = pending_remote_order;
229
230		let booking_timestamp = tick_timestamp.timestamp();
231
232		let working_remote_order = WorkingRemoteOrder::new(
233			local_order_id,
234			remote_order_id,
235			dependencies,
236			desired_directional_intent_volume,
237			booking_timestamp,
238			rest_at,
239		);
240
241		directional_exposure.change_armed_directional_exposure(
242			&working_remote_order.armed_directional_exposure,
243		);
244
245		self.0.push(working_remote_order);
246
247		if cancel_on_process {
248			let order_action = OrderAction::CancelRemote(
249				CancelRemoteOrder::new(local_order_id),
250			);
251
252			deferred_order_actions.push(order_action);
253		}
254
255		Ok(())
256	}
257
258	pub fn get(
259		&self,
260		order_id: impl Into<OrderId>,
261	) -> Result<&WorkingRemoteOrder<IS, CS>, OrderIdError> {
262		match &order_id.into() {
263			OrderId::Local(
264				local_order_id,
265			) => self.get_with_local(local_order_id),
266			OrderId::Remote(
267				remote_order_id,
268			) => self.get_with_remote(remote_order_id),
269		}
270	}
271
272	#[instrument(skip_all)]
273	pub fn get_with_local(
274		&self,
275		local_order_id: &LocalOrderId,
276	) -> Result<&WorkingRemoteOrder<IS, CS>, OrderIdError> {
277		let Some(
278			working_remote_order,
279		) = self.0.iter().find(|
280			working_remote_order,
281		| &working_remote_order.local_order_id == local_order_id) else {
282			return local_order_id.err_invalid();
283		};
284
285		Ok(working_remote_order)
286	}
287
288	#[instrument(skip_all)]
289	pub fn get_with_remote(
290		&self,
291		remote_order_id: &RemoteOrderId,
292	) -> Result<&WorkingRemoteOrder<IS, CS>, OrderIdError> {
293		let Some(
294			working_remote_order,
295		) = self.0.iter().find(|
296			working_remote_order,
297		| &working_remote_order.remote_order_id == remote_order_id) else {
298			return remote_order_id.err_invalid();
299		};
300
301		Ok(working_remote_order)
302	}
303
304	pub(crate) fn get_mut(
305		&mut self,
306		order_id: impl Into<OrderId>,
307	) -> Result<&mut WorkingRemoteOrder<IS, CS>, OrderIdError> {
308		match &order_id.into() {
309			OrderId::Local(
310				local_order_id,
311			) => self.get_mut_with_local(local_order_id),
312			OrderId::Remote(
313				remote_order_id,
314			) => self.get_mut_with_remote(remote_order_id),
315		}
316	}
317
318	#[instrument(skip_all)]
319	pub(crate) fn get_mut_with_local(
320		&mut self,
321		local_order_id: &LocalOrderId,
322	) -> Result<&mut WorkingRemoteOrder<IS, CS>, OrderIdError> {
323		let Some(
324			working_remote_order,
325		) = self.0.iter_mut().find(|
326			working_remote_order,
327		| &working_remote_order.local_order_id == local_order_id) else {
328			return local_order_id.err_invalid();
329		};
330
331		Ok(working_remote_order)
332	}
333
334	#[instrument(skip_all)]
335	pub(crate) fn get_mut_with_remote(
336		&mut self,
337		remote_order_id: &RemoteOrderId,
338	) -> Result<&mut WorkingRemoteOrder<IS, CS>, OrderIdError> {
339		let Some(
340			working_remote_order,
341		) = self.0.iter_mut().find(|
342			working_remote_order,
343		| &working_remote_order.remote_order_id == remote_order_id) else {
344			return remote_order_id.err_invalid();
345		};
346
347		Ok(working_remote_order)
348	}
349
350	#[instrument(skip_all)]
351	pub(crate) fn remove(
352		&mut self,
353		directional_exposure: &mut DirectionalExposure<IS>,
354		order_id: impl Into<OrderId>,
355	) -> Result<WorkingRemoteOrder<IS, CS>, OrderIdError>
356	where
357		IS: Send,
358	{
359		let order_id = order_id.into();
360
361		#[cfg(feature = "log-trace-order-manager")]
362		trace!("Removing id: `{:?}`.", order_id);
363
364		let idx = match order_id {
365			OrderId::Local(
366				local_order_id,
367			) => {
368				self.0.iter().position(|
369					working_remote_order,
370				| working_remote_order.local_order_id == local_order_id)
371			},
372			OrderId::Remote(
373				remote_order_id,
374			) => {
375				self.0.iter().position(|
376					working_remote_order,
377				| working_remote_order.remote_order_id == remote_order_id)
378			},
379		};
380
381		let Some(
382			idx,
383		) = idx else {
384			return order_id.err_invalid();
385		};
386
387		let mut working_remote_order = self.0.swap_remove(idx);
388
389		if let Some(
390			armed_directional_exposure,
391		) = working_remote_order.armed_directional_exposure.as_optional_nonzero_ref() {
392			#[cfg(feature = "log-trace-directional-exposure")]
393			trace!(
394				"Reverting armed directional exposure `{:?}` during removing order from working state.",
395				armed_directional_exposure
396			);
397
398			directional_exposure.change_armed_directional_exposure(
399				&armed_directional_exposure.as_flipped().as_zeroable(),
400			);
401		}
402
403		working_remote_order.armed_directional_exposure = Zeroable::ZERO;
404		Ok(working_remote_order)
405	}
406
407	#[instrument(skip_all)]
408	pub(crate) fn processed<OB: OrdersBackend<IS>>(
409		&mut self,
410		remote_order_id: &RemoteOrderId,
411	) -> Result<(), OrderIdError>
412	where
413		IS: Send,
414	{
415		#[cfg(feature = "log-trace-order-manager")]
416		trace!("Processing id: `{:?}`.", remote_order_id);
417
418		let working_remote_order = self.get_mut_with_remote(remote_order_id)?;
419
420		let Some(
421			rest_at,
422		) = &working_remote_order.rest_at else {
423			return Ok(());
424		};
425
426		let bid_ask_price_spread = BidAskPriceSpread {
427			ask_price: *rest_at,
428			bid_price: *rest_at,
429		};
430
431		for (
432			_,
433			remote_order_action,
434		) in working_remote_order.dependencies.iter_mut() {
435			remote_order_action.parent_processed(&bid_ask_price_spread);
436		}
437
438		Ok(())
439	}
440
441	#[instrument(skip_all)]
442	pub(crate) async fn partial_fill<OB: OrdersBackend<IS>>(
443		&mut self,
444		tick_timestamp: &TickTimestamp,
445		completed_remote_orders: &mut CompletedRemoteOrders<IS, CS>,
446		deferred_order_actions: &mut DeferredOrderActions<IS>,
447		directional_exposure: &mut DirectionalExposure<IS>,
448		partial_order_fill: PartialOrderFill<IS>,
449		remote_order_id: &RemoteOrderId,
450	) -> Result<(), OrderIdError>
451	where
452		IS: Send,
453	{
454		#[cfg(feature = "log-trace-order-manager")]
455		trace!(
456			"Partial filling `{partial_order_fill:?}` for id: `{:?}`.",
457			remote_order_id,
458		);
459
460		let working_remote_order = self.get_mut_with_remote(remote_order_id)?;
461
462		working_remote_order.armed_directional_exposure = working_remote_order.armed_directional_exposure.sub_unchecked(
463			partial_order_fill.directional_intent_volume.as_zeroable(),
464		);
465
466		directional_exposure.change_armed_directional_exposure(
467			&partial_order_fill
468				.directional_intent_volume
469				.as_flipped().as_zeroable(),
470		);
471
472		working_remote_order.effective_directional_exposure = working_remote_order.effective_directional_exposure.add_unchecked(
473			partial_order_fill.directional_intent_volume.as_zeroable(),
474		);
475
476		directional_exposure.apply_partial_order_fill(
477			&partial_order_fill,
478		).await;
479
480		working_remote_order.partial_order_fills.push(partial_order_fill);
481
482		for (
483			remote_timing_condition,
484			remote_order_action,
485		) in working_remote_order.dependencies.iter() {
486			if remote_timing_condition != &RemoteTimingCondition::PartialFill {
487				continue;
488			}
489
490			deferred_order_actions.push(remote_order_action.clone());
491		}
492
493		let Some(
494			bid_ask_price_spread,
495		) = working_remote_order.get_fully_filled() else {
496			return Ok(());
497		};
498
499		#[cfg(feature = "log-trace-order-manager")]
500		trace!("Partial fill fully filled working order with spread: `{bid_ask_price_spread:?}`.");
501
502		let mut working_remote_order = self.remove(
503			directional_exposure,
504			remote_order_id,
505		)?;
506
507		if working_remote_order.rest_at.is_none() {
508			for (
509				_,
510				remote_order_action,
511			) in working_remote_order.dependencies.iter_mut() {
512				remote_order_action.parent_processed(&bid_ask_price_spread);
513			}
514		}
515
516		for (
517			remote_timing_condition,
518			remote_order_action,
519		) in working_remote_order.dependencies.iter() {
520			if remote_timing_condition != &RemoteTimingCondition::FullyFilled {
521				continue;
522			}
523
524			deferred_order_actions.push(remote_order_action.clone());
525		}
526
527		completed_remote_orders.submit::<OB>(tick_timestamp, working_remote_order);
528		Ok(())
529	}
530
531	#[instrument(skip_all)]
532	pub(crate) async fn modify_volume<OB: OrdersBackend<IS>>(
533		&mut self,
534		orders_backend: &mut OB,
535		directional_exposure: &mut DirectionalExposure<IS>,
536		local_order_id: &LocalOrderId,
537		volume: &DirectionlessVolume<IS>,
538	) -> Result<(), OrderIdError>
539	where
540		IS: Send,
541	{
542		let working_remote_order = self.get_mut_with_local(local_order_id)?;
543
544		working_remote_order.modify_volume(directional_exposure, volume);
545
546		orders_backend.modify_order_volume(
547			&working_remote_order.remote_order_id,
548			volume,
549		).await;
550
551		Ok(())
552	}
553}
554
555impl<
556	IS: InstrumentSpec,
557	CS: OrdersCapacitySpec,
558> Default for WorkingRemoteOrders<IS, CS> {
559	fn default() -> Self {
560		Self(SmallVec::default())
561	}
562}