Repository navigation
fix: validate candles at ingestion - #106
Merged
Merged
Conversation
Drop non-finite OHLC, keep the last row for each time, expand high/low to cover the body, clamp negative volume, and warn once when a timestamp looks like milliseconds. Co-authored-by: Joe Blau <joeblau@users.noreply.github.com>
This was referenced Nov 10, 2026
Merged
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.
Closes #88
Closes #89
Closes #90
Closes #91
Closes #92
Behavior
Validation runs only when data enters the chart:
setData,appendData,prependData, constructorconfig.data, andupdateConfig({ data }). The datafeed'snormalizeCandlesuses the same rules. Nothing in the frame loop changed.time,open,high,low, orcloseis dropped.appendDataof a bad candle is a no-op: no load event and no redraw. ANaNclose can no longer poison every later SMA value.DataStore.prepend. Rows that are not strictly earlier than the candle already stored are dropped, so the existing series wins ties. A followingappendDataat that time updates the one bar.highandlowexpand to coveropenandclose. The caller's objects are not mutated. Autoscale then includes the body and the last-price line.0. A missing volume stays missing on the core path. The datafeed still writes a numeric volume, and now clamps negatives too. MFI stays inside 0–100.1e11(year 5138 in seconds, 1973 in milliseconds) logs oneconsole.warnoutsideNODE_ENV=production. The value is kept. Nothing is divided by 1000.Tests
1905 tests passed.
scripts/check-coverage.mjsreports 100% line, branch, and function coverage.bun run typecheck:tscandbun run check:invariantspassed.Benchmarks
node bench/suite/run.mjs --samples=7 --sizes=100000, Node 24, median ms/op.The first cut scanned and copied, so sorted
setDatawent from 0.8122 ms to 0.9959 ms. #108 stores a valid array by reference after one pass:Valid
setDatais faster than before the validation (0.6442 vs 1.0430 in the same back-to-back run; minimums 0.7880 ms and 0.6145 ms). Overhead on valid data is under 5%.