@tradejs/node 1.0.6 → 1.0.9
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.
- package/dist/ai.js +274 -126
- package/dist/ai.mjs +3 -3
- package/dist/backtest.js +345 -42
- package/dist/backtest.mjs +98 -16
- package/dist/chunk-2JKX3DM7.mjs +619 -0
- package/dist/{chunk-XW5L327F.mjs → chunk-JMDYEKIO.mjs} +1 -1
- package/dist/chunk-JRRG3YQG.mjs +154 -0
- package/dist/{chunk-CGJ2UU6H.mjs → chunk-JU77QVJ3.mjs} +8 -1
- package/dist/{chunk-SCMBUEGK.mjs → chunk-KMJQQ53K.mjs} +1 -1
- package/dist/{chunk-GKDBAF3A.mjs → chunk-KZDHZ56N.mjs} +25 -7
- package/dist/{chunk-EOZSJKUM.mjs → chunk-WGOYR6AB.mjs} +1 -1
- package/dist/cli.d.mts +20 -2
- package/dist/cli.d.ts +20 -2
- package/dist/cli.js +456 -183
- package/dist/cli.mjs +155 -49
- package/dist/connectors.js +6 -1
- package/dist/connectors.mjs +2 -2
- package/dist/constants.js +1 -1
- package/dist/constants.mjs +1 -1
- package/dist/registry.js +6 -1
- package/dist/registry.mjs +2 -2
- package/dist/strategies.d.mts +24 -9
- package/dist/strategies.d.ts +24 -9
- package/dist/strategies.js +7465 -6467
- package/dist/strategies.mjs +865 -140
- package/package.json +6 -6
- package/dist/chunk-72FKXJ2I.mjs +0 -473
- package/dist/chunk-PQH5PAFC.mjs +0 -49
package/dist/strategies.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
buildMlPayload
|
|
3
|
-
|
|
2
|
+
buildMlPayload,
|
|
3
|
+
enrichSignalWithDerivativesContext
|
|
4
|
+
} from "./chunk-JRRG3YQG.mjs";
|
|
4
5
|
import {
|
|
5
6
|
require_lodash
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-KZDHZ56N.mjs";
|
|
7
8
|
import {
|
|
8
9
|
DEFAULT_AI_MODEL,
|
|
9
10
|
MAX_AI_SERIES_POINTS,
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
runAiPrompt,
|
|
20
21
|
runAiPromptLocal,
|
|
21
22
|
trimSeriesDeep
|
|
22
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-2JKX3DM7.mjs";
|
|
23
24
|
import {
|
|
24
25
|
createPineScriptLoader
|
|
25
26
|
} from "./chunk-H6LIYHU4.mjs";
|
|
@@ -36,102 +37,15 @@ import {
|
|
|
36
37
|
registerStrategyEntries,
|
|
37
38
|
resetStrategyRegistryCache,
|
|
38
39
|
strategies
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-WGOYR6AB.mjs";
|
|
40
41
|
import {
|
|
41
|
-
getTradejsProjectCwd
|
|
42
|
-
|
|
42
|
+
getTradejsProjectCwd,
|
|
43
|
+
loadTradejsConfig
|
|
44
|
+
} from "./chunk-JU77QVJ3.mjs";
|
|
43
45
|
import {
|
|
44
46
|
__toESM
|
|
45
47
|
} from "./chunk-6DZX6EAA.mjs";
|
|
46
48
|
|
|
47
|
-
// src/closeOppositePositionsBeforeOpen.ts
|
|
48
|
-
var import_lodash = __toESM(require_lodash());
|
|
49
|
-
import { logger } from "@tradejs/infra/logger";
|
|
50
|
-
var closeOppositePositionsBeforeOpen = async ({
|
|
51
|
-
connector,
|
|
52
|
-
entryContext
|
|
53
|
-
}) => {
|
|
54
|
-
const {
|
|
55
|
-
symbol: currentSymbol,
|
|
56
|
-
direction: currentDirection,
|
|
57
|
-
timestamp,
|
|
58
|
-
prices,
|
|
59
|
-
strategy: strategyName
|
|
60
|
-
} = entryContext;
|
|
61
|
-
const price = prices.currentPrice;
|
|
62
|
-
try {
|
|
63
|
-
logger.log(
|
|
64
|
-
"info",
|
|
65
|
-
"[%s] checking open positions before open: %s %s",
|
|
66
|
-
strategyName,
|
|
67
|
-
currentSymbol,
|
|
68
|
-
currentDirection
|
|
69
|
-
);
|
|
70
|
-
const positions = await connector.getPositions();
|
|
71
|
-
const openPositions = (positions || []).filter(
|
|
72
|
-
(item) => item && Number(item.qty) > 0
|
|
73
|
-
);
|
|
74
|
-
logger.log(
|
|
75
|
-
"info",
|
|
76
|
-
"[%s] open positions found: %s",
|
|
77
|
-
strategyName,
|
|
78
|
-
openPositions.length
|
|
79
|
-
);
|
|
80
|
-
const oppositePositions = openPositions.filter(
|
|
81
|
-
(item) => item.symbol !== currentSymbol && item.direction !== currentDirection
|
|
82
|
-
);
|
|
83
|
-
if (import_lodash.default.isEmpty(oppositePositions)) {
|
|
84
|
-
logger.log(
|
|
85
|
-
"info",
|
|
86
|
-
"[%s] no opposite positions to close before open: %s",
|
|
87
|
-
strategyName,
|
|
88
|
-
currentSymbol
|
|
89
|
-
);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
for (const position of oppositePositions) {
|
|
93
|
-
logger.log(
|
|
94
|
-
"info",
|
|
95
|
-
"[%s] closing opposite position: %s %s qty=%s",
|
|
96
|
-
strategyName,
|
|
97
|
-
position.symbol,
|
|
98
|
-
position.direction,
|
|
99
|
-
position.qty
|
|
100
|
-
);
|
|
101
|
-
try {
|
|
102
|
-
await connector.closePosition({
|
|
103
|
-
symbol: position.symbol,
|
|
104
|
-
price,
|
|
105
|
-
timestamp,
|
|
106
|
-
direction: position.direction
|
|
107
|
-
});
|
|
108
|
-
logger.log(
|
|
109
|
-
"info",
|
|
110
|
-
"[%s] opposite position closed: %s",
|
|
111
|
-
strategyName,
|
|
112
|
-
position.symbol
|
|
113
|
-
);
|
|
114
|
-
} catch (err) {
|
|
115
|
-
logger.log(
|
|
116
|
-
"error",
|
|
117
|
-
"[%s] failed to close opposite position: %s %s",
|
|
118
|
-
strategyName,
|
|
119
|
-
position.symbol,
|
|
120
|
-
err
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
} catch (err) {
|
|
125
|
-
logger.log(
|
|
126
|
-
"error",
|
|
127
|
-
"[%s] failed to load open positions before open: %s %s",
|
|
128
|
-
strategyName,
|
|
129
|
-
currentSymbol,
|
|
130
|
-
err
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
|
|
135
49
|
// src/strategies.ts
|
|
136
50
|
export * from "@tradejs/core/strategies";
|
|
137
51
|
|
|
@@ -148,12 +62,125 @@ import { logger as logger3 } from "@tradejs/infra/logger";
|
|
|
148
62
|
|
|
149
63
|
// src/strategyHelpers/runtime.ts
|
|
150
64
|
import { logger as logger2 } from "@tradejs/infra/logger";
|
|
65
|
+
import { redisKeys as redisKeys2, setData as setData2 } from "@tradejs/infra/redis";
|
|
151
66
|
import {
|
|
152
67
|
buildMlFeatures,
|
|
153
68
|
buildMlTrainingRow,
|
|
154
69
|
fetchMlThreshold,
|
|
155
70
|
trimMlTrainingRowWindows
|
|
156
71
|
} from "@tradejs/infra/ml";
|
|
72
|
+
|
|
73
|
+
// src/runtimeJournal.ts
|
|
74
|
+
import { randomUUID } from "crypto";
|
|
75
|
+
import { TTL_1M } from "@tradejs/core/constants";
|
|
76
|
+
import { logger } from "@tradejs/infra/logger";
|
|
77
|
+
import { delKey, getData, redisKeys, setData } from "@tradejs/infra/redis";
|
|
78
|
+
var now = () => Date.now();
|
|
79
|
+
var toOrderId = () => `tjs-${randomUUID().replace(/-/g, "").slice(0, 24).toLowerCase()}`;
|
|
80
|
+
var calculateClosedPnl = ({
|
|
81
|
+
direction,
|
|
82
|
+
entryPrice,
|
|
83
|
+
exitPrice,
|
|
84
|
+
qty
|
|
85
|
+
}) => {
|
|
86
|
+
const pnl = direction === "LONG" ? (exitPrice - entryPrice) * qty : (entryPrice - exitPrice) * qty;
|
|
87
|
+
return Number.isFinite(pnl) ? pnl : null;
|
|
88
|
+
};
|
|
89
|
+
var createRuntimeOrderId = () => toOrderId();
|
|
90
|
+
var recordRuntimeTradeOpen = async (params) => {
|
|
91
|
+
const { userName } = params;
|
|
92
|
+
if (!userName) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const record = {
|
|
96
|
+
...params,
|
|
97
|
+
status: "active",
|
|
98
|
+
currentPrice: params.entryPrice,
|
|
99
|
+
currentPnl: 0,
|
|
100
|
+
closedPnl: null,
|
|
101
|
+
exitPrice: null,
|
|
102
|
+
exitTimestamp: null,
|
|
103
|
+
lastSyncedAt: now()
|
|
104
|
+
};
|
|
105
|
+
try {
|
|
106
|
+
await Promise.all([
|
|
107
|
+
setData(redisKeys.runtimeTrade(userName, record.orderId), record, {
|
|
108
|
+
expire: 0
|
|
109
|
+
}),
|
|
110
|
+
setData(
|
|
111
|
+
redisKeys.runtimeActiveTrade(userName, record.symbol),
|
|
112
|
+
{ orderId: record.orderId },
|
|
113
|
+
{ expire: 0 }
|
|
114
|
+
)
|
|
115
|
+
]);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
logger.error(
|
|
118
|
+
"runtime trade open journal failed: %s %s",
|
|
119
|
+
record.symbol,
|
|
120
|
+
error?.message || String(error)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return record;
|
|
124
|
+
};
|
|
125
|
+
var markRuntimeTradeClosed = async (params) => {
|
|
126
|
+
const { userName, symbol, strategy, exitPrice, exitTimestamp, closedPnl } = params;
|
|
127
|
+
if (!userName) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const activeRef = await getData(
|
|
131
|
+
redisKeys.runtimeActiveTrade(userName, symbol),
|
|
132
|
+
null
|
|
133
|
+
);
|
|
134
|
+
const orderId = String(activeRef?.orderId || "").trim();
|
|
135
|
+
if (!orderId) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const existing = await getData(
|
|
139
|
+
redisKeys.runtimeTrade(userName, orderId),
|
|
140
|
+
null
|
|
141
|
+
);
|
|
142
|
+
if (!existing) {
|
|
143
|
+
await delKey(redisKeys.runtimeActiveTrade(userName, symbol));
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
if (strategy && existing.strategy !== strategy) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const resolvedExitPrice = typeof exitPrice === "number" && Number.isFinite(exitPrice) ? exitPrice : existing.currentPrice ?? existing.entryPrice;
|
|
150
|
+
const resolvedClosedPnl = typeof closedPnl === "number" && Number.isFinite(closedPnl) ? closedPnl : typeof resolvedExitPrice === "number" && Number.isFinite(resolvedExitPrice) ? calculateClosedPnl({
|
|
151
|
+
direction: existing.direction,
|
|
152
|
+
entryPrice: existing.entryPrice,
|
|
153
|
+
exitPrice: resolvedExitPrice,
|
|
154
|
+
qty: existing.qty
|
|
155
|
+
}) : existing.closedPnl ?? existing.currentPnl ?? null;
|
|
156
|
+
const next = {
|
|
157
|
+
...existing,
|
|
158
|
+
status: "closed",
|
|
159
|
+
currentPrice: resolvedExitPrice,
|
|
160
|
+
currentPnl: resolvedClosedPnl,
|
|
161
|
+
closedPnl: resolvedClosedPnl,
|
|
162
|
+
exitPrice: resolvedExitPrice,
|
|
163
|
+
exitTimestamp: typeof exitTimestamp === "number" && Number.isFinite(exitTimestamp) ? exitTimestamp : now(),
|
|
164
|
+
lastSyncedAt: now()
|
|
165
|
+
};
|
|
166
|
+
try {
|
|
167
|
+
await Promise.all([
|
|
168
|
+
setData(redisKeys.runtimeTrade(userName, orderId), next, {
|
|
169
|
+
expire: TTL_1M
|
|
170
|
+
}),
|
|
171
|
+
delKey(redisKeys.runtimeActiveTrade(userName, symbol))
|
|
172
|
+
]);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
logger.error(
|
|
175
|
+
"runtime trade close journal failed: %s %s",
|
|
176
|
+
symbol,
|
|
177
|
+
error?.message || String(error)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return next;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// src/strategyHelpers/runtime.ts
|
|
157
184
|
var formatAiError = (err) => {
|
|
158
185
|
const error = err;
|
|
159
186
|
const safeJson = (value) => {
|
|
@@ -172,6 +199,60 @@ var formatAiError = (err) => {
|
|
|
172
199
|
};
|
|
173
200
|
return safeJson(details);
|
|
174
201
|
};
|
|
202
|
+
var resolveAiQuality = (analysis, direction) => {
|
|
203
|
+
if (typeof analysis?.quality !== "number") {
|
|
204
|
+
return void 0;
|
|
205
|
+
}
|
|
206
|
+
const normalizedQuality = Math.round(analysis.quality);
|
|
207
|
+
const aiApprovedCurrentTrade = analysis.direction === direction;
|
|
208
|
+
return aiApprovedCurrentTrade ? normalizedQuality : 0;
|
|
209
|
+
};
|
|
210
|
+
var resolveAiDecision = (analysis, direction, minQuality = 4) => {
|
|
211
|
+
const quality = resolveAiQuality(analysis, direction);
|
|
212
|
+
return quality != null && quality >= minQuality ? "approved" : "rejected";
|
|
213
|
+
};
|
|
214
|
+
var withGateComparison = ({
|
|
215
|
+
llmAnalysis,
|
|
216
|
+
gateAnalysis,
|
|
217
|
+
direction,
|
|
218
|
+
minQuality
|
|
219
|
+
}) => {
|
|
220
|
+
const gateDecision = resolveAiDecision(gateAnalysis, direction, minQuality);
|
|
221
|
+
const llmDecision = resolveAiDecision(llmAnalysis, direction, minQuality);
|
|
222
|
+
return {
|
|
223
|
+
...llmAnalysis,
|
|
224
|
+
gateAnalysis,
|
|
225
|
+
gateDecision,
|
|
226
|
+
llmDecision,
|
|
227
|
+
gateContradictsLlm: gateDecision !== llmDecision
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
var findReplayAiAnalysis = ({
|
|
231
|
+
signal,
|
|
232
|
+
direction,
|
|
233
|
+
ai
|
|
234
|
+
}) => {
|
|
235
|
+
const snapshots = ai?.replayAnalyses;
|
|
236
|
+
if (!Array.isArray(snapshots) || !snapshots.length) {
|
|
237
|
+
return void 0;
|
|
238
|
+
}
|
|
239
|
+
let best = null;
|
|
240
|
+
for (const snapshot of snapshots) {
|
|
241
|
+
if (snapshot.symbol !== signal.symbol || snapshot.direction !== direction || snapshot.strategy && snapshot.strategy !== signal.strategy) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const toleranceMs = Math.max(0, Number(snapshot.toleranceMs ?? 0));
|
|
245
|
+
const diff = Math.abs(snapshot.timestamp - signal.timestamp);
|
|
246
|
+
if (diff > toleranceMs || best && diff >= best.diff) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
best = {
|
|
250
|
+
diff,
|
|
251
|
+
analysis: snapshot.analysis
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return best?.analysis;
|
|
255
|
+
};
|
|
175
256
|
var enrichSignalWithMl = async ({
|
|
176
257
|
signal,
|
|
177
258
|
env,
|
|
@@ -211,17 +292,48 @@ var enrichSignalWithAi = async ({
|
|
|
211
292
|
env,
|
|
212
293
|
ai
|
|
213
294
|
}) => {
|
|
214
|
-
if (
|
|
295
|
+
if (ai?.enabled === false) {
|
|
215
296
|
return void 0;
|
|
216
297
|
}
|
|
298
|
+
if (env === "PARITY") {
|
|
299
|
+
const replayAnalysis = findReplayAiAnalysis({ signal, direction, ai });
|
|
300
|
+
if (replayAnalysis) {
|
|
301
|
+
signal.aiAnalysis = replayAnalysis;
|
|
302
|
+
return resolveAiQuality(replayAnalysis, direction);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (env === "BACKTEST") {
|
|
306
|
+
return void 0;
|
|
307
|
+
}
|
|
308
|
+
if (ai?.mode === "gate") {
|
|
309
|
+
const minQuality = ai.minQuality ?? 4;
|
|
310
|
+
const { askAI: askAI2, runAiPromptLocal: runAiPromptLocal2 } = await import("./ai.mjs");
|
|
311
|
+
const gateAnalysis = await runAiPromptLocal2(signal);
|
|
312
|
+
const gateQuality = resolveAiQuality(gateAnalysis, direction);
|
|
313
|
+
try {
|
|
314
|
+
const llmAnalysis = await askAI2(signal, { userName });
|
|
315
|
+
const comparedAnalysis = withGateComparison({
|
|
316
|
+
llmAnalysis,
|
|
317
|
+
gateAnalysis,
|
|
318
|
+
direction,
|
|
319
|
+
minQuality
|
|
320
|
+
});
|
|
321
|
+
signal.aiAnalysis = comparedAnalysis;
|
|
322
|
+
await setData2(
|
|
323
|
+
redisKeys2.analysis(signal.symbol, signal.signalId),
|
|
324
|
+
comparedAnalysis
|
|
325
|
+
);
|
|
326
|
+
} catch (err) {
|
|
327
|
+
logger2.error("AI analysis error: %s %s", symbol, formatAiError(err));
|
|
328
|
+
signal.aiAnalysis = gateAnalysis;
|
|
329
|
+
}
|
|
330
|
+
return gateQuality;
|
|
331
|
+
}
|
|
217
332
|
try {
|
|
218
333
|
const { askAI: askAI2 } = await import("./ai.mjs");
|
|
219
334
|
const analysis = await askAI2(signal, { userName });
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const aiApprovedCurrentTrade = analysis?.direction === direction;
|
|
223
|
-
return aiApprovedCurrentTrade ? normalizedQuality : 0;
|
|
224
|
-
}
|
|
335
|
+
signal.aiAnalysis = analysis;
|
|
336
|
+
return resolveAiQuality(analysis, direction);
|
|
225
337
|
} catch (err) {
|
|
226
338
|
logger2.error("AI analysis error: %s %s", symbol, formatAiError(err));
|
|
227
339
|
}
|
|
@@ -236,6 +348,7 @@ var enrichSignalWithMlAi = async ({
|
|
|
236
348
|
ml,
|
|
237
349
|
ai
|
|
238
350
|
}) => {
|
|
351
|
+
await enrichSignalWithDerivativesContext({ signal, env });
|
|
239
352
|
await enrichSignalWithMl({ signal, env, ml });
|
|
240
353
|
return enrichSignalWithAi({ signal, userName, symbol, direction, env, ai });
|
|
241
354
|
};
|
|
@@ -271,6 +384,7 @@ var applyProtectiveOrders = async ({
|
|
|
271
384
|
};
|
|
272
385
|
var executeEntryOrder = async ({
|
|
273
386
|
connector,
|
|
387
|
+
userName,
|
|
274
388
|
symbol,
|
|
275
389
|
direction,
|
|
276
390
|
qty,
|
|
@@ -279,9 +393,12 @@ var executeEntryOrder = async ({
|
|
|
279
393
|
takeProfits,
|
|
280
394
|
stopLossPrice,
|
|
281
395
|
signal,
|
|
282
|
-
beforePlaceOrder
|
|
396
|
+
beforePlaceOrder,
|
|
397
|
+
recordRuntimeTrade = true
|
|
283
398
|
}) => {
|
|
284
399
|
await beforePlaceOrder?.();
|
|
400
|
+
const orderId = signal.orderId || createRuntimeOrderId();
|
|
401
|
+
signal.orderId = orderId;
|
|
285
402
|
const orderPlaced = await connector.placeOrder({
|
|
286
403
|
symbol,
|
|
287
404
|
qty,
|
|
@@ -289,6 +406,7 @@ var executeEntryOrder = async ({
|
|
|
289
406
|
isLimit: false,
|
|
290
407
|
timestamp,
|
|
291
408
|
direction,
|
|
409
|
+
orderId,
|
|
292
410
|
signal
|
|
293
411
|
});
|
|
294
412
|
if (orderPlaced) {
|
|
@@ -314,11 +432,26 @@ var executeEntryOrder = async ({
|
|
|
314
432
|
signal.orderStatus = orderPlaced ? "completed" : "failed";
|
|
315
433
|
signal.orderSkipReason = void 0;
|
|
316
434
|
const currentPosition = await connector.getPosition(symbol);
|
|
435
|
+
const entryPrice = currentPosition?.price && Number.isFinite(currentPosition.price) ? currentPosition.price : currentPrice;
|
|
436
|
+
signal.prices.currentPrice = entryPrice;
|
|
437
|
+
if (orderPlaced && recordRuntimeTrade) {
|
|
438
|
+
await recordRuntimeTradeOpen({
|
|
439
|
+
userName,
|
|
440
|
+
orderId,
|
|
441
|
+
signalId: signal.signalId,
|
|
442
|
+
strategy: signal.strategy,
|
|
443
|
+
symbol,
|
|
444
|
+
direction,
|
|
445
|
+
qty,
|
|
446
|
+
entryPrice,
|
|
447
|
+
entryTimestamp: timestamp,
|
|
448
|
+
...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
317
451
|
if (currentPosition?.price) {
|
|
318
|
-
signal.prices.currentPrice = currentPosition.price;
|
|
319
452
|
return currentPosition.price;
|
|
320
453
|
}
|
|
321
|
-
return
|
|
454
|
+
return entryPrice;
|
|
322
455
|
};
|
|
323
456
|
var updatePositionProtection = async ({
|
|
324
457
|
connector,
|
|
@@ -339,8 +472,8 @@ var updatePositionProtection = async ({
|
|
|
339
472
|
};
|
|
340
473
|
|
|
341
474
|
// src/strategyHelpers/config.ts
|
|
342
|
-
var
|
|
343
|
-
import { getData, redisKeys } from "@tradejs/infra/redis";
|
|
475
|
+
var import_lodash = __toESM(require_lodash());
|
|
476
|
+
import { getData as getData2, redisKeys as redisKeys3 } from "@tradejs/infra/redis";
|
|
344
477
|
var resolveStrategyConfig = async ({
|
|
345
478
|
strategyName,
|
|
346
479
|
userName,
|
|
@@ -348,7 +481,7 @@ var resolveStrategyConfig = async ({
|
|
|
348
481
|
baseConfig,
|
|
349
482
|
defaults
|
|
350
483
|
}) => {
|
|
351
|
-
const mergeIfNotEmpty = (target, patch) => patch && !
|
|
484
|
+
const mergeIfNotEmpty = (target, patch) => patch && !import_lodash.default.isEmpty(patch) ? {
|
|
352
485
|
...target,
|
|
353
486
|
...patch
|
|
354
487
|
} : target;
|
|
@@ -358,17 +491,17 @@ var resolveStrategyConfig = async ({
|
|
|
358
491
|
};
|
|
359
492
|
let isConfigFromBacktest = false;
|
|
360
493
|
if (config.ENV !== "BACKTEST") {
|
|
361
|
-
const userConfig = await
|
|
362
|
-
|
|
494
|
+
const userConfig = await getData2(
|
|
495
|
+
redisKeys3.strategyConfig(userName, strategyName),
|
|
363
496
|
{}
|
|
364
497
|
);
|
|
365
498
|
config = mergeIfNotEmpty(config, userConfig);
|
|
366
|
-
const results = await
|
|
367
|
-
|
|
499
|
+
const results = await getData2(
|
|
500
|
+
redisKeys3.strategyResults(userName, strategyName),
|
|
368
501
|
{}
|
|
369
502
|
);
|
|
370
503
|
const backtestResult = results?.[symbol];
|
|
371
|
-
if (backtestResult && !
|
|
504
|
+
if (backtestResult && !import_lodash.default.isEmpty(backtestResult.config)) {
|
|
372
505
|
config = mergeIfNotEmpty(
|
|
373
506
|
config,
|
|
374
507
|
backtestResult.config
|
|
@@ -405,10 +538,26 @@ var resolveEntryRuntimePolicy = ({
|
|
|
405
538
|
ai
|
|
406
539
|
};
|
|
407
540
|
};
|
|
541
|
+
var formatGateNumber = (value) => {
|
|
542
|
+
if (!Number.isFinite(value)) {
|
|
543
|
+
return String(value);
|
|
544
|
+
}
|
|
545
|
+
const normalized = Number(value.toFixed(6));
|
|
546
|
+
return Number.isInteger(normalized) ? String(normalized) : String(normalized);
|
|
547
|
+
};
|
|
548
|
+
var isMlRuntimeGateEnabled = (params) => {
|
|
549
|
+
const { env, ml } = params;
|
|
550
|
+
return env !== "BACKTEST" && ml?.config != null && ml.config.enabled !== false;
|
|
551
|
+
};
|
|
552
|
+
var isMlResultUnavailable = (params) => {
|
|
553
|
+
const { env, ml } = params;
|
|
554
|
+
return isMlRuntimeGateEnabled({ env, ml }) && ml?.result == null;
|
|
555
|
+
};
|
|
408
556
|
var shouldExecuteEntryDecision = ({
|
|
409
557
|
makeOrdersEnabled,
|
|
410
558
|
env,
|
|
411
559
|
signal,
|
|
560
|
+
ml,
|
|
412
561
|
aiEnabled,
|
|
413
562
|
quality,
|
|
414
563
|
minAiQuality
|
|
@@ -416,7 +565,16 @@ var shouldExecuteEntryDecision = ({
|
|
|
416
565
|
if (!makeOrdersEnabled) {
|
|
417
566
|
return false;
|
|
418
567
|
}
|
|
419
|
-
if (!signal || env === "BACKTEST"
|
|
568
|
+
if (!signal || env === "BACKTEST") {
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
if (isMlResultUnavailable({ env, ml })) {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
if (isMlRuntimeGateEnabled({ env, ml }) && ml?.result?.passed === false) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
if (!aiEnabled) {
|
|
420
578
|
return true;
|
|
421
579
|
}
|
|
422
580
|
return Number.isFinite(quality) && quality >= minAiQuality;
|
|
@@ -424,6 +582,7 @@ var shouldExecuteEntryDecision = ({
|
|
|
424
582
|
var getEntrySkipReason = ({
|
|
425
583
|
makeOrdersEnabled,
|
|
426
584
|
env,
|
|
585
|
+
ml,
|
|
427
586
|
aiEnabled,
|
|
428
587
|
quality,
|
|
429
588
|
minAiQuality
|
|
@@ -431,6 +590,14 @@ var getEntrySkipReason = ({
|
|
|
431
590
|
if (!makeOrdersEnabled) {
|
|
432
591
|
return "MAKE_ORDERS_DISABLED";
|
|
433
592
|
}
|
|
593
|
+
if (isMlResultUnavailable({ env, ml })) {
|
|
594
|
+
return "ML_RESULT_UNAVAILABLE";
|
|
595
|
+
}
|
|
596
|
+
if (isMlRuntimeGateEnabled({ env, ml }) && ml?.result?.passed === false) {
|
|
597
|
+
const probability = formatGateNumber(ml.result.probability);
|
|
598
|
+
const threshold = formatGateNumber(ml.result.threshold);
|
|
599
|
+
return `ML_THRESHOLD_NOT_MET (${probability} < ${threshold})`;
|
|
600
|
+
}
|
|
434
601
|
if (env !== "BACKTEST" && aiEnabled && quality == null) {
|
|
435
602
|
return "AI_QUALITY_UNAVAILABLE";
|
|
436
603
|
}
|
|
@@ -439,6 +606,35 @@ var getEntrySkipReason = ({
|
|
|
439
606
|
}
|
|
440
607
|
return "ENTRY_POLICY_BLOCKED";
|
|
441
608
|
};
|
|
609
|
+
var normalizeConfigHookList = (value) => {
|
|
610
|
+
if (Array.isArray(value)) {
|
|
611
|
+
return value;
|
|
612
|
+
}
|
|
613
|
+
return value ? [value] : [];
|
|
614
|
+
};
|
|
615
|
+
var isStrategyDecision = (value) => {
|
|
616
|
+
if (!value || typeof value !== "object") {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
const kind = value.kind;
|
|
620
|
+
return kind === "skip" || kind === "entry" || kind === "exit" || kind === "protect";
|
|
621
|
+
};
|
|
622
|
+
var CONFIG_HOOK_STAGES = [
|
|
623
|
+
"onInit",
|
|
624
|
+
"onBar",
|
|
625
|
+
"afterCoreDecision",
|
|
626
|
+
"afterBarDecision",
|
|
627
|
+
"onSkip",
|
|
628
|
+
"beforeClosePosition",
|
|
629
|
+
"afterEnrichMl",
|
|
630
|
+
"afterEnrichAi",
|
|
631
|
+
"beforeEntryGate",
|
|
632
|
+
"beforePlaceOrder",
|
|
633
|
+
"afterPlaceOrder"
|
|
634
|
+
];
|
|
635
|
+
var isConfigHookStage = (stage) => CONFIG_HOOK_STAGES.includes(
|
|
636
|
+
stage
|
|
637
|
+
);
|
|
442
638
|
var buildHookCtx = ({
|
|
443
639
|
connector,
|
|
444
640
|
strategyName,
|
|
@@ -477,6 +673,13 @@ var buildHookPolicy = ({
|
|
|
477
673
|
makeOrdersEnabled,
|
|
478
674
|
minAiQuality
|
|
479
675
|
});
|
|
676
|
+
var shouldRecordRuntimeJournal = ({
|
|
677
|
+
env,
|
|
678
|
+
config
|
|
679
|
+
}) => env !== "BACKTEST" && env !== "PARITY" && config.RECORD_RUNTIME_TRADES !== false;
|
|
680
|
+
var isTestConnector = (connector) => Boolean(
|
|
681
|
+
connector.__tradejsTestConnector
|
|
682
|
+
);
|
|
480
683
|
var buildMlHookContext = ({
|
|
481
684
|
signal,
|
|
482
685
|
env,
|
|
@@ -581,6 +784,8 @@ var buildAiHookContext = ({
|
|
|
581
784
|
};
|
|
582
785
|
var handleExitDecision = async ({
|
|
583
786
|
connector,
|
|
787
|
+
userName,
|
|
788
|
+
strategyName,
|
|
584
789
|
symbol,
|
|
585
790
|
decision,
|
|
586
791
|
market,
|
|
@@ -593,6 +798,13 @@ var handleExitDecision = async ({
|
|
|
593
798
|
timestamp: decision.closePlan.timestamp,
|
|
594
799
|
direction: decision.closePlan.direction
|
|
595
800
|
});
|
|
801
|
+
await markRuntimeTradeClosed({
|
|
802
|
+
userName,
|
|
803
|
+
strategy: strategyName,
|
|
804
|
+
symbol,
|
|
805
|
+
exitPrice: decision.closePlan.price,
|
|
806
|
+
exitTimestamp: decision.closePlan.timestamp
|
|
807
|
+
});
|
|
596
808
|
} catch (err) {
|
|
597
809
|
await onRuntimeError?.({
|
|
598
810
|
stage: "closePosition",
|
|
@@ -644,12 +856,13 @@ var executeEntryDecision = async ({
|
|
|
644
856
|
policy,
|
|
645
857
|
ml,
|
|
646
858
|
ai,
|
|
647
|
-
|
|
859
|
+
recordRuntimeJournal,
|
|
860
|
+
invokeStageHooks,
|
|
648
861
|
notifyRuntimeError
|
|
649
862
|
}) => {
|
|
650
863
|
const signal = decision.signal;
|
|
651
864
|
const beforePlaceOrder = async () => {
|
|
652
|
-
await
|
|
865
|
+
await invokeStageHooks(
|
|
653
866
|
"beforePlaceOrder",
|
|
654
867
|
manifest?.hooks?.beforePlaceOrder,
|
|
655
868
|
{
|
|
@@ -680,6 +893,7 @@ var executeEntryDecision = async ({
|
|
|
680
893
|
if (signal) {
|
|
681
894
|
await executeEntryOrder({
|
|
682
895
|
connector,
|
|
896
|
+
userName: hookCtx.userName,
|
|
683
897
|
symbol,
|
|
684
898
|
direction: decision.entryContext.direction,
|
|
685
899
|
qty: decision.orderPlan.qty,
|
|
@@ -688,9 +902,10 @@ var executeEntryDecision = async ({
|
|
|
688
902
|
takeProfits: decision.orderPlan.takeProfits,
|
|
689
903
|
stopLossPrice: decision.orderPlan.stopLossPrice,
|
|
690
904
|
signal,
|
|
691
|
-
beforePlaceOrder
|
|
905
|
+
beforePlaceOrder,
|
|
906
|
+
recordRuntimeTrade: recordRuntimeJournal
|
|
692
907
|
});
|
|
693
|
-
await
|
|
908
|
+
await invokeStageHooks(
|
|
694
909
|
"afterPlaceOrder",
|
|
695
910
|
manifest?.hooks?.afterPlaceOrder,
|
|
696
911
|
{
|
|
@@ -738,7 +953,7 @@ var executeEntryDecision = async ({
|
|
|
738
953
|
});
|
|
739
954
|
throw error;
|
|
740
955
|
}
|
|
741
|
-
await
|
|
956
|
+
await invokeStageHooks(
|
|
742
957
|
"afterPlaceOrder",
|
|
743
958
|
manifest?.hooks?.afterPlaceOrder,
|
|
744
959
|
{
|
|
@@ -814,7 +1029,10 @@ var createStrategyRuntime = ({
|
|
|
814
1029
|
baseConfig,
|
|
815
1030
|
defaults
|
|
816
1031
|
});
|
|
1032
|
+
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
1033
|
+
const projectHooks = projectConfig.hooks;
|
|
817
1034
|
const env = String(config.ENV ?? "BACKTEST");
|
|
1035
|
+
const recordRuntimeJournal = shouldRecordRuntimeJournal({ env, config });
|
|
818
1036
|
const strategyManifest = resolveManifest(strategyName);
|
|
819
1037
|
const hookBase = {
|
|
820
1038
|
connector,
|
|
@@ -829,6 +1047,7 @@ var createStrategyRuntime = ({
|
|
|
829
1047
|
...hookBase,
|
|
830
1048
|
strategyName: name
|
|
831
1049
|
});
|
|
1050
|
+
const getProjectHookList = (stage) => normalizeConfigHookList(projectHooks?.[stage]);
|
|
832
1051
|
const notifyRuntimeError = async ({
|
|
833
1052
|
stage,
|
|
834
1053
|
error,
|
|
@@ -838,21 +1057,33 @@ var createStrategyRuntime = ({
|
|
|
838
1057
|
}) => {
|
|
839
1058
|
const errorStrategyName = decision?.kind === "entry" ? decision.entryContext.strategy : strategyName;
|
|
840
1059
|
const errorManifest = resolveManifest(errorStrategyName) ?? strategyManifest;
|
|
1060
|
+
const errorParams = {
|
|
1061
|
+
ctx: getHookCtx(errorStrategyName),
|
|
1062
|
+
market,
|
|
1063
|
+
decision,
|
|
1064
|
+
entry,
|
|
1065
|
+
error: {
|
|
1066
|
+
stage,
|
|
1067
|
+
cause: error
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
for (const projectHook of getProjectHookList("onRuntimeError")) {
|
|
1071
|
+
try {
|
|
1072
|
+
await projectHook(errorParams);
|
|
1073
|
+
} catch (hookError) {
|
|
1074
|
+
logger3.error(
|
|
1075
|
+
"project hook onRuntimeError failed: %s %s",
|
|
1076
|
+
strategyName,
|
|
1077
|
+
hookError
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
841
1081
|
const onRuntimeError = errorManifest?.hooks?.onRuntimeError;
|
|
842
1082
|
if (!onRuntimeError) {
|
|
843
1083
|
return;
|
|
844
1084
|
}
|
|
845
1085
|
try {
|
|
846
|
-
await onRuntimeError(
|
|
847
|
-
ctx: getHookCtx(errorStrategyName),
|
|
848
|
-
market,
|
|
849
|
-
decision,
|
|
850
|
-
entry,
|
|
851
|
-
error: {
|
|
852
|
-
stage,
|
|
853
|
-
cause: error
|
|
854
|
-
}
|
|
855
|
-
});
|
|
1086
|
+
await onRuntimeError(errorParams);
|
|
856
1087
|
} catch (hookError) {
|
|
857
1088
|
logger3.error(
|
|
858
1089
|
"runtime hook onRuntimeError failed: %s %s",
|
|
@@ -884,6 +1115,126 @@ var createStrategyRuntime = ({
|
|
|
884
1115
|
return void 0;
|
|
885
1116
|
}
|
|
886
1117
|
};
|
|
1118
|
+
const invokeProjectHooks = async (stage, params, errorContext = {}) => {
|
|
1119
|
+
const results = [];
|
|
1120
|
+
for (const hook of getProjectHookList(stage)) {
|
|
1121
|
+
const result = await invokeHook(
|
|
1122
|
+
stage,
|
|
1123
|
+
hook,
|
|
1124
|
+
params,
|
|
1125
|
+
errorContext
|
|
1126
|
+
);
|
|
1127
|
+
if (result !== void 0) {
|
|
1128
|
+
results.push(result);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
return results;
|
|
1132
|
+
};
|
|
1133
|
+
const invokeStageHooks = async (stage, hook, params, errorContext = {}) => {
|
|
1134
|
+
if (isConfigHookStage(stage)) {
|
|
1135
|
+
await invokeProjectHooks(stage, params, errorContext);
|
|
1136
|
+
}
|
|
1137
|
+
return invokeHook(stage, hook, params, errorContext);
|
|
1138
|
+
};
|
|
1139
|
+
const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
|
|
1140
|
+
const projectResults = await invokeProjectHooks(
|
|
1141
|
+
stage,
|
|
1142
|
+
params,
|
|
1143
|
+
errorContext
|
|
1144
|
+
);
|
|
1145
|
+
const projectBlock = projectResults.find(
|
|
1146
|
+
(result) => result?.allow === false
|
|
1147
|
+
);
|
|
1148
|
+
if (projectBlock?.allow === false) {
|
|
1149
|
+
return projectBlock;
|
|
1150
|
+
}
|
|
1151
|
+
return invokeHook(
|
|
1152
|
+
stage,
|
|
1153
|
+
hook,
|
|
1154
|
+
params,
|
|
1155
|
+
errorContext
|
|
1156
|
+
);
|
|
1157
|
+
};
|
|
1158
|
+
const applyProjectAfterCoreDecisionHooks = async ({
|
|
1159
|
+
hookCtx,
|
|
1160
|
+
market,
|
|
1161
|
+
decision
|
|
1162
|
+
}) => {
|
|
1163
|
+
let nextDecision = decision;
|
|
1164
|
+
for (const hook of getProjectHookList(
|
|
1165
|
+
"afterCoreDecision"
|
|
1166
|
+
)) {
|
|
1167
|
+
const result = await invokeHook(
|
|
1168
|
+
"afterCoreDecision",
|
|
1169
|
+
hook,
|
|
1170
|
+
{
|
|
1171
|
+
ctx: hookCtx,
|
|
1172
|
+
market,
|
|
1173
|
+
decision: nextDecision
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
decision: nextDecision,
|
|
1177
|
+
market
|
|
1178
|
+
}
|
|
1179
|
+
);
|
|
1180
|
+
if (isStrategyDecision(result)) {
|
|
1181
|
+
nextDecision = result;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return nextDecision;
|
|
1185
|
+
};
|
|
1186
|
+
const applyProjectAfterBarDecisionHooks = async ({
|
|
1187
|
+
hookCtx,
|
|
1188
|
+
market,
|
|
1189
|
+
decision
|
|
1190
|
+
}) => {
|
|
1191
|
+
let nextDecision = decision;
|
|
1192
|
+
for (const hook of getProjectHookList(
|
|
1193
|
+
"afterBarDecision"
|
|
1194
|
+
)) {
|
|
1195
|
+
const result = await invokeHook(
|
|
1196
|
+
"afterBarDecision",
|
|
1197
|
+
hook,
|
|
1198
|
+
{
|
|
1199
|
+
ctx: hookCtx,
|
|
1200
|
+
market,
|
|
1201
|
+
decision: nextDecision
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
decision: nextDecision,
|
|
1205
|
+
market
|
|
1206
|
+
}
|
|
1207
|
+
);
|
|
1208
|
+
if (isStrategyDecision(result)) {
|
|
1209
|
+
nextDecision = result;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return nextDecision;
|
|
1213
|
+
};
|
|
1214
|
+
const applyProjectOnBarHooks = async ({
|
|
1215
|
+
hookCtx,
|
|
1216
|
+
market
|
|
1217
|
+
}) => {
|
|
1218
|
+
for (const hook of getProjectHookList(
|
|
1219
|
+
"onBar"
|
|
1220
|
+
)) {
|
|
1221
|
+
const result = await invokeHook(
|
|
1222
|
+
"onBar",
|
|
1223
|
+
hook,
|
|
1224
|
+
{
|
|
1225
|
+
ctx: hookCtx,
|
|
1226
|
+
market
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
market
|
|
1230
|
+
}
|
|
1231
|
+
);
|
|
1232
|
+
if (isStrategyDecision(result)) {
|
|
1233
|
+
return result;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return void 0;
|
|
1237
|
+
};
|
|
887
1238
|
const indicatorsState = createStrategyIndicatorsState({
|
|
888
1239
|
env,
|
|
889
1240
|
data,
|
|
@@ -917,7 +1268,7 @@ var createStrategyRuntime = ({
|
|
|
917
1268
|
strategyApi,
|
|
918
1269
|
indicatorsState
|
|
919
1270
|
});
|
|
920
|
-
await
|
|
1271
|
+
await invokeStageHooks("onInit", strategyManifest?.hooks?.onInit, {
|
|
921
1272
|
ctx: getHookCtx(),
|
|
922
1273
|
market: {
|
|
923
1274
|
data,
|
|
@@ -932,13 +1283,64 @@ var createStrategyRuntime = ({
|
|
|
932
1283
|
candle,
|
|
933
1284
|
btcCandle
|
|
934
1285
|
};
|
|
935
|
-
const
|
|
1286
|
+
const onBarHookCtx = getHookCtx();
|
|
1287
|
+
const projectOnBarDecision = await applyProjectOnBarHooks({
|
|
1288
|
+
hookCtx: onBarHookCtx,
|
|
1289
|
+
market
|
|
1290
|
+
});
|
|
1291
|
+
let decision;
|
|
1292
|
+
let shouldInvokeAfterCoreDecisionHook = false;
|
|
1293
|
+
if (projectOnBarDecision) {
|
|
1294
|
+
decision = projectOnBarDecision;
|
|
1295
|
+
} else {
|
|
1296
|
+
const manifestOnBarDecision = await invokeHook(
|
|
1297
|
+
"onBar",
|
|
1298
|
+
strategyManifest?.hooks?.onBar,
|
|
1299
|
+
{
|
|
1300
|
+
ctx: onBarHookCtx,
|
|
1301
|
+
market
|
|
1302
|
+
},
|
|
1303
|
+
{ market }
|
|
1304
|
+
);
|
|
1305
|
+
if (isStrategyDecision(manifestOnBarDecision)) {
|
|
1306
|
+
decision = manifestOnBarDecision;
|
|
1307
|
+
} else {
|
|
1308
|
+
decision = await core(candle, btcCandle);
|
|
1309
|
+
shouldInvokeAfterCoreDecisionHook = true;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
if (shouldInvokeAfterCoreDecisionHook) {
|
|
1313
|
+
const initialDecisionStrategyName = decision.kind === "entry" ? decision.entryContext.strategy : strategyName;
|
|
1314
|
+
decision = await applyProjectAfterCoreDecisionHooks({
|
|
1315
|
+
hookCtx: getHookCtx(initialDecisionStrategyName),
|
|
1316
|
+
market,
|
|
1317
|
+
decision
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
const initialAfterBarDecisionStrategyName = decision.kind === "entry" ? decision.entryContext.strategy : strategyName;
|
|
1321
|
+
decision = await applyProjectAfterBarDecisionHooks({
|
|
1322
|
+
hookCtx: getHookCtx(initialAfterBarDecisionStrategyName),
|
|
1323
|
+
market,
|
|
1324
|
+
decision
|
|
1325
|
+
});
|
|
936
1326
|
const decisionStrategyName = decision.kind === "entry" ? decision.entryContext.strategy : strategyName;
|
|
937
1327
|
const decisionManifest = resolveManifest(decisionStrategyName) ?? strategyManifest;
|
|
938
1328
|
const decisionHookCtx = getHookCtx(decisionStrategyName);
|
|
1329
|
+
if (shouldInvokeAfterCoreDecisionHook) {
|
|
1330
|
+
await invokeHook(
|
|
1331
|
+
"afterCoreDecision",
|
|
1332
|
+
decisionManifest?.hooks?.afterCoreDecision,
|
|
1333
|
+
{
|
|
1334
|
+
ctx: decisionHookCtx,
|
|
1335
|
+
market,
|
|
1336
|
+
decision
|
|
1337
|
+
},
|
|
1338
|
+
{ decision, market }
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
939
1341
|
await invokeHook(
|
|
940
|
-
"
|
|
941
|
-
decisionManifest?.hooks?.
|
|
1342
|
+
"afterBarDecision",
|
|
1343
|
+
decisionManifest?.hooks?.afterBarDecision,
|
|
942
1344
|
{
|
|
943
1345
|
ctx: decisionHookCtx,
|
|
944
1346
|
market,
|
|
@@ -947,7 +1349,7 @@ var createStrategyRuntime = ({
|
|
|
947
1349
|
{ decision, market }
|
|
948
1350
|
);
|
|
949
1351
|
if (decision.kind === "skip") {
|
|
950
|
-
await
|
|
1352
|
+
await invokeStageHooks(
|
|
951
1353
|
"onSkip",
|
|
952
1354
|
decisionManifest?.hooks?.onSkip,
|
|
953
1355
|
{
|
|
@@ -959,12 +1361,13 @@ var createStrategyRuntime = ({
|
|
|
959
1361
|
);
|
|
960
1362
|
return decision.code;
|
|
961
1363
|
}
|
|
962
|
-
const
|
|
1364
|
+
const rawMakeOrdersEnabled = typeof config.MAKE_ORDERS === "boolean" ? config.MAKE_ORDERS : true;
|
|
1365
|
+
const makeOrdersEnabled = rawMakeOrdersEnabled && (env !== "PARITY" || isTestConnector(connector));
|
|
963
1366
|
if (decision.kind === "exit") {
|
|
964
1367
|
if (!makeOrdersEnabled) {
|
|
965
1368
|
return decision.code;
|
|
966
1369
|
}
|
|
967
|
-
const closeGate = await
|
|
1370
|
+
const closeGate = await invokeGateHooks(
|
|
968
1371
|
"beforeClosePosition",
|
|
969
1372
|
decisionManifest?.hooks?.beforeClosePosition,
|
|
970
1373
|
{
|
|
@@ -979,6 +1382,8 @@ var createStrategyRuntime = ({
|
|
|
979
1382
|
}
|
|
980
1383
|
return handleExitDecision({
|
|
981
1384
|
connector,
|
|
1385
|
+
userName: recordRuntimeJournal ? userName : void 0,
|
|
1386
|
+
strategyName,
|
|
982
1387
|
symbol,
|
|
983
1388
|
decision,
|
|
984
1389
|
market,
|
|
@@ -1054,7 +1459,7 @@ var createStrategyRuntime = ({
|
|
|
1054
1459
|
env,
|
|
1055
1460
|
ml: runtime.ml
|
|
1056
1461
|
});
|
|
1057
|
-
await
|
|
1462
|
+
await invokeStageHooks(
|
|
1058
1463
|
"afterEnrichMl",
|
|
1059
1464
|
decisionManifest?.hooks?.afterEnrichMl,
|
|
1060
1465
|
{
|
|
@@ -1071,6 +1476,10 @@ var createStrategyRuntime = ({
|
|
|
1071
1476
|
let ai;
|
|
1072
1477
|
if (signal) {
|
|
1073
1478
|
try {
|
|
1479
|
+
await enrichSignalWithDerivativesContext({
|
|
1480
|
+
signal,
|
|
1481
|
+
env
|
|
1482
|
+
});
|
|
1074
1483
|
quality = await enrichSignalWithAi({
|
|
1075
1484
|
signal,
|
|
1076
1485
|
userName,
|
|
@@ -1094,7 +1503,7 @@ var createStrategyRuntime = ({
|
|
|
1094
1503
|
ai: runtime.ai,
|
|
1095
1504
|
quality
|
|
1096
1505
|
});
|
|
1097
|
-
await
|
|
1506
|
+
await invokeStageHooks(
|
|
1098
1507
|
"afterEnrichAi",
|
|
1099
1508
|
decisionManifest?.hooks?.afterEnrichAi,
|
|
1100
1509
|
{
|
|
@@ -1119,6 +1528,7 @@ var createStrategyRuntime = ({
|
|
|
1119
1528
|
makeOrdersEnabled,
|
|
1120
1529
|
env,
|
|
1121
1530
|
signal,
|
|
1531
|
+
ml,
|
|
1122
1532
|
aiEnabled,
|
|
1123
1533
|
quality,
|
|
1124
1534
|
minAiQuality
|
|
@@ -1129,6 +1539,7 @@ var createStrategyRuntime = ({
|
|
|
1129
1539
|
signal.orderSkipReason = getEntrySkipReason({
|
|
1130
1540
|
makeOrdersEnabled,
|
|
1131
1541
|
env,
|
|
1542
|
+
ml,
|
|
1132
1543
|
aiEnabled,
|
|
1133
1544
|
quality,
|
|
1134
1545
|
minAiQuality
|
|
@@ -1136,7 +1547,7 @@ var createStrategyRuntime = ({
|
|
|
1136
1547
|
}
|
|
1137
1548
|
return signal ?? decision.code;
|
|
1138
1549
|
}
|
|
1139
|
-
const entryGate = await
|
|
1550
|
+
const entryGate = await invokeGateHooks(
|
|
1140
1551
|
"beforeEntryGate",
|
|
1141
1552
|
decisionManifest?.hooks?.beforeEntryGate,
|
|
1142
1553
|
{
|
|
@@ -1170,18 +1581,108 @@ var createStrategyRuntime = ({
|
|
|
1170
1581
|
policy,
|
|
1171
1582
|
ml,
|
|
1172
1583
|
ai,
|
|
1173
|
-
|
|
1584
|
+
recordRuntimeJournal,
|
|
1585
|
+
invokeStageHooks,
|
|
1174
1586
|
notifyRuntimeError
|
|
1175
1587
|
});
|
|
1176
1588
|
};
|
|
1177
1589
|
};
|
|
1178
1590
|
};
|
|
1179
1591
|
|
|
1180
|
-
// src/
|
|
1592
|
+
// src/strategyHooks/closeOppositePositionsBeforeOpen.ts
|
|
1593
|
+
var import_lodash2 = __toESM(require_lodash());
|
|
1594
|
+
import { logger as logger4 } from "@tradejs/infra/logger";
|
|
1595
|
+
var closeOppositePositionsBeforeOpen = async ({
|
|
1596
|
+
connector,
|
|
1597
|
+
entryContext
|
|
1598
|
+
}) => {
|
|
1599
|
+
const {
|
|
1600
|
+
symbol: currentSymbol,
|
|
1601
|
+
direction: currentDirection,
|
|
1602
|
+
timestamp,
|
|
1603
|
+
prices,
|
|
1604
|
+
strategy: strategyName
|
|
1605
|
+
} = entryContext;
|
|
1606
|
+
const price = prices.currentPrice;
|
|
1607
|
+
try {
|
|
1608
|
+
logger4.log(
|
|
1609
|
+
"info",
|
|
1610
|
+
"[%s] checking open positions before open: %s %s",
|
|
1611
|
+
strategyName,
|
|
1612
|
+
currentSymbol,
|
|
1613
|
+
currentDirection
|
|
1614
|
+
);
|
|
1615
|
+
const positions = await connector.getPositions();
|
|
1616
|
+
const openPositions = (positions || []).filter(
|
|
1617
|
+
(item) => item && Number(item.qty) > 0
|
|
1618
|
+
);
|
|
1619
|
+
logger4.log(
|
|
1620
|
+
"info",
|
|
1621
|
+
"[%s] open positions found: %s",
|
|
1622
|
+
strategyName,
|
|
1623
|
+
openPositions.length
|
|
1624
|
+
);
|
|
1625
|
+
const oppositePositions = openPositions.filter(
|
|
1626
|
+
(item) => item.symbol !== currentSymbol && item.direction !== currentDirection
|
|
1627
|
+
);
|
|
1628
|
+
if (import_lodash2.default.isEmpty(oppositePositions)) {
|
|
1629
|
+
logger4.log(
|
|
1630
|
+
"info",
|
|
1631
|
+
"[%s] no opposite positions to close before open: %s",
|
|
1632
|
+
strategyName,
|
|
1633
|
+
currentSymbol
|
|
1634
|
+
);
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
for (const position of oppositePositions) {
|
|
1638
|
+
logger4.log(
|
|
1639
|
+
"info",
|
|
1640
|
+
"[%s] closing opposite position: %s %s qty=%s",
|
|
1641
|
+
strategyName,
|
|
1642
|
+
position.symbol,
|
|
1643
|
+
position.direction,
|
|
1644
|
+
position.qty
|
|
1645
|
+
);
|
|
1646
|
+
try {
|
|
1647
|
+
await connector.closePosition({
|
|
1648
|
+
symbol: position.symbol,
|
|
1649
|
+
price,
|
|
1650
|
+
timestamp,
|
|
1651
|
+
direction: position.direction
|
|
1652
|
+
});
|
|
1653
|
+
logger4.log(
|
|
1654
|
+
"info",
|
|
1655
|
+
"[%s] opposite position closed: %s",
|
|
1656
|
+
strategyName,
|
|
1657
|
+
position.symbol
|
|
1658
|
+
);
|
|
1659
|
+
} catch (err) {
|
|
1660
|
+
logger4.log(
|
|
1661
|
+
"error",
|
|
1662
|
+
"[%s] failed to close opposite position: %s %s",
|
|
1663
|
+
strategyName,
|
|
1664
|
+
position.symbol,
|
|
1665
|
+
err
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
} catch (err) {
|
|
1670
|
+
logger4.log(
|
|
1671
|
+
"error",
|
|
1672
|
+
"[%s] failed to load open positions before open: %s %s",
|
|
1673
|
+
strategyName,
|
|
1674
|
+
currentSymbol,
|
|
1675
|
+
err
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1181
1679
|
var createCloseOppositeBeforePlaceOrderHook = ({
|
|
1182
1680
|
isEnabled
|
|
1183
1681
|
}) => {
|
|
1184
1682
|
return async ({ ctx, entry }) => {
|
|
1683
|
+
if (ctx.env === "BACKTEST") {
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1185
1686
|
if (!isEnabled(ctx.strategyConfig)) {
|
|
1186
1687
|
return;
|
|
1187
1688
|
}
|
|
@@ -1191,6 +1692,227 @@ var createCloseOppositeBeforePlaceOrderHook = ({
|
|
|
1191
1692
|
});
|
|
1192
1693
|
};
|
|
1193
1694
|
};
|
|
1695
|
+
|
|
1696
|
+
// src/strategyHooks/shared.ts
|
|
1697
|
+
var DEFAULT_BREAK_EVEN_TRIGGER_RISK_MULTIPLIER = 0.5;
|
|
1698
|
+
var DEFAULT_GLOBAL_UNREALIZED_PNL_TRIGGER_RISK_MULTIPLIER = 4;
|
|
1699
|
+
var GLOBAL_UNREALIZED_PNL_CLOSE_ALL_CODE = "GLOBAL_UNREALIZED_PNL_TARGET_REACHED_CLOSE_ALL";
|
|
1700
|
+
var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
1701
|
+
var isOpenPosition = (position) => Boolean(
|
|
1702
|
+
position && isFiniteNumber(position.price) && isFiniteNumber(position.qty) && position.qty > 0 && (position.direction === "LONG" || position.direction === "SHORT")
|
|
1703
|
+
);
|
|
1704
|
+
var isOpenPositionPnlSnapshot = (position) => Boolean(
|
|
1705
|
+
isOpenPosition(position) && isFiniteNumber(position?.currentPrice) && isFiniteNumber(position?.unrealizedPnl)
|
|
1706
|
+
);
|
|
1707
|
+
var getStrategyMaxLossValue = (strategyConfig) => {
|
|
1708
|
+
const maxLossValue = Number(strategyConfig?.MAX_LOSS_VALUE ?? Number.NaN);
|
|
1709
|
+
return Number.isFinite(maxLossValue) && maxLossValue > 0 ? maxLossValue : null;
|
|
1710
|
+
};
|
|
1711
|
+
var getPositionStopLossPrice = (position) => {
|
|
1712
|
+
if (!position || typeof position !== "object") {
|
|
1713
|
+
return null;
|
|
1714
|
+
}
|
|
1715
|
+
const slPrice = Number(
|
|
1716
|
+
position.slPrice ?? Number.NaN
|
|
1717
|
+
);
|
|
1718
|
+
if (Number.isFinite(slPrice)) {
|
|
1719
|
+
return slPrice;
|
|
1720
|
+
}
|
|
1721
|
+
const signalStopLossPrice = Number(
|
|
1722
|
+
position.signal?.prices?.stopLossPrice ?? Number.NaN
|
|
1723
|
+
);
|
|
1724
|
+
return Number.isFinite(signalStopLossPrice) ? signalStopLossPrice : null;
|
|
1725
|
+
};
|
|
1726
|
+
var getFavorableMovePct = ({
|
|
1727
|
+
direction,
|
|
1728
|
+
entryPrice,
|
|
1729
|
+
currentPrice
|
|
1730
|
+
}) => {
|
|
1731
|
+
if (!Number.isFinite(entryPrice) || !Number.isFinite(currentPrice) || entryPrice <= 0) {
|
|
1732
|
+
return null;
|
|
1733
|
+
}
|
|
1734
|
+
return direction === "LONG" ? (currentPrice - entryPrice) / entryPrice * 100 : (entryPrice - currentPrice) / entryPrice * 100;
|
|
1735
|
+
};
|
|
1736
|
+
var getPositionRiskPct = ({
|
|
1737
|
+
direction,
|
|
1738
|
+
entryPrice,
|
|
1739
|
+
stopLossPrice
|
|
1740
|
+
}) => {
|
|
1741
|
+
if (stopLossPrice == null || !Number.isFinite(entryPrice) || !Number.isFinite(stopLossPrice) || entryPrice <= 0) {
|
|
1742
|
+
return null;
|
|
1743
|
+
}
|
|
1744
|
+
return direction === "LONG" ? (entryPrice - stopLossPrice) / entryPrice * 100 : (stopLossPrice - entryPrice) / entryPrice * 100;
|
|
1745
|
+
};
|
|
1746
|
+
var isBreakEvenStopAlreadyApplied = ({
|
|
1747
|
+
direction,
|
|
1748
|
+
entryPrice,
|
|
1749
|
+
stopLossPrice
|
|
1750
|
+
}) => {
|
|
1751
|
+
if (stopLossPrice == null || !Number.isFinite(entryPrice) || !Number.isFinite(stopLossPrice)) {
|
|
1752
|
+
return false;
|
|
1753
|
+
}
|
|
1754
|
+
return direction === "LONG" ? stopLossPrice >= entryPrice : stopLossPrice <= entryPrice;
|
|
1755
|
+
};
|
|
1756
|
+
var getConfiguredDirectionRiskPct = ({
|
|
1757
|
+
strategyConfig,
|
|
1758
|
+
direction
|
|
1759
|
+
}) => {
|
|
1760
|
+
if (!strategyConfig || typeof strategyConfig !== "object") {
|
|
1761
|
+
return null;
|
|
1762
|
+
}
|
|
1763
|
+
const directSideConfig = strategyConfig[direction];
|
|
1764
|
+
const directSideRiskPct = Number(directSideConfig?.SL ?? Number.NaN);
|
|
1765
|
+
if (Number.isFinite(directSideRiskPct)) {
|
|
1766
|
+
return directSideRiskPct;
|
|
1767
|
+
}
|
|
1768
|
+
for (const candidate of Object.values(strategyConfig)) {
|
|
1769
|
+
if (!candidate || typeof candidate !== "object") {
|
|
1770
|
+
continue;
|
|
1771
|
+
}
|
|
1772
|
+
const candidateDirection = candidate.direction;
|
|
1773
|
+
const candidateRiskPct = Number(
|
|
1774
|
+
candidate.SL ?? Number.NaN
|
|
1775
|
+
);
|
|
1776
|
+
if (candidateDirection === direction && Number.isFinite(candidateRiskPct)) {
|
|
1777
|
+
return candidateRiskPct;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
return null;
|
|
1781
|
+
};
|
|
1782
|
+
var toStrategyCodePrefix = (strategyName) => strategyName === "TrendLine" ? "TRENDLINE" : strategyName.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase();
|
|
1783
|
+
|
|
1784
|
+
// src/strategyHooks/moveStopToBreakEvenAfterCoreDecision.ts
|
|
1785
|
+
var createMoveStopToBreakEvenOnBarHook = ({
|
|
1786
|
+
isEnabled = () => true,
|
|
1787
|
+
triggerRiskMultiplier = DEFAULT_BREAK_EVEN_TRIGGER_RISK_MULTIPLIER
|
|
1788
|
+
} = {}) => {
|
|
1789
|
+
return async ({ ctx, market }) => {
|
|
1790
|
+
if (!isEnabled(ctx.strategyConfig)) {
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
const currentPosition = await ctx.connector.getPosition(ctx.symbol);
|
|
1794
|
+
if (!isOpenPosition(currentPosition)) {
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
const currentPrice = Number(market.candle.close ?? Number.NaN);
|
|
1798
|
+
if (!Number.isFinite(currentPrice)) {
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
const currentStopLossPrice = getPositionStopLossPrice(currentPosition);
|
|
1802
|
+
if (isBreakEvenStopAlreadyApplied({
|
|
1803
|
+
direction: currentPosition.direction,
|
|
1804
|
+
entryPrice: currentPosition.price,
|
|
1805
|
+
stopLossPrice: currentStopLossPrice
|
|
1806
|
+
})) {
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
const favorableMovePct = getFavorableMovePct({
|
|
1810
|
+
direction: currentPosition.direction,
|
|
1811
|
+
entryPrice: currentPosition.price,
|
|
1812
|
+
currentPrice
|
|
1813
|
+
});
|
|
1814
|
+
const currentPositionRiskPct = getPositionRiskPct({
|
|
1815
|
+
direction: currentPosition.direction,
|
|
1816
|
+
entryPrice: currentPosition.price,
|
|
1817
|
+
stopLossPrice: currentStopLossPrice
|
|
1818
|
+
});
|
|
1819
|
+
const configuredRiskPct = getConfiguredDirectionRiskPct({
|
|
1820
|
+
strategyConfig: ctx.strategyConfig,
|
|
1821
|
+
direction: currentPosition.direction
|
|
1822
|
+
});
|
|
1823
|
+
const triggerRiskPct = currentPositionRiskPct ?? configuredRiskPct;
|
|
1824
|
+
if (favorableMovePct == null || triggerRiskPct == null || favorableMovePct < triggerRiskPct * triggerRiskMultiplier) {
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
return {
|
|
1828
|
+
kind: "protect",
|
|
1829
|
+
code: `${toStrategyCodePrefix(ctx.strategyName)}_MOVE_STOP_TO_BREAK_EVEN`,
|
|
1830
|
+
protectPlan: {
|
|
1831
|
+
direction: currentPosition.direction,
|
|
1832
|
+
stopLossPrice: currentPosition.price
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1835
|
+
};
|
|
1836
|
+
};
|
|
1837
|
+
var createMoveStopToBreakEvenAfterCoreDecisionHook = createMoveStopToBreakEvenOnBarHook;
|
|
1838
|
+
|
|
1839
|
+
// src/signalsHooks/closeAllPositionsOnGlobalProfitBeforeSignals.ts
|
|
1840
|
+
import { logger as logger5 } from "@tradejs/infra/logger";
|
|
1841
|
+
var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
|
|
1842
|
+
getStrategyDefaultConfig = () => void 0,
|
|
1843
|
+
profitRiskMultiplier = DEFAULT_GLOBAL_UNREALIZED_PNL_TRIGGER_RISK_MULTIPLIER
|
|
1844
|
+
} = {}) => {
|
|
1845
|
+
return async ({ connector, runtimeStrategies }) => {
|
|
1846
|
+
if (typeof connector.getOpenPositionPnl !== "function") {
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
const openPositions = (await connector.getOpenPositionPnl()).filter(
|
|
1850
|
+
isOpenPositionPnlSnapshot
|
|
1851
|
+
);
|
|
1852
|
+
if (!openPositions.length) {
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
const totalUnrealizedPnl = openPositions.reduce(
|
|
1856
|
+
(sum, position) => sum + position.unrealizedPnl,
|
|
1857
|
+
0
|
|
1858
|
+
);
|
|
1859
|
+
if (!Number.isFinite(totalUnrealizedPnl) || totalUnrealizedPnl <= 0) {
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
const maxLossValues = runtimeStrategies.flatMap(
|
|
1863
|
+
({ strategyName, strategyConfig }) => {
|
|
1864
|
+
const maxLossValue = getStrategyMaxLossValue({
|
|
1865
|
+
...getStrategyDefaultConfig(strategyName) ?? {},
|
|
1866
|
+
...strategyConfig ?? {}
|
|
1867
|
+
});
|
|
1868
|
+
return maxLossValue == null ? [] : [maxLossValue];
|
|
1869
|
+
}
|
|
1870
|
+
);
|
|
1871
|
+
if (!maxLossValues.length) {
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
const averageMaxLossValue = maxLossValues.reduce((sum, value) => sum + value, 0) / maxLossValues.length;
|
|
1875
|
+
const unrealizedPnlThreshold = averageMaxLossValue * profitRiskMultiplier;
|
|
1876
|
+
if (!Number.isFinite(unrealizedPnlThreshold) || unrealizedPnlThreshold <= 0 || totalUnrealizedPnl < unrealizedPnlThreshold) {
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
logger5.info(
|
|
1880
|
+
"closing all positions before signals by global unrealized pnl threshold: totalPnl=%s threshold=%s positions=%s",
|
|
1881
|
+
totalUnrealizedPnl,
|
|
1882
|
+
unrealizedPnlThreshold,
|
|
1883
|
+
openPositions.length
|
|
1884
|
+
);
|
|
1885
|
+
const closeTimestamp = Date.now();
|
|
1886
|
+
const closeResults = await Promise.allSettled(
|
|
1887
|
+
openPositions.map(
|
|
1888
|
+
(position) => connector.closePosition({
|
|
1889
|
+
symbol: position.symbol,
|
|
1890
|
+
direction: position.direction,
|
|
1891
|
+
price: position.currentPrice,
|
|
1892
|
+
timestamp: closeTimestamp
|
|
1893
|
+
})
|
|
1894
|
+
)
|
|
1895
|
+
);
|
|
1896
|
+
const failedClosures = closeResults.flatMap((result, index) => {
|
|
1897
|
+
if (result.status === "fulfilled" && result.value === true) {
|
|
1898
|
+
return [];
|
|
1899
|
+
}
|
|
1900
|
+
return [
|
|
1901
|
+
`${openPositions[index]?.symbol}:${openPositions[index]?.direction ?? "UNKNOWN"}`
|
|
1902
|
+
];
|
|
1903
|
+
});
|
|
1904
|
+
if (failedClosures.length) {
|
|
1905
|
+
logger5.warn(
|
|
1906
|
+
"close-all before signals hook could not confirm closures for %s",
|
|
1907
|
+
failedClosures.join(", ")
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
return {
|
|
1911
|
+
abort: true,
|
|
1912
|
+
reason: GLOBAL_UNREALIZED_PNL_CLOSE_ALL_CODE
|
|
1913
|
+
};
|
|
1914
|
+
};
|
|
1915
|
+
};
|
|
1194
1916
|
export {
|
|
1195
1917
|
DEFAULT_AI_MODEL,
|
|
1196
1918
|
MAX_AI_SERIES_POINTS,
|
|
@@ -1200,7 +1922,10 @@ export {
|
|
|
1200
1922
|
buildAiPrompts,
|
|
1201
1923
|
buildAiSystemPrompt,
|
|
1202
1924
|
closeOppositePositionsBeforeOpen,
|
|
1925
|
+
createCloseAllOnGlobalProfitBeforeSignalsHook,
|
|
1203
1926
|
createCloseOppositeBeforePlaceOrderHook,
|
|
1927
|
+
createMoveStopToBreakEvenAfterCoreDecisionHook,
|
|
1928
|
+
createMoveStopToBreakEvenOnBarHook,
|
|
1204
1929
|
createStrategyRuntime,
|
|
1205
1930
|
enrichSignalWithAi,
|
|
1206
1931
|
enrichSignalWithMl,
|