@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.
@@ -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
+ };