@okx_ai/okx-trade-cli 1.3.3-beta.1 → 1.3.3-beta.2

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
@@ -880,32 +880,29 @@ import * as path3 from "path";
880
880
  import * as os3 from "os";
881
881
  import { execFileSync } from "child_process";
882
882
  import {
883
- createWriteStream as createWriteStream3,
884
883
  mkdirSync as mkdirSync9,
885
884
  chmodSync as chmodSync2,
886
885
  existsSync as existsSync7,
887
- unlinkSync as unlinkSync4,
886
+ unlinkSync as unlinkSync5,
888
887
  renameSync as renameSync4
889
888
  } from "fs";
890
889
  import { homedir as homedir9, platform as platform2 } from "os";
891
890
  import { join as join11, dirname as dirname7 } from "path";
892
- import { get as httpsGet2 } from "https";
893
- import { get as httpGet2 } from "http";
891
+ import { createWriteStream as createWriteStream2, unlinkSync as unlinkSync3 } from "fs";
892
+ import { get as httpsGet } from "https";
893
+ import { get as httpGet } from "http";
894
894
  import {
895
895
  readFileSync as readFileSync7,
896
- createWriteStream as createWriteStream2,
897
896
  mkdirSync as mkdirSync8,
898
897
  chmodSync,
899
898
  existsSync as existsSync6,
900
- unlinkSync as unlinkSync3,
899
+ unlinkSync as unlinkSync4,
901
900
  renameSync as renameSync3
902
901
  } from "fs";
903
902
  import { createHash } from "crypto";
904
903
  import { homedir as homedir8, platform, arch } from "os";
905
904
  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";
905
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10, unlinkSync as unlinkSync6, existsSync as existsSync8 } from "fs";
909
906
  import { join as join12 } from "path";
910
907
  import { homedir as homedir10 } from "os";
911
908
  var EXEC_TIMEOUT_MS = 3e4;
@@ -1634,7 +1631,7 @@ var OkxRestClient = class _OkxRestClient {
1634
1631
  headers.set("OK-ACCESS-TIMESTAMP", timestamp);
1635
1632
  }
