@tradejs/strategies 1.0.6 → 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.
@@ -0,0 +1,128 @@
1
+ // src/Breakout/adapters/ai.ts
2
+ var breakoutAiAdapter = {};
3
+
4
+ // src/Breakout/adapters/ml.ts
5
+ import { mapMlRuntimeFromConfig } from "@tradejs/core/strategies";
6
+ var breakoutMlAdapter = {
7
+ mapEntryRuntimeFromConfig: (config2) => mapMlRuntimeFromConfig(config2)
8
+ };
9
+
10
+ // src/Breakout/manifest.ts
11
+ var breakoutManifest = {
12
+ name: "Breakout",
13
+ entryRuntimeDefaults: {
14
+ ai: {
15
+ enabled: false
16
+ },
17
+ ml: {
18
+ enabled: false
19
+ }
20
+ },
21
+ aiAdapter: breakoutAiAdapter,
22
+ mlAdapter: breakoutMlAdapter
23
+ };
24
+
25
+ // src/Breakout/config.ts
26
+ var config = {
27
+ ML_ENABLED: false,
28
+ MA_FAST: 49,
29
+ MA_MEDIUM: 49,
30
+ MA_SLOW: 99,
31
+ OBV_SMA: 10,
32
+ ATR: 14,
33
+ ATR_PCT_SHORT: 7,
34
+ ATR_PCT_LONG: 30,
35
+ BB: 20,
36
+ BB_STD: 2,
37
+ MACD_FAST: 12,
38
+ MACD_SLOW: 26,
39
+ MACD_SIGNAL: 9,
40
+ LEVEL_LOOKBACK: 20,
41
+ LEVEL_DELAY: 2,
42
+ ATR_PERIOD: 14,
43
+ BB_PERIOD: 20,
44
+ BB_STDDEV: 2,
45
+ LIMIT: 100,
46
+ ATR_OPEN: 0.5,
47
+ ATR_CLOSE: 1.5,
48
+ OBV_SMA_PERIOD: 10,
49
+ BREAKOUT_LOOKBACK_DELAY: 2,
50
+ BREAKOUT_LOOKBACK: 20,
51
+ REQUIRED_SCORE_LONG: 7,
52
+ REQUIRED_SCORE_SHORT: 7,
53
+ SIGNALS_LONG: {
54
+ VOLATILE: {
55
+ weight: 1,
56
+ required: true
57
+ },
58
+ SMA_UPTREND: {
59
+ weight: 1,
60
+ required: true
61
+ },
62
+ OBV_ABOVE_SMA: {
63
+ weight: 1,
64
+ required: true
65
+ },
66
+ PREV_HIGH_BREAKOUT: {
67
+ weight: 1,
68
+ required: false
69
+ },
70
+ CLOSE_ABOVE_UPPER_BB: {
71
+ weight: 1,
72
+ required: false
73
+ },
74
+ CLOSE_ABOVE_HIGH_LEVEL: {
75
+ weight: 1,
76
+ required: false
77
+ },
78
+ CLOSE_ABOVE_PREV_CLOSE: {
79
+ weight: 1,
80
+ required: false
81
+ }
82
+ },
83
+ SIGNALS_SHORT: {
84
+ VOLATILE: {
85
+ weight: 1,
86
+ required: true
87
+ },
88
+ SMA_DOWNTREND: {
89
+ weight: 1,
90
+ required: true
91
+ },
92
+ OBV_BELOW_SMA: {
93
+ weight: 1,
94
+ required: true
95
+ },
96
+ PREV_LOW_BREAKDOWN: {
97
+ weight: 1,
98
+ required: false
99
+ },
100
+ CLOSE_BELOW_LOWER_BB: {
101
+ weight: 1,
102
+ required: false
103
+ },
104
+ CLOSE_BELOW_LOW_LEVEL: {
105
+ weight: 1,
106
+ required: false
107
+ },
108
+ CLOSE_BELOW_PREV_CLOSE: {
109
+ weight: 1,
110
+ required: false
111
+ }
112
+ },
113
+ TP_LONG: [
114
+ { profit: 0.1, rate: 0.25 },
115
+ { profit: 0.15, rate: 0.5 }
116
+ ],
117
+ TP_SHORT: [
118
+ { profit: 0.05, rate: 0.25 },
119
+ { profit: 0.1, rate: 0.5 }
120
+ ],
121
+ SL_LONG: 0.06,
122
+ SL_SHORT: 0.03
123
+ };
124
+
125
+ export {
126
+ breakoutManifest,
127
+ config
128
+ };
@@ -0,0 +1,62 @@
1
+ // src/MaStrategy/adapters/ai.ts
2
+ import { mapAiRuntimeFromConfig } from "@tradejs/core/strategies";
3
+ var maStrategyAiAdapter = {
4
+ mapEntryRuntimeFromConfig: (config2) => mapAiRuntimeFromConfig(
5
+ config2
6
+ )
7
+ };
8
+
9
+ // src/MaStrategy/adapters/ml.ts
10
+ import { mapMlRuntimeFromConfig } from "@tradejs/core/strategies";
11
+ var maStrategyMlAdapter = {
12
+ mapEntryRuntimeFromConfig: (config2) => mapMlRuntimeFromConfig(
13
+ config2
14
+ )
15
+ };
16
+
17
+ // src/MaStrategy/manifest.ts
18
+ var maStrategyManifest = {
19
+ name: "MaStrategy",
20
+ aiAdapter: maStrategyAiAdapter,
21
+ mlAdapter: maStrategyMlAdapter
22
+ };
23
+
24
+ // src/MaStrategy/config.ts
25
+ var config = {
26
+ ENV: "BACKTEST",
27
+ INTERVAL: "15",
28
+ MAKE_ORDERS: true,
29
+ CLOSE_OPPOSITE_POSITIONS: false,
30
+ BACKTEST_PRICE_MODE: "mid",
31
+ AI_ENABLED: false,
32
+ AI_MODE: "llm",
33
+ ML_ENABLED: false,
34
+ ML_THRESHOLD: 0.1,
35
+ MIN_AI_QUALITY: 3,
36
+ FEE_PERCENT: 5e-3,
37
+ MAX_LOSS_VALUE: 10,
38
+ TRADE_COOLDOWN_MS: 0,
39
+ MA_FAST: 21,
40
+ MA_SLOW: 55,
41
+ LONG: {
42
+ enable: true,
43
+ direction: "LONG",
44
+ TP: 2,
45
+ SL: 1,
46
+ minRiskRatio: 1.5
47
+ },
48
+ SHORT: {
49
+ enable: true,
50
+ direction: "SHORT",
51
+ TP: 2,
52
+ SL: 1,
53
+ minRiskRatio: 1.5
54
+ }
55
+ };
56
+
57
+ export {
58
+ maStrategyAiAdapter,
59
+ maStrategyMlAdapter,
60
+ maStrategyManifest,
61
+ config
62
+ };
@@ -1,19 +1,19 @@
1
1
  // src/AdaptiveMomentumRibbon/adapters/ai.ts
