@tradejs/strategies 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/chunk-37ZWRG3W.mjs +128 -0
- package/dist/chunk-H4MHFD4B.mjs +62 -0
- package/dist/{chunk-MOBKL73M.mjs → chunk-IMLNXICX.mjs} +183 -41
- package/dist/{chunk-XMRB45ZO.mjs → chunk-QVWMBYYM.mjs} +92 -45
- package/dist/chunk-SOVTOGY4.mjs +163 -0
- package/dist/{chunk-H2TU2YMA.mjs → chunk-TDUTYEGH.mjs} +111 -76
- package/dist/{chunk-GNQJ5TVU.mjs → chunk-UK4YHD2E.mjs} +34 -38
- package/dist/index.d.mts +294 -13
- package/dist/index.d.ts +294 -13
- package/dist/index.js +1825 -906
- package/dist/index.mjs +51 -18
- package/dist/{strategy-FYNNJDOH.mjs → strategy-ABIO65CR.mjs} +2 -33
- package/dist/{strategy-AFIGEHDS.mjs → strategy-D7H5J3C4.mjs} +1 -71
- package/dist/{strategy-LC2FSFVN.mjs → strategy-HQIPCUOY.mjs} +1 -72
- package/dist/{strategy-Y4SOK6FF.mjs → strategy-JQIJILHQ.mjs} +28 -109
- package/dist/strategy-QEIPAPY4.mjs +373 -0
- package/dist/{strategy-6TS2NFSC.mjs → strategy-WYN4FZ5S.mjs} +2 -125
- package/dist/{strategy-OI4WRB3S.mjs → strategy-XTUKPYPM.mjs} +466 -120
- package/package.json +5 -5
- package/dist/chunk-3PN7ZSJJ.mjs +0 -27
- package/dist/chunk-RDK2JK3K.mjs +0 -28
|
@@ -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
|
+
};
|
|
@@ -1,65 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildVolumeDivergenceSetupFeatures,
|
|
3
|
+
config,
|
|
3
4
|
getVolumeDivergenceEntryThresholds,
|
|
4
5
|
volumeDivergenceManifest
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QVWMBYYM.mjs";
|
|
6
7
|
import "./chunk-HEBXNMVQ.mjs";
|
|
7
8
|
|
|
8
9
|
// src/VolumeDivergence/strategy.ts
|
|
9
10
|
import { createStrategyRuntime } from "@tradejs/node/strategies";
|
|
10
11
|
|
|
11
|
-
// src/VolumeDivergence/config.ts
|
|
12
|
-
var config = {
|
|
13
|
-
ENV: "BACKTEST",
|
|
14
|
-
INTERVAL: "15",
|
|
15
|
-
MAKE_ORDERS: true,
|
|
16
|
-
CLOSE_OPPOSITE_POSITIONS: false,
|
|
17
|
-
BACKTEST_PRICE_MODE: "mid",
|
|
18
|
-
AI_ENABLED: false,
|
|
19
|
-
ML_ENABLED: false,
|
|
20
|
-
ML_THRESHOLD: 0.1,
|
|
21
|
-
MIN_AI_QUALITY: 3,
|
|
22
|
-
FEE_PERCENT: 5e-3,
|
|
23
|
-
MAX_LOSS_VALUE: 10,
|
|
24
|
-
MA_FAST: 14,
|
|
25
|
-
MA_MEDIUM: 49,
|
|
26
|
-
MA_SLOW: 50,
|
|
27
|
-
OBV_SMA: 10,
|
|
28
|
-
ATR: 14,
|
|
29
|
-
ATR_PCT_SHORT: 7,
|
|
30
|
-
ATR_PCT_LONG: 30,
|
|
31
|
-
BB: 20,
|
|
32
|
-
BB_STD: 2,
|
|
33
|
-
MACD_FAST: 12,
|
|
34
|
-
MACD_SLOW: 26,
|
|
35
|
-
MACD_SIGNAL: 9,
|
|
36
|
-
LEVEL_LOOKBACK: 20,
|
|
37
|
-
LEVEL_DELAY: 2,
|
|
38
|
-
NORMALIZATION_LENGTH: 100,
|
|
39
|
-
PIVOT_LOOKBACK_LEFT: 8,
|
|
40
|
-
PIVOT_LOOKBACK_RIGHT: 3,
|
|
41
|
-
MIN_BARS_BETWEEN_PIVOTS: 4,
|
|
42
|
-
MAX_BARS_BETWEEN_PIVOTS: 36,
|
|
43
|
-
ALLOW_STRUCTURE_ADVANCE_ENTRY: false,
|
|
44
|
-
MIN_DIVERGENCE_AMPLITUDE_ATR_RATIO: 0.35,
|
|
45
|
-
MIN_RECLAIM_PCT: 105,
|
|
46
|
-
MIN_CONFIRMATION_CANDLE_QUALITY: 0.58,
|
|
47
|
-
BULLISH: {
|
|
48
|
-
enable: true,
|
|
49
|
-
direction: "LONG",
|
|
50
|
-
TP: 4,
|
|
51
|
-
SL: 1.3,
|
|
52
|
-
minRiskRatio: 2
|
|
53
|
-
},
|
|
54
|
-
BEARISH: {
|
|
55
|
-
enable: true,
|
|
56
|
-
direction: "SHORT",
|
|
57
|
-
TP: 4,
|
|
58
|
-
SL: 1.3,
|
|
59
|
-
minRiskRatio: 2
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
12
|
// src/VolumeDivergence/core.ts
|
|
64
13
|
import { round } from "@tradejs/core/math";
|
|
65
14
|
|
|
@@ -114,57 +63,11 @@ var buildVolumeDivergenceFigures = ({
|
|
|
114
63
|
});
|
|
115
64
|
|
|
116
65
|
// src/VolumeDivergence/core.ts
|
|
117
|
-
var BREAK_EVEN_TRIGGER_RISK_MULTIPLIER = 0.5;
|
|
118
66
|
var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
119
67
|
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
120
68
|
var isOpenPosition = (position) => Boolean(
|
|
121
69
|
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")
|
|
122
70
|
);
|
|
123
|
-
var getFavorableMovePct = ({
|
|
124
|
-
direction,
|
|
125
|
-
entryPrice,
|
|
126
|
-
currentPrice
|
|
127
|
-
}) => {
|
|
128
|
-
if (!Number.isFinite(entryPrice) || !Number.isFinite(currentPrice) || entryPrice <= 0) {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
return direction === "LONG" ? (currentPrice - entryPrice) / entryPrice * 100 : (entryPrice - currentPrice) / entryPrice * 100;
|
|
132
|
-
};
|
|
133
|
-
var getPositionStopLossPrice = (position) => {
|
|
134
|
-
if (!position || typeof position !== "object") {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
const slPrice = Number(
|
|
138
|
-
position.slPrice ?? Number.NaN
|
|
139
|
-
);
|
|
140
|
-
if (Number.isFinite(slPrice)) {
|
|
141
|
-
return slPrice;
|
|
142
|
-
}
|
|
143
|
-
const signalStopLossPrice = Number(
|
|
144
|
-
position.signal?.prices?.stopLossPrice ?? Number.NaN
|
|
145
|
-
);
|
|
146
|
-
return Number.isFinite(signalStopLossPrice) ? signalStopLossPrice : null;
|
|
147
|
-
};
|
|
148
|
-
var getPositionRiskPct = ({
|
|
149
|
-
direction,
|
|
150
|
-
entryPrice,
|
|
151
|
-
stopLossPrice
|
|
152
|
-
}) => {
|
|
153
|
-
if (stopLossPrice == null || !Number.isFinite(entryPrice) || !Number.isFinite(stopLossPrice) || entryPrice <= 0) {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
return direction === "LONG" ? (entryPrice - stopLossPrice) / entryPrice * 100 : (stopLossPrice - entryPrice) / entryPrice * 100;
|
|
157
|
-
};
|
|
158
|
-
var isBreakEvenStopAlreadyApplied = ({
|
|
159
|
-
direction,
|
|
160
|
-
entryPrice,
|
|
161
|
-
stopLossPrice
|
|
162
|
-
}) => {
|
|
163
|
-
if (stopLossPrice == null || !Number.isFinite(entryPrice) || !Number.isFinite(stopLossPrice)) {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
return direction === "LONG" ? stopLossPrice >= entryPrice : stopLossPrice <= entryPrice;
|
|
167
|
-
};
|
|
168
71
|
var compactQueue = (queue) => {
|
|
169
72
|
if (queue.start <= 1024 || queue.start * 2 <= queue.indices.length) {
|
|
170
73
|
return;
|
|
@@ -565,32 +468,6 @@ var createVolumeDivergenceCore = async ({ config: config2, strategyApi, indicato
|
|
|
565
468
|
const timestamp = Number(candle.timestamp);
|
|
566
469
|
const currentPosition = await strategyApi.getCurrentPosition();
|
|
567
470
|
if (isOpenPosition(currentPosition)) {
|
|
568
|
-
const { currentPrice: currentPrice2 } = await strategyApi.getMarketData();
|
|
569
|
-
const activeModeConfig = currentPosition.direction === "LONG" ? BULLISH : BEARISH;
|
|
570
|
-
const currentStopLossPrice = getPositionStopLossPrice(currentPosition);
|
|
571
|
-
const favorableMovePct = getFavorableMovePct({
|
|
572
|
-
direction: currentPosition.direction,
|
|
573
|
-
entryPrice: currentPosition.price,
|
|
574
|
-
currentPrice: currentPrice2
|
|
575
|
-
});
|
|
576
|
-
const currentPositionRiskPct = getPositionRiskPct({
|
|
577
|
-
direction: currentPosition.direction,
|
|
578
|
-
entryPrice: currentPosition.price,
|
|
579
|
-
stopLossPrice: currentStopLossPrice
|
|
580
|
-
});
|
|
581
|
-
if (!isBreakEvenStopAlreadyApplied({
|
|
582
|
-
direction: currentPosition.direction,
|
|
583
|
-
entryPrice: currentPosition.price,
|
|
584
|
-
stopLossPrice: currentStopLossPrice
|
|
585
|
-
}) && favorableMovePct != null && favorableMovePct >= (currentPositionRiskPct ?? activeModeConfig.SL) * BREAK_EVEN_TRIGGER_RISK_MULTIPLIER) {
|
|
586
|
-
return strategyApi.protect({
|
|
587
|
-
code: "VOLUME_DIVERGENCE_MOVE_STOP_TO_BREAK_EVEN",
|
|
588
|
-
protectPlan: {
|
|
589
|
-
direction: currentPosition.direction,
|
|
590
|
-
stopLossPrice: currentPosition.price
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
471
|
return strategyApi.skip("POSITION_EXISTS");
|
|
595
472
|
}
|
|
596
473
|
if (candleWindow.length < PIVOT_LOOKBACK_LEFT + PIVOT_LOOKBACK_RIGHT + 2) {
|