@tradejs/node 1.0.4 → 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/README.md +1 -1
- package/dist/ai.d.mts +31 -0
- package/dist/ai.d.ts +31 -0
- package/dist/ai.js +926 -0
- package/dist/ai.mjs +35 -0
- package/dist/backtest.js +408 -5687
- package/dist/backtest.mjs +331 -175
- package/dist/chunk-72FKXJ2I.mjs +473 -0
- package/dist/{chunk-P2ZUWONT.mjs → chunk-CGJ2UU6H.mjs} +1 -1
- package/dist/{chunk-ZY6ULOWK.mjs → chunk-EOZSJKUM.mjs} +1 -1
- package/dist/{chunk-7ICOZAKA.mjs → chunk-H6LIYHU4.mjs} +29 -11
- package/dist/{chunk-RBE4PZER.mjs → chunk-PQH5PAFC.mjs} +1 -1
- package/dist/chunk-SCMBUEGK.mjs +22 -0
- package/dist/{chunk-CIY64D57.mjs → chunk-XW5L327F.mjs} +1 -1
- package/dist/cli.d.mts +4 -4
- package/dist/cli.d.ts +4 -4
- package/dist/cli.js +1179 -207
- package/dist/cli.mjs +652 -147
- package/dist/connectors.js +1 -1
- package/dist/connectors.mjs +2 -2
- package/dist/constants.js +10 -1
- package/dist/constants.mjs +1 -1
- package/dist/pine.d.mts +10 -6
- package/dist/pine.d.ts +10 -6
- package/dist/pine.js +40 -18
- package/dist/pine.mjs +21 -13
- package/dist/registry.js +1 -1
- package/dist/registry.mjs +2 -2
- package/dist/strategies.d.mts +6 -12
- package/dist/strategies.d.ts +6 -12
- package/dist/strategies.js +426 -83
- package/dist/strategies.mjs +200 -34
- package/package.json +18 -5
- package/dist/ai-G7ATN4YL.mjs +0 -19
- package/dist/chunk-4ZPGZWO7.mjs +0 -13
- package/dist/chunk-LMAKIC3C.mjs +0 -294
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" ?
|
|
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 :
|
|
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
|
|
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 =
|
|
2964
|
+
var data = getData2(other);
|
|
2965
2965
|
return !!data && func === data[0];
|
|
2966
2966
|
}
|
|
2967
2967
|
function isMasked(func) {
|
|
@@ -3933,7 +3933,7 @@ var require_lodash = __commonJS({
|
|
|
3933
3933
|
var defer = baseRest(function(func, args) {
|
|
3934
3934
|
return baseDelay(func, 1, args);
|
|
3935
3935
|
});
|
|
3936
|
-
var
|
|
3936
|
+
var delay3 = baseRest(function(func, wait, args) {
|
|
3937
3937
|
return baseDelay(func, toNumber(wait) || 0, args);
|
|
3938
3938
|
});
|
|
3939
3939
|
function flip(func) {
|
|
@@ -5047,7 +5047,7 @@ var require_lodash = __commonJS({
|
|
|
5047
5047
|
lodash.defaults = defaults;
|
|
5048
5048
|
lodash.defaultsDeep = defaultsDeep;
|
|
5049
5049
|
lodash.defer = defer;
|
|
5050
|
-
lodash.delay =
|
|
5050
|
+
lodash.delay = delay3;
|
|
5051
5051
|
lodash.difference = difference;
|
|
5052
5052
|
lodash.differenceBy = differenceBy;
|
|
5053
5053
|
lodash.differenceWith = differenceWith;
|
|
@@ -5528,7 +5528,7 @@ var import_promises2 = __toESM(require("fs/promises"));
|
|
|
5528
5528
|
var import_lodash = __toESM(require_lodash());
|
|
5529
5529
|
var import_progress = __toESM(require("progress"));
|
|
5530
5530
|
var import_chalk = __toESM(require("chalk"));
|
|
5531
|
-
var
|
|
5531
|
+
var import_async3 = require("@tradejs/core/async");
|
|
5532
5532
|
var import_backtest = require("@tradejs/core/backtest");
|
|
5533
5533
|
var import_constants = require("@tradejs/core/constants");
|
|
5534
5534
|
var import_tickers = require("@tradejs/core/tickers");
|
|
@@ -5536,18 +5536,27 @@ var import_time = require("@tradejs/core/time");
|
|
|
5536
5536
|
|
|
5537
5537
|
// src/constants.ts
|
|
5538
5538
|
var { NODE_ENV } = process.env;
|
|
5539
|
-
var
|
|
5540
|
-
|
|
5539
|
+
var getPositiveIntegerEnv = (name, fallback) => {
|
|
5540
|
+
const rawValue = process.env[name];
|
|
5541
|
+
const parsedValue = Number(rawValue);
|
|
5542
|
+
return Number.isInteger(parsedValue) && parsedValue > 0 ? parsedValue : fallback;
|
|
5543
|
+
};
|
|
5544
|
+
var DEFAULT_KLINE_CONCURRENCY_LIMIT = NODE_ENV === "production" ? 1 : 10;
|
|
5545
|
+
var KLINE_CONCURRENCY_LIMIT = getPositiveIntegerEnv(
|
|
5546
|
+
"KLINE_CONCURRENCY_LIMIT",
|
|
5547
|
+
DEFAULT_KLINE_CONCURRENCY_LIMIT
|
|
5548
|
+
);
|
|
5541
5549
|
var AI_CONCURRENCY_LIMIT = 3;
|
|
5542
5550
|
var SCREENSHOT_CONCURRENCY_LIMIT = NODE_ENV === "production" ? 1 : 2;
|
|
5543
5551
|
|
|
5544
5552
|
// src/cli.ts
|
|
5545
5553
|
var import_files = require("@tradejs/infra/files");
|
|
5546
|
-
var
|
|
5547
|
-
var
|
|
5554
|
+
var import_redis2 = require("@tradejs/infra/redis");
|
|
5555
|
+
var import_logger5 = require("@tradejs/infra/logger");
|
|
5548
5556
|
|
|
5549
5557
|
// src/ai.ts
|
|
5550
5558
|
var import_redis = require("@tradejs/infra/redis");
|
|
5559
|
+
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
5551
5560
|
|
|
5552
5561
|
// src/aiShared.ts
|
|
5553
5562
|
var MAX_AI_SERIES_POINTS = 5;
|
|
@@ -5578,8 +5587,23 @@ var import_indicators = require("@tradejs/core/indicators");
|
|
|
5578
5587
|
var import_logger2 = require("@tradejs/infra/logger");
|
|
5579
5588
|
|
|
5580
5589
|
// src/tradejsConfig.ts
|
|
5590
|
+
var import_fs = __toESM(require("fs"));
|
|
5581
5591
|
var import_path = __toESM(require("path"));
|
|
5592
|
+
var import_module = require("module");
|
|
5593
|
+
var import_url = require("url");
|
|
5582
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();
|
|
5583
5607
|
var getTradejsProjectCwd = (cwd) => {
|
|
5584
5608
|
const explicit = String(cwd ?? "").trim();
|
|
5585
5609
|
if (explicit) {
|
|
@@ -5591,6 +5615,202 @@ var getTradejsProjectCwd = (cwd) => {
|
|
|
5591
5615
|
}
|
|
5592
5616
|
return process.cwd();
|
|
5593
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
|
+
};
|
|
5594
5814
|
|
|
5595
5815
|
// src/strategy/manifests.ts
|
|
5596
5816
|
var createStrategyRegistryState = () => ({
|
|
@@ -5611,6 +5831,144 @@ var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
|
|
|
5611
5831
|
state
|
|
5612
5832
|
};
|
|
5613
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
|
+
};
|
|
5614
5972
|
var getStrategyManifest = (name, cwd = getTradejsProjectCwd()) => {
|
|
5615
5973
|
if (!name) {
|
|
5616
5974
|
return void 0;
|
|
@@ -5669,6 +6027,11 @@ var buildAiHumanPromptAddonByStrategy = (signal, payload) => getSignalAiAdapter(
|
|
|
5669
6027
|
signal,
|
|
5670
6028
|
payload
|
|
5671
6029
|
}) ?? "";
|
|
6030
|
+
var postProcessAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => getSignalAiAdapter(signal).postProcessAnalysis?.({
|
|
6031
|
+
signal,
|
|
6032
|
+
payload,
|
|
6033
|
+
analysis
|
|
6034
|
+
}) ?? analysis;
|
|
5672
6035
|
|
|
5673
6036
|
// src/ai.ts
|
|
5674
6037
|
var parseAIResponse = (input) => {
|
|
@@ -5725,15 +6088,17 @@ var normalizeAnalysis = (raw) => {
|
|
|
5725
6088
|
};
|
|
5726
6089
|
};
|
|
5727
6090
|
var buildAiSystemPrompt = (signal) => `
|
|
5728
|
-
\u0422\u044B \u2014 \
|
|
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.
|
|
5729
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.
|
|
5730
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.
|
|
5731
6094
|
|
|
5732
6095
|
\u0412\u0410\u0416\u041D\u041E:
|
|
5733
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.
|
|
5734
|
-
- \
|
|
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.
|
|
5735
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.
|
|
5736
|
-
- \u0422\u0432\u043E\u044F \u0446\u0435\u043B\u044C: \
|
|
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.
|
|
5737
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.
|
|
5738
6103
|
- \u041F\u0438\u0448\u0438 comment \u043F\u043E-\u0440\u0443\u0441\u0441\u043A\u0438.
|
|
5739
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.
|
|
@@ -5759,19 +6124,19 @@ var buildAiSystemPrompt = (signal) => `
|
|
|
5759
6124
|
- \u041D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u0439 \u0434\u0440\u0443\u0433\u0438\u0435 \u043F\u043E\u043B\u044F.
|
|
5760
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.
|
|
5761
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).
|
|
5762
|
-
- "direction" \u2014 \
|
|
5763
|
-
- "quality" \u2014 \
|
|
5764
|
-
- "needRetest" \u2014 \u043D\u0443\u0436\u043D
|
|
5765
|
-
- "retestPrice" \u2014 \
|
|
5766
|
-
- "takeProfitPrice" \u0438 "stopLossPrice" \u2014 \
|
|
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.
|
|
5767
6132
|
- \u0420\u0430\u0437\u0431\u0438\u0432\u0430\u0439 \u0430\u043D\u0430\u043B\u0438\u0437 \u043D\u0430 \u043F\u043E\u043B\u044F:
|
|
5768
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.
|
|
5769
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.
|
|
5770
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.
|
|
5771
|
-
- "retestPlan" \u2014 \u0447\u0442\u043E \u0436\u0434\
|
|
5772
|
-
- "riskLevels" \u2014 \u043A\u0440\u0430\u0442\u043A\u043E \u043F\u0440\u043E
|
|
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.
|
|
5773
6138
|
- "qualityReason" \u2014 \u043F\u043E\u0447\u0435\u043C\u0443 quality \u0438\u043C\u0435\u043D\u043D\u043E \u0442\u0430\u043A\u043E\u0439.
|
|
5774
|
-
- "triggerInvalidation" \u2014 \u0447\u0442\u043E \u0434\u043E\u043B\u0436\u043D\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \
|
|
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.
|
|
5775
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.
|
|
5776
6141
|
|
|
5777
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.
|
|
@@ -5785,6 +6150,10 @@ var buildAiSystemPrompt = (signal) => `
|
|
|
5785
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.
|
|
5786
6151
|
- payload.indicators:
|
|
5787
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.
|
|
5788
6157
|
\u041F\u0430\u0442\u0442\u0435\u0440\u043D\u044B \u043A\u043B\u044E\u0447\u0435\u0439:
|
|
5789
6158
|
\u2022 \u043C\u043E\u043D\u0435\u0442\u0430: maFast, atrPct, macd..., candles15m/candles1h/candles4h/candles1d, \u0430 \u0442\u0430\u043A\u0436\u0435 *1h/*4h/*1d
|
|
5790
6159
|
\u2022 BTC: btcMaFast, btcAtr, btcMacd..., btcCandles*, \u0430 \u0442\u0430\u043A\u0436\u0435 btc*1h/*4h/*1d
|
|
@@ -5792,41 +6161,42 @@ var buildAiSystemPrompt = (signal) => `
|
|
|
5792
6161
|
|
|
5793
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):
|
|
5794
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.
|
|
5795
|
-
2) \u0417\u0430\u0442\u0435\u043C \
|
|
5796
|
-
3) \u0417\u0430\u0442\u0435\u043C \u043F\
|
|
5797
|
-
4) \
|
|
5798
|
-
5) \
|
|
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.
|
|
5799
6169
|
|
|
5800
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:
|
|
5801
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.
|
|
5802
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.
|
|
5803
|
-
- \u0415\u0441\u043B\u0438 \
|
|
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.
|
|
5804
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".
|
|
5805
6176
|
|
|
5806
6177
|
\u041F\u0440\u0430\u0432\u0438\u043B\u0430 \u0434\u043B\u044F direction / TP / SL:
|
|
5807
|
-
- direction = LONG \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \
|
|
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.
|
|
5808
6179
|
- \u0414\u043B\u044F LONG \u043E\u0431\u044B\u0447\u043D\u043E stopLossPrice < currentPrice < takeProfitPrice.
|
|
5809
6180
|
- \u0414\u043B\u044F SHORT \u043E\u0431\u044B\u0447\u043D\u043E takeProfitPrice < currentPrice < stopLossPrice.
|
|
5810
|
-
-
|
|
5811
|
-
- \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.
|
|
5812
6182
|
- \u0415\u0441\u043B\u0438 direction = null, \u0442\u043E takeProfitPrice = null \u0438 stopLossPrice = null.
|
|
5813
6183
|
- \u0415\u0441\u043B\u0438 needRetest = false, \u0442\u043E retestPrice = null.
|
|
5814
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.
|
|
5815
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.
|
|
5816
6186
|
|
|
5817
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):
|
|
5818
|
-
- 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, \
|
|
5819
|
-
- 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, \
|
|
5820
|
-
- 3: \u0441\u0440\u0435\u0434\u043D\u0438\u0439 \u0441\u0435\u0442\u0430\u043F, \
|
|
5821
|
-
- 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\
|
|
5822
|
-
- 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 + \
|
|
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
|
|
5823
6193
|
|
|
5824
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):
|
|
5825
|
-
- \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" \
|
|
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".
|
|
5826
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).
|
|
5827
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).
|
|
5828
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.
|
|
5829
|
-
- \u0415\u0441\u043B\u0438 \u043D\u0435 \u0432\
|
|
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.
|
|
5830
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.
|
|
5831
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.
|
|
5832
6202
|
|
|
@@ -5836,49 +6206,121 @@ var buildAiSystemPrompt = (signal) => `
|
|
|
5836
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.
|
|
5837
6207
|
|
|
5838
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):
|
|
5839
|
-
{"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 \
|
|
5840
|
-
{"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\
|
|
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."}
|
|
5841
6211
|
|
|
5842
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.
|
|
5843
6213
|
${signal ? buildAiSystemPromptAddonByStrategy(signal) : ""}
|
|
5844
6214
|
`;
|
|
5845
6215
|
var buildAiPayload = (signal) => buildAiPayloadByStrategy(signal);
|
|
5846
6216
|
var buildAiHumanPrompt = (signal, payload = buildAiPayload(signal)) => `
|
|
5847
|
-
\u041F\u0440\u043E\u0430\u043D\u0430\u043B\u0438\u0437\u0438\u0440\u0443\u0439 \u0441\
|
|
5848
|
-
\
|
|
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.
|
|
5849
6219
|
|
|
5850
6220
|
\u0414\u0430\u043D\u043D\u044B\u0435 \u0441\u0434\u0435\u043B\u043A\u0438:
|
|
5851
6221
|
${JSON.stringify(payload)}
|
|
5852
6222
|
${buildAiHumanPromptAddonByStrategy(signal, payload)}
|
|
5853
6223
|
`;
|
|
5854
|
-
var
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
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)
|
|
5858
6315
|
]);
|
|
5859
|
-
const { symbol } = signal;
|
|
5860
6316
|
const messages = [];
|
|
5861
|
-
|
|
5862
|
-
temperature: 0.2,
|
|
5863
|
-
//modelName: 'anthropic/claude-sonnet-4.5',
|
|
5864
|
-
modelName: "google/gemini-3.1-pro-preview",
|
|
5865
|
-
openAIApiKey: process.env.OPENAI_API_KEY,
|
|
5866
|
-
configuration: {
|
|
5867
|
-
baseURL: process.env.OPENAI_API_ENDPOINT || "https://api.openai.com/v1",
|
|
5868
|
-
defaultHeaders: {
|
|
5869
|
-
"HTTP-Referer": "https://tradejs.dev",
|
|
5870
|
-
"X-Title": "Inv"
|
|
5871
|
-
}
|
|
5872
|
-
}
|
|
5873
|
-
});
|
|
5874
|
-
messages.push(new SystemMessage(buildAiSystemPrompt(signal)));
|
|
5875
|
-
const payload = buildAiPayload(signal);
|
|
6317
|
+
messages.push(new SystemMessage(systemPrompt));
|
|
5876
6318
|
messages.push(
|
|
5877
6319
|
new HumanMessage({
|
|
5878
6320
|
content: [
|
|
5879
6321
|
{
|
|
5880
6322
|
type: "text",
|
|
5881
|
-
text:
|
|
6323
|
+
text: humanPrompt
|
|
5882
6324
|
}
|
|
5883
6325
|
]
|
|
5884
6326
|
})
|
|
@@ -5887,7 +6329,31 @@ var askAI = async (signal) => {
|
|
|
5887
6329
|
const parsed = parseAIResponse(
|
|
5888
6330
|
normalizeResponseContent(response.content)
|
|
5889
6331
|
);
|
|
5890
|
-
const
|
|
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
|
+
);
|
|
5891
6357
|
await (0, import_redis.setData)(import_redis.redisKeys.analysis(symbol, signal.signalId), content);
|
|
5892
6358
|
return content;
|
|
5893
6359
|
};
|
|
@@ -5897,66 +6363,428 @@ var import_promises = __toESM(require("fs/promises"));
|
|
|
5897
6363
|
var import_path2 = __toESM(require("path"));
|
|
5898
6364
|
var import_puppeteer = __toESM(require("puppeteer"));
|
|
5899
6365
|
var import_async = require("@tradejs/core/async");
|
|
5900
|
-
var
|
|
6366
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
6367
|
+
var import_userSettings2 = require("@tradejs/infra/userSettings");
|
|
5901
6368
|
var { APP_URL } = process.env;
|
|
6369
|
+
var SCREENSHOT_NAVIGATION_ATTEMPTS = 3;
|
|
6370
|
+
var SCREENSHOT_NAVIGATION_RETRY_DELAY_MS = 2e3;
|
|
6371
|
+
var SCREENSHOT_CAPTURE_ATTEMPTS = 2;
|
|
6372
|
+
var SCREENSHOT_CAPTURE_RETRY_DELAY_MS = 2e3;
|
|
6373
|
+
var SCREENSHOT_NAVIGATION_TIMEOUT_MS = 3e4;
|
|
6374
|
+
var SCREENSHOT_RENDER_DELAY_MS = 1e4;
|
|
6375
|
+
var SCREENSHOT_CONSOLE_LOG_LIMIT = 20;
|
|
6376
|
+
var SCREENSHOT_CONSOLE_TEXT_LIMIT = 500;
|
|
6377
|
+
var SCREENSHOT_BROWSER_STDERR = process.env.SCREENSHOT_BROWSER_STDERR === "1";
|
|
6378
|
+
var PUPPETEER_DUMPIO = process.env.PUPPETEER_DUMPIO === "1";
|
|
6379
|
+
var SCREENSHOT_VIEWPORT = {
|
|
6380
|
+
width: 1280,
|
|
6381
|
+
height: 800,
|
|
6382
|
+
deviceScaleFactor: process.env.NODE_ENV === "production" ? 1 : 2
|
|
6383
|
+
};
|
|
5902
6384
|
var getProjectRoot = (projectRoot) => import_path2.default.resolve(getTradejsProjectCwd(projectRoot));
|
|
5903
6385
|
var getScreenshotsDir = (projectRoot) => import_path2.default.join(getProjectRoot(projectRoot), "data", "screenshots");
|
|
5904
|
-
var
|
|
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
|
+
};
|
|
6430
|
+
var getErrorMessage = (error) => {
|
|
6431
|
+
const maybeError = error;
|
|
6432
|
+
const message = describeErrorValue(error) || String(error);
|
|
6433
|
+
if (maybeError?.cause == null) {
|
|
6434
|
+
return message;
|
|
6435
|
+
}
|
|
6436
|
+
const cause = describeErrorValue(maybeError.cause);
|
|
6437
|
+
if (!cause) {
|
|
6438
|
+
return message;
|
|
6439
|
+
}
|
|
6440
|
+
return `${message}; cause: ${cause}`;
|
|
6441
|
+
};
|
|
6442
|
+
var truncateText = (value, maxLength = SCREENSHOT_CONSOLE_TEXT_LIMIT) => value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
|
|
6443
|
+
var shouldLogConsoleMessage = (type) => ["error", "warning", "assert"].includes(type);
|
|
6444
|
+
var getScreenshotBuffer = async (signal, projectRoot) => {
|
|
6445
|
+
const screenshotPath = getScreenshotPath(signal, projectRoot);
|
|
6446
|
+
return import_promises.default.readFile(screenshotPath);
|
|
6447
|
+
};
|
|
6448
|
+
var getScreenshotFilename = ({
|
|
6449
|
+
symbol,
|
|
6450
|
+
signalId,
|
|
6451
|
+
interval
|
|
6452
|
+
}) => `${symbol}_${signalId}_${interval}.png`;
|
|
6453
|
+
var getScreenshotRenderBaseUrl = () => {
|
|
6454
|
+
const fallback = String(APP_URL || "").trim();
|
|
6455
|
+
if (fallback) {
|
|
6456
|
+
return fallback;
|
|
6457
|
+
}
|
|
6458
|
+
throw new Error("APP_URL is required for screenshots");
|
|
6459
|
+
};
|
|
5905
6460
|
var getScreenshotPath = ({ symbol, signalId, interval }, projectRoot) => {
|
|
5906
6461
|
return import_path2.default.join(
|
|
5907
6462
|
getScreenshotsDir(projectRoot),
|
|
5908
|
-
|
|
6463
|
+
getScreenshotFilename({ symbol, signalId, interval })
|
|
5909
6464
|
);
|
|
5910
6465
|
};
|
|
5911
|
-
var screenDashboard = async (signal, projectRoot) => {
|
|
6466
|
+
var screenDashboard = async (signal, projectRoot, userName = "root") => {
|
|
5912
6467
|
const { symbol, signalId, interval } = signal;
|
|
5913
|
-
const
|
|
5914
|
-
const
|
|
5915
|
-
const
|
|
5916
|
-
const
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
6468
|
+
const screenshotBaseUrl = getScreenshotRenderBaseUrl();
|
|
6469
|
+
const screenshotPath = getScreenshotPath(signal, projectRoot);
|
|
6470
|
+
const settings = await (0, import_userSettings2.getUserSettings)(userName);
|
|
6471
|
+
const token = settings.token;
|
|
6472
|
+
const tokenParam = typeof token === "string" && token.length > 0 ? `&token=${encodeURIComponent(token)}` : "";
|
|
6473
|
+
const dashboardUrl = `${screenshotBaseUrl}/routes/dashboard/bybit/${symbol}/${interval}/?signalId=${signalId}&autoZoom=true&screenshot=1${tokenParam}`;
|
|
6474
|
+
const maskedDashboardUrl = maskTokenInUrl(dashboardUrl);
|
|
6475
|
+
import_logger3.logger.info(
|
|
6476
|
+
"screenshot start: %s %sm url=%s path=%s",
|
|
6477
|
+
symbol,
|
|
6478
|
+
interval,
|
|
6479
|
+
maskedDashboardUrl,
|
|
6480
|
+
screenshotPath
|
|
6481
|
+
);
|
|
6482
|
+
let captureError = null;
|
|
6483
|
+
for (let captureAttempt = 1; captureAttempt <= SCREENSHOT_CAPTURE_ATTEMPTS; captureAttempt += 1) {
|
|
6484
|
+
const browser = await import_puppeteer.default.launch({
|
|
6485
|
+
headless: true,
|
|
6486
|
+
dumpio: PUPPETEER_DUMPIO,
|
|
6487
|
+
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
|
|
6488
|
+
args: [
|
|
6489
|
+
"--no-sandbox",
|
|
6490
|
+
"--disable-setuid-sandbox",
|
|
6491
|
+
"--disable-dev-shm-usage",
|
|
6492
|
+
"--disable-gpu",
|
|
6493
|
+
"--font-render-hinting=medium"
|
|
6494
|
+
]
|
|
6495
|
+
});
|
|
6496
|
+
const browserProcess = browser.process();
|
|
6497
|
+
let browserCloseRequested = false;
|
|
5929
6498
|
try {
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
6499
|
+
if (browserProcess) {
|
|
6500
|
+
import_logger3.logger.info(
|
|
6501
|
+
"screenshot browser: %s %sm captureAttempt=%d pid=%s exec=%s",
|
|
6502
|
+
symbol,
|
|
6503
|
+
interval,
|
|
6504
|
+
captureAttempt,
|
|
6505
|
+
String(browserProcess.pid ?? "unknown"),
|
|
6506
|
+
browserProcess.spawnfile || "unknown"
|
|
6507
|
+
);
|
|
6508
|
+
}
|
|
6509
|
+
import_logger3.logger.info(
|
|
6510
|
+
"screenshot browser version: %s %sm captureAttempt=%d version=%s",
|
|
6511
|
+
symbol,
|
|
6512
|
+
interval,
|
|
6513
|
+
captureAttempt,
|
|
6514
|
+
await browser.version()
|
|
5937
6515
|
);
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
6516
|
+
browser.on("disconnected", () => {
|
|
6517
|
+
if (browserCloseRequested) {
|
|
6518
|
+
return;
|
|
6519
|
+
}
|
|
6520
|
+
import_logger3.logger.error(
|
|
6521
|
+
"screenshot browser disconnected: %s %sm captureAttempt=%d",
|
|
6522
|
+
symbol,
|
|
6523
|
+
interval,
|
|
6524
|
+
captureAttempt
|
|
6525
|
+
);
|
|
5942
6526
|
});
|
|
6527
|
+
if (SCREENSHOT_BROWSER_STDERR && browserProcess?.stderr) {
|
|
6528
|
+
browserProcess.stderr.on("data", (chunk) => {
|
|
6529
|
+
const text = String(chunk).trim();
|
|
6530
|
+
if (!text) {
|
|
6531
|
+
return;
|
|
6532
|
+
}
|
|
6533
|
+
import_logger3.logger.error(
|
|
6534
|
+
"screenshot browser stderr: %s %sm captureAttempt=%d (%s)",
|
|
6535
|
+
symbol,
|
|
6536
|
+
interval,
|
|
6537
|
+
captureAttempt,
|
|
6538
|
+
truncateText(text)
|
|
6539
|
+
);
|
|
6540
|
+
});
|
|
6541
|
+
}
|
|
6542
|
+
const page = await browser.newPage();
|
|
6543
|
+
try {
|
|
6544
|
+
let consoleMessagesLogged = 0;
|
|
6545
|
+
page.on("requestfailed", (request) => {
|
|
6546
|
+
if (!request.isNavigationRequest() && request.resourceType() !== "document") {
|
|
6547
|
+
return;
|
|
6548
|
+
}
|
|
6549
|
+
import_logger3.logger.error(
|
|
6550
|
+
"screenshot request failed: %s %sm %s (%s)",
|
|
6551
|
+
symbol,
|
|
6552
|
+
interval,
|
|
6553
|
+
maskTokenInUrl(request.url()),
|
|
6554
|
+
request.failure()?.errorText || "unknown"
|
|
6555
|
+
);
|
|
6556
|
+
});
|
|
6557
|
+
page.on("pageerror", (error) => {
|
|
6558
|
+
import_logger3.logger.error(
|
|
6559
|
+
"screenshot page error: %s %sm (%s)",
|
|
6560
|
+
symbol,
|
|
6561
|
+
interval,
|
|
6562
|
+
error.message || String(error)
|
|
6563
|
+
);
|
|
6564
|
+
});
|
|
6565
|
+
page.on("error", (error) => {
|
|
6566
|
+
import_logger3.logger.error(
|
|
6567
|
+
"screenshot target error: %s %sm (%s)",
|
|
6568
|
+
symbol,
|
|
6569
|
+
interval,
|
|
6570
|
+
error.message || String(error)
|
|
6571
|
+
);
|
|
6572
|
+
});
|
|
6573
|
+
page.on("close", () => {
|
|
6574
|
+
import_logger3.logger.info(
|
|
6575
|
+
"screenshot page closed: %s %sm captureAttempt=%d",
|
|
6576
|
+
symbol,
|
|
6577
|
+
interval,
|
|
6578
|
+
captureAttempt
|
|
6579
|
+
);
|
|
6580
|
+
});
|
|
6581
|
+
page.on("console", (message) => {
|
|
6582
|
+
if (!shouldLogConsoleMessage(message.type())) {
|
|
6583
|
+
return;
|
|
6584
|
+
}
|
|
6585
|
+
if (consoleMessagesLogged >= SCREENSHOT_CONSOLE_LOG_LIMIT) {
|
|
6586
|
+
return;
|
|
6587
|
+
}
|
|
6588
|
+
consoleMessagesLogged += 1;
|
|
6589
|
+
const location = message.location();
|
|
6590
|
+
const suffix = location.url ? ` ${maskTokenInUrl(location.url)}:${location.lineNumber ?? 0}:${location.columnNumber ?? 0}` : "";
|
|
6591
|
+
import_logger3.logger.error(
|
|
6592
|
+
"screenshot console %s: %s %sm captureAttempt=%d (%s%s)",
|
|
6593
|
+
message.type(),
|
|
6594
|
+
symbol,
|
|
6595
|
+
interval,
|
|
6596
|
+
captureAttempt,
|
|
6597
|
+
truncateText(message.text()),
|
|
6598
|
+
suffix
|
|
6599
|
+
);
|
|
6600
|
+
});
|
|
6601
|
+
page.on("response", (response) => {
|
|
6602
|
+
const status = response.status();
|
|
6603
|
+
if (status < 400) {
|
|
6604
|
+
return;
|
|
6605
|
+
}
|
|
6606
|
+
const request = response.request();
|
|
6607
|
+
const resourceType = request.resourceType();
|
|
6608
|
+
if (!["document", "script", "stylesheet", "fetch", "xhr"].includes(
|
|
6609
|
+
resourceType
|
|
6610
|
+
)) {
|
|
6611
|
+
return;
|
|
6612
|
+
}
|
|
6613
|
+
import_logger3.logger.error(
|
|
6614
|
+
"screenshot response failed: %s %sm captureAttempt=%d status=%d type=%s url=%s",
|
|
6615
|
+
symbol,
|
|
6616
|
+
interval,
|
|
6617
|
+
captureAttempt,
|
|
6618
|
+
status,
|
|
6619
|
+
resourceType,
|
|
6620
|
+
maskTokenInUrl(response.url())
|
|
6621
|
+
);
|
|
6622
|
+
});
|
|
6623
|
+
await page.setViewport(SCREENSHOT_VIEWPORT);
|
|
6624
|
+
let gotoError = null;
|
|
6625
|
+
import_logger3.logger.info(
|
|
6626
|
+
"screenshot render: %s %sm captureAttempt=%d viewport=%dx%d@%d",
|
|
6627
|
+
symbol,
|
|
6628
|
+
interval,
|
|
6629
|
+
captureAttempt,
|
|
6630
|
+
SCREENSHOT_VIEWPORT.width,
|
|
6631
|
+
SCREENSHOT_VIEWPORT.height,
|
|
6632
|
+
SCREENSHOT_VIEWPORT.deviceScaleFactor
|
|
6633
|
+
);
|
|
6634
|
+
for (let attempt = 1; attempt <= SCREENSHOT_NAVIGATION_ATTEMPTS; attempt += 1) {
|
|
6635
|
+
try {
|
|
6636
|
+
import_logger3.logger.info(
|
|
6637
|
+
"screenshot goto: %s %sm captureAttempt=%d attempt=%d url=%s",
|
|
6638
|
+
symbol,
|
|
6639
|
+
interval,
|
|
6640
|
+
captureAttempt,
|
|
6641
|
+
attempt,
|
|
6642
|
+
maskedDashboardUrl
|
|
6643
|
+
);
|
|
6644
|
+
const response = await page.goto(dashboardUrl, {
|
|
6645
|
+
waitUntil: "domcontentloaded",
|
|
6646
|
+
timeout: SCREENSHOT_NAVIGATION_TIMEOUT_MS
|
|
6647
|
+
});
|
|
6648
|
+
import_logger3.logger.info(
|
|
6649
|
+
"screenshot goto ok: %s %sm captureAttempt=%d attempt=%d status=%s finalUrl=%s",
|
|
6650
|
+
symbol,
|
|
6651
|
+
interval,
|
|
6652
|
+
captureAttempt,
|
|
6653
|
+
attempt,
|
|
6654
|
+
response ? String(response.status()) : "null",
|
|
6655
|
+
maskTokenInUrl(page.url())
|
|
6656
|
+
);
|
|
6657
|
+
import_logger3.logger.info(
|
|
6658
|
+
"screenshot page info: %s %sm captureAttempt=%d title=%s ua=%s",
|
|
6659
|
+
symbol,
|
|
6660
|
+
interval,
|
|
6661
|
+
captureAttempt,
|
|
6662
|
+
truncateText(await page.title(), 120),
|
|
6663
|
+
truncateText(await page.evaluate(() => navigator.userAgent), 240)
|
|
6664
|
+
);
|
|
6665
|
+
gotoError = null;
|
|
6666
|
+
break;
|
|
6667
|
+
} catch (error) {
|
|
6668
|
+
gotoError = error;
|
|
6669
|
+
import_logger3.logger.error(
|
|
6670
|
+
"screenshot goto failed: %s %sm captureAttempt=%d attempt=%d url=%s (%s)",
|
|
6671
|
+
symbol,
|
|
6672
|
+
interval,
|
|
6673
|
+
captureAttempt,
|
|
6674
|
+
attempt,
|
|
6675
|
+
maskedDashboardUrl,
|
|
6676
|
+
gotoError.message || String(gotoError)
|
|
6677
|
+
);
|
|
6678
|
+
if (attempt >= SCREENSHOT_NAVIGATION_ATTEMPTS) {
|
|
6679
|
+
break;
|
|
6680
|
+
}
|
|
6681
|
+
await (0, import_async.delay)(SCREENSHOT_NAVIGATION_RETRY_DELAY_MS);
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
if (gotoError) {
|
|
6685
|
+
throw new Error(
|
|
6686
|
+
`Failed to open dashboard ${dashboardUrl}: ${gotoError.message || String(gotoError)}`
|
|
6687
|
+
);
|
|
6688
|
+
}
|
|
6689
|
+
await (0, import_async.delay)(SCREENSHOT_RENDER_DELAY_MS);
|
|
6690
|
+
await import_promises.default.mkdir(getScreenshotsDir(projectRoot), { recursive: true });
|
|
6691
|
+
await page.screenshot({
|
|
6692
|
+
path: screenshotPath,
|
|
6693
|
+
type: "png"
|
|
6694
|
+
});
|
|
6695
|
+
import_logger3.logger.info(
|
|
6696
|
+
"screenshot saved: %s %sm path=%s",
|
|
6697
|
+
symbol,
|
|
6698
|
+
interval,
|
|
6699
|
+
screenshotPath
|
|
6700
|
+
);
|
|
6701
|
+
return;
|
|
6702
|
+
} finally {
|
|
6703
|
+
await page.close().catch(() => void 0);
|
|
6704
|
+
}
|
|
6705
|
+
} catch (error) {
|
|
6706
|
+
captureError = error;
|
|
6707
|
+
import_logger3.logger.error(
|
|
6708
|
+
"screenshot capture failed: %s %sm captureAttempt=%d (%s)",
|
|
6709
|
+
symbol,
|
|
6710
|
+
interval,
|
|
6711
|
+
captureAttempt,
|
|
6712
|
+
getErrorMessage(captureError)
|
|
6713
|
+
);
|
|
6714
|
+
if (captureAttempt < SCREENSHOT_CAPTURE_ATTEMPTS) {
|
|
6715
|
+
await (0, import_async.delay)(SCREENSHOT_CAPTURE_RETRY_DELAY_MS);
|
|
6716
|
+
}
|
|
5943
6717
|
} finally {
|
|
5944
|
-
|
|
6718
|
+
browserCloseRequested = true;
|
|
6719
|
+
await browser.close().catch(() => void 0);
|
|
5945
6720
|
}
|
|
5946
|
-
} finally {
|
|
5947
|
-
await browser.close();
|
|
5948
6721
|
}
|
|
6722
|
+
throw captureError || new Error(`Failed screenshot: ${symbol} ${interval}`);
|
|
5949
6723
|
};
|
|
5950
6724
|
|
|
5951
6725
|
// src/signals.ts
|
|
6726
|
+
var import_async2 = require("@tradejs/core/async");
|
|
5952
6727
|
var import_math = require("@tradejs/core/math");
|
|
5953
|
-
var
|
|
6728
|
+
var import_logger4 = require("@tradejs/infra/logger");
|
|
6729
|
+
var import_userSettings3 = require("@tradejs/infra/userSettings");
|
|
5954
6730
|
var escapeHtml = (s) => {
|
|
5955
6731
|
if (!s) return "";
|
|
5956
6732
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
5957
6733
|
};
|
|
5958
|
-
var { APP_URL: APP_URL2
|
|
6734
|
+
var { APP_URL: APP_URL2 } = process.env;
|
|
6735
|
+
var TG_REQUEST_ATTEMPTS = 3;
|
|
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
|
+
};
|
|
5959
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
|
+
};
|
|
5960
6788
|
var getLastNumber = (value) => {
|
|
5961
6789
|
if (Array.isArray(value)) {
|
|
5962
6790
|
const last = value[value.length - 1];
|
|
@@ -5976,6 +6804,121 @@ var getAiQualityLine = (analysis) => {
|
|
|
5976
6804
|
}
|
|
5977
6805
|
return `\u{1F534} AI Quality: ${quality}/5`;
|
|
5978
6806
|
};
|
|
6807
|
+
var getTelegramErrorReason = (data) => {
|
|
6808
|
+
if (data && typeof data === "object") {
|
|
6809
|
+
const record = data;
|
|
6810
|
+
const description = typeof record.description === "string" ? record.description : "";
|
|
6811
|
+
const errorCode = typeof record.error_code === "number" ? record.error_code : void 0;
|
|
6812
|
+
if (description && errorCode != null) {
|
|
6813
|
+
return `${errorCode}: ${description}`;
|
|
6814
|
+
}
|
|
6815
|
+
if (description) {
|
|
6816
|
+
return description;
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6819
|
+
try {
|
|
6820
|
+
return JSON.stringify(data);
|
|
6821
|
+
} catch {
|
|
6822
|
+
return String(data);
|
|
6823
|
+
}
|
|
6824
|
+
};
|
|
6825
|
+
var getErrorMessage2 = (error) => {
|
|
6826
|
+
const maybeError = error;
|
|
6827
|
+
const message = describeErrorValue2(error) || String(error);
|
|
6828
|
+
if (maybeError?.cause == null) {
|
|
6829
|
+
return message;
|
|
6830
|
+
}
|
|
6831
|
+
const cause = describeErrorValue2(maybeError.cause);
|
|
6832
|
+
if (!cause) {
|
|
6833
|
+
return message;
|
|
6834
|
+
}
|
|
6835
|
+
return `${message}; cause: ${cause}`;
|
|
6836
|
+
};
|
|
6837
|
+
var parseTelegramResponse = async (response) => {
|
|
6838
|
+
const fallback = {
|
|
6839
|
+
ok: response.ok,
|
|
6840
|
+
error_code: response.status,
|
|
6841
|
+
description: response.statusText
|
|
6842
|
+
};
|
|
6843
|
+
try {
|
|
6844
|
+
return await response.json();
|
|
6845
|
+
} catch {
|
|
6846
|
+
try {
|
|
6847
|
+
const text = await response.text();
|
|
6848
|
+
return text ? {
|
|
6849
|
+
...fallback,
|
|
6850
|
+
description: text
|
|
6851
|
+
} : fallback;
|
|
6852
|
+
} catch {
|
|
6853
|
+
return fallback;
|
|
6854
|
+
}
|
|
6855
|
+
}
|
|
6856
|
+
};
|
|
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
|
+
}) => {
|
|
6871
|
+
let lastError = null;
|
|
6872
|
+
for (let attempt = 1; attempt <= TG_REQUEST_ATTEMPTS; attempt += 1) {
|
|
6873
|
+
try {
|
|
6874
|
+
const res = await fetch(
|
|
6875
|
+
`https://api.telegram.org/bot${token}/${method}`,
|
|
6876
|
+
{
|
|
6877
|
+
...init
|
|
6878
|
+
}
|
|
6879
|
+
);
|
|
6880
|
+
return await parseTelegramResponse(res);
|
|
6881
|
+
} catch (error) {
|
|
6882
|
+
lastError = error;
|
|
6883
|
+
import_logger4.logger.error(
|
|
6884
|
+
"tg %s network failed: attempt=%d (%s)",
|
|
6885
|
+
method,
|
|
6886
|
+
attempt,
|
|
6887
|
+
getErrorMessage2(error)
|
|
6888
|
+
);
|
|
6889
|
+
if (attempt < TG_REQUEST_ATTEMPTS) {
|
|
6890
|
+
await (0, import_async2.delay)(TG_REQUEST_RETRY_DELAY_MS);
|
|
6891
|
+
}
|
|
6892
|
+
}
|
|
6893
|
+
}
|
|
6894
|
+
throw lastError || new Error(`Telegram ${method} request failed`);
|
|
6895
|
+
};
|
|
6896
|
+
var sendTelegramMessage = async ({
|
|
6897
|
+
message,
|
|
6898
|
+
markup,
|
|
6899
|
+
token,
|
|
6900
|
+
chatId
|
|
6901
|
+
}) => {
|
|
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
|
+
}
|
|
6915
|
+
});
|
|
6916
|
+
import_logger4.logger.info(
|
|
6917
|
+
"tg sendMessage: %s",
|
|
6918
|
+
data?.ok ? "sent" : getTelegramErrorReason(data)
|
|
6919
|
+
);
|
|
6920
|
+
return data;
|
|
6921
|
+
};
|
|
5979
6922
|
var formatMessage = (signal, analysis) => {
|
|
5980
6923
|
const {
|
|
5981
6924
|
symbol,
|
|
@@ -6027,7 +6970,9 @@ var formatMessage = (signal, analysis) => {
|
|
|
6027
6970
|
}
|
|
6028
6971
|
lines.push(orderStatusText);
|
|
6029
6972
|
if ((orderStatus === "skipped" || orderStatus === "canceled") && orderSkipReason) {
|
|
6030
|
-
lines.push(
|
|
6973
|
+
lines.push(
|
|
6974
|
+
`Skip reason: <b>${escapeHtml(formatOrderSkipReason(orderSkipReason))}</b>`
|
|
6975
|
+
);
|
|
6031
6976
|
}
|
|
6032
6977
|
}
|
|
6033
6978
|
if (isConfigFromBacktest) {
|
|
@@ -6045,21 +6990,9 @@ var formatMessage = (signal, analysis) => {
|
|
|
6045
6990
|
lines.push(aiQualityLine);
|
|
6046
6991
|
}
|
|
6047
6992
|
lines.push("");
|
|
6048
|
-
if (touches) {
|
|
6049
|
-
lines.push(`Points: ${touches}`);
|
|
6050
|
-
}
|
|
6051
|
-
if (atrPct != null && Number.isFinite(atrPct)) {
|
|
6052
|
-
lines.push(`ATR: ${atrPct.toFixed(2)}`);
|
|
6053
|
-
}
|
|
6054
|
-
if (distance) {
|
|
6055
|
-
lines.push(`Distance: ${distance}`);
|
|
6056
|
-
}
|
|
6057
6993
|
if (correlation) {
|
|
6058
6994
|
lines.push(`BTC correlation: ${correlation}`);
|
|
6059
6995
|
}
|
|
6060
|
-
if (spread != null && Number.isFinite(spread)) {
|
|
6061
|
-
lines.push(`BTC spread (CB-BN)/BN: ${spread.toFixed(6)}`);
|
|
6062
|
-
}
|
|
6063
6996
|
const prices = formatPrices();
|
|
6064
6997
|
if (prices) {
|
|
6065
6998
|
lines.push("");
|
|
@@ -6073,58 +7006,88 @@ var formatMessage = (signal, analysis) => {
|
|
|
6073
7006
|
\u0414\u0435\u0442\u0430\u043B\u0438: ${err.message || String(err)}`;
|
|
6074
7007
|
}
|
|
6075
7008
|
};
|
|
6076
|
-
var sendSignal = async (signal, imgInterval, analysis) => {
|
|
7009
|
+
var sendSignal = async (signal, imgInterval, analysis, options = {}) => {
|
|
6077
7010
|
const { symbol, signalId, interval } = signal;
|
|
7011
|
+
const { token, chatId } = await getTelegramSettings(options.userName);
|
|
6078
7012
|
const message = formatMessage(signal, analysis);
|
|
6079
|
-
const
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
7013
|
+
const publicAppUrl = APP_URL2?.startsWith("https") ? APP_URL2 : null;
|
|
7014
|
+
const dashboardUrl = publicAppUrl ? `${APP_URL2}/routes/dashboard/bybit/${symbol}/${interval}/?signalId=${signalId}` : null;
|
|
7015
|
+
const actionButtons = [
|
|
7016
|
+
dashboardUrl ? { text: "Dashboard", url: dashboardUrl } : null
|
|
7017
|
+
].filter(Boolean);
|
|
7018
|
+
const markup = actionButtons.length ? {
|
|
7019
|
+
inline_keyboard: [actionButtons]
|
|
7020
|
+
} : void 0;
|
|
7021
|
+
let screenshotBytes = null;
|
|
7022
|
+
try {
|
|
7023
|
+
const screenshot = await getScreenshotBuffer({
|
|
7024
|
+
...signal,
|
|
7025
|
+
interval: imgInterval
|
|
7026
|
+
});
|
|
7027
|
+
screenshotBytes = screenshot.buffer.slice(
|
|
7028
|
+
screenshot.byteOffset,
|
|
7029
|
+
screenshot.byteOffset + screenshot.byteLength
|
|
7030
|
+
);
|
|
7031
|
+
} catch (error) {
|
|
7032
|
+
import_logger4.logger.error(
|
|
7033
|
+
"tg screenshot unavailable: %s (%s)",
|
|
7034
|
+
symbol,
|
|
7035
|
+
error?.message || String(error)
|
|
7036
|
+
);
|
|
7037
|
+
await sendTelegramMessage({ message, markup, token, chatId });
|
|
7038
|
+
return;
|
|
7039
|
+
}
|
|
7040
|
+
const photoBody = new FormData();
|
|
7041
|
+
photoBody.set("chat_id", String(chatId || ""));
|
|
7042
|
+
photoBody.set(
|
|
7043
|
+
"photo",
|
|
7044
|
+
new Blob([screenshotBytes], { type: "image/png" }),
|
|
7045
|
+
getScreenshotFilename({ ...signal, interval: imgInterval })
|
|
7046
|
+
);
|
|
7047
|
+
photoBody.set("caption", message);
|
|
7048
|
+
photoBody.set("parse_mode", "HTML");
|
|
7049
|
+
if (markup) {
|
|
7050
|
+
photoBody.set("reply_markup", JSON.stringify(markup));
|
|
7051
|
+
}
|
|
7052
|
+
let data;
|
|
7053
|
+
try {
|
|
7054
|
+
data = await requestTelegram({
|
|
7055
|
+
method: "sendPhoto",
|
|
7056
|
+
token,
|
|
7057
|
+
init: {
|
|
7058
|
+
method: "POST",
|
|
7059
|
+
body: photoBody
|
|
7060
|
+
}
|
|
7061
|
+
});
|
|
7062
|
+
} catch (error) {
|
|
7063
|
+
const reason = getErrorMessage2(error);
|
|
7064
|
+
import_logger4.logger.error("tg sendPhoto request failed: %s", reason);
|
|
7065
|
+
await sendTelegramMessage({
|
|
7066
|
+
message: `${message}
|
|
7067
|
+
|
|
7068
|
+
\u26A0\uFE0F <b>Photo delivery failed</b>
|
|
7069
|
+
Reason: <code>${escapeHtml(reason)}</code>`,
|
|
7070
|
+
markup,
|
|
7071
|
+
token,
|
|
7072
|
+
chatId
|
|
6098
7073
|
});
|
|
6099
7074
|
return;
|
|
6100
7075
|
}
|
|
6101
|
-
const imageUrl = getImageUrl({ ...signal, interval: imgInterval });
|
|
6102
|
-
const res = await fetch(`https://api.telegram.org/bot${token}/sendPhoto`, {
|
|
6103
|
-
method: "POST",
|
|
6104
|
-
headers: { "Content-Type": "application/json" },
|
|
6105
|
-
body: JSON.stringify({
|
|
6106
|
-
chat_id: chatId,
|
|
6107
|
-
photo: imageUrl,
|
|
6108
|
-
caption: message,
|
|
6109
|
-
reply_markup: markup,
|
|
6110
|
-
parse_mode: "HTML"
|
|
6111
|
-
})
|
|
6112
|
-
});
|
|
6113
|
-
const data = await res.json();
|
|
6114
7076
|
if (!data?.ok) {
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
7077
|
+
const reason = getTelegramErrorReason(data);
|
|
7078
|
+
import_logger4.logger.error("tg sendPhoto failed: %s", JSON.stringify(data));
|
|
7079
|
+
await sendTelegramMessage({
|
|
7080
|
+
message: `${message}
|
|
7081
|
+
|
|
7082
|
+
\u26A0\uFE0F <b>Photo delivery failed</b>
|
|
7083
|
+
Reason: <code>${escapeHtml(reason)}</code>`,
|
|
7084
|
+
markup,
|
|
7085
|
+
token,
|
|
7086
|
+
chatId
|
|
6125
7087
|
});
|
|
7088
|
+
return;
|
|
6126
7089
|
}
|
|
6127
|
-
|
|
7090
|
+
import_logger4.logger.info("tg sendPhoto: sent");
|
|
6128
7091
|
};
|
|
6129
7092
|
var formatAnalysisMessage = (signal, analysis) => {
|
|
6130
7093
|
const lines = [];
|
|
@@ -6168,21 +7131,25 @@ ${escapeHtml(clean)}`);
|
|
|
6168
7131
|
}
|
|
6169
7132
|
return lines.join("\n");
|
|
6170
7133
|
};
|
|
6171
|
-
var sendSignalAnalysis = async (signal, analysis) => {
|
|
7134
|
+
var sendSignalAnalysis = async (signal, analysis, options = {}) => {
|
|
7135
|
+
const { token, chatId } = await getTelegramSettings(options.userName);
|
|
6172
7136
|
const message = formatAnalysisMessage(signal, analysis);
|
|
6173
|
-
const
|
|
6174
|
-
method: "
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
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
|
+
}
|
|
6181
7149
|
});
|
|
6182
|
-
|
|
6183
|
-
import_logger3.logger.info(
|
|
7150
|
+
import_logger4.logger.info(
|
|
6184
7151
|
"tg sendMessage (analysis): %s",
|
|
6185
|
-
data?.ok ? "sent" :
|
|
7152
|
+
data?.ok ? "sent" : getTelegramErrorReason(data)
|
|
6186
7153
|
);
|
|
6187
7154
|
};
|
|
6188
7155
|
|
|
@@ -6196,7 +7163,7 @@ var cleanFiles = async (dir) => {
|
|
|
6196
7163
|
total: files.length,
|
|
6197
7164
|
width: 30
|
|
6198
7165
|
});
|
|
6199
|
-
|
|
7166
|
+
import_logger5.logger.info(import_chalk.default.yellow("clean:", dir));
|
|
6200
7167
|
for await (const file of files) {
|
|
6201
7168
|
completed++;
|
|
6202
7169
|
const fullPath = `${dir}/${file}`;
|
|
@@ -6210,41 +7177,41 @@ var cleanFiles = async (dir) => {
|
|
|
6210
7177
|
bar.tick(completed === files.length ? completed % 100 : 100);
|
|
6211
7178
|
}
|
|
6212
7179
|
}
|
|
6213
|
-
|
|
7180
|
+
import_logger5.logger.info("");
|
|
6214
7181
|
};
|
|
6215
7182
|
var cleanRedis = async (area) => {
|
|
6216
7183
|
let completed = 0;
|
|
6217
|
-
const keys = await (0,
|
|
7184
|
+
const keys = await (0, import_redis2.getKeys)(area);
|
|
6218
7185
|
const bar = new import_progress.default(":current/:total [:bar][:percent] :eta(s)", {
|
|
6219
7186
|
total: keys.length,
|
|
6220
7187
|
width: 30
|
|
6221
7188
|
});
|
|
6222
|
-
|
|
7189
|
+
import_logger5.logger.info(import_chalk.default.yellow("clean:", area));
|
|
6223
7190
|
try {
|
|
6224
7191
|
for await (const key of keys) {
|
|
6225
7192
|
completed++;
|
|
6226
|
-
await (0,
|
|
7193
|
+
await (0, import_redis2.delKeyWithOptions)(key, { raiseOnMisconf: true });
|
|
6227
7194
|
if (completed % 100 === 0 || completed === keys.length) {
|
|
6228
7195
|
bar.tick(completed === keys.length ? completed % 100 : 100);
|
|
6229
7196
|
}
|
|
6230
7197
|
}
|
|
6231
7198
|
} catch (e) {
|
|
6232
|
-
if (e instanceof
|
|
6233
|
-
|
|
7199
|
+
if (e instanceof import_redis2.RedisWriteBlockedError || String(e).includes("MISCONF")) {
|
|
7200
|
+
import_logger5.logger.error(
|
|
6234
7201
|
"Redis write is blocked by MISCONF (RDB save failure). Cleanup stopped early."
|
|
6235
7202
|
);
|
|
6236
|
-
|
|
7203
|
+
import_logger5.logger.error(
|
|
6237
7204
|
"Check Redis logs and fix persistence/memory pressure before retry."
|
|
6238
7205
|
);
|
|
6239
|
-
|
|
7206
|
+
import_logger5.logger.info("");
|
|
6240
7207
|
return;
|
|
6241
7208
|
}
|
|
6242
7209
|
throw e;
|
|
6243
7210
|
}
|
|
6244
|
-
|
|
7211
|
+
import_logger5.logger.info("");
|
|
6245
7212
|
};
|
|
6246
|
-
var update = async (connector, interval, tickers) => {
|
|
6247
|
-
const PRELOAD_START = (0, import_time.getTimestamp)(
|
|
7213
|
+
var update = async (connector, interval, tickers, preloadDays = import_constants.PRELOAD_DAYS) => {
|
|
7214
|
+
const PRELOAD_START = (0, import_time.getTimestamp)(preloadDays);
|
|
6248
7215
|
const PRELOAD_END = (0, import_time.getTimestamp)();
|
|
6249
7216
|
const bar = new import_progress.default(
|
|
6250
7217
|
":current/:total [:bar][:percent] :eta(s) :symbol",
|
|
@@ -6253,27 +7220,32 @@ var update = async (connector, interval, tickers) => {
|
|
|
6253
7220
|
width: 30
|
|
6254
7221
|
}
|
|
6255
7222
|
);
|
|
6256
|
-
|
|
7223
|
+
import_logger5.logger.info(
|
|
7224
|
+
import_chalk.default.yellow(
|
|
7225
|
+
`update: ${tickers.length} (klineConcurrency=${KLINE_CONCURRENCY_LIMIT}, preloadDays=${preloadDays})`
|
|
7226
|
+
)
|
|
7227
|
+
);
|
|
6257
7228
|
const queue = tickers.slice();
|
|
6258
7229
|
if (!queue.includes("BTCUSDT")) {
|
|
6259
7230
|
queue.unshift("BTCUSDT");
|
|
6260
7231
|
}
|
|
6261
|
-
await (0,
|
|
7232
|
+
await (0, import_async3.runWithConcurrency)(queue, KLINE_CONCURRENCY_LIMIT, async (symbol) => {
|
|
6262
7233
|
try {
|
|
6263
7234
|
await connector.kline({
|
|
6264
7235
|
symbol,
|
|
6265
7236
|
start: PRELOAD_START,
|
|
6266
7237
|
end: PRELOAD_END,
|
|
6267
7238
|
interval,
|
|
6268
|
-
silent: true
|
|
7239
|
+
silent: true,
|
|
7240
|
+
warmOnly: true
|
|
6269
7241
|
});
|
|
6270
7242
|
} catch {
|
|
6271
|
-
|
|
7243
|
+
import_logger5.logger.error("Failed loading: %s", symbol);
|
|
6272
7244
|
} finally {
|
|
6273
7245
|
bar.tick(1, { symbol: import_chalk.default.gray(symbol) });
|
|
6274
7246
|
}
|
|
6275
7247
|
});
|
|
6276
|
-
|
|
7248
|
+
import_logger5.logger.info("");
|
|
6277
7249
|
};
|
|
6278
7250
|
var parseSymbolsFromCLI = (symbol = "") => symbol.split(",").map((s) => {
|
|
6279
7251
|
const ticker = s.toUpperCase();
|
|
@@ -6311,14 +7283,14 @@ var getTickers = async (connector, include = "", exclude = "", limit, chunk) =>
|
|
|
6311
7283
|
}
|
|
6312
7284
|
if (chunk) {
|
|
6313
7285
|
const [currentChunk, chunksCount] = chunk.split("/").map((c) => parseInt(c));
|
|
6314
|
-
|
|
7286
|
+
import_logger5.logger.info("chunk: %d / %d", currentChunk, chunksCount);
|
|
6315
7287
|
const chunkSize = Math.ceil(tickers.length / chunksCount);
|
|
6316
7288
|
const chunks = import_lodash.default.chunk(tickers, chunkSize);
|
|
6317
7289
|
tickers = chunks[currentChunk - 1];
|
|
6318
7290
|
}
|
|
6319
7291
|
return tickers.filter((t) => !excludeTickers.includes(t));
|
|
6320
7292
|
};
|
|
6321
|
-
var makeScreenshots = async (signals, interval) => {
|
|
7293
|
+
var makeScreenshots = async (signals, interval, userName = "root") => {
|
|
6322
7294
|
const projectRoot = getProjectRoot2();
|
|
6323
7295
|
const bar = new import_progress.default(
|
|
6324
7296
|
":current/:total [:bar][:percent] :eta(s) :symbol",
|
|
@@ -6327,15 +7299,15 @@ var makeScreenshots = async (signals, interval) => {
|
|
|
6327
7299
|
width: 30
|
|
6328
7300
|
}
|
|
6329
7301
|
);
|
|
6330
|
-
|
|
6331
|
-
await (0,
|
|
7302
|
+
import_logger5.logger.info(import_chalk.default.yellow("screenshots:", `${interval}m`, signals.length));
|
|
7303
|
+
await (0, import_async3.runWithConcurrency)(
|
|
6332
7304
|
signals,
|
|
6333
7305
|
SCREENSHOT_CONCURRENCY_LIMIT,
|
|
6334
7306
|
async (signal) => {
|
|
6335
7307
|
try {
|
|
6336
|
-
await screenDashboard({ ...signal, interval }, projectRoot);
|
|
7308
|
+
await screenDashboard({ ...signal, interval }, projectRoot, userName);
|
|
6337
7309
|
} catch (error) {
|
|
6338
|
-
|
|
7310
|
+
import_logger5.logger.error(
|
|
6339
7311
|
"Failed screenshot: %s (%s)",
|
|
6340
7312
|
signal.symbol,
|
|
6341
7313
|
error?.message || String(error)
|
|
@@ -6345,9 +7317,9 @@ var makeScreenshots = async (signals, interval) => {
|
|
|
6345
7317
|
}
|
|
6346
7318
|
}
|
|
6347
7319
|
);
|
|
6348
|
-
|
|
7320
|
+
import_logger5.logger.info("");
|
|
6349
7321
|
};
|
|
6350
|
-
var sendToAI = async (signals) => {
|
|
7322
|
+
var sendToAI = async (signals, userName = "root") => {
|
|
6351
7323
|
const bar = new import_progress.default(
|
|
6352
7324
|
":current/:total [:bar][:percent] :eta(s) :symbol",
|
|
6353
7325
|
{
|
|
@@ -6355,19 +7327,19 @@ var sendToAI = async (signals) => {
|
|
|
6355
7327
|
width: 30
|
|
6356
7328
|
}
|
|
6357
7329
|
);
|
|
6358
|
-
|
|
6359
|
-
await (0,
|
|
7330
|
+
import_logger5.logger.info(import_chalk.default.yellow("AI:", signals.length));
|
|
7331
|
+
await (0, import_async3.runWithConcurrency)(signals, AI_CONCURRENCY_LIMIT, async (signal) => {
|
|
6360
7332
|
try {
|
|
6361
|
-
await askAI(signal);
|
|
7333
|
+
await askAI(signal, { userName });
|
|
6362
7334
|
} catch {
|
|
6363
|
-
|
|
7335
|
+
import_logger5.logger.error("Failed ask: %s", signal.symbol);
|
|
6364
7336
|
} finally {
|
|
6365
7337
|
bar.tick(1, { symbol: import_chalk.default.gray(signal.symbol) });
|
|
6366
7338
|
}
|
|
6367
7339
|
});
|
|
6368
|
-
|
|
7340
|
+
import_logger5.logger.info("");
|
|
6369
7341
|
};
|
|
6370
|
-
var sendToTG = async (signals, imgInterval) => {
|
|
7342
|
+
var sendToTG = async (signals, imgInterval, userName = "root") => {
|
|
6371
7343
|
const bar = new import_progress.default(
|
|
6372
7344
|
":current/:total [:bar][:percent] :eta(s) :symbol",
|
|
6373
7345
|
{
|
|
@@ -6375,19 +7347,19 @@ var sendToTG = async (signals, imgInterval) => {
|
|
|
6375
7347
|
width: 30
|
|
6376
7348
|
}
|
|
6377
7349
|
);
|
|
6378
|
-
|
|
6379
|
-
await (0,
|
|
7350
|
+
import_logger5.logger.info(import_chalk.default.yellow("messages:", signals.length));
|
|
7351
|
+
await (0, import_async3.runWithConcurrency)(signals, 1, async (signal) => {
|
|
6380
7352
|
try {
|
|
6381
|
-
const analysis = await (0,
|
|
6382
|
-
|
|
7353
|
+
const analysis = await (0, import_redis2.getData)(
|
|
7354
|
+
import_redis2.redisKeys.analysis(signal.symbol, signal.signalId),
|
|
6383
7355
|
null
|
|
6384
7356
|
);
|
|
6385
|
-
await sendSignal(signal, imgInterval, analysis);
|
|
7357
|
+
await sendSignal(signal, imgInterval, analysis, { userName });
|
|
6386
7358
|
if (analysis && typeof analysis === "object" && Object.keys(analysis).length > 0) {
|
|
6387
|
-
await sendSignalAnalysis(signal, analysis);
|
|
7359
|
+
await sendSignalAnalysis(signal, analysis, { userName });
|
|
6388
7360
|
}
|
|
6389
7361
|
} catch (err) {
|
|
6390
|
-
|
|
7362
|
+
import_logger5.logger.error(
|
|
6391
7363
|
"Failed sent: %s %s",
|
|
6392
7364
|
signal.symbol,
|
|
6393
7365
|
err?.message || String(err)
|
|
@@ -6396,7 +7368,7 @@ var sendToTG = async (signals, imgInterval) => {
|
|
|
6396
7368
|
bar.tick(1, { symbol: import_chalk.default.gray(signal.symbol) });
|
|
6397
7369
|
}
|
|
6398
7370
|
});
|
|
6399
|
-
|
|
7371
|
+
import_logger5.logger.info("");
|
|
6400
7372
|
};
|
|
6401
7373
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6402
7374
|
0 && (module.exports = {
|