@tradejs/strategies 1.0.5 → 1.0.8
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-37ZWRG3W.mjs +128 -0
- package/dist/chunk-H4MHFD4B.mjs +62 -0
- package/dist/chunk-IMLNXICX.mjs +547 -0
- package/dist/chunk-QVWMBYYM.mjs +836 -0
- package/dist/chunk-SOVTOGY4.mjs +163 -0
- package/dist/chunk-TDUTYEGH.mjs +797 -0
- package/dist/chunk-UK4YHD2E.mjs +683 -0
- package/dist/index.d.mts +325 -4
- package/dist/index.d.ts +325 -4
- package/dist/index.js +5689 -1195
- package/dist/index.mjs +61 -14
- package/dist/{strategy-ZVNTA6UR.mjs → strategy-ABIO65CR.mjs} +3 -46
- package/dist/strategy-D7H5J3C4.mjs +348 -0
- package/dist/strategy-HQIPCUOY.mjs +399 -0
- package/dist/{strategy-Y4SOK6FF.mjs → strategy-JQIJILHQ.mjs} +28 -109
- package/dist/strategy-QEIPAPY4.mjs +373 -0
- package/dist/strategy-WYN4FZ5S.mjs +613 -0
- package/dist/{strategy-UHRWSGZC.mjs → strategy-XTUKPYPM.mjs} +484 -128
- package/package.json +5 -5
- package/dist/chunk-3PN7ZSJJ.mjs +0 -27
- package/dist/chunk-MVIV5ZII.mjs +0 -137
- package/dist/chunk-RDK2JK3K.mjs +0 -28
- 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,836 @@
|
|
|
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
|
+
VolumeDivergence addon:
|
|
170
|
+
- This is a reversal setup built on price and normalized-volume divergence, not a breakout strategy.
|
|
171
|
+
- Bullish divergence means price makes a lower low while volume makes a higher low.
|
|
172
|
+
- Bearish divergence means price makes a higher high while volume makes a lower high.
|
|
173
|
+
- For a bullish signal, do not overstate quality if price still failed to bounce meaningfully away from the current pivot low or failed to reclaim enough structure.
|
|
174
|
+
- For a bearish signal, mirror that logic: do not overstate quality if price failed to move down meaningfully away from the current pivot high.
|
|
175
|
+
- If \`payload.additionalIndicators.volumeDivergenceContext.confirmationReady=false\`, this is usually not a fully confirmed reversal yet; quality is often \`<= 4\` and a retest or confirmation is often still needed.
|
|
176
|
+
- For live approval, treat \`confirmationReady\` as much more important than \`structureAdvanced\`; structure advance alone does not mean the reversal is entry-ready.
|
|
177
|
+
- For a reversal setup, do not automatically reward quality just because the coin or BTC MA bias already matches the signal direction.
|
|
178
|
+
- For LONG with \`entryTiming=structure_advance\`, avoid \`quality=5\`; that is an intermediate phase, not a fully confirmed reversal.
|
|
179
|
+
- Be stricter for SHORT than for LONG: a bearish reversal should require cleaner follow-through, and bias or delta conflict should reduce quality more aggressively.
|
|
180
|
+
- If \`deltaAtPivot\` conflicts with the reversal direction or the coin/BTC bias conflicts with the signal, do not overstate quality just because divergence exists.
|
|
181
|
+
- Use \`divergenceAmplitudeAtrRatio\`, \`reclaimPct\`, and \`confirmationCandleQuality\` as explicit setup features describing how meaningful the divergence is relative to ATR, how much structure price reclaimed, and how strong the confirmation candle was.
|
|
182
|
+
- \`confirmationDistancePct\` tells you how far price moved beyond the confirmation level; do not overstate quality when confirmation exists only marginally.
|
|
183
|
+
- \`additionalIndicators.deltaAtPivot\` is a proxy net-volume value on the pivot candle, not true lower-timeframe TradingView volume delta.
|
|
184
|
+
- If \`payload.additionalIndicators.derivativesContext\` exists, use Coinalyze-derived open interest, funding, and liquidations as positioning context: a liquidation flush can strengthen reversal odds, while crowded positioning against the trade or stale or missing data should not mechanically increase quality.
|
|
185
|
+
`;
|
|
186
|
+
var VOLUME_DIVERGENCE_PAYLOAD_PROMPT = `
|
|
187
|
+
- \`payload.additionalIndicators.volumeDivergenceContext\` contains a compact divergence-strength summary:
|
|
188
|
+
divergenceKind / confirmationPrice / confirmationReady / structureAdvanced / reboundFromPivotPct / confirmationDistancePct / priceDisplacementPct / divergenceAmplitudeAtrRatio / reclaimPct / confirmationCandleQuality / volumeDivergenceStrength / deltaAligned / coinBiasAligned / btcBiasAligned / deterministicQuality / approvalAllowedNow / structuralHardBlockReasons / maxAllowedQuality.
|
|
189
|
+
- Use this context as the explicit strategy-specific summary instead of trying to derive the same conclusion again only from generic candles.
|
|
190
|
+
- If \`payload.additionalIndicators.derivativesContext\` exists, it is a Coinalyze-derived summary of derivatives state at signal time; \`stale\` or \`missing_derivatives\` means that Coinalyze context must not be used.
|
|
191
|
+
`;
|
|
192
|
+
var toFiniteNumberOrNull = (value) => {
|
|
193
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
if (typeof value === "string" && value.trim()) {
|
|
197
|
+
const parsed = Number(value);
|
|
198
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
};
|
|
202
|
+
var getLastFiniteNumber = (value) => {
|
|
203
|
+
if (Array.isArray(value)) {
|
|
204
|
+
for (let i = value.length - 1; i >= 0; i -= 1) {
|
|
205
|
+
const item = toFiniteNumberOrNull(value[i]);
|
|
206
|
+
if (item != null) {
|
|
207
|
+
return item;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
return toFiniteNumberOrNull(value);
|
|
213
|
+
};
|
|
214
|
+
var getBias = (fast, slow) => {
|
|
215
|
+
if (fast == null || slow == null) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
if (fast > slow) {
|
|
219
|
+
return "bullish";
|
|
220
|
+
}
|
|
221
|
+
if (fast < slow) {
|
|
222
|
+
return "bearish";
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
};
|
|
226
|
+
var getSignalDirection = (signal) => signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
|
|
227
|
+
var getDivergenceSummary = (signal) => {
|
|
228
|
+
const additional = signal.additionalIndicators;
|
|
229
|
+
const divergence = additional?.divergence;
|
|
230
|
+
return divergence && typeof divergence === "object" ? divergence : {};
|
|
231
|
+
};
|
|
232
|
+
var getVolumeDivergenceSetupSummary = (signal) => {
|
|
233
|
+
const additional = signal.additionalIndicators;
|
|
234
|
+
const setup = additional?.volumeDivergenceSetup;
|
|
235
|
+
return setup && typeof setup === "object" ? setup : {};
|
|
236
|
+
};
|
|
237
|
+
var getVolumeDivergenceThresholdSummary = (signal) => {
|
|
238
|
+
const additional = signal.additionalIndicators;
|
|
239
|
+
const thresholds = additional?.volumeDivergenceThresholds;
|
|
240
|
+
if (!thresholds || typeof thresholds !== "object") {
|
|
241
|
+
return DEFAULT_VOLUME_DIVERGENCE_ENTRY_THRESHOLDS;
|
|
242
|
+
}
|
|
243
|
+
const candidate = thresholds;
|
|
244
|
+
return {
|
|
245
|
+
allowStructureAdvanceEntry: typeof candidate.allowStructureAdvanceEntry === "boolean" ? candidate.allowStructureAdvanceEntry : DEFAULT_VOLUME_DIVERGENCE_ENTRY_THRESHOLDS.allowStructureAdvanceEntry,
|
|
246
|
+
minDivergenceAmplitudeAtrRatio: toFiniteNumberOrNull(candidate.minDivergenceAmplitudeAtrRatio) ?? DEFAULT_VOLUME_DIVERGENCE_ENTRY_THRESHOLDS.minDivergenceAmplitudeAtrRatio,
|
|
247
|
+
minReclaimPct: toFiniteNumberOrNull(candidate.minReclaimPct) ?? DEFAULT_VOLUME_DIVERGENCE_ENTRY_THRESHOLDS.minReclaimPct,
|
|
248
|
+
minConfirmationCandleQuality: toFiniteNumberOrNull(candidate.minConfirmationCandleQuality) ?? DEFAULT_VOLUME_DIVERGENCE_ENTRY_THRESHOLDS.minConfirmationCandleQuality
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
var isAtLeast = (value, threshold) => value != null && value >= threshold;
|
|
252
|
+
var isAtMost = (value, threshold) => value != null && value <= threshold;
|
|
253
|
+
var isInRange = (value, min, max) => value != null && value >= min && value <= max;
|
|
254
|
+
var getConfirmationDistancePct = ({
|
|
255
|
+
signalDirection,
|
|
256
|
+
currentPrice,
|
|
257
|
+
confirmationPrice,
|
|
258
|
+
setupValue
|
|
259
|
+
}) => {
|
|
260
|
+
if (setupValue != null) {
|
|
261
|
+
return setupValue;
|
|
262
|
+
}
|
|
263
|
+
if (signalDirection == null || currentPrice == null || confirmationPrice == null || confirmationPrice <= 0) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
return signalDirection === "LONG" ? (currentPrice - confirmationPrice) / confirmationPrice * 100 : (confirmationPrice - currentPrice) / confirmationPrice * 100;
|
|
267
|
+
};
|
|
268
|
+
var buildHardBlockReasons = ({
|
|
269
|
+
confirmationReady,
|
|
270
|
+
reboundFromPivotPct,
|
|
271
|
+
divergenceAmplitudeAtrRatio,
|
|
272
|
+
reclaimPct,
|
|
273
|
+
confirmationCandleQuality,
|
|
274
|
+
entryThresholds
|
|
275
|
+
}) => {
|
|
276
|
+
const reasons = [];
|
|
277
|
+
if (reboundFromPivotPct != null && reboundFromPivotPct <= 0) {
|
|
278
|
+
reasons.push("no_rebound_from_pivot");
|
|
279
|
+
}
|
|
280
|
+
if (divergenceAmplitudeAtrRatio != null && divergenceAmplitudeAtrRatio < entryThresholds.minDivergenceAmplitudeAtrRatio) {
|
|
281
|
+
reasons.push("weak_divergence_amplitude");
|
|
282
|
+
}
|
|
283
|
+
if (confirmationReady && reclaimPct != null && reclaimPct < entryThresholds.minReclaimPct) {
|
|
284
|
+
reasons.push("weak_reclaim");
|
|
285
|
+
}
|
|
286
|
+
if (confirmationReady && confirmationCandleQuality != null && confirmationCandleQuality < entryThresholds.minConfirmationCandleQuality) {
|
|
287
|
+
reasons.push("weak_confirmation_candle");
|
|
288
|
+
}
|
|
289
|
+
return reasons;
|
|
290
|
+
};
|
|
291
|
+
var getLongQ4Demotion = ({
|
|
292
|
+
divergenceAmplitudeAtrRatio,
|
|
293
|
+
volumeDivergenceRatio,
|
|
294
|
+
coinBiasAligned,
|
|
295
|
+
btcBiasAligned,
|
|
296
|
+
confirmationDistancePct,
|
|
297
|
+
barsSinceDetection,
|
|
298
|
+
atrPct,
|
|
299
|
+
reclaimPct
|
|
300
|
+
}) => {
|
|
301
|
+
const longOverextendedWithoutVolumeSupport = isAtLeast(divergenceAmplitudeAtrRatio, 2.2) && volumeDivergenceRatio != null && volumeDivergenceRatio < 2.2;
|
|
302
|
+
const longBtcLedWithoutCoinSupport = coinBiasAligned === false && btcBiasAligned === true && volumeDivergenceRatio != null && volumeDivergenceRatio < 2.6;
|
|
303
|
+
const longDoubleConflictWithoutMaturity = coinBiasAligned === false && btcBiasAligned === false && (!isAtLeast(confirmationDistancePct, 0.35) || !isAtLeast(barsSinceDetection, 2) || !isAtMost(atrPct, 0.95));
|
|
304
|
+
const longDoubleConflictOverextended = coinBiasAligned === false && btcBiasAligned === false && (!isAtMost(confirmationDistancePct, 1.4) || !isAtMost(divergenceAmplitudeAtrRatio, 2.2) || !isAtMost(atrPct, 1) || !isAtLeast(reclaimPct, 130));
|
|
305
|
+
const longDoubleConflictStaleConfirmation = coinBiasAligned === false && btcBiasAligned === false && isAtLeast(barsSinceDetection, 5);
|
|
306
|
+
const longFullyAlignedLateWeakConfirmation = coinBiasAligned === true && btcBiasAligned === true && isAtLeast(barsSinceDetection, 5) && (!isAtLeast(confirmationDistancePct, 1.2) || !isAtLeast(reclaimPct, 170));
|
|
307
|
+
const longLateExtendedConfirmation = isAtLeast(barsSinceDetection, 6) && isAtLeast(confirmationDistancePct, 1.5) && !isAtMost(atrPct, 1);
|
|
308
|
+
return longOverextendedWithoutVolumeSupport || longBtcLedWithoutCoinSupport || longDoubleConflictWithoutMaturity || longDoubleConflictOverextended || longDoubleConflictStaleConfirmation || longFullyAlignedLateWeakConfirmation || longLateExtendedConfirmation;
|
|
309
|
+
};
|
|
310
|
+
var getLongDeterministicQuality = ({
|
|
311
|
+
confirmationReady,
|
|
312
|
+
structureAdvanced,
|
|
313
|
+
hardBlockReasons,
|
|
314
|
+
divergenceAmplitudeAtrRatio,
|
|
315
|
+
reclaimPct,
|
|
316
|
+
confirmationCandleQuality,
|
|
317
|
+
atrPct,
|
|
318
|
+
confirmationDistancePct,
|
|
319
|
+
reboundFromPivotPct,
|
|
320
|
+
volumeDivergenceStrength,
|
|
321
|
+
volumeDivergenceRatio,
|
|
322
|
+
deltaAligned,
|
|
323
|
+
barsSinceDetection,
|
|
324
|
+
coinBiasAligned,
|
|
325
|
+
btcBiasAligned,
|
|
326
|
+
entryThresholds,
|
|
327
|
+
aiThresholds
|
|
328
|
+
}) => {
|
|
329
|
+
if (hardBlockReasons.length > 0) {
|
|
330
|
+
return 2;
|
|
331
|
+
}
|
|
332
|
+
const reboundModerate = isAtLeast(reboundFromPivotPct, 0.6);
|
|
333
|
+
const reboundStrong = isAtLeast(reboundFromPivotPct, 1.2);
|
|
334
|
+
const reboundVeryStrong = isAtLeast(reboundFromPivotPct, 1.8);
|
|
335
|
+
const confirmationDistanceModerate = isAtLeast(confirmationDistancePct, 0.35);
|
|
336
|
+
const confirmationDistanceStrong = isAtLeast(confirmationDistancePct, 0.7);
|
|
337
|
+
const confirmationDistanceContained = isAtMost(confirmationDistancePct, 1.4);
|
|
338
|
+
const confirmationDistanceBalanced = isInRange(
|
|
339
|
+
confirmationDistancePct,
|
|
340
|
+
0.45,
|
|
341
|
+
1.1
|
|
342
|
+
);
|
|
343
|
+
const maturityReady = isAtLeast(barsSinceDetection, 2);
|
|
344
|
+
const maturityFresh = isInRange(barsSinceDetection, 2, 5);
|
|
345
|
+
const maturityCounterTrend = isInRange(barsSinceDetection, 2, 4);
|
|
346
|
+
const calmAtr = isAtMost(atrPct, 0.95);
|
|
347
|
+
const veryCalmAtr = isAtMost(atrPct, 0.85);
|
|
348
|
+
const volumeModerate = isAtLeast(volumeDivergenceStrength, 5);
|
|
349
|
+
const volumeStrong = isAtLeast(volumeDivergenceStrength, 15);
|
|
350
|
+
const volumeVeryStrong = isAtLeast(volumeDivergenceStrength, 30);
|
|
351
|
+
const volumeRatioModerate = isAtLeast(volumeDivergenceRatio, 1.3);
|
|
352
|
+
const volumeRatioStrong = isAtLeast(volumeDivergenceRatio, 1.7);
|
|
353
|
+
const volumeRatioVeryStrong = isAtLeast(volumeDivergenceRatio, 2.2);
|
|
354
|
+
const longBiasConflictCount = Number(coinBiasAligned === false) + Number(btcBiasAligned === false);
|
|
355
|
+
const longQ4Demotion = getLongQ4Demotion({
|
|
356
|
+
divergenceAmplitudeAtrRatio,
|
|
357
|
+
volumeDivergenceRatio,
|
|
358
|
+
coinBiasAligned,
|
|
359
|
+
btcBiasAligned,
|
|
360
|
+
confirmationDistancePct,
|
|
361
|
+
barsSinceDetection,
|
|
362
|
+
atrPct,
|
|
363
|
+
reclaimPct
|
|
364
|
+
});
|
|
365
|
+
const q4SetupReady = aiThresholds != null && isAtLeast(
|
|
366
|
+
divergenceAmplitudeAtrRatio,
|
|
367
|
+
aiThresholds.q4DivergenceAmplitudeAtrRatio
|
|
368
|
+
) && isAtLeast(reclaimPct, aiThresholds.q4ReclaimPct) && isAtLeast(
|
|
369
|
+
confirmationCandleQuality,
|
|
370
|
+
aiThresholds.q4ConfirmationCandleQuality
|
|
371
|
+
);
|
|
372
|
+
const q5SetupReady = aiThresholds != null && isAtLeast(
|
|
373
|
+
divergenceAmplitudeAtrRatio,
|
|
374
|
+
aiThresholds.q5DivergenceAmplitudeAtrRatio
|
|
375
|
+
) && isAtLeast(reclaimPct, aiThresholds.q5ReclaimPct) && isAtLeast(
|
|
376
|
+
confirmationCandleQuality,
|
|
377
|
+
aiThresholds.q5ConfirmationCandleQuality
|
|
378
|
+
);
|
|
379
|
+
const minimumSetupReady = isAtLeast(
|
|
380
|
+
divergenceAmplitudeAtrRatio,
|
|
381
|
+
entryThresholds.minDivergenceAmplitudeAtrRatio
|
|
382
|
+
) && isAtLeast(reclaimPct, entryThresholds.minReclaimPct) && isAtLeast(
|
|
383
|
+
confirmationCandleQuality,
|
|
384
|
+
entryThresholds.minConfirmationCandleQuality
|
|
385
|
+
);
|
|
386
|
+
const longSelectivePromotion = confirmationReady && minimumSetupReady && reboundStrong && confirmationDistanceBalanced && maturityFresh && calmAtr && volumeStrong && volumeRatioStrong && isAtLeast(reclaimPct, Math.max(entryThresholds.minReclaimPct + 15, 130)) && isAtLeast(
|
|
387
|
+
confirmationCandleQuality,
|
|
388
|
+
Math.max(entryThresholds.minConfirmationCandleQuality + 0.1, 0.7)
|
|
389
|
+
) && deltaAligned !== false && longBiasConflictCount <= 1 && !longQ4Demotion;
|
|
390
|
+
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;
|
|
391
|
+
const longSemiAlignedFnPromotion = confirmationReady && minimumSetupReady && (coinBiasAligned === true || btcBiasAligned === true) && reboundModerate && isAtLeast(reclaimPct, 140) && isAtLeast(confirmationCandleQuality, 0.8) && isAtMost(divergenceAmplitudeAtrRatio, 2.5) && !longQ4Demotion;
|
|
392
|
+
const longCounterTrendSelectivePromotion = confirmationReady && minimumSetupReady && reboundStrong && confirmationDistanceBalanced && maturityCounterTrend && veryCalmAtr && volumeVeryStrong && volumeRatioVeryStrong && isAtLeast(reclaimPct, 130) && isAtLeast(
|
|
393
|
+
confirmationCandleQuality,
|
|
394
|
+
Math.max(entryThresholds.minConfirmationCandleQuality + 0.1, 0.7)
|
|
395
|
+
) && longBiasConflictCount === 2 && !longQ4Demotion;
|
|
396
|
+
if (confirmationReady && q5SetupReady && reboundVeryStrong && confirmationDistanceStrong && confirmationDistanceContained && maturityReady && calmAtr && volumeVeryStrong && volumeRatioVeryStrong && deltaAligned === true && longBiasConflictCount === 0 && !longQ4Demotion) {
|
|
397
|
+
return 5;
|
|
398
|
+
}
|
|
399
|
+
if (longCounterTrendSelectivePromotion || longSemiAlignedFnPromotion || longAlignedFnPromotion || longSelectivePromotion || confirmationReady && q4SetupReady && reboundModerate && confirmationDistanceModerate && confirmationDistanceContained && volumeModerate && volumeRatioModerate && !longQ4Demotion && deltaAligned !== false) {
|
|
400
|
+
return 4;
|
|
401
|
+
}
|
|
402
|
+
if (confirmationReady && minimumSetupReady && reboundModerate) {
|
|
403
|
+
return 3;
|
|
404
|
+
}
|
|
405
|
+
if (structureAdvanced && isAtLeast(reboundFromPivotPct, 0.25)) {
|
|
406
|
+
return 3;
|
|
407
|
+
}
|
|
408
|
+
return 2;
|
|
409
|
+
};
|
|
410
|
+
var getShortDeterministicQuality = ({
|
|
411
|
+
confirmationReady,
|
|
412
|
+
structureAdvanced,
|
|
413
|
+
hardBlockReasons,
|
|
414
|
+
divergenceAmplitudeAtrRatio,
|
|
415
|
+
reclaimPct,
|
|
416
|
+
confirmationCandleQuality,
|
|
417
|
+
reboundFromPivotPct,
|
|
418
|
+
volumeDivergenceStrength,
|
|
419
|
+
deltaAligned,
|
|
420
|
+
coinBiasAligned,
|
|
421
|
+
btcBiasAligned,
|
|
422
|
+
entryThresholds,
|
|
423
|
+
aiThresholds
|
|
424
|
+
}) => {
|
|
425
|
+
if (hardBlockReasons.length > 0) {
|
|
426
|
+
return 2;
|
|
427
|
+
}
|
|
428
|
+
const reboundModerate = isAtLeast(reboundFromPivotPct, 0.6);
|
|
429
|
+
const reboundStrong = isAtLeast(reboundFromPivotPct, 1.2);
|
|
430
|
+
const reboundVeryStrong = isAtLeast(reboundFromPivotPct, 1.8);
|
|
431
|
+
const volumeVeryStrong = isAtLeast(volumeDivergenceStrength, 30);
|
|
432
|
+
const shortBiasConflictCount = Number(coinBiasAligned === false) + Number(btcBiasAligned === false);
|
|
433
|
+
const q4SetupReady = aiThresholds != null && isAtLeast(
|
|
434
|
+
divergenceAmplitudeAtrRatio,
|
|
435
|
+
aiThresholds.q4DivergenceAmplitudeAtrRatio
|
|
436
|
+
) && isAtLeast(reclaimPct, aiThresholds.q4ReclaimPct) && isAtLeast(
|
|
437
|
+
confirmationCandleQuality,
|
|
438
|
+
aiThresholds.q4ConfirmationCandleQuality
|
|
439
|
+
);
|
|
440
|
+
const q5SetupReady = aiThresholds != null && isAtLeast(
|
|
441
|
+
divergenceAmplitudeAtrRatio,
|
|
442
|
+
aiThresholds.q5DivergenceAmplitudeAtrRatio
|
|
443
|
+
) && isAtLeast(reclaimPct, aiThresholds.q5ReclaimPct) && isAtLeast(
|
|
444
|
+
confirmationCandleQuality,
|
|
445
|
+
aiThresholds.q5ConfirmationCandleQuality
|
|
446
|
+
);
|
|
447
|
+
const minimumSetupReady = isAtLeast(
|
|
448
|
+
divergenceAmplitudeAtrRatio,
|
|
449
|
+
entryThresholds.minDivergenceAmplitudeAtrRatio
|
|
450
|
+
) && isAtLeast(reclaimPct, entryThresholds.minReclaimPct) && isAtLeast(
|
|
451
|
+
confirmationCandleQuality,
|
|
452
|
+
entryThresholds.minConfirmationCandleQuality
|
|
453
|
+
);
|
|
454
|
+
if (confirmationReady && q5SetupReady && reboundVeryStrong && volumeVeryStrong && deltaAligned === true && shortBiasConflictCount === 0) {
|
|
455
|
+
return 5;
|
|
456
|
+
}
|
|
457
|
+
if (confirmationReady && q4SetupReady && reboundStrong && volumeVeryStrong && deltaAligned === true && shortBiasConflictCount === 0) {
|
|
458
|
+
return 4;
|
|
459
|
+
}
|
|
460
|
+
if (confirmationReady && minimumSetupReady && reboundModerate && deltaAligned !== false) {
|
|
461
|
+
return 3;
|
|
462
|
+
}
|
|
463
|
+
if (structureAdvanced && isAtLeast(reboundFromPivotPct, 0.25) && deltaAligned !== false) {
|
|
464
|
+
return 3;
|
|
465
|
+
}
|
|
466
|
+
return 2;
|
|
467
|
+
};
|
|
468
|
+
var getVolumeDivergenceContext = (signal) => {
|
|
469
|
+
const signalDirection = getSignalDirection(signal);
|
|
470
|
+
const divergence = getDivergenceSummary(signal);
|
|
471
|
+
const divergenceKind = divergence.kind === "bullish" || divergence.kind === "bearish" ? divergence.kind : null;
|
|
472
|
+
const currentPrice = toFiniteNumberOrNull(signal.prices?.currentPrice);
|
|
473
|
+
const currentPivotLow = toFiniteNumberOrNull(
|
|
474
|
+
divergence.currentPivot?.priceLow
|
|
475
|
+
);
|
|
476
|
+
const currentPivotHigh = toFiniteNumberOrNull(
|
|
477
|
+
divergence.currentPivot?.priceHigh
|
|
478
|
+
);
|
|
479
|
+
const previousPivotLow = toFiniteNumberOrNull(
|
|
480
|
+
divergence.previousPivot?.priceLow
|
|
481
|
+
);
|
|
482
|
+
const previousPivotHigh = toFiniteNumberOrNull(
|
|
483
|
+
divergence.previousPivot?.priceHigh
|
|
484
|
+
);
|
|
485
|
+
const currentVolumeNorm = toFiniteNumberOrNull(
|
|
486
|
+
divergence.currentPivot?.volumeNorm
|
|
487
|
+
);
|
|
488
|
+
const previousVolumeNorm = toFiniteNumberOrNull(
|
|
489
|
+
divergence.previousPivot?.volumeNorm
|
|
490
|
+
);
|
|
491
|
+
const pivotLookbackRight = toFiniteNumberOrNull(
|
|
492
|
+
divergence.pivotLookbackRight
|
|
493
|
+
);
|
|
494
|
+
const barsBetweenPivotConfirmations = toFiniteNumberOrNull(
|
|
495
|
+
divergence.barsBetweenPivotConfirmations
|
|
496
|
+
);
|
|
497
|
+
const timing = signal.additionalIndicators?.volumeDivergenceSignalTiming;
|
|
498
|
+
const entryTiming = timing?.entryTiming === "confirmation_ready" || timing?.entryTiming === "structure_advance" ? timing.entryTiming : null;
|
|
499
|
+
const barsSinceDetection = toFiniteNumberOrNull(timing?.barsSinceDetection);
|
|
500
|
+
const deltaAtPivot = toFiniteNumberOrNull(
|
|
501
|
+
signal.additionalIndicators?.deltaAtPivot
|
|
502
|
+
);
|
|
503
|
+
const setup = getVolumeDivergenceSetupSummary(signal);
|
|
504
|
+
const atrPct = toFiniteNumberOrNull(setup.atrPct);
|
|
505
|
+
const divergenceAmplitudeAtrRatio = toFiniteNumberOrNull(
|
|
506
|
+
setup.divergenceAmplitudeAtrRatio
|
|
507
|
+
);
|
|
508
|
+
const reclaimPct = toFiniteNumberOrNull(setup.reclaimPct);
|
|
509
|
+
const confirmationCandleQuality = toFiniteNumberOrNull(
|
|
510
|
+
setup.confirmationCandleQuality
|
|
511
|
+
);
|
|
512
|
+
const setupConfirmationDistancePct = toFiniteNumberOrNull(
|
|
513
|
+
setup.confirmationDistancePct
|
|
514
|
+
);
|
|
515
|
+
const entryThresholds = getVolumeDivergenceThresholdSummary(signal);
|
|
516
|
+
const coinMaBias = getBias(
|
|
517
|
+
getLastFiniteNumber(signal.indicators?.maFast),
|
|
518
|
+
getLastFiniteNumber(signal.indicators?.maSlow)
|
|
519
|
+
);
|
|
520
|
+
const btcMaBias = getBias(
|
|
521
|
+
getLastFiniteNumber(signal.indicators?.btcMaFast),
|
|
522
|
+
getLastFiniteNumber(signal.indicators?.btcMaSlow)
|
|
523
|
+
);
|
|
524
|
+
const confirmationPrice = divergenceKind === "bullish" ? currentPivotHigh : divergenceKind === "bearish" ? currentPivotLow : null;
|
|
525
|
+
const confirmationReady = divergenceKind === "bullish" ? currentPrice != null && confirmationPrice != null && currentPrice >= confirmationPrice : divergenceKind === "bearish" ? currentPrice != null && confirmationPrice != null && currentPrice <= confirmationPrice : false;
|
|
526
|
+
const confirmationDistancePct = getConfirmationDistancePct({
|
|
527
|
+
signalDirection,
|
|
528
|
+
currentPrice,
|
|
529
|
+
confirmationPrice,
|
|
530
|
+
setupValue: setupConfirmationDistancePct
|
|
531
|
+
});
|
|
532
|
+
const structureAdvanced = divergenceKind === "bullish" ? currentPrice != null && previousPivotLow != null && currentPrice >= previousPivotLow : divergenceKind === "bearish" ? currentPrice != null && previousPivotHigh != null && currentPrice <= previousPivotHigh : false;
|
|
533
|
+
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;
|
|
534
|
+
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;
|
|
535
|
+
const volumeDivergenceStrength = divergenceKind === "bullish" && currentVolumeNorm != null && previousVolumeNorm != null ? currentVolumeNorm - previousVolumeNorm : divergenceKind === "bearish" && currentVolumeNorm != null && previousVolumeNorm != null ? previousVolumeNorm - currentVolumeNorm : null;
|
|
536
|
+
const volumeDivergenceRatio = divergenceKind === "bullish" && currentVolumeNorm != null && previousVolumeNorm != null && previousVolumeNorm > 0 ? currentVolumeNorm / previousVolumeNorm : divergenceKind === "bearish" && currentVolumeNorm != null && previousVolumeNorm != null && currentVolumeNorm > 0 ? previousVolumeNorm / currentVolumeNorm : null;
|
|
537
|
+
const deltaAligned = signalDirection === "LONG" ? deltaAtPivot != null ? deltaAtPivot > 0 : null : signalDirection === "SHORT" ? deltaAtPivot != null ? deltaAtPivot < 0 : null : null;
|
|
538
|
+
const coinBiasAligned = signalDirection === "LONG" ? coinMaBias != null ? coinMaBias === "bullish" : null : signalDirection === "SHORT" ? coinMaBias != null ? coinMaBias === "bearish" : null : null;
|
|
539
|
+
const btcBiasAligned = signalDirection === "LONG" ? btcMaBias != null ? btcMaBias === "bullish" : null : signalDirection === "SHORT" ? btcMaBias != null ? btcMaBias === "bearish" : null : null;
|
|
540
|
+
const aiThresholds = signalDirection != null ? getVolumeDivergenceAiThresholds(signalDirection) : null;
|
|
541
|
+
const hardBlockReasons = buildHardBlockReasons({
|
|
542
|
+
confirmationReady,
|
|
543
|
+
reboundFromPivotPct,
|
|
544
|
+
divergenceAmplitudeAtrRatio,
|
|
545
|
+
reclaimPct,
|
|
546
|
+
confirmationCandleQuality,
|
|
547
|
+
entryThresholds
|
|
548
|
+
});
|
|
549
|
+
const deterministicQuality = signalDirection === "LONG" ? getLongDeterministicQuality({
|
|
550
|
+
confirmationReady,
|
|
551
|
+
structureAdvanced,
|
|
552
|
+
hardBlockReasons,
|
|
553
|
+
divergenceAmplitudeAtrRatio,
|
|
554
|
+
reclaimPct,
|
|
555
|
+
confirmationCandleQuality,
|
|
556
|
+
atrPct,
|
|
557
|
+
confirmationDistancePct,
|
|
558
|
+
reboundFromPivotPct,
|
|
559
|
+
volumeDivergenceStrength,
|
|
560
|
+
volumeDivergenceRatio,
|
|
561
|
+
deltaAligned,
|
|
562
|
+
barsSinceDetection,
|
|
563
|
+
coinBiasAligned,
|
|
564
|
+
btcBiasAligned,
|
|
565
|
+
entryThresholds,
|
|
566
|
+
aiThresholds
|
|
567
|
+
}) : signalDirection === "SHORT" ? getShortDeterministicQuality({
|
|
568
|
+
confirmationReady,
|
|
569
|
+
structureAdvanced,
|
|
570
|
+
hardBlockReasons,
|
|
571
|
+
divergenceAmplitudeAtrRatio,
|
|
572
|
+
reclaimPct,
|
|
573
|
+
confirmationCandleQuality,
|
|
574
|
+
reboundFromPivotPct,
|
|
575
|
+
volumeDivergenceStrength,
|
|
576
|
+
deltaAligned,
|
|
577
|
+
coinBiasAligned,
|
|
578
|
+
btcBiasAligned,
|
|
579
|
+
entryThresholds,
|
|
580
|
+
aiThresholds
|
|
581
|
+
}) : hardBlockReasons.length > 0 ? 2 : 3;
|
|
582
|
+
const approvalAllowedNow = hardBlockReasons.length === 0 && deterministicQuality >= 4 && confirmationReady;
|
|
583
|
+
return {
|
|
584
|
+
signalDirection,
|
|
585
|
+
divergenceKind,
|
|
586
|
+
confirmationPrice,
|
|
587
|
+
confirmationReady,
|
|
588
|
+
structureAdvanced,
|
|
589
|
+
reboundFromPivotPct,
|
|
590
|
+
confirmationDistancePct,
|
|
591
|
+
priceDisplacementPct,
|
|
592
|
+
atrPct,
|
|
593
|
+
divergenceAmplitudeAtrRatio,
|
|
594
|
+
reclaimPct,
|
|
595
|
+
confirmationCandleQuality,
|
|
596
|
+
volumeDivergenceStrength,
|
|
597
|
+
volumeDivergenceRatio,
|
|
598
|
+
deltaAtPivot,
|
|
599
|
+
deltaAligned,
|
|
600
|
+
barsSincePivot: pivotLookbackRight,
|
|
601
|
+
barsBetweenPivotConfirmations,
|
|
602
|
+
entryTiming,
|
|
603
|
+
barsSinceDetection,
|
|
604
|
+
coinMaBias,
|
|
605
|
+
btcMaBias,
|
|
606
|
+
coinBiasAligned,
|
|
607
|
+
btcBiasAligned,
|
|
608
|
+
hardBlockReasons,
|
|
609
|
+
structuralHardBlockReasons: [...hardBlockReasons],
|
|
610
|
+
deterministicQuality,
|
|
611
|
+
approvalAllowedNow,
|
|
612
|
+
maxAllowedQuality: deterministicQuality
|
|
613
|
+
};
|
|
614
|
+
};
|
|
615
|
+
var getVolumeDivergenceContextFromPayload = (payload, signal) => {
|
|
616
|
+
const additional = payload.additionalIndicators;
|
|
617
|
+
const fromPayload = additional?.volumeDivergenceContext;
|
|
618
|
+
if (fromPayload && typeof fromPayload === "object") {
|
|
619
|
+
return fromPayload;
|
|
620
|
+
}
|
|
621
|
+
return getVolumeDivergenceContext(signal);
|
|
622
|
+
};
|
|
623
|
+
var clampQuality = (quality, maxAllowedQuality) => Math.max(1, Math.min(5, Math.min(quality, maxAllowedQuality)));
|
|
624
|
+
var getHardBlockReasonText = (reason) => {
|
|
625
|
+
switch (reason) {
|
|
626
|
+
case "no_rebound_from_pivot":
|
|
627
|
+
return "price failed to move away from the pivot in the reversal direction";
|
|
628
|
+
case "weak_divergence_amplitude":
|
|
629
|
+
return "divergence amplitude is too small relative to ATR";
|
|
630
|
+
case "weak_reclaim":
|
|
631
|
+
return "price reclaimed too little structure after the pivot";
|
|
632
|
+
case "weak_confirmation_candle":
|
|
633
|
+
return "confirmation candle is too weak";
|
|
634
|
+
default:
|
|
635
|
+
return reason;
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
var buildGuardrailReason = (context) => {
|
|
639
|
+
if (context.hardBlockReasons.length > 0) {
|
|
640
|
+
return `VolumeDivergence guardrail: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.`;
|
|
641
|
+
}
|
|
642
|
+
if (!context.confirmationReady && context.entryTiming == null) {
|
|
643
|
+
return "VolumeDivergence guardrail: reversal is visible, but the confirmation level has not been cleared yet.";
|
|
644
|
+
}
|
|
645
|
+
return "VolumeDivergence guardrail: quality is limited by confirmation state and the strength of reversal away from the pivot.";
|
|
646
|
+
};
|
|
647
|
+
var postProcessAnalysis = ({
|
|
648
|
+
signal,
|
|
649
|
+
payload,
|
|
650
|
+
analysis
|
|
651
|
+
}) => {
|
|
652
|
+
const context = getVolumeDivergenceContextFromPayload(payload, signal);
|
|
653
|
+
const signalDirection = getSignalDirection(signal);
|
|
654
|
+
const requestedDirection = analysis.direction === signalDirection ? signalDirection : null;
|
|
655
|
+
const finalDirection = requestedDirection != null && context.approvalAllowedNow ? requestedDirection : null;
|
|
656
|
+
const requestedQuality = typeof analysis.quality === "number" ? analysis.quality : context.maxAllowedQuality;
|
|
657
|
+
const finalQuality = clampQuality(
|
|
658
|
+
requestedQuality,
|
|
659
|
+
context.maxAllowedQuality
|
|
660
|
+
);
|
|
661
|
+
const needRetest = finalDirection == null;
|
|
662
|
+
const retestPrice = needRetest ? context.confirmationPrice : null;
|
|
663
|
+
if (finalDirection == null) {
|
|
664
|
+
return {
|
|
665
|
+
...analysis,
|
|
666
|
+
direction: null,
|
|
667
|
+
quality: finalQuality,
|
|
668
|
+
needRetest,
|
|
669
|
+
retestPrice,
|
|
670
|
+
takeProfitPrice: null,
|
|
671
|
+
stopLossPrice: null,
|
|
672
|
+
qualityReason: analysis.qualityReason || buildGuardrailReason(context),
|
|
673
|
+
triggerInvalidation: analysis.triggerInvalidation || (context.confirmationPrice != null ? `Wait for reversal confirmation relative to level ${context.confirmationPrice}.` : "Wait for a confirmed reversal after the pivot."),
|
|
674
|
+
comment: analysis.comment || (context.hardBlockReasons.length > 0 ? `VolumeDivergence rejected: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "VolumeDivergence remains in watch mode until the reversal is confirmed.")
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
return {
|
|
678
|
+
...analysis,
|
|
679
|
+
direction: finalDirection,
|
|
680
|
+
quality: finalQuality,
|
|
681
|
+
needRetest,
|
|
682
|
+
retestPrice,
|
|
683
|
+
takeProfitPrice: signal.prices?.takeProfitPrice ?? null,
|
|
684
|
+
stopLossPrice: signal.prices?.stopLossPrice ?? null
|
|
685
|
+
};
|
|
686
|
+
};
|
|
687
|
+
var volumeDivergenceAiAdapter = {
|
|
688
|
+
buildPayload: ({ signal, basePayload }) => ({
|
|
689
|
+
...basePayload,
|
|
690
|
+
additionalIndicators: {
|
|
691
|
+
...basePayload.additionalIndicators,
|
|
692
|
+
volumeDivergenceContext: getVolumeDivergenceContext(signal)
|
|
693
|
+
}
|
|
694
|
+
}),
|
|
695
|
+
postProcessAnalysis,
|
|
696
|
+
buildSystemPromptAddon: () => `${VOLUME_DIVERGENCE_CONTEXT_PROMPT}
|
|
697
|
+
${VOLUME_DIVERGENCE_PAYLOAD_PROMPT}`,
|
|
698
|
+
buildHumanPromptAddon: ({ signal, payload }) => {
|
|
699
|
+
const context = getVolumeDivergenceContextFromPayload(payload, signal);
|
|
700
|
+
return `
|
|
701
|
+
|
|
702
|
+
Additional VolumeDivergence context:
|
|
703
|
+
- divergenceKind=${context.divergenceKind ?? "n/a"}
|
|
704
|
+
- confirmationPrice=${context.confirmationPrice ?? "n/a"}
|
|
705
|
+
- confirmationReady=${context.confirmationReady}
|
|
706
|
+
- structureAdvanced=${context.structureAdvanced}
|
|
707
|
+
- reboundFromPivotPct=${context.reboundFromPivotPct?.toFixed?.(3) ?? "n/a"}%
|
|
708
|
+
- atrPct=${context.atrPct?.toFixed?.(3) ?? "n/a"}%
|
|
709
|
+
- divergenceAmplitudeAtrRatio=${context.divergenceAmplitudeAtrRatio?.toFixed?.(3) ?? "n/a"}
|
|
710
|
+
- reclaimPct=${context.reclaimPct?.toFixed?.(3) ?? "n/a"}
|
|
711
|
+
- confirmationCandleQuality=${context.confirmationCandleQuality?.toFixed?.(3) ?? "n/a"}
|
|
712
|
+
- confirmationDistancePct=${context.confirmationDistancePct?.toFixed?.(3) ?? "n/a"}%
|
|
713
|
+
- priceDisplacementPct=${context.priceDisplacementPct?.toFixed?.(3) ?? "n/a"}%
|
|
714
|
+
- volumeDivergenceStrength=${context.volumeDivergenceStrength?.toFixed?.(3) ?? "n/a"}
|
|
715
|
+
- volumeDivergenceRatio=${context.volumeDivergenceRatio?.toFixed?.(3) ?? "n/a"}
|
|
716
|
+
- deltaAligned=${context.deltaAligned}
|
|
717
|
+
- coinBiasAligned=${context.coinBiasAligned}
|
|
718
|
+
- btcBiasAligned=${context.btcBiasAligned}
|
|
719
|
+
- barsSincePivot=${context.barsSincePivot ?? "n/a"}
|
|
720
|
+
- barsBetweenPivotConfirmations=${context.barsBetweenPivotConfirmations ?? "n/a"}
|
|
721
|
+
- entryTiming=${context.entryTiming ?? "n/a"}
|
|
722
|
+
- barsSinceDetection=${context.barsSinceDetection ?? "n/a"}
|
|
723
|
+
- deterministicQuality=${context.deterministicQuality}
|
|
724
|
+
- approvalAllowedNow=${context.approvalAllowedNow}
|
|
725
|
+
- structuralHardBlockReasons=${context.structuralHardBlockReasons.join(", ") || "none"}
|
|
726
|
+
- maxAllowedQuality=${context.maxAllowedQuality}
|
|
727
|
+
- hardBlockReasons=${context.hardBlockReasons.join(", ") || "none"}
|
|
728
|
+
|
|
729
|
+
Interpretation rules for VolumeDivergence:
|
|
730
|
+
- first evaluate whether there is a real reversal away from the pivot, not just divergence on paper;
|
|
731
|
+
- \`confirmationReady=false\` usually means the reversal is not fully confirmed yet;
|
|
732
|
+
- if price did not bounce away from the current pivot in the signal direction, do not treat the setup as confirmed;
|
|
733
|
+
- delta or bias conflict should reduce quality, not be ignored.
|
|
734
|
+
`;
|
|
735
|
+
},
|
|
736
|
+
mapEntryRuntimeFromConfig: (config2) => mapAiRuntimeFromConfig(
|
|
737
|
+
config2
|
|
738
|
+
)
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
// src/VolumeDivergence/adapters/ml.ts
|
|
742
|
+
import { mapMlRuntimeFromConfig } from "@tradejs/core/strategies";
|
|
743
|
+
var toVolumeDivergenceMlStrategyConfig = (input) => {
|
|
744
|
+
if (!input) return void 0;
|
|
745
|
+
return {
|
|
746
|
+
...input,
|
|
747
|
+
VOLUME_DIVERGENCE_CONFIG: input.VOLUME_DIVERGENCE_CONFIG ?? {
|
|
748
|
+
normalizationLength: input.NORMALIZATION_LENGTH,
|
|
749
|
+
pivotLookbackLeft: input.PIVOT_LOOKBACK_LEFT,
|
|
750
|
+
pivotLookbackRight: input.PIVOT_LOOKBACK_RIGHT,
|
|
751
|
+
maxBarsBetweenPivots: input.MAX_BARS_BETWEEN_PIVOTS,
|
|
752
|
+
minBarsBetweenPivots: input.MIN_BARS_BETWEEN_PIVOTS,
|
|
753
|
+
bullish: input.BULLISH,
|
|
754
|
+
bearish: input.BEARISH
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
};
|
|
758
|
+
var volumeDivergenceMlAdapter = {
|
|
759
|
+
normalizeStrategyConfig: (strategyConfig) => {
|
|
760
|
+
return toVolumeDivergenceMlStrategyConfig(strategyConfig);
|
|
761
|
+
},
|
|
762
|
+
mapEntryRuntimeFromConfig: (config2) => mapMlRuntimeFromConfig(config2, {
|
|
763
|
+
strategyConfig: toVolumeDivergenceMlStrategyConfig(
|
|
764
|
+
config2
|
|
765
|
+
)
|
|
766
|
+
})
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
// src/VolumeDivergence/manifest.ts
|
|
770
|
+
var volumeDivergenceManifest = {
|
|
771
|
+
name: "VolumeDivergence",
|
|
772
|
+
aiAdapter: volumeDivergenceAiAdapter,
|
|
773
|
+
mlAdapter: volumeDivergenceMlAdapter
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
// src/VolumeDivergence/config.ts
|
|
777
|
+
var config = {
|
|
778
|
+
ENV: "BACKTEST",
|
|
779
|
+
INTERVAL: "15",
|
|
780
|
+
MAKE_ORDERS: true,
|
|
781
|
+
CLOSE_OPPOSITE_POSITIONS: false,
|
|
782
|
+
BACKTEST_PRICE_MODE: "mid",
|
|
783
|
+
AI_ENABLED: false,
|
|
784
|
+
AI_MODE: "llm",
|
|
785
|
+
ML_ENABLED: false,
|
|
786
|
+
ML_THRESHOLD: 0.1,
|
|
787
|
+
MIN_AI_QUALITY: 3,
|
|
788
|
+
FEE_PERCENT: 5e-3,
|
|
789
|
+
MAX_LOSS_VALUE: 10,
|
|
790
|
+
MA_FAST: 14,
|
|
791
|
+
MA_MEDIUM: 49,
|
|
792
|
+
MA_SLOW: 50,
|
|
793
|
+
OBV_SMA: 10,
|
|
794
|
+
ATR: 14,
|
|
795
|
+
ATR_PCT_SHORT: 7,
|
|
796
|
+
ATR_PCT_LONG: 30,
|
|
797
|
+
BB: 20,
|
|
798
|
+
BB_STD: 2,
|
|
799
|
+
MACD_FAST: 12,
|
|
800
|
+
MACD_SLOW: 26,
|
|
801
|
+
MACD_SIGNAL: 9,
|
|
802
|
+
LEVEL_LOOKBACK: 20,
|
|
803
|
+
LEVEL_DELAY: 2,
|
|
804
|
+
NORMALIZATION_LENGTH: 100,
|
|
805
|
+
PIVOT_LOOKBACK_LEFT: 8,
|
|
806
|
+
PIVOT_LOOKBACK_RIGHT: 3,
|
|
807
|
+
MIN_BARS_BETWEEN_PIVOTS: 4,
|
|
808
|
+
MAX_BARS_BETWEEN_PIVOTS: 36,
|
|
809
|
+
ALLOW_STRUCTURE_ADVANCE_ENTRY: false,
|
|
810
|
+
MIN_DIVERGENCE_AMPLITUDE_ATR_RATIO: 0.35,
|
|
811
|
+
MIN_RECLAIM_PCT: 105,
|
|
812
|
+
MIN_CONFIRMATION_CANDLE_QUALITY: 0.58,
|
|
813
|
+
BULLISH: {
|
|
814
|
+
enable: true,
|
|
815
|
+
direction: "LONG",
|
|
816
|
+
TP: 4,
|
|
817
|
+
SL: 1.3,
|
|
818
|
+
minRiskRatio: 2
|
|
819
|
+
},
|
|
820
|
+
BEARISH: {
|
|
821
|
+
enable: true,
|
|
822
|
+
direction: "SHORT",
|
|
823
|
+
TP: 4,
|
|
824
|
+
SL: 1.3,
|
|
825
|
+
minRiskRatio: 2
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
export {
|
|
830
|
+
getVolumeDivergenceEntryThresholds,
|
|
831
|
+
buildVolumeDivergenceSetupFeatures,
|
|
832
|
+
volumeDivergenceAiAdapter,
|
|
833
|
+
volumeDivergenceMlAdapter,
|
|
834
|
+
volumeDivergenceManifest,
|
|
835
|
+
config
|
|
836
|
+
};
|