Monte Carlo Simulation Guide
Overview
The Monte Carlo simulation now includes market-calibrated expected move integration and intelligent strategy execution rules based on actual market conditions.
What Changed
1. Expected Move Calibration
The simulation can now accept the expected move percentage calculated from market straddle prices (from calculator.py) and automatically calibrate the earnings jump distribution.
Before:
After:
expected_move_pct=8.5, # Market-implied 8.5% from straddle
# Automatically sets earnings_jump_std=0.085
2. Strategy Execution Rules
The simulation now applies realistic trading rules based on market conditions:
Rule 1: Profit Target Exit (Existing)
- Exit when P&L reaches 25% of max profit (configurable)
- Standard profit-taking strategy
Rule 2: Large Move Exit (NEW)
- When: After earnings announcement
- Condition: If actual price move > 1.5x expected move
- Rationale: Outsized moves may indicate market repricing; lock in gains/limit losses
- Example: With 8.5% expected move, exits if actual move > 12.75%
Rule 3: Stop Loss Exit (NEW)
- When: After earnings announcement
- Condition: Position down 75%+ of initial cost
- Rationale: Cut losses on severely adverse scenarios
- Example: If initial cost is $2.00, exits if loss > $1.50
Rule 4: Expiration Exit (Default)
- Hold to front month expiration if no other rule triggers
New Parameters
SimulationParams
@dataclass
class SimulationParams:
# ... existing parameters ...
# NEW: Expected move from straddle pricing
expected_move_pct: float | None = None # e.g., 8.5 for 8.5%
# NEW: Strategy execution flags
use_dynamic_strikes: bool = True # Adjust strikes based on expected move
early_exit_on_large_move: bool = True # Exit if move exceeds 1.5x expected
Parameter Details
expected_move_pct (Optional)
- Type: float | None
- Example: 8.5 for 8.5% expected move
- Source: Get from calculator.py straddle pricing
- Effect: Auto-calibrates earnings_jump_std and enables market-aware strategy rules
- If None: Uses manually specified earnings_jump_std
use_dynamic_strikes (Default: True)
- Type: bool
- Purpose: Enable strike adjustment based on expected move
- Current implementation: Keeps ATM (reserved for future enhancements)
- Future: May adjust to OTM based on expected move magnitude
early_exit_on_large_move (Default: True)
- Type: bool
- Purpose: Exit positions early when actual move exceeds 1.5x expected move
- Threshold: expected_move_pct * 1.5
- Requires: expected_move_pct to be set
How to Get Expected Move
From calculator.py:
From strategy.py directly:
from trade_calc.strategy import compute_recommendation
result = compute_recommendation("NVDA")
expected_move_pct = result["expected_move_pct"] # e.g., 8.5
Usage Examples
Example 1: Basic Usage with Expected Move
from scripts.monte_carlo import SimulationParams, CalendarSpreadSimulator
# Get expected move from calculator.py for NVDA: 8.5%
params = SimulationParams(
spot_price=135,
strike_price=135,
front_dte=7,
back_dte=37,
pre_earnings_iv=0.65,
post_earnings_iv=0.32,
back_month_pre_iv=0.45,
back_month_post_iv=0.38,
earnings_jump_mean=0.0,
earnings_jump_std=0.05, # Will be overridden
drift_rate=0.0,
risk_free_rate=0.05,
num_simulations=5000,
profit_target=0.25,
expected_move_pct=8.5, # From market straddle
)
simulator = CalendarSpreadSimulator(params)
results = simulator.run()
stats = simulator.analyze_results(results)
simulator.plot_results(results, stats)
Example 2: Different Expected Moves for Different Stocks
# High volatility stock (e.g., TSLA)
high_vol_params = SimulationParams(
spot_price=245,
strike_price=245,
# ... other params ...
expected_move_pct=12.0, # TSLA typically has larger expected moves
)
# Low volatility stock (e.g., AAPL)
low_vol_params = SimulationParams(
spot_price=185,
strike_price=185,
# ... other params ...
expected_move_pct=6.5, # AAPL typically has smaller expected moves
)
Example 3: Disable Strategy Rules (Pure Monte Carlo)
# Traditional Monte Carlo without strategy execution rules
params = SimulationParams(
# ... other params ...
expected_move_pct=None, # Don't use market calibration
use_dynamic_strikes=False, # No dynamic adjustments
early_exit_on_large_move=False, # No early exits
)
Output Changes
Console Output
Adjusted DTEs to land on Fridays:
Front: 7 -> 12 days
Back: 37 -> 40 days
Calibrated earnings jump from expected move:
Expected move: 8.50%
Earnings jump std: 0.0500 -> 0.0850
Dynamic strike selection: ENABLED (based on 8.50% expected move)
Early exit on large moves: ENABLED (threshold: 12.75%)
Results Include Exit Reasons
Exit Reason Breakdown:
Profit Target: 54.9% # Hit profit target
Expiration: 13.8% # Held to expiration
Large Move: 31.3% # Exited due to large move
Stop Loss: 0.0% # Hit stop loss
Visualization Updates
The summary statistics panel now includes: - Expected move percentage (if configured) - Exit reason breakdown - Strategy execution rules applied
Workflow Integration
Complete Workflow:
-
Find candidate with
calculator.py: -
Create simulation with market data:
-
Run simulation:
-
Analyze results with strategy execution metrics
Benefits
1. Market Calibration
- Uses actual market expectations instead of guesses
- More realistic price distributions
- Better risk assessment
2. Strategy Realism
- Simulates actual trading decisions
- Models when you'd actually exit positions
- Shows distribution of exit reasons
3. Risk Management
- Automatic stop losses on bad trades
- Early exits on abnormal moves
- Reduces tail risk
4. Comparative Analysis
- Compare different stocks' expected moves
- See how different volatility profiles affect strategy
- Optimize entry criteria based on expected move
Important Notes
-
Expected move represents ~1 standard deviation of the distribution implied by straddle pricing
-
Earnings jump std is auto-calibrated when
expected_move_pctis provided, overriding manualearnings_jump_std -
Strategy rules only apply when enabled - you can still run pure Monte Carlo by setting parameters to
None/False -
Date constraints remain active - Earnings only on weekdays, options expire on Fridays
-
Multiple trades per year - The yearly simulation chains multiple calendar spreads sequentially
Advanced Usage
Custom Exit Rules
To add your own exit rules, modify the run_single_simulation method in CalendarSpreadSimulator:
# Strategy Rule 4: Your custom rule
if exit_day is None and your_condition:
exit_day = day
exit_pnl = pnl
exit_reason = "custom_rule"
break
Strike Selection Enhancement
Currently strikes stay ATM. To implement dynamic strike selection:
if p.expected_move_pct is not None and p.use_dynamic_strikes:
# Example: Go slightly OTM for high expected moves
if p.expected_move_pct > 10.0:
strike_price = p.spot_price * 1.02 # 2% OTM call
else:
strike_price = p.spot_price # ATM
Troubleshooting
Q: Simulation shows different results with same parameters
A: Monte Carlo uses random sampling. Run more simulations (increase num_simulations) for stable statistics.
Q: Expected move calibration not working
A: Ensure expected_move_pct is set as a float (e.g., 8.5, not "8.5%"). Check console output for calibration message.
Q: No large move exits occurring
A: Verify early_exit_on_large_move=True and expected_move_pct is set. Check if moves in simulation exceed 1.5x threshold.
Q: All trades hitting stop loss A: Your IVs or expected move may be too high/low. Verify parameters match actual market conditions.