diff --git a/atelier-data/Cargo.toml b/atelier-data/Cargo.toml index 4b7473e..ca7ca09 100644 --- a/atelier-data/Cargo.toml +++ b/atelier-data/Cargo.toml @@ -4,17 +4,12 @@ description = "Core data structures and I/O tools for the atelier-rs engine" publish = true readme = "README.md" -version = "0.0.1" +version = "0.0.10" rust-version = "1.84.1" edition = "2021" exclude = ["assets/*", ".github", "Makefile.toml", "*.log", "tags"] -include = [ - "src/**/*", - "Cargo.toml", - "README.md", - "../katex-header.html" -] +include = ["src/**/*", "Cargo.toml", "README.md", "../katex-header.html"] authors = ["IteraLabs.ai"] documentation = "https://docs.rs/atelier-rs/" @@ -50,6 +45,7 @@ csv = { version = "1.3" } # AI/ML with LibTorch from C++ tch = { version = "0.20.0" } +tokio.workspace = true # ------------------------------------------------------------------------- Examples -- # # ------------------------------------------------------------------------- -------- -- # @@ -65,4 +61,3 @@ path = "examples/orderbook.rs" [[example]] name = "basic_orderbook_progressions" path = "examples/progressions.rs" - diff --git a/atelier-data/src/lib.rs b/atelier-data/src/lib.rs index 737d85a..eb9fba6 100644 --- a/atelier-data/src/lib.rs +++ b/atelier-data/src/lib.rs @@ -5,17 +5,21 @@ #![allow(clippy::too_many_arguments)] -/// Configurations and experiments -pub mod templates; - /// Dataset defintion and tools pub mod data; -/// Implementation of orders -pub mod orders; - /// Orders-Price-Volume levels for Orderbooks. pub mod levels; /// Single thread Orderbook structure. pub mod orderbooks; + +/// Implementation of orders +pub mod orders; + +/// Configurations and experiments +pub mod templates; + +/// Implementation of trades +pub mod trades; + diff --git a/atelier-data/src/templates/agent.rs b/atelier-data/src/templates/agent.rs new file mode 100644 index 0000000..c52f608 --- /dev/null +++ b/atelier-data/src/templates/agent.rs @@ -0,0 +1,46 @@ +use serde::Deserialize; + +use super::{exchanges::OrderbookConfig, models::ModelConfig}; + +#[derive(Debug, Deserialize)] +pub struct Agents { + #[allow(unused)] + agents: Vec, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Config { + pub name: String, + pub model: ModelConfig, + pub orderbook: OrderbookConfig, +} + +#[cfg(test)] +mod test { + use super::*; + + static TOML: &str = r#"[[agents]] + name = "a" + [agents.orderbook] + update_freq = 20 + bid_price = 3_000.00 + bid_levels = [4, 12] + bid_orders = [5, 15] + ticksize = [0.2, 2.0] + ask_price = 3_001.00 + ask_levels = [3, 13] + ask_orders = [6, 16] + [agents.model] + id = "mod_00" + label = "GBM" + description = "Geometric Brownian Motion" + params_labels = ["mu", "sigma"] + params_values = [1e-3, 1e-6] + seed = 20995"#; + + #[test] + fn toml_works() { + let x: Agents = toml::from_str(TOML).unwrap(); + println!("{x:#?}"); + } +} diff --git a/atelier-data/src/templates/exchanges/centralized.rs b/atelier-data/src/templates/exchanges/centralized.rs index c87f5a9..b95f04e 100644 --- a/atelier-data/src/templates/exchanges/centralized.rs +++ b/atelier-data/src/templates/exchanges/centralized.rs @@ -9,6 +9,7 @@ pub struct ExchangeConfig { pub orderbook: Option, } +// TODO refactor #[derive(Debug, Deserialize, Clone)] pub struct OrderbookConfig { pub update_freq: Option, diff --git a/atelier-data/src/templates/mod.rs b/atelier-data/src/templates/mod.rs index 5b0063c..95d1c28 100644 --- a/atelier-data/src/templates/mod.rs +++ b/atelier-data/src/templates/mod.rs @@ -7,6 +7,7 @@ use crate::templates::{ features::FeatureConfig, models::ModelConfig, }; +pub mod agent; /// Load exchanges config files pub mod exchanges; /// Load experiments config files diff --git a/atelier-data/src/templates/models.rs b/atelier-data/src/templates/models.rs index 3c19f60..9d0b2de 100644 --- a/atelier-data/src/templates/models.rs +++ b/atelier-data/src/templates/models.rs @@ -15,6 +15,7 @@ pub struct ModelConfig { pub description: Option, pub params_labels: Option>, pub params_values: Option>, + pub seed: Option, } impl ModelConfig { @@ -23,30 +24,19 @@ impl ModelConfig { } } -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Clone, Default)] pub struct ModelConfigBuilder { pub id: Option, pub label: Option, pub description: Option, pub params_labels: Option>, pub params_values: Option>, -} - -impl Default for ModelConfigBuilder { - fn default() -> Self { - Self::new() - } + pub seed: Option, } impl ModelConfigBuilder { pub fn new() -> Self { - ModelConfigBuilder { - id: None, - label: None, - description: None, - params_labels: None, - params_values: None, - } + Self::default() } pub fn id(mut self, id: String) -> Self { @@ -87,6 +77,7 @@ impl ModelConfigBuilder { description: Some(description), params_labels: Some(params_labels), params_values: Some(params_values), + seed: self.seed, }) } } diff --git a/atelier-data/src/trades/mod.rs b/atelier-data/src/trades/mod.rs new file mode 100644 index 0000000..40b4512 --- /dev/null +++ b/atelier-data/src/trades/mod.rs @@ -0,0 +1,216 @@ +pub use serde::{Deserialize, Serialize}; +pub use std::time::{SystemTime, UNIX_EPOCH}; + +/// TradeSide +/// +/// Enum for identification of either a buy or sell side +/// used to describe the Trade side. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] +pub enum TradeSide { + Bids, + Asks, +} + +impl TradeSide { + /// + /// Creates a random choice of the Side enum variants, which currently + /// has implemented: {Bids, Asks} + pub fn random() -> Self { + let now_ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + if now_ts % 2 == 0 { + TradeSide::Bids + } else { + TradeSide::Asks + } + } +} + +/// trade_id +/// +/// Method to generate unique `trade_id` values for individual trades. +/// currently taking the trade timestamp, the trade symbol and the trade side to deliver +/// a hashed u64 value. +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub struct TradeId(u64); + +impl TradeId { + const TIME_MASK: u64 = 0x0FFF_FFFF_FFFF_FFFF; + + pub fn new(trade_ts: u64, trade_side: TradeSide, reserved: u64) -> Self { + let mut val = trade_ts & Self::TIME_MASK; + val |= (trade_side as u64) << 63; + val |= (reserved) << 62; + TradeId(val) + } + + pub fn timestamp(&self) -> u64 { + self.0 & Self::TIME_MASK + } + + pub fn reserved(&self) -> u64 { + if self.0 >> 62 & 1 == 0 { + 1 + } else { + 2 + } + } + + pub fn side(&self) -> TradeSide { + if self.0 >> 63 & 1 == 0 { + TradeSide::Bids + } else { + TradeSide::Asks + } + } +} + +// Represents a single public trade +#[derive(Debug, Clone)] +pub struct Trade { + pub trade_id: u64, + pub trade_ts: u64, + pub symbol: String, + pub price: f64, + pub side: TradeSide, + pub amount: f64, +} + +impl Trade { + pub fn builder() -> TradeBuilder { + TradeBuilder::new() + } +} + +#[derive(Debug, Clone)] +pub struct TradeBuilder { + trade_id: Option, + trade_ts: Option, + symbol: Option, + price: Option, + side: Option, + amount: Option, +} + +impl Default for TradeBuilder { + fn default() -> Self { + Self::new() + } +} + +impl TradeBuilder { + pub fn new() -> Self { + TradeBuilder { + trade_id: None, + trade_ts: None, + symbol: None, + price: None, + side: None, + amount: None, + } + } + + pub fn trade_id(mut self, trade_id: u64) -> Self { + self.trade_id = Some(trade_id); + self + } + + pub fn trade_ts(mut self, trade_ts: u64) -> Self { + self.trade_ts = Some(trade_ts); + self + } + + pub fn symbol(mut self, symbol: String) -> Self { + self.symbol = Some(symbol); + self + } + + pub fn side(mut self, side: TradeSide) -> Self { + self.side = Some(side); + self + } + + pub fn price(mut self, price: f64) -> Self { + self.price = Some(price); + self + } + + pub fn amount(mut self, amount: f64) -> Self { + self.amount = Some(amount); + self + } + + pub fn build(self) -> Result { + let trade_ts = self.trade_ts.unwrap_or_else(|| { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_micros() as u64 + }); + let symbol = self.symbol.ok_or("Missing symbol")?; + let price = self.price.ok_or("Missing price")?; + let side = self.side.ok_or("Missing side")?; + let amount = self.amount.ok_or("Missing amount")?; + let trade_id = Trade::encode_trade_id(side, trade_ts, 0); + + Ok(Trade { + trade_id, + trade_ts, + symbol, + price, + side, + amount, + }) + } +} + +impl Trade { + /// Encoded Trade ID formation + /// + /// The trade_id field is an u64 containing encoded info about: side, type, + /// timestamp. The Bit allocation is the following: + /// + /// 00TTSSSS SSSSSSSS SSSSSSSS SSSSSSSS SSSSSSSS SSSSSSSS SSSSSSSS SSSSSSSS + /// ||└ 60 bits for timestamp (valid until ~2079) + /// |└─ 1 bit for type (0=Market, 1=Limit) + /// └── 1 bit reserved for future implementation. + pub fn encode_trade_id(trade_side: TradeSide, trade_ts: u64, reserved: u64) -> u64 { + // Highest bit + let side_bit = match trade_side { + TradeSide::Bids => 0, + TradeSide::Asks => 1, + } << 63; + + // Second highest bit + let reserved_bit = match reserved { + 0 => 0, + _ => 1, + } << 62; + + // 60 bits starting at position 2 + let timestamp_bits = (trade_ts & ((1 << 60) - 1)) << 2; + + side_bit | reserved_bit | timestamp_bits + } + + /// Decode Order ID encoded formation. check `encoded_order_id` for more + /// details. + pub fn decode_trade_id(order_id: u64) -> (TradeSide, u64, u64) { + // Highest bit + let order_side = if (order_id >> 63) & 1 == 0 { + TradeSide::Bids + } else { + TradeSide::Asks + }; + // Second highest bit + let order_type = if (order_id >> 62) & 1 == 0 { 0 } else { 1 }; + // 60 bits starting at position 2 + let order_ts = (order_id >> 2) & ((1 << 60) - 1); + + (order_side, order_type, order_ts) + } + +} diff --git a/atelier-dcml/Cargo.toml b/atelier-dcml/Cargo.toml index ba277bb..e728c37 100644 --- a/atelier-dcml/Cargo.toml +++ b/atelier-dcml/Cargo.toml @@ -4,7 +4,7 @@ description = "Distributed Convex Machine Learning for the atelier-rs engine" publish = true readme = "README.md" -version = "0.0.1" +version = "0.0.10" rust-version = "1.84.1" edition = "2021" @@ -31,8 +31,8 @@ path = "src/lib.rs" [dependencies] # Atelier sub-modules -atelier_data = { path = "../atelier-data", version = "0.0.1" } -atelier_generators = { path = "../atelier-generators", version = "0.0.1" } +atelier_data = { version = "0.0.10" } +atelier_generators = { version = "0.0.1" } # Probabilistic features rand = { version = "0.9.0" } @@ -49,13 +49,29 @@ serde_json = { version = "1.0" } # AI/ML with LibTorch from C++ tch = { version = "0.20.0" } -[[example]] -name = "loss_functions" -path = "examples/loss_functions.rs" +[[test]] +name = "test_compute_features" +path = "test/features/test_compute_features.rs" -[[example]] -name = "singular_training" -path = "examples/singular_training.rs" +[[test]] +name = "test_toxicity" +path = "test/features/test_toxicity.rs" + +[[test]] +name = "test_loss_functions" +path = "test/functions/test_loss_functions.rs" + +[[test]] +name = "test_compute_targets" +path = "test/targets/test_compute_targets.rs" + +[[test]] +name = "test_distributed_p1" +path = "test/training/test_distributed_p1.rs" + +[[test]] +name = "test_distributed_p3" +path = "test/training/test_distributed_p3.rs" [lints.rust] unsafe_code = "forbid" diff --git a/atelier-dcml/src/features/liquidity.rs b/atelier-dcml/src/features/liquidity.rs new file mode 100644 index 0000000..ea97c77 --- /dev/null +++ b/atelier-dcml/src/features/liquidity.rs @@ -0,0 +1,46 @@ + +use atelier_data::{orders:: OrderSide, orderbooks::Orderbook}; + +/// Calculates the price impact of a hypothetical market order of a given size. +/// For a buy order, it measures the average price paid vs the best ask. +/// For a sell order, it measures the average price received vs the best bid. +/// Returns None if the book depth is insufficient. +pub fn compute_price_impact( + book: &Orderbook, + order_side: OrderSide, + order_size: f64, +) -> Option { + let mut remaining_size = order_size; + let mut weighted_price_sum = 0.0; + let book_side = if order_side == OrderSide::Bids { + &book.asks + } else { + &book.bids + }; + let start_price = if order_side == OrderSide::Bids { + book.asks[0].price + } else { + book.bids[0].price + }; + + for level in book_side { + if remaining_size <= 0.0 { + break; + } + + let size_to_take = remaining_size.min(level.volume); + weighted_price_sum += size_to_take * level.price; + remaining_size -= size_to_take; + } + + if remaining_size > 0.0 { + return None; // Not enough depth in the book + } + + let avg_execution_price = weighted_price_sum / order_size; + + // Relative price impact + let impact = (avg_execution_price - start_price).abs() / start_price; + Some(impact) +} + diff --git a/atelier-dcml/src/features/mod.rs b/atelier-dcml/src/features/mod.rs new file mode 100644 index 0000000..25f2161 --- /dev/null +++ b/atelier-dcml/src/features/mod.rs @@ -0,0 +1,10 @@ +pub mod order_book; +// pub use order_book::*; +pub mod order_flow; +// pub mod trade_flow; +pub mod toxicity; +//pub use toxicity::*; +pub mod volatility; +//pub use volatility::*; +pub mod liquidity; +//pub use liquidity::*; diff --git a/atelier-dcml/src/features.rs b/atelier-dcml/src/features/order_book.rs similarity index 98% rename from atelier-dcml/src/features.rs rename to atelier-dcml/src/features/order_book.rs index 4f7687d..385b747 100644 --- a/atelier-dcml/src/features.rs +++ b/atelier-dcml/src/features/order_book.rs @@ -42,7 +42,7 @@ impl OrderbookFeatures { OrderbookFeatures::WeightedMidprice => Ok(compute_w_midprice(ob)), OrderbookFeatures::VWAP => Ok(compute_vwap(ob, depth)), OrderbookFeatures::Imb => Ok(compute_imb(ob)), - OrderbookFeatures::TAV => Ok(compute_tav(ob, bps)), + OrderbookFeatures::TAV => Ok(compute_tav(ob, bps)) } } @@ -141,7 +141,50 @@ pub fn compute_features( } } -// --- Different Features Computations --- // +/// Orderbook Updates Distribution +/// +/// From the timeseries of timestamps where a new orderbook was formed +/// +pub fn compute_obts(obs: &[Orderbook]) -> Vec { + let ob_ts = obs + .iter() + .map(|ob| ob.orderbook_ts as f64) + .collect::>() + .windows(2) + .map(|pair| pair[1] - pair[0]) + .collect(); + + ob_ts +} + +/// Total Available Volume +/// +/// The total volume posted in the orderbook within X bps of the midprice +/// +pub fn compute_tav(ob: &Orderbook, bps: f64) -> f64 { + let best_bid = &ob.bids[0].price; + let best_ask = &ob.asks[0].price; + let upper_ask = best_ask * (1.0 + bps); + let lower_bid = best_bid * (1.0 - bps); + + // find the closest bid leve to lower bid + let bid_volume: f64 = ob + .bids + .iter() + .filter(|level| level.price >= lower_bid) + .map(|level| level.volume) + .sum(); + + let ask_volume: f64 = ob + .asks + .iter() + .filter(|level| level.price <= upper_ask) + .map(|level| level.volume) + .sum(); + + let i_tav = bid_volume + ask_volume; + data::truncate_to_decimal(i_tav, 8) +} /// Spread pub fn compute_spread(ob: &Orderbook) -> f64 { @@ -189,47 +232,3 @@ pub fn compute_vwap(ob: &Orderbook, depth: usize) -> f64 { } } -/// Total Available Volume -/// -/// The total volume posted in the orderbook within X bps of the midprice -/// -pub fn compute_tav(ob: &Orderbook, bps: f64) -> f64 { - let best_bid = &ob.bids[0].price; - let best_ask = &ob.asks[0].price; - let upper_ask = best_ask * (1.0 + bps); - let lower_bid = best_bid * (1.0 - bps); - - // find the closest bid leve to lower bid - let bid_volume: f64 = ob - .bids - .iter() - .filter(|level| level.price >= lower_bid) - .map(|level| level.volume) - .sum(); - - let ask_volume: f64 = ob - .asks - .iter() - .filter(|level| level.price <= upper_ask) - .map(|level| level.volume) - .sum(); - - let i_tav = bid_volume + ask_volume; - data::truncate_to_decimal(i_tav, 8) -} - -/// Orderbook Updates Distribution -/// -/// From the timeseries of timestamps where a new orderbook was formed -/// -pub fn compute_obts(obs: &[Orderbook]) -> Vec { - let ob_ts = obs - .iter() - .map(|ob| ob.orderbook_ts as f64) - .collect::>() - .windows(2) - .map(|pair| pair[1] - pair[0]) - .collect(); - - ob_ts -} diff --git a/atelier-dcml/src/features/order_flow.rs b/atelier-dcml/src/features/order_flow.rs new file mode 100644 index 0000000..90ad131 --- /dev/null +++ b/atelier-dcml/src/features/order_flow.rs @@ -0,0 +1,67 @@ +use atelier_data::{ + orderbooks::Orderbook, +}; + +use std::error::Error; + +#[derive(Debug, Clone, Copy)] +pub enum OrderFlowFeatures { + OrderFlowImb, +} + +impl OrderFlowFeatures { + pub fn name(&self) -> &'static str { + match self { + OrderFlowFeatures::OrderFlowImb => "of_imb", + } + } + + pub fn compute(&self, ob: &[Orderbook]) -> Result>{ + match self { + OrderFlowFeatures::OrderFlowImb => Ok(compute_order_flow_imbalance(ob).unwrap()), + } + } + + pub fn from_name(name: &str) -> Option { + match name { + "of_imb" => Some(OrderFlowFeatures::OrderFlowImb), + _ => None, + } + } + + pub fn all_features() -> Vec { + vec![ + OrderFlowFeatures::OrderFlowImb, + ] + } + + pub fn list_features() -> Vec<&'static str> { + Self::all_features().iter().map(|f| f.name()).collect() + } + +} + +/// Calculates the imbalance in order flow over a slice of recent orderbooks. +/// OFI = (buy_volume - sell_volume) / (buy_volume + ask_volume) +/// Returns None if there are no trades or zero total volume. +pub fn compute_order_flow_imbalance(orderbooks: &[Orderbook]) -> Option { + if orderbooks.is_empty() { + return None; + } + + let mut buy_volume: f64 = 0.0; + let mut sell_volume: f64 = 0.0; + + for orderbook in orderbooks { + buy_volume += orderbook.bids.iter().map(|ob| ob.volume).sum::(); + sell_volume += orderbook.asks.iter().map(|ob| ob.volume).sum::(); + } + + let total_volume = buy_volume + sell_volume; + if total_volume > 0.0 { + Some((buy_volume - sell_volume) / total_volume) + } else { + None + } +} + diff --git a/atelier-dcml/src/features/toxicity.rs b/atelier-dcml/src/features/toxicity.rs new file mode 100644 index 0000000..65c1552 --- /dev/null +++ b/atelier-dcml/src/features/toxicity.rs @@ -0,0 +1,76 @@ + +pub use std::collections::VecDeque; +pub use atelier_data::trades::{TradeSide, Trade}; + +/// A simplified VPIN calculation over a series of trades. +/// This function processes a batch of trades and updates the VPIN state. +/// A full implementation would manage this state continuously. +/// Here, we calculate VPIN for the provided trade slice. +pub fn calculate_vpin( + trades: &[Trade], + trades_per_day: u64, + volume_per_bucket_ratio: f64, +) -> f64 { + let total_volume: f64 = trades.iter().map(|t| t.amount).sum(); + if total_volume == 0.0 { + return 0.0; + } + + let volume_per_bucket = + (total_volume / trades_per_day as f64) * volume_per_bucket_ratio; + if volume_per_bucket == 0.0 { + return 0.0; + } + + let num_buckets = trades.len(); + let mut buckets = Vec::new(); + let mut current_volume = 0.0; + let mut buy_volume_in_bucket = 0.0; + let mut sell_volume_in_bucket = 0.0; + + for trade in trades { + let trade_buy_volume = if trade.side == TradeSide::Bids { + trade.amount + } else { + 0.0 + }; + let trade_sell_volume = if trade.side == TradeSide::Asks { + trade.amount + } else { + 0.0 + }; + + current_volume += trade.amount; + buy_volume_in_bucket += trade_buy_volume; + sell_volume_in_bucket += trade_sell_volume; + + if current_volume >= volume_per_bucket { + buckets.push((buy_volume_in_bucket, sell_volume_in_bucket)); + current_volume = 0.0; + buy_volume_in_bucket = 0.0; + sell_volume_in_bucket = 0.0; + } + } + + if buckets.is_empty() { + return 0.0; + } + + let order_flow_imbalances: Vec = buckets + .iter() + .map(|(buy_v, sell_v)| { + let total_v = buy_v + sell_v; + if total_v > 0.0 { + (buy_v - sell_v).abs() / total_v + } else { + 0.0 + } + }) + .collect(); + + // The VPIN is the sum of imbalances divided by the number of buckets + // In the paper, it's a CDF, but this sum is a common proxy. + let vpin_sum: f64 = order_flow_imbalances.iter().sum(); + vpin_sum / num_buckets as f64 +} + diff --git a/atelier-dcml/src/features/trade_flow.rs b/atelier-dcml/src/features/trade_flow.rs new file mode 100644 index 0000000..a731830 --- /dev/null +++ b/atelier-dcml/src/features/trade_flow.rs @@ -0,0 +1,46 @@ + +use atelier_data::{ + trades::{Trade, TradeSide}, +}; + +#[derive(Debug, Clone, Copy)] +pub enum OrderFlowFeatures { + TradeFlowImb, +} + +impl OrderFlowFeatures { + pub fn name(&self) -> &'static str { + match self { + OrderFlowFeatures::TradeFlowImb => "tf_imb", + } + } + + // pub fn compute(&self, ob: &[Trade]) {} +} + +/// Calculates the imbalance in trade flow over a slice of recent trades. +/// TFI = (BuyVolume - SellVolume) / (BuyVolume + SellVolume) +/// Returns None if there are no trades or zero total volume. +pub fn calculate_trade_flow_imbalance(trades: &[Trade]) -> Option { + if trades.is_empty() { + return None; + } + + let mut buy_volume = 0.0; + let mut sell_volume = 0.0; + + for trade in trades { + match trade.side { + TradeSide::Bids => buy_volume += trade.amount, + TradeSide::Asks => sell_volume += trade.amount, + } + } + + let total_volume = buy_volume + sell_volume; + if total_volume > 0.0 { + Some((buy_volume - sell_volume) / total_volume) + } else { + None + } +} + diff --git a/atelier-dcml/src/features/volatility.rs b/atelier-dcml/src/features/volatility.rs new file mode 100644 index 0000000..62713b6 --- /dev/null +++ b/atelier-dcml/src/features/volatility.rs @@ -0,0 +1,38 @@ +use atelier_data::trades::Trade; + +#[derive(Debug, Clone, Copy)] +pub enum VolatilityFeatures { + RealizedVol, +} + +impl VolatilityFeatures { + pub fn name(&self) -> &'static str { + match self { + VolatilityFeatures::RealizedVol => "vol_realized", + } + } + + // pub fn compute(&self, ob: &[Trade]) {} +} + +/// Calculates the realized volatility from a slice of recent trade prices. +/// Uses the standard deviation of log returns. +/// Returns None if there are fewer than 2 trades. +pub fn calculate_realized_volatility(trades: &[Trade]) -> Option { + if trades.len() < 2 { + return None; + } + + let log_returns: Vec = trades + .windows(2) + .map(|w| (w[1].price / w[0].price).ln()) + .collect(); + + let n = log_returns.len() as f64; + if n == 0.0 { return Some(0.0); } + + let mean = log_returns.iter().sum::() / n; + let variance = log_returns.iter().map(|ret| (ret - mean).powi(2)).sum::() / n; + + Some(variance.sqrt()) +} diff --git a/tests/dcml/features/test_compute_features.rs b/atelier-dcml/test/features/test_compute_features.rs similarity index 96% rename from tests/dcml/features/test_compute_features.rs rename to atelier-dcml/test/features/test_compute_features.rs index 1c010d7..04a923f 100644 --- a/tests/dcml/features/test_compute_features.rs +++ b/atelier-dcml/test/features/test_compute_features.rs @@ -30,7 +30,7 @@ mod tests { #[test] fn test_compute_features() { use crate::test_utils::test_orderbook; - use atelier_dcml::features; + use atelier_dcml::features::order_book as features; // Get a random Orderbook from test_orderbook let ob_data_0 = test_orderbook(50); @@ -59,3 +59,4 @@ mod tests { println!("i_obts: {:?}", i_obts); } } + diff --git a/atelier-dcml/test/features/test_liquidity.rs b/atelier-dcml/test/features/test_liquidity.rs new file mode 100644 index 0000000..e69de29 diff --git a/atelier-dcml/test/features/test_order_flow.rs b/atelier-dcml/test/features/test_order_flow.rs new file mode 100644 index 0000000..e69de29 diff --git a/atelier-dcml/test/features/test_toxicity.rs b/atelier-dcml/test/features/test_toxicity.rs new file mode 100644 index 0000000..10051c7 --- /dev/null +++ b/atelier-dcml/test/features/test_toxicity.rs @@ -0,0 +1 @@ +// Placeholder diff --git a/atelier-dcml/test/features/test_trade_flow.rs b/atelier-dcml/test/features/test_trade_flow.rs new file mode 100644 index 0000000..e69de29 diff --git a/atelier-dcml/test/features/test_volatility.rs b/atelier-dcml/test/features/test_volatility.rs new file mode 100644 index 0000000..e69de29 diff --git a/tests/dcml/functions/test_loss_functions.rs b/atelier-dcml/test/functions/test_loss_functions.rs similarity index 100% rename from tests/dcml/functions/test_loss_functions.rs rename to atelier-dcml/test/functions/test_loss_functions.rs diff --git a/tests/dcml/targets/test_compute_targets.rs b/atelier-dcml/test/targets/test_compute_targets.rs similarity index 100% rename from tests/dcml/targets/test_compute_targets.rs rename to atelier-dcml/test/targets/test_compute_targets.rs diff --git a/tests/dcml/training/test_distributed_p1.rs b/atelier-dcml/test/training/test_distributed_p1.rs similarity index 91% rename from tests/dcml/training/test_distributed_p1.rs rename to atelier-dcml/test/training/test_distributed_p1.rs index c44c8c6..c86b93e 100644 --- a/tests/dcml/training/test_distributed_p1.rs +++ b/atelier-dcml/test/training/test_distributed_p1.rs @@ -7,7 +7,9 @@ mod test_utils { // ------------------------------------------------------------- TEST ORDERBOOK -- // - pub fn generate_features(n_features: u32) -> Vec { + #[test] + pub fn generate_p1() { + // dataset // models // loss @@ -25,7 +27,7 @@ mod test_utils { vec![0.1, 0.2, 0.1, 0.2, 0.1, 0.2], ]; - + println!("v_features {:?}", v_features); } } diff --git a/tests/dcml/training/test_distributed_p3.rs b/atelier-dcml/test/training/test_distributed_p3.rs similarity index 87% rename from tests/dcml/training/test_distributed_p3.rs rename to atelier-dcml/test/training/test_distributed_p3.rs index 3eb777e..64817f4 100644 --- a/tests/dcml/training/test_distributed_p3.rs +++ b/atelier-dcml/test/training/test_distributed_p3.rs @@ -6,8 +6,10 @@ mod test_utils { // ------------------------------------------------------------- TEST ORDERBOOK -- // + + #[test] + fn generate_p3() { - pub fn generate_features(n_features: u32) -> Vec { // dataset // models // loss @@ -16,7 +18,7 @@ mod test_utils { // optimizer // strategy // learning - + let v_features: Vec> = vec![ vec![0.1, 0.2, 0.1, 0.2, 0.1, 0.2], vec![0.1, 0.2, 0.1, 0.2, 0.1, 0.2], @@ -25,9 +27,10 @@ mod test_utils { vec![0.1, 0.2, 0.1, 0.2, 0.1, 0.2], ]; - + println!("v_features {:?}", v_features); } + } mod tests { @@ -35,9 +38,5 @@ mod tests { // ----------------------------------------------------------- COMPUTE_FEATURES -- // #[test] - fn test_distributed_p3() { - - } - + fn test_compute_features() {} } - diff --git a/atelier-generators/src/brownian.rs b/atelier-generators/src/brownian.rs index 372d12c..ec82c5e 100644 --- a/atelier-generators/src/brownian.rs +++ b/atelier-generators/src/brownian.rs @@ -29,8 +29,9 @@ use crate::{probabilistic, probabilistic::Sampling}; use atelier_results::errors::GeneratorError; +use rand::Rng; -fn gbm_return_valid_inputs( +pub fn gbm_return_valid_inputs( s0: &f64, sigma: &f64, dt: &f64, @@ -42,49 +43,47 @@ fn gbm_return_valid_inputs( } } -pub fn gbm_return( +pub fn gbm_return( + rng: &mut T, s0: f64, mu: f64, sigma: f64, dt: f64, n: usize, ) -> Result, GeneratorError> { - match gbm_return_valid_inputs(&s0, &sigma, &dt, &n) { - Ok(()) => { - let dis = probabilistic::NormalDistribution { - mu: 0.0, - sigma: dt.sqrt(), - }; + gbm_return_valid_inputs(&s0, &sigma, &dt, &n)?; - if n == 1 { - let dwt = dt.sqrt() * dis.sample(n)[0]; - let drift = mu * s0 * dt; - let diffusion = sigma * s0 * dwt; - let dst = drift + diffusion; + let dis = probabilistic::NormalDistribution { + mu: 0.0, + sigma: dt.sqrt(), + }; - Ok(vec![dst]) - } else { - let dwt: Vec = dis.sample(n).clone().into_iter().collect(); - let mut v_ds = vec![]; - let mut v_s = vec![s0]; + if n == 1 { + let dwt = dis.sample(rng, n)[0]; + let drift = mu * s0 * dt; + let diffusion = sigma * s0 * dwt; + let dst = drift + diffusion; - // progress through all the future steps - for t in 0..dwt.len() { - // drift = mu * s_t * d_t - let drift = mu * v_s[t] * dt; - // diffusion = sigma * s_t * dW_t - let diffusion = sigma * v_s[t] * dwt[t]; - // dS_t = drift_t + diffusion_t - let ds = drift + diffusion; - // Update vector of prices - v_s.push(v_s[t] + ds); - // Update vector of differences of prices - v_ds.push(ds); - } + return Ok(vec![dst]); + } + + let dwt: Vec = dis.sample(rng, n).clone().into_iter().collect(); + let mut v_ds = vec![]; + let mut v_s = vec![s0]; - Ok(v_ds) - } - } - Err(e) => Err(e), + // progress through all the future steps + for t in 0..dwt.len() { + // drift = mu * s_t * d_t + let drift = mu * v_s[t] * dt; + // diffusion = sigma * s_t * dW_t + let diffusion = sigma * v_s[t] * dwt[t]; + // dS_t = drift_t + diffusion_t + let ds = drift + diffusion; + // Update vector of prices + v_s.push(v_s[t] + ds); + // Update vector of differences of prices + v_ds.push(ds); } + + Ok(v_ds) } diff --git a/atelier-generators/src/probabilistic.rs b/atelier-generators/src/probabilistic.rs index 52d6776..7ccd184 100644 --- a/atelier-generators/src/probabilistic.rs +++ b/atelier-generators/src/probabilistic.rs @@ -21,8 +21,8 @@ pub enum Distributions { Exponential, } -pub trait Sampling { - fn sample(&self, n: usize) -> Vec; +pub trait Sampling { + fn sample(&self, rng: &mut R, n: usize) -> Vec; } pub trait PDF { @@ -34,15 +34,8 @@ pub struct UniformDistribution { pub upper: f64, } -pub fn uniform_return(lower: f64, upper: f64, n: usize) -> Vec { - let uniform = UniformDistribution { lower, upper }; - - uniform.sample(n) -} - -impl Sampling for UniformDistribution { - fn sample(&self, n: usize) -> Vec { - let mut rng = rand::rng(); +impl Sampling for UniformDistribution { + fn sample(&self, rng: &mut R, n: usize) -> Vec { let uni = Uniform::new(self.lower, self.upper).unwrap(); (0..n).map(|_| rng.sample(uni)).collect() } @@ -53,19 +46,11 @@ pub struct NormalDistribution { pub sigma: f64, } -impl Sampling for NormalDistribution { - fn sample(&self, n: usize) -> Vec { - if self.mu == 0.0 && self.sigma == 1.0 { - let std_normal = Normal::new(0.0, 1.0).unwrap(); - let v_std_normal: Vec = - std_normal.sample_iter(&mut rand::rng()).take(n).collect(); - v_std_normal - } else { - let normal = Normal::new(self.mu, self.sigma).unwrap(); - let v_normal: Vec = - normal.sample_iter(&mut rand::rng()).take(n).collect(); - v_normal - } +impl Sampling for NormalDistribution { + fn sample(&self, rng: &mut R, n: usize) -> Vec { + let std_normal = Normal::new(0.0, 1.0).unwrap(); + let v_std_normal: Vec = std_normal.sample_iter(rng).take(n).collect(); + v_std_normal } } @@ -75,7 +60,7 @@ pub struct Poisson { } impl Sampling for Poisson { - fn sample(&self, n: usize) -> Vec { + fn sample(&self, rng: &mut ThreadRng, n: usize) -> Vec { let mut samples = Vec::with_capacity(n); let exp_neg_lambda = (-self.lambda).exp(); @@ -83,7 +68,7 @@ impl Sampling for Poisson { let mut x = 0; let mut p = 1.0; loop { - let u = rand::random::(); + let u = rng.random::(); p *= u; if p <= exp_neg_lambda { break; @@ -109,12 +94,12 @@ pub struct Exponential { } impl Sampling for Exponential { - fn sample(&self, n: usize) -> Vec { + fn sample(&self, rng: &mut ThreadRng, n: usize) -> Vec { let mut samples = Vec::new(); // Inverse Method for Random Sampling for _ in 0..n { - let x = (-1.0 / self.lambda) * (rand::random::().ln()) as f64; + let x = (-1.0 / self.lambda) * (rng.random::().ln()); samples.push(x); } samples diff --git a/atelier-rs/Cargo.toml b/atelier-rs/Cargo.toml index a21ef4d..94c662b 100644 --- a/atelier-rs/Cargo.toml +++ b/atelier-rs/Cargo.toml @@ -31,8 +31,8 @@ path = "src/lib.rs" [dependencies] # Atelier internal dependencies -atelier_data = { path = "../atelier-data", version = "0.0.1" } -atelier_dcml = { path = "../atelier-dcml", version = "0.0.1" } +atelier_data = { path = "../atelier-data", version = "0.0.10" } +atelier_dcml = { path = "../atelier-dcml", version = "0.0.10" } atelier_generators = { path = "../atelier-generators", version = "0.0.1" } atelier_results = { path = "../atelier-results", version = "0.0.1" } atelier_synth = { path = "../atelier-synth", version = "0.0.1" } diff --git a/atelier-rs/src/bin/synthetizer.rs b/atelier-rs/src/bin/synthetizer.rs index 82543af..a3ef195 100644 --- a/atelier-rs/src/bin/synthetizer.rs +++ b/atelier-rs/src/bin/synthetizer.rs @@ -51,22 +51,12 @@ pub async fn main() { // --- Create progressions let v_rand_ob = - synthbooks::progressions(template_orderbook, template_model, n_progres); + synthbooks::progressions(&template_orderbook, &template_model, n_progres); // --- Compute basic stats - let level_bids: Vec = v_rand_ob - .as_ref() - .unwrap() - .iter() - .map(|x| x.bids.len() as f32) - .collect(); + let level_bids: Vec = v_rand_ob.iter().map(|x| x.bids.len() as f32).collect(); - let level_asks: Vec = v_rand_ob - .as_ref() - .unwrap() - .iter() - .map(|x| x.asks.len() as f32) - .collect(); + let level_asks: Vec = v_rand_ob.iter().map(|x| x.asks.len() as f32).collect(); let bids_stats = Stats::new(&level_bids); let asks_stats = Stats::new(&level_asks); @@ -86,7 +76,7 @@ pub async fn main() { folder_route.push_str(&args.suffix); folder_route.push_str(".json"); - let i_ob = v_rand_ob.as_ref().unwrap(); + let i_ob = v_rand_ob; let ob_json = serde_json::to_string(&i_ob).unwrap(); let mut file = File::create(&folder_route).unwrap(); diff --git a/atelier-synth/Cargo.toml b/atelier-synth/Cargo.toml index e124355..815b103 100644 --- a/atelier-synth/Cargo.toml +++ b/atelier-synth/Cargo.toml @@ -31,8 +31,8 @@ path = "src/lib.rs" [dependencies] # Atelier sub-modules -atelier_dcml = { path = "../atelier-dcml", version = "0.0.1" } -atelier_data = { path = "../atelier-data", version = "0.0.1" } +atelier_dcml = { path = "../atelier-dcml", version = "0.0.10" } +atelier_data = { path = "../atelier-data", version = "0.0.10" } atelier_generators = { path = "../atelier-generators", version = "0.0.1" } atelier_results = { path = "../atelier-results", version = "0.0.1" } diff --git a/atelier-synth/examples/single_synthetic_ob.rs b/atelier-synth/examples/single_synthetic_ob.rs index e0ace51..fec49cc 100644 --- a/atelier-synth/examples/single_synthetic_ob.rs +++ b/atelier-synth/examples/single_synthetic_ob.rs @@ -30,17 +30,17 @@ pub fn main() { println!("template_orderbook: {template_orderbook:?}"); // --- Create progressions - let orderbooks = progressions(template_orderbook, returns_model, n_progres).unwrap(); + let orderbooks = progressions(&template_orderbook, &returns_model, n_progres); // --- Print results for debug purposes - for (i, _) in orderbooks.iter().enumerate().take(n_progres) { + for (i, book) in orderbooks.iter().enumerate() { println!( "orderbook_{} - bid {:.4} - ask {:.4} - spread {:.4} - mid {:.4}", i, - orderbooks[i].bids[0].price, - orderbooks[i].asks[0].price, - orderbooks[i].asks[0].price - orderbooks[i].bids[0].price, - (orderbooks[i].bids[0].price + orderbooks[i].asks[0].price) / 2.0, + book.bids[0].price, + book.asks[0].price, + book.asks[0].price - book.bids[0].price, + (book.bids[0].price + book.asks[0].price) / 2.0, ) } } diff --git a/atelier-synth/src/agent.rs b/atelier-synth/src/agent.rs new file mode 100644 index 0000000..f150de8 --- /dev/null +++ b/atelier-synth/src/agent.rs @@ -0,0 +1,129 @@ +use std::time::Duration; + +use atelier_data::{ + orderbooks::Orderbook, + templates::{agent::Config, exchanges::OrderbookConfig, models::ModelConfig}, +}; +use tokio::{sync::mpsc::UnboundedSender, time::interval}; + +use crate::synthbooks::progressions; + +pub struct Agent { + pub model: ModelConfig, + // TODO expect on everything until refactor + pub orderbook: OrderbookConfig, +} + +// glue +impl From for Agent { + fn from(value: Config) -> Self { + let Config { + model, orderbook, .. + } = value; + Self { model, orderbook } + } +} + +impl Agent { + pub fn generator(self, tx: UnboundedSender) { + tokio::spawn(async move { + let Self { + model, + orderbook: order_book, + } = &self; + let id = &model.id; + let p = order_book.update_freq.expect("required"); + let mut tick = interval(Duration::from_micros(p)); + tick.reset(); + loop { + tick.tick().await; + let x = progressions(order_book, model, 1).pop().unwrap(); + if let Err(e) = tx.send(x) { + // TODO add tracing; + println!("{id:?} {e}"); + break; + } + } + }); + } +} + +#[cfg(test)] +mod test { + use super::*; + use atelier_data::templates::{ + agent::Config, exchanges::OrderbookConfig, models::ModelConfig, + }; + use futures::future::join_all; + use tokio::{sync::mpsc::unbounded_channel, time::Instant}; + + #[tokio::test] + async fn generator_works() { + let agent_1 = Config { + name: String::from("a"), + model: ModelConfig { + id: Some(String::from("mod_00")), + label: Some(atelier_data::templates::models::Models::GBM), + description: Some(String::from("Geometric Brownian Motion")), + params_labels: Some(vec![String::from("mu"), String::from("sigma")]), + params_values: Some(vec![0.001, 1e-6]), + seed: Some(20995), + }, + orderbook: OrderbookConfig { + update_freq: Some(100_000), + bid_price: Some(3000.0), + bid_levels: Some(vec![4, 12]), + bid_orders: Some(vec![5, 15]), + ticksize: Some(vec![0.2, 2.0]), + ask_price: Some(3001.0), + ask_levels: Some(vec![3, 13]), + ask_orders: Some(vec![6, 16]), + rands: None, + }, + }; + let agent_2 = { + let mut x = agent_1.clone(); + x.orderbook.update_freq = Some(50_000); + x + }; + // assert on freq. + let agents = vec![agent_1, agent_2]; + + let freqs: Vec<_> = agents + .iter() + .map(|m| m.orderbook.update_freq.unwrap()) + .collect(); + + let rxs = agents + .into_iter() + .map(|a| { + let a: Agent = a.into(); + let (tx, rx) = unbounded_channel(); + a.generator(tx); + rx + }) + .collect::>(); + + let x = rxs.into_iter().zip(freqs.clone()).map(move |(mut rx, t)| { + tokio::spawn(async move { + for i in 0..10 { + let start = Instant::now(); + rx.recv().await.unwrap(); + let t_h = start.elapsed().as_micros(); + let x = t.abs_diff(t_h as u64); + let ratio_in_pct = x * 100 / t; + // warm up + if i > 3 { + println!("{:?} {:?}", t_h, t); + assert!(ratio_in_pct < 2, "{:?} {:?}", ratio_in_pct, t); + } + } + }) + }); + join_all(x) + .await + .into_iter() + .collect::, _>>() + .unwrap(); + } +} diff --git a/atelier-synth/src/lib.rs b/atelier-synth/src/lib.rs index b527ff0..abdda4f 100644 --- a/atelier-synth/src/lib.rs +++ b/atelier-synth/src/lib.rs @@ -5,5 +5,6 @@ #![allow(clippy::too_many_arguments)] +pub mod agent; /// Synthetic Orderbooks Generation pub mod synthbooks; diff --git a/atelier-synth/src/synthbooks.rs b/atelier-synth/src/synthbooks.rs index 58a7559..b5f43d9 100644 --- a/atelier-synth/src/synthbooks.rs +++ b/atelier-synth/src/synthbooks.rs @@ -2,9 +2,13 @@ use atelier_data::{ orderbooks::Orderbook, templates::{exchanges::OrderbookConfig, models::ModelConfig, models::Models}, }; -use atelier_generators::{brownian, probabilistic}; +use atelier_generators::{ + brownian::{self}, + probabilistic::{Sampling, UniformDistribution}, +}; use atelier_results::errors; use futures::future::join_all; +use rand::{rngs::SmallRng, SeedableRng}; use std::error::Error; use tokio::task; @@ -79,10 +83,10 @@ pub fn progress( /// - If μ or σ lead to negative prices /// pub fn progressions( - template_orderbook: OrderbookConfig, - template_model: ModelConfig, + template_orderbook: &OrderbookConfig, + template_model: &ModelConfig, n_progres: usize, -) -> Result, errors::SynthetizerError> { +) -> Vec { let mut v_orderbooks: Vec = Vec::new(); // Extract and calculate initial values @@ -102,25 +106,34 @@ pub fn progressions( let lower = template_model.clone().params_values.as_ref().unwrap()[0]; let upper = template_model.clone().params_values.as_ref().unwrap()[1]; - let bid_return = probabilistic::uniform_return(lower, upper, 1)[0]; - let ask_return = probabilistic::uniform_return(lower, upper, 1)[0]; + let dist = UniformDistribution { lower, upper }; - (bid_return, ask_return) - } + let sample = if let Some(seed) = template_model.seed { + let mut rng = SmallRng::seed_from_u64(seed); + dist.sample(&mut rng, 2) + } else { + let mut rng = rand::rng(); + dist.sample(&mut rng, 2) + }; + (sample[0], sample[1]) + } Models::GBM => { let mu = template_model.clone().params_values.as_ref().unwrap()[0]; let sigma = template_model.clone().params_values.unwrap()[1]; let dt = (update_freq / 1e3) / SECONDS_IN_YEAR as f64; - let n = 1; - let bid_return = - brownian::gbm_return(current_bid_price, mu, sigma, dt, n).unwrap()[0]; - - let ask_return = - brownian::gbm_return(current_ask_price, mu, sigma, dt, n).unwrap()[0]; - - (bid_return, ask_return) + let sample = if let Some(seed) = template_model.seed { + let mut rng = SmallRng::seed_from_u64(seed); + brownian::gbm_return(&mut rng, current_bid_price, mu, sigma, dt, 2) + .unwrap() + } else { + let mut rng = rand::rng(); + brownian::gbm_return(&mut rng, current_ask_price, mu, sigma, dt, 2) + .unwrap() + }; + + (sample[0], sample[1]) } _ => (0.001, 0.001), @@ -174,7 +187,7 @@ pub fn progressions( v_orderbooks.push(r_ob); } - Ok(v_orderbooks) + v_orderbooks } /// Executes multiple orderbook progression scenarios concurrently. @@ -205,7 +218,7 @@ pub async fn async_progressions( .into_iter() .zip(model_templates.into_iter()) .map(|(ob, model)| async move { - task::spawn_blocking(move || progressions(ob, model, n_progres)) + task::spawn_blocking(move || progressions(&ob, &model, n_progres)) .await .map_err(|e| errors::SynthetizerError::GenerationError(e.to_string())) }) @@ -213,26 +226,8 @@ pub async fn async_progressions( let results = join_all(tasks).await; - // Process the results - // Process results: aggregate successes or return first error - let mut all_progressions = Vec::with_capacity(results.len()); - println!("results: {:?}", results.len()); - for res in results { - match res { - // Handle task join error - Err(join_err) => { - join_err.to_string(); - } - - // Handle progression execution error - Ok(Err(e)) => return Err(e), - - // Aggregate successful orderbooks - Ok(Ok(v_orderbook)) => all_progressions.push(v_orderbook), - } - } - - Ok(all_progressions) + // try_collect + results.into_iter().collect::>() } diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 9855302..2bb6257 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -30,7 +30,7 @@ license = "Apache-2.0" rand = { version = "0.9.0" } # Atelier sub-modules -atelier_data = { path = "../atelier-data", version = "0.0.1" } +atelier_data = { path = "../atelier-data", version = "0.0.10" } atelier_generators = { path = "../atelier-generators", version = "0.0.1" } # Benchmarks diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 065aedf..a570028 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -27,8 +27,8 @@ license = "Apache-2.0" [dependencies] # Atelier internal dependencies -atelier_data = { path = "../atelier-data", version = "0.0.1" } -atelier_dcml = { path = "../atelier-dcml", version = "0.0.1" } +atelier_data = { path = "../atelier-data", version = "0.0.10" } +atelier_dcml = { path = "../atelier-dcml", version = "0.0.10" } atelier_generators = { path = "../atelier-generators", version = "0.0.1" } atelier_results = { path = "../atelier-results", version = "0.0.1" } atelier_rs = { path = "../atelier-rs", version = "0.0.1" } diff --git a/examples/case_a/case_a_data.rs b/examples/case_a/case_a_data.rs index b3a2d0f..91301c7 100644 --- a/examples/case_a/case_a_data.rs +++ b/examples/case_a/case_a_data.rs @@ -1,5 +1,5 @@ -use atelier_data::{data, templates}; -use atelier_dcml::{features, targets}; +use atelier_data::{data, templates, orderbooks::Orderbook}; +use atelier_dcml::{features::order_book, targets}; use atelier_synth::synthbooks::progressions; use std::{env, path::Path}; @@ -28,7 +28,7 @@ pub async fn main() { let returns_model = template.models[0].clone(); // --- Create Orderbook Progressions - let orderbook = progressions(template_orderbook, returns_model, n_progres); + let orderbook: Vec = progressions(&template_orderbook, &returns_model, n_progres); // --- Orderbook data file (json) let file_name_ob = exp_id.to_owned() + "_ob" + ".json"; @@ -46,12 +46,12 @@ pub async fn main() { let selected_features = ["spread", "midprice", "w_midprice", "vwap", "imb", "tav"]; let depth: usize = 10; let bps: f64 = 1.0; - let features_vec = features::compute_features( - &orderbook.as_ref().unwrap(), + let features_vec = order_book::compute_features( + &orderbook, &selected_features, depth, bps, - features::FeaturesOutput::Values, + order_book::FeaturesOutput::Values, ); // println!("features_vec: {:?}", features_vec); @@ -59,7 +59,7 @@ pub async fn main() { // -- Compute Target from Orderbook Synthetic Data let selected_target = ["return_sign"]; let target_vec = targets::compute_targets( - &orderbook.as_ref().unwrap(), + &orderbook.as_ref(), &selected_target, targets::TargetsOutput::Values, ); diff --git a/examples/case_b/case_b_data.rs b/examples/case_b/case_b_data.rs index 9128197..65da922 100644 --- a/examples/case_b/case_b_data.rs +++ b/examples/case_b/case_b_data.rs @@ -34,7 +34,7 @@ pub async fn main() { let returns_model = template.models[i_exchange].clone(); // --- Create Orderbook Progressions - let orderbook = progressions(template_orderbook, returns_model, n_progres); + let orderbook = progressions(&template_orderbook, &returns_model, n_progres); // --- Orderbook data file (json) let file_name_ob = exp_id.to_owned() @@ -52,7 +52,7 @@ pub async fn main() { .unwrap() .to_owned(); - data::write_to_json(orderbook.as_ref().unwrap(), &folder_route_ob); + data::write_to_json(orderbook.as_ref(), &folder_route_ob); // --- Compute Features from Orderbook Synthetic Data let selected_features = @@ -60,7 +60,7 @@ pub async fn main() { let depth = features.params_values.clone().unwrap()[0] as usize; let bps = features.params_values.clone().unwrap()[1] as f64; let features_vec = features::compute_features( - &orderbook.as_ref().unwrap(), + &orderbook.as_ref(), &selected_features, depth, bps, @@ -70,7 +70,7 @@ pub async fn main() { // -- Compute Target from Orderbook Synthetic Data let selected_target = ["return_sign"]; let target_vec = targets::compute_targets( - &orderbook.as_ref().unwrap(), + &orderbook.as_ref(), &selected_target, targets::TargetsOutput::Values, ); diff --git a/tests/Cargo.toml b/tests/Cargo.toml index fab9a21..d378dd9 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -27,8 +27,9 @@ license = "Apache-2.0" [dev-dependencies] # Atelier sub-modules -atelier_data = { path = "../atelier-data", version = "0.0.1" } -atelier_dcml = { path = "../atelier-dcml", version = "0.0.1" } + +atelier_data = { path = "../atelier-data", version = "0.0.10" } +atelier_dcml = { path = "../atelier-dcml", version = "0.0.10" } atelier_generators = { path = "../atelier-generators", version = "0.0.1" } atelier_results = { path = "../atelier-results", version = "0.0.1" } atelier_synth = { path = "../atelier-synth", version = "0.0.1" } @@ -53,22 +54,6 @@ tch = { version = "0.20.0" } name = "test_loss_function" path = "dcml/functions/test_loss_functions.rs" -[[test]] -name = "test_compute_features" -path = "dcml/features/test_compute_features.rs" - -[[test]] -name = "test_compute_targets" -path = "dcml/targets/test_compute_targets.rs" - -# [[test]] -# name = "test_singular_training" -# path = "dcml/test_singular_training.rs" -# -# [[test]] -# name = "test_distributed_training" -# path = "dcml/test_distributed_training.rs" - [[test]] name = "test_single_synthetic_ob" path = "synth/test_single_synthetic_ob.rs" diff --git a/tests/dcml/training/test_singular.rs b/tests/dcml/training/test_singular.rs deleted file mode 100644 index 599f693..0000000 --- a/tests/dcml/training/test_singular.rs +++ /dev/null @@ -1,33 +0,0 @@ -#[cfg(test)] - -// -- ----------------------------------------------------------------- TESTS UTILS -- // -// -- ----------------------------------------------------------------- ----------- -- // - -mod test_utils { - - // ------------------------------------------------------------- TEST ORDERBOOK -- // - - pub fn generate_features(n_features: u32) -> Vec { - // dataset - // models - // loss - // metrics - // topology - // optimizer - // strategy - // learning - - } -} - -mod tests { - - // ----------------------------------------------------------- COMPUTE_FEATURES -- // - - #[test] - fn test_distributed_p3() { - - } - -} - diff --git a/tests/generators/test_probabilistic_fit.rs b/tests/generators/test_probabilistic_fit.rs index 420805b..e16b342 100644 --- a/tests/generators/test_probabilistic_fit.rs +++ b/tests/generators/test_probabilistic_fit.rs @@ -27,6 +27,8 @@ mod tests { // ------------------------------------------------------------- FIT PDF PARAMS -- // + use rand::{rng, rngs::ThreadRng}; + #[test] fn test_probabilistic_fit() { use crate::test_utils::test_orderbook; @@ -54,11 +56,20 @@ mod tests { let f_lambda = 250.0; let mut lmbd = probabilistic::Poisson { lambda: f_lambda }; - println!("Before fit: {:?} - Samples: {:?}", lmbd, lmbd.sample(4)); + let mut rng = rng(); + println!( + "Before fit: {:?} - Samples: {:?}", + lmbd, + lmbd.sample(&mut rng, 4) + ); lmbd.fit(l_obts.clone()); let n_lambda = lmbd.lambda.clone(); - println!("After fit: {:?} - Samples: {:?}", lmbd, lmbd.sample(4)); + println!( + "After fit: {:?} - Samples: {:?}", + lmbd, + lmbd.sample(&mut rng, 4) + ); // Verify lambda was updated let lambdas_diff = (f_lambda - n_lambda).abs(); diff --git a/tests/synth/test_single_synthetic_ob.rs b/tests/synth/test_single_synthetic_ob.rs index 59b13e1..32e1d05 100644 --- a/tests/synth/test_single_synthetic_ob.rs +++ b/tests/synth/test_single_synthetic_ob.rs @@ -31,29 +31,27 @@ mod tests { // --- Extract parameters from template let returns_model = template.models[0].clone(); - println!("\nmodel: {:?}", returns_model); + println!("\nmodel: {:#?}", returns_model); let n_progres = template.experiments[0].n_progressions as usize; - println!("\nn_progres: {:?}", n_progres); - let template_orderbook = template.exchanges[0].orderbook.clone().unwrap(); - println!("\ntemplate_orderbook: {:?}", template_orderbook); + println!("\ntemplate_orderbook: {:#?}", template_orderbook); // --- Create progressions let orderbooks = - progressions(template_orderbook, returns_model, n_progres).unwrap(); + progressions(&template_orderbook, &returns_model, n_progres); println!("\n"); // --- Print results for debug purposes - for i in 0..n_progres { + for (i, book) in orderbooks.iter().enumerate() { println!( "orderbook_{} - bid {:.4} - ask {:.4} - spread {:.4} - mid {:.4}", i, - orderbooks[i].bids[0].price, - orderbooks[i].asks[0].price, - orderbooks[i].asks[0].price - orderbooks[i].bids[0].price, - (orderbooks[i].bids[0].price + orderbooks[i].asks[0].price) / 2.0, + book.bids[0].price, + book.asks[0].price, + book.asks[0].price - book.bids[0].price, + (book.bids[0].price + book.asks[0].price) / 2.0, ) } } diff --git a/tests/templates/test_temp_synth_00.toml b/tests/templates/test_temp_synth_00.toml index 54a54b1..749f7e6 100644 --- a/tests/templates/test_temp_synth_00.toml +++ b/tests/templates/test_temp_synth_00.toml @@ -28,3 +28,4 @@ label = "GBM" description = "Geometric Brownian Motion" params_labels = ["mu", "sigma"] params_values = [1e-3, 1e-6] +seed = 20995