use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(name = "aprender-monte-carlo")]
#[command(author = "paiml")]
#[command(version = "0.1.0")]
#[command(about = "Monte Carlo simulations for finance and business forecasting")]
#[command(long_about = r#"
Monte Carlo simulation tool for:
- Stock market analysis using S&P 500 historical data
- Business revenue forecasting with Bayesian models
- Custom data analysis from CSV files
Examples:
# Simulate 30-year retirement portfolio using S&P 500 returns
aprender-monte-carlo sp500 --years 30 --initial 100000 --simulations 10000
# Analyze custom return data from CSV
aprender-monte-carlo csv --file returns.csv --initial 50000 --years 10
# Business revenue forecast
aprender-monte-carlo revenue --file products.csv --quarters 8
"#)]
pub struct Cli {
#[arg(short, long, value_enum, default_value_t = OutputFormat::Table)]
pub format: OutputFormat,
#[arg(short, long, default_value_t = 42)]
pub seed: u64,
#[arg(short, long)]
pub verbose: bool,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Sp500 {
#[arg(short, long, default_value_t = 30)]
years: u32,
#[arg(short = 'n', long, default_value_t = 10000)]
simulations: usize,
#[arg(short, long, default_value_t = 100_000.0)]
initial: f64,
#[arg(short, long)]
withdrawal_rate: Option<f64>,
#[arg(long)]
real_returns: bool,
},
Csv {
#[arg(short, long)]
file: PathBuf,
#[arg(short, long)]
column: Option<String>,
#[arg(short = 'y', long, default_value_t = 10)]
years: u32,
#[arg(short = 'n', long, default_value_t = 10000)]
simulations: usize,
#[arg(short, long, default_value_t = 100.0)]
initial: f64,
},
Revenue {
#[arg(short, long)]
file: PathBuf,
#[arg(short, long, default_value_t = 4)]
quarters: u32,
#[arg(short = 'n', long, default_value_t = 10000)]
simulations: usize,
#[arg(long)]
bayesian: bool,
},
Stats {
#[arg(long)]
monthly: bool,
#[arg(long)]
decades: bool,
},
}
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)]
pub enum OutputFormat {
Table,
Json,
Csv,
}
impl Cli {
#[must_use]
pub fn parse_args() -> Self {
Self::parse()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cli_parse_sp500() {
let cli = Cli::try_parse_from([
"aprender-monte-carlo",
"sp500",
"--years",
"20",
"--simulations",
"5000",
])
.expect("Should parse");
match cli.command {
Commands::Sp500 {
years, simulations, ..
} => {
assert_eq!(years, 20);
assert_eq!(simulations, 5000);
}
_ => panic!("Expected Sp500 command"),
}
}
#[test]
fn test_cli_parse_csv() {
let cli = Cli::try_parse_from([
"aprender-monte-carlo",
"csv",
"--file",
"test.csv",
"--initial",
"50000",
])
.expect("Should parse");
match cli.command {
Commands::Csv { initial, file, .. } => {
assert!((initial - 50000.0).abs() < 0.01);
assert_eq!(file.to_string_lossy(), "test.csv");
}
_ => panic!("Expected Csv command"),
}
}
#[test]
fn test_output_format() {
let cli = Cli::try_parse_from(["aprender-monte-carlo", "--format", "json", "sp500"])
.expect("Should parse");
assert_eq!(cli.format, OutputFormat::Json);
}
}