@tradejs/core 2.0.14 → 2.0.16

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.
@@ -8550,19 +8550,9 @@ var buildBaseContextSnapshot = ({
8550
8550
  const btcCloseSeries = fullBtcCloseSeries.slice(-BASE_CONTEXT_CANDLE_WINDOW);
8551
8551
  const atr2 = toNullable(baseResult.atr);
8552
8552
  const bbWidthPct = baseResult.bbUpper != null && baseResult.bbLower != null && baseResult.bbMiddle != null && baseResult.bbMiddle !== 0 ? (baseResult.bbUpper - baseResult.bbLower) / baseResult.bbMiddle * 100 : null;
8553
- const atrPctSeries = materializeNumericHistory(
8554
- indicatorHistory.atrPct ?? createNumericHistoryBuffer()
8555
- );
8556
- const macdHistogramSeries = materializeNumericHistory(
8557
- indicatorHistory.macdHistogram ?? createNumericHistoryBuffer()
8558
- );
8559
- const spreadSeries = materializeNumericHistory(
8560
- indicatorHistory.spread ?? createNumericHistoryBuffer()
8561
- );
8562
8553
  const recent20 = candlesHistory.slice(-20);
8563
8554
  const prior20 = candlesHistory.slice(-21, -1);
8564
8555
  const structureWindow = candlesHistory.slice(-STRUCTURE_LOOKBACK);
8565
- const session = buildSessionContext(candle.timestamp);
8566
8556
  const recent20High = recent20.length > 0 ? Math.max(...recent20.map((item) => item.high)) : null;
8567
8557
  const recent20Low = recent20.length > 0 ? Math.min(...recent20.map((item) => item.low)) : null;
8568
8558
  const avgVolume20 = recent20.length > 0 ? recent20.reduce((sum2, item) => sum2 + item.volume, 0) / recent20.length : null;
@@ -8591,38 +8581,7 @@ var buildBaseContextSnapshot = ({
8591
8581
  );
8592
8582
  const maStackScore = baseResult.maFast == null || baseResult.maMedium == null || baseResult.maSlow == null ? null : Math.sign(baseResult.maFast - baseResult.maMedium) + Math.sign(baseResult.maMedium - baseResult.maSlow);
8593
8583
  const trendBias = maStackScore == null ? "neutral" : maStackScore > 0 ? "bull" : maStackScore < 0 ? "bear" : "neutral";
8594
- const persistenceWindow = closeSeries.slice(-10);
8595
- const directionalMoves = persistenceWindow.slice(1).map((value, index) => value - persistenceWindow[index]);
8596
- const persistence = directionalMoves.length === 0 ? null : directionalMoves.filter(
8597
- (delta) => trendBias === "bull" ? delta > 0 : trendBias === "bear" ? delta < 0 : delta === 0
8598
- ).length / directionalMoves.length;
8599
- const atrPctZScore = calculateZScore(
8600
- atrPctSeries,
8601
- toNullable(baseResult.atrPct)
8602
- );
8603
- const atrSlope = calculateLineSlope(atrPctSeries, 5);
8604
- const compressionScore = safeDivide(
8605
- toNullable(baseResult.atrPct),
8606
- getLastFiniteValue(atrPctSeries.slice(0, -1))
8607
- );
8608
- const expansionScore = compressionScore == null || compressionScore === 0 ? null : 1 / compressionScore;
8609
- const bbWidthPctSeries = calculateRecentBbWidthPctSeries(closeSeries, 100);
8610
- const rawAtrPctSeries = calculateRecentAtrPctSeries(candlesHistory, 100);
8611
- const rawAtrPct = safeDivide(atr2, candle.close);
8612
- const realizedVolatility = calculateRealizedVolatility(closeSeries);
8613
- const realizedVolatilitySeries = calculateRecentRealizedVolatilitySeries(
8614
- closeSeries,
8615
- 100
8616
- );
8617
- const rangeExpansionSeries = calculateRecentRangeExpansionSeries(
8618
- candlesHistory,
8619
- 20
8620
- );
8621
- const rangeExpansion = rangeExpansionSeries[rangeExpansionSeries.length - 1] ?? null;
8622
- const volatilityState = compressionScore == null ? "unknown" : compressionScore <= 0.9 ? "compressed" : compressionScore >= 1.1 ? "expanded" : "normal";
8623
8584
  const highLowRange = candle.high - candle.low;
8624
- const bodyStrength = highLowRange > 0 ? Math.abs(candle.close - candle.open) / highLowRange : null;
8625
- const closeLocationInRange = highLowRange > 0 ? (candle.close - candle.low) / highLowRange : null;
8626
8585
  const breakoutState = baseResult.highLevel == null || baseResult.lowLevel == null ? "unknown" : candle.close > baseResult.highLevel ? prevCandle != null && prevCandle.close <= baseResult.highLevel ? "above_high_level" : "failed_high_breakout" : candle.close < baseResult.lowLevel ? prevCandle != null && prevCandle.close >= baseResult.lowLevel ? "below_low_level" : "failed_low_breakout" : "inside_range";
8627
8586
  const touchTolerance = atr2 != null && Number.isFinite(atr2) && atr2 > 0 ? atr2 * 0.15 : null;
8628
8587
  const highLevel = baseResult.highLevel;
@@ -8655,253 +8614,347 @@ var buildBaseContextSnapshot = ({
8655
8614
  distanceScore * 0.45 + wickSupport * 0.25 + closeAcceptance * 0.3
8656
8615
  );
8657
8616
  })();
8658
- const recentFalseBreakoutDensity = highLevel == null || lowLevel == null || recent20.length < 2 ? null : recent20.reduce((count, item, index) => {
8659
- if (index === 0) {
8660
- return count;
8661
- }
8662
- const prevItem = recent20[index - 1];
8663
- if (!prevItem) {
8664
- return count;
8665
- }
8666
- if (prevItem.close > highLevel && item.close <= highLevel) {
8667
- return count + 1;
8668
- }
8669
- if (prevItem.close < lowLevel && item.close >= lowLevel) {
8670
- return count + 1;
8671
- }
8672
- return count;
8673
- }, 0) / (recent20.length - 1);
8674
8617
  const rejectionWickScore = trendBias === "bull" ? lowerWick : trendBias === "bear" ? upperWick : Math.max(upperWick ?? 0, lowerWick ?? 0);
8675
- const adxContext = buildAdxContext(adxValue) ?? calculateAdxContext(candlesHistory);
8676
- const rsiContext = buildRsiContext(rsiValue) ?? calculateRsiContext(closeSeries);
8677
- const benchmarkMaFast = averageLastN(btcCloseSeries, indicatorPeriods.maFast);
8678
- const benchmarkMaSlow = averageLastN(btcCloseSeries, indicatorPeriods.maSlow);
8679
- const coin1h = coinResampledCandles.h1;
8680
- const btc1h = btcResampledCandles.h1;
8681
- const btc4h = btcResampledCandles.h4;
8682
- const btc1d = btcResampledCandles.d1;
8683
- const eth1h = ethResampledCandles?.h1 ?? [];
8684
- const eth4h = ethResampledCandles?.h4 ?? [];
8685
- const eth1d = ethResampledCandles?.d1 ?? [];
8686
8618
  const coin4h = coinResampledCandles.h4;
8687
8619
  const coin1d = coinResampledCandles.d1;
8688
- const targetVsBtc = buildTargetVsBtcContext({
8689
- coin1h,
8690
- btc1h,
8691
- coin4h,
8692
- btc4h,
8693
- coin1d,
8694
- btc1d,
8695
- coinCandles: candlesHistory,
8696
- btcCandles: btcCandlesHistory
8697
- });
8698
- const targetVsEth = ethCandlesHistory.length >= 2 ? buildTargetVsEthContext({
8699
- coin1h,
8700
- eth1h,
8701
- coin4h,
8702
- eth4h,
8703
- coin1d,
8704
- eth1d,
8705
- coinCandles: candlesHistory,
8706
- ethCandles: ethCandlesHistory
8707
- }) : null;
8708
- const btcPsychologicalLevels = buildPsychologicalLevelAssetContext(
8709
- btcCandlesHistory,
8710
- 1e3
8711
- );
8712
- const ethPsychologicalLevels = buildPsychologicalLevelAssetContext(
8713
- ethCandlesHistory,
8714
- 100
8715
- );
8716
- const referencePsychologicalLevels = btcPsychologicalLevels != null || ethPsychologicalLevels != null ? {
8717
- ...btcPsychologicalLevels != null ? { BTCUSDT: btcPsychologicalLevels } : {},
8718
- ...ethPsychologicalLevels != null ? { ETHUSDT: ethPsychologicalLevels } : {}
8719
- } : null;
8720
- const relativeStrength1h = getRelativeChange(
8721
- baseResult.price1hPcnt,
8722
- btc1h.length >= 2 ? percentChange(
8723
- btc1h[btc1h.length - 1].close,
8724
- btc1h[Math.max(0, btc1h.length - 2)].close
8725
- ) : null
8726
- );
8727
- const relativeStrength4h = getRelativeChange(
8728
- coin4h.length >= 2 ? percentChange(
8729
- coin4h[coin4h.length - 1].close,
8730
- coin4h[coin4h.length - 2].close
8731
- ) : null,
8732
- btc4h.length >= 2 ? percentChange(
8733
- btc4h[btc4h.length - 1].close,
8734
- btc4h[btc4h.length - 2].close
8735
- ) : null
8736
- );
8737
- const relativeStrength1d = getRelativeChange(
8738
- coin1d.length >= 2 ? percentChange(
8739
- coin1d[coin1d.length - 1].close,
8740
- coin1d[coin1d.length - 2].close
8741
- ) : null,
8742
- btc1d.length >= 2 ? percentChange(
8743
- btc1d[btc1d.length - 1].close,
8744
- btc1d[btc1d.length - 2].close
8745
- ) : null
8746
- );
8747
- const benchmarkBias = btc1h.length >= 2 ? btc1h[btc1h.length - 1].close > btc1h[btc1h.length - 2].close ? "bull" : btc1h[btc1h.length - 1].close < btc1h[btc1h.length - 2].close ? "bear" : "neutral" : "neutral";
8748
- const trendAlignment = trendBias === "neutral" || benchmarkBias === "neutral" ? "neutral" : trendBias === benchmarkBias ? trendBias === "bull" ? "aligned_bull" : "aligned_bear" : "against_benchmark";
8749
- const benchmarkTrendBias = benchmarkMaFast == null || benchmarkMaSlow == null ? "neutral" : benchmarkMaFast > benchmarkMaSlow ? "bull" : benchmarkMaFast < benchmarkMaSlow ? "bear" : "neutral";
8750
- const structurePivots = detectConfirmedPivots(structureWindow, atr2);
8751
- const structureZones = buildPriceZones(structureWindow, structurePivots, atr2);
8752
- const swingContext = buildSwingContext(structurePivots);
8753
- const pivotContext = buildPivotContext(
8754
- structurePivots,
8755
- structureWindow.length,
8756
- atr2
8757
- );
8758
- const nearestSupport = getNearestZone(
8759
- structureZones,
8760
- "support",
8761
- candle.close
8762
- );
8763
- const nearestResistance = getNearestZone(
8764
- structureZones,
8765
- "resistance",
8766
- candle.close
8767
- );
8768
- const totalStructureVolume = structureWindow.reduce(
8769
- (sum2, item) => sum2 + item.volume,
8770
- 0
8771
- );
8772
- const priceInSupportZone = nearestSupport == null ? null : candle.close >= nearestSupport.lower && candle.close <= nearestSupport.upper;
8773
- const priceInResistanceZone = nearestResistance == null ? null : candle.close >= nearestResistance.lower && candle.close <= nearestResistance.upper;
8774
- const activeZoneType = priceInSupportZone ? "support" : priceInResistanceZone ? "resistance" : null;
8775
- const priceInZone = priceInSupportZone == null && priceInResistanceZone == null ? null : Boolean(priceInSupportZone || priceInResistanceZone);
8776
- const resistanceVolumeShare = safeDivide(
8777
- nearestResistance?.volume ?? null,
8778
- totalStructureVolume
8779
- );
8780
- const supportVolumeShare = safeDivide(
8781
- nearestSupport?.volume ?? null,
8782
- totalStructureVolume
8783
- );
8784
- const sweepState = nearestResistance == null && nearestSupport == null ? "unknown" : nearestResistance != null && candle.high > nearestResistance.upper && candle.close < nearestResistance.level ? "swept_high" : nearestSupport != null && candle.low < nearestSupport.lower && candle.close > nearestSupport.level ? "swept_low" : nearestResistance != null && candle.close > nearestResistance.upper ? "broken_high" : nearestSupport != null && candle.close < nearestSupport.lower ? "broken_low" : "none";
8785
- const liquiditySide = sweepState === "swept_high" || sweepState === "broken_high" ? "high" : sweepState === "swept_low" || sweepState === "broken_low" ? "low" : null;
8786
- const referenceZoneSide = liquiditySide === "high" ? "resistance" : liquiditySide === "low" ? "support" : null;
8787
- const prior20High = prior20.length > 0 ? Math.max(...prior20.map((item) => item.high)) : null;
8788
- const prior20Low = prior20.length > 0 ? Math.min(...prior20.map((item) => item.low)) : null;
8789
- const sweepHigh20 = prior20High == null ? null : candle.high > prior20High && candle.close < prior20High;
8790
- const sweepLow20 = prior20Low == null ? null : candle.low < prior20Low && candle.close > prior20Low;
8791
- const closeBackInsideRange = sweepHigh20 == null && sweepLow20 == null ? null : Boolean(sweepHigh20 || sweepLow20);
8792
- const stopRunDirection = sweepHigh20 ? "up" : sweepLow20 ? "down" : null;
8793
- const sweepWickPct = stopRunDirection === "up" ? upperWick : stopRunDirection === "down" ? lowerWick : null;
8794
- const recent3 = candlesHistory.slice(-3);
8795
- const recent5 = candlesHistory.slice(-5);
8796
- const closesAboveHighLevel3 = highLevel == null ? null : recent3.filter((item) => item.close > highLevel).length;
8797
- const closesBelowLowLevel3 = lowLevel == null ? null : recent3.filter((item) => item.close < lowLevel).length;
8798
- const failedAcceptanceBars = highLevel == null || lowLevel == null ? null : recent5.filter(
8799
- (item) => item.high > highLevel && item.close <= highLevel || item.low < lowLevel && item.close >= lowLevel
8800
- ).length;
8801
- const acceptanceScore = closesAboveHighLevel3 == null || closesBelowLowLevel3 == null ? null : (closesAboveHighLevel3 - closesBelowLowLevel3) / Math.max(1, recent3.length);
8802
- const breakoutBodyAtr = safeDivide(Math.abs(candle.close - candle.open), atr2);
8803
- const priceVolumeProfile = buildPriceVolumeProfileContext(
8804
- structureWindow,
8805
- candle.close,
8806
- atr2
8807
- );
8808
- const volumeStructure = buildVolumeStructureContext(
8809
- candlesHistory,
8810
- candle.close,
8811
- atr2
8812
- );
8813
- const srZones = buildSrZonesContext(
8814
- structureWindow,
8815
- candle.close,
8816
- prevCandle?.close ?? null,
8817
- atr2
8818
- );
8819
- const liquidityZones = buildLiquidityZonesContext(
8820
- candlesHistory.slice(-180),
8821
- candle.close,
8822
- prevCandle?.close ?? null,
8823
- atr2
8824
- );
8825
- const liquidityTails = buildLiquidityTailsContext(
8826
- candlesHistory.slice(-180),
8827
- candle.close,
8828
- atr2
8829
- );
8830
- const trendFollow = buildTrendFollowContext(
8831
- candlesHistory.slice(-220),
8832
- candle.close,
8833
- atr2
8834
- );
8835
- const structureZonesContext = buildStructureZonesContext(
8836
- swingContext,
8837
- pivotContext,
8838
- candle.close,
8839
- atr2,
8840
- structureWindow
8841
- );
8842
- const hl2Series = precomputedMaLayers === void 0 ? candlesHistory.map((item) => (item.high + item.low) / 2) : [];
8843
- const maLayers = buildMaLayersContext(hl2Series, precomputedMaLayers);
8844
- const contextMa = buildContextMaContext(
8845
- closeSeries,
8846
- candle.close,
8847
- atr2,
8848
- precomputedContextMa
8849
- );
8850
- const adaptiveChannel = buildAdaptiveChannelContext(
8851
- candlesHistory,
8852
- candle.close,
8853
- atr2,
8854
- precomputedAdaptiveChannel
8855
- );
8856
- const deltaContext = buildDeltaContext(structureWindow);
8857
- const snapshot = {
8858
- candle,
8859
- prevCandle,
8860
- raw: {
8861
- trend: {
8862
- maFast: baseResult.maFast,
8863
- maMedium: baseResult.maMedium,
8864
- maSlow: baseResult.maSlow
8620
+ const buildRelativeSnapshot = () => {
8621
+ const spreadSeries = materializeNumericHistory(
8622
+ indicatorHistory.spread ?? createNumericHistoryBuffer()
8623
+ );
8624
+ const benchmarkMaFast = averageLastN(
8625
+ btcCloseSeries,
8626
+ indicatorPeriods.maFast
8627
+ );
8628
+ const benchmarkMaSlow = averageLastN(
8629
+ btcCloseSeries,
8630
+ indicatorPeriods.maSlow
8631
+ );
8632
+ const coin1h = coinResampledCandles.h1;
8633
+ const btc1h = btcResampledCandles.h1;
8634
+ const btc4h = btcResampledCandles.h4;
8635
+ const btc1d = btcResampledCandles.d1;
8636
+ const eth1h = ethResampledCandles?.h1 ?? [];
8637
+ const eth4h = ethResampledCandles?.h4 ?? [];
8638
+ const eth1d = ethResampledCandles?.d1 ?? [];
8639
+ const targetVsBtc = buildTargetVsBtcContext({
8640
+ coin1h,
8641
+ btc1h,
8642
+ coin4h,
8643
+ btc4h,
8644
+ coin1d,
8645
+ btc1d,
8646
+ coinCandles: candlesHistory,
8647
+ btcCandles: btcCandlesHistory
8648
+ });
8649
+ const targetVsEth = ethCandlesHistory.length >= 2 ? buildTargetVsEthContext({
8650
+ coin1h,
8651
+ eth1h,
8652
+ coin4h,
8653
+ eth4h,
8654
+ coin1d,
8655
+ eth1d,
8656
+ coinCandles: candlesHistory,
8657
+ ethCandles: ethCandlesHistory
8658
+ }) : null;
8659
+ const btcPsychologicalLevels = buildPsychologicalLevelAssetContext(
8660
+ btcCandlesHistory,
8661
+ 1e3
8662
+ );
8663
+ const ethPsychologicalLevels = buildPsychologicalLevelAssetContext(
8664
+ ethCandlesHistory,
8665
+ 100
8666
+ );
8667
+ const referencePsychologicalLevels = btcPsychologicalLevels != null || ethPsychologicalLevels != null ? {
8668
+ ...btcPsychologicalLevels != null ? { BTCUSDT: btcPsychologicalLevels } : {},
8669
+ ...ethPsychologicalLevels != null ? { ETHUSDT: ethPsychologicalLevels } : {}
8670
+ } : null;
8671
+ const relativeStrength1h = getRelativeChange(
8672
+ baseResult.price1hPcnt,
8673
+ btc1h.length >= 2 ? percentChange(
8674
+ btc1h[btc1h.length - 1].close,
8675
+ btc1h[Math.max(0, btc1h.length - 2)].close
8676
+ ) : null
8677
+ );
8678
+ const relativeStrength4h = getRelativeChange(
8679
+ coin4h.length >= 2 ? percentChange(
8680
+ coin4h[coin4h.length - 1].close,
8681
+ coin4h[coin4h.length - 2].close
8682
+ ) : null,
8683
+ btc4h.length >= 2 ? percentChange(
8684
+ btc4h[btc4h.length - 1].close,
8685
+ btc4h[btc4h.length - 2].close
8686
+ ) : null
8687
+ );
8688
+ const relativeStrength1d = getRelativeChange(
8689
+ coin1d.length >= 2 ? percentChange(
8690
+ coin1d[coin1d.length - 1].close,
8691
+ coin1d[coin1d.length - 2].close
8692
+ ) : null,
8693
+ btc1d.length >= 2 ? percentChange(
8694
+ btc1d[btc1d.length - 1].close,
8695
+ btc1d[btc1d.length - 2].close
8696
+ ) : null
8697
+ );
8698
+ const benchmarkBias = btc1h.length >= 2 ? btc1h[btc1h.length - 1].close > btc1h[btc1h.length - 2].close ? "bull" : btc1h[btc1h.length - 1].close < btc1h[btc1h.length - 2].close ? "bear" : "neutral" : "neutral";
8699
+ const trendAlignment = trendBias === "neutral" || benchmarkBias === "neutral" ? "neutral" : trendBias === benchmarkBias ? trendBias === "bull" ? "aligned_bull" : "aligned_bear" : "against_benchmark";
8700
+ const benchmarkTrendBias = benchmarkMaFast == null || benchmarkMaSlow == null ? "neutral" : benchmarkMaFast > benchmarkMaSlow ? "bull" : benchmarkMaFast < benchmarkMaSlow ? "bear" : "neutral";
8701
+ return {
8702
+ benchmark: {
8703
+ maFast: benchmarkMaFast,
8704
+ maSlow: benchmarkMaSlow,
8705
+ bias: benchmarkTrendBias,
8706
+ relativeStrength1h,
8707
+ relativeStrength4h,
8708
+ relativeStrength1d,
8709
+ trendAlignment
8865
8710
  },
8866
- volatility: {
8867
- atr: atr2,
8868
- atrPct: toNullable(baseResult.atrPct),
8869
- bbUpper: baseResult.bbUpper,
8870
- bbMiddle: baseResult.bbMiddle,
8871
- bbLower: baseResult.bbLower,
8872
- bbWidthPct
8711
+ execution: {
8712
+ venueSpread: baseResult.spread,
8713
+ venueSpreadZScore: calculateZScore(
8714
+ spreadSeries,
8715
+ toNullable(baseResult.spread)
8716
+ )
8873
8717
  },
8874
- momentum: {
8875
- macd: toNullable(baseResult.macd),
8876
- macdSignal: toNullable(baseResult.macdSignal),
8877
- macdHistogram: toNullable(baseResult.macdHistogram)
8718
+ targetVsBtc,
8719
+ ...targetVsEth != null ? { targetVsEth } : {},
8720
+ ...referencePsychologicalLevels != null ? { referencePsychologicalLevels } : {}
8721
+ };
8722
+ };
8723
+ const buildStructureSnapshot = () => {
8724
+ const structurePivots = detectConfirmedPivots(structureWindow, atr2);
8725
+ const structureZones = buildPriceZones(
8726
+ structureWindow,
8727
+ structurePivots,
8728
+ atr2
8729
+ );
8730
+ const swingContext = buildSwingContext(structurePivots);
8731
+ const pivotContext = buildPivotContext(
8732
+ structurePivots,
8733
+ structureWindow.length,
8734
+ atr2
8735
+ );
8736
+ const nearestSupport = getNearestZone(
8737
+ structureZones,
8738
+ "support",
8739
+ candle.close
8740
+ );
8741
+ const nearestResistance = getNearestZone(
8742
+ structureZones,
8743
+ "resistance",
8744
+ candle.close
8745
+ );
8746
+ const totalStructureVolume = structureWindow.reduce(
8747
+ (sum2, item) => sum2 + item.volume,
8748
+ 0
8749
+ );
8750
+ const priceInSupportZone = nearestSupport == null ? null : candle.close >= nearestSupport.lower && candle.close <= nearestSupport.upper;
8751
+ const priceInResistanceZone = nearestResistance == null ? null : candle.close >= nearestResistance.lower && candle.close <= nearestResistance.upper;
8752
+ const activeZoneType = priceInSupportZone ? "support" : priceInResistanceZone ? "resistance" : null;
8753
+ const priceInZone = priceInSupportZone == null && priceInResistanceZone == null ? null : Boolean(priceInSupportZone || priceInResistanceZone);
8754
+ const resistanceVolumeShare = safeDivide(
8755
+ nearestResistance?.volume ?? null,
8756
+ totalStructureVolume
8757
+ );
8758
+ const supportVolumeShare = safeDivide(
8759
+ nearestSupport?.volume ?? null,
8760
+ totalStructureVolume
8761
+ );
8762
+ const sweepState = nearestResistance == null && nearestSupport == null ? "unknown" : nearestResistance != null && candle.high > nearestResistance.upper && candle.close < nearestResistance.level ? "swept_high" : nearestSupport != null && candle.low < nearestSupport.lower && candle.close > nearestSupport.level ? "swept_low" : nearestResistance != null && candle.close > nearestResistance.upper ? "broken_high" : nearestSupport != null && candle.close < nearestSupport.lower ? "broken_low" : "none";
8763
+ const liquiditySide = sweepState === "swept_high" || sweepState === "broken_high" ? "high" : sweepState === "swept_low" || sweepState === "broken_low" ? "low" : null;
8764
+ const referenceZoneSide = liquiditySide === "high" ? "resistance" : liquiditySide === "low" ? "support" : null;
8765
+ const prior20High = prior20.length > 0 ? Math.max(...prior20.map((item) => item.high)) : null;
8766
+ const prior20Low = prior20.length > 0 ? Math.min(...prior20.map((item) => item.low)) : null;
8767
+ const sweepHigh20 = prior20High == null ? null : candle.high > prior20High && candle.close < prior20High;
8768
+ const sweepLow20 = prior20Low == null ? null : candle.low < prior20Low && candle.close > prior20Low;
8769
+ const closeBackInsideRange = sweepHigh20 == null && sweepLow20 == null ? null : Boolean(sweepHigh20 || sweepLow20);
8770
+ const stopRunDirection = sweepHigh20 ? "up" : sweepLow20 ? "down" : null;
8771
+ const sweepWickPct = stopRunDirection === "up" ? upperWick : stopRunDirection === "down" ? lowerWick : null;
8772
+ const recent3 = candlesHistory.slice(-3);
8773
+ const recent5 = candlesHistory.slice(-5);
8774
+ const closesAboveHighLevel3 = highLevel == null ? null : recent3.filter((item) => item.close > highLevel).length;
8775
+ const closesBelowLowLevel3 = lowLevel == null ? null : recent3.filter((item) => item.close < lowLevel).length;
8776
+ const failedAcceptanceBars = highLevel == null || lowLevel == null ? null : recent5.filter(
8777
+ (item) => item.high > highLevel && item.close <= highLevel || item.low < lowLevel && item.close >= lowLevel
8778
+ ).length;
8779
+ const acceptanceScore = closesAboveHighLevel3 == null || closesBelowLowLevel3 == null ? null : (closesAboveHighLevel3 - closesBelowLowLevel3) / Math.max(1, recent3.length);
8780
+ const breakoutBodyAtr = safeDivide(
8781
+ Math.abs(candle.close - candle.open),
8782
+ atr2
8783
+ );
8784
+ const srZones = buildSrZonesContext(
8785
+ structureWindow,
8786
+ candle.close,
8787
+ prevCandle?.close ?? null,
8788
+ atr2
8789
+ );
8790
+ const liquidityZones = buildLiquidityZonesContext(
8791
+ candlesHistory.slice(-180),
8792
+ candle.close,
8793
+ prevCandle?.close ?? null,
8794
+ atr2
8795
+ );
8796
+ const liquidityTails = buildLiquidityTailsContext(
8797
+ candlesHistory.slice(-180),
8798
+ candle.close,
8799
+ atr2
8800
+ );
8801
+ const structureZonesContext = buildStructureZonesContext(
8802
+ swingContext,
8803
+ pivotContext,
8804
+ candle.close,
8805
+ atr2,
8806
+ structureWindow
8807
+ );
8808
+ return {
8809
+ swing: swingContext,
8810
+ zones: {
8811
+ support: {
8812
+ level: nearestSupport?.level ?? null,
8813
+ lower: nearestSupport?.lower ?? null,
8814
+ upper: nearestSupport?.upper ?? null,
8815
+ touches: nearestSupport?.touches ?? null,
8816
+ ageBars: nearestSupport?.ageBars ?? null,
8817
+ volumeShare: supportVolumeShare,
8818
+ distanceAtr: safeDivide(
8819
+ nearestSupport == null ? null : candle.close - nearestSupport.level,
8820
+ atr2
8821
+ )
8822
+ },
8823
+ resistance: {
8824
+ level: nearestResistance?.level ?? null,
8825
+ lower: nearestResistance?.lower ?? null,
8826
+ upper: nearestResistance?.upper ?? null,
8827
+ touches: nearestResistance?.touches ?? null,
8828
+ ageBars: nearestResistance?.ageBars ?? null,
8829
+ volumeShare: resistanceVolumeShare,
8830
+ distanceAtr: safeDivide(
8831
+ nearestResistance == null ? null : nearestResistance.level - candle.close,
8832
+ atr2
8833
+ )
8834
+ },
8835
+ active: {
8836
+ side: activeZoneType,
8837
+ priceInZone
8838
+ }
8878
8839
  },
8879
- volume: {
8880
- volume: candle.volume,
8881
- turnover: candle.turnover,
8882
- obv: baseResult.obv,
8883
- obvSma: baseResult.smaObv,
8884
- volume1h: baseResult.volume1h,
8885
- volume24h: baseResult.volume24h
8840
+ srZones,
8841
+ liquidity: {
8842
+ sweepState,
8843
+ side: liquiditySide,
8844
+ referenceZoneSide,
8845
+ sweepHigh20,
8846
+ sweepLow20,
8847
+ closeBackInsideRange,
8848
+ stopRunDirection,
8849
+ sweepWickPct
8886
8850
  },
8887
- price: {
8888
- prevClose: baseResult.prevClose,
8889
- price1hPct: baseResult.price1hPcnt,
8890
- price24hPct: baseResult.price24hPcnt,
8891
- highPrice1h: baseResult.highPrice1h,
8892
- lowPrice1h: baseResult.lowPrice1h,
8893
- highPrice24h: baseResult.highPrice24h,
8894
- lowPrice24h: baseResult.lowPrice24h
8851
+ liquidityZones,
8852
+ liquidityTails,
8853
+ structureZones: structureZonesContext,
8854
+ pivots: pivotContext,
8855
+ acceptance: {
8856
+ closesAboveHighLevel3,
8857
+ closesBelowLowLevel3,
8858
+ failedAcceptanceBars,
8859
+ acceptanceScore,
8860
+ breakoutBodyAtr
8861
+ },
8862
+ localRange: {
8863
+ rangePosition20: calculateRangePosition(
8864
+ candle.close,
8865
+ recent20Low,
8866
+ recent20High
8867
+ ),
8868
+ distanceToHighLevelAtr,
8869
+ distanceToLowLevelAtr,
8870
+ breakoutState,
8871
+ barsSinceBreakout: breakoutRuntimeState.barsSinceBreakout,
8872
+ breakoutRetestQuality
8895
8873
  },
8896
8874
  levels: {
8897
- highLevel: baseResult.highLevel,
8898
- lowLevel: baseResult.lowLevel
8875
+ highTouchCount20,
8876
+ lowTouchCount20,
8877
+ dominantTouchCount20
8899
8878
  },
8900
- crossAsset: {
8901
- btcCorrelation: baseResult.correlation
8879
+ candleQuality: {
8880
+ upperWickPct: upperWick,
8881
+ lowerWickPct: lowerWick,
8882
+ rejectionWickScore
8902
8883
  }
8903
- },
8904
- regime: {
8884
+ };
8885
+ };
8886
+ const buildRegimeSnapshot = () => {
8887
+ const atrPctSeries = materializeNumericHistory(
8888
+ indicatorHistory.atrPct ?? createNumericHistoryBuffer()
8889
+ );
8890
+ const macdHistogramSeries = materializeNumericHistory(
8891
+ indicatorHistory.macdHistogram ?? createNumericHistoryBuffer()
8892
+ );
8893
+ const persistenceWindow = closeSeries.slice(-10);
8894
+ const directionalMoves = persistenceWindow.slice(1).map((value, index) => value - persistenceWindow[index]);
8895
+ const persistence = directionalMoves.length === 0 ? null : directionalMoves.filter(
8896
+ (delta) => trendBias === "bull" ? delta > 0 : trendBias === "bear" ? delta < 0 : delta === 0
8897
+ ).length / directionalMoves.length;
8898
+ const atrPctZScore = calculateZScore(
8899
+ atrPctSeries,
8900
+ toNullable(baseResult.atrPct)
8901
+ );
8902
+ const atrSlope = calculateLineSlope(atrPctSeries, 5);
8903
+ const compressionScore = safeDivide(
8904
+ toNullable(baseResult.atrPct),
8905
+ getLastFiniteValue(atrPctSeries.slice(0, -1))
8906
+ );
8907
+ const expansionScore = compressionScore == null || compressionScore === 0 ? null : 1 / compressionScore;
8908
+ const bbWidthPctSeries = calculateRecentBbWidthPctSeries(closeSeries, 100);
8909
+ const rawAtrPctSeries = calculateRecentAtrPctSeries(candlesHistory, 100);
8910
+ const rawAtrPct = safeDivide(atr2, candle.close);
8911
+ const realizedVolatility = calculateRealizedVolatility(closeSeries);
8912
+ const realizedVolatilitySeries = calculateRecentRealizedVolatilitySeries(
8913
+ closeSeries,
8914
+ 100
8915
+ );
8916
+ const rangeExpansionSeries = calculateRecentRangeExpansionSeries(
8917
+ candlesHistory,
8918
+ 20
8919
+ );
8920
+ const rangeExpansion = rangeExpansionSeries[rangeExpansionSeries.length - 1] ?? null;
8921
+ const volatilityState = compressionScore == null ? "unknown" : compressionScore <= 0.9 ? "compressed" : compressionScore >= 1.1 ? "expanded" : "normal";
8922
+ const bodyStrength = highLowRange > 0 ? Math.abs(candle.close - candle.open) / highLowRange : null;
8923
+ const closeLocationInRange = highLowRange > 0 ? (candle.close - candle.low) / highLowRange : null;
8924
+ const recentFalseBreakoutDensity = highLevel == null || lowLevel == null || recent20.length < 2 ? null : recent20.reduce((count, item, index) => {
8925
+ if (index === 0) return count;
8926
+ const prevItem = recent20[index - 1];
8927
+ if (!prevItem) return count;
8928
+ if (prevItem.close > highLevel && item.close <= highLevel) {
8929
+ return count + 1;
8930
+ }
8931
+ if (prevItem.close < lowLevel && item.close >= lowLevel) {
8932
+ return count + 1;
8933
+ }
8934
+ return count;
8935
+ }, 0) / (recent20.length - 1);
8936
+ const adxContext = buildAdxContext(adxValue) ?? calculateAdxContext(candlesHistory);
8937
+ const rsiContext = buildRsiContext(rsiValue) ?? calculateRsiContext(closeSeries);
8938
+ const trendFollow = buildTrendFollowContext(
8939
+ candlesHistory.slice(-220),
8940
+ candle.close,
8941
+ atr2
8942
+ );
8943
+ const hl2Series = precomputedMaLayers === void 0 ? candlesHistory.map((item) => (item.high + item.low) / 2) : [];
8944
+ const maLayers = buildMaLayersContext(hl2Series, precomputedMaLayers);
8945
+ const contextMa = buildContextMaContext(
8946
+ closeSeries,
8947
+ candle.close,
8948
+ atr2,
8949
+ precomputedContextMa
8950
+ );
8951
+ const adaptiveChannel = buildAdaptiveChannelContext(
8952
+ candlesHistory,
8953
+ candle.close,
8954
+ atr2,
8955
+ precomputedAdaptiveChannel
8956
+ );
8957
+ return {
8905
8958
  trend: {
8906
8959
  bias: trendBias,
8907
8960
  maStackScore,
@@ -8974,89 +9027,17 @@ var buildBaseContextSnapshot = ({
8974
9027
  upCloseStreak: closeStreaks.up,
8975
9028
  downCloseStreak: closeStreaks.down
8976
9029
  },
8977
- session,
9030
+ session: buildSessionContext(candle.timestamp),
8978
9031
  memory: {
8979
9032
  recentFalseBreakoutDensity
8980
9033
  }
8981
- },
8982
- structure: {
8983
- swing: swingContext,
8984
- zones: {
8985
- support: {
8986
- level: nearestSupport?.level ?? null,
8987
- lower: nearestSupport?.lower ?? null,
8988
- upper: nearestSupport?.upper ?? null,
8989
- touches: nearestSupport?.touches ?? null,
8990
- ageBars: nearestSupport?.ageBars ?? null,
8991
- volumeShare: supportVolumeShare,
8992
- distanceAtr: safeDivide(
8993
- nearestSupport == null ? null : candle.close - nearestSupport.level,
8994
- atr2
8995
- )
8996
- },
8997
- resistance: {
8998
- level: nearestResistance?.level ?? null,
8999
- lower: nearestResistance?.lower ?? null,
9000
- upper: nearestResistance?.upper ?? null,
9001
- touches: nearestResistance?.touches ?? null,
9002
- ageBars: nearestResistance?.ageBars ?? null,
9003
- volumeShare: resistanceVolumeShare,
9004
- distanceAtr: safeDivide(
9005
- nearestResistance == null ? null : nearestResistance.level - candle.close,
9006
- atr2
9007
- )
9008
- },
9009
- active: {
9010
- side: activeZoneType,
9011
- priceInZone
9012
- }
9013
- },
9014
- srZones,
9015
- liquidity: {
9016
- sweepState,
9017
- side: liquiditySide,
9018
- referenceZoneSide,
9019
- sweepHigh20,
9020
- sweepLow20,
9021
- closeBackInsideRange,
9022
- stopRunDirection,
9023
- sweepWickPct
9024
- },
9025
- liquidityZones,
9026
- liquidityTails,
9027
- structureZones: structureZonesContext,
9028
- pivots: pivotContext,
9029
- acceptance: {
9030
- closesAboveHighLevel3,
9031
- closesBelowLowLevel3,
9032
- failedAcceptanceBars,
9033
- acceptanceScore,
9034
- breakoutBodyAtr
9035
- },
9036
- localRange: {
9037
- rangePosition20: calculateRangePosition(
9038
- candle.close,
9039
- recent20Low,
9040
- recent20High
9041
- ),
9042
- distanceToHighLevelAtr,
9043
- distanceToLowLevelAtr,
9044
- breakoutState,
9045
- barsSinceBreakout: breakoutRuntimeState.barsSinceBreakout,
9046
- breakoutRetestQuality
9047
- },
9048
- levels: {
9049
- highTouchCount20,
9050
- lowTouchCount20,
9051
- dominantTouchCount20
9052
- },
9053
- candleQuality: {
9054
- upperWickPct: upperWick,
9055
- lowerWickPct: lowerWick,
9056
- rejectionWickScore
9057
- }
9058
- },
9059
- participation: {
9034
+ };
9035
+ };
9036
+ const buildParticipationSnapshot = () => {
9037
+ let cachedPriceVolumeProfile;
9038
+ let cachedVolumeStructure;
9039
+ let cachedDelta;
9040
+ return {
9060
9041
  volume: {
9061
9042
  volumeRel20,
9062
9043
  turnoverRel20,
@@ -9069,30 +9050,89 @@ var buildBaseContextSnapshot = ({
9069
9050
  ),
9070
9051
  effortVsResult
9071
9052
  },
9072
- priceVolumeProfile,
9073
- volumeStructure,
9074
- delta: deltaContext
9075
- },
9076
- relative: {
9077
- benchmark: {
9078
- maFast: benchmarkMaFast,
9079
- maSlow: benchmarkMaSlow,
9080
- bias: benchmarkTrendBias,
9081
- relativeStrength1h,
9082
- relativeStrength4h,
9083
- relativeStrength1d,
9084
- trendAlignment
9053
+ get priceVolumeProfile() {
9054
+ return cachedPriceVolumeProfile ??= buildPriceVolumeProfileContext(
9055
+ structureWindow,
9056
+ candle.close,
9057
+ atr2
9058
+ );
9085
9059
  },
9086
- execution: {
9087
- venueSpread: baseResult.spread,
9088
- venueSpreadZScore: calculateZScore(
9089
- spreadSeries,
9090
- toNullable(baseResult.spread)
9091
- )
9060
+ get volumeStructure() {
9061
+ return cachedVolumeStructure ??= buildVolumeStructureContext(
9062
+ candlesHistory,
9063
+ candle.close,
9064
+ atr2
9065
+ );
9092
9066
  },
9093
- targetVsBtc,
9094
- ...targetVsEth != null ? { targetVsEth } : {},
9095
- ...referencePsychologicalLevels != null ? { referencePsychologicalLevels } : {}
9067
+ get delta() {
9068
+ return cachedDelta ??= buildDeltaContext(
9069
+ structureWindow
9070
+ );
9071
+ }
9072
+ };
9073
+ };
9074
+ let cachedStructureSnapshot;
9075
+ let cachedRegimeSnapshot;
9076
+ let cachedParticipationSnapshot;
9077
+ let cachedRelativeSnapshot;
9078
+ const snapshot = {
9079
+ candle,
9080
+ prevCandle,
9081
+ raw: {
9082
+ trend: {
9083
+ maFast: baseResult.maFast,
9084
+ maMedium: baseResult.maMedium,
9085
+ maSlow: baseResult.maSlow
9086
+ },
9087
+ volatility: {
9088
+ atr: atr2,
9089
+ atrPct: toNullable(baseResult.atrPct),
9090
+ bbUpper: baseResult.bbUpper,
9091
+ bbMiddle: baseResult.bbMiddle,
9092
+ bbLower: baseResult.bbLower,
9093
+ bbWidthPct
9094
+ },
9095
+ momentum: {
9096
+ macd: toNullable(baseResult.macd),
9097
+ macdSignal: toNullable(baseResult.macdSignal),
9098
+ macdHistogram: toNullable(baseResult.macdHistogram)
9099
+ },
9100
+ volume: {
9101
+ volume: candle.volume,
9102
+ turnover: candle.turnover,
9103
+ obv: baseResult.obv,
9104
+ obvSma: baseResult.smaObv,
9105
+ volume1h: baseResult.volume1h,
9106
+ volume24h: baseResult.volume24h
9107
+ },
9108
+ price: {
9109
+ prevClose: baseResult.prevClose,
9110
+ price1hPct: baseResult.price1hPcnt,
9111
+ price24hPct: baseResult.price24hPcnt,
9112
+ highPrice1h: baseResult.highPrice1h,
9113
+ lowPrice1h: baseResult.lowPrice1h,
9114
+ highPrice24h: baseResult.highPrice24h,
9115
+ lowPrice24h: baseResult.lowPrice24h
9116
+ },
9117
+ levels: {
9118
+ highLevel: baseResult.highLevel,
9119
+ lowLevel: baseResult.lowLevel
9120
+ },
9121
+ crossAsset: {
9122
+ btcCorrelation: baseResult.correlation
9123
+ }
9124
+ },
9125
+ get regime() {
9126
+ return cachedRegimeSnapshot ??= buildRegimeSnapshot();
9127
+ },
9128
+ get structure() {
9129
+ return cachedStructureSnapshot ??= buildStructureSnapshot();
9130
+ },
9131
+ get participation() {
9132
+ return cachedParticipationSnapshot ??= buildParticipationSnapshot();
9133
+ },
9134
+ get relative() {
9135
+ return cachedRelativeSnapshot ??= buildRelativeSnapshot();
9096
9136
  }
9097
9137
  };
9098
9138
  let cachedMtfSnapshot = null;
@@ -11300,6 +11340,22 @@ var createIndicators = (data, btcData = [], options = {}) => {
11300
11340
  const last = value[value.length - 1];
11301
11341
  return typeof last === "number" ? last : void 0;
11302
11342
  },
11343
+ latestNumbers: (key, count) => {
11344
+ const normalizedCount = Number.isFinite(count) ? Math.max(0, Math.trunc(count)) : 0;
11345
+ if (normalizedCount === 0) return [];
11346
+ const buffer = indicatorHistory[key];
11347
+ if (buffer) {
11348
+ const resultSize = Math.min(normalizedCount, buffer.size);
11349
+ const result = new Array(resultSize);
11350
+ const firstOffset = buffer.size - resultSize;
11351
+ for (let index = 0; index < resultSize; index += 1) {
11352
+ result[index] = buffer.values[(buffer.start + firstOffset + index) % ML_BASE_CANDLES_WINDOW];
11353
+ }
11354
+ return result;
11355
+ }
11356
+ const value = getHistoryResult()[key];
11357
+ return Array.isArray(value) ? value.slice(-normalizedCount).filter((item) => typeof item === "number") : [];
11358
+ },
11303
11359
  result: () => {
11304
11360
  return cloneHistorySnapshot(
11305
11361
  getHistoryResult()
@@ -12127,23 +12183,13 @@ var createTrendlineEngine = (initialCandles, options) => {
12127
12183
  };
12128
12184
  const next = (candle) => {
12129
12185
  appendCandle(candle);
12130
- let result = buildResult();
12131
- if (opts.capture && result.length === 0 && rawExtremaPoints.length) {
12132
- rebuildCandidatesLikeBatch();
12133
- result = buildResult();
12134
- }
12135
- return result;
12186
+ return buildResult();
12136
12187
  };
12137
12188
  const nextMany = (candles) => {
12138
12189
  for (const candle of candles) {
12139
12190
  appendCandle(candle);
12140
12191
  }
12141
- let result = buildResult();
12142
- if (opts.capture && result.length === 0 && rawExtremaPoints.length) {
12143
- rebuildCandidatesLikeBatch();
12144
- result = buildResult();
12145
- }
12146
- return result;
12192
+ return buildResult();
12147
12193
  };
12148
12194
  const getLines = () => buildResult();
12149
12195
  const reset = () => {