Initial commit
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
//! MarketSim - Financial World Model Demo.
|
||||
//!
|
||||
//! This demo showcases market scenario generation using world models,
|
||||
//! enabling stress testing and scenario analysis for portfolios.
|
||||
|
||||
pub mod backtest;
|
||||
pub mod sample_data;
|
||||
pub mod scenario_generator;
|
||||
pub mod world_model;
|
||||
|
||||
use marketsim_shared::{BacktestRequest, BacktestResult, ScenarioRequest, ScenarioResult};
|
||||
use thiserror::Error;
|
||||
|
||||
use backtest::BacktestEngine;
|
||||
use scenario_generator::ScenarioGenerator;
|
||||
use world_model::MarketWorldModel;
|
||||
|
||||
/// Errors that can occur during market simulation.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MarketSimError {
|
||||
/// No assets provided.
|
||||
#[error("No assets provided for simulation")]
|
||||
NoAssets,
|
||||
|
||||
/// No historical data provided.
|
||||
#[error("No historical data provided")]
|
||||
NoHistoricalData,
|
||||
|
||||
/// Invalid scenario parameters.
|
||||
#[error("Invalid scenario parameters: {0}")]
|
||||
InvalidScenario(String),
|
||||
|
||||
/// Simulation failed.
|
||||
#[error("Simulation failed: {0}")]
|
||||
SimulationFailed(String),
|
||||
|
||||
/// Backtest failed.
|
||||
#[error("Backtest failed: {0}")]
|
||||
BacktestFailed(String),
|
||||
}
|
||||
|
||||
/// Main MarketSim system.
|
||||
#[derive(Debug)]
|
||||
pub struct MarketSim {
|
||||
/// World model for market dynamics.
|
||||
world_model: MarketWorldModel,
|
||||
/// Scenario generator.
|
||||
scenario_gen: ScenarioGenerator,
|
||||
/// Backtest engine.
|
||||
backtest_engine: BacktestEngine,
|
||||
}
|
||||
|
||||
impl Default for MarketSim {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MarketSim {
|
||||
/// Create a new MarketSim instance.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
world_model: MarketWorldModel::new(),
|
||||
scenario_gen: ScenarioGenerator::new(),
|
||||
backtest_engine: BacktestEngine::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate market scenarios.
|
||||
pub fn generate_scenarios(
|
||||
&self,
|
||||
request: &ScenarioRequest,
|
||||
) -> Result<ScenarioResult, MarketSimError> {
|
||||
// Validate request
|
||||
if request.assets.is_empty() {
|
||||
return Err(MarketSimError::NoAssets);
|
||||
}
|
||||
|
||||
if request.num_paths == 0 {
|
||||
return Err(MarketSimError::InvalidScenario(
|
||||
"num_paths must be > 0".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if request.horizon_days == 0 {
|
||||
return Err(MarketSimError::InvalidScenario(
|
||||
"horizon_days must be > 0".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Generate scenarios using world model
|
||||
self.scenario_gen.generate(request, &self.world_model)
|
||||
}
|
||||
|
||||
/// Run backtest on generated scenarios.
|
||||
pub fn run_backtest(
|
||||
&self,
|
||||
request: &BacktestRequest,
|
||||
) -> Result<BacktestResult, MarketSimError> {
|
||||
if request.scenarios.is_empty() {
|
||||
return Err(MarketSimError::BacktestFailed(
|
||||
"No scenarios provided".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
self.backtest_engine.run(request)
|
||||
}
|
||||
|
||||
/// Get the world model.
|
||||
#[must_use]
|
||||
pub fn world_model(&self) -> &MarketWorldModel {
|
||||
&self.world_model
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the full demo.
|
||||
pub fn run_demo() -> Result<(ScenarioResult, BacktestResult), MarketSimError> {
|
||||
let sim = MarketSim::new();
|
||||
|
||||
// Generate crisis scenario
|
||||
let request = sample_data::create_crisis_simulation_request();
|
||||
let scenario_result = sim.generate_scenarios(&request)?;
|
||||
|
||||
// Run backtest with momentum strategy
|
||||
let backtest_request = sample_data::create_backtest_request(vec![scenario_result.clone()]);
|
||||
let backtest_result = sim.run_backtest(&backtest_request)?;
|
||||
|
||||
Ok((scenario_result, backtest_result))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use marketsim_shared::{
|
||||
Asset, AssetPrice, AssetType, EconomicFactors, MarketIndicators, MarketState,
|
||||
ScenarioDescription, ScenarioType,
|
||||
};
|
||||
|
||||
fn create_test_request() -> ScenarioRequest {
|
||||
ScenarioRequest {
|
||||
assets: vec![
|
||||
Asset {
|
||||
symbol: "SPY".to_string(),
|
||||
name: "S&P 500".to_string(),
|
||||
asset_type: AssetType::Index,
|
||||
current_price: 500.0,
|
||||
},
|
||||
Asset {
|
||||
symbol: "TLT".to_string(),
|
||||
name: "Treasury".to_string(),
|
||||
asset_type: AssetType::Bond,
|
||||
current_price: 90.0,
|
||||
},
|
||||
],
|
||||
historical_data: vec![],
|
||||
initial_state: MarketState {
|
||||
timestamp: "2024-01-01T00:00:00Z".to_string(),
|
||||
prices: vec![
|
||||
AssetPrice {
|
||||
symbol: "SPY".to_string(),
|
||||
price: 500.0,
|
||||
change_pct: 0.0,
|
||||
},
|
||||
AssetPrice {
|
||||
symbol: "TLT".to_string(),
|
||||
price: 90.0,
|
||||
change_pct: 0.0,
|
||||
},
|
||||
],
|
||||
indicators: MarketIndicators::default(),
|
||||
economic_factors: EconomicFactors::default(),
|
||||
},
|
||||
scenario: ScenarioDescription {
|
||||
name: "Test Crisis".to_string(),
|
||||
scenario_type: ScenarioType::Crisis,
|
||||
description: "Test".to_string(),
|
||||
severity: 0.5,
|
||||
duration_days: 20,
|
||||
},
|
||||
num_paths: 10,
|
||||
horizon_days: 20,
|
||||
seed: Some(42),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_marketsim_creation() {
|
||||
let sim = MarketSim::new();
|
||||
assert!(std::mem::size_of_val(&sim) > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_scenarios() {
|
||||
let sim = MarketSim::new();
|
||||
let request = create_test_request();
|
||||
|
||||
let result = sim.generate_scenarios(&request);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let scenario = result.unwrap();
|
||||
assert_eq!(scenario.price_paths.len(), 2);
|
||||
assert_eq!(scenario.price_paths[0].paths.len(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_assets_error() {
|
||||
let sim = MarketSim::new();
|
||||
let mut request = create_test_request();
|
||||
request.assets.clear();
|
||||
|
||||
let result = sim.generate_scenarios(&request);
|
||||
assert!(matches!(result, Err(MarketSimError::NoAssets)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_num_paths() {
|
||||
let sim = MarketSim::new();
|
||||
let mut request = create_test_request();
|
||||
request.num_paths = 0;
|
||||
|
||||
let result = sim.generate_scenarios(&request);
|
||||
assert!(matches!(result, Err(MarketSimError::InvalidScenario(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_demo() {
|
||||
let result = run_demo();
|
||||
assert!(result.is_ok());
|
||||
|
||||
let (scenario, backtest) = result.unwrap();
|
||||
assert!(!scenario.price_paths.is_empty());
|
||||
assert!(!backtest.scenario_results.is_empty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user