@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.
@@ -338,29 +338,43 @@ var buildTrendlineTimingContext = ({
338
338
 
339
339
  // src/TrendLine/adapters/ai.ts
340
340
  var TRENDLINE_CONTEXT_PROMPT = `
341
- \u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0434\u043B\u044F trendline-\u0441\u0435\u0442\u0430\u043F\u043E\u0432:
342
- - \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.
343
- - \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.
344
- - \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.
345
- - \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.
346
- - \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.
347
- - \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.
348
- - \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.
349
- - \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".
350
- - \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.
351
- - \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.
352
- - \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.
353
- - \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.
354
- - \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.
355
- - \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.
356
- - \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.
341
+ TrendLine addon:
342
+ - 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.
343
+ - For TrendLine, geometry and price structure have higher priority than indicator confirmation.
344
+ - 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.
345
+ - 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'.
346
+ - 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'.
347
+ - 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.
348
+ - 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.
349
+ - 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.
350
+ - 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.
351
+ - 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.
352
+ - If 'payload.additionalIndicators.trendlineContext.clearBreak=false' and price is still near the line, do not describe it as a clean breakout.
353
+ - 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.
354
+ - 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.
355
+ - 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.
356
+ - 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.
357
+ - 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.
358
+ - 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.
359
+ - 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.
357
360
  `;
358
361
  var TRENDLINE_PAYLOAD_PROMPT = `
359
- - \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.
360
- - \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.
361
- - \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.
362
+ - 'payload.figures.trendline' contains the full trendline geometry without trimming so touches and structure can be evaluated.
363
+ - 'payload.additionalIndicators.trendlineContext' contains 'mode / touches / distance / currentLinePrice / priceVsLinePct / priceVsLineSide / clearBreak / nearLineNoise / coinMaBias / btcMaBias / maxAllowedQuality / approvalAllowedNow / hardBlockReasons'.
364
+ - It also includes 'atrPct / breakVsAtrRatio / coinMaSpreadPct / btcMaSpreadPct / aggressivePreBreakPressure / strongNearBreakPressure / weakCleanBreak / compressedCleanBreak / weakBtcLedBreak / weakLongFarBreak'.
365
+ - 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.
362
366
  `;
363
367
  var buildTrendlineContext = (signal) => {
368
+ const marketContext = signal.additionalIndicators && typeof signal.additionalIndicators.marketContext === "object" && signal.additionalIndicators.marketContext && !Array.isArray(signal.additionalIndicators.marketContext) ? signal.additionalIndicators.marketContext : null;
369
+ const tradingSession = marketContext && typeof marketContext.tradingSession === "object" && marketContext.tradingSession && !Array.isArray(marketContext.tradingSession) ? marketContext.tradingSession : null;
370
+ const sessionPrimary = typeof tradingSession?.primarySession === "string" ? tradingSession.primarySession : null;
371
+ const sessionIsOverlap = tradingSession?.isOverlap === true;
372
+ const derivativesContext = signal.additionalIndicators && typeof signal.additionalIndicators.derivativesContext === "object" && signal.additionalIndicators.derivativesContext && !Array.isArray(signal.additionalIndicators.derivativesContext) ? signal.additionalIndicators.derivativesContext : null;
373
+ const derivativesSummary = derivativesContext && typeof derivativesContext.summary === "object" && derivativesContext.summary && !Array.isArray(derivativesContext.summary) ? derivativesContext.summary : null;
374
+ const derivativesRiskFlags = Array.isArray(derivativesSummary?.riskFlags) ? derivativesSummary.riskFlags.filter(
375
+ (flag) => typeof flag === "string" && flag.length > 0
376
+ ) : [];
377
+ const oiNotConfirming = derivativesRiskFlags.includes("oi_not_confirming");
364
378
  const structural = buildTrendlineStructuralContext(signal);
365
379
  const trendLine = getTrendLineFromPayload(signal);
366
380
  const coinMaFast = getLastFiniteNumber(signal.indicators?.maFast);
@@ -382,6 +396,12 @@ var buildTrendlineContext = (signal) => {
382
396
  if (weakBtcLedBreak) {
383
397
  hardBlockReasons.push("weak_btc_led_break");
384
398
  }
399
+ if (oiNotConfirming && !hardBlockReasons.includes("oi_not_confirming")) {
400
+ hardBlockReasons.push("oi_not_confirming");
401
+ }
402
+ if (structural.signalDirection === "SHORT" && (entryTiming === "ready_follow_through" || entryTiming === "ready_retest") && (sessionPrimary === "off_hours" || sessionIsOverlap) && !hardBlockReasons.includes("short_session_risk")) {
403
+ hardBlockReasons.push("short_session_risk");
404
+ }
385
405
  const deterministicQuality = getDeterministicTrendlineQuality({
386
406
  signalDirection: structural.signalDirection,
387
407
  clearBreak: structural.clearBreak,
@@ -410,6 +430,10 @@ var buildTrendlineContext = (signal) => {
410
430
  aggressivePreBreakPressure,
411
431
  strongNearBreakPressure,
412
432
  weakBtcLedBreak,
433
+ sessionPrimary,
434
+ sessionIsOverlap,
435
+ derivativesRiskFlags,
436
+ oiNotConfirming,
413
437
  deterministicQuality,
414
438
  maxAllowedQuality,
415
439
  approvalAllowedNow,
@@ -425,21 +449,25 @@ var formatPromptNumber = (value, fractionDigits = 4) => {
425
449
  var getHardBlockReasonText = (reason) => {
426
450
  switch (reason) {
427
451
  case "no_clear_break":
428
- return "\u043D\u0435\u0442 \u0447\u0438\u0441\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F \u043B\u0438\u043D\u0438\u0438";
452
+ return "there is no clean breakout of the line";
429
453
  case "near_line_noise":
430
- 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";
454
+ return "price is too close to the line and looks like noise";
431
455
  case "coin_bias_conflict":
432
- 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";
456
+ return "coin bias conflicts with the direction";
433
457
  case "btc_bias_conflict":
434
- 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";
458
+ return "BTC context conflicts with the direction";
435
459
  case "weak_clean_break":
436
- 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";
460
+ return "the formal breakout exists, but displacement is still too weak relative to ATR";
437
461
  case "compressed_clean_break":
438
- 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";
462
+ return "the breakout looks too compressed: clustered close touches on a short line without enough follow-through";
439
463
  case "weak_btc_led_break":
440
- 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";
464
+ return "the breakout is too small relative to ATR and looks more like a BTC-led move without enough coin follow-through";
441
465
  case "weak_long_far_break":
442
- 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";
466
+ return "for LONG, the breakout of the very long line is still too modest and BTC support is too weak";
467
+ case "oi_not_confirming":
468
+ return "open interest does not confirm the move, so the breakout still looks unconfirmed on derivatives context";
469
+ case "short_session_risk":
470
+ return "for SHORT, the current session is too noisy or thin (off-hours or overlap), so clearer follow-through is required";
443
471
  default:
444
472
  return reason;
445
473
  }
@@ -474,7 +502,8 @@ var getDeterministicTrendlineQuality = (trendlineContext) => {
474
502
  const shortLineStrengthQuality4 = breakVsAtrRatio >= 0.6 && priceVsLinePctAbs >= 0.65 && touches >= 6 && distance < 120 && btcMaSpreadPct >= 0.75;
475
503
  const matureLineQuality4 = breakVsAtrRatio >= 0.8 && priceVsLinePctAbs >= 0.7 && touches >= 5 && distance < 350 && btcMaSpreadPct >= 0.4;
476
504
  const extendedHighConvictionQuality4 = breakVsAtrRatio >= 0.75 && priceVsLinePctAbs >= 0.65 && touches >= 5 && distance < 600 && btcMaSpreadPct >= 0.9;
477
- return compactBreakoutQuality4 || shortLineStrengthQuality4 || matureLineQuality4 || extendedHighConvictionQuality4 ? 4 : 3;
505
+ 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;
506
+ return compactBreakoutQuality4 || shortLineStrengthQuality4 || matureLineQuality4 || extendedHighConvictionQuality4 || alignedRecentFollowThroughQuality4 ? 4 : 3;
478
507
  }
479
508
  const quality5 = breakVsAtrRatio >= 5 && priceVsLinePctAbs >= 10 && touches >= 5 && distance >= 240 && distance <= 400 && btcMaSpreadPct <= -1;
480
509
  if (quality5) {
@@ -482,19 +511,23 @@ var getDeterministicTrendlineQuality = (trendlineContext) => {
482
511
  }
483
512
  const quality4 = breakVsAtrRatio >= 1 && breakVsAtrRatio < 2.5 && priceVsLinePctAbs >= 1 && touches >= 5 && distance < 300 && btcMaSpreadPct <= -0.5;
484
513
  const strongReadyBreakoutQuality4 = entryTiming === "ready_breakout" && breakVsAtrRatio >= 2 && priceVsLinePctAbs >= 1.8 && touches >= 5 && btcMaSpreadPct <= -1 && (coinMaSpreadPct <= -1 || breakVsAtrRatio >= 3);
485
- return quality4 || strongReadyBreakoutQuality4 ? 4 : 3;
514
+ 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;
515
+ if ((quality4 || moderateReadyBreakoutQuality4) && entryTiming === "ready_breakout") {
516
+ return 4;
517
+ }
518
+ return strongReadyBreakoutQuality4 ? 4 : 3;
486
519
  };
487
520
  var getDeterministicTrendlineQualityReason = (trendlineContext) => {
488
521
  if (trendlineContext.hardBlockReasons.length > 0) {
489
- 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(getHardBlockReasonText).join("; ")}.`;
522
+ return `TrendLine guardrail: entry is blocked because ${trendlineContext.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.`;
490
523
  }
491
524
  if (trendlineContext.signalDirection === "LONG") {
492
- 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.";
525
+ return "TrendLine deterministic quality: the breakout exists, but LONG still lacks enough displacement, BTC support, or a compact enough line for immediate entry.";
493
526
  }
494
527
  if (trendlineContext.signalDirection === "SHORT") {
495
- 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.";
528
+ return "TrendLine deterministic quality: the breakout exists, but SHORT still lacks enough bearish displacement or follow-through, so entry is still too early.";
496
529
  }
497
- 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.";
530
+ return "TrendLine deterministic quality: the structure is still not strong enough for entry right now.";
498
531
  };
499
532
  var getTrendlineContextFromPayload = (payload, signal) => {
500
533
  const additional = payload.additionalIndicators;
@@ -503,25 +536,35 @@ var getTrendlineContextFromPayload = (payload, signal) => {
503
536
  };
504
537
  var trendLineAiAdapter = {
505
538
  // Shared builder trims nested series/figures; TrendLine keeps trendline geometry untrimmed on purpose.
506
- buildPayload: ({ signal, basePayload }) => ({
507
- ...basePayload,
508
- figures: {
509
- ...basePayload.figures,
510
- // Keep raw line geometry available exactly where the shared prompt expects it.
511
- trendline: getTrendLineFromPayload(signal)
512
- },
513
- additionalIndicators: {
514
- ...basePayload.additionalIndicators,
515
- trendlineContext: buildTrendlineContext(signal)
516
- }
517
- }),
539
+ buildPayload: ({ signal, basePayload }) => {
540
+ const mergedAdditionalIndicators = {
541
+ ...signal.additionalIndicators ?? {},
542
+ ...basePayload.additionalIndicators ?? {}
543
+ };
544
+ const trendlineContext = buildTrendlineContext({
545
+ ...signal,
546
+ additionalIndicators: mergedAdditionalIndicators
547
+ });
548
+ return {
549
+ ...basePayload,
550
+ figures: {
551
+ ...basePayload.figures,
552
+ // Keep raw line geometry available exactly where the shared prompt expects it.
553
+ trendline: getTrendLineFromPayload(signal)
554
+ },
555
+ additionalIndicators: {
556
+ ...mergedAdditionalIndicators,
557
+ trendlineContext
558
+ }
559
+ };
560
+ },
518
561
  postProcessAnalysis: ({ signal, payload, analysis }) => {
519
562
  const trendlineContext = getTrendlineContextFromPayload(payload, signal);
520
563
  const quality = trendlineContext.deterministicQuality;
521
564
  const signalDirection = signal.direction === "LONG" || signal.direction === "SHORT" ? signal.direction : null;
522
565
  if ((trendlineContext.aggressivePreBreakPressure === true || trendlineContext.strongNearBreakPressure === true) && signalDirection != null) {
523
- 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.";
524
- 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.";
566
+ 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.";
567
+ 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.";
525
568
  return {
526
569
  ...analysis,
527
570
  direction: signalDirection,
@@ -552,17 +595,17 @@ var trendLineAiAdapter = {
552
595
  const retestPrice = trendlineContext.currentLinePrice ?? analysis.retestPrice ?? null;
553
596
  const qualityReason = mergeShortText(
554
597
  getDeterministicTrendlineQualityReason(trendlineContext),
555
- "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.",
598
+ "TrendLine guardrail: entry is blocked until the structure is confirmed.",
556
599
  400
557
600
  );
558
601
  const triggerInvalidation = mergeShortText(
559
- 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(getHardBlockReasonText).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.",
560
- "\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.",
602
+ trendlineContext.hardBlockReasons.length > 0 ? `Wait for a clean breakout or retest of the line and resolve the conflicts: ${trendlineContext.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "Wait for stronger breakout follow-through or a line retest confirmed by both the coin and BTC.",
603
+ "Wait for a clean line breakout or retest plus confirmation from the coin and BTC.",
561
604
  400
562
605
  );
563
606
  const comment = mergeShortText(
564
- 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(getHardBlockReasonText).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.",
565
- "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.",
607
+ trendlineContext.hardBlockReasons.length > 0 ? `TrendLine guardrail blocked the entry: ${trendlineContext.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "TrendLine deterministic quality downgraded the entry to watch or reject until stronger structure appears.",
608
+ "TrendLine guardrail blocked the entry until the structure is confirmed.",
566
609
  1024
567
610
  );
568
611
  return {
@@ -575,12 +618,12 @@ var trendLineAiAdapter = {
575
618
  stopLossPrice: null,
576
619
  setup: mergeShortText(
577
620
  analysis.setup ?? "",
578
- "\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.",
621
+ "There is no confirmed trendline breakout or retest for entry right now.",
579
622
  400
580
623
  ),
581
624
  retestPlan: mergeShortText(
582
625
  analysis.retestPlan ?? "",
583
- "\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.",
626
+ "Wait for a return to the line and a reaction in the trade direction before a new entry.",
584
627
  400
585
628
  ),
586
629
  qualityReason,
@@ -600,7 +643,7 @@ ${TRENDLINE_PAYLOAD_PROMPT}
600
643
  }
601
644
  return `
602
645
 
603
- \u0414\u043E\u043F. \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 TrendLine:
646
+ Additional TrendLine context:
604
647
  - trendline.mode=${trendlineContext.mode ?? "n/a"}
605
648
  - trendline.touches=${formatPromptNumber(trendlineContext.touches, 0)}
606
649
  - trendline.distance=${formatPromptNumber(trendlineContext.distance, 0)}
@@ -634,18 +677,18 @@ ${TRENDLINE_PAYLOAD_PROMPT}
634
677
  - btc.maSpreadPct=${formatPromptNumber(trendlineContext.btcMaSpreadPct, 3)}%
635
678
  - btc.biasAligned=${String(trendlineContext.btcBiasAligned)}
636
679
 
637
- \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u0438\u043D\u0442\u0435\u0440\u043F\u0440\u0435\u0442\u0430\u0446\u0438\u0438 \u0434\u043B\u044F TrendLine:
638
- - 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.
639
- - 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.
640
- - \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.
641
- - \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.
642
- - \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.
643
- - \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.
644
- - \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.
645
- - \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.
646
- - \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.
647
- - \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.
648
- - \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.
680
+ Interpretation rules for TrendLine:
681
+ - SHORT from a 'lows' line is confirmed only by a clear move below the line or a retest from below with rejection.
682
+ - LONG from a 'highs' line is confirmed only by a clear move above the line or a retest from above with rejection.
683
+ - 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.
684
+ - 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.
685
+ - 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.
686
+ - 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.
687
+ - If 'clearBreak=false' or any alignment is false, do not raise quality above 3.
688
+ - 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.
689
+ - 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.
690
+ - 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.
691
+ - If 'trendline.approvalAllowedNow=false', do not describe the signal as fully confirmed right now; explain what is still missing for confirmation.
649
692
  `;
650
693
  },
651
694
  mapEntryRuntimeFromConfig: (config2) => mapAiRuntimeFromConfig(
@@ -689,18 +732,9 @@ var trendLineMlAdapter = {
689
732
  })
690
733
  };
691
734
 
692
- // src/TrendLine/hooks.ts
693
- import { createCloseOppositeBeforePlaceOrderHook } from "@tradejs/node/strategies";
694
- var trendLineBeforePlaceOrderHook = createCloseOppositeBeforePlaceOrderHook({
695
- isEnabled: (config2) => Boolean(config2.CLOSE_OPPOSITE_POSITIONS)
696
- });
697
-
698
735
  // src/TrendLine/manifest.ts
699
736
  var trendLineManifest = {
700
737
  name: "TrendLine",
701
- hooks: {
702
- beforePlaceOrder: trendLineBeforePlaceOrderHook
703
- },
704
738
  aiAdapter: trendLineAiAdapter,
705
739
  mlAdapter: trendLineMlAdapter
706
740
  };
@@ -713,6 +747,7 @@ var config = {
713
747
  CLOSE_OPPOSITE_POSITIONS: false,
714
748
  BACKTEST_PRICE_MODE: "mid",
715
749
  AI_ENABLED: false,
750
+ AI_MODE: "llm",
716
751
  ML_ENABLED: false,
717
752
  ML_THRESHOLD: 0.1,
718
753
  MIN_AI_QUALITY: 3,
@@ -387,23 +387,23 @@ var buildReverseTrendlineTimingContext = ({
387
387
 
388
388
  // src/ReverseTrendLine/adapters/ai.ts
389
389
  var REVERSE_TRENDLINE_CONTEXT_PROMPT = `
390
- \u0414\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0434\u043B\u044F ReverseTrendLine:
391
- - \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.
392
- - \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.
393
- - \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.
394
- - \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.
395
- - \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.
396
- - \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.
397
- - \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.
398
- - \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:
399
- - \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;
400
- - \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.
401
- - \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.
402
- - \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.
390
+ ReverseTrendLine addon:
391
+ - This is a trendline bounce strategy, not a breakout strategy.
392
+ - 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.
393
+ - 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.
394
+ - 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\`.
395
+ - For bounce setups, prioritize candle reaction at the line, rejection wick quality, a close on the correct side, and next-bar follow-through.
396
+ - If \`payload.additionalIndicators.reverseTrendlineContext.failedBounceBreak=true\`, do not treat the signal as structurally confirmed.
397
+ - If \`payload.additionalIndicators.reverseTrendlineContext.entryTiming\` is not \`ready_rejection\` or \`ready_follow_through\`, quality is usually \`<= 3\`.
398
+ - Baseline deterministic approval for same-bar rejection is intentionally strict:
399
+ - a strong conflict-only rejection may qualify for \`quality=4\`;
400
+ - some same-bar rejections with \`conflictState=none\` or \`both\` may reach \`quality=4\` only with a very strong deterministic rejection score.
401
+ - 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.
402
+ - If \`deterministicRejectionScore\` is low or medium, do not assign \`quality=4\` just because the candle visually resembles a rejection.
403
403
  `;
404
404
  var REVERSE_TRENDLINE_PAYLOAD_PROMPT = `
405
- - \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.
406
- - \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.
405
+ - \`payload.figures.trendline\` contains the line geometry.
406
+ - \`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\`.
407
407
  `;
408
408
  var getReverseTrendlineBiasConflictState = (context) => {
409
409
  const coinConflict = context.coinBiasAligned === false;
@@ -444,12 +444,16 @@ var getDeterministicReverseTrendlineQuality = (context) => {
444
444
  if (quality4FollowThrough) {
445
445
  return 4;
446
446
  }
447
- const quality4ConflictRejection = context.entryTiming === "ready_rejection" && conflictOnly && rejectionStrengthPct >= 0.45 && touches >= 5 && !(context.signalDirection === "SHORT" && biasConflictState === "btc_only");
447
+ 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");
448
448
  if (quality4ConflictRejection) {
449
449
  return 4;
450
450
  }
451
451
  const rejectionScore = getDeterministicReverseTrendlineRejectionScore(context);
452
- const quality4ScoredRejection = context.entryTiming === "ready_rejection" && (biasConflictState === "none" || biasConflictState === "both") && rejectionScore != null && rejectionScore >= 7;
452
+ const quality4EliteShortBtcOnlyRejection = context.entryTiming === "ready_rejection" && context.signalDirection === "SHORT" && biasConflictState === "btc_only" && rejectionScore != null && rejectionScore >= 5 && rejectionWickPct >= 0.6 && touches >= 5 && distance <= 200;
453
+ if (quality4EliteShortBtcOnlyRejection) {
454
+ return 4;
455
+ }
456
+ 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));
453
457
  if (quality4ScoredRejection) {
454
458
  return 4;
455
459
  }
@@ -536,11 +540,11 @@ var getReverseTrendlineContextFromPayload = (payload, signal) => {
536
540
  var getHardBlockReasonText = (reason) => {
537
541
  switch (reason) {
538
542
  case "failed_bounce_break":
539
- 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";
543
+ return "price broke through the line against the intended bounce";
540
544
  case "coin_bias_conflict":
541
- 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";
545
+ return "coin bias conflicts with the bounce direction";
542
546
  case "btc_bias_conflict":
543
- 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";
547
+ return "BTC context conflicts with the bounce direction";
544
548
  default:
545
549
  return reason;
546
550
  }
@@ -579,9 +583,9 @@ var reverseTrendLineAiAdapter = {
579
583
  retestPrice: context.currentLinePrice ?? null,
580
584
  takeProfitPrice: null,
581
585
  stopLossPrice: null,
582
- qualityReason: context.hardBlockReasons.length > 0 ? `ReverseTrendLine guardrail: ${context.hardBlockReasons.map(getHardBlockReasonText).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.",
583
- triggerInvalidation: context.hardBlockReasons.length > 0 ? `\u0416\u0434\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0439 bounce setup: ${context.hardBlockReasons.map(getHardBlockReasonText).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.",
584
- 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(getHardBlockReasonText).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."
586
+ qualityReason: context.hardBlockReasons.length > 0 ? `ReverseTrendLine guardrail: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "ReverseTrendLine deterministic quality requires either a strong conflict-only rejection or a confirmed aligned follow-through for a bounce.",
587
+ triggerInvalidation: context.hardBlockReasons.length > 0 ? `Wait for a new bounce setup: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "Wait for a line touch, a rejection candle, and a close held on the correct side of the line.",
588
+ comment: context.hardBlockReasons.length > 0 ? `ReverseTrendLine guardrail blocked the entry: ${context.hardBlockReasons.map(getHardBlockReasonText).join("; ")}.` : "ReverseTrendLine keeps the setup in watch mode until the bounce is confirmed."
585
589
  };
586
590
  },
587
591
  buildSystemPromptAddon: () => `${REVERSE_TRENDLINE_CONTEXT_PROMPT}
@@ -590,7 +594,7 @@ ${REVERSE_TRENDLINE_PAYLOAD_PROMPT}`,
590
594
  const context = getReverseTrendlineContextFromPayload(payload, signal);
591
595
  return `
592
596
 
593
- \u0414\u043E\u043F. \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 ReverseTrendLine:
597
+ Additional ReverseTrendLine context:
594
598
  - entryTiming=${context.entryTiming}
595
599
  - lineTouchedNow=${context.lineTouchedNow}
596
600
  - closeOnBounceSide=${context.closeOnBounceSide}
@@ -605,11 +609,11 @@ ${REVERSE_TRENDLINE_PAYLOAD_PROMPT}`,
605
609
  - approvalAllowedNow=${context.approvalAllowedNow}
606
610
  - hardBlockReasons=${context.hardBlockReasons.join(", ") || "none"}
607
611
 
608
- \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u0438\u043D\u0442\u0435\u0440\u043F\u0440\u0435\u0442\u0430\u0446\u0438\u0438 \u0434\u043B\u044F ReverseTrendLine:
609
- - \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;
610
- - \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;
611
- - \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.
612
- - \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.
612
+ Interpretation rules for ReverseTrendLine:
613
+ - look for structural confirmation of a reaction from the line, not a breakout through the line;
614
+ - if \`failedBounceBreak=true\` is already present, do not treat the signal as confirmed;
615
+ - if the setup is still in \`wait_touch\`, \`wait_reaction_confirmation\`, or \`stale_reaction\`, do not overstate quality;
616
+ - if \`deterministicRejectionScore\` is high, use it only as an extra signal together with the proper bounce context, not as a replacement for structure.
613
617
  `;
614
618
  },
615
619
  mapEntryRuntimeFromConfig: (config2) => mapAiRuntimeFromConfig(
@@ -617,18 +621,9 @@ ${REVERSE_TRENDLINE_PAYLOAD_PROMPT}`,
617
621
  )
618
622
  };
619
623
 
620
- // src/ReverseTrendLine/hooks.ts
621
- import { createCloseOppositeBeforePlaceOrderHook } from "@tradejs/node/strategies";
622
- var reverseTrendLineBeforePlaceOrderHook = createCloseOppositeBeforePlaceOrderHook({
623
- isEnabled: (config2) => Boolean(config2.CLOSE_OPPOSITE_POSITIONS)
624
- });
625
-
626
624
  // src/ReverseTrendLine/manifest.ts
627
625
  var reverseTrendLineManifest = {
628
626
  name: "ReverseTrendLine",
629
- hooks: {
630
- beforePlaceOrder: reverseTrendLineBeforePlaceOrderHook
631
- },
632
627
  aiAdapter: reverseTrendLineAiAdapter
633
628
  };
634
629
 
@@ -640,6 +635,7 @@ var config = {
640
635
  CLOSE_OPPOSITE_POSITIONS: false,
641
636
  BACKTEST_PRICE_MODE: "mid",
642
637
  AI_ENABLED: false,
638
+ AI_MODE: "llm",
643
639
  MIN_AI_QUALITY: 3,
644
640
  FEE_PERCENT: 5e-3,
645
641
  MAX_LOSS_VALUE: 10,