Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,11 @@ fn main() -> Result<(), Error> {
walker,
};

let random_walk = RandomWalk::new("Random Walk".to_string(), &walk_params, |p| {
generator_optionchain(p).expect("generator_optionchain failed")
});
let random_walk = RandomWalk::new(
"Random Walk".to_string(),
&walk_params,
generator_optionchain,
)?;
debug!("Random Walk: {}", random_walk);
let path: &Path = "Draws/Simulation/historical_build_chain.png".as_ref();
random_walk.write_png(path)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ fn main() -> Result<(), Error> {
"Long Call Simulator".to_string(),
n_simulations,
&walk_params,
|p| generator_positive(p).expect("generator_positive failed"),
);
generator_positive,
)?;

info!("Running simulations using Simulate trait...");

Expand Down
9 changes: 6 additions & 3 deletions examples/examples_simulation/src/bin/position_simulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ fn main() -> Result<(), Error> {
walker,
};

let simulator = Simulator::new("Simulator".to_string(), simulator_size, &walk_params, |p| {
generator_positive(p).expect("generator_positive failed")
});
let simulator = Simulator::new(
"Simulator".to_string(),
simulator_size,
&walk_params,
generator_positive,
)?;
debug!("Simulator: {}", simulator);

let option: Options = Options::new(
Expand Down
4 changes: 1 addition & 3 deletions examples/examples_simulation/src/bin/random_walk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ fn main() -> Result<(), Error> {
walker,
};

let random_walk = RandomWalk::new("Random Walk".to_string(), &walk_params, |p| {
generator_positive(p).expect("generator_positive failed")
});
let random_walk = RandomWalk::new("Random Walk".to_string(), &walk_params, generator_positive)?;
debug!("Random Walk: {}", random_walk);
let path: &std::path::Path = "Draws/Simulation/random_walk.png".as_ref();
random_walk.write_png(path)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@ fn main() -> Result<(), Error> {
walker,
};

let random_walk = RandomWalk::new("Random Walk".to_string(), &walk_params, |p| {
generator_optionchain(p).expect("generator_optionchain failed")
});
let random_walk = RandomWalk::new(
"Random Walk".to_string(),
&walk_params,
generator_optionchain,
)?;
debug!("Random Walk: {}", random_walk);
let path: &std::path::Path = "Draws/Simulation/random_walk_build_chain.png".as_ref();
random_walk.write_png(path)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,9 @@ fn main() -> Result<(), Error> {
},
walker,
};
let random_walk = RandomWalk::new(
"Random Walk".to_string(),
&walk_params,
generator_optionseries,
);
let random_walk = RandomWalk::new("Random Walk".to_string(), &walk_params, |p| {
Ok::<_, Error>(generator_optionseries(p))
})?;
debug!("Random Walk: {}", random_walk);
let path: &std::path::Path = "Draws/Simulation/random_walk_build_series.png".as_ref();
random_walk.write_png(path)?;
Expand Down
8 changes: 5 additions & 3 deletions examples/examples_simulation/src/bin/random_walk_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ fn main() -> Result<(), Error> {
walker,
};

let random_walk = RandomWalk::new("Random Walk".to_string(), &walk_params, |p| {
generator_optionchain(p).expect("generator_optionchain failed")
});
let random_walk = RandomWalk::new(
"Random Walk".to_string(),
&walk_params,
generator_optionchain,
)?;

debug!("Random Walk: {}", random_walk);
let path: &std::path::Path = "Draws/Simulation/random_walk_chain.png".as_ref();
Expand Down
4 changes: 2 additions & 2 deletions examples/examples_simulation/src/bin/short_put_simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,8 @@ fn main() -> Result<(), Error> {
"Short Put Simulator".to_string(),
n_simulations,
&walk_params,
|p| generator_positive(p).expect("generator_positive failed"),
);
generator_positive,
)?;

// Create progress bar
let progress_bar = ProgressBar::new(n_simulations as u64);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ fn main() -> Result<(), Error> {
"Short Put Simulator".to_string(),
n_simulations,
&walk_params,
|p| generator_positive(p).expect("generator_positive failed"),
);
generator_positive,
)?;

info!("Running simulations using Simulate trait...");

Expand Down
9 changes: 6 additions & 3 deletions examples/examples_simulation/src/bin/simulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,12 @@ fn main() -> Result<(), Error> {
walker,
};

let simulator = Simulator::new("Simulator".to_string(), simulator_size, &walk_params, |p| {
generator_positive(p).expect("generator_positive failed")
});
let simulator = Simulator::new(
"Simulator".to_string(),
simulator_size,
&walk_params,
generator_positive,
)?;
debug!("Simulator: {}", simulator);

// let last_steps: Vec<&Step<Positive, Positive>> = simulator
Expand Down
9 changes: 6 additions & 3 deletions examples/examples_simulation/src/bin/strategy_simulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@ fn main() -> Result<(), Error> {
walker,
};

let simulator = Simulator::new("Simulator".to_string(), simulator_size, &walk_params, |p| {
generator_positive(p).expect("generator_positive failed")
});
let simulator = Simulator::new(
"Simulator".to_string(),
simulator_size,
&walk_params,
generator_positive,
)?;
debug!("Simulator: {}", simulator);

info!("Open Premium: ${:.2}", open_premium);
Expand Down
18 changes: 11 additions & 7 deletions examples/examples_simulation/src/bin/unified_pricing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

use optionstratlib::prelude::*;
use positive::pos_or_panic;
use std::convert::Infallible;
use std::fmt::Display;
use std::ops::AddAssign;

Expand All @@ -34,7 +35,9 @@ where
}

