@tradejs/strategies 1.0.5 → 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/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 +5 -5
- 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
|
@@ -0,0 +1,789 @@
|
|
|
1
|
+
// src/VolumeDivergence/adapters/ai.ts
|
|
2
|
+
import { mapAiRuntimeFromConfig } from "@tradejs/core/strategies";
|
|
3
|
+
|
|
4
|
+
// src/VolumeDivergence/setup.ts
|
|
5
|
+
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
6
|
+
var DEFAULT_VOLUME_DIVERGENCE_ENTRY_THRESHOLDS = {
|
|
7
|
+
allowStructureAdvanceEntry: false,
|
|
8
|
+
minDivergenceAmplitudeAtrRatio: 0.35,
|
|
9
|
+
minReclaimPct: 105,
|
|
10
|
+
minConfirmationCandleQuality: 0.58
|
|
11
|
+
};
|
|
12
|
+
var VOLUME_DIVERGENCE_AI_THRESHOLDS = {
|
|
13
|
+
LONG: {
|
|
14
|
+
q4DivergenceAmplitudeAtrRatio: 0.45,
|
|
15
|
+
q4ReclaimPct: 115,
|
|
16
|
+
q4ConfirmationCandleQuality: 0.62,
|
|
17
|
+
q5DivergenceAmplitudeAtrRatio: 0.8,
|
|
18
|
+
q5ReclaimPct: 145,
|
|
19
|
+
q5ConfirmationCandleQuality: 0.8
|
|
20
|
+
},
|
|
21
|
+
SHORT: {
|
|
22
|
+
q4DivergenceAmplitudeAtrRatio: 0.6,
|
|
23
|
+
q4ReclaimPct: 125,
|
|
24
|
+
q4ConfirmationCandleQuality: 0.7,
|
|
25
|
+
q5DivergenceAmplitudeAtrRatio: 0.95,
|
|
26
|
+
q5ReclaimPct: 160,
|
|
27
|
+
q5ConfirmationCandleQuality: 0.82
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var getVolumeDivergenceEntryThresholds = ({
|
|
31
|
+
ALLOW_STRUCTURE_ADVANCE_ENTRY,
|
|
32
|
+
MIN_DIVERGENCE_AMPLITUDE_ATR_RATIO,
|
|
33
|
+
MIN_RECLAIM_PCT,
|
|
34
|
+
MIN_CONFIRMATION_CANDLE_QUALITY
|
|
35
|
+
}) => ({
|
|
36
|
+
allowStructureAdvanceEntry: ALLOW_STRUCTURE_ADVANCE_ENTRY,
|
|
37
|
+
minDivergenceAmplitudeAtrRatio: MIN_DIVERGENCE_AMPLITUDE_ATR_RATIO,
|
|
38
|
+
minReclaimPct: MIN_RECLAIM_PCT,
|
|
39
|
+
minConfirmationCandleQuality: MIN_CONFIRMATION_CANDLE_QUALITY
|
|
40
|
+
});
|
|
41
|
+
var getVolumeDivergenceAiThresholds = (direction) => VOLUME_DIVERGENCE_AI_THRESHOLDS[direction];
|
|
42
|
+
var calculateAverageTrueRange = (candles, period) => {
|
|
43
|
+
if (!Array.isArray(candles) || candles.length < 2 || period <= 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
let total = 0;
|
|
47
|
+
let count = 0;
|
|
48
|
+
const startIndex = Math.max(1, candles.length - period);
|
|
49
|
+
for (let i = startIndex; i < candles.length; i += 1) {
|
|
50
|
+
const candle = candles[i];
|
|
51
|
+
const previousClose = Number(candles[i - 1]?.close);
|
|
52
|
+
const high = Number(candle?.high);
|
|
53
|
+
const low = Number(candle?.low);
|
|
54
|
+
if (!Number.isFinite(previousClose) || !Number.isFinite(high) || !Number.isFinite(low)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const trueRange = Math.max(
|
|
58
|
+
high - low,
|
|
59
|
+
Math.abs(high - previousClose),
|
|
60
|
+
Math.abs(low - previousClose)
|
|
61
|
+
);
|
|
62
|
+
total += trueRange;
|
|
63
|
+
count += 1;
|
|
64
|
+
}
|
|
65
|
+
return count > 0 ? total / count : null;
|
|
66
|
+
};
|
|
67
|
+
var getConfirmationCandleQuality = ({
|
|
68
|
+
candle,
|
|
69
|
+
direction
|
|
70
|
+
}) => {
|
|
71
|
+
const high = Number(candle.high);
|
|
72
|
+
const low = Number(candle.low);
|
|
73
|
+
const open = Number(candle.open);
|
|
74
|
+
const close = Number(candle.close);
|
|
75
|
+
if (!Number.isFinite(high) || !Number.isFinite(low) || !Number.isFinite(open) || !Number.isFinite(close)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const range = Math.max(high - low, 1e-9);
|
|
79
|
+
const bodyPct = clamp(Math.abs(close - open) / range, 0, 1);
|
|
80
|
+
const closeLocation = direction === "LONG" ? clamp((close - low) / range, 0, 1) : clamp((high - close) / range, 0, 1);
|
|
81
|
+
return closeLocation * 0.7 + bodyPct * 0.3;
|
|
82
|
+
};
|
|
83
|
+
var calculateAtrPct = ({
|
|
84
|
+
atrAbsolute,
|
|
85
|
+
currentPrice
|
|
86
|
+
}) => atrAbsolute != null && currentPrice > 0 ? atrAbsolute / currentPrice * 100 : null;
|
|
87
|
+
var calculateDivergenceAmplitudeAtrRatio = ({
|
|
88
|
+
direction,
|
|
89
|
+
atrAbsolute,
|
|
90
|
+
currentPivotLow,
|
|
91
|
+
previousPivotLow,
|
|
92
|
+
currentPivotHigh,
|
|
93
|
+
previousPivotHigh
|
|
94
|
+
}) => {
|
|
95
|
+
const divergenceAmplitude = direction === "LONG" ? previousPivotLow - currentPivotLow : currentPivotHigh - previousPivotHigh;
|
|
96
|
+
return atrAbsolute != null && atrAbsolute > 0 && divergenceAmplitude > 0 ? divergenceAmplitude / atrAbsolute : null;
|
|
97
|
+
};
|
|
98
|
+
var calculateReclaimPct = ({
|
|
99
|
+
direction,
|
|
100
|
+
currentPrice,
|
|
101
|
+
currentPivotLow,
|
|
102
|
+
currentPivotHigh
|
|
103
|
+
}) => {
|
|
104
|
+
const reclaimRange = currentPivotHigh - currentPivotLow;
|
|
105
|
+
const reclaimProgress = direction === "LONG" ? currentPrice - currentPivotLow : currentPivotHigh - currentPrice;
|
|
106
|
+
return reclaimRange > 0 ? reclaimProgress / reclaimRange * 100 : null;
|
|
107
|
+
};
|
|
108
|
+
var calculateConfirmationDistancePct = ({
|
|
109
|
+
direction,
|
|
110
|
+
currentPrice,
|
|
111
|
+
currentPivotLow,
|
|
112
|
+
currentPivotHigh
|
|
113
|
+
}) => {
|
|
114
|
+
const confirmationPrice = direction === "LONG" ? currentPivotHigh : currentPivotLow;
|
|
115
|
+
if (!(confirmationPrice > 0)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return direction === "LONG" ? (currentPrice - confirmationPrice) / confirmationPrice * 100 : (confirmationPrice - currentPrice) / confirmationPrice * 100;
|
|
119
|
+
};
|
|
120
|
+
var buildVolumeDivergenceSetupFeatures = ({
|
|
121
|
+
candles,
|
|
122
|
+
currentCandle,
|
|
123
|
+
direction,
|
|
124
|
+
currentPrice,
|
|
125
|
+
currentPivotLow,
|
|
126
|
+
previousPivotLow,
|
|
127
|
+
currentPivotHigh,
|
|
128
|
+
previousPivotHigh,
|
|
129
|
+
atrPeriod
|
|
130
|
+
}) => {
|
|
131
|
+
const atrAbsolute = calculateAverageTrueRange(candles, atrPeriod);
|
|
132
|
+
const atrPct = calculateAtrPct({ atrAbsolute, currentPrice });
|
|
133
|
+
const divergenceAmplitudeAtrRatio = calculateDivergenceAmplitudeAtrRatio({
|
|
134
|
+
direction,
|
|
135
|
+
atrAbsolute,
|
|
136
|
+
currentPivotLow,
|
|
137
|
+
previousPivotLow,
|
|
138
|
+
currentPivotHigh,
|
|
139
|
+
previousPivotHigh
|
|
140
|
+
});
|
|
141
|
+
const reclaimPct = calculateReclaimPct({
|
|
142
|
+
direction,
|
|
143
|
+
currentPrice,
|
|
144
|
+
currentPivotLow,
|
|
145
|
+
currentPivotHigh
|
|
146
|
+
});
|
|
147
|
+
const confirmationCandleQuality = getConfirmationCandleQuality({
|
|
148
|
+
candle: currentCandle,
|
|
149
|
+
direction
|
|
150
|
+
});
|
|
151
|
+
const confirmationDistancePct = calculateConfirmationDistancePct({
|
|
152
|
+
direction,
|
|
153
|
+
currentPrice,
|
|
154
|
+
currentPivotLow,
|
|
155
|
+
currentPivotHigh
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
atrAbsolute,
|
|
159
|
+
atrPct,
|
|
160
|
+
divergenceAmplitudeAtrRatio,
|
|
161
|
+
reclaimPct,
|
|
162
|
+
confirmationCandleQuality,
|
|
163
|
+
confirmationDistancePct
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/VolumeDivergence/adapters/ai.ts
|
|
168
|
+
var VOLUME_DIVERGENCE_CONTEXT_PROMPT = `
|
|
169
|
+
\u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0434\u043B\u044F VolumeDivergence:
|
|
170
|
+
- \u042D\u0442\u043E reversal-\u0441\u0435\u0442\u0430\u043F \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, \u0430 \u043D\u0435 breakout-\u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044F.
|
|
171
|
+
- Bullish divergence: price \u0434\u0435\u043B\u0430\u0435\u0442 lower low, \u0430 volume \u0434\u0435\u043B\u0430\u0435\u0442 higher low.
|
|
172
|
+
- Bearish divergence: price \u0434\u0435\u043B\u0430\u0435\u0442 higher high, \u0430 volume \u0434\u0435\u043B\u0430\u0435\u0442 lower high.
|
|
173
|
+
- \u0414\u043B\u044F bullish-\u0441\u0438\u0433\u043D\u0430\u043B\u0430 \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439 quality, \u0435\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u043F\u043E\u0441\u043B\u0435 pivot low \u0442\u0430\u043A \u0438 \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u0430 \u0437\u0430\u043C\u0435\u0442\u043D\u043E \u043E\u0442\u0441\u043A\u043E\u0447\u0438\u0442\u044C \u043E\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E pivot low \u0438\u043B\u0438 \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u0430 \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0445\u043E\u0442\u044F \u0431\u044B \u0447\u0430\u0441\u0442\u044C \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B.
|
|
174
|
+
- \u0414\u043B\u044F bearish-\u0441\u0438\u0433\u043D\u0430\u043B\u0430 \u0437\u0435\u0440\u043A\u0430\u043B\u044C\u043D\u043E: \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439 quality, \u0435\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u043F\u043E\u0441\u043B\u0435 pivot high \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u0430 \u0437\u0430\u043C\u0435\u0442\u043D\u043E \u0443\u0439\u0442\u0438 \u0432\u043D\u0438\u0437 \u043E\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E pivot high.
|
|
175
|
+
- \u0415\u0441\u043B\u0438 payload.additionalIndicators.volumeDivergenceContext.confirmationReady=false, \u043E\u0431\u044B\u0447\u043D\u043E \u044D\u0442\u043E \u0435\u0449\u0435 \u043D\u0435 fully confirmed reversal; \u0447\u0430\u0449\u0435 quality <= 4 \u0438 \u0447\u0430\u0441\u0442\u043E \u043D\u0443\u0436\u0435\u043D retest/confirmation.
|
|
176
|
+
- \u0414\u043B\u044F live approve \u0441\u0447\u0438\u0442\u0430\u0439 confirmationReady \u043D\u0430\u043C\u043D\u043E\u0433\u043E \u0432\u0430\u0436\u043D\u0435\u0435, \u0447\u0435\u043C structureAdvanced: structure advance \u0441\u0430\u043C \u043F\u043E \u0441\u0435\u0431\u0435 \u0435\u0449\u0435 \u043D\u0435 \u043E\u0437\u043D\u0430\u0447\u0430\u0435\u0442 \u0433\u043E\u0442\u043E\u0432\u044B\u0439 reversal entry.
|
|
177
|
+
- \u0414\u043B\u044F reversal-\u0441\u0435\u0442\u0430\u043F\u0430 \u043D\u0435 \u043D\u0430\u0433\u0440\u0430\u0436\u0434\u0430\u0439 quality \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0437\u0430 \u0442\u043E, \u0447\u0442\u043E MA bias \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435/BTC \u0443\u0436\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0430\u0435\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
|
|
178
|
+
- \u0414\u043B\u044F LONG \u0441 entryTiming=structure_advance \u043E\u0431\u044B\u0447\u043D\u043E \u043D\u0435 \u0441\u0442\u0430\u0432\u044C quality=5: \u044D\u0442\u043E \u043F\u0440\u043E\u043C\u0435\u0436\u0443\u0442\u043E\u0447\u043D\u044B\u0439 \u044D\u0442\u0430\u043F, \u0430 \u043D\u0435 fully confirmed reversal.
|
|
179
|
+
- \u0414\u043B\u044F SHORT \u0431\u0443\u0434\u044C \u0441\u0442\u0440\u043E\u0436\u0435, \u0447\u0435\u043C \u0434\u043B\u044F LONG: bearish reversal \u0434\u043E\u043B\u0436\u0435\u043D \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u0442\u044C \u0431\u043E\u043B\u0435\u0435 \u0447\u0438\u0441\u0442\u043E\u0433\u043E follow-through, \u0430 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u043F\u043E bias/delta \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u0438\u043B\u044C\u043D\u0435\u0435 \u0441\u043D\u0438\u0436\u0430\u0442\u044C quality.
|
|
180
|
+
- \u0415\u0441\u043B\u0438 deltaAtPivot \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C reversal \u0438\u043B\u0438 bias \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435/BTC \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C, \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439 quality \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u0437-\u0437\u0430 \u0441\u0430\u043C\u043E\u0439 \u0434\u0438\u0432\u0435\u0440\u0433\u0435\u043D\u0446\u0438\u0438.
|
|
181
|
+
- \u0421\u043C\u043E\u0442\u0440\u0438 \u043D\u0430 divergenceAmplitudeAtrRatio / reclaimPct / confirmationCandleQuality: \u044D\u0442\u043E explicit setup-features, \u043E\u043F\u0438\u0441\u044B\u0432\u0430\u044E\u0449\u0438\u0435 \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0434\u0438\u0432\u0435\u0440\u0433\u0435\u043D\u0446\u0438\u044F \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0437\u043D\u0430\u0447\u0438\u043C\u0430 \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E ATR, \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0446\u0435\u043D\u0430 \u0432\u0435\u0440\u043D\u0443\u043B\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u0438 \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0439 \u0431\u044B\u043B\u0430 confirmation candle.
|
|
182
|
+
- confirmationDistancePct \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0434\u0430\u043B\u0435\u043A\u043E \u0446\u0435\u043D\u0430 \u0443\u0448\u043B\u0430 \u0437\u0430 confirmation level; \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439 quality, \u0435\u0441\u043B\u0438 confirmation \u0432\u0440\u043E\u0434\u0435 \u0431\u044B \u0435\u0441\u0442\u044C, \u043D\u043E \u0437\u0430\u043A\u0440\u0435\u043F\u043B\u0435\u043D\u0438\u0435 \u0437\u0430 \u0443\u0440\u043E\u0432\u043D\u0435\u043C \u043C\u0438\u043D\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0435.
|
|
183
|
+
- additionalIndicators.deltaAtPivot \u2014 \u044D\u0442\u043E proxy net-volume \u043F\u043E \u0441\u0432\u0435\u0447\u0435 pivot, \u0430 \u043D\u0435 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0438\u0439 lower-timeframe volume delta TradingView.
|
|
184
|
+
`;
|
|
185
|
+
var VOLUME_DIVERGENCE_PAYLOAD_PROMPT = `
|
|
186
|
+
- \u0412 payload.additionalIndicators.volumeDivergenceContext \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F \u043A\u0440\u0430\u0442\u043A\u0430\u044F \u0441\u0432\u043E\u0434\u043A\u0430 \u043F\u043E \u0441\u0438\u043B\u0435 \u0434\u0438\u0432\u0435\u0440\u0433\u0435\u043D\u0446\u0438\u0438:
|
|
187
|
+
divergenceKind / confirmationPrice / confirmationReady / structureAdvanced / reboundFromPivotPct / confirmationDistancePct / priceDisplacementPct / divergenceAmplitudeAtrRatio / reclaimPct / confirmationCandleQuality / volumeDivergenceStrength / deltaAligned / coinBiasAligned / btcBiasAligned / deterministicQuality / approvalAllowedNow / structuralHardBlockReasons / maxAllowedQuality.
|
|
188
|
+
- \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u044D\u0442\u043E\u0442 context \u043A\u0430\u043A explicit strategy-specific summary, \u0430 \u043D\u0435 \u043F\u044B\u0442\u0430\u0439\u0441\u044F \u0437\u0430\u043D\u043E\u0432\u043E \u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E \u043E\u0431\u0449\u0438\u043C \u0441\u0432\u0435\u0447\u0430\u043C.
|
|
189
|
+
`;
|
|
190
|
+
var toFiniteNumberOrNull = (value) => {
|
|
191
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
if (typeof value === "string" && value.trim()) {
|
|
195
|
+
const parsed = Number(value);
|
|
196
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
};
|
|
200
|
+
var getLastFiniteNumber = (value) => {
|
|
201
|
+
if (Array.isArray(value)) {
|
|
202
|
+
for (let i = value.length - 1; i >= 0; i -= 1) {
|
|
203
|
+
const item = toFiniteNumberOrNull(value[i]);
|
|
204
|
+
if (item != null) {
|
|
205
|
+
return item;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
return toFiniteNumberOrNull(value);
|
|
211
|
+
};
|
|
212
|
+
var getBias = (fast, slow) => {
|
|
213
|
+
if (fast == null || slow == null) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
if (fast > slow) {
|
|
217
|
+
return "bullish";
|
|
218
|
+
}
|
|
219
|
+
if (fast < slow) {
|
|
220
|
+
return "bearish";
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
};
|
|
224
|
+
var getSignalDirection = (signal) => signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
|
|
225
|
+
var getDivergenceSummary = (signal) => {
|
|
226
|
+
const additional = signal.additionalIndicators;
|
|
227
|
+
const divergence = additional?.divergence;
|
|
228
|
+
return divergence && typeof divergence === "object" ? divergence : {};
|
|
229
|
+
};
|
|
230
|
+
var getVolumeDivergenceSetupSummary = (signal) => {
|
|
231
|
+
const additional = signal.additionalIndicators;
|
|
232
|
+
const setup = additional?.volumeDivergenceSetup;
|
|
233
|
+
return setup && typeof setup === "object" ? setup : {};
|
|
234
|
+
};
|
|
235
|
+
var getVolumeDivergenceThresholdSummary = (signal) => {
|
|
236
|
+
const additional = signal.additionalIndicators;
|
|
237
|
+
const thresholds = additional?.volumeDivergenceThresholds;
|
|
238
|
+
if (!thresholds || typeof thresholds !== "object") {
|
|
239
|
+
return DEFAULT_VOLUME_DIVERGENCE_ENTRY_THRESHOLDS;
|
|
240
|
+
}
|
|
241
|
+
const candidate = thresholds;
|
|
242
|
+
return {
|
|
243
|
+
allowStructureAdvanceEntry: typeof candidate.allowStructureAdvanceEntry === "boolean" ? candidate.allowStructureAdvanceEntry : DEFAULT_VOLUME_DIVERGENCE_ENTRY_THRESHOLDS.allowStructureAdvanceEntry,
|
|
244
|
+
minDivergenceAmplitudeAtrRatio: toFiniteNumberOrNull(candidate.minDivergenceAmplitudeAtrRatio) ?? DEFAULT_VOLUME_DIVERGENCE_ENTRY_THRESHOLDS.minDivergenceAmplitudeAtrRatio,
|
|
245
|
+
minReclaimPct: toFiniteNumberOrNull(candidate.minReclaimPct) ?? DEFAULT_VOLUME_DIVERGENCE_ENTRY_THRESHOLDS.minReclaimPct,
|
|
246
|
+
minConfirmationCandleQuality: toFiniteNumberOrNull(candidate.minConfirmationCandleQuality) ?? DEFAULT_VOLUME_DIVERGENCE_ENTRY_THRESHOLDS.minConfirmationCandleQuality
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
var isAtLeast = (value, threshold) => value != null && value >= threshold;
|
|
250
|
+
var isAtMost = (value, threshold) => value != null && value <= threshold;
|
|
251
|
+
var isInRange = (value, min, max) => value != null && value >= min && value <= max;
|
|
252
|
+
var getConfirmationDistancePct = ({
|
|
253
|
+
signalDirection,
|
|
254
|
+
currentPrice,
|
|
255
|
+
confirmationPrice,
|
|
256
|
+
setupValue
|
|
257
|
+
}) => {
|
|
258
|
+
if (setupValue != null) {
|
|
259
|
+
return setupValue;
|
|
260
|
+
}
|
|
261
|
+
if (signalDirection == null || currentPrice == null || confirmationPrice == null || confirmationPrice <= 0) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
return signalDirection === "LONG" ? (currentPrice - confirmationPrice) / confirmationPrice * 100 : (confirmationPrice - currentPrice) / confirmationPrice * 100;
|
|
265
|
+
};
|
|
266
|
+
var buildHardBlockReasons = ({
|
|
267
|
+
confirmationReady,
|
|
268
|
+
reboundFromPivotPct,
|
|
269
|
+
divergenceAmplitudeAtrRatio,
|
|
270
|
+
reclaimPct,
|
|
271
|
+
confirmationCandleQuality,
|
|
272
|
+
entryThresholds
|
|
273
|
+
}) => {
|
|
274
|
+
const reasons = [];
|
|
275
|
+
if (reboundFromPivotPct != null && reboundFromPivotPct <= 0) {
|
|
276
|
+
reasons.push("no_rebound_from_pivot");
|
|
277
|
+
}
|
|
278
|
+
if (divergenceAmplitudeAtrRatio != null && divergenceAmplitudeAtrRatio < entryThresholds.minDivergenceAmplitudeAtrRatio) {
|
|
279
|
+
reasons.push("weak_divergence_amplitude");
|
|
280
|
+
}
|
|
281
|
+
if (confirmationReady && reclaimPct != null && reclaimPct < entryThresholds.minReclaimPct) {
|
|
282
|
+
reasons.push("weak_reclaim");
|
|
283
|
+
}
|
|
284
|
+
if (confirmationReady && confirmationCandleQuality != null && confirmationCandleQuality < entryThresholds.minConfirmationCandleQuality) {
|
|
285
|
+
reasons.push("weak_confirmation_candle");
|
|
286
|
+
}
|
|
287
|
+
return reasons;
|
|
288
|
+
};
|
|
289
|
+
var getLongQ4Demotion = ({
|
|
290
|
+
divergenceAmplitudeAtrRatio,
|
|
291
|
+
volumeDivergenceRatio,
|
|
292
|
+
coinBiasAligned,
|
|
293
|
+
btcBiasAligned,
|
|
294
|
+
confirmationDistancePct,
|
|
295
|
+
barsSinceDetection,
|
|
296
|
+
atrPct,
|
|
297
|
+
reclaimPct
|
|
298
|
+
}) => {
|
|
299
|
+
const longOverextendedWithoutVolumeSupport = isAtLeast(divergenceAmplitudeAtrRatio, 2.2) && volumeDivergenceRatio != null && volumeDivergenceRatio < 2.2;
|
|
300
|
+
const longBtcLedWithoutCoinSupport = coinBiasAligned === false && btcBiasAligned === true && volumeDivergenceRatio != null && volumeDivergenceRatio < 2.6;
|
|
301
|
+
const longDoubleConflictWithoutMaturity = coinBiasAligned === false && btcBiasAligned === false && (!isAtLeast(confirmationDistancePct, 0.35) || !isAtLeast(barsSinceDetection, 2) || !isAtMost(atrPct, 0.95));
|
|
302
|
+
const longDoubleConflictOverextended = coinBiasAligned === false && btcBiasAligned === false && (!isAtMost(confirmationDistancePct, 1.4) || !isAtMost(divergenceAmplitudeAtrRatio, 2.2) || !isAtMost(atrPct, 1) || !isAtLeast(reclaimPct, 130));
|
|
303
|
+
const longDoubleConflictStaleConfirmation = coinBiasAligned === false && btcBiasAligned === false && isAtLeast(barsSinceDetection, 5);
|
|
304
|
+
const longFullyAlignedLateWeakConfirmation = coinBiasAligned === true && btcBiasAligned === true && isAtLeast(barsSinceDetection, 5) && (!isAtLeast(confirmationDistancePct, 1.2) || !isAtLeast(reclaimPct, 170));
|
|
305
|
+
const longLateExtendedConfirmation = isAtLeast(barsSinceDetection, 6) && isAtLeast(confirmationDistancePct, 1.5) && !isAtMost(atrPct, 1);
|
|
306
|
+
return longOverextendedWithoutVolumeSupport || longBtcLedWithoutCoinSupport || longDoubleConflictWithoutMaturity || longDoubleConflictOverextended || longDoubleConflictStaleConfirmation || longFullyAlignedLateWeakConfirmation || longLateExtendedConfirmation;
|
|
307
|
+
};
|
|
308
|
+
var getLongDeterministicQuality = ({
|
|
309
|
+
confirmationReady,
|
|
310
|
+
structureAdvanced,
|
|
311
|
+
hardBlockReasons,
|
|
312
|
+
divergenceAmplitudeAtrRatio,
|
|
313
|
+
reclaimPct,
|
|
314
|
+
confirmationCandleQuality,
|
|
315
|
+
atrPct,
|
|
316
|
+
confirmationDistancePct,
|
|
317
|
+
reboundFromPivotPct,
|
|
318
|
+
volumeDivergenceStrength,
|
|
319
|
+
volumeDivergenceRatio,
|
|
320
|
+
deltaAligned,
|
|
321
|
+
barsSinceDetection,
|
|
322
|
+
coinBiasAligned,
|
|
323
|
+
btcBiasAligned,
|
|
324
|
+
entryThresholds,
|
|
325
|
+
aiThresholds
|
|
326
|
+
}) => {
|
|
327
|
+
if (hardBlockReasons.length > 0) {
|
|
328
|
+
return 2;
|
|
329
|
+
}
|
|
330
|
+
const reboundModerate = isAtLeast(reboundFromPivotPct, 0.6);
|
|
331
|
+
const reboundStrong = isAtLeast(reboundFromPivotPct, 1.2);
|
|
332
|
+
const reboundVeryStrong = isAtLeast(reboundFromPivotPct, 1.8);
|
|
333
|
+
const confirmationDistanceModerate = isAtLeast(confirmationDistancePct, 0.35);
|
|
334
|
+
const confirmationDistanceStrong = isAtLeast(confirmationDistancePct, 0.7);
|
|
335
|
+
const confirmationDistanceContained = isAtMost(confirmationDistancePct, 1.4);
|
|
336
|
+
const confirmationDistanceBalanced = isInRange(
|
|
337
|
+
confirmationDistancePct,
|
|
338
|
+
0.45,
|
|
339
|
+
1.1
|
|
340
|
+
);
|
|
341
|
+
const maturityReady = isAtLeast(barsSinceDetection, 2);
|
|
342
|
+
const maturityFresh = isInRange(barsSinceDetection, 2, 5);
|
|
343
|
+
const maturityCounterTrend = isInRange(barsSinceDetection, 2, 4);
|
|
344
|
+
const calmAtr = isAtMost(atrPct, 0.95);
|
|
345
|
+
const veryCalmAtr = isAtMost(atrPct, 0.85);
|
|
346
|
+
const volumeModerate = isAtLeast(volumeDivergenceStrength, 5);
|
|
347
|
+
const volumeStrong = isAtLeast(volumeDivergenceStrength, 15);
|
|
348
|
+
const volumeVeryStrong = isAtLeast(volumeDivergenceStrength, 30);
|
|
349
|
+
const volumeRatioModerate = isAtLeast(volumeDivergenceRatio, 1.3);
|
|
350
|
+
const volumeRatioStrong = isAtLeast(volumeDivergenceRatio, 1.7);
|
|
351
|
+
const volumeRatioVeryStrong = isAtLeast(volumeDivergenceRatio, 2.2);
|
|
352
|
+
const longBiasConflictCount = Number(coinBiasAligned === false) + Number(btcBiasAligned === false);
|
|
353
|
+
const longQ4Demotion = getLongQ4Demotion({
|
|
354
|
+
divergenceAmplitudeAtrRatio,
|
|
355
|
+
volumeDivergenceRatio,
|
|
356
|
+
coinBiasAligned,
|
|
357
|
+
btcBiasAligned,
|
|
358
|
+
confirmationDistancePct,
|
|
359
|
+
barsSinceDetection,
|
|
360
|
+
atrPct,
|
|
361
|
+
reclaimPct
|
|
362
|
+
});
|
|
363
|
+
const q4SetupReady = aiThresholds != null && isAtLeast(
|
|
364
|
+
divergenceAmplitudeAtrRatio,
|
|
365
|
+
aiThresholds.q4DivergenceAmplitudeAtrRatio
|
|
366
|
+
) && isAtLeast(reclaimPct, aiThresholds.q4ReclaimPct) && isAtLeast(
|
|
367
|
+
confirmationCandleQuality,
|
|
368
|
+
aiThresholds.q4ConfirmationCandleQuality
|
|
369
|
+
);
|
|
370
|
+
const q5SetupReady = aiThresholds != null && isAtLeast(
|
|
371
|
+
divergenceAmplitudeAtrRatio,
|
|
372
|
+
aiThresholds.q5DivergenceAmplitudeAtrRatio
|
|
373
|
+
) && isAtLeast(reclaimPct, aiThresholds.q5ReclaimPct) && isAtLeast(
|
|
374
|
+
confirmationCandleQuality,
|
|
375
|
+
aiThresholds.q5ConfirmationCandleQuality
|
|
376
|
+
);
|
|
377
|
+
const minimumSetupReady = isAtLeast(
|
|
378
|
+
divergenceAmplitudeAtrRatio,
|
|
379
|
+
entryThresholds.minDivergenceAmplitudeAtrRatio
|
|
380
|
+
) && isAtLeast(reclaimPct, entryThresholds.minReclaimPct) && isAtLeast(
|
|
381
|
+
confirmationCandleQuality,
|
|
382
|
+
entryThresholds.minConfirmationCandleQuality
|
|
383
|
+
);
|
|
384
|
+
const longSelectivePromotion = confirmationReady && minimumSetupReady && reboundStrong && confirmationDistanceBalanced && maturityFresh && calmAtr && volumeStrong && volumeRatioStrong && isAtLeast(reclaimPct, Math.max(entryThresholds.minReclaimPct + 15, 130)) && isAtLeast(
|
|
385
|
+
confirmationCandleQuality,
|
|
386
|
+
Math.max(entryThresholds.minConfirmationCandleQuality + 0.1, 0.7)
|
|
387
|
+
) && deltaAligned !== false && longBiasConflictCount <= 1 && !longQ4Demotion;
|
|
388
|
+
const longAlignedFnPromotion = confirmationReady && minimumSetupReady && coinBiasAligned === true && btcBiasAligned === true && reboundModerate && calmAtr && isAtLeast(reclaimPct, 145) && isAtLeast(confirmationCandleQuality, 0.8) && isAtMost(divergenceAmplitudeAtrRatio, 1.8) && isAtMost(confirmationDistancePct, 0.8) && !longQ4Demotion;
|
|
389
|
+
const longSemiAlignedFnPromotion = confirmationReady && minimumSetupReady && (coinBiasAligned === true || btcBiasAligned === true) && reboundModerate && isAtLeast(reclaimPct, 140) && isAtLeast(confirmationCandleQuality, 0.8) && isAtMost(divergenceAmplitudeAtrRatio, 2.5) && !longQ4Demotion;
|
|
390
|
+
const longCounterTrendSelectivePromotion = confirmationReady && minimumSetupReady && reboundStrong && confirmationDistanceBalanced && maturityCounterTrend && veryCalmAtr && volumeVeryStrong && volumeRatioVeryStrong && isAtLeast(reclaimPct, 130) && isAtLeast(
|
|
391
|
+
confirmationCandleQuality,
|
|
392
|
+
Math.max(entryThresholds.minConfirmationCandleQuality + 0.1, 0.7)
|
|
393
|
+
) && longBiasConflictCount === 2 && !longQ4Demotion;
|
|
394
|
+
if (confirmationReady && q5SetupReady && reboundVeryStrong && confirmationDistanceStrong && confirmationDistanceContained && maturityReady && calmAtr && volumeVeryStrong && volumeRatioVeryStrong && deltaAligned === true && longBiasConflictCount === 0 && !longQ4Demotion) {
|
|
395
|
+
return 5;
|
|
396
|
+
}
|
|
397
|
+
if (longCounterTrendSelectivePromotion || longSemiAlignedFnPromotion || longAlignedFnPromotion || longSelectivePromotion || confirmationReady && q4SetupReady && reboundModerate && confirmationDistanceModerate && confirmationDistanceContained && volumeModerate && volumeRatioModerate && !longQ4Demotion && deltaAligned !== false) {
|
|
398
|
+
return 4;
|
|
399
|
+
}
|
|
400
|
+
if (confirmationReady && minimumSetupReady && reboundModerate) {
|
|
401
|
+
return 3;
|
|
402
|
+
}
|
|
403
|
+
if (structureAdvanced && isAtLeast(reboundFromPivotPct, 0.25)) {
|
|
404
|
+
return 3;
|
|
405
|
+
}
|
|
406
|
+
return 2;
|
|
407
|
+
};
|
|
408
|
+
var getShortDeterministicQuality = ({
|
|
409
|
+
confirmationReady,
|
|
410
|
+
structureAdvanced,
|
|
411
|
+
hardBlockReasons,
|
|
412
|
+
divergenceAmplitudeAtrRatio,
|
|
413
|
+
reclaimPct,
|
|
414
|
+
confirmationCandleQuality,
|
|
415
|
+
reboundFromPivotPct,
|
|
416
|
+
volumeDivergenceStrength,
|
|
417
|
+
deltaAligned,
|
|
418
|
+
coinBiasAligned,
|
|
419
|
+
btcBiasAligned,
|
|
420
|
+
entryThresholds,
|
|
421
|
+
aiThresholds
|
|
422
|
+
}) => {
|
|
423
|
+
if (hardBlockReasons.length > 0) {
|
|
424
|
+
return 2;
|
|
425
|
+
}
|
|
426
|
+
const reboundModerate = isAtLeast(reboundFromPivotPct, 0.6);
|
|
427
|
+
const reboundStrong = isAtLeast(reboundFromPivotPct, 1.2);
|
|
428
|
+
const reboundVeryStrong = isAtLeast(reboundFromPivotPct, 1.8);
|
|
429
|
+
const volumeVeryStrong = isAtLeast(volumeDivergenceStrength, 30);
|
|
430
|
+
const shortBiasConflictCount = Number(coinBiasAligned === false) + Number(btcBiasAligned === false);
|
|
431
|
+
const q4SetupReady = aiThresholds != null && isAtLeast(
|
|
432
|
+
divergenceAmplitudeAtrRatio,
|
|
433
|
+
aiThresholds.q4DivergenceAmplitudeAtrRatio
|
|
434
|
+
) && isAtLeast(reclaimPct, aiThresholds.q4ReclaimPct) && isAtLeast(
|
|
435
|
+
confirmationCandleQuality,
|
|
436
|
+
aiThresholds.q4ConfirmationCandleQuality
|
|
437
|
+
);
|
|
438
|
+
const q5SetupReady = aiThresholds != null && isAtLeast(
|
|
439
|
+
divergenceAmplitudeAtrRatio,
|
|
440
|
+
aiThresholds.q5DivergenceAmplitudeAtrRatio
|
|
441
|
+
) && isAtLeast(reclaimPct, aiThresholds.q5ReclaimPct) && isAtLeast(
|
|
442
|
+
confirmationCandleQuality,
|
|
443
|
+
aiThresholds.q5ConfirmationCandleQuality
|
|
444
|
+
);
|
|
445
|
+
const minimumSetupReady = isAtLeast(
|
|
446
|
+
divergenceAmplitudeAtrRatio,
|
|
447
|
+
entryThresholds.minDivergenceAmplitudeAtrRatio
|
|
448
|
+
) && isAtLeast(reclaimPct, entryThresholds.minReclaimPct) && isAtLeast(
|
|
449
|
+
confirmationCandleQuality,
|
|
450
|
+
entryThresholds.minConfirmationCandleQuality
|
|
451
|
+
);
|
|
452
|
+
if (confirmationReady && q5SetupReady && reboundVeryStrong && volumeVeryStrong && deltaAligned === true && shortBiasConflictCount === 0) {
|
|
453
|
+
return 5;
|
|
454
|
+
}
|
|
455
|
+
if (confirmationReady && q4SetupReady && reboundStrong && volumeVeryStrong && deltaAligned === true && shortBiasConflictCount === 0) {
|
|
456
|
+
return 4;
|
|
457
|
+
}
|
|
458
|
+
if (confirmationReady && minimumSetupReady && reboundModerate && deltaAligned !== false) {
|
|
459
|
+
return 3;
|
|
460
|
+
}
|
|
461
|
+
if (structureAdvanced && isAtLeast(reboundFromPivotPct, 0.25) && deltaAligned !== false) {
|
|
462
|
+
return 3;
|
|
463
|
+
}
|
|
464
|
+
return 2;
|
|
465
|
+
};
|
|
466
|
+
var getVolumeDivergenceContext = (signal) => {
|
|
467
|
+
const signalDirection = getSignalDirection(signal);
|
|
468
|
+
const divergence = getDivergenceSummary(signal);
|
|
469
|
+
const divergenceKind = divergence.kind === "bullish" || divergence.kind === "bearish" ? divergence.kind : null;
|
|
470
|
+
const currentPrice = toFiniteNumberOrNull(signal.prices?.currentPrice);
|
|
471
|
+
const currentPivotLow = toFiniteNumberOrNull(
|
|
472
|
+
divergence.currentPivot?.priceLow
|
|
473
|
+
);
|
|
474
|
+
const currentPivotHigh = toFiniteNumberOrNull(
|
|
475
|
+
divergence.currentPivot?.priceHigh
|
|
476
|
+
);
|
|
477
|
+
const previousPivotLow = toFiniteNumberOrNull(
|
|
478
|
+
divergence.previousPivot?.priceLow
|
|
479
|
+
);
|
|
480
|
+
const previousPivotHigh = toFiniteNumberOrNull(
|
|
481
|
+
divergence.previousPivot?.priceHigh
|
|
482
|
+
);
|
|
483
|
+
const currentVolumeNorm = toFiniteNumberOrNull(
|
|
484
|
+
divergence.currentPivot?.volumeNorm
|
|
485
|
+
);
|
|
486
|
+
const previousVolumeNorm = toFiniteNumberOrNull(
|
|
487
|
+
divergence.previousPivot?.volumeNorm
|
|
488
|
+
);
|
|
489
|
+
const pivotLookbackRight = toFiniteNumberOrNull(
|
|
490
|
+
divergence.pivotLookbackRight
|
|
491
|
+
);
|
|
492
|
+
const barsBetweenPivotConfirmations = toFiniteNumberOrNull(
|
|
493
|
+
divergence.barsBetweenPivotConfirmations
|
|
494
|
+
);
|
|
495
|
+
const timing = signal.additionalIndicators?.volumeDivergenceSignalTiming;
|
|
496
|
+
const entryTiming = timing?.entryTiming === "confirmation_ready" || timing?.entryTiming === "structure_advance" ? timing.entryTiming : null;
|
|
497
|
+
const barsSinceDetection = toFiniteNumberOrNull(timing?.barsSinceDetection);
|
|
498
|
+
const deltaAtPivot = toFiniteNumberOrNull(
|
|
499
|
+
signal.additionalIndicators?.deltaAtPivot
|
|
500
|
+
);
|
|
501
|
+
const setup = getVolumeDivergenceSetupSummary(signal);
|
|
502
|
+
const atrPct = toFiniteNumberOrNull(setup.atrPct);
|
|
503
|
+
const divergenceAmplitudeAtrRatio = toFiniteNumberOrNull(
|
|
504
|
+
setup.divergenceAmplitudeAtrRatio
|
|
505
|
+
);
|
|
506
|
+
const reclaimPct = toFiniteNumberOrNull(setup.reclaimPct);
|
|
507
|
+
const confirmationCandleQuality = toFiniteNumberOrNull(
|
|
508
|
+
setup.confirmationCandleQuality
|
|
509
|
+
);
|
|
510
|
+
const setupConfirmationDistancePct = toFiniteNumberOrNull(
|
|
511
|
+
setup.confirmationDistancePct
|
|
512
|
+
);
|
|
513
|
+
const entryThresholds = getVolumeDivergenceThresholdSummary(signal);
|
|
514
|
+
const coinMaBias = getBias(
|
|
515
|
+
getLastFiniteNumber(signal.indicators?.maFast),
|
|
516
|
+
getLastFiniteNumber(signal.indicators?.maSlow)
|
|
517
|
+
);
|
|
518
|
+
const btcMaBias = getBias(
|
|
519
|
+
getLastFiniteNumber(signal.indicators?.btcMaFast),
|
|
520
|
+
getLastFiniteNumber(signal.indicators?.btcMaSlow)
|
|
521
|
+
);
|
|
522
|
+
const confirmationPrice = divergenceKind === "bullish" ? currentPivotHigh : divergenceKind === "bearish" ? currentPivotLow : null;
|
|
523
|
+
const confirmationReady = divergenceKind === "bullish" ? currentPrice != null && confirmationPrice != null && currentPrice >= confirmationPrice : divergenceKind === "bearish" ? currentPrice != null && confirmationPrice != null && currentPrice <= confirmationPrice : false;
|
|
524
|
+
const confirmationDistancePct = getConfirmationDistancePct({
|
|
525
|
+
signalDirection,
|
|
526
|
+
currentPrice,
|
|
527
|
+
confirmationPrice,
|
|
528
|
+
setupValue: setupConfirmationDistancePct
|
|
529
|
+
});
|
|
530
|
+
const structureAdvanced = divergenceKind === "bullish" ? currentPrice != null && previousPivotLow != null && currentPrice >= previousPivotLow : divergenceKind === "bearish" ? currentPrice != null && previousPivotHigh != null && currentPrice <= previousPivotHigh : false;
|
|
531
|
+
const reboundFromPivotPct = divergenceKind === "bullish" && currentPrice != null && currentPivotLow != null && currentPivotLow > 0 ? (currentPrice - currentPivotLow) / currentPivotLow * 100 : divergenceKind === "bearish" && currentPrice != null && currentPivotHigh != null && currentPivotHigh > 0 ? (currentPivotHigh - currentPrice) / currentPivotHigh * 100 : null;
|
|
532
|
+
const priceDisplacementPct = divergenceKind === "bullish" && currentPivotLow != null && previousPivotLow != null && previousPivotLow > 0 ? (previousPivotLow - currentPivotLow) / previousPivotLow * 100 : divergenceKind === "bearish" && currentPivotHigh != null && previousPivotHigh != null && previousPivotHigh > 0 ? (currentPivotHigh - previousPivotHigh) / previousPivotHigh * 100 : null;
|
|
533
|
+
const volumeDivergenceStrength = divergenceKind === "bullish" && currentVolumeNorm != null && previousVolumeNorm != null ? currentVolumeNorm - previousVolumeNorm : divergenceKind === "bearish" && currentVolumeNorm != null && previousVolumeNorm != null ? previousVolumeNorm - currentVolumeNorm : null;
|
|
534
|
+
const volumeDivergenceRatio = divergenceKind === "bullish" && currentVolumeNorm != null && previousVolumeNorm != null && previousVolumeNorm > 0 ? currentVolumeNorm / previousVolumeNorm : divergenceKind === "bearish" && currentVolumeNorm != null && previousVolumeNorm != null && currentVolumeNorm > 0 ? previousVolumeNorm / currentVolumeNorm : null;
|
|
535
|
+
const deltaAligned = signalDirection === "LONG" ? deltaAtPivot != null ? deltaAtPivot > 0 : null : signalDirection === "SHORT" ? deltaAtPivot != null ? deltaAtPivot < 0 : null : null;
|
|
536
|
+
const coinBiasAligned = signalDirection === "LONG" ? coinMaBias != null ? coinMaBias === "bullish" : null : signalDirection === "SHORT" ? coinMaBias != null ? coinMaBias === "bearish" : null : null;
|
|
537
|
+
const btcBiasAligned = signalDirection === "LONG" ? btcMaBias != null ? btcMaBias === "bullish" : null : signalDirection === "SHORT" ? btcMaBias != null ? btcMaBias === "bearish" : null : null;
|
|
538
|
+
const aiThresholds = signalDirection != null ? getVolumeDivergenceAiThresholds(signalDirection) : null;
|
|
539
|
+
const hardBlockReasons = buildHardBlockReasons({
|
|
540
|
+
confirmationReady,
|
|
541
|
+
reboundFromPivotPct,
|
|
542
|
+
divergenceAmplitudeAtrRatio,
|
|
543
|
+
reclaimPct,
|
|
544
|
+
confirmationCandleQuality,
|
|
545
|
+
entryThresholds
|
|
546
|
+
});
|
|
547
|
+
const deterministicQuality = signalDirection === "LONG" ? getLongDeterministicQuality({
|
|
548
|
+
confirmationReady,
|
|
549
|
+
structureAdvanced,
|
|
550
|
+
hardBlockReasons,
|
|
551
|
+
divergenceAmplitudeAtrRatio,
|
|
552
|
+
reclaimPct,
|
|
553
|
+
confirmationCandleQuality,
|
|
554
|
+
atrPct,
|
|
555
|
+
confirmationDistancePct,
|
|
556
|
+
reboundFromPivotPct,
|
|
557
|
+
volumeDivergenceStrength,
|
|
558
|
+
volumeDivergenceRatio,
|
|
559
|
+
deltaAligned,
|
|
560
|
+
barsSinceDetection,
|
|
561
|
+
coinBiasAligned,
|
|
562
|
+
btcBiasAligned,
|
|
563
|
+
entryThresholds,
|
|
564
|
+
aiThresholds
|
|
565
|
+
}) : signalDirection === "SHORT" ? getShortDeterministicQuality({
|
|
566
|
+
confirmationReady,
|
|
567
|
+
structureAdvanced,
|
|
568
|
+
hardBlockReasons,
|
|
569
|
+
divergenceAmplitudeAtrRatio,
|
|
570
|
+
reclaimPct,
|
|
571
|
+
confirmationCandleQuality,
|
|
572
|
+
reboundFromPivotPct,
|
|
573
|
+
volumeDivergenceStrength,
|
|
574
|
+
deltaAligned,
|
|
575
|
+
coinBiasAligned,
|
|
576
|
+
btcBiasAligned,
|
|
577
|
+
entryThresholds,
|
|
578
|
+
aiThresholds
|
|
579
|
+
}) : hardBlockReasons.length > 0 ? 2 : 3;
|
|
580
|
+
const approvalAllowedNow = hardBlockReasons.length === 0 && deterministicQuality >= 4 && confirmationReady;
|
|
581
|
+
return {
|
|
582
|
+
signalDirection,
|
|
583
|
+
divergenceKind,
|
|
584
|
+
confirmationPrice,
|
|
585
|
+
confirmationReady,
|
|
586
|
+
structureAdvanced,
|
|
587
|
+
reboundFromPivotPct,
|
|
588
|
+
confirmationDistancePct,
|
|
589
|
+
priceDisplacementPct,
|
|
590
|
+
atrPct,
|
|
591
|
+
divergenceAmplitudeAtrRatio,
|
|
592
|
+
reclaimPct,
|
|
593
|
+
confirmationCandleQuality,
|
|
594
|
+
volumeDivergenceStrength,
|
|
595
|
+
volumeDivergenceRatio,
|
|
596
|
+
deltaAtPivot,
|
|
597
|
+
deltaAligned,
|
|
598
|
+
barsSincePivot: pivotLookbackRight,
|
|
599
|
+
barsBetweenPivotConfirmations,
|
|
600
|
+
entryTiming,
|
|
601
|
+
barsSinceDetection,
|
|
602
|
+
coinMaBias,
|
|
603
|
+
btcMaBias,
|
|
604
|
+
coinBiasAligned,
|
|
605
|
+
btcBiasAligned,
|
|
606
|
+
hardBlockReasons,
|
|
607
|
+
structuralHardBlockReasons: [...hardBlockReasons],
|
|
608
|
+
deterministicQuality,
|
|
609
|
+
approvalAllowedNow,
|
|
610
|
+
maxAllowedQuality: deterministicQuality
|
|
611
|
+
};
|
|
612
|
+
};
|
|
613
|
+
var getVolumeDivergenceContextFromPayload = (payload, signal) => {
|
|
614
|
+
const additional = payload.additionalIndicators;
|
|
615
|
+
const fromPayload = additional?.volumeDivergenceContext;
|
|
616
|
+
if (fromPayload && typeof fromPayload === "object") {
|
|
617
|
+
return fromPayload;
|
|
618
|
+
}
|
|
619
|
+
return getVolumeDivergenceContext(signal);
|
|
620
|
+
};
|
|
621
|
+
var clampQuality = (quality, maxAllowedQuality) => Math.max(1, Math.min(5, Math.min(quality, maxAllowedQuality)));
|
|
622
|
+
var getHardBlockReasonText = (reason) => {
|
|
623
|
+
switch (reason) {
|
|
624
|
+
case "no_rebound_from_pivot":
|
|
625
|
+
return "\u0446\u0435\u043D\u0430 \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u0430 \u0443\u0439\u0442\u0438 \u043E\u0442 pivot \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443 reversal";
|
|
626
|
+
case "weak_divergence_amplitude":
|
|
627
|
+
return "\u0434\u0438\u0432\u0435\u0440\u0433\u0435\u043D\u0446\u0438\u044F \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u0430\u043B\u0435\u043D\u044C\u043A\u0430\u044F \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E ATR";
|
|
628
|
+
case "weak_reclaim":
|
|
629
|
+
return "\u0446\u0435\u043D\u0430 \u0432\u0435\u0440\u043D\u0443\u043B\u0430 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u0430\u043B\u043E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B \u043F\u043E\u0441\u043B\u0435 pivot";
|
|
630
|
+
case "weak_confirmation_candle":
|
|
631
|
+
return "confirmation candle \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0441\u043B\u0430\u0431\u0430\u044F";
|
|
632
|
+
default:
|
|
633
|
+
return reason;
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
var buildGuardrailReason = (context) => {
|
|
637
|
+
if (context.hardBlockReasons.length > 0) {
|
|
638
|
+
return `VolumeDivergence guardrail: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.`;
|
|
639
|
+
}
|
|
640
|
+
if (!context.confirmationReady && context.entryTiming == null) {
|
|
641
|
+
return "VolumeDivergence guardrail: reversal \u0443\u0436\u0435 \u0432\u0438\u0434\u0435\u043D, \u043D\u043E confirmation level \u0435\u0449\u0435 \u043D\u0435 \u043F\u0440\u043E\u0439\u0434\u0435\u043D.";
|
|
642
|
+
}
|
|
643
|
+
return "VolumeDivergence guardrail: quality \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0441\u0442\u044C\u044E \u0438 \u0441\u0438\u043B\u043E\u0439 reversal away from pivot.";
|
|
644
|
+
};
|
|
645
|
+
var postProcessAnalysis = ({
|
|
646
|
+
signal,
|
|
647
|
+
payload,
|
|
648
|
+
analysis
|
|
649
|
+
}) => {
|
|
650
|
+
const context = getVolumeDivergenceContextFromPayload(payload, signal);
|
|
651
|
+
const signalDirection = getSignalDirection(signal);
|
|
652
|
+
const requestedDirection = analysis.direction === signalDirection ? signalDirection : null;
|
|
653
|
+
const finalDirection = requestedDirection != null && context.approvalAllowedNow ? requestedDirection : null;
|
|
654
|
+
const requestedQuality = typeof analysis.quality === "number" ? analysis.quality : context.maxAllowedQuality;
|
|
655
|
+
const finalQuality = clampQuality(
|
|
656
|
+
requestedQuality,
|
|
657
|
+
context.maxAllowedQuality
|
|
658
|
+
);
|
|
659
|
+
const needRetest = finalDirection == null;
|
|
660
|
+
const retestPrice = needRetest ? context.confirmationPrice : null;
|
|
661
|
+
if (finalDirection == null) {
|
|
662
|
+
return {
|
|
663
|
+
...analysis,
|
|
664
|
+
direction: null,
|
|
665
|
+
quality: finalQuality,
|
|
666
|
+
needRetest,
|
|
667
|
+
retestPrice,
|
|
668
|
+
takeProfitPrice: null,
|
|
669
|
+
stopLossPrice: null,
|
|
670
|
+
qualityReason: analysis.qualityReason || buildGuardrailReason(context),
|
|
671
|
+
triggerInvalidation: analysis.triggerInvalidation || (context.confirmationPrice != null ? `\u0416\u0434\u0430\u0442\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 reversal \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0443\u0440\u043E\u0432\u043D\u044F ${context.confirmationPrice}.` : "\u0416\u0434\u0430\u0442\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u0439 reversal \u043F\u043E\u0441\u043B\u0435 pivot."),
|
|
672
|
+
comment: analysis.comment || (context.hardBlockReasons.length > 0 ? `VolumeDivergence \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "VolumeDivergence \u043F\u043E\u043A\u0430 \u043E\u0441\u0442\u0430\u0435\u0442\u0441\u044F \u0432 \u0441\u0442\u0430\u0434\u0438\u0438 watch \u0434\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F reversal.")
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
return {
|
|
676
|
+
...analysis,
|
|
677
|
+
direction: finalDirection,
|
|
678
|
+
quality: finalQuality,
|
|
679
|
+
needRetest,
|
|
680
|
+
retestPrice,
|
|
681
|
+
takeProfitPrice: signal.prices?.takeProfitPrice ?? null,
|
|
682
|
+
stopLossPrice: signal.prices?.stopLossPrice ?? null
|
|
683
|
+
};
|
|
684
|
+
};
|
|
685
|
+
var volumeDivergenceAiAdapter = {
|
|
686
|
+
buildPayload: ({ signal, basePayload }) => ({
|
|
687
|
+
...basePayload,
|
|
688
|
+
additionalIndicators: {
|
|
689
|
+
...basePayload.additionalIndicators,
|
|
690
|
+
volumeDivergenceContext: getVolumeDivergenceContext(signal)
|
|
691
|
+
}
|
|
692
|
+
}),
|
|
693
|
+
postProcessAnalysis,
|
|
694
|
+
buildSystemPromptAddon: () => `${VOLUME_DIVERGENCE_CONTEXT_PROMPT}
|
|
695
|
+
${VOLUME_DIVERGENCE_PAYLOAD_PROMPT}`,
|
|
696
|
+
buildHumanPromptAddon: ({ signal, payload }) => {
|
|
697
|
+
const context = getVolumeDivergenceContextFromPayload(payload, signal);
|
|
698
|
+
return `
|
|
699
|
+
|
|
700
|
+
\u0414\u043E\u043F. \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 VolumeDivergence:
|
|
701
|
+
- divergenceKind=${context.divergenceKind ?? "n/a"}
|
|
702
|
+
- confirmationPrice=${context.confirmationPrice ?? "n/a"}
|
|
703
|
+
- confirmationReady=${context.confirmationReady}
|
|
704
|
+
- structureAdvanced=${context.structureAdvanced}
|
|
705
|
+
- reboundFromPivotPct=${context.reboundFromPivotPct?.toFixed?.(3) ?? "n/a"}%
|
|
706
|
+
- atrPct=${context.atrPct?.toFixed?.(3) ?? "n/a"}%
|
|
707
|
+
- divergenceAmplitudeAtrRatio=${context.divergenceAmplitudeAtrRatio?.toFixed?.(3) ?? "n/a"}
|
|
708
|
+
- reclaimPct=${context.reclaimPct?.toFixed?.(3) ?? "n/a"}
|
|
709
|
+
- confirmationCandleQuality=${context.confirmationCandleQuality?.toFixed?.(3) ?? "n/a"}
|
|
710
|
+
- confirmationDistancePct=${context.confirmationDistancePct?.toFixed?.(3) ?? "n/a"}%
|
|
711
|
+
- priceDisplacementPct=${context.priceDisplacementPct?.toFixed?.(3) ?? "n/a"}%
|
|
712
|
+
- volumeDivergenceStrength=${context.volumeDivergenceStrength?.toFixed?.(3) ?? "n/a"}
|
|
713
|
+
- volumeDivergenceRatio=${context.volumeDivergenceRatio?.toFixed?.(3) ?? "n/a"}
|
|
714
|
+
- deltaAligned=${context.deltaAligned}
|
|
715
|
+
- coinBiasAligned=${context.coinBiasAligned}
|
|
716
|
+
- btcBiasAligned=${context.btcBiasAligned}
|
|
717
|
+
- barsSincePivot=${context.barsSincePivot ?? "n/a"}
|
|
718
|
+
- barsBetweenPivotConfirmations=${context.barsBetweenPivotConfirmations ?? "n/a"}
|
|
719
|
+
- entryTiming=${context.entryTiming ?? "n/a"}
|
|
720
|
+
- barsSinceDetection=${context.barsSinceDetection ?? "n/a"}
|
|
721
|
+
- deterministicQuality=${context.deterministicQuality}
|
|
722
|
+
- approvalAllowedNow=${context.approvalAllowedNow}
|
|
723
|
+
- structuralHardBlockReasons=${context.structuralHardBlockReasons.join(", ") || "none"}
|
|
724
|
+
- maxAllowedQuality=${context.maxAllowedQuality}
|
|
725
|
+
- hardBlockReasons=${context.hardBlockReasons.join(", ") || "none"}
|
|
726
|
+
|
|
727
|
+
\u041F\u0440\u0430\u0432\u0438\u043B\u043E \u0438\u043D\u0442\u0435\u0440\u043F\u0440\u0435\u0442\u0430\u0446\u0438\u0438 \u0434\u043B\u044F VolumeDivergence:
|
|
728
|
+
- \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u043E\u0446\u0435\u043D\u0438, \u0435\u0441\u0442\u044C \u043B\u0438 \u0440\u0435\u0430\u043B\u044C\u043D\u044B\u0439 reversal away from pivot, \u0430 \u043D\u0435 \u043F\u0440\u043E\u0441\u0442\u043E \u0444\u0430\u043A\u0442 \u0434\u0438\u0432\u0435\u0440\u0433\u0435\u043D\u0446\u0438\u0438;
|
|
729
|
+
- confirmationReady=false \u043E\u0431\u044B\u0447\u043D\u043E \u043E\u0437\u043D\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043E reversal \u0435\u0449\u0435 \u043D\u0435 fully confirmed;
|
|
730
|
+
- \u0435\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u043D\u0435 \u043E\u0442\u0441\u043A\u043E\u0447\u0438\u043B\u0430 \u043E\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E pivot \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u0441\u0438\u0433\u043D\u0430\u043B\u0430, \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0441\u0435\u0442\u0430\u043F \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C;
|
|
731
|
+
- conflict \u043F\u043E delta/bias \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043D\u0438\u0436\u0430\u0442\u044C quality, \u0430 \u043D\u0435 \u0438\u0433\u043D\u043E\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C\u0441\u044F.
|
|
732
|
+
`;
|
|
733
|
+
},
|
|
734
|
+
mapEntryRuntimeFromConfig: (config) => mapAiRuntimeFromConfig(
|
|
735
|
+
config
|
|
736
|
+
)
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
// src/VolumeDivergence/adapters/ml.ts
|
|
740
|
+
import { mapMlRuntimeFromConfig } from "@tradejs/core/strategies";
|
|
741
|
+
var toVolumeDivergenceMlStrategyConfig = (input) => {
|
|
742
|
+
if (!input) return void 0;
|
|
743
|
+
return {
|
|
744
|
+
...input,
|
|
745
|
+
VOLUME_DIVERGENCE_CONFIG: input.VOLUME_DIVERGENCE_CONFIG ?? {
|
|
746
|
+
normalizationLength: input.NORMALIZATION_LENGTH,
|
|
747
|
+
pivotLookbackLeft: input.PIVOT_LOOKBACK_LEFT,
|
|
748
|
+
pivotLookbackRight: input.PIVOT_LOOKBACK_RIGHT,
|
|
749
|
+
maxBarsBetweenPivots: input.MAX_BARS_BETWEEN_PIVOTS,
|
|
750
|
+
minBarsBetweenPivots: input.MIN_BARS_BETWEEN_PIVOTS,
|
|
751
|
+
bullish: input.BULLISH,
|
|
752
|
+
bearish: input.BEARISH
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
};
|
|
756
|
+
var volumeDivergenceMlAdapter = {
|
|
757
|
+
normalizeStrategyConfig: (strategyConfig) => {
|
|
758
|
+
return toVolumeDivergenceMlStrategyConfig(strategyConfig);
|
|
759
|
+
},
|
|
760
|
+
mapEntryRuntimeFromConfig: (config) => mapMlRuntimeFromConfig(config, {
|
|
761
|
+
strategyConfig: toVolumeDivergenceMlStrategyConfig(
|
|
762
|
+
config
|
|
763
|
+
)
|
|
764
|
+
})
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
// src/VolumeDivergence/hooks.ts
|
|
768
|
+
import { createCloseOppositeBeforePlaceOrderHook } from "@tradejs/node/strategies";
|
|
769
|
+
var volumeDivergenceBeforePlaceOrderHook = createCloseOppositeBeforePlaceOrderHook({
|
|
770
|
+
isEnabled: (config) => Boolean(config.CLOSE_OPPOSITE_POSITIONS)
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
// src/VolumeDivergence/manifest.ts
|
|
774
|
+
var volumeDivergenceManifest = {
|
|
775
|
+
name: "VolumeDivergence",
|
|
776
|
+
hooks: {
|
|
777
|
+
beforePlaceOrder: volumeDivergenceBeforePlaceOrderHook
|
|
778
|
+
},
|
|
779
|
+
aiAdapter: volumeDivergenceAiAdapter,
|
|
780
|
+
mlAdapter: volumeDivergenceMlAdapter
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
export {
|
|
784
|
+
getVolumeDivergenceEntryThresholds,
|
|
785
|
+
buildVolumeDivergenceSetupFeatures,
|
|
786
|
+
volumeDivergenceAiAdapter,
|
|
787
|
+
volumeDivergenceMlAdapter,
|
|
788
|
+
volumeDivergenceManifest
|
|
789
|
+
};
|