@363045841yyt/klinechart-core 0.8.11-alpha.0 → 0.8.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/alerts/createAlertController.d.ts +30 -0
- package/dist/alerts/createAlertController.d.ts.map +1 -0
- package/dist/alerts/createAlertController.js +261 -0
- package/dist/alerts/createAlertController.js.map +1 -0
- package/dist/alerts/index.d.ts +5 -0
- package/dist/alerts/index.d.ts.map +1 -0
- package/dist/alerts/index.js +4 -0
- package/dist/alerts/index.js.map +1 -0
- package/dist/alerts/predicates.d.ts +28 -0
- package/dist/alerts/predicates.d.ts.map +1 -0
- package/dist/alerts/predicates.js +211 -0
- package/dist/alerts/predicates.js.map +1 -0
- package/dist/alerts/rollingVolume.d.ts +15 -0
- package/dist/alerts/rollingVolume.d.ts.map +1 -0
- package/dist/alerts/rollingVolume.js +42 -0
- package/dist/alerts/rollingVolume.js.map +1 -0
- package/dist/alerts/ruleSchema.d.ts +42 -0
- package/dist/alerts/ruleSchema.d.ts.map +1 -0
- package/dist/alerts/ruleSchema.js +240 -0
- package/dist/alerts/ruleSchema.js.map +1 -0
- package/dist/alerts/types.d.ts +169 -0
- package/dist/alerts/types.d.ts.map +1 -0
- package/dist/alerts/types.js +24 -0
- package/dist/alerts/types.js.map +1 -0
- package/dist/chartTypes/heikinAshi.d.ts +49 -0
- package/dist/chartTypes/heikinAshi.d.ts.map +1 -0
- package/dist/chartTypes/heikinAshi.js +94 -0
- package/dist/chartTypes/heikinAshi.js.map +1 -0
- package/dist/chartTypes/index.d.ts +6 -0
- package/dist/chartTypes/index.d.ts.map +1 -0
- package/dist/chartTypes/index.js +5 -0
- package/dist/chartTypes/index.js.map +1 -0
- package/dist/chartTypes/pointAndFigure.d.ts +83 -0
- package/dist/chartTypes/pointAndFigure.d.ts.map +1 -0
- package/dist/chartTypes/pointAndFigure.js +159 -0
- package/dist/chartTypes/pointAndFigure.js.map +1 -0
- package/dist/chartTypes/rangeBars.d.ts +61 -0
- package/dist/chartTypes/rangeBars.d.ts.map +1 -0
- package/dist/chartTypes/rangeBars.js +181 -0
- package/dist/chartTypes/rangeBars.js.map +1 -0
- package/dist/chartTypes/renko.d.ts +94 -0
- package/dist/chartTypes/renko.d.ts.map +1 -0
- package/dist/chartTypes/renko.js +207 -0
- package/dist/chartTypes/renko.js.map +1 -0
- package/dist/chartTypes/types.d.ts +97 -0
- package/dist/chartTypes/types.d.ts.map +1 -0
- package/dist/chartTypes/types.js +39 -0
- package/dist/chartTypes/types.js.map +1 -0
- package/dist/components/anchoredVwap/computeAnchoredVwap.d.ts +76 -0
- package/dist/components/anchoredVwap/computeAnchoredVwap.d.ts.map +1 -0
- package/dist/components/anchoredVwap/computeAnchoredVwap.js +155 -0
- package/dist/components/anchoredVwap/computeAnchoredVwap.js.map +1 -0
- package/dist/components/anchoredVwap/createAnchoredVwapController.d.ts +28 -0
- package/dist/components/anchoredVwap/createAnchoredVwapController.d.ts.map +1 -0
- package/dist/components/anchoredVwap/createAnchoredVwapController.js +282 -0
- package/dist/components/anchoredVwap/createAnchoredVwapController.js.map +1 -0
- package/dist/components/anchoredVwap/index.d.ts +11 -0
- package/dist/components/anchoredVwap/index.d.ts.map +1 -0
- package/dist/components/anchoredVwap/index.js +10 -0
- package/dist/components/anchoredVwap/index.js.map +1 -0
- package/dist/components/anchoredVwap/types.d.ts +171 -0
- package/dist/components/anchoredVwap/types.d.ts.map +1 -0
- package/dist/components/anchoredVwap/types.js +25 -0
- package/dist/components/anchoredVwap/types.js.map +1 -0
- package/dist/components/crosshairSync/createCrosshairSync.d.ts +94 -0
- package/dist/components/crosshairSync/createCrosshairSync.d.ts.map +1 -0
- package/dist/components/crosshairSync/createCrosshairSync.js +118 -0
- package/dist/components/crosshairSync/createCrosshairSync.js.map +1 -0
- package/dist/components/crosshairSync/index.d.ts +2 -0
- package/dist/components/crosshairSync/index.d.ts.map +1 -0
- package/dist/components/crosshairSync/index.js +2 -0
- package/dist/components/crosshairSync/index.js.map +1 -0
- package/dist/components/footprint/aggressor.d.ts +89 -0
- package/dist/components/footprint/aggressor.d.ts.map +1 -0
- package/dist/components/footprint/aggressor.js +106 -0
- package/dist/components/footprint/aggressor.js.map +1 -0
- package/dist/components/footprint/createFootprintController.d.ts +29 -0
- package/dist/components/footprint/createFootprintController.d.ts.map +1 -0
- package/dist/components/footprint/createFootprintController.js +264 -0
- package/dist/components/footprint/createFootprintController.js.map +1 -0
- package/dist/components/footprint/index.d.ts +7 -0
- package/dist/components/footprint/index.d.ts.map +1 -0
- package/dist/components/footprint/index.js +4 -0
- package/dist/components/footprint/index.js.map +1 -0
- package/dist/components/footprint/perBarStats.d.ts +63 -0
- package/dist/components/footprint/perBarStats.d.ts.map +1 -0
- package/dist/components/footprint/perBarStats.js +123 -0
- package/dist/components/footprint/perBarStats.js.map +1 -0
- package/dist/components/footprint/types.d.ts +197 -0
- package/dist/components/footprint/types.d.ts.map +1 -0
- package/dist/components/footprint/types.js +23 -0
- package/dist/components/footprint/types.js.map +1 -0
- package/dist/components/mtfOverlay/alignToBaseIndex.d.ts +45 -0
- package/dist/components/mtfOverlay/alignToBaseIndex.d.ts.map +1 -0
- package/dist/components/mtfOverlay/alignToBaseIndex.js +92 -0
- package/dist/components/mtfOverlay/alignToBaseIndex.js.map +1 -0
- package/dist/components/mtfOverlay/createMtfController.d.ts +7 -0
- package/dist/components/mtfOverlay/createMtfController.d.ts.map +1 -0
- package/dist/components/mtfOverlay/createMtfController.js +134 -0
- package/dist/components/mtfOverlay/createMtfController.js.map +1 -0
- package/dist/components/mtfOverlay/index.d.ts +19 -0
- package/dist/components/mtfOverlay/index.d.ts.map +1 -0
- package/dist/components/mtfOverlay/index.js +18 -0
- package/dist/components/mtfOverlay/index.js.map +1 -0
- package/dist/components/mtfOverlay/resampleBars.d.ts +37 -0
- package/dist/components/mtfOverlay/resampleBars.d.ts.map +1 -0
- package/dist/components/mtfOverlay/resampleBars.js +93 -0
- package/dist/components/mtfOverlay/resampleBars.js.map +1 -0
- package/dist/components/mtfOverlay/types.d.ts +132 -0
- package/dist/components/mtfOverlay/types.d.ts.map +1 -0
- package/dist/components/mtfOverlay/types.js +22 -0
- package/dist/components/mtfOverlay/types.js.map +1 -0
- package/dist/components/orderBookHeatmap/createHeatmapController.d.ts +21 -0
- package/dist/components/orderBookHeatmap/createHeatmapController.d.ts.map +1 -0
- package/dist/components/orderBookHeatmap/createHeatmapController.js +234 -0
- package/dist/components/orderBookHeatmap/createHeatmapController.js.map +1 -0
- package/dist/components/orderBookHeatmap/createOrderBookState.d.ts +18 -0
- package/dist/components/orderBookHeatmap/createOrderBookState.d.ts.map +1 -0
- package/dist/components/orderBookHeatmap/createOrderBookState.js +102 -0
- package/dist/components/orderBookHeatmap/createOrderBookState.js.map +1 -0
- package/dist/components/orderBookHeatmap/deltaArchive.d.ts +25 -0
- package/dist/components/orderBookHeatmap/deltaArchive.d.ts.map +1 -0
- package/dist/components/orderBookHeatmap/deltaArchive.js +106 -0
- package/dist/components/orderBookHeatmap/deltaArchive.js.map +1 -0
- package/dist/components/orderBookHeatmap/index.d.ts +13 -0
- package/dist/components/orderBookHeatmap/index.d.ts.map +1 -0
- package/dist/components/orderBookHeatmap/index.js +12 -0
- package/dist/components/orderBookHeatmap/index.js.map +1 -0
- package/dist/components/orderBookHeatmap/logColorScale.d.ts +20 -0
- package/dist/components/orderBookHeatmap/logColorScale.d.ts.map +1 -0
- package/dist/components/orderBookHeatmap/logColorScale.js +71 -0
- package/dist/components/orderBookHeatmap/logColorScale.js.map +1 -0
- package/dist/components/orderBookHeatmap/snapshotRing.d.ts +16 -0
- package/dist/components/orderBookHeatmap/snapshotRing.d.ts.map +1 -0
- package/dist/components/orderBookHeatmap/snapshotRing.js +61 -0
- package/dist/components/orderBookHeatmap/snapshotRing.js.map +1 -0
- package/dist/components/orderBookHeatmap/types.d.ts +128 -0
- package/dist/components/orderBookHeatmap/types.d.ts.map +1 -0
- package/dist/components/orderBookHeatmap/types.js +16 -0
- package/dist/components/orderBookHeatmap/types.js.map +1 -0
- package/dist/components/volumeProfile/binning.d.ts +54 -0
- package/dist/components/volumeProfile/binning.d.ts.map +1 -0
- package/dist/components/volumeProfile/binning.js +123 -0
- package/dist/components/volumeProfile/binning.js.map +1 -0
- package/dist/components/volumeProfile/createVolumeProfileController.d.ts +31 -0
- package/dist/components/volumeProfile/createVolumeProfileController.d.ts.map +1 -0
- package/dist/components/volumeProfile/createVolumeProfileController.js +179 -0
- package/dist/components/volumeProfile/createVolumeProfileController.js.map +1 -0
- package/dist/components/volumeProfile/index.d.ts +6 -0
- package/dist/components/volumeProfile/index.d.ts.map +1 -0
- package/dist/components/volumeProfile/index.js +5 -0
- package/dist/components/volumeProfile/index.js.map +1 -0
- package/dist/components/volumeProfile/poc.d.ts +35 -0
- package/dist/components/volumeProfile/poc.d.ts.map +1 -0
- package/dist/components/volumeProfile/poc.js +51 -0
- package/dist/components/volumeProfile/poc.js.map +1 -0
- package/dist/components/volumeProfile/types.d.ts +138 -0
- package/dist/components/volumeProfile/types.d.ts.map +1 -0
- package/dist/components/volumeProfile/types.js +19 -0
- package/dist/components/volumeProfile/types.js.map +1 -0
- package/dist/components/volumeProfile/valueArea.d.ts +58 -0
- package/dist/components/volumeProfile/valueArea.d.ts.map +1 -0
- package/dist/components/volumeProfile/valueArea.js +172 -0
- package/dist/components/volumeProfile/valueArea.js.map +1 -0
- package/dist/controllers/createChartController.d.ts.map +1 -1
- package/dist/controllers/createChartController.js +22 -11
- package/dist/controllers/createChartController.js.map +1 -1
- package/dist/controllers/types.d.ts +3 -1
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/data-fetchers/baostock.d.ts.map +1 -1
- package/dist/data-fetchers/baostock.js +2 -1
- package/dist/data-fetchers/baostock.js.map +1 -1
- package/dist/data-fetchers/dataBuffer.effects.d.ts.map +1 -1
- package/dist/data-fetchers/dataBuffer.effects.js +2 -1
- package/dist/data-fetchers/dataBuffer.effects.js.map +1 -1
- package/dist/data-fetchers/fetcherDefinitionRegistry.d.ts.map +1 -1
- package/dist/data-fetchers/fetcherDefinitionRegistry.js +2 -1
- package/dist/data-fetchers/fetcherDefinitionRegistry.js.map +1 -1
- package/dist/data-fetchers/gotdx.js +4 -3
- package/dist/data-fetchers/gotdx.js.map +1 -1
- package/dist/data-fetchers/router.d.ts.map +1 -1
- package/dist/data-fetchers/router.js +4 -3
- package/dist/data-fetchers/router.js.map +1 -1
- package/dist/data-fetchers/tradingview.js +3 -2
- package/dist/data-fetchers/tradingview.js.map +1 -1
- package/dist/engine/chart.d.ts +13 -0
- package/dist/engine/chart.d.ts.map +1 -1
- package/dist/engine/chart.js +109 -0
- package/dist/engine/chart.js.map +1 -1
- package/dist/engine/data/chartDataManager.d.ts +1 -0
- package/dist/engine/data/chartDataManager.d.ts.map +1 -1
- package/dist/engine/data/chartDataManager.js +27 -4
- package/dist/engine/data/chartDataManager.js.map +1 -1
- package/dist/engine/drawing/toolConfig.d.ts.map +1 -1
- package/dist/engine/drawing/toolConfig.js +2 -1
- package/dist/engine/drawing/toolConfig.js.map +1 -1
- package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -1
- package/dist/engine/indicators/chartIndicatorManager.js +2 -1
- package/dist/engine/indicators/chartIndicatorManager.js.map +1 -1
- package/dist/engine/indicators/indicatorDefinitionRegistry.d.ts.map +1 -1
- package/dist/engine/indicators/indicatorDefinitionRegistry.js +2 -1
- package/dist/engine/indicators/indicatorDefinitionRegistry.js.map +1 -1
- package/dist/engine/indicators/indicatorMetadata.d.ts.map +1 -1
- package/dist/engine/indicators/indicatorMetadata.js +2 -1
- package/dist/engine/indicators/indicatorMetadata.js.map +1 -1
- package/dist/engine/indicators/indicatorRegistry.d.ts.map +1 -1
- package/dist/engine/indicators/indicatorRegistry.js +6 -5
- package/dist/engine/indicators/indicatorRegistry.js.map +1 -1
- package/dist/engine/indicators/registerBuiltins.d.ts.map +1 -1
- package/dist/engine/indicators/registerBuiltins.js +2 -1
- package/dist/engine/indicators/registerBuiltins.js.map +1 -1
- package/dist/engine/indicators/scheduler.d.ts +6 -0
- package/dist/engine/indicators/scheduler.d.ts.map +1 -1
- package/dist/engine/indicators/scheduler.js +12 -0
- package/dist/engine/indicators/scheduler.js.map +1 -1
- package/dist/engine/indicators/soa.d.ts.map +1 -1
- package/dist/engine/indicators/soa.js +2 -1
- package/dist/engine/indicators/soa.js.map +1 -1
- package/dist/engine/indicators/stateComposer.d.ts.map +1 -1
- package/dist/engine/indicators/stateComposer.js +2 -1
- package/dist/engine/indicators/stateComposer.js.map +1 -1
- package/dist/engine/subPaneManager.d.ts.map +1 -1
- package/dist/engine/subPaneManager.js +2 -1
- package/dist/engine/subPaneManager.js.map +1 -1
- package/dist/engine/utils/chartZoomController.d.ts +2 -0
- package/dist/engine/utils/chartZoomController.d.ts.map +1 -1
- package/dist/engine/utils/chartZoomController.js +5 -1
- package/dist/engine/utils/chartZoomController.js.map +1 -1
- package/dist/errors-help.d.ts +72 -0
- package/dist/errors-help.d.ts.map +1 -0
- package/dist/errors-help.js +149 -0
- package/dist/errors-help.js.map +1 -0
- package/dist/errors.d.ts +64 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +61 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -1
- package/dist/indicators/alma.d.ts +27 -0
- package/dist/indicators/alma.d.ts.map +1 -0
- package/dist/indicators/alma.js +36 -0
- package/dist/indicators/alma.js.map +1 -0
- package/dist/indicators/awesomeOscillator.d.ts +19 -0
- package/dist/indicators/awesomeOscillator.d.ts.map +1 -0
- package/dist/indicators/awesomeOscillator.js +36 -0
- package/dist/indicators/awesomeOscillator.js.map +1 -0
- package/dist/indicators/dpo.d.ts +17 -0
- package/dist/indicators/dpo.d.ts.map +1 -0
- package/dist/indicators/dpo.js +24 -0
- package/dist/indicators/dpo.js.map +1 -0
- package/dist/indicators/fisherTransform.d.ts +26 -0
- package/dist/indicators/fisherTransform.d.ts.map +1 -0
- package/dist/indicators/fisherTransform.js +44 -0
- package/dist/indicators/fisherTransform.js.map +1 -0
- package/dist/indicators/frama.d.ts +25 -0
- package/dist/indicators/frama.d.ts.map +1 -0
- package/dist/indicators/frama.js +61 -0
- package/dist/indicators/frama.js.map +1 -0
- package/dist/indicators/index.d.ts +24 -0
- package/dist/indicators/index.d.ts.map +1 -0
- package/dist/indicators/index.js +26 -0
- package/dist/indicators/index.js.map +1 -0
- package/dist/indicators/lsma.d.ts +21 -0
- package/dist/indicators/lsma.d.ts.map +1 -0
- package/dist/indicators/lsma.js +37 -0
- package/dist/indicators/lsma.js.map +1 -0
- package/dist/indicators/schaffTrendCycle.d.ts +24 -0
- package/dist/indicators/schaffTrendCycle.d.ts.map +1 -0
- package/dist/indicators/schaffTrendCycle.js +70 -0
- package/dist/indicators/schaffTrendCycle.js.map +1 -0
- package/dist/indicators/stochRSI.d.ts +24 -0
- package/dist/indicators/stochRSI.d.ts.map +1 -0
- package/dist/indicators/stochRSI.js +86 -0
- package/dist/indicators/stochRSI.js.map +1 -0
- package/dist/indicators/t3.d.ts +31 -0
- package/dist/indicators/t3.d.ts.map +1 -0
- package/dist/indicators/t3.js +63 -0
- package/dist/indicators/t3.js.map +1 -0
- package/dist/indicators/ultimateOscillator.d.ts +26 -0
- package/dist/indicators/ultimateOscillator.d.ts.map +1 -0
- package/dist/indicators/ultimateOscillator.js +59 -0
- package/dist/indicators/ultimateOscillator.js.map +1 -0
- package/dist/indicators/vidya.d.ts +24 -0
- package/dist/indicators/vidya.d.ts.map +1 -0
- package/dist/indicators/vidya.js +54 -0
- package/dist/indicators/vidya.js.map +1 -0
- package/dist/indicators/zlema.d.ts +16 -0
- package/dist/indicators/zlema.d.ts.map +1 -0
- package/dist/indicators/zlema.js +26 -0
- package/dist/indicators/zlema.js.map +1 -0
- package/dist/input/gesture.d.ts +125 -0
- package/dist/input/gesture.d.ts.map +1 -0
- package/dist/input/gesture.js +249 -0
- package/dist/input/gesture.js.map +1 -0
- package/dist/input/index.d.ts +9 -0
- package/dist/input/index.d.ts.map +1 -0
- package/dist/input/index.js +9 -0
- package/dist/input/index.js.map +1 -0
- package/dist/input/keyboard.d.ts +140 -0
- package/dist/input/keyboard.d.ts.map +1 -0
- package/dist/input/keyboard.js +260 -0
- package/dist/input/keyboard.js.map +1 -0
- package/dist/mcp/chartBridge.d.ts +2 -2
- package/dist/mcp/chartBridge.d.ts.map +1 -1
- package/dist/mcp/chartBridge.js +27 -21
- package/dist/mcp/chartBridge.js.map +1 -1
- package/dist/plugin/PluginHost.d.ts +0 -3
- package/dist/plugin/PluginHost.d.ts.map +1 -1
- package/dist/plugin/PluginHost.js +7 -3
- package/dist/plugin/PluginHost.js.map +1 -1
- package/dist/plugin/PluginRegistry.d.ts +0 -3
- package/dist/plugin/PluginRegistry.d.ts.map +1 -1
- package/dist/plugin/PluginRegistry.js +5 -1
- package/dist/plugin/PluginRegistry.js.map +1 -1
- package/dist/render/Renderer.d.ts +116 -0
- package/dist/render/Renderer.d.ts.map +1 -0
- package/dist/render/Renderer.js +31 -0
- package/dist/render/Renderer.js.map +1 -0
- package/dist/render/SurfaceBackend.d.ts +83 -0
- package/dist/render/SurfaceBackend.d.ts.map +1 -0
- package/dist/render/SurfaceBackend.js +27 -0
- package/dist/render/SurfaceBackend.js.map +1 -0
- package/dist/render/index.d.ts +12 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +11 -0
- package/dist/render/index.js.map +1 -0
- package/dist/renderer-tier/detectRendererTier.d.ts +57 -0
- package/dist/renderer-tier/detectRendererTier.d.ts.map +1 -0
- package/dist/renderer-tier/detectRendererTier.js +143 -0
- package/dist/renderer-tier/detectRendererTier.js.map +1 -0
- package/dist/renderer-tier/index.d.ts +12 -0
- package/dist/renderer-tier/index.d.ts.map +1 -0
- package/dist/renderer-tier/index.js +12 -0
- package/dist/renderer-tier/index.js.map +1 -0
- package/dist/renderer-tier/selectBackend.d.ts +106 -0
- package/dist/renderer-tier/selectBackend.d.ts.map +1 -0
- package/dist/renderer-tier/selectBackend.js +113 -0
- package/dist/renderer-tier/selectBackend.js.map +1 -0
- package/dist/renderer-tier/types.d.ts +50 -0
- package/dist/renderer-tier/types.d.ts.map +1 -0
- package/dist/renderer-tier/types.js +24 -0
- package/dist/renderer-tier/types.js.map +1 -0
- package/dist/replay/createReplayController.d.ts +3 -0
- package/dist/replay/createReplayController.d.ts.map +1 -0
- package/dist/replay/createReplayController.js +251 -0
- package/dist/replay/createReplayController.js.map +1 -0
- package/dist/replay/index.d.ts +5 -0
- package/dist/replay/index.d.ts.map +1 -0
- package/dist/replay/index.js +3 -0
- package/dist/replay/index.js.map +1 -0
- package/dist/replay/timeline.d.ts +50 -0
- package/dist/replay/timeline.d.ts.map +1 -0
- package/dist/replay/timeline.js +104 -0
- package/dist/replay/timeline.js.map +1 -0
- package/dist/replay/types.d.ts +94 -0
- package/dist/replay/types.d.ts.map +1 -0
- package/dist/replay/types.js +21 -0
- package/dist/replay/types.js.map +1 -0
- package/dist/scale/anchoredZoom.d.ts +64 -0
- package/dist/scale/anchoredZoom.d.ts.map +1 -0
- package/dist/scale/anchoredZoom.js +67 -0
- package/dist/scale/anchoredZoom.js.map +1 -0
- package/dist/scale/createPriceScale.d.ts +49 -0
- package/dist/scale/createPriceScale.d.ts.map +1 -0
- package/dist/scale/createPriceScale.js +175 -0
- package/dist/scale/createPriceScale.js.map +1 -0
- package/dist/scale/createTimeScale.d.ts +27 -0
- package/dist/scale/createTimeScale.d.ts.map +1 -0
- package/dist/scale/createTimeScale.js +139 -0
- package/dist/scale/createTimeScale.js.map +1 -0
- package/dist/scale/index.d.ts +6 -0
- package/dist/scale/index.d.ts.map +1 -0
- package/dist/scale/index.js +5 -0
- package/dist/scale/index.js.map +1 -0
- package/dist/scale/originShift.d.ts +69 -0
- package/dist/scale/originShift.d.ts.map +1 -0
- package/dist/scale/originShift.js +41 -0
- package/dist/scale/originShift.js.map +1 -0
- package/dist/scale/types.d.ts +90 -0
- package/dist/scale/types.d.ts.map +1 -0
- package/dist/scale/types.js +17 -0
- package/dist/scale/types.js.map +1 -0
- package/dist/scene/createScene.d.ts +22 -0
- package/dist/scene/createScene.d.ts.map +1 -0
- package/dist/scene/createScene.js +114 -0
- package/dist/scene/createScene.js.map +1 -0
- package/dist/scene/index.d.ts +13 -0
- package/dist/scene/index.d.ts.map +1 -0
- package/dist/scene/index.js +11 -0
- package/dist/scene/index.js.map +1 -0
- package/dist/scene/layerRegistry.d.ts +83 -0
- package/dist/scene/layerRegistry.d.ts.map +1 -0
- package/dist/scene/layerRegistry.js +43 -0
- package/dist/scene/layerRegistry.js.map +1 -0
- package/dist/scene/types.d.ts +128 -0
- package/dist/scene/types.d.ts.map +1 -0
- package/dist/scene/types.js +21 -0
- package/dist/scene/types.js.map +1 -0
- package/dist/scheduler/createFrameBudget.d.ts +91 -0
- package/dist/scheduler/createFrameBudget.d.ts.map +1 -0
- package/dist/scheduler/createFrameBudget.js +232 -0
- package/dist/scheduler/createFrameBudget.js.map +1 -0
- package/dist/scheduler/index.d.ts +2 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +2 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/tokens/themeToCssVars.d.ts.map +1 -1
- package/dist/tokens/themeToCssVars.js +2 -28
- package/dist/tokens/themeToCssVars.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/src/__bench__/indicators.bench.ts +215 -0
- package/src/__bench__/orderBookHeatmap.bench.ts +68 -0
- package/src/__bench__/scale.bench.ts +80 -0
- package/src/__bench__/signal.bench.ts +41 -0
- package/src/__bench__/volumeProfile.bench.ts +66 -0
- package/src/__tests__/errors-help.test.ts +184 -0
- package/src/__tests__/errors.test.ts +187 -0
- package/src/alerts/__tests__/controller.test.ts +231 -0
- package/src/alerts/__tests__/predicates.test.ts +374 -0
- package/src/alerts/__tests__/ruleSchema.test.ts +180 -0
- package/src/alerts/createAlertController.ts +312 -0
- package/src/alerts/index.ts +18 -0
- package/src/alerts/predicates.ts +231 -0
- package/src/alerts/rollingVolume.ts +51 -0
- package/src/alerts/ruleSchema.ts +278 -0
- package/src/alerts/types.ts +177 -0
- package/src/chartTypes/__tests__/heikinAshi.test.ts +122 -0
- package/src/chartTypes/__tests__/pointAndFigure.test.ts +167 -0
- package/src/chartTypes/__tests__/rangeBars.test.ts +146 -0
- package/src/chartTypes/__tests__/renko.test.ts +160 -0
- package/src/chartTypes/heikinAshi.ts +116 -0
- package/src/chartTypes/index.ts +5 -0
- package/src/chartTypes/pointAndFigure.ts +278 -0
- package/src/chartTypes/rangeBars.ts +303 -0
- package/src/chartTypes/renko.ts +311 -0
- package/src/chartTypes/types.ts +99 -0
- package/src/components/anchoredVwap/__tests__/computeAnchoredVwap.test.ts +331 -0
- package/src/components/anchoredVwap/__tests__/controller.test.ts +430 -0
- package/src/components/anchoredVwap/computeAnchoredVwap.ts +174 -0
- package/src/components/anchoredVwap/createAnchoredVwapController.ts +358 -0
- package/src/components/anchoredVwap/index.ts +17 -0
- package/src/components/anchoredVwap/types.ts +187 -0
- package/src/components/crosshairSync/__tests__/crosshairSync.test.ts +261 -0
- package/src/components/crosshairSync/createCrosshairSync.ts +187 -0
- package/src/components/crosshairSync/index.ts +5 -0
- package/src/components/footprint/__tests__/aggressor.test.ts +127 -0
- package/src/components/footprint/__tests__/controller.test.ts +130 -0
- package/src/components/footprint/__tests__/perBarStats.test.ts +114 -0
- package/src/components/footprint/aggressor.ts +165 -0
- package/src/components/footprint/createFootprintController.ts +338 -0
- package/src/components/footprint/index.ts +21 -0
- package/src/components/footprint/perBarStats.ts +137 -0
- package/src/components/footprint/types.ts +232 -0
- package/src/components/mtfOverlay/__tests__/alignToBaseIndex.test.ts +103 -0
- package/src/components/mtfOverlay/__tests__/controller.test.ts +172 -0
- package/src/components/mtfOverlay/__tests__/resampleBars.test.ts +106 -0
- package/src/components/mtfOverlay/alignToBaseIndex.ts +108 -0
- package/src/components/mtfOverlay/createMtfController.ts +180 -0
- package/src/components/mtfOverlay/index.ts +26 -0
- package/src/components/mtfOverlay/resampleBars.ts +134 -0
- package/src/components/mtfOverlay/types.ts +148 -0
- package/src/components/orderBookHeatmap/__tests__/controller.test.ts +237 -0
- package/src/components/orderBookHeatmap/__tests__/deltaArchive.test.ts +88 -0
- package/src/components/orderBookHeatmap/__tests__/logColorScale.test.ts +69 -0
- package/src/components/orderBookHeatmap/__tests__/orderBookState.test.ts +113 -0
- package/src/components/orderBookHeatmap/__tests__/snapshotRing.test.ts +53 -0
- package/src/components/orderBookHeatmap/computeShader.wgsl.md +130 -0
- package/src/components/orderBookHeatmap/createHeatmapController.ts +273 -0
- package/src/components/orderBookHeatmap/createOrderBookState.ts +116 -0
- package/src/components/orderBookHeatmap/deltaArchive.ts +113 -0
- package/src/components/orderBookHeatmap/index.ts +25 -0
- package/src/components/orderBookHeatmap/logColorScale.ts +77 -0
- package/src/components/orderBookHeatmap/snapshotRing.ts +65 -0
- package/src/components/orderBookHeatmap/types.ts +168 -0
- package/src/components/volumeProfile/__tests__/binning.test.ts +133 -0
- package/src/components/volumeProfile/__tests__/poc.test.ts +44 -0
- package/src/components/volumeProfile/__tests__/valueArea.test.ts +177 -0
- package/src/components/volumeProfile/binning.ts +133 -0
- package/src/components/volumeProfile/createVolumeProfileController.ts +213 -0
- package/src/components/volumeProfile/index.ts +12 -0
- package/src/components/volumeProfile/poc.ts +53 -0
- package/src/components/volumeProfile/types.ts +148 -0
- package/src/components/volumeProfile/valueArea.ts +187 -0
- package/src/controllers/createChartController.ts +23 -10
- package/src/controllers/types.ts +5 -1
- package/src/data-fetchers/baostock.ts +2 -1
- package/src/data-fetchers/dataBuffer.effects.ts +3 -1
- package/src/data-fetchers/fetcherDefinitionRegistry.ts +2 -1
- package/src/data-fetchers/gotdx.ts +4 -3
- package/src/data-fetchers/router.ts +6 -3
- package/src/data-fetchers/tradingview.ts +3 -2
- package/src/engine/chart.ts +126 -3
- package/src/engine/data/chartDataManager.ts +26 -4
- package/src/engine/drawing/toolConfig.ts +2 -1
- package/src/engine/indicators/chartIndicatorManager.ts +2 -1
- package/src/engine/indicators/indicatorDefinitionRegistry.ts +2 -1
- package/src/engine/indicators/indicatorMetadata.ts +2 -1
- package/src/engine/indicators/indicatorRegistry.ts +6 -5
- package/src/engine/indicators/registerBuiltins.ts +2 -1
- package/src/engine/indicators/scheduler.ts +15 -0
- package/src/engine/indicators/soa.ts +2 -1
- package/src/engine/indicators/stateComposer.ts +2 -1
- package/src/engine/subPaneManager.ts +2 -1
- package/src/engine/utils/chartZoomController.ts +7 -1
- package/src/errors-help.ts +218 -0
- package/src/errors.ts +128 -0
- package/src/index.ts +37 -0
- package/src/indicators/__tests__/maFamily.test.ts +159 -0
- package/src/indicators/__tests__/oscillators.test.ts +150 -0
- package/src/indicators/alma.ts +61 -0
- package/src/indicators/awesomeOscillator.ts +54 -0
- package/src/indicators/dpo.ts +37 -0
- package/src/indicators/fisherTransform.ts +63 -0
- package/src/indicators/frama.ts +87 -0
- package/src/indicators/index.ts +39 -0
- package/src/indicators/lsma.ts +58 -0
- package/src/indicators/schaffTrendCycle.ts +92 -0
- package/src/indicators/stochRSI.ts +108 -0
- package/src/indicators/t3.ts +96 -0
- package/src/indicators/ultimateOscillator.ts +83 -0
- package/src/indicators/vidya.ts +80 -0
- package/src/indicators/zlema.ts +41 -0
- package/src/input/__tests__/gesture.test.ts +271 -0
- package/src/input/__tests__/keyboard.test.ts +317 -0
- package/src/input/gesture.ts +357 -0
- package/src/input/index.ts +26 -0
- package/src/input/keyboard.ts +373 -0
- package/src/mcp/chartBridge.ts +98 -72
- package/src/plugin/PluginHost.ts +4 -3
- package/src/plugin/PluginRegistry.ts +2 -1
- package/src/render/Renderer.ts +127 -0
- package/src/render/SurfaceBackend.ts +94 -0
- package/src/render/__tests__/contract.test.ts +176 -0
- package/src/render/index.ts +27 -0
- package/src/renderer-tier/__tests__/detectRendererTier.test.ts +180 -0
- package/src/renderer-tier/__tests__/selectBackend.test.ts +253 -0
- package/src/renderer-tier/detectRendererTier.ts +168 -0
- package/src/renderer-tier/index.ts +30 -0
- package/src/renderer-tier/selectBackend.ts +201 -0
- package/src/renderer-tier/types.ts +59 -0
- package/src/replay/__tests__/controller.test.ts +309 -0
- package/src/replay/__tests__/timeline.test.ts +99 -0
- package/src/replay/createReplayController.ts +302 -0
- package/src/replay/index.ts +15 -0
- package/src/replay/timeline.ts +106 -0
- package/src/replay/types.ts +107 -0
- package/src/scale/__tests__/anchoredZoom.test.ts +125 -0
- package/src/scale/__tests__/originShift.test.ts +97 -0
- package/src/scale/__tests__/priceScale.test.ts +219 -0
- package/src/scale/__tests__/timeScale.test.ts +119 -0
- package/src/scale/anchoredZoom.ts +104 -0
- package/src/scale/createPriceScale.ts +226 -0
- package/src/scale/createTimeScale.ts +150 -0
- package/src/scale/index.ts +9 -0
- package/src/scale/originShift.ts +111 -0
- package/src/scale/types.ts +100 -0
- package/src/scene/__tests__/layerRegistry.test.ts +207 -0
- package/src/scene/__tests__/scene.test.ts +411 -0
- package/src/scene/createScene.ts +117 -0
- package/src/scene/index.ts +29 -0
- package/src/scene/layerRegistry.ts +117 -0
- package/src/scene/types.ts +143 -0
- package/src/scheduler/__tests__/createFrameBudget.test.ts +315 -0
- package/src/scheduler/createFrameBudget.ts +316 -0
- package/src/scheduler/index.ts +7 -0
- package/src/tokens/themeToCssVars.ts +3 -1
- package/src/version.ts +1 -1
- package/dist/data-fetchers/gotdx/gotdx.d.ts +0 -7
- package/dist/data-fetchers/gotdx/gotdx.d.ts.map +0 -1
- package/dist/data-fetchers/gotdx/gotdx.js +0 -7
- package/dist/data-fetchers/gotdx/gotdx.js.map +0 -1
- package/src/data-fetchers/gotdx/gotdx.ts +0 -6
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createHeatmapController } from '../createHeatmapController'
|
|
4
|
+
import { createOrderBookState } from '../createOrderBookState'
|
|
5
|
+
import type { BookSnapshot, OrderBookDelta } from '../types'
|
|
6
|
+
|
|
7
|
+
function bid(ts: number, price: number, size: number): OrderBookDelta {
|
|
8
|
+
return { side: 'bid', price, size, timestamp: ts }
|
|
9
|
+
}
|
|
10
|
+
function ask(ts: number, price: number, size: number): OrderBookDelta {
|
|
11
|
+
return { side: 'ask', price, size, timestamp: ts }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function findBidSize(snap: BookSnapshot, price: number): number {
|
|
15
|
+
for (const [p, s] of snap.bids) if (p === price) return s
|
|
16
|
+
return 0
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('createHeatmapController', () => {
|
|
20
|
+
it('auto-generates snapshots at the configured interval driven by delta timestamps', () => {
|
|
21
|
+
const ctrl = createHeatmapController({
|
|
22
|
+
tickSize: 0.01,
|
|
23
|
+
snapshotIntervalMs: 100,
|
|
24
|
+
snapshotRingCapacity: 10,
|
|
25
|
+
deltaArchiveMaxSize: 1000,
|
|
26
|
+
logColorRange: { sizeMin: 1, sizeMax: 1000 },
|
|
27
|
+
})
|
|
28
|
+
// Send 5 deltas spanning 0 → 450ms — 4 interval crossings.
|
|
29
|
+
ctrl.ingestDelta(bid(0, 100, 1))
|
|
30
|
+
ctrl.ingestDelta(bid(150, 100, 2)) // crosses 100ms
|
|
31
|
+
ctrl.ingestDelta(bid(220, 100, 3)) // crosses 200ms
|
|
32
|
+
ctrl.ingestDelta(bid(330, 100, 4)) // crosses 300ms
|
|
33
|
+
ctrl.ingestDelta(bid(450, 100, 5)) // crosses 400ms
|
|
34
|
+
const st = ctrl.state.peek()
|
|
35
|
+
// Each crossing produces one snapshot.
|
|
36
|
+
expect(st.snapshotCount).toBe(4)
|
|
37
|
+
expect(st.deltaCount).toBe(5)
|
|
38
|
+
ctrl.dispose()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('records flash orders in the archive even when they vanish before the next snapshot', () => {
|
|
42
|
+
const ctrl = createHeatmapController({
|
|
43
|
+
tickSize: 0.01,
|
|
44
|
+
snapshotIntervalMs: 100,
|
|
45
|
+
snapshotRingCapacity: 10,
|
|
46
|
+
deltaArchiveMaxSize: 1000,
|
|
47
|
+
logColorRange: { sizeMin: 1, sizeMax: 1000 },
|
|
48
|
+
})
|
|
49
|
+
// First delta anchors snapshot clock at t=0.
|
|
50
|
+
ctrl.ingestDelta(bid(0, 100, 1))
|
|
51
|
+
// Place + cancel inside the [0, 100) window.
|
|
52
|
+
ctrl.ingestDelta(bid(10, 99.5, 500)) // flash place
|
|
53
|
+
ctrl.ingestDelta(bid(20, 99.5, 0)) // flash cancel
|
|
54
|
+
// Now cross the interval to fire a snapshot.
|
|
55
|
+
ctrl.ingestDelta(bid(150, 100, 2))
|
|
56
|
+
const st = ctrl.state.peek()
|
|
57
|
+
expect(st.snapshotCount).toBe(1)
|
|
58
|
+
// The forced snapshot should NOT show the flash level (it was cancelled).
|
|
59
|
+
ctrl.forceSnapshot()
|
|
60
|
+
const latest = ctrl.state.peek().latestSnapshot
|
|
61
|
+
expect(latest).not.toBeNull()
|
|
62
|
+
expect(findBidSize(latest as BookSnapshot, 99.5)).toBe(0)
|
|
63
|
+
// But replay across the flash window MUST show it.
|
|
64
|
+
const series = ctrl.replay(5, 25, 5)
|
|
65
|
+
const flashOn = series.find((s) => findBidSize(s, 99.5) === 500)
|
|
66
|
+
const flashOff = series.find(
|
|
67
|
+
(s) => s.timestamp >= 20 && findBidSize(s, 99.5) === 0,
|
|
68
|
+
)
|
|
69
|
+
expect(flashOn).toBeDefined()
|
|
70
|
+
expect(flashOff).toBeDefined()
|
|
71
|
+
ctrl.dispose()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('forceSnapshot() pushes the current book state immediately', () => {
|
|
75
|
+
const ctrl = createHeatmapController({
|
|
76
|
+
tickSize: 0.01,
|
|
77
|
+
snapshotIntervalMs: 1000,
|
|
78
|
+
snapshotRingCapacity: 10,
|
|
79
|
+
deltaArchiveMaxSize: 1000,
|
|
80
|
+
logColorRange: { sizeMin: 1, sizeMax: 1000 },
|
|
81
|
+
})
|
|
82
|
+
ctrl.ingestDelta(bid(0, 100, 5))
|
|
83
|
+
ctrl.ingestDelta(ask(0, 101, 7))
|
|
84
|
+
expect(ctrl.state.peek().snapshotCount).toBe(0)
|
|
85
|
+
ctrl.forceSnapshot()
|
|
86
|
+
const st = ctrl.state.peek()
|
|
87
|
+
expect(st.snapshotCount).toBe(1)
|
|
88
|
+
expect(st.latestSnapshot?.bids).toEqual([[100, 5]])
|
|
89
|
+
expect(st.latestSnapshot?.asks).toEqual([[101, 7]])
|
|
90
|
+
ctrl.dispose()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('replay(t1,t2) reconstructs the book equivalent to fold-left of deltas', () => {
|
|
94
|
+
// Property-test equivalent: build a fixed delta stream, replay through
|
|
95
|
+
// the controller, and assert the replayed snapshot at every grid
|
|
96
|
+
// timestamp equals the snapshot computed by applying deltas
|
|
97
|
+
// up-to-and-including that timestamp to a fresh order book.
|
|
98
|
+
const ctrl = createHeatmapController({
|
|
99
|
+
tickSize: 0.01,
|
|
100
|
+
snapshotIntervalMs: 50,
|
|
101
|
+
snapshotRingCapacity: 100,
|
|
102
|
+
deltaArchiveMaxSize: 10_000,
|
|
103
|
+
logColorRange: { sizeMin: 1, sizeMax: 1000 },
|
|
104
|
+
})
|
|
105
|
+
const deltas: OrderBookDelta[] = [
|
|
106
|
+
bid(0, 100, 5),
|
|
107
|
+
ask(0, 101, 5),
|
|
108
|
+
bid(40, 99.5, 10),
|
|
109
|
+
ask(60, 101.5, 3),
|
|
110
|
+
bid(75, 100, 0), // remove
|
|
111
|
+
ask(110, 102, 4),
|
|
112
|
+
bid(140, 99.5, 25),
|
|
113
|
+
ask(170, 101, 0),
|
|
114
|
+
bid(190, 99, 1),
|
|
115
|
+
]
|
|
116
|
+
for (const d of deltas) ctrl.ingestDelta(d)
|
|
117
|
+
|
|
118
|
+
const replayed = ctrl.replay(0, 200, 25)
|
|
119
|
+
|
|
120
|
+
for (const snap of replayed) {
|
|
121
|
+
const oracle = createOrderBookState({ tickSize: 0.01 })
|
|
122
|
+
for (const d of deltas) {
|
|
123
|
+
if (d.timestamp <= snap.timestamp) oracle.applyDelta(d)
|
|
124
|
+
}
|
|
125
|
+
const expected = oracle.snapshot()
|
|
126
|
+
expect(snap.bids).toEqual(expected.bids)
|
|
127
|
+
expect(snap.asks).toEqual(expected.asks)
|
|
128
|
+
}
|
|
129
|
+
ctrl.dispose()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('replay across midpoint matches live-state snapshot at that point', () => {
|
|
133
|
+
// Hand-coded equivalence: a known midpoint timestamp must yield the
|
|
134
|
+
// same book state whether reached via live ingest or via replay.
|
|
135
|
+
const cfg = {
|
|
136
|
+
tickSize: 0.01,
|
|
137
|
+
snapshotIntervalMs: 100,
|
|
138
|
+
snapshotRingCapacity: 64,
|
|
139
|
+
deltaArchiveMaxSize: 10_000,
|
|
140
|
+
logColorRange: { sizeMin: 1, sizeMax: 1000 },
|
|
141
|
+
}
|
|
142
|
+
const live = createHeatmapController(cfg)
|
|
143
|
+
const deltas: OrderBookDelta[] = [
|
|
144
|
+
bid(0, 100, 1),
|
|
145
|
+
ask(0, 101, 1),
|
|
146
|
+
bid(50, 99.5, 2),
|
|
147
|
+
bid(120, 100, 0),
|
|
148
|
+
ask(160, 101.5, 4),
|
|
149
|
+
]
|
|
150
|
+
for (const d of deltas) live.ingestDelta(d)
|
|
151
|
+
|
|
152
|
+
// Live midpoint reference: replay BOOK alone, no controller, up to t=80.
|
|
153
|
+
const reference = createOrderBookState({ tickSize: 0.01 })
|
|
154
|
+
for (const d of deltas) if (d.timestamp <= 80) reference.applyDelta(d)
|
|
155
|
+
const refSnap = reference.snapshot()
|
|
156
|
+
|
|
157
|
+
// Controller-produced replay snapshot at exactly t=80.
|
|
158
|
+
const series = live.replay(80, 80, 1)
|
|
159
|
+
expect(series).toHaveLength(1)
|
|
160
|
+
expect(series[0].bids).toEqual(refSnap.bids)
|
|
161
|
+
expect(series[0].asks).toEqual(refSnap.asks)
|
|
162
|
+
live.dispose()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('dispose() silences subsequent mutator calls', () => {
|
|
166
|
+
const ctrl = createHeatmapController({
|
|
167
|
+
tickSize: 0.01,
|
|
168
|
+
snapshotIntervalMs: 100,
|
|
169
|
+
snapshotRingCapacity: 10,
|
|
170
|
+
deltaArchiveMaxSize: 1000,
|
|
171
|
+
logColorRange: { sizeMin: 1, sizeMax: 1000 },
|
|
172
|
+
})
|
|
173
|
+
ctrl.ingestDelta(bid(0, 100, 1))
|
|
174
|
+
const before = ctrl.state.peek().deltaCount
|
|
175
|
+
ctrl.dispose()
|
|
176
|
+
ctrl.ingestDelta(bid(100, 100, 2))
|
|
177
|
+
ctrl.forceSnapshot()
|
|
178
|
+
ctrl.setConfig({ snapshotIntervalMs: 50 })
|
|
179
|
+
expect(ctrl.state.peek().deltaCount).toBe(before)
|
|
180
|
+
// replay() on disposed controller returns empty.
|
|
181
|
+
expect(ctrl.replay(0, 100, 10)).toEqual([])
|
|
182
|
+
// dispose() is idempotent.
|
|
183
|
+
ctrl.dispose()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('setConfig() rebuilds book + ring + archive cap when tick/capacity/maxSize change', () => {
|
|
187
|
+
const ctrl = createHeatmapController({
|
|
188
|
+
tickSize: 0.01,
|
|
189
|
+
snapshotIntervalMs: 100,
|
|
190
|
+
snapshotRingCapacity: 4,
|
|
191
|
+
deltaArchiveMaxSize: 1000,
|
|
192
|
+
logColorRange: { sizeMin: 1, sizeMax: 1000 },
|
|
193
|
+
})
|
|
194
|
+
// Three distinct fine prices that all fall inside a single coarse
|
|
195
|
+
// bucket once we re-quantize.
|
|
196
|
+
ctrl.ingestDelta(bid(0, 100.10, 1))
|
|
197
|
+
ctrl.ingestDelta(bid(150, 100.20, 2))
|
|
198
|
+
ctrl.ingestDelta(bid(300, 100.30, 3))
|
|
199
|
+
// Coarser tick: 0.10/0.20/0.30 → ticks 1/2/3 at 0.01, but at 1.0
|
|
200
|
+
// they all round to 100.0.
|
|
201
|
+
ctrl.setConfig({ tickSize: 1.0 })
|
|
202
|
+
ctrl.forceSnapshot()
|
|
203
|
+
const snap = ctrl.state.peek().latestSnapshot as BookSnapshot
|
|
204
|
+
const buckets = new Set(snap.bids.map((b) => b[0]))
|
|
205
|
+
expect(buckets.size).toBe(1)
|
|
206
|
+
expect(snap.bids[0][0]).toBe(100)
|
|
207
|
+
ctrl.dispose()
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('rejects invalid replay configuration', () => {
|
|
211
|
+
const ctrl = createHeatmapController()
|
|
212
|
+
expect(() => ctrl.replay(0, 100, 0)).toThrow()
|
|
213
|
+
expect(() => ctrl.replay(0, 100, -1)).toThrow()
|
|
214
|
+
// from > to returns empty (not throw).
|
|
215
|
+
expect(ctrl.replay(100, 0, 10)).toEqual([])
|
|
216
|
+
ctrl.dispose()
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('emits a state notification on every ingestDelta', () => {
|
|
220
|
+
const ctrl = createHeatmapController({
|
|
221
|
+
tickSize: 0.01,
|
|
222
|
+
snapshotIntervalMs: 1_000,
|
|
223
|
+
snapshotRingCapacity: 10,
|
|
224
|
+
deltaArchiveMaxSize: 1000,
|
|
225
|
+
logColorRange: { sizeMin: 1, sizeMax: 1000 },
|
|
226
|
+
})
|
|
227
|
+
let calls = 0
|
|
228
|
+
const off = ctrl.state.subscribe(() => calls++)
|
|
229
|
+
ctrl.ingestDelta(bid(0, 100, 1))
|
|
230
|
+
ctrl.ingestDelta(bid(1, 100, 2))
|
|
231
|
+
ctrl.ingestDelta(bid(2, 100, 3))
|
|
232
|
+
// 3 deltas → 3 notifications.
|
|
233
|
+
expect(calls).toBe(3)
|
|
234
|
+
off()
|
|
235
|
+
ctrl.dispose()
|
|
236
|
+
})
|
|
237
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createDeltaArchive } from '../deltaArchive'
|
|
4
|
+
import type { OrderBookDelta } from '../types'
|
|
5
|
+
|
|
6
|
+
function delta(ts: number, price = 100, size = 1): OrderBookDelta {
|
|
7
|
+
return { side: 'bid', price, size, timestamp: ts }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('createDeltaArchive', () => {
|
|
11
|
+
it('appends + ranges across the inserted window', () => {
|
|
12
|
+
const a = createDeltaArchive()
|
|
13
|
+
for (let i = 0; i < 10; i++) a.append(delta(i))
|
|
14
|
+
const r = a.range(2, 5)
|
|
15
|
+
expect(r.map((d) => d.timestamp)).toEqual([2, 3, 4, 5])
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('range bounds are inclusive on both ends', () => {
|
|
19
|
+
const a = createDeltaArchive()
|
|
20
|
+
a.append(delta(10))
|
|
21
|
+
a.append(delta(20))
|
|
22
|
+
a.append(delta(30))
|
|
23
|
+
expect(a.range(10, 30).map((d) => d.timestamp)).toEqual([10, 20, 30])
|
|
24
|
+
expect(a.range(11, 29).map((d) => d.timestamp)).toEqual([20])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('trim drops the oldest entries until size ≤ target', () => {
|
|
28
|
+
const a = createDeltaArchive()
|
|
29
|
+
for (let i = 0; i < 100; i++) a.append(delta(i))
|
|
30
|
+
a.trim(30)
|
|
31
|
+
expect(a.size()).toBe(30)
|
|
32
|
+
// The 30 newest should remain.
|
|
33
|
+
const all = a.range(0, 999)
|
|
34
|
+
expect(all[0].timestamp).toBe(70)
|
|
35
|
+
expect(all[all.length - 1].timestamp).toBe(99)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('reports accurate size after append, trim, and clear', () => {
|
|
39
|
+
const a = createDeltaArchive()
|
|
40
|
+
expect(a.size()).toBe(0)
|
|
41
|
+
for (let i = 0; i < 5; i++) a.append(delta(i))
|
|
42
|
+
expect(a.size()).toBe(5)
|
|
43
|
+
a.trim(2)
|
|
44
|
+
expect(a.size()).toBe(2)
|
|
45
|
+
a.clear()
|
|
46
|
+
expect(a.size()).toBe(0)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('chunked storage does not break range queries across chunks', () => {
|
|
50
|
+
// CHUNK_SIZE is 10_000 in the impl. Push more than that to force at
|
|
51
|
+
// least one chunk boundary and ensure ordering still holds.
|
|
52
|
+
const a = createDeltaArchive()
|
|
53
|
+
const N = 25_000
|
|
54
|
+
for (let i = 0; i < N; i++) a.append(delta(i))
|
|
55
|
+
// Window straddles two chunk boundaries.
|
|
56
|
+
const r = a.range(9_998, 10_002)
|
|
57
|
+
expect(r.map((d) => d.timestamp)).toEqual([9_998, 9_999, 10_000, 10_001, 10_002])
|
|
58
|
+
// Full range returns every delta exactly once.
|
|
59
|
+
const all = a.range(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY)
|
|
60
|
+
expect(all.length).toBe(N)
|
|
61
|
+
expect(all[0].timestamp).toBe(0)
|
|
62
|
+
expect(all[N - 1].timestamp).toBe(N - 1)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('respects maxSize at construction by auto-trimming on append', () => {
|
|
66
|
+
const a = createDeltaArchive({ maxSize: 5 })
|
|
67
|
+
for (let i = 0; i < 12; i++) a.append(delta(i))
|
|
68
|
+
expect(a.size()).toBe(5)
|
|
69
|
+
const all = a.range(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY)
|
|
70
|
+
expect(all.map((d) => d.timestamp)).toEqual([7, 8, 9, 10, 11])
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('returns empty array when from > to', () => {
|
|
74
|
+
const a = createDeltaArchive()
|
|
75
|
+
for (let i = 0; i < 5; i++) a.append(delta(i))
|
|
76
|
+
expect(a.range(3, 1)).toEqual([])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('clear() resets the archive without breaking subsequent appends', () => {
|
|
80
|
+
const a = createDeltaArchive()
|
|
81
|
+
for (let i = 0; i < 5; i++) a.append(delta(i))
|
|
82
|
+
a.clear()
|
|
83
|
+
expect(a.size()).toBe(0)
|
|
84
|
+
a.append(delta(99))
|
|
85
|
+
expect(a.size()).toBe(1)
|
|
86
|
+
expect(a.range(0, 1000).map((d) => d.timestamp)).toEqual([99])
|
|
87
|
+
})
|
|
88
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createLogColorScale } from '../logColorScale'
|
|
4
|
+
|
|
5
|
+
describe('createLogColorScale', () => {
|
|
6
|
+
it('maps size = 0 to intensity 0', () => {
|
|
7
|
+
const scale = createLogColorScale(1, 1000)
|
|
8
|
+
expect(scale.intensity(0)).toBe(0)
|
|
9
|
+
expect(scale.intensity(-5)).toBe(0)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('maps size below sizeMin to 0', () => {
|
|
13
|
+
const scale = createLogColorScale(10, 1000)
|
|
14
|
+
expect(scale.intensity(1)).toBe(0)
|
|
15
|
+
expect(scale.intensity(10)).toBe(0)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('maps size above sizeMax to 1', () => {
|
|
19
|
+
const scale = createLogColorScale(1, 100)
|
|
20
|
+
expect(scale.intensity(100)).toBe(1)
|
|
21
|
+
expect(scale.intensity(1_000_000)).toBe(1)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('mid-range is monotonically increasing in log size', () => {
|
|
25
|
+
const scale = createLogColorScale(1, 10_000)
|
|
26
|
+
const a = scale.intensity(2)
|
|
27
|
+
const b = scale.intensity(20)
|
|
28
|
+
const c = scale.intensity(200)
|
|
29
|
+
const d = scale.intensity(2_000)
|
|
30
|
+
expect(a).toBeGreaterThan(0)
|
|
31
|
+
expect(a).toBeLessThan(b)
|
|
32
|
+
expect(b).toBeLessThan(c)
|
|
33
|
+
expect(c).toBeLessThan(d)
|
|
34
|
+
expect(d).toBeLessThan(1)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('matches the exact log-ratio formula for a known interior point', () => {
|
|
38
|
+
// sizeMin = 1, sizeMax = 100 → log range = ln(100). At size = 10
|
|
39
|
+
// intensity should be ln(10) / ln(100) = 0.5.
|
|
40
|
+
const scale = createLogColorScale(1, 100)
|
|
41
|
+
expect(scale.intensity(10)).toBeCloseTo(0.5, 10)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('degenerate range (sizeMin === sizeMax) returns 0.5 for any positive size', () => {
|
|
45
|
+
const scale = createLogColorScale(5, 5)
|
|
46
|
+
expect(scale.intensity(1)).toBe(0.5)
|
|
47
|
+
expect(scale.intensity(5)).toBe(0.5)
|
|
48
|
+
expect(scale.intensity(1_000_000)).toBe(0.5)
|
|
49
|
+
// Non-positive sizes still map to 0.
|
|
50
|
+
expect(scale.intensity(0)).toBe(0)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('setRange swaps the range without rebuilding the scale', () => {
|
|
54
|
+
const scale = createLogColorScale(1, 100)
|
|
55
|
+
expect(scale.intensity(10)).toBeCloseTo(0.5, 10)
|
|
56
|
+
scale.setRange(1, 1_000_000)
|
|
57
|
+
// Now ln(10) / ln(1e6) = 1 / 6.
|
|
58
|
+
expect(scale.intensity(10)).toBeCloseTo(1 / 6, 6)
|
|
59
|
+
expect(scale.range()).toEqual({ sizeMin: 1, sizeMax: 1_000_000 })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('rejects invalid ranges at construction', () => {
|
|
63
|
+
expect(() => createLogColorScale(0, 1)).toThrow()
|
|
64
|
+
expect(() => createLogColorScale(1, 0)).toThrow()
|
|
65
|
+
expect(() => createLogColorScale(-1, 1)).toThrow()
|
|
66
|
+
expect(() => createLogColorScale(10, 1)).toThrow()
|
|
67
|
+
expect(() => createLogColorScale(Infinity, 1)).toThrow()
|
|
68
|
+
})
|
|
69
|
+
})
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createOrderBookState } from '../createOrderBookState'
|
|
4
|
+
|
|
5
|
+
describe('createOrderBookState', () => {
|
|
6
|
+
it('applies a bid delta and reflects it in the next snapshot', () => {
|
|
7
|
+
const book = createOrderBookState({ tickSize: 0.01 })
|
|
8
|
+
book.applyDelta({ side: 'bid', price: 100.0, size: 5, timestamp: 1 })
|
|
9
|
+
const snap = book.snapshot()
|
|
10
|
+
expect(snap.bids).toEqual([[100.0, 5]])
|
|
11
|
+
expect(snap.asks).toEqual([])
|
|
12
|
+
expect(snap.timestamp).toBe(1)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('treats size=0 as a removal of the level', () => {
|
|
16
|
+
const book = createOrderBookState({ tickSize: 0.01 })
|
|
17
|
+
book.applyDelta({ side: 'bid', price: 100.0, size: 5, timestamp: 1 })
|
|
18
|
+
book.applyDelta({ side: 'bid', price: 100.0, size: 0, timestamp: 2 })
|
|
19
|
+
const snap = book.snapshot()
|
|
20
|
+
expect(snap.bids).toEqual([])
|
|
21
|
+
expect(snap.timestamp).toBe(2)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('quantizes input prices to ticks (67000.123 with tick 0.01 → 67000.12)', () => {
|
|
25
|
+
const book = createOrderBookState({ tickSize: 0.01 })
|
|
26
|
+
book.applyDelta({ side: 'ask', price: 67000.123, size: 1, timestamp: 1 })
|
|
27
|
+
const snap = book.snapshot()
|
|
28
|
+
expect(snap.asks).toHaveLength(1)
|
|
29
|
+
expect(snap.asks[0][0]).toBeCloseTo(67000.12, 6)
|
|
30
|
+
expect(snap.asks[0][1]).toBe(1)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('returns dequantized prices (consumer never sees the tick index)', () => {
|
|
34
|
+
const book = createOrderBookState({ tickSize: 0.5 })
|
|
35
|
+
// 100.7 → tick 201 → 100.5
|
|
36
|
+
book.applyDelta({ side: 'bid', price: 100.7, size: 3, timestamp: 1 })
|
|
37
|
+
// 101.3 → tick 203 → 101.5
|
|
38
|
+
book.applyDelta({ side: 'ask', price: 101.3, size: 4, timestamp: 1 })
|
|
39
|
+
const snap = book.snapshot()
|
|
40
|
+
expect(snap.bids).toEqual([[100.5, 3]])
|
|
41
|
+
expect(snap.asks).toEqual([[101.5, 4]])
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('clear() resets both sides and the last timestamp', () => {
|
|
45
|
+
const book = createOrderBookState({ tickSize: 0.01 })
|
|
46
|
+
book.applyDelta({ side: 'bid', price: 100, size: 1, timestamp: 5 })
|
|
47
|
+
book.applyDelta({ side: 'ask', price: 101, size: 2, timestamp: 6 })
|
|
48
|
+
book.clear()
|
|
49
|
+
const snap = book.snapshot()
|
|
50
|
+
expect(snap.bids).toEqual([])
|
|
51
|
+
expect(snap.asks).toEqual([])
|
|
52
|
+
expect(snap.timestamp).toBe(0)
|
|
53
|
+
expect(book.lastTimestamp()).toBe(0)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('last update wins for the same price (multiple updates collapse)', () => {
|
|
57
|
+
const book = createOrderBookState({ tickSize: 0.01 })
|
|
58
|
+
book.applyDelta({ side: 'bid', price: 100, size: 1, timestamp: 1 })
|
|
59
|
+
book.applyDelta({ side: 'bid', price: 100, size: 2, timestamp: 2 })
|
|
60
|
+
book.applyDelta({ side: 'bid', price: 100, size: 9, timestamp: 3 })
|
|
61
|
+
const snap = book.snapshot()
|
|
62
|
+
expect(snap.bids).toEqual([[100.0, 9]])
|
|
63
|
+
expect(snap.timestamp).toBe(3)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('maintains bid and ask side independence', () => {
|
|
67
|
+
const book = createOrderBookState({ tickSize: 0.01 })
|
|
68
|
+
book.applyDelta({ side: 'bid', price: 100, size: 5, timestamp: 1 })
|
|
69
|
+
book.applyDelta({ side: 'ask', price: 100, size: 7, timestamp: 2 })
|
|
70
|
+
// Removing bid at 100 must not remove ask at 100.
|
|
71
|
+
book.applyDelta({ side: 'bid', price: 100, size: 0, timestamp: 3 })
|
|
72
|
+
const snap = book.snapshot()
|
|
73
|
+
expect(snap.bids).toEqual([])
|
|
74
|
+
expect(snap.asks).toEqual([[100, 7]])
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('sorts bids descending and asks ascending', () => {
|
|
78
|
+
const book = createOrderBookState({ tickSize: 0.01 })
|
|
79
|
+
book.applyDelta({ side: 'bid', price: 99.5, size: 1, timestamp: 1 })
|
|
80
|
+
book.applyDelta({ side: 'bid', price: 100.0, size: 1, timestamp: 1 })
|
|
81
|
+
book.applyDelta({ side: 'bid', price: 99.8, size: 1, timestamp: 1 })
|
|
82
|
+
book.applyDelta({ side: 'ask', price: 101.0, size: 1, timestamp: 1 })
|
|
83
|
+
book.applyDelta({ side: 'ask', price: 100.2, size: 1, timestamp: 1 })
|
|
84
|
+
book.applyDelta({ side: 'ask', price: 100.5, size: 1, timestamp: 1 })
|
|
85
|
+
const snap = book.snapshot()
|
|
86
|
+
expect(snap.bids.map((b) => b[0])).toEqual([100.0, 99.8, 99.5])
|
|
87
|
+
expect(snap.asks.map((a) => a[0])).toEqual([100.2, 100.5, 101.0])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('respects maxLevels by truncating to top-of-book first', () => {
|
|
91
|
+
const book = createOrderBookState({ tickSize: 0.01, maxLevels: 2 })
|
|
92
|
+
book.applyDelta({ side: 'bid', price: 99.5, size: 1, timestamp: 1 })
|
|
93
|
+
book.applyDelta({ side: 'bid', price: 100.0, size: 1, timestamp: 1 })
|
|
94
|
+
book.applyDelta({ side: 'bid', price: 99.8, size: 1, timestamp: 1 })
|
|
95
|
+
const snap = book.snapshot()
|
|
96
|
+
expect(snap.bids.map((b) => b[0])).toEqual([100.0, 99.8])
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('rejects non-positive tickSize at construction', () => {
|
|
100
|
+
expect(() => createOrderBookState({ tickSize: 0 })).toThrow()
|
|
101
|
+
expect(() => createOrderBookState({ tickSize: -1 })).toThrow()
|
|
102
|
+
expect(() => createOrderBookState({ tickSize: Infinity })).toThrow()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('ignores NaN / Infinity deltas without corrupting state', () => {
|
|
106
|
+
const book = createOrderBookState({ tickSize: 0.01 })
|
|
107
|
+
book.applyDelta({ side: 'bid', price: NaN, size: 1, timestamp: 1 })
|
|
108
|
+
book.applyDelta({ side: 'bid', price: 100, size: NaN, timestamp: 2 })
|
|
109
|
+
book.applyDelta({ side: 'bid', price: 100, size: 1, timestamp: 3 })
|
|
110
|
+
const snap = book.snapshot()
|
|
111
|
+
expect(snap.bids).toEqual([[100, 1]])
|
|
112
|
+
})
|
|
113
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createSnapshotRing } from '../snapshotRing'
|
|
4
|
+
import type { BookSnapshot } from '../types'
|
|
5
|
+
|
|
6
|
+
function makeSnap(ts: number): BookSnapshot {
|
|
7
|
+
return { bids: [], asks: [], timestamp: ts }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('createSnapshotRing', () => {
|
|
11
|
+
it('respects the declared capacity and reports size accurately', () => {
|
|
12
|
+
const ring = createSnapshotRing(3)
|
|
13
|
+
expect(ring.capacity).toBe(3)
|
|
14
|
+
expect(ring.size()).toBe(0)
|
|
15
|
+
ring.push(makeSnap(1))
|
|
16
|
+
ring.push(makeSnap(2))
|
|
17
|
+
expect(ring.size()).toBe(2)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('drops the oldest entry when pushed past capacity', () => {
|
|
21
|
+
const ring = createSnapshotRing(3)
|
|
22
|
+
ring.push(makeSnap(1))
|
|
23
|
+
ring.push(makeSnap(2))
|
|
24
|
+
ring.push(makeSnap(3))
|
|
25
|
+
ring.push(makeSnap(4))
|
|
26
|
+
const out = ring.toArray()
|
|
27
|
+
expect(out.map((s) => s.timestamp)).toEqual([2, 3, 4])
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('toArray returns snapshots oldest → newest, even after wrap-around', () => {
|
|
31
|
+
const ring = createSnapshotRing(4)
|
|
32
|
+
for (let i = 1; i <= 10; i++) ring.push(makeSnap(i))
|
|
33
|
+
const out = ring.toArray()
|
|
34
|
+
expect(out.map((s) => s.timestamp)).toEqual([7, 8, 9, 10])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('clear() empties the buffer without affecting capacity', () => {
|
|
38
|
+
const ring = createSnapshotRing(2)
|
|
39
|
+
ring.push(makeSnap(1))
|
|
40
|
+
ring.push(makeSnap(2))
|
|
41
|
+
ring.clear()
|
|
42
|
+
expect(ring.size()).toBe(0)
|
|
43
|
+
expect(ring.toArray()).toEqual([])
|
|
44
|
+
ring.push(makeSnap(3))
|
|
45
|
+
expect(ring.toArray().map((s) => s.timestamp)).toEqual([3])
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('rejects non-positive capacity', () => {
|
|
49
|
+
expect(() => createSnapshotRing(0)).toThrow()
|
|
50
|
+
expect(() => createSnapshotRing(-5)).toThrow()
|
|
51
|
+
expect(() => createSnapshotRing(1.5)).toThrow()
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Order Book Heatmap — WGSL Render Path Spec
|
|
2
|
+
|
|
3
|
+
This document specifies the GPU side of the heatmap. **No `.wgsl` source ships
|
|
4
|
+
with this commit** — the JS controller in this module produces the snapshot
|
|
5
|
+
ring; the renderer (P1 deliverable) will translate the spec below into a real
|
|
6
|
+
pipeline once the WebGPU surface lands. The spec is authoritative for that
|
|
7
|
+
work.
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
|
|
11
|
+
### `snapshotRing` — storage buffer (read-only in shaders)
|
|
12
|
+
|
|
13
|
+
Logically a 2-D grid `intensity[col][row]` where:
|
|
14
|
+
|
|
15
|
+
- `col` ∈ `[0, snapshotRingCapacity)` — time axis (oldest → newest)
|
|
16
|
+
- `row` ∈ `[0, priceRowCount)` — price axis (low → high)
|
|
17
|
+
|
|
18
|
+
CPU-side layout (one upload per render frame for simplicity in v0):
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
struct SnapshotCell {
|
|
22
|
+
intensity: f32, // log-mapped, already in [0, 1]
|
|
23
|
+
};
|
|
24
|
+
@group(0) @binding(0) var<storage, read> ring: array<SnapshotCell>;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Index conversion: `cellIdx = col * priceRowCount + row`.
|
|
28
|
+
|
|
29
|
+
Empty levels (no resting size at that price/time) are written as `0.0`. The
|
|
30
|
+
fragment shader treats `0.0` as fully transparent.
|
|
31
|
+
|
|
32
|
+
### `viridisLUT` — 256-entry uniform color table
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
@group(0) @binding(1) var<uniform> viridisLUT: array<vec4<f32>, 256>;
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`viridis` is the perceptually-uniform colormap from `d3-scale-chromatic`. Each
|
|
39
|
+
entry is `vec4<f32>(r, g, b, 1.0)` with channels in `[0, 1]` and gamma already
|
|
40
|
+
applied. The CPU sends it once at controller init and on theme change.
|
|
41
|
+
|
|
42
|
+
### `params` — uniform block
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
struct HeatmapParams {
|
|
46
|
+
priceRowCount: u32,
|
|
47
|
+
snapshotColumnCount: u32,
|
|
48
|
+
// Scroll offset: when the ring buffer wraps, this is the col where the
|
|
49
|
+
// oldest sample lives. The fragment shader rotates around this.
|
|
50
|
+
ringHead: u32,
|
|
51
|
+
// Range used by the CPU when log-mapping; replicated here so the shader
|
|
52
|
+
// can re-map should we move log mapping onto the GPU later (see §4).
|
|
53
|
+
sizeMinLog: f32,
|
|
54
|
+
sizeMaxLog: f32,
|
|
55
|
+
_pad: f32,
|
|
56
|
+
};
|
|
57
|
+
@group(0) @binding(2) var<uniform> params: HeatmapParams;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Fragment shader
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
@fragment
|
|
64
|
+
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
|
65
|
+
// uv.x: 0 (left, oldest) → 1 (right, newest)
|
|
66
|
+
// uv.y: 0 (bottom, lowest price) → 1 (top, highest price)
|
|
67
|
+
let col = u32(floor(uv.x * f32(params.snapshotColumnCount)));
|
|
68
|
+
let row = u32(floor(uv.y * f32(params.priceRowCount)));
|
|
69
|
+
let rotated = (col + params.ringHead) % params.snapshotColumnCount;
|
|
70
|
+
let idx = rotated * params.priceRowCount + row;
|
|
71
|
+
let i = ring[idx].intensity;
|
|
72
|
+
if (i <= 0.0) {
|
|
73
|
+
return vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
|
74
|
+
}
|
|
75
|
+
let lutIdx = u32(clamp(i * 255.0, 0.0, 255.0));
|
|
76
|
+
return viridisLUT[lutIdx];
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Trade-off — full re-upload vs scrolling write
|
|
81
|
+
|
|
82
|
+
There are two reasonable upload strategies. The CPU controller currently
|
|
83
|
+
produces the data shape for **either**; the renderer picks at integration time.
|
|
84
|
+
|
|
85
|
+
### Option A — full re-upload every frame
|
|
86
|
+
|
|
87
|
+
Each render frame the CPU calls `snapshotRing.toArray()` and writes the whole
|
|
88
|
+
buffer. Simple, no GPU-side state, fast on small rings.
|
|
89
|
+
|
|
90
|
+
- Cost: `O(N · R · sizeof(SnapshotCell))` per frame
|
|
91
|
+
(N = ring capacity, R = price rows). For N = 600, R = 400, 4 B per cell
|
|
92
|
+
that's ~960 KB per frame — fine at 60 fps on any GPU we target.
|
|
93
|
+
- Pros: stateless, robust to seeking/replay, code is short.
|
|
94
|
+
- Cons: redundant uploads for the columns that didn't change.
|
|
95
|
+
|
|
96
|
+
### Option B — scrolling buffer with single-column write
|
|
97
|
+
|
|
98
|
+
Treat the storage buffer as a true ring. On each new snapshot:
|
|
99
|
+
|
|
100
|
+
1. CPU writes one column at offset `ringHead * R` (only the new data).
|
|
101
|
+
2. CPU bumps `params.ringHead = (ringHead + 1) % N`.
|
|
102
|
+
|
|
103
|
+
- Cost: `O(R · sizeof(SnapshotCell))` per snapshot.
|
|
104
|
+
- Pros: minimal bandwidth, scales to N >> 600.
|
|
105
|
+
- Cons: needs careful synchronization with frame timing; the fragment shader
|
|
106
|
+
already accounts for `ringHead` rotation (see snippet above).
|
|
107
|
+
|
|
108
|
+
**Recommendation**: start with A in v0 (correct + easy to validate); migrate
|
|
109
|
+
to B once the renderer is on WebGPU and we want to drive much wider time
|
|
110
|
+
windows.
|
|
111
|
+
|
|
112
|
+
## Why CPU-side log mapping (today) and not GPU?
|
|
113
|
+
|
|
114
|
+
We do the log transform on CPU inside `logColorScale` because:
|
|
115
|
+
|
|
116
|
+
- The mapping is cheap (one `log` per cell, ~tens of µs per snapshot).
|
|
117
|
+
- Having the intensity already in `[0, 1]` keeps the fragment shader trivial
|
|
118
|
+
and avoids passing raw size ranges through every uniform.
|
|
119
|
+
|
|
120
|
+
Once we move to Option B and want to scrub a long history with dynamic range
|
|
121
|
+
recalibration, we can move log mapping onto the GPU using `sizeMinLog` /
|
|
122
|
+
`sizeMaxLog` from `HeatmapParams`. The shader code path is one extra `log`
|
|
123
|
+
call and a divide — no change to the buffer shape is required.
|
|
124
|
+
|
|
125
|
+
## Composition with depth chart / order book ladder
|
|
126
|
+
|
|
127
|
+
The same `ring` storage buffer is consumed by §3.4's depth chart, which only
|
|
128
|
+
needs the *current* column (`ringHead - 1 mod N`) and a different fragment
|
|
129
|
+
shader that does a cumulative-sum sweep. We deliberately avoid duplicating
|
|
130
|
+
state for the two visualizations.
|