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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -880,32 +880,29 @@ import * as path3 from "path";
880
880
  import * as os3 from "os";
881
881
  import { execFileSync } from "child_process";
882
882
  import {
883
- createWriteStream as createWriteStream3,
884
883
  mkdirSync as mkdirSync9,
885
884
  chmodSync as chmodSync2,
886
885
  existsSync as existsSync7,
887
- unlinkSync as unlinkSync4,
886
+ unlinkSync as unlinkSync5,
888
887
  renameSync as renameSync4
889
888
  } from "fs";
890
889
  import { homedir as homedir9, platform as platform2 } from "os";
891
890
  import { join as join11, dirname as dirname7 } from "path";
892
- import { get as httpsGet2 } from "https";
893
- import { get as httpGet2 } from "http";
891
+ import { createWriteStream as createWriteStream2, unlinkSync as unlinkSync3 } from "fs";
892
+ import { get as httpsGet } from "https";
893
+ import { get as httpGet } from "http";
894
894
  import {
895
895
  readFileSync as readFileSync7,
896
- createWriteStream as createWriteStream2,
897
896
  mkdirSync as mkdirSync8,
898
897
  chmodSync,
899
898
  existsSync as existsSync6,
900
- unlinkSync as unlinkSync3,
899
+ unlinkSync as unlinkSync4,
901
900
  renameSync as renameSync3
902
901
  } from "fs";
903
902
  import { createHash } from "crypto";
904
903
  import { homedir as homedir8, platform, arch } from "os";
905
904
  import { join as join10, dirname as dirname6 } from "path";
906
- import { get as httpsGet } from "https";
907
- import { get as httpGet } from "http";
908
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10, unlinkSync as unlinkSync5, existsSync as existsSync8 } from "fs";
905
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10, unlinkSync as unlinkSync6, existsSync as existsSync8 } from "fs";
909
906
  import { join as join12 } from "path";
910
907
  import { homedir as homedir10 } from "os";
911
908
  var EXEC_TIMEOUT_MS = 3e4;
@@ -1634,7 +1631,7 @@ var OkxRestClient = class _OkxRestClient {
1634
1631
  headers.set("OK-ACCESS-TIMESTAMP", timestamp);
1635
1632
  }
