fix(orchestrate): the firing-rate channel was dead through the seam - #17
Merged
Conversation
check_decay built its TradeLog from bare floats, TradeLog.from_arrays(trade_r),
while crucible derives the live firing rate from the log's entry_date. So
live_trades_per_year and frequency_ratio came back None on every cycle, for every
book, however carefully the baseline's own rate had been frozen at promotion.
Two of the monitor's three channels ran. The third reported itself off, in a
reason line that read as a fact about the book ("live log carries no entry_date")
when it was a fact about the seam. npf had just gone to some trouble to compute a
trades_per_year explicitly and freeze it into every baseline, and nothing could
ever consume it.
It is the channel that matters most on its own, because neither of the others can
see the failure it covers: a signal that stops firing while the trades it still
takes keep their per-trade edge. Expectancy is unchanged, so the CUSUM stays quiet
and the rolling window reads full size, and annual R falls anyway because the
opportunity set shrank.
check_decay now takes trade_dates, TriggerContext carries them, run_cycle forwards
them, EdgeDecayTrigger passes them through, and the CLI sources them from an
optional trade_dates_since(since, params) on the book.
Three choices worth recording:
* TriggerContext refuses a trade_dates/trade_r length mismatch. Misaligned dates
do not fail downstream, they date the wrong trades and yield a rate that is
wrong in a direction nothing else in the verdict would reveal.
* The dates are optional where trade_r is not, and their absence warns rather
than refusing. A book that cannot date its trades is still worth monitoring on
expectancy. What is not acceptable is the previous state, where supplying R
alone turned the channel off with no way to tell that apart from a book that
genuinely has no dates.
* No crucible change was needed. TradeLog.from_arrays has accepted entry_date all
along, so this was the seam throwing the dates away rather than a gap in the
judge.
One test premise of mine was wrong and is recorded rather than quietly fixed: I
assumed a live book at exactly baseline expectancy reads HOLDING, so the frequency
test could isolate its channel. At n=300, sigma=1.0, the 200-trade rolling window
has SE 0.071 against a 0.10 soft line, so it dips below on roughly 1 seed in 7 from
noise alone (2 of the first 12). The test now uses a seed sitting at 192% of
baseline, and the isolation is a property of the fixture rather than an assumption.
399 tests pass, ruff clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
check_decaybuilt itsTradeLogfrom bare floats,TradeLog.from_arrays(trade_r), while crucible derives the live firing rate from the log'sentry_date. Solive_trades_per_yearandfrequency_ratiocame backNoneon every cycle, for every book, however carefully the baseline's own rate had been frozen at promotion.Two of the monitor's three channels ran. The third reported itself off, in a reason line that read as a fact about the book ("live log carries no entry_date") when it was a fact about the seam. npf had just gone to some trouble to compute a
trades_per_yearexplicitly and freeze it into every baseline, and nothing could ever consume it.It is the channel that matters most on its own, because neither of the others can see the failure it covers: a signal that stops firing while the trades it still takes keep their per-trade edge. Expectancy is unchanged, so the CUSUM stays quiet and the rolling window reads full size, and annual R falls anyway because the opportunity set shrank.
What changed
check_decaytakestrade_dates,TriggerContextcarries them,run_cycleforwards them,EdgeDecayTriggerpasses them through, and the CLI sources them from an optionaltrade_dates_since(since, params)on the book.Three choices worth recording:
TriggerContextrefuses atrade_dates/trade_rlength mismatch. Misaligned dates do not fail downstream, they date the wrong trades and yield a rate that is wrong in a direction nothing else in the verdict would reveal.trade_ris not, and their absence warns rather than refusing. A book that cannot date its trades is still worth monitoring on expectancy. What is not acceptable is the previous state, where supplying R alone turned the channel off with no way to tell that apart from a book that genuinely has no dates.TradeLog.from_arrayshas acceptedentry_dateall along, so this was the seam throwing the dates away rather than a gap in the judge.A wrong premise, recorded
I assumed a live book at exactly baseline expectancy reads HOLDING, so the frequency test could isolate its channel. At n=300, sigma=1.0, the 200-trade rolling window has SE 0.071 against a 0.10 soft line, so it dips below on roughly 1 seed in 7 from noise alone (2 of the first 12). The test now uses a seed sitting at 192% of baseline, so the isolation is a property of the fixture rather than an assumption.
Verification
399 tests pass, ruff clean. Eight new tests, including the direct regression: undated reports the channel off, dated reports a ratio, and a book whose rate collapses to 20% while per-trade expectancy is untouched goes HOLDING to SLIPPING only once dated.
Merge this before npf#184, which adds the
trade_dates_sincethis consumes. npf CI checks out crucible-stack's default branch.🤖 Generated with Claude Code