mcframework.sims.BlackScholesSimulation#
- class mcframework.sims.BlackScholesSimulation[source]#
Bases:
MonteCarloSimulationMonte Carlo simulation for Black-Scholes option pricing.
Uses Geometric Brownian Motion for stock price dynamics and supports European and American options (calls and puts) with Greeks calculation.
- Parameters:
- name
str, optional Simulation name. Defaults to “Black-Scholes Option Pricing”.
- name
Methods
Closed-form Black-Scholes-Merton price (the oracle for European exercise).
Estimate primary Greeks via finite differences.
Optional vectorized cuRAND implementation using CuPy.
Price an American option with the Longstaff-Schwartz (LSM) algorithm.
Run the Monte Carlo simulation.
Set the random seed for reproducible experiments.
Price a single option instance under Black–Scholes dynamics.
Optional vectorized Torch implementation.
- analytic_reference(*, S0: float = 100.0, K: float = 100.0, T: float = 1.0, r: float = 0.05, sigma: float = 0.2, option_type: str = 'call', exercise_type: str = 'european', **params) float | None[source]#
Closed-form Black-Scholes-Merton price (the oracle for European exercise).
\[C = S_0\,\Phi(d_1) - K e^{-rT}\,\Phi(d_2), \qquad P = K e^{-rT}\,\Phi(-d_2) - S_0\,\Phi(-d_1),\]with \(d_1 = \frac{\ln(S_0/K) + (r + \tfrac12\sigma^2)T}{\sigma\sqrt T}\) and \(d_2 = d_1 - \sigma\sqrt T\).
Returns
Noneforexercise_type="american": an American option has no closed-form price, so it is deliberately not convergence-validated. That absence is itself the governance signal.
- calculate_greeks(n_simulations: int, S0: float = 100.0, K: float = 100.0, T: float = 1.0, r: float = 0.05, sigma: float = 0.2, option_type: str = 'call', exercise_type: str = 'european', n_steps: int = 252, backend: str = 'sequential', bump_pct: float = 0.01, time_bump_days: float = 1.0) dict[str, float][source]#
Estimate primary Greeks via finite differences.
- curand_batch(n: int, device_id: int, rng: cupy.random.RandomState) cupy.ndarray#
Optional vectorized cuRAND implementation using CuPy.
Override this method in subclasses to enable GPU-accelerated batch execution via cuRAND. When implemented alongside
supports_batch = Trueanduse_curand=True, the CUDA backend will call this method instead oftorch_batch().- Parameters:
- n
int Number of simulation draws.
- device_id
int CUDA device index.
- rng
cupy.random.RandomState cuRAND generator for reproducible random sampling.
- n
- Returns:
cupy.ndarrayA 1D array of length
ncontaining simulation results.
- price_american(n_paths: int, *, S0: float = 100.0, K: float = 100.0, T: float = 1.0, r: float = 0.05, sigma: float = 0.2, option_type: str = 'call', n_steps: int = 50, _rng: Generator | None = None) float[source]#
Price an American option with the Longstaff-Schwartz (LSM) algorithm.
Unlike
single_simulation()withexercise_type="american"(which is a high-biased perfect-foresight bound), this simulates a batch of GBM paths jointly and regresses continuation values across paths, so the exercise decision uses no future information. This is the estimator to use for actual American option valuation.- Parameters:
- n_paths
int Number of Monte Carlo paths to simulate. LSM regression quality improves with more paths.
- S0, K, T, r, sigma
float Standard Black-Scholes parameters (spot, strike, maturity in years, risk-free rate, volatility).
- option_type{“call”, “put”}, default
"call" Payoff family. Early exercise matters mainly for puts (and dividend -paying calls, not modeled here).
- n_steps
int, default 50 Number of exercise opportunities (time steps) along each path.
- _rng
numpy.random.Generator, optional Explicit generator. Falls back to
self.rng(set viaset_seed()).
- n_paths
- Returns:
floatEstimated arbitrage-free American option price.
- run(n_simulations: int, *, backend: str = 'auto', torch_device: str = 'cpu', cuda_device_id: int = 0, cuda_use_curand: bool = False, cuda_batch_size: int | None = None, cuda_use_streams: bool = True, n_workers: int | None = None, progress_callback: Callable[[int, int], None] | None = None, percentiles: Iterable[int] | None = None, compute_stats: bool = True, stats_engine: StatsEngine | None = None, confidence: float = 0.95, ci_method: str = 'auto', extra_context: Mapping[str, Any] | None = None, **simulation_kwargs: Any) SimulationResult#
Run the Monte Carlo simulation.
- Parameters:
- n_simulations
int Number of simulation draws.
- backend{“auto”, “sequential”, “thread”, “process”, “torch”}, default
"auto" Execution backend to use:
"auto"— Sequential for small jobs, parallel (thread/process) for large jobs"sequential"— Single-threaded execution"thread"— Thread-based parallelism (best when NumPy releases GIL)"process"— Process-based parallelism (required on Windows for true parallelism)"torch"— Torch batch execution (requiressupports_batch = True)
- torch_device{“cpu”, “mps”, “cuda”}, default
"cpu" Torch device for
backend="torch". Ignored for other backends."cpu"— Safe default, works everywhere"mps"— Apple Metal Performance Shaders (M1/M2/M3 Macs)"cuda"— NVIDIA GPU acceleration
- cuda_device_id
int, default 0 CUDA device index for multi-GPU systems. Only used when
backend="torch"andtorch_device="cuda".- cuda_use_curand
bool, defaultFalse Use cuRAND (via CuPy) instead of torch.Generator for maximum GPU performance. Requires CuPy and
curand_batch()implementation.- cuda_batch_size
intorNone, defaultNone Fixed batch size for CUDA execution. If None, automatically estimates optimal batch size based on available GPU memory.
- cuda_use_streams
bool, defaultTrue Use CUDA streams for overlapped execution. Recommended for performance.
- n_workers
int, optional Worker count for parallel backends. Defaults to CPU count.
- progress_callback
callable(), optional A function
f(completed: int, total: int)called periodically.- percentilesiterable of
int, optional Percentiles to compute from raw results. If
Noneandcompute_stats=True, the stats engine’s defaults (_PCTS) are used; ifcompute_stats=False, no percentiles are computed unless explicitly provided.- compute_stats
bool, defaultTrue Compute additional metrics via a
StatsEngine.- stats_engine
StatsEngine, optional Custom engine (defaults to
mcframework.stats_engine.DEFAULT_ENGINE).- confidence
float, default0.95 Confidence level for CI-related metrics.
- ci_method{“auto”,”z”,”t”}, default
"auto" Which critical values the stats engine should use.
- extra_contextmapping, optional
Extra context forwarded to the stats engine.
- **simulation_kwargs
Any Keyword arguments forwarded to
single_simulation().
- n_simulations
- Returns:
See also
run_simulation()Run a registered simulation by name.
Notes
MPS determinism caveat. When using
torch_device="mps", the framework preserves RNG stream structure but does not guarantee bitwise reproducibility due to Metal backend scheduling and float32 arithmetic. Statistical properties (mean, variance, CI coverage) remain correct.
- set_seed(seed: int | None) None#
Set the random seed for reproducible experiments.
- Parameters:
- seed
intorNone Seed for
numpy.random.SeedSequence.Nonechooses entropy from the OS.
- seed
Notes
The framework spawns independent child sequences per worker/chunk via
numpy.random.SeedSequence.spawn(), ensuring deterministic parallel streams given the sameseedand the same block layout.Warning
Reproducibility is per-(backend, block-layout), not absolute. The number of spawned child streams depends on the resolved backend and the work partition:
The sequential backend spawns one child stream and draws every sample from it.
The thread/process backends spawn one child stream per block, where the block count depends on
n_workers,n_simulations, and_CHUNKS_PER_WORKER.
Because
backend="auto"switches from sequential to parallel oncen_simulationscrosses_PARALLEL_THRESHOLD(20_000), the sameseedcan produce different draws above vs. below that threshold, and sequential vs. parallel results are not bitwise identical. For run-to-run reproducible numbers, pin the backend andn_workersexplicitly. Statistical properties (mean, variance, CI coverage) are unaffected.
- single_simulation(*, S0: float = 100.0, K: float = 100.0, T: float = 1.0, r: float = 0.05, sigma: float = 0.2, option_type: str = 'call', exercise_type: str = 'european', n_steps: int = 252, _rng: Generator | None = None, **kwargs) float[source]#
Price a single option instance under Black–Scholes dynamics.
Notes
For
exercise_type="american"this returns the perfect-foresight upper bound (the maximum discounted intrinsic value along the path), which is a high-biased estimator, not a fair price. Useprice_american()for a look-ahead-free Longstaff-Schwartz price.
- torch_batch(n: int, *, device: torch.device, generator: torch.Generator) torch.Tensor#
Optional vectorized Torch implementation.
Override this method in subclasses to enable GPU-accelerated batch execution. When implemented alongside
supports_batch = True, the framework will use this method instead of repeatedsingle_simulationcalls.- Parameters:
- n
int Number of simulation draws.
- device
torch.device Device to use for the simulation (
"cpu","mps", or"cuda").- generator
torch.Generator Explicit Torch generator for reproducible random sampling. This generator is seeded from
numpy.random.SeedSequenceto maintain the same spawning semantics as the NumPy backend.
- n
- Returns:
torch.TensorA 1D tensor of length
ncontaining simulation results. Use float32 for MPS compatibility; the framework promotes to float64 after moving to CPU.
- Raises:
NotImplementedErrorIf the subclass does not implement this method.
Notes
RNG discipline. All random sampling must use the provided
generatorexplicitly. Never use global Torch RNG (torch.manual_seed).Dtype policy (device-specific):
MPS (Apple Silicon): Must return float32 (Metal doesn’t support float64). Framework promotes to float64 on CPU.
CUDA (NVIDIA): Can return float32 or float64. Float64 preferred for zero conversion overhead and full precision.
CPU: Can return float32 or float64. Float64 preferred for consistency with framework precision.
This method is optional and must be implemented by subclasses that support the Torch backend. If not implemented, the framework will fall back to the NumPy backend.
Examples
>>> class PiSim(MonteCarloSimulation): ... supports_batch = True ... def torch_batch(self, n, *, device, generator): ... import torch ... x = torch.rand(n, device=device, generator=generator) ... y = torch.rand(n, device=device, generator=generator) ... inside = (x * x + y * y) <= 1.0 ... return 4.0 * inside.float() # float32 for MPS compatibility
- classmethod __new__(*args, **kwargs)#