@tradejs/node 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.
@@ -1,27 +1,6 @@
1
+ import "./chunk-5YNMSWL3.mjs";
1
2
  import {
2
3
  BINANCE_BREADTH_UNIVERSE_KEYS,
3
- buildBinanceBreadthUniverseSnapshot,
4
- buildMlPayload,
5
- enrichSignalWithBinanceMarketContext,
6
- enrichSignalWithCoinMarketCapContext,
7
- enrichSignalWithHyperliquidWhaleContext,
8
- enrichSignalWithMarketContextStages,
9
- getBinanceBreadthUniverseSnapshot,
10
- getBinanceBreadthUniverses,
11
- getHyperliquidPerpSymbols,
12
- getHyperliquidPerpUniverseSnapshot,
13
- getHyperliquidWhaleAddresses,
14
- getHyperliquidWhaleRegistrySnapshot,
15
- getPrimaryBinanceBreadthUniverse,
16
- isTrackedHyperliquidPerp,
17
- isTrackedHyperliquidWhale,
18
- loadHyperliquidWhaleFlowContext,
19
- resolveHyperliquidPerpFromSignalSymbol
20
- } from "./chunk-3BP3ETAD.mjs";
21
- import {
22
- require_lodash
23
- } from "./chunk-GPR56UYQ.mjs";
24
- import {
25
4
  DEFAULT_AI_MODEL,
26
5
  MAX_AI_SERIES_POINTS,
27
6
  askAI,
@@ -29,2537 +8,57 @@ import {
29
8
  buildAiPayload,
30
9
  buildAiPrompts,
31
10
  buildAiSystemPrompt,
11
+ buildBinanceBreadthUniverseSnapshot,
32
12
  buildCompactAiIndicatorsSnapshot,
13
+ createStrategyRuntime,
14
+ enrichSignalWithAi,
15
+ enrichSignalWithBinanceMarketContext,
16
+ enrichSignalWithCoinMarketCapContext,
17
+ enrichSignalWithHyperliquidWhaleContext,
18
+ enrichSignalWithMl,
19
+ enrichSignalWithMlAi,
33
20
  ensureAiStrategyPluginsLoaded,
34
- getDeterministicAiGateContext,
35
- getOpenRouterModelKwargs,
36
- resetAiRuntimeCache,
37
- resolveStrategyPolicyProfile,
38
- runAiPrompt,
39
- runAiPromptLocal,
40
- trimSeriesDeep
41
- } from "./chunk-WK5EUCX5.mjs";
42
- import {
43
- createPineScriptLoader
44
- } from "./chunk-IQKMII6L.mjs";
45
- import "./chunk-5YNMSWL3.mjs";
46
- import {
47
21
  ensureIndicatorPluginsLoaded,
48
22
  ensureStrategyPluginsLoaded,
23
+ executeEntryOrder,
49
24
  getAvailableStrategyNames,
25
+ getBinanceBreadthUniverseSnapshot,
26
+ getBinanceBreadthUniverses,
27
+ getDeterministicAiGateContext,
28
+ getHyperliquidPerpSymbols,
29
+ getHyperliquidPerpUniverseSnapshot,
30
+ getHyperliquidWhaleAddresses,
31
+ getHyperliquidWhaleRegistrySnapshot,
32
+ getOpenRouterModelKwargs,
33
+ getOrderArrivalSnapshot,
34
+ getPrimaryBinanceBreadthUniverse,
50
35
  getRegisteredManifests,
51
36
  getRegisteredStrategies,
52
37
  getStrategyCreator,
53
38
  getStrategyManifest,
54
39
  isKnownStrategy,
40
+ isTrackedHyperliquidPerp,
41
+ isTrackedHyperliquidWhale,
55
42
  registerStrategyEntries,
43
+ resetAiRuntimeCache,
56
44
  resetStrategyRegistryCache,
57
- strategies
58
- } from "./chunk-QVSMINLG.mjs";
59
- import {
60
- getTradejsProjectCwd,
61
- loadTradejsConfig
62
- } from "./chunk-WS5DYEVZ.mjs";
63
- import {
64
- __toESM
65
- } from "./chunk-UV2HVMKZ.mjs";
45
+ resolveHyperliquidPerpFromSignalSymbol,
46
+ resolveStrategyConfig,
47
+ runAiPrompt,
48
+ runAiPromptLocal,
49
+ strategies,
50
+ trimSeriesDeep,
51
+ validateEntryProtectionAtArrival
52
+ } from "./chunk-4AVFJMQL.mjs";
53
+ import "./chunk-WS5DYEVZ.mjs";
54
+ import "./chunk-Y6FXYEAI.mjs";
66
55
 
67
56
  // src/strategies.ts
68
57
  export * from "@tradejs/core/strategies";
69
58
 
