@tradejs/strategies 1.0.6 → 1.0.9

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/index.js CHANGED
@@ -34,25 +34,25 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
35
35
 
36
36
  // src/AdaptiveMomentumRibbon/adapters/ai.ts
37
- var import_strategies, ADAPTIVE_MOMENTUM_RIBBON_CONTEXT_PROMPT, ADAPTIVE_MOMENTUM_RIBBON_PAYLOAD_PROMPT, toFiniteNumberOrNull, getLastFiniteNumber, getBias, getSignalDirection, asBoolean, getAdaptiveMomentumRibbonSnapshot, isAtLeast, isInRange, getDirectionalInvalidationDistancePct, getDirectionalRewardPct, getChannelState, getRetestPrice, getDeterministicAdaptiveMomentumRibbonQuality, getHardBlockReasonText, buildAdaptiveMomentumRibbonContext, getAdaptiveMomentumRibbonContextFromPayload, clampQuality, postProcessAnalysis, adaptiveMomentumRibbonAiAdapter;
37
+ var import_strategies, ADAPTIVE_MOMENTUM_RIBBON_CONTEXT_PROMPT, ADAPTIVE_MOMENTUM_RIBBON_PAYLOAD_PROMPT, toFiniteNumberOrNull, getLastFiniteNumber, getBias, getSignalDirection, asBoolean, getRecord, getPrimarySession, getAdaptiveMomentumRibbonSnapshot, getAdaptiveMomentumRibbonConfigSnapshot, isAtLeast, isInRange, getDirectionalInvalidationDistancePct, getDirectionalRewardPct, getDirectionalChannelExtensionPct, getChannelState, getRetestPrice, getDeterministicAdaptiveMomentumRibbonQuality, getHardBlockReasonText, buildAdaptiveMomentumRibbonContext, getAdaptiveMomentumRibbonContextFromPayload, clampQuality, postProcessAnalysis, adaptiveMomentumRibbonAiAdapter;
38
38
  var init_ai = __esm({
39
39
  "src/AdaptiveMomentumRibbon/adapters/ai.ts"() {
40
40
  "use strict";
41
41
  import_strategies = require("@tradejs/core/strategies");
42
42
  ADAPTIVE_MOMENTUM_RIBBON_CONTEXT_PROMPT = `
43
- \u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0434\u043B\u044F AdaptiveMomentumRibbon:
44
- - \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.
45
- - 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.
46
- - 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.
47
- - 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.
48
- - 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.
49
- - \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.
50
- - \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.
43
+ AdaptiveMomentumRibbon addon:
44
+ - This is a momentum entry based on an oscillator zero-cross, not a trendline breakout and not a line-reversal setup.
45
+ - LONG appears when \`signalOsc\` crosses above 0 and the ribbon switches into \`activeBuy\`; SHORT is the mirror case.
46
+ - \`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.
47
+ - \`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\`.
48
+ - \`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.
49
+ - \`quality=5\` requires very clean momentum: correct channel side, strong \`signalOsc\`, sane invalidation distance, and no coin/BTC bias conflicts.
50
+ - If \`approvalAllowedNow=false\` or \`deterministicQuality<4\`, this is usually watch mode rather than a ready live approval.
51
51
  `;
52
52
  ADAPTIVE_MOMENTUM_RIBBON_PAYLOAD_PROMPT = `
53
- - \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:
54
- signalOsc / oscillatorStrength / channelState / invalidationDistancePct / structuralRewardRiskRatio / coinBiasAligned / btcBiasAligned / deterministicQuality / approvalAllowedNow / structuralHardBlockReasons.
55
- - \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.
53
+ - \`payload.additionalIndicators.adaptiveMomentumRibbonContext\` contains a compact signal summary:
54
+ signalOsc / oscillatorStrength / channelState / channelExtensionPct / invalidationDistancePct / structuralRewardRiskRatio / coinBiasAligned / btcBiasAligned / deterministicQuality / approvalAllowedNow / structuralHardBlockReasons.
55
+ - Use this context as the primary strategy-specific interpretation instead of re-deriving it only from generic series.
56
56
  `;
57
57
  toFiniteNumberOrNull = (value) => {
58
58
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -90,11 +90,49 @@ var init_ai = __esm({
90
90
  };
91
91
  getSignalDirection = (signal) => signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
92
92
  asBoolean = (value) => value === true || value === 1;
93
+ getRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
94
+ getPrimarySession = (signal, additionalIndicators) => {
95
+ const marketContext = getRecord(
96
+ additionalIndicators?.marketContext ?? signal.additionalIndicators?.marketContext
97
+ );
98
+ const tradingSession = getRecord(marketContext?.tradingSession);
99
+ const primarySession = tradingSession?.primarySession;
100
+ if (primarySession === "asia" || primarySession === "europe" || primarySession === "us" || primarySession === "off_hours") {
101
+ return primarySession;
102
+ }
103
+ const timestamp = toFiniteNumberOrNull(signal.timestamp);
104
+ if (timestamp == null) {
105
+ return null;
106
+ }
107
+ const date = new Date(timestamp);
108
+ const minuteUtc = date.getUTCHours() * 60 + date.getUTCMinutes();
109
+ const activeSessions = [
110
+ minuteUtc >= 0 && minuteUtc < 8 * 60 ? "asia" : null,
111
+ minuteUtc >= 7 * 60 && minuteUtc < 16 * 60 ? "europe" : null,
112
+ minuteUtc >= 13 * 60 && minuteUtc < 22 * 60 ? "us" : null
113
+ ].filter(
114
+ (session) => session != null
115
+ );
116
+ if (activeSessions.includes("us")) {
117
+ return "us";
118
+ }
119
+ if (activeSessions.includes("europe")) {
120
+ return "europe";
121
+ }
122
+ if (activeSessions.includes("asia")) {
123
+ return "asia";
124
+ }
125
+ return "off_hours";
126
+ };
93
127
  getAdaptiveMomentumRibbonSnapshot = (signal) => {
94
- const additional = signal.additionalIndicators;
128
+ const additional = getRecord(signal.additionalIndicators);
95
129
  const amr = additional?.amr;
96
130
  return amr && typeof amr === "object" ? amr : {};
97
131
  };
132
+ getAdaptiveMomentumRibbonConfigSnapshot = (signal) => {
133
+ const additional = getRecord(signal.additionalIndicators);
134
+ return getRecord(additional?.amrConfigSnapshot);
135
+ };
98
136
  isAtLeast = (value, threshold) => value != null && value >= threshold;
99
137
  isInRange = (value, min, max) => value != null && value >= min && value <= max;
100
138
  getDirectionalInvalidationDistancePct = ({
@@ -126,6 +164,26 @@ var init_ai = __esm({
126
164
  }
127
165
  return signalDirection === "LONG" ? (takeProfitPrice - currentPrice) / currentPrice * 100 : (currentPrice - takeProfitPrice) / currentPrice * 100;
128
166
  };
167
+ getDirectionalChannelExtensionPct = ({
168
+ signalDirection,
169
+ currentPrice,
170
+ kcUpper,
171
+ kcLower
172
+ }) => {
173
+ if (signalDirection == null || currentPrice == null || currentPrice <= 0) {
174
+ return null;
175
+ }
176
+ if (signalDirection === "LONG") {
177
+ if (kcUpper == null || currentPrice <= kcUpper) {
178
+ return null;
179
+ }
180
+ return (currentPrice - kcUpper) / currentPrice * 100;
181
+ }
182
+ if (kcLower == null || currentPrice >= kcLower) {
183
+ return null;
184
+ }
185
+ return (kcLower - currentPrice) / currentPrice * 100;
186
+ };
129
187
  getChannelState = ({
130
188
  signalDirection,
131
189
  currentPrice,
@@ -178,8 +236,8 @@ var init_ai = __esm({
178
236
  if (context.hardBlockReasons.length > 0) {
179
237
  return 2;
180
238
  }
181
- const noBiasConflict = context.coinBiasAligned === true && context.btcBiasAligned === true;
182
239
  const biasConflictCount = Number(context.coinBiasAligned === false) + Number(context.btcBiasAligned === false);
240
+ const noBiasConflict = biasConflictCount === 0;
183
241
  const oscillatorModerate = isAtLeast(context.oscillatorStrength, 0.3);
184
242
  const oscillatorStrong = isAtLeast(context.oscillatorStrength, 0.55);
185
243
  const oscillatorElite = isAtLeast(context.oscillatorStrength, 0.9);
@@ -200,11 +258,16 @@ var init_ai = __esm({
200
258
  const structuralRrStrong = isAtLeast(context.structuralRewardRiskRatio, 1.8);
201
259
  const channelSupportive = context.channelBiasAligned === true;
202
260
  const channelExpansion = context.signalDirection === "LONG" ? context.channelState === "above_upper" : context.channelState === "below_lower";
203
- const channelInside = context.channelState === "inside_channel" || channelExpansion;
204
- if (channelSupportive && channelExpansion && oscillatorElite && invalidationTight && structuralRrStrong && noBiasConflict) {
261
+ const channelExtensionStrong = isAtLeast(context.channelExtensionPct, 0.08);
262
+ const sessionAllowsApproval = context.sessionAllowsApproval !== false;
263
+ const slowestDetector = context.momentumPeriod === 48 && context.butterworthSmoothing === 6;
264
+ if (!sessionAllowsApproval) {
265
+ return 3;
266
+ }
267
+ if (channelSupportive && channelExpansion && channelExtensionStrong && oscillatorElite && invalidationTight && structuralRrStrong && noBiasConflict) {
205
268
  return 5;
206
269
  }
207
- if (channelSupportive && channelInside && oscillatorModerate && invalidationCompact && structuralRrModerate && biasConflictCount < 2 && (biasConflictCount === 0 || channelExpansion || oscillatorStrong)) {
270
+ if (channelSupportive && channelExpansion && !slowestDetector && oscillatorModerate && invalidationCompact && structuralRrModerate && biasConflictCount < 2 && (biasConflictCount === 0 || oscillatorStrong)) {
208
271
  return 4;
209
272
  }
210
273
  return 3;
@@ -212,20 +275,25 @@ var init_ai = __esm({
212
275
  getHardBlockReasonText = (reason) => {
213
276
  switch (reason) {
214
277
  case "invalidated":
215
- 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";
278
+ return "the signal is already invalidated relative to invalidationLevel";
216
279
  case "inactive_signal_state":
217
- 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";
280
+ return "the active ribbon state does not confirm the current direction";
218
281
  case "oscillator_conflict":
219
- 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";
282
+ return "signalOsc conflicts with the signal direction";
220
283
  case "invalidation_wrong_side":
221
- 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";
284
+ return "invalidationLevel is on the wrong side of the current price";
222
285
  default:
223
286
  return reason;
224
287
  }
225
288
  };
226
- buildAdaptiveMomentumRibbonContext = (signal) => {
289
+ buildAdaptiveMomentumRibbonContext = (signal, additionalIndicators) => {
227
290
  const signalDirection = getSignalDirection(signal);
228
291
  const snapshot = getAdaptiveMomentumRibbonSnapshot(signal);
292
+ const configSnapshot = getAdaptiveMomentumRibbonConfigSnapshot(signal);
293
+ const momentumPeriod = toFiniteNumberOrNull(configSnapshot?.momentumPeriod);
294
+ const butterworthSmoothing = toFiniteNumberOrNull(
295
+ configSnapshot?.butterworthSmoothing
296
+ );
229
297
  const currentPrice = toFiniteNumberOrNull(signal.prices?.currentPrice);
230
298
  const takeProfitPrice = toFiniteNumberOrNull(signal.prices?.takeProfitPrice);
231
299
  const signalOsc = toFiniteNumberOrNull(snapshot.signalOsc);
@@ -247,6 +315,12 @@ var init_ai = __esm({
247
315
  kcLower
248
316
  });
249
317
  const channelBiasAligned = signalDirection === "LONG" ? kcMidline != null && currentPrice != null ? currentPrice >= kcMidline : null : signalDirection === "SHORT" ? kcMidline != null && currentPrice != null ? currentPrice <= kcMidline : null : null;
318
+ const channelExtensionPct = getDirectionalChannelExtensionPct({
319
+ signalDirection,
320
+ currentPrice,
321
+ kcUpper,
322
+ kcLower
323
+ });
250
324
  const invalidationDistancePct = getDirectionalInvalidationDistancePct({
251
325
  signalDirection,
252
326
  currentPrice,
@@ -268,6 +342,8 @@ var init_ai = __esm({
268
342
  );
269
343
  const coinBiasAligned = signalDirection === "LONG" ? coinBias == null ? null : coinBias === "bullish" : signalDirection === "SHORT" ? coinBias == null ? null : coinBias === "bearish" : null;
270
344
  const btcBiasAligned = signalDirection === "LONG" ? btcBias == null ? null : btcBias === "bullish" : signalDirection === "SHORT" ? btcBias == null ? null : btcBias === "bearish" : null;
345
+ const primarySession = getPrimarySession(signal, additionalIndicators);
346
+ const sessionAllowsApproval = primarySession == null ? null : primarySession === "off_hours";
271
347
  const hardBlockReasons = [];
272
348
  if (invalidated) {
273
349
  hardBlockReasons.push("invalidated");
@@ -283,6 +359,8 @@ var init_ai = __esm({
283
359
  }
284
360
  const deterministicQuality = getDeterministicAdaptiveMomentumRibbonQuality({
285
361
  signalDirection,
362
+ momentumPeriod,
363
+ butterworthSmoothing,
286
364
  entryLong,
287
365
  entryShort,
288
366
  activeBuy,
@@ -296,16 +374,21 @@ var init_ai = __esm({
296
374
  invalidationLevel,
297
375
  channelState,
298
376
  channelBiasAligned,
377
+ channelExtensionPct,
299
378
  invalidationDistancePct,
300
379
  structuralRewardRiskRatio,
301
380
  coinMaBias: coinBias,
302
381
  btcMaBias: btcBias,
303
382
  coinBiasAligned,
304
383
  btcBiasAligned,
384
+ primarySession,
385
+ sessionAllowsApproval,
305
386
  hardBlockReasons
306
387
  });
307
388
  return {
308
389
  signalDirection,
390
+ momentumPeriod,
391
+ butterworthSmoothing,
309
392
  entryLong,
310
393
  entryShort,
311
394
  activeBuy,
@@ -319,12 +402,15 @@ var init_ai = __esm({
319
402
  invalidationLevel,
320
403
  channelState,
321
404
  channelBiasAligned,
405
+ channelExtensionPct,
322
406
  invalidationDistancePct,
323
407
  structuralRewardRiskRatio,
324
408
  coinMaBias: coinBias,
325
409
  btcMaBias: btcBias,
326
410
  coinBiasAligned,
327
411
  btcBiasAligned,
412
+ primarySession,
413
+ sessionAllowsApproval,
328
414
  hardBlockReasons,
329
415
  structuralHardBlockReasons: hardBlockReasons,
330
416
  deterministicQuality,
@@ -365,9 +451,9 @@ var init_ai = __esm({
365
451
  retestPrice,
366
452
  takeProfitPrice: null,
367
453
  stopLossPrice: null,
368
- 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."),
369
- 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."),
370
- 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.")
454
+ 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."),
455
+ 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."),
456
+ 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.")
371
457
  };
372
458
  }
373
459
  return {
@@ -381,13 +467,19 @@ var init_ai = __esm({
381
467
  };
382
468
  };
383
469
  adaptiveMomentumRibbonAiAdapter = {
384
- buildPayload: ({ signal, basePayload }) => ({
385
- ...basePayload,
386
- additionalIndicators: {
387
- ...basePayload.additionalIndicators,
388
- adaptiveMomentumRibbonContext: buildAdaptiveMomentumRibbonContext(signal)
389
- }
390
- }),
470
+ buildPayload: ({ signal, basePayload }) => {
471
+ const additionalIndicators = getRecord(basePayload.additionalIndicators);
472
+ return {
473
+ ...basePayload,
474
+ additionalIndicators: {
475
+ ...additionalIndicators ?? {},
476
+ adaptiveMomentumRibbonContext: buildAdaptiveMomentumRibbonContext(
477
+ signal,
478
+ additionalIndicators
479
+ )
480
+ }
481
+ };
482
+ },
391
483
  postProcessAnalysis,
392
484
  buildSystemPromptAddon: () => `${ADAPTIVE_MOMENTUM_RIBBON_CONTEXT_PROMPT}
393
485
  ${ADAPTIVE_MOMENTUM_RIBBON_PAYLOAD_PROMPT}`,
@@ -398,27 +490,32 @@ ${ADAPTIVE_MOMENTUM_RIBBON_PAYLOAD_PROMPT}`,
398
490
  );
399
491
  return `
400
492
 
401
- \u0414\u043E\u043F. \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 AdaptiveMomentumRibbon:
493
+ Additional AdaptiveMomentumRibbon context:
494
+ - momentumPeriod=${context.momentumPeriod ?? "n/a"}
495
+ - butterworthSmoothing=${context.butterworthSmoothing ?? "n/a"}
402
496
  - signalOsc=${context.signalOsc?.toFixed?.(3) ?? "n/a"}
403
497
  - oscillatorStrength=${context.oscillatorStrength?.toFixed?.(3) ?? "n/a"}
404
498
  - channelState=${context.channelState}
405
499
  - channelBiasAligned=${context.channelBiasAligned}
500
+ - channelExtensionPct=${context.channelExtensionPct?.toFixed?.(3) ?? "n/a"}%
406
501
  - invalidationDistancePct=${context.invalidationDistancePct?.toFixed?.(3) ?? "n/a"}%
407
502
  - structuralRewardRiskRatio=${context.structuralRewardRiskRatio?.toFixed?.(3) ?? "n/a"}
408
503
  - coinBiasAligned=${context.coinBiasAligned}
409
504
  - btcBiasAligned=${context.btcBiasAligned}
505
+ - primarySession=${context.primarySession ?? "n/a"}
506
+ - sessionAllowsApproval=${context.sessionAllowsApproval}
410
507
  - deterministicQuality=${context.deterministicQuality}
411
508
  - approvalAllowedNow=${context.approvalAllowedNow}
412
509
  - hardBlockReasons=${context.hardBlockReasons.join(", ") || "none"}
413
510
 
414
- \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u0438\u043D\u0442\u0435\u0440\u043F\u0440\u0435\u0442\u0430\u0446\u0438\u0438 \u0434\u043B\u044F AdaptiveMomentumRibbon:
415
- - 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;
416
- - \u0441\u043C\u043E\u0442\u0440\u0438 \u043D\u0430 \u0441\u0442\u043E\u0440\u043E\u043D\u0443 Keltner channel, sane invalidation distance \u0438 bias alignment;
417
- - \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.
511
+ Interpretation rules for AdaptiveMomentumRibbon:
512
+ - a zero-cross alone does not make quality high;
513
+ - pay attention to Keltner channel side, sane invalidation distance, and bias alignment;
514
+ - if \`signalOsc\` already conflicts with direction or the signal is invalidated, do not treat the entry as confirmed.
418
515
  `;
419
516
  },
420
- mapEntryRuntimeFromConfig: (config7) => (0, import_strategies.mapAiRuntimeFromConfig)(
421
- config7
517
+ mapEntryRuntimeFromConfig: (config8) => (0, import_strategies.mapAiRuntimeFromConfig)(
518
+ config8
422
519
  )
423
520
  };
424
521
  }
@@ -431,8 +528,8 @@ var init_ml = __esm({
431
528
  "use strict";
432
529
  import_strategies2 = require("@tradejs/core/strategies");
433
530
  adaptiveMomentumRibbonMlAdapter = {
434
- mapEntryRuntimeFromConfig: (config7) => (0, import_strategies2.mapMlRuntimeFromConfig)(
435
- config7
531
+ mapEntryRuntimeFromConfig: (config8) => (0, import_strategies2.mapMlRuntimeFromConfig)(
532
+ config8
436
533
  )
437
534
  };
438
535
  }
@@ -469,7 +566,7 @@ var init_ml2 = __esm({
469
566
  "use strict";
470
567
  import_strategies3 = require("@tradejs/core/strategies");
471
568
  breakoutMlAdapter = {
472
- mapEntryRuntimeFromConfig: (config7) => (0, import_strategies3.mapMlRuntimeFromConfig)(config7)
569
+ mapEntryRuntimeFromConfig: (config8) => (0, import_strategies3.mapMlRuntimeFromConfig)(config8)
473
570
  };
474
571
  }
475
572
  });
@@ -504,8 +601,8 @@ var init_ai3 = __esm({
504
601
  "use strict";
505
602
  import_strategies4 = require("@tradejs/core/strategies");
506
603
  maStrategyAiAdapter = {
507
- mapEntryRuntimeFromConfig: (config7) => (0, import_strategies4.mapAiRuntimeFromConfig)(
508
- config7
604
+ mapEntryRuntimeFromConfig: (config8) => (0, import_strategies4.mapAiRuntimeFromConfig)(
605
+ config8
509
606
  )
510
607
  };
511
608
  }
@@ -518,8 +615,8 @@ var init_ml3 = __esm({
518
615
  "use strict";
519
616
  import_strategies5 = require("@tradejs/core/strategies");
520
617
  maStrategyMlAdapter = {
521
- mapEntryRuntimeFromConfig: (config7) => (0, import_strategies5.mapMlRuntimeFromConfig)(
522
- config7
618
+ mapEntryRuntimeFromConfig: (config8) => (0, import_strategies5.mapMlRuntimeFromConfig)(
619
+ config8
523
620
  )
524
621
  };
525
622
  }
@@ -540,18 +637,6 @@ var init_manifest3 = __esm({
540
637
  }
541
638
  });
542
639
 
543
- // src/ReverseTrendLine/hooks.ts
544
- var import_strategies6, reverseTrendLineBeforePlaceOrderHook;
545
- var init_hooks = __esm({
546
- "src/ReverseTrendLine/hooks.ts"() {
547
- "use strict";
548
- import_strategies6 = require("@tradejs/node/strategies");
549
- reverseTrendLineBeforePlaceOrderHook = (0, import_strategies6.createCloseOppositeBeforePlaceOrderHook)({
550
- isEnabled: (config7) => Boolean(config7.CLOSE_OPPOSITE_POSITIONS)
551
- });
552
- }
553
- });
554
-
555
640
  // src/ReverseTrendLine/guardrails.ts
556
641
  var REVERSE_TRENDLINE_NEAR_LINE_PCT, REVERSE_TRENDLINE_FAILED_BOUNCE_PCT, REVERSE_TRENDLINE_TIMING_WINDOW, MIN_REJECTION_WICK_PCT, MIN_REJECTION_STRENGTH_PCT, FOLLOW_THROUGH_STRENGTH_PCT, toFiniteNumberOrNull2, getLastFiniteNumber2, getBias2, getSpreadPct, getTrendLineFromPayload, deriveDirectionFromMode, getSortedTrendLinePoints, buildTrendLineEvaluator, getCurrentCandle, getLineTouched, getCloseOnBounceSide, getFailedBounceBreak, getBodyAligned, getRejectionWickPct, getRejectionStrengthPct, getRejectionBar, buildReverseTrendlineStructuralContext, buildReverseTrendlineTimingContext;
557
642
  var init_guardrails = __esm({
@@ -943,30 +1028,30 @@ var init_guardrails = __esm({
943
1028
  });
944
1029
 
945
1030
  // src/ReverseTrendLine/adapters/ai.ts
946
- var import_strategies7, REVERSE_TRENDLINE_CONTEXT_PROMPT, REVERSE_TRENDLINE_PAYLOAD_PROMPT, getReverseTrendlineBiasConflictState, getDeterministicReverseTrendlineQuality, getDeterministicReverseTrendlineRejectionScore, buildReverseTrendlineAiContext, getReverseTrendlineContextFromPayload, getHardBlockReasonText2, reverseTrendLineAiAdapter;
1031
+ var import_strategies6, REVERSE_TRENDLINE_CONTEXT_PROMPT, REVERSE_TRENDLINE_PAYLOAD_PROMPT, getReverseTrendlineBiasConflictState, getDeterministicReverseTrendlineQuality, getDeterministicReverseTrendlineRejectionScore, buildReverseTrendlineAiContext, getReverseTrendlineContextFromPayload, getHardBlockReasonText2, reverseTrendLineAiAdapter;
947
1032
  var init_ai4 = __esm({
948
1033
  "src/ReverseTrendLine/adapters/ai.ts"() {
949
1034
  "use strict";
950
- import_strategies7 = require("@tradejs/core/strategies");
1035
+ import_strategies6 = require("@tradejs/core/strategies");
951
1036
  init_guardrails();
952
1037
  REVERSE_TRENDLINE_CONTEXT_PROMPT = `
953
- \u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0434\u043B\u044F ReverseTrendLine:
954
- - \u042D\u0442\u043E \u043D\u0435 breakout-\u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044F, \u0430 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044F \u043D\u0430 \u043E\u0442\u0441\u043A\u043E\u043A \u043E\u0442 \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u043B\u0438\u043D\u0438\u0438.
955
- - \u0414\u043B\u044F LONG \u043F\u043E support line (trendline.mode="lows") \u043D\u0443\u0436\u0435\u043D \u043A\u0430\u0441\u0430\u043D\u0438\u0435/\u043B\u043E\u0436\u043D\u044B\u0439 \u043F\u0440\u043E\u043A\u043E\u043B \u043B\u0438\u043D\u0438\u0438 \u0438 \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u0438\u044F \u0432\u044B\u0448\u0435 \u043D\u0435\u0435.
956
- - \u0414\u043B\u044F SHORT \u043F\u043E resistance line (trendline.mode="highs") \u043D\u0443\u0436\u0435\u043D \u043A\u0430\u0441\u0430\u043D\u0438\u0435/\u043B\u043E\u0436\u043D\u044B\u0439 \u043F\u0440\u043E\u043A\u043E\u043B \u043B\u0438\u043D\u0438\u0438 \u0438 \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u0438\u044F \u043D\u0438\u0436\u0435 \u043D\u0435\u0435.
957
- - \u0415\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u0443\u0436\u0435 \u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E \u043F\u0440\u043E\u0431\u0438\u043B\u0430 \u043B\u0438\u043D\u0438\u044E \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443, \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u043F\u043E\u043B\u043E\u0436\u043D\u0443\u044E \u043E\u0442\u0441\u043A\u043E\u043A\u0443, \u044D\u0442\u043E \u043D\u0435 bounce setup: direction=null \u0438 quality <= 2.
958
- - \u0414\u043B\u044F bounce-\u0441\u0435\u0442\u0430\u043F\u043E\u0432 \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u0435\u0435 \u0440\u0435\u0430\u043A\u0446\u0438\u044F \u0441\u0432\u0435\u0447\u0438 \u043D\u0430 \u043B\u0438\u043D\u0438\u0438, rejection wick, \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u0438\u044F \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u0443\u044E \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u0438 follow-through \u043D\u0430 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C \u0431\u0430\u0440\u0435.
959
- - \u0415\u0441\u043B\u0438 payload.additionalIndicators.reverseTrendlineContext.failedBounceBreak=true, \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0441\u0438\u0433\u043D\u0430\u043B \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C.
960
- - \u0415\u0441\u043B\u0438 payload.additionalIndicators.reverseTrendlineContext.entryTiming \u043D\u0435 \u0440\u0430\u0432\u0435\u043D ready_rejection \u0438\u043B\u0438 ready_follow_through, \u043E\u0431\u044B\u0447\u043D\u043E quality <= 3.
961
- - \u0411\u0430\u0437\u043E\u0432\u044B\u0439 deterministic approve \u0434\u043B\u044F same-bar rejection \u0434\u0430\u0435\u0442\u0441\u044F \u043D\u0435 \u0432\u0441\u0435\u043C:
962
- - \u0441\u0438\u043B\u044C\u043D\u044B\u0439 conflict-only rejection \u043C\u043E\u0436\u0435\u0442 \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C quality=4;
963
- - \u0447\u0430\u0441\u0442\u044C same-bar rejection \u0441 conflictState=none \u0438\u043B\u0438 both \u043C\u043E\u0436\u0435\u0442 \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C quality=4 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u0438 \u043E\u0447\u0435\u043D\u044C \u0441\u0438\u043B\u044C\u043D\u043E\u043C deterministic rejection score.
964
- - \u0414\u043B\u044F SHORT bounce setup \u0441 btc_bias_conflict \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439 quality: \u0442\u0430\u043A\u0438\u0435 \u043A\u0435\u0439\u0441\u044B \u043E\u0431\u044B\u0447\u043D\u043E \u043E\u0441\u0442\u0430\u044E\u0442\u0441\u044F watch, \u0435\u0441\u043B\u0438 \u043D\u0435\u0442 \u0433\u043E\u0440\u0430\u0437\u0434\u043E \u0431\u043E\u043B\u0435\u0435 \u0441\u0438\u043B\u044C\u043D\u043E\u0433\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F.
965
- - \u0415\u0441\u043B\u0438 deterministicRejectionScore \u043D\u0438\u0437\u043A\u0438\u0439 \u0438\u043B\u0438 \u0441\u0440\u0435\u0434\u043D\u0438\u0439, \u043D\u0435 \u043F\u0440\u0438\u0434\u0443\u043C\u044B\u0432\u0430\u0439 quality=4 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0441\u0432\u0435\u0447\u0430 \u0432\u0438\u0437\u0443\u0430\u043B\u044C\u043D\u043E \u043F\u043E\u0445\u043E\u0436\u0430 \u043D\u0430 rejection.
1038
+ ReverseTrendLine addon:
1039
+ - This is a trendline bounce strategy, not a breakout strategy.
1040
+ - For LONG on a support line (\`trendline.mode="lows"\`), you need a touch or false break of the line followed by a close back above it.
1041
+ - For SHORT on a resistance line (\`trendline.mode="highs"\`), you need a touch or false break of the line followed by a close back below it.
1042
+ - If price has already broken through the line with conviction in the opposite direction, this is not a bounce setup: use \`direction=null\` and \`quality <= 2\`.
1043
+ - For bounce setups, prioritize candle reaction at the line, rejection wick quality, a close on the correct side, and next-bar follow-through.
1044
+ - If \`payload.additionalIndicators.reverseTrendlineContext.failedBounceBreak=true\`, do not treat the signal as structurally confirmed.
1045
+ - If \`payload.additionalIndicators.reverseTrendlineContext.entryTiming\` is not \`ready_rejection\` or \`ready_follow_through\`, quality is usually \`<= 3\`.
1046
+ - Baseline deterministic approval for same-bar rejection is intentionally strict:
1047
+ - a strong conflict-only rejection may qualify for \`quality=4\`;
1048
+ - some same-bar rejections with \`conflictState=none\` or \`both\` may reach \`quality=4\` only with a very strong deterministic rejection score.
1049
+ - For SHORT bounce setups with \`btc_bias_conflict\`, do not overstate quality; those cases usually stay in watch mode unless the structural confirmation is much stronger.
1050
+ - If \`deterministicRejectionScore\` is low or medium, do not assign \`quality=4\` just because the candle visually resembles a rejection.
966
1051
  `;
967
1052
  REVERSE_TRENDLINE_PAYLOAD_PROMPT = `
968
- - \u0412 payload.figures.trendline \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u044F \u043B\u0438\u043D\u0438\u0438.
969
- - \u0412 payload.additionalIndicators.reverseTrendlineContext \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F \u043A\u0440\u0430\u0442\u043A\u0430\u044F \u0441\u0432\u043E\u0434\u043A\u0430 bounce-\u043B\u043E\u0433\u0438\u043A\u0438: \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435, \u0440\u0430\u0441\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0446\u0435\u043D\u044B \u0434\u043E \u043B\u0438\u043D\u0438\u0438, \u0431\u044B\u043B \u043B\u0438 \u043A\u0430\u0441\u0430\u043D\u0438\u0435 \u043B\u0438\u043D\u0438\u0438, \u0431\u044B\u043B\u0430 \u043B\u0438 rejection-\u0441\u0432\u0435\u0447\u0430, \u0441\u0438\u043B\u0443 rejection, timing-stage, \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B bias \u0438 deterministicRejectionScore.
1053
+ - \`payload.figures.trendline\` contains the line geometry.
1054
+ - \`payload.additionalIndicators.reverseTrendlineContext\` contains a compact bounce summary: direction, price distance to the line, whether the line was touched, whether there was a rejection candle, rejection strength, timing stage, bias conflicts, and \`deterministicRejectionScore\`.
970
1055
  `;
971
1056
  getReverseTrendlineBiasConflictState = (context) => {
972
1057
  const coinConflict = context.coinBiasAligned === false;
@@ -1007,12 +1092,16 @@ var init_ai4 = __esm({
1007
1092
  if (quality4FollowThrough) {
1008
1093
  return 4;
1009
1094
  }
1010
- const quality4ConflictRejection = context.entryTiming === "ready_rejection" && conflictOnly && rejectionStrengthPct >= 0.45 && touches >= 5 && !(context.signalDirection === "SHORT" && biasConflictState === "btc_only");
1095
+ const quality4ConflictRejection = context.entryTiming === "ready_rejection" && conflictOnly && rejectionStrengthPct >= 0.45 && touches >= 5 && !(context.signalDirection === "SHORT" && biasConflictState === "coin_only" && distance <= 180 && rejectionWickPct <= 0.45) && !(context.signalDirection === "SHORT" && biasConflictState === "btc_only");
1011
1096
  if (quality4ConflictRejection) {
1012
1097
  return 4;
1013
1098
  }
1014
1099
  const rejectionScore = getDeterministicReverseTrendlineRejectionScore(context);
1015
- const quality4ScoredRejection = context.entryTiming === "ready_rejection" && (biasConflictState === "none" || biasConflictState === "both") && rejectionScore != null && rejectionScore >= 7;
1100
+ const quality4EliteShortBtcOnlyRejection = context.entryTiming === "ready_rejection" && context.signalDirection === "SHORT" && biasConflictState === "btc_only" && rejectionScore != null && rejectionScore >= 5 && rejectionWickPct >= 0.6 && touches >= 5 && distance <= 200;
1101
+ if (quality4EliteShortBtcOnlyRejection) {
1102
+ return 4;
1103
+ }
1104
+ const quality4ScoredRejection = context.entryTiming === "ready_rejection" && (biasConflictState === "none" || biasConflictState === "both") && rejectionScore != null && rejectionScore >= 7 && !(context.signalDirection === "SHORT" && biasConflictState === "none" && distance <= 150 && (rejectionWickPct >= 0.7 || rejectionStrengthPct >= 1.3));
1016
1105
  if (quality4ScoredRejection) {
1017
1106
  return 4;
1018
1107
  }
@@ -1099,11 +1188,11 @@ var init_ai4 = __esm({
1099
1188
  getHardBlockReasonText2 = (reason) => {
1100
1189
  switch (reason) {
1101
1190
  case "failed_bounce_break":
1102
- return "\u0446\u0435\u043D\u0430 \u043F\u0440\u043E\u0431\u0438\u043B\u0430 \u043B\u0438\u043D\u0438\u044E \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443, \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u043F\u043E\u043B\u043E\u0436\u043D\u0443\u044E \u043E\u0442\u0441\u043A\u043E\u043A\u0443";
1191
+ return "price broke through the line against the intended bounce";
1103
1192
  case "coin_bias_conflict":
1104
- return "bias \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C bounce-\u0441\u0434\u0435\u043B\u043A\u0438";
1193
+ return "coin bias conflicts with the bounce direction";
1105
1194
  case "btc_bias_conflict":
1106
- return "BTC-\u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C bounce-\u0441\u0434\u0435\u043B\u043A\u0438";
1195
+ return "BTC context conflicts with the bounce direction";
1107
1196
  default:
1108
1197
  return reason;
1109
1198
  }
@@ -1142,9 +1231,9 @@ var init_ai4 = __esm({
1142
1231
  retestPrice: context.currentLinePrice ?? null,
1143
1232
  takeProfitPrice: null,
1144
1233
  stopLossPrice: null,
1145
- qualityReason: context.hardBlockReasons.length > 0 ? `ReverseTrendLine guardrail: ${context.hardBlockReasons.map(getHardBlockReasonText2).join("; ")}.` : "ReverseTrendLine deterministic quality: \u0434\u043B\u044F bounce \u043D\u0443\u0436\u0435\u043D \u043B\u0438\u0431\u043E \u0441\u0438\u043B\u044C\u043D\u044B\u0439 conflict-only rejection, \u043B\u0438\u0431\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u0439 aligned follow-through.",
1146
- triggerInvalidation: context.hardBlockReasons.length > 0 ? `\u0416\u0434\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0439 bounce setup: ${context.hardBlockReasons.map(getHardBlockReasonText2).join("; ")}.` : "\u0416\u0434\u0430\u0442\u044C \u043A\u0430\u0441\u0430\u043D\u0438\u0435 \u043B\u0438\u043D\u0438\u0438, rejection-\u0441\u0432\u0435\u0447\u0443 \u0438 \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u0438\u044F \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u0443\u044E \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u043B\u0438\u043D\u0438\u0438.",
1147
- comment: context.hardBlockReasons.length > 0 ? `ReverseTrendLine guardrail \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043B \u0432\u0445\u043E\u0434: ${context.hardBlockReasons.map(getHardBlockReasonText2).join("; ")}.` : "ReverseTrendLine \u043F\u043E\u043A\u0430 \u043F\u0435\u0440\u0435\u0432\u043E\u0434\u0438\u0442 \u0441\u0435\u0442\u0430\u043F \u0432 watch \u0434\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u043E\u0442\u0441\u043A\u043E\u043A\u0430."
1234
+ qualityReason: context.hardBlockReasons.length > 0 ? `ReverseTrendLine guardrail: ${context.hardBlockReasons.map(getHardBlockReasonText2).join("; ")}.` : "ReverseTrendLine deterministic quality requires either a strong conflict-only rejection or a confirmed aligned follow-through for a bounce.",
1235
+ triggerInvalidation: context.hardBlockReasons.length > 0 ? `Wait for a new bounce setup: ${context.hardBlockReasons.map(getHardBlockReasonText2).join("; ")}.` : "Wait for a line touch, a rejection candle, and a close held on the correct side of the line.",
1236
+ comment: context.hardBlockReasons.length > 0 ? `ReverseTrendLine guardrail blocked the entry: ${context.hardBlockReasons.map(getHardBlockReasonText2).join("; ")}.` : "ReverseTrendLine keeps the setup in watch mode until the bounce is confirmed."
1148
1237
  };
1149
1238
  },
1150
1239
  buildSystemPromptAddon: () => `${REVERSE_TRENDLINE_CONTEXT_PROMPT}
@@ -1153,7 +1242,7 @@ ${REVERSE_TRENDLINE_PAYLOAD_PROMPT}`,
1153
1242
  const context = getReverseTrendlineContextFromPayload(payload, signal);
1154
1243
  return `
1155
1244
 
1156
- \u0414\u043E\u043F. \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 ReverseTrendLine:
1245
+ Additional ReverseTrendLine context:
1157
1246
  - entryTiming=${context.entryTiming}
1158
1247
  - lineTouchedNow=${context.lineTouchedNow}
1159
1248
  - closeOnBounceSide=${context.closeOnBounceSide}
@@ -1168,15 +1257,15 @@ ${REVERSE_TRENDLINE_PAYLOAD_PROMPT}`,
1168
1257
  - approvalAllowedNow=${context.approvalAllowedNow}
1169
1258
  - hardBlockReasons=${context.hardBlockReasons.join(", ") || "none"}
1170
1259
 
1171
- \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u0438\u043D\u0442\u0435\u0440\u043F\u0440\u0435\u0442\u0430\u0446\u0438\u0438 \u0434\u043B\u044F ReverseTrendLine:
1172
- - \u0438\u0441\u043A\u0430\u0442\u044C \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0440\u0435\u0430\u043A\u0446\u0438\u0438 \u043E\u0442 \u043B\u0438\u043D\u0438\u0438, \u0430 \u043D\u0435 \u043F\u0440\u043E\u0431\u043E\u044F \u0447\u0435\u0440\u0435\u0437 \u043B\u0438\u043D\u0438\u044E;
1173
- - \u0435\u0441\u043B\u0438 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C failedBounceBreak=true, \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C;
1174
- - \u0435\u0441\u043B\u0438 setup \u0435\u0449\u0435 \u0432 \u0441\u0442\u0430\u0434\u0438\u0438 wait_touch / wait_reaction_confirmation / stale_reaction, \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0442\u044C quality.
1175
- - \u0435\u0441\u043B\u0438 deterministicRejectionScore \u0432\u044B\u0441\u043E\u043A\u0438\u0439, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0435\u0433\u043E \u043A\u0430\u043A \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 \u0441\u0438\u0433\u043D\u0430\u043B \u0442\u043E\u043B\u044C\u043A\u043E \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u044B\u043C bounce-\u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u043E\u043C, \u0430 \u043D\u0435 \u043A\u0430\u043A \u0437\u0430\u043C\u0435\u043D\u0443 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B.
1260
+ Interpretation rules for ReverseTrendLine:
1261
+ - look for structural confirmation of a reaction from the line, not a breakout through the line;
1262
+ - if \`failedBounceBreak=true\` is already present, do not treat the signal as confirmed;
1263
+ - if the setup is still in \`wait_touch\`, \`wait_reaction_confirmation\`, or \`stale_reaction\`, do not overstate quality;
1264
+ - if \`deterministicRejectionScore\` is high, use it only as an extra signal together with the proper bounce context, not as a replacement for structure.
1176
1265
  `;
1177
1266
  },
1178
- mapEntryRuntimeFromConfig: (config7) => (0, import_strategies7.mapAiRuntimeFromConfig)(
1179
- config7
1267
+ mapEntryRuntimeFromConfig: (config8) => (0, import_strategies6.mapAiRuntimeFromConfig)(
1268
+ config8
1180
1269
  )
1181
1270
  };
1182
1271
  }
@@ -1187,18 +1276,135 @@ var reverseTrendLineManifest;
1187
1276
  var init_manifest4 = __esm({
1188
1277
  "src/ReverseTrendLine/manifest.ts"() {
1189
1278
  "use strict";
1190
- init_hooks();
1191
1279
  init_ai4();
1192
1280
  reverseTrendLineManifest = {
1193
1281
  name: "ReverseTrendLine",
1194
- hooks: {
1195
- beforePlaceOrder: reverseTrendLineBeforePlaceOrderHook
1196
- },
1197
1282
  aiAdapter: reverseTrendLineAiAdapter
1198
1283
  };
1199
1284
  }
1200
1285
  });
1201
1286
 
1287
+ // src/TrendShift/adapters/ai.ts
1288
+ var import_strategies7, getTrendShiftContext, reasonText, trendShiftAiAdapter;
1289
+ var init_ai5 = __esm({
1290
+ "src/TrendShift/adapters/ai.ts"() {
1291
+ "use strict";
1292
+ import_strategies7 = require("@tradejs/core/strategies");
1293
+ getTrendShiftContext = (payload) => {
1294
+ const additional = payload.additionalIndicators;
1295
+ const raw = additional?.trendShiftContext ?? {};
1296
+ const hardBlockReasons = [];
1297
+ if (!raw.confirmedFlip) {
1298
+ hardBlockReasons.push("unconfirmed_flip");
1299
+ }
1300
+ if (!raw.flipDistanceOk) {
1301
+ hardBlockReasons.push("weak_flip_distance");
1302
+ }
1303
+ if (raw.coinBiasAligned === false) {
1304
+ hardBlockReasons.push("coin_bias_conflict");
1305
+ }
1306
+ const slopeAbs = Math.abs(raw.avgSlopePct ?? 0);
1307
+ const distanceAtrRatio = raw.distanceAtrRatio ?? 0;
1308
+ const closeVsAvgPctAbs = Math.abs(raw.closeVsAvgPct ?? 0);
1309
+ let deterministicQuality = 3;
1310
+ if (hardBlockReasons.length > 0) {
1311
+ deterministicQuality = raw.confirmedFlip ? 2 : 1;
1312
+ } else if (distanceAtrRatio >= 0.8 && slopeAbs >= 0.09 && closeVsAvgPctAbs >= 0.12) {
1313
+ deterministicQuality = 5;
1314
+ } else if (distanceAtrRatio >= 0.45 && slopeAbs >= 0.04 && closeVsAvgPctAbs >= 0.05) {
1315
+ deterministicQuality = 4;
1316
+ }
1317
+ return {
1318
+ ...raw,
1319
+ deterministicQuality,
1320
+ approvalAllowedNow: deterministicQuality >= 4,
1321
+ hardBlockReasons
1322
+ };
1323
+ };
1324
+ reasonText = (reason) => {
1325
+ switch (reason) {
1326
+ case "unconfirmed_flip":
1327
+ return "the internal flip is not confirmed yet";
1328
+ case "weak_flip_distance":
1329
+ return "price moved away from the adaptive average too weakly";
1330
+ case "coin_bias_conflict":
1331
+ return "coin MA bias conflicts with the flip direction";
1332
+ default:
1333
+ return reason;
1334
+ }
1335
+ };
1336
+ trendShiftAiAdapter = {
1337
+ buildPayload: ({ signal, basePayload }) => ({
1338
+ ...basePayload,
1339
+ additionalIndicators: {
1340
+ ...basePayload.additionalIndicators,
1341
+ trendShiftContext: signal.additionalIndicators?.trendShiftContext
1342
+ }
1343
+ }),
1344
+ postProcessAnalysis: ({ payload, analysis }) => {
1345
+ const context = getTrendShiftContext(payload);
1346
+ const requestedDirection = analysis.direction === "LONG" || analysis.direction === "SHORT" ? analysis.direction : context.signalDirection;
1347
+ if (context.approvalAllowedNow === true && requestedDirection != null) {
1348
+ return {
1349
+ ...analysis,
1350
+ direction: requestedDirection,
1351
+ quality: context.deterministicQuality,
1352
+ approved: true
1353
+ };
1354
+ }
1355
+ return {
1356
+ ...analysis,
1357
+ direction: null,
1358
+ quality: context.deterministicQuality,
1359
+ approved: false,
1360
+ rejectReason: context.hardBlockReasons.length > 0 ? context.hardBlockReasons.map(reasonText).join("; ") : "the flip still does not look strong enough for live approval"
1361
+ };
1362
+ },
1363
+ buildHumanPromptAddon: ({ payload }) => {
1364
+ const context = getTrendShiftContext(payload);
1365
+ return `
1366
+ Additional TrendShift context:
1367
+ - signalDirection=${context.signalDirection ?? "n/a"}
1368
+ - confirmedFlip=${String(context.confirmedFlip)}
1369
+ - bullFlip=${String(context.bullFlip)}
1370
+ - bearFlip=${String(context.bearFlip)}
1371
+ - flipDistanceOk=${String(context.flipDistanceOk)}
1372
+ - closeVsAvgPct=${String(context.closeVsAvgPct ?? "n/a")}
1373
+ - bandWidthPct=${String(context.bandWidthPct ?? "n/a")}
1374
+ - avgSlopePct=${String(context.avgSlopePct ?? "n/a")}
1375
+ - distanceAtrRatio=${String(context.distanceAtrRatio ?? "n/a")}
1376
+ - coinBias=${context.coinBias ?? "n/a"}
1377
+ - coinBiasAligned=${String(context.coinBiasAligned)}
1378
+ - deterministicQuality=${context.deterministicQuality}
1379
+ - approvalAllowedNow=${context.approvalAllowedNow}
1380
+ - hardBlockReasons=${JSON.stringify(context.hardBlockReasons)}
1381
+
1382
+ Interpretation rules for TrendShift:
1383
+ - This is a trend-state flip strategy, not a forecast of future impulse.
1384
+ - If approvalAllowedNow=false, do not describe the signal as a fully confirmed live entry.
1385
+ - If hardBlockReasons is not empty, explain exactly what is still missing for confirmation.
1386
+ `.trim();
1387
+ },
1388
+ mapEntryRuntimeFromConfig: (config8) => (0, import_strategies7.mapAiRuntimeFromConfig)(
1389
+ config8
1390
+ )
1391
+ };
1392
+ }
1393
+ });
1394
+
1395
+ // src/TrendShift/manifest.ts
1396
+ var trendShiftManifest;
1397
+ var init_manifest5 = __esm({
1398
+ "src/TrendShift/manifest.ts"() {
1399
+ "use strict";
1400
+ init_ai5();
1401
+ trendShiftManifest = {
1402
+ name: "TrendShift",
1403
+ aiAdapter: trendShiftAiAdapter
1404
+ };
1405
+ }
1406
+ });
1407
+
1202
1408
  // src/TrendLine/guardrails.ts
1203
1409
  var TRENDLINE_CLEAR_BREAK_PCT, TRENDLINE_TIMING_WINDOW, WEAK_CLEAN_BREAK_ATR_RATIO_MAX, COMPRESSED_CLEAN_BREAK_ATR_RATIO_MAX, COMPRESSED_CLEAN_BREAK_DISTANCE_MAX, COMPRESSED_CLEAN_BREAK_TOUCHES_MIN, WEAK_LONG_FAR_BREAK_ATR_RATIO_MAX, WEAK_LONG_FAR_BREAK_DISTANCE_MIN, WEAK_LONG_FAR_BREAK_BTC_SPREAD_MAX, toFiniteNumberOrNull3, getLastFiniteNumber3, getFiniteTailNumbers, getBias3, getSpreadPct2, getTrendLineFromPayload2, getSortedTrendLinePoints2, buildTrendLineEvaluator2, getBreakoutSide, getClearBreakAtPct, getLineSlopeDirection, buildTrendlineStructuralContext, buildTrendlineTimingContext;
1204
1410
  var init_guardrails2 = __esm({
@@ -1542,35 +1748,49 @@ var init_guardrails2 = __esm({
1542
1748
 
1543
1749
  // src/TrendLine/adapters/ai.ts
1544
1750
  var import_strategies8, TRENDLINE_CONTEXT_PROMPT, TRENDLINE_PAYLOAD_PROMPT, buildTrendlineContext, formatPromptNumber, getHardBlockReasonText3, mergeShortText, getDeterministicTrendlineQuality, getDeterministicTrendlineQualityReason, getTrendlineContextFromPayload, trendLineAiAdapter;
1545
- var init_ai5 = __esm({
1751
+ var init_ai6 = __esm({
1546
1752
  "src/TrendLine/adapters/ai.ts"() {
1547
1753
  "use strict";
1548
1754
  import_strategies8 = require("@tradejs/core/strategies");
1549
1755
  init_guardrails2();
1550
1756
  TRENDLINE_CONTEXT_PROMPT = `
1551
- \u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0434\u043B\u044F trendline-\u0441\u0435\u0442\u0430\u043F\u043E\u0432:
1552
- - \u042D\u0442\u043E \u0441\u0435\u0442\u0430\u043F \u043D\u0430 \u043E\u0441\u043D\u043E\u0432\u0435 \u043F\u0440\u043E\u0431\u043E\u044F/\u0440\u0435\u0430\u043A\u0446\u0438\u0438 \u043E\u0442 \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u043B\u0438\u043D\u0438\u0438; \u043F\u043E\u043B\u0435 payload.figures.trendline \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u044E \u044D\u0442\u043E\u0439 \u043B\u0438\u043D\u0438\u0438, \u0430 payload.additionalIndicators.trendlineContext \u2014 \u043A\u0440\u0430\u0442\u043A\u0443\u044E \u0441\u0432\u043E\u0434\u043A\u0443 \u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0446\u0435\u043D\u044B \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u043B\u0438\u043D\u0438\u0438.
1553
- - \u0414\u043B\u044F TrendLine \u0440\u043E\u043B\u044C \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u0438/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B \u0446\u0435\u043D\u044B \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u0435\u0435 \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u043D\u044B\u0445 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0439.
1554
- - \u041A\u0430\u0441\u0430\u043D\u0438\u044F \u0443\u0441\u0438\u043B\u0438\u0432\u0430\u044E\u0442 \u043B\u0438\u043D\u0438\u044E, \u043D\u043E \u0441\u0430\u043C\u0438 \u043F\u043E \u0441\u0435\u0431\u0435 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044E\u0442 \u0441\u0438\u0433\u043D\u0430\u043B. \u0411\u0435\u0437 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F/\u0440\u0435\u0442\u0435\u0441\u0442\u0430 \u043D\u0435 \u043F\u043E\u0432\u044B\u0448\u0430\u0439 quality \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u0437-\u0437\u0430 \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u043A\u0430\u0441\u0430\u043D\u0438\u0439.
1555
- - \u0414\u043B\u044F SHORT \u043F\u043E rising support (trendline.mode="lows") \u043E\u0431\u044B\u0447\u043D\u043E \u043D\u0443\u0436\u0435\u043D \u043B\u0438\u0431\u043E \u044F\u0432\u043D\u044B\u0439 \u0443\u0445\u043E\u0434 \u0446\u0435\u043D\u044B \u043D\u0438\u0436\u0435 \u043B\u0438\u043D\u0438\u0438, \u043B\u0438\u0431\u043E \u0440\u0435\u0442\u0435\u0441\u0442 \u043B\u0438\u043D\u0438\u0438 \u0441\u043D\u0438\u0437\u0443 \u0441 \u043E\u0442\u0431\u043E\u0435\u043C. \u0415\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u043E\u0441\u0442\u0430\u0435\u0442\u0441\u044F \u043D\u0430\u0434 \u043B\u0438\u043D\u0438\u0435\u0439 \u0438\u043B\u0438 \u043F\u0440\u044F\u043C\u043E \u043D\u0430 \u043D\u0435\u0439, \u043E\u0431\u044B\u0447\u043D\u043E direction=null \u0438 quality <= 2.
1556
- - \u0414\u043B\u044F LONG \u043F\u043E descending resistance (trendline.mode="highs") \u0437\u0435\u0440\u043A\u0430\u043B\u044C\u043D\u043E: \u043D\u0443\u0436\u0435\u043D \u0432\u044B\u0445\u043E\u0434 \u0432\u044B\u0448\u0435 \u043B\u0438\u043D\u0438\u0438 \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442 \u0441\u0432\u0435\u0440\u0445\u0443. \u0415\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u043F\u043E\u0434 \u043B\u0438\u043D\u0438\u0435\u0439 \u0438\u043B\u0438 \u043F\u0440\u044F\u043C\u043E \u043D\u0430 \u043D\u0435\u0439, \u043E\u0431\u044B\u0447\u043D\u043E direction=null \u0438 quality <= 2.
1557
- - \u0415\u0441\u043B\u0438 payload.additionalIndicators.trendlineContext.nearLineNoise=true, \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u044D\u0442\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C \u043F\u0440\u043E\u0431\u043E\u0435\u043C: \u0447\u0430\u0449\u0435 quality <= 2-3 \u0438 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u0440\u0435\u0442\u0435\u0441\u0442\u0430/\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F.
1558
- - \u0415\u0441\u043B\u0438 payload.additionalIndicators.trendlineContext.coinBiasAligned=false \u0438\u043B\u0438 btcBiasAligned=false, \u0442\u0440\u0430\u043A\u0442\u0443\u0439 \u044D\u0442\u043E \u043A\u0430\u043A \u043F\u0440\u044F\u043C\u043E\u0439 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u0430. \u0412 \u0442\u0430\u043A\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u043E\u0431\u044B\u0447\u043D\u043E \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C, \u0435\u0441\u043B\u0438 \u043D\u0435\u0442 \u0438\u0441\u043A\u043B\u044E\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0433\u043E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0433\u043E \u043F\u0440\u0435\u0438\u043C\u0443\u0449\u0435\u0441\u0442\u0432\u0430.
1559
- - \u0415\u0441\u043B\u0438 payload.additionalIndicators.trendlineContext.clearBreak=false \u0438 \u0446\u0435\u043D\u0430 \u0432\u0441\u0435 \u0435\u0449\u0435 \u043E\u043A\u043E\u043B\u043E \u043B\u0438\u043D\u0438\u0438, \u043D\u0435 \u043E\u043F\u0438\u0441\u044B\u0432\u0430\u0439 \u044D\u0442\u043E \u043A\u0430\u043A "\u0447\u0438\u0441\u0442\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439".
1560
- - \u0415\u0441\u043B\u0438 clearBreak=true, \u043D\u043E trendlineContext.weakCleanBreak=true, \u0442\u0440\u0430\u043A\u0442\u0443\u0439 \u044D\u0442\u043E \u043A\u0430\u043A \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0441\u043B\u0430\u0431\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439: \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u0443\u0436\u0435 \u0437\u0430\u0434\u0435\u043B\u043E, \u043D\u043E \u0437\u0430\u043F\u0430\u0441\u0430 \u043F\u043E displacement \u043F\u043E\u043A\u0430 \u043C\u0430\u043B\u043E. \u041E\u0431\u044B\u0447\u043D\u043E \u0437\u0434\u0435\u0441\u044C \u043D\u0443\u0436\u0435\u043D follow-through \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442, \u0430 \u043D\u0435 \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
1561
- - \u0415\u0441\u043B\u0438 clearBreak=true, \u043D\u043E trendlineContext.compressedCleanBreak=true, \u044D\u0442\u043E \u0441\u0436\u0430\u0442\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439 \u043F\u043E\u0441\u043B\u0435 \u0441\u0435\u0440\u0438\u0438 \u0431\u043B\u0438\u0437\u043A\u0438\u0445 \u043A\u0430\u0441\u0430\u043D\u0438\u0439 \u043D\u0430 \u043A\u043E\u0440\u043E\u0442\u043A\u043E\u0439 \u043B\u0438\u043D\u0438\u0438. \u0414\u0430\u0436\u0435 \u043F\u0440\u0438 \u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u043E\u043C \u0432\u044B\u0445\u043E\u0434\u0435 \u0437\u0430 \u043B\u0438\u043D\u0438\u044E \u0437\u0434\u0435\u0441\u044C \u0447\u0430\u0449\u0435 \u043D\u0443\u0436\u0435\u043D follow-through \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442, \u0430 \u043D\u0435 \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
1562
- - \u0415\u0441\u043B\u0438 clearBreak=true, \u043D\u043E trendlineContext.breakVsAtrRatio < 0.5 \u0438 \u043F\u0440\u0438 \u044D\u0442\u043E\u043C trendlineContext.weakBtcLedBreak=true, \u0441\u0447\u0438\u0442\u0430\u0439 \u044D\u0442\u043E \u0441\u043B\u0430\u0431\u044B\u043C BTC-led \u043F\u0440\u043E\u0431\u043E\u0435\u043C \u0431\u0435\u0437 \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0433\u043E follow-through \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435. \u041E\u0431\u044B\u0447\u043D\u043E \u0437\u0434\u0435\u0441\u044C \u043D\u0443\u0436\u0435\u043D \u0440\u0435\u0442\u0435\u0441\u0442/\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435, \u0430 \u043D\u0435 \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
1563
- - \u0414\u043B\u044F LONG \u043F\u043E descending resistance, \u0435\u0441\u043B\u0438 \u043B\u0438\u043D\u0438\u044F \u043E\u0447\u0435\u043D\u044C \u0434\u043B\u0438\u043D\u043D\u0430\u044F, \u0430 \u0432\u044B\u0445\u043E\u0434 \u043D\u0430\u0434 \u043D\u0435\u0439 \u043F\u043E\u043A\u0430 \u0443\u043C\u0435\u0440\u0435\u043D\u043D\u044B\u0439 \u0438 BTC \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043F\u0440\u043E\u0431\u043E\u0439 \u0441\u043B\u0430\u0431\u043E, \u0442\u0440\u0430\u043A\u0442\u0443\u0439 \u044D\u0442\u043E \u043A\u0430\u043A \u0440\u0430\u043D\u043D\u0438\u0439 breakout \u0431\u0435\u0437 follow-through. \u0412 \u0442\u0430\u043A\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u0447\u0430\u0449\u0435 \u043D\u0443\u0436\u0435\u043D \u0440\u0435\u0442\u0435\u0441\u0442/\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435, \u0430 \u043D\u0435 \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
1564
- - \u0414\u043B\u044F TrendLine quality 4-5 \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C \u0442\u043E\u043B\u044C\u043A\u043E \u043A\u043E\u0433\u0434\u0430 \u043E\u0434\u043D\u043E\u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E: clearBreak=true, nearLineNoise=false, coinBiasAligned=true \u0438 btcBiasAligned=true. \u0415\u0441\u043B\u0438 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u043D\u043E \u0438\u0437 \u044D\u0442\u0438\u0445 \u0443\u0441\u043B\u043E\u0432\u0438\u0439 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u043E, \u043D\u0435 \u0441\u0442\u0430\u0432\u044C quality \u0432\u044B\u0448\u0435 3.
1565
- - \u0420\u0435\u0434\u043A\u043E\u0435 \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435: \u0435\u0441\u043B\u0438 trendlineContext.aggressivePreBreakPressure=true, \u044D\u0442\u043E \u0430\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043D\u044B\u0439 pre-break pressure \u0441\u0435\u0442\u0430\u043F. \u0412 \u0442\u0430\u043A\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C quality=4 \u0434\u0430\u0436\u0435 \u0431\u0435\u0437 clearBreak, \u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043A\u0430\u043A \u0440\u0430\u043D\u043D\u0435\u0435 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043D\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u044E\u0442 coin/BTC bias.
1566
- - \u0415\u0449\u0435 \u043E\u0434\u043D\u043E \u0440\u0435\u0434\u043A\u043E\u0435 \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435: \u0435\u0441\u043B\u0438 trendlineContext.strongNearBreakPressure=true, \u044D\u0442\u043E \u0437\u0440\u0435\u043B\u0430\u044F \u043B\u0438\u043D\u0438\u044F \u0441 \u0443\u0436\u0435 \u043D\u0430\u0447\u0430\u0432\u0448\u0438\u043C\u0441\u044F \u043F\u0440\u043E\u0434\u0430\u0432\u043B\u0438\u0432\u0430\u043D\u0438\u0435\u043C \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u0441\u0438\u0433\u043D\u0430\u043B\u0430 \u0438 \u043E\u0447\u0435\u043D\u044C \u0441\u0438\u043B\u044C\u043D\u044B\u043C aligned pressure \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0438 BTC. \u0412 \u0442\u0430\u043A\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C quality=4 \u0434\u0430\u0436\u0435 \u043F\u0440\u0438 nearLineNoise=true, \u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043A\u0430\u043A \u0440\u0430\u043D\u043D\u0435\u0435 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u043E \u0441\u0438\u043B\u044C\u043D\u043E\u0439 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0435.
1757
+ TrendLine addon:
1758
+ - This setup is based on breakout or reaction around a trendline. 'payload.figures.trendline' contains the line geometry, and 'payload.additionalIndicators.trendlineContext' contains a compact summary of price location versus the line.
1759
+ - For TrendLine, geometry and price structure have higher priority than indicator confirmation.
1760
+ - Touches strengthen a line but do not confirm the signal by themselves. Without a confirmed breakout or retest, do not raise quality only because there were many touches.
1761
+ - For SHORT on rising support ('trendline.mode="lows"'), you usually need either a clear move below the line or a retest from below with rejection. If price remains above the line or directly on it, use 'direction=null' and usually 'quality <= 2'.
1762
+ - For LONG on descending resistance ('trendline.mode="highs"'), the mirror logic applies: you usually need a move above the line or a retest from above. If price remains below the line or directly on it, use 'direction=null' and usually 'quality <= 2'.
1763
+ - If 'payload.additionalIndicators.trendlineContext.nearLineNoise=true', do not treat that as a confirmed breakout. Quality is usually '<= 2-3', and a retest or confirmation is still needed.
1764
+ - If 'payload.additionalIndicators.trendlineContext.coinBiasAligned=false' or 'btcBiasAligned=false', treat it as a direct conflict with the signal direction. In that case, the signal is usually not confirmed unless the structural edge is exceptional.
1765
+ - If 'payload.additionalIndicators.derivativesContext' exists, use it as Coinalyze-based breakout confirmation or conflict: open interest should support the move, funding should not be extremely crowded against the entry quality, and liquidation spikes can indicate flush, squeeze, or exhaustion.
1766
+ - If 'derivativesContext.summary.riskFlags' contains 'oi_not_confirming', treat it as a direct sign that open interest does not confirm the breakout yet. Without very strong follow-through, do not elevate the signal to immediate entry.
1767
+ - For SHORT during 'off_hours' or session overlap, require cleaner structural follow-through than during normal hours; those windows are noisier and less suitable for immediate approval.
1768
+ - If 'payload.additionalIndicators.trendlineContext.clearBreak=false' and price is still near the line, do not describe it as a clean breakout.
1769
+ - If 'clearBreak=true' but 'trendlineContext.weakCleanBreak=true', treat it as a formally valid but too-weak breakout: structure has been touched, but displacement margin is still limited. This usually calls for follow-through or retest, not immediate confirmation.
1770
+ - If 'clearBreak=true' but 'trendlineContext.compressedCleanBreak=true', treat it as a compressed breakout after a cluster of close touches on a short line. Even with a formal line exit, this still usually calls for follow-through or retest rather than immediate confirmation.
1771
+ - If 'clearBreak=true', 'trendlineContext.breakVsAtrRatio < 0.5', and 'trendlineContext.weakBtcLedBreak=true', treat it as a weak BTC-led break without enough coin-specific follow-through. This usually calls for retest or extra confirmation rather than immediate confirmation.
1772
+ - For LONG on descending resistance, if the line is very long, the move above it is still modest, and BTC only weakly supports the break, treat it as an early breakout without sufficient follow-through. That usually needs retest or confirmation rather than immediate confirmation.
1773
+ - For TrendLine, quality 4-5 is only allowed when all of the following are true at once: 'clearBreak=true', 'nearLineNoise=false', 'coinBiasAligned=true', and 'btcBiasAligned=true'. If any one of these conditions is not met, do not set quality above 3.
1774
+ - Rare exception: if 'trendlineContext.aggressivePreBreakPressure=true', this is an aggressive pre-break-pressure setup. In that case 'quality=4' is allowed even without 'clearBreak', but only as early structural confirmation and only when coin/BTC bias is not conflicting.
1775
+ - Another rare exception: if 'trendlineContext.strongNearBreakPressure=true', this is a mature line with pressure already building in the signal direction and very strong aligned pressure from both the coin and BTC. In that case 'quality=4' is allowed even when 'nearLineNoise=true', but only as early structural confirmation on strong structure.
1567
1776
  `;
1568
1777
  TRENDLINE_PAYLOAD_PROMPT = `
1569
- - \u0412 payload.figures.trendline \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F \u043F\u043E\u043B\u043D\u0430\u044F \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u044F \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u043B\u0438\u043D\u0438\u0438 (\u0431\u0435\u0437 trim), \u0447\u0442\u043E\u0431\u044B \u043C\u043E\u0436\u043D\u043E \u0431\u044B\u043B\u043E \u043E\u0446\u0435\u043D\u0438\u0432\u0430\u0442\u044C \u043A\u0430\u0441\u0430\u043D\u0438\u044F/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443.
1570
- - \u0412 payload.additionalIndicators.trendlineContext \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F mode / touches / distance / currentLinePrice / priceVsLinePct / priceVsLineSide / clearBreak / nearLineNoise / coinMaBias / btcMaBias / maxAllowedQuality / approvalAllowedNow / hardBlockReasons.
1571
- - \u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0432 trendlineContext \u043F\u0435\u0440\u0435\u0434\u0430\u044E\u0442\u0441\u044F atrPct / breakVsAtrRatio / coinMaSpreadPct / btcMaSpreadPct / aggressivePreBreakPressure / strongNearBreakPressure / weakCleanBreak / compressedCleanBreak / weakBtcLedBreak / weakLongFarBreak.
1778
+ - 'payload.figures.trendline' contains the full trendline geometry without trimming so touches and structure can be evaluated.
1779
+ - 'payload.additionalIndicators.trendlineContext' contains 'mode / touches / distance / currentLinePrice / priceVsLinePct / priceVsLineSide / clearBreak / nearLineNoise / coinMaBias / btcMaBias / maxAllowedQuality / approvalAllowedNow / hardBlockReasons'.
1780
+ - It also includes 'atrPct / breakVsAtrRatio / coinMaSpreadPct / btcMaSpreadPct / aggressivePreBreakPressure / strongNearBreakPressure / weakCleanBreak / compressedCleanBreak / weakBtcLedBreak / weakLongFarBreak'.
1781
+ - If 'payload.additionalIndicators.derivativesContext' exists, it contains Coinalyze-derived open interest, funding, and liquidation fields for the signal moment; do not treat 'stale' or 'missing_derivatives' as confirmation or conflict.
1572
1782
  `;
1573
1783
  buildTrendlineContext = (signal) => {
1784
+ const marketContext = signal.additionalIndicators && typeof signal.additionalIndicators.marketContext === "object" && signal.additionalIndicators.marketContext && !Array.isArray(signal.additionalIndicators.marketContext) ? signal.additionalIndicators.marketContext : null;
1785
+ const tradingSession = marketContext && typeof marketContext.tradingSession === "object" && marketContext.tradingSession && !Array.isArray(marketContext.tradingSession) ? marketContext.tradingSession : null;
1786
+ const sessionPrimary = typeof tradingSession?.primarySession === "string" ? tradingSession.primarySession : null;
1787
+ const sessionIsOverlap = tradingSession?.isOverlap === true;
1788
+ const derivativesContext = signal.additionalIndicators && typeof signal.additionalIndicators.derivativesContext === "object" && signal.additionalIndicators.derivativesContext && !Array.isArray(signal.additionalIndicators.derivativesContext) ? signal.additionalIndicators.derivativesContext : null;
1789
+ const derivativesSummary = derivativesContext && typeof derivativesContext.summary === "object" && derivativesContext.summary && !Array.isArray(derivativesContext.summary) ? derivativesContext.summary : null;
1790
+ const derivativesRiskFlags = Array.isArray(derivativesSummary?.riskFlags) ? derivativesSummary.riskFlags.filter(
1791
+ (flag) => typeof flag === "string" && flag.length > 0
1792
+ ) : [];
1793
+ const oiNotConfirming = derivativesRiskFlags.includes("oi_not_confirming");
1574
1794
  const structural = buildTrendlineStructuralContext(signal);
1575
1795
  const trendLine = getTrendLineFromPayload2(signal);
1576
1796
  const coinMaFast = getLastFiniteNumber3(signal.indicators?.maFast);
@@ -1592,6 +1812,12 @@ var init_ai5 = __esm({
1592
1812
  if (weakBtcLedBreak) {
1593
1813
  hardBlockReasons.push("weak_btc_led_break");
1594
1814
  }
1815
+ if (oiNotConfirming && !hardBlockReasons.includes("oi_not_confirming")) {
1816
+ hardBlockReasons.push("oi_not_confirming");
1817
+ }
1818
+ if (structural.signalDirection === "SHORT" && (entryTiming === "ready_follow_through" || entryTiming === "ready_retest") && (sessionPrimary === "off_hours" || sessionIsOverlap) && !hardBlockReasons.includes("short_session_risk")) {
1819
+ hardBlockReasons.push("short_session_risk");
1820
+ }
1595
1821
  const deterministicQuality = getDeterministicTrendlineQuality({
1596
1822
  signalDirection: structural.signalDirection,
1597
1823
  clearBreak: structural.clearBreak,
@@ -1620,6 +1846,10 @@ var init_ai5 = __esm({
1620
1846
  aggressivePreBreakPressure,
1621
1847
  strongNearBreakPressure,
1622
1848
  weakBtcLedBreak,
1849
+ sessionPrimary,
1850
+ sessionIsOverlap,
1851
+ derivativesRiskFlags,
1852
+ oiNotConfirming,
1623
1853
  deterministicQuality,
1624
1854
  maxAllowedQuality,
1625
1855
  approvalAllowedNow,
@@ -1635,21 +1865,25 @@ var init_ai5 = __esm({
1635
1865
  getHardBlockReasonText3 = (reason) => {
1636
1866
  switch (reason) {
1637
1867
  case "no_clear_break":
1638
- return "\u043D\u0435\u0442 \u0447\u0438\u0441\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F \u043B\u0438\u043D\u0438\u0438";
1868
+ return "there is no clean breakout of the line";
1639
1869
  case "near_line_noise":
1640
- return "\u0446\u0435\u043D\u0430 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0431\u043B\u0438\u0437\u043A\u043E \u043A \u043B\u0438\u043D\u0438\u0438 \u0438 \u044D\u0442\u043E \u043F\u043E\u0445\u043E\u0436\u0435 \u043D\u0430 \u0448\u0443\u043C";
1870
+ return "price is too close to the line and looks like noise";
1641
1871
  case "coin_bias_conflict":
1642
- return "bias \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C";
1872
+ return "coin bias conflicts with the direction";
1643
1873
  case "btc_bias_conflict":
1644
- return "BTC-\u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C";
1874
+ return "BTC context conflicts with the direction";
1645
1875
  case "weak_clean_break":
1646
- return "\u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439 \u0435\u0441\u0442\u044C, \u043D\u043E displacement \u0435\u0449\u0435 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0441\u043B\u0430\u0431\u044B\u0439 \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E ATR";
1876
+ return "the formal breakout exists, but displacement is still too weak relative to ATR";
1647
1877
  case "compressed_clean_break":
1648
- return "\u043F\u0440\u043E\u0431\u043E\u0439 \u0432\u044B\u0433\u043B\u044F\u0434\u0438\u0442 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0441\u0436\u0430\u0442\u044B\u043C: \u0441\u0435\u0440\u0438\u044F \u0431\u043B\u0438\u0437\u043A\u0438\u0445 \u043A\u0430\u0441\u0430\u043D\u0438\u0439 \u043D\u0430 \u043A\u043E\u0440\u043E\u0442\u043A\u043E\u0439 \u043B\u0438\u043D\u0438\u0438 \u0431\u0435\u0437 \u0434\u043E\u0441\u0442\u0430\u0442\u043E\u0447\u043D\u043E\u0433\u043E follow-through";
1878
+ return "the breakout looks too compressed: clustered close touches on a short line without enough follow-through";
1649
1879
  case "weak_btc_led_break":
1650
- return "\u043F\u0440\u043E\u0431\u043E\u0439 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u0435\u043B\u043A\u0438\u0439 \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E ATR \u0438 \u0431\u043E\u043B\u044C\u0448\u0435 \u043F\u043E\u0445\u043E\u0436 \u043D\u0430 BTC-led \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u0435 \u0431\u0435\u0437 follow-through \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435";
1880
+ return "the breakout is too small relative to ATR and looks more like a BTC-led move without enough coin follow-through";
1651
1881
  case "weak_long_far_break":
1652
- return "\u0434\u043B\u044F LONG \u043F\u0440\u043E\u0431\u043E\u0439 \u043E\u0447\u0435\u043D\u044C \u0434\u043B\u0438\u043D\u043D\u043E\u0439 \u043B\u0438\u043D\u0438\u0438 \u043F\u043E\u043A\u0430 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0443\u043C\u0435\u0440\u0435\u043D\u043D\u044B\u0439, \u0430 BTC \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0435\u0433\u043E \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0441\u043B\u0430\u0431\u043E";
1882
+ return "for LONG, the breakout of the very long line is still too modest and BTC support is too weak";
1883
+ case "oi_not_confirming":
1884
+ return "open interest does not confirm the move, so the breakout still looks unconfirmed on derivatives context";
1885
+ case "short_session_risk":
1886
+ return "for SHORT, the current session is too noisy or thin (off-hours or overlap), so clearer follow-through is required";
1653
1887
  default:
1654
1888
  return reason;
1655
1889
  }
@@ -1684,7 +1918,8 @@ var init_ai5 = __esm({
1684
1918
  const shortLineStrengthQuality4 = breakVsAtrRatio >= 0.6 && priceVsLinePctAbs >= 0.65 && touches >= 6 && distance < 120 && btcMaSpreadPct >= 0.75;
1685
1919
  const matureLineQuality4 = breakVsAtrRatio >= 0.8 && priceVsLinePctAbs >= 0.7 && touches >= 5 && distance < 350 && btcMaSpreadPct >= 0.4;
1686
1920
  const extendedHighConvictionQuality4 = breakVsAtrRatio >= 0.75 && priceVsLinePctAbs >= 0.65 && touches >= 5 && distance < 600 && btcMaSpreadPct >= 0.9;
1687
- return compactBreakoutQuality4 || shortLineStrengthQuality4 || matureLineQuality4 || extendedHighConvictionQuality4 ? 4 : 3;
1921
+ const alignedRecentFollowThroughQuality4 = (entryTiming === "ready_follow_through" || entryTiming === "ready_retest") && breakVsAtrRatio >= 0.58 && breakVsAtrRatio <= 0.72 && priceVsLinePctAbs >= 0.48 && priceVsLinePctAbs <= 0.7 && touches >= 5 && distance <= 420 && btcMaSpreadPct >= 0.45 && coinMaSpreadPct >= 0.25;
1922
+ return compactBreakoutQuality4 || shortLineStrengthQuality4 || matureLineQuality4 || extendedHighConvictionQuality4 || alignedRecentFollowThroughQuality4 ? 4 : 3;
1688
1923
  }
1689
1924
  const quality5 = breakVsAtrRatio >= 5 && priceVsLinePctAbs >= 10 && touches >= 5 && distance >= 240 && distance <= 400 && btcMaSpreadPct <= -1;
1690
1925
  if (quality5) {
@@ -1692,19 +1927,23 @@ var init_ai5 = __esm({
1692
1927
  }
1693
1928
  const quality4 = breakVsAtrRatio >= 1 && breakVsAtrRatio < 2.5 && priceVsLinePctAbs >= 1 && touches >= 5 && distance < 300 && btcMaSpreadPct <= -0.5;
1694
1929
  const strongReadyBreakoutQuality4 = entryTiming === "ready_breakout" && breakVsAtrRatio >= 2 && priceVsLinePctAbs >= 1.8 && touches >= 5 && btcMaSpreadPct <= -1 && (coinMaSpreadPct <= -1 || breakVsAtrRatio >= 3);
1695
- return quality4 || strongReadyBreakoutQuality4 ? 4 : 3;
1930
+ const moderateReadyBreakoutQuality4 = entryTiming === "ready_breakout" && breakVsAtrRatio >= 0.65 && breakVsAtrRatio <= 1.2 && priceVsLinePctAbs >= 0.65 && priceVsLinePctAbs <= 1 && touches >= 5 && distance >= 150 && distance <= 320 && btcMaSpreadPct <= -0.05 && coinMaSpreadPct <= -0.25;
1931
+ if ((quality4 || moderateReadyBreakoutQuality4) && entryTiming === "ready_breakout") {
1932
+ return 4;
1933
+ }
1934
+ return strongReadyBreakoutQuality4 ? 4 : 3;
1696
1935
  };
1697
1936
  getDeterministicTrendlineQualityReason = (trendlineContext) => {
1698
1937
  if (trendlineContext.hardBlockReasons.length > 0) {
1699
- return `TrendLine guardrail: \u0432\u0445\u043E\u0434 \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E ${trendlineContext.hardBlockReasons.map(getHardBlockReasonText3).join("; ")}.`;
1938
+ return `TrendLine guardrail: entry is blocked because ${trendlineContext.hardBlockReasons.map(getHardBlockReasonText3).join("; ")}.`;
1700
1939
  }
1701
1940
  if (trendlineContext.signalDirection === "LONG") {
1702
- return "TrendLine deterministic quality: \u043F\u0440\u043E\u0431\u043E\u0439 \u0435\u0441\u0442\u044C, \u043D\u043E \u0434\u043B\u044F LONG \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 displacement, \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438 BTC \u0438\u043B\u0438 \u043B\u0438\u043D\u0438\u044F \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0434\u043B\u0438\u043D\u043D\u0430\u044F \u0434\u043B\u044F \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0433\u043E \u0432\u0445\u043E\u0434\u0430.";
1941
+ return "TrendLine deterministic quality: the breakout exists, but LONG still lacks enough displacement, BTC support, or a compact enough line for immediate entry.";
1703
1942
  }
1704
1943
  if (trendlineContext.signalDirection === "SHORT") {
1705
- return "TrendLine deterministic quality: \u043F\u0440\u043E\u0431\u043E\u0439 \u0435\u0441\u0442\u044C, \u043D\u043E \u0434\u043B\u044F SHORT \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 bearish displacement \u0438\u043B\u0438 follow-through, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u0432\u0445\u043E\u0434 \u043F\u043E\u043A\u0430 \u0440\u0430\u043D\u043E \u043E\u0434\u043E\u0431\u0440\u044F\u0442\u044C.";
1944
+ return "TrendLine deterministic quality: the breakout exists, but SHORT still lacks enough bearish displacement or follow-through, so entry is still too early.";
1706
1945
  }
1707
- return "TrendLine deterministic quality: \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0435\u0449\u0435 \u043D\u0435 \u0434\u043E\u0442\u044F\u0433\u0438\u0432\u0430\u0435\u0442 \u0434\u043E \u0432\u0445\u043E\u0434\u0430 \u043F\u0440\u044F\u043C\u043E \u0441\u0435\u0439\u0447\u0430\u0441.";
1946
+ return "TrendLine deterministic quality: the structure is still not strong enough for entry right now.";
1708
1947
  };
1709
1948
  getTrendlineContextFromPayload = (payload, signal) => {
1710
1949
  const additional = payload.additionalIndicators;
@@ -1713,25 +1952,35 @@ var init_ai5 = __esm({
1713
1952
  };
1714
1953
  trendLineAiAdapter = {
1715
1954
  // Shared builder trims nested series/figures; TrendLine keeps trendline geometry untrimmed on purpose.
1716
- buildPayload: ({ signal, basePayload }) => ({
1717
- ...basePayload,
1718
- figures: {
1719
- ...basePayload.figures,
1720
- // Keep raw line geometry available exactly where the shared prompt expects it.
1721
- trendline: getTrendLineFromPayload2(signal)
1722
- },
1723
- additionalIndicators: {
1724
- ...basePayload.additionalIndicators,
1725
- trendlineContext: buildTrendlineContext(signal)
1726
- }
1727
- }),
1955
+ buildPayload: ({ signal, basePayload }) => {
1956
+ const mergedAdditionalIndicators = {
1957
+ ...signal.additionalIndicators ?? {},
1958
+ ...basePayload.additionalIndicators ?? {}
1959
+ };
1960
+ const trendlineContext = buildTrendlineContext({
1961
+ ...signal,
1962
+ additionalIndicators: mergedAdditionalIndicators
1963
+ });
1964
+ return {
1965
+ ...basePayload,
1966
+ figures: {
1967
+ ...basePayload.figures,
1968
+ // Keep raw line geometry available exactly where the shared prompt expects it.
1969
+ trendline: getTrendLineFromPayload2(signal)
1970
+ },
1971
+ additionalIndicators: {
1972
+ ...mergedAdditionalIndicators,
1973
+ trendlineContext
1974
+ }
1975
+ };
1976
+ },
1728
1977
  postProcessAnalysis: ({ signal, payload, analysis }) => {
1729
1978
  const trendlineContext = getTrendlineContextFromPayload(payload, signal);
1730
1979
  const quality = trendlineContext.deterministicQuality;
1731
1980
  const signalDirection = signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
1732
1981
  if ((trendlineContext.aggressivePreBreakPressure === true || trendlineContext.strongNearBreakPressure === true) && signalDirection != null) {
1733
- const fallbackReason = trendlineContext.strongNearBreakPressure === true ? "TrendLine strong near-break pressure: \u0437\u0440\u0435\u043B\u0430\u044F \u043B\u0438\u043D\u0438\u044F \u0443\u0436\u0435 \u043F\u0440\u043E\u0434\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u0441\u0434\u0435\u043B\u043A\u0438, \u0440\u0430\u043D\u043D\u0438\u0439 \u0432\u0445\u043E\u0434 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D \u043A\u043E\u0434\u043E\u043C \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438." : "TrendLine aggressive pre-break pressure: \u0440\u0430\u043D\u043D\u0435\u0435 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E \u043F\u0440\u0438 \u0441\u0438\u043B\u044C\u043D\u043E\u043C bearish pressure.";
1734
- const fallbackComment = trendlineContext.strongNearBreakPressure === true ? "TrendLine strong near-break pressure: \u0440\u0430\u043D\u043D\u0438\u0439 \u0432\u0445\u043E\u0434 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D \u043A\u043E\u0434\u043E\u043C \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438." : "TrendLine aggressive pre-break pressure: \u0440\u0430\u043D\u043D\u0438\u0439 \u0432\u0445\u043E\u0434 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D \u043A\u043E\u0434\u043E\u043C \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438.";
1982
+ const fallbackReason = trendlineContext.strongNearBreakPressure === true ? "TrendLine strong near-break pressure: a mature line is already compressing in the trade direction, so early entry is allowed by strategy code." : "TrendLine aggressive pre-break pressure: early structural confirmation is allowed under strong bearish pressure.";
1983
+ const fallbackComment = trendlineContext.strongNearBreakPressure === true ? "TrendLine strong near-break pressure: early entry is allowed by strategy code." : "TrendLine aggressive pre-break pressure: early entry is allowed by strategy code.";
1735
1984
  return {
1736
1985
  ...analysis,
1737
1986
  direction: signalDirection,
@@ -1762,17 +2011,17 @@ var init_ai5 = __esm({
1762
2011
  const retestPrice = trendlineContext.currentLinePrice ?? analysis.retestPrice ?? null;
1763
2012
  const qualityReason = mergeShortText(
1764
2013
  getDeterministicTrendlineQualityReason(trendlineContext),
1765
- "TrendLine guardrail: \u0432\u0445\u043E\u0434 \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D \u0434\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B.",
2014
+ "TrendLine guardrail: entry is blocked until the structure is confirmed.",
1766
2015
  400
1767
2016
  );
1768
2017
  const triggerInvalidation = mergeShortText(
1769
- trendlineContext.hardBlockReasons.length > 0 ? `\u0416\u0434\u0430\u0442\u044C \u0447\u0438\u0441\u0442\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439/\u0440\u0435\u0442\u0435\u0441\u0442 \u043B\u0438\u043D\u0438\u0438 \u0438 \u0443\u0431\u0440\u0430\u0442\u044C \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B: ${trendlineContext.hardBlockReasons.map(getHardBlockReasonText3).join("; ")}.` : "\u0416\u0434\u0430\u0442\u044C \u0431\u043E\u043B\u0435\u0435 \u0441\u0438\u043B\u044C\u043D\u044B\u0439 breakout/follow-through \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442 \u043B\u0438\u043D\u0438\u0438 \u0441 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435\u043C \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0438 BTC.",
1770
- "\u0416\u0434\u0430\u0442\u044C \u0447\u0438\u0441\u0442\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439/\u0440\u0435\u0442\u0435\u0441\u0442 \u043B\u0438\u043D\u0438\u0438 \u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0438 BTC.",
2018
+ trendlineContext.hardBlockReasons.length > 0 ? `Wait for a clean breakout or retest of the line and resolve the conflicts: ${trendlineContext.hardBlockReasons.map(getHardBlockReasonText3).join("; ")}.` : "Wait for stronger breakout follow-through or a line retest confirmed by both the coin and BTC.",
2019
+ "Wait for a clean line breakout or retest plus confirmation from the coin and BTC.",
1771
2020
  400
1772
2021
  );
1773
2022
  const comment = mergeShortText(
1774
- trendlineContext.hardBlockReasons.length > 0 ? `TrendLine guardrail \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043B \u0432\u0445\u043E\u0434: ${trendlineContext.hardBlockReasons.map(getHardBlockReasonText3).join("; ")}.` : "TrendLine deterministic quality \u043E\u043F\u0443\u0441\u0442\u0438\u043B \u0432\u0445\u043E\u0434 \u0432 watch/reject \u0434\u043E \u043F\u043E\u044F\u0432\u043B\u0435\u043D\u0438\u044F \u0431\u043E\u043B\u0435\u0435 \u0441\u0438\u043B\u044C\u043D\u043E\u0439 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B.",
1775
- "TrendLine guardrail \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043B \u0432\u0445\u043E\u0434 \u0434\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B.",
2023
+ trendlineContext.hardBlockReasons.length > 0 ? `TrendLine guardrail blocked the entry: ${trendlineContext.hardBlockReasons.map(getHardBlockReasonText3).join("; ")}.` : "TrendLine deterministic quality downgraded the entry to watch or reject until stronger structure appears.",
2024
+ "TrendLine guardrail blocked the entry until the structure is confirmed.",
1776
2025
  1024
1777
2026
  );
1778
2027
  return {
@@ -1785,12 +2034,12 @@ var init_ai5 = __esm({
1785
2034
  stopLossPrice: null,
1786
2035
  setup: mergeShortText(
1787
2036
  analysis.setup ?? "",
1788
- "\u0421\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F/\u0440\u0435\u0442\u0435\u0441\u0442\u0430 \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u0434\u043B\u044F \u0432\u0445\u043E\u0434\u0430.",
2037
+ "There is no confirmed trendline breakout or retest for entry right now.",
1789
2038
  400
1790
2039
  ),
1791
2040
  retestPlan: mergeShortText(
1792
2041
  analysis.retestPlan ?? "",
1793
- "\u0416\u0434\u0430\u0442\u044C \u0432\u043E\u0437\u0432\u0440\u0430\u0442 \u043A \u043B\u0438\u043D\u0438\u0438 \u0438 \u0440\u0435\u0430\u043A\u0446\u0438\u044E \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u0441\u0434\u0435\u043B\u043A\u0438 \u043F\u0435\u0440\u0435\u0434 \u043D\u043E\u0432\u044B\u043C \u0432\u0445\u043E\u0434\u043E\u043C.",
2042
+ "Wait for a return to the line and a reaction in the trade direction before a new entry.",
1794
2043
  400
1795
2044
  ),
1796
2045
  qualityReason,
@@ -1810,7 +2059,7 @@ ${TRENDLINE_PAYLOAD_PROMPT}
1810
2059
  }
1811
2060
  return `
1812
2061
 
1813
- \u0414\u043E\u043F. \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 TrendLine:
2062
+ Additional TrendLine context:
1814
2063
  - trendline.mode=${trendlineContext.mode ?? "n/a"}
1815
2064
  - trendline.touches=${formatPromptNumber(trendlineContext.touches, 0)}
1816
2065
  - trendline.distance=${formatPromptNumber(trendlineContext.distance, 0)}
@@ -1844,22 +2093,22 @@ ${TRENDLINE_PAYLOAD_PROMPT}
1844
2093
  - btc.maSpreadPct=${formatPromptNumber(trendlineContext.btcMaSpreadPct, 3)}%
1845
2094
  - btc.biasAligned=${String(trendlineContext.btcBiasAligned)}
1846
2095
 
1847
- \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u0438\u043D\u0442\u0435\u0440\u043F\u0440\u0435\u0442\u0430\u0446\u0438\u0438 \u0434\u043B\u044F TrendLine:
1848
- - SHORT \u043E\u0442 \u043B\u0438\u043D\u0438\u0438 lows \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u044F\u0432\u043D\u044B\u043C \u0443\u0445\u043E\u0434\u043E\u043C \u043D\u0438\u0436\u0435 \u043B\u0438\u043D\u0438\u0438 \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442\u043E\u043C \u0441\u043D\u0438\u0437\u0443 \u0441 \u043E\u0442\u0431\u043E\u0435\u043C.
1849
- - LONG \u043E\u0442 \u043B\u0438\u043D\u0438\u0438 highs \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u044F\u0432\u043D\u044B\u043C \u0443\u0445\u043E\u0434\u043E\u043C \u0432\u044B\u0448\u0435 \u043B\u0438\u043D\u0438\u0438 \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442\u043E\u043C \u0441\u0432\u0435\u0440\u0445\u0443 \u0441 \u043E\u0442\u0431\u043E\u0435\u043C.
1850
- - \u0415\u0441\u043B\u0438 trendline.nearLineNoise=true \u0438\u043B\u0438 biasAligned=false, \u043B\u0443\u0447\u0448\u0435 \u0432\u0435\u0440\u043D\u0443\u0442\u044C direction=null \u0438 quality 1-3, \u0447\u0435\u043C \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C \u0431\u0435\u0437 \u0437\u0430\u043F\u0430\u0441\u0430.
1851
- - \u0415\u0441\u043B\u0438 trendline.weakCleanBreak=true, \u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C, \u043D\u043E \u043E\u043D \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0441\u043B\u0430\u0431\u044B\u0439 \u043F\u043E displacement: \u043D\u0443\u0436\u0435\u043D follow-through \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442, \u0430 \u043D\u0435 quality 4-5.
1852
- - \u0415\u0441\u043B\u0438 trendline.compressedCleanBreak=true, \u043F\u0440\u043E\u0431\u043E\u0439 \u0444\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u043E \u0435\u0441\u0442\u044C, \u043D\u043E \u043B\u0438\u043D\u0438\u044F \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043A\u043E\u0440\u043E\u0442\u043A\u0430\u044F \u0438 \u0441\u0436\u0430\u0442\u0430\u044F \u043F\u043E\u0441\u043B\u0435 \u0441\u0435\u0440\u0438\u0438 \u0431\u043B\u0438\u0437\u043A\u0438\u0445 \u043A\u0430\u0441\u0430\u043D\u0438\u0439: \u043E\u0431\u044B\u0447\u043D\u043E \u0437\u0434\u0435\u0441\u044C \u043D\u0443\u0436\u0435\u043D follow-through \u0438\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442, \u0430 \u043D\u0435 \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
1853
- - \u0415\u0441\u043B\u0438 trendline.weakBtcLedBreak=true, \u0442\u0440\u0430\u043A\u0442\u0443\u0439 \u044D\u0442\u043E \u043A\u0430\u043A \u043C\u0435\u043B\u043A\u0438\u0439 \u043F\u0440\u043E\u0431\u043E\u0439, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0441\u0438\u043B\u044C\u043D\u0435\u0435 \u0442\u044F\u043D\u0435\u0442 BTC, \u0447\u0435\u043C \u0441\u0430\u043C\u0430 \u043C\u043E\u043D\u0435\u0442\u0430: \u0437\u0434\u0435\u0441\u044C \u043E\u0431\u044B\u0447\u043D\u043E \u043D\u0443\u0436\u0435\u043D \u0440\u0435\u0442\u0435\u0441\u0442 \u0438 quality 1-3.
1854
- - \u0415\u0441\u043B\u0438 clearBreak=false \u0438\u043B\u0438 \u043B\u044E\u0431\u043E\u0439 alignment=false, \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0439 quality \u0432\u044B\u0448\u0435 3.
1855
- - \u0415\u0441\u043B\u0438 trendline.aggressivePreBreakPressure=true, \u043C\u043E\u0436\u043D\u043E \u0440\u0430\u0441\u0441\u043C\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044C \u0440\u0430\u043D\u043D\u0438\u0439 SHORT \u0434\u043E \u044F\u0432\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F, \u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043A\u0430\u043A \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435: quality \u043C\u0430\u043A\u0441\u0438\u043C\u0443\u043C 4 \u0438 \u044F\u0432\u043D\u043E\u0435 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435, \u0447\u0442\u043E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u043E\u043A\u0430 \u0430\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043D\u043E\u0435.
1856
- - \u0415\u0441\u043B\u0438 trendline.strongNearBreakPressure=true, \u043C\u043E\u0436\u043D\u043E \u0440\u0430\u0441\u0441\u043C\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044C \u0440\u0430\u043D\u043D\u0438\u0439 SHORT \u043F\u0440\u0438 \u0441\u0438\u043B\u044C\u043D\u043E\u043C \u0434\u0430\u0432\u043B\u0435\u043D\u0438\u0438 \u0443\u0436\u0435 \u043F\u043E \u043D\u0443\u0436\u043D\u0443\u044E \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u043B\u0438\u043D\u0438\u0438, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u043F\u0440\u043E\u0431\u043E\u0439 \u0435\u0449\u0435 \u043D\u0435 \u0434\u043E\u0442\u044F\u0433\u0438\u0432\u0430\u0435\u0442 \u0434\u043E clearBreak-\u043F\u043E\u0440\u043E\u0433\u0430: quality \u043C\u0430\u043A\u0441\u0438\u043C\u0443\u043C 4.
1857
- - \u0421\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044F \u0434\u0435\u0442\u0435\u0440\u043C\u0438\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u0438\u0437\u0443\u0435\u0442 \u0438\u0442\u043E\u0433\u043E\u0432\u044B\u0439 quality \u0434\u043E trendline.deterministicQuality; \u0442\u0432\u043E\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u2014 \u043E\u0431\u044A\u044F\u0441\u043D\u0438\u0442\u044C \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0432 \u044D\u0442\u0438\u0445 \u0440\u0430\u043C\u043A\u0430\u0445, \u0430 \u043D\u0435 \u0441\u043F\u043E\u0440\u0438\u0442\u044C \u0441 tier.
1858
- - \u0415\u0441\u043B\u0438 trendline.approvalAllowedNow=false, \u043D\u0435 \u043E\u043F\u0438\u0441\u044B\u0432\u0430\u0439 \u044D\u0442\u043E \u043A\u0430\u043A \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u0439 \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u0440\u044F\u043C\u043E \u0441\u0435\u0439\u0447\u0430\u0441: \u043E\u0431\u044A\u044F\u0441\u043D\u044F\u0439, \u0447\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u0434\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F.
2096
+ Interpretation rules for TrendLine:
2097
+ - SHORT from a 'lows' line is confirmed only by a clear move below the line or a retest from below with rejection.
2098
+ - LONG from a 'highs' line is confirmed only by a clear move above the line or a retest from above with rejection.
2099
+ - If 'trendline.nearLineNoise=true' or any bias alignment is false, it is better to return 'direction=null' and quality 1-3 than to describe the signal as confirmed without margin.
2100
+ - If 'trendline.weakCleanBreak=true', the formal breakout exists but is too weak in displacement; it needs follow-through or retest rather than quality 4-5.
2101
+ - If 'trendline.compressedCleanBreak=true', the breakout exists formally, but the line is too short and compressed after clustered touches; this usually needs follow-through or retest rather than immediate confirmation.
2102
+ - If 'trendline.weakBtcLedBreak=true', treat it as a small breakout driven more by BTC than by the coin itself; this usually calls for retest and quality 1-3.
2103
+ - If 'clearBreak=false' or any alignment is false, do not raise quality above 3.
2104
+ - If 'trendline.aggressivePreBreakPressure=true', an early SHORT before a clear breakout may be considered only as an exception: quality can be at most 4, and the explanation must clearly state that the structural confirmation is still aggressive.
2105
+ - If 'trendline.strongNearBreakPressure=true', an early SHORT may be considered when pressure is already strong on the correct side of the line even if the breakout still falls short of the 'clearBreak' threshold: quality can be at most 4.
2106
+ - The strategy deterministically normalizes final quality to 'trendline.deterministicQuality'; your job is to explain the decision within that frame, not to argue with the tier.
2107
+ - If 'trendline.approvalAllowedNow=false', do not describe the signal as fully confirmed right now; explain what is still missing for confirmation.
1859
2108
  `;
1860
2109
  },
1861
- mapEntryRuntimeFromConfig: (config7) => (0, import_strategies8.mapAiRuntimeFromConfig)(
1862
- config7
2110
+ mapEntryRuntimeFromConfig: (config8) => (0, import_strategies8.mapAiRuntimeFromConfig)(
2111
+ config8
1863
2112
  )
1864
2113
  };
1865
2114
  }
@@ -1898,40 +2147,24 @@ var init_ml4 = __esm({
1898
2147
  normalizeStrategyConfig: (strategyConfig) => {
1899
2148
  return toTrendLineMlStrategyConfig(strategyConfig);
1900
2149
  },
1901
- mapEntryRuntimeFromConfig: (config7) => (0, import_strategies9.mapMlRuntimeFromConfig)(config7, {
2150
+ mapEntryRuntimeFromConfig: (config8) => (0, import_strategies9.mapMlRuntimeFromConfig)(config8, {
1902
2151
  strategyConfig: toTrendLineMlStrategyConfig(
1903
- config7
2152
+ config8
1904
2153
  )
1905
2154
  })
1906
2155
  };
1907
2156
  }
1908
2157
  });
1909
2158
 
1910
- // src/TrendLine/hooks.ts
1911
- var import_strategies10, trendLineBeforePlaceOrderHook;
1912
- var init_hooks2 = __esm({
1913
- "src/TrendLine/hooks.ts"() {
1914
- "use strict";
1915
- import_strategies10 = require("@tradejs/node/strategies");
1916
- trendLineBeforePlaceOrderHook = (0, import_strategies10.createCloseOppositeBeforePlaceOrderHook)({
1917
- isEnabled: (config7) => Boolean(config7.CLOSE_OPPOSITE_POSITIONS)
1918
- });
1919
- }
1920
- });
1921
-
1922
2159
  // src/TrendLine/manifest.ts
1923
2160
  var trendLineManifest;
1924
- var init_manifest5 = __esm({
2161
+ var init_manifest6 = __esm({
1925
2162
  "src/TrendLine/manifest.ts"() {
1926
2163
  "use strict";
1927
- init_ai5();
2164
+ init_ai6();
1928
2165
  init_ml4();
1929
- init_hooks2();
1930
2166
  trendLineManifest = {
1931
2167
  name: "TrendLine",
1932
- hooks: {
1933
- beforePlaceOrder: trendLineBeforePlaceOrderHook
1934
- },
1935
2168
  aiAdapter: trendLineAiAdapter,
1936
2169
  mlAdapter: trendLineMlAdapter
1937
2170
  };
@@ -2108,33 +2341,35 @@ var init_setup = __esm({
2108
2341
  });
2109
2342
 
2110
2343
  // src/VolumeDivergence/adapters/ai.ts
2111
- var import_strategies11, VOLUME_DIVERGENCE_CONTEXT_PROMPT, VOLUME_DIVERGENCE_PAYLOAD_PROMPT, toFiniteNumberOrNull4, getLastFiniteNumber4, getBias4, getSignalDirection2, getDivergenceSummary, getVolumeDivergenceSetupSummary, getVolumeDivergenceThresholdSummary, isAtLeast2, isAtMost, isInRange2, getConfirmationDistancePct, buildHardBlockReasons, getLongQ4Demotion, getLongDeterministicQuality, getShortDeterministicQuality, getVolumeDivergenceContext, getVolumeDivergenceContextFromPayload, clampQuality2, getHardBlockReasonText4, buildGuardrailReason, postProcessAnalysis2, volumeDivergenceAiAdapter;
2112
- var init_ai6 = __esm({
2344
+ var import_strategies10, VOLUME_DIVERGENCE_CONTEXT_PROMPT, VOLUME_DIVERGENCE_PAYLOAD_PROMPT, toFiniteNumberOrNull4, getLastFiniteNumber4, getBias4, getSignalDirection2, getDivergenceSummary, getVolumeDivergenceSetupSummary, getVolumeDivergenceThresholdSummary, isAtLeast2, isAtMost, isInRange2, getConfirmationDistancePct, buildHardBlockReasons, getLongQ4Demotion, getLongDeterministicQuality, getShortDeterministicQuality, getVolumeDivergenceContext, getVolumeDivergenceContextFromPayload, clampQuality2, getHardBlockReasonText4, buildGuardrailReason, postProcessAnalysis2, volumeDivergenceAiAdapter;
2345
+ var init_ai7 = __esm({
2113
2346
  "src/VolumeDivergence/adapters/ai.ts"() {
2114
2347
  "use strict";
2115
- import_strategies11 = require("@tradejs/core/strategies");
2348
+ import_strategies10 = require("@tradejs/core/strategies");
2116
2349
  init_setup();
2117
2350
  VOLUME_DIVERGENCE_CONTEXT_PROMPT = `
2118
- \u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0434\u043B\u044F VolumeDivergence:
2119
- - \u042D\u0442\u043E reversal-\u0441\u0435\u0442\u0430\u043F \u043D\u0430 \u0434\u0438\u0432\u0435\u0440\u0433\u0435\u043D\u0446\u0438\u0438 \u0446\u0435\u043D\u044B \u0438 \u043D\u043E\u0440\u043C\u0430\u043B\u0438\u0437\u043E\u0432\u0430\u043D\u043D\u043E\u0433\u043E \u043E\u0431\u044A\u0435\u043C\u0430, \u0430 \u043D\u0435 breakout-\u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044F.
2120
- - Bullish divergence: price \u0434\u0435\u043B\u0430\u0435\u0442 lower low, \u0430 volume \u0434\u0435\u043B\u0430\u0435\u0442 higher low.
2121
- - Bearish divergence: price \u0434\u0435\u043B\u0430\u0435\u0442 higher high, \u0430 volume \u0434\u0435\u043B\u0430\u0435\u0442 lower high.
2122
- - \u0414\u043B\u044F bullish-\u0441\u0438\u0433\u043D\u0430\u043B\u0430 \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439 quality, \u0435\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u043F\u043E\u0441\u043B\u0435 pivot low \u0442\u0430\u043A \u0438 \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u0430 \u0437\u0430\u043C\u0435\u0442\u043D\u043E \u043E\u0442\u0441\u043A\u043E\u0447\u0438\u0442\u044C \u043E\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E pivot low \u0438\u043B\u0438 \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u0430 \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0445\u043E\u0442\u044F \u0431\u044B \u0447\u0430\u0441\u0442\u044C \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B.
2123
- - \u0414\u043B\u044F bearish-\u0441\u0438\u0433\u043D\u0430\u043B\u0430 \u0437\u0435\u0440\u043A\u0430\u043B\u044C\u043D\u043E: \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439 quality, \u0435\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u043F\u043E\u0441\u043B\u0435 pivot high \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u0430 \u0437\u0430\u043C\u0435\u0442\u043D\u043E \u0443\u0439\u0442\u0438 \u0432\u043D\u0438\u0437 \u043E\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E pivot high.
2124
- - \u0415\u0441\u043B\u0438 payload.additionalIndicators.volumeDivergenceContext.confirmationReady=false, \u043E\u0431\u044B\u0447\u043D\u043E \u044D\u0442\u043E \u0435\u0449\u0435 \u043D\u0435 fully confirmed reversal; \u0447\u0430\u0449\u0435 quality <= 4 \u0438 \u0447\u0430\u0441\u0442\u043E \u043D\u0443\u0436\u0435\u043D retest/confirmation.
2125
- - \u0414\u043B\u044F live approve \u0441\u0447\u0438\u0442\u0430\u0439 confirmationReady \u043D\u0430\u043C\u043D\u043E\u0433\u043E \u0432\u0430\u0436\u043D\u0435\u0435, \u0447\u0435\u043C structureAdvanced: structure advance \u0441\u0430\u043C \u043F\u043E \u0441\u0435\u0431\u0435 \u0435\u0449\u0435 \u043D\u0435 \u043E\u0437\u043D\u0430\u0447\u0430\u0435\u0442 \u0433\u043E\u0442\u043E\u0432\u044B\u0439 reversal entry.
2126
- - \u0414\u043B\u044F reversal-\u0441\u0435\u0442\u0430\u043F\u0430 \u043D\u0435 \u043D\u0430\u0433\u0440\u0430\u0436\u0434\u0430\u0439 quality \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0437\u0430 \u0442\u043E, \u0447\u0442\u043E MA bias \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435/BTC \u0443\u0436\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0430\u0435\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
2127
- - \u0414\u043B\u044F LONG \u0441 entryTiming=structure_advance \u043E\u0431\u044B\u0447\u043D\u043E \u043D\u0435 \u0441\u0442\u0430\u0432\u044C quality=5: \u044D\u0442\u043E \u043F\u0440\u043E\u043C\u0435\u0436\u0443\u0442\u043E\u0447\u043D\u044B\u0439 \u044D\u0442\u0430\u043F, \u0430 \u043D\u0435 fully confirmed reversal.
2128
- - \u0414\u043B\u044F SHORT \u0431\u0443\u0434\u044C \u0441\u0442\u0440\u043E\u0436\u0435, \u0447\u0435\u043C \u0434\u043B\u044F LONG: bearish reversal \u0434\u043E\u043B\u0436\u0435\u043D \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u0442\u044C \u0431\u043E\u043B\u0435\u0435 \u0447\u0438\u0441\u0442\u043E\u0433\u043E follow-through, \u0430 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u043F\u043E bias/delta \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u0438\u043B\u044C\u043D\u0435\u0435 \u0441\u043D\u0438\u0436\u0430\u0442\u044C quality.
2129
- - \u0415\u0441\u043B\u0438 deltaAtPivot \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C reversal \u0438\u043B\u0438 bias \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435/BTC \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C, \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439 quality \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u0437-\u0437\u0430 \u0441\u0430\u043C\u043E\u0439 \u0434\u0438\u0432\u0435\u0440\u0433\u0435\u043D\u0446\u0438\u0438.
2130
- - \u0421\u043C\u043E\u0442\u0440\u0438 \u043D\u0430 divergenceAmplitudeAtrRatio / reclaimPct / confirmationCandleQuality: \u044D\u0442\u043E explicit setup-features, \u043E\u043F\u0438\u0441\u044B\u0432\u0430\u044E\u0449\u0438\u0435 \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0434\u0438\u0432\u0435\u0440\u0433\u0435\u043D\u0446\u0438\u044F \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0437\u043D\u0430\u0447\u0438\u043C\u0430 \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E ATR, \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0446\u0435\u043D\u0430 \u0432\u0435\u0440\u043D\u0443\u043B\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u0438 \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0439 \u0431\u044B\u043B\u0430 confirmation candle.
2131
- - confirmationDistancePct \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0434\u0430\u043B\u0435\u043A\u043E \u0446\u0435\u043D\u0430 \u0443\u0448\u043B\u0430 \u0437\u0430 confirmation level; \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439 quality, \u0435\u0441\u043B\u0438 confirmation \u0432\u0440\u043E\u0434\u0435 \u0431\u044B \u0435\u0441\u0442\u044C, \u043D\u043E \u0437\u0430\u043A\u0440\u0435\u043F\u043B\u0435\u043D\u0438\u0435 \u0437\u0430 \u0443\u0440\u043E\u0432\u043D\u0435\u043C \u043C\u0438\u043D\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0435.
2132
- - additionalIndicators.deltaAtPivot \u2014 \u044D\u0442\u043E proxy net-volume \u043F\u043E \u0441\u0432\u0435\u0447\u0435 pivot, \u0430 \u043D\u0435 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0438\u0439 lower-timeframe volume delta TradingView.
2351
+ VolumeDivergence addon:
2352
+ - This is a reversal setup built on price and normalized-volume divergence, not a breakout strategy.
2353
+ - Bullish divergence means price makes a lower low while volume makes a higher low.
2354
+ - Bearish divergence means price makes a higher high while volume makes a lower high.
2355
+ - For a bullish signal, do not overstate quality if price still failed to bounce meaningfully away from the current pivot low or failed to reclaim enough structure.
2356
+ - For a bearish signal, mirror that logic: do not overstate quality if price failed to move down meaningfully away from the current pivot high.
2357
+ - If \`payload.additionalIndicators.volumeDivergenceContext.confirmationReady=false\`, this is usually not a fully confirmed reversal yet; quality is often \`<= 4\` and a retest or confirmation is often still needed.
2358
+ - For live approval, treat \`confirmationReady\` as much more important than \`structureAdvanced\`; structure advance alone does not mean the reversal is entry-ready.
2359
+ - For a reversal setup, do not automatically reward quality just because the coin or BTC MA bias already matches the signal direction.
2360
+ - For LONG with \`entryTiming=structure_advance\`, avoid \`quality=5\`; that is an intermediate phase, not a fully confirmed reversal.
2361
+ - Be stricter for SHORT than for LONG: a bearish reversal should require cleaner follow-through, and bias or delta conflict should reduce quality more aggressively.
2362
+ - If \`deltaAtPivot\` conflicts with the reversal direction or the coin/BTC bias conflicts with the signal, do not overstate quality just because divergence exists.
2363
+ - Use \`divergenceAmplitudeAtrRatio\`, \`reclaimPct\`, and \`confirmationCandleQuality\` as explicit setup features describing how meaningful the divergence is relative to ATR, how much structure price reclaimed, and how strong the confirmation candle was.
2364
+ - \`confirmationDistancePct\` tells you how far price moved beyond the confirmation level; do not overstate quality when confirmation exists only marginally.
2365
+ - \`additionalIndicators.deltaAtPivot\` is a proxy net-volume value on the pivot candle, not true lower-timeframe TradingView volume delta.
2366
+ - If \`payload.additionalIndicators.derivativesContext\` exists, use Coinalyze-derived open interest, funding, and liquidations as positioning context: a liquidation flush can strengthen reversal odds, while crowded positioning against the trade or stale or missing data should not mechanically increase quality.
2133
2367
  `;
2134
2368
  VOLUME_DIVERGENCE_PAYLOAD_PROMPT = `
2135
- - \u0412 payload.additionalIndicators.volumeDivergenceContext \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044F \u043A\u0440\u0430\u0442\u043A\u0430\u044F \u0441\u0432\u043E\u0434\u043A\u0430 \u043F\u043E \u0441\u0438\u043B\u0435 \u0434\u0438\u0432\u0435\u0440\u0433\u0435\u043D\u0446\u0438\u0438:
2369
+ - \`payload.additionalIndicators.volumeDivergenceContext\` contains a compact divergence-strength summary:
2136
2370
  divergenceKind / confirmationPrice / confirmationReady / structureAdvanced / reboundFromPivotPct / confirmationDistancePct / priceDisplacementPct / divergenceAmplitudeAtrRatio / reclaimPct / confirmationCandleQuality / volumeDivergenceStrength / deltaAligned / coinBiasAligned / btcBiasAligned / deterministicQuality / approvalAllowedNow / structuralHardBlockReasons / maxAllowedQuality.
2137
- - \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u044D\u0442\u043E\u0442 context \u043A\u0430\u043A explicit strategy-specific summary, \u0430 \u043D\u0435 \u043F\u044B\u0442\u0430\u0439\u0441\u044F \u0437\u0430\u043D\u043E\u0432\u043E \u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E \u043E\u0431\u0449\u0438\u043C \u0441\u0432\u0435\u0447\u0430\u043C.
2371
+ - Use this context as the explicit strategy-specific summary instead of trying to derive the same conclusion again only from generic candles.
2372
+ - If \`payload.additionalIndicators.derivativesContext\` exists, it is a Coinalyze-derived summary of derivatives state at signal time; \`stale\` or \`missing_derivatives\` means that Coinalyze context must not be used.
2138
2373
  `;
2139
2374
  toFiniteNumberOrNull4 = (value) => {
2140
2375
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -2571,13 +2806,13 @@ var init_ai6 = __esm({
2571
2806
  getHardBlockReasonText4 = (reason) => {
2572
2807
  switch (reason) {
2573
2808
  case "no_rebound_from_pivot":
2574
- return "\u0446\u0435\u043D\u0430 \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u0430 \u0443\u0439\u0442\u0438 \u043E\u0442 pivot \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443 reversal";
2809
+ return "price failed to move away from the pivot in the reversal direction";
2575
2810
  case "weak_divergence_amplitude":
2576
- return "\u0434\u0438\u0432\u0435\u0440\u0433\u0435\u043D\u0446\u0438\u044F \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u0430\u043B\u0435\u043D\u044C\u043A\u0430\u044F \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E ATR";
2811
+ return "divergence amplitude is too small relative to ATR";
2577
2812
  case "weak_reclaim":
2578
- return "\u0446\u0435\u043D\u0430 \u0432\u0435\u0440\u043D\u0443\u043B\u0430 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u0430\u043B\u043E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B \u043F\u043E\u0441\u043B\u0435 pivot";
2813
+ return "price reclaimed too little structure after the pivot";
2579
2814
  case "weak_confirmation_candle":
2580
- return "confirmation candle \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0441\u043B\u0430\u0431\u0430\u044F";
2815
+ return "confirmation candle is too weak";
2581
2816
  default:
2582
2817
  return reason;
2583
2818
  }
@@ -2587,9 +2822,9 @@ var init_ai6 = __esm({
2587
2822
  return `VolumeDivergence guardrail: ${context.hardBlockReasons.map(getHardBlockReasonText4).join("; ")}.`;
2588
2823
  }
2589
2824
  if (!context.confirmationReady && context.entryTiming == null) {
2590
- return "VolumeDivergence guardrail: reversal \u0443\u0436\u0435 \u0432\u0438\u0434\u0435\u043D, \u043D\u043E confirmation level \u0435\u0449\u0435 \u043D\u0435 \u043F\u0440\u043E\u0439\u0434\u0435\u043D.";
2825
+ return "VolumeDivergence guardrail: reversal is visible, but the confirmation level has not been cleared yet.";
2591
2826
  }
2592
- return "VolumeDivergence guardrail: quality \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0441\u0442\u044C\u044E \u0438 \u0441\u0438\u043B\u043E\u0439 reversal away from pivot.";
2827
+ return "VolumeDivergence guardrail: quality is limited by confirmation state and the strength of reversal away from the pivot.";
2593
2828
  };
2594
2829
  postProcessAnalysis2 = ({
2595
2830
  signal,
@@ -2617,8 +2852,8 @@ var init_ai6 = __esm({
2617
2852
  takeProfitPrice: null,
2618
2853
  stopLossPrice: null,
2619
2854
  qualityReason: analysis.qualityReason || buildGuardrailReason(context),
2620
- triggerInvalidation: analysis.triggerInvalidation || (context.confirmationPrice != null ? `\u0416\u0434\u0430\u0442\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 reversal \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0443\u0440\u043E\u0432\u043D\u044F ${context.confirmationPrice}.` : "\u0416\u0434\u0430\u0442\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u0439 reversal \u043F\u043E\u0441\u043B\u0435 pivot."),
2621
- comment: analysis.comment || (context.hardBlockReasons.length > 0 ? `VolumeDivergence \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D: ${context.hardBlockReasons.map(getHardBlockReasonText4).join("; ")}.` : "VolumeDivergence \u043F\u043E\u043A\u0430 \u043E\u0441\u0442\u0430\u0435\u0442\u0441\u044F \u0432 \u0441\u0442\u0430\u0434\u0438\u0438 watch \u0434\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F reversal.")
2855
+ triggerInvalidation: analysis.triggerInvalidation || (context.confirmationPrice != null ? `Wait for reversal confirmation relative to level ${context.confirmationPrice}.` : "Wait for a confirmed reversal after the pivot."),
2856
+ comment: analysis.comment || (context.hardBlockReasons.length > 0 ? `VolumeDivergence rejected: ${context.hardBlockReasons.map(getHardBlockReasonText4).join("; ")}.` : "VolumeDivergence remains in watch mode until the reversal is confirmed.")
2622
2857
  };
2623
2858
  }
2624
2859
  return {
@@ -2646,7 +2881,7 @@ ${VOLUME_DIVERGENCE_PAYLOAD_PROMPT}`,
2646
2881
  const context = getVolumeDivergenceContextFromPayload(payload, signal);
2647
2882
  return `
2648
2883
 
2649
- \u0414\u043E\u043F. \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 VolumeDivergence:
2884
+ Additional VolumeDivergence context:
2650
2885
  - divergenceKind=${context.divergenceKind ?? "n/a"}
2651
2886
  - confirmationPrice=${context.confirmationPrice ?? "n/a"}
2652
2887
  - confirmationReady=${context.confirmationReady}
@@ -2673,26 +2908,26 @@ ${VOLUME_DIVERGENCE_PAYLOAD_PROMPT}`,
2673
2908
  - maxAllowedQuality=${context.maxAllowedQuality}
2674
2909
  - hardBlockReasons=${context.hardBlockReasons.join(", ") || "none"}
2675
2910
 
2676
- \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u0438\u043D\u0442\u0435\u0440\u043F\u0440\u0435\u0442\u0430\u0446\u0438\u0438 \u0434\u043B\u044F VolumeDivergence:
2677
- - \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u043E\u0446\u0435\u043D\u0438, \u0435\u0441\u0442\u044C \u043B\u0438 \u0440\u0435\u0430\u043B\u044C\u043D\u044B\u0439 reversal away from pivot, \u0430 \u043D\u0435 \u043F\u0440\u043E\u0441\u0442\u043E \u0444\u0430\u043A\u0442 \u0434\u0438\u0432\u0435\u0440\u0433\u0435\u043D\u0446\u0438\u0438;
2678
- - confirmationReady=false \u043E\u0431\u044B\u0447\u043D\u043E \u043E\u0437\u043D\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043E reversal \u0435\u0449\u0435 \u043D\u0435 fully confirmed;
2679
- - \u0435\u0441\u043B\u0438 \u0446\u0435\u043D\u0430 \u043D\u0435 \u043E\u0442\u0441\u043A\u043E\u0447\u0438\u043B\u0430 \u043E\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E pivot \u0432 \u0441\u0442\u043E\u0440\u043E\u043D\u0443 \u0441\u0438\u0433\u043D\u0430\u043B\u0430, \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;
2680
- - conflict \u043F\u043E delta/bias \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043D\u0438\u0436\u0430\u0442\u044C quality, \u0430 \u043D\u0435 \u0438\u0433\u043D\u043E\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C\u0441\u044F.
2911
+ Interpretation rules for VolumeDivergence:
2912
+ - first evaluate whether there is a real reversal away from the pivot, not just divergence on paper;
2913
+ - \`confirmationReady=false\` usually means the reversal is not fully confirmed yet;
2914
+ - if price did not bounce away from the current pivot in the signal direction, do not treat the setup as confirmed;
2915
+ - delta or bias conflict should reduce quality, not be ignored.
2681
2916
  `;
2682
2917
  },
2683
- mapEntryRuntimeFromConfig: (config7) => (0, import_strategies11.mapAiRuntimeFromConfig)(
2684
- config7
2918
+ mapEntryRuntimeFromConfig: (config8) => (0, import_strategies10.mapAiRuntimeFromConfig)(
2919
+ config8
2685
2920
  )
2686
2921
  };
2687
2922
  }
2688
2923
  });
2689
2924
 
2690
2925
  // src/VolumeDivergence/adapters/ml.ts
2691
- var import_strategies12, toVolumeDivergenceMlStrategyConfig, volumeDivergenceMlAdapter;
2926
+ var import_strategies11, toVolumeDivergenceMlStrategyConfig, volumeDivergenceMlAdapter;
2692
2927
  var init_ml5 = __esm({
2693
2928
  "src/VolumeDivergence/adapters/ml.ts"() {
2694
2929
  "use strict";
2695
- import_strategies12 = require("@tradejs/core/strategies");
2930
+ import_strategies11 = require("@tradejs/core/strategies");
2696
2931
  toVolumeDivergenceMlStrategyConfig = (input) => {
2697
2932
  if (!input) return void 0;
2698
2933
  return {
@@ -2712,50 +2947,34 @@ var init_ml5 = __esm({
2712
2947
  normalizeStrategyConfig: (strategyConfig) => {
2713
2948
  return toVolumeDivergenceMlStrategyConfig(strategyConfig);
2714
2949
  },
2715
- mapEntryRuntimeFromConfig: (config7) => (0, import_strategies12.mapMlRuntimeFromConfig)(config7, {
2950
+ mapEntryRuntimeFromConfig: (config8) => (0, import_strategies11.mapMlRuntimeFromConfig)(config8, {
2716
2951
  strategyConfig: toVolumeDivergenceMlStrategyConfig(
2717
- config7
2952
+ config8
2718
2953
  )
2719
2954
  })
2720
2955
  };
2721
2956
  }
2722
2957
  });
2723
2958
 
2724
- // src/VolumeDivergence/hooks.ts
2725
- var import_strategies13, volumeDivergenceBeforePlaceOrderHook;
2726
- var init_hooks3 = __esm({
2727
- "src/VolumeDivergence/hooks.ts"() {
2728
- "use strict";
2729
- import_strategies13 = require("@tradejs/node/strategies");
2730
- volumeDivergenceBeforePlaceOrderHook = (0, import_strategies13.createCloseOppositeBeforePlaceOrderHook)({
2731
- isEnabled: (config7) => Boolean(config7.CLOSE_OPPOSITE_POSITIONS)
2732
- });
2733
- }
2734
- });
2735
-
2736
2959
  // src/VolumeDivergence/manifest.ts
2737
2960
  var volumeDivergenceManifest;
2738
- var init_manifest6 = __esm({
2961
+ var init_manifest7 = __esm({
2739
2962
  "src/VolumeDivergence/manifest.ts"() {
2740
2963
  "use strict";
2741
- init_ai6();
2964
+ init_ai7();
2742
2965
  init_ml5();
2743
- init_hooks3();
2744
2966
  volumeDivergenceManifest = {
2745
2967
  name: "VolumeDivergence",
2746
- hooks: {
2747
- beforePlaceOrder: volumeDivergenceBeforePlaceOrderHook
2748
- },
2749
2968
  aiAdapter: volumeDivergenceAiAdapter,
2750
2969
  mlAdapter: volumeDivergenceMlAdapter
2751
2970
  };
2752
2971
  }
2753
2972
  });
2754
2973
 
2755
- // src/TrendLine/config.ts
2974
+ // src/AdaptiveMomentumRibbon/config.ts
2756
2975
  var config;
2757
2976
  var init_config = __esm({
2758
- "src/TrendLine/config.ts"() {
2977
+ "src/AdaptiveMomentumRibbon/config.ts"() {
2759
2978
  "use strict";
2760
2979
  config = {
2761
2980
  ENV: "BACKTEST",
@@ -2764,110 +2983,54 @@ var init_config = __esm({
2764
2983
  CLOSE_OPPOSITE_POSITIONS: false,
2765
2984
  BACKTEST_PRICE_MODE: "mid",
2766
2985
  AI_ENABLED: false,
2986
+ AI_MODE: "llm",
2767
2987
  ML_ENABLED: false,
2768
2988
  ML_THRESHOLD: 0.1,
2769
2989
  MIN_AI_QUALITY: 3,
2770
2990
  FEE_PERCENT: 5e-3,
2771
2991
  MAX_LOSS_VALUE: 10,
2772
- MA_FAST: 14,
2773
- MA_MEDIUM: 49,
2774
- MA_SLOW: 50,
2775
- OBV_SMA: 10,
2776
- ATR: 14,
2777
- ATR_PCT_SHORT: 7,
2778
- ATR_PCT_LONG: 30,
2779
- BB: 20,
2780
- BB_STD: 2,
2781
- MACD_FAST: 12,
2782
- MACD_SLOW: 26,
2783
- MACD_SIGNAL: 9,
2784
- LEVEL_LOOKBACK: 20,
2785
- LEVEL_DELAY: 2,
2786
- TRENDLINE: {
2787
- minTouches: 4,
2788
- offset: 3,
2789
- epsilon: 3e-3,
2790
- epsilonOffset: 4e-3
2791
- },
2792
- HIGHS: {
2992
+ AMR_LOOKBACK_BARS: 200,
2993
+ AMR_MOMENTUM_PERIOD: 32,
2994
+ AMR_BUTTERWORTH_SMOOTHING: 4,
2995
+ AMR_WAIT_CLOSE: true,
2996
+ AMR_CONFIRM_ON_NEXT_BAR: true,
2997
+ AMR_MIN_SIGNAL_OSC_ABS: 0.55,
2998
+ AMR_REQUIRE_KC_BIAS: true,
2999
+ AMR_MIN_BARS_BETWEEN_SIGNALS: 12,
3000
+ AMR_SHOW_INVALIDATION_LEVELS: true,
3001
+ AMR_SHOW_KELTNER_CHANNEL: true,
3002
+ AMR_KC_LENGTH: 20,
3003
+ AMR_KC_MA_TYPE: "EMA",
3004
+ AMR_ATR_LENGTH: 14,
3005
+ AMR_ATR_MULTIPLIER: 2,
3006
+ AMR_EXIT_ON_INVALIDATION: true,
3007
+ AMR_LINE_PLOTS: ["kcMidline", "kcUpper", "kcLower", "invalidationLevel"],
3008
+ LONG: {
2793
3009
  enable: true,
2794
3010
  direction: "LONG",
2795
- TP: 4,
2796
- SL: 1.3,
2797
- minRiskRatio: 2
3011
+ TP: 2,
3012
+ SL: 1
2798
3013
  },
2799
- LOWS: {
3014
+ SHORT: {
2800
3015
  enable: true,
2801
3016
  direction: "SHORT",
2802
- TP: 4,
2803
- SL: 1.3,
2804
- minRiskRatio: 2
3017
+ TP: 2,
3018
+ SL: 1
2805
3019
  }
2806
3020
  };
2807
3021
  }
2808
3022
  });
2809
3023
 
2810
- // src/ReverseTrendLine/config.ts
3024
+ // src/Breakout/config.ts
2811
3025
  var config2;
2812
3026
  var init_config2 = __esm({
2813
- "src/ReverseTrendLine/config.ts"() {
3027
+ "src/Breakout/config.ts"() {
2814
3028
  "use strict";
2815
3029
  config2 = {
2816
- ENV: "BACKTEST",
2817
- INTERVAL: "15",
2818
- MAKE_ORDERS: true,
2819
- CLOSE_OPPOSITE_POSITIONS: false,
2820
- BACKTEST_PRICE_MODE: "mid",
2821
- AI_ENABLED: false,
2822
- MIN_AI_QUALITY: 3,
2823
- FEE_PERCENT: 5e-3,
2824
- MAX_LOSS_VALUE: 10,
2825
- MA_FAST: 14,
3030
+ ML_ENABLED: false,
3031
+ MA_FAST: 49,
2826
3032
  MA_MEDIUM: 49,
2827
- MA_SLOW: 50,
2828
- OBV_SMA: 10,
2829
- ATR: 14,
2830
- ATR_PCT_SHORT: 7,
2831
- ATR_PCT_LONG: 30,
2832
- BB: 20,
2833
- BB_STD: 2,
2834
- MACD_FAST: 12,
2835
- MACD_SLOW: 26,
2836
- MACD_SIGNAL: 9,
2837
- TRENDLINE: {
2838
- minTouches: 4,
2839
- offset: 3,
2840
- epsilon: 3e-3,
2841
- epsilonOffset: 4e-3
2842
- },
2843
- HIGHS: {
2844
- enable: true,
2845
- direction: "SHORT",
2846
- TP: 3.2,
2847
- SL: 1.1,
2848
- minRiskRatio: 1.6
2849
- },
2850
- LOWS: {
2851
- enable: true,
2852
- direction: "LONG",
2853
- TP: 3.2,
2854
- SL: 1.1,
2855
- minRiskRatio: 1.6
2856
- }
2857
- };
2858
- }
2859
- });
2860
-
2861
- // src/Breakout/config.ts
2862
- var config3;
2863
- var init_config3 = __esm({
2864
- "src/Breakout/config.ts"() {
2865
- "use strict";
2866
- config3 = {
2867
- ML_ENABLED: false,
2868
- MA_FAST: 49,
2869
- MA_MEDIUM: 49,
2870
- MA_SLOW: 99,
3033
+ MA_SLOW: 99,
2871
3034
  OBV_SMA: 10,
2872
3035
  ATR: 14,
2873
3036
  ATR_PCT_SHORT: 7,
@@ -2964,15 +3127,277 @@ var init_config3 = __esm({
2964
3127
  }
2965
3128
  });
2966
3129
 
3130
+ // src/MaStrategy/config.ts
3131
+ var config3;
3132
+ var init_config3 = __esm({
3133
+ "src/MaStrategy/config.ts"() {
3134
+ "use strict";
3135
+ config3 = {
3136
+ ENV: "BACKTEST",
3137
+ INTERVAL: "15",
3138
+ MAKE_ORDERS: true,
3139
+ CLOSE_OPPOSITE_POSITIONS: false,
3140
+ BACKTEST_PRICE_MODE: "mid",
3141
+ AI_ENABLED: false,
3142
+ AI_MODE: "llm",
3143
+ ML_ENABLED: false,
3144
+ ML_THRESHOLD: 0.1,
3145
+ MIN_AI_QUALITY: 3,
3146
+ FEE_PERCENT: 5e-3,
3147
+ MAX_LOSS_VALUE: 10,
3148
+ TRADE_COOLDOWN_MS: 0,
3149
+ MA_FAST: 21,
3150
+ MA_SLOW: 55,
3151
+ LONG: {
3152
+ enable: true,
3153
+ direction: "LONG",
3154
+ TP: 2,
3155
+ SL: 1,
3156
+ minRiskRatio: 1.5
3157
+ },
3158
+ SHORT: {
3159
+ enable: true,
3160
+ direction: "SHORT",
3161
+ TP: 2,
3162
+ SL: 1,
3163
+ minRiskRatio: 1.5
3164
+ }
3165
+ };
3166
+ }
3167
+ });
3168
+
3169
+ // src/ReverseTrendLine/config.ts
3170
+ var config4;
3171
+ var init_config4 = __esm({
3172
+ "src/ReverseTrendLine/config.ts"() {
3173
+ "use strict";
3174
+ config4 = {
3175
+ ENV: "BACKTEST",
3176
+ INTERVAL: "15",
3177
+ MAKE_ORDERS: true,
3178
+ CLOSE_OPPOSITE_POSITIONS: false,
3179
+ BACKTEST_PRICE_MODE: "mid",
3180
+ AI_ENABLED: false,
3181
+ AI_MODE: "llm",
3182
+ MIN_AI_QUALITY: 3,
3183
+ FEE_PERCENT: 5e-3,
3184
+ MAX_LOSS_VALUE: 10,
3185
+ MA_FAST: 14,
3186
+ MA_MEDIUM: 49,
3187
+ MA_SLOW: 50,
3188
+ OBV_SMA: 10,
3189
+ ATR: 14,
3190
+ ATR_PCT_SHORT: 7,
3191
+ ATR_PCT_LONG: 30,
3192
+ BB: 20,
3193
+ BB_STD: 2,
3194
+ MACD_FAST: 12,
3195
+ MACD_SLOW: 26,
3196
+ MACD_SIGNAL: 9,
3197
+ TRENDLINE: {
3198
+ minTouches: 4,
3199
+ offset: 3,
3200
+ epsilon: 3e-3,
3201
+ epsilonOffset: 4e-3
3202
+ },
3203
+ HIGHS: {
3204
+ enable: true,
3205
+ direction: "SHORT",
3206
+ TP: 3.2,
3207
+ SL: 1.1,
3208
+ minRiskRatio: 1.6
3209
+ },
3210
+ LOWS: {
3211
+ enable: true,
3212
+ direction: "LONG",
3213
+ TP: 3.2,
3214
+ SL: 1.1,
3215
+ minRiskRatio: 1.6
3216
+ }
3217
+ };
3218
+ }
3219
+ });
3220
+
3221
+ // src/TrendShift/config.ts
3222
+ var config5;
3223
+ var init_config5 = __esm({
3224
+ "src/TrendShift/config.ts"() {
3225
+ "use strict";
3226
+ config5 = {
3227
+ ENV: "BACKTEST",
3228
+ INTERVAL: "15",
3229
+ MAKE_ORDERS: true,
3230
+ CLOSE_OPPOSITE_POSITIONS: false,
3231
+ BACKTEST_PRICE_MODE: "mid",
3232
+ AI_ENABLED: false,
3233
+ AI_MODE: "llm",
3234
+ ML_ENABLED: false,
3235
+ ML_THRESHOLD: 0.1,
3236
+ MIN_AI_QUALITY: 3,
3237
+ FEE_PERCENT: 5e-3,
3238
+ MAX_LOSS_VALUE: 10,
3239
+ MA_FAST: 14,
3240
+ MA_MEDIUM: 49,
3241
+ MA_SLOW: 50,
3242
+ OBV_SMA: 10,
3243
+ ATR: 14,
3244
+ ATR_PCT_SHORT: 7,
3245
+ ATR_PCT_LONG: 30,
3246
+ BB: 20,
3247
+ BB_STD: 2,
3248
+ MACD_FAST: 12,
3249
+ MACD_SLOW: 26,
3250
+ MACD_SIGNAL: 9,
3251
+ TRENDSHIFT_MULTIPLICATIVE_FACTOR: 4,
3252
+ TRENDSHIFT_SLOPE: 12,
3253
+ TRENDSHIFT_ATR_LENGTH: 150,
3254
+ TRENDSHIFT_WIDTH_PCT: 75,
3255
+ TRENDSHIFT_CONFIRM_FLIP_WITH_CLOSE: true,
3256
+ TRENDSHIFT_MIN_FLIP_DISTANCE_ATR: 0.15,
3257
+ TRENDSHIFT_EXIT_ON_OPPOSITE_FLIP: true,
3258
+ TRENDSHIFT_MAX_FIGURE_POINTS: 180,
3259
+ LONG: {
3260
+ enable: true,
3261
+ direction: "LONG",
3262
+ TP: 2.8,
3263
+ SL: 1.1,
3264
+ minRiskRatio: 1.6
3265
+ },
3266
+ SHORT: {
3267
+ enable: true,
3268
+ direction: "SHORT",
3269
+ TP: 2.8,
3270
+ SL: 1.1,
3271
+ minRiskRatio: 1.6
3272
+ }
3273
+ };
3274
+ }
3275
+ });
3276
+
3277
+ // src/TrendLine/config.ts
3278
+ var config6;
3279
+ var init_config6 = __esm({
3280
+ "src/TrendLine/config.ts"() {
3281
+ "use strict";
3282
+ config6 = {
3283
+ ENV: "BACKTEST",
3284
+ INTERVAL: "15",
3285
+ MAKE_ORDERS: true,
3286
+ CLOSE_OPPOSITE_POSITIONS: false,
3287
+ BACKTEST_PRICE_MODE: "mid",
3288
+ AI_ENABLED: false,
3289
+ AI_MODE: "llm",
3290
+ ML_ENABLED: false,
3291
+ ML_THRESHOLD: 0.1,
3292
+ MIN_AI_QUALITY: 3,
3293
+ FEE_PERCENT: 5e-3,
3294
+ MAX_LOSS_VALUE: 10,
3295
+ MA_FAST: 14,
3296
+ MA_MEDIUM: 49,
3297
+ MA_SLOW: 50,
3298
+ OBV_SMA: 10,
3299
+ ATR: 14,
3300
+ ATR_PCT_SHORT: 7,
3301
+ ATR_PCT_LONG: 30,
3302
+ BB: 20,
3303
+ BB_STD: 2,
3304
+ MACD_FAST: 12,
3305
+ MACD_SLOW: 26,
3306
+ MACD_SIGNAL: 9,
3307
+ LEVEL_LOOKBACK: 20,
3308
+ LEVEL_DELAY: 2,
3309
+ TRENDLINE: {
3310
+ minTouches: 4,
3311
+ offset: 3,
3312
+ epsilon: 3e-3,
3313
+ epsilonOffset: 4e-3
3314
+ },
3315
+ HIGHS: {
3316
+ enable: true,
3317
+ direction: "LONG",
3318
+ TP: 4,
3319
+ SL: 1.3,
3320
+ minRiskRatio: 2
3321
+ },
3322
+ LOWS: {
3323
+ enable: true,
3324
+ direction: "SHORT",
3325
+ TP: 4,
3326
+ SL: 1.3,
3327
+ minRiskRatio: 2
3328
+ }
3329
+ };
3330
+ }
3331
+ });
3332
+
3333
+ // src/VolumeDivergence/config.ts
3334
+ var config7;
3335
+ var init_config7 = __esm({
3336
+ "src/VolumeDivergence/config.ts"() {
3337
+ "use strict";
3338
+ config7 = {
3339
+ ENV: "BACKTEST",
3340
+ INTERVAL: "15",
3341
+ MAKE_ORDERS: true,
3342
+ CLOSE_OPPOSITE_POSITIONS: false,
3343
+ BACKTEST_PRICE_MODE: "mid",
3344
+ AI_ENABLED: false,
3345
+ AI_MODE: "llm",
3346
+ ML_ENABLED: false,
3347
+ ML_THRESHOLD: 0.1,
3348
+ MIN_AI_QUALITY: 3,
3349
+ FEE_PERCENT: 5e-3,
3350
+ MAX_LOSS_VALUE: 10,
3351
+ MA_FAST: 14,
3352
+ MA_MEDIUM: 49,
3353
+ MA_SLOW: 50,
3354
+ OBV_SMA: 10,
3355
+ ATR: 14,
3356
+ ATR_PCT_SHORT: 7,
3357
+ ATR_PCT_LONG: 30,
3358
+ BB: 20,
3359
+ BB_STD: 2,
3360
+ MACD_FAST: 12,
3361
+ MACD_SLOW: 26,
3362
+ MACD_SIGNAL: 9,
3363
+ LEVEL_LOOKBACK: 20,
3364
+ LEVEL_DELAY: 2,
3365
+ NORMALIZATION_LENGTH: 100,
3366
+ PIVOT_LOOKBACK_LEFT: 8,
3367
+ PIVOT_LOOKBACK_RIGHT: 3,
3368
+ MIN_BARS_BETWEEN_PIVOTS: 4,
3369
+ MAX_BARS_BETWEEN_PIVOTS: 36,
3370
+ ALLOW_STRUCTURE_ADVANCE_ENTRY: false,
3371
+ MIN_DIVERGENCE_AMPLITUDE_ATR_RATIO: 0.35,
3372
+ MIN_RECLAIM_PCT: 105,
3373
+ MIN_CONFIRMATION_CANDLE_QUALITY: 0.58,
3374
+ BULLISH: {
3375
+ enable: true,
3376
+ direction: "LONG",
3377
+ TP: 4,
3378
+ SL: 1.3,
3379
+ minRiskRatio: 2
3380
+ },
3381
+ BEARISH: {
3382
+ enable: true,
3383
+ direction: "SHORT",
3384
+ TP: 4,
3385
+ SL: 1.3,
3386
+ minRiskRatio: 2
3387
+ }
3388
+ };
3389
+ }
3390
+ });
3391
+
2967
3392
  // ../../node_modules/lodash/lodash.js
2968
3393
  var require_lodash = __commonJS({
2969
3394
  "../../node_modules/lodash/lodash.js"(exports2, module2) {
2970
3395
  "use strict";
2971
3396
  (function() {
2972
3397
  var undefined2;
2973
- var VERSION = "4.17.21";
3398
+ var VERSION = "4.18.1";
2974
3399
  var LARGE_ARRAY_SIZE = 200;
2975
- var CORE_ERROR_TEXT = "Unsupported core-js use. Try https://npms.io/search?q=ponyfill.", FUNC_ERROR_TEXT = "Expected a function", INVALID_TEMPL_VAR_ERROR_TEXT = "Invalid `variable` option passed into `_.template`";
3400
+ var CORE_ERROR_TEXT = "Unsupported core-js use. Try https://npms.io/search?q=ponyfill.", FUNC_ERROR_TEXT = "Expected a function", INVALID_TEMPL_VAR_ERROR_TEXT = "Invalid `variable` option passed into `_.template`", INVALID_TEMPL_IMPORTS_ERROR_TEXT = "Invalid `imports` option passed into `_.template`";
2976
3401
  var HASH_UNDEFINED = "__lodash_hash_undefined__";
2977
3402
  var MAX_MEMOIZE_SIZE = 500;
2978
3403
  var PLACEHOLDER = "__lodash_placeholder__";
@@ -4898,9 +5323,22 @@ var require_lodash = __commonJS({
4898
5323
  }
4899
5324
  function baseUnset(object, path) {
4900
5325
  path = castPath(path, object);
4901
- object = parent(object, path);
4902
- return object == null || delete object[toKey(last(path))];
4903
- }
5326
+ var index = -1, length = path.length;
5327
+ if (!length) {
5328
+ return true;
5329
+ }
5330
+ while (++index < length) {
5331
+ var key = toKey(path[index]);
5332
+ if (key === "__proto__" && !hasOwnProperty.call(object, "__proto__")) {
5333
+ return false;
5334
+ }
5335
+ if ((key === "constructor" || key === "prototype") && index < length - 1) {
5336
+ return false;
5337
+ }
5338
+ }
5339
+ var obj = parent(object, path);
5340
+ return obj == null || delete obj[toKey(last(path))];
5341
+ }
4904
5342
  function baseUpdate(object, path, updater, customizer) {
4905
5343
  return baseSet(object, path, updater(baseGet(object, path)), customizer);
4906
5344
  }
@@ -5381,14 +5819,14 @@ var require_lodash = __commonJS({
5381
5819
  if (step && typeof step != "number" && isIterateeCall(start, end, step)) {
5382
5820
  end = step = undefined2;
5383
5821
  }
5384
- start = toFinite(start);
5822
+ start = toFinite2(start);
5385
5823
  if (end === undefined2) {
5386
5824
  end = start;
5387
5825
  start = 0;
5388
5826
  } else {
5389
- end = toFinite(end);
5827
+ end = toFinite2(end);
5390
5828
  }
5391
- step = step === undefined2 ? start < end ? 1 : -1 : toFinite(step);
5829
+ step = step === undefined2 ? start < end ? 1 : -1 : toFinite2(step);
5392
5830
  return baseRange(start, end, step, fromRight);
5393
5831
  };
5394
5832
  }
@@ -6226,7 +6664,7 @@ var require_lodash = __commonJS({
6226
6664
  var index = -1, length = pairs == null ? 0 : pairs.length, result2 = {};
6227
6665
  while (++index < length) {
6228
6666
  var pair = pairs[index];
6229
- result2[pair[0]] = pair[1];
6667
+ baseAssignValue(result2, pair[0], pair[1]);
6230
6668
  }
6231
6669
  return result2;
6232
6670
  }
@@ -7161,7 +7599,7 @@ var require_lodash = __commonJS({
7161
7599
  var tag = getTag(value), func = tag == mapTag ? mapToArray : tag == setTag ? setToArray : values;
7162
7600
  return func(value);
7163
7601
  }
7164
- function toFinite(value) {
7602
+ function toFinite2(value) {
7165
7603
  if (!value) {
7166
7604
  return value === 0 ? value : 0;
7167
7605
  }
@@ -7173,7 +7611,7 @@ var require_lodash = __commonJS({
7173
7611
  return value === value ? value : 0;
7174
7612
  }
7175
7613
  function toInteger(value) {
7176
- var result2 = toFinite(value), remainder = result2 % 1;
7614
+ var result2 = toFinite2(value), remainder = result2 % 1;
7177
7615
  return result2 === result2 ? remainder ? result2 - remainder : result2 : 0;
7178
7616
  }
7179
7617
  function toLength(value) {
@@ -7452,12 +7890,12 @@ var require_lodash = __commonJS({
7452
7890
  return baseClamp(toNumber(number), lower, upper);
7453
7891
  }
7454
7892
  function inRange(number, start, end) {
7455
- start = toFinite(start);
7893
+ start = toFinite2(start);
7456
7894
  if (end === undefined2) {
7457
7895
  end = start;
7458
7896
  start = 0;
7459
7897
  } else {
7460
- end = toFinite(end);
7898
+ end = toFinite2(end);
7461
7899
  }
7462
7900
  number = toNumber(number);
7463
7901
  return baseInRange(number, start, end);
@@ -7479,12 +7917,12 @@ var require_lodash = __commonJS({
7479
7917
  lower = 0;
7480
7918
  upper = 1;
7481
7919
  } else {
7482
- lower = toFinite(lower);
7920
+ lower = toFinite2(lower);
7483
7921
  if (upper === undefined2) {
7484
7922
  upper = lower;
7485
7923
  lower = 0;
7486
7924
  } else {
7487
- upper = toFinite(upper);
7925
+ upper = toFinite2(upper);
7488
7926
  }
7489
7927
  }
7490
7928
  if (lower > upper) {
@@ -7610,8 +8048,13 @@ var require_lodash = __commonJS({
7610
8048
  options = undefined2;
7611
8049
  }
7612
8050
  string = toString(string);
7613
- options = assignInWith({}, options, settings, customDefaultsAssignIn);
7614
- var imports = assignInWith({}, options.imports, settings.imports, customDefaultsAssignIn), importsKeys = keys(imports), importsValues = baseValues(imports, importsKeys);
8051
+ options = assignWith({}, options, settings, customDefaultsAssignIn);
8052
+ var imports = assignWith({}, options.imports, settings.imports, customDefaultsAssignIn), importsKeys = keys(imports), importsValues = baseValues(imports, importsKeys);
8053
+ arrayEach(importsKeys, function(key) {
8054
+ if (reForbiddenIdentifierChars.test(key)) {
8055
+ throw new Error2(INVALID_TEMPL_IMPORTS_ERROR_TEXT);
8056
+ }
8057
+ });
7615
8058
  var isEscaping, isEvaluating, index = 0, interpolate = options.interpolate || reNoMatch, source = "__p += '";
7616
8059
  var reDelimiters = RegExp2(
7617
8060
  (options.escape || reNoMatch).source + "|" + interpolate.source + "|" + (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + "|" + (options.evaluate || reNoMatch).source + "|$",
@@ -7944,7 +8387,7 @@ var require_lodash = __commonJS({
7944
8387
  var multiply = createMathOperation(function(multiplier, multiplicand) {
7945
8388
  return multiplier * multiplicand;
7946
8389
  }, 1);
7947
- var round7 = createRound("round");
8390
+ var round8 = createRound("round");
7948
8391
  var subtract = createMathOperation(function(minuend, subtrahend) {
7949
8392
  return minuend - subtrahend;
7950
8393
  }, 0);
@@ -8222,7 +8665,7 @@ var require_lodash = __commonJS({
8222
8665
  lodash.repeat = repeat;
8223
8666
  lodash.replace = replace;
8224
8667
  lodash.result = result;
8225
- lodash.round = round7;
8668
+ lodash.round = round8;
8226
8669
  lodash.runInContext = runInContext2;
8227
8670
  lodash.sample = sample;
8228
8671
  lodash.size = size;
@@ -8241,7 +8684,7 @@ var require_lodash = __commonJS({
8241
8684
  lodash.sumBy = sumBy;
8242
8685
  lodash.template = template;
8243
8686
  lodash.times = times;
8244
- lodash.toFinite = toFinite;
8687
+ lodash.toFinite = toFinite2;
8245
8688
  lodash.toInteger = toInteger;
8246
8689
  lodash.toLength = toLength;
8247
8690
  lodash.toLower = toLower;
@@ -8461,7 +8904,7 @@ var init_core = __esm({
8461
8904
  "use strict";
8462
8905
  import_lodash = __toESM(require_lodash());
8463
8906
  init_figures();
8464
- getSignals = (config7, indicators) => {
8907
+ getSignals = (config8, indicators) => {
8465
8908
  const {
8466
8909
  candle,
8467
8910
  prevCandle,
@@ -8486,7 +8929,7 @@ var init_core = __esm({
8486
8929
  const closeBelowLowerBB = candle.close < bb.lower;
8487
8930
  const closeBelowLowLevel = candle.close < lowLevel;
8488
8931
  const closeBelowPrevClose = candle.close < prevCandle.close;
8489
- const atrThreshold = atr * config7.ATR_OPEN;
8932
+ const atrThreshold = atr * config8.ATR_OPEN;
8490
8933
  const trueRange = Math.max(
8491
8934
  candle.high - candle.low,
8492
8935
  Math.abs(candle.high - prevCandle.close),
@@ -8509,9 +8952,9 @@ var init_core = __esm({
8509
8952
  ["CLOSE_BELOW_PREV_CLOSE" /* CLOSE_BELOW_PREV_CLOSE */]: closeBelowPrevClose
8510
8953
  };
8511
8954
  };
8512
- checkSignals = (config7, minScore, signals) => {
8955
+ checkSignals = (config8, minScore, signals) => {
8513
8956
  let score = 0;
8514
- for (const [signal, rules] of Object.entries(config7)) {
8957
+ for (const [signal, rules] of Object.entries(config8)) {
8515
8958
  if (rules.required && !signals[signal]) {
8516
8959
  return false;
8517
8960
  }
@@ -8521,7 +8964,7 @@ var init_core = __esm({
8521
8964
  }
8522
8965
  return score >= minScore;
8523
8966
  };
8524
- createBreakoutCore = async ({ config: config7, strategyApi }) => {
8967
+ createBreakoutCore = async ({ config: config8, strategyApi }) => {
8525
8968
  return async (candle, btcCandle) => {
8526
8969
  if (import_lodash.default.isEmpty(candle)) {
8527
8970
  return strategyApi.skip("NO_DATA");
@@ -8536,8 +8979,8 @@ var init_core = __esm({
8536
8979
  const { currentPrice, timestamp } = await strategyApi.getMarketData();
8537
8980
  const position = await strategyApi.getCurrentPosition();
8538
8981
  const positionExists = await strategyApi.isCurrentPositionExists();
8539
- const qty = config7.LIMIT / currentPrice;
8540
- const signals = getSignals(config7, {
8982
+ const qty = config8.LIMIT / currentPrice;
8983
+ const signals = getSignals(config8, {
8541
8984
  ...indicatorValues,
8542
8985
  prevCandle: indicatorValues.prevCandle,
8543
8986
  highLevel: indicatorValues.highLevel,
@@ -8548,13 +8991,13 @@ var init_core = __esm({
8548
8991
  }
8549
8992
  });
8550
8993
  const shouldOpenLong = checkSignals(
8551
- config7.SIGNALS_LONG,
8552
- config7.REQUIRED_SCORE_LONG,
8994
+ config8.SIGNALS_LONG,
8995
+ config8.REQUIRED_SCORE_LONG,
8553
8996
  signals
8554
8997
  );
8555
8998
  const shouldOpenShort = checkSignals(
8556
- config7.SIGNALS_SHORT,
8557
- config7.REQUIRED_SCORE_SHORT,
8999
+ config8.SIGNALS_SHORT,
9000
+ config8.REQUIRED_SCORE_SHORT,
8558
9001
  signals
8559
9002
  );
8560
9003
  if (!positionExists || !position) {
@@ -8562,8 +9005,8 @@ var init_core = __esm({
8562
9005
  const { stopLossPrice, takeProfitPrice } = strategyApi.getDirectionalTpSlPrices({
8563
9006
  price: currentPrice,
8564
9007
  direction: "LONG",
8565
- takeProfitDelta: config7.TP_LONG?.[0]?.profit ?? 0,
8566
- stopLossDelta: config7.SL_LONG,
9008
+ takeProfitDelta: config8.TP_LONG?.[0]?.profit ?? 0,
9009
+ stopLossDelta: config8.SL_LONG,
8567
9010
  unit: "ratio"
8568
9011
  });
8569
9012
  return strategyApi.entry({
@@ -8588,7 +9031,7 @@ var init_core = __esm({
8588
9031
  orderPlan: {
8589
9032
  qty,
8590
9033
  stopLossPrice,
8591
- takeProfits: config7.TP_LONG.length > 0 ? config7.TP_LONG.map(({ rate, profit }) => ({
9034
+ takeProfits: config8.TP_LONG.length > 0 ? config8.TP_LONG.map(({ rate, profit }) => ({
8592
9035
  rate,
8593
9036
  price: currentPrice * (1 + profit)
8594
9037
  })) : [{ rate: 1, price: takeProfitPrice }]
@@ -8599,8 +9042,8 @@ var init_core = __esm({
8599
9042
  const { stopLossPrice, takeProfitPrice } = strategyApi.getDirectionalTpSlPrices({
8600
9043
  price: currentPrice,
8601
9044
  direction: "SHORT",
8602
- takeProfitDelta: config7.TP_SHORT?.[0]?.profit ?? 0,
8603
- stopLossDelta: config7.SL_SHORT,
9045
+ takeProfitDelta: config8.TP_SHORT?.[0]?.profit ?? 0,
9046
+ stopLossDelta: config8.SL_SHORT,
8604
9047
  unit: "ratio"
8605
9048
  });
8606
9049
  return strategyApi.entry({
@@ -8625,7 +9068,7 @@ var init_core = __esm({
8625
9068
  orderPlan: {
8626
9069
  qty,
8627
9070
  stopLossPrice,
8628
- takeProfits: config7.TP_SHORT.length > 0 ? config7.TP_SHORT.map(({ rate, profit }) => ({
9071
+ takeProfits: config8.TP_SHORT.length > 0 ? config8.TP_SHORT.map(({ rate, profit }) => ({
8629
9072
  rate,
8630
9073
  price: currentPrice * (1 - profit)
8631
9074
  })) : [{ rate: 1, price: takeProfitPrice }]
@@ -8662,17 +9105,17 @@ var strategy_exports = {};
8662
9105
  __export(strategy_exports, {
8663
9106
  BreakoutStrategyCreator: () => BreakoutStrategyCreator
8664
9107
  });
8665
- var import_strategies14, BreakoutStrategyCreator;
9108
+ var import_strategies12, BreakoutStrategyCreator;
8666
9109
  var init_strategy = __esm({
8667
9110
  "src/Breakout/strategy.ts"() {
8668
9111
  "use strict";
8669
- import_strategies14 = require("@tradejs/node/strategies");
8670
- init_config3();
9112
+ import_strategies12 = require("@tradejs/node/strategies");
9113
+ init_config2();
8671
9114
  init_core();
8672
9115
  init_manifest2();
8673
- BreakoutStrategyCreator = (0, import_strategies14.createStrategyRuntime)({
9116
+ BreakoutStrategyCreator = (0, import_strategies12.createStrategyRuntime)({
8674
9117
  strategyName: "Breakout",
8675
- defaults: config3,
9118
+ defaults: config2,
8676
9119
  createCore: createBreakoutCore,
8677
9120
  manifest: breakoutManifest,
8678
9121
  strategyDirectory: __dirname
@@ -8867,7 +9310,7 @@ var init_risk = __esm({
8867
9310
  });
8868
9311
 
8869
9312
  // src/TrendLine/core.ts
8870
- var import_math3, import_indicators2, BREAK_EVEN_TRIGGER_RISK_MULTIPLIER, buildTrendlineSignalSeed, isOpenPosition, getFavorableMovePct, getPositionStopLossPrice, getPositionRiskPct, isBreakEvenStopAlreadyApplied, isFailedBreakout, createTrendLineCore;
9313
+ var import_math3, import_indicators2, buildTrendlineSignalSeed, isOpenPosition, isFailedBreakout, createTrendLineCore;
8871
9314
  var init_core2 = __esm({
8872
9315
  "src/TrendLine/core.ts"() {
8873
9316
  "use strict";
@@ -8877,7 +9320,6 @@ var init_core2 = __esm({
8877
9320
  init_figures2();
8878
9321
  init_guardrails2();
8879
9322
  init_risk();
8880
- BREAK_EVEN_TRIGGER_RISK_MULTIPLIER = 0.5;
8881
9323
  buildTrendlineSignalSeed = ({
8882
9324
  direction,
8883
9325
  currentPrice,
@@ -8901,51 +9343,6 @@ var init_core2 = __esm({
8901
9343
  isOpenPosition = (position) => Boolean(
8902
9344
  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")
8903
9345
  );
8904
- getFavorableMovePct = ({
8905
- direction,
8906
- entryPrice,
8907
- currentPrice
8908
- }) => {
8909
- if (!Number.isFinite(entryPrice) || !Number.isFinite(currentPrice) || entryPrice <= 0) {
8910
- return null;
8911
- }
8912
- return direction === "LONG" ? (currentPrice - entryPrice) / entryPrice * 100 : (entryPrice - currentPrice) / entryPrice * 100;
8913
- };
8914
- getPositionStopLossPrice = (position) => {
8915
- if (!position || typeof position !== "object") {
8916
- return null;
8917
- }
8918
- const slPrice = Number(
8919
- position.slPrice ?? Number.NaN
8920
- );
8921
- if (Number.isFinite(slPrice)) {
8922
- return slPrice;
8923
- }
8924
- const signalStopLossPrice = Number(
8925
- position.signal?.prices?.stopLossPrice ?? Number.NaN
8926
- );
8927
- return Number.isFinite(signalStopLossPrice) ? signalStopLossPrice : null;
8928
- };
8929
- getPositionRiskPct = ({
8930
- direction,
8931
- entryPrice,
8932
- stopLossPrice
8933
- }) => {
8934
- if (stopLossPrice == null || !Number.isFinite(entryPrice) || !Number.isFinite(stopLossPrice) || entryPrice <= 0) {
8935
- return null;
8936
- }
8937
- return direction === "LONG" ? (entryPrice - stopLossPrice) / entryPrice * 100 : (stopLossPrice - entryPrice) / entryPrice * 100;
8938
- };
8939
- isBreakEvenStopAlreadyApplied = ({
8940
- direction,
8941
- entryPrice,
8942
- stopLossPrice
8943
- }) => {
8944
- if (stopLossPrice == null || !Number.isFinite(entryPrice) || !Number.isFinite(stopLossPrice)) {
8945
- return false;
8946
- }
8947
- return direction === "LONG" ? stopLossPrice >= entryPrice : stopLossPrice <= entryPrice;
8948
- };
8949
9346
  isFailedBreakout = ({
8950
9347
  direction,
8951
9348
  priceVsLinePct
@@ -8955,8 +9352,8 @@ var init_core2 = __esm({
8955
9352
  }
8956
9353
  return direction === "LONG" ? priceVsLinePct < 0 : priceVsLinePct > 0;
8957
9354
  };
8958
- createTrendLineCore = async ({ config: config7, data: cachedData, strategyApi, indicatorsState }) => {
8959
- const { TRENDLINE, FEE_PERCENT, MAX_LOSS_VALUE, HIGHS, LOWS } = config7;
9355
+ createTrendLineCore = async ({ config: config8, data: cachedData, strategyApi, indicatorsState }) => {
9356
+ const { TRENDLINE, FEE_PERCENT, MAX_LOSS_VALUE, HIGHS, LOWS } = config8;
8960
9357
  const lastTradeController = strategyApi.createLastTradeController();
8961
9358
  const trendlineOptions = {
8962
9359
  bestLines: 1,
@@ -8980,17 +9377,6 @@ var init_core2 = __esm({
8980
9377
  const { currentPrice: currentPrice2 } = await strategyApi.getMarketData();
8981
9378
  const activeLine = currentPosition.direction === "LONG" ? highsTrendlines[0] : lowsTrendlines[0];
8982
9379
  const activeModeConfig = currentPosition.direction === "LONG" ? HIGHS : LOWS;
8983
- const currentStopLossPrice = getPositionStopLossPrice(currentPosition);
8984
- const favorableMovePct = getFavorableMovePct({
8985
- direction: currentPosition.direction,
8986
- entryPrice: currentPosition.price,
8987
- currentPrice: currentPrice2
8988
- });
8989
- const currentPositionRiskPct = getPositionRiskPct({
8990
- direction: currentPosition.direction,
8991
- entryPrice: currentPosition.price,
8992
- stopLossPrice: currentStopLossPrice
8993
- });
8994
9380
  if (activeLine) {
8995
9381
  const indicators2 = indicatorsState.snapshot();
8996
9382
  const manageSignalSeed = buildTrendlineSignalSeed({
@@ -9010,19 +9396,6 @@ var init_core2 = __esm({
9010
9396
  });
9011
9397
  }
9012
9398
  }
9013
- if (!isBreakEvenStopAlreadyApplied({
9014
- direction: currentPosition.direction,
9015
- entryPrice: currentPosition.price,
9016
- stopLossPrice: currentStopLossPrice
9017
- }) && favorableMovePct != null && favorableMovePct >= (currentPositionRiskPct ?? activeModeConfig.SL) * BREAK_EVEN_TRIGGER_RISK_MULTIPLIER) {
9018
- return strategyApi.protect({
9019
- code: "TRENDLINE_MOVE_STOP_TO_BREAK_EVEN",
9020
- protectPlan: {
9021
- direction: currentPosition.direction,
9022
- stopLossPrice: currentPosition.price
9023
- }
9024
- });
9025
- }
9026
9399
  return strategyApi.skip("POSITION_EXISTS");
9027
9400
  }
9028
9401
  const bestLine = lowsTrendlines.length > 0 ? lowsTrendlines[0] : highsTrendlines[0];
@@ -9115,17 +9488,17 @@ var strategy_exports2 = {};
9115
9488
  __export(strategy_exports2, {
9116
9489
  TrendlineStrategyCreator: () => TrendlineStrategyCreator
9117
9490
  });
9118
- var import_strategies15, TrendlineStrategyCreator;
9491
+ var import_strategies13, TrendlineStrategyCreator;
9119
9492
  var init_strategy2 = __esm({
9120
9493
  "src/TrendLine/strategy.ts"() {
9121
9494
  "use strict";
9122
- import_strategies15 = require("@tradejs/node/strategies");
9123
- init_config();
9495
+ import_strategies13 = require("@tradejs/node/strategies");
9496
+ init_config6();
9124
9497
  init_core2();
9125
- init_manifest5();
9126
- TrendlineStrategyCreator = (0, import_strategies15.createStrategyRuntime)({
9498
+ init_manifest6();
9499
+ TrendlineStrategyCreator = (0, import_strategies13.createStrategyRuntime)({
9127
9500
  strategyName: "TrendLine",
9128
- defaults: config,
9501
+ defaults: config6,
9129
9502
  createCore: createTrendLineCore,
9130
9503
  manifest: trendLineManifest,
9131
9504
  strategyDirectory: __dirname
@@ -9133,12 +9506,406 @@ var init_strategy2 = __esm({
9133
9506
  }
9134
9507
  });
9135
9508
 
9509
+ // src/TrendShift/engine.ts
9510
+ var clampPositive, calculateTrueRange, updateAtrState, trimSeries, asDirection, buildTrendShiftSignalContext, getConfigNumbers, createTrendShiftEngine;
9511
+ var init_engine = __esm({
9512
+ "src/TrendShift/engine.ts"() {
9513
+ "use strict";
9514
+ clampPositive = (value, fallback) => Number.isFinite(value) && value > 0 ? value : fallback;
9515
+ calculateTrueRange = (candle, prevClose) => {
9516
+ const high = Number(candle.high);
9517
+ const low = Number(candle.low);
9518
+ const close = Number(candle.close);
9519
+ if (!Number.isFinite(high) || !Number.isFinite(low) || !Number.isFinite(close)) {
9520
+ return 0;
9521
+ }
9522
+ if (prevClose == null || !Number.isFinite(prevClose)) {
9523
+ return Math.max(high - low, 0);
9524
+ }
9525
+ return Math.max(
9526
+ high - low,
9527
+ Math.abs(high - prevClose),
9528
+ Math.abs(low - prevClose)
9529
+ );
9530
+ };
9531
+ updateAtrState = ({
9532
+ atrState,
9533
+ tr,
9534
+ period
9535
+ }) => {
9536
+ const safeTr = Number.isFinite(tr) ? Math.max(tr, 0) : 0;
9537
+ const safePeriod = Math.max(1, Math.floor(period));
9538
+ if (atrState.value == null) {
9539
+ return {
9540
+ value: safeTr,
9541
+ count: 1
9542
+ };
9543
+ }
9544
+ if (atrState.count < safePeriod) {
9545
+ const nextCount = atrState.count + 1;
9546
+ return {
9547
+ value: (atrState.value * atrState.count + safeTr) / nextCount,
9548
+ count: nextCount
9549
+ };
9550
+ }
9551
+ return {
9552
+ value: (atrState.value * (safePeriod - 1) + safeTr) / safePeriod,
9553
+ count: atrState.count + 1
9554
+ };
9555
+ };
9556
+ trimSeries = (series, maxPoints) => series.length <= maxPoints ? series : series.slice(series.length - maxPoints);
9557
+ asDirection = (trendState) => trendState === 1 ? "LONG" : "SHORT";
9558
+ buildTrendShiftSignalContext = ({
9559
+ snapshot,
9560
+ indicators
9561
+ }) => {
9562
+ const maFastSeries = Array.isArray(indicators?.maFast) ? indicators?.maFast : [];
9563
+ const maSlowSeries = Array.isArray(indicators?.maSlow) ? indicators?.maSlow : [];
9564
+ const maFast = maFastSeries[maFastSeries.length - 1];
9565
+ const maSlow = maSlowSeries[maSlowSeries.length - 1];
9566
+ const coinBias = Number.isFinite(maFast) && Number.isFinite(maSlow) ? maFast > maSlow ? "bullish" : maFast < maSlow ? "bearish" : "neutral" : "unknown";
9567
+ const signalDirection = snapshot.bullFlip ? "LONG" : snapshot.bearFlip ? "SHORT" : asDirection(snapshot.trendState);
9568
+ const coinBiasAligned = coinBias === "unknown" || coinBias === "neutral" ? null : signalDirection === "LONG" ? coinBias === "bullish" : coinBias === "bearish";
9569
+ return {
9570
+ signalDirection,
9571
+ trendState: snapshot.trendState,
9572
+ rawTrend: snapshot.rawTrend,
9573
+ confirmedFlip: snapshot.bullFlip || snapshot.bearFlip,
9574
+ bullFlip: snapshot.bullFlip,
9575
+ bearFlip: snapshot.bearFlip,
9576
+ bullFlipRaw: snapshot.bullFlipRaw,
9577
+ bearFlipRaw: snapshot.bearFlipRaw,
9578
+ flipDistanceOk: snapshot.flipDistanceOk,
9579
+ closeVsAvgPct: snapshot.closeVsAvgPct,
9580
+ bandWidthPct: snapshot.bandWidthPct,
9581
+ avgSlopePct: snapshot.avgSlopePct,
9582
+ distanceAtrRatio: snapshot.distanceAtrRatio,
9583
+ adaptiveAtr: snapshot.adaptiveAtr,
9584
+ avg: snapshot.avg,
9585
+ upper: snapshot.upper,
9586
+ lower: snapshot.lower,
9587
+ hold: snapshot.hold,
9588
+ currentPrice: snapshot.close,
9589
+ coinMaFast: Number.isFinite(maFast) ? maFast : null,
9590
+ coinMaSlow: Number.isFinite(maSlow) ? maSlow : null,
9591
+ coinBias,
9592
+ coinBiasAligned
9593
+ };
9594
+ };
9595
+ getConfigNumbers = (config8) => ({
9596
+ mult: clampPositive(config8.TRENDSHIFT_MULTIPLICATIVE_FACTOR, 4),
9597
+ slope: clampPositive(config8.TRENDSHIFT_SLOPE, 12),
9598
+ atrLength: Math.max(1, Math.floor(config8.TRENDSHIFT_ATR_LENGTH ?? 150)),
9599
+ widthPct: clampPositive(config8.TRENDSHIFT_WIDTH_PCT, 75) / 100,
9600
+ minFlipAtr: Math.max(0, Number(config8.TRENDSHIFT_MIN_FLIP_DISTANCE_ATR ?? 0)),
9601
+ confirmFlipWithClose: Boolean(config8.TRENDSHIFT_CONFIRM_FLIP_WITH_CLOSE),
9602
+ maxFigurePoints: Math.max(
9603
+ 20,
9604
+ Math.floor(config8.TRENDSHIFT_MAX_FIGURE_POINTS ?? 180)
9605
+ )
9606
+ });
9607
+ createTrendShiftEngine = ({
9608
+ config: config8,
9609
+ initialCandles = []
9610
+ }) => {
9611
+ const {
9612
+ mult,
9613
+ slope,
9614
+ atrLength,
9615
+ widthPct,
9616
+ minFlipAtr,
9617
+ confirmFlipWithClose,
9618
+ maxFigurePoints
9619
+ } = getConfigNumbers(config8);
9620
+ const state = {
9621
+ atrState: {
9622
+ value: null,
9623
+ count: 0
9624
+ },
9625
+ avg: null,
9626
+ hold: null,
9627
+ rawTrend: 1,
9628
+ trendState: 1,
9629
+ prevClose: null,
9630
+ series: {
9631
+ avg: [],
9632
+ upper: [],
9633
+ lower: []
9634
+ },
9635
+ snapshot: null
9636
+ };
9637
+ const apply = (candle) => {
9638
+ const close = Number(candle.close);
9639
+ if (!Number.isFinite(close)) {
9640
+ return {
9641
+ snapshot: state.snapshot,
9642
+ series: state.series
9643
+ };
9644
+ }
9645
+ const tr = calculateTrueRange(candle, state.prevClose);
9646
+ state.atrState = updateAtrState({
9647
+ atrState: state.atrState,
9648
+ tr,
9649
+ period: atrLength
9650
+ });
9651
+ const adaptiveAtr = Math.max((state.atrState.value ?? tr) * mult, 1e-9);
9652
+ const prevAvg = state.avg ?? close;
9653
+ const prevHold = state.hold ?? adaptiveAtr;
9654
+ const prevRawTrend = state.rawTrend;
9655
+ const prevTrendState = state.trendState;
9656
+ const avg = Math.abs(close - prevAvg) > adaptiveAtr ? (close + prevAvg) / 2 : prevAvg + prevRawTrend * (prevHold / mult / Math.max(slope, 1));
9657
+ const rawTrend = avg > prevAvg ? 1 : avg < prevAvg ? -1 : prevRawTrend;
9658
+ const hold = rawTrend !== prevRawTrend ? adaptiveAtr : prevHold + (adaptiveAtr - prevHold) / Math.max(slope, 1);
9659
+ const upper = avg + widthPct * hold;
9660
+ const lower = avg - widthPct * hold;
9661
+ const closeDistance = Math.abs(close - avg);
9662
+ const flipDistanceOk = closeDistance >= adaptiveAtr * minFlipAtr;
9663
+ const bullFlipRaw = rawTrend === 1 && prevTrendState !== 1;
9664
+ const bearFlipRaw = rawTrend === -1 && prevTrendState !== -1;
9665
+ const bullFlip = bullFlipRaw && flipDistanceOk && (!confirmFlipWithClose || close > avg);
9666
+ const bearFlip = bearFlipRaw && flipDistanceOk && (!confirmFlipWithClose || close < avg);
9667
+ const trendState = bullFlip ? 1 : bearFlip ? -1 : prevTrendState;
9668
+ const closeVsAvgPct = avg !== 0 ? (close - avg) / avg * 100 : 0;
9669
+ const bandWidthPct = avg !== 0 ? (upper - lower) / avg * 100 : 0;
9670
+ const avgSlopePct = prevAvg !== 0 ? (avg - prevAvg) / prevAvg * 100 : 0;
9671
+ const distanceAtrRatio = adaptiveAtr > 0 ? closeDistance / adaptiveAtr : 0;
9672
+ const labelOffset = hold * 0.2;
9673
+ state.avg = avg;
9674
+ state.hold = hold;
9675
+ state.rawTrend = rawTrend;
9676
+ state.trendState = trendState;
9677
+ state.prevClose = close;
9678
+ state.series.avg = trimSeries(
9679
+ [...state.series.avg, { timestamp: candle.timestamp, value: avg }],
9680
+ maxFigurePoints
9681
+ );
9682
+ state.series.upper = trimSeries(
9683
+ [...state.series.upper, { timestamp: candle.timestamp, value: upper }],
9684
+ maxFigurePoints
9685
+ );
9686
+ state.series.lower = trimSeries(
9687
+ [...state.series.lower, { timestamp: candle.timestamp, value: lower }],
9688
+ maxFigurePoints
9689
+ );
9690
+ state.snapshot = {
9691
+ avg,
9692
+ upper,
9693
+ lower,
9694
+ hold,
9695
+ adaptiveAtr,
9696
+ rawTrend,
9697
+ trendState,
9698
+ bullFlipRaw,
9699
+ bearFlipRaw,
9700
+ bullFlip,
9701
+ bearFlip,
9702
+ flipDistanceOk,
9703
+ closeVsAvgPct,
9704
+ bandWidthPct,
9705
+ avgSlopePct,
9706
+ distanceAtrRatio,
9707
+ labelOffset,
9708
+ timestamp: candle.timestamp,
9709
+ close
9710
+ };
9711
+ return {
9712
+ snapshot: state.snapshot,
9713
+ series: state.series
9714
+ };
9715
+ };
9716
+ for (const candle of initialCandles) {
9717
+ apply(candle);
9718
+ }
9719
+ return {
9720
+ next: apply,
9721
+ getState: () => ({
9722
+ snapshot: state.snapshot,
9723
+ series: state.series
9724
+ })
9725
+ };
9726
+ };
9727
+ }
9728
+ });
9729
+
9730
+ // src/TrendShift/figures.ts
9731
+ var buildTrendShiftFigures;
9732
+ var init_figures3 = __esm({
9733
+ "src/TrendShift/figures.ts"() {
9734
+ "use strict";
9735
+ buildTrendShiftFigures = ({
9736
+ series,
9737
+ direction,
9738
+ entryTimestamp,
9739
+ entryPrice
9740
+ }) => {
9741
+ const trendColor = direction === "LONG" ? "#00b894" : "#d63031";
9742
+ const lines = [
9743
+ {
9744
+ id: "trendshift-upper",
9745
+ kind: "trendshift_upper",
9746
+ points: series.upper,
9747
+ color: trendColor,
9748
+ width: 1,
9749
+ style: "dashed"
9750
+ },
9751
+ {
9752
+ id: "trendshift-avg",
9753
+ kind: "trendshift_avg",
9754
+ points: series.avg,
9755
+ color: trendColor,
9756
+ width: 2,
9757
+ style: "solid"
9758
+ },
9759
+ {
9760
+ id: "trendshift-lower",
9761
+ kind: "trendshift_lower",
9762
+ points: series.lower,
9763
+ color: trendColor,
9764
+ width: 1,
9765
+ style: "dashed"
9766
+ }
9767
+ ].filter((line) => Array.isArray(line.points) && line.points.length > 0);
9768
+ const points = [
9769
+ {
9770
+ id: `trendshift-entry-${entryTimestamp}`,
9771
+ kind: "trendshift_entry",
9772
+ points: [{ timestamp: entryTimestamp, value: entryPrice }],
9773
+ color: trendColor,
9774
+ radius: 4
9775
+ }
9776
+ ];
9777
+ return { lines, points };
9778
+ };
9779
+ }
9780
+ });
9781
+
9782
+ // src/TrendShift/core.ts
9783
+ var import_math4, isOpenPosition2, createTrendShiftCore;
9784
+ var init_core3 = __esm({
9785
+ "src/TrendShift/core.ts"() {
9786
+ "use strict";
9787
+ import_math4 = require("@tradejs/core/math");
9788
+ init_engine();
9789
+ init_figures3();
9790
+ isOpenPosition2 = (position) => Boolean(
9791
+ 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")
9792
+ );
9793
+ createTrendShiftCore = async ({ config: config8, data: initialData, strategyApi, indicatorsState }) => {
9794
+ const engine = createTrendShiftEngine({
9795
+ config: config8,
9796
+ initialCandles: initialData
9797
+ });
9798
+ const lastTradeController = strategyApi.createLastTradeController();
9799
+ return async (candle) => {
9800
+ indicatorsState.onBar();
9801
+ const runtimeState = engine.next(candle);
9802
+ const snapshot = runtimeState.snapshot;
9803
+ if (!snapshot) {
9804
+ return strategyApi.skip("WAIT_DATA");
9805
+ }
9806
+ const position = await strategyApi.getCurrentPosition();
9807
+ if (isOpenPosition2(position)) {
9808
+ const oppositeBullExit = position.direction === "SHORT" && snapshot.bullFlip;
9809
+ const oppositeBearExit = position.direction === "LONG" && snapshot.bearFlip;
9810
+ if (Boolean(config8.TRENDSHIFT_EXIT_ON_OPPOSITE_FLIP) && (oppositeBullExit || oppositeBearExit)) {
9811
+ return strategyApi.exit({
9812
+ code: "TRENDSHIFT_OPPOSITE_FLIP_EXIT",
9813
+ direction: position.direction
9814
+ });
9815
+ }
9816
+ return strategyApi.skip("POSITION_EXISTS");
9817
+ }
9818
+ if (lastTradeController.isInCooldown(candle.timestamp)) {
9819
+ return strategyApi.skip("DEV_TRADE_COOLDOWN");
9820
+ }
9821
+ const isBullEntry = snapshot.bullFlip;
9822
+ const isBearEntry = snapshot.bearFlip;
9823
+ if (!isBullEntry && !isBearEntry) {
9824
+ return strategyApi.skip("NO_SIGNAL");
9825
+ }
9826
+ const modeConfig = isBullEntry ? config8.LONG : config8.SHORT;
9827
+ if (!modeConfig.enable) {
9828
+ return strategyApi.skip("STRATEGY_DISABLED");
9829
+ }
9830
+ const indicators = indicatorsState.snapshot();
9831
+ const { timestamp, currentPrice } = await strategyApi.getMarketData();
9832
+ const direction = modeConfig.direction;
9833
+ const signalContext = buildTrendShiftSignalContext({
9834
+ snapshot: {
9835
+ ...snapshot,
9836
+ close: currentPrice
9837
+ },
9838
+ indicators
9839
+ });
9840
+ const { stopLossPrice, takeProfitPrice, riskRatio, qty } = strategyApi.getDirectionalTpSlPrices({
9841
+ price: currentPrice,
9842
+ direction,
9843
+ takeProfitDelta: modeConfig.TP,
9844
+ stopLossDelta: modeConfig.SL,
9845
+ unit: "percent",
9846
+ maxLossValue: config8.MAX_LOSS_VALUE,
9847
+ feePercent: Number(config8.FEE_PERCENT ?? 0)
9848
+ });
9849
+ if (!qty || !Number.isFinite(qty) || qty <= 0) {
9850
+ return strategyApi.skip("INVALID_QTY");
9851
+ }
9852
+ if (riskRatio <= modeConfig.minRiskRatio) {
9853
+ return strategyApi.skip(`RISK_RATIO:${(0, import_math4.round)(riskRatio)}`);
9854
+ }
9855
+ lastTradeController.markTrade(timestamp);
9856
+ return strategyApi.entry({
9857
+ code: isBullEntry ? "TRENDSHIFT_BULLISH_FLIP" : "TRENDSHIFT_BEARISH_FLIP",
9858
+ direction,
9859
+ indicators,
9860
+ additionalIndicators: {
9861
+ trendShiftContext: signalContext
9862
+ },
9863
+ figures: buildTrendShiftFigures({
9864
+ series: runtimeState.series,
9865
+ direction,
9866
+ entryTimestamp: timestamp,
9867
+ entryPrice: currentPrice
9868
+ }),
9869
+ orderPlan: {
9870
+ qty,
9871
+ stopLossPrice,
9872
+ takeProfits: [{ rate: 1, price: takeProfitPrice }]
9873
+ }
9874
+ });
9875
+ };
9876
+ };
9877
+ }
9878
+ });
9879
+
9880
+ // src/TrendShift/strategy.ts
9881
+ var strategy_exports3 = {};
9882
+ __export(strategy_exports3, {
9883
+ TrendShiftStrategyCreator: () => TrendShiftStrategyCreator
9884
+ });
9885
+ var import_strategies14, TrendShiftStrategyCreator;
9886
+ var init_strategy3 = __esm({
9887
+ "src/TrendShift/strategy.ts"() {
9888
+ "use strict";
9889
+ import_strategies14 = require("@tradejs/node/strategies");
9890
+ init_config5();
9891
+ init_core3();
9892
+ init_manifest5();
9893
+ TrendShiftStrategyCreator = (0, import_strategies14.createStrategyRuntime)({
9894
+ strategyName: "TrendShift",
9895
+ defaults: config5,
9896
+ createCore: createTrendShiftCore,
9897
+ manifest: trendShiftManifest,
9898
+ strategyDirectory: __dirname
9899
+ });
9900
+ }
9901
+ });
9902
+
9136
9903
  // src/ReverseTrendLine/filters.ts
9137
- var import_math4, MAX_CANDLE_VOLATILITY2, filterByVeryVolatility2;
9904
+ var import_math5, MAX_CANDLE_VOLATILITY2, filterByVeryVolatility2;
9138
9905
  var init_filters2 = __esm({
9139
9906
  "src/ReverseTrendLine/filters.ts"() {
9140
9907
  "use strict";
9141
- import_math4 = require("@tradejs/core/math");
9908
+ import_math5 = require("@tradejs/core/math");
9142
9909
  MAX_CANDLE_VOLATILITY2 = 0.025;
9143
9910
  filterByVeryVolatility2 = (data) => {
9144
9911
  const lastCandle = data[data.length - 1];
@@ -9146,7 +9913,7 @@ var init_filters2 = __esm({
9146
9913
  if (!lastCandle || !prevCandle) {
9147
9914
  return false;
9148
9915
  }
9149
- const isVeryVolatility = (0, import_math4.diffRel)(lastCandle.low, lastCandle.high) > MAX_CANDLE_VOLATILITY2 || (0, import_math4.diffRel)(prevCandle.low, prevCandle.high) > MAX_CANDLE_VOLATILITY2;
9916
+ const isVeryVolatility = (0, import_math5.diffRel)(lastCandle.low, lastCandle.high) > MAX_CANDLE_VOLATILITY2 || (0, import_math5.diffRel)(prevCandle.low, prevCandle.high) > MAX_CANDLE_VOLATILITY2;
9150
9917
  return !isVeryVolatility;
9151
9918
  };
9152
9919
  }
@@ -9154,7 +9921,7 @@ var init_filters2 = __esm({
9154
9921
 
9155
9922
  // src/ReverseTrendLine/figures.ts
9156
9923
  var buildReverseTrendLineFigures;
9157
- var init_figures3 = __esm({
9924
+ var init_figures4 = __esm({
9158
9925
  "src/ReverseTrendLine/figures.ts"() {
9159
9926
  "use strict";
9160
9927
  buildReverseTrendLineFigures = (bestLine) => ({
@@ -9186,11 +9953,11 @@ var init_figures3 = __esm({
9186
9953
  });
9187
9954
 
9188
9955
  // src/ReverseTrendLine/risk.ts
9189
- var import_math5, MIN_STOP_BUFFER_PCT2, LINE_BUFFER_ATR_FACTOR2, LINE_BUFFER_BASE_SL_FACTOR2, ATR_STOP_FLOOR_FACTOR2, MIN_STOP_LOSS_FACTOR2, MAX_STOP_LOSS_FACTOR2, clampNumber2, getTimingStopFactor2, getTimingTargetRiskRatio2, buildReverseTrendlineRiskPlan;
9956
+ var import_math6, MIN_STOP_BUFFER_PCT2, LINE_BUFFER_ATR_FACTOR2, LINE_BUFFER_BASE_SL_FACTOR2, ATR_STOP_FLOOR_FACTOR2, MIN_STOP_LOSS_FACTOR2, MAX_STOP_LOSS_FACTOR2, clampNumber2, getTimingStopFactor2, getTimingTargetRiskRatio2, buildReverseTrendlineRiskPlan;
9190
9957
  var init_risk2 = __esm({
9191
9958
  "src/ReverseTrendLine/risk.ts"() {
9192
9959
  "use strict";
9193
- import_math5 = require("@tradejs/core/math");
9960
+ import_math6 = require("@tradejs/core/math");
9194
9961
  MIN_STOP_BUFFER_PCT2 = 0.1;
9195
9962
  LINE_BUFFER_ATR_FACTOR2 = 0.25;
9196
9963
  LINE_BUFFER_BASE_SL_FACTOR2 = 0.1;
@@ -9279,26 +10046,25 @@ var init_risk2 = __esm({
9279
10046
  maxTargetRiskRatio
9280
10047
  );
9281
10048
  return {
9282
- stopLossDelta: (0, import_math5.round)(stopLossDelta, 3),
9283
- targetRiskRatio: (0, import_math5.round)(targetRiskRatio, 2),
9284
- takeProfitDelta: (0, import_math5.round)(stopLossDelta * targetRiskRatio, 3)
10049
+ stopLossDelta: (0, import_math6.round)(stopLossDelta, 3),
10050
+ targetRiskRatio: (0, import_math6.round)(targetRiskRatio, 2),
10051
+ takeProfitDelta: (0, import_math6.round)(stopLossDelta * targetRiskRatio, 3)
9285
10052
  };
9286
10053
  };
9287
10054
  }
9288
10055
  });
9289
10056
 
9290
10057
  // src/ReverseTrendLine/core.ts
9291
- var import_math6, import_indicators3, BREAK_EVEN_TRIGGER_RISK_MULTIPLIER2, buildReverseTrendlineSignalSeed, isOpenPosition2, getFavorableMovePct2, getPositionStopLossPrice2, getPositionRiskPct2, isBreakEvenStopAlreadyApplied2, getLinePriceAtNow, buildReverseTrendlineCandidateContext, pickBestCandidateLine, createReverseTrendLineCore;
9292
- var init_core3 = __esm({
10058
+ var import_math7, import_indicators3, buildReverseTrendlineSignalSeed, isOpenPosition3, getLinePriceAtNow, buildReverseTrendlineCandidateContext, pickBestCandidateLine, createReverseTrendLineCore;
10059
+ var init_core4 = __esm({
9293
10060
  "src/ReverseTrendLine/core.ts"() {
9294
10061
  "use strict";
9295
- import_math6 = require("@tradejs/core/math");
10062
+ import_math7 = require("@tradejs/core/math");
9296
10063
  import_indicators3 = require("@tradejs/core/indicators");
9297
10064
  init_filters2();
9298
- init_figures3();
10065
+ init_figures4();
9299
10066
  init_guardrails();
9300
10067
  init_risk2();
9301
- BREAK_EVEN_TRIGGER_RISK_MULTIPLIER2 = 0.5;
9302
10068
  buildReverseTrendlineSignalSeed = ({
9303
10069
  direction,
9304
10070
  currentPrice,
@@ -9321,54 +10087,9 @@ var init_core3 = __esm({
9321
10087
  trendLine: bestLine
9322
10088
  }
9323
10089
  });
9324
- isOpenPosition2 = (position) => Boolean(
10090
+ isOpenPosition3 = (position) => Boolean(
9325
10091
  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")
9326
10092
  );
9327
- getFavorableMovePct2 = ({
9328
- direction,
9329
- entryPrice,
9330
- currentPrice
9331
- }) => {
9332
- if (!Number.isFinite(entryPrice) || !Number.isFinite(currentPrice) || entryPrice <= 0) {
9333
- return null;
9334
- }
9335
- return direction === "LONG" ? (currentPrice - entryPrice) / entryPrice * 100 : (entryPrice - currentPrice) / entryPrice * 100;
9336
- };
9337
- getPositionStopLossPrice2 = (position) => {
9338
- if (!position || typeof position !== "object") {
9339
- return null;
9340
- }
9341
- const slPrice = Number(
9342
- position.slPrice ?? Number.NaN
9343
- );
9344
- if (Number.isFinite(slPrice)) {
9345
- return slPrice;
9346
- }
9347
- const signalStopLossPrice = Number(
9348
- position.signal?.prices?.stopLossPrice ?? Number.NaN
9349
- );
9350
- return Number.isFinite(signalStopLossPrice) ? signalStopLossPrice : null;
9351
- };
9352
- getPositionRiskPct2 = ({
9353
- direction,
9354
- entryPrice,
9355
- stopLossPrice
9356
- }) => {
9357
- if (stopLossPrice == null || !Number.isFinite(entryPrice) || !Number.isFinite(stopLossPrice) || entryPrice <= 0) {
9358
- return null;
9359
- }
9360
- return direction === "LONG" ? (entryPrice - stopLossPrice) / entryPrice * 100 : (stopLossPrice - entryPrice) / entryPrice * 100;
9361
- };
9362
- isBreakEvenStopAlreadyApplied2 = ({
9363
- direction,
9364
- entryPrice,
9365
- stopLossPrice
9366
- }) => {
9367
- if (stopLossPrice == null || !Number.isFinite(entryPrice) || !Number.isFinite(stopLossPrice)) {
9368
- return false;
9369
- }
9370
- return direction === "LONG" ? stopLossPrice >= entryPrice : stopLossPrice <= entryPrice;
9371
- };
9372
10093
  getLinePriceAtNow = (line, timestamp) => {
9373
10094
  if (!line || !Array.isArray(line.points) || line.points.length === 0) {
9374
10095
  return null;
@@ -9428,8 +10149,8 @@ var init_core3 = __esm({
9428
10149
  });
9429
10150
  return ranked[0] ?? null;
9430
10151
  };
9431
- createReverseTrendLineCore = async ({ config: config7, data: cachedData, strategyApi, indicatorsState }) => {
9432
- const { TRENDLINE, FEE_PERCENT, MAX_LOSS_VALUE, HIGHS, LOWS } = config7;
10152
+ createReverseTrendLineCore = async ({ config: config8, data: cachedData, strategyApi, indicatorsState }) => {
10153
+ const { TRENDLINE, FEE_PERCENT, MAX_LOSS_VALUE, HIGHS, LOWS } = config8;
9433
10154
  const lastTradeController = strategyApi.createLastTradeController();
9434
10155
  const trendlineOptions = {
9435
10156
  bestLines: 1,
@@ -9449,9 +10170,8 @@ var init_core3 = __esm({
9449
10170
  const highsTrendlines = getHighsTrendlines.next(candle);
9450
10171
  indicatorsState.onBar();
9451
10172
  const currentPosition = await strategyApi.getCurrentPosition();
9452
- if (isOpenPosition2(currentPosition)) {
10173
+ if (isOpenPosition3(currentPosition)) {
9453
10174
  const activeLine = currentPosition.direction === "LONG" ? lowsTrendlines[0] : highsTrendlines[0];
9454
- const activeModeConfig = currentPosition.direction === "LONG" ? LOWS : HIGHS;
9455
10175
  const activeLinePrice = getLinePriceAtNow(
9456
10176
  activeLine ?? null,
9457
10177
  candle.timestamp
@@ -9464,30 +10184,6 @@ var init_core3 = __esm({
9464
10184
  direction: currentPosition.direction
9465
10185
  });
9466
10186
  }
9467
- const favorableMovePct = getFavorableMovePct2({
9468
- direction: currentPosition.direction,
9469
- entryPrice: currentPosition.price,
9470
- currentPrice: candle.close
9471
- });
9472
- const currentStopLossPrice = getPositionStopLossPrice2(currentPosition);
9473
- const currentPositionRiskPct = getPositionRiskPct2({
9474
- direction: currentPosition.direction,
9475
- entryPrice: currentPosition.price,
9476
- stopLossPrice: currentStopLossPrice
9477
- });
9478
- if (!isBreakEvenStopAlreadyApplied2({
9479
- direction: currentPosition.direction,
9480
- entryPrice: currentPosition.price,
9481
- stopLossPrice: currentStopLossPrice
9482
- }) && favorableMovePct != null && favorableMovePct >= (currentPositionRiskPct ?? activeModeConfig.SL) * BREAK_EVEN_TRIGGER_RISK_MULTIPLIER2) {
9483
- return strategyApi.protect({
9484
- code: "REVERSE_TRENDLINE_MOVE_STOP_TO_BREAK_EVEN",
9485
- protectPlan: {
9486
- direction: currentPosition.direction,
9487
- stopLossPrice: currentPosition.price
9488
- }
9489
- });
9490
- }
9491
10187
  return strategyApi.skip("POSITION_EXISTS");
9492
10188
  }
9493
10189
  if (lastTradeController.isInCooldown(candle.timestamp)) {
@@ -9571,7 +10267,7 @@ var init_core3 = __esm({
9571
10267
  return strategyApi.skip("INVALID_QTY");
9572
10268
  }
9573
10269
  if (riskRatio <= minRiskRatio) {
9574
- return strategyApi.skip(`RISK_RATIO:${(0, import_math6.round)(riskRatio)}`);
10270
+ return strategyApi.skip(`RISK_RATIO:${(0, import_math7.round)(riskRatio)}`);
9575
10271
  }
9576
10272
  lastTradeController.markTrade(timestamp);
9577
10273
  return strategyApi.entry({
@@ -9607,21 +10303,21 @@ var init_core3 = __esm({
9607
10303
  });
9608
10304
 
9609
10305
  // src/ReverseTrendLine/strategy.ts
9610
- var strategy_exports3 = {};
9611
- __export(strategy_exports3, {
10306
+ var strategy_exports4 = {};
10307
+ __export(strategy_exports4, {
9612
10308
  ReverseTrendLineStrategyCreator: () => ReverseTrendLineStrategyCreator
9613
10309
  });
9614
- var import_strategies16, ReverseTrendLineStrategyCreator;
9615
- var init_strategy3 = __esm({
10310
+ var import_strategies15, ReverseTrendLineStrategyCreator;
10311
+ var init_strategy4 = __esm({
9616
10312
  "src/ReverseTrendLine/strategy.ts"() {
9617
10313
  "use strict";
9618
- import_strategies16 = require("@tradejs/node/strategies");
9619
- init_config2();
9620
- init_core3();
10314
+ import_strategies15 = require("@tradejs/node/strategies");
10315
+ init_config4();
10316
+ init_core4();
9621
10317
  init_manifest4();
9622
- ReverseTrendLineStrategyCreator = (0, import_strategies16.createStrategyRuntime)({
10318
+ ReverseTrendLineStrategyCreator = (0, import_strategies15.createStrategyRuntime)({
9623
10319
  strategyName: "ReverseTrendLine",
9624
- defaults: config2,
10320
+ defaults: config4,
9625
10321
  createCore: createReverseTrendLineCore,
9626
10322
  manifest: reverseTrendLineManifest,
9627
10323
  strategyDirectory: __dirname
@@ -9629,47 +10325,9 @@ var init_strategy3 = __esm({
9629
10325
  }
9630
10326
  });
9631
10327
 
9632
- // src/MaStrategy/config.ts
9633
- var config4;
9634
- var init_config4 = __esm({
9635
- "src/MaStrategy/config.ts"() {
9636
- "use strict";
9637
- config4 = {
9638
- ENV: "BACKTEST",
9639
- INTERVAL: "15",
9640
- MAKE_ORDERS: true,
9641
- CLOSE_OPPOSITE_POSITIONS: false,
9642
- BACKTEST_PRICE_MODE: "mid",
9643
- AI_ENABLED: false,
9644
- ML_ENABLED: false,
9645
- ML_THRESHOLD: 0.1,
9646
- MIN_AI_QUALITY: 3,
9647
- FEE_PERCENT: 5e-3,
9648
- MAX_LOSS_VALUE: 10,
9649
- TRADE_COOLDOWN_MS: 0,
9650
- MA_FAST: 21,
9651
- MA_SLOW: 55,
9652
- LONG: {
9653
- enable: true,
9654
- direction: "LONG",
9655
- TP: 2,
9656
- SL: 1,
9657
- minRiskRatio: 1.5
9658
- },
9659
- SHORT: {
9660
- enable: true,
9661
- direction: "SHORT",
9662
- TP: 2,
9663
- SL: 1,
9664
- minRiskRatio: 1.5
9665
- }
9666
- };
9667
- }
9668
- });
9669
-
9670
10328
  // src/MaStrategy/figures.ts
9671
10329
  var toLinePoints, buildMaStrategyFigures;
9672
- var init_figures4 = __esm({
10330
+ var init_figures5 = __esm({
9673
10331
  "src/MaStrategy/figures.ts"() {
9674
10332
  "use strict";
9675
10333
  toLinePoints = (candles, values, limit = 120) => {
@@ -9732,12 +10390,12 @@ var init_figures4 = __esm({
9732
10390
  });
9733
10391
 
9734
10392
  // src/MaStrategy/core.ts
9735
- var import_math7, isFiniteNumber, detectCross, createMaStrategyCore;
9736
- var init_core4 = __esm({
10393
+ var import_math8, isFiniteNumber, detectCross, createMaStrategyCore;
10394
+ var init_core5 = __esm({
9737
10395
  "src/MaStrategy/core.ts"() {
9738
10396
  "use strict";
9739
- import_math7 = require("@tradejs/core/math");
9740
- init_figures4();
10397
+ import_math8 = require("@tradejs/core/math");
10398
+ init_figures5();
9741
10399
  isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
9742
10400
  detectCross = (maFast, maSlow) => {
9743
10401
  if (maFast.length < 2 || maSlow.length < 2) {
@@ -9770,8 +10428,8 @@ var init_core4 = __esm({
9770
10428
  }
9771
10429
  return null;
9772
10430
  };
9773
- createMaStrategyCore = async ({ config: config7, strategyApi, indicatorsState }) => {
9774
- const { FEE_PERCENT, MAX_LOSS_VALUE, TRADE_COOLDOWN_MS, LONG, SHORT } = config7;
10431
+ createMaStrategyCore = async ({ config: config8, strategyApi, indicatorsState }) => {
10432
+ const { FEE_PERCENT, MAX_LOSS_VALUE, TRADE_COOLDOWN_MS, LONG, SHORT } = config8;
9775
10433
  const lastTradeController = strategyApi.createLastTradeController({
9776
10434
  enabled: Number(TRADE_COOLDOWN_MS ?? 0) > 0,
9777
10435
  cooldownMs: Number(TRADE_COOLDOWN_MS ?? 0)
@@ -9831,7 +10489,7 @@ var init_core4 = __esm({
9831
10489
  return strategyApi.skip("INVALID_QTY");
9832
10490
  }
9833
10491
  if (riskRatio <= modeConfig.minRiskRatio) {
9834
- return strategyApi.skip(`RISK_RATIO:${(0, import_math7.round)(riskRatio)}`);
10492
+ return strategyApi.skip(`RISK_RATIO:${(0, import_math8.round)(riskRatio)}`);
9835
10493
  }
9836
10494
  const correlation = indicatorsState.latestNumber("correlation");
9837
10495
  lastTradeController.markTrade(timestamp);
@@ -9868,21 +10526,21 @@ var init_core4 = __esm({
9868
10526
  });
9869
10527
 
9870
10528
  // src/MaStrategy/strategy.ts
9871
- var strategy_exports4 = {};
9872
- __export(strategy_exports4, {
10529
+ var strategy_exports5 = {};
10530
+ __export(strategy_exports5, {
9873
10531
  MaStrategyCreator: () => MaStrategyCreator
9874
10532
  });
9875
- var import_strategies17, MaStrategyCreator;
9876
- var init_strategy4 = __esm({
10533
+ var import_strategies16, MaStrategyCreator;
10534
+ var init_strategy5 = __esm({
9877
10535
  "src/MaStrategy/strategy.ts"() {
9878
10536
  "use strict";
9879
- import_strategies17 = require("@tradejs/node/strategies");
9880
- init_config4();
9881
- init_core4();
10537
+ import_strategies16 = require("@tradejs/node/strategies");
10538
+ init_config3();
10539
+ init_core5();
9882
10540
  init_manifest3();
9883
- MaStrategyCreator = (0, import_strategies17.createStrategyRuntime)({
10541
+ MaStrategyCreator = (0, import_strategies16.createStrategyRuntime)({
9884
10542
  strategyName: "MaStrategy",
9885
- defaults: config4,
10543
+ defaults: config3,
9886
10544
  createCore: createMaStrategyCore,
9887
10545
  manifest: maStrategyManifest,
9888
10546
  strategyDirectory: __dirname
@@ -9890,51 +10548,6 @@ var init_strategy4 = __esm({
9890
10548
  }
9891
10549
  });
9892
10550
 
9893
- // src/AdaptiveMomentumRibbon/config.ts
9894
- var config5;
9895
- var init_config5 = __esm({
9896
- "src/AdaptiveMomentumRibbon/config.ts"() {
9897
- "use strict";
9898
- config5 = {
9899
- ENV: "BACKTEST",
9900
- INTERVAL: "15",
9901
- MAKE_ORDERS: true,
9902
- CLOSE_OPPOSITE_POSITIONS: false,
9903
- BACKTEST_PRICE_MODE: "mid",
9904
- AI_ENABLED: false,
9905
- ML_ENABLED: false,
9906
- ML_THRESHOLD: 0.1,
9907
- MIN_AI_QUALITY: 3,
9908
- FEE_PERCENT: 5e-3,
9909
- MAX_LOSS_VALUE: 10,
9910
- AMR_LOOKBACK_BARS: 400,
9911
- AMR_MOMENTUM_PERIOD: 20,
9912
- AMR_BUTTERWORTH_SMOOTHING: 3,
9913
- AMR_WAIT_CLOSE: true,
9914
- AMR_SHOW_INVALIDATION_LEVELS: true,
9915
- AMR_SHOW_KELTNER_CHANNEL: true,
9916
- AMR_KC_LENGTH: 20,
9917
- AMR_KC_MA_TYPE: "EMA",
9918
- AMR_ATR_LENGTH: 14,
9919
- AMR_ATR_MULTIPLIER: 2,
9920
- AMR_EXIT_ON_INVALIDATION: true,
9921
- AMR_LINE_PLOTS: ["kcMidline", "kcUpper", "kcLower", "invalidationLevel"],
9922
- LONG: {
9923
- enable: true,
9924
- direction: "LONG",
9925
- TP: 2,
9926
- SL: 1
9927
- },
9928
- SHORT: {
9929
- enable: true,
9930
- direction: "SHORT",
9931
- TP: 2,
9932
- SL: 1
9933
- }
9934
- };
9935
- }
9936
- });
9937
-
9938
10551
  // ../../node_modules/logform/format.js
9939
10552
  var require_format = __commonJS({
9940
10553
  "../../node_modules/logform/format.js"(exports2, module2) {
@@ -10834,9 +11447,9 @@ var require_levels = __commonJS({
10834
11447
  "../../node_modules/logform/levels.js"(exports2, module2) {
10835
11448
  "use strict";
10836
11449
  var { Colorizer } = require_colorize();
10837
- module2.exports = (config7) => {
10838
- Colorizer.addColors(config7.colors || config7);
10839
- return config7;
11450
+ module2.exports = (config8) => {
11451
+ Colorizer.addColors(config8.colors || config8);
11452
+ return config8;
10840
11453
  };
10841
11454
  }
10842
11455
  });
@@ -20090,7 +20703,7 @@ var require_logger = __commonJS({
20090
20703
  var LegacyTransportStream = require_legacy();
20091
20704
  var Profiler = require_profiler();
20092
20705
  var { warn } = require_common();
20093
- var config7 = require_config2();
20706
+ var config8 = require_config2();
20094
20707
  var formatRegExp = /%[scdjifoO%]/g;
20095
20708
  var Logger = class extends Transform {
20096
20709
  /**
@@ -20152,7 +20765,7 @@ var require_logger = __commonJS({
20152
20765
  this.silent = silent;
20153
20766
  this.format = format3 || this.format || require_json()();
20154
20767
  this.defaultMeta = defaultMeta || null;
20155
- this.levels = levels || this.levels || config7.npm.levels;
20768
+ this.levels = levels || this.levels || config8.npm.levels;
20156
20769
  this.level = level;
20157
20770
  if (this.exceptions) {
20158
20771
  this.exceptions.unhandle();
@@ -20602,14 +21215,14 @@ var require_create_logger = __commonJS({
20602
21215
  "../../node_modules/winston/lib/winston/create-logger.js"(exports2, module2) {
20603
21216
  "use strict";
20604
21217
  var { LEVEL } = require_triple_beam();
20605
- var config7 = require_config2();
21218
+ var config8 = require_config2();
20606
21219
  var Logger = require_logger();
20607
21220
  var debug = require_node2()("winston:create-logger");
20608
21221
  function isLevelEnabledFunctionName(level) {
20609
21222
  return "is" + level.charAt(0).toUpperCase() + level.slice(1) + "Enabled";
20610
21223
  }
20611
21224
  module2.exports = function(opts = {}) {
20612
- opts.levels = opts.levels || config7.npm.levels;
21225
+ opts.levels = opts.levels || config8.npm.levels;
20613
21226
  class DerivedLogger extends Logger {
20614
21227
  /**
20615
21228
  * Create a new class derived logger for which the levels can be attached to
@@ -20881,12 +21494,447 @@ var init_logger = __esm({
20881
21494
  }
20882
21495
  });
20883
21496
 
21497
+ // src/AdaptiveMomentumRibbon/engine.ts
21498
+ var import_math9, PI, MAX_PLOT_POINTS, toFinite, percentileNearestRank, stdev, pushAndTrim, createMovingAverageState, updateMovingAverage, createAtrState, updateAtr, createButterworthState, updateButterworth, pushPlotPoint, evaluateAdaptiveMomentumRibbon;
21499
+ var init_engine2 = __esm({
21500
+ "src/AdaptiveMomentumRibbon/engine.ts"() {
21501
+ "use strict";
21502
+ import_math9 = require("@tradejs/core/math");
21503
+ PI = 3.14159265359;
21504
+ MAX_PLOT_POINTS = 240;
21505
+ toFinite = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
21506
+ percentileNearestRank = (values, percent) => {
21507
+ if (!values.length) {
21508
+ return null;
21509
+ }
21510
+ const sorted = [...values].sort((a, b) => a - b);
21511
+ const rank = Math.max(
21512
+ 0,
21513
+ Math.min(sorted.length - 1, Math.ceil(percent / 100 * sorted.length) - 1)
21514
+ );
21515
+ return sorted[rank] ?? null;
21516
+ };
21517
+ stdev = (values) => {
21518
+ if (!values.length) {
21519
+ return null;
21520
+ }
21521
+ const mean = values.reduce((sum, value) => sum + value, 0) / values.length;
21522
+ const variance = values.reduce((sum, value) => sum + (value - mean) ** 2, 0) / values.length;
21523
+ return Math.sqrt(variance);
21524
+ };
21525
+ pushAndTrim = (values, value, limit) => {
21526
+ values.push(value);
21527
+ if (values.length > limit) {
21528
+ values.splice(0, values.length - limit);
21529
+ }
21530
+ };
21531
+ createMovingAverageState = (type, length) => {
21532
+ switch (type) {
21533
+ case "SMA":
21534
+ return {
21535
+ type,
21536
+ length,
21537
+ window: [],
21538
+ sum: 0
21539
+ };
21540
+ case "EMA":
21541
+ return {
21542
+ type,
21543
+ length,
21544
+ seedWindow: [],
21545
+ value: null
21546
+ };
21547
+ case "SMMA (RMA)":
21548
+ return {
21549
+ type,
21550
+ length,
21551
+ seedWindow: [],
21552
+ value: null
21553
+ };
21554
+ case "WMA":
21555
+ return {
21556
+ type,
21557
+ length,
21558
+ window: []
21559
+ };
21560
+ case "VWMA":
21561
+ return {
21562
+ type,
21563
+ length,
21564
+ priceWindow: [],
21565
+ volumeWindow: [],
21566
+ weightedSum: 0,
21567
+ volumeSum: 0
21568
+ };
21569
+ }
21570
+ };
21571
+ updateMovingAverage = (state, price, volume) => {
21572
+ switch (state.type) {
21573
+ case "SMA": {
21574
+ state.window.push(price);
21575
+ state.sum += price;
21576
+ if (state.window.length > state.length) {
21577
+ state.sum -= state.window.shift() ?? 0;
21578
+ }
21579
+ if (state.window.length < state.length) {
21580
+ return null;
21581
+ }
21582
+ return state.sum / state.length;
21583
+ }
21584
+ case "EMA": {
21585
+ if (state.value == null) {
21586
+ state.seedWindow.push(price);
21587
+ if (state.seedWindow.length < state.length) {
21588
+ return null;
21589
+ }
21590
+ if (state.seedWindow.length === state.length) {
21591
+ state.value = state.seedWindow.reduce((sum, item) => sum + item, 0) / state.length;
21592
+ return state.value;
21593
+ }
21594
+ }
21595
+ const alpha = 2 / (state.length + 1);
21596
+ state.value = price * alpha + (state.value ?? price) * (1 - alpha);
21597
+ return state.value;
21598
+ }
21599
+ case "SMMA (RMA)": {
21600
+ if (state.value == null) {
21601
+ state.seedWindow.push(price);
21602
+ if (state.seedWindow.length < state.length) {
21603
+ return null;
21604
+ }
21605
+ if (state.seedWindow.length === state.length) {
21606
+ state.value = state.seedWindow.reduce((sum, item) => sum + item, 0) / state.length;
21607
+ return state.value;
21608
+ }
21609
+ }
21610
+ state.value = ((state.value ?? price) * (state.length - 1) + price) / state.length;
21611
+ return state.value;
21612
+ }
21613
+ case "WMA": {
21614
+ state.window.push(price);
21615
+ if (state.window.length > state.length) {
21616
+ state.window.shift();
21617
+ }
21618
+ if (state.window.length < state.length) {
21619
+ return null;
21620
+ }
21621
+ let weightedSum = 0;
21622
+ let weightSum = 0;
21623
+ for (let index = 0; index < state.window.length; index += 1) {
21624
+ const weight = index + 1;
21625
+ weightedSum += state.window[index] * weight;
21626
+ weightSum += weight;
21627
+ }
21628
+ return weightSum > 0 ? weightedSum / weightSum : null;
21629
+ }
21630
+ case "VWMA": {
21631
+ state.priceWindow.push(price);
21632
+ state.volumeWindow.push(volume);
21633
+ state.weightedSum += price * volume;
21634
+ state.volumeSum += volume;
21635
+ if (state.priceWindow.length > state.length) {
21636
+ const removedPrice = state.priceWindow.shift() ?? 0;
21637
+ const removedVolume = state.volumeWindow.shift() ?? 0;
21638
+ state.weightedSum -= removedPrice * removedVolume;
21639
+ state.volumeSum -= removedVolume;
21640
+ }
21641
+ if (state.priceWindow.length < state.length || state.volumeSum <= 0) {
21642
+ return null;
21643
+ }
21644
+ return state.weightedSum / state.volumeSum;
21645
+ }
21646
+ }
21647
+ };
21648
+ createAtrState = (length) => ({
21649
+ length,
21650
+ previousClose: null,
21651
+ trSeedWindow: [],
21652
+ atrValue: null
21653
+ });
21654
+ updateAtr = (state, candle) => {
21655
+ const previousClose = state.previousClose;
21656
+ const tr = previousClose == null ? candle.high - candle.low : Math.max(
21657
+ candle.high - candle.low,
21658
+ Math.abs(candle.high - previousClose),
21659
+ Math.abs(candle.low - previousClose)
21660
+ );
21661
+ state.previousClose = candle.close;
21662
+ if (state.atrValue == null) {
21663
+ state.trSeedWindow.push(tr);
21664
+ if (state.trSeedWindow.length < state.length) {
21665
+ return null;
21666
+ }
21667
+ if (state.trSeedWindow.length === state.length) {
21668
+ state.atrValue = state.trSeedWindow.reduce((sum, value) => sum + value, 0) / state.length;
21669
+ return state.atrValue;
21670
+ }
21671
+ }
21672
+ state.atrValue = ((state.atrValue ?? tr) * (state.length - 1) + tr) / state.length;
21673
+ return state.atrValue;
21674
+ };
21675
+ createButterworthState = (length) => ({
21676
+ length,
21677
+ prev1: null,
21678
+ prev2: null
21679
+ });
21680
+ updateButterworth = (state, source) => {
21681
+ if (source == null) {
21682
+ return null;
21683
+ }
21684
+ const safeLength = Math.max(state.length, 1);
21685
+ const a = Math.exp(-Math.sqrt(2) * PI / safeLength);
21686
+ const b = 2 * a * Math.cos(Math.sqrt(2) * PI / safeLength);
21687
+ const c2 = b;
21688
+ const c3 = -(a * a);
21689
+ const c1 = 1 - c2 - c3;
21690
+ if (state.prev1 == null || state.prev2 == null) {
21691
+ state.prev1 = source;
21692
+ state.prev2 = source;
21693
+ return source;
21694
+ }
21695
+ const result = c1 * source + c2 * state.prev1 + c3 * state.prev2;
21696
+ state.prev2 = state.prev1;
21697
+ state.prev1 = result;
21698
+ return result;
21699
+ };
21700
+ pushPlotPoint = (plotSeries, plotName, candle, value) => {
21701
+ if (value == null) {
21702
+ return;
21703
+ }
21704
+ const points = plotSeries[plotName] ?? [];
21705
+ points.push({
21706
+ time: candle.timestamp,
21707
+ value
21708
+ });
21709
+ if (points.length > MAX_PLOT_POINTS) {
21710
+ points.splice(0, points.length - MAX_PLOT_POINTS);
21711
+ }
21712
+ plotSeries[plotName] = points;
21713
+ };
21714
+ evaluateAdaptiveMomentumRibbon = ({
21715
+ candles,
21716
+ config: config8,
21717
+ linePlots
21718
+ }) => {
21719
+ const momentumPeriod = (0, import_math9.asPositiveInt)(config8.AMR_MOMENTUM_PERIOD, 20);
21720
+ const smoothingLength = (0, import_math9.asPositiveInt)(config8.AMR_BUTTERWORTH_SMOOTHING, 3);
21721
+ const waitClose = Boolean(config8.AMR_WAIT_CLOSE);
21722
+ const confirmOnNextBar = Boolean(config8.AMR_CONFIRM_ON_NEXT_BAR);
21723
+ const minSignalOscAbs = (0, import_math9.asPositiveNumber)(config8.AMR_MIN_SIGNAL_OSC_ABS, 0.55);
21724
+ const requireKcBias = Boolean(config8.AMR_REQUIRE_KC_BIAS);
21725
+ const minBarsBetweenSignals = (0, import_math9.asPositiveInt)(
21726
+ config8.AMR_MIN_BARS_BETWEEN_SIGNALS,
21727
+ 12
21728
+ );
21729
+ const showInvalidationLevels = Boolean(config8.AMR_SHOW_INVALIDATION_LEVELS);
21730
+ const showKeltnerChannel = Boolean(config8.AMR_SHOW_KELTNER_CHANNEL);
21731
+ const kcLength = (0, import_math9.asPositiveInt)(config8.AMR_KC_LENGTH, 20);
21732
+ const kcMaType = config8.AMR_KC_MA_TYPE === "SMA" || config8.AMR_KC_MA_TYPE === "EMA" || config8.AMR_KC_MA_TYPE === "SMMA (RMA)" || config8.AMR_KC_MA_TYPE === "WMA" || config8.AMR_KC_MA_TYPE === "VWMA" ? config8.AMR_KC_MA_TYPE : "EMA";
21733
+ const atrLength = (0, import_math9.asPositiveInt)(config8.AMR_ATR_LENGTH, 14);
21734
+ const atrMultiplier = (0, import_math9.asPositiveNumber)(config8.AMR_ATR_MULTIPLIER, 2);
21735
+ const sourceWindow = [];
21736
+ const deviationWindow = [];
21737
+ const maState = createMovingAverageState(kcMaType, kcLength);
21738
+ const atrState = createAtrState(atrLength);
21739
+ const butterworthState = createButterworthState(smoothingLength);
21740
+ const plotSeries = {};
21741
+ let previousSignalOsc = null;
21742
+ let lastAcceptedSignalIndex = null;
21743
+ let pendingSignal = null;
21744
+ let invalidationLevel = null;
21745
+ let activeBuy = false;
21746
+ let activeSell = false;
21747
+ let lastSnapshot = {
21748
+ entryLong: false,
21749
+ entryShort: false,
21750
+ invalidated: false,
21751
+ activeBuy: false,
21752
+ activeSell: false,
21753
+ signalOsc: null,
21754
+ kcMidline: null,
21755
+ kcUpper: null,
21756
+ kcLower: null,
21757
+ invalidationLevel: null,
21758
+ lineValues: Object.fromEntries(
21759
+ linePlots.map((plotName) => [plotName, null])
21760
+ )
21761
+ };
21762
+ for (let index = 0; index < candles.length; index += 1) {
21763
+ const candle = candles[index];
21764
+ const previousCandle = index > 0 ? candles[index - 1] : null;
21765
+ const kcMidline = updateMovingAverage(
21766
+ maState,
21767
+ candle.close,
21768
+ Number(candle.volume ?? 0)
21769
+ );
21770
+ const atrValue = updateAtr(atrState, candle);
21771
+ const kcUpper = kcMidline != null && atrValue != null ? kcMidline + atrMultiplier * atrValue : null;
21772
+ const kcLower = kcMidline != null && atrValue != null ? kcMidline - atrMultiplier * atrValue : null;
21773
+ const sourceCandle = waitClose ? previousCandle : candle;
21774
+ const sourceClose = sourceCandle?.close ?? null;
21775
+ let signalOsc = null;
21776
+ let entryLong = false;
21777
+ let entryShort = false;
21778
+ if (sourceClose != null) {
21779
+ pushAndTrim(sourceWindow, sourceClose, momentumPeriod);
21780
+ if (sourceWindow.length >= momentumPeriod) {
21781
+ const medianValue = percentileNearestRank(sourceWindow, 50);
21782
+ const deviation = medianValue != null ? sourceClose - medianValue : null;
21783
+ if (deviation != null) {
21784
+ pushAndTrim(deviationWindow, deviation, momentumPeriod);
21785
+ }
21786
+ if (deviation != null && deviationWindow.length >= momentumPeriod) {
21787
+ const absoluteDeviationWindow = deviationWindow.map(
21788
+ (value) => Math.abs(value)
21789
+ );
21790
+ const medDeviation = percentileNearestRank(
21791
+ absoluteDeviationWindow,
21792
+ 50
21793
+ );
21794
+ const scale = medDeviation === 0 ? stdev(sourceWindow) : medDeviation != null ? medDeviation * 1.4826 : null;
21795
+ const rawOsc = scale != null && scale !== 0 ? deviation / scale : 0;
21796
+ signalOsc = updateButterworth(butterworthState, rawOsc);
21797
+ }
21798
+ }
21799
+ }
21800
+ if (signalOsc != null && previousSignalOsc != null) {
21801
+ const rawEntryLong = previousSignalOsc <= 0 && signalOsc > 0;
21802
+ const rawEntryShort = previousSignalOsc >= 0 && signalOsc < 0;
21803
+ const strongEnough = Math.abs(signalOsc) >= minSignalOscAbs;
21804
+ const spacingOk = lastAcceptedSignalIndex == null || index - lastAcceptedSignalIndex >= minBarsBetweenSignals;
21805
+ const longKcBiasOk = !requireKcBias || kcMidline != null && candle.close > kcMidline;
21806
+ const shortKcBiasOk = !requireKcBias || kcMidline != null && candle.close < kcMidline;
21807
+ if (confirmOnNextBar) {
21808
+ if (pendingSignal?.direction === "LONG") {
21809
+ const pendingStillValid = pendingSignal.invalidationLevel == null || candle.low >= pendingSignal.invalidationLevel;
21810
+ const confirmed = pendingStillValid && signalOsc > 0 && strongEnough && longKcBiasOk;
21811
+ if (confirmed) {
21812
+ entryLong = true;
21813
+ invalidationLevel = pendingSignal.invalidationLevel;
21814
+ lastAcceptedSignalIndex = index;
21815
+ }
21816
+ pendingSignal = null;
21817
+ } else if (pendingSignal?.direction === "SHORT") {
21818
+ const pendingStillValid = pendingSignal.invalidationLevel == null || candle.high <= pendingSignal.invalidationLevel;
21819
+ const confirmed = pendingStillValid && signalOsc < 0 && strongEnough && shortKcBiasOk;
21820
+ if (confirmed) {
21821
+ entryShort = true;
21822
+ invalidationLevel = pendingSignal.invalidationLevel;
21823
+ lastAcceptedSignalIndex = index;
21824
+ }
21825
+ pendingSignal = null;
21826
+ }
21827
+ if (!entryLong && !entryShort && spacingOk) {
21828
+ if (rawEntryLong && strongEnough && longKcBiasOk && sourceCandle) {
21829
+ pendingSignal = {
21830
+ direction: "LONG",
21831
+ invalidationLevel: sourceCandle.low
21832
+ };
21833
+ } else if (rawEntryShort && strongEnough && shortKcBiasOk && sourceCandle) {
21834
+ pendingSignal = {
21835
+ direction: "SHORT",
21836
+ invalidationLevel: sourceCandle.high
21837
+ };
21838
+ }
21839
+ }
21840
+ } else {
21841
+ entryLong = rawEntryLong && strongEnough && spacingOk && longKcBiasOk;
21842
+ entryShort = rawEntryShort && strongEnough && spacingOk && shortKcBiasOk;
21843
+ }
21844
+ }
21845
+ if (signalOsc != null) {
21846
+ previousSignalOsc = signalOsc;
21847
+ }
21848
+ if (entryLong && sourceCandle) {
21849
+ if (invalidationLevel == null) {
21850
+ invalidationLevel = sourceCandle.low;
21851
+ }
21852
+ activeBuy = true;
21853
+ activeSell = false;
21854
+ }
21855
+ if (entryShort && sourceCandle) {
21856
+ if (invalidationLevel == null) {
21857
+ invalidationLevel = sourceCandle.high;
21858
+ }
21859
+ activeSell = true;
21860
+ activeBuy = false;
21861
+ }
21862
+ const checkCandle = waitClose ? previousCandle : candle;
21863
+ let invalidated = false;
21864
+ if (activeBuy && checkCandle && invalidationLevel != null && checkCandle.low < invalidationLevel) {
21865
+ invalidated = true;
21866
+ }
21867
+ if (activeSell && checkCandle && invalidationLevel != null && checkCandle.high > invalidationLevel) {
21868
+ invalidated = true;
21869
+ }
21870
+ if (invalidated) {
21871
+ activeBuy = false;
21872
+ activeSell = false;
21873
+ }
21874
+ const displayedKcMidline = showKeltnerChannel ? kcMidline : null;
21875
+ const displayedKcUpper = showKeltnerChannel ? kcUpper : null;
21876
+ const displayedKcLower = showKeltnerChannel ? kcLower : null;
21877
+ const displayedInvalidationLevel = showInvalidationLevels ? invalidationLevel : null;
21878
+ pushPlotPoint(plotSeries, "signalOsc", candle, signalOsc);
21879
+ pushPlotPoint(plotSeries, "kcMidline", candle, displayedKcMidline);
21880
+ pushPlotPoint(plotSeries, "kcUpper", candle, displayedKcUpper);
21881
+ pushPlotPoint(plotSeries, "kcLower", candle, displayedKcLower);
21882
+ pushPlotPoint(
21883
+ plotSeries,
21884
+ "invalidationLevel",
21885
+ candle,
21886
+ displayedInvalidationLevel
21887
+ );
21888
+ const currentLineValues = {};
21889
+ for (const plotName of linePlots) {
21890
+ switch (plotName) {
21891
+ case "signalOsc":
21892
+ currentLineValues[plotName] = signalOsc;
21893
+ break;
21894
+ case "kcMidline":
21895
+ currentLineValues[plotName] = displayedKcMidline;
21896
+ break;
21897
+ case "kcUpper":
21898
+ currentLineValues[plotName] = displayedKcUpper;
21899
+ break;
21900
+ case "kcLower":
21901
+ currentLineValues[plotName] = displayedKcLower;
21902
+ break;
21903
+ case "invalidationLevel":
21904
+ currentLineValues[plotName] = displayedInvalidationLevel;
21905
+ break;
21906
+ default:
21907
+ currentLineValues[plotName] = null;
21908
+ break;
21909
+ }
21910
+ }
21911
+ lastSnapshot = {
21912
+ entryLong,
21913
+ entryShort,
21914
+ invalidated,
21915
+ activeBuy,
21916
+ activeSell,
21917
+ signalOsc: toFinite(signalOsc),
21918
+ kcMidline: toFinite(displayedKcMidline),
21919
+ kcUpper: toFinite(displayedKcUpper),
21920
+ kcLower: toFinite(displayedKcLower),
21921
+ invalidationLevel: toFinite(displayedInvalidationLevel),
21922
+ lineValues: currentLineValues
21923
+ };
21924
+ }
21925
+ return {
21926
+ snapshot: lastSnapshot,
21927
+ plotSeries
21928
+ };
21929
+ };
21930
+ }
21931
+ });
21932
+
20884
21933
  // src/AdaptiveMomentumRibbon/figures.ts
20885
- var import_pine, DEFAULT_COLORS, LINE_STYLE_BY_PLOT, toFigurePoints, buildAdaptiveMomentumRibbonFigures;
20886
- var init_figures5 = __esm({
21934
+ var DEFAULT_COLORS, LINE_STYLE_BY_PLOT, toFigurePoints, buildAdaptiveMomentumRibbonFigures;
21935
+ var init_figures6 = __esm({
20887
21936
  "src/AdaptiveMomentumRibbon/figures.ts"() {
20888
21937
  "use strict";
20889
- import_pine = require("@tradejs/node/pine");
20890
21938
  DEFAULT_COLORS = ["#2962ff", "#f23645", "#089981", "#f59e0b"];
20891
21939
  LINE_STYLE_BY_PLOT = {
20892
21940
  kcMidline: {
@@ -20915,18 +21963,18 @@ var init_figures5 = __esm({
20915
21963
  const points = [];
20916
21964
  for (let i = start; i < series.length; i += 1) {
20917
21965
  const item = series[i];
20918
- const timestamp = (0, import_pine.toFiniteNumber)(item?.time);
20919
- const value = (0, import_pine.toFiniteNumber)(item?.value);
20920
- if (timestamp == null || value == null) continue;
21966
+ if (!Number.isFinite(item?.time) || !Number.isFinite(item?.value)) {
21967
+ continue;
21968
+ }
20921
21969
  points.push({
20922
- timestamp,
20923
- value
21970
+ timestamp: item.time,
21971
+ value: item.value
20924
21972
  });
20925
21973
  }
20926
21974
  return points;
20927
21975
  };
20928
21976
  buildAdaptiveMomentumRibbonFigures = ({
20929
- pineContext,
21977
+ plotSeries,
20930
21978
  linePlots,
20931
21979
  direction,
20932
21980
  entryTimestamp,
@@ -20934,7 +21982,7 @@ var init_figures5 = __esm({
20934
21982
  maxPoints = 180
20935
21983
  }) => {
20936
21984
  const lines = linePlots.map((plotName, index) => {
20937
- const series = (0, import_pine.getPinePlotSeries)(pineContext, plotName);
21985
+ const series = plotSeries[plotName] ?? [];
20938
21986
  const points = toFigurePoints(series, maxPoints);
20939
21987
  if (!points.length) {
20940
21988
  return null;
@@ -20969,70 +22017,25 @@ var init_figures5 = __esm({
20969
22017
  });
20970
22018
 
20971
22019
  // src/AdaptiveMomentumRibbon/core.ts
20972
- var import_pine2, import_math8, AMR_PINE_FILE_NAME, AMR_BOOLEAN_PLOTS, AMR_NUMBER_PLOTS, asKcMaType, resolveAmrInputs, resolveLinePlots, readAmrSnapshot, createAdaptiveMomentumRibbonCore;
20973
- var init_core5 = __esm({
22020
+ var import_math10, resolveLinePlots, createAdaptiveMomentumRibbonCore;
22021
+ var init_core6 = __esm({
20974
22022
  "src/AdaptiveMomentumRibbon/core.ts"() {
20975
22023
  "use strict";
20976
- import_pine2 = require("@tradejs/node/pine");
20977
- import_math8 = require("@tradejs/core/math");
22024
+ import_math10 = require("@tradejs/core/math");
20978
22025
  init_logger();
20979
- init_figures5();
20980
- AMR_PINE_FILE_NAME = "adaptiveMomentumRibbon.pine";
20981
- AMR_BOOLEAN_PLOTS = [
20982
- "entryLong",
20983
- "entryShort",
20984
- "invalidated",
20985
- "activeBuy",
20986
- "activeSell"
20987
- ];
20988
- AMR_NUMBER_PLOTS = [
20989
- "signalOsc",
20990
- "kcMidline",
20991
- "kcUpper",
20992
- "kcLower",
20993
- "invalidationLevel"
20994
- ];
20995
- asKcMaType = (value) => {
20996
- if (value === "SMA" || value === "EMA" || value === "SMMA (RMA)" || value === "WMA" || value === "VWMA") {
20997
- return value;
20998
- }
20999
- return "EMA";
21000
- };
21001
- resolveAmrInputs = (config7) => ({
21002
- "Momentum Period": (0, import_math8.asPositiveInt)(config7.AMR_MOMENTUM_PERIOD, 20),
21003
- "Butterworth Smoothing": (0, import_math8.asPositiveInt)(config7.AMR_BUTTERWORTH_SMOOTHING, 3),
21004
- "Confirm Signals on Bar Close": Boolean(config7.AMR_WAIT_CLOSE),
21005
- "Show Invalidation Levels": Boolean(config7.AMR_SHOW_INVALIDATION_LEVELS),
21006
- "Show Keltner Channel": Boolean(config7.AMR_SHOW_KELTNER_CHANNEL),
21007
- "KC Length": (0, import_math8.asPositiveInt)(config7.AMR_KC_LENGTH, 20),
21008
- "KC MA Type": asKcMaType(config7.AMR_KC_MA_TYPE),
21009
- "ATR Length": (0, import_math8.asPositiveInt)(config7.AMR_ATR_LENGTH, 14),
21010
- "ATR Multiplier": (0, import_math8.asPositiveNumber)(config7.AMR_ATR_MULTIPLIER, 2)
21011
- });
22026
+ init_engine2();
22027
+ init_figures6();
21012
22028
  resolveLinePlots = (value) => {
21013
22029
  if (!Array.isArray(value)) {
21014
22030
  return [];
21015
22031
  }
21016
22032
  return value.map((item) => String(item ?? "").trim()).filter((item) => item.length > 0);
21017
22033
  };
21018
- readAmrSnapshot = (pineContext, linePlots) => {
21019
- return {
21020
- ...(0, import_pine2.getLatestPineBooleanPlotValues)(pineContext, AMR_BOOLEAN_PLOTS),
21021
- ...(0, import_pine2.getLatestPineNumberPlotValues)(pineContext, AMR_NUMBER_PLOTS),
21022
- lineValues: (0, import_pine2.getLatestPineNumberPlotValues)(pineContext, linePlots)
21023
- };
21024
- };
21025
- createAdaptiveMomentumRibbonCore = async ({ config: config7, symbol, loadPineScriptFile, strategyApi }) => {
21026
- const script = loadPineScriptFile(AMR_PINE_FILE_NAME);
21027
- const { LONG, SHORT, AMR_EXIT_ON_INVALIDATION, MAX_LOSS_VALUE, FEE_PERCENT } = config7;
21028
- const linePlots = resolveLinePlots(config7.AMR_LINE_PLOTS);
21029
- const lookbackBars = (0, import_math8.asPositiveInt)(config7.AMR_LOOKBACK_BARS, 0);
21030
- const pineInputs = resolveAmrInputs(config7);
21031
- const timeframe = String(config7.INTERVAL ?? "15");
22034
+ createAdaptiveMomentumRibbonCore = async ({ config: config8, symbol, strategyApi }) => {
22035
+ const { LONG, SHORT, AMR_EXIT_ON_INVALIDATION, MAX_LOSS_VALUE, FEE_PERCENT } = config8;
22036
+ const linePlots = resolveLinePlots(config8.AMR_LINE_PLOTS);
22037
+ const lookbackBars = (0, import_math10.asPositiveInt)(config8.AMR_LOOKBACK_BARS, 0);
21032
22038
  return async () => {
21033
- if (!script) {
21034
- return strategyApi.skip("AMR_SCRIPT_EMPTY");
21035
- }
21036
22039
  const { fullData, currentPrice, timestamp } = await strategyApi.getMarketData();
21037
22040
  if (fullData.length < 2) {
21038
22041
  return strategyApi.skip("WAIT_DATA");
@@ -21042,26 +22045,24 @@ var init_core5 = __esm({
21042
22045
  position && typeof position.qty === "number" && position.qty > 0
21043
22046
  );
21044
22047
  const candles = lookbackBars > 0 ? fullData.slice(-lookbackBars) : fullData;
21045
- let pineContext;
22048
+ let evaluation;
21046
22049
  try {
21047
- pineContext = await (0, import_pine2.runPineScript)({
22050
+ evaluation = evaluateAdaptiveMomentumRibbon({
21048
22051
  candles,
21049
- script,
21050
- symbol,
21051
- timeframe,
21052
- inputs: pineInputs
22052
+ config: config8,
22053
+ linePlots
21053
22054
  });
21054
22055
  } catch (error) {
21055
22056
  if (typeof globalThis.setImmediate === "function") {
21056
22057
  logger.warn(
21057
- "AdaptiveMomentumRibbon pine run failed for %s: %s",
22058
+ "AdaptiveMomentumRibbon evaluation failed for %s: %s",
21058
22059
  symbol,
21059
22060
  String(error)
21060
22061
  );
21061
22062
  }
21062
- return strategyApi.skip("AMR_SCRIPT_FAILED");
22063
+ return strategyApi.skip("AMR_EVALUATION_FAILED");
21063
22064
  }
21064
- const amr = readAmrSnapshot(pineContext, linePlots);
22065
+ const { snapshot: amr, plotSeries } = evaluation;
21065
22066
  if (amr.entryLong && amr.entryShort) {
21066
22067
  return strategyApi.skip("AMR_SIGNAL_CONFLICT");
21067
22068
  }
@@ -21113,7 +22114,7 @@ var init_core5 = __esm({
21113
22114
  code: amr.entryLong ? "AMR_ENTRY_LONG" : "AMR_ENTRY_SHORT",
21114
22115
  direction: modeConfig.direction,
21115
22116
  figures: buildAdaptiveMomentumRibbonFigures({
21116
- pineContext,
22117
+ plotSeries,
21117
22118
  linePlots,
21118
22119
  direction: modeConfig.direction,
21119
22120
  entryTimestamp: timestamp,
@@ -21123,18 +22124,28 @@ var init_core5 = __esm({
21123
22124
  amr,
21124
22125
  amrSignalTiming: {
21125
22126
  entryTiming: "zero_cross",
21126
- waitClose: Boolean(config7.AMR_WAIT_CLOSE),
22127
+ waitClose: Boolean(config8.AMR_WAIT_CLOSE),
22128
+ confirmOnNextBar: Boolean(config8.AMR_CONFIRM_ON_NEXT_BAR),
21127
22129
  lookbackBars
21128
22130
  },
21129
22131
  amrConfigSnapshot: {
21130
- momentumPeriod: (0, import_math8.asPositiveInt)(config7.AMR_MOMENTUM_PERIOD, 20),
21131
- butterworthSmoothing: (0, import_math8.asPositiveInt)(
21132
- config7.AMR_BUTTERWORTH_SMOOTHING,
22132
+ momentumPeriod: (0, import_math10.asPositiveInt)(config8.AMR_MOMENTUM_PERIOD, 20),
22133
+ butterworthSmoothing: (0, import_math10.asPositiveInt)(
22134
+ config8.AMR_BUTTERWORTH_SMOOTHING,
21133
22135
  3
21134
22136
  ),
21135
- kcLength: (0, import_math8.asPositiveInt)(config7.AMR_KC_LENGTH, 20),
21136
- atrLength: (0, import_math8.asPositiveInt)(config7.AMR_ATR_LENGTH, 14),
21137
- atrMultiplier: (0, import_math8.asPositiveNumber)(config7.AMR_ATR_MULTIPLIER, 2)
22137
+ minSignalOscAbs: (0, import_math10.asPositiveNumber)(
22138
+ config8.AMR_MIN_SIGNAL_OSC_ABS,
22139
+ 0.55
22140
+ ),
22141
+ requireKcBias: Boolean(config8.AMR_REQUIRE_KC_BIAS),
22142
+ minBarsBetweenSignals: (0, import_math10.asPositiveInt)(
22143
+ config8.AMR_MIN_BARS_BETWEEN_SIGNALS,
22144
+ 12
22145
+ ),
22146
+ kcLength: (0, import_math10.asPositiveInt)(config8.AMR_KC_LENGTH, 20),
22147
+ atrLength: (0, import_math10.asPositiveInt)(config8.AMR_ATR_LENGTH, 14),
22148
+ atrMultiplier: (0, import_math10.asPositiveNumber)(config8.AMR_ATR_MULTIPLIER, 2)
21138
22149
  }
21139
22150
  },
21140
22151
  orderPlan: {
@@ -21149,21 +22160,21 @@ var init_core5 = __esm({
21149
22160
  });
21150
22161
 
21151
22162
  // src/AdaptiveMomentumRibbon/strategy.ts
21152
- var strategy_exports5 = {};
21153
- __export(strategy_exports5, {
22163
+ var strategy_exports6 = {};
22164
+ __export(strategy_exports6, {
21154
22165
  AdaptiveMomentumRibbonStrategyCreator: () => AdaptiveMomentumRibbonStrategyCreator
21155
22166
  });
21156
- var import_strategies18, AdaptiveMomentumRibbonStrategyCreator;
21157
- var init_strategy5 = __esm({
22167
+ var import_strategies17, AdaptiveMomentumRibbonStrategyCreator;
22168
+ var init_strategy6 = __esm({
21158
22169
  "src/AdaptiveMomentumRibbon/strategy.ts"() {
21159
22170
  "use strict";
21160
- import_strategies18 = require("@tradejs/node/strategies");
21161
- init_config5();
21162
- init_core5();
22171
+ import_strategies17 = require("@tradejs/node/strategies");
22172
+ init_config();
22173
+ init_core6();
21163
22174
  init_manifest();
21164
- AdaptiveMomentumRibbonStrategyCreator = (0, import_strategies18.createStrategyRuntime)({
22175
+ AdaptiveMomentumRibbonStrategyCreator = (0, import_strategies17.createStrategyRuntime)({
21165
22176
  strategyName: "AdaptiveMomentumRibbon",
21166
- defaults: config5,
22177
+ defaults: config,
21167
22178
  createCore: createAdaptiveMomentumRibbonCore,
21168
22179
  manifest: adaptiveMomentumRibbonManifest,
21169
22180
  strategyDirectory: __dirname
@@ -21171,67 +22182,9 @@ var init_strategy5 = __esm({
21171
22182
  }
21172
22183
  });
21173
22184
 
21174
- // src/VolumeDivergence/config.ts
21175
- var config6;
21176
- var init_config6 = __esm({
21177
- "src/VolumeDivergence/config.ts"() {
21178
- "use strict";
21179
- config6 = {
21180
- ENV: "BACKTEST",
21181
- INTERVAL: "15",
21182
- MAKE_ORDERS: true,
21183
- CLOSE_OPPOSITE_POSITIONS: false,
21184
- BACKTEST_PRICE_MODE: "mid",
21185
- AI_ENABLED: false,
21186
- ML_ENABLED: false,
21187
- ML_THRESHOLD: 0.1,
21188
- MIN_AI_QUALITY: 3,
21189
- FEE_PERCENT: 5e-3,
21190
- MAX_LOSS_VALUE: 10,
21191
- MA_FAST: 14,
21192
- MA_MEDIUM: 49,
21193
- MA_SLOW: 50,
21194
- OBV_SMA: 10,
21195
- ATR: 14,
21196
- ATR_PCT_SHORT: 7,
21197
- ATR_PCT_LONG: 30,
21198
- BB: 20,
21199
- BB_STD: 2,
21200
- MACD_FAST: 12,
21201
- MACD_SLOW: 26,
21202
- MACD_SIGNAL: 9,
21203
- LEVEL_LOOKBACK: 20,
21204
- LEVEL_DELAY: 2,
21205
- NORMALIZATION_LENGTH: 100,
21206
- PIVOT_LOOKBACK_LEFT: 8,
21207
- PIVOT_LOOKBACK_RIGHT: 3,
21208
- MIN_BARS_BETWEEN_PIVOTS: 4,
21209
- MAX_BARS_BETWEEN_PIVOTS: 36,
21210
- ALLOW_STRUCTURE_ADVANCE_ENTRY: false,
21211
- MIN_DIVERGENCE_AMPLITUDE_ATR_RATIO: 0.35,
21212
- MIN_RECLAIM_PCT: 105,
21213
- MIN_CONFIRMATION_CANDLE_QUALITY: 0.58,
21214
- BULLISH: {
21215
- enable: true,
21216
- direction: "LONG",
21217
- TP: 4,
21218
- SL: 1.3,
21219
- minRiskRatio: 2
21220
- },
21221
- BEARISH: {
21222
- enable: true,
21223
- direction: "SHORT",
21224
- TP: 4,
21225
- SL: 1.3,
21226
- minRiskRatio: 2
21227
- }
21228
- };
21229
- }
21230
- });
21231
-
21232
22185
  // src/VolumeDivergence/figures.ts
21233
22186
  var buildVolumeDivergenceFigures;
21234
- var init_figures6 = __esm({
22187
+ var init_figures7 = __esm({
21235
22188
  "src/VolumeDivergence/figures.ts"() {
21236
22189
  "use strict";
21237
22190
  buildVolumeDivergenceFigures = ({
@@ -21286,64 +22239,18 @@ var init_figures6 = __esm({
21286
22239
  });
21287
22240
 
21288
22241
  // src/VolumeDivergence/core.ts
21289
- var import_math9, BREAK_EVEN_TRIGGER_RISK_MULTIPLIER3, isFiniteNumber2, clamp2, isOpenPosition3, getFavorableMovePct3, getPositionStopLossPrice3, getPositionRiskPct3, isBreakEvenStopAlreadyApplied3, compactQueue, rebaseQueue, rebaseConfirmedPivots, appendNormalizedVolumes, isPivotHigh, candleDeltaProxy, appendConfirmedPivotIndices, findLatestDivergence, getRequiredHistorySize, buildPendingDivergenceCandidate, updatePendingCandidateProgress, resolvePendingEntryTiming, findCandleIndexByTimestamp, getModeConfigByKind, buildEntryPayloadFromPendingCandidate, createVolumeDivergenceCore;
21290
- var init_core6 = __esm({
22242
+ var import_math11, isFiniteNumber2, clamp2, isOpenPosition4, compactQueue, rebaseQueue, rebaseConfirmedPivots, appendNormalizedVolumes, isPivotHigh, candleDeltaProxy, appendConfirmedPivotIndices, findLatestDivergence, getRequiredHistorySize, buildPendingDivergenceCandidate, updatePendingCandidateProgress, resolvePendingEntryTiming, findCandleIndexByTimestamp, getModeConfigByKind, buildEntryPayloadFromPendingCandidate, createVolumeDivergenceCore;
22243
+ var init_core7 = __esm({
21291
22244
  "src/VolumeDivergence/core.ts"() {
21292
22245
  "use strict";
21293
- import_math9 = require("@tradejs/core/math");
21294
- init_figures6();
22246
+ import_math11 = require("@tradejs/core/math");
22247
+ init_figures7();
21295
22248
  init_setup();
21296
- BREAK_EVEN_TRIGGER_RISK_MULTIPLIER3 = 0.5;
21297
22249
  isFiniteNumber2 = (value) => typeof value === "number" && Number.isFinite(value);
21298
22250
  clamp2 = (value, min, max) => Math.min(max, Math.max(min, value));
21299
- isOpenPosition3 = (position) => Boolean(
22251
+ isOpenPosition4 = (position) => Boolean(
21300
22252
  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")
21301
22253
  );
21302
- getFavorableMovePct3 = ({
21303
- direction,
21304
- entryPrice,
21305
- currentPrice
21306
- }) => {
21307
- if (!Number.isFinite(entryPrice) || !Number.isFinite(currentPrice) || entryPrice <= 0) {
21308
- return null;
21309
- }
21310
- return direction === "LONG" ? (currentPrice - entryPrice) / entryPrice * 100 : (entryPrice - currentPrice) / entryPrice * 100;
21311
- };
21312
- getPositionStopLossPrice3 = (position) => {
21313
- if (!position || typeof position !== "object") {
21314
- return null;
21315
- }
21316
- const slPrice = Number(
21317
- position.slPrice ?? Number.NaN
21318
- );
21319
- if (Number.isFinite(slPrice)) {
21320
- return slPrice;
21321
- }
21322
- const signalStopLossPrice = Number(
21323
- position.signal?.prices?.stopLossPrice ?? Number.NaN
21324
- );
21325
- return Number.isFinite(signalStopLossPrice) ? signalStopLossPrice : null;
21326
- };
21327
- getPositionRiskPct3 = ({
21328
- direction,
21329
- entryPrice,
21330
- stopLossPrice
21331
- }) => {
21332
- if (stopLossPrice == null || !Number.isFinite(entryPrice) || !Number.isFinite(stopLossPrice) || entryPrice <= 0) {
21333
- return null;
21334
- }
21335
- return direction === "LONG" ? (entryPrice - stopLossPrice) / entryPrice * 100 : (stopLossPrice - entryPrice) / entryPrice * 100;
21336
- };
21337
- isBreakEvenStopAlreadyApplied3 = ({
21338
- direction,
21339
- entryPrice,
21340
- stopLossPrice
21341
- }) => {
21342
- if (stopLossPrice == null || !Number.isFinite(entryPrice) || !Number.isFinite(stopLossPrice)) {
21343
- return false;
21344
- }
21345
- return direction === "LONG" ? stopLossPrice >= entryPrice : stopLossPrice <= entryPrice;
21346
- };
21347
22254
  compactQueue = (queue) => {
21348
22255
  if (queue.start <= 1024 || queue.start * 2 <= queue.indices.length) {
21349
22256
  return;
@@ -21653,7 +22560,7 @@ var init_core6 = __esm({
21653
22560
  }
21654
22561
  };
21655
22562
  };
21656
- createVolumeDivergenceCore = async ({ config: config7, strategyApi, indicatorsState, data: initialData }) => {
22563
+ createVolumeDivergenceCore = async ({ config: config8, strategyApi, indicatorsState, data: initialData }) => {
21657
22564
  const {
21658
22565
  NORMALIZATION_LENGTH,
21659
22566
  PIVOT_LOOKBACK_LEFT,
@@ -21668,7 +22575,7 @@ var init_core6 = __esm({
21668
22575
  MAX_LOSS_VALUE,
21669
22576
  BULLISH,
21670
22577
  BEARISH
21671
- } = config7;
22578
+ } = config8;
21672
22579
  const entryThresholds = getVolumeDivergenceEntryThresholds({
21673
22580
  ALLOW_STRUCTURE_ADVANCE_ENTRY,
21674
22581
  MIN_DIVERGENCE_AMPLITUDE_ATR_RATIO,
@@ -21743,33 +22650,7 @@ var init_core6 = __esm({
21743
22650
  indicatorsState.onBar();
21744
22651
  const timestamp = Number(candle.timestamp);
21745
22652
  const currentPosition = await strategyApi.getCurrentPosition();
21746
- if (isOpenPosition3(currentPosition)) {
21747
- const { currentPrice: currentPrice2 } = await strategyApi.getMarketData();
21748
- const activeModeConfig = currentPosition.direction === "LONG" ? BULLISH : BEARISH;
21749
- const currentStopLossPrice = getPositionStopLossPrice3(currentPosition);
21750
- const favorableMovePct = getFavorableMovePct3({
21751
- direction: currentPosition.direction,
21752
- entryPrice: currentPosition.price,
21753
- currentPrice: currentPrice2
21754
- });
21755
- const currentPositionRiskPct = getPositionRiskPct3({
21756
- direction: currentPosition.direction,
21757
- entryPrice: currentPosition.price,
21758
- stopLossPrice: currentStopLossPrice
21759
- });
21760
- if (!isBreakEvenStopAlreadyApplied3({
21761
- direction: currentPosition.direction,
21762
- entryPrice: currentPosition.price,
21763
- stopLossPrice: currentStopLossPrice
21764
- }) && favorableMovePct != null && favorableMovePct >= (currentPositionRiskPct ?? activeModeConfig.SL) * BREAK_EVEN_TRIGGER_RISK_MULTIPLIER3) {
21765
- return strategyApi.protect({
21766
- code: "VOLUME_DIVERGENCE_MOVE_STOP_TO_BREAK_EVEN",
21767
- protectPlan: {
21768
- direction: currentPosition.direction,
21769
- stopLossPrice: currentPosition.price
21770
- }
21771
- });
21772
- }
22653
+ if (isOpenPosition4(currentPosition)) {
21773
22654
  return strategyApi.skip("POSITION_EXISTS");
21774
22655
  }
21775
22656
  if (candleWindow.length < PIVOT_LOOKBACK_LEFT + PIVOT_LOOKBACK_RIGHT + 2) {
@@ -21813,7 +22694,7 @@ var init_core6 = __esm({
21813
22694
  previousPivotLow: nextPendingCandidate.previousPivotLow,
21814
22695
  currentPivotHigh: nextPendingCandidate.currentPivotHigh,
21815
22696
  previousPivotHigh: nextPendingCandidate.previousPivotHigh,
21816
- atrPeriod: config7.ATR
22697
+ atrPeriod: config8.ATR
21817
22698
  });
21818
22699
  if (detectionSetupFeatures.divergenceAmplitudeAtrRatio != null && detectionSetupFeatures.divergenceAmplitudeAtrRatio < entryThresholds.minDivergenceAmplitudeAtrRatio) {
21819
22700
  return strategyApi.skip("WEAK_DIVERGENCE_AMPLITUDE_ATR");
@@ -21854,7 +22735,7 @@ var init_core6 = __esm({
21854
22735
  previousPivotLow: pendingCandidate.previousPivotLow,
21855
22736
  currentPivotHigh: pendingCandidate.currentPivotHigh,
21856
22737
  previousPivotHigh: pendingCandidate.previousPivotHigh,
21857
- atrPeriod: config7.ATR
22738
+ atrPeriod: config8.ATR
21858
22739
  });
21859
22740
  if (setupFeatures.reclaimPct != null && setupFeatures.reclaimPct < entryThresholds.minReclaimPct) {
21860
22741
  return strategyApi.skip("WAIT_CONFIRMATION_RECLAIM");
@@ -21875,7 +22756,7 @@ var init_core6 = __esm({
21875
22756
  return strategyApi.skip("INVALID_QTY");
21876
22757
  }
21877
22758
  if (riskRatio <= modeConfig.minRiskRatio) {
21878
- return strategyApi.skip(`RISK_RATIO:${(0, import_math9.round)(riskRatio)}`);
22759
+ return strategyApi.skip(`RISK_RATIO:${(0, import_math11.round)(riskRatio)}`);
21879
22760
  }
21880
22761
  const indicators = indicatorsState.snapshot();
21881
22762
  const entryPayload = buildEntryPayloadFromPendingCandidate({
@@ -21905,21 +22786,21 @@ var init_core6 = __esm({
21905
22786
  });
21906
22787
 
21907
22788
  // src/VolumeDivergence/strategy.ts
21908
- var strategy_exports6 = {};
21909
- __export(strategy_exports6, {
22789
+ var strategy_exports7 = {};
22790
+ __export(strategy_exports7, {
21910
22791
  VolumeDivergenceStrategyCreator: () => VolumeDivergenceStrategyCreator
21911
22792
  });
21912
- var import_strategies19, VolumeDivergenceStrategyCreator;
21913
- var init_strategy6 = __esm({
22793
+ var import_strategies18, VolumeDivergenceStrategyCreator;
22794
+ var init_strategy7 = __esm({
21914
22795
  "src/VolumeDivergence/strategy.ts"() {
21915
22796
  "use strict";
21916
- import_strategies19 = require("@tradejs/node/strategies");
21917
- init_config6();
21918
- init_core6();
21919
- init_manifest6();
21920
- VolumeDivergenceStrategyCreator = (0, import_strategies19.createStrategyRuntime)({
22797
+ import_strategies18 = require("@tradejs/node/strategies");
22798
+ init_config7();
22799
+ init_core7();
22800
+ init_manifest7();
22801
+ VolumeDivergenceStrategyCreator = (0, import_strategies18.createStrategyRuntime)({
21921
22802
  strategyName: "VolumeDivergence",
21922
- defaults: config6,
22803
+ defaults: config7,
21923
22804
  createCore: createVolumeDivergenceCore,
21924
22805
  manifest: volumeDivergenceManifest,
21925
22806
  strategyDirectory: __dirname
@@ -21931,33 +22812,47 @@ var init_strategy6 = __esm({
21931
22812
  var index_exports = {};
21932
22813
  __export(index_exports, {
21933
22814
  adaptiveMomentumRibbonAiAdapter: () => adaptiveMomentumRibbonAiAdapter,
22815
+ adaptiveMomentumRibbonDefaultConfig: () => config,
21934
22816
  adaptiveMomentumRibbonMlAdapter: () => adaptiveMomentumRibbonMlAdapter,
22817
+ breakoutDefaultConfig: () => config2,
21935
22818
  default: () => index_default,
22819
+ getBuiltInStrategyDefaultConfig: () => getBuiltInStrategyDefaultConfig,
21936
22820
  maStrategyAiAdapter: () => maStrategyAiAdapter,
22821
+ maStrategyDefaultConfig: () => config3,
21937
22822
  maStrategyMlAdapter: () => maStrategyMlAdapter,
21938
22823
  reverseTrendLineAiAdapter: () => reverseTrendLineAiAdapter,
21939
- reverseTrendLineDefaultConfig: () => config2,
22824
+ reverseTrendLineDefaultConfig: () => config4,
21940
22825
  strategyEntries: () => strategyEntries,
21941
- trendLineDefaultConfig: () => config,
22826
+ trendLineDefaultConfig: () => config6,
22827
+ trendShiftAiAdapter: () => trendShiftAiAdapter,
22828
+ trendShiftDefaultConfig: () => config5,
21942
22829
  volumeDivergenceAiAdapter: () => volumeDivergenceAiAdapter,
22830
+ volumeDivergenceDefaultConfig: () => config7,
21943
22831
  volumeDivergenceMlAdapter: () => volumeDivergenceMlAdapter
21944
22832
  });
21945
22833
  module.exports = __toCommonJS(index_exports);
21946
- var import_config7 = require("@tradejs/core/config");
22834
+ var import_config8 = require("@tradejs/core/config");
21947
22835
  init_manifest();
21948
22836
  init_manifest2();
21949
22837
  init_manifest3();
21950
22838
  init_manifest4();
21951
22839
  init_manifest5();
21952
22840
  init_manifest6();
22841
+ init_manifest7();
21953
22842
  init_config();
21954
22843
  init_config2();
22844
+ init_config3();
22845
+ init_config4();
22846
+ init_config5();
22847
+ init_config6();
22848
+ init_config7();
21955
22849
  init_ai();
21956
22850
  init_ml();
21957
22851
  init_ai3();
21958
22852
  init_ml3();
21959
22853
  init_ai4();
21960
- init_ai6();
22854
+ init_ai5();
22855
+ init_ai7();
21961
22856
  init_ml5();
21962
22857
  var createLazyStrategyCreator = (loader, exportName) => {
21963
22858
  return async (params) => {
@@ -21987,46 +22882,70 @@ var strategyEntries = [
21987
22882
  )
21988
22883
  },
21989
22884
  {
21990
- manifest: reverseTrendLineManifest,
22885
+ manifest: trendShiftManifest,
21991
22886
  creator: createLazyStrategyCreator(
21992
22887
  () => Promise.resolve().then(() => (init_strategy3(), strategy_exports3)),
22888
+ "TrendShiftStrategyCreator"
22889
+ )
22890
+ },
22891
+ {
22892
+ manifest: reverseTrendLineManifest,
22893
+ creator: createLazyStrategyCreator(
22894
+ () => Promise.resolve().then(() => (init_strategy4(), strategy_exports4)),
21993
22895
  "ReverseTrendLineStrategyCreator"
21994
22896
  )
21995
22897
  },
21996
22898
  {
21997
22899
  manifest: maStrategyManifest,
21998
22900
  creator: createLazyStrategyCreator(
21999
- () => Promise.resolve().then(() => (init_strategy4(), strategy_exports4)),
22901
+ () => Promise.resolve().then(() => (init_strategy5(), strategy_exports5)),
22000
22902
  "MaStrategyCreator"
22001
22903
  )
22002
22904
  },
22003
22905
  {
22004
22906
  manifest: adaptiveMomentumRibbonManifest,
22005
22907
  creator: createLazyStrategyCreator(
22006
- () => Promise.resolve().then(() => (init_strategy5(), strategy_exports5)),
22908
+ () => Promise.resolve().then(() => (init_strategy6(), strategy_exports6)),
22007
22909
  "AdaptiveMomentumRibbonStrategyCreator"
22008
22910
  )
22009
22911
  },
22010
22912
  {
22011
22913
  manifest: volumeDivergenceManifest,
22012
22914
  creator: createLazyStrategyCreator(
22013
- () => Promise.resolve().then(() => (init_strategy6(), strategy_exports6)),
22915
+ () => Promise.resolve().then(() => (init_strategy7(), strategy_exports7)),
22014
22916
  "VolumeDivergenceStrategyCreator"
22015
22917
  )
22016
22918
  }
22017
22919
  ];
22018
- var index_default = (0, import_config7.defineStrategyPlugin)({ strategyEntries });
22920
+ var builtInStrategyDefaultConfigs = {
22921
+ Breakout: config2,
22922
+ TrendLine: config6,
22923
+ TrendShift: config5,
22924
+ ReverseTrendLine: config4,
22925
+ MaStrategy: config3,
22926
+ AdaptiveMomentumRibbon: config,
22927
+ VolumeDivergence: config7
22928
+ };
22929
+ var getBuiltInStrategyDefaultConfig = (strategyName) => builtInStrategyDefaultConfigs[strategyName];
22930
+ var index_default = (0, import_config8.defineStrategyPlugin)({ strategyEntries });
22019
22931
  // Annotate the CommonJS export names for ESM import in node:
22020
22932
  0 && (module.exports = {
22021
22933
  adaptiveMomentumRibbonAiAdapter,
22934
+ adaptiveMomentumRibbonDefaultConfig,
22022
22935
  adaptiveMomentumRibbonMlAdapter,
22936
+ breakoutDefaultConfig,
22937
+ getBuiltInStrategyDefaultConfig,
22023
22938
  maStrategyAiAdapter,
22939
+ maStrategyDefaultConfig,
22024
22940
  maStrategyMlAdapter,
22025
22941
  reverseTrendLineAiAdapter,
22026
22942
  reverseTrendLineDefaultConfig,
22027
22943
  strategyEntries,
22028
22944
  trendLineDefaultConfig,
22945
+ trendShiftAiAdapter,
22946
+ trendShiftDefaultConfig,
22029
22947
  volumeDivergenceAiAdapter,
22948
+ volumeDivergenceDefaultConfig,
22030
22949
  volumeDivergenceMlAdapter
22031
22950
  });
22032
22951
  /*! Bundled license information: