Repository navigation
Reconcile time zones before the as-of join, so trade enrichment stops failing silently - #97
Merged
Merged
Conversation
… failing silently enrich_trades_with_signals joins trade times against signal timestamps, and the two sides reach it from different places: trade times come out of the engine carrying the bar clock without its zone, while a prediction panel is read from parquet with its zone intact. join_asof refuses a naive left key against an aware right key, and BacktestProfile wraps this call in a try that degrades to an empty frame with a UserWarning - so the reader does not get a traceback, they get a tear sheet quietly missing its ML trade-alignment charts. Found in case_studies/nasdaq100_microstructure/20_strategy_analysis.ipynb, whose committed output carries 'Prediction enrichment failed: datatypes of join keys don't match - entry_time: datetime[us] on left does not match timestamp: datetime[us, UTC] on right'. A naive column here is the same wall clock with the zone dropped, so it is localized and never shifted. Measured on that case study: reading the naive trade times as UTC matches 703 of 905 trades to an exact (symbol, instant) row in the prediction panel, and reading them as America/New_York matches 199; the residual 202 are trades entered between two prediction instants, which is what a backward as-of join is for. Shifting instead of localizing would still return rows, so getting this backwards would be silent rather than loud. Where both sides are aware and disagree the signals are converted, because two aware columns already name instants. The negative control neutralizes the helper with a monkeypatch and asserts the join still raises, so the other six tests are evidence that this path is load-bearing rather than assertions polars would satisfy on its own.
…source The timezone-alignment change moves the engine tree digest, and provenance.py stamps that digest into every retained record, so all 68 read stale against the working tree. Correctness is the kind of evidence that is genuinely re-derived rather than certified: it is cheap and tolerant of a contended machine. 66 pass, 2 unsupported, unchanged from the previous matrix. Compared leaf by leaf against the committed copy: 231,740 values, 201 differ, and every one of the 201 is a digest, a commit or a timestamp. No correctness value moved. Every record is stamped dirty:false, so this was produced from a clean tree.
…ify the timings against it The timezone-alignment change moves the engine tree digest, and the three retained evidence files answer to it in three different ways. Correctness was re-derived in 281370c. This is the other two. REAL_STRATEGY_RESULTS.json has a hard equality on the digest and no certification path, so it is rebuilt from runs: all 17 required pairs regenerated and compared, 17/17 pass, and the promoted file carries commit 281370c with dirty false. The first rebuild today was refused by its own gate - it ran while CORRECTNESS_RESULTS.json was still uncommitted, and provenance.py stamps dirty from git status taken at build time - so the driver that produced this one refuses on a dirty tree rather than discovering it eighteen minutes later. REAL_STRATEGY_PERFORMANCE.json is certified rather than re-timed. The timed region is the single line result = engine.run() at validation/real_strategy_runner.py:386, the declared timing_policy excludes result extraction and reporting, and enrich_trades_with_signals - the only caller of the new _align_join_time_zones - has no caller anywhere in src/ and runs after that call returns. So the added code cannot execute inside the measured interval. That mechanism is the stored reason; correctness parity alone would not be sufficient and certify_source's own docstring says so. generate_parity_claims.py --check passes against the regenerated claims.
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.
enrich_trades_with_signalsjoins trade times against signal timestamps, and the two sides reach it from different places: trade times come out of the engine carrying the bar clock without its zone, while a prediction panel is read from parquet with its zone intact.join_asofrefuses a naive left key against an aware right key.That would be loud, except
BacktestProfile.prediction_enriched_trades_dfwraps the call in atryand degrades to an empty frame with aUserWarning. So the reader does not get a traceback; they get a tear sheet quietly missing its ML trade-alignment charts.Found in the published
nasdaq100_microstructure/20_strategy_analysisnotebook, whose committed output carries:The direction matters more than the fix
A naive column here is the same wall clock with the zone dropped, so it is localized and never shifted. Measured on that case study: reading the naive trade times as UTC matches 703 of 905 trades to an exact
(symbol, instant)row in the prediction panel; reading them asAmerica/New_Yorkmatches 199. The residual 202 are trades entered between two prediction instants, which is what a backward as-of join is for.Getting this backwards would still return rows, so the wrong direction fails silently where the current bug fails loudly. Where both sides are aware and disagree, the signals are converted, because two aware columns already name instants.
Tests
Six cases plus a negative control. The control neutralizes
_align_join_time_zoneswith a monkeypatch and asserts the join still raises, so the other six are evidence this path is load-bearing today rather than assertions polars would satisfy on its own.tests/test_result.pyis 78 passed.