pub mod generators;
use crate::FixedPoint;
use crate::trade::Tick;
use crate::types::{DataSource, OpenDeviationBar};
pub fn create_test_agg_trade(id: i64, price: &str, volume: &str, timestamp: i64) -> Tick {
Tick {
ref_id: id,
price: FixedPoint::from_str(price).unwrap(),
volume: FixedPoint::from_str(volume).unwrap(),
first_sub_id: id * 10,
last_sub_id: id * 10,
timestamp,
is_buyer_maker: id % 2 == 0, is_best_match: None, best_bid: None,
best_ask: None,
}
}
pub fn create_test_agg_trade_with_range(
agg_id: i64,
price: &str,
volume: &str,
timestamp: i64,
first_sub_id: i64,
last_sub_id: i64,
is_buyer_maker: bool,
) -> Tick {
Tick {
ref_id: agg_id,
price: FixedPoint::from_str(price).unwrap(),
volume: FixedPoint::from_str(volume).unwrap(),
first_sub_id,
last_sub_id,
timestamp,
is_buyer_maker,
is_best_match: None,
best_bid: None,
best_ask: None,
}
}
pub fn create_test_spot_agg_trade(
id: i64,
price: &str,
volume: &str,
timestamp: i64,
is_best_match: bool,
) -> Tick {
Tick {
ref_id: id,
price: FixedPoint::from_str(price).unwrap(),
volume: FixedPoint::from_str(volume).unwrap(),
first_sub_id: id * 10,
last_sub_id: id * 10,
timestamp,
is_buyer_maker: id % 2 == 0,
is_best_match: Some(is_best_match),
best_bid: None,
best_ask: None,
}
}
pub fn create_test_open_deviation_bar(
open_time: i64,
close_time: i64,
open: &str,
high: &str,
low: &str,
close: &str,
volume: &str,
individual_trade_count: u32,
) -> OpenDeviationBar {
OpenDeviationBar {
open_time,
close_time,
open: FixedPoint::from_str(open).unwrap(),
high: FixedPoint::from_str(high).unwrap(),
low: FixedPoint::from_str(low).unwrap(),
close: FixedPoint::from_str(close).unwrap(),
volume: FixedPoint::from_str(volume).unwrap().0 as i128,
turnover: 0,
individual_trade_count,
agg_record_count: 1,
first_trade_id: 1,
last_trade_id: individual_trade_count as i64,
first_agg_trade_id: 1, last_agg_trade_id: 1, data_source: DataSource::BinanceFuturesUM,
buy_volume: 0i128,
buy_turnover: 0,
sell_volume: 0i128,
sell_turnover: 0,
buy_trade_count: 0,
sell_trade_count: 0,
vwap: FixedPoint::from_str(open).unwrap(), ..Default::default()
}
}
pub mod constants {
pub const BASE_PRICE: &str = "50000.00000000";
pub const BASE_VOLUME: &str = "1.50000000";
#[allow(clippy::unreadable_literal)]
pub const BASE_TIMESTAMP: i64 = 1640995200000; pub const BTCUSDT_PRICE: &str = "50000.00000000";
pub const ETHUSDT_PRICE: &str = "4000.00000000";
}
pub fn create_breach_test_sequence() -> Vec<Tick> {
vec![
create_test_agg_trade(1, "50000.0", "1.0", constants::BASE_TIMESTAMP),
create_test_agg_trade(2, "50200.0", "1.0", constants::BASE_TIMESTAMP + 1000), create_test_agg_trade(3, "50300.0", "1.0", constants::BASE_TIMESTAMP + 2000), ]
}
pub struct TickBuilder {
base_price: f64,
base_timestamp: i64,
base_volume: String,
trades: Vec<Tick>,
}
impl Default for TickBuilder {
fn default() -> Self {
Self::new()
}
}
impl TickBuilder {
pub fn new() -> Self {
Self {
base_price: 50000.0,
base_timestamp: constants::BASE_TIMESTAMP,
base_volume: "1.0".to_string(),
trades: Vec::new(),
}
}
pub fn with_base_price(mut self, price: f64) -> Self {
self.base_price = price;
self
}
pub fn with_base_timestamp(mut self, timestamp: i64) -> Self {
self.base_timestamp = timestamp;
self
}
pub fn with_base_volume(mut self, volume: &str) -> Self {
self.base_volume = volume.to_string();
self
}
pub fn add_trade(mut self, id: i64, price_factor: f64, time_offset_ms: i64) -> Self {
let price = self.base_price * price_factor;
let trade = create_test_agg_trade(
id,
&format!("{:.8}", price),
&self.base_volume,
self.base_timestamp + time_offset_ms,
);
self.trades.push(trade);
self
}
pub fn add_trade_with_volume(
mut self,
id: i64,
price_factor: f64,
volume: &str,
time_offset_ms: i64,
) -> Self {
let price = self.base_price * price_factor;
let trade = create_test_agg_trade(
id,
&format!("{:.8}", price),
volume,
self.base_timestamp + time_offset_ms,
);
self.trades.push(trade);
self
}
pub fn build(self) -> Vec<Tick> {
self.trades
}
}
pub mod scenarios {
use super::*;
pub fn no_breach_sequence(threshold_decimal_bps: u32) -> Vec<Tick> {
let max_change = (threshold_decimal_bps as f64 / 100_000.0) * 0.8; TickBuilder::new()
.add_trade(1, 1.0, 0)
.add_trade(2, 1.0 + max_change, 1000)
.add_trade(3, 1.0 - max_change, 2000)
.build()
}
pub fn single_breach_sequence(threshold_decimal_bps: u32) -> Vec<Tick> {
let breach_change = (threshold_decimal_bps as f64 / 100_000.0) * 1.2; TickBuilder::new()
.add_trade(1, 1.0, 0)
.add_trade(2, 1.0 + breach_change, 1000) .build()
}
pub fn empty_sequence() -> Vec<Tick> {
Vec::new()
}
pub fn exact_breach_upward(threshold_decimal_bps: u32) -> Vec<Tick> {
let breach_change = threshold_decimal_bps as f64 / 100_000.0; TickBuilder::new()
.add_trade(1, 1.0, 0) .add_trade(2, 1.0 + breach_change * 0.8, 1000) .add_trade(3, 1.0 + breach_change, 2000) .add_trade(4, 1.01, 3000) .build()
}
pub fn exact_breach_downward(threshold_decimal_bps: u32) -> Vec<Tick> {
let breach_change = threshold_decimal_bps as f64 / 100_000.0; TickBuilder::new()
.add_trade(1, 1.0, 0) .add_trade(2, 1.0 - breach_change * 0.8, 1000) .add_trade(3, 1.0 - breach_change, 2000) .add_trade(4, 0.99, 3000) .build()
}
pub fn large_gap_sequence() -> Vec<Tick> {
TickBuilder::new()
.add_trade(1, 1.0, 0) .add_trade(2, 1.02, 1000) .build()
}
pub fn unsorted_sequence() -> Vec<Tick> {
use super::constants;
vec![
create_test_agg_trade_with_range(
1,
"50000.0",
"1.0",
constants::BASE_TIMESTAMP + 2000,
10,
10,
false,
), create_test_agg_trade_with_range(
2,
"50100.0",
"1.0",
constants::BASE_TIMESTAMP + 1000,
20,
20,
false,
), ]
}
pub fn large_sequence(count: usize) -> Vec<Tick> {
let mut builder = TickBuilder::new();
for i in 0..count {
let price_factor = 1.0 + (i as f64 * 0.001); builder = builder.add_trade(i as i64 + 1, price_factor, i as i64 * 100);
}
builder.build()
}
}