Python scraper for the Forex Factory economic calendar. Pulls monthly events into structured CSV/JSON, filters by currency and impact, converts timezones, and fires configurable pre-event alerts to Discord, Telegram, or any HTTP webhook.
- Scrapes the full FF economic calendar — currency, impact, time, event name, detail URL
- Filter by currency (USD, EUR, GBP, CAD, JPY, ...) and impact level (high/medium/low)
- Converts all event times to your local timezone
- Stores data in three tiers: last run, monthly canonical, and timestamped history
- Rule-based alerts: match events by currency, impact, keywords, exact name, or weekday
- Fires alerts N minutes before each matched event
- Delivers to Discord webhooks, Telegram bots, or any HTTPS endpoint
- Runs unattended via Docker Compose with built-in scheduling
- Optional Streamlit UI for browsing data and editing rules live
Docker (recommended)
cp .env.example .env
# add connector secrets to .env, enable connectors in config.yaml
./scripts/refresh_docker.sh refreshThat builds the image, starts the scraper scheduler and the alert checker, and prints their status. Common management commands:
./scripts/refresh_docker.sh restart # restart without rebuilding
./scripts/refresh_docker.sh down # stop and remove containers
./scripts/refresh_docker.sh status # show running containers
docker compose logs -f alerts # tail alert logs
docker compose logs -f scraper # tail scraper logsLocal
./scripts/setup_env.sh
cp .env.example .env
./scripts/run.sh scrapepython -m ff_calendar_toolkit.cli scrape # scrape now
python -m ff_calendar_toolkit.cli alerts-check # check alerts now
python -m ff_calendar_toolkit.cli test-notify # verify notification delivery
python -m ff_calendar_toolkit.cli view # open the Streamlit UIEach event row contains:
| Field | Example |
|---|---|
currency |
USD |
impact |
red |
date |
2025-01-15 |
time |
13:30 |
event |
Core CPI m/m |
detail |
https://www.forexfactory.com/... |
timezone |
Asia/Karachi |
scraped_at |
2025-01-14T08:00:00 |
Control which events are stored via config.yaml:
allowed_currencies:
- USD
- EUR
- GBP
- CAD
allowed_impacts:
- red # high impact
- orange # medium impact
- gray # low impact / holidaysOverride at runtime with CLI flags:
python -m ff_calendar_toolkit.cli scrape --currencies USD EUR --impacts red orangemonths:
- this # current month
- next # next monthOr pass a specific month:
python -m ff_calendar_toolkit.cli scrape --months 2025-03Multiple values are supported: --months this next 2025-06
output_format: both # csv | json | both
output_dir: newsThree storage tiers are written on every run:
news/last_run/ ← overwritten each run (easy to read latest)
news/monthly/ ← canonical file for each month (updated in place)
news/history/ ← timestamped snapshots, never overwritten
timezone: Asia/KarachiAll event times are converted from the Forex Factory source timezone to your configured timezone. Any tz database name works.
Set schedule_preset in config.yaml:
Scraper presets
| Preset | Cron |
|---|---|
weekly (default) |
0 0 * * 0 |
daily |
0 0 * * * |
monthly |
0 0 1 * * |
hourly |
0 * * * * |
Alert check presets
| Preset | Cron |
|---|---|
every_1_minute |
* * * * * |
every_5_minutes |
*/5 * * * * |
every_10_minutes |
*/10 * * * * |
hourly |
0 * * * * |
Use a custom cron expression instead of a preset via the CRON_SCHEDULE or ALERT_CRON_SCHEDULE env variables.
One YAML file per rule in rules/. A rule fires when all match conditions are met.
name: usd-cpi-alert
enabled: true
match:
currencies: [USD]
impacts: [red]
event_keywords: [CPI]
weekdays: [Wed]
trigger:
minutes_before: 10
deliver:
- discord_mainMatch fields (all optional, combined with AND logic):
| Field | Type | Example |
|---|---|---|
currencies |
list | [USD, EUR, GBP] |
impacts |
list | [red, orange] |
event_names |
list | exact event name match |
event_keywords |
list | substring match on event name |
weekdays |
list | [Mon, Tue, Wed, Thu, Fri] |
Multiple rules can target the same connector. State is tracked so an alert fires only once per event.
Enable connectors in config.yaml under alerts.connectors, then add secrets to .env.
Discord
A webhook is a special URL that lets this script post messages directly to a channel. No bot account or coding required.
How to create one:
- Open Discord and go to the channel where you want alerts (e.g.
#forex-alerts) - Click the gear icon next to the channel name to open Edit Channel
- In the left menu click Integrations
- Click Create Webhook (or New Webhook if one already exists)
- Give it a name like
Forex Factory Alertsand optionally upload an avatar - Click Copy Webhook URL — this is the URL you need
- Paste it into your
.envas shown below
Keep the webhook URL private — anyone with it can post to your channel.
- id: discord_main
type: discord
enabled: true
webhook_url_env: DISCORD_WEBHOOK_URLDISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/1234567890/xxxxVerify it works by running:
python -m ff_calendar_toolkit.cli test-notifyTelegram
Create a bot via @BotFather (/newbot). Get your chat ID by messaging the bot, then calling https://api.telegram.org/bot<TOKEN>/getUpdates. Group chat IDs are negative.
- id: telegram_main
type: telegram
enabled: true
bot_token_env: TELEGRAM_BOT_TOKEN
chat_id_env: TELEGRAM_CHAT_IDTELEGRAM_BOT_TOKEN=123456:ABC-DEF
TELEGRAM_CHAT_ID=-1001234567890Webhook
Sends a JSON payload with message, rule, event, and event_time fields. Supports an optional auth header.
- id: webhook_main
type: webhook
enabled: true
url_env: ALERT_WEBHOOK_URL
auth_header_name: Authorization
auth_header_env: ALERT_WEBHOOK_AUTHTesting your setup
After configuring any connector, run:
python -m ff_calendar_toolkit.cli test-notifyIt sends a real test message through every enabled connector and prints the result:
ok discord_main
ok telegram_main
fail webhook_main: Missing required secret environment variable 'ALERT_WEBHOOK_URL'
fail lines show the exact error — usually a missing .env variable or an invalid URL. Fix those before relying on live alerts.
Full config.yaml with all available keys:
months: [this] # this | next | YYYY-MM (list)
output_format: both # csv | json | both
output_dir: news
timezone: UTC # any tz database name
allowed_currencies: [USD, EUR, GBP, CAD]
allowed_impacts: [red, orange, gray]
headless: true
schedule_preset: weekly # weekly | daily | monthly | hourly
viewer_host: 127.0.0.1
viewer_port: 8501
alerts:
rules_dir: rules
state_dir: state/alerts
check_interval_minutes: 5
schedule_preset: every_5_minutes
retry_attempts: 3
retry_backoff_seconds: 1
message_prefix: "Forex Factory Alert"
connectors: []Config precedence: CLI flags > env vars > config.yaml > defaults
- Alerts not firing: check
enabled: trueon the rule and connector, and confirm the secret exists in.env - Run
test-notifyto verify delivery works before waiting for a real event - If the FF site changes and rows stop parsing, update selectors in
ff_calendar_toolkit/scraper.py
For educational use. Respect Forex Factory's terms of service.