@tradejs/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +60 -0
  2. package/dist/api.d.mts +7 -0
  3. package/dist/api.d.ts +7 -0
  4. package/dist/api.js +64 -0
  5. package/dist/api.mjs +39 -0
  6. package/dist/async.d.mts +4 -0
  7. package/dist/async.d.ts +4 -0
  8. package/dist/async.js +48 -0
  9. package/dist/async.mjs +20 -0
  10. package/dist/backtest.d.mts +45 -0
  11. package/dist/backtest.d.ts +45 -0
  12. package/dist/backtest.js +574 -0
  13. package/dist/backtest.mjs +355 -0
  14. package/dist/chunk-AYC2QVKI.mjs +35 -0
  15. package/dist/chunk-JG2QPVAV.mjs +190 -0
  16. package/dist/chunk-LIGD3WWX.mjs +1545 -0
  17. package/dist/chunk-M7QGVZ3J.mjs +61 -0
  18. package/dist/chunk-NQ7D3T4E.mjs +10 -0
  19. package/dist/chunk-PXLXXXLA.mjs +67 -0
  20. package/dist/config.d.mts +14 -0
  21. package/dist/config.d.ts +14 -0
  22. package/dist/config.js +49 -0
  23. package/dist/config.mjs +21 -0
  24. package/dist/constants.d.mts +41 -0
  25. package/dist/constants.d.ts +41 -0
  26. package/dist/constants.js +238 -0
  27. package/dist/constants.mjs +50 -0
  28. package/dist/data.d.mts +9 -0
  29. package/dist/data.d.ts +9 -0
  30. package/dist/data.js +100 -0
  31. package/dist/data.mjs +12 -0
  32. package/dist/figures.d.mts +103 -0
  33. package/dist/figures.d.ts +103 -0
  34. package/dist/figures.js +274 -0
  35. package/dist/figures.mjs +239 -0
  36. package/dist/indicators-x3xKl3_W.d.mts +90 -0
  37. package/dist/indicators-x3xKl3_W.d.ts +90 -0
  38. package/dist/indicators.d.mts +124 -0
  39. package/dist/indicators.d.ts +124 -0
  40. package/dist/indicators.js +1631 -0
  41. package/dist/indicators.mjs +66 -0
  42. package/dist/json.d.mts +3 -0
  43. package/dist/json.d.ts +3 -0
  44. package/dist/json.js +34 -0
  45. package/dist/json.mjs +7 -0
  46. package/dist/math.d.mts +35 -0
  47. package/dist/math.d.ts +35 -0
  48. package/dist/math.js +98 -0
  49. package/dist/math.mjs +38 -0
  50. package/dist/pine.d.mts +29 -0
  51. package/dist/pine.d.ts +29 -0
  52. package/dist/pine.js +59 -0
  53. package/dist/pine.mjs +29 -0
  54. package/dist/strategies.d.mts +104 -0
  55. package/dist/strategies.d.ts +104 -0
  56. package/dist/strategies.js +1080 -0
  57. package/dist/strategies.mjs +390 -0
  58. package/dist/tickers.d.mts +7 -0
  59. package/dist/tickers.d.ts +7 -0
  60. package/dist/tickers.js +166 -0
  61. package/dist/tickers.mjs +125 -0
  62. package/dist/time-DEyFa2vI.d.mts +11 -0
  63. package/dist/time-DEyFa2vI.d.ts +11 -0
  64. package/dist/time.d.mts +2 -0
  65. package/dist/time.d.ts +2 -0
  66. package/dist/time.js +58 -0
  67. package/dist/time.mjs +15 -0
  68. package/package.json +99 -0
