@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.
@@ -8435,19 +8435,9 @@ var buildBaseContextSnapshot = ({
8435
8435
  const btcCloseSeries = fullBtcCloseSeries.slice(-BASE_CONTEXT_CANDLE_WINDOW);
8436
8436
  const atr2 = toNullable(baseResult.atr);
8437
8437
  const bbWidthPct = baseResult.bbUpper != null && baseResult.bbLower != null && baseResult.bbMiddle != null && baseResult.bbMiddle !== 0 ? (baseResult.bbUpper - baseResult.bbLower) / baseResult.bbMiddle * 100 : null;
8438
- const atrPctSeries = materializeNumericHistory(
8439
- indicatorHistory.atrPct ?? createNumericHistoryBuffer()
8440
- );
8441
- const macdHistogramSeries = materializeNumericHistory(
8442
- indicatorHistory.macdHistogram ?? createNumericHistoryBuffer()
8443
- );
8444
- const spreadSeries = materializeNumericHistory(
8445
- indicatorHistory.spread ?? createNumericHistoryBuffer()
8446
- );
8447
8438
  const recent20 = candlesHistory.slice(-20);
8448
8439
  const prior20 = candlesHistory.slice(-21, -1);
8449
8440
  const structureWindow = candlesHistory.slice(-STRUCTURE_LOOKBACK);
8450
- const session = buildSessionContext(candle.timestamp);
8451
8441
  const recent20High = recent20.length > 0 ? Math.max(...recent20.map((item) => item.high)) : null;
8452
8442
  const recent20Low = recent20.length > 0 ? Math.min(...recent20.map((item) => item.low)) : null;
8453
8443
  const avgVolume20 = recent20.length > 0 ? recent20.reduce((sum2, item) => sum2 + item.volume, 0) / recent20.length : null;
@@ -8476,38 +8466,7 @@ var buildBaseContextSnapshot = ({
8476
8466
  );
8477
8467
  const maStackScore = baseResult.maFast == null || baseResult.maMedium == null || baseResult.maSlow == null ? null : Math.sign(baseResult.maFast - baseResult.maMedium) + Math.sign(baseResult.maMedium - baseResult.maSlow);
8478
8468
  const trendBias = maStackScore == null ? "neutral" : maStackScore > 0 ? "bull" : maStackScore < 0 ? "bear" : "neutral";
8479
- const persistenceWindow = closeSeries.slice(-10);
8480
- const directionalMoves = persistenceWindow.slice(1).map((value, index) => value - persistenceWindow[index]);
8481
- const persistence = directionalMoves.length === 0 ? null : directionalMoves.filter(
8482
- (delta) => trendBias === "bull" ? delta > 0 : trendBias === "bear" ? delta < 0 : delta === 0
8483
- ).length / directionalMoves.length;
8484
- const atrPctZScore = calculateZScore(
8485
- atrPctSeries,
8486
- toNullable(baseResult.atrPct)
8487
- );
8488
- const atrSlope = calculateLineSlope(atrPctSeries, 5);
8489
- const compressionScore = safeDivide(
8490
- toNullable(baseResult.atrPct),
8491
- getLastFiniteValue(atrPctSeries.slice(0, -1))
8492
- );
8493
- const expansionScore = compressionScore == null || compressionScore === 0 ? null : 1 / compressionScore;
8494
- const bbWidthPctSeries = calculateRecentBbWidthPctSeries(closeSeries, 100);
8495
- const rawAtrPctSeries = calculateRecentAtrPctSeries(candlesHistory, 100);
8496
- const rawAtrPct = safeDivide(atr2, candle.close);
8497
- const realizedVolatility = calculateRealizedVolatility(closeSeries);
8498
- const realizedVolatilitySeries = calculateRecentRealizedVolatilitySeries(
8499
- closeSeries,
8500
- 100
8501
- );
8502
- const rangeExpansionSeries = calculateRecentRangeExpansionSeries(
8503
- candlesHistory,
8504
- 20
8505
- );
8506
- const rangeExpansion = rangeExpansionSeries[rangeExpansionSeries.length - 1] ?? null;
8507
- const volatilityState = compressionScore == null ? "unknown" : compressionScore <= 0.9 ? "compressed" : compressionScore >= 1.1 ? "expanded" : "normal";
8508
8469
  const highLowRange = candle.high - candle.low;
8509
- const bodyStrength = highLowRange > 0 ? Math.abs(candle.close - candle.open) / highLowRange : null;
8510
- const closeLocationInRange = highLowRange > 0 ? (candle.close - candle.low) / highLowRange : null;
8511
8470
  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";
8512
8471
  const touchTolerance = atr2 != null && Number.isFinite(atr2) && atr2 > 0 ? atr2 * 0.15 : null;
8513
8472
  const highLevel = baseResult.highLevel;
@@ -8540,253 +8499,347 @@ var buildBaseContextSnapshot = ({
8540
8499
  distanceScore * 0.45 + wickSupport * 0.25 + closeAcceptance * 0.3
8541
8500
  );
8542
8501
  })();
8543
- const recentFalseBreakoutDensity = highLevel == null || lowLevel == null || recent20.length < 2 ? null : recent20.reduce((count, item, index) => {
8544
- if (index === 0) {
8545
- return count;
8546
- }
8547
- const prevItem = recent20[index - 1];
8548
- if (!prevItem) {
8549
- return count;
8550
- }
8551
- if (prevItem.close > highLevel && item.close <= highLevel) {
8552
- return count + 1;
8553
- }
8554
- if (prevItem.close < lowLevel && item.close >= lowLevel) {
8555
- return count + 1;
8556
- }
8557
- return count;
8558
- }, 0) / (recent20.length - 1);
8559
8502
  const rejectionWickScore = trendBias === "bull" ? lowerWick : trendBias === "bear" ? upperWick : Math.max(upperWick ?? 0, lowerWick ?? 0);
8560
- const adxContext = buildAdxContext(adxValue) ?? calculateAdxContext(candlesHistory);
8561
- const rsiContext = buildRsiContext(rsiValue) ?? calculateRsiContext(closeSeries);
8562
- const benchmarkMaFast = averageLastN(btcCloseSeries, indicatorPeriods.maFast);
8563
- const benchmarkMaSlow = averageLastN(btcCloseSeries, indicatorPeriods.maSlow);
8564
- const coin1h = coinResampledCandles.h1;
8565
- const btc1h = btcResampledCandles.h1;
8566
- const btc4h = btcResampledCandles.h4;
8567
- const btc1d = btcResampledCandles.d1;
8568
- const eth1h = ethResampledCandles?.h1 ?? [];
8569
- const eth4h = ethResampledCandles?.h4 ?? [];
8570
- const eth1d = ethResampledCandles?.d1 ?? [];
8571
8503
  const coin4h = coinResampledCandles.h4;
8572
8504
  const coin1d = coinResampledCandles.d1;
8573
- const targetVsBtc = buildTargetVsBtcContext({
8574
- coin1h,
8575
- btc1h,
8576
- coin4h,
8577
- btc4h,
8578
- coin1d,
8579
- btc1d,
8580
- coinCandles: candlesHistory,
8581
- btcCandles: btcCandlesHistory
8582
- });
8583
- const targetVsEth = ethCandlesHistory.length >= 2 ? buildTargetVsEthContext({
8584
- coin1h,
8585
- eth1h,
8586
- coin4h,
8587
- eth4h,
8588
- coin1d,
8589
- eth1d,
8590
- coinCandles: candlesHistory,
8591
- ethCandles: ethCandlesHistory
8592
- }) : null;
8593
- const btcPsychologicalLevels = buildPsychologicalLevelAssetContext(
8594
- btcCandlesHistory,
8595
- 1e3
8596
- );
8597
- const ethPsychologicalLevels = buildPsychologicalLevelAssetContext(
8598
- ethCandlesHistory,
8599
- 100
8600
- );
8601
- const referencePsychologicalLevels = btcPsychologicalLevels != null || ethPsychologicalLevels != null ? {
8602
- ...btcPsychologicalLevels != null ? { BTCUSDT: btcPsychologicalLevels } : {},
8603
- ...ethPsychologicalLevels != null ? { ETHUSDT: ethPsychologicalLevels } : {}
8604
- } : null;
8605
- const relativeStrength1h = getRelativeChange(
8606
- baseResult.price1hPcnt,
8607
- btc1h.length >= 2 ? percentChange(
8608
- btc1h[btc1h.length - 1].close,
8609
- btc1h[Math.max(0, btc1h.length - 2)].close
8610
- ) : null
8611
- );
8612
- const relativeStrength4h = getRelativeChange(
8613
- coin4h.length >= 2 ? percentChange(
8614
- coin4h[coin4h.length - 1].close,
8615
- coin4h[coin4h.length - 2].close
8616
- ) : null,
8617
- btc4h.length >= 2 ? percentChange(
8618
- btc4h[btc4h.length - 1].close,
8619
- btc4h[btc4h.length - 2].close
8620
- ) : null
8621
- );
8622
- const relativeStrength1d = getRelativeChange(
8623
- coin1d.length >= 2 ? percentChange(
8624
- coin1d[coin1d.length - 1].close,
8625
- coin1d[coin1d.length - 2].close
8626
- ) : null,
8627
- btc1d.length >= 2 ? percentChange(
8628
- btc1d[btc1d.length - 1].close,
8629
- btc1d[btc1d.length - 2].close
8630
- ) : null
8631
- );
8632
- 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";
8633
- const trendAlignment = trendBias === "neutral" || benchmarkBias === "neutral" ? "neutral" : trendBias === benchmarkBias ? trendBias === "bull" ? "aligned_bull" : "aligned_bear" : "against_benchmark";
8634
- const benchmarkTrendBias = benchmarkMaFast == null || benchmarkMaSlow == null ? "neutral" : benchmarkMaFast > benchmarkMaSlow ? "bull" : benchmarkMaFast < benchmarkMaSlow ? "bear" : "neutral";
8635
- const structurePivots = detectConfirmedPivots(structureWindow, atr2);
8636
- const structureZones = buildPriceZones(structureWindow, structurePivots, atr2);
8637
- const swingContext = buildSwingContext(structurePivots);
8638
- const pivotContext = buildPivotContext(
8639
- structurePivots,
8640
- structureWindow.length,
8641
- atr2
8642
- );
8643
- const nearestSupport = getNearestZone(
8644
- structureZones,
8645
- "support",
8646
- candle.close
8647
- );
8648
- const nearestResistance = getNearestZone(
8649
- structureZones,
8650
- "resistance",
8651
- candle.close
8652
- );
8653
- const totalStructureVolume = structureWindow.reduce(
8654
- (sum2, item) => sum2 + item.volume,
8655
- 0
8656
- );
8657
- const priceInSupportZone = nearestSupport == null ? null : candle.close >= nearestSupport.lower && candle.close <= nearestSupport.upper;
8658
- const priceInResistanceZone = nearestResistance == null ? null : candle.close >= nearestResistance.lower && candle.close <= nearestResistance.upper;
8659
- const activeZoneType = priceInSupportZone ? "support" : priceInResistanceZone ? "resistance" : null;
8660
- const priceInZone = priceInSupportZone == null && priceInResistanceZone == null ? null : Boolean(priceInSupportZone || priceInResistanceZone);
8661
- const resistanceVolumeShare = safeDivide(
8662
- nearestResistance?.volume ?? null,
8663
- totalStructureVolume
8664
- );
8665
- const supportVolumeShare = safeDivide(
8666
- nearestSupport?.volume ?? null,
8667
- totalStructureVolume
8668
- );
8669
- 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";
8670
- const liquiditySide = sweepState === "swept_high" || sweepState === "broken_high" ? "high" : sweepState === "swept_low" || sweepState === "broken_low" ? "low" : null;
8671
- const referenceZoneSide = liquiditySide === "high" ? "resistance" : liquiditySide === "low" ? "support" : null;
8672
- const prior20High = prior20.length > 0 ? Math.max(...prior20.map((item) => item.high)) : null;
8673
- const prior20Low = prior20.length > 0 ? Math.min(...prior20.map((item) => item.low)) : null;
8674
- const sweepHigh20 = prior20High == null ? null : candle.high > prior20High && candle.close < prior20High;
8675
- const sweepLow20 = prior20Low == null ? null : candle.low < prior20Low && candle.close > prior20Low;
8676
- const closeBackInsideRange = sweepHigh20 == null && sweepLow20 == null ? null : Boolean(sweepHigh20 || sweepLow20);
8677
- const stopRunDirection = sweepHigh20 ? "up" : sweepLow20 ? "down" : null;
8678
- const sweepWickPct = stopRunDirection === "up" ? upperWick : stopRunDirection === "down" ? lowerWick : null;
8679
- const recent3 = candlesHistory.slice(-3);
8680
- const recent5 = candlesHistory.slice(-5);
8681
- const closesAboveHighLevel3 = highLevel == null ? null : recent3.filter((item) => item.close > highLevel).length;
8682
- const closesBelowLowLevel3 = lowLevel == null ? null : recent3.filter((item) => item.close < lowLevel).length;
8683
- const failedAcceptanceBars = highLevel == null || lowLevel == null ? null : recent5.filter(
8684
- (item) => item.high > highLevel && item.close <= highLevel || item.low < lowLevel && item.close >= lowLevel
8685
- ).length;
8686
- const acceptanceScore = closesAboveHighLevel3 == null || closesBelowLowLevel3 == null ? null : (closesAboveHighLevel3 - closesBelowLowLevel3) / Math.max(1, recent3.length);
8687
- const breakoutBodyAtr = safeDivide(Math.abs(candle.close - candle.open), atr2);
8688
- const priceVolumeProfile = buildPriceVolumeProfileContext(
8689
- structureWindow,
8690
- candle.close,
8691
- atr2
8692
- );
8693
- const volumeStructure = buildVolumeStructureContext(
8694
- candlesHistory,
8695
- candle.close,
8696
- atr2
8697
- );
8698
- const srZones = buildSrZonesContext(
8699
- structureWindow,
8700
- candle.close,
8701
- prevCandle?.close ?? null,
8702
- atr2
8703
- );
8704
- const liquidityZones = buildLiquidityZonesContext(
8705
- candlesHistory.slice(-180),
8706
- candle.close,
8707
- prevCandle?.close ?? null,
8708
- atr2
8709
- );
8710
- const liquidityTails = buildLiquidityTailsContext(
8711
- candlesHistory.slice(-180),
8712
- candle.close,
8713
- atr2
8714
- );
8715
- const trendFollow = buildTrendFollowContext(
8716
- candlesHistory.slice(-220),
8717
- candle.close,
8718
- atr2
8719
- );
8720
- const structureZonesContext = buildStructureZonesContext(
8721
- swingContext,
8722
- pivotContext,
8723
- candle.close,
8724
- atr2,
8725
- structureWindow
8726
- );
8727
- const hl2Series = precomputedMaLayers === void 0 ? candlesHistory.map((item) => (item.high + item.low) / 2) : [];
8728
- const maLayers = buildMaLayersContext(hl2Series, precomputedMaLayers);
8729
- const contextMa = buildContextMaContext(
8730
- closeSeries,
8731
- candle.close,
8732
- atr2,
8733
- precomputedContextMa
8734
- );
8735
- const adaptiveChannel = buildAdaptiveChannelContext(
8736
- candlesHistory,
8737
- candle.close,
8738
- atr2,
8739
- precomputedAdaptiveChannel
8740
- );
8741
- const deltaContext = buildDeltaContext(structureWindow);
8742
- const snapshot = {
8743
- candle,
8744
- prevCandle,
8745
- raw: {
8746
- trend: {
8747
- maFast: baseResult.maFast,
8748
- maMedium: baseResult.maMedium,
8749
- maSlow: baseResult.maSlow
8505
+ const buildRelativeSnapshot = () => {
8506
+ const spreadSeries = materializeNumericHistory(
8507
+ indicatorHistory.spread ?? createNumericHistoryBuffer()
8508
+ );
8509
+ const benchmarkMaFast = averageLastN(
8510
+ btcCloseSeries,
8511
+ indicatorPeriods.maFast
8512
+ );
8513
+ const benchmarkMaSlow = averageLastN(
8514
+ btcCloseSeries,
8515
+ indicatorPeriods.maSlow
8516
+ );
8517
+ const coin1h = coinResampledCandles.h1;
8518
+ const btc1h = btcResampledCandles.h1;
8519
+ const btc4h = btcResampledCandles.h4;
8520
+ const btc1d = btcResampledCandles.d1;
8521
+ const eth1h = ethResampledCandles?.h1 ?? [];
8522
+ const eth4h = ethResampledCandles?.h4 ?? [];
8523
+ const eth1d = ethResampledCandles?.d1 ?? [];
8524
+ const targetVsBtc = buildTargetVsBtcContext({
8525
+ coin1h,
8526
+ btc1h,
8527
+ coin4h,
8528
+ btc4h,
8529
+ coin1d,
8530
+ btc1d,
8531
+ coinCandles: candlesHistory,
8532
+ btcCandles: btcCandlesHistory
8533
+ });
8534
+ const targetVsEth = ethCandlesHistory.length >= 2 ? buildTargetVsEthContext({
8535
+ coin1h,
8536
+ eth1h,
8537
+ coin4h,
8538
+ eth4h,
8539
+ coin1d,
8540
+ eth1d,
8541
+ coinCandles: candlesHistory,
8542
+ ethCandles: ethCandlesHistory
8543
+ }) : null;
8544
+ const btcPsychologicalLevels = buildPsychologicalLevelAssetContext(
8545
+ btcCandlesHistory,
8546
+ 1e3
8547
+ );
8548
+ const ethPsychologicalLevels = buildPsychologicalLevelAssetContext(
8549
+ ethCandlesHistory,
8550
+ 100
8551
+ );
8552
+ const referencePsychologicalLevels = btcPsychologicalLevels != null || ethPsychologicalLevels != null ? {
8553
+ ...btcPsychologicalLevels != null ? { BTCUSDT: btcPsychologicalLevels } : {},
8554
+ ...ethPsychologicalLevels != null ? { ETHUSDT: ethPsychologicalLevels } : {}
8555
+ } : null;
8556
+ const relativeStrength1h = getRelativeChange(
8557
+ baseResult.price1hPcnt,
8558
+ btc1h.length >= 2 ? percentChange(
8559
+ btc1h[btc1h.length - 1].close,
8560
+ btc1h[Math.max(0, btc1h.length - 2)].close
8561
+ ) : null
8562
+ );
8563
+ const relativeStrength4h = getRelativeChange(
8564
+ coin4h.length >= 2 ? percentChange(
8565
+ coin4h[coin4h.length - 1].close,
8566
+ coin4h[coin4h.length - 2].close
8567
+ ) : null,
8568
+ btc4h.length >= 2 ? percentChange(
8569
+ btc4h[btc4h.length - 1].close,
8570
+ btc4h[btc4h.length - 2].close
8571
+ ) : null
8572
+ );
8573
+ const relativeStrength1d = getRelativeChange(
8574
+ coin1d.length >= 2 ? percentChange(
8575
+ coin1d[coin1d.length - 1].close,
8576
+ coin1d[coin1d.length - 2].close
8577
+ ) : null,
8578
+ btc1d.length >= 2 ? percentChange(
8579
+ btc1d[btc1d.length - 1].close,
8580
+ btc1d[btc1d.length - 2].close
8581
+ ) : null
8582
+ );
8583
+ 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";
8584
+ const trendAlignment = trendBias === "neutral" || benchmarkBias === "neutral" ? "neutral" : trendBias === benchmarkBias ? trendBias === "bull" ? "aligned_bull" : "aligned_bear" : "against_benchmark";
8585
+ const benchmarkTrendBias = benchmarkMaFast == null || benchmarkMaSlow == null ? "neutral" : benchmarkMaFast > benchmarkMaSlow ? "bull" : benchmarkMaFast < benchmarkMaSlow ? "bear" : "neutral";
8586
+ return {
8587
+ benchmark: {
8588
+ maFast: benchmarkMaFast,
8589
+ maSlow: benchmarkMaSlow,
8590
+ bias: benchmarkTrendBias,
8591
+ relativeStrength1h,
8592
+ relativeStrength4h,
8593
+ relativeStrength1d,
8594
+ trendAlignment
8750
8595
  },
8751
- volatility: {
8752
- atr: atr2,
8753
- atrPct: toNullable(baseResult.atrPct),
8754
- bbUpper: baseResult.bbUpper,
8755
- bbMiddle: baseResult.bbMiddle,
8756
- bbLower: baseResult.bbLower,
8757
- bbWidthPct
8596
+ execution: {
8597
+ venueSpread: baseResult.spread,
8598
+ venueSpreadZScore: calculateZScore(
8599
+ spreadSeries,
8600
+ toNullable(baseResult.spread)
8601
+ )
8758
8602
  },
8759
- momentum: {
8760
- macd: toNullable(baseResult.macd),
8761
- macdSignal: toNullable(baseResult.macdSignal),
8762
- macdHistogram: toNullable(baseResult.macdHistogram)
8603
+ targetVsBtc,
8604
+ ...targetVsEth != null ? { targetVsEth } : {},
8605
+ ...referencePsychologicalLevels != null ? { referencePsychologicalLevels } : {}
8606
+ };
8607
+ };
8608
+ const buildStructureSnapshot = () => {
8609
+ const structurePivots = detectConfirmedPivots(structureWindow, atr2);
8610
+ const structureZones = buildPriceZones(
8611
+ structureWindow,
8612
+ structurePivots,
8613
+ atr2
8614
+ );
8615
+ const swingContext = buildSwingContext(structurePivots);
8616
+ const pivotContext = buildPivotContext(
8617
+ structurePivots,
8618
+ structureWindow.length,
8619
+ atr2
8620
+ );
8621
+ const nearestSupport = getNearestZone(
8622
+ structureZones,
8623
+ "support",
8624
+ candle.close
8625
+ );
8626
+ const nearestResistance = getNearestZone(
8627
+ structureZones,
8628
+ "resistance",
8629
+ candle.close
8630
+ );
8631
+ const totalStructureVolume = structureWindow.reduce(
8632
+ (sum2, item) => sum2 + item.volume,
8633
+ 0
8634
+ );
8635
+ const priceInSupportZone = nearestSupport == null ? null : candle.close >= nearestSupport.lower && candle.close <= nearestSupport.upper;
8636
+ const priceInResistanceZone = nearestResistance == null ? null : candle.close >= nearestResistance.lower && candle.close <= nearestResistance.upper;
8637
+ const activeZoneType = priceInSupportZone ? "support" : priceInResistanceZone ? "resistance" : null;
8638
+ const priceInZone = priceInSupportZone == null && priceInResistanceZone == null ? null : Boolean(priceInSupportZone || priceInResistanceZone);
8639
+ const resistanceVolumeShare = safeDivide(
8640
+ nearestResistance?.volume ?? null,
8641
+ totalStructureVolume
8642
+ );
8643
+ const supportVolumeShare = safeDivide(
8644
+ nearestSupport?.volume ?? null,
8645
+ totalStructureVolume
8646
+ );
8647
+ 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";
8648
+ const liquiditySide = sweepState === "swept_high" || sweepState === "broken_high" ? "high" : sweepState === "swept_low" || sweepState === "broken_low" ? "low" : null;
8649
+ const referenceZoneSide = liquiditySide === "high" ? "resistance" : liquiditySide === "low" ? "support" : null;
8650
+ const prior20High = prior20.length > 0 ? Math.max(...prior20.map((item) => item.high)) : null;
8651
+ const prior20Low = prior20.length > 0 ? Math.min(...prior20.map((item) => item.low)) : null;
8652
+ const sweepHigh20 = prior20High == null ? null : candle.high > prior20High && candle.close < prior20High;
8653
+ const sweepLow20 = prior20Low == null ? null : candle.low < prior20Low && candle.close > prior20Low;
8654
+ const closeBackInsideRange = sweepHigh20 == null && sweepLow20 == null ? null : Boolean(sweepHigh20 || sweepLow20);
8655
+ const stopRunDirection = sweepHigh20 ? "up" : sweepLow20 ? "down" : null;
8656
+ const sweepWickPct = stopRunDirection === "up" ? upperWick : stopRunDirection === "down" ? lowerWick : null;
8657
+ const recent3 = candlesHistory.slice(-3);
8658
+ const recent5 = candlesHistory.slice(-5);
8659
+ const closesAboveHighLevel3 = highLevel == null ? null : recent3.filter((item) => item.close > highLevel).length;
8660
+ const closesBelowLowLevel3 = lowLevel == null ? null : recent3.filter((item) => item.close < lowLevel).length;
8661
+ const failedAcceptanceBars = highLevel == null || lowLevel == null ? null : recent5.filter(
8662
+ (item) => item.high > highLevel && item.close <= highLevel || item.low < lowLevel && item.close >= lowLevel
8663
+ ).length;
8664
+ const acceptanceScore = closesAboveHighLevel3 == null || closesBelowLowLevel3 == null ? null : (closesAboveHighLevel3 - closesBelowLowLevel3) / Math.max(1, recent3.length);
8665
+ const breakoutBodyAtr = safeDivide(
8666
+ Math.abs(candle.close - candle.open),
8667
+ atr2
8668
+ );
8669
+ const srZones = buildSrZonesContext(
8670
+ structureWindow,
8671
+ candle.close,
8672
+ prevCandle?.close ?? null,
8673
+ atr2
8674
+ );
8675
+ const liquidityZones = buildLiquidityZonesContext(
8676
+ candlesHistory.slice(-180),
8677
+ candle.close,
8678
+ prevCandle?.close ?? null,
8679
+ atr2
8680
+ );
8681
+ const liquidityTails = buildLiquidityTailsContext(
8682
+ candlesHistory.slice(-180),
8683
+ candle.close,
8684
+ atr2
8685
+ );
8686
+ const structureZonesContext = buildStructureZonesContext(
8687
+ swingContext,
8688
+ pivotContext,
8689
+ candle.close,
8690
+ atr2,
8691
+ structureWindow
8692
+ );
8693
+ return {
8694
+ swing: swingContext,
8695
+ zones: {
8696
+ support: {
8697
+ level: nearestSupport?.level ?? null,
8698
+ lower: nearestSupport?.lower ?? null,
8699
+ upper: nearestSupport?.upper ?? null,
8700
+ touches: nearestSupport?.touches ?? null,
8701
+ ageBars: nearestSupport?.ageBars ?? null,
8702
+ volumeShare: supportVolumeShare,
8703
+ distanceAtr: safeDivide(
8704
+ nearestSupport == null ? null : candle.close - nearestSupport.level,
8705
+ atr2
8706
+ )
8707
+ },
8708
+ resistance: {
8709
+ level: nearestResistance?.level ?? null,
8710
+ lower: nearestResistance?.lower ?? null,
8711
+ upper: nearestResistance?.upper ?? null,
8712
+ touches: nearestResistance?.touches ?? null,
8713
+ ageBars: nearestResistance?.ageBars ?? null,
8714
+ volumeShare: resistanceVolumeShare,
8715
+ distanceAtr: safeDivide(
8716
+ nearestResistance == null ? null : nearestResistance.level - candle.close,
8717
+ atr2
8718
+ )
8719
+ },
8720
+ active: {
8721
+ side: activeZoneType,
8722
+ priceInZone
8723
+ }
8763
8724
  },
8764
- volume: {
8765
- volume: candle.volume,
8766
- turnover: candle.turnover,
8767
- obv: baseResult.obv,
8768
- obvSma: baseResult.smaObv,
8769
- volume1h: baseResult.volume1h,
8770
- volume24h: baseResult.volume24h
8725
+ srZones,
8726
+ liquidity: {
8727
+ sweepState,
8728
+ side: liquiditySide,
8729
+ referenceZoneSide,
8730
+ sweepHigh20,
8731
+ sweepLow20,
8732
+ closeBackInsideRange,
8733
+ stopRunDirection,
8734
+ sweepWickPct
8771
8735
  },
8772
- price: {
8773
- prevClose: baseResult.prevClose,
8774
- price1hPct: baseResult.price1hPcnt,
8775
- price24hPct: baseResult.price24hPcnt,
8776
- highPrice1h: baseResult.highPrice1h,
8777
- lowPrice1h: baseResult.lowPrice1h,
8778
- highPrice24h: baseResult.highPrice24h,
8779
- lowPrice24h: baseResult.lowPrice24h
8736
+ liquidityZones,
8737
+ liquidityTails,
8738
+ structureZones: structureZonesContext,
8739
+ pivots: pivotContext,
8740
+ acceptance: {
8741
+ closesAboveHighLevel3,
8742
+ closesBelowLowLevel3,
8743
+ failedAcceptanceBars,
8744
+ acceptanceScore,
8745
+ breakoutBodyAtr
8746
+ },
8747
+ localRange: {
8748
+ rangePosition20: calculateRangePosition(
8749
+ candle.close,
8750
+ recent20Low,
8751
+ recent20High
8752
+ ),
8753
+ distanceToHighLevelAtr,
8754
+ distanceToLowLevelAtr,
8755
+ breakoutState,
8756
+ barsSinceBreakout: breakoutRuntimeState.barsSinceBreakout,
8757
+ breakoutRetestQuality
8780
8758
  },
8781
8759
  levels: {
8782
- highLevel: baseResult.highLevel,
8783
- lowLevel: baseResult.lowLevel
8760
+ highTouchCount20,
8761
+ lowTouchCount20,
8762
+ dominantTouchCount20
8784
8763
  },
8785
- crossAsset: {
8786
- btcCorrelation: baseResult.correlation
8764
+ candleQuality: {
8765
+ upperWickPct: upperWick,
8766
+ lowerWickPct: lowerWick,
8767
+ rejectionWickScore
8787
8768
  }
8788
- },
8789
- regime: {
8769
+ };
8770
+ };
8771
+ const buildRegimeSnapshot = () => {
8772
+ const atrPctSeries = materializeNumericHistory(
8773
+ indicatorHistory.atrPct ?? createNumericHistoryBuffer()
8774
+ );
8775
+ const macdHistogramSeries = materializeNumericHistory(
8776
+ indicatorHistory.macdHistogram ?? createNumericHistoryBuffer()
8777
+ );
8778
+ const persistenceWindow = closeSeries.slice(-10);
8779
+ const directionalMoves = persistenceWindow.slice(1).map((value, index) => value - persistenceWindow[index]);
8780
+ const persistence = directionalMoves.length === 0 ? null : directionalMoves.filter(
8781
+ (delta) => trendBias === "bull" ? delta > 0 : trendBias === "bear" ? delta < 0 : delta === 0
8782
+ ).length / directionalMoves.length;
8783
+ const atrPctZScore = calculateZScore(
8784
+ atrPctSeries,
8785
+ toNullable(baseResult.atrPct)
8786
+ );
8787
+ const atrSlope = calculateLineSlope(atrPctSeries, 5);
8788
+ const compressionScore = safeDivide(
8789
+ toNullable(baseResult.atrPct),
8790
+ getLastFiniteValue(atrPctSeries.slice(0, -1))
8791
+ );
8792
+ const expansionScore = compressionScore == null || compressionScore === 0 ? null : 1 / compressionScore;
8793
+ const bbWidthPctSeries = calculateRecentBbWidthPctSeries(closeSeries, 100);
8794
+ const rawAtrPctSeries = calculateRecentAtrPctSeries(candlesHistory, 100);
8795
+ const rawAtrPct = safeDivide(atr2, candle.close);
8796
+ const realizedVolatility = calculateRealizedVolatility(closeSeries);
8797
+ const realizedVolatilitySeries = calculateRecentRealizedVolatilitySeries(
8798
+ closeSeries,
8799
+ 100
8800
+ );
8801
+ const rangeExpansionSeries = calculateRecentRangeExpansionSeries(
8802
+ candlesHistory,
8803
+ 20
8804
+ );
8805
+ const rangeExpansion = rangeExpansionSeries[rangeExpansionSeries.length - 1] ?? null;
8806
+ const volatilityState = compressionScore == null ? "unknown" : compressionScore <= 0.9 ? "compressed" : compressionScore >= 1.1 ? "expanded" : "normal";
8807
+ const bodyStrength = highLowRange > 0 ? Math.abs(candle.close - candle.open) / highLowRange : null;
8808
+ const closeLocationInRange = highLowRange > 0 ? (candle.close - candle.low) / highLowRange : null;
8809
+ const recentFalseBreakoutDensity = highLevel == null || lowLevel == null || recent20.length < 2 ? null : recent20.reduce((count, item, index) => {
8810
+ if (index === 0) return count;
8811
+ const prevItem = recent20[index - 1];
8812
+ if (!prevItem) return count;
8813
+ if (prevItem.close > highLevel && item.close <= highLevel) {
8814
+ return count + 1;
8815
+ }
8816
+ if (prevItem.close < lowLevel && item.close >= lowLevel) {
8817
+ return count + 1;
8818
+ }
8819
+ return count;
8820
+ }, 0) / (recent20.length - 1);
8821
+ const adxContext = buildAdxContext(adxValue) ?? calculateAdxContext(candlesHistory);
8822
+ const rsiContext = buildRsiContext(rsiValue) ?? calculateRsiContext(closeSeries);
8823
+ const trendFollow = buildTrendFollowContext(
8824
+ candlesHistory.slice(-220),
8825
+ candle.close,
8826
+ atr2
8827
+ );
8828
+ const hl2Series = precomputedMaLayers === void 0 ? candlesHistory.map((item) => (item.high + item.low) / 2) : [];
8829
+ const maLayers = buildMaLayersContext(hl2Series, precomputedMaLayers);
8830
+ const contextMa = buildContextMaContext(
8831
+ closeSeries,
8832
+ candle.close,
8833
+ atr2,
8834
+ precomputedContextMa
8835
+ );
8836
+ const adaptiveChannel = buildAdaptiveChannelContext(
8837
+ candlesHistory,
8838
+ candle.close,
8839
+ atr2,
8840
+ precomputedAdaptiveChannel
8841
+ );
8842
+ return {
8790
8843
  trend: {
8791
8844
  bias: trendBias,
8792
8845
  maStackScore,
@@ -8859,89 +8912,17 @@ var buildBaseContextSnapshot = ({
8859
8912
  upCloseStreak: closeStreaks.up,
8860
8913
  downCloseStreak: closeStreaks.down
8861
8914
  },
8862
- session,
8915
+ session: buildSessionContext(candle.timestamp),
8863
8916
  memory: {
8864
8917
  recentFalseBreakoutDensity
8865
8918
  }
8866
- },
8867
- structure: {
8868
- swing: swingContext,
8869
- zones: {
8870
- support: {
8871
- level: nearestSupport?.level ?? null,
8872
- lower: nearestSupport?.lower ?? null,
8873
- upper: nearestSupport?.upper ?? null,
8874
- touches: nearestSupport?.touches ?? null,
8875
- ageBars: nearestSupport?.ageBars ?? null,
8876
- volumeShare: supportVolumeShare,
8877
- distanceAtr: safeDivide(
8878
- nearestSupport == null ? null : candle.close - nearestSupport.level,
8879
- atr2
8880
- )
8881
- },
8882
- resistance: {
8883
- level: nearestResistance?.level ?? null,
8884
- lower: nearestResistance?.lower ?? null,
8885
- upper: nearestResistance?.upper ?? null,
8886
- touches: nearestResistance?.touches ?? null,
8887
- ageBars: nearestResistance?.ageBars ?? null,
8888
- volumeShare: resistanceVolumeShare,
8889
- distanceAtr: safeDivide(
8890
- nearestResistance == null ? null : nearestResistance.level - candle.close,
8891
- atr2
8892
- )
8893
- },
8894
- active: {
8895
- side: activeZoneType,
8896
- priceInZone
8897
- }
8898
- },
8899
- srZones,
8900
- liquidity: {
8901
- sweepState,
8902
- side: liquiditySide,
8903
- referenceZoneSide,
8904
- sweepHigh20,
8905
- sweepLow20,
8906
- closeBackInsideRange,
8907
- stopRunDirection,
8908
- sweepWickPct
8909
- },
8910
- liquidityZones,
8911
- liquidityTails,
8912
- structureZones: structureZonesContext,
8913
- pivots: pivotContext,
8914
- acceptance: {
8915
- closesAboveHighLevel3,
8916
- closesBelowLowLevel3,
8917
- failedAcceptanceBars,
8918
- acceptanceScore,
8919
- breakoutBodyAtr
8920
- },
8921
- localRange: {
8922
- rangePosition20: calculateRangePosition(
8923
- candle.close,
8924
- recent20Low,
8925
- recent20High
8926
- ),
8927
- distanceToHighLevelAtr,
8928
- distanceToLowLevelAtr,
8929
- breakoutState,
8930
- barsSinceBreakout: breakoutRuntimeState.barsSinceBreakout,
8931
- breakoutRetestQuality
8932
- },
8933
- levels: {
8934
- highTouchCount20,
8935
- lowTouchCount20,
8936
- dominantTouchCount20
8937
- },
8938
- candleQuality: {
8939
- upperWickPct: upperWick,
8940
- lowerWickPct: lowerWick,
8941
- rejectionWickScore
8942
- }
8943
- },
8944
- participation: {
8919
+ };
8920
+ };
8921
+ const buildParticipationSnapshot = () => {
8922
+ let cachedPriceVolumeProfile;
8923
+ let cachedVolumeStructure;
8924
+ let cachedDelta;
8925
+ return {
8945
8926
  volume: {
8946
8927
  volumeRel20,
8947
8928
  turnoverRel20,
@@ -8954,30 +8935,89 @@ var buildBaseContextSnapshot = ({
8954
8935
  ),
8955
8936
  effortVsResult
8956
8937
  },
8957
- priceVolumeProfile,
8958
- volumeStructure,
8959
- delta: deltaContext
8960
- },
8961
- relative: {
8962
- benchmark: {
8963
- maFast: benchmarkMaFast,
8964
- maSlow: benchmarkMaSlow,
8965
- bias: benchmarkTrendBias,
8966
- relativeStrength1h,
8967
- relativeStrength4h,
8968
- relativeStrength1d,
8969
- trendAlignment
8938
+ get priceVolumeProfile() {
8939
+ return cachedPriceVolumeProfile ??= buildPriceVolumeProfileContext(
8940
+ structureWindow,
8941
+ candle.close,
8942
+ atr2
8943
+ );
8970
8944
  },
8971
- execution: {
8972
- venueSpread: baseResult.spread,
8973
- venueSpreadZScore: calculateZScore(
8974
- spreadSeries,
8975
- toNullable(baseResult.spread)
8976
- )
8945
+ get volumeStructure() {
8946
+ return cachedVolumeStructure ??= buildVolumeStructureContext(
8947
+ candlesHistory,
8948
+ candle.close,
8949
+ atr2
8950
+ );
8977
8951
  },
8978
- targetVsBtc,
8979
- ...targetVsEth != null ? { targetVsEth } : {},
8980
- ...referencePsychologicalLevels != null ? { referencePsychologicalLevels } : {}
8952
+ get delta() {
8953
+ return cachedDelta ??= buildDeltaContext(
8954
+ structureWindow
8955
+ );
8956
+ }
8957
+ };
8958
+ };
8959
+ let cachedStructureSnapshot;
8960
+ let cachedRegimeSnapshot;
8961
+ let cachedParticipationSnapshot;
8962
+ let cachedRelativeSnapshot;
8963
+ const snapshot = {
8964
+ candle,
8965
+ prevCandle,
8966
+ raw: {
8967
+ trend: {
8968
+ maFast: baseResult.maFast,
8969
+ maMedium: baseResult.maMedium,
8970
+ maSlow: baseResult.maSlow
8971
+ },
8972
+ volatility: {
8973
+ atr: atr2,
8974
+ atrPct: toNullable(baseResult.atrPct),
8975
+ bbUpper: baseResult.bbUpper,
8976
+ bbMiddle: baseResult.bbMiddle,
8977
+ bbLower: baseResult.bbLower,
8978
+ bbWidthPct
8979
+ },
8980
+ momentum: {
8981
+ macd: toNullable(baseResult.macd),
8982
+ macdSignal: toNullable(baseResult.macdSignal),
8983
+ macdHistogram: toNullable(baseResult.macdHistogram)
8984
+ },
8985
+ volume: {
8986
+ volume: candle.volume,
8987
+ turnover: candle.turnover,
8988
+ obv: baseResult.obv,
8989
+ obvSma: baseResult.smaObv,
8990
+ volume1h: baseResult.volume1h,
8991
+ volume24h: baseResult.volume24h
8992
+ },
8993
+ price: {
8994
+ prevClose: baseResult.prevClose,
8995
+ price1hPct: baseResult.price1hPcnt,
8996
+ price24hPct: baseResult.price24hPcnt,
8997
+ highPrice1h: baseResult.highPrice1h,
8998
+ lowPrice1h: baseResult.lowPrice1h,
8999
+ highPrice24h: baseResult.highPrice24h,
9000
+ lowPrice24h: baseResult.lowPrice24h
9001
+ },
9002
+ levels: {
9003
+ highLevel: baseResult.highLevel,
9004
+ lowLevel: baseResult.lowLevel
9005
+ },
9006
+ crossAsset: {
9007
+ btcCorrelation: baseResult.correlation
9008
+ }
9009
+ },
9010
+ get regime() {
9011
+ return cachedRegimeSnapshot ??= buildRegimeSnapshot();
9012
+ },
9013
+ get structure() {
9014
+ return cachedStructureSnapshot ??= buildStructureSnapshot();
9015
+ },
9016
+ get participation() {
9017
+ return cachedParticipationSnapshot ??= buildParticipationSnapshot();
9018
+ },
9019
+ get relative() {
9020
+ return cachedRelativeSnapshot ??= buildRelativeSnapshot();
8981
9021
  }
8982
9022
  };
8983
9023
  let cachedMtfSnapshot = null;
@@ -11185,6 +11225,22 @@ var createIndicators = (data, btcData = [], options = {}) => {
11185
11225
  const last = value[value.length - 1];
11186
11226
  return typeof last === "number" ? last : void 0;
11187
11227
  },
11228
+ latestNumbers: (key, count) => {
11229
+ const normalizedCount = Number.isFinite(count) ? Math.max(0, Math.trunc(count)) : 0;
11230
+ if (normalizedCount === 0) return [];
11231
+ const buffer = indicatorHistory[key];
11232
+ if (buffer) {
11233
+ const resultSize = Math.min(normalizedCount, buffer.size);
11234
+ const result = new Array(resultSize);
11235
+ const firstOffset = buffer.size - resultSize;
11236
+ for (let index = 0; index < resultSize; index += 1) {
11237
+ result[index] = buffer.values[(buffer.start + firstOffset + index) % ML_BASE_CANDLES_WINDOW];
11238
+ }
11239
+ return result;
11240
+ }
11241
+ const value = getHistoryResult()[key];
11242
+ return Array.isArray(value) ? value.slice(-normalizedCount).filter((item) => typeof item === "number") : [];
11243
+ },
11188
11244
  result: () => {
11189
11245
  return cloneHistorySnapshot(
11190
11246
  getHistoryResult()
@@ -12006,23 +12062,13 @@ var createTrendlineEngine = (initialCandles, options) => {
12006
12062
  };
12007
12063
  const next = (candle) => {
12008
12064
  appendCandle(candle);
12009
- let result = buildResult();
12010
- if (opts.capture && result.length === 0 && rawExtremaPoints.length) {
12011
- rebuildCandidatesLikeBatch();
12012
- result = buildResult();
12013
- }
12014
- return result;
12065
+ return buildResult();
12015
12066
  };
12016
12067
  const nextMany = (candles) => {
12017
12068
  for (const candle of candles) {
12018
12069
  appendCandle(candle);
12019
12070
  }
12020
- let result = buildResult();
12021
- if (opts.capture && result.length === 0 && rawExtremaPoints.length) {
12022
- rebuildCandidatesLikeBatch();
12023
- result = buildResult();
12024
- }
12025
- return result;
12071
+ return buildResult();
12026
12072
  };
12027
12073
  const getLines = () => buildResult();
12028
12074
  const reset = () => {