Files
foxhunt/backtesting/src/strategy_tester.rs
jgrusewski ea9d8f2c88 🚨 ARCHITECTURAL DISASTER: THREE Competing Type Sources Discovered
## Critical Investigation Results

**DISASTER CONFIRMED**: Agents discovered THREE type sources instead of ONE:
1. foxhunt-common-types/ (SHOULD NOT EXIST - still active!)
2. trading_engine/src/types/ (massive duplication)
3. common/src/types.rs (depends on competing crate)

## Evidence of Violations
- foxhunt-common-types still in workspace members (line 86)
- common/Cargo.toml depends on foxhunt-common-types (line 48)
- 48+ duplicate type definitions across OrderSide, OrderStatus, OrderType
- Compilation failures due to competing imports

## Immediate Action Required
- Choose ONE canonical source
- DELETE foxhunt-common-types completely
- Consolidate ALL types to single source
- Fix THREE-WAY import chaos

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-26 15:33:34 +02:00

897 lines
28 KiB
Rust

//! Strategy testing framework for backtesting
//!
//! Provides infrastructure for executing trading strategies against historical data,
//! managing positions, tracking performance, and handling risk management.
// Import everything async_trait needs - use fully qualified paths to avoid shadowing
use std::{
collections::{HashMap, VecDeque},
future::Future,
pin::Pin,
sync::Arc,
time::{Duration, Instant},
};
use anyhow::{Context, Result};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use tokio::sync::{mpsc, RwLock};
use tracing::{debug, error, info, warn};
use trading_engine::types::basic::{
Order, OrderId, OrderStatus, OrderType, Position, Price, Quantity, Side as OrderSide, Symbol,
TimeInForce,
};
use trading_engine::types::events::MarketEvent;
use trading_engine::types::prelude::*;
use uuid::Uuid;
// TECHNICAL DEBT ELIMINATED - Use String and DateTime<Utc> directly
use crate::replay_engine::{MarketReplay, ReplayEvent};
/// Trading strategy trait that backtesting strategies must implement
#[async_trait(?Send)]
pub trait Strategy: Send + Sync {
/// Strategy name for identification
fn name(&self) -> &str;
/// Initialize strategy with initial capital and configuration
async fn initialize(&mut self, initial_capital: Decimal, config: StrategyConfig) -> Result<()>;
/// Process market event and generate trading signals
async fn on_market_event(
&mut self,
event: &MarketEvent,
context: &StrategyContext,
) -> Result<Vec<TradingSignal>>;
/// Handle order execution updates
async fn on_order_update(&mut self, order: &Order, context: &StrategyContext) -> Result<()>;
/// Handle position updates
async fn on_position_update(
&mut self,
position: &Position,
context: &StrategyContext,
) -> Result<()>;
/// Strategy cleanup and final calculations
async fn finalize(&mut self, context: &StrategyContext) -> Result<StrategyResult>;
/// Get current strategy state for debugging
async fn get_state(&self) -> Result<serde_json::Value>;
}
/// Strategy configuration parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrategyConfig {
/// Maximum position size per symbol
pub max_position_size: Decimal,
/// Risk per trade as percentage of capital
pub risk_per_trade: Decimal,
/// Maximum number of open positions
pub max_open_positions: u32,
/// Stop loss percentage
pub stop_loss_pct: Option<Decimal>,
/// Take profit percentage
pub take_profit_pct: Option<Decimal>,
/// Strategy-specific parameters
pub parameters: HashMap<String, serde_json::Value>,
/// Enable position sizing
pub position_sizing_enabled: bool,
/// Commission rate per trade
pub commission_rate: Decimal,
/// Slippage factor
pub slippage_factor: Decimal,
}
impl Default for StrategyConfig {
fn default() -> Self {
Self {
max_position_size: Decimal::from(100000),
risk_per_trade: Decimal::new(2, 2), // 2%
max_open_positions: 10,
stop_loss_pct: Some(Decimal::new(5, 2)), // 5%
take_profit_pct: Some(Decimal::new(10, 2)), // 10%
parameters: HashMap::new(),
position_sizing_enabled: true,
commission_rate: Decimal::new(1, 4), // 0.01%
slippage_factor: Decimal::new(5, 5), // 0.005%
}
}
}
/// Context provided to strategy during execution
#[derive(Debug, Clone)]
pub struct StrategyContext {
/// Current timestamp
pub current_time: DateTime<Utc>,
/// Current account balance
pub account_balance: Decimal,
/// Available buying power
pub buying_power: Decimal,
/// Current positions
pub positions: HashMap<Symbol, Position>,
/// Open orders
pub open_orders: HashMap<OrderId, Order>,
/// Current market prices
pub market_prices: HashMap<Symbol, Price>,
/// Performance metrics
pub performance: PerformanceMetrics,
}
/// Trading signal generated by strategy
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradingSignal {
/// Symbol to trade
pub symbol: Symbol,
/// Signal type
pub signal_type: SignalType,
/// Suggested quantity
pub quantity: Quantity,
/// Target price (if limit order)
pub target_price: Option<Price>,
/// Stop loss price
pub stop_loss: Option<Price>,
/// Take profit price
pub take_profit: Option<Price>,
/// Signal confidence (0.0 - 1.0)
pub confidence: Decimal,
/// Additional metadata
pub metadata: HashMap<String, serde_json::Value>,
}
/// Types of trading signals
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SignalType {
Buy,
Sell,
Short,
Cover,
CloseLong,
CloseShort,
CloseAll,
}
/// Strategy execution result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrategyResult {
/// Strategy name
pub strategy_name: String,
/// Total return
pub total_return: Decimal,
/// Annualized return
pub annualized_return: Decimal,
/// Maximum drawdown
pub max_drawdown: Decimal,
/// Sharpe ratio
pub sharpe_ratio: Decimal,
/// Number of trades
pub total_trades: u64,
/// Win rate
pub win_rate: Decimal,
/// Average trade return
pub avg_trade_return: Decimal,
/// Final portfolio value
pub final_value: Decimal,
/// Detailed trade history
pub trades: Vec<TradeRecord>,
/// Performance timeline
pub performance_timeline: Vec<PerformanceSnapshot>,
}
/// Individual trade record
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeRecord {
/// Trade ID
pub trade_id: String,
/// Symbol traded
pub symbol: Symbol,
/// Trade side
pub side: OrderSide,
/// Entry price
pub entry_price: Price,
/// Exit price
pub exit_price: Price,
/// Quantity traded
pub quantity: Quantity,
/// Entry timestamp
pub entry_time: DateTime<Utc>,
/// Exit timestamp
pub exit_time: DateTime<Utc>,
/// Profit/loss
pub pnl: Decimal,
/// Return percentage
pub return_pct: Decimal,
/// Commission paid
pub commission: Decimal,
}
/// Performance snapshot at a point in time
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceSnapshot {
/// Timestamp
pub timestamp: DateTime<Utc>,
/// Portfolio value
pub portfolio_value: Decimal,
/// Cash balance
pub cash_balance: Decimal,
/// Unrealized PnL
pub unrealized_pnl: Decimal,
/// Realized PnL
pub realized_pnl: Decimal,
/// Number of open positions
pub open_positions: u32,
/// Current drawdown
pub drawdown: Decimal,
}
/// Performance metrics tracked during execution
#[derive(Debug, Clone, Default)]
pub struct PerformanceMetrics {
/// Total realized PnL
pub total_realized_pnl: Decimal,
/// Total unrealized PnL
pub total_unrealized_pnl: Decimal,
/// Peak portfolio value
pub peak_value: Decimal,
/// Current drawdown
pub current_drawdown: Decimal,
/// Maximum drawdown
pub max_drawdown: Decimal,
/// Total trades executed
pub total_trades: u64,
/// Winning trades
pub winning_trades: u64,
/// Total commission paid
pub total_commission: Decimal,
/// Returns history
pub daily_returns: VecDeque<Decimal>,
}
/// Strategy tester engine
pub struct StrategyTester {
/// Strategy being tested
strategy: Box<dyn Strategy>,
/// Strategy configuration
config: StrategyConfig,
/// Market replay engine
market_replay: Arc<MarketReplay>,
/// Current account state
account: Arc<RwLock<Account>>,
/// Order management system
order_manager: Arc<OrderManager>,
/// Position tracker
position_tracker: Arc<PositionTracker>,
/// Performance tracker
performance_tracker: Arc<RwLock<PerformanceTracker>>,
/// Current market data
market_data: Arc<DashMap<Symbol, MarketEvent>>,
}
/// Account state for backtesting
#[derive(Debug, Clone)]
pub struct Account {
/// Initial capital
pub initial_capital: Decimal,
/// Current cash balance
pub cash_balance: Decimal,
/// Total portfolio value
pub portfolio_value: Decimal,
/// Account creation time
pub created_at: DateTime<Utc>,
/// Last update time
pub last_updated: DateTime<Utc>,
}
/// Order management for backtesting
pub struct OrderManager {
/// Open orders
orders: DashMap<OrderId, Order>,
/// Order history
order_history: RwLock<Vec<Order>>,
/// Next order ID
next_order_id: std::sync::atomic::AtomicU64,
}
/// Position tracking for backtesting
pub struct PositionTracker {
/// Current positions
positions: DashMap<Symbol, Position>,
/// Position history
position_history: RwLock<Vec<Position>>,
/// Trade records
trade_records: RwLock<Vec<TradeRecord>>,
}
/// Performance tracking
pub struct PerformanceTracker {
/// Performance metrics
metrics: PerformanceMetrics,
/// Performance snapshots
snapshots: Vec<PerformanceSnapshot>,
/// Last snapshot time
last_snapshot: Option<DateTime<Utc>>,
}
impl StrategyTester {
/// Create new strategy tester
pub fn new(
strategy: Box<dyn Strategy>,
config: StrategyConfig,
market_replay: Arc<MarketReplay>,
initial_capital: Decimal,
) -> Self {
let account = Account {
initial_capital,
cash_balance: initial_capital,
portfolio_value: initial_capital,
created_at: Utc::now(),
last_updated: Utc::now(),
};
Self {
strategy,
config,
market_replay,
account: Arc::new(RwLock::new(account)),
order_manager: Arc::new(OrderManager::new()),
position_tracker: Arc::new(PositionTracker::new()),
performance_tracker: Arc::new(RwLock::new(PerformanceTracker::new())),
market_data: Arc::new(DashMap::new()),
}
}
/// Run the strategy test
pub async fn run_test(&mut self) -> Result<StrategyResult> {
info!("Starting strategy test for: {}", self.strategy.name());
// Initialize strategy
self.strategy
.initialize(
self.account.read().await.initial_capital,
self.config.clone(),
)
.await?;
// Get market data receiver
let mut event_receiver = self
.market_replay
.take_receiver()
.await
.context("Failed to get market data receiver")?;
// Start market replay
let replay_handle = {
let replay = Arc::clone(&self.market_replay);
tokio::spawn(async move {
if let Err(e) = replay.start_replay().await {
error!("Market replay failed: {}", e);
}
})
};
// Process market events
while let Some(replay_event) = event_receiver.recv().await {
if let Err(e) = self.process_market_event(replay_event).await {
error!("Failed to process market event: {}", e);
}
}
// Wait for replay to complete
replay_handle.await?;
// Finalize strategy and generate results
let context = self.build_strategy_context().await?;
let result = self.strategy.finalize(&context).await?;
info!(
"Strategy test completed. Total return: {:.2}%",
result.total_return * Decimal::from(100)
);
Ok(result)
}
/// Process a single market event
async fn process_market_event(&mut self, replay_event: ReplayEvent) -> Result<()> {
let event = &replay_event.event;
// Update market data
self.update_market_data(event).await;
// Update account and positions with current market prices
self.update_valuations().await?;
// Process pending orders
self.process_pending_orders().await?;
// Build strategy context
let context = self.build_strategy_context().await?;
// Get trading signals from strategy
let signals = self.strategy.on_market_event(event, &context).await?;
// Execute trading signals
for signal in signals {
self.execute_trading_signal(signal).await?;
}
// Take performance snapshot periodically
self.take_performance_snapshot(&context).await?;
Ok(())
}
/// Update market data cache
async fn update_market_data(&self, event: &MarketEvent) {
let symbol = match event {
MarketEvent::Trade { symbol, .. } => symbol,
MarketEvent::Quote { symbol, .. } => symbol,
MarketEvent::OrderBookUpdate { symbol, .. } => symbol,
MarketEvent::Bar { symbol, .. } => symbol,
MarketEvent::OrderBook { symbol, .. } => symbol,
MarketEvent::Sentiment { .. } => return, // Skip sentiment events for now
MarketEvent::Control { .. } => return, // Skip control events for now
};
self.market_data.insert(symbol.clone(), event.clone());
}
/// Update portfolio valuations
async fn update_valuations(&self) -> Result<()> {
let mut account = self.account.write().await;
let positions = self.position_tracker.get_all_positions().await;
let mut total_value = account.cash_balance;
for (symbol, position) in positions {
if let Some(market_event) = self.market_data.get(&symbol) {
let current_price = self.extract_price_from_event(&market_event)?;
let position_value = position.quantity.to_decimal().unwrap_or_default()
* Price::from(current_price).to_decimal().unwrap_or_default();
if position.quantity.to_decimal().unwrap_or_default() >= Decimal::ZERO {
total_value += position_value;
} else {
// Short position
total_value -= position_value;
}
}
}
account.portfolio_value = total_value;
account.last_updated = Utc::now();
Ok(())
}
/// Process pending orders for execution
async fn process_pending_orders(&mut self) -> Result<()> {
let pending_orders = self.order_manager.get_pending_orders().await;
for order in pending_orders {
// Extract the current price first to avoid borrowing conflicts
let current_price = if let Some(market_event) = self.market_data.get(&order.symbol) {
self.extract_price_from_event(&market_event)?
} else {
continue; // Skip this order if no market data available
};
// Now check if we should execute and execute if needed
if self.should_execute_order(&order, current_price) {
self.execute_order(order).await?;
}
}
Ok(())
}
/// Check if order should be executed
fn should_execute_order(&self, order: &Order, current_price: Price) -> bool {
match order.order_type {
OrderType::Market => true,
OrderType::Iceberg => {
// For backtesting, treat Iceberg orders as market orders
true
}
OrderType::Limit => {
if let Some(order_price) = order.price {
match order.side {
OrderSide::Buy => current_price <= order_price,
OrderSide::Sell => current_price >= order_price,
}
} else {
false
}
}
OrderType::Stop => {
if let Some(order_price) = order.price {
match order.side {
OrderSide::Buy => current_price >= order_price,
OrderSide::Sell => current_price <= order_price,
}
} else {
false
}
}
OrderType::StopLimit => {
// Simplified logic - would need stop price tracking
if let Some(order_price) = order.price {
match order.side {
OrderSide::Buy => current_price >= order_price,
OrderSide::Sell => current_price <= order_price,
}
} else {
false
}
}
}
}
/// Execute an order
async fn execute_order(&mut self, mut order: Order) -> Result<()> {
let current_price = if let Some(market_event) = self.market_data.get(&order.symbol) {
self.extract_price_from_event(&market_event)?
} else {
return Err(anyhow::anyhow!(
"No market data for symbol: {}",
order.symbol
));
};
// Apply slippage
let execution_price = self.apply_slippage(current_price, &order);
// Calculate commission
let commission = self.calculate_commission(&order, execution_price);
// Update order
order.status = OrderStatus::Filled;
order.filled_quantity = order.quantity;
order.average_price = Some(execution_price);
// Update account
let mut account = self.account.write().await;
let trade_value = order.quantity.to_decimal().unwrap_or_default()
* execution_price.to_decimal().unwrap_or_default();
match order.side {
OrderSide::Buy => {
account.cash_balance -= trade_value + commission;
}
OrderSide::Sell => {
account.cash_balance += trade_value - commission;
}
}
// Update positions
self.position_tracker
.update_position(&order.symbol, &order, execution_price)
.await?;
// Record trade
self.position_tracker
.record_trade(&order, execution_price, commission)
.await;
// Notify strategy of order update
let context = self.build_strategy_context().await?;
self.strategy.on_order_update(&order, &context).await?;
info!(
"Executed order: {:?} {} {} @ {} (commission: {})",
order.side, order.quantity, order.symbol, execution_price, commission
);
Ok(())
}
/// Execute a trading signal
async fn execute_trading_signal(&mut self, signal: TradingSignal) -> Result<()> {
let order = self.convert_signal_to_order(signal).await?;
self.order_manager.place_order(order).await?;
Ok(())
}
/// Convert trading signal to order
async fn convert_signal_to_order(&self, signal: TradingSignal) -> Result<Order> {
let order_id = self.order_manager.generate_order_id();
let (side, order_type, price) = match signal.signal_type {
SignalType::Buy => (OrderSide::Buy, OrderType::Market, Price::zero()),
SignalType::Sell => (OrderSide::Sell, OrderType::Market, Price::zero()),
SignalType::Short => (OrderSide::Sell, OrderType::Market, Price::zero()),
SignalType::Cover => (OrderSide::Buy, OrderType::Market, Price::zero()),
_ => {
return Err(anyhow::anyhow!(
"Unsupported signal type: {:?}",
signal.signal_type
))
}
};
Ok(Order {
id: order_id.clone(),
order_id: order_id,
symbol: signal.symbol,
side,
quantity: signal.quantity,
order_type,
price: Some(price),
stop_price: None,
time_in_force: TimeInForce::Day,
status: OrderStatus::Pending,
timestamp: Utc::now(),
created_at: Utc::now(),
filled_quantity: Quantity::zero(),
remaining_quantity: signal.quantity,
average_price: None,
client_order_id: format!("client_{}", Uuid::new_v4()),
broker_order_id: None,
account_id: "default".to_string(),
})
}
/// Apply slippage to execution price
fn apply_slippage(&self, price: Price, order: &Order) -> Price {
let slippage = price.to_decimal().unwrap_or_default() * self.config.slippage_factor;
match order.side {
OrderSide::Buy => Price::from_f64(
(price.to_decimal().unwrap_or_default() + slippage)
.try_into()
.unwrap_or(0.0),
)
.unwrap_or(Price::zero()),
OrderSide::Sell => Price::from_f64(
(price.to_decimal().unwrap_or_default() - slippage)
.try_into()
.unwrap_or(0.0),
)
.unwrap_or(Price::zero()),
}
}
/// Calculate commission for trade
fn calculate_commission(&self, order: &Order, price: Price) -> Decimal {
let trade_value = order.quantity.to_decimal().unwrap_or_default()
* price.to_decimal().unwrap_or_default();
trade_value * self.config.commission_rate
}
/// Extract price from market event
fn extract_price_from_event(&self, event: &MarketEvent) -> Result<Price> {
match event {
MarketEvent::Trade { price, .. } => Ok(*price),
MarketEvent::Quote {
bid_price,
ask_price,
..
} => {
let avg_price = (bid_price.to_decimal().unwrap_or_default()
+ ask_price.to_decimal().unwrap_or_default())
/ Decimal::from(2);
Ok(Price::from_f64(avg_price.try_into().unwrap_or(0.0)).unwrap_or(Price::ZERO))
}
MarketEvent::Bar { close, .. } => Ok(*close),
_ => Err(anyhow::anyhow!(
"Cannot extract price from event: {:?}",
event
)),
}
}
/// Build strategy context
async fn build_strategy_context(&self) -> Result<StrategyContext> {
let account = self.account.read().await;
let positions = self.position_tracker.get_all_positions().await;
let open_orders = self.order_manager.get_all_orders().await;
let performance = self.performance_tracker.read().await.metrics.clone();
let mut market_prices = HashMap::new();
for item in self.market_data.iter() {
let symbol = item.key();
let event = item.value();
if let Ok(price) = self.extract_price_from_event(event) {
market_prices.insert(symbol.clone(), price);
}
}
Ok(StrategyContext {
current_time: Utc::now(),
account_balance: account.cash_balance,
buying_power: account.cash_balance, // Simplified
positions,
open_orders,
market_prices,
performance,
})
}
/// Take performance snapshot
async fn take_performance_snapshot(&self, context: &StrategyContext) -> Result<()> {
let mut tracker = self.performance_tracker.write().await;
let snapshot = PerformanceSnapshot {
timestamp: context.current_time,
portfolio_value: context.account_balance,
cash_balance: context.account_balance,
unrealized_pnl: context.performance.total_unrealized_pnl,
realized_pnl: context.performance.total_realized_pnl,
open_positions: context.positions.len() as u32,
drawdown: context.performance.current_drawdown,
};
tracker.snapshots.push(snapshot);
tracker.last_snapshot = Some(context.current_time);
Ok(())
}
}
impl OrderManager {
pub fn new() -> Self {
Self {
orders: DashMap::new(),
order_history: RwLock::new(Vec::new()),
next_order_id: std::sync::atomic::AtomicU64::new(1),
}
}
pub fn generate_order_id(&self) -> OrderId {
format!(
"order_{}",
self.next_order_id
.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
)
.into()
}
pub async fn place_order(&self, order: Order) -> Result<()> {
let order_id = order.id.clone();
self.orders.insert(order_id, order);
Ok(())
}
pub async fn get_pending_orders(&self) -> Vec<Order> {
self.orders
.iter()
.filter(|entry| entry.value().status == OrderStatus::Pending)
.map(|entry| entry.value().clone())
.collect()
}
pub async fn get_all_orders(&self) -> HashMap<OrderId, Order> {
self.orders
.iter()
.map(|entry| (entry.key().clone(), entry.value().clone()))
.collect()
}
}
impl PositionTracker {
pub fn new() -> Self {
Self {
positions: DashMap::new(),
position_history: RwLock::new(Vec::new()),
trade_records: RwLock::new(Vec::new()),
}
}
pub async fn get_all_positions(&self) -> HashMap<Symbol, Position> {
self.positions
.iter()
.map(|entry| (entry.key().clone(), entry.value().clone()))
.collect()
}
pub async fn update_position(
&self,
symbol: &Symbol,
order: &Order,
execution_price: Price,
) -> Result<()> {
// Position update logic would be implemented here
// This is a simplified version
Ok(())
}
pub async fn record_trade(&self, order: &Order, execution_price: Price, commission: Decimal) {
// Trade recording logic would be implemented here
}
}
impl PerformanceTracker {
pub fn new() -> Self {
Self {
metrics: PerformanceMetrics::default(),
snapshots: Vec::new(),
last_snapshot: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestStrategy {
name: String,
}
#[async_trait(?Send)]
impl Strategy for TestStrategy {
fn name(&self) -> &str {
&self.name
}
async fn initialize(
&mut self,
_initial_capital: Decimal,
_config: StrategyConfig,
) -> Result<()> {
Ok(())
}
async fn on_market_event(
&mut self,
_event: &MarketEvent,
_context: &StrategyContext,
) -> Result<Vec<TradingSignal>> {
Ok(vec![])
}
async fn on_order_update(
&mut self,
_order: &Order,
_context: &StrategyContext,
) -> Result<()> {
Ok(())
}
async fn on_position_update(
&mut self,
_position: &Position,
_context: &StrategyContext,
) -> Result<()> {
Ok(())
}
async fn finalize(&mut self, _context: &StrategyContext) -> Result<StrategyResult> {
Ok(StrategyResult {
strategy_name: self.name.clone(),
total_return: Decimal::ZERO,
annualized_return: Decimal::ZERO,
max_drawdown: Decimal::ZERO,
sharpe_ratio: Decimal::ZERO,
total_trades: 0,
win_rate: Decimal::ZERO,
avg_trade_return: Decimal::ZERO,
final_value: Decimal::from(100000),
trades: vec![],
performance_timeline: vec![],
})
}
async fn get_state(&self) -> Result<serde_json::Value> {
Ok(serde_json::json!({"name": self.name}))
}
}
#[tokio::test]
async fn test_strategy_tester_creation() {
use crate::replay_engine::{MarketReplay, ReplayConfig};
let strategy = Box::new(TestStrategy {
name: "test_strategy".to_string(),
});
let config = StrategyConfig::default();
let replay_config = ReplayConfig::default();
let market_replay = Arc::new(MarketReplay::new(replay_config));
let initial_capital = Decimal::from(100000);
let tester = StrategyTester::new(strategy, config, market_replay, initial_capital);
assert_eq!(tester.strategy.name(), "test_strategy");
}
}