Files
foxhunt/tests/fixtures/test_data.rs
jgrusewski 001624c5b2 fix: eliminate all 8,384 clippy warnings across workspace
Systematic clippy warning cleanup achieving zero warnings:

- Add domain-appropriate crate-level #![allow(...)] to 20+ crate roots
  for pedantic lints that are noise in HFT/ML code (float_arithmetic,
  indexing_slicing, missing_const_for_fn, cognitive_complexity, etc.)
- Fix attribute ordering in risk/src/lib.rs: move #![warn(clippy::pedantic)]
  before #![allow(...)] so individual allows correctly override pedantic
- Remove module-level #![warn(clippy::pedantic)] from 8 trading_engine
  submodules that were overriding crate-level allows
- Add 45+ workspace-level lint allows in Cargo.toml for common pedantic
  noise (mixed_attributes_style, cargo_common_metadata, etc.)
- Auto-fix 67 machine-applicable warnings (redundant_closure, clone_on_copy,
  unnecessary_cast, etc.) via cargo clippy --fix
- Fix 3 unsafe JSON indexing in risk/circuit_breaker.rs with safe .get()
- Fix unused variables, unused mut, unnecessary parens in 4 files
- Proto-generated code: suppress missing_const_for_fn, indexing_slicing,
  cognitive_complexity in ctrader-openapi and service crates

75 files changed across 20+ crates. All tests pass (3,122+ verified).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 19:16:35 +01:00

