Getting Started#
This guide walks you through the fundamentals of mcframework, from running your first simulation to building custom ones with advanced statistical analysis.
Prerequisites#
Python 3.10+
NumPy, SciPy, Matplotlib (installed automatically with the package)
Installation#
From PyPI (recommended)
Install the package from PyPI using pip (or your favorite package manager):
pip install mcframework
From Source (for development)
Clone the repository and install in editable mode with all dependencies:
git clone https://github.com/milanfusco/mcframework.git
cd mcframework
pip install -e .
Your First Simulation#
Let’s estimate π using Monte Carlo integration. The idea: throw random darts at a square and count how many land inside an inscribed circle.
from mcframework import MonteCarloFramework, PiEstimationSimulation
# Create and seed the simulation
sim = PiEstimationSimulation()
sim.set_seed(42) # For reproducibility
# Register with the framework
fw = MonteCarloFramework()
fw.register_simulation(sim)
# Run 10,000 simulations, each throwing 5,000 darts
result = fw.run_simulation(
"Pi Estimation",
n_simulations=10_000,
n_points=5000,
parallel=True
)
# View results
print(result.result_to_string())
What happened?
Each of the 10,000 simulations threw 5,000 random points
The framework ran them in parallel using all CPU cores
Statistics were automatically computed on the resulting estimates
Understanding SimulationResult#
Every simulation returns a SimulationResult object containing:
result.results # np.ndarray of raw simulation values
result.n_simulations # Number of simulations run
result.execution_time # Wall-clock time in seconds
result.mean # Sample mean
result.std # Sample standard deviation
result.percentiles # Dict of requested percentiles {5: ..., 50: ..., 95: ...}
result.stats # Additional stats from the engine (ci_mean, skew, etc.)
result.metadata # Timestamps, seed info, etc.
Confidence Intervals#
The stats engine computes confidence intervals automatically:
ci = result.stats.get("ci_mean")
print(f"95% CI: [{ci[0]:.6f}, {ci[1]:.6f}]")
# For more detail, access the full CI dict:
ci_detail = result.stats.get("ci_mean") # Contains: low, high, method, se, crit
Customizing Your Run#
Control Statistics Computation#
# Custom percentiles
result = sim.run(
10_000,
percentiles=(1, 5, 25, 50, 75, 95, 99),
confidence=0.99, # 99% CI instead of 95%
ci_method="t", # Force t-distribution critical values
)
# Disable stats engine for raw speed
result = sim.run(10_000, compute_stats=False)
Parallel Execution Control#
# Control worker count
result = sim.run(50_000, parallel=True, n_workers=4)
# Force specific backend
sim.parallel_backend = "thread" # or "process"
result = sim.run(50_000, parallel=True)
# Progress callback
def on_progress(completed, total):
print(f"Progress: {completed}/{total} ({100*completed/total:.1f}%)")
result = sim.run(50_000, parallel=True, progress_callback=on_progress)
Building Custom Simulations#
Create your own simulation by subclassing MonteCarloSimulation:
from mcframework import MonteCarloSimulation
import numpy as np
class CoinFlipStreakSimulation(MonteCarloSimulation):
"""
Simulate flipping coins and find the longest streak of heads.
"""
def __init__(self):
super().__init__("Coin Flip Streak")
def single_simulation(
self,
n_flips: int = 100,
_rng=None,
**kwargs
) -> float:
"""
Flip n_flips coins and return the longest consecutive heads streak.
Parameters
----------
n_flips : int
Number of coin flips per simulation
_rng : Generator, optional
Thread-safe RNG provided by the framework
Returns
-------
float
Length of the longest heads streak
"""
# Always use the framework's RNG for reproducibility
rng = self._rng(_rng, self.rng)
# Simulate flips (1 = heads, 0 = tails)
flips = rng.integers(0, 2, size=n_flips)
# Find longest streak of heads
max_streak = current_streak = 0
for flip in flips:
if flip == 1:
current_streak += 1
max_streak = max(max_streak, current_streak)
else:
current_streak = 0
return float(max_streak)
# Use your custom simulation
sim = CoinFlipStreakSimulation()
sim.set_seed(42)
result = sim.run(
n_simulations=50_000,
n_flips=1000,
parallel=True,
percentiles=(50, 90, 99)
)
print(f"Expected longest streak in 1000 flips: {result.mean:.1f}")
print(f"Median: {result.percentiles[50]:.0f}")
print(f"90th percentile: {result.percentiles[90]:.0f}")
print(f"99th percentile: {result.percentiles[99]:.0f}")
Portfolio Simulation Example#
Model investment growth under Geometric Brownian Motion:
from mcframework import PortfolioSimulation, MonteCarloFramework
sim = PortfolioSimulation()
sim.set_seed(2024)
fw = MonteCarloFramework()
fw.register_simulation(sim)
# Simulate $100k invested for 30 years
result = fw.run_simulation(
"Portfolio Simulation",
n_simulations=100_000,
initial_value=100_000,
annual_return=0.07, # 7% expected return
volatility=0.18, # 18% annual volatility
years=30,
parallel=True,
percentiles=(5, 25, 50, 75, 95)
)
print(f"After 30 years:")
print(f" Expected value: ${result.mean:,.0f}")
print(f" Median value: ${result.percentiles[50]:,.0f}")
print(f" 5th percentile (bad case): ${result.percentiles[5]:,.0f}")
print(f" 95th percentile (good case): ${result.percentiles[95]:,.0f}")
Black-Scholes Option Pricing#
Price European and American options with Greeks:
from mcframework import BlackScholesSimulation
bs = BlackScholesSimulation()
bs.set_seed(42)
# Price a European call option
result = bs.run(
n_simulations=100_000,
S0=100.0, # Current stock price
K=105.0, # Strike price
T=0.5, # Time to maturity (years)
r=0.05, # Risk-free rate
sigma=0.25, # Volatility
option_type="call",
exercise_type="european",
parallel=True
)
print(f"European Call Price: ${result.mean:.4f}")
print(f"95% CI: [${result.stats['ci_mean'][0]:.4f}, ${result.stats['ci_mean'][1]:.4f}]")
# Calculate Greeks
greeks = bs.calculate_greeks(
n_simulations=50_000,
S0=100.0, K=105.0, T=0.5, r=0.05, sigma=0.25,
option_type="call",
parallel=True
)
print(f"\nGreeks:")
print(f" Delta: {greeks['delta']:.4f}")
print(f" Gamma: {greeks['gamma']:.6f}")
print(f" Vega: {greeks['vega']:.4f}")
print(f" Theta: {greeks['theta']:.4f}")
print(f" Rho: {greeks['rho']:.4f}")
Comparing Multiple Simulations#
Use MonteCarloFramework to manage and compare simulations:
from mcframework import MonteCarloFramework, PiEstimationSimulation
fw = MonteCarloFramework()
# Create variations with different parameters
sim_small = PiEstimationSimulation()
sim_small.name = "Pi (1k points)"
sim_small.set_seed(42)
sim_large = PiEstimationSimulation()
sim_large.name = "Pi (100k points)"
sim_large.set_seed(42)
fw.register_simulation(sim_small)
fw.register_simulation(sim_large)
# Run both
fw.run_simulation("Pi (1k points)", 1000, n_points=1000, parallel=True)
fw.run_simulation("Pi (100k points)", 1000, n_points=100_000, parallel=True)
# Compare metrics
comparison = fw.compare_results(
["Pi (1k points)", "Pi (100k points)"],
metric="std" # Compare standard deviations
)
print("Standard deviation comparison:")
for name, std in comparison.items():
print(f" {name}: {std:.6f}")
Advanced: Custom Statistics Engine#
Create a custom stats engine with specific metrics:
from mcframework.stats_engine import (
StatsEngine, FnMetric, StatsContext,
mean, std, ci_mean, percentiles
)
import numpy as np
# Define custom metrics
def coefficient_of_variation(x, ctx):
"""CV = std / mean, measuring relative variability."""
m = float(np.mean(x))
s = float(np.std(x, ddof=1))
return s / m if m != 0 else float('nan')
def interquartile_range(x, ctx):
"""IQR = Q3 - Q1"""
q1, q3 = np.percentile(x, [25, 75])
return float(q3 - q1)
# Build custom engine
custom_engine = StatsEngine([
FnMetric("mean", mean),
FnMetric("std", std),
FnMetric("ci_mean", ci_mean),
FnMetric("cv", coefficient_of_variation),
FnMetric("iqr", interquartile_range),
])
# Use with a simulation
from mcframework import PiEstimationSimulation
sim = PiEstimationSimulation()
sim.set_seed(42)
result = sim.run(
10_000,
stats_engine=custom_engine,
parallel=True
)
print(f"Mean: {result.stats.get('mean', result.mean):.6f}")
print(f"CV: {result.stats.get('cv'):.4f}")
print(f"IQR: {result.stats.get('iqr'):.6f}")
Bootstrap Confidence Intervals#
For non-normal distributions, use bootstrap CIs:
from mcframework import PiEstimationSimulation
from mcframework.stats_engine import StatsContext
sim = PiEstimationSimulation()
sim.set_seed(42)
result = sim.run(
5000,
parallel=True,
extra_context={
"n_bootstrap": 10_000,
"bootstrap": "bca", # Bias-corrected and accelerated
}
)
# Bootstrap CI is in stats
bootstrap_ci = result.stats.get("ci_mean_bootstrap")
if bootstrap_ci:
print(f"Bootstrap 95% CI: [{bootstrap_ci['low']:.6f}, {bootstrap_ci['high']:.6f}]")
print(f"Method: {bootstrap_ci['method']}")
Reproducibility Guide#
For fully reproducible results:
Always set a seed before running:
sim.set_seed(42)
Use the same worker count for parallel runs:
result = sim.run(10_000, parallel=True, n_workers=8)
Check seed entropy in results:
print(f"Seed entropy: {result.metadata['seed_entropy']}")
The framework uses NumPy’s SeedSequence.spawn() to create independent, deterministic random streams for each parallel worker.
Next Steps#
Explore the API Reference for detailed class documentation
Check out the demos/ folder for visualization examples
Try the GUI application for interactive Black-Scholes analysis:
pip install -e ".[gui]" python demos/gui/quant_black_scholes.py