Never get caught by an FTMO maintenance window again.
A public instance runs at calendar.bogdantruta.com. Add the feed to your calendar in 30 seconds:
https://calendar.bogdantruta.com/feed.ics
Google Calendar: Other calendars → + → From URL → paste → appears on your phone automatically. (Apple/Outlook instructions and per-event-type filters on the live page.)
AutoFtmoCalendar watches FTMO's trading updates page, extracts scheduled platform maintenance and market closures with an LLM, and keeps a dedicated Google Calendar in sync — including updating or removing events when FTMO reschedules an announcement. Events come with popup reminders, so you get warned before the platform goes down, not after.
| Role | What you do | What you need |
|---|---|---|
| Subscriber (most people) | Paste a hosted feed URL into Google/Apple/Outlook calendar — done in 30 seconds | Nothing. No accounts, no API keys, no install |
| Host (one person per group) | Run one Docker container on any VPS; it scrapes, parses, and publishes the feed for everyone | An LLM API key. Google account optional |
Use the public instance above, or any feed someone hosts for your group:
- Google Calendar: Other calendars → + → From URL → paste
https://<host>/feed.ics - Apple Calendar: File → New Calendar Subscription… → paste the URL
- Outlook: Add calendar → Subscribe from web → paste the URL
Your calendar app re-polls the feed automatically; the feed itself carries a refresh hint matching the host's sync interval.
Feed-only mode needs no Google account at all — one LLM key and one container:
git clone https://github.com/Bogzx/ftmo-calendar && cd ftmo-calendar
mkdir data
printf '[calendar]\nenabled = false\n' > data/config.toml
cp .env.example .env # put your LLM_API_KEY in it
docker compose up -dThat's it. Your group subscribes to http://your-vps:8080/feed.ics, and
http://your-vps:8080/status is a shareable page with the next event and
subscribe instructions. /healthz reports ok, last_run, next_run, and
last_error for uptime monitors. A failing sync never takes the feed down —
the last good data keeps serving and you get a notification (see below).
For public hosting, put it behind a reverse proxy with HTTPS (Caddy/nginx) — the container itself serves plain HTTP. A complete VPS walkthrough (Cloudflare DNS, Docker, Caddy with automatic HTTPS) is in docs/DEPLOYMENT.md.
flowchart LR
A[FTMO updates page] -->|scrape all recent posts| B[Content-hash cache]
B -->|only new/changed posts| C[LLM extraction<br>Gemini or any OpenAI-compatible API]
C --> D[Validation<br>duration, dates, timezone]
D --> E[Reconcile<br>create / update / delete]
E --> F[(Google Calendar)]
- Trustworthy sync. Every created event carries a stable reconcile key. When an announcement changes, stale future events are removed and replaced; events that already happened are preserved as history. A lost state file does not cause duplicates — events are rediscovered in the calendar by key.
- Cheap. Post contents are hashed; unchanged posts cost zero LLM calls.
- Deterministic. Temperature-0 extraction with a strict JSON schema, a repair retry, model fallback, and sanity validation (end after start, duration caps, plausible date window, timezone taken from the announcement's stated offset).
- Fails loudly. A broken scraper or expired token exits non-zero with clear instructions — it never silently does nothing while you trust an empty calendar.
git clone https://github.com/Bogzx/ftmo-calendar
cd ftmo-calendar
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
cp .env.example .env # add your LLM API key
cp config.example.toml config.toml # optional: tweak settings
ftmo-calendar auth # one-time Google authorization (opens a browser)
ftmo-calendar run --dry-run # see what it would do
ftmo-calendar run # sync for realAny API key works — pick whichever provider you already have.
Gemini (default). Free tier available. Get a key at
aistudio.google.com/apikey and put it in .env
as LLM_API_KEY.
OpenRouter / OpenAI / Groq / Ollama / anything OpenAI-compatible:
# config.toml — example: DeepSeek via OpenRouter
[llm]
provider = "openai-compatible"
base_url = "https://openrouter.ai/api/v1" # or your provider's endpoint
models = ["deepseek/deepseek-v4-flash", "deepseek/deepseek-v4-pro"]Set LLM_API_KEY in .env to your OpenRouter key.
Any model on the platform works — deepseek/deepseek-chat, openai/gpt-5-mini,
google/gemini-2.5-flash, … The extractor is robust to model quirks: it strips
reasoning <think> blocks (DeepSeek R1 etc.), markdown fences, and prose around
the JSON, and retries with the validation error before falling back to the next
model in models.
- Follow Google's Calendar API quickstart
to create a Desktop app OAuth client; download
credentials.jsoninto the project directory. - Important — publish your app to Production. In Google Cloud console → APIs & Services → OAuth consent screen, click Publish app. Apps left in Testing status get refresh tokens that expire every 7 days, which is the usual cause of "it keeps asking me to log in". Publishing for personal use does not require verification (you'll just see an "unverified app" warning once).
- Run
ftmo-calendar auth. A browser opens; grant access. The token is saved totoken.jsonand auto-refreshes from then on. ftmo-calendar auth --checkshows token health at any time.
The calendar named in config.toml (Trading by default) is found or created
automatically.
No browser, no token, nothing ever expires:
- In Google Cloud console, create a service account and download its JSON key
as
service_account.jsonin the project directory. - In Google Calendar, create (or pick) a calendar → Settings and sharing → Share with specific people → add the service account's email with Make changes to events.
- Copy the calendar's Calendar ID (Settings → Integrate calendar) into config:
[calendar]
auth_mode = "service_account"
calendar_id = "xxxxxxxxxxxx@group.calendar.google.com"Get pinged when something changes — or when something breaks. Add a channel to
.env and it activates automatically:
DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..." # and/or:
TELEGRAM_BOT_TOKEN="123456:ABC..."
TELEGRAM_CHAT_ID="123456789"You'll receive messages like:
📅 FTMO Calendar updated
➕ ⚠️ FTMO Platform Maintenance — Sat 06 Jun 08:00–14:00 EEST
❌ ftmo-calendar run failed: OAuth token refresh failed (expired or revoked). ...
Set heartbeat_hours = 24 under [notify] in config.toml for a daily
"✅ alive" ping — so silence always means something is wrong, never that the
tool quietly died.
Set [ics] enabled = true (forced on automatically in feed-only and serve
modes) and every run writes ftmo-events.ics: stable UIDs per event, UTC
times, popup alarms matching reminders_minutes, a REFRESH-INTERVAL hint
for subscribers, and a source link in each event's description.
ftmo-calendar serve exposes it over HTTP alongside operations endpoints:
GET /feed.ics— the calendar feed (add it as "subscribe by URL")GET /status— shareable page: next event, sync health, subscribe how-toGET /healthz— JSON withok,last_run,next_run,last_error
Serve mode keeps the feed available from the moment it starts (last good data, even if the newest sync attempt fails) and notifies a given error only once — not every interval — until it changes or resolves.
Per-interest feeds: subscribers who only care about some event types can filter with a query parameter — each distinct URL behaves as its own calendar:
/feed.ics # everything
/feed.ics?types=crypto_closure # crypto closures only
/feed.ics?types=early_close,holiday_closure # any combination
| Type | Example event title |
|---|---|
maintenance |
|
crypto_closure |
🚫 Crypto Closed |
holiday_closure |
🏖️ Closed All Day — UK100.cash, HK50.cash, Equities I CFD |
early_close |
⏳ Early Close — US30.cash, US100.cash, US500.cash |
late_open |
🕗 Late Open — CORN.c, SOYBEAN.c, WHEAT.c |
symbol_event |
📌 Forced Action — FDX |
other |
ℹ️ FTMO Trading Update |
Event titles carry the affected symbols, extracted from the announcement. The landing page has checkboxes that build the URL for you; unknown types return a 400 listing the valid ones.
Serve mode keeps simple, self-hosted usage stats: page views, unique visitors
(an anonymous first-party cookie with a random id — no third parties, nothing
identifiable), feed pulls, and unique feed clients. Today's numbers appear in
the page footer; GET /stats returns JSON with a 30-day daily history
(persisted in stats.json).
Exit codes: 0 success, 1 runtime error, 2 configuration/auth error — so your
scheduler can alert you on failure.
Linux (cron), every 6 hours:
0 */6 * * * cd /opt/AutoFtmoCalendar && .venv/bin/ftmo-calendar run >> cron.log 2>&1Windows (Task Scheduler):
schtasks /Create /TN "FTMO Calendar" /SC HOURLY /MO 6 `
/TR "C:\path\to\AutoFtmoCalendar\.venv\Scripts\ftmo-calendar.exe --config C:\path\to\AutoFtmoCalendar\config.toml run"| Command | What it does |
|---|---|
ftmo-calendar run |
Scrape, extract, and sync the calendar (default command) |
ftmo-calendar run --dry-run |
Print planned creates/updates/deletes; touch nothing |
ftmo-calendar auth |
One-time interactive Google authorization (OAuth mode) |
ftmo-calendar auth --check |
Report credential/token health |
ftmo-calendar status |
Show tracked posts and the events created for them |
ftmo-calendar serve [--port N] |
Periodic sync + hosted ICS feed and status page |
--config PATH |
Use a config file other than ./config.toml |
-v |
Debug logging |
- "Token refresh failed" every week → your OAuth app is in Testing status.
Publish it to Production (see setup above), then
ftmo-calendar authonce more. Or switch to a service account and never think about tokens again. - "No trading-update posts found" → FTMO changed their page structure. Please open an issue.
- LLM quota errors → add more fallback
models, or pointprovider/base_urlat a different (or local) provider. - Wrong event times → FTMO states times in GMT+3; the extractor uses the offset
stated in each announcement. Check
[source] timezoneonly if announcements stop stating an offset.
pip install -e .[dev]
pytest # run tests
ruff check . # lint
mypy src # type-checkThe architecture and roadmap live in docs/superpowers/specs/.
This is a personal project and is not affiliated with FTMO.