1636
1633
  throwOkxError(code, msg, reqConfig, traceId) {
1637
- const message = msg || "OKX API request failed.";
1634
+ const message = msg && msg.trim() !== "" ? msg : `OKX API rejected request (code ${code}).`;
1638
1635
  const endpoint = `${reqConfig.method} ${reqConfig.path}`;
1639
1636
  if (code === "50111" || code === "50112" || code === "50113") {
1640
1637
  throw new AuthenticationError(
@@ -1988,6 +1985,16 @@ function readBoolean(args, key) {
1988
1985
  }
1989
1986
  return value;
1990
1987
  }
1988
+ function readStringArray(args, key) {
1989
+ const value = args[key];
1990
+ if (value === void 0 || value === null) {
1991
+ return void 0;
1992
+ }
1993
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
1994
+ throw new ValidationError(`Parameter "${key}" must be an array of strings.`);
1995
+ }
1996
+ return value;
1997
+ }
1991
1998
  function requireString(args, key) {
1992
1999
  const value = readString(args, key);
1993
2000
  if (!value || value.length === 0) {
@@ -2028,12 +2035,170 @@ function validateSwapInstId(instId) {
2028
2035
  );
2029
2036
  }
2030
2037
  }
2038
+ var TP_ORD_KIND_SCHEMA = {
2039
+ type: "string",
2040
+ enum: ["condition", "limit"],
2041
+ description: "condition(default)=trigger-based TP; limit=immediate limit order (no trigger phase)"
2042
+ };
2043
+ var TP_TRIGGER_PX_TYPE_SCHEMA = {
2044
+ type: "string",
2045
+ enum: ["last", "index", "mark"],
2046
+ description: "TP trigger price source: last(default)|index|mark"
2047
+ };
2048
+ var SL_TRIGGER_PX_TYPE_SCHEMA = {
2049
+ type: "string",
2050
+ enum: ["last", "index", "mark"],
2051
+ description: "SL trigger price source: last(default)|index|mark"
2052
+ };
2053
+ var STP_MODE_SCHEMA = {
2054
+ type: "string",
2055
+ enum: ["cancel_maker", "cancel_taker", "cancel_both"],
2056
+ description: "Self-trade prevention: cancel_maker|cancel_taker|cancel_both"
2057
+ };
2058
+ var CXL_ON_CLOSE_POS_SCHEMA = {
2059
+ type: "boolean",
2060
+ description: "Auto-cancel TP/SL when associated position closes (algo only)"
2061
+ };
2062
+ var PHASE1_PLACE_FLAGS_SCHEMA = {
2063
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
2064
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
2065
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
2066
+ stpMode: STP_MODE_SCHEMA
2067
+ };
2068
+ var PHASE1_ALGO_FLAGS_SCHEMA = {
2069
+ ...PHASE1_PLACE_FLAGS_SCHEMA,
2070
+ cxlOnClosePos: CXL_ON_CLOSE_POS_SCHEMA
2071
+ };
2072
+ var TRIGGER_FLAGS_SCHEMA = {
2073
+ triggerPx: {
2074
+ type: "string",
2075
+ description: "Activation price; order submits when market hits this level (trigger only)"
2076
+ },
2077
+ orderPx: {
2078
+ type: "string",
2079
+ description: "Order price submitted when trigger fires; -1=market (trigger only)"
2080
+ },
2081
+ advanceOrdType: {
2082
+ type: "string",
2083
+ enum: ["fok", "ioc"],
2084
+ description: "Execution qualifier for triggered order: fok=fill-or-kill, ioc=immediate-or-cancel (trigger only)"
2085
+ },
2086
+ triggerPxType: {
2087
+ type: "string",
2088
+ enum: ["last", "index", "mark"],
2089
+ description: "Price type used to evaluate trigger: last(default)|index|mark (trigger only)"
2090
+ }
2091
+ };
2092
+ var CHASE_FLAGS_SCHEMA = {
2093
+ chaseType: {
2094
+ type: "string",
2095
+ enum: ["distance", "ratio"],
2096
+ description: "Chase unit: distance=price ticks, ratio=proportion of price (chase only, default distance)"
2097
+ },
2098
+ chaseVal: {
2099
+ type: "string",
2100
+ description: "Chase amount matching chaseType (e.g. 0.5 ticks for distance, 0.001 for ratio) (chase only)"
2101
+ },
2102
+ maxChaseType: {
2103
+ type: "string",
2104
+ enum: ["distance", "ratio"],
2105
+ description: "Upper-bound unit for chase (chase only)"
2106
+ },
2107
+ maxChaseVal: {
2108
+ type: "string",
2109
+ description: "Upper-bound value for chase (chase only)"
2110
+ }
2111
+ };
2112
+ var ICEBERG_TWAP_FLAGS_SCHEMA = {
2113
+ pxVar: {
2114
+ type: "string",
2115
+ description: "Price variance % [0.0001, 0.01]; provide pxVar OR pxSpread (iceberg/twap only)"
2116
+ },
2117
+ pxSpread: {
2118
+ type: "string",
2119
+ description: "Price variance constant >= 0; provide pxVar OR pxSpread (iceberg/twap only)"
2120
+ },
2121
+ szLimit: {
2122
+ type: "string",
2123
+ description: "Average per-child-order size (iceberg/twap only)"
2124
+ },
2125
+ pxLimit: {
2126
+ type: "string",
2127
+ description: "Order price ceiling >= 0 (iceberg/twap only)"
2128
+ },
2129
+ timeInterval: {
2130
+ type: "string",
2131
+ description: "Seconds between child orders (iceberg/twap only)"
2132
+ }
2133
+ };
2134
+ function buildTriggerOrdTypeBody(args) {
2135
+ return compactObject({
2136
+ triggerPx: readString(args, "triggerPx"),
2137
+ orderPx: readString(args, "orderPx"),
2138
+ advanceOrdType: readString(args, "advanceOrdType"),
2139
+ triggerPxType: readString(args, "triggerPxType"),
2140
+ attachAlgoOrds: buildAttachAlgoOrds(args)
2141
+ });
2142
+ }
2143
+ function buildChaseOrdTypeBody(args) {
2144
+ return compactObject({
2145
+ chaseType: readString(args, "chaseType"),
2146
+ chaseVal: readString(args, "chaseVal"),
2147
+ maxChaseType: readString(args, "maxChaseType"),
2148
+ maxChaseVal: readString(args, "maxChaseVal")
2149
+ });
2150
+ }
2151
+ function buildIcebergTwapOrdTypeBody(args) {
2152
+ return compactObject({
2153
+ pxVar: readString(args, "pxVar"),
2154
+ pxSpread: readString(args, "pxSpread"),
2155
+ szLimit: readString(args, "szLimit"),
2156
+ pxLimit: readString(args, "pxLimit"),
2157
+ timeInterval: readString(args, "timeInterval")
2158
+ });
2159
+ }
2160
+ function buildAlgoConditionalCommonFields(args) {
2161
+ return {
2162
+ tpTriggerPx: readString(args, "tpTriggerPx"),
2163
+ tpOrdPx: readString(args, "tpOrdPx"),
2164
+ tpOrdKind: readString(args, "tpOrdKind"),
2165
+ tpTriggerPxType: readString(args, "tpTriggerPxType"),
2166
+ tpTriggerRatio: readString(args, "tpTriggerRatio"),
2167
+ slTriggerPx: readString(args, "slTriggerPx"),
2168
+ slOrdPx: readString(args, "slOrdPx"),
2169
+ slTriggerPxType: readString(args, "slTriggerPxType"),
2170
+ slTriggerRatio: readString(args, "slTriggerRatio"),
2171
+ closeFraction: readString(args, "closeFraction"),
2172
+ activePx: readString(args, "activePx")
2173
+ };
2174
+ }
2031
2175
  function buildAttachAlgoOrds(source) {
2176
+ const tpLevels = source["tpLevels"];
2177
+ if (Array.isArray(tpLevels) && tpLevels.length > 0) {
2178
+ return tpLevels.map(
2179
+ (level) => compactObject(level)
2180
+ );
2181
+ }
2032
2182
  const tpTriggerPx = readString(source, "tpTriggerPx");
2033
2183
  const tpOrdPx = readString(source, "tpOrdPx");
2034
2184
  const slTriggerPx = readString(source, "slTriggerPx");
2035
2185
  const slOrdPx = readString(source, "slOrdPx");
2036
- const entry = compactObject({ tpTriggerPx, tpOrdPx, slTriggerPx, slOrdPx });
2186
+ const tpOrdKind = readString(source, "tpOrdKind");
2187
+ const tpTriggerPxType = readString(source, "tpTriggerPxType");
2188
+ const slTriggerPxType = readString(source, "slTriggerPxType");
2189
+ const tpTriggerRatio = readString(source, "tpTriggerRatio");
2190
+ const slTriggerRatio = readString(source, "slTriggerRatio");
2191
+ const entry = compactObject({
2192
+ tpTriggerPx,
2193
+ tpOrdPx,
2194
+ slTriggerPx,
2195
+ slOrdPx,
2196
+ tpOrdKind,
2197
+ tpTriggerPxType,
2198
+ slTriggerPxType,
2199
+ tpTriggerRatio,
2200
+ slTriggerRatio
2201
+ });
2037
2202
  return Object.keys(entry).length > 0 ? [entry] : void 0;
2038
2203
  }
2039
2204
  var OKX_CANDLE_BARS = [
@@ -3108,7 +3273,7 @@ function registerAlgoTradeTools() {
3108
3273
  {
3109
3274
  name: "swap_place_algo_order",
3110
3275
  module: "swap",
3111
- description: "Place a SWAP/FUTURES algo order: TP/SL (conditional/oco) or trailing stop (move_order_stop). [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: provide callbackRatio (e.g. '0.01'=1%) OR callbackSpread, and optionally activePx. Set tpOrdPx='-1' or slOrdPx='-1' for market execution.",
3276
+ 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.",
3112
3277
  isWrite: true,
3113
3278
  inputSchema: {
3114
3279
  type: "object",
@@ -3134,8 +3299,8 @@ function registerAlgoTradeTools() {
3134
3299
  },
3135
3300
  ordType: {
3136
3301
  type: "string",
3137
- enum: ["conditional", "oco", "move_order_stop"],
3138
- description: "conditional=single TP/SL or both; oco=TP+SL pair (first trigger cancels other); move_order_stop=trailing stop"
3302
+ enum: ["conditional", "oco", "move_order_stop", "trigger", "chase", "iceberg", "twap"],
3303
+ description: "conditional=single TP/SL or both; oco=TP+SL pair; move_order_stop=trailing stop; trigger=pending order; chase=follow best bid/ask; iceberg=split order; twap=time-weighted split"
3139
3304
  },
3140
3305
  sz: {
3141
3306
  type: "string",
@@ -3149,11 +3314,8 @@ function registerAlgoTradeTools() {
3149
3314
  type: "string",
3150
3315
  description: "TP order price; -1=market (conditional/oco only)"
3151
3316
  },
3152
- tpTriggerPxType: {
3153
- type: "string",
3154
- enum: ["last", "index", "mark"],
3155
- description: "last(default)|index|mark (conditional/oco only)"
3156
- },
3317
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
3318
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
3157
3319
  slTriggerPx: {
3158
3320
  type: "string",
3159
3321
  description: "SL trigger price (conditional/oco only)"
@@ -3162,11 +3324,9 @@ function registerAlgoTradeTools() {
3162
3324
  type: "string",
3163
3325
  description: "SL order price; -1=market (recommended) (conditional/oco only)"
3164
3326
  },
3165
- slTriggerPxType: {
3166
- type: "string",
3167
- enum: ["last", "index", "mark"],
3168
- description: "last(default)|index|mark (conditional/oco only)"
3169
- },
3327
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
3328
+ stpMode: STP_MODE_SCHEMA,
3329
+ cxlOnClosePos: CXL_ON_CLOSE_POS_SCHEMA,
3170
3330
  callbackRatio: {
3171
3331
  type: "string",
3172
3332
  description: "Callback ratio (e.g. '0.01'=1%); provide either ratio or spread (move_order_stop only)"
@@ -3179,6 +3339,9 @@ function registerAlgoTradeTools() {
3179
3339
  type: "string",
3180
3340
  description: "Activation price; tracking starts after market reaches this level (move_order_stop only)"
3181
3341
  },
3342
+ ...TRIGGER_FLAGS_SCHEMA,
3343
+ ...CHASE_FLAGS_SCHEMA,
3344
+ ...ICEBERG_TWAP_FLAGS_SCHEMA,
3182
3345
  tgtCcy: {
3183
3346
  type: "string",
3184
3347
  enum: ["base_ccy", "quote_ccy", "margin"],
@@ -3198,6 +3361,8 @@ function registerAlgoTradeTools() {
3198
3361
  handler: async (rawArgs, context) => {
3199
3362
  const args = asRecord(rawArgs);
3200
3363
  const reduceOnly = args.reduceOnly;
3364
+ const cxlOnClosePos = args.cxlOnClosePos;
3365
+ const ordType = requireString(args, "ordType");
3201
3366
  const resolved = await resolveQuoteCcySz(
3202
3367
  requireString(args, "instId"),
3203
3368
  requireString(args, "sz"),
@@ -3206,29 +3371,44 @@ function registerAlgoTradeTools() {
3206
3371
  context.client,
3207
3372
  readString(args, "tdMode")
3208
3373
  );
3374
+ const base = compactObject({
3375
+ instId: requireString(args, "instId"),
3376
+ tdMode: requireString(args, "tdMode"),
3377
+ side: requireString(args, "side"),
3378
+ posSide: readString(args, "posSide"),
3379
+ ordType,
3380
+ sz: resolved.sz,
3381
+ tgtCcy: resolved.tgtCcy,
3382
+ stpMode: readString(args, "stpMode"),
3383
+ cxlOnClosePos: typeof cxlOnClosePos === "boolean" ? String(cxlOnClosePos) : void 0,
3384
+ reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
3385
+ clOrdId: readString(args, "clOrdId"),
3386
+ // Phase 3a+c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
3387
+ pxAmendType: readString(args, "pxAmendType"),
3388
+ tag: context.config.sourceTag
3389
+ });
3390
+ switch (ordType) {
3391
+ case "trigger":
3392
+ Object.assign(base, buildTriggerOrdTypeBody(args));
3393
+ break;
3394
+ case "chase":
3395
+ Object.assign(base, buildChaseOrdTypeBody(args));
3396
+ break;
3397
+ case "iceberg":
3398
+ case "twap":
3399
+ Object.assign(base, buildIcebergTwapOrdTypeBody(args));
3400
+ break;
3401
+ default:
3402
+ Object.assign(base, compactObject({
3403
+ ...buildAlgoConditionalCommonFields(args),
3404
+ callBackRatio: readString(args, "callbackRatio"),
3405
+ callBackSpread: readString(args, "callbackSpread")
3406
+ }));
3407
+ break;
3408
+ }
3209
3409
  const response = await context.client.privatePost(
3210
3410
  "/api/v5/trade/order-algo",
3211
- compactObject({
3212
- instId: requireString(args, "instId"),
3213
- tdMode: requireString(args, "tdMode"),
3214
- side: requireString(args, "side"),
3215
- posSide: readString(args, "posSide"),
3216
- ordType: requireString(args, "ordType"),
3217
- sz: resolved.sz,
3218
- tgtCcy: resolved.tgtCcy,
3219
- tpTriggerPx: readString(args, "tpTriggerPx"),
3220
- tpOrdPx: readString(args, "tpOrdPx"),
3221
- tpTriggerPxType: readString(args, "tpTriggerPxType"),
3222
- slTriggerPx: readString(args, "slTriggerPx"),
3223
- slOrdPx: readString(args, "slOrdPx"),
3224
- slTriggerPxType: readString(args, "slTriggerPxType"),
3225
- callBackRatio: readString(args, "callbackRatio"),
3226
- callBackSpread: readString(args, "callbackSpread"),
3227
- activePx: readString(args, "activePx"),
3228
- reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
3229
- clOrdId: readString(args, "clOrdId"),
3230
- tag: context.config.sourceTag
3231
- }),
3411
+ base,
3232
3412
  privateRateLimit("swap_place_algo_order", 20)
3233
3413
  );
3234
3414
  const result = normalizeResponse(response);
@@ -3453,7 +3633,7 @@ function registerFuturesAlgoTools() {
3453
3633
  {
3454
3634
  name: "futures_place_algo_order",
3455
3635
  module: "futures",
3456
- description: "Place a FUTURES delivery algo order: TP/SL (conditional/oco) or trailing stop (move_order_stop). [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: provide callbackRatio (e.g. '0.01'=1%) OR callbackSpread, and optionally activePx. Set tpOrdPx='-1' or slOrdPx='-1' for market execution.",
3636
+ 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.",
3457
3637
  isWrite: true,
3458
3638
  inputSchema: {
3459
3639
  type: "object",
@@ -3479,8 +3659,8 @@ function registerFuturesAlgoTools() {
3479
3659
  },
3480
3660
  ordType: {
3481
3661
  type: "string",
3482
- enum: ["conditional", "oco", "move_order_stop"],
3483
- description: "conditional=single TP/SL or both; oco=TP+SL pair; move_order_stop=trailing stop"
3662
+ enum: ["conditional", "oco", "move_order_stop", "trigger", "chase", "iceberg", "twap"],
3663
+ description: "conditional=single TP/SL or both; oco=TP+SL pair; move_order_stop=trailing stop; trigger=pending order; chase=follow best bid/ask; iceberg=split order; twap=time-weighted split"
3484
3664
  },
3485
3665
  sz: {
3486
3666
  type: "string",
@@ -3494,11 +3674,8 @@ function registerFuturesAlgoTools() {
3494
3674
  type: "string",
3495
3675
  description: "TP order price; -1=market (conditional/oco only)"
3496
3676
  },
3497
- tpTriggerPxType: {
3498
- type: "string",
3499
- enum: ["last", "index", "mark"],
3500
- description: "last(default)|index|mark (conditional/oco only)"
3501
- },
3677
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
3678
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
3502
3679
  slTriggerPx: {
3503
3680
  type: "string",
3504
3681
  description: "SL trigger price (conditional/oco only)"
@@ -3507,11 +3684,9 @@ function registerFuturesAlgoTools() {
3507
3684
  type: "string",
3508
3685
  description: "SL order price; -1=market (conditional/oco only)"
3509
3686
  },
3510
- slTriggerPxType: {
3511
- type: "string",
3512
- enum: ["last", "index", "mark"],
3513
- description: "last(default)|index|mark (conditional/oco only)"
3514
- },
3687
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
3688
+ stpMode: STP_MODE_SCHEMA,
3689
+ cxlOnClosePos: CXL_ON_CLOSE_POS_SCHEMA,
3515
3690
  callbackRatio: {
3516
3691
  type: "string",
3517
3692
  description: "Callback ratio (e.g. '0.01'=1%); provide either ratio or spread (move_order_stop only)"
@@ -3524,6 +3699,9 @@ function registerFuturesAlgoTools() {
3524
3699
  type: "string",
3525
3700
  description: "Activation price; tracking starts after market reaches this level (move_order_stop only)"
3526
3701
  },
3702
+ ...TRIGGER_FLAGS_SCHEMA,
3703
+ ...CHASE_FLAGS_SCHEMA,
3704
+ ...ICEBERG_TWAP_FLAGS_SCHEMA,
3527
3705
  tgtCcy: {
3528
3706
  type: "string",
3529
3707
  enum: ["base_ccy", "quote_ccy", "margin"],
@@ -3543,6 +3721,8 @@ function registerFuturesAlgoTools() {
3543
3721
  handler: async (rawArgs, context) => {
3544
3722
  const args = asRecord(rawArgs);
3545
3723
  const reduceOnly = args.reduceOnly;
3724
+ const cxlOnClosePos = args.cxlOnClosePos;
3725
+ const ordType = requireString(args, "ordType");
3546
3726
  const resolved = await resolveQuoteCcySz(
3547
3727
  requireString(args, "instId"),
3548
3728
  requireString(args, "sz"),
@@ -3551,29 +3731,44 @@ function registerFuturesAlgoTools() {
3551
3731
  context.client,
3552
3732
  readString(args, "tdMode")
3553
3733
  );
3734
+ const base = compactObject({
3735
+ instId: requireString(args, "instId"),
3736
+ tdMode: requireString(args, "tdMode"),
3737
+ side: requireString(args, "side"),
3738
+ posSide: readString(args, "posSide"),
3739
+ ordType,
3740
+ sz: resolved.sz,
3741
+ tgtCcy: resolved.tgtCcy,
3742
+ stpMode: readString(args, "stpMode"),
3743
+ cxlOnClosePos: typeof cxlOnClosePos === "boolean" ? String(cxlOnClosePos) : void 0,
3744
+ reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
3745
+ clOrdId: readString(args, "clOrdId"),
3746
+ // Phase 3a+c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
3747
+ pxAmendType: readString(args, "pxAmendType"),
3748
+ tag: context.config.sourceTag
3749
+ });
3750
+ switch (ordType) {
3751
+ case "trigger":
3752
+ Object.assign(base, buildTriggerOrdTypeBody(args));
3753
+ break;
3754
+ case "chase":
3755
+ Object.assign(base, buildChaseOrdTypeBody(args));
3756
+ break;
3757
+ case "iceberg":
3758
+ case "twap":
3759
+ Object.assign(base, buildIcebergTwapOrdTypeBody(args));
3760
+ break;
3761
+ default:
3762
+ Object.assign(base, compactObject({
3763
+ ...buildAlgoConditionalCommonFields(args),
3764
+ callBackRatio: readString(args, "callbackRatio"),
3765
+ callBackSpread: readString(args, "callbackSpread")
3766
+ }));
3767
+ break;
3768
+ }
3554
3769
  const response = await context.client.privatePost(
3555
3770
  "/api/v5/trade/order-algo",
3556
- compactObject({
3557
- instId: requireString(args, "instId"),
3558
- tdMode: requireString(args, "tdMode"),
3559
- side: requireString(args, "side"),
3560
- posSide: readString(args, "posSide"),
3561
- ordType: requireString(args, "ordType"),
3562
- sz: resolved.sz,
3563
- tgtCcy: resolved.tgtCcy,
3564
- tpTriggerPx: readString(args, "tpTriggerPx"),
3565
- tpOrdPx: readString(args, "tpOrdPx"),
3566
- tpTriggerPxType: readString(args, "tpTriggerPxType"),
3567
- slTriggerPx: readString(args, "slTriggerPx"),
3568
- slOrdPx: readString(args, "slOrdPx"),
3569
- slTriggerPxType: readString(args, "slTriggerPxType"),
3570
- callBackRatio: readString(args, "callbackRatio"),
3571
- callBackSpread: readString(args, "callbackSpread"),
3572
- activePx: readString(args, "activePx"),
3573
- reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
3574
- clOrdId: readString(args, "clOrdId"),
3575
- tag: context.config.sourceTag
3576
- }),
3771
+ base,
3577
3772
  privateRateLimit("futures_place_algo_order", 20)
3578
3773
  );
3579
3774
  const result = normalizeResponse(response);
@@ -6156,6 +6351,23 @@ function resolveOutcome(value) {
6156
6351
  }
6157
6352
  return resolved;
6158
6353
  }
6354
+ async function withConcurrency(items, maxConcurrency, fn) {
6355
+ const results = new Array(items.length);
6356
+ let nextIndex = 0;
6357
+ async function runNext() {
6358
+ while (nextIndex < items.length) {
6359
+ const idx = nextIndex++;
6360
+ try {
6361
+ results[idx] = { status: "fulfilled", value: await fn(items[idx]) };
6362
+ } catch (err) {
6363
+ results[idx] = { status: "rejected", reason: err };
6364
+ }
6365
+ }
6366
+ }
6367
+ const workers = Array.from({ length: Math.min(maxConcurrency, items.length) }, runNext);
6368
+ await Promise.all(workers);
6369
+ return results;
6370
+ }
6159
6371
  function filterBrowseCandidates(allSeries, underlyingFilter) {
6160
6372
  const isHumanReadable = (id) => /^(BTC|ETH|TRX|EOS|SOL|IOTA|KISHU|SUSHI|BTG|XTZ|SOLVU)-/.test(id);
6161
6373
  const seen = /* @__PURE__ */ new Set();
@@ -6296,6 +6508,7 @@ function handlePlaceOrderError(base, rawArgs, endpoint) {
6296
6508
  }
6297
6509
  throw new OkxApiError(`[${sCode}] ${sMsg}`, { code: sCode, endpoint });
6298
6510
  }
6511
+ var MAX_CONCURRENT_MARKET_FETCHES = 8;
6299
6512
  var OUTCOME_SCHEMA = {
6300
6513
  type: "string",
6301
6514
  enum: ["UP", "YES", "DOWN", "NO"],
@@ -6335,10 +6548,15 @@ function registerEventContractTools() {
6335
6548
  const normalizedSeries = normalizeResponse(seriesResp);
6336
6549
  const allSeries = Array.isArray(normalizedSeries["data"]) ? normalizedSeries["data"] : [];
6337
6550
  const candidates = filterBrowseCandidates(allSeries, underlyingFilter);
6338
- const marketResults = await Promise.all(
6339
- candidates.map((s) => fetchActiveContractsForSeries(context.client, s))
6551
+ const settled = await withConcurrency(
6552
+ candidates,
6553
+ MAX_CONCURRENT_MARKET_FETCHES,
6554
+ (s) => fetchActiveContractsForSeries(context.client, s)
6340
6555
  );
6341
- const results = marketResults.filter(Boolean);
6556
+ const fulfilled = settled.filter(
6557
+ (r) => r.status === "fulfilled" && r.value !== null
6558
+ );
6559
+ const results = fulfilled.map((r) => r.value);
6342
6560
  return {
6343
6561
  data: results,
6344
6562
  total: results.reduce((n, r) => n + (r?.contracts?.length ?? 0), 0)
@@ -6758,379 +6976,1195 @@ function registerEventContractTools() {
6758
6976
  }
6759
6977
  ];
6760
6978
  }
6979
+ var SMARTMONEY_RPS = 5;
6761
6980
  var PATH_LEADERBOARD = "/api/v5/orbit/public/leaderboard";
6762
6981
  var PATH_POSITION_CURRENT = "/api/v5/orbit/public/position-current";
6982
+ var PATH_POSITION_HISTORY = "/api/v5/orbit/public/position-history";
6763
6983
  var PATH_TRADE_RECORDS = "/api/v5/orbit/public/trade-records";
6984
+ var PATH_TOP_TRADER_SEARCH = "/api/v5/orbit/top-trader-search";
6764
6985
  var PATH_OVERVIEW = "/api/v5/journal/smartmoney/overview";
6765
- var PATH_SIGNAL = "/api/v5/journal/smartmoney/signal";
6766
6986
  var PATH_SIGNAL_HISTORY = "/api/v5/journal/smartmoney/signal-history";
6987
+ var PERIOD_DAYS = ["3", "7", "30", "90"];
6767
6988
  var SIGNAL_POOL_FILTER_PROPS = {
6768
- sortType: {
6989
+ sortBy: {
6769
6990
  type: "string",
6770
- description: "Pool ranking: pnl|pnlRatio (default pnl)"
6991
+ enum: ["pnl", "pnlRatio"],
6992
+ default: "pnl",
6993
+ description: "Ranking key used to pick traders into the aggregation pool. `pnl` = absolute USD profit (favors high-AUM whales); `pnlRatio` = percentage return (favors small-but-efficient traders)."
6771
6994
  },
6772
6995
  period: {
6773
6996
  type: "string",
6774
- description: "Win-rate window days: 3|7|30|90 (default 90). Not snapshot range."
6997
+ enum: PERIOD_DAYS,
6998
+ default: "7",
6999
+ description: 'Lookback window in days. One of `"3"` / `"7"` / `"30"` / `"90"`. Drives capability metrics (avgLongWinRate / avgShortWinRate) and the `winRateTier` filter. Does NOT affect signal fields (which always use the latest snapshot).'
6775
7000
  },
6776
- pnl: {
7001
+ pnlTier: {
6777
7002
  type: "string",
6778
- description: "Top N% by PnL: PNL_ANY|PNL_TOP50|PNL_TOP20|PNL_TOP5 (default PNL_ANY)"
7003
+ enum: ["PNL_ANY", "PNL_TOP50", "PNL_TOP20", "PNL_TOP5"],
7004
+ default: "PNL_ANY",
7005
+ description: "PnL percentile gate applied on top of `sortBy`. Naming: `TOP{N}` = top N% percentile (NOT an absolute PnL value). PNL_ANY = no filter; PNL_TOP50 = PnL \u2265 P50 (median); PNL_TOP20 = \u2265 P80; PNL_TOP5 = \u2265 P95. PnL distribution is long-tailed \u2014 use percentile, not absolute thresholds."
6779
7006
  },
6780
- winRatio: {
7007
+ winRateTier: {
6781
7008
  type: "string",
6782
- description: "Min win-rate: WR_ANY|WR_GE_50|WR_GE_80 (default WR_ANY)"
7009
+ enum: ["WR_ANY", "WR_GE_50", "WR_GE_80"],
7010
+ default: "WR_ANY",
7011
+ description: "Minimum win-rate gate (absolute thresholds, NOT percentile). Naming: `GE_{N}` = career win-rate \u2265 N% \u2014 distinct from `pnlTier`/`aumTier` `TOP{N}` which are percentiles. WR_ANY = no filter; WR_GE_50 = \u2265 50%; WR_GE_80 = \u2265 80%."
6783
7012
  },
6784
- maxRetreat: {
7013
+ maxDrawdownTier: {
6785
7014
  type: "string",
6786
- description: "Max drawdown: MR_ANY|MR_LE_20|MR_LE_50 (default MR_ANY)"
7015
+ enum: ["MR_ANY", "MR_LE_20", "MR_LE_50"],
7016
+ default: "MR_ANY",
7017
+ description: "Maximum-drawdown gate (absolute thresholds, NOT percentile; smaller drawdown = lower risk). Naming: `LE_{N}` = drawdown \u2264 N% \u2014 distinct from `pnlTier`/`aumTier` `TOP{N}` which are percentiles. MR_ANY = no filter; MR_LE_20 = drawdown \u2264 20%; MR_LE_50 = \u2264 50%."
6787
7018
  },
6788
- asset: {
7019
+ aumTier: {
6789
7020
  type: "string",
6790
- description: "Top N% by AUM: AUM_ANY|AUM_TOP50|AUM_TOP20|AUM_TOP5 (default AUM_ANY)"
7021
+ enum: ["AUM_ANY", "AUM_TOP50", "AUM_TOP20", "AUM_TOP5"],
7022
+ default: "AUM_ANY",
7023
+ description: "AUM (Assets Under Management) percentile gate. Naming: `TOP{N}` = top N% percentile (NOT an absolute USD amount). AUM_ANY = no filter; AUM_TOP50 = AUM \u2265 P50; AUM_TOP20 = \u2265 P80; AUM_TOP5 = \u2265 P95. AUM is long-tailed \u2014 use percentile, not absolute USD."
6791
7024
  }
6792
7025
  };
6793
7026
  var LEADERBOARD_POOL_FILTER_PROPS = {
6794
- sortType: {
7027
+ sortBy: {
6795
7028
  type: "string",
6796
- description: "pnl or pnl_ratio"
7029
+ enum: ["pnl", "pnlRatio"],
7030
+ default: "pnl",
7031
+ description: 'Required. Leaderboard sort key. `pnl` = absolute USD profit; `pnlRatio` = percentage return. Default `"pnl"`.'
6797
7032
  },
6798
7033
  period: {
6799
7034
  type: "string",
6800
- description: "3|7|30|90 days, empty=all"
7035
+ enum: PERIOD_DAYS,
7036
+ default: "90",
7037
+ description: 'Required. Performance lookback window in days. One of `"3"` / `"7"` / `"30"` / `"90"`. Default `"90"` (matches leaderboard UI). Filters AND ranks traders by their PnL over that window.'
6801
7038
  },
6802
- pnl: {
7039
+ minPnl: {
6803
7040
  type: "string",
6804
- description: "Min PnL USD"
7041
+ description: 'Minimum absolute PnL in USD as a string, e.g. `"10000"` \u2192 traders with PnL \u2265 $10,000. Numeric threshold \u2014 distinct from the signal-side `pnlTier` percentile enum.'
6805
7042
  },
6806
- winRatio: {
7043
+ minWinRate: {
6807
7044
  type: "string",
6808
- description: "Min ratio (0.8=80%)"
7045
+ description: 'Minimum win-rate as a decimal in 0~1 range, passed as a string, e.g. `"0.8"` \u2192 traders with win-rate \u2265 80%. Numeric threshold \u2014 distinct from the signal-side `winRateTier` enum.'
6809
7046
  },
6810
- maxRetreat: {
7047
+ maxDrawdown: {
6811
7048
  type: "string",
6812
- description: "Max DD (0.1=10%)"
7049
+ description: 'Maximum drawdown as a decimal, passed as a string, e.g. `"0.1"` \u2192 traders with drawdown \u2264 10%. Lower = lower risk. Numeric threshold \u2014 distinct from the signal-side `maxDrawdownTier` enum.'
6813
7050
  },
6814
- asset: {
7051
+ minAum: {
6815
7052
  type: "string",
6816
- description: "Min AUM USD"
7053
+ description: 'Minimum AUM (Assets Under Management) in USD as a string, e.g. `"1000"` \u2192 traders with AUM \u2265 $1,000. Numeric threshold \u2014 distinct from the signal-side `aumTier` percentile enum.'
6817
7054
  }
6818
7055
  };
6819
- var POOL_FILTER_KEYS = ["sortType", "period", "pnl", "winRatio", "maxRetreat", "asset"];
7056
+ var LEADERBOARD_FILTER_UPSTREAM_NAMES = {
7057
+ sortBy: "sortBy",
7058
+ period: "period",
7059
+ minPnl: "pnl",
7060
+ minWinRate: "winRate",
7061
+ maxDrawdown: "maxDrawdown",
7062
+ minAum: "asset"
7063
+ };
6820
7064
  function readPoolFilters(args) {
7065
+ assertPoolFilterEnums(args, LEADERBOARD_POOL_FILTER_PROPS);
6821
7066
  const result = {};
6822
- for (const key of POOL_FILTER_KEYS) {
7067
+ for (const [publicKey, upstreamKey] of Object.entries(LEADERBOARD_FILTER_UPSTREAM_NAMES)) {
7068
+ const val = readString(args, publicKey);
7069
+ if (val !== void 0 && val !== "") result[upstreamKey] = val;
7070
+ }
7071
+ if (result.sortBy === void 0) result.sortBy = "pnl";
7072
+ if (result.period === void 0) result.period = "90";
7073
+ return result;
7074
+ }
7075
+ function readSignalPoolFilters(args) {
7076
+ assertPoolFilterEnums(args, SIGNAL_POOL_FILTER_PROPS);
7077
+ const result = {};
7078
+ for (const key of Object.keys(SIGNAL_POOL_FILTER_PROPS)) {
6823
7079
  const val = readString(args, key);
6824
7080
  if (val) result[key] = val;
6825
7081
  }
7082
+ if (result.sortBy === void 0) result.sortBy = "pnl";
7083
+ if (result.period === void 0) result.period = "7";
6826
7084
  return result;
6827
7085
  }
6828
- function extractLeaderboardData(data) {
6829
- if (Array.isArray(data)) return data;
7086
+ function extractLeaderboardEnvelope(data) {
7087
+ if (Array.isArray(data)) return { items: data };
6830
7088
  if (data && typeof data === "object") {
6831
- const inner = data.data;
6832
- if (Array.isArray(inner)) return inner;
7089
+ const obj = data;
7090
+ const inner = obj.data;
7091
+ const updateTime = typeof obj.updateTime === "string" ? obj.updateTime : void 0;
7092
+ if (Array.isArray(inner)) return { items: inner, updateTime };
7093
+ }
7094
+ return { items: [] };
7095
+ }
7096
+ function deriveDirection(posSide, pos) {
7097
+ if (posSide === "long") return "long";
7098
+ if (posSide === "short") return "short";
7099
+ if ((posSide === "net" || posSide === "both") && typeof pos === "string" && pos !== "") {
7100
+ const n = Number(pos);
7101
+ if (Number.isFinite(n) && n !== 0) return n > 0 ? "long" : "short";
7102
+ }
7103
+ return void 0;
7104
+ }
7105
+ function extractPositionData(data) {
7106
+ if (!Array.isArray(data) || data.length === 0) return [];
7107
+ const first = data[0];
7108
+ if (first && typeof first === "object") {
7109
+ const posData = first.posData;
7110
+ if (Array.isArray(posData)) {
7111
+ return posData.map((row) => {
7112
+ if (!row || typeof row !== "object") return row;
7113
+ const r = row;
7114
+ const direction = deriveDirection(r.posSide, r.pos);
7115
+ return direction ? { ...r, direction } : r;
7116
+ });
7117
+ }
6833
7118
  }
6834
7119
  return [];
6835
7120
  }
7121
+ function buildPagination(data, effectiveLimit, cursorField) {
7122
+ const hasMore = data.length >= effectiveLimit;
7123
+ const last = data.length > 0 ? data[data.length - 1] : void 0;
7124
+ const nextAfter = hasMore && last && typeof last === "object" && last !== null ? last[cursorField] : void 0;
7125
+ return nextAfter !== void 0 ? { hasMore, nextAfter } : { hasMore };
7126
+ }
7127
+ function extractBaseCcy(instId) {
7128
+ if (!instId) return void 0;
7129
+ const idx = instId.indexOf("-");
7130
+ const base = idx === -1 ? instId : instId.slice(0, idx);
7131
+ return base || void 0;
7132
+ }
7133
+ function actionableError(message, hint) {
7134
+ return new ValidationError(`${message} ${hint}`);
7135
+ }
7136
+ function assertEnum2(args, key, allowed) {
7137
+ const val = readString(args, key);
7138
+ if (val === void 0 || val === "") return;
7139
+ if (!allowed.includes(val)) {
7140
+ throw actionableError(
7141
+ `Invalid value for "${key}": ${JSON.stringify(val)}.`,
7142
+ `Allowed values: ${allowed.map((v) => JSON.stringify(v)).join(", ")}.`
7143
+ );
7144
+ }
7145
+ }
7146
+ function assertPoolFilterEnums(args, props) {
7147
+ for (const [key, spec] of Object.entries(props)) {
7148
+ const enumList = spec.enum;
7149
+ if (Array.isArray(enumList)) assertEnum2(args, key, enumList);
7150
+ }
7151
+ }
7152
+ function readArrayAsCsv(args, key) {
7153
+ const arr = readStringArray(args, key);
7154
+ if (!arr || arr.length === 0) return void 0;
7155
+ return arr.join(",");
7156
+ }
7157
+ function envelope(dataSchema, extras = {}) {
7158
+ return {
7159
+ type: "object",
7160
+ properties: {
7161
+ endpoint: {
7162
+ type: "string",
7163
+ description: "Upstream API path that produced this payload (debug/audit aid)."
7164
+ },
7165
+ requestTime: { type: "string", description: "ISO-8601 timestamp when the request was issued." },
7166
+ data: dataSchema,
7167
+ ...extras
7168
+ },
7169
+ required: ["endpoint", "data"]
7170
+ };
7171
+ }
7172
+ var PAGINATION_PROP = {
7173
+ type: "object",
7174
+ description: "Cursor pagination metadata. Only emitted on list tools that support paging.",
7175
+ properties: {
7176
+ hasMore: {
7177
+ type: "boolean",
7178
+ description: "True when `data.length` reached the requested `limit` (more pages likely). False guarantees there are no more results after this page."
7179
+ },
7180
+ nextAfter: {
7181
+ type: "string",
7182
+ description: "Cursor to pass back as `after` on the next call to fetch the following page. Absent/empty when `hasMore` is false."
7183
+ }
7184
+ }
7185
+ };
7186
+ var TRADER_ITEM_PROPS = {
7187
+ authorId: { type: "string", description: "Trader's unique ID \u2014 pass to other smartmoney_get_trader_* tools." },
7188
+ nickName: { type: "string", description: "Display nickname." },
7189
+ pnl: { type: "string", description: "Absolute PnL in USD over the requested `period` (numeric string)." },
7190
+ pnlRatio: { type: "string", description: 'PnL as a decimal ratio over the requested `period` (e.g. "0.35" = +35%).' },
7191
+ asset: { type: "string", description: "AUM (Assets Under Management) in USD \u2014 same field that the input `minAum` filter applies to." },
7192
+ winRate: { type: "string", description: "Lifetime win-rate as a decimal (0~1)." },
7193
+ maxDrawdown: { type: "string", description: 'Max drawdown as a decimal (e.g. "0.2" = 20%). Lower = lower risk.' },
7194
+ onboardDuration: { type: "string", description: "Days the trader has been onboarded on the leaderboard (numeric string)." },
7195
+ portrait: { type: "string", description: "Avatar image URL." },
7196
+ rates: {
7197
+ type: "array",
7198
+ description: "Equity-curve / PnL-rate time series (one entry per day).",
7199
+ items: {
7200
+ type: "object",
7201
+ properties: {
7202
+ statTime: { type: "string", description: 'Date stamp in `YYMMDD` (e.g. "240726" = 2024-07-26).' },
7203
+ value: { type: "string", description: "Cumulative PnL ratio at that day (decimal)." }
7204
+ }
7205
+ }
7206
+ }
7207
+ };
7208
+ var SIGNAL_ITEM_PROPS = {
7209
+ ccy: { type: "string", description: "Instrument ID e.g. BTC-USDT-SWAP (outer identifier; the field name is `ccy`, not `instId`)." },
7210
+ dataVersion: {
7211
+ type: "string",
7212
+ description: "Snapshot version key in `yyyyMMddHH` UTC (10 digits, e.g. `2026043014` = 2026-04-30 14:00 UTC; floored to the hour)."
7213
+ },
7214
+ tradersWithPosition: {
7215
+ type: "integer",
7216
+ description: "Pool traders holding this asset at latest_snap (double-sided counted once). Higher = stronger consensus."
7217
+ },
7218
+ tradersQualified: {
7219
+ type: "integer",
7220
+ description: "Pool size after applying tier filters (incl. those without positions on this instrument)."
7221
+ },
7222
+ longTraders: { type: "integer", description: "Number of pool traders currently long this asset (incl. double-sided)." },
7223
+ shortTraders: { type: "integer", description: "Number of pool traders currently short this asset (incl. double-sided)." },
7224
+ notional: {
7225
+ type: "object",
7226
+ description: "Notional / capital-flow group.",
7227
+ properties: {
7228
+ longNotionalUsdt: {
7229
+ type: "string",
7230
+ description: "Sum of long-side notional in USDT, weighted by each trader's ENTRY PRICE (price_avg), not mark price. Moves only when positions are opened / closed / scaled \u2014 stays constant when positions are unchanged."
7231
+ },
7232
+ shortNotionalUsdt: {
7233
+ type: "string",
7234
+ description: "Sum of short-side notional in USDT, weighted by each trader's ENTRY PRICE (price_avg), not mark price. Moves only when positions are opened / closed / scaled \u2014 stays constant when positions are unchanged."
7235
+ },
7236
+ netNotionalUsdt: {
7237
+ type: "string",
7238
+ description: "Net directional notional in USDT = long \u2212 short. Weighted by each trader's ENTRY PRICE (price_avg), not mark price \u2014 reflects position scaling, not underlying price movement."
7239
+ },
7240
+ totalNotionalUsdt: {
7241
+ type: "string",
7242
+ description: "Gross notional in USDT = long + short. Weighted by each trader's ENTRY PRICE (price_avg), not mark price \u2014 reflects position scaling (open / close / add), not underlying price movement. Stays constant across buckets when traders hold positions unchanged."
7243
+ },
7244
+ totalNotionalVs24h: {
7245
+ type: "string",
7246
+ description: "Capital-flow change ratio vs 24h: (curr \u2212 hist_24h) / hist_24h. Positive = smart money adding exposure; negative = retreating. NULL when hist=0."
7247
+ },
7248
+ smartMoneyLongAvgEntry: {
7249
+ type: "string",
7250
+ description: 'Long-side notional-weighted average entry price (USDT). Empty string `""` when no long. Compare to current price to judge whether following the longs is cheap/expensive now.'
7251
+ },
7252
+ smartMoneyShortAvgEntry: {
7253
+ type: "string",
7254
+ description: 'Short-side notional-weighted average entry price (USDT). Empty string `""` when no short.'
7255
+ }
7256
+ }
7257
+ },
7258
+ longShortRatio: {
7259
+ type: "object",
7260
+ description: "Long/short ratio + historical-delta group.",
7261
+ properties: {
7262
+ longRatioVs1h: { type: "string", description: "longRatio \u2212 hist_1h.longRatio. NULL when no hist." },
7263
+ longRatioVs24h: { type: "string", description: "longRatio \u2212 hist_24h.longRatio. NULL when no hist." },
7264
+ longRatioVs7d: { type: "string", description: "longRatio \u2212 hist_7d.longRatio. NULL when no hist." },
7265
+ longRatio: { type: "string", description: "Headcount long ratio = longTraders / tradersWithPosition. NULL when no traders." },
7266
+ shortRatio: {
7267
+ type: "string",
7268
+ description: "Headcount short ratio = shortTraders / tradersWithPosition. NULL when no traders."
7269
+ },
7270
+ weightedLongRatio: {
7271
+ type: "string",
7272
+ description: "Notional-weighted long ratio = \u03A3(long_notional) / \u03A3(notional). Notional uses each trader's ENTRY PRICE (price_avg), not mark price \u2014 ratio shifts only when positions are scaled. NULL when no notional."
7273
+ },
7274
+ weightedShortRatio: {
7275
+ type: "string",
7276
+ description: "Notional-weighted short ratio = \u03A3(short_notional) / \u03A3(notional). Notional uses each trader's ENTRY PRICE (price_avg), not mark price. NULL when no notional."
7277
+ }
7278
+ }
7279
+ },
7280
+ winRate: {
7281
+ type: "object",
7282
+ description: "Capability (historical performance) group; driven by the `period` window.",
7283
+ properties: {
7284
+ avgLongWinRate: {
7285
+ type: "string",
7286
+ description: "Mean closed-position win-rate (full-market) over `period` days for users currently long. NULL when closed-position sample size is below the configured minimum."
7287
+ },
7288
+ avgShortWinRate: {
7289
+ type: "string",
7290
+ description: "Mean closed-position win-rate (full-market) over `period` days for users currently short. NULL when sample is below threshold."
7291
+ }
7292
+ }
7293
+ }
7294
+ };
7295
+ var SIGNAL_HISTORY_ITEM_PROPS = {
7296
+ ccy: { type: "string", description: "Base currency / instrument key for this bucket." },
7297
+ longRatio: {
7298
+ type: "string",
7299
+ description: "Headcount long ratio at this bucket. Decimal 0~1."
7300
+ },
7301
+ shortRatio: {
7302
+ type: "string",
7303
+ description: "Headcount short ratio at this bucket = shortTraders / tradersWithPosition. Decimal 0~1."
7304
+ },
7305
+ weightedLongRatio: {
7306
+ type: "string",
7307
+ description: "Notional-weighted long ratio at this bucket = \u03A3(long_notional) / \u03A3(notional). Decimal 0~1. Notional uses each trader's ENTRY PRICE (price_avg), not mark price \u2014 ratio shifts only when positions are scaled."
7308
+ },
7309
+ weightedShortRatio: {
7310
+ type: "string",
7311
+ description: "Notional-weighted short ratio at this bucket = \u03A3(short_notional) / \u03A3(notional). Decimal 0~1. Notional uses each trader's ENTRY PRICE (price_avg), not mark price."
7312
+ },
7313
+ longTraders: {
7314
+ type: "integer",
7315
+ description: "Number of traders with long exposure at this bucket (includes dual-side traders)."
7316
+ },
7317
+ shortTraders: {
7318
+ type: "integer",
7319
+ description: "Number of traders with short exposure at this bucket (includes dual-side traders)."
7320
+ },
7321
+ tradersWithPosition: {
7322
+ type: "integer",
7323
+ description: "Pool traders holding this asset at this bucket. Few traders = unreliable signal."
7324
+ },
7325
+ netNotionalUsdt: {
7326
+ type: "string",
7327
+ description: "Net directional notional in USDT at this bucket = long notional \u2212 short notional. Weighted by each trader's ENTRY PRICE (price_avg), not mark price \u2014 reflects position scaling, not underlying price movement."
7328
+ },
7329
+ totalNotionalUsdt: {
7330
+ type: "string",
7331
+ description: "Gross notional in USDT at this bucket = long notional + short notional. Weighted by each trader's ENTRY PRICE (price_avg), not mark price \u2014 tracks capital deployed (rising = adding, falling = retreating). Stays constant across buckets when traders hold positions unchanged."
7332
+ },
7333
+ tradersQualified: { type: "integer", description: "Pool size after applying tier filters (includes traders without a position)." },
7334
+ dataVersion: { type: "string", description: "Snapshot version key in `yyyyMMddHH` UTC (10-digit, e.g. `2026042820`)." }
7335
+ };
6836
7336
  function registerSmartmoneyTools() {
6837
7337
  const tools = [
6838
- /* ---------- 1. Overview ---------- */
7338
+ /* ===================================================== */
7339
+ /* Trader family (6) */
7340
+ /* ===================================================== */
7341
+ /* ---------- T1. Top traders (leaderboard rank) ---------- */
6839
7342
  {
6840
- name: "smartmoney_get_overview",
7343
+ name: "smartmoney_get_traders_by_filter",
6841
7344
  module: "smartmoney",
6842
- description: "Multi-currency smart money overview, ranked by tradersWithPosition DESC (most-watched first). Requires either ts (recommended, Date.now()) or dataVersion (yyyyMMddHHmm UTC) \u2014 at least one must be set; ts wins when both are sent. For single-currency signal with entry prices and trend, use smartmoney_get_signal.",
7345
+ 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.",
6843
7346
  isWrite: false,
7347
+ outputSchema: envelope(
7348
+ { type: "array", items: { type: "object", properties: TRADER_ITEM_PROPS } },
7349
+ {
7350
+ updateTime: {
7351
+ type: "string",
7352
+ description: "Snapshot version of the leaderboard, in `yyyyMMddHHmm` (UTC+8, e.g. `202604301815`). Lives at the response top level (shared by every item in `data`), NOT inside each trader row. Refreshed approximately every 5 minutes."
7353
+ },
7354
+ pagination: PAGINATION_PROP
7355
+ }
7356
+ ),
6844
7357
  inputSchema: {
6845
7358
  type: "object",
6846
7359
  properties: {
6847
- ts: {
6848
- type: "string",
6849
- description: "Recommended. Timestamp ms \u2014 use Date.now() for latest."
6850
- },
6851
- dataVersion: {
6852
- type: "string",
6853
- description: "Alternative. yyyyMMddHHmm UTC for prior snapshot. If both sent, ts wins."
6854
- },
6855
- instType: {
6856
- type: "string",
6857
- description: "SPOT|MARGIN|FUTURES|SWAP|OPTION (default SWAP)"
6858
- },
6859
- ...SIGNAL_POOL_FILTER_PROPS,
6860
- lmtNum: {
7360
+ updateTime: {
6861
7361
  type: "string",
6862
- description: "Trader pool size 1-500 (default 100)"
7362
+ description: 'Snapshot version key \u2014 12-digit `yyyyMMddHHmm` (UTC+8) as a string, e.g. `"202604301815"`. Omit to query the latest snapshot (refreshed every ~5 min).'
6863
7363
  },
6864
- instCcyList: {
7364
+ ...LEADERBOARD_POOL_FILTER_PROPS,
7365
+ after: {
6865
7366
  type: "string",
6866
- description: "Comma-separated currency codes e.g. BTC,ETH,SOL (prefix-matched against instId)"
7367
+ description: 'Pagination cursor (older page) \u2014 pass the `authorId` of the last item from the previous page as a string, e.g. `"872913470357110787"`. Cursor anchors on `authorId` while preserving the current `sortBy` order.'
6867
7368
  },
6868
- instCcy: {
7369
+ before: {
6869
7370
  type: "string",
6870
- description: "Single currency e.g. BTC; alias for instCcyList (instCcyList wins if both set)"
7371
+ description: 'Pagination cursor (newer page) \u2014 pass the `authorId` of the first item from the previous page as a string, e.g. `"872913470357110787"`. Cursor anchors on `authorId` while preserving the current `sortBy` order.'
6871
7372
  },
6872
- topInstruments: {
6873
- type: "string",
6874
- description: "Top N instruments 1-100 (default 20)"
7373
+ limit: {
7374
+ type: "integer",
7375
+ minimum: 1,
7376
+ maximum: 100,
7377
+ default: 10,
7378
+ description: "Max results per page (default 10, max 100)."
6875
7379
  }
6876
- }
7380
+ },
7381
+ required: ["sortBy", "period"]
6877
7382
  },
6878
7383
  handler: async (rawArgs, context) => {
6879
7384
  const args = asRecord(rawArgs);
6880
- const dv = readString(args, "dataVersion");
6881
- const ts = readString(args, "ts");
6882
- if (!dv && !ts) {
6883
- throw new ValidationError('Either "dataVersion" or "ts" is required for smartmoney_get_overview.');
6884
- }
7385
+ const limit = readNumber(args, "limit");
6885
7386
  const response = await context.client.privateGet(
6886
- PATH_OVERVIEW,
7387
+ PATH_LEADERBOARD,
6887
7388
  compactObject({
6888
- dataVersion: dv,
6889
- ts,
6890
- instType: readString(args, "instType"),
7389
+ updateTime: readString(args, "updateTime"),
6891
7390
  ...readPoolFilters(args),
6892
- lmtNum: readString(args, "lmtNum"),
6893
- instCcyList: readString(args, "instCcyList"),
6894
- instCcy: readString(args, "instCcy"),
6895
- topInstruments: readString(args, "topInstruments")
7391
+ after: readString(args, "after"),
7392
+ before: readString(args, "before"),
7393
+ limit
6896
7394
  }),
6897
- publicRateLimit("smartmoney_get_overview", 5)
7395
+ publicRateLimit("smartmoney_get_traders_by_filter", SMARTMONEY_RPS)
6898
7396
  );
6899
- return normalizeResponse(response);
7397
+ const normalized = normalizeResponse(response);
7398
+ const { items, updateTime } = extractLeaderboardEnvelope(normalized.data);
7399
+ return {
7400
+ ...normalized,
7401
+ data: items,
7402
+ ...updateTime ? { updateTime } : {},
7403
+ pagination: buildPagination(items, limit ?? 10, "authorId")
7404
+ };
6900
7405
  }
6901
7406
  },
6902
- /* ---------- 2. Signal ---------- */
7407
+ /* ---------- T2. Trader performance (by authorIds) ---------- */
6903
7408
  {
6904
- name: "smartmoney_get_signal",
7409
+ name: "smartmoney_get_performance_by_trader",
6905
7410
  module: "smartmoney",
6906
- description: "Single-currency consensus signal: long/short ratio, entry prices, trend, capital flow. Requires either instId (e.g. BTC-USDT-SWAP, recommended) or instCcy (SPOT/SWAP only) \u2014 instId wins when both are sent. Requires either ts (recommended, Date.now()) or dataVersion (yyyyMMddHHmm UTC) \u2014 at least one must be set; ts wins when both are sent. For multi-currency overview, use smartmoney_get_overview. For timeline, use smartmoney_get_signal_history.",
7411
+ 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).",
6907
7412
  isWrite: false,
7413
+ outputSchema: envelope(
7414
+ { type: "array", items: { type: "object", properties: TRADER_ITEM_PROPS } },
7415
+ {
7416
+ updateTime: {
7417
+ type: "string",
7418
+ description: "Snapshot version of the leaderboard, in `yyyyMMddHHmm` (UTC+8, e.g. `202604301815`). Lives at the response top level (shared by every item in `data`), NOT inside each trader row."
7419
+ }
7420
+ }
7421
+ ),
6908
7422
  inputSchema: {
6909
7423
  type: "object",
6910
7424
  properties: {
6911
- instId: {
6912
- type: "string",
6913
- description: "Recommended. e.g. BTC-USDT-SWAP"
7425
+ authorIds: {
7426
+ type: "array",
7427
+ items: { type: "string" },
7428
+ minItems: 1,
7429
+ description: 'Trader IDs to look up, e.g. `["1001", "1002"]`. Required.'
6914
7430
  },
6915
- instCcy: {
7431
+ sortBy: {
6916
7432
  type: "string",
6917
- description: "e.g. BTC (SPOT/SWAP only); instId takes precedence if both set"
7433
+ enum: ["pnl", "pnlRatio"],
7434
+ default: "pnl",
7435
+ description: 'Required. Result sort key. `pnl` = absolute USD profit; `pnlRatio` = percentage return. Default `"pnl"`.'
6918
7436
  },
6919
- ts: {
7437
+ period: {
6920
7438
  type: "string",
6921
- description: "Recommended. Timestamp ms \u2014 use Date.now() for latest."
6922
- },
6923
- dataVersion: {
6924
- type: "string",
6925
- description: "Alternative. yyyyMMddHHmm UTC for prior snapshot. If both sent, ts wins."
6926
- },
6927
- ...SIGNAL_POOL_FILTER_PROPS,
6928
- lmtNum: {
7439
+ enum: PERIOD_DAYS,
7440
+ default: "90",
7441
+ description: 'Required. Performance lookback window in days. One of `"3"` / `"7"` / `"30"` / `"90"`. Default `"90"`.'
7442
+ }
7443
+ },
7444
+ required: ["authorIds", "sortBy", "period"]
7445
+ },
7446
+ handler: async (rawArgs, context) => {
7447
+ const args = asRecord(rawArgs);
7448
+ assertEnum2(args, "sortBy", ["pnl", "pnlRatio"]);
7449
+ assertEnum2(args, "period", PERIOD_DAYS);
7450
+ const authorIds = readArrayAsCsv(args, "authorIds");
7451
+ if (!authorIds) {
7452
+ throw actionableError(
7453
+ '"authorIds" is required and must be a non-empty array.',
7454
+ 'Discover trader IDs via `smartmoney_get_traders_by_filter`, then pass them as an array (e.g. ["1001", "1002"]).'
7455
+ );
7456
+ }
7457
+ const response = await context.client.privateGet(
7458
+ PATH_LEADERBOARD,
7459
+ compactObject({
7460
+ authorIds,
7461
+ // Apply schema defaults explicitly so behavior does not depend on backend defaults
7462
+ // (CLI bypasses MCP `required` validation, so handler is the only deterministic layer).
7463
+ sortBy: readString(args, "sortBy") ?? "pnl",
7464
+ period: readString(args, "period") ?? "90"
7465
+ }),
7466
+ publicRateLimit("smartmoney_get_performance_by_trader", SMARTMONEY_RPS)
7467
+ );
7468
+ const normalized = normalizeResponse(response);
7469
+ const { items, updateTime } = extractLeaderboardEnvelope(normalized.data);
7470
+ return {
7471
+ ...normalized,
7472
+ data: items,
7473
+ ...updateTime ? { updateTime } : {}
7474
+ };
7475
+ }
7476
+ },
7477
+ /* ---------- T3. Trader current positions ---------- */
7478
+ {
7479
+ name: "smartmoney_get_trader_positions",
7480
+ module: "smartmoney",
7481
+ 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).",
7482
+ isWrite: false,
7483
+ outputSchema: envelope({
7484
+ type: "array",
7485
+ items: {
7486
+ type: "object",
7487
+ properties: {
7488
+ posId: { type: "string", description: "Unique position ID. Stable across the position's lifetime." },
7489
+ instId: { type: "string", description: "Instrument ID e.g. BTC-USDT-SWAP." },
7490
+ instType: {
7491
+ type: "string",
7492
+ description: "Instrument business line: `SWAP` (perpetual) | `SPOT` | `FUTURES` (delivery) | `MARGIN` | `OPTION`."
7493
+ },
7494
+ posSide: {
7495
+ type: "string",
7496
+ description: "Raw upstream position direction. `long` = long-side position (buy-to-open); `short` = short-side position (sell-to-open); `net` (or legacy `both`) = net/one-way position mode where the sign of `pos` encodes direction. Prefer the derived `direction` field below for agent logic."
7497
+ },
7498
+ direction: {
7499
+ type: "string",
7500
+ enum: ["long", "short"],
7501
+ description: 'Derived clean direction (`long` | `short`) \u2014 handler computes this from `posSide` + sign of `pos` so agents do not have to branch on the `posSide="net"` net-mode case.'
7502
+ },
7503
+ posCcy: { type: "string", description: 'Position currency \u2014 the asset being held, e.g. "BTC".' },
7504
+ quoteCcy: { type: "string", description: 'Quote currency the position is priced/settled in, e.g. "USDT".' },
7505
+ pos: {
7506
+ type: "string",
7507
+ description: "Position size (numeric string). Unit depends on instType: coins for SPOT/MARGIN, contracts (\u5F20) for SWAP/FUTURES/OPTION."
7508
+ },
7509
+ lever: { type: "string", description: 'Leverage multiplier (numeric string; "1" for spot).' },
7510
+ avgPx: { type: "string", description: "Volume-weighted average entry price (numeric string)." },
7511
+ last: { type: "string", description: "Latest market/mark price for the instrument (numeric string)." },
7512
+ notionalUsd: { type: "string", description: "Current position notional value in USD." },
7513
+ upl: { type: "string", description: "Unrealized (floating) PnL, denominated in `quoteCcy`." },
7514
+ pnl: { type: "string", description: "Realized PnL accrued on this position so far, denominated in `quoteCcy`." },
7515
+ cTime: { type: "string", description: "Position open time as Unix milliseconds (numeric string)." },
7516
+ positionIntensity: {
7517
+ type: "string",
7518
+ description: "Conviction metric = notionalUsd / trader.asset (this position's notional as a share of the trader's AUM). Higher = the trader is betting a larger fraction of their book on this position."
7519
+ }
7520
+ }
7521
+ }
7522
+ }),
7523
+ inputSchema: {
7524
+ type: "object",
7525
+ properties: {
7526
+ authorId: {
6929
7527
  type: "string",
6930
- description: "Trader pool size 1-500 (default 100)"
7528
+ description: "Trader's unique ID (obtain from `smartmoney_get_traders_by_filter`)."
6931
7529
  },
6932
- authorIds: {
7530
+ instId: {
6933
7531
  type: "string",
6934
- description: "Comma-separated user IDs e.g. 1001,1002 \u2014 restricts the trader pool to these IDs only (precise filter)"
7532
+ 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.'
6935
7533
  }
6936
- }
7534
+ },
7535
+ required: ["authorId"]
6937
7536
  },
6938
7537
  handler: async (rawArgs, context) => {
6939
7538
  const args = asRecord(rawArgs);
6940
- const instId = readString(args, "instId");
6941
- const instCcy = readString(args, "instCcy");
6942
- if (!instId && !instCcy) {
6943
- throw new ValidationError('Either "instId" or "instCcy" is required for smartmoney_get_signal.');
6944
- }
6945
- const dv = readString(args, "dataVersion");
6946
- const ts = readString(args, "ts");
6947
- if (!dv && !ts) {
6948
- throw new ValidationError('Either "dataVersion" or "ts" is required for smartmoney_get_signal.');
7539
+ const authorId = readString(args, "authorId");
7540
+ if (!authorId) {
7541
+ throw actionableError(
7542
+ '"authorId" is required.',
7543
+ "Discover trader IDs via `smartmoney_get_traders_by_filter` and pass one ID here."
7544
+ );
6949
7545
  }
6950
7546
  const response = await context.client.privateGet(
6951
- PATH_SIGNAL,
7547
+ PATH_POSITION_CURRENT,
6952
7548
  compactObject({
6953
- instId,
6954
- instCcy,
6955
- dataVersion: dv,
6956
- ts,
6957
- ...readPoolFilters(args),
6958
- lmtNum: readString(args, "lmtNum"),
6959
- authorIds: readString(args, "authorIds")
7549
+ authorId,
7550
+ instCcy: extractBaseCcy(readString(args, "instId"))
6960
7551
  }),
6961
- publicRateLimit("smartmoney_get_signal", 5)
7552
+ publicRateLimit("smartmoney_get_trader_positions", SMARTMONEY_RPS)
6962
7553
  );
6963
- return normalizeResponse(response);
7554
+ const normalized = normalizeResponse(response);
7555
+ return { ...normalized, data: extractPositionData(normalized.data) };
6964
7556
  }
6965
7557
  },
6966
- /* ---------- 3. Signal History ---------- */
7558
+ /* ---------- T4. Trader closed-position history ---------- */
6967
7559
  {
6968
- name: "smartmoney_get_signal_history",
7560
+ name: "smartmoney_get_trader_positions_history",
6969
7561
  module: "smartmoney",
6970
- description: "Signal history timeline sorted by ts DESC for trend analysis. Requires instId. Requires either ts (recommended, Date.now()) or dataVersion (yyyyMMddHHmm UTC) \u2014 at least one must be set; ts wins when both are sent. For current snapshot, use smartmoney_get_signal.",
7562
+ 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).",
6971
7563
  isWrite: false,
7564
+ outputSchema: envelope(
7565
+ {
7566
+ type: "array",
7567
+ items: {
7568
+ type: "object",
7569
+ properties: {
7570
+ posId: { type: "string", description: "Unique closed-position ID. Use as the `after` / `before` cursor when paginating." },
7571
+ instId: { type: "string", description: "Instrument ID e.g. BTC-USDT-SWAP." },
7572
+ instType: {
7573
+ type: "string",
7574
+ description: "Instrument business line: `SWAP` (perpetual) | `FUTURES` (delivery) | `MARGIN` | `SPOT`."
7575
+ },
7576
+ ctVal: {
7577
+ type: "string",
7578
+ description: "Contract face value \u2014 USD value of a single contract (\u5F20). Numeric string. Empty/0 for non-contract instruments."
7579
+ },
7580
+ posSide: {
7581
+ type: "string",
7582
+ description: "Position direction at close. `long` = was long-side; `short` = was short-side."
7583
+ },
7584
+ lever: { type: "string", description: "Leverage multiplier used for this position (numeric string)." },
7585
+ mgnMode: {
7586
+ type: "string",
7587
+ description: "Margin mode used for this position. `cross` = cross-margin (shared collateral pool); `isolated` = isolated-margin (per-position collateral)."
7588
+ },
7589
+ marginCcy: {
7590
+ type: "string",
7591
+ description: 'Margin currency held as collateral for this position, e.g. "BTC" or "USDT".'
7592
+ },
7593
+ quoteCcy: { type: "string", description: 'Quote currency the position settled in, e.g. "USDT".' },
7594
+ openAvgPx: { type: "string", description: "Volume-weighted average price across all open fills (numeric string)." },
7595
+ closeAvgPx: { type: "string", description: "Volume-weighted average price across all close fills (numeric string)." },
7596
+ openMaxAmount: {
7597
+ type: "string",
7598
+ description: "Peak position size held during the position's lifetime, in contracts (\u5F20)."
7599
+ },
7600
+ closeAmount: {
7601
+ type: "string",
7602
+ description: "Total amount closed across all close fills, in contracts for SWAP/FUTURES or in base currency for SPOT/MARGIN (numeric string)."
7603
+ },
7604
+ realizedPnl: { type: "string", description: "Cumulative realized PnL during the position's lifetime, in `quoteCcy` units." },
7605
+ pnl: {
7606
+ type: "string",
7607
+ description: "Total realized PnL for this position including fees and funding, denominated in quoteCcy (numeric string). Differs from `realizedPnl` which may exclude fees."
7608
+ },
7609
+ pnlRatio: {
7610
+ type: "string",
7611
+ description: 'Realized PnL as a decimal ratio of cost basis (e.g. "0.15" = +15%, "-0.20" = \u221220%).'
7612
+ },
7613
+ fee: {
7614
+ type: "string",
7615
+ description: "Cumulative trading fee paid over the position's lifetime, denominated in quoteCcy (numeric string, negative = cost)."
7616
+ },
7617
+ fundingFee: {
7618
+ type: "string",
7619
+ description: "Cumulative funding fee paid or received over the position's lifetime, denominated in quoteCcy (numeric string; negative = paid, positive = received)."
7620
+ },
7621
+ liquidationStatus: {
7622
+ type: "string",
7623
+ description: 'Whether the position was liquidated. `"0"` = normal close (not liquidated); `"1"` = liquidated. Use this dedicated field for liquidation checks; `closeType` may also encode liquidation via the `liquidateClose` / `liquidateReceive` / `adl` values.'
7624
+ },
7625
+ closeType: {
7626
+ type: "string",
7627
+ description: "How the position was closed. `allClose` = entire position closed in one action; `partClose` = partially closed (position reduced but not fully exited); `liquidateClose` = forced liquidation; `liquidateReceive` = received liquidation transfer; `adl` = auto-deleveraging."
7628
+ },
7629
+ cTime: { type: "string", description: "Position open time as Unix milliseconds (numeric string)." },
7630
+ uTime: { type: "string", description: "Position close time as Unix milliseconds (numeric string)." }
7631
+ }
7632
+ }
7633
+ },
7634
+ { pagination: PAGINATION_PROP }
7635
+ ),
6972
7636
  inputSchema: {
6973
7637
  type: "object",
6974
7638
  properties: {
6975
- instId: {
7639
+ authorId: {
6976
7640
  type: "string",
6977
- description: "e.g. BTC-USDT-SWAP"
7641
+ description: "Trader's unique ID (obtain from `smartmoney_get_traders_by_filter`)."
6978
7642
  },
6979
- ts: {
7643
+ instId: {
6980
7644
  type: "string",
6981
- description: "Recommended. Timestamp ms \u2014 use Date.now() for latest."
7645
+ 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.'
6982
7646
  },
6983
- dataVersion: {
7647
+ after: {
6984
7648
  type: "string",
6985
- description: "Alternative. yyyyMMddHHmm UTC for prior snapshot. If both sent, ts wins."
7649
+ description: 'Pagination cursor (older) \u2014 returns positions with `posId` smaller than this value. Pass the `posId` as a string, e.g. `"872913470357110787"`.'
6986
7650
  },
6987
- granularity: {
7651
+ before: {
6988
7652
  type: "string",
6989
- description: "1h or 1d (default 1h)"
7653
+ description: 'Pagination cursor (newer) \u2014 returns positions with `posId` greater than this value. Pass the `posId` as a string, e.g. `"872913470357110787"`.'
6990
7654
  },
6991
7655
  limit: {
6992
- type: "string",
6993
- description: "Data points 1-500 (default 24)"
6994
- },
6995
- ...SIGNAL_POOL_FILTER_PROPS
7656
+ type: "integer",
7657
+ minimum: 1,
7658
+ maximum: 100,
7659
+ default: 10,
7660
+ description: "Max positions per page (default 10, max 100)."
7661
+ }
6996
7662
  },
6997
- required: ["instId"]
7663
+ required: ["authorId"]
6998
7664
  },
6999
7665
  handler: async (rawArgs, context) => {
7000
7666
  const args = asRecord(rawArgs);
7001
- const dv = readString(args, "dataVersion");
7002
- const ts = readString(args, "ts");
7003
- if (!dv && !ts) {
7004
- throw new ValidationError('Either "dataVersion" or "ts" is required for smartmoney_get_signal_history.');
7667
+ const authorId = readString(args, "authorId");
7668
+ if (!authorId) {
7669
+ throw actionableError(
7670
+ '"authorId" is required.',
7671
+ "Discover trader IDs via `smartmoney_get_traders_by_filter`."
7672
+ );
7005
7673
  }
7674
+ const limit = readNumber(args, "limit");
7006
7675
  const response = await context.client.privateGet(
7007
- PATH_SIGNAL_HISTORY,
7676
+ PATH_POSITION_HISTORY,
7008
7677
  compactObject({
7009
- instId: requireString(args, "instId"),
7010
- dataVersion: dv,
7011
- ts,
7012
- granularity: readString(args, "granularity"),
7013
- limit: readString(args, "limit"),
7014
- ...readPoolFilters(args)
7678
+ authorId,
7679
+ instCcy: extractBaseCcy(readString(args, "instId")),
7680
+ after: readString(args, "after"),
7681
+ before: readString(args, "before"),
7682
+ limit
7015
7683
  }),
7016
- publicRateLimit("smartmoney_get_signal_history", 5)
7684
+ publicRateLimit("smartmoney_get_trader_positions_history", SMARTMONEY_RPS)
7017
7685
  );
7018
- return normalizeResponse(response);
7686
+ const normalized = normalizeResponse(response);
7687
+ const data = Array.isArray(normalized.data) ? normalized.data : [];
7688
+ return {
7689
+ ...normalized,
7690
+ data,
7691
+ pagination: buildPagination(data, limit ?? 10, "posId")
7692
+ };
7019
7693
  }
7020
7694
  },
7021
- /* ---------- 4. Traders (list) ---------- */
7695
+ /* ---------- T5. Trader order history ---------- */
7022
7696
  {
7023
- name: "smartmoney_get_traders",
7697
+ name: "smartmoney_get_trader_orders_history",
7024
7698
  module: "smartmoney",
7025
- description: "List/filter leaderboard traders. For single trader detail: smartmoney_get_trader_detail.",
7699
+ 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).",
7026
7700
  isWrite: false,
7701
+ outputSchema: envelope(
7702
+ {
7703
+ type: "array",
7704
+ items: {
7705
+ type: "object",
7706
+ properties: {
7707
+ ordId: { type: "string", description: "Unique order ID. Use as the `after` / `before` cursor when paginating." },
7708
+ instId: { type: "string", description: "Instrument ID e.g. BTC-USDT-SWAP." },
7709
+ displayId: { type: "string", description: "Display-form instrument ID used in OKX UI." },
7710
+ instType: {
7711
+ type: "string",
7712
+ description: "Instrument business line: `SWAP` (perpetual contract) | `SPOT`."
7713
+ },
7714
+ baseName: { type: "string", description: 'Base currency symbol, e.g. "BTC".' },
7715
+ quoteName: { type: "string", description: 'Quote currency symbol, e.g. "USD".' },
7716
+ tradeQuoteCcy: { type: "string", description: "Quote currency the fill actually settled in." },
7717
+ side: {
7718
+ type: "string",
7719
+ description: "Order side. `buy` = open long / close short; `sell` = open short / close long."
7720
+ },
7721
+ posSide: {
7722
+ type: "string",
7723
+ description: "Position direction the order applies to: `long` | `short`. Indicates whether the trader was opening/closing a long-side or short-side position."
7724
+ },
7725
+ ordType: {
7726
+ type: "string",
7727
+ description: "Order type. `limit` = price-protected limit order; `market` = immediate at best available price."
7728
+ },
7729
+ lever: { type: "string", description: 'Leverage multiplier used for this order (numeric string; "1" for spot).' },
7730
+ px: { type: "string", description: "Submitted order price (numeric string). For market orders this may be empty/0." },
7731
+ avgPx: { type: "string", description: "Volume-weighted average fill price (numeric string)." },
7732
+ sz: {
7733
+ type: "string",
7734
+ description: "Order size. Unit depends on instType: coins (\u5E01) for SPOT, contracts (\u5F20) for SWAP/FUTURES."
7735
+ },
7736
+ value: {
7737
+ type: "string",
7738
+ description: "Order notional value, denominated in `quoteName` units."
7739
+ },
7740
+ cTime: { type: "string", description: "Order creation time as Unix milliseconds (numeric string)." },
7741
+ fillTime: {
7742
+ type: "string",
7743
+ description: "Timestamp when the order was last filled, as Unix milliseconds (numeric string)."
7744
+ },
7745
+ uTime: {
7746
+ type: "string",
7747
+ description: "Timestamp when the order record was last updated, as Unix milliseconds (numeric string)."
7748
+ }
7749
+ }
7750
+ }
7751
+ },
7752
+ { pagination: PAGINATION_PROP }
7753
+ ),
7027
7754
  inputSchema: {
7028
7755
  type: "object",
7029
7756
  properties: {
7030
- dataVersion: {
7757
+ authorId: {
7031
7758
  type: "string",
7032
- description: "yyyyMMddHHmm, omit=latest"
7759
+ description: "Trader's unique ID (obtain from `smartmoney_get_traders_by_filter`)."
7033
7760
  },
7034
- ...LEADERBOARD_POOL_FILTER_PROPS,
7035
- authorIds: {
7761
+ instId: {
7036
7762
  type: "string",
7037
- description: "Comma-separated author IDs"
7763
+ 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.'
7038
7764
  },
7039
7765
  after: {
7040
7766
  type: "string",
7041
- description: "Cursor after this authorId"
7767
+ description: 'Pagination cursor (older) \u2014 returns trades with `ordId` smaller than this value. Pass the `ordId` as a string, e.g. `"872913470357110787"`.'
7042
7768
  },
7043
7769
  before: {
7044
7770
  type: "string",
7045
- description: "Cursor before this authorId"
7771
+ description: 'Pagination cursor (newer) \u2014 returns trades with `ordId` greater than this value. Pass the `ordId` as a string, e.g. `"872913470357110787"`.'
7046
7772
  },
7047
7773
  limit: {
7048
- type: "string",
7049
- description: "Max results 1-100"
7774
+ type: "integer",
7775
+ minimum: 1,
7776
+ maximum: 100,
7777
+ default: 10,
7778
+ description: "Max trades per page (default 10, max 100)."
7050
7779
  }
7051
- }
7780
+ },
7781
+ required: ["authorId"]
7052
7782
  },
7053
7783
  handler: async (rawArgs, context) => {
7054
7784
  const args = asRecord(rawArgs);
7785
+ const authorId = readString(args, "authorId");
7786
+ if (!authorId) {
7787
+ throw actionableError(
7788
+ '"authorId" is required.',
7789
+ "Discover trader IDs via `smartmoney_get_traders_by_filter`."
7790
+ );
7791
+ }
7792
+ const limit = readNumber(args, "limit");
7055
7793
  const response = await context.client.privateGet(
7056
- PATH_LEADERBOARD,
7794
+ PATH_TRADE_RECORDS,
7057
7795
  compactObject({
7058
- dataVersion: readString(args, "dataVersion"),
7059
- ...readPoolFilters(args),
7060
- authorIds: readString(args, "authorIds"),
7796
+ authorId,
7797
+ instCcy: extractBaseCcy(readString(args, "instId")),
7061
7798
  after: readString(args, "after"),
7062
7799
  before: readString(args, "before"),
7063
- limit: readString(args, "limit")
7800
+ limit
7064
7801
  }),
7065
- publicRateLimit("smartmoney_get_traders", 5)
7802
+ publicRateLimit("smartmoney_get_trader_orders_history", SMARTMONEY_RPS)
7066
7803
  );
7067
7804
  const normalized = normalizeResponse(response);
7068
- return { ...normalized, data: extractLeaderboardData(normalized.data) };
7805
+ const data = Array.isArray(normalized.data) ? normalized.data : [];
7806
+ return {
7807
+ ...normalized,
7808
+ data,
7809
+ pagination: buildPagination(data, limit ?? 10, "ordId")
7810
+ };
7069
7811
  }
7070
7812
  },
7071
- /* ---------- 5. Trader Detail (composite) ---------- */
7813
+ /* ---------- T6. Search top traders by nickname keyword ---------- */
7072
7814
  {
7073
- name: "smartmoney_get_trader_detail",
7815
+ name: "smartmoney_search_trader",
7074
7816
  module: "smartmoney",
7075
- description: "Trader portrait: profile + positions + trades. Requires authorId from smartmoney_get_traders. Do NOT use for listing \u2014 use smartmoney_get_traders.",
7817
+ description: "Search Top Traders by nickname keyword, ranked by OKX-platform follower count DESC. Returns up to 10 matches; intersects KOL full-text recall with the Top Trader set. Use when: resolving a nickname or partial name to `authorId`(s) before calling other `smartmoney_get_trader_*` tools. See also: `smartmoney_get_traders_by_filter` (discover top performers by criteria), `smartmoney_get_performance_by_trader` (lookup by known authorId).",
7076
7818
  isWrite: false,
7819
+ outputSchema: envelope({
7820
+ type: "array",
7821
+ description: "Matched Top Traders (\u226410), sorted by `followerCount` DESC. Empty array when no recall intersects the Top Trader set.",
7822
+ items: {
7823
+ type: "object",
7824
+ properties: {
7825
+ authorId: { type: "string", description: "Trader's unique ID \u2014 pass to other `smartmoney_get_trader_*` tools." },
7826
+ nickName: { type: "string", description: "Display nickname matched against the keyword." },
7827
+ followerCount: { type: "string", description: "OKX-platform follower count (numeric string; Twitter followers excluded). Sort key." }
7828
+ }
7829
+ }
7830
+ }),
7077
7831
  inputSchema: {
7078
7832
  type: "object",
7079
7833
  properties: {
7080
- authorId: {
7834
+ keyword: {
7835
+ type: "string",
7836
+ description: "Nickname search keyword. Required, must be non-empty / non-whitespace. Matched candidates are intersected with the Top Trader set."
7837
+ }
7838
+ },
7839
+ required: ["keyword"]
7840
+ },
7841
+ handler: async (rawArgs, context) => {
7842
+ const args = asRecord(rawArgs);
7843
+ const keyword = readString(args, "keyword");
7844
+ if (!keyword || keyword.trim() === "") {
7845
+ throw actionableError(
7846
+ '"keyword" is required and must be non-empty.',
7847
+ 'Pass a nickname fragment (e.g. "alice", "\u5C0F\u660E"). For known author IDs use `smartmoney_get_performance_by_trader` instead.'
7848
+ );
7849
+ }
7850
+ const response = await context.client.privateGet(
7851
+ PATH_TOP_TRADER_SEARCH,
7852
+ compactObject({ keyword }),
7853
+ publicRateLimit("smartmoney_search_trader", SMARTMONEY_RPS)
7854
+ );
7855
+ const normalized = normalizeResponse(response);
7856
+ const data = Array.isArray(normalized.data) ? normalized.data : [];
7857
+ return { ...normalized, data };
7858
+ }
7859
+ },
7860
+ /* ===================================================== */
7861
+ /* Signal/Coin family (4) */
7862
+ /* ===================================================== */
7863
+ /* ---------- S1. Signal overview by filter (multi-asset, tier-filtered pool) ---------- */
7864
+ {
7865
+ name: "smartmoney_get_signal_overview_by_filter",
7866
+ module: "smartmoney",
7867
+ description: "Multi-asset smart-money consensus signals (long/short ratio, weighted entry, capital flow, deltas vs 1h/24h/7d), aggregated over a tier-filtered trader pool (PnL / win-rate / drawdown / AUM). Pick instruments via `topInstruments` OR `instCcyList` \u2014 exactly one. Snapshot time auto-resolved to current hour. **Linear (USDT/USDS-margined) contracts only \u2014 coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions are excluded by upstream and silently omitted from the aggregation.** Use when: latest cross-asset consensus from a criteria-defined pool. See also: `smartmoney_get_signal_overview_by_trader` (restrict pool to specific traders), `smartmoney_get_signal_trend_by_filter` (time-series instead of latest snapshot).",
7868
+ isWrite: false,
7869
+ outputSchema: envelope({
7870
+ type: "array",
7871
+ description: "Per-instrument snapshot, one element per requested coin.",
7872
+ items: { type: "object", properties: SIGNAL_ITEM_PROPS }
7873
+ }),
7874
+ inputSchema: {
7875
+ type: "object",
7876
+ properties: {
7877
+ topInstruments: {
7878
+ type: "integer",
7879
+ minimum: 1,
7880
+ maximum: 100,
7881
+ default: 20,
7882
+ description: "Top-N hottest instruments to aggregate (sorted by `tradersWithPosition` DESC). Mutually exclusive with `instCcyList`; one of the two is required (default 20 if neither provided)."
7883
+ },
7884
+ instCcyList: {
7885
+ type: "array",
7886
+ items: { type: "string" },
7887
+ minItems: 1,
7888
+ description: 'Base currencies to aggregate, e.g. `["BTC", "ETH", "SOL"]`. Mutually exclusive with `topInstruments`. Scope: only USDT-margined and USDS-margined (linear) instruments \u2014 e.g. `BTC` covers `BTC-USDT-SWAP` + `BTC-USDS-SWAP`. Coin-margined contracts (`BTC-USD-SWAP`, `BTC-USD-DELIVERY`) are NOT included; positions a trader holds in those instruments are silently dropped from the aggregation.'
7889
+ },
7890
+ ...SIGNAL_POOL_FILTER_PROPS,
7891
+ lmtNum: {
7892
+ type: "integer",
7893
+ minimum: 1,
7894
+ maximum: 2e3,
7895
+ default: 100,
7896
+ description: "Top-N traders to pull into the aggregation pool, ranked by `sortBy` (DESC). Larger pool = stronger signal but slower. Default 100 is fine for most cases."
7897
+ }
7898
+ },
7899
+ required: ["sortBy", "period"]
7900
+ },
7901
+ handler: async (rawArgs, context) => {
7902
+ const args = asRecord(rawArgs);
7903
+ const instCcyList = readArrayAsCsv(args, "instCcyList");
7904
+ const topInstrumentsRaw = readNumber(args, "topInstruments");
7905
+ if (instCcyList && topInstrumentsRaw !== void 0) {
7906
+ throw actionableError(
7907
+ '"topInstruments" and "instCcyList" are mutually exclusive.',
7908
+ "Pass exactly one \u2014 `topInstruments` for top-N hottest coins, or `instCcyList` for specific coins."
7909
+ );
7910
+ }
7911
+ const response = await context.client.privateGet(
7912
+ PATH_OVERVIEW,
7913
+ compactObject({
7914
+ ...instCcyList ? { instCcyList } : { topInstruments: topInstrumentsRaw ?? 20 },
7915
+ ...readSignalPoolFilters(args),
7916
+ lmtNum: readNumber(args, "lmtNum")
7917
+ }),
7918
+ publicRateLimit("smartmoney_get_signal_overview_by_filter", SMARTMONEY_RPS)
7919
+ );
7920
+ return normalizeResponse(response);
7921
+ }
7922
+ },
7923
+ /* ---------- S2. Signal overview by trader (multi-asset, authorIds-restricted) ---------- */
7924
+ {
7925
+ name: "smartmoney_get_signal_overview_by_trader",
7926
+ module: "smartmoney",
7927
+ description: "Multi-asset smart-money signals aggregated over a hand-picked set of traders (`authorIds`). Pick instruments via `topInstruments` OR `instCcyList`. Capability tier filters (pnlTier / winRateTier / etc.) not exposed \u2014 backend uses defaults for direct-lookup scenarios. **Linear (USDT/USDS-margined) contracts only \u2014 a trader's coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions are silently excluded from the aggregation, even when those positions are large.** Use `smartmoney_get_trader_positions` if the full position book is needed. Use when: caller already knows which traders to follow and wants their cross-asset consensus at the latest hour. See also: `smartmoney_get_signal_overview_by_filter` (criteria-defined pool), `smartmoney_get_signal_trend_by_trader` (time-series), `smartmoney_get_traders_by_filter` / `smartmoney_search_trader` (discover authorIds).",
7928
+ isWrite: false,
7929
+ outputSchema: envelope({
7930
+ type: "array",
7931
+ description: "Per-instrument snapshot, one element per requested coin.",
7932
+ items: { type: "object", properties: SIGNAL_ITEM_PROPS }
7933
+ }),
7934
+ inputSchema: {
7935
+ type: "object",
7936
+ properties: {
7937
+ authorIds: {
7938
+ type: "array",
7939
+ items: { type: "string" },
7940
+ minItems: 1,
7941
+ description: 'Trader IDs to aggregate over, e.g. `["1001", "1002"]`. Required.'
7942
+ },
7943
+ topInstruments: {
7944
+ type: "integer",
7945
+ minimum: 1,
7946
+ maximum: 100,
7947
+ default: 20,
7948
+ description: "Top-N hottest instruments held by the given traders (sorted by `tradersWithPosition` DESC). Mutually exclusive with `instCcyList`; one of the two is required (default 20 if neither provided)."
7949
+ },
7950
+ instCcyList: {
7951
+ type: "array",
7952
+ items: { type: "string" },
7953
+ minItems: 1,
7954
+ description: 'Base currencies to aggregate, e.g. `["BTC", "ETH", "SOL"]`. Mutually exclusive with `topInstruments`. Scope: only USDT-margined and USDS-margined (linear) instruments \u2014 e.g. `BTC` covers `BTC-USDT-SWAP` + `BTC-USDS-SWAP`. Coin-margined contracts (`BTC-USD-SWAP`, `BTC-USD-DELIVERY`) are NOT included; the trader\'s positions in those instruments are silently dropped.'
7955
+ },
7956
+ sortBy: {
7081
7957
  type: "string",
7082
- description: "Trader author ID"
7958
+ enum: ["pnl", "pnlRatio"],
7959
+ default: "pnl",
7960
+ description: 'Required. Ranking key for the trader set. `pnl` = absolute USD profit; `pnlRatio` = percentage return. Default `"pnl"`.'
7083
7961
  },
7084
7962
  period: {
7085
7963
  type: "string",
7086
- description: "3|7|30|90 days, omit=all"
7087
- },
7964
+ enum: PERIOD_DAYS,
7965
+ default: "7",
7966
+ description: 'Required. Lookback window in days for capability metrics (`winRate.avgLongWinRate` / `avgShortWinRate`). One of `"3"` / `"7"` / `"30"` / `"90"`. Default `"7"`. Does NOT affect signal fields.'
7967
+ }
7968
+ },
7969
+ required: ["authorIds", "sortBy", "period"]
7970
+ },
7971
+ handler: async (rawArgs, context) => {
7972
+ const args = asRecord(rawArgs);
7973
+ assertEnum2(args, "sortBy", ["pnl", "pnlRatio"]);
7974
+ assertEnum2(args, "period", PERIOD_DAYS);
7975
+ const authorIds = readArrayAsCsv(args, "authorIds");
7976
+ if (!authorIds) {
7977
+ throw actionableError(
7978
+ '"authorIds" is required and must be a non-empty array.',
7979
+ 'Pass IDs from `smartmoney_get_traders_by_filter` as an array (e.g. ["1001", "1002"]).'
7980
+ );
7981
+ }
7982
+ const instCcyList = readArrayAsCsv(args, "instCcyList");
7983
+ const topInstrumentsRaw = readNumber(args, "topInstruments");
7984
+ if (instCcyList && topInstrumentsRaw !== void 0) {
7985
+ throw actionableError(
7986
+ '"topInstruments" and "instCcyList" are mutually exclusive.',
7987
+ "Pass exactly one \u2014 `topInstruments` for top-N hottest coins, or `instCcyList` for specific coins."
7988
+ );
7989
+ }
7990
+ const response = await context.client.privateGet(
7991
+ PATH_OVERVIEW,
7992
+ compactObject({
7993
+ authorIds,
7994
+ ...instCcyList ? { instCcyList } : { topInstruments: topInstrumentsRaw ?? 20 },
7995
+ // Apply schema defaults explicitly so behavior does not depend on backend defaults
7996
+ // (CLI bypasses MCP `required` validation, so handler is the only deterministic layer).
7997
+ sortBy: readString(args, "sortBy") ?? "pnl",
7998
+ period: readString(args, "period") ?? "7"
7999
+ }),
8000
+ publicRateLimit("smartmoney_get_signal_overview_by_trader", SMARTMONEY_RPS)
8001
+ );
8002
+ return normalizeResponse(response);
8003
+ }
8004
+ },
8005
+ /* ---------- S3. Signal trend by filter (single-asset, tier-filtered pool, asOfTime anchor) ---------- */
8006
+ {
8007
+ name: "smartmoney_get_signal_trend_by_filter",
8008
+ module: "smartmoney",
8009
+ description: "Time-series of single-asset smart-money signal across hourly/daily buckets, aggregated over a tier-filtered trader pool. Returns the latest `limit` buckets ending at `asOfTime` (defaults to current UTC hour). **Linear (USDT/USDS-margined) contracts only \u2014 coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions are excluded by upstream and silently omitted.** Use when: tracking how long/short conviction and capital evolve over time (smart money adding exposure or retreating). See also: `smartmoney_get_signal_overview_by_filter` (latest snapshot only), `smartmoney_get_signal_trend_by_trader` (restrict to specific traders). Note: `asOfTime` is 10-digit `yyyyMMddHH` UTC, different from leaderboard tools' 12-digit UTC+8 `updateTime` \u2014 do not cross-pass.",
8010
+ isWrite: false,
8011
+ outputSchema: envelope({
8012
+ type: "array",
8013
+ description: "Time-bucket series for the requested instrument, sorted by time DESC (newest first).",
8014
+ items: { type: "object", properties: SIGNAL_HISTORY_ITEM_PROPS }
8015
+ }),
8016
+ inputSchema: {
8017
+ type: "object",
8018
+ properties: {
7088
8019
  instCcy: {
7089
8020
  type: "string",
7090
- description: "Currency filter e.g. BTC"
8021
+ description: 'Base currency to scope the time-series, e.g. "BTC". Required. Scope: USDT-margined and USDS-margined (linear) instruments only \u2014 coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions are NOT included.'
7091
8022
  },
7092
- tradeLimit: {
8023
+ asOfTime: {
7093
8024
  type: "string",
7094
- description: "Max trades 1-100"
8025
+ description: 'Anchor snapshot time \u2014 10-digit `yyyyMMddHH` UTC as a string, e.g. `"2026050100"`. Returns the latest `limit` buckets ending at this anchor. Omit to use the current UTC hour.'
8026
+ },
8027
+ granularity: {
8028
+ type: "string",
8029
+ enum: ["1h", "1d"],
8030
+ default: "1h",
8031
+ description: "Time-bucket size. `1h` = hourly snapshots (intraday/short-term trend), `1d` = daily snapshots (multi-day trend)."
8032
+ },
8033
+ limit: {
8034
+ type: "integer",
8035
+ minimum: 1,
8036
+ maximum: 500,
8037
+ default: 24,
8038
+ description: "Number of buckets to return (newest first), ending at `asOfTime`. Default 24, max 500."
8039
+ },
8040
+ ...SIGNAL_POOL_FILTER_PROPS,
8041
+ lmtNum: {
8042
+ type: "integer",
8043
+ minimum: 1,
8044
+ maximum: 2e3,
8045
+ default: 100,
8046
+ description: "Top-N traders to pull into the aggregation pool, ranked by `sortBy` (DESC). Default 100, max 2000."
7095
8047
  }
7096
8048
  },
7097
- required: ["authorId"]
8049
+ required: ["instCcy", "granularity", "sortBy", "period"]
7098
8050
  },
7099
8051
  handler: async (rawArgs, context) => {
7100
8052
  const args = asRecord(rawArgs);
7101
- const authorId = requireString(args, "authorId");
7102
- const period = readString(args, "period");
8053
+ assertEnum2(args, "granularity", ["1h", "1d"]);
7103
8054
  const instCcy = readString(args, "instCcy");
7104
- const tradeLimit = readString(args, "tradeLimit");
7105
- const [profileRes, positionsRes, tradesRes] = await Promise.all([
7106
- context.client.privateGet(
7107
- PATH_LEADERBOARD,
7108
- compactObject({ authorIds: authorId, period }),
7109
- publicRateLimit("smartmoney_get_traders", 5)
7110
- ),
7111
- context.client.privateGet(
7112
- PATH_POSITION_CURRENT,
7113
- compactObject({ authorId, instCcy }),
7114
- publicRateLimit("smartmoney_trader_positions", 5)
7115
- ),
7116
- context.client.privateGet(
7117
- PATH_TRADE_RECORDS,
7118
- compactObject({ authorId, instCcy, limit: tradeLimit }),
7119
- publicRateLimit("smartmoney_trade_records", 5)
7120
- )
7121
- ]);
7122
- const profileNorm = normalizeResponse(profileRes);
7123
- const positionsNorm = normalizeResponse(positionsRes);
7124
- const tradesNorm = normalizeResponse(tradesRes);
7125
- return {
7126
- endpoint: "smartmoney_get_trader_detail (composite)",
7127
- requestTime: (/* @__PURE__ */ new Date()).toISOString(),
7128
- data: {
7129
- profile: extractLeaderboardData(profileNorm.data),
7130
- positions: positionsNorm.data,
7131
- trades: tradesNorm.data
8055
+ if (!instCcy) {
8056
+ throw actionableError(
8057
+ '"instCcy" is required.',
8058
+ 'Pass a base currency, e.g. "BTC".'
8059
+ );
8060
+ }
8061
+ const response = await context.client.privateGet(
8062
+ PATH_SIGNAL_HISTORY,
8063
+ compactObject({
8064
+ instCcy,
8065
+ asOfTime: readString(args, "asOfTime"),
8066
+ // Apply schema default explicitly (CLI bypasses MCP `required` validation).
8067
+ granularity: readString(args, "granularity") ?? "1h",
8068
+ limit: readNumber(args, "limit"),
8069
+ ...readSignalPoolFilters(args),
8070
+ lmtNum: readNumber(args, "lmtNum")
8071
+ }),
8072
+ publicRateLimit("smartmoney_get_signal_trend_by_filter", SMARTMONEY_RPS)
8073
+ );
8074
+ return normalizeResponse(response);
8075
+ }
8076
+ },
8077
+ /* ---------- S4. Signal trend by trader (single-asset, authorIds-restricted) ---------- */
8078
+ {
8079
+ name: "smartmoney_get_signal_trend_by_trader",
8080
+ module: "smartmoney",
8081
+ description: "Time-series of single-asset smart-money signal aggregated over a hand-picked set of traders (`authorIds`). Returns the latest `limit` buckets ending at `asOfTime` (defaults to current UTC hour). Capability tier filters (pnlTier / winRateTier / etc.) not exposed \u2014 backend uses defaults for direct-lookup scenarios. **Linear (USDT/USDS-margined) contracts only \u2014 a trader's coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions on the requested base ccy are silently excluded from each bucket.** Use `smartmoney_get_trader_positions` to inspect the full position book. Use when: tracking how a specific group of traders has evolved their long/short consensus over time on one coin. See also: `smartmoney_get_signal_trend_by_filter` (criteria-defined pool), `smartmoney_get_signal_overview_by_trader` (latest snapshot only), `smartmoney_get_traders_by_filter` / `smartmoney_search_trader` (discover authorIds). Note: `asOfTime` is 10-digit `yyyyMMddHH` UTC, different from leaderboard tools' 12-digit UTC+8 `updateTime` \u2014 do not cross-pass.",
8082
+ isWrite: false,
8083
+ outputSchema: envelope({
8084
+ type: "array",
8085
+ description: "Time-bucket series for the requested instrument, sorted by time DESC (newest first).",
8086
+ items: { type: "object", properties: SIGNAL_HISTORY_ITEM_PROPS }
8087
+ }),
8088
+ inputSchema: {
8089
+ type: "object",
8090
+ properties: {
8091
+ authorIds: {
8092
+ type: "array",
8093
+ items: { type: "string" },
8094
+ minItems: 1,
8095
+ description: 'Trader IDs to aggregate over, e.g. `["1001", "1002"]`. Required.'
8096
+ },
8097
+ instCcy: {
8098
+ type: "string",
8099
+ description: 'Base currency to scope the time-series, e.g. "BTC". Required. Scope: USDT-margined and USDS-margined (linear) instruments only \u2014 coin-margined (`-USD-SWAP` / `-USD-DELIVERY`) positions held by the trader set are NOT included.'
8100
+ },
8101
+ asOfTime: {
8102
+ type: "string",
8103
+ description: 'Anchor snapshot time \u2014 10-digit `yyyyMMddHH` UTC as a string, e.g. `"2026050100"`. Returns the latest `limit` buckets ending at this anchor. Omit to use the current UTC hour.'
8104
+ },
8105
+ granularity: {
8106
+ type: "string",
8107
+ enum: ["1h", "1d"],
8108
+ default: "1h",
8109
+ description: "Time-bucket size. `1h` = hourly snapshots (intraday/short-term trend), `1d` = daily snapshots (multi-day trend)."
8110
+ },
8111
+ limit: {
8112
+ type: "integer",
8113
+ minimum: 1,
8114
+ maximum: 500,
8115
+ default: 24,
8116
+ description: "Number of buckets to return (newest first), ending at `asOfTime`. Default 24, max 500."
8117
+ },
8118
+ sortBy: {
8119
+ type: "string",
8120
+ enum: ["pnl", "pnlRatio"],
8121
+ default: "pnl",
8122
+ description: 'Required. Ranking key for the trader set. `pnl` = absolute USD profit; `pnlRatio` = percentage return. Default `"pnl"`.'
8123
+ },
8124
+ period: {
8125
+ type: "string",
8126
+ enum: PERIOD_DAYS,
8127
+ default: "7",
8128
+ description: 'Required. Lookback window in days. One of `"3"` / `"7"` / `"30"` / `"90"`. Default `"7"`. Does NOT affect signal fields (which always use the latest snapshot).'
7132
8129
  }
7133
- };
8130
+ },
8131
+ required: ["authorIds", "instCcy", "granularity", "sortBy", "period"]
8132
+ },
8133
+ handler: async (rawArgs, context) => {
8134
+ const args = asRecord(rawArgs);
8135
+ assertEnum2(args, "granularity", ["1h", "1d"]);
8136
+ assertEnum2(args, "sortBy", ["pnl", "pnlRatio"]);
8137
+ assertEnum2(args, "period", PERIOD_DAYS);
8138
+ const authorIds = readArrayAsCsv(args, "authorIds");
8139
+ const instCcy = readString(args, "instCcy");
8140
+ if (!authorIds) {
8141
+ throw actionableError(
8142
+ '"authorIds" is required and must be a non-empty array.',
8143
+ 'Pass IDs from `smartmoney_get_traders_by_filter` as an array (e.g. ["1001", "1002"]).'
8144
+ );
8145
+ }
8146
+ if (!instCcy) {
8147
+ throw actionableError(
8148
+ '"instCcy" is required.',
8149
+ 'Pass a base currency, e.g. "BTC".'
8150
+ );
8151
+ }
8152
+ const response = await context.client.privateGet(
8153
+ PATH_SIGNAL_HISTORY,
8154
+ compactObject({
8155
+ authorIds,
8156
+ instCcy,
8157
+ asOfTime: readString(args, "asOfTime"),
8158
+ // Apply schema defaults explicitly so behavior does not depend on backend defaults
8159
+ // (CLI bypasses MCP `required` validation, so handler is the only deterministic layer).
8160
+ granularity: readString(args, "granularity") ?? "1h",
8161
+ limit: readNumber(args, "limit"),
8162
+ sortBy: readString(args, "sortBy") ?? "pnl",
8163
+ period: readString(args, "period") ?? "7"
8164
+ }),
8165
+ publicRateLimit("smartmoney_get_signal_trend_by_trader", SMARTMONEY_RPS)
8166
+ );
8167
+ return normalizeResponse(response);
7134
8168
  }
7135
8169
  }
7136
8170
  ];
@@ -7189,8 +8223,12 @@ function buildContractTradeTools(cfg) {
7189
8223
  clOrdId: { type: "string", description: "Client order ID (max 32 chars)" },
7190
8224
  tpTriggerPx: { type: "string", description: "TP trigger price" },
7191
8225
  tpOrdPx: { type: "string", description: "TP order price; -1=market" },
8226
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
8227
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
7192
8228
  slTriggerPx: { type: "string", description: "SL trigger price" },
7193
- slOrdPx: { type: "string", description: "SL order price; -1=market" }
8229
+ slOrdPx: { type: "string", description: "SL order price; -1=market" },
8230
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
8231
+ stpMode: STP_MODE_SCHEMA
7194
8232
  },
7195
8233
  required: ["instId", "tdMode", "side", "ordType", "sz"]
7196
8234
  },
@@ -7219,6 +8257,9 @@ function buildContractTradeTools(cfg) {
7219
8257
  px: readString(args, "px"),
7220
8258
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
7221
8259
  clOrdId: readString(args, "clOrdId"),
8260
+ stpMode: readString(args, "stpMode"),
8261
+ // Phase 3c CLI power-user flag (issue #182, CLI-only no MCP/skill exposure)
8262
+ pxAmendType: readString(args, "pxAmendType"),
7222
8263
  tag: context.config.sourceTag,
7223
8264
  attachAlgoOrds
7224
8265
  }),
@@ -9218,6 +10259,8 @@ function registerOptionTools() {
9218
10259
  type: "string",
9219
10260
  description: "TP order price; -1=market"
9220
10261
  },
10262
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
10263
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
9221
10264
  slTriggerPx: {
9222
10265
  type: "string",
9223
10266
  description: "SL trigger price"
@@ -9225,7 +10268,9 @@ function registerOptionTools() {
9225
10268
  slOrdPx: {
9226
10269
  type: "string",
9227
10270
  description: "SL order price; -1=market"
9228
- }
10271
+ },
10272
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
10273
+ stpMode: STP_MODE_SCHEMA
9229
10274
  },
9230
10275
  required: ["instId", "tdMode", "side", "ordType", "sz"]
9231
10276
  },
@@ -9253,6 +10298,7 @@ function registerOptionTools() {
9253
10298
  px: readString(args, "px"),
9254
10299
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
9255
10300
  clOrdId: readString(args, "clOrdId"),
10301
+ stpMode: readString(args, "stpMode"),
9256
10302
  tag: context.config.sourceTag,
9257
10303
  attachAlgoOrds
9258
10304
  }),
@@ -9621,6 +10667,8 @@ function registerSpotTradeTools() {
9621
10667
  type: "string",
9622
10668
  description: "TP order price, -1=market"
9623
10669
  },
10670
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
10671
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
9624
10672
  slTriggerPx: {
9625
10673
  type: "string",
9626
10674
  description: "SL trigger price"
@@ -9628,13 +10676,16 @@ function registerSpotTradeTools() {
9628
10676
  slOrdPx: {
9629
10677
  type: "string",
9630
10678
  description: "SL order price, -1=market"
9631
- }
10679
+ },
10680
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
10681
+ stpMode: STP_MODE_SCHEMA
9632
10682
  },
9633
10683
  required: ["instId", "tdMode", "side", "ordType", "sz"]
9634
10684
  },
9635
10685
  handler: async (rawArgs, context) => {
9636
10686
  const args = asRecord(rawArgs);
9637
10687
  const attachAlgoOrds = buildAttachAlgoOrds(args);
10688
+ const banAmend = args.banAmend;
9638
10689
  const response = await context.client.privatePost(
9639
10690
  "/api/v5/trade/order",
9640
10691
  compactObject({
@@ -9646,6 +10697,11 @@ function registerSpotTradeTools() {
9646
10697
  tgtCcy: readString(args, "tgtCcy"),
9647
10698
  px: readString(args, "px"),
9648
10699
  clOrdId: readString(args, "clOrdId"),
10700
+ stpMode: readString(args, "stpMode"),
10701
+ // Phase 3c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
10702
+ tradeQuoteCcy: readString(args, "tradeQuoteCcy"),
10703
+ banAmend: typeof banAmend === "boolean" ? String(banAmend) : void 0,
10704
+ pxAmendType: readString(args, "pxAmendType"),
9649
10705
  tag: context.config.sourceTag,
9650
10706
  attachAlgoOrds
9651
10707
  }),
@@ -9810,7 +10866,7 @@ function registerSpotTradeTools() {
9810
10866
  {
9811
10867
  name: "spot_place_algo_order",
9812
10868
  module: "spot",
9813
- description: "Place a spot algo order: TP/SL (conditional/oco) or trailing stop (move_order_stop). [CAUTION] Executes real trades.",
10869
+ description: "Place a spot algo order. [CAUTION] Executes real trades. conditional: single TP/SL. oco: TP+SL pair. move_order_stop: trailing stop. trigger: pending order at triggerPx. chase: follow best bid/ask. iceberg: split large order into child orders. twap: time-weighted split.",
9814
10870
  isWrite: true,
9815
10871
  inputSchema: {
9816
10872
  type: "object",
@@ -9830,8 +10886,8 @@ function registerSpotTradeTools() {
9830
10886
  },
9831
10887
  ordType: {
9832
10888
  type: "string",
9833
- enum: ["conditional", "oco", "move_order_stop"],
9834
- description: "conditional=single TP/SL, oco=TP+SL pair, move_order_stop=trailing stop"
10889
+ enum: ["conditional", "oco", "move_order_stop", "trigger", "chase", "iceberg", "twap"],
10890
+ description: "conditional=single TP/SL, oco=TP+SL pair, move_order_stop=trailing stop, trigger=pending order, chase=follow best bid/ask, iceberg=split order, twap=time-weighted split"
9835
10891
  },
9836
10892
  sz: {
9837
10893
  type: "string",
@@ -9845,6 +10901,8 @@ function registerSpotTradeTools() {
9845
10901
  type: "string",
9846
10902
  description: "TP order price, -1=market (conditional/oco only)"
9847
10903
  },
10904
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
10905
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
9848
10906
  slTriggerPx: {
9849
10907
  type: "string",
9850
10908
  description: "SL trigger price (conditional/oco only)"
@@ -9853,6 +10911,8 @@ function registerSpotTradeTools() {
9853
10911
  type: "string",
9854
10912
  description: "SL order price, -1=market (conditional/oco only)"
9855
10913
  },
10914
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
10915
+ stpMode: STP_MODE_SCHEMA,
9856
10916
  tgtCcy: {
9857
10917
  type: "string",
9858
10918
  enum: ["base_ccy", "quote_ccy"],
@@ -9869,30 +10929,50 @@ function registerSpotTradeTools() {
9869
10929
  activePx: {
9870
10930
  type: "string",
9871
10931
  description: "Activation price, trailing starts when market hits this (move_order_stop only)"
9872
- }
10932
+ },
10933
+ ...TRIGGER_FLAGS_SCHEMA,
10934
+ ...CHASE_FLAGS_SCHEMA,
10935
+ ...ICEBERG_TWAP_FLAGS_SCHEMA
9873
10936
  },
9874
10937
  required: ["instId", "side", "ordType", "sz"]
9875
10938
  },
9876
10939
  handler: async (rawArgs, context) => {
9877
10940
  const args = asRecord(rawArgs);
10941
+ const ordType = requireString(args, "ordType");
10942
+ const base = compactObject({
10943
+ instId: requireString(args, "instId"),
10944
+ tdMode: readString(args, "tdMode") ?? "cash",
10945
+ side: requireString(args, "side"),
10946
+ ordType,
10947
+ sz: requireString(args, "sz"),
10948
+ tgtCcy: readString(args, "tgtCcy"),
10949
+ stpMode: readString(args, "stpMode"),
10950
+ // Phase 3a+c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
10951
+ pxAmendType: readString(args, "pxAmendType"),
10952
+ tag: context.config.sourceTag
10953
+ });
10954
+ switch (ordType) {
10955
+ case "trigger":
10956
+ Object.assign(base, buildTriggerOrdTypeBody(args));
10957
+ break;
10958
+ case "chase":
10959
+ Object.assign(base, buildChaseOrdTypeBody(args));
10960
+ break;
10961
+ case "iceberg":
10962
+ case "twap":
10963
+ Object.assign(base, buildIcebergTwapOrdTypeBody(args));
10964
+ break;
10965
+ default:
10966
+ Object.assign(base, compactObject({
10967
+ ...buildAlgoConditionalCommonFields(args),
10968
+ callbackRatio: readString(args, "callbackRatio"),
10969
+ callbackSpread: readString(args, "callbackSpread")
10970
+ }));
10971
+ break;
10972
+ }
9878
10973
  const response = await context.client.privatePost(
9879
10974
  "/api/v5/trade/order-algo",
9880
- compactObject({
9881
- instId: requireString(args, "instId"),
9882
- tdMode: readString(args, "tdMode") ?? "cash",
9883
- side: requireString(args, "side"),
9884
- ordType: requireString(args, "ordType"),
9885
- sz: requireString(args, "sz"),
9886
- tgtCcy: readString(args, "tgtCcy"),
9887
- tpTriggerPx: readString(args, "tpTriggerPx"),
9888
- tpOrdPx: readString(args, "tpOrdPx"),
9889
- slTriggerPx: readString(args, "slTriggerPx"),
9890
- slOrdPx: readString(args, "slOrdPx"),
9891
- callbackRatio: readString(args, "callbackRatio"),
9892
- callbackSpread: readString(args, "callbackSpread"),
9893
- activePx: readString(args, "activePx"),
9894
- tag: context.config.sourceTag
9895
- }),
10975
+ base,
9896
10976
  privateRateLimit("spot_place_algo_order", 20)
9897
10977
  );
9898
10978
  return normalizeResponse(response);
@@ -10931,6 +12011,78 @@ function runSetup(options) {
10931
12011
  `);
10932
12012
  }
10933
12013
  }
12014
+ function isRedirect(statusCode) {
12015
+ return statusCode !== void 0 && statusCode >= 300 && statusCode < 400;
12016
+ }
12017
+ function validateRedirect(res, requestUrl, redirectCount, maxRedirects) {
12018
+ if (redirectCount > maxRedirects) {
12019
+ throw new Error(`Too many redirects (${maxRedirects})`);
12020
+ }
12021
+ const location = res.headers.location;
12022
+ if (requestUrl.startsWith("https") && !location.startsWith("https")) {
12023
+ throw new Error("Refused HTTPS \u2192 HTTP redirect downgrade");
12024
+ }
12025
+ return location;
12026
+ }
12027
+ function fetchResponse(url, timeoutMs) {
12028
+ return new Promise((resolve3, reject) => {
12029
+ let redirects = 0;
12030
+ const maxRedirects = 5;
12031
+ function doRequest(requestUrl) {
12032
+ const reqFn = requestUrl.startsWith("https") ? httpsGet : httpGet;
12033
+ const req = reqFn(requestUrl, { timeout: timeoutMs }, (res) => {
12034
+ if (isRedirect(res.statusCode) && res.headers.location) {
12035
+ redirects++;
12036
+ try {
12037
+ const location = validateRedirect(res, requestUrl, redirects, maxRedirects);
12038
+ res.resume();
12039
+ doRequest(location);
12040
+ } catch (err) {
12041
+ reject(err);
12042
+ }
12043
+ return;
12044
+ }
12045
+ if (res.statusCode !== 200) {
12046
+ reject(new Error(`HTTP ${res.statusCode ?? "unknown"}`));
12047
+ return;
12048
+ }
12049
+ resolve3(res);
12050
+ });
12051
+ req.on("error", reject);
12052
+ req.on("timeout", () => {
12053
+ req.destroy();
12054
+ reject(new Error("Download timed out"));
12055
+ });
12056
+ }
12057
+ doRequest(url);
12058
+ });
12059
+ }
12060
+ function download(url, destPath, timeoutMs) {
12061
+ return fetchResponse(url, timeoutMs).then(
12062
+ (res) => new Promise((resolve3, reject) => {
12063
+ const file = createWriteStream2(destPath);
12064
+ res.pipe(file);
12065
+ file.on("finish", () => file.close(() => resolve3()));
12066
+ file.on("error", (err) => {
12067
+ try {
12068
+ unlinkSync3(destPath);
12069
+ } catch {
12070
+ }
12071
+ reject(err);
12072
+ });
12073
+ })
12074
+ );
12075
+ }
12076
+ function downloadText(url, timeoutMs) {
12077
+ return fetchResponse(url, timeoutMs).then(
12078
+ (res) => new Promise((resolve3, reject) => {
12079
+ const chunks = [];
12080
+ res.on("data", (chunk) => chunks.push(chunk));
12081
+ res.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
12082
+ res.on("error", reject);
12083
+ })
12084
+ );
12085
+ }
10934
12086
  var CDN_SOURCES = [
10935
12087
  { host: "static.jingyunyilian.com", protocol: "https" },
10936
12088
  { host: "static.okx.com", protocol: "https" },
@@ -10941,7 +12093,7 @@ var DOWNLOAD_TIMEOUT_MS = 3e4;
10941
12093
  var PLATFORM_MAP = {
10942
12094
  "darwin-arm64": "darwin-arm64",
10943
12095
  "darwin-x64": "darwin-x64",
10944
- "linux-arm64": "linux-x64",
12096
+ "linux-arm64": "linux-arm64",
10945
12097
  "linux-x64": "linux-x64",
10946
12098
  "win32-arm64": "win32-arm64",
10947
12099
  "win32-x64": "win32-x64"
@@ -11044,7 +12196,7 @@ async function downloadAndVerify(host, protocol, binaryPath, tmpPath, checksum,
11044
12196
  function atomicReplace(tmpPath, resolvedDest) {
11045
12197
  if (platform() === "win32") {
11046
12198
  try {
11047
- unlinkSync3(resolvedDest);
12199
+ unlinkSync4(resolvedDest);
11048
12200
  } catch {
11049
12201
  }
11050
12202
  }
@@ -11100,7 +12252,7 @@ async function installPilotBinary(destPath, sources = CDN_SOURCES, onProgress) {
11100
12252
  return { status: "installed", source: host };
11101
12253
  } catch (err) {
11102
12254
  try {
11103
- unlinkSync3(tmpPath);
12255
+ unlinkSync4(tmpPath);
11104
12256
  } catch {
11105
12257
  }
11106
12258
  const msg = err instanceof Error ? err.message : String(err);
@@ -11114,7 +12266,7 @@ ${errors.join("\n")}` };
11114
12266
  function removePilotBinary(binaryPath) {
11115
12267
  const resolvedPath = binaryPath ?? getPilotBinaryPath();
11116
12268
  try {
11117
- unlinkSync3(resolvedPath);
12269
+ unlinkSync4(resolvedPath);
11118
12270
  return { status: "removed" };
11119
12271
  } catch (err) {
11120
12272
  if (err.code === "ENOENT") {
@@ -11124,78 +12276,6 @@ function removePilotBinary(binaryPath) {
11124
12276
  throw new Error(`Failed to remove ${resolvedPath}: ${msg}`);
11125
12277
  }
11126
12278
  }
11127
- function isRedirect(statusCode) {
11128
- return statusCode !== void 0 && statusCode >= 300 && statusCode < 400;
11129
- }
11130
- function validateRedirect(res, requestUrl, redirectCount, maxRedirects) {
11131
- if (redirectCount > maxRedirects) {
11132
- throw new Error(`Too many redirects (${maxRedirects})`);
11133
- }
11134
- const location = res.headers.location;
11135
- if (requestUrl.startsWith("https") && !location.startsWith("https")) {
11136
- throw new Error("Refused HTTPS \u2192 HTTP redirect downgrade");
11137
- }
11138
- return location;
11139
- }
11140
- function fetchResponse(url, timeoutMs) {
11141
- return new Promise((resolve3, reject) => {
11142
- let redirects = 0;
11143
- const maxRedirects = 5;
11144
- function doRequest(requestUrl) {
11145
- const reqFn = requestUrl.startsWith("https") ? httpsGet : httpGet;
11146
- const req = reqFn(requestUrl, { timeout: timeoutMs }, (res) => {
11147
- if (isRedirect(res.statusCode) && res.headers.location) {
11148
- redirects++;
11149
- try {
11150
- const location = validateRedirect(res, requestUrl, redirects, maxRedirects);
11151
- res.resume();
11152
- doRequest(location);
11153
- } catch (err) {
11154
- reject(err);
11155
- }
11156
- return;
11157
- }
11158
- if (res.statusCode !== 200) {
11159
- reject(new Error(`HTTP ${res.statusCode ?? "unknown"}`));
11160
- return;
11161
- }
11162
- resolve3(res);
11163
- });
11164
- req.on("error", reject);
11165
- req.on("timeout", () => {
11166
- req.destroy();
11167
- reject(new Error("Download timed out"));
11168
- });
11169
- }
11170
- doRequest(url);
11171
- });
11172
- }
11173
- function download(url, destPath, timeoutMs) {
11174
- return fetchResponse(url, timeoutMs).then(
11175
- (res) => new Promise((resolve3, reject) => {
11176
- const file = createWriteStream2(destPath);
11177
- res.pipe(file);
11178
- file.on("finish", () => file.close(() => resolve3()));
11179
- file.on("error", (err) => {
11180
- try {
11181
- unlinkSync3(destPath);
11182
- } catch {
11183
- }
11184
- reject(err);
11185
- });
11186
- })
11187
- );
11188
- }
11189
- function downloadText(url, timeoutMs) {
11190
- return fetchResponse(url, timeoutMs).then(
11191
- (res) => new Promise((resolve3, reject) => {
11192
- const chunks = [];
11193
- res.on("data", (chunk) => chunks.push(chunk));
11194
- res.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
11195
- res.on("error", reject);
11196
- })
11197
- );
11198
- }
11199
12279
  var AUTH_CDN_PATH_PREFIX = "/upgradeapp/tools/oauth";
11200
12280
  function getAuthBinaryName() {
11201
12281
  return platform2() === "win32" ? "okx-auth.exe" : "okx-auth";
@@ -11219,7 +12299,7 @@ async function fetchAuthCdnChecksum(sources = CDN_SOURCES, timeoutMs = DOWNLOAD_
11219
12299
  for (const { host, protocol } of sources) {
11220
12300
  try {
11221
12301
  const url = `${protocol}://${host}${checksumPath}`;
11222
- const raw = await downloadText2(url, timeoutMs);
12302
+ const raw = await downloadText(url, timeoutMs);
11223
12303
  const data = JSON.parse(raw);
11224
12304
  if (typeof data.sha256 !== "string" || typeof data.size !== "number" || typeof data.target !== "string") {
11225
12305
  continue;
@@ -11233,7 +12313,7 @@ async function fetchAuthCdnChecksum(sources = CDN_SOURCES, timeoutMs = DOWNLOAD_
11233
12313
  async function fetchAndValidateChecksum2(host, protocol, checksumPath, platformDir, timeoutMs, onProgress) {
11234
12314
  const checksumUrl = `${protocol}://${host}${checksumPath}`;
11235
12315
  onProgress?.(`Fetching checksum from ${host}...`);
11236
- const raw = await downloadText2(checksumUrl, timeoutMs);
12316
+ const raw = await downloadText(checksumUrl, timeoutMs);
11237
12317
  const checksum = JSON.parse(raw);
11238
12318
  if (typeof checksum.sha256 !== "string" || typeof checksum.size !== "number" || typeof checksum.target !== "string") {
11239
12319
  throw new Error("Invalid checksum.json: missing sha256, size, or target");
@@ -11246,7 +12326,7 @@ async function fetchAndValidateChecksum2(host, protocol, checksumPath, platformD
11246
12326
  async function downloadAndVerify2(host, protocol, binaryPath, tmpPath, checksum, timeoutMs, onProgress) {
11247
12327
  const binaryUrl = `${protocol}://${host}${binaryPath}`;
11248
12328
  onProgress?.(`Downloading binary from ${host}...`);
11249
- await download2(binaryUrl, tmpPath, timeoutMs);
12329
+ await download(binaryUrl, tmpPath, timeoutMs);
11250
12330
  const actual = hashFile(tmpPath);
11251
12331
  if (actual.size !== checksum.size) {
11252
12332
  throw new Error(`Size mismatch: expected ${checksum.size}, got ${actual.size}`);
@@ -11258,7 +12338,7 @@ async function downloadAndVerify2(host, protocol, binaryPath, tmpPath, checksum,
11258
12338
  function atomicReplace2(tmpPath, resolvedDest) {
11259
12339
  if (platform2() === "win32") {
11260
12340
  try {
11261
- unlinkSync4(resolvedDest);
12341
+ unlinkSync5(resolvedDest);
11262
12342
  } catch {
11263
12343
  }
11264
12344
  }
@@ -11314,7 +12394,7 @@ async function installAuthBinary(destPath, sources = CDN_SOURCES, onProgress) {
11314
12394
  return { status: "installed", source: host };
11315
12395
  } catch (err) {
11316
12396
  try {
11317
- unlinkSync4(tmpPath);
12397
+ unlinkSync5(tmpPath);
11318
12398
  } catch {
11319
12399
  }
11320
12400
  const msg = err instanceof Error ? err.message : String(err);
@@ -11328,7 +12408,7 @@ ${errors.join("\n")}` };
11328
12408
  function removeAuthBinary(binaryPath) {
11329
12409
  const resolvedPath = binaryPath ?? getAuthBinaryPath();
11330
12410
  try {
11331
- unlinkSync4(resolvedPath);
12411
+ unlinkSync5(resolvedPath);
11332
12412
  return { status: "removed" };
11333
12413
  } catch (err) {
11334
12414
  if (err.code === "ENOENT") {
@@ -11338,78 +12418,6 @@ function removeAuthBinary(binaryPath) {
11338
12418
  throw new Error(`Failed to remove ${resolvedPath}: ${msg}`);
11339
12419
  }
11340
12420
  }
11341
- function isRedirect2(statusCode) {
11342
- return statusCode !== void 0 && statusCode >= 300 && statusCode < 400;
11343
- }
11344
- function validateRedirect2(res, requestUrl, redirectCount, maxRedirects) {
11345
- if (redirectCount > maxRedirects) {
11346
- throw new Error(`Too many redirects (${maxRedirects})`);
11347
- }
11348
- const location = res.headers.location;
11349
- if (requestUrl.startsWith("https") && !location.startsWith("https")) {
11350
- throw new Error("Refused HTTPS \u2192 HTTP redirect downgrade");
11351
- }
11352
- return location;
11353
- }
11354
- function fetchResponse2(url, timeoutMs) {
11355
- return new Promise((resolve3, reject) => {
11356
- let redirects = 0;
11357
- const maxRedirects = 5;
11358
- function doRequest(requestUrl) {
11359
- const reqFn = requestUrl.startsWith("https") ? httpsGet2 : httpGet2;
11360
- const req = reqFn(requestUrl, { timeout: timeoutMs }, (res) => {
11361
- if (isRedirect2(res.statusCode) && res.headers.location) {
11362
- redirects++;
11363
- try {
11364
- const location = validateRedirect2(res, requestUrl, redirects, maxRedirects);
11365
- res.resume();
11366
- doRequest(location);
11367
- } catch (err) {
11368
- reject(err);
11369
- }
11370
- return;
11371
- }
11372
- if (res.statusCode !== 200) {
11373
- reject(new Error(`HTTP ${res.statusCode ?? "unknown"}`));
11374
- return;
11375
- }
11376
- resolve3(res);
11377
- });
11378
- req.on("error", reject);
11379
- req.on("timeout", () => {
11380
- req.destroy();
11381
- reject(new Error("Download timed out"));
11382
- });
11383
- }
11384
- doRequest(url);
11385
- });
11386
- }
11387
- function download2(url, destPath, timeoutMs) {
11388
- return fetchResponse2(url, timeoutMs).then(
11389
- (res) => new Promise((resolve3, reject) => {
11390
- const file = createWriteStream3(destPath);
11391
- res.pipe(file);
11392
- file.on("finish", () => file.close(() => resolve3()));
11393
- file.on("error", (err) => {
11394
- try {
11395
- unlinkSync4(destPath);
11396
- } catch {
11397
- }
11398
- reject(err);
11399
- });
11400
- })
11401
- );
11402
- }
11403
- function downloadText2(url, timeoutMs) {
11404
- return fetchResponse2(url, timeoutMs).then(
11405
- (res) => new Promise((resolve3, reject) => {
11406
- const chunks = [];
11407
- res.on("data", (chunk) => chunks.push(chunk));
11408
- res.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
11409
- res.on("error", reject);
11410
- })
11411
- );
11412
- }
11413
12421
  var CACHE_PATH = join12(homedir10(), ".okx", "auth-binary-check.json");
11414
12422
  var CHECK_INTERVAL_MS2 = 2 * 60 * 60 * 1e3;
11415
12423
  function readCache3() {
@@ -11458,7 +12466,7 @@ function updateAuthBinaryCache(sha256) {
11458
12466
  }
11459
12467
  function clearAuthBinaryCache() {
11460
12468
  try {
11461
- unlinkSync5(CACHE_PATH);
12469
+ unlinkSync6(CACHE_PATH);
11462
12470
  } catch {
11463
12471
  }
11464
12472
  }
@@ -12374,7 +13382,7 @@ async function cmdDiagnoseMcp(options = {}) {
12374
13382
 
12375
13383
  // src/commands/diagnose.ts
12376
13384
  var CLI_VERSION = readCliVersion();
12377
- var GIT_HASH = true ? "1bb94dcd" : "dev";
13385
+ var GIT_HASH = true ? "e8a0930a" : "dev";
12378
13386
  function maskKey2(key) {
12379
13387
  if (!key) return "(not set)";
12380
13388
  if (key.length <= 8) return "****";
@@ -13066,7 +14074,7 @@ var CLI_REGISTRY = {
13066
14074
  },
13067
14075
  place: {
13068
14076
  toolName: "spot_place_order",
13069
- usage: "okx spot place --instId <id> --side <buy|sell> --ordType <type> --sz <n> [--px <price>] [--tdMode <cash|cross|isolated>]\n [--tgtCcy <base_ccy|quote_ccy>] [--clOrdId <id>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--slTriggerPx <price>] [--slOrdPx <price|-1>]",
14077
+ usage: 'okx spot place --instId <id> --side <buy|sell> --ordType <type> --sz <n> [--px <price>] [--tdMode <cash|cross|isolated>]\n [--tgtCcy <base_ccy|quote_ccy>] [--clOrdId <id>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--tpOrdKind <condition|limit>] [--tpTriggerPxType <last|index|mark>]\n [--slTriggerPx <price>] [--slOrdPx <price|-1>] [--slTriggerPxType <last|index|mark>]\n [--stpMode <cancel_maker|cancel_taker|cancel_both>]\n power-user (CLI-only): [--tradeQuoteCcy <USDT|USDC|BTC>] [--banAmend] [--pxAmendType <0|1>]\n power-user (CLI-only): [--tpLevel "px:78000,sz:0.5,kind:limit"] [--tpLevel "px:81000,sz:0.5"] (repeatable; split multi-tier take-profit; mutually exclusive with --tpTriggerPx/--tpOrdPx)',
13070
14078
  description: "Place a new spot order (supports attached TP/SL)"
13071
14079
  },
13072
14080
  amend: {
@@ -13102,8 +14110,8 @@ var CLI_REGISTRY = {
13102
14110
  },
13103
14111
  place: {
13104
14112
  toolName: "spot_place_algo_order",
13105
- usage: "okx spot algo place --instId <id> --side <buy|sell> --sz <n> [--ordType <conditional|oco>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>]\n [--slTriggerPx <price>] [--slOrdPx <price|-1>] [--tdMode <cash|cross|isolated>]",
13106
- description: "Place a spot algo order (take-profit/stop-loss)"
14113
+ usage: 'okx spot algo place --instId <id> --side <buy|sell> --sz <n> [--ordType <conditional|oco|move_order_stop|trigger|chase|iceberg|twap>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--tpOrdKind <condition|limit>] [--tpTriggerPxType <last|index|mark>]\n [--slTriggerPx <price>] [--slOrdPx <price|-1>] [--slTriggerPxType <last|index|mark>]\n [--stpMode <cancel_maker|cancel_taker|cancel_both>] [--tdMode <cash|cross|isolated>]\n trigger: [--triggerPx <price>] [--orderPx <price|-1>] [--advanceOrdType <fok|ioc>] [--triggerPxType <last|index|mark>]\n chase: [--chaseType <distance|ratio>] [--chaseVal <n>] [--maxChaseType <distance|ratio>] [--maxChaseVal <n>]\n iceberg/twap: [--pxVar <n>|--pxSpread <n>] [--szLimit <n>] [--pxLimit <price>] [--timeInterval <secs>]\n power-user (CLI-only): [--tpTriggerRatio <ratio>] [--slTriggerRatio <ratio>] [--closeFraction <frac>] [--pxAmendType <0|1>]\n power-user (CLI-only): [--tpLevel "px:78000,sz:0.5,kind:limit"] [--tpLevel "px:81000,sz:0.5"] (repeatable; split multi-tier take-profit)',
14114
+ description: "Place a spot algo order (TP/SL, pending order, chase, iceberg, twap)"
13107
14115
  },
13108
14116
  trail: {
13109
14117
  toolName: "spot_place_algo_order",
@@ -13150,7 +14158,7 @@ var CLI_REGISTRY = {
13150
14158
  },
13151
14159
  place: {
13152
14160
  toolName: "swap_place_order",
13153
- usage: "okx swap place --instId <id> --side <buy|sell> --ordType <type> --sz <n> [--posSide <side>] [--px <price>]\n [--tdMode <cross|isolated>] [--tgtCcy <base_ccy|quote_ccy|margin>] [--reduceOnly] [--clOrdId <id>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--slTriggerPx <price>] [--slOrdPx <price|-1>]",
14161
+ usage: 'okx swap place --instId <id> --side <buy|sell> --ordType <type> --sz <n> [--posSide <side>] [--px <price>]\n [--tdMode <cross|isolated>] [--tgtCcy <base_ccy|quote_ccy|margin>] [--reduceOnly] [--clOrdId <id>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--tpOrdKind <condition|limit>] [--tpTriggerPxType <last|index|mark>]\n [--slTriggerPx <price>] [--slOrdPx <price|-1>] [--slTriggerPxType <last|index|mark>]\n [--stpMode <cancel_maker|cancel_taker|cancel_both>]\n power-user (CLI-only): [--pxAmendType <0|1>]\n power-user (CLI-only): [--tpLevel "px:78000,sz:0.5,kind:limit"] [--tpLevel "px:81000,sz:0.5"] (repeatable; split multi-tier take-profit; mutually exclusive with --tpTriggerPx/--tpOrdPx)',
13154
14162
  description: "Place a new perpetual swap order (supports attached TP/SL)"
13155
14163
  },
13156
14164
  cancel: {
@@ -13202,8 +14210,8 @@ var CLI_REGISTRY = {
13202
14210
  },
13203
14211
  place: {
13204
14212
  toolName: "swap_place_algo_order",
13205
- usage: "okx swap algo place --instId <id> --side <buy|sell> --sz <n> [--ordType <conditional|oco>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>]\n [--slTriggerPx <price>] [--slOrdPx <price|-1>]\n [--posSide <net|long|short>] [--tdMode <cross|isolated>] [--reduceOnly]",
13206
- description: "Place a swap algo order (take-profit/stop-loss)"
14213
+ usage: 'okx swap algo place --instId <id> --side <buy|sell> --sz <n> [--ordType <conditional|oco|move_order_stop|trigger|chase|iceberg|twap>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--tpOrdKind <condition|limit>] [--tpTriggerPxType <last|index|mark>]\n [--slTriggerPx <price>] [--slOrdPx <price|-1>] [--slTriggerPxType <last|index|mark>]\n [--stpMode <cancel_maker|cancel_taker|cancel_both>] [--cxlOnClosePos]\n [--posSide <net|long|short>] [--tdMode <cross|isolated>] [--reduceOnly]\n trigger: [--triggerPx <price>] [--orderPx <price|-1>] [--advanceOrdType <fok|ioc>] [--triggerPxType <last|index|mark>]\n chase: [--chaseType <distance|ratio>] [--chaseVal <n>] [--maxChaseType <distance|ratio>] [--maxChaseVal <n>]\n iceberg/twap: [--pxVar <n>|--pxSpread <n>] [--szLimit <n>] [--pxLimit <price>] [--timeInterval <secs>]\n power-user (CLI-only): [--tpTriggerRatio <ratio>] [--slTriggerRatio <ratio>] [--closeFraction <frac>] [--pxAmendType <0|1>]\n power-user (CLI-only): [--tpLevel "px:78000,sz:0.5,kind:limit"] [--tpLevel "px:81000,sz:0.5"] (repeatable; split multi-tier take-profit)',
14214
+ description: "Place a swap algo order (TP/SL, pending order, chase, iceberg, twap)"
13207
14215
  },
13208
14216
  amend: {
13209
14217
  toolName: "swap_amend_algo_order",
@@ -13240,7 +14248,7 @@ var CLI_REGISTRY = {
13240
14248
  },
13241
14249
  place: {
13242
14250
  toolName: "futures_place_order",
13243
- usage: "okx futures place --instId <id> --side <buy|sell> --ordType <type> --sz <n>\n [--tdMode <cross|isolated>] [--posSide <net|long|short>] [--px <price>] [--reduceOnly]\n [--tgtCcy <base_ccy|quote_ccy|margin>] [--clOrdId <id>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--slTriggerPx <price>] [--slOrdPx <price|-1>]",
14251
+ usage: 'okx futures place --instId <id> --side <buy|sell> --ordType <type> --sz <n>\n [--tdMode <cross|isolated>] [--posSide <net|long|short>] [--px <price>] [--reduceOnly]\n [--tgtCcy <base_ccy|quote_ccy|margin>] [--clOrdId <id>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--tpOrdKind <condition|limit>] [--tpTriggerPxType <last|index|mark>]\n [--slTriggerPx <price>] [--slOrdPx <price|-1>] [--slTriggerPxType <last|index|mark>]\n [--stpMode <cancel_maker|cancel_taker|cancel_both>]\n power-user (CLI-only): [--pxAmendType <0|1>]\n power-user (CLI-only): [--tpLevel "px:78000,sz:0.5,kind:limit"] [--tpLevel "px:81000,sz:0.5"] (repeatable; split multi-tier take-profit; mutually exclusive with --tpTriggerPx/--tpOrdPx)',
13244
14252
  description: "Place a new futures order (supports attached TP/SL)"
13245
14253
  },
13246
14254
  cancel: {
@@ -13296,7 +14304,7 @@ var CLI_REGISTRY = {
13296
14304
  },
13297
14305
  place: {
13298
14306
  toolName: "futures_place_algo_order",
13299
- usage: "okx futures algo place --instId <id> --side <buy|sell> --sz <n> [--ordType <conditional|oco>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>]\n [--slTriggerPx <price>] [--slOrdPx <price|-1>]\n [--posSide <net|long|short>] [--tdMode <cross|isolated>] [--reduceOnly]",
14307
+ usage: 'okx futures algo place --instId <id> --side <buy|sell> --sz <n> [--ordType <conditional|oco|move_order_stop|trigger|chase|iceberg|twap>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--tpOrdKind <condition|limit>] [--tpTriggerPxType <last|index|mark>]\n [--slTriggerPx <price>] [--slOrdPx <price|-1>] [--slTriggerPxType <last|index|mark>]\n [--stpMode <cancel_maker|cancel_taker|cancel_both>] [--cxlOnClosePos]\n [--posSide <net|long|short>] [--tdMode <cross|isolated>] [--reduceOnly]\n trigger: [--triggerPx <price>] [--orderPx <price|-1>] [--advanceOrdType <fok|ioc>] [--triggerPxType <last|index|mark>]\n chase: [--chaseType <distance|ratio>] [--chaseVal <n>] [--maxChaseType <distance|ratio>] [--maxChaseVal <n>]\n iceberg/twap: [--pxVar <n>|--pxSpread <n>] [--szLimit <n>] [--pxLimit <price>] [--timeInterval <secs>]\n power-user (CLI-only): [--tpTriggerRatio <ratio>] [--slTriggerRatio <ratio>] [--closeFraction <frac>] [--pxAmendType <0|1>]\n power-user (CLI-only): [--tpLevel "px:78000,sz:0.5,kind:limit"] [--tpLevel "px:81000,sz:0.5"] (repeatable; split multi-tier take-profit)',
13300
14308
  description: "Place a futures algo order (take-profit/stop-loss)"
13301
14309
  },
13302
14310
  amend: {
@@ -13349,7 +14357,7 @@ var CLI_REGISTRY = {
13349
14357
  },
13350
14358
  place: {
13351
14359
  toolName: "option_place_order",
13352
- usage: "okx option place --instId <id> --tdMode <cash|cross|isolated> --side <buy|sell> --ordType <type> --sz <n>\n [--px <price>] [--tgtCcy <base_ccy|quote_ccy|margin>] [--reduceOnly] [--clOrdId <id>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--slTriggerPx <price>] [--slOrdPx <price|-1>]",
14360
+ usage: "okx option place --instId <id> --tdMode <cash|cross|isolated> --side <buy|sell> --ordType <type> --sz <n>\n [--px <price>] [--tgtCcy <base_ccy|quote_ccy|margin>] [--reduceOnly] [--clOrdId <id>]\n [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--tpOrdKind <condition|limit>] [--tpTriggerPxType <last|index|mark>]\n [--slTriggerPx <price>] [--slOrdPx <price|-1>] [--slTriggerPxType <last|index|mark>]\n [--stpMode <cancel_maker|cancel_taker|cancel_both>]",
13353
14361
  description: "Place a new option order"
13354
14362
  },
13355
14363
  cancel: {
@@ -13379,7 +14387,7 @@ var CLI_REGISTRY = {
13379
14387
  },
13380
14388
  place: {
13381
14389
  toolName: "option_place_algo_order",
13382
- usage: "okx option algo place --instId <id> --tdMode <cash|cross|isolated> --side <buy|sell> --sz <n>\n [--ordType <conditional|oco>] [--tpTriggerPx <price>] [--tpOrdPx <price|-1>]\n [--slTriggerPx <price>] [--slOrdPx <price|-1>] [--reduceOnly] [--clOrdId <id>]",
14390
+ usage: "okx option algo place --instId <id> --tdMode <cash|cross|isolated> --side <buy|sell> --sz <n>\n [--ordType <conditional|oco>] [--tpTriggerPx <price>] [--tpOrdPx <price|-1>] [--tpTriggerPxType <last|index|mark>]\n [--slTriggerPx <price>] [--slOrdPx <price|-1>] [--slTriggerPxType <last|index|mark>]\n [--reduceOnly] [--clOrdId <id>]",
13383
14391
  description: "Place an option algo order (take-profit/stop-loss)"
13384
14392
  },
13385
14393
  amend: {
@@ -13678,32 +14686,57 @@ var CLI_REGISTRY = {
13678
14686
  },
13679
14687
  // ── smartmoney ─────────────────────────────────────────────────────────────
13680
14688
  smartmoney: {
13681
- description: "Smart money signals \u2014 trader leaderboard, consensus signals, and position analysis",
14689
+ description: "Smart money analytics \u2014 trader leaderboard, consensus signals, and position analysis",
13682
14690
  commands: {
13683
- overview: {
13684
- toolName: "smartmoney_get_overview",
13685
- usage: "okx smartmoney overview [--ts <ms> | --dataVersion <ver>] [--instType <SWAP|SPOT>] [--sortType <pnl|pnlRatio>] [--period <3|7|30|90>] [--pnl <tier>] [--winRatio <tier>] [--maxRetreat <tier>] [--asset <tier>] [--lmtNum <n>] [--instCcyList <ccys>] [--instCcy <ccy>] [--topInstruments <n>] [--json]",
13686
- description: "Multi-currency smart money overview ranked by tradersWithPosition DESC (requires --ts or --dataVersion; --ts takes precedence)"
13687
- },
13688
- signal: {
13689
- toolName: "smartmoney_get_signal",
13690
- usage: "okx smartmoney signal [--instId <id>] [--instCcy <ccy>] [--ts <ms> | --dataVersion <ver>] [--sortType <pnl|pnlRatio>] [--period <3|7|30|90>] [--pnl <tier>] [--winRatio <tier>] [--maxRetreat <tier>] [--asset <tier>] [--lmtNum <n>] [--authorIds <ids>] [--json]",
13691
- description: "Single-currency aggregated consensus signal (requires --instId or --instCcy, and --ts or --dataVersion; --instId / --ts take precedence)"
13692
- },
13693
- "signal-history": {
13694
- toolName: "smartmoney_get_signal_history",
13695
- usage: "okx smartmoney signal-history --instId <id> [--ts <ms> | --dataVersion <ver>] [--granularity <1h|1d>] [--limit <n>] [--sortType <pnl|pnlRatio>] [--period <3|7|30|90>] [--pnl <tier>] [--winRatio <tier>] [--maxRetreat <tier>] [--asset <tier>] [--json]",
13696
- description: "Signal history timeline sorted by ts DESC (requires --instId and --ts/--dataVersion)"
13697
- },
13698
- traders: {
13699
- toolName: "smartmoney_get_traders",
13700
- usage: 'okx smartmoney traders [--dataVersion <ts>] [--sortType <pnl|pnl_ratio>] [--period <""|3|7|30|90>] [--pnl <n>] [--winRatio <r>] [--maxRetreat <r>] [--asset <n>] [--authorIds <ids>] [--limit <n>] [--after <id>] [--before <id>] [--json]',
13701
- description: "List/filter traders from the smart money leaderboard"
13702
- },
13703
- trader: {
13704
- toolName: "smartmoney_get_trader_detail",
13705
- usage: "okx smartmoney trader --authorId <id> [--period <3|7|30|90>] [--instCcy <ccy>] [--tradeLimit <n>] [--json]",
13706
- description: "Trader full portrait (profile + positions + trades)"
14691
+ "traders-by-filter": {
14692
+ toolName: "smartmoney_get_traders_by_filter",
14693
+ usage: "okx smartmoney traders-by-filter [--updateTime <yyyyMMddHHmmss UTC+8>] [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 90>] [--minPnl <n>] [--minWinRate <r>] [--maxDrawdown <r>] [--minAum <n>] [--after <authorId>] [--before <authorId>] [--limit <n, default 10>] [--json]",
14694
+ description: "Leaderboard of top smart-money traders, ranked and filtered by pool conditions"
14695
+ },
14696
+ "performance-by-trader": {
14697
+ toolName: "smartmoney_get_performance_by_trader",
14698
+ usage: "okx smartmoney performance-by-trader --authorIds <id1,id2> [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 90>] [--json]",
14699
+ description: "PnL / win-rate / drawdown profile for one or more traders by authorIds"
14700
+ },
14701
+ "trader-positions": {
14702
+ toolName: "smartmoney_get_trader_positions",
14703
+ usage: "okx smartmoney trader-positions --authorId <id> [--instId <id>] [--json]",
14704
+ description: "Currently-open positions held by a single trader"
14705
+ },
14706
+ "trader-positions-history": {
14707
+ toolName: "smartmoney_get_trader_positions_history",
14708
+ usage: "okx smartmoney trader-positions-history --authorId <id> [--instId <id>] [--after <posId>] [--before <posId>] [--limit <n, default 10>] [--json]",
14709
+ description: "Closed-position history of a single trader (paginated)"
14710
+ },
14711
+ "trader-orders-history": {
14712
+ toolName: "smartmoney_get_trader_orders_history",
14713
+ usage: "okx smartmoney trader-orders-history --authorId <id> [--instId <id>] [--after <ordId>] [--before <ordId>] [--limit <n, default 10>] [--json]",
14714
+ description: "Recent orders/fills placed by a single trader (paginated)"
14715
+ },
14716
+ "search-trader": {
14717
+ toolName: "smartmoney_search_trader",
14718
+ usage: "okx smartmoney search-trader --keyword <name> [--json]",
14719
+ description: "Search Top Traders by nickname keyword (\u226410 results, ranked by follower count)"
14720
+ },
14721
+ "signal-overview-by-filter": {
14722
+ toolName: "smartmoney_get_signal_overview_by_filter",
14723
+ usage: "okx smartmoney signal-overview-by-filter [--topInstruments <n, default 20> | --instCcyList <BTC,ETH,...>] [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 7>] [--pnlTier <tier>] [--winRateTier <tier>] [--maxDrawdownTier <tier>] [--aumTier <tier>] [--lmtNum <n, default 100>] [--json]",
14724
+ description: "Multi-asset smart-money consensus signal aggregated over a tier-filtered pool"
14725
+ },
14726
+ "signal-overview-by-trader": {
14727
+ toolName: "smartmoney_get_signal_overview_by_trader",
14728
+ usage: "okx smartmoney signal-overview-by-trader --authorIds <id1,id2> [--topInstruments <n, default 20> | --instCcyList <BTC,ETH,...>] [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 7>] [--json]",
14729
+ description: "Multi-asset smart-money signal aggregated over a hand-picked set of traders (authorIds-direct-lookup; pool filters not exposed)"
14730
+ },
14731
+ "signal-trend-by-filter": {
14732
+ toolName: "smartmoney_get_signal_trend_by_filter",
14733
+ usage: "okx smartmoney signal-trend-by-filter --instCcy <ccy> [--asOfTime <yyyyMMddHH UTC>] [--granularity <1h|1d, default 1h>] [--limit <n, default 24>] [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 7>] [--pnlTier <tier>] [--winRateTier <tier>] [--maxDrawdownTier <tier>] [--aumTier <tier>] [--lmtNum <n, default 100>] [--json]",
14734
+ description: "Single-coin smart-money signal time-series aggregated over a tier-filtered pool, anchored at asOfTime"
14735
+ },
14736
+ "signal-trend-by-trader": {
14737
+ toolName: "smartmoney_get_signal_trend_by_trader",
14738
+ usage: "okx smartmoney signal-trend-by-trader --authorIds <id1,id2> --instCcy <ccy> [--asOfTime <yyyyMMddHH UTC>] [--granularity <1h|1d, default 1h>] [--limit <n, default 24>] [--sortBy <pnl|pnlRatio, default pnl>] [--period <3|7|30|90, default 7>] [--json]",
14739
+ description: "Single-coin smart-money signal time-series aggregated over a hand-picked set of traders (authorIds-direct-lookup; pool filters not exposed)"
13707
14740
  }
13708
14741
  }
13709
14742
  },
@@ -14407,6 +15440,33 @@ var CLI_OPTIONS = {
14407
15440
  tpOrdPx: { type: "string" },
14408
15441
  slTriggerPx: { type: "string" },
14409
15442
  slOrdPx: { type: "string" },
15443
+ // Phase 1 algo order flags (issue #178)
15444
+ tpOrdKind: { type: "string" },
15445
+ tpTriggerPxType: { type: "string" },
15446
+ slTriggerPxType: { type: "string" },
15447
+ stpMode: { type: "string" },
15448
+ cxlOnClosePos: { type: "boolean", default: false },
15449
+ // Phase 3a+c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
15450
+ tpTriggerRatio: { type: "string" },
15451
+ slTriggerRatio: { type: "string" },
15452
+ closeFraction: { type: "string" },
15453
+ banAmend: { type: "boolean", default: false },
15454
+ pxAmendType: { type: "string" },
15455
+ // Phase 3b CLI power-user flag (issue #183, CLI-only no MCP/skill exposure)
15456
+ tpLevel: { type: "string", multiple: true },
15457
+ // Phase 2 algo ordType flags (issue #181)
15458
+ orderPx: { type: "string" },
15459
+ advanceOrdType: { type: "string" },
15460
+ triggerPxType: { type: "string" },
15461
+ chaseType: { type: "string" },
15462
+ chaseVal: { type: "string" },
15463
+ maxChaseType: { type: "string" },
15464
+ maxChaseVal: { type: "string" },
15465
+ pxVar: { type: "string" },
15466
+ pxSpread: { type: "string" },
15467
+ szLimit: { type: "string" },
15468
+ pxLimit: { type: "string" },
15469
+ timeInterval: { type: "string" },
14410
15470
  algoId: { type: "string" },
14411
15471
  reduceOnly: { type: "boolean", default: false },
14412
15472
  // algo amend
@@ -14494,19 +15554,23 @@ var CLI_OPTIONS = {
14494
15554
  // smartmoney
14495
15555
  authorId: { type: "string" },
14496
15556
  authorIds: { type: "string" },
14497
- dataVersion: { type: "string" },
14498
- sortType: { type: "string" },
15557
+ updateTime: { type: "string" },
14499
15558
  granularity: { type: "string" },
14500
15559
  lmtNum: { type: "string" },
14501
15560
  instCcy: { type: "string" },
14502
15561
  instCcyList: { type: "string" },
14503
15562
  topInstruments: { type: "string" },
14504
- tradeLimit: { type: "string" },
14505
- // smartmoney pool filters
14506
- pnl: { type: "string" },
14507
- winRatio: { type: "string" },
14508
- maxRetreat: { type: "string" },
14509
- asset: { type: "string" },
15563
+ asOfTime: { type: "string" },
15564
+ // smartmoney pool filters — leaderboard (numeric thresholds)
15565
+ minPnl: { type: "string" },
15566
+ minWinRate: { type: "string" },
15567
+ maxDrawdown: { type: "string" },
15568
+ minAum: { type: "string" },
15569
+ // smartmoney pool filters — signal endpoints (enum tiers)
15570
+ pnlTier: { type: "string" },
15571
+ winRateTier: { type: "string" },
15572
+ maxDrawdownTier: { type: "string" },
15573
+ aumTier: { type: "string" },
14510
15574
  // upgrade
14511
15575
  beta: { type: "boolean", default: false },
14512
15576
  check: { type: "boolean", default: false },
@@ -14593,6 +15657,42 @@ var CLI_OPTIONS = {
14593
15657
  output: { type: "string" }
14594
15658
  // diagnose --output only: save report to file
14595
15659
  };
15660
+ var TP_LEVEL_KEY_MAP = {
15661
+ px: "tpOrdPx",
15662
+ sz: "sz",
15663
+ kind: "tpOrdKind",
15664
+ triggerPx: "tpTriggerPx",
15665
+ triggerPxType: "tpTriggerPxType",
15666
+ amendPxOnTrigger: "amendPxOnTriggerType",
15667
+ clOrdId: "attachAlgoClOrdId"
15668
+ };
15669
+ function parseTpLevel(s) {
15670
+ if (!s || !s.includes(":")) {
15671
+ throw new Error(
15672
+ `Invalid --tpLevel format: "${s}". Expected "key:value,key:value,..." (e.g. "px:78000,sz:0.5,kind:limit")`
15673
+ );
15674
+ }
15675
+ const result = {};
15676
+ const pairs = s.split(",");
15677
+ for (const pair of pairs) {
15678
+ const colonIdx = pair.indexOf(":");
15679
+ if (colonIdx < 1) {
15680
+ throw new Error(
15681
+ `Invalid --tpLevel format: "${pair}". Each entry must be "key:value", not "${pair}". Valid keys: ${Object.keys(TP_LEVEL_KEY_MAP).join(", ")}`
15682
+ );
15683
+ }
15684
+ const key = pair.slice(0, colonIdx).trim();
15685
+ const value = pair.slice(colonIdx + 1).trim();
15686
+ const mapped = TP_LEVEL_KEY_MAP[key];
15687
+ if (!mapped) {
15688
+ throw new Error(
15689
+ `Unknown --tpLevel key: "${key}". Valid keys: ${Object.keys(TP_LEVEL_KEY_MAP).join(", ")}`
15690
+ );
15691
+ }
15692
+ result[mapped] = value;
15693
+ }
15694
+ return result;
15695
+ }
14596
15696
  function parseCli(argv) {
14597
15697
  const negated = /* @__PURE__ */ new Set();
14598
15698
  const filtered = argv.filter((arg) => {
@@ -15338,8 +16438,16 @@ async function cmdSpotPlace(run, opts) {
15338
16438
  clOrdId: opts.clOrdId,
15339
16439
  tpTriggerPx: opts.tpTriggerPx,
15340
16440
  tpOrdPx: opts.tpOrdPx,
16441
+ tpOrdKind: opts.tpOrdKind,
16442
+ tpTriggerPxType: opts.tpTriggerPxType,
15341
16443
  slTriggerPx: opts.slTriggerPx,
15342
- slOrdPx: opts.slOrdPx
16444
+ slOrdPx: opts.slOrdPx,
16445
+ slTriggerPxType: opts.slTriggerPxType,
16446
+ stpMode: opts.stpMode,
16447
+ tradeQuoteCcy: opts.tradeQuoteCcy,
16448
+ banAmend: opts.banAmend,
16449
+ pxAmendType: opts.pxAmendType,
16450
+ tpLevels: opts.tpLevels
15343
16451
  });
15344
16452
  const data = getData4(result);
15345
16453
  if (opts.json) return printJson(data);
@@ -15364,11 +16472,33 @@ async function cmdSpotAlgoPlace(run, opts) {
15364
16472
  tgtCcy: opts.tgtCcy,
15365
16473
  tpTriggerPx: opts.tpTriggerPx,
15366
16474
  tpOrdPx: opts.tpOrdPx,
16475
+ tpOrdKind: opts.tpOrdKind,
16476
+ tpTriggerPxType: opts.tpTriggerPxType,
15367
16477
  slTriggerPx: opts.slTriggerPx,
15368
16478
  slOrdPx: opts.slOrdPx,
16479
+ slTriggerPxType: opts.slTriggerPxType,
16480
+ stpMode: opts.stpMode,
15369
16481
  callbackRatio: opts.callbackRatio,
15370
16482
  callbackSpread: opts.callbackSpread,
15371
- activePx: opts.activePx
16483
+ activePx: opts.activePx,
16484
+ triggerPx: opts.triggerPx,
16485
+ orderPx: opts.orderPx,
16486
+ advanceOrdType: opts.advanceOrdType,
16487
+ triggerPxType: opts.triggerPxType,
16488
+ chaseType: opts.chaseType,
16489
+ chaseVal: opts.chaseVal,
16490
+ maxChaseType: opts.maxChaseType,
16491
+ maxChaseVal: opts.maxChaseVal,
16492
+ pxVar: opts.pxVar,
16493
+ pxSpread: opts.pxSpread,
16494
+ szLimit: opts.szLimit,
16495
+ pxLimit: opts.pxLimit,
16496
+ timeInterval: opts.timeInterval,
16497
+ tpTriggerRatio: opts.tpTriggerRatio,
16498
+ slTriggerRatio: opts.slTriggerRatio,
16499
+ closeFraction: opts.closeFraction,
16500
+ pxAmendType: opts.pxAmendType,
16501
+ tpLevels: opts.tpLevels
15372
16502
  });
15373
16503
  const data = getData4(result);
15374
16504
  if (opts.json) return printJson(data);
@@ -15602,8 +16732,14 @@ async function cmdSwapPlace(run, opts) {
15602
16732
  clOrdId: opts.clOrdId,
15603
16733
  tpTriggerPx: opts.tpTriggerPx,
15604
16734
  tpOrdPx: opts.tpOrdPx,
16735
+ tpOrdKind: opts.tpOrdKind,
16736
+ tpTriggerPxType: opts.tpTriggerPxType,
15605
16737
  slTriggerPx: opts.slTriggerPx,
15606
- slOrdPx: opts.slOrdPx
16738
+ slOrdPx: opts.slOrdPx,
16739
+ slTriggerPxType: opts.slTriggerPxType,
16740
+ stpMode: opts.stpMode,
16741
+ pxAmendType: opts.pxAmendType,
16742
+ tpLevels: opts.tpLevels
15607
16743
  });
15608
16744
  const data = getData5(result);
15609
16745
  if (opts.json) return printJson(data);
@@ -15629,12 +16765,35 @@ async function cmdSwapAlgoPlace(run, opts) {
15629
16765
  posSide: opts.posSide,
15630
16766
  tpTriggerPx: opts.tpTriggerPx,
15631
16767
  tpOrdPx: opts.tpOrdPx,
16768
+ tpOrdKind: opts.tpOrdKind,
16769
+ tpTriggerPxType: opts.tpTriggerPxType,
15632
16770
  slTriggerPx: opts.slTriggerPx,
15633
16771
  slOrdPx: opts.slOrdPx,
16772
+ slTriggerPxType: opts.slTriggerPxType,
16773
+ stpMode: opts.stpMode,
16774
+ cxlOnClosePos: opts.cxlOnClosePos,
15634
16775
  reduceOnly: opts.reduceOnly,
15635
16776
  callbackRatio: opts.callbackRatio,
15636
16777
  callbackSpread: opts.callbackSpread,
15637
- activePx: opts.activePx
16778
+ activePx: opts.activePx,
16779
+ triggerPx: opts.triggerPx,
16780
+ orderPx: opts.orderPx,
16781
+ advanceOrdType: opts.advanceOrdType,
16782
+ triggerPxType: opts.triggerPxType,
16783
+ chaseType: opts.chaseType,
16784
+ chaseVal: opts.chaseVal,
16785
+ maxChaseType: opts.maxChaseType,
16786
+ maxChaseVal: opts.maxChaseVal,
16787
+ pxVar: opts.pxVar,
16788
+ pxSpread: opts.pxSpread,
16789
+ szLimit: opts.szLimit,
16790
+ pxLimit: opts.pxLimit,
16791
+ timeInterval: opts.timeInterval,
16792
+ tpTriggerRatio: opts.tpTriggerRatio,
16793
+ slTriggerRatio: opts.slTriggerRatio,
16794
+ closeFraction: opts.closeFraction,
16795
+ pxAmendType: opts.pxAmendType,
16796
+ tpLevels: opts.tpLevels
15638
16797
  });
15639
16798
  const data = getData5(result);
15640
16799
  if (opts.json) return printJson(data);
@@ -15908,8 +17067,14 @@ async function cmdFuturesPlace(run, opts) {
15908
17067
  clOrdId: opts.clOrdId,
15909
17068
  tpTriggerPx: opts.tpTriggerPx,
15910
17069
  tpOrdPx: opts.tpOrdPx,
17070
+ tpOrdKind: opts.tpOrdKind,
17071
+ tpTriggerPxType: opts.tpTriggerPxType,
15911
17072
  slTriggerPx: opts.slTriggerPx,
15912
- slOrdPx: opts.slOrdPx
17073
+ slOrdPx: opts.slOrdPx,
17074
+ slTriggerPxType: opts.slTriggerPxType,
17075
+ stpMode: opts.stpMode,
17076
+ pxAmendType: opts.pxAmendType,
17077
+ tpLevels: opts.tpLevels
15913
17078
  });
15914
17079
  const data = getData6(result);
15915
17080
  if (opts.json) return printJson(data);
@@ -16037,12 +17202,35 @@ async function cmdFuturesAlgoPlace(run, opts) {
16037
17202
  posSide: opts.posSide,
16038
17203
  tpTriggerPx: opts.tpTriggerPx,
16039
17204
  tpOrdPx: opts.tpOrdPx,
17205
+ tpOrdKind: opts.tpOrdKind,
17206
+ tpTriggerPxType: opts.tpTriggerPxType,
16040
17207
  slTriggerPx: opts.slTriggerPx,
16041
17208
  slOrdPx: opts.slOrdPx,
17209
+ slTriggerPxType: opts.slTriggerPxType,
17210
+ stpMode: opts.stpMode,
17211
+ cxlOnClosePos: opts.cxlOnClosePos,
16042
17212
  reduceOnly: opts.reduceOnly,
16043
17213
  callbackRatio: opts.callbackRatio,
16044
17214
  callbackSpread: opts.callbackSpread,
16045
- activePx: opts.activePx
17215
+ activePx: opts.activePx,
17216
+ triggerPx: opts.triggerPx,
17217
+ orderPx: opts.orderPx,
17218
+ advanceOrdType: opts.advanceOrdType,
17219
+ triggerPxType: opts.triggerPxType,
17220
+ chaseType: opts.chaseType,
17221
+ chaseVal: opts.chaseVal,
17222
+ maxChaseType: opts.maxChaseType,
17223
+ maxChaseVal: opts.maxChaseVal,
17224
+ pxVar: opts.pxVar,
17225
+ pxSpread: opts.pxSpread,
17226
+ szLimit: opts.szLimit,
17227
+ pxLimit: opts.pxLimit,
17228
+ timeInterval: opts.timeInterval,
17229
+ tpTriggerRatio: opts.tpTriggerRatio,
17230
+ slTriggerRatio: opts.slTriggerRatio,
17231
+ closeFraction: opts.closeFraction,
17232
+ pxAmendType: opts.pxAmendType,
17233
+ tpLevels: opts.tpLevels
16046
17234
  });
16047
17235
  const data = getData6(result);
16048
17236
  if (opts.json) return printJson(data);
@@ -16274,8 +17462,12 @@ async function cmdOptionPlace(run, opts) {
16274
17462
  clOrdId: opts.clOrdId,
16275
17463
  tpTriggerPx: opts.tpTriggerPx,
16276
17464
  tpOrdPx: opts.tpOrdPx,
17465
+ tpOrdKind: opts.tpOrdKind,
17466
+ tpTriggerPxType: opts.tpTriggerPxType,
16277
17467
  slTriggerPx: opts.slTriggerPx,
16278
- slOrdPx: opts.slOrdPx
17468
+ slOrdPx: opts.slOrdPx,
17469
+ slTriggerPxType: opts.slTriggerPxType,
17470
+ stpMode: opts.stpMode
16279
17471
  });
16280
17472
  const data = getData7(result);
16281
17473
  if (opts.json) return printJson(data);
@@ -16894,175 +18086,272 @@ function extractFixedOffers(result) {
16894
18086
  }
16895
18087
 
16896
18088
  // src/commands/smartmoney.ts
16897
- function printDataList2(data, json, emptyMsg, mapper) {
16898
- if (json) {
16899
- printJson(data);
16900
- return;
16901
- }
18089
+ function printDataTable(data, emptyMsg, mapper) {
16902
18090
  if (!data.length) {
16903
18091
  outputLine(emptyMsg);
16904
18092
  return;
16905
18093
  }
16906
18094
  printTable(data.map(mapper));
16907
18095
  }
16908
- function poolFilterArgs(o) {
18096
+ function csvToArray(value) {
18097
+ if (!value) return void 0;
18098
+ const arr = value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
18099
+ return arr.length > 0 ? arr : void 0;
18100
+ }
18101
+ function printPaginationHint(result) {
18102
+ if (!result || typeof result !== "object") return;
18103
+ const pagination = result.pagination;
18104
+ if (!pagination || typeof pagination !== "object") return;
18105
+ const { hasMore, nextAfter } = pagination;
18106
+ if (hasMore !== true) return;
18107
+ const cursor = typeof nextAfter === "string" || typeof nextAfter === "number" ? String(nextAfter) : "";
18108
+ errorLine(cursor ? `more results \u2014 pass --after ${cursor} for next page` : "more results \u2014 pass --after <cursor> for next page");
18109
+ }
18110
+ function signalPoolFilterArgs(o) {
16909
18111
  const result = {};
16910
- if (o.sortType) result.sortType = o.sortType;
18112
+ if (o.sortBy) result.sortBy = o.sortBy;
16911
18113
  if (o.period) result.period = o.period;
16912
- if (o.pnl) result.pnl = o.pnl;
16913
- if (o.winRatio) result.winRatio = o.winRatio;
16914
- if (o.maxRetreat) result.maxRetreat = o.maxRetreat;
16915
- if (o.asset) result.asset = o.asset;
18114
+ if (o.pnlTier) result.pnlTier = o.pnlTier;
18115
+ if (o.winRateTier) result.winRateTier = o.winRateTier;
18116
+ if (o.maxDrawdownTier) result.maxDrawdownTier = o.maxDrawdownTier;
18117
+ if (o.aumTier) result.aumTier = o.aumTier;
16916
18118
  return result;
16917
18119
  }
16918
- async function cmdSmartmoneyOverview(run, opts) {
16919
- const result = await run("smartmoney_get_overview", {
16920
- dataVersion: opts.dataVersion,
16921
- ts: opts.ts,
16922
- instType: opts.instType,
16923
- ...poolFilterArgs(opts),
16924
- lmtNum: opts.lmtNum,
16925
- instCcyList: opts.instCcyList,
16926
- instCcy: opts.instCcy,
16927
- topInstruments: opts.topInstruments
18120
+ function leaderboardPoolFilterArgs(o) {
18121
+ const result = {};
18122
+ if (o.sortBy) result.sortBy = o.sortBy;
18123
+ if (o.period) result.period = o.period;
18124
+ if (o.minPnl) result.minPnl = o.minPnl;
18125
+ if (o.minWinRate) result.minWinRate = o.minWinRate;
18126
+ if (o.maxDrawdown) result.maxDrawdown = o.maxDrawdown;
18127
+ if (o.minAum) result.minAum = o.minAum;
18128
+ return result;
18129
+ }
18130
+ async function cmdSmartmoneyTradersByFilter(run, opts) {
18131
+ const result = await run("smartmoney_get_traders_by_filter", {
18132
+ updateTime: opts.updateTime,
18133
+ ...leaderboardPoolFilterArgs(opts),
18134
+ after: opts.after,
18135
+ before: opts.before,
18136
+ limit: opts.limit
16928
18137
  });
18138
+ if (opts.json) {
18139
+ printJson(result);
18140
+ return;
18141
+ }
16929
18142
  const data = extractData(result);
16930
- printDataList2(data, opts.json, "No overview data", (r) => ({
16931
- instId: r["instId"],
16932
- tradersWithPosition: r["tradersWithPosition"],
16933
- longRatio: r["longRatio"],
16934
- weightedLongRatio: r["weightedLongRatio"],
16935
- netNotionalUsdt: r["netNotionalUsdt"],
16936
- vs24h: r["vs24h"]
18143
+ printDataTable(data, "No traders found", (r) => ({
18144
+ authorId: r["authorId"],
18145
+ nickName: r["nickName"],
18146
+ pnl: r["pnl"],
18147
+ pnlRatio: r["pnlRatio"],
18148
+ winRate: r["winRate"],
18149
+ maxDrawdown: r["maxDrawdown"],
18150
+ asset: r["asset"]
16937
18151
  }));
18152
+ printPaginationHint(result);
16938
18153
  }
16939
- async function cmdSmartmoneySignal(run, opts) {
16940
- const result = await run("smartmoney_get_signal", {
16941
- instId: opts.instId,
16942
- instCcy: opts.instCcy,
16943
- dataVersion: opts.dataVersion,
16944
- ts: opts.ts,
16945
- ...poolFilterArgs(opts),
16946
- lmtNum: opts.lmtNum,
16947
- authorIds: opts.authorIds
18154
+ async function cmdSmartmoneyPerformanceByTrader(run, opts) {
18155
+ const result = await run("smartmoney_get_performance_by_trader", {
18156
+ authorIds: csvToArray(opts.authorIds),
18157
+ sortBy: opts.sortBy,
18158
+ period: opts.period
16948
18159
  });
16949
- const data = extractData(result);
16950
- const signal = data[0];
16951
18160
  if (opts.json) {
16952
- printJson(signal ?? {});
18161
+ printJson(result);
16953
18162
  return;
16954
18163
  }
16955
- if (!signal) {
16956
- outputLine("No signal data");
18164
+ const data = extractData(result);
18165
+ printDataTable(data, "No traders found", (r) => ({
18166
+ authorId: r["authorId"],
18167
+ nickName: r["nickName"],
18168
+ pnl: r["pnl"],
18169
+ pnlRatio: r["pnlRatio"],
18170
+ winRate: r["winRate"],
18171
+ maxDrawdown: r["maxDrawdown"],
18172
+ asset: r["asset"]
18173
+ }));
18174
+ }
18175
+ async function cmdSmartmoneyTraderPositions(run, opts) {
18176
+ const result = await run("smartmoney_get_trader_positions", {
18177
+ authorId: opts.authorId,
18178
+ instId: opts.instId
18179
+ });
18180
+ if (opts.json) {
18181
+ printJson(result);
16957
18182
  return;
16958
18183
  }
16959
- printKv({
16960
- instId: signal["instId"],
16961
- instType: signal["instType"],
16962
- tradersWithPosition: signal["tradersWithPosition"],
16963
- tradersTotal: signal["tradersTotal"],
16964
- longRatio: signal["longRatio"],
16965
- weightedLongRatio: signal["weightedLongRatio"],
16966
- avgLongWinRatio: signal["avgLongWinRatio"],
16967
- avgShortWinRatio: signal["avgShortWinRatio"],
16968
- longNotionalUsdt: signal["longNotionalUsdt"],
16969
- shortNotionalUsdt: signal["shortNotionalUsdt"],
16970
- netNotionalUsdt: signal["netNotionalUsdt"],
16971
- longTraders: signal["longTraders"],
16972
- shortTraders: signal["shortTraders"],
16973
- vs1h: signal["vs1h"],
16974
- vs24h: signal["vs24h"],
16975
- vs7d: signal["vs7d"],
16976
- smartMoneyLongAvgEntry: signal["smartMoneyLongAvgEntry"],
16977
- smartMoneyShortAvgEntry: signal["smartMoneyShortAvgEntry"],
16978
- totalNotionalVs24h: signal["totalNotionalVs24h"],
16979
- currentPrice: signal["currentPrice"],
16980
- priceChange24h: signal["priceChange24h"],
16981
- fundingRate: signal["fundingRate"],
16982
- openInterest: signal["openInterest"],
16983
- longShortAccountRatio: signal["longShortAccountRatio"]
16984
- });
18184
+ const data = extractData(result);
18185
+ printDataTable(data, "No open positions", (r) => ({
18186
+ instId: r["instId"],
18187
+ direction: r["direction"] ?? r["posSide"],
18188
+ pos: r["pos"],
18189
+ lever: r["lever"],
18190
+ avgPx: r["avgPx"],
18191
+ last: r["last"],
18192
+ notionalUsd: r["notionalUsd"],
18193
+ upl: r["upl"],
18194
+ pnl: r["pnl"],
18195
+ positionIntensity: r["positionIntensity"]
18196
+ }));
16985
18197
  }
16986
- async function cmdSmartmoneySignalHistory(run, opts) {
16987
- const result = await run("smartmoney_get_signal_history", {
18198
+ async function cmdSmartmoneyTraderPositionsHistory(run, opts) {
18199
+ const result = await run("smartmoney_get_trader_positions_history", {
18200
+ authorId: opts.authorId,
16988
18201
  instId: opts.instId,
16989
- dataVersion: opts.dataVersion,
16990
- ts: opts.ts,
16991
- granularity: opts.granularity,
16992
- limit: opts.limit,
16993
- ...poolFilterArgs(opts)
18202
+ after: opts.after,
18203
+ before: opts.before,
18204
+ limit: opts.limit
16994
18205
  });
18206
+ if (opts.json) {
18207
+ printJson(result);
18208
+ return;
18209
+ }
16995
18210
  const data = extractData(result);
16996
- printDataList2(data, opts.json, "No signal history data", (r) => ({
16997
- ts: r["ts"],
16998
- longRatio: r["longRatio"],
16999
- weightedLongRatio: r["weightedLongRatio"],
17000
- tradersWithPosition: r["tradersWithPosition"],
17001
- netNotionalUsdt: r["netNotionalUsdt"],
17002
- totalNotionalUsdt: r["totalNotionalUsdt"],
17003
- tradersQualified: r["tradersQualified"]
18211
+ printDataTable(data, "No closed positions", (r) => ({
18212
+ cTime: r["cTime"],
18213
+ uTime: r["uTime"],
18214
+ instId: r["instId"],
18215
+ posSide: r["posSide"],
18216
+ openAvgPx: r["openAvgPx"],
18217
+ closeAvgPx: r["closeAvgPx"],
18218
+ pnl: r["pnl"],
18219
+ pnlRatio: r["pnlRatio"],
18220
+ closeType: r["closeType"]
17004
18221
  }));
18222
+ printPaginationHint(result);
17005
18223
  }
17006
- async function cmdSmartmoneyTraders(run, opts) {
17007
- const data = extractData(await run("smartmoney_get_traders", {
17008
- dataVersion: opts.dataVersion,
17009
- ...poolFilterArgs(opts),
17010
- authorIds: opts.authorIds,
18224
+ async function cmdSmartmoneyTraderOrdersHistory(run, opts) {
18225
+ const result = await run("smartmoney_get_trader_orders_history", {
18226
+ authorId: opts.authorId,
18227
+ instId: opts.instId,
17011
18228
  after: opts.after,
17012
18229
  before: opts.before,
17013
18230
  limit: opts.limit
18231
+ });
18232
+ if (opts.json) {
18233
+ printJson(result);
18234
+ return;
18235
+ }
18236
+ const data = extractData(result);
18237
+ printDataTable(data, "No order history", (r) => ({
18238
+ cTime: r["cTime"],
18239
+ instId: r["instId"],
18240
+ side: r["side"],
18241
+ posSide: r["posSide"],
18242
+ ordType: r["ordType"],
18243
+ avgPx: r["avgPx"],
18244
+ sz: r["sz"],
18245
+ value: r["value"]
17014
18246
  }));
17015
- printDataList2(data, opts.json, "No traders found", (r) => ({
18247
+ printPaginationHint(result);
18248
+ }
18249
+ async function cmdSmartmoneySearchTrader(run, opts) {
18250
+ const result = await run("smartmoney_search_trader", {
18251
+ keyword: opts.keyword
18252
+ });
18253
+ if (opts.json) {
18254
+ printJson(result);
18255
+ return;
18256
+ }
18257
+ const data = extractData(result);
18258
+ printDataTable(data, "No matching top traders", (r) => ({
17016
18259
  authorId: r["authorId"],
17017
18260
  nickName: r["nickName"],
17018
- pnl: r["pnl"],
17019
- pnlRatio: r["pnlRatio"],
17020
- winRatio: r["winRatio"],
17021
- asset: r["asset"]
18261
+ followerCount: r["followerCount"]
17022
18262
  }));
17023
18263
  }
17024
- async function cmdSmartmoneyTraderDetail(run, opts) {
17025
- const result = await run("smartmoney_get_trader_detail", {
17026
- authorId: opts.authorId,
17027
- period: opts.period,
17028
- instCcy: opts.instCcy,
17029
- tradeLimit: opts.tradeLimit
18264
+ var signalRowMapper = (r) => {
18265
+ const lsr = r["longShortRatio"] ?? {};
18266
+ const notional = r["notional"] ?? {};
18267
+ return {
18268
+ ccy: r["ccy"],
18269
+ tradersWithPosition: r["tradersWithPosition"],
18270
+ longRatio: lsr["longRatio"],
18271
+ weightedLongRatio: lsr["weightedLongRatio"],
18272
+ longTraders: r["longTraders"],
18273
+ shortTraders: r["shortTraders"],
18274
+ netNotionalUsdt: notional["netNotionalUsdt"],
18275
+ longRatioVs1h: lsr["longRatioVs1h"],
18276
+ longRatioVs24h: lsr["longRatioVs24h"],
18277
+ longRatioVs7d: lsr["longRatioVs7d"]
18278
+ };
18279
+ };
18280
+ async function cmdSmartmoneySignalOverviewByFilter(run, opts) {
18281
+ const result = await run("smartmoney_get_signal_overview_by_filter", {
18282
+ topInstruments: opts.topInstruments,
18283
+ instCcyList: csvToArray(opts.instCcyList),
18284
+ ...signalPoolFilterArgs(opts),
18285
+ lmtNum: opts.lmtNum
17030
18286
  });
17031
- const data = result;
17032
- const inner = data["data"];
17033
18287
  if (opts.json) {
17034
- printJson(inner ?? {});
18288
+ printJson(result);
17035
18289
  return;
17036
18290
  }
17037
- if (!inner) {
17038
- outputLine("No data");
18291
+ const data = extractData(result);
18292
+ printDataTable(data, "No signal data", signalRowMapper);
18293
+ }
18294
+ async function cmdSmartmoneySignalOverviewByTrader(run, opts) {
18295
+ const result = await run("smartmoney_get_signal_overview_by_trader", {
18296
+ authorIds: csvToArray(opts.authorIds),
18297
+ topInstruments: opts.topInstruments,
18298
+ instCcyList: csvToArray(opts.instCcyList),
18299
+ sortBy: opts.sortBy,
18300
+ period: opts.period
18301
+ });
18302
+ if (opts.json) {
18303
+ printJson(result);
17039
18304
  return;
17040
18305
  }
17041
- const profileArr = inner["profile"];
17042
- if (Array.isArray(profileArr) && profileArr.length > 0) {
17043
- const p = profileArr[0];
17044
- outputLine("=== Profile ===");
17045
- printKv({
17046
- authorId: p["authorId"],
17047
- nickName: p["nickName"],
17048
- pnl: p["pnl"],
17049
- pnlRatio: p["pnlRatio"],
17050
- winRatio: p["winRatio"],
17051
- maxRetreat: p["maxRetreat"],
17052
- asset: p["asset"],
17053
- onboardDuration: p["onboardDuration"]
17054
- });
17055
- }
17056
- const posArr = inner["positions"];
17057
- if (Array.isArray(posArr) && posArr.length > 0) {
17058
- outputLine("\n=== Current Positions ===");
17059
- printJson(posArr);
18306
+ const data = extractData(result);
18307
+ printDataTable(data, "No signal data", signalRowMapper);
18308
+ }
18309
+ var trendRowMapper = (r) => ({
18310
+ dataVersion: r["dataVersion"],
18311
+ ccy: r["ccy"],
18312
+ longRatio: r["longRatio"],
18313
+ shortRatio: r["shortRatio"],
18314
+ weightedLongRatio: r["weightedLongRatio"],
18315
+ weightedShortRatio: r["weightedShortRatio"],
18316
+ longTraders: r["longTraders"],
18317
+ shortTraders: r["shortTraders"],
18318
+ tradersWithPosition: r["tradersWithPosition"],
18319
+ tradersQualified: r["tradersQualified"],
18320
+ netNotionalUsdt: r["netNotionalUsdt"],
18321
+ totalNotionalUsdt: r["totalNotionalUsdt"]
18322
+ });
18323
+ async function cmdSmartmoneySignalTrendByFilter(run, opts) {
18324
+ const result = await run("smartmoney_get_signal_trend_by_filter", {
18325
+ instCcy: opts.instCcy,
18326
+ asOfTime: opts.asOfTime,
18327
+ granularity: opts.granularity,
18328
+ limit: opts.limit,
18329
+ ...signalPoolFilterArgs(opts),
18330
+ lmtNum: opts.lmtNum
18331
+ });
18332
+ if (opts.json) {
18333
+ printJson(result);
18334
+ return;
17060
18335
  }
17061
- const tradeArr = inner["trades"];
17062
- if (Array.isArray(tradeArr) && tradeArr.length > 0) {
17063
- outputLine("\n=== Recent Trades ===");
17064
- printJson(tradeArr);
18336
+ const data = extractData(result);
18337
+ printDataTable(data, "No signal trend data", trendRowMapper);
18338
+ }
18339
+ async function cmdSmartmoneySignalTrendByTrader(run, opts) {
18340
+ const result = await run("smartmoney_get_signal_trend_by_trader", {
18341
+ authorIds: csvToArray(opts.authorIds),
18342
+ instCcy: opts.instCcy,
18343
+ asOfTime: opts.asOfTime,
18344
+ granularity: opts.granularity,
18345
+ limit: opts.limit,
18346
+ sortBy: opts.sortBy,
18347
+ period: opts.period
18348
+ });
18349
+ if (opts.json) {
18350
+ printJson(result);
18351
+ return;
17065
18352
  }
18353
+ const data = extractData(result);
18354
+ printDataTable(data, "No signal trend data", trendRowMapper);
17066
18355
  }
17067
18356
 
17068
18357
  // src/commands/auto-earn.ts
@@ -18579,7 +19868,7 @@ async function cmdEventCancel(run, opts) {
18579
19868
  // src/index.ts
18580
19869
  var _require3 = createRequire3(import.meta.url);
18581
19870
  var CLI_VERSION2 = _require3("../package.json").version;
18582
- var GIT_HASH2 = true ? "1bb94dcd" : "dev";
19871
+ var GIT_HASH2 = true ? "e8a0930a" : "dev";
18583
19872
  function handlePilotCommand(action, json, force, binaryPath) {
18584
19873
  if (action === "status") return cmdPilotStatus(json, binaryPath);
18585
19874
  if (action === "install") return cmdPilotInstall(json, binaryPath);
@@ -18809,7 +20098,8 @@ function handleSpotAlgoCommand(run, subAction, v, json) {
18809
20098
  tdMode: v.tdMode,
18810
20099
  json
18811
20100
  });
18812
- if (subAction === "place")
20101
+ if (subAction === "place") {
20102
+ assertNoTpConflict(v.tpLevel, { tpTriggerPx: v.tpTriggerPx, tpOrdPx: v.tpOrdPx });
18813
20103
  return cmdSpotAlgoPlace(run, {
18814
20104
  instId: v.instId,
18815
20105
  tdMode: v.tdMode,
@@ -18820,13 +20110,38 @@ function handleSpotAlgoCommand(run, subAction, v, json) {
18820
20110
  tgtCcy: v.tgtCcy,
18821
20111
  tpTriggerPx: v.tpTriggerPx,
18822
20112
  tpOrdPx: v.tpOrdPx,
20113
+ tpOrdKind: v.tpOrdKind,
20114
+ tpTriggerPxType: v.tpTriggerPxType,
18823
20115
  slTriggerPx: v.slTriggerPx,
18824
20116
  slOrdPx: v.slOrdPx,
20117
+ slTriggerPxType: v.slTriggerPxType,
20118
+ stpMode: v.stpMode,
18825
20119
  callbackRatio: v.callbackRatio,
18826
20120
  callbackSpread: v.callbackSpread,
18827
20121
  activePx: v.activePx,
20122
+ triggerPx: v.triggerPx,
20123
+ orderPx: v.orderPx,
20124
+ advanceOrdType: v.advanceOrdType,
20125
+ triggerPxType: v.triggerPxType,
20126
+ chaseType: v.chaseType,
20127
+ chaseVal: v.chaseVal,
20128
+ maxChaseType: v.maxChaseType,
20129
+ maxChaseVal: v.maxChaseVal,
20130
+ pxVar: v.pxVar,
20131
+ pxSpread: v.pxSpread,
20132
+ szLimit: v.szLimit,
20133
+ pxLimit: v.pxLimit,
20134
+ timeInterval: v.timeInterval,
20135
+ // Phase 3a+c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
20136
+ tpTriggerRatio: v.tpTriggerRatio,
20137
+ slTriggerRatio: v.slTriggerRatio,
20138
+ closeFraction: v.closeFraction,
20139
+ pxAmendType: v.pxAmendType,
20140
+ // Phase 3b CLI power-user flag (issue #183, CLI-only no MCP/skill exposure)
20141
+ tpLevels: v.tpLevel?.map(parseTpLevel),
18828
20142
  json
18829
20143
  });
20144
+ }
18830
20145
  if (subAction === "amend")
18831
20146
  return cmdSpotAlgoAmend(run, {
18832
20147
  instId: v.instId,
@@ -18849,6 +20164,16 @@ function handleSpotAlgoCommand(run, subAction, v, json) {
18849
20164
  });
18850
20165
  unknownSubcommand("spot algo", subAction, ["trail", "place", "amend", "cancel", "orders"]);
18851
20166
  }
20167
+ function assertNoTpConflict(tpLevel, singleFields) {
20168
+ if (!tpLevel || tpLevel.length === 0) return;
20169
+ const conflicting = ["tpTriggerPx", "tpOrdPx"].filter((k) => singleFields[k] !== void 0);
20170
+ if (conflicting.length > 0) {
20171
+ const flagNames = conflicting.map((k) => `--${k}`).join(", ");
20172
+ throw new Error(
20173
+ `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.`
20174
+ );
20175
+ }
20176
+ }
18852
20177
  function handleSpotCommand(run, action, rest, v, json) {
18853
20178
  if (action === "orders")
18854
20179
  return cmdSpotOrders(run, {
@@ -18869,7 +20194,8 @@ function handleSpotCommand(run, action, rest, v, json) {
18869
20194
  newPx: v.newPx,
18870
20195
  json
18871
20196
  });
18872
- if (action === "place")
20197
+ if (action === "place") {
20198
+ assertNoTpConflict(v.tpLevel, { tpTriggerPx: v.tpTriggerPx, tpOrdPx: v.tpOrdPx });
18873
20199
  return cmdSpotPlace(run, {
18874
20200
  instId: v.instId,
18875
20201
  tdMode: v.tdMode,
@@ -18881,10 +20207,21 @@ function handleSpotCommand(run, action, rest, v, json) {
18881
20207
  clOrdId: v.clOrdId,
18882
20208
  tpTriggerPx: v.tpTriggerPx,
18883
20209
  tpOrdPx: v.tpOrdPx,
20210
+ tpOrdKind: v.tpOrdKind,
20211
+ tpTriggerPxType: v.tpTriggerPxType,
18884
20212
  slTriggerPx: v.slTriggerPx,
18885
20213
  slOrdPx: v.slOrdPx,
20214
+ slTriggerPxType: v.slTriggerPxType,
20215
+ stpMode: v.stpMode,
20216
+ // Phase 3c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
20217
+ tradeQuoteCcy: v.tradeQuoteCcy,
20218
+ banAmend: v.banAmend,
20219
+ pxAmendType: v.pxAmendType,
20220
+ // Phase 3b CLI power-user flag (issue #183, CLI-only no MCP/skill exposure)
20221
+ tpLevels: v.tpLevel?.map(parseTpLevel),
18886
20222
  json
18887
20223
  });
20224
+ }
18888
20225
  if (action === "cancel")
18889
20226
  return cmdSpotCancel(run, { instId: v.instId ?? rest[0], ordId: v.ordId, clOrdId: v.clOrdId, json });
18890
20227
  if (action === "algo")
@@ -18925,7 +20262,8 @@ function handleSwapAlgoCommand(run, subAction, v, json) {
18925
20262
  reduceOnly: v.reduceOnly,
18926
20263
  json
18927
20264
  });
18928
- if (subAction === "place")
20265
+ if (subAction === "place") {
20266
+ assertNoTpConflict(v.tpLevel, { tpTriggerPx: v.tpTriggerPx, tpOrdPx: v.tpOrdPx });
18929
20267
  return cmdSwapAlgoPlace(run, {
18930
20268
  instId: v.instId,
18931
20269
  side: v.side,
@@ -18937,14 +20275,40 @@ function handleSwapAlgoCommand(run, subAction, v, json) {
18937
20275
  tgtCcy: v.tgtCcy,
18938
20276
  tpTriggerPx: v.tpTriggerPx,
18939
20277
  tpOrdPx: v.tpOrdPx,
20278
+ tpOrdKind: v.tpOrdKind,
20279
+ tpTriggerPxType: v.tpTriggerPxType,
18940
20280
  slTriggerPx: v.slTriggerPx,
18941
20281
  slOrdPx: v.slOrdPx,
20282
+ slTriggerPxType: v.slTriggerPxType,
20283
+ stpMode: v.stpMode,
20284
+ cxlOnClosePos: v.cxlOnClosePos,
18942
20285
  reduceOnly: v.reduceOnly,
18943
20286
  callbackRatio: v.callbackRatio,
18944
20287
  callbackSpread: v.callbackSpread,
18945
20288
  activePx: v.activePx,
20289
+ triggerPx: v.triggerPx,
20290
+ orderPx: v.orderPx,
20291
+ advanceOrdType: v.advanceOrdType,
20292
+ triggerPxType: v.triggerPxType,
20293
+ chaseType: v.chaseType,
20294
+ chaseVal: v.chaseVal,
20295
+ maxChaseType: v.maxChaseType,
20296
+ maxChaseVal: v.maxChaseVal,
20297
+ pxVar: v.pxVar,
20298
+ pxSpread: v.pxSpread,
20299
+ szLimit: v.szLimit,
20300
+ pxLimit: v.pxLimit,
20301
+ timeInterval: v.timeInterval,
20302
+ // Phase 3a+c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
20303
+ tpTriggerRatio: v.tpTriggerRatio,
20304
+ slTriggerRatio: v.slTriggerRatio,
20305
+ closeFraction: v.closeFraction,
20306
+ pxAmendType: v.pxAmendType,
20307
+ // Phase 3b CLI power-user flag (issue #183, CLI-only no MCP/skill exposure)
20308
+ tpLevels: v.tpLevel?.map(parseTpLevel),
18946
20309
  json
18947
20310
  });
20311
+ }
18948
20312
  if (subAction === "amend")
18949
20313
  return cmdSwapAlgoAmend(run, {
18950
20314
  instId: v.instId,
@@ -19000,7 +20364,8 @@ function handleSwapCommand(run, action, rest, v, json) {
19000
20364
  autoCxl: v.autoCxl,
19001
20365
  json
19002
20366
  });
19003
- if (action === "place")
20367
+ if (action === "place") {
20368
+ assertNoTpConflict(v.tpLevel, { tpTriggerPx: v.tpTriggerPx, tpOrdPx: v.tpOrdPx });
19004
20369
  return cmdSwapPlace(run, {
19005
20370
  instId: v.instId,
19006
20371
  side: v.side,
@@ -19014,10 +20379,19 @@ function handleSwapCommand(run, action, rest, v, json) {
19014
20379
  clOrdId: v.clOrdId,
19015
20380
  tpTriggerPx: v.tpTriggerPx,
19016
20381
  tpOrdPx: v.tpOrdPx,
20382
+ tpOrdKind: v.tpOrdKind,
20383
+ tpTriggerPxType: v.tpTriggerPxType,
19017
20384
  slTriggerPx: v.slTriggerPx,
19018
20385
  slOrdPx: v.slOrdPx,
20386
+ slTriggerPxType: v.slTriggerPxType,
20387
+ stpMode: v.stpMode,
20388
+ // Phase 3c CLI power-user flag (issue #182, CLI-only no MCP/skill exposure)
20389
+ pxAmendType: v.pxAmendType,
20390
+ // Phase 3b CLI power-user flag (issue #183, CLI-only no MCP/skill exposure)
20391
+ tpLevels: v.tpLevel?.map(parseTpLevel),
19019
20392
  json
19020
20393
  });
20394
+ }
19021
20395
  if (action === "cancel")
19022
20396
  return cmdSwapCancel(run, { instId: v.instId ?? rest[0], ordId: v.ordId, clOrdId: v.clOrdId, json });
19023
20397
  if (action === "amend")
@@ -19125,8 +20499,12 @@ function handleOptionCommand(run, action, rest, v, json) {
19125
20499
  clOrdId: v.clOrdId,
19126
20500
  tpTriggerPx: v.tpTriggerPx,
19127
20501
  tpOrdPx: v.tpOrdPx,
20502
+ tpOrdKind: v.tpOrdKind,
20503
+ tpTriggerPxType: v.tpTriggerPxType,
19128
20504
  slTriggerPx: v.slTriggerPx,
19129
20505
  slOrdPx: v.slOrdPx,
20506
+ slTriggerPxType: v.slTriggerPxType,
20507
+ stpMode: v.stpMode,
19130
20508
  json
19131
20509
  });
19132
20510
  if (action === "cancel")
@@ -19172,7 +20550,8 @@ function handleFuturesAlgoCommand(run, subAction, v, json) {
19172
20550
  reduceOnly: v.reduceOnly,
19173
20551
  json
19174
20552
  });
19175
- if (subAction === "place")
20553
+ if (subAction === "place") {
20554
+ assertNoTpConflict(v.tpLevel, { tpTriggerPx: v.tpTriggerPx, tpOrdPx: v.tpOrdPx });
19176
20555
  return cmdFuturesAlgoPlace(run, {
19177
20556
  instId: v.instId,
19178
20557
  side: v.side,
@@ -19184,14 +20563,40 @@ function handleFuturesAlgoCommand(run, subAction, v, json) {
19184
20563
  tgtCcy: v.tgtCcy,
19185
20564
  tpTriggerPx: v.tpTriggerPx,
19186
20565
  tpOrdPx: v.tpOrdPx,
20566
+ tpOrdKind: v.tpOrdKind,
20567
+ tpTriggerPxType: v.tpTriggerPxType,
19187
20568
  slTriggerPx: v.slTriggerPx,
19188
20569
  slOrdPx: v.slOrdPx,
20570
+ slTriggerPxType: v.slTriggerPxType,
20571
+ stpMode: v.stpMode,
20572
+ cxlOnClosePos: v.cxlOnClosePos,
19189
20573
  reduceOnly: v.reduceOnly,
19190
20574
  callbackRatio: v.callbackRatio,
19191
20575
  callbackSpread: v.callbackSpread,
19192
20576
  activePx: v.activePx,
20577
+ triggerPx: v.triggerPx,
20578
+ orderPx: v.orderPx,
20579
+ advanceOrdType: v.advanceOrdType,
20580
+ triggerPxType: v.triggerPxType,
20581
+ chaseType: v.chaseType,
20582
+ chaseVal: v.chaseVal,
20583
+ maxChaseType: v.maxChaseType,
20584
+ maxChaseVal: v.maxChaseVal,
20585
+ pxVar: v.pxVar,
20586
+ pxSpread: v.pxSpread,
20587
+ szLimit: v.szLimit,
20588
+ pxLimit: v.pxLimit,
20589
+ timeInterval: v.timeInterval,
20590
+ // Phase 3a+c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
20591
+ tpTriggerRatio: v.tpTriggerRatio,
20592
+ slTriggerRatio: v.slTriggerRatio,
20593
+ closeFraction: v.closeFraction,
20594
+ pxAmendType: v.pxAmendType,
20595
+ // Phase 3b CLI power-user flag (issue #183, CLI-only no MCP/skill exposure)
20596
+ tpLevels: v.tpLevel?.map(parseTpLevel),
19193
20597
  json
19194
20598
  });
20599
+ }
19195
20600
  if (subAction === "amend")
19196
20601
  return cmdFuturesAlgoAmend(run, {
19197
20602
  instId: v.instId,
@@ -19239,7 +20644,8 @@ function handleFuturesQuery(run, action, v, json) {
19239
20644
  function handleFuturesCommand(run, action, rest, v, json) {
19240
20645
  const queryResult = handleFuturesQuery(run, action, v, json);
19241
20646
  if (queryResult !== void 0) return queryResult;
19242
- if (action === "place")
20647
+ if (action === "place") {
20648
+ assertNoTpConflict(v.tpLevel, { tpTriggerPx: v.tpTriggerPx, tpOrdPx: v.tpOrdPx });
19243
20649
  return cmdFuturesPlace(run, {
19244
20650
  instId: v.instId,
19245
20651
  side: v.side,
@@ -19253,10 +20659,19 @@ function handleFuturesCommand(run, action, rest, v, json) {
19253
20659
  clOrdId: v.clOrdId,
19254
20660
  tpTriggerPx: v.tpTriggerPx,
19255
20661
  tpOrdPx: v.tpOrdPx,
20662
+ tpOrdKind: v.tpOrdKind,
20663
+ tpTriggerPxType: v.tpTriggerPxType,
19256
20664
  slTriggerPx: v.slTriggerPx,
19257
20665
  slOrdPx: v.slOrdPx,
20666
+ slTriggerPxType: v.slTriggerPxType,
20667
+ stpMode: v.stpMode,
20668
+ // Phase 3c CLI power-user flag (issue #182, CLI-only no MCP/skill exposure)
20669
+ pxAmendType: v.pxAmendType,
20670
+ // Phase 3b CLI power-user flag (issue #183, CLI-only no MCP/skill exposure)
20671
+ tpLevels: v.tpLevel?.map(parseTpLevel),
19258
20672
  json
19259
20673
  });
20674
+ }
19260
20675
  if (action === "cancel")
19261
20676
  return cmdFuturesCancel(run, { instId: v.instId ?? rest[0], ordId: v.ordId, clOrdId: v.clOrdId, json });
19262
20677
  if (action === "amend")
@@ -19457,79 +20872,176 @@ function handleEarnFlashEarnCommand(run, action, v, json) {
19457
20872
  process.exitCode = 1;
19458
20873
  }
19459
20874
  function handleSmartmoneyCommand(run, action, rest, v, json) {
19460
- const poolFilters = {
19461
- sortType: v.sortType,
20875
+ void rest;
20876
+ const signalPoolFilters = {
20877
+ sortBy: v.sortBy,
19462
20878
  period: v.period,
19463
- pnl: v.pnl,
19464
- winRatio: v.winRatio,
19465
- maxRetreat: v.maxRetreat,
19466
- asset: v.asset
20879
+ pnlTier: v.pnlTier,
20880
+ winRateTier: v.winRateTier,
20881
+ maxDrawdownTier: v.maxDrawdownTier,
20882
+ aumTier: v.aumTier
19467
20883
  };
19468
- if (action === "overview")
19469
- return cmdSmartmoneyOverview(run, {
19470
- dataVersion: v.dataVersion,
19471
- ts: v.ts,
19472
- instType: v.instType,
19473
- ...poolFilters,
19474
- lmtNum: v.lmtNum,
19475
- instCcyList: v.instCcyList,
19476
- instCcy: v.instCcy,
19477
- topInstruments: v.topInstruments,
20884
+ const leaderboardPoolFilters = {
20885
+ sortBy: v.sortBy,
20886
+ period: v.period,
20887
+ minPnl: v.minPnl,
20888
+ minWinRate: v.minWinRate,
20889
+ maxDrawdown: v.maxDrawdown,
20890
+ minAum: v.minAum
20891
+ };
20892
+ if (action === "traders-by-filter")
20893
+ return cmdSmartmoneyTradersByFilter(run, {
20894
+ updateTime: v.updateTime,
20895
+ ...leaderboardPoolFilters,
20896
+ after: v.after,
20897
+ before: v.before,
20898
+ limit: v.limit,
19478
20899
  json
19479
20900
  });
19480
- if (action === "signal")
19481
- return cmdSmartmoneySignal(run, {
19482
- instId: v.instId,
19483
- dataVersion: v.dataVersion,
19484
- ts: v.ts,
19485
- ...poolFilters,
19486
- instCcy: v.instCcy,
19487
- lmtNum: v.lmtNum,
20901
+ if (action === "performance-by-trader") {
20902
+ if (!v.authorIds) {
20903
+ errorLine("Missing required --authorIds: okx smartmoney performance-by-trader --authorIds <id1,id2> [--period <3|7|30|90>]");
20904
+ process.exitCode = 1;
20905
+ return;
20906
+ }
20907
+ return cmdSmartmoneyPerformanceByTrader(run, {
19488
20908
  authorIds: v.authorIds,
20909
+ sortBy: v.sortBy,
20910
+ period: v.period,
19489
20911
  json
19490
20912
  });
19491
- if (action === "signal-history") {
19492
- if (!v.instId) {
19493
- errorLine("Missing required --instId: okx smartmoney signal-history --instId <id>");
20913
+ }
20914
+ if (action === "trader-positions") {
20915
+ if (!v.authorId) {
20916
+ errorLine("Missing required --authorId: okx smartmoney trader-positions --authorId <id> [--instId <id>]");
19494
20917
  process.exitCode = 1;
19495
20918
  return;
19496
20919
  }
19497
- return cmdSmartmoneySignalHistory(run, {
20920
+ return cmdSmartmoneyTraderPositions(run, {
20921
+ authorId: v.authorId,
19498
20922
  instId: v.instId,
19499
- dataVersion: v.dataVersion,
19500
- ts: v.ts,
19501
- granularity: v.granularity,
19502
- limit: v.limit,
19503
- ...poolFilters,
19504
20923
  json
19505
20924
  });
19506
20925
  }
19507
- if (action === "traders")
19508
- return cmdSmartmoneyTraders(run, {
19509
- dataVersion: v.dataVersion,
19510
- ...poolFilters,
19511
- authorIds: v.authorIds,
20926
+ if (action === "trader-positions-history") {
20927
+ if (!v.authorId) {
20928
+ errorLine("Missing required --authorId: okx smartmoney trader-positions-history --authorId <id> [--instId <id>] [--limit <n>]");
20929
+ process.exitCode = 1;
20930
+ return;
20931
+ }
20932
+ return cmdSmartmoneyTraderPositionsHistory(run, {
20933
+ authorId: v.authorId,
20934
+ instId: v.instId,
19512
20935
  after: v.after,
19513
20936
  before: v.before,
19514
20937
  limit: v.limit,
19515
20938
  json
19516
20939
  });
19517
- if (action === "trader") {
20940
+ }
20941
+ if (action === "trader-orders-history") {
19518
20942
  if (!v.authorId) {
19519
- errorLine("Missing required --authorId: okx smartmoney trader --authorId <id>");
20943
+ errorLine("Missing required --authorId: okx smartmoney trader-orders-history --authorId <id> [--instId <id>] [--limit <n>]");
19520
20944
  process.exitCode = 1;
19521
20945
  return;
19522
20946
  }
19523
- return cmdSmartmoneyTraderDetail(run, {
20947
+ return cmdSmartmoneyTraderOrdersHistory(run, {
19524
20948
  authorId: v.authorId,
20949
+ instId: v.instId,
20950
+ after: v.after,
20951
+ before: v.before,
20952
+ limit: v.limit,
20953
+ json
20954
+ });
20955
+ }
20956
+ if (action === "search-trader") {
20957
+ if (!v.keyword || v.keyword.trim() === "") {
20958
+ errorLine("Missing required --keyword: okx smartmoney search-trader --keyword <name>");
20959
+ process.exitCode = 1;
20960
+ return;
20961
+ }
20962
+ return cmdSmartmoneySearchTrader(run, {
20963
+ keyword: v.keyword,
20964
+ json
20965
+ });
20966
+ }
20967
+ if (action === "signal-overview-by-filter") {
20968
+ if (v.topInstruments && v.instCcyList) {
20969
+ errorLine(
20970
+ "--topInstruments and --instCcyList are mutually exclusive. Pass exactly one \u2014 `--topInstruments` for top-N hottest coins, or `--instCcyList` for specific coins."
20971
+ );
20972
+ process.exitCode = 1;
20973
+ return;
20974
+ }
20975
+ return cmdSmartmoneySignalOverviewByFilter(run, {
20976
+ topInstruments: v.topInstruments,
20977
+ instCcyList: v.instCcyList,
20978
+ ...signalPoolFilters,
20979
+ lmtNum: v.lmtNum,
20980
+ json
20981
+ });
20982
+ }
20983
+ if (action === "signal-overview-by-trader") {
20984
+ if (!v.authorIds) {
20985
+ errorLine("Missing required --authorIds: okx smartmoney signal-overview-by-trader --authorIds <id1,id2> [--topInstruments <n> | --instCcyList <BTC,ETH>]");
20986
+ process.exitCode = 1;
20987
+ return;
20988
+ }
20989
+ if (v.topInstruments && v.instCcyList) {
20990
+ errorLine(
20991
+ "--topInstruments and --instCcyList are mutually exclusive. Pass exactly one \u2014 `--topInstruments` for top-N hottest coins, or `--instCcyList` for specific coins."
20992
+ );
20993
+ process.exitCode = 1;
20994
+ return;
20995
+ }
20996
+ return cmdSmartmoneySignalOverviewByTrader(run, {
20997
+ authorIds: v.authorIds,
20998
+ topInstruments: v.topInstruments,
20999
+ instCcyList: v.instCcyList,
21000
+ sortBy: v.sortBy,
19525
21001
  period: v.period,
21002
+ json
21003
+ });
21004
+ }
21005
+ if (action === "signal-trend-by-filter") {
21006
+ if (!v.instCcy) {
21007
+ errorLine("Missing required --instCcy: okx smartmoney signal-trend-by-filter --instCcy <ccy> [--asOfTime <yyyyMMddHH>] [--granularity <1h|1d>] [--limit <n>]");
21008
+ process.exitCode = 1;
21009
+ return;
21010
+ }
21011
+ return cmdSmartmoneySignalTrendByFilter(run, {
21012
+ instCcy: v.instCcy,
21013
+ asOfTime: v.asOfTime,
21014
+ granularity: v.granularity,
21015
+ limit: v.limit,
21016
+ ...signalPoolFilters,
21017
+ lmtNum: v.lmtNum,
21018
+ json
21019
+ });
21020
+ }
21021
+ if (action === "signal-trend-by-trader") {
21022
+ if (!v.authorIds) {
21023
+ errorLine("Missing required --authorIds: okx smartmoney signal-trend-by-trader --authorIds <id1,id2> --instCcy <ccy> [--asOfTime <yyyyMMddHH>]");
21024
+ process.exitCode = 1;
21025
+ return;
21026
+ }
21027
+ if (!v.instCcy) {
21028
+ errorLine("Missing required --instCcy: okx smartmoney signal-trend-by-trader --authorIds <id1,id2> --instCcy <ccy> [--asOfTime <yyyyMMddHH>]");
21029
+ process.exitCode = 1;
21030
+ return;
21031
+ }
21032
+ return cmdSmartmoneySignalTrendByTrader(run, {
21033
+ authorIds: v.authorIds,
19526
21034
  instCcy: v.instCcy,
19527
- tradeLimit: v.tradeLimit,
21035
+ asOfTime: v.asOfTime,
21036
+ granularity: v.granularity,
21037
+ limit: v.limit,
21038
+ sortBy: v.sortBy,
21039
+ period: v.period,
19528
21040
  json
19529
21041
  });
19530
21042
  }
19531
21043
  errorLine(`Unknown smartmoney command: ${action}`);
19532
- errorLine("Valid: overview, signal, signal-history, traders, trader");
21044
+ errorLine("Valid: traders-by-filter, performance-by-trader, trader-positions, trader-positions-history, trader-orders-history, search-trader, signal-overview-by-filter, signal-overview-by-trader, signal-trend-by-filter, signal-trend-by-trader");
19533
21045
  process.exitCode = 1;
19534
21046
  }
19535
21047
  function handleEarnSavingsCommand(run, action, rest, v, json) {
@@ -19854,12 +21366,14 @@ async function main() {
19854
21366
  main().catch((error) => {
19855
21367
  const payload = toToolErrorPayload(error);
19856
21368
  errorLine(`Error: ${payload.message}`);
21369
+ if (payload.code) errorLine(`Code: ${payload.code}`);
19857
21370
  if (payload.traceId) errorLine(`TraceId: ${payload.traceId}`);
19858
21371
  if (payload.suggestion) errorLine(`Hint: ${payload.suggestion}`);
19859
21372
  errorLine(`Version: @okx_ai/okx-trade-cli@${CLI_VERSION2}`);
19860
21373
  process.exitCode = 1;
19861
21374
  });
19862
21375
  export {
21376
+ assertNoTpConflict,
19863
21377
  handleAccountWriteCommand,
19864
21378
  handleBotCommand,
19865
21379
  handleBotDcaCommand,