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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use std::fs::File;

use crate::{errors::Result, Aggregator, ModularCandle, TakerTrade, Trade};

/// Determine the candle volume which produces the same number of candles
/// as the given time aggregation equivalent
///
/// # Parameters:
/// `total_volume` - sum of traded volume over entire time period
/// `total_time_days` - total number of days
/// `target_time_minutes` - time aggregated candle period which to target
///
/// # Returns:
/// target candle volume for which volume aggregation produces
/// the same number of candles as the time aggregation did
/// e.g.:
/// 10 days of 1 hour candles -> 240 candles
/// assuming 9840 volume traded over 10 days
/// -> each candle should have 41 volume to produce 240 candles using volume aggregation
pub fn candle_volume_from_time_period(
    total_volume: f64,
    total_time_days: f64,
    target_time_minutes: f64,
) -> f64 {
    let num_candles = total_time_days * 24.0 * (60.0 / target_time_minutes);
    total_volume / num_candles
}

/// Apply an aggregator for all trades at once
///
/// # Arguments:
/// trades: The input trade data to aggregate
/// aggregator: Something that can aggregate
///
/// # Returns:
/// A vector of aggregated candle data
pub fn aggregate_all_trades<A, C, T>(trades: &[T], aggregator: &mut A) -> Vec<C>
where
    A: Aggregator<C, T>,
    C: ModularCandle<T>,
    T: TakerTrade,
{
    let mut out: Vec<C> = vec![];

    for t in trades {
        if let Some(candle) = aggregator.update(t) {
            out.push(candle)
        }
    }

    out
}

/// Load trades from csv file
///
/// # Arguments:
/// filename: The path to the csv file
///
/// # Returns
/// If Ok, A vector of the trades inside the file
pub fn load_trades_from_csv(filename: &str) -> Result<Vec<Trade>> {
    let f = File::open(filename)?;

    let mut r = csv::Reader::from_reader(f);

    let mut out: Vec<Trade> = vec![];
    for record in r.records() {
        let row = record?;

        let ts = row[0].parse::<i64>()?;
        let price = row[1].parse::<f64>()?;
        let size = row[2].parse::<f64>()?;

        // convert to Trade
        let trade = Trade {
            timestamp: ts,
            price,
            size,
        };
        out.push(trade);
    }

    Ok(out)
}

#[cfg(test)]
mod tests {
    use round::round;

    use super::*;

    // TODO: re-enable this test
    /*
    #[test]
    fn test_aggregate_all_trades() {
        let trades = load_trades_from_csv("data/Bitmex_XBTUSD_1M.csv").unwrap();
        let mut aggregator = GenericAggregator::new(100.0, By::Quote);
        let candles = aggregate_all_trades(&trades, &mut aggregator);
        assert!(candles.len() > 0);
    }
    */

    #[test]
    fn test_candle_volume_from_time_period() {
        let total_volume = 100.0;
        let time_days = 10.0;
        let target_time_minutes = 5.0;
        let vol_threshold =
            candle_volume_from_time_period(total_volume, time_days, target_time_minutes);
        assert_eq!(round(vol_threshold, 3), 0.035);

        let total_volume = 100.0;
        let time_days = 10.0;
        let target_time_minutes = 10.0;
        let vol_threshold =
            candle_volume_from_time_period(total_volume, time_days, target_time_minutes);
        assert_eq!(round(vol_threshold, 3), 0.069);

        let total_volume = 200.0;
        let time_days = 10.0;
        let target_time_minutes = 10.0;
        let vol_threshold =
            candle_volume_from_time_period(total_volume, time_days, target_time_minutes);
        assert_eq!(round(vol_threshold, 3), 0.139);

        let total_volume = 50.0;
        let time_days = 10.0;
        let target_time_minutes = 10.0;
        let vol_threshold =
            candle_volume_from_time_period(total_volume, time_days, target_time_minutes);
        assert_eq!(round(vol_threshold, 3), 0.035);

        let total_volume = 100.0;
        let time_days = 5.0;
        let target_time_minutes = 5.0;
        let vol_threshold =
            candle_volume_from_time_period(total_volume, time_days, target_time_minutes);
        assert_eq!(round(vol_threshold, 3), 0.069);

        let total_volume = 100.0;
        let time_days = 5.0;
        let target_time_minutes = 10.0;
        let vol_threshold =
            candle_volume_from_time_period(total_volume, time_days, target_time_minutes);
        assert_eq!(round(vol_threshold, 3), 0.139);
    }
}