@okx_ai/okx-trade-cli 1.3.3-beta.1 → 1.3.3-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +410 -280
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21,9 +21,12 @@ import {
|
|
|
21
21
|
import { homedir as homedir2 } from "os";
|
|
22
22
|
import { join as join2, dirname } from "path";
|
|
23
23
|
import { createHmac } from "crypto";
|
|
24
|
-
import { spawn, execFile as execFile2 } from "child_process";
|
|
24
|
+
import { spawn as spawn2, execFile as execFile2 } from "child_process";
|
|
25
25
|
import { homedir as homedir3 } from "os";
|
|
26
26
|
import { join as join3 } from "path";
|
|
27
|
+
import { spawn } from "child_process";
|
|
28
|
+
import { createServer } from "net";
|
|
29
|
+
import { randomBytes } from "crypto";
|
|
27
30
|
import fs from "fs";
|
|
28
31
|
import path from "path";
|
|
29
32
|
import os from "os";
|
|
@@ -880,32 +883,29 @@ import * as path3 from "path";
|
|
|
880
883
|
import * as os3 from "os";
|
|
881
884
|
import { execFileSync } from "child_process";
|
|
882
885
|
import {
|
|
883
|
-
createWriteStream as createWriteStream3,
|
|
884
886
|
mkdirSync as mkdirSync9,
|
|
885
887
|
chmodSync as chmodSync2,
|
|
886
888
|
existsSync as existsSync7,
|
|
887
|
-
unlinkSync as
|
|
889
|
+
unlinkSync as unlinkSync5,
|
|
888
890
|
renameSync as renameSync4
|
|
889
891
|
} from "fs";
|
|
890
892
|
import { homedir as homedir9, platform as platform2 } from "os";
|
|
891
893
|
import { join as join11, dirname as dirname7 } from "path";
|
|
892
|
-
import {
|
|
893
|
-
import { get as
|
|
894
|
+
import { createWriteStream as createWriteStream2, unlinkSync as unlinkSync3 } from "fs";
|
|
895
|
+
import { get as httpsGet } from "https";
|
|
896
|
+
import { get as httpGet } from "http";
|
|
894
897
|
import {
|
|
895
898
|
readFileSync as readFileSync7,
|
|
896
|
-
createWriteStream as createWriteStream2,
|
|
897
899
|
mkdirSync as mkdirSync8,
|
|
898
900
|
chmodSync,
|
|
899
901
|
existsSync as existsSync6,
|
|
900
|
-
unlinkSync as
|
|
902
|
+
unlinkSync as unlinkSync4,
|
|
901
903
|
renameSync as renameSync3
|
|
902
904
|
} from "fs";
|
|
903
905
|
import { createHash } from "crypto";
|
|
904
906
|
import { homedir as homedir8, platform, arch } from "os";
|
|
905
907
|
import { join as join10, dirname as dirname6 } from "path";
|
|
906
|
-
import {
|
|
907
|
-
import { get as httpGet } from "http";
|
|
908
|
-
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10, unlinkSync as unlinkSync5, existsSync as existsSync8 } from "fs";
|
|
908
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10, unlinkSync as unlinkSync6, existsSync as existsSync8 } from "fs";
|
|
909
909
|
import { join as join12 } from "path";
|
|
910
910
|
import { homedir as homedir10 } from "os";
|
|
911
911
|
var EXEC_TIMEOUT_MS = 3e4;
|
|
@@ -1282,6 +1282,105 @@ var EXIT_CODES = {
|
|
|
1282
1282
|
NOT_LOGGED_IN: 2,
|
|
1283
1283
|
REFRESH_FAILED: 3
|
|
1284
1284
|
};
|
|
1285
|
+
function finalizeToken(code, token, resolve3, reject) {
|
|
1286
|
+
if (code === EXIT_CODES.SUCCESS) {
|
|
1287
|
+
if (!token) {
|
|
1288
|
+
reject(new AuthenticationError(
|
|
1289
|
+
"okx-auth returned empty token.",
|
|
1290
|
+
"Run `okx auth login` to re-authenticate."
|
|
1291
|
+
));
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
resolve3(token);
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
if (code === EXIT_CODES.NOT_LOGGED_IN) {
|
|
1298
|
+
reject(new NotLoggedInError());
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
if (code === EXIT_CODES.UNAUTHORIZED_CALLER) {
|
|
1302
|
+
reject(new AuthenticationError(
|
|
1303
|
+
"okx-auth rejected the caller (unauthorized).",
|
|
1304
|
+
"Ensure you are running from a trusted OKX tool."
|
|
1305
|
+
));
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
if (code === EXIT_CODES.REFRESH_FAILED) {
|
|
1309
|
+
reject(new AuthenticationError(
|
|
1310
|
+
"Token refresh failed.",
|
|
1311
|
+
"Run `okx auth login` to re-authenticate."
|
|
1312
|
+
));
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
reject(new AuthenticationError(
|
|
1316
|
+
`okx-auth token exited with code ${code}.`,
|
|
1317
|
+
"Run `okx auth login` to re-authenticate."
|
|
1318
|
+
));
|
|
1319
|
+
}
|
|
1320
|
+
function spawnFailedError(err) {
|
|
1321
|
+
return new ConfigError(
|
|
1322
|
+
`Failed to spawn okx-auth: ${err.message}`,
|
|
1323
|
+
"Ensure the okx-auth binary exists and is executable."
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
var WIN_PIPE_PREFIX = String.raw`\\.\pipe\okx-auth-`;
|
|
1327
|
+
function defaultWindowsPipeName() {
|
|
1328
|
+
return WIN_PIPE_PREFIX + randomBytes(32).toString("hex");
|
|
1329
|
+
}
|
|
1330
|
+
function execAuthTokenWindows(binPath, makePipeName = defaultWindowsPipeName) {
|
|
1331
|
+
return new Promise((resolve3, reject) => {
|
|
1332
|
+
const pipeName = makePipeName();
|
|
1333
|
+
const server = createServer();
|
|
1334
|
+
const chunks = [];
|
|
1335
|
+
let connectionMade = false;
|
|
1336
|
+
let pipeClosed = false;
|
|
1337
|
+
let exitCode;
|
|
1338
|
+
let settled = false;
|
|
1339
|
+
const settle = (fn) => {
|
|
1340
|
+
if (settled) return;
|
|
1341
|
+
settled = true;
|
|
1342
|
+
try {
|
|
1343
|
+
server.close();
|
|
1344
|
+
} catch {
|
|
1345
|
+
}
|
|
1346
|
+
fn();
|
|
1347
|
+
};
|
|
1348
|
+
const tryFinalize = () => {
|
|
1349
|
+
if (!pipeClosed || exitCode === void 0) return;
|
|
1350
|
+
const token = Buffer.concat(chunks).toString("utf-8").trim();
|
|
1351
|
+
settle(() => finalizeToken(exitCode, token, resolve3, reject));
|
|
1352
|
+
};
|
|
1353
|
+
server.on("connection", (socket) => {
|
|
1354
|
+
connectionMade = true;
|
|
1355
|
+
socket.on("data", (c) => chunks.push(c));
|
|
1356
|
+
socket.on("end", () => {
|
|
1357
|
+
pipeClosed = true;
|
|
1358
|
+
tryFinalize();
|
|
1359
|
+
});
|
|
1360
|
+
socket.on("error", (err) => settle(() => reject(spawnFailedError(err))));
|
|
1361
|
+
});
|
|
1362
|
+
server.on("error", (err) => settle(() => reject(spawnFailedError(err))));
|
|
1363
|
+
server.listen(pipeName, () => {
|
|
1364
|
+
let child;
|
|
1365
|
+
try {
|
|
1366
|
+
child = spawn(binPath, ["token"], {
|
|
1367
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
1368
|
+
env: { ...process.env, OKX_AUTH_TOKEN_PIPE: pipeName },
|
|
1369
|
+
windowsHide: true
|
|
1370
|
+
});
|
|
1371
|
+
} catch (err) {
|
|
1372
|
+
settle(() => reject(spawnFailedError(err)));
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
child.on("error", (err) => settle(() => reject(spawnFailedError(err))));
|
|
1376
|
+
child.on("close", (code) => {
|
|
1377
|
+
exitCode = code;
|
|
1378
|
+
if (!connectionMade) pipeClosed = true;
|
|
1379
|
+
tryFinalize();
|
|
1380
|
+
});
|
|
1381
|
+
});
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1285
1384
|
var EXEC_TIMEOUT_MS2 = 5e3;
|
|
1286
1385
|
var AUTH_BIN_DIR = join3(homedir3(), ".okx", "bin");
|
|
1287
1386
|
function getAuthBinaryPath() {
|
|
@@ -1293,8 +1392,11 @@ function getAuthBinaryPath() {
|
|
|
1293
1392
|
}
|
|
1294
1393
|
function execAuthToken() {
|
|
1295
1394
|
const binPath = getAuthBinaryPath();
|
|
1395
|
+
return process.platform === "win32" ? execAuthTokenWindows(binPath) : execAuthTokenUnix(binPath);
|
|
1396
|
+
}
|
|
1397
|
+
function execAuthTokenUnix(binPath) {
|
|
1296
1398
|
return new Promise((resolve3, reject) => {
|
|
1297
|
-
const child =
|
|
1399
|
+
const child = spawn2(binPath, ["token"], {
|
|
1298
1400
|
stdio: ["ignore", "ignore", "inherit", "pipe"]
|
|
1299
1401
|
// stdin stdout stderr fd3 (pipe)
|
|
1300
1402
|
});
|
|
@@ -1302,46 +1404,11 @@ function execAuthToken() {
|
|
|
1302
1404
|
const fd3 = child.stdio[3];
|
|
1303
1405
|
fd3.on("data", (chunk) => chunks.push(chunk));
|
|
1304
1406
|
child.on("error", (err) => {
|
|
1305
|
-
reject(
|
|
1306
|
-
`Failed to spawn okx-auth: ${err.message}`,
|
|
1307
|
-
"Ensure the okx-auth binary exists and is executable."
|
|
1308
|
-
));
|
|
1407
|
+
reject(spawnFailedError(err));
|
|
1309
1408
|
});
|
|
1310
1409
|
child.on("close", (code) => {
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
if (!token) {
|
|
1314
|
-
reject(new AuthenticationError(
|
|
1315
|
-
"okx-auth returned empty token.",
|
|
1316
|
-
"Run `okx auth login` to re-authenticate."
|
|
1317
|
-
));
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
resolve3(token);
|
|
1321
|
-
return;
|
|
1322
|
-
}
|
|
1323
|
-
if (code === EXIT_CODES.NOT_LOGGED_IN) {
|
|
1324
|
-
reject(new NotLoggedInError());
|
|
1325
|
-
return;
|
|
1326
|
-
}
|
|
1327
|
-
if (code === EXIT_CODES.UNAUTHORIZED_CALLER) {
|
|
1328
|
-
reject(new AuthenticationError(
|
|
1329
|
-
"okx-auth rejected the caller (unauthorized).",
|
|
1330
|
-
"Ensure you are running from a trusted OKX tool."
|
|
1331
|
-
));
|
|
1332
|
-
return;
|
|
1333
|
-
}
|
|
1334
|
-
if (code === EXIT_CODES.REFRESH_FAILED) {
|
|
1335
|
-
reject(new AuthenticationError(
|
|
1336
|
-
"Token refresh failed.",
|
|
1337
|
-
"Run `okx auth login` to re-authenticate."
|
|
1338
|
-
));
|
|
1339
|
-
return;
|
|
1340
|
-
}
|
|
1341
|
-
reject(new AuthenticationError(
|
|
1342
|
-
`okx-auth token exited with code ${code}.`,
|
|
1343
|
-
"Run `okx auth login` to re-authenticate."
|
|
1344
|
-
));
|
|
1410
|
+
const token = Buffer.concat(chunks).toString("utf-8").trim();
|
|
1411
|
+
finalizeToken(code, token, resolve3, reject);
|
|
1345
1412
|
});
|
|
1346
1413
|
});
|
|
1347
1414
|
}
|
|
@@ -1634,7 +1701,7 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1634
1701
|
headers.set("OK-ACCESS-TIMESTAMP", timestamp);
|
|
1635
1702
|
}
|
|
1636
1703
|
throwOkxError(code, msg, reqConfig, traceId) {
|
|
1637
|
-
const message = msg
|
|
1704
|
+
const message = msg && msg.trim() !== "" ? msg : `OKX API rejected request (code ${code}).`;
|
|
1638
1705
|
const endpoint = `${reqConfig.method} ${reqConfig.path}`;
|
|
1639
1706
|
if (code === "50111" || code === "50112" || code === "50113") {
|
|
1640
1707
|
throw new AuthenticationError(
|
|
@@ -6999,31 +7066,31 @@ var SIGNAL_POOL_FILTER_PROPS = {
|
|
|
6999
7066
|
type: "string",
|
|
7000
7067
|
enum: PERIOD_DAYS,
|
|
7001
7068
|
default: "7",
|
|
7002
|
-
description: 'Lookback window in days.
|
|
7069
|
+
description: 'Lookback window in days. One of `"3"` / `"7"` / `"30"` / `"90"`. Drives capability metrics (avgLongWinRate / avgShortWinRate) and the `winRateTier` filter. Does NOT affect signal fields (which always use the latest snapshot).'
|
|
7003
7070
|
},
|
|
7004
7071
|
pnlTier: {
|
|
7005
7072
|
type: "string",
|
|
7006
7073
|
enum: ["PNL_ANY", "PNL_TOP50", "PNL_TOP20", "PNL_TOP5"],
|
|
7007
7074
|
default: "PNL_ANY",
|
|
7008
|
-
description: "PnL percentile gate applied on top of `sortBy`.
|
|
7075
|
+
description: "PnL percentile gate applied on top of `sortBy`. Naming: `TOP{N}` = top N% percentile (NOT an absolute PnL value). PNL_ANY = no filter; PNL_TOP50 = PnL \u2265 P50 (median); PNL_TOP20 = \u2265 P80; PNL_TOP5 = \u2265 P95. PnL distribution is long-tailed \u2014 use percentile, not absolute thresholds."
|
|
7009
7076
|
},
|
|
7010
7077
|
winRateTier: {
|
|
7011
7078
|
type: "string",
|
|
7012
7079
|
enum: ["WR_ANY", "WR_GE_50", "WR_GE_80"],
|
|
7013
7080
|
default: "WR_ANY",
|
|
7014
|
-
description: "Minimum win-rate gate (
|
|
7081
|
+
description: "Minimum win-rate gate (absolute thresholds, NOT percentile). Naming: `GE_{N}` = career win-rate \u2265 N% \u2014 distinct from `pnlTier`/`aumTier` `TOP{N}` which are percentiles. WR_ANY = no filter; WR_GE_50 = \u2265 50%; WR_GE_80 = \u2265 80%."
|
|
7015
7082
|
},
|
|
7016
7083
|
maxDrawdownTier: {
|
|
7017
7084
|
type: "string",
|
|
7018
7085
|
enum: ["MR_ANY", "MR_LE_20", "MR_LE_50"],
|
|
7019
7086
|
default: "MR_ANY",
|
|
7020
|
-
description: "Maximum-drawdown gate (
|
|
7087
|
+
description: "Maximum-drawdown gate (absolute thresholds, NOT percentile; smaller drawdown = lower risk). Naming: `LE_{N}` = drawdown \u2264 N% \u2014 distinct from `pnlTier`/`aumTier` `TOP{N}` which are percentiles. MR_ANY = no filter; MR_LE_20 = drawdown \u2264 20%; MR_LE_50 = \u2264 50%."
|
|
7021
7088
|
},
|
|
7022
7089
|
aumTier: {
|
|
7023
7090
|
type: "string",
|
|
7024
7091
|
enum: ["AUM_ANY", "AUM_TOP50", "AUM_TOP20", "AUM_TOP5"],
|
|
7025
7092
|
default: "AUM_ANY",
|
|
7026
|
-
description: "AUM (Assets Under Management) percentile gate.
|
|
7093
|
+
description: "AUM (Assets Under Management) percentile gate. Naming: `TOP{N}` = top N% percentile (NOT an absolute USD amount). AUM_ANY = no filter; AUM_TOP50 = AUM \u2265 P50; AUM_TOP20 = \u2265 P80; AUM_TOP5 = \u2265 P95. AUM is long-tailed \u2014 use percentile, not absolute USD."
|
|
7027
7094
|
}
|
|
7028
7095
|
};
|
|
7029
7096
|
var LEADERBOARD_POOL_FILTER_PROPS = {
|
|
@@ -7031,29 +7098,29 @@ var LEADERBOARD_POOL_FILTER_PROPS = {
|
|
|
7031
7098
|
type: "string",
|
|
7032
7099
|
enum: ["pnl", "pnlRatio"],
|
|
7033
7100
|
default: "pnl",
|
|
7034
|
-
description:
|
|
7101
|
+
description: 'Required. Leaderboard sort key. `pnl` = absolute USD profit; `pnlRatio` = percentage return. Default `"pnl"`.'
|
|
7035
7102
|
},
|
|
7036
7103
|
period: {
|
|
7037
7104
|
type: "string",
|
|
7038
7105
|
enum: PERIOD_DAYS,
|
|
7039
7106
|
default: "90",
|
|
7040
|
-
description: 'Performance lookback window in days.
|
|
7107
|
+
description: 'Required. Performance lookback window in days. One of `"3"` / `"7"` / `"30"` / `"90"`. Default `"90"` (matches leaderboard UI). Filters AND ranks traders by their PnL over that window.'
|
|
7041
7108
|
},
|
|
7042
7109
|
minPnl: {
|
|
7043
7110
|
type: "string",
|
|
7044
|
-
description: 'Minimum absolute PnL in USD
|
|
7111
|
+
description: 'Minimum absolute PnL in USD as a string, e.g. `"10000"` \u2192 traders with PnL \u2265 $10,000. Numeric threshold \u2014 distinct from the signal-side `pnlTier` percentile enum.'
|
|
7045
7112
|
},
|
|
7046
7113
|
minWinRate: {
|
|
7047
7114
|
type: "string",
|
|
7048
|
-
description: 'Minimum win-rate as decimal in 0~1 range
|
|
7115
|
+
description: 'Minimum win-rate as a decimal in 0~1 range, passed as a string, e.g. `"0.8"` \u2192 traders with win-rate \u2265 80%. Numeric threshold \u2014 distinct from the signal-side `winRateTier` enum.'
|
|
7049
7116
|
},
|
|
7050
7117
|
maxDrawdown: {
|
|
7051
7118
|
type: "string",
|
|
7052
|
-
description: 'Maximum drawdown as decimal
|
|
7119
|
+
description: 'Maximum drawdown as a decimal, passed as a string, e.g. `"0.1"` \u2192 traders with drawdown \u2264 10%. Lower = lower risk. Numeric threshold \u2014 distinct from the signal-side `maxDrawdownTier` enum.'
|
|
7053
7120
|
},
|
|
7054
7121
|
minAum: {
|
|
7055
7122
|
type: "string",
|
|
7056
|
-
description: 'Minimum AUM (Assets Under Management) in USD
|
|
7123
|
+
description: 'Minimum AUM (Assets Under Management) in USD as a string, e.g. `"1000"` \u2192 traders with AUM \u2265 $1,000. Numeric threshold \u2014 distinct from the signal-side `aumTier` percentile enum.'
|
|
7057
7124
|
}
|
|
7058
7125
|
};
|
|
7059
7126
|
var LEADERBOARD_FILTER_UPSTREAM_NAMES = {
|
|
@@ -7065,19 +7132,25 @@ var LEADERBOARD_FILTER_UPSTREAM_NAMES = {
|
|
|
7065
7132
|
minAum: "asset"
|
|
7066
7133
|
};
|
|
7067
7134
|
function readPoolFilters(args) {
|
|
7135
|
+
assertPoolFilterEnums(args, LEADERBOARD_POOL_FILTER_PROPS);
|
|
7068
7136
|
const result = {};
|
|
7069
7137
|
for (const [publicKey, upstreamKey] of Object.entries(LEADERBOARD_FILTER_UPSTREAM_NAMES)) {
|
|
7070
7138
|
const val = readString(args, publicKey);
|
|
7071
7139
|
if (val !== void 0 && val !== "") result[upstreamKey] = val;
|
|
7072
7140
|
}
|
|
7141
|
+
if (result.sortBy === void 0) result.sortBy = "pnl";
|
|
7142
|
+
if (result.period === void 0) result.period = "90";
|
|
7073
7143
|
return result;
|
|
7074
7144
|
}
|
|
7075
7145
|
function readSignalPoolFilters(args) {
|
|
7146
|
+
assertPoolFilterEnums(args, SIGNAL_POOL_FILTER_PROPS);
|
|
7076
7147
|
const result = {};
|
|
7077
7148
|
for (const key of Object.keys(SIGNAL_POOL_FILTER_PROPS)) {
|
|
7078
7149
|
const val = readString(args, key);
|
|
7079
7150
|
if (val) result[key] = val;
|
|
7080
7151
|
}
|
|
7152
|
+
if (result.sortBy === void 0) result.sortBy = "pnl";
|
|
7153
|
+
if (result.period === void 0) result.period = "7";
|
|
7081
7154
|
return result;
|
|
7082
7155
|
}
|
|
7083
7156
|
function extractLeaderboardEnvelope(data) {
|
|
@@ -7093,7 +7166,7 @@ function extractLeaderboardEnvelope(data) {
|
|
|
7093
7166
|
function deriveDirection(posSide, pos) {
|
|
7094
7167
|
if (posSide === "long") return "long";
|
|
7095
7168
|
if (posSide === "short") return "short";
|
|
7096
|
-
if (posSide === "both" && typeof pos === "string" && pos !== "") {
|
|
7169
|
+
if ((posSide === "net" || posSide === "both") && typeof pos === "string" && pos !== "") {
|
|
7097
7170
|
const n = Number(pos);
|
|
7098
7171
|
if (Number.isFinite(n) && n !== 0) return n > 0 ? "long" : "short";
|
|
7099
7172
|
}
|
|
@@ -7130,6 +7203,22 @@ function extractBaseCcy(instId) {
|
|
|
7130
7203
|
function actionableError(message, hint) {
|
|
7131
7204
|
return new ValidationError(`${message} ${hint}`);
|
|
7132
7205
|
}
|
|
7206
|
+
function assertEnum2(args, key, allowed) {
|
|
7207
|
+
const val = readString(args, key);
|
|
7208
|
+
if (val === void 0 || val === "") return;
|
|
7209
|
+
if (!allowed.includes(val)) {
|
|
7210
|
+
throw actionableError(
|
|
7211
|
+
`Invalid value for "${key}": ${JSON.stringify(val)}.`,
|
|
7212
|
+
`Allowed values: ${allowed.map((v) => JSON.stringify(v)).join(", ")}.`
|
|
7213
|
+
);
|
|
7214
|
+
}
|
|
7215
|
+
}
|
|
7216
|
+
function assertPoolFilterEnums(args, props) {
|
|
7217
|
+
for (const [key, spec] of Object.entries(props)) {
|
|
7218
|
+
const enumList = spec.enum;
|
|
7219
|
+
if (Array.isArray(enumList)) assertEnum2(args, key, enumList);
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
7133
7222
|
function readArrayAsCsv(args, key) {
|
|
7134
7223
|
const arr = readStringArray(args, key);
|
|
7135
7224
|
if (!arr || arr.length === 0) return void 0;
|
|
@@ -7206,21 +7295,33 @@ var SIGNAL_ITEM_PROPS = {
|
|
|
7206
7295
|
type: "object",
|
|
7207
7296
|
description: "Notional / capital-flow group.",
|
|
7208
7297
|
properties: {
|
|
7209
|
-
longNotionalUsdt: {
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7298
|
+
longNotionalUsdt: {
|
|
7299
|
+
type: "string",
|
|
7300
|
+
description: "Sum of long-side notional in USDT, weighted by each trader's ENTRY PRICE (price_avg), not mark price. Moves only when positions are opened / closed / scaled \u2014 stays constant when positions are unchanged."
|
|
7301
|
+
},
|
|
7302
|
+
shortNotionalUsdt: {
|
|
7303
|
+
type: "string",
|
|
7304
|
+
description: "Sum of short-side notional in USDT, weighted by each trader's ENTRY PRICE (price_avg), not mark price. Moves only when positions are opened / closed / scaled \u2014 stays constant when positions are unchanged."
|
|
7305
|
+
},
|
|
7306
|
+
netNotionalUsdt: {
|
|
7307
|
+
type: "string",
|
|
7308
|
+
description: "Net directional notional in USDT = long \u2212 short. Weighted by each trader's ENTRY PRICE (price_avg), not mark price \u2014 reflects position scaling, not underlying price movement."
|
|
7309
|
+
},
|
|
7310
|
+
totalNotionalUsdt: {
|
|
7311
|
+
type: "string",
|
|
7312
|
+
description: "Gross notional in USDT = long + short. Weighted by each trader's ENTRY PRICE (price_avg), not mark price \u2014 reflects position scaling (open / close / add), not underlying price movement. Stays constant across buckets when traders hold positions unchanged."
|
|
7313
|
+
},
|
|
7213
7314
|
totalNotionalVs24h: {
|
|
7214
7315
|
type: "string",
|
|
7215
7316
|
description: "Capital-flow change ratio vs 24h: (curr \u2212 hist_24h) / hist_24h. Positive = smart money adding exposure; negative = retreating. NULL when hist=0."
|
|
7216
7317
|
},
|
|
7217
7318
|
smartMoneyLongAvgEntry: {
|
|
7218
7319
|
type: "string",
|
|
7219
|
-
description:
|
|
7320
|
+
description: 'Long-side notional-weighted average entry price (USDT). Empty string `""` when no long. Compare to current price to judge whether following the longs is cheap/expensive now.'
|
|
7220
7321
|
},
|
|
7221
7322
|
smartMoneyShortAvgEntry: {
|
|
7222
7323
|
type: "string",
|
|
7223
|
-
description:
|
|
7324
|
+
description: 'Short-side notional-weighted average entry price (USDT). Empty string `""` when no short.'
|
|
7224
7325
|
}
|
|
7225
7326
|
}
|
|
7226
7327
|
},
|
|
@@ -7232,14 +7333,17 @@ var SIGNAL_ITEM_PROPS = {
|
|
|
7232
7333
|
longRatioVs24h: { type: "string", description: "longRatio \u2212 hist_24h.longRatio. NULL when no hist." },
|
|
7233
7334
|
longRatioVs7d: { type: "string", description: "longRatio \u2212 hist_7d.longRatio. NULL when no hist." },
|
|
7234
7335
|
longRatio: { type: "string", description: "Headcount long ratio = longTraders / tradersWithPosition. NULL when no traders." },
|
|
7235
|
-
shortRatio: {
|
|
7336
|
+
shortRatio: {
|
|
7337
|
+
type: "string",
|
|
7338
|
+
description: "Headcount short ratio = shortTraders / tradersWithPosition. NULL when no traders."
|
|
7339
|
+
},
|
|
7236
7340
|
weightedLongRatio: {
|
|
7237
7341
|
type: "string",
|
|
7238
|
-
description: "Notional-weighted long ratio = \u03A3(long_notional) / \u03A3(notional). NULL when no notional."
|
|
7342
|
+
description: "Notional-weighted long ratio = \u03A3(long_notional) / \u03A3(notional). Notional uses each trader's ENTRY PRICE (price_avg), not mark price \u2014 ratio shifts only when positions are scaled. NULL when no notional."
|
|
7239
7343
|
},
|
|
7240
7344
|
weightedShortRatio: {
|
|
7241
7345
|
type: "string",
|
|
7242
|
-
description: "Notional-weighted short ratio = \u03A3(short_notional) / \u03A3(notional). NULL when no notional."
|
|
7346
|
+
description: "Notional-weighted short ratio = \u03A3(short_notional) / \u03A3(notional). Notional uses each trader's ENTRY PRICE (price_avg), not mark price. NULL when no notional."
|
|
7243
7347
|
}
|
|
7244
7348
|
}
|
|
7245
7349
|
},
|
|
@@ -7266,15 +7370,15 @@ var SIGNAL_HISTORY_ITEM_PROPS = {
|
|
|
7266
7370
|
},
|
|
7267
7371
|
shortRatio: {
|
|
7268
7372
|
type: "string",
|
|
7269
|
-
description: "Headcount short ratio at this bucket =
|
|
7373
|
+
description: "Headcount short ratio at this bucket = shortTraders / tradersWithPosition. Decimal 0~1."
|
|
7270
7374
|
},
|
|
7271
7375
|
weightedLongRatio: {
|
|
7272
7376
|
type: "string",
|
|
7273
|
-
description: "Notional-weighted long ratio at this bucket. Decimal 0~1."
|
|
7377
|
+
description: "Notional-weighted long ratio at this bucket = \u03A3(long_notional) / \u03A3(notional). Decimal 0~1. Notional uses each trader's ENTRY PRICE (price_avg), not mark price \u2014 ratio shifts only when positions are scaled."
|
|
7274
7378
|
},
|
|
7275
7379
|
weightedShortRatio: {
|
|
7276
7380
|
type: "string",
|
|
7277
|
-
description: "Notional-weighted short ratio at this bucket. Decimal 0~1."
|
|
7381
|
+
description: "Notional-weighted short ratio at this bucket = \u03A3(short_notional) / \u03A3(notional). Decimal 0~1. Notional uses each trader's ENTRY PRICE (price_avg), not mark price."
|
|
7278
7382
|
},
|
|
7279
7383
|
longTraders: {
|
|
7280
7384
|
type: "integer",
|
|
@@ -7290,11 +7394,11 @@ var SIGNAL_HISTORY_ITEM_PROPS = {
|
|
|
7290
7394
|
},
|
|
7291
7395
|
netNotionalUsdt: {
|
|
7292
7396
|
type: "string",
|
|
7293
|
-
description: "Net directional notional in USDT at this bucket = long notional \u2212 short notional."
|
|
7397
|
+
description: "Net directional notional in USDT at this bucket = long notional \u2212 short notional. Weighted by each trader's ENTRY PRICE (price_avg), not mark price \u2014 reflects position scaling, not underlying price movement."
|
|
7294
7398
|
},
|
|
7295
7399
|
totalNotionalUsdt: {
|
|
7296
7400
|
type: "string",
|
|
7297
|
-
description: "Gross notional in USDT at this bucket = long notional + short notional.
|
|
7401
|
+
description: "Gross notional in USDT at this bucket = long notional + short notional. Weighted by each trader's ENTRY PRICE (price_avg), not mark price \u2014 tracks capital deployed (rising = adding, falling = retreating). Stays constant across buckets when traders hold positions unchanged."
|
|
7298
7402
|
},
|
|
7299
7403
|
tradersQualified: { type: "integer", description: "Pool size after applying tier filters (includes traders without a position)." },
|
|
7300
7404
|
dataVersion: { type: "string", description: "Snapshot version key in `yyyyMMddHH` UTC (10-digit, e.g. `2026042820`)." }
|
|
@@ -7325,16 +7429,16 @@ function registerSmartmoneyTools() {
|
|
|
7325
7429
|
properties: {
|
|
7326
7430
|
updateTime: {
|
|
7327
7431
|
type: "string",
|
|
7328
|
-
description: 'Snapshot version key
|
|
7432
|
+
description: 'Snapshot version key \u2014 12-digit `yyyyMMddHHmm` (UTC+8) as a string, e.g. `"202604301815"`. Omit to query the latest snapshot (refreshed every ~5 min).'
|
|
7329
7433
|
},
|
|
7330
7434
|
...LEADERBOARD_POOL_FILTER_PROPS,
|
|
7331
7435
|
after: {
|
|
7332
7436
|
type: "string",
|
|
7333
|
-
description:
|
|
7437
|
+
description: 'Pagination cursor (older page) \u2014 pass the `authorId` of the last item from the previous page as a string, e.g. `"872913470357110787"`. Cursor anchors on `authorId` while preserving the current `sortBy` order.'
|
|
7334
7438
|
},
|
|
7335
7439
|
before: {
|
|
7336
7440
|
type: "string",
|
|
7337
|
-
description:
|
|
7441
|
+
description: 'Pagination cursor (newer page) \u2014 pass the `authorId` of the first item from the previous page as a string, e.g. `"872913470357110787"`. Cursor anchors on `authorId` while preserving the current `sortBy` order.'
|
|
7338
7442
|
},
|
|
7339
7443
|
limit: {
|
|
7340
7444
|
type: "integer",
|
|
@@ -7343,7 +7447,8 @@ function registerSmartmoneyTools() {
|
|
|
7343
7447
|
default: 10,
|
|
7344
7448
|
description: "Max results per page (default 10, max 100)."
|
|
7345
7449
|
}
|
|
7346
|
-
}
|
|
7450
|
+
},
|
|
7451
|
+
required: ["sortBy", "period"]
|
|
7347
7452
|
},
|
|
7348
7453
|
handler: async (rawArgs, context) => {
|
|
7349
7454
|
const args = asRecord(rawArgs);
|
|
@@ -7393,17 +7498,25 @@ function registerSmartmoneyTools() {
|
|
|
7393
7498
|
minItems: 1,
|
|
7394
7499
|
description: 'Trader IDs to look up, e.g. `["1001", "1002"]`. Required.'
|
|
7395
7500
|
},
|
|
7501
|
+
sortBy: {
|
|
7502
|
+
type: "string",
|
|
7503
|
+
enum: ["pnl", "pnlRatio"],
|
|
7504
|
+
default: "pnl",
|
|
7505
|
+
description: 'Required. Result sort key. `pnl` = absolute USD profit; `pnlRatio` = percentage return. Default `"pnl"`.'
|
|
7506
|
+
},
|
|
7396
7507
|
period: {
|
|
7397
7508
|
type: "string",
|
|
7398
7509
|
enum: PERIOD_DAYS,
|
|
7399
7510
|
default: "90",
|
|
7400
|
-
description: 'Performance lookback window in days.
|
|
7511
|
+
description: 'Required. Performance lookback window in days. One of `"3"` / `"7"` / `"30"` / `"90"`. Default `"90"`.'
|
|
7401
7512
|
}
|
|
7402
7513
|
},
|
|
7403
|
-
required: ["authorIds"]
|
|
7514
|
+
required: ["authorIds", "sortBy", "period"]
|
|
7404
7515
|
},
|
|
7405
7516
|
handler: async (rawArgs, context) => {
|
|
7406
7517
|
const args = asRecord(rawArgs);
|
|
7518
|
+
assertEnum2(args, "sortBy", ["pnl", "pnlRatio"]);
|
|
7519
|
+
assertEnum2(args, "period", PERIOD_DAYS);
|
|
7407
7520
|
const authorIds = readArrayAsCsv(args, "authorIds");
|
|
7408
7521
|
if (!authorIds) {
|
|
7409
7522
|
throw actionableError(
|
|
@@ -7415,7 +7528,10 @@ function registerSmartmoneyTools() {
|
|
|
7415
7528
|
PATH_LEADERBOARD,
|
|
7416
7529
|
compactObject({
|
|
7417
7530
|
authorIds,
|
|
7418
|
-
|
|
7531
|
+
// Apply schema defaults explicitly so behavior does not depend on backend defaults
|
|
7532
|
+
// (CLI bypasses MCP `required` validation, so handler is the only deterministic layer).
|
|
7533
|
+
sortBy: readString(args, "sortBy") ?? "pnl",
|
|
7534
|
+
period: readString(args, "period") ?? "90"
|
|
7419
7535
|
}),
|
|
7420
7536
|
publicRateLimit("smartmoney_get_performance_by_trader", SMARTMONEY_RPS)
|
|
7421
7537
|
);
|
|
@@ -7447,12 +7563,12 @@ function registerSmartmoneyTools() {
|
|
|
7447
7563
|
},
|
|
7448
7564
|
posSide: {
|
|
7449
7565
|
type: "string",
|
|
7450
|
-
description: "Raw upstream position direction. `long` = long-side position (buy-to-open); `short` = short-side position (sell-to-open); `both` = net/one-way position mode where the sign of `pos` encodes direction. Prefer the derived `direction` field below for agent logic."
|
|
7566
|
+
description: "Raw upstream position direction. `long` = long-side position (buy-to-open); `short` = short-side position (sell-to-open); `net` (or legacy `both`) = net/one-way position mode where the sign of `pos` encodes direction. Prefer the derived `direction` field below for agent logic."
|
|
7451
7567
|
},
|
|
7452
7568
|
direction: {
|
|
7453
7569
|
type: "string",
|
|
7454
7570
|
enum: ["long", "short"],
|
|
7455
|
-
description: 'Derived clean direction (`long` | `short`) \u2014 handler computes this from `posSide` + sign of `pos` so agents do not have to branch on the `posSide="
|
|
7571
|
+
description: 'Derived clean direction (`long` | `short`) \u2014 handler computes this from `posSide` + sign of `pos` so agents do not have to branch on the `posSide="net"` net-mode case.'
|
|
7456
7572
|
},
|
|
7457
7573
|
posCcy: { type: "string", description: 'Position currency \u2014 the asset being held, e.g. "BTC".' },
|
|
7458
7574
|
quoteCcy: { type: "string", description: 'Quote currency the position is priced/settled in, e.g. "USDT".' },
|
|
@@ -7600,11 +7716,11 @@ function registerSmartmoneyTools() {
|
|
|
7600
7716
|
},
|
|
7601
7717
|
after: {
|
|
7602
7718
|
type: "string",
|
|
7603
|
-
description:
|
|
7719
|
+
description: 'Pagination cursor (older) \u2014 returns positions with `posId` smaller than this value. Pass the `posId` as a string, e.g. `"872913470357110787"`.'
|
|
7604
7720
|
},
|
|
7605
7721
|
before: {
|
|
7606
7722
|
type: "string",
|
|
7607
|
-
description:
|
|
7723
|
+
description: 'Pagination cursor (newer) \u2014 returns positions with `posId` greater than this value. Pass the `posId` as a string, e.g. `"872913470357110787"`.'
|
|
7608
7724
|
},
|
|
7609
7725
|
limit: {
|
|
7610
7726
|
type: "integer",
|
|
@@ -7718,11 +7834,11 @@ function registerSmartmoneyTools() {
|
|
|
7718
7834
|
},
|
|
7719
7835
|
after: {
|
|
7720
7836
|
type: "string",
|
|
7721
|
-
description:
|
|
7837
|
+
description: 'Pagination cursor (older) \u2014 returns trades with `ordId` smaller than this value. Pass the `ordId` as a string, e.g. `"872913470357110787"`.'
|
|
7722
7838
|
},
|
|
7723
7839
|
before: {
|
|
7724
7840
|
type: "string",
|
|
7725
|
-
description:
|
|
7841
|
+
description: 'Pagination cursor (newer) \u2014 returns trades with `ordId` greater than this value. Pass the `ordId` as a string, e.g. `"872913470357110787"`.'
|
|
7726
7842
|
},
|
|
7727
7843
|
limit: {
|
|
7728
7844
|
type: "integer",
|
|
@@ -7818,7 +7934,7 @@ function registerSmartmoneyTools() {
|
|
|
7818
7934
|
{
|
|
7819
7935
|
name: "smartmoney_get_signal_overview_by_filter",
|
|
7820
7936
|
module: "smartmoney",
|
|
7821
|
-
description: "Multi-asset smart-money consensus signals (long/short ratio, weighted entry, capital flow, deltas vs 1h/24h/7d), aggregated over a tier-filtered trader pool (PnL / win-rate / drawdown / AUM). Pick instruments via `topInstruments` OR `instCcyList` \u2014 exactly one. Snapshot time auto-resolved to current hour. Use when: latest cross-asset consensus from a criteria-defined pool. See also: `smartmoney_get_signal_overview_by_trader` (restrict pool to specific traders), `smartmoney_get_signal_trend_by_filter` (time-series instead of latest snapshot).",
|
|
7937
|
+
description: "Multi-asset smart-money consensus signals (long/short ratio, weighted entry, capital flow, deltas vs 1h/24h/7d), aggregated over a tier-filtered trader pool (PnL / win-rate / drawdown / AUM). Pick instruments via `topInstruments` OR `instCcyList` \u2014 exactly one. Snapshot time auto-resolved to current hour. **Linear (USDT/USDS-margined) contracts only \u2014 coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions are excluded by upstream and silently omitted from the aggregation.** Use when: latest cross-asset consensus from a criteria-defined pool. See also: `smartmoney_get_signal_overview_by_trader` (restrict pool to specific traders), `smartmoney_get_signal_trend_by_filter` (time-series instead of latest snapshot).",
|
|
7822
7938
|
isWrite: false,
|
|
7823
7939
|
outputSchema: envelope({
|
|
7824
7940
|
type: "array",
|
|
@@ -7839,7 +7955,7 @@ function registerSmartmoneyTools() {
|
|
|
7839
7955
|
type: "array",
|
|
7840
7956
|
items: { type: "string" },
|
|
7841
7957
|
minItems: 1,
|
|
7842
|
-
description: 'Base currencies to aggregate, e.g. `["BTC", "ETH", "SOL"]`. Mutually exclusive with `topInstruments`.'
|
|
7958
|
+
description: 'Base currencies to aggregate, e.g. `["BTC", "ETH", "SOL"]`. Mutually exclusive with `topInstruments`. Scope: only USDT-margined and USDS-margined (linear) instruments \u2014 e.g. `BTC` covers `BTC-USDT-SWAP` + `BTC-USDS-SWAP`. Coin-margined contracts (`BTC-USD-SWAP`, `BTC-USD-DELIVERY`) are NOT included; positions a trader holds in those instruments are silently dropped from the aggregation.'
|
|
7843
7959
|
},
|
|
7844
7960
|
...SIGNAL_POOL_FILTER_PROPS,
|
|
7845
7961
|
lmtNum: {
|
|
@@ -7849,7 +7965,8 @@ function registerSmartmoneyTools() {
|
|
|
7849
7965
|
default: 100,
|
|
7850
7966
|
description: "Top-N traders to pull into the aggregation pool, ranked by `sortBy` (DESC). Larger pool = stronger signal but slower. Default 100 is fine for most cases."
|
|
7851
7967
|
}
|
|
7852
|
-
}
|
|
7968
|
+
},
|
|
7969
|
+
required: ["sortBy", "period"]
|
|
7853
7970
|
},
|
|
7854
7971
|
handler: async (rawArgs, context) => {
|
|
7855
7972
|
const args = asRecord(rawArgs);
|
|
@@ -7877,7 +7994,7 @@ function registerSmartmoneyTools() {
|
|
|
7877
7994
|
{
|
|
7878
7995
|
name: "smartmoney_get_signal_overview_by_trader",
|
|
7879
7996
|
module: "smartmoney",
|
|
7880
|
-
description: "Multi-asset smart-money signals aggregated over a hand-picked set of traders (`authorIds`). Pick instruments via `topInstruments` OR `instCcyList`.
|
|
7997
|
+
description: "Multi-asset smart-money signals aggregated over a hand-picked set of traders (`authorIds`). Pick instruments via `topInstruments` OR `instCcyList`. Capability tier filters (pnlTier / winRateTier / etc.) not exposed \u2014 backend uses defaults for direct-lookup scenarios. **Linear (USDT/USDS-margined) contracts only \u2014 a trader's coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions are silently excluded from the aggregation, even when those positions are large.** Use `smartmoney_get_trader_positions` if the full position book is needed. Use when: caller already knows which traders to follow and wants their cross-asset consensus at the latest hour. See also: `smartmoney_get_signal_overview_by_filter` (criteria-defined pool), `smartmoney_get_signal_trend_by_trader` (time-series), `smartmoney_get_traders_by_filter` / `smartmoney_search_trader` (discover authorIds).",
|
|
7881
7998
|
isWrite: false,
|
|
7882
7999
|
outputSchema: envelope({
|
|
7883
8000
|
type: "array",
|
|
@@ -7904,13 +8021,27 @@ function registerSmartmoneyTools() {
|
|
|
7904
8021
|
type: "array",
|
|
7905
8022
|
items: { type: "string" },
|
|
7906
8023
|
minItems: 1,
|
|
7907
|
-
description: 'Base currencies to aggregate, e.g. `["BTC", "ETH", "SOL"]`. Mutually exclusive with `topInstruments`.'
|
|
8024
|
+
description: 'Base currencies to aggregate, e.g. `["BTC", "ETH", "SOL"]`. Mutually exclusive with `topInstruments`. Scope: only USDT-margined and USDS-margined (linear) instruments \u2014 e.g. `BTC` covers `BTC-USDT-SWAP` + `BTC-USDS-SWAP`. Coin-margined contracts (`BTC-USD-SWAP`, `BTC-USD-DELIVERY`) are NOT included; the trader\'s positions in those instruments are silently dropped.'
|
|
8025
|
+
},
|
|
8026
|
+
sortBy: {
|
|
8027
|
+
type: "string",
|
|
8028
|
+
enum: ["pnl", "pnlRatio"],
|
|
8029
|
+
default: "pnl",
|
|
8030
|
+
description: 'Required. Ranking key for the trader set. `pnl` = absolute USD profit; `pnlRatio` = percentage return. Default `"pnl"`.'
|
|
8031
|
+
},
|
|
8032
|
+
period: {
|
|
8033
|
+
type: "string",
|
|
8034
|
+
enum: PERIOD_DAYS,
|
|
8035
|
+
default: "7",
|
|
8036
|
+
description: 'Required. Lookback window in days for capability metrics (`winRate.avgLongWinRate` / `avgShortWinRate`). One of `"3"` / `"7"` / `"30"` / `"90"`. Default `"7"`. Does NOT affect signal fields.'
|
|
7908
8037
|
}
|
|
7909
8038
|
},
|
|
7910
|
-
required: ["authorIds"]
|
|
8039
|
+
required: ["authorIds", "sortBy", "period"]
|
|
7911
8040
|
},
|
|
7912
8041
|
handler: async (rawArgs, context) => {
|
|
7913
8042
|
const args = asRecord(rawArgs);
|
|
8043
|
+
assertEnum2(args, "sortBy", ["pnl", "pnlRatio"]);
|
|
8044
|
+
assertEnum2(args, "period", PERIOD_DAYS);
|
|
7914
8045
|
const authorIds = readArrayAsCsv(args, "authorIds");
|
|
7915
8046
|
if (!authorIds) {
|
|
7916
8047
|
throw actionableError(
|
|
@@ -7930,7 +8061,11 @@ function registerSmartmoneyTools() {
|
|
|
7930
8061
|
PATH_OVERVIEW,
|
|
7931
8062
|
compactObject({
|
|
7932
8063
|
authorIds,
|
|
7933
|
-
...instCcyList ? { instCcyList } : { topInstruments: topInstrumentsRaw ?? 20 }
|
|
8064
|
+
...instCcyList ? { instCcyList } : { topInstruments: topInstrumentsRaw ?? 20 },
|
|
8065
|
+
// Apply schema defaults explicitly so behavior does not depend on backend defaults
|
|
8066
|
+
// (CLI bypasses MCP `required` validation, so handler is the only deterministic layer).
|
|
8067
|
+
sortBy: readString(args, "sortBy") ?? "pnl",
|
|
8068
|
+
period: readString(args, "period") ?? "7"
|
|
7934
8069
|
}),
|
|
7935
8070
|
publicRateLimit("smartmoney_get_signal_overview_by_trader", SMARTMONEY_RPS)
|
|
7936
8071
|
);
|
|
@@ -7941,7 +8076,7 @@ function registerSmartmoneyTools() {
|
|
|
7941
8076
|
{
|
|
7942
8077
|
name: "smartmoney_get_signal_trend_by_filter",
|
|
7943
8078
|
module: "smartmoney",
|
|
7944
|
-
description: "Time-series of single-asset smart-money signal across hourly/daily buckets, aggregated over a tier-filtered trader pool. Returns the latest `limit` buckets ending at `asOfTime` (defaults to current UTC hour). Use when: tracking how long/short conviction and capital evolve over time (smart money adding exposure or retreating). See also: `smartmoney_get_signal_overview_by_filter` (latest snapshot only), `smartmoney_get_signal_trend_by_trader` (restrict to specific traders). Note: `asOfTime` is 10-digit `yyyyMMddHH` UTC, different from leaderboard tools' 12-digit UTC+8 `updateTime` \u2014 do not cross-pass.",
|
|
8079
|
+
description: "Time-series of single-asset smart-money signal across hourly/daily buckets, aggregated over a tier-filtered trader pool. Returns the latest `limit` buckets ending at `asOfTime` (defaults to current UTC hour). **Linear (USDT/USDS-margined) contracts only \u2014 coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions are excluded by upstream and silently omitted.** Use when: tracking how long/short conviction and capital evolve over time (smart money adding exposure or retreating). See also: `smartmoney_get_signal_overview_by_filter` (latest snapshot only), `smartmoney_get_signal_trend_by_trader` (restrict to specific traders). Note: `asOfTime` is 10-digit `yyyyMMddHH` UTC, different from leaderboard tools' 12-digit UTC+8 `updateTime` \u2014 do not cross-pass.",
|
|
7945
8080
|
isWrite: false,
|
|
7946
8081
|
outputSchema: envelope({
|
|
7947
8082
|
type: "array",
|
|
@@ -7953,11 +8088,11 @@ function registerSmartmoneyTools() {
|
|
|
7953
8088
|
properties: {
|
|
7954
8089
|
instCcy: {
|
|
7955
8090
|
type: "string",
|
|
7956
|
-
description: 'Base currency to scope the time-series, e.g. "BTC". Required.'
|
|
8091
|
+
description: 'Base currency to scope the time-series, e.g. "BTC". Required. Scope: USDT-margined and USDS-margined (linear) instruments only \u2014 coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions are NOT included.'
|
|
7957
8092
|
},
|
|
7958
8093
|
asOfTime: {
|
|
7959
8094
|
type: "string",
|
|
7960
|
-
description: 'Anchor snapshot time
|
|
8095
|
+
description: 'Anchor snapshot time \u2014 10-digit `yyyyMMddHH` UTC as a string, e.g. `"2026050100"`. Returns the latest `limit` buckets ending at this anchor. Omit to use the current UTC hour.'
|
|
7961
8096
|
},
|
|
7962
8097
|
granularity: {
|
|
7963
8098
|
type: "string",
|
|
@@ -7981,10 +8116,11 @@ function registerSmartmoneyTools() {
|
|
|
7981
8116
|
description: "Top-N traders to pull into the aggregation pool, ranked by `sortBy` (DESC). Default 100, max 2000."
|
|
7982
8117
|
}
|
|
7983
8118
|
},
|
|
7984
|
-
required: ["instCcy"]
|
|
8119
|
+
required: ["instCcy", "granularity", "sortBy", "period"]
|
|
7985
8120
|
},
|
|
7986
8121
|
handler: async (rawArgs, context) => {
|
|
7987
8122
|
const args = asRecord(rawArgs);
|
|
8123
|
+
assertEnum2(args, "granularity", ["1h", "1d"]);
|
|
7988
8124
|
const instCcy = readString(args, "instCcy");
|
|
7989
8125
|
if (!instCcy) {
|
|
7990
8126
|
throw actionableError(
|
|
@@ -7997,7 +8133,8 @@ function registerSmartmoneyTools() {
|
|
|
7997
8133
|
compactObject({
|
|
7998
8134
|
instCcy,
|
|
7999
8135
|
asOfTime: readString(args, "asOfTime"),
|
|
8000
|
-
|
|
8136
|
+
// Apply schema default explicitly (CLI bypasses MCP `required` validation).
|
|
8137
|
+
granularity: readString(args, "granularity") ?? "1h",
|
|
8001
8138
|
limit: readNumber(args, "limit"),
|
|
8002
8139
|
...readSignalPoolFilters(args),
|
|
8003
8140
|
lmtNum: readNumber(args, "lmtNum")
|
|
@@ -8011,7 +8148,7 @@ function registerSmartmoneyTools() {
|
|
|
8011
8148
|
{
|
|
8012
8149
|
name: "smartmoney_get_signal_trend_by_trader",
|
|
8013
8150
|
module: "smartmoney",
|
|
8014
|
-
description: "Time-series of single-asset smart-money signal aggregated over a hand-picked set of traders (`authorIds`). Returns the latest `limit` buckets ending at `asOfTime` (defaults to current UTC hour).
|
|
8151
|
+
description: "Time-series of single-asset smart-money signal aggregated over a hand-picked set of traders (`authorIds`). Returns the latest `limit` buckets ending at `asOfTime` (defaults to current UTC hour). Capability tier filters (pnlTier / winRateTier / etc.) not exposed \u2014 backend uses defaults for direct-lookup scenarios. **Linear (USDT/USDS-margined) contracts only \u2014 a trader's coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions on the requested base ccy are silently excluded from each bucket.** Use `smartmoney_get_trader_positions` to inspect the full position book. Use when: tracking how a specific group of traders has evolved their long/short consensus over time on one coin. See also: `smartmoney_get_signal_trend_by_filter` (criteria-defined pool), `smartmoney_get_signal_overview_by_trader` (latest snapshot only), `smartmoney_get_traders_by_filter` / `smartmoney_search_trader` (discover authorIds). Note: `asOfTime` is 10-digit `yyyyMMddHH` UTC, different from leaderboard tools' 12-digit UTC+8 `updateTime` \u2014 do not cross-pass.",
|
|
8015
8152
|
isWrite: false,
|
|
8016
8153
|
outputSchema: envelope({
|
|
8017
8154
|
type: "array",
|
|
@@ -8029,11 +8166,11 @@ function registerSmartmoneyTools() {
|
|
|
8029
8166
|
},
|
|
8030
8167
|
instCcy: {
|
|
8031
8168
|
type: "string",
|
|
8032
|
-
description: 'Base currency to scope the time-series, e.g. "BTC". Required.'
|
|
8169
|
+
description: 'Base currency to scope the time-series, e.g. "BTC". Required. Scope: USDT-margined and USDS-margined (linear) instruments only \u2014 coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions held by the trader set are NOT included.'
|
|
8033
8170
|
},
|
|
8034
8171
|
asOfTime: {
|
|
8035
8172
|
type: "string",
|
|
8036
|
-
description: 'Anchor snapshot time
|
|
8173
|
+
description: 'Anchor snapshot time \u2014 10-digit `yyyyMMddHH` UTC as a string, e.g. `"2026050100"`. Returns the latest `limit` buckets ending at this anchor. Omit to use the current UTC hour.'
|
|
8037
8174
|
},
|
|
8038
8175
|
granularity: {
|
|
8039
8176
|
type: "string",
|
|
@@ -8047,12 +8184,27 @@ function registerSmartmoneyTools() {
|
|
|
8047
8184
|
maximum: 500,
|
|
8048
8185
|
default: 24,
|
|
8049
8186
|
description: "Number of buckets to return (newest first), ending at `asOfTime`. Default 24, max 500."
|
|
8187
|
+
},
|
|
8188
|
+
sortBy: {
|
|
8189
|
+
type: "string",
|
|
8190
|
+
enum: ["pnl", "pnlRatio"],
|
|
8191
|
+
default: "pnl",
|
|
8192
|
+
description: 'Required. Ranking key for the trader set. `pnl` = absolute USD profit; `pnlRatio` = percentage return. Default `"pnl"`.'
|
|
8193
|
+
},
|
|
8194
|
+
period: {
|
|
8195
|
+
type: "string",
|
|
8196
|
+
enum: PERIOD_DAYS,
|
|
8197
|
+
default: "7",
|
|
8198
|
+
description: 'Required. Lookback window in days. One of `"3"` / `"7"` / `"30"` / `"90"`. Default `"7"`. Does NOT affect signal fields (which always use the latest snapshot).'
|
|
8050
8199
|
}
|
|
8051
8200
|
},
|
|
8052
|
-
required: ["authorIds", "instCcy"]
|
|
8201
|
+
required: ["authorIds", "instCcy", "granularity", "sortBy", "period"]
|
|
8053
8202
|
},
|
|
8054
8203
|
handler: async (rawArgs, context) => {
|
|
8055
8204
|
const args = asRecord(rawArgs);
|
|
8205
|
+
assertEnum2(args, "granularity", ["1h", "1d"]);
|
|
8206
|
+
assertEnum2(args, "sortBy", ["pnl", "pnlRatio"]);
|
|
8207
|
+
assertEnum2(args, "period", PERIOD_DAYS);
|
|
8056
8208
|
const authorIds = readArrayAsCsv(args, "authorIds");
|
|
8057
8209
|
const instCcy = readString(args, "instCcy");
|
|
8058
8210
|
if (!authorIds) {
|
|
@@ -8073,8 +8225,12 @@ function registerSmartmoneyTools() {
|
|
|
8073
8225
|
authorIds,
|
|
8074
8226
|
instCcy,
|
|
8075
8227
|
asOfTime: readString(args, "asOfTime"),
|
|
8076
|
-
|
|
8077
|
-
|
|
8228
|
+
// Apply schema defaults explicitly so behavior does not depend on backend defaults
|
|
8229
|
+
// (CLI bypasses MCP `required` validation, so handler is the only deterministic layer).
|
|
8230
|
+
granularity: readString(args, "granularity") ?? "1h",
|
|
8231
|
+
limit: readNumber(args, "limit"),
|
|
8232
|
+
sortBy: readString(args, "sortBy") ?? "pnl",
|
|
8233
|
+
period: readString(args, "period") ?? "7"
|
|
8078
8234
|
}),
|
|
8079
8235
|
publicRateLimit("smartmoney_get_signal_trend_by_trader", SMARTMONEY_RPS)
|
|
8080
8236
|
);
|
|
@@ -11744,12 +11900,47 @@ function sanitize(value) {
|
|
|
11744
11900
|
}
|
|
11745
11901
|
return value;
|
|
11746
11902
|
}
|
|
11903
|
+
var PRUNE_MARKER = ".last-prune.json";
|
|
11904
|
+
var PRUNE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
11905
|
+
var DEFAULT_RETENTION_DAYS = 30;
|
|
11906
|
+
var MS_PER_DAY = 864e5;
|
|
11907
|
+
var LOG_FILE_PATTERN = /^trade-.+\.log$/;
|
|
11908
|
+
function pruneOldLogs(logDir, retentionDays, now) {
|
|
11909
|
+
if (retentionDays === 0) return;
|
|
11910
|
+
try {
|
|
11911
|
+
const markerPath = path2.join(logDir, PRUNE_MARKER);
|
|
11912
|
+
let checkedAt = 0;
|
|
11913
|
+
try {
|
|
11914
|
+
const raw = fs2.readFileSync(markerPath, "utf8");
|
|
11915
|
+
const data = JSON.parse(raw);
|
|
11916
|
+
if (typeof data.checkedAt === "number") {
|
|
11917
|
+
checkedAt = data.checkedAt;
|
|
11918
|
+
}
|
|
11919
|
+
} catch {
|
|
11920
|
+
}
|
|
11921
|
+
if (checkedAt > 0 && now - checkedAt <= PRUNE_INTERVAL_MS) return;
|
|
11922
|
+
const cutoff = now - retentionDays * MS_PER_DAY;
|
|
11923
|
+
for (const name of fs2.readdirSync(logDir).filter((f) => LOG_FILE_PATTERN.test(f))) {
|
|
11924
|
+
try {
|
|
11925
|
+
if (fs2.statSync(path2.join(logDir, name)).mtimeMs < cutoff) {
|
|
11926
|
+
fs2.unlinkSync(path2.join(logDir, name));
|
|
11927
|
+
}
|
|
11928
|
+
} catch {
|
|
11929
|
+
}
|
|
11930
|
+
}
|
|
11931
|
+
fs2.writeFileSync(markerPath, JSON.stringify({ checkedAt: now }), "utf8");
|
|
11932
|
+
} catch {
|
|
11933
|
+
}
|
|
11934
|
+
}
|
|
11747
11935
|
var TradeLogger = class {
|
|
11748
11936
|
logLevel;
|
|
11749
11937
|
logDir;
|
|
11750
11938
|
constructor(logLevel = "info", logDir) {
|
|
11751
11939
|
this.logLevel = logLevel;
|
|
11752
11940
|
this.logDir = logDir ?? path2.join(os2.homedir(), ".okx", "logs");
|
|
11941
|
+
const parsed = parseInt(process.env.OKX_LOG_RETENTION_DAYS ?? "", 10);
|
|
11942
|
+
const retentionDays = isNaN(parsed) || parsed < 0 ? DEFAULT_RETENTION_DAYS : parsed;
|
|
11943
|
+
pruneOldLogs(this.logDir, retentionDays, Date.now());
|
|
11753
11944
|
}
|
|
11754
11945
|
getLogPath(date) {
|
|
11755
11946
|
const d = date ?? /* @__PURE__ */ new Date();
|
|
@@ -11925,6 +12116,78 @@ function runSetup(options) {
|
|
|
11925
12116
|
`);
|
|
11926
12117
|
}
|
|
11927
12118
|
}
|
|
12119
|
+
function isRedirect(statusCode) {
|
|
12120
|
+
return statusCode !== void 0 && statusCode >= 300 && statusCode < 400;
|
|
12121
|
+
}
|
|
12122
|
+
function validateRedirect(res, requestUrl, redirectCount, maxRedirects) {
|
|
12123
|
+
if (redirectCount > maxRedirects) {
|
|
12124
|
+
throw new Error(`Too many redirects (${maxRedirects})`);
|
|
12125
|
+
}
|
|
12126
|
+
const location = res.headers.location;
|
|
12127
|
+
if (requestUrl.startsWith("https") && !location.startsWith("https")) {
|
|
12128
|
+
throw new Error("Refused HTTPS \u2192 HTTP redirect downgrade");
|
|
12129
|
+
}
|
|
12130
|
+
return location;
|
|
12131
|
+
}
|
|
12132
|
+
function fetchResponse(url, timeoutMs) {
|
|
12133
|
+
return new Promise((resolve3, reject) => {
|
|
12134
|
+
let redirects = 0;
|
|
12135
|
+
const maxRedirects = 5;
|
|
12136
|
+
function doRequest(requestUrl) {
|
|
12137
|
+
const reqFn = requestUrl.startsWith("https") ? httpsGet : httpGet;
|
|
12138
|
+
const req = reqFn(requestUrl, { timeout: timeoutMs }, (res) => {
|
|
12139
|
+
if (isRedirect(res.statusCode) && res.headers.location) {
|
|
12140
|
+
redirects++;
|
|
12141
|
+
try {
|
|
12142
|
+
const location = validateRedirect(res, requestUrl, redirects, maxRedirects);
|
|
12143
|
+
res.resume();
|
|
12144
|
+
doRequest(location);
|
|
12145
|
+
} catch (err) {
|
|
12146
|
+
reject(err);
|
|
12147
|
+
}
|
|
12148
|
+
return;
|
|
12149
|
+
}
|
|
12150
|
+
if (res.statusCode !== 200) {
|
|
12151
|
+
reject(new Error(`HTTP ${res.statusCode ?? "unknown"}`));
|
|
12152
|
+
return;
|
|
12153
|
+
}
|
|
12154
|
+
resolve3(res);
|
|
12155
|
+
});
|
|
12156
|
+
req.on("error", reject);
|
|
12157
|
+
req.on("timeout", () => {
|
|
12158
|
+
req.destroy();
|
|
12159
|
+
reject(new Error("Download timed out"));
|
|
12160
|
+
});
|
|
12161
|
+
}
|
|
12162
|
+
doRequest(url);
|
|
12163
|
+
});
|
|
12164
|
+
}
|
|
12165
|
+
function download(url, destPath, timeoutMs) {
|
|
12166
|
+
return fetchResponse(url, timeoutMs).then(
|
|
12167
|
+
(res) => new Promise((resolve3, reject) => {
|
|
12168
|
+
const file = createWriteStream2(destPath);
|
|
12169
|
+
res.pipe(file);
|
|
12170
|
+
file.on("finish", () => file.close(() => resolve3()));
|
|
12171
|
+
file.on("error", (err) => {
|
|
12172
|
+
try {
|
|
12173
|
+
unlinkSync3(destPath);
|
|
12174
|
+
} catch {
|
|
12175
|
+
}
|
|
12176
|
+
reject(err);
|
|
12177
|
+
});
|
|
12178
|
+
})
|
|
12179
|
+
);
|
|
12180
|
+
}
|
|
12181
|
+
function downloadText(url, timeoutMs) {
|
|
12182
|
+
return fetchResponse(url, timeoutMs).then(
|
|
12183
|
+
(res) => new Promise((resolve3, reject) => {
|
|
12184
|
+
const chunks = [];
|
|
12185
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
12186
|
+
res.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
|
|
12187
|
+
res.on("error", reject);
|
|
12188
|
+
})
|
|
12189
|
+
);
|
|
12190
|
+
}
|
|
11928
12191
|
var CDN_SOURCES = [
|
|
11929
12192
|
{ host: "static.jingyunyilian.com", protocol: "https" },
|
|
11930
12193
|
{ host: "static.okx.com", protocol: "https" },
|
|
@@ -12038,7 +12301,7 @@ async function downloadAndVerify(host, protocol, binaryPath, tmpPath, checksum,
|
|
|
12038
12301
|
function atomicReplace(tmpPath, resolvedDest) {
|
|
12039
12302
|
if (platform() === "win32") {
|
|
12040
12303
|
try {
|
|
12041
|
-
|
|
12304
|
+
unlinkSync4(resolvedDest);
|
|
12042
12305
|
} catch {
|
|
12043
12306
|
}
|
|
12044
12307
|
}
|
|
@@ -12094,7 +12357,7 @@ async function installPilotBinary(destPath, sources = CDN_SOURCES, onProgress) {
|
|
|
12094
12357
|
return { status: "installed", source: host };
|
|
12095
12358
|
} catch (err) {
|
|
12096
12359
|
try {
|
|
12097
|
-
|
|
12360
|
+
unlinkSync4(tmpPath);
|
|
12098
12361
|
} catch {
|
|
12099
12362
|
}
|
|
12100
12363
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -12108,7 +12371,7 @@ ${errors.join("\n")}` };
|
|
|
12108
12371
|
function removePilotBinary(binaryPath) {
|
|
12109
12372
|
const resolvedPath = binaryPath ?? getPilotBinaryPath();
|
|
12110
12373
|
try {
|
|
12111
|
-
|
|
12374
|
+
unlinkSync4(resolvedPath);
|
|
12112
12375
|
return { status: "removed" };
|
|
12113
12376
|
} catch (err) {
|
|
12114
12377
|
if (err.code === "ENOENT") {
|
|
@@ -12118,78 +12381,6 @@ function removePilotBinary(binaryPath) {
|
|
|
12118
12381
|
throw new Error(`Failed to remove ${resolvedPath}: ${msg}`);
|
|
12119
12382
|
}
|
|
12120
12383
|
}
|
|
12121
|
-
function isRedirect(statusCode) {
|
|
12122
|
-
return statusCode !== void 0 && statusCode >= 300 && statusCode < 400;
|
|
12123
|
-
}
|
|
12124
|
-
function validateRedirect(res, requestUrl, redirectCount, maxRedirects) {
|
|
12125
|
-
if (redirectCount > maxRedirects) {
|
|
12126
|
-
throw new Error(`Too many redirects (${maxRedirects})`);
|
|
12127
|
-
}
|
|
12128
|
-
const location = res.headers.location;
|
|
12129
|
-
if (requestUrl.startsWith("https") && !location.startsWith("https")) {
|
|
12130
|
-
throw new Error("Refused HTTPS \u2192 HTTP redirect downgrade");
|
|
12131
|
-
}
|
|
12132
|
-
return location;
|
|
12133
|
-
}
|
|
12134
|
-
function fetchResponse(url, timeoutMs) {
|
|
12135
|
-
return new Promise((resolve3, reject) => {
|
|
12136
|
-
let redirects = 0;
|
|
12137
|
-
const maxRedirects = 5;
|
|
12138
|
-
function doRequest(requestUrl) {
|
|
12139
|
-
const reqFn = requestUrl.startsWith("https") ? httpsGet : httpGet;
|
|
12140
|
-
const req = reqFn(requestUrl, { timeout: timeoutMs }, (res) => {
|
|
12141
|
-
if (isRedirect(res.statusCode) && res.headers.location) {
|
|
12142
|
-
redirects++;
|
|
12143
|
-
try {
|
|
12144
|
-
const location = validateRedirect(res, requestUrl, redirects, maxRedirects);
|
|
12145
|
-
res.resume();
|
|
12146
|
-
doRequest(location);
|
|
12147
|
-
} catch (err) {
|
|
12148
|
-
reject(err);
|
|
12149
|
-
}
|
|
12150
|
-
return;
|
|
12151
|
-
}
|
|
12152
|
-
if (res.statusCode !== 200) {
|
|
12153
|
-
reject(new Error(`HTTP ${res.statusCode ?? "unknown"}`));
|
|
12154
|
-
return;
|
|
12155
|
-
}
|
|
12156
|
-
resolve3(res);
|
|
12157
|
-
});
|
|
12158
|
-
req.on("error", reject);
|
|
12159
|
-
req.on("timeout", () => {
|
|
12160
|
-
req.destroy();
|
|
12161
|
-
reject(new Error("Download timed out"));
|
|
12162
|
-
});
|
|
12163
|
-
}
|
|
12164
|
-
doRequest(url);
|
|
12165
|
-
});
|
|
12166
|
-
}
|
|
12167
|
-
function download(url, destPath, timeoutMs) {
|
|
12168
|
-
return fetchResponse(url, timeoutMs).then(
|
|
12169
|
-
(res) => new Promise((resolve3, reject) => {
|
|
12170
|
-
const file = createWriteStream2(destPath);
|
|
12171
|
-
res.pipe(file);
|
|
12172
|
-
file.on("finish", () => file.close(() => resolve3()));
|
|
12173
|
-
file.on("error", (err) => {
|
|
12174
|
-
try {
|
|
12175
|
-
unlinkSync3(destPath);
|
|
12176
|
-
} catch {
|
|
12177
|
-
}
|
|
12178
|
-
reject(err);
|
|
12179
|
-
});
|
|
12180
|
-
})
|
|
12181
|
-
);
|
|
12182
|
-
}
|
|
12183
|
-
function downloadText(url, timeoutMs) {
|
|
12184
|
-
return fetchResponse(url, timeoutMs).then(
|
|
12185
|
-
(res) => new Promise((resolve3, reject) => {
|
|
12186
|
-
const chunks = [];
|
|
12187
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
12188
|
-
res.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
|
|
12189
|
-
res.on("error", reject);
|
|
12190
|
-
})
|
|
12191
|
-
);
|
|
12192
|
-
}
|
|
12193
12384
|
var AUTH_CDN_PATH_PREFIX = "/upgradeapp/tools/oauth";
|
|
12194
12385
|
function getAuthBinaryName() {
|
|
12195
12386
|
return platform2() === "win32" ? "okx-auth.exe" : "okx-auth";
|
|
@@ -12213,7 +12404,7 @@ async function fetchAuthCdnChecksum(sources = CDN_SOURCES, timeoutMs = DOWNLOAD_
|
|
|
12213
12404
|
for (const { host, protocol } of sources) {
|
|
12214
12405
|
try {
|
|
12215
12406
|
const url = `${protocol}://${host}${checksumPath}`;
|
|
12216
|
-
const raw = await
|
|
12407
|
+
const raw = await downloadText(url, timeoutMs);
|
|
12217
12408
|
const data = JSON.parse(raw);
|
|
12218
12409
|
if (typeof data.sha256 !== "string" || typeof data.size !== "number" || typeof data.target !== "string") {
|
|
12219
12410
|
continue;
|
|
@@ -12227,7 +12418,7 @@ async function fetchAuthCdnChecksum(sources = CDN_SOURCES, timeoutMs = DOWNLOAD_
|
|
|
12227
12418
|
async function fetchAndValidateChecksum2(host, protocol, checksumPath, platformDir, timeoutMs, onProgress) {
|
|
12228
12419
|
const checksumUrl = `${protocol}://${host}${checksumPath}`;
|
|
12229
12420
|
onProgress?.(`Fetching checksum from ${host}...`);
|
|
12230
|
-
const raw = await
|
|
12421
|
+
const raw = await downloadText(checksumUrl, timeoutMs);
|
|
12231
12422
|
const checksum = JSON.parse(raw);
|
|
12232
12423
|
if (typeof checksum.sha256 !== "string" || typeof checksum.size !== "number" || typeof checksum.target !== "string") {
|
|
12233
12424
|
throw new Error("Invalid checksum.json: missing sha256, size, or target");
|
|
@@ -12240,7 +12431,7 @@ async function fetchAndValidateChecksum2(host, protocol, checksumPath, platformD
|
|
|
12240
12431
|
async function downloadAndVerify2(host, protocol, binaryPath, tmpPath, checksum, timeoutMs, onProgress) {
|
|
12241
12432
|
const binaryUrl = `${protocol}://${host}${binaryPath}`;
|
|
12242
12433
|
onProgress?.(`Downloading binary from ${host}...`);
|
|
12243
|
-
await
|
|
12434
|
+
await download(binaryUrl, tmpPath, timeoutMs);
|
|
12244
12435
|
const actual = hashFile(tmpPath);
|
|
12245
12436
|
if (actual.size !== checksum.size) {
|
|
12246
12437
|
throw new Error(`Size mismatch: expected ${checksum.size}, got ${actual.size}`);
|
|
@@ -12252,7 +12443,7 @@ async function downloadAndVerify2(host, protocol, binaryPath, tmpPath, checksum,
|
|
|
12252
12443
|
function atomicReplace2(tmpPath, resolvedDest) {
|
|
12253
12444
|
if (platform2() === "win32") {
|
|
12254
12445
|
try {
|
|
12255
|
-
|
|
12446
|
+
unlinkSync5(resolvedDest);
|
|
12256
12447
|
} catch {
|
|
12257
12448
|
}
|
|
12258
12449
|
}
|
|
@@ -12308,7 +12499,7 @@ async function installAuthBinary(destPath, sources = CDN_SOURCES, onProgress) {
|
|
|
12308
12499
|
return { status: "installed", source: host };
|
|
12309
12500
|
} catch (err) {
|
|
12310
12501
|
try {
|
|
12311
|
-
|
|
12502
|
+
unlinkSync5(tmpPath);
|
|
12312
12503
|
} catch {
|
|
12313
12504
|
}
|
|
12314
12505
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -12322,7 +12513,7 @@ ${errors.join("\n")}` };
|
|
|
12322
12513
|
function removeAuthBinary(binaryPath) {
|
|
12323
12514
|
const resolvedPath = binaryPath ?? getAuthBinaryPath();
|
|
12324
12515
|
try {
|
|
12325
|
-
|
|
12516
|
+
unlinkSync5(resolvedPath);
|
|
12326
12517
|
return { status: "removed" };
|
|
12327
12518
|
} catch (err) {
|
|
12328
12519
|
if (err.code === "ENOENT") {
|
|
@@ -12332,78 +12523,6 @@ function removeAuthBinary(binaryPath) {
|
|
|
12332
12523
|
throw new Error(`Failed to remove ${resolvedPath}: ${msg}`);
|
|
12333
12524
|
}
|
|
12334
12525
|
}
|
|
12335
|
-
function isRedirect2(statusCode) {
|
|
12336
|
-
return statusCode !== void 0 && statusCode >= 300 && statusCode < 400;
|
|
12337
|
-
}
|
|
12338
|
-
function validateRedirect2(res, requestUrl, redirectCount, maxRedirects) {
|
|
12339
|
-
if (redirectCount > maxRedirects) {
|
|
12340
|
-
throw new Error(`Too many redirects (${maxRedirects})`);
|
|
12341
|
-
}
|
|
12342
|
-
const location = res.headers.location;
|
|
12343
|
-
if (requestUrl.startsWith("https") && !location.startsWith("https")) {
|
|
12344
|
-
throw new Error("Refused HTTPS \u2192 HTTP redirect downgrade");
|
|
12345
|
-
}
|
|
12346
|
-
return location;
|
|
12347
|
-
}
|
|
12348
|
-
function fetchResponse2(url, timeoutMs) {
|
|
12349
|
-
return new Promise((resolve3, reject) => {
|
|
12350
|
-
let redirects = 0;
|
|
12351
|
-
const maxRedirects = 5;
|
|
12352
|
-
function doRequest(requestUrl) {
|
|
12353
|
-
const reqFn = requestUrl.startsWith("https") ? httpsGet2 : httpGet2;
|
|
12354
|
-
const req = reqFn(requestUrl, { timeout: timeoutMs }, (res) => {
|
|
12355
|
-
if (isRedirect2(res.statusCode) && res.headers.location) {
|
|
12356
|
-
redirects++;
|
|
12357
|
-
try {
|
|
12358
|
-
const location = validateRedirect2(res, requestUrl, redirects, maxRedirects);
|
|
12359
|
-
res.resume();
|
|
12360
|
-
doRequest(location);
|
|
12361
|
-
} catch (err) {
|
|
12362
|
-
reject(err);
|
|
12363
|
-
}
|
|
12364
|
-
return;
|
|
12365
|
-
}
|
|
12366
|
-
if (res.statusCode !== 200) {
|
|
12367
|
-
reject(new Error(`HTTP ${res.statusCode ?? "unknown"}`));
|
|
12368
|
-
return;
|
|
12369
|
-
}
|
|
12370
|
-
resolve3(res);
|
|
12371
|
-
});
|
|
12372
|
-
req.on("error", reject);
|
|
12373
|
-
req.on("timeout", () => {
|
|
12374
|
-
req.destroy();
|
|
12375
|
-
reject(new Error("Download timed out"));
|
|
12376
|
-
});
|
|
12377
|
-
}
|
|
12378
|
-
doRequest(url);
|
|
12379
|
-
});
|
|
12380
|
-
}
|
|
12381
|
-
function download2(url, destPath, timeoutMs) {
|
|
12382
|
-
return fetchResponse2(url, timeoutMs).then(
|
|
12383
|
-
(res) => new Promise((resolve3, reject) => {
|
|
12384
|
-
const file = createWriteStream3(destPath);
|
|
12385
|
-
res.pipe(file);
|
|
12386
|
-
file.on("finish", () => file.close(() => resolve3()));
|
|
12387
|
-
file.on("error", (err) => {
|
|
12388
|
-
try {
|
|
12389
|
-
unlinkSync4(destPath);
|
|
12390
|
-
} catch {
|
|
12391
|
-
}
|
|
12392
|
-
reject(err);
|
|
12393
|
-
});
|
|
12394
|
-
})
|
|
12395
|
-
);
|
|
12396
|
-
}
|
|
12397
|
-
function downloadText2(url, timeoutMs) {
|
|
12398
|
-
return fetchResponse2(url, timeoutMs).then(
|
|
12399
|
-
(res) => new Promise((resolve3, reject) => {
|
|
12400
|
-
const chunks = [];
|
|
12401
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
12402
|
-
res.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
|
|
12403
|
-
res.on("error", reject);
|
|
12404
|
-
})
|
|
12405
|
-
);
|
|
12406
|
-
}
|
|
12407
12526
|
var CACHE_PATH = join12(homedir10(), ".okx", "auth-binary-check.json");
|
|
12408
12527
|
var CHECK_INTERVAL_MS2 = 2 * 60 * 60 * 1e3;
|
|
12409
12528
|
function readCache3() {
|
|
@@ -12452,13 +12571,13 @@ function updateAuthBinaryCache(sha256) {
|
|
|
12452
12571
|
}
|
|
12453
12572
|
function clearAuthBinaryCache() {
|
|
12454
12573
|
try {
|
|
12455
|
-
|
|
12574
|
+
unlinkSync6(CACHE_PATH);
|
|
12456
12575
|
} catch {
|
|
12457
12576
|
}
|
|
12458
12577
|
}
|
|
12459
12578
|
|
|
12460
12579
|
// src/commands/auth.ts
|
|
12461
|
-
import { spawn as
|
|
12580
|
+
import { spawn as spawn3 } from "child_process";
|
|
12462
12581
|
import readline from "readline";
|
|
12463
12582
|
|
|
12464
12583
|
// src/formatter.ts
|
|
@@ -12554,7 +12673,7 @@ function markFailedIfSCodeError(data) {
|
|
|
12554
12673
|
function runOkxAuth(args) {
|
|
12555
12674
|
const binPath = getAuthBinaryPath();
|
|
12556
12675
|
return new Promise((resolve3, reject) => {
|
|
12557
|
-
const child =
|
|
12676
|
+
const child = spawn3(binPath, args, {
|
|
12558
12677
|
stdio: "inherit"
|
|
12559
12678
|
});
|
|
12560
12679
|
child.on("error", (err) => {
|
|
@@ -12568,7 +12687,7 @@ function runOkxAuth(args) {
|
|
|
12568
12687
|
function runOkxAuthCapture(args) {
|
|
12569
12688
|
const binPath = getAuthBinaryPath();
|
|
12570
12689
|
return new Promise((resolve3, reject) => {
|
|
12571
|
-
const child =
|
|
12690
|
+
const child = spawn3(binPath, args, {
|
|
12572
12691
|
stdio: ["inherit", "pipe", "inherit"]
|
|
12573
12692
|
});
|
|
12574
12693
|
const chunks = [];
|
|
@@ -12915,7 +13034,7 @@ function sanitize2(value) {
|
|
|
12915
13034
|
import fs5 from "fs";
|
|
12916
13035
|
import path4 from "path";
|
|
12917
13036
|
import os4 from "os";
|
|
12918
|
-
import { spawnSync, spawn as
|
|
13037
|
+
import { spawnSync, spawn as spawn4 } from "child_process";
|
|
12919
13038
|
import { createRequire as createRequire2 } from "module";
|
|
12920
13039
|
import { fileURLToPath } from "url";
|
|
12921
13040
|
var _require2 = createRequire2(import.meta.url);
|
|
@@ -13249,7 +13368,7 @@ async function checkStdioHandshake(entryPath, report) {
|
|
|
13249
13368
|
clearTimeout(timer);
|
|
13250
13369
|
resolve3(passed);
|
|
13251
13370
|
};
|
|
13252
|
-
const child =
|
|
13371
|
+
const child = spawn4(process.execPath, [entryPath], {
|
|
13253
13372
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13254
13373
|
env: { ...process.env }
|
|
13255
13374
|
});
|
|
@@ -13368,7 +13487,7 @@ async function cmdDiagnoseMcp(options = {}) {
|
|
|
13368
13487
|
|
|
13369
13488
|
// src/commands/diagnose.ts
|
|
13370
13489
|
var CLI_VERSION = readCliVersion();
|
|
13371
|
-
var GIT_HASH = true ? "
|
|
13490
|
+
var GIT_HASH = true ? "3247f9a2" : "dev";
|
|
13372
13491
|
function maskKey2(key) {
|
|
13373
13492
|
if (!key) return "(not set)";
|
|
13374
13493
|
if (key.length <= 8) return "****";
|
|
@@ -14676,12 +14795,12 @@ var CLI_REGISTRY = {
|
|
|
14676
14795
|
commands: {
|
|
14677
14796
|
"traders-by-filter": {
|
|
14678
14797
|
toolName: "smartmoney_get_traders_by_filter",
|
|
14679
|
-
usage: "okx smartmoney traders-by-filter [--updateTime <
|
|
14798
|
+
usage: "okx smartmoney traders-by-filter [--updateTime <yyyyMMddHHmm UTC+8>] [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 90>] [--minPnl <n>] [--minWinRate <r>] [--maxDrawdown <r>] [--minAum <n>] [--after <authorId>] [--before <authorId>] [--limit <n, default 10>] [--json]",
|
|
14680
14799
|
description: "Leaderboard of top smart-money traders, ranked and filtered by pool conditions"
|
|
14681
14800
|
},
|
|
14682
14801
|
"performance-by-trader": {
|
|
14683
14802
|
toolName: "smartmoney_get_performance_by_trader",
|
|
14684
|
-
usage: "okx smartmoney performance-by-trader --authorIds <id1,id2> [--period <3|7|30|90>] [--json]",
|
|
14803
|
+
usage: "okx smartmoney performance-by-trader --authorIds <id1,id2> [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 90>] [--json]",
|
|
14685
14804
|
description: "PnL / win-rate / drawdown profile for one or more traders by authorIds"
|
|
14686
14805
|
},
|
|
14687
14806
|
"trader-positions": {
|
|
@@ -14691,12 +14810,12 @@ var CLI_REGISTRY = {
|
|
|
14691
14810
|
},
|
|
14692
14811
|
"trader-positions-history": {
|
|
14693
14812
|
toolName: "smartmoney_get_trader_positions_history",
|
|
14694
|
-
usage: "okx smartmoney trader-positions-history --authorId <id> [--instId <id>] [--after <posId>] [--before <posId>] [--limit <n>] [--json]",
|
|
14813
|
+
usage: "okx smartmoney trader-positions-history --authorId <id> [--instId <id>] [--after <posId>] [--before <posId>] [--limit <n, default 10>] [--json]",
|
|
14695
14814
|
description: "Closed-position history of a single trader (paginated)"
|
|
14696
14815
|
},
|
|
14697
14816
|
"trader-orders-history": {
|
|
14698
14817
|
toolName: "smartmoney_get_trader_orders_history",
|
|
14699
|
-
usage: "okx smartmoney trader-orders-history --authorId <id> [--instId <id>] [--after <ordId>] [--before <ordId>] [--limit <n>] [--json]",
|
|
14818
|
+
usage: "okx smartmoney trader-orders-history --authorId <id> [--instId <id>] [--after <ordId>] [--before <ordId>] [--limit <n, default 10>] [--json]",
|
|
14700
14819
|
description: "Recent orders/fills placed by a single trader (paginated)"
|
|
14701
14820
|
},
|
|
14702
14821
|
"search-trader": {
|
|
@@ -14706,22 +14825,22 @@ var CLI_REGISTRY = {
|
|
|
14706
14825
|
},
|
|
14707
14826
|
"signal-overview-by-filter": {
|
|
14708
14827
|
toolName: "smartmoney_get_signal_overview_by_filter",
|
|
14709
|
-
usage: "okx smartmoney signal-overview-by-filter [--topInstruments <n> | --instCcyList <BTC,ETH,...>] [--sortBy <pnl|pnlRatio>] [--period <3|7|30|90>] [--pnlTier <tier>] [--winRateTier <tier>] [--maxDrawdownTier <tier>] [--aumTier <tier>] [--lmtNum <n>] [--json]",
|
|
14828
|
+
usage: "okx smartmoney signal-overview-by-filter [--topInstruments <n, default 20> | --instCcyList <BTC,ETH,...>] [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 7>] [--pnlTier <tier>] [--winRateTier <tier>] [--maxDrawdownTier <tier>] [--aumTier <tier>] [--lmtNum <n, default 100>] [--json]",
|
|
14710
14829
|
description: "Multi-asset smart-money consensus signal aggregated over a tier-filtered pool"
|
|
14711
14830
|
},
|
|
14712
14831
|
"signal-overview-by-trader": {
|
|
14713
14832
|
toolName: "smartmoney_get_signal_overview_by_trader",
|
|
14714
|
-
usage: "okx smartmoney signal-overview-by-trader --authorIds <id1,id2> [--topInstruments <n> | --instCcyList <BTC,ETH,...>] [--json]",
|
|
14833
|
+
usage: "okx smartmoney signal-overview-by-trader --authorIds <id1,id2> [--topInstruments <n, default 20> | --instCcyList <BTC,ETH,...>] [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 7>] [--json]",
|
|
14715
14834
|
description: "Multi-asset smart-money signal aggregated over a hand-picked set of traders (authorIds-direct-lookup; pool filters not exposed)"
|
|
14716
14835
|
},
|
|
14717
14836
|
"signal-trend-by-filter": {
|
|
14718
14837
|
toolName: "smartmoney_get_signal_trend_by_filter",
|
|
14719
|
-
usage: "okx smartmoney signal-trend-by-filter --instCcy <ccy> [--asOfTime <yyyyMMddHH>] [--granularity <1h|1d>] [--limit <n>] [--sortBy <pnl|pnlRatio>] [--period <3|7|30|90>] [--pnlTier <tier>] [--winRateTier <tier>] [--maxDrawdownTier <tier>] [--aumTier <tier>] [--lmtNum <n>] [--json]",
|
|
14838
|
+
usage: "okx smartmoney signal-trend-by-filter --instCcy <ccy> [--asOfTime <yyyyMMddHH UTC>] [--granularity <1h|1d, default 1h>] [--limit <n, default 24>] [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 7>] [--pnlTier <tier>] [--winRateTier <tier>] [--maxDrawdownTier <tier>] [--aumTier <tier>] [--lmtNum <n, default 100>] [--json]",
|
|
14720
14839
|
description: "Single-coin smart-money signal time-series aggregated over a tier-filtered pool, anchored at asOfTime"
|
|
14721
14840
|
},
|
|
14722
14841
|
"signal-trend-by-trader": {
|
|
14723
14842
|
toolName: "smartmoney_get_signal_trend_by_trader",
|
|
14724
|
-
usage: "okx smartmoney signal-trend-by-trader --authorIds <id1,id2> --instCcy <ccy> [--asOfTime <yyyyMMddHH>] [--granularity <1h|1d>] [--limit <n>] [--json]",
|
|
14843
|
+
usage: "okx smartmoney signal-trend-by-trader --authorIds <id1,id2> --instCcy <ccy> [--asOfTime <yyyyMMddHH UTC>] [--granularity <1h|1d, default 1h>] [--limit <n, default 24>] [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 7>] [--json]",
|
|
14725
14844
|
description: "Single-coin smart-money signal time-series aggregated over a hand-picked set of traders (authorIds-direct-lookup; pool filters not exposed)"
|
|
14726
14845
|
}
|
|
14727
14846
|
}
|
|
@@ -18140,6 +18259,7 @@ async function cmdSmartmoneyTradersByFilter(run, opts) {
|
|
|
18140
18259
|
async function cmdSmartmoneyPerformanceByTrader(run, opts) {
|
|
18141
18260
|
const result = await run("smartmoney_get_performance_by_trader", {
|
|
18142
18261
|
authorIds: csvToArray(opts.authorIds),
|
|
18262
|
+
sortBy: opts.sortBy,
|
|
18143
18263
|
period: opts.period
|
|
18144
18264
|
});
|
|
18145
18265
|
if (opts.json) {
|
|
@@ -18280,7 +18400,9 @@ async function cmdSmartmoneySignalOverviewByTrader(run, opts) {
|
|
|
18280
18400
|
const result = await run("smartmoney_get_signal_overview_by_trader", {
|
|
18281
18401
|
authorIds: csvToArray(opts.authorIds),
|
|
18282
18402
|
topInstruments: opts.topInstruments,
|
|
18283
|
-
instCcyList: csvToArray(opts.instCcyList)
|
|
18403
|
+
instCcyList: csvToArray(opts.instCcyList),
|
|
18404
|
+
sortBy: opts.sortBy,
|
|
18405
|
+
period: opts.period
|
|
18284
18406
|
});
|
|
18285
18407
|
if (opts.json) {
|
|
18286
18408
|
printJson(result);
|
|
@@ -18325,7 +18447,9 @@ async function cmdSmartmoneySignalTrendByTrader(run, opts) {
|
|
|
18325
18447
|
instCcy: opts.instCcy,
|
|
18326
18448
|
asOfTime: opts.asOfTime,
|
|
18327
18449
|
granularity: opts.granularity,
|
|
18328
|
-
limit: opts.limit
|
|
18450
|
+
limit: opts.limit,
|
|
18451
|
+
sortBy: opts.sortBy,
|
|
18452
|
+
period: opts.period
|
|
18329
18453
|
});
|
|
18330
18454
|
if (opts.json) {
|
|
18331
18455
|
printJson(result);
|
|
@@ -18849,12 +18973,12 @@ function filterByStrike(data, ref) {
|
|
|
18849
18973
|
});
|
|
18850
18974
|
}
|
|
18851
18975
|
function filterByTerm(data, termDays, minTermDays, maxTermDays) {
|
|
18852
|
-
const
|
|
18976
|
+
const MS_PER_DAY2 = 864e5;
|
|
18853
18977
|
return data.filter((r) => {
|
|
18854
18978
|
const exp = Number(r["expTime"]);
|
|
18855
18979
|
const start = Number(r["interestAccrualTime"]);
|
|
18856
18980
|
if (!exp || !start) return false;
|
|
18857
|
-
const days = Math.round((exp - start) /
|
|
18981
|
+
const days = Math.round((exp - start) / MS_PER_DAY2);
|
|
18858
18982
|
if (termDays !== void 0 && days !== termDays) return false;
|
|
18859
18983
|
if (minTermDays !== void 0 && days < minTermDays) return false;
|
|
18860
18984
|
if (maxTermDays !== void 0 && days > maxTermDays) return false;
|
|
@@ -19849,7 +19973,7 @@ async function cmdEventCancel(run, opts) {
|
|
|
19849
19973
|
// src/index.ts
|
|
19850
19974
|
var _require3 = createRequire3(import.meta.url);
|
|
19851
19975
|
var CLI_VERSION2 = _require3("../package.json").version;
|
|
19852
|
-
var GIT_HASH2 = true ? "
|
|
19976
|
+
var GIT_HASH2 = true ? "3247f9a2" : "dev";
|
|
19853
19977
|
function handlePilotCommand(action, json, force, binaryPath) {
|
|
19854
19978
|
if (action === "status") return cmdPilotStatus(json, binaryPath);
|
|
19855
19979
|
if (action === "install") return cmdPilotInstall(json, binaryPath);
|
|
@@ -20887,6 +21011,7 @@ function handleSmartmoneyCommand(run, action, rest, v, json) {
|
|
|
20887
21011
|
}
|
|
20888
21012
|
return cmdSmartmoneyPerformanceByTrader(run, {
|
|
20889
21013
|
authorIds: v.authorIds,
|
|
21014
|
+
sortBy: v.sortBy,
|
|
20890
21015
|
period: v.period,
|
|
20891
21016
|
json
|
|
20892
21017
|
});
|
|
@@ -20977,6 +21102,8 @@ function handleSmartmoneyCommand(run, action, rest, v, json) {
|
|
|
20977
21102
|
authorIds: v.authorIds,
|
|
20978
21103
|
topInstruments: v.topInstruments,
|
|
20979
21104
|
instCcyList: v.instCcyList,
|
|
21105
|
+
sortBy: v.sortBy,
|
|
21106
|
+
period: v.period,
|
|
20980
21107
|
json
|
|
20981
21108
|
});
|
|
20982
21109
|
}
|
|
@@ -21013,6 +21140,8 @@ function handleSmartmoneyCommand(run, action, rest, v, json) {
|
|
|
21013
21140
|
asOfTime: v.asOfTime,
|
|
21014
21141
|
granularity: v.granularity,
|
|
21015
21142
|
limit: v.limit,
|
|
21143
|
+
sortBy: v.sortBy,
|
|
21144
|
+
period: v.period,
|
|
21016
21145
|
json
|
|
21017
21146
|
});
|
|
21018
21147
|
}
|
|
@@ -21342,6 +21471,7 @@ async function main() {
|
|
|
21342
21471
|
main().catch((error) => {
|
|
21343
21472
|
const payload = toToolErrorPayload(error);
|
|
21344
21473
|
errorLine(`Error: ${payload.message}`);
|
|
21474
|
+
if (payload.code) errorLine(`Code: ${payload.code}`);
|
|
21345
21475
|
if (payload.traceId) errorLine(`TraceId: ${payload.traceId}`);
|
|
21346
21476
|
if (payload.suggestion) errorLine(`Hint: ${payload.suggestion}`);
|
|
21347
21477
|
errorLine(`Version: @okx_ai/okx-trade-cli@${CLI_VERSION2}`);
|