1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
use crate::{aggregation_rules::TimestampResolution, AggregationRule, ModularCandle, TakerTrade};
/// The classic time based aggregation rule,
/// creating a new candle every n seconds. The time trigger is aligned such that
/// the trigger points are starting from a time equals zero. For example, if the first
/// tick comes in a 1:32:00 on a 5 minute candle, that first candle will only contain
/// 3 minutes of trades, representing a 1:30 start.
pub struct AlignedTimeRule {
/// If true, the reference timestamp needs to be reset
init: bool,
// The timestamp this rule uses as a reference
reference_timestamp: i64,
// The period for the candle in seconds
// constants can be used nicely here from constants.rs
// e.g.: M1 -> 1 minute candles
period_s: i64,
}
impl AlignedTimeRule {
/// Create a new instance of the aligned time rule,
/// with a given candle period in seconds
///
/// # Arguments:
/// period_s: How many seconds a candle will contain
/// ts_res: The resolution each Trade timestamp will have
///
pub fn new(period_s: i64, ts_res: TimestampResolution) -> Self {
let ts_multiplier = match ts_res {
TimestampResolution::Second => 1,
TimestampResolution::Millisecond => 1_000,
TimestampResolution::Microsecond => 1_000_000,
TimestampResolution::Nanosecond => 1_000_000_000,
};
Self {
init: true,
reference_timestamp: 0,
period_s: period_s * ts_multiplier,
}
}
/// Calculates the "aligned" timestamp, which the rule will use when receiving
/// for determining the trigger. This is done at the initialization of
/// each period.
#[must_use]
pub fn aligned_timestamp(&self, timestamp: i64) -> i64 {
timestamp - (timestamp % self.period_s)
}
}
impl<C, T> AggregationRule<C, T> for AlignedTimeRule
where
C: ModularCandle<T>,
T: TakerTrade,
{
fn should_trigger(&mut self, trade: &T, _candle: &C) -> bool {
if self.init {
self.reference_timestamp = self.aligned_timestamp(trade.timestamp());
self.init = false;
}
let should_trigger = trade.timestamp() - self.reference_timestamp > self.period_s;
if should_trigger {
self.init = true;
}
should_trigger
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
aggregate_all_trades, load_trades_from_csv, plot::OhlcCandle, GenericAggregator, Trade, M15,
};
#[test]
fn aligned_time_rule() {
let trades = load_trades_from_csv("data/Bitmex_XBTUSD_1M.csv").unwrap();
let mut aggregator = GenericAggregator::<OhlcCandle, AlignedTimeRule, Trade>::new(
AlignedTimeRule::new(M15, TimestampResolution::Millisecond),
);
let candles = aggregate_all_trades(&trades, &mut aggregator);
assert_eq!(candles.len(), 396);
// make sure that the aggregator starts a new candle with the "trigger tick",
// and includes that information of the trade that triggered the new candle as well
let c = &candles[0];
assert_eq!(c.open(), 13873.0);
assert_eq!(c.close(), 13768.5);
let c = &candles[1];
assert_eq!(c.open(), 13768.5);
assert_eq!(c.close(), 13722.0);
}
}