820 lines
26 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Test Data Utilities and Generators for Foxhunt HFT Trading System
//!
//! This module provides utilities for generating test data, including
//! market data, time series, random data generation, and test data persistence.
//!
//! ## Usage
//!
//! ```rust
//! use tests::fixtures::test_data::*;
//!
//! // Generate market data
//! let market_data = MarketDataGenerator::new()
//! .with_symbol(TEST_EQUITY_1)
//! .generate_price_series(1000);
//!
//! // Generate time series data
//! let time_series = TimeSeriesGenerator::new()
//! .with_frequency(Duration::from_secs(1))
//! .generate_ohlcv(24 * 60 * 60); // 1 day of second data
//!
//! // Generate random portfolios
//! let random_portfolio = RandomDataGenerator::new()
//! .generate_random_portfolio(10); // 10 positions
//! ```
use ::rust_decimal::Decimal;
use chrono::{DateTime, Duration as ChronoDuration, Timelike, Utc};
use rand::distributions::Distribution;
use rand::{rngs::StdRng, Rng, SeedableRng};
use rand_distr::Normal;
use serde_json::json;
use std::collections::HashMap;
use uuid::Uuid;
// Import types from risk crate
use super::builders::*;
use super::*;
use crate::fixtures::helpers::ToDecimal;
use risk::risk_types::{Position, StressScenario};
/// Legacy constant for backward compatibility
pub const SAMPLE_PRICE: f64 = 100.0;
// =============================================================================
// MARKET DATA GENERATOR
// =============================================================================
/// Generator for realistic market data with configurable parameters
#[derive(Debug, Clone)]
pub struct MarketDataGenerator {
pub symbol: String,
pub initial_price: Decimal,
pub volatility: f64, // Daily volatility (e.g., 0.02 = 2%)
pub drift: f64, // Daily drift (e.g., 0.0001 = 0.01%)
pub tick_size: Decimal, // Minimum price increment
pub bid_ask_spread: Decimal, // Spread in price units
pub seed: Option<u64>, // Random seed for reproducible data
}
impl Default for MarketDataGenerator {
fn default() -> Self {
Self::new()
}
}
impl MarketDataGenerator {
pub fn new() -> Self {
Self {
symbol: TEST_EQUITY_1.to_string(),
initial_price: Decimal::from(100),
volatility: 0.02, // 2% daily volatility
drift: 0.0001, // 0.01% daily drift
tick_size: Decimal::new(1, 2), // $0.01
bid_ask_spread: Decimal::new(2, 2), // $0.02
seed: Some(42), // Deterministic by default for testing
}
}
pub fn with_symbol(mut self, symbol: impl Into<String>) -> Self {
self.symbol = symbol.into();
self
}
pub fn with_initial_price(mut self, price: Decimal) -> Self {
self.initial_price = price;
self
}
pub fn with_volatility(mut self, volatility: f64) -> Self {
self.volatility = volatility;
self
}
pub fn with_drift(mut self, drift: f64) -> Self {
self.drift = drift;
self
}
pub fn with_tick_size(mut self, tick_size: Decimal) -> Self {
self.tick_size = tick_size;
self
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
pub fn random(mut self) -> Self {
self.seed = None;
self
}
/// Generate a price series using geometric Brownian motion
pub fn generate_price_series(&self, count: usize) -> Vec<PricePoint> {
let mut rng = match self.seed {
Some(seed) => StdRng::seed_from_u64(seed),
None => StdRng::from_entropy(),
};
let normal = Normal::new(0.0, 1.0).unwrap();
let mut prices = Vec::with_capacity(count);
let mut current_price = self.initial_price;
let start_time = Utc::now();
// Convert daily parameters to per-tick parameters
let dt = 1.0 / (252.0 * 24.0 * 60.0 * 60.0); // Assume 1-second intervals
let drift_per_tick = self.drift * dt;
let vol_per_tick = self.volatility * dt.sqrt();
for i in 0..count {
let timestamp = start_time + ChronoDuration::seconds(i as i64);
// Geometric Brownian Motion: dS = μS dt + σS dW
let random_shock = normal.sample(&mut rng);
let price_change_pct = drift_per_tick + vol_per_tick * random_shock;
let new_price = current_price
* (Decimal::ONE + Decimal::try_from(price_change_pct).unwrap_or(Decimal::ZERO));
// Round to tick size
let rounded_price = self.round_to_tick_size(new_price);
current_price = rounded_price;
prices.push(PricePoint {
symbol: self.symbol.clone(),
timestamp,
price: current_price,
bid: current_price - (self.bid_ask_spread / Decimal::from(2)),
ask: current_price + (self.bid_ask_spread / Decimal::from(2)),
volume: Decimal::from(rng.gen_range(100..=10000)),
sequence: i as u64,
});
}
prices
}
/// Generate OHLCV bars from price series
pub fn generate_ohlcv_bars(&self, count: usize, bar_duration: ChronoDuration) -> Vec<OHLCVBar> {
let price_points = self.generate_price_series(count * 60); // 60 ticks per bar
let mut bars = Vec::new();
let mut current_bar: Option<OHLCVBar> = None;
for point in price_points {
let bar_start = self.get_bar_start_time(point.timestamp, bar_duration);
match &mut current_bar {
Some(bar) if bar.timestamp == bar_start => {
// Update existing bar
bar.high = bar.high.max(point.price);
bar.low = bar.low.min(point.price);
bar.close = point.price;
bar.volume += point.volume;
},
_ => {
// Start new bar
if let Some(completed_bar) = current_bar.take() {
bars.push(completed_bar);
}
current_bar = Some(OHLCVBar {
symbol: self.symbol.clone(),
timestamp: bar_start,
open: point.price,
high: point.price,
low: point.price,
close: point.price,
volume: point.volume,
});
},
}
if bars.len() >= count {
break;
}
}
if let Some(final_bar) = current_bar {
bars.push(final_bar);
}
bars.truncate(count);
bars
}
/// Generate market depth (order book) data
pub fn generate_market_depth(&self, levels: usize) -> MarketDepth {
let mut rng = match self.seed {
Some(seed) => StdRng::seed_from_u64(seed),
None => StdRng::from_entropy(),
};
let mid_price = self.initial_price;
let mut bids = Vec::with_capacity(levels);
let mut asks = Vec::with_capacity(levels);
for i in 0..levels {
let level_offset = Decimal::from(i + 1) * self.tick_size;
let bid_price = mid_price - level_offset;
let ask_price = mid_price + level_offset;
let bid_size = Decimal::from(rng.gen_range(100..=5000));
let ask_size = Decimal::from(rng.gen_range(100..=5000));
bids.push(DepthLevel {
price: bid_price,
size: bid_size,
order_count: rng.gen_range(1..=10),
});
asks.push(DepthLevel {
price: ask_price,
size: ask_size,
order_count: rng.gen_range(1..=10),
});
}
MarketDepth {
symbol: self.symbol.clone(),
timestamp: Utc::now(),
bids,
asks,
}
}
fn round_to_tick_size(&self, price: Decimal) -> Decimal {
if self.tick_size == Decimal::ZERO {
return price;
}
let ticks = price / self.tick_size;
let rounded_ticks = ticks.round();
rounded_ticks * self.tick_size
}
fn get_bar_start_time(
&self,
timestamp: DateTime<Utc>,
duration: ChronoDuration,
) -> DateTime<Utc> {
let seconds_since_epoch = timestamp.timestamp();
let duration_seconds = duration.num_seconds();
let bar_start_seconds = (seconds_since_epoch / duration_seconds) * duration_seconds;
DateTime::from_timestamp(bar_start_seconds, 0).unwrap_or(timestamp)
}
}
// =============================================================================
// TIME SERIES GENERATOR
// =============================================================================
/// Generator for time series data with various patterns
#[derive(Debug, Clone)]
pub struct TimeSeriesGenerator {
pub frequency: ChronoDuration,
pub start_time: DateTime<Utc>,
pub trend: f64, // Linear trend component
pub seasonality: f64, // Seasonal amplitude
pub noise_level: f64, // Random noise level
pub seed: Option<u64>,
}
impl Default for TimeSeriesGenerator {
fn default() -> Self {
Self::new()
}
}
impl TimeSeriesGenerator {
pub fn new() -> Self {
Self {
frequency: ChronoDuration::minutes(1),
start_time: Utc::now() - ChronoDuration::hours(24),
trend: 0.0,
seasonality: 0.1,
noise_level: 0.05,
seed: Some(42),
}
}
pub fn with_frequency(mut self, frequency: ChronoDuration) -> Self {
self.frequency = frequency;
self
}
pub fn with_start_time(mut self, start_time: DateTime<Utc>) -> Self {
self.start_time = start_time;
self
}
pub fn with_trend(mut self, trend: f64) -> Self {
self.trend = trend;
self
}
pub fn with_seasonality(mut self, seasonality: f64) -> Self {
self.seasonality = seasonality;
self
}
pub fn with_noise_level(mut self, noise_level: f64) -> Self {
self.noise_level = noise_level;
self
}
/// Generate time series with trend, seasonality, and noise
pub fn generate_series(&self, count: usize, base_value: f64) -> Vec<TimeSeriesPoint> {
let mut rng = match self.seed {
Some(seed) => StdRng::seed_from_u64(seed),
None => StdRng::from_entropy(),
};
let normal = Normal::new(0.0, self.noise_level).unwrap();
let mut series = Vec::with_capacity(count);
for i in 0..count {
let timestamp = self.start_time + (self.frequency * i as i32);
// Trend component
let trend_value = self.trend * i as f64;
// Seasonal component (daily pattern)
let hour_of_day = timestamp.hour() as f64;
let seasonal_value =
self.seasonality * (2.0 * std::f64::consts::PI * hour_of_day / 24.0).sin();
// Noise component
let noise_value = normal.sample(&mut rng);
// Combine components
let value = base_value + trend_value + seasonal_value + noise_value;
series.push(TimeSeriesPoint {
timestamp,
value: Decimal::try_from(value).unwrap_or(Decimal::from(0)),
metadata: json!({
"trend": trend_value,
"seasonal": seasonal_value,
"noise": noise_value
}),
});
}
series
}
/// Generate OHLCV data from time series
pub fn generate_ohlcv(&self, count: usize) -> Vec<OHLCVBar> {
let base_price = 100.0;
let series = self.generate_series(count, base_price);
let mut ohlcv = Vec::with_capacity(count);
for point in series {
let price = point.value;
let volume = Decimal::from(1000 + (point.timestamp.timestamp() % 9000));
ohlcv.push(OHLCVBar {
symbol: TEST_EQUITY_1.to_string(),
timestamp: point.timestamp,
open: price,
high: price * Decimal::new(1001, 3), // +0.1%
low: price * Decimal::new(999, 3), // -0.1%
close: price,
volume,
});
}
ohlcv
}
}
// =============================================================================
// RANDOM DATA GENERATOR
// =============================================================================
/// Generator for random test data with realistic distributions
#[derive(Debug)]
pub struct RandomDataGenerator {
rng: StdRng,
}
impl RandomDataGenerator {
pub fn new() -> Self {
Self {
rng: StdRng::seed_from_u64(42),
}
}
pub fn with_seed(seed: u64) -> Self {
Self {
rng: StdRng::seed_from_u64(seed),
}
}
/// Generate a random portfolio with specified number of positions
pub fn generate_random_portfolio(
&mut self,
position_count: usize,
) -> (Portfolio, Vec<Instrument>, Vec<Position>) {
let portfolio_id = format!("RANDOM_PORTFOLIO_{}", self.rng.gen::<u32>());
let portfolio = PortfolioBuilder::new()
.with_id(&portfolio_id)
.with_name("Random Test Portfolio")
.with_base_currency("USD")
.strategy_portfolio()
.build();
let mut instruments = Vec::with_capacity(position_count);
let mut positions = Vec::with_capacity(position_count);
let asset_classes = [
AssetClass::Equities,
AssetClass::Currencies,
AssetClass::FixedIncome,
AssetClass::Derivatives,
AssetClass::Commodities,
AssetClass::Alternatives,
];
for i in 0..position_count {
let asset_class = asset_classes[self.rng.gen_range(0..asset_classes.len())];
let symbol = generate_test_symbol(asset_class);
// Generate random instrument
let mut instrument_builder = InstrumentBuilder::new()
.with_symbol(&symbol)
.with_name(format!("Random Instrument {}", i + 1));
instrument_builder = match asset_class {
AssetClass::Equities => instrument_builder.equity(),
AssetClass::Currencies => instrument_builder.currency(),
AssetClass::FixedIncome => instrument_builder.bond(),
AssetClass::Derivatives => instrument_builder.future(),
AssetClass::Commodities => instrument_builder.commodity(),
AssetClass::Alternatives => instrument_builder.crypto(),
AssetClass::Cash => instrument_builder.equity(),
};
instruments.push(instrument_builder.build());
// Generate random position
let quantity = Decimal::from(self.rng.gen_range(100..=10000));
let price = self.rng.gen_range(10.0..=1000.0).to_decimal();
let price_change_pct = self.rng.gen_range(-0.1..=0.1); // ±10%
let market_price = price
* (Decimal::ONE + Decimal::try_from(price_change_pct).unwrap_or(Decimal::ZERO));
let position = PositionBuilder::new()
.with_portfolio_id(&portfolio_id)
.with_symbol(&symbol)
.with_quantity(quantity)
.with_average_price(price)
.with_market_price(market_price)
.with_beta(Decimal::try_from(self.rng.gen_range(0.5..=2.0)).unwrap())
.build();
positions.push(position);
}
(portfolio, instruments, positions)
}
/// Generate random market events
pub fn generate_random_events(&mut self, count: usize) -> Vec<MarketEvent> {
let event_types = ["trade", "quote", "news", "halt", "resume"];
let symbols = ALL_TEST_SYMBOLS;
let mut events = Vec::with_capacity(count);
for i in 0..count {
let event_type = event_types[self.rng.gen_range(0..event_types.len())];
let symbol = symbols[self.rng.gen_range(0..symbols.len())];
let timestamp = Utc::now() + ChronoDuration::seconds(i as i64);
events.push(MarketEvent {
id: Uuid::new_v4(),
event_type: event_type.to_string(),
symbol: symbol.to_string(),
timestamp,
data: json!({
"random_data": true,
"sequence": i,
"value": self.rng.gen_range(1.0..=1000.0)
}),
});
}
events
}
/// Generate random stress test scenarios
pub fn generate_random_stress_scenarios(&mut self, count: usize) -> Vec<StressScenario> {
let scenario_types = ["Historical", "Hypothetical", "Monte Carlo"];
let mut scenarios = Vec::with_capacity(count);
for i in 0..count {
let _scenario_type = scenario_types[self.rng.gen_range(0..scenario_types.len())];
let shock_factors = json!({
"equity_shock": self.rng.gen_range(-0.5..=0.2),
"bond_shock": self.rng.gen_range(-0.2..=0.3),
"fx_shock": self.rng.gen_range(-0.3..=0.3),
"commodity_shock": self.rng.gen_range(-0.4..=0.4),
"volatility_multiplier": self.rng.gen_range(1.0..=5.0)
});
let equity_shock = shock_factors
.get("equity_shock")
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
let mut price_shocks = HashMap::new();
price_shocks.insert("EQUITY".to_string(), equity_shock);
scenarios.push(StressScenario {
id: Uuid::new_v4().to_string(),
name: format!("Random Stress Scenario {}", i + 1),
price_shocks: price_shocks.clone(),
market_shocks: price_shocks,
volatility_multiplier: shock_factors
.get("volatility_multiplier")
.and_then(|v| v.as_f64())
.unwrap_or(1.0),
volatility_multipliers: HashMap::new(),
correlation_changes: HashMap::new(),
correlation_adjustments: HashMap::new(),
liquidity_haircuts: HashMap::new(),
});
}
scenarios
}
}
impl Default for RandomDataGenerator {
fn default() -> Self {
Self::new()
}
}
// =============================================================================
// DATA STRUCTURES
// =============================================================================
/// Price point for market data
#[derive(Debug, Clone)]
pub struct PricePoint {
pub symbol: String,
pub timestamp: DateTime<Utc>,
pub price: Decimal,
pub bid: Decimal,
pub ask: Decimal,
pub volume: Decimal,
pub sequence: u64,
}
/// OHLCV bar for candlestick data
#[derive(Debug, Clone)]
pub struct OHLCVBar {
pub symbol: String,
pub timestamp: DateTime<Utc>,
pub open: Decimal,
pub high: Decimal,
pub low: Decimal,
pub close: Decimal,
pub volume: Decimal,
}
/// Market depth level
#[derive(Debug, Clone)]
pub struct DepthLevel {
pub price: Decimal,
pub size: Decimal,
pub order_count: u32,
}
/// Market depth (order book)
#[derive(Debug, Clone)]
pub struct MarketDepth {
pub symbol: String,
pub timestamp: DateTime<Utc>,
pub bids: Vec<DepthLevel>,
pub asks: Vec<DepthLevel>,
}
/// Time series data point
#[derive(Debug, Clone)]
pub struct TimeSeriesPoint {
pub timestamp: DateTime<Utc>,
pub value: Decimal,
pub metadata: serde_json::Value,
}
/// Market event for event-driven testing
#[derive(Debug, Clone)]
pub struct MarketEvent {
pub id: Uuid,
pub event_type: String,
pub symbol: String,
pub timestamp: DateTime<Utc>,
pub data: serde_json::Value,
}
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
/// Create realistic test prices for different asset classes
pub fn create_realistic_test_prices() -> HashMap<String, Decimal> {
let mut prices = HashMap::new();
// Equity prices
for symbol in ALL_TEST_EQUITIES {
prices.insert(
symbol.to_string(),
Decimal::from(100 + (symbol.len() as i64 * 10)),
);
}
// FX prices (rates)
prices.insert(TEST_FOREX_1.to_string(), Decimal::new(12345, 5)); // 1.2345
prices.insert(TEST_FOREX_2.to_string(), Decimal::new(13456, 5)); // 1.3456
prices.insert(TEST_FOREX_3.to_string(), Decimal::from(150)); // 150.00
prices.insert(TEST_FOREX_EXOTIC.to_string(), Decimal::new(2850, 2)); // 28.50 (USDTRY)
// Futures prices
for symbol in ALL_TEST_FUTURES {
prices.insert(
symbol.to_string(),
Decimal::from(4000 + (symbol.len() as i64 * 100)),
);
}
// Bond prices (yield-like)
for symbol in ALL_TEST_BONDS {
prices.insert(
symbol.to_string(),
Decimal::new(250 + (symbol.len() as i64 * 10), 2),
);
}
// Commodity prices
prices.insert(TEST_COMMODITY_1.to_string(), Decimal::from(2000)); // Gold
prices.insert(TEST_COMMODITY_2.to_string(), Decimal::from(25)); // Silver
prices.insert(TEST_COMMODITY_OIL.to_string(), Decimal::from(80)); // Oil
prices.insert(TEST_COMMODITY_GAS.to_string(), Decimal::from(4)); // Natural Gas
// Crypto prices
prices.insert(TEST_CRYPTO_1.to_string(), Decimal::from(50000)); // BTC-like
prices.insert(TEST_CRYPTO_2.to_string(), Decimal::from(3000)); // ETH-like
prices.insert(TEST_CRYPTO_ALT.to_string(), Decimal::from(1)); // Altcoin
// Option prices
prices.insert(TEST_OPTION_CALL.to_string(), Decimal::from(5)); // Call option premium
prices.insert(TEST_OPTION_PUT.to_string(), Decimal::from(3)); // Put option premium
prices
}
/// Create test volatility estimates for different asset classes
pub fn create_test_volatilities() -> HashMap<String, f64> {
let mut volatilities = HashMap::new();
// Equity volatilities (annualized)
for symbol in ALL_TEST_EQUITIES {
volatilities.insert(symbol.to_string(), 0.20); // 20% annual vol
}
// FX volatilities
volatilities.insert(TEST_FOREX_1.to_string(), 0.10); // 10% annual vol
volatilities.insert(TEST_FOREX_2.to_string(), 0.12);
volatilities.insert(TEST_FOREX_3.to_string(), 0.08);
volatilities.insert(TEST_FOREX_EXOTIC.to_string(), 0.18); // Higher vol for exotic pair
// Futures volatilities
for symbol in ALL_TEST_FUTURES {
volatilities.insert(symbol.to_string(), 0.25); // 25% annual vol
}
// Bond volatilities
for symbol in ALL_TEST_BONDS {
volatilities.insert(symbol.to_string(), 0.05); // 5% annual vol
}
// Commodity volatilities
volatilities.insert(TEST_COMMODITY_1.to_string(), 0.15); // Gold
volatilities.insert(TEST_COMMODITY_2.to_string(), 0.20); // Silver
volatilities.insert(TEST_COMMODITY_OIL.to_string(), 0.35); // Oil
volatilities.insert(TEST_COMMODITY_GAS.to_string(), 0.50); // Natural Gas
// Crypto volatilities
volatilities.insert(TEST_CRYPTO_1.to_string(), 0.60); // BTC-like
volatilities.insert(TEST_CRYPTO_2.to_string(), 0.70); // ETH-like
volatilities.insert(TEST_CRYPTO_ALT.to_string(), 0.80); // Altcoin
volatilities
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_market_data_generator() {
let generator = MarketDataGenerator::new();
let prices = generator.generate_price_series(100);
assert_eq!(prices.len(), 100);
assert!(prices[0].price > Decimal::ZERO);
// Check timestamps are sequential
for window in prices.windows(2) {
assert!(window[1].timestamp >= window[0].timestamp);
}
}
#[test]
fn test_ohlcv_generation() {
let generator = MarketDataGenerator::new();
let bars = generator.generate_ohlcv_bars(50, ChronoDuration::minutes(1));
assert_eq!(bars.len(), 50);
for bar in &bars {
assert!(bar.high >= bar.low);
assert!(bar.high >= bar.open);
assert!(bar.high >= bar.close);
assert!(bar.low <= bar.open);
assert!(bar.low <= bar.close);
}
}
#[test]
fn test_time_series_generator() {
let generator = TimeSeriesGenerator::new()
.with_trend(0.1)
.with_seasonality(0.2);
let series = generator.generate_series(100, 100.0);
assert_eq!(series.len(), 100);
// Check that trend is applied (last value should be higher due to positive trend)
assert!(series.last().unwrap().value > series.first().unwrap().value);
}
#[test]
fn test_random_data_generator() {
let mut generator = RandomDataGenerator::new();
let (portfolio, instruments, positions) = generator.generate_random_portfolio(5);
assert_eq!(instruments.len(), 5);
assert_eq!(positions.len(), 5);
assert_eq!(portfolio.id.len() > 0, true);
// Check that positions are created (portfolio_id not stored in Position anymore)
assert!(!positions.is_empty());
}
#[test]
fn test_market_depth_generation() {
let generator = MarketDataGenerator::new();
let depth = generator.generate_market_depth(5);
assert_eq!(depth.bids.len(), 5);
assert_eq!(depth.asks.len(), 5);
// Check that bid prices are decreasing and ask prices are increasing
for window in depth.bids.windows(2) {
assert!(window[0].price > window[1].price);
}
for window in depth.asks.windows(2) {
assert!(window[0].price < window[1].price);
}
}
#[test]
fn test_realistic_test_prices() {
let prices = create_realistic_test_prices();
assert!(!prices.is_empty());
// Check that all test symbols have prices
for &symbol in ALL_TEST_SYMBOLS {
assert!(prices.contains_key(symbol));
assert!(prices[symbol] > Decimal::ZERO);
}
}
#[test]
fn test_volatility_estimates() {
let volatilities = create_test_volatilities();
assert!(!volatilities.is_empty());
// Check that volatilities are reasonable (between 0 and 100%)
for (symbol, &vol) in &volatilities {
assert!(vol > 0.0, "Volatility for {} should be > 0", symbol);
assert!(vol < 1.0, "Volatility for {} should be < 1.0", symbol); // Less than 100% for most assets
}
}
}