@tradejs/strategies 1.0.4 → 1.0.6

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.
@@ -1,137 +0,0 @@
1
- // src/TrendLine/adapters/ai.ts
2
- import { mapAiRuntimeFromConfig } from "@tradejs/core/strategies";
3
- var TRENDLINE_CONTEXT_PROMPT = `
4
- \u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0434\u043B\u044F trendline-\u0441\u0435\u0442\u0430\u043F\u043E\u0432:
5
- - \u042D\u0442\u043E \u0441\u0435\u0442\u0430\u043F \u043D\u0430 \u043E\u0441\u043D\u043E\u0432\u0435 \u043F\u0440\u043E\u0431\u043E\u044F/\u0440\u0435\u0430\u043A\u0446\u0438\u0438 \u043E\u0442 \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u043B\u0438\u043D\u0438\u0438; \u043F\u043E\u043B\u0435 figures.trendline \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u044E \u044D\u0442\u043E\u0439 \u043B\u0438\u043D\u0438\u0438.
6
- - \u0414\u043B\u044F TrendLine \u0440\u043E\u043B\u044C \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u0438/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B \u0446\u0435\u043D\u044B \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u0435\u0435 \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u043D\u044B\u0445 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0439.
7
- `;
8
- var TRENDLINE_PAYLOAD_PROMPT = `
9
- - \u0412 payload.figures.trendline \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F \u043F\u043E\u043B\u043D\u0430\u044F \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u044F \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u043B\u0438\u043D\u0438\u0438 (\u0431\u0435\u0437 trim), \u0447\u0442\u043E\u0431\u044B \u043C\u043E\u0436\u043D\u043E \u0431\u044B\u043B\u043E \u043E\u0446\u0435\u043D\u0438\u0432\u0430\u0442\u044C \u043A\u0430\u0441\u0430\u043D\u0438\u044F/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443.
10
- `;
11
- var trendLineAiAdapter = {
12
- // Shared builder trims nested series/figures; TrendLine keeps trendline geometry untrimmed on purpose.
13
- buildPayload: ({ signal, basePayload }) => ({
14
- ...basePayload,
15
- figures: {
16
- ...basePayload.figures,
17
- // Keep trendline geometry untrimmed for LLM reasoning.
18
- trendline: signal.figures?.trendLine ?? null
19
- }
20
- }),
21
- buildSystemPromptAddon: () => `
22
- ${TRENDLINE_CONTEXT_PROMPT}
23
- ${TRENDLINE_PAYLOAD_PROMPT}
24
- `,
25
- mapEntryRuntimeFromConfig: (config2) => mapAiRuntimeFromConfig(
26
- config2
27
- )
28
- // Intentionally omitted now: base human prompt is sufficient.
29
- // buildHumanPromptAddon: () => '',
30
- };
31
-
32
- // src/TrendLine/adapters/ml.ts
33
- import { mapMlRuntimeFromConfig } from "@tradejs/core/strategies";
34
- var toTrendLineMlStrategyConfig = (input) => {
35
- if (!input) return void 0;
36
- return {
37
- ...input,
38
- TRENDLINE_CONFIG: input.TRENDLINE_CONFIG ?? input.TRENDLINE ?? {}
39
- };
40
- };
41
- var trendLineMlAdapter = {
42
- normalizeSignal: (signal) => {
43
- const nextSignal = {
44
- ...signal,
45
- indicators: {
46
- ...signal.indicators ?? {}
47
- }
48
- };
49
- const additional = signal.additionalIndicators ?? {};
50
- if (nextSignal.indicators.touches == null && additional.touches != null) {
51
- nextSignal.indicators.touches = additional.touches;
52
- }
53
- if (nextSignal.indicators.distance == null && additional.distance != null) {
54
- nextSignal.indicators.distance = additional.distance;
55
- }
56
- return nextSignal;
57
- },
58
- normalizeStrategyConfig: (strategyConfig) => {
59
- return toTrendLineMlStrategyConfig(strategyConfig);
60
- },
61
- mapEntryRuntimeFromConfig: (config2) => mapMlRuntimeFromConfig(config2, {
62
- strategyConfig: toTrendLineMlStrategyConfig(
63
- config2
64
- )
65
- })
66
- };
67
-
68
- // src/TrendLine/hooks.ts
69
- import { createCloseOppositeBeforePlaceOrderHook } from "@tradejs/node/strategies";
70
- var trendLineBeforePlaceOrderHook = createCloseOppositeBeforePlaceOrderHook({
71
- isEnabled: (config2) => Boolean(config2.CLOSE_OPPOSITE_POSITIONS)
72
- });
73
-
74
- // src/TrendLine/manifest.ts
75
- var trendLineManifest = {
76
- name: "TrendLine",
77
- hooks: {
78
- beforePlaceOrder: trendLineBeforePlaceOrderHook
79
- },
80
- aiAdapter: trendLineAiAdapter,
81
- mlAdapter: trendLineMlAdapter
82
- };
83
-
84
- // src/TrendLine/config.ts
85
- var config = {
86
- ENV: "BACKTEST",
87
- INTERVAL: "15",
88
- MAKE_ORDERS: true,
89
- CLOSE_OPPOSITE_POSITIONS: false,
90
- BACKTEST_PRICE_MODE: "mid",
91
- AI_ENABLED: false,
92
- ML_ENABLED: false,
93
- ML_THRESHOLD: 0.1,
94
- MIN_AI_QUALITY: 3,
95
- FEE_PERCENT: 5e-3,
96
- MAX_LOSS_VALUE: 10,
97
- MAX_CORRELATION: 0.45,
98
- MA_FAST: 14,
99
- MA_MEDIUM: 49,
100
- MA_SLOW: 50,
101
- OBV_SMA: 10,
102
- ATR: 14,
103
- ATR_PCT_SHORT: 7,
104
- ATR_PCT_LONG: 30,
105
- BB: 20,
106
- BB_STD: 2,
107
- MACD_FAST: 12,
108
- MACD_SLOW: 26,
109
- MACD_SIGNAL: 9,
110
- LEVEL_LOOKBACK: 20,
111
- LEVEL_DELAY: 2,
112
- TRENDLINE: {
113
- minTouches: 4,
114
- offset: 3,
115
- epsilon: 3e-3,
116
- epsilonOffset: 4e-3
117
- },
118
- HIGHS: {
119
- enable: true,
120
- direction: "LONG",
121
- TP: 4,
122
- SL: 1.3,
123
- minRiskRatio: 2
124
- },
125
- LOWS: {
126
- enable: true,
127
- direction: "SHORT",
128
- TP: 4,
129
- SL: 1.3,
130
- minRiskRatio: 2
131
- }
132
- };
133
-
134
- export {
135
- trendLineManifest,
136
- config
137
- };
@@ -1,28 +0,0 @@
1
- // src/AdaptiveMomentumRibbon/adapters/ai.ts
2
- import { mapAiRuntimeFromConfig } from "@tradejs/core/strategies";
3
- var adaptiveMomentumRibbonAiAdapter = {
4
- mapEntryRuntimeFromConfig: (config) => mapAiRuntimeFromConfig(
5
- config
6
- )
7
- };
8
-
9
- // src/AdaptiveMomentumRibbon/adapters/ml.ts
10
- import { mapMlRuntimeFromConfig } from "@tradejs/core/strategies";
11
- var adaptiveMomentumRibbonMlAdapter = {
12
- mapEntryRuntimeFromConfig: (config) => mapMlRuntimeFromConfig(
13
- config
14
- )
15
- };
16
-
17
- // src/AdaptiveMomentumRibbon/manifest.ts
18
- var adaptiveMomentumRibbonManifest = {
19
- name: "AdaptiveMomentumRibbon",
20
- aiAdapter: adaptiveMomentumRibbonAiAdapter,
21
- mlAdapter: adaptiveMomentumRibbonMlAdapter
22
- };
23
-
24
- export {
25
- adaptiveMomentumRibbonAiAdapter,
26
- adaptiveMomentumRibbonMlAdapter,
27
- adaptiveMomentumRibbonManifest
28
- };
@@ -1,67 +0,0 @@
1
- // src/VolumeDivergence/adapters/ai.ts
2
- import { mapAiRuntimeFromConfig } from "@tradejs/core/strategies";
3
- var VOLUME_DIVERGENCE_CONTEXT_PROMPT = `
4
- \u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0434\u043B\u044F Volume Divergence Reversal Signals:
5
- - \u0421\u0438\u0433\u043D\u0430\u043B \u0441\u0442\u0440\u043E\u0438\u0442\u0441\u044F \u043D\u0430 \u0434\u0438\u0432\u0435\u0440\u0433\u0435\u043D\u0446\u0438\u0438 \u0446\u0435\u043D\u044B \u0438 \u043D\u043E\u0440\u043C\u0430\u043B\u0438\u0437\u043E\u0432\u0430\u043D\u043D\u043E\u0433\u043E \u043E\u0431\u044A\u0435\u043C\u0430 (0-100).
6
- - Bullish divergence: price \u0434\u0435\u043B\u0430\u0435\u0442 lower low, volume \u0434\u0435\u043B\u0430\u0435\u0442 higher low.
7
- - Bearish divergence: price \u0434\u0435\u043B\u0430\u0435\u0442 higher high, volume \u0434\u0435\u043B\u0430\u0435\u0442 lower high.
8
- - \u0412 additionalIndicators.deltaAtPivot \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F proxy net-volume (\u043E\u0446\u0435\u043D\u043A\u0430 \u043F\u043E \u0442\u0435\u043B\u0443 \u0441\u0432\u0435\u0447\u0438), \u044D\u0442\u043E \u043D\u0435 lower-timeframe volume delta TradingView.
9
- `;
10
- var volumeDivergenceAiAdapter = {
11
- buildSystemPromptAddon: () => `
12
- ${VOLUME_DIVERGENCE_CONTEXT_PROMPT}
13
- `,
14
- mapEntryRuntimeFromConfig: (config) => mapAiRuntimeFromConfig(
15
- config
16
- )
17
- };
18
-
19
- // src/VolumeDivergence/adapters/ml.ts
20
- import { mapMlRuntimeFromConfig } from "@tradejs/core/strategies";
21
- var toVolumeDivergenceMlStrategyConfig = (input) => {
22
- if (!input) return void 0;
23
- return {
24
- ...input,
25
- VOLUME_DIVERGENCE_CONFIG: input.VOLUME_DIVERGENCE_CONFIG ?? {
26
- normalizationLength: input.NORMALIZATION_LENGTH,
27
- pivotLookbackLeft: input.PIVOT_LOOKBACK_LEFT,
28
- pivotLookbackRight: input.PIVOT_LOOKBACK_RIGHT,
29
- maxBarsBetweenPivots: input.MAX_BARS_BETWEEN_PIVOTS,
30
- minBarsBetweenPivots: input.MIN_BARS_BETWEEN_PIVOTS,
31
- bullish: input.BULLISH,
32
- bearish: input.BEARISH
33
- }
34
- };
35
- };
36
- var volumeDivergenceMlAdapter = {
37
- normalizeStrategyConfig: (strategyConfig) => {
38
- return toVolumeDivergenceMlStrategyConfig(strategyConfig);
39
- },
40
- mapEntryRuntimeFromConfig: (config) => mapMlRuntimeFromConfig(config, {
41
- strategyConfig: toVolumeDivergenceMlStrategyConfig(
42
- config
43
- )
44
- })
45
- };
46
-
47
- // src/VolumeDivergence/hooks.ts
48
- import { createCloseOppositeBeforePlaceOrderHook } from "@tradejs/node/strategies";
49
- var volumeDivergenceBeforePlaceOrderHook = createCloseOppositeBeforePlaceOrderHook({
50
- isEnabled: (config) => Boolean(config.CLOSE_OPPOSITE_POSITIONS)
51
- });
52
-
53
- // src/VolumeDivergence/manifest.ts
54
- var volumeDivergenceManifest = {
55
- name: "VolumeDivergence",
56
- hooks: {
57
- beforePlaceOrder: volumeDivergenceBeforePlaceOrderHook
58
- },
59
- aiAdapter: volumeDivergenceAiAdapter,
60
- mlAdapter: volumeDivergenceMlAdapter
61
- };
62
-
63
- export {
64
- volumeDivergenceAiAdapter,
65
- volumeDivergenceMlAdapter,
66
- volumeDivergenceManifest
67
- };
@@ -1,377 +0,0 @@
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
- };