Skip to main content

apple_quant_algorithmic/volume/
directional_intent_volume.rs

1use std::{
2	cmp,
3	ops::{Add, AddAssign, Sub, SubAssign},
4};
5
6use apple_quant_core::{UnwrapOptionExt, UnwrapResultExt};
7
8use crate::instrument::InstrumentSpec;
9
10use super::{AggressiveVolume, DirectionalIntent, DirectionlessVolume, RestingVolume, VolumeDelta};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub struct DirectionalIntentVolume<IS: InstrumentSpec> {
14	pub directional_intent: DirectionalIntent,
15	pub directionless_volume: DirectionlessVolume<IS>,
16}
17
18impl<IS: InstrumentSpec> DirectionalIntentVolume<IS> {
19	pub fn as_flipped(&self) -> Self {
20		Self {
21			directional_intent: self
22				.directional_intent
23				.as_flipped(),
24			directionless_volume: self.directionless_volume,
25		}
26	}
27
28	pub fn as_zeroable(&self) -> ZeroableDirectionalIntentVolume<IS> {
29		self.into()
30	}
31}
32
33impl<IS: InstrumentSpec> Add for DirectionalIntentVolume<IS> {
34	type Output = Option<Self>;
35
36	fn add(
37		mut self,
38		mut rhs: Self,
39	) -> Self::Output {
40		if self.directional_intent == rhs.directional_intent {
41			self.directionless_volume += rhs.directionless_volume;
42			return Some(self);
43		}
44
45		// Cancels each other out.
46		if self.directionless_volume == rhs.directionless_volume {
47			return None;
48		}
49
50		// Dominate directional intent gets returned
51		// with delta in their direction.
52		if self.directionless_volume > rhs.directionless_volume {
53			// Safe because of if statements above.
54			let directionless_volume = (self.directionless_volume - rhs.directionless_volume)
55				.unwrap_release_unsafe()
56				.unwrap_release_unsafe();
57
58			self.directionless_volume = directionless_volume;
59			return Some(self);
60		} else {
61			// Safe because of if statements above.
62			let directionless_volume = (rhs.directionless_volume - self.directionless_volume)
63				.unwrap_release_unsafe()
64				.unwrap_release_unsafe();
65
66			rhs.directionless_volume = directionless_volume;
67			return Some(rhs);
68		}
69	}
70}
71
72impl<IS: InstrumentSpec> Sub for DirectionalIntentVolume<IS> {
73	type Output = Option<Self>;
74
75	fn sub(
76		self,
77		mut rhs: Self,
78	) -> Self::Output {
79		rhs.directional_intent.flip();
80		self + rhs
81	}
82}
83
84impl<IS: InstrumentSpec> From<AggressiveVolume<IS>> for DirectionalIntentVolume<IS> {
85	fn from(value: AggressiveVolume<IS>) -> Self {
86		(&value).into()
87	}
88}
89
90impl<IS: InstrumentSpec> From<&AggressiveVolume<IS>> for DirectionalIntentVolume<IS> {
91	fn from(value: &AggressiveVolume<IS>) -> Self {
92		Self {
93			directional_intent: value.aggressor_side().into(),
94			directionless_volume: *value.as_directionless_volume(),
95		}
96	}
97}
98
99impl<IS: InstrumentSpec> From<RestingVolume<IS>> for DirectionalIntentVolume<IS> {
100	fn from(value: RestingVolume<IS>) -> Self {
101		Self {
102			directional_intent: value.resting_side().into(),
103			directionless_volume: *value.as_directionless_volume(),
104		}
105	}
106}
107
108impl<IS: InstrumentSpec> From<VolumeDelta<IS>> for Option<DirectionalIntentVolume<IS>> {
109	fn from(value: VolumeDelta<IS>) -> Self {
110		match value {
111			VolumeDelta::Zero => None,
112			VolumeDelta::AggressiveDelta {
113				directionless_volume,
114				aggressor_side,
115			} => Some(DirectionalIntentVolume {
116				directional_intent: aggressor_side.as_directional_intent(),
117				directionless_volume,
118			}),
119			VolumeDelta::RestingDelta {
120				directionless_volume,
121				resting_side,
122			} => Some(DirectionalIntentVolume {
123				directional_intent: resting_side.as_directional_intent(),
124				directionless_volume,
125			}),
126		}
127	}
128}
129
130impl<IS: InstrumentSpec> PartialOrd for DirectionalIntentVolume<IS> {
131	fn partial_cmp(
132		&self,
133		other: &Self,
134	) -> Option<cmp::Ordering> {
135		Some(self.cmp(other))
136	}
137}
138
139impl<IS: InstrumentSpec> Ord for DirectionalIntentVolume<IS> {
140	fn cmp(
141		&self,
142		other: &Self,
143	) -> cmp::Ordering {
144		match (
145			self.directional_intent,
146			other.directional_intent,
147		) {
148			(DirectionalIntent::Positive, DirectionalIntent::Negative) => cmp::Ordering::Greater,
149			(DirectionalIntent::Negative, DirectionalIntent::Positive) => cmp::Ordering::Less,
150			_ => {
151				if self.directionless_volume == other.directionless_volume {
152					cmp::Ordering::Equal
153				} else if self.directionless_volume > other.directionless_volume {
154					cmp::Ordering::Greater
155				} else {
156					cmp::Ordering::Less
157				}
158			}
159		}
160	}
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
164pub struct ZeroableDirectionalIntentVolume<IS: InstrumentSpec>(Option<DirectionalIntentVolume<IS>>);
165
166impl<IS: InstrumentSpec> ZeroableDirectionalIntentVolume<IS> {
167	pub const ZERO: Self = Self(None);
168
169	pub fn new(directional_intent_volume: Option<DirectionalIntentVolume<IS>>) -> Self {
170		Self(directional_intent_volume)
171	}
172
173	pub fn as_flipped(&self) -> Self {
174		let Some(directional_intent_volume) = &self.0 else {
175			return Self(None);
176		};
177
178		Self(Some(
179			directional_intent_volume.as_flipped(),
180		))
181	}
182
183	pub fn directional_intent_volume(&self) -> &Option<DirectionalIntentVolume<IS>> {
184		&self.0
185	}
186}
187
188impl<IS: InstrumentSpec> From<DirectionalIntentVolume<IS>> for ZeroableDirectionalIntentVolume<IS> {
189	fn from(value: DirectionalIntentVolume<IS>) -> Self {
190		Self(Some(value))
191	}
192}
193
194impl<IS: InstrumentSpec> From<&DirectionalIntentVolume<IS>>
195	for ZeroableDirectionalIntentVolume<IS>
196{
197	fn from(value: &DirectionalIntentVolume<IS>) -> Self {
198		Self(Some(*value))
199	}
200}
201
202impl<IS: InstrumentSpec> From<Option<DirectionalIntentVolume<IS>>>
203	for ZeroableDirectionalIntentVolume<IS>
204{
205	fn from(value: Option<DirectionalIntentVolume<IS>>) -> Self {
206		Self(value)
207	}
208}
209
210impl<IS: InstrumentSpec> From<&Option<DirectionalIntentVolume<IS>>>
211	for ZeroableDirectionalIntentVolume<IS>
212{
213	fn from(value: &Option<DirectionalIntentVolume<IS>>) -> Self {
214		Self(*value)
215	}
216}
217
218impl<IS: InstrumentSpec> Add for ZeroableDirectionalIntentVolume<IS> {
219	type Output = Self;
220
221	fn add(
222		self,
223		rhs: Self,
224	) -> Self::Output {
225		match (self.0, rhs.0) {
226			(Some(self_directional_intent_volume), Some(rhs_directional_intent_volume)) => {
227				(self_directional_intent_volume + rhs_directional_intent_volume).into()
228			}
229			(Some(self_directional_intent_volume), None) => {
230				Some(self_directional_intent_volume).into()
231			}
232			(None, Some(rhs_directional_intent_volume)) => {
233				Some(rhs_directional_intent_volume).into()
234			}
235			(None, None) => None.into(),
236		}
237	}
238}
239
240impl<IS: InstrumentSpec> Sub for ZeroableDirectionalIntentVolume<IS> {
241	type Output = Self;
242
243	fn sub(
244		self,
245		mut rhs: Self,
246	) -> Self::Output {
247		rhs = rhs.as_flipped();
248		self + rhs
249	}
250}
251
252impl<IS: InstrumentSpec> AddAssign for ZeroableDirectionalIntentVolume<IS> {
253	fn add_assign(
254		&mut self,
255		rhs: Self,
256	) {
257		*self = *self + rhs
258	}
259}
260
261impl<IS: InstrumentSpec> SubAssign for ZeroableDirectionalIntentVolume<IS> {
262	fn sub_assign(
263		&mut self,
264		rhs: Self,
265	) {
266		*self = *self - rhs;
267	}
268}
269
270impl<IS: InstrumentSpec> Default for ZeroableDirectionalIntentVolume<IS> {
271	fn default() -> Self {
272		Self(None)
273	}
274}
275
276impl<IS: InstrumentSpec> PartialOrd for ZeroableDirectionalIntentVolume<IS> {
277	fn partial_cmp(
278		&self,
279		other: &Self,
280	) -> Option<cmp::Ordering> {
281		Some(self.cmp(other))
282	}
283}
284
285impl<IS: InstrumentSpec> Ord for ZeroableDirectionalIntentVolume<IS> {
286	fn cmp(
287		&self,
288		other: &Self,
289	) -> cmp::Ordering {
290		match (self, other) {
291			(Self(None), Self(None)) => cmp::Ordering::Equal,
292			(Self(None), Self(Some(other_directional_intent_volume))) => {
293				if other_directional_intent_volume.directional_intent == DirectionalIntent::Positive
294				{
295					cmp::Ordering::Less
296				} else {
297					cmp::Ordering::Greater
298				}
299			}
300			(Self(Some(self_directional_intent_volume)), Self(None)) => {
301				if self_directional_intent_volume.directional_intent == DirectionalIntent::Positive
302				{
303					cmp::Ordering::Greater
304				} else {
305					cmp::Ordering::Less
306				}
307			}
308			(
309				Self(Some(self_directional_intent_volume)),
310				Self(Some(other_directional_intent_volume)),
311			) => self_directional_intent_volume.cmp(other_directional_intent_volume),
312		}
313	}
314}