Next Friday Calendar Implementation Guide
Overview
The next_friday_calc.py script is the practical implementation of the Earnings Volatility Selling Strategy described in the Selling Earnings Volatility Strategy document. This script identifies upcoming earnings events and filters them based on rigorous statistical criteria to find high-probability trading opportunities.
Strategy Foundation
The Core Edge
The strategy exploits a systematic market inefficiency around earnings announcements:
- IV Overpricing: Options systematically overprice earnings moves due to hedging demand and speculation
- IV Crush: Implied volatility collapses after earnings, benefiting short volatility positions
- Actual Move < Expected Move: Stocks typically move less than the expected move priced into straddles
- Price-Insensitive Buyers: Institutional hedgers and retail speculators drive up option prices regardless of fair value
Why Calendar Spreads?
Based on Monte Carlo simulations over 72,500 historical earnings events (2007-2019), the long calendar spread structure provides:
- Better Risk/Reward: Higher Sharpe ratio than short straddles (3.5 vs 3.0)
- Limited Downside: Max loss is the debit paid (vs unlimited loss in straddles)
- Lower Volatility: Smaller drawdowns and more consistent returns
- Higher Kelly Criterion: 60% vs 6.5% suggests superior risk-adjusted returns
The optimal position sizing is 10% Kelly (6% of portfolio per trade) which historically delivers: - Mean return: 7.3% per trade - Win rate: 66% - Max drawdown over 10 years: ~20% - Annualized Sharpe: 3.5
Script Architecture
Workflow
1. Database Query
↓
2. Fetch Earnings Calendar
↓
3. Filter by Date Range (Next Friday → Friday After)
↓
4. Compute Recommendations (4 criteria)
↓
5. Filter Passing Symbols
↓
6. Fetch Options Chains
↓
7. Display Calendar Spread Details
↓
8. Show Strategy Summary
Key Functions
get_next_friday()
Calculates the next Friday from today. If today is Friday, returns the following Friday (7 days ahead).
Returns: date object
get_symbols_with_earnings_between_dates(start_date, end_date)
Queries the PostgreSQL database for symbols with earnings between two dates.
Args:
- start_date: Start of date range (inclusive)
- end_date: End of date range (inclusive)
Returns: Dictionary mapping symbol → earnings date
Database Schema:
SELECT DISTINCT symbol, report_date
FROM public.earnings_calendar
WHERE report_date >= start_date AND report_date <= end_date
get_calendar_spread_details(symbol)
Fetches live options data and constructs calendar spread pricing.
Returns: Dictionary with structure:
{
'symbol': str,
'current_price': float,
'atm_strike': float,
'front_expiration': str, # YYYY-MM-DD
'back_expiration': str, # YYYY-MM-DD
'front_call_mid': float, # Credit received
'back_call_mid': float, # Debit paid
'calendar_debit': float, # Net cost
'max_loss': float, # = calendar_debit
}
Logic:
1. Get all expiration dates via get_expiration_dates()
2. Use first two expirations (front and back month)
3. Fetch options chains via get_options_chain()
4. Find ATM strike (closest to current price)
5. Get call option prices at ATM strike via get_option_prices()
6. Calculate calendar debit: back_month_call - front_month_call
Entry Criteria (The 4 Tests)
The strategy uses a rule-based filter that mirrors the statistical findings from the research. All 4 criteria must pass:
1. Stock Price ≥ $10
Rationale: Ensures sufficient liquidity and reasonable bid-ask spreads.
Implementation:
2. Average Volume ≥ 250,000 shares/day
Rationale: Higher volume correlates with better returns. More participants → more hedging/speculation → higher IV overpricing.
Statistical Finding: Top decile by volume had 2x the returns of bottom decile.
Implementation:
avg_volume = price_history["Volume"].rolling(30).mean().iloc[-1]
avg_volume >= config.thresholds.min_avg_volume # Default: 250,000
3. IV30/RV30 Ratio ≥ 1.0
Rationale: If IV exceeds realized volatility in normal times, it's likely even more overpriced before earnings.
Statistical Finding: IV30/RV30 > 1.2 showed consistently positive returns across all simulations.
Implementation:
iv30 = term_spline(30) # Interpolated 30-day IV
rv30 = yang_zhang(price_history, window=30) # Yang-Zhang realized vol
iv30_rv30_ratio = iv30 / rv30
iv30_rv30_ratio >= config.thresholds.min_iv_rv_ratio # Default: 1.0
4. Term Structure Slope ≤ 0 (Backwardation)
Rationale: Negative slope (front-month IV > back-month IV) indicates short-term options are overpriced relative to longer-term options. This "backwardation" shape is ideal for calendar spreads.
Statistical Finding: Most negative slopes (top decile) had 3x the returns of flat/positive slopes.
Implementation:
# Slope between first DTE and minimum DTE (usually 45 days)
ts_slope = (term_spline(45) - term_spline(first_dte)) / (45 - first_dte)
ts_slope <= config.thresholds.max_ts_slope # Default: 0.0
Technical Implementation Details
Options Data Pipeline
The script uses the new options data functions from trade_calc.data:
get_expiration_dates(ticker)- Lightweight function to fetch available expiration dates
-
Used for initial calendar spread construction
-
get_options_chain(ticker, expiration_date) - Fetches full options chain for a specific expiration
- Returns all strikes with bid/ask/IV data
-
Used to find ATM strike
-
get_option_prices(ticker, expiration_date, strike, option_type) - Gets specific option prices at a strike
- Returns bid/ask/mid/IV
- Used for precise calendar spread pricing
Calendar Spread Construction
Structure:
Long ATM Call Calendar Spread
SELL: Front-month $X Call → Credit: C1
BUY: Back-month $X Call → Debit: C2
Net Position: Debit = C2 - C1
Max Loss: Debit paid
Max Gain: Unlimited (but practically capped)
Example:
Symbol: AAPL
Current Price: $180.00
ATM Strike: $180.00
SELL: Feb 7 $180 Call @ $2.50 (Credit)
BUY: Mar 7 $180 Call @ $4.00 (Debit)
Calendar Debit: $4.00 - $2.50 = $1.50
Max Loss: $1.50
Expected Move Calculation
The script calculates the expected move percentage based on straddle pricing:
straddle_price = atm_call_mid + atm_put_mid
expected_move_pct = (straddle_price / underlying_price) * 100
This represents the market's implied 1-standard-deviation move (±68% probability range).
Configuration
The script uses StrategyConfig from configs/strategy_config.toml:
[thresholds]
min_stock_price = 10.0 # Minimum stock price
min_avg_volume = 250_000 # Minimum 30-day avg volume
min_iv_rv_ratio = 1.0 # Minimum IV30/RV30 ratio
max_ts_slope = 0.0 # Maximum term structure slope
[expiration]
min_days_to_expiration = 45 # Minimum DTE for back month
[volatility]
rolling_window = 30 # Window for RV calculation
trading_periods_per_year = 252 # Annualization factor
price_history_period = "3mo" # History to fetch
Output Format
Summary Table (Verbose Mode)
Symbol | Earnings Date | Price | Avg Volume | IV30/RV30 | TS Slope | Expected Move % | ...
AAPL | 2024-01-25 | 180.5 | 52.5M | 1.25 | -0.045 | 4.2% | ...
Recommended Symbols
Only symbols passing all 4 tests are shown:
✓ Recommended - Symbols passing all 4 tests (3):
Symbol | Earnings Date | Expected Move
AAPL | 2024-01-25 | 4.2%
MSFT | 2024-01-26 | 3.8%
TSLA | 2024-01-27 | 8.5%
Calendar Spread Details
For each recommended symbol:
● AAPL (Earnings: 2024-01-25)
Current Price $180.50
ATM Strike $180.00
SELL (Front Month) 2024-01-26
Call Bid/Ask $2.30 / $2.70
Call Mid (Credit) $2.50
BUY (Back Month) 2024-02-23
Call Bid/Ask $3.80 / $4.20
Call Mid (Debit) $4.00
Net Position
Calendar Debit $1.50
Max Loss $1.50
Considered Symbols (Fallback)
If no symbols pass all 4 tests, shows symbols passing at least 2 tests:
⚠ Considered - Symbols passing 2+ tests (5):
Symbol | Earnings Date | Tests | Expected Move | Failed
NVDA | 2024-01-28 | 3/4 | 6.2% | Volume
AMD | 2024-01-29 | 2/4 | 5.5% | IV/RV, TS Slope
Position Sizing Guidelines
Based on 10% Kelly criterion (6% of portfolio):
| Portfolio Size | Max Position Size | Example Debit | # of Contracts |
|---|---|---|---|
| $10,000 | $600 | $1.50 | 4 contracts |
| $25,000 | $1,500 | $1.50 | 10 contracts |
| $50,000 | $3,000 | $1.50 | 20 contracts |
| $100,000 | $6,000 | $1.50 | 40 contracts |
Formula:
max_position_size = portfolio_value * 0.06
num_contracts = floor(max_position_size / (calendar_debit * 100))
Entry and Exit Timing
Entry
When: 15 minutes before market close the day before earnings
Why: - IV is at its peak - Front-month IV is maximally elevated - Avoids overnight gap risk before entry
Exit
When: 15 minutes into trading session after earnings ("Jump Exit")
Why: - Captures immediate IV crush - Front-month IV drops faster than back-month - Avoids post-earnings announcement drift (PEAD) working against us - Historical data shows morning exit outperforms holding until close
Risk Considerations
Maximum Loss
Limited to the debit paid. In worst case (stock moves significantly): - Front-month option expires worthless or deep ITM - Back-month option retains some value - Loss approaches but doesn't exceed initial debit
Maximum Drawdown
Historical simulations (10% Kelly, 10-year period): - Mean max drawdown: ~20% - 95th percentile: ~35% - Longest drawdown duration: ~6 months
Win Rate
66% based on historical back-test
This means 34% of trades will be losers. Edge comes from: - Winners being larger than losers on average - High frequency of trades (dozens per year) - Law of large numbers working in our favor
Usage
Basic Usage
Shows only recommended symbols passing all 4 tests.
Verbose Mode
Shows full metrics table for all symbols with earnings.
Typical Workflow
- Run script on Monday/Tuesday of earnings week
- Review recommended symbols
- Calculate position sizes (6% of portfolio per trade)
- Enter positions 15 min before close day before earnings
- Set alerts for morning after earnings
- Exit positions 15 min after market open
- Track results and adjust if needed
Integration with Database
The script requires a PostgreSQL database with earnings calendar data:
Connection: Reads from .env file:
Table Schema:
CREATE TABLE public.earnings_calendar (
id SERIAL PRIMARY KEY,
symbol VARCHAR(10) NOT NULL,
company_name VARCHAR(255),
report_date DATE NOT NULL,
fiscal_quarter VARCHAR(10),
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_earnings_report_date ON public.earnings_calendar(report_date);
CREATE INDEX idx_earnings_symbol ON public.earnings_calendar(symbol);
Performance Expectations
Based on 10-year historical simulation with 10% Kelly:
| Metric | Value |
|---|---|
| Starting Capital | $10,000 |
| Mean Ending Capital | $6,185,120 |
| CAGR | ~90% |
| Sharpe Ratio | 3.5 |
| Win Rate | 66% |
| Mean Trade Return | 7.3% |
| Max Drawdown | ~20% |
| Longest Drawdown | ~6 months |
Important: These are simulated results using historical data. Real trading involves slippage, commission, execution risk, and market conditions may differ from historical periods.
Comparison with Alternative Strategies
Short Straddle (Jump Exit)
- Higher Returns: 9% mean vs 7.3% for calendar
- Higher Risk: Fat left tail, 1% chance of -130% loss
- Lower Kelly: 6.5% vs 60% suggests much riskier
- Higher Volatility: SD of 48% vs 29%
Verdict: Calendar is superior for risk-adjusted returns
Hold Until Close (Move Exit)
- Worse Performance: Consistently negative due to PEAD
- Post-Earnings Drift: Stock continues moving in direction of surprise
- Avoid: Always exit in the morning after earnings
Common Issues and Troubleshooting
No Recommended Symbols
Possible Reasons: 1. Market conditions (low volatility environment) 2. Earnings season timing (fewer events) 3. Criteria too strict for current market
Solutions: - Check "Considered" symbols (2+ tests passed) - Review individual test failures - Consider adjusting thresholds in config (with caution)
Unable to Fetch Calendar Spread Details
Possible Reasons: 1. Low liquidity options 2. Wide bid-ask spreads 3. API rate limiting 4. Network issues
Solutions:
- Refresh and try again
- Check symbol manually on options platform
- Verify yfinance is working (yf.Ticker('AAPL').options)
Database Connection Failed
Possible Reasons:
1. Database not running
2. Wrong credentials in .env
3. Network firewall blocking connection
Solutions:
- Verify PostgreSQL is running: pg_isready
- Check .env file exists and has correct credentials
- Test connection: psql -h localhost -U trader -d trade_data
Future Enhancements
Planned Features
- Backtesting Mode: Simulate past earnings to validate strategy
- Live Position Tracking: Monitor open positions and P&L
- Auto-Exit Alerts: Send notifications when it's time to exit
- Historical Performance: Track actual trades vs expected
- Machine Learning: Enhance filters with ML-based scoring
- Alternative Structures: Support put calendars and iron condors
Possible Improvements
- Add Greeks display (delta, gamma, vega, theta)
- Show bid-ask spread percentage
- Display open interest for liquidity assessment
- Add filters for market cap, sector, etc.
- Support for multi-leg complex spreads
- Integration with broker APIs for automated execution
References
- Selling Earnings Volatility Strategy - Complete strategy documentation
- Yang-Zhang Volatility Estimator - RV calculation method
- Kelly Criterion - Position sizing theory
- Post-Earnings Announcement Drift (PEAD) - Why we exit early
Disclaimer
This script and strategy documentation are for educational purposes only. Options trading involves significant risk of loss and is not suitable for all investors. Past performance does not guarantee future results. Always conduct your own research and consider consulting with a financial advisor before trading.
The historical performance data is based on simulations using past market data and includes estimated trading costs. Actual trading results may vary significantly due to: - Market conditions - Execution slippage - Commission and fees - Tax implications - Liquidity constraints - Psychological factors
Never risk more capital than you can afford to lose.