@tradejs/node 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -36,9 +36,9 @@ var require_lodash = __commonJS({
36
36
  "use strict";
37
37
  (function() {
38
38
  var undefined2;
39
- var VERSION = "4.17.21";
39
+ var VERSION = "4.18.1";
40
40
  var LARGE_ARRAY_SIZE = 200;
41
- 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`";
41
+ 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`";
42
42
  var HASH_UNDEFINED = "__lodash_hash_undefined__";
43
43
  var MAX_MEMOIZE_SIZE = 500;
44
44
  var PLACEHOLDER = "__lodash_placeholder__";
@@ -1964,8 +1964,21 @@ var require_lodash = __commonJS({
1964
1964
  }
1965
1965
  function baseUnset(object, path3) {
1966
1966
  path3 = castPath(path3, object);
1967
- object = parent(object, path3);
1968
- return object == null || delete object[toKey(last(path3))];
1967
+ var index = -1, length = path3.length;
1968
+ if (!length) {
1969
+ return true;
1970
+ }
1971
+ while (++index < length) {
1972
+ var key = toKey(path3[index]);
1973
+ if (key === "__proto__" && !hasOwnProperty.call(object, "__proto__")) {
1974
+ return false;
1975
+ }
1976
+ if ((key === "constructor" || key === "prototype") && index < length - 1) {
1977
+ return false;
1978
+ }
1979
+ }
1980
+ var obj = parent(object, path3);
1981
+ return obj == null || delete obj[toKey(last(path3))];
1969
1982
  }
1970
1983
  function baseUpdate(object, path3, updater, customizer) {
1971
1984
  return baseSet(object, path3, updater(baseGet(object, path3)), customizer);
@@ -3292,7 +3305,7 @@ var require_lodash = __commonJS({
3292
3305
  var index = -1, length = pairs == null ? 0 : pairs.length, result2 = {};
3293
3306
  while (++index < length) {
3294
3307
  var pair = pairs[index];
3295
- result2[pair[0]] = pair[1];
3308
+ baseAssignValue(result2, pair[0], pair[1]);
3296
3309
  }
3297
3310
  return result2;
3298
3311
  }
@@ -4676,8 +4689,13 @@ var require_lodash = __commonJS({
4676
4689
  options = undefined2;
4677
4690
  }
4678
4691
  string = toString(string);
4679
- options = assignInWith({}, options, settings, customDefaultsAssignIn);
4680
- var imports = assignInWith({}, options.imports, settings.imports, customDefaultsAssignIn), importsKeys = keys(imports), importsValues = baseValues(imports, importsKeys);
4692
+ options = assignWith({}, options, settings, customDefaultsAssignIn);
4693
+ var imports = assignWith({}, options.imports, settings.imports, customDefaultsAssignIn), importsKeys = keys(imports), importsValues = baseValues(imports, importsKeys);
4694
+ arrayEach(importsKeys, function(key) {
4695
+ if (reForbiddenIdentifierChars.test(key)) {
4696
+ throw new Error2(INVALID_TEMPL_IMPORTS_ERROR_TEXT);
4697
+ }
4698
+ });
4681
4699
  var isEscaping, isEvaluating, index = 0, interpolate = options.interpolate || reNoMatch, source = "__p += '";
4682
4700
  var reDelimiters = RegExp2(
4683
4701
  (options.escape || reNoMatch).source + "|" + interpolate.source + "|" + (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + "|" + (options.evaluate || reNoMatch).source + "|$",
@@ -5518,7 +5536,9 @@ __export(cli_exports, {
5518
5536
  cleanRedis: () => cleanRedis,
5519
5537
  drawStatInCLI: () => drawStatInCLI,
5520
5538
  getTickers: () => getTickers,
5539
+ loadTradejsConfig: () => loadTradejsConfig,
5521
5540
  makeScreenshots: () => makeScreenshots,
5541
+ sendTextToTG: () => sendTextToTG,
5522
5542
  sendToAI: () => sendToAI,
5523
5543
  sendToTG: () => sendToTG,
5524
5544
  update: () => update
@@ -5541,7 +5561,7 @@ var getPositiveIntegerEnv = (name, fallback) => {
5541
5561
  const parsedValue = Number(rawValue);
5542
5562
  return Number.isInteger(parsedValue) && parsedValue > 0 ? parsedValue : fallback;
5543
5563
  };
5544
- var DEFAULT_KLINE_CONCURRENCY_LIMIT = NODE_ENV === "production" ? 1 : 10;
5564
+ var DEFAULT_KLINE_CONCURRENCY_LIMIT = NODE_ENV === "production" ? 5 : 10;
5545
5565
  var KLINE_CONCURRENCY_LIMIT = getPositiveIntegerEnv(
5546
5566
  "KLINE_CONCURRENCY_LIMIT",
5547
5567
  DEFAULT_KLINE_CONCURRENCY_LIMIT
@@ -5551,10 +5571,11 @@ var SCREENSHOT_CONCURRENCY_LIMIT = NODE_ENV === "production" ? 1 : 2;
5551
5571
 
5552
5572
  // src/cli.ts
5553
5573
  var import_files = require("@tradejs/infra/files");
5554
- var import_redis2 = require("@tradejs/infra/redis");
5574
+ var import_redis3 = require("@tradejs/infra/redis");
5555
5575
  var import_logger5 = require("@tradejs/infra/logger");
5556
5576
 
5557
5577
  // src/ai.ts
5578
+ var import_aiLanguages = require("@tradejs/infra/aiLanguages");
5558
5579
  var import_redis = require("@tradejs/infra/redis");
5559
5580
  var import_userSettings = require("@tradejs/infra/userSettings");
5560
5581
 
@@ -5582,6 +5603,109 @@ var trimSeriesDeep = (value) => {
5582
5603
  return value;
5583
5604
  };
5584
5605
 
5606
+ // src/aiMarketContext.ts
5607
+ var SESSION_WINDOWS = [
5608
+ { name: "asia", startMinuteUtc: 0, endMinuteUtc: 8 * 60 },
5609
+ { name: "europe", startMinuteUtc: 7 * 60, endMinuteUtc: 16 * 60 },
5610
+ { name: "us", startMinuteUtc: 13 * 60, endMinuteUtc: 22 * 60 }
5611
+ ];
5612
+ var toRecord = (value) => {
5613
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5614
+ return null;
5615
+ }
5616
+ return value;
5617
+ };
5618
+ var toFiniteNumber = (value) => {
5619
+ const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
5620
+ return Number.isFinite(numeric) ? numeric : null;
5621
+ };
5622
+ var getLastFiniteNumber = (value) => {
5623
+ const numeric = toFiniteNumber(value);
5624
+ if (numeric != null) {
5625
+ return numeric;
5626
+ }
5627
+ if (!Array.isArray(value)) {
5628
+ return null;
5629
+ }
5630
+ for (let i = value.length - 1; i >= 0; i -= 1) {
5631
+ const nested = getLastFiniteNumber(value[i]);
5632
+ if (nested != null) {
5633
+ return nested;
5634
+ }
5635
+ }
5636
+ return null;
5637
+ };
5638
+ var roundTo = (value, decimals) => {
5639
+ const factor = 10 ** decimals;
5640
+ return Math.round(value * factor) / factor;
5641
+ };
5642
+ var isInsideSession = (minuteUtc, startMinuteUtc, endMinuteUtc) => startMinuteUtc <= endMinuteUtc ? minuteUtc >= startMinuteUtc && minuteUtc < endMinuteUtc : minuteUtc >= startMinuteUtc || minuteUtc < endMinuteUtc;
5643
+ var buildTradingSessionContext = (timestamp) => {
5644
+ const date = new Date(timestamp);
5645
+ const utcHour = date.getUTCHours();
5646
+ const utcMinute = date.getUTCMinutes();
5647
+ const minuteUtc = utcHour * 60 + utcMinute;
5648
+ const activeSessions = SESSION_WINDOWS.filter(
5649
+ (session) => isInsideSession(minuteUtc, session.startMinuteUtc, session.endMinuteUtc)
5650
+ ).map((session) => session.name);
5651
+ const primarySession = activeSessions.includes("us") ? "us" : activeSessions.includes("europe") ? "europe" : activeSessions.includes("asia") ? "asia" : "off_hours";
5652
+ return {
5653
+ timezone: "UTC",
5654
+ utcHour,
5655
+ utcMinute,
5656
+ primarySession,
5657
+ activeSessions,
5658
+ isOverlap: activeSessions.length > 1,
5659
+ overlap: activeSessions.length > 1 ? `${activeSessions.join("_")}_overlap` : null
5660
+ };
5661
+ };
5662
+ var buildMissingSpreadContext = () => ({
5663
+ source: "binance_coinbase_btc",
5664
+ indicatorKey: "payload.indicators.spread",
5665
+ available: false,
5666
+ value: null,
5667
+ bps: null,
5668
+ absBps: null,
5669
+ bias: null,
5670
+ severity: null
5671
+ });
5672
+ var buildSpreadContextFromValue = (spread) => {
5673
+ const value = roundTo(spread, 8);
5674
+ const bps = roundTo(value * 1e4, 2);
5675
+ const absBps = Math.abs(bps);
5676
+ const bias = Math.abs(bps) < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
5677
+ const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
5678
+ return {
5679
+ source: "binance_coinbase_btc",
5680
+ indicatorKey: "payload.indicators.spread",
5681
+ available: true,
5682
+ value,
5683
+ bps,
5684
+ absBps,
5685
+ bias,
5686
+ severity
5687
+ };
5688
+ };
5689
+ var readSpreadFromSignal = (signal) => {
5690
+ const indicatorSpread = getLastFiniteNumber(signal.indicators?.spread);
5691
+ if (indicatorSpread != null) {
5692
+ return indicatorSpread;
5693
+ }
5694
+ return getLastFiniteNumber(signal.additionalIndicators?.spread);
5695
+ };
5696
+ var buildAiMarketContext = (signal) => {
5697
+ const existingMarketContext = toRecord(
5698
+ signal.additionalIndicators?.marketContext
5699
+ );
5700
+ const existingSpread = toRecord(existingMarketContext?.binanceCoinbaseSpread);
5701
+ const spread = readSpreadFromSignal(signal);
5702
+ return {
5703
+ ...existingMarketContext ?? {},
5704
+ tradingSession: buildTradingSessionContext(signal.timestamp),
5705
+ binanceCoinbaseSpread: spread != null ? buildSpreadContextFromValue(spread) : existingSpread ?? buildMissingSpreadContext()
5706
+ };
5707
+ };
5708
+
5585
5709
  // src/strategy/manifests.ts
5586
5710
  var import_indicators = require("@tradejs/core/indicators");
5587
5711
  var import_logger2 = require("@tradejs/infra/logger");
@@ -5591,6 +5715,7 @@ var import_fs = __toESM(require("fs"));
5591
5715
  var import_path = __toESM(require("path"));
5592
5716
  var import_module = require("module");
5593
5717
  var import_url = require("url");
5718
+ var import_config = require("@tradejs/core/config");
5594
5719
  var import_logger = require("@tradejs/infra/logger");
5595
5720
  var CONFIG_FILE_NAMES = [
5596
5721
  "tradejs.config.ts",
@@ -5623,10 +5748,14 @@ var normalizeConfig = (rawConfig) => {
5623
5748
  const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
5624
5749
  const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
5625
5750
  const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
5751
+ const hooks = (0, import_config.normalizeTradejsConfigHooks)(
5752
+ config.hooks
5753
+ );
5626
5754
  return {
5627
5755
  strategies: strategies2,
5628
5756
  indicators,
5629
- connectors
5757
+ connectors,
5758
+ ...hooks ? { hooks } : {}
5630
5759
  };
5631
5760
  };
5632
5761
  var getRequireFn = (cwd = getTradejsProjectCwd()) => (0, import_module.createRequire)(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
@@ -5996,24 +6125,36 @@ var strategies = new Proxy(
5996
6125
  );
5997
6126
 
5998
6127
  // src/strategyAdapters/ai.ts
5999
- var buildBaseAiPayload = (signal) => ({
6000
- signal: {
6001
- symbol: signal.symbol,
6002
- signalId: signal.signalId,
6003
- interval: signal.interval,
6004
- direction: signal.direction,
6005
- timestamp: signal.timestamp,
6006
- strategy: signal.strategy,
6007
- prices: {
6008
- currentPrice: signal.prices.currentPrice,
6009
- takeProfitPrice: signal.prices.takeProfitPrice,
6010
- stopLossPrice: signal.prices.stopLossPrice
6011
- }
6012
- },
6013
- figures: trimSeriesDeep(signal.figures ?? {}),
6014
- indicators: trimSeriesDeep(signal.indicators),
6015
- additionalIndicators: trimSeriesDeep(signal.additionalIndicators ?? {})
6016
- });
6128
+ var toRecord2 = (value) => {
6129
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
6130
+ return {};
6131
+ }
6132
+ return value;
6133
+ };
6134
+ var buildBaseAiPayload = (signal) => {
6135
+ const additionalIndicators = {
6136
+ ...toRecord2(signal.additionalIndicators),
6137
+ marketContext: buildAiMarketContext(signal)
6138
+ };
6139
+ return {
6140
+ signal: {
6141
+ symbol: signal.symbol,
6142
+ signalId: signal.signalId,
6143
+ interval: signal.interval,
6144
+ direction: signal.direction,
6145
+ timestamp: signal.timestamp,
6146
+ strategy: signal.strategy,
6147
+ prices: {
6148
+ currentPrice: signal.prices.currentPrice,
6149
+ takeProfitPrice: signal.prices.takeProfitPrice,
6150
+ stopLossPrice: signal.prices.stopLossPrice
6151
+ }
6152
+ },
6153
+ figures: trimSeriesDeep(signal.figures ?? {}),
6154
+ indicators: trimSeriesDeep(signal.indicators),
6155
+ additionalIndicators: trimSeriesDeep(additionalIndicators)
6156
+ };
6157
+ };
6017
6158
  var defaultAiAdapter = {};
6018
6159
  var getStrategyAiAdapter = (strategy) => getStrategyManifest(strategy)?.aiAdapter ?? defaultAiAdapter;
6019
6160
  var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy);
@@ -6041,8 +6182,8 @@ var parseAIResponse = (input) => {
6041
6182
  if (!match) throw new Error("JSON block not found");
6042
6183
  return JSON.parse(match[0]);
6043
6184
  } catch (err) {
6044
- console.error("\u274C \u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0430\u0440\u0441\u0438\u043D\u0433\u0430 AI-\u043E\u0442\u0432\u0435\u0442\u0430:", err);
6045
- console.log("\u{1F50D} \u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u0442\u0435\u043A\u0441\u0442:", input);
6185
+ console.error("Failed to parse AI response:", err);
6186
+ console.log("Raw AI response:", input);
6046
6187
  return {};
6047
6188
  }
6048
6189
  };
@@ -6088,22 +6229,22 @@ var normalizeAnalysis = (raw) => {
6088
6229
  };
6089
6230
  };
6090
6231
  var buildAiSystemPrompt = (signal) => `
6091
- \u0422\u044B \u2014 \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u0438\u0439 \u043A\u043B\u0430\u0441\u0441\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B \u0440\u044B\u043D\u043A\u0430 \u0434\u043B\u044F \u0443\u0436\u0435 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043D\u043D\u043E\u0433\u043E \u0441\u0438\u0441\u0442\u0435\u043C\u043D\u043E\u0433\u043E \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
6092
- \u0410\u043D\u0430\u043B\u0438\u0437\u0438\u0440\u0443\u0439 \u043F\u0440\u0438\u0441\u043B\u0430\u043D\u043D\u044B\u0439 JSON \u0441\u043E \u0441\u0434\u0435\u043B\u043A\u043E\u0439, \u0441\u0432\u0435\u0447\u0430\u043C\u0438, \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u0430\u043C\u0438 (\u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0438 BTC \u043D\u0430 \u0440\u0430\u0437\u043D\u044B\u0445 \u0422\u0424) \u0438 \u0444\u0438\u0433\u0443\u0440\u0430\u043C\u0438/\u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438.
6093
- \u0414\u0430\u043D\u043D\u044B\u0435 \u0440\u044F\u0434\u043E\u0432 \u0443\u0436\u0435 \u0443\u043A\u043E\u0440\u043E\u0447\u0435\u043D\u044B \u0434\u043E \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0445 5 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0439.
6232
+ You are an internal market-structure classifier for an already computed system signal.
6233
+ Analyze the provided JSON containing the trade, candles, indicators (for the coin and BTC across multiple timeframes), and strategy figures/context.
6234
+ Series data is already trimmed to the latest 5 values.
6094
6235
 
6095
- \u0412\u0410\u0416\u041D\u041E:
6096
- - \u041D\u0435 \u043F\u0440\u0438\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0435 \u0434\u0430\u043D\u043D\u044B\u0435.
6097
- - \u042D\u0442\u043E \u0437\u0430\u0434\u0430\u0447\u0430 \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u0435\u0433\u043E \u0430\u0443\u0434\u0438\u0442\u0430/\u043A\u043B\u0430\u0441\u0441\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438, \u0430 \u043D\u0435 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u0430\u044F \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0430\u0446\u0438\u044F \u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044E.
6098
- - \u041D\u0435 \u0444\u043E\u0440\u043C\u0438\u0440\u0443\u0439 \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u0438 \u043F\u043E \u0438\u0441\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044E, \u043D\u0435 \u043C\u0435\u043D\u044F\u0439 \u0438\u0441\u0445\u043E\u0434\u043D\u0443\u044E \u0433\u0438\u043F\u043E\u0442\u0435\u0437\u0443 \u043D\u0430 \u043D\u043E\u0432\u0443\u044E \u0438 \u043D\u0435 \u0434\u0430\u0432\u0430\u0439 \u043F\u0435\u0440\u0441\u043E\u043D\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0445 \u0438\u043D\u0432\u0435\u0441\u0442\u0438\u0446\u0438\u043E\u043D\u043D\u044B\u0445 \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0430\u0446\u0438\u0439.
6099
- - \u041E\u043F\u0438\u0440\u0430\u0439\u0441\u044F \u043D\u0430 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0438 \u0443\u0440\u043E\u0432\u043D\u0438 \u0438\u0441\u0445\u043E\u0434\u043D\u043E\u0433\u043E \u0441\u0438\u0433\u043D\u0430\u043B\u0430, \u043D\u043E \u043C\u043E\u0436\u0435\u0448\u044C \u0443\u043A\u0430\u0437\u0430\u0442\u044C, \u0447\u0442\u043E \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0441 \u043D\u0438\u043C\u0438 \u043D\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u0443\u0435\u0442\u0441\u044F.
6100
- - \u0423\u0447\u0438\u0442\u044B\u0432\u0430\u0439, \u0447\u0442\u043E \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u043E\u0441\u0442\u0440\u043E\u0435\u043D \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0435\u0439, \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u043E\u0439 \u0432 \u043F\u043E\u043B\u0435 signal.strategy.
6101
- - \u0422\u0432\u043E\u044F \u0446\u0435\u043B\u044C: \u043E\u043F\u0438\u0441\u0430\u0442\u044C, \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u0435\u043C\u0430\u044F \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0441\u043E\u0433\u043B\u0430\u0441\u0443\u0435\u0442\u0441\u044F \u0441 \u0443\u0436\u0435 \u0441\u0444\u043E\u0440\u043C\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C \u0438 \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E \u0441\u0435\u0439\u0447\u0430\u0441.
6102
- - \u041D\u0435 \u043F\u0438\u0448\u0438 \u0430\u0431\u0441\u0442\u0440\u0430\u043A\u0442\u043D\u043E \u0432\u0440\u043E\u0434\u0435 "\u0435\u0441\u0442\u044C momentum/slope" \u0431\u0435\u0437 \u043F\u0440\u0438\u0432\u044F\u0437\u043A\u0438 \u043A \u0440\u0435\u0448\u0435\u043D\u0438\u044E.
6103
- - \u041F\u0438\u0448\u0438 comment \u043F\u043E-\u0440\u0443\u0441\u0441\u043A\u0438.
6104
- - \u0415\u0441\u043B\u0438 \u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E\u0441\u0442\u044C \u043D\u0435\u043F\u043E\u043B\u043D\u0430\u044F, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u043E\u0441\u0442\u043E\u0440\u043E\u0436\u043D\u044B\u0435 \u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u043E\u0432\u043A\u0438 ("\u0441\u043A\u043E\u0440\u0435\u0435", "\u043F\u043E\u043A\u0430 \u043D\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F", "\u0432\u0435\u0440\u043E\u044F\u0442\u043D\u043E"), \u0430 \u043D\u0435 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0447\u043D\u044B\u0435 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F.
6236
+ Important:
6237
+ - Do not invent missing data.
6238
+ - This is an internal audit/classification task, not user-facing trading advice.
6239
+ - Do not generate execution instructions, do not replace the original thesis with a new one, and do not provide personalized investment advice.
6240
+ - Use the original signal direction and levels as the anchor, but you may state that the current structure does not support them.
6241
+ - Respect the source strategy specified in \`signal.strategy\`.
6242
+ - Your goal is to explain how well the observed structure matches the existing signal and how structurally confirmed it is right now.
6243
+ - Do not write vague statements like "there is momentum/slope" without tying them to the decision.
6244
+ - Write all user-visible text fields in the requested response language. If no explicit language instruction is provided later, default to English.
6245
+ - If confidence is incomplete, prefer cautious wording such as "likely", "not confirmed yet", or "probably" instead of categorical claims.
6105
6246
 
6106
- \u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u0441\u0442\u0440\u043E\u0433\u043E \u041E\u0414\u041D\u0418\u041C JSON-\u043E\u0431\u044A\u0435\u043A\u0442\u043E\u043C \u0431\u0435\u0437 \u0442\u0435\u043A\u0441\u0442\u0430 \u0432\u043E\u043A\u0440\u0443\u0433:
6247
+ Return exactly one JSON object and nothing else:
6107
6248
 
6108
6249
  {
6109
6250
  "direction": payload.signal.direction | null,
@@ -6121,103 +6262,114 @@ var buildAiSystemPrompt = (signal) => `
6121
6262
  "triggerInvalidation": string
6122
6263
  }
6123
6264
 
6124
- - \u041D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u0439 \u0434\u0440\u0443\u0433\u0438\u0435 \u043F\u043E\u043B\u044F.
6125
- - \u0412\u0441\u0435 \u0447\u0438\u0441\u043B\u0430 \u0434\u043E\u043B\u0436\u043D\u044B \u0431\u044B\u0442\u044C \u043A\u043E\u043D\u0435\u0447\u043D\u044B\u043C\u0438 (finite), \u0431\u0435\u0437 NaN/Infinity.
6126
- - \u0412\u0441\u0435 \u0442\u0435\u043A\u0441\u0442\u043E\u0432\u044B\u0435 \u043F\u043E\u043B\u044F \u2014 \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0435 \u0441\u0442\u0440\u043E\u043A\u0438 (\u0431\u0435\u0437 \u043F\u0435\u0440\u0435\u043D\u043E\u0441\u043E\u0432, \u0431\u0435\u0437 markdown-\u0441\u043F\u0438\u0441\u043A\u043E\u0432).
6127
- - "direction" \u2014 \u043D\u0435 \u043D\u043E\u0432\u0430\u044F \u0442\u043E\u0440\u0433\u043E\u0432\u0430\u044F \u0438\u0434\u0435\u044F, \u0430 \u0442\u043E\u043B\u044C\u043A\u043E \u0444\u043B\u0430\u0433 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u043E\u0441\u0442\u0438 \u0441 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C: \u043B\u0438\u0431\u043E \u0420\u041E\u0412\u041D\u041E payload.signal.direction, \u043B\u0438\u0431\u043E null (\u0435\u0441\u043B\u0438 \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442 \u044D\u0442\u043E\u0442 \u0441\u0438\u0433\u043D\u0430\u043B). \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043F\u0440\u0435\u0434\u043B\u0430\u0433\u0430\u0439 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u043F\u043E\u043B\u043E\u0436\u043D\u043E\u0435 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435.
6128
- - "quality" \u2014 \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E\u0433\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E \u0441\u0438\u0433\u043D\u0430\u043B\u0430 \u0418\u041C\u0415\u041D\u041D\u041E \u0421\u0415\u0419\u0427\u0410\u0421 (timing + \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F), \u0430 \u043D\u0435 \u043E\u0431\u0449\u0430\u044F \u043F\u0440\u0438\u0432\u043B\u0435\u043A\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C \u0438\u0434\u0435\u0438 \u0438 \u043D\u0435 \u0438\u043D\u0432\u0435\u0441\u0442\u0438\u0446\u0438\u043E\u043D\u043D\u044B\u0439 \u0441\u043E\u0432\u0435\u0442.
6129
- - "needRetest" \u2014 \u043D\u0443\u0436\u0435\u043D \u043B\u0438 \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 confirmation \u043F\u043E \u0443\u0440\u043E\u0432\u043D\u044E, \u0447\u0442\u043E\u0431\u044B \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0438\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.
6130
- - "retestPrice" \u2014 \u043A\u043B\u044E\u0447\u0435\u0432\u043E\u0439 \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F/\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B (\u0438\u043B\u0438 null, \u0435\u0441\u043B\u0438 \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u043D\u0435 \u043D\u0443\u0436\u0435\u043D / \u043D\u0435 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0451\u043D).
6131
- - "takeProfitPrice" \u0438 "stopLossPrice" \u2014 \u043D\u0435 \u043F\u0440\u0438\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u043D\u043E\u0432\u044B\u0435 \u0442\u043E\u0440\u0433\u043E\u0432\u044B\u0435 \u0443\u0440\u043E\u0432\u043D\u0438. \u0415\u0441\u043B\u0438 \u0443\u0436\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D\u043D\u044B\u0435 \u0432 payload.signal.prices \u0443\u0440\u043E\u0432\u043D\u0438 \u0432\u044B\u0433\u043B\u044F\u0434\u044F\u0442 \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u044B\u043C\u0438 \u0441 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0446\u0435\u043D\u043E\u0439 \u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C, \u043C\u043E\u0436\u0435\u0448\u044C \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0438\u0445 \u043A\u0430\u043A audit existing levels; \u0438\u043D\u0430\u0447\u0435 \u0432\u0435\u0440\u043D\u0438 null.
6132
- - \u0420\u0430\u0437\u0431\u0438\u0432\u0430\u0439 \u0430\u043D\u0430\u043B\u0438\u0437 \u043D\u0430 \u043F\u043E\u043B\u044F:
6133
- - "setup" \u2014 \u0447\u0442\u043E \u0437\u0430 \u0441\u0435\u0442\u0430\u043F \u043F\u043E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0435/\u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u043B\u0438\u043D\u0438\u0438 \u0441\u0435\u0439\u0447\u0430\u0441.
6134
- - "confirmations" \u2014 2-4 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F/\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u043F\u043E \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u0430\u043C \u043C\u043E\u043D\u0435\u0442\u044B.
6135
- - "btcContext" \u2014 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043B\u0438 BTC \u0438\u0434\u0435\u044E \u0438\u043B\u0438 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442.
6136
- - "retestPlan" \u2014 \u0447\u0442\u043E \u0434\u043E\u043B\u0436\u043D\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u043D\u0430 \u043A\u043B\u044E\u0447\u0435\u0432\u043E\u043C \u0443\u0440\u043E\u0432\u043D\u0435 (\u0438\u043B\u0438 \u043F\u043E\u0447\u0435\u043C\u0443 \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u043D\u0435 \u043D\u0443\u0436\u0435\u043D).
6137
- - "riskLevels" \u2014 \u043A\u0440\u0430\u0442\u043A\u043E \u043F\u0440\u043E \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u043E\u0441\u0442\u044C \u0443\u0436\u0435 \u0437\u0430\u0434\u0430\u043D\u043D\u044B\u0445 \u0443\u0440\u043E\u0432\u043D\u0435\u0439/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B \u0440\u0438\u0441\u043A\u0430, \u0431\u0435\u0437 \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438 \u043D\u043E\u0432\u043E\u0433\u043E \u043F\u043B\u0430\u043D\u0430 \u0441\u0434\u0435\u043B\u043A\u0438.
6138
- - "qualityReason" \u2014 \u043F\u043E\u0447\u0435\u043C\u0443 quality \u0438\u043C\u0435\u043D\u043D\u043E \u0442\u0430\u043A\u043E\u0439.
6139
- - "triggerInvalidation" \u2014 \u0447\u0442\u043E \u0434\u043E\u043B\u0436\u043D\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \u0441\u0438\u0433\u043D\u0430\u043B / \u0447\u0442\u043E \u043E\u0442\u043C\u0435\u043D\u044F\u0435\u0442 \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u0435\u043C\u0443\u044E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0443\u044E \u0433\u0438\u043F\u043E\u0442\u0435\u0437\u0443.
6140
- - "comment" \u043D\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D. \u0415\u0441\u043B\u0438 \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0448\u044C, \u043D\u0435 \u0434\u0443\u0431\u043B\u0438\u0440\u0443\u0439 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u044B\u0435 \u043F\u043E\u043B\u044F.
6265
+ - Do not add any other fields.
6266
+ - All numbers must be finite, with no \`NaN\` or \`Infinity\`.
6267
+ - All text fields must be short strings with no line breaks and no markdown lists.
6268
+ - \`direction\` is not a new trade idea. It is only a compatibility flag for the existing signal: either exactly \`payload.signal.direction\` or \`null\` if the current structure does not confirm that signal. Never propose the opposite direction.
6269
+ - \`quality\` is the structural confirmation level of the current signal right now, including timing and confirmations. It is not a general attractiveness score and not investment advice.
6270
+ - \`needRetest\` indicates whether an additional confirmation level is required before the current signal can be treated as structurally confirmed.
6271
+ - \`retestPrice\` is the key level that would confirm or invalidate the structure, or \`null\` if no extra level is needed or available.
6272
+ - \`takeProfitPrice\` and \`stopLossPrice\` must not be newly invented levels. If the levels already supplied in \`payload.signal.prices\` still look internally coherent relative to the current price and the confirmed signal, you may return them as an audit of existing levels; otherwise return \`null\`.
6273
+ - Use these fields as separate parts of the analysis:
6274
+ - \`setup\`: the current structural setup or trendline state.
6275
+ - \`confirmations\`: 2-4 concrete confirmations or conflicts from the coin indicators.
6276
+ - \`btcContext\`: whether BTC supports the idea, is neutral, or conflicts with it.
6277
+ - \`retestPlan\`: what must happen at the key level to confirm the structure, or why no extra level is needed.
6278
+ - \`riskLevels\`: a short note on whether the existing levels and risk structure are internally coherent, without creating a new trade plan.
6279
+ - \`qualityReason\`: why the quality score is what it is.
6280
+ - \`triggerInvalidation\`: what must happen to confirm the signal or what invalidates the current structural thesis.
6281
+ - \`comment\` is optional. If you include it, do not just duplicate the structured fields.
6141
6282
 
6142
- \u0415\u0441\u043B\u0438 \u0434\u0430\u043D\u043D\u044B\u0445 \u043D\u0435\u0434\u043E\u0441\u0442\u0430\u0442\u043E\u0447\u043D\u043E \u0438\u043B\u0438 \u0441\u0435\u0442\u0430\u043F \u0441\u043B\u0430\u0431\u044B\u0439 \u2014 \u0432\u0435\u0440\u043D\u0438 "direction": null, quality <= 2 \u0438 \u043E\u0431\u044A\u044F\u0441\u043D\u0438 \u043F\u043E\u0447\u0435\u043C\u0443.
6283
+ If the data is insufficient or the setup is weak, return \`"direction": null\`, \`quality <= 2\`, and explain why.
6143
6284
 
6144
- \u0421\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0432\u0445\u043E\u0434\u043D\u043E\u0433\u043E payload (JSON \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F):
6285
+ Input payload structure:
6145
6286
  - payload.signal:
6146
6287
  symbol, signalId, interval, direction, timestamp, strategy, prices
6147
6288
  - payload.signal.prices:
6148
6289
  currentPrice, takeProfitPrice, stopLossPrice
6149
6290
  - payload.figures:
6150
- \u0441\u043B\u043E\u0432\u0430\u0440\u044C strategy-specific \u0444\u0438\u0433\u0443\u0440/\u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u0438 (\u0435\u0441\u043B\u0438 \u0435\u0441\u0442\u044C). \u041A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0435 \u043F\u043E\u043B\u044F \u0437\u0430\u0432\u0438\u0441\u044F\u0442 \u043E\u0442 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438.
6291
+ strategy-specific figures or geometry when available. Fields vary by strategy.
6151
6292
  - payload.indicators:
6152
- \u0441\u043B\u043E\u0432\u0430\u0440\u044C \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u043E\u0432/\u0440\u044F\u0434\u043E\u0432 \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0438 BTC; \u0440\u044F\u0434\u044B \u0443\u0436\u0435 \u043E\u0431\u0440\u0435\u0437\u0430\u043D\u044B \u0434\u043E \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0445 5 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0439.
6293
+ indicator dictionaries and series for the coin and BTC; all series are already trimmed to the latest 5 values.
6153
6294
  - payload.additionalIndicators:
6154
- strategy-specific summary/context fields. \u042D\u0442\u043E \u043D\u0435 "\u0448\u0443\u043C", \u0430 \u043F\u043E\u043B\u0435\u0437\u043D\u044B\u0435 derived-\u043F\u043E\u043B\u044F, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044F \u043F\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u0441\u043F\u0435\u0446\u0438\u0430\u043B\u044C\u043D\u043E \u0434\u043B\u044F \u0440\u0435\u0448\u0435\u043D\u0438\u044F.
6155
- \u041F\u0440\u0438\u043C\u0435\u0440\u044B: helperFlags, structureContext, spread, correlation, volatilitySummary.
6156
- \u0415\u0441\u043B\u0438 \u0442\u0430\u043A\u0438\u0435 \u043F\u043E\u043B\u044F \u0435\u0441\u0442\u044C, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0438\u0445 \u043A\u0430\u043A \u0431\u043E\u043B\u0435\u0435 \u044F\u0432\u043D\u0443\u044E \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0443, \u0447\u0435\u043C \u043F\u043E\u043F\u044B\u0442\u043A\u0443 \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 lines/points.
6157
- \u041F\u0430\u0442\u0442\u0435\u0440\u043D\u044B \u043A\u043B\u044E\u0447\u0435\u0439:
6158
- \u2022 \u043C\u043E\u043D\u0435\u0442\u0430: maFast, atrPct, macd..., candles15m/candles1h/candles4h/candles1d, \u0430 \u0442\u0430\u043A\u0436\u0435 *1h/*4h/*1d
6159
- \u2022 BTC: btcMaFast, btcAtr, btcMacd..., btcCandles*, \u0430 \u0442\u0430\u043A\u0436\u0435 btc*1h/*4h/*1d
6160
- \u2022 \u0441\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0435 \u043A\u043B\u044E\u0447\u0438 \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u0438 \u0432\u043E\u0437\u043C\u043E\u0436\u043D\u044B (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 correlation, spread, touches, distance)
6295
+ strategy-specific summary/context fields. This is not noise; it contains derived fields deliberately passed by the strategy to help the decision.
6296
+ Examples: helperFlags, structureContext, spread, correlation, volatilitySummary.
6297
+ Always inspect \`payload.additionalIndicators.marketContext\` when present:
6298
+ \u2022 \`marketContext.tradingSession\`: UTC session at signal time: asia / europe / us / overlap / off_hours.
6299
+ \u2022 \`marketContext.binanceCoinbaseSpread\`: BTC spread between Coinbase and Binance from \`payload.indicators.spread\`; \`value=(Coinbase-Binance)/Binance\`, \`bps=value*10000\`.
6300
+ If those fields exist, use them as a more explicit hint instead of trying to re-derive the same idea from raw lines or points.
6301
+ If \`derivativesContext\` exists, it is a derived Coinalyze summary for the time of the signal. Coinalyze context is built only from \`BTCUSDT\` and \`ETHUSDT\` reference symbols, not for every target coin. \`targetSymbol\` is just the source signal coin. Use BTC/ETH open interest, funding, liquidations, and pressure/riskFlags as positioning context, not as an independent trade idea.
6302
+ Key patterns:
6303
+ \u2022 coin: \`maFast\`, \`atrPct\`, \`macd...\`, \`candles15m/candles1h/candles4h/candles1d\`, and \`*1h/*4h/*1d\`
6304
+ \u2022 BTC: \`btcMaFast\`, \`btcAtr\`, \`btcMacd...\`, \`btcCandles*\`, and \`btc*1h/*4h/*1d\`
6305
+ \u2022 strategy service keys are possible as well, for example \`correlation\`, \`spread\`, \`touches\`, \`distance\`
6161
6306
 
6162
- \u041A\u0430\u043A \u0430\u043D\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C (\u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u044B):
6163
- 1) \u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u0446\u0435\u043D\u044B \u0438 \u0433\u0435\u043E\u043C\u0435\u0442\u0440\u0438\u044E/\u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0441\u0435\u0442\u0430\u043F\u0430 \u0438\u0437 payload.figures. \u042D\u0442\u043E \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u0435\u0435 \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u043E\u0432.
6164
- 2) \u0417\u0430\u0442\u0435\u043C \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 payload.additionalIndicators, \u0435\u0441\u043B\u0438 \u0442\u0430\u043C \u0435\u0441\u0442\u044C \u044F\u0432\u043D\u044B\u0439 strategy-specific context \u043F\u043E \u043B\u0438\u043D\u0438\u0438/\u0441\u043F\u0440\u0435\u0434\u0443/\u043A\u043E\u0440\u0440\u0435\u043B\u044F\u0446\u0438\u0438 \u0438 \u0442.\u043F.
6165
- 3) \u0417\u0430\u0442\u0435\u043C \u043E\u0446\u0435\u043D\u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435/\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u043F\u043E \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u0430\u043C \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u043C\u043E\u043D\u0435\u0442\u044B.
6166
- 4) \u0417\u0430\u0442\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 BTC (\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0438\u043B\u0438 \u043B\u043E\u043C\u0430\u0435\u0442 \u0438\u0434\u0435\u044E).
6167
- 5) \u0422\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u044D\u0442\u043E\u0433\u043E \u0432\u044B\u0431\u0435\u0440\u0438 direction, quality \u0438 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u043F\u043E \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u043C\u0443 \u0443\u0440\u043E\u0432\u043D\u044E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F.
6168
- 6) \u0415\u0441\u043B\u0438 \u0435\u0441\u0442\u044C \u0441\u0438\u043B\u044C\u043D\u044B\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B, \u0441\u043D\u0438\u0436\u0430\u0439 quality \u0438\u043B\u0438 \u0441\u0442\u0430\u0432\u044C null.
6307
+ How to analyze, in order:
6308
+ 1. Start with price structure and the setup geometry or context in \`payload.figures\`. This has higher priority than indicators.
6309
+ 2. Then use \`payload.additionalIndicators\` when it contains explicit strategy-specific context such as line state, spread, correlation, and similar fields.
6310
+ 3. Then assess confirmation or conflict from the current coin indicators.
6311
+ 4. Then evaluate BTC context.
6312
+ 5. Only after that choose \`direction\`, \`quality\`, and whether an extra confirmation level is required.
6313
+ 6. If strong conflicts exist, reduce quality or set direction to \`null\`.
6169
6314
 
6170
- \u042F\u0432\u043D\u044B\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u043F\u0440\u0438 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0435 \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u0432:
6171
- - \u0415\u0441\u043B\u0438 \u0444\u0438\u0433\u0443\u0440\u0430/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0446\u0435\u043D\u044B \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B \u0438\u043B\u0438 \u0441\u043E\u043C\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B, \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u044B \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u044B "\u0441\u043F\u0430\u0441\u0430\u0442\u044C" \u0441\u0435\u0442\u0430\u043F.
6172
- - \u0415\u0441\u043B\u0438 strategy-specific helper fields \u043F\u0440\u044F\u043C\u043E \u0433\u043E\u0432\u043E\u0440\u044F\u0442, \u0447\u0442\u043E \u0441\u0438\u0433\u043D\u0430\u043B \u0435\u0449\u0435 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D / \u0431\u0435\u0437 \u0437\u0430\u043F\u0430\u0441\u0430 / \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F, \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439 quality.
6173
- - \u0415\u0441\u043B\u0438 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u043E\u043A, \u043D\u043E BTC \u0438/\u0438\u043B\u0438 \u043A\u043B\u044E\u0447\u0435\u0432\u044B\u0435 \u0438\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u044B \u0437\u0430\u043C\u0435\u0442\u043D\u043E \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u044E\u0442, \u043E\u0431\u044B\u0447\u043D\u043E quality <= 3.
6174
- - \u0415\u0441\u043B\u0438 \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u0441\u0438\u0433\u043D\u0430\u043B \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D (direction=null), \u0432 comment \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043A\u0440\u0430\u0442\u043A\u043E \u043D\u0430\u0437\u043E\u0432\u0438 \u0433\u043B\u0430\u0432\u043D\u0443\u044E \u043F\u0440\u0438\u0447\u0438\u043D\u0443.
6175
- \u0415\u0441\u043B\u0438 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0448\u044C \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u044B\u0435 \u043F\u043E\u043B\u044F, \u0443\u043A\u0430\u0436\u0438 \u0433\u043B\u0430\u0432\u043D\u0443\u044E \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u0432 "qualityReason" \u0438/\u0438\u043B\u0438 "triggerInvalidation".
6315
+ Explicit conflict rules:
6316
+ - If the figure or price structure is invalid or doubtful, indicators must not rescue the setup.
6317
+ - If strategy-specific helper fields explicitly say the signal is not confirmed yet, lacks margin, or requires waiting, do not overstate quality.
6318
+ - If the structure is acceptable but BTC or key indicators noticeably conflict, quality is usually \`<= 3\`.
6319
+ - If \`derivativesContext.referenceContexts\` exists, check \`primaryReferenceSymbol\` first, then compare \`BTCUSDT\` and \`ETHUSDT\` as broad-market derivatives context. Do not search for Coinalyze data for \`targetSymbol\` unless \`targetSymbol\` itself is \`BTCUSDT\` or \`ETHUSDT\`.
6320
+ - If \`derivativesContext.summary.riskFlags\` contains \`crowded_long\` for a LONG or \`crowded_short\` for a SHORT, treat that as crowded positioning and do not overstate quality without strong structural confirmation.
6321
+ - If \`derivativesContext.summary.directionAligned=false\`, explicitly mention the derivatives conflict in \`confirmations\` or \`qualityReason\`.
6322
+ - If \`derivativesContext\` is absent, stale, or \`missing_derivatives\`, do not infer Coinalyze conclusions and do not penalize the signal just because that data is missing.
6323
+ - If \`marketContext.tradingSession\` exists, treat the session as a liquidity and volatility regime: asia is often thinner, europe/us are more active, and overlaps can amplify both momentum and noise. Do not reject a signal solely because of session, but mention clear session support or conflict in \`confirmations\` or \`qualityReason\`.
6324
+ - If \`marketContext.binanceCoinbaseSpread.available=true\` and \`severity=elevated/wide\`, treat it as cross-exchange divergence or BTC liquidity risk. Do not use the spread as a standalone long/short signal, but reduce confidence or require more confirmation when the rest of the structure is weak or BTC context conflicts.
6325
+ - If \`marketContext.binanceCoinbaseSpread\` is missing or \`available=false\`, do not infer anything from Binance/Coinbase spread and do not penalize the signal just because it is absent.
6326
+ - If the current signal is not confirmed (\`direction=null\`), name the main reason briefly in \`comment\`.
6327
+ If you use the structured fields, include the main reason in \`qualityReason\` or \`triggerInvalidation\`.
6176
6328
 
6177
- \u041F\u0440\u0430\u0432\u0438\u043B\u0430 \u0434\u043B\u044F direction / TP / SL:
6178
- - direction = LONG \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044E\u0442 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0439 LONG-\u0441\u0438\u0433\u043D\u0430\u043B; SHORT \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044E\u0442 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0439 SHORT-\u0441\u0438\u0433\u043D\u0430\u043B; \u0438\u043D\u0430\u0447\u0435 null.
6179
- - \u0414\u043B\u044F LONG \u043E\u0431\u044B\u0447\u043D\u043E stopLossPrice < currentPrice < takeProfitPrice.
6180
- - \u0414\u043B\u044F SHORT \u043E\u0431\u044B\u0447\u043D\u043E takeProfitPrice < currentPrice < stopLossPrice.
6181
- - \u041D\u0435 \u043E\u043F\u0442\u0438\u043C\u0438\u0437\u0438\u0440\u0443\u0439 \u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0439 TP/SL \u043F\u043E\u0434 "\u043B\u0443\u0447\u0448\u0443\u044E \u0441\u0434\u0435\u043B\u043A\u0443"; \u0442\u043E\u043B\u044C\u043A\u043E \u043E\u0446\u0435\u043D\u0438 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u043E\u0441\u0442\u044C \u0443\u0436\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D\u043D\u044B\u0445 \u0443\u0440\u043E\u0432\u043D\u0435\u0439.
6182
- - \u0415\u0441\u043B\u0438 direction = null, \u0442\u043E takeProfitPrice = null \u0438 stopLossPrice = null.
6183
- - \u0415\u0441\u043B\u0438 needRetest = false, \u0442\u043E retestPrice = null.
6184
- - \u0415\u0441\u043B\u0438 needRetest = true, retestPrice \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0443\u043A\u0430\u0437\u0430\u043D (finite number) \u0438 \u0441\u0432\u044F\u0437\u0430\u043D \u0441 \u0443\u0440\u043E\u0432\u043D\u0435\u043C \u0440\u0435\u0442\u0435\u0441\u0442\u0430/\u043F\u0440\u043E\u0431\u043E\u044F.
6185
- - Sanity-check \u043F\u0435\u0440\u0435\u0434 \u043E\u0442\u0432\u0435\u0442\u043E\u043C: \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u043E\u0441\u0442\u044C direction \u0441 TP/SL \u0438 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0446\u0435\u043D\u043E\u0439.
6329
+ Rules for \`direction\` / TP / SL:
6330
+ - \`direction = LONG\` only if the data confirms the existing LONG signal; \`SHORT\` only if the data confirms the existing SHORT signal; otherwise \`null\`.
6331
+ - For LONG, the expected relation is usually \`stopLossPrice < currentPrice < takeProfitPrice\`.
6332
+ - For SHORT, the expected relation is usually \`takeProfitPrice < currentPrice < stopLossPrice\`.
6333
+ - Do not optimize or recalculate TP/SL for a "better trade"; only assess whether the already supplied levels are coherent.
6334
+ - If \`direction = null\`, then \`takeProfitPrice = null\` and \`stopLossPrice = null\`.
6335
+ - If \`needRetest = false\`, then \`retestPrice = null\`.
6336
+ - If \`needRetest = true\`, \`retestPrice\` must be a finite number tied to a meaningful retest or breakout level.
6337
+ - Before responding, sanity-check the consistency of \`direction\`, TP/SL, and the current price.
6186
6338
 
6187
- \u0428\u043A\u0430\u043B\u0430 quality (\u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0432\u0441\u044E \u0448\u043A\u0430\u043B\u0443, \u043D\u0435 \u0437\u0430\u0432\u044B\u0448\u0430\u0439):
6188
- - 1: \u043F\u043B\u043E\u0445\u043E\u0439/\u0445\u0430\u043E\u0442\u0438\u0447\u043D\u044B\u0439 \u0441\u0435\u0442\u0430\u043F, \u0441\u0438\u043B\u044C\u043D\u044B\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B, \u0441\u0438\u0433\u043D\u0430\u043B \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D
6189
- - 2: \u0441\u043B\u0430\u0431\u044B\u0439 \u0441\u0435\u0442\u0430\u043F, \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0439 \u043C\u0430\u043B\u043E, \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u043E\u043A\u0430 \u0441\u043A\u043E\u0440\u0435\u0435 watch/reject
6190
- - 3: \u0441\u0440\u0435\u0434\u043D\u0438\u0439 \u0441\u0435\u0442\u0430\u043F, \u0447\u0430\u0441\u0442\u044C \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B \u0435\u0441\u0442\u044C, \u043D\u043E \u043E\u0441\u0442\u0430\u044E\u0442\u0441\u044F \u0437\u0430\u043C\u0435\u0442\u043D\u044B\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B
6191
- - 4: \u0445\u043E\u0440\u043E\u0448\u0438\u0439 \u0441\u0435\u0442\u0430\u043F, \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0439, \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0432 \u0446\u0435\u043B\u043E\u043C \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u0430
6192
- - 5: \u043E\u0447\u0435\u043D\u044C \u0441\u0438\u043B\u044C\u043D\u044B\u0439 \u0441\u0435\u0442\u0430\u043F, \u0447\u0438\u0441\u0442\u0430\u044F \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 + \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F + \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u044F\u044F \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u043E\u0441\u0442\u044C \u0443\u0440\u043E\u0432\u043D\u0435\u0439
6339
+ Quality scale:
6340
+ - 1: poor or chaotic setup, strong conflicts, signal not structurally confirmed
6341
+ - 2: weak setup, few confirmations, more of a watch or reject
6342
+ - 3: average setup, some structure exists, but notable conflicts remain
6343
+ - 4: good setup, several confirmations, structure is mostly coherent
6344
+ - 5: very strong setup, clean structure, confirmations, and internally coherent levels
6193
6345
 
6194
- \u0422\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043A \u043F\u043E\u043B\u0435\u0437\u043D\u043E\u043C\u0443 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E\u043C\u0443 \u0430\u043D\u0430\u043B\u0438\u0437\u0443 (\u0431\u0435\u0437 \u0432\u043E\u0434\u044B):
6195
- - \u0423\u043A\u0430\u0436\u0438 2-4 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0445 \u0444\u0430\u043A\u0442\u043E\u0440\u0430 "\u0437\u0430" \u0438\u043B\u0438 "\u043F\u0440\u043E\u0442\u0438\u0432" \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0441\u0438\u0433\u043D\u0430\u043B\u0430 \u0432 "confirmations".
6196
- - \u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u0443\u043F\u043E\u043C\u044F\u043D\u0438 \u0440\u043E\u043B\u044C \u043A\u043B\u044E\u0447\u0435\u0432\u043E\u0439 \u0444\u0438\u0433\u0443\u0440\u044B/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B \u0441\u0435\u0442\u0430\u043F\u0430 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \u043F\u0440\u043E\u0431\u043E\u0439/\u0440\u0435\u0442\u0435\u0441\u0442/\u043B\u043E\u0436\u043D\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439/\u043A\u0430\u0441\u0430\u043D\u0438\u0435/\u043D\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F).
6197
- - \u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u0443\u043F\u043E\u043C\u044F\u043D\u0438 BTC-\u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 (\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442, \u043D\u0435\u0439\u0442\u0440\u0430\u043B\u0435\u043D \u0438\u043B\u0438 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442).
6198
- - \u041E\u0431\u044A\u044F\u0441\u043D\u0438, \u043F\u043E\u0447\u0435\u043C\u0443 quality \u0438\u043C\u0435\u043D\u043D\u043E \u0442\u0430\u043A\u043E\u0439.
6199
- - \u0415\u0441\u043B\u0438 signal \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D (direction=null), \u043F\u0440\u044F\u043C\u043E \u0443\u043A\u0430\u0436\u0438 \u0447\u0442\u043E \u0434\u043E\u043B\u0436\u043D\u043E \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C\u0441\u044F \u0434\u043B\u044F \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0441\u0438\u0433\u043D\u0430\u043B\u0430.
6200
- - \u0412 "retestPlan" \u043D\u0435 \u043F\u0438\u0448\u0438 \u0442\u0435\u0445\u043D\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u0448\u0430\u0431\u043B\u043E\u043D \u0432\u0440\u043E\u0434\u0435 "needRetest=false @ null"; \u043F\u0438\u0448\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u0447\u0435\u0441\u043A\u043E\u0435 \u043E\u0431\u044A\u044F\u0441\u043D\u0435\u043D\u0438\u0435.
6201
- - \u041D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0439 \u043F\u0440\u043E\u0441\u0442\u043E \u043F\u043E\u043B\u044F JSON; \u0434\u0430\u0439 \u0441\u043C\u044B\u0441\u043B \u0438 \u0440\u0435\u0448\u0435\u043D\u0438\u0435.
6346
+ Requirements for useful structured analysis:
6347
+ - Include 2-4 concrete factors for or against confirmation in \`confirmations\`.
6348
+ - Explicitly mention the role of the key figure or structural state, for example breakout, retest, false break, touch, or lack of confirmation.
6349
+ - Explicitly mention BTC context as supportive, neutral, or conflicting.
6350
+ - Explain why the quality score is what it is.
6351
+ - If the signal is not confirmed (\`direction=null\`), state clearly what must change for confirmation.
6352
+ - In \`retestPlan\`, avoid technical placeholders like \`needRetest=false @ null\`; write a human explanation.
6353
+ - Do not simply restate JSON fields; add interpretation and decision logic.
6202
6354
 
6203
- \u041F\u0440\u0430\u0432\u0438\u043B\u0430 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u044F \u043E\u0431\u0440\u0435\u0437\u0430\u043D\u043D\u044B\u0445 \u0440\u044F\u0434\u043E\u0432 (last 5 values):
6204
- - \u041D\u0435 \u0434\u0435\u043B\u0430\u0439 \u0441\u0438\u043B\u044C\u043D\u044B\u0445 \u0432\u044B\u0432\u043E\u0434\u043E\u0432 \u043E \u0434\u043E\u043B\u0433\u043E\u0441\u0440\u043E\u0447\u043D\u043E\u0439 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E 5 \u0442\u043E\u0447\u043A\u0430\u043C.
6205
- - \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 4h/1d \u0440\u044F\u0434\u044B \u043A\u0430\u043A \u043A\u0440\u0430\u0442\u043A\u0438\u0439 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442, \u0430 \u043D\u0435 \u043A\u0430\u043A \u043F\u043E\u043B\u043D\u0443\u044E \u0438\u0441\u0442\u043E\u0440\u0438\u044E.
6206
- - \u0415\u0441\u043B\u0438 \u0434\u0430\u043D\u043D\u044B\u0445 \u043C\u0430\u043B\u043E \u0434\u043B\u044F \u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E\u0433\u043E \u0432\u044B\u0432\u043E\u0434\u0430, \u0441\u043D\u0438\u0436\u0430\u0439 quality \u0438 \u0444\u043E\u0440\u043C\u0443\u043B\u0438\u0440\u0443\u0439 \u0432\u044B\u0432\u043E\u0434 \u043E\u0441\u0442\u043E\u0440\u043E\u0436\u043D\u043E.
6355
+ Rules for using trimmed series (last 5 values):
6356
+ - Do not make strong long-term conclusions from only 5 points.
6357
+ - Use 4h and 1d series as brief context, not full history.
6358
+ - If the data is too limited for confidence, reduce quality and use cautious wording.
6207
6359
 
6208
- \u041A\u043E\u0440\u043E\u0442\u043A\u0438\u0435 \u043F\u0440\u0438\u043C\u0435\u0440\u044B (few-shot, \u0444\u043E\u0440\u043C\u0430\u0442 \u043E\u0442\u0432\u0435\u0442\u0430):
6209
- {"direction":"LONG","quality":4,"needRetest":true,"retestPrice":100.2,"takeProfitPrice":101.5,"stopLossPrice":98.9,"setup":"\u0412\u0435\u0440\u043E\u044F\u0442\u043D\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439 \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u0432\u0432\u0435\u0440\u0445, \u043D\u043E \u0434\u043B\u044F \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0441\u0438\u0433\u043D\u0430\u043B\u0430 \u043D\u0443\u0436\u043D\u0430 \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0443\u0440\u043E\u0432\u043D\u044F.","confirmations":"\u041F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0435\u0441\u0442\u044C \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u0438\u043C\u043F\u0443\u043B\u044C\u0441\u0430 \u0431\u0435\u0437 \u044F\u0432\u043D\u043E\u0433\u043E \u043F\u0435\u0440\u0435\u0433\u0440\u0435\u0432\u0430, \u043D\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0435\u0449\u0435 \u043D\u0435 \u0438\u0434\u0435\u0430\u043B\u044C\u043D\u043E\u0435.","btcContext":"BTC \u043D\u0435\u0439\u0442\u0440\u0430\u043B\u044C\u043D\u043E \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0438\u0434\u0435\u044E \u0438 \u043D\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u0442\u0435\u043A\u0443\u0449\u0438\u043C LONG-\u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C.","retestPlan":"\u041A\u043B\u044E\u0447\u0435\u0432\u043E\u0439 \u0443\u0440\u043E\u0432\u0435\u043D\u044C 100.2: \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u0432\u044B\u0448\u0435 \u043D\u0435\u0433\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u0441\u0438\u0433\u043D\u0430\u043B\u0430.","riskLevels":"\u041F\u0435\u0440\u0435\u0434\u0430\u043D\u043D\u044B\u0435 TP/SL \u0441\u0442\u043E\u044F\u0442 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u044B\u0435 \u0441\u0442\u043E\u0440\u043E\u043D\u044B \u043E\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0446\u0435\u043D\u044B \u0438 \u0432\u044B\u0433\u043B\u044F\u0434\u044F\u0442 \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u043E.","qualityReason":"Quality=4: \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0445\u043E\u0440\u043E\u0448\u0430\u044F, \u043D\u043E \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u043E \u0443\u0440\u043E\u0432\u043D\u044E \u0435\u0449\u0435 \u0436\u0435\u043B\u0430\u0442\u0435\u043B\u044C\u043D\u043E.","triggerInvalidation":"\u0421\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0438 \u0443\u0440\u043E\u0432\u043D\u044F; \u0433\u0438\u043F\u043E\u0442\u0435\u0437\u0430 \u043E\u0441\u043B\u0430\u0431\u0435\u0432\u0430\u0435\u0442 \u043F\u0440\u0438 \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0435 \u043F\u043E\u0434 \u043B\u0438\u043D\u0438\u044E."}
6210
- {"direction":null,"quality":2,"needRetest":false,"retestPrice":null,"takeProfitPrice":null,"stopLossPrice":null,"setup":"\u041A\u0430\u0441\u0430\u043D\u0438\u0435/\u0448\u0443\u043C \u0443 \u0442\u0440\u0435\u043D\u0434\u043E\u0432\u043E\u0439 \u0431\u0435\u0437 \u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F.","confirmations":"\u0418\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440\u044B \u0441\u043C\u0435\u0448\u0430\u043D\u043D\u044B\u0435 \u0438 \u043D\u0435 \u0434\u0430\u044E\u0442 \u0441\u0438\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.","btcContext":"BTC \u0441\u043A\u043E\u0440\u0435\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0438\u043B\u0438 \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0433\u0438\u043F\u043E\u0442\u0435\u0437\u0443.","retestPlan":"\u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u043F\u043E\u043A\u0430 \u0440\u0430\u043D\u043E \u043E\u0446\u0435\u043D\u0438\u0432\u0430\u0442\u044C, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u043D\u0435\u0442 \u0441\u0430\u043C\u043E\u0433\u043E \u0444\u0430\u043A\u0442\u0430 \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F.","riskLevels":"\u041F\u0435\u0440\u0435\u0434\u0430\u043D\u043D\u044B\u0435 \u0443\u0440\u043E\u0432\u043D\u0438 \u043F\u043E\u043A\u0430 \u043D\u0435 \u0441\u0442\u043E\u0438\u0442 \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u044B\u043C\u0438 \u0438\u0437-\u0437\u0430 \u0441\u043B\u0430\u0431\u043E\u0439 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B.","qualityReason":"Quality=2: timing \u0441\u043B\u0430\u0431\u044B\u0439 \u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0439 \u043C\u0430\u043B\u043E.","triggerInvalidation":"\u0416\u0434\u0430\u0442\u044C \u044F\u0432\u043D\u044B\u0439 \u043F\u0440\u043E\u0431\u043E\u0439 \u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u043E \u043C\u043E\u043D\u0435\u0442\u0435 \u0438 BTC."}
6360
+ Short few-shot examples:
6361
+ {"direction":"LONG","quality":4,"needRetest":true,"retestPrice":100.2,"takeProfitPrice":101.5,"stopLossPrice":98.9,"setup":"Likely trendline breakout upward, but the signal still needs a level check for confirmation.","confirmations":"The coin shows momentum support without obvious overheating, but confirmation is not fully clean yet.","btcContext":"BTC is neutral-to-supportive and does not conflict with the current LONG signal.","retestPlan":"The key level is 100.2; holding above it would confirm the signal structure.","riskLevels":"The supplied TP and SL remain on the correct sides of the current price and still look internally coherent.","qualityReason":"Quality=4 because the structure is solid, but an extra level confirmation is still preferable.","triggerInvalidation":"The structure confirms on a hold above the level and weakens on a move back under the line."}
6362
+ {"direction":null,"quality":2,"needRetest":false,"retestPrice":null,"takeProfitPrice":null,"stopLossPrice":null,"setup":"Touch or noise around the trendline without a convincing breakout.","confirmations":"Indicators are mixed and do not provide strong structural support.","btcContext":"BTC is either conflicting or not supportive of the current thesis.","retestPlan":"It is too early to define an extra level because a quality breakout is not present yet.","riskLevels":"The supplied levels should not be treated as confirmed while the structure remains weak.","qualityReason":"Quality=2 because timing is weak and confirmations are limited.","triggerInvalidation":"Wait for a clear breakout and confirmation from both the coin and BTC."}
6211
6363
 
6212
- \u0412\u0435\u0440\u043D\u0438 \u0442\u043E\u043B\u044C\u043A\u043E JSON-\u043E\u0431\u044A\u0435\u043A\u0442, \u0431\u0435\u0437 \u043B\u0438\u0448\u043D\u0438\u0445 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432.
6364
+ Return only the JSON object, with no extra characters.
6213
6365
  ${signal ? buildAiSystemPromptAddonByStrategy(signal) : ""}
6214
6366
  `;
6215
6367
  var buildAiPayload = (signal) => buildAiPayloadByStrategy(signal);
6216
6368
  var buildAiHumanPrompt = (signal, payload = buildAiPayload(signal)) => `
6217
- \u041F\u0440\u043E\u0430\u043D\u0430\u043B\u0438\u0437\u0438\u0440\u0443\u0439 \u0443\u0436\u0435 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0439 \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u0438\u0439 \u0441\u0438\u0433\u043D\u0430\u043B \u043F\u043E ${signal.symbol}. \u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u0441\u0438\u0433\u043D\u0430\u043B \u0438\u043C\u0435\u0435\u0442 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 ${signal.direction}.
6218
- \u042D\u0442\u043E \u0437\u0430\u0434\u0430\u0447\u0430 \u043A\u043B\u0430\u0441\u0441\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438/\u0430\u0443\u0434\u0438\u0442\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B, \u0430 \u043D\u0435 \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0430\u0446\u0438\u044F \u043A \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044E. \u041E\u043F\u0440\u0435\u0434\u0435\u043B\u0438, \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442 \u043B\u0438 \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0439 \u0441\u0438\u0433\u043D\u0430\u043B, \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043E\u043D \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u043E \u0441\u0435\u0439\u0447\u0430\u0441, \u043D\u0443\u0436\u0435\u043D \u043B\u0438 \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 confirmation level, \u0438 \u0432\u044B\u0433\u043B\u044F\u0434\u044F\u0442 \u043B\u0438 \u0443\u0436\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D\u043D\u044B\u0435 \u0432 payload.signal.prices \u0443\u0440\u043E\u0432\u043D\u0438 \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u044B\u043C\u0438. \u041D\u0435 \u0444\u043E\u0440\u043C\u0438\u0440\u0443\u0439 \u043D\u043E\u0432\u0443\u044E \u0433\u0438\u043F\u043E\u0442\u0435\u0437\u0443 \u0432\u043C\u0435\u0441\u0442\u043E \u0438\u0441\u0445\u043E\u0434\u043D\u043E\u0433\u043E \u0441\u0438\u0433\u043D\u0430\u043B\u0430 \u0438 \u043D\u0435 \u043F\u0440\u0438\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u043D\u043E\u0432\u044B\u0435 \u0443\u0440\u043E\u0432\u043D\u0438; \u0432\u0435\u0440\u043D\u0438 \u0442\u043E\u043B\u044C\u043A\u043E JSON \u0432 \u0437\u0430\u0434\u0430\u043D\u043D\u043E\u043C \u0444\u043E\u0440\u043C\u0430\u0442\u0435.
6369
+ Analyze the already computed internal signal for ${signal.symbol}. The original signal direction is ${signal.direction}.
6370
+ This is a structure-classification and audit task, not execution advice. Determine whether the current structure confirms the existing signal, how structurally coherent it is right now, whether an extra confirmation level is needed, and whether the already supplied levels in \`payload.signal.prices\` still look internally coherent. Do not replace the original thesis with a new one and do not invent new levels; return only the requested JSON.
6219
6371
 
6220
- \u0414\u0430\u043D\u043D\u044B\u0435 \u0441\u0434\u0435\u043B\u043A\u0438:
6372
+ Trade payload:
6221
6373
  ${JSON.stringify(payload)}
6222
6374
  ${buildAiHumanPromptAddonByStrategy(signal, payload)}
6223
6375
  `;
@@ -6225,6 +6377,14 @@ var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
6225
6377
  var userSettingsCache = /* @__PURE__ */ new Map();
6226
6378
  var aiModelCache = /* @__PURE__ */ new Map();
6227
6379
  var getAiModelCacheKey = (userName, modelName) => `${userName}::${modelName}`;
6380
+ var resolveAiModelName = (settings, requestedModelName) => {
6381
+ const explicitModelName = typeof requestedModelName === "string" ? requestedModelName.trim() : "";
6382
+ if (explicitModelName) {
6383
+ return explicitModelName;
6384
+ }
6385
+ const settingsModelName = typeof settings.AI_MODEL === "string" ? settings.AI_MODEL.trim() : "";
6386
+ return settingsModelName || DEFAULT_AI_MODEL;
6387
+ };
6228
6388
  var getOpenRouterModelKwargs = (apiEndpoint) => {
6229
6389
  const endpoint = String(apiEndpoint ?? "").trim();
6230
6390
  if (!endpoint) {
@@ -6255,30 +6415,27 @@ var getAiSettings = async (userName = "root") => {
6255
6415
  userSettingsCache.set(userName, settingsPromise);
6256
6416
  }
6257
6417
  const settings = await settingsPromise;
6258
- if (!settings.OPENAI_API_KEY || !settings.OPENAI_API_ENDPOINT) {
6418
+ if (!settings.AI_API_KEY || !settings.AI_API_ENDPOINT) {
6259
6419
  throw new Error(`AI settings are incomplete for user ${userName}`);
6260
6420
  }
6261
6421
  return settings;
6262
6422
  };
6263
- var createAiModel = async (userName = "root", modelName = DEFAULT_AI_MODEL) => {
6423
+ var createAiModel = async (userName = "root", requestedModelName) => {
6424
+ const settings = await getAiSettings(userName);
6425
+ const modelName = resolveAiModelName(settings, requestedModelName);
6264
6426
  const cacheKey = getAiModelCacheKey(userName, modelName);
6265
6427
  let modelPromise = aiModelCache.get(cacheKey);
6266
6428
  if (!modelPromise) {
6267
6429
  modelPromise = (async () => {
6268
- const [{ ChatOpenAI }, settings] = await Promise.all([
6269
- import("@langchain/openai"),
6270
- getAiSettings(userName)
6271
- ]);
6272
- const modelKwargs = getOpenRouterModelKwargs(
6273
- settings.OPENAI_API_ENDPOINT
6274
- );
6430
+ const { ChatOpenAI } = await import("@langchain/openai");
6431
+ const modelKwargs = getOpenRouterModelKwargs(settings.AI_API_ENDPOINT);
6275
6432
  return new ChatOpenAI({
6276
6433
  temperature: 0.2,
6277
6434
  modelName,
6278
- apiKey: settings.OPENAI_API_KEY,
6435
+ apiKey: settings.AI_API_KEY,
6279
6436
  ...Object.keys(modelKwargs).length ? { modelKwargs } : {},
6280
6437
  configuration: {
6281
- baseURL: settings.OPENAI_API_ENDPOINT,
6438
+ baseURL: settings.AI_API_ENDPOINT,
6282
6439
  defaultHeaders: {
6283
6440
  "HTTP-Referer": "https://tradejs.dev",
6284
6441
  "X-Title": "Inv"
@@ -6293,11 +6450,13 @@ var createAiModel = async (userName = "root", modelName = DEFAULT_AI_MODEL) => {
6293
6450
  }
6294
6451
  return modelPromise;
6295
6452
  };
6296
- var getAiModel = async (userName = "root", modelName = DEFAULT_AI_MODEL) => {
6453
+ var getAiModel = async (userName = "root", requestedModelName) => {
6454
+ const settings = await getAiSettings(userName);
6455
+ const resolvedModelName = resolveAiModelName(settings, requestedModelName);
6297
6456
  try {
6298
- return await createAiModel(userName, modelName);
6457
+ return await createAiModel(userName, resolvedModelName);
6299
6458
  } catch (error) {
6300
- aiModelCache.delete(getAiModelCacheKey(userName, modelName));
6459
+ aiModelCache.delete(getAiModelCacheKey(userName, resolvedModelName));
6301
6460
  userSettingsCache.delete(userName);
6302
6461
  throw error;
6303
6462
  }
@@ -6309,12 +6468,21 @@ var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
6309
6468
  if (options.signal) {
6310
6469
  await ensureAiStrategyPluginsLoaded();
6311
6470
  }
6312
- const [{ HumanMessage, SystemMessage }, model] = await Promise.all([
6471
+ const [{ HumanMessage, SystemMessage }, model, settings] = await Promise.all([
6313
6472
  import("@langchain/core/messages"),
6314
- getAiModel(options.userName, options.model)
6473
+ getAiModel(options.userName, options.model),
6474
+ getAiSettings(options.userName)
6315
6475
  ]);
6316
6476
  const messages = [];
6477
+ const responseLanguage = (0, import_aiLanguages.getAiResponseLanguagePromptName)(
6478
+ settings.AI_RESPONSE_LANGUAGE || import_aiLanguages.DEFAULT_AI_RESPONSE_LANGUAGE
6479
+ );
6317
6480
  messages.push(new SystemMessage(systemPrompt));
6481
+ messages.push(
6482
+ new SystemMessage(
6483
+ `Write all user-visible text fields in ${responseLanguage}. Keep field names and JSON syntax unchanged.`
6484
+ )
6485
+ );
6318
6486
  messages.push(
6319
6487
  new HumanMessage({
6320
6488
  content: [
@@ -6364,7 +6532,7 @@ var import_path2 = __toESM(require("path"));
6364
6532
  var import_puppeteer = __toESM(require("puppeteer"));
6365
6533
  var import_async = require("@tradejs/core/async");
6366
6534
  var import_logger3 = require("@tradejs/infra/logger");
6367
- var import_userSettings2 = require("@tradejs/infra/userSettings");
6535
+ var import_redis2 = require("@tradejs/infra/redis");
6368
6536
  var { APP_URL } = process.env;
6369
6537
  var SCREENSHOT_NAVIGATION_ATTEMPTS = 3;
6370
6538
  var SCREENSHOT_NAVIGATION_RETRY_DELAY_MS = 2e3;
@@ -6383,7 +6551,7 @@ var SCREENSHOT_VIEWPORT = {
6383
6551
  };
6384
6552
  var getProjectRoot = (projectRoot) => import_path2.default.resolve(getTradejsProjectCwd(projectRoot));
6385
6553
  var getScreenshotsDir = (projectRoot) => import_path2.default.join(getProjectRoot(projectRoot), "data", "screenshots");
6386
- var maskTokenInUrl = (url) => url.replace(/([?&]token=)[^&]+/i, "$1<hidden>");
6554
+ var maskTokenInUrl = (url) => url.replace(/([?&]screenshotToken=)[^&]+/i, "$1<hidden>");
6387
6555
  var getErrorFields = (value) => {
6388
6556
  if (!value || typeof value !== "object") {
6389
6557
  return [];
@@ -6467,10 +6635,13 @@ var screenDashboard = async (signal, projectRoot, userName = "root") => {
6467
6635
  const { symbol, signalId, interval } = signal;
6468
6636
  const screenshotBaseUrl = getScreenshotRenderBaseUrl();
6469
6637
  const screenshotPath = getScreenshotPath(signal, projectRoot);
6470
- const settings = await (0, import_userSettings2.getUserSettings)(userName);
6471
- const token = settings.token;
6472
- const tokenParam = typeof token === "string" && token.length > 0 ? `&token=${encodeURIComponent(token)}` : "";
6473
- const dashboardUrl = `${screenshotBaseUrl}/routes/dashboard/bybit/${symbol}/${interval}/?signalId=${signalId}&autoZoom=true&screenshot=1${tokenParam}`;
6638
+ const screenshotToken = await (0, import_redis2.createScreenshotSessionToken)(userName);
6639
+ if (!screenshotToken) {
6640
+ throw new Error(
6641
+ `Failed to create screenshot session token for ${userName}`
6642
+ );
6643
+ }
6644
+ const dashboardUrl = `${screenshotBaseUrl}/routes/dashboard/bybit/${symbol}/${interval}/?signalId=${signalId}&autoZoom=true&screenshot=1&screenshotToken=${encodeURIComponent(screenshotToken)}`;
6474
6645
  const maskedDashboardUrl = maskTokenInUrl(dashboardUrl);
6475
6646
  import_logger3.logger.info(
6476
6647
  "screenshot start: %s %sm url=%s path=%s",
@@ -6726,7 +6897,7 @@ var screenDashboard = async (signal, projectRoot, userName = "root") => {
6726
6897
  var import_async2 = require("@tradejs/core/async");
6727
6898
  var import_math = require("@tradejs/core/math");
6728
6899
  var import_logger4 = require("@tradejs/infra/logger");
6729
- var import_userSettings3 = require("@tradejs/infra/userSettings");
6900
+ var import_userSettings2 = require("@tradejs/infra/userSettings");
6730
6901
  var escapeHtml = (s) => {
6731
6902
  if (!s) return "";
6732
6903
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -6778,11 +6949,51 @@ var describeErrorValue2 = (value) => {
6778
6949
  return String(value);
6779
6950
  };
6780
6951
  var normalizeQuality = (value) => typeof value === "number" ? Math.max(1, Math.min(5, Math.round(value))) : null;
6952
+ var formatAnalysisLevel = (value) => {
6953
+ if (value == null || !Number.isFinite(value)) return null;
6954
+ if (Math.abs(value) >= 1e3) {
6955
+ return value.toFixed(0);
6956
+ }
6957
+ const digits = Math.abs(value) < 1 ? 8 : 6;
6958
+ return value.toFixed(digits).replace(/\.?0+$/, "");
6959
+ };
6960
+ var normalizeAnalysisText = (value) => {
6961
+ if (typeof value !== "string") return null;
6962
+ const clean = value.replace(/\s+/g, " ").trim();
6963
+ return clean || null;
6964
+ };
6965
+ var getReadableAnalysisComment = (value) => {
6966
+ const clean = normalizeAnalysisText(value);
6967
+ if (!clean) return null;
6968
+ if (/^(ok|approved|approved by ai|historical approval|historical reject)$/i.test(
6969
+ clean
6970
+ )) {
6971
+ return null;
6972
+ }
6973
+ return clean;
6974
+ };
6975
+ var takeUniqueAnalysisText = (usedValues, ...values) => {
6976
+ for (const value of values) {
6977
+ const clean = normalizeAnalysisText(value);
6978
+ if (!clean) continue;
6979
+ const key = clean.toLowerCase();
6980
+ if (usedValues.has(key)) continue;
6981
+ usedValues.add(key);
6982
+ return clean;
6983
+ }
6984
+ return null;
6985
+ };
6781
6986
  var formatOrderSkipReason = (reason) => {
6782
6987
  if (!reason) return "";
6783
6988
  if (reason.startsWith("AI_QUALITY_BELOW_MIN")) {
6784
6989
  return "AI_QUALITY_BELOW_MIN";
6785
6990
  }
6991
+ if (reason.startsWith("ML_THRESHOLD_NOT_MET")) {
6992
+ return "ML_THRESHOLD_NOT_MET";
6993
+ }
6994
+ if (reason === "ML_RESULT_UNAVAILABLE") {
6995
+ return "ML_RESULT_UNAVAILABLE";
6996
+ }
6786
6997
  return reason;
6787
6998
  };
6788
6999
  var getLastNumber = (value) => {
@@ -6855,7 +7066,7 @@ var parseTelegramResponse = async (response) => {
6855
7066
  }
6856
7067
  };
6857
7068
  var getTelegramSettings = async (userName = "root") => {
6858
- const settings = await (0, import_userSettings3.getUserSettings)(userName);
7069
+ const settings = await (0, import_userSettings2.getUserSettings)(userName);
6859
7070
  const token = settings.TG_BOT_TOKEN;
6860
7071
  const chatId = settings.TG_CHAT_ID;
6861
7072
  if (!token || !chatId) {
@@ -6919,6 +7130,15 @@ var sendTelegramMessage = async ({
6919
7130
  );
6920
7131
  return data;
6921
7132
  };
7133
+ var sendTextToTG = async (message, options = {}) => {
7134
+ const { token, chatId } = await getTelegramSettings(options.userName);
7135
+ return sendTelegramMessage({
7136
+ message,
7137
+ markup: options.markup,
7138
+ token,
7139
+ chatId
7140
+ });
7141
+ };
6922
7142
  var formatMessage = (signal, analysis) => {
6923
7143
  const {
6924
7144
  symbol,
@@ -7091,43 +7311,78 @@ Reason: <code>${escapeHtml(reason)}</code>`,
7091
7311
  };
7092
7312
  var formatAnalysisMessage = (signal, analysis) => {
7093
7313
  const lines = [];
7094
- const blocks = [];
7095
7314
  const quality = normalizeQuality(analysis.quality);
7315
+ const readableComment = getReadableAnalysisComment(analysis.comment);
7316
+ const approvedDirection = analysis.direction != null && analysis.direction === signal.direction && analysis.needRetest !== true;
7317
+ const oppositeDirection = analysis.direction != null && analysis.direction !== signal.direction;
7318
+ const verdict = approvedDirection ? `Approved ${signal.direction}` : oppositeDirection ? `Not approved: AI prefers ${analysis.direction}` : analysis.needRetest ? "Not approved yet" : "Not approved";
7319
+ const summary = approvedDirection ? `AI approves this ${signal.direction} setup right now.` : oppositeDirection ? `AI does not approve this ${signal.direction} setup and currently leans ${analysis.direction}.` : analysis.needRetest ? `The ${signal.direction} setup is visible, but confirmation is still missing before entry.` : `AI does not approve this ${signal.direction} setup right now.`;
7320
+ const usedValues = /* @__PURE__ */ new Set();
7096
7321
  lines.push(`<b>AI analysis ${signal.symbol}</b>`);
7097
- lines.push(`Signal direction: <b>${signal.direction}</b>`);
7098
- lines.push(`AI direction: <b>${analysis.direction ?? "NO TRADE"}</b>`);
7322
+ lines.push(`Verdict: <b>${escapeHtml(verdict)}</b>`);
7323
+ lines.push(escapeHtml(summary));
7099
7324
  if (quality) {
7100
7325
  lines.push(`Quality: <b>${quality}/5</b>`);
7101
7326
  }
7102
- if (typeof analysis.needRetest === "boolean") {
7103
- lines.push(`Need retest: <b>${analysis.needRetest ? "YES" : "NO"}</b>`);
7327
+ if (analysis.gateDecision && analysis.llmDecision) {
7328
+ lines.push(
7329
+ `Gate vs LLM: <b>${analysis.gateContradictsLlm ? "conflict" : "aligned"}</b> (gate ${analysis.gateDecision}, LLM ${analysis.llmDecision})`
7330
+ );
7104
7331
  }
7105
- if (typeof analysis.retestPrice === "number") {
7106
- lines.push(`Retest price: <b>${(0, import_math.formatNumber)(analysis.retestPrice)}</b>`);
7332
+ const happeningText = takeUniqueAnalysisText(
7333
+ usedValues,
7334
+ analysis.setup,
7335
+ readableComment
7336
+ );
7337
+ if (happeningText) {
7338
+ lines.push(`What's happening: ${escapeHtml(happeningText)}`);
7339
+ }
7340
+ const reasonText = approvedDirection ? takeUniqueAnalysisText(
7341
+ usedValues,
7342
+ analysis.qualityReason,
7343
+ analysis.confirmations,
7344
+ readableComment
7345
+ ) : takeUniqueAnalysisText(
7346
+ usedValues,
7347
+ analysis.qualityReason,
7348
+ analysis.confirmations,
7349
+ readableComment,
7350
+ analysis.btcContext
7351
+ );
7352
+ if (reasonText) {
7353
+ lines.push(
7354
+ `${approvedDirection ? "Why approved" : "Why not approved"}: ${escapeHtml(reasonText)}`
7355
+ );
7107
7356
  }
7357
+ const nextText = analysis.needRetest ? takeUniqueAnalysisText(
7358
+ usedValues,
7359
+ analysis.retestPlan,
7360
+ analysis.triggerInvalidation,
7361
+ typeof analysis.retestPrice === "number" ? `Wait for confirmation around ${formatAnalysisLevel(analysis.retestPrice)}.` : null
7362
+ ) : takeUniqueAnalysisText(
7363
+ usedValues,
7364
+ analysis.triggerInvalidation,
7365
+ analysis.retestPlan
7366
+ );
7367
+ if (nextText) {
7368
+ lines.push(`Next: ${escapeHtml(nextText)}`);
7369
+ }
7370
+ const btcText = takeUniqueAnalysisText(usedValues, analysis.btcContext);
7371
+ if (btcText) {
7372
+ lines.push(`BTC context: ${escapeHtml(btcText)}`);
7373
+ }
7374
+ const levels = [];
7108
7375
  if (typeof analysis.takeProfitPrice === "number") {
7109
- lines.push(`AI TP: <b>${(0, import_math.formatNumber)(analysis.takeProfitPrice)}</b>`);
7376
+ levels.push(`TP <b>${formatAnalysisLevel(analysis.takeProfitPrice)}</b>`);
7110
7377
  }
7111
7378
  if (typeof analysis.stopLossPrice === "number") {
7112
- lines.push(`AI SL: <b>${(0, import_math.formatNumber)(analysis.stopLossPrice)}</b>`);
7379
+ levels.push(`SL <b>${formatAnalysisLevel(analysis.stopLossPrice)}</b>`);
7113
7380
  }
7114
- const pushBlock = (title, value) => {
7115
- if (!value) return;
7116
- const clean = value.trim();
7117
- if (!clean) return;
7118
- blocks.push(`<b>${title}:</b>
7119
- ${escapeHtml(clean)}`);
7120
- };
7121
- pushBlock("Setup", analysis.setup);
7122
- pushBlock("Confirmations", analysis.confirmations);
7123
- pushBlock("BTC", analysis.btcContext);
7124
- pushBlock("Retest", analysis.retestPlan);
7125
- pushBlock("Risk/Levels", analysis.riskLevels);
7126
- pushBlock("Why Quality", analysis.qualityReason);
7127
- pushBlock("Trigger/Invalidation", analysis.triggerInvalidation);
7128
- if (blocks.length > 0) {
7129
- lines.push("");
7130
- lines.push(blocks.join("\n\n"));
7381
+ if (typeof analysis.retestPrice === "number") {
7382
+ levels.push(`Retest <b>${formatAnalysisLevel(analysis.retestPrice)}</b>`);
7383
+ }
7384
+ if (levels.length > 0) {
7385
+ lines.push(`Levels: ${levels.join(" | ")}`);
7131
7386
  }
7132
7387
  return lines.join("\n");
7133
7388
  };
@@ -7181,7 +7436,7 @@ var cleanFiles = async (dir) => {
7181
7436
  };
7182
7437
  var cleanRedis = async (area) => {
7183
7438
  let completed = 0;
7184
- const keys = await (0, import_redis2.getKeys)(area);
7439
+ const keys = await (0, import_redis3.getKeys)(area);
7185
7440
  const bar = new import_progress.default(":current/:total [:bar][:percent] :eta(s)", {
7186
7441
  total: keys.length,
7187
7442
  width: 30
@@ -7190,13 +7445,13 @@ var cleanRedis = async (area) => {
7190
7445
  try {
7191
7446
  for await (const key of keys) {
7192
7447
  completed++;
7193
- await (0, import_redis2.delKeyWithOptions)(key, { raiseOnMisconf: true });
7448
+ await (0, import_redis3.delKeyWithOptions)(key, { raiseOnMisconf: true });
7194
7449
  if (completed % 100 === 0 || completed === keys.length) {
7195
7450
  bar.tick(completed === keys.length ? completed % 100 : 100);
7196
7451
  }
7197
7452
  }
7198
7453
  } catch (e) {
7199
- if (e instanceof import_redis2.RedisWriteBlockedError || String(e).includes("MISCONF")) {
7454
+ if (e instanceof import_redis3.RedisWriteBlockedError || String(e).includes("MISCONF")) {
7200
7455
  import_logger5.logger.error(
7201
7456
  "Redis write is blocked by MISCONF (RDB save failure). Cleanup stopped early."
7202
7457
  );
@@ -7210,9 +7465,18 @@ var cleanRedis = async (area) => {
7210
7465
  }
7211
7466
  import_logger5.logger.info("");
7212
7467
  };
7213
- var update = async (connector, interval, tickers, preloadDays = import_constants.PRELOAD_DAYS) => {
7214
- const PRELOAD_START = (0, import_time.getTimestamp)(preloadDays);
7215
- const PRELOAD_END = (0, import_time.getTimestamp)();
7468
+ var update = async (connector, interval, tickers, preloadDays = import_constants.PRELOAD_DAYS, options = {}) => {
7469
+ const preloadStart = Math.trunc(
7470
+ options.preloadStart ?? (0, import_time.getTimestamp)(preloadDays)
7471
+ );
7472
+ const preloadEnd = Math.trunc(options.preloadEnd ?? (0, import_time.getTimestamp)());
7473
+ const connectorLabel = String(options.connectorLabel || "").trim();
7474
+ const preloadLabel = options.preloadStart != null || options.preloadEnd != null ? `preloadStart=${preloadStart}, preloadEnd=${preloadEnd}` : `preloadDays=${preloadDays}`;
7475
+ if (preloadStart >= preloadEnd) {
7476
+ throw new Error(
7477
+ `Invalid update preload window: start (${preloadStart}) must be less than end (${preloadEnd})`
7478
+ );
7479
+ }
7216
7480
  const bar = new import_progress.default(
7217
7481
  ":current/:total [:bar][:percent] :eta(s) :symbol",
7218
7482
  {
@@ -7222,7 +7486,7 @@ var update = async (connector, interval, tickers, preloadDays = import_constants
7222
7486
  );
7223
7487
  import_logger5.logger.info(
7224
7488
  import_chalk.default.yellow(
7225
- `update: ${tickers.length} (klineConcurrency=${KLINE_CONCURRENCY_LIMIT}, preloadDays=${preloadDays})`
7489
+ `update: ${tickers.length} (connector=${connectorLabel || "unknown"}, klineConcurrency=${KLINE_CONCURRENCY_LIMIT}, ${preloadLabel})`
7226
7490
  )
7227
7491
  );
7228
7492
  const queue = tickers.slice();
@@ -7233,8 +7497,8 @@ var update = async (connector, interval, tickers, preloadDays = import_constants
7233
7497
  try {
7234
7498
  await connector.kline({
7235
7499
  symbol,
7236
- start: PRELOAD_START,
7237
- end: PRELOAD_END,
7500
+ start: preloadStart,
7501
+ end: preloadEnd,
7238
7502
  interval,
7239
7503
  silent: true,
7240
7504
  warmOnly: true
@@ -7340,18 +7604,25 @@ var sendToAI = async (signals, userName = "root") => {
7340
7604
  import_logger5.logger.info("");
7341
7605
  };
7342
7606
  var sendToTG = async (signals, imgInterval, userName = "root") => {
7607
+ const deliverableSignals = signals.filter(
7608
+ (signal) => signal.orderStatus !== "skipped" && signal.orderStatus !== "canceled"
7609
+ );
7610
+ import_logger5.logger.info(import_chalk.default.yellow("messages:", deliverableSignals.length));
7611
+ if (!deliverableSignals.length) {
7612
+ import_logger5.logger.info("");
7613
+ return;
7614
+ }
7343
7615
  const bar = new import_progress.default(
7344
7616
  ":current/:total [:bar][:percent] :eta(s) :symbol",
7345
7617
  {
7346
- total: signals.length,
7618
+ total: deliverableSignals.length,
7347
7619
  width: 30
7348
7620
  }
7349
7621
  );
7350
- import_logger5.logger.info(import_chalk.default.yellow("messages:", signals.length));
7351
- await (0, import_async3.runWithConcurrency)(signals, 1, async (signal) => {
7622
+ await (0, import_async3.runWithConcurrency)(deliverableSignals, 1, async (signal) => {
7352
7623
  try {
7353
- const analysis = await (0, import_redis2.getData)(
7354
- import_redis2.redisKeys.analysis(signal.symbol, signal.signalId),
7624
+ const analysis = await (0, import_redis3.getData)(
7625
+ import_redis3.redisKeys.analysis(signal.symbol, signal.signalId),
7355
7626
  null
7356
7627
  );
7357
7628
  await sendSignal(signal, imgInterval, analysis, { userName });
@@ -7376,7 +7647,9 @@ var sendToTG = async (signals, imgInterval, userName = "root") => {
7376
7647
  cleanRedis,
7377
7648
  drawStatInCLI,
7378
7649
  getTickers,
7650
+ loadTradejsConfig,
7379
7651
  makeScreenshots,
7652
+ sendTextToTG,
7380
7653
  sendToAI,
7381
7654
  sendToTG,
7382
7655
  update