Files
foxhunt/crates/ml/src/microstructure/kyle_lambda.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

127 lines
4.1 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.
//! # Kyle's Lambda Estimator
//!
//! Implementation of Kyle's Lambda for measuring price impact and
//! information asymmetry in financial markets.
//!
//! ## Algorithm
//!
//! Kyle's Lambda (λ) measures the price impact per unit of signed order flow:
//! - Returns = λ × SignedOrderFlow + ε
//! - λ is estimated via regression of returns on signed square-root dollar volume
//! - Higher λ indicates greater price impact (lower liquidity)
//!
//! ## Performance
//!
//! - Target latency: <25μs per calculation
//! - Rolling regression with fixed-point arithmetic
//! - Efficient covariance calculation updates
use std::collections::VecDeque;
use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
use serde::{Deserialize, Serialize};
use super::*;
use super::{
#[test]
fn test_kyle_lambda_estimator_creation() {
let estimator = KyleLambdaEstimator::default();
assert_eq!(estimator.get_lambda(), 0.0);
assert_eq!(estimator.get_interval_count(), 0);
assert_eq!(estimator.get_r_squared(), 0.0);
}
#[test]
fn test_trading_interval() {
let mut interval = TradingInterval::new(0, 1000000, 2000000);
let update = MarketDataUpdate {
timestamp: 1500000,
symbol: "AAPL".to_owned(),
price: 150000,
volume: 1000,
bid: 149000,
ask: 151000,
bid_size: 100,
ask_size: 100,
direction: Some(TradeDirection::Buy),
};
interval.add_trade(&update);
assert_eq!(interval.trade_count, 1);
assert_eq!(interval.open_price, 150000);
assert_eq!(interval.close_price, 150000);
assert!(interval.signed_sqrt_dollar_volume > 0); // Buy trade
interval.finalize();
assert!(interval.is_valid());
}
#[test]
fn test_lambda_calculation() {
let config = KyleLambdaConfig {
interval_duration_ns: 1000000, // 1ms for testing
min_trades_per_interval: 1,
regression_window: 5,
..Default::default()
};
let mut estimator = KyleLambdaEstimator::new(config);
// Add trades with price impact pattern
for i in 0..20 {
let price_impact = if i % 2 == 0 { 100 } else { -100 };
let direction = if i % 2 == 0 { TradeDirection::Buy } else { TradeDirection::Sell };
let update = MarketDataUpdate {
timestamp: (i * 2000000) as u64, // 2ms intervals
symbol: "AAPL".to_owned(),
price: 150000 + price_impact,
volume: 1000,
bid: 149000,
ask: 151000,
bid_size: 100,
ask_size: 100,
direction: Some(direction),
};
estimator.update(&update)?;
}
// Should have calculated lambda
let result = estimator.get_result();
assert!(result.interval_count > 0);
// Lambda should be non-zero if there's a price impact pattern
// (exact value depends on the specific pattern)
println!("Lambda: {}, R²: {}", result.lambda, result.r_squared);
}
#[test]
fn test_information_asymmetry() {
let mut estimator = KyleLambdaEstimator::default();
// Add persistent positive returns (trend)
for i in 0..10 {
let update = MarketDataUpdate {
timestamp: (i * 300_000_000_000) as u64, // 5 min intervals
symbol: "AAPL".to_owned(),
price: 150000 + (i * 100), // Trending up
volume: 1000,
bid: 149000,
ask: 151000,
bid_size: 100,
ask_size: 100,
direction: Some(TradeDirection::Buy),
};
estimator.update(&update)?;
}
let info_asymmetry = estimator.get_information_asymmetry();
assert!(info_asymmetry >= 0.0); // Should detect some persistence
}
}