kaldamus entity reference · EventHistory
Every full run of update_events appends one probability point per traded event, per UTC day, then compacts the series back down — so a file that used to grow unbounded now has a ceiling by design, not by cleanup.
| Sub-model | Captured | Holds |
|---|---|---|
| HistoryPoint | one per UTC day (key: date) | probability at capture · volume_24h · open_interest |
| HistorySignals | recomputed every advance | delta_1d / delta_7d · volatility_30d · prob_min/max · days_in_band · volume/oi_trend |
| SettlementSnapshot | written once, on first terminal advance | final_probability (first-write-wins) · resolved_outcome · settled_at · surprise_7d |
Row-level fields around the three blocks: id (= event_id) · first_seen · running prob_min/prob_max · points[] · updated_at (derived from as_of, not the wall clock).
EventHistory row (id = event_id) is a thin wrapper around three purpose-built sub-models: a raw daily capture, a derived signal block recomputed from it, and a settlement record frozen once.events.json is authoritative for those and never deletes, so copying them here would only be drift-prone.updated_at derives from the capture's as_of, not the wall clock.
flowchart TD
classDef srcNode fill:#16233b,stroke:#5a8fd4,stroke-width:1.5px,color:#dfe7f5;
classDef signalNode fill:#241d0c,stroke:#c9a84c,stroke-width:1.5px,color:#f3e6bd;
classDef decisionNode fill:#12151f,stroke:#5a6178,stroke-width:1px,color:#c9cbd6;
classDef deadEnd fill:#12151a,stroke:#3a4058,stroke-width:1px,color:#7a8296,stroke-dasharray:3 3;
A["Event row
(full run only)"]:::srcNode --> B{"row exists?"}:::decisionNode
B -- no --> C{"terminal, or
never traded?"}:::decisionNode
C -- yes --> Z1["no row created"]:::deadEnd
C -- no --> D["new EventHistory row"]:::srcNode
B -- yes --> D
D --> E{"open AND
probability set?"}:::decisionNode
E -- yes --> F["capture today's point
(replace if same date)
widen prob_min / prob_max"]:::srcNode
E -- no --> G["no new point"]:::deadEnd
F --> H{"status terminal?"}:::decisionNode
G --> H
H -- yes --> J["freeze settlement once"]:::signalNode
H -- no --> M["compact"]:::decisionNode
J --> M
M --> N{"terminal AND
30d past last point?"}:::decisionNode
N -- yes --> O["weekly-only, whole series"]:::signalNode
N -- no --> P["90d daily window kept;
older → 1 / ISO-week"]:::signalNode
O --> Q{"over 400 points?"}:::decisionNode
P --> Q
Q -- yes --> R["drop oldest to cap"]:::deadEnd
Q -- no --> S["compute_signals()"]:::signalNode
R --> S
S --> T["events_history.json
+ Event.history_signals"]:::signalNode
One call to advance(row, event, as_of, knobs) per event. Blue = raw capture off the live Event row; gold = derived/frozen output; dashed = a path that leaves the row unchanged.
update_events composes four pure functions over data already in memory — no model call, no HTTP request — guarded to full runs, exactly like lifecycle reconciliation right before it.Event itself — never predictions.json directly — preserving the one-upstream-per-stage invariant.Event.probability / signals| Field | Written by | Changes again when… |
|---|---|---|
| points (today's entry) | Stage 3 capture, open + traded only | replaced on a same-day rerun; a missed day just leaves a gap |
| prob_min / prob_max | every capture, before compaction | only ever widen — the cap can drop the point that set an extreme without resetting it |
| signals | compute_signals() | every advance with non-empty points, new point or not |
| settlement.final_probability | first terminal advance | never again — even if the event later moves closed → settled |
update_events run touches a row's content, and within it nothing is a blanket "overwrite every run" — each field has its own regime.--id run → history untouched| Scenario | What happens | Kept? |
|---|---|---|
| Open, traded | advances daily; points accumulate then compact | yes — grows toward steady state |
| Goes terminal | settlement frozen once; captures stop; series rolls off to weekly | yes — this is the calibration record |
| Orphaned, settled | event gone from events.json, but a settlement exists | yes, forever — exempted from the orphan check |
| Orphaned, unsettled | event gone, no settlement — no future points possible | no — swept by --purge-orphan-history |
events.json never does — with exactly one deliberate exception, added after production evidence.--dry-run — wired into make purge.make update-all when they remember to.delta_1d's tolerance is ≤2 days, so a run after a longer gap reports null — an honest absence, not a flat market.delta_1d = nulldelta_7d's longer, no-lower-bound window is far more resilient to the same gaps.