70
- // src/strategyRuntime.ts
71
- import path from "path";
72
- import {
73
- BACKTEST_EXECUTION_DELAY_MS,
74
- BACKTEST_EXECUTION_INTERVAL,
75
- BACKTEST_LOWER_TIMEFRAME_EXECUTION_ENABLED
76
- } from "@tradejs/core/constants";
77
- import { intervalToMs } from "@tradejs/core/data";
78
- import {
79
- buildDefaultIndicatorPeriods,
80
- calculateRiskRatio,
81
- createStrategyAPI,
82
- createStrategyIndicatorsState,
83
- getSharedStrategyReplayState,
84
- resolveBacktestExecutionPrice
85
- } from "@tradejs/core/strategies";
86
- import { logger as logger3 } from "@tradejs/infra/logger";
87
-
88
- // src/strategyHelpers/runtime.ts
89
- import { logger as logger2 } from "@tradejs/infra/logger";
90
- import { FEE_PERCENT } from "@tradejs/core/constants";
91
- import {
92
- buildMlFeatures,
93
- buildMlTrainingRow,
94
- fetchMlThreshold,
95
- trimMlTrainingRowWindows
96
- } from "@tradejs/infra/ml";
97
-
98
- // src/runtimeJournal.ts
99
- import { randomUUID } from "crypto";
100
- import { TTL_1M } from "@tradejs/core/constants";
101
- import { getRuntimeStorageDayKey } from "@tradejs/core/time";
102
- import { createRuntimeOrderLinkPrefix } from "@tradejs/core/trade";
103
- import { logger } from "@tradejs/infra/logger";
104
- import {
105
- delKey,
106
- getData,
107
- redisKeys,
108
- setData,
109
- setHashJsonField
110
- } from "@tradejs/infra/redis";
111
- var now = () => Date.now();
112
- var toRandomOrderSuffix = () => randomUUID().replace(/-/g, "").slice(0, 12).toLowerCase();
113
- var calculateClosedPnl = ({
114
- direction,
115
- entryPrice,
116
- exitPrice,
117
- qty
118
- }) => {
119
- const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
120
- return Number.isFinite(pnl) ? pnl : null;
121
- };
122
- var createRuntimeOrderId = (strategy) => {
123
- const prefix = createRuntimeOrderLinkPrefix(strategy);
124
- if (prefix === "tjs-") {
125
- return `tjs-${randomUUID().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
126
- }
127
- return `${prefix}${toRandomOrderSuffix()}`;
128
- };
129
- var recordRuntimeTradeOpen = async (params) => {
130
- const { userName } = params;
131
- if (!userName) {
132
- return null;
133
- }
134
- const record = {
135
- ...params,
136
- entryCount: 1,
137
- lastEntryPrice: params.entryPrice,
138
- lastEntryQty: params.qty,
139
- lastEntryTimestamp: params.entryTimestamp,
140
- status: "active",
141
- currentPrice: params.entryPrice,
142
- currentPnl: 0,
143
- closedPnl: null,
144
- exitPrice: null,
145
- exitTimestamp: null,
146
- lastSyncedAt: now()
147
- };
148
- const dayKey = getRuntimeStorageDayKey(record.entryTimestamp);
149
- const runtimeScopeId = record.deploymentId ?? record.accountId;
150
- try {
151
- await Promise.all([
152
- setData(redisKeys.runtimeTrade(userName, record.orderId), record, {
153
- expire: 0
154
- }),
155
- setHashJsonField(
156
- redisKeys.runtimeTradeBucket(userName, dayKey),
157
- record.orderId,
158
- record,
159
- { expire: 0 }
160
- ),
161
- setData(
162
- redisKeys.runtimeActiveTrade(userName, record.symbol, runtimeScopeId),
163
- { orderId: record.orderId },
164
- { expire: 0 }
165
- )
166
- ]);
167
- } catch (error) {
168
- logger.error(
169
- "runtime trade open journal failed: %s %s",
170
- record.symbol,
171
- error?.message || String(error)
172
- );
173
- }
174
- return record;
175
- };
176
- var recordRuntimeTradeIncrease = async (params) => {
177
- const {
178
- userName,
179
- strategy,
180
- symbol,
181
- direction,
182
- resultingQty,
183
- resultingEntryPrice,
184
- addedQty,
185
- addedEntryPrice,
186
- entryTimestamp,
187
- fee,
188
- accountId,
189
- deploymentId
190
- } = params;
191
- if (!userName) {
192
- return null;
193
- }
194
- const existing = await getActiveRuntimeTrade({
195
- userName,
196
- symbol,
197
- accountId,
198
- deploymentId
199
- });
200
- if (!existing || existing.strategy !== strategy || existing.direction !== direction) {
201
- return null;
202
- }
203
- const addedFee = typeof fee === "number" && Number.isFinite(fee) ? fee : 0;
204
- const openFee = (existing.openFee ?? existing.fee ?? 0) + addedFee;
205
- const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
206
- const next = {
207
- ...existing,
208
- qty: resultingQty,
209
- entryPrice: resultingEntryPrice,
210
- entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
211
- lastEntryPrice: addedEntryPrice,
212
- lastEntryQty: addedQty,
213
- lastEntryTimestamp: entryTimestamp,
214
- currentPrice: resultingEntryPrice,
215
- currentPnl: 0,
216
- fee: openFee,
217
- openFee,
218
- totalFee,
219
- lastSyncedAt: now()
220
- };
221
- const dayKey = getRuntimeStorageDayKey(existing.entryTimestamp);
222
- try {
223
- await Promise.all([
224
- setData(redisKeys.runtimeTrade(userName, existing.orderId), next, {
225
- expire: 0
226
- }),
227
- setHashJsonField(
228
- redisKeys.runtimeTradeBucket(userName, dayKey),
229
- existing.orderId,
230
- next,
231
- { expire: 0 }
232
- )
233
- ]);
234
- } catch (error) {
235
- logger.error(
236
- "runtime trade increase journal failed: %s %s",
237
- symbol,
238
- error?.message || String(error)
239
- );
240
- }
241
- return next;
242
- };
243
- var getActiveRuntimeTrade = async (params) => {
244
- const { userName, symbol, accountId, deploymentId } = params;
245
- if (!userName) {
246
- return null;
247
- }
248
- const activeRef = await getData(
249
- redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId),
250
- null
251
- );
252
- const orderId = String(activeRef?.orderId || "").trim();
253
- if (!orderId) {
254
- return null;
255
- }
256
- const existing = await getData(
257
- redisKeys.runtimeTrade(userName, orderId),
258
- null
259
- );
260
- if (!existing || existing.status !== "active") {
261
- await delKey(
262
- redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
263
- );
264
- return null;
265
- }
266
- return existing;
267
- };
268
- var markRuntimeTradeClosed = async (params) => {
269
- const {
270
- userName,
271
- symbol,
272
- strategy,
273
- exitPrice,
274
- exitTimestamp,
275
- closedPnl,
276
- exitType,
277
- accountId,
278
- deploymentId
279
- } = params;
280
- if (!userName) {
281
- return null;
282
- }
283
- const existing = await getActiveRuntimeTrade({
284
- userName,
285
- symbol,
286
- accountId,
287
- deploymentId
288
- });
289
- if (!existing) {
290
- return null;
291
- }
292
- const orderId = existing.orderId;
293
- if (strategy && existing.strategy !== strategy) {
294
- return null;
295
- }
296
- const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
297
- const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
298
- direction: existing.direction,
299
- entryPrice: existing.entryPrice,
300
- exitPrice: resolvedExitPrice,
301
- qty: existing.qty
302
- }) : existing.closedPnl ?? existing.currentPnl ?? null;
303
- const next = {
304
- ...existing,
305
- status: "closed",
306
- currentPrice: resolvedExitPrice,
307
- currentPnl: resolvedClosedPnl,
308
- closedPnl: resolvedClosedPnl,
309
- exitPrice: resolvedExitPrice,
310
- exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
311
- exitType: exitType ?? existing.exitType ?? null,
312
- lastSyncedAt: now()
313
- };
314
- const dayKey = getRuntimeStorageDayKey(existing.entryTimestamp);
315
- const closeDayKey = getRuntimeStorageDayKey(next.exitTimestamp);
316
- try {
317
- await Promise.all([
318
- setData(redisKeys.runtimeTrade(userName, orderId), next, {
319
- expire: TTL_1M
320
- }),
321
- setHashJsonField(
322
- redisKeys.runtimeTradeBucket(userName, dayKey),
323
- orderId,
324
- next,
325
- { expire: TTL_1M }
326
- ),
327
- setHashJsonField(
328
- redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
329
- orderId,
330
- next,
331
- { expire: TTL_1M }
332
- ),
333
- delKey(
334
- redisKeys.runtimeActiveTrade(
335
- userName,
336
- symbol,
337
- deploymentId ?? accountId
338
- )
339
- )
340
- ]);
341
- } catch (error) {
342
- logger.error(
343
- "runtime trade close journal failed: %s %s",
344
- symbol,
345
- error?.message || String(error)
346
- );
347
- }
348
- return next;
349
- };
350
-
351
- // src/strategyHelpers/runtime.ts
352
- var formatAiError = (err) => {
353
- const error = err;
354
- const safeJson = (value) => {
355
- try {
356
- return JSON.stringify(value);
357
- } catch {
358
- return String(value);
359
- }
360
- };
361
- const details = {
362
- message: String(error?.message ?? "unknown"),
363
- status: error?.status ?? null,
364
- code: error?.code ?? null,
365
- type: error?.type ?? null,
366
- providerError: error?.error ?? null
367
- };
368
- return safeJson(details);
369
- };
370
- var resolveAiQuality = (analysis, direction) => {
371
- if (typeof analysis?.quality !== "number") {
372
- return void 0;
373
- }
374
- const normalizedQuality = Math.round(analysis.quality);
375
- const aiApprovedCurrentTrade = analysis.direction === direction;
376
- return aiApprovedCurrentTrade ? normalizedQuality : 0;
377
- };
378
- var findReplayAiAnalysis = ({
379
- signal,
380
- direction,
381
- ai
382
- }) => {
383
- const snapshots = ai?.replayAnalyses;
384
- if (!Array.isArray(snapshots) || !snapshots.length) {
385
- return void 0;
386
- }
387
- let best = null;
388
- for (const snapshot of snapshots) {
389
- if (snapshot.symbol !== signal.symbol || snapshot.direction !== direction || snapshot.strategy && snapshot.strategy !== signal.strategy) {
390
- continue;
391
- }
392
- const toleranceMs = Math.max(0, Number(snapshot.toleranceMs ?? 0));
393
- const diff = Math.abs(snapshot.timestamp - signal.timestamp);
394
- if (diff > toleranceMs || best && diff >= best.diff) {
395
- continue;
396
- }
397
- best = {
398
- diff,
399
- analysis: snapshot.analysis
400
- };
401
- }
402
- return best?.analysis;
403
- };
404
- var enrichSignalWithMl = async ({
405
- signal,
406
- env,
407
- ml
408
- }) => {
409
- if (env !== "BACKTEST" && ml && ml.enabled !== false && ml.strategyConfig && typeof ml.mlThreshold === "number") {
410
- const strategy = signal.strategy;
411
- const fullRow = buildMlTrainingRow(
412
- buildMlPayload({
413
- signal,
414
- context: {
415
- strategyConfig: ml.strategyConfig,
416
- strategyName: strategy,
417
- symbol: signal.symbol
418
- }
419
- }),
420
- null
421
- );
422
- const row = trimMlTrainingRowWindows(fullRow, 5);
423
- const features = buildMlFeatures(row);
424
- const mlResult = await fetchMlThreshold({
425
- strategy: ml.modelKey ?? strategy,
426
- features,
427
- threshold: ml.mlThreshold,
428
- projectRoot: getTradejsProjectCwd()
429
- });
430
- if (mlResult) {
431
- signal.ml = mlResult;
432
- }
433
- }
434
- };
435
- var enrichSignalWithAi = async ({
436
- signal,
437
- symbol,
438
- userName,
439
- direction,
440
- env,
441
- ai
442
- }) => {
443
- if (ai?.enabled === false) {
444
- return void 0;
445
- }
446
- if (env === "PARITY") {
447
- const replayAnalysis = findReplayAiAnalysis({ signal, direction, ai });
448
- if (replayAnalysis) {
449
- signal.aiAnalysis = replayAnalysis;
450
- return resolveAiQuality(replayAnalysis, direction);
451
- }
452
- }
453
- if (env === "BACKTEST") {
454
- return void 0;
455
- }
456
- if (ai?.mode === "gate") {
457
- const gateAnalysis = await runAiPromptLocal(signal);
458
- const gateQuality = resolveAiQuality(gateAnalysis, direction);
459
- signal.aiAnalysis = gateAnalysis;
460
- return gateQuality;
461
- }
462
- try {
463
- const analysis = await askAI(signal, { userName });
464
- signal.aiAnalysis = analysis;
465
- return resolveAiQuality(analysis, direction);
466
- } catch (err) {
467
- logger2.error("AI analysis error: %s %s", symbol, formatAiError(err));
468
- }
469
- return void 0;
470
- };
471
- var enrichSignalWithMlAi = async ({
472
- signal,
473
- userName,
474
- symbol,
475
- direction,
476
- env,
477
- ml,
478
- ai
479
- }) => {
480
- await enrichSignalWithMarketContextStages({ signal, env });
481
- await enrichSignalWithMl({ signal, env, ml });
482
- return enrichSignalWithAi({ signal, userName, symbol, direction, env, ai });
483
- };
484
- var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
485
- var getOrderArrivalSnapshot = async ({
486
- connector,
487
- symbol
488
- }) => {
489
- if (typeof connector.getTopOfBookTicker !== "function") {
490
- return {
491
- arrivalSnapshotTime: Date.now(),
492
- arrivalSource: "unavailable",
493
- bid: null,
494
- ask: null,
495
- arrivalMid: null,
496
- spreadBps: null
497
- };
498
- }
499
- try {
500
- const ticker = await connector.getTopOfBookTicker(symbol);
501
- const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
502
- const bid = toFiniteNumberOrNull(ticker?.bidPrice);
503
- const ask = toFiniteNumberOrNull(ticker?.askPrice);
504
- const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
505
- const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
506
- return {
507
- arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
508
- arrivalSource: "top_of_book",
509
- bid,
510
- ask,
511
- arrivalMid,
512
- spreadBps
513
- };
514
- } catch (error) {
515
- logger2.warn(
516
- "runtime order arrival snapshot failed: %s %s",
517
- symbol,
518
- error?.message || String(error)
519
- );
520
- return {
521
- arrivalSnapshotTime: Date.now(),
522
- arrivalSource: "top_of_book_error",
523
- bid: null,
524
- ask: null,
525
- arrivalMid: null,
526
- spreadBps: null
527
- };
528
- }
529
- };
530
- var validateEntryProtectionAtArrival = ({
531
- direction,
532
- signalPrice,
533
- bid,
534
- ask,
535
- arrivalMid,
536
- takeProfits,
537
- stopLossPrice
538
- }) => {
539
- const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
540
- const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
541
- if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
542
- throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
543
- }
544
- for (const takeProfit of takeProfits) {
545
- if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
546
- throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
547
- }
548
- }
549
- if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
550
- throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
551
- }
552
- };
553
- var resolveRuntimeTelemetryQuality = ({
554
- signalClosePrice,
555
- arrivalMid,
556
- orderSubmitTime,
557
- orderAckTime,
558
- fillAvgPrice,
559
- fillTime
560
- }) => {
561
- if (signalClosePrice != null && arrivalMid != null && orderSubmitTime != null && orderAckTime != null && fillAvgPrice != null && fillTime != null) {
562
- return "full";
563
- }
564
- if (fillAvgPrice != null && (arrivalMid != null || orderSubmitTime != null)) {
565
- return "partial";
566
- }
567
- if (fillAvgPrice != null) {
568
- return "price_only";
569
- }
570
- return "none";
571
- };
572
- var applyProtectiveOrders = async ({
573
- connector,
574
- symbol,
575
- direction,
576
- qty,
577
- takeProfits,
578
- stopLossPrice
579
- }) => {
580
- if (Array.isArray(takeProfits) && takeProfits.length > 0) {
581
- const tpOk = await connector.setTakeProfits({
582
- symbol,
583
- direction,
584
- qty,
585
- takeProfits
586
- });
587
- if (!tpOk) {
588
- throw new Error("SET_TAKE_PROFITS_FAILED");
589
- }
590
- }
591
- if (typeof stopLossPrice === "number" && Number.isFinite(stopLossPrice)) {
592
- const slOk = await connector.setStopLoss({
593
- symbol,
594
- direction,
595
- stopLossPrice
596
- });
597
- if (!slOk) {
598
- throw new Error("SET_STOP_LOSS_FAILED");
599
- }
600
- }
601
- };
602
- var executeEntryOrder = async ({
603
- connector,
604
- userName,
605
- symbol,
606
- direction,
607
- qty,
608
- currentPrice,
609
- timestamp,
610
- takeProfits,
611
- stopLossPrice,
612
- positionIntent = "open",
613
- signal,
614
- beforePlaceOrder,
615
- recordRuntimeTrade = true,
616
- leverage
617
- }) => {
618
- await beforePlaceOrder?.();
619
- const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
620
- const orderId = signal.orderId || createRuntimeOrderId(signal.strategy);
621
- const signalTimestamp = signal.timestamp;
622
- const signalClosePrice = currentPrice;
623
- signal.orderId = orderId;
624
- signal.orderQty = qty;
625
- signal.orderValue = qty * currentPrice;
626
- signal.orderFailureReason = void 0;
627
- const arrivalSnapshot = await getOrderArrivalSnapshot({ connector, symbol });
628
- try {
629
- validateEntryProtectionAtArrival({
630
- direction,
631
- signalPrice: currentPrice,
632
- bid: arrivalSnapshot.bid,
633
- ask: arrivalSnapshot.ask,
634
- arrivalMid: arrivalSnapshot.arrivalMid,
635
- takeProfits,
636
- stopLossPrice
637
- });
638
- } catch (error) {
639
- signal.orderStatus = "failed";
640
- signal.orderFailureReason = error.message;
641
- throw error;
642
- }
643
- const orderSubmitTime = Date.now();
644
- const orderPlaced = await connector.placeOrder({
645
- symbol,
646
- qty,
647
- price: currentPrice,
648
- isLimit: false,
649
- positionIntent,
650
- timestamp,
651
- direction,
652
- ...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
653
- orderId,
654
- signal
655
- });
656
- const orderAckTime = Date.now();
657
- const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
658
- const currentPosition = await connector.getPosition(symbol);
659
- const fillTime = Date.now();
660
- const isPositionIncrease = positionIntent === "increase" && previousPosition?.direction === direction && Number.isFinite(previousPosition.qty) && previousPosition.qty > 0;
661
- const hasRefreshedIncreasedPosition = Boolean(
662
- isPositionIncrease && previousPosition && currentPosition?.qty && currentPosition.qty > previousPosition.qty
663
- );
664
- const hasUsableCurrentPosition = Boolean(
665
- currentPosition && Number.isFinite(currentPosition.price) && Number.isFinite(currentPosition.qty) && (!isPositionIncrease || hasRefreshedIncreasedPosition)
666
- );
667
- const resultingEntryPrice = hasUsableCurrentPosition && currentPosition ? currentPosition.price : isPositionIncrease && previousPosition ? (previousPosition.price * previousPosition.qty + currentPrice * placedQty) / (previousPosition.qty + placedQty) : currentPrice;
668
- const resultingQty = hasUsableCurrentPosition && currentPosition ? currentPosition.qty : isPositionIncrease && previousPosition ? previousPosition.qty + placedQty : placedQty;
669
- const filledQty = isPositionIncrease && previousPosition ? hasRefreshedIncreasedPosition && currentPosition ? currentPosition.qty - previousPosition.qty : placedQty : resultingQty;
670
- const fillPrice = isPositionIncrease && previousPosition && hasRefreshedIncreasedPosition && currentPosition ? (resultingEntryPrice * currentPosition.qty - previousPosition.price * previousPosition.qty) / (currentPosition.qty - previousPosition.qty) : isPositionIncrease ? currentPrice : resultingEntryPrice;
671
- const fillSource = hasUsableCurrentPosition ? "exchange_position" : orderPlaced ? "requested_price" : "unknown";
672
- const estimatedOpenFee = fillPrice * filledQty * FEE_PERCENT;
673
- signal.prices.currentPrice = fillPrice;
674
- signal.orderQty = filledQty;
675
- signal.orderValue = filledQty * fillPrice;
676
- if (orderPlaced) {
677
- try {
678
- await applyProtectiveOrders({
679
- connector,
680
- symbol,
681
- direction,
682
- qty: resultingQty,
683
- takeProfits,
684
- stopLossPrice
685
- });
686
- } catch (error) {
687
- await connector.closePosition({
688
- symbol,
689
- price: resultingEntryPrice,
690
- timestamp,
691
- direction,
692
- signal
693
- });
694
- throw error;
695
- }
696
- }
697
- signal.orderStatus = orderPlaced ? "completed" : "failed";
698
- signal.orderSkipReason = void 0;
699
- if (orderPlaced) {
700
- signal.orderFailureReason = void 0;
701
- }
702
- if (orderPlaced && recordRuntimeTrade && !isPositionIncrease) {
703
- await recordRuntimeTradeOpen({
704
- userName,
705
- orderId,
706
- signalId: signal.signalId,
707
- strategy: signal.strategy,
708
- symbol,
709
- interval: signal.interval,
710
- direction,
711
- qty: resultingQty,
712
- entryPrice: resultingEntryPrice,
713
- signalTimestamp,
714
- signalClosePrice,
715
- arrivalSnapshotTime: arrivalSnapshot.arrivalSnapshotTime,
716
- arrivalSource: arrivalSnapshot.arrivalSource,
717
- arrivalMid: arrivalSnapshot.arrivalMid,
718
- bid: arrivalSnapshot.bid,
719
- ask: arrivalSnapshot.ask,
720
- spreadBps: arrivalSnapshot.spreadBps,
721
- orderSubmitTime,
722
- orderAckTime,
723
- fillAvgPrice: fillPrice,
724
- fillSource,
725
- fillTime,
726
- telemetryQuality: resolveRuntimeTelemetryQuality({
727
- signalClosePrice,
728
- arrivalMid: arrivalSnapshot.arrivalMid,
729
- orderSubmitTime,
730
- orderAckTime,
731
- fillAvgPrice: fillPrice,
732
- fillTime
733
- }),
734
- fee: estimatedOpenFee,
735
- openFee: estimatedOpenFee,
736
- totalFee: estimatedOpenFee,
737
- entryTimestamp: timestamp,
738
- universe: signal.universe,
739
- assetClass: signal.assetClass,
740
- accountId: signal.accountId,
741
- deploymentId: signal.deploymentId,
742
- policyProfileId: signal.policyProfileId,
743
- runtimeConfigId: signal.runtimeConfigId,
744
- runtimeLineage: signal.runtimeLineage,
745
- ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {}
746
- });
747
- }
748
- if (orderPlaced && recordRuntimeTrade && isPositionIncrease) {
749
- await recordRuntimeTradeIncrease({
750
- userName,
751
- strategy: signal.strategy,
752
- symbol,
753
- direction,
754
- resultingQty,
755
- resultingEntryPrice,
756
- addedQty: filledQty,
757
- addedEntryPrice: fillPrice,
758
- entryTimestamp: timestamp,
759
- fee: estimatedOpenFee,
760
- accountId: signal.accountId,
761
- deploymentId: signal.deploymentId
762
- });
763
- }
764
- if (hasUsableCurrentPosition && currentPosition?.price) {
765
- return currentPosition.price;
766
- }
767
- return resultingEntryPrice;
768
- };
769
- var updatePositionProtection = async ({
770
- connector,
771
- symbol,
772
- direction,
773
- qty,
774
- takeProfits,
775
- stopLossPrice
776
- }) => {
777
- await applyProtectiveOrders({
778
- connector,
779
- symbol,
780
- direction,
781
- qty,
782
- takeProfits: takeProfits ?? [],
783
- stopLossPrice: stopLossPrice ?? null
784
- });
785
- };
786
-
787
- // src/strategyHelpers/config.ts
788
- var import_lodash = __toESM(require_lodash());
789
- import { getData as getData2, redisKeys as redisKeys2 } from "@tradejs/infra/redis";
790
- var resolveStrategyConfig = async ({
791
- strategyName,
792
- userName,
793
- symbol,
794
- baseConfig,
795
- defaults,
796
- runtimeConfigId,
797
- runtimeConfigSnapshot
798
- }) => {
799
- const mergeIfNotEmpty = (target, patch) => patch && !import_lodash.default.isEmpty(patch) ? {
800
- ...target,
801
- ...patch
802
- } : target;
803
- let config = {
804
- ...defaults,
805
- ...baseConfig
806
- };
807
- let isConfigFromBacktest = false;
808
- if (config.ENV !== "BACKTEST") {
809
- const userConfig = runtimeConfigSnapshot ? runtimeConfigSnapshot.userConfig : await getData2(
810
- runtimeConfigId ? redisKeys2.strategyConfig(
811
- userName,
812
- strategyName,
813
- runtimeConfigId
814
- ) : redisKeys2.strategyConfig(userName, strategyName),
815
- {}
816
- );
817
- config = mergeIfNotEmpty(config, userConfig);
818
- if (!runtimeConfigId || runtimeConfigId === "config") {
819
- const symbolResultConfig = runtimeConfigSnapshot ? runtimeConfigSnapshot.symbolResultConfig : (await getData2(
820
- redisKeys2.strategyResults(userName, strategyName),
821
- {}
822
- ))?.[symbol]?.config;
823
- if (symbolResultConfig && !import_lodash.default.isEmpty(symbolResultConfig)) {
824
- config = mergeIfNotEmpty(
825
- config,
826
- symbolResultConfig
827
- );
828
- isConfigFromBacktest = true;
829
- }
830
- }
831
- }
832
- return { config, isConfigFromBacktest };
833
- };
834
-
835
- // src/strategyRuntime.ts
836
- import {
837
- BACKTEST_WARNING_CODES
838
- } from "@tradejs/types";
839
- var cloneWithPropertyDescriptors = (value) => Object.create(
840
- Object.getPrototypeOf(value),
841
- Object.getOwnPropertyDescriptors(value)
842
- );
843
- var buildExitOrderSignal = ({
844
- strategyName,
845
- symbol,
846
- decision
847
- }) => {
848
- if (!strategyName) {
849
- return void 0;
850
- }
851
- return {
852
- signalId: `${strategyName}:${symbol}:exit:${decision.closePlan.timestamp}`,
853
- strategy: strategyName,
854
- symbol,
855
- interval: "15",
856
- direction: decision.closePlan.direction,
857
- timestamp: decision.closePlan.timestamp,
858
- figures: {},
859
- indicators: {},
860
- prices: {
861
- currentPrice: decision.closePlan.price,
862
- takeProfitPrice: decision.closePlan.price,
863
- stopLossPrice: decision.closePlan.price,
864
- riskRatio: 0
865
- },
866
- additionalIndicators: {
867
- exit: {
868
- code: decision.code
869
- }
870
- }
871
- };
872
- };
873
- var resolveEntryRuntimePolicy = ({
874
- decision,
875
- config,
876
- manifest,
877
- policyProfile
878
- }) => {
879
- const baseDefaults = manifest?.entryRuntimeDefaults;
880
- const profileDefaults = policyProfile?.entryRuntimeDefaults;
881
- const manifestDefaults = baseDefaults || profileDefaults ? {
882
- ...baseDefaults,
883
- ...profileDefaults,
884
- ...baseDefaults?.ml || profileDefaults?.ml ? { ml: { ...baseDefaults?.ml, ...profileDefaults?.ml } } : {},
885
- ...baseDefaults?.ai || profileDefaults?.ai ? { ai: { ...baseDefaults?.ai, ...profileDefaults?.ai } } : {}
886
- } : void 0;
887
- const adapterMl = (policyProfile?.mlAdapter ?? manifest?.mlAdapter)?.mapEntryRuntimeFromConfig?.(config);
888
- const adapterAi = (policyProfile?.aiAdapter ?? manifest?.aiAdapter)?.mapEntryRuntimeFromConfig?.(config);
889
- const ml = manifestDefaults?.ml || adapterMl || decision.runtime?.ml ? {
890
- ...manifestDefaults?.ml,
891
- ...adapterMl,
892
- ...decision.runtime?.ml
893
- } : void 0;
894
- const ai = manifestDefaults?.ai || adapterAi || decision.runtime?.ai ? {
895
- ...manifestDefaults?.ai,
896
- ...adapterAi,
897
- ...decision.runtime?.ai
898
- } : void 0;
899
- return {
900
- ...manifestDefaults,
901
- ...decision.runtime,
902
- ml,
903
- ai
904
- };
905
- };
906
- var formatGateNumber = (value) => {
907
- if (!Number.isFinite(value)) {
908
- return String(value);
909
- }
910
- const normalized = Number(value.toFixed(6));
911
- return Number.isInteger(normalized) ? String(normalized) : String(normalized);
912
- };
913
- var isMlRuntimeGateEnabled = (params) => {
914
- const { env, ml } = params;
915
- return env !== "BACKTEST" && ml?.config != null && ml.config.enabled !== false;
916
- };
917
- var isMlResultUnavailable = (params) => {
918
- const { env, ml } = params;
919
- return isMlRuntimeGateEnabled({ env, ml }) && ml?.result == null;
920
- };
921
- var shouldExecuteEntryDecision = ({
922
- makeOrdersEnabled,
923
- env,
924
- signal,
925
- ml,
926
- aiEnabled,
927
- quality,
928
- minAiQuality
929
- }) => {
930
- if (!makeOrdersEnabled) {
931
- return false;
932
- }
933
- if (!signal || env === "BACKTEST") {
934
- return true;
935
- }
936
- if (isMlResultUnavailable({ env, ml })) {
937
- return false;
938
- }
939
- if (isMlRuntimeGateEnabled({ env, ml }) && ml?.result?.passed === false) {
940
- return false;
941
- }
942
- if (!aiEnabled) {
943
- return true;
944
- }
945
- return Number.isFinite(quality) && quality >= minAiQuality;
946
- };
947
- var getEntrySkipReason = ({
948
- makeOrdersEnabled,
949
- env,
950
- ml,
951
- aiEnabled,
952
- quality,
953
- minAiQuality
954
- }) => {
955
- if (!makeOrdersEnabled) {
956
- return "MAKE_ORDERS_DISABLED";
957
- }
958
- if (isMlResultUnavailable({ env, ml })) {
959
- return "ML_RESULT_UNAVAILABLE";
960
- }
961
- if (isMlRuntimeGateEnabled({ env, ml }) && ml?.result?.passed === false) {
962
- const probability = formatGateNumber(ml.result.probability);
963
- const threshold = formatGateNumber(ml.result.threshold);
964
- return `ML_THRESHOLD_NOT_MET (${probability} < ${threshold})`;
965
- }
966
- if (env !== "BACKTEST" && aiEnabled && quality == null) {
967
- return "AI_QUALITY_UNAVAILABLE";
968
- }
969
- if (env !== "BACKTEST" && aiEnabled && quality != null && Number.isFinite(quality) && quality < minAiQuality) {
970
- return `AI_QUALITY_BELOW_MIN (${quality} < ${minAiQuality})`;
971
- }
972
- return "ENTRY_POLICY_BLOCKED";
973
- };
974
- var resolveBacktestEntryDelayBars = (value) => {
975
- if (value == null || value === "") {
976
- return 1;
977
- }
978
- const parsed = parseInt(String(value), 10);
979
- return Number.isFinite(parsed) ? Math.max(0, parsed) : 1;
980
- };
981
- var resolveBacktestExecutionIntervalForPrimary = (interval) => {
982
- const normalized = String(interval ?? "15");
983
- if (normalized === "15") {
984
- return BACKTEST_EXECUTION_INTERVAL;
985
- }
986
- if (normalized === "60") {
987
- return "15";
988
- }
989
- return null;
990
- };
991
- var resolveBacktestExecutionDelayMs = (value, fallbackDelayMs) => {
992
- if (value == null || value === "") {
993
- return fallbackDelayMs;
994
- }
995
- const parsed = Number(value);
996
- return Number.isFinite(parsed) ? Math.max(0, Math.trunc(parsed)) : fallbackDelayMs;
997
- };
998
- var safeIntervalToMs = (interval) => {
999
- try {
1000
- return intervalToMs(interval);
1001
- } catch {
1002
- return null;
1003
- }
1004
- };
1005
- var buildCandleByTimestamp = (candles) => new Map(
1006
- (candles ?? []).filter((candle) => typeof candle?.timestamp === "number").map((candle) => [candle.timestamp, candle])
1007
- );
1008
- var buildBacktestExecutionOnlyCandle = (candle, executionPrice) => ({
1009
- ...candle,
1010
- open: executionPrice,
1011
- high: executionPrice,
1012
- low: executionPrice,
1013
- close: executionPrice,
1014
- volume: 0,
1015
- turnover: 0
1016
- });
1017
- var resolveInvalidDelayedEntryReason = ({
1018
- decision,
1019
- executionPrice,
1020
- takeProfitPrice,
1021
- stopLossPrice,
1022
- riskRatio
1023
- }) => {
1024
- if (!Number.isFinite(executionPrice) || !Number.isFinite(takeProfitPrice) || !Number.isFinite(stopLossPrice)) {
1025
- return "BACKTEST_DELAYED_ENTRY_INVALID_PRICE";
1026
- }
1027
- if (decision.entryContext.direction === "LONG") {
1028
- if (executionPrice <= stopLossPrice) {
1029
- return "BACKTEST_DELAYED_ENTRY_BEYOND_STOP";
1030
- }
1031
- if (executionPrice >= takeProfitPrice) {
1032
- return "BACKTEST_DELAYED_ENTRY_BEYOND_TAKE_PROFIT";
1033
- }
1034
- return !Number.isFinite(riskRatio) || riskRatio <= 0 ? "BACKTEST_DELAYED_ENTRY_INVALID_PRICE" : null;
1035
- }
1036
- if (executionPrice >= stopLossPrice) {
1037
- return "BACKTEST_DELAYED_ENTRY_BEYOND_STOP";
1038
- }
1039
- if (executionPrice <= takeProfitPrice) {
1040
- return "BACKTEST_DELAYED_ENTRY_BEYOND_TAKE_PROFIT";
1041
- }
1042
- return !Number.isFinite(riskRatio) || riskRatio <= 0 ? "BACKTEST_DELAYED_ENTRY_INVALID_PRICE" : null;
1043
- };
1044
- var applyBacktestDelayedEntryExecution = ({
1045
- decision,
1046
- execution,
1047
- backtestPriceMode,
1048
- delayBars
1049
- }) => {
1050
- const { candle, btcCandle } = execution;
1051
- const signalTimestamp = decision.signal?.timestamp ?? decision.entryContext.timestamp;
1052
- const signalPrice = decision.signal?.prices.currentPrice ?? decision.entryContext.prices.currentPrice;
1053
- const skipReason = execution.skipReason ?? (!candle || !btcCandle ? "BACKTEST_LOWER_EXECUTION_CANDLE_MISSING" : void 0);
1054
- if (skipReason || !candle || !btcCandle) {
1055
- if (decision.signal) {
1056
- decision.signal.additionalIndicators = {
1057
- ...decision.signal.additionalIndicators ?? {},
1058
- backtestExecution: {
1059
- entryDelayBars: delayBars,
1060
- priceMode: backtestPriceMode ?? "open",
1061
- signalTimestamp,
1062
- signalPrice,
1063
- executionSource: execution.source,
1064
- ...execution.executionInterval ? { executionInterval: execution.executionInterval } : {},
1065
- ...execution.executionDelayMs != null ? { executionDelayMs: execution.executionDelayMs } : {},
1066
- ...execution.primaryExecutionTimestamp != null ? { primaryExecutionTimestamp: execution.primaryExecutionTimestamp } : {},
1067
- ...execution.requestedExecutionTimestamp != null ? {
1068
- requestedExecutionTimestamp: execution.requestedExecutionTimestamp
1069
- } : {},
1070
- skipReason
1071
- }
1072
- };
1073
- decision.signal.orderStatus = "skipped";
1074
- decision.signal.orderSkipReason = skipReason;
1075
- }
1076
- return {
1077
- skipReason,
1078
- executionCandle: null,
1079
- btcExecutionCandle: null
1080
- };
1081
- }
1082
- const executionPrice = resolveBacktestExecutionPrice(
1083
- candle,
1084
- backtestPriceMode ?? "open"
1085
- );
1086
- const executionTimestamp = candle.timestamp;
1087
- const takeProfitPrice = decision.entryContext.prices.takeProfitPrice;
1088
- const stopLossPrice = decision.orderPlan.stopLossPrice;
1089
- const riskRatio = calculateRiskRatio({
1090
- direction: decision.entryContext.direction,
1091
- currentPrice: executionPrice,
1092
- takeProfitPrice,
1093
- stopLossPrice
1094
- });
1095
- const invalidSkipReason = resolveInvalidDelayedEntryReason({
1096
- decision,
1097
- executionPrice,
1098
- takeProfitPrice,
1099
- stopLossPrice,
1100
- riskRatio
1101
- });
1102
- decision.entryContext = {
1103
- ...decision.entryContext,
1104
- timestamp: executionTimestamp,
1105
- prices: {
1106
- ...decision.entryContext.prices,
1107
- currentPrice: executionPrice,
1108
- stopLossPrice,
1109
- riskRatio
1110
- }
1111
- };
1112
- const executionResult = {
1113
- skipReason: invalidSkipReason,
1114
- executionCandle: buildBacktestExecutionOnlyCandle(candle, executionPrice),
1115
- btcExecutionCandle: buildBacktestExecutionOnlyCandle(
1116
- btcCandle,
1117
- resolveBacktestExecutionPrice(btcCandle, backtestPriceMode ?? "open")
1118
- )
1119
- };
1120
- if (!decision.signal) {
1121
- return executionResult;
1122
- }
1123
- decision.signal.prices = {
1124
- ...decision.signal.prices,
1125
- currentPrice: executionPrice,
1126
- stopLossPrice,
1127
- riskRatio
1128
- };
1129
- decision.signal.additionalIndicators = {
1130
- ...decision.signal.additionalIndicators ?? {},
1131
- backtestExecution: {
1132
- entryDelayBars: delayBars,
1133
- priceMode: backtestPriceMode ?? "open",
1134
- signalTimestamp,
1135
- signalPrice,
1136
- executionTimestamp,
1137
- executionPrice,
1138
- executionSource: execution.source,
1139
- ...execution.executionInterval ? { executionInterval: execution.executionInterval } : {},
1140
- ...execution.executionDelayMs != null ? { executionDelayMs: execution.executionDelayMs } : {},
1141
- ...execution.primaryExecutionTimestamp != null ? { primaryExecutionTimestamp: execution.primaryExecutionTimestamp } : {},
1142
- ...execution.requestedExecutionTimestamp != null ? { requestedExecutionTimestamp: execution.requestedExecutionTimestamp } : {},
1143
- ...invalidSkipReason ? { skipReason: invalidSkipReason } : {}
1144
- }
1145
- };
1146
- if (invalidSkipReason) {
1147
- decision.signal.orderStatus = "skipped";
1148
- decision.signal.orderSkipReason = invalidSkipReason;
1149
- }
1150
- return executionResult;
1151
- };
1152
- var normalizeConfigHookList = (value) => {
1153
- if (Array.isArray(value)) {
1154
- return value;
1155
- }
1156
- return value ? [value] : [];
1157
- };
1158
- var isStrategyDecision = (value) => {
1159
- if (!value || typeof value !== "object") {
1160
- return false;
1161
- }
1162
- const kind = value.kind;
1163
- return kind === "skip" || kind === "entry" || kind === "exit" || kind === "protect";
1164
- };
1165
- var CONFIG_HOOK_STAGES = [
1166
- "onInit",
1167
- "onBar",
1168
- "afterCoreDecision",
1169
- "afterBarDecision",
1170
- "onSkip",
1171
- "beforeClosePosition",
1172
- "afterEnrichMl",
1173
- "afterEnrichAi",
1174
- "beforeEntryGate",
1175
- "beforePlaceOrder",
1176
- "afterPlaceOrder"
1177
- ];
1178
- var isConfigHookStage = (stage) => CONFIG_HOOK_STAGES.includes(
1179
- stage
1180
- );
1181
- var buildHookCtx = ({
1182
- connector,
1183
- strategyName,
1184
- userName,
1185
- symbol,
1186
- universe,
1187
- assetClass,
1188
- accountId,
1189
- deploymentId,
1190
- policyProfileId,
1191
- strategyConfig,
1192
- env,
1193
- isConfigFromBacktest
1194
- }) => ({
1195
- connector,
1196
- strategyName,
1197
- userName,
1198
- symbol,
1199
- ...universe ? { universe } : {},
1200
- ...assetClass ? { assetClass } : {},
1201
- ...accountId ? { accountId } : {},
1202
- ...deploymentId ? { deploymentId } : {},
1203
- ...policyProfileId ? { policyProfileId } : {},
1204
- strategyConfig,
1205
- env,
1206
- isConfigFromBacktest
1207
- });
1208
- var buildHookEntry = ({
1209
- decision,
1210
- runtime
1211
- }) => ({
1212
- context: decision.entryContext,
1213
- orderPlan: decision.orderPlan,
1214
- signal: decision.signal,
1215
- runtime: {
1216
- raw: decision.runtime,
1217
- resolved: runtime
1218
- }
1219
- });
1220
- var buildHookPolicy = ({
1221
- quality,
1222
- makeOrdersEnabled,
1223
- minAiQuality
1224
- }) => ({
1225
- aiQuality: quality,
1226
- makeOrdersEnabled,
1227
- minAiQuality
1228
- });
1229
- var shouldRecordRuntimeJournal = ({
1230
- env,
1231
- config
1232
- }) => env !== "BACKTEST" && env !== "PARITY" && config.RECORD_RUNTIME_TRADES !== false;
1233
- var isTestConnector = (connector) => Boolean(
1234
- connector.__tradejsTestConnector
1235
- );
1236
- var canUseSharedReplayState = ({
1237
- env,
1238
- sharedReplayKey
1239
- }) => (env === "BACKTEST" || env === "PARITY") && Boolean(sharedReplayKey);
1240
- var buildMlHookContext = ({
1241
- signal,
1242
- env,
1243
- ml
1244
- }) => {
1245
- if (env === "BACKTEST") {
1246
- return {
1247
- config: ml,
1248
- attempted: false,
1249
- applied: false,
1250
- skippedReason: "BACKTEST"
1251
- };
1252
- }
1253
- if (!ml) {
1254
- return {
1255
- attempted: false,
1256
- applied: false,
1257
- skippedReason: "NO_RUNTIME"
1258
- };
1259
- }
1260
- if (ml.enabled === false) {
1261
- return {
1262
- config: ml,
1263
- attempted: false,
1264
- applied: false,
1265
- skippedReason: "DISABLED"
1266
- };
1267
- }
1268
- if (!ml.strategyConfig) {
1269
- return {
1270
- config: ml,
1271
- attempted: false,
1272
- applied: false,
1273
- skippedReason: "NO_STRATEGY_CONFIG"
1274
- };
1275
- }
1276
- if (typeof ml.mlThreshold !== "number") {
1277
- return {
1278
- config: ml,
1279
- attempted: false,
1280
- applied: false,
1281
- skippedReason: "NO_THRESHOLD"
1282
- };
1283
- }
1284
- if (signal.ml) {
1285
- return {
1286
- config: ml,
1287
- attempted: true,
1288
- applied: true,
1289
- result: signal.ml
1290
- };
1291
- }
1292
- return {
1293
- config: ml,
1294
- attempted: true,
1295
- applied: false,
1296
- skippedReason: "NO_RESULT"
1297
- };
1298
- };
1299
- var buildAiHookContext = ({
1300
- env,
1301
- ai,
1302
- quality
1303
- }) => {
1304
- if (env === "BACKTEST") {
1305
- return {
1306
- config: ai,
1307
- attempted: false,
1308
- applied: false,
1309
- skippedReason: "BACKTEST"
1310
- };
1311
- }
1312
- if (!ai) {
1313
- return {
1314
- attempted: false,
1315
- applied: false,
1316
- skippedReason: "NO_RUNTIME"
1317
- };
1318
- }
1319
- if (ai.enabled === false) {
1320
- return {
1321
- config: ai,
1322
- attempted: false,
1323
- applied: false,
1324
- skippedReason: "DISABLED"
1325
- };
1326
- }
1327
- if (typeof quality === "number") {
1328
- return {
1329
- config: ai,
1330
- attempted: true,
1331
- applied: true,
1332
- quality
1333
- };
1334
- }
1335
- return {
1336
- config: ai,
1337
- attempted: true,
1338
- applied: false,
1339
- skippedReason: "NO_QUALITY"
1340
- };
1341
- };
1342
- var handleExitDecision = async ({
1343
- connector,
1344
- userName,
1345
- strategyName,
1346
- symbol,
1347
- decision,
1348
- market,
1349
- onRuntimeClose,
1350
- onRuntimeError
1351
- }) => {
1352
- try {
1353
- let activeTradeForClose = null;
1354
- if (userName) {
1355
- const activeTrade = await getActiveRuntimeTrade({
1356
- userName,
1357
- symbol,
1358
- accountId: connector.accountId,
1359
- deploymentId: connector.deploymentId
1360
- });
1361
- if (!activeTrade) {
1362
- logger3.warn(
1363
- "[%s] blocked closePosition for untracked runtime position: %s",
1364
- strategyName ?? "unknown",
1365
- symbol
1366
- );
1367
- return "CLOSE_BLOCKED_BY_UNTRACKED_POSITION";
1368
- }
1369
- if (!strategyName || activeTrade.strategy !== strategyName) {
1370
- logger3.warn(
1371
- "[%s] blocked closePosition for foreign runtime position: %s ownedBy=%s",
1372
- strategyName ?? "unknown",
1373
- symbol,
1374
- activeTrade.strategy
1375
- );
1376
- return "CLOSE_BLOCKED_BY_FOREIGN_STRATEGY_POSITION";
1377
- }
1378
- activeTradeForClose = activeTrade;
1379
- }
1380
- await connector.closePosition({
1381
- symbol,
1382
- price: decision.closePlan.price,
1383
- timestamp: decision.closePlan.timestamp,
1384
- direction: decision.closePlan.direction,
1385
- signal: buildExitOrderSignal({
1386
- strategyName,
1387
- symbol,
1388
- decision
1389
- })
1390
- });
1391
- const closedTrade = await markRuntimeTradeClosed({
1392
- userName,
1393
- strategy: strategyName,
1394
- symbol,
1395
- exitPrice: decision.closePlan.price,
1396
- exitTimestamp: decision.closePlan.timestamp,
1397
- exitType: "exit",
1398
- accountId: connector.accountId,
1399
- deploymentId: connector.deploymentId
1400
- });
1401
- const trade = closedTrade ?? activeTradeForClose;
1402
- if (trade && strategyName) {
1403
- try {
1404
- onRuntimeClose?.({
1405
- userName,
1406
- strategy: strategyName,
1407
- openedByStrategy: trade.strategy,
1408
- symbol,
1409
- direction: trade.direction,
1410
- code: decision.code,
1411
- orderId: trade.orderId,
1412
- signalId: trade.signalId,
1413
- qty: trade.qty,
1414
- entryPrice: trade.entryPrice,
1415
- entryTimestamp: trade.entryTimestamp,
1416
- exitPrice: closedTrade?.exitPrice ?? decision.closePlan.price,
1417
- exitTimestamp: closedTrade?.exitTimestamp ?? decision.closePlan.timestamp,
1418
- closedPnl: closedTrade?.closedPnl ?? trade.closedPnl ?? null,
1419
- exitType: closedTrade?.exitType ?? "exit"
1420
- });
1421
- } catch (notificationError) {
1422
- logger3.error(
1423
- "runtime close notification error: %s %s",
1424
- symbol,
1425
- notificationError
1426
- );
1427
- }
1428
- }
1429
- } catch (err) {
1430
- await onRuntimeError?.({
1431
- stage: "closePosition",
1432
- error: err,
1433
- decision,
1434
- market
1435
- });
1436
- logger3.error("close order error: %s %s", symbol, err);
1437
- return "ORDER_ERROR";
1438
- }
1439
- return decision.code;
1440
- };
1441
- var handleProtectDecision = async ({
1442
- connector,
1443
- symbol,
1444
- decision,
1445
- market,
1446
- onRuntimeError
1447
- }) => {
1448
- try {
1449
- await updatePositionProtection({
1450
- connector,
1451
- symbol,
1452
- direction: decision.protectPlan.direction,
1453
- takeProfits: decision.protectPlan.takeProfits ?? [],
1454
- stopLossPrice: decision.protectPlan.stopLossPrice ?? null
1455
- });
1456
- } catch (err) {
1457
- await onRuntimeError?.({
1458
- stage: "protectPosition",
1459
- error: err,
1460
- decision,
1461
- market
1462
- });
1463
- logger3.error("protect position error: %s %s", symbol, err);
1464
- return "ORDER_ERROR";
1465
- }
1466
- return decision.code;
1467
- };
1468
- var executeEntryDecision = async ({
1469
- connector,
1470
- symbol,
1471
- decision,
1472
- runtime,
1473
- manifest,
1474
- hookCtx,
1475
- market,
1476
- entry,
1477
- policy,
1478
- ml,
1479
- ai,
1480
- recordRuntimeJournal,
1481
- invokeStageHooks,
1482
- notifyRuntimeError
1483
- }) => {
1484
- const signal = decision.signal;
1485
- const beforePlaceOrder = async () => {
1486
- await invokeStageHooks(
1487
- "beforePlaceOrder",
1488
- manifest?.hooks?.beforePlaceOrder,
1489
- {
1490
- ctx: hookCtx,
1491
- market,
1492
- decision,
1493
- entry,
1494
- policy,
1495
- ml,
1496
- ai
1497
- },
1498
- { decision, entry, market }
1499
- );
1500
- try {
1501
- await runtime.beforePlaceOrder?.();
1502
- } catch (error) {
1503
- await notifyRuntimeError({
1504
- stage: "runtime.beforePlaceOrder",
1505
- error,
1506
- decision,
1507
- entry,
1508
- market
1509
- });
1510
- throw error;
1511
- }
1512
- };
1513
- try {
1514
- if (signal) {
1515
- await executeEntryOrder({
1516
- connector,
1517
- userName: hookCtx.userName,
1518
- symbol,
1519
- direction: decision.entryContext.direction,
1520
- qty: decision.orderPlan.qty,
1521
- currentPrice: decision.entryContext.prices.currentPrice,
1522
- timestamp: decision.entryContext.timestamp,
1523
- takeProfits: decision.orderPlan.takeProfits,
1524
- stopLossPrice: decision.orderPlan.stopLossPrice,
1525
- positionIntent: decision.orderPlan.positionIntent,
1526
- ...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
1527
- signal,
1528
- beforePlaceOrder,
1529
- recordRuntimeTrade: recordRuntimeJournal
1530
- });
1531
- await invokeStageHooks(
1532
- "afterPlaceOrder",
1533
- manifest?.hooks?.afterPlaceOrder,
1534
- {
1535
- ctx: hookCtx,
1536
- market,
1537
- decision,
1538
- entry,
1539
- policy,
1540
- ml,
1541
- ai,
1542
- order: {
1543
- result: signal
1544
- }
1545
- },
1546
- { decision, entry, market }
1547
- );
1548
- return signal;
1549
- }
1550
- await beforePlaceOrder();
1551
- const arrivalSnapshot = await getOrderArrivalSnapshot({
1552
- connector,
1553
- symbol
1554
- });
1555
- validateEntryProtectionAtArrival({
1556
- direction: decision.entryContext.direction,
1557
- signalPrice: decision.entryContext.prices.currentPrice,
1558
- bid: arrivalSnapshot.bid,
1559
- ask: arrivalSnapshot.ask,
1560
- arrivalMid: arrivalSnapshot.arrivalMid,
1561
- takeProfits: decision.orderPlan.takeProfits,
1562
- stopLossPrice: decision.orderPlan.stopLossPrice
1563
- });
1564
- const orderPlaced = await connector.placeOrder({
1565
- symbol,
1566
- qty: decision.orderPlan.qty,
1567
- price: decision.entryContext.prices.currentPrice,
1568
- timestamp: decision.entryContext.timestamp,
1569
- direction: decision.entryContext.direction,
1570
- positionIntent: decision.orderPlan.positionIntent,
1571
- ...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {}
1572
- });
1573
- if (!orderPlaced) {
1574
- throw new Error("PLACE_ORDER_FAILED");
1575
- }
1576
- try {
1577
- await updatePositionProtection({
1578
- connector,
1579
- symbol,
1580
- direction: decision.entryContext.direction,
1581
- qty: decision.orderPlan.qty,
1582
- takeProfits: decision.orderPlan.takeProfits,
1583
- stopLossPrice: decision.orderPlan.stopLossPrice
1584
- });
1585
- } catch (error) {
1586
- await connector.closePosition({
1587
- symbol,
1588
- price: decision.entryContext.prices.currentPrice,
1589
- timestamp: decision.entryContext.timestamp,
1590
- direction: decision.entryContext.direction
1591
- });
1592
- throw error;
1593
- }
1594
- await invokeStageHooks(
1595
- "afterPlaceOrder",
1596
- manifest?.hooks?.afterPlaceOrder,
1597
- {
1598
- ctx: hookCtx,
1599
- market,
1600
- decision,
1601
- entry,
1602
- policy,
1603
- ml,
1604
- ai,
1605
- order: {
1606
- result: decision.code
1607
- }
1608
- },
1609
- { decision, entry, market }
1610
- );
1611
- } catch (err) {
1612
- if (signal) {
1613
- signal.orderStatus = "failed";
1614
- if (typeof signal.orderFailureReason !== "string" || !signal.orderFailureReason.trim()) {
1615
- signal.orderFailureReason = typeof err?.message === "string" && err.message.trim() ? err.message.trim() : void 0;
1616
- }
1617
- }
1618
- await notifyRuntimeError({
1619
- stage: "placeOrder",
1620
- error: err,
1621
- decision,
1622
- entry,
1623
- market
1624
- });
1625
- if (err?.message === BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
1626
- logger3.warn("order warning: %s %s", symbol, err);
1627
- } else {
1628
- logger3.error("order error: %s %s", symbol, err);
1629
- }
1630
- return signal ?? "ORDER_ERROR";
1631
- }
1632
- return signal ?? decision.code;
1633
- };
1634
- var createStrategyRuntime = ({
1635
- strategyName,
1636
- defaults,
1637
- createCore,
1638
- manifest: staticManifest,
1639
- strategyDirectory,
1640
- detectorKey,
1641
- detectorNoSignalSkipReason
1642
- }) => {
1643
- const projectRoot = getTradejsProjectCwd();
1644
- const resolveManifest = (name) => {
1645
- if (!name) {
1646
- return void 0;
1647
- }
1648
- if (staticManifest?.name === name) {
1649
- return staticManifest;
1650
- }
1651
- return getStrategyManifest(name, projectRoot);
1652
- };
1653
- const loadPineScriptFile = createPineScriptLoader(
1654
- strategyDirectory ? path.resolve(strategyDirectory) : path.resolve(
1655
- projectRoot,
1656
- "packages",
1657
- "strategies",
1658
- "src",
1659
- strategyName
1660
- )
1661
- );
1662
- const creator = async ({
1663
- userName,
1664
- connectorName,
1665
- config: baseConfig,
1666
- symbol,
1667
- universe: requestedUniverse,
1668
- assetClass,
1669
- accountId: requestedAccountId,
1670
- deploymentId: requestedDeploymentId,
1671
- policyProfileId,
1672
- runtimeConfigId,
1673
- runtimeConfigSnapshot,
1674
- data,
1675
- btcData,
1676
- ethData = [],
1677
- btcBinanceData,
1678
- btcCoinbaseData,
1679
- backtestExecutionMarketData,
1680
- connector,
1681
- sharedIndicatorsReplayKey,
1682
- sharedStrategyStateKey,
1683
- onRuntimeClose
1684
- }) => {
1685
- const { config, isConfigFromBacktest } = await resolveStrategyConfig({
1686
- strategyName,
1687
- userName,
1688
- symbol,
1689
- baseConfig,
1690
- defaults,
1691
- runtimeConfigId,
1692
- runtimeConfigSnapshot
1693
- });
1694
- const universe = requestedUniverse ?? connector.universe;
1695
- const accountId = requestedAccountId ?? connector.accountId;
1696
- const deploymentId = requestedDeploymentId ?? connector.deploymentId;
1697
- const projectConfig = await loadTradejsConfig(projectRoot);
1698
- const projectHooks = projectConfig.hooks;
1699
- const env = String(config.ENV ?? "BACKTEST");
1700
- const backtestPriceMode = config.BACKTEST_PRICE_MODE ?? "open";
1701
- const backtestEntryDelayBars = env === "BACKTEST" ? resolveBacktestEntryDelayBars(config.BACKTEST_ENTRY_DELAY_BARS) : 0;
1702
- const resolvedBacktestExecutionInterval = config.BACKTEST_EXECUTION_INTERVAL ?? backtestExecutionMarketData?.interval ?? resolveBacktestExecutionIntervalForPrimary(config.INTERVAL ?? "15");
1703
- const backtestExecutionInterval = resolvedBacktestExecutionInterval == null ? null : String(resolvedBacktestExecutionInterval);
1704
- const backtestExecutionIntervalLabel = backtestExecutionInterval == null ? void 0 : String(backtestExecutionInterval);
1705
- const primaryIntervalMs = safeIntervalToMs(config.INTERVAL ?? "15");
1706
- const backtestExecutionIntervalMs = safeIntervalToMs(
1707
- backtestExecutionInterval
1708
- );
1709
- const backtestExecutionDelayMs = resolveBacktestExecutionDelayMs(
1710
- config.BACKTEST_EXECUTION_DELAY_MS,
1711
- backtestExecutionIntervalMs ?? BACKTEST_EXECUTION_DELAY_MS
1712
- );
1713
- const backtestExecutionCandleByTimestamp = backtestExecutionMarketData?.dataByTimestamp ?? buildCandleByTimestamp(backtestExecutionMarketData?.data);
1714
- const backtestExecutionBtcCandleByTimestamp = backtestExecutionMarketData?.btcDataByTimestamp ?? buildCandleByTimestamp(backtestExecutionMarketData?.btcData);
1715
- const canUseLowerBacktestExecution = env === "BACKTEST" && backtestEntryDelayBars > 0 && backtestExecutionIntervalMs != null && primaryIntervalMs != null && backtestExecutionIntervalMs < primaryIntervalMs;
1716
- const recordRuntimeJournal = shouldRecordRuntimeJournal({ env, config });
1717
- const strategyManifest = resolveManifest(strategyName);
1718
- const requestedPolicyProfileId = policyProfileId ?? (typeof config.POLICY_PROFILE_ID === "string" ? config.POLICY_PROFILE_ID : void 0);
1719
- const getPolicyProfile = (name = strategyName) => resolveStrategyPolicyProfile(resolveManifest(name), {
1720
- profileId: requestedPolicyProfileId,
1721
- universe,
1722
- assetClass
1723
- });
1724
- const strategyPolicyProfile = getPolicyProfile();
1725
- const indicatorPeriods = buildDefaultIndicatorPeriods(config);
1726
- const hookBase = {
1727
- connector,
1728
- strategyName,
1729
- userName,
1730
- symbol,
1731
- universe,
1732
- assetClass,
1733
- accountId,
1734
- deploymentId,
1735
- policyProfileId: strategyPolicyProfile?.id ?? requestedPolicyProfileId,
1736
- strategyConfig: config,
1737
- env,
1738
- isConfigFromBacktest
1739
- };
1740
- const getHookCtx = (name = strategyName) => {
1741
- const profile = getPolicyProfile(name);
1742
- return buildHookCtx({
1743
- ...hookBase,
1744
- strategyName: name,
1745
- policyProfileId: profile?.id ?? requestedPolicyProfileId
1746
- });
1747
- };
1748
- const getProjectHookList = (stage) => normalizeConfigHookList(projectHooks?.[stage]);
1749
- const indicatorReplayKey = JSON.stringify({
1750
- periods: indicatorPeriods,
1751
- universe
1752
- });
1753
- const sharedReplayEnabled = canUseSharedReplayState({
1754
- env,
1755
- sharedReplayKey: sharedIndicatorsReplayKey
1756
- });
1757
- const indicatorSharedReplayKey = sharedReplayEnabled && sharedIndicatorsReplayKey ? `${sharedIndicatorsReplayKey}:indicators:${indicatorReplayKey}` : void 0;
1758
- const strategyStateBaseKey = env === "CRON" && sharedStrategyStateKey ? sharedStrategyStateKey : sharedReplayEnabled ? sharedIndicatorsReplayKey : void 0;
1759
- const strategySharedReplayKey = strategyStateBaseKey ? `${strategyStateBaseKey}:strategy:${strategyName}` : void 0;
1760
- const notifyRuntimeError = async ({
1761
- stage,
1762
- error,
1763
- decision,
1764
- entry,
1765
- market
1766
- }) => {
1767
- const errorStrategyName = decision?.kind === "entry" ? decision.entryContext.strategy : strategyName;
1768
- const errorManifest = resolveManifest(errorStrategyName) ?? strategyManifest;
1769
- const errorParams = {
1770
- ctx: getHookCtx(errorStrategyName),
1771
- market,
1772
- decision,
1773
- entry,
1774
- error: {
1775
- stage,
1776
- cause: error
1777
- }
1778
- };
1779
- for (const projectHook of getProjectHookList("onRuntimeError")) {
1780
- try {
1781
- await projectHook(errorParams);
1782
- } catch (hookError) {
1783
- logger3.error(
1784
- "project hook onRuntimeError failed: %s %s",
1785
- strategyName,
1786
- hookError
1787
- );
1788
- }
1789
- }
1790
- const onRuntimeError = errorManifest?.hooks?.onRuntimeError;
1791
- if (!onRuntimeError) {
1792
- return;
1793
- }
1794
- try {
1795
- await onRuntimeError(errorParams);
1796
- } catch (hookError) {
1797
- logger3.error(
1798
- "runtime hook onRuntimeError failed: %s %s",
1799
- strategyName,
1800
- hookError
1801
- );
1802
- }
1803
- };
1804
- const invokeHook = async (stage, hook, params, errorContext = {}) => {
1805
- if (!hook) {
1806
- return void 0;
1807
- }
1808
- try {
1809
- return await hook(params);
1810
- } catch (error) {
1811
- logger3.error(
1812
- 'strategy hook "%s" failed for %s: %s',
1813
- stage,
1814
- strategyName,
1815
- error
1816
- );
1817
- await notifyRuntimeError({
1818
- stage,
1819
- error,
1820
- decision: errorContext.decision,
1821
- entry: errorContext.entry,
1822
- market: errorContext.market
1823
- });
1824
- return void 0;
1825
- }
1826
- };
1827
- const invokeProjectHooks = async (stage, params, errorContext = {}) => {
1828
- const results = [];
1829
- for (const hook of getProjectHookList(stage)) {
1830
- const result = await invokeHook(
1831
- stage,
1832
- hook,
1833
- params,
1834
- errorContext
1835
- );
1836
- if (result !== void 0) {
1837
- results.push(result);
1838
- }
1839
- }
1840
- return results;
1841
- };
1842
- const invokeStageHooks = async (stage, hook, params, errorContext = {}) => {
1843
- if (isConfigHookStage(stage)) {
1844
- await invokeProjectHooks(stage, params, errorContext);
1845
- }
1846
- return invokeHook(stage, hook, params, errorContext);
1847
- };
1848
- const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
1849
- const projectResults = await invokeProjectHooks(
1850
- stage,
1851
- params,
1852
- errorContext
1853
- );
1854
- const projectBlock = projectResults.find(
1855
- (result) => result?.allow === false
1856
- );
1857
- if (projectBlock?.allow === false) {
1858
- return projectBlock;
1859
- }
1860
- return invokeHook(
1861
- stage,
1862
- hook,
1863
- params,
1864
- errorContext
1865
- );
1866
- };
1867
- const applyProjectAfterCoreDecisionHooks = async ({
1868
- hookCtx,
1869
- market,
1870
- decision
1871
- }) => {
1872
- let nextDecision = decision;
1873
- for (const hook of getProjectHookList(
1874
- "afterCoreDecision"
1875
- )) {
1876
- const result = await invokeHook(
1877
- "afterCoreDecision",
1878
- hook,
1879
- {
1880
- ctx: hookCtx,
1881
- market,
1882
- decision: nextDecision
1883
- },
1884
- {
1885
- decision: nextDecision,
1886
- market
1887
- }
1888
- );
1889
- if (isStrategyDecision(result)) {
1890
- nextDecision = result;
1891
- }
1892
- }
1893
- return nextDecision;
1894
- };
1895
- const applyProjectAfterBarDecisionHooks = async ({
1896
- hookCtx,
1897
- market,
1898
- decision
1899
- }) => {
1900
- let nextDecision = decision;
1901
- for (const hook of getProjectHookList(
1902
- "afterBarDecision"
1903
- )) {
1904
- const result = await invokeHook(
1905
- "afterBarDecision",
1906
- hook,
1907
- {
1908
- ctx: hookCtx,
1909
- market,
1910
- decision: nextDecision
1911
- },
1912
- {
1913
- decision: nextDecision,
1914
- market
1915
- }
1916
- );
1917
- if (isStrategyDecision(result)) {
1918
- nextDecision = result;
1919
- }
1920
- }
1921
- return nextDecision;
1922
- };
1923
- const applyProjectOnBarHooks = async ({
1924
- hookCtx,
1925
- market
1926
- }) => {
1927
- for (const hook of getProjectHookList(
1928
- "onBar"
1929
- )) {
1930
- const result = await invokeHook(
1931
- "onBar",
1932
- hook,
1933
- {
1934
- ctx: hookCtx,
1935
- market
1936
- },
1937
- {
1938
- market
1939
- }
1940
- );
1941
- if (isStrategyDecision(result)) {
1942
- return result;
1943
- }
1944
- }
1945
- return void 0;
1946
- };
1947
- const indicatorsState = createStrategyIndicatorsState({
1948
- env,
1949
- data,
1950
- btcData,
1951
- ethData,
1952
- btcBinanceData,
1953
- btcCoinbaseData,
1954
- periods: indicatorPeriods,
1955
- pluginRegistryScope: projectRoot,
1956
- sharedReplayKey: indicatorSharedReplayKey,
1957
- useBtcReference: universe === "crypto"
1958
- });
1959
- const coreContextRequirements = new Set(
1960
- strategyManifest?.contextRequirements?.core ?? []
1961
- );
1962
- const strategyApi = createStrategyAPI({
1963
- strategy: strategyName,
1964
- symbol,
1965
- interval: config.INTERVAL ?? "15",
1966
- env,
1967
- connector,
1968
- cachedData: data,
1969
- indicatorsState,
1970
- isConfigFromBacktest,
1971
- sharedReplayKey: strategySharedReplayKey,
1972
- getSharedReplayState: getSharedStrategyReplayState,
1973
- loadDecisionBaseContext: async ({
1974
- baseContext,
1975
- candle,
1976
- symbol: decisionSymbol,
1977
- interval: decisionInterval
1978
- }) => {
1979
- if (!baseContext) return void 0;
1980
- if (!coreContextRequirements.has("hyperliquidWhales")) {
1981
- return baseContext;
1982
- }
1983
- const hyperliquidWhales = await loadHyperliquidWhaleFlowContext({
1984
- symbol: decisionSymbol,
1985
- interval: decisionInterval,
1986
- timestamp: candle.timestamp,
1987
- env,
1988
- useSeriesCache: env === "BACKTEST" || env === "PARITY"
1989
- });
1990
- if (!hyperliquidWhales) return baseContext;
1991
- const participation = cloneWithPropertyDescriptors(
1992
- baseContext.participation
1993
- );
1994
- Object.defineProperty(participation, "hyperliquidWhales", {
1995
- configurable: true,
1996
- enumerable: true,
1997
- value: hyperliquidWhales,
1998
- writable: true
1999
- });
2000
- const enrichedBaseContext = cloneWithPropertyDescriptors(baseContext);
2001
- Object.defineProperty(enrichedBaseContext, "participation", {
2002
- configurable: true,
2003
- enumerable: true,
2004
- value: participation,
2005
- writable: true
2006
- });
2007
- return enrichedBaseContext;
2008
- }
2009
- });
2010
- const core = await createCore({
2011
- userName,
2012
- symbol,
2013
- config,
2014
- isConfigFromBacktest,
2015
- connector,
2016
- data,
2017
- btcData,
2018
- ethData,
2019
- loadPineScriptFile,
2020
- strategyApi,
2021
- indicatorsState,
2022
- sharedReplayKey: strategySharedReplayKey,
2023
- getSharedReplayState: getSharedStrategyReplayState
2024
- });
2025
- await invokeStageHooks("onInit", strategyManifest?.hooks?.onInit, {
2026
- ctx: getHookCtx(),
2027
- market: {
2028
- data,
2029
- btcData
2030
- }
2031
- });
2032
- const appendCurrentMarketData = (candle, btcCandle, ethCandle) => {
2033
- if (data[data.length - 1]?.timestamp !== candle.timestamp) {
2034
- data.push(candle);
2035
- }
2036
- if (universe === "crypto" && btcData[btcData.length - 1]?.timestamp !== btcCandle.timestamp) {
2037
- btcData.push(btcCandle);
2038
- }
2039
- if (universe === "crypto" && ethCandle && ethData[ethData.length - 1]?.timestamp !== ethCandle.timestamp) {
2040
- ethData.push(ethCandle);
2041
- }
2042
- };
2043
- const resolveEthCandle = (candle, ethCandle) => {
2044
- if (ethCandle?.timestamp === candle.timestamp) {
2045
- return ethCandle;
2046
- }
2047
- const alignedEthCandle = ethData[data.length - 1];
2048
- if (alignedEthCandle?.timestamp === candle.timestamp) {
2049
- return alignedEthCandle;
2050
- }
2051
- const latestEthCandle = ethData[ethData.length - 1];
2052
- if (latestEthCandle?.timestamp === candle.timestamp) {
2053
- return latestEthCandle;
2054
- }
2055
- return void 0;
2056
- };
2057
- const resolveBacktestExecutionCandle = (candle, btcCandle) => {
2058
- if (!BACKTEST_LOWER_TIMEFRAME_EXECUTION_ENABLED) {
2059
- return {
2060
- candle,
2061
- btcCandle,
2062
- source: "primary_timeframe",
2063
- requestedExecutionTimestamp: candle.timestamp,
2064
- executionInterval: String(config.INTERVAL ?? "15"),
2065
- executionDelayMs: 0,
2066
- primaryExecutionTimestamp: candle.timestamp
2067
- };
2068
- }
2069
- const requestedExecutionTimestamp = candle.timestamp + backtestExecutionDelayMs;
2070
- const primaryExecutionTimestamp = candle.timestamp;
2071
- if (!canUseLowerBacktestExecution || primaryIntervalMs == null) {
2072
- return {
2073
- source: "lower_timeframe",
2074
- requestedExecutionTimestamp,
2075
- executionInterval: backtestExecutionIntervalLabel,
2076
- executionDelayMs: backtestExecutionDelayMs,
2077
- primaryExecutionTimestamp,
2078
- skipReason: "BACKTEST_LOWER_EXECUTION_UNAVAILABLE"
2079
- };
2080
- }
2081
- if (requestedExecutionTimestamp >= candle.timestamp + primaryIntervalMs) {
2082
- return {
2083
- source: "lower_timeframe",
2084
- requestedExecutionTimestamp,
2085
- executionInterval: backtestExecutionIntervalLabel,
2086
- executionDelayMs: backtestExecutionDelayMs,
2087
- primaryExecutionTimestamp,
2088
- skipReason: "BACKTEST_LOWER_EXECUTION_DELAY_OUT_OF_BAR"
2089
- };
2090
- }
2091
- const lowerCandle = backtestExecutionCandleByTimestamp.get(
2092
- requestedExecutionTimestamp
2093
- );
2094
- const lowerBtcCandle = backtestExecutionBtcCandleByTimestamp.get(
2095
- requestedExecutionTimestamp
2096
- );
2097
- if (lowerCandle && lowerBtcCandle) {
2098
- return {
2099
- candle: lowerCandle,
2100
- btcCandle: lowerBtcCandle,
2101
- source: "lower_timeframe",
2102
- requestedExecutionTimestamp,
2103
- executionInterval: backtestExecutionIntervalLabel,
2104
- executionDelayMs: backtestExecutionDelayMs,
2105
- primaryExecutionTimestamp
2106
- };
2107
- }
2108
- return {
2109
- source: "lower_timeframe",
2110
- requestedExecutionTimestamp,
2111
- executionInterval: backtestExecutionIntervalLabel,
2112
- executionDelayMs: backtestExecutionDelayMs,
2113
- primaryExecutionTimestamp,
2114
- skipReason: !lowerCandle ? "BACKTEST_LOWER_EXECUTION_CANDLE_MISSING" : "BACKTEST_LOWER_EXECUTION_BTC_CANDLE_MISSING"
2115
- };
2116
- };
2117
- let pendingBacktestEntry = null;
2118
- const flushPendingBacktestEntry = async (candle, btcCandle, ethCandle) => {
2119
- if (!pendingBacktestEntry) {
2120
- return void 0;
2121
- }
2122
- appendCurrentMarketData(candle, btcCandle, ethCandle);
2123
- const resolvedEthCandle = resolveEthCandle(candle, ethCandle);
2124
- indicatorsState.setCurrentBar(candle, btcCandle, resolvedEthCandle);
2125
- pendingBacktestEntry.delayBarsRemaining -= 1;
2126
- if (pendingBacktestEntry.delayBarsRemaining > 0) {
2127
- return `BACKTEST_ENTRY_DELAY_PENDING:${pendingBacktestEntry.delayBarsRemaining}`;
2128
- }
2129
- const pending = pendingBacktestEntry;
2130
- pendingBacktestEntry = null;
2131
- const executionCandleResolution = resolveBacktestExecutionCandle(
2132
- candle,
2133
- btcCandle
2134
- );
2135
- const execution = applyBacktestDelayedEntryExecution({
2136
- decision: pending.decision,
2137
- execution: executionCandleResolution,
2138
- backtestPriceMode: executionCandleResolution.source === "primary_timeframe" ? "open" : backtestPriceMode,
2139
- delayBars: pending.delayBars
2140
- });
2141
- if (execution.skipReason) {
2142
- return pending.decision.signal ?? execution.skipReason;
2143
- }
2144
- if (!execution.executionCandle || !execution.btcExecutionCandle) {
2145
- return pending.decision.signal ?? "BACKTEST_LOWER_EXECUTION_CANDLE_MISSING";
2146
- }
2147
- const market = {
2148
- candle: execution.executionCandle,
2149
- btcCandle: execution.btcExecutionCandle
2150
- };
2151
- const entry = buildHookEntry({
2152
- decision: pending.decision,
2153
- runtime: pending.runtime
2154
- });
2155
- return executeEntryDecision({
2156
- connector,
2157
- symbol,
2158
- decision: pending.decision,
2159
- runtime: pending.runtime,
2160
- manifest: pending.manifest,
2161
- hookCtx: pending.hookCtx,
2162
- market,
2163
- entry,
2164
- policy: pending.policy,
2165
- ml: pending.ml,
2166
- ai: pending.ai,
2167
- recordRuntimeJournal,
2168
- invokeStageHooks,
2169
- notifyRuntimeError
2170
- });
2171
- };
2172
- const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
2173
- appendCurrentMarketData(candle, btcCandle, options.ethCandle);
2174
- const ethCandle = resolveEthCandle(candle, options.ethCandle);
2175
- indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
2176
- const delayedEntrySignal = await flushPendingBacktestEntry(
2177
- candle,
2178
- btcCandle,
2179
- ethCandle
2180
- );
2181
- if (delayedEntrySignal) {
2182
- return delayedEntrySignal;
2183
- }
2184
- const market = {
2185
- candle,
2186
- btcCandle
2187
- };
2188
- const onBarHookCtx = getHookCtx();
2189
- const projectOnBarDecision = await applyProjectOnBarHooks({
2190
- hookCtx: onBarHookCtx,
2191
- market
2192
- });
2193
- let decision;
2194
- let shouldInvokeAfterCoreDecisionHook = false;
2195
- if (projectOnBarDecision) {
2196
- decision = projectOnBarDecision;
2197
- } else {
2198
- const manifestOnBarDecision = await invokeHook(
2199
- "onBar",
2200
- strategyManifest?.hooks?.onBar,
2201
- {
2202
- ctx: onBarHookCtx,
2203
- market
2204
- },
2205
- { market }
2206
- );
2207
- if (isStrategyDecision(manifestOnBarDecision)) {
2208
- decision = manifestOnBarDecision;
2209
- } else {
2210
- decision = options.coreDecisionOverride ?? await core(candle, btcCandle);
2211
- shouldInvokeAfterCoreDecisionHook = true;
2212
- }
2213
- }
2214
- if (shouldInvokeAfterCoreDecisionHook) {
2215
- const initialDecisionStrategyName = decision.kind === "entry" ? decision.entryContext.strategy : strategyName;
2216
- decision = await applyProjectAfterCoreDecisionHooks({
2217
- hookCtx: getHookCtx(initialDecisionStrategyName),
2218
- market,
2219
- decision
2220
- });
2221
- }
2222
- const initialAfterBarDecisionStrategyName = decision.kind === "entry" ? decision.entryContext.strategy : strategyName;
2223
- decision = await applyProjectAfterBarDecisionHooks({
2224
- hookCtx: getHookCtx(initialAfterBarDecisionStrategyName),
2225
- market,
2226
- decision
2227
- });
2228
- const decisionStrategyName = decision.kind === "entry" ? decision.entryContext.strategy : strategyName;
2229
- const decisionManifest = resolveManifest(decisionStrategyName) ?? strategyManifest;
2230
- const decisionHookCtx = getHookCtx(decisionStrategyName);
2231
- if (shouldInvokeAfterCoreDecisionHook) {
2232
- await invokeHook(
2233
- "afterCoreDecision",
2234
- decisionManifest?.hooks?.afterCoreDecision,
2235
- {
2236
- ctx: decisionHookCtx,
2237
- market,
2238
- decision
2239
- },
2240
- { decision, market }
2241
- );
2242
- }
2243
- await invokeHook(
2244
- "afterBarDecision",
2245
- decisionManifest?.hooks?.afterBarDecision,
2246
- {
2247
- ctx: decisionHookCtx,
2248
- market,
2249
- decision
2250
- },
2251
- { decision, market }
2252
- );
2253
- if (decision.kind === "skip") {
2254
- await invokeStageHooks(
2255
- "onSkip",
2256
- decisionManifest?.hooks?.onSkip,
2257
- {
2258
- ctx: decisionHookCtx,
2259
- market,
2260
- decision
2261
- },
2262
- { decision, market }
2263
- );
2264
- return decision.code;
2265
- }
2266
- const rawMakeOrdersEnabled = typeof config.MAKE_ORDERS === "boolean" ? config.MAKE_ORDERS : true;
2267
- const makeOrdersEnabled = rawMakeOrdersEnabled && (env !== "PARITY" || isTestConnector(connector));
2268
- if (decision.kind === "exit") {
2269
- if (!makeOrdersEnabled) {
2270
- return decision.code;
2271
- }
2272
- const closeGate = await invokeGateHooks(
2273
- "beforeClosePosition",
2274
- decisionManifest?.hooks?.beforeClosePosition,
2275
- {
2276
- ctx: decisionHookCtx,
2277
- market,
2278
- decision
2279
- },
2280
- { decision, market }
2281
- );
2282
- if (closeGate?.allow === false) {
2283
- return closeGate.reason ? `CLOSE_BLOCKED_BY_HOOK:${closeGate.reason}` : "CLOSE_BLOCKED_BY_HOOK";
2284
- }
2285
- return handleExitDecision({
2286
- connector,
2287
- userName: recordRuntimeJournal ? userName : void 0,
2288
- strategyName,
2289
- symbol,
2290
- decision,
2291
- market,
2292
- onRuntimeClose,
2293
- onRuntimeError: async ({
2294
- stage,
2295
- error,
2296
- decision: exitDecision,
2297
- market: errorMarket
2298
- }) => {
2299
- await notifyRuntimeError({
2300
- stage,
2301
- error,
2302
- decision: exitDecision,
2303
- market: errorMarket
2304
- });
2305
- }
2306
- });
2307
- }
2308
- if (decision.kind === "protect") {
2309
- if (!makeOrdersEnabled) {
2310
- return decision.code;
2311
- }
2312
- return handleProtectDecision({
2313
- connector,
2314
- symbol,
2315
- decision,
2316
- market,
2317
- onRuntimeError: async ({
2318
- stage,
2319
- error,
2320
- decision: protectDecision,
2321
- market: errorMarket
2322
- }) => {
2323
- await notifyRuntimeError({
2324
- stage,
2325
- error,
2326
- decision: protectDecision,
2327
- market: errorMarket
2328
- });
2329
- }
2330
- });
2331
- }
2332
- const runtime = resolveEntryRuntimePolicy({
2333
- decision,
2334
- config,
2335
- manifest: decisionManifest,
2336
- policyProfile: getPolicyProfile(decisionStrategyName)
2337
- });
2338
- const signal = decision.signal;
2339
- if (signal) {
2340
- if (universe) signal.universe = universe;
2341
- if (assetClass) signal.assetClass = assetClass;
2342
- if (accountId) signal.accountId = accountId;
2343
- if (deploymentId) signal.deploymentId = deploymentId;
2344
- if (runtimeConfigId) {
2345
- signal.runtimeConfigId = runtimeConfigId;
2346
- if (runtimeConfigId !== "config") {
2347
- signal.signalId = `${signal.signalId}:${runtimeConfigId}`;
2348
- }
2349
- }
2350
- if (decisionHookCtx.policyProfileId) {
2351
- signal.policyProfileId = decisionHookCtx.policyProfileId;
2352
- }
2353
- }
2354
- const entry = buildHookEntry({
2355
- decision,
2356
- runtime
2357
- });
2358
- let ml;
2359
- if (signal) {
2360
- try {
2361
- await enrichSignalWithMl({
2362
- signal,
2363
- env,
2364
- ml: runtime.ml
2365
- });
2366
- } catch (error) {
2367
- await notifyRuntimeError({
2368
- stage: "enrichSignalWithMl",
2369
- error,
2370
- decision,
2371
- entry,
2372
- market
2373
- });
2374
- throw error;
2375
- }
2376
- ml = buildMlHookContext({
2377
- signal,
2378
- env,
2379
- ml: runtime.ml
2380
- });
2381
- await invokeStageHooks(
2382
- "afterEnrichMl",
2383
- decisionManifest?.hooks?.afterEnrichMl,
2384
- {
2385
- ctx: decisionHookCtx,
2386
- market,
2387
- decision,
2388
- entry,
2389
- ml
2390
- },
2391
- { decision, entry, market }
2392
- );
2393
- }
2394
- let quality;
2395
- let ai;
2396
- if (signal) {
2397
- try {
2398
- await enrichSignalWithMarketContextStages({
2399
- signal,
2400
- env,
2401
- includeHyperliquidWhales: false
2402
- });
2403
- quality = await enrichSignalWithAi({
2404
- signal,
2405
- userName,
2406
- symbol,
2407
- direction: signal.direction,
2408
- env,
2409
- ai: runtime.ai
2410
- });
2411
- } catch (error) {
2412
- await notifyRuntimeError({
2413
- stage: "enrichSignalWithAi",
2414
- error,
2415
- decision,
2416
- entry,
2417
- market
2418
- });
2419
- throw error;
2420
- }
2421
- ai = buildAiHookContext({
2422
- env,
2423
- ai: runtime.ai,
2424
- quality
2425
- });
2426
- await invokeStageHooks(
2427
- "afterEnrichAi",
2428
- decisionManifest?.hooks?.afterEnrichAi,
2429
- {
2430
- ctx: decisionHookCtx,
2431
- market,
2432
- decision,
2433
- entry,
2434
- ml: ml ?? buildMlHookContext({ signal, env, ml: runtime.ml }),
2435
- ai
2436
- },
2437
- { decision, entry, market }
2438
- );
2439
- }
2440
- const minAiQuality = runtime.ai?.minQuality ?? 4;
2441
- const aiEnabled = runtime.ai?.enabled !== false && runtime.ai != null;
2442
- const policy = buildHookPolicy({
2443
- quality,
2444
- makeOrdersEnabled,
2445
- minAiQuality
2446
- });
2447
- const shouldMakeOrder = shouldExecuteEntryDecision({
2448
- makeOrdersEnabled,
2449
- env,
2450
- signal,
2451
- ml,
2452
- aiEnabled,
2453
- quality,
2454
- minAiQuality
2455
- });
2456
- if (!shouldMakeOrder) {
2457
- if (signal) {
2458
- signal.orderStatus = "skipped";
2459
- signal.orderSkipReason = getEntrySkipReason({
2460
- makeOrdersEnabled,
2461
- env,
2462
- ml,
2463
- aiEnabled,
2464
- quality,
2465
- minAiQuality
2466
- });
2467
- }
2468
- return signal ?? decision.code;
2469
- }
2470
- const entryGate = await invokeGateHooks(
2471
- "beforeEntryGate",
2472
- decisionManifest?.hooks?.beforeEntryGate,
2473
- {
2474
- ctx: decisionHookCtx,
2475
- market,
2476
- decision,
2477
- entry,
2478
- policy,
2479
- ml,
2480
- ai
2481
- },
2482
- { decision, entry, market }
2483
- );
2484
- if (entryGate?.allow === false) {
2485
- const skipReason = entryGate.reason ? `HOOK_BEFORE_ENTRY_GATE:${entryGate.reason}` : "HOOK_BEFORE_ENTRY_GATE";
2486
- if (signal) {
2487
- signal.orderStatus = "skipped";
2488
- signal.orderSkipReason = skipReason;
2489
- }
2490
- return signal ?? skipReason;
2491
- }
2492
- if (backtestEntryDelayBars > 0) {
2493
- pendingBacktestEntry = {
2494
- delayBars: backtestEntryDelayBars,
2495
- delayBarsRemaining: backtestEntryDelayBars,
2496
- decision,
2497
- runtime,
2498
- manifest: decisionManifest,
2499
- hookCtx: decisionHookCtx,
2500
- policy,
2501
- ml,
2502
- ai
2503
- };
2504
- return `BACKTEST_ENTRY_DELAY_QUEUED:${backtestEntryDelayBars}`;
2505
- }
2506
- return executeEntryDecision({
2507
- connector,
2508
- symbol,
2509
- decision,
2510
- runtime,
2511
- manifest: decisionManifest,
2512
- hookCtx: decisionHookCtx,
2513
- market,
2514
- entry,
2515
- policy,
2516
- ml,
2517
- ai,
2518
- recordRuntimeJournal,
2519
- invokeStageHooks,
2520
- notifyRuntimeError
2521
- });
2522
- };
2523
- const strategy = (async (candle, btcCandle, ethCandle) => runWithDecisionOverride(candle, btcCandle, { ethCandle }));
2524
- strategy.__tradejsUpdateReferenceData = (params) => indicatorsState.updateReferenceData?.(params);
2525
- strategy.__tradejsFlushBacktestDelayedEntry = flushPendingBacktestEntry;
2526
- const resolvedDetectorKey = detectorKey?.(config);
2527
- if (resolvedDetectorKey && detectorNoSignalSkipReason) {
2528
- const canFastAdvanceDetectorNoSignal = env === "BACKTEST" && getProjectHookList("onBar").length === 0 && getProjectHookList("afterCoreDecision").length === 0 && getProjectHookList("afterBarDecision").length === 0 && getProjectHookList("onSkip").length === 0 && !strategyManifest?.hooks?.onBar && !strategyManifest?.hooks?.afterCoreDecision && !strategyManifest?.hooks?.afterBarDecision && !strategyManifest?.hooks?.onSkip;
2529
- strategy.detectorFanoutKey = [strategyName, resolvedDetectorKey].join(
2530
- ":"
2531
- );
2532
- strategy.detectorNoSignalSkipReason = detectorNoSignalSkipReason;
2533
- strategy.canFastAdvanceDetectorNoSignal = canFastAdvanceDetectorNoSignal;
2534
- if (canFastAdvanceDetectorNoSignal) {
2535
- strategy.advanceDetectorNoSignal = (candle, btcCandle, code) => {
2536
- appendCurrentMarketData(candle, btcCandle);
2537
- indicatorsState.setCurrentBar(
2538
- candle,
2539
- btcCandle,
2540
- resolveEthCandle(candle)
2541
- );
2542
- return Promise.resolve(code);
2543
- };
2544
- }
2545
- strategy.skipDetectorNoSignal = (candle, btcCandle, code) => runWithDecisionOverride(candle, btcCandle, {
2546
- coreDecisionOverride: strategyApi.skip(code)
2547
- });
2548
- }
2549
- return strategy;
2550
- };
2551
- if (detectorKey) {
2552
- creator.detectorKey = detectorKey;
2553
- }
2554
- if (detectorNoSignalSkipReason) {
2555
- creator.detectorNoSignalSkipReason = detectorNoSignalSkipReason;
2556
- }
2557
- return creator;
2558
- };
2559
-
2560
59
  // src/strategyHooks/closeOppositePositionsBeforeOpen.ts
2561
- var import_lodash2 = __toESM(require_lodash());
2562
- import { logger as logger4 } from "@tradejs/infra/logger";
60
+ import _ from "lodash";
61
+ import { logger } from "@tradejs/infra/logger";
2563
62
  var closeOppositePositionsBeforeOpen = async ({
2564
63
  connector,
2565
64
  entryContext
@@ -2573,7 +72,7 @@ var closeOppositePositionsBeforeOpen = async ({
2573
72
  } = entryContext;
2574
73
  const price = prices.currentPrice;
2575
74
  try {
2576
- logger4.log(
75
+ logger.log(
2577
76
  "info",
2578
77
  "[%s] checking open positions before open: %s %s",
2579
78
  strategyName,
@@ -2584,7 +83,7 @@ var closeOppositePositionsBeforeOpen = async ({
2584
83
  const openPositions = (positions || []).filter(
2585
84
  (item) => item && Number(item.qty) > 0
2586
85
  );
2587
- logger4.log(
86
+ logger.log(
2588
87
  "info",
2589
88
  "[%s] open positions found: %s",
2590
89
  strategyName,
@@ -2593,8 +92,8 @@ var closeOppositePositionsBeforeOpen = async ({
2593
92
  const oppositePositions = openPositions.filter(
2594
93
  (item) => item.symbol !== currentSymbol && item.direction !== currentDirection
2595
94
  );
2596
- if (import_lodash2.default.isEmpty(oppositePositions)) {
2597
- logger4.log(
95
+ if (_.isEmpty(oppositePositions)) {
96
+ logger.log(
2598
97
  "info",
2599
98
  "[%s] no opposite positions to close before open: %s",
2600
99
  strategyName,
@@ -2603,7 +102,7 @@ var closeOppositePositionsBeforeOpen = async ({
2603
102
  return;
2604
103
  }
2605
104
  for (const position of oppositePositions) {
2606
- logger4.log(
105
+ logger.log(
2607
106
  "info",
2608
107
  "[%s] closing opposite position: %s %s qty=%s",
2609
108
  strategyName,
@@ -2618,14 +117,14 @@ var closeOppositePositionsBeforeOpen = async ({
2618
117
  timestamp,
2619
118
  direction: position.direction
2620
119
  });
2621
- logger4.log(
120
+ logger.log(
2622
121
  "info",
2623
122
  "[%s] opposite position closed: %s",
2624
123
  strategyName,
2625
124
  position.symbol
2626
125
  );
2627
126
  } catch (err) {
2628
- logger4.log(
127
+ logger.log(
2629
128
  "error",
2630
129
  "[%s] failed to close opposite position: %s %s",
2631
130
  strategyName,
@@ -2635,7 +134,7 @@ var closeOppositePositionsBeforeOpen = async ({
2635
134
  }
2636
135
  }
2637
136
  } catch (err) {
2638
- logger4.log(
137
+ logger.log(
2639
138
  "error",
2640
139
  "[%s] failed to load open positions before open: %s %s",
2641
140
  strategyName,
@@ -2847,7 +346,7 @@ var createMoveStopToBreakEvenOnBarHook = ({
2847
346
  var createMoveStopToBreakEvenAfterCoreDecisionHook = createMoveStopToBreakEvenOnBarHook;
2848
347
 
2849
348
  // src/signalsHooks/closeAllPositionsOnGlobalProfitBeforeSignals.ts
2850
- import { logger as logger5 } from "@tradejs/infra/logger";
349
+ import { logger as logger2 } from "@tradejs/infra/logger";
2851
350
  var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
2852
351
  getStrategyDefaultConfig = () => void 0,
2853
352
  profitRiskMultiplier = DEFAULT_GLOBAL_UNREALIZED_PNL_TRIGGER_RISK_MULTIPLIER
@@ -2886,7 +385,7 @@ var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
2886
385
  if (!Number.isFinite(unrealizedPnlThreshold) || unrealizedPnlThreshold <= 0 || totalUnrealizedPnl < unrealizedPnlThreshold) {
2887
386
  return;
2888
387
  }
2889
- logger5.info(
388
+ logger2.info(
2890
389
  "closing all positions before signals by global unrealized pnl threshold: totalPnl=%s threshold=%s positions=%s",
2891
390
  totalUnrealizedPnl,
2892
391
  unrealizedPnlThreshold,
@@ -2912,7 +411,7 @@ var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
2912
411
  ];
2913
412
  });
2914
413
  if (failedClosures.length) {
2915
- logger5.warn(
414
+ logger2.warn(
2916
415
  "close-all before signals hook could not confirm closures for %s",
2917
416
  failedClosures.join(", ")
2918
417
  );