@tradejs/strategies 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,405 @@
1
+ // src/AdaptiveMomentumRibbon/adapters/ai.ts
2
+ import { mapAiRuntimeFromConfig } from "@tradejs/core/strategies";
3
+ var ADAPTIVE_MOMENTUM_RIBBON_CONTEXT_PROMPT = `
4
+ \u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0434\u043B\u044F AdaptiveMomentumRibbon:
5
+ - \u042D\u0442\u043E momentum-entry \u043D\u0430 zero-cross \u043E\u0441\u0446\u0438\u043B\u043B\u044F\u0442\u043E\u0440\u0430, \u0430 \u043D\u0435 \u0442\u0440\u0435\u043D\u0434\u043B\u0430\u0439\u043D\u043E\u0432\u044B\u0439 breakout \u0438 \u043D\u0435 reversal \u043E\u0442 \u043B\u0438\u043D\u0438\u0438.
6
+ - LONG \u043F\u043E\u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F, \u043A\u043E\u0433\u0434\u0430 signalOsc \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u0438\u0442 \u0432\u044B\u0448\u0435 0 \u0438 ribbon \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0430\u0435\u0442\u0441\u044F \u0432 activeBuy; SHORT \u0437\u0435\u0440\u043A\u0430\u043B\u044C\u043D\u043E.
7
+ - invalidationLevel \u2014 \u044D\u0442\u043E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u044B\u0439 \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u043E\u0442\u043C\u0435\u043D\u044B \u0441\u0438\u0433\u043D\u0430\u043B\u0430 \u043D\u0430 \u0431\u0430\u0440\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u0430. \u0415\u0441\u043B\u0438 invalidated=true \u0438\u043B\u0438 invalidationLevel \u043E\u043A\u0430\u0437\u0430\u043B\u0441\u044F \u043D\u0435 \u0441 \u0442\u043E\u0439 \u0441\u0442\u043E\u0440\u043E\u043D\u044B \u043E\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0446\u0435\u043D\u044B, \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0441\u0435\u0442\u0430\u043F \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C.
8
+ - channelState / channelBiasAligned \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u044E\u0442, \u0433\u0434\u0435 \u0446\u0435\u043D\u0430 \u043D\u0430\u0445\u043E\u0434\u0438\u0442\u0441\u044F \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E Keltner Channel. \u0414\u043B\u044F LONG \u043F\u043B\u043E\u0445\u043E, \u0435\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u0432\u0441\u0435 \u0435\u0449\u0435 \u043D\u0438\u0436\u0435 kcMidline; \u0434\u043B\u044F SHORT \u043F\u043B\u043E\u0445\u043E, \u0435\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u0432\u044B\u0448\u0435 kcMidline.
9
+ - invalidationDistancePct \u0438 structuralRewardRiskRatio \u043E\u043F\u0438\u0441\u044B\u0432\u0430\u044E\u0442, \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0438\u0433\u043D\u0430\u043B \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E \u043A\u043E\u043C\u043F\u0430\u043A\u0442\u0435\u043D. \u041D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439 quality, \u0435\u0441\u043B\u0438 invalidation \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0448\u0438\u0440\u043E\u043A\u0430\u044F \u0438\u043B\u0438 reward/risk \u043A invalidation \u0441\u043B\u0430\u0431\u044B\u0439.
10
+ - \u0414\u043B\u044F quality=5 \u043D\u0443\u0436\u0435\u043D \u043E\u0447\u0435\u043D\u044C \u0447\u0438\u0441\u0442\u044B\u0439 momentum: \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u0430\u044F \u0441\u0442\u043E\u0440\u043E\u043D\u0430 \u043A\u0430\u043D\u0430\u043B\u0430, \u0441\u0438\u043B\u044C\u043D\u044B\u0439 signalOsc, sane invalidationDistance \u0438 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u043E\u0432 \u043F\u043E bias \u043C\u043E\u043D\u0435\u0442\u044B \u0438 BTC.
11
+ - \u0415\u0441\u043B\u0438 approvalAllowedNow=false \u0438\u043B\u0438 deterministicQuality<4, \u043E\u0431\u044B\u0447\u043D\u043E \u044D\u0442\u043E watch-mode, \u0430 \u043D\u0435 \u0433\u043E\u0442\u043E\u0432\u044B\u0439 live-approval.
12
+ `;
13
+ var ADAPTIVE_MOMENTUM_RIBBON_PAYLOAD_PROMPT = `
14
+ - \u0412 payload.additionalIndicators.adaptiveMomentumRibbonContext \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F \u043A\u0440\u0430\u0442\u043A\u0430\u044F \u0441\u0432\u043E\u0434\u043A\u0430 \u0441\u0438\u0433\u043D\u0430\u043B\u0430:
15
+ signalOsc / oscillatorStrength / channelState / invalidationDistancePct / structuralRewardRiskRatio / coinBiasAligned / btcBiasAligned / deterministicQuality / approvalAllowedNow / structuralHardBlockReasons.
16
+ - \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u044D\u0442\u043E\u0442 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u043A\u0430\u043A \u043E\u0441\u043D\u043E\u0432\u043D\u0443\u044E strategy-specific \u0438\u043D\u0442\u0435\u0440\u043F\u0440\u0435\u0442\u0430\u0446\u0438\u044E, \u0430 \u043D\u0435 \u043F\u044B\u0442\u0430\u0439\u0441\u044F \u0437\u0430\u043D\u043E\u0432\u043E \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0435\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E \u043E\u0431\u0449\u0438\u043C \u0440\u044F\u0434\u0430\u043C.
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 getAdaptiveMomentumRibbonSnapshot = (signal) => {
55
+ const additional = signal.additionalIndicators;
56
+ const amr = additional?.amr;
57
+ return amr && typeof amr === "object" ? amr : {};
58
+ };
59
+ var isAtLeast = (value, threshold) => value != null && value >= threshold;
60
+ var isInRange = (value, min, max) => value != null && value >= min && value <= max;
61
+ var getDirectionalInvalidationDistancePct = ({
62
+ signalDirection,
63
+ currentPrice,
64
+ invalidationLevel
65
+ }) => {
66
+ if (signalDirection == null || currentPrice == null || currentPrice <= 0 || invalidationLevel == null) {
67
+ return null;
68
+ }
69
+ if (signalDirection === "LONG") {
70
+ if (invalidationLevel >= currentPrice) {
71
+ return null;
72
+ }
73
+ return (currentPrice - invalidationLevel) / currentPrice * 100;
74
+ }
75
+ if (invalidationLevel <= currentPrice) {
76
+ return null;
77
+ }
78
+ return (invalidationLevel - currentPrice) / currentPrice * 100;
79
+ };
80
+ var getDirectionalRewardPct = ({
81
+ signalDirection,
82
+ currentPrice,
83
+ takeProfitPrice
84
+ }) => {
85
+ if (signalDirection == null || currentPrice == null || currentPrice <= 0 || takeProfitPrice == null) {
86
+ return null;
87
+ }
88
+ return signalDirection === "LONG" ? (takeProfitPrice - currentPrice) / currentPrice * 100 : (currentPrice - takeProfitPrice) / currentPrice * 100;
89
+ };
90
+ var getChannelState = ({
91
+ signalDirection,
92
+ currentPrice,
93
+ kcMidline,
94
+ kcUpper,
95
+ kcLower
96
+ }) => {
97
+ if (signalDirection == null || currentPrice == null || kcMidline == null) {
98
+ return "unknown";
99
+ }
100
+ if (signalDirection === "LONG") {
101
+ if (kcUpper != null && currentPrice >= kcUpper) {
102
+ return "above_upper";
103
+ }
104
+ if (currentPrice >= kcMidline) {
105
+ return "inside_channel";
106
+ }
107
+ if (kcLower != null && currentPrice <= kcLower) {
108
+ return "below_lower";
109
+ }
110
+ return "below_midline";
111
+ }
112
+ if (kcLower != null && currentPrice <= kcLower) {
113
+ return "below_lower";
114
+ }
115
+ if (currentPrice <= kcMidline) {
116
+ return "inside_channel";
117
+ }
118
+ if (kcUpper != null && currentPrice >= kcUpper) {
119
+ return "above_upper";
120
+ }
121
+ return "above_midline";
122
+ };
123
+ var getRetestPrice = (context) => {
124
+ if (context.signalDirection === "LONG") {
125
+ if (context.kcMidline != null && context.channelState === "below_midline") {
126
+ return context.kcMidline;
127
+ }
128
+ return context.kcUpper ?? context.kcMidline ?? context.invalidationLevel;
129
+ }
130
+ if (context.signalDirection === "SHORT") {
131
+ if (context.kcMidline != null && context.channelState === "above_midline") {
132
+ return context.kcMidline;
133
+ }
134
+ return context.kcLower ?? context.kcMidline ?? context.invalidationLevel;
135
+ }
136
+ return null;
137
+ };
138
+ var getDeterministicAdaptiveMomentumRibbonQuality = (context) => {
139
+ if (context.hardBlockReasons.length > 0) {
140
+ return 2;
141
+ }
142
+ const noBiasConflict = context.coinBiasAligned === true && context.btcBiasAligned === true;
143
+ const biasConflictCount = Number(context.coinBiasAligned === false) + Number(context.btcBiasAligned === false);
144
+ const oscillatorModerate = isAtLeast(context.oscillatorStrength, 0.3);
145
+ const oscillatorStrong = isAtLeast(context.oscillatorStrength, 0.55);
146
+ const oscillatorElite = isAtLeast(context.oscillatorStrength, 0.9);
147
+ const invalidationCompact = isInRange(
148
+ context.invalidationDistancePct,
149
+ 0.15,
150
+ 1.8
151
+ );
152
+ const invalidationTight = isInRange(
153
+ context.invalidationDistancePct,
154
+ 0.2,
155
+ 1.25
156
+ );
157
+ const structuralRrModerate = isAtLeast(
158
+ context.structuralRewardRiskRatio,
159
+ 1.2
160
+ );
161
+ const structuralRrStrong = isAtLeast(context.structuralRewardRiskRatio, 1.8);
162
+ const channelSupportive = context.channelBiasAligned === true;
163
+ const channelExpansion = context.signalDirection === "LONG" ? context.channelState === "above_upper" : context.channelState === "below_lower";
164
+ const channelInside = context.channelState === "inside_channel" || channelExpansion;
165
+ if (channelSupportive && channelExpansion && oscillatorElite && invalidationTight && structuralRrStrong && noBiasConflict) {
166
+ return 5;
167
+ }
168
+ if (channelSupportive && channelInside && oscillatorModerate && invalidationCompact && structuralRrModerate && biasConflictCount < 2 && (biasConflictCount === 0 || channelExpansion || oscillatorStrong)) {
169
+ return 4;
170
+ }
171
+ return 3;
172
+ };
173
+ var getHardBlockReasonText = (reason) => {
174
+ switch (reason) {
175
+ case "invalidated":
176
+ return "\u0441\u0438\u0433\u043D\u0430\u043B \u0443\u0436\u0435 \u0438\u043D\u0432\u0430\u043B\u0438\u0434\u0438\u0440\u043E\u0432\u0430\u043D \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E invalidationLevel";
177
+ case "inactive_signal_state":
178
+ return "active ribbon state \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0435 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435";
179
+ case "oscillator_conflict":
180
+ return "signalOsc \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u0430";
181
+ case "invalidation_wrong_side":
182
+ return "invalidationLevel \u043D\u0430\u0445\u043E\u0434\u0438\u0442\u0441\u044F \u043D\u0435 \u0441 \u0442\u043E\u0439 \u0441\u0442\u043E\u0440\u043E\u043D\u044B \u043E\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0446\u0435\u043D\u044B";
183
+ default:
184
+ return reason;
185
+ }
186
+ };
187
+ var buildAdaptiveMomentumRibbonContext = (signal) => {
188
+ const signalDirection = getSignalDirection(signal);
189
+ const snapshot = getAdaptiveMomentumRibbonSnapshot(signal);
190
+ const currentPrice = toFiniteNumberOrNull(signal.prices?.currentPrice);
191
+ const takeProfitPrice = toFiniteNumberOrNull(signal.prices?.takeProfitPrice);
192
+ const signalOsc = toFiniteNumberOrNull(snapshot.signalOsc);
193
+ const oscillatorStrength = signalOsc != null ? Math.abs(signalOsc) : null;
194
+ const kcMidline = toFiniteNumberOrNull(snapshot.kcMidline);
195
+ const kcUpper = toFiniteNumberOrNull(snapshot.kcUpper);
196
+ const kcLower = toFiniteNumberOrNull(snapshot.kcLower);
197
+ const invalidationLevel = toFiniteNumberOrNull(snapshot.invalidationLevel);
198
+ const entryLong = asBoolean(snapshot.entryLong);
199
+ const entryShort = asBoolean(snapshot.entryShort);
200
+ const activeBuy = asBoolean(snapshot.activeBuy);
201
+ const activeSell = asBoolean(snapshot.activeSell);
202
+ const invalidated = asBoolean(snapshot.invalidated);
203
+ const channelState = getChannelState({
204
+ signalDirection,
205
+ currentPrice,
206
+ kcMidline,
207
+ kcUpper,
208
+ kcLower
209
+ });
210
+ const channelBiasAligned = signalDirection === "LONG" ? kcMidline != null && currentPrice != null ? currentPrice >= kcMidline : null : signalDirection === "SHORT" ? kcMidline != null && currentPrice != null ? currentPrice <= kcMidline : null : null;
211
+ const invalidationDistancePct = getDirectionalInvalidationDistancePct({
212
+ signalDirection,
213
+ currentPrice,
214
+ invalidationLevel
215
+ });
216
+ const rewardPct = getDirectionalRewardPct({
217
+ signalDirection,
218
+ currentPrice,
219
+ takeProfitPrice
220
+ });
221
+ const structuralRewardRiskRatio = rewardPct != null && invalidationDistancePct != null && invalidationDistancePct > 0 ? rewardPct / invalidationDistancePct : null;
222
+ const coinBias = getBias(
223
+ getLastFiniteNumber(signal.indicators?.maFast),
224
+ getLastFiniteNumber(signal.indicators?.maSlow)
225
+ );
226
+ const btcBias = getBias(
227
+ getLastFiniteNumber(signal.indicators?.btcMaFast),
228
+ getLastFiniteNumber(signal.indicators?.btcMaSlow)
229
+ );
230
+ const coinBiasAligned = signalDirection === "LONG" ? coinBias == null ? null : coinBias === "bullish" : signalDirection === "SHORT" ? coinBias == null ? null : coinBias === "bearish" : null;
231
+ const btcBiasAligned = signalDirection === "LONG" ? btcBias == null ? null : btcBias === "bullish" : signalDirection === "SHORT" ? btcBias == null ? null : btcBias === "bearish" : null;
232
+ const hardBlockReasons = [];
233
+ if (invalidated) {
234
+ hardBlockReasons.push("invalidated");
235
+ }
236
+ if (signalDirection === "LONG" && (!entryLong || !activeBuy) || signalDirection === "SHORT" && (!entryShort || !activeSell)) {
237
+ hardBlockReasons.push("inactive_signal_state");
238
+ }
239
+ if (signalDirection === "LONG" && !isAtLeast(signalOsc, Number.EPSILON) || signalDirection === "SHORT" && !(signalOsc != null && signalOsc < -Number.EPSILON)) {
240
+ hardBlockReasons.push("oscillator_conflict");
241
+ }
242
+ if (signalDirection != null && invalidationLevel != null && currentPrice != null && (signalDirection === "LONG" && invalidationLevel >= currentPrice || signalDirection === "SHORT" && invalidationLevel <= currentPrice)) {
243
+ hardBlockReasons.push("invalidation_wrong_side");
244
+ }
245
+ const deterministicQuality = getDeterministicAdaptiveMomentumRibbonQuality({
246
+ signalDirection,
247
+ entryLong,
248
+ entryShort,
249
+ activeBuy,
250
+ activeSell,
251
+ invalidated,
252
+ signalOsc,
253
+ oscillatorStrength,
254
+ kcMidline,
255
+ kcUpper,
256
+ kcLower,
257
+ invalidationLevel,
258
+ channelState,
259
+ channelBiasAligned,
260
+ invalidationDistancePct,
261
+ structuralRewardRiskRatio,
262
+ coinMaBias: coinBias,
263
+ btcMaBias: btcBias,
264
+ coinBiasAligned,
265
+ btcBiasAligned,
266
+ hardBlockReasons
267
+ });
268
+ return {
269
+ signalDirection,
270
+ entryLong,
271
+ entryShort,
272
+ activeBuy,
273
+ activeSell,
274
+ invalidated,
275
+ signalOsc,
276
+ oscillatorStrength,
277
+ kcMidline,
278
+ kcUpper,
279
+ kcLower,
280
+ invalidationLevel,
281
+ channelState,
282
+ channelBiasAligned,
283
+ invalidationDistancePct,
284
+ structuralRewardRiskRatio,
285
+ coinMaBias: coinBias,
286
+ btcMaBias: btcBias,
287
+ coinBiasAligned,
288
+ btcBiasAligned,
289
+ hardBlockReasons,
290
+ structuralHardBlockReasons: hardBlockReasons,
291
+ deterministicQuality,
292
+ approvalAllowedNow: deterministicQuality >= 4,
293
+ maxAllowedQuality: deterministicQuality
294
+ };
295
+ };
296
+ var getAdaptiveMomentumRibbonContextFromPayload = (payload, signal) => {
297
+ const additional = payload.additionalIndicators;
298
+ const context = additional?.adaptiveMomentumRibbonContext;
299
+ return context && typeof context === "object" ? context : buildAdaptiveMomentumRibbonContext(signal);
300
+ };
301
+ var clampQuality = (value, maxAllowedQuality) => {
302
+ const resolved = typeof value === "number" ? value : maxAllowedQuality;
303
+ return Math.max(1, Math.min(maxAllowedQuality, Math.round(resolved)));
304
+ };
305
+ var postProcessAnalysis = ({
306
+ signal,
307
+ payload,
308
+ analysis
309
+ }) => {
310
+ const context = getAdaptiveMomentumRibbonContextFromPayload(payload, signal);
311
+ const signalDirection = getSignalDirection(signal);
312
+ const requestedDirection = analysis.direction === signalDirection ? signalDirection : null;
313
+ const finalDirection = requestedDirection != null && context.approvalAllowedNow ? requestedDirection : null;
314
+ const finalQuality = clampQuality(
315
+ typeof analysis.quality === "number" ? analysis.quality : context.deterministicQuality,
316
+ context.maxAllowedQuality
317
+ );
318
+ const needRetest = finalDirection == null;
319
+ const retestPrice = needRetest ? getRetestPrice(context) : null;
320
+ if (finalDirection == null) {
321
+ return {
322
+ ...analysis,
323
+ direction: null,
324
+ quality: finalQuality,
325
+ needRetest: true,
326
+ retestPrice,
327
+ takeProfitPrice: null,
328
+ stopLossPrice: null,
329
+ qualityReason: analysis.qualityReason || (context.hardBlockReasons.length > 0 ? `AdaptiveMomentumRibbon guardrail: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "AdaptiveMomentumRibbon \u043F\u043E\u043A\u0430 \u043E\u0441\u0442\u0430\u0432\u043B\u044F\u0435\u0442 \u0441\u0435\u0442\u0430\u043F \u0432 watch \u0434\u043E \u0431\u043E\u043B\u0435\u0435 \u0447\u0438\u0441\u0442\u043E\u0433\u043E momentum confirmation."),
330
+ triggerInvalidation: analysis.triggerInvalidation || (retestPrice != null ? `\u0416\u0434\u0430\u0442\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0443\u0440\u043E\u0432\u043D\u044F ${retestPrice}.` : "\u0416\u0434\u0430\u0442\u044C \u0431\u043E\u043B\u0435\u0435 \u0447\u0438\u0441\u0442\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 momentum \u0438 \u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0446\u0435\u043D\u044B \u0432 Keltner channel."),
331
+ comment: analysis.comment || (context.hardBlockReasons.length > 0 ? `AdaptiveMomentumRibbon \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "AdaptiveMomentumRibbon \u043F\u043E\u043A\u0430 \u043F\u0435\u0440\u0435\u0432\u043E\u0434\u0438\u0442 \u0441\u0438\u0433\u043D\u0430\u043B \u0432 watch \u0434\u043E \u0431\u043E\u043B\u0435\u0435 \u0447\u0438\u0441\u0442\u043E\u0433\u043E continuation confirmation.")
332
+ };
333
+ }
334
+ return {
335
+ ...analysis,
336
+ direction: finalDirection,
337
+ quality: finalQuality,
338
+ needRetest: false,
339
+ retestPrice: null,
340
+ takeProfitPrice: signal.prices?.takeProfitPrice ?? null,
341
+ stopLossPrice: signal.prices?.stopLossPrice ?? null
342
+ };
343
+ };
344
+ var adaptiveMomentumRibbonAiAdapter = {
345
+ buildPayload: ({ signal, basePayload }) => ({
346
+ ...basePayload,
347
+ additionalIndicators: {
348
+ ...basePayload.additionalIndicators,
349
+ adaptiveMomentumRibbonContext: buildAdaptiveMomentumRibbonContext(signal)
350
+ }
351
+ }),
352
+ postProcessAnalysis,
353
+ buildSystemPromptAddon: () => `${ADAPTIVE_MOMENTUM_RIBBON_CONTEXT_PROMPT}
354
+ ${ADAPTIVE_MOMENTUM_RIBBON_PAYLOAD_PROMPT}`,
355
+ buildHumanPromptAddon: ({ signal, payload }) => {
356
+ const context = getAdaptiveMomentumRibbonContextFromPayload(
357
+ payload,
358
+ signal
359
+ );
360
+ return `
361
+
362
+ \u0414\u043E\u043F. \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 AdaptiveMomentumRibbon:
363
+ - signalOsc=${context.signalOsc?.toFixed?.(3) ?? "n/a"}
364
+ - oscillatorStrength=${context.oscillatorStrength?.toFixed?.(3) ?? "n/a"}
365
+ - channelState=${context.channelState}
366
+ - channelBiasAligned=${context.channelBiasAligned}
367
+ - invalidationDistancePct=${context.invalidationDistancePct?.toFixed?.(3) ?? "n/a"}%
368
+ - structuralRewardRiskRatio=${context.structuralRewardRiskRatio?.toFixed?.(3) ?? "n/a"}
369
+ - coinBiasAligned=${context.coinBiasAligned}
370
+ - btcBiasAligned=${context.btcBiasAligned}
371
+ - deterministicQuality=${context.deterministicQuality}
372
+ - approvalAllowedNow=${context.approvalAllowedNow}
373
+ - hardBlockReasons=${context.hardBlockReasons.join(", ") || "none"}
374
+
375
+ \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u0438\u043D\u0442\u0435\u0440\u043F\u0440\u0435\u0442\u0430\u0446\u0438\u0438 \u0434\u043B\u044F AdaptiveMomentumRibbon:
376
+ - zero-cross \u0441\u0430\u043C \u043F\u043E \u0441\u0435\u0431\u0435 \u043D\u0435 \u0434\u0435\u043B\u0430\u0435\u0442 quality \u0432\u044B\u0441\u043E\u043A\u0438\u043C;
377
+ - \u0441\u043C\u043E\u0442\u0440\u0438 \u043D\u0430 \u0441\u0442\u043E\u0440\u043E\u043D\u0443 Keltner channel, sane invalidation distance \u0438 bias alignment;
378
+ - \u0435\u0441\u043B\u0438 signalOsc \u0443\u0436\u0435 \u043F\u0440\u043E\u0442\u0438\u0432 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F \u0438\u043B\u0438 \u0441\u0438\u0433\u043D\u0430\u043B invalidated, \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0432\u0445\u043E\u0434 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C.
379
+ `;
380
+ },
381
+ mapEntryRuntimeFromConfig: (config) => mapAiRuntimeFromConfig(
382
+ config
383
+ )
384
+ };
385
+
386
+ // src/AdaptiveMomentumRibbon/adapters/ml.ts
387
+ import { mapMlRuntimeFromConfig } from "@tradejs/core/strategies";
388
+ var adaptiveMomentumRibbonMlAdapter = {
389
+ mapEntryRuntimeFromConfig: (config) => mapMlRuntimeFromConfig(
390
+ config
391
+ )
392
+ };
393
+
394
+ // src/AdaptiveMomentumRibbon/manifest.ts
395
+ var adaptiveMomentumRibbonManifest = {
396
+ name: "AdaptiveMomentumRibbon",
397
+ aiAdapter: adaptiveMomentumRibbonAiAdapter,
398
+ mlAdapter: adaptiveMomentumRibbonMlAdapter
399
+ };
400
+
401
+ export {
402
+ adaptiveMomentumRibbonAiAdapter,
403
+ adaptiveMomentumRibbonMlAdapter,
404
+ adaptiveMomentumRibbonManifest
405
+ };