@tradejs/node 3.1.3 → 3.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/node",
3
- "version": "3.1.3",
3
+ "version": "3.1.5",
4
4
  "description": "Node-only runtime for the TradeJS TypeScript framework: strategies, backtests, Pine strategy loading, and plugin registries.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -85,9 +85,9 @@
85
85
  "dependencies": {
86
86
  "@langchain/core": "^1.2.3",
87
87
  "@langchain/openai": "^1.5.5",
88
- "@tradejs/core": "^3.1.3",
89
- "@tradejs/infra": "^3.1.3",
90
- "@tradejs/types": "^3.1.3",
88
+ "@tradejs/core": "^3.1.5",
89
+ "@tradejs/infra": "^3.1.5",
90
+ "@tradejs/types": "^3.1.5",
91
91
  "chalk": "4.1.2",
92
92
  "ioredis": "5.11.1",
93
93
  "lodash": "^4.18.1",
@@ -1,377 +0,0 @@
1
- // src/strategies.ts
2
- export * from "@tradejs/core/strategies";
3
-
4
- // src/strategyHooks/closeOppositePositionsBeforeOpen.ts
5
- import _ from "lodash";
6
- import { logger } from "@tradejs/infra/logger";
7
- var closeOppositePositionsBeforeOpen = async ({
8
- connector,
9
- entryContext
10
- }) => {
11
- const {
12
- symbol: currentSymbol,
13
- direction: currentDirection,
14
- timestamp,
15
- prices,
16
- strategy: strategyName
17
- } = entryContext;
18
- const price = prices.currentPrice;
19
- try {
20
- logger.log(
21
- "info",
22
- "[%s] checking open positions before open: %s %s",
23
- strategyName,
24
- currentSymbol,
25
- currentDirection
26
- );
27
- const positions = await connector.getPositions();
28
- const openPositions = (positions || []).filter(
29
- (item) => item && Number(item.qty) > 0
30
- );
31
- logger.log(
32
- "info",
33
- "[%s] open positions found: %s",
34
- strategyName,
35
- openPositions.length
36
- );
37
- const oppositePositions = openPositions.filter(
38
- (item) => item.symbol !== currentSymbol && item.direction !== currentDirection
39
- );
40
- if (_.isEmpty(oppositePositions)) {
41
- logger.log(
42
- "info",
43
- "[%s] no opposite positions to close before open: %s",
44
- strategyName,
45
- currentSymbol
46
- );
47
- return;
48
- }
49
- for (const position of oppositePositions) {
50
- logger.log(
51
- "info",
52
- "[%s] closing opposite position: %s %s qty=%s",
53
- strategyName,
54
- position.symbol,
55
- position.direction,
56
- position.qty
57
- );
58
- try {
59
- await connector.closePosition({
60
- symbol: position.symbol,
61
- price,
62
- timestamp,
63
- direction: position.direction
64
- });
65
- logger.log(
66
- "info",
67
- "[%s] opposite position closed: %s",
68
- strategyName,
69
- position.symbol
70
- );
71
- } catch (err) {
72
- logger.log(
73
- "error",
74
- "[%s] failed to close opposite position: %s %s",
75
- strategyName,
76
- position.symbol,
77
- err
78
- );
79
- }
80
- }
81
- } catch (err) {
82
- logger.log(
83
- "error",
84
- "[%s] failed to load open positions before open: %s %s",
85
- strategyName,
86
- currentSymbol,
87
- err
88
- );
89
- }
90
- };
91
- var createCloseOppositeBeforePlaceOrderHook = ({
92
- isEnabled
93
- }) => {
94
- return async ({ ctx, entry }) => {
95
- if (ctx.env === "BACKTEST") {
96
- return;
97
- }
98
- if (!isEnabled(ctx.strategyConfig)) {
99
- return;
100
- }
101
- await closeOppositePositionsBeforeOpen({
102
- connector: ctx.connector,
103
- entryContext: entry.context
104
- });
105
- };
106
- };
107
-
108
- // src/strategyHooks/shared.ts
109
- var DEFAULT_BREAK_EVEN_TRIGGER_RISK_MULTIPLIER = 0.5;
110
- var DEFAULT_BREAK_EVEN_STOP_PROFIT_MULTIPLIER = 0;
111
- var DEFAULT_GLOBAL_UNREALIZED_PNL_TRIGGER_RISK_MULTIPLIER = 4;
112
- var GLOBAL_UNREALIZED_PNL_CLOSE_ALL_CODE = "GLOBAL_UNREALIZED_PNL_TARGET_REACHED_CLOSE_ALL";
113
- var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
114
- var isOpenPosition = (position) => Boolean(
115
- position && isFiniteNumber(position.price) && isFiniteNumber(position.qty) && position.qty > 0 && (position.direction === "LONG" || position.direction === "SHORT")
116
- );
117
- var isOpenPositionPnlSnapshot = (position) => Boolean(
118
- isOpenPosition(position) && isFiniteNumber(position?.currentPrice) && isFiniteNumber(position?.unrealizedPnl)
119
- );
120
- var getStrategyMaxLossValue = (strategyConfig) => {
121
- const maxLossValue = Number(strategyConfig?.MAX_LOSS_VALUE ?? Number.NaN);
122
- return Number.isFinite(maxLossValue) && maxLossValue > 0 ? maxLossValue : null;
123
- };
124
- var getPositionStopLossPrice = (position) => {
125
- if (!position || typeof position !== "object") {
126
- return null;
127
- }
128
- const slPrice = Number(
129
- position.slPrice ?? Number.NaN
130
- );
131
- if (Number.isFinite(slPrice)) {
132
- return slPrice;
133
- }
134
- const signalStopLossPrice = Number(
135
- position.signal?.prices?.stopLossPrice ?? Number.NaN
136
- );
137
- return Number.isFinite(signalStopLossPrice) ? signalStopLossPrice : null;
138
- };
139
- var getPositionTakeProfitPrice = (position) => {
140
- if (!position || typeof position !== "object") {
141
- return null;
142
- }
143
- const directTakeProfitPrice = Number(
144
- position.tpPrice ?? position.takeProfitPrice ?? Number.NaN
145
- );
146
- if (Number.isFinite(directTakeProfitPrice)) {
147
- return directTakeProfitPrice;
148
- }
149
- const signalTakeProfitPrice = Number(
150
- position.signal?.prices?.takeProfitPrice ?? Number.NaN
151
- );
152
- return Number.isFinite(signalTakeProfitPrice) ? signalTakeProfitPrice : null;
153
- };
154
- var getBreakEvenStopPrice = ({
155
- direction,
156
- entryPrice,
157
- takeProfitPrice,
158
- stopProfitMultiplier
159
- }) => {
160
- if (!Number.isFinite(entryPrice)) {
161
- return null;
162
- }
163
- const normalizedStopProfitMultiplier = Number.isFinite(stopProfitMultiplier) ? Math.min(Math.max(stopProfitMultiplier, 0), 1) : DEFAULT_BREAK_EVEN_STOP_PROFIT_MULTIPLIER;
164
- if (takeProfitPrice == null || !Number.isFinite(takeProfitPrice) || direction === "LONG" && takeProfitPrice <= entryPrice || direction === "SHORT" && takeProfitPrice >= entryPrice) {
165
- return entryPrice;
166
- }
167
- const distanceToTakeProfit = takeProfitPrice - entryPrice;
168
- return entryPrice + distanceToTakeProfit * normalizedStopProfitMultiplier;
169
- };
170
- var getFavorableMovePct = ({
171
- direction,
172
- entryPrice,
173
- currentPrice
174
- }) => {
175
- if (!Number.isFinite(entryPrice) || !Number.isFinite(currentPrice) || entryPrice <= 0) {
176
- return null;
177
- }
178
- return direction === "LONG" ? (currentPrice - entryPrice) / entryPrice * 100 : (entryPrice - currentPrice) / entryPrice * 100;
179
- };
180
- var getPositionRiskPct = ({
181
- direction,
182
- entryPrice,
183
- stopLossPrice
184
- }) => {
185
- if (stopLossPrice == null || !Number.isFinite(entryPrice) || !Number.isFinite(stopLossPrice) || entryPrice <= 0) {
186
- return null;
187
- }
188
- return direction === "LONG" ? (entryPrice - stopLossPrice) / entryPrice * 100 : (stopLossPrice - entryPrice) / entryPrice * 100;
189
- };
190
- var isBreakEvenStopAlreadyApplied = ({
191
- direction,
192
- entryPrice,
193
- stopLossPrice
194
- }) => {
195
- if (stopLossPrice == null || !Number.isFinite(entryPrice) || !Number.isFinite(stopLossPrice)) {
196
- return false;
197
- }
198
- return direction === "LONG" ? stopLossPrice >= entryPrice : stopLossPrice <= entryPrice;
199
- };
200
- var getConfiguredDirectionRiskPct = ({
201
- strategyConfig,
202
- direction
203
- }) => {
204
- if (!strategyConfig || typeof strategyConfig !== "object") {
205
- return null;
206
- }
207
- const directSideConfig = strategyConfig[direction];
208
- const directSideRiskPct = Number(directSideConfig?.SL ?? Number.NaN);
209
- if (Number.isFinite(directSideRiskPct)) {
210
- return directSideRiskPct;
211
- }
212
- for (const candidate of Object.values(strategyConfig)) {
213
- if (!candidate || typeof candidate !== "object") {
214
- continue;
215
- }
216
- const candidateDirection = candidate.direction;
217
- const candidateRiskPct = Number(
218
- candidate.SL ?? Number.NaN
219
- );
220
- if (candidateDirection === direction && Number.isFinite(candidateRiskPct)) {
221
- return candidateRiskPct;
222
- }
223
- }
224
- return null;
225
- };
226
- var toStrategyCodePrefix = (strategyName) => strategyName === "TrendLine" ? "TRENDLINE" : strategyName.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase();
227
-
228
- // src/strategyHooks/moveStopToBreakEvenAfterCoreDecision.ts
229
- var createMoveStopToBreakEvenOnBarHook = ({
230
- isEnabled = () => true,
231
- triggerRiskMultiplier = DEFAULT_BREAK_EVEN_TRIGGER_RISK_MULTIPLIER,
232
- stopProfitMultiplier = DEFAULT_BREAK_EVEN_STOP_PROFIT_MULTIPLIER
233
- } = {}) => {
234
- return async ({ ctx, market }) => {
235
- if (!isEnabled(ctx.strategyConfig)) {
236
- return;
237
- }
238
- const currentPosition = await ctx.connector.getPosition(ctx.symbol);
239
- if (!isOpenPosition(currentPosition)) {
240
- return;
241
- }
242
- const currentPrice = Number(market.candle.close ?? Number.NaN);
243
- if (!Number.isFinite(currentPrice)) {
244
- return;
245
- }
246
- const currentStopLossPrice = getPositionStopLossPrice(currentPosition);
247
- if (isBreakEvenStopAlreadyApplied({
248
- direction: currentPosition.direction,
249
- entryPrice: currentPosition.price,
250
- stopLossPrice: currentStopLossPrice
251
- })) {
252
- return;
253
- }
254
- const favorableMovePct = getFavorableMovePct({
255
- direction: currentPosition.direction,
256
- entryPrice: currentPosition.price,
257
- currentPrice
258
- });
259
- const currentPositionRiskPct = getPositionRiskPct({
260
- direction: currentPosition.direction,
261
- entryPrice: currentPosition.price,
262
- stopLossPrice: currentStopLossPrice
263
- });
264
- const configuredRiskPct = getConfiguredDirectionRiskPct({
265
- strategyConfig: ctx.strategyConfig,
266
- direction: currentPosition.direction
267
- });
268
- const triggerRiskPct = currentPositionRiskPct ?? configuredRiskPct;
269
- if (favorableMovePct == null || triggerRiskPct == null || favorableMovePct < triggerRiskPct * triggerRiskMultiplier) {
270
- return;
271
- }
272
- const stopLossPrice = getBreakEvenStopPrice({
273
- direction: currentPosition.direction,
274
- entryPrice: currentPosition.price,
275
- takeProfitPrice: getPositionTakeProfitPrice(currentPosition),
276
- stopProfitMultiplier
277
- });
278
- if (stopLossPrice == null) {
279
- return;
280
- }
281
- return {
282
- kind: "protect",
283
- code: `${toStrategyCodePrefix(ctx.strategyName)}_MOVE_STOP_TO_BREAK_EVEN`,
284
- protectPlan: {
285
- direction: currentPosition.direction,
286
- stopLossPrice
287
- }
288
- };
289
- };
290
- };
291
- var createMoveStopToBreakEvenAfterCoreDecisionHook = createMoveStopToBreakEvenOnBarHook;
292
-
293
- // src/signalsHooks/closeAllPositionsOnGlobalProfitBeforeSignals.ts
294
- import { logger as logger2 } from "@tradejs/infra/logger";
295
- var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
296
- getStrategyDefaultConfig = () => void 0,
297
- profitRiskMultiplier = DEFAULT_GLOBAL_UNREALIZED_PNL_TRIGGER_RISK_MULTIPLIER
298
- } = {}) => {
299
- return async ({ connector, runtimeStrategies }) => {
300
- if (typeof connector.getOpenPositionPnl !== "function") {
301
- return;
302
- }
303
- const openPositions = (await connector.getOpenPositionPnl()).filter(
304
- isOpenPositionPnlSnapshot
305
- );
306
- if (!openPositions.length) {
307
- return;
308
- }
309
- const totalUnrealizedPnl = openPositions.reduce(
310
- (sum, position) => sum + position.unrealizedPnl,
311
- 0
312
- );
313
- if (!Number.isFinite(totalUnrealizedPnl) || totalUnrealizedPnl <= 0) {
314
- return;
315
- }
316
- const maxLossValues = runtimeStrategies.flatMap(
317
- ({ strategyName, strategyConfig }) => {
318
- const maxLossValue = getStrategyMaxLossValue({
319
- ...getStrategyDefaultConfig(strategyName) ?? {},
320
- ...strategyConfig ?? {}
321
- });
322
- return maxLossValue == null ? [] : [maxLossValue];
323
- }
324
- );
325
- if (!maxLossValues.length) {
326
- return;
327
- }
328
- const averageMaxLossValue = maxLossValues.reduce((sum, value) => sum + value, 0) / maxLossValues.length;
329
- const unrealizedPnlThreshold = averageMaxLossValue * profitRiskMultiplier;
330
- if (!Number.isFinite(unrealizedPnlThreshold) || unrealizedPnlThreshold <= 0 || totalUnrealizedPnl < unrealizedPnlThreshold) {
331
- return;
332
- }
333
- logger2.info(
334
- "closing all positions before signals by global unrealized pnl threshold: totalPnl=%s threshold=%s positions=%s",
335
- totalUnrealizedPnl,
336
- unrealizedPnlThreshold,
337
- openPositions.length
338
- );
339
- const closeTimestamp = Date.now();
340
- const closeResults = await Promise.allSettled(
341
- openPositions.map(
342
- (position) => connector.closePosition({
343
- symbol: position.symbol,
344
- direction: position.direction,
345
- price: position.currentPrice,
346
- timestamp: closeTimestamp
347
- })
348
- )
349
- );
350
- const failedClosures = closeResults.flatMap((result, index) => {
351
- if (result.status === "fulfilled" && result.value === true) {
352
- return [];
353
- }
354
- return [
355
- `${openPositions[index]?.symbol}:${openPositions[index]?.direction ?? "UNKNOWN"}`
356
- ];
357
- });
358
- if (failedClosures.length) {
359
- logger2.warn(
360
- "close-all before signals hook could not confirm closures for %s",
361
- failedClosures.join(", ")
362
- );
363
- }
364
- return {
365
- abort: true,
366
- reason: GLOBAL_UNREALIZED_PNL_CLOSE_ALL_CODE
367
- };
368
- };
369
- };
370
-
371
- export {
372
- closeOppositePositionsBeforeOpen,
373
- createCloseOppositeBeforePlaceOrderHook,
374
- createMoveStopToBreakEvenOnBarHook,
375
- createMoveStopToBreakEvenAfterCoreDecisionHook,
376
- createCloseAllOnGlobalProfitBeforeSignalsHook
377
- };