apple_quant_algorithmic/volume/
directional_intent_volume.rs1use 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 if self.directionless_volume == rhs.directionless_volume {
47 return None;
48 }
49
50 if self.directionless_volume > rhs.directionless_volume {
53 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 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}