@tradejs/node 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2309,7 +2309,7 @@ var require_lodash = __commonJS({
2309
2309
  index = wrapper ? index : length;
2310
2310
  while (++index < length) {
2311
2311
  func = funcs[index];
2312
- var funcName = getFuncName(func), data = funcName == "wrapper" ? getData3(func) : undefined2;
2312
+ var funcName = getFuncName(func), data = funcName == "wrapper" ? getData2(func) : undefined2;
2313
2313
  if (data && isLaziable(data[0]) && data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) && !data[4].length && data[9] == 1) {
2314
2314
  wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
2315
2315
  } else {
@@ -2538,7 +2538,7 @@ var require_lodash = __commonJS({
2538
2538
  var partialsRight = partials, holdersRight = holders;
2539
2539
  partials = holders = undefined2;
2540
2540
  }
2541
- var data = isBindKey ? undefined2 : getData3(func);
2541
+ var data = isBindKey ? undefined2 : getData2(func);
2542
2542
  var newData = [
2543
2543
  func,
2544
2544
  bitmask,
@@ -2733,7 +2733,7 @@ var require_lodash = __commonJS({
2733
2733
  function getAllKeysIn(object) {
2734
2734
  return baseGetAllKeys(object, keysIn, getSymbolsIn);
2735
2735
  }
2736
- var getData3 = !metaMap ? noop : function(func) {
2736
+ var getData2 = !metaMap ? noop : function(func) {
2737
2737
  return metaMap.get(func);
2738
2738
  };
2739
2739
  function getFuncName(func) {
@@ -2961,7 +2961,7 @@ var require_lodash = __commonJS({
2961
2961
  if (func === other) {
2962
2962
  return true;
2963
2963
  }
2964
- var data = getData3(other);
2964
+ var data = getData2(other);
2965
2965
  return !!data && func === data[0];
2966
2966
  }
2967
2967
  function isMasked(func) {
@@ -5546,17 +5546,17 @@ var KLINE_CONCURRENCY_LIMIT = getPositiveIntegerEnv(
5546
5546
  "KLINE_CONCURRENCY_LIMIT",
5547
5547
  DEFAULT_KLINE_CONCURRENCY_LIMIT
5548
5548
  );
5549
- var TG_CONCURRENCY_LIMIT = 3;
5550
5549
  var AI_CONCURRENCY_LIMIT = 3;
5551
5550
  var SCREENSHOT_CONCURRENCY_LIMIT = NODE_ENV === "production" ? 1 : 2;
5552
5551
 
5553
5552
  // src/cli.ts
5554
5553
  var import_files = require("@tradejs/infra/files");
5555
- var import_redis3 = require("@tradejs/infra/redis");
5554
+ var import_redis2 = require("@tradejs/infra/redis");
5556
5555
  var import_logger5 = require("@tradejs/infra/logger");
5557
5556
 
5558
5557
  // src/ai.ts
5559
5558
  var import_redis = require("@tradejs/infra/redis");
5559
+ var import_userSettings = require("@tradejs/infra/userSettings");
5560
5560
 
5561
5561
  // src/aiShared.ts
5562
5562
  var MAX_AI_SERIES_POINTS = 5;
@@ -5587,8 +5587,23 @@ var import_indicators = require("@tradejs/core/indicators");
5587
5587
  var import_logger2 = require("@tradejs/infra/logger");
5588
5588
 
5589
5589
  // src/tradejsConfig.ts
5590
+ var import_fs = __toESM(require("fs"));
5590
5591
  var import_path = __toESM(require("path"));
5592
+ var import_module = require("module");
5593
+ var import_url = require("url");
5591
5594
  var import_logger = require("@tradejs/infra/logger");
5595
+ var CONFIG_FILE_NAMES = [
5596
+ "tradejs.config.ts",
5597
+ "tradejs.config.mts",
5598
+ "tradejs.config.js",
5599
+ "tradejs.config.mjs",
5600
+ "tradejs.config.cjs"
5601
+ ];
5602
+ var TS_MODULE_RE = /\.(cts|mts|ts)$/i;
5603
+ var cachedByCwd = /* @__PURE__ */ new Map();
5604
+ var announcedConfigFile = /* @__PURE__ */ new Set();
5605
+ var tsNodeRegistered = false;
5606
+ var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
5592
5607
  var getTradejsProjectCwd = (cwd) => {
5593
5608
  const explicit = String(cwd ?? "").trim();
5594
5609
  if (explicit) {
@@ -5600,6 +5615,202 @@ var getTradejsProjectCwd = (cwd) => {
5600
5615
  }
5601
5616
  return process.cwd();
5602
5617
  };
5618
+ var normalizeConfig = (rawConfig) => {
5619
+ if (!rawConfig || typeof rawConfig !== "object") {
5620
+ return {};
5621
+ }
5622
+ const config = rawConfig;
5623
+ const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
5624
+ const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
5625
+ const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
5626
+ return {
5627
+ strategies: strategies2,
5628
+ indicators,
5629
+ connectors
5630
+ };
5631
+ };
5632
+ var getRequireFn = (cwd = getTradejsProjectCwd()) => (0, import_module.createRequire)(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
5633
+ var ensureTsNodeRegistered = async () => {
5634
+ if (tsNodeRegistered) {
5635
+ return;
5636
+ }
5637
+ const tsNodeModule = await import("ts-node");
5638
+ const tsNode = tsNodeModule.default ?? tsNodeModule;
5639
+ tsNode.register?.({
5640
+ transpileOnly: true,
5641
+ compilerOptions: {
5642
+ module: "commonjs",
5643
+ moduleResolution: "node"
5644
+ }
5645
+ });
5646
+ tsNodeRegistered = true;
5647
+ };
5648
+ var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
5649
+ const projectRoot = getTradejsProjectCwd(cwd);
5650
+ if (tsconfigPathsRegisteredByCwd.has(projectRoot)) {
5651
+ return;
5652
+ }
5653
+ const tsconfigPathsModule = await import("tsconfig-paths");
5654
+ const loadConfig = tsconfigPathsModule.loadConfig;
5655
+ const register = tsconfigPathsModule.register;
5656
+ if (typeof loadConfig !== "function" || typeof register !== "function") {
5657
+ return;
5658
+ }
5659
+ const loadedConfig = loadConfig(projectRoot);
5660
+ if (loadedConfig.resultType !== "success") {
5661
+ return;
5662
+ }
5663
+ register({
5664
+ baseUrl: loadedConfig.absoluteBaseUrl,
5665
+ paths: loadedConfig.paths,
5666
+ addMatchAll: false
5667
+ });
5668
+ tsconfigPathsRegisteredByCwd.add(projectRoot);
5669
+ };
5670
+ var toImportSpecifier = (moduleName) => {
5671
+ if (moduleName.startsWith("file://")) {
5672
+ return moduleName;
5673
+ }
5674
+ if (import_path.default.isAbsolute(moduleName)) {
5675
+ return (0, import_url.pathToFileURL)(moduleName).href;
5676
+ }
5677
+ return moduleName;
5678
+ };
5679
+ var isTsModulePath = (moduleName) => TS_MODULE_RE.test(moduleName.split("?")[0]);
5680
+ var isRelativeModulePath = (moduleName) => moduleName.startsWith("./") || moduleName.startsWith("../");
5681
+ var isBareModuleSpecifier = (moduleName) => {
5682
+ const normalized = String(moduleName ?? "").trim();
5683
+ if (!normalized) {
5684
+ return false;
5685
+ }
5686
+ if (normalized.startsWith("file://") || import_path.default.isAbsolute(normalized) || isRelativeModulePath(normalized)) {
5687
+ return false;
5688
+ }
5689
+ return true;
5690
+ };
5691
+ var importConfigFile = async (configFilePath) => {
5692
+ const ext = import_path.default.extname(configFilePath).toLowerCase();
5693
+ const configFileUrl = `${toImportSpecifier(configFilePath)}?t=${Date.now()}`;
5694
+ if (ext === ".ts" || ext === ".mts") {
5695
+ const requireFn = getRequireFn(import_path.default.dirname(configFilePath));
5696
+ await ensureTsNodeRegistered();
5697
+ await ensureTsconfigPathsRegistered(import_path.default.dirname(configFilePath));
5698
+ return requireFn(configFilePath);
5699
+ }
5700
+ return import(
5701
+ /* webpackIgnore: true */
5702
+ configFileUrl
5703
+ );
5704
+ };
5705
+ var importTradejsModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
5706
+ const normalized = String(moduleName ?? "").trim();
5707
+ if (!normalized) {
5708
+ return {};
5709
+ }
5710
+ let modulePath = normalized;
5711
+ if (normalized.startsWith("file://")) {
5712
+ try {
5713
+ modulePath = (0, import_url.fileURLToPath)(normalized);
5714
+ } catch {
5715
+ modulePath = normalized;
5716
+ }
5717
+ }
5718
+ const requireFn = getRequireFn(
5719
+ import_path.default.isAbsolute(modulePath) ? import_path.default.dirname(modulePath) : cwd
5720
+ );
5721
+ if (isTsModulePath(modulePath)) {
5722
+ await ensureTsNodeRegistered();
5723
+ await ensureTsconfigPathsRegistered(cwd);
5724
+ return requireFn(modulePath);
5725
+ }
5726
+ if (isBareModuleSpecifier(normalized)) {
5727
+ await ensureTsconfigPathsRegistered(cwd);
5728
+ return requireFn(normalized);
5729
+ }
5730
+ try {
5731
+ return await import(
5732
+ /* webpackIgnore: true */
5733
+ toImportSpecifier(normalized)
5734
+ );
5735
+ } catch (error) {
5736
+ if (isTsModulePath(modulePath)) {
5737
+ await ensureTsNodeRegistered();
5738
+ await ensureTsconfigPathsRegistered(cwd);
5739
+ return requireFn(modulePath);
5740
+ }
5741
+ throw error;
5742
+ }
5743
+ };
5744
+ var resolveExportedConfig = (moduleExports) => {
5745
+ const candidate = moduleExports && typeof moduleExports === "object" && "default" in moduleExports ? moduleExports.default : moduleExports;
5746
+ return normalizeConfig(candidate);
5747
+ };
5748
+ var findConfigFilePath = (cwd) => {
5749
+ let currentDir = import_path.default.resolve(cwd);
5750
+ while (true) {
5751
+ for (const fileName of CONFIG_FILE_NAMES) {
5752
+ const fullPath = import_path.default.join(currentDir, fileName);
5753
+ if (import_fs.default.existsSync(fullPath) && import_fs.default.statSync(fullPath).isFile()) {
5754
+ return fullPath;
5755
+ }
5756
+ }
5757
+ const parentDir = import_path.default.dirname(currentDir);
5758
+ if (parentDir === currentDir) {
5759
+ return null;
5760
+ }
5761
+ currentDir = parentDir;
5762
+ }
5763
+ };
5764
+ var resolvePluginModuleSpecifier = (moduleName, cwd = getTradejsProjectCwd()) => {
5765
+ const normalized = String(moduleName ?? "").trim();
5766
+ if (!normalized) {
5767
+ return "";
5768
+ }
5769
+ if (normalized.startsWith("file://")) {
5770
+ try {
5771
+ return (0, import_url.fileURLToPath)(normalized);
5772
+ } catch {
5773
+ return normalized;
5774
+ }
5775
+ }
5776
+ if (import_path.default.isAbsolute(normalized)) {
5777
+ return normalized;
5778
+ }
5779
+ if (isRelativeModulePath(normalized)) {
5780
+ return import_path.default.resolve(cwd, normalized);
5781
+ }
5782
+ return normalized;
5783
+ };
5784
+ var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
5785
+ const cached = cachedByCwd.get(cwd);
5786
+ if (cached) {
5787
+ return cached;
5788
+ }
5789
+ const configFilePath = findConfigFilePath(cwd);
5790
+ if (!configFilePath) {
5791
+ cachedByCwd.set(cwd, {});
5792
+ return {};
5793
+ }
5794
+ try {
5795
+ const moduleExports = await importConfigFile(configFilePath);
5796
+ const config = resolveExportedConfig(moduleExports);
5797
+ cachedByCwd.set(cwd, config);
5798
+ if (!announcedConfigFile.has(configFilePath)) {
5799
+ announcedConfigFile.add(configFilePath);
5800
+ import_logger.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
5801
+ }
5802
+ return config;
5803
+ } catch (error) {
5804
+ import_logger.logger.log(
5805
+ "warn",
5806
+ "Failed to load TradeJS config from %s: %s",
5807
+ configFilePath,
5808
+ String(error)
5809
+ );
5810
+ cachedByCwd.set(cwd, {});
5811
+ return {};
5812
+ }
5813
+ };
5603
5814
 
5604
5815
  // src/strategy/manifests.ts
5605
5816
  var createStrategyRegistryState = () => ({
@@ -5620,6 +5831,144 @@ var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
5620
5831
  state
5621
5832
  };
5622
5833
  };
5834
+ var toUniqueModules = (modules = []) => [
5835
+ ...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
5836
+ ];
5837
+ var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
5838
+ const config = await loadTradejsConfig(cwd);
5839
+ return {
5840
+ strategyModules: toUniqueModules(config.strategies),
5841
+ indicatorModules: toUniqueModules(config.indicators)
5842
+ };
5843
+ };
5844
+ var extractModuleEntries = (moduleExport, key) => {
5845
+ if (!moduleExport || typeof moduleExport !== "object") {
5846
+ return null;
5847
+ }
5848
+ const candidate = moduleExport;
5849
+ if (Array.isArray(candidate[key])) {
5850
+ return candidate[key];
5851
+ }
5852
+ const defaultExport = candidate.default;
5853
+ if (defaultExport && Array.isArray(defaultExport[key])) {
5854
+ return defaultExport[key];
5855
+ }
5856
+ return null;
5857
+ };
5858
+ var extractStrategyPluginDefinition = (moduleExport) => {
5859
+ const strategyEntries = extractModuleEntries(
5860
+ moduleExport,
5861
+ "strategyEntries"
5862
+ );
5863
+ return strategyEntries ? { strategyEntries } : null;
5864
+ };
5865
+ var extractIndicatorPluginDefinition = (moduleExport) => {
5866
+ const indicatorEntries = extractModuleEntries(
5867
+ moduleExport,
5868
+ "indicatorEntries"
5869
+ );
5870
+ return indicatorEntries ? { indicatorEntries } : null;
5871
+ };
5872
+ var registerEntries = (entries, source, state) => {
5873
+ for (const entry of entries) {
5874
+ const strategyName = entry.manifest?.name;
5875
+ if (!strategyName) {
5876
+ import_logger2.logger.warn("Skip strategy entry without name from %s", source);
5877
+ continue;
5878
+ }
5879
+ if (state.strategyCreators.has(strategyName)) {
5880
+ import_logger2.logger.warn(
5881
+ 'Skip duplicate strategy "%s" from %s: already registered',
5882
+ strategyName,
5883
+ source
5884
+ );
5885
+ continue;
5886
+ }
5887
+ state.strategyCreators.set(strategyName, entry.creator);
5888
+ state.strategyManifestsMap.set(strategyName, entry.manifest);
5889
+ }
5890
+ };
5891
+ var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
5892
+ if (typeof importTradejsModule === "function") {
5893
+ return importTradejsModule(moduleName, cwd);
5894
+ }
5895
+ return import(
5896
+ /* webpackIgnore: true */
5897
+ moduleName
5898
+ );
5899
+ };
5900
+ var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
5901
+ const { projectRoot, state } = getStrategyRegistryState(cwd);
5902
+ if (!state.pluginsLoadPromise) {
5903
+ (0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
5904
+ state.pluginsLoadPromise = (async () => {
5905
+ const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
5906
+ const strategySet = new Set(strategyModules);
5907
+ const indicatorSet = new Set(indicatorModules);
5908
+ const pluginModuleNames = [
5909
+ .../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
5910
+ ];
5911
+ if (!pluginModuleNames.length) {
5912
+ return;
5913
+ }
5914
+ for (const moduleName of pluginModuleNames) {
5915
+ try {
5916
+ const resolvedModuleName = resolvePluginModuleSpecifier(
5917
+ moduleName,
5918
+ projectRoot
5919
+ );
5920
+ const moduleExport = await importStrategyPluginModule(
5921
+ resolvedModuleName,
5922
+ projectRoot
5923
+ );
5924
+ if (strategySet.has(moduleName)) {
5925
+ const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
5926
+ if (!pluginDefinition) {
5927
+ import_logger2.logger.warn(
5928
+ 'Skip strategy plugin "%s": export { strategyEntries } is missing',
5929
+ moduleName
5930
+ );
5931
+ } else {
5932
+ registerEntries(
5933
+ pluginDefinition.strategyEntries,
5934
+ moduleName,
5935
+ state
5936
+ );
5937
+ }
5938
+ }
5939
+ if (indicatorSet.has(moduleName)) {
5940
+ const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
5941
+ if (!indicatorPluginDefinition) {
5942
+ import_logger2.logger.warn(
5943
+ 'Skip indicator plugin "%s": export { indicatorEntries } is missing',
5944
+ moduleName
5945
+ );
5946
+ } else {
5947
+ (0, import_indicators.registerIndicatorEntries)(
5948
+ indicatorPluginDefinition.indicatorEntries,
5949
+ moduleName,
5950
+ projectRoot
5951
+ );
5952
+ }
5953
+ }
5954
+ if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
5955
+ import_logger2.logger.warn(
5956
+ 'Skip plugin "%s": no strategy/indicator sections requested in config',
5957
+ moduleName
5958
+ );
5959
+ }
5960
+ } catch (error) {
5961
+ import_logger2.logger.warn(
5962
+ 'Failed to load plugin "%s": %s',
5963
+ moduleName,
5964
+ String(error)
5965
+ );
5966
+ }
5967
+ }
5968
+ })();
5969
+ }
5970
+ await state.pluginsLoadPromise;
5971
+ };
5623
5972
  var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
5624
5973
  if (!name) {
5625
5974
  return void 0;
@@ -5678,6 +6027,11 @@ var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(
5678
6027
  signal,
5679
6028
  payload
5680
6029
  }) ?? "";
6030
+ var postProcessAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => getSignalAiAdapter(signal).postProcessAnalysis?.({
6031
+ signal,
6032
+ payload,
6033
+ analysis
6034
+ }) ?? analysis;
5681
6035
 
5682
6036
  // src/ai.ts
5683
6037
  var parseAIResponse = (input) => {
@@ -5734,15 +6088,17 @@ var normalizeAnalysis = (raw) => {
5734
6088
  };
5735
6089
  };
5736
6090
  var buildAiSystemPrompt = (signal) => `
5737
- \u0422\u044B \u2014 \u043F\u043E\u043C\u043E\u0449\u043D\u0438\u043A \u043A\u0440\u0438\u043F\u0442\u043E-\u0442\u0440\u0435\u0439\u0434\u0435\u0440\u0430.
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.
5738
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.
5739
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.
5740
6094
 
5741
6095
  \u0412\u0410\u0416\u041D\u041E:
5742
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.
5743
- - \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 \u043D\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0442\u044C\u0441\u044F \u0441 \u043D\u0438\u043C\u0438.
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.
5744
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.
5745
- - \u0422\u0432\u043E\u044F \u0446\u0435\u043B\u044C: \u0434\u0430\u0442\u044C \u041F\u0420\u0410\u041A\u0422\u0418\u0427\u041D\u042B\u0419 short-term trade plan, \u0430 \u043D\u0435 \u043E\u0431\u0449\u0438\u0435 \u0441\u043B\u043E\u0432\u0430.
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.
5746
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.
5747
6103
  - \u041F\u0438\u0448\u0438 comment \u043F\u043E-\u0440\u0443\u0441\u0441\u043A\u0438.
5748
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.
@@ -5768,19 +6124,19 @@ var buildAiSystemPrompt = (signal) => `
5768
6124
  - \u041D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u0439 \u0434\u0440\u0443\u0433\u0438\u0435 \u043F\u043E\u043B\u044F.
5769
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.
5770
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).
5771
- - "direction" \u2014 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0441\u0434\u0435\u043B\u043A\u0438: \u043B\u0438\u0431\u043E \u0420\u041E\u0412\u041D\u041E payload.signal.direction, \u043B\u0438\u0431\u043E null (\u0435\u0441\u043B\u0438 \u0441\u0434\u0435\u043B\u043A\u0443 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435 \u043E\u0434\u043E\u0431\u0440\u044F\u0435\u0448\u044C). \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.
5772
- - "quality" \u2014 \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u043E \u0412\u0425\u041E\u0414\u0410 \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 \u0438\u0434\u0435\u044F \u0441\u0435\u0442\u0430\u043F\u0430.
5773
- - "needRetest" \u2014 \u043D\u0443\u0436\u043D\u043E \u043B\u0438 \u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F \u0440\u0435\u0442\u0435\u0441\u0442\u0430 \u043F\u0435\u0440\u0435\u0434 \u0432\u0445\u043E\u0434\u043E\u043C \u043F\u043E \u0442\u0435\u043A\u0443\u0449\u0435\u043C\u0443 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044E.
5774
- - "retestPrice" \u2014 \u0446\u0435\u043D\u0430/\u0443\u0440\u043E\u0432\u0435\u043D\u044C, \u043F\u043E \u043A\u043E\u0442\u043E\u0440\u043E\u043C\u0443 \u0431\u0443\u0434\u0435\u043C \u043E\u0446\u0435\u043D\u0438\u0432\u0430\u0442\u044C \u0440\u0435\u0442\u0435\u0441\u0442 (\u0438\u043B\u0438 null, \u0435\u0441\u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D / \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u043D\u0435 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0451\u043D).
5775
- - "takeProfitPrice" \u0438 "stopLossPrice" \u2014 \u0442\u0432\u043E\u0438 \u0443\u0440\u043E\u0432\u043D\u0438 \u0434\u043B\u044F \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u043E\u0433\u043E \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F. \u0415\u0441\u043B\u0438 \u0441\u0434\u0435\u043B\u043A\u0438 \u043D\u0435\u0442, \u043E\u0431\u0430 null.
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.
5776
6132
  - \u0420\u0430\u0437\u0431\u0438\u0432\u0430\u0439 \u0430\u043D\u0430\u043B\u0438\u0437 \u043D\u0430 \u043F\u043E\u043B\u044F:
5777
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.
5778
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.
5779
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.
5780
- - "retestPlan" \u2014 \u0447\u0442\u043E \u0436\u0434\u0430\u0442\u044C \u043F\u043E \u0440\u0435\u0442\u0435\u0441\u0442\u0443 (\u0438\u043B\u0438 \u043F\u043E\u0447\u0435\u043C\u0443 \u0440\u0435\u0442\u0435\u0441\u0442 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D).
5781
- - "riskLevels" \u2014 \u043A\u0440\u0430\u0442\u043A\u043E \u043F\u0440\u043E TP/SL \u0438 \u0440\u0438\u0441\u043A-\u043F\u0440\u043E\u0444\u0438\u043B\u044C.
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.
5782
6138
  - "qualityReason" \u2014 \u043F\u043E\u0447\u0435\u043C\u0443 quality \u0438\u043C\u0435\u043D\u043D\u043E \u0442\u0430\u043A\u043E\u0439.
5783
- - "triggerInvalidation" \u2014 \u0447\u0442\u043E \u0434\u043E\u043B\u0436\u043D\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \u0432\u0445\u043E\u0434 / \u0447\u0442\u043E \u043E\u0442\u043C\u0435\u043D\u044F\u0435\u0442 \u0438\u0434\u0435\u044E.
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.
5784
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.
5785
6141
 
5786
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.
@@ -5794,6 +6150,10 @@ var buildAiSystemPrompt = (signal) => `
5794
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.
5795
6151
  - payload.indicators:
5796
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.
6153
+ - 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.
5797
6157
  \u041F\u0430\u0442\u0442\u0435\u0440\u043D\u044B \u043A\u043B\u044E\u0447\u0435\u0439:
5798
6158
  \u2022 \u043C\u043E\u043D\u0435\u0442\u0430: maFast, atrPct, macd..., candles15m/candles1h/candles4h/candles1d, \u0430 \u0442\u0430\u043A\u0436\u0435 *1h/*4h/*1d
5799
6159
  \u2022 BTC: btcMaFast, btcAtr, btcMacd..., btcCandles*, \u0430 \u0442\u0430\u043A\u0436\u0435 btc*1h/*4h/*1d
@@ -5801,41 +6161,42 @@ var buildAiSystemPrompt = (signal) => `
5801
6161
 
5802
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):
5803
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.
5804
- 2) \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.
5805
- 3) \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).
5806
- 4) \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 \u0440\u0435\u0442\u0435\u0441\u0442\u0443.
5807
- 5) \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.
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.
5808
6169
 