// Simple generator for demonstration purposes
fn demo_generator(params: &WalkParams<Positive, Positive>) -> Vec<Step<Positive, Positive>> {
fn demo_generator(
params: &WalkParams<Positive, Positive>,
) -> Result<Vec<Step<Positive, Positive>>, Infallible> {
let mut out = Vec::with_capacity(params.size);
let mut current = params.init_step.clone();
out.push(current.clone());
Expand All @@ -44,10 +47,10 @@ fn demo_generator(params: &WalkParams<Positive, Positive>) -> Vec<Step<Positive,
current = current.next(new_y).expect("step.next should succeed");
out.push(current.clone());
}
out
Ok(out)
}

fn main() {
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Unified Pricing System Example ===\n");

// Create a sample European call option
Expand Down Expand Up @@ -118,7 +121,7 @@ fn main() {
1000,
&gbm_params,
demo_generator,
);
)?;

let mc_engine = PricingEngine::MonteCarlo {
simulator: gbm_simulator,
Expand Down Expand Up @@ -152,7 +155,7 @@ fn main() {
1000,
&heston_params,
demo_generator,
);
)?;

let heston_engine = PricingEngine::MonteCarlo {
simulator: heston_simulator,
Expand Down Expand Up @@ -185,7 +188,7 @@ fn main() {
1000,
&jump_params,
demo_generator,
);
)?;

let jump_engine = PricingEngine::MonteCarlo {
simulator: jump_simulator,
Expand Down Expand Up @@ -219,7 +222,7 @@ fn main() {
1000,
&telegraph_params,
demo_generator,
);
)?;

let telegraph_engine = PricingEngine::MonteCarlo {
simulator: telegraph_simulator,
Expand All @@ -230,4 +233,5 @@ fn main() {
}

println!("=== Example Complete ===");
Ok(())
}
15 changes: 9 additions & 6 deletions src/chains/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,12 @@ mod tests {
walker,
};

let random_walk = RandomWalk::new("Random Walk".to_string(), &walk_params, |p| {
generator_optionchain(p).unwrap()
});
let random_walk = RandomWalk::new(
"Random Walk".to_string(),
&walk_params,
generator_optionchain,
)
.expect("random walk construction");
assert_eq!(random_walk.len(), n_steps);
}

Expand Down Expand Up @@ -341,9 +344,9 @@ mod tests {
},
walker,
};
let random_walk = RandomWalk::new("Random Walk".to_string(), &walk_params, |p| {
generator_positive(p).unwrap()
});
let random_walk =
RandomWalk::new("Random Walk".to_string(), &walk_params, generator_positive)
.expect("random walk construction");
assert_eq!(random_walk.len(), n_steps);
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/error/simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ impl From<StrategyError> for SimulationError {
}
}

impl From<crate::error::ChainError> for SimulationError {
fn from(err: crate::error::ChainError) -> Self {
SimulationError::OtherError {
reason: err.to_string(),
}
}
}

/// Type alias for Results that may return a `SimulationError`.
///
/// This is a convenience type for functions that return simulation results.
Expand Down
11 changes: 8 additions & 3 deletions src/pricing/monte_carlo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,14 @@ mod tests_price_option_monte_carlo {
walker,
};

let simulator = Simulator::new("Test Simulator".to_string(), 100, &walk_params, |p| {
generator_positive(p).unwrap()
});
let Ok(simulator) = Simulator::new(
"Test Simulator".to_string(),
100,
&walk_params,
generator_positive,
) else {
panic!("simulator setup failed");
};

#[cfg(feature = "static_export")]
simulator
Expand Down
40 changes: 39 additions & 1 deletion src/pricing/telegraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,11 @@ impl TelegraphProcess {
self.lambda_up
};
let lambda_dt = -lambda * dt;
let probability = if lambda_dt < dec!(11.7) {
// lambda_dt is non-positive (lambda, dt >= 0). For very-negative
// values exp(lambda_dt) underflows to 0; treat as a guaranteed
// flip (probability = 1). Otherwise use the standard Poisson
// transition: P(flip in dt) = 1 - exp(-lambda * dt).
let probability = if lambda_dt < dec!(-11.7) {
Decimal::ONE
} else {
Decimal::ONE - lambda_dt.exp()
Expand Down Expand Up @@ -412,6 +416,40 @@ mod tests_telegraph_process_basis {
// There's a chance the state didn't change, so we can't assert inequality
}

#[test]
fn test_next_state_empirical_flip_rate_matches_poisson() {
// Regression test for #351: prior code had an inverted underflow
// guard that forced probability = 1.0 every step. Verify the
// empirical flip rate now matches the Poisson transition
// probability P(flip in dt) = 1 - exp(-lambda * dt) within a
// 5 σ Monte-Carlo bound.
let lambda_f = 0.5_f64;
let dt_f = 0.01_f64;
let mut tp = TelegraphProcess::new(dec!(0.5), dec!(0.5));

let n = 100_000_u64;
let mut prev = tp.get_current_state();
let mut flips: u64 = 0;
for _ in 0..n {
let next = tp.next_state(dec!(0.01));
if next != prev {
flips += 1;
}
prev = next;
}
let empirical = flips as f64 / n as f64;
let expected = 1.0 - (-lambda_f * dt_f).exp();
let std_err = (expected * (1.0 - expected) / n as f64).sqrt();

assert!(
(empirical - expected).abs() < 5.0 * std_err,
"empirical flip rate {empirical} differs from expected {expected} by more than 5σ ({})",
5.0 * std_err
);
// Sanity: must be far below 1.0 (the buggy value).
assert!(empirical < 0.05, "flip rate suspiciously high: {empirical}");
}

#[test]
fn test_telegraph_process_get_current_state() {
let tp = TelegraphProcess::new(dec!(0.5), dec!(0.5));
Expand Down
Loading
Loading