Skip to main content

apple_quant_algorithmic/volume/
directionless.rs

1use std::borrow::Borrow;
2use std::ops::Deref;
3
4use apple_quant_core::{
5	AddChecked, AddReleaseUnchecked, AddUnchecked, SubChecked, SubReleaseUnchecked,
6	SubUnchecked,
7};
8
9use num_traits::{Signed, Zero};
10
11use rust_decimal::Decimal;
12use thiserror::Error;
13use tracing::instrument;
14
15use crate::{
16	instrument::{AsDecimal, InstrumentSpec},
17	points::{Subpoints, WholePoints},
18	volume::{VolumeType, ZeroableVolume},
19};
20
21use super::{
22	DirectionalIntent, DirectionalIntentVolume, FromVolumeType, FromVolumeTypeError,
23	MatchReduceResult, VolumeConstruct,
24};
25
26#[derive(Debug, Error)]
27pub enum DirectionlessVolumeInvalidError {
28	#[error("Invalid negative value: {0}.")]
29	Negative(Decimal),
30
31	#[error("Invalid zero value.")]
32	Zero,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct DirectionlessVolume<IS: InstrumentSpec>(IS::VolumeType);
37
38impl<IS: InstrumentSpec> DirectionlessVolume<IS> {
39	pub fn as_volume_type(
40		&self,
41	) -> IS::VolumeType {
42		debug_assert!(self.0.is_positive() || self.0.is_zero());
43		self.0
44	}
45
46	#[instrument(skip_all)]
47	pub fn match_reduce(
48		self,
49		matcher: &Self,
50	) -> MatchReduceResult<Self> {
51		let self_volume_type = self.as_volume_type();
52		let matcher_volume_type = matcher.as_volume_type();
53		let self_delta = self_volume_type - matcher_volume_type;
54
55		if self_delta.is_zero() {
56			return MatchReduceResult {
57				new_matched: None,
58				new_matcher: None,
59				matched_quantity: self_volume_type.into(),
60			};
61		}
62
63		if self_delta.is_positive() {
64			return MatchReduceResult {
65				new_matched: Some(Self::new_unchecked_release(self_delta)),
66				new_matcher: None,
67				matched_quantity: matcher_volume_type.into(),
68			};
69		}
70
71		// Subtracting the reverse was negative, so this must be positive.
72		let new_matcher = matcher_volume_type - self_volume_type;
73		let new_matcher = Self::new_unchecked_release(new_matcher);
74
75		MatchReduceResult {
76			new_matched: None,
77			new_matcher: Some(new_matcher),
78			matched_quantity: self_volume_type.into(),
79		}
80	}
81
82	pub fn with_directional_intent(
83		self,
84		directional_intent: DirectionalIntent,
85	) -> DirectionalIntentVolume<IS> {
86		DirectionalIntentVolume::new_unchecked_release((directional_intent, self))
87	}
88}
89
90impl<IS: InstrumentSpec> VolumeConstruct<
91	IS::VolumeType,
92	DirectionlessVolumeInvalidError,
93> for DirectionlessVolume<IS> {
94	fn new_checked(
95		t: impl Into<IS::VolumeType>,
96	) -> Result<Self, DirectionlessVolumeInvalidError>
97	where
98		Self: Sized,
99	{
100		let t = t.into();
101
102		if t.is_zero() {
103			return Err(DirectionlessVolumeInvalidError::Zero);
104		}
105
106		if t.is_negative() {
107			return Err(DirectionlessVolumeInvalidError::Negative(t.as_decimal()));
108		}
109
110		Ok(Self::new_unchecked(t))
111	}
112
113	fn new_unchecked(
114		t: impl Into<IS::VolumeType>,
115	) -> Self {
116		let volume_type = t.into();
117		debug_assert!(volume_type.is_positive() || volume_type.is_zero());
118		Self(volume_type)
119	}
120}
121
122impl<IS: InstrumentSpec> FromVolumeType<IS> for DirectionlessVolume<IS> {
123	fn from_volume_type_checked(
124		volume_type: impl Into<IS::VolumeType>,
125	) -> Result<Self, FromVolumeTypeError>
126	where
127		Self: Sized,
128	{
129		let volume_type = volume_type.into();
130
131		Self::new_checked(volume_type).map_err(|_| {
132			FromVolumeTypeError::new(volume_type.as_decimal())
133		})
134	}
135
136	fn from_volume_type_unchecked(
137		volume_type: impl Into<IS::VolumeType>,
138	) -> Self {
139		Self::new_unchecked(volume_type)
140	}
141}
142
143impl<IS: InstrumentSpec> ZeroableVolume for DirectionlessVolume<IS> {
144	const ZERO: Self = Self(IS::VolumeType::ZERO);
145
146	fn is_zero(
147		&self,
148	) -> bool {
149		*self == Self::ZERO
150	}
151}
152
153impl<IS: InstrumentSpec> Deref for DirectionlessVolume<IS> {
154	type Target = IS::VolumeType;
155
156	fn deref(
157		&self,
158	) -> &Self::Target {
159		&self.0
160	}
161}
162
163impl<IS: InstrumentSpec> From<WholePoints> for DirectionlessVolume<IS> {
164	fn from(
165		value: WholePoints,
166	) -> Self {
167		Self::new_unchecked_release(value.into_subpoints::<IS::VolumeSpec>())
168	}
169}
170
171impl<IS: InstrumentSpec> From<&WholePoints> for DirectionlessVolume<IS> {
172	fn from(
173		value: &WholePoints,
174	) -> Self {
175		(*value).into()
176	}
177}
178
179impl<IS: InstrumentSpec> From<Subpoints> for DirectionlessVolume<IS> {
180	fn from(
181		value: Subpoints,
182	) -> Self {
183		Self::new_unchecked_release(value)
184	}
185}
186
187impl<IS: InstrumentSpec> From<&Subpoints> for DirectionlessVolume<IS> {
188	fn from(
189		value: &Subpoints,
190	) -> Self {
191		(*value).into()
192	}
193}
194
195impl<IS: InstrumentSpec> From<DirectionlessVolume<IS>> for Result<
196	DirectionlessVolume<IS>,
197	FromVolumeTypeError,
198> {
199	fn from(
200		value: DirectionlessVolume<IS>,
201	) -> Self {
202		Ok(value)
203	}
204}
205
206impl_volume_binop!(
207	DirectionlessVolume,
208	AddReleaseUnchecked,
209	AddChecked,
210	add_checked,
211	AddUnchecked,
212	add_unchecked,
213	|lhs: DirectionlessVolume<IS>, rhs: DirectionlessVolume<IS>| { let volume_type =
214	lhs.0 + rhs.0; DirectionlessVolume::<IS>::from_volume_type_checked(volume_type) },
215	|lhs: DirectionlessVolume<IS>, rhs: DirectionlessVolume<IS>| { let volume_type =
216	lhs.0 + rhs.0; DirectionlessVolume::<IS>::from_volume_type_unchecked(volume_type) },
217	FromVolumeTypeError,
218	IS,
219	InstrumentSpec
220);
221
222impl_volume_binop!(
223	DirectionlessVolume,
224	SubReleaseUnchecked,
225	SubChecked,
226	sub_checked,
227	SubUnchecked,
228	sub_unchecked,
229	|lhs: DirectionlessVolume<IS>, rhs: DirectionlessVolume<IS>| { let volume_type =
230	lhs.0 - rhs.0; DirectionlessVolume::<IS>::from_volume_type_checked(volume_type) },
231	|lhs: DirectionlessVolume<IS>, rhs: DirectionlessVolume<IS>| { let volume_type =
232	lhs.0 - rhs.0; DirectionlessVolume::<IS>::from_volume_type_unchecked(volume_type) },
233	FromVolumeTypeError,
234	IS,
235	InstrumentSpec
236);