5809
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:
5810
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.
5811
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.
5812
- - \u0415\u0441\u043B\u0438 \u043D\u0435 \u043E\u0434\u043E\u0431\u0440\u044F\u0435\u0448\u044C \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0441\u0434\u0435\u043B\u043A\u0443 (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.
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.
5813
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".
5814
6176
 
5815
6177
  \u041F\u0440\u0430\u0432\u0438\u043B\u0430 \u0434\u043B\u044F direction / TP / SL:
5816
- - direction = LONG \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u043E\u0435 \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u0435 \u0432\u0432\u0435\u0440\u0445 \u043E\u0431\u043E\u0441\u043D\u043E\u0432\u0430\u043D\u043E; SHORT \u2014 \u0432\u043D\u0438\u0437; \u0438\u043D\u0430\u0447\u0435 null.
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.
5817
6179
  - \u0414\u043B\u044F LONG \u043E\u0431\u044B\u0447\u043D\u043E stopLossPrice < currentPrice < takeProfitPrice.
5818
6180
  - \u0414\u043B\u044F SHORT \u043E\u0431\u044B\u0447\u043D\u043E takeProfitPrice < currentPrice < stopLossPrice.
5819
- - TP/SL \u0434\u043E\u043B\u0436\u043D\u044B \u0431\u044B\u0442\u044C \u0440\u0435\u0430\u043B\u0438\u0441\u0442\u0438\u0447\u043D\u044B\u043C\u0438 \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0446\u0435\u043D\u044B (\u043D\u0435 \u0441\u0442\u0430\u0432\u044C absurdly far/near \u0443\u0440\u043E\u0432\u043D\u0438).
5820
- - \u0422\u0440\u0435\u0431\u0443\u0435\u043C\u043E\u0435 \u0441\u043E\u043E\u0442\u043D\u043E\u0448\u0435\u043D\u0438\u0435 reward:risk \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u0445\u0443\u0436\u0435 1:3 (\u0442\u043E \u0435\u0441\u0442\u044C reward/risk >= 0.33).
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.
5821
6182
  - \u0415\u0441\u043B\u0438 direction = null, \u0442\u043E takeProfitPrice = null \u0438 stopLossPrice = null.
5822
6183
  - \u0415\u0441\u043B\u0438 needRetest = false, \u0442\u043E retestPrice = null.
5823
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.
5824
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.
5825
6186
 
5826
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):
5827
- - 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, \u0432\u0445\u043E\u0434 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D
5828
- - 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, \u043B\u0443\u0447\u0448\u0435 \u043F\u0440\u043E\u043F\u0443\u0441\u043A
5829
- - 3: \u0441\u0440\u0435\u0434\u043D\u0438\u0439 \u0441\u0435\u0442\u0430\u043F, \u0435\u0441\u0442\u044C \u0438\u0434\u0435\u044F, \u043D\u043E \u0435\u0441\u0442\u044C \u0437\u0430\u043C\u0435\u0442\u043D\u044B\u0435 \u0440\u0438\u0441\u043A\u0438/\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B
5830
- - 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, \u0440\u0438\u0441\u043A\u0438 \u043F\u043E\u043D\u044F\u0442\u043D\u044B
5831
- - 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 + \u0430\u0434\u0435\u043A\u0432\u0430\u0442\u043D\u044B\u0439 \u0440\u0438\u0441\u043A
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
5832
6193
 
