Skip to content

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:

  1. IV Overpricing: Options systematically overprice earnings moves due to hedging demand and speculation
  2. IV Crush: Implied volatility collapses after earnings, benefiting short volatility positions
  3. Actual Move < Expected Move: Stocks typically move less than the expected move priced into straddles
  4. 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:

underlying_price >= config.thresholds.min_stock_price  # Default: $10

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:

  1. get_expiration_dates(ticker)
  2. Lightweight function to fetch available expiration dates
  3. Used for initial calendar spread construction

  4. get_options_chain(ticker, expiration_date)

  5. Fetches full options chain for a specific expiration
  6. Returns all strikes with bid/ask/IV data
  7. Used to find ATM strike

  8. get_option_prices(ticker, expiration_date, strike, option_type)

  9. Gets specific option prices at a strike
  10. Returns bid/ask/mid/IV
  11. 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%           | ...

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

uv run python scripts/next_friday_calc.py

Shows only recommended symbols passing all 4 tests.

Verbose Mode

uv run python scripts/next_friday_calc.py --verbose

Shows full metrics table for all symbols with earnings.

Typical Workflow

  1. Run script on Monday/Tuesday of earnings week
  2. Review recommended symbols
  3. Calculate position sizes (6% of portfolio per trade)
  4. Enter positions 15 min before close day before earnings
  5. Set alerts for morning after earnings
  6. Exit positions 15 min after market open
  7. Track results and adjust if needed

Integration with Database

The script requires a PostgreSQL database with earnings calendar data:

Connection: Reads from .env file:

DB_HOST=localhost
DB_PORT=5432
DB_NAME=trade_data
DB_USER=trader
DB_PASSWORD=xxxxx

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

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

  1. Backtesting Mode: Simulate past earnings to validate strategy
  2. Live Position Tracking: Monitor open positions and P&L
  3. Auto-Exit Alerts: Send notifications when it's time to exit
  4. Historical Performance: Track actual trades vs expected
  5. Machine Learning: Enhance filters with ML-based scoring
  6. 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

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.