Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
+235
View File
@@ -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());
}
}