@vninvestcharts/widget 0.0.0-stage → 0.2.0
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/LICENSE +201 -0
- package/NOTICE.md +83 -0
- package/README.md +851 -3
- package/dist/AlertsModal-WKEUDTUZ.js +2 -0
- package/dist/ConnectedTradingPanel-KWJMIKTN.css +2 -0
- package/dist/ConnectedTradingPanel-XDKMOQY5.js +2 -0
- package/dist/PineEditorPanel-AUXB4FD3.js +2 -0
- package/dist/PineEditorPanel-V6YN2OTU.css +2 -0
- package/dist/RightSidebarDockHost-6FPZ6EKV.css +2 -0
- package/dist/RightSidebarDockHost-OBFXKSIY.js +2 -0
- package/dist/StrategyTesterPanel-6IUTS2P4.js +2 -0
- package/dist/StrategyTesterPanel-LEWEUMXP.css +2 -0
- package/dist/VolumeProfileWorker-UREKVPUE.js +189 -0
- package/dist/adapters/index.cjs +2532 -0
- package/dist/adapters/index.d.mts +673 -0
- package/dist/adapters/index.d.ts +673 -0
- package/dist/adapters/index.js +2521 -0
- package/dist/adapters/index.umd.js +2535 -0
- package/dist/chunk-5T6DGB3V.js +2 -0
- package/dist/chunk-66JBEGR3.js +4 -0
- package/dist/chunk-6D7DVBIK.js +2 -0
- package/dist/chunk-76E7CKUD.js +2 -0
- package/dist/chunk-AKWVXRQ6.js +2 -0
- package/dist/chunk-FHHSTO6Z.js +2 -0
- package/dist/chunk-HPL723JA.js +2 -0
- package/dist/chunk-NVGTF73S.js +2 -0
- package/dist/chunk-PNIB4OM3.js +2 -0
- package/dist/chunk-UKJ5AKQX.js +595 -0
- package/dist/chunk-UYJTX3SD.js +2 -0
- package/dist/combined.css +3068 -0
- package/dist/css-modules-manifest.json +297 -0
- package/dist/dist-Y6PZ2RN3.js +12 -0
- package/dist/drawing/index.cjs +53 -0
- package/dist/drawing/index.d.mts +2 -0
- package/dist/drawing/index.d.ts +2 -0
- package/dist/drawing/index.js +2 -0
- package/dist/drawing/index.umd.js +54 -0
- package/dist/dts/drawing-shim.d.ts +3 -0
- package/dist/dts/index.d.ts +31 -0
- package/dist/dts/lib/Chart.d.ts +31 -0
- package/dist/dts/lib/ChartContext.d.ts +10 -0
- package/dist/dts/lib/EventCapture.d.ts +3 -0
- package/dist/dts/lib/GenericChartComponent.d.ts +5 -0
- package/dist/dts/lib/GenericComponent.d.ts +124 -0
- package/dist/dts/lib/StockChartContext.d.ts +53 -0
- package/dist/dts/lib/adapters/BaseAdapter.d.ts +25 -0
- package/dist/dts/lib/adapters/BinanceAdapter.d.ts +51 -0
- package/dist/dts/lib/adapters/CSVAdapter.d.ts +17 -0
- package/dist/dts/lib/adapters/ContinuousContractAdapter.d.ts +63 -0
- package/dist/dts/lib/adapters/ContinuousContractTypes.d.ts +154 -0
- package/dist/dts/lib/adapters/DBAdapter.d.ts +16 -0
- package/dist/dts/lib/adapters/DataAdapter.d.ts +136 -0
- package/dist/dts/lib/adapters/DjangoVnstockAdapter.d.ts +15 -0
- package/dist/dts/lib/adapters/LocalCacheAdapter.d.ts +18 -0
- package/dist/dts/lib/adapters/MockAdapter.d.ts +22 -0
- package/dist/dts/lib/adapters/RESTAdapter.d.ts +16 -0
- package/dist/dts/lib/adapters/StockDataAdapter.d.ts +1 -0
- package/dist/dts/lib/adapters/UDFDataAdapter.d.ts +29 -0
- package/dist/dts/lib/adapters/VNStocksAdapter.d.ts +8 -0
- package/dist/dts/lib/adapters/WSAdapter.d.ts +30 -0
- package/dist/dts/lib/adapters/gapFixture.d.ts +71 -0
- package/dist/dts/lib/adapters/index.d.ts +15 -0
- package/dist/dts/lib/adapters/rollAlgorithms.d.ts +68 -0
- package/dist/dts/lib/advisory/AdvisoryPanel.d.ts +5 -0
- package/dist/dts/lib/alerts/AlertEvaluator.d.ts +5 -0
- package/dist/dts/lib/alerts/AlertManager.d.ts +123 -0
- package/dist/dts/lib/alerts/types.d.ts +58 -0
- package/dist/dts/lib/cache/cacheDebug.d.ts +3 -0
- package/dist/dts/lib/calendars/ExchangeCalendar.d.ts +23 -0
- package/dist/dts/lib/calendars/calendarRegistry.d.ts +10 -0
- package/dist/dts/lib/calendars/index.d.ts +3 -0
- package/dist/dts/lib/core/ChartPane.d.ts +8 -0
- package/dist/dts/lib/core/ChartSplitter.d.ts +70 -0
- package/dist/dts/lib/core/ChartTerminal.d.ts +12 -0
- package/dist/dts/lib/core/ErrorBoundary.d.ts +17 -0
- package/dist/dts/lib/core/IndicatorLegend.d.ts +47 -0
- package/dist/dts/lib/core/PaneHeader.d.ts +24 -0
- package/dist/dts/lib/core/PaneLabel.d.ts +8 -0
- package/dist/dts/lib/core/PaneSplitter.d.ts +9 -0
- package/dist/dts/lib/core/SeriesPicker.d.ts +10 -0
- package/dist/dts/lib/core/calculators/alignBarsByDate.d.ts +38 -0
- package/dist/dts/lib/core/calculators/buildIndicatorComputationPlan.d.ts +16 -0
- package/dist/dts/lib/core/calculators/calcCVDApprox.d.ts +6 -0
- package/dist/dts/lib/core/calculators/calcStrengthElder.d.ts +7 -0
- package/dist/dts/lib/core/calculators/calcWhaleApprox.d.ts +6 -0
- package/dist/dts/lib/core/calculators/computationHelpers.d.ts +12 -0
- package/dist/dts/lib/core/calculators/computeIndicatorComputationResult.d.ts +3 -0
- package/dist/dts/lib/core/calculators/computeMarketStructure.d.ts +8 -0
- package/dist/dts/lib/core/calculators/computeMomentum.d.ts +22 -0
- package/dist/dts/lib/core/calculators/computeMovingAverage.d.ts +15 -0
- package/dist/dts/lib/core/calculators/computeProfile.d.ts +9 -0
- package/dist/dts/lib/core/calculators/computeTrend.d.ts +10 -0
- package/dist/dts/lib/core/calculators/computeVolatility.d.ts +14 -0
- package/dist/dts/lib/core/calculators/computeVolumeFlow.d.ts +19 -0
- package/dist/dts/lib/core/calculators/enrichData.d.ts +7 -0
- package/dist/dts/lib/core/calculators/enrichDataWithCanonicalStore.d.ts +33 -0
- package/dist/dts/lib/core/calculators/exoticChartData.d.ts +140 -0
- package/dist/dts/lib/core/calculators/fixtures/mockData.d.ts +2 -0
- package/dist/dts/lib/core/calculators/indicatorComputation.d.ts +27 -0
- package/dist/dts/lib/core/calculators/indicatorComputationTypes.d.ts +249 -0
- package/dist/dts/lib/core/calculators/materializeIndicatorValues.d.ts +3 -0
- package/dist/dts/lib/core/calculators/normalizePrices.d.ts +36 -0
- package/dist/dts/lib/core/calculators/resolveCalculationOrder.d.ts +17 -0
- package/dist/dts/lib/core/calculators/types.d.ts +269 -0
- package/dist/dts/lib/core/canvas/ChartRenderContext.d.ts +14 -0
- package/dist/dts/lib/core/canvas/OverlayCanvas.d.ts +19 -0
- package/dist/dts/lib/core/context/ChartSyncContext.d.ts +21 -0
- package/dist/dts/lib/core/context/DataContext.d.ts +15 -0
- package/dist/dts/lib/core/context/PaneManagerContext.d.ts +10 -0
- package/dist/dts/lib/core/hooks/paneSizingHelpers.d.ts +13 -0
- package/dist/dts/lib/core/hooks/useCanvasResize.d.ts +8 -0
- package/dist/dts/lib/core/hooks/useChartTheme.d.ts +12 -0
- package/dist/dts/lib/core/hooks/useDynamicPanes.d.ts +155 -0
- package/dist/dts/lib/core/hooks/useIndicatorSets.d.ts +20 -0
- package/dist/dts/lib/core/hooks/usePaneManager.d.ts +39 -0
- package/dist/dts/lib/core/hooks/usePaneSizes.d.ts +52 -0
- package/dist/dts/lib/core/index.d.ts +41 -0
- package/dist/dts/lib/core/paneScaleSlotDomains.d.ts +3 -0
- package/dist/dts/lib/core/registry/SeriesRegistry.d.ts +22 -0
- package/dist/dts/lib/core/registry/filterPanesBySeriesTier.d.ts +43 -0
- package/dist/dts/lib/core/registry/marketStructure.d.ts +7 -0
- package/dist/dts/lib/core/registry/momentum.d.ts +20 -0
- package/dist/dts/lib/core/registry/movingAverage.d.ts +14 -0
- package/dist/dts/lib/core/registry/priceChartTypes.d.ts +16 -0
- package/dist/dts/lib/core/registry/profile.d.ts +8 -0
- package/dist/dts/lib/core/registry/registerAll.d.ts +1 -0
- package/dist/dts/lib/core/registry/registerPhaseOne.d.ts +3 -0
- package/dist/dts/lib/core/registry/registryCore.d.ts +77 -0
- package/dist/dts/lib/core/registry/trend.d.ts +11 -0
- package/dist/dts/lib/core/registry/volatility.d.ts +11 -0
- package/dist/dts/lib/core/registry/volumeFlow.d.ts +20 -0
- package/dist/dts/lib/core/replay/BarReplayController.d.ts +62 -0
- package/dist/dts/lib/core/replay/ReplayEventBus.d.ts +68 -0
- package/dist/dts/lib/core/replay/ReplayFillSimulator.d.ts +33 -0
- package/dist/dts/lib/core/replay/ReplayOrderBook.d.ts +24 -0
- package/dist/dts/lib/core/replay/ReplayPositionManager.d.ts +30 -0
- package/dist/dts/lib/core/replay/ReplaySessionAnalytics.d.ts +31 -0
- package/dist/dts/lib/core/replay/createReplayTradeMarkerPrimitives.d.ts +7 -0
- package/dist/dts/lib/core/replay/pnlUtils.d.ts +2 -0
- package/dist/dts/lib/core/replay/replayBrokerAdapters.d.ts +51 -0
- package/dist/dts/lib/core/replay/replayTradingTypes.d.ts +61 -0
- package/dist/dts/lib/core/scales/computeScales.d.ts +8 -0
- package/dist/dts/lib/core/seriesValueResolver.d.ts +27 -0
- package/dist/dts/lib/core/sets/builtins.d.ts +2 -0
- package/dist/dts/lib/core/sets/indicatorSetCodec.d.ts +15 -0
- package/dist/dts/lib/core/trading/AnalyticsEngine.d.ts +56 -0
- package/dist/dts/lib/core/trading/BrokerAPI.d.ts +40 -0
- package/dist/dts/lib/core/trading/ExecutionSimulator.d.ts +53 -0
- package/dist/dts/lib/core/trading/FillSettlement.d.ts +38 -0
- package/dist/dts/lib/core/trading/MarketDataPort.d.ts +41 -0
- package/dist/dts/lib/core/trading/MarketRules.d.ts +58 -0
- package/dist/dts/lib/core/trading/OrderManagementSystem.d.ts +57 -0
- package/dist/dts/lib/core/trading/PositionManager.d.ts +79 -0
- package/dist/dts/lib/core/trading/RiskEngine.d.ts +40 -0
- package/dist/dts/lib/core/trading/SettlementCalendar.d.ts +10 -0
- package/dist/dts/lib/core/trading/VirtualBrokerEngine.d.ts +92 -0
- package/dist/dts/lib/core/trading/orderMargin.d.ts +3 -0
- package/dist/dts/lib/core/trading/storage/InMemoryStorageAdapter.d.ts +10 -0
- package/dist/dts/lib/core/trading/storage/TradingStorage.d.ts +34 -0
- package/dist/dts/lib/core/trading/types.d.ts +99 -0
- package/dist/dts/lib/core/types/chart.d.ts +21 -0
- package/dist/dts/lib/core/types/indicator-catalog.d.ts +38 -0
- package/dist/dts/lib/core/types/indicator-set.d.ts +19 -0
- package/dist/dts/lib/core/types/pane-descriptor.d.ts +46 -0
- package/dist/dts/lib/core/types/seriesComponents.d.ts +15 -0
- package/dist/dts/lib/credentials/credentialStore.d.ts +93 -0
- package/dist/dts/lib/helper/SaveChartAsImage.d.ts +90 -0
- package/dist/dts/lib/indicator/defaultOptionsForAppearance.d.ts +105 -0
- package/dist/dts/lib/indicators/builtin/MTFWrapper.d.ts +3 -0
- package/dist/dts/lib/indicators/builtin/VolumeProfileWorker.d.ts +19 -0
- package/dist/dts/lib/indicators/builtin/ac.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/alligator.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/anchoredVwap.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/ao.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/bbi.d.ts +10 -0
- package/dist/dts/lib/indicators/builtin/bias.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/bollinger.d.ts +9 -0
- package/dist/dts/lib/indicators/builtin/brar.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/cci.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/chaikinOscillator.d.ts +14 -0
- package/dist/dts/lib/indicators/builtin/chaikinVol.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/ci.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/cmf.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/cr.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/cvd.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/deltaProfile.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/dema.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/dma.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/dmi.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/donchian.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/ema.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/emv.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/footprint.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/fractals.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/gator.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/hma.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/hv.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/ichimoku.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/kdj.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/keltner.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/ma.d.ts +10 -0
- package/dist/dts/lib/indicators/builtin/maRibbon.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/macd.d.ts +9 -0
- package/dist/dts/lib/indicators/builtin/marketProfile.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/mfi.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/mtm.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/obv.d.ts +10 -0
- package/dist/dts/lib/indicators/builtin/pivotHighLow.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/pivotStandard.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/psy.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/pvt.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/roc.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/rsi.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/sar.d.ts +10 -0
- package/dist/dts/lib/indicators/builtin/sma.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/smfi.d.ts +8 -0
- package/dist/dts/lib/indicators/builtin/stdDev.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/strengthRelative.d.ts +7 -0
- package/dist/dts/lib/indicators/builtin/supertrend.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/tema.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/trix.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/volOsc.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/volume.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/volumeProfile.d.ts +8 -0
- package/dist/dts/lib/indicators/builtin/vr.d.ts +10 -0
- package/dist/dts/lib/indicators/builtin/vwap.d.ts +5 -0
- package/dist/dts/lib/indicators/builtin/vwma.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/wma.d.ts +4 -0
- package/dist/dts/lib/indicators/builtin/wr.d.ts +10 -0
- package/dist/dts/lib/indicators/builtin/wyckoffWave.d.ts +15 -0
- package/dist/dts/lib/indicators/index.d.ts +63 -0
- package/dist/dts/lib/indicators/indicatorValueShapes.d.ts +15 -0
- package/dist/dts/lib/indicators/outputKeys.d.ts +50 -0
- package/dist/dts/lib/indicators/overlays/CVDIndicator.d.ts +26 -0
- package/dist/dts/lib/indicators/overlays/DarkFlowBadge.d.ts +19 -0
- package/dist/dts/lib/indicators/overlays/ExternalCVDOverlay.d.ts +15 -0
- package/dist/dts/lib/indicators/overlays/WhaleBubbleOverlay.d.ts +14 -0
- package/dist/dts/lib/indicators/overlays/WhaleMarketBubbleOverlay.d.ts +27 -0
- package/dist/dts/lib/indicators/overlays/whaleDetector.d.ts +66 -0
- package/dist/dts/lib/indicators/registry.d.ts +6 -0
- package/dist/dts/lib/indicators/types.d.ts +5 -0
- package/dist/dts/lib/indicators/utils.d.ts +259 -0
- package/dist/dts/lib/pinescript/PineCompiler.d.ts +34 -0
- package/dist/dts/lib/pinescript/ast.d.ts +73 -0
- package/dist/dts/lib/pinescript/compile.d.ts +40 -0
- package/dist/dts/lib/pinescript/grammar.d.ts +12 -0
- package/dist/dts/lib/pinescript/pine.worker.d.ts +1 -0
- package/dist/dts/lib/pinescript/pineBlocks.d.ts +54 -0
- package/dist/dts/lib/pinescript/pineBuiltins.d.ts +47 -0
- package/dist/dts/lib/pinescript/pineEnv.d.ts +46 -0
- package/dist/dts/lib/pinescript/pineMath.d.ts +13 -0
- package/dist/dts/lib/pinescript/pineParse.d.ts +25 -0
- package/dist/dts/lib/pinescript/pineStrings.d.ts +50 -0
- package/dist/dts/lib/pinescript/runtime.d.ts +5 -0
- package/dist/dts/lib/pinescript/scriptRegistry.d.ts +40 -0
- package/dist/dts/lib/pinescript/series-value.d.ts +64 -0
- package/dist/dts/lib/pinescript/vnscript-completion.d.ts +1 -0
- package/dist/dts/lib/pinescript/vnscript-grammar.d.ts +19 -0
- package/dist/dts/lib/scale/discontinuousTimeScaleProvider.d.ts +3 -0
- package/dist/dts/lib/scale/financeDiscontinuousScale.d.ts +15 -0
- package/dist/dts/lib/scale/index.d.ts +1 -0
- package/dist/dts/lib/scale/levels.d.ts +12 -0
- package/dist/dts/lib/series/AlternatingFillAreaSeries.d.ts +13 -0
- package/dist/dts/lib/series/AreaOnlySeries.d.ts +10 -0
- package/dist/dts/lib/series/AreaSeries.d.ts +30 -0
- package/dist/dts/lib/series/BarSeries.d.ts +40 -0
- package/dist/dts/lib/series/BollingerSeries.d.ts +11 -0
- package/dist/dts/lib/series/CandlestickSeries.d.ts +56 -0
- package/dist/dts/lib/series/CircleMarker.d.ts +28 -0
- package/dist/dts/lib/series/ElderRaySeries.d.ts +14 -0
- package/dist/dts/lib/series/FootprintSeries.d.ts +21 -0
- package/dist/dts/lib/series/GroupedBarSeries.d.ts +9 -0
- package/dist/dts/lib/series/HTFOverlaySeries.d.ts +32 -0
- package/dist/dts/lib/series/HistogramSeries.d.ts +51 -0
- package/dist/dts/lib/series/KagiSeries.d.ts +9 -0
- package/dist/dts/lib/series/LineBreakSeries.d.ts +28 -0
- package/dist/dts/lib/series/LineSeries.d.ts +54 -0
- package/dist/dts/lib/series/MACDSeries.d.ts +28 -0
- package/dist/dts/lib/series/MarketProfileSeries.d.ts +18 -0
- package/dist/dts/lib/series/OHLCSeries.d.ts +9 -0
- package/dist/dts/lib/series/OverlayBarSeries.d.ts +9 -0
- package/dist/dts/lib/series/PointAndFigureSeries.d.ts +45 -0
- package/dist/dts/lib/series/RSISeries.d.ts +38 -0
- package/dist/dts/lib/series/RenkoSeries.d.ts +9 -0
- package/dist/dts/lib/series/SARSeries.d.ts +10 -0
- package/dist/dts/lib/series/SVGComponent.d.ts +5 -0
- package/dist/dts/lib/series/ScatterSeries.d.ts +9 -0
- package/dist/dts/lib/series/SquareMarker.d.ts +28 -0
- package/dist/dts/lib/series/StackedBarSeries.d.ts +19 -0
- package/dist/dts/lib/series/StochasticSeries.d.ts +9 -0
- package/dist/dts/lib/series/StraightLine.d.ts +18 -0
- package/dist/dts/lib/series/TriangleMarker.d.ts +30 -0
- package/dist/dts/lib/series/VolumeProfileSeries.d.ts +9 -0
- package/dist/dts/lib/series/index.d.ts +28 -0
- package/dist/dts/lib/strategy/BrokerSimulator.d.ts +11 -0
- package/dist/dts/lib/strategy/types.d.ts +35 -0
- package/dist/dts/lib/tape/TapePanel.d.ts +5 -0
- package/dist/dts/lib/types/adapter.d.ts +1 -0
- package/dist/dts/lib/types/index.d.ts +5 -0
- package/dist/dts/lib/types/indicator.d.ts +11 -0
- package/dist/dts/lib/types/ohlcv.d.ts +1 -0
- package/dist/dts/lib/types/pane.d.ts +23 -0
- package/dist/dts/lib/types/runtime.d.ts +11 -0
- package/dist/dts/lib/types/vninvest.d.ts +320 -0
- package/dist/dts/lib/types.d.ts +45 -0
- package/dist/dts/lib/utils/ChartDataUtil.d.ts +26 -0
- package/dist/dts/lib/utils/PureComponent.d.ts +5 -0
- package/dist/dts/lib/utils/accumulatingWindow.d.ts +1 -0
- package/dist/dts/lib/utils/axisPriceFormat.d.ts +6 -0
- package/dist/dts/lib/utils/barWidth.d.ts +14 -0
- package/dist/dts/lib/utils/identity.d.ts +1 -0
- package/dist/dts/lib/utils/index.d.ts +70 -0
- package/dist/dts/lib/utils/logger.d.ts +28 -0
- package/dist/dts/lib/utils/mappedSlidingWindow.d.ts +1 -0
- package/dist/dts/lib/utils/merge.d.ts +6 -0
- package/dist/dts/lib/utils/noop.d.ts +1 -0
- package/dist/dts/lib/utils/rebind.d.ts +1 -0
- package/dist/dts/lib/utils/shallowEqual.d.ts +1 -0
- package/dist/dts/lib/utils/slidingWindow.d.ts +1 -0
- package/dist/dts/lib/utils/storage.d.ts +32 -0
- package/dist/dts/lib/utils/strokeDasharray.d.ts +3 -0
- package/dist/dts/lib/utils/timezoneFormatter.d.ts +14 -0
- package/dist/dts/lib/utils/zipper.d.ts +4 -0
- package/dist/dts/public-api.d.ts +31 -0
- package/dist/dts/widget/BackfillWorkerFlags.d.ts +12 -0
- package/dist/dts/widget/CanaryRollbackHelper.d.ts +95 -0
- package/dist/dts/widget/CanaryRolloutConfig.d.ts +100 -0
- package/dist/dts/widget/DrawingPriceLabelsOverlay.d.ts +20 -0
- package/dist/dts/widget/ForkDrawingsOverlay.d.ts +16 -0
- package/dist/dts/widget/MeasurementOverlay.d.ts +24 -0
- package/dist/dts/widget/VNInvestCharts.d.ts +5 -0
- package/dist/dts/widget/VNInvestChartsProps.d.ts +302 -0
- package/dist/dts/widget/VNInvestMultiCharts.d.ts +8 -0
- package/dist/dts/widget/VNInvestMultiCharts.types.d.ts +130 -0
- package/dist/dts/widget/WidgetEmptyState.d.ts +11 -0
- package/dist/dts/widget/WidgetErrorBoundary.d.ts +26 -0
- package/dist/dts/widget/adapters/ContinuousFuturesStockDataAdapter.d.ts +18 -0
- package/dist/dts/widget/adapters/DataProviderAdapterWrapper.d.ts +14 -0
- package/dist/dts/widget/adapters/MultiSourceDataAdapter.d.ts +24 -0
- package/dist/dts/widget/adapters/VNInvestClient.d.ts +161 -0
- package/dist/dts/widget/adapters/VNInvestDataAdapter.d.ts +77 -0
- package/dist/dts/widget/adapters/VNInvestWebSocketClient.d.ts +79 -0
- package/dist/dts/widget/adapters/barsPagingPolicy.d.ts +18 -0
- package/dist/dts/widget/adapters/corporateEvents.d.ts +13 -0
- package/dist/dts/widget/adapters/index.d.ts +20 -0
- package/dist/dts/widget/adapters/nativeShapes.d.ts +32 -0
- package/dist/dts/widget/adapters/watchlistRealtimeSync.d.ts +16 -0
- package/dist/dts/widget/alerts/DrawingAlertsHost.d.ts +18 -0
- package/dist/dts/widget/alerts/useDrawingAlerts.d.ts +37 -0
- package/dist/dts/widget/canaryBarFiltering.d.ts +35 -0
- package/dist/dts/widget/capabilities/CapabilityResolver.d.ts +71 -0
- package/dist/dts/widget/capabilities/capabilityProbe.d.ts +28 -0
- package/dist/dts/widget/capabilities/useTierGatedFeatures.d.ts +48 -0
- package/dist/dts/widget/components/BottomPanelHost.d.ts +80 -0
- package/dist/dts/widget/components/BottomPanelSplitter.d.ts +15 -0
- package/dist/dts/widget/components/CapabilityGate.d.ts +65 -0
- package/dist/dts/widget/components/ChartDiagOverlay.d.ts +31 -0
- package/dist/dts/widget/components/ConfirmDialog.d.ts +12 -0
- package/dist/dts/widget/components/DowngradeBanner.d.ts +9 -0
- package/dist/dts/widget/components/ForkPaneSeparators.d.ts +11 -0
- package/dist/dts/widget/components/ForkPaneTooltips.d.ts +23 -0
- package/dist/dts/widget/components/ForkXAxisOverlay.d.ts +11 -0
- package/dist/dts/widget/components/ForkYAxisCrosshairLabels.d.ts +9 -0
- package/dist/dts/widget/components/LayoutDesigner.d.ts +26 -0
- package/dist/dts/widget/components/PremiumFeatureGate.d.ts +10 -0
- package/dist/dts/widget/components/ShortcutHelpModal.d.ts +44 -0
- package/dist/dts/widget/components/SystemMenu.d.ts +66 -0
- package/dist/dts/widget/components/Toast.d.ts +20 -0
- package/dist/dts/widget/components/canvas/BarCountdownBadge.d.ts +8 -0
- package/dist/dts/widget/components/canvas/ChartA11ySummary.d.ts +40 -0
- package/dist/dts/widget/components/canvas/ChartStatusLine.d.ts +39 -0
- package/dist/dts/widget/components/canvas/PricePaneHeader.d.ts +24 -0
- package/dist/dts/widget/components/canvas/PriceScaleMenu.d.ts +27 -0
- package/dist/dts/widget/components/canvas/TimeAxisScrollbar.d.ts +18 -0
- package/dist/dts/widget/components/common/Icons.d.ts +67 -0
- package/dist/dts/widget/components/dock/DataWindow.d.ts +7 -0
- package/dist/dts/widget/components/dock/DockUpgradeNudge.d.ts +10 -0
- package/dist/dts/widget/components/dock/RightSidebarDockHost.d.ts +57 -0
- package/dist/dts/widget/components/dock/WatchlistPanel.d.ts +7 -0
- package/dist/dts/widget/components/dock/WorkspaceRightDockSlot.d.ts +46 -0
- package/dist/dts/widget/components/dock/dockAccess.d.ts +20 -0
- package/dist/dts/widget/components/dock/drawingDockContentBuilders.d.ts +29 -0
- package/dist/dts/widget/components/drawing/DrawingDock.d.ts +47 -0
- package/dist/dts/widget/components/drawing/DrawingInspector.d.ts +94 -0
- package/dist/dts/widget/components/drawing/DrawingListPanel.d.ts +62 -0
- package/dist/dts/widget/components/drawing/DrawingSemanticPointLegend.d.ts +14 -0
- package/dist/dts/widget/components/drawing/FloatingDrawingInspector.d.ts +18 -0
- package/dist/dts/widget/components/drawing/IconPickerPanel.d.ts +5 -0
- package/dist/dts/widget/components/drawing/InlineTextEditor.d.ts +20 -0
- package/dist/dts/widget/components/drawing/PatternScanPanel.d.ts +27 -0
- package/dist/dts/widget/components/drawing/TemplateManagerPanel.d.ts +18 -0
- package/dist/dts/widget/components/events/CorporateEventsOverlay.d.ts +23 -0
- package/dist/dts/widget/components/events/EventMarkerIcon.d.ts +6 -0
- package/dist/dts/widget/components/footer/ChartFooter.d.ts +26 -0
- package/dist/dts/widget/components/footer/MultiChartsFooter.d.ts +37 -0
- package/dist/dts/widget/components/footer/TimeframeFooter.d.ts +25 -0
- package/dist/dts/widget/components/footer/useBarCountdown.d.ts +5 -0
- package/dist/dts/widget/components/header/OHLCHeader.d.ts +72 -0
- package/dist/dts/widget/components/layout/DrawingConflictNotice.d.ts +4 -0
- package/dist/dts/widget/components/layout/GridContainer.d.ts +70 -0
- package/dist/dts/widget/components/layout/GridSlotErrorBoundary.d.ts +17 -0
- package/dist/dts/widget/components/layout/Splitter.d.ts +23 -0
- package/dist/dts/widget/components/layout/gridSplitterMath.d.ts +40 -0
- package/dist/dts/widget/components/layout/useGridSplitterDrag.d.ts +18 -0
- package/dist/dts/widget/components/mobile/MobileChartCarousel.d.ts +11 -0
- package/dist/dts/widget/components/mobile/MobileToolbar.d.ts +29 -0
- package/dist/dts/widget/components/overlays/AdvisoryPanel.d.ts +25 -0
- package/dist/dts/widget/components/overlays/TapePanel.d.ts +25 -0
- package/dist/dts/widget/components/overlays/WhalePanel.d.ts +5 -0
- package/dist/dts/widget/components/panels/AdvisoryPanel.d.ts +25 -0
- package/dist/dts/widget/components/panels/PineEditorPanel.d.ts +18 -0
- package/dist/dts/widget/components/panels/StrategyTesterPanel.d.ts +7 -0
- package/dist/dts/widget/components/panels/TapePanel.d.ts +17 -0
- package/dist/dts/widget/components/panels/index.d.ts +8 -0
- package/dist/dts/widget/components/replay/FloatingReplayToolbar.d.ts +24 -0
- package/dist/dts/widget/components/replay/ReplayCutOverlay.d.ts +8 -0
- package/dist/dts/widget/components/search/SymbolCompareModal.d.ts +24 -0
- package/dist/dts/widget/components/search/SymbolSearchModal.d.ts +12 -0
- package/dist/dts/widget/components/search/VNSymbolSearch.d.ts +83 -0
- package/dist/dts/widget/components/settings/AlertsModal.d.ts +74 -0
- package/dist/dts/widget/components/settings/SettingsContext.d.ts +58 -0
- package/dist/dts/widget/components/settings/SettingsModal.d.ts +78 -0
- package/dist/dts/widget/components/settings/SettingsRangeSlider.d.ts +17 -0
- package/dist/dts/widget/components/settings/SnapshotPreviewModal.d.ts +16 -0
- package/dist/dts/widget/components/settings/WorkspaceSettingsContext.d.ts +27 -0
- package/dist/dts/widget/components/settings/helpers/seriesFormatters.d.ts +11 -0
- package/dist/dts/widget/components/settings/model/appearancePayload.d.ts +31 -0
- package/dist/dts/widget/components/settings/model/chartSettingsSchema.d.ts +100 -0
- package/dist/dts/widget/components/settings/model/defaultSettings.d.ts +5 -0
- package/dist/dts/widget/components/settings/model/indicatorShare.d.ts +29 -0
- package/dist/dts/widget/components/settings/storageKeys.d.ts +29 -0
- package/dist/dts/widget/components/settings/tabs/AppearanceSettingsTab.d.ts +5 -0
- package/dist/dts/widget/components/settings/tabs/IndicatorParamsForm.d.ts +12 -0
- package/dist/dts/widget/components/settings/tabs/IndicatorsSettingsTab.d.ts +34 -0
- package/dist/dts/widget/components/settings/tabs/LicensingSettingsTab.d.ts +5 -0
- package/dist/dts/widget/components/settings/tabs/PanesLayoutSettingsTab.d.ts +11 -0
- package/dist/dts/widget/components/settings/tabs/ResetSettingsTab.d.ts +8 -0
- package/dist/dts/widget/components/settings/tabs/ScalesSettingsTab.d.ts +5 -0
- package/dist/dts/widget/components/settings/tabs/StatusLineSettingsTab.d.ts +5 -0
- package/dist/dts/widget/components/settings/tabs/SymbolSettingsTab.d.ts +5 -0
- package/dist/dts/widget/components/settings/tabs/ThemePaletteSettingsTab.d.ts +9 -0
- package/dist/dts/widget/components/settings/tabs/TradingEventsSettingsTab.d.ts +8 -0
- package/dist/dts/widget/components/toolbar/DrawingToolIcon.d.ts +10 -0
- package/dist/dts/widget/components/toolbar/DrawingToolbar.d.ts +33 -0
- package/dist/dts/widget/components/toolbar/FloatingChartBar.d.ts +20 -0
- package/dist/dts/widget/components/toolbar/FloatingFavoritesBar.d.ts +18 -0
- package/dist/dts/widget/components/toolbar/MultiChartTopbar.d.ts +47 -0
- package/dist/dts/widget/components/toolbar/UnifiedDrawingToolbar.d.ts +28 -0
- package/dist/dts/widget/components/trading/PaperTradePanel.d.ts +41 -0
- package/dist/dts/widget/components/workspace/WorkspaceToolbar.d.ts +25 -0
- package/dist/dts/widget/config/toolGroups.d.ts +11 -0
- package/dist/dts/widget/config/toolShortcuts.d.ts +27 -0
- package/dist/dts/widget/constants/comparison.d.ts +1 -0
- package/dist/dts/widget/constants/symbolSearch.d.ts +14 -0
- package/dist/dts/widget/constants/vn30.d.ts +7 -0
- package/dist/dts/widget/context/DataProviderContext.d.ts +7 -0
- package/dist/dts/widget/context/HoverEmitter.d.ts +50 -0
- package/dist/dts/widget/context/SyncContextShare.d.ts +11 -0
- package/dist/dts/widget/context/VNInvestChartsConfigContext.d.ts +24 -0
- package/dist/dts/widget/context/VNInvestChartsContext.d.ts +70 -0
- package/dist/dts/widget/context/VNInvestChartsStateContext.d.ts +50 -0
- package/dist/dts/widget/context/WidgetI18nContext.d.ts +25 -0
- package/dist/dts/widget/context/WorkspaceLiveTradingContext.d.ts +30 -0
- package/dist/dts/widget/context/WorkspacePaperVisibilityContext.d.ts +23 -0
- package/dist/dts/widget/context/WorkspaceReplayTradingContext.d.ts +36 -0
- package/dist/dts/widget/contexts/SaaSFeatureContext.d.ts +23 -0
- package/dist/dts/widget/data/DataEnvelope.d.ts +100 -0
- package/dist/dts/widget/drawingPriceLabelInputs.d.ts +3 -0
- package/dist/dts/widget/export/chartCsv.d.ts +59 -0
- package/dist/dts/widget/forkAnchorReconciliation.d.ts +6 -0
- package/dist/dts/widget/forkInteractionHelpers.d.ts +69 -0
- package/dist/dts/widget/grid/DeviceProfiles.d.ts +49 -0
- package/dist/dts/widget/grid/GridChartRegistry.d.ts +9 -0
- package/dist/dts/widget/grid/MemoryBudgetManager.d.ts +63 -0
- package/dist/dts/widget/grid/PerformanceMonitor.d.ts +70 -0
- package/dist/dts/widget/grid/customPresets.d.ts +42 -0
- package/dist/dts/widget/grid/presets.d.ts +18 -0
- package/dist/dts/widget/grid/types.d.ts +39 -0
- package/dist/dts/widget/history/BackfillDedupContext.d.ts +63 -0
- package/dist/dts/widget/history/RequestCoordinator.d.ts +59 -0
- package/dist/dts/widget/history/SharedBarStoreContext.d.ts +22 -0
- package/dist/dts/widget/history/cache/IndexedDBCacheMatrix.d.ts +16 -0
- package/dist/dts/widget/history/cache/LocalCacheMatrix.d.ts +86 -0
- package/dist/dts/widget/history/cache/index.d.ts +3 -0
- package/dist/dts/widget/history/chunkStore.d.ts +77 -0
- package/dist/dts/widget/history/externalBarsNormalization.d.ts +9 -0
- package/dist/dts/widget/history/externalBarsNormalization.worker.d.ts +1 -0
- package/dist/dts/widget/history/externalBarsNormalizationAsync.d.ts +25 -0
- package/dist/dts/widget/history/historyDataUtils.d.ts +82 -0
- package/dist/dts/widget/history/historyWindowPlanner.d.ts +89 -0
- package/dist/dts/widget/history/inflightController.d.ts +15 -0
- package/dist/dts/widget/history/requestCoalescing.d.ts +10 -0
- package/dist/dts/widget/history/sessionGuards.d.ts +3 -0
- package/dist/dts/widget/history/singleWriterLane.d.ts +4 -0
- package/dist/dts/widget/history/useExternalBarsNormalization.d.ts +5 -0
- package/dist/dts/widget/hooks/ShortcutManager.d.ts +65 -0
- package/dist/dts/widget/hooks/overlays/PaneHeaderWrapper.d.ts +11 -0
- package/dist/dts/widget/hooks/overlays/overlayHelpers.d.ts +60 -0
- package/dist/dts/widget/hooks/overlays/useDrawingDockNode.d.ts +15 -0
- package/dist/dts/widget/hooks/overlays/useLegendChrome.d.ts +36 -0
- package/dist/dts/widget/hooks/overlays/useMultiSelectActions.d.ts +22 -0
- package/dist/dts/widget/hooks/overlays/useSidebarResize.d.ts +13 -0
- package/dist/dts/widget/hooks/resolveEntitlement.d.ts +73 -0
- package/dist/dts/widget/hooks/useBottomPanelBehavior.d.ts +38 -0
- package/dist/dts/widget/hooks/useBottomPanelHeight.d.ts +39 -0
- package/dist/dts/widget/hooks/useCanvasTheme.d.ts +24 -0
- package/dist/dts/widget/hooks/useChartImperativeHandle.d.ts +57 -0
- package/dist/dts/widget/hooks/useClockAdapter.d.ts +11 -0
- package/dist/dts/widget/hooks/useComparisonState.d.ts +29 -0
- package/dist/dts/widget/hooks/useDialogFocus.d.ts +25 -0
- package/dist/dts/widget/hooks/useDrawingToolShortcuts.d.ts +16 -0
- package/dist/dts/widget/hooks/useFeatureGate.d.ts +28 -0
- package/dist/dts/widget/hooks/useFloatingChartBar.d.ts +33 -0
- package/dist/dts/widget/hooks/useForkRuntime.d.ts +155 -0
- package/dist/dts/widget/hooks/useIndicatorsShareApplier.d.ts +1 -0
- package/dist/dts/widget/hooks/useIntersectionObserver.d.ts +49 -0
- package/dist/dts/widget/hooks/useKeyboardAdapter.d.ts +9 -0
- package/dist/dts/widget/hooks/useLayoutHistory.d.ts +8 -0
- package/dist/dts/widget/hooks/useLicenseDiagnostics.d.ts +15 -0
- package/dist/dts/widget/hooks/useMultiChartReplaySync.d.ts +31 -0
- package/dist/dts/widget/hooks/useMultiWorkspacePersistence.d.ts +49 -0
- package/dist/dts/widget/hooks/usePaperTradeConfig.d.ts +27 -0
- package/dist/dts/widget/hooks/usePaperTradeInteraction.d.ts +27 -0
- package/dist/dts/widget/hooks/usePatTierAuthentication.d.ts +42 -0
- package/dist/dts/widget/hooks/usePineScriptRestore.d.ts +23 -0
- package/dist/dts/widget/hooks/usePortalAdapter.d.ts +8 -0
- package/dist/dts/widget/hooks/useRecentSymbols.d.ts +3 -0
- package/dist/dts/widget/hooks/useReplayState.d.ts +28 -0
- package/dist/dts/widget/hooks/useReplayTrading.d.ts +26 -0
- package/dist/dts/widget/hooks/useResizeAdapter.d.ts +13 -0
- package/dist/dts/widget/hooks/useScaleOverlays.d.ts +62 -0
- package/dist/dts/widget/hooks/useScriptPersistence.d.ts +72 -0
- package/dist/dts/widget/hooks/useSymbolSearch.d.ts +30 -0
- package/dist/dts/widget/hooks/useViewportBreakpoint.d.ts +15 -0
- package/dist/dts/widget/hooks/useWidgetCoordinateBridge.d.ts +70 -0
- package/dist/dts/widget/hooks/useWidgetData.d.ts +138 -0
- package/dist/dts/widget/hooks/useWidgetDataBackfill.d.ts +67 -0
- package/dist/dts/widget/hooks/useWidgetDataEnrichment.d.ts +32 -0
- package/dist/dts/widget/hooks/useWidgetDrawings.d.ts +38 -0
- package/dist/dts/widget/hooks/useWidgetForkInteraction.d.ts +68 -0
- package/dist/dts/widget/hooks/useWidgetInteractionActions.d.ts +36 -0
- package/dist/dts/widget/hooks/useWidgetOverlays.d.ts +190 -0
- package/dist/dts/widget/hooks/useWidgetRuntime.d.ts +20 -0
- package/dist/dts/widget/hooks/useWidgetSettings.d.ts +244 -0
- package/dist/dts/widget/hooks/useWidgetWorkspaceIO.d.ts +24 -0
- package/dist/dts/widget/hooks/useWorkspaceDockPanels.d.ts +11 -0
- package/dist/dts/widget/hooks/useWorkspaceLiveTrading.d.ts +28 -0
- package/dist/dts/widget/hooks/useWorkspaceReplayTrading.d.ts +22 -0
- package/dist/dts/widget/i18n/a11y.messages.en.d.ts +8 -0
- package/dist/dts/widget/i18n/a11y.messages.vi.d.ts +3 -0
- package/dist/dts/widget/i18n/capability.messages.en.d.ts +7 -0
- package/dist/dts/widget/i18n/capability.messages.vi.d.ts +3 -0
- package/dist/dts/widget/i18n/dock.messages.en.d.ts +2 -0
- package/dist/dts/widget/i18n/dock.messages.vi.d.ts +2 -0
- package/dist/dts/widget/i18n/messages.en.d.ts +2 -0
- package/dist/dts/widget/i18n/messages.licensing.d.ts +48 -0
- package/dist/dts/widget/i18n/messages.vi.d.ts +2 -0
- package/dist/dts/widget/i18n/trading.messages.en.d.ts +2 -0
- package/dist/dts/widget/i18n/trading.messages.vi.d.ts +2 -0
- package/dist/dts/widget/i18n/types.d.ts +3 -0
- package/dist/dts/widget/iframe/builtInAdapters.d.ts +63 -0
- package/dist/dts/widget/iframe/host-entry.d.ts +11 -0
- package/dist/dts/widget/iframe/hostSDK.d.ts +151 -0
- package/dist/dts/widget/iframe/iframeBridge.d.ts +165 -0
- package/dist/dts/widget/iframe/iframeEntry.d.ts +52 -0
- package/dist/dts/widget/iframe/iframeFrame.d.ts +15 -0
- package/dist/dts/widget/iframe/iframeQuotes.d.ts +20 -0
- package/dist/dts/widget/iframe/messageProtocol.d.ts +545 -0
- package/dist/dts/widget/iframe/quoteHub.d.ts +20 -0
- package/dist/dts/widget/iframe/useVNInvestChartsHost.d.ts +8 -0
- package/dist/dts/widget/iife-entry.d.ts +61 -0
- package/dist/dts/widget/iifeWidgetInstance.d.ts +60 -0
- package/dist/dts/widget/index.d.ts +56 -0
- package/dist/dts/widget/multiChartDefaults.d.ts +18 -0
- package/dist/dts/widget/paneInteractionUtils.d.ts +25 -0
- package/dist/dts/widget/patterns/autoPatternScan.d.ts +77 -0
- package/dist/dts/widget/patterns/patternDrawingFactory.d.ts +87 -0
- package/dist/dts/widget/plugin/ProPluginRegistry.d.ts +40 -0
- package/dist/dts/widget/runtime/ChartScaleAdapter.d.ts +73 -0
- package/dist/dts/widget/runtime/ChartSerializer.d.ts +12 -0
- package/dist/dts/widget/runtime/ForkRuntimeAdapter.d.ts +19 -0
- package/dist/dts/widget/runtime/IRuntimeEngine.d.ts +40 -0
- package/dist/dts/widget/runtime/WorkspaceSerializer.d.ts +31 -0
- package/dist/dts/widget/runtimePolicy.d.ts +36 -0
- package/dist/dts/widget/styles/injectStyles.d.ts +9 -0
- package/dist/dts/widget/theme/ThemeConfig.d.ts +21 -0
- package/dist/dts/widget/theme/applyTheme.d.ts +3 -0
- package/dist/dts/widget/trading/chart/DraggableOrderHandles.d.ts +15 -0
- package/dist/dts/widget/trading/chart/InteractivePositionOverlay.d.ts +19 -0
- package/dist/dts/widget/trading/chart/PendingOrderOverlay.d.ts +11 -0
- package/dist/dts/widget/trading/chart/QuickTradingHeader.d.ts +20 -0
- package/dist/dts/widget/trading/chart/TradingChartSurface.d.ts +8 -0
- package/dist/dts/widget/trading/chart/TradingOverlaysHost.d.ts +28 -0
- package/dist/dts/widget/trading/chart/priceTicks.d.ts +1 -0
- package/dist/dts/widget/trading/chart/useFloatingDrag.d.ts +40 -0
- package/dist/dts/widget/trading/chart/useTradingHotkeys.d.ts +21 -0
- package/dist/dts/widget/trading/config/paperRuntimeConfig.d.ts +53 -0
- package/dist/dts/widget/trading/context/PaperPanelRegistry.d.ts +45 -0
- package/dist/dts/widget/trading/context/TradingContext.d.ts +80 -0
- package/dist/dts/widget/trading/context/WorkspaceTradingSync.d.ts +24 -0
- package/dist/dts/widget/trading/hooks/usePaperRuntimeConfig.d.ts +36 -0
- package/dist/dts/widget/trading/index.d.ts +5 -0
- package/dist/dts/widget/trading/panel/AccountSummaryTab.d.ts +7 -0
- package/dist/dts/widget/trading/panel/ActiveChartPaperPanel.d.ts +26 -0
- package/dist/dts/widget/trading/panel/ConnectedTradingPanel.d.ts +3 -0
- package/dist/dts/widget/trading/panel/HistoryTab.d.ts +5 -0
- package/dist/dts/widget/trading/panel/MarketFallbackBanner.d.ts +4 -0
- package/dist/dts/widget/trading/panel/OrdersTab.d.ts +8 -0
- package/dist/dts/widget/trading/panel/PaperCapitalDialog.d.ts +8 -0
- package/dist/dts/widget/trading/panel/PaperSettingsModal.d.ts +17 -0
- package/dist/dts/widget/trading/panel/PositionsTab.d.ts +15 -0
- package/dist/dts/widget/trading/panel/TradeHistoryTab.d.ts +5 -0
- package/dist/dts/widget/trading/panel/TradingBottomPanel.d.ts +36 -0
- package/dist/dts/widget/trading/panel/TradingLogTab.d.ts +8 -0
- package/dist/dts/widget/trading/panel/TradingPanelHeader.d.ts +17 -0
- package/dist/dts/widget/trading/panel/csvExport.d.ts +18 -0
- package/dist/dts/widget/trading/ticket/ConnectedTradingDockPanel.d.ts +5 -0
- package/dist/dts/widget/trading/ticket/OrderTicketForm.d.ts +31 -0
- package/dist/dts/widget/trading/ticket/OrderTicketModal.d.ts +29 -0
- package/dist/dts/widget/trading/ticket/RegistryTradingDockPanel.d.ts +9 -0
- package/dist/dts/widget/trading/ticket/RiskCalculatorView.d.ts +9 -0
- package/dist/dts/widget/trading/ticket/TradingDockPanel.d.ts +36 -0
- package/dist/dts/widget/trading/ticket/orderTicketModel.d.ts +49 -0
- package/dist/dts/widget/trading/visibility/paperVisibility.d.ts +38 -0
- package/dist/dts/widget/types/dataProvider.d.ts +8 -0
- package/dist/dts/widget/types/fullChartHandle.d.ts +25 -0
- package/dist/dts/widget/types/saas.d.ts +83 -0
- package/dist/dts/widget/types/symbolSearch.d.ts +9 -0
- package/dist/dts/widget/types/watchlist.d.ts +26 -0
- package/dist/dts/widget/utils/axisHit.d.ts +16 -0
- package/dist/dts/widget/utils/chartRange.d.ts +12 -0
- package/dist/dts/widget/utils/chartTypeOptions.d.ts +38 -0
- package/dist/dts/widget/utils/clickOutside.d.ts +1 -0
- package/dist/dts/widget/utils/coordinateBridge.d.ts +31 -0
- package/dist/dts/widget/utils/crosshairSync.d.ts +50 -0
- package/dist/dts/widget/utils/drawingSync.d.ts +198 -0
- package/dist/dts/widget/utils/enrichedHash.d.ts +15 -0
- package/dist/dts/widget/utils/headerSync.d.ts +12 -0
- package/dist/dts/widget/utils/numberFormat.d.ts +13 -0
- package/dist/dts/widget/utils/paperTradeAdapters.d.ts +11 -0
- package/dist/dts/widget/utils/pineSeriesData.d.ts +71 -0
- package/dist/dts/widget/utils/rangeSync.d.ts +29 -0
- package/dist/dts/widget/utils/telemetry.d.ts +19 -0
- package/dist/dts/widget/utils/widgetEquality.d.ts +57 -0
- package/dist/dts/widget/utils/widgetErrors.d.ts +29 -0
- package/dist/dts/widget/utils/widgetHelpers.d.ts +395 -0
- package/dist/dts/widget/utils/xAxisTickFormatter.d.ts +33 -0
- package/dist/dts/widget/visibleRangeBridge.d.ts +28 -0
- package/dist/dts/widget/visibleRangeDiagnostics.d.ts +10 -0
- package/dist/dts/widget/visibleRangePolicies.d.ts +6 -0
- package/dist/dts/widget/workspace/WorkspaceManager.d.ts +23 -0
- package/dist/dts/widget/workspace/WorkspaceStore.d.ts +85 -0
- package/dist/dts/widget/workspace/types.d.ts +21 -0
- package/dist/externalBarsNormalization.worker.js +108 -0
- package/dist/iframe/index.html +63 -0
- package/dist/iframe/injected.css +6456 -0
- package/dist/iframe/vninvestchart.iframe.css +2 -0
- package/dist/iframe/vninvestchart.iframe.js +945 -0
- package/dist/iframe-host/host-entry.cjs +687 -0
- package/dist/iframe-host/host-entry.d.mts +818 -0
- package/dist/iframe-host/host-entry.d.ts +818 -0
- package/dist/iframe-host/host-entry.js +680 -0
- package/dist/index.cjs +7253 -0
- package/dist/index.css +3390 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +6461 -0
- package/dist/injected.css +6456 -0
- package/dist/rollAlgorithms-2P2ZKKHO.js +2 -0
- package/dist/types/runtime.cjs +8 -0
- package/dist/types/runtime.d.mts +92 -0
- package/dist/types/runtime.d.ts +92 -0
- package/dist/types/runtime.js +5 -0
- package/dist/types/runtime.umd.js +14 -0
- package/dist/vninvestchart.iife.css +2 -0
- package/dist/vninvestchart.iife.js +945 -0
- package/package.json +225 -5
|
@@ -0,0 +1,2535 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@vninvestcharts/engine-core'), require('react')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', '@vninvestcharts/engine-core', 'react'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VNInvestChartsAdapters = {}, global.engineCore, global.react));
|
|
5
|
+
})(this, (function (exports, engineCore, react) { 'use strict';
|
|
6
|
+
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/lib/adapters/rollAlgorithms.ts
|
|
18
|
+
var rollAlgorithms_exports = {};
|
|
19
|
+
__export(rollAlgorithms_exports, {
|
|
20
|
+
computeRollAdjustment: () => computeRollAdjustment,
|
|
21
|
+
computeSingleRollAdjustment: () => computeSingleRollAdjustment,
|
|
22
|
+
generateTestBarsWithRollGap: () => generateTestBarsWithRollGap,
|
|
23
|
+
generateTestContractChain: () => generateTestContractChain,
|
|
24
|
+
verifyNoPriceGaps: () => verifyNoPriceGaps
|
|
25
|
+
});
|
|
26
|
+
function computeRollAdjustment(input) {
|
|
27
|
+
const { contracts, contractBars, config, from, to } = input;
|
|
28
|
+
const { rollMethod, adjustVolumes } = config;
|
|
29
|
+
const allBars = [];
|
|
30
|
+
const rollPoints = [];
|
|
31
|
+
const warnings = [];
|
|
32
|
+
let cumulativeAdjustment = 0;
|
|
33
|
+
let currentRatioFactor = 1;
|
|
34
|
+
let currentPriceOffset = 0;
|
|
35
|
+
for (let i = 0; i < contracts.length; i++) {
|
|
36
|
+
const contract = contracts[i];
|
|
37
|
+
const bars = contractBars.get(contract.symbol) ?? [];
|
|
38
|
+
const contractStart = Math.max(contract.firstTradeDate, from);
|
|
39
|
+
const contractEnd = Math.min(contract.lastTradeDate, to);
|
|
40
|
+
let relevantBars = bars.filter(
|
|
41
|
+
(b) => b.timestamp >= contractStart && b.timestamp <= contractEnd
|
|
42
|
+
);
|
|
43
|
+
if (relevantBars.length === 0) {
|
|
44
|
+
warnings.push(`No data for contract ${contract.symbol} in range [${new Date(contractStart).toISOString()}, ${new Date(contractEnd).toISOString()}]`);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (i > 0) {
|
|
48
|
+
relevantBars = applyCumulativeAdjustment(relevantBars, rollMethod, currentRatioFactor, currentPriceOffset, adjustVolumes);
|
|
49
|
+
}
|
|
50
|
+
allBars.push(...relevantBars);
|
|
51
|
+
if (i < contracts.length - 1) {
|
|
52
|
+
const nextContract = contracts[i + 1];
|
|
53
|
+
const nextBars = contractBars.get(nextContract.symbol) ?? [];
|
|
54
|
+
const rollDate = contract.lastTradeDate;
|
|
55
|
+
const fromBar = findBarAtOrBefore(relevantBars, rollDate);
|
|
56
|
+
const toBar = findBarAtOrAfter(nextBars, rollDate);
|
|
57
|
+
if (fromBar && toBar) {
|
|
58
|
+
const fromPrice = fromBar.close;
|
|
59
|
+
const toPrice = toBar.close;
|
|
60
|
+
const fromBarRaw = findBarAtOrBefore(bars, rollDate);
|
|
61
|
+
const rawFromPrice = fromBarRaw ? fromBarRaw.close : fromPrice;
|
|
62
|
+
const rawToPrice = toPrice;
|
|
63
|
+
let adjustmentFactor = 1;
|
|
64
|
+
let priceOffset = 0;
|
|
65
|
+
if (rollMethod === "ratio") {
|
|
66
|
+
adjustmentFactor = rawFromPrice / rawToPrice;
|
|
67
|
+
currentRatioFactor *= adjustmentFactor;
|
|
68
|
+
} else if (rollMethod === "panama") {
|
|
69
|
+
priceOffset = rawFromPrice - rawToPrice;
|
|
70
|
+
currentPriceOffset += priceOffset;
|
|
71
|
+
cumulativeAdjustment += priceOffset;
|
|
72
|
+
} else if (rollMethod === "backward-ratio") {
|
|
73
|
+
adjustmentFactor = rawToPrice / rawFromPrice;
|
|
74
|
+
currentRatioFactor *= adjustmentFactor;
|
|
75
|
+
}
|
|
76
|
+
const adjustedFromPrice = fromPrice;
|
|
77
|
+
let adjustedToPrice = toPrice;
|
|
78
|
+
if (rollMethod === "ratio") {
|
|
79
|
+
adjustedToPrice = toPrice * currentRatioFactor;
|
|
80
|
+
} else if (rollMethod === "panama") {
|
|
81
|
+
adjustedToPrice = toPrice + currentPriceOffset;
|
|
82
|
+
} else if (rollMethod === "backward-ratio") {
|
|
83
|
+
adjustedToPrice = toPrice / currentRatioFactor;
|
|
84
|
+
}
|
|
85
|
+
rollPoints.push({
|
|
86
|
+
timestamp: rollDate,
|
|
87
|
+
fromContract: contract.symbol,
|
|
88
|
+
toContract: nextContract.symbol,
|
|
89
|
+
fromPrice: adjustedFromPrice,
|
|
90
|
+
toPrice: adjustedToPrice,
|
|
91
|
+
adjustmentFactor: rollMethod === "panama" ? priceOffset : adjustmentFactor,
|
|
92
|
+
cumulativeAdjustment,
|
|
93
|
+
method: rollMethod
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
warnings.push(`Could not determine roll prices at ${new Date(rollDate).toISOString()} for ${contract.symbol} -> ${nextContract.symbol}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
allBars.sort((a, b) => a.timestamp - b.timestamp);
|
|
101
|
+
const dedupedBars = deduplicateBars(allBars);
|
|
102
|
+
return {
|
|
103
|
+
bars: dedupedBars,
|
|
104
|
+
rollPoints,
|
|
105
|
+
totalCumulativeAdjustment: cumulativeAdjustment,
|
|
106
|
+
warnings
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function applyCumulativeAdjustment(bars, method, ratioFactor, priceOffset, adjustVolumes) {
|
|
110
|
+
return bars.map((bar) => {
|
|
111
|
+
let { open, high, low, close, volume } = bar;
|
|
112
|
+
if (method === "ratio") {
|
|
113
|
+
open *= ratioFactor;
|
|
114
|
+
high *= ratioFactor;
|
|
115
|
+
low *= ratioFactor;
|
|
116
|
+
close *= ratioFactor;
|
|
117
|
+
if (adjustVolumes) {
|
|
118
|
+
volume = Math.round(volume / ratioFactor);
|
|
119
|
+
}
|
|
120
|
+
} else if (method === "panama") {
|
|
121
|
+
open += priceOffset;
|
|
122
|
+
high += priceOffset;
|
|
123
|
+
low += priceOffset;
|
|
124
|
+
close += priceOffset;
|
|
125
|
+
} else if (method === "backward-ratio") {
|
|
126
|
+
open /= ratioFactor;
|
|
127
|
+
high /= ratioFactor;
|
|
128
|
+
low /= ratioFactor;
|
|
129
|
+
close /= ratioFactor;
|
|
130
|
+
if (adjustVolumes) {
|
|
131
|
+
volume = Math.round(volume * ratioFactor);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { ...bar, open, high, low, close, volume };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function findBarAtOrBefore(bars, timestamp) {
|
|
138
|
+
let result = null;
|
|
139
|
+
for (const bar of bars) {
|
|
140
|
+
if (bar.timestamp <= timestamp) {
|
|
141
|
+
result = bar;
|
|
142
|
+
} else {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
function findBarAtOrAfter(bars, timestamp) {
|
|
149
|
+
for (const bar of bars) {
|
|
150
|
+
if (bar.timestamp >= timestamp) {
|
|
151
|
+
return bar;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
function deduplicateBars(bars) {
|
|
157
|
+
const seen = /* @__PURE__ */ new Map();
|
|
158
|
+
for (const bar of bars) {
|
|
159
|
+
const existing = seen.get(bar.timestamp);
|
|
160
|
+
if (!existing || bar.volume > existing.volume) {
|
|
161
|
+
seen.set(bar.timestamp, bar);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return Array.from(seen.values()).sort((a, b) => a.timestamp - b.timestamp);
|
|
165
|
+
}
|
|
166
|
+
function computeSingleRollAdjustment(fromPrice, toPrice, method) {
|
|
167
|
+
if (method === "ratio" && toPrice === 0) {
|
|
168
|
+
throw new Error("toPrice cannot be zero for ratio roll adjustment");
|
|
169
|
+
}
|
|
170
|
+
if (method === "backward-ratio" && fromPrice === 0) {
|
|
171
|
+
throw new Error("fromPrice cannot be zero for backward-ratio roll adjustment");
|
|
172
|
+
}
|
|
173
|
+
switch (method) {
|
|
174
|
+
case "ratio":
|
|
175
|
+
return { factor: fromPrice / toPrice, offset: 0 };
|
|
176
|
+
case "panama":
|
|
177
|
+
return { factor: 1, offset: fromPrice - toPrice };
|
|
178
|
+
case "backward-ratio":
|
|
179
|
+
return { factor: toPrice / fromPrice, offset: 0 };
|
|
180
|
+
default:
|
|
181
|
+
throw new Error(`Unknown roll method: ${method}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function verifyNoPriceGaps(rollPoints, thresholdPercent = 0.1) {
|
|
185
|
+
const gaps = rollPoints.map((rp) => {
|
|
186
|
+
const gapPercent = Math.abs((rp.fromPrice - rp.toPrice) / rp.fromPrice) * 100;
|
|
187
|
+
return { rollPoint: rp, gapPercent };
|
|
188
|
+
});
|
|
189
|
+
const maxGap = Math.max(...gaps.map((g) => g.gapPercent), 0);
|
|
190
|
+
return {
|
|
191
|
+
passed: maxGap <= thresholdPercent,
|
|
192
|
+
gaps
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function generateTestContractChain(rootSymbol, count, startDate, intervalDays = 90) {
|
|
196
|
+
const contracts = [];
|
|
197
|
+
const monthCodes = ["F", "G", "H", "J", "K", "M", "N", "Q", "U", "V", "X", "Z"];
|
|
198
|
+
for (let i = 0; i < count; i++) {
|
|
199
|
+
const firstTrade = startDate + i * intervalDays * 24 * 60 * 60 * 1e3;
|
|
200
|
+
const expiry = firstTrade + intervalDays * 24 * 60 * 60 * 1e3;
|
|
201
|
+
const lastTrade = expiry - 3 * 24 * 60 * 60 * 1e3;
|
|
202
|
+
const monthCode = monthCodes[i % 12];
|
|
203
|
+
const year = Math.floor(startDate / (365 * 24 * 60 * 60 * 1e3) + i / 4) % 100;
|
|
204
|
+
contracts.push({
|
|
205
|
+
symbol: `${rootSymbol}${year.toString().padStart(2, "0")}${monthCode}`,
|
|
206
|
+
rootSymbol,
|
|
207
|
+
expiry,
|
|
208
|
+
firstTradeDate: firstTrade,
|
|
209
|
+
lastTradeDate: lastTrade,
|
|
210
|
+
monthCode,
|
|
211
|
+
year,
|
|
212
|
+
tickSize: 0.1,
|
|
213
|
+
multiplier: 1e5,
|
|
214
|
+
exchange: "VNX"
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return contracts;
|
|
218
|
+
}
|
|
219
|
+
function generateTestBarsWithRollGap(contracts, rollMethod, gapPercent = 0) {
|
|
220
|
+
const barsMap = /* @__PURE__ */ new Map();
|
|
221
|
+
let basePrice = 1e3;
|
|
222
|
+
for (let i = 0; i < contracts.length; i++) {
|
|
223
|
+
const contract = contracts[i];
|
|
224
|
+
const bars = [];
|
|
225
|
+
const days = Math.ceil((contract.lastTradeDate - contract.firstTradeDate) / (24 * 60 * 60 * 1e3));
|
|
226
|
+
if (i > 0 && gapPercent !== 0) {
|
|
227
|
+
const gapFactor = 1 + gapPercent / 100;
|
|
228
|
+
if (rollMethod === "ratio") {
|
|
229
|
+
basePrice *= gapFactor;
|
|
230
|
+
} else if (rollMethod === "panama") {
|
|
231
|
+
basePrice += basePrice * gapPercent / 100;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
for (let d = 0; d < days; d++) {
|
|
235
|
+
const timestamp = contract.firstTradeDate + d * 24 * 60 * 60 * 1e3;
|
|
236
|
+
const date = new Date(timestamp);
|
|
237
|
+
if (date.getDay() === 0 || date.getDay() === 6) continue;
|
|
238
|
+
const volatility = 0.01;
|
|
239
|
+
const change = (Math.random() - 0.5) * 2 * volatility * basePrice;
|
|
240
|
+
basePrice = Math.max(10, basePrice + change);
|
|
241
|
+
const open = basePrice * (1 + (Math.random() - 0.5) * 3e-3);
|
|
242
|
+
const close = basePrice * (1 + (Math.random() - 0.5) * 3e-3);
|
|
243
|
+
const high = Math.max(open, close) * (1 + Math.random() * 3e-3);
|
|
244
|
+
const low = Math.min(open, close) * (1 - Math.random() * 3e-3);
|
|
245
|
+
const volume = Math.floor(1e4 + Math.random() * 5e4);
|
|
246
|
+
bars.push({ timestamp, open, high, low, close, volume });
|
|
247
|
+
}
|
|
248
|
+
barsMap.set(contract.symbol, bars);
|
|
249
|
+
}
|
|
250
|
+
return barsMap;
|
|
251
|
+
}
|
|
252
|
+
var init_rollAlgorithms = __esm({
|
|
253
|
+
"src/lib/adapters/rollAlgorithms.ts"() {
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// src/widget/adapters/barsPagingPolicy.ts
|
|
258
|
+
function asFiniteNumber(value) {
|
|
259
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
260
|
+
return value;
|
|
261
|
+
}
|
|
262
|
+
if (typeof value === "string") {
|
|
263
|
+
const parsed = Number(value.trim());
|
|
264
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
function resolveOldestBarTimestamp(bars) {
|
|
269
|
+
if (bars.length === 0) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
let oldest = Number.POSITIVE_INFINITY;
|
|
273
|
+
for (const bar of bars) {
|
|
274
|
+
const ts = bar.date instanceof Date ? bar.date.valueOf() : new Date(bar.date).valueOf();
|
|
275
|
+
if (Number.isFinite(ts) && ts < oldest) {
|
|
276
|
+
oldest = ts;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return Number.isFinite(oldest) ? oldest : null;
|
|
280
|
+
}
|
|
281
|
+
function normalizeBarsPagingMeta(payload, bars) {
|
|
282
|
+
const explicitHasMoreBefore = payload.has_more_before;
|
|
283
|
+
const explicitHasMoreAfter = payload.has_more_after;
|
|
284
|
+
const explicitNextBefore = asFiniteNumber(payload.next_before);
|
|
285
|
+
const explicitNextAfter = asFiniteNumber(payload.next_after);
|
|
286
|
+
const snapshotId = typeof payload.snapshot_id === "string" ? payload.snapshot_id : null;
|
|
287
|
+
const oldestBarTs = resolveOldestBarTimestamp(bars);
|
|
288
|
+
const nextBefore = explicitNextBefore ?? oldestBarTs;
|
|
289
|
+
const hasMoreBefore = typeof explicitHasMoreBefore === "boolean" ? explicitHasMoreBefore : nextBefore !== null;
|
|
290
|
+
const hasMoreAfter = typeof explicitHasMoreAfter === "boolean" ? explicitHasMoreAfter : false;
|
|
291
|
+
return {
|
|
292
|
+
hasMoreBefore,
|
|
293
|
+
hasMoreAfter,
|
|
294
|
+
nextBefore: hasMoreBefore ? nextBefore : null,
|
|
295
|
+
nextAfter: hasMoreAfter ? explicitNextAfter : null,
|
|
296
|
+
snapshotId
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function buildBackwardPageRequest(meta, fallbackBefore, limit) {
|
|
300
|
+
if (!meta.hasMoreBefore) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
const before = meta.nextBefore ?? fallbackBefore;
|
|
304
|
+
if (!Number.isFinite(before)) {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
before,
|
|
309
|
+
limit,
|
|
310
|
+
snapshotId: meta.snapshotId ?? void 0
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/widget/adapters/nativeShapes.ts
|
|
315
|
+
var BARS_LIMIT_MAX = 1e3;
|
|
316
|
+
function clampBarsLimit(raw) {
|
|
317
|
+
if (raw == null) return raw;
|
|
318
|
+
if (!Number.isFinite(raw)) return BARS_LIMIT_MAX;
|
|
319
|
+
return Math.max(1, Math.min(BARS_LIMIT_MAX, Math.floor(raw)));
|
|
320
|
+
}
|
|
321
|
+
var ORDER_FLOW_WINDOW_MINUTES = { "5m": 5, "15m": 15, "30m": 30, "1h": 60 };
|
|
322
|
+
var DARK_FLOW_SIGNAL_TYPES = ["none", "absorption", "iceberg", "twap_bot", "mixed"];
|
|
323
|
+
function parseDarkFlowSignalType(value) {
|
|
324
|
+
return typeof value === "string" && DARK_FLOW_SIGNAL_TYPES.includes(value) ? value : "none";
|
|
325
|
+
}
|
|
326
|
+
function asFiniteNumber2(value) {
|
|
327
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
328
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
329
|
+
const parsed = Number(value);
|
|
330
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
function mapNativeSignal(s) {
|
|
335
|
+
return {
|
|
336
|
+
id: typeof s["id"] === "number" || typeof s["id"] === "string" ? s["id"] : -1,
|
|
337
|
+
symbol: typeof s["symbol"] === "string" ? s["symbol"] : "",
|
|
338
|
+
signal_type: typeof s["signal_type"] === "string" ? s["signal_type"] : "unknown",
|
|
339
|
+
strength: asFiniteNumber2(s["strength"]) ?? 0,
|
|
340
|
+
confidence: asFiniteNumber2(s["confidence"]) ?? 0,
|
|
341
|
+
tick_rate: asFiniteNumber2(s["tick_rate"]) ?? 0,
|
|
342
|
+
small_order_ratio: asFiniteNumber2(s["small_order_ratio"]) ?? 0,
|
|
343
|
+
cvd_at_signal: asFiniteNumber2(s["cvd_at_signal"]),
|
|
344
|
+
price_at_signal: asFiniteNumber2(s["price_at_signal"]),
|
|
345
|
+
created_at: typeof s["created_at"] === "string" ? s["created_at"] : "",
|
|
346
|
+
event_id: typeof s["event_id"] === "string" ? s["event_id"] : null,
|
|
347
|
+
severity: typeof s["severity"] === "string" ? s["severity"] : void 0
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function mapNativeOrderFlow(obj, window2) {
|
|
351
|
+
const rawSignals = Array.isArray(obj["signals"]) ? obj["signals"] : [];
|
|
352
|
+
const darkRaw = obj["dark_flow"] ?? {};
|
|
353
|
+
const latestRaw = obj["latest_signal"] ?? null;
|
|
354
|
+
return {
|
|
355
|
+
ok: true,
|
|
356
|
+
symbol: typeof obj["symbol"] === "string" ? obj["symbol"] : "",
|
|
357
|
+
window: typeof obj["window"] === "string" ? obj["window"] : window2,
|
|
358
|
+
window_minutes: ORDER_FLOW_WINDOW_MINUTES[window2] ?? 30,
|
|
359
|
+
latest_signal: latestRaw ? mapNativeSignal(latestRaw) : null,
|
|
360
|
+
signals: rawSignals.map(mapNativeSignal),
|
|
361
|
+
cvd_series: [],
|
|
362
|
+
dark_flow: {
|
|
363
|
+
signal_type: parseDarkFlowSignalType(darkRaw["signal_type"]),
|
|
364
|
+
strength: asFiniteNumber2(darkRaw["strength"]) ?? 0,
|
|
365
|
+
confidence: asFiniteNumber2(darkRaw["confidence"]) ?? 0,
|
|
366
|
+
components: darkRaw["components"],
|
|
367
|
+
triggered_at: typeof darkRaw["triggered_at"] === "string" ? darkRaw["triggered_at"] : null
|
|
368
|
+
},
|
|
369
|
+
tick_rate: obj["tick_rate"] ?? {},
|
|
370
|
+
size_distribution: obj["size_distribution"] ?? {}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
var ZERO_WHALE_SUMMARY = {
|
|
374
|
+
buy_value: 0,
|
|
375
|
+
sell_value: 0,
|
|
376
|
+
shark_buy: 0,
|
|
377
|
+
shark_sell: 0,
|
|
378
|
+
whale_buy: 0,
|
|
379
|
+
whale_sell: 0,
|
|
380
|
+
small_buy: 0,
|
|
381
|
+
small_sell: 0
|
|
382
|
+
};
|
|
383
|
+
function mapNativeWhaleFeed(obj) {
|
|
384
|
+
const rawAlerts = Array.isArray(obj["alerts"]) ? obj["alerts"] : [];
|
|
385
|
+
const alerts = rawAlerts.map((a) => ({
|
|
386
|
+
id: typeof a["id"] === "string" ? a["id"] : "",
|
|
387
|
+
alert_type: typeof a["alert_type"] === "string" ? a["alert_type"] : "signal",
|
|
388
|
+
severity: typeof a["severity"] === "string" ? a["severity"] : "",
|
|
389
|
+
title: typeof a["title"] === "string" ? a["title"] : "",
|
|
390
|
+
value: typeof a["value"] === "string" || typeof a["value"] === "number" ? a["value"] : null,
|
|
391
|
+
direction: typeof a["direction"] === "string" ? a["direction"] : "",
|
|
392
|
+
triggered_at: typeof a["triggered_at"] === "string" ? a["triggered_at"] : ""
|
|
393
|
+
}));
|
|
394
|
+
return {
|
|
395
|
+
symbol: typeof obj["symbol"] === "string" ? obj["symbol"] : "",
|
|
396
|
+
whale_orders: [],
|
|
397
|
+
summary: { ...ZERO_WHALE_SUMMARY },
|
|
398
|
+
alerts
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function mapNativeTape(obj) {
|
|
402
|
+
const rawTrades = Array.isArray(obj["trades"]) ? obj["trades"] : [];
|
|
403
|
+
const entries = rawTrades.map((t, index) => {
|
|
404
|
+
const price = asFiniteNumber2(t["price"]) ?? 0;
|
|
405
|
+
const volume = asFiniteNumber2(t["volume"]) ?? 0;
|
|
406
|
+
const sideRaw = typeof t["side"] === "string" ? t["side"].toLowerCase() : "";
|
|
407
|
+
const side = sideRaw === "buy" || sideRaw === "sell" ? sideRaw : "unknown";
|
|
408
|
+
const ts = typeof t["ts"] === "string" ? t["ts"] : typeof t["time"] === "string" ? t["time"] : "";
|
|
409
|
+
return {
|
|
410
|
+
id: `${ts}|${price}|${volume}|${index}`,
|
|
411
|
+
ts,
|
|
412
|
+
price,
|
|
413
|
+
volume,
|
|
414
|
+
value: price * volume,
|
|
415
|
+
side,
|
|
416
|
+
is_whale: false
|
|
417
|
+
};
|
|
418
|
+
});
|
|
419
|
+
return {
|
|
420
|
+
ok: true,
|
|
421
|
+
symbol: typeof obj["symbol"] === "string" ? obj["symbol"] : "",
|
|
422
|
+
entries
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/lib/cache/cacheDebug.ts
|
|
427
|
+
typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
|
|
428
|
+
var qs = () => typeof window !== "undefined" ? new URLSearchParams(window.location.search) : new URLSearchParams();
|
|
429
|
+
function getCacheTTL(defaultMs) {
|
|
430
|
+
const override = Number(qs().get("bars-ttl"));
|
|
431
|
+
return Number.isFinite(override) && override > 0 ? override : defaultMs;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// src/widget/history/historyDataUtils.ts
|
|
435
|
+
function resolveTimeframeMs(timeframe) {
|
|
436
|
+
const result = (() => {
|
|
437
|
+
switch (timeframe) {
|
|
438
|
+
case "1m":
|
|
439
|
+
return 6e4;
|
|
440
|
+
case "3m":
|
|
441
|
+
return 3 * 6e4;
|
|
442
|
+
case "5m":
|
|
443
|
+
return 5 * 6e4;
|
|
444
|
+
case "15m":
|
|
445
|
+
return 15 * 6e4;
|
|
446
|
+
case "30m":
|
|
447
|
+
return 30 * 6e4;
|
|
448
|
+
case "1h":
|
|
449
|
+
return 60 * 6e4;
|
|
450
|
+
case "2h":
|
|
451
|
+
return 2 * 60 * 6e4;
|
|
452
|
+
case "4h":
|
|
453
|
+
return 4 * 60 * 6e4;
|
|
454
|
+
case "1d":
|
|
455
|
+
case "1D":
|
|
456
|
+
return 24 * 60 * 6e4;
|
|
457
|
+
case "1w":
|
|
458
|
+
case "1W":
|
|
459
|
+
return 7 * 24 * 60 * 6e4;
|
|
460
|
+
case "1M":
|
|
461
|
+
return 30 * 24 * 60 * 6e4;
|
|
462
|
+
default:
|
|
463
|
+
return 24 * 60 * 6e4;
|
|
464
|
+
}
|
|
465
|
+
})();
|
|
466
|
+
return result;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/widget/data/DataEnvelope.ts
|
|
470
|
+
var NUMERIC_SENTINELS = /* @__PURE__ */ new Set(["", "-", "--", "N/A", "n/a", "null", "undefined", "NaN"]);
|
|
471
|
+
function toNum(value) {
|
|
472
|
+
if (typeof value === "number") {
|
|
473
|
+
return Number.isFinite(value) ? value : null;
|
|
474
|
+
}
|
|
475
|
+
if (typeof value === "string") {
|
|
476
|
+
const trimmed = value.trim();
|
|
477
|
+
if (NUMERIC_SENTINELS.has(trimmed)) return null;
|
|
478
|
+
const parsed = Number(trimmed);
|
|
479
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
480
|
+
}
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
function toNumOrZero(value) {
|
|
484
|
+
const n = toNum(value);
|
|
485
|
+
return n === null ? 0 : n;
|
|
486
|
+
}
|
|
487
|
+
var DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
488
|
+
var NAIVE_DATETIME_RE = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(:\d{2})?(\.\d+)?$/;
|
|
489
|
+
function toTimestampMs(value) {
|
|
490
|
+
if (value instanceof Date) {
|
|
491
|
+
const t = value.getTime();
|
|
492
|
+
return Number.isFinite(t) ? t : null;
|
|
493
|
+
}
|
|
494
|
+
if (typeof value === "number") {
|
|
495
|
+
if (!Number.isFinite(value) || value <= 0) return null;
|
|
496
|
+
return value < 1e11 ? Math.round(value * 1e3) : Math.round(value);
|
|
497
|
+
}
|
|
498
|
+
if (typeof value !== "string") return null;
|
|
499
|
+
const trimmed = value.trim();
|
|
500
|
+
if (NUMERIC_SENTINELS.has(trimmed)) return null;
|
|
501
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) return toTimestampMs(Number(trimmed));
|
|
502
|
+
if (DATE_ONLY_RE.test(trimmed)) return Date.parse(`${trimmed}T00:00:00Z`);
|
|
503
|
+
if (NAIVE_DATETIME_RE.test(trimmed)) {
|
|
504
|
+
const iso = trimmed.replace(" ", "T");
|
|
505
|
+
const withSeconds = iso.length === 16 ? `${iso}:00` : iso;
|
|
506
|
+
return Date.parse(`${withSeconds}Z`);
|
|
507
|
+
}
|
|
508
|
+
const parsed = Date.parse(trimmed);
|
|
509
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
510
|
+
}
|
|
511
|
+
function readRecord(raw) {
|
|
512
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
513
|
+
return raw;
|
|
514
|
+
}
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
function readBarTimestamp(rec) {
|
|
518
|
+
for (const key of ["timestamp", "date", "time", "t", "ts"]) {
|
|
519
|
+
const ms = toTimestampMs(rec[key]);
|
|
520
|
+
if (ms !== null) return ms;
|
|
521
|
+
}
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
function readBarSource(rec) {
|
|
525
|
+
if (rec.timestamp !== void 0) return "bars";
|
|
526
|
+
if (rec.date !== void 0 || rec.time !== void 0) return "chart";
|
|
527
|
+
return "unknown";
|
|
528
|
+
}
|
|
529
|
+
function normalizeBar(raw) {
|
|
530
|
+
const rec = readRecord(raw);
|
|
531
|
+
if (!rec) return null;
|
|
532
|
+
const timestamp = readBarTimestamp(rec);
|
|
533
|
+
if (timestamp === null) return null;
|
|
534
|
+
return {
|
|
535
|
+
timestamp,
|
|
536
|
+
open: toNum(rec.open ?? rec.o),
|
|
537
|
+
high: toNum(rec.high ?? rec.h),
|
|
538
|
+
low: toNum(rec.low ?? rec.l),
|
|
539
|
+
close: toNum(rec.close ?? rec.c),
|
|
540
|
+
volume: toNumOrZero(rec.volume ?? rec.v),
|
|
541
|
+
adjClose: toNum(rec.adj_close ?? rec.adjClose),
|
|
542
|
+
adjFactor: toNum(rec.adj_factor ?? rec.adjFactor),
|
|
543
|
+
isPartial: rec.isPartial === true || rec.is_partial === true,
|
|
544
|
+
source: readBarSource(rec)
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/lib/utils/storage.ts
|
|
549
|
+
function getSafeStorage() {
|
|
550
|
+
try {
|
|
551
|
+
if (typeof localStorage === "undefined") return null;
|
|
552
|
+
return localStorage;
|
|
553
|
+
} catch {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function readString(key) {
|
|
558
|
+
const store = getSafeStorage();
|
|
559
|
+
if (!store) return null;
|
|
560
|
+
try {
|
|
561
|
+
return store.getItem(key);
|
|
562
|
+
} catch {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function writeString(key, value) {
|
|
567
|
+
const store = getSafeStorage();
|
|
568
|
+
if (!store) return false;
|
|
569
|
+
try {
|
|
570
|
+
store.setItem(key, value);
|
|
571
|
+
return true;
|
|
572
|
+
} catch {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
function removeKey(key) {
|
|
577
|
+
const store = getSafeStorage();
|
|
578
|
+
if (!store) return false;
|
|
579
|
+
try {
|
|
580
|
+
store.removeItem(key);
|
|
581
|
+
return true;
|
|
582
|
+
} catch {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/lib/credentials/credentialStore.ts
|
|
588
|
+
var LICENSE_STORAGE_KEY = "vni_license_key";
|
|
589
|
+
var PAT_STORAGE_KEY = "vni_pat";
|
|
590
|
+
var PENDING_MIGRATION_WARNING = "pending-license-migration";
|
|
591
|
+
var migrationState = "idle";
|
|
592
|
+
function isLicenseMigrationPending() {
|
|
593
|
+
return migrationState === "pending";
|
|
594
|
+
}
|
|
595
|
+
function isLicenseBlob(value) {
|
|
596
|
+
if (!value) return false;
|
|
597
|
+
return engineCore.parseLicenseKey(value) !== null;
|
|
598
|
+
}
|
|
599
|
+
function readRaw(key) {
|
|
600
|
+
return readString(key) ?? "";
|
|
601
|
+
}
|
|
602
|
+
function writeRaw(key, value) {
|
|
603
|
+
writeString(key, value);
|
|
604
|
+
}
|
|
605
|
+
function warnOnce(message) {
|
|
606
|
+
try {
|
|
607
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") return;
|
|
608
|
+
} catch {
|
|
609
|
+
}
|
|
610
|
+
console.warn(`[credentialStore] ${message}`);
|
|
611
|
+
}
|
|
612
|
+
function removeRaw(key) {
|
|
613
|
+
removeKey(key);
|
|
614
|
+
}
|
|
615
|
+
var sessionPat = null;
|
|
616
|
+
function readPat() {
|
|
617
|
+
if (sessionPat !== null) {
|
|
618
|
+
if (!sessionPat || isLicenseBlob(sessionPat)) return "";
|
|
619
|
+
return sessionPat;
|
|
620
|
+
}
|
|
621
|
+
const stored = readRaw(PAT_STORAGE_KEY);
|
|
622
|
+
if (!stored) return "";
|
|
623
|
+
if (isLicenseBlob(stored)) return "";
|
|
624
|
+
return stored;
|
|
625
|
+
}
|
|
626
|
+
function readSessionPat() {
|
|
627
|
+
if (sessionPat === null || !sessionPat || isLicenseBlob(sessionPat)) return "";
|
|
628
|
+
return sessionPat;
|
|
629
|
+
}
|
|
630
|
+
function writePat(value) {
|
|
631
|
+
const trimmed = (value ?? "").trim();
|
|
632
|
+
if (!trimmed) {
|
|
633
|
+
clearPat();
|
|
634
|
+
return { ok: true };
|
|
635
|
+
}
|
|
636
|
+
if (isLicenseBlob(trimmed)) {
|
|
637
|
+
warnOnce("refused a license blob on the PAT channel");
|
|
638
|
+
return { ok: false, reason: "is-license-blob" };
|
|
639
|
+
}
|
|
640
|
+
if (isLicenseMigrationPending()) {
|
|
641
|
+
warnOnce(`${PENDING_MIGRATION_WARNING}: PAT not saved, a license key is still being migrated`);
|
|
642
|
+
return { ok: false, reason: "migration-pending" };
|
|
643
|
+
}
|
|
644
|
+
sessionPat = trimmed;
|
|
645
|
+
writeRaw(PAT_STORAGE_KEY, trimmed);
|
|
646
|
+
return { ok: true };
|
|
647
|
+
}
|
|
648
|
+
function clearPat() {
|
|
649
|
+
sessionPat = null;
|
|
650
|
+
removeRaw(PAT_STORAGE_KEY);
|
|
651
|
+
}
|
|
652
|
+
var inFlight = null;
|
|
653
|
+
async function migrateLegacyLicenseSlot() {
|
|
654
|
+
if (inFlight) return inFlight;
|
|
655
|
+
migrationState = "pending";
|
|
656
|
+
inFlight = (async () => {
|
|
657
|
+
if (readRaw(LICENSE_STORAGE_KEY)) return { migrated: false, reason: "already-migrated" };
|
|
658
|
+
const stored = readRaw(PAT_STORAGE_KEY);
|
|
659
|
+
if (!stored) return { migrated: false, reason: "not-a-license" };
|
|
660
|
+
if (!isLicenseBlob(stored)) return { migrated: false, reason: "not-a-license" };
|
|
661
|
+
writeRaw(LICENSE_STORAGE_KEY, stored);
|
|
662
|
+
const verified = await engineCore.verifyLicenseKey(stored, { host: typeof location !== "undefined" ? location.hostname : "" });
|
|
663
|
+
if (verified) {
|
|
664
|
+
if (readRaw(LICENSE_STORAGE_KEY) === stored) ;
|
|
665
|
+
if (readRaw(PAT_STORAGE_KEY) === stored) {
|
|
666
|
+
sessionPat = null;
|
|
667
|
+
removeRaw(PAT_STORAGE_KEY);
|
|
668
|
+
}
|
|
669
|
+
return { migrated: true, reason: "already-migrated" };
|
|
670
|
+
}
|
|
671
|
+
if (readRaw(LICENSE_STORAGE_KEY) === stored) {
|
|
672
|
+
removeRaw(LICENSE_STORAGE_KEY);
|
|
673
|
+
}
|
|
674
|
+
return { migrated: false, reason: "verify-failed" };
|
|
675
|
+
})();
|
|
676
|
+
try {
|
|
677
|
+
return await inFlight;
|
|
678
|
+
} finally {
|
|
679
|
+
inFlight = null;
|
|
680
|
+
migrationState = "settled";
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
void migrateLegacyLicenseSlot();
|
|
684
|
+
|
|
685
|
+
// src/widget/adapters/VNInvestClient.ts
|
|
686
|
+
var API_BASE_STORAGE_KEY = "vni_api_base";
|
|
687
|
+
var DEFAULT_API_BASE = "https://127.0.0.1";
|
|
688
|
+
var DEFAULT_PAT = "";
|
|
689
|
+
function normalizeApiBase(value) {
|
|
690
|
+
const trimmed = value.trim();
|
|
691
|
+
return trimmed.replace(/\/+$/, "");
|
|
692
|
+
}
|
|
693
|
+
function resolveBrowserOrigin() {
|
|
694
|
+
try {
|
|
695
|
+
if (typeof location === "undefined" || typeof location.origin !== "string") {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
const normalizedOrigin = normalizeApiBase(location.origin);
|
|
699
|
+
if (!/^https?:\/\//i.test(normalizedOrigin)) {
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
return normalizedOrigin;
|
|
703
|
+
} catch {
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function resolveApiBase() {
|
|
708
|
+
try {
|
|
709
|
+
const globalScope = globalThis;
|
|
710
|
+
if (typeof globalScope.__VNI_API_BASE__ === "string") {
|
|
711
|
+
return normalizeApiBase(globalScope.__VNI_API_BASE__);
|
|
712
|
+
}
|
|
713
|
+
} catch {
|
|
714
|
+
}
|
|
715
|
+
try {
|
|
716
|
+
if (typeof localStorage !== "undefined") {
|
|
717
|
+
const stored = localStorage.getItem(API_BASE_STORAGE_KEY);
|
|
718
|
+
if (stored) return normalizeApiBase(stored);
|
|
719
|
+
}
|
|
720
|
+
} catch {
|
|
721
|
+
}
|
|
722
|
+
const browserOrigin = resolveBrowserOrigin();
|
|
723
|
+
if (browserOrigin) {
|
|
724
|
+
return browserOrigin;
|
|
725
|
+
}
|
|
726
|
+
return DEFAULT_API_BASE;
|
|
727
|
+
}
|
|
728
|
+
var TIMEFRAME_MAP = {
|
|
729
|
+
"1m": "1m",
|
|
730
|
+
"5m": "5m",
|
|
731
|
+
"15m": "15m",
|
|
732
|
+
"1h": "1H",
|
|
733
|
+
"1H": "1H",
|
|
734
|
+
"2H": "2H",
|
|
735
|
+
"4H": "4H",
|
|
736
|
+
"1d": "D",
|
|
737
|
+
"1D": "D",
|
|
738
|
+
"d": "D",
|
|
739
|
+
"D": "D",
|
|
740
|
+
"W": "W",
|
|
741
|
+
"M": "M",
|
|
742
|
+
"Y": "Y"
|
|
743
|
+
};
|
|
744
|
+
function normalizeTimeframe(tf) {
|
|
745
|
+
const result = TIMEFRAME_MAP[tf] ?? tf;
|
|
746
|
+
return result;
|
|
747
|
+
}
|
|
748
|
+
function isCurrentCandleResponse(response, timeframe) {
|
|
749
|
+
const bars = response.bars;
|
|
750
|
+
if (bars.length === 0) return false;
|
|
751
|
+
const lastBar = bars[bars.length - 1];
|
|
752
|
+
const tfMs = resolveTimeframeMs(timeframe);
|
|
753
|
+
return Date.now() - lastBar.date.getTime() < tfMs;
|
|
754
|
+
}
|
|
755
|
+
function asObject(value) {
|
|
756
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
757
|
+
return value;
|
|
758
|
+
}
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
function normalizeChartPoint(value) {
|
|
762
|
+
const bar = normalizeBar(value);
|
|
763
|
+
if (!bar) return null;
|
|
764
|
+
if (bar.open === null || bar.high === null || bar.low === null || bar.close === null) {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
return {
|
|
768
|
+
date: new Date(bar.timestamp).toISOString(),
|
|
769
|
+
open: bar.open,
|
|
770
|
+
high: bar.high,
|
|
771
|
+
low: bar.low,
|
|
772
|
+
close: bar.close,
|
|
773
|
+
volume: bar.volume
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
function extractChartPoints(data) {
|
|
777
|
+
if (Array.isArray(data)) {
|
|
778
|
+
return data.map(normalizeChartPoint).filter((point) => point !== null);
|
|
779
|
+
}
|
|
780
|
+
const payload = asObject(data);
|
|
781
|
+
if (!payload) {
|
|
782
|
+
return [];
|
|
783
|
+
}
|
|
784
|
+
const nestedData = asObject(payload.data);
|
|
785
|
+
const candidates = [
|
|
786
|
+
payload.points,
|
|
787
|
+
nestedData?.points,
|
|
788
|
+
payload.candles,
|
|
789
|
+
nestedData?.candles,
|
|
790
|
+
payload.ohlcv,
|
|
791
|
+
nestedData?.ohlcv,
|
|
792
|
+
payload.results,
|
|
793
|
+
nestedData?.results,
|
|
794
|
+
payload.data
|
|
795
|
+
];
|
|
796
|
+
for (const candidate of candidates) {
|
|
797
|
+
if (!Array.isArray(candidate)) {
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
const normalized = candidate.map(normalizeChartPoint).filter((point) => point !== null);
|
|
801
|
+
if (normalized.length > 0 || candidate.length === 0) {
|
|
802
|
+
return normalized;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return [];
|
|
806
|
+
}
|
|
807
|
+
function resolveTechnical(data) {
|
|
808
|
+
if (Array.isArray(data)) {
|
|
809
|
+
return void 0;
|
|
810
|
+
}
|
|
811
|
+
const payload = asObject(data);
|
|
812
|
+
if (!payload) {
|
|
813
|
+
return void 0;
|
|
814
|
+
}
|
|
815
|
+
const nestedData = asObject(payload.data);
|
|
816
|
+
const technical = payload.technical ?? nestedData?.technical;
|
|
817
|
+
return technical;
|
|
818
|
+
}
|
|
819
|
+
function normalizeChartBars(data) {
|
|
820
|
+
const points = extractChartPoints(data);
|
|
821
|
+
const technical = resolveTechnical(data);
|
|
822
|
+
return points.map((point) => ({
|
|
823
|
+
date: new Date(point.date),
|
|
824
|
+
open: point.open,
|
|
825
|
+
high: point.high,
|
|
826
|
+
low: point.low,
|
|
827
|
+
close: point.close,
|
|
828
|
+
volume: point.volume,
|
|
829
|
+
technical
|
|
830
|
+
})).filter((bar) => Number.isFinite(bar.date.valueOf()));
|
|
831
|
+
}
|
|
832
|
+
function isWhaleFeedResponse(data) {
|
|
833
|
+
if (!data || Array.isArray(data) || typeof data !== "object") {
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
return "summary" in data && "whale_orders" in data;
|
|
837
|
+
}
|
|
838
|
+
function buildEndpoints(base) {
|
|
839
|
+
return {
|
|
840
|
+
CHART: (symbol) => `${base}/core/v1/stock-management/stocks/${encodeURIComponent(symbol)}/chart/`,
|
|
841
|
+
BARS: (symbol) => `${base}/core/v1/stock-management/stocks/${encodeURIComponent(symbol)}/bars/`,
|
|
842
|
+
ORDER_FLOW: (symbol) => `${base}/core/v1/stock-management/stocks/${encodeURIComponent(symbol)}/order-flow/`,
|
|
843
|
+
STOCKS_LIST: `${base}/core/v1/stock-management/stocks/`,
|
|
844
|
+
AUTH_TOKEN: `${base}/core/v1/auth/token/`,
|
|
845
|
+
AUTH_ME: `${base}/core/v1/auth/me`,
|
|
846
|
+
TICKER: (symbol) => `${base}/core/v1/realtime/ticker/${encodeURIComponent(symbol)}/`,
|
|
847
|
+
WHALE_FEED: (symbol) => `${base}/core/v1/realtime/whale-feed/${encodeURIComponent(symbol)}/`,
|
|
848
|
+
AI_ADVISORY: `${base}/core/v1/stock_ai_advisory_session/`,
|
|
849
|
+
// Decision A (follow-up): native advisory session — GET reads, POST triggers refresh.
|
|
850
|
+
AI_ADVISORY_V2: (symbol) => `${base}/core/v1/stock-management/stocks/${encodeURIComponent(symbol)}/ai-advisory-session/`,
|
|
851
|
+
// BE-03: backend serves path-param style only: /intraday-tape/{symbol}/
|
|
852
|
+
// (verified live 2026-09-21; the old ?symbol= style 404s).
|
|
853
|
+
INTRADAY_TAPE: (symbol) => `${base}/core/v1/market-data/intraday-tape/${encodeURIComponent(symbol)}/`,
|
|
854
|
+
WIDGET_AD: `${base}/core/v1/widget/ad`
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
var BARS_HOT_WINDOW_TTL_MS = 15e3;
|
|
858
|
+
var BARS_HOT_WINDOW_STALE_MS = 6e4;
|
|
859
|
+
var barsHotWindowCache = /* @__PURE__ */ new Map();
|
|
860
|
+
var barsHotWindowInFlight = /* @__PURE__ */ new Map();
|
|
861
|
+
var barsHotWindowTelemetry = {
|
|
862
|
+
cacheHits: 0,
|
|
863
|
+
inFlightCollapses: 0,
|
|
864
|
+
networkRequests: 0,
|
|
865
|
+
lastUpdatedAt: null
|
|
866
|
+
};
|
|
867
|
+
function markBarsHotWindowTelemetry(metric) {
|
|
868
|
+
barsHotWindowTelemetry[metric] += 1;
|
|
869
|
+
barsHotWindowTelemetry.lastUpdatedAt = Date.now();
|
|
870
|
+
}
|
|
871
|
+
function cloneBarsHotWindowTelemetry(telemetry) {
|
|
872
|
+
return {
|
|
873
|
+
cacheHits: telemetry.cacheHits,
|
|
874
|
+
inFlightCollapses: telemetry.inFlightCollapses,
|
|
875
|
+
networkRequests: telemetry.networkRequests,
|
|
876
|
+
lastUpdatedAt: telemetry.lastUpdatedAt
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function createBarsHotWindowKey(base, pat, params) {
|
|
880
|
+
const scope = [base, pat, params.symbol.trim().toUpperCase(), normalizeTimeframe(params.timeframe)].join("|");
|
|
881
|
+
if (params.type === "backward") {
|
|
882
|
+
return `${scope}|backward|${params.before}|${params.limit}|${params.snapshotId ?? ""}`;
|
|
883
|
+
}
|
|
884
|
+
return `${scope}|init|${params.from ?? ""}|${params.to ?? ""}|${params.limit ?? ""}`;
|
|
885
|
+
}
|
|
886
|
+
function cloneBarsPageResponse(response) {
|
|
887
|
+
return {
|
|
888
|
+
bars: response.bars.map((bar) => ({
|
|
889
|
+
date: new Date(bar.date),
|
|
890
|
+
open: bar.open,
|
|
891
|
+
high: bar.high,
|
|
892
|
+
low: bar.low,
|
|
893
|
+
close: bar.close,
|
|
894
|
+
volume: bar.volume,
|
|
895
|
+
technical: bar.technical
|
|
896
|
+
})),
|
|
897
|
+
hasMoreBefore: response.hasMoreBefore,
|
|
898
|
+
hasMoreAfter: response.hasMoreAfter,
|
|
899
|
+
nextBefore: response.nextBefore,
|
|
900
|
+
nextAfter: response.nextAfter,
|
|
901
|
+
snapshotId: response.snapshotId
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function pruneExpiredBarsHotWindowEntries(now) {
|
|
905
|
+
for (const [key, entry] of barsHotWindowCache.entries()) {
|
|
906
|
+
if (entry.expiresAt <= now) {
|
|
907
|
+
barsHotWindowCache.delete(key);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
async function revalidateBarsHotWindow(base, pat, cacheKey, params) {
|
|
912
|
+
const endpoints = buildEndpoints(base);
|
|
913
|
+
const urlParams = new URLSearchParams();
|
|
914
|
+
urlParams.set("timeframe", normalizeTimeframe(params.timeframe));
|
|
915
|
+
if (params.type === "backward") {
|
|
916
|
+
urlParams.set("before", String(params.before));
|
|
917
|
+
urlParams.set("limit", String(params.limit));
|
|
918
|
+
if (params.snapshotId) urlParams.set("snapshot_id", params.snapshotId);
|
|
919
|
+
} else {
|
|
920
|
+
if (params.from != null) urlParams.set("from", String(params.from));
|
|
921
|
+
if (params.to != null) urlParams.set("to", String(params.to));
|
|
922
|
+
if (params.limit != null) urlParams.set("limit", String(params.limit));
|
|
923
|
+
}
|
|
924
|
+
try {
|
|
925
|
+
markBarsHotWindowTelemetry("networkRequests");
|
|
926
|
+
const url = `${endpoints.BARS(params.symbol)}?${urlParams.toString()}`;
|
|
927
|
+
const authHeaders = {};
|
|
928
|
+
if (pat) authHeaders["Authorization"] = `Bearer ${pat}`;
|
|
929
|
+
const response = await globalThis.fetch(url, { headers: authHeaders });
|
|
930
|
+
if (!response.ok) return;
|
|
931
|
+
const data = await response.json();
|
|
932
|
+
const rawBars = Array.isArray(data?.bars) ? data.bars : [];
|
|
933
|
+
const bars = rawBars.map(normalizeChartPoint).filter((p) => p !== null).map((point) => ({
|
|
934
|
+
date: new Date(point.date),
|
|
935
|
+
open: point.open,
|
|
936
|
+
high: point.high,
|
|
937
|
+
low: point.low,
|
|
938
|
+
close: point.close,
|
|
939
|
+
volume: point.volume
|
|
940
|
+
})).filter((bar) => Number.isFinite(bar.date.valueOf()));
|
|
941
|
+
const pagingMeta = normalizeBarsPagingMeta(data, bars);
|
|
942
|
+
const swrResponse = {
|
|
943
|
+
bars,
|
|
944
|
+
hasMoreBefore: pagingMeta.hasMoreBefore,
|
|
945
|
+
hasMoreAfter: pagingMeta.hasMoreAfter,
|
|
946
|
+
nextBefore: pagingMeta.nextBefore,
|
|
947
|
+
nextAfter: pagingMeta.nextAfter,
|
|
948
|
+
snapshotId: pagingMeta.snapshotId
|
|
949
|
+
};
|
|
950
|
+
const ttl = getCacheTTL(BARS_HOT_WINDOW_TTL_MS);
|
|
951
|
+
const now = Date.now();
|
|
952
|
+
if (!isCurrentCandleResponse(swrResponse, params.timeframe)) {
|
|
953
|
+
barsHotWindowCache.set(cacheKey, {
|
|
954
|
+
response: cloneBarsPageResponse(swrResponse),
|
|
955
|
+
expiresAt: now + ttl,
|
|
956
|
+
fetchedAt: now
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
} catch {
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
var VNInvestClient = class _VNInvestClient {
|
|
963
|
+
_pat = "";
|
|
964
|
+
_base;
|
|
965
|
+
_authHandler;
|
|
966
|
+
constructor(patToken) {
|
|
967
|
+
this._base = resolveApiBase();
|
|
968
|
+
try {
|
|
969
|
+
if (typeof globalThis !== "undefined") {
|
|
970
|
+
const globalPat = globalThis.__VNI_PAT_TOKEN__;
|
|
971
|
+
if (typeof globalPat === "string" && globalPat.trim()) {
|
|
972
|
+
this._pat = globalPat.trim();
|
|
973
|
+
writePat(this._pat);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
} catch {
|
|
978
|
+
}
|
|
979
|
+
try {
|
|
980
|
+
if (typeof localStorage !== "undefined") {
|
|
981
|
+
const stored = localStorage.getItem(PAT_STORAGE_KEY);
|
|
982
|
+
if (stored) {
|
|
983
|
+
const isLicenseKey = !!engineCore.parseLicenseKey(stored);
|
|
984
|
+
if (!DEFAULT_PAT && !isLicenseKey) {
|
|
985
|
+
const migrationKey = "vni_pat_cleared_v1";
|
|
986
|
+
if (!localStorage.getItem(migrationKey)) {
|
|
987
|
+
clearPat();
|
|
988
|
+
localStorage.removeItem("vninvest_saas_features_vni_internal_Q3zRGQ2bpLoLjGdYx6R72E7TH50tQ2iIYRUecgSKfDnirANYBGQrM2wQlZQ12uP6");
|
|
989
|
+
localStorage.setItem(migrationKey, "1");
|
|
990
|
+
}
|
|
991
|
+
} else {
|
|
992
|
+
const token = readPat();
|
|
993
|
+
if (token) {
|
|
994
|
+
this._pat = token;
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
} catch {
|
|
1001
|
+
}
|
|
1002
|
+
if (patToken) {
|
|
1003
|
+
this.setPAT(patToken);
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
if (!this._pat) {
|
|
1007
|
+
const mem = readSessionPat();
|
|
1008
|
+
if (mem) this._pat = mem;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
setAuthHandler(handler) {
|
|
1012
|
+
this._authHandler = handler;
|
|
1013
|
+
}
|
|
1014
|
+
setPAT(token) {
|
|
1015
|
+
const trimmed = (token ?? "").trim();
|
|
1016
|
+
const changed = trimmed !== this._pat;
|
|
1017
|
+
if (!trimmed) {
|
|
1018
|
+
this._pat = "";
|
|
1019
|
+
clearPat();
|
|
1020
|
+
if (changed) this._notifyPatChanged();
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
const result = writePat(trimmed);
|
|
1024
|
+
if (!result.ok) return;
|
|
1025
|
+
this._pat = trimmed;
|
|
1026
|
+
if (changed) this._notifyPatChanged();
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Transport-only credential sync. Sets `_pat` WITHOUT touching storage —
|
|
1030
|
+
* storage belongs to credentialStore alone. The SaaS context calls this before
|
|
1031
|
+
* validation when its resolved credential (tenant > user > env) differs from
|
|
1032
|
+
* what this transport carries; otherwise a user-pasted PAT sits in storage
|
|
1033
|
+
* while requests go out with a stale or empty token, and the backend 401s with
|
|
1034
|
+
* "Authentication credentials were not provided." No-op when equal.
|
|
1035
|
+
*/
|
|
1036
|
+
syncTransportPat(token) {
|
|
1037
|
+
const trimmed = (token ?? "").trim();
|
|
1038
|
+
if (trimmed === this._pat) return;
|
|
1039
|
+
this._pat = trimmed;
|
|
1040
|
+
this._notifyPatChanged();
|
|
1041
|
+
}
|
|
1042
|
+
_notifyPatChanged() {
|
|
1043
|
+
try {
|
|
1044
|
+
window.dispatchEvent(new CustomEvent("vni:pat-changed"));
|
|
1045
|
+
} catch {
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
getPAT() {
|
|
1049
|
+
return this._pat;
|
|
1050
|
+
}
|
|
1051
|
+
setBase(url) {
|
|
1052
|
+
const trimmed = (url ?? "").trim().replace(/\/+$/, "");
|
|
1053
|
+
if (!trimmed) return;
|
|
1054
|
+
this._base = trimmed;
|
|
1055
|
+
try {
|
|
1056
|
+
if (typeof localStorage !== "undefined") {
|
|
1057
|
+
localStorage.setItem(API_BASE_STORAGE_KEY, trimmed);
|
|
1058
|
+
}
|
|
1059
|
+
} catch {
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
getBase() {
|
|
1063
|
+
return this._base;
|
|
1064
|
+
}
|
|
1065
|
+
hasPAT() {
|
|
1066
|
+
return this._pat.length > 0;
|
|
1067
|
+
}
|
|
1068
|
+
buildHeaders() {
|
|
1069
|
+
const headers = { "Content-Type": "application/json" };
|
|
1070
|
+
if (this._pat) {
|
|
1071
|
+
headers["Authorization"] = `Bearer ${this._pat}`;
|
|
1072
|
+
}
|
|
1073
|
+
return headers;
|
|
1074
|
+
}
|
|
1075
|
+
async _fetch(url, init, isRetry = false) {
|
|
1076
|
+
const response = await fetch(url, { ...init, headers: this.buildHeaders() });
|
|
1077
|
+
if (response.status === 401) {
|
|
1078
|
+
if (!isRetry && this._authHandler) {
|
|
1079
|
+
try {
|
|
1080
|
+
const currentToken = await this._authHandler.getToken();
|
|
1081
|
+
if (currentToken && currentToken.trim() !== this._pat) {
|
|
1082
|
+
this.setPAT(currentToken);
|
|
1083
|
+
return await this._fetch(url, init, true);
|
|
1084
|
+
}
|
|
1085
|
+
if (this._authHandler.getRefreshToken) {
|
|
1086
|
+
const refreshToken = await this._authHandler.getRefreshToken();
|
|
1087
|
+
if (refreshToken) {
|
|
1088
|
+
const refreshUrl = `${this._base}/api/auth/token/refresh/`;
|
|
1089
|
+
const refreshResponse = await fetch(refreshUrl, {
|
|
1090
|
+
method: "POST",
|
|
1091
|
+
headers: { "Content-Type": "application/json" },
|
|
1092
|
+
body: JSON.stringify({ refresh: refreshToken })
|
|
1093
|
+
});
|
|
1094
|
+
if (refreshResponse.ok) {
|
|
1095
|
+
const data = await refreshResponse.json();
|
|
1096
|
+
const newPat = data.access || data.token || data.patToken;
|
|
1097
|
+
if (newPat) {
|
|
1098
|
+
this.setPAT(newPat);
|
|
1099
|
+
await this._authHandler.onTokensUpdated?.({
|
|
1100
|
+
patToken: newPat,
|
|
1101
|
+
refreshToken: data.refresh || refreshToken
|
|
1102
|
+
});
|
|
1103
|
+
return await this._fetch(url, init, true);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
} catch (refreshError) {
|
|
1109
|
+
console.warn("Auto-refresh token failed in VNInvestClient:", refreshError);
|
|
1110
|
+
}
|
|
1111
|
+
this._authHandler.onAuthError?.(new Error("TOKEN_EXPIRED"));
|
|
1112
|
+
}
|
|
1113
|
+
const err = new Error("TOKEN_EXPIRED");
|
|
1114
|
+
try {
|
|
1115
|
+
const body = await response.clone().json();
|
|
1116
|
+
if (typeof body?.detail === "string" && body.detail) {
|
|
1117
|
+
err.detail = body.detail;
|
|
1118
|
+
}
|
|
1119
|
+
} catch {
|
|
1120
|
+
}
|
|
1121
|
+
throw err;
|
|
1122
|
+
}
|
|
1123
|
+
if (response.status === 404) {
|
|
1124
|
+
throw new Error("SYMBOL_NOT_FOUND");
|
|
1125
|
+
}
|
|
1126
|
+
if (!response.ok) {
|
|
1127
|
+
const text = await response.text().catch(() => String(response.status));
|
|
1128
|
+
throw new Error(`HTTP_${response.status}: ${text.slice(0, 200)}`);
|
|
1129
|
+
}
|
|
1130
|
+
return response.json();
|
|
1131
|
+
}
|
|
1132
|
+
async getChart(symbol, options) {
|
|
1133
|
+
const endpoints = buildEndpoints(this._base);
|
|
1134
|
+
const params = new URLSearchParams();
|
|
1135
|
+
if (options?.timeframe) params.set("timeframe", normalizeTimeframe(options.timeframe));
|
|
1136
|
+
if (options?.days != null) params.set("days", String(options.days));
|
|
1137
|
+
const url = `${endpoints.CHART(symbol)}?${params.toString()}`;
|
|
1138
|
+
const data = await this._fetch(url);
|
|
1139
|
+
return normalizeChartBars(data);
|
|
1140
|
+
}
|
|
1141
|
+
async validateMe() {
|
|
1142
|
+
const endpoints = buildEndpoints(this._base);
|
|
1143
|
+
return this._fetch(endpoints.AUTH_ME);
|
|
1144
|
+
}
|
|
1145
|
+
/** Fetch eligible ad campaigns. Bearer PAT auto-attached; premium → empty list. */
|
|
1146
|
+
async fetchAd() {
|
|
1147
|
+
const endpoints = buildEndpoints(this._base);
|
|
1148
|
+
return this._fetch(endpoints.WIDGET_AD);
|
|
1149
|
+
}
|
|
1150
|
+
async getBarsPage(params) {
|
|
1151
|
+
const cacheKey = createBarsHotWindowKey(this._base, this._pat, params);
|
|
1152
|
+
const now = Date.now();
|
|
1153
|
+
pruneExpiredBarsHotWindowEntries(now);
|
|
1154
|
+
const tfMs = resolveTimeframeMs(params.timeframe);
|
|
1155
|
+
const isNearNow = params.type === "init" && params.to != null ? now - params.to < tfMs : false;
|
|
1156
|
+
if (!isNearNow) {
|
|
1157
|
+
const cachedEntry = barsHotWindowCache.get(cacheKey);
|
|
1158
|
+
if (cachedEntry && cachedEntry.expiresAt > now) {
|
|
1159
|
+
markBarsHotWindowTelemetry("cacheHits");
|
|
1160
|
+
return cloneBarsPageResponse(cachedEntry.response);
|
|
1161
|
+
}
|
|
1162
|
+
if (cachedEntry && now - cachedEntry.fetchedAt < BARS_HOT_WINDOW_STALE_MS) {
|
|
1163
|
+
markBarsHotWindowTelemetry("cacheHits");
|
|
1164
|
+
void this._revalidateBarsHotWindow(cacheKey, params);
|
|
1165
|
+
return cloneBarsPageResponse(cachedEntry.response);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
const inFlight2 = barsHotWindowInFlight.get(cacheKey);
|
|
1169
|
+
if (inFlight2) {
|
|
1170
|
+
markBarsHotWindowTelemetry("inFlightCollapses");
|
|
1171
|
+
const sharedResponse = await inFlight2;
|
|
1172
|
+
return cloneBarsPageResponse(sharedResponse);
|
|
1173
|
+
}
|
|
1174
|
+
const endpoints = buildEndpoints(this._base);
|
|
1175
|
+
const urlParams = new URLSearchParams();
|
|
1176
|
+
urlParams.set("timeframe", normalizeTimeframe(params.timeframe));
|
|
1177
|
+
const safeLimit = clampBarsLimit;
|
|
1178
|
+
if (params.type === "backward") {
|
|
1179
|
+
urlParams.set("before", String(params.before));
|
|
1180
|
+
urlParams.set("limit", String(safeLimit(params.limit) ?? BARS_LIMIT_MAX));
|
|
1181
|
+
if (params.snapshotId) urlParams.set("snapshot_id", params.snapshotId);
|
|
1182
|
+
} else {
|
|
1183
|
+
if (params.from != null) urlParams.set("from", String(params.from));
|
|
1184
|
+
if (params.to != null) urlParams.set("to", String(params.to));
|
|
1185
|
+
if (params.limit != null) urlParams.set("limit", String(safeLimit(params.limit)));
|
|
1186
|
+
}
|
|
1187
|
+
const requestPromise = (async () => {
|
|
1188
|
+
markBarsHotWindowTelemetry("networkRequests");
|
|
1189
|
+
const url = `${endpoints.BARS(params.symbol)}?${urlParams.toString()}`;
|
|
1190
|
+
const data = await this._fetch(url);
|
|
1191
|
+
const payload = data;
|
|
1192
|
+
const rawBars = Array.isArray(payload?.bars) ? payload.bars : [];
|
|
1193
|
+
const bars = rawBars.map(normalizeChartPoint).filter((p) => p !== null).map((point) => ({
|
|
1194
|
+
date: new Date(point.date),
|
|
1195
|
+
open: point.open,
|
|
1196
|
+
high: point.high,
|
|
1197
|
+
low: point.low,
|
|
1198
|
+
close: point.close,
|
|
1199
|
+
volume: point.volume
|
|
1200
|
+
})).filter((bar) => Number.isFinite(bar.date.valueOf()));
|
|
1201
|
+
const pagingMeta = normalizeBarsPagingMeta(payload, bars);
|
|
1202
|
+
const response = {
|
|
1203
|
+
bars,
|
|
1204
|
+
hasMoreBefore: pagingMeta.hasMoreBefore,
|
|
1205
|
+
hasMoreAfter: pagingMeta.hasMoreAfter,
|
|
1206
|
+
nextBefore: pagingMeta.nextBefore,
|
|
1207
|
+
nextAfter: pagingMeta.nextAfter,
|
|
1208
|
+
snapshotId: pagingMeta.snapshotId
|
|
1209
|
+
};
|
|
1210
|
+
const ttl = getCacheTTL(BARS_HOT_WINDOW_TTL_MS);
|
|
1211
|
+
const now2 = Date.now();
|
|
1212
|
+
if (!isCurrentCandleResponse(response, params.timeframe)) {
|
|
1213
|
+
barsHotWindowCache.set(cacheKey, {
|
|
1214
|
+
response: cloneBarsPageResponse(response),
|
|
1215
|
+
expiresAt: now2 + ttl,
|
|
1216
|
+
fetchedAt: now2
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
return response;
|
|
1220
|
+
})();
|
|
1221
|
+
barsHotWindowInFlight.set(cacheKey, requestPromise);
|
|
1222
|
+
try {
|
|
1223
|
+
const response = await requestPromise;
|
|
1224
|
+
return cloneBarsPageResponse(response);
|
|
1225
|
+
} finally {
|
|
1226
|
+
barsHotWindowInFlight.delete(cacheKey);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
static __resetBarsHotWindowCacheForTests() {
|
|
1230
|
+
barsHotWindowCache.clear();
|
|
1231
|
+
barsHotWindowInFlight.clear();
|
|
1232
|
+
barsHotWindowTelemetry.cacheHits = 0;
|
|
1233
|
+
barsHotWindowTelemetry.inFlightCollapses = 0;
|
|
1234
|
+
barsHotWindowTelemetry.networkRequests = 0;
|
|
1235
|
+
barsHotWindowTelemetry.lastUpdatedAt = null;
|
|
1236
|
+
}
|
|
1237
|
+
static getBarsHotWindowTelemetry() {
|
|
1238
|
+
return cloneBarsHotWindowTelemetry(barsHotWindowTelemetry);
|
|
1239
|
+
}
|
|
1240
|
+
static __getBarsHotWindowTelemetryForTests() {
|
|
1241
|
+
return _VNInvestClient.getBarsHotWindowTelemetry();
|
|
1242
|
+
}
|
|
1243
|
+
async getStocks(query, exchange) {
|
|
1244
|
+
const endpoints = buildEndpoints(this._base);
|
|
1245
|
+
const params = new URLSearchParams();
|
|
1246
|
+
if (query) params.set("search", query);
|
|
1247
|
+
if (exchange) params.set("exchange", exchange);
|
|
1248
|
+
const url = `${endpoints.STOCKS_LIST}?${params.toString()}`;
|
|
1249
|
+
const data = await this._fetch(url);
|
|
1250
|
+
if (Array.isArray(data)) return data;
|
|
1251
|
+
if ("results" in data && Array.isArray(data.results)) return data.results;
|
|
1252
|
+
return [];
|
|
1253
|
+
}
|
|
1254
|
+
async getWhaleFeed(symbol) {
|
|
1255
|
+
const endpoints = buildEndpoints(this._base);
|
|
1256
|
+
const data = await this._fetch(endpoints.WHALE_FEED(symbol));
|
|
1257
|
+
if (isWhaleFeedResponse(data)) return data;
|
|
1258
|
+
const obj = data;
|
|
1259
|
+
if (obj && typeof obj === "object" && typeof obj["symbol"] === "string" && Array.isArray(obj["alerts"])) {
|
|
1260
|
+
return mapNativeWhaleFeed(obj);
|
|
1261
|
+
}
|
|
1262
|
+
return null;
|
|
1263
|
+
}
|
|
1264
|
+
async getOrderFlow(symbol, window2 = "30m") {
|
|
1265
|
+
const endpoints = buildEndpoints(this._base);
|
|
1266
|
+
const params = new URLSearchParams({ window: window2 });
|
|
1267
|
+
const url = `${endpoints.ORDER_FLOW(symbol)}?${params.toString()}`;
|
|
1268
|
+
const data = await this._fetch(url);
|
|
1269
|
+
const obj = data;
|
|
1270
|
+
if (!obj || typeof obj !== "object") {
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
if (obj["ok"] === true) {
|
|
1274
|
+
return obj;
|
|
1275
|
+
}
|
|
1276
|
+
if (typeof obj["symbol"] === "string" && (Array.isArray(obj["signals"]) || typeof obj["dark_flow"] === "object")) {
|
|
1277
|
+
return mapNativeOrderFlow(obj, window2);
|
|
1278
|
+
}
|
|
1279
|
+
return null;
|
|
1280
|
+
}
|
|
1281
|
+
/** Shared GET/POST fetch for the native advisory session endpoint. */
|
|
1282
|
+
async fetchAdvisorySessionV2(url, init) {
|
|
1283
|
+
const data = await this._fetch(url, init).catch(() => null);
|
|
1284
|
+
const obj = data;
|
|
1285
|
+
if (!obj || typeof obj !== "object" || typeof obj["symbol"] !== "string") {
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
return obj;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Follow-up advisory V2: read the native AI advisory session.
|
|
1292
|
+
* Returns the session in both empty (`reason: "no_advisory_data"`) and record states;
|
|
1293
|
+
* null only on transport/auth failure. Never throws for empty sessions.
|
|
1294
|
+
*/
|
|
1295
|
+
async getAdvisorySessionV2(symbol) {
|
|
1296
|
+
return this.fetchAdvisorySessionV2(buildEndpoints(this._base).AI_ADVISORY_V2(symbol));
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Follow-up advisory V2: trigger a manual refresh (queues generation server-side).
|
|
1300
|
+
* Returns the updated session payload; null on transport/auth failure.
|
|
1301
|
+
*/
|
|
1302
|
+
async refreshAdvisorySession(symbol) {
|
|
1303
|
+
return this.fetchAdvisorySessionV2(buildEndpoints(this._base).AI_ADVISORY_V2(symbol), {
|
|
1304
|
+
method: "POST",
|
|
1305
|
+
headers: { "Content-Type": "application/json" },
|
|
1306
|
+
body: JSON.stringify({})
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
/** @deprecated Legacy POST path 404s on current backends — use getAdvisorySessionV2. */
|
|
1310
|
+
async getAdvisorySession(symbol) {
|
|
1311
|
+
const endpoints = buildEndpoints(this._base);
|
|
1312
|
+
const data = await this._fetch(endpoints.AI_ADVISORY, {
|
|
1313
|
+
method: "POST",
|
|
1314
|
+
headers: { "Content-Type": "application/json" },
|
|
1315
|
+
body: JSON.stringify({ symbol })
|
|
1316
|
+
});
|
|
1317
|
+
const obj = data;
|
|
1318
|
+
if (!obj || obj["ok"] !== true) {
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
return obj;
|
|
1322
|
+
}
|
|
1323
|
+
async getTapeEntries(symbol, pageSize = 100) {
|
|
1324
|
+
const endpoints = buildEndpoints(this._base);
|
|
1325
|
+
const url = `${endpoints.INTRADAY_TAPE(symbol)}?page_size=${Math.max(1, Math.min(200, Math.floor(pageSize)))}`;
|
|
1326
|
+
const data = await this._fetch(url);
|
|
1327
|
+
const obj = data;
|
|
1328
|
+
if (!obj || typeof obj !== "object") {
|
|
1329
|
+
return null;
|
|
1330
|
+
}
|
|
1331
|
+
if (obj["ok"] === true) {
|
|
1332
|
+
return obj;
|
|
1333
|
+
}
|
|
1334
|
+
if (typeof obj["symbol"] === "string" && Array.isArray(obj["trades"])) {
|
|
1335
|
+
return mapNativeTape(obj);
|
|
1336
|
+
}
|
|
1337
|
+
return null;
|
|
1338
|
+
}
|
|
1339
|
+
async _revalidateBarsHotWindow(cacheKey, params) {
|
|
1340
|
+
await revalidateBarsHotWindow(this._base, this._pat, cacheKey, params);
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
// src/widget/adapters/VNInvestWebSocketClient.ts
|
|
1345
|
+
function unwrapWsEnvelope(parsed) {
|
|
1346
|
+
const data = parsed["data"];
|
|
1347
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
1348
|
+
return parsed;
|
|
1349
|
+
}
|
|
1350
|
+
const body = { ...data };
|
|
1351
|
+
for (const key of ["event_type", "symbol", "ts", "seq"]) {
|
|
1352
|
+
if (body[key] === void 0 && parsed[key] !== void 0) {
|
|
1353
|
+
body[key] = parsed[key];
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return body;
|
|
1357
|
+
}
|
|
1358
|
+
var VNInvestWebSocketClient = class _VNInvestWebSocketClient {
|
|
1359
|
+
static MAX_RECONNECT_ATTEMPTS = 5;
|
|
1360
|
+
static RECONNECT_DELAY_MS = 5e3;
|
|
1361
|
+
static WS_PATH_CANDIDATES = ["/ws/realtime/", "/api/realtime/ws/"];
|
|
1362
|
+
ws = null;
|
|
1363
|
+
pat;
|
|
1364
|
+
apiBase;
|
|
1365
|
+
selectedWsPath = null;
|
|
1366
|
+
wsUnavailable = false;
|
|
1367
|
+
wsProbePromise = null;
|
|
1368
|
+
subscribedSymbols = /* @__PURE__ */ new Set();
|
|
1369
|
+
onTickerUpdateCb = null;
|
|
1370
|
+
onWhaleAlertCb = null;
|
|
1371
|
+
reconnectTimer = null;
|
|
1372
|
+
reconnectAttempts = 0;
|
|
1373
|
+
_isConnected = false;
|
|
1374
|
+
_destroyed = false;
|
|
1375
|
+
constructor(pat, apiBase = resolveApiBase()) {
|
|
1376
|
+
this.pat = pat;
|
|
1377
|
+
this.apiBase = apiBase;
|
|
1378
|
+
}
|
|
1379
|
+
get isConnected() {
|
|
1380
|
+
return this._isConnected;
|
|
1381
|
+
}
|
|
1382
|
+
get destroyed() {
|
|
1383
|
+
return this._destroyed;
|
|
1384
|
+
}
|
|
1385
|
+
buildWsUrl(path = "/ws/realtime/") {
|
|
1386
|
+
const wsBase = this.apiBase.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://").replace(/\/$/, "");
|
|
1387
|
+
return `${wsBase}${path}?token=${encodeURIComponent(this.pat)}`;
|
|
1388
|
+
}
|
|
1389
|
+
buildHttpProbeUrl(path) {
|
|
1390
|
+
const httpBase = this.apiBase.replace(/\/$/, "");
|
|
1391
|
+
return `${httpBase}${path}`;
|
|
1392
|
+
}
|
|
1393
|
+
async probeWsPath(path) {
|
|
1394
|
+
try {
|
|
1395
|
+
const response = await fetch(this.buildHttpProbeUrl(path), {
|
|
1396
|
+
method: "GET",
|
|
1397
|
+
headers: {
|
|
1398
|
+
Authorization: `Bearer ${this.pat}`
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
if (response.status === 404 || response.status === 502 || response.status === 503) {
|
|
1402
|
+
return false;
|
|
1403
|
+
}
|
|
1404
|
+
return true;
|
|
1405
|
+
} catch {
|
|
1406
|
+
return false;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
async resolveWsPath() {
|
|
1410
|
+
if (this.selectedWsPath) {
|
|
1411
|
+
return this.selectedWsPath;
|
|
1412
|
+
}
|
|
1413
|
+
try {
|
|
1414
|
+
const hostname = new URL(this.apiBase).hostname;
|
|
1415
|
+
if (hostname.endsWith(".test")) {
|
|
1416
|
+
this.selectedWsPath = _VNInvestWebSocketClient.WS_PATH_CANDIDATES[0];
|
|
1417
|
+
return this.selectedWsPath;
|
|
1418
|
+
}
|
|
1419
|
+
} catch {
|
|
1420
|
+
}
|
|
1421
|
+
if (this.wsProbePromise) {
|
|
1422
|
+
return this.wsProbePromise;
|
|
1423
|
+
}
|
|
1424
|
+
this.wsProbePromise = (async () => {
|
|
1425
|
+
for (const candidate of _VNInvestWebSocketClient.WS_PATH_CANDIDATES) {
|
|
1426
|
+
const available = candidate === "/ws/realtime/" ? true : await this.probeWsPath(candidate);
|
|
1427
|
+
if (available) {
|
|
1428
|
+
this.selectedWsPath = candidate;
|
|
1429
|
+
return candidate;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
this.wsUnavailable = true;
|
|
1433
|
+
return null;
|
|
1434
|
+
})();
|
|
1435
|
+
const resolved = await this.wsProbePromise;
|
|
1436
|
+
this.wsProbePromise = null;
|
|
1437
|
+
return resolved;
|
|
1438
|
+
}
|
|
1439
|
+
connect() {
|
|
1440
|
+
if (this._destroyed || this.ws !== null || this.wsUnavailable) {
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
void this.openConnection();
|
|
1444
|
+
}
|
|
1445
|
+
async openConnection() {
|
|
1446
|
+
if (this._destroyed || this.ws !== null || this.wsUnavailable) {
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
const wsPath = await this.resolveWsPath();
|
|
1450
|
+
if (!wsPath || this._destroyed || this.wsUnavailable) {
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
const url = this.buildWsUrl(wsPath);
|
|
1454
|
+
try {
|
|
1455
|
+
this.ws = new WebSocket(url);
|
|
1456
|
+
} catch {
|
|
1457
|
+
this.scheduleReconnect();
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
this.ws.onopen = () => {
|
|
1461
|
+
this.reconnectAttempts = 0;
|
|
1462
|
+
this._isConnected = true;
|
|
1463
|
+
const symbols = Array.from(this.subscribedSymbols);
|
|
1464
|
+
if (symbols.length > 0) {
|
|
1465
|
+
this.sendJson({ action: "subscribe", symbols });
|
|
1466
|
+
}
|
|
1467
|
+
};
|
|
1468
|
+
this.ws.onmessage = (event) => {
|
|
1469
|
+
this.handleRawMessage(event.data);
|
|
1470
|
+
};
|
|
1471
|
+
this.ws.onclose = (event) => {
|
|
1472
|
+
this.ws = null;
|
|
1473
|
+
this._isConnected = false;
|
|
1474
|
+
if (event.code === 4401) {
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
this.scheduleReconnect();
|
|
1478
|
+
};
|
|
1479
|
+
this.ws.onerror = () => {
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
subscribe(symbols) {
|
|
1483
|
+
const normalized = symbols.map((s) => s.trim().toUpperCase()).filter(Boolean);
|
|
1484
|
+
for (const sym of normalized) {
|
|
1485
|
+
this.subscribedSymbols.add(sym);
|
|
1486
|
+
}
|
|
1487
|
+
if (this._isConnected && normalized.length > 0) {
|
|
1488
|
+
this.sendJson({ action: "subscribe", symbols: normalized });
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
unsubscribe(symbols) {
|
|
1492
|
+
const normalized = symbols.map((s) => s.trim().toUpperCase()).filter(Boolean);
|
|
1493
|
+
for (const sym of normalized) {
|
|
1494
|
+
this.subscribedSymbols.delete(sym);
|
|
1495
|
+
}
|
|
1496
|
+
if (this._isConnected && normalized.length > 0) {
|
|
1497
|
+
this.sendJson({ action: "unsubscribe", symbols: normalized });
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
setOnTickerUpdate(cb) {
|
|
1501
|
+
this.onTickerUpdateCb = cb;
|
|
1502
|
+
}
|
|
1503
|
+
setOnWhaleAlert(cb) {
|
|
1504
|
+
this.onWhaleAlertCb = cb;
|
|
1505
|
+
}
|
|
1506
|
+
destroy() {
|
|
1507
|
+
this._destroyed = true;
|
|
1508
|
+
if (this.reconnectTimer !== null) {
|
|
1509
|
+
clearTimeout(this.reconnectTimer);
|
|
1510
|
+
this.reconnectTimer = null;
|
|
1511
|
+
}
|
|
1512
|
+
if (this.ws !== null) {
|
|
1513
|
+
this.ws.onclose = null;
|
|
1514
|
+
this.ws.onmessage = null;
|
|
1515
|
+
this.ws.onerror = null;
|
|
1516
|
+
this.ws.onopen = null;
|
|
1517
|
+
try {
|
|
1518
|
+
this.ws.close(1e3, "destroy");
|
|
1519
|
+
} catch {
|
|
1520
|
+
}
|
|
1521
|
+
this.ws = null;
|
|
1522
|
+
}
|
|
1523
|
+
this._isConnected = false;
|
|
1524
|
+
}
|
|
1525
|
+
handleRawMessage(data) {
|
|
1526
|
+
let parsed;
|
|
1527
|
+
try {
|
|
1528
|
+
parsed = JSON.parse(data);
|
|
1529
|
+
} catch {
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
const body = unwrapWsEnvelope(parsed);
|
|
1533
|
+
if (body.event_type === "ticker_update") {
|
|
1534
|
+
this.onTickerUpdateCb?.(body);
|
|
1535
|
+
} else if (body.event_type === "whale_alert") {
|
|
1536
|
+
this.onWhaleAlertCb?.(body);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
scheduleReconnect() {
|
|
1540
|
+
if (this._destroyed || this.wsUnavailable || this.reconnectAttempts >= _VNInvestWebSocketClient.MAX_RECONNECT_ATTEMPTS) {
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
this.reconnectAttempts += 1;
|
|
1544
|
+
const delay = _VNInvestWebSocketClient.RECONNECT_DELAY_MS * Math.pow(2, this.reconnectAttempts - 1);
|
|
1545
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1546
|
+
this.reconnectTimer = null;
|
|
1547
|
+
if (!this._destroyed) {
|
|
1548
|
+
this.connect();
|
|
1549
|
+
}
|
|
1550
|
+
}, delay);
|
|
1551
|
+
}
|
|
1552
|
+
sendJson(msg) {
|
|
1553
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1554
|
+
this.ws.send(JSON.stringify(msg));
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
// src/widget/adapters/VNInvestDataAdapter.ts
|
|
1560
|
+
var PAGE_LIMIT = 1e3;
|
|
1561
|
+
var VNInvestDataAdapter = class {
|
|
1562
|
+
name = "vninvest";
|
|
1563
|
+
client;
|
|
1564
|
+
wsClient = null;
|
|
1565
|
+
barSubscribers = /* @__PURE__ */ new Map();
|
|
1566
|
+
snapshotIds = /* @__PURE__ */ new Map();
|
|
1567
|
+
lastBackfillCoverageMap = /* @__PURE__ */ new Map();
|
|
1568
|
+
lastRangeCoverageMap = /* @__PURE__ */ new Map();
|
|
1569
|
+
constructor(config) {
|
|
1570
|
+
this.client = new VNInvestClient(config.patToken);
|
|
1571
|
+
if (config.apiBase) {
|
|
1572
|
+
this.client.setBase(config.apiBase);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
setAuthHandler(handler) {
|
|
1576
|
+
this.client.setAuthHandler(handler);
|
|
1577
|
+
}
|
|
1578
|
+
getSourceSemantics() {
|
|
1579
|
+
return {
|
|
1580
|
+
sourceId: this.name,
|
|
1581
|
+
sourceContractVersion: "vninvest-backfill-v1",
|
|
1582
|
+
instrumentClass: "equity",
|
|
1583
|
+
coverageMode: "exact-range",
|
|
1584
|
+
sessionModel: "exchange-calendar",
|
|
1585
|
+
timezone: "Asia/Ho_Chi_Minh",
|
|
1586
|
+
sessionCalendarId: "vn-exchange-calendar",
|
|
1587
|
+
supportsExactRangeFetch: true,
|
|
1588
|
+
supportsBackwardAnchor: true,
|
|
1589
|
+
supportsForwardAnchor: false,
|
|
1590
|
+
supportsLiveBars: true,
|
|
1591
|
+
maxBarsPerRequest: PAGE_LIMIT,
|
|
1592
|
+
finalityMode: "historically-correctable",
|
|
1593
|
+
correctionLookbackMs: 30 * 864e5,
|
|
1594
|
+
expectedGapReasons: ["not-fetched", "market-closed", "source-no-data", "rate-limited", "boundary-exhausted"]
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
coverageKey(symbol, timeframe, chartId) {
|
|
1598
|
+
const scope = chartId ?? "__global__";
|
|
1599
|
+
return `${scope}:${symbol.toUpperCase()}:${timeframe}`;
|
|
1600
|
+
}
|
|
1601
|
+
getLastBackfillCoverage(symbol, timeframe, chartId) {
|
|
1602
|
+
if (!symbol || !timeframe) {
|
|
1603
|
+
return null;
|
|
1604
|
+
}
|
|
1605
|
+
const key = this.coverageKey(symbol, timeframe, chartId);
|
|
1606
|
+
return this.lastBackfillCoverageMap.get(key) ?? null;
|
|
1607
|
+
}
|
|
1608
|
+
getLastRangeCoverage(symbol, timeframe, chartId) {
|
|
1609
|
+
if (!symbol || !timeframe) {
|
|
1610
|
+
return null;
|
|
1611
|
+
}
|
|
1612
|
+
const key = this.coverageKey(symbol, timeframe, chartId);
|
|
1613
|
+
return this.lastRangeCoverageMap.get(key) ?? null;
|
|
1614
|
+
}
|
|
1615
|
+
/** Expose the underlying REST client for advanced use cases. */
|
|
1616
|
+
get restClient() {
|
|
1617
|
+
return this.client;
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Fetch bars in a date range.
|
|
1621
|
+
* Uses the cursor-paged /bars/ endpoint with backward pagination.
|
|
1622
|
+
*
|
|
1623
|
+
* 504-guard: a single wide intraday window (e.g. 90d of 1H) times the server
|
|
1624
|
+
* out — each request is capped to MAX_INTRADAY_SPAN_MS and the existing
|
|
1625
|
+
* hasMoreAfter/nextAfter loop walks the remainder. Daily+ ranges are unaffected.
|
|
1626
|
+
*/
|
|
1627
|
+
async fetchBarsInRange(symbol, timeframe, from, to, chartId, signal) {
|
|
1628
|
+
const throwIfAborted = () => {
|
|
1629
|
+
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
1630
|
+
};
|
|
1631
|
+
throwIfAborted();
|
|
1632
|
+
const allBars = [];
|
|
1633
|
+
const chunkSpan = isIntradayTimeframe(timeframe) ? MAX_INTRADAY_SPAN_MS : Number.POSITIVE_INFINITY;
|
|
1634
|
+
const initParams = {
|
|
1635
|
+
type: "init",
|
|
1636
|
+
symbol,
|
|
1637
|
+
timeframe,
|
|
1638
|
+
from: from.valueOf(),
|
|
1639
|
+
to: Math.min(to.valueOf(), from.valueOf() + chunkSpan),
|
|
1640
|
+
limit: PAGE_LIMIT
|
|
1641
|
+
};
|
|
1642
|
+
let page = await this.client.getBarsPage(initParams);
|
|
1643
|
+
const key = this.coverageKey(symbol, timeframe, chartId);
|
|
1644
|
+
if (page.snapshotId) {
|
|
1645
|
+
this.snapshotIds.set(key, page.snapshotId);
|
|
1646
|
+
}
|
|
1647
|
+
let bars = sortBarsByDate(pageToOHLCV(page.bars));
|
|
1648
|
+
allBars.push(...bars);
|
|
1649
|
+
while (page.hasMoreAfter && page.nextAfter !== null) {
|
|
1650
|
+
throwIfAborted();
|
|
1651
|
+
const forwardParams = {
|
|
1652
|
+
type: "init",
|
|
1653
|
+
symbol,
|
|
1654
|
+
timeframe,
|
|
1655
|
+
from: page.nextAfter,
|
|
1656
|
+
to: Math.min(to.valueOf(), page.nextAfter + chunkSpan),
|
|
1657
|
+
limit: PAGE_LIMIT
|
|
1658
|
+
};
|
|
1659
|
+
page = await this.client.getBarsPage(forwardParams);
|
|
1660
|
+
if (page.snapshotId) {
|
|
1661
|
+
this.snapshotIds.set(key, page.snapshotId);
|
|
1662
|
+
}
|
|
1663
|
+
bars = sortBarsByDate(pageToOHLCV(page.bars));
|
|
1664
|
+
if (bars.length === 0) break;
|
|
1665
|
+
allBars.push(...bars);
|
|
1666
|
+
}
|
|
1667
|
+
if (allBars.length === 0) {
|
|
1668
|
+
this.lastRangeCoverageMap.set(key, [
|
|
1669
|
+
{ fromTs: from.valueOf(), toTs: to.valueOf(), status: "unresolved", reason: "source-no-data" }
|
|
1670
|
+
]);
|
|
1671
|
+
} else {
|
|
1672
|
+
const oldestTs = allBars[0].date.getTime();
|
|
1673
|
+
const newestTs = allBars[allBars.length - 1].date.getTime();
|
|
1674
|
+
const covered = { fromTs: oldestTs, toTs: newestTs, status: "covered" };
|
|
1675
|
+
const fragments = [covered];
|
|
1676
|
+
if (!page.hasMoreBefore) {
|
|
1677
|
+
fragments.unshift({ fromTs: 0, toTs: oldestTs, status: "unresolved", reason: "boundary-exhausted" });
|
|
1678
|
+
}
|
|
1679
|
+
if (!page.hasMoreAfter) {
|
|
1680
|
+
fragments.push({ fromTs: newestTs, toTs: to.valueOf(), status: "unresolved", reason: "boundary-exhausted" });
|
|
1681
|
+
}
|
|
1682
|
+
this.lastRangeCoverageMap.set(key, fragments);
|
|
1683
|
+
}
|
|
1684
|
+
return allBars;
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Fetch bars before a given anchor date (backward pagination).
|
|
1688
|
+
*/
|
|
1689
|
+
async fetchBarsBefore(symbol, timeframe, before, limit = PAGE_LIMIT, chartId) {
|
|
1690
|
+
const key = this.coverageKey(symbol, timeframe, chartId);
|
|
1691
|
+
const snapshotId = this.snapshotIds.get(key);
|
|
1692
|
+
const params = {
|
|
1693
|
+
type: "backward",
|
|
1694
|
+
symbol,
|
|
1695
|
+
timeframe,
|
|
1696
|
+
before: before.valueOf(),
|
|
1697
|
+
limit,
|
|
1698
|
+
snapshotId
|
|
1699
|
+
};
|
|
1700
|
+
const page = await this.client.getBarsPage(params);
|
|
1701
|
+
if (page.snapshotId) {
|
|
1702
|
+
this.snapshotIds.set(key, page.snapshotId);
|
|
1703
|
+
}
|
|
1704
|
+
const bars = sortBarsByDate(pageToOHLCV(page.bars));
|
|
1705
|
+
if (bars.length === 0) {
|
|
1706
|
+
this.lastBackfillCoverageMap.set(key, [
|
|
1707
|
+
{ fromTs: 0, toTs: before.valueOf(), status: "unresolved", reason: "boundary-exhausted" }
|
|
1708
|
+
]);
|
|
1709
|
+
} else {
|
|
1710
|
+
const oldestTs = bars[0].date.getTime();
|
|
1711
|
+
const newestTs = bars[bars.length - 1].date.getTime();
|
|
1712
|
+
const covered = { fromTs: oldestTs, toTs: newestTs, status: "covered" };
|
|
1713
|
+
if (!page.hasMoreBefore) {
|
|
1714
|
+
this.lastBackfillCoverageMap.set(key, [
|
|
1715
|
+
{ fromTs: 0, toTs: oldestTs, status: "unresolved", reason: "boundary-exhausted" },
|
|
1716
|
+
covered
|
|
1717
|
+
]);
|
|
1718
|
+
} else {
|
|
1719
|
+
this.lastBackfillCoverageMap.set(key, [covered]);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
return bars;
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Subscribe to real-time bar updates via WebSocket.
|
|
1726
|
+
* Maps ticker_update events to OHLCVBar callbacks.
|
|
1727
|
+
*/
|
|
1728
|
+
subscribeToBar(symbol, timeframe, onBar) {
|
|
1729
|
+
const key = `${symbol.toUpperCase()}:${timeframe}`;
|
|
1730
|
+
if (!this.wsClient) {
|
|
1731
|
+
this.wsClient = new VNInvestWebSocketClient(
|
|
1732
|
+
this.client.getPAT(),
|
|
1733
|
+
this.client.getBase()
|
|
1734
|
+
);
|
|
1735
|
+
this.wsClient.setOnTickerUpdate(this.handleTickerUpdate.bind(this));
|
|
1736
|
+
this.wsClient.connect();
|
|
1737
|
+
}
|
|
1738
|
+
if (!this.barSubscribers.has(key)) {
|
|
1739
|
+
this.barSubscribers.set(key, /* @__PURE__ */ new Set());
|
|
1740
|
+
this.wsClient.subscribe([symbol.toUpperCase()]);
|
|
1741
|
+
}
|
|
1742
|
+
this.barSubscribers.get(key).add(onBar);
|
|
1743
|
+
return () => {
|
|
1744
|
+
const subs = this.barSubscribers.get(key);
|
|
1745
|
+
if (subs) {
|
|
1746
|
+
subs.delete(onBar);
|
|
1747
|
+
if (subs.size === 0) {
|
|
1748
|
+
this.barSubscribers.delete(key);
|
|
1749
|
+
this.wsClient?.unsubscribe([symbol.toUpperCase()]);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
/** Not supported by VNInvest — returns no-op. */
|
|
1755
|
+
subscribeToTrades(_symbol, _onTrade) {
|
|
1756
|
+
return () => {
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
/** Not supported by VNInvest — returns no-op. */
|
|
1760
|
+
subscribeToOrderbook(_symbol, _onUpdate) {
|
|
1761
|
+
return () => {
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
/** Search symbols via GET /stocks/ endpoint. */
|
|
1765
|
+
async searchSymbols(query) {
|
|
1766
|
+
const items = await this.client.getStocks(query);
|
|
1767
|
+
return items.map((item) => ({
|
|
1768
|
+
symbol: item.symbol,
|
|
1769
|
+
name: item.company_name,
|
|
1770
|
+
exchange: item.exchange
|
|
1771
|
+
}));
|
|
1772
|
+
}
|
|
1773
|
+
/** Clean up: destroy WebSocket connection. */
|
|
1774
|
+
destroy() {
|
|
1775
|
+
this.wsClient?.destroy();
|
|
1776
|
+
this.wsClient = null;
|
|
1777
|
+
this.barSubscribers.clear();
|
|
1778
|
+
}
|
|
1779
|
+
handleTickerUpdate(event) {
|
|
1780
|
+
if (event.stale) return;
|
|
1781
|
+
const price = Number.isFinite(event.price) ? event.price : event.current_price;
|
|
1782
|
+
const currentPrice = Number.isFinite(event.current_price) ? event.current_price : price;
|
|
1783
|
+
if (!Number.isFinite(currentPrice)) return;
|
|
1784
|
+
const high = Number.isFinite(event.high_price) ? event.high_price : currentPrice;
|
|
1785
|
+
const low = Number.isFinite(event.low_price) ? event.low_price : currentPrice;
|
|
1786
|
+
const open = Number.isFinite(event.open_price) ? event.open_price : price;
|
|
1787
|
+
const symbolUpper = event.symbol.toUpperCase();
|
|
1788
|
+
const seq = Number.isFinite(event.seq) ? event.seq : 0;
|
|
1789
|
+
const volume = Number.isFinite(event.volume) ? event.volume : 0;
|
|
1790
|
+
for (const [key, subs] of this.barSubscribers.entries()) {
|
|
1791
|
+
if (key.startsWith(`${symbolUpper}:`)) {
|
|
1792
|
+
const bar = {
|
|
1793
|
+
date: new Date(event.ts),
|
|
1794
|
+
open,
|
|
1795
|
+
high,
|
|
1796
|
+
low,
|
|
1797
|
+
close: currentPrice,
|
|
1798
|
+
volume,
|
|
1799
|
+
index: seq,
|
|
1800
|
+
dataIndex: seq
|
|
1801
|
+
};
|
|
1802
|
+
for (const cb of subs) {
|
|
1803
|
+
cb(bar);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
};
|
|
1809
|
+
var INTRADAY_TIMEFRAMES = /* @__PURE__ */ new Set(["1m", "5m", "15m", "1h", "2h", "4h"]);
|
|
1810
|
+
var MAX_INTRADAY_SPAN_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1811
|
+
function isIntradayTimeframe(timeframe) {
|
|
1812
|
+
return INTRADAY_TIMEFRAMES.has(String(timeframe).toLowerCase());
|
|
1813
|
+
}
|
|
1814
|
+
function pageToOHLCV(bars) {
|
|
1815
|
+
return bars.map((bar, index) => ({
|
|
1816
|
+
date: bar.date,
|
|
1817
|
+
open: bar.open,
|
|
1818
|
+
high: bar.high,
|
|
1819
|
+
low: bar.low,
|
|
1820
|
+
close: bar.close,
|
|
1821
|
+
volume: bar.volume,
|
|
1822
|
+
index,
|
|
1823
|
+
dataIndex: index
|
|
1824
|
+
}));
|
|
1825
|
+
}
|
|
1826
|
+
function sortBarsByDate(bars) {
|
|
1827
|
+
return bars.sort((a, b) => a.date.valueOf() - b.date.valueOf());
|
|
1828
|
+
}
|
|
1829
|
+
function useWatchlistRealtimeSync({
|
|
1830
|
+
wsClient,
|
|
1831
|
+
symbols,
|
|
1832
|
+
onUpdate,
|
|
1833
|
+
throttleMs = 500
|
|
1834
|
+
}) {
|
|
1835
|
+
const [tickerData, setTickerData] = react.useState(/* @__PURE__ */ new Map());
|
|
1836
|
+
const bufferRef = react.useRef(/* @__PURE__ */ new Map());
|
|
1837
|
+
const onUpdateRef = react.useRef(onUpdate);
|
|
1838
|
+
onUpdateRef.current = onUpdate;
|
|
1839
|
+
const subscribedSymbolsRef = react.useRef(/* @__PURE__ */ new Set());
|
|
1840
|
+
react.useEffect(() => {
|
|
1841
|
+
if (!wsClient || symbols.length === 0) return;
|
|
1842
|
+
const ws = wsClient;
|
|
1843
|
+
const uppercaseSymbols = symbols.map((s) => s.trim().toUpperCase()).filter(Boolean);
|
|
1844
|
+
ws.subscribe(uppercaseSymbols);
|
|
1845
|
+
for (const sym of uppercaseSymbols) {
|
|
1846
|
+
subscribedSymbolsRef.current.add(sym);
|
|
1847
|
+
}
|
|
1848
|
+
const handleTickerUpdate = (event) => {
|
|
1849
|
+
const sym = event.symbol.toUpperCase();
|
|
1850
|
+
if (subscribedSymbolsRef.current.has(sym)) {
|
|
1851
|
+
bufferRef.current.set(sym, event);
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
ws.setOnTickerUpdate(handleTickerUpdate);
|
|
1855
|
+
const intervalId = setInterval(() => {
|
|
1856
|
+
if (bufferRef.current.size === 0) return;
|
|
1857
|
+
setTickerData((prev) => {
|
|
1858
|
+
const next = new Map(prev);
|
|
1859
|
+
bufferRef.current.forEach((value, key) => {
|
|
1860
|
+
next.set(key, value);
|
|
1861
|
+
});
|
|
1862
|
+
onUpdateRef.current?.(next);
|
|
1863
|
+
return next;
|
|
1864
|
+
});
|
|
1865
|
+
bufferRef.current.clear();
|
|
1866
|
+
}, throttleMs);
|
|
1867
|
+
return () => {
|
|
1868
|
+
clearInterval(intervalId);
|
|
1869
|
+
const activeSymbols = Array.from(subscribedSymbolsRef.current);
|
|
1870
|
+
if (activeSymbols.length > 0) {
|
|
1871
|
+
ws.unsubscribe(activeSymbols);
|
|
1872
|
+
}
|
|
1873
|
+
subscribedSymbolsRef.current.clear();
|
|
1874
|
+
bufferRef.current.clear();
|
|
1875
|
+
};
|
|
1876
|
+
}, [wsClient, symbols, throttleMs]);
|
|
1877
|
+
return tickerData;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
// src/lib/adapters/ContinuousContractTypes.ts
|
|
1881
|
+
var DEFAULT_VN_FUTURES_ROLL_CONFIG = {
|
|
1882
|
+
"VN30F1M": {
|
|
1883
|
+
rootSymbol: "VN30F",
|
|
1884
|
+
rollMethod: "ratio",
|
|
1885
|
+
rollTrigger: "days-before-expiry",
|
|
1886
|
+
rollDaysBeforeExpiry: 3,
|
|
1887
|
+
adjustVolumes: true
|
|
1888
|
+
},
|
|
1889
|
+
"VN30F1M-panama": {
|
|
1890
|
+
rootSymbol: "VN30F",
|
|
1891
|
+
rollMethod: "panama",
|
|
1892
|
+
rollTrigger: "days-before-expiry",
|
|
1893
|
+
rollDaysBeforeExpiry: 3,
|
|
1894
|
+
adjustVolumes: true
|
|
1895
|
+
},
|
|
1896
|
+
"VN30F3M": {
|
|
1897
|
+
rootSymbol: "VN30F",
|
|
1898
|
+
rollMethod: "ratio",
|
|
1899
|
+
rollTrigger: "days-before-expiry",
|
|
1900
|
+
rollDaysBeforeExpiry: 5,
|
|
1901
|
+
adjustVolumes: true
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
|
|
1905
|
+
// src/lib/utils/logger.ts
|
|
1906
|
+
var noop = () => {
|
|
1907
|
+
};
|
|
1908
|
+
var noopLogger = {
|
|
1909
|
+
log: noop,
|
|
1910
|
+
info: noop,
|
|
1911
|
+
warn: noop,
|
|
1912
|
+
error: noop,
|
|
1913
|
+
debug: noop,
|
|
1914
|
+
trace: noop,
|
|
1915
|
+
extend: () => noopLogger
|
|
1916
|
+
};
|
|
1917
|
+
function isDebugEnabled() {
|
|
1918
|
+
if (typeof process !== "undefined" && process.env?.DEBUG) return true;
|
|
1919
|
+
try {
|
|
1920
|
+
if (typeof localStorage !== "undefined") {
|
|
1921
|
+
return !!(localStorage.getItem("DEBUG") || localStorage.getItem("VNINVEST_DEBUG"));
|
|
1922
|
+
}
|
|
1923
|
+
} catch {
|
|
1924
|
+
}
|
|
1925
|
+
return false;
|
|
1926
|
+
}
|
|
1927
|
+
var enabled = isDebugEnabled();
|
|
1928
|
+
function createLogFn(namespace, level) {
|
|
1929
|
+
if (!enabled) return noop;
|
|
1930
|
+
return (...args) => {
|
|
1931
|
+
console[level](`[${namespace}]`, ...args);
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
function createLogger(namespace) {
|
|
1935
|
+
if (!enabled) return noopLogger;
|
|
1936
|
+
return {
|
|
1937
|
+
log: createLogFn(namespace, "log"),
|
|
1938
|
+
info: createLogFn(namespace, "info"),
|
|
1939
|
+
warn: createLogFn(namespace, "warn"),
|
|
1940
|
+
error: createLogFn(namespace, "error"),
|
|
1941
|
+
debug: createLogFn(namespace, "debug"),
|
|
1942
|
+
trace: createLogFn(namespace, "trace"),
|
|
1943
|
+
extend: (sub) => createLogger(`${namespace}:${sub}`)
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
function createAdapterLogger(adapterName) {
|
|
1947
|
+
return createLogger(`vninvestcharts-widget:adapter:${adapterName}`);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// src/lib/adapters/ContinuousContractAdapter.ts
|
|
1951
|
+
var log = createAdapterLogger("continuous-contract");
|
|
1952
|
+
var BFF_ENDPOINT = "/api/continuous-futures/stitch";
|
|
1953
|
+
var metadataCache = /* @__PURE__ */ new Map();
|
|
1954
|
+
var barsCache = /* @__PURE__ */ new Map();
|
|
1955
|
+
var ContinuousContractAdapter = class {
|
|
1956
|
+
name = "continuous-futures";
|
|
1957
|
+
config;
|
|
1958
|
+
rootSymbol;
|
|
1959
|
+
fetchRawBars;
|
|
1960
|
+
constructor(rootSymbol, config, fetchRawBars) {
|
|
1961
|
+
this.rootSymbol = rootSymbol;
|
|
1962
|
+
const defaultConfig = DEFAULT_VN_FUTURES_ROLL_CONFIG[rootSymbol] ?? DEFAULT_VN_FUTURES_ROLL_CONFIG["VN30F1M"];
|
|
1963
|
+
this.config = { ...defaultConfig, ...config, rootSymbol };
|
|
1964
|
+
this.fetchRawBars = fetchRawBars;
|
|
1965
|
+
}
|
|
1966
|
+
/** Get the continuous contract configuration */
|
|
1967
|
+
getConfig() {
|
|
1968
|
+
return { ...this.config };
|
|
1969
|
+
}
|
|
1970
|
+
/** Update roll configuration */
|
|
1971
|
+
setConfig(config) {
|
|
1972
|
+
this.config = { ...this.config, ...config };
|
|
1973
|
+
metadataCache.delete(this.rootSymbol);
|
|
1974
|
+
}
|
|
1975
|
+
async getBars(params) {
|
|
1976
|
+
const continuousParams = this.mapToContinuousParams(params);
|
|
1977
|
+
const result = await this.fetchContinuousBars(continuousParams);
|
|
1978
|
+
return {
|
|
1979
|
+
bars: result.bars,
|
|
1980
|
+
hasMore: result.hasMore
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
async getBarsCanonical(params) {
|
|
1984
|
+
const continuousParams = this.mapToContinuousParams(params);
|
|
1985
|
+
const result = await this.fetchContinuousBars(continuousParams);
|
|
1986
|
+
const coverage = this.buildCoverage(result, continuousParams);
|
|
1987
|
+
return {
|
|
1988
|
+
bars: result.bars,
|
|
1989
|
+
hasMore: result.hasMore,
|
|
1990
|
+
coverage
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
async fetchBarsInRange(symbol, timeframe, from, to, signal) {
|
|
1994
|
+
const params = {
|
|
1995
|
+
rootSymbol: symbol,
|
|
1996
|
+
interval: timeframe,
|
|
1997
|
+
type: "init",
|
|
1998
|
+
timestamp: null,
|
|
1999
|
+
limit: Math.ceil((to.getTime() - from.getTime()) / this.intervalToMs(timeframe)) + 100,
|
|
2000
|
+
signal
|
|
2001
|
+
};
|
|
2002
|
+
const result = await this.fetchContinuousBars(params);
|
|
2003
|
+
return result.bars.filter(
|
|
2004
|
+
(bar) => bar.timestamp >= from.getTime() && bar.timestamp <= to.getTime()
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
async fetchBarsBefore(symbol, timeframe, before, limit, signal) {
|
|
2008
|
+
const params = {
|
|
2009
|
+
rootSymbol: symbol,
|
|
2010
|
+
interval: timeframe,
|
|
2011
|
+
type: "backward",
|
|
2012
|
+
timestamp: before.getTime(),
|
|
2013
|
+
limit: limit ?? 500,
|
|
2014
|
+
signal
|
|
2015
|
+
};
|
|
2016
|
+
const result = await this.fetchContinuousBars(params);
|
|
2017
|
+
return result.bars;
|
|
2018
|
+
}
|
|
2019
|
+
getSourceSemantics() {
|
|
2020
|
+
return {
|
|
2021
|
+
sourceId: this.name,
|
|
2022
|
+
sourceContractVersion: "continuous-futures-v1",
|
|
2023
|
+
instrumentClass: "futures",
|
|
2024
|
+
coverageMode: "window-hint",
|
|
2025
|
+
sessionModel: "exchange-calendar",
|
|
2026
|
+
timezone: "Asia/Ho_Chi_Minh",
|
|
2027
|
+
sessionCalendarId: "vn-futures-calendar",
|
|
2028
|
+
supportsExactRangeFetch: true,
|
|
2029
|
+
supportsBackwardAnchor: true,
|
|
2030
|
+
supportsForwardAnchor: false,
|
|
2031
|
+
supportsLiveBars: true,
|
|
2032
|
+
maxBarsPerRequest: 5e3,
|
|
2033
|
+
finalityMode: "historically-correctable",
|
|
2034
|
+
correctionLookbackMs: 7 * 24 * 60 * 60 * 1e3,
|
|
2035
|
+
// 1 week
|
|
2036
|
+
expectedGapReasons: [
|
|
2037
|
+
"not-fetched",
|
|
2038
|
+
"market-closed",
|
|
2039
|
+
"source-no-data",
|
|
2040
|
+
"rate-limited",
|
|
2041
|
+
"boundary-exhausted",
|
|
2042
|
+
"roll-gap"
|
|
2043
|
+
]
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Fetch continuous contract bars - tries BFF first, falls back to local computation
|
|
2048
|
+
*/
|
|
2049
|
+
async fetchContinuousBars(params) {
|
|
2050
|
+
const cacheKey = this.getCacheKey(params);
|
|
2051
|
+
const cached = barsCache.get(cacheKey);
|
|
2052
|
+
if (cached && params.type !== "backward") {
|
|
2053
|
+
const metadata = await this.getMetadata();
|
|
2054
|
+
return {
|
|
2055
|
+
bars: cached.slice(-params.limit),
|
|
2056
|
+
hasMore: cached.length > params.limit,
|
|
2057
|
+
metadata,
|
|
2058
|
+
rollPointsInRange: []
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
try {
|
|
2062
|
+
const bffResult = await this.fetchFromBFF(params);
|
|
2063
|
+
if (bffResult) {
|
|
2064
|
+
this.cacheBars(cacheKey, bffResult.bars);
|
|
2065
|
+
return bffResult;
|
|
2066
|
+
}
|
|
2067
|
+
} catch (error) {
|
|
2068
|
+
log.warn("[ContinuousContractAdapter] BFF fetch failed, falling back to local:", error);
|
|
2069
|
+
}
|
|
2070
|
+
return this.computeLocally(params);
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Fetch from BFF endpoint
|
|
2074
|
+
*/
|
|
2075
|
+
async fetchFromBFF(params) {
|
|
2076
|
+
const metadata = await this.getMetadata();
|
|
2077
|
+
const from = params.type === "backward" && params.timestamp ? params.timestamp - params.limit * this.intervalToMs(params.interval) : metadata.dataRange.from;
|
|
2078
|
+
const to = params.type === "backward" && params.timestamp ? params.timestamp : metadata.dataRange.to;
|
|
2079
|
+
const request = {
|
|
2080
|
+
method: "POST",
|
|
2081
|
+
headers: { "Content-Type": "application/json" },
|
|
2082
|
+
body: JSON.stringify({
|
|
2083
|
+
rootSymbol: params.rootSymbol,
|
|
2084
|
+
interval: params.interval,
|
|
2085
|
+
from,
|
|
2086
|
+
to,
|
|
2087
|
+
rollConfig: this.config
|
|
2088
|
+
}),
|
|
2089
|
+
signal: params.signal
|
|
2090
|
+
};
|
|
2091
|
+
const response = await fetch(BFF_ENDPOINT, request);
|
|
2092
|
+
if (!response.ok) {
|
|
2093
|
+
throw new Error(`BFF error: ${response.status}`);
|
|
2094
|
+
}
|
|
2095
|
+
const data = await response.json();
|
|
2096
|
+
return {
|
|
2097
|
+
bars: data.bars,
|
|
2098
|
+
hasMore: data.bars.length >= params.limit,
|
|
2099
|
+
metadata: data.metadata,
|
|
2100
|
+
rollPointsInRange: data.rollPoints
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
/**
|
|
2104
|
+
* Local computation fallback - stitches contracts client-side
|
|
2105
|
+
* Requires individual contract data to be available via another adapter
|
|
2106
|
+
*/
|
|
2107
|
+
async computeLocally(params) {
|
|
2108
|
+
const metadata = await this.getMetadata();
|
|
2109
|
+
if (!this.fetchRawBars) {
|
|
2110
|
+
log.warn("[ContinuousContractAdapter] fetchRawBars not provided, cannot compute locally");
|
|
2111
|
+
return {
|
|
2112
|
+
bars: [],
|
|
2113
|
+
hasMore: false,
|
|
2114
|
+
metadata,
|
|
2115
|
+
rollPointsInRange: []
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
const fromMs = params.type === "backward" && params.timestamp ? params.timestamp - params.limit * this.intervalToMs(params.interval) : metadata.dataRange.from;
|
|
2119
|
+
const toMs = params.type === "backward" && params.timestamp ? params.timestamp : metadata.dataRange.to;
|
|
2120
|
+
const relevantContracts = metadata.contracts.filter(
|
|
2121
|
+
(c) => c.lastTradeDate >= fromMs && c.firstTradeDate <= toMs
|
|
2122
|
+
);
|
|
2123
|
+
if (relevantContracts.length === 0) {
|
|
2124
|
+
return {
|
|
2125
|
+
bars: [],
|
|
2126
|
+
hasMore: false,
|
|
2127
|
+
metadata,
|
|
2128
|
+
rollPointsInRange: []
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
const contractBars = /* @__PURE__ */ new Map();
|
|
2132
|
+
await Promise.all(
|
|
2133
|
+
relevantContracts.map(async (c) => {
|
|
2134
|
+
try {
|
|
2135
|
+
const bars = await this.fetchRawBars(c.symbol, params.interval, new Date(fromMs), new Date(toMs));
|
|
2136
|
+
contractBars.set(c.symbol, [...bars]);
|
|
2137
|
+
} catch (err) {
|
|
2138
|
+
log.error(`Failed to fetch raw bars for contract ${c.symbol}:`, err);
|
|
2139
|
+
}
|
|
2140
|
+
})
|
|
2141
|
+
);
|
|
2142
|
+
const { computeRollAdjustment: computeRollAdjustment2 } = await Promise.resolve().then(() => (init_rollAlgorithms(), rollAlgorithms_exports));
|
|
2143
|
+
const stitchResult = computeRollAdjustment2({
|
|
2144
|
+
contracts: relevantContracts,
|
|
2145
|
+
contractBars,
|
|
2146
|
+
config: {
|
|
2147
|
+
rollMethod: this.config.rollMethod,
|
|
2148
|
+
adjustVolumes: this.config.adjustVolumes
|
|
2149
|
+
},
|
|
2150
|
+
from: fromMs,
|
|
2151
|
+
to: toMs
|
|
2152
|
+
});
|
|
2153
|
+
return {
|
|
2154
|
+
bars: stitchResult.bars,
|
|
2155
|
+
hasMore: stitchResult.bars.length >= params.limit,
|
|
2156
|
+
metadata: {
|
|
2157
|
+
...metadata,
|
|
2158
|
+
totalCumulativeAdjustment: stitchResult.totalCumulativeAdjustment
|
|
2159
|
+
},
|
|
2160
|
+
rollPointsInRange: stitchResult.rollPoints
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Get or fetch continuous contract metadata
|
|
2165
|
+
*/
|
|
2166
|
+
async getMetadata() {
|
|
2167
|
+
if (metadataCache.has(this.rootSymbol)) {
|
|
2168
|
+
return metadataCache.get(this.rootSymbol);
|
|
2169
|
+
}
|
|
2170
|
+
try {
|
|
2171
|
+
const response = await fetch(`${BFF_ENDPOINT}/metadata?rootSymbol=${this.rootSymbol}`);
|
|
2172
|
+
if (response.ok) {
|
|
2173
|
+
const metadata2 = await response.json();
|
|
2174
|
+
metadataCache.set(this.rootSymbol, metadata2);
|
|
2175
|
+
return metadata2;
|
|
2176
|
+
}
|
|
2177
|
+
} catch {
|
|
2178
|
+
}
|
|
2179
|
+
const metadata = this.generateSyntheticMetadata();
|
|
2180
|
+
metadataCache.set(this.rootSymbol, metadata);
|
|
2181
|
+
return metadata;
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Generate synthetic metadata for development/testing
|
|
2185
|
+
*/
|
|
2186
|
+
generateSyntheticMetadata() {
|
|
2187
|
+
const now = Date.now();
|
|
2188
|
+
const contracts = [
|
|
2189
|
+
{
|
|
2190
|
+
symbol: "VN30F2409",
|
|
2191
|
+
rootSymbol: "VN30F",
|
|
2192
|
+
expiry: (/* @__PURE__ */ new Date("2024-09-19")).getTime(),
|
|
2193
|
+
firstTradeDate: (/* @__PURE__ */ new Date("2024-06-20")).getTime(),
|
|
2194
|
+
lastTradeDate: (/* @__PURE__ */ new Date("2024-09-16")).getTime(),
|
|
2195
|
+
monthCode: "U",
|
|
2196
|
+
year: 24,
|
|
2197
|
+
tickSize: 0.1,
|
|
2198
|
+
multiplier: 1e5,
|
|
2199
|
+
exchange: "VNX"
|
|
2200
|
+
},
|
|
2201
|
+
{
|
|
2202
|
+
symbol: "VN30F2412",
|
|
2203
|
+
rootSymbol: "VN30F",
|
|
2204
|
+
expiry: (/* @__PURE__ */ new Date("2024-12-19")).getTime(),
|
|
2205
|
+
firstTradeDate: (/* @__PURE__ */ new Date("2024-09-20")).getTime(),
|
|
2206
|
+
lastTradeDate: (/* @__PURE__ */ new Date("2024-12-16")).getTime(),
|
|
2207
|
+
monthCode: "Z",
|
|
2208
|
+
year: 24,
|
|
2209
|
+
tickSize: 0.1,
|
|
2210
|
+
multiplier: 1e5,
|
|
2211
|
+
exchange: "VNX"
|
|
2212
|
+
},
|
|
2213
|
+
{
|
|
2214
|
+
symbol: "VN30F2503",
|
|
2215
|
+
rootSymbol: "VN30F",
|
|
2216
|
+
expiry: (/* @__PURE__ */ new Date("2025-03-20")).getTime(),
|
|
2217
|
+
firstTradeDate: (/* @__PURE__ */ new Date("2024-12-20")).getTime(),
|
|
2218
|
+
lastTradeDate: (/* @__PURE__ */ new Date("2025-03-17")).getTime(),
|
|
2219
|
+
monthCode: "H",
|
|
2220
|
+
year: 25,
|
|
2221
|
+
tickSize: 0.1,
|
|
2222
|
+
multiplier: 1e5,
|
|
2223
|
+
exchange: "VNX"
|
|
2224
|
+
}
|
|
2225
|
+
];
|
|
2226
|
+
const rollPoints = [];
|
|
2227
|
+
let cumulativeAdjustment = 0;
|
|
2228
|
+
for (let i = 0; i < contracts.length - 1; i++) {
|
|
2229
|
+
const fromContract = contracts[i];
|
|
2230
|
+
const toContract = contracts[i + 1];
|
|
2231
|
+
const rollDate = fromContract.lastTradeDate;
|
|
2232
|
+
const fromPrice = 1200 + i * 10;
|
|
2233
|
+
const toPrice = 1210 + i * 10;
|
|
2234
|
+
let adjustmentFactor = 1;
|
|
2235
|
+
const method = this.config.rollMethod;
|
|
2236
|
+
if (method === "ratio") {
|
|
2237
|
+
adjustmentFactor = fromPrice / toPrice;
|
|
2238
|
+
} else if (method === "panama") {
|
|
2239
|
+
adjustmentFactor = fromPrice - toPrice;
|
|
2240
|
+
cumulativeAdjustment += adjustmentFactor;
|
|
2241
|
+
} else if (method === "backward-ratio") {
|
|
2242
|
+
adjustmentFactor = toPrice / fromPrice;
|
|
2243
|
+
}
|
|
2244
|
+
rollPoints.push({
|
|
2245
|
+
timestamp: rollDate,
|
|
2246
|
+
fromContract: fromContract.symbol,
|
|
2247
|
+
toContract: toContract.symbol,
|
|
2248
|
+
fromPrice,
|
|
2249
|
+
toPrice,
|
|
2250
|
+
adjustmentFactor,
|
|
2251
|
+
cumulativeAdjustment,
|
|
2252
|
+
method
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
return {
|
|
2256
|
+
rootSymbol: this.rootSymbol,
|
|
2257
|
+
contracts,
|
|
2258
|
+
rollPoints,
|
|
2259
|
+
activeContract: contracts[contracts.length - 1].symbol,
|
|
2260
|
+
nextRollDate: contracts[contracts.length - 1].lastTradeDate,
|
|
2261
|
+
totalCumulativeAdjustment: cumulativeAdjustment,
|
|
2262
|
+
dataRange: {
|
|
2263
|
+
from: contracts[0].firstTradeDate,
|
|
2264
|
+
to: now
|
|
2265
|
+
}
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Build coverage fragments for canonical fetch
|
|
2270
|
+
*/
|
|
2271
|
+
buildCoverage(result, params) {
|
|
2272
|
+
const { bars, metadata } = result;
|
|
2273
|
+
if (bars.length === 0) {
|
|
2274
|
+
if (params.type === "backward" && params.timestamp !== null) {
|
|
2275
|
+
return [
|
|
2276
|
+
{ fromTs: 0, toTs: params.timestamp, status: "unresolved", reason: "boundary-exhausted" }
|
|
2277
|
+
];
|
|
2278
|
+
}
|
|
2279
|
+
return [{ fromTs: 0, toTs: Date.now(), status: "unresolved", reason: "source-no-data" }];
|
|
2280
|
+
}
|
|
2281
|
+
const oldestTs = bars[0].timestamp;
|
|
2282
|
+
const newestTs = bars[bars.length - 1].timestamp;
|
|
2283
|
+
const covered = { fromTs: oldestTs, toTs: newestTs, status: "covered" };
|
|
2284
|
+
const fragments = [covered];
|
|
2285
|
+
if (oldestTs > metadata.dataRange.from) {
|
|
2286
|
+
fragments.unshift({
|
|
2287
|
+
fromTs: metadata.dataRange.from,
|
|
2288
|
+
toTs: oldestTs,
|
|
2289
|
+
status: "unresolved",
|
|
2290
|
+
reason: "not-fetched"
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
if (!result.hasMore && newestTs < metadata.dataRange.to) {
|
|
2294
|
+
fragments.push({
|
|
2295
|
+
fromTs: newestTs,
|
|
2296
|
+
toTs: metadata.dataRange.to,
|
|
2297
|
+
status: "unresolved",
|
|
2298
|
+
reason: "boundary-exhausted"
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
for (const rollPoint of result.rollPointsInRange) {
|
|
2302
|
+
fragments.push({
|
|
2303
|
+
fromTs: rollPoint.timestamp,
|
|
2304
|
+
toTs: rollPoint.timestamp + 1,
|
|
2305
|
+
status: "covered",
|
|
2306
|
+
reason: "roll-gap"
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2309
|
+
return fragments.sort((a, b) => a.fromTs - b.fromTs);
|
|
2310
|
+
}
|
|
2311
|
+
mapToContinuousParams(params) {
|
|
2312
|
+
return {
|
|
2313
|
+
rootSymbol: params.symbol,
|
|
2314
|
+
interval: params.interval,
|
|
2315
|
+
type: params.type,
|
|
2316
|
+
timestamp: params.timestamp,
|
|
2317
|
+
limit: params.limit,
|
|
2318
|
+
signal: params.signal
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
getCacheKey(params) {
|
|
2322
|
+
return `${params.rootSymbol}|${params.interval}|${params.type}|${params.timestamp ?? "init"}|${params.limit}`;
|
|
2323
|
+
}
|
|
2324
|
+
cacheBars(key, bars) {
|
|
2325
|
+
if (barsCache.size > 50) {
|
|
2326
|
+
const firstKey = barsCache.keys().next().value;
|
|
2327
|
+
if (firstKey) barsCache.delete(firstKey);
|
|
2328
|
+
}
|
|
2329
|
+
barsCache.set(key, bars);
|
|
2330
|
+
}
|
|
2331
|
+
intervalToMs(interval) {
|
|
2332
|
+
switch (interval) {
|
|
2333
|
+
case "1m":
|
|
2334
|
+
return 6e4;
|
|
2335
|
+
case "3m":
|
|
2336
|
+
return 18e4;
|
|
2337
|
+
case "5m":
|
|
2338
|
+
return 3e5;
|
|
2339
|
+
case "15m":
|
|
2340
|
+
return 9e5;
|
|
2341
|
+
case "30m":
|
|
2342
|
+
return 18e5;
|
|
2343
|
+
case "1h":
|
|
2344
|
+
case "1H":
|
|
2345
|
+
return 36e5;
|
|
2346
|
+
case "2H":
|
|
2347
|
+
return 72e5;
|
|
2348
|
+
case "4h":
|
|
2349
|
+
case "4H":
|
|
2350
|
+
return 144e5;
|
|
2351
|
+
case "D":
|
|
2352
|
+
case "1D":
|
|
2353
|
+
case "1d":
|
|
2354
|
+
return 864e5;
|
|
2355
|
+
case "W":
|
|
2356
|
+
case "1W":
|
|
2357
|
+
case "1w":
|
|
2358
|
+
return 6048e5;
|
|
2359
|
+
default:
|
|
2360
|
+
return 864e5;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
};
|
|
2364
|
+
|
|
2365
|
+
// src/widget/adapters/ContinuousFuturesStockDataAdapter.ts
|
|
2366
|
+
var ContinuousFuturesStockDataAdapter = class {
|
|
2367
|
+
continuousAdapter;
|
|
2368
|
+
baseAdapter;
|
|
2369
|
+
symbol;
|
|
2370
|
+
constructor(symbol, baseAdapter) {
|
|
2371
|
+
this.baseAdapter = baseAdapter;
|
|
2372
|
+
this.symbol = symbol;
|
|
2373
|
+
const config = DEFAULT_VN_FUTURES_ROLL_CONFIG[symbol] || DEFAULT_VN_FUTURES_ROLL_CONFIG["VN30F1M"];
|
|
2374
|
+
this.continuousAdapter = new ContinuousContractAdapter(
|
|
2375
|
+
config.rootSymbol,
|
|
2376
|
+
config,
|
|
2377
|
+
async (sym, tf, from, to) => {
|
|
2378
|
+
const bars = await this.baseAdapter.fetchBarsInRange(sym, tf, from, to);
|
|
2379
|
+
return bars.map((b) => ({
|
|
2380
|
+
timestamp: b.date.getTime(),
|
|
2381
|
+
open: b.open,
|
|
2382
|
+
high: b.high,
|
|
2383
|
+
low: b.low,
|
|
2384
|
+
close: b.close,
|
|
2385
|
+
volume: b.volume
|
|
2386
|
+
}));
|
|
2387
|
+
}
|
|
2388
|
+
);
|
|
2389
|
+
}
|
|
2390
|
+
async fetchBarsInRange(symbol, timeframe, from, to, chartId) {
|
|
2391
|
+
if (symbol === this.symbol) {
|
|
2392
|
+
const rootSymbol = this.continuousAdapter.getConfig().rootSymbol;
|
|
2393
|
+
const bars = await this.continuousAdapter.fetchBarsInRange?.(rootSymbol, timeframe, from, to);
|
|
2394
|
+
return (bars || []).map((b, index) => ({
|
|
2395
|
+
date: new Date(b.timestamp),
|
|
2396
|
+
open: b.open,
|
|
2397
|
+
high: b.high,
|
|
2398
|
+
low: b.low,
|
|
2399
|
+
close: b.close,
|
|
2400
|
+
volume: b.volume,
|
|
2401
|
+
index,
|
|
2402
|
+
dataIndex: index
|
|
2403
|
+
}));
|
|
2404
|
+
}
|
|
2405
|
+
return this.baseAdapter.fetchBarsInRange(symbol, timeframe, from, to, chartId);
|
|
2406
|
+
}
|
|
2407
|
+
async fetchBarsBefore(symbol, timeframe, before, limit, chartId) {
|
|
2408
|
+
if (symbol === this.symbol) {
|
|
2409
|
+
const rootSymbol = this.continuousAdapter.getConfig().rootSymbol;
|
|
2410
|
+
const bars = await this.continuousAdapter.fetchBarsBefore?.(rootSymbol, timeframe, before, limit);
|
|
2411
|
+
return (bars || []).map((b, index) => ({
|
|
2412
|
+
date: new Date(b.timestamp),
|
|
2413
|
+
open: b.open,
|
|
2414
|
+
high: b.high,
|
|
2415
|
+
low: b.low,
|
|
2416
|
+
close: b.close,
|
|
2417
|
+
volume: b.volume,
|
|
2418
|
+
index,
|
|
2419
|
+
dataIndex: index
|
|
2420
|
+
}));
|
|
2421
|
+
}
|
|
2422
|
+
return this.baseAdapter.fetchBarsBefore(symbol, timeframe, before, limit, chartId);
|
|
2423
|
+
}
|
|
2424
|
+
subscribeToBar(symbol, timeframe, onBar) {
|
|
2425
|
+
if (symbol === this.symbol) {
|
|
2426
|
+
let unsubscribed = false;
|
|
2427
|
+
let innerUnsubscribe = null;
|
|
2428
|
+
this.continuousAdapter.getMetadata().then((metadata) => {
|
|
2429
|
+
if (unsubscribed) return;
|
|
2430
|
+
const activeContract = metadata.activeContract || symbol;
|
|
2431
|
+
innerUnsubscribe = this.baseAdapter.subscribeToBar(activeContract, timeframe, onBar);
|
|
2432
|
+
}).catch((err) => {
|
|
2433
|
+
console.warn("Failed to resolve active contract for continuous subscription:", err);
|
|
2434
|
+
});
|
|
2435
|
+
return () => {
|
|
2436
|
+
unsubscribed = true;
|
|
2437
|
+
if (innerUnsubscribe) innerUnsubscribe();
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
return this.baseAdapter.subscribeToBar(symbol, timeframe, onBar);
|
|
2441
|
+
}
|
|
2442
|
+
subscribeToTrades(symbol, onTrade) {
|
|
2443
|
+
return this.baseAdapter.subscribeToTrades(symbol, onTrade);
|
|
2444
|
+
}
|
|
2445
|
+
subscribeToOrderbook(symbol, onUpdate) {
|
|
2446
|
+
return this.baseAdapter.subscribeToOrderbook(symbol, onUpdate);
|
|
2447
|
+
}
|
|
2448
|
+
async searchSymbols(query) {
|
|
2449
|
+
return this.baseAdapter.searchSymbols(query);
|
|
2450
|
+
}
|
|
2451
|
+
getSourceSemantics() {
|
|
2452
|
+
return this.continuousAdapter.getSourceSemantics();
|
|
2453
|
+
}
|
|
2454
|
+
getLastBackfillCoverage(symbol, timeframe, chartId) {
|
|
2455
|
+
return this.baseAdapter.getLastBackfillCoverage?.(symbol, timeframe, chartId) ?? null;
|
|
2456
|
+
}
|
|
2457
|
+
getLastRangeCoverage(symbol, timeframe, chartId) {
|
|
2458
|
+
return this.baseAdapter.getLastRangeCoverage?.(symbol, timeframe, chartId) ?? null;
|
|
2459
|
+
}
|
|
2460
|
+
};
|
|
2461
|
+
|
|
2462
|
+
// src/widget/adapters/MultiSourceDataAdapter.ts
|
|
2463
|
+
function matchAdapter(symbol, config) {
|
|
2464
|
+
const route = config.routes.find((r) => r.pattern.test(symbol));
|
|
2465
|
+
return route?.adapter ?? config.defaultAdapter;
|
|
2466
|
+
}
|
|
2467
|
+
var MultiSourceDataAdapter = class {
|
|
2468
|
+
constructor(config) {
|
|
2469
|
+
this.config = config;
|
|
2470
|
+
this._cache = /* @__PURE__ */ new Map();
|
|
2471
|
+
}
|
|
2472
|
+
config;
|
|
2473
|
+
_cache;
|
|
2474
|
+
_resolve(symbol) {
|
|
2475
|
+
const cached = this._cache.get(symbol);
|
|
2476
|
+
if (cached) {
|
|
2477
|
+
return cached;
|
|
2478
|
+
}
|
|
2479
|
+
const adapter = matchAdapter(symbol, this.config);
|
|
2480
|
+
this._cache.set(symbol, adapter);
|
|
2481
|
+
return adapter;
|
|
2482
|
+
}
|
|
2483
|
+
async fetchBarsInRange(symbol, timeframe, from, to, chartId) {
|
|
2484
|
+
return this._resolve(symbol).fetchBarsInRange(symbol, timeframe, from, to, chartId);
|
|
2485
|
+
}
|
|
2486
|
+
async fetchBarsBefore(symbol, timeframe, before, limit, chartId) {
|
|
2487
|
+
return this._resolve(symbol).fetchBarsBefore(symbol, timeframe, before, limit, chartId);
|
|
2488
|
+
}
|
|
2489
|
+
subscribeToBar(symbol, timeframe, onBar) {
|
|
2490
|
+
return this._resolve(symbol).subscribeToBar(symbol, timeframe, onBar);
|
|
2491
|
+
}
|
|
2492
|
+
subscribeToTrades(symbol, onTrade) {
|
|
2493
|
+
return this._resolve(symbol).subscribeToTrades(symbol, onTrade);
|
|
2494
|
+
}
|
|
2495
|
+
subscribeToOrderbook(symbol, onUpdate) {
|
|
2496
|
+
return this._resolve(symbol).subscribeToOrderbook(symbol, onUpdate);
|
|
2497
|
+
}
|
|
2498
|
+
async searchSymbols(query) {
|
|
2499
|
+
const adapters = [
|
|
2500
|
+
...this.config.routes.map((r) => r.adapter),
|
|
2501
|
+
this.config.defaultAdapter
|
|
2502
|
+
];
|
|
2503
|
+
const results = await Promise.all(
|
|
2504
|
+
adapters.map((a) => a.searchSymbols(query))
|
|
2505
|
+
);
|
|
2506
|
+
const flat = results.flat();
|
|
2507
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2508
|
+
return flat.filter((s) => {
|
|
2509
|
+
if (seen.has(s.symbol)) return false;
|
|
2510
|
+
seen.add(s.symbol);
|
|
2511
|
+
return true;
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
getLastBackfillCoverage(symbol, timeframe, chartId) {
|
|
2515
|
+
if (!symbol) return null;
|
|
2516
|
+
return this._resolve(symbol).getLastBackfillCoverage?.(symbol, timeframe, chartId) ?? null;
|
|
2517
|
+
}
|
|
2518
|
+
getLastRangeCoverage(symbol, timeframe, chartId) {
|
|
2519
|
+
if (!symbol) return null;
|
|
2520
|
+
return this._resolve(symbol).getLastRangeCoverage?.(symbol, timeframe, chartId) ?? null;
|
|
2521
|
+
}
|
|
2522
|
+
};
|
|
2523
|
+
|
|
2524
|
+
exports.ContinuousFuturesStockDataAdapter = ContinuousFuturesStockDataAdapter;
|
|
2525
|
+
exports.MultiSourceDataAdapter = MultiSourceDataAdapter;
|
|
2526
|
+
exports.TIMEFRAME_MAP = TIMEFRAME_MAP;
|
|
2527
|
+
exports.VNInvestClient = VNInvestClient;
|
|
2528
|
+
exports.VNInvestDataAdapter = VNInvestDataAdapter;
|
|
2529
|
+
exports.VNInvestWebSocketClient = VNInvestWebSocketClient;
|
|
2530
|
+
exports.buildBackwardPageRequest = buildBackwardPageRequest;
|
|
2531
|
+
exports.normalizeBarsPagingMeta = normalizeBarsPagingMeta;
|
|
2532
|
+
exports.resolveApiBase = resolveApiBase;
|
|
2533
|
+
exports.useWatchlistRealtimeSync = useWatchlistRealtimeSync;
|
|
2534
|
+
|
|
2535
|
+
}));
|