apple-quant-algorithmic 0.1.0

Apple Quant's algorithmic trading api
Documentation
use std::range::Range;

use bevy::log::warn;
use smallvec::SmallVec;
use time::Duration;

use crate::{
	aggregation::TradeTradeTimestamp,
	aggregation_std::StdTrades,
	binned::FlexBinnedTimePointData,
	binned_method::BinnedPoint,
	hot::FixedHotData,
	timestamp::{Timestamp, TradeTimestamped},
};

use super::InstrumentSpec;

/// Primary hot storage of securities enabled foundational data used for further aggregation and
/// permanent storage.
pub struct InstrumentData<'instrument_data: 'aggregated_data, 'aggregated_data, IS: InstrumentSpec>
{
	latest_trades_count: usize,

	trades: Box<
		FixedHotData<
			'instrument_data,
			'aggregated_data,
			100,
			IS,
			StdTrades<'instrument_data, IS>,
			TradeTradeTimestamp<IS>,
		>,
	>,

	binned_trades: FlexBinnedTimePointData<
		'instrument_data,
		'aggregated_data,
		IS,
		TradeTradeTimestamp<IS>,
		StdTrades<'instrument_data, IS>,
		const { Duration::HOUR.whole_nanoseconds() as u64 },
	>,

	walk_previous_end_timestamp: Option<Timestamp>,
}

impl<'instrument_data, 'aggregated_data, IS: InstrumentSpec>
	InstrumentData<'instrument_data, 'aggregated_data, IS>
{
	pub fn new_trades(
		&mut self,
		data: impl ExactSizeIterator<Item = TradeTradeTimestamp<IS>>,
	) {
		self.binned_trades
			.insert(data);
	}

	pub fn initialize_walk(
		&mut self,
		start_timestamp: Timestamp,
	) {
		self.walk_previous_end_timestamp = Some(start_timestamp);
		self.trades.clear();
	}

	pub async fn walk<const INTERVAL_NS: u64>(
		&mut self,
		walk_end_timestamp: &Timestamp,
	) -> Option<&Timestamp> {
		let start_timestamp = self
			.walk_previous_end_timestamp
			.unwrap();

		if start_timestamp >= *walk_end_timestamp {
			if start_timestamp > *walk_end_timestamp {
				warn!("Walk attempted when already past end timestamp.");
			}

			return None;
		}

		let mut end_timestamp = start_timestamp + Duration::nanoseconds_i128(INTERVAL_NS as i128);

		end_timestamp = end_timestamp.min(*walk_end_timestamp);
		self.walk_previous_end_timestamp = Some(end_timestamp);

		let Ok(binned_trades) = self
			.binned_trades
			.iter(Range {
				start: &start_timestamp,
				end: &end_timestamp,
			})
		else {
			return self
				.walk_previous_end_timestamp
				.as_ref();
		};

		let latest_trades: SmallVec<[TradeTradeTimestamp<IS>; 100]> = binned_trades
			.cloned()
			.collect();

		self.latest_trades_count = latest_trades
			.len()
			.min(self.trades.capacity());

		self.trades
			.append_manual(latest_trades.into_iter());

		self.walk_previous_end_timestamp
			.as_ref()
	}

	pub fn iter_recent_latest_trades_forward(
		&self
	) -> impl ExactSizeIterator<Item = &TradeTradeTimestamp<IS>> + Clone {
		let count = self
			.latest_trades_count
			.min(self.trades.len());

		debug_assert!(count <= self.trades.as_ref().len());

		let start_idx = self.trades.len() - count;

		self.trades.forward_slice()[start_idx..].into_iter()
	}

	pub fn iter_recent_trades_forward(
		&self,
		count: Option<usize>,
	) -> impl ExactSizeIterator<Item = &TradeTradeTimestamp<IS>> + Clone {
		let count = count
			.unwrap_or(self.trades.len())
			.min(self.trades.len());

		debug_assert!(count <= self.trades.as_ref().len());

		let start_idx = self.trades.len() - count;
		// let start_idx = start_idx
		// 	.checked_sub(1)
		// 	.unwrap();

		self.trades.forward_slice()[start_idx..].into_iter()
	}

	pub fn iter_reaggregate_trade_forward<T: TradeTimestamped>(
		&self,
		aggregation: &T,
	) -> impl Iterator<Item = &TradeTradeTimestamp<IS>> {
		self.reaggregate_trade_forward_slice(aggregation)
			.iter()
	}

	pub fn reaggregate_trade_forward_slice<T: TradeTimestamped>(
		&self,
		aggregation: &T,
	) -> &[TradeTradeTimestamp<IS>] {
		self.trades
			.forward_slice_from(|trade_trade_timestamp| {
				trade_trade_timestamp.trade_timestamp() < aggregation.trade_timestamp()
			})
	}

	pub fn iter_recent_trades_backward(
		&self,
		count: Option<usize>,
	) -> impl Iterator<Item = &TradeTradeTimestamp<IS>> {
		let count = count
			.unwrap_or(self.trades.len())
			.min(self.trades.len());

		debug_assert!(count <= self.trades.as_ref().len());

		self.trades
			.as_ref()
			.iter_rev()
			.take(count)
	}

	pub fn trades_forward_slice_from<P: FnMut(&TradeTradeTimestamp<IS>) -> bool>(
		&self,
		exclude: P,
	) -> &[TradeTradeTimestamp<IS>] {
		self.trades
			.forward_slice_from(exclude)
	}

	pub fn trades_forward_slice(&self) -> &[TradeTradeTimestamp<IS>] {
		self.trades.forward_slice()
	}

	pub fn trades_new_recent_forward_slice(&self) -> &[TradeTradeTimestamp<IS>] {
		let trade_trade_timestamps = self.trades_forward_slice();

		debug_assert!(self.latest_trades_count <= trade_trade_timestamps.len());

		let first_idx = trade_trade_timestamps.len() - self.latest_trades_count;
		&trade_trade_timestamps[first_idx..]
	}
}

impl<'instrument_data: 'aggregated_data, 'aggregated_data, IS: InstrumentSpec> Default
	for InstrumentData<'instrument_data, 'aggregated_data, IS>
{
	fn default() -> Self {
		Self {
			walk_previous_end_timestamp: None,

			trades: Box::new(FixedHotData::default()),
			binned_trades: FlexBinnedTimePointData::default(),

			latest_trades_count: 0,
		}
	}
}