@tradejs/strategies 1.0.0

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/index.mjs ADDED
@@ -0,0 +1,87 @@
1
+ import {
2
+ breakoutManifest
3
+ } from "./chunk-3PN7ZSJJ.mjs";
4
+ import {
5
+ config,
6
+ trendLineManifest
7
+ } from "./chunk-MVIV5ZII.mjs";
8
+ import {
9
+ maStrategyAiAdapter,
10
+ maStrategyManifest,
11
+ maStrategyMlAdapter
12
+ } from "./chunk-RDK2JK3K.mjs";
13
+ import {
14
+ adaptiveMomentumRibbonAiAdapter,
15
+ adaptiveMomentumRibbonManifest,
16
+ adaptiveMomentumRibbonMlAdapter
17
+ } from "./chunk-RYEPHOGL.mjs";
18
+ import {
19
+ volumeDivergenceAiAdapter,
20
+ volumeDivergenceManifest,
21
+ volumeDivergenceMlAdapter
22
+ } from "./chunk-ULLCAH5C.mjs";
23
+ import "./chunk-HEBXNMVQ.mjs";
24
+
25
+ // src/index.ts
26
+ import { defineStrategyPlugin } from "@tradejs/core/config";
27
+ var createLazyStrategyCreator = (loader, exportName) => {
28
+ return async (params) => {
29
+ const module = await loader();
30
+ const creator = module[exportName];
31
+ if (typeof creator !== "function") {
32
+ throw new Error(
33
+ `Strategy creator export "${String(exportName)}" is missing`
34
+ );
35
+ }
36
+ return creator(params);
37
+ };
38
+ };
39
+ var strategyEntries = [
40
+ {
41
+ manifest: breakoutManifest,
42
+ creator: createLazyStrategyCreator(
43
+ () => import("./strategy-Y4SOK6FF.mjs"),
44
+ "BreakoutStrategyCreator"
45
+ )
46
+ },
47
+ {
48
+ manifest: trendLineManifest,
49
+ creator: createLazyStrategyCreator(
50
+ () => import("./strategy-UZBWST3G.mjs"),
51
+ "TrendlineStrategyCreator"
52
+ )
53
+ },
54
+ {
55
+ manifest: maStrategyManifest,
56
+ creator: createLazyStrategyCreator(
57
+ () => import("./strategy-ZVNTA6UR.mjs"),
58
+ "MaStrategyCreator"
59
+ )
60
+ },
61
+ {
62
+ manifest: adaptiveMomentumRibbonManifest,
63
+ creator: createLazyStrategyCreator(
64
+ () => import("./strategy-Q2QNDZK5.mjs"),
65
+ "AdaptiveMomentumRibbonStrategyCreator"
66
+ )
67
+ },
68
+ {
69
+ manifest: volumeDivergenceManifest,
70
+ creator: createLazyStrategyCreator(
71
+ () => import("./strategy-M3BRWDRR.mjs"),
72
+ "VolumeDivergenceStrategyCreator"
73
+ )
74
+ }
75
+ ];
76
+ var index_default = defineStrategyPlugin({ strategyEntries });
77
+ export {
78
+ adaptiveMomentumRibbonAiAdapter,
79
+ adaptiveMomentumRibbonMlAdapter,
80
+ index_default as default,
81
+ maStrategyAiAdapter,
82
+ maStrategyMlAdapter,
83
+ strategyEntries,
84
+ config as trendLineDefaultConfig,
85
+ volumeDivergenceAiAdapter,
86
+ volumeDivergenceMlAdapter
87
+ };
@@ -0,0 +1,377 @@
1
+ import {
2
+ volumeDivergenceManifest
3
+ } from "./chunk-ULLCAH5C.mjs";
4
+ import "./chunk-HEBXNMVQ.mjs";
5
+
6
+ // src/VolumeDivergence/strategy.ts
7
+ import { createStrategyRuntime } from "@tradejs/node/strategies";
8
+
9
+ // src/VolumeDivergence/config.ts
10
+ var config = {
11
+ ENV: "BACKTEST",
12
+ INTERVAL: "15",
13
+ MAKE_ORDERS: true,
14
+ CLOSE_OPPOSITE_POSITIONS: false,
15
+ BACKTEST_PRICE_MODE: "mid",
16
+ AI_ENABLED: false,
17
+ ML_ENABLED: false,
18
+ ML_THRESHOLD: 0.1,
19
+ MIN_AI_QUALITY: 3,
20
+ FEE_PERCENT: 5e-3,
21
+ MAX_LOSS_VALUE: 10,
22
+ MAX_CORRELATION: 0.45,
23
+ MA_FAST: 14,
24
+ MA_MEDIUM: 49,
25
+ MA_SLOW: 50,
26
+ OBV_SMA: 10,
27
+ ATR: 14,
28
+ ATR_PCT_SHORT: 7,
29
+ ATR_PCT_LONG: 30,
30
+ BB: 20,
31
+ BB_STD: 2,
32
+ MACD_FAST: 12,
33
+ MACD_SLOW: 26,
34
+ MACD_SIGNAL: 9,
35
+ LEVEL_LOOKBACK: 20,
36
+ LEVEL_DELAY: 2,
37
+ NORMALIZATION_LENGTH: 1e3,
38
+ PIVOT_LOOKBACK_LEFT: 21,
39
+ PIVOT_LOOKBACK_RIGHT: 5,
40
+ MAX_BARS_BETWEEN_PIVOTS: 60,
41
+ MIN_BARS_BETWEEN_PIVOTS: 5,
42
+ BULLISH: {
43
+ enable: true,
44
+ direction: "LONG",
45
+ TP: 4,
46
+ SL: 1.3,
47
+ minRiskRatio: 2
48
+ },
49
+ BEARISH: {
50
+ enable: true,
51
+ direction: "SHORT",
52
+ TP: 4,
53
+ SL: 1.3,
54
+ minRiskRatio: 2
55
+ }
56
+ };
57
+
58
+ // src/VolumeDivergence/core.ts
59
+ import { round } from "@tradejs/core/math";
60
+
61
+ // src/VolumeDivergence/figures.ts
62
+ var buildVolumeDivergenceFigures = ({
63
+ kind,
64
+ previousPivotIndex,
65
+ currentPivotIndex,
66
+ previousPivotLow,
67
+ previousPivotHigh,
68
+ currentPivotLow,
69
+ currentPivotHigh,
70
+ fullData
71
+ }) => ({
72
+ lines: [
73
+ {
74
+ id: `volume-divergence-price-${kind}`,
75
+ kind: `volume_divergence_${kind}_price`,
76
+ points: [
77
+ {
78
+ timestamp: fullData[previousPivotIndex]?.timestamp ?? 0,
79
+ value: kind === "bullish" ? previousPivotLow : previousPivotHigh
80
+ },
81
+ {
82
+ timestamp: fullData[currentPivotIndex]?.timestamp ?? 0,
83
+ value: kind === "bullish" ? currentPivotLow : currentPivotHigh
84
+ }
85
+ ],
86
+ color: kind === "bullish" ? "#22c55e" : "#ef4444",
87
+ width: 2,
88
+ style: "dashed"
89
+ }
90
+ ],
91
+ points: [
92
+ {
93
+ id: `volume-divergence-pivots-${kind}`,
94
+ kind: `volume_divergence_${kind}_pivots`,
95
+ points: [
96
+ {
97
+ timestamp: fullData[previousPivotIndex]?.timestamp ?? 0,
98
+ value: kind === "bullish" ? previousPivotLow : previousPivotHigh
99
+ },
100
+ {
101
+ timestamp: fullData[currentPivotIndex]?.timestamp ?? 0,
102
+ value: kind === "bullish" ? currentPivotLow : currentPivotHigh
103
+ }
104
+ ],
105
+ color: kind === "bullish" ? "#22c55e" : "#ef4444",
106
+ radius: 4
107
+ }
108
+ ]
109
+ });
110
+
111
+ // src/VolumeDivergence/core.ts
112
+ var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
113
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
114
+ var appendNormalizedVolumes = ({
115
+ candles,
116
+ length,
117
+ normalizedVolumes
118
+ }) => {
119
+ while (normalizedVolumes.length < candles.length) {
120
+ const i = normalizedVolumes.length;
121
+ const start = Math.max(0, i - length + 1);
122
+ let highest = 0;
123
+ for (let j = start; j <= i; j += 1) {
124
+ highest = Math.max(highest, Number(candles[j]?.volume) || 0);
125
+ }
126
+ const volume = Number(candles[i]?.volume) || 0;
127
+ normalizedVolumes.push(highest > 0 ? volume / highest * 100 : 0);
128
+ }
129
+ };
130
+ var isPivotHigh = ({
131
+ values,
132
+ index,
133
+ left,
134
+ right
135
+ }) => {
136
+ const pivotValue = values[index];
137
+ if (!isFiniteNumber(pivotValue)) {
138
+ return false;
139
+ }
140
+ if (index - left < 0 || index + right >= values.length) {
141
+ return false;
142
+ }
143
+ for (let i = index - left; i <= index + right; i += 1) {
144
+ if (i === index) continue;
145
+ if (!isFiniteNumber(values[i]) || values[i] >= pivotValue) {
146
+ return false;
147
+ }
148
+ }
149
+ return true;
150
+ };
151
+ var candleDeltaProxy = (candle) => {
152
+ const volume = Number(candle.volume) || 0;
153
+ const range = Math.max(Math.abs(candle.high - candle.low), 1e-9);
154
+ const bodyBias = (candle.close - candle.open) / range;
155
+ return volume * clamp(bodyBias, -1, 1);
156
+ };
157
+ var findLatestDivergence = ({
158
+ candles,
159
+ normalizedVolumes,
160
+ lookbackLeft,
161
+ lookbackRight,
162
+ rangeLower,
163
+ rangeUpper
164
+ }) => {
165
+ const currentConfirmationIndex = candles.length - 1;
166
+ const currentPivotIndex = currentConfirmationIndex - lookbackRight;
167
+ if (currentPivotIndex <= 0) {
168
+ return null;
169
+ }
170
+ if (!isPivotHigh({
171
+ values: normalizedVolumes,
172
+ index: currentPivotIndex,
173
+ left: lookbackLeft,
174
+ right: lookbackRight
175
+ })) {
176
+ return null;
177
+ }
178
+ let previousPivotIndex = -1;
179
+ for (let i = currentPivotIndex - 1; i >= lookbackLeft; i -= 1) {
180
+ if (isPivotHigh({
181
+ values: normalizedVolumes,
182
+ index: i,
183
+ left: lookbackLeft,
184
+ right: lookbackRight
185
+ })) {
186
+ previousPivotIndex = i;
187
+ break;
188
+ }
189
+ }
190
+ if (previousPivotIndex < 0) {
191
+ return null;
192
+ }
193
+ const previousConfirmationIndex = previousPivotIndex + lookbackRight;
194
+ const barsBetweenPivotConfirmations = currentConfirmationIndex - previousConfirmationIndex - 1;
195
+ if (barsBetweenPivotConfirmations < rangeLower || barsBetweenPivotConfirmations > rangeUpper) {
196
+ return null;
197
+ }
198
+ const currentPivotVolumeNorm = normalizedVolumes[currentPivotIndex];
199
+ const previousPivotVolumeNorm = normalizedVolumes[previousPivotIndex];
200
+ const currentPivotLow = Number(candles[currentPivotIndex]?.low);
201
+ const previousPivotLow = Number(candles[previousPivotIndex]?.low);
202
+ const currentPivotHigh = Number(candles[currentPivotIndex]?.high);
203
+ const previousPivotHigh = Number(candles[previousPivotIndex]?.high);
204
+ const currentPivotCandle = candles[currentPivotIndex];
205
+ if (!isFiniteNumber(currentPivotVolumeNorm) || !isFiniteNumber(previousPivotVolumeNorm) || !isFiniteNumber(currentPivotLow) || !isFiniteNumber(previousPivotLow) || !isFiniteNumber(currentPivotHigh) || !isFiniteNumber(previousPivotHigh)) {
206
+ return null;
207
+ }
208
+ const volHigherLow = currentPivotVolumeNorm > previousPivotVolumeNorm;
209
+ const volLowerHigh = currentPivotVolumeNorm < previousPivotVolumeNorm;
210
+ const priceLowerLow = currentPivotLow < previousPivotLow;
211
+ const priceHigherHigh = currentPivotHigh > previousPivotHigh;
212
+ if (priceLowerLow && volHigherLow) {
213
+ return {
214
+ currentPivotIndex,
215
+ previousPivotIndex,
216
+ currentPivotVolumeNorm,
217
+ previousPivotVolumeNorm,
218
+ currentPivotLow,
219
+ previousPivotLow,
220
+ currentPivotHigh,
221
+ previousPivotHigh,
222
+ currentPivotVolume: Number(currentPivotCandle.volume) || 0,
223
+ currentPivotDelta: candleDeltaProxy(currentPivotCandle),
224
+ barsBetweenPivotConfirmations,
225
+ kind: "bullish"
226
+ };
227
+ }
228
+ if (priceHigherHigh && volLowerHigh) {
229
+ return {
230
+ currentPivotIndex,
231
+ previousPivotIndex,
232
+ currentPivotVolumeNorm,
233
+ previousPivotVolumeNorm,
234
+ currentPivotLow,
235
+ previousPivotLow,
236
+ currentPivotHigh,
237
+ previousPivotHigh,
238
+ currentPivotVolume: Number(currentPivotCandle.volume) || 0,
239
+ currentPivotDelta: candleDeltaProxy(currentPivotCandle),
240
+ barsBetweenPivotConfirmations,
241
+ kind: "bearish"
242
+ };
243
+ }
244
+ return null;
245
+ };
246
+ var createVolumeDivergenceCore = async ({ config: config2, strategyApi, indicatorsState }) => {
247
+ const {
248
+ NORMALIZATION_LENGTH,
249
+ PIVOT_LOOKBACK_LEFT,
250
+ PIVOT_LOOKBACK_RIGHT,
251
+ MAX_BARS_BETWEEN_PIVOTS,
252
+ MIN_BARS_BETWEEN_PIVOTS,
253
+ FEE_PERCENT,
254
+ MAX_LOSS_VALUE,
255
+ MAX_CORRELATION,
256
+ ENV,
257
+ BULLISH,
258
+ BEARISH
259
+ } = config2;
260
+ const lastTradeController = strategyApi.createLastTradeController();
261
+ const normalizedVolumes = [];
262
+ return async () => {
263
+ indicatorsState.onBar();
264
+ const positionExists = await strategyApi.isCurrentPositionExists();
265
+ if (positionExists) {
266
+ return strategyApi.skip("POSITION_EXISTS");
267
+ }
268
+ const { fullData, timestamp, currentPrice } = await strategyApi.getMarketData();
269
+ if (fullData.length < PIVOT_LOOKBACK_LEFT + PIVOT_LOOKBACK_RIGHT + 2) {
270
+ return strategyApi.skip("WAIT_DATA");
271
+ }
272
+ if (lastTradeController.isInCooldown(timestamp)) {
273
+ return strategyApi.skip("DEV_TRADE_COOLDOWN");
274
+ }
275
+ appendNormalizedVolumes({
276
+ candles: fullData,
277
+ length: NORMALIZATION_LENGTH,
278
+ normalizedVolumes
279
+ });
280
+ const divergence = findLatestDivergence({
281
+ candles: fullData,
282
+ normalizedVolumes,
283
+ lookbackLeft: PIVOT_LOOKBACK_LEFT,
284
+ lookbackRight: PIVOT_LOOKBACK_RIGHT,
285
+ rangeLower: MIN_BARS_BETWEEN_PIVOTS,
286
+ rangeUpper: MAX_BARS_BETWEEN_PIVOTS
287
+ });
288
+ if (!divergence) {
289
+ return strategyApi.skip("NO_DIVERGENCE");
290
+ }
291
+ const modeConfig = divergence.kind === "bullish" ? BULLISH : BEARISH;
292
+ if (!modeConfig.enable) {
293
+ return strategyApi.skip("STRATEGY_DISABLED");
294
+ }
295
+ const { stopLossPrice, takeProfitPrice, riskRatio, qty } = strategyApi.getDirectionalTpSlPrices({
296
+ price: currentPrice,
297
+ direction: modeConfig.direction,
298
+ takeProfitDelta: modeConfig.TP,
299
+ stopLossDelta: modeConfig.SL,
300
+ unit: "percent",
301
+ maxLossValue: MAX_LOSS_VALUE,
302
+ feePercent: Number(FEE_PERCENT ?? 0)
303
+ });
304
+ if (!qty || !Number.isFinite(qty) || qty <= 0) {
305
+ return strategyApi.skip("INVALID_QTY");
306
+ }
307
+ if (riskRatio <= modeConfig.minRiskRatio) {
308
+ return strategyApi.skip(`RISK_RATIO:${round(riskRatio)}`);
309
+ }
310
+ const indicators = indicatorsState.snapshot();
311
+ const correlation = indicatorsState.latestNumber("correlation");
312
+ if (ENV !== "BACKTEST" && correlation != null && correlation >= MAX_CORRELATION) {
313
+ return strategyApi.skip(`MAX_CORRELATION:${round(correlation)}`);
314
+ }
315
+ lastTradeController.markTrade(timestamp);
316
+ return strategyApi.entry({
317
+ code: "VOLUME_DIVERGENCE_REVERSAL_SIGNAL",
318
+ direction: modeConfig.direction,
319
+ figures: buildVolumeDivergenceFigures({
320
+ kind: divergence.kind,
321
+ previousPivotIndex: divergence.previousPivotIndex,
322
+ currentPivotIndex: divergence.currentPivotIndex,
323
+ previousPivotLow: divergence.previousPivotLow,
324
+ previousPivotHigh: divergence.previousPivotHigh,
325
+ currentPivotLow: divergence.currentPivotLow,
326
+ currentPivotHigh: divergence.currentPivotHigh,
327
+ fullData
328
+ }),
329
+ indicators,
330
+ additionalIndicators: {
331
+ divergenceKind: divergence.kind,
332
+ normalizedVolumeAtPivot: divergence.currentPivotVolumeNorm,
333
+ previousNormalizedVolumeAtPivot: divergence.previousPivotVolumeNorm,
334
+ volumeAtPivot: divergence.currentPivotVolume,
335
+ deltaAtPivot: divergence.currentPivotDelta,
336
+ barsBetweenPivotConfirmations: divergence.barsBetweenPivotConfirmations,
337
+ divergence: {
338
+ kind: divergence.kind,
339
+ pivotLookbackLeft: PIVOT_LOOKBACK_LEFT,
340
+ pivotLookbackRight: PIVOT_LOOKBACK_RIGHT,
341
+ currentPivot: {
342
+ index: divergence.currentPivotIndex,
343
+ timestamp: fullData[divergence.currentPivotIndex]?.timestamp,
344
+ priceLow: divergence.currentPivotLow,
345
+ priceHigh: divergence.currentPivotHigh,
346
+ volumeNorm: divergence.currentPivotVolumeNorm
347
+ },
348
+ previousPivot: {
349
+ index: divergence.previousPivotIndex,
350
+ timestamp: fullData[divergence.previousPivotIndex]?.timestamp,
351
+ priceLow: divergence.previousPivotLow,
352
+ priceHigh: divergence.previousPivotHigh,
353
+ volumeNorm: divergence.previousPivotVolumeNorm
354
+ },
355
+ barsBetweenPivotConfirmations: divergence.barsBetweenPivotConfirmations
356
+ }
357
+ },
358
+ orderPlan: {
359
+ qty,
360
+ stopLossPrice,
361
+ takeProfits: [{ rate: 1, price: takeProfitPrice }]
362
+ }
363
+ });
364
+ };
365
+ };
366
+
367
+ // src/VolumeDivergence/strategy.ts
368
+ var VolumeDivergenceStrategyCreator = createStrategyRuntime({
369
+ strategyName: "VolumeDivergence",
370
+ defaults: config,
371
+ createCore: createVolumeDivergenceCore,
372
+ manifest: volumeDivergenceManifest,
373
+ strategyDirectory: __dirname
374
+ });
375
+ export {
376
+ VolumeDivergenceStrategyCreator
377
+ };