@tradejs/node 1.0.4 → 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/chunk-SCMBUEGK.mjs +22 -0
- package/dist/cli.js +491 -105
- package/dist/cli.mjs +475 -98
- package/dist/constants.js +10 -1
- package/dist/constants.mjs +1 -1
- package/package.json +13 -5
- 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,55 +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
|
-
|
|
6123
|
-
parse_mode: "HTML"
|
|
6124
|
-
})
|
|
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
|
|
6125
6507
|
});
|
|
6508
|
+
return;
|
|
6126
6509
|
}
|
|
6127
|
-
|
|
6510
|
+
import_logger4.logger.info("tg sendPhoto: sent");
|
|
6128
6511
|
};
|
|
6129
6512
|
var formatAnalysisMessage = (signal, analysis) => {
|
|
6130
6513
|
const lines = [];
|
|
@@ -6170,7 +6553,7 @@ ${escapeHtml(clean)}`);
|
|
|
6170
6553
|
};
|
|
6171
6554
|
var sendSignalAnalysis = async (signal, analysis) => {
|
|
6172
6555
|
const message = formatAnalysisMessage(signal, analysis);
|
|
6173
|
-
const
|
|
6556
|
+
const data = await requestTelegram("sendMessage", {
|
|
6174
6557
|
method: "POST",
|
|
6175
6558
|
headers: { "Content-Type": "application/json" },
|
|
6176
6559
|
body: JSON.stringify({
|
|
@@ -6179,10 +6562,9 @@ var sendSignalAnalysis = async (signal, analysis) => {
|
|
|
6179
6562
|
parse_mode: "HTML"
|
|
6180
6563
|
})
|
|
6181
6564
|
});
|
|
6182
|
-
|
|
6183
|
-
import_logger3.logger.info(
|
|
6565
|
+
import_logger4.logger.info(
|
|
6184
6566
|
"tg sendMessage (analysis): %s",
|
|
6185
|
-
data?.ok ? "sent" :
|
|
6567
|
+
data?.ok ? "sent" : getTelegramErrorReason(data)
|
|
6186
6568
|
);
|
|
6187
6569
|
};
|
|
6188
6570
|
|
|
@@ -6196,7 +6578,7 @@ var cleanFiles = async (dir) => {
|
|
|
6196
6578
|
total: files.length,
|
|
6197
6579
|
width: 30
|
|
6198
6580
|
});
|
|
6199
|
-
|
|
6581
|
+
import_logger5.logger.info(import_chalk.default.yellow("clean:", dir));
|
|
6200
6582
|
for await (const file of files) {
|
|
6201
6583
|
completed++;
|
|
6202
6584
|
const fullPath = `${dir}/${file}`;
|
|
@@ -6210,7 +6592,7 @@ var cleanFiles = async (dir) => {
|
|
|
6210
6592
|
bar.tick(completed === files.length ? completed % 100 : 100);
|
|
6211
6593
|
}
|
|
6212
6594
|
}
|
|
6213
|
-
|
|
6595
|
+
import_logger5.logger.info("");
|
|
6214
6596
|
};
|
|
6215
6597
|
var cleanRedis = async (area) => {
|
|
6216
6598
|
let completed = 0;
|
|
@@ -6219,7 +6601,7 @@ var cleanRedis = async (area) => {
|
|
|
6219
6601
|
total: keys.length,
|
|
6220
6602
|
width: 30
|
|
6221
6603
|
});
|
|
6222
|
-
|
|
6604
|
+
import_logger5.logger.info(import_chalk.default.yellow("clean:", area));
|
|
6223
6605
|
try {
|
|
6224
6606
|
for await (const key of keys) {
|
|
6225
6607
|
completed++;
|
|
@@ -6230,18 +6612,18 @@ var cleanRedis = async (area) => {
|
|
|
6230
6612
|
}
|
|
6231
6613
|
} catch (e) {
|
|
6232
6614
|
if (e instanceof import_redis3.RedisWriteBlockedError || String(e).includes("MISCONF")) {
|
|
6233
|
-
|
|
6615
|
+
import_logger5.logger.error(
|
|
6234
6616
|
"Redis write is blocked by MISCONF (RDB save failure). Cleanup stopped early."
|
|
6235
6617
|
);
|
|
6236
|
-
|
|
6618
|
+
import_logger5.logger.error(
|
|
6237
6619
|
"Check Redis logs and fix persistence/memory pressure before retry."
|
|
6238
6620
|
);
|
|
6239
|
-
|
|
6621
|
+
import_logger5.logger.info("");
|
|
6240
6622
|
return;
|
|
6241
6623
|
}
|
|
6242
6624
|
throw e;
|
|
6243
6625
|
}
|
|
6244
|
-
|
|
6626
|
+
import_logger5.logger.info("");
|
|
6245
6627
|
};
|
|
6246
6628
|
var update = async (connector, interval, tickers) => {
|
|
6247
6629
|
const PRELOAD_START = (0, import_time.getTimestamp)(import_constants.PRELOAD_DAYS);
|
|
@@ -6253,12 +6635,16 @@ var update = async (connector, interval, tickers) => {
|
|
|
6253
6635
|
width: 30
|
|
6254
6636
|
}
|
|
6255
6637
|
);
|
|
6256
|
-
|
|
6638
|
+
import_logger5.logger.info(
|
|
6639
|
+
import_chalk.default.yellow(
|
|
6640
|
+
`update: ${tickers.length} (klineConcurrency=${KLINE_CONCURRENCY_LIMIT})`
|
|
6641
|
+
)
|
|
6642
|
+
);
|
|
6257
6643
|
const queue = tickers.slice();
|
|
6258
6644
|
if (!queue.includes("BTCUSDT")) {
|
|
6259
6645
|
queue.unshift("BTCUSDT");
|
|
6260
6646
|
}
|
|
6261
|
-
await (0,
|
|
6647
|
+
await (0, import_async3.runWithConcurrency)(queue, KLINE_CONCURRENCY_LIMIT, async (symbol) => {
|
|
6262
6648
|
try {
|
|
6263
6649
|
await connector.kline({
|
|
6264
6650
|
symbol,
|
|
@@ -6268,12 +6654,12 @@ var update = async (connector, interval, tickers) => {
|
|
|
6268
6654
|
silent: true
|
|
6269
6655
|
});
|
|
6270
6656
|
} catch {
|
|
6271
|
-
|
|
6657
|
+
import_logger5.logger.error("Failed loading: %s", symbol);
|
|
6272
6658
|
} finally {
|
|
6273
6659
|
bar.tick(1, { symbol: import_chalk.default.gray(symbol) });
|
|
6274
6660
|
}
|
|
6275
6661
|
});
|
|
6276
|
-
|
|
6662
|
+
import_logger5.logger.info("");
|
|
6277
6663
|
};
|
|
6278
6664
|
var parseSymbolsFromCLI = (symbol = "") => symbol.split(",").map((s) => {
|
|
6279
6665
|
const ticker = s.toUpperCase();
|
|
@@ -6311,7 +6697,7 @@ var getTickers = async (connector, include = "", exclude = "", limit, chunk) =>
|
|
|
6311
6697
|
}
|
|
6312
6698
|
if (chunk) {
|
|
6313
6699
|
const [currentChunk, chunksCount] = chunk.split("/").map((c) => parseInt(c));
|
|
6314
|
-
|
|
6700
|
+
import_logger5.logger.info("chunk: %d / %d", currentChunk, chunksCount);
|
|
6315
6701
|
const chunkSize = Math.ceil(tickers.length / chunksCount);
|
|
6316
6702
|
const chunks = import_lodash.default.chunk(tickers, chunkSize);
|
|
6317
6703
|
tickers = chunks[currentChunk - 1];
|
|
@@ -6327,15 +6713,15 @@ var makeScreenshots = async (signals, interval) => {
|
|
|
6327
6713
|
width: 30
|
|
6328
6714
|
}
|
|
6329
6715
|
);
|
|
6330
|
-
|
|
6331
|
-
await (0,
|
|
6716
|
+
import_logger5.logger.info(import_chalk.default.yellow("screenshots:", `${interval}m`, signals.length));
|
|
6717
|
+
await (0, import_async3.runWithConcurrency)(
|
|
6332
6718
|
signals,
|
|
6333
6719
|
SCREENSHOT_CONCURRENCY_LIMIT,
|
|
6334
6720
|
async (signal) => {
|
|
6335
6721
|
try {
|
|
6336
6722
|
await screenDashboard({ ...signal, interval }, projectRoot);
|
|
6337
6723
|
} catch (error) {
|
|
6338
|
-
|
|
6724
|
+
import_logger5.logger.error(
|
|
6339
6725
|
"Failed screenshot: %s (%s)",
|
|
6340
6726
|
signal.symbol,
|
|
6341
6727
|
error?.message || String(error)
|
|
@@ -6345,7 +6731,7 @@ var makeScreenshots = async (signals, interval) => {
|
|
|
6345
6731
|
}
|
|
6346
6732
|
}
|
|
6347
6733
|
);
|
|
6348
|
-
|
|
6734
|
+
import_logger5.logger.info("");
|
|
6349
6735
|
};
|
|
6350
6736
|
var sendToAI = async (signals) => {
|
|
6351
6737
|
const bar = new import_progress.default(
|
|
@@ -6355,17 +6741,17 @@ var sendToAI = async (signals) => {
|
|
|
6355
6741
|
width: 30
|
|
6356
6742
|
}
|
|
6357
6743
|
);
|
|
6358
|
-
|
|
6359
|
-
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) => {
|
|
6360
6746
|
try {
|
|
6361
6747
|
await askAI(signal);
|
|
6362
6748
|
} catch {
|
|
6363
|
-
|
|
6749
|
+
import_logger5.logger.error("Failed ask: %s", signal.symbol);
|
|
6364
6750
|
} finally {
|
|
6365
6751
|
bar.tick(1, { symbol: import_chalk.default.gray(signal.symbol) });
|
|
6366
6752
|
}
|
|
6367
6753
|
});
|
|
6368
|
-
|
|
6754
|
+
import_logger5.logger.info("");
|
|
6369
6755
|
};
|
|
6370
6756
|
var sendToTG = async (signals, imgInterval) => {
|
|
6371
6757
|
const bar = new import_progress.default(
|
|
@@ -6375,8 +6761,8 @@ var sendToTG = async (signals, imgInterval) => {
|
|
|
6375
6761
|
width: 30
|
|
6376
6762
|
}
|
|
6377
6763
|
);
|
|
6378
|
-
|
|
6379
|
-
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) => {
|
|
6380
6766
|
try {
|
|
6381
6767
|
const analysis = await (0, import_redis3.getData)(
|
|
6382
6768
|
import_redis3.redisKeys.analysis(signal.symbol, signal.signalId),
|
|
@@ -6387,7 +6773,7 @@ var sendToTG = async (signals, imgInterval) => {
|
|
|
6387
6773
|
await sendSignalAnalysis(signal, analysis);
|
|
6388
6774
|
}
|
|
6389
6775
|
} catch (err) {
|
|
6390
|
-
|
|
6776
|
+
import_logger5.logger.error(
|
|
6391
6777
|
"Failed sent: %s %s",
|
|
6392
6778
|
signal.symbol,
|
|
6393
6779
|
err?.message || String(err)
|
|
@@ -6396,7 +6782,7 @@ var sendToTG = async (signals, imgInterval) => {
|
|
|
6396
6782
|
bar.tick(1, { symbol: import_chalk.default.gray(signal.symbol) });
|
|
6397
6783
|
}
|
|
6398
6784
|
});
|
|
6399
|
-
|
|
6785
|
+
import_logger5.logger.info("");
|
|
6400
6786
|
};
|
|
6401
6787
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6402
6788
|
0 && (module.exports = {
|