@tradejs/core 1.0.5 → 1.0.8

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.
@@ -33,6 +33,7 @@ __export(indicators_exports, {
33
33
  alignSortedCandlesByTimestamp: () => alignSortedCandlesByTimestamp,
34
34
  alignSpreadRows: () => alignSpreadRows,
35
35
  applyIndicatorsToHistory: () => applyIndicatorsToHistory,
36
+ buildDerivativesContext: () => buildDerivativesContext,
36
37
  buildMlCandleIndicators: () => buildMlCandleIndicators,
37
38
  buildMlTimeframeIndicators: () => buildMlTimeframeIndicators,
38
39
  buildReturnsFromCandles: () => buildReturnsFromCandles,
@@ -264,6 +265,235 @@ var coinalyzePointsToRows = (points, interval, source) => points.map((point) =>
264
265
  source
265
266
  }));
266
267
 
268
+ // src/utils/derivativesContext.ts
269
+ var HOUR_MS = 60 * 60 * 1e3;
270
+ var DEFAULT_STALE_AFTER_MS = {
271
+ "15m": 45 * 60 * 1e3,
272
+ "1h": 3 * HOUR_MS
273
+ };
274
+ var DERIVATIVES_INTERVALS = ["15m", "1h"];
275
+ var toFiniteNumberOrNull = (value) => {
276
+ if (typeof value === "number" && Number.isFinite(value)) return value;
277
+ if (typeof value === "string" && value.trim()) {
278
+ const parsed = Number(value);
279
+ return Number.isFinite(parsed) ? parsed : null;
280
+ }
281
+ return null;
282
+ };
283
+ var toTimestampMs = (value) => {
284
+ if (value instanceof Date) {
285
+ const time = value.getTime();
286
+ return Number.isFinite(time) ? time : null;
287
+ }
288
+ const num = toFiniteNumberOrNull(value);
289
+ if (num == null) return null;
290
+ return num > 1e10 ? Math.floor(num) : Math.floor(num * 1e3);
291
+ };
292
+ var roundNullable = (value, digits = 6) => {
293
+ if (value == null || !Number.isFinite(value)) return null;
294
+ const multiplier = 10 ** digits;
295
+ return Math.round(value * multiplier) / multiplier;
296
+ };
297
+ var pctChange = (current, previous) => {
298
+ if (current == null || previous == null || !Number.isFinite(current) || !Number.isFinite(previous) || previous === 0) {
299
+ return null;
300
+ }
301
+ return (current - previous) / Math.abs(previous) * 100;
302
+ };
303
+ var normalizeRows = (rows, timestamp) => (rows ?? []).map((row) => ({
304
+ ...row,
305
+ tsMs: toTimestampMs(row.ts),
306
+ openInterest: toFiniteNumberOrNull(row.openInterest),
307
+ fundingRate: toFiniteNumberOrNull(row.fundingRate),
308
+ liqLong: toFiniteNumberOrNull(row.liqLong),
309
+ liqShort: toFiniteNumberOrNull(row.liqShort),
310
+ liqTotal: toFiniteNumberOrNull(row.liqTotal)
311
+ })).filter((row) => {
312
+ return row.tsMs != null && row.tsMs <= timestamp;
313
+ }).sort((a, b) => a.tsMs - b.tsMs);
314
+ var findRowAtOrBefore = (rows, targetTs) => {
315
+ for (let i = rows.length - 1; i >= 0; i -= 1) {
316
+ if (rows[i].tsMs <= targetTs) {
317
+ return rows[i];
318
+ }
319
+ }
320
+ return null;
321
+ };
322
+ var calculateZScore = (values, current) => {
323
+ const finite = values.filter(
324
+ (value) => typeof value === "number" && Number.isFinite(value)
325
+ );
326
+ if (current == null || finite.length < 3) return null;
327
+ const mean = finite.reduce((sum, value) => sum + value, 0) / finite.length;
328
+ const variance = finite.reduce((sum, value) => sum + (value - mean) ** 2, 0) / finite.length;
329
+ const std = Math.sqrt(variance);
330
+ if (!Number.isFinite(std) || std === 0) return 0;
331
+ return (current - mean) / std;
332
+ };
333
+ var calculateAverage = (values) => {
334
+ const finite = values.filter(
335
+ (value) => typeof value === "number" && Number.isFinite(value)
336
+ );
337
+ if (!finite.length) return null;
338
+ return finite.reduce((sum, value) => sum + value, 0) / finite.length;
339
+ };
340
+ var buildIntervalContext = (params) => {
341
+ const { interval, rows, timestamp, staleAfterMs } = params;
342
+ const normalizedRows = normalizeRows(rows, timestamp);
343
+ const latest = normalizedRows[normalizedRows.length - 1];
344
+ if (!latest) return null;
345
+ const openInterest = latest.openInterest;
346
+ const row1h = findRowAtOrBefore(normalizedRows, latest.tsMs - HOUR_MS);
347
+ const row4h = findRowAtOrBefore(normalizedRows, latest.tsMs - 4 * HOUR_MS);
348
+ const row24h = findRowAtOrBefore(normalizedRows, latest.tsMs - 24 * HOUR_MS);
349
+ const liqLong = latest.liqLong;
350
+ const liqShort = latest.liqShort;
351
+ const liqTotal = latest.liqTotal ?? (liqLong ?? 0) + (liqShort ?? 0);
352
+ const previousLiquidations = normalizedRows.slice(0, -1).map((row) => row.liqTotal ?? (row.liqLong ?? 0) + (row.liqShort ?? 0));
353
+ const avgPreviousLiquidations = calculateAverage(previousLiquidations);
354
+ const liqSpikeRatio = liqTotal != null && avgPreviousLiquidations != null && avgPreviousLiquidations > 0 ? liqTotal / avgPreviousLiquidations : null;
355
+ const liqImbalance = liqTotal != null && liqTotal > 0 ? ((liqShort ?? 0) - (liqLong ?? 0)) / liqTotal : null;
356
+ return {
357
+ interval,
358
+ asOfTs: latest.tsMs,
359
+ stale: timestamp - latest.tsMs > staleAfterMs,
360
+ points: normalizedRows.length,
361
+ openInterest: roundNullable(openInterest),
362
+ oiChangePct1h: roundNullable(
363
+ pctChange(openInterest, row1h?.openInterest ?? null),
364
+ 4
365
+ ),
366
+ oiChangePct4h: roundNullable(
367
+ pctChange(openInterest, row4h?.openInterest ?? null),
368
+ 4
369
+ ),
370
+ oiChangePct24h: roundNullable(
371
+ pctChange(openInterest, row24h?.openInterest ?? null),
372
+ 4
373
+ ),
374
+ fundingRate: roundNullable(latest.fundingRate, 8),
375
+ fundingZScore: roundNullable(
376
+ calculateZScore(
377
+ normalizedRows.map((row) => row.fundingRate),
378
+ latest.fundingRate
379
+ ),
380
+ 4
381
+ ),
382
+ liqLong: roundNullable(liqLong),
383
+ liqShort: roundNullable(liqShort),
384
+ liqTotal: roundNullable(liqTotal),
385
+ liqImbalance: roundNullable(liqImbalance, 4),
386
+ liqSpikeRatio: roundNullable(liqSpikeRatio, 4)
387
+ };
388
+ };
389
+ var getPrimaryContext = (intervals) => intervals["15m"] ?? intervals["1h"] ?? null;
390
+ var isCrowdedLong = (context) => context.fundingRate != null && context.fundingRate >= 5e-4 || context.fundingZScore != null && context.fundingZScore >= 1.5;
391
+ var isCrowdedShort = (context) => context.fundingRate != null && context.fundingRate <= -5e-4 || context.fundingZScore != null && context.fundingZScore <= -1.5;
392
+ var hasLiquidationSpike = (context) => context.liqSpikeRatio != null && context.liqSpikeRatio >= 2;
393
+ var detectPressure = (context) => {
394
+ if (!context) return "neutral";
395
+ if (hasLiquidationSpike(context) && context.liqImbalance != null && context.liqImbalance <= -0.35) {
396
+ return "long_flush";
397
+ }
398
+ if (hasLiquidationSpike(context) && context.liqImbalance != null && context.liqImbalance >= 0.35) {
399
+ return "short_flush";
400
+ }
401
+ if (isCrowdedLong(context)) return "crowded_long";
402
+ if (isCrowdedShort(context)) return "crowded_short";
403
+ return "neutral";
404
+ };
405
+ var collectRiskFlags = (contexts) => {
406
+ const flags = /* @__PURE__ */ new Set();
407
+ if (!contexts.length) {
408
+ flags.add("missing_derivatives");
409
+ return [...flags];
410
+ }
411
+ if (contexts.some((context) => context.stale)) {
412
+ flags.add("stale_derivatives");
413
+ }
414
+ for (const context of contexts) {
415
+ if (isCrowdedLong(context)) flags.add("crowded_long");
416
+ if (isCrowdedShort(context)) flags.add("crowded_short");
417
+ if (context.oiChangePct1h != null && context.oiChangePct1h < -1) {
418
+ flags.add("oi_falling");
419
+ }
420
+ if (context.oiChangePct1h != null && Math.abs(context.oiChangePct1h) < 0.15) {
421
+ flags.add("oi_not_confirming");
422
+ }
423
+ if (hasLiquidationSpike(context) && context.liqImbalance != null && context.liqImbalance <= -0.35) {
424
+ flags.add("long_liquidation_spike");
425
+ }
426
+ if (hasLiquidationSpike(context) && context.liqImbalance != null && context.liqImbalance >= 0.35) {
427
+ flags.add("short_liquidation_spike");
428
+ }
429
+ }
430
+ return [...flags];
431
+ };
432
+ var resolveDirectionAligned = (params) => {
433
+ const { direction, primary, pressure, riskFlags } = params;
434
+ if (!primary || primary.stale || riskFlags.includes("missing_derivatives")) {
435
+ return null;
436
+ }
437
+ if (direction === "LONG") {
438
+ if (pressure === "crowded_long" || riskFlags.includes("oi_falling")) {
439
+ return false;
440
+ }
441
+ if (pressure === "short_flush" || primary.oiChangePct1h != null && primary.oiChangePct1h > 0.25 && !riskFlags.includes("crowded_long")) {
442
+ return true;
443
+ }
444
+ return null;
445
+ }
446
+ if (pressure === "crowded_short" || riskFlags.includes("oi_falling")) {
447
+ return false;
448
+ }
449
+ if (pressure === "long_flush" || primary.oiChangePct1h != null && primary.oiChangePct1h > 0.25 && !riskFlags.includes("crowded_short")) {
450
+ return true;
451
+ }
452
+ return null;
453
+ };
454
+ var buildDerivativesContext = (params) => {
455
+ const {
456
+ symbol,
457
+ direction,
458
+ timestamp,
459
+ rowsByInterval,
460
+ intervals = DERIVATIVES_INTERVALS,
461
+ staleAfterMsByInterval = {}
462
+ } = params;
463
+ const intervalContexts = {};
464
+ for (const interval of intervals) {
465
+ const context = buildIntervalContext({
466
+ interval,
467
+ rows: rowsByInterval[interval],
468
+ timestamp,
469
+ staleAfterMs: staleAfterMsByInterval[interval] ?? DEFAULT_STALE_AFTER_MS[interval]
470
+ });
471
+ if (context) {
472
+ intervalContexts[interval] = context;
473
+ }
474
+ }
475
+ const contexts = Object.values(intervalContexts);
476
+ const primary = getPrimaryContext(intervalContexts);
477
+ const pressure = detectPressure(primary);
478
+ const riskFlags = collectRiskFlags(contexts);
479
+ return {
480
+ source: "coinalyze",
481
+ symbol,
482
+ timestamp,
483
+ intervals: intervalContexts,
484
+ summary: {
485
+ pressure,
486
+ directionAligned: resolveDirectionAligned({
487
+ direction,
488
+ primary,
489
+ pressure,
490
+ riskFlags
491
+ }),
492
+ riskFlags
493
+ }
494
+ };
495
+ };
496
+
267
497
  // src/utils/indicators.ts