5833
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):
5834
- - \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" \u0441\u0434\u0435\u043B\u043A\u0443 \u0432 "confirmations".
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".
5835
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).
5836
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).
5837
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.
5838
- - \u0415\u0441\u043B\u0438 \u043D\u0435 \u0432\u0445\u043E\u0434\u0438\u0448\u044C (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 \u0432\u0445\u043E\u0434\u0430.
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.
5839
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.
5840
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.
5841
6202
 
@@ -5845,49 +6206,121 @@ var buildAiSystemPrompt = (signal) => `
5845
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.
5846
6207
 
5847
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):
5848
- {"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 \u0432\u0445\u043E\u0434 \u0441\u0435\u0439\u0447\u0430\u0441 \u043B\u0443\u0447\u0448\u0435 \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \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 \u043C\u0435\u0448\u0430\u0435\u0442 LONG.","retestPlan":"\u0416\u0434\u0430\u0442\u044C \u0440\u0435\u0442\u0435\u0441\u0442 \u0437\u043E\u043D\u044B 100.2 \u0438 \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u0432\u044B\u0448\u0435 \u043D\u0435\u0435 \u043F\u0435\u0440\u0435\u0434 \u0432\u0445\u043E\u0434\u043E\u043C.","riskLevels":"TP/SL \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, \u0440\u0438\u0441\u043A \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u0438\u0440\u0443\u0435\u043C\u044B\u0439.","qualityReason":"Quality=4: \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430 \u0445\u043E\u0440\u043E\u0448\u0430\u044F, \u043D\u043E \u043D\u0443\u0436\u0435\u043D \u0440\u0435\u0442\u0435\u0441\u0442 \u0434\u043B\u044F \u0431\u043E\u043B\u0435\u0435 \u0447\u0438\u0441\u0442\u043E\u0433\u043E timing.","triggerInvalidation":"\u0412\u0445\u043E\u0434 \u043F\u043E\u0441\u043B\u0435 \u0440\u0435\u0442\u0435\u0441\u0442\u0430 \u0438 \u0443\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u044F \u0443\u0440\u043E\u0432\u043D\u044F; \u043E\u0442\u043C\u0435\u043D\u0430 \u043F\u0440\u0438 \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0435 \u043F\u043E\u0434 \u043B\u0438\u043D\u0438\u044E."}
5849
- {"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 \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 \u0438\u0434\u0435\u044E.","retestPlan":"\u0420\u0435\u0442\u0435\u0441\u0442 \u043F\u043E\u043A\u0430 \u043D\u0435 \u043E\u0446\u0435\u043D\u0438\u0432\u0430\u0435\u043C, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u043D\u0443\u0436\u0435\u043D \u0441\u0430\u043C \u0444\u0430\u043A\u0442 \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0431\u043E\u044F.","riskLevels":"\u0421\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0442 \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0433\u043E \u0441\u043E\u043E\u0442\u043D\u043E\u0448\u0435\u043D\u0438\u044F \u0434\u043B\u044F \u0432\u0445\u043E\u0434\u0430.","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."}
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."}
5850
6211
 
5851
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.
5852
6213
  ${signal ? buildAiSystemPromptAddonByStrategy(signal) : ""}
5853
6214
  `;
5854
6215
  var buildAiPayload = (signal) => buildAiPayloadByStrategy(signal);
5855
6216
  var buildAiHumanPrompt = (signal, payload = buildAiPayload(signal)) => `
5856
- \u041F\u0440\u043E\u0430\u043D\u0430\u043B\u0438\u0437\u0438\u0440\u0443\u0439 \u0441\u0434\u0435\u043B\u043A\u0443 \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}.
5857
- \u041E\u0446\u0435\u043D\u0438 \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0441\u0434\u0435\u043B\u043A\u0443 (\u0431\u0435\u0437 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u043F\u043E\u043B\u043E\u0436\u043D\u043E\u0433\u043E \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F): \u043E\u0434\u043E\u0431\u0440\u044F\u0435\u0448\u044C \u043B\u0438 \u0432\u0445\u043E\u0434 \u0441\u0435\u0439\u0447\u0430\u0441, \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u043E \u0432\u0445\u043E\u0434\u0430 \u0441\u0435\u0439\u0447\u0430\u0441, \u043D\u0443\u0436\u0435\u043D \u043B\u0438 \u0440\u0435\u0442\u0435\u0441\u0442 \u0438 \u043F\u043E \u043A\u0430\u043A\u043E\u0439 \u0446\u0435\u043D\u0435 \u0435\u0433\u043E \u043E\u0446\u0435\u043D\u0438\u0432\u0430\u0442\u044C, \u0430 \u0442\u0430\u043A\u0436\u0435 \u0434\u0430\u0439 TP/SL \u0438 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 \u0441\u0442\u0440\u043E\u0433\u043E \u0432 \u0437\u0430\u0434\u0430\u043D\u043D\u043E\u043C JSON-\u0444\u043E\u0440\u043C\u0430\u0442\u0435.
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.
5858
6219
 
5859
6220
  \u0414\u0430\u043D\u043D\u044B\u0435 \u0441\u0434\u0435\u043B\u043A\u0438:
5860
6221
  ${JSON.stringify(payload)}
5861
6222
  ${buildAiHumanPromptAddonByStrategy(signal, payload)}
5862
6223
  `;
5863
- var askAI = async (signal) => {
5864
- const [{ ChatOpenAI }, { HumanMessage, SystemMessage }] = await Promise.all([
5865
- import("@langchain/openai"),
5866
- import("@langchain/core/messages")
6224
+ var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
6225
+ var userSettingsCache = /* @__PURE__ */ new Map();
6226
+ var aiModelCache = /* @__PURE__ */ new Map();
6227
+ var getAiModelCacheKey = (userName, modelName) => `${userName}::${modelName}`;
6228
+ var getOpenRouterModelKwargs = (apiEndpoint) => {
6229
+ const endpoint = String(apiEndpoint ?? "").trim();
6230
+ if (!endpoint) {
6231
+ return {};
6232
+ }
6233
+ let hostname = "";
6234
+ try {
6235
+ hostname = new URL(endpoint).hostname;
6236
+ } catch {
6237
+ hostname = endpoint;
6238
+ }
6239
+ if (!hostname.toLowerCase().includes("openrouter")) {
6240
+ return {};
6241
+ }
6242
+ return {
6243
+ provider: {
6244
+ ignore: ["azure"]
6245
+ }
6246
+ };
6247
+ };
6248
+ var getAiSettings = async (userName = "root") => {
6249
+ let settingsPromise = userSettingsCache.get(userName);
6250
+ if (!settingsPromise) {
6251
+ settingsPromise = (0, import_userSettings.getUserSettings)(userName);
6252
+ settingsPromise.catch(() => {
6253
+ userSettingsCache.delete(userName);
6254
+ });
6255
+ userSettingsCache.set(userName, settingsPromise);
6256
+ }
6257
+ const settings = await settingsPromise;
6258
+ if (!settings.OPENAI_API_KEY || !settings.OPENAI_API_ENDPOINT) {
6259
+ throw new Error(`AI settings are incomplete for user ${userName}`);
6260
+ }
6261
+ return settings;
6262
+ };
6263
+ var createAiModel = async (userName = "root", modelName = DEFAULT_AI_MODEL) => {
6264
+ const cacheKey = getAiModelCacheKey(userName, modelName);
6265
+ let modelPromise = aiModelCache.get(cacheKey);
6266
+ if (!modelPromise) {
6267
+ 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
+ );
6275
+ return new ChatOpenAI({
6276
+ temperature: 0.2,
6277
+ modelName,
6278
+ apiKey: settings.OPENAI_API_KEY,
6279
+ ...Object.keys(modelKwargs).length ? { modelKwargs } : {},
6280
+ configuration: {
6281
+ baseURL: settings.OPENAI_API_ENDPOINT,
6282
+ defaultHeaders: {
6283
+ "HTTP-Referer": "https://tradejs.dev",
6284
+ "X-Title": "Inv"
6285
+ }
6286
+ }
6287
+ });
6288
+ })();
6289
+ modelPromise.catch(() => {
6290
+ aiModelCache.delete(cacheKey);
6291
+ });
6292
+ aiModelCache.set(cacheKey, modelPromise);
6293
+ }
6294
+ return modelPromise;
6295
+ };
6296
+ var getAiModel = async (userName = "root", modelName = DEFAULT_AI_MODEL) => {
6297
+ try {
6298
+ return await createAiModel(userName, modelName);
6299
+ } catch (error) {
6300
+ aiModelCache.delete(getAiModelCacheKey(userName, modelName));
6301
+ userSettingsCache.delete(userName);
6302
+ throw error;
6303
+ }
6304
+ };
6305
+ var ensureAiStrategyPluginsLoaded = async () => {
6306
+ await ensureStrategyPluginsLoaded();
6307
+ };
6308
+ var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
6309
+ if (options.signal) {
6310
+ await ensureAiStrategyPluginsLoaded();
6311
+ }
6312
+ const [{ HumanMessage, SystemMessage }, model] = await Promise.all([
6313
+ import("@langchain/core/messages"),
6314
+ getAiModel(options.userName, options.model)
5867
6315
  ]);
5868
- const { symbol } = signal;
5869
6316
  const messages = [];
5870
- const model = new ChatOpenAI({
5871
- temperature: 0.2,
5872
- //modelName: 'anthropic/claude-sonnet-4.5',
5873
- modelName: "google/gemini-3.1-pro-preview",
5874
- openAIApiKey: process.env.OPENAI_API_KEY,
5875
- configuration: {
5876
- baseURL: process.env.OPENAI_API_ENDPOINT || "https://api.openai.com/v1",
5877
- defaultHeaders: {
5878
- "HTTP-Referer": "https://tradejs.dev",
5879
- "X-Title": "Inv"
5880
- }
5881
- }
5882
- });
5883
- messages.push(new SystemMessage(buildAiSystemPrompt(signal)));
5884
- const payload = buildAiPayload(signal);
6317
+ messages.push(new SystemMessage(systemPrompt));
5885
6318
  messages.push(
5886
6319
  new HumanMessage({
5887
6320
  content: [
5888
6321
  {
5889
6322
  type: "text",
5890
- text: buildAiHumanPrompt(signal, payload)
6323
+ text: humanPrompt
5891
6324
  }
5892
6325
  ]
5893
6326
  })
@@ -5896,7 +6329,31 @@ var askAI = async (signal) => {
5896
6329
  const parsed = parseAIResponse(
5897
6330
  normalizeResponseContent(response.content)
5898
6331
  );
5899
- const content = normalizeAnalysis(parsed);
6332
+ const normalized = normalizeAnalysis(parsed);
6333
+ if (!options.signal) {
6334
+ return normalized;
6335
+ }
6336
+ return postProcessAiAnalysisByStrategy(
6337
+ options.signal,
6338
+ normalized,
6339
+ options.payload
6340
+ );
6341
+ };
6342
+ var askAI = async (signal, options = {}) => {
6343
+ const { symbol } = signal;
6344
+ await ensureAiStrategyPluginsLoaded();
6345
+ const payload = buildAiPayload(signal);
6346
+ const content = await runAiPrompt(
6347
+ {
6348
+ systemPrompt: buildAiSystemPrompt(signal),
6349
+ humanPrompt: buildAiHumanPrompt(signal, payload)
6350
+ },
6351
+ {
6352
+ ...options,
6353
+ signal,
6354
+ payload
6355
+ }
6356
+ );
5900
6357
  await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
5901
6358
  return content;
5902
6359
  };
@@ -5907,7 +6364,7 @@ var import_path2 = __toESM(require("path"));
5907
6364
  var import_puppeteer = __toESM(require("puppeteer"));
5908
6365
  var import_async = require("@tradejs/core/async");
5909
6366
  var import_logger3 = require("@tradejs/infra/logger");
5910
- var import_redis2 = require("@tradejs/infra/redis");
6367
+ var import_userSettings2 = require("@tradejs/infra/userSettings");
5911
6368
  var { APP_URL } = process.env;
5912
6369
  var SCREENSHOT_NAVIGATION_ATTEMPTS = 3;
5913
6370
  var SCREENSHOT_NAVIGATION_RETRY_DELAY_MS = 2e3;
@@ -5927,13 +6384,59 @@ var SCREENSHOT_VIEWPORT = {
5927
6384
  var getProjectRoot = (projectRoot) => import_path2.default.resolve(getTradejsProjectCwd(projectRoot));
5928
6385
  var getScreenshotsDir = (projectRoot) => import_path2.default.join(getProjectRoot(projectRoot), "data", "screenshots");
5929
6386
  var maskTokenInUrl = (url) => url.replace(/([?&]token=)[^&]+/i, "$1<hidden>");
6387
+ var getErrorFields = (value) => {
6388
+ if (!value || typeof value !== "object") {
6389
+ return [];
6390
+ }
6391
+ const record = value;
6392
+ const fields = [
6393
+ ["name", record.name],
6394
+ ["code", record.code],
6395
+ ["errno", record.errno],
6396
+ ["type", record.type],
6397
+ ["syscall", record.syscall],
6398
+ ["hostname", record.hostname],
6399
+ ["host", record.host],
6400
+ ["address", record.address],
6401
+ ["port", record.port]
6402
+ ];
6403
+ return fields.filter(([, fieldValue]) => fieldValue != null && String(fieldValue).trim()).map(([key, fieldValue]) => `${key}=${String(fieldValue)}`);
6404
+ };
6405
+ var describeErrorValue = (value) => {
6406
+ if (value == null) {
6407
+ return "";
6408
+ }
6409
+ if (typeof value === "string") {
6410
+ return value;
6411
+ }
6412
+ if (value instanceof Error) {
6413
+ const message = value.message?.trim() || "";
6414
+ const fields = getErrorFields(value);
6415
+ return [message, ...fields].filter(Boolean).join(" ");
6416
+ }
6417
+ if (typeof value === "object") {
6418
+ const fields = getErrorFields(value);
6419
+ if (fields.length) {
6420
+ return fields.join(" ");
6421
+ }
6422
+ try {
6423
+ return JSON.stringify(value);
6424
+ } catch {
6425
+ return String(value);
6426
+ }
6427
+ }
6428
+ return String(value);
6429
+ };
5930
6430
  var getErrorMessage = (error) => {
5931
6431
  const maybeError = error;
5932
- const message = maybeError?.message || String(error);
6432
+ const message = describeErrorValue(error) || String(error);
5933
6433
  if (maybeError?.cause == null) {
5934
6434
  return message;
5935
6435
  }
5936
- const cause = maybeError.cause instanceof Error ? maybeError.cause.message : String(maybeError.cause);
6436
+ const cause = describeErrorValue(maybeError.cause);
6437
+ if (!cause) {
6438
+ return message;
6439
+ }
5937
6440
  return `${message}; cause: ${cause}`;
5938
6441
  };
5939
6442
  var truncateText = (value, maxLength = SCREENSHOT_CONSOLE_TEXT_LIMIT) => value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
@@ -5960,13 +6463,13 @@ var getScreenshotPath = ({ symbol, signalId, interval }, projectRoot) => {
5960
6463
  getScreenshotFilename({ symbol, signalId, interval })
5961
6464
  );
5962
6465
  };
5963
- var screenDashboard = async (signal, projectRoot) => {
6466
+ var screenDashboard = async (signal, projectRoot, userName = "root") => {
5964
6467
  const { symbol, signalId, interval } = signal;
5965
6468
  const screenshotBaseUrl = getScreenshotRenderBaseUrl();
5966
6469
  const screenshotPath = getScreenshotPath(signal, projectRoot);
5967
- const rootUser = await (0, import_redis2.getData)(import_redis2.redisKeys.user("root"), null);
5968
- const token2 = rootUser && typeof rootUser === "object" ? rootUser.token : null;
5969
- const tokenParam = typeof token2 === "string" && token2.length > 0 ? `&token=${encodeURIComponent(token2)}` : "";
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)}` : "";
5970
6473
  const dashboardUrl = `${screenshotBaseUrl}/routes/dashboard/bybit/${symbol}/${interval}/?signalId=${signalId}&autoZoom=true&screenshot=1${tokenParam}`;
5971
6474
  const maskedDashboardUrl = maskTokenInUrl(dashboardUrl);
5972
6475
  import_logger3.logger.info(
@@ -5991,6 +6494,7 @@ var screenDashboard = async (signal, projectRoot) => {
5991
6494
  ]
5992
6495
  });
5993
6496
  const browserProcess = browser.process();
6497
+ let browserCloseRequested = false;
5994
6498
  try {
5995
6499
  if (browserProcess) {
5996
6500
  import_logger3.logger.info(
@@ -6010,6 +6514,9 @@ var screenDashboard = async (signal, projectRoot) => {
6010
6514
  await browser.version()
6011
6515
  );
6012
6516
  browser.on("disconnected", () => {
6517
+ if (browserCloseRequested) {
6518
+ return;
6519
+ }
6013
6520
  import_logger3.logger.error(
6014
6521
  "screenshot browser disconnected: %s %sm captureAttempt=%d",
6015
6522
  symbol,
@@ -6208,6 +6715,7 @@ var screenDashboard = async (signal, projectRoot) => {
6208
6715
  await (0, import_async.delay)(SCREENSHOT_CAPTURE_RETRY_DELAY_MS);
6209
6716
  }
6210
6717
  } finally {
6718
+ browserCloseRequested = true;
6211
6719
  await browser.close().catch(() => void 0);
6212
6720
  }
6213
6721
  }
@@ -6218,14 +6726,65 @@ var screenDashboard = async (signal, projectRoot) => {
6218
6726
  var import_async2 = require("@tradejs/core/async");
6219
6727
  var import_math = require("@tradejs/core/math");
6220
6728
  var import_logger4 = require("@tradejs/infra/logger");
6729
+ var import_userSettings3 = require("@tradejs/infra/userSettings");
6221
6730
  var escapeHtml = (s) => {
6222
6731
  if (!s) return "";
6223
6732
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
6224
6733
  };
6225
- var { APP_URL: APP_URL2, TG_BOT_TOKEN: token, TG_CHAT_ID: chatId } = process.env;
6734
+ var { APP_URL: APP_URL2 } = process.env;
6226
6735
  var TG_REQUEST_ATTEMPTS = 3;
6227
6736
  var TG_REQUEST_RETRY_DELAY_MS = 2e3;
6737
+ var getErrorFields2 = (value) => {
6738
+ if (!value || typeof value !== "object") {
6739
+ return [];
6740
+ }
6741
+ const record = value;
6742
+ const fields = [
6743
+ ["name", record.name],
6744
+ ["code", record.code],
6745
+ ["errno", record.errno],
6746
+ ["type", record.type],
6747
+ ["syscall", record.syscall],
6748
+ ["hostname", record.hostname],
6749
+ ["host", record.host],
6750
+ ["address", record.address],
6751
+ ["port", record.port]
6752
+ ];
6753
+ return fields.filter(([, fieldValue]) => fieldValue != null && String(fieldValue).trim()).map(([key, fieldValue]) => `${key}=${String(fieldValue)}`);
6754
+ };
6755
+ var describeErrorValue2 = (value) => {
6756
+ if (value == null) {
6757
+ return "";
6758
+ }
6759
+ if (typeof value === "string") {
6760
+ return value;
6761
+ }
6762
+ if (value instanceof Error) {
6763
+ const message = value.message?.trim() || "";
6764
+ const fields = getErrorFields2(value);
6765
+ return [message, ...fields].filter(Boolean).join(" ");
6766
+ }
6767
+ if (typeof value === "object") {
6768
+ const fields = getErrorFields2(value);
6769
+ if (fields.length) {
6770
+ return fields.join(" ");
6771
+ }
6772
+ try {
6773
+ return JSON.stringify(value);
6774
+ } catch {
6775
+ return String(value);
6776
+ }
6777
+ }
6778
+ return String(value);
6779
+ };
6228
6780
  var normalizeQuality = (value) => typeof value === "number" ? Math.max(1, Math.min(5, Math.round(value))) : null;
6781
+ var formatOrderSkipReason = (reason) => {
6782
+ if (!reason) return "";
6783
+ if (reason.startsWith("AI_QUALITY_BELOW_MIN")) {
6784
+ return "AI_QUALITY_BELOW_MIN";
6785
+ }
6786
+ return reason;
6787
+ };
6229
6788
  var getLastNumber = (value) => {
6230
6789
  if (Array.isArray(value)) {
6231
6790
  const last = value[value.length - 1];
@@ -6265,11 +6824,14 @@ var getTelegramErrorReason = (data) => {
6265
6824
  };
6266
6825
  var getErrorMessage2 = (error) => {
6267
6826
  const maybeError = error;
6268
- const message = maybeError?.message || String(error);
6827
+ const message = describeErrorValue2(error) || String(error);
6269
6828
  if (maybeError?.cause == null) {
6270
6829
  return message;
6271
6830
  }
6272
- const cause = maybeError.cause instanceof Error ? maybeError.cause.message : String(maybeError.cause);
6831
+ const cause = describeErrorValue2(maybeError.cause);
6832
+ if (!cause) {
6833
+ return message;
6834
+ }
6273
6835
  return `${message}; cause: ${cause}`;
6274
6836
  };
6275
6837
  var parseTelegramResponse = async (response) => {
@@ -6292,7 +6854,20 @@ var parseTelegramResponse = async (response) => {
6292
6854
  }
6293
6855
  }
6294
6856
  };
6295
- var requestTelegram = async (method, init) => {
6857
+ var getTelegramSettings = async (userName = "root") => {
6858
+ const settings = await (0, import_userSettings3.getUserSettings)(userName);
6859
+ const token = settings.TG_BOT_TOKEN;
6860
+ const chatId = settings.TG_CHAT_ID;
6861
+ if (!token || !chatId) {
6862
+ throw new Error(`Telegram settings are incomplete for user ${userName}`);
6863
+ }
6864
+ return { token, chatId };
6865
+ };
6866
+ var requestTelegram = async ({
6867
+ method,
6868
+ token,
6869
+ init
6870
+ }) => {
6296
6871
  let lastError = null;
6297
6872
  for (let attempt = 1; attempt <= TG_REQUEST_ATTEMPTS; attempt += 1) {
6298
6873
  try {
@@ -6320,17 +6895,23 @@ var requestTelegram = async (method, init) => {
6320
6895
  };
6321
6896
  var sendTelegramMessage = async ({
6322
6897
  message,
6323
- markup
6898
+ markup,
6899
+ token,
6900
+ chatId
6324
6901
  }) => {
6325
- const data = await requestTelegram("sendMessage", {
6326
- method: "POST",
6327
- headers: { "Content-Type": "application/json" },
6328
- body: JSON.stringify({
6329
- chat_id: chatId,
6330
- text: message,
6331
- reply_markup: markup,
6332
- parse_mode: "HTML"
6333
- })
6902
+ const data = await requestTelegram({
6903
+ method: "sendMessage",
6904
+ token,
6905
+ init: {
6906
+ method: "POST",
6907
+ headers: { "Content-Type": "application/json" },
6908
+ body: JSON.stringify({
6909
+ chat_id: chatId,
6910
+ text: message,
6911
+ reply_markup: markup,
6912
+ parse_mode: "HTML"
6913
+ })
6914
+ }
6334
6915
  });
6335
6916
  import_logger4.logger.info(
6336
6917
  "tg sendMessage: %s",
@@ -6389,7 +6970,9 @@ var formatMessage = (signal, analysis) => {
6389
6970
  }
6390
6971
  lines.push(orderStatusText);
6391
6972
  if ((orderStatus === "skipped" || orderStatus === "canceled") && orderSkipReason) {
6392
- lines.push(`Skip reason: <b>${escapeHtml(orderSkipReason)}</b>`);
6973
+ lines.push(
6974
+ `Skip reason: <b>${escapeHtml(formatOrderSkipReason(orderSkipReason))}</b>`
6975
+ );
6393
6976
  }
6394
6977
  }
6395
6978
  if (isConfigFromBacktest) {
@@ -6407,21 +6990,9 @@ var formatMessage = (signal, analysis) => {
6407
6990
  lines.push(aiQualityLine);
6408
6991
  }
6409
6992
  lines.push("");
6410
- if (touches) {
6411
- lines.push(`Points: ${touches}`);
6412
- }
6413
- if (atrPct != null && Number.isFinite(atrPct)) {
6414
- lines.push(`ATR: ${atrPct.toFixed(2)}`);
6415
- }
6416
- if (distance) {
6417
- lines.push(`Distance: ${distance}`);
6418
- }
6419
6993
  if (correlation) {
6420
6994
  lines.push(`BTC correlation: ${correlation}`);
6421
6995
  }
6422
- if (spread != null && Number.isFinite(spread)) {
6423
- lines.push(`BTC spread (CB-BN)/BN: ${spread.toFixed(6)}`);
6424
- }
6425
6996
  const prices = formatPrices();
6426
6997
  if (prices) {
6427
6998
  lines.push("");
@@ -6435,8 +7006,9 @@ var formatMessage = (signal, analysis) => {
6435
7006
  \u0414\u0435\u0442\u0430\u043B\u0438: ${err.message || String(err)}`;
6436
7007
  }
6437
7008
  };
