@tradejs/strategy-double-tap 3.0.0
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/LICENSE +96 -0
- package/README.md +34 -0
- package/dist/index.cjs +1677 -0
- package/dist/index.d.cts +90 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +1653 -0
- package/package.json +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1653 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { defineStrategyPlugin } from "@tradejs/core/config";
|
|
3
|
+
|
|
4
|
+
// src/DoubleTap/config.ts
|
|
5
|
+
import { FEE_PERCENT } from "@tradejs/core/constants";
|
|
6
|
+
var config = {
|
|
7
|
+
ENV: "BACKTEST",
|
|
8
|
+
INTERVAL: "15",
|
|
9
|
+
MAKE_ORDERS: true,
|
|
10
|
+
CLOSE_OPPOSITE_POSITIONS: false,
|
|
11
|
+
BACKTEST_PRICE_MODE: "open",
|
|
12
|
+
AI_ENABLED: false,
|
|
13
|
+
AI_MODE: "llm",
|
|
14
|
+
ML_ENABLED: false,
|
|
15
|
+
ML_THRESHOLD: 0.1,
|
|
16
|
+
MIN_AI_QUALITY: 4,
|
|
17
|
+
FEE_PERCENT,
|
|
18
|
+
MAX_LOSS_VALUE: 10,
|
|
19
|
+
MA_FAST: 14,
|
|
20
|
+
MA_MEDIUM: 49,
|
|
21
|
+
MA_SLOW: 50,
|
|
22
|
+
OBV_SMA: 10,
|
|
23
|
+
ATR: 14,
|
|
24
|
+
ATR_PCT_SHORT: 7,
|
|
25
|
+
ATR_PCT_LONG: 30,
|
|
26
|
+
BB: 20,
|
|
27
|
+
BB_STD: 2,
|
|
28
|
+
MACD_FAST: 12,
|
|
29
|
+
MACD_SLOW: 26,
|
|
30
|
+
MACD_SIGNAL: 9,
|
|
31
|
+
DOUBLETAP_PIVOT_LENGTH: 35,
|
|
32
|
+
DOUBLETAP_PIVOT_TOLERANCE_PCT: 12,
|
|
33
|
+
DOUBLETAP_TARGET_FIB_PCT: 180,
|
|
34
|
+
DOUBLETAP_STOP_FIB_PCT: 0,
|
|
35
|
+
DOUBLETAP_MIN_PATTERN_HEIGHT_PCT: 0.2,
|
|
36
|
+
DOUBLETAP_MIN_PATTERN_HEIGHT_ATR: 1,
|
|
37
|
+
DOUBLETAP_ATR_PERIOD: 14,
|
|
38
|
+
DOUBLETAP_MIN_TAP_SPACING_BARS: 3,
|
|
39
|
+
DOUBLETAP_MAX_PATTERN_AGE_BARS: 180,
|
|
40
|
+
DOUBLETAP_MIN_LEG_SYMMETRY_RATIO: 0.25,
|
|
41
|
+
DOUBLETAP_MIN_BREAKOUT_DISTANCE_ATR: 0.05,
|
|
42
|
+
DOUBLETAP_MAX_BREAKOUT_DISTANCE_HEIGHT_RATIO: 0.8,
|
|
43
|
+
DOUBLETAP_MAX_BREAKOUT_DISTANCE_PCT: 0.8,
|
|
44
|
+
DOUBLETAP_ENTRY_MODE: "close_acceptance",
|
|
45
|
+
DOUBLETAP_CONFIRMATION_MAX_BARS: 2,
|
|
46
|
+
DOUBLETAP_MAX_ENTRY_CONFIRMATION_BARS: 0,
|
|
47
|
+
DOUBLETAP_MAX_ENTRY_CONFIRMATION_BARS_LONG: 1,
|
|
48
|
+
DOUBLETAP_MAX_ENTRY_CONFIRMATION_BARS_SHORT: 0,
|
|
49
|
+
DOUBLETAP_MAX_BB_WIDTH_PCT: 0,
|
|
50
|
+
DOUBLETAP_MAX_BB_WIDTH_PCT_LONG: 0,
|
|
51
|
+
DOUBLETAP_MAX_BB_WIDTH_PCT_SHORT: 6,
|
|
52
|
+
DOUBLETAP_RETEST_MAX_BARS: 4,
|
|
53
|
+
DOUBLETAP_RETEST_TOLERANCE_ATR: 0.25,
|
|
54
|
+
DOUBLETAP_EXIT_ON_OPPOSITE_PATTERN: true,
|
|
55
|
+
LONG: {
|
|
56
|
+
enable: true,
|
|
57
|
+
direction: "LONG",
|
|
58
|
+
minRiskRatio: 0.7
|
|
59
|
+
},
|
|
60
|
+
SHORT: {
|
|
61
|
+
enable: true,
|
|
62
|
+
direction: "SHORT",
|
|
63
|
+
minRiskRatio: 0.7
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/DoubleTap/core.ts
|
|
68
|
+
import { round } from "@tradejs/core/math";
|
|
69
|
+
|
|
70
|
+
// src/DoubleTap/engine.ts
|
|
71
|
+
var asNumber = (value) => {
|
|
72
|
+
const numeric = Number(value);
|
|
73
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
74
|
+
};
|
|
75
|
+
var clampPositive = (value, fallback) => Number.isFinite(value) && value > 0 ? value : fallback;
|
|
76
|
+
var getConfigNumbers = (config2) => ({
|
|
77
|
+
pivotLength: Math.max(1, Math.floor(config2.DOUBLETAP_PIVOT_LENGTH ?? 50)),
|
|
78
|
+
tolerancePct: clampPositive(config2.DOUBLETAP_PIVOT_TOLERANCE_PCT, 15),
|
|
79
|
+
targetFibPct: Math.max(0, Number(config2.DOUBLETAP_TARGET_FIB_PCT ?? 100)),
|
|
80
|
+
stopFibPct: Number(config2.DOUBLETAP_STOP_FIB_PCT ?? 0),
|
|
81
|
+
minPatternHeightPct: Math.max(
|
|
82
|
+
0,
|
|
83
|
+
Number(config2.DOUBLETAP_MIN_PATTERN_HEIGHT_PCT ?? 0)
|
|
84
|
+
),
|
|
85
|
+
minPatternHeightAtr: Math.max(
|
|
86
|
+
0,
|
|
87
|
+
Number(config2.DOUBLETAP_MIN_PATTERN_HEIGHT_ATR ?? 0)
|
|
88
|
+
),
|
|
89
|
+
atrPeriod: Math.max(2, Math.floor(config2.DOUBLETAP_ATR_PERIOD ?? 14)),
|
|
90
|
+
minTapSpacingBars: Math.max(
|
|
91
|
+
1,
|
|
92
|
+
Math.floor(config2.DOUBLETAP_MIN_TAP_SPACING_BARS ?? 1)
|
|
93
|
+
),
|
|
94
|
+
maxPatternAgeBars: Math.max(
|
|
95
|
+
1,
|
|
96
|
+
Math.floor(config2.DOUBLETAP_MAX_PATTERN_AGE_BARS ?? 180)
|
|
97
|
+
),
|
|
98
|
+
minLegSymmetryRatio: Math.min(
|
|
99
|
+
1,
|
|
100
|
+
Math.max(0, Number(config2.DOUBLETAP_MIN_LEG_SYMMETRY_RATIO ?? 0))
|
|
101
|
+
),
|
|
102
|
+
minBreakoutDistanceAtr: Math.max(
|
|
103
|
+
0,
|
|
104
|
+
Number(config2.DOUBLETAP_MIN_BREAKOUT_DISTANCE_ATR ?? 0)
|
|
105
|
+
),
|
|
106
|
+
maxBreakoutDistanceHeightRatio: Math.max(
|
|
107
|
+
0,
|
|
108
|
+
Number(config2.DOUBLETAP_MAX_BREAKOUT_DISTANCE_HEIGHT_RATIO ?? 0)
|
|
109
|
+
),
|
|
110
|
+
maxBreakoutDistancePct: Math.max(
|
|
111
|
+
0,
|
|
112
|
+
Number(config2.DOUBLETAP_MAX_BREAKOUT_DISTANCE_PCT ?? 0)
|
|
113
|
+
),
|
|
114
|
+
entryMode: config2.DOUBLETAP_ENTRY_MODE ?? "close_acceptance",
|
|
115
|
+
confirmationMaxBars: Math.max(
|
|
116
|
+
1,
|
|
117
|
+
Math.floor(config2.DOUBLETAP_CONFIRMATION_MAX_BARS ?? 2)
|
|
118
|
+
),
|
|
119
|
+
retestMaxBars: Math.max(1, Math.floor(config2.DOUBLETAP_RETEST_MAX_BARS ?? 4)),
|
|
120
|
+
retestToleranceAtr: Math.max(
|
|
121
|
+
0,
|
|
122
|
+
Number(config2.DOUBLETAP_RETEST_TOLERANCE_ATR ?? 0.25)
|
|
123
|
+
)
|
|
124
|
+
});
|
|
125
|
+
var highest = (candles) => candles.reduce(
|
|
126
|
+
(max, candle) => Math.max(max, Number(candle.high)),
|
|
127
|
+
-Infinity
|
|
128
|
+
);
|
|
129
|
+
var lowest = (candles) => candles.reduce((min, candle) => Math.min(min, Number(candle.low)), Infinity);
|
|
130
|
+
var calculateAtr = (candles, period) => {
|
|
131
|
+
const relevant = candles.slice(-(period + 1));
|
|
132
|
+
if (relevant.length < 2) return null;
|
|
133
|
+
const trueRanges = [];
|
|
134
|
+
for (let index = 1; index < relevant.length; index += 1) {
|
|
135
|
+
const candle = relevant[index];
|
|
136
|
+
const previous = relevant[index - 1];
|
|
137
|
+
const high = asNumber(candle?.high);
|
|
138
|
+
const low = asNumber(candle?.low);
|
|
139
|
+
const previousClose = asNumber(previous?.close);
|
|
140
|
+
if (high == null || low == null || previousClose == null) continue;
|
|
141
|
+
trueRanges.push(
|
|
142
|
+
Math.max(
|
|
143
|
+
high - low,
|
|
144
|
+
Math.abs(high - previousClose),
|
|
145
|
+
Math.abs(low - previousClose)
|
|
146
|
+
)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (trueRanges.length === 0) return null;
|
|
150
|
+
return trueRanges.reduce((sum, value) => sum + value, 0) / trueRanges.length;
|
|
151
|
+
};
|
|
152
|
+
var pushBoundedCandle = (state, candle, maxCandles) => {
|
|
153
|
+
state.currentIndex += 1;
|
|
154
|
+
state.candles.push(candle);
|
|
155
|
+
if (state.candles.length > maxCandles) {
|
|
156
|
+
state.candles.splice(0, state.candles.length - maxCandles);
|
|
157
|
+
}
|
|
158
|
+
return state.currentIndex;
|
|
159
|
+
};
|
|
160
|
+
var pushPivot = ({
|
|
161
|
+
pivots,
|
|
162
|
+
candle,
|
|
163
|
+
index,
|
|
164
|
+
kind,
|
|
165
|
+
value
|
|
166
|
+
}) => {
|
|
167
|
+
pivots.push({
|
|
168
|
+
timestamp: candle.timestamp,
|
|
169
|
+
index,
|
|
170
|
+
value,
|
|
171
|
+
kind,
|
|
172
|
+
traded: false
|
|
173
|
+
});
|
|
174
|
+
if (pivots.length > 12) pivots.shift();
|
|
175
|
+
};
|
|
176
|
+
var updateLatestPivot = ({
|
|
177
|
+
pivots,
|
|
178
|
+
candle,
|
|
179
|
+
index,
|
|
180
|
+
kind,
|
|
181
|
+
value
|
|
182
|
+
}) => {
|
|
183
|
+
const latest = pivots[pivots.length - 1];
|
|
184
|
+
if (!latest || latest.kind !== kind) return;
|
|
185
|
+
const isMoreExtreme = kind === "high" ? value > latest.value : value < latest.value;
|
|
186
|
+
if (!isMoreExtreme) return;
|
|
187
|
+
latest.timestamp = candle.timestamp;
|
|
188
|
+
latest.index = index;
|
|
189
|
+
latest.value = value;
|
|
190
|
+
};
|
|
191
|
+
var markTerminal = (state, setup) => {
|
|
192
|
+
state.consumedSetupIds.add(setup.setupId);
|
|
193
|
+
const terminalPivot = state.pivots.find(
|
|
194
|
+
(pivot) => pivot.timestamp === setup.pattern.pivots[3].timestamp
|
|
195
|
+
);
|
|
196
|
+
if (terminalPivot) terminalPivot.traded = true;
|
|
197
|
+
};
|
|
198
|
+
var buildBreakoutPattern = ({
|
|
199
|
+
state,
|
|
200
|
+
candle,
|
|
201
|
+
prevClose,
|
|
202
|
+
atr,
|
|
203
|
+
multiplier,
|
|
204
|
+
tolerancePct,
|
|
205
|
+
targetFibPct,
|
|
206
|
+
stopFibPct,
|
|
207
|
+
minPatternHeightPct,
|
|
208
|
+
minPatternHeightAtr,
|
|
209
|
+
minTapSpacingBars,
|
|
210
|
+
maxPatternAgeBars,
|
|
211
|
+
minLegSymmetryRatio,
|
|
212
|
+
minBreakoutDistanceAtr,
|
|
213
|
+
maxBreakoutDistanceHeightRatio,
|
|
214
|
+
maxBreakoutDistancePct,
|
|
215
|
+
entryMode
|
|
216
|
+
}) => {
|
|
217
|
+
const { pivots } = state;
|
|
218
|
+
if (pivots.length < 5 || prevClose == null) return null;
|
|
219
|
+
const rows = pivots.length;
|
|
220
|
+
const p1 = pivots[rows - 5];
|
|
221
|
+
const p2 = pivots[rows - 4];
|
|
222
|
+
const p3 = pivots[rows - 3];
|
|
223
|
+
const p4 = pivots[rows - 2];
|
|
224
|
+
if (!p1 || !p2 || !p3 || !p4 || p4.traded) return null;
|
|
225
|
+
const close = asNumber(candle.close);
|
|
226
|
+
if (close == null) return null;
|
|
227
|
+
const signedHeight = (p2.value + p4.value) / 2 - p3.value;
|
|
228
|
+
const height = Math.abs(signedHeight);
|
|
229
|
+
if (height <= 0) return null;
|
|
230
|
+
const heightPct = p3.value !== 0 ? height / Math.abs(p3.value) * 100 : 0;
|
|
231
|
+
const patternHeightAtr = atr != null && atr > 0 ? height / atr : 0;
|
|
232
|
+
if (heightPct < minPatternHeightPct || patternHeightAtr < minPatternHeightAtr) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
const upper = p2.value + signedHeight * (tolerancePct / 100);
|
|
236
|
+
const lower = p2.value - signedHeight * (tolerancePct / 100);
|
|
237
|
+
const crossedNeckline = close * multiplier < p3.value * multiplier && !(prevClose * multiplier < p3.value * multiplier);
|
|
238
|
+
const pivotsAligned = p1.value * multiplier < p3.value * multiplier && p4.value * multiplier <= upper * multiplier && p4.value * multiplier >= lower * multiplier;
|
|
239
|
+
if (!crossedNeckline || !pivotsAligned) return null;
|
|
240
|
+
const tapSpacingBars = p4.index - p2.index;
|
|
241
|
+
const firstLegBars = p3.index - p2.index;
|
|
242
|
+
const secondLegBars = p4.index - p3.index;
|
|
243
|
+
const legDurationSymmetryRatio = Math.max(firstLegBars, secondLegBars) > 0 ? Math.min(firstLegBars, secondLegBars) / Math.max(firstLegBars, secondLegBars) : 0;
|
|
244
|
+
const patternAgeBars = state.currentIndex - p2.index;
|
|
245
|
+
if (tapSpacingBars < minTapSpacingBars || patternAgeBars > maxPatternAgeBars || legDurationSymmetryRatio < minLegSymmetryRatio) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const breakoutDistance = Math.abs(close - p3.value);
|
|
249
|
+
const breakoutDistancePct = p3.value !== 0 ? breakoutDistance / Math.abs(p3.value) * 100 : 0;
|
|
250
|
+
const breakoutDistanceAtr = atr != null && atr > 0 ? breakoutDistance / atr : 0;
|
|
251
|
+
const breakoutDistanceHeightRatio = breakoutDistance / height;
|
|
252
|
+
if (breakoutDistanceAtr < minBreakoutDistanceAtr || maxBreakoutDistanceHeightRatio > 0 && breakoutDistanceHeightRatio > maxBreakoutDistanceHeightRatio || maxBreakoutDistancePct > 0 && breakoutDistancePct > maxBreakoutDistancePct) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
const kind = multiplier === -1 ? "double_bottom" : "double_top";
|
|
256
|
+
const setupId = `${kind}:${p2.timestamp}:${p3.timestamp}:${p4.timestamp}`;
|
|
257
|
+
if (state.consumedSetupIds.has(setupId)) return null;
|
|
258
|
+
const lowerInvalidation = Math.min(p2.value, p4.value);
|
|
259
|
+
const upperInvalidation = Math.max(p2.value, p4.value);
|
|
260
|
+
const stopLossPrice = multiplier === -1 ? lowerInvalidation + height * (stopFibPct / 100) : upperInvalidation - height * (stopFibPct / 100);
|
|
261
|
+
return {
|
|
262
|
+
setupId,
|
|
263
|
+
kind,
|
|
264
|
+
direction: multiplier === -1 ? "LONG" : "SHORT",
|
|
265
|
+
entryMode,
|
|
266
|
+
entryStage: "breakout",
|
|
267
|
+
pivots: [p1, p2, p3, p4],
|
|
268
|
+
neckline: p3.value,
|
|
269
|
+
targetPrice: p3.value - signedHeight * (targetFibPct / 100),
|
|
270
|
+
stopLossPrice,
|
|
271
|
+
height,
|
|
272
|
+
patternHeightAtr,
|
|
273
|
+
patternAgeBars,
|
|
274
|
+
tapSpacingBars,
|
|
275
|
+
legDurationSymmetryRatio,
|
|
276
|
+
pivotTolerancePct: tolerancePct,
|
|
277
|
+
breakoutDistancePct,
|
|
278
|
+
breakoutDistanceAtr,
|
|
279
|
+
breakoutDistanceHeightRatio,
|
|
280
|
+
breakoutTimestamp: candle.timestamp,
|
|
281
|
+
confirmationBars: 0,
|
|
282
|
+
timestamp: candle.timestamp,
|
|
283
|
+
close
|
|
284
|
+
};
|
|
285
|
+
};
|
|
286
|
+
var isBeyondNeckline = (direction, close, neckline, minimumDistance) => direction === "LONG" ? close >= neckline + minimumDistance : close <= neckline - minimumDistance;
|
|
287
|
+
var resolvePending = ({
|
|
288
|
+
state,
|
|
289
|
+
candle,
|
|
290
|
+
atr,
|
|
291
|
+
confirmationMaxBars,
|
|
292
|
+
retestMaxBars,
|
|
293
|
+
retestToleranceAtr,
|
|
294
|
+
minBreakoutDistanceAtr
|
|
295
|
+
}) => {
|
|
296
|
+
const pending = state.pending;
|
|
297
|
+
if (!pending) return null;
|
|
298
|
+
const confirmationBars = state.currentIndex - pending.breakoutIndex;
|
|
299
|
+
if (confirmationBars < 1) return null;
|
|
300
|
+
const close = asNumber(candle.close);
|
|
301
|
+
const high = asNumber(candle.high);
|
|
302
|
+
const low = asNumber(candle.low);
|
|
303
|
+
if (close == null || high == null || low == null) return null;
|
|
304
|
+
const pattern = pending.pattern;
|
|
305
|
+
const invalidated = pattern.direction === "LONG" ? low <= pattern.stopLossPrice : high >= pattern.stopLossPrice;
|
|
306
|
+
const maxBars = pending.mode === "retest" ? retestMaxBars : confirmationMaxBars;
|
|
307
|
+
if (invalidated || confirmationBars > maxBars) {
|
|
308
|
+
markTerminal(state, pending);
|
|
309
|
+
state.pending = null;
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
const effectiveAtr = atr != null && atr > 0 ? atr : pattern.height;
|
|
313
|
+
const minimumDistance = effectiveAtr * minBreakoutDistanceAtr;
|
|
314
|
+
const closeAccepted = isBeyondNeckline(
|
|
315
|
+
pattern.direction,
|
|
316
|
+
close,
|
|
317
|
+
pattern.neckline,
|
|
318
|
+
minimumDistance
|
|
319
|
+
);
|
|
320
|
+
let entryStage = null;
|
|
321
|
+
if (pending.mode === "close_acceptance") {
|
|
322
|
+
if (closeAccepted) entryStage = "close_accepted";
|
|
323
|
+
} else {
|
|
324
|
+
const tolerance = effectiveAtr * retestToleranceAtr;
|
|
325
|
+
const touched = pattern.direction === "LONG" ? low <= pattern.neckline + tolerance && low >= pattern.neckline - tolerance : high >= pattern.neckline - tolerance && high <= pattern.neckline + tolerance;
|
|
326
|
+
if (touched && closeAccepted) entryStage = "retest_held";
|
|
327
|
+
}
|
|
328
|
+
if (!entryStage) return null;
|
|
329
|
+
markTerminal(state, pending);
|
|
330
|
+
state.pending = null;
|
|
331
|
+
return {
|
|
332
|
+
...pattern,
|
|
333
|
+
entryStage,
|
|
334
|
+
confirmationBars,
|
|
335
|
+
timestamp: candle.timestamp,
|
|
336
|
+
close
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
var clonePending = (pending) => pending ? {
|
|
340
|
+
...pending,
|
|
341
|
+
pattern: { ...pending.pattern, pivots: [...pending.pattern.pivots] }
|
|
342
|
+
} : null;
|
|
343
|
+
var buildDoubleTapSignalContext = (pattern) => ({
|
|
344
|
+
setupId: pattern.setupId,
|
|
345
|
+
patternKind: pattern.kind,
|
|
346
|
+
signalDirection: pattern.direction,
|
|
347
|
+
entryMode: pattern.entryMode,
|
|
348
|
+
entryStage: pattern.entryStage,
|
|
349
|
+
neckline: pattern.neckline,
|
|
350
|
+
targetPrice: pattern.targetPrice,
|
|
351
|
+
stopLossPrice: pattern.stopLossPrice,
|
|
352
|
+
height: pattern.height,
|
|
353
|
+
patternHeightAtr: pattern.patternHeightAtr,
|
|
354
|
+
patternAgeBars: pattern.patternAgeBars,
|
|
355
|
+
tapSpacingBars: pattern.tapSpacingBars,
|
|
356
|
+
legDurationSymmetryRatio: pattern.legDurationSymmetryRatio,
|
|
357
|
+
pivotTolerancePct: pattern.pivotTolerancePct,
|
|
358
|
+
breakoutDistancePct: pattern.breakoutDistancePct,
|
|
359
|
+
breakoutDistanceAtr: pattern.breakoutDistanceAtr,
|
|
360
|
+
breakoutDistanceHeightRatio: pattern.breakoutDistanceHeightRatio,
|
|
361
|
+
breakoutTimestamp: pattern.breakoutTimestamp,
|
|
362
|
+
confirmationBars: pattern.confirmationBars,
|
|
363
|
+
currentPrice: pattern.close,
|
|
364
|
+
pivots: pattern.pivots.map(({ timestamp, value, kind }) => ({
|
|
365
|
+
timestamp,
|
|
366
|
+
value,
|
|
367
|
+
kind
|
|
368
|
+
}))
|
|
369
|
+
});
|
|
370
|
+
var createDoubleTapEngine = ({
|
|
371
|
+
config: config2,
|
|
372
|
+
initialCandles = []
|
|
373
|
+
}) => {
|
|
374
|
+
const options = getConfigNumbers(config2);
|
|
375
|
+
const state = {
|
|
376
|
+
candles: [],
|
|
377
|
+
currentIndex: -1,
|
|
378
|
+
pivots: [],
|
|
379
|
+
dir: null,
|
|
380
|
+
pattern: null,
|
|
381
|
+
pending: null,
|
|
382
|
+
consumedSetupIds: /* @__PURE__ */ new Set(),
|
|
383
|
+
lastTimestamp: null
|
|
384
|
+
};
|
|
385
|
+
const snapshot = () => ({
|
|
386
|
+
pattern: state.pattern ? { ...state.pattern, pivots: [...state.pattern.pivots] } : null,
|
|
387
|
+
pending: clonePending(state.pending),
|
|
388
|
+
pivots: state.pivots.map((pivot) => ({ ...pivot }))
|
|
389
|
+
});
|
|
390
|
+
const apply = (candle) => {
|
|
391
|
+
if (state.lastTimestamp === candle.timestamp) return snapshot();
|
|
392
|
+
state.lastTimestamp = candle.timestamp;
|
|
393
|
+
state.pattern = null;
|
|
394
|
+
const previous = state.candles[state.candles.length - 1];
|
|
395
|
+
const prevClose = previous ? asNumber(previous.close) : null;
|
|
396
|
+
const maxCandles = Math.max(options.pivotLength + 1, options.atrPeriod + 1);
|
|
397
|
+
const currentIndex = pushBoundedCandle(state, candle, maxCandles);
|
|
398
|
+
const atr = calculateAtr(state.candles, options.atrPeriod);
|
|
399
|
+
const pendingPattern = resolvePending({
|
|
400
|
+
state,
|
|
401
|
+
candle,
|
|
402
|
+
atr,
|
|
403
|
+
confirmationMaxBars: options.confirmationMaxBars,
|
|
404
|
+
retestMaxBars: options.retestMaxBars,
|
|
405
|
+
retestToleranceAtr: options.retestToleranceAtr,
|
|
406
|
+
minBreakoutDistanceAtr: options.minBreakoutDistanceAtr
|
|
407
|
+
});
|
|
408
|
+
const window = state.candles.slice(-options.pivotLength);
|
|
409
|
+
const high = asNumber(candle.high);
|
|
410
|
+
const low = asNumber(candle.low);
|
|
411
|
+
const currentIsHigh = high != null && high >= highest(window);
|
|
412
|
+
const currentIsLow = low != null && low <= lowest(window);
|
|
413
|
+
const nextDir = currentIsHigh ? 1 : currentIsLow ? 0 : state.dir;
|
|
414
|
+
const dirChanged = state.dir != null && nextDir !== state.dir;
|
|
415
|
+
if (dirChanged && nextDir === 1 && high != null) {
|
|
416
|
+
pushPivot({
|
|
417
|
+
pivots: state.pivots,
|
|
418
|
+
candle,
|
|
419
|
+
index: currentIndex,
|
|
420
|
+
kind: "high",
|
|
421
|
+
value: high
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (dirChanged && nextDir === 0 && low != null) {
|
|
425
|
+
pushPivot({
|
|
426
|
+
pivots: state.pivots,
|
|
427
|
+
candle,
|
|
428
|
+
index: currentIndex,
|
|
429
|
+
kind: "low",
|
|
430
|
+
value: low
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
if (!dirChanged && nextDir === 1 && high != null) {
|
|
434
|
+
updateLatestPivot({
|
|
435
|
+
pivots: state.pivots,
|
|
436
|
+
candle,
|
|
437
|
+
index: currentIndex,
|
|
438
|
+
kind: "high",
|
|
439
|
+
value: high
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
if (!dirChanged && nextDir === 0 && low != null) {
|
|
443
|
+
updateLatestPivot({
|
|
444
|
+
pivots: state.pivots,
|
|
445
|
+
candle,
|
|
446
|
+
index: currentIndex,
|
|
447
|
+
kind: "low",
|
|
448
|
+
value: low
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
state.dir = nextDir;
|
|
452
|
+
if (pendingPattern) {
|
|
453
|
+
state.pattern = pendingPattern;
|
|
454
|
+
return snapshot();
|
|
455
|
+
}
|
|
456
|
+
if (state.pending) return snapshot();
|
|
457
|
+
const common = {
|
|
458
|
+
state,
|
|
459
|
+
candle,
|
|
460
|
+
prevClose,
|
|
461
|
+
atr,
|
|
462
|
+
tolerancePct: options.tolerancePct,
|
|
463
|
+
targetFibPct: options.targetFibPct,
|
|
464
|
+
stopFibPct: options.stopFibPct,
|
|
465
|
+
minPatternHeightPct: options.minPatternHeightPct,
|
|
466
|
+
minPatternHeightAtr: options.minPatternHeightAtr,
|
|
467
|
+
minTapSpacingBars: options.minTapSpacingBars,
|
|
468
|
+
maxPatternAgeBars: options.maxPatternAgeBars,
|
|
469
|
+
minLegSymmetryRatio: options.minLegSymmetryRatio,
|
|
470
|
+
minBreakoutDistanceAtr: options.minBreakoutDistanceAtr,
|
|
471
|
+
maxBreakoutDistanceHeightRatio: options.maxBreakoutDistanceHeightRatio,
|
|
472
|
+
maxBreakoutDistancePct: options.maxBreakoutDistancePct,
|
|
473
|
+
entryMode: options.entryMode
|
|
474
|
+
};
|
|
475
|
+
const breakout = buildBreakoutPattern({ ...common, multiplier: 1 }) ?? buildBreakoutPattern({ ...common, multiplier: -1 });
|
|
476
|
+
if (!breakout) return snapshot();
|
|
477
|
+
if (options.entryMode === "breakout") {
|
|
478
|
+
const terminal = {
|
|
479
|
+
setupId: breakout.setupId,
|
|
480
|
+
mode: "close_acceptance",
|
|
481
|
+
stage: "neckline_crossed",
|
|
482
|
+
breakoutIndex: currentIndex,
|
|
483
|
+
pattern: breakout
|
|
484
|
+
};
|
|
485
|
+
markTerminal(state, terminal);
|
|
486
|
+
state.pattern = breakout;
|
|
487
|
+
return snapshot();
|
|
488
|
+
}
|
|
489
|
+
state.pending = {
|
|
490
|
+
setupId: breakout.setupId,
|
|
491
|
+
mode: options.entryMode,
|
|
492
|
+
stage: options.entryMode === "retest" ? "retest_pending" : "neckline_crossed",
|
|
493
|
+
breakoutIndex: currentIndex,
|
|
494
|
+
pattern: breakout
|
|
495
|
+
};
|
|
496
|
+
return snapshot();
|
|
497
|
+
};
|
|
498
|
+
for (const candle of initialCandles) apply(candle);
|
|
499
|
+
return { next: apply, getState: snapshot };
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// src/DoubleTap/figures.ts
|
|
503
|
+
var buildDoubleTapFigures = ({
|
|
504
|
+
pattern,
|
|
505
|
+
entryTimestamp,
|
|
506
|
+
entryPrice
|
|
507
|
+
}) => {
|
|
508
|
+
const color = pattern.direction === "LONG" ? "#22c55e" : "#ef4444";
|
|
509
|
+
const [p1, p2, p3, p4] = pattern.pivots;
|
|
510
|
+
const patternPoints = [
|
|
511
|
+
{ timestamp: p1.timestamp, value: p1.value },
|
|
512
|
+
{ timestamp: p2.timestamp, value: p2.value },
|
|
513
|
+
{ timestamp: p3.timestamp, value: p3.value },
|
|
514
|
+
{ timestamp: p4.timestamp, value: p4.value },
|
|
515
|
+
{ timestamp: entryTimestamp, value: entryPrice }
|
|
516
|
+
];
|
|
517
|
+
const lines = [
|
|
518
|
+
{
|
|
519
|
+
id: `doubletap-pattern-${entryTimestamp}`,
|
|
520
|
+
kind: `doubletap_${pattern.kind}_pattern`,
|
|
521
|
+
points: patternPoints,
|
|
522
|
+
color,
|
|
523
|
+
width: 2,
|
|
524
|
+
style: "solid"
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
id: `doubletap-neckline-${entryTimestamp}`,
|
|
528
|
+
kind: "doubletap_neckline",
|
|
529
|
+
points: [
|
|
530
|
+
{ timestamp: p3.timestamp, value: pattern.neckline },
|
|
531
|
+
{ timestamp: entryTimestamp, value: pattern.neckline }
|
|
532
|
+
],
|
|
533
|
+
color: "#f59e0b",
|
|
534
|
+
width: 2,
|
|
535
|
+
style: "dashed"
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
id: `doubletap-target-${entryTimestamp}`,
|
|
539
|
+
kind: "doubletap_target",
|
|
540
|
+
points: [
|
|
541
|
+
{ timestamp: p3.timestamp, value: pattern.targetPrice },
|
|
542
|
+
{ timestamp: entryTimestamp, value: pattern.targetPrice }
|
|
543
|
+
],
|
|
544
|
+
color: "#22c55e",
|
|
545
|
+
width: 1,
|
|
546
|
+
style: "dashed"
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
id: `doubletap-stop-${entryTimestamp}`,
|
|
550
|
+
kind: "doubletap_stop",
|
|
551
|
+
points: [
|
|
552
|
+
{ timestamp: p3.timestamp, value: pattern.stopLossPrice },
|
|
553
|
+
{ timestamp: entryTimestamp, value: pattern.stopLossPrice }
|
|
554
|
+
],
|
|
555
|
+
color: "#ef4444",
|
|
556
|
+
width: 1,
|
|
557
|
+
style: "dashed"
|
|
558
|
+
}
|
|
559
|
+
];
|
|
560
|
+
const points = [
|
|
561
|
+
{
|
|
562
|
+
id: `doubletap-pivots-${entryTimestamp}`,
|
|
563
|
+
kind: `doubletap_${pattern.kind}_pivots`,
|
|
564
|
+
points: patternPoints.slice(0, 4),
|
|
565
|
+
color,
|
|
566
|
+
radius: 4
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
id: `doubletap-entry-${entryTimestamp}`,
|
|
570
|
+
kind: "doubletap_entry",
|
|
571
|
+
points: [{ timestamp: entryTimestamp, value: entryPrice }],
|
|
572
|
+
color,
|
|
573
|
+
radius: 5
|
|
574
|
+
}
|
|
575
|
+
];
|
|
576
|
+
return { lines, points };
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// src/DoubleTap/filters.ts
|
|
580
|
+
import { resolveDirectionalConfigNumber } from "@tradejs/strategy-kit/config";
|
|
581
|
+
var asPositiveThreshold = (value) => {
|
|
582
|
+
const parsed = Number(value);
|
|
583
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
584
|
+
};
|
|
585
|
+
var getDoubleTapCoreFilterSkipCode = ({
|
|
586
|
+
config: config2,
|
|
587
|
+
pattern,
|
|
588
|
+
baseContext
|
|
589
|
+
}) => {
|
|
590
|
+
const maxConfirmationBars = asPositiveThreshold(
|
|
591
|
+
resolveDirectionalConfigNumber({
|
|
592
|
+
config: config2,
|
|
593
|
+
key: "DOUBLETAP_MAX_ENTRY_CONFIRMATION_BARS",
|
|
594
|
+
direction: pattern.direction,
|
|
595
|
+
fallback: 0
|
|
596
|
+
})
|
|
597
|
+
);
|
|
598
|
+
if (maxConfirmationBars != null && pattern.confirmationBars > maxConfirmationBars) {
|
|
599
|
+
return "DOUBLETAP_CONFIRMATION_TOO_LATE";
|
|
600
|
+
}
|
|
601
|
+
const maxBbWidthPct = asPositiveThreshold(
|
|
602
|
+
resolveDirectionalConfigNumber({
|
|
603
|
+
config: config2,
|
|
604
|
+
key: "DOUBLETAP_MAX_BB_WIDTH_PCT",
|
|
605
|
+
direction: pattern.direction,
|
|
606
|
+
fallback: 0
|
|
607
|
+
})
|
|
608
|
+
);
|
|
609
|
+
if (maxBbWidthPct != null) {
|
|
610
|
+
const bbWidthPct = Number(baseContext?.raw?.volatility?.bbWidthPct);
|
|
611
|
+
if (!Number.isFinite(bbWidthPct) || bbWidthPct > maxBbWidthPct) {
|
|
612
|
+
return "DOUBLETAP_VOLATILITY_TOO_WIDE";
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// src/DoubleTap/core.ts
|
|
619
|
+
import {
|
|
620
|
+
buildTradeEconomics,
|
|
621
|
+
isStopLossOnCorrectSide
|
|
622
|
+
} from "@tradejs/strategy-kit/risk";
|
|
623
|
+
var isOpenPosition = (position) => Boolean(
|
|
624
|
+
position && typeof position.price === "number" && Number.isFinite(position.price) && typeof position.qty === "number" && Number.isFinite(position.qty) && position.qty > 0 && (position.direction === "LONG" || position.direction === "SHORT")
|
|
625
|
+
);
|
|
626
|
+
var buildDoubleTapStateKey = (config2) => JSON.stringify({
|
|
627
|
+
pivotLength: config2.DOUBLETAP_PIVOT_LENGTH,
|
|
628
|
+
pivotTolerancePct: config2.DOUBLETAP_PIVOT_TOLERANCE_PCT,
|
|
629
|
+
targetFibPct: config2.DOUBLETAP_TARGET_FIB_PCT,
|
|
630
|
+
stopFibPct: config2.DOUBLETAP_STOP_FIB_PCT,
|
|
631
|
+
minPatternHeightPct: config2.DOUBLETAP_MIN_PATTERN_HEIGHT_PCT,
|
|
632
|
+
minPatternHeightAtr: config2.DOUBLETAP_MIN_PATTERN_HEIGHT_ATR,
|
|
633
|
+
atrPeriod: config2.DOUBLETAP_ATR_PERIOD,
|
|
634
|
+
minTapSpacingBars: config2.DOUBLETAP_MIN_TAP_SPACING_BARS,
|
|
635
|
+
maxPatternAgeBars: config2.DOUBLETAP_MAX_PATTERN_AGE_BARS,
|
|
636
|
+
minLegSymmetryRatio: config2.DOUBLETAP_MIN_LEG_SYMMETRY_RATIO,
|
|
637
|
+
minBreakoutDistanceAtr: config2.DOUBLETAP_MIN_BREAKOUT_DISTANCE_ATR,
|
|
638
|
+
maxBreakoutDistanceHeightRatio: config2.DOUBLETAP_MAX_BREAKOUT_DISTANCE_HEIGHT_RATIO,
|
|
639
|
+
maxBreakoutDistancePct: config2.DOUBLETAP_MAX_BREAKOUT_DISTANCE_PCT,
|
|
640
|
+
entryMode: config2.DOUBLETAP_ENTRY_MODE,
|
|
641
|
+
confirmationMaxBars: config2.DOUBLETAP_CONFIRMATION_MAX_BARS,
|
|
642
|
+
retestMaxBars: config2.DOUBLETAP_RETEST_MAX_BARS,
|
|
643
|
+
retestToleranceAtr: config2.DOUBLETAP_RETEST_TOLERANCE_ATR
|
|
644
|
+
});
|
|
645
|
+
var createDoubleTapCore = async ({ config: config2, data: initialData, strategyApi, indicatorsState }) => {
|
|
646
|
+
const detectorState = strategyApi.createStateController(
|
|
647
|
+
"DoubleTap",
|
|
648
|
+
() => ({
|
|
649
|
+
engine: createDoubleTapEngine({
|
|
650
|
+
config: config2,
|
|
651
|
+
initialCandles: initialData
|
|
652
|
+
})
|
|
653
|
+
}),
|
|
654
|
+
{
|
|
655
|
+
configKey: buildDoubleTapStateKey(config2),
|
|
656
|
+
snapshot: (state) => state.engine.getState()
|
|
657
|
+
}
|
|
658
|
+
);
|
|
659
|
+
const lastTradeController = strategyApi.createLastTradeController({
|
|
660
|
+
enabled: true
|
|
661
|
+
});
|
|
662
|
+
const nextDetectorState = (candle) => detectorState.oncePerTimestamp(
|
|
663
|
+
candle.timestamp,
|
|
664
|
+
(state) => state.engine.next(candle)
|
|
665
|
+
);
|
|
666
|
+
return async (candle) => {
|
|
667
|
+
const runtimeState = nextDetectorState(candle);
|
|
668
|
+
const pattern = runtimeState.pattern;
|
|
669
|
+
if (!pattern) {
|
|
670
|
+
return strategyApi.skip("NO_PATTERN");
|
|
671
|
+
}
|
|
672
|
+
const position = await strategyApi.getCurrentPosition();
|
|
673
|
+
if (isOpenPosition(position)) {
|
|
674
|
+
const oppositePattern = position.direction === "LONG" ? pattern.direction === "SHORT" : pattern.direction === "LONG";
|
|
675
|
+
if (Boolean(config2.DOUBLETAP_EXIT_ON_OPPOSITE_PATTERN) && oppositePattern) {
|
|
676
|
+
return strategyApi.exit({
|
|
677
|
+
code: "DOUBLETAP_OPPOSITE_PATTERN_EXIT",
|
|
678
|
+
direction: position.direction
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
return strategyApi.skip("POSITION_EXISTS");
|
|
682
|
+
}
|
|
683
|
+
if (lastTradeController.isInCooldown(candle.timestamp)) {
|
|
684
|
+
return strategyApi.skip("DEV_TRADE_COOLDOWN");
|
|
685
|
+
}
|
|
686
|
+
const modeConfig = pattern.direction === "LONG" ? config2.LONG : config2.SHORT;
|
|
687
|
+
if (!modeConfig.enable) {
|
|
688
|
+
return strategyApi.skip("STRATEGY_DISABLED");
|
|
689
|
+
}
|
|
690
|
+
const coreFilterSkipCode = getDoubleTapCoreFilterSkipCode({
|
|
691
|
+
config: config2,
|
|
692
|
+
pattern,
|
|
693
|
+
baseContext: strategyApi.getBaseContext()
|
|
694
|
+
});
|
|
695
|
+
if (coreFilterSkipCode) {
|
|
696
|
+
return strategyApi.skip(coreFilterSkipCode);
|
|
697
|
+
}
|
|
698
|
+
const { timestamp, currentPrice } = await strategyApi.getDecisionPriceContext();
|
|
699
|
+
if (!isStopLossOnCorrectSide({
|
|
700
|
+
direction: pattern.direction,
|
|
701
|
+
currentPrice,
|
|
702
|
+
stopLossPrice: pattern.stopLossPrice
|
|
703
|
+
})) {
|
|
704
|
+
return strategyApi.skip("INVALID_STOP");
|
|
705
|
+
}
|
|
706
|
+
const targetIsValid = pattern.direction === "LONG" ? pattern.targetPrice > currentPrice : pattern.targetPrice < currentPrice;
|
|
707
|
+
if (!targetIsValid) {
|
|
708
|
+
return strategyApi.skip("TARGET_ALREADY_PASSED");
|
|
709
|
+
}
|
|
710
|
+
const economics = buildTradeEconomics({
|
|
711
|
+
entryPrice: currentPrice,
|
|
712
|
+
stopLossPrice: pattern.stopLossPrice,
|
|
713
|
+
takeProfitPrice: pattern.targetPrice,
|
|
714
|
+
feeRate: Number(config2.FEE_PERCENT ?? 0),
|
|
715
|
+
slippageBps: Number(config2.SLIPPAGE_BASE_BPS ?? 0) + Number(config2.SLIPPAGE_MARKET_IMPACT_BPS ?? 0)
|
|
716
|
+
});
|
|
717
|
+
const qty = economics.lossPerUnit > 0 ? Number(config2.MAX_LOSS_VALUE ?? 0) / economics.lossPerUnit : 0;
|
|
718
|
+
const riskRatio = economics.netRiskRatio;
|
|
719
|
+
const signalContext = {
|
|
720
|
+
...buildDoubleTapSignalContext({ ...pattern, close: currentPrice }),
|
|
721
|
+
executionEconomics: {
|
|
722
|
+
grossRiskRatio: economics.grossRiskRatio,
|
|
723
|
+
netRiskRatio: economics.netRiskRatio,
|
|
724
|
+
lossPerUnit: economics.lossPerUnit,
|
|
725
|
+
rewardPerUnit: economics.rewardPerUnit
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
if (!qty || !Number.isFinite(qty) || qty <= 0) {
|
|
729
|
+
return strategyApi.skip("INVALID_QTY");
|
|
730
|
+
}
|
|
731
|
+
if (riskRatio <= modeConfig.minRiskRatio) {
|
|
732
|
+
return strategyApi.skip(`RISK_RATIO:${round(riskRatio)}`);
|
|
733
|
+
}
|
|
734
|
+
const indicators = indicatorsState.snapshot();
|
|
735
|
+
lastTradeController.markTrade(timestamp);
|
|
736
|
+
return strategyApi.entry({
|
|
737
|
+
code: pattern.direction === "LONG" ? `DOUBLETAP_DOUBLE_BOTTOM_${pattern.entryStage.toUpperCase()}` : `DOUBLETAP_DOUBLE_TOP_${pattern.entryStage.toUpperCase()}`,
|
|
738
|
+
direction: modeConfig.direction,
|
|
739
|
+
indicators,
|
|
740
|
+
additionalIndicators: {
|
|
741
|
+
doubleTapContext: signalContext
|
|
742
|
+
},
|
|
743
|
+
figures: buildDoubleTapFigures({
|
|
744
|
+
pattern,
|
|
745
|
+
entryTimestamp: timestamp,
|
|
746
|
+
entryPrice: currentPrice
|
|
747
|
+
}),
|
|
748
|
+
orderPlan: {
|
|
749
|
+
qty,
|
|
750
|
+
stopLossPrice: pattern.stopLossPrice,
|
|
751
|
+
takeProfits: [{ rate: 1, price: pattern.targetPrice }]
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
};
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// src/DoubleTap/adapters/ai.ts
|
|
758
|
+
import { intervalToMs } from "@tradejs/core/data";
|
|
759
|
+
import { mapAiRuntimeFromConfig } from "@tradejs/core/strategies";
|
|
760
|
+
import {
|
|
761
|
+
getAiPayloadNumber,
|
|
762
|
+
withStrategyLocalAiGate
|
|
763
|
+
} from "@tradejs/strategy-kit/ai-gate";
|
|
764
|
+
var MIN_EXECUTION_SCORE_FOR_AI_GATE = 35;
|
|
765
|
+
var MIN_LOW_TOUCH_COUNT20_FOR_AI_GATE = 1;
|
|
766
|
+
var HIGH_PRECISION_CMC_ALT_VOLUME_CHANGE_MAX = 0.5;
|
|
767
|
+
var Q4_CMC_BTC_DOMINANCE_CHANGE_MIN = -0.3;
|
|
768
|
+
var Q4_CMC_BTC_DOMINANCE_CHANGE_MAX = -0.05;
|
|
769
|
+
var Q4_ALT_DISPERSION_24H_MAX = 0.06;
|
|
770
|
+
var Q4_DERIVATIVES_BTC_VS_ALT_RETURN_24H_MAX = -9e-3;
|
|
771
|
+
var Q4_DERIVATIVES_ETH_CROWDING_PERSISTENCE_MIN = 140;
|
|
772
|
+
var Q4_DERIVATIVES_SOL_FUNDING_Z_SCORE_15M_MAX = 0.2;
|
|
773
|
+
var Q4_DERIVATIVES_BAD_BTC_VS_ALT_RETURN_24H_MAX = -0.014;
|
|
774
|
+
var Q4_DERIVATIVES_BAD_CMC20_TO_CMC100_CHANGE_24H_MAX = -7e-4;
|
|
775
|
+
var BNB_OI_ROTATION_CHANGE_24H_15M_MIN = 0.65;
|
|
776
|
+
var BNB_OI_ROTATION_CHANGE_1H_1H_MAX = -0.28;
|
|
777
|
+
var XRP_OI_SHORT_NO_HTF_CHANGE_1H_15M_MIN = 0.32;
|
|
778
|
+
var ETH_VOLUME_BREADTH_ETH_VS_BTC_VOLUME_RATIO_MAX = 0.39;
|
|
779
|
+
var ETH_VOLUME_BREADTH_TOP5_DISPERSION_MAX = 7e-4;
|
|
780
|
+
var ETH_VOLUME_BREADTH_ETH_DOMINANCE_CHANGE_MIN = -0.05;
|
|
781
|
+
var APPROVAL_ETH_VS_BTC_VOLUME_RATIO_MIN = 0.34;
|
|
782
|
+
var STRICT_MOMENTUM_ROC1D_MIN = -5.25;
|
|
783
|
+
var PROTECTIVE_RELATIVE_STRENGTH_4H_MAX = 7.77009;
|
|
784
|
+
var PROTECTIVE_CMC_EXCHANGE_LIQUIDITY_CHANGE_24H_MIN = -0.378662;
|
|
785
|
+
var PROTECTIVE_BNB_REFERENCE_PRESSURE_BLOCK = "crowded_long";
|
|
786
|
+
var asRecord = (value) => typeof value === "object" && value != null && !Array.isArray(value) ? value : null;
|
|
787
|
+
var asNumber2 = (value) => {
|
|
788
|
+
if (typeof value === "number") {
|
|
789
|
+
return Number.isFinite(value) ? value : null;
|
|
790
|
+
}
|
|
791
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
792
|
+
const numeric = Number(value);
|
|
793
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
794
|
+
}
|
|
795
|
+
return null;
|
|
796
|
+
};
|
|
797
|
+
var divideFinite = (numerator, denominator) => numerator != null && denominator != null && Number.isFinite(numerator) && Number.isFinite(denominator) && Math.abs(denominator) > Number.EPSILON ? numerator / Math.abs(denominator) : null;
|
|
798
|
+
var resolveIntervalMs = (interval) => {
|
|
799
|
+
if (typeof interval !== "string" || interval.length === 0) {
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
const value = intervalToMs(interval);
|
|
804
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
805
|
+
} catch {
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
var resolveElapsedBars = ({
|
|
810
|
+
from,
|
|
811
|
+
to,
|
|
812
|
+
intervalMs
|
|
813
|
+
}) => {
|
|
814
|
+
if (from == null || to == null || intervalMs == null || to < from) {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
const bars = (to - from) / intervalMs;
|
|
818
|
+
return Number.isFinite(bars) ? bars : null;
|
|
819
|
+
};
|
|
820
|
+
var resolveSlopePctPerBar = ({
|
|
821
|
+
fromValue,
|
|
822
|
+
toValue,
|
|
823
|
+
bars
|
|
824
|
+
}) => {
|
|
825
|
+
if (fromValue == null || toValue == null || bars == null || bars <= 0 || Math.abs(fromValue) <= Number.EPSILON) {
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
return Math.abs(toValue - fromValue) / Math.abs(fromValue) / bars * 100;
|
|
829
|
+
};
|
|
830
|
+
var buildDoubleTapSetupFeatures = ({
|
|
831
|
+
context,
|
|
832
|
+
interval,
|
|
833
|
+
signalTimestamp
|
|
834
|
+
}) => {
|
|
835
|
+
const pivots = Array.isArray(context.pivots) ? context.pivots : [];
|
|
836
|
+
const readPivotNumber = (index, key) => asNumber2(asRecord(pivots[index])?.[key]);
|
|
837
|
+
const p1Timestamp = readPivotNumber(0, "timestamp");
|
|
838
|
+
const p2Timestamp = readPivotNumber(1, "timestamp");
|
|
839
|
+
const p3Timestamp = readPivotNumber(2, "timestamp");
|
|
840
|
+
const p4Timestamp = readPivotNumber(3, "timestamp");
|
|
841
|
+
const p2Value = readPivotNumber(1, "value");
|
|
842
|
+
const p3Value = readPivotNumber(2, "value");
|
|
843
|
+
const p4Value = readPivotNumber(3, "value");
|
|
844
|
+
const intervalMs = resolveIntervalMs(interval);
|
|
845
|
+
const leadInBars = resolveElapsedBars({
|
|
846
|
+
from: p1Timestamp,
|
|
847
|
+
to: p2Timestamp,
|
|
848
|
+
intervalMs
|
|
849
|
+
});
|
|
850
|
+
const firstLegBars = resolveElapsedBars({
|
|
851
|
+
from: p2Timestamp,
|
|
852
|
+
to: p3Timestamp,
|
|
853
|
+
intervalMs
|
|
854
|
+
});
|
|
855
|
+
const secondLegBars = resolveElapsedBars({
|
|
856
|
+
from: p3Timestamp,
|
|
857
|
+
to: p4Timestamp,
|
|
858
|
+
intervalMs
|
|
859
|
+
});
|
|
860
|
+
const tapSpacingBars = resolveElapsedBars({
|
|
861
|
+
from: p2Timestamp,
|
|
862
|
+
to: p4Timestamp,
|
|
863
|
+
intervalMs
|
|
864
|
+
});
|
|
865
|
+
const breakoutLagBars = resolveElapsedBars({
|
|
866
|
+
from: p4Timestamp,
|
|
867
|
+
to: asNumber2(signalTimestamp),
|
|
868
|
+
intervalMs
|
|
869
|
+
});
|
|
870
|
+
const heightValue = asNumber2(context.height);
|
|
871
|
+
const height = heightValue == null ? null : Math.abs(heightValue);
|
|
872
|
+
const neckline = asNumber2(context.neckline);
|
|
873
|
+
const currentPrice = asNumber2(context.currentPrice);
|
|
874
|
+
const stopLossPrice = asNumber2(context.stopLossPrice);
|
|
875
|
+
const targetPrice = asNumber2(context.targetPrice);
|
|
876
|
+
const breakoutDistance = currentPrice == null || neckline == null ? null : Math.abs(currentPrice - neckline);
|
|
877
|
+
const breakoutDistanceHeightRatio = divideFinite(breakoutDistance, height);
|
|
878
|
+
const tapPriceDeviation = p2Value == null || p4Value == null ? null : Math.abs(p2Value - p4Value);
|
|
879
|
+
const averageTapPrice = p2Value == null || p4Value == null ? null : (Math.abs(p2Value) + Math.abs(p4Value)) / 2;
|
|
880
|
+
const patternHeightPriceRatio = divideFinite(height, neckline);
|
|
881
|
+
const tapPriceDeviationRatio = divideFinite(
|
|
882
|
+
tapPriceDeviation,
|
|
883
|
+
averageTapPrice
|
|
884
|
+
);
|
|
885
|
+
const firstLegSlopePctPerBar = resolveSlopePctPerBar({
|
|
886
|
+
fromValue: p2Value,
|
|
887
|
+
toValue: p3Value,
|
|
888
|
+
bars: firstLegBars
|
|
889
|
+
});
|
|
890
|
+
const secondLegSlopePctPerBar = resolveSlopePctPerBar({
|
|
891
|
+
fromValue: p3Value,
|
|
892
|
+
toValue: p4Value,
|
|
893
|
+
bars: secondLegBars
|
|
894
|
+
});
|
|
895
|
+
return {
|
|
896
|
+
geometry: {
|
|
897
|
+
patternHeightPct: patternHeightPriceRatio == null ? null : patternHeightPriceRatio * 100,
|
|
898
|
+
breakoutDistanceHeightRatio,
|
|
899
|
+
tapPriceDeviationPct: tapPriceDeviationRatio == null ? null : tapPriceDeviationRatio * 100,
|
|
900
|
+
tapDeviationHeightRatio: divideFinite(tapPriceDeviation, height),
|
|
901
|
+
stopDistanceHeightRatio: currentPrice == null || stopLossPrice == null ? null : divideFinite(Math.abs(currentPrice - stopLossPrice), height),
|
|
902
|
+
targetDistanceHeightRatio: currentPrice == null || targetPrice == null ? null : divideFinite(Math.abs(targetPrice - currentPrice), height)
|
|
903
|
+
},
|
|
904
|
+
path: {
|
|
905
|
+
leadInBars,
|
|
906
|
+
firstLegBars,
|
|
907
|
+
secondLegBars,
|
|
908
|
+
tapSpacingBars,
|
|
909
|
+
breakoutLagBars,
|
|
910
|
+
legDurationSymmetryRatio: firstLegBars == null || secondLegBars == null ? null : divideFinite(
|
|
911
|
+
Math.min(firstLegBars, secondLegBars),
|
|
912
|
+
Math.max(firstLegBars, secondLegBars)
|
|
913
|
+
),
|
|
914
|
+
firstLegSlopePctPerBar,
|
|
915
|
+
secondLegSlopePctPerBar,
|
|
916
|
+
legSlopeSymmetryRatio: firstLegSlopePctPerBar == null || secondLegSlopePctPerBar == null ? null : divideFinite(
|
|
917
|
+
Math.min(firstLegSlopePctPerBar, secondLegSlopePctPerBar),
|
|
918
|
+
Math.max(firstLegSlopePctPerBar, secondLegSlopePctPerBar)
|
|
919
|
+
),
|
|
920
|
+
breakoutSpeedHeightRatioPerBar: breakoutDistanceHeightRatio == null || breakoutLagBars == null ? null : breakoutDistanceHeightRatio / Math.max(1, breakoutLagBars)
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
};
|
|
924
|
+
var getDoubleTapContext = (payload) => {
|
|
925
|
+
const additional = asRecord(payload.additionalIndicators);
|
|
926
|
+
return (additional?.doubleTapContext ?? {}) || {};
|
|
927
|
+
};
|
|
928
|
+
var getNestedRecord = (record, path) => path.reduce(
|
|
929
|
+
(current, key) => asRecord(current?.[key]),
|
|
930
|
+
record
|
|
931
|
+
);
|
|
932
|
+
var getNestedNumber = (record, path) => asNumber2(
|
|
933
|
+
path.reduce((current, key) => {
|
|
934
|
+
return asRecord(current)?.[key];
|
|
935
|
+
}, record)
|
|
936
|
+
);
|
|
937
|
+
var getNestedString = (record, path) => {
|
|
938
|
+
const value = path.reduce((current, key) => {
|
|
939
|
+
return asRecord(current)?.[key];
|
|
940
|
+
}, record);
|
|
941
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
942
|
+
};
|
|
943
|
+
var getNestedBoolean = (record, path) => {
|
|
944
|
+
const value = path.reduce((current, key) => {
|
|
945
|
+
return asRecord(current)?.[key];
|
|
946
|
+
}, record);
|
|
947
|
+
return typeof value === "boolean" ? value : null;
|
|
948
|
+
};
|
|
949
|
+
var getStringArray = (value) => Array.isArray(value) ? value.filter(
|
|
950
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
951
|
+
) : [];
|
|
952
|
+
var resolveGeometryQuality = (context) => {
|
|
953
|
+
const breakoutDistancePct = asNumber2(context.breakoutDistancePct) ?? 999;
|
|
954
|
+
const height = asNumber2(context.height) ?? 0;
|
|
955
|
+
if (height <= 0) {
|
|
956
|
+
return 1;
|
|
957
|
+
}
|
|
958
|
+
if (breakoutDistancePct <= 0.8) {
|
|
959
|
+
return 4;
|
|
960
|
+
}
|
|
961
|
+
if (breakoutDistancePct <= 1.4) {
|
|
962
|
+
return 3;
|
|
963
|
+
}
|
|
964
|
+
if (breakoutDistancePct <= 2.5) {
|
|
965
|
+
return 2;
|
|
966
|
+
}
|
|
967
|
+
return 1;
|
|
968
|
+
};
|
|
969
|
+
var isDirectionalCrowding = (direction, riskFlags) => direction === "LONG" ? riskFlags.includes("crowded_long") : direction === "SHORT" ? riskFlags.includes("crowded_short") : false;
|
|
970
|
+
var isBenchmarkAligned = ({
|
|
971
|
+
direction,
|
|
972
|
+
trendAlignment,
|
|
973
|
+
benchmarkBias
|
|
974
|
+
}) => direction === "LONG" ? trendAlignment === "aligned_bull" || benchmarkBias === "bull" : direction === "SHORT" ? trendAlignment === "aligned_bear" || benchmarkBias === "bear" : false;
|
|
975
|
+
var buildDoubleTapGateFeatures = ({
|
|
976
|
+
setupFeatures,
|
|
977
|
+
signalDirection,
|
|
978
|
+
height,
|
|
979
|
+
breakoutDistancePct,
|
|
980
|
+
trendAligned,
|
|
981
|
+
benchmarkAligned,
|
|
982
|
+
volumeRel20,
|
|
983
|
+
derivativesDirectionAligned,
|
|
984
|
+
derivativesRiskFlags,
|
|
985
|
+
venueSpreadZScore,
|
|
986
|
+
directionalCrowding,
|
|
987
|
+
approvalPocket,
|
|
988
|
+
bnbOiRotationPocket,
|
|
989
|
+
bnbOiRotationBlocked,
|
|
990
|
+
xrpOiShortNoHtfPocket,
|
|
991
|
+
xrpOiShortNoHtfBlocked,
|
|
992
|
+
ethVolumeBreadthCompressionPocket,
|
|
993
|
+
ethVolumeBreadthCompressionBlocked,
|
|
994
|
+
highPrecisionPocket,
|
|
995
|
+
highPrecisionApprovalBlocked,
|
|
996
|
+
q4DerivativesPocket,
|
|
997
|
+
q4DerivativesApprovalBlocked,
|
|
998
|
+
q4ApprovalBlocked,
|
|
999
|
+
defaultApprovalAllowed,
|
|
1000
|
+
q4AltDispersionOk,
|
|
1001
|
+
q4DerivativesCmcRiskOk,
|
|
1002
|
+
q4DerivativesDirectionSessionOk,
|
|
1003
|
+
bnbOiRotationContextAvailable,
|
|
1004
|
+
bnbOiRotationMomentumOk,
|
|
1005
|
+
xrpOiShortNoHtfContextAvailable,
|
|
1006
|
+
ethVolumeBreadthContextAvailable,
|
|
1007
|
+
ethVsBtcVolumeRatioApprovalOk,
|
|
1008
|
+
strictMomentumApproved,
|
|
1009
|
+
strictMomentumRoc1dOk,
|
|
1010
|
+
protectiveApprovalContextAvailable,
|
|
1011
|
+
protectiveApprovalContextOk
|
|
1012
|
+
}) => {
|
|
1013
|
+
const patternGeometry = height == null ? "unknown" : height <= 0 ? "invalid" : breakoutDistancePct != null && breakoutDistancePct <= 1.4 ? "compact" : "extended";
|
|
1014
|
+
const necklineBreakout = breakoutDistancePct == null ? "missing" : breakoutDistancePct <= 0.25 ? "early_noise" : breakoutDistancePct <= 0.8 ? "compact" : breakoutDistancePct <= 1.4 ? "confirmed" : "extended";
|
|
1015
|
+
const trendContext = signalDirection == null ? "unknown" : trendAligned || benchmarkAligned ? "aligned" : "neutral";
|
|
1016
|
+
const participationState = volumeRel20 == null ? "unknown" : volumeRel20 < 0.8 ? "thin" : volumeRel20 >= 2 ? "strong" : "normal";
|
|
1017
|
+
const derivativesState = derivativesDirectionAligned === true ? "aligned" : derivativesDirectionAligned === false ? "conflict" : directionalCrowding ? "crowded" : derivativesRiskFlags.length > 0 ? "neutral" : "unknown";
|
|
1018
|
+
const executionSpreadState = venueSpreadZScore == null ? "unknown" : venueSpreadZScore >= 1 ? "supportive" : venueSpreadZScore <= -1 ? "adverse" : "neutral";
|
|
1019
|
+
return {
|
|
1020
|
+
...setupFeatures,
|
|
1021
|
+
patternGeometry,
|
|
1022
|
+
necklineBreakout,
|
|
1023
|
+
trendContext,
|
|
1024
|
+
participationState,
|
|
1025
|
+
derivativesState,
|
|
1026
|
+
executionSpreadState,
|
|
1027
|
+
approvalPocket: bnbOiRotationMomentumOk ? bnbOiRotationBlocked ? "bnb_oi_rotation_blocked" : "bnb_oi_rotation" : xrpOiShortNoHtfPocket ? xrpOiShortNoHtfBlocked ? "xrp_oi_short_no_htf_blocked" : "xrp_oi_short_no_htf" : ethVolumeBreadthCompressionPocket ? ethVolumeBreadthCompressionBlocked ? "eth_volume_breadth_blocked" : "eth_volume_breadth" : bnbOiRotationPocket ? bnbOiRotationBlocked ? "bnb_oi_rotation_blocked" : "bnb_oi_rotation" : highPrecisionPocket ? highPrecisionApprovalBlocked ? "high_precision_blocked" : "high_precision" : q4DerivativesPocket ? q4DerivativesApprovalBlocked ? "q4_derivatives_blocked" : "q4_derivatives" : approvalPocket && q4ApprovalBlocked ? "q4_blocked" : approvalPocket ? "q4" : "watch",
|
|
1028
|
+
highQualityCadencePocket: bnbOiRotationMomentumOk && !bnbOiRotationBlocked || xrpOiShortNoHtfPocket && !xrpOiShortNoHtfBlocked,
|
|
1029
|
+
defaultApprovalAllowed,
|
|
1030
|
+
q4AltDispersionOk,
|
|
1031
|
+
q4DerivativesPocket,
|
|
1032
|
+
q4DerivativesCmcRiskOk,
|
|
1033
|
+
q4DerivativesDirectionSessionOk,
|
|
1034
|
+
bnbOiRotationPocket,
|
|
1035
|
+
bnbOiRotationContextAvailable,
|
|
1036
|
+
bnbOiRotationMomentumOk,
|
|
1037
|
+
xrpOiShortNoHtfPocket,
|
|
1038
|
+
xrpOiShortNoHtfContextAvailable,
|
|
1039
|
+
ethVolumeBreadthContextAvailable,
|
|
1040
|
+
ethVolumeBreadthCompressionPocket,
|
|
1041
|
+
ethVsBtcVolumeRatioApprovalOk,
|
|
1042
|
+
strictMomentumApproved,
|
|
1043
|
+
strictMomentumRoc1dOk,
|
|
1044
|
+
protectiveApprovalContextAvailable,
|
|
1045
|
+
protectiveApprovalContextOk
|
|
1046
|
+
};
|
|
1047
|
+
};
|
|
1048
|
+
var buildDoubleTapAiContext = (payload) => {
|
|
1049
|
+
const context = getDoubleTapContext(payload);
|
|
1050
|
+
const additional = asRecord(payload.additionalIndicators);
|
|
1051
|
+
const baseContext = asRecord(additional?.baseContext);
|
|
1052
|
+
const derivativesSummary = getNestedRecord(baseContext, [
|
|
1053
|
+
"derivatives",
|
|
1054
|
+
"summary"
|
|
1055
|
+
]);
|
|
1056
|
+
const signalDirection = context.signalDirection === "LONG" || context.signalDirection === "SHORT" ? context.signalDirection : null;
|
|
1057
|
+
const breakoutDistancePct = asNumber2(context.breakoutDistancePct);
|
|
1058
|
+
const height = asNumber2(context.height);
|
|
1059
|
+
const baseContextAvailable = Boolean(baseContext);
|
|
1060
|
+
const primarySession = getNestedString(baseContext, [
|
|
1061
|
+
"regime",
|
|
1062
|
+
"session",
|
|
1063
|
+
"sessionPhase"
|
|
1064
|
+
]);
|
|
1065
|
+
const sessionWindowPhase = getNestedString(baseContext, [
|
|
1066
|
+
"regime",
|
|
1067
|
+
"session",
|
|
1068
|
+
"sessionWindowPhase"
|
|
1069
|
+
]);
|
|
1070
|
+
const trendBias = getNestedString(baseContext, ["regime", "trend", "bias"]);
|
|
1071
|
+
const swingBias = getNestedString(baseContext, [
|
|
1072
|
+
"structure",
|
|
1073
|
+
"swing",
|
|
1074
|
+
"bias"
|
|
1075
|
+
]);
|
|
1076
|
+
const breakoutState = getNestedString(baseContext, [
|
|
1077
|
+
"structure",
|
|
1078
|
+
"localRange",
|
|
1079
|
+
"breakoutState"
|
|
1080
|
+
]);
|
|
1081
|
+
const barsSinceBreakout = getNestedNumber(baseContext, [
|
|
1082
|
+
"structure",
|
|
1083
|
+
"localRange",
|
|
1084
|
+
"barsSinceBreakout"
|
|
1085
|
+
]);
|
|
1086
|
+
const lowTouchCount20 = getNestedNumber(baseContext, [
|
|
1087
|
+
"structure",
|
|
1088
|
+
"levels",
|
|
1089
|
+
"lowTouchCount20"
|
|
1090
|
+
]);
|
|
1091
|
+
const volumeRel20 = getNestedNumber(baseContext, [
|
|
1092
|
+
"participation",
|
|
1093
|
+
"volume",
|
|
1094
|
+
"volumeRel20"
|
|
1095
|
+
]);
|
|
1096
|
+
const benchmarkTrendAlignment = getNestedString(baseContext, [
|
|
1097
|
+
"relative",
|
|
1098
|
+
"benchmark",
|
|
1099
|
+
"trendAlignment"
|
|
1100
|
+
]);
|
|
1101
|
+
const benchmarkRelativeStrength4h = getNestedNumber(baseContext, [
|
|
1102
|
+
"relative",
|
|
1103
|
+
"benchmark",
|
|
1104
|
+
"relativeStrength4h"
|
|
1105
|
+
]);
|
|
1106
|
+
const benchmarkBias = getNestedString(baseContext, [
|
|
1107
|
+
"relative",
|
|
1108
|
+
"benchmark",
|
|
1109
|
+
"bias"
|
|
1110
|
+
]);
|
|
1111
|
+
const derivativesDirectionAligned = typeof derivativesSummary?.directionAligned === "boolean" ? derivativesSummary.directionAligned : null;
|
|
1112
|
+
const derivativesRiskFlags = getStringArray(derivativesSummary?.riskFlags);
|
|
1113
|
+
const bodyStrength = getNestedNumber(baseContext, [
|
|
1114
|
+
"regime",
|
|
1115
|
+
"momentum",
|
|
1116
|
+
"bodyStrength"
|
|
1117
|
+
]);
|
|
1118
|
+
const roc1d = getNestedNumber(baseContext, ["regime", "momentum", "roc1d"]);
|
|
1119
|
+
const venueSpreadZScore = getNestedNumber(baseContext, [
|
|
1120
|
+
"relative",
|
|
1121
|
+
"execution",
|
|
1122
|
+
"venueSpreadZScore"
|
|
1123
|
+
]);
|
|
1124
|
+
const rewardToVolatility = getNestedNumber(baseContext, [
|
|
1125
|
+
"gateFeatures",
|
|
1126
|
+
"setup",
|
|
1127
|
+
"rewardToVolatility"
|
|
1128
|
+
]);
|
|
1129
|
+
const executionScore = getNestedNumber(baseContext, [
|
|
1130
|
+
"gateFeatures",
|
|
1131
|
+
"scores",
|
|
1132
|
+
"execution"
|
|
1133
|
+
]);
|
|
1134
|
+
const volumeStructureAligned = getNestedBoolean(baseContext, [
|
|
1135
|
+
"gateFeatures",
|
|
1136
|
+
"participation",
|
|
1137
|
+
"volumeStructureAligned"
|
|
1138
|
+
]);
|
|
1139
|
+
const benchmarkConflict = getNestedBoolean(baseContext, [
|
|
1140
|
+
"gateFeatures",
|
|
1141
|
+
"relative",
|
|
1142
|
+
"benchmarkConflict"
|
|
1143
|
+
]);
|
|
1144
|
+
const cmcAltVolumeChange24hPct = getNestedNumber(baseContext, [
|
|
1145
|
+
"relative",
|
|
1146
|
+
"cmcGlobal",
|
|
1147
|
+
"altVolumeChange24hPct"
|
|
1148
|
+
]);
|
|
1149
|
+
const cmcExchangeLiquidityVolumeChange24hPct = getNestedNumber(baseContext, [
|
|
1150
|
+
"gateFeatures",
|
|
1151
|
+
"relative",
|
|
1152
|
+
"cmcExchangeLiquidityVolumeChange24hPct"
|
|
1153
|
+
]) ?? getNestedNumber(baseContext, [
|
|
1154
|
+
"relative",
|
|
1155
|
+
"cmcExchangeLiquidity",
|
|
1156
|
+
"totalVolumeChange24hPct"
|
|
1157
|
+
]);
|
|
1158
|
+
const cmcBtcDominanceChange24hPct = getNestedNumber(baseContext, [
|
|
1159
|
+
"relative",
|
|
1160
|
+
"cmcGlobal",
|
|
1161
|
+
"btcDominanceChange24hPct"
|
|
1162
|
+
]);
|
|
1163
|
+
const cmcEthDominanceChange24hPct = getNestedNumber(baseContext, [
|
|
1164
|
+
"relative",
|
|
1165
|
+
"cmcGlobal",
|
|
1166
|
+
"ethDominanceChange24hPct"
|
|
1167
|
+
]);
|
|
1168
|
+
const cmc20ToCmc100RatioChange24hPct = getNestedNumber(baseContext, [
|
|
1169
|
+
"relative",
|
|
1170
|
+
"cmcIndexes",
|
|
1171
|
+
"cmc20ToCmc100RatioChange24hPct"
|
|
1172
|
+
]) ?? getNestedNumber(baseContext, [
|
|
1173
|
+
"gateFeatures",
|
|
1174
|
+
"relative",
|
|
1175
|
+
"cmc20ToCmc100RatioChange24hPct"
|
|
1176
|
+
]);
|
|
1177
|
+
const ethVsBtcVolumeRatio = getNestedNumber(baseContext, [
|
|
1178
|
+
"relative",
|
|
1179
|
+
"cmcReferenceAssets",
|
|
1180
|
+
"ethVsBtcVolumeRatio"
|
|
1181
|
+
]);
|
|
1182
|
+
const marketBreadthTop5Dispersion = getNestedNumber(baseContext, [
|
|
1183
|
+
"relative",
|
|
1184
|
+
"marketBreadths",
|
|
1185
|
+
"top5",
|
|
1186
|
+
"dispersion"
|
|
1187
|
+
]);
|
|
1188
|
+
const btcVsAltReturn24h = getNestedNumber(baseContext, [
|
|
1189
|
+
"relative",
|
|
1190
|
+
"btcAltRegime",
|
|
1191
|
+
"btcVsAltReturn24h"
|
|
1192
|
+
]) ?? getNestedNumber(baseContext, [
|
|
1193
|
+
"gateFeatures",
|
|
1194
|
+
"relative",
|
|
1195
|
+
"btcVsAltReturn24h"
|
|
1196
|
+
]);
|
|
1197
|
+
const ethCrowdingPersistenceBars = getNestedNumber(baseContext, [
|
|
1198
|
+
"derivatives",
|
|
1199
|
+
"referenceContexts",
|
|
1200
|
+
"ETHUSDT",
|
|
1201
|
+
"summary",
|
|
1202
|
+
"crowdingPersistenceBars"
|
|
1203
|
+
]);
|
|
1204
|
+
const solFundingZScore15m = getNestedNumber(baseContext, [
|
|
1205
|
+
"derivatives",
|
|
1206
|
+
"referenceContexts",
|
|
1207
|
+
"SOLUSDT",
|
|
1208
|
+
"intervals",
|
|
1209
|
+
"15m",
|
|
1210
|
+
"fundingZScore"
|
|
1211
|
+
]);
|
|
1212
|
+
const bnbOiChangePct24h15m = getNestedNumber(baseContext, [
|
|
1213
|
+
"derivatives",
|
|
1214
|
+
"referenceContexts",
|
|
1215
|
+
"BNBUSDT",
|
|
1216
|
+
"intervals",
|
|
1217
|
+
"15m",
|
|
1218
|
+
"oiChangePct24h"
|
|
1219
|
+
]);
|
|
1220
|
+
const bnbOiChangePct1h1h = getNestedNumber(baseContext, [
|
|
1221
|
+
"derivatives",
|
|
1222
|
+
"referenceContexts",
|
|
1223
|
+
"BNBUSDT",
|
|
1224
|
+
"intervals",
|
|
1225
|
+
"1h",
|
|
1226
|
+
"oiChangePct1h"
|
|
1227
|
+
]);
|
|
1228
|
+
const bnbReferencePressure = getNestedString(baseContext, [
|
|
1229
|
+
"derivatives",
|
|
1230
|
+
"referenceContexts",
|
|
1231
|
+
"BNBUSDT",
|
|
1232
|
+
"summary",
|
|
1233
|
+
"pressure"
|
|
1234
|
+
]);
|
|
1235
|
+
const xrpOiChangePct1h15m = getNestedNumber(baseContext, [
|
|
1236
|
+
"derivatives",
|
|
1237
|
+
"referenceContexts",
|
|
1238
|
+
"XRPUSDT",
|
|
1239
|
+
"intervals",
|
|
1240
|
+
"15m",
|
|
1241
|
+
"oiChangePct1h"
|
|
1242
|
+
]);
|
|
1243
|
+
const higherTimeframeConflict = getNestedBoolean(baseContext, [
|
|
1244
|
+
"gateFeatures",
|
|
1245
|
+
"mtf",
|
|
1246
|
+
"higherTimeframeConflict"
|
|
1247
|
+
]);
|
|
1248
|
+
const altDispersion24h = getNestedNumber(baseContext, [
|
|
1249
|
+
"relative",
|
|
1250
|
+
"btcAltRegime",
|
|
1251
|
+
"altDispersion24h"
|
|
1252
|
+
]);
|
|
1253
|
+
const structuralHardBlockReasons = [];
|
|
1254
|
+
if (!baseContextAvailable) {
|
|
1255
|
+
structuralHardBlockReasons.push("missing_base_context");
|
|
1256
|
+
}
|
|
1257
|
+
if ((height ?? 0) <= 0) {
|
|
1258
|
+
structuralHardBlockReasons.push("invalid_pattern_height");
|
|
1259
|
+
}
|
|
1260
|
+
if (breakoutDistancePct == null || breakoutDistancePct > 1.4) {
|
|
1261
|
+
structuralHardBlockReasons.push("extended_or_missing_breakout");
|
|
1262
|
+
}
|
|
1263
|
+
const geometryQuality = resolveGeometryQuality(context);
|
|
1264
|
+
const compactButNotTooEarly = breakoutDistancePct != null && breakoutDistancePct > 0.35 && breakoutDistancePct <= 0.8;
|
|
1265
|
+
const directionalCrowding = isDirectionalCrowding(
|
|
1266
|
+
signalDirection,
|
|
1267
|
+
derivativesRiskFlags
|
|
1268
|
+
);
|
|
1269
|
+
const benchmarkAligned = isBenchmarkAligned({
|
|
1270
|
+
direction: signalDirection,
|
|
1271
|
+
trendAlignment: benchmarkTrendAlignment,
|
|
1272
|
+
benchmarkBias
|
|
1273
|
+
});
|
|
1274
|
+
const trendAligned = signalDirection === "LONG" ? trendBias === "bull" : signalDirection === "SHORT" ? trendBias === "bear" : false;
|
|
1275
|
+
const breakoutAligned = signalDirection === "LONG" ? breakoutState === "above_high_level" : signalDirection === "SHORT" ? breakoutState === "below_low_level" : false;
|
|
1276
|
+
const legacyLongHighPrecisionShapeCandidate = signalDirection === "LONG" && breakoutDistancePct != null && breakoutDistancePct > 0.5 && breakoutDistancePct <= 1.4 && primarySession !== "us" && volumeRel20 != null && volumeRel20 > 2 && barsSinceBreakout != null && barsSinceBreakout <= 1;
|
|
1277
|
+
const legacyShortHighPrecisionShapeCandidate = signalDirection === "SHORT" && breakoutDistancePct != null && breakoutDistancePct > 0.25 && breakoutDistancePct <= 0.8 && (primarySession === "europe" || primarySession === "off_hours") && volumeRel20 != null && volumeRel20 > 0.8 && !directionalCrowding && trendAligned;
|
|
1278
|
+
const legacyHighPrecisionShapeCandidate = legacyLongHighPrecisionShapeCandidate || legacyShortHighPrecisionShapeCandidate;
|
|
1279
|
+
const legacyLongStructuralShapeCandidate = signalDirection === "LONG" && breakoutDistancePct != null && breakoutDistancePct > 0.25 && breakoutDistancePct <= 0.8 && primarySession !== "us" && volumeRel20 != null && volumeRel20 > 0.8 && breakoutAligned;
|
|
1280
|
+
const legacyShortStructuralShapeCandidate = signalDirection === "SHORT" && compactButNotTooEarly && !directionalCrowding && (primarySession === "europe" || primarySession === "off_hours" || volumeRel20 != null && volumeRel20 > 1.2 || benchmarkAligned) && (primarySession !== "us" || trendAligned || volumeRel20 != null && volumeRel20 > 2);
|
|
1281
|
+
const legacyStructuralShapeCandidate = legacyLongStructuralShapeCandidate || legacyShortStructuralShapeCandidate;
|
|
1282
|
+
const legacyShapeCandidate = baseContextAvailable && (legacyStructuralShapeCandidate || legacyHighPrecisionShapeCandidate);
|
|
1283
|
+
const baseIndicatorCandidate = sessionWindowPhase === "active" && executionScore != null && executionScore >= MIN_EXECUTION_SCORE_FOR_AI_GATE && lowTouchCount20 != null && lowTouchCount20 >= MIN_LOW_TOUCH_COUNT20_FOR_AI_GATE;
|
|
1284
|
+
const q4CmcApproval = legacyShapeCandidate && !legacyHighPrecisionShapeCandidate && baseIndicatorCandidate && cmcBtcDominanceChange24hPct != null && cmcBtcDominanceChange24hPct > Q4_CMC_BTC_DOMINANCE_CHANGE_MIN && cmcBtcDominanceChange24hPct <= Q4_CMC_BTC_DOMINANCE_CHANGE_MAX && altDispersion24h != null && altDispersion24h < Q4_ALT_DISPERSION_24H_MAX;
|
|
1285
|
+
const q4DerivativesBadCmcPocket = btcVsAltReturn24h != null && cmc20ToCmc100RatioChange24hPct != null && btcVsAltReturn24h <= Q4_DERIVATIVES_BAD_BTC_VS_ALT_RETURN_24H_MAX && cmc20ToCmc100RatioChange24hPct <= Q4_DERIVATIVES_BAD_CMC20_TO_CMC100_CHANGE_24H_MAX;
|
|
1286
|
+
const q4DerivativesPocket = baseContextAvailable && btcVsAltReturn24h != null && ethCrowdingPersistenceBars != null && solFundingZScore15m != null && btcVsAltReturn24h <= Q4_DERIVATIVES_BTC_VS_ALT_RETURN_24H_MAX && ethCrowdingPersistenceBars >= Q4_DERIVATIVES_ETH_CROWDING_PERSISTENCE_MIN && solFundingZScore15m <= Q4_DERIVATIVES_SOL_FUNDING_Z_SCORE_15M_MAX;
|
|
1287
|
+
const q4DerivativesDirectionSessionOk = q4DerivativesPocket ? signalDirection === "LONG" ? primarySession === "europe" : signalDirection === "SHORT" ? true : false : null;
|
|
1288
|
+
const bnbOiRotationContextAvailable = bnbOiChangePct24h15m != null && bnbOiChangePct1h1h != null;
|
|
1289
|
+
const bnbOiRotationPocket = baseContextAvailable && bnbOiChangePct24h15m != null && bnbOiChangePct1h1h != null && bnbOiChangePct24h15m >= BNB_OI_ROTATION_CHANGE_24H_15M_MIN && bnbOiChangePct1h1h <= BNB_OI_ROTATION_CHANGE_1H_1H_MAX;
|
|
1290
|
+
const strictMomentumRoc1dOk = roc1d == null ? null : roc1d >= STRICT_MOMENTUM_ROC1D_MIN;
|
|
1291
|
+
const bnbOiRotationMomentumOk = bnbOiRotationPocket && strictMomentumRoc1dOk === true;
|
|
1292
|
+
const xrpOiShortNoHtfContextAvailable = xrpOiChangePct1h15m != null && higherTimeframeConflict != null;
|
|
1293
|
+
const xrpOiShortNoHtfPocket = baseContextAvailable && signalDirection === "SHORT" && xrpOiChangePct1h15m != null && xrpOiChangePct1h15m >= XRP_OI_SHORT_NO_HTF_CHANGE_1H_15M_MIN && higherTimeframeConflict === false;
|
|
1294
|
+
const ethVolumeBreadthContextAvailable = ethVsBtcVolumeRatio != null && marketBreadthTop5Dispersion != null && cmcEthDominanceChange24hPct != null;
|
|
1295
|
+
const ethVolumeBreadthCompressionPocket = baseContextAvailable && ethVsBtcVolumeRatio != null && ethVsBtcVolumeRatio <= ETH_VOLUME_BREADTH_ETH_VS_BTC_VOLUME_RATIO_MAX && marketBreadthTop5Dispersion != null && marketBreadthTop5Dispersion <= ETH_VOLUME_BREADTH_TOP5_DISPERSION_MAX && cmcEthDominanceChange24hPct != null && cmcEthDominanceChange24hPct > ETH_VOLUME_BREADTH_ETH_DOMINANCE_CHANGE_MIN;
|
|
1296
|
+
const ethVsBtcVolumeRatioApprovalOk = ethVsBtcVolumeRatio == null ? null : ethVsBtcVolumeRatio >= APPROVAL_ETH_VS_BTC_VOLUME_RATIO_MIN;
|
|
1297
|
+
const protectiveApprovalContextAvailable = benchmarkRelativeStrength4h != null && cmcExchangeLiquidityVolumeChange24hPct != null && bnbReferencePressure != null;
|
|
1298
|
+
const protectiveApprovalContextOk = benchmarkRelativeStrength4h != null && benchmarkRelativeStrength4h <= PROTECTIVE_RELATIVE_STRENGTH_4H_MAX && cmcExchangeLiquidityVolumeChange24hPct != null && cmcExchangeLiquidityVolumeChange24hPct >= PROTECTIVE_CMC_EXCHANGE_LIQUIDITY_CHANGE_24H_MIN && bnbReferencePressure != null && bnbReferencePressure !== PROTECTIVE_BNB_REFERENCE_PRESSURE_BLOCK;
|
|
1299
|
+
const protectiveApprovalSourceAllowed = bnbOiRotationMomentumOk || xrpOiShortNoHtfPocket;
|
|
1300
|
+
const approvalSourceAllowed = protectiveApprovalSourceAllowed;
|
|
1301
|
+
const xrpOiShortNoHtfMissingContext = !bnbOiRotationMomentumOk && signalDirection === "SHORT" && !xrpOiShortNoHtfContextAvailable;
|
|
1302
|
+
const bnbOiRotationMissingContext = !xrpOiShortNoHtfPocket && !bnbOiRotationContextAvailable;
|
|
1303
|
+
const bnbOiRotationOutsideGate = !xrpOiShortNoHtfPocket && bnbOiRotationContextAvailable && !bnbOiRotationPocket;
|
|
1304
|
+
const softBlockReasons = [
|
|
1305
|
+
...bnbOiRotationMissingContext ? ["missing_bnb_oi_rotation_context"] : [],
|
|
1306
|
+
...bnbOiRotationOutsideGate ? ["bnb_oi_rotation_outside_gate"] : [],
|
|
1307
|
+
...bnbOiRotationPocket && strictMomentumRoc1dOk === false ? ["bnb_oi_rotation_roc1d_below_gate"] : [],
|
|
1308
|
+
...bnbOiRotationPocket && strictMomentumRoc1dOk == null ? ["missing_roc1d_for_bnb_oi_rotation"] : [],
|
|
1309
|
+
...xrpOiShortNoHtfMissingContext ? ["missing_xrp_oi_short_no_htf_context"] : [],
|
|
1310
|
+
...xrpOiChangePct1h15m != null && xrpOiChangePct1h15m < XRP_OI_SHORT_NO_HTF_CHANGE_1H_15M_MIN ? ["xrp_oi_short_no_htf_outside_gate"] : [],
|
|
1311
|
+
...xrpOiChangePct1h15m != null && xrpOiChangePct1h15m >= XRP_OI_SHORT_NO_HTF_CHANGE_1H_15M_MIN && signalDirection !== "SHORT" ? ["xrp_oi_short_no_htf_requires_short_signal"] : [],
|
|
1312
|
+
...xrpOiChangePct1h15m != null && xrpOiChangePct1h15m >= XRP_OI_SHORT_NO_HTF_CHANGE_1H_15M_MIN && signalDirection === "SHORT" && higherTimeframeConflict !== false ? ["xrp_oi_short_no_htf_higher_timeframe_conflict"] : [],
|
|
1313
|
+
...protectiveApprovalSourceAllowed && benchmarkRelativeStrength4h == null ? ["missing_relative_strength_4h_for_protective_gate"] : [],
|
|
1314
|
+
...protectiveApprovalSourceAllowed && benchmarkRelativeStrength4h != null && benchmarkRelativeStrength4h > PROTECTIVE_RELATIVE_STRENGTH_4H_MAX ? ["relative_strength_4h_above_protective_gate"] : [],
|
|
1315
|
+
...protectiveApprovalSourceAllowed && cmcExchangeLiquidityVolumeChange24hPct == null ? ["missing_cmc_exchange_liquidity_change_for_protective_gate"] : [],
|
|
1316
|
+
...protectiveApprovalSourceAllowed && cmcExchangeLiquidityVolumeChange24hPct != null && cmcExchangeLiquidityVolumeChange24hPct < PROTECTIVE_CMC_EXCHANGE_LIQUIDITY_CHANGE_24H_MIN ? ["cmc_exchange_liquidity_change_below_protective_gate"] : [],
|
|
1317
|
+
...protectiveApprovalSourceAllowed && bnbReferencePressure == null ? ["missing_bnb_reference_pressure_for_protective_gate"] : [],
|
|
1318
|
+
...protectiveApprovalSourceAllowed && bnbReferencePressure === PROTECTIVE_BNB_REFERENCE_PRESSURE_BLOCK ? ["bnb_reference_pressure_crowded_long_protective_gate"] : [],
|
|
1319
|
+
...protectiveApprovalSourceAllowed && ethVsBtcVolumeRatio == null ? ["missing_eth_vs_btc_volume_ratio_for_approval"] : [],
|
|
1320
|
+
...protectiveApprovalSourceAllowed && ethVsBtcVolumeRatio != null && ethVsBtcVolumeRatio < APPROVAL_ETH_VS_BTC_VOLUME_RATIO_MIN ? ["eth_vs_btc_volume_ratio_below_approval_gate"] : [],
|
|
1321
|
+
...ethVolumeBreadthCompressionPocket ? ["eth_volume_breadth_observation_only"] : [],
|
|
1322
|
+
...legacyShapeCandidate && sessionWindowPhase !== "active" ? ["inactive_session_window"] : [],
|
|
1323
|
+
...legacyShapeCandidate && (executionScore == null || executionScore < MIN_EXECUTION_SCORE_FOR_AI_GATE) ? ["low_or_missing_execution_score"] : [],
|
|
1324
|
+
...legacyShapeCandidate && (lowTouchCount20 == null || lowTouchCount20 < MIN_LOW_TOUCH_COUNT20_FOR_AI_GATE) ? ["low_touch_count_below_gate"] : [],
|
|
1325
|
+
...legacyHighPrecisionShapeCandidate && volumeStructureAligned !== true ? ["high_precision_volume_structure_not_aligned"] : [],
|
|
1326
|
+
...legacyHighPrecisionShapeCandidate && benchmarkConflict !== false ? ["high_precision_benchmark_conflict_or_missing"] : [],
|
|
1327
|
+
...legacyHighPrecisionShapeCandidate && cmcAltVolumeChange24hPct == null ? ["missing_cmc_alt_volume_change"] : [],
|
|
1328
|
+
...legacyHighPrecisionShapeCandidate && cmcAltVolumeChange24hPct != null && cmcAltVolumeChange24hPct > HIGH_PRECISION_CMC_ALT_VOLUME_CHANGE_MAX ? ["cmc_alt_volume_change_too_hot"] : [],
|
|
1329
|
+
...legacyShapeCandidate && !legacyHighPrecisionShapeCandidate && cmcBtcDominanceChange24hPct == null ? ["missing_cmc_btc_dominance_change"] : [],
|
|
1330
|
+
...legacyShapeCandidate && !legacyHighPrecisionShapeCandidate && cmcBtcDominanceChange24hPct != null && (cmcBtcDominanceChange24hPct <= Q4_CMC_BTC_DOMINANCE_CHANGE_MIN || cmcBtcDominanceChange24hPct > Q4_CMC_BTC_DOMINANCE_CHANGE_MAX) ? ["cmc_btc_dominance_change_outside_band"] : [],
|
|
1331
|
+
...legacyShapeCandidate && !legacyHighPrecisionShapeCandidate && cmcBtcDominanceChange24hPct != null && cmcBtcDominanceChange24hPct > Q4_CMC_BTC_DOMINANCE_CHANGE_MIN && cmcBtcDominanceChange24hPct <= Q4_CMC_BTC_DOMINANCE_CHANGE_MAX && altDispersion24h == null ? ["missing_alt_dispersion_24h_for_q4"] : [],
|
|
1332
|
+
...legacyShapeCandidate && !legacyHighPrecisionShapeCandidate && altDispersion24h != null && altDispersion24h >= Q4_ALT_DISPERSION_24H_MAX ? ["alt_dispersion_24h_too_high_for_q4"] : [],
|
|
1333
|
+
...q4CmcApproval ? ["legacy_q4_structural_observation_only"] : [],
|
|
1334
|
+
...q4DerivativesPocket && signalDirection === "LONG" && q4DerivativesDirectionSessionOk === false ? ["long_q4_derivatives_outside_europe_session"] : [],
|
|
1335
|
+
...q4DerivativesPocket && signalDirection == null ? ["missing_signal_direction_for_q4_derivatives"] : []
|
|
1336
|
+
];
|
|
1337
|
+
const highPrecisionApprovalBlocked = legacyHighPrecisionShapeCandidate && !bnbOiRotationMomentumOk && !xrpOiShortNoHtfPocket;
|
|
1338
|
+
const q4DerivativesApprovalBlocked = q4DerivativesPocket && !bnbOiRotationMomentumOk && !xrpOiShortNoHtfPocket;
|
|
1339
|
+
const q4ApprovalBlocked = legacyShapeCandidate && !legacyHighPrecisionShapeCandidate && !bnbOiRotationMomentumOk && !xrpOiShortNoHtfPocket;
|
|
1340
|
+
const protectiveApprovalBlocked = protectiveApprovalSourceAllowed && !protectiveApprovalContextOk;
|
|
1341
|
+
const approvalLiquidityBlocked = protectiveApprovalSourceAllowed && ethVsBtcVolumeRatioApprovalOk !== true;
|
|
1342
|
+
const approvalBlocked = !approvalSourceAllowed && (highPrecisionApprovalBlocked || q4ApprovalBlocked || q4DerivativesApprovalBlocked);
|
|
1343
|
+
const approvalSourcePassesOwnGuards = protectiveApprovalSourceAllowed && protectiveApprovalContextOk && ethVsBtcVolumeRatioApprovalOk === true;
|
|
1344
|
+
const defaultApprovalAllowed = structuralHardBlockReasons.length === 0 && approvalSourcePassesOwnGuards && !approvalBlocked;
|
|
1345
|
+
const bnbOiRotationBlocked = bnbOiRotationPocket && (structuralHardBlockReasons.length > 0 || !bnbOiRotationMomentumOk || protectiveApprovalBlocked || approvalLiquidityBlocked);
|
|
1346
|
+
const xrpOiShortNoHtfBlocked = xrpOiShortNoHtfPocket && (structuralHardBlockReasons.length > 0 || protectiveApprovalBlocked || approvalLiquidityBlocked);
|
|
1347
|
+
const ethVolumeBreadthCompressionBlocked = ethVolumeBreadthCompressionPocket;
|
|
1348
|
+
const strictMomentumBlockReasons = [];
|
|
1349
|
+
const strictMomentumApprovalAllowedNow = bnbOiRotationMomentumOk && structuralHardBlockReasons.length === 0 && protectiveApprovalContextOk && strictMomentumRoc1dOk === true && ethVsBtcVolumeRatioApprovalOk === true;
|
|
1350
|
+
const approvalAllowedNow = defaultApprovalAllowed;
|
|
1351
|
+
const setupFeatures = buildDoubleTapSetupFeatures({
|
|
1352
|
+
context,
|
|
1353
|
+
interval: payload.signal?.interval,
|
|
1354
|
+
signalTimestamp: payload.signal?.timestamp
|
|
1355
|
+
});
|
|
1356
|
+
const doubleTapGateFeatures = buildDoubleTapGateFeatures({
|
|
1357
|
+
setupFeatures,
|
|
1358
|
+
signalDirection,
|
|
1359
|
+
height,
|
|
1360
|
+
breakoutDistancePct,
|
|
1361
|
+
trendAligned,
|
|
1362
|
+
benchmarkAligned,
|
|
1363
|
+
volumeRel20,
|
|
1364
|
+
derivativesDirectionAligned,
|
|
1365
|
+
derivativesRiskFlags,
|
|
1366
|
+
venueSpreadZScore,
|
|
1367
|
+
directionalCrowding,
|
|
1368
|
+
approvalPocket: legacyShapeCandidate,
|
|
1369
|
+
bnbOiRotationPocket,
|
|
1370
|
+
bnbOiRotationBlocked,
|
|
1371
|
+
xrpOiShortNoHtfPocket,
|
|
1372
|
+
xrpOiShortNoHtfBlocked,
|
|
1373
|
+
ethVolumeBreadthCompressionPocket,
|
|
1374
|
+
ethVolumeBreadthCompressionBlocked,
|
|
1375
|
+
highPrecisionPocket: legacyHighPrecisionShapeCandidate,
|
|
1376
|
+
highPrecisionApprovalBlocked,
|
|
1377
|
+
q4DerivativesPocket,
|
|
1378
|
+
q4DerivativesApprovalBlocked,
|
|
1379
|
+
q4ApprovalBlocked,
|
|
1380
|
+
defaultApprovalAllowed,
|
|
1381
|
+
q4AltDispersionOk: altDispersion24h == null ? null : altDispersion24h < Q4_ALT_DISPERSION_24H_MAX,
|
|
1382
|
+
q4DerivativesCmcRiskOk: btcVsAltReturn24h == null || cmc20ToCmc100RatioChange24hPct == null ? null : !q4DerivativesBadCmcPocket,
|
|
1383
|
+
q4DerivativesDirectionSessionOk,
|
|
1384
|
+
bnbOiRotationContextAvailable,
|
|
1385
|
+
bnbOiRotationMomentumOk,
|
|
1386
|
+
xrpOiShortNoHtfContextAvailable,
|
|
1387
|
+
ethVolumeBreadthContextAvailable,
|
|
1388
|
+
ethVsBtcVolumeRatioApprovalOk,
|
|
1389
|
+
strictMomentumApproved: strictMomentumApprovalAllowedNow,
|
|
1390
|
+
strictMomentumRoc1dOk,
|
|
1391
|
+
protectiveApprovalContextAvailable,
|
|
1392
|
+
protectiveApprovalContextOk
|
|
1393
|
+
});
|
|
1394
|
+
const deterministicQuality = structuralHardBlockReasons.length > 0 ? Math.min(geometryQuality, 2) : approvalSourcePassesOwnGuards ? 4 : Math.min(geometryQuality, 3);
|
|
1395
|
+
return {
|
|
1396
|
+
...context,
|
|
1397
|
+
baseContextAvailable,
|
|
1398
|
+
primarySession,
|
|
1399
|
+
sessionWindowPhase,
|
|
1400
|
+
trendBias,
|
|
1401
|
+
swingBias,
|
|
1402
|
+
breakoutState,
|
|
1403
|
+
barsSinceBreakout,
|
|
1404
|
+
lowTouchCount20,
|
|
1405
|
+
volumeRel20,
|
|
1406
|
+
benchmarkTrendAlignment,
|
|
1407
|
+
benchmarkRelativeStrength4h,
|
|
1408
|
+
benchmarkBias,
|
|
1409
|
+
derivativesDirectionAligned,
|
|
1410
|
+
derivativesRiskFlags,
|
|
1411
|
+
bodyStrength,
|
|
1412
|
+
roc1d,
|
|
1413
|
+
venueSpreadZScore,
|
|
1414
|
+
rewardToVolatility,
|
|
1415
|
+
executionScore,
|
|
1416
|
+
volumeStructureAligned,
|
|
1417
|
+
benchmarkConflict,
|
|
1418
|
+
cmcAltVolumeChange24hPct,
|
|
1419
|
+
cmcExchangeLiquidityVolumeChange24hPct,
|
|
1420
|
+
cmcBtcDominanceChange24hPct,
|
|
1421
|
+
cmcEthDominanceChange24hPct,
|
|
1422
|
+
cmc20ToCmc100RatioChange24hPct,
|
|
1423
|
+
ethVsBtcVolumeRatio,
|
|
1424
|
+
marketBreadthTop5Dispersion,
|
|
1425
|
+
btcVsAltReturn24h,
|
|
1426
|
+
ethCrowdingPersistenceBars,
|
|
1427
|
+
solFundingZScore15m,
|
|
1428
|
+
bnbOiChangePct24h15m,
|
|
1429
|
+
bnbOiChangePct1h1h,
|
|
1430
|
+
bnbReferencePressure,
|
|
1431
|
+
xrpOiChangePct1h15m,
|
|
1432
|
+
ethVolumeBreadthContextAvailable,
|
|
1433
|
+
ethVolumeBreadthCompressionPocket,
|
|
1434
|
+
ethVsBtcVolumeRatioApprovalOk,
|
|
1435
|
+
higherTimeframeConflict,
|
|
1436
|
+
altDispersion24h,
|
|
1437
|
+
q4DerivativesDirectionSessionOk,
|
|
1438
|
+
doubleTapGateFeatures,
|
|
1439
|
+
structuralHardBlockReasons,
|
|
1440
|
+
softBlockReasons,
|
|
1441
|
+
strictMomentumBlockReasons,
|
|
1442
|
+
deterministicQuality,
|
|
1443
|
+
approvalAllowedNow: deterministicQuality >= 4 && approvalAllowedNow,
|
|
1444
|
+
protectiveApprovalContextAvailable,
|
|
1445
|
+
protectiveApprovalContextOk,
|
|
1446
|
+
strictMomentumApprovalAllowedNow,
|
|
1447
|
+
maxAllowedQuality: deterministicQuality
|
|
1448
|
+
};
|
|
1449
|
+
};
|
|
1450
|
+
var withDoubleTapGateFeatures = ({
|
|
1451
|
+
baseContext,
|
|
1452
|
+
context
|
|
1453
|
+
}) => baseContext == null ? baseContext : {
|
|
1454
|
+
...baseContext,
|
|
1455
|
+
doubleTapGateFeatures: context.doubleTapGateFeatures
|
|
1456
|
+
};
|
|
1457
|
+
var doubleTapBaseAiAdapter = {
|
|
1458
|
+
buildPayload: ({ signal, basePayload }) => {
|
|
1459
|
+
const baseAdditional = basePayload.additionalIndicators ?? {};
|
|
1460
|
+
const payload = {
|
|
1461
|
+
...basePayload,
|
|
1462
|
+
additionalIndicators: {
|
|
1463
|
+
...baseAdditional,
|
|
1464
|
+
doubleTapContext: signal.additionalIndicators?.doubleTapContext
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
const context = buildDoubleTapAiContext(payload);
|
|
1468
|
+
const baseContext = baseAdditional.baseContext ?? null;
|
|
1469
|
+
return {
|
|
1470
|
+
...payload,
|
|
1471
|
+
additionalIndicators: {
|
|
1472
|
+
...payload.additionalIndicators,
|
|
1473
|
+
baseContext: withDoubleTapGateFeatures({
|
|
1474
|
+
baseContext,
|
|
1475
|
+
context
|
|
1476
|
+
}),
|
|
1477
|
+
doubleTapContext: context
|
|
1478
|
+
}
|
|
1479
|
+
};
|
|
1480
|
+
},
|
|
1481
|
+
postProcessAnalysis: ({ payload, analysis }) => {
|
|
1482
|
+
const context = buildDoubleTapAiContext(payload);
|
|
1483
|
+
const direction = analysis.direction === "LONG" || analysis.direction === "SHORT" ? analysis.direction : context.signalDirection;
|
|
1484
|
+
const quality = context.deterministicQuality;
|
|
1485
|
+
const approved = context.approvalAllowedNow && Boolean(direction);
|
|
1486
|
+
return {
|
|
1487
|
+
...analysis,
|
|
1488
|
+
direction: approved ? direction ?? null : null,
|
|
1489
|
+
quality,
|
|
1490
|
+
qualityReason: approved ? analysis.qualityReason : "DoubleTap breakout is not in a strict baseContext-supported approval pocket."
|
|
1491
|
+
};
|
|
1492
|
+
},
|
|
1493
|
+
buildHumanPromptAddon: ({ payload }) => {
|
|
1494
|
+
const context = buildDoubleTapAiContext(payload);
|
|
1495
|
+
return `
|
|
1496
|
+
Additional DoubleTap context:
|
|
1497
|
+
- patternKind=${context.patternKind ?? "n/a"}
|
|
1498
|
+
- signalDirection=${context.signalDirection ?? "n/a"}
|
|
1499
|
+
- neckline=${String(context.neckline ?? "n/a")}
|
|
1500
|
+
- targetPrice=${String(context.targetPrice ?? "n/a")}
|
|
1501
|
+
- stopLossPrice=${String(context.stopLossPrice ?? "n/a")}
|
|
1502
|
+
- height=${String(context.height ?? "n/a")}
|
|
1503
|
+
- pivotTolerancePct=${String(context.pivotTolerancePct ?? "n/a")}
|
|
1504
|
+
- breakoutDistancePct=${String(context.breakoutDistancePct ?? "n/a")}
|
|
1505
|
+
- currentPrice=${String(context.currentPrice ?? "n/a")}
|
|
1506
|
+
- baseContextAvailable=${String(context.baseContextAvailable)}
|
|
1507
|
+
- primarySession=${context.primarySession ?? "n/a"}
|
|
1508
|
+
- sessionWindowPhase=${context.sessionWindowPhase ?? "n/a"}
|
|
1509
|
+
- trendBias=${context.trendBias ?? "n/a"}
|
|
1510
|
+
- swingBias=${context.swingBias ?? "n/a"}
|
|
1511
|
+
- breakoutState=${context.breakoutState ?? "n/a"}
|
|
1512
|
+
- barsSinceBreakout=${String(context.barsSinceBreakout ?? "n/a")}
|
|
1513
|
+
- lowTouchCount20=${String(context.lowTouchCount20 ?? "n/a")}
|
|
1514
|
+
- volumeRel20=${String(context.volumeRel20 ?? "n/a")}
|
|
1515
|
+
- benchmarkTrendAlignment=${context.benchmarkTrendAlignment ?? "n/a"}
|
|
1516
|
+
- benchmarkRelativeStrength4h=${String(context.benchmarkRelativeStrength4h ?? "n/a")}
|
|
1517
|
+
- derivativesDirectionAligned=${String(context.derivativesDirectionAligned ?? "n/a")}
|
|
1518
|
+
- derivativesRiskFlags=${JSON.stringify(context.derivativesRiskFlags)}
|
|
1519
|
+
- bodyStrength=${String(context.bodyStrength ?? "n/a")}
|
|
1520
|
+
- roc1d=${String(context.roc1d ?? "n/a")}
|
|
1521
|
+
- venueSpreadZScore=${String(context.venueSpreadZScore ?? "n/a")}
|
|
1522
|
+
- rewardToVolatility=${String(context.rewardToVolatility ?? "n/a")}
|
|
1523
|
+
- executionScore=${String(context.executionScore ?? "n/a")}
|
|
1524
|
+
- volumeStructureAligned=${String(context.volumeStructureAligned ?? "n/a")}
|
|
1525
|
+
- benchmarkConflict=${String(context.benchmarkConflict ?? "n/a")}
|
|
1526
|
+
- cmcAltVolumeChange24hPct=${String(context.cmcAltVolumeChange24hPct ?? "n/a")}
|
|
1527
|
+
- cmcExchangeLiquidityVolumeChange24hPct=${String(context.cmcExchangeLiquidityVolumeChange24hPct ?? "n/a")}
|
|
1528
|
+
- cmcBtcDominanceChange24hPct=${String(context.cmcBtcDominanceChange24hPct ?? "n/a")}
|
|
1529
|
+
- cmcEthDominanceChange24hPct=${String(context.cmcEthDominanceChange24hPct ?? "n/a")}
|
|
1530
|
+
- cmc20ToCmc100RatioChange24hPct=${String(context.cmc20ToCmc100RatioChange24hPct ?? "n/a")}
|
|
1531
|
+
- ethVsBtcVolumeRatio=${String(context.ethVsBtcVolumeRatio ?? "n/a")}
|
|
1532
|
+
- marketBreadthTop5Dispersion=${String(context.marketBreadthTop5Dispersion ?? "n/a")}
|
|
1533
|
+
- btcVsAltReturn24h=${String(context.btcVsAltReturn24h ?? "n/a")}
|
|
1534
|
+
- ethCrowdingPersistenceBars=${String(context.ethCrowdingPersistenceBars ?? "n/a")}
|
|
1535
|
+
- solFundingZScore15m=${String(context.solFundingZScore15m ?? "n/a")}
|
|
1536
|
+
- bnbOiChangePct24h15m=${String(context.bnbOiChangePct24h15m ?? "n/a")}
|
|
1537
|
+
- bnbOiChangePct1h1h=${String(context.bnbOiChangePct1h1h ?? "n/a")}
|
|
1538
|
+
- bnbReferencePressure=${context.bnbReferencePressure ?? "n/a"}
|
|
1539
|
+
- xrpOiChangePct1h15m=${String(context.xrpOiChangePct1h15m ?? "n/a")}
|
|
1540
|
+
- ethVolumeBreadthContextAvailable=${String(context.ethVolumeBreadthContextAvailable)}
|
|
1541
|
+
- ethVolumeBreadthCompressionPocket=${String(context.ethVolumeBreadthCompressionPocket)}
|
|
1542
|
+
- higherTimeframeConflict=${String(context.higherTimeframeConflict ?? "n/a")}
|
|
1543
|
+
- altDispersion24h=${String(context.altDispersion24h ?? "n/a")}
|
|
1544
|
+
- doubleTapGatePatternGeometry=${context.doubleTapGateFeatures.patternGeometry}
|
|
1545
|
+
- doubleTapGateNecklineBreakout=${context.doubleTapGateFeatures.necklineBreakout}
|
|
1546
|
+
- doubleTapGateTrendContext=${context.doubleTapGateFeatures.trendContext}
|
|
1547
|
+
- doubleTapGateParticipationState=${context.doubleTapGateFeatures.participationState}
|
|
1548
|
+
- doubleTapGateDerivativesState=${context.doubleTapGateFeatures.derivativesState}
|
|
1549
|
+
- doubleTapGateExecutionSpreadState=${context.doubleTapGateFeatures.executionSpreadState}
|
|
1550
|
+
- doubleTapGateGeometry=${JSON.stringify(context.doubleTapGateFeatures.geometry)}
|
|
1551
|
+
- doubleTapGatePath=${JSON.stringify(context.doubleTapGateFeatures.path)}
|
|
1552
|
+
- doubleTapGateApprovalPocket=${context.doubleTapGateFeatures.approvalPocket}
|
|
1553
|
+
- doubleTapGateHighQualityCadencePocket=${String(context.doubleTapGateFeatures.highQualityCadencePocket)}
|
|
1554
|
+
- doubleTapGateDefaultApprovalAllowed=${String(context.doubleTapGateFeatures.defaultApprovalAllowed)}
|
|
1555
|
+
- doubleTapGateQ4AltDispersionOk=${String(context.doubleTapGateFeatures.q4AltDispersionOk ?? "n/a")}
|
|
1556
|
+
- doubleTapGateQ4DerivativesPocket=${String(context.doubleTapGateFeatures.q4DerivativesPocket)}
|
|
1557
|
+
- doubleTapGateQ4DerivativesCmcRiskOk=${String(context.doubleTapGateFeatures.q4DerivativesCmcRiskOk ?? "n/a")}
|
|
1558
|
+
- doubleTapGateQ4DerivativesDirectionSessionOk=${String(context.doubleTapGateFeatures.q4DerivativesDirectionSessionOk ?? "n/a")}
|
|
1559
|
+
- doubleTapGateBnbOiRotationPocket=${String(context.doubleTapGateFeatures.bnbOiRotationPocket)}
|
|
1560
|
+
- doubleTapGateBnbOiRotationContextAvailable=${String(context.doubleTapGateFeatures.bnbOiRotationContextAvailable)}
|
|
1561
|
+
- doubleTapGateBnbOiRotationMomentumOk=${String(context.doubleTapGateFeatures.bnbOiRotationMomentumOk)}
|
|
1562
|
+
- doubleTapGateXrpOiShortNoHtfPocket=${String(context.doubleTapGateFeatures.xrpOiShortNoHtfPocket)}
|
|
1563
|
+
- doubleTapGateXrpOiShortNoHtfContextAvailable=${String(context.doubleTapGateFeatures.xrpOiShortNoHtfContextAvailable)}
|
|
1564
|
+
- doubleTapGateEthVolumeBreadthContextAvailable=${String(context.doubleTapGateFeatures.ethVolumeBreadthContextAvailable)}
|
|
1565
|
+
- doubleTapGateEthVolumeBreadthCompressionPocket=${String(context.doubleTapGateFeatures.ethVolumeBreadthCompressionPocket)}
|
|
1566
|
+
- doubleTapGateEthVsBtcVolumeRatioApprovalOk=${String(context.doubleTapGateFeatures.ethVsBtcVolumeRatioApprovalOk ?? "n/a")}
|
|
1567
|
+
- doubleTapGateStrictMomentumApproved=${String(context.doubleTapGateFeatures.strictMomentumApproved)}
|
|
1568
|
+
- doubleTapGateStrictMomentumRoc1dOk=${String(context.doubleTapGateFeatures.strictMomentumRoc1dOk ?? "n/a")}
|
|
1569
|
+
- doubleTapGateProtectiveApprovalContextAvailable=${String(context.doubleTapGateFeatures.protectiveApprovalContextAvailable)}
|
|
1570
|
+
- doubleTapGateProtectiveApprovalContextOk=${String(context.doubleTapGateFeatures.protectiveApprovalContextOk)}
|
|
1571
|
+
- deterministicQuality=${String(context.deterministicQuality)}
|
|
1572
|
+
- approvalAllowedNow=${String(context.approvalAllowedNow)}
|
|
1573
|
+
- protectiveApprovalContextAvailable=${String(context.protectiveApprovalContextAvailable)}
|
|
1574
|
+
- protectiveApprovalContextOk=${String(context.protectiveApprovalContextOk)}
|
|
1575
|
+
- strictMomentumApprovalAllowedNow=${String(context.strictMomentumApprovalAllowedNow)}
|
|
1576
|
+
- structuralHardBlockReasons=${JSON.stringify(context.structuralHardBlockReasons)}
|
|
1577
|
+
- softBlockReasons=${JSON.stringify(context.softBlockReasons)}
|
|
1578
|
+
- strictMomentumBlockReasons=${JSON.stringify(context.strictMomentumBlockReasons)}
|
|
1579
|
+
- pivots=${JSON.stringify(context.pivots ?? [])}
|
|
1580
|
+
|
|
1581
|
+
Interpretation rules for DoubleTap:
|
|
1582
|
+
- This strategy enters only after a confirmed neckline break of a double bottom or double top.
|
|
1583
|
+
- Prefer compact breaks close to the neckline; late/extended breaks should be downgraded.
|
|
1584
|
+
- Extremely tiny breaks can still be early noise; live approval needs support from baseContext.
|
|
1585
|
+
- Treat deterministicQuality and approvalAllowedNow as the normalized local gate result.
|
|
1586
|
+
- Local q4 approval comes from BNB reference OI rotation with ROC1D confirmation or an XRP OI short/no-HTF-conflict pocket.
|
|
1587
|
+
- BNB approval requires BNB 15m oiChangePct24h >= 0.65, BNB 1h oiChangePct1h <= -0.28, roc1d >= -5.25, and ethVsBtcVolumeRatio >= 0.34.
|
|
1588
|
+
- XRP approval requires a SHORT signal, XRP 15m oiChangePct1h >= 0.32, higherTimeframeConflict=false, and ethVsBtcVolumeRatio >= 0.34.
|
|
1589
|
+
- The BNB/XRP protective gate requires benchmark relativeStrength4h <= 7.77009, CMC exchange liquidity volume change >= -0.378662, and BNB reference pressure not crowded_long.
|
|
1590
|
+
- The ETH volume/breadth pocket requires ethVsBtcVolumeRatio <= 0.39, top5 market-breadth dispersion <= 0.0007, and CMC ETH dominance change > -0.05; it is retained only as observation context and should not be treated as local approval.
|
|
1591
|
+
- Legacy high-precision CMC, structural q4 CMC, and old BTC/ETH/SOL derivatives pockets are retained only as observation context and should not be treated as local approval.
|
|
1592
|
+
- A good long has two comparable lows and a clean close above the neckline.
|
|
1593
|
+
- A good short has two comparable highs and a clean close below the neckline.
|
|
1594
|
+
- Venue spread, trend bias, body strength, and reward-to-volatility are diagnostics for this gate, not strict local blockers.
|
|
1595
|
+
`.trim();
|
|
1596
|
+
},
|
|
1597
|
+
mapEntryRuntimeFromConfig: (config2) => mapAiRuntimeFromConfig(
|
|
1598
|
+
config2
|
|
1599
|
+
)
|
|
1600
|
+
};
|
|
1601
|
+
var doubleTapAiAdapter = withStrategyLocalAiGate(
|
|
1602
|
+
doubleTapBaseAiAdapter,
|
|
1603
|
+
{
|
|
1604
|
+
id: "double_tap_direction_aware_release_candidate",
|
|
1605
|
+
approves: ({ signal, payload }) => {
|
|
1606
|
+
const altDispersion24h = getAiPayloadNumber(
|
|
1607
|
+
payload,
|
|
1608
|
+
"additionalIndicators.doubleTapContext.altDispersion24h"
|
|
1609
|
+
);
|
|
1610
|
+
const pricePositionInChannel = getAiPayloadNumber(
|
|
1611
|
+
payload,
|
|
1612
|
+
"additionalIndicators.baseContext.regime.trend.adaptiveChannel.pricePositionInChannel"
|
|
1613
|
+
);
|
|
1614
|
+
const barsSinceSwingHigh = getAiPayloadNumber(
|
|
1615
|
+
payload,
|
|
1616
|
+
"additionalIndicators.baseContext.structure.pivots.barsSinceSwingHigh"
|
|
1617
|
+
);
|
|
1618
|
+
return signal.direction === "LONG" && altDispersion24h != null && altDispersion24h >= 0.0285 && pricePositionInChannel != null && pricePositionInChannel >= 0.757 || signal.direction === "SHORT" && barsSinceSwingHigh != null && barsSinceSwingHigh <= 47;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
);
|
|
1622
|
+
|
|
1623
|
+
// src/DoubleTap/manifest.ts
|
|
1624
|
+
var doubleTapManifest = {
|
|
1625
|
+
name: "DoubleTap",
|
|
1626
|
+
aiAdapter: doubleTapAiAdapter
|
|
1627
|
+
};
|
|
1628
|
+
|
|
1629
|
+
// src/DoubleTap/strategy.ts
|
|
1630
|
+
var DoubleTapStrategyDefinition = {
|
|
1631
|
+
defaults: config,
|
|
1632
|
+
createCore: createDoubleTapCore,
|
|
1633
|
+
manifest: doubleTapManifest
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
// src/index.ts
|
|
1637
|
+
var strategyEntries = [
|
|
1638
|
+
DoubleTapStrategyDefinition
|
|
1639
|
+
];
|
|
1640
|
+
var defaultConfigs = {
|
|
1641
|
+
DoubleTap: config
|
|
1642
|
+
};
|
|
1643
|
+
var getBuiltInStrategyDefaultConfig = (strategyName) => defaultConfigs[strategyName];
|
|
1644
|
+
var index_default = defineStrategyPlugin({ strategyEntries });
|
|
1645
|
+
export {
|
|
1646
|
+
DoubleTapStrategyDefinition,
|
|
1647
|
+
index_default as default,
|
|
1648
|
+
doubleTapAiAdapter,
|
|
1649
|
+
config as doubleTapDefaultConfig,
|
|
1650
|
+
doubleTapManifest,
|
|
1651
|
+
getBuiltInStrategyDefaultConfig,
|
|
1652
|
+
strategyEntries
|
|
1653
|
+
};
|