Never get caught off guard by an earnings report again. OpenClaw checks your watchlist every morning and alerts you 3 days before any ticker reports — using free data, zero paid subscriptions.
Every public company is required by law to report its financial results four times a year—once each quarter. The dates when these reports will be published are announced in advance on the earnings calendar. This is public information. You can find it on Yahoo Finance, CNBC, MarketWatch, or any financial data provider.
But here's the problem: if you have a diversified watchlist of 20+ stocks, manually checking each one on different financial sites every single day is tedious and error-prone. You might miss a ticker entirely. You might be unaware that your largest position is reporting in two days. That's when an earnings report can move a stock 10–20% or more in minutes, and you're caught off guard.
Before Market Open (BMO) vs After Market Close (AMC) timing matters for how you position. A report released BMO can gap your position at the open; AMC gives you time to react after the close. OpenClaw tracks both, so you can plan accordingly.
The Earnings Cycle Timeline
Most earnings happen within 4–6 weeks of the quarter's end. Earnings calls (where executives explain the results) typically happen within days. The stock can react anywhere from 30 minutes to several hours after the announcement.
The traditional approach is manual. You. Every morning, checking 20 different sites for 20 tickers. Hoping you don't skip one.
OpenClaw replaces that with a single script that runs automatically every morning and does the work for you. Here's the before-and-after:
| Without OpenClaw | With OpenClaw |
|---|---|
| Manually check Yahoo Finance, Seeking Alpha, etc. every day | Script runs automatically at 6:30 AM every weekday |
| Easy to miss a ticker. No alerts if you don't check. | Checks all tickers in your config. Sends email if anything reports in the next 3 days. |
| Inconsistent record-keeping. Dates scribbled in notes. | Results logged to CSV automatically. Historical record for backtesting. |
| Reacts to earnings. Positioned during the move. | Gets a 3-day heads-up. Time to review position size, check IV, read filings. |
That 3-day window is your edge. You're not reacting—you're preparing. You can check if the stock is overbought (high IV rank), pull the latest 10-Q filing, read the last earnings call transcript, and decide whether to trim, hold, or add to your position. By the time the report hits, you're ready.
earnings-config.yaml SnippetAll OpenClaw scripts read from a single config file. This is where you tell OpenClaw what to do: which tickers to watch, how many days ahead to look, when to send alerts, and when to run.
Here's the earnings calendar section of the config:
watchlist:
tickers: [AAPL, MSFT, NVDA, AMZN, GOOGL]
calendar:
lookahead_days: 5
alert_days_before: 3
market_hours_only: false # include pre-market & after-hours reports
alerts:
email: "you@example.com"
daily_digest: true
send_time: "06:30"
scheduler:
earnings_calendar:
script: earnings_calendar.py
schedule: "30 6 * * 1-5" # Weekdays at 6:30 AM
Field Explanations:
watchlist.tickers — The tickers you want to monitor. Add or remove as needed.lookahead_days — How far ahead to search for earnings (5 days = next work week).alert_days_before — Trigger an alert if an earnings date is this many days away (3 = get warned 3 days out).market_hours_only — Set to true if you only care about BMO (before market open) reports. false includes AMC too.alerts.email — Where to send the alert email. Can be your Gmail, Outlook, or work email.alerts.send_time — Time of day to send alerts (24-hour format). 06:30 = 6:30 AM.scheduler.schedule — Cron expression. "30 6 * * 1-5" means 6:30 AM, every weekday.earnings_calendar.pyThis is the script that OpenClaw runs every morning. It reads your config, fetches earnings dates from yfinance (a free, open-source library), checks for upcoming reports, and sends an email alert if anything falls within your alert window.
Every line is commented so you can understand what's happening. You don't need to modify this script to use it—just save it, and let the cron scheduler run it.
"""
earnings_calendar.py — OpenClaw Earnings Calendar Monitor
Reads earnings-config.yaml, checks upcoming earnings for your watchlist,
and sends an email alert if any ticker reports within your alert window.
Usage: python earnings_calendar.py
(Typically run by cron each morning)
"""
import yaml
import yfinance as yf
import pandas as pd
from datetime import datetime, timedelta
import smtplib
from email.mime.text import MIMEText
import csv
# ──────────────────────────────────────────────────────────────────────────
# 1. LOAD CONFIGURATION
# ──────────────────────────────────────────────────────────────────────────
with open("earnings-config.yaml") as f:
cfg = yaml.safe_load(f)
# Extract settings from config
tickers = cfg["watchlist"]["tickers"]
lookahead = cfg["calendar"]["lookahead_days"]
alert_window = cfg["calendar"]["alert_days_before"]
alert_email = cfg["alerts"]["email"]
send_time = cfg["alerts"]["send_time"]
print("\n" + "="*60)
print("OpenClaw Earnings Calendar Monitor")
print("="*60)
print(f"Scanning {len(tickers)} tickers for earnings in next {lookahead} days")
print(f"Alert threshold: {alert_window} days before report")
print()
# Calculate date boundaries
today = datetime.today().date()
cutoff = today + timedelta(days=lookahead)
print(f"Date range: {today} to {cutoff}\n")
# ──────────────────────────────────────────────────────────────────────────
# 2. FETCH EARNINGS DATES FROM YFINANCE
# ──────────────────────────────────────────────────────────────────────────
upcoming = []
for ticker in tickers:
try:
print(f" Fetching {ticker}...", end=" ")
stock = yf.Ticker(ticker)
# yfinance.earnings_dates returns a DataFrame indexed by date
# Columns: "EPS Estimate" and "Reported EPS"
cal = stock.earnings_dates
if cal is None or cal.empty:
print("(no earnings data)")
continue
# Filter to future dates within our lookahead window
cal = cal[cal.index.date >= today]
cal = cal[cal.index.date <= cutoff]
# If nothing found, skip
if cal.empty:
print("(none in window)")
continue
# Iterate over matching earnings dates
for date, row in cal.iterrows():
days_away = (date.date() - today).days
upcoming.append({
"Ticker": ticker,
"Date": date.strftime("%Y-%m-%d"),
"Days Away": days_away,
"Alert": "⚠️ ALERT" if days_away <= alert_window else "",
"EPS Estimate": row.get("EPS Estimate", "N/A"),
})
print(f"({len(cal)} earnings)")
except Exception as e:
print(f"(error: {e})")
continue
# ──────────────────────────────────────────────────────────────────────────
# 3. PRINT RESULTS TO TERMINAL
# ──────────────────────────────────────────────────────────────────────────
if upcoming:
# Sort by days away (nearest first)
df = pd.DataFrame(upcoming).sort_values("Days Away")
print("\n📅 UPCOMING EARNINGS ON YOUR WATCHLIST")
print("="*65)
print(df.to_string(index=False))
print()
else:
print("✅ No earnings found in the next", lookahead, "days for your watchlist.")
print()
# ──────────────────────────────────────────────────────────────────────────
# 4. SEND EMAIL ALERT IF ANY REPORTS ARE WITHIN THE ALERT WINDOW
# ──────────────────────────────────────────────────────────────────────────
# Filter to only upcoming reports (within alert_days_before)
alerts = [u for u in upcoming if u["Days Away"] <= alert_window]
if alerts:
# Build email body
body = "OpenClaw Earnings Alert\n"
body += "="*50 + "\n\n"
body += "The following stocks on your watchlist report earnings soon:\n\n"
for a in alerts:
body += f" {a['Ticker']:6s} — {a['Date']} ({a['Days Away']:2d} days away)\n"
body += f" EPS Estimate: {a['EPS Estimate']}\n\n"
body += "---\n"
body += "Next steps: Review your position size, check IV rank,\n"
body += "pull the latest filing, and read the last earnings transcript.\n"
body += "This is your 3-day heads-up to prepare.\n\n"
body += "Not financial advice. Automated from OpenClaw.\n"
# NOTE: To actually send email, configure SMTP credentials below.
# This is a stub. In production, you'd use:
#
# import os
# smtp_server = "smtp.gmail.com"
# smtp_port = 587
# sender_email = os.getenv("OPENCLAW_EMAIL")
# sender_password = os.getenv("OPENCLAW_PASSWORD")
#
# msg = MIMEText(body)
# msg["Subject"] = f"OpenClaw: {len(alerts)} earnings report(s) coming up"
# msg["From"] = sender_email
# msg["To"] = alert_email
#
# server = smtplib.SMTP(smtp_server, smtp_port)
# server.starttls()
# server.login(sender_email, sender_password)
# server.send_message(msg)
# server.quit()
print("📧 ALERT WOULD BE SENT")
print("="*65)
print(f"To: {alert_email}")
print(f"Subject: OpenClaw: {len(alerts)} earnings report(s) coming up")
print()
print(body)
else:
print("✅ No alerts triggered (no earnings within", alert_window, "day window)")
print()
# ──────────────────────────────────────────────────────────────────────────
# 5. LOG RESULTS TO CSV (FOR HISTORICAL RECORD)
# ──────────────────────────────────────────────────────────────────────────
if upcoming:
import os
os.makedirs("data", exist_ok=True)
log_file = "data/earnings_calendar_log.csv"
# Add a timestamp column
df["Timestamp"] = datetime.today().strftime("%Y-%m-%d %H:%M:%S")
# Append to existing CSV or create new
if os.path.exists(log_file):
df.to_csv(log_file, mode='a', header=False, index=False)
else:
df.to_csv(log_file, index=False)
print("💾 LOGGED TO CSV")
print("="*65)
print(f"File: {log_file}")
print(f"Rows: {len(df)}")
print()
print("Done.\n")
Here's what the script prints to your terminal when it runs in the morning:
============================================================
OpenClaw Earnings Calendar Monitor
============================================================
Scanning 5 tickers for earnings in next 5 days
Alert threshold: 3 days before report
Date range: 2026-03-28 to 2026-04-02
Fetching AAPL... (1 earnings)
Fetching MSFT... (1 earnings)
Fetching NVDA... (none in window)
Fetching AMZN... (1 earnings)
Fetching GOOGL... (none in window)
📅 UPCOMING EARNINGS ON YOUR WATCHLIST
=================================================================
Ticker Date Days Away Alert EPS Estimate
MSFT 2026-03-29 1 ⚠️ ALERT 3.22
AAPL 2026-04-01 4 1.61
AMZN 2026-04-02 5 1.37
📧 ALERT WOULD BE SENT
=================================================================
To: you@example.com
Subject: OpenClaw: 1 earnings report(s) coming up
OpenClaw Earnings Alert
==================================================
The following stocks on your watchlist report earnings soon:
MSFT — 2026-03-29 ( 1 days away)
EPS Estimate: 3.22
---
Next steps: Review your position size, check IV rank,
pull the latest filing, and read the last earnings transcript.
This is your 3-day heads-up to prepare.
Not financial advice. Automated from OpenClaw.
💾 LOGGED TO CSV
=================================================================
File: data/earnings_calendar_log.csv
Rows: 3
Done.
Notice that MSFT is flagged with ⚠️ ALERT because it reports in 1 day (within the 3-day window). The script logs all three upcoming reports to CSV for your historical record. You can review this log later to see which earnings moved your portfolio, and which ones you handled well.
Right now, you run the script manually. But the whole point of OpenClaw is automation. You want the script to run every morning at 6:30 AM, while you're still asleep, and have the email waiting for you when you wake up. That's what cron does.
Cron is a job scheduler on Unix-like systems (Linux, macOS). You give it a command and a schedule, and it runs the command at that time, every day.
Open your terminal and edit your crontab:
crontab -e
Add this line at the bottom:
30 6 * * 1-5 cd /path/to/openclaw && python earnings_calendar.py
Save and exit. That's it. The script will now run every weekday at 6:30 AM.
Cron Syntax Breakdown:
30 6 * * 1-5
│ │ │ │ └─ Day of week (0-6, where 0=Sunday)
│ │ │ │ 1-5 = Monday through Friday
│ │ │ └─── Month (*, or 1-12)
│ │ └───── Day of month (*, or 1-31)
│ └─────── Hour (0-23, 24-hour format)
└────────── Minute (0-59)
So 30 6 * * 1-5 means: "At 6:30 AM (06:30), every day, every month, Monday through Friday."
The command cd /path/to/openclaw && python earnings_calendar.py means:
cd /path/to/openclaw — Navigate to your OpenClaw directory&& — Then (if successful)…python earnings_calendar.py — Run the Python script/path/to/openclaw with the actual path to your OpenClaw
directory. For example: /Users/john/openclaw or /home/trader/projects/openclaw.
To verify your crontab is set:
crontab -l
This will print your scheduled jobs. If you see your earnings_calendar line, you're good.
Getting an alert that MSFT reports in 3 days is only the start. That's when the real work begins. Here's how the Terminal series stacks together:
Each piece is automated. Each runs from the same earnings-config.yaml. Each can be triggered
by cron. By the end of the series, you have a full earnings surveillance system that runs automatically
every morning and gives you the intelligence you need to trade with confidence.
That's OpenClaw.