Skip to content

Releases

v0.10.0 — Opt-in model panel, ICS conformance, live monitor

Choose a tag to compare

@Bogzx Bogzx released this 01 Oct 12:53

Four pull requests (#8–#11) and this version bump. Upgrading needs no
config, env or systemd change
: everything new is either opt-in or additive.

Added

  • Model panel ([[llm.panel]], opt-in, #8). Several independent models
    (any mix of providers, each with its own key variable) extract every changed
    post once each; an event is published only when panel_quorum of them (a
    majority by default) extracted the same window. A failed model abstains and
    counts against every event, and with fewer answers than the quorum the sync
    fails rather than reporting an empty announcement. A window too few models
    saw is not published but is reported like a rejected extraction (anomaly,
    notification, rejected_extractions on /healthz). Without a panel the
    single-model extractor is unchanged (same prompt, same voting)
  • /healthz extraction (#8): which models extract events and how many
    must agree, or the single-model consensus. prop-firm-calendar eval also
    scores each panel member alone, from the same calls
  • Live monitor (#10): scripts/check_live.py checks an instance from
    outside the way a subscriber's app would (healthz ok, a well-formed feed
    with events, the API answering). .github/workflows/live-monitor.yml runs
    it against the public instance every 30 minutes and opens, updates and
    closes a live-feed-down issue. The server leaves the monitor's
    User-Agent out of its usage statistics
  • schedule_note in source profiles (#11), shown per source on
    /healthz and, while the firm has nothing upcoming, on the status page.
    Blueberry Funded's explains why its calendar can be empty
  • Independent ICS conformance tests (#9): the feed is read back with
    icalendar in five zones across every 2026 DST change, each time resolved
    through the feed's own VTIMEZONE

Fixed

  • The hour repeated when clocks go back (#9). An instant in its second
    pass was written as TZID-local time, which RFC 5545 reads as the first
    pass: a London-zone window ending 01:30 GMT on 25 Oct ended an hour early.
    Such instants are now written in UTC. The public instance (fixed GMT+3)
    was not affected
  • Topstep titles name the holiday (#11), e.g. "⏳ Early Close —
    Thanksgiving" instead of 13 identical "⏳ Early Close" entries. Applies to
    rows extracted after Topstep next edits its article
  • tests/test_autodeploy.py runs on git < 2.28 (the production host has
    2.25) (#9)

Changed

  • Every feed event is TRANSP:TRANSPARENT (#9): a firm's maintenance window
    is not the subscriber's busy time. UIDs are unchanged
  • README: the pipeline diagram shows the real system, a subscriber FAQ, and a
    JSON API example taken from the public instance (#11)

v0.9.0 — Multi-firm, JSON API, evidence quotes

Choose a tag to compare

@Bogzx Bogzx released this 01 Oct 12:53

First version string since 0.8.1. It covers the two sections below it, which
were merged and deployed in August without a version bump, plus the fixes
here. Upgrading needs no config, env or systemd change (see "Renamed").

Fixed

  • Every event linked to FTMO. The feed passed [source] url — FTMO's
    index page — as the link for every event, so Topstep, Blueberry Funded and E8
    Markets rows all sent subscribers to ftmo.com. Each event now links to its own
    announcement (state records the post URL; older entries fall back to their
    firm's page and heal on the next sync)
  • Events beyond max_days_ahead were lost for good. They were rejected,
    and an unchanged post is never re-extracted, so Topstep's 2027-01-01 closure
    never appeared even once it was in range. They are now held in state
    (deferred) and published once within range, with no LLM call — also for
    posts that have since left the index page. After upgrading, each tracked post
    is re-extracted once (state written before 0.9 cannot say what it dropped)
  • Rejected extractions were invisible. Only a log line said an event had
    been dropped (e.g. an E8 row with no stated offset). A rejection that drops a
    real event now raises a per-firm anomaly (non-zero exit, notification, 503 on
    /healthz for that run), and each /healthz source reports
    rejected_extractions, events_upcoming and events_deferred
  • Auto-deploy waits for CI. scripts/autodeploy.sh rebuilt production from
    main whatever CI said; it now deploys a commit only after the CI workflow
    passed for it (scripts/ci_gate.py, stdlib only). The first deploy of this
    version is still made by the old script; opt out with
    AUTODEPLOY_REQUIRE_CI=0 — see docs/DEPLOYMENT.md

Added

  • Read-only JSON API in serve mode: /api/v1/events?firm=&type=&from=&to=,
    /api/v1/next (the current or next window per firm) and /api/v1/ (index).
    CORS-open, Cache-Control: public, max-age=300, ETag/304. Same state
    and de-duplication as the feed
  • Evidence spans: each event carries the announcement's own words for it,
    verified word-for-word against the scraped text (ICS/Google description, API).
    An unfound quote publishes the event as (unconfirmed);
    [events] require_evidence = true rejects it
  • prop-firm-calendar eval: the production prompt over every golden fixture
    N times against a real model, diffed against the expected events; also a
    live_llm pytest marker and a manual/weekly workflow that needs the
    LLM_API_KEY secret
  • Secret scanning: a gitleaks CI job over each push/PR's new commits, an
    optional pre-commit config, and SECURITY.md

Changed

  • One calendar entry per window across posts (Google sync): a follow-up
    post re-announcing a window shares the existing entry, which is deleted only
    when the last post announcing it withdraws it. Duplicates created by earlier
    versions are merged on the first run after upgrading

Renamed

  • Package ftmo_calendar → prop_firm_calendar, distribution
    ftmo-calendar → prop-firm-calendar, command prop-firm-calendar
  • Kept as aliases, so nothing deployed breaks: the ftmo-calendar command
    (same entry point), and import ftmo_calendar[.x], which resolves to the
    very same module objects and emits a DeprecationWarning
  • Deliberately not renamed: ICS UIDs (…@ftmo-calendar — changing them
    would duplicate every subscriber's events), the Google aftc_key reconcile
    property, the compose service ftmo-calendar, the ftmo-autodeploy systemd
    unit, the ftmo container user, and the ftmo-events.ics default path
  • PRODID is now -//Bogzx//prop-firm-calendar//EN and descriptions credit
    prop-firm-calendar; those two strings are the only bytes of the feed the
    rename moved (pinned in tests/test_ftmo_compatibility.py)
  • Change-notification heading reads "Trading calendar updated"

v0.8.0 — Granular events + stats

Choose a tag to compare

@Bogzx Bogzx released this 09 Jun 21:26

Granular events (grounded in FTMO history) + self-hosted stats.

Seven event types replace the coarse four: maintenance, crypto_closure, holiday_closure, early_close, late_open, symbol_event, other. Derived from sampling FTMO's announcement archive. Verified live on DeepSeek: the Memorial Day post extracts as 8 correctly-typed events:

maintenance      Sat 17:00-21:00  Platform Maintenance - MT4, MT5, cTrader, DXtrade
holiday_closure  Mon all day      Closed All Day - UK100, HK50, Equities I, Agriculture
early_close      Mon 20:00        Early Close - JP225, US30, US100, US500, US2000
early_close      Mon 20:30        Early Close - UKOIL
early_close      Mon 21:30        Early Close - Metals, USOIL, HEATOIL, NATGAS
early_close      Mon 23:00        Early Close - GER40
late_open        Tue ->16:35      Late Open - CORN, SOYBEAN, WHEAT
late_open        Tue ->03:05      Late Open - UK100
  • Event titles carry affected symbols via a new affected extraction field; consensus merges partial lists
  • Seven filter chips on the landing page; old state files stay compatible
  • Anonymous usage stats in serve mode: page views, unique visitors (random-id first-party cookie, no third parties), feed pulls, unique feed clients. Footer summary + GET /stats JSON with 30-day history.

v0.7.0 — Per-interest feeds

Choose a tag to compare

@Bogzx Bogzx released this 09 Jun 21:13

Subscribe to only what you trade.

  • Type-filtered feeds: /feed.ics?types=crypto_closure (any combination of maintenance, crypto_closure, holiday_hours, other). Each distinct URL behaves as its own calendar in Google/Apple/Outlook.
  • Landing page filter chips: untick event types and the subscribe URL + webcal link rebuild live.
  • Filtered calendars are named after their filter; unknown types return a 400 listing valid ones.
  • State v3: tracked events carry their event type (older state files load transparently).

Verified live: filtered endpoint returns only matching events; UI chips rebuild the URL in a real browser.

v0.6.0 — Landing page

Choose a tag to compare

@Bogzx Bogzx released this 09 Jun 20:55

A real website for hosted deployments (e.g. calendar.yourdomain.com).

The served / page is now a designed, self-contained landing page - trading-terminal aesthetic, zero external requests:

  • Live countdown to the next trading interruption (switches to "ends in" while one is in progress, "ALL CLEAR" when nothing is scheduled)
  • Every time rendered in the visitor's own timezone
  • One-click feed subscribe: copy-URL button + webcal:// open, with per-app instructions for Google/Apple/Outlook
  • Upcoming / in-progress (LIVE) / past schedule, sync health in the footer
  • Works without JavaScript, responsive down to phones, ~16 KB single response

landing

v0.5.0 — Deterministic extraction

Choose a tag to compare

@Bogzx Bogzx released this 09 Jun 20:39

Always reports the same thing — verified live on DeepSeek via OpenRouter.

  • Consensus voting ([llm] consensus_runs, default 3): each changed post is extracted N times, majority events kept. Hosted APIs are not deterministic at temperature 0 (OpenRouter routes one model id across providers); voting makes the result stable. Live proof: 4 consecutive consensus extractions of a real FTMO post -> identical event sets, identical event keys.
  • Prompt rule excluding Client Area/IT maintenance — the one borderline case that flickered between runs
  • Consensus identity merges timezone-attribution variants (explicit offset wins) so the vote never splits on equivalent events
  • seed hint on OpenAI-compatible calls

v0.4.0 — Feed-first hosting

Choose a tag to compare

@Bogzx Bogzx released this 09 Jun 20:13

Run a public feed for a whole trading group with just an LLM key.

  • Feed-only mode ([calendar] enabled = false): no Google account needed anywhere — one host runs a container, everyone else subscribes to /feed.ics
  • --dry-run works before any Google setup
  • Status page is now a shareable landing page: next event, sync health, per-app subscribe instructions
  • /healthz reports next_run for overdue-sync detection; Docker HEALTHCHECK included
  • ICS feed: refresh hints (REFRESH-INTERVAL) and per-event source links
  • DeepSeek/OpenRouter hardening: <think> blocks and prose-wrapped JSON handled

Fixed: UTF-8 BOM configs (Notepad/PowerShell) parse correctly; feed serves last-good data immediately after restart; identical persistent errors notify once, not every interval.

v0.3.0 — Notifications, ICS feed, hosted mode, Docker

Choose a tag to compare

@Bogzx Bogzx released this 09 Jun 20:12

Reach & visibility.

  • Discord webhook and Telegram bot notifications: new/removed events, run failures, optional heartbeat
  • ICS feed export: subscribe from any calendar app with zero OAuth setup
  • ftmo-calendar serve: periodic sync + HTTP server exposing /feed.ics, /status, /healthz
  • Docker support: docker compose up -d runs serve mode with a ./data volume
  • State v2: tracked events carry display data; v1 state files load transparently

v0.2.0 — Trustworthy sync core

Choose a tag to compare

@Bogzx Bogzx released this 09 Jun 20:12

Complete rewrite as a professional package.

  • ftmo-calendar CLI: run (with --dry-run), auth (with --check), status
  • Provider-agnostic LLM parsing: Gemini or any OpenAI-compatible endpoint
  • Service-account auth mode: no browser, no token expiry
  • Reconcile sync: rescheduled/withdrawn announcements update or remove their calendar events
  • Multi-post scraping matching FTMO's redesigned site
  • Content-hash caching: unchanged posts cost zero LLM calls
  • Event reminders, validation, timezone normalization
  • Tests, ruff, mypy, GitHub Actions CI, TOML config

Fixed: expired OAuth tokens no longer hang cron with a browser prompt; documented the 7-day Testing-status token expiry.