@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 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 unlinkSync4,
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 { get as httpsGet2 } from "https";
893
- import { get as httpGet2 } from "http";
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 unlinkSync3,
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 { get as httpsGet } from "https";
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 = spawn(binPath, ["token"], {
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(new ConfigError(
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
- if (code === EXIT_CODES.SUCCESS) {
1312
- const token = Buffer.concat(chunks).toString("utf-8").trim();
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 || "OKX API request failed.";
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. Pass as a quoted string: `"3"` / `"7"` / `"30"` / `"90"` (NOT integer 7). Drives capability metrics (avgLongWinRate / avgShortWinRate) and the `winRateTier` filter. Does NOT affect signal fields (which always use the latest snapshot).'
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`. ANY = no filter; TOP50 = PnL \u2265 P50 (median); TOP20 = \u2265 P80; TOP5 = \u2265 P95. PnL distribution is long-tailed \u2014 use percentile, not absolute thresholds."
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 (fixed thresholds). ANY = no filter; WR_GE_50 = \u2265 50%; WR_GE_80 = \u2265 80%."
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 (fixed thresholds; smaller drawdown = lower risk). ANY = no filter; MR_LE_20 = drawdown \u2264 20%; MR_LE_50 = \u2264 50%."
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. ANY = no filter; TOP50 = AUM \u2265 P50; TOP20 = \u2265 P80; TOP5 = \u2265 P95. AUM is long-tailed \u2014 use percentile, not absolute USD."
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: "Leaderboard sort key. `pnl` = absolute USD profit; `pnlRatio` = percentage return."
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. Pass as a quoted string: `"3"` / `"7"` / `"30"` / `"90"` (NOT integer 90). Default `"90"` (matches leaderboard UI). Filters AND ranks traders by their PnL over that window.'
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. Pass as a quoted numeric string, e.g. `"10000"` (NOT integer 10000) \u2192 traders with PnL \u2265 $10,000. Numeric threshold \u2014 distinct from the signal-side `pnlTier` percentile enum.'
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. Pass as a quoted numeric string, e.g. `"0.8"` (NOT number 0.8) \u2192 traders with win-rate \u2265 80%. Numeric threshold \u2014 distinct from the signal-side `winRateTier` enum.'
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. Pass as a quoted numeric string, e.g. `"0.1"` (NOT number 0.1) \u2192 traders with drawdown \u2264 10%. Lower = lower risk. Numeric threshold \u2014 distinct from the signal-side `maxDrawdownTier` enum.'
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. Pass as a quoted numeric string, e.g. `"1000"` (NOT integer 1000) \u2192 traders with AUM \u2265 $1,000. Numeric threshold \u2014 distinct from the signal-side `aumTier` percentile enum.'
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: { type: "string", description: "Sum of long-side notional in USDT." },
7210
- shortNotionalUsdt: { type: "string", description: "Sum of short-side notional in USDT." },
7211
- netNotionalUsdt: { type: "string", description: "Net directional notional in USDT = long \u2212 short." },
7212
- totalNotionalUsdt: { type: "string", description: "Gross notional in USDT = long + short." },
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: "Long-side notional-weighted average entry price (USDT). NULL when no long. Compare to current price to judge whether following the longs is cheap/expensive now."
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: "Short-side notional-weighted average entry price (USDT). NULL when no short."
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: { type: "string", description: "1 \u2212 longRatio. NULL when no traders." },
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 = 1 \u2212 longRatio. Decimal 0~1."
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. Tracks total capital deployed (rising = adding, falling = retreating)."
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. Pass as a quoted 12-digit string `yyyyMMddHHmm` (UTC+8), e.g. `"202604301815"` (NOT integer). Omit to query the latest snapshot (refreshed every ~5 min).'
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: "Cursor for paginating backwards (older page). Pass the `authorId` of the last item from the previous page verbatim as a quoted string (NOT a number). Cursor anchors on `authorId` while preserving the current `sortBy` order."
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: "Cursor for paginating forwards (newer page). Pass the `authorId` of the first item from the previous page verbatim as a quoted string. Cursor anchors on `authorId` while preserving the current `sortBy` order."
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. Pass as a quoted string: `"3"` / `"7"` / `"30"` / `"90"` (NOT integer). Default `"90"`.'
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
- period: readString(args, "period")
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="both"` net-mode case.'
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: "Cursor: returns positions with `posId` smaller than this value (older \u2014 paginate backwards). Pass as quoted string (the `posId` value verbatim, NOT a number \u2014 `posId` is a 19-digit ID and number coercion can lose precision)."
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: "Cursor: returns positions with `posId` greater than this value (newer \u2014 paginate forwards). Pass as quoted string."
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: "Cursor: returns trades with `ordId` smaller than this value (older \u2014 paginate backwards). Pass as quoted string (the `ordId` value verbatim, NOT a number \u2014 `ordId` is a 19-digit ID and number coercion can lose precision)."
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: "Cursor: returns trades with `ordId` greater than this value (newer \u2014 paginate forwards). Pass as quoted string."
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`. Pool sizing / ranking / capability filters not exposed \u2014 backend uses defaults for direct-lookup scenarios. 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).",
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. Pass as a quoted 10-digit string `yyyyMMddHH` UTC, e.g. `"2026050100"` (NOT integer 2026050100). Returns the latest `limit` buckets ending at this anchor. Omit to use the current UTC hour.'
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
- granularity: readString(args, "granularity"),
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). Pool sizing / ranking / capability filters not exposed \u2014 backend uses defaults for direct-lookup scenarios. 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.",
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. Pass as a quoted 10-digit string `yyyyMMddHH` UTC, e.g. `"2026050100"` (NOT integer 2026050100). Returns the latest `limit` buckets ending at this anchor. Omit to use the current UTC hour.'
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
- granularity: readString(args, "granularity"),
8077
- limit: readNumber(args, "limit")
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
- unlinkSync3(resolvedDest);
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
- unlinkSync3(tmpPath);
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
- unlinkSync3(resolvedPath);
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 downloadText2(url, timeoutMs);
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 downloadText2(checksumUrl, timeoutMs);
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 download2(binaryUrl, tmpPath, timeoutMs);
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
- unlinkSync4(resolvedDest);
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
- unlinkSync4(tmpPath);
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
- unlinkSync4(resolvedPath);
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
- unlinkSync5(CACHE_PATH);
12574
+ unlinkSync6(CACHE_PATH);
12456
12575
  } catch {
12457
12576
  }
12458
12577
  }
12459
12578
 
12460
12579
  // src/commands/auth.ts
12461
- import { spawn as spawn2 } from "child_process";
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 = spawn2(binPath, args, {
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 = spawn2(binPath, args, {
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 spawn3 } from "child_process";
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 = spawn3(process.execPath, [entryPath], {
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 ? "d0f8ad6" : "dev";
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 <ts>] [--sortBy <pnl|pnlRatio>] [--period <3|7|30|90>] [--minPnl <n>] [--minWinRate <r>] [--maxDrawdown <r>] [--minAum <n>] [--after <id>] [--before <id>] [--limit <n>] [--json]",
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 MS_PER_DAY = 864e5;
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) / MS_PER_DAY);
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 ? "d0f8ad6" : "dev";
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}`);