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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
#![deny(missing_docs, missing_crate_level_docs)]
#[macro_use]
extern crate serde;
use chrono::naive::NaiveDateTime;
use std::fs::File;
mod time_aggregator;
mod volume_aggregator;
mod modular_volume_aggregator;
mod modules;
mod welford_online;
pub use time_aggregator::TimeAggregator;
pub use volume_aggregator::VolumeAggregator;
pub use modular_volume_aggregator::ModularVolumeAggregator;
pub use modules::{FeatureModules, ModularCandle};
pub const M1: i64 = 60;
pub const M5: i64 = 300;
pub const M15: i64 = 900;
pub const M30: i64 = 1800;
pub const H1: i64 = 3600;
pub const H2: i64 = 7200;
pub const H4: i64 = 14400;
pub const H8: i64 = 28800;
pub const H12: i64 = 43200;
pub const D1: i64 = 86400;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Trade {
pub timestamp: i64,
pub price: f64,
pub size: f64,
}
#[derive(Debug, Clone)]
pub struct Candle {
pub timestamp: i64,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
pub directional_trade_ratio: f64,
pub directional_volume_ratio: f64,
pub num_trades: i32,
pub arithmetic_mean_price: f64,
pub weighted_price: f64,
pub std_dev_prices: f64,
pub std_dev_sizes: f64,
pub time_velocity: f64,
}
impl std::fmt::Display for Candle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(ts: {:?}, o: {:.2}, h: {:.2}, l: {:.2}, c: {:.2}, wp: {:.2}, v: {:.2}, dtr: {:.2}, dvr: {:.2}, #t: {}, σ_price: {:.2}, σ_size: {:.2}, tv: {:.4})",
NaiveDateTime::from_timestamp(self.timestamp / 1000, (self.timestamp % 1000) as u32),
self.open,
self.high,
self.low,
self.close,
self.weighted_price,
self.volume,
self.directional_trade_ratio,
self.directional_volume_ratio,
self.num_trades,
self.std_dev_prices,
self.std_dev_sizes,
self.time_velocity,
)
}
}
#[derive(Debug, Clone, Copy)]
pub enum By {
Base,
Quote,
}
pub trait Aggregator {
fn update(&mut self, trade: &Trade) -> Option<Candle>;
}
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
}
pub fn aggregate_all_trades(
trades: &Vec<Trade>,
aggregator: &mut impl Aggregator
) -> Vec<Candle> {
let mut out: Vec<Candle> = vec![];
for t in trades {
match aggregator.update(t) {
Some(candle) => out.push(candle),
None => {}
}
}
out
}
pub fn load_trades_from_csv(filename: &str) -> Vec<Trade> {
let f = File::open(filename).unwrap();
let mut r = csv::Reader::from_reader(f);
let mut out: Vec<Trade> = vec![];
for record in r.records() {
let row = record.unwrap();
let ts = row[0].parse::<i64>().unwrap();
let price = row[1].parse::<f64>().unwrap();
let size = row[2].parse::<f64>().unwrap();
let trade = Trade{
timestamp: ts,
price,
size,
};
out.push(trade);
};
return out
}
#[cfg(test)]
mod tests {
use super::*;
use round::round;
use crate::By;
pub fn test_candle(candle: &Candle) {
assert!(candle.open <= candle.high);
assert!(candle.open >= candle.low);
assert!(candle.high >= candle.low);
assert!(candle.close <= candle.high);
assert!(candle.close >= candle.low);
assert!(candle.volume > 0.0);
assert!(candle.weighted_price <= candle.high);
assert!(candle.weighted_price >= candle.low);
assert!(candle.timestamp > 0);
assert!(candle.directional_volume_ratio <= 1.0);
assert!(candle.directional_volume_ratio >= 0.0);
assert!(candle.directional_trade_ratio <= 1.0);
assert!(candle.directional_trade_ratio >= 0.0);
assert!(candle.num_trades > 0);
}
#[test]
fn test_aggregate_all_trades() {
let trades = load_trades_from_csv("data/Bitmex_XBTUSD_1M.csv");
let mut aggregator = VolumeAggregator::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);
}
#[test]
fn candle_display() {
let c = Candle {
timestamp: 1591889593548,
open: 9565.0,
high: 9566.5,
low: 9555.0,
close: 9555.0,
volume: 6.500301656683413,
directional_volume_ratio: 0.042005987543157944,
directional_trade_ratio: 0.0,
num_trades: 58,
arithmetic_mean_price: 9556.0,
weighted_price: 9556.479572933373,
std_dev_prices: 3.953000116537345,
std_dev_sizes: 6565.830432012996,
time_velocity: 0.1,
};
println!("c: {}", c);
}
}