The evidence
The recentre schedule
One recentre per NYSE session, at 02:00 UTC, when the stock is shut, the feed has settled and the next open is hours away.
Why 02:00 UTC
A recentre is a trade against the pool. Doing it while the stock is trading means the band moves in the middle of the flow the vault is trying to earn from, and the swap that rebalances the legs pays the spread at its widest. Doing it after the close and before the next open avoids both.
02:00 UTC is 22:00 in New York during daylight saving and 21:00 during standard time. The session has ended at 16:00 ET, the Chainlink answer has settled, and the next session is more than eleven hours away. The keeper does not need a clock change: it computes the next slot from the exchange calendar and compares UTC timestamps.
What the hour is worth
The hour was chosen on one half of a 30-day window and read on the other. All 24 UTC hours were scored on the calibration half, the best hour was picked without looking at the second half, and that single hour was then measured out of sample. The comparison schedule is a recentre at 09:35 ET on trading days only, with slots the vault could not execute dropped.
| Token | Best calibration hour | Held-out kept APR | NYSE open, held out | Gain |
|---|---|---|---|---|
| NVDA | 02:00 UTC | +3.80% | +1.32% | +2.48 points |
| GOOGL | 03:00 UTC | +6.96% | +6.58% | +0.39 points |
NVDA is the reason for the change. Under the conservative share mode the old schedule put its weaker half at -0.42% a year, which is below break-even and caps each deposit at $25,000; at 02:00 UTC the same half keeps 1.81% and the vault opens with no deposit cap. GOOGL stays reduced either way, because its weaker half is negative under both schedules.
What changed in the replay
Re-running the same 30 days under the new schedule moves every token in the same direction. The figures below are full-window, in both engine share modes, with the gate taken from the weaker half in the conservative mode.
| Token | Old, conservative | New, conservative | Old, current engine | New, current engine | Gate |
|---|---|---|---|---|---|
| NVDA | +5.04% | +6.94% | +6.33% | +8.26% | reduced to open |
| GOOGL | -2.57% | -1.80% | +1.85% | +2.65% | reduced, unchanged |
| CRCL | -78.49% | -76.23% | -51.54% | -48.43% | closed, unchanged |
| SPCX | -25.07% | -20.64% | -14.60% | -10.14% | closed, unchanged |
The count changes because the schedule changes, not because slots were dropped: 30 calendar-day checkpoints, weekends included, became 22 after-session slots. No slot at 02:00 was skipped for staleness in this window, since the feed is roughly six hours old at that hour.
The session calendar
The keeper carries a rule-based NYSE calendar rather than a downloaded file. Given a year it derives weekends, the ten full-day holidays with their observed shifts, and the early closes.
| Case | Rule | Effect on the keeper |
|---|---|---|
| Weekend | Saturday and Sunday | No scheduled slot and no out-of-range recentre. |
| Holiday on a Saturday | Observed on the Friday before | That Friday is closed. A Saturday January 1 is the exception: the NYSE does not close the prior Friday. |
| Holiday on a Sunday | Observed on the Monday after | That Monday is closed. |
| Good Friday | Two days before Easter Sunday, from the anonymous Gregorian algorithm | Closed. |
| Early close | 13:00 ET | The session ends earlier; the after-session slot is unchanged. |
Two triggers
Each pass of the 60 second loop reads the pool's current tick, the Chainlink answer and the vault's stored range, then asks whether either trigger fires. They are separate on purpose: one keeps the schedule, the other handles a stock that has run away from the band.
| Trigger | Condition | Window |
|---|---|---|
| scheduled | The 02:00 UTC slot for the last completed NYSE session has arrived and nothing has been sent since that slot. | Six hours from 02:00 UTC. |
| out_of_range | The tick is outside the stored range and has been for 30 minutes, at least 2 hours after the last rebalance. | NYSE trading days only. |
The out-of-range path exists because a 5% band can be left behind by an overnight gap. It stays inside NYSE hours so the keeper is trading while the stock is liquid, and its two hour gap stops a band edge from producing a burst of rebalances.
Guards before a send
A due rebalance is still only a candidate. Two guards run first, and either one stops the send; the keeper retries each minute until the window closes.
| Guard | Limit | On failure |
|---|---|---|
| Feed age | At most 87,000 seconds | Blocked. The keeper retries each minute until the window closes. |
| Pool against feed | At most 200 bps | Blocked. A pool priced 2% away from Chainlink is not a market to rebalance into. |
The contract runs its own gate on every send, and it is the binding one: it measures the 10 minute TWAP and the spot price against Chainlink at maxDeviationBps, 100 bps by default, so a pool that is 150 bps away from the feed passes the keeper's guard and still reverts with TwapDeviates or SpotDeviates. The keeper's 200 bps is the outer check, not a replacement.
After the guards pass, the keeper simulates the exact rebalance() call with eth_call against the current block. Only a successful simulation is signed. Every pass appends one JSON line, so a blocked day is visible in the log rather than silent.
start one line at boot: mode, chain, tickers, guards
tick action: none | blocked_by_guard | skipped | dry_run_would_send
simulate_failed action: not_sent, with the first line of the revert
simulate_ok
sent hash
reverted hash, block
rebalanced hash, block, gas_usedEach line also carries the ticker, block, pool price, Chainlink price and age, deviation in bps, the guard verdict and reasons, the current band, the trigger and the next scheduled slot. A guard failure is a line with blocked_by_guard and the reason that fired.
The backstop
The keeper is a convenience, not a single point of failure. rebalance() is open to anyone once 26 hours have passed since the last one, and the vault's ownminRebalanceInterval is six hours. A vault keeps earning at its old band if the keeper stops, and anyone can move it.
Sources
keeper/src/calendar.tsThe rule-based NYSE calendar, the 02:00 UTC slot and the six hour window.
keeper/src/keeper.tsThe 60 second loop, both triggers, the guards, the simulation and the log lines.
keeper/src/config.tsFeed age and deviation limits, out-of-range minutes and the minimum gap.
keeper/strategist/RECENTRE-HOUR.mdThe 24 hour sweep, the held-out gains, the staleness bound and the 2026-10-19 decision.
keeper/replay/SCHEDULE-CHANGE.mdOld against new schedule across the full window, in both engine share modes.