@tradejs/core 2.0.18 → 2.0.20

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.
@@ -6407,7 +6407,7 @@ var calculateRecentRangeExpansionSeries = (candles, lookback) => calculateRecent
6407
6407
  (index) => calculateRangeExpansionAt(candles, index)
6408
6408
  );
6409
6409
 
6410
- // src/utils/indicatorBaseContext.ts
6410
+ // src/utils/indicatorBaseContextRelative.ts
6411
6411
  var SESSION_WINDOWS = [
6412
6412
  { name: "asia", startMinuteUtc: 0, endMinuteUtc: 8 * 60 },
6413
6413
  { name: "europe", startMinuteUtc: 7 * 60, endMinuteUtc: 16 * 60 },
@@ -6416,28 +6416,24 @@ var SESSION_WINDOWS = [
6416
6416
  var FUNDING_WINDOW_STEP_MINUTES = 8 * 60;
6417
6417
  var FUNDING_WINDOW_NEARBY_MINUTES = 60;
6418
6418
  var SESSION_WINDOW_EDGE_MINUTES = 60;
6419
- var BASE_INTERVAL_MS = 15 * 6e4;
6420
6419
  var PSYCHOLOGICAL_LEVEL_WINDOWS = {
6421
- m15: BASE_INTERVAL_MS,
6420
+ m15: 15 * 6e4,
6422
6421
  h1: 60 * 6e4,
6423
6422
  h4: 4 * 60 * 6e4
6424
6423
  };
6425
- var isInsideSession = (minuteUtc, startMinuteUtc, endMinuteUtc) => startMinuteUtc <= endMinuteUtc ? minuteUtc >= startMinuteUtc && minuteUtc < endMinuteUtc : minuteUtc >= startMinuteUtc || minuteUtc < endMinuteUtc;
6426
6424
  var buildSessionContext = (timestamp) => {
6427
6425
  const date = new Date(timestamp);
6428
- const utcHour = date.getUTCHours();
6429
- const utcMinute = date.getUTCMinutes();
6430
- const minuteUtc = utcHour * 60 + utcMinute;
6426
+ const minuteUtc = date.getUTCHours() * 60 + date.getUTCMinutes();
6431
6427
  const dayOfWeekUtc = date.getUTCDay() || 7;
6432
6428
  const activeSessions = SESSION_WINDOWS.filter(
6433
- (session) => isInsideSession(minuteUtc, session.startMinuteUtc, session.endMinuteUtc)
6434
- ).map((session) => session.name);
6429
+ ({ startMinuteUtc, endMinuteUtc }) => startMinuteUtc <= endMinuteUtc ? minuteUtc >= startMinuteUtc && minuteUtc < endMinuteUtc : minuteUtc >= startMinuteUtc || minuteUtc < endMinuteUtc
6430
+ ).map(({ name }) => name);
6435
6431
  const sessionPhase = activeSessions.includes("us") ? "us" : activeSessions.includes("europe") ? "europe" : activeSessions.includes("asia") ? "asia" : "off_hours";
6436
6432
  const primaryWindow = SESSION_WINDOWS.find(
6437
- (session) => session.name === sessionPhase
6433
+ ({ name }) => name === sessionPhase
6438
6434
  );
6439
- const minutesFromSessionOpen = primaryWindow != null ? minuteUtc - primaryWindow.startMinuteUtc : null;
6440
- const minutesToSessionClose = primaryWindow != null ? primaryWindow.endMinuteUtc - minuteUtc : null;
6435
+ const minutesFromSessionOpen = primaryWindow ? minuteUtc - primaryWindow.startMinuteUtc : null;
6436
+ const minutesToSessionClose = primaryWindow ? primaryWindow.endMinuteUtc - minuteUtc : null;
6441
6437
  const sessionWindowPhase = primaryWindow == null || minutesFromSessionOpen == null ? "off_hours" : minutesFromSessionOpen < SESSION_WINDOW_EDGE_MINUTES ? "opening" : minutesToSessionClose != null && minutesToSessionClose <= SESSION_WINDOW_EDGE_MINUTES ? "closing" : "active";
6442
6438
  const minutesToFundingWindow = (FUNDING_WINDOW_STEP_MINUTES - minuteUtc % FUNDING_WINDOW_STEP_MINUTES) % FUNDING_WINDOW_STEP_MINUTES;
6443
6439
  return {
@@ -6453,7 +6449,7 @@ var buildSessionContext = (timestamp) => {
6453
6449
  isWeekendUtc: dayOfWeekUtc >= 6
6454
6450
  };
6455
6451
  };
6456
- var buildUnavailablePsychologicalLevelWindow = () => ({
6452
+ var unavailablePsychologicalLevelWindow = () => ({
6457
6453
  crossed: null,
6458
6454
  direction: "unknown",
6459
6455
  level: null,
@@ -6462,7 +6458,7 @@ var buildUnavailablePsychologicalLevelWindow = () => ({
6462
6458
  });
6463
6459
  var buildPsychologicalLevelWindow = (startPrice, endPrice, stepUsd) => {
6464
6460
  if (!Number.isFinite(startPrice) || !Number.isFinite(endPrice) || !Number.isFinite(stepUsd) || startPrice <= 0 || endPrice <= 0 || stepUsd <= 0) {
6465
- return buildUnavailablePsychologicalLevelWindow();
6461
+ return unavailablePsychologicalLevelWindow();
6466
6462
  }
6467
6463
  if (endPrice === startPrice) {
6468
6464
  return {
@@ -6486,21 +6482,17 @@ var buildPsychologicalLevelWindow = (startPrice, endPrice, stepUsd) => {
6486
6482
  distanceBeyondLevelBps: null
6487
6483
  };
6488
6484
  }
6489
- const levelsCrossed = Math.round(Math.abs(lastCrossedLevel - firstCrossedLevel) / stepUsd) + 1;
6490
- const distanceBeyondLevelBps = Math.abs(endPrice - lastCrossedLevel) / lastCrossedLevel * 1e4;
6491
6485
  return {
6492
6486
  crossed: true,
6493
6487
  direction: movingUp ? "up" : "down",
6494
6488
  level: lastCrossedLevel,
6495
- levelsCrossed,
6496
- distanceBeyondLevelBps
6489
+ levelsCrossed: Math.round(Math.abs(lastCrossedLevel - firstCrossedLevel) / stepUsd) + 1,
6490
+ distanceBeyondLevelBps: Math.abs(endPrice - lastCrossedLevel) / lastCrossedLevel * 1e4
6497
6491
  };
6498
6492
  };
6499
6493
  var buildPsychologicalLevelAssetContext = (candles, stepUsd) => {
6500
6494
  const endCandle = candles[candles.length - 1];
6501
- if (!endCandle) {
6502
- return null;
6503
- }
6495
+ if (!endCandle) return null;
6504
6496
  const candlesByTimestamp = new Map(
6505
6497
  candles.map((item) => [item.timestamp, item])
6506
6498
  );
@@ -6515,104 +6507,70 @@ var buildPsychologicalLevelAssetContext = (candles, stepUsd) => {
6515
6507
  startCandle.close,
6516
6508
  endCandle.close,
6517
6509
  stepUsd
6518
- ) : buildUnavailablePsychologicalLevelWindow()
6510
+ ) : unavailablePsychologicalLevelWindow()
6519
6511
  ];
6520
6512
  })
6521
6513
  );
6522
- return {
6523
- source: "aligned_15m_ohlcv",
6524
- stepUsd,
6525
- windows
6526
- };
6514
+ return { source: "aligned_15m_ohlcv", stepUsd, windows };
6527
6515
  };
6528
- var STRUCTURE_LOOKBACK = 80;
6529
- var BASE_CONTEXT_CANDLE_WINDOW = 256;
6530
- var PIVOT_LEFT_RIGHT = 2;
6531
- var ZONE_ATR_FACTOR = 0.5;
6532
- var PROFILE_BIN_COUNT = 24;
6533
- var SR_ZONE_PIVOT_PERIOD = 9;
6534
- var SR_ZONE_MIN_STRENGTH = 2;
6535
- var SR_ZONE_MAX_PIVOTS = 15;
6536
- var SR_ZONE_CHANNEL_WIDTH_PCT = 8;
6537
- var SR_ZONE_MAX_LEVELS = 6;
6538
- var VOLUME_STRUCTURE_CALC_BARS = 180;
6539
- var VOLUME_STRUCTURE_ROW_COUNT = 20;
6540
- var LIQUIDITY_ZONE_LOOKBACK = 15;
6541
- var LIQUIDITY_ZONE_MAX_AGE = 120;
6542
- var LIQUIDITY_TAIL_ATR_LENGTH = 14;
6543
- var LIQUIDITY_TAIL_ATR_MULT = 0.8;
6544
- var LIQUIDITY_TAIL_MIN_WICK_RATIO = 1.3;
6545
- var LIQUIDITY_TAIL_WICK_DOMINANCE = 1.2;
6546
- var LIQUIDITY_TAIL_MIN_GAP = 5;
6547
- var LIQUIDITY_TAIL_MAX_AGE = 120;
6548
- var TREND_FOLLOW_PIVOT_LENGTH = 10;
6549
- var TREND_FOLLOW_ATR_LENGTH = 14;
6550
- var TREND_FOLLOW_ATR_MULT = 4;
6551
- var ADAPTIVE_CHANNEL_REGRESSION_BARS = 7;
6552
- var ADAPTIVE_CHANNEL_ENVELOPE_BARS = 2;
6553
- var ADAPTIVE_CHANNEL_ATR_STRETCH = 2;
6554
- var ADAPTIVE_CHANNEL_VOLATILITY_LOOKBACK = 100;
6555
- var ADAPTIVE_CHANNEL_CALC_LOOKBACK = 220;
6556
- var STRUCTURE_ZONES_ZONE_WIDTH_ATR = 0.5;
6557
- var STRUCTURE_ZONES_ACCEPT_BARS = 2;
6558
- var calculateReturnPctFromCandles = (candles) => {
6516
+ var returnPct = (candles) => {
6559
6517
  if (candles.length < 2) return null;
6560
- const previous = candles[candles.length - 2];
6561
- const current = candles[candles.length - 1];
6562
- return percentChange(current.close, previous.close);
6563
- };
6564
- var calculateRatioReturnPct = (coinCandles, btcCandles) => {
6565
- if (coinCandles.length < 2 || btcCandles.length < 2) return null;
6566
- const currentCoin = coinCandles[coinCandles.length - 1];
6567
- const previousCoin = coinCandles[coinCandles.length - 2];
6568
- const currentBtc = btcCandles[btcCandles.length - 1];
6569
- const previousBtc = btcCandles[btcCandles.length - 2];
6570
- if (currentBtc.close <= 0 || previousBtc.close <= 0) return null;
6571
6518
  return percentChange(
6572
- currentCoin.close / currentBtc.close,
6573
- previousCoin.close / previousBtc.close
6519
+ candles[candles.length - 1].close,
6520
+ candles[candles.length - 2].close
6574
6521
  );
6575
6522
  };
6576
- var alignRecentCandlesByTimestamp = (coinCandles, btcCandles) => {
6577
- const alignedCoinCandles = [];
6578
- const alignedBtcCandles = [];
6579
- let coinIndex = Math.max(0, coinCandles.length - 80);
6580
- let btcIndex = Math.max(0, btcCandles.length - 80);
6581
- while (coinIndex < coinCandles.length && btcIndex < btcCandles.length) {
6582
- const coinTimestamp = coinCandles[coinIndex].timestamp;
6583
- const btcTimestamp = btcCandles[btcIndex].timestamp;
6584
- if (coinTimestamp === btcTimestamp) {
6585
- alignedCoinCandles.push(coinCandles[coinIndex]);
6586
- alignedBtcCandles.push(btcCandles[btcIndex]);
6587
- coinIndex += 1;
6588
- btcIndex += 1;
6589
- } else if (coinTimestamp < btcTimestamp) {
6590
- coinIndex += 1;
6523
+ var ratioReturnPct = (target, reference) => {
6524
+ if (target.length < 2 || reference.length < 2) return null;
6525
+ const targetCurrent = target[target.length - 1];
6526
+ const targetPrevious = target[target.length - 2];
6527
+ const referenceCurrent = reference[reference.length - 1];
6528
+ const referencePrevious = reference[reference.length - 2];
6529
+ if (referenceCurrent.close <= 0 || referencePrevious.close <= 0) return null;
6530
+ return percentChange(
6531
+ targetCurrent.close / referenceCurrent.close,
6532
+ targetPrevious.close / referencePrevious.close
6533
+ );
6534
+ };
6535
+ var alignRecentCandles = (target, reference) => {
6536
+ const alignedTarget = [];
6537
+ const alignedReference = [];
6538
+ let targetIndex = Math.max(0, target.length - 80);
6539
+ let referenceIndex = Math.max(0, reference.length - 80);
6540
+ while (targetIndex < target.length && referenceIndex < reference.length) {
6541
+ const targetTimestamp = target[targetIndex].timestamp;
6542
+ const referenceTimestamp = reference[referenceIndex].timestamp;
6543
+ if (targetTimestamp === referenceTimestamp) {
6544
+ alignedTarget.push(target[targetIndex]);
6545
+ alignedReference.push(reference[referenceIndex]);
6546
+ targetIndex += 1;
6547
+ referenceIndex += 1;
6548
+ } else if (targetTimestamp < referenceTimestamp) {
6549
+ targetIndex += 1;
6591
6550
  } else {
6592
- btcIndex += 1;
6551
+ referenceIndex += 1;
6593
6552
  }
6594
6553
  }
6595
- return { alignedCoinCandles, alignedBtcCandles };
6554
+ return { alignedTarget, alignedReference };
6596
6555
  };
6597
- var buildReturnPctSeries = (candles, limit) => {
6556
+ var returnSeries = (candles, limit) => {
6598
6557
  const returns = [];
6599
- const startIndex = Math.max(1, candles.length - limit);
6600
- for (let index = startIndex; index < candles.length; index += 1) {
6558
+ for (let index = Math.max(1, candles.length - limit); index < candles.length; index += 1) {
6601
6559
  const previous = candles[index - 1];
6602
6560
  const current = candles[index];
6603
6561
  if (previous.close <= 0) continue;
6604
- const ret = percentChange(current.close, previous.close);
6605
- if (ret != null && Number.isFinite(ret)) returns.push(ret);
6562
+ const value = percentChange(current.close, previous.close);
6563
+ if (value != null && Number.isFinite(value)) returns.push(value);
6606
6564
  }
6607
6565
  return returns;
6608
6566
  };
6609
- var calculateBetaToReference = (targetReturns, referenceReturns) => {
6567
+ var beta = (targetReturns, referenceReturns) => {
6610
6568
  const length = Math.min(targetReturns.length, referenceReturns.length);
6611
6569
  if (length < 2) return null;
6612
6570
  const target = targetReturns.slice(-length);
6613
6571
  const reference = referenceReturns.slice(-length);
6614
- const referenceMean = reference.reduce((sum2, value) => sum2 + value, 0) / reference.length;
6615
- const targetMean = target.reduce((sum2, value) => sum2 + value, 0) / target.length;
6572
+ const referenceMean = reference.reduce((sum2, value) => sum2 + value, 0) / length;
6573
+ const targetMean = target.reduce((sum2, value) => sum2 + value, 0) / length;
6616
6574
  let covariance = 0;
6617
6575
  let referenceVariance = 0;
6618
6576
  for (let index = 0; index < length; index += 1) {
@@ -6629,16 +6587,6 @@ var classifyRatioTrend = (ratioReturn24h, ratioReturn4h) => {
6629
6587
  if (value < -0.15) return "down";
6630
6588
  return "flat";
6631
6589
  };
6632
- var hasDistinctReferenceCandles = (coinCandles, referenceCandles) => {
6633
- const coinLast = coinCandles[coinCandles.length - 1];
6634
- const referenceLast = referenceCandles[referenceCandles.length - 1];
6635
- const coinPrev = coinCandles[coinCandles.length - 2];
6636
- const referencePrev = referenceCandles[referenceCandles.length - 2];
6637
- if (!coinLast || !referenceLast || !coinPrev || !referencePrev) {
6638
- return false;
6639
- }
6640
- return coinLast.timestamp !== referenceLast.timestamp || coinLast.close !== referenceLast.close || coinPrev.timestamp !== referencePrev.timestamp || coinPrev.close !== referencePrev.close;
6641
- };
6642
6590
  var buildTargetVsBtcContext = ({
6643
6591
  coin1h,
6644
6592
  btc1h,
@@ -6649,19 +6597,21 @@ var buildTargetVsBtcContext = ({
6649
6597
  coinCandles,
6650
6598
  btcCandles
6651
6599
  }) => {
6652
- const ratioReturn1h = calculateRatioReturnPct(coin1h, btc1h);
6653
- const ratioReturn4h = calculateRatioReturnPct(coin4h, btc4h);
6654
- const ratioReturn24h = calculateRatioReturnPct(coin1d, btc1d);
6655
- const coinReturn1h = calculateReturnPctFromCandles(coin1h);
6656
- const coinReturn4h = calculateReturnPctFromCandles(coin4h);
6657
- const coinReturn24h = calculateReturnPctFromCandles(coin1d);
6658
- const btcReturn1h = calculateReturnPctFromCandles(btc1h);
6659
- const btcReturn4h = calculateReturnPctFromCandles(btc4h);
6660
- const btcReturn24h = calculateReturnPctFromCandles(btc1d);
6661
- const { alignedCoinCandles, alignedBtcCandles } = alignRecentCandlesByTimestamp(coinCandles, btcCandles);
6662
- const coinReturns20 = buildReturnPctSeries(alignedCoinCandles, 21).slice(-20);
6663
- const btcReturns20 = buildReturnPctSeries(alignedBtcCandles, 21).slice(-20);
6664
- const correlationToBtc20 = coinReturns20.length === btcReturns20.length && coinReturns20.length >= 2 ? calculatePearsonCorrelation(coinReturns20, btcReturns20) : null;
6600
+ const ratioReturn1h = ratioReturnPct(coin1h, btc1h);
6601
+ const ratioReturn4h = ratioReturnPct(coin4h, btc4h);
6602
+ const ratioReturn24h = ratioReturnPct(coin1d, btc1d);
6603
+ const coinReturn1h = returnPct(coin1h);
6604
+ const coinReturn4h = returnPct(coin4h);
6605
+ const coinReturn24h = returnPct(coin1d);
6606
+ const btcReturn1h = returnPct(btc1h);
6607
+ const btcReturn4h = returnPct(btc4h);
6608
+ const btcReturn24h = returnPct(btc1d);
6609
+ const { alignedTarget, alignedReference } = alignRecentCandles(
6610
+ coinCandles,
6611
+ btcCandles
6612
+ );
6613
+ const coinReturns20 = returnSeries(alignedTarget, 21).slice(-20);
6614
+ const btcReturns20 = returnSeries(alignedReference, 21).slice(-20);
6665
6615
  return {
6666
6616
  source: "aligned_ohlcv",
6667
6617
  ratioReturn1h,
@@ -6670,11 +6620,21 @@ var buildTargetVsBtcContext = ({
6670
6620
  alphaVsBtc1h: coinReturn1h == null || btcReturn1h == null ? null : coinReturn1h - btcReturn1h,
6671
6621
  alphaVsBtc4h: coinReturn4h == null || btcReturn4h == null ? null : coinReturn4h - btcReturn4h,
6672
6622
  alphaVsBtc24h: coinReturn24h == null || btcReturn24h == null ? null : coinReturn24h - btcReturn24h,
6673
- betaToBtc20: calculateBetaToReference(coinReturns20, btcReturns20),
6674
- correlationToBtc20,
6623
+ betaToBtc20: beta(coinReturns20, btcReturns20),
6624
+ correlationToBtc20: coinReturns20.length === btcReturns20.length && coinReturns20.length >= 2 ? calculatePearsonCorrelation(coinReturns20, btcReturns20) : null,
6675
6625
  ratioTrend: classifyRatioTrend(ratioReturn24h, ratioReturn4h)
6676
6626
  };
6677
6627
  };
6628
+ var hasDistinctReferenceCandles = (target, reference) => {
6629
+ const targetLast = target[target.length - 1];
6630
+ const referenceLast = reference[reference.length - 1];
6631
+ const targetPrevious = target[target.length - 2];
6632
+ const referencePrevious = reference[reference.length - 2];
6633
+ if (!targetLast || !referenceLast || !targetPrevious || !referencePrevious) {
6634
+ return false;
6635
+ }
6636
+ return targetLast.timestamp !== referenceLast.timestamp || targetLast.close !== referenceLast.close || targetPrevious.timestamp !== referencePrevious.timestamp || targetPrevious.close !== referencePrevious.close;
6637
+ };
6678
6638
  var buildTargetVsEthContext = ({
6679
6639
  coin1h,
6680
6640
  eth1h,
@@ -6685,9 +6645,7 @@ var buildTargetVsEthContext = ({
6685
6645
  coinCandles,
6686
6646
  ethCandles
6687
6647
  }) => {
6688
- if (!hasDistinctReferenceCandles(coinCandles, ethCandles)) {
6689
- return null;
6690
- }
6648
+ if (!hasDistinctReferenceCandles(coinCandles, ethCandles)) return null;
6691
6649
  const context = buildTargetVsBtcContext({
6692
6650
  coin1h,
6693
6651
  btc1h: eth1h,
@@ -6711,6 +6669,38 @@ var buildTargetVsEthContext = ({
6711
6669
  ratioTrend: context.ratioTrend
6712
6670
  };
6713
6671
  };
6672
+
6673
+ // src/utils/indicatorBaseContext.ts
6674
+ var STRUCTURE_LOOKBACK = 80;
6675
+ var BASE_CONTEXT_CANDLE_WINDOW = 256;
6676
+ var PIVOT_LEFT_RIGHT = 2;
6677
+ var ZONE_ATR_FACTOR = 0.5;
6678
+ var PROFILE_BIN_COUNT = 24;
6679
+ var SR_ZONE_PIVOT_PERIOD = 9;
6680
+ var SR_ZONE_MIN_STRENGTH = 2;
6681
+ var SR_ZONE_MAX_PIVOTS = 15;
6682
+ var SR_ZONE_CHANNEL_WIDTH_PCT = 8;
6683
+ var SR_ZONE_MAX_LEVELS = 6;
6684
+ var VOLUME_STRUCTURE_CALC_BARS = 180;
6685
+ var VOLUME_STRUCTURE_ROW_COUNT = 20;
6686
+ var LIQUIDITY_ZONE_LOOKBACK = 15;
6687
+ var LIQUIDITY_ZONE_MAX_AGE = 120;
6688
+ var LIQUIDITY_TAIL_ATR_LENGTH = 14;
6689
+ var LIQUIDITY_TAIL_ATR_MULT = 0.8;
6690
+ var LIQUIDITY_TAIL_MIN_WICK_RATIO = 1.3;
6691
+ var LIQUIDITY_TAIL_WICK_DOMINANCE = 1.2;
6692
+ var LIQUIDITY_TAIL_MIN_GAP = 5;
6693
+ var LIQUIDITY_TAIL_MAX_AGE = 120;
6694
+ var TREND_FOLLOW_PIVOT_LENGTH = 10;
6695
+ var TREND_FOLLOW_ATR_LENGTH = 14;
6696
+ var TREND_FOLLOW_ATR_MULT = 4;
6697
+ var ADAPTIVE_CHANNEL_REGRESSION_BARS = 7;
6698
+ var ADAPTIVE_CHANNEL_ENVELOPE_BARS = 2;
6699
+ var ADAPTIVE_CHANNEL_ATR_STRETCH = 2;
6700
+ var ADAPTIVE_CHANNEL_VOLATILITY_LOOKBACK = 100;
6701
+ var ADAPTIVE_CHANNEL_CALC_LOOKBACK = 220;
6702
+ var STRUCTURE_ZONES_ZONE_WIDTH_ATR = 0.5;
6703
+ var STRUCTURE_ZONES_ACCEPT_BARS = 2;
6714
6704
  var BASE_CONTEXT_MA_LAYER_PERIODS = [
6715
6705
  [5, 12],
6716
6706
  [9, 13],
@@ -10701,13 +10691,12 @@ var buildIndicatorSeriesByTimeframes = (candles, periods, sourcePrefix = "") =>
10701
10691
 
10702
10692
  // src/utils/timestamp.ts
10703
10693
  var import_date_fns = require("date-fns");
10704
- var import_date_fns2 = require("date-fns");
10705
10694
  var RUNTIME_STORAGE_DAY_OFFSET_MS = 6 * 60 * 60 * 1e3;
10706
10695
  var getTimestamp = (days = 0) => {
10707
10696
  if (days > 0) {
10708
- return (0, import_date_fns2.getUnixTime)((0, import_date_fns2.subDays)(/* @__PURE__ */ new Date(), days)) * 1e3;
10697
+ return (0, import_date_fns.getUnixTime)((0, import_date_fns.subDays)(/* @__PURE__ */ new Date(), days)) * 1e3;
10709
10698
  }
10710
- return (0, import_date_fns2.getUnixTime)(/* @__PURE__ */ new Date()) * 1e3;
10699
+ return (0, import_date_fns.getUnixTime)(/* @__PURE__ */ new Date()) * 1e3;
10711
10700
  };
10712
10701
 
10713
10702
  // src/utils/strategyHelpers/indicators.ts
@@ -11076,23 +11065,46 @@ var getDirectionalTpSlPrices = ({
11076
11065
  };
11077
11066
 
11078
11067
  // src/utils/strategyHelpers/state.ts
11079
- var createLastTradeController = ({
11068
+ var resolveLastTradeControllerParams = ({
11080
11069
  env,
11081
11070
  enabled = env ? env === "BACKTEST" : true,
11082
11071
  cooldownMs = 864e5
11072
+ }) => ({
11073
+ enabled,
11074
+ cooldownMs
11075
+ });
11076
+ var createStateBackedLastTradeController = ({
11077
+ state,
11078
+ enabled,
11079
+ cooldownMs
11083
11080
  }) => {
11084
- let lastTradeTimestamp = null;
11081
+ const getLastTradeTimestamp = () => state.get().lastTradeTimestamp;
11085
11082
  return {
11086
- isInCooldown: (timestamp) => Boolean(
11087
- enabled && lastTradeTimestamp != null && timestamp <= lastTradeTimestamp + cooldownMs
11088
- ),
11083
+ isInCooldown: (timestamp) => {
11084
+ const lastTradeTimestamp = getLastTradeTimestamp();
11085
+ return Boolean(
11086
+ enabled && lastTradeTimestamp != null && timestamp <= lastTradeTimestamp + cooldownMs
11087
+ );
11088
+ },
11089
11089
  markTrade: (timestamp) => {
11090
11090
  if (!enabled) return;
11091
- lastTradeTimestamp = timestamp;
11091
+ state.update((current) => {
11092
+ current.lastTradeTimestamp = timestamp;
11093
+ });
11092
11094
  },
11093
- getLastTradeTimestamp: () => lastTradeTimestamp
11095
+ getLastTradeTimestamp
11094
11096
  };
11095
11097
  };
11098
+ var createLastTradeController = (params) => {
11099
+ const state = { lastTradeTimestamp: null };
11100
+ return createStateBackedLastTradeController({
11101
+ ...resolveLastTradeControllerParams(params),
11102
+ state: {
11103
+ get: () => state,
11104
+ update: (fn) => fn(state)
11105
+ }
11106
+ });
11107
+ };
11096
11108
  var stableStringify = (value, seen = /* @__PURE__ */ new WeakSet()) => {
11097
11109
  if (value == null) {
11098
11110
  return String(value);
@@ -11234,11 +11246,14 @@ var createStrategyStateControllerFactory = ({
11234
11246
  };
11235
11247
 
11236
11248
  // src/utils/uuid.ts
11237
- var import_node_crypto = require("crypto");
11238
- var uuid = (len = 12) => {
11239
- const uuid2 = (0, import_node_crypto.randomUUID)();
11240
- return uuid2.slice(-len);
11249
+ var randomUuid = () => {
11250
+ if (!globalThis.crypto?.randomUUID) {
11251
+ throw new Error("crypto.randomUUID is not available in this runtime");
11252
+ }
11253
+ return globalThis.crypto.randomUUID();
11241
11254
  };
11255
+ var createIdGenerator = (generateUuid = randomUuid) => (length = 12) => generateUuid().slice(-length);
11256
+ var uuid = createIdGenerator();
11242
11257
 
11243
11258
  // src/utils/strategyHelpers/signalBuilders.ts
11244
11259
  var COMPACT_MTF_CANDLE_LIMIT = 3;
@@ -11922,13 +11937,14 @@ var buildEntrySignalDecision = ({
11922
11937
  additionalIndicators,
11923
11938
  signalId,
11924
11939
  orderPlan,
11925
- runtime
11940
+ runtime,
11941
+ generateId = uuid
11926
11942
  }) => ({
11927
11943
  kind: "entry",
11928
11944
  code,
11929
11945
  entryContext,
11930
11946
  signal: buildStrategySignal({
11931
- signalId: signalId ?? uuid(),
11947
+ signalId: signalId ?? generateId(),
11932
11948
  strategy: entryContext.strategy,
11933
11949
  symbol: entryContext.symbol,
11934
11950
  interval: entryContext.interval,
@@ -11943,7 +11959,7 @@ var buildEntrySignalDecision = ({
11943
11959
  orderPlan,
11944
11960
  runtime
11945
11961
  });
11946
- var isFiniteNumber3 = (value) => typeof value === "number" && Number.isFinite(value);
11962
+ var isFiniteNumber2 = (value) => typeof value === "number" && Number.isFinite(value);
11947
11963
  var toDefaultEntryCode = (strategy, direction) => `${strategy.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_${direction}_ENTRY`;
11948
11964
  var toDefaultExitCode = (strategy, direction) => `${strategy.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_${direction}_EXIT`;
11949
11965
  var toDefaultProtectCode = (strategy, direction) => `${strategy.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_${direction}_PROTECT`;
@@ -11954,7 +11970,7 @@ var resolveTakeProfitPrice = ({
11954
11970
  if (!Array.isArray(takeProfits) || takeProfits.length === 0) {
11955
11971
  throw new Error("strategyApi.entry requires at least one takeProfit");
11956
11972
  }
11957
- const prices = takeProfits.map((tp) => tp?.price).filter((price) => isFiniteNumber3(price));
11973
+ const prices = takeProfits.map((tp) => tp?.price).filter((price) => isFiniteNumber2(price));
11958
11974
  if (prices.length === 0) {
11959
11975
  throw new Error("strategyApi.entry requires finite takeProfit prices");
11960
11976
  }
@@ -11971,6 +11987,7 @@ var createStrategyAPI = ({
11971
11987
  isConfigFromBacktest,
11972
11988
  sharedReplayKey,
11973
11989
  getSharedReplayState,
11990
+ generateId,
11974
11991
  loadDecisionBaseContext
11975
11992
  }) => {
11976
11993
  const isBacktestEnv = env === "BACKTEST";
@@ -12028,6 +12045,7 @@ var createStrategyAPI = ({
12028
12045
  sharedReplayKey,
12029
12046
  getSharedReplayState
12030
12047
  });
12048
+ let lastTradeControllerIndex = 0;
12031
12049
  const getBaseContextFromIndicators = (indicators) => isRecordLike(indicators) && isRecordLike(indicators.baseContext) ? indicators.baseContext : void 0;
12032
12050
  const getCurrentIndicatorsContext = () => {
12033
12051
  ensureBarCache();
@@ -12088,7 +12106,7 @@ var createStrategyAPI = ({
12088
12106
  };
12089
12107
  const resolveDecisionPriceContext = () => {
12090
12108
  const candle = cachedData[cachedData.length - 1];
12091
- if (!candle || !isFiniteNumber3(candle.timestamp) || !isFiniteNumber3(candle.close)) {
12109
+ if (!candle || !isFiniteNumber2(candle.timestamp) || !isFiniteNumber2(candle.close)) {
12092
12110
  throw new Error(
12093
12111
  "strategyApi.getDecisionPriceContext requires a current closed candle"
12094
12112
  );
@@ -12120,7 +12138,7 @@ var createStrategyAPI = ({
12120
12138
  direction,
12121
12139
  takeProfits: orderPlan.takeProfits
12122
12140
  });
12123
- if (!isFiniteNumber3(stopLossPrice)) {
12141
+ if (!isFiniteNumber2(stopLossPrice)) {
12124
12142
  throw new Error(
12125
12143
  "strategyApi.entry requires finite orderPlan.stopLossPrice"
12126
12144
  );
@@ -12153,7 +12171,8 @@ var createStrategyAPI = ({
12153
12171
  additionalIndicators,
12154
12172
  signalId,
12155
12173
  orderPlan,
12156
- runtime
12174
+ runtime,
12175
+ generateId
12157
12176
  });
12158
12177
  },
12159
12178
  exit: async ({ code, direction }) => {
@@ -12179,10 +12198,26 @@ var createStrategyAPI = ({
12179
12198
  getDecisionPriceContext,
12180
12199
  getCurrentPosition,
12181
12200
  getDirectionalTpSlPrices: (params) => getDirectionalTpSlPrices(params),
12182
- createLastTradeController: (params) => createLastTradeController({
12183
- env,
12184
- ...params
12185
- }),
12201
+ createLastTradeController: (params) => {
12202
+ const resolvedParams = resolveLastTradeControllerParams({
12203
+ env,
12204
+ ...params
12205
+ });
12206
+ const controllerIndex = lastTradeControllerIndex;
12207
+ lastTradeControllerIndex += 1;
12208
+ const state = createStateController(
12209
+ `__tradejs:last-trade:${controllerIndex}`,
12210
+ () => ({ lastTradeTimestamp: null }),
12211
+ {
12212
+ configKey: JSON.stringify(resolvedParams),
12213
+ sharedReplay: env === "CRON"
12214
+ }
12215
+ );
12216
+ return createStateBackedLastTradeController({
12217
+ ...resolvedParams,
12218
+ state
12219
+ });
12220
+ },
12186
12221
  createStateController
12187
12222
  };
12188
12223
  };