@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,547 @@
|
|
|
1
|
+
// src/AdaptiveMomentumRibbon/adapters/ai.ts
|
|
2
|
+
import { mapAiRuntimeFromConfig } from "@tradejs/core/strategies";
|
|
3
|
+
var ADAPTIVE_MOMENTUM_RIBBON_CONTEXT_PROMPT = `
|
|
4
|
+
AdaptiveMomentumRibbon addon:
|
|
5
|
+
- This is a momentum entry based on an oscillator zero-cross, not a trendline breakout and not a line-reversal setup.
|
|
6
|
+
- LONG appears when \`signalOsc\` crosses above 0 and the ribbon switches into \`activeBuy\`; SHORT is the mirror case.
|
|
7
|
+
- \`invalidationLevel\` is the structural invalidation level on the signal bar. If \`invalidated=true\` or \`invalidationLevel\` sits on the wrong side of the current price, do not treat the setup as confirmed.
|
|
8
|
+
- \`channelState\` and \`channelBiasAligned\` describe where price sits relative to the Keltner Channel. For LONG it is a negative sign if price is still below \`kcMidline\`; for SHORT it is a negative sign if price is above \`kcMidline\`.
|
|
9
|
+
- \`invalidationDistancePct\` and \`structuralRewardRiskRatio\` describe how compact the structure is. Do not overstate quality when invalidation is too wide or reward/risk versus invalidation is weak.
|
|
10
|
+
- \`quality=5\` requires very clean momentum: correct channel side, strong \`signalOsc\`, sane invalidation distance, and no coin/BTC bias conflicts.
|
|
11
|
+
- If \`approvalAllowedNow=false\` or \`deterministicQuality<4\`, this is usually watch mode rather than a ready live approval.
|
|
12
|
+
`;
|
|
13
|
+
var ADAPTIVE_MOMENTUM_RIBBON_PAYLOAD_PROMPT = `
|
|
14
|
+
- \`payload.additionalIndicators.adaptiveMomentumRibbonContext\` contains a compact signal summary:
|
|
15
|
+
signalOsc / oscillatorStrength / channelState / channelExtensionPct / invalidationDistancePct / structuralRewardRiskRatio / coinBiasAligned / btcBiasAligned / deterministicQuality / approvalAllowedNow / structuralHardBlockReasons.
|
|
16
|
+
- Use this context as the primary strategy-specific interpretation instead of re-deriving it only from generic series.
|
|
17
|
+
`;
|
|
18
|
+
var toFiniteNumberOrNull = (value) => {
|
|
19
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
if (typeof value === "string" && value.trim()) {
|
|
23
|
+
const parsed = Number(value);
|
|
24
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
};
|
|
28
|
+
var getLastFiniteNumber = (value) => {
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
for (let i = value.length - 1; i >= 0; i -= 1) {
|
|
31
|
+
const item = toFiniteNumberOrNull(value[i]);
|
|
32
|
+
if (item != null) {
|
|
33
|
+
return item;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return toFiniteNumberOrNull(value);
|
|
39
|
+
};
|
|
40
|
+
var getBias = (fast, slow) => {
|
|
41
|
+
if (fast == null || slow == null) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (fast > slow) {
|
|
45
|
+
return "bullish";
|
|
46
|
+
}
|
|
47
|
+
if (fast < slow) {
|
|
48
|
+
return "bearish";
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
};
|
|
52
|
+
var getSignalDirection = (signal) => signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
|
|
53
|
+
var asBoolean = (value) => value === true || value === 1;
|
|
54
|
+
var getRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
55
|
+
var getPrimarySession = (signal, additionalIndicators) => {
|
|
56
|
+
const marketContext = getRecord(
|
|
57
|
+
additionalIndicators?.marketContext ?? signal.additionalIndicators?.marketContext
|
|
58
|
+
);
|
|
59
|
+
const tradingSession = getRecord(marketContext?.tradingSession);
|
|
60
|
+
const primarySession = tradingSession?.primarySession;
|
|
61
|
+
if (primarySession === "asia" || primarySession === "europe" || primarySession === "us" || primarySession === "off_hours") {
|
|
62
|
+
return primarySession;
|
|
63
|
+
}
|
|
64
|
+
const timestamp = toFiniteNumberOrNull(signal.timestamp);
|
|
65
|
+
if (timestamp == null) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const date = new Date(timestamp);
|
|
69
|
+
const minuteUtc = date.getUTCHours() * 60 + date.getUTCMinutes();
|
|
70
|
+
const activeSessions = [
|
|
71
|
+
minuteUtc >= 0 && minuteUtc < 8 * 60 ? "asia" : null,
|
|
72
|
+
minuteUtc >= 7 * 60 && minuteUtc < 16 * 60 ? "europe" : null,
|
|
73
|
+
minuteUtc >= 13 * 60 && minuteUtc < 22 * 60 ? "us" : null
|
|
74
|
+
].filter(
|
|
75
|
+
(session) => session != null
|
|
76
|
+
);
|
|
77
|
+
if (activeSessions.includes("us")) {
|
|
78
|
+
return "us";
|
|
79
|
+
}
|
|
80
|
+
if (activeSessions.includes("europe")) {
|
|
81
|
+
return "europe";
|
|
82
|
+
}
|
|
83
|
+
if (activeSessions.includes("asia")) {
|
|
84
|
+
return "asia";
|
|
85
|
+
}
|
|
86
|
+
return "off_hours";
|
|
87
|
+
};
|
|
88
|
+
var getAdaptiveMomentumRibbonSnapshot = (signal) => {
|
|
89
|
+
const additional = getRecord(signal.additionalIndicators);
|
|
90
|
+
const amr = additional?.amr;
|
|
91
|
+
return amr && typeof amr === "object" ? amr : {};
|
|
92
|
+
};
|
|
93
|
+
var getAdaptiveMomentumRibbonConfigSnapshot = (signal) => {
|
|
94
|
+
const additional = getRecord(signal.additionalIndicators);
|
|
95
|
+
return getRecord(additional?.amrConfigSnapshot);
|
|
96
|
+
};
|
|
97
|
+
var isAtLeast = (value, threshold) => value != null && value >= threshold;
|
|
98
|
+
var isInRange = (value, min, max) => value != null && value >= min && value <= max;
|
|
99
|
+
var getDirectionalInvalidationDistancePct = ({
|
|
100
|
+
signalDirection,
|
|
101
|
+
currentPrice,
|
|
102
|
+
invalidationLevel
|
|
103
|
+
}) => {
|
|
104
|
+
if (signalDirection == null || currentPrice == null || currentPrice <= 0 || invalidationLevel == null) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (signalDirection === "LONG") {
|
|
108
|
+
if (invalidationLevel >= currentPrice) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return (currentPrice - invalidationLevel) / currentPrice * 100;
|
|
112
|
+
}
|
|
113
|
+
if (invalidationLevel <= currentPrice) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return (invalidationLevel - currentPrice) / currentPrice * 100;
|
|
117
|
+
};
|
|
118
|
+
var getDirectionalRewardPct = ({
|
|
119
|
+
signalDirection,
|
|
120
|
+
currentPrice,
|
|
121
|
+
takeProfitPrice
|
|
122
|
+
}) => {
|
|
123
|
+
if (signalDirection == null || currentPrice == null || currentPrice <= 0 || takeProfitPrice == null) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return signalDirection === "LONG" ? (takeProfitPrice - currentPrice) / currentPrice * 100 : (currentPrice - takeProfitPrice) / currentPrice * 100;
|
|
127
|
+
};
|
|
128
|
+
var getDirectionalChannelExtensionPct = ({
|
|
129
|
+
signalDirection,
|
|
130
|
+
currentPrice,
|
|
131
|
+
kcUpper,
|
|
132
|
+
kcLower
|
|
133
|
+
}) => {
|
|
134
|
+
if (signalDirection == null || currentPrice == null || currentPrice <= 0) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
if (signalDirection === "LONG") {
|
|
138
|
+
if (kcUpper == null || currentPrice <= kcUpper) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return (currentPrice - kcUpper) / currentPrice * 100;
|
|
142
|
+
}
|
|
143
|
+
if (kcLower == null || currentPrice >= kcLower) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
return (kcLower - currentPrice) / currentPrice * 100;
|
|
147
|
+
};
|
|
148
|
+
var getChannelState = ({
|
|
149
|
+
signalDirection,
|
|
150
|
+
currentPrice,
|
|
151
|
+
kcMidline,
|
|
152
|
+
kcUpper,
|
|
153
|
+
kcLower
|
|
154
|
+
}) => {
|
|
155
|
+
if (signalDirection == null || currentPrice == null || kcMidline == null) {
|
|
156
|
+
return "unknown";
|
|
157
|
+
}
|
|
158
|
+
if (signalDirection === "LONG") {
|
|
159
|
+
if (kcUpper != null && currentPrice >= kcUpper) {
|
|
160
|
+
return "above_upper";
|
|
161
|
+
}
|
|
162
|
+
if (currentPrice >= kcMidline) {
|
|
163
|
+
return "inside_channel";
|
|
164
|
+
}
|
|
165
|
+
if (kcLower != null && currentPrice <= kcLower) {
|
|
166
|
+
return "below_lower";
|
|
167
|
+
}
|
|
168
|
+
return "below_midline";
|
|
169
|
+
}
|
|
170
|
+
if (kcLower != null && currentPrice <= kcLower) {
|
|
171
|
+
return "below_lower";
|
|
172
|
+
}
|
|
173
|
+
if (currentPrice <= kcMidline) {
|
|
174
|
+
return "inside_channel";
|
|
175
|
+
}
|
|
176
|
+
if (kcUpper != null && currentPrice >= kcUpper) {
|
|
177
|
+
return "above_upper";
|
|
178
|
+
}
|
|
179
|
+
return "above_midline";
|
|
180
|
+
};
|
|
181
|
+
var getRetestPrice = (context) => {
|
|
182
|
+
if (context.signalDirection === "LONG") {
|
|
183
|
+
if (context.kcMidline != null && context.channelState === "below_midline") {
|
|
184
|
+
return context.kcMidline;
|
|
185
|
+
}
|
|
186
|
+
return context.kcUpper ?? context.kcMidline ?? context.invalidationLevel;
|
|
187
|
+
}
|
|
188
|
+
if (context.signalDirection === "SHORT") {
|
|
189
|
+
if (context.kcMidline != null && context.channelState === "above_midline") {
|
|
190
|
+
return context.kcMidline;
|
|
191
|
+
}
|
|
192
|
+
return context.kcLower ?? context.kcMidline ?? context.invalidationLevel;
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
};
|
|
196
|
+
var getDeterministicAdaptiveMomentumRibbonQuality = (context) => {
|
|
197
|
+
if (context.hardBlockReasons.length > 0) {
|
|
198
|
+
return 2;
|
|
199
|
+
}
|
|
200
|
+
const biasConflictCount = Number(context.coinBiasAligned === false) + Number(context.btcBiasAligned === false);
|
|
201
|
+
const noBiasConflict = biasConflictCount === 0;
|
|
202
|
+
const oscillatorModerate = isAtLeast(context.oscillatorStrength, 0.3);
|
|
203
|
+
const oscillatorStrong = isAtLeast(context.oscillatorStrength, 0.55);
|
|
204
|
+
const oscillatorElite = isAtLeast(context.oscillatorStrength, 0.9);
|
|
205
|
+
const invalidationCompact = isInRange(
|
|
206
|
+
context.invalidationDistancePct,
|
|
207
|
+
0.15,
|
|
208
|
+
1.8
|
|
209
|
+
);
|
|
210
|
+
const invalidationTight = isInRange(
|
|
211
|
+
context.invalidationDistancePct,
|
|
212
|
+
0.2,
|
|
213
|
+
1.25
|
|
214
|
+
);
|
|
215
|
+
const structuralRrModerate = isAtLeast(
|
|
216
|
+
context.structuralRewardRiskRatio,
|
|
217
|
+
1.2
|
|
218
|
+
);
|
|
219
|
+
const structuralRrStrong = isAtLeast(context.structuralRewardRiskRatio, 1.8);
|
|
220
|
+
const channelSupportive = context.channelBiasAligned === true;
|
|
221
|
+
const channelExpansion = context.signalDirection === "LONG" ? context.channelState === "above_upper" : context.channelState === "below_lower";
|
|
222
|
+
const channelExtensionStrong = isAtLeast(context.channelExtensionPct, 0.08);
|
|
223
|
+
const sessionAllowsApproval = context.sessionAllowsApproval !== false;
|
|
224
|
+
const slowestDetector = context.momentumPeriod === 48 && context.butterworthSmoothing === 6;
|
|
225
|
+
if (!sessionAllowsApproval) {
|
|
226
|
+
return 3;
|
|
227
|
+
}
|
|
228
|
+
if (channelSupportive && channelExpansion && channelExtensionStrong && oscillatorElite && invalidationTight && structuralRrStrong && noBiasConflict) {
|
|
229
|
+
return 5;
|
|
230
|
+
}
|
|
231
|
+
if (channelSupportive && channelExpansion && !slowestDetector && oscillatorModerate && invalidationCompact && structuralRrModerate && biasConflictCount < 2 && (biasConflictCount === 0 || oscillatorStrong)) {
|
|
232
|
+
return 4;
|
|
233
|
+
}
|
|
234
|
+
return 3;
|
|
235
|
+
};
|
|
236
|
+
var getHardBlockReasonText = (reason) => {
|
|
237
|
+
switch (reason) {
|
|
238
|
+
case "invalidated":
|
|
239
|
+
return "the signal is already invalidated relative to invalidationLevel";
|
|
240
|
+
case "inactive_signal_state":
|
|
241
|
+
return "the active ribbon state does not confirm the current direction";
|
|
242
|
+
case "oscillator_conflict":
|
|
243
|
+
return "signalOsc conflicts with the signal direction";
|
|
244
|
+
case "invalidation_wrong_side":
|
|
245
|
+
return "invalidationLevel is on the wrong side of the current price";
|
|
246
|
+
default:
|
|
247
|
+
return reason;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
var buildAdaptiveMomentumRibbonContext = (signal, additionalIndicators) => {
|
|
251
|
+
const signalDirection = getSignalDirection(signal);
|
|
252
|
+
const snapshot = getAdaptiveMomentumRibbonSnapshot(signal);
|
|
253
|
+
const configSnapshot = getAdaptiveMomentumRibbonConfigSnapshot(signal);
|
|
254
|
+
const momentumPeriod = toFiniteNumberOrNull(configSnapshot?.momentumPeriod);
|
|
255
|
+
const butterworthSmoothing = toFiniteNumberOrNull(
|
|
256
|
+
configSnapshot?.butterworthSmoothing
|
|
257
|
+
);
|
|
258
|
+
const currentPrice = toFiniteNumberOrNull(signal.prices?.currentPrice);
|
|
259
|
+
const takeProfitPrice = toFiniteNumberOrNull(signal.prices?.takeProfitPrice);
|
|
260
|
+
const signalOsc = toFiniteNumberOrNull(snapshot.signalOsc);
|
|
261
|
+
const oscillatorStrength = signalOsc != null ? Math.abs(signalOsc) : null;
|
|
262
|
+
const kcMidline = toFiniteNumberOrNull(snapshot.kcMidline);
|
|
263
|
+
const kcUpper = toFiniteNumberOrNull(snapshot.kcUpper);
|
|
264
|
+
const kcLower = toFiniteNumberOrNull(snapshot.kcLower);
|
|
265
|
+
const invalidationLevel = toFiniteNumberOrNull(snapshot.invalidationLevel);
|
|
266
|
+
const entryLong = asBoolean(snapshot.entryLong);
|
|
267
|
+
const entryShort = asBoolean(snapshot.entryShort);
|
|
268
|
+
const activeBuy = asBoolean(snapshot.activeBuy);
|
|
269
|
+
const activeSell = asBoolean(snapshot.activeSell);
|
|
270
|
+
const invalidated = asBoolean(snapshot.invalidated);
|
|
271
|
+
const channelState = getChannelState({
|
|
272
|
+
signalDirection,
|
|
273
|
+
currentPrice,
|
|
274
|
+
kcMidline,
|
|
275
|
+
kcUpper,
|
|
276
|
+
kcLower
|
|
277
|
+
});
|
|
278
|
+
const channelBiasAligned = signalDirection === "LONG" ? kcMidline != null && currentPrice != null ? currentPrice >= kcMidline : null : signalDirection === "SHORT" ? kcMidline != null && currentPrice != null ? currentPrice <= kcMidline : null : null;
|
|
279
|
+
const channelExtensionPct = getDirectionalChannelExtensionPct({
|
|
280
|
+
signalDirection,
|
|
281
|
+
currentPrice,
|
|
282
|
+
kcUpper,
|
|
283
|
+
kcLower
|
|
284
|
+
});
|
|
285
|
+
const invalidationDistancePct = getDirectionalInvalidationDistancePct({
|
|
286
|
+
signalDirection,
|
|
287
|
+
currentPrice,
|
|
288
|
+
invalidationLevel
|
|
289
|
+
});
|
|
290
|
+
const rewardPct = getDirectionalRewardPct({
|
|
291
|
+
signalDirection,
|
|
292
|
+
currentPrice,
|
|
293
|
+
takeProfitPrice
|
|
294
|
+
});
|
|
295
|
+
const structuralRewardRiskRatio = rewardPct != null && invalidationDistancePct != null && invalidationDistancePct > 0 ? rewardPct / invalidationDistancePct : null;
|
|
296
|
+
const coinBias = getBias(
|
|
297
|
+
getLastFiniteNumber(signal.indicators?.maFast),
|
|
298
|
+
getLastFiniteNumber(signal.indicators?.maSlow)
|
|
299
|
+
);
|
|
300
|
+
const btcBias = getBias(
|
|
301
|
+
getLastFiniteNumber(signal.indicators?.btcMaFast),
|
|
302
|
+
getLastFiniteNumber(signal.indicators?.btcMaSlow)
|
|
303
|
+
);
|
|
304
|
+
const coinBiasAligned = signalDirection === "LONG" ? coinBias == null ? null : coinBias === "bullish" : signalDirection === "SHORT" ? coinBias == null ? null : coinBias === "bearish" : null;
|
|
305
|
+
const btcBiasAligned = signalDirection === "LONG" ? btcBias == null ? null : btcBias === "bullish" : signalDirection === "SHORT" ? btcBias == null ? null : btcBias === "bearish" : null;
|
|
306
|
+
const primarySession = getPrimarySession(signal, additionalIndicators);
|
|
307
|
+
const sessionAllowsApproval = primarySession == null ? null : primarySession === "off_hours";
|
|
308
|
+
const hardBlockReasons = [];
|
|
309
|
+
if (invalidated) {
|
|
310
|
+
hardBlockReasons.push("invalidated");
|
|
311
|
+
}
|
|
312
|
+
if (signalDirection === "LONG" && (!entryLong || !activeBuy) || signalDirection === "SHORT" && (!entryShort || !activeSell)) {
|
|
313
|
+
hardBlockReasons.push("inactive_signal_state");
|
|
314
|
+
}
|
|
315
|
+
if (signalDirection === "LONG" && !isAtLeast(signalOsc, Number.EPSILON) || signalDirection === "SHORT" && !(signalOsc != null && signalOsc < -Number.EPSILON)) {
|
|
316
|
+
hardBlockReasons.push("oscillator_conflict");
|
|
317
|
+
}
|
|
318
|
+
if (signalDirection != null && invalidationLevel != null && currentPrice != null && (signalDirection === "LONG" && invalidationLevel >= currentPrice || signalDirection === "SHORT" && invalidationLevel <= currentPrice)) {
|
|
319
|
+
hardBlockReasons.push("invalidation_wrong_side");
|
|
320
|
+
}
|
|
321
|
+
const deterministicQuality = getDeterministicAdaptiveMomentumRibbonQuality({
|
|
322
|
+
signalDirection,
|
|
323
|
+
momentumPeriod,
|
|
324
|
+
butterworthSmoothing,
|
|
325
|
+
entryLong,
|
|
326
|
+
entryShort,
|
|
327
|
+
activeBuy,
|
|
328
|
+
activeSell,
|
|
329
|
+
invalidated,
|
|
330
|
+
signalOsc,
|
|
331
|
+
oscillatorStrength,
|
|
332
|
+
kcMidline,
|
|
333
|
+
kcUpper,
|
|
334
|
+
kcLower,
|
|
335
|
+
invalidationLevel,
|
|
336
|
+
channelState,
|
|
337
|
+
channelBiasAligned,
|
|
338
|
+
channelExtensionPct,
|
|
339
|
+
invalidationDistancePct,
|
|
340
|
+
structuralRewardRiskRatio,
|
|
341
|
+
coinMaBias: coinBias,
|
|
342
|
+
btcMaBias: btcBias,
|
|
343
|
+
coinBiasAligned,
|
|
344
|
+
btcBiasAligned,
|
|
345
|
+
primarySession,
|
|
346
|
+
sessionAllowsApproval,
|
|
347
|
+
hardBlockReasons
|
|
348
|
+
});
|
|
349
|
+
return {
|
|
350
|
+
signalDirection,
|
|
351
|
+
momentumPeriod,
|
|
352
|
+
butterworthSmoothing,
|
|
353
|
+
entryLong,
|
|
354
|
+
entryShort,
|
|
355
|
+
activeBuy,
|
|
356
|
+
activeSell,
|
|
357
|
+
invalidated,
|
|
358
|
+
signalOsc,
|
|
359
|
+
oscillatorStrength,
|
|
360
|
+
kcMidline,
|
|
361
|
+
kcUpper,
|
|
362
|
+
kcLower,
|
|
363
|
+
invalidationLevel,
|
|
364
|
+
channelState,
|
|
365
|
+
channelBiasAligned,
|
|
366
|
+
channelExtensionPct,
|
|
367
|
+
invalidationDistancePct,
|
|
368
|
+
structuralRewardRiskRatio,
|
|
369
|
+
coinMaBias: coinBias,
|
|
370
|
+
btcMaBias: btcBias,
|
|
371
|
+
coinBiasAligned,
|
|
372
|
+
btcBiasAligned,
|
|
373
|
+
primarySession,
|
|
374
|
+
sessionAllowsApproval,
|
|
375
|
+
hardBlockReasons,
|
|
376
|
+
structuralHardBlockReasons: hardBlockReasons,
|
|
377
|
+
deterministicQuality,
|
|
378
|
+
approvalAllowedNow: deterministicQuality >= 4,
|
|
379
|
+
maxAllowedQuality: deterministicQuality
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
var getAdaptiveMomentumRibbonContextFromPayload = (payload, signal) => {
|
|
383
|
+
const additional = payload.additionalIndicators;
|
|
384
|
+
const context = additional?.adaptiveMomentumRibbonContext;
|
|
385
|
+
return context && typeof context === "object" ? context : buildAdaptiveMomentumRibbonContext(signal);
|
|
386
|
+
};
|
|
387
|
+
var clampQuality = (value, maxAllowedQuality) => {
|
|
388
|
+
const resolved = typeof value === "number" ? value : maxAllowedQuality;
|
|
389
|
+
return Math.max(1, Math.min(maxAllowedQuality, Math.round(resolved)));
|
|
390
|
+
};
|
|
391
|
+
var postProcessAnalysis = ({
|
|
392
|
+
signal,
|
|
393
|
+
payload,
|
|
394
|
+
analysis
|
|
395
|
+
}) => {
|
|
396
|
+
const context = getAdaptiveMomentumRibbonContextFromPayload(payload, signal);
|
|
397
|
+
const signalDirection = getSignalDirection(signal);
|
|
398
|
+
const requestedDirection = analysis.direction === signalDirection ? signalDirection : null;
|
|
399
|
+
const finalDirection = requestedDirection != null && context.approvalAllowedNow ? requestedDirection : null;
|
|
400
|
+
const finalQuality = clampQuality(
|
|
401
|
+
typeof analysis.quality === "number" ? analysis.quality : context.deterministicQuality,
|
|
402
|
+
context.maxAllowedQuality
|
|
403
|
+
);
|
|
404
|
+
const needRetest = finalDirection == null;
|
|
405
|
+
const retestPrice = needRetest ? getRetestPrice(context) : null;
|
|
406
|
+
if (finalDirection == null) {
|
|
407
|
+
return {
|
|
408
|
+
...analysis,
|
|
409
|
+
direction: null,
|
|
410
|
+
quality: finalQuality,
|
|
411
|
+
needRetest: true,
|
|
412
|
+
retestPrice,
|
|
413
|
+
takeProfitPrice: null,
|
|
414
|
+
stopLossPrice: null,
|
|
415
|
+
qualityReason: analysis.qualityReason || (context.hardBlockReasons.length > 0 ? `AdaptiveMomentumRibbon guardrail: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "AdaptiveMomentumRibbon keeps the setup in watch mode until momentum confirmation becomes cleaner."),
|
|
416
|
+
triggerInvalidation: analysis.triggerInvalidation || (retestPrice != null ? `Wait for confirmation relative to level ${retestPrice}.` : "Wait for cleaner momentum confirmation and better price positioning inside the Keltner channel."),
|
|
417
|
+
comment: analysis.comment || (context.hardBlockReasons.length > 0 ? `AdaptiveMomentumRibbon rejected: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "AdaptiveMomentumRibbon keeps the signal in watch mode until continuation confirmation becomes cleaner.")
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
...analysis,
|
|
422
|
+
direction: finalDirection,
|
|
423
|
+
quality: finalQuality,
|
|
424
|
+
needRetest: false,
|
|
425
|
+
retestPrice: null,
|
|
426
|
+
takeProfitPrice: signal.prices?.takeProfitPrice ?? null,
|
|
427
|
+
stopLossPrice: signal.prices?.stopLossPrice ?? null
|
|
428
|
+
};
|
|
429
|
+
};
|
|
430
|
+
var adaptiveMomentumRibbonAiAdapter = {
|
|
431
|
+
buildPayload: ({ signal, basePayload }) => {
|
|
432
|
+
const additionalIndicators = getRecord(basePayload.additionalIndicators);
|
|
433
|
+
return {
|
|
434
|
+
...basePayload,
|
|
435
|
+
additionalIndicators: {
|
|
436
|
+
...additionalIndicators ?? {},
|
|
437
|
+
adaptiveMomentumRibbonContext: buildAdaptiveMomentumRibbonContext(
|
|
438
|
+
signal,
|
|
439
|
+
additionalIndicators
|
|
440
|
+
)
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
},
|
|
444
|
+
postProcessAnalysis,
|
|
445
|
+
buildSystemPromptAddon: () => `${ADAPTIVE_MOMENTUM_RIBBON_CONTEXT_PROMPT}
|
|
446
|
+
${ADAPTIVE_MOMENTUM_RIBBON_PAYLOAD_PROMPT}`,
|
|
447
|
+
buildHumanPromptAddon: ({ signal, payload }) => {
|
|
448
|
+
const context = getAdaptiveMomentumRibbonContextFromPayload(
|
|
449
|
+
payload,
|
|
450
|
+
signal
|
|
451
|
+
);
|
|
452
|
+
return `
|
|
453
|
+
|
|
454
|
+
Additional AdaptiveMomentumRibbon context:
|
|
455
|
+
- momentumPeriod=${context.momentumPeriod ?? "n/a"}
|
|
456
|
+
- butterworthSmoothing=${context.butterworthSmoothing ?? "n/a"}
|
|
457
|
+
- signalOsc=${context.signalOsc?.toFixed?.(3) ?? "n/a"}
|
|
458
|
+
- oscillatorStrength=${context.oscillatorStrength?.toFixed?.(3) ?? "n/a"}
|
|
459
|
+
- channelState=${context.channelState}
|
|
460
|
+
- channelBiasAligned=${context.channelBiasAligned}
|
|
461
|
+
- channelExtensionPct=${context.channelExtensionPct?.toFixed?.(3) ?? "n/a"}%
|
|
462
|
+
- invalidationDistancePct=${context.invalidationDistancePct?.toFixed?.(3) ?? "n/a"}%
|
|
463
|
+
- structuralRewardRiskRatio=${context.structuralRewardRiskRatio?.toFixed?.(3) ?? "n/a"}
|
|
464
|
+
- coinBiasAligned=${context.coinBiasAligned}
|
|
465
|
+
- btcBiasAligned=${context.btcBiasAligned}
|
|
466
|
+
- primarySession=${context.primarySession ?? "n/a"}
|
|
467
|
+
- sessionAllowsApproval=${context.sessionAllowsApproval}
|
|
468
|
+
- deterministicQuality=${context.deterministicQuality}
|
|
469
|
+
- approvalAllowedNow=${context.approvalAllowedNow}
|
|
470
|
+
- hardBlockReasons=${context.hardBlockReasons.join(", ") || "none"}
|
|
471
|
+
|
|
472
|
+
Interpretation rules for AdaptiveMomentumRibbon:
|
|
473
|
+
- a zero-cross alone does not make quality high;
|
|
474
|
+
- pay attention to Keltner channel side, sane invalidation distance, and bias alignment;
|
|
475
|
+
- if \`signalOsc\` already conflicts with direction or the signal is invalidated, do not treat the entry as confirmed.
|
|
476
|
+
`;
|
|
477
|
+
},
|
|
478
|
+
mapEntryRuntimeFromConfig: (config2) => mapAiRuntimeFromConfig(
|
|
479
|
+
config2
|
|
480
|
+
)
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// src/AdaptiveMomentumRibbon/adapters/ml.ts
|
|
484
|
+
import { mapMlRuntimeFromConfig } from "@tradejs/core/strategies";
|
|
485
|
+
var adaptiveMomentumRibbonMlAdapter = {
|
|
486
|
+
mapEntryRuntimeFromConfig: (config2) => mapMlRuntimeFromConfig(
|
|
487
|
+
config2
|
|
488
|
+
)
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/AdaptiveMomentumRibbon/manifest.ts
|
|
492
|
+
var adaptiveMomentumRibbonManifest = {
|
|
493
|
+
name: "AdaptiveMomentumRibbon",
|
|
494
|
+
aiAdapter: adaptiveMomentumRibbonAiAdapter,
|
|
495
|
+
mlAdapter: adaptiveMomentumRibbonMlAdapter
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
// src/AdaptiveMomentumRibbon/config.ts
|
|
499
|
+
var config = {
|
|
500
|
+
ENV: "BACKTEST",
|
|
501
|
+
INTERVAL: "15",
|
|
502
|
+
MAKE_ORDERS: true,
|
|
503
|
+
CLOSE_OPPOSITE_POSITIONS: false,
|
|
504
|
+
BACKTEST_PRICE_MODE: "mid",
|
|
505
|
+
AI_ENABLED: false,
|
|
506
|
+
AI_MODE: "llm",
|
|
507
|
+
ML_ENABLED: false,
|
|
508
|
+
ML_THRESHOLD: 0.1,
|
|
509
|
+
MIN_AI_QUALITY: 3,
|
|
510
|
+
FEE_PERCENT: 5e-3,
|
|
511
|
+
MAX_LOSS_VALUE: 10,
|
|
512
|
+
AMR_LOOKBACK_BARS: 200,
|
|
513
|
+
AMR_MOMENTUM_PERIOD: 32,
|
|
514
|
+
AMR_BUTTERWORTH_SMOOTHING: 4,
|
|
515
|
+
AMR_WAIT_CLOSE: true,
|
|
516
|
+
AMR_CONFIRM_ON_NEXT_BAR: true,
|
|
517
|
+
AMR_MIN_SIGNAL_OSC_ABS: 0.55,
|
|
518
|
+
AMR_REQUIRE_KC_BIAS: true,
|
|
519
|
+
AMR_MIN_BARS_BETWEEN_SIGNALS: 12,
|
|
520
|
+
AMR_SHOW_INVALIDATION_LEVELS: true,
|
|
521
|
+
AMR_SHOW_KELTNER_CHANNEL: true,
|
|
522
|
+
AMR_KC_LENGTH: 20,
|
|
523
|
+
AMR_KC_MA_TYPE: "EMA",
|
|
524
|
+
AMR_ATR_LENGTH: 14,
|
|
525
|
+
AMR_ATR_MULTIPLIER: 2,
|
|
526
|
+
AMR_EXIT_ON_INVALIDATION: true,
|
|
527
|
+
AMR_LINE_PLOTS: ["kcMidline", "kcUpper", "kcLower", "invalidationLevel"],
|
|
528
|
+
LONG: {
|
|
529
|
+
enable: true,
|
|
530
|
+
direction: "LONG",
|
|
531
|
+
TP: 2,
|
|
532
|
+
SL: 1
|
|
533
|
+
},
|
|
534
|
+
SHORT: {
|
|
535
|
+
enable: true,
|
|
536
|
+
direction: "SHORT",
|
|
537
|
+
TP: 2,
|
|
538
|
+
SL: 1
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
export {
|
|
543
|
+
adaptiveMomentumRibbonAiAdapter,
|
|
544
|
+
adaptiveMomentumRibbonMlAdapter,
|
|
545
|
+
adaptiveMomentumRibbonManifest,
|
|
546
|
+
config
|
|
547
|
+
};
|