@okx_ai/okx-trade-cli 1.3.4 → 1.3.5-beta.1

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
@@ -1157,8 +1157,8 @@ var PilotManager = class {
1157
1157
  * Apply a Pilot node: set up the custom Agent + base URL.
1158
1158
  *
1159
1159
  * node.ip may be a real IP or a domain (CNAME like *.aliyunddos1021.com).
1160
- * - Real IP use directly in lookup callback
1161
- * - Domain dns.lookup on every connection to get a fresh IP
1160
+ * - Real IP -> use directly in lookup callback
1161
+ * - Domain -> dns.lookup on every connection to get a fresh IP
1162
1162
  */
1163
1163
  applyNode(node, protocol) {
1164
1164
  this.pilotNode = node;
@@ -1508,29 +1508,29 @@ var RateLimiter = class {
1508
1508
  }
1509
1509
  };
1510
1510
  var OKX_CODE_BEHAVIORS = {
1511
- // Rate limit throw RateLimitError
1511
+ // Rate limit -> throw RateLimitError
1512
1512
  "50011": { retry: true, suggestion: "Rate limited. Back off and retry after a delay." },
1513
1513
  "50061": { retry: true, suggestion: "Too many connections. Reduce request frequency and retry." },
1514
- // Server temporarily unavailable retryable
1514
+ // Server temporarily unavailable -> retryable
1515
1515
  "50001": { retry: true, suggestion: "Service temporarily unavailable. Retry in a few minutes." },
1516
1516
  "50004": { retry: true, suggestion: "Endpoint request timeout. Retry later." },
1517
1517
  "50013": { retry: true, suggestion: "System busy. Retry after 1-2 seconds." },
1518
1518
  "50026": { retry: true, suggestion: "System error. Retry in a few minutes." },
1519
- // Region / compliance restriction do not retry
1519
+ // Region / compliance restriction -> do not retry
1520
1520
  "51155": { retry: false, suggestion: "Feature unavailable in your region (site: {site}). Verify your site setting matches your account registration region. Available sites: global, eea, us. Do not retry." },
1521
1521
  "51734": { retry: false, suggestion: "Feature not supported for your KYC country (site: {site}). Verify your site setting matches your account registration region. Available sites: global, eea, us. Do not retry." },
1522
- // Account issues do not retry
1522
+ // Account issues -> do not retry
1523
1523
  "50007": { retry: false, suggestion: "Account suspended. Contact OKX support. Do not retry." },
1524
1524
  "50009": { retry: false, suggestion: "Account blocked by risk control. Contact OKX support. Do not retry." },
1525
1525
  "51009": { retry: false, suggestion: "Account mode not supported for this operation. Check account settings." },
1526
- // API key permission / expiry do not retry
1526
+ // API key permission / expiry -> do not retry
1527
1527
  "50100": { retry: false, suggestion: "API key lacks required permissions. Update API key permissions." },
1528
1528
  "50110": { retry: false, suggestion: "API key expired. Generate a new API key." },
1529
- // Insufficient funds / margin do not retry
1530
- "51008": { retry: false, suggestion: "Insufficient balance in trading account. Check funding account via account_get_asset_balance \u2014 funds may be there. Use account_transfer (from=18, to=6) to move funds to trading account, then retry." },
1529
+ // Insufficient funds / margin -> do not retry
1530
+ "51008": { retry: false, suggestion: "Insufficient balance in trading account. Check funding account via account_get_asset_balance - funds may be there. Use account_transfer (from=18, to=6) to move funds to trading account, then retry." },
1531
1531
  "51119": { retry: false, suggestion: "Insufficient margin. Add margin or check funding account (account_get_asset_balance). Transfer via account_transfer (from=18, to=6) if needed." },
1532
1532
  "51127": { retry: false, suggestion: "Insufficient available margin. Reduce position, add margin, or transfer from funding account (account_transfer from=18 to=6)." },
1533
- // Instrument unavailable do not retry
1533
+ // Instrument unavailable -> do not retry
1534
1534
  "51021": { retry: false, suggestion: "Instrument does not exist. Check instId." },
1535
1535
  "51022": { retry: false, suggestion: "Instrument not available for trading." },
1536
1536
  "51027": { retry: false, suggestion: "Contract has expired." }
@@ -1614,11 +1614,11 @@ var OkxRestClient = class _OkxRestClient {
1614
1614
  }
1615
1615
  }
1616
1616
  /**
1617
- * Dynamic auth determines auth method per request.
1617
+ * Dynamic auth - determines auth method per request.
1618
1618
  *
1619
- * 1. API key in config HMAC signing (no OAuth fallback)
1620
- * 2. OAuth token via okx-auth binary Bearer token
1621
- * 3. Neither throw ConfigError
1619
+ * 1. API key in config -> HMAC signing (no OAuth fallback)
1620
+ * 2. OAuth token via okx-auth binary -> Bearer token
1621
+ * 3. Neither -> throw ConfigError
1622
1622
  */
1623
1623
  async applyAuth(headers, method, requestPath, bodyJson, timestamp) {
1624
1624
  if (this.config.apiKey && this.config.secretKey && this.config.passphrase) {
@@ -1772,7 +1772,7 @@ var OkxRestClient = class _OkxRestClient {
1772
1772
  };
1773
1773
  }
1774
1774
  // ---------------------------------------------------------------------------
1775
- // Binary (non-JSON) download reuses auth, proxy, rate-limit, verbose
1775
+ // Binary (non-JSON) download - reuses auth, proxy, rate-limit, verbose
1776
1776
  // ---------------------------------------------------------------------------
1777
1777
  static DEFAULT_MAX_BYTES = 50 * 1024 * 1024;
1778
1778
  // 50 MB
@@ -2384,7 +2384,7 @@ var INDICATOR_CODE_OVERRIDES = {
2384
2384
  // default: NVI_PVI
2385
2385
  "top-long-short": "TOPLONGSHORT"
2386
2386
  // default: TOP_LONG_SHORT
2387
- // Note: range-filter RANGE_FILTER is correct via the default rule; no override needed.
2387
+ // Note: range-filter -> RANGE_FILTER is correct via the default rule; no override needed.
2388
2388
  };
2389
2389
  var KNOWN_INDICATORS = [
2390
2390
  // Moving Averages
@@ -2633,7 +2633,7 @@ var MODULES = [
2633
2633
  "skills"
2634
2634
  ];
2635
2635
  var DEFAULT_MODULES = ["spot", "swap", "option", "account", ...BOT_DEFAULT_SUB_MODULES, "skills"];
2636
- var SKILLS_MARKETPLACE_DESC = "OKX Skills Marketplace \u2014 search, install, and manage agent skills";
2636
+ var SKILLS_MARKETPLACE_DESC = "OKX Skills Marketplace - search, install, and manage agent skills";
2637
2637
  var MODULE_DESCRIPTIONS = {
2638
2638
  market: "Market data (ticker, orderbook, candles, trades)",
2639
2639
  spot: "Spot trading (orders, algo orders)",
@@ -2641,18 +2641,18 @@ var MODULE_DESCRIPTIONS = {
2641
2641
  futures: "Futures trading (orders, positions, algo orders, leverage)",
2642
2642
  option: "Options trading (orders, positions, greeks)",
2643
2643
  account: "Account balance, positions, bills, and configuration",
2644
- "earn.savings": "Simple Earn \u2014 flexible savings, fixed-term, and lending",
2645
- "earn.onchain": "On-chain Earn \u2014 staking and DeFi products",
2646
- "earn.dcd": "DCD (Dual Currency Deposit) \u2014 structured products with fixed yield",
2647
- "earn.autoearn": "Auto-earn \u2014 automatically lend, stake, or earn on idle assets",
2648
- "earn.flash": "Flash Earn \u2014 short-window high-yield earn projects",
2649
- event: "Event contracts \u2014 binary prediction markets (YES/NO, UP/DOWN)",
2650
- "bot.grid": "Grid trading bot \u2014 create, monitor, and stop grid orders",
2651
- "bot.dca": "DCA (Martingale) bot \u2014 spot or contract recurring buys",
2644
+ "earn.savings": "Simple Earn - flexible savings, fixed-term, and lending",
2645
+ "earn.onchain": "On-chain Earn - staking and DeFi products",
2646
+ "earn.dcd": "DCD (Dual Currency Deposit) - structured products with fixed yield",
2647
+ "earn.autoearn": "Auto-earn - automatically lend, stake, or earn on idle assets",
2648
+ "earn.flash": "Flash Earn - short-window high-yield earn projects",
2649
+ event: "Event contracts - binary prediction markets (YES/NO, UP/DOWN)",
2650
+ "bot.grid": "Grid trading bot - create, monitor, and stop grid orders",
2651
+ "bot.dca": "DCA (Martingale) bot - spot or contract recurring buys",
2652
2652
  news: "Crypto news, sentiment analysis, and coin trend tracking",
2653
- smartmoney: "Smart money signals \u2014 trader leaderboard, consensus signals, and position analysis",
2653
+ smartmoney: "Smart money signals - trader leaderboard, consensus signals, and position analysis",
2654
2654
  skills: SKILLS_MARKETPLACE_DESC,
2655
- earn: "Earn products \u2014 Simple Earn, On-chain Earn, DCD, Flash Earn, and Auto-Earn",
2655
+ earn: "Earn products - Simple Earn, On-chain Earn, DCD, Flash Earn, and Auto-Earn",
2656
2656
  bot: "Trading bot strategies (grid, dca)",
2657
2657
  config: "Manage CLI configuration profiles",
2658
2658
  setup: "Set up client integrations (Cursor, Windsurf, Claude, etc.)",
@@ -2713,7 +2713,7 @@ function registerAccountTools() {
2713
2713
  },
2714
2714
  type: {
2715
2715
  type: "string",
2716
- description: "0=main account (default), 1=main\u2192sub, 2=sub\u2192main, 3=sub\u2192sub"
2716
+ description: "0=main account (default), 1=main->sub, 2=sub->main, 3=sub->sub"
2717
2717
  },
2718
2718
  subAcct: {
2719
2719
  type: "string",
@@ -3297,7 +3297,7 @@ function computeContracts(p) {
3297
3297
  );
3298
3298
  }
3299
3299
  const contractsStr = contractsRounded.toFixed(lotSzDecimals);
3300
- const conversionNote = isMarginMode ? `Converting ${sz} USDT margin (${leverStr}x leverage) \u2192 ${contractsStr} contracts (notional value \u2248 ${(contractsRounded * contractValue).toFixed(2)} USDT, ctVal=${ctValStr}, lastPx=${lastStr}, lever=${leverStr}, minSz=${minSzStr}, lotSz=${lotSzStr})` : `Converting ${sz} USDT \u2192 ${contractsStr} contracts (ctVal=${ctValStr}, lastPx=${lastStr}, minSz=${minSzStr}, lotSz=${lotSzStr})`;
3300
+ const conversionNote = isMarginMode ? `Converting ${sz} USDT margin (${leverStr}x leverage) -> ${contractsStr} contracts (notional value \u2248 ${(contractsRounded * contractValue).toFixed(2)} USDT, ctVal=${ctValStr}, lastPx=${lastStr}, lever=${leverStr}, minSz=${minSzStr}, lotSz=${lotSzStr})` : `Converting ${sz} USDT -> ${contractsStr} contracts (ctVal=${ctValStr}, lastPx=${lastStr}, minSz=${minSzStr}, lotSz=${lotSzStr})`;
3301
3301
  return { contractsStr, conversionNote };
3302
3302
  }
3303
3303
  async function resolveQuoteCcySz(instId, sz, tgtCcy, instType, client, tdMode) {
@@ -3345,7 +3345,7 @@ function registerAlgoTradeTools() {
3345
3345
  {
3346
3346
  name: "swap_place_algo_order",
3347
3347
  module: "swap",
3348
- description: "Place a SWAP/FUTURES algo order. [CAUTION] Executes real trades. conditional: single TP, single SL, or both on one order. oco: TP+SL simultaneously \u2014 first trigger cancels the other. move_order_stop: trailing stop (callbackRatio or callbackSpread). trigger: pending order activated when triggerPx is hit (provide triggerPx + orderPx). chase: smart-follow best bid/ask. iceberg: split large order into child orders at intervals. twap: time-weighted average price order splitting.",
3348
+ description: "Place a SWAP/FUTURES algo order. [CAUTION] Executes real trades. conditional: single TP, single SL, or both on one order. oco: TP+SL simultaneously - first trigger cancels the other. move_order_stop: trailing stop (callbackRatio or callbackSpread). trigger: pending order activated when triggerPx is hit (provide triggerPx + orderPx). chase: smart-follow best bid/ask. iceberg: split large order into child orders at intervals. twap: time-weighted average price order splitting.",
3349
3349
  isWrite: true,
3350
3350
  inputSchema: {
3351
3351
  type: "object",
@@ -3705,7 +3705,7 @@ function registerFuturesAlgoTools() {
3705
3705
  {
3706
3706
  name: "futures_place_algo_order",
3707
3707
  module: "futures",
3708
- description: "Place a FUTURES delivery algo order. [CAUTION] Executes real trades. conditional: single TP, single SL, or both on one order. oco: TP+SL simultaneously \u2014 first trigger cancels the other. move_order_stop: trailing stop (callbackRatio or callbackSpread). trigger: pending order activated when triggerPx is hit (provide triggerPx + orderPx). chase: smart-follow best bid/ask. iceberg: split large order into child orders at intervals. twap: time-weighted average price order splitting.",
3708
+ description: "Place a FUTURES delivery algo order. [CAUTION] Executes real trades. conditional: single TP, single SL, or both on one order. oco: TP+SL simultaneously - first trigger cancels the other. move_order_stop: trailing stop (callbackRatio or callbackSpread). trigger: pending order activated when triggerPx is hit (provide triggerPx + orderPx). chase: smart-follow best bid/ask. iceberg: split large order into child orders at intervals. twap: time-weighted average price order splitting.",
3709
3709
  isWrite: true,
3710
3710
  inputSchema: {
3711
3711
  type: "object",
@@ -3930,7 +3930,7 @@ function registerFuturesAlgoTools() {
3930
3930
  {
3931
3931
  name: "futures_amend_algo_order",
3932
3932
  module: "futures",
3933
- description: "Amend a pending FUTURES delivery algo order (modify TP/SL prices or size). Also covers TP/SL orders attached when placing the main order \u2014 look up algoId via futures_get_algo_orders first.",
3933
+ description: "Amend a pending FUTURES delivery algo order (modify TP/SL prices or size). Also covers TP/SL orders attached when placing the main order - look up algoId via futures_get_algo_orders first.",
3934
3934
  isWrite: true,
3935
3935
  inputSchema: {
3936
3936
  type: "object",
@@ -4383,7 +4383,7 @@ function registerSkillsTools() {
4383
4383
  {
4384
4384
  name: "skills_get_categories",
4385
4385
  module: "skills",
4386
- description: "List all available skill categories in OKX Skills Marketplace. Use the returned categoryId as input to skills_search for category filtering. Do NOT use for searching or downloading skills \u2014 use skills_search or skills_download.",
4386
+ description: "List all available skill categories in OKX Skills Marketplace. Use the returned categoryId as input to skills_search for category filtering. Do NOT use for searching or downloading skills - use skills_search or skills_download.",
4387
4387
  inputSchema: {
4388
4388
  type: "object",
4389
4389
  properties: {},
@@ -4395,7 +4395,7 @@ function registerSkillsTools() {
4395
4395
  {
4396
4396
  name: "skills_search",
4397
4397
  module: "skills",
4398
- description: "Search for skills in OKX Skills Marketplace by keyword or category. To get valid category IDs, call skills_get_categories first. Returns skill names for use with skills_download. Do NOT use for downloading \u2014 use skills_download.",
4398
+ description: "Search for skills in OKX Skills Marketplace by keyword or category. To get valid category IDs, call skills_get_categories first. Returns skill names for use with skills_download. Do NOT use for downloading - use skills_download.",
4399
4399
  inputSchema: {
4400
4400
  type: "object",
4401
4401
  properties: {
@@ -4424,7 +4424,7 @@ function registerSkillsTools() {
4424
4424
  {
4425
4425
  name: "skills_download",
4426
4426
  module: "skills",
4427
- description: "Download a skill package from OKX Skills Marketplace to a local directory. Always call skills_search first to confirm the skill name exists. Downloads the latest approved version. NOTE: Downloads third-party developer content \u2014 does NOT install to agents. For full installation use CLI: okx skill add <name>. Use when the user wants to inspect or manually install a skill package.",
4427
+ description: "Download a skill package from OKX Skills Marketplace to a local directory. Always call skills_search first to confirm the skill name exists. Downloads the latest approved version. NOTE: Downloads third-party developer content - does NOT install to agents. For full installation use CLI: okx skill add <name>. Use when the user wants to inspect or manually install a skill package.",
4428
4428
  inputSchema: {
4429
4429
  type: "object",
4430
4430
  properties: {
@@ -4705,7 +4705,7 @@ function registerGridTools() {
4705
4705
  {
4706
4706
  name: "grid_amend_order",
4707
4707
  module: "bot.grid",
4708
- description: "Amend a running grid bot. [CAUTION] Modifies a running bot. Use grid_list_orders to confirm the bot is running and obtain the algoId before calling.\nSupports two modes, which can be combined in a single call:\n\u2022 Price-range mode (maxPx+minPx+gridNum): change upper/lower price boundary and grid count. Contract grid: if new range requires more margin, pass topUpAmt; omit to auto-use the minimum required. Spot grid: topUpAmt is not supported.\n\u2022 TP/SL mode (instId + any of tpTriggerPx/slTriggerPx/tpRatio/slRatio): update take-profit and/or stop-loss. Pass '-1' to explicitly clear an existing TP or SL. tpTriggerPx/slTriggerPx are absolute prices; tpRatio/slRatio are profit ratios (e.g. '0.1' = 10%).\nWhen both sets of params are provided, both APIs are called sequentially.\nDo NOT use to create a new grid bot \u2014 use grid_create_order instead. Do NOT use to stop a grid bot \u2014 use grid_stop_order instead.",
4708
+ description: "Amend a running grid bot. [CAUTION] Modifies a running bot. Use grid_list_orders to confirm the bot is running and obtain the algoId before calling.\nSupports two modes, which can be combined in a single call:\n\u2022 Price-range mode (maxPx+minPx+gridNum): change upper/lower price boundary and grid count. Contract grid: if new range requires more margin, pass topUpAmt; omit to auto-use the minimum required. Spot grid: topUpAmt is not supported.\n\u2022 TP/SL mode (instId + any of tpTriggerPx/slTriggerPx/tpRatio/slRatio): update take-profit and/or stop-loss. Pass '-1' to explicitly clear an existing TP or SL. tpTriggerPx/slTriggerPx are absolute prices; tpRatio/slRatio are profit ratios (e.g. '0.1' = 10%).\nWhen both sets of params are provided, both APIs are called sequentially.\nDo NOT use to create a new grid bot - use grid_create_order instead. Do NOT use to stop a grid bot - use grid_stop_order instead.",
4709
4709
  isWrite: true,
4710
4710
  inputSchema: {
4711
4711
  type: "object",
@@ -4783,7 +4783,7 @@ function registerGridTools() {
4783
4783
  maxPx,
4784
4784
  minPx: requireString(args, "minPx"),
4785
4785
  gridNum: requireString(args, "gridNum"),
4786
- // API field is "topupAmount" (lowercase u) different from TP/SL mode's "topUpAmt"
4786
+ // API field is "topupAmount" (lowercase u) - different from TP/SL mode's "topUpAmt"
4787
4787
  // Contract grid only; omitting lets the API use the minimum required
4788
4788
  topupAmount: readString(args, "topUpAmt")
4789
4789
  }),
@@ -4804,7 +4804,7 @@ function registerGridTools() {
4804
4804
  tpRatio: readString(args, "tpRatio"),
4805
4805
  slRatio: readString(args, "slRatio"),
4806
4806
  topUpAmt: readString(args, "topUpAmt")
4807
- // API field is "topUpAmt" (uppercase U) different from price-range mode's "topupAmount"
4807
+ // API field is "topUpAmt" (uppercase U) - different from price-range mode's "topupAmount"
4808
4808
  }),
4809
4809
  privateRateLimit("grid_amend_order", 20),
4810
4810
  true
@@ -4828,7 +4828,7 @@ function registerGridTools() {
4828
4828
  {
4829
4829
  name: "grid_stop_order",
4830
4830
  module: "bot.grid",
4831
- description: "Stop a running grid bot. [CAUTION] This stops the strategy and handles open orders/positions according to stopType. Default (stopType='1') closes all positions immediately \u2014 use this for a clean exit. stopType='2' stops the strategy without selling: spot grid keeps all base assets as-is (no sell-back to quote); contract grid cancels all grid orders but leaves the position open for manual close later.",
4831
+ description: "[CAUTION] Stop a grid bot or close its remaining open position \u2014 real trades, irreversible. Workflow: (1) If the user has not specified which bot to stop, call grid_get_orders first and ask the user to confirm which bot before proceeding. (2) Call grid_get_order_details to check the current 'state' field. (3) If state='running' \u2192 call this tool: stopType='1' (default, clean exit) \u2014 spot grid sells all base assets back to quote; contract grid market-closes all positions. stopType='2' (keep assets) \u2014 spot grid keeps base assets as-is; contract grid cancels grid orders but leaves the position open. (4) If state='no_close_position' \u2192 call this tool with stopType='1' to close the remaining open position.",
4832
4832
  isWrite: true,
4833
4833
  inputSchema: {
4834
4834
  type: "object",
@@ -4843,7 +4843,7 @@ function registerGridTools() {
4843
4843
  stopType: {
4844
4844
  type: "string",
4845
4845
  enum: ["1", "2"],
4846
- description: "'1' (default): stop strategy and sell \u2014 spot grid sells all base assets back to quote; contract grid market-closes all positions. '2': stop strategy without selling \u2014 spot grid keeps base assets as-is; contract grid cancels all grid orders but leaves the position open. After stopType='2', the remaining position can be closed manually from the Positions page."
4846
+ description: "'1' (default): stop strategy and sell - spot grid sells all base assets back to quote; contract grid market-closes all positions. '2': stop strategy without selling - spot grid keeps base assets as-is; contract grid cancels all grid orders but leaves the position open. After stopType='2', the remaining position can be closed manually from the Positions page."
4847
4847
  }
4848
4848
  },
4849
4849
  required: ["algoId", "algoOrdType", "instId"]
@@ -4860,7 +4860,7 @@ function registerGridTools() {
4860
4860
  })],
4861
4861
  privateRateLimit("grid_stop_order", 20),
4862
4862
  true
4863
- // retryOnNetworkError: safe to retry already-stopped returns an error but does not harm state
4863
+ // retryOnNetworkError: safe to retry - already-stopped returns an error but does not harm state
4864
4864
  );
4865
4865
  return normalizeWrite(response);
4866
4866
  }
@@ -5006,7 +5006,7 @@ function registerDcaTools() {
5006
5006
  {
5007
5007
  name: "dca_stop_order",
5008
5008
  module: "bot.dca",
5009
- description: "Stop a running DCA bot. [CAUTION] spot_dca needs stopType: 1=sell, 2=keep.",
5009
+ description: "[CAUTION] Stop a DCA bot or close its remaining open position \u2014 real trades, irreversible. Workflow: (1) If the user has not specified which bot to stop, call dca_get_orders first and ask the user to confirm which bot before proceeding. (2) Call dca_get_order_details to check the current 'state' field. (3) If state='running' \u2192 call this tool. (4) If state='no_close_position' \u2192 call this tool with stopType='1' to close the remaining open position. spot_dca requires stopType: 1=sell all tokens, 2=keep tokens.",
5010
5010
  isWrite: true,
5011
5011
  inputSchema: {
5012
5012
  type: "object",
@@ -5161,7 +5161,7 @@ function registerEarnTools() {
5161
5161
  {
5162
5162
  name: "earn_get_savings_balance",
5163
5163
  module: "earn.savings",
5164
- description: "Get Simple Earn (savings/flexible earn) balance. Returns current holdings for all currencies or a specific one. To show market rates alongside balance (\u5E02\u573A\u5747\u5229\u7387), call earn_get_lending_rate_history. earn_get_lending_rate_history also returns fixed-term (\u5B9A\u671F) product offers, so one call gives a complete view of both flexible and fixed options. Do NOT use for fixed-term (\u5B9A\u671F) order queries \u2014 use earn_get_fixed_order_list instead.",
5164
+ description: "Get Simple Earn (savings/flexible earn) balance. Returns current holdings for all currencies or a specific one. To show market rates alongside balance (\u5E02\u573A\u5747\u5229\u7387), call earn_get_lending_rate_history. earn_get_lending_rate_history also returns fixed-term (\u5B9A\u671F) product offers, so one call gives a complete view of both flexible and fixed options. Do NOT use for fixed-term (\u5B9A\u671F) order queries - use earn_get_fixed_order_list instead.",
5165
5165
  isWrite: false,
5166
5166
  inputSchema: {
5167
5167
  type: "object",
@@ -5185,7 +5185,7 @@ function registerEarnTools() {
5185
5185
  {
5186
5186
  name: "earn_get_fixed_order_list",
5187
5187
  module: "earn.savings",
5188
- description: "Get Simple Earn Fixed (\u5B9A\u671F\u8D5A\u5E01) lending order list. Returns orders sorted by creation time descending. Use this to check status of fixed-term lending orders (pending/earning/expired/settled/cancelled). Do NOT use for flexible earn balance \u2014 use earn_get_savings_balance instead. If the result is empty, do NOT display any fixed-term section in the output.",
5188
+ description: "Get Simple Earn Fixed (\u5B9A\u671F\u8D5A\u5E01) lending order list. Returns orders sorted by creation time descending. Use this to check status of fixed-term lending orders (pending/earning/expired/settled/cancelled). Do NOT use for flexible earn balance - use earn_get_savings_balance instead. If the result is empty, do NOT display any fixed-term section in the output.",
5189
5189
  isWrite: false,
5190
5190
  inputSchema: {
5191
5191
  type: "object",
@@ -5237,7 +5237,7 @@ function registerEarnTools() {
5237
5237
  },
5238
5238
  rate: {
5239
5239
  type: "string",
5240
- description: "Minimum lending rate threshold (annual, decimal). e.g. 0.01 = 1%. Only matched when market rate \u2265 this value. Defaults to 0.01. Keep at 0.01 to maximize matching probability \u2014 do NOT raise this to increase yield, as actual yield (lendingRate) is determined by market supply/demand, not this setting."
5240
+ description: "Minimum lending rate threshold (annual, decimal). e.g. 0.01 = 1%. Only matched when market rate \u2265 this value. Defaults to 0.01. Keep at 0.01 to maximize matching probability - do NOT raise this to increase yield, as actual yield (lendingRate) is determined by market supply/demand, not this setting."
5241
5241
  }
5242
5242
  },
5243
5243
  required: ["ccy", "amt"]
@@ -5304,7 +5304,7 @@ function registerEarnTools() {
5304
5304
  },
5305
5305
  rate: {
5306
5306
  type: "string",
5307
- description: "Minimum lending rate threshold (annual, decimal). e.g. 0.01 = 1%. Only matched when market rate \u2265 this value. Keep at 0.01 to maximize matching probability \u2014 do NOT raise this to increase yield, as actual yield (lendingRate) is determined by market supply/demand, not this setting."
5307
+ description: "Minimum lending rate threshold (annual, decimal). e.g. 0.01 = 1%. Only matched when market rate \u2265 this value. Keep at 0.01 to maximize matching probability - do NOT raise this to increase yield, as actual yield (lendingRate) is determined by market supply/demand, not this setting."
5308
5308
  }
5309
5309
  },
5310
5310
  required: ["ccy", "rate"]
@@ -5366,7 +5366,7 @@ function registerEarnTools() {
5366
5366
  {
5367
5367
  name: "earn_fixed_purchase",
5368
5368
  module: "earn.savings",
5369
- description: "Purchase Simple Earn Fixed (\u5B9A\u671F) product, two-step flow. First call (confirm omitted or false): returns purchase preview with product details and risk warning. Preview offer fields: lendQuota = remaining quota (\u5269\u4F59\u989D\u5EA6), soldOut = whether product is sold out (lendQuota is 0). YOU MUST display the 'warning' field from the preview response to the user VERBATIM before asking for confirmation \u2014 do NOT omit or summarize it. Second call (confirm=true): executes the purchase. Only proceed after the user explicitly confirms. IMPORTANT: Orders in 'pending' (\u5339\u914D\u4E2D) state can still be cancelled via earn_fixed_redeem; once the status changes to 'earning' (\u8D5A\u5E01\u4E2D), funds are LOCKED until maturity \u2014 no early redemption allowed.",
5369
+ description: "Purchase Simple Earn Fixed (\u5B9A\u671F) product, two-step flow. First call (confirm omitted or false): returns purchase preview with product details and risk warning. Preview offer fields: lendQuota = remaining quota (\u5269\u4F59\u989D\u5EA6), soldOut = whether product is sold out (lendQuota is 0). YOU MUST display the 'warning' field from the preview response to the user VERBATIM before asking for confirmation - do NOT omit or summarize it. Second call (confirm=true): executes the purchase. Only proceed after the user explicitly confirms. IMPORTANT: Orders in 'pending' (\u5339\u914D\u4E2D) state can still be cancelled via earn_fixed_redeem; once the status changes to 'earning' (\u8D5A\u5E01\u4E2D), funds are LOCKED until maturity - no early redemption allowed.",
5370
5370
  isWrite: true,
5371
5371
  inputSchema: {
5372
5372
  type: "object",
@@ -5425,7 +5425,7 @@ function registerEarnTools() {
5425
5425
  term,
5426
5426
  offer: offerWithSoldOut,
5427
5427
  currentFlexibleRate: rateArr[0]?.["lendingRate"] ?? null,
5428
- warning: "\u26A0\uFE0F Orders still in 'pending' state can be cancelled before matching completes. Once the status changes to 'earning', funds are LOCKED until maturity \u2014 early redemption is NOT allowed. Please call again with confirm=true to proceed."
5428
+ warning: "\u26A0\uFE0F Orders still in 'pending' state can be cancelled before matching completes. Once the status changes to 'earning', funds are LOCKED until maturity - early redemption is NOT allowed. Please call again with confirm=true to proceed."
5429
5429
  };
5430
5430
  }
5431
5431
  assertNotDemo(context.config, "earn_fixed_purchase");
@@ -5440,7 +5440,7 @@ function registerEarnTools() {
5440
5440
  {
5441
5441
  name: "earn_fixed_redeem",
5442
5442
  module: "earn.savings",
5443
- description: "Redeem Simple Earn Fixed (\u5B9A\u671F\u8D5A\u5E01) order. [CAUTION] Redeems a fixed-term lending order. Always redeems the full order amount. Only orders in 'pending' (\u5339\u914D\u4E2D) state can be redeemed \u2014 orders in 'earning' state are locked until maturity and cannot be redeemed early. Do NOT use for flexible earn redemption \u2014 use earn_savings_redeem instead.",
5443
+ description: "Redeem Simple Earn Fixed (\u5B9A\u671F\u8D5A\u5E01) order. [CAUTION] Redeems a fixed-term lending order. Always redeems the full order amount. Only orders in 'pending' (\u5339\u914D\u4E2D) state can be redeemed - orders in 'earning' state are locked until maturity and cannot be redeemed early. Do NOT use for flexible earn redemption - use earn_savings_redeem instead.",
5444
5444
  isWrite: true,
5445
5445
  inputSchema: {
5446
5446
  type: "object",
@@ -5468,7 +5468,7 @@ function registerEarnTools() {
5468
5468
  {
5469
5469
  name: "earn_get_lending_rate_history",
5470
5470
  module: "earn.savings",
5471
- description: "Query Simple Earn lending rates and fixed-term offers. Use this tool when the user asks about Simple Earn products, current or historical lending rates, or when displaying savings balance with market rate context (\u5E02\u573A\u5747\u5229\u7387). Returns lending rate history (lendingRate field, newest-first) AND available fixed-term (\u5B9A\u671F) offers with APR, term, min amount, and quota \u2014 one call gives a complete view of both flexible and fixed options. In fixedOffers: lendQuota = remaining quota (\u5269\u4F59\u989D\u5EA6), soldOut = whether product is sold out (lendQuota is 0). To get current flexible APY: use limit=1 and read lendingRate.",
5471
+ description: "Query Simple Earn lending rates and fixed-term offers. Use this tool when the user asks about Simple Earn products, current or historical lending rates, or when displaying savings balance with market rate context (\u5E02\u573A\u5747\u5229\u7387). Returns lending rate history (lendingRate field, newest-first) AND available fixed-term (\u5B9A\u671F) offers with APR, term, min amount, and quota - one call gives a complete view of both flexible and fixed options. In fixedOffers: lendQuota = remaining quota (\u5269\u4F59\u989D\u5EA6), soldOut = whether product is sold out (lendQuota is 0). To get current flexible APY: use limit=1 and read lendingRate.",
5472
5472
  isWrite: false,
5473
5473
  inputSchema: {
5474
5474
  type: "object",
@@ -5801,7 +5801,7 @@ function registerOnchainEarnTools() {
5801
5801
  }
5802
5802
  var DCD_CODE_BEHAVIORS = {
5803
5803
  "50001": { retry: true, suggestion: "Service temporarily unavailable. Retry in a few minutes." },
5804
- "50002": { retry: false, suggestion: "Invalid JSON in request body. This is likely a bug \u2014 check request parameters." },
5804
+ "50002": { retry: false, suggestion: "Invalid JSON in request body. This is likely a bug - check request parameters." },
5805
5805
  "50014": { retry: false, suggestion: "Missing required parameter. Check that all required fields are provided." },
5806
5806
  "50016": { retry: false, suggestion: "notionalCcy does not match productId option type. Use baseCcy for CALL, quoteCcy for PUT." },
5807
5807
  "50026": { retry: true, suggestion: "DCD system error. Retry in a few minutes." },
@@ -6176,7 +6176,7 @@ function registerFlashEarnTools() {
6176
6176
  {
6177
6177
  name: "earn_get_flash_earn_projects",
6178
6178
  module: "earn.flash",
6179
- description: "Get Flash Earn projects. Use this to browse upcoming or in-progress Flash Earn opportunities. Do NOT use for purchase or redeem actions \u2014 Flash Earn is query-only in this module.",
6179
+ description: "Get Flash Earn projects. Use this to browse upcoming or in-progress Flash Earn opportunities. Do NOT use for purchase or redeem actions - Flash Earn is query-only in this module.",
6180
6180
  isWrite: false,
6181
6181
  inputSchema: {
6182
6182
  type: "object",
@@ -6572,7 +6572,7 @@ function handlePlaceOrderError(base, rawArgs, endpoint) {
6572
6572
  const seriesId = extractSeriesId(instId);
6573
6573
  const expiryMs = inferExpiryMsFromInstId(instId);
6574
6574
  const isExpired = expiryMs !== null && expiryMs < Date.now();
6575
- const reason = isExpired ? `The contract (${instId}) has expired.` : `The contract (${instId}) was not found \u2014 it may not exist or has not started yet.`;
6575
+ const reason = isExpired ? `The contract (${instId}) has expired.` : `The contract (${instId}) was not found - it may not exist or has not started yet.`;
6576
6576
  throw new OkxApiError(
6577
6577
  `${reason} Ask the user if they'd like to place the same order on the next session. If yes, call event_get_markets with seriesId=${seriesId} and state=live to find available contracts.`,
6578
6578
  { code: sCode, endpoint }
@@ -6588,17 +6588,17 @@ var OUTCOME_SCHEMA = {
6588
6588
  UP/DOWN direction contracts: UP (price rises during the period) or DOWN (price falls).
6589
6589
  YES/NO price-target or touch contracts: YES (condition met) or NO (condition not met).
6590
6590
  Check the series type from event_get_series to determine which applies.
6591
- NOTE: px is the event contract price (0.01\u20130.99), NOT the underlying asset price. It reflects market-implied probability when actively trading.`
6591
+ NOTE: px is the event contract price (0.01-0.99), NOT the underlying asset price. It reflects market-implied probability when actively trading.`
6592
6592
  };
6593
6593
  function registerEventContractTools() {
6594
6594
  return [
6595
6595
  // -----------------------------------------------------------------------
6596
- // Read-only browse (user-facing) + series / events / markets (internal)
6596
+ // Read-only - browse (user-facing) + series / events / markets (internal)
6597
6597
  // -----------------------------------------------------------------------
6598
6598
  {
6599
6599
  name: "event_browse",
6600
6600
  module: "event",
6601
- description: "Browse currently active (in-progress) event contracts. Call when user asks what event contracts are available to trade. Returns only in-progress contracts (floorStrike set). If a live quote field px is present, it is the event contract price (0.01\u20130.99), not the underlying asset price; it reflects the market-implied probability when actively trading. Grouped by settlement type and underlying. Do NOT use for querying contracts within a specific series \u2014 use event_get_markets with seriesId instead.",
6601
+ description: "Browse currently active (in-progress) event contracts. Call when user asks what event contracts are available to trade. Returns only in-progress contracts (floorStrike set). If a live quote field px is present, it is the event contract price (0.01-0.99), not the underlying asset price; it reflects the market-implied probability when actively trading. Grouped by settlement type and underlying. Do NOT use for querying contracts within a specific series - use event_get_markets with seriesId instead.",
6602
6602
  isWrite: false,
6603
6603
  inputSchema: {
6604
6604
  type: "object",
@@ -6717,7 +6717,7 @@ function registerEventContractTools() {
6717
6717
  {
6718
6718
  name: "event_get_markets",
6719
6719
  module: "event",
6720
- description: "List tradeable contracts within a series. state=live for active contracts, state=expired for settlement results. floorStrike=strike price; px (when present) is the event contract price (0.01\u20130.99), not the underlying asset price \u2014 reflects the market-implied probability when actively trading; outcome pre-translated (pending/YES/NO/UP/DOWN); timestamps UTC+8. Do NOT use for discovering what series are available across all underlyings \u2014 use event_browse instead.",
6720
+ description: "List tradeable contracts within a series. state=live for active contracts, state=expired for settlement results. floorStrike=strike price; px (when present) is the event contract price (0.01-0.99), not the underlying asset price - reflects the market-implied probability when actively trading; outcome pre-translated (pending/YES/NO/UP/DOWN); timestamps UTC+8. Do NOT use for discovering what series are available across all underlyings - use event_browse instead.",
6721
6721
  isWrite: false,
6722
6722
  inputSchema: {
6723
6723
  type: "object",
@@ -6798,7 +6798,7 @@ function registerEventContractTools() {
6798
6798
  {
6799
6799
  name: "event_get_orders",
6800
6800
  module: "event",
6801
- description: "Query event contract orders (open, 7d history, or 3-month archive). outcome pre-translated (YES/NO/UP/DOWN). Do NOT use for trade executions \u2014 use event_get_fills for fill records and settlement outcomes.",
6801
+ description: "Query event contract orders (open, 7d history, or 3-month archive). outcome pre-translated (YES/NO/UP/DOWN). Do NOT use for trade executions - use event_get_fills for fill records and settlement outcomes.",
6802
6802
  isWrite: false,
6803
6803
  inputSchema: {
6804
6804
  type: "object",
@@ -6856,7 +6856,7 @@ function registerEventContractTools() {
6856
6856
  {
6857
6857
  name: "event_get_fills",
6858
6858
  module: "event",
6859
- description: "Get event contract fill history (trade executions and settlement payouts). archive=true for up to 3mo, false (default) for last 3d. outcome pre-translated (YES/NO/UP/DOWN). Each record includes a 'type' field: 'fill' (opening trade) or 'settlement' (expiry payout with settlementResult win/loss and pnl). Do NOT use for order status \u2014 use event_get_orders instead.",
6859
+ description: "Get event contract fill history (trade executions and settlement payouts). archive=true for up to 3mo, false (default) for last 3d. outcome pre-translated (YES/NO/UP/DOWN). Each record includes a 'type' field: 'fill' (opening trade) or 'settlement' (expiry payout with settlementResult win/loss and pnl). Do NOT use for order status - use event_get_orders instead.",
6860
6860
  isWrite: false,
6861
6861
  inputSchema: {
6862
6862
  type: "object",
@@ -6898,15 +6898,15 @@ function registerEventContractTools() {
6898
6898
  }
6899
6899
  },
6900
6900
  // -----------------------------------------------------------------------
6901
- // Private write
6901
+ // Private - write
6902
6902
  // -----------------------------------------------------------------------
6903
6903
  {
6904
6904
  name: "event_place_order",
6905
6905
  module: "event",
6906
6906
  description: `Place an event contract order. [CAUTION] Places a real order. Before placing, call event_get_markets(seriesId, state=live) to obtain the instId of the target contract.
6907
6907
  - outcome: UP/YES (bet price goes up/condition met) or DOWN/NO (bet price goes down/condition not met)
6908
- - For limit orders: px is the event contract price (0.01\u20130.99), NOT the underlying asset price. It reflects market-implied probability when actively trading
6909
- - tdMode is always isolated; speedBump is auto-set per exchange requirement \u2014 do not pass either`,
6908
+ - For limit orders: px is the event contract price (0.01-0.99), NOT the underlying asset price. It reflects market-implied probability when actively trading
6909
+ - tdMode is always isolated; speedBump is auto-set per exchange requirement - do not pass either`,
6910
6910
  isWrite: true,
6911
6911
  inputSchema: {
6912
6912
  type: "object",
@@ -6932,7 +6932,7 @@ function registerEventContractTools() {
6932
6932
  },
6933
6933
  px: {
6934
6934
  type: "string",
6935
- description: "Event contract price (0.01\u20130.99). Required when ordType=limit. Do NOT use for market orders."
6935
+ description: "Event contract price (0.01-0.99). Required when ordType=limit. Do NOT use for market orders."
6936
6936
  }
6937
6937
  },
6938
6938
  required: ["instId", "side", "outcome", "sz"]
@@ -6985,7 +6985,7 @@ function registerEventContractTools() {
6985
6985
  properties: {
6986
6986
  instId: { type: "string", description: "Event contract instrument ID" },
6987
6987
  ordId: { type: "string", description: "Order ID to amend" },
6988
- newPx: { type: "string", description: "New event contract price (0.01\u20130.99). Omit to keep current." },
6988
+ newPx: { type: "string", description: "New event contract price (0.01-0.99). Omit to keep current." },
6989
6989
  newSz: { type: "string", description: "New size in contracts (omit to keep current)" }
6990
6990
  },
6991
6991
  required: ["instId", "ordId"]
@@ -7039,7 +7039,7 @@ function registerEventContractTools() {
7039
7039
  if (sCode === "51001") {
7040
7040
  const expiryMs = inferExpiryMsFromInstId(instId);
7041
7041
  const isExpired = expiryMs !== null && expiryMs < Date.now();
7042
- const reason = isExpired ? `The contract (${instId}) has already expired \u2014 the order was auto-cancelled at settlement. Check event_get_fills to confirm the outcome.` : `Instrument (${instId}) not found. Verify the instId with event_get_markets before retrying.`;
7042
+ const reason = isExpired ? `The contract (${instId}) has already expired - the order was auto-cancelled at settlement. Check event_get_fills to confirm the outcome.` : `Instrument (${instId}) not found. Verify the instId with event_get_markets before retrying.`;
7043
7043
  throw new OkxApiError(reason, { code: sCode, endpoint: response.endpoint });
7044
7044
  }
7045
7045
  }
@@ -7074,25 +7074,25 @@ var SIGNAL_POOL_FILTER_PROPS = {
7074
7074
  type: "string",
7075
7075
  enum: ["PNL_ANY", "PNL_TOP50", "PNL_TOP20", "PNL_TOP5"],
7076
7076
  default: "PNL_ANY",
7077
- 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."
7077
+ description: "PnL percentile gate. 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 - use percentile, not absolute thresholds."
7078
7078
  },
7079
7079
  winRateTier: {
7080
7080
  type: "string",
7081
7081
  enum: ["WR_ANY", "WR_GE_50", "WR_GE_80"],
7082
7082
  default: "WR_ANY",
7083
- 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%."
7083
+ description: "Minimum win-rate gate (absolute thresholds, NOT percentile). Naming: `GE_{N}` = career win-rate \u2265 N% - distinct from `pnlTier`/`aumTier` `TOP{N}` which are percentiles. WR_ANY = no filter; WR_GE_50 = \u2265 50%; WR_GE_80 = \u2265 80%."
7084
7084
  },
7085
7085
  maxDrawdownTier: {
7086
7086
  type: "string",
7087
7087
  enum: ["MR_ANY", "MR_LE_20", "MR_LE_50"],
7088
7088
  default: "MR_ANY",
7089
- 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%."
7089
+ description: "Maximum-drawdown gate (absolute thresholds, NOT percentile; smaller drawdown = lower risk). Naming: `LE_{N}` = drawdown \u2264 N% - distinct from `pnlTier`/`aumTier` `TOP{N}` which are percentiles. MR_ANY = no filter; MR_LE_20 = drawdown \u2264 20%; MR_LE_50 = \u2264 50%."
7090
7090
  },
7091
7091
  aumTier: {
7092
7092
  type: "string",
7093
7093
  enum: ["AUM_ANY", "AUM_TOP50", "AUM_TOP20", "AUM_TOP5"],
7094
7094
  default: "AUM_ANY",
7095
- 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."
7095
+ 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 - use percentile, not absolute USD."
7096
7096
  }
7097
7097
  };
7098
7098
  var LEADERBOARD_POOL_FILTER_PROPS = {
@@ -7110,19 +7110,19 @@ var LEADERBOARD_POOL_FILTER_PROPS = {
7110
7110
  },
7111
7111
  minPnl: {
7112
7112
  type: "string",
7113
- 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.'
7113
+ description: 'Minimum absolute PnL in USD as a string, e.g. `"10000"` -> traders with PnL \u2265 $10,000. Numeric threshold - distinct from the signal-side `pnlTier` percentile enum.'
7114
7114
  },
7115
7115
  minWinRate: {
7116
7116
  type: "string",
7117
- 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.'
7117
+ description: 'Minimum win-rate as a decimal in 0~1 range, passed as a string, e.g. `"0.8"` -> traders with win-rate \u2265 80%. Numeric threshold - distinct from the signal-side `winRateTier` enum.'
7118
7118
  },
7119
7119
  maxDrawdown: {
7120
7120
  type: "string",
7121
- 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.'
7121
+ description: 'Maximum drawdown as a decimal, passed as a string, e.g. `"0.1"` -> traders with drawdown \u2264 10%. Lower = lower risk. Numeric threshold - distinct from the signal-side `maxDrawdownTier` enum.'
7122
7122
  },
7123
7123
  minAum: {
7124
7124
  type: "string",
7125
- 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.'
7125
+ description: 'Minimum AUM (Assets Under Management) in USD as a string, e.g. `"1000"` -> traders with AUM \u2265 $1,000. Numeric threshold - distinct from the signal-side `aumTier` percentile enum.'
7126
7126
  }
7127
7127
  };
7128
7128
  var LEADERBOARD_FILTER_UPSTREAM_NAMES = {
@@ -7256,11 +7256,11 @@ var PAGINATION_PROP = {
7256
7256
  }
7257
7257
  };
7258
7258
  var TRADER_ITEM_PROPS = {
7259
- authorId: { type: "string", description: "Trader's unique ID \u2014 pass to other smartmoney_get_trader_* tools." },
7259
+ authorId: { type: "string", description: "Trader's unique ID - pass to other smartmoney_get_trader_* tools." },
7260
7260
  nickName: { type: "string", description: "Display nickname." },
7261
7261
  pnl: { type: "string", description: "Absolute PnL in USD over the requested `period` (numeric string)." },
7262
7262
  pnlRatio: { type: "string", description: 'PnL as a decimal ratio over the requested `period` (e.g. "0.35" = +35%).' },
7263
- asset: { type: "string", description: "AUM (Assets Under Management) in USD \u2014 same field that the input `minAum` filter applies to." },
7263
+ asset: { type: "string", description: "AUM (Assets Under Management) in USD - same field that the input `minAum` filter applies to." },
7264
7264
  winRate: { type: "string", description: "Lifetime win-rate as a decimal (0~1)." },
7265
7265
  maxDrawdown: { type: "string", description: 'Max drawdown as a decimal (e.g. "0.2" = 20%). Lower = lower risk.' },
7266
7266
  onboardDuration: { type: "string", description: "Days the trader has been onboarded on the leaderboard (numeric string)." },
@@ -7289,7 +7289,7 @@ var SIGNAL_ITEM_PROPS = {
7289
7289
  },
7290
7290
  tradersQualified: {
7291
7291
  type: "integer",
7292
- description: "Pool size after applying tier filters (incl. those without positions on this instrument)."
7292
+ description: "Final aggregation pool size after tier filters + top-N truncation by `sortBy`. Bounded by the request's pool size limit (`lmtNum` for `_by_filter` variants, `authorIds.length` for `_by_trader` variants). Smaller than the bound only when the upstream candidate pool (traders passing all tier filters) contains fewer entries than the bound \u2014 typical for low-volume instruments or very strict tier combos."
7293
7293
  },
7294
7294
  longTraders: { type: "integer", description: "Number of pool traders currently long this asset (incl. double-sided)." },
7295
7295
  shortTraders: { type: "integer", description: "Number of pool traders currently short this asset (incl. double-sided)." },
@@ -7299,19 +7299,19 @@ var SIGNAL_ITEM_PROPS = {
7299
7299
  properties: {
7300
7300
  longNotionalUsdt: {
7301
7301
  type: "string",
7302
- 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."
7302
+ 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 - stays constant when positions are unchanged."
7303
7303
  },
7304
7304
  shortNotionalUsdt: {
7305
7305
  type: "string",
7306
- 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."
7306
+ 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 - stays constant when positions are unchanged."
7307
7307
  },
7308
7308
  netNotionalUsdt: {
7309
7309
  type: "string",
7310
- 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."
7310
+ description: "Net directional notional in USDT = long \u2212 short. Weighted by each trader's ENTRY PRICE (price_avg), not mark price - reflects position scaling, not underlying price movement."
7311
7311
  },
7312
7312
  totalNotionalUsdt: {
7313
7313
  type: "string",
7314
- 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."
7314
+ description: "Gross notional in USDT = long + short. Weighted by each trader's ENTRY PRICE (price_avg), not mark price - reflects position scaling (open / close / add), not underlying price movement. Stays constant across buckets when traders hold positions unchanged."
7315
7315
  },
7316
7316
  totalNotionalVs24h: {
7317
7317
  type: "string",
@@ -7341,7 +7341,7 @@ var SIGNAL_ITEM_PROPS = {
7341
7341
  },
7342
7342
  weightedLongRatio: {
7343
7343
  type: "string",
7344
- 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."
7344
+ description: "Notional-weighted long ratio = \u03A3(long_notional) / \u03A3(notional). Notional uses each trader's ENTRY PRICE (price_avg), not mark price - ratio shifts only when positions are scaled. NULL when no notional."
7345
7345
  },
7346
7346
  weightedShortRatio: {
7347
7347
  type: "string",
@@ -7376,7 +7376,7 @@ var SIGNAL_HISTORY_ITEM_PROPS = {
7376
7376
  },
7377
7377
  weightedLongRatio: {
7378
7378
  type: "string",
7379
- 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."
7379
+ 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 - ratio shifts only when positions are scaled."
7380
7380
  },
7381
7381
  weightedShortRatio: {
7382
7382
  type: "string",
@@ -7396,13 +7396,16 @@ var SIGNAL_HISTORY_ITEM_PROPS = {
7396
7396
  },
7397
7397
  netNotionalUsdt: {
7398
7398
  type: "string",
7399
- 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."
7399
+ 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 - reflects position scaling, not underlying price movement."
7400
7400
  },
7401
7401
  totalNotionalUsdt: {
7402
7402
  type: "string",
7403
- 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."
7403
+ description: "Gross notional in USDT at this bucket = long notional + short notional. Weighted by each trader's ENTRY PRICE (price_avg), not mark price - tracks capital deployed (rising = adding, falling = retreating). Stays constant across buckets when traders hold positions unchanged."
7404
+ },
7405
+ tradersQualified: {
7406
+ type: "integer",
7407
+ description: "Final aggregation pool size in this bucket after tier filters + top-N truncation by `sortBy`. Bounded by the request's pool size limit (`lmtNum` for `_by_filter` variants, `authorIds.length` for `_by_trader` variants). The funnel + top-N selection runs once per query (not per bucket); each bucket reuses the same selected pool for position aggregation. Smaller than the bound only when the upstream candidate pool underflows."
7404
7408
  },
7405
- tradersQualified: { type: "integer", description: "Pool size after applying tier filters (includes traders without a position)." },
7406
7409
  dataVersion: { type: "string", description: "Snapshot version key in `yyyyMMddHH` UTC (10-digit, e.g. `2026042820`)." }
7407
7410
  };
7408
7411
  function registerSmartmoneyTools() {
@@ -7414,7 +7417,7 @@ function registerSmartmoneyTools() {
7414
7417
  {
7415
7418
  name: "smartmoney_get_traders_by_filter",
7416
7419
  module: "smartmoney",
7417
- description: "Leaderboard ranking of OKX smart-money traders, filtered by pool conditions and ranked by `sortBy`. Use when: discovering top performers by criteria (PnL / win-rate / drawdown / AUM). See also: `smartmoney_get_performance_by_trader` (lookup by ID), `smartmoney_search_trader` (lookup by nickname). Note: `updateTime` is 12-digit `yyyyMMddHHmm` UTC+8, different from signal tools' 10-digit UTC `asOfTime`/`dataVersion` \u2014 do not cross-pass.",
7420
+ description: "Leaderboard ranking of OKX smart-money traders, filtered by pool conditions and ranked by `sortBy`. Use when: discovering top performers by criteria (PnL / win-rate / drawdown / AUM). See also: `smartmoney_get_performance_by_trader` (lookup by ID), `smartmoney_search_trader` (lookup by nickname). Note: `updateTime` is 12-digit `yyyyMMddHHmm` UTC+8, different from signal tools' 10-digit UTC `asOfTime`/`dataVersion` - do not cross-pass.",
7418
7421
  isWrite: false,
7419
7422
  outputSchema: envelope(
7420
7423
  { type: "array", items: { type: "object", properties: TRADER_ITEM_PROPS } },
@@ -7431,16 +7434,16 @@ function registerSmartmoneyTools() {
7431
7434
  properties: {
7432
7435
  updateTime: {
7433
7436
  type: "string",
7434
- 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).'
7437
+ description: 'Snapshot version key - 12-digit `yyyyMMddHHmm` (UTC+8) as a string, e.g. `"202604301815"`. Omit to query the latest snapshot (refreshed every ~5 min).'
7435
7438
  },
7436
7439
  ...LEADERBOARD_POOL_FILTER_PROPS,
7437
7440
  after: {
7438
7441
  type: "string",
7439
- 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.'
7442
+ description: 'Pagination cursor (older page) - 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.'
7440
7443
  },
7441
7444
  before: {
7442
7445
  type: "string",
7443
- 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.'
7446
+ description: 'Pagination cursor (newer page) - 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.'
7444
7447
  },
7445
7448
  limit: {
7446
7449
  type: "integer",
@@ -7480,7 +7483,7 @@ function registerSmartmoneyTools() {
7480
7483
  {
7481
7484
  name: "smartmoney_get_performance_by_trader",
7482
7485
  module: "smartmoney",
7483
- description: "PnL / win-rate / drawdown profile for one or more traders looked up by `authorIds`. Use when: caller already has trader IDs and needs their performance metrics. See also: `smartmoney_search_trader` (resolve nickname \u2192 authorId), `smartmoney_get_traders_by_filter` (criteria-based discovery). Note: response `updateTime` is 12-digit `yyyyMMddHHmm` UTC+8 \u2014 do not pass to signal-side tools' `asOfTime` (10-digit UTC).",
7486
+ description: "PnL / win-rate / drawdown profile for one or more traders looked up by `authorIds`. Use when: caller already has trader IDs and needs their performance metrics. See also: `smartmoney_search_trader` (resolve nickname -> authorId), `smartmoney_get_traders_by_filter` (criteria-based discovery). Note: response `updateTime` is 12-digit `yyyyMMddHHmm` UTC+8 - do not pass to signal-side tools' `asOfTime` (10-digit UTC).",
7484
7487
  isWrite: false,
7485
7488
  outputSchema: envelope(
7486
7489
  { type: "array", items: { type: "object", properties: TRADER_ITEM_PROPS } },
@@ -7550,7 +7553,7 @@ function registerSmartmoneyTools() {
7550
7553
  {
7551
7554
  name: "smartmoney_get_trader_positions",
7552
7555
  module: "smartmoney",
7553
- description: "Currently-open positions held by a single trader (direction, size, leverage, entry, conviction). Use when: inspecting what a top trader is holding RIGHT NOW. See also: `smartmoney_get_trader_positions_history` (closed positions), `smartmoney_search_trader` (nickname \u2192 authorId), `smartmoney_get_traders_by_filter` (discover trader).",
7556
+ description: "Currently-open positions held by a single trader (direction, size, leverage, entry, conviction). Use when: inspecting what a top trader is holding RIGHT NOW. See also: `smartmoney_get_trader_positions_history` (closed positions), `smartmoney_search_trader` (nickname -> authorId), `smartmoney_get_traders_by_filter` (discover trader).",
7554
7557
  isWrite: false,
7555
7558
  outputSchema: envelope({
7556
7559
  type: "array",
@@ -7570,9 +7573,9 @@ function registerSmartmoneyTools() {
7570
7573
  direction: {
7571
7574
  type: "string",
7572
7575
  enum: ["long", "short"],
7573
- 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.'
7576
+ description: 'Derived clean direction (`long` | `short`) - handler computes this from `posSide` + sign of `pos` so agents do not have to branch on the `posSide="net"` net-mode case.'
7574
7577
  },
7575
- posCcy: { type: "string", description: 'Position currency \u2014 the asset being held, e.g. "BTC".' },
7578
+ posCcy: { type: "string", description: 'Position currency - the asset being held, e.g. "BTC".' },
7576
7579
  quoteCcy: { type: "string", description: 'Quote currency the position is priced/settled in, e.g. "USDT".' },
7577
7580
  pos: {
7578
7581
  type: "string",
@@ -7601,7 +7604,7 @@ function registerSmartmoneyTools() {
7601
7604
  },
7602
7605
  instId: {
7603
7606
  type: "string",
7604
- description: 'Optional instrument filter. Accepts either full instId (e.g. "BTC-USDT-SWAP") or bare base currency (e.g. "BTC") \u2014 the handler extracts the base currency for the upstream filter.'
7607
+ description: 'Optional instrument filter. Accepts either full instId (e.g. "BTC-USDT-SWAP") or bare base currency (e.g. "BTC") - the handler extracts the base currency for the upstream filter.'
7605
7608
  }
7606
7609
  },
7607
7610
  required: ["authorId"]
@@ -7631,7 +7634,7 @@ function registerSmartmoneyTools() {
7631
7634
  {
7632
7635
  name: "smartmoney_get_trader_positions_history",
7633
7636
  module: "smartmoney",
7634
- description: "Closed-position history of a single trader, paginated by `posId` cursor. Use when: studying realized PnL pattern, holding duration, win/loss streaks, or how positions ended (closed vs liquidated). See also: `smartmoney_get_trader_positions` (currently-open), `smartmoney_search_trader` (nickname \u2192 authorId), `smartmoney_get_traders_by_filter` (discover trader).",
7637
+ description: "Closed-position history of a single trader, paginated by `posId` cursor. Use when: studying realized PnL pattern, holding duration, win/loss streaks, or how positions ended (closed vs liquidated). See also: `smartmoney_get_trader_positions` (currently-open), `smartmoney_search_trader` (nickname -> authorId), `smartmoney_get_traders_by_filter` (discover trader).",
7635
7638
  isWrite: false,
7636
7639
  outputSchema: envelope(
7637
7640
  {
@@ -7647,7 +7650,7 @@ function registerSmartmoneyTools() {
7647
7650
  },
7648
7651
  ctVal: {
7649
7652
  type: "string",
7650
- description: "Contract face value \u2014 USD value of a single contract (\u5F20). Numeric string. Empty/0 for non-contract instruments."
7653
+ description: "Contract face value - USD value of a single contract (\u5F20). Numeric string. Empty/0 for non-contract instruments."
7651
7654
  },
7652
7655
  posSide: {
7653
7656
  type: "string",
@@ -7714,15 +7717,15 @@ function registerSmartmoneyTools() {
7714
7717
  },
7715
7718
  instId: {
7716
7719
  type: "string",
7717
- description: 'Optional instrument filter. Accepts either full instId (e.g. "BTC-USDT-SWAP") or bare base currency (e.g. "BTC") \u2014 the handler extracts the base currency for the upstream filter.'
7720
+ description: 'Optional instrument filter. Accepts either full instId (e.g. "BTC-USDT-SWAP") or bare base currency (e.g. "BTC") - the handler extracts the base currency for the upstream filter.'
7718
7721
  },
7719
7722
  after: {
7720
7723
  type: "string",
7721
- description: 'Pagination cursor (older) \u2014 returns positions with `posId` smaller than this value. Pass the `posId` as a string, e.g. `"872913470357110787"`.'
7724
+ description: 'Pagination cursor (older) - returns positions with `posId` smaller than this value. Pass the `posId` as a string, e.g. `"872913470357110787"`.'
7722
7725
  },
7723
7726
  before: {
7724
7727
  type: "string",
7725
- description: 'Pagination cursor (newer) \u2014 returns positions with `posId` greater than this value. Pass the `posId` as a string, e.g. `"872913470357110787"`.'
7728
+ description: 'Pagination cursor (newer) - returns positions with `posId` greater than this value. Pass the `posId` as a string, e.g. `"872913470357110787"`.'
7726
7729
  },
7727
7730
  limit: {
7728
7731
  type: "integer",
@@ -7768,7 +7771,7 @@ function registerSmartmoneyTools() {
7768
7771
  {
7769
7772
  name: "smartmoney_get_trader_orders_history",
7770
7773
  module: "smartmoney",
7771
- description: "Recent orders/fills placed by a single trader (direction, size, price, leverage), paginated by `ordId` cursor. Aligned with the cross-module `*_get_orders` family. Use when: tracking a top trader's latest trade activity. See also: `smartmoney_search_trader` (nickname \u2192 authorId), `smartmoney_get_traders_by_filter` (discover trader).",
7774
+ description: "Recent orders/fills placed by a single trader (direction, size, price, leverage), paginated by `ordId` cursor. Aligned with the cross-module `*_get_orders` family. Use when: tracking a top trader's latest trade activity. See also: `smartmoney_search_trader` (nickname -> authorId), `smartmoney_get_traders_by_filter` (discover trader).",
7772
7775
  isWrite: false,
7773
7776
  outputSchema: envelope(
7774
7777
  {
@@ -7832,15 +7835,15 @@ function registerSmartmoneyTools() {
7832
7835
  },
7833
7836
  instId: {
7834
7837
  type: "string",
7835
- description: 'Optional instrument filter. Accepts either full instId (e.g. "BTC-USDT-SWAP") or bare base currency (e.g. "BTC") \u2014 the handler extracts the base currency for the upstream filter.'
7838
+ description: 'Optional instrument filter. Accepts either full instId (e.g. "BTC-USDT-SWAP") or bare base currency (e.g. "BTC") - the handler extracts the base currency for the upstream filter.'
7836
7839
  },
7837
7840
  after: {
7838
7841
  type: "string",
7839
- description: 'Pagination cursor (older) \u2014 returns trades with `ordId` smaller than this value. Pass the `ordId` as a string, e.g. `"872913470357110787"`.'
7842
+ description: 'Pagination cursor (older) - returns trades with `ordId` smaller than this value. Pass the `ordId` as a string, e.g. `"872913470357110787"`.'
7840
7843
  },
7841
7844
  before: {
7842
7845
  type: "string",
7843
- description: 'Pagination cursor (newer) \u2014 returns trades with `ordId` greater than this value. Pass the `ordId` as a string, e.g. `"872913470357110787"`.'
7846
+ description: 'Pagination cursor (newer) - returns trades with `ordId` greater than this value. Pass the `ordId` as a string, e.g. `"872913470357110787"`.'
7844
7847
  },
7845
7848
  limit: {
7846
7849
  type: "integer",
@@ -7894,7 +7897,7 @@ function registerSmartmoneyTools() {
7894
7897
  items: {
7895
7898
  type: "object",
7896
7899
  properties: {
7897
- authorId: { type: "string", description: "Trader's unique ID \u2014 pass to other `smartmoney_get_trader_*` tools." },
7900
+ authorId: { type: "string", description: "Trader's unique ID - pass to other `smartmoney_get_trader_*` tools." },
7898
7901
  nickName: { type: "string", description: "Display nickname matched against the keyword." },
7899
7902
  followerCount: { type: "string", description: "OKX-platform follower count (numeric string; Twitter followers excluded). Sort key." }
7900
7903
  }
@@ -7936,7 +7939,7 @@ function registerSmartmoneyTools() {
7936
7939
  {
7937
7940
  name: "smartmoney_get_signal_overview_by_filter",
7938
7941
  module: "smartmoney",
7939
- 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).",
7942
+ 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` - exactly one. Snapshot time auto-resolved to current hour. **Linear (USDT/USDS-margined) contracts only - 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).",
7940
7943
  isWrite: false,
7941
7944
  outputSchema: envelope({
7942
7945
  type: "array",
@@ -7957,7 +7960,7 @@ function registerSmartmoneyTools() {
7957
7960
  type: "array",
7958
7961
  items: { type: "string" },
7959
7962
  minItems: 1,
7960
- 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.'
7963
+ description: 'Base currencies to aggregate, e.g. `["BTC", "ETH", "SOL"]`. Mutually exclusive with `topInstruments`. Scope: only USDT-margined and USDS-margined (linear) instruments - 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.'
7961
7964
  },
7962
7965
  ...SIGNAL_POOL_FILTER_PROPS,
7963
7966
  lmtNum: {
@@ -7965,7 +7968,7 @@ function registerSmartmoneyTools() {
7965
7968
  minimum: 1,
7966
7969
  maximum: 2e3,
7967
7970
  default: 100,
7968
- 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."
7971
+ description: "Upper bound on `tradersQualified` (final aggregation pool size). Candidates pass through tier filters (`pnlTier` / `winRateTier` / `aumTier` / `maxDrawdownTier`), then are truncated to top-N by `sortBy` (DESC). `tradersQualified` \u2264 `lmtNum` always; equals `lmtNum` unless candidate pool underflows (rare ccy / strict tier combos). Default 100; values above ~1500 add latency without benefit (exceeds typical candidate pool size)."
7969
7972
  }
7970
7973
  },
7971
7974
  required: ["sortBy", "period"]
@@ -7977,7 +7980,7 @@ function registerSmartmoneyTools() {
7977
7980
  if (instCcyList && topInstrumentsRaw !== void 0) {
7978
7981
  throw actionableError(
7979
7982
  '"topInstruments" and "instCcyList" are mutually exclusive.',
7980
- "Pass exactly one \u2014 `topInstruments` for top-N hottest coins, or `instCcyList` for specific coins."
7983
+ "Pass exactly one - `topInstruments` for top-N hottest coins, or `instCcyList` for specific coins."
7981
7984
  );
7982
7985
  }
7983
7986
  const response = await context.client.privateGet(
@@ -7996,7 +7999,7 @@ function registerSmartmoneyTools() {
7996
7999
  {
7997
8000
  name: "smartmoney_get_signal_overview_by_trader",
7998
8001
  module: "smartmoney",
7999
- 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).",
8002
+ 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 - backend uses defaults for direct-lookup scenarios. **Linear (USDT/USDS-margined) contracts only - 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).",
8000
8003
  isWrite: false,
8001
8004
  outputSchema: envelope({
8002
8005
  type: "array",
@@ -8023,7 +8026,7 @@ function registerSmartmoneyTools() {
8023
8026
  type: "array",
8024
8027
  items: { type: "string" },
8025
8028
  minItems: 1,
8026
- 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.'
8029
+ description: 'Base currencies to aggregate, e.g. `["BTC", "ETH", "SOL"]`. Mutually exclusive with `topInstruments`. Scope: only USDT-margined and USDS-margined (linear) instruments - 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.'
8027
8030
  },
8028
8031
  sortBy: {
8029
8032
  type: "string",
@@ -8056,7 +8059,7 @@ function registerSmartmoneyTools() {
8056
8059
  if (instCcyList && topInstrumentsRaw !== void 0) {
8057
8060
  throw actionableError(
8058
8061
  '"topInstruments" and "instCcyList" are mutually exclusive.',
8059
- "Pass exactly one \u2014 `topInstruments` for top-N hottest coins, or `instCcyList` for specific coins."
8062
+ "Pass exactly one - `topInstruments` for top-N hottest coins, or `instCcyList` for specific coins."
8060
8063
  );
8061
8064
  }
8062
8065
  const response = await context.client.privateGet(
@@ -8078,7 +8081,7 @@ function registerSmartmoneyTools() {
8078
8081
  {
8079
8082
  name: "smartmoney_get_signal_trend_by_filter",
8080
8083
  module: "smartmoney",
8081
- 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.",
8084
+ 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 - 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` - do not cross-pass.",
8082
8085
  isWrite: false,
8083
8086
  outputSchema: envelope({
8084
8087
  type: "array",
@@ -8090,11 +8093,11 @@ function registerSmartmoneyTools() {
8090
8093
  properties: {
8091
8094
  instCcy: {
8092
8095
  type: "string",
8093
- 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.'
8096
+ description: 'Base currency to scope the time-series, e.g. "BTC". Required. Scope: USDT-margined and USDS-margined (linear) instruments only - coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions are NOT included.'
8094
8097
  },
8095
8098
  asOfTime: {
8096
8099
  type: "string",
8097
- 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.'
8100
+ description: 'Anchor snapshot time - 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.'
8098
8101
  },
8099
8102
  granularity: {
8100
8103
  type: "string",
@@ -8115,7 +8118,7 @@ function registerSmartmoneyTools() {
8115
8118
  minimum: 1,
8116
8119
  maximum: 2e3,
8117
8120
  default: 100,
8118
- description: "Top-N traders to pull into the aggregation pool, ranked by `sortBy` (DESC). Default 100, max 2000."
8121
+ description: "Upper bound on `tradersQualified` per bucket (final aggregation pool size). Candidates pass through tier filters (`pnlTier` / `winRateTier` / `aumTier` / `maxDrawdownTier`), then are truncated to top-N by `sortBy` (DESC). `tradersQualified` \u2264 `lmtNum` always; equals `lmtNum` unless candidate pool underflows (rare ccy / strict tier combos). Default 100; values above ~1500 add latency without benefit (exceeds typical candidate pool size)."
8119
8122
  }
8120
8123
  },
8121
8124
  required: ["instCcy", "granularity", "sortBy", "period"]
@@ -8150,7 +8153,7 @@ function registerSmartmoneyTools() {
8150
8153
  {
8151
8154
  name: "smartmoney_get_signal_trend_by_trader",
8152
8155
  module: "smartmoney",
8153
- 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.",
8156
+ 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 - backend uses defaults for direct-lookup scenarios. **Linear (USDT/USDS-margined) contracts only - 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` - do not cross-pass.",
8154
8157
  isWrite: false,
8155
8158
  outputSchema: envelope({
8156
8159
  type: "array",
@@ -8168,11 +8171,11 @@ function registerSmartmoneyTools() {
8168
8171
  },
8169
8172
  instCcy: {
8170
8173
  type: "string",
8171
- 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.'
8174
+ description: 'Base currency to scope the time-series, e.g. "BTC". Required. Scope: USDT-margined and USDS-margined (linear) instruments only - coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions held by the trader set are NOT included.'
8172
8175
  },
8173
8176
  asOfTime: {
8174
8177
  type: "string",
8175
- 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.'
8178
+ description: 'Anchor snapshot time - 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.'
8176
8179
  },
8177
8180
  granularity: {
8178
8181
  type: "string",
@@ -8252,7 +8255,7 @@ function buildContractTradeTools(cfg) {
8252
8255
  {
8253
8256
  name: n("place_order"),
8254
8257
  module,
8255
- description: `Place ${label} order. Attach TP/SL via tpTriggerPx/slTriggerPx. Before placing, use market_get_instruments to get ctVal (contract face value) \u2014 do NOT assume contract sizes. [CAUTION] Executes real trades.`,
8258
+ description: `Place ${label} order. Attach TP/SL via tpTriggerPx/slTriggerPx. Before placing, use market_get_instruments to get ctVal (contract face value) - do NOT assume contract sizes. [CAUTION] Executes real trades.`,
8256
8259
  isWrite: true,
8257
8260
  inputSchema: {
8258
8261
  type: "object",
@@ -8592,10 +8595,10 @@ function buildContractTradeTools(cfg) {
8592
8595
  module,
8593
8596
  description: `Set leverage for a ${label} instrument or position. [CAUTION] Changes risk parameters.
8594
8597
  Scenarios (SWAP/FUTURES only):
8595
- \u2022 cross + any instId under the index \u2192 sets leverage at the index level
8596
- \u2022 isolated + buy-sell (net) posMode \u2192 instId only
8597
- \u2022 isolated + long-short (hedge) posMode \u2192 instId + posSide=long|short (BOTH directions must be set separately)
8598
- Not supported: PORTFOLIO MARGIN accounts cannot adjust cross leverage for SWAP/FUTURES \u2014 the request will be rejected by OKX. Use account_get_config first if unsure of the account's margin mode.`,
8598
+ \u2022 cross + any instId under the index -> sets leverage at the index level
8599
+ \u2022 isolated + buy-sell (net) posMode -> instId only
8600
+ \u2022 isolated + long-short (hedge) posMode -> instId + posSide=long|short (BOTH directions must be set separately)
8601
+ Not supported: PORTFOLIO MARGIN accounts cannot adjust cross leverage for SWAP/FUTURES - the request will be rejected by OKX. Use account_get_config first if unsure of the account's margin mode.`,
8599
8602
  isWrite: true,
8600
8603
  inputSchema: {
8601
8604
  type: "object",
@@ -8603,13 +8606,13 @@ Not supported: PORTFOLIO MARGIN accounts cannot adjust cross leverage for SWAP/F
8603
8606
  instId: { type: "string", description: instIdExample },
8604
8607
  lever: {
8605
8608
  type: "string",
8606
- description: "Leverage multiplier as a positive number string, e.g. '10'. Max value depends on the instrument (query market_get_instruments \u2192 lever)."
8609
+ description: "Leverage multiplier as a positive number string, e.g. '10'. Max value depends on the instrument (query market_get_instruments -> lever)."
8607
8610
  },
8608
8611
  mgnMode: { type: "string", enum: ["cross", "isolated"] },
8609
8612
  posSide: {
8610
8613
  type: "string",
8611
8614
  enum: ["long", "short"],
8612
- description: "REQUIRED when mgnMode=isolated AND the account is in hedge (long/short) position mode. Use 'long' or 'short' \u2014 setting one side does NOT auto-apply to the other. Omit entirely for one-way (net) position mode or for cross margin."
8615
+ description: "REQUIRED when mgnMode=isolated AND the account is in hedge (long/short) position mode. Use 'long' or 'short' - setting one side does NOT auto-apply to the other. Omit entirely for one-way (net) position mode or for cross margin."
8613
8616
  }
8614
8617
  },
8615
8618
  required: ["instId", "lever", "mgnMode"]
@@ -9425,12 +9428,12 @@ var OI_BARS = ["5m", "15m", "1H", "4H", "1D"];
9425
9428
  function registerMarketFilterTools() {
9426
9429
  return [
9427
9430
  // ─────────────────────────────────────────────────────────────────────────
9428
- // market_filter /api/v5/aigc/mcp/market-filter
9431
+ // market_filter - /api/v5/aigc/mcp/market-filter
9429
9432
  // ─────────────────────────────────────────────────────────────────────────
9430
9433
  {
9431
9434
  name: "market_filter",
9432
9435
  module: "market",
9433
- description: "Screen / rank instruments across SPOT, SWAP, or FUTURES by multi-dimensional criteria: price range, 24h change %, market cap, 24h volume (USD), funding rate (SWAP), open interest (USD), listing time. Returns ranked rows with full ticker snapshot. Use to find top movers, high-OI contracts, newly listed tokens, etc. No credentials required. Do NOT use to get OI change rankings across contracts \u2014 use market_filter_oi_change instead. Do NOT use to get OI time series for a single instrument \u2014 use market_get_oi_history instead.",
9436
+ description: "Screen / rank instruments across SPOT, SWAP, or FUTURES by multi-dimensional criteria: price range, 24h change %, market cap, 24h volume (USD), funding rate (SWAP), open interest (USD), listing time. Returns ranked rows with full ticker snapshot. Use to find top movers, high-OI contracts, newly listed tokens, etc. No credentials required. Do NOT use to get OI change rankings across contracts - use market_filter_oi_change instead. Do NOT use to get OI time series for a single instrument - use market_get_oi_history instead.",
9434
9437
  isWrite: false,
9435
9438
  inputSchema: {
9436
9439
  type: "object",
@@ -9514,7 +9517,7 @@ function registerMarketFilterTools() {
9514
9517
  sortBy: {
9515
9518
  type: "string",
9516
9519
  enum: ["last", "chg24hPct", "marketCapUsd", "volUsd24h", "fundingRate", "oiUsd", "listTime"],
9517
- description: "Sort field. Default: volUsd24h. Note: marketCapUsd is only meaningful for SPOT (null for SWAP/FUTURES). To rank by OI *change* (oiDeltaPct / absOiDeltaPct), use market_filter_oi_change \u2014 market_filter only sorts by the current snapshot."
9520
+ description: "Sort field. Default: volUsd24h. Note: marketCapUsd is only meaningful for SPOT (null for SWAP/FUTURES). To rank by OI *change* (oiDeltaPct / absOiDeltaPct), use market_filter_oi_change - market_filter only sorts by the current snapshot."
9518
9521
  },
9519
9522
  sortOrder: {
9520
9523
  type: "string",
@@ -9562,12 +9565,12 @@ function registerMarketFilterTools() {
9562
9565
  }
9563
9566
  },
9564
9567
  // ─────────────────────────────────────────────────────────────────────────
9565
- // market_get_oi_history /api/v5/aigc/mcp/oi-history
9568
+ // market_get_oi_history - /api/v5/aigc/mcp/oi-history
9566
9569
  // ─────────────────────────────────────────────────────────────────────────
9567
9570
  {
9568
9571
  name: "market_get_oi_history",
9569
9572
  module: "market",
9570
- description: "Get open interest (OI) history time series for a single SWAP or FUTURES instrument. Returns per-bar OI in contracts, base currency and USD, plus bar-over-bar delta and delta %. Useful for tracking how OI evolves around price moves. No credentials required. Do NOT use to compare OI changes across multiple contracts \u2014 use market_filter_oi_change instead. Do NOT use to screen instruments by current OI level \u2014 use market_filter instead.",
9573
+ description: "Get open interest (OI) history time series for a single SWAP or FUTURES instrument. Returns per-bar OI in contracts, base currency and USD, plus bar-over-bar delta and delta %. Useful for tracking how OI evolves around price moves. No credentials required. Do NOT use to compare OI changes across multiple contracts - use market_filter_oi_change instead. Do NOT use to screen instruments by current OI level - use market_filter instead.",
9571
9574
  isWrite: false,
9572
9575
  inputSchema: {
9573
9576
  type: "object",
@@ -9609,12 +9612,12 @@ function registerMarketFilterTools() {
9609
9612
  }
9610
9613
  },
9611
9614
  // ─────────────────────────────────────────────────────────────────────────
9612
- // market_filter_oi_change /api/v5/aigc/mcp/oi-change-filter
9615
+ // market_filter_oi_change - /api/v5/aigc/mcp/oi-change-filter
9613
9616
  // ─────────────────────────────────────────────────────────────────────────
9614
9617
  {
9615
9618
  name: "market_filter_oi_change",
9616
9619
  module: "market",
9617
- description: "Find SWAP or FUTURES instruments with significant open interest changes over a given bar window. Returns ranked rows with current OI (USD), previous OI (USD), OI delta (USD and %), price change %, 24h volume and funding rate. Ideal for spotting unusual accumulation/distribution or confirming trend momentum. No credentials required. Do NOT use to get OI time series for a single instrument \u2014 use market_get_oi_history instead. Do NOT use to screen by current OI absolute level or other non-OI metrics \u2014 use market_filter instead.",
9620
+ description: "Find SWAP or FUTURES instruments with significant open interest changes over a given bar window. Returns ranked rows with current OI (USD), previous OI (USD), OI delta (USD and %), price change %, 24h volume and funding rate. Ideal for spotting unusual accumulation/distribution or confirming trend momentum. No credentials required. Do NOT use to get OI time series for a single instrument - use market_get_oi_history instead. Do NOT use to screen by current OI absolute level or other non-OI metrics - use market_filter instead.",
9618
9621
  isWrite: false,
9619
9622
  inputSchema: {
9620
9623
  type: "object",
@@ -9646,7 +9649,7 @@ function registerMarketFilterTools() {
9646
9649
  sortBy: {
9647
9650
  type: "string",
9648
9651
  enum: ["oiUsd", "oiDeltaUsd", "oiDeltaPct", "absOiDeltaPct", "volUsd24h", "fundingRate", "last"],
9649
- description: "Sort field. Default: oiDeltaPct (largest movers first, signed \u2014 longs and shorts separate). Use absOiDeltaPct to sort by |oiDeltaPct| (largest-magnitude moves regardless of direction). fundingRate is also supported for SWAP. Do NOT use the market_filter tool's sort fields (chg24hPct, marketCapUsd, listTime) here \u2014 they are not in the OI-change Row."
9652
+ description: "Sort field. Default: oiDeltaPct (largest movers first, signed - longs and shorts separate). Use absOiDeltaPct to sort by |oiDeltaPct| (largest-magnitude moves regardless of direction). fundingRate is also supported for SWAP. Do NOT use the market_filter tool's sort fields (chg24hPct, marketCapUsd, listTime) here - they are not in the OI-change Row."
9650
9653
  },
9651
9654
  sortOrder: {
9652
9655
  type: "string",
@@ -9679,6 +9682,58 @@ function registerMarketFilterTools() {
9679
9682
  );
9680
9683
  return normalizeResponse(response);
9681
9684
  }
9685
+ },
9686
+ // ─────────────────────────────────────────────────────────────────────────
9687
+ // market_get_pair_spread - /api/v5/aigc/mcp/pair-spread
9688
+ // ─────────────────────────────────────────────────────────────────────────
9689
+ {
9690
+ name: "market_get_pair_spread",
9691
+ module: "market",
9692
+ description: "Compute spread statistics between two instruments over a lookback window. Returns absolute and ratio spread (mean / stdDev / median / min / max) plus an optional realtime spread snapshot. Use to size pairs trades, detect mean-reversion setups, or compare cross-listed contracts. Results are cached ~60s per (pair, bar, window) tuple. Read-only, no credentials required.\nDo NOT use to fetch raw candles (use market_get_candles) or single-instrument OI/funding (use market_get_oi_history / market_filter_oi_change).",
9693
+ isWrite: false,
9694
+ inputSchema: {
9695
+ type: "object",
9696
+ properties: {
9697
+ instIdA: {
9698
+ type: "string",
9699
+ description: "First instrument ID, e.g. BTC-USDT-SWAP"
9700
+ },
9701
+ instIdB: {
9702
+ type: "string",
9703
+ description: "Second instrument ID; must differ from instIdA"
9704
+ },
9705
+ bar: {
9706
+ type: "string",
9707
+ enum: ["5m", "15m"],
9708
+ description: "Bar size for the spread series. Default 15m. 1m is not supported."
9709
+ },
9710
+ window: {
9711
+ type: "string",
9712
+ description: "Lookback window. Format <n><unit>, units m/H/D/W (case-sensitive), max 1W. Default 1W."
9713
+ },
9714
+ backtestTime: {
9715
+ type: "number",
9716
+ description: "Anchor timestamp (ms epoch) for backtest mode. When provided, realtime is omitted."
9717
+ }
9718
+ },
9719
+ required: ["instIdA", "instIdB"]
9720
+ },
9721
+ handler: async (rawArgs, context) => {
9722
+ const args = asRecord(rawArgs);
9723
+ const body = compactObject({
9724
+ instIdA: requireString(args, "instIdA"),
9725
+ instIdB: requireString(args, "instIdB"),
9726
+ bar: readString(args, "bar"),
9727
+ window: readString(args, "window"),
9728
+ backtestTime: readNumber(args, "backtestTime")
9729
+ });
9730
+ const response = await context.client.publicPost(
9731
+ "/api/v5/aigc/mcp/pair-spread",
9732
+ body,
9733
+ publicRateLimit("market_get_pair_spread", 5)
9734
+ );
9735
+ return normalizeResponse(response);
9736
+ }
9682
9737
  }
9683
9738
  ];
9684
9739
  }
@@ -9915,7 +9970,7 @@ var D_COINS_SENTIMENT = 'Comma-separated uppercase ticker symbols, max 20 (e.g.
9915
9970
  var D_LANGUAGE = "Content language: zh-CN or en-US. Infer from user's message. No server default.";
9916
9971
  var D_BEGIN = "Start time, Unix epoch milliseconds. API defaults to 72 hours ago when omitted. Pass explicitly for older topics (e.g. 'last 30 days'). Max range: 180 days. Parse relative time if given.";
9917
9972
  var D_END = "End time, Unix epoch milliseconds. Parse relative time if given. Omit for no upper bound.";
9918
- var D_IMPORTANCE = "Importance filter: 'low' returns all news (both low and high importance); 'high' narrows to major/breaking news only. Omitted \u2192 server default (high-only). Default to 'low' for broad browsing; pass 'high' only when the user explicitly asks for major news.";
9973
+ var D_IMPORTANCE = "Importance filter: 'low' returns all news (both low and high importance); 'high' narrows to major/breaking news only. Omitted -> server default (high-only). Default to 'low' for broad browsing; pass 'high' only when the user explicitly asks for major news.";
9919
9974
  var D_PLATFORM = "Filter by news source. Use values from news_get_domains (e.g. blockbeats, odaily_flash). Omit for all sources.";
9920
9975
  var D_LIMIT = "Number of results (default 10, max 50).";
9921
9976
  function registerNewsTools() {
@@ -10014,7 +10069,7 @@ function registerNewsTools() {
10014
10069
  {
10015
10070
  name: "news_search",
10016
10071
  module: "news",
10017
- description: "Search crypto news by keyword with optional filters. Use when user provides specific search terms: 'SEC ETF news', 'stablecoin regulation'. Keyword is optional \u2014 pass sentiment alone to browse by sentiment direction. For coin-only queries prefer news_get_by_coin.",
10072
+ description: "Search crypto news by keyword with optional filters. Use when user provides specific search terms: 'SEC ETF news', 'stablecoin regulation'. Keyword is optional - pass sentiment alone to browse by sentiment direction. For coin-only queries prefer news_get_by_coin.",
10018
10073
  isWrite: false,
10019
10074
  inputSchema: {
10020
10075
  type: "object",
@@ -10124,7 +10179,7 @@ function registerNewsTools() {
10124
10179
  {
10125
10180
  name: "news_get_coin_sentiment",
10126
10181
  module: "news",
10127
- description: "Get sentiment snapshot or time-series trend for coins. Returns bullish/bearish ratios and mention counts. Pass trendPoints for trend data (1h\u219224 points, 4h\u21926, 24h\u21927). Use when user asks about coin sentiment, sentiment trend, or how bullish/bearish a coin is. For ranking all coins by sentiment, use news_get_sentiment_ranking instead.",
10182
+ description: "Get sentiment snapshot or time-series trend for coins. Returns bullish/bearish ratios and mention counts. Pass trendPoints for trend data (1h->24 points, 4h->6, 24h->7). Use when user asks about coin sentiment, sentiment trend, or how bullish/bearish a coin is. For ranking all coins by sentiment, use news_get_sentiment_ranking instead.",
10128
10183
  isWrite: false,
10129
10184
  inputSchema: {
10130
10185
  type: "object",
@@ -10137,7 +10192,7 @@ function registerNewsTools() {
10137
10192
  },
10138
10193
  trendPoints: {
10139
10194
  type: "number",
10140
- description: "Trend data points. Pass for time-series trend; omit for snapshot. Guide: 1h\u219224, 4h\u21926, 24h\u21927."
10195
+ description: "Trend data points. Pass for time-series trend; omit for snapshot. Guide: 1h->24, 4h->6, 24h->7."
10141
10196
  }
10142
10197
  },
10143
10198
  required: ["coins"]
@@ -10204,7 +10259,7 @@ function registerNewsTools() {
10204
10259
  {
10205
10260
  name: "news_list_calendar_regions",
10206
10261
  module: "news",
10207
- description: "List all valid region values for the economic calendar. Returns a string array of snake_case region codes. Call this when economic-calendar returns empty results to verify the region value, or to help the user pick a valid region. Do NOT use to list news source platforms \u2014 use news_get_domains instead.",
10262
+ description: "List all valid region values for the economic calendar. Returns a string array of snake_case region codes. Call this when economic-calendar returns empty results to verify the region value, or to help the user pick a valid region. Do NOT use to list news source platforms - use news_get_domains instead.",
10208
10263
  isWrite: false,
10209
10264
  inputSchema: { type: "object", properties: {}, required: [] },
10210
10265
  handler: async () => ({ data: CALENDAR_REGIONS })
@@ -10212,15 +10267,15 @@ function registerNewsTools() {
10212
10267
  {
10213
10268
  name: "news_get_economic_calendar",
10214
10269
  module: "news",
10215
- description: "Get macro-economic calendar data (GDP, CPI, NFP, interest rate decisions, PMI, etc.). Returns scheduled and released economic events with forecast, previous, and actual values. Use when user asks about economic calendar, macro data, or specific indicators like NFP/CPI/GDP/FOMC. Do NOT use for news articles or sentiment \u2014 use news_get_latest or news_search instead.",
10270
+ description: "Get macro-economic calendar data (GDP, CPI, NFP, interest rate decisions, PMI, etc.). Returns scheduled and released economic events with forecast, previous, and actual values. Use when user asks about economic calendar, macro data, or specific indicators like NFP/CPI/GDP/FOMC. Do NOT use for news articles or sentiment - use news_get_latest or news_search instead.",
10216
10271
  isWrite: false,
10217
10272
  inputSchema: {
10218
10273
  type: "object",
10219
10274
  properties: {
10220
10275
  region: { type: "string", description: "Country/region filter in snake_case (e.g. united_states, euro_area, japan). Invalid values return empty results silently. If empty results, call news_list_calendar_regions to verify the value." },
10221
10276
  importance: { type: "string", enum: ["1", "2", "3"], description: "Importance level: 1=low, 2=medium, 3=high. Omit for all levels." },
10222
- before: { type: "string", description: "Lower time bound \u2014 returns events NEWER than this timestamp (reversed semantics). Pair with 'after' for future-event windows. Unix ms." },
10223
- after: { type: "string", description: "Upper time bound \u2014 returns events OLDER than this timestamp (reversed semantics). Default=now (returns past events). Pair with 'before' for a bounded window. Unix ms." },
10277
+ before: { type: "string", description: "Lower time bound - returns events NEWER than this timestamp (reversed semantics). Pair with 'after' for future-event windows. Unix ms." },
10278
+ after: { type: "string", description: "Upper time bound - returns events OLDER than this timestamp (reversed semantics). Default=now (returns past events). Pair with 'before' for a bounded window. Unix ms." },
10224
10279
  limit: { type: "number", minimum: 1, maximum: 100, description: "Number of results (default 100, max 100)." }
10225
10280
  },
10226
10281
  required: []
@@ -10378,7 +10433,7 @@ function registerOptionAlgoTools() {
10378
10433
  {
10379
10434
  name: "option_amend_algo_order",
10380
10435
  module: "option",
10381
- description: "Amend a pending OPTION algo order (modify TP/SL prices or size). Also covers TP/SL orders attached when placing the main order \u2014 look up algoId via option_get_algo_orders first.",
10436
+ description: "Amend a pending OPTION algo order (modify TP/SL prices or size). Also covers TP/SL orders attached when placing the main order - look up algoId via option_get_algo_orders first.",
10382
10437
  isWrite: true,
10383
10438
  inputSchema: {
10384
10439
  type: "object",
@@ -10541,7 +10596,7 @@ function registerOptionTools() {
10541
10596
  {
10542
10597
  name: "option_place_order",
10543
10598
  module: "option",
10544
- description: "Place OPTION order. instId: {uly}-{expiry}-{strike}-C/P, e.g. BTC-USD-241227-50000-C. Before placing, use market_get_instruments to get ctVal (contract face value) \u2014 do NOT assume contract sizes. [CAUTION] Executes real trades.",
10599
+ description: "Place OPTION order. instId: {uly}-{expiry}-{strike}-C/P, e.g. BTC-USD-241227-50000-C. Before placing, use market_get_instruments to get ctVal (contract face value) - do NOT assume contract sizes. [CAUTION] Executes real trades.",
10545
10600
  isWrite: true,
10546
10601
  inputSchema: {
10547
10602
  type: "object",
@@ -11315,7 +11370,7 @@ function registerSpotTradeTools() {
11315
11370
  {
11316
11371
  name: "spot_amend_algo_order",
11317
11372
  module: "spot",
11318
- description: "Amend a pending spot algo order (modify TP/SL prices or size). Also covers TP/SL orders attached when placing the main order \u2014 look up algoId via spot_get_algo_orders first.",
11373
+ description: "Amend a pending spot algo order (modify TP/SL prices or size). Also covers TP/SL orders attached when placing the main order - look up algoId via spot_get_algo_orders first.",
11319
11374
  isWrite: true,
11320
11375
  inputSchema: {
11321
11376
  type: "object",
@@ -11384,7 +11439,7 @@ function registerSpotTradeTools() {
11384
11439
  {
11385
11440
  name: "spot_get_algo_orders",
11386
11441
  module: "spot",
11387
- description: "Query spot algo orders (TP/SL) \u2014 pending or history.",
11442
+ description: "Query spot algo orders (TP/SL) - pending or history.",
11388
11443
  isWrite: false,
11389
11444
  inputSchema: {
11390
11445
  type: "object",
@@ -11677,16 +11732,16 @@ function registerSpotTradeTools() {
11677
11732
  }
11678
11733
  },
11679
11734
  // ── set_leverage (SPOT margin: instId-level isolated OR ccy-level cross) ──
11680
- // Covers OKX scenarios 15 (everything except SWAP/FUTURES, which are in
11735
+ // Covers OKX scenarios 1-5 (everything except SWAP/FUTURES, which are in
11681
11736
  // contract-trade.ts). Callers supply exactly one of {instId, ccy}:
11682
- // • instId + isolated scenario 1 (pair-level margin)
11683
- // • instId + cross scenario 3 (contract-mode pair-level cross margin)
11684
- // • ccy + cross scenarios 2 / 4 / 5 (spot/multi-ccy/PM currency-level cross)
11737
+ // • instId + isolated -> scenario 1 (pair-level margin)
11738
+ // • instId + cross -> scenario 3 (contract-mode pair-level cross margin)
11739
+ // • ccy + cross -> scenarios 2 / 4 / 5 (spot/multi-ccy/PM currency-level cross)
11685
11740
  // Not applicable: posSide (spot has no long/short hedge).
11686
11741
  {
11687
11742
  name: "spot_set_leverage",
11688
11743
  module: "spot",
11689
- description: "Set leverage for SPOT margin trading. Provide exactly ONE of instId (pair-level) or ccy (currency-level cross, requires borrow-enabled account / multi-ccy / portfolio margin). [CAUTION] Changes risk parameters.\nScenarios:\n \u2022 instId + mgnMode=isolated \u2192 pair-level isolated margin\n \u2022 instId + mgnMode=cross \u2192 pair-level cross margin (contract-mode account)\n \u2022 ccy + mgnMode=cross \u2192 currency-level cross margin (spot-with-borrow / multi-ccy / portfolio margin)\nWhen ccy is supplied, mgnMode MUST be cross. posSide is never applicable to spot margin.",
11744
+ description: "Set leverage for SPOT margin trading. Provide exactly ONE of instId (pair-level) or ccy (currency-level cross, requires borrow-enabled account / multi-ccy / portfolio margin). [CAUTION] Changes risk parameters.\nScenarios:\n \u2022 instId + mgnMode=isolated -> pair-level isolated margin\n \u2022 instId + mgnMode=cross -> pair-level cross margin (contract-mode account)\n \u2022 ccy + mgnMode=cross -> currency-level cross margin (spot-with-borrow / multi-ccy / portfolio margin)\nWhen ccy is supplied, mgnMode MUST be cross. posSide is never applicable to spot margin.",
11690
11745
  isWrite: true,
11691
11746
  inputSchema: {
11692
11747
  type: "object",
@@ -11701,7 +11756,7 @@ function registerSpotTradeTools() {
11701
11756
  },
11702
11757
  lever: {
11703
11758
  type: "string",
11704
- description: "Leverage multiplier as a positive number string, e.g. '3'. Max depends on the pair (query market_get_instruments \u2192 lever) or the account policy for ccy-level."
11759
+ description: "Leverage multiplier as a positive number string, e.g. '3'. Max depends on the pair (query market_get_instruments -> lever) or the account policy for ccy-level."
11705
11760
  },
11706
11761
  mgnMode: {
11707
11762
  type: "string",
@@ -11722,7 +11777,7 @@ function registerSpotTradeTools() {
11722
11777
  }
11723
11778
  if (instId && ccy) {
11724
11779
  throw new ValidationError(
11725
- `Parameters "instId" and "ccy" are mutually exclusive \u2014 provide only one. instId sets pair-level leverage; ccy sets currency-level cross margin leverage.`
11780
+ `Parameters "instId" and "ccy" are mutually exclusive - provide only one. instId sets pair-level leverage; ccy sets currency-level cross margin leverage.`
11726
11781
  );
11727
11782
  }
11728
11783
  const leverRaw = requireString(args, "lever");
@@ -11769,7 +11824,7 @@ function registerSwapTradeTools() {
11769
11824
  {
11770
11825
  name: "swap_amend_algo_order",
11771
11826
  module: "swap",
11772
- description: "Amend a pending SWAP/FUTURES algo order (modify TP/SL prices or size). Also covers TP/SL orders attached when placing the main order \u2014 look up algoId via swap_get_algo_orders first.",
11827
+ description: "Amend a pending SWAP/FUTURES algo order (modify TP/SL prices or size). Also covers TP/SL orders attached when placing the main order - look up algoId via swap_get_algo_orders first.",
11773
11828
  isWrite: true,
11774
11829
  inputSchema: {
11775
11830
  type: "object",
@@ -11911,9 +11966,9 @@ function readFullConfig() {
11911
11966
  throw new ConfigError(
11912
11967
  `Failed to parse ${path42}: ${err instanceof Error ? err.message : String(err)}`,
11913
11968
  `If your passphrase or keys contain special characters:
11914
- - Contains # \\ " \u2192 use single quotes: passphrase = 'your#pass'
11915
- - Contains ' \u2192 use double quotes: passphrase = "your'pass"
11916
- - Contains both \u2192 use triple quotes: passphrase = '''your'#pass'''
11969
+ - Contains # \\ " -> use single quotes: passphrase = 'your#pass'
11970
+ - Contains ' -> use double quotes: passphrase = "your'pass"
11971
+ - Contains both -> use triple quotes: passphrase = '''your'#pass'''
11917
11972
  Or re-run: okx config init`
11918
11973
  );
11919
11974
  }
@@ -12056,6 +12111,9 @@ async function loadConfig(cli) {
12056
12111
  }
12057
12112
  var CACHE_FILE = join8(homedir6(), ".okx", "update-check.json");
12058
12113
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
12114
+ var NEGATIVE_CHECK_INTERVAL_MS = 60 * 60 * 1e3;
12115
+ var DEFAULT_REGISTRY = "https://registry.npmjs.org/";
12116
+ var FETCH_TIMEOUT_MS = 3e3;
12059
12117
  function readCache2() {
12060
12118
  try {
12061
12119
  if (existsSync4(CACHE_FILE)) {
@@ -12072,7 +12130,73 @@ function writeCache2(cache) {
12072
12130
  } catch {
12073
12131
  }
12074
12132
  }
12133
+ function resolveNpmRegistry() {
12134
+ const envRegistry = process.env.npm_config_registry;
12135
+ if (envRegistry) {
12136
+ return envRegistry.endsWith("/") ? envRegistry : `${envRegistry}/`;
12137
+ }
12138
+ for (const filePath of buildNpmrcCandidates()) {
12139
+ const registry = readNpmrcRegistry(filePath);
12140
+ if (registry) {
12141
+ return registry.endsWith("/") ? registry : `${registry}/`;
12142
+ }
12143
+ }
12144
+ return DEFAULT_REGISTRY;
12145
+ }
12146
+ function buildNpmrcCandidates() {
12147
+ const paths = [];
12148
+ const seen = /* @__PURE__ */ new Set();
12149
+ const add = (p) => {
12150
+ if (!seen.has(p)) {
12151
+ seen.add(p);
12152
+ paths.push(p);
12153
+ }
12154
+ };
12155
+ let dir = process.cwd();
12156
+ const root = dir.startsWith("/") ? "/" : dir.slice(0, 3);
12157
+ while (true) {
12158
+ add(join8(dir, ".npmrc"));
12159
+ if (dir === root) break;
12160
+ const parent = join8(dir, "..");
12161
+ if (parent === dir) break;
12162
+ dir = parent;
12163
+ }
12164
+ add(join8(homedir6(), ".npmrc"));
12165
+ if (process.platform !== "win32") {
12166
+ add("/etc/npmrc");
12167
+ }
12168
+ return paths;
12169
+ }
12170
+ function readNpmrcRegistry(filePath) {
12171
+ try {
12172
+ if (!existsSync4(filePath)) return null;
12173
+ const lines = readFileSync5(filePath, "utf-8").split(/\r?\n/);
12174
+ for (const line of lines) {
12175
+ const trimmed = line.trim();
12176
+ if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
12177
+ const eqIdx = trimmed.indexOf("=");
12178
+ const key = trimmed.slice(0, eqIdx).trim();
12179
+ const value = trimmed.slice(eqIdx + 1).trim();
12180
+ if (key === "registry" && value) return value;
12181
+ }
12182
+ } catch {
12183
+ }
12184
+ return null;
12185
+ }
12186
+ async function fetchFromRegistry(packageName, suffix = "") {
12187
+ const registry = resolveNpmRegistry();
12188
+ const url = `${registry}${encodeURIComponent(packageName)}${suffix}`;
12189
+ try {
12190
+ return await fetch(url, {
12191
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
12192
+ headers: { accept: "application/json" }
12193
+ });
12194
+ } catch {
12195
+ return null;
12196
+ }
12197
+ }
12075
12198
  function isNewerVersion(current, latest) {
12199
+ if (!latest) return false;
12076
12200
  const parse2 = (v) => v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10));
12077
12201
  const [cMaj, cMin, cPat] = parse2(current);
12078
12202
  const [lMaj, lMin, lPat] = parse2(latest);
@@ -12082,14 +12206,8 @@ function isNewerVersion(current, latest) {
12082
12206
  }
12083
12207
  async function fetchDistTags(packageName) {
12084
12208
  try {
12085
- const controller = new AbortController();
12086
- const timeout = setTimeout(() => controller.abort(), 3e3);
12087
- const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}`, {
12088
- signal: controller.signal,
12089
- headers: { accept: "application/json" }
12090
- });
12091
- clearTimeout(timeout);
12092
- if (!res.ok) return null;
12209
+ const res = await fetchFromRegistry(packageName);
12210
+ if (!res || !res.ok) return null;
12093
12211
  const data = await res.json();
12094
12212
  return data["dist-tags"] ?? null;
12095
12213
  } catch {
@@ -12098,14 +12216,8 @@ async function fetchDistTags(packageName) {
12098
12216
  }
12099
12217
  async function fetchLatestVersion(packageName) {
12100
12218
  try {
12101
- const controller = new AbortController();
12102
- const timeout = setTimeout(() => controller.abort(), 3e3);
12103
- const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`, {
12104
- signal: controller.signal,
12105
- headers: { accept: "application/json" }
12106
- });
12107
- clearTimeout(timeout);
12108
- if (!res.ok) return null;
12219
+ const res = await fetchFromRegistry(packageName, "/latest");
12220
+ if (!res || !res.ok) return null;
12109
12221
  const data = await res.json();
12110
12222
  return data.version ?? null;
12111
12223
  } catch {
@@ -12114,26 +12226,31 @@ async function fetchLatestVersion(packageName) {
12114
12226
  }
12115
12227
  function refreshCacheInBackground(packageName) {
12116
12228
  fetchLatestVersion(packageName).then((latest) => {
12117
- if (!latest) return;
12118
12229
  const cache = readCache2();
12119
- cache[packageName] = { latestVersion: latest, checkedAt: Date.now() };
12230
+ if (latest) {
12231
+ cache[packageName] = { latestVersion: latest, checkedAt: Date.now() };
12232
+ } else {
12233
+ cache[packageName] = { latestVersion: null, checkedAt: Date.now(), failed: true };
12234
+ }
12120
12235
  writeCache2(cache);
12121
12236
  }).catch(() => {
12122
12237
  });
12123
12238
  }
12124
12239
  function checkForUpdates(packageName, currentVersion) {
12240
+ if (process.env.OKX_UPDATE_CHECK === "false") return;
12125
12241
  const cache = readCache2();
12126
12242
  const entry = cache[packageName];
12127
- if (entry && isNewerVersion(currentVersion, entry.latestVersion)) {
12243
+ if (entry && entry.latestVersion && isNewerVersion(currentVersion, entry.latestVersion)) {
12128
12244
  process.stderr.write(
12129
12245
  `
12130
- Update available for ${packageName}: ${currentVersion} \u2192 ${entry.latestVersion}
12246
+ Update available for ${packageName}: ${currentVersion} -> ${entry.latestVersion}
12131
12247
  Run: npm install -g ${packageName}
12132
12248
 
12133
12249
  `
12134
12250
  );
12135
12251
  }
12136
- if (!entry || Date.now() - entry.checkedAt > CHECK_INTERVAL_MS) {
12252
+ const ttl = entry?.failed ? NEGATIVE_CHECK_INTERVAL_MS : CHECK_INTERVAL_MS;
12253
+ if (!entry || Date.now() - entry.checkedAt > ttl) {
12137
12254
  refreshCacheInBackground(packageName);
12138
12255
  }
12139
12256
  }
@@ -12318,7 +12435,7 @@ function mergeJsonConfig(configPath, serverName, entry) {
12318
12435
  }
12319
12436
  const backupPath = configPath + ".bak";
12320
12437
  fs3.copyFileSync(configPath, backupPath);
12321
- process.stdout.write(` Backup \u2192 ${backupPath}
12438
+ process.stdout.write(` Backup -> ${backupPath}
12322
12439
  `);
12323
12440
  }
12324
12441
  if (typeof data.mcpServers !== "object" || data.mcpServers === null) {
@@ -12389,7 +12506,7 @@ function validateRedirect(res, requestUrl, redirectCount, maxRedirects) {
12389
12506
  }
12390
12507
  const location = res.headers.location;
12391
12508
  if (requestUrl.startsWith("https") && !location.startsWith("https")) {
12392
- throw new Error("Refused HTTPS \u2192 HTTP redirect downgrade");
12509
+ throw new Error("Refused HTTPS -> HTTP redirect downgrade");
12393
12510
  }
12394
12511
  return location;
12395
12512
  }
@@ -12987,10 +13104,10 @@ async function cmdAuthLogin(args) {
12987
13104
  status: "skipped",
12988
13105
  reason: "api_key_configured",
12989
13106
  profile: apiKeyProfile,
12990
- message: `API key already configured (profile: ${apiKeyProfile}). OAuth login skipped \u2014 API key will be used automatically.`
13107
+ message: `API key already configured (profile: ${apiKeyProfile}). OAuth login skipped - API key will be used automatically.`
12991
13108
  }));
12992
13109
  } else {
12993
- outputLine(`API key already configured (profile: ${apiKeyProfile}). OAuth login skipped \u2014 API key will be used automatically.`);
13110
+ outputLine(`API key already configured (profile: ${apiKeyProfile}). OAuth login skipped - API key will be used automatically.`);
12994
13111
  }
12995
13112
  return;
12996
13113
  }
@@ -13509,7 +13626,7 @@ function checkToolCount(report, configuredClients, getSpecs = allToolSpecs) {
13509
13626
  if (totalCount > limits.total) {
13510
13627
  warn(
13511
13628
  "tool count",
13512
- `${totalCount} tools loaded \u2014 exceeds ${name} limit (${limits.total} total / ${limits.perServer} per server)`,
13629
+ `${totalCount} tools loaded - exceeds ${name} limit (${limits.total} total / ${limits.perServer} per server)`,
13513
13630
  [
13514
13631
  `Use --modules to reduce: okx-trade-mcp --modules ${defaultModulesArg} (${defaultCount} tools)`
13515
13632
  ]
@@ -13519,7 +13636,7 @@ function checkToolCount(report, configuredClients, getSpecs = allToolSpecs) {
13519
13636
  } else if (totalCount > limits.perServer) {
13520
13637
  warn(
13521
13638
  "tool count",
13522
- `${totalCount} tools loaded \u2014 exceeds ${name} per-server limit (${limits.perServer})`,
13639
+ `${totalCount} tools loaded - exceeds ${name} per-server limit (${limits.perServer})`,
13523
13640
  [
13524
13641
  `Use --modules to reduce: okx-trade-mcp --modules ${defaultModulesArg} (${defaultCount} tools)`
13525
13642
  ]
@@ -13586,7 +13703,7 @@ function checkMcpLogs(report) {
13586
13703
  } catch (_e) {
13587
13704
  }
13588
13705
  }
13589
- ok("log file", "(not found \u2014 logs only appear after MCP server has been started)");
13706
+ ok("log file", "(not found - logs only appear after MCP server has been started)");
13590
13707
  report.add("mcp_log", "not_found");
13591
13708
  }
13592
13709
  function parseHandshakeResponse(line) {
@@ -13662,7 +13779,7 @@ async function checkStdioHandshake(entryPath, report) {
13662
13779
  const parsed = parseHandshakeResponse(line);
13663
13780
  if (!parsed) continue;
13664
13781
  if (parsed.ok) {
13665
- ok("handshake", `OK \u2014 ${parsed.serverName} v${parsed.serverVer}`);
13782
+ ok("handshake", `OK - ${parsed.serverName} v${parsed.serverVer}`);
13666
13783
  report.add("handshake", `OK ${parsed.serverName}@${parsed.serverVer}`);
13667
13784
  } else {
13668
13785
  fail("handshake", `JSON-RPC error: ${parsed.errMsg}`, [
@@ -13689,7 +13806,7 @@ async function checkStdioHandshake(entryPath, report) {
13689
13806
  function checkModuleLoading(entryPath, report) {
13690
13807
  section("Module Loading");
13691
13808
  if (!entryPath) {
13692
- ok("module load", "(skipped \u2014 entry point not found)");
13809
+ ok("module load", "(skipped - entry point not found)");
13693
13810
  report.add("module_load", "skipped");
13694
13811
  return true;
13695
13812
  }
@@ -13732,7 +13849,7 @@ async function cmdDiagnoseMcp(options = {}) {
13732
13849
  handshakePassed = await checkStdioHandshake(entryPath, report);
13733
13850
  } else {
13734
13851
  section("stdio Handshake");
13735
- ok("handshake", "(skipped \u2014 entry point not available)");
13852
+ ok("handshake", "(skipped - entry point not available)");
13736
13853
  report.add("handshake", "skipped");
13737
13854
  handshakePassed = true;
13738
13855
  }
@@ -13751,7 +13868,7 @@ async function cmdDiagnoseMcp(options = {}) {
13751
13868
 
13752
13869
  // src/commands/diagnose.ts
13753
13870
  var CLI_VERSION = readCliVersion();
13754
- var GIT_HASH = true ? "cd99d487" : "dev";
13871
+ var GIT_HASH = true ? "ce35abd7" : "dev";
13755
13872
  function maskKey2(key) {
13756
13873
  if (!key) return "(not set)";
13757
13874
  if (key.length <= 8) return "****";
@@ -14008,13 +14125,13 @@ async function checkPilot(report) {
14008
14125
  report.add("pilot_binary", `installed (${local.platform ?? "unknown"})`);
14009
14126
  const cdnChecksum = await fetchCdnChecksum(void 0, 5e3);
14010
14127
  if (!cdnChecksum) {
14011
- warn("Pilot checksum", "CDN unreachable \u2014 cannot verify");
14128
+ warn("Pilot checksum", "CDN unreachable - cannot verify");
14012
14129
  report.add("pilot_checksum", "CDN unreachable");
14013
14130
  } else if (cdnChecksum.sha256 === local.sha256) {
14014
14131
  ok("Pilot checksum", `match (${cdnChecksum.source})`);
14015
14132
  report.add("pilot_checksum", `match (${cdnChecksum.source})`);
14016
14133
  } else {
14017
- warn("Pilot checksum", "mismatch \u2014 update available", ["Run: okx pilot install"]);
14134
+ warn("Pilot checksum", "mismatch - update available", ["Run: okx pilot install"]);
14018
14135
  report.add("pilot_checksum", "mismatch");
14019
14136
  }
14020
14137
  try {
@@ -14043,9 +14160,9 @@ function checkConfigFile(report) {
14043
14160
  const msg = e instanceof Error ? e.message : String(e);
14044
14161
  fail("Config parse", msg, [
14045
14162
  `If passphrase contains special characters (# \\ " '), wrap in quotes:`,
14046
- ` Contains # \\ " \u2192 passphrase = 'value'`,
14047
- ` Contains ' \u2192 passphrase = "value"`,
14048
- " Contains both \u2192 passphrase = '''value'''",
14163
+ ` Contains # \\ " -> passphrase = 'value'`,
14164
+ ` Contains ' -> passphrase = "value"`,
14165
+ " Contains both -> passphrase = '''value'''",
14049
14166
  "Or re-run: okx config init"
14050
14167
  ]);
14051
14168
  report.add("config_parse", `FAIL ${msg}`);
@@ -14144,13 +14261,13 @@ function printResult(result, json) {
14144
14261
  break;
14145
14262
  case "update-available":
14146
14263
  process.stderr.write(
14147
- `[info] Update available: ${result.currentVersion} \u2192 ${result.latestVersion}
14264
+ `[info] Update available: ${result.currentVersion} -> ${result.latestVersion}
14148
14265
  Run: okx upgrade
14149
14266
  `
14150
14267
  );
14151
14268
  break;
14152
14269
  case "updated":
14153
- process.stderr.write(`[ok] Upgraded: ${result.currentVersion} \u2192 ${result.latestVersion}
14270
+ process.stderr.write(`[ok] Upgraded: ${result.currentVersion} -> ${result.latestVersion}
14154
14271
  `);
14155
14272
  break;
14156
14273
  case "error":
@@ -14327,6 +14444,11 @@ var CLI_REGISTRY = {
14327
14444
  toolName: "market_filter_oi_change",
14328
14445
  usage: "okx market oi-change --instType <SWAP|FUTURES> [--bar <5m|15m|1H|4H|1D>] [--sortBy <oiUsd|oiDeltaUsd|oiDeltaPct|absOiDeltaPct|volUsd24h|fundingRate|last>] [--sortOrder <asc|desc>] [--limit <1-100>] [--minOiUsd <n>] [--minVolUsd24h <n>] [--minAbsOiDeltaPct <n>]",
14329
14446
  description: "Find instruments with largest OI changes over a bar window (accumulation/distribution scanner)"
14447
+ },
14448
+ "pair-spread": {
14449
+ toolName: "market_get_pair_spread",
14450
+ usage: "okx market pair-spread <instIdA> <instIdB> [--bar <5m|15m>] [--window <window>] [--backtest-time <ms>] [--json]",
14451
+ description: "Compute spread statistics (abs + ratio) between two instruments over a lookback window"
14330
14452
  }
14331
14453
  },
14332
14454
  subgroups: {
@@ -14549,7 +14671,7 @@ var CLI_REGISTRY = {
14549
14671
  leverage: {
14550
14672
  toolName: "swap_set_leverage",
14551
14673
  usage: "okx swap leverage --instId <id> --lever <positive-number> --mgnMode <cross|isolated> [--posSide <long|short>]",
14552
- description: "Set leverage for a swap instrument. posSide is REQUIRED when mgnMode=isolated and account is in hedge mode \u2014 must be set for BOTH long and short separately. Not supported for portfolio margin + cross."
14674
+ description: "Set leverage for a swap instrument. posSide is REQUIRED when mgnMode=isolated and account is in hedge mode - must be set for BOTH long and short separately. Not supported for portfolio margin + cross."
14553
14675
  },
14554
14676
  "get-leverage": {
14555
14677
  toolName: "swap_get_leverage",
@@ -14648,7 +14770,7 @@ var CLI_REGISTRY = {
14648
14770
  leverage: {
14649
14771
  toolName: "futures_set_leverage",
14650
14772
  usage: "okx futures leverage --instId <id> --lever <positive-number> --mgnMode <cross|isolated> [--posSide <long|short>]",
14651
- description: "Set leverage for a futures instrument. posSide is REQUIRED when mgnMode=isolated and account is in hedge mode \u2014 must be set for BOTH long and short separately. Not supported for portfolio margin + cross."
14773
+ description: "Set leverage for a futures instrument. posSide is REQUIRED when mgnMode=isolated and account is in hedge mode - must be set for BOTH long and short separately. Not supported for portfolio margin + cross."
14652
14774
  },
14653
14775
  batch: {
14654
14776
  toolName: "futures_batch_orders",
@@ -14775,10 +14897,10 @@ var CLI_REGISTRY = {
14775
14897
  },
14776
14898
  // ── earn ───────────────────────────────────────────────────────────────────
14777
14899
  earn: {
14778
- description: "Earn products \u2014 Simple Earn, On-chain Earn, DCD, Flash Earn, and Auto-Earn",
14900
+ description: "Earn products - Simple Earn, On-chain Earn, DCD, Flash Earn, and Auto-Earn",
14779
14901
  subgroups: {
14780
14902
  savings: {
14781
- description: "Simple Earn \u2014 flexible savings, fixed-term, and lending",
14903
+ description: "Simple Earn - flexible savings, fixed-term, and lending",
14782
14904
  commands: {
14783
14905
  balance: {
14784
14906
  toolName: "earn_get_savings_balance",
@@ -14828,7 +14950,7 @@ var CLI_REGISTRY = {
14828
14950
  }
14829
14951
  },
14830
14952
  onchain: {
14831
- description: "On-chain Earn \u2014 staking and DeFi products",
14953
+ description: "On-chain Earn - staking and DeFi products",
14832
14954
  commands: {
14833
14955
  offers: {
14834
14956
  toolName: "onchain_earn_get_offers",
@@ -14863,7 +14985,7 @@ var CLI_REGISTRY = {
14863
14985
  }
14864
14986
  },
14865
14987
  "auto-earn": {
14866
- description: "Auto-earn \u2014 automatically lend, stake, or earn on idle assets",
14988
+ description: "Auto-earn - automatically lend, stake, or earn on idle assets",
14867
14989
  commands: {
14868
14990
  status: {
14869
14991
  // CLI reads from account_get_balance; earn_auto_set is covered by 'on' command below
@@ -14885,7 +15007,7 @@ var CLI_REGISTRY = {
14885
15007
  }
14886
15008
  },
14887
15009
  "flash-earn": {
14888
- description: "Flash Earn \u2014 browse short-window earn projects by status",
15010
+ description: "Flash Earn - browse short-window earn projects by status",
14889
15011
  commands: {
14890
15012
  projects: {
14891
15013
  toolName: "earn_get_flash_earn_projects",
@@ -14895,7 +15017,7 @@ var CLI_REGISTRY = {
14895
15017
  }
14896
15018
  },
14897
15019
  dcd: {
14898
- description: "DCD (Dual Currency Deposit) \u2014 structured products with fixed yield",
15020
+ description: "DCD (Dual Currency Deposit) - structured products with fixed yield",
14899
15021
  commands: {
14900
15022
  pairs: {
14901
15023
  toolName: "dcd_get_currency_pairs",
@@ -14936,7 +15058,7 @@ var CLI_REGISTRY = {
14936
15058
  description: "Trading bot strategies (grid, dca)",
14937
15059
  subgroups: {
14938
15060
  grid: {
14939
- description: "Grid trading bot \u2014 create, monitor, and stop grid orders",
15061
+ description: "Grid trading bot - create, monitor, and stop grid orders",
14940
15062
  commands: {
14941
15063
  orders: {
14942
15064
  toolName: "grid_get_orders",
@@ -14971,7 +15093,7 @@ var CLI_REGISTRY = {
14971
15093
  }
14972
15094
  },
14973
15095
  dca: {
14974
- description: "DCA (Martingale) bot \u2014 spot or contract recurring buys",
15096
+ description: "DCA (Martingale) bot - spot or contract recurring buys",
14975
15097
  commands: {
14976
15098
  orders: {
14977
15099
  toolName: "dca_get_orders",
@@ -15004,7 +15126,7 @@ var CLI_REGISTRY = {
15004
15126
  },
15005
15127
  // ── event ──────────────────────────────────────────────────────────────────
15006
15128
  event: {
15007
- description: "Event contracts \u2014 binary prediction markets (YES/NO, UP/DOWN)",
15129
+ description: "Event contracts - binary prediction markets (YES/NO, UP/DOWN)",
15008
15130
  commands: {
15009
15131
  browse: {
15010
15132
  toolName: "event_browse",
@@ -15055,7 +15177,7 @@ var CLI_REGISTRY = {
15055
15177
  },
15056
15178
  // ── smartmoney ─────────────────────────────────────────────────────────────
15057
15179
  smartmoney: {
15058
- description: "Smart money analytics \u2014 trader leaderboard, consensus signals, and position analysis",
15180
+ description: "Smart money analytics - trader leaderboard, consensus signals, and position analysis",
15059
15181
  commands: {
15060
15182
  "traders-by-filter": {
15061
15183
  toolName: "smartmoney_get_traders_by_filter",
@@ -15227,7 +15349,7 @@ var CLI_REGISTRY = {
15227
15349
  },
15228
15350
  // ── skill ──────────────────────────────────────────────────────────────────
15229
15351
  skill: {
15230
- description: "OKX Skills Marketplace \u2014 search, install, and manage agent skills",
15352
+ description: "OKX Skills Marketplace - search, install, and manage agent skills",
15231
15353
  commands: {
15232
15354
  search: {
15233
15355
  toolName: "skills_search",
@@ -15393,7 +15515,7 @@ function cmdListTools(json) {
15393
15515
  }
15394
15516
  const lines = [
15395
15517
  "",
15396
- `OKX CLI v${data.version} \u2014 ${data.totalTools} tool-backed commands across ${data.modules.length} modules`,
15518
+ `OKX CLI v${data.version} - ${data.totalTools} tool-backed commands across ${data.modules.length} modules`,
15397
15519
  "",
15398
15520
  "Modules:"
15399
15521
  ];
@@ -15989,12 +16111,12 @@ var CLI_OPTIONS = {
15989
16111
  instCcyList: { type: "string" },
15990
16112
  topInstruments: { type: "string" },
15991
16113
  asOfTime: { type: "string" },
15992
- // smartmoney pool filters leaderboard (numeric thresholds)
16114
+ // smartmoney pool filters - leaderboard (numeric thresholds)
15993
16115
  minPnl: { type: "string" },
15994
16116
  minWinRate: { type: "string" },
15995
16117
  maxDrawdown: { type: "string" },
15996
16118
  minAum: { type: "string" },
15997
- // smartmoney pool filters signal endpoints (enum tiers)
16119
+ // smartmoney pool filters - signal endpoints (enum tiers)
15998
16120
  pnlTier: { type: "string" },
15999
16121
  winRateTier: { type: "string" },
16000
16122
  maxDrawdownTier: { type: "string" },
@@ -16031,6 +16153,8 @@ var CLI_OPTIONS = {
16031
16153
  params: { type: "string" },
16032
16154
  list: { type: "boolean", default: false },
16033
16155
  "backtest-time": { type: "string" },
16156
+ // pair-spread
16157
+ window: { type: "string" },
16034
16158
  // news
16035
16159
  coins: { type: "string" },
16036
16160
  sentiment: { type: "string" },
@@ -16075,7 +16199,7 @@ var CLI_OPTIONS = {
16075
16199
  settleCcy: { type: "string" },
16076
16200
  ts: { type: "string" },
16077
16201
  minAbsOiDeltaPct: { type: "string" },
16078
- // diagnostics cli/mcp/all/output are diagnose-specific; verbose is shared
16202
+ // diagnostics - cli/mcp/all/output are diagnose-specific; verbose is shared
16079
16203
  verbose: { type: "boolean", default: false },
16080
16204
  mcp: { type: "boolean", default: false },
16081
16205
  // diagnose --mcp only: MCP server checks
@@ -16415,7 +16539,7 @@ async function cmdMarketInstrumentsByCategory(run, opts) {
16415
16539
  "7": "Bonds"
16416
16540
  };
16417
16541
  const label = CATEGORY_LABELS[opts.instCategory] ?? opts.instCategory;
16418
- process.stdout.write(`instCategory=${opts.instCategory} (${label}) \u2014 ${items?.length ?? 0} instruments
16542
+ process.stdout.write(`instCategory=${opts.instCategory} (${label}) - ${items?.length ?? 0} instruments
16419
16543
 
16420
16544
  `);
16421
16545
  printTable(
@@ -16554,6 +16678,42 @@ async function cmdMarketOiChangeFilter(run, opts) {
16554
16678
  }))
16555
16679
  );
16556
16680
  }
16681
+ async function cmdMarketPairSpread(run, instIdA, instIdB, opts) {
16682
+ const result = await run("market_get_pair_spread", {
16683
+ instIdA,
16684
+ instIdB,
16685
+ bar: opts.bar,
16686
+ window: opts.window,
16687
+ backtestTime: opts.backtestTime
16688
+ });
16689
+ const data = getData2(result);
16690
+ if (opts.json) return printJson(data);
16691
+ const bar = data?.["bar"] ?? "15m";
16692
+ const win = data?.["window"] ?? "1W";
16693
+ const mode = data?.["mode"] ?? "live";
16694
+ outputLine(`${instIdA} / ${instIdB} bar=${bar} window=${win} mode=${mode}`);
16695
+ const rt = data?.["realtime"];
16696
+ if (rt) {
16697
+ outputLine(`realtime lastA=${rt["lastPriceA"]} lastB=${rt["lastPriceB"]} abs=${rt["spreadAbs"]} ratio=${rt["spreadRatio"]}`);
16698
+ }
16699
+ const stats = data?.["statistics"];
16700
+ if (stats) {
16701
+ const abs = stats["absolute"];
16702
+ const ratio = stats["ratio"];
16703
+ const meta = data?.["meta"];
16704
+ outputLine(`samples count=${meta?.["alignedBars"] ?? "?"} start=${stats?.["windowStartTs"] ?? "?"} end=${stats?.["windowEndTs"] ?? "?"}`);
16705
+ printTable([
16706
+ { stat: "mean", absolute: abs?.["mean"], ratio: ratio?.["mean"] },
16707
+ { stat: "stdDev", absolute: abs?.["stdDev"], ratio: ratio?.["stdDev"] },
16708
+ { stat: "median", absolute: abs?.["median"], ratio: ratio?.["median"] },
16709
+ { stat: "min", absolute: abs?.["min"], ratio: ratio?.["min"] },
16710
+ { stat: "max", absolute: abs?.["max"], ratio: ratio?.["max"] }
16711
+ ]);
16712
+ if (meta) {
16713
+ outputLine(`meta requested=${meta["requestedBars"]} aligned=${meta["alignedBars"]} dropped=${meta["droppedBars"]} truncated=${meta["truncated"]}`);
16714
+ }
16715
+ }
16716
+ }
16557
16717
 
16558
16718
  // src/commands/account.ts
16559
16719
  import * as fs7 from "fs";
@@ -18018,7 +18178,7 @@ import { createInterface } from "readline";
18018
18178
  import { spawnSync as spawnSync3 } from "child_process";
18019
18179
  var messages = {
18020
18180
  en: {
18021
- title: "OKX Trade CLI \u2014 Configuration Wizard",
18181
+ title: "OKX Trade CLI - Configuration Wizard",
18022
18182
  selectSite: "Select site:",
18023
18183
  sitePrompt: "Site (1/2/3, default: 1): ",
18024
18184
  demoPrompt: "Use demo trading? (Y/n) ",
@@ -18052,7 +18212,7 @@ Config saved to ${p}
18052
18212
  `
18053
18213
  },
18054
18214
  zh: {
18055
- title: "OKX Trade CLI \u2014 \u914D\u7F6E\u5411\u5BFC",
18215
+ title: "OKX Trade CLI - \u914D\u7F6E\u5411\u5BFC",
18056
18216
  selectSite: "\u8BF7\u9009\u62E9\u7AD9\u70B9:",
18057
18217
  sitePrompt: "\u7AD9\u70B9 (1/2/3, \u9ED8\u8BA4: 1): ",
18058
18218
  demoPrompt: "\u4F7F\u7528\u6A21\u62DF\u76D8\uFF1F(Y/n) ",
@@ -18399,7 +18559,7 @@ async function cmdEarnSetLendingRate(run, opts) {
18399
18559
  return;
18400
18560
  }
18401
18561
  const r = data[0];
18402
- outputLine(`Lending rate set: ${r?.["ccy"]} \u2192 ${r?.["rate"]}`);
18562
+ outputLine(`Lending rate set: ${r?.["ccy"]} -> ${r?.["rate"]}`);
18403
18563
  }
18404
18564
  async function cmdEarnLendingHistory(run, opts) {
18405
18565
  const data = extractData(await run("earn_get_lending_history", { ccy: opts.ccy, limit: opts.limit }));
@@ -18534,7 +18694,7 @@ function printPaginationHint(result) {
18534
18694
  const { hasMore, nextAfter } = pagination;
18535
18695
  if (hasMore !== true) return;
18536
18696
  const cursor = typeof nextAfter === "string" || typeof nextAfter === "number" ? String(nextAfter) : "";
18537
- errorLine(cursor ? `more results \u2014 pass --after ${cursor} for next page` : "more results \u2014 pass --after <cursor> for next page");
18697
+ errorLine(cursor ? `more results - pass --after ${cursor} for next page` : "more results - pass --after <cursor> for next page");
18538
18698
  }
18539
18699
  function signalPoolFilterArgs(o) {
18540
18700
  const result = {};
@@ -19349,8 +19509,8 @@ async function cmdDcdProducts(run, opts) {
19349
19509
  quoteCcy: r["quoteCcy"],
19350
19510
  optType: r["optType"],
19351
19511
  strike: r["strike"],
19352
- // products endpoint returns decimal (e.g. 0.3423 = 34.23%) multiply by 100
19353
- annualizedYield: r["annualizedYield"] ? `${(parseFloat(r["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
19512
+ // products endpoint returns decimal (e.g. 0.3423 = 34.23%) - multiply by 100
19513
+ annualizedYield: r["annualizedYield"] ? `${(parseFloat(r["annualizedYield"]) * 100).toFixed(2)}%` : "-",
19354
19514
  minSize: r["minSize"],
19355
19515
  expTime: r["expTime"] ? new Date(Number(r["expTime"])).toLocaleDateString() : ""
19356
19516
  })));
@@ -19380,8 +19540,8 @@ async function cmdDcdRedeemExecute(run, opts) {
19380
19540
  printKv({
19381
19541
  ordId: r["ordId"],
19382
19542
  state: r["state"],
19383
- redeemSz: q["redeemSz"] ? `${parseFloat(q["redeemSz"]).toFixed(8)} ${q["redeemCcy"]}` : "\u2014",
19384
- termRate: q["termRate"] ? `${(parseFloat(q["termRate"]) * 100).toFixed(2)}%` : "\u2014"
19543
+ redeemSz: q["redeemSz"] ? `${parseFloat(q["redeemSz"]).toFixed(8)} ${q["redeemCcy"]}` : "-",
19544
+ termRate: q["termRate"] ? `${(parseFloat(q["termRate"]) * 100).toFixed(2)}%` : "-"
19385
19545
  });
19386
19546
  }
19387
19547
  async function cmdDcdOrderState(run, opts) {
@@ -19434,7 +19594,7 @@ async function cmdDcdOrders(run, opts) {
19434
19594
  quoteCcy: r["quoteCcy"],
19435
19595
  strike: r["strike"],
19436
19596
  notionalSz: r["notionalSz"],
19437
- annualizedYield: r["annualizedYield"] ? `${(parseFloat(r["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
19597
+ annualizedYield: r["annualizedYield"] ? `${(parseFloat(r["annualizedYield"]) * 100).toFixed(2)}%` : "-",
19438
19598
  yieldSz: r["yieldSz"],
19439
19599
  settleTime: r["settleTime"] ? new Date(Number(r["settleTime"])).toLocaleDateString() : "",
19440
19600
  // scheduled settlement time
@@ -19474,7 +19634,7 @@ async function cmdDcdQuoteAndBuy(run, opts) {
19474
19634
  outputLine("Quote:");
19475
19635
  printKv({
19476
19636
  quoteId: q["quoteId"],
19477
- annualizedYield: q["annualizedYield"] ? `${(parseFloat(q["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
19637
+ annualizedYield: q["annualizedYield"] ? `${(parseFloat(q["annualizedYield"]) * 100).toFixed(2)}%` : "-",
19478
19638
  absYield: q["absYield"],
19479
19639
  notionalSz: q["notionalSz"],
19480
19640
  notionalCcy: q["notionalCcy"]
@@ -19649,9 +19809,9 @@ async function cmdSkillCheck(run, name, json) {
19649
19809
  upToDate
19650
19810
  }, null, 2));
19651
19811
  } else if (upToDate) {
19652
- outputLine(`${name}: installed v${local.version} \u2192 latest v${remote.latestVersion} (up to date)`);
19812
+ outputLine(`${name}: installed v${local.version} -> latest v${remote.latestVersion} (up to date)`);
19653
19813
  } else {
19654
- outputLine(`${name}: installed v${local.version} \u2192 latest v${remote.latestVersion} (update available)`);
19814
+ outputLine(`${name}: installed v${local.version} -> latest v${remote.latestVersion} (update available)`);
19655
19815
  outputLine(` Use \`okx skill add ${name}\` to update.`);
19656
19816
  }
19657
19817
  }
@@ -19928,9 +20088,9 @@ async function cmdEventBrowse(run, opts) {
19928
20088
  contracts.map((c) => ({
19929
20089
  "Contract": formatDisplayTitle(String(c["instId"] ?? "")),
19930
20090
  "Expiry": c["expTime"] ?? "",
19931
- "Target Price": c["floorStrike"] ? String(c["floorStrike"]) : "\u2014",
20091
+ "Target Price": c["floorStrike"] ? String(c["floorStrike"]) : "-",
19932
20092
  "Probability": fmtProbability(c["px"]),
19933
- "Outcome": fmtOutcome(c["outcome"]) || "\u2014",
20093
+ "Outcome": fmtOutcome(c["outcome"]) || "-",
19934
20094
  "instId": c["instId"]
19935
20095
  }))
19936
20096
  );
@@ -20067,7 +20227,7 @@ async function cmdEventMarkets(run, opts) {
20067
20227
  expTime: m["expTime"] ?? "",
20068
20228
  targetPrice: m["floorStrike"] ?? "",
20069
20229
  probability: fmtProbability(m["px"]),
20070
- outcome: outcome.toLowerCase() === "pending" ? "\u2014" : outcome,
20230
+ outcome: outcome.toLowerCase() === "pending" ? "-" : outcome,
20071
20231
  settleValue: m["settleValue"] ?? "",
20072
20232
  instId: id
20073
20233
  };
@@ -20120,7 +20280,7 @@ async function cmdEventFills(run, opts) {
20120
20280
  const side = String(f["side"] ?? "").toUpperCase();
20121
20281
  const outcome = fmtOrderOutcome(f["instId"], f["outcome"]).toUpperCase();
20122
20282
  const dir = `${side} ${outcome}`.trim();
20123
- return dir || "\u2014";
20283
+ return dir || "-";
20124
20284
  })(),
20125
20285
  "Fill Price": f["fillPx"],
20126
20286
  "Fill Size": f["fillSz"],
@@ -20222,7 +20382,7 @@ async function cmdEventPlace(run, opts) {
20222
20382
  const data = getData9(result);
20223
20383
  if (opts.json) return printJson(data);
20224
20384
  const order = data?.[0];
20225
- const stateHint = ordType === "market" ? "market order \u2014 typically fills immediately" : `${ordType} order \u2014 may still be live; verify with: okx event orders --instId ${opts.instId} --state live`;
20385
+ const stateHint = ordType === "market" ? "market order - typically fills immediately" : `${ordType} order - may still be live; verify with: okx event orders --instId ${opts.instId} --state live`;
20226
20386
  const period = fmtPeriodFromInstId(opts.instId);
20227
20387
  const pxPart = opts.px ? ` px: ${opts.px}` : "";
20228
20388
  process.stdout.write(
@@ -20268,7 +20428,7 @@ function handleCancelCatchError(instId, ordId, err) {
20268
20428
  if (isExpired) {
20269
20429
  process.stdout.write(
20270
20430
  `Cannot cancel: contract ${instId} has already expired.
20271
- The order was auto-cancelled at settlement \u2014 no action needed.
20431
+ The order was auto-cancelled at settlement - no action needed.
20272
20432
  `
20273
20433
  );
20274
20434
  } else {
@@ -20297,7 +20457,7 @@ async function cmdEventCancel(run, opts) {
20297
20457
  // src/index.ts
20298
20458
  var _require3 = createRequire3(import.meta.url);
20299
20459
  var CLI_VERSION2 = _require3("../package.json").version;
20300
- var GIT_HASH2 = true ? "cd99d487" : "dev";
20460
+ var GIT_HASH2 = true ? "ce35abd7" : "dev";
20301
20461
  function handlePilotCommand(action, json, force, binaryPath) {
20302
20462
  if (action === "status") return cmdPilotStatus(json, binaryPath);
20303
20463
  if (action === "install") return cmdPilotInstall(json, binaryPath);
@@ -20402,6 +20562,15 @@ function handleMarketFilterCommand(run, action, rest, v, json) {
20402
20562
  limit,
20403
20563
  json
20404
20564
  });
20565
+ if (action === "pair-spread") {
20566
+ const backtestTime = v["backtest-time"] !== void 0 ? Number(v["backtest-time"]) : void 0;
20567
+ return cmdMarketPairSpread(run, rest[0], rest[1], {
20568
+ bar: v.bar,
20569
+ window: v.window,
20570
+ backtestTime,
20571
+ json
20572
+ });
20573
+ }
20405
20574
  }
20406
20575
  function handleIndicatorAction(run, rest, v, json) {
20407
20576
  if (rest[0] === "list") return cmdMarketIndicatorList(json);
@@ -20450,7 +20619,8 @@ function handleMarketCommand(run, action, rest, v, json) {
20450
20619
  "filter",
20451
20620
  "oi-history",
20452
20621
  "oi-change",
20453
- "index-candles"
20622
+ "index-candles",
20623
+ "pair-spread"
20454
20624
  ]);
20455
20625
  }
20456
20626
  function handleAccountWriteCommand(run, action, v, json) {
@@ -20599,7 +20769,7 @@ function assertNoTpConflict(tpLevel, singleFields) {
20599
20769
  if (conflicting.length > 0) {
20600
20770
  const flagNames = conflicting.map((k) => `--${k}`).join(", ");
20601
20771
  throw new Error(
20602
- `Cannot use --tpLevel together with ${flagNames}. Use --tpLevel for split multi-tier take-profit, or single-TP flags for a single TP \u2014 not both.`
20772
+ `Cannot use --tpLevel together with ${flagNames}. Use --tpLevel for split multi-tier take-profit, or single-TP flags for a single TP - not both.`
20603
20773
  );
20604
20774
  }
20605
20775
  }
@@ -21396,7 +21566,7 @@ function handleSmartmoneyCommand(run, action, rest, v, json) {
21396
21566
  if (action === "signal-overview-by-filter") {
21397
21567
  if (v.topInstruments && v.instCcyList) {
21398
21568
  errorLine(
21399
- "--topInstruments and --instCcyList are mutually exclusive. Pass exactly one \u2014 `--topInstruments` for top-N hottest coins, or `--instCcyList` for specific coins."
21569
+ "--topInstruments and --instCcyList are mutually exclusive. Pass exactly one - `--topInstruments` for top-N hottest coins, or `--instCcyList` for specific coins."
21400
21570
  );
21401
21571
  process.exitCode = 1;
21402
21572
  return;
@@ -21417,7 +21587,7 @@ function handleSmartmoneyCommand(run, action, rest, v, json) {
21417
21587
  }
21418
21588
  if (v.topInstruments && v.instCcyList) {
21419
21589
  errorLine(
21420
- "--topInstruments and --instCcyList are mutually exclusive. Pass exactly one \u2014 `--topInstruments` for top-N hottest coins, or `--instCcyList` for specific coins."
21590
+ "--topInstruments and --instCcyList are mutually exclusive. Pass exactly one - `--topInstruments` for top-N hottest coins, or `--instCcyList` for specific coins."
21421
21591
  );
21422
21592
  process.exitCode = 1;
21423
21593
  return;