@forgecharts/sdk 1.1.23 → 1.1.28
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/__tests__/backwardCompatibility.test.d.ts +14 -0
- package/dist/__tests__/backwardCompatibility.test.d.ts.map +1 -0
- package/dist/__tests__/backwardCompatibility.test.js +159 -0
- package/dist/__tests__/backwardCompatibility.test.js.map +1 -0
- package/dist/__tests__/candleInvariant.test.d.ts +20 -0
- package/dist/__tests__/candleInvariant.test.d.ts.map +1 -0
- package/dist/__tests__/candleInvariant.test.js +415 -0
- package/dist/__tests__/candleInvariant.test.js.map +1 -0
- package/dist/__tests__/public-api-surface.d.ts +13 -0
- package/dist/__tests__/public-api-surface.d.ts.map +1 -0
- package/{src/__tests__/public-api-surface.ts → dist/__tests__/public-api-surface.js} +4 -42
- package/dist/__tests__/public-api-surface.js.map +1 -0
- package/dist/__tests__/timeframeBoundary.test.d.ts +17 -0
- package/dist/__tests__/timeframeBoundary.test.d.ts.map +1 -0
- package/dist/__tests__/timeframeBoundary.test.js +452 -0
- package/dist/__tests__/timeframeBoundary.test.js.map +1 -0
- package/dist/api/DrawingManager.d.ts +20 -0
- package/dist/api/DrawingManager.d.ts.map +1 -0
- package/dist/api/DrawingManager.js +190 -0
- package/dist/api/DrawingManager.js.map +1 -0
- package/dist/api/EventBus.d.ts +19 -0
- package/dist/api/EventBus.d.ts.map +1 -0
- package/dist/api/EventBus.js +44 -0
- package/dist/api/EventBus.js.map +1 -0
- package/dist/api/IndicatorDAG.d.ts +85 -0
- package/dist/api/IndicatorDAG.d.ts.map +1 -0
- package/dist/api/IndicatorDAG.js +316 -0
- package/dist/api/IndicatorDAG.js.map +1 -0
- package/dist/api/IndicatorRegistry.d.ts +22 -0
- package/dist/api/IndicatorRegistry.d.ts.map +1 -0
- package/dist/api/IndicatorRegistry.js +39 -0
- package/dist/api/IndicatorRegistry.js.map +1 -0
- package/dist/api/LayoutManager.d.ts +30 -0
- package/dist/api/LayoutManager.d.ts.map +1 -0
- package/dist/api/LayoutManager.js +51 -0
- package/dist/api/LayoutManager.js.map +1 -0
- package/dist/api/PaneManager.d.ts +42 -0
- package/dist/api/PaneManager.d.ts.map +1 -0
- package/dist/api/PaneManager.js +119 -0
- package/dist/api/PaneManager.js.map +1 -0
- package/dist/api/ReferenceAPI.d.ts +78 -0
- package/dist/api/ReferenceAPI.d.ts.map +1 -0
- package/dist/api/ReferenceAPI.js +153 -0
- package/dist/api/ReferenceAPI.js.map +1 -0
- package/dist/api/TChart.d.ts +345 -0
- package/dist/api/TChart.d.ts.map +1 -0
- package/dist/api/TChart.js +765 -0
- package/dist/api/TChart.js.map +1 -0
- package/{src/api/createChart.ts → dist/api/createChart.d.ts} +2 -7
- package/dist/api/createChart.d.ts.map +1 -0
- package/dist/api/createChart.js +42 -0
- package/dist/api/createChart.js.map +1 -0
- package/dist/api/drawing tools/fib gann menu/fibRetracement.d.ts +4 -0
- package/dist/api/drawing tools/fib gann menu/fibRetracement.d.ts.map +1 -0
- package/dist/api/drawing tools/fib gann menu/fibRetracement.js +22 -0
- package/dist/api/drawing tools/fib gann menu/fibRetracement.js.map +1 -0
- package/dist/api/drawing tools/lines menu/crossLine.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/crossLine.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/crossLine.js +16 -0
- package/dist/api/drawing tools/lines menu/crossLine.js.map +1 -0
- package/dist/api/drawing tools/lines menu/disjointChannel.d.ts +17 -0
- package/dist/api/drawing tools/lines menu/disjointChannel.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/disjointChannel.js +59 -0
- package/dist/api/drawing tools/lines menu/disjointChannel.js.map +1 -0
- package/dist/api/drawing tools/lines menu/extendedLine.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/extendedLine.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/extendedLine.js +17 -0
- package/dist/api/drawing tools/lines menu/extendedLine.js.map +1 -0
- package/dist/api/drawing tools/lines menu/flatTopBottom.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/flatTopBottom.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/flatTopBottom.js +41 -0
- package/dist/api/drawing tools/lines menu/flatTopBottom.js.map +1 -0
- package/dist/api/drawing tools/lines menu/horizontal.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/horizontal.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/horizontal.js +19 -0
- package/dist/api/drawing tools/lines menu/horizontal.js.map +1 -0
- package/dist/api/drawing tools/lines menu/horizontalRay.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/horizontalRay.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/horizontalRay.js +20 -0
- package/dist/api/drawing tools/lines menu/horizontalRay.js.map +1 -0
- package/dist/api/drawing tools/lines menu/infoLine.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/infoLine.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/infoLine.js +107 -0
- package/dist/api/drawing tools/lines menu/infoLine.js.map +1 -0
- package/dist/api/drawing tools/lines menu/insidePitchfork.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/insidePitchfork.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/insidePitchfork.js +31 -0
- package/dist/api/drawing tools/lines menu/insidePitchfork.js.map +1 -0
- package/dist/api/drawing tools/lines menu/modifiedSchiffPitchfork.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/modifiedSchiffPitchfork.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/modifiedSchiffPitchfork.js +15 -0
- package/dist/api/drawing tools/lines menu/modifiedSchiffPitchfork.js.map +1 -0
- package/dist/api/drawing tools/lines menu/parallelChannel.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/parallelChannel.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/parallelChannel.js +43 -0
- package/dist/api/drawing tools/lines menu/parallelChannel.js.map +1 -0
- package/dist/api/drawing tools/lines menu/pitchfork.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/pitchfork.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/pitchfork.js +12 -0
- package/dist/api/drawing tools/lines menu/pitchfork.js.map +1 -0
- package/dist/api/drawing tools/lines menu/ray.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/ray.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/ray.js +23 -0
- package/dist/api/drawing tools/lines menu/ray.js.map +1 -0
- package/dist/api/drawing tools/lines menu/regressionTrend.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/regressionTrend.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/regressionTrend.js +127 -0
- package/dist/api/drawing tools/lines menu/regressionTrend.js.map +1 -0
- package/dist/api/drawing tools/lines menu/schiffPitchfork.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/schiffPitchfork.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/schiffPitchfork.js +15 -0
- package/dist/api/drawing tools/lines menu/schiffPitchfork.js.map +1 -0
- package/dist/api/drawing tools/lines menu/trendAngle.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/trendAngle.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/trendAngle.js +51 -0
- package/dist/api/drawing tools/lines menu/trendAngle.js.map +1 -0
- package/dist/api/drawing tools/lines menu/trendline.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/trendline.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/trendline.js +11 -0
- package/dist/api/drawing tools/lines menu/trendline.js.map +1 -0
- package/dist/api/drawing tools/lines menu/vertical.d.ts +4 -0
- package/dist/api/drawing tools/lines menu/vertical.d.ts.map +1 -0
- package/dist/api/drawing tools/lines menu/vertical.js +11 -0
- package/dist/api/drawing tools/lines menu/vertical.js.map +1 -0
- package/dist/api/drawing tools/pointers menu/crosshair.d.ts +16 -0
- package/dist/api/drawing tools/pointers menu/crosshair.d.ts.map +1 -0
- package/{src/api/drawing tools/pointers menu/crosshair.ts → dist/api/drawing tools/pointers menu/crosshair.js } +6 -7
- package/dist/api/drawing tools/pointers menu/crosshair.js.map +1 -0
- package/dist/api/drawing tools/pointers menu/cursor.d.ts +15 -0
- package/dist/api/drawing tools/pointers menu/cursor.d.ts.map +1 -0
- package/{src/api/drawing tools/pointers menu/cursor.ts → dist/api/drawing tools/pointers menu/cursor.js } +7 -8
- package/dist/api/drawing tools/pointers menu/cursor.js.map +1 -0
- package/dist/api/drawing tools/pointers menu/demonstration.d.ts +30 -0
- package/dist/api/drawing tools/pointers menu/demonstration.d.ts.map +1 -0
- package/{src/api/drawing tools/pointers menu/demonstration.ts → dist/api/drawing tools/pointers menu/demonstration.js } +9 -14
- package/dist/api/drawing tools/pointers menu/demonstration.js.map +1 -0
- package/dist/api/drawing tools/pointers menu/dot.d.ts +23 -0
- package/dist/api/drawing tools/pointers menu/dot.d.ts.map +1 -0
- package/{src/api/drawing tools/pointers menu/dot.ts → dist/api/drawing tools/pointers menu/dot.js } +8 -11
- package/dist/api/drawing tools/pointers menu/dot.js.map +1 -0
- package/dist/api/drawing tools/shapes menu/rectangle.d.ts +4 -0
- package/dist/api/drawing tools/shapes menu/rectangle.d.ts.map +1 -0
- package/dist/api/drawing tools/shapes menu/rectangle.js +19 -0
- package/dist/api/drawing tools/shapes menu/rectangle.js.map +1 -0
- package/dist/api/drawing tools/shapes menu/text.d.ts +4 -0
- package/dist/api/drawing tools/shapes menu/text.d.ts.map +1 -0
- package/dist/api/drawing tools/shapes menu/text.js +25 -0
- package/dist/api/drawing tools/shapes menu/text.js.map +1 -0
- package/dist/api/drawingUtils.d.ts +22 -0
- package/dist/api/drawingUtils.d.ts.map +1 -0
- package/dist/api/drawingUtils.js +83 -0
- package/dist/api/drawingUtils.js.map +1 -0
- package/dist/core/CanvasLayer.d.ts +26 -0
- package/dist/core/CanvasLayer.d.ts.map +1 -0
- package/dist/core/CanvasLayer.js +56 -0
- package/dist/core/CanvasLayer.js.map +1 -0
- package/dist/core/Chart.d.ts +164 -0
- package/dist/core/Chart.d.ts.map +1 -0
- package/dist/core/Chart.js +839 -0
- package/dist/core/Chart.js.map +1 -0
- package/dist/core/CoordTransform.d.ts +168 -0
- package/dist/core/CoordTransform.d.ts.map +1 -0
- package/dist/core/CoordTransform.js +224 -0
- package/dist/core/CoordTransform.js.map +1 -0
- package/dist/core/Crosshair.d.ts +30 -0
- package/dist/core/Crosshair.d.ts.map +1 -0
- package/dist/core/Crosshair.js +186 -0
- package/dist/core/Crosshair.js.map +1 -0
- package/dist/core/IndicatorEngine.d.ts +51 -0
- package/dist/core/IndicatorEngine.d.ts.map +1 -0
- package/dist/core/IndicatorEngine.js +181 -0
- package/dist/core/IndicatorEngine.js.map +1 -0
- package/dist/core/InteractionManager.d.ts +197 -0
- package/dist/core/InteractionManager.d.ts.map +1 -0
- package/dist/core/InteractionManager.js +698 -0
- package/dist/core/InteractionManager.js.map +1 -0
- package/dist/core/PriceScale.d.ts +27 -0
- package/dist/core/PriceScale.d.ts.map +1 -0
- package/dist/core/PriceScale.js +113 -0
- package/dist/core/PriceScale.js.map +1 -0
- package/dist/core/Series.d.ts +40 -0
- package/dist/core/Series.d.ts.map +1 -0
- package/dist/core/Series.js +114 -0
- package/dist/core/Series.js.map +1 -0
- package/dist/core/TimeScale.d.ts +43 -0
- package/dist/core/TimeScale.d.ts.map +1 -0
- package/dist/core/TimeScale.js +150 -0
- package/dist/core/TimeScale.js.map +1 -0
- package/dist/datafeed/DatafeedConnector.d.ts +89 -0
- package/dist/datafeed/DatafeedConnector.d.ts.map +1 -0
- package/dist/datafeed/DatafeedConnector.js +268 -0
- package/dist/datafeed/DatafeedConnector.js.map +1 -0
- package/dist/engine/CandleEngine.d.ts +207 -0
- package/dist/engine/CandleEngine.d.ts.map +1 -0
- package/dist/engine/CandleEngine.js +318 -0
- package/dist/engine/CandleEngine.js.map +1 -0
- package/dist/engine/__tests__/CandleEngine.test.d.ts +2 -0
- package/dist/engine/__tests__/CandleEngine.test.d.ts.map +1 -0
- package/dist/engine/__tests__/CandleEngine.test.js +300 -0
- package/dist/engine/__tests__/CandleEngine.test.js.map +1 -0
- package/dist/engine/candleInvariants.d.ts +66 -0
- package/dist/engine/candleInvariants.d.ts.map +1 -0
- package/dist/engine/candleInvariants.js +134 -0
- package/dist/engine/candleInvariants.js.map +1 -0
- package/{src/engine/mergeUtils.ts → dist/engine/mergeUtils.d.ts} +15 -52
- package/dist/engine/mergeUtils.d.ts.map +1 -0
- package/dist/engine/mergeUtils.js +64 -0
- package/dist/engine/mergeUtils.js.map +1 -0
- package/dist/engine/timeframeUtils.d.ts +80 -0
- package/dist/engine/timeframeUtils.d.ts.map +1 -0
- package/{src/engine/timeframeUtils.ts → dist/engine/timeframeUtils.js} +30 -48
- package/dist/engine/timeframeUtils.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +29 -0
- package/dist/internal.d.ts.map +1 -0
- package/{src/internal.ts → dist/internal.js} +2 -11
- package/dist/internal.js.map +1 -0
- package/dist/licensing/ChartRuntimeResolver.d.ts +233 -0
- package/dist/licensing/ChartRuntimeResolver.d.ts.map +1 -0
- package/dist/licensing/ChartRuntimeResolver.js +310 -0
- package/dist/licensing/ChartRuntimeResolver.js.map +1 -0
- package/dist/licensing/LicenseManager.d.ts +55 -0
- package/dist/licensing/LicenseManager.d.ts.map +1 -0
- package/dist/licensing/LicenseManager.js +114 -0
- package/dist/licensing/LicenseManager.js.map +1 -0
- package/dist/licensing/__tests__/ChartRuntimeResolver.test.d.ts +13 -0
- package/dist/licensing/__tests__/ChartRuntimeResolver.test.d.ts.map +1 -0
- package/dist/licensing/__tests__/ChartRuntimeResolver.test.js +177 -0
- package/dist/licensing/__tests__/ChartRuntimeResolver.test.js.map +1 -0
- package/dist/licensing/__tests__/LicenseManager.test.d.ts +12 -0
- package/dist/licensing/__tests__/LicenseManager.test.d.ts.map +1 -0
- package/dist/licensing/__tests__/LicenseManager.test.js +153 -0
- package/dist/licensing/__tests__/LicenseManager.test.js.map +1 -0
- package/dist/licensing/licenseTypes.d.ts +18 -0
- package/dist/licensing/licenseTypes.d.ts.map +1 -0
- package/dist/licensing/licenseTypes.js +2 -0
- package/dist/licensing/licenseTypes.js.map +1 -0
- package/dist/pine/PineCompiler.d.ts +35 -0
- package/dist/pine/PineCompiler.d.ts.map +1 -0
- package/dist/pine/PineCompiler.js +44 -0
- package/dist/pine/PineCompiler.js.map +1 -0
- package/dist/pine/diagnostics.d.ts +20 -0
- package/dist/pine/diagnostics.d.ts.map +1 -0
- package/dist/pine/diagnostics.js +11 -0
- package/dist/pine/diagnostics.js.map +1 -0
- package/{src/pine/index.ts → dist/pine/index.d.ts} +4 -3
- package/dist/pine/index.d.ts.map +1 -0
- package/dist/pine/index.js +5 -0
- package/dist/pine/index.js.map +1 -0
- package/dist/pine/pine-ast.d.ts +142 -0
- package/dist/pine/pine-ast.d.ts.map +1 -0
- package/dist/pine/pine-ast.js +19 -0
- package/dist/pine/pine-ast.js.map +1 -0
- package/dist/pine/pine-lexer.d.ts +41 -0
- package/dist/pine/pine-lexer.d.ts.map +1 -0
- package/dist/pine/pine-lexer.js +249 -0
- package/dist/pine/pine-lexer.js.map +1 -0
- package/dist/pine/pine-parser.d.ts +51 -0
- package/dist/pine/pine-parser.d.ts.map +1 -0
- package/dist/pine/pine-parser.js +416 -0
- package/dist/pine/pine-parser.js.map +1 -0
- package/dist/pine/pine-transpiler.d.ts +33 -0
- package/dist/pine/pine-transpiler.d.ts.map +1 -0
- package/dist/pine/pine-transpiler.js +260 -0
- package/dist/pine/pine-transpiler.js.map +1 -0
- package/dist/pixi/LayerName.d.ts +18 -0
- package/dist/pixi/LayerName.d.ts.map +1 -0
- package/dist/pixi/LayerName.js +35 -0
- package/dist/pixi/LayerName.js.map +1 -0
- package/dist/pixi/PixiCandlestickRenderer.d.ts +23 -0
- package/dist/pixi/PixiCandlestickRenderer.d.ts.map +1 -0
- package/dist/pixi/PixiCandlestickRenderer.js +107 -0
- package/dist/pixi/PixiCandlestickRenderer.js.map +1 -0
- package/dist/pixi/PixiChart.d.ts +72 -0
- package/dist/pixi/PixiChart.d.ts.map +1 -0
- package/dist/pixi/PixiChart.js +367 -0
- package/dist/pixi/PixiChart.js.map +1 -0
- package/dist/pixi/PixiCrosshairRenderer.d.ts +29 -0
- package/dist/pixi/PixiCrosshairRenderer.d.ts.map +1 -0
- package/dist/pixi/PixiCrosshairRenderer.js +110 -0
- package/dist/pixi/PixiCrosshairRenderer.js.map +1 -0
- package/dist/pixi/PixiDrawingRenderer.d.ts +17 -0
- package/dist/pixi/PixiDrawingRenderer.d.ts.map +1 -0
- package/dist/pixi/PixiDrawingRenderer.js +111 -0
- package/dist/pixi/PixiDrawingRenderer.js.map +1 -0
- package/dist/pixi/PixiGridRenderer.d.ts +22 -0
- package/dist/pixi/PixiGridRenderer.d.ts.map +1 -0
- package/dist/pixi/PixiGridRenderer.js +114 -0
- package/dist/pixi/PixiGridRenderer.js.map +1 -0
- package/dist/pixi/PixiLayerManager.d.ts +56 -0
- package/dist/pixi/PixiLayerManager.d.ts.map +1 -0
- package/dist/pixi/PixiLayerManager.js +92 -0
- package/dist/pixi/PixiLayerManager.js.map +1 -0
- package/dist/react/canvas/ChartCanvas.d.ts +85 -0
- package/dist/react/canvas/ChartCanvas.d.ts.map +1 -0
- package/dist/react/canvas/ChartCanvas.js +604 -0
- package/dist/react/canvas/ChartCanvas.js.map +1 -0
- package/dist/react/canvas/ChartContextMenu.d.ts +18 -0
- package/dist/react/canvas/ChartContextMenu.d.ts.map +1 -0
- package/dist/react/canvas/ChartContextMenu.js +5 -0
- package/dist/react/canvas/ChartContextMenu.js.map +1 -0
- package/dist/react/canvas/ChartSettingsDialog.d.ts +25 -0
- package/dist/react/canvas/ChartSettingsDialog.d.ts.map +1 -0
- package/dist/react/canvas/ChartSettingsDialog.js +28 -0
- package/dist/react/canvas/ChartSettingsDialog.js.map +1 -0
- package/dist/react/canvas/IndicatorLabel.d.ts +21 -0
- package/dist/react/canvas/IndicatorLabel.d.ts.map +1 -0
- package/dist/react/canvas/IndicatorLabel.js +196 -0
- package/dist/react/canvas/IndicatorLabel.js.map +1 -0
- package/dist/react/canvas/IndicatorPane.d.ts +32 -0
- package/dist/react/canvas/IndicatorPane.d.ts.map +1 -0
- package/dist/react/canvas/IndicatorPane.js +395 -0
- package/dist/react/canvas/IndicatorPane.js.map +1 -0
- package/dist/react/canvas/PointerOverlay.d.ts +23 -0
- package/dist/react/canvas/PointerOverlay.d.ts.map +1 -0
- package/dist/react/canvas/PointerOverlay.js +61 -0
- package/dist/react/canvas/PointerOverlay.js.map +1 -0
- package/dist/react/canvas/toolbars/LeftToolbar.d.ts +19 -0
- package/dist/react/canvas/toolbars/LeftToolbar.d.ts.map +1 -0
- package/dist/react/canvas/toolbars/LeftToolbar.js +407 -0
- package/dist/react/canvas/toolbars/LeftToolbar.js.map +1 -0
- package/dist/react/hooks/useChartCapabilities.d.ts +21 -0
- package/dist/react/hooks/useChartCapabilities.d.ts.map +1 -0
- package/dist/react/hooks/useChartCapabilities.js +66 -0
- package/dist/react/hooks/useChartCapabilities.js.map +1 -0
- package/dist/react/index.d.ts +30 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +28 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/internal.d.ts +38 -0
- package/dist/react/internal.d.ts.map +1 -0
- package/dist/react/internal.js +44 -0
- package/dist/react/internal.js.map +1 -0
- package/dist/react/shell/ManagedAppShell.d.ts +91 -0
- package/dist/react/shell/ManagedAppShell.d.ts.map +1 -0
- package/dist/react/shell/ManagedAppShell.js +440 -0
- package/dist/react/shell/ManagedAppShell.js.map +1 -0
- package/dist/react/trading/TradingBridge.d.ts +86 -0
- package/dist/react/trading/TradingBridge.d.ts.map +1 -0
- package/dist/react/trading/TradingBridge.js +73 -0
- package/dist/react/trading/TradingBridge.js.map +1 -0
- package/dist/react/workspace/ChartWorkspace.d.ts +73 -0
- package/dist/react/workspace/ChartWorkspace.d.ts.map +1 -0
- package/dist/react/workspace/ChartWorkspace.js +42 -0
- package/dist/react/workspace/ChartWorkspace.js.map +1 -0
- package/dist/react/workspace/FloatingPanel.d.ts +18 -0
- package/dist/react/workspace/FloatingPanel.d.ts.map +1 -0
- package/dist/react/workspace/FloatingPanel.js +82 -0
- package/dist/react/workspace/FloatingPanel.js.map +1 -0
- package/dist/react/workspace/IndicatorsDialog.d.ts +8 -0
- package/dist/react/workspace/IndicatorsDialog.d.ts.map +1 -0
- package/dist/react/workspace/IndicatorsDialog.js +121 -0
- package/dist/react/workspace/IndicatorsDialog.js.map +1 -0
- package/dist/react/workspace/LayoutMenu.d.ts +33 -0
- package/dist/react/workspace/LayoutMenu.d.ts.map +1 -0
- package/dist/react/workspace/LayoutMenu.js +113 -0
- package/dist/react/workspace/LayoutMenu.js.map +1 -0
- package/dist/react/workspace/SymbolSearchDialog.d.ts +10 -0
- package/dist/react/workspace/SymbolSearchDialog.d.ts.map +1 -0
- package/dist/react/workspace/SymbolSearchDialog.js +245 -0
- package/dist/react/workspace/SymbolSearchDialog.js.map +1 -0
- package/dist/react/workspace/TabBar.d.ts +17 -0
- package/dist/react/workspace/TabBar.d.ts.map +1 -0
- package/dist/react/workspace/TabBar.js +29 -0
- package/dist/react/workspace/TabBar.js.map +1 -0
- package/dist/react/workspace/toolbars/BottomToolbar.d.ts +19 -0
- package/dist/react/workspace/toolbars/BottomToolbar.d.ts.map +1 -0
- package/dist/react/workspace/toolbars/BottomToolbar.js +236 -0
- package/dist/react/workspace/toolbars/BottomToolbar.js.map +1 -0
- package/dist/react/workspace/toolbars/RightToolbar.d.ts +8 -0
- package/dist/react/workspace/toolbars/RightToolbar.d.ts.map +1 -0
- package/dist/react/workspace/toolbars/RightToolbar.js +18 -0
- package/dist/react/workspace/toolbars/RightToolbar.js.map +1 -0
- package/dist/react/workspace/toolbars/TopToolbar.d.ts +41 -0
- package/dist/react/workspace/toolbars/TopToolbar.d.ts.map +1 -0
- package/dist/react/workspace/toolbars/TopToolbar.js +82 -0
- package/dist/react/workspace/toolbars/TopToolbar.js.map +1 -0
- package/dist/renderers/CandlestickRenderer.d.ts +13 -0
- package/dist/renderers/CandlestickRenderer.d.ts.map +1 -0
- package/dist/renderers/CandlestickRenderer.js +98 -0
- package/dist/renderers/CandlestickRenderer.js.map +1 -0
- package/dist/renderers/HistogramRenderer.d.ts +11 -0
- package/dist/renderers/HistogramRenderer.d.ts.map +1 -0
- package/dist/renderers/HistogramRenderer.js +50 -0
- package/dist/renderers/HistogramRenderer.js.map +1 -0
- package/dist/renderers/LineRenderer.d.ts +12 -0
- package/dist/renderers/LineRenderer.d.ts.map +1 -0
- package/dist/renderers/LineRenderer.js +64 -0
- package/dist/renderers/LineRenderer.js.map +1 -0
- package/dist/theme/colors.d.ts +4 -0
- package/dist/theme/colors.d.ts.map +1 -0
- package/dist/theme/colors.js +19 -0
- package/dist/theme/colors.js.map +1 -0
- package/dist/tools/barDivergenceCheck.d.ts +120 -0
- package/dist/tools/barDivergenceCheck.d.ts.map +1 -0
- package/dist/tools/barDivergenceCheck.js +200 -0
- package/dist/tools/barDivergenceCheck.js.map +1 -0
- package/dist/trading/TradingOverlayStore.d.ts +86 -0
- package/dist/trading/TradingOverlayStore.d.ts.map +1 -0
- package/dist/trading/TradingOverlayStore.js +139 -0
- package/dist/trading/TradingOverlayStore.js.map +1 -0
- package/dist/trading/UnmanagedIngestion.d.ts +91 -0
- package/dist/trading/UnmanagedIngestion.d.ts.map +1 -0
- package/dist/trading/UnmanagedIngestion.js +114 -0
- package/dist/trading/UnmanagedIngestion.js.map +1 -0
- package/dist/trading/__tests__/ManagedTradingController.test.d.ts +18 -0
- package/dist/trading/__tests__/ManagedTradingController.test.d.ts.map +1 -0
- package/dist/trading/__tests__/ManagedTradingController.test.js +271 -0
- package/dist/trading/__tests__/ManagedTradingController.test.js.map +1 -0
- package/dist/trading/__tests__/TradingOverlayStore.test.d.ts +16 -0
- package/dist/trading/__tests__/TradingOverlayStore.test.d.ts.map +1 -0
- package/dist/trading/__tests__/TradingOverlayStore.test.js +267 -0
- package/dist/trading/__tests__/TradingOverlayStore.test.js.map +1 -0
- package/dist/trading/__tests__/UnmanagedIngestion.test.d.ts +16 -0
- package/dist/trading/__tests__/UnmanagedIngestion.test.d.ts.map +1 -0
- package/dist/trading/__tests__/UnmanagedIngestion.test.js +170 -0
- package/dist/trading/__tests__/UnmanagedIngestion.test.js.map +1 -0
- package/dist/trading/managed/ManagedTradingController.d.ts +110 -0
- package/dist/trading/managed/ManagedTradingController.d.ts.map +1 -0
- package/dist/trading/managed/ManagedTradingController.js +247 -0
- package/dist/trading/managed/ManagedTradingController.js.map +1 -0
- package/dist/trading/managed/managedCapabilities.d.ts +45 -0
- package/dist/trading/managed/managedCapabilities.d.ts.map +1 -0
- package/dist/trading/managed/managedCapabilities.js +79 -0
- package/dist/trading/managed/managedCapabilities.js.map +1 -0
- package/dist/trading/managed/managedTypes.d.ts +122 -0
- package/dist/trading/managed/managedTypes.d.ts.map +1 -0
- package/dist/trading/managed/managedTypes.js +13 -0
- package/dist/trading/managed/managedTypes.js.map +1 -0
- package/dist/trading/tradingTypes.d.ts +89 -0
- package/dist/trading/tradingTypes.d.ts.map +1 -0
- package/dist/trading/tradingTypes.js +8 -0
- package/dist/trading/tradingTypes.js.map +1 -0
- package/dist/tscript/TScriptIndicator.d.ts +41 -0
- package/dist/tscript/TScriptIndicator.d.ts.map +1 -0
- package/dist/tscript/TScriptIndicator.js +47 -0
- package/dist/tscript/TScriptIndicator.js.map +1 -0
- package/dist/tscript/ast.d.ts +89 -0
- package/dist/tscript/ast.d.ts.map +1 -0
- package/dist/tscript/ast.js +22 -0
- package/dist/tscript/ast.js.map +1 -0
- package/dist/tscript/lexer.d.ts +36 -0
- package/dist/tscript/lexer.d.ts.map +1 -0
- package/dist/tscript/lexer.js +187 -0
- package/dist/tscript/lexer.js.map +1 -0
- package/dist/tscript/parser.d.ts +50 -0
- package/dist/tscript/parser.d.ts.map +1 -0
- package/dist/tscript/parser.js +318 -0
- package/dist/tscript/parser.js.map +1 -0
- package/dist/tscript/runtime.d.ts +123 -0
- package/dist/tscript/runtime.d.ts.map +1 -0
- package/dist/tscript/runtime.js +475 -0
- package/dist/tscript/runtime.js.map +1 -0
- package/dist/tscript/series.d.ts +49 -0
- package/dist/tscript/series.d.ts.map +1 -0
- package/dist/tscript/series.js +79 -0
- package/dist/tscript/series.js.map +1 -0
- package/dist/types/IChart.d.ts +48 -0
- package/dist/types/IChart.d.ts.map +1 -0
- package/dist/types/IChart.js +2 -0
- package/dist/types/IChart.js.map +1 -0
- package/{src/types/IRenderer.ts → dist/types/IRenderer.d.ts} +2 -8
- package/dist/types/IRenderer.d.ts.map +1 -0
- package/dist/types/IRenderer.js +2 -0
- package/dist/types/IRenderer.js.map +1 -0
- package/dist/types/ISeries.d.ts +26 -0
- package/dist/types/ISeries.d.ts.map +1 -0
- package/dist/types/ISeries.js +2 -0
- package/dist/types/ISeries.js.map +1 -0
- package/package.json +32 -4
- package/src/__tests__/backwardCompatibility.test.ts +0 -191
- package/src/__tests__/candleInvariant.test.ts +0 -500
- package/src/__tests__/timeframeBoundary.test.ts +0 -583
- package/src/api/DrawingManager.ts +0 -188
- package/src/api/EventBus.ts +0 -53
- package/src/api/IndicatorDAG.ts +0 -389
- package/src/api/IndicatorRegistry.ts +0 -47
- package/src/api/LayoutManager.ts +0 -72
- package/src/api/PaneManager.ts +0 -129
- package/src/api/ReferenceAPI.ts +0 -195
- package/src/api/TChart.ts +0 -881
- package/src/api/drawing tools/fib gann menu/fibRetracement.ts +0 -27
- package/src/api/drawing tools/lines menu/crossLine.ts +0 -21
- package/src/api/drawing tools/lines menu/disjointChannel.ts +0 -74
- package/src/api/drawing tools/lines menu/extendedLine.ts +0 -22
- package/src/api/drawing tools/lines menu/flatTopBottom.ts +0 -45
- package/src/api/drawing tools/lines menu/horizontal.ts +0 -24
- package/src/api/drawing tools/lines menu/horizontalRay.ts +0 -25
- package/src/api/drawing tools/lines menu/infoLine.ts +0 -127
- package/src/api/drawing tools/lines menu/insidePitchfork.ts +0 -21
- package/src/api/drawing tools/lines menu/modifiedSchiffPitchfork.ts +0 -18
- package/src/api/drawing tools/lines menu/parallelChannel.ts +0 -47
- package/src/api/drawing tools/lines menu/pitchfork.ts +0 -15
- package/src/api/drawing tools/lines menu/ray.ts +0 -28
- package/src/api/drawing tools/lines menu/regressionTrend.ts +0 -157
- package/src/api/drawing tools/lines menu/schiffPitchfork.ts +0 -18
- package/src/api/drawing tools/lines menu/trendAngle.ts +0 -64
- package/src/api/drawing tools/lines menu/trendline.ts +0 -16
- package/src/api/drawing tools/lines menu/vertical.ts +0 -16
- package/src/api/drawing tools/shapes menu/rectangle.ts +0 -24
- package/src/api/drawing tools/shapes menu/text.ts +0 -30
- package/src/api/drawingUtils.ts +0 -82
- package/src/core/CanvasLayer.ts +0 -77
- package/src/core/Chart.ts +0 -917
- package/src/core/CoordTransform.ts +0 -282
- package/src/core/Crosshair.ts +0 -207
- package/src/core/IndicatorEngine.ts +0 -216
- package/src/core/InteractionManager.ts +0 -899
- package/src/core/PriceScale.ts +0 -133
- package/src/core/Series.ts +0 -132
- package/src/core/TimeScale.ts +0 -175
- package/src/datafeed/DatafeedConnector.ts +0 -300
- package/src/engine/CandleEngine.ts +0 -458
- package/src/engine/__tests__/CandleEngine.test.ts +0 -402
- package/src/engine/candleInvariants.ts +0 -172
- package/src/index.ts +0 -190
- package/src/licensing/ChartRuntimeResolver.ts +0 -380
- package/src/licensing/LicenseManager.ts +0 -131
- package/src/licensing/__tests__/ChartRuntimeResolver.test.ts +0 -207
- package/src/licensing/__tests__/LicenseManager.test.ts +0 -180
- package/src/licensing/licenseTypes.ts +0 -19
- package/src/pine/PineCompiler.ts +0 -68
- package/src/pine/diagnostics.ts +0 -30
- package/src/pine/pine-ast.ts +0 -163
- package/src/pine/pine-lexer.ts +0 -265
- package/src/pine/pine-parser.ts +0 -439
- package/src/pine/pine-transpiler.ts +0 -301
- package/src/pixi/LayerName.ts +0 -35
- package/src/pixi/PixiCandlestickRenderer.ts +0 -125
- package/src/pixi/PixiChart.ts +0 -425
- package/src/pixi/PixiCrosshairRenderer.ts +0 -134
- package/src/pixi/PixiDrawingRenderer.ts +0 -121
- package/src/pixi/PixiGridRenderer.ts +0 -136
- package/src/pixi/PixiLayerManager.ts +0 -102
- package/src/renderers/CandlestickRenderer.ts +0 -130
- package/src/renderers/HistogramRenderer.ts +0 -63
- package/src/renderers/LineRenderer.ts +0 -77
- package/src/theme/colors.ts +0 -21
- package/src/tools/barDivergenceCheck.ts +0 -305
- package/src/trading/TradingOverlayStore.ts +0 -161
- package/src/trading/UnmanagedIngestion.ts +0 -156
- package/src/trading/__tests__/ManagedTradingController.test.ts +0 -338
- package/src/trading/__tests__/TradingOverlayStore.test.ts +0 -323
- package/src/trading/__tests__/UnmanagedIngestion.test.ts +0 -205
- package/src/trading/managed/ManagedTradingController.ts +0 -292
- package/src/trading/managed/managedCapabilities.ts +0 -98
- package/src/trading/managed/managedTypes.ts +0 -151
- package/src/trading/tradingTypes.ts +0 -135
- package/src/tscript/TScriptIndicator.ts +0 -54
- package/src/tscript/ast.ts +0 -105
- package/src/tscript/lexer.ts +0 -190
- package/src/tscript/parser.ts +0 -334
- package/src/tscript/runtime.ts +0 -525
- package/src/tscript/series.ts +0 -84
- package/src/types/IChart.ts +0 -56
- package/src/types/ISeries.ts +0 -30
- package/tsconfig.json +0 -22
- package/tsup.config.ts +0 -15
- package/vitest.config.ts +0 -25
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CandleEngine — the single source of truth for live candle data.
|
|
3
|
+
*
|
|
4
|
+
* Architecture:
|
|
5
|
+
*
|
|
6
|
+
* WebSocket feed
|
|
7
|
+
* ↓
|
|
8
|
+
* Market Data Normalizer (normalizeOhlcv — handles strings, ms vs s)
|
|
9
|
+
* ↓
|
|
10
|
+
* CandleEngine (applyCandleUpdate — O(1) update path)
|
|
11
|
+
* ↓
|
|
12
|
+
* Chart Update Adapter (onBarUpdated / onResync callbacks)
|
|
13
|
+
* ↓
|
|
14
|
+
* Chart Library (series.update / series.setData)
|
|
15
|
+
*
|
|
16
|
+
* Guarantees:
|
|
17
|
+
* - open is never overwritten on an existing live candle
|
|
18
|
+
* - high can only increase within a candle
|
|
19
|
+
* - low can only decrease within a candle
|
|
20
|
+
* - close updates on every tick
|
|
21
|
+
* - out-of-order ticks are silently discarded
|
|
22
|
+
* - gaps are detected and reported for async REST backfill
|
|
23
|
+
* - the engine is framework-agnostic (no DOM, React, or chart-lib imports)
|
|
24
|
+
*/
|
|
25
|
+
import { timeframeToMs, getCalendarBucketStart } from './timeframeUtils';
|
|
26
|
+
import { getMissingBarCount, mergeBars } from './mergeUtils';
|
|
27
|
+
import { assertOhlcRelationships, assertBucketAlignment, assertNotFinalized, assertOpenImmutability, assertNoDuplicateAppend, assertFinalizedUnchanged, } from './candleInvariants';
|
|
28
|
+
// ─── Internal normalizer ─────────────────────────────────────────────────────
|
|
29
|
+
/**
|
|
30
|
+
* Normalise a raw OHLCV object into a CandleBar.
|
|
31
|
+
*
|
|
32
|
+
* - Coerces all fields to `number` (handles numeric strings from the feed).
|
|
33
|
+
* - Auto-detects ms vs s timestamps: values > 1e12 are treated as milliseconds.
|
|
34
|
+
* - Aligns `timeMs` to the correct candle-open boundary, with calendar
|
|
35
|
+
* alignment for `1w` (Monday) and `1M` (first of month).
|
|
36
|
+
*
|
|
37
|
+
* Never use `Date.now()` for candle time — always use the feed-provided value.
|
|
38
|
+
*/
|
|
39
|
+
function normalizeOhlcv(raw, intervalMs, tf) {
|
|
40
|
+
const rawTime = Number(raw.time);
|
|
41
|
+
// Auto-detect: Unix ms timestamps are > 1e12 (year 2001+).
|
|
42
|
+
// If the value is smaller, treat it as seconds and convert.
|
|
43
|
+
const timeMs = rawTime > 1e12 ? rawTime : rawTime * 1000;
|
|
44
|
+
// Calendar-aware bucket alignment (handles 1w/1M specially)
|
|
45
|
+
const bucketMs = getCalendarBucketStart(timeMs, tf);
|
|
46
|
+
const timeSec = Math.floor(bucketMs / 1000);
|
|
47
|
+
return {
|
|
48
|
+
timeMs: bucketMs,
|
|
49
|
+
time: timeSec,
|
|
50
|
+
open: Number(raw.open),
|
|
51
|
+
high: Number(raw.high),
|
|
52
|
+
low: Number(raw.low),
|
|
53
|
+
close: Number(raw.close),
|
|
54
|
+
volume: Number(raw.volume ?? 0),
|
|
55
|
+
isClosed: raw.isClosed ?? false,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// ─── CandleEngine ─────────────────────────────────────────────────────────────
|
|
59
|
+
export class CandleEngine {
|
|
60
|
+
/** Authoritative ordered bar array. Only the rightmost bar may be open. */
|
|
61
|
+
_bars = [];
|
|
62
|
+
_timeframe = '1m';
|
|
63
|
+
_intervalMs = 60_000;
|
|
64
|
+
_opts;
|
|
65
|
+
constructor(options = {}) {
|
|
66
|
+
this._opts = options;
|
|
67
|
+
}
|
|
68
|
+
// ── Initialisation ──────────────────────────────────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* Seed the engine with historical bars and set the active timeframe.
|
|
71
|
+
* All existing bars are replaced.
|
|
72
|
+
*
|
|
73
|
+
* Historical bars loaded from REST are assumed closed, except possibly
|
|
74
|
+
* the last one (which may represent the still-forming candle).
|
|
75
|
+
*
|
|
76
|
+
* @param history Raw bars from a REST endpoint (oldest → newest).
|
|
77
|
+
* @param timeframe The active timeframe label (e.g. '1m', '1h').
|
|
78
|
+
*/
|
|
79
|
+
initialize(history, timeframe) {
|
|
80
|
+
this._timeframe = timeframe;
|
|
81
|
+
this._intervalMs = timeframeToMs(timeframe);
|
|
82
|
+
// Normalise, deduplicate by timeMs (latest entry wins), sort newest-last
|
|
83
|
+
const seen = new Map();
|
|
84
|
+
for (const raw of history) {
|
|
85
|
+
const bar = normalizeOhlcv(raw, this._intervalMs, this._timeframe);
|
|
86
|
+
seen.set(bar.timeMs, bar);
|
|
87
|
+
}
|
|
88
|
+
this._bars = Array.from(seen.values()).sort((a, b) => a.timeMs - b.timeMs);
|
|
89
|
+
// Mark all bars except the last as closed
|
|
90
|
+
for (let i = 0; i < this._bars.length - 1; i++) {
|
|
91
|
+
this._bars[i].isClosed = true;
|
|
92
|
+
}
|
|
93
|
+
this._debugLog('initialize', {
|
|
94
|
+
timeframe,
|
|
95
|
+
intervalMs: this._intervalMs,
|
|
96
|
+
barCount: this._bars.length,
|
|
97
|
+
firstTime: this._bars[0]?.timeMs,
|
|
98
|
+
lastTime: this._bars[this._bars.length - 1]?.timeMs,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// ── Live updates ────────────────────────────────────────────────────────────
|
|
102
|
+
/**
|
|
103
|
+
* Process a single live OHLCV tick from the WebSocket feed.
|
|
104
|
+
*
|
|
105
|
+
* Algorithm (mirrors TradingView / Binance candle engine behaviour):
|
|
106
|
+
*
|
|
107
|
+
* 1. Normalise: coerce strings to numbers, detect ms vs s, align to bucket.
|
|
108
|
+
* 2. Empty array → append as first bar.
|
|
109
|
+
* 3. incoming.timeMs === last.timeMs → SAME CANDLE (update in-place):
|
|
110
|
+
* open = unchanged (first tick's open is authoritative)
|
|
111
|
+
* high = max(last.high, inc.high, inc.close)
|
|
112
|
+
* low = min(last.low, inc.low, inc.close)
|
|
113
|
+
* close = inc.close
|
|
114
|
+
* volume = inc.volume
|
|
115
|
+
* 4. incoming.timeMs > last.timeMs → NEW CANDLE:
|
|
116
|
+
* - Detect and report any gap (missing buckets between last and incoming).
|
|
117
|
+
* - Mark last bar as closed, fire onBarClosed.
|
|
118
|
+
* - Append incoming as a fresh live candle.
|
|
119
|
+
* 5. incoming.timeMs < last.timeMs → OUT-OF-ORDER: discard silently.
|
|
120
|
+
*
|
|
121
|
+
* @returns UpdateResult indicating what was done — the chart adapter uses
|
|
122
|
+
* this to choose between series.update() and series.setData().
|
|
123
|
+
*/
|
|
124
|
+
applyLiveUpdate(raw) {
|
|
125
|
+
const incoming = normalizeOhlcv(raw, this._intervalMs, this._timeframe);
|
|
126
|
+
// Invariant: incoming bar must be bucket-aligned and OHLC-valid
|
|
127
|
+
assertBucketAlignment(incoming, this._timeframe, 'applyLiveUpdate:normalize');
|
|
128
|
+
assertOhlcRelationships(incoming, 'applyLiveUpdate:normalize');
|
|
129
|
+
// ── First bar ever ─────────────────────────────────────────────────────
|
|
130
|
+
if (this._bars.length === 0) {
|
|
131
|
+
this._bars.push(incoming);
|
|
132
|
+
const result = { type: 'append', bar: incoming, previousBarClosed: false };
|
|
133
|
+
this._debugLog('append:first', incoming);
|
|
134
|
+
this._opts.onBarUpdated?.(incoming, result);
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
const last = this._bars[this._bars.length - 1];
|
|
138
|
+
// ── Same candle: update in-place (O(1)) ────────────────────────────────
|
|
139
|
+
if (incoming.timeMs === last.timeMs) {
|
|
140
|
+
// Invariant: must not mutate a finalized candle
|
|
141
|
+
assertNotFinalized(last, 'applyLiveUpdate:same-bucket');
|
|
142
|
+
const prevOpen = last.open;
|
|
143
|
+
const prevCount = this._bars.length;
|
|
144
|
+
// open is intentionally not updated — the first tick's open is correct
|
|
145
|
+
last.high = Math.max(last.high, incoming.high, incoming.close);
|
|
146
|
+
last.low = Math.min(last.low, incoming.low, incoming.close);
|
|
147
|
+
last.close = incoming.close;
|
|
148
|
+
last.volume = incoming.volume;
|
|
149
|
+
// Invariant: open must be unchanged, no spurious append must have occurred
|
|
150
|
+
assertOpenImmutability(prevOpen, last, 'applyLiveUpdate:same-bucket');
|
|
151
|
+
assertOhlcRelationships(last, 'applyLiveUpdate:same-bucket');
|
|
152
|
+
assertNoDuplicateAppend(prevCount, this._bars.length, 'applyLiveUpdate:same-bucket');
|
|
153
|
+
const result = { type: 'update', bar: last };
|
|
154
|
+
this._debugLog('update', last);
|
|
155
|
+
this._opts.onBarUpdated?.(last, result);
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
// ── Out-of-order: older than the current live candle ──────────────────
|
|
159
|
+
if (incoming.timeMs < last.timeMs) {
|
|
160
|
+
this._debugLog('ignore', {
|
|
161
|
+
incomingTimeMs: incoming.timeMs,
|
|
162
|
+
lastTimeMs: last.timeMs,
|
|
163
|
+
delta: last.timeMs - incoming.timeMs,
|
|
164
|
+
});
|
|
165
|
+
return { type: 'ignore' };
|
|
166
|
+
}
|
|
167
|
+
// ── New candle (incoming.timeMs > last.timeMs) ─────────────────────────
|
|
168
|
+
// Gap detection: how many candle buckets are missing between last and new?
|
|
169
|
+
const missingCount = getMissingBarCount(last.timeMs, incoming.timeMs, this._intervalMs);
|
|
170
|
+
if (missingCount > 0) {
|
|
171
|
+
const gapInfo = { fromTimeMs: last.timeMs, toTimeMs: incoming.timeMs, missingCount };
|
|
172
|
+
this._debugLog('gap', gapInfo);
|
|
173
|
+
this._opts.onGapDetected?.(gapInfo);
|
|
174
|
+
}
|
|
175
|
+
// Finalise the previous live candle
|
|
176
|
+
const prevLastBar = { ...last }; // snapshot for post-append invariant check
|
|
177
|
+
last.isClosed = true;
|
|
178
|
+
this._opts.onBarClosed?.(last);
|
|
179
|
+
// Append the new candle
|
|
180
|
+
this._bars.push(incoming);
|
|
181
|
+
// Invariant: previous bar must be closed and not mutated after the append
|
|
182
|
+
assertFinalizedUnchanged(prevLastBar, this._bars, 'applyLiveUpdate:new-bucket');
|
|
183
|
+
assertOhlcRelationships(incoming, 'applyLiveUpdate:new-bucket');
|
|
184
|
+
const result = missingCount > 0
|
|
185
|
+
? { type: 'gap', bar: incoming, missingCount }
|
|
186
|
+
: { type: 'append', bar: incoming, previousBarClosed: true };
|
|
187
|
+
this._debugLog(result.type, incoming);
|
|
188
|
+
this._opts.onBarUpdated?.(incoming, result);
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
// ── Gap backfill ────────────────────────────────────────────────────────────
|
|
192
|
+
/**
|
|
193
|
+
* Merge REST-fetched bars into the engine to fill a detected gap.
|
|
194
|
+
*
|
|
195
|
+
* Incoming bars are treated as authoritative closed candles.
|
|
196
|
+
* The current live candle is preserved if present.
|
|
197
|
+
*
|
|
198
|
+
* After calling this, `onResync` fires — the chart adapter must call
|
|
199
|
+
* `series.setData(engine.getBars())` to fully resync the chart.
|
|
200
|
+
*
|
|
201
|
+
* @param rawBars Historical bars from the REST endpoint covering the gap.
|
|
202
|
+
*/
|
|
203
|
+
backfillGap(rawBars) {
|
|
204
|
+
const incoming = rawBars.map(r => {
|
|
205
|
+
const bar = normalizeOhlcv(r, this._intervalMs, this._timeframe);
|
|
206
|
+
bar.isClosed = true; // REST bars are closed and authoritative
|
|
207
|
+
return bar;
|
|
208
|
+
});
|
|
209
|
+
// Preserve the live candle's open state after merge
|
|
210
|
+
const liveBefore = this._bars[this._bars.length - 1];
|
|
211
|
+
this._bars = mergeBars(this._bars, incoming);
|
|
212
|
+
// Re-apply live candle's isClosed=false if it survived the merge
|
|
213
|
+
if (liveBefore && !liveBefore.isClosed) {
|
|
214
|
+
const last = this._bars[this._bars.length - 1];
|
|
215
|
+
if (last && last.timeMs === liveBefore.timeMs) {
|
|
216
|
+
last.isClosed = false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
this._debugLog('backfillGap', { filled: rawBars.length, totalBars: this._bars.length });
|
|
220
|
+
this._opts.onResync?.(this._bars);
|
|
221
|
+
}
|
|
222
|
+
// ── Reconnect reconciliation ────────────────────────────────────────────────
|
|
223
|
+
/**
|
|
224
|
+
* Reconcile after a WebSocket reconnect.
|
|
225
|
+
*
|
|
226
|
+
* Fetch a rolling overlap window from REST (e.g. last 50 bars or 2 hours)
|
|
227
|
+
* and pass them here. The engine merges them with the existing bar array,
|
|
228
|
+
* correcting any candles that were missed or corrupted during the outage.
|
|
229
|
+
*
|
|
230
|
+
* After reconciliation, `onResync` fires so the chart adapter can resync
|
|
231
|
+
* the chart series with `series.setData(engine.getBars())`.
|
|
232
|
+
*
|
|
233
|
+
* @param recentBars REST bars covering the reconnect overlap window.
|
|
234
|
+
*/
|
|
235
|
+
handleReconnect(recentBars) {
|
|
236
|
+
const incoming = recentBars.map(r => normalizeOhlcv(r, this._intervalMs, this._timeframe));
|
|
237
|
+
this._bars = mergeBars(this._bars, incoming);
|
|
238
|
+
this._debugLog('handleReconnect', { count: recentBars.length, totalBars: this._bars.length });
|
|
239
|
+
this._opts.onResync?.(this._bars);
|
|
240
|
+
}
|
|
241
|
+
// ── Trade-tick stub ─────────────────────────────────────────────────────────
|
|
242
|
+
/**
|
|
243
|
+
* Stub for future tick/trade-based candle construction.
|
|
244
|
+
*
|
|
245
|
+
* When implemented, this will:
|
|
246
|
+
* - Assign the trade to its bucket using `getBucketStart(timeMs, intervalMs)`.
|
|
247
|
+
* - Create a new candle if the bucket is absent.
|
|
248
|
+
* - Update OHLCV: open/close from trade price, high/low expanded, volume summed.
|
|
249
|
+
*
|
|
250
|
+
* Not yet implemented — structure is in place for future extension.
|
|
251
|
+
*/
|
|
252
|
+
applyTradeTick(_trade) {
|
|
253
|
+
// TODO: implement tick-based candle construction
|
|
254
|
+
}
|
|
255
|
+
// ── Read access ─────────────────────────────────────────────────────────────
|
|
256
|
+
/**
|
|
257
|
+
* Returns a readonly view of the authoritative bar array.
|
|
258
|
+
* Do not mutate — call `applyLiveUpdate`, `backfillGap`, etc. instead.
|
|
259
|
+
*/
|
|
260
|
+
getBars() {
|
|
261
|
+
return this._bars;
|
|
262
|
+
}
|
|
263
|
+
/** Returns the most recent (possibly still live) bar, or null if empty. */
|
|
264
|
+
getLastBar() {
|
|
265
|
+
return this._bars[this._bars.length - 1] ?? null;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Prepend older historical bars fetched during a lazy scroll-left page load.
|
|
269
|
+
* Only bars older than the current oldest bar are accepted; duplicates and
|
|
270
|
+
* out-of-range bars are silently dropped.
|
|
271
|
+
* Fires `onResync` so the chart adapter updates the series with `setData`.
|
|
272
|
+
*
|
|
273
|
+
* @param rawBars Historical bars from the REST endpoint, oldest → newest.
|
|
274
|
+
*/
|
|
275
|
+
prependHistory(rawBars) {
|
|
276
|
+
const oldest = this._bars[0];
|
|
277
|
+
const cutoff = oldest ? oldest.timeMs : Infinity;
|
|
278
|
+
const incoming = rawBars
|
|
279
|
+
.map(r => {
|
|
280
|
+
const bar = normalizeOhlcv(r, this._intervalMs, this._timeframe);
|
|
281
|
+
bar.isClosed = true;
|
|
282
|
+
return bar;
|
|
283
|
+
})
|
|
284
|
+
.filter(bar => bar.timeMs < cutoff);
|
|
285
|
+
if (incoming.length === 0)
|
|
286
|
+
return;
|
|
287
|
+
incoming.sort((a, b) => a.timeMs - b.timeMs);
|
|
288
|
+
this._bars = [...incoming, ...this._bars];
|
|
289
|
+
this._debugLog('prependHistory', {
|
|
290
|
+
prepended: incoming.length,
|
|
291
|
+
firstTime: this._bars[0]?.timeMs,
|
|
292
|
+
});
|
|
293
|
+
this._opts.onResync?.(this._bars);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Clear all state.
|
|
297
|
+
* Call before switching symbol or timeframe, or on a hard disconnect.
|
|
298
|
+
*/
|
|
299
|
+
reset() {
|
|
300
|
+
this._bars = [];
|
|
301
|
+
this._debugLog('reset', { timeframe: this._timeframe });
|
|
302
|
+
}
|
|
303
|
+
// ── Private helpers ──────────────────────────────────────────────────────────
|
|
304
|
+
_debugLog(action, data) {
|
|
305
|
+
if (!this._opts.debug)
|
|
306
|
+
return;
|
|
307
|
+
console.debug('[CANDLE_ENGINE]', {
|
|
308
|
+
action,
|
|
309
|
+
timeframe: this._timeframe,
|
|
310
|
+
barCount: this._bars.length,
|
|
311
|
+
liveBar: this._bars[this._bars.length - 1]
|
|
312
|
+
? { open: this._bars[this._bars.length - 1].open, high: this._bars[this._bars.length - 1].high, low: this._bars[this._bars.length - 1].low, close: this._bars[this._bars.length - 1].close }
|
|
313
|
+
: null,
|
|
314
|
+
...data,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=CandleEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CandleEngine.js","sourceRoot":"","sources":["../../src/engine/CandleEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CandleEngine.test.d.ts","sourceRoot":"","sources":["../../../src/engine/__tests__/CandleEngine.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { CandleEngine } from '../CandleEngine';
|
|
3
|
+
import { timeframeToMs, getBucketStart } from '../timeframeUtils';
|
|
4
|
+
import { getMissingBarCount, mergeBars } from '../mergeUtils';
|
|
5
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
6
|
+
/** Build a minimal raw bar with time in seconds. */
|
|
7
|
+
function rawBar(timeSec, open, high, low, close, volume = 1, isClosed = false) {
|
|
8
|
+
return { time: timeSec, open, high, low, close, volume, isClosed };
|
|
9
|
+
}
|
|
10
|
+
/** Build a raw bar with time in milliseconds. */
|
|
11
|
+
function rawBarMs(timeSec, open, high, low, close) {
|
|
12
|
+
return { time: timeSec * 1000, open, high, low, close, volume: 1 };
|
|
13
|
+
}
|
|
14
|
+
// 1-minute boundary: 2024-07-03T10:00:00Z = 1719997200 seconds
|
|
15
|
+
const T0 = 1_719_997_200;
|
|
16
|
+
const T1 = T0 + 60;
|
|
17
|
+
const T2 = T0 + 120;
|
|
18
|
+
const T3 = T0 + 180;
|
|
19
|
+
const T4 = T0 + 240;
|
|
20
|
+
// ─── 1. Same-candle update ────────────────────────────────────────────────────
|
|
21
|
+
describe('same-candle update (3 ticks, same bucket)', () => {
|
|
22
|
+
it('produces exactly one candle with correct O/H/L/C', () => {
|
|
23
|
+
const engine = new CandleEngine();
|
|
24
|
+
engine.initialize([], '1m');
|
|
25
|
+
engine.applyLiveUpdate(rawBar(T0, 100, 105, 98, 103));
|
|
26
|
+
engine.applyLiveUpdate(rawBar(T0, 100, 108, 96, 107)); // high rises, low drops
|
|
27
|
+
engine.applyLiveUpdate(rawBar(T0, 100, 107, 97, 106)); // high recedes — not applied
|
|
28
|
+
expect(engine.getBars().length).toBe(1);
|
|
29
|
+
const bar = engine.getLastBar();
|
|
30
|
+
expect(bar.open).toBe(100); // open preserved from first tick
|
|
31
|
+
expect(bar.high).toBe(108); // max of all ticks
|
|
32
|
+
expect(bar.low).toBe(96); // min of all ticks
|
|
33
|
+
expect(bar.close).toBe(106); // close from latest tick
|
|
34
|
+
});
|
|
35
|
+
it('returns UpdateResult type "update" for same-candle ticks', () => {
|
|
36
|
+
const engine = new CandleEngine();
|
|
37
|
+
engine.initialize([], '1m');
|
|
38
|
+
const r1 = engine.applyLiveUpdate(rawBar(T0, 100, 100, 100, 100));
|
|
39
|
+
const r2 = engine.applyLiveUpdate(rawBar(T0, 100, 110, 90, 105));
|
|
40
|
+
expect(r1.type).toBe('append'); // first tick is an append
|
|
41
|
+
expect(r2.type).toBe('update');
|
|
42
|
+
});
|
|
43
|
+
it('high can never decrease within a candle', () => {
|
|
44
|
+
const engine = new CandleEngine();
|
|
45
|
+
engine.initialize([], '1m');
|
|
46
|
+
engine.applyLiveUpdate(rawBar(T0, 50, 120, 40, 100));
|
|
47
|
+
engine.applyLiveUpdate(rawBar(T0, 50, 80, 60, 90)); // high=80 < 120
|
|
48
|
+
expect(engine.getLastBar().high).toBe(120);
|
|
49
|
+
});
|
|
50
|
+
it('low can never increase within a candle', () => {
|
|
51
|
+
const engine = new CandleEngine();
|
|
52
|
+
engine.initialize([], '1m');
|
|
53
|
+
engine.applyLiveUpdate(rawBar(T0, 50, 120, 40, 100));
|
|
54
|
+
engine.applyLiveUpdate(rawBar(T0, 50, 110, 75, 105)); // low=75 > 40
|
|
55
|
+
expect(engine.getLastBar().low).toBe(40);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
// ─── 2. New-candle append ─────────────────────────────────────────────────────
|
|
59
|
+
describe('new-candle append (time advances)', () => {
|
|
60
|
+
it('appends a second candle and marks first as closed', () => {
|
|
61
|
+
const engine = new CandleEngine();
|
|
62
|
+
engine.initialize([], '1m');
|
|
63
|
+
engine.applyLiveUpdate(rawBar(T0, 100, 105, 98, 103));
|
|
64
|
+
const result = engine.applyLiveUpdate(rawBar(T1, 103, 110, 101, 108));
|
|
65
|
+
expect(result.type).toBe('append');
|
|
66
|
+
expect(engine.getBars().length).toBe(2);
|
|
67
|
+
expect(engine.getBars()[0].isClosed).toBe(true);
|
|
68
|
+
expect(engine.getBars()[1].isClosed).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
it('appended candle uses the new tick\'s open price', () => {
|
|
71
|
+
const engine = new CandleEngine();
|
|
72
|
+
engine.initialize([], '1m');
|
|
73
|
+
engine.applyLiveUpdate(rawBar(T0, 100, 105, 98, 103));
|
|
74
|
+
engine.applyLiveUpdate(rawBar(T1, 200, 210, 195, 205));
|
|
75
|
+
expect(engine.getBars()[1].open).toBe(200);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
// ─── 3. Out-of-order discard ──────────────────────────────────────────────────
|
|
79
|
+
describe('out-of-order ticks', () => {
|
|
80
|
+
it('discards ticks older than the current live candle', () => {
|
|
81
|
+
const engine = new CandleEngine();
|
|
82
|
+
engine.initialize([], '1m');
|
|
83
|
+
engine.applyLiveUpdate(rawBar(T1, 103, 110, 100, 107)); // at T1
|
|
84
|
+
const result = engine.applyLiveUpdate(rawBar(T0, 100, 120, 95, 115)); // T0 < T1
|
|
85
|
+
expect(result.type).toBe('ignore');
|
|
86
|
+
expect(engine.getBars().length).toBe(1);
|
|
87
|
+
expect(engine.getLastBar().time).toBe(T1);
|
|
88
|
+
});
|
|
89
|
+
it('does not change any OHLCV values after discarding', () => {
|
|
90
|
+
const engine = new CandleEngine();
|
|
91
|
+
engine.initialize([], '1m');
|
|
92
|
+
engine.applyLiveUpdate(rawBar(T1, 103, 110, 100, 107));
|
|
93
|
+
const snapBefore = { ...engine.getLastBar() };
|
|
94
|
+
engine.applyLiveUpdate(rawBar(T0, 999, 999, 1, 999));
|
|
95
|
+
expect(engine.getLastBar().open).toBe(snapBefore.open);
|
|
96
|
+
expect(engine.getLastBar().high).toBe(snapBefore.high);
|
|
97
|
+
expect(engine.getLastBar().low).toBe(snapBefore.low);
|
|
98
|
+
expect(engine.getLastBar().close).toBe(snapBefore.close);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
// ─── 4. Gap detection ─────────────────────────────────────────────────────────
|
|
102
|
+
describe('gap detection', () => {
|
|
103
|
+
it('fires onGapDetected when a bucket is skipped', () => {
|
|
104
|
+
const gaps = [];
|
|
105
|
+
const engine = new CandleEngine({ onGapDetected: (g) => gaps.push(g) });
|
|
106
|
+
engine.initialize([], '1m');
|
|
107
|
+
engine.applyLiveUpdate(rawBar(T0, 100, 105, 98, 103));
|
|
108
|
+
// Jump 3 minutes — T1 and T2 are missing
|
|
109
|
+
engine.applyLiveUpdate(rawBar(T3, 110, 115, 108, 113));
|
|
110
|
+
expect(gaps.length).toBe(1);
|
|
111
|
+
expect(gaps[0].missingCount).toBe(2);
|
|
112
|
+
expect(gaps[0].fromTimeMs).toBe(T0 * 1000);
|
|
113
|
+
expect(gaps[0].toTimeMs).toBe(T3 * 1000);
|
|
114
|
+
});
|
|
115
|
+
it('returns UpdateResult type "gap" when a gap occurs', () => {
|
|
116
|
+
const engine = new CandleEngine();
|
|
117
|
+
engine.initialize([], '1m');
|
|
118
|
+
engine.applyLiveUpdate(rawBar(T0, 100, 105, 98, 103));
|
|
119
|
+
const result = engine.applyLiveUpdate(rawBar(T4, 103, 108, 101, 106));
|
|
120
|
+
expect(result.type).toBe('gap');
|
|
121
|
+
if (result.type === 'gap') {
|
|
122
|
+
expect(result.missingCount).toBe(3);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
it('does not fire onGapDetected for consecutive candles', () => {
|
|
126
|
+
const gapFn = vi.fn();
|
|
127
|
+
const engine = new CandleEngine({ onGapDetected: gapFn });
|
|
128
|
+
engine.initialize([], '1m');
|
|
129
|
+
engine.applyLiveUpdate(rawBar(T0, 100, 105, 98, 103));
|
|
130
|
+
engine.applyLiveUpdate(rawBar(T1, 103, 108, 101, 106));
|
|
131
|
+
expect(gapFn).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
// ─── 5. Reconnect reconciliation ─────────────────────────────────────────────
|
|
135
|
+
describe('handleReconnect', () => {
|
|
136
|
+
it('deduplicates bars correctly when there is overlap', () => {
|
|
137
|
+
const engine = new CandleEngine();
|
|
138
|
+
engine.initialize([
|
|
139
|
+
rawBar(T0, 100, 105, 98, 103, 10, true),
|
|
140
|
+
rawBar(T1, 103, 108, 101, 106, 12, true),
|
|
141
|
+
rawBar(T2, 106, 110, 104, 109, 8, false), // live
|
|
142
|
+
], '1m');
|
|
143
|
+
// Reconnect brings T1 and T2 with corrected values
|
|
144
|
+
engine.handleReconnect([
|
|
145
|
+
rawBar(T1, 103, 112, 99, 111, 15, true), // corrected T1
|
|
146
|
+
rawBar(T2, 106, 111, 103, 110, 9, false), // corrected T2 (live)
|
|
147
|
+
]);
|
|
148
|
+
expect(engine.getBars().length).toBe(3);
|
|
149
|
+
expect(engine.getBars()[1].high).toBe(112); // T1 updated
|
|
150
|
+
expect(engine.getBars()[2].high).toBe(111); // T2 updated
|
|
151
|
+
});
|
|
152
|
+
it('fires onResync after handleReconnect', () => {
|
|
153
|
+
const resyncFn = vi.fn();
|
|
154
|
+
const engine = new CandleEngine({ onResync: resyncFn });
|
|
155
|
+
engine.initialize([rawBar(T0, 100, 105, 98, 103)], '1m');
|
|
156
|
+
engine.handleReconnect([rawBar(T0, 100, 107, 96, 104)]);
|
|
157
|
+
expect(resyncFn).toHaveBeenCalledOnce();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
// ─── 6. ms → s timestamp conversion ─────────────────────────────────────────
|
|
161
|
+
describe('millisecond vs second timestamp normalisation', () => {
|
|
162
|
+
it('converts ms timestamps to seconds correctly', () => {
|
|
163
|
+
const engine = new CandleEngine();
|
|
164
|
+
engine.initialize([], '1m');
|
|
165
|
+
// T0 in ms
|
|
166
|
+
engine.applyLiveUpdate(rawBarMs(T0, 100, 105, 98, 103));
|
|
167
|
+
const bar = engine.getLastBar();
|
|
168
|
+
expect(bar.time).toBe(T0); // chart field = seconds
|
|
169
|
+
expect(bar.timeMs).toBe(T0 * 1000); // engine field = ms
|
|
170
|
+
});
|
|
171
|
+
it('handles both ms and s inputs in the same session without corruption', () => {
|
|
172
|
+
const engine = new CandleEngine();
|
|
173
|
+
engine.initialize([], '1m');
|
|
174
|
+
engine.applyLiveUpdate(rawBarMs(T0, 100, 105, 98, 103)); // ms
|
|
175
|
+
engine.applyLiveUpdate(rawBar(T0, 100, 110, 95, 108)); // seconds, same bucket
|
|
176
|
+
expect(engine.getBars().length).toBe(1); // same bucket → still one candle
|
|
177
|
+
expect(engine.getLastBar().high).toBe(110);
|
|
178
|
+
});
|
|
179
|
+
it('floors ms timestamp to candle boundary', () => {
|
|
180
|
+
const engine = new CandleEngine();
|
|
181
|
+
engine.initialize([], '1m');
|
|
182
|
+
// T0 + 30s (mid-candle) in ms → should still map to T0 bucket
|
|
183
|
+
engine.applyLiveUpdate({ time: (T0 + 30) * 1000, open: 100, high: 105, low: 98, close: 103 });
|
|
184
|
+
expect(engine.getLastBar().time).toBe(T0);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
// ─── 7. Custom timeframe aggregation (timeframeUtils) ────────────────────────
|
|
188
|
+
describe('timeframeUtils helpers', () => {
|
|
189
|
+
it('timeframeToMs returns correct durations', () => {
|
|
190
|
+
expect(timeframeToMs('1m')).toBe(60_000);
|
|
191
|
+
expect(timeframeToMs('5m')).toBe(300_000);
|
|
192
|
+
expect(timeframeToMs('1h')).toBe(3_600_000);
|
|
193
|
+
expect(timeframeToMs('4h')).toBe(14_400_000);
|
|
194
|
+
expect(timeframeToMs('1d')).toBe(86_400_000);
|
|
195
|
+
// 1w and 1M are calendar timeframes — no fixed ms duration.
|
|
196
|
+
// timeframeUtils returns the 60_000 compat fallback so CandleEngine
|
|
197
|
+
// always receives a finite number (calendar alignment is handled separately).
|
|
198
|
+
expect(timeframeToMs('1w')).toBe(60_000);
|
|
199
|
+
});
|
|
200
|
+
it('timeframeToMs falls back to 60_000 for unknown labels', () => {
|
|
201
|
+
// '3h' and '8h' are orphaned (excluded from canonical registry) → 60_000 fallback.
|
|
202
|
+
// '2m' is now registered (120_000) — use genuinely unknown keys here.
|
|
203
|
+
expect(timeframeToMs('3h')).toBe(60_000);
|
|
204
|
+
expect(timeframeToMs('')).toBe(60_000);
|
|
205
|
+
});
|
|
206
|
+
it('getBucketStart floors to candle boundary', () => {
|
|
207
|
+
expect(getBucketStart(62_000, 60_000)).toBe(60_000); // 1m01s → 1m00s
|
|
208
|
+
expect(getBucketStart(3_720_000, 3_600_000)).toBe(3_600_000); // 1h02m → 1h00m
|
|
209
|
+
expect(getBucketStart(0, 60_000)).toBe(0);
|
|
210
|
+
});
|
|
211
|
+
it('5m candle engine buckets a mid-bar tick to the 5m open', () => {
|
|
212
|
+
const engine = new CandleEngine();
|
|
213
|
+
engine.initialize([], '5m');
|
|
214
|
+
const T5m = getBucketStart(T0 * 1000, timeframeToMs('5m')); // floor T0 to 5m
|
|
215
|
+
const mid = T5m / 1000 + 120; // 2 minutes into the 5m bucket
|
|
216
|
+
engine.applyLiveUpdate({ time: mid, open: 50, high: 55, low: 48, close: 52 });
|
|
217
|
+
expect(engine.getLastBar().timeMs).toBe(T5m);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
// ─── 8. mergeUtils helpers ────────────────────────────────────────────────────
|
|
221
|
+
describe('mergeUtils helpers', () => {
|
|
222
|
+
const makeBar = (timeSec, close, isClosed) => ({
|
|
223
|
+
timeMs: timeSec * 1000,
|
|
224
|
+
time: timeSec,
|
|
225
|
+
open: 100, high: 110, low: 90, close,
|
|
226
|
+
volume: 1, isClosed,
|
|
227
|
+
});
|
|
228
|
+
it('getMissingBarCount returns 0 for consecutive bars', () => {
|
|
229
|
+
expect(getMissingBarCount(T0 * 1000, T1 * 1000, 60_000)).toBe(0);
|
|
230
|
+
});
|
|
231
|
+
it('getMissingBarCount returns correct count for gap', () => {
|
|
232
|
+
expect(getMissingBarCount(T0 * 1000, T3 * 1000, 60_000)).toBe(2); // T1 and T2
|
|
233
|
+
expect(getMissingBarCount(T0 * 1000, T4 * 1000, 60_000)).toBe(3); // T1, T2, T3
|
|
234
|
+
});
|
|
235
|
+
it('mergeBars deduplicates by timeMs with incoming winning', () => {
|
|
236
|
+
const existing = [makeBar(T0, 103, true), makeBar(T1, 106, true)];
|
|
237
|
+
const incoming = [makeBar(T1, 999, true), makeBar(T2, 110, false)]; // T1 override
|
|
238
|
+
const merged = mergeBars(existing, incoming);
|
|
239
|
+
expect(merged.length).toBe(3);
|
|
240
|
+
expect(merged[1].close).toBe(999); // incoming wins for closed bar
|
|
241
|
+
expect(merged[2].time).toBe(T2);
|
|
242
|
+
});
|
|
243
|
+
it('mergeBars preserves existing live candle when incoming is also live and same time', () => {
|
|
244
|
+
const existing = [makeBar(T0, 103, false)]; // live, close=103
|
|
245
|
+
const incoming = [makeBar(T0, 107, false)]; // fresher live tick, close=107
|
|
246
|
+
const merged = mergeBars(existing, incoming);
|
|
247
|
+
// Live bar: incoming wins because it is the freshest tick
|
|
248
|
+
expect(merged[0].close).toBe(107);
|
|
249
|
+
});
|
|
250
|
+
it('mergeBars sorts output by timeMs', () => {
|
|
251
|
+
const bars = [makeBar(T2, 100, true), makeBar(T0, 100, true), makeBar(T1, 100, true)];
|
|
252
|
+
const sorted = mergeBars(bars, []);
|
|
253
|
+
expect(sorted.map(b => b.time)).toEqual([T0, T1, T2]);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
// ─── 9. backfillGap ───────────────────────────────────────────────────────────
|
|
257
|
+
describe('backfillGap', () => {
|
|
258
|
+
it('fills missing buckets and fires onResync', () => {
|
|
259
|
+
const resyncFn = vi.fn();
|
|
260
|
+
const engine = new CandleEngine({ onResync: resyncFn });
|
|
261
|
+
engine.initialize([
|
|
262
|
+
rawBar(T0, 100, 105, 98, 103),
|
|
263
|
+
rawBar(T3, 110, 115, 108, 113), // gap: T1, T2 missing
|
|
264
|
+
], '1m');
|
|
265
|
+
engine.backfillGap([
|
|
266
|
+
rawBar(T1, 103, 108, 101, 106),
|
|
267
|
+
rawBar(T2, 106, 110, 104, 109),
|
|
268
|
+
]);
|
|
269
|
+
expect(engine.getBars().length).toBe(4);
|
|
270
|
+
expect(resyncFn).toHaveBeenCalledOnce();
|
|
271
|
+
const times = engine.getBars().map(b => b.time);
|
|
272
|
+
expect(times).toEqual([T0, T1, T2, T3]);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
// ─── 10. CandleEngine lifecycle ──────────────────────────────────────────────
|
|
276
|
+
describe('lifecycle', () => {
|
|
277
|
+
it('reset() clears all bars', () => {
|
|
278
|
+
const engine = new CandleEngine();
|
|
279
|
+
engine.initialize([rawBar(T0, 100, 105, 98, 103)], '1m');
|
|
280
|
+
engine.reset();
|
|
281
|
+
expect(engine.getBars().length).toBe(0);
|
|
282
|
+
expect(engine.getLastBar()).toBeNull();
|
|
283
|
+
});
|
|
284
|
+
it('handles initialize() with empty history', () => {
|
|
285
|
+
const engine = new CandleEngine();
|
|
286
|
+
engine.initialize([], '1m');
|
|
287
|
+
expect(engine.getBars().length).toBe(0);
|
|
288
|
+
expect(engine.getLastBar()).toBeNull();
|
|
289
|
+
});
|
|
290
|
+
it('handles duplicate historical bars (keeps last per timeMs)', () => {
|
|
291
|
+
const engine = new CandleEngine();
|
|
292
|
+
engine.initialize([
|
|
293
|
+
rawBar(T0, 100, 105, 98, 103),
|
|
294
|
+
rawBar(T0, 100, 110, 96, 108), // same time, last should win
|
|
295
|
+
], '1m');
|
|
296
|
+
expect(engine.getBars().length).toBe(1);
|
|
297
|
+
expect(engine.getLastBar().high).toBe(110);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
//# sourceMappingURL=CandleEngine.test.js.map
|