6438
- var sendSignal = async (signal, imgInterval, analysis) => {
7009
+ var sendSignal = async (signal, imgInterval, analysis, options = {}) => {
6439
7010
  const { symbol, signalId, interval } = signal;
7011
+ const { token, chatId } = await getTelegramSettings(options.userName);
6440
7012
  const message = formatMessage(signal, analysis);
6441
7013
  const publicAppUrl = APP_URL2?.startsWith("https") ? APP_URL2 : null;
6442
7014
  const dashboardUrl = publicAppUrl ? `${APP_URL2}/routes/dashboard/bybit/${symbol}/${interval}/?signalId=${signalId}` : null;
@@ -6462,7 +7034,7 @@ var sendSignal = async (signal, imgInterval, analysis) => {
6462
7034
  symbol,
6463
7035
  error?.message || String(error)
6464
7036
  );
6465
- await sendTelegramMessage({ message, markup });
7037
+ await sendTelegramMessage({ message, markup, token, chatId });
6466
7038
  return;
6467
7039
  }
6468
7040
  const photoBody = new FormData();
@@ -6479,9 +7051,13 @@ var sendSignal = async (signal, imgInterval, analysis) => {
6479
7051
  }
6480
7052
  let data;
6481
7053
  try {
6482
- data = await requestTelegram("sendPhoto", {
6483
- method: "POST",
6484
- body: photoBody
7054
+ data = await requestTelegram({
7055
+ method: "sendPhoto",
7056
+ token,
7057
+ init: {
7058
+ method: "POST",
7059
+ body: photoBody
7060
+ }
6485
7061
  });
6486
7062
  } catch (error) {
6487
7063
  const reason = getErrorMessage2(error);
@@ -6491,7 +7067,9 @@ var sendSignal = async (signal, imgInterval, analysis) => {
6491
7067
 
6492
7068
  \u26A0\uFE0F <b>Photo delivery failed</b>
6493
7069
  Reason: <code>${escapeHtml(reason)}</code>`,
6494
- markup
7070
+ markup,
7071
+ token,
7072
+ chatId
6495
7073
  });
6496
7074
  return;
6497
7075
  }
@@ -6503,7 +7081,9 @@ Reason: <code>${escapeHtml(reason)}</code>`,
6503
7081
 
6504
7082
  \u26A0\uFE0F <b>Photo delivery failed</b>
6505
7083
  Reason: <code>${escapeHtml(reason)}</code>`,
6506
- markup
7084
+ markup,
7085
+ token,
7086
+ chatId
6507
7087
  });
6508
7088
  return;
6509
7089
  }
@@ -6551,16 +7131,21 @@ ${escapeHtml(clean)}`);
6551
7131
  }
6552
7132
  return lines.join("\n");
6553
7133
  };
6554
- var sendSignalAnalysis = async (signal, analysis) => {
7134
+ var sendSignalAnalysis = async (signal, analysis, options = {}) => {
7135
+ const { token, chatId } = await getTelegramSettings(options.userName);
6555
7136
  const message = formatAnalysisMessage(signal, analysis);
6556
- const data = await requestTelegram("sendMessage", {
6557
- method: "POST",
6558
- headers: { "Content-Type": "application/json" },
6559
- body: JSON.stringify({
6560
- chat_id: chatId,
6561
- text: message,
6562
- parse_mode: "HTML"
6563
- })
7137
+ const data = await requestTelegram({
7138
+ method: "sendMessage",
7139
+ token,
7140
+ init: {
7141
+ method: "POST",
7142
+ headers: { "Content-Type": "application/json" },
7143
+ body: JSON.stringify({
7144
+ chat_id: chatId,
7145
+ text: message,
7146
+ parse_mode: "HTML"
7147
+ })
7148
+ }
6564
7149
  });
6565
7150
  import_logger4.logger.info(
6566
7151
  "tg sendMessage (analysis): %s",
@@ -6596,7 +7181,7 @@ var cleanFiles = async (dir) => {
6596
7181
  };
6597
7182
  var cleanRedis = async (area) => {
6598
7183
  let completed = 0;
6599
- const keys = await (0, import_redis3.getKeys)(area);
7184
+ const keys = await (0, import_redis2.getKeys)(area);
6600
7185
  const bar = new import_progress.default(":current/:total [:bar][:percent] :eta(s)", {
6601
7186
  total: keys.length,
6602
7187
  width: 30
@@ -6605,13 +7190,13 @@ var cleanRedis = async (area) => {
6605
7190
  try {
6606
7191
  for await (const key of keys) {
6607
7192
  completed++;
6608
- await (0, import_redis3.delKeyWithOptions)(key, { raiseOnMisconf: true });
7193
+ await (0, import_redis2.delKeyWithOptions)(key, { raiseOnMisconf: true });
6609
7194
  if (completed % 100 === 0 || completed === keys.length) {
6610
7195
  bar.tick(completed === keys.length ? completed % 100 : 100);
6611
7196
  }
6612
7197
  }
6613
7198
  } catch (e) {
6614
- if (e instanceof import_redis3.RedisWriteBlockedError || String(e).includes("MISCONF")) {
7199
+ if (e instanceof import_redis2.RedisWriteBlockedError || String(e).includes("MISCONF")) {
6615
7200
  import_logger5.logger.error(
6616
7201
  "Redis write is blocked by MISCONF (RDB save failure). Cleanup stopped early."
6617
7202
  );
@@ -6625,8 +7210,8 @@ var cleanRedis = async (area) => {
6625
7210
  }
6626
7211
  import_logger5.logger.info("");
6627
7212
  };
6628
- var update = async (connector, interval, tickers) => {
6629
- const PRELOAD_START = (0, import_time.getTimestamp)(import_constants.PRELOAD_DAYS);
7213
+ var update = async (connector, interval, tickers, preloadDays = import_constants.PRELOAD_DAYS) => {
7214
+ const PRELOAD_START = (0, import_time.getTimestamp)(preloadDays);
6630
7215
  const PRELOAD_END = (0, import_time.getTimestamp)();
6631
7216
  const bar = new import_progress.default(
6632
7217
  ":current/:total [:bar][:percent] :eta(s) :symbol",
@@ -6637,7 +7222,7 @@ var update = async (connector, interval, tickers) => {
6637
7222
  );
6638
7223
  import_logger5.logger.info(
6639
7224
  import_chalk.default.yellow(
6640
- `update: ${tickers.length} (klineConcurrency=${KLINE_CONCURRENCY_LIMIT})`
7225
+ `update: ${tickers.length} (klineConcurrency=${KLINE_CONCURRENCY_LIMIT}, preloadDays=${preloadDays})`
6641
7226
  )
6642
7227
  );
6643
7228
  const queue = tickers.slice();
@@ -6651,7 +7236,8 @@ var update = async (connector, interval, tickers) => {
6651
7236
  start: PRELOAD_START,
6652
7237
  end: PRELOAD_END,
6653
7238
  interval,
6654
- silent: true
7239
+ silent: true,
7240
+ warmOnly: true
6655
7241
  });
6656
7242
  } catch {
6657
7243
  import_logger5.logger.error("Failed loading: %s", symbol);
@@ -6704,7 +7290,7 @@ var getTickers = async (connector, include = "", exclude = "", limit, chunk) =>
6704
7290
  }
6705
7291
  return tickers.filter((t) => !excludeTickers.includes(t));
6706
7292
  };
6707
- var makeScreenshots = async (signals, interval) => {
7293
+ var makeScreenshots = async (signals, interval, userName = "root") => {
6708
7294
  const projectRoot = getProjectRoot2();
6709
7295
  const bar = new import_progress.default(
6710
7296
  ":current/:total [:bar][:percent] :eta(s) :symbol",
@@ -6719,7 +7305,7 @@ var makeScreenshots = async (signals, interval) => {
6719
7305
  SCREENSHOT_CONCURRENCY_LIMIT,
6720
7306
  async (signal) => {
6721
7307
  try {
6722
- await screenDashboard({ ...signal, interval }, projectRoot);
7308
+ await screenDashboard({ ...signal, interval }, projectRoot, userName);
6723
7309
  } catch (error) {
6724
7310
  import_logger5.logger.error(
6725
7311
  "Failed screenshot: %s (%s)",
@@ -6733,7 +7319,7 @@ var makeScreenshots = async (signals, interval) => {
6733
7319
  );
6734
7320
  import_logger5.logger.info("");
6735
7321
  };
6736
- var sendToAI = async (signals) => {
7322
+ var sendToAI = async (signals, userName = "root") => {
6737
7323
  const bar = new import_progress.default(
6738
7324
  ":current/:total [:bar][:percent] :eta(s) :symbol",
6739
7325
  {
@@ -6744,7 +7330,7 @@ var sendToAI = async (signals) => {
6744
7330
  import_logger5.logger.info(import_chalk.default.yellow("AI:", signals.length));
6745
7331
  await (0, import_async3.runWithConcurrency)(signals, AI_CONCURRENCY_LIMIT, async (signal) => {
6746
7332
  try {
6747
- await askAI(signal);
7333
+ await askAI(signal, { userName });
6748
7334
  } catch {
6749
7335
  import_logger5.logger.error("Failed ask: %s", signal.symbol);
6750
7336
  } finally {
@@ -6753,7 +7339,7 @@ var sendToAI = async (signals) => {
6753
7339
  });
6754
7340
  import_logger5.logger.info("");
6755
7341
  };
6756
- var sendToTG = async (signals, imgInterval) => {
7342
+ var sendToTG = async (signals, imgInterval, userName = "root") => {
6757
7343
  const bar = new import_progress.default(
6758
7344
  ":current/:total [:bar][:percent] :eta(s) :symbol",
6759
7345
  {
@@ -6762,15 +7348,15 @@ var sendToTG = async (signals, imgInterval) => {
6762
7348
  }
6763
7349
  );
6764
7350
  import_logger5.logger.info(import_chalk.default.yellow("messages:", signals.length));
6765
- await (0, import_async3.runWithConcurrency)(signals, TG_CONCURRENCY_LIMIT, async (signal) => {
7351
+ await (0, import_async3.runWithConcurrency)(signals, 1, async (signal) => {
6766
7352
  try {
6767
- const analysis = await (0, import_redis3.getData)(
6768
- import_redis3.redisKeys.analysis(signal.symbol, signal.signalId),
7353
+ const analysis = await (0, import_redis2.getData)(
7354
+ import_redis2.redisKeys.analysis(signal.symbol, signal.signalId),
6769
7355
  null
6770
7356
  );
6771
- await sendSignal(signal, imgInterval, analysis);
7357
+ await sendSignal(signal, imgInterval, analysis, { userName });
6772
7358
  if (analysis && typeof analysis === "object" && Object.keys(analysis).length > 0) {
6773
- await sendSignalAnalysis(signal, analysis);
7359
+ await sendSignalAnalysis(signal, analysis, { userName });
6774
7360
  }
6775
7361
  } catch (err) {
6776
7362
  import_logger5.logger.error(