Author: Andrew Lee (with Claude)
Tracking: A-4770 (parent: settlement ignores market sessions) → A-4771 (calendar core) · A-4772 (trading-calendar proration) · A-4813 (daily maintenance window) · A-4774 (sessions in the rewards GUI) · A-4783 (coverage invariant alignment).
Date: 2026-08-31
Why now: MMLPv2 went live on prod today in
recording-only mode
(MMLP_SNAPSHOT_ENABLED/MMLP_BACKFILL_ENABLED
on; settle and payout off). Every gap in this SoW is a
settlement gap: nothing here loses recorded data, but none of
it may be wrong when the settler first runs, because the accrual ledger
is immutable — an epoch settled thin stays thin. This SoW is the gate on
MMLP_SETTLE_ENABLED.
Decision (2026-09-03), superseding work item 2's open question: design (b) is what shipped for the pool, on top of the maintenance-window constant from (a).
epoch_poolnow returns the full daily pool regardless of coverage;prorated_poolis deleted. The per-snapshot normalization indaily_weightsalready distributes that pool over exactly the scoring minutes, which is what the published terms describe (§3.3, Example 3) — so the pre-cap allocations of a thin day sum to the wholeP_daily, the 40% cap and themin($10, 1% × P_daily)threshold are computed against it, and a day with zero scoring snapshots pays nothing because it is never settled at all.The (a) half is kept and unchanged: the maintenance hour still leaves both the numerator and the denominator, so
covered/expectedstay meaningful as a coverage figure. They are now displayed and alerted on (#3889) and never priced in.MMLP_SETTLE_MIN_COVERAGE_PCTstays off by default and stays an operational hold rather than a payout rule: above the floor an epoch pays in full, below it the tick refuses to settle rather than paying less. The accepted trade-off is the one item 2 named — a genuine sampling outage concentrates a whole day's pool on whoever quoted through it, rather than shrinking the pool.
Decision (2026-09-01): AX trades every calendar day — prod shows fills on all seven weekdays for equity, FX, metals and energy perps — so class 1 below does not exist for us and the trading-calendar branch (#3872, A-4771/A-4772/ A-4774) is abandoned. Class 2 is fixed by design (a): the maintenance hour is a constant in
ax-mmlp2(MAINTENANCE_WINDOW,epoch.rs), and every minute in it leaves the settlement denominator. Sampling coverage (the admin cards and the A-4783 invariant) counts sampled cursor minutes, which the snapshotter writes through the window, so those already read 100% on a clean day and need no calendar.Addendum (2026-09-01): minutes scored inside the window are also ignored by the settler (the numerator,
scoring_rowsinsettle.rs), not only removed from the denominator. Demo's EP3 sandbox scores straight through the window (every 2026-08-31 demo epoch had 57–60 rows inside 20:00–21:00Z, socovered = 1440 > 1380and the epoch refused to settle), and prod's EP3 can return from maintenance early.coveredis the number of scoring minutes present outside the maintenance window; rows scored inside it are ignored and logged. Over-coverage therefore means duplicate rows only.
Settlement prices each epoch as
pool_daily × covered / expected
(prorated_pool,
rs/sdk-internal/mmlp2/src/settle.rs:291), where
pool_daily = P / calendar-month days, covered
= scored minutes (settle_epoch,
rs/accrual-engine/src/mmlp/settle.rs:70), and
expected = all wall-clock minutes of the epoch (1440,
DST-adjusted). Two classes of minutes can never score, and both silently
dilute payouts:
warn! on every epoch and would make the
coverage invariant (A-4783 / PR #3889) alert daily.Both diverge from the published program doc (AX_Liquidity_Program_v8262026, §3.3), which distributes each day's pool entirely across scoring snapshots — "non-scoring snapshots count toward neither the numerator nor the denominator" — with no wall-clock proration anywhere in the customer-facing math.
Branch tin/mmlp2-session-calendar (Tin,
current with main as of 2026-08-29; PR #3872 was
closed unreviewed and needs reopening or a successor):
MarketSession calendar to
ax-mmlp2
(rs/sdk-internal/mmlp2/src/session.rs, ~260 lines + tests)
driven by each symbol's funding_schedule timezone, trading
days, and holiday exceptions, plus an open/closed/holiday period
classification for the GUI.covered and
expected measure over the same calendar; a fully
off-calendar epoch settles to zero instead of stranding the settle
tick.trading_schedule — so it fixes class 1 but explicitly not
class 2: a trading day's expected minutes still include the maintenance
hour.expected: subtract
the known downtime window from a trading day's expected minutes. Keeps
coverage proration as outage protection; requires a source of truth for
the window (below).MMLP_SETTLE_MIN_COVERAGE_PCT outage
floor (measured against the trading calendar from item 1). Simpler,
matches the published terms exactly, and dissolves the
source-of-truth question: closed-market and downtime minutes
need no modelling because they no longer carry pool dollars. The
trade-off: a genuine sampling outage no longer shrinks the pool pro-rata
— it concentrates the pool on the minutes that did score — so the
coverage floor becomes the only guard against paying a full pool on a
thin sample, and its default (currently 0 = off) must be revisited.ax-mmlp2 beside the epoch
boundary. The epoch's 15:00-CT boundary is already a hard
constant (epoch.rs); the downtime window is the same class
of exchange-level fact and changes on the same timescale (a release).
Cheapest, testable, and point-in-time stable.epoch_start like the program row, or a
window change re-prices history.mmlp-epoch-coverage-complete must measure against the same
calendar (and downtime window, if modelled) as settlement, or it alerts
on every epoch. Same for the epoch-coverage displays in the admin
GUI.P_daily = P / n (calendar-month days) is itself the class-1
bug in prose; the fix changes the published formula. Changes must be
announced "prior to the start of the monthly reward period" (doc,
Appendix B) — coordinate the doc revision and MM announcement with BD so
code and terms flip together, ideally for the October period with
September settled under the corrected math from day one.Recording is live and unaffected; the settler's lookback re-scans past epochs, so nothing is lost while this lands. Two clocks are running:
MMLP_SETTLE_LOOKBACK_DAYS defaults to 30. If the
settler is not enabled by ~Oct 1, raise it to reach back to
2026-08-31T20:00Z so the first epochs still settle.Enable-settle checklist (all must hold before
MMLP_SETTLE_ENABLED=true on prod): items 1–2 merged and
released; item 4 merged (or #3889 held back);
MMLP_SETTLE_GRACE_SECS ≥ MMLP_BACKFILL_LOOKBACK_SECS (the
runbook claims this is validated at startup — it is not; validate it in
code as part of this work); a spot-check of one settled demo epoch
against the doc's worked examples.
Item 1 is review-and-land (the code exists). Item 2 is the design
decision plus a focused change to
settle.rs/prorated_pool either way; (b) is
less code than (a). Items 3–5 are small once 2 is decided. Item 6 is
coordination, not code. The critical path is the design decision and
review bandwidth on money math, not implementation volume.