Examples¶
This page provides real-world examples of using Optopsy for options backtesting.
Basic Examples¶
Simple Long Calls Backtest¶
import optopsy as op
# Load data
data = op.csv_data('SPX_2023.csv')
# Backtest long calls
results = op.long_calls(data)
print(results.head())
Iron Condor with Custom Parameters¶
results = op.iron_condor(
data,
max_entry_dte=45,
exit_dte=21,
leg2_delta={"target": 0.25, "min": 0.20, "max": 0.30}, # short strikes
leg3_delta={"target": 0.25, "min": 0.20, "max": 0.30},
min_bid_ask=0.10
)
# Filter for best performing DTE ranges
best_dte = results[results['mean'] > 0.20]
print(best_dte)
Advanced Examples¶
Delta-Targeted Iron Condors¶
Target specific delta ranges for short strikes:
results = op.iron_condor(
data,
max_entry_dte=45,
exit_dte=21,
leg2_delta={"target": 0.16, "min": 0.15, "max": 0.20},
leg3_delta={"target": 0.16, "min": 0.15, "max": 0.20},
delta_interval=0.05,
min_bid_ask=0.10
)
# Analyze by delta ranges
print(results.groupby('delta_range')['mean'].describe())
Earnings Straddle Strategy¶
Backtest long straddles on specific dates using entry_dates:
import pandas as pd
from optopsy import custom_signal, apply_signal
# Define earnings dates as entry signals
earnings_dates = pd.DataFrame({
"underlying_symbol": ["SPX"] * 4,
"quote_date": pd.to_datetime(['2023-01-15', '2023-04-15', '2023-07-15', '2023-10-15']),
"enter": [True] * 4,
})
sig = custom_signal(earnings_dates, flag_col="enter")
entry_dates = apply_signal(earnings_dates, sig)
# Backtest ATM straddles — only enters on earnings dates
results = op.long_straddles(
data,
max_entry_dte=7, # Enter up to 1 week before expiration
exit_dte=0, # Hold to expiration
leg1_delta={"target": 0.50, "min": 0.45, "max": 0.55}, # ATM
entry_dates=entry_dates,
)
print(results)
Time Decay Analysis¶
Compare different exit times for short strangles:
exit_times = [0, 7, 14, 21, 30]
results_by_exit = {}
for exit_dte in exit_times:
results = op.short_strangles(
data,
max_entry_dte=45,
exit_dte=exit_dte,
)
results_by_exit[exit_dte] = results['mean'].mean()
# Plot results
import matplotlib.pyplot as plt
plt.bar(exit_times, list(results_by_exit.values()))
plt.xlabel('Exit DTE')
plt.ylabel('Mean Return')
plt.title('Short Strangle Returns by Exit Time')
plt.show()
Slippage Comparison¶
Compare different slippage models:
slippage_modes = ['mid', 'spread', 'liquidity', 'per_leg']
results_comparison = {}
for mode in slippage_modes:
results = op.iron_condor(
data,
max_entry_dte=45,
exit_dte=21,
slippage=mode,
fill_ratio=0.5,
reference_volume=1000
)
results_comparison[mode] = results['mean'].mean()
print("Slippage Model Comparison:")
for mode, avg_return in results_comparison.items():
print(f"{mode}: {avg_return:.2%}")
Early Exit Examples¶
Stop Loss and Take Profit¶
Close positions early based on P&L thresholds:
# Short puts with early exit rules
trades = op.short_puts(
data,
max_entry_dte=45,
exit_dte=0,
stop_loss=-1.0, # Close if losing 100%+
take_profit=0.50, # Close if gaining 50%+
raw=True
)
# Analyze exit types
print(trades['exit_type'].value_counts())
# Compare returns by exit type
print(trades.groupby('exit_type')['pct_change'].describe())
Maximum Holding Period¶
Limit how long positions are held:
trades = op.iron_condor(
data,
max_entry_dte=45,
max_hold_days=21, # Exit after 21 calendar days
take_profit=0.50, # Or take profit at 50%
raw=True
)
# Check which exit condition triggered
print(trades['exit_type'].value_counts())
Commission Examples¶
Per-Contract Commission¶
Full Fee Structure¶
from optopsy import Commission
results = op.iron_condor(
data,
commission=Commission(
per_contract=0.65,
base_fee=4.95,
),
raw=True
)
Data Analysis Examples¶
Raw Trade Data Analysis¶
Get individual trades for custom analysis:
# Get raw trade data
trades = op.iron_condor(
data,
max_entry_dte=45,
exit_dte=21,
raw=True # Return individual trades
)
# Custom analysis
import pandas as pd
# Win rate
win_rate = (trades['pct_change'] > 0).mean()
print(f"Win Rate: {win_rate:.1%}")
# Average winner vs loser
avg_winner = trades[trades['pct_change'] > 0]['pct_change'].mean()
avg_loser = trades[trades['pct_change'] < 0]['pct_change'].mean()
print(f"Avg Winner: {avg_winner:.2%}")
print(f"Avg Loser: {avg_loser:.2%}")
# Profit factor
total_profit = trades[trades['pct_change'] > 0]['pct_change'].sum()
total_loss = abs(trades[trades['pct_change'] < 0]['pct_change'].sum())
profit_factor = total_profit / total_loss if total_loss > 0 else float('inf')
print(f"Profit Factor: {profit_factor:.2f}")
Monthly Performance¶
Analyze strategy performance by month:
trades = op.short_puts(data, raw=True)
# Convert to datetime
trades['entry_date'] = pd.to_datetime(trades['quote_date_entry'])
trades['month'] = trades['entry_date'].dt.to_period('M')
# Group by month
monthly_perf = trades.groupby('month').agg({
'pct_change': ['count', 'mean', 'std', 'sum']
})
print(monthly_perf)
Strike Selection Analysis¶
Analyze performance by delta range:
results = op.short_puts(
data,
max_entry_dte=45,
exit_dte=21,
delta_interval=0.05,
)
# Find optimal delta range
optimal = results.loc[results['mean'].idxmax()]
print(f"Optimal Delta Range: {optimal['delta_range']}")
print(f"Mean Return: {optimal['mean']:.2%}")
print(f"Count: {optimal['count']:.0f}")
Multi-Strategy Comparison¶
Compare multiple strategies:
strategies = {
'Long Calls': op.long_calls,
'Short Puts': op.short_puts,
'Iron Condor': op.iron_condor,
'Long Straddle': op.long_straddles
}
comparison = {}
for name, strategy_func in strategies.items():
results = strategy_func(
data,
max_entry_dte=45,
exit_dte=21
)
comparison[name] = {
'mean': results['mean'].mean(),
'std': results['std'].mean(),
'max': results['max'].max(),
'min': results['min'].min()
}
# Display comparison
df_comparison = pd.DataFrame(comparison).T
print(df_comparison)
Portfolio Simulation¶
Simulate a weighted portfolio across multiple strategies using simulate_portfolio():
import optopsy as op
spy = op.csv_data('SPY_2023.csv')
qqq = op.csv_data('QQQ_2023.csv')
result = op.simulate_portfolio(
legs=[
{
"data": spy,
"strategy": op.short_puts,
"weight": 0.6,
"max_entry_dte": 45,
"exit_dte": 14,
},
{
"data": qqq,
"strategy": op.iron_condor,
"weight": 0.4,
"max_entry_dte": 30,
"exit_dte": 7,
},
],
capital=100_000,
)
# Portfolio-level summary
print(result.summary)
# Combined trade log (includes a 'leg' column)
print(result.trade_log)
# Portfolio equity curve
print(result.equity_curve)
# Access individual leg results
for name, leg_result in result.leg_results.items():
print(f"\n{name}:")
print(leg_result.summary)
Strategy Simulation¶
Use simulate() for chronological backtesting with capital tracking, position limits, and a full equity curve:
import optopsy as op
data = op.csv_data('SPX_2023.csv')
# Simulate short puts with $100k capital
result = op.simulate(
data,
op.short_puts,
capital=100_000,
quantity=1,
max_positions=2,
multiplier=100,
selector='nearest', # Pick ATM strike per entry date
max_entry_dte=45,
exit_dte=14,
)
# Summary statistics (includes Sharpe, Sortino, VaR, max drawdown, etc.)
print(result.summary)
# Individual trade log with P&L
print(result.trade_log)
# Equity curve indexed by exit date
print(result.equity_curve)
The selector parameter controls how a trade is picked when multiple candidates exist for the same entry date:
| Selector | Behavior |
|---|---|
'nearest' |
Closest to ATM (lowest absolute OTM%) |
'highest_premium' |
Largest credit received |
'lowest_premium' |
Cheapest debit paid |
'first' |
First candidate (deterministic) |
You can also pass a custom callable as the selector.
Risk Metrics¶
Optopsy provides built-in risk metrics via the metrics module. These are used automatically by simulate() and are also available standalone:
import optopsy as op
trades = op.iron_condor(data, raw=True)
returns = trades['pct_change']
# Individual metrics
print(f"Sharpe: {op.sharpe_ratio(returns):.2f}")
print(f"Sortino: {op.sortino_ratio(returns):.2f}")
print(f"Win Rate: {op.win_rate(returns):.1%}")
print(f"Profit Factor: {op.profit_factor(returns):.2f}")
print(f"VaR (95%): {op.value_at_risk(returns, 0.95):.2%}")
print(f"CVaR (95%): {op.conditional_value_at_risk(returns, 0.95):.2%}")
print(f"Max Drawdown: {op.max_drawdown_from_returns(returns):.2%}")
print(f"Calmar: {op.calmar_ratio(returns):.2f}")
print(f"Omega: {op.omega_ratio(returns):.2f}")
print(f"Tail Ratio: {op.tail_ratio(returns):.2f}")
# Or compute all at once
all_metrics = op.compute_risk_metrics(returns)
print(all_metrics)
Custom Signal Entry Example¶
Use custom_signal() to drive entries from any external data source:
import pandas as pd
import optopsy as op
# Load your own signal data (model output, manual flags, external indicator, etc.)
my_flags = pd.DataFrame({
"underlying_symbol": ["SPY"] * 5,
"quote_date": pd.date_range("2023-01-02", periods=5, freq="B"),
"go": [True, False, True, False, True],
})
sig = op.custom_signal(my_flags, flag_col="go")
entry_dates = op.apply_signal(my_flags, sig)
data = op.csv_data('SPY_2023.csv')
results = op.long_calls(data, entry_dates=entry_dates)
print(results)
Performance Metrics (Manual)¶
Calculate performance statistics manually from raw trades:
def calculate_metrics(trades_df):
"""Calculate performance metrics for a strategy."""
returns = trades_df['pct_change']
metrics = {
'Total Trades': len(returns),
'Win Rate': (returns > 0).mean(),
'Mean Return': returns.mean(),
'Median Return': returns.median(),
'Std Dev': returns.std(),
'Max Win': returns.max(),
'Max Loss': returns.min(),
'Profit Factor': returns[returns > 0].sum() / abs(returns[returns < 0].sum()),
'Sharpe Ratio': returns.mean() / returns.std() if returns.std() > 0 else 0
}
return pd.Series(metrics)
# Apply to strategy
trades = op.iron_condor(data, raw=True)
metrics = calculate_metrics(trades)
print(metrics)
Tip
For most use cases, prefer op.simulate() or op.compute_risk_metrics() over manual calculations — they handle edge cases, annualisation, and additional metrics automatically.
Working with Different Data Sources¶
Historical Options Data Providers¶
# Example: Loading from different CSV formats
# Column parameters are integer indices (0-based)
# Format 1: CBOE data export (map columns by position)
data = op.csv_data(
'cboe_spx.csv',
underlying_symbol=0,
underlying_price=1,
option_type=2,
expiration=3,
quote_date=4,
strike=5,
bid=6,
ask=7,
delta=8
)
# Format 2: Non-standard column ordering
data = op.csv_data(
'provider_data.csv',
underlying_symbol=0,
underlying_price=3,
option_type=5,
expiration=6,
quote_date=7,
strike=8,
bid=9,
ask=10,
delta=11
)
Best Practices¶
1. Always Use Filters¶
# Bad: No filtering
results = op.iron_condor(data)
# Good: Filtered for quality
results = op.iron_condor(
data,
max_entry_dte=45,
exit_dte=21,
min_bid_ask=0.10,
leg2_delta={"target": 0.20, "min": 0.15, "max": 0.25},
leg3_delta={"target": 0.20, "min": 0.15, "max": 0.25},
)
2. Compare Apples to Apples¶
# Use same parameters when comparing strategies
params = {
'max_entry_dte': 45,
'exit_dte': 21,
}
ic_results = op.iron_condor(data, **params)
strangle_results = op.short_strangles(data, **params)
3. Realistic Slippage¶
# Use liquidity mode for realistic results
results = op.iron_condor(
data,
slippage='liquidity',
fill_ratio=0.5,
reference_volume=1000
)
Entry Signal Examples¶
Optopsy supports filtering entries with technical analysis indicators. See the Entry Signals page for the full reference. Here's a quick example:
from optopsy import apply_signal, rsi_below, sma_above, signal
# Enter long calls only when RSI < 30 AND price is above the 50-day SMA
entry = signal(rsi_below(14, 30)) & signal(sma_above(50))
entry_dates = apply_signal(data, entry)
results = op.long_calls(data, entry_dates=entry_dates)
Data Sources¶
Optopsy works with any historical options data in CSV or DataFrame format. Some sources:
- EODHD US Stock Options Data API - Built-in integration via the Chat UI (API key required)
- HistoricalOptionData.com - Free samples available
- CBOE DataShop - Official exchange data
- Polygon.io - Options data API
- Your broker's data export (Schwab, IBKR, Tastytrade, etc.)
Next Steps¶
- Review Strategy documentation for specific strategy details
- Check Parameters for all configuration options
- Learn about Entry Signals for TA-based filtering
- Try the AI Chat UI for natural language backtesting
- See API Reference for function signatures