apple-quant-algorithmic 0.4.0

Apple Quant's algorithmic library.
Documentation
use std::borrow::Borrow;
use std::ops::Deref;

use apple_quant_core::{
	AddChecked, AddReleaseUnchecked, AddUnchecked, SubChecked, SubReleaseUnchecked,
	SubUnchecked,
};

use num_traits::{Signed, Zero};

use rust_decimal::Decimal;
use thiserror::Error;
use tracing::instrument;

use crate::{
	instrument::{AsDecimal, InstrumentSpec},
	points::{Subpoints, WholePoints},
	volume::{VolumeType, ZeroableVolume},
};

use super::{
	DirectionalIntent, DirectionalIntentVolume, FromVolumeType, FromVolumeTypeError,
	MatchReduceResult, VolumeConstruct,
};

#[derive(Debug, Error)]
pub enum DirectionlessVolumeInvalidError {
	#[error("Invalid negative value: {0}.")]
	Negative(Decimal),

	#[error("Invalid zero value.")]
	Zero,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DirectionlessVolume<IS: InstrumentSpec>(IS::VolumeType);

impl<IS: InstrumentSpec> DirectionlessVolume<IS> {
	pub fn as_volume_type(
		&self,
	) -> IS::VolumeType {
		debug_assert!(self.0.is_positive() || self.0.is_zero());
		self.0
	}

	#[instrument(skip_all)]
	pub fn match_reduce(
		self,
		matcher: &Self,
	) -> MatchReduceResult<Self> {
		let self_volume_type = self.as_volume_type();
		let matcher_volume_type = matcher.as_volume_type();
		let self_delta = self_volume_type - matcher_volume_type;

		if self_delta.is_zero() {
			return MatchReduceResult {
				new_matched: None,
				new_matcher: None,
				matched_quantity: self_volume_type.into(),
			};
		}

		if self_delta.is_positive() {
			return MatchReduceResult {
				new_matched: Some(Self::new_unchecked_release(self_delta)),
				new_matcher: None,
				matched_quantity: matcher_volume_type.into(),
			};
		}

		// Subtracting the reverse was negative, so this must be positive.
		let new_matcher = matcher_volume_type - self_volume_type;
		let new_matcher = Self::new_unchecked_release(new_matcher);

		MatchReduceResult {
			new_matched: None,
			new_matcher: Some(new_matcher),
			matched_quantity: self_volume_type.into(),
		}
	}

	pub fn with_directional_intent(
		self,
		directional_intent: DirectionalIntent,
	) -> DirectionalIntentVolume<IS> {
		DirectionalIntentVolume::new_unchecked_release((directional_intent, self))
	}
}

impl<IS: InstrumentSpec> VolumeConstruct<
	IS::VolumeType,
	DirectionlessVolumeInvalidError,
> for DirectionlessVolume<IS> {
	fn new_checked(
		t: impl Into<IS::VolumeType>,
	) -> Result<Self, DirectionlessVolumeInvalidError>
	where
		Self: Sized,
	{
		let t = t.into();

		if t.is_zero() {
			return Err(DirectionlessVolumeInvalidError::Zero);
		}

		if t.is_negative() {
			return Err(DirectionlessVolumeInvalidError::Negative(t.as_decimal()));
		}

		Ok(Self::new_unchecked(t))
	}

	fn new_unchecked(
		t: impl Into<IS::VolumeType>,
	) -> Self {
		let volume_type = t.into();
		debug_assert!(volume_type.is_positive() || volume_type.is_zero());
		Self(volume_type)
	}
}

impl<IS: InstrumentSpec> FromVolumeType<IS> for DirectionlessVolume<IS> {
	fn from_volume_type_checked(
		volume_type: impl Into<IS::VolumeType>,
	) -> Result<Self, FromVolumeTypeError>
	where
		Self: Sized,
	{
		let volume_type = volume_type.into();

		Self::new_checked(volume_type).map_err(|_| {
			FromVolumeTypeError::new(volume_type.as_decimal())
		})
	}

	fn from_volume_type_unchecked(
		volume_type: impl Into<IS::VolumeType>,
	) -> Self {
		Self::new_unchecked(volume_type)
	}
}

impl<IS: InstrumentSpec> ZeroableVolume for DirectionlessVolume<IS> {
	const ZERO: Self = Self(IS::VolumeType::ZERO);

	fn is_zero(
		&self,
	) -> bool {
		*self == Self::ZERO
	}
}

impl<IS: InstrumentSpec> Deref for DirectionlessVolume<IS> {
	type Target = IS::VolumeType;

	fn deref(
		&self,
	) -> &Self::Target {
		&self.0
	}
}

impl<IS: InstrumentSpec> From<WholePoints> for DirectionlessVolume<IS> {
	fn from(
		value: WholePoints,
	) -> Self {
		Self::new_unchecked_release(value.into_subpoints::<IS::VolumeSpec>())
	}
}

impl<IS: InstrumentSpec> From<&WholePoints> for DirectionlessVolume<IS> {
	fn from(
		value: &WholePoints,
	) -> Self {
		(*value).into()
	}
}

impl<IS: InstrumentSpec> From<Subpoints> for DirectionlessVolume<IS> {
	fn from(
		value: Subpoints,
	) -> Self {
		Self::new_unchecked_release(value)
	}
}

impl<IS: InstrumentSpec> From<&Subpoints> for DirectionlessVolume<IS> {
	fn from(
		value: &Subpoints,
	) -> Self {
		(*value).into()
	}
}

impl<IS: InstrumentSpec> From<DirectionlessVolume<IS>> for Result<
	DirectionlessVolume<IS>,
	FromVolumeTypeError,
> {
	fn from(
		value: DirectionlessVolume<IS>,
	) -> Self {
		Ok(value)
	}
}

impl_volume_binop!(
	DirectionlessVolume,
	AddReleaseUnchecked,
	AddChecked,
	add_checked,
	AddUnchecked,
	add_unchecked,
	|lhs: DirectionlessVolume<IS>, rhs: DirectionlessVolume<IS>| { let volume_type =
	lhs.0 + rhs.0; DirectionlessVolume::<IS>::from_volume_type_checked(volume_type) },
	|lhs: DirectionlessVolume<IS>, rhs: DirectionlessVolume<IS>| { let volume_type =
	lhs.0 + rhs.0; DirectionlessVolume::<IS>::from_volume_type_unchecked(volume_type) },
	FromVolumeTypeError,
	IS,
	InstrumentSpec
);

impl_volume_binop!(
	DirectionlessVolume,
	SubReleaseUnchecked,
	SubChecked,
	sub_checked,
	SubUnchecked,
	sub_unchecked,
	|lhs: DirectionlessVolume<IS>, rhs: DirectionlessVolume<IS>| { let volume_type =
	lhs.0 - rhs.0; DirectionlessVolume::<IS>::from_volume_type_checked(volume_type) },
	|lhs: DirectionlessVolume<IS>, rhs: DirectionlessVolume<IS>| { let volume_type =
	lhs.0 - rhs.0; DirectionlessVolume::<IS>::from_volume_type_unchecked(volume_type) },
	FromVolumeTypeError,
	IS,
	InstrumentSpec
);