2
2
  import { mapAiRuntimeFromConfig } from "@tradejs/core/strategies";
3
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.
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
12
  `;
13
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.
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
17
  `;
18
18
  var toFiniteNumberOrNull = (value) => {
19
19
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -51,11 +51,49 @@ var getBias = (fast, slow) => {
51
51
  };
52
52
  var getSignalDirection = (signal) => signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
53
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
+ };
54
88
  var getAdaptiveMomentumRibbonSnapshot = (signal) => {
55
- const additional = signal.additionalIndicators;
89
+ const additional = getRecord(signal.additionalIndicators);
56
90
  const amr = additional?.amr;
57
91
  return amr && typeof amr === "object" ? amr : {};
58
92
  };
93
+ var getAdaptiveMomentumRibbonConfigSnapshot = (signal) => {
94
+ const additional = getRecord(signal.additionalIndicators);
95
+ return getRecord(additional?.amrConfigSnapshot);
96
+ };
59
97
  var isAtLeast = (value, threshold) => value != null && value >= threshold;
60
98
  var isInRange = (value, min, max) => value != null && value >= min && value <= max;
61
99
  var getDirectionalInvalidationDistancePct = ({
@@ -87,6 +125,26 @@ var getDirectionalRewardPct = ({
87
125
  }
88
126
  return signalDirection === "LONG" ? (takeProfitPrice - currentPrice) / currentPrice * 100 : (currentPrice - takeProfitPrice) / currentPrice * 100;
89
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
+ };
90
148
  var getChannelState = ({
91
149
  signalDirection,
92
150
  currentPrice,
@@ -139,8 +197,8 @@ var getDeterministicAdaptiveMomentumRibbonQuality = (context) => {
139
197
  if (context.hardBlockReasons.length > 0) {
140
198
  return 2;
141
199
  }
142
- const noBiasConflict = context.coinBiasAligned === true && context.btcBiasAligned === true;
143
200
  const biasConflictCount = Number(context.coinBiasAligned === false) + Number(context.btcBiasAligned === false);
201
+ const noBiasConflict = biasConflictCount === 0;
144
202
  const oscillatorModerate = isAtLeast(context.oscillatorStrength, 0.3);
145
203
  const oscillatorStrong = isAtLeast(context.oscillatorStrength, 0.55);
146
204
  const oscillatorElite = isAtLeast(context.oscillatorStrength, 0.9);
@@ -161,11 +219,16 @@ var getDeterministicAdaptiveMomentumRibbonQuality = (context) => {
161
219
  const structuralRrStrong = isAtLeast(context.structuralRewardRiskRatio, 1.8);
162
220
  const channelSupportive = context.channelBiasAligned === true;
163
221
  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) {
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) {
166
229
  return 5;
167
230
  }
168
- if (channelSupportive && channelInside && oscillatorModerate && invalidationCompact && structuralRrModerate && biasConflictCount < 2 && (biasConflictCount === 0 || channelExpansion || oscillatorStrong)) {
231
+ if (channelSupportive && channelExpansion && !slowestDetector && oscillatorModerate && invalidationCompact && structuralRrModerate && biasConflictCount < 2 && (biasConflictCount === 0 || oscillatorStrong)) {
169
232
  return 4;
170
233
  }
171
234
  return 3;
@@ -173,20 +236,25 @@ var getDeterministicAdaptiveMomentumRibbonQuality = (context) => {
173
236
  var getHardBlockReasonText = (reason) => {
174
237
  switch (reason) {
175
238
  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";
239
+ return "the signal is already invalidated relative to invalidationLevel";
177
240
  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";
241
+ return "the active ribbon state does not confirm the current direction";
179
242
  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";
243
+ return "signalOsc conflicts with the signal direction";
181
244
  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";
245
+ return "invalidationLevel is on the wrong side of the current price";
183
246
  default:
184
247
  return reason;
185
248
  }
186
249
  };
187
- var buildAdaptiveMomentumRibbonContext = (signal) => {
250
+ var buildAdaptiveMomentumRibbonContext = (signal, additionalIndicators) => {
188
251
  const signalDirection = getSignalDirection(signal);
189
252
  const snapshot = getAdaptiveMomentumRibbonSnapshot(signal);
253
+ const configSnapshot = getAdaptiveMomentumRibbonConfigSnapshot(signal);
254
+ const momentumPeriod = toFiniteNumberOrNull(configSnapshot?.momentumPeriod);
255
+ const butterworthSmoothing = toFiniteNumberOrNull(
256
+ configSnapshot?.butterworthSmoothing
257
+ );
190
258
  const currentPrice = toFiniteNumberOrNull(signal.prices?.currentPrice);
191
259
  const takeProfitPrice = toFiniteNumberOrNull(signal.prices?.takeProfitPrice);
192
260
  const signalOsc = toFiniteNumberOrNull(snapshot.signalOsc);
@@ -208,6 +276,12 @@ var buildAdaptiveMomentumRibbonContext = (signal) => {
208
276
  kcLower
209
277
  });
210
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
+ });
211
285
  const invalidationDistancePct = getDirectionalInvalidationDistancePct({
212
286
  signalDirection,
213
287
  currentPrice,
@@ -229,6 +303,8 @@ var buildAdaptiveMomentumRibbonContext = (signal) => {
229
303
  );
230
304
  const coinBiasAligned = signalDirection === "LONG" ? coinBias == null ? null : coinBias === "bullish" : signalDirection === "SHORT" ? coinBias == null ? null : coinBias === "bearish" : null;
231
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";
232
308
  const hardBlockReasons = [];
233
309
  if (invalidated) {
234
310
  hardBlockReasons.push("invalidated");
@@ -244,6 +320,8 @@ var buildAdaptiveMomentumRibbonContext = (signal) => {
244
320
  }
245
321
  const deterministicQuality = getDeterministicAdaptiveMomentumRibbonQuality({
246
322
  signalDirection,
323
+ momentumPeriod,
324
+ butterworthSmoothing,
247
325
  entryLong,
248
326
  entryShort,
249
327
  activeBuy,
@@ -257,16 +335,21 @@ var buildAdaptiveMomentumRibbonContext = (signal) => {
257
335
  invalidationLevel,
258
336
  channelState,
259
337
  channelBiasAligned,
338
+ channelExtensionPct,
260
339
  invalidationDistancePct,
261
340
  structuralRewardRiskRatio,
262
341
  coinMaBias: coinBias,
263
342
  btcMaBias: btcBias,
264
343
  coinBiasAligned,
265
344
  btcBiasAligned,
345
+ primarySession,
346
+ sessionAllowsApproval,
266
347
  hardBlockReasons
267
348
  });
268
349
  return {
269
350
  signalDirection,
351
+ momentumPeriod,
352
+ butterworthSmoothing,
270
353
  entryLong,
271
354
  entryShort,
272
355
  activeBuy,
@@ -280,12 +363,15 @@ var buildAdaptiveMomentumRibbonContext = (signal) => {
280
363
  invalidationLevel,
281
364
  channelState,
282
365
  channelBiasAligned,
366
+ channelExtensionPct,
283
367
  invalidationDistancePct,
284
368
  structuralRewardRiskRatio,
285
369
  coinMaBias: coinBias,
286
370
  btcMaBias: btcBias,
287
371
  coinBiasAligned,
288
372
  btcBiasAligned,
373
+ primarySession,
374
+ sessionAllowsApproval,
289
375
  hardBlockReasons,
290
376
  structuralHardBlockReasons: hardBlockReasons,
291
377
  deterministicQuality,
@@ -326,9 +412,9 @@ var postProcessAnalysis = ({
326
412
  retestPrice,
327
413
  takeProfitPrice: null,
328
414
  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.")
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.")
332
418
  };
333
419
  }
334
420
  return {
@@ -342,13 +428,19 @@ var postProcessAnalysis = ({
342
428
  };
343
429
  };
344
430
  var adaptiveMomentumRibbonAiAdapter = {
345
- buildPayload: ({ signal, basePayload }) => ({
346
- ...basePayload,
347
- additionalIndicators: {
348
- ...basePayload.additionalIndicators,
349
- adaptiveMomentumRibbonContext: buildAdaptiveMomentumRibbonContext(signal)
350
- }
351
- }),
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
+ },
352
444
  postProcessAnalysis,
353
445
  buildSystemPromptAddon: () => `${ADAPTIVE_MOMENTUM_RIBBON_CONTEXT_PROMPT}
354
446
  ${ADAPTIVE_MOMENTUM_RIBBON_PAYLOAD_PROMPT}`,
@@ -359,35 +451,40 @@ ${ADAPTIVE_MOMENTUM_RIBBON_PAYLOAD_PROMPT}`,
359
451
  );
360
452
  return `
361
453
 
362
- \u0414\u043E\u043F. \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 AdaptiveMomentumRibbon:
454
+ Additional AdaptiveMomentumRibbon context:
455
+ - momentumPeriod=${context.momentumPeriod ?? "n/a"}
456
+ - butterworthSmoothing=${context.butterworthSmoothing ?? "n/a"}
363
457
  - signalOsc=${context.signalOsc?.toFixed?.(3) ?? "n/a"}
364
458
  - oscillatorStrength=${context.oscillatorStrength?.toFixed?.(3) ?? "n/a"}
365
459
  - channelState=${context.channelState}
366
460
  - channelBiasAligned=${context.channelBiasAligned}
461
+ - channelExtensionPct=${context.channelExtensionPct?.toFixed?.(3) ?? "n/a"}%
367
462
  - invalidationDistancePct=${context.invalidationDistancePct?.toFixed?.(3) ?? "n/a"}%
368
463
  - structuralRewardRiskRatio=${context.structuralRewardRiskRatio?.toFixed?.(3) ?? "n/a"}
369
464
  - coinBiasAligned=${context.coinBiasAligned}
370
465
  - btcBiasAligned=${context.btcBiasAligned}
466
+ - primarySession=${context.primarySession ?? "n/a"}
467
+ - sessionAllowsApproval=${context.sessionAllowsApproval}
371
468
  - deterministicQuality=${context.deterministicQuality}
372
469
  - approvalAllowedNow=${context.approvalAllowedNow}
373
470
  - hardBlockReasons=${context.hardBlockReasons.join(", ") || "none"}
374
471
 
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.
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.
379
476
  `;
380
477
  },
381
- mapEntryRuntimeFromConfig: (config) => mapAiRuntimeFromConfig(
382
- config
478
+ mapEntryRuntimeFromConfig: (config2) => mapAiRuntimeFromConfig(
479
+ config2
383
480
  )
384
481
  };
385
482
 
386
483
  // src/AdaptiveMomentumRibbon/adapters/ml.ts
387
484
  import { mapMlRuntimeFromConfig } from "@tradejs/core/strategies";
388
485
  var adaptiveMomentumRibbonMlAdapter = {
389
- mapEntryRuntimeFromConfig: (config) => mapMlRuntimeFromConfig(
390
- config
486
+ mapEntryRuntimeFromConfig: (config2) => mapMlRuntimeFromConfig(
487
+ config2
391
488
  )
392
489
  };
393
490
 
@@ -398,8 +495,53 @@ var adaptiveMomentumRibbonManifest = {
398
495
  mlAdapter: adaptiveMomentumRibbonMlAdapter
399
496
  };
400
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
+
401
542
  export {
402
543
  adaptiveMomentumRibbonAiAdapter,
403
544
  adaptiveMomentumRibbonMlAdapter,
404
- adaptiveMomentumRibbonManifest
545
+ adaptiveMomentumRibbonManifest,
546
+ config
405
547
  };