@tradejs/strategies 1.0.5 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-37ZWRG3W.mjs +128 -0
- package/dist/chunk-H4MHFD4B.mjs +62 -0
- package/dist/chunk-IMLNXICX.mjs +547 -0
- package/dist/chunk-QVWMBYYM.mjs +836 -0
- package/dist/chunk-SOVTOGY4.mjs +163 -0
- package/dist/chunk-TDUTYEGH.mjs +797 -0
- package/dist/chunk-UK4YHD2E.mjs +683 -0
- package/dist/index.d.mts +325 -4
- package/dist/index.d.ts +325 -4
- package/dist/index.js +5689 -1195
- package/dist/index.mjs +61 -14
- package/dist/{strategy-ZVNTA6UR.mjs → strategy-ABIO65CR.mjs} +3 -46
- package/dist/strategy-D7H5J3C4.mjs +348 -0
- package/dist/strategy-HQIPCUOY.mjs +399 -0
- package/dist/{strategy-Y4SOK6FF.mjs → strategy-JQIJILHQ.mjs} +28 -109
- package/dist/strategy-QEIPAPY4.mjs +373 -0
- package/dist/strategy-WYN4FZ5S.mjs +613 -0
- package/dist/{strategy-UHRWSGZC.mjs → strategy-XTUKPYPM.mjs} +484 -128
- package/package.json +5 -5
- package/dist/chunk-3PN7ZSJJ.mjs +0 -27
- package/dist/chunk-MVIV5ZII.mjs +0 -137
- package/dist/chunk-RDK2JK3K.mjs +0 -28
- package/dist/chunk-RYEPHOGL.mjs +0 -28
- package/dist/chunk-ULLCAH5C.mjs +0 -67
- package/dist/strategy-M3BRWDRR.mjs +0 -377
- package/dist/strategy-UZBWST3G.mjs +0 -156
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import {
|
|
2
|
+
config,
|
|
3
|
+
trendShiftManifest
|
|
4
|
+
} from "./chunk-SOVTOGY4.mjs";
|
|
5
|
+
import "./chunk-HEBXNMVQ.mjs";
|
|
6
|
+
|
|
7
|
+
// src/TrendShift/strategy.ts
|
|
8
|
+
import { createStrategyRuntime } from "@tradejs/node/strategies";
|
|
9
|
+
|
|
10
|
+
// src/TrendShift/core.ts
|
|
11
|
+
import { round } from "@tradejs/core/math";
|
|
12
|
+
|
|
13
|
+
// src/TrendShift/engine.ts
|
|
14
|
+
var clampPositive = (value, fallback) => Number.isFinite(value) && value > 0 ? value : fallback;
|
|
15
|
+
var calculateTrueRange = (candle, prevClose) => {
|
|
16
|
+
const high = Number(candle.high);
|
|
17
|
+
const low = Number(candle.low);
|
|
18
|
+
const close = Number(candle.close);
|
|
19
|
+
if (!Number.isFinite(high) || !Number.isFinite(low) || !Number.isFinite(close)) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
if (prevClose == null || !Number.isFinite(prevClose)) {
|
|
23
|
+
return Math.max(high - low, 0);
|
|
24
|
+
}
|
|
25
|
+
return Math.max(
|
|
26
|
+
high - low,
|
|
27
|
+
Math.abs(high - prevClose),
|
|
28
|
+
Math.abs(low - prevClose)
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
var updateAtrState = ({
|
|
32
|
+
atrState,
|
|
33
|
+
tr,
|
|
34
|
+
period
|
|
35
|
+
}) => {
|
|
36
|
+
const safeTr = Number.isFinite(tr) ? Math.max(tr, 0) : 0;
|
|
37
|
+
const safePeriod = Math.max(1, Math.floor(period));
|
|
38
|
+
if (atrState.value == null) {
|
|
39
|
+
return {
|
|
40
|
+
value: safeTr,
|
|
41
|
+
count: 1
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (atrState.count < safePeriod) {
|
|
45
|
+
const nextCount = atrState.count + 1;
|
|
46
|
+
return {
|
|
47
|
+
value: (atrState.value * atrState.count + safeTr) / nextCount,
|
|
48
|
+
count: nextCount
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
value: (atrState.value * (safePeriod - 1) + safeTr) / safePeriod,
|
|
53
|
+
count: atrState.count + 1
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
var trimSeries = (series, maxPoints) => series.length <= maxPoints ? series : series.slice(series.length - maxPoints);
|
|
57
|
+
var asDirection = (trendState) => trendState === 1 ? "LONG" : "SHORT";
|
|
58
|
+
var buildTrendShiftSignalContext = ({
|
|
59
|
+
snapshot,
|
|
60
|
+
indicators
|
|
61
|
+
}) => {
|
|
62
|
+
const maFastSeries = Array.isArray(indicators?.maFast) ? indicators?.maFast : [];
|
|
63
|
+
const maSlowSeries = Array.isArray(indicators?.maSlow) ? indicators?.maSlow : [];
|
|
64
|
+
const maFast = maFastSeries[maFastSeries.length - 1];
|
|
65
|
+
const maSlow = maSlowSeries[maSlowSeries.length - 1];
|
|
66
|
+
const coinBias = Number.isFinite(maFast) && Number.isFinite(maSlow) ? maFast > maSlow ? "bullish" : maFast < maSlow ? "bearish" : "neutral" : "unknown";
|
|
67
|
+
const signalDirection = snapshot.bullFlip ? "LONG" : snapshot.bearFlip ? "SHORT" : asDirection(snapshot.trendState);
|
|
68
|
+
const coinBiasAligned = coinBias === "unknown" || coinBias === "neutral" ? null : signalDirection === "LONG" ? coinBias === "bullish" : coinBias === "bearish";
|
|
69
|
+
return {
|
|
70
|
+
signalDirection,
|
|
71
|
+
trendState: snapshot.trendState,
|
|
72
|
+
rawTrend: snapshot.rawTrend,
|
|
73
|
+
confirmedFlip: snapshot.bullFlip || snapshot.bearFlip,
|
|
74
|
+
bullFlip: snapshot.bullFlip,
|
|
75
|
+
bearFlip: snapshot.bearFlip,
|
|
76
|
+
bullFlipRaw: snapshot.bullFlipRaw,
|
|
77
|
+
bearFlipRaw: snapshot.bearFlipRaw,
|
|
78
|
+
flipDistanceOk: snapshot.flipDistanceOk,
|
|
79
|
+
closeVsAvgPct: snapshot.closeVsAvgPct,
|
|
80
|
+
bandWidthPct: snapshot.bandWidthPct,
|
|
81
|
+
avgSlopePct: snapshot.avgSlopePct,
|
|
82
|
+
distanceAtrRatio: snapshot.distanceAtrRatio,
|
|
83
|
+
adaptiveAtr: snapshot.adaptiveAtr,
|
|
84
|
+
avg: snapshot.avg,
|
|
85
|
+
upper: snapshot.upper,
|
|
86
|
+
lower: snapshot.lower,
|
|
87
|
+
hold: snapshot.hold,
|
|
88
|
+
currentPrice: snapshot.close,
|
|
89
|
+
coinMaFast: Number.isFinite(maFast) ? maFast : null,
|
|
90
|
+
coinMaSlow: Number.isFinite(maSlow) ? maSlow : null,
|
|
91
|
+
coinBias,
|
|
92
|
+
coinBiasAligned
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
var getConfigNumbers = (config2) => ({
|
|
96
|
+
mult: clampPositive(config2.TRENDSHIFT_MULTIPLICATIVE_FACTOR, 4),
|
|
97
|
+
slope: clampPositive(config2.TRENDSHIFT_SLOPE, 12),
|
|
98
|
+
atrLength: Math.max(1, Math.floor(config2.TRENDSHIFT_ATR_LENGTH ?? 150)),
|
|
99
|
+
widthPct: clampPositive(config2.TRENDSHIFT_WIDTH_PCT, 75) / 100,
|
|
100
|
+
minFlipAtr: Math.max(0, Number(config2.TRENDSHIFT_MIN_FLIP_DISTANCE_ATR ?? 0)),
|
|
101
|
+
confirmFlipWithClose: Boolean(config2.TRENDSHIFT_CONFIRM_FLIP_WITH_CLOSE),
|
|
102
|
+
maxFigurePoints: Math.max(
|
|
103
|
+
20,
|
|
104
|
+
Math.floor(config2.TRENDSHIFT_MAX_FIGURE_POINTS ?? 180)
|
|
105
|
+
)
|
|
106
|
+
});
|
|
107
|
+
var createTrendShiftEngine = ({
|
|
108
|
+
config: config2,
|
|
109
|
+
initialCandles = []
|
|
110
|
+
}) => {
|
|
111
|
+
const {
|
|
112
|
+
mult,
|
|
113
|
+
slope,
|
|
114
|
+
atrLength,
|
|
115
|
+
widthPct,
|
|
116
|
+
minFlipAtr,
|
|
117
|
+
confirmFlipWithClose,
|
|
118
|
+
maxFigurePoints
|
|
119
|
+
} = getConfigNumbers(config2);
|
|
120
|
+
const state = {
|
|
121
|
+
atrState: {
|
|
122
|
+
value: null,
|
|
123
|
+
count: 0
|
|
124
|
+
},
|
|
125
|
+
avg: null,
|
|
126
|
+
hold: null,
|
|
127
|
+
rawTrend: 1,
|
|
128
|
+
trendState: 1,
|
|
129
|
+
prevClose: null,
|
|
130
|
+
series: {
|
|
131
|
+
avg: [],
|
|
132
|
+
upper: [],
|
|
133
|
+
lower: []
|
|
134
|
+
},
|
|
135
|
+
snapshot: null
|
|
136
|
+
};
|
|
137
|
+
const apply = (candle) => {
|
|
138
|
+
const close = Number(candle.close);
|
|
139
|
+
if (!Number.isFinite(close)) {
|
|
140
|
+
return {
|
|
141
|
+
snapshot: state.snapshot,
|
|
142
|
+
series: state.series
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const tr = calculateTrueRange(candle, state.prevClose);
|
|
146
|
+
state.atrState = updateAtrState({
|
|
147
|
+
atrState: state.atrState,
|
|
148
|
+
tr,
|
|
149
|
+
period: atrLength
|
|
150
|
+
});
|
|
151
|
+
const adaptiveAtr = Math.max((state.atrState.value ?? tr) * mult, 1e-9);
|
|
152
|
+
const prevAvg = state.avg ?? close;
|
|
153
|
+
const prevHold = state.hold ?? adaptiveAtr;
|
|
154
|
+
const prevRawTrend = state.rawTrend;
|
|
155
|
+
const prevTrendState = state.trendState;
|
|
156
|
+
const avg = Math.abs(close - prevAvg) > adaptiveAtr ? (close + prevAvg) / 2 : prevAvg + prevRawTrend * (prevHold / mult / Math.max(slope, 1));
|
|
157
|
+
const rawTrend = avg > prevAvg ? 1 : avg < prevAvg ? -1 : prevRawTrend;
|
|
158
|
+
const hold = rawTrend !== prevRawTrend ? adaptiveAtr : prevHold + (adaptiveAtr - prevHold) / Math.max(slope, 1);
|
|
159
|
+
const upper = avg + widthPct * hold;
|
|
160
|
+
const lower = avg - widthPct * hold;
|
|
161
|
+
const closeDistance = Math.abs(close - avg);
|
|
162
|
+
const flipDistanceOk = closeDistance >= adaptiveAtr * minFlipAtr;
|
|
163
|
+
const bullFlipRaw = rawTrend === 1 && prevTrendState !== 1;
|
|
164
|
+
const bearFlipRaw = rawTrend === -1 && prevTrendState !== -1;
|
|
165
|
+
const bullFlip = bullFlipRaw && flipDistanceOk && (!confirmFlipWithClose || close > avg);
|
|
166
|
+
const bearFlip = bearFlipRaw && flipDistanceOk && (!confirmFlipWithClose || close < avg);
|
|
167
|
+
const trendState = bullFlip ? 1 : bearFlip ? -1 : prevTrendState;
|
|
168
|
+
const closeVsAvgPct = avg !== 0 ? (close - avg) / avg * 100 : 0;
|
|
169
|
+
const bandWidthPct = avg !== 0 ? (upper - lower) / avg * 100 : 0;
|
|
170
|
+
const avgSlopePct = prevAvg !== 0 ? (avg - prevAvg) / prevAvg * 100 : 0;
|
|
171
|
+
const distanceAtrRatio = adaptiveAtr > 0 ? closeDistance / adaptiveAtr : 0;
|
|
172
|
+
const labelOffset = hold * 0.2;
|
|
173
|
+
state.avg = avg;
|
|
174
|
+
state.hold = hold;
|
|
175
|
+
state.rawTrend = rawTrend;
|
|
176
|
+
state.trendState = trendState;
|
|
177
|
+
state.prevClose = close;
|
|
178
|
+
state.series.avg = trimSeries(
|
|
179
|
+
[...state.series.avg, { timestamp: candle.timestamp, value: avg }],
|
|
180
|
+
maxFigurePoints
|
|
181
|
+
);
|
|
182
|
+
state.series.upper = trimSeries(
|
|
183
|
+
[...state.series.upper, { timestamp: candle.timestamp, value: upper }],
|
|
184
|
+
maxFigurePoints
|
|
185
|
+
);
|
|
186
|
+
state.series.lower = trimSeries(
|
|
187
|
+
[...state.series.lower, { timestamp: candle.timestamp, value: lower }],
|
|
188
|
+
maxFigurePoints
|
|
189
|
+
);
|
|
190
|
+
state.snapshot = {
|
|
191
|
+
avg,
|
|
192
|
+
upper,
|
|
193
|
+
lower,
|
|
194
|
+
hold,
|
|
195
|
+
adaptiveAtr,
|
|
196
|
+
rawTrend,
|
|
197
|
+
trendState,
|
|
198
|
+
bullFlipRaw,
|
|
199
|
+
bearFlipRaw,
|
|
200
|
+
bullFlip,
|
|
201
|
+
bearFlip,
|
|
202
|
+
flipDistanceOk,
|
|
203
|
+
closeVsAvgPct,
|
|
204
|
+
bandWidthPct,
|
|
205
|
+
avgSlopePct,
|
|
206
|
+
distanceAtrRatio,
|
|
207
|
+
labelOffset,
|
|
208
|
+
timestamp: candle.timestamp,
|
|
209
|
+
close
|
|
210
|
+
};
|
|
211
|
+
return {
|
|
212
|
+
snapshot: state.snapshot,
|
|
213
|
+
series: state.series
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
for (const candle of initialCandles) {
|
|
217
|
+
apply(candle);
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
next: apply,
|
|
221
|
+
getState: () => ({
|
|
222
|
+
snapshot: state.snapshot,
|
|
223
|
+
series: state.series
|
|
224
|
+
})
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// src/TrendShift/figures.ts
|
|
229
|
+
var buildTrendShiftFigures = ({
|
|
230
|
+
series,
|
|
231
|
+
direction,
|
|
232
|
+
entryTimestamp,
|
|
233
|
+
entryPrice
|
|
234
|
+
}) => {
|
|
235
|
+
const trendColor = direction === "LONG" ? "#00b894" : "#d63031";
|
|
236
|
+
const lines = [
|
|
237
|
+
{
|
|
238
|
+
id: "trendshift-upper",
|
|
239
|
+
kind: "trendshift_upper",
|
|
240
|
+
points: series.upper,
|
|
241
|
+
color: trendColor,
|
|
242
|
+
width: 1,
|
|
243
|
+
style: "dashed"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: "trendshift-avg",
|
|
247
|
+
kind: "trendshift_avg",
|
|
248
|
+
points: series.avg,
|
|
249
|
+
color: trendColor,
|
|
250
|
+
width: 2,
|
|
251
|
+
style: "solid"
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: "trendshift-lower",
|
|
255
|
+
kind: "trendshift_lower",
|
|
256
|
+
points: series.lower,
|
|
257
|
+
color: trendColor,
|
|
258
|
+
width: 1,
|
|
259
|
+
style: "dashed"
|
|
260
|
+
}
|
|
261
|
+
].filter((line) => Array.isArray(line.points) && line.points.length > 0);
|
|
262
|
+
const points = [
|
|
263
|
+
{
|
|
264
|
+
id: `trendshift-entry-${entryTimestamp}`,
|
|
265
|
+
kind: "trendshift_entry",
|
|
266
|
+
points: [{ timestamp: entryTimestamp, value: entryPrice }],
|
|
267
|
+
color: trendColor,
|
|
268
|
+
radius: 4
|
|
269
|
+
}
|
|
270
|
+
];
|
|
271
|
+
return { lines, points };
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// src/TrendShift/core.ts
|
|
275
|
+
var isOpenPosition = (position) => Boolean(
|
|
276
|
+
position && typeof position.price === "number" && Number.isFinite(position.price) && typeof position.qty === "number" && Number.isFinite(position.qty) && position.qty > 0 && (position.direction === "LONG" || position.direction === "SHORT")
|
|
277
|
+
);
|
|
278
|
+
var createTrendShiftCore = async ({ config: config2, data: initialData, strategyApi, indicatorsState }) => {
|
|
279
|
+
const engine = createTrendShiftEngine({
|
|
280
|
+
config: config2,
|
|
281
|
+
initialCandles: initialData
|
|
282
|
+
});
|
|
283
|
+
const lastTradeController = strategyApi.createLastTradeController();
|
|
284
|
+
return async (candle) => {
|
|
285
|
+
indicatorsState.onBar();
|
|
286
|
+
const runtimeState = engine.next(candle);
|
|
287
|
+
const snapshot = runtimeState.snapshot;
|
|
288
|
+
if (!snapshot) {
|
|
289
|
+
return strategyApi.skip("WAIT_DATA");
|
|
290
|
+
}
|
|
291
|
+
const position = await strategyApi.getCurrentPosition();
|
|
292
|
+
if (isOpenPosition(position)) {
|
|
293
|
+
const oppositeBullExit = position.direction === "SHORT" && snapshot.bullFlip;
|
|
294
|
+
const oppositeBearExit = position.direction === "LONG" && snapshot.bearFlip;
|
|
295
|
+
if (Boolean(config2.TRENDSHIFT_EXIT_ON_OPPOSITE_FLIP) && (oppositeBullExit || oppositeBearExit)) {
|
|
296
|
+
return strategyApi.exit({
|
|
297
|
+
code: "TRENDSHIFT_OPPOSITE_FLIP_EXIT",
|
|
298
|
+
direction: position.direction
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return strategyApi.skip("POSITION_EXISTS");
|
|
302
|
+
}
|
|
303
|
+
if (lastTradeController.isInCooldown(candle.timestamp)) {
|
|
304
|
+
return strategyApi.skip("DEV_TRADE_COOLDOWN");
|
|
305
|
+
}
|
|
306
|
+
const isBullEntry = snapshot.bullFlip;
|
|
307
|
+
const isBearEntry = snapshot.bearFlip;
|
|
308
|
+
if (!isBullEntry && !isBearEntry) {
|
|
309
|
+
return strategyApi.skip("NO_SIGNAL");
|
|
310
|
+
}
|
|
311
|
+
const modeConfig = isBullEntry ? config2.LONG : config2.SHORT;
|
|
312
|
+
if (!modeConfig.enable) {
|
|
313
|
+
return strategyApi.skip("STRATEGY_DISABLED");
|
|
314
|
+
}
|
|
315
|
+
const indicators = indicatorsState.snapshot();
|
|
316
|
+
const { timestamp, currentPrice } = await strategyApi.getMarketData();
|
|
317
|
+
const direction = modeConfig.direction;
|
|
318
|
+
const signalContext = buildTrendShiftSignalContext({
|
|
319
|
+
snapshot: {
|
|
320
|
+
...snapshot,
|
|
321
|
+
close: currentPrice
|
|
322
|
+
},
|
|
323
|
+
indicators
|
|
324
|
+
});
|
|
325
|
+
const { stopLossPrice, takeProfitPrice, riskRatio, qty } = strategyApi.getDirectionalTpSlPrices({
|
|
326
|
+
price: currentPrice,
|
|
327
|
+
direction,
|
|
328
|
+
takeProfitDelta: modeConfig.TP,
|
|
329
|
+
stopLossDelta: modeConfig.SL,
|
|
330
|
+
unit: "percent",
|
|
331
|
+
maxLossValue: config2.MAX_LOSS_VALUE,
|
|
332
|
+
feePercent: Number(config2.FEE_PERCENT ?? 0)
|
|
333
|
+
});
|
|
334
|
+
if (!qty || !Number.isFinite(qty) || qty <= 0) {
|
|
335
|
+
return strategyApi.skip("INVALID_QTY");
|
|
336
|
+
}
|
|
337
|
+
if (riskRatio <= modeConfig.minRiskRatio) {
|
|
338
|
+
return strategyApi.skip(`RISK_RATIO:${round(riskRatio)}`);
|
|
339
|
+
}
|
|
340
|
+
lastTradeController.markTrade(timestamp);
|
|
341
|
+
return strategyApi.entry({
|
|
342
|
+
code: isBullEntry ? "TRENDSHIFT_BULLISH_FLIP" : "TRENDSHIFT_BEARISH_FLIP",
|
|
343
|
+
direction,
|
|
344
|
+
indicators,
|
|
345
|
+
additionalIndicators: {
|
|
346
|
+
trendShiftContext: signalContext
|
|
347
|
+
},
|
|
348
|
+
figures: buildTrendShiftFigures({
|
|
349
|
+
series: runtimeState.series,
|
|
350
|
+
direction,
|
|
351
|
+
entryTimestamp: timestamp,
|
|
352
|
+
entryPrice: currentPrice
|
|
353
|
+
}),
|
|
354
|
+
orderPlan: {
|
|
355
|
+
qty,
|
|
356
|
+
stopLossPrice,
|
|
357
|
+
takeProfits: [{ rate: 1, price: takeProfitPrice }]
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// src/TrendShift/strategy.ts
|
|
364
|
+
var TrendShiftStrategyCreator = createStrategyRuntime({
|
|
365
|
+
strategyName: "TrendShift",
|
|
366
|
+
defaults: config,
|
|
367
|
+
createCore: createTrendShiftCore,
|
|
368
|
+
manifest: trendShiftManifest,
|
|
369
|
+
strategyDirectory: __dirname
|
|
370
|
+
});
|
|
371
|
+
export {
|
|
372
|
+
TrendShiftStrategyCreator
|
|
373
|
+
};
|