268
498
  var import_technicalindicators = require("technicalindicators");
269
499
 
@@ -487,6 +717,17 @@ var DEFAULT_INDICATOR_PERIODS = {
487
717
  levelLookback: 20,
488
718
  levelDelay: 2
489
719
  };
720
+ var resolveIndicatorPeriods = (periods = {}) => {
721
+ const resolved = {
722
+ ...DEFAULT_INDICATOR_PERIODS
723
+ };
724
+ for (const [key, value] of Object.entries(periods)) {
725
+ if (typeof value === "number" && Number.isFinite(value)) {
726
+ resolved[key] = value;
727
+ }
728
+ }
729
+ return resolved;
730
+ };
490
731
  var ONE_HOUR_MS = 36e5;
491
732
  var ONE_DAY_MS = 864e5;
492
733
  var toMlCandle = (candle) => ({
@@ -569,10 +810,7 @@ var createIndicators = (data, btcData = [], options = {}) => {
569
810
  options.pluginRegistryScope
570
811
  );
571
812
  const includeMlPayload = options.includeMlPayload !== false;
572
- const indicatorPeriods = {
573
- ...DEFAULT_INDICATOR_PERIODS,
574
- ...options.periods || {}
575
- };
813
+ const indicatorPeriods = resolveIndicatorPeriods(options.periods);
576
814
  const closes = [];
577
815
  const highs = [];
578
816
  const lows = [];
@@ -901,10 +1139,7 @@ var createIndicators = (data, btcData = [], options = {}) => {
901
1139
  };
902
1140
  var buildMlTimeframeIndicators = (candles, periods = {}) => {
903
1141
  const result = {};
904
- const indicatorPeriods = {
905
- ...DEFAULT_INDICATOR_PERIODS,
906
- ...periods
907
- };
1142
+ const indicatorPeriods = resolveIndicatorPeriods(periods);
908
1143
  for (const timeframe of INDICATOR_TIMEFRAMES) {
909
1144
  const tfCandles = resampleCandles(candles, timeframe.minutes);
910
1145
  if (tfCandles.length === 0) continue;
@@ -1608,8 +1843,14 @@ var createTrendlineEngine = (initialCandles, options) => {
1608
1843
  return result;
1609
1844
  };
1610
1845
  const nextMany = (candles) => {
1611
- let result = [];
1612
- for (const candle of candles) result = next(candle);
1846
+ for (const candle of candles) {
1847
+ appendCandle(candle);
1848
+ }
1849
+ let result = buildResult();
1850
+ if (opts.capture && result.length === 0 && rawExtremaPoints.length) {
1851
+ rebuildCandidatesLikeBatch();
1852
+ result = buildResult();
1853
+ }
1613
1854
  return result;
1614
1855
  };
1615
1856
  const getLines = () => buildResult();
@@ -1626,6 +1867,7 @@ var createTrendlineEngine = (initialCandles, options) => {
1626
1867
  alignSortedCandlesByTimestamp,
1627
1868
  alignSpreadRows,
1628
1869
  applyIndicatorsToHistory,
1870
+ buildDerivativesContext,
1629
1871
  buildMlCandleIndicators,
1630
1872
  buildMlTimeframeIndicators,
1631
1873
  buildReturnsFromCandles,
@@ -2,6 +2,7 @@ import {
2
2
  alignSortedCandlesByTimestamp,
3
3
  alignSpreadRows,
4
4
  applyIndicatorsToHistory,
5
+ buildDerivativesContext,
5
6
  buildMlCandleIndicators,
6
7
  buildMlTimeframeIndicators,
7
8
  buildReturnsFromCandles,
@@ -28,15 +29,16 @@ import {
28
29
  toArrayData,
29
30
  toCoinalyzeTimestampMs,
30
31
  toFiniteNumber
31
- } from "./chunk-4F73AYK6.mjs";
32
+ } from "./chunk-622V7IAT.mjs";
32
33
  import "./chunk-AYC2QVKI.mjs";
33
- import "./chunk-PXLXXXLA.mjs";
34
- import "./chunk-JG2QPVAV.mjs";
34
+ import "./chunk-PQETJ42A.mjs";
35
+ import "./chunk-JLORHLL6.mjs";
35
36
  import "./chunk-M7QGVZ3J.mjs";
36
37
  export {
37
38
  alignSortedCandlesByTimestamp,
38
39
  alignSpreadRows,
39
40
  applyIndicatorsToHistory,
41
+ buildDerivativesContext,
40
42
  buildMlCandleIndicators,
41
43
  buildMlTimeframeIndicators,
42
44
  buildReturnsFromCandles,
@@ -49,7 +49,9 @@ declare const getDirectionalTpSlPrices: ({ price, direction, takeProfitDelta, st
49
49
 
50
50
  type AiRuntimeConfigLike = {
51
51
  AI_ENABLED?: boolean;
52
+ AI_MODE?: StrategyRuntimeAiOptions['mode'];
52
53
  MIN_AI_QUALITY?: number;
54
+ AI_REPLAY_ANALYSES?: StrategyRuntimeAiOptions['replayAnalyses'];
53
55
  };
54
56
  type MlRuntimeConfigLike = {
55
57
  ML_ENABLED?: boolean;
@@ -49,7 +49,9 @@ declare const getDirectionalTpSlPrices: ({ price, direction, takeProfitDelta, st
49
49
 
50
50
  type AiRuntimeConfigLike = {
51
51
  AI_ENABLED?: boolean;
52
+ AI_MODE?: StrategyRuntimeAiOptions['mode'];
52
53
  MIN_AI_QUALITY?: number;
54
+ AI_REPLAY_ANALYSES?: StrategyRuntimeAiOptions['replayAnalyses'];
53
55
  };
54
56
  type MlRuntimeConfigLike = {
55
57
  ML_ENABLED?: boolean;
@@ -145,6 +145,13 @@ var calculateCoinBtcCorrelation = (coinCandles, btcCandles) => {
145
145
  };
146
146
  };
147
147
 
148
+ // src/utils/derivativesContext.ts
149
+ var HOUR_MS = 60 * 60 * 1e3;
150
+ var DEFAULT_STALE_AFTER_MS = {
151
+ "15m": 45 * 60 * 1e3,
152
+ "1h": 3 * HOUR_MS
153
+ };
154
+
148
155
  // src/utils/indicators.ts
149
156
  var import_technicalindicators = require("technicalindicators");
150
157
 
@@ -251,6 +258,17 @@ var DEFAULT_INDICATOR_PERIODS = {
251
258
  levelLookback: 20,
252
259
  levelDelay: 2
253
260
  };
261
+ var resolveIndicatorPeriods = (periods = {}) => {
262
+ const resolved = {
263
+ ...DEFAULT_INDICATOR_PERIODS
264
+ };
265
+ for (const [key, value] of Object.entries(periods)) {
266
+ if (typeof value === "number" && Number.isFinite(value)) {
267
+ resolved[key] = value;
268
+ }
269
+ }
270
+ return resolved;
271
+ };
254
272
  var ONE_HOUR_MS = 36e5;
255
273
  var ONE_DAY_MS = 864e5;
256
274
  var toMlCandle = (candle) => ({
@@ -333,10 +351,7 @@ var createIndicators = (data, btcData = [], options = {}) => {
333
351
  options.pluginRegistryScope
334
352
  );
335
353
  const includeMlPayload = options.includeMlPayload !== false;
336
- const indicatorPeriods = {
337
- ...DEFAULT_INDICATOR_PERIODS,
338
- ...options.periods || {}
339
- };
354
+ const indicatorPeriods = resolveIndicatorPeriods(options.periods);
340
355
  const closes = [];
341
356
  const highs = [];
342
357
  const lows = [];
@@ -665,10 +680,7 @@ var createIndicators = (data, btcData = [], options = {}) => {
665
680
  };
666
681
  var buildMlTimeframeIndicators = (candles, periods = {}) => {
667
682
  const result = {};
668
- const indicatorPeriods = {
669
- ...DEFAULT_INDICATOR_PERIODS,
670
- ...periods
671
- };
683
+ const indicatorPeriods = resolveIndicatorPeriods(periods);
672
684
  for (const timeframe of INDICATOR_TIMEFRAMES) {
673
685
  const tfCandles = resampleCandles(candles, timeframe.minutes);
674
686
  if (tfCandles.length === 0) continue;
@@ -714,22 +726,29 @@ var getTimestamp = (days = 0) => {
714
726
  };
715
727
 
716
728
  // src/utils/strategyHelpers/indicators.ts
717
- var buildDefaultIndicatorPeriods = (config) => ({
718
- maFast: config.MA_FAST,
719
- maMedium: config.MA_MEDIUM,
720
- maSlow: config.MA_SLOW,
721
- obvSma: config.OBV_SMA,
722
- atr: config.ATR,
723
- atrPctShort: config.ATR_PCT_SHORT,
724
- atrPctLong: config.ATR_PCT_LONG,
725
- bb: config.BB,
726
- bbStd: config.BB_STD,
727
- macdFast: config.MACD_FAST,
728
- macdSlow: config.MACD_SLOW,
729
- macdSignal: config.MACD_SIGNAL,
730
- levelLookback: config.LEVEL_LOOKBACK,
731
- levelDelay: config.LEVEL_DELAY
732
- });
729
+ var buildDefaultIndicatorPeriods = (config) => {
730
+ const periods = {};
731
+ const assignIfFinite = (key, value) => {
732
+ if (typeof value === "number" && Number.isFinite(value)) {
733
+ periods[key] = value;
734
+ }
735
+ };
736
+ assignIfFinite("maFast", config.MA_FAST);
737
+ assignIfFinite("maMedium", config.MA_MEDIUM);
738
+ assignIfFinite("maSlow", config.MA_SLOW);
739
+ assignIfFinite("obvSma", config.OBV_SMA);
740
+ assignIfFinite("atr", config.ATR);
741
+ assignIfFinite("atrPctShort", config.ATR_PCT_SHORT);
742
+ assignIfFinite("atrPctLong", config.ATR_PCT_LONG);
743
+ assignIfFinite("bb", config.BB);
744
+ assignIfFinite("bbStd", config.BB_STD);
745
+ assignIfFinite("macdFast", config.MACD_FAST);
746
+ assignIfFinite("macdSlow", config.MACD_SLOW);
747
+ assignIfFinite("macdSignal", config.MACD_SIGNAL);
748
+ assignIfFinite("levelLookback", config.LEVEL_LOOKBACK);
749
+ assignIfFinite("levelDelay", config.LEVEL_DELAY);
750
+ return periods;
751
+ };
733
752
  var createStrategyIndicatorsState = ({
734
753
  env,
735
754
  data,
@@ -808,7 +827,7 @@ var getStrategyMarketSnapshot = async ({
808
827
  preloadStart,
809
828
  backtestPriceMode = "mid"
810
829
  }) => {
811
- const fullData = env === "BACKTEST" ? cachedData : await connector.kline({
830
+ const fullData = env === "BACKTEST" || env === "CRON" ? cachedData : await connector.kline({
812
831
  symbol,
813
832
  start: preloadStart,
814
833
  end: getTimestamp(),
@@ -897,16 +916,18 @@ var createLastTradeController = ({
897
916
  };
898
917
 
899
918
  // src/utils/uuid.ts
900
- var import_uuid = require("uuid");
919
+ var import_node_crypto = require("crypto");
901
920
  var uuid = (len = 12) => {
902
- const uuid2 = (0, import_uuid.v4)();
921
+ const uuid2 = (0, import_node_crypto.randomUUID)();
903
922
  return uuid2.slice(-len);
904
923
  };
905
924
 
906
925
  // src/utils/strategyHelpers/signalBuilders.ts
907
926
  var mapAiRuntimeFromConfig = (config, overrides = {}) => ({
908
927
  enabled: Boolean(config.AI_ENABLED ?? true),
928
+ mode: config.AI_MODE ?? "llm",
909
929
  minQuality: Number(config.MIN_AI_QUALITY ?? 4),
930
+ replayAnalyses: config.AI_REPLAY_ANALYSES,
910
931
  ...overrides
911
932
  });
912
933
  var mapMlRuntimeFromConfig = (config, overrides = {}) => ({
@@ -970,6 +991,8 @@ var buildEntrySignalDecision = ({
970
991
  });
971
992
  var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
972
993
  var toDefaultEntryCode = (strategy, direction) => `${strategy.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_${direction}_ENTRY`;
994
+ var toDefaultExitCode = (strategy, direction) => `${strategy.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_${direction}_EXIT`;
995
+ var toDefaultProtectCode = (strategy, direction) => `${strategy.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_${direction}_PROTECT`;
973
996
  var resolveTakeProfitPrice = ({
974
997
  direction,
975
998
  takeProfits
@@ -1074,6 +1097,28 @@ var createStrategyAPI = ({
1074
1097
  runtime
1075
1098
  });
1076
1099
  },
1100
+ exit: async ({
1101
+ code,
1102
+ direction,
1103
+ price,
1104
+ timestamp
1105
+ }) => {
1106
+ const marketData = await getMarketData();
1107
+ return {
1108
+ kind: "exit",
1109
+ code: code ?? toDefaultExitCode(String(strategy), direction),
1110
+ closePlan: {
1111
+ price: price ?? marketData.currentPrice,
1112
+ timestamp: timestamp ?? marketData.timestamp,
1113
+ direction
1114
+ }
1115
+ };
1116
+ },
1117
+ protect: ({ code, protectPlan }) => ({
1118
+ kind: "protect",
1119
+ code: code ?? toDefaultProtectCode(String(strategy), protectPlan.direction),
1120
+ protectPlan
1121
+ }),
1077
1122
  getMarketData,
1078
1123
  nextIndicators: (candle, btcCandle) => indicatorsState?.next(candle, btcCandle),
1079
1124
  getCurrentPosition,
@@ -1,35 +1,42 @@
1
1
  import {
2
2
  uuid
3
- } from "./chunk-NQ7D3T4E.mjs";
3
+ } from "./chunk-AJK4NS7Y.mjs";
4
4
  import {
5
5
  createIndicators
6
- } from "./chunk-4F73AYK6.mjs";
6
+ } from "./chunk-622V7IAT.mjs";
7
7
  import "./chunk-AYC2QVKI.mjs";
8
8
  import {
9
9
  getTimestamp
10
- } from "./chunk-PXLXXXLA.mjs";
10
+ } from "./chunk-PQETJ42A.mjs";
11
11
  import {
12
12
  FEE_PERCENT
13
- } from "./chunk-JG2QPVAV.mjs";
13
+ } from "./chunk-JLORHLL6.mjs";
14
14
  import "./chunk-M7QGVZ3J.mjs";
15
15
 
16
16
  // src/utils/strategyHelpers/indicators.ts
17
- var buildDefaultIndicatorPeriods = (config) => ({
18
- maFast: config.MA_FAST,
19
- maMedium: config.MA_MEDIUM,
20
- maSlow: config.MA_SLOW,
21
- obvSma: config.OBV_SMA,
22
- atr: config.ATR,
23
- atrPctShort: config.ATR_PCT_SHORT,
24
- atrPctLong: config.ATR_PCT_LONG,
25
- bb: config.BB,
26
- bbStd: config.BB_STD,
27
- macdFast: config.MACD_FAST,
28
- macdSlow: config.MACD_SLOW,
29
- macdSignal: config.MACD_SIGNAL,
30
- levelLookback: config.LEVEL_LOOKBACK,
31
- levelDelay: config.LEVEL_DELAY
32
- });
17
+ var buildDefaultIndicatorPeriods = (config) => {
18
+ const periods = {};
19
+ const assignIfFinite = (key, value) => {
20
+ if (typeof value === "number" && Number.isFinite(value)) {
21
+ periods[key] = value;
22
+ }
23
+ };
24
+ assignIfFinite("maFast", config.MA_FAST);
25
+ assignIfFinite("maMedium", config.MA_MEDIUM);
26
+ assignIfFinite("maSlow", config.MA_SLOW);
27
+ assignIfFinite("obvSma", config.OBV_SMA);
28
+ assignIfFinite("atr", config.ATR);
29
+ assignIfFinite("atrPctShort", config.ATR_PCT_SHORT);
30
+ assignIfFinite("atrPctLong", config.ATR_PCT_LONG);
31
+ assignIfFinite("bb", config.BB);
32
+ assignIfFinite("bbStd", config.BB_STD);
33
+ assignIfFinite("macdFast", config.MACD_FAST);
34
+ assignIfFinite("macdSlow", config.MACD_SLOW);
35
+ assignIfFinite("macdSignal", config.MACD_SIGNAL);
36
+ assignIfFinite("levelLookback", config.LEVEL_LOOKBACK);
37
+ assignIfFinite("levelDelay", config.LEVEL_DELAY);
38
+ return periods;
39
+ };
33
40
  var createStrategyIndicatorsState = ({
34
41
  env,
35
42
  data,
@@ -108,7 +115,7 @@ var getStrategyMarketSnapshot = async ({
108
115
  preloadStart,
109
116
  backtestPriceMode = "mid"
110
117
  }) => {
111
- const fullData = env === "BACKTEST" ? cachedData : await connector.kline({
118
+ const fullData = env === "BACKTEST" || env === "CRON" ? cachedData : await connector.kline({
112
119
  symbol,
113
120
  start: preloadStart,
114
121
  end: getTimestamp(),
@@ -199,7 +206,9 @@ var createLastTradeController = ({
199
206
  // src/utils/strategyHelpers/signalBuilders.ts
200
207
  var mapAiRuntimeFromConfig = (config, overrides = {}) => ({
201
208
  enabled: Boolean(config.AI_ENABLED ?? true),
209
+ mode: config.AI_MODE ?? "llm",
202
210
  minQuality: Number(config.MIN_AI_QUALITY ?? 4),
211
+ replayAnalyses: config.AI_REPLAY_ANALYSES,
203
212
  ...overrides
204
213
  });
205
214
  var mapMlRuntimeFromConfig = (config, overrides = {}) => ({
@@ -263,6 +272,8 @@ var buildEntrySignalDecision = ({
263
272
  });
264
273
  var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
265
274
  var toDefaultEntryCode = (strategy, direction) => `${strategy.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_${direction}_ENTRY`;
275
+ var toDefaultExitCode = (strategy, direction) => `${strategy.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_${direction}_EXIT`;
276
+ var toDefaultProtectCode = (strategy, direction) => `${strategy.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_${direction}_PROTECT`;
266
277
  var resolveTakeProfitPrice = ({
267
278
  direction,
268
279
  takeProfits
@@ -367,6 +378,28 @@ var createStrategyAPI = ({
367
378
  runtime
368
379
  });
369
380
  },
381
+ exit: async ({
382
+ code,
383
+ direction,
384
+ price,
385
+ timestamp
386
+ }) => {
387
+ const marketData = await getMarketData();
388
+ return {
389
+ kind: "exit",
390
+ code: code ?? toDefaultExitCode(String(strategy), direction),
391
+ closePlan: {
392
+ price: price ?? marketData.currentPrice,
393
+ timestamp: timestamp ?? marketData.timestamp,
394
+ direction
395
+ }
396
+ };
397
+ },
398
+ protect: ({ code, protectPlan }) => ({
399
+ kind: "protect",
400
+ code: code ?? toDefaultProtectCode(String(strategy), protectPlan.direction),
401
+ protectPlan
402
+ }),
370
403
  getMarketData,
371
404
  nextIndicators: (candle, btcCandle) => indicatorsState?.next(candle, btcCandle),
372
405
  getCurrentPosition,
@@ -5,7 +5,8 @@ declare const getTimestamp: (days?: number) => number;
5
5
  declare const getItemTimestamp: (item: KlineChartItem) => number;
6
6
  declare const getDataTimestamp: (data: KlineChartData) => number | null;
7
7
  declare const formatUnix: (dt: number) => string;
8
+ declare const getBacktestPreloadStart: (start: number, preloadDays?: number) => number;
8
9
  declare const getTimeline: (start?: number, end?: number, step?: number) => number[];
9
10
  declare const compactOrderLog: (timeline: number[], orderLog: OrderLogData) => SimpleOrderLogData;
10
11
 
11
- export { getDataTimestamp as a, getItemTimestamp as b, compactOrderLog as c, getTimestamp as d, formatUnix as f, getTimeline as g, toMs as t };
12
+ export { getBacktestPreloadStart as a, getDataTimestamp as b, compactOrderLog as c, getItemTimestamp as d, getTimestamp as e, formatUnix as f, getTimeline as g, toMs as t };
@@ -5,7 +5,8 @@ declare const getTimestamp: (days?: number) => number;
5
5
  declare const getItemTimestamp: (item: KlineChartItem) => number;
6
6
  declare const getDataTimestamp: (data: KlineChartData) => number | null;
7
7
  declare const formatUnix: (dt: number) => string;
8
+ declare const getBacktestPreloadStart: (start: number, preloadDays?: number) => number;
8
9
  declare const getTimeline: (start?: number, end?: number, step?: number) => number[];
9
10
  declare const compactOrderLog: (timeline: number[], orderLog: OrderLogData) => SimpleOrderLogData;
10
11
 
11
- export { getDataTimestamp as a, getItemTimestamp as b, compactOrderLog as c, getTimestamp as d, formatUnix as f, getTimeline as g, toMs as t };
12
+ export { getBacktestPreloadStart as a, getDataTimestamp as b, compactOrderLog as c, getItemTimestamp as d, getTimestamp as e, formatUnix as f, getTimeline as g, toMs as t };
package/dist/time.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- export { f as formatUnix, a as getDataTimestamp, b as getItemTimestamp, d as getTimestamp, t as toMs } from './time-DEyFa2vI.mjs';
1
+ export { f as formatUnix, a as getBacktestPreloadStart, b as getDataTimestamp, d as getItemTimestamp, e as getTimestamp, t as toMs } from './time-BMkFD4Kd.mjs';
2
2
  import '@tradejs/types';
package/dist/time.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { f as formatUnix, a as getDataTimestamp, b as getItemTimestamp, d as getTimestamp, t as toMs } from './time-DEyFa2vI.js';
1
+ export { f as formatUnix, a as getBacktestPreloadStart, b as getDataTimestamp, d as getItemTimestamp, e as getTimestamp, t as toMs } from './time-BMkFD4Kd.js';
2
2
  import '@tradejs/types';