@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.
- package/README.md +1 -1
- package/dist/chunk-GNQJ5TVU.mjs +687 -0
- package/dist/chunk-H2TU2YMA.mjs +762 -0
- package/dist/chunk-MOBKL73M.mjs +405 -0
- package/dist/chunk-XMRB45ZO.mjs +789 -0
- package/dist/index.d.mts +43 -3
- package/dist/index.d.ts +43 -3
- package/dist/index.js +4043 -468
- package/dist/index.mjs +21 -7
- package/dist/strategy-6TS2NFSC.mjs +736 -0
- package/dist/strategy-AFIGEHDS.mjs +418 -0
- package/dist/{strategy-ZVNTA6UR.mjs → strategy-FYNNJDOH.mjs} +1 -13
- package/dist/strategy-LC2FSFVN.mjs +470 -0
- package/dist/{strategy-UHRWSGZC.mjs → strategy-OI4WRB3S.mjs} +51 -41
- package/package.json +14 -6
- package/dist/chunk-MVIV5ZII.mjs +0 -137
- 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
package/dist/chunk-MVIV5ZII.mjs
DELETED
|
@@ -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
|
-
};
|
package/dist/chunk-RYEPHOGL.mjs
DELETED
|
@@ -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
|
-
};
|
package/dist/chunk-ULLCAH5C.mjs
DELETED
|
@@ -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
|
-
};
|