@tradejs/node 1.0.3 → 1.0.5
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-MDMDKAEE.mjs → ai-G7ATN4YL.mjs} +3 -3
- package/dist/backtest.js +41 -8
- package/dist/backtest.mjs +4 -4
- package/dist/{chunk-MGFEID6K.mjs → chunk-CIY64D57.mjs} +7 -4
- package/dist/{chunk-QDYCJ2OK.mjs → chunk-LMAKIC3C.mjs} +1 -1
- package/dist/{chunk-3C76HVLA.mjs → chunk-P2ZUWONT.mjs} +29 -2
- package/dist/{chunk-H4HXW3EZ.mjs → chunk-RBE4PZER.mjs} +1 -1
- package/dist/chunk-SCMBUEGK.mjs +22 -0
- package/dist/{chunk-CK2PW4L5.mjs → chunk-ZY6ULOWK.mjs} +7 -4
- package/dist/cli.js +496 -104
- package/dist/cli.mjs +483 -100
- package/dist/connectors.js +35 -5
- package/dist/connectors.mjs +2 -2
- package/dist/constants.js +10 -1
- package/dist/constants.mjs +1 -1
- package/dist/registry.js +35 -5
- package/dist/registry.mjs +2 -2
- package/dist/strategies.js +321 -81
- package/dist/strategies.mjs +290 -80
- package/package.json +15 -6
- package/dist/chunk-4ZPGZWO7.mjs +0 -13
package/dist/cli.js
CHANGED
|
@@ -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,7 +5536,16 @@ var import_time = require("@tradejs/core/time");
|
|
|
5536
5536
|
|
|
5537
5537
|
// src/constants.ts
|
|
5538
5538
|
var { NODE_ENV } = process.env;
|
|
5539
|
-
var
|
|
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
|
+
);
|
|
5540
5549
|
var TG_CONCURRENCY_LIMIT = 3;
|
|
5541
5550
|
var AI_CONCURRENCY_LIMIT = 3;
|
|
5542
5551
|
var SCREENSHOT_CONCURRENCY_LIMIT = NODE_ENV === "production" ? 1 : 2;
|
|
@@ -5544,7 +5553,7 @@ var SCREENSHOT_CONCURRENCY_LIMIT = NODE_ENV === "production" ? 1 : 2;
|
|
|
5544
5553
|
// src/cli.ts
|
|
5545
5554
|
var import_files = require("@tradejs/infra/files");
|
|
5546
5555
|
var import_redis3 = require("@tradejs/infra/redis");
|
|
5547
|
-
var
|
|
5556
|
+
var import_logger5 = require("@tradejs/infra/logger");
|
|
5548
5557
|
|
|
5549
5558
|
// src/ai.ts
|
|
5550
5559
|
var import_redis = require("@tradejs/infra/redis");
|
|
@@ -5897,65 +5906,325 @@ var import_promises = __toESM(require("fs/promises"));
|
|
|
5897
5906
|
var import_path2 = __toESM(require("path"));
|
|
5898
5907
|
var import_puppeteer = __toESM(require("puppeteer"));
|
|
5899
5908
|
var import_async = require("@tradejs/core/async");
|
|
5909
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
5900
5910
|
var import_redis2 = require("@tradejs/infra/redis");
|
|
5901
5911
|
var { APP_URL } = process.env;
|
|
5912
|
+
var SCREENSHOT_NAVIGATION_ATTEMPTS = 3;
|
|
5913
|
+
var SCREENSHOT_NAVIGATION_RETRY_DELAY_MS = 2e3;
|
|
5914
|
+
var SCREENSHOT_CAPTURE_ATTEMPTS = 2;
|
|
5915
|
+
var SCREENSHOT_CAPTURE_RETRY_DELAY_MS = 2e3;
|
|
5916
|
+
var SCREENSHOT_NAVIGATION_TIMEOUT_MS = 3e4;
|
|
5917
|
+
var SCREENSHOT_RENDER_DELAY_MS = 1e4;
|
|
5918
|
+
var SCREENSHOT_CONSOLE_LOG_LIMIT = 20;
|
|
5919
|
+
var SCREENSHOT_CONSOLE_TEXT_LIMIT = 500;
|
|
5920
|
+
var SCREENSHOT_BROWSER_STDERR = process.env.SCREENSHOT_BROWSER_STDERR === "1";
|
|
5921
|
+
var PUPPETEER_DUMPIO = process.env.PUPPETEER_DUMPIO === "1";
|
|
5922
|
+
var SCREENSHOT_VIEWPORT = {
|
|
5923
|
+
width: 1280,
|
|
5924
|
+
height: 800,
|
|
5925
|
+
deviceScaleFactor: process.env.NODE_ENV === "production" ? 1 : 2
|
|
5926
|
+
};
|
|
5902
5927
|
var getProjectRoot = (projectRoot) => import_path2.default.resolve(getTradejsProjectCwd(projectRoot));
|
|
5903
5928
|
var getScreenshotsDir = (projectRoot) => import_path2.default.join(getProjectRoot(projectRoot), "data", "screenshots");
|
|
5904
|
-
var
|
|
5929
|
+
var maskTokenInUrl = (url) => url.replace(/([?&]token=)[^&]+/i, "$1<hidden>");
|
|
5930
|
+
var getErrorMessage = (error) => {
|
|
5931
|
+
const maybeError = error;
|
|
5932
|
+
const message = maybeError?.message || String(error);
|
|
5933
|
+
if (maybeError?.cause == null) {
|
|
5934
|
+
return message;
|
|
5935
|
+
}
|
|
5936
|
+
const cause = maybeError.cause instanceof Error ? maybeError.cause.message : String(maybeError.cause);
|
|
5937
|
+
return `${message}; cause: ${cause}`;
|
|
5938
|
+
};
|
|
5939
|
+
var truncateText = (value, maxLength = SCREENSHOT_CONSOLE_TEXT_LIMIT) => value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
|
|
5940
|
+
var shouldLogConsoleMessage = (type) => ["error", "warning", "assert"].includes(type);
|
|
5941
|
+
var getScreenshotBuffer = async (signal, projectRoot) => {
|
|
5942
|
+
const screenshotPath = getScreenshotPath(signal, projectRoot);
|
|
5943
|
+
return import_promises.default.readFile(screenshotPath);
|
|
5944
|
+
};
|
|
5945
|
+
var getScreenshotFilename = ({
|
|
5946
|
+
symbol,
|
|
5947
|
+
signalId,
|
|
5948
|
+
interval
|
|
5949
|
+
}) => `${symbol}_${signalId}_${interval}.png`;
|
|
5950
|
+
var getScreenshotRenderBaseUrl = () => {
|
|
5951
|
+
const fallback = String(APP_URL || "").trim();
|
|
5952
|
+
if (fallback) {
|
|
5953
|
+
return fallback;
|
|
5954
|
+
}
|
|
5955
|
+
throw new Error("APP_URL is required for screenshots");
|
|
5956
|
+
};
|
|
5905
5957
|
var getScreenshotPath = ({ symbol, signalId, interval }, projectRoot) => {
|
|
5906
5958
|
return import_path2.default.join(
|
|
5907
5959
|
getScreenshotsDir(projectRoot),
|
|
5908
|
-
|
|
5960
|
+
getScreenshotFilename({ symbol, signalId, interval })
|
|
5909
5961
|
);
|
|
5910
5962
|
};
|
|
5911
5963
|
var screenDashboard = async (signal, projectRoot) => {
|
|
5912
5964
|
const { symbol, signalId, interval } = signal;
|
|
5965
|
+
const screenshotBaseUrl = getScreenshotRenderBaseUrl();
|
|
5966
|
+
const screenshotPath = getScreenshotPath(signal, projectRoot);
|
|
5913
5967
|
const rootUser = await (0, import_redis2.getData)(import_redis2.redisKeys.user("root"), null);
|
|
5914
5968
|
const token2 = rootUser && typeof rootUser === "object" ? rootUser.token : null;
|
|
5915
5969
|
const tokenParam = typeof token2 === "string" && token2.length > 0 ? `&token=${encodeURIComponent(token2)}` : "";
|
|
5916
|
-
const
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5970
|
+
const dashboardUrl = `${screenshotBaseUrl}/routes/dashboard/bybit/${symbol}/${interval}/?signalId=${signalId}&autoZoom=true&screenshot=1${tokenParam}`;
|
|
5971
|
+
const maskedDashboardUrl = maskTokenInUrl(dashboardUrl);
|
|
5972
|
+
import_logger3.logger.info(
|
|
5973
|
+
"screenshot start: %s %sm url=%s path=%s",
|
|
5974
|
+
symbol,
|
|
5975
|
+
interval,
|
|
5976
|
+
maskedDashboardUrl,
|
|
5977
|
+
screenshotPath
|
|
5978
|
+
);
|
|
5979
|
+
let captureError = null;
|
|
5980
|
+
for (let captureAttempt = 1; captureAttempt <= SCREENSHOT_CAPTURE_ATTEMPTS; captureAttempt += 1) {
|
|
5981
|
+
const browser = await import_puppeteer.default.launch({
|
|
5982
|
+
headless: true,
|
|
5983
|
+
dumpio: PUPPETEER_DUMPIO,
|
|
5984
|
+
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
|
|
5985
|
+
args: [
|
|
5986
|
+
"--no-sandbox",
|
|
5987
|
+
"--disable-setuid-sandbox",
|
|
5988
|
+
"--disable-dev-shm-usage",
|
|
5989
|
+
"--disable-gpu",
|
|
5990
|
+
"--font-render-hinting=medium"
|
|
5991
|
+
]
|
|
5992
|
+
});
|
|
5993
|
+
const browserProcess = browser.process();
|
|
5929
5994
|
try {
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5995
|
+
if (browserProcess) {
|
|
5996
|
+
import_logger3.logger.info(
|
|
5997
|
+
"screenshot browser: %s %sm captureAttempt=%d pid=%s exec=%s",
|
|
5998
|
+
symbol,
|
|
5999
|
+
interval,
|
|
6000
|
+
captureAttempt,
|
|
6001
|
+
String(browserProcess.pid ?? "unknown"),
|
|
6002
|
+
browserProcess.spawnfile || "unknown"
|
|
6003
|
+
);
|
|
6004
|
+
}
|
|
6005
|
+
import_logger3.logger.info(
|
|
6006
|
+
"screenshot browser version: %s %sm captureAttempt=%d version=%s",
|
|
6007
|
+
symbol,
|
|
6008
|
+
interval,
|
|
6009
|
+
captureAttempt,
|
|
6010
|
+
await browser.version()
|
|
5937
6011
|
);
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
6012
|
+
browser.on("disconnected", () => {
|
|
6013
|
+
import_logger3.logger.error(
|
|
6014
|
+
"screenshot browser disconnected: %s %sm captureAttempt=%d",
|
|
6015
|
+
symbol,
|
|
6016
|
+
interval,
|
|
6017
|
+
captureAttempt
|
|
6018
|
+
);
|
|
5942
6019
|
});
|
|
6020
|
+
if (SCREENSHOT_BROWSER_STDERR && browserProcess?.stderr) {
|
|
6021
|
+
browserProcess.stderr.on("data", (chunk) => {
|
|
6022
|
+
const text = String(chunk).trim();
|
|
6023
|
+
if (!text) {
|
|
6024
|
+
return;
|
|
6025
|
+
}
|
|
6026
|
+
import_logger3.logger.error(
|
|
6027
|
+
"screenshot browser stderr: %s %sm captureAttempt=%d (%s)",
|
|
6028
|
+
symbol,
|
|
6029
|
+
interval,
|
|
6030
|
+
captureAttempt,
|
|
6031
|
+
truncateText(text)
|
|
6032
|
+
);
|
|
6033
|
+
});
|
|
6034
|
+
}
|
|
6035
|
+
const page = await browser.newPage();
|
|
6036
|
+
try {
|
|
6037
|
+
let consoleMessagesLogged = 0;
|
|
6038
|
+
page.on("requestfailed", (request) => {
|
|
6039
|
+
if (!request.isNavigationRequest() && request.resourceType() !== "document") {
|
|
6040
|
+
return;
|
|
6041
|
+
}
|
|
6042
|
+
import_logger3.logger.error(
|
|
6043
|
+
"screenshot request failed: %s %sm %s (%s)",
|
|
6044
|
+
symbol,
|
|
6045
|
+
interval,
|
|
6046
|
+
maskTokenInUrl(request.url()),
|
|
6047
|
+
request.failure()?.errorText || "unknown"
|
|
6048
|
+
);
|
|
6049
|
+
});
|
|
6050
|
+
page.on("pageerror", (error) => {
|
|
6051
|
+
import_logger3.logger.error(
|
|
6052
|
+
"screenshot page error: %s %sm (%s)",
|
|
6053
|
+
symbol,
|
|
6054
|
+
interval,
|
|
6055
|
+
error.message || String(error)
|
|
6056
|
+
);
|
|
6057
|
+
});
|
|
6058
|
+
page.on("error", (error) => {
|
|
6059
|
+
import_logger3.logger.error(
|
|
6060
|
+
"screenshot target error: %s %sm (%s)",
|
|
6061
|
+
symbol,
|
|
6062
|
+
interval,
|
|
6063
|
+
error.message || String(error)
|
|
6064
|
+
);
|
|
6065
|
+
});
|
|
6066
|
+
page.on("close", () => {
|
|
6067
|
+
import_logger3.logger.info(
|
|
6068
|
+
"screenshot page closed: %s %sm captureAttempt=%d",
|
|
6069
|
+
symbol,
|
|
6070
|
+
interval,
|
|
6071
|
+
captureAttempt
|
|
6072
|
+
);
|
|
6073
|
+
});
|
|
6074
|
+
page.on("console", (message) => {
|
|
6075
|
+
if (!shouldLogConsoleMessage(message.type())) {
|
|
6076
|
+
return;
|
|
6077
|
+
}
|
|
6078
|
+
if (consoleMessagesLogged >= SCREENSHOT_CONSOLE_LOG_LIMIT) {
|
|
6079
|
+
return;
|
|
6080
|
+
}
|
|
6081
|
+
consoleMessagesLogged += 1;
|
|
6082
|
+
const location = message.location();
|
|
6083
|
+
const suffix = location.url ? ` ${maskTokenInUrl(location.url)}:${location.lineNumber ?? 0}:${location.columnNumber ?? 0}` : "";
|
|
6084
|
+
import_logger3.logger.error(
|
|
6085
|
+
"screenshot console %s: %s %sm captureAttempt=%d (%s%s)",
|
|
6086
|
+
message.type(),
|
|
6087
|
+
symbol,
|
|
6088
|
+
interval,
|
|
6089
|
+
captureAttempt,
|
|
6090
|
+
truncateText(message.text()),
|
|
6091
|
+
suffix
|
|
6092
|
+
);
|
|
6093
|
+
});
|
|
6094
|
+
page.on("response", (response) => {
|
|
6095
|
+
const status = response.status();
|
|
6096
|
+
if (status < 400) {
|
|
6097
|
+
return;
|
|
6098
|
+
}
|
|
6099
|
+
const request = response.request();
|
|
6100
|
+
const resourceType = request.resourceType();
|
|
6101
|
+
if (!["document", "script", "stylesheet", "fetch", "xhr"].includes(
|
|
6102
|
+
resourceType
|
|
6103
|
+
)) {
|
|
6104
|
+
return;
|
|
6105
|
+
}
|
|
6106
|
+
import_logger3.logger.error(
|
|
6107
|
+
"screenshot response failed: %s %sm captureAttempt=%d status=%d type=%s url=%s",
|
|
6108
|
+
symbol,
|
|
6109
|
+
interval,
|
|
6110
|
+
captureAttempt,
|
|
6111
|
+
status,
|
|
6112
|
+
resourceType,
|
|
6113
|
+
maskTokenInUrl(response.url())
|
|
6114
|
+
);
|
|
6115
|
+
});
|
|
6116
|
+
await page.setViewport(SCREENSHOT_VIEWPORT);
|
|
6117
|
+
let gotoError = null;
|
|
6118
|
+
import_logger3.logger.info(
|
|
6119
|
+
"screenshot render: %s %sm captureAttempt=%d viewport=%dx%d@%d",
|
|
6120
|
+
symbol,
|
|
6121
|
+
interval,
|
|
6122
|
+
captureAttempt,
|
|
6123
|
+
SCREENSHOT_VIEWPORT.width,
|
|
6124
|
+
SCREENSHOT_VIEWPORT.height,
|
|
6125
|
+
SCREENSHOT_VIEWPORT.deviceScaleFactor
|
|
6126
|
+
);
|
|
6127
|
+
for (let attempt = 1; attempt <= SCREENSHOT_NAVIGATION_ATTEMPTS; attempt += 1) {
|
|
6128
|
+
try {
|
|
6129
|
+
import_logger3.logger.info(
|
|
6130
|
+
"screenshot goto: %s %sm captureAttempt=%d attempt=%d url=%s",
|
|
6131
|
+
symbol,
|
|
6132
|
+
interval,
|
|
6133
|
+
captureAttempt,
|
|
6134
|
+
attempt,
|
|
6135
|
+
maskedDashboardUrl
|
|
6136
|
+
);
|
|
6137
|
+
const response = await page.goto(dashboardUrl, {
|
|
6138
|
+
waitUntil: "domcontentloaded",
|
|
6139
|
+
timeout: SCREENSHOT_NAVIGATION_TIMEOUT_MS
|
|
6140
|
+
});
|
|
6141
|
+
import_logger3.logger.info(
|
|
6142
|
+
"screenshot goto ok: %s %sm captureAttempt=%d attempt=%d status=%s finalUrl=%s",
|
|
6143
|
+
symbol,
|
|
6144
|
+
interval,
|
|
6145
|
+
captureAttempt,
|
|
6146
|
+
attempt,
|
|
6147
|
+
response ? String(response.status()) : "null",
|
|
6148
|
+
maskTokenInUrl(page.url())
|
|
6149
|
+
);
|
|
6150
|
+
import_logger3.logger.info(
|
|
6151
|
+
"screenshot page info: %s %sm captureAttempt=%d title=%s ua=%s",
|
|
6152
|
+
symbol,
|
|
6153
|
+
interval,
|
|
6154
|
+
captureAttempt,
|
|
6155
|
+
truncateText(await page.title(), 120),
|
|
6156
|
+
truncateText(await page.evaluate(() => navigator.userAgent), 240)
|
|
6157
|
+
);
|
|
6158
|
+
gotoError = null;
|
|
6159
|
+
break;
|
|
6160
|
+
} catch (error) {
|
|
6161
|
+
gotoError = error;
|
|
6162
|
+
import_logger3.logger.error(
|
|
6163
|
+
"screenshot goto failed: %s %sm captureAttempt=%d attempt=%d url=%s (%s)",
|
|
6164
|
+
symbol,
|
|
6165
|
+
interval,
|
|
6166
|
+
captureAttempt,
|
|
6167
|
+
attempt,
|
|
6168
|
+
maskedDashboardUrl,
|
|
6169
|
+
gotoError.message || String(gotoError)
|
|
6170
|
+
);
|
|
6171
|
+
if (attempt >= SCREENSHOT_NAVIGATION_ATTEMPTS) {
|
|
6172
|
+
break;
|
|
6173
|
+
}
|
|
6174
|
+
await (0, import_async.delay)(SCREENSHOT_NAVIGATION_RETRY_DELAY_MS);
|
|
6175
|
+
}
|
|
6176
|
+
}
|
|
6177
|
+
if (gotoError) {
|
|
6178
|
+
throw new Error(
|
|
6179
|
+
`Failed to open dashboard ${dashboardUrl}: ${gotoError.message || String(gotoError)}`
|
|
6180
|
+
);
|
|
6181
|
+
}
|
|
6182
|
+
await (0, import_async.delay)(SCREENSHOT_RENDER_DELAY_MS);
|
|
6183
|
+
await import_promises.default.mkdir(getScreenshotsDir(projectRoot), { recursive: true });
|
|
6184
|
+
await page.screenshot({
|
|
6185
|
+
path: screenshotPath,
|
|
6186
|
+
type: "png"
|
|
6187
|
+
});
|
|
6188
|
+
import_logger3.logger.info(
|
|
6189
|
+
"screenshot saved: %s %sm path=%s",
|
|
6190
|
+
symbol,
|
|
6191
|
+
interval,
|
|
6192
|
+
screenshotPath
|
|
6193
|
+
);
|
|
6194
|
+
return;
|
|
6195
|
+
} finally {
|
|
6196
|
+
await page.close().catch(() => void 0);
|
|
6197
|
+
}
|
|
6198
|
+
} catch (error) {
|
|
6199
|
+
captureError = error;
|
|
6200
|
+
import_logger3.logger.error(
|
|
6201
|
+
"screenshot capture failed: %s %sm captureAttempt=%d (%s)",
|
|
6202
|
+
symbol,
|
|
6203
|
+
interval,
|
|
6204
|
+
captureAttempt,
|
|
6205
|
+
getErrorMessage(captureError)
|
|
6206
|
+
);
|
|
6207
|
+
if (captureAttempt < SCREENSHOT_CAPTURE_ATTEMPTS) {
|
|
6208
|
+
await (0, import_async.delay)(SCREENSHOT_CAPTURE_RETRY_DELAY_MS);
|
|
6209
|
+
}
|
|
5943
6210
|
} finally {
|
|
5944
|
-
await
|
|
6211
|
+
await browser.close().catch(() => void 0);
|
|
5945
6212
|
}
|
|
5946
|
-
} finally {
|
|
5947
|
-
await browser.close();
|
|
5948
6213
|
}
|
|
6214
|
+
throw captureError || new Error(`Failed screenshot: ${symbol} ${interval}`);
|
|
5949
6215
|
};
|
|
5950
6216
|
|
|
5951
6217
|
// src/signals.ts
|
|
6218
|
+
var import_async2 = require("@tradejs/core/async");
|
|
5952
6219
|
var import_math = require("@tradejs/core/math");
|
|
5953
|
-
var
|
|
6220
|
+
var import_logger4 = require("@tradejs/infra/logger");
|
|
5954
6221
|
var escapeHtml = (s) => {
|
|
5955
6222
|
if (!s) return "";
|
|
5956
6223
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
5957
6224
|
};
|
|
5958
6225
|
var { APP_URL: APP_URL2, TG_BOT_TOKEN: token, TG_CHAT_ID: chatId } = process.env;
|
|
6226
|
+
var TG_REQUEST_ATTEMPTS = 3;
|
|
6227
|
+
var TG_REQUEST_RETRY_DELAY_MS = 2e3;
|
|
5959
6228
|
var normalizeQuality = (value) => typeof value === "number" ? Math.max(1, Math.min(5, Math.round(value))) : null;
|
|
5960
6229
|
var getLastNumber = (value) => {
|
|
5961
6230
|
if (Array.isArray(value)) {
|
|
@@ -5976,6 +6245,99 @@ var getAiQualityLine = (analysis) => {
|
|
|
5976
6245
|
}
|
|
5977
6246
|
return `\u{1F534} AI Quality: ${quality}/5`;
|
|
5978
6247
|
};
|
|
6248
|
+
var getTelegramErrorReason = (data) => {
|
|
6249
|
+
if (data && typeof data === "object") {
|
|
6250
|
+
const record = data;
|
|
6251
|
+
const description = typeof record.description === "string" ? record.description : "";
|
|
6252
|
+
const errorCode = typeof record.error_code === "number" ? record.error_code : void 0;
|
|
6253
|
+
if (description && errorCode != null) {
|
|
6254
|
+
return `${errorCode}: ${description}`;
|
|
6255
|
+
}
|
|
6256
|
+
if (description) {
|
|
6257
|
+
return description;
|
|
6258
|
+
}
|
|
6259
|
+
}
|
|
6260
|
+
try {
|
|
6261
|
+
return JSON.stringify(data);
|
|
6262
|
+
} catch {
|
|
6263
|
+
return String(data);
|
|
6264
|
+
}
|
|
6265
|
+
};
|
|
6266
|
+
var getErrorMessage2 = (error) => {
|
|
6267
|
+
const maybeError = error;
|
|
6268
|
+
const message = maybeError?.message || String(error);
|
|
6269
|
+
if (maybeError?.cause == null) {
|
|
6270
|
+
return message;
|
|
6271
|
+
}
|
|
6272
|
+
const cause = maybeError.cause instanceof Error ? maybeError.cause.message : String(maybeError.cause);
|
|
6273
|
+
return `${message}; cause: ${cause}`;
|
|
6274
|
+
};
|
|
6275
|
+
var parseTelegramResponse = async (response) => {
|
|
6276
|
+
const fallback = {
|
|
6277
|
+
ok: response.ok,
|
|
6278
|
+
error_code: response.status,
|
|
6279
|
+
description: response.statusText
|
|
6280
|
+
};
|
|
6281
|
+
try {
|
|
6282
|
+
return await response.json();
|
|
6283
|
+
} catch {
|
|
6284
|
+
try {
|
|
6285
|
+
const text = await response.text();
|
|
6286
|
+
return text ? {
|
|
6287
|
+
...fallback,
|
|
6288
|
+
description: text
|
|
6289
|
+
} : fallback;
|
|
6290
|
+
} catch {
|
|
6291
|
+
return fallback;
|
|
6292
|
+
}
|
|
6293
|
+
}
|
|
6294
|
+
};
|
|
6295
|
+
var requestTelegram = async (method, init) => {
|
|
6296
|
+
let lastError = null;
|
|
6297
|
+
for (let attempt = 1; attempt <= TG_REQUEST_ATTEMPTS; attempt += 1) {
|
|
6298
|
+
try {
|
|
6299
|
+
const res = await fetch(
|
|
6300
|
+
`https://api.telegram.org/bot${token}/${method}`,
|
|
6301
|
+
{
|
|
6302
|
+
...init
|
|
6303
|
+
}
|
|
6304
|
+
);
|
|
6305
|
+
return await parseTelegramResponse(res);
|
|
6306
|
+
} catch (error) {
|
|
6307
|
+
lastError = error;
|
|
6308
|
+
import_logger4.logger.error(
|
|
6309
|
+
"tg %s network failed: attempt=%d (%s)",
|
|
6310
|
+
method,
|
|
6311
|
+
attempt,
|
|
6312
|
+
getErrorMessage2(error)
|
|
6313
|
+
);
|
|
6314
|
+
if (attempt < TG_REQUEST_ATTEMPTS) {
|
|
6315
|
+
await (0, import_async2.delay)(TG_REQUEST_RETRY_DELAY_MS);
|
|
6316
|
+
}
|
|
6317
|
+
}
|
|
6318
|
+
}
|
|
6319
|
+
throw lastError || new Error(`Telegram ${method} request failed`);
|
|
6320
|
+
};
|
|
6321
|
+
var sendTelegramMessage = async ({
|
|
6322
|
+
message,
|
|
6323
|
+
markup
|
|
6324
|
+
}) => {
|
|
6325
|
+
const data = await requestTelegram("sendMessage", {
|
|
6326
|
+
method: "POST",
|
|
6327
|
+
headers: { "Content-Type": "application/json" },
|
|
6328
|
+
body: JSON.stringify({
|
|
6329
|
+
chat_id: chatId,
|
|
6330
|
+
text: message,
|
|
6331
|
+
reply_markup: markup,
|
|
6332
|
+
parse_mode: "HTML"
|
|
6333
|
+
})
|
|
6334
|
+
});
|
|
6335
|
+
import_logger4.logger.info(
|
|
6336
|
+
"tg sendMessage: %s",
|
|
6337
|
+
data?.ok ? "sent" : getTelegramErrorReason(data)
|
|
6338
|
+
);
|
|
6339
|
+
return data;
|
|
6340
|
+
};
|
|
5979
6341
|
var formatMessage = (signal, analysis) => {
|
|
5980
6342
|
const {
|
|
5981
6343
|
symbol,
|
|
@@ -6076,53 +6438,76 @@ var formatMessage = (signal, analysis) => {
|
|
|
6076
6438
|
var sendSignal = async (signal, imgInterval, analysis) => {
|
|
6077
6439
|
const { symbol, signalId, interval } = signal;
|
|
6078
6440
|
const message = formatMessage(signal, analysis);
|
|
6079
|
-
const
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6441
|
+
const publicAppUrl = APP_URL2?.startsWith("https") ? APP_URL2 : null;
|
|
6442
|
+
const dashboardUrl = publicAppUrl ? `${APP_URL2}/routes/dashboard/bybit/${symbol}/${interval}/?signalId=${signalId}` : null;
|
|
6443
|
+
const actionButtons = [
|
|
6444
|
+
dashboardUrl ? { text: "Dashboard", url: dashboardUrl } : null
|
|
6445
|
+
].filter(Boolean);
|
|
6446
|
+
const markup = actionButtons.length ? {
|
|
6447
|
+
inline_keyboard: [actionButtons]
|
|
6448
|
+
} : void 0;
|
|
6449
|
+
let screenshotBytes = null;
|
|
6450
|
+
try {
|
|
6451
|
+
const screenshot = await getScreenshotBuffer({
|
|
6452
|
+
...signal,
|
|
6453
|
+
interval: imgInterval
|
|
6454
|
+
});
|
|
6455
|
+
screenshotBytes = screenshot.buffer.slice(
|
|
6456
|
+
screenshot.byteOffset,
|
|
6457
|
+
screenshot.byteOffset + screenshot.byteLength
|
|
6458
|
+
);
|
|
6459
|
+
} catch (error) {
|
|
6460
|
+
import_logger4.logger.error(
|
|
6461
|
+
"tg screenshot unavailable: %s (%s)",
|
|
6462
|
+
symbol,
|
|
6463
|
+
error?.message || String(error)
|
|
6464
|
+
);
|
|
6465
|
+
await sendTelegramMessage({ message, markup });
|
|
6466
|
+
return;
|
|
6467
|
+
}
|
|
6468
|
+
const photoBody = new FormData();
|
|
6469
|
+
photoBody.set("chat_id", String(chatId || ""));
|
|
6470
|
+
photoBody.set(
|
|
6471
|
+
"photo",
|
|
6472
|
+
new Blob([screenshotBytes], { type: "image/png" }),
|
|
6473
|
+
getScreenshotFilename({ ...signal, interval: imgInterval })
|
|
6474
|
+
);
|
|
6475
|
+
photoBody.set("caption", message);
|
|
6476
|
+
photoBody.set("parse_mode", "HTML");
|
|
6477
|
+
if (markup) {
|
|
6478
|
+
photoBody.set("reply_markup", JSON.stringify(markup));
|
|
6479
|
+
}
|
|
6480
|
+
let data;
|
|
6481
|
+
try {
|
|
6482
|
+
data = await requestTelegram("sendPhoto", {
|
|
6091
6483
|
method: "POST",
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6484
|
+
body: photoBody
|
|
6485
|
+
});
|
|
6486
|
+
} catch (error) {
|
|
6487
|
+
const reason = getErrorMessage2(error);
|
|
6488
|
+
import_logger4.logger.error("tg sendPhoto request failed: %s", reason);
|
|
6489
|
+
await sendTelegramMessage({
|
|
6490
|
+
message: `${message}
|
|
6491
|
+
|
|
6492
|
+
\u26A0\uFE0F <b>Photo delivery failed</b>
|
|
6493
|
+
Reason: <code>${escapeHtml(reason)}</code>`,
|
|
6494
|
+
markup
|
|
6098
6495
|
});
|
|
6099
6496
|
return;
|
|
6100
6497
|
}
|
|
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
6498
|
if (!data?.ok) {
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6499
|
+
const reason = getTelegramErrorReason(data);
|
|
6500
|
+
import_logger4.logger.error("tg sendPhoto failed: %s", JSON.stringify(data));
|
|
6501
|
+
await sendTelegramMessage({
|
|
6502
|
+
message: `${message}
|
|
6503
|
+
|
|
6504
|
+
\u26A0\uFE0F <b>Photo delivery failed</b>
|
|
6505
|
+
Reason: <code>${escapeHtml(reason)}</code>`,
|
|
6506
|
+
markup
|
|
6123
6507
|
});
|
|
6508
|
+
return;
|
|
6124
6509
|
}
|
|
6125
|
-
|
|
6510
|
+
import_logger4.logger.info("tg sendPhoto: sent");
|
|
6126
6511
|
};
|
|
6127
6512
|
var formatAnalysisMessage = (signal, analysis) => {
|
|
6128
6513
|
const lines = [];
|
|
@@ -6168,7 +6553,7 @@ ${escapeHtml(clean)}`);
|
|
|
6168
6553
|
};
|
|
6169
6554
|
var sendSignalAnalysis = async (signal, analysis) => {
|
|
6170
6555
|
const message = formatAnalysisMessage(signal, analysis);
|
|
6171
|
-
const
|
|
6556
|
+
const data = await requestTelegram("sendMessage", {
|
|
6172
6557
|
method: "POST",
|
|
6173
6558
|
headers: { "Content-Type": "application/json" },
|
|
6174
6559
|
body: JSON.stringify({
|
|
@@ -6177,10 +6562,9 @@ var sendSignalAnalysis = async (signal, analysis) => {
|
|
|
6177
6562
|
parse_mode: "HTML"
|
|
6178
6563
|
})
|
|
6179
6564
|
});
|
|
6180
|
-
|
|
6181
|
-
import_logger3.logger.info(
|
|
6565
|
+
import_logger4.logger.info(
|
|
6182
6566
|
"tg sendMessage (analysis): %s",
|
|
6183
|
-
data?.ok ? "sent" :
|
|
6567
|
+
data?.ok ? "sent" : getTelegramErrorReason(data)
|
|
6184
6568
|
);
|
|
6185
6569
|
};
|
|
6186
6570
|
|
|
@@ -6194,7 +6578,7 @@ var cleanFiles = async (dir) => {
|
|
|
6194
6578
|
total: files.length,
|
|
6195
6579
|
width: 30
|
|
6196
6580
|
});
|
|
6197
|
-
|
|
6581
|
+
import_logger5.logger.info(import_chalk.default.yellow("clean:", dir));
|
|
6198
6582
|
for await (const file of files) {
|
|
6199
6583
|
completed++;
|
|
6200
6584
|
const fullPath = `${dir}/${file}`;
|
|
@@ -6208,7 +6592,7 @@ var cleanFiles = async (dir) => {
|
|
|
6208
6592
|
bar.tick(completed === files.length ? completed % 100 : 100);
|
|
6209
6593
|
}
|
|
6210
6594
|
}
|
|
6211
|
-
|
|
6595
|
+
import_logger5.logger.info("");
|
|
6212
6596
|
};
|
|
6213
6597
|
var cleanRedis = async (area) => {
|
|
6214
6598
|
let completed = 0;
|
|
@@ -6217,7 +6601,7 @@ var cleanRedis = async (area) => {
|
|
|
6217
6601
|
total: keys.length,
|
|
6218
6602
|
width: 30
|
|
6219
6603
|
});
|
|
6220
|
-
|
|
6604
|
+
import_logger5.logger.info(import_chalk.default.yellow("clean:", area));
|
|
6221
6605
|
try {
|
|
6222
6606
|
for await (const key of keys) {
|
|
6223
6607
|
completed++;
|
|
@@ -6228,18 +6612,18 @@ var cleanRedis = async (area) => {
|
|
|
6228
6612
|
}
|
|
6229
6613
|
} catch (e) {
|
|
6230
6614
|
if (e instanceof import_redis3.RedisWriteBlockedError || String(e).includes("MISCONF")) {
|
|
6231
|
-
|
|
6615
|
+
import_logger5.logger.error(
|
|
6232
6616
|
"Redis write is blocked by MISCONF (RDB save failure). Cleanup stopped early."
|
|
6233
6617
|
);
|
|
6234
|
-
|
|
6618
|
+
import_logger5.logger.error(
|
|
6235
6619
|
"Check Redis logs and fix persistence/memory pressure before retry."
|
|
6236
6620
|
);
|
|
6237
|
-
|
|
6621
|
+
import_logger5.logger.info("");
|
|
6238
6622
|
return;
|
|
6239
6623
|
}
|
|
6240
6624
|
throw e;
|
|
6241
6625
|
}
|
|
6242
|
-
|
|
6626
|
+
import_logger5.logger.info("");
|
|
6243
6627
|
};
|
|
6244
6628
|
var update = async (connector, interval, tickers) => {
|
|
6245
6629
|
const PRELOAD_START = (0, import_time.getTimestamp)(import_constants.PRELOAD_DAYS);
|
|
@@ -6251,12 +6635,16 @@ var update = async (connector, interval, tickers) => {
|
|
|
6251
6635
|
width: 30
|
|
6252
6636
|
}
|
|
6253
6637
|
);
|
|
6254
|
-
|
|
6638
|
+
import_logger5.logger.info(
|
|
6639
|
+
import_chalk.default.yellow(
|
|
6640
|
+
`update: ${tickers.length} (klineConcurrency=${KLINE_CONCURRENCY_LIMIT})`
|
|
6641
|
+
)
|
|
6642
|
+
);
|
|
6255
6643
|
const queue = tickers.slice();
|
|
6256
6644
|
if (!queue.includes("BTCUSDT")) {
|
|
6257
6645
|
queue.unshift("BTCUSDT");
|
|
6258
6646
|
}
|
|
6259
|
-
await (0,
|
|
6647
|
+
await (0, import_async3.runWithConcurrency)(queue, KLINE_CONCURRENCY_LIMIT, async (symbol) => {
|
|
6260
6648
|
try {
|
|
6261
6649
|
await connector.kline({
|
|
6262
6650
|
symbol,
|
|
@@ -6266,12 +6654,12 @@ var update = async (connector, interval, tickers) => {
|
|
|
6266
6654
|
silent: true
|
|
6267
6655
|
});
|
|
6268
6656
|
} catch {
|
|
6269
|
-
|
|
6657
|
+
import_logger5.logger.error("Failed loading: %s", symbol);
|
|
6270
6658
|
} finally {
|
|
6271
6659
|
bar.tick(1, { symbol: import_chalk.default.gray(symbol) });
|
|
6272
6660
|
}
|
|
6273
6661
|
});
|
|
6274
|
-
|
|
6662
|
+
import_logger5.logger.info("");
|
|
6275
6663
|
};
|
|
6276
6664
|
var parseSymbolsFromCLI = (symbol = "") => symbol.split(",").map((s) => {
|
|
6277
6665
|
const ticker = s.toUpperCase();
|
|
@@ -6309,7 +6697,7 @@ var getTickers = async (connector, include = "", exclude = "", limit, chunk) =>
|
|
|
6309
6697
|
}
|
|
6310
6698
|
if (chunk) {
|
|
6311
6699
|
const [currentChunk, chunksCount] = chunk.split("/").map((c) => parseInt(c));
|
|
6312
|
-
|
|
6700
|
+
import_logger5.logger.info("chunk: %d / %d", currentChunk, chunksCount);
|
|
6313
6701
|
const chunkSize = Math.ceil(tickers.length / chunksCount);
|
|
6314
6702
|
const chunks = import_lodash.default.chunk(tickers, chunkSize);
|
|
6315
6703
|
tickers = chunks[currentChunk - 1];
|
|
@@ -6325,15 +6713,15 @@ var makeScreenshots = async (signals, interval) => {
|
|
|
6325
6713
|
width: 30
|
|
6326
6714
|
}
|
|
6327
6715
|
);
|
|
6328
|
-
|
|
6329
|
-
await (0,
|
|
6716
|
+
import_logger5.logger.info(import_chalk.default.yellow("screenshots:", `${interval}m`, signals.length));
|
|
6717
|
+
await (0, import_async3.runWithConcurrency)(
|
|
6330
6718
|
signals,
|
|
6331
6719
|
SCREENSHOT_CONCURRENCY_LIMIT,
|
|
6332
6720
|
async (signal) => {
|
|
6333
6721
|
try {
|
|
6334
6722
|
await screenDashboard({ ...signal, interval }, projectRoot);
|
|
6335
6723
|
} catch (error) {
|
|
6336
|
-
|
|
6724
|
+
import_logger5.logger.error(
|
|
6337
6725
|
"Failed screenshot: %s (%s)",
|
|
6338
6726
|
signal.symbol,
|
|
6339
6727
|
error?.message || String(error)
|
|
@@ -6343,7 +6731,7 @@ var makeScreenshots = async (signals, interval) => {
|
|
|
6343
6731
|
}
|
|
6344
6732
|
}
|
|
6345
6733
|
);
|
|
6346
|
-
|
|
6734
|
+
import_logger5.logger.info("");
|
|
6347
6735
|
};
|
|
6348
6736
|
var sendToAI = async (signals) => {
|
|
6349
6737
|
const bar = new import_progress.default(
|
|
@@ -6353,17 +6741,17 @@ var sendToAI = async (signals) => {
|
|
|
6353
6741
|
width: 30
|
|
6354
6742
|
}
|
|
6355
6743
|
);
|
|
6356
|
-
|
|
6357
|
-
await (0,
|
|
6744
|
+
import_logger5.logger.info(import_chalk.default.yellow("AI:", signals.length));
|
|
6745
|
+
await (0, import_async3.runWithConcurrency)(signals, AI_CONCURRENCY_LIMIT, async (signal) => {
|
|
6358
6746
|
try {
|
|
6359
6747
|
await askAI(signal);
|
|
6360
6748
|
} catch {
|
|
6361
|
-
|
|
6749
|
+
import_logger5.logger.error("Failed ask: %s", signal.symbol);
|
|
6362
6750
|
} finally {
|
|
6363
6751
|
bar.tick(1, { symbol: import_chalk.default.gray(signal.symbol) });
|
|
6364
6752
|
}
|
|
6365
6753
|
});
|
|
6366
|
-
|
|
6754
|
+
import_logger5.logger.info("");
|
|
6367
6755
|
};
|
|
6368
6756
|
var sendToTG = async (signals, imgInterval) => {
|
|
6369
6757
|
const bar = new import_progress.default(
|
|
@@ -6373,8 +6761,8 @@ var sendToTG = async (signals, imgInterval) => {
|
|
|
6373
6761
|
width: 30
|
|
6374
6762
|
}
|
|
6375
6763
|
);
|
|
6376
|
-
|
|
6377
|
-
await (0,
|
|
6764
|
+
import_logger5.logger.info(import_chalk.default.yellow("messages:", signals.length));
|
|
6765
|
+
await (0, import_async3.runWithConcurrency)(signals, TG_CONCURRENCY_LIMIT, async (signal) => {
|
|
6378
6766
|
try {
|
|
6379
6767
|
const analysis = await (0, import_redis3.getData)(
|
|
6380
6768
|
import_redis3.redisKeys.analysis(signal.symbol, signal.signalId),
|
|
@@ -6384,13 +6772,17 @@ var sendToTG = async (signals, imgInterval) => {
|
|
|
6384
6772
|
if (analysis && typeof analysis === "object" && Object.keys(analysis).length > 0) {
|
|
6385
6773
|
await sendSignalAnalysis(signal, analysis);
|
|
6386
6774
|
}
|
|
6387
|
-
} catch {
|
|
6388
|
-
|
|
6775
|
+
} catch (err) {
|
|
6776
|
+
import_logger5.logger.error(
|
|
6777
|
+
"Failed sent: %s %s",
|
|
6778
|
+
signal.symbol,
|
|
6779
|
+
err?.message || String(err)
|
|
6780
|
+
);
|
|
6389
6781
|
} finally {
|
|
6390
6782
|
bar.tick(1, { symbol: import_chalk.default.gray(signal.symbol) });
|
|
6391
6783
|
}
|
|
6392
6784
|
});
|
|
6393
|
-
|
|
6785
|
+
import_logger5.logger.info("");
|
|
6394
6786
|
};
|
|
6395
6787
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6396
6788
|
0 && (module.exports = {
|