@@ -0,0 +1,1080 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/strategies.ts
31
+ var strategies_exports = {};
32
+ __export(strategies_exports, {
33
+ buildDefaultIndicatorPeriods: () => buildDefaultIndicatorPeriods,
34
+ buildEntrySignalDecision: () => buildEntrySignalDecision,
35
+ buildStrategySignal: () => buildStrategySignal,
36
+ calculateRiskRatio: () => calculateRiskRatio,
37
+ createLastTradeController: () => createLastTradeController,
38
+ createStrategyAPI: () => createStrategyAPI,
39
+ createStrategyIndicatorsState: () => createStrategyIndicatorsState,
40
+ getDirectionalTpSlPrices: () => getDirectionalTpSlPrices,
41
+ getStrategyMarketSnapshot: () => getStrategyMarketSnapshot,
42
+ mapAiRuntimeFromConfig: () => mapAiRuntimeFromConfig,
43
+ mapMlRuntimeFromConfig: () => mapMlRuntimeFromConfig
44
+ });
45
+ module.exports = __toCommonJS(strategies_exports);
46
+
47
+ // src/utils/math.ts
48
+ var import_lodash = __toESM(require("lodash"));
49
+ var round = (value, precision = 2) => precision > 0 ? Math.round(value * 10 ** precision) / 10 ** precision : Math.round(value);
50
+
51
+ // src/utils/correlation.ts
52
+ var alignSortedCandlesByTimestamp = (coinCandles, btcCandles) => {
53
+ const alignedCoinCandles = [];
54
+ const alignedBtcCandles = [];
55
+ let coinIndex = 0;
56
+ let btcIndex = 0;
57
+ while (coinIndex < coinCandles.length && btcIndex < btcCandles.length) {
58
+ const coinTimestampMs = coinCandles[coinIndex].timestamp;
59
+ const btcTimestampMs = btcCandles[btcIndex].timestamp;
60
+ if (coinTimestampMs === btcTimestampMs) {
61
+ alignedCoinCandles.push(coinCandles[coinIndex]);
62
+ alignedBtcCandles.push(btcCandles[btcIndex]);
63
+ coinIndex += 1;
64
+ btcIndex += 1;
65
+ } else if (coinTimestampMs < btcTimestampMs) {
66
+ coinIndex += 1;
67
+ } else {
68
+ btcIndex += 1;
69
+ }
70
+ }
71
+ return {
72
+ alignedCoinCandles,
73
+ alignedBtcCandles
74
+ };
75
+ };
76
+ var buildReturnsFromCandles = (candles) => {
77
+ const returns = [];
78
+ for (let candleIndex = 1; candleIndex < candles.length; candleIndex += 1) {
79
+ const previousClose = candles[candleIndex - 1].close;
80
+ const currentClose = candles[candleIndex].close;
81
+ if (!Number.isFinite(previousClose) || !Number.isFinite(currentClose)) {
82
+ continue;
83
+ }
84
+ const change = (currentClose - previousClose) / previousClose;
85
+ returns.push(change);
86
+ }
87
+ return returns;
88
+ };
89
+ var calculatePearsonCorrelation = (firstSeries, secondSeries) => {
90
+ if (firstSeries.length !== secondSeries.length) {
91
+ throw new Error(
92
+ "calculatePearsonCorrelation: series lengths are different"
93
+ );
94
+ }
95
+ const length = firstSeries.length;
96
+ if (length === 0) return null;
97
+ if (length === 1) return null;
98
+ const sumFirst = firstSeries.reduce((sum, value) => sum + value, 0);
99
+ const sumSecond = secondSeries.reduce((sum, value) => sum + value, 0);
100
+ const meanFirst = sumFirst / length;
101
+ const meanSecond = sumSecond / length;
102
+ let covariance = 0;
103
+ let varianceFirst = 0;
104
+ let varianceSecond = 0;
105
+ for (let index = 0; index < length; index += 1) {
106
+ const deltaFirst = firstSeries[index] - meanFirst;
107
+ const deltaSecond = secondSeries[index] - meanSecond;
108
+ covariance += deltaFirst * deltaSecond;
109
+ varianceFirst += deltaFirst * deltaFirst;
110
+ varianceSecond += deltaSecond * deltaSecond;
111
+ }
112
+ if (varianceFirst === 0 || varianceSecond === 0) {
113
+ return null;
114
+ }
115
+ const correlation = covariance / Math.sqrt(varianceFirst * varianceSecond);
116
+ return correlation;
117
+ };
118
+ var calculateCoinBtcCorrelation = (coinCandles, btcCandles) => {
119
+ const { alignedCoinCandles, alignedBtcCandles } = alignSortedCandlesByTimestamp(coinCandles, btcCandles);
120
+ const MIN_LENGTH_FOR_CORRELATION = 10;
121
+ if (alignedCoinCandles.length <= MIN_LENGTH_FOR_CORRELATION) {
122
+ return {
123
+ correlation: null,
124
+ alignedCoinCandles,
125
+ alignedBtcCandles,
126
+ coinReturns: [],
127
+ btcReturns: []
128
+ };
129
+ }
130
+ const coinReturns = buildReturnsFromCandles(alignedCoinCandles);
131
+ const btcReturns = buildReturnsFromCandles(alignedBtcCandles);
132
+ const minReturnsLength = Math.min(coinReturns.length, btcReturns.length);
133
+ const slicedCoinReturns = coinReturns.slice(-minReturnsLength);
134
+ const slicedBtcReturns = btcReturns.slice(-minReturnsLength);
135
+ const correlation = calculatePearsonCorrelation(
136
+ slicedCoinReturns,
137
+ slicedBtcReturns
138
+ );
139
+ return {
140
+ correlation: correlation ? round(correlation) : correlation,
141
+ alignedCoinCandles,
142
+ alignedBtcCandles,
143
+ coinReturns: slicedCoinReturns,
144
+ btcReturns: slicedBtcReturns
145
+ };
146
+ };
147
+
148
+ // src/utils/indicators.ts
149
+ var import_technicalindicators = require("technicalindicators");
150
+
151
+ // src/constants/index.ts
152
+ var FEE_PERCENT = 5e-3;
153
+ var CORRELATION_WINDOW = 50;
154
+ var ML_BASE_CANDLES_WINDOW = 50;
155
+
156
+ // src/utils/array.ts
157
+ var import_lodash2 = __toESM(require("lodash"));
158
+ var cloneArrayValues = (record) => Object.fromEntries(
159
+ Object.entries(record).map(([key, value]) => [
160
+ key,
161
+ Array.isArray(value) ? value.slice() : value
162
+ ])
163
+ );
164
+
165
+ // src/utils/indicatorPlugins.ts
166
+ var pluginIndicatorEntries = /* @__PURE__ */ new Map();
167
+ var getRegisteredIndicatorEntries = () => [
168
+ ...pluginIndicatorEntries.values()
169
+ ];
170
+
171
+ // src/utils/spread.ts
172
+ var DEFAULT_SPREAD_WINDOW = 50;
173
+ var toFinitePrice = (value) => {
174
+ if (value == null) return null;
175
+ const num = Number(value);
176
+ return Number.isFinite(num) ? num : null;
177
+ };
178
+ var toFiniteSpread = (value) => {
179
+ if (value == null) return null;
180
+ const num = Number(value);
181
+ return Number.isFinite(num) ? num : null;
182
+ };
183
+ var createSpreadSmoother = (window = DEFAULT_SPREAD_WINDOW) => {
184
+ const binanceWindow = [];
185
+ const coinbaseWindow = [];
186
+ let binanceSum = 0;
187
+ let coinbaseSum = 0;
188
+ const next = (params) => {
189
+ const binance = toFinitePrice(params.binancePrice);
190
+ const coinbase = toFinitePrice(params.coinbasePrice);
191
+ if (binance != null && coinbase != null) {
192
+ binanceWindow.push(binance);
193
+ coinbaseWindow.push(coinbase);
194
+ binanceSum += binance;
195
+ coinbaseSum += coinbase;
196
+ if (binanceWindow.length > window) {
197
+ binanceSum -= binanceWindow.shift() ?? 0;
198
+ coinbaseSum -= coinbaseWindow.shift() ?? 0;
199
+ }
200
+ }
201
+ let spread = toFiniteSpread(params.fallbackSpread);
202
+ if (binanceWindow.length > 0 && coinbaseWindow.length > 0) {
203
+ const avgBinance = binanceSum / binanceWindow.length;
204
+ const avgCoinbase = coinbaseSum / coinbaseWindow.length;
205
+ if (Number.isFinite(avgBinance) && avgBinance > 0) {
206
+ spread = (avgCoinbase - avgBinance) / avgBinance;
207
+ }
208
+ }
209
+ return toFiniteSpread(spread);
210
+ };
211
+ return { next };
212
+ };
213
+
214
+ // src/utils/indicators.ts
215
+ var CANDLE_WINDOW = ML_BASE_CANDLES_WINDOW;
216
+ var BASE_INTERVAL_MINUTES = 15;
217
+ var INDICATOR_TIMEFRAMES = [
218
+ { minutes: 60, suffix: "1h" },
219
+ { minutes: 240, suffix: "4h" },
220
+ { minutes: 1440, suffix: "1d" }
221
+ ];
222
+ var DEFAULT_INDICATOR_PERIODS = {
223
+ maFast: 14,
224
+ maMedium: 49,
225
+ maSlow: 50,
226
+ obvSma: 10,
227
+ atr: 14,
228
+ atrPctShort: 7,
229
+ atrPctLong: 30,
230
+ bb: 20,
231
+ bbStd: 2,
232
+ macdFast: 12,
233
+ macdSlow: 26,
234
+ macdSignal: 9,
235
+ levelLookback: 20,
236
+ levelDelay: 2
237
+ };
238
+ var ONE_HOUR_MS = 36e5;
239
+ var ONE_DAY_MS = 864e5;
240
+ var toMlCandle = (candle) => ({
241
+ open: Number(candle.open) || 0,
242
+ high: Number(candle.high) || 0,
243
+ low: Number(candle.low) || 0,
244
+ close: Number(candle.close) || 0,
245
+ volume: Number(candle.volume) || 0,
246
+ turnover: Number(candle.turnover) || 0,
247
+ timestamp: Number(candle.timestamp) || 0
248
+ });
249
+ var resampleCandles = (candles, targetMinutes) => {
250
+ if (targetMinutes <= BASE_INTERVAL_MINUTES) return candles.map(toMlCandle);
251
+ const bucketMs = targetMinutes * 6e4;
252
+ const buckets = /* @__PURE__ */ new Map();
253
+ for (const raw of candles) {
254
+ const candle = toMlCandle(raw);
255
+ const ts = candle.timestamp;
256
+ if (!Number.isFinite(ts) || ts <= 0) continue;
257
+ const bucket = Math.floor(ts / bucketMs) * bucketMs;
258
+ const current = buckets.get(bucket);
259
+ if (!current) {
260
+ buckets.set(bucket, { ...candle, timestamp: bucket });
261
+ continue;
262
+ }
263
+ current.high = Math.max(current.high, candle.high);
264
+ current.low = Math.min(current.low, candle.low);
265
+ current.close = candle.close;
266
+ current.volume += candle.volume;
267
+ current.turnover += candle.turnover;
268
+ }
269
+ return [...buckets.entries()].sort((a, b) => a[0] - b[0]).map(([, candle]) => candle);
270
+ };
271
+ var buildMlCandleIndicators = (candles, btcCandles) => ({
272
+ candles15m: candles.slice(-CANDLE_WINDOW).map(toMlCandle),
273
+ candles1h: resampleCandles(candles, 60).slice(-CANDLE_WINDOW),
274
+ candles4h: resampleCandles(candles, 240).slice(-CANDLE_WINDOW),
275
+ candles1d: resampleCandles(candles, 1440).slice(-CANDLE_WINDOW),
276
+ btcCandles15m: btcCandles.slice(-CANDLE_WINDOW).map(toMlCandle),
277
+ btcCandles1h: resampleCandles(btcCandles, 60).slice(-CANDLE_WINDOW),
278
+ btcCandles4h: resampleCandles(btcCandles, 240).slice(-CANDLE_WINDOW),
279
+ btcCandles1d: resampleCandles(btcCandles, 1440).slice(-CANDLE_WINDOW)
280
+ });
281
+ var percentChange = (current, previous) => {
282
+ if (!Number.isFinite(current) || !Number.isFinite(previous) || previous === 0) {
283
+ return null;
284
+ }
285
+ return (current - previous) / previous * 100;
286
+ };
287
+ var applyIndicatorsToHistory = (indicators, pushIndicator) => {
288
+ pushIndicator("maFast", indicators.maFast);
289
+ pushIndicator("maMedium", indicators.maMedium);
290
+ pushIndicator("maSlow", indicators.maSlow);
291
+ pushIndicator("atr", indicators.atr);
292
+ pushIndicator("atrPct", indicators.atrPct);
293
+ pushIndicator("bbUpper", indicators.bbUpper);
294
+ pushIndicator("bbMiddle", indicators.bbMiddle);
295
+ pushIndicator("bbLower", indicators.bbLower);
296
+ pushIndicator("obv", indicators.obv);
297
+ pushIndicator("smaObv", indicators.smaObv);
298
+ pushIndicator("macd", indicators.macd);
299
+ pushIndicator("macdSignal", indicators.macdSignal);
300
+ pushIndicator("macdHistogram", indicators.macdHistogram);
301
+ pushIndicator("price24hPcnt", indicators.price24hPcnt ?? void 0);
302
+ pushIndicator("price1hPcnt", indicators.price1hPcnt ?? void 0);
303
+ pushIndicator("highPrice1h", indicators.highPrice1h ?? void 0);
304
+ pushIndicator("lowPrice1h", indicators.lowPrice1h ?? void 0);
305
+ pushIndicator("volume1h", indicators.volume1h ?? void 0);
306
+ pushIndicator("highPrice24h", indicators.highPrice24h ?? void 0);
307
+ pushIndicator("lowPrice24h", indicators.lowPrice24h ?? void 0);
308
+ pushIndicator("volume24h", indicators.volume24h ?? void 0);
309
+ pushIndicator("highLevel", indicators.highLevel ?? void 0);
310
+ pushIndicator("lowLevel", indicators.lowLevel ?? void 0);
311
+ pushIndicator("prevClose", indicators.prevClose ?? void 0);
312
+ pushIndicator("correlation", indicators.correlation ?? void 0);
313
+ pushIndicator("spread", indicators.spread ?? void 0);
314
+ };
315
+ var createIndicators = (data, btcData = [], options = {}) => {
316
+ const indicatorPluginEntries = getRegisteredIndicatorEntries();
317
+ const includeMlPayload = options.includeMlPayload !== false;
318
+ const indicatorPeriods = {
319
+ ...DEFAULT_INDICATOR_PERIODS,
320
+ ...options.periods || {}
321
+ };
322
+ const closes = [];
323
+ const highs = [];
324
+ const lows = [];
325
+ const volumes = [];
326
+ const timestamps = [];
327
+ const candlesHistory = [];
328
+ const btcCandlesHistory = [];
329
+ const btcBinanceCandles = (options.btcBinanceData ?? []).map(toMlCandle);
330
+ const btcCoinbaseCandles = (options.btcCoinbaseData ?? []).map(toMlCandle);
331
+ const spreadSmoother = createSpreadSmoother();
332
+ let btcBinanceCursor = 0;
333
+ let btcCoinbaseCursor = 0;
334
+ const obv = new import_technicalindicators.OBV({ close: [], volume: [] });
335
+ const smaObv = new import_technicalindicators.SMA({ period: indicatorPeriods.obvSma, values: [] });
336
+ const ma14 = new import_technicalindicators.SMA({ period: indicatorPeriods.maFast, values: [] });
337
+ const ma49 = new import_technicalindicators.SMA({ period: indicatorPeriods.maMedium, values: [] });
338
+ const ma50 = new import_technicalindicators.SMA({ period: indicatorPeriods.maSlow, values: [] });
339
+ const atr = new import_technicalindicators.ATR({
340
+ period: indicatorPeriods.atr,
341
+ high: [],
342
+ low: [],
343
+ close: []
344
+ });
345
+ const atrPctShort = new import_technicalindicators.SMA({
346
+ period: indicatorPeriods.atrPctShort,
347
+ values: []
348
+ });
349
+ const atrPctLong = new import_technicalindicators.SMA({
350
+ period: indicatorPeriods.atrPctLong,
351
+ values: []
352
+ });
353
+ const bb = new import_technicalindicators.BollingerBands({
354
+ period: indicatorPeriods.bb,
355
+ values: [],
356
+ stdDev: indicatorPeriods.bbStd
357
+ });
358
+ const macd = new import_technicalindicators.MACD({
359
+ fastPeriod: indicatorPeriods.macdFast,
360
+ slowPeriod: indicatorPeriods.macdSlow,
361
+ signalPeriod: indicatorPeriods.macdSignal,
362
+ values: [],
363
+ SimpleMAOscillator: false,
364
+ SimpleMASignal: false
365
+ });
366
+ const indicatorHistory = {};
367
+ const indicatorPluginErrorShown = /* @__PURE__ */ new Set();
368
+ const pushIndicator = (key, value) => {
369
+ if (value == null) {
370
+ return;
371
+ }
372
+ if (!indicatorHistory[key]) {
373
+ indicatorHistory[key] = [];
374
+ }
375
+ indicatorHistory[key].push(value);
376
+ if (indicatorHistory[key].length > ML_BASE_CANDLES_WINDOW) {
377
+ indicatorHistory[key].splice(
378
+ 0,
379
+ indicatorHistory[key].length - ML_BASE_CANDLES_WINDOW
380
+ );
381
+ }
382
+ };
383
+ const resolveCloseAtOrBefore = (candles, cursor, targetTs) => {
384
+ let idx = cursor;
385
+ while (idx + 1 < candles.length && candles[idx + 1].timestamp <= targetTs) {
386
+ idx += 1;
387
+ }
388
+ const close = idx < candles.length && candles[idx].timestamp <= targetTs ? candles[idx].close : null;
389
+ return { close, cursor: idx };
390
+ };
391
+ let window1hStart = 0;
392
+ let window24hStart = 0;
393
+ const computeWindow = (currentTimestamp, windowMs, startIdx) => {
394
+ const windowStart = currentTimestamp - windowMs;
395
+ if (timestamps.length === 0 || timestamps[0] > windowStart) {
396
+ return {
397
+ startIdx,
398
+ high: null,
399
+ low: null,
400
+ volume: null,
401
+ startClose: null,
402
+ hasFullWindow: false
403
+ };
404
+ }
405
+ let idx = startIdx;
406
+ while (idx < timestamps.length && timestamps[idx] < windowStart) {
407
+ idx += 1;
408
+ }
409
+ let high = -Infinity;
410
+ let low = Infinity;
411
+ let volume = 0;
412
+ for (let i = idx; i < highs.length; i += 1) {
413
+ const highValue = highs[i];
414
+ const lowValue = lows[i];
415
+ const volumeValue = volumes[i];
416
+ if (highValue > high) high = highValue;
417
+ if (lowValue < low) low = lowValue;
418
+ volume += volumeValue;
419
+ }
420
+ return {
421
+ startIdx: idx,
422
+ high,
423
+ low,
424
+ volume,
425
+ startClose: closes[idx],
426
+ hasFullWindow: true
427
+ };
428
+ };
429
+ const findNearestStartClose = (currentTimestamp, windowMs) => {
430
+ if (timestamps.length === 0) {
431
+ return { startClose: null, startIdx: 0 };
432
+ }
433
+ const windowStart = currentTimestamp - windowMs;
434
+ let idx = 0;
435
+ while (idx < timestamps.length && timestamps[idx] < windowStart) {
436
+ idx += 1;
437
+ }
438
+ if (idx <= 0) {
439
+ return { startClose: closes[0], startIdx: 0 };
440
+ }
441
+ if (idx >= timestamps.length) {
442
+ const lastIdx = timestamps.length - 1;
443
+ return { startClose: closes[lastIdx], startIdx: lastIdx };
444
+ }
445
+ const prevIdx = idx - 1;
446
+ const currentIdx = timestamps.length - 1;
447
+ if (idx === currentIdx && timestamps[idx] > windowStart) {
448
+ return { startClose: closes[prevIdx], startIdx: prevIdx };
449
+ }
450
+ const prevDiff = windowStart - timestamps[prevIdx];
451
+ const nextDiff = timestamps[idx] - windowStart;
452
+ const chosenIdx = prevDiff <= nextDiff ? prevIdx : idx;
453
+ return { startClose: closes[chosenIdx], startIdx: chosenIdx };
454
+ };
455
+ const next = (candle, btcCandle) => {
456
+ candlesHistory.push(candle);
457
+ if (btcCandle) {
458
+ btcCandlesHistory.push(btcCandle);
459
+ }
460
+ closes.push(candle.close);
461
+ highs.push(candle.high);
462
+ lows.push(candle.low);
463
+ volumes.push(candle.volume);
464
+ timestamps.push(candle.timestamp);
465
+ const ma14Value = ma14.nextValue(candle.close);
466
+ const ma49Value = ma49.nextValue(candle.close);
467
+ const ma50Value = ma50.nextValue(candle.close);
468
+ const atrValue = atr.nextValue(candle);
469
+ const atrPctValue = atrValue != null && Number.isFinite(atrValue) && candle.close ? atrValue / candle.close * 100 : null;
470
+ const atrPctShortValue = atrPctValue == null ? null : atrPctShort.nextValue(atrPctValue);
471
+ const atrPctLongValue = atrPctValue == null ? null : atrPctLong.nextValue(atrPctValue);
472
+ const atrPctRatio = typeof atrPctShortValue === "number" && Number.isFinite(atrPctShortValue) && typeof atrPctLongValue === "number" && Number.isFinite(atrPctLongValue) && atrPctLongValue !== 0 ? atrPctShortValue / atrPctLongValue : null;
473
+ const bbValue = bb.nextValue(candle.close);
474
+ const obvValue = obv.nextValue(candle);
475
+ const smaObvValue = obvValue == null ? null : smaObv.nextValue(obvValue);
476
+ const macdValue = macd.nextValue(candle.close);
477
+ const currentTimestamp = candle.timestamp;
478
+ const len = candlesHistory.length;
479
+ const prevCandle = len > 1 ? candlesHistory[len - 2] : null;
480
+ const correlation = btcCandlesHistory.length > 0 ? calculateCoinBtcCorrelation(
481
+ candlesHistory.slice(-CORRELATION_WINDOW),
482
+ btcCandlesHistory.slice(-CORRELATION_WINDOW)
483
+ ).correlation ?? 0 : 0;
484
+ let spread = null;
485
+ if (btcBinanceCandles.length > 0 && btcCoinbaseCandles.length > 0) {
486
+ const binanceResolved = resolveCloseAtOrBefore(
487
+ btcBinanceCandles,
488
+ btcBinanceCursor,
489
+ currentTimestamp
490
+ );
491
+ const coinbaseResolved = resolveCloseAtOrBefore(
492
+ btcCoinbaseCandles,
493
+ btcCoinbaseCursor,
494
+ currentTimestamp
495
+ );
496
+ btcBinanceCursor = binanceResolved.cursor;
497
+ btcCoinbaseCursor = coinbaseResolved.cursor;
498
+ if (binanceResolved.close != null && coinbaseResolved.close != null && Number.isFinite(binanceResolved.close) && Number.isFinite(coinbaseResolved.close) && binanceResolved.close > 0) {
499
+ spread = spreadSmoother.next({
500
+ binancePrice: binanceResolved.close,
501
+ coinbasePrice: coinbaseResolved.close
502
+ });
503
+ }
504
+ }
505
+ const computePluginSeries = (baseResult2) => {
506
+ const pluginSeries2 = {};
507
+ for (const pluginEntry of indicatorPluginEntries) {
508
+ if (!pluginEntry.compute) continue;
509
+ const historyKey = pluginEntry.historyKey || pluginEntry.indicator.id;
510
+ try {
511
+ const pluginValue = pluginEntry.compute({
512
+ candle,
513
+ btcCandle,
514
+ data: candlesHistory,
515
+ btcData: btcCandlesHistory,
516
+ baseResult: baseResult2
517
+ });
518
+ if (pluginValue == null || typeof pluginValue !== "number" || !Number.isFinite(pluginValue)) {
519
+ continue;
520
+ }
521
+ pluginSeries2[historyKey] = pluginValue;
522
+ pushIndicator(historyKey, pluginValue);
523
+ } catch (error) {
524
+ if (indicatorPluginErrorShown.has(historyKey)) {
525
+ continue;
526
+ }
527
+ indicatorPluginErrorShown.add(historyKey);
528
+ console.warn(
529
+ `Indicator plugin "${historyKey}" compute failed: ${String(error)}`
530
+ );
531
+ }
532
+ }
533
+ return pluginSeries2;
534
+ };
535
+ if (ma14Value == null || ma49Value == null || ma50Value == null || atrValue == null || !bbValue || obvValue == null || smaObvValue == null || !macdValue) {
536
+ computePluginSeries({
537
+ prevCandle,
538
+ correlation,
539
+ spread,
540
+ candle
541
+ });
542
+ return null;
543
+ }
544
+ const window1h = computeWindow(
545
+ currentTimestamp,
546
+ ONE_HOUR_MS,
547
+ window1hStart
548
+ );
549
+ window1hStart = window1h.startIdx;
550
+ const window24h = computeWindow(
551
+ currentTimestamp,
552
+ ONE_DAY_MS,
553
+ window24hStart
554
+ );
555
+ window24hStart = window24h.startIdx;
556
+ const price1hStart = findNearestStartClose(currentTimestamp, ONE_HOUR_MS);
557
+ const price24hStart = findNearestStartClose(currentTimestamp, ONE_DAY_MS);
558
+ const price1hPcntRaw = price1hStart.startClose != null ? percentChange(candle.close, price1hStart.startClose) : null;
559
+ const price24hPcntRaw = price24hStart.startClose != null ? percentChange(candle.close, price24hStart.startClose) : null;
560
+ const price1hPcnt = price1hPcntRaw ?? 0;
561
+ const price24hPcnt = price24hPcntRaw ?? 0;
562
+ const highPrice1h = window1h.hasFullWindow ? window1h.high : null;
563
+ const lowPrice1h = window1h.hasFullWindow ? window1h.low : null;
564
+ const volume1h = window1h.hasFullWindow ? window1h.volume : null;
565
+ const highPrice24h = window24h.hasFullWindow ? window24h.high : null;
566
+ const lowPrice24h = window24h.hasFullWindow ? window24h.low : null;
567
+ const volume24h = window24h.hasFullWindow ? window24h.volume : null;
568
+ let highLevel = null;
569
+ let lowLevel = null;
570
+ if (len >= indicatorPeriods.levelLookback + indicatorPeriods.levelDelay) {
571
+ const window = candlesHistory.slice(
572
+ len - indicatorPeriods.levelLookback - indicatorPeriods.levelDelay,
573
+ len - indicatorPeriods.levelDelay
574
+ );
575
+ highLevel = Math.max(...window.map((item) => item.high));
576
+ lowLevel = Math.min(...window.map((item) => item.low));
577
+ }
578
+ const baseResult = {
579
+ maFast: ma14Value,
580
+ maMedium: ma49Value,
581
+ maSlow: ma50Value,
582
+ atr: atrValue,
583
+ atrPct: atrPctRatio,
584
+ bbUpper: bbValue.upper,
585
+ bbMiddle: bbValue.middle,
586
+ bbLower: bbValue.lower,
587
+ obv: obvValue,
588
+ smaObv: smaObvValue,
589
+ macd: macdValue.MACD,
590
+ macdSignal: macdValue.signal,
591
+ macdHistogram: macdValue.histogram,
592
+ price24hPcnt,
593
+ price1hPcnt,
594
+ highPrice1h,
595
+ lowPrice1h,
596
+ volume1h,
597
+ highPrice24h,
598
+ lowPrice24h,
599
+ volume24h,
600
+ highLevel,
601
+ lowLevel,
602
+ prevClose: prevCandle?.close ?? null,
603
+ correlation,
604
+ spread
605
+ };
606
+ applyIndicatorsToHistory(baseResult, pushIndicator);
607
+ const pluginSeries = computePluginSeries({
608
+ ...baseResult,
609
+ candle,
610
+ prevCandle,
611
+ correlation
612
+ });
613
+ const result = {
614
+ ...baseResult,
615
+ ...pluginSeries,
616
+ candle,
617
+ prevCandle,
618
+ highLevel,
619
+ lowLevel,
620
+ correlation
621
+ };
622
+ return result;
623
+ };
624
+ data.forEach((candle, index) => {
625
+ next(candle, btcData[index]);
626
+ });
627
+ return {
628
+ next,
629
+ result: () => {
630
+ const baseHistory = cloneArrayValues(indicatorHistory);
631
+ if (!includeMlPayload) {
632
+ return baseHistory;
633
+ }
634
+ const fullHistory = {
635
+ ...baseHistory,
636
+ ...buildMlTimeframeIndicators(candlesHistory, indicatorPeriods),
637
+ ...buildMlCandleIndicators(candlesHistory, btcCandlesHistory),
638
+ ...buildIndicatorSeriesByTimeframes(
639
+ btcCandlesHistory,
640
+ indicatorPeriods,
641
+ "btc"
642
+ )
643
+ };
644
+ return fullHistory;
645
+ }
646
+ };
647
+ };
648
+ var buildMlTimeframeIndicators = (candles, periods = {}) => {
649
+ const result = {};
650
+ const indicatorPeriods = {
651
+ ...DEFAULT_INDICATOR_PERIODS,
652
+ ...periods
653
+ };
654
+ for (const timeframe of INDICATOR_TIMEFRAMES) {
655
+ const tfCandles = resampleCandles(candles, timeframe.minutes);
656
+ if (tfCandles.length === 0) continue;
657
+ const history = createIndicators(tfCandles, [], {
658
+ includeMlPayload: false,
659
+ periods: indicatorPeriods
660
+ }).result();
661
+ for (const [key, values] of Object.entries(history)) {
662
+ result[`${key}${timeframe.suffix}`] = values;
663
+ }
664
+ }
665
+ return cloneArrayValues(result);
666
+ };
667
+ var withSourcePrefix = (key, sourcePrefix = "") => {
668
+ if (!sourcePrefix) return key;
669
+ return `${sourcePrefix}${key[0].toUpperCase()}${key.slice(1)}`;
670
+ };
671
+ var buildIndicatorSeriesByTimeframes = (candles, periods, sourcePrefix = "") => {
672
+ const result = {};
673
+ if (candles.length === 0) return result;
674
+ const baseHistory = createIndicators(candles, [], {
675
+ includeMlPayload: false,
676
+ periods
677
+ }).result();
678
+ for (const [key, values] of Object.entries(baseHistory)) {
679
+ result[withSourcePrefix(key, sourcePrefix)] = values;
680
+ }
681
+ const timeframeHistory = buildMlTimeframeIndicators(candles, periods);
682
+ for (const [key, values] of Object.entries(timeframeHistory)) {
683
+ result[withSourcePrefix(key, sourcePrefix)] = values;
684
+ }
685
+ return cloneArrayValues(result);
686
+ };
687
+
688
+ // src/utils/timestamp.ts
689
+ var import_date_fns = require("date-fns");
690
+ var import_date_fns2 = require("date-fns");
691
+ var getTimestamp = (days = 0) => {
692
+ if (days > 0) {
693
+ return (0, import_date_fns2.getUnixTime)((0, import_date_fns2.subDays)(/* @__PURE__ */ new Date(), days)) * 1e3;
694
+ }
695
+ return (0, import_date_fns2.getUnixTime)(/* @__PURE__ */ new Date()) * 1e3;
696
+ };
697
+
698
+ // src/utils/strategyHelpers/indicators.ts
699
+ var buildDefaultIndicatorPeriods = (config) => ({
700
+ maFast: config.MA_FAST,
701
+ maMedium: config.MA_MEDIUM,
702
+ maSlow: config.MA_SLOW,
703
+ obvSma: config.OBV_SMA,
704
+ atr: config.ATR,
705
+ atrPctShort: config.ATR_PCT_SHORT,
706
+ atrPctLong: config.ATR_PCT_LONG,
707
+ bb: config.BB,
708
+ bbStd: config.BB_STD,
709
+ macdFast: config.MACD_FAST,
710
+ macdSlow: config.MACD_SLOW,
711
+ macdSignal: config.MACD_SIGNAL,
712
+ levelLookback: config.LEVEL_LOOKBACK,
713
+ levelDelay: config.LEVEL_DELAY
714
+ });
715
+ var createStrategyIndicatorsState = ({
716
+ env,
717
+ data,
718
+ btcData,
719
+ btcBinanceData,
720
+ btcCoinbaseData,
721
+ periods
722
+ }) => {
723
+ let controller = env === "BACKTEST" ? createIndicators(data, btcData, {
724
+ periods,
725
+ btcBinanceData,
726
+ btcCoinbaseData
727
+ }) : null;
728
+ let currentBarPair;
729
+ const withSnapshot = (value) => Object.assign(value, {
730
+ snapshot: () => value.result()
731
+ });
732
+ const applyBar = (candle, btcCandle) => {
733
+ if (!controller) return;
734
+ controller.next(candle, btcCandle);
735
+ };
736
+ const ensureControllerInitialized = () => {
737
+ if (controller) return withSnapshot(controller);
738
+ controller = createIndicators(data.slice(0, -1), btcData.slice(0, -1), {
739
+ periods,
740
+ btcBinanceData,
741
+ btcCoinbaseData
742
+ });
743
+ const lastCandle = data[data.length - 1];
744
+ const lastBtcCandle = btcData[btcData.length - 1];
745
+ if (lastCandle && lastBtcCandle) {
746
+ controller.next(lastCandle, lastBtcCandle);
747
+ }
748
+ return withSnapshot(controller);
749
+ };
750
+ return {
751
+ isInitialized: () => controller != null,
752
+ setCurrentBar: (candle, btcCandle) => {
753
+ currentBarPair = { candle, btcCandle };
754
+ },
755
+ onBar: (candle, btcCandle) => {
756
+ const resolvedCandle = candle ?? currentBarPair?.candle;
757
+ const resolvedBtcCandle = btcCandle ?? currentBarPair?.btcCandle;
758
+ if (!resolvedCandle || !resolvedBtcCandle) return;
759
+ applyBar(resolvedCandle, resolvedBtcCandle);
760
+ },
761
+ next: (candle, btcCandle) => {
762
+ if (!controller) return void 0;
763
+ return controller.next(candle, btcCandle);
764
+ },
765
+ // Lazy bootstrap for live mode: initialize on history before current bar and then apply current bar once.
766
+ ensureInitializedWithCurrentBar: ensureControllerInitialized,
767
+ snapshot: () => ensureControllerInitialized().snapshot(),
768
+ latestNumber: (key) => {
769
+ const snapshot = ensureControllerInitialized().snapshot();
770
+ const value = snapshot?.[key];
771
+ if (!Array.isArray(value) || value.length === 0) {
772
+ return void 0;
773
+ }
774
+ const last = value[value.length - 1];
775
+ return typeof last === "number" ? last : void 0;
776
+ }
777
+ };
778
+ };
779
+
780
+ // src/utils/strategyHelpers/market.ts
781
+ var getStrategyMarketSnapshot = async ({
782
+ env,
783
+ connector,
784
+ symbol,
785
+ interval,
786
+ cachedData,
787
+ preloadStart,
788
+ backtestPriceMode = "mid"
789
+ }) => {
790
+ const fullData = env === "BACKTEST" ? cachedData : await connector.kline({
791
+ symbol,
792
+ start: preloadStart,
793
+ end: getTimestamp(),
794
+ cacheOnly: false,
795
+ interval
796
+ });
797
+ const lastCandle = fullData[fullData.length - 1];
798
+ let currentPrice = lastCandle.close;
799
+ if (env === "BACKTEST") {
800
+ if (backtestPriceMode === "mid") {
801
+ currentPrice = (lastCandle.open + lastCandle.close) / 2;
802
+ } else if (backtestPriceMode === "open") {
803
+ currentPrice = lastCandle.open;
804
+ } else if (backtestPriceMode === "rand") {
805
+ const min = Math.min(lastCandle.low, lastCandle.high);
806
+ const max = Math.max(lastCandle.low, lastCandle.high);
807
+ currentPrice = min + Math.random() * (max - min);
808
+ }
809
+ }
810
+ return {
811
+ fullData,
812
+ lastCandle,
813
+ timestamp: lastCandle.timestamp,
814
+ currentPrice
815
+ };
816
+ };
817
+ var calculateRiskRatio = ({
818
+ direction,
819
+ currentPrice,
820
+ takeProfitPrice,
821
+ stopLossPrice
822
+ }) => {
823
+ const isLong = direction === "LONG";
824
+ const reward = isLong ? takeProfitPrice - currentPrice : currentPrice - takeProfitPrice;
825
+ const risk = isLong ? currentPrice - stopLossPrice : stopLossPrice - currentPrice;
826
+ return risk > 0 ? reward / risk : 0;
827
+ };
828
+ var getDirectionalTpSlPrices = ({
829
+ price,
830
+ direction,
831
+ takeProfitDelta,
832
+ stopLossDelta,
833
+ unit = "percent",
834
+ maxLossValue,
835
+ feePercent = FEE_PERCENT
836
+ }) => {
837
+ const deltaFactor = unit === "percent" ? 100 : 1;
838
+ const tp = takeProfitDelta / deltaFactor;
839
+ const sl = stopLossDelta / deltaFactor;
840
+ const isLong = direction === "LONG";
841
+ const stopLossPrice = isLong ? price * (1 - sl) : price * (1 + sl);
842
+ const takeProfitPrice = isLong ? price * (1 + tp) : price * (1 - tp);
843
+ const riskRatio = calculateRiskRatio({
844
+ direction,
845
+ currentPrice: price,
846
+ takeProfitPrice,
847
+ stopLossPrice
848
+ });
849
+ const slPercent = unit === "percent" ? stopLossDelta : stopLossDelta * 100;
850
+ const qty = typeof maxLossValue === "number" && Number.isFinite(maxLossValue) && maxLossValue > 0 ? maxLossValue / (price * (slPercent + feePercent) / 100) : void 0;
851
+ return {
852
+ stopLossPrice,
853
+ takeProfitPrice,
854
+ riskRatio,
855
+ qty
856
+ };
857
+ };
858
+
859
+ // src/utils/strategyHelpers/state.ts
860
+ var createLastTradeController = ({
861
+ env,
862
+ enabled = env ? env === "BACKTEST" : true,
863
+ cooldownMs = 864e5
864
+ }) => {
865
+ let lastTradeTimestamp = null;
866
+ return {
867
+ isInCooldown: (timestamp) => Boolean(
868
+ enabled && lastTradeTimestamp != null && timestamp <= lastTradeTimestamp + cooldownMs
869
+ ),
870
+ markTrade: (timestamp) => {
871
+ if (!enabled) return;
872
+ lastTradeTimestamp = timestamp;
873
+ },
874
+ getLastTradeTimestamp: () => lastTradeTimestamp
875
+ };
876
+ };
877
+
878
+ // src/utils/uuid.ts
879
+ var import_uuid = require("uuid");
880
+ var uuid = (len = 12) => {
881
+ const uuid2 = (0, import_uuid.v4)();
882
+ return uuid2.slice(-len);
883
+ };
884
+
885
+ // src/utils/strategyHelpers/signalBuilders.ts
886
+ var mapAiRuntimeFromConfig = (config, overrides = {}) => ({
887
+ enabled: Boolean(config.AI_ENABLED ?? true),
888
+ minQuality: Number(config.MIN_AI_QUALITY ?? 4),
889
+ ...overrides
890
+ });
891
+ var mapMlRuntimeFromConfig = (config, overrides = {}) => ({
892
+ enabled: Boolean(config.ML_ENABLED ?? true),
893
+ mlThreshold: Number(config.ML_THRESHOLD ?? 0),
894
+ ...overrides
895
+ });
896
+ var buildStrategySignal = ({
897
+ signalId,
898
+ strategy,
899
+ symbol,
900
+ interval,
901
+ direction,
902
+ timestamp,
903
+ prices,
904
+ figures = {},
905
+ indicators = {},
906
+ additionalIndicators,
907
+ isConfigFromBacktest
908
+ }) => ({
909
+ signalId,
910
+ strategy,
911
+ symbol,
912
+ interval,
913
+ direction,
914
+ timestamp,
915
+ figures,
916
+ prices,
917
+ indicators,
918
+ additionalIndicators,
919
+ isConfigFromBacktest
920
+ });
921
+ var buildEntrySignalDecision = ({
922
+ code,
923
+ entryContext,
924
+ figures,
925
+ indicators,
926
+ additionalIndicators,
927
+ signalId,
928
+ orderPlan,
929
+ runtime
930
+ }) => ({
931
+ kind: "entry",
932
+ code,
933
+ entryContext,
934
+ signal: buildStrategySignal({
935
+ signalId: signalId ?? uuid(),
936
+ strategy: entryContext.strategy,
937
+ symbol: entryContext.symbol,
938
+ interval: entryContext.interval,
939
+ direction: entryContext.direction,
940
+ timestamp: entryContext.timestamp,
941
+ prices: entryContext.prices,
942
+ figures,
943
+ indicators,
944
+ additionalIndicators,
945
+ isConfigFromBacktest: entryContext.isConfigFromBacktest
946
+ }),
947
+ orderPlan,
948
+ runtime
949
+ });
950
+ var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
951
+ var toDefaultEntryCode = (strategy, direction) => `${strategy.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_${direction}_ENTRY`;
952
+ var resolveTakeProfitPrice = ({
953
+ direction,
954
+ takeProfits
955
+ }) => {
956
+ if (!Array.isArray(takeProfits) || takeProfits.length === 0) {
957
+ throw new Error("strategyApi.entry requires at least one takeProfit");
958
+ }
959
+ const prices = takeProfits.map((tp) => tp?.price).filter((price) => isFiniteNumber(price));
960
+ if (prices.length === 0) {
961
+ throw new Error("strategyApi.entry requires finite takeProfit prices");
962
+ }
963
+ return direction === "LONG" ? Math.max(...prices) : Math.min(...prices);
964
+ };
965
+ var createStrategyAPI = ({
966
+ strategy,
967
+ symbol,
968
+ interval,
969
+ env,
970
+ connector,
971
+ cachedData,
972
+ indicatorsState,
973
+ preloadStart,
974
+ backtestPriceMode,
975
+ isConfigFromBacktest
976
+ }) => {
977
+ const getCurrentPosition = () => connector.getPosition(symbol);
978
+ const isPositionExists = async () => {
979
+ const position = await getCurrentPosition();
980
+ return Boolean(
981
+ position && typeof position.qty === "number" && position.qty > 0
982
+ );
983
+ };
984
+ const getMarketData = async (params = {}) => {
985
+ const resolvedPreloadStart = params.preloadStart ?? preloadStart;
986
+ if (typeof resolvedPreloadStart !== "number") {
987
+ throw new Error("strategyApi.getMarketData requires preloadStart");
988
+ }
989
+ const snapshot = await getStrategyMarketSnapshot({
990
+ env,
991
+ connector,
992
+ symbol,
993
+ interval,
994
+ cachedData,
995
+ preloadStart: resolvedPreloadStart,
996
+ backtestPriceMode: params.backtestPriceMode ?? backtestPriceMode
997
+ });
998
+ return snapshot;
999
+ };
1000
+ return {
1001
+ skip: (code) => ({ kind: "skip", code }),
1002
+ entry: async ({
1003
+ code,
1004
+ direction,
1005
+ figures,
1006
+ indicators,
1007
+ additionalIndicators,
1008
+ signalId,
1009
+ orderPlan,
1010
+ runtime
1011
+ }) => {
1012
+ const marketData = await getMarketData();
1013
+ const currentPrice = marketData.currentPrice;
1014
+ const timestamp = marketData.timestamp;
1015
+ const stopLossPrice = orderPlan.stopLossPrice;
1016
+ const takeProfitPrice = resolveTakeProfitPrice({
1017
+ direction,
1018
+ takeProfits: orderPlan.takeProfits
1019
+ });
1020
+ if (!isFiniteNumber(stopLossPrice)) {
1021
+ throw new Error(
1022
+ "strategyApi.entry requires finite orderPlan.stopLossPrice"
1023
+ );
1024
+ }
1025
+ const resolvedCode = code ?? toDefaultEntryCode(String(strategy), direction);
1026
+ const riskRatio = calculateRiskRatio({
1027
+ direction,
1028
+ currentPrice,
1029
+ takeProfitPrice,
1030
+ stopLossPrice
1031
+ });
1032
+ return buildEntrySignalDecision({
1033
+ code: resolvedCode,
1034
+ entryContext: {
1035
+ strategy,
1036
+ symbol,
1037
+ interval,
1038
+ direction,
1039
+ timestamp,
1040
+ prices: {
1041
+ currentPrice,
1042
+ takeProfitPrice,
1043
+ stopLossPrice,
1044
+ riskRatio
1045
+ },
1046
+ isConfigFromBacktest
1047
+ },
1048
+ figures,
1049
+ indicators,
1050
+ additionalIndicators,
1051
+ signalId,
1052
+ orderPlan,
1053
+ runtime
1054
+ });
1055
+ },
1056
+ getMarketData,
1057
+ nextIndicators: (candle, btcCandle) => indicatorsState?.next(candle, btcCandle),
1058
+ getCurrentPosition,
1059
+ isCurrentPositionExists: isPositionExists,
1060
+ getDirectionalTpSlPrices: (params) => getDirectionalTpSlPrices(params),
1061
+ createLastTradeController: (params) => createLastTradeController({
1062
+ env,
1063
+ ...params
1064
+ })
1065
+ };
1066
+ };
1067
+ // Annotate the CommonJS export names for ESM import in node:
1068
+ 0 && (module.exports = {
1069
+ buildDefaultIndicatorPeriods,
1070
+ buildEntrySignalDecision,
1071
+ buildStrategySignal,
1072
+ calculateRiskRatio,
1073
+ createLastTradeController,
1074
+ createStrategyAPI,
1075
+ createStrategyIndicatorsState,
1076
+ getDirectionalTpSlPrices,
1077
+ getStrategyMarketSnapshot,
1078
+ mapAiRuntimeFromConfig,
1079
+ mapMlRuntimeFromConfig
1080
+ });