1636
1633
  throwOkxError(code, msg, reqConfig, traceId) {
1637
- const message = msg || "OKX API request failed.";
1634
+ const message = msg && msg.trim() !== "" ? msg : `OKX API rejected request (code ${code}).`;
1638
1635
  const endpoint = `${reqConfig.method} ${reqConfig.path}`;
1639
1636
  if (code === "50111" || code === "50112" || code === "50113") {
1640
1637
  throw new AuthenticationError(
@@ -6999,31 +6996,31 @@ var SIGNAL_POOL_FILTER_PROPS = {
6999
6996
  type: "string",
7000
6997
  enum: PERIOD_DAYS,
7001
6998
  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).'
6999
+ 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
7000
  },
7004
7001
  pnlTier: {
7005
7002
  type: "string",
7006
7003
  enum: ["PNL_ANY", "PNL_TOP50", "PNL_TOP20", "PNL_TOP5"],
7007
7004
  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."
7005
+ 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
7006
  },
7010
7007
  winRateTier: {
7011
7008
  type: "string",
7012
7009
  enum: ["WR_ANY", "WR_GE_50", "WR_GE_80"],
7013
7010
  default: "WR_ANY",
7014
- description: "Minimum win-rate gate (fixed thresholds). ANY = no filter; WR_GE_50 = \u2265 50%; WR_GE_80 = \u2265 80%."
7011
+ 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
7012
  },
7016
7013
  maxDrawdownTier: {
7017
7014
  type: "string",
7018
7015
  enum: ["MR_ANY", "MR_LE_20", "MR_LE_50"],
7019
7016
  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%."
7017
+ 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
7018
  },
7022
7019
  aumTier: {
7023
7020
  type: "string",
7024
7021
  enum: ["AUM_ANY", "AUM_TOP50", "AUM_TOP20", "AUM_TOP5"],
7025
7022
  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."
7023
+ 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
7024
  }
7028
7025
  };
7029
7026
  var LEADERBOARD_POOL_FILTER_PROPS = {
@@ -7031,29 +7028,29 @@ var LEADERBOARD_POOL_FILTER_PROPS = {
7031
7028
  type: "string",
7032
7029
  enum: ["pnl", "pnlRatio"],
7033
7030
  default: "pnl",
7034
- description: "Leaderboard sort key. `pnl` = absolute USD profit; `pnlRatio` = percentage return."
7031
+ description: 'Required. Leaderboard sort key. `pnl` = absolute USD profit; `pnlRatio` = percentage return. Default `"pnl"`.'
7035
7032
  },
7036
7033
  period: {
7037
7034
  type: "string",
7038
7035
  enum: PERIOD_DAYS,
7039
7036
  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.'
7037
+ 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
7038
  },
7042
7039
  minPnl: {
7043
7040
  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.'
7041
+ 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
7042
  },
7046
7043
  minWinRate: {
7047
7044
  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.'
7045
+ 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
7046
  },
7050
7047
  maxDrawdown: {
7051
7048
  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.'
7049
+ 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
7050
  },
7054
7051
  minAum: {
7055
7052
  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.'
7053
+ 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
7054
  }
7058
7055
  };
7059
7056
  var LEADERBOARD_FILTER_UPSTREAM_NAMES = {
@@ -7065,19 +7062,25 @@ var LEADERBOARD_FILTER_UPSTREAM_NAMES = {
7065
7062
  minAum: "asset"
7066
7063
  };
7067
7064
  function readPoolFilters(args) {
7065
+ assertPoolFilterEnums(args, LEADERBOARD_POOL_FILTER_PROPS);
7068
7066
  const result = {};
7069
7067
  for (const [publicKey, upstreamKey] of Object.entries(LEADERBOARD_FILTER_UPSTREAM_NAMES)) {
7070
7068
  const val = readString(args, publicKey);
7071
7069
  if (val !== void 0 && val !== "") result[upstreamKey] = val;
7072
7070
  }
7071
+ if (result.sortBy === void 0) result.sortBy = "pnl";
7072
+ if (result.period === void 0) result.period = "90";
7073
7073
  return result;
7074
7074
  }
7075
7075
  function readSignalPoolFilters(args) {
7076
+ assertPoolFilterEnums(args, SIGNAL_POOL_FILTER_PROPS);
7076
7077
  const result = {};
7077
7078
  for (const key of Object.keys(SIGNAL_POOL_FILTER_PROPS)) {
7078
7079
  const val = readString(args, key);
7079
7080
  if (val) result[key] = val;
7080
7081
  }
7082
+ if (result.sortBy === void 0) result.sortBy = "pnl";
7083
+ if (result.period === void 0) result.period = "7";
7081
7084
  return result;
7082
7085
  }
7083
7086
  function extractLeaderboardEnvelope(data) {
@@ -7093,7 +7096,7 @@ function extractLeaderboardEnvelope(data) {
7093
7096
  function deriveDirection(posSide, pos) {
7094
7097
  if (posSide === "long") return "long";
7095
7098
  if (posSide === "short") return "short";
7096
- if (posSide === "both" && typeof pos === "string" && pos !== "") {
7099
+ if ((posSide === "net" || posSide === "both") && typeof pos === "string" && pos !== "") {
7097
7100
  const n = Number(pos);
7098
7101
  if (Number.isFinite(n) && n !== 0) return n > 0 ? "long" : "short";
7099
7102
  }
@@ -7130,6 +7133,22 @@ function extractBaseCcy(instId) {
7130
7133
  function actionableError(message, hint) {
7131
7134
  return new ValidationError(`${message} ${hint}`);
7132
7135
  }
7136
+ function assertEnum2(args, key, allowed) {
7137
+ const val = readString(args, key);
7138
+ if (val === void 0 || val === "") return;
7139
+ if (!allowed.includes(val)) {
7140
+ throw actionableError(
7141
+ `Invalid value for "${key}": ${JSON.stringify(val)}.`,
7142
+ `Allowed values: ${allowed.map((v) => JSON.stringify(v)).join(", ")}.`
7143
+ );
7144
+ }
7145
+ }
7146
+ function assertPoolFilterEnums(args, props) {
7147
+ for (const [key, spec] of Object.entries(props)) {
7148
+ const enumList = spec.enum;
7149
+ if (Array.isArray(enumList)) assertEnum2(args, key, enumList);
7150
+ }
7151
+ }
7133
7152
  function readArrayAsCsv(args, key) {
7134
7153
  const arr = readStringArray(args, key);
7135
7154
  if (!arr || arr.length === 0) return void 0;
@@ -7206,21 +7225,33 @@ var SIGNAL_ITEM_PROPS = {
7206
7225
  type: "object",
7207
7226
  description: "Notional / capital-flow group.",
7208
7227
  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." },
7228
+ longNotionalUsdt: {
7229
+ type: "string",
7230
+ 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."
7231
+ },
7232
+ shortNotionalUsdt: {
7233
+ type: "string",
7234
+ 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."
7235
+ },
7236
+ netNotionalUsdt: {
7237
+ type: "string",
7238
+ 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."
7239
+ },
7240
+ totalNotionalUsdt: {
7241
+ type: "string",
7242
+ 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."
7243
+ },
7213
7244
  totalNotionalVs24h: {
7214
7245
  type: "string",
7215
7246
  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
7247
  },
7217
7248
  smartMoneyLongAvgEntry: {
7218
7249
  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."
7250
+ 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
7251
  },
7221
7252
  smartMoneyShortAvgEntry: {
7222
7253
  type: "string",
7223
- description: "Short-side notional-weighted average entry price (USDT). NULL when no short."
7254
+ description: 'Short-side notional-weighted average entry price (USDT). Empty string `""` when no short.'
7224
7255
  }
7225
7256
  }
7226
7257
  },
@@ -7232,14 +7263,17 @@ var SIGNAL_ITEM_PROPS = {
7232
7263
  longRatioVs24h: { type: "string", description: "longRatio \u2212 hist_24h.longRatio. NULL when no hist." },
7233
7264
  longRatioVs7d: { type: "string", description: "longRatio \u2212 hist_7d.longRatio. NULL when no hist." },
7234
7265
  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." },
7266
+ shortRatio: {
7267
+ type: "string",
7268
+ description: "Headcount short ratio = shortTraders / tradersWithPosition. NULL when no traders."
7269
+ },
7236
7270
  weightedLongRatio: {
7237
7271
  type: "string",
7238
- description: "Notional-weighted long ratio = \u03A3(long_notional) / \u03A3(notional). NULL when no notional."
7272
+ 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
7273
  },
7240
7274
  weightedShortRatio: {
7241
7275
  type: "string",
7242
- description: "Notional-weighted short ratio = \u03A3(short_notional) / \u03A3(notional). NULL when no notional."
7276
+ 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
7277
  }
7244
7278
  }
7245
7279
  },
@@ -7266,15 +7300,15 @@ var SIGNAL_HISTORY_ITEM_PROPS = {
7266
7300
  },
7267
7301
  shortRatio: {
7268
7302
  type: "string",
7269
- description: "Headcount short ratio at this bucket = 1 \u2212 longRatio. Decimal 0~1."
7303
+ description: "Headcount short ratio at this bucket = shortTraders / tradersWithPosition. Decimal 0~1."
7270
7304
  },
7271
7305
  weightedLongRatio: {
7272
7306
  type: "string",
7273
- description: "Notional-weighted long ratio at this bucket. Decimal 0~1."
7307
+ 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
7308
  },
7275
7309
  weightedShortRatio: {
7276
7310
  type: "string",
7277
- description: "Notional-weighted short ratio at this bucket. Decimal 0~1."
7311
+ 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
7312
  },
7279
7313
  longTraders: {
7280
7314
  type: "integer",
@@ -7290,11 +7324,11 @@ var SIGNAL_HISTORY_ITEM_PROPS = {
7290
7324
  },
7291
7325
  netNotionalUsdt: {
7292
7326
  type: "string",
7293
- description: "Net directional notional in USDT at this bucket = long notional \u2212 short notional."
7327
+ 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
7328
  },
7295
7329
  totalNotionalUsdt: {
7296
7330
  type: "string",
7297
- description: "Gross notional in USDT at this bucket = long notional + short notional. Tracks total capital deployed (rising = adding, falling = retreating)."
7331
+ 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
7332
  },
7299
7333
  tradersQualified: { type: "integer", description: "Pool size after applying tier filters (includes traders without a position)." },
7300
7334
  dataVersion: { type: "string", description: "Snapshot version key in `yyyyMMddHH` UTC (10-digit, e.g. `2026042820`)." }
@@ -7325,16 +7359,16 @@ function registerSmartmoneyTools() {
7325
7359
  properties: {
7326
7360
  updateTime: {
7327
7361
  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).'
7362
+ 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
7363
  },
7330
7364
  ...LEADERBOARD_POOL_FILTER_PROPS,
7331
7365
  after: {
7332
7366
  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."
7367
+ 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
7368
  },
7335
7369
  before: {
7336
7370
  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."
7371
+ 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
7372
  },
7339
7373
  limit: {
7340
7374
  type: "integer",
@@ -7343,7 +7377,8 @@ function registerSmartmoneyTools() {
7343
7377
  default: 10,
7344
7378
  description: "Max results per page (default 10, max 100)."
7345
7379
  }
7346
- }
7380
+ },
7381
+ required: ["sortBy", "period"]
7347
7382
  },
7348
7383
  handler: async (rawArgs, context) => {
7349
7384
  const args = asRecord(rawArgs);
@@ -7393,17 +7428,25 @@ function registerSmartmoneyTools() {
7393
7428
  minItems: 1,
7394
7429
  description: 'Trader IDs to look up, e.g. `["1001", "1002"]`. Required.'
7395
7430
  },
7431
+ sortBy: {
7432
+ type: "string",
7433
+ enum: ["pnl", "pnlRatio"],
7434
+ default: "pnl",
7435
+ description: 'Required. Result sort key. `pnl` = absolute USD profit; `pnlRatio` = percentage return. Default `"pnl"`.'
7436
+ },
7396
7437
  period: {
7397
7438
  type: "string",
7398
7439
  enum: PERIOD_DAYS,
7399
7440
  default: "90",
7400
- description: 'Performance lookback window in days. Pass as a quoted string: `"3"` / `"7"` / `"30"` / `"90"` (NOT integer). Default `"90"`.'
7441
+ description: 'Required. Performance lookback window in days. One of `"3"` / `"7"` / `"30"` / `"90"`. Default `"90"`.'
7401
7442
  }
7402
7443
  },
7403
- required: ["authorIds"]
7444
+ required: ["authorIds", "sortBy", "period"]
7404
7445
  },
7405
7446
  handler: async (rawArgs, context) => {
7406
7447
  const args = asRecord(rawArgs);
7448
+ assertEnum2(args, "sortBy", ["pnl", "pnlRatio"]);
7449
+ assertEnum2(args, "period", PERIOD_DAYS);
7407
7450
  const authorIds = readArrayAsCsv(args, "authorIds");
7408
7451
  if (!authorIds) {
7409
7452
  throw actionableError(
@@ -7415,7 +7458,10 @@ function registerSmartmoneyTools() {
7415
7458
  PATH_LEADERBOARD,
7416
7459
  compactObject({
7417
7460
  authorIds,
7418
- period: readString(args, "period")
7461
+ // Apply schema defaults explicitly so behavior does not depend on backend defaults
7462
+ // (CLI bypasses MCP `required` validation, so handler is the only deterministic layer).
7463
+ sortBy: readString(args, "sortBy") ?? "pnl",
7464
+ period: readString(args, "period") ?? "90"
7419
7465
  }),
7420
7466
  publicRateLimit("smartmoney_get_performance_by_trader", SMARTMONEY_RPS)
7421
7467
  );
@@ -7447,12 +7493,12 @@ function registerSmartmoneyTools() {
7447
7493
  },
7448
7494
  posSide: {
7449
7495
  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."
7496
+ 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
7497
  },
7452
7498
  direction: {
7453
7499
  type: "string",
7454
7500
  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.'
7501
+ 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
7502
  },
7457
7503
  posCcy: { type: "string", description: 'Position currency \u2014 the asset being held, e.g. "BTC".' },
7458
7504
  quoteCcy: { type: "string", description: 'Quote currency the position is priced/settled in, e.g. "USDT".' },
@@ -7600,11 +7646,11 @@ function registerSmartmoneyTools() {
7600
7646
  },
7601
7647
  after: {
7602
7648
  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)."
7649
+ description: 'Pagination cursor (older) \u2014 returns positions with `posId` smaller than this value. Pass the `posId` as a string, e.g. `"872913470357110787"`.'
7604
7650
  },
7605
7651
  before: {
7606
7652
  type: "string",
7607
- description: "Cursor: returns positions with `posId` greater than this value (newer \u2014 paginate forwards). Pass as quoted string."
7653
+ description: 'Pagination cursor (newer) \u2014 returns positions with `posId` greater than this value. Pass the `posId` as a string, e.g. `"872913470357110787"`.'
7608
7654
  },
7609
7655
  limit: {
7610
7656
  type: "integer",
@@ -7718,11 +7764,11 @@ function registerSmartmoneyTools() {
7718
7764
  },
7719
7765
  after: {
7720
7766
  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)."
7767
+ description: 'Pagination cursor (older) \u2014 returns trades with `ordId` smaller than this value. Pass the `ordId` as a string, e.g. `"872913470357110787"`.'
7722
7768
  },
7723
7769
  before: {
7724
7770
  type: "string",
7725
- description: "Cursor: returns trades with `ordId` greater than this value (newer \u2014 paginate forwards). Pass as quoted string."
7771
+ description: 'Pagination cursor (newer) \u2014 returns trades with `ordId` greater than this value. Pass the `ordId` as a string, e.g. `"872913470357110787"`.'
7726
7772
  },
7727
7773
  limit: {
7728
7774
  type: "integer",
@@ -7818,7 +7864,7 @@ function registerSmartmoneyTools() {
7818
7864
  {
7819
7865
  name: "smartmoney_get_signal_overview_by_filter",
7820
7866
  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).",
7867
+ 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
7868
  isWrite: false,
7823
7869
  outputSchema: envelope({
7824
7870
  type: "array",
@@ -7839,7 +7885,7 @@ function registerSmartmoneyTools() {
7839
7885
  type: "array",
7840
7886
  items: { type: "string" },
7841
7887
  minItems: 1,
7842
- description: 'Base currencies to aggregate, e.g. `["BTC", "ETH", "SOL"]`. Mutually exclusive with `topInstruments`.'
7888
+ 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
7889
  },
7844
7890
  ...SIGNAL_POOL_FILTER_PROPS,
7845
7891
  lmtNum: {
@@ -7849,7 +7895,8 @@ function registerSmartmoneyTools() {
7849
7895
  default: 100,
7850
7896
  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
7897
  }
7852
- }
7898
+ },
7899
+ required: ["sortBy", "period"]
7853
7900
  },
7854
7901
  handler: async (rawArgs, context) => {
7855
7902
  const args = asRecord(rawArgs);
@@ -7877,7 +7924,7 @@ function registerSmartmoneyTools() {
7877
7924
  {
7878
7925
  name: "smartmoney_get_signal_overview_by_trader",
7879
7926
  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).",
7927
+ 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
7928
  isWrite: false,
7882
7929
  outputSchema: envelope({
7883
7930
  type: "array",
@@ -7904,13 +7951,27 @@ function registerSmartmoneyTools() {
7904
7951
  type: "array",
7905
7952
  items: { type: "string" },
7906
7953
  minItems: 1,
7907
- description: 'Base currencies to aggregate, e.g. `["BTC", "ETH", "SOL"]`. Mutually exclusive with `topInstruments`.'
7954
+ 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.'
7955
+ },
7956
+ sortBy: {
7957
+ type: "string",
7958
+ enum: ["pnl", "pnlRatio"],
7959
+ default: "pnl",
7960
+ description: 'Required. Ranking key for the trader set. `pnl` = absolute USD profit; `pnlRatio` = percentage return. Default `"pnl"`.'
7961
+ },
7962
+ period: {
7963
+ type: "string",
7964
+ enum: PERIOD_DAYS,
7965
+ default: "7",
7966
+ 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
7967
  }
7909
7968
  },
7910
- required: ["authorIds"]
7969
+ required: ["authorIds", "sortBy", "period"]
7911
7970
  },
7912
7971
  handler: async (rawArgs, context) => {
7913
7972
  const args = asRecord(rawArgs);
7973
+ assertEnum2(args, "sortBy", ["pnl", "pnlRatio"]);
7974
+ assertEnum2(args, "period", PERIOD_DAYS);
7914
7975
  const authorIds = readArrayAsCsv(args, "authorIds");
7915
7976
  if (!authorIds) {
7916
7977
  throw actionableError(
@@ -7930,7 +7991,11 @@ function registerSmartmoneyTools() {
7930
7991
  PATH_OVERVIEW,
7931
7992
  compactObject({
7932
7993
  authorIds,
7933
- ...instCcyList ? { instCcyList } : { topInstruments: topInstrumentsRaw ?? 20 }
7994
+ ...instCcyList ? { instCcyList } : { topInstruments: topInstrumentsRaw ?? 20 },
7995
+ // Apply schema defaults explicitly so behavior does not depend on backend defaults
7996
+ // (CLI bypasses MCP `required` validation, so handler is the only deterministic layer).
7997
+ sortBy: readString(args, "sortBy") ?? "pnl",
7998
+ period: readString(args, "period") ?? "7"
7934
7999
  }),
7935
8000
  publicRateLimit("smartmoney_get_signal_overview_by_trader", SMARTMONEY_RPS)
7936
8001
  );
@@ -7941,7 +8006,7 @@ function registerSmartmoneyTools() {
7941
8006
  {
7942
8007
  name: "smartmoney_get_signal_trend_by_filter",
7943
8008
  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.",
8009
+ 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
8010
  isWrite: false,
7946
8011
  outputSchema: envelope({
7947
8012
  type: "array",
@@ -7953,11 +8018,11 @@ function registerSmartmoneyTools() {
7953
8018
  properties: {
7954
8019
  instCcy: {
7955
8020
  type: "string",
7956
- description: 'Base currency to scope the time-series, e.g. "BTC". Required.'
8021
+ 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
8022
  },
7958
8023
  asOfTime: {
7959
8024
  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.'
8025
+ 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
8026
  },
7962
8027
  granularity: {
7963
8028
  type: "string",
@@ -7981,10 +8046,11 @@ function registerSmartmoneyTools() {
7981
8046
  description: "Top-N traders to pull into the aggregation pool, ranked by `sortBy` (DESC). Default 100, max 2000."
7982
8047
  }
7983
8048
  },
7984
- required: ["instCcy"]
8049
+ required: ["instCcy", "granularity", "sortBy", "period"]
7985
8050
  },
7986
8051
  handler: async (rawArgs, context) => {
7987
8052
  const args = asRecord(rawArgs);
8053
+ assertEnum2(args, "granularity", ["1h", "1d"]);
7988
8054
  const instCcy = readString(args, "instCcy");
7989
8055
  if (!instCcy) {
7990
8056
  throw actionableError(
@@ -7997,7 +8063,8 @@ function registerSmartmoneyTools() {
7997
8063
  compactObject({
7998
8064
  instCcy,
7999
8065
  asOfTime: readString(args, "asOfTime"),
8000
- granularity: readString(args, "granularity"),
8066
+ // Apply schema default explicitly (CLI bypasses MCP `required` validation).
8067
+ granularity: readString(args, "granularity") ?? "1h",
8001
8068
  limit: readNumber(args, "limit"),
8002
8069
  ...readSignalPoolFilters(args),
8003
8070
  lmtNum: readNumber(args, "lmtNum")
@@ -8011,7 +8078,7 @@ function registerSmartmoneyTools() {
8011
8078
  {
8012
8079
  name: "smartmoney_get_signal_trend_by_trader",
8013
8080
  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.",
8081
+ 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
8082
  isWrite: false,
8016
8083
  outputSchema: envelope({
8017
8084
  type: "array",
@@ -8029,11 +8096,11 @@ function registerSmartmoneyTools() {
8029
8096
  },
8030
8097
  instCcy: {
8031
8098
  type: "string",
8032
- description: 'Base currency to scope the time-series, e.g. "BTC". Required.'
8099
+ 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
8100
  },
8034
8101
  asOfTime: {
8035
8102
  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.'
8103
+ 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
8104
  },
8038
8105
  granularity: {
8039
8106
  type: "string",
@@ -8047,12 +8114,27 @@ function registerSmartmoneyTools() {
8047
8114
  maximum: 500,
8048
8115
  default: 24,
8049
8116
  description: "Number of buckets to return (newest first), ending at `asOfTime`. Default 24, max 500."
8117
+ },
8118
+ sortBy: {
8119
+ type: "string",
8120
+ enum: ["pnl", "pnlRatio"],
8121
+ default: "pnl",
8122
+ description: 'Required. Ranking key for the trader set. `pnl` = absolute USD profit; `pnlRatio` = percentage return. Default `"pnl"`.'
8123
+ },
8124
+ period: {
8125
+ type: "string",
8126
+ enum: PERIOD_DAYS,
8127
+ default: "7",
8128
+ 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
8129
  }
8051
8130
  },
8052
- required: ["authorIds", "instCcy"]
8131
+ required: ["authorIds", "instCcy", "granularity", "sortBy", "period"]
8053
8132
  },
8054
8133
  handler: async (rawArgs, context) => {
8055
8134
  const args = asRecord(rawArgs);
8135
+ assertEnum2(args, "granularity", ["1h", "1d"]);
8136
+ assertEnum2(args, "sortBy", ["pnl", "pnlRatio"]);
8137
+ assertEnum2(args, "period", PERIOD_DAYS);
8056
8138
  const authorIds = readArrayAsCsv(args, "authorIds");
8057
8139
  const instCcy = readString(args, "instCcy");
8058
8140
  if (!authorIds) {
@@ -8073,8 +8155,12 @@ function registerSmartmoneyTools() {
8073
8155
  authorIds,
8074
8156
  instCcy,
8075
8157
  asOfTime: readString(args, "asOfTime"),
8076
- granularity: readString(args, "granularity"),
8077
- limit: readNumber(args, "limit")
8158
+ // Apply schema defaults explicitly so behavior does not depend on backend defaults
8159
+ // (CLI bypasses MCP `required` validation, so handler is the only deterministic layer).
8160
+ granularity: readString(args, "granularity") ?? "1h",
8161
+ limit: readNumber(args, "limit"),
8162
+ sortBy: readString(args, "sortBy") ?? "pnl",
8163
+ period: readString(args, "period") ?? "7"
8078
8164
  }),
8079
8165
  publicRateLimit("smartmoney_get_signal_trend_by_trader", SMARTMONEY_RPS)
8080
8166
  );
@@ -11925,6 +12011,78 @@ function runSetup(options) {
11925
12011
  `);
11926
12012
  }
11927
12013
  }
12014
+ function isRedirect(statusCode) {
12015
+ return statusCode !== void 0 && statusCode >= 300 && statusCode < 400;
12016
+ }
12017
+ function validateRedirect(res, requestUrl, redirectCount, maxRedirects) {
12018
+ if (redirectCount > maxRedirects) {
12019
+ throw new Error(`Too many redirects (${maxRedirects})`);
12020
+ }
12021
+ const location = res.headers.location;
12022
+ if (requestUrl.startsWith("https") && !location.startsWith("https")) {
12023
+ throw new Error("Refused HTTPS \u2192 HTTP redirect downgrade");
12024
+ }
12025
+ return location;
12026
+ }
12027
+ function fetchResponse(url, timeoutMs) {
12028
+ return new Promise((resolve3, reject) => {
12029
+ let redirects = 0;
12030
+ const maxRedirects = 5;
12031
+ function doRequest(requestUrl) {
12032
+ const reqFn = requestUrl.startsWith("https") ? httpsGet : httpGet;
12033
+ const req = reqFn(requestUrl, { timeout: timeoutMs }, (res) => {
12034
+ if (isRedirect(res.statusCode) && res.headers.location) {
12035
+ redirects++;
12036
+ try {
12037
+ const location = validateRedirect(res, requestUrl, redirects, maxRedirects);
12038
+ res.resume();
12039
+ doRequest(location);
12040
+ } catch (err) {
12041
+ reject(err);
12042
+ }
12043
+ return;
12044
+ }
12045
+ if (res.statusCode !== 200) {
12046
+ reject(new Error(`HTTP ${res.statusCode ?? "unknown"}`));
12047
+ return;
12048
+ }
12049
+ resolve3(res);
12050
+ });
12051
+ req.on("error", reject);
12052
+ req.on("timeout", () => {
12053
+ req.destroy();
12054
+ reject(new Error("Download timed out"));
12055
+ });
12056
+ }
12057
+ doRequest(url);
12058
+ });
12059
+ }
12060
+ function download(url, destPath, timeoutMs) {
12061
+ return fetchResponse(url, timeoutMs).then(
12062
+ (res) => new Promise((resolve3, reject) => {
12063
+ const file = createWriteStream2(destPath);
12064
+ res.pipe(file);
12065
+ file.on("finish", () => file.close(() => resolve3()));
12066
+ file.on("error", (err) => {
12067
+ try {
12068
+ unlinkSync3(destPath);
12069
+ } catch {
12070
+ }
12071
+ reject(err);
12072
+ });
12073
+ })
12074
+ );
12075
+ }
12076
+ function downloadText(url, timeoutMs) {
12077
+ return fetchResponse(url, timeoutMs).then(
12078
+ (res) => new Promise((resolve3, reject) => {
12079
+ const chunks = [];
12080
+ res.on("data", (chunk) => chunks.push(chunk));
12081
+ res.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
12082
+ res.on("error", reject);
12083
+ })
12084
+ );
12085
+ }
11928
12086
  var CDN_SOURCES = [
11929
12087
  { host: "static.jingyunyilian.com", protocol: "https" },
11930
12088
  { host: "static.okx.com", protocol: "https" },
@@ -12038,7 +12196,7 @@ async function downloadAndVerify(host, protocol, binaryPath, tmpPath, checksum,
12038
12196
  function atomicReplace(tmpPath, resolvedDest) {
12039
12197
  if (platform() === "win32") {
12040
12198
  try {
12041
- unlinkSync3(resolvedDest);
12199
+ unlinkSync4(resolvedDest);
12042
12200
  } catch {
12043
12201
  }
12044
12202
  }
@@ -12094,7 +12252,7 @@ async function installPilotBinary(destPath, sources = CDN_SOURCES, onProgress) {
12094
12252
  return { status: "installed", source: host };
12095
12253
  } catch (err) {
12096
12254
  try {
12097
- unlinkSync3(tmpPath);
12255
+ unlinkSync4(tmpPath);
12098
12256
  } catch {
12099
12257
  }
12100
12258
  const msg = err instanceof Error ? err.message : String(err);
@@ -12108,7 +12266,7 @@ ${errors.join("\n")}` };
12108
12266
  function removePilotBinary(binaryPath) {
12109
12267
  const resolvedPath = binaryPath ?? getPilotBinaryPath();
12110
12268
  try {
12111
- unlinkSync3(resolvedPath);
12269
+ unlinkSync4(resolvedPath);
12112
12270
  return { status: "removed" };
12113
12271
  } catch (err) {
12114
12272
  if (err.code === "ENOENT") {
@@ -12118,78 +12276,6 @@ function removePilotBinary(binaryPath) {
12118
12276
  throw new Error(`Failed to remove ${resolvedPath}: ${msg}`);
12119
12277
  }
12120
12278
  }
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
12279
  var AUTH_CDN_PATH_PREFIX = "/upgradeapp/tools/oauth";
12194
12280
  function getAuthBinaryName() {
12195
12281
  return platform2() === "win32" ? "okx-auth.exe" : "okx-auth";
@@ -12213,7 +12299,7 @@ async function fetchAuthCdnChecksum(sources = CDN_SOURCES, timeoutMs = DOWNLOAD_
12213
12299
  for (const { host, protocol } of sources) {
12214
12300
  try {
12215
12301
  const url = `${protocol}://${host}${checksumPath}`;
12216
- const raw = await downloadText2(url, timeoutMs);
12302
+ const raw = await downloadText(url, timeoutMs);
12217
12303
  const data = JSON.parse(raw);
12218
12304
  if (typeof data.sha256 !== "string" || typeof data.size !== "number" || typeof data.target !== "string") {
12219
12305
  continue;
@@ -12227,7 +12313,7 @@ async function fetchAuthCdnChecksum(sources = CDN_SOURCES, timeoutMs = DOWNLOAD_
12227
12313
  async function fetchAndValidateChecksum2(host, protocol, checksumPath, platformDir, timeoutMs, onProgress) {
12228
12314
  const checksumUrl = `${protocol}://${host}${checksumPath}`;
12229
12315
  onProgress?.(`Fetching checksum from ${host}...`);
12230
- const raw = await downloadText2(checksumUrl, timeoutMs);
12316
+ const raw = await downloadText(checksumUrl, timeoutMs);
12231
12317
  const checksum = JSON.parse(raw);
12232
12318
  if (typeof checksum.sha256 !== "string" || typeof checksum.size !== "number" || typeof checksum.target !== "string") {
12233
12319
  throw new Error("Invalid checksum.json: missing sha256, size, or target");
@@ -12240,7 +12326,7 @@ async function fetchAndValidateChecksum2(host, protocol, checksumPath, platformD
12240
12326
  async function downloadAndVerify2(host, protocol, binaryPath, tmpPath, checksum, timeoutMs, onProgress) {
12241
12327
  const binaryUrl = `${protocol}://${host}${binaryPath}`;
12242
12328
  onProgress?.(`Downloading binary from ${host}...`);
12243
- await download2(binaryUrl, tmpPath, timeoutMs);
12329
+ await download(binaryUrl, tmpPath, timeoutMs);
12244
12330
  const actual = hashFile(tmpPath);
12245
12331
  if (actual.size !== checksum.size) {
12246
12332
  throw new Error(`Size mismatch: expected ${checksum.size}, got ${actual.size}`);
@@ -12252,7 +12338,7 @@ async function downloadAndVerify2(host, protocol, binaryPath, tmpPath, checksum,
12252
12338
  function atomicReplace2(tmpPath, resolvedDest) {
12253
12339
  if (platform2() === "win32") {
12254
12340
  try {
12255
- unlinkSync4(resolvedDest);
12341
+ unlinkSync5(resolvedDest);
12256
12342
  } catch {
12257
12343
  }
12258
12344
  }
@@ -12308,7 +12394,7 @@ async function installAuthBinary(destPath, sources = CDN_SOURCES, onProgress) {
12308
12394
  return { status: "installed", source: host };
12309
12395
  } catch (err) {
12310
12396
  try {
12311
- unlinkSync4(tmpPath);
12397
+ unlinkSync5(tmpPath);
12312
12398
  } catch {
12313
12399
  }
12314
12400
  const msg = err instanceof Error ? err.message : String(err);
@@ -12322,7 +12408,7 @@ ${errors.join("\n")}` };
12322
12408
  function removeAuthBinary(binaryPath) {
12323
12409
  const resolvedPath = binaryPath ?? getAuthBinaryPath();
12324
12410
  try {
12325
- unlinkSync4(resolvedPath);
12411
+ unlinkSync5(resolvedPath);
12326
12412
  return { status: "removed" };
12327
12413
  } catch (err) {
12328
12414
  if (err.code === "ENOENT") {
@@ -12332,78 +12418,6 @@ function removeAuthBinary(binaryPath) {
12332
12418
  throw new Error(`Failed to remove ${resolvedPath}: ${msg}`);
12333
12419
  }
12334
12420
  }
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
12421
  var CACHE_PATH = join12(homedir10(), ".okx", "auth-binary-check.json");
12408
12422
  var CHECK_INTERVAL_MS2 = 2 * 60 * 60 * 1e3;
12409
12423
  function readCache3() {
@@ -12452,7 +12466,7 @@ function updateAuthBinaryCache(sha256) {
12452
12466
  }
12453
12467
  function clearAuthBinaryCache() {
12454
12468
  try {
12455
- unlinkSync5(CACHE_PATH);
12469
+ unlinkSync6(CACHE_PATH);
12456
12470
  } catch {
12457
12471
  }
12458
12472
  }
@@ -13368,7 +13382,7 @@ async function cmdDiagnoseMcp(options = {}) {
13368
13382
 
13369
13383
  // src/commands/diagnose.ts
13370
13384
  var CLI_VERSION = readCliVersion();
13371
- var GIT_HASH = true ? "d0f8ad6" : "dev";
13385
+ var GIT_HASH = true ? "e8a0930a" : "dev";
13372
13386
  function maskKey2(key) {
13373
13387
  if (!key) return "(not set)";
13374
13388
  if (key.length <= 8) return "****";
@@ -14676,12 +14690,12 @@ var CLI_REGISTRY = {
14676
14690
  commands: {
14677
14691
  "traders-by-filter": {
14678
14692
  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]",
14693
+ usage: "okx smartmoney traders-by-filter [--updateTime <yyyyMMddHHmmss 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
14694
  description: "Leaderboard of top smart-money traders, ranked and filtered by pool conditions"
14681
14695
  },
14682
14696
  "performance-by-trader": {
14683
14697
  toolName: "smartmoney_get_performance_by_trader",
14684
- usage: "okx smartmoney performance-by-trader --authorIds <id1,id2> [--period <3|7|30|90>] [--json]",
14698
+ usage: "okx smartmoney performance-by-trader --authorIds <id1,id2> [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 90>] [--json]",
14685
14699
  description: "PnL / win-rate / drawdown profile for one or more traders by authorIds"
14686
14700
  },
14687
14701
  "trader-positions": {
@@ -14691,12 +14705,12 @@ var CLI_REGISTRY = {
14691
14705
  },
14692
14706
  "trader-positions-history": {
14693
14707
  toolName: "smartmoney_get_trader_positions_history",
14694
- usage: "okx smartmoney trader-positions-history --authorId <id> [--instId <id>] [--after <posId>] [--before <posId>] [--limit <n>] [--json]",
14708
+ usage: "okx smartmoney trader-positions-history --authorId <id> [--instId <id>] [--after <posId>] [--before <posId>] [--limit <n, default 10>] [--json]",
14695
14709
  description: "Closed-position history of a single trader (paginated)"
14696
14710
  },
14697
14711
  "trader-orders-history": {
14698
14712
  toolName: "smartmoney_get_trader_orders_history",
14699
- usage: "okx smartmoney trader-orders-history --authorId <id> [--instId <id>] [--after <ordId>] [--before <ordId>] [--limit <n>] [--json]",
14713
+ usage: "okx smartmoney trader-orders-history --authorId <id> [--instId <id>] [--after <ordId>] [--before <ordId>] [--limit <n, default 10>] [--json]",
14700
14714
  description: "Recent orders/fills placed by a single trader (paginated)"
14701
14715
  },
14702
14716
  "search-trader": {
@@ -14706,22 +14720,22 @@ var CLI_REGISTRY = {
14706
14720
  },
14707
14721
  "signal-overview-by-filter": {
14708
14722
  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]",
14723
+ 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
14724
  description: "Multi-asset smart-money consensus signal aggregated over a tier-filtered pool"
14711
14725
  },
14712
14726
  "signal-overview-by-trader": {
14713
14727
  toolName: "smartmoney_get_signal_overview_by_trader",
14714
- usage: "okx smartmoney signal-overview-by-trader --authorIds <id1,id2> [--topInstruments <n> | --instCcyList <BTC,ETH,...>] [--json]",
14728
+ 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
14729
  description: "Multi-asset smart-money signal aggregated over a hand-picked set of traders (authorIds-direct-lookup; pool filters not exposed)"
14716
14730
  },
14717
14731
  "signal-trend-by-filter": {
14718
14732
  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]",
14733
+ 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
14734
  description: "Single-coin smart-money signal time-series aggregated over a tier-filtered pool, anchored at asOfTime"
14721
14735
  },
14722
14736
  "signal-trend-by-trader": {
14723
14737
  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]",
14738
+ 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
14739
  description: "Single-coin smart-money signal time-series aggregated over a hand-picked set of traders (authorIds-direct-lookup; pool filters not exposed)"
14726
14740
  }
14727
14741
  }
@@ -18140,6 +18154,7 @@ async function cmdSmartmoneyTradersByFilter(run, opts) {
18140
18154
  async function cmdSmartmoneyPerformanceByTrader(run, opts) {
18141
18155
  const result = await run("smartmoney_get_performance_by_trader", {
18142
18156
  authorIds: csvToArray(opts.authorIds),
18157
+ sortBy: opts.sortBy,
18143
18158
  period: opts.period
18144
18159
  });
18145
18160
  if (opts.json) {
@@ -18280,7 +18295,9 @@ async function cmdSmartmoneySignalOverviewByTrader(run, opts) {
18280
18295
  const result = await run("smartmoney_get_signal_overview_by_trader", {
18281
18296
  authorIds: csvToArray(opts.authorIds),
18282
18297
  topInstruments: opts.topInstruments,
18283
- instCcyList: csvToArray(opts.instCcyList)
18298
+ instCcyList: csvToArray(opts.instCcyList),
18299
+ sortBy: opts.sortBy,
18300
+ period: opts.period
18284
18301
  });
18285
18302
  if (opts.json) {
18286
18303
  printJson(result);
@@ -18325,7 +18342,9 @@ async function cmdSmartmoneySignalTrendByTrader(run, opts) {
18325
18342
  instCcy: opts.instCcy,
18326
18343
  asOfTime: opts.asOfTime,
18327
18344
  granularity: opts.granularity,
18328
- limit: opts.limit
18345
+ limit: opts.limit,
18346
+ sortBy: opts.sortBy,
18347
+ period: opts.period
18329
18348
  });
18330
18349
  if (opts.json) {
18331
18350
  printJson(result);
@@ -19849,7 +19868,7 @@ async function cmdEventCancel(run, opts) {
19849
19868
  // src/index.ts
19850
19869
  var _require3 = createRequire3(import.meta.url);
19851
19870
  var CLI_VERSION2 = _require3("../package.json").version;
19852
- var GIT_HASH2 = true ? "d0f8ad6" : "dev";
19871
+ var GIT_HASH2 = true ? "e8a0930a" : "dev";
19853
19872
  function handlePilotCommand(action, json, force, binaryPath) {
19854
19873
  if (action === "status") return cmdPilotStatus(json, binaryPath);
19855
19874
  if (action === "install") return cmdPilotInstall(json, binaryPath);
@@ -20887,6 +20906,7 @@ function handleSmartmoneyCommand(run, action, rest, v, json) {
20887
20906
  }
20888
20907
  return cmdSmartmoneyPerformanceByTrader(run, {
20889
20908
  authorIds: v.authorIds,
20909
+ sortBy: v.sortBy,
20890
20910
  period: v.period,
20891
20911
  json
20892
20912
  });
@@ -20977,6 +20997,8 @@ function handleSmartmoneyCommand(run, action, rest, v, json) {
20977
20997
  authorIds: v.authorIds,
20978
20998
  topInstruments: v.topInstruments,
20979
20999
  instCcyList: v.instCcyList,
21000
+ sortBy: v.sortBy,
21001
+ period: v.period,
20980
21002
  json
20981
21003
  });
20982
21004
  }
@@ -21013,6 +21035,8 @@ function handleSmartmoneyCommand(run, action, rest, v, json) {
21013
21035
  asOfTime: v.asOfTime,
21014
21036
  granularity: v.granularity,
21015
21037
  limit: v.limit,
21038
+ sortBy: v.sortBy,
21039
+ period: v.period,
21016
21040
  json
21017
21041
  });
21018
21042
  }
@@ -21342,6 +21366,7 @@ async function main() {
21342
21366
  main().catch((error) => {
21343
21367
  const payload = toToolErrorPayload(error);
21344
21368
  errorLine(`Error: ${payload.message}`);
21369
+ if (payload.code) errorLine(`Code: ${payload.code}`);
21345
21370
  if (payload.traceId) errorLine(`TraceId: ${payload.traceId}`);
21346
21371
  if (payload.suggestion) errorLine(`Hint: ${payload.suggestion}`);
21347
21372
  errorLine(`Version: @okx_ai/okx-trade-cli@${CLI_VERSION2}`);