@okx_ai/okx-trade-mcp 1.3.2 → 1.3.3-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1816,6 +1816,16 @@ function readBoolean(args, key) {
1816
1816
  }
1817
1817
  return value;
1818
1818
  }
1819
+ function readStringArray(args, key) {
1820
+ const value = args[key];
1821
+ if (value === void 0 || value === null) {
1822
+ return void 0;
1823
+ }
1824
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
1825
+ throw new ValidationError(`Parameter "${key}" must be an array of strings.`);
1826
+ }
1827
+ return value;
1828
+ }
1819
1829
  function requireString(args, key) {
1820
1830
  const value = readString(args, key);
1821
1831
  if (!value || value.length === 0) {
@@ -1856,12 +1866,170 @@ function validateSwapInstId(instId) {
1856
1866
  );
1857
1867
  }
1858
1868
  }
1869
+ var TP_ORD_KIND_SCHEMA = {
1870
+ type: "string",
1871
+ enum: ["condition", "limit"],
1872
+ description: "condition(default)=trigger-based TP; limit=immediate limit order (no trigger phase)"
1873
+ };
1874
+ var TP_TRIGGER_PX_TYPE_SCHEMA = {
1875
+ type: "string",
1876
+ enum: ["last", "index", "mark"],
1877
+ description: "TP trigger price source: last(default)|index|mark"
1878
+ };
1879
+ var SL_TRIGGER_PX_TYPE_SCHEMA = {
1880
+ type: "string",
1881
+ enum: ["last", "index", "mark"],
1882
+ description: "SL trigger price source: last(default)|index|mark"
1883
+ };
1884
+ var STP_MODE_SCHEMA = {
1885
+ type: "string",
1886
+ enum: ["cancel_maker", "cancel_taker", "cancel_both"],
1887
+ description: "Self-trade prevention: cancel_maker|cancel_taker|cancel_both"
1888
+ };
1889
+ var CXL_ON_CLOSE_POS_SCHEMA = {
1890
+ type: "boolean",
1891
+ description: "Auto-cancel TP/SL when associated position closes (algo only)"
1892
+ };
1893
+ var PHASE1_PLACE_FLAGS_SCHEMA = {
1894
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
1895
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
1896
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
1897
+ stpMode: STP_MODE_SCHEMA
1898
+ };
1899
+ var PHASE1_ALGO_FLAGS_SCHEMA = {
1900
+ ...PHASE1_PLACE_FLAGS_SCHEMA,
1901
+ cxlOnClosePos: CXL_ON_CLOSE_POS_SCHEMA
1902
+ };
1903
+ var TRIGGER_FLAGS_SCHEMA = {
1904
+ triggerPx: {
1905
+ type: "string",
1906
+ description: "Activation price; order submits when market hits this level (trigger only)"
1907
+ },
1908
+ orderPx: {
1909
+ type: "string",
1910
+ description: "Order price submitted when trigger fires; -1=market (trigger only)"
1911
+ },
1912
+ advanceOrdType: {
1913
+ type: "string",
1914
+ enum: ["fok", "ioc"],
1915
+ description: "Execution qualifier for triggered order: fok=fill-or-kill, ioc=immediate-or-cancel (trigger only)"
1916
+ },
1917
+ triggerPxType: {
1918
+ type: "string",
1919
+ enum: ["last", "index", "mark"],
1920
+ description: "Price type used to evaluate trigger: last(default)|index|mark (trigger only)"
1921
+ }
1922
+ };
1923
+ var CHASE_FLAGS_SCHEMA = {
1924
+ chaseType: {
1925
+ type: "string",
1926
+ enum: ["distance", "ratio"],
1927
+ description: "Chase unit: distance=price ticks, ratio=proportion of price (chase only, default distance)"
1928
+ },
1929
+ chaseVal: {
1930
+ type: "string",
1931
+ description: "Chase amount matching chaseType (e.g. 0.5 ticks for distance, 0.001 for ratio) (chase only)"
1932
+ },
1933
+ maxChaseType: {
1934
+ type: "string",
1935
+ enum: ["distance", "ratio"],
1936
+ description: "Upper-bound unit for chase (chase only)"
1937
+ },
1938
+ maxChaseVal: {
1939
+ type: "string",
1940
+ description: "Upper-bound value for chase (chase only)"
1941
+ }
1942
+ };
1943
+ var ICEBERG_TWAP_FLAGS_SCHEMA = {
1944
+ pxVar: {
1945
+ type: "string",
1946
+ description: "Price variance % [0.0001, 0.01]; provide pxVar OR pxSpread (iceberg/twap only)"
1947
+ },
1948
+ pxSpread: {
1949
+ type: "string",
1950
+ description: "Price variance constant >= 0; provide pxVar OR pxSpread (iceberg/twap only)"
1951
+ },
1952
+ szLimit: {
1953
+ type: "string",
1954
+ description: "Average per-child-order size (iceberg/twap only)"
1955
+ },
1956
+ pxLimit: {
1957
+ type: "string",
1958
+ description: "Order price ceiling >= 0 (iceberg/twap only)"
1959
+ },
1960
+ timeInterval: {
1961
+ type: "string",
1962
+ description: "Seconds between child orders (iceberg/twap only)"
1963
+ }
1964
+ };
1965
+ function buildTriggerOrdTypeBody(args) {
1966
+ return compactObject({
1967
+ triggerPx: readString(args, "triggerPx"),
1968
+ orderPx: readString(args, "orderPx"),
1969
+ advanceOrdType: readString(args, "advanceOrdType"),
1970
+ triggerPxType: readString(args, "triggerPxType"),
1971
+ attachAlgoOrds: buildAttachAlgoOrds(args)
1972
+ });
1973
+ }
1974
+ function buildChaseOrdTypeBody(args) {
1975
+ return compactObject({
1976
+ chaseType: readString(args, "chaseType"),
1977
+ chaseVal: readString(args, "chaseVal"),
1978
+ maxChaseType: readString(args, "maxChaseType"),
1979
+ maxChaseVal: readString(args, "maxChaseVal")
1980
+ });
1981
+ }
1982
+ function buildIcebergTwapOrdTypeBody(args) {
1983
+ return compactObject({
1984
+ pxVar: readString(args, "pxVar"),
1985
+ pxSpread: readString(args, "pxSpread"),
1986
+ szLimit: readString(args, "szLimit"),
1987
+ pxLimit: readString(args, "pxLimit"),
1988
+ timeInterval: readString(args, "timeInterval")
1989
+ });
1990
+ }
1991
+ function buildAlgoConditionalCommonFields(args) {
1992
+ return {
1993
+ tpTriggerPx: readString(args, "tpTriggerPx"),
1994
+ tpOrdPx: readString(args, "tpOrdPx"),
1995
+ tpOrdKind: readString(args, "tpOrdKind"),
1996
+ tpTriggerPxType: readString(args, "tpTriggerPxType"),
1997
+ tpTriggerRatio: readString(args, "tpTriggerRatio"),
1998
+ slTriggerPx: readString(args, "slTriggerPx"),
1999
+ slOrdPx: readString(args, "slOrdPx"),
2000
+ slTriggerPxType: readString(args, "slTriggerPxType"),
2001
+ slTriggerRatio: readString(args, "slTriggerRatio"),
2002
+ closeFraction: readString(args, "closeFraction"),
2003
+ activePx: readString(args, "activePx")
2004
+ };
2005
+ }
1859
2006
  function buildAttachAlgoOrds(source) {
2007
+ const tpLevels = source["tpLevels"];
2008
+ if (Array.isArray(tpLevels) && tpLevels.length > 0) {
2009
+ return tpLevels.map(
2010
+ (level) => compactObject(level)
2011
+ );
2012
+ }
1860
2013
  const tpTriggerPx = readString(source, "tpTriggerPx");
1861
2014
  const tpOrdPx = readString(source, "tpOrdPx");
1862
2015
  const slTriggerPx = readString(source, "slTriggerPx");
1863
2016
  const slOrdPx = readString(source, "slOrdPx");
1864
- const entry = compactObject({ tpTriggerPx, tpOrdPx, slTriggerPx, slOrdPx });
2017
+ const tpOrdKind = readString(source, "tpOrdKind");
2018
+ const tpTriggerPxType = readString(source, "tpTriggerPxType");
2019
+ const slTriggerPxType = readString(source, "slTriggerPxType");
2020
+ const tpTriggerRatio = readString(source, "tpTriggerRatio");
2021
+ const slTriggerRatio = readString(source, "slTriggerRatio");
2022
+ const entry = compactObject({
2023
+ tpTriggerPx,
2024
+ tpOrdPx,
2025
+ slTriggerPx,
2026
+ slOrdPx,
2027
+ tpOrdKind,
2028
+ tpTriggerPxType,
2029
+ slTriggerPxType,
2030
+ tpTriggerRatio,
2031
+ slTriggerRatio
2032
+ });
1865
2033
  return Object.keys(entry).length > 0 ? [entry] : void 0;
1866
2034
  }
1867
2035
  var OKX_CANDLE_BARS = [
@@ -2908,7 +3076,7 @@ function registerAlgoTradeTools() {
2908
3076
  {
2909
3077
  name: "swap_place_algo_order",
2910
3078
  module: "swap",
2911
- 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.",
3079
+ 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.",
2912
3080
  isWrite: true,
2913
3081
  inputSchema: {
2914
3082
  type: "object",
@@ -2934,8 +3102,8 @@ function registerAlgoTradeTools() {
2934
3102
  },
2935
3103
  ordType: {
2936
3104
  type: "string",
2937
- enum: ["conditional", "oco", "move_order_stop"],
2938
- description: "conditional=single TP/SL or both; oco=TP+SL pair (first trigger cancels other); move_order_stop=trailing stop"
3105
+ enum: ["conditional", "oco", "move_order_stop", "trigger", "chase", "iceberg", "twap"],
3106
+ 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"
2939
3107
  },
2940
3108
  sz: {
2941
3109
  type: "string",
@@ -2949,11 +3117,8 @@ function registerAlgoTradeTools() {
2949
3117
  type: "string",
2950
3118
  description: "TP order price; -1=market (conditional/oco only)"
2951
3119
  },
2952
- tpTriggerPxType: {
2953
- type: "string",
2954
- enum: ["last", "index", "mark"],
2955
- description: "last(default)|index|mark (conditional/oco only)"
2956
- },
3120
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
3121
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
2957
3122
  slTriggerPx: {
2958
3123
  type: "string",
2959
3124
  description: "SL trigger price (conditional/oco only)"
@@ -2962,11 +3127,9 @@ function registerAlgoTradeTools() {
2962
3127
  type: "string",
2963
3128
  description: "SL order price; -1=market (recommended) (conditional/oco only)"
2964
3129
  },
2965
- slTriggerPxType: {
2966
- type: "string",
2967
- enum: ["last", "index", "mark"],
2968
- description: "last(default)|index|mark (conditional/oco only)"
2969
- },
3130
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
3131
+ stpMode: STP_MODE_SCHEMA,
3132
+ cxlOnClosePos: CXL_ON_CLOSE_POS_SCHEMA,
2970
3133
  callbackRatio: {
2971
3134
  type: "string",
2972
3135
  description: "Callback ratio (e.g. '0.01'=1%); provide either ratio or spread (move_order_stop only)"
@@ -2979,6 +3142,9 @@ function registerAlgoTradeTools() {
2979
3142
  type: "string",
2980
3143
  description: "Activation price; tracking starts after market reaches this level (move_order_stop only)"
2981
3144
  },
3145
+ ...TRIGGER_FLAGS_SCHEMA,
3146
+ ...CHASE_FLAGS_SCHEMA,
3147
+ ...ICEBERG_TWAP_FLAGS_SCHEMA,
2982
3148
  tgtCcy: {
2983
3149
  type: "string",
2984
3150
  enum: ["base_ccy", "quote_ccy", "margin"],
@@ -2998,6 +3164,8 @@ function registerAlgoTradeTools() {
2998
3164
  handler: async (rawArgs, context) => {
2999
3165
  const args = asRecord(rawArgs);
3000
3166
  const reduceOnly = args.reduceOnly;
3167
+ const cxlOnClosePos = args.cxlOnClosePos;
3168
+ const ordType = requireString(args, "ordType");
3001
3169
  const resolved = await resolveQuoteCcySz(
3002
3170
  requireString(args, "instId"),
3003
3171
  requireString(args, "sz"),
@@ -3006,29 +3174,44 @@ function registerAlgoTradeTools() {
3006
3174
  context.client,
3007
3175
  readString(args, "tdMode")
3008
3176
  );
3177
+ const base = compactObject({
3178
+ instId: requireString(args, "instId"),
3179
+ tdMode: requireString(args, "tdMode"),
3180
+ side: requireString(args, "side"),
3181
+ posSide: readString(args, "posSide"),
3182
+ ordType,
3183
+ sz: resolved.sz,
3184
+ tgtCcy: resolved.tgtCcy,
3185
+ stpMode: readString(args, "stpMode"),
3186
+ cxlOnClosePos: typeof cxlOnClosePos === "boolean" ? String(cxlOnClosePos) : void 0,
3187
+ reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
3188
+ clOrdId: readString(args, "clOrdId"),
3189
+ // Phase 3a+c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
3190
+ pxAmendType: readString(args, "pxAmendType"),
3191
+ tag: context.config.sourceTag
3192
+ });
3193
+ switch (ordType) {
3194
+ case "trigger":
3195
+ Object.assign(base, buildTriggerOrdTypeBody(args));
3196
+ break;
3197
+ case "chase":
3198
+ Object.assign(base, buildChaseOrdTypeBody(args));
3199
+ break;
3200
+ case "iceberg":
3201
+ case "twap":
3202
+ Object.assign(base, buildIcebergTwapOrdTypeBody(args));
3203
+ break;
3204
+ default:
3205
+ Object.assign(base, compactObject({
3206
+ ...buildAlgoConditionalCommonFields(args),
3207
+ callBackRatio: readString(args, "callbackRatio"),
3208
+ callBackSpread: readString(args, "callbackSpread")
3209
+ }));
3210
+ break;
3211
+ }
3009
3212
  const response = await context.client.privatePost(
3010
3213
  "/api/v5/trade/order-algo",
3011
- compactObject({
3012
- instId: requireString(args, "instId"),
3013
- tdMode: requireString(args, "tdMode"),
3014
- side: requireString(args, "side"),
3015
- posSide: readString(args, "posSide"),
3016
- ordType: requireString(args, "ordType"),
3017
- sz: resolved.sz,
3018
- tgtCcy: resolved.tgtCcy,
3019
- tpTriggerPx: readString(args, "tpTriggerPx"),
3020
- tpOrdPx: readString(args, "tpOrdPx"),
3021
- tpTriggerPxType: readString(args, "tpTriggerPxType"),
3022
- slTriggerPx: readString(args, "slTriggerPx"),
3023
- slOrdPx: readString(args, "slOrdPx"),
3024
- slTriggerPxType: readString(args, "slTriggerPxType"),
3025
- callBackRatio: readString(args, "callbackRatio"),
3026
- callBackSpread: readString(args, "callbackSpread"),
3027
- activePx: readString(args, "activePx"),
3028
- reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
3029
- clOrdId: readString(args, "clOrdId"),
3030
- tag: context.config.sourceTag
3031
- }),
3214
+ base,
3032
3215
  privateRateLimit("swap_place_algo_order", 20)
3033
3216
  );
3034
3217
  const result = normalizeResponse(response);
@@ -3253,7 +3436,7 @@ function registerFuturesAlgoTools() {
3253
3436
  {
3254
3437
  name: "futures_place_algo_order",
3255
3438
  module: "futures",
3256
- 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.",
3439
+ 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.",
3257
3440
  isWrite: true,
3258
3441
  inputSchema: {
3259
3442
  type: "object",
@@ -3279,8 +3462,8 @@ function registerFuturesAlgoTools() {
3279
3462
  },
3280
3463
  ordType: {
3281
3464
  type: "string",
3282
- enum: ["conditional", "oco", "move_order_stop"],
3283
- description: "conditional=single TP/SL or both; oco=TP+SL pair; move_order_stop=trailing stop"
3465
+ enum: ["conditional", "oco", "move_order_stop", "trigger", "chase", "iceberg", "twap"],
3466
+ 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"
3284
3467
  },
3285
3468
  sz: {
3286
3469
  type: "string",
@@ -3294,11 +3477,8 @@ function registerFuturesAlgoTools() {
3294
3477
  type: "string",
3295
3478
  description: "TP order price; -1=market (conditional/oco only)"
3296
3479
  },
3297
- tpTriggerPxType: {
3298
- type: "string",
3299
- enum: ["last", "index", "mark"],
3300
- description: "last(default)|index|mark (conditional/oco only)"
3301
- },
3480
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
3481
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
3302
3482
  slTriggerPx: {
3303
3483
  type: "string",
3304
3484
  description: "SL trigger price (conditional/oco only)"
@@ -3307,11 +3487,9 @@ function registerFuturesAlgoTools() {
3307
3487
  type: "string",
3308
3488
  description: "SL order price; -1=market (conditional/oco only)"
3309
3489
  },
3310
- slTriggerPxType: {
3311
- type: "string",
3312
- enum: ["last", "index", "mark"],
3313
- description: "last(default)|index|mark (conditional/oco only)"
3314
- },
3490
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
3491
+ stpMode: STP_MODE_SCHEMA,
3492
+ cxlOnClosePos: CXL_ON_CLOSE_POS_SCHEMA,
3315
3493
  callbackRatio: {
3316
3494
  type: "string",
3317
3495
  description: "Callback ratio (e.g. '0.01'=1%); provide either ratio or spread (move_order_stop only)"
@@ -3324,6 +3502,9 @@ function registerFuturesAlgoTools() {
3324
3502
  type: "string",
3325
3503
  description: "Activation price; tracking starts after market reaches this level (move_order_stop only)"
3326
3504
  },
3505
+ ...TRIGGER_FLAGS_SCHEMA,
3506
+ ...CHASE_FLAGS_SCHEMA,
3507
+ ...ICEBERG_TWAP_FLAGS_SCHEMA,
3327
3508
  tgtCcy: {
3328
3509
  type: "string",
3329
3510
  enum: ["base_ccy", "quote_ccy", "margin"],
@@ -3343,6 +3524,8 @@ function registerFuturesAlgoTools() {
3343
3524
  handler: async (rawArgs, context) => {
3344
3525
  const args = asRecord(rawArgs);
3345
3526
  const reduceOnly = args.reduceOnly;
3527
+ const cxlOnClosePos = args.cxlOnClosePos;
3528
+ const ordType = requireString(args, "ordType");
3346
3529
  const resolved = await resolveQuoteCcySz(
3347
3530
  requireString(args, "instId"),
3348
3531
  requireString(args, "sz"),
@@ -3351,29 +3534,44 @@ function registerFuturesAlgoTools() {
3351
3534
  context.client,
3352
3535
  readString(args, "tdMode")
3353
3536
  );
3537
+ const base = compactObject({
3538
+ instId: requireString(args, "instId"),
3539
+ tdMode: requireString(args, "tdMode"),
3540
+ side: requireString(args, "side"),
3541
+ posSide: readString(args, "posSide"),
3542
+ ordType,
3543
+ sz: resolved.sz,
3544
+ tgtCcy: resolved.tgtCcy,
3545
+ stpMode: readString(args, "stpMode"),
3546
+ cxlOnClosePos: typeof cxlOnClosePos === "boolean" ? String(cxlOnClosePos) : void 0,
3547
+ reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
3548
+ clOrdId: readString(args, "clOrdId"),
3549
+ // Phase 3a+c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
3550
+ pxAmendType: readString(args, "pxAmendType"),
3551
+ tag: context.config.sourceTag
3552
+ });
3553
+ switch (ordType) {
3554
+ case "trigger":
3555
+ Object.assign(base, buildTriggerOrdTypeBody(args));
3556
+ break;
3557
+ case "chase":
3558
+ Object.assign(base, buildChaseOrdTypeBody(args));
3559
+ break;
3560
+ case "iceberg":
3561
+ case "twap":
3562
+ Object.assign(base, buildIcebergTwapOrdTypeBody(args));
3563
+ break;
3564
+ default:
3565
+ Object.assign(base, compactObject({
3566
+ ...buildAlgoConditionalCommonFields(args),
3567
+ callBackRatio: readString(args, "callbackRatio"),
3568
+ callBackSpread: readString(args, "callbackSpread")
3569
+ }));
3570
+ break;
3571
+ }
3354
3572
  const response = await context.client.privatePost(
3355
3573
  "/api/v5/trade/order-algo",
3356
- compactObject({
3357
- instId: requireString(args, "instId"),
3358
- tdMode: requireString(args, "tdMode"),
3359
- side: requireString(args, "side"),
3360
- posSide: readString(args, "posSide"),
3361
- ordType: requireString(args, "ordType"),
3362
- sz: resolved.sz,
3363
- tgtCcy: resolved.tgtCcy,
3364
- tpTriggerPx: readString(args, "tpTriggerPx"),
3365
- tpOrdPx: readString(args, "tpOrdPx"),
3366
- tpTriggerPxType: readString(args, "tpTriggerPxType"),
3367
- slTriggerPx: readString(args, "slTriggerPx"),
3368
- slOrdPx: readString(args, "slOrdPx"),
3369
- slTriggerPxType: readString(args, "slTriggerPxType"),
3370
- callBackRatio: readString(args, "callbackRatio"),
3371
- callBackSpread: readString(args, "callbackSpread"),
3372
- activePx: readString(args, "activePx"),
3373
- reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
3374
- clOrdId: readString(args, "clOrdId"),
3375
- tag: context.config.sourceTag
3376
- }),
3574
+ base,
3377
3575
  privateRateLimit("futures_place_algo_order", 20)
3378
3576
  );
3379
3577
  const result = normalizeResponse(response);
@@ -5800,6 +5998,23 @@ function resolveOutcome(value) {
5800
5998
  }
5801
5999
  return resolved;
5802
6000
  }
6001
+ async function withConcurrency(items, maxConcurrency, fn) {
6002
+ const results = new Array(items.length);
6003
+ let nextIndex = 0;
6004
+ async function runNext() {
6005
+ while (nextIndex < items.length) {
6006
+ const idx = nextIndex++;
6007
+ try {
6008
+ results[idx] = { status: "fulfilled", value: await fn(items[idx]) };
6009
+ } catch (err) {
6010
+ results[idx] = { status: "rejected", reason: err };
6011
+ }
6012
+ }
6013
+ }
6014
+ const workers = Array.from({ length: Math.min(maxConcurrency, items.length) }, runNext);
6015
+ await Promise.all(workers);
6016
+ return results;
6017
+ }
5803
6018
  function filterBrowseCandidates(allSeries, underlyingFilter) {
5804
6019
  const isHumanReadable = (id) => /^(BTC|ETH|TRX|EOS|SOL|IOTA|KISHU|SUSHI|BTG|XTZ|SOLVU)-/.test(id);
5805
6020
  const seen = /* @__PURE__ */ new Set();
@@ -5940,6 +6155,7 @@ function handlePlaceOrderError(base, rawArgs, endpoint) {
5940
6155
  }
5941
6156
  throw new OkxApiError(`[${sCode}] ${sMsg}`, { code: sCode, endpoint });
5942
6157
  }
6158
+ var MAX_CONCURRENT_MARKET_FETCHES = 8;
5943
6159
  var OUTCOME_SCHEMA = {
5944
6160
  type: "string",
5945
6161
  enum: ["UP", "YES", "DOWN", "NO"],
@@ -5979,10 +6195,15 @@ function registerEventContractTools() {
5979
6195
  const normalizedSeries = normalizeResponse(seriesResp);
5980
6196
  const allSeries = Array.isArray(normalizedSeries["data"]) ? normalizedSeries["data"] : [];
5981
6197
  const candidates = filterBrowseCandidates(allSeries, underlyingFilter);
5982
- const marketResults = await Promise.all(
5983
- candidates.map((s) => fetchActiveContractsForSeries(context.client, s))
6198
+ const settled = await withConcurrency(
6199
+ candidates,
6200
+ MAX_CONCURRENT_MARKET_FETCHES,
6201
+ (s) => fetchActiveContractsForSeries(context.client, s)
5984
6202
  );
5985
- const results = marketResults.filter(Boolean);
6203
+ const fulfilled = settled.filter(
6204
+ (r) => r.status === "fulfilled" && r.value !== null
6205
+ );
6206
+ const results = fulfilled.map((r) => r.value);
5986
6207
  return {
5987
6208
  data: results,
5988
6209
  total: results.reduce((n, r) => n + (r?.contracts?.length ?? 0), 0)
@@ -6402,379 +6623,1106 @@ function registerEventContractTools() {
6402
6623
  }
6403
6624
  ];
6404
6625
  }
6626
+ var SMARTMONEY_RPS = 5;
6405
6627
  var PATH_LEADERBOARD = "/api/v5/orbit/public/leaderboard";
6406
6628
  var PATH_POSITION_CURRENT = "/api/v5/orbit/public/position-current";
6629
+ var PATH_POSITION_HISTORY = "/api/v5/orbit/public/position-history";
6407
6630
  var PATH_TRADE_RECORDS = "/api/v5/orbit/public/trade-records";
6631
+ var PATH_TOP_TRADER_SEARCH = "/api/v5/orbit/top-trader-search";
6408
6632
  var PATH_OVERVIEW = "/api/v5/journal/smartmoney/overview";
6409
- var PATH_SIGNAL = "/api/v5/journal/smartmoney/signal";
6410
6633
  var PATH_SIGNAL_HISTORY = "/api/v5/journal/smartmoney/signal-history";
6634
+ var PERIOD_DAYS = ["3", "7", "30", "90"];
6411
6635
  var SIGNAL_POOL_FILTER_PROPS = {
6412
- sortType: {
6636
+ sortBy: {
6413
6637
  type: "string",
6414
- description: "Pool ranking: pnl|pnlRatio (default pnl)"
6638
+ enum: ["pnl", "pnlRatio"],
6639
+ default: "pnl",
6640
+ 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)."
6415
6641
  },
6416
6642
  period: {
6417
6643
  type: "string",
6418
- description: "Win-rate window days: 3|7|30|90 (default 90). Not snapshot range."
6644
+ enum: PERIOD_DAYS,
6645
+ default: "7",
6646
+ description: 'Lookback window in days. Pass as a quoted string: `"3"` / `"7"` / `"30"` / `"90"` (NOT integer 7). Drives capability metrics (avgLongWinRate / avgShortWinRate) and the `winRateTier` filter. Does NOT affect signal fields (which always use the latest snapshot).'
6419
6647
  },
6420
- pnl: {
6648
+ pnlTier: {
6421
6649
  type: "string",
6422
- description: "Top N% by PnL: PNL_ANY|PNL_TOP50|PNL_TOP20|PNL_TOP5 (default PNL_ANY)"
6650
+ enum: ["PNL_ANY", "PNL_TOP50", "PNL_TOP20", "PNL_TOP5"],
6651
+ default: "PNL_ANY",
6652
+ description: "PnL percentile gate applied on top of `sortBy`. ANY = no filter; TOP50 = PnL \u2265 P50 (median); TOP20 = \u2265 P80; TOP5 = \u2265 P95. PnL distribution is long-tailed \u2014 use percentile, not absolute thresholds."
6423
6653
  },
6424
- winRatio: {
6654
+ winRateTier: {
6425
6655
  type: "string",
6426
- description: "Min win-rate: WR_ANY|WR_GE_50|WR_GE_80 (default WR_ANY)"
6656
+ enum: ["WR_ANY", "WR_GE_50", "WR_GE_80"],
6657
+ default: "WR_ANY",
6658
+ description: "Minimum win-rate gate (fixed thresholds). ANY = no filter; WR_GE_50 = \u2265 50%; WR_GE_80 = \u2265 80%."
6427
6659
  },
6428
- maxRetreat: {
6660
+ maxDrawdownTier: {
6429
6661
  type: "string",
6430
- description: "Max drawdown: MR_ANY|MR_LE_20|MR_LE_50 (default MR_ANY)"
6662
+ enum: ["MR_ANY", "MR_LE_20", "MR_LE_50"],
6663
+ default: "MR_ANY",
6664
+ description: "Maximum-drawdown gate (fixed thresholds; smaller drawdown = lower risk). ANY = no filter; MR_LE_20 = drawdown \u2264 20%; MR_LE_50 = \u2264 50%."
6431
6665
  },
6432
- asset: {
6666
+ aumTier: {
6433
6667
  type: "string",
6434
- description: "Top N% by AUM: AUM_ANY|AUM_TOP50|AUM_TOP20|AUM_TOP5 (default AUM_ANY)"
6668
+ enum: ["AUM_ANY", "AUM_TOP50", "AUM_TOP20", "AUM_TOP5"],
6669
+ default: "AUM_ANY",
6670
+ description: "AUM (Assets Under Management) percentile gate. ANY = no filter; TOP50 = AUM \u2265 P50; TOP20 = \u2265 P80; TOP5 = \u2265 P95. AUM is long-tailed \u2014 use percentile, not absolute USD."
6435
6671
  }
6436
6672
  };
6437
6673
  var LEADERBOARD_POOL_FILTER_PROPS = {
6438
- sortType: {
6674
+ sortBy: {
6439
6675
  type: "string",
6440
- description: "pnl or pnl_ratio"
6676
+ enum: ["pnl", "pnlRatio"],
6677
+ default: "pnl",
6678
+ description: "Leaderboard sort key. `pnl` = absolute USD profit; `pnlRatio` = percentage return."
6441
6679
  },
6442
6680
  period: {
6443
6681
  type: "string",
6444
- description: "3|7|30|90 days, empty=all"
6682
+ enum: PERIOD_DAYS,
6683
+ default: "90",
6684
+ description: 'Performance lookback window in days. Pass as a quoted string: `"3"` / `"7"` / `"30"` / `"90"` (NOT integer 90). Default `"90"` (matches leaderboard UI). Filters AND ranks traders by their PnL over that window.'
6445
6685
  },
6446
- pnl: {
6686
+ minPnl: {
6447
6687
  type: "string",
6448
- description: "Min PnL USD"
6688
+ description: 'Minimum absolute PnL in USD. Pass as a quoted numeric string, e.g. `"10000"` (NOT integer 10000) \u2192 traders with PnL \u2265 $10,000. Numeric threshold \u2014 distinct from the signal-side `pnlTier` percentile enum.'
6449
6689
  },
6450
- winRatio: {
6690
+ minWinRate: {
6451
6691
  type: "string",
6452
- description: "Min ratio (0.8=80%)"
6692
+ description: 'Minimum win-rate as decimal in 0~1 range. Pass as a quoted numeric string, e.g. `"0.8"` (NOT number 0.8) \u2192 traders with win-rate \u2265 80%. Numeric threshold \u2014 distinct from the signal-side `winRateTier` enum.'
6453
6693
  },
6454
- maxRetreat: {
6694
+ maxDrawdown: {
6455
6695
  type: "string",
6456
- description: "Max DD (0.1=10%)"
6696
+ description: 'Maximum drawdown as decimal. Pass as a quoted numeric string, e.g. `"0.1"` (NOT number 0.1) \u2192 traders with drawdown \u2264 10%. Lower = lower risk. Numeric threshold \u2014 distinct from the signal-side `maxDrawdownTier` enum.'
6457
6697
  },
6458
- asset: {
6698
+ minAum: {
6459
6699
  type: "string",
6460
- description: "Min AUM USD"
6700
+ description: 'Minimum AUM (Assets Under Management) in USD. Pass as a quoted numeric string, e.g. `"1000"` (NOT integer 1000) \u2192 traders with AUM \u2265 $1,000. Numeric threshold \u2014 distinct from the signal-side `aumTier` percentile enum.'
6461
6701
  }
6462
6702
  };
6463
- var POOL_FILTER_KEYS = ["sortType", "period", "pnl", "winRatio", "maxRetreat", "asset"];
6703
+ var LEADERBOARD_FILTER_UPSTREAM_NAMES = {
6704
+ sortBy: "sortBy",
6705
+ period: "period",
6706
+ minPnl: "pnl",
6707
+ minWinRate: "winRate",
6708
+ maxDrawdown: "maxDrawdown",
6709
+ minAum: "asset"
6710
+ };
6464
6711
  function readPoolFilters(args) {
6465
6712
  const result = {};
6466
- for (const key of POOL_FILTER_KEYS) {
6713
+ for (const [publicKey, upstreamKey] of Object.entries(LEADERBOARD_FILTER_UPSTREAM_NAMES)) {
6714
+ const val = readString(args, publicKey);
6715
+ if (val !== void 0 && val !== "") result[upstreamKey] = val;
6716
+ }
6717
+ return result;
6718
+ }
6719
+ function readSignalPoolFilters(args) {
6720
+ const result = {};
6721
+ for (const key of Object.keys(SIGNAL_POOL_FILTER_PROPS)) {
6467
6722
  const val = readString(args, key);
6468
6723
  if (val) result[key] = val;
6469
6724
  }
6470
6725
  return result;
6471
6726
  }
6472
- function extractLeaderboardData(data) {
6473
- if (Array.isArray(data)) return data;
6727
+ function extractLeaderboardEnvelope(data) {
6728
+ if (Array.isArray(data)) return { items: data };
6474
6729
  if (data && typeof data === "object") {
6475
- const inner = data.data;
6476
- if (Array.isArray(inner)) return inner;
6730
+ const obj = data;
6731
+ const inner = obj.data;
6732
+ const updateTime = typeof obj.updateTime === "string" ? obj.updateTime : void 0;
6733
+ if (Array.isArray(inner)) return { items: inner, updateTime };
6734
+ }
6735
+ return { items: [] };
6736
+ }
6737
+ function deriveDirection(posSide, pos) {
6738
+ if (posSide === "long") return "long";
6739
+ if (posSide === "short") return "short";
6740
+ if (posSide === "both" && typeof pos === "string" && pos !== "") {
6741
+ const n = Number(pos);
6742
+ if (Number.isFinite(n) && n !== 0) return n > 0 ? "long" : "short";
6743
+ }
6744
+ return void 0;
6745
+ }
6746
+ function extractPositionData(data) {
6747
+ if (!Array.isArray(data) || data.length === 0) return [];
6748
+ const first = data[0];
6749
+ if (first && typeof first === "object") {
6750
+ const posData = first.posData;
6751
+ if (Array.isArray(posData)) {
6752
+ return posData.map((row) => {
6753
+ if (!row || typeof row !== "object") return row;
6754
+ const r = row;
6755
+ const direction = deriveDirection(r.posSide, r.pos);
6756
+ return direction ? { ...r, direction } : r;
6757
+ });
6758
+ }
6477
6759
  }
6478
6760
  return [];
6479
6761
  }
6762
+ function buildPagination(data, effectiveLimit, cursorField) {
6763
+ const hasMore = data.length >= effectiveLimit;
6764
+ const last = data.length > 0 ? data[data.length - 1] : void 0;
6765
+ const nextAfter = hasMore && last && typeof last === "object" && last !== null ? last[cursorField] : void 0;
6766
+ return nextAfter !== void 0 ? { hasMore, nextAfter } : { hasMore };
6767
+ }
6768
+ function extractBaseCcy(instId) {
6769
+ if (!instId) return void 0;
6770
+ const idx = instId.indexOf("-");
6771
+ const base = idx === -1 ? instId : instId.slice(0, idx);
6772
+ return base || void 0;
6773
+ }
6774
+ function actionableError(message, hint) {
6775
+ return new ValidationError(`${message} ${hint}`);
6776
+ }
6777
+ function readArrayAsCsv(args, key) {
6778
+ const arr = readStringArray(args, key);
6779
+ if (!arr || arr.length === 0) return void 0;
6780
+ return arr.join(",");
6781
+ }
6782
+ function envelope(dataSchema, extras = {}) {
6783
+ return {
6784
+ type: "object",
6785
+ properties: {
6786
+ endpoint: {
6787
+ type: "string",
6788
+ description: "Upstream API path that produced this payload (debug/audit aid)."
6789
+ },
6790
+ requestTime: { type: "string", description: "ISO-8601 timestamp when the request was issued." },
6791
+ data: dataSchema,
6792
+ ...extras
6793
+ },
6794
+ required: ["endpoint", "data"]
6795
+ };
6796
+ }
6797
+ var PAGINATION_PROP = {
6798
+ type: "object",
6799
+ description: "Cursor pagination metadata. Only emitted on list tools that support paging.",
6800
+ properties: {
6801
+ hasMore: {
6802
+ type: "boolean",
6803
+ description: "True when `data.length` reached the requested `limit` (more pages likely). False guarantees there are no more results after this page."
6804
+ },
6805
+ nextAfter: {
6806
+ type: "string",
6807
+ description: "Cursor to pass back as `after` on the next call to fetch the following page. Absent/empty when `hasMore` is false."
6808
+ }
6809
+ }
6810
+ };
6811
+ var TRADER_ITEM_PROPS = {
6812
+ authorId: { type: "string", description: "Trader's unique ID \u2014 pass to other smartmoney_get_trader_* tools." },
6813
+ nickName: { type: "string", description: "Display nickname." },
6814
+ pnl: { type: "string", description: "Absolute PnL in USD over the requested `period` (numeric string)." },
6815
+ pnlRatio: { type: "string", description: 'PnL as a decimal ratio over the requested `period` (e.g. "0.35" = +35%).' },
6816
+ asset: { type: "string", description: "AUM (Assets Under Management) in USD \u2014 same field that the input `minAum` filter applies to." },
6817
+ winRate: { type: "string", description: "Lifetime win-rate as a decimal (0~1)." },
6818
+ maxDrawdown: { type: "string", description: 'Max drawdown as a decimal (e.g. "0.2" = 20%). Lower = lower risk.' },
6819
+ onboardDuration: { type: "string", description: "Days the trader has been onboarded on the leaderboard (numeric string)." },
6820
+ portrait: { type: "string", description: "Avatar image URL." },
6821
+ rates: {
6822
+ type: "array",
6823
+ description: "Equity-curve / PnL-rate time series (one entry per day).",
6824
+ items: {
6825
+ type: "object",
6826
+ properties: {
6827
+ statTime: { type: "string", description: 'Date stamp in `YYMMDD` (e.g. "240726" = 2024-07-26).' },
6828
+ value: { type: "string", description: "Cumulative PnL ratio at that day (decimal)." }
6829
+ }
6830
+ }
6831
+ }
6832
+ };
6833
+ var SIGNAL_ITEM_PROPS = {
6834
+ ccy: { type: "string", description: "Instrument ID e.g. BTC-USDT-SWAP (outer identifier; the field name is `ccy`, not `instId`)." },
6835
+ dataVersion: {
6836
+ type: "string",
6837
+ description: "Snapshot version key in `yyyyMMddHH` UTC (10 digits, e.g. `2026043014` = 2026-04-30 14:00 UTC; floored to the hour)."
6838
+ },
6839
+ tradersWithPosition: {
6840
+ type: "integer",
6841
+ description: "Pool traders holding this asset at latest_snap (double-sided counted once). Higher = stronger consensus."
6842
+ },
6843
+ tradersQualified: {
6844
+ type: "integer",
6845
+ description: "Pool size after applying tier filters (incl. those without positions on this instrument)."
6846
+ },
6847
+ longTraders: { type: "integer", description: "Number of pool traders currently long this asset (incl. double-sided)." },
6848
+ shortTraders: { type: "integer", description: "Number of pool traders currently short this asset (incl. double-sided)." },
6849
+ notional: {
6850
+ type: "object",
6851
+ description: "Notional / capital-flow group.",
6852
+ properties: {
6853
+ longNotionalUsdt: { type: "string", description: "Sum of long-side notional in USDT." },
6854
+ shortNotionalUsdt: { type: "string", description: "Sum of short-side notional in USDT." },
6855
+ netNotionalUsdt: { type: "string", description: "Net directional notional in USDT = long \u2212 short." },
6856
+ totalNotionalUsdt: { type: "string", description: "Gross notional in USDT = long + short." },
6857
+ totalNotionalVs24h: {
6858
+ type: "string",
6859
+ description: "Capital-flow change ratio vs 24h: (curr \u2212 hist_24h) / hist_24h. Positive = smart money adding exposure; negative = retreating. NULL when hist=0."
6860
+ },
6861
+ smartMoneyLongAvgEntry: {
6862
+ type: "string",
6863
+ description: "Long-side notional-weighted average entry price (USDT). NULL when no long. Compare to current price to judge whether following the longs is cheap/expensive now."
6864
+ },
6865
+ smartMoneyShortAvgEntry: {
6866
+ type: "string",
6867
+ description: "Short-side notional-weighted average entry price (USDT). NULL when no short."
6868
+ }
6869
+ }
6870
+ },
6871
+ longShortRatio: {
6872
+ type: "object",
6873
+ description: "Long/short ratio + historical-delta group.",
6874
+ properties: {
6875
+ longRatioVs1h: { type: "string", description: "longRatio \u2212 hist_1h.longRatio. NULL when no hist." },
6876
+ longRatioVs24h: { type: "string", description: "longRatio \u2212 hist_24h.longRatio. NULL when no hist." },
6877
+ longRatioVs7d: { type: "string", description: "longRatio \u2212 hist_7d.longRatio. NULL when no hist." },
6878
+ longRatio: { type: "string", description: "Headcount long ratio = longTraders / tradersWithPosition. NULL when no traders." },
6879
+ shortRatio: { type: "string", description: "1 \u2212 longRatio. NULL when no traders." },
6880
+ weightedLongRatio: {
6881
+ type: "string",
6882
+ description: "Notional-weighted long ratio = \u03A3(long_notional) / \u03A3(notional). NULL when no notional."
6883
+ },
6884
+ weightedShortRatio: {
6885
+ type: "string",
6886
+ description: "Notional-weighted short ratio = \u03A3(short_notional) / \u03A3(notional). NULL when no notional."
6887
+ }
6888
+ }
6889
+ },
6890
+ winRate: {
6891
+ type: "object",
6892
+ description: "Capability (historical performance) group; driven by the `period` window.",
6893
+ properties: {
6894
+ avgLongWinRate: {
6895
+ type: "string",
6896
+ 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."
6897
+ },
6898
+ avgShortWinRate: {
6899
+ type: "string",
6900
+ description: "Mean closed-position win-rate (full-market) over `period` days for users currently short. NULL when sample is below threshold."
6901
+ }
6902
+ }
6903
+ }
6904
+ };
6905
+ var SIGNAL_HISTORY_ITEM_PROPS = {
6906
+ ccy: { type: "string", description: "Base currency / instrument key for this bucket." },
6907
+ longRatio: {
6908
+ type: "string",
6909
+ description: "Headcount long ratio at this bucket. Decimal 0~1."
6910
+ },
6911
+ shortRatio: {
6912
+ type: "string",
6913
+ description: "Headcount short ratio at this bucket = 1 \u2212 longRatio. Decimal 0~1."
6914
+ },
6915
+ weightedLongRatio: {
6916
+ type: "string",
6917
+ description: "Notional-weighted long ratio at this bucket. Decimal 0~1."
6918
+ },
6919
+ weightedShortRatio: {
6920
+ type: "string",
6921
+ description: "Notional-weighted short ratio at this bucket. Decimal 0~1."
6922
+ },
6923
+ longTraders: {
6924
+ type: "integer",
6925
+ description: "Number of traders with long exposure at this bucket (includes dual-side traders)."
6926
+ },
6927
+ shortTraders: {
6928
+ type: "integer",
6929
+ description: "Number of traders with short exposure at this bucket (includes dual-side traders)."
6930
+ },
6931
+ tradersWithPosition: {
6932
+ type: "integer",
6933
+ description: "Pool traders holding this asset at this bucket. Few traders = unreliable signal."
6934
+ },
6935
+ netNotionalUsdt: {
6936
+ type: "string",
6937
+ description: "Net directional notional in USDT at this bucket = long notional \u2212 short notional."
6938
+ },
6939
+ totalNotionalUsdt: {
6940
+ type: "string",
6941
+ description: "Gross notional in USDT at this bucket = long notional + short notional. Tracks total capital deployed (rising = adding, falling = retreating)."
6942
+ },
6943
+ tradersQualified: { type: "integer", description: "Pool size after applying tier filters (includes traders without a position)." },
6944
+ dataVersion: { type: "string", description: "Snapshot version key in `yyyyMMddHH` UTC (10-digit, e.g. `2026042820`)." }
6945
+ };
6480
6946
  function registerSmartmoneyTools() {
6481
6947
  const tools = [
6482
- /* ---------- 1. Overview ---------- */
6948
+ /* ===================================================== */
6949
+ /* Trader family (6) */
6950
+ /* ===================================================== */
6951
+ /* ---------- T1. Top traders (leaderboard rank) ---------- */
6483
6952
  {
6484
- name: "smartmoney_get_overview",
6953
+ name: "smartmoney_get_traders_by_filter",
6485
6954
  module: "smartmoney",
6486
- 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.",
6955
+ 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.",
6487
6956
  isWrite: false,
6957
+ outputSchema: envelope(
6958
+ { type: "array", items: { type: "object", properties: TRADER_ITEM_PROPS } },
6959
+ {
6960
+ updateTime: {
6961
+ type: "string",
6962
+ 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."
6963
+ },
6964
+ pagination: PAGINATION_PROP
6965
+ }
6966
+ ),
6488
6967
  inputSchema: {
6489
6968
  type: "object",
6490
6969
  properties: {
6491
- ts: {
6492
- type: "string",
6493
- description: "Recommended. Timestamp ms \u2014 use Date.now() for latest."
6494
- },
6495
- dataVersion: {
6496
- type: "string",
6497
- description: "Alternative. yyyyMMddHHmm UTC for prior snapshot. If both sent, ts wins."
6498
- },
6499
- instType: {
6500
- type: "string",
6501
- description: "SPOT|MARGIN|FUTURES|SWAP|OPTION (default SWAP)"
6502
- },
6503
- ...SIGNAL_POOL_FILTER_PROPS,
6504
- lmtNum: {
6970
+ updateTime: {
6505
6971
  type: "string",
6506
- description: "Trader pool size 1-500 (default 100)"
6972
+ description: 'Snapshot version key. Pass as a quoted 12-digit string `yyyyMMddHHmm` (UTC+8), e.g. `"202604301815"` (NOT integer). Omit to query the latest snapshot (refreshed every ~5 min).'
6507
6973
  },
6508
- instCcyList: {
6974
+ ...LEADERBOARD_POOL_FILTER_PROPS,
6975
+ after: {
6509
6976
  type: "string",
6510
- description: "Comma-separated currency codes e.g. BTC,ETH,SOL (prefix-matched against instId)"
6977
+ description: "Cursor for paginating backwards (older page). Pass the `authorId` of the last item from the previous page verbatim as a quoted string (NOT a number). Cursor anchors on `authorId` while preserving the current `sortBy` order."
6511
6978
  },
6512
- instCcy: {
6979
+ before: {
6513
6980
  type: "string",
6514
- description: "Single currency e.g. BTC; alias for instCcyList (instCcyList wins if both set)"
6981
+ description: "Cursor for paginating forwards (newer page). Pass the `authorId` of the first item from the previous page verbatim as a quoted string. Cursor anchors on `authorId` while preserving the current `sortBy` order."
6515
6982
  },
6516
- topInstruments: {
6517
- type: "string",
6518
- description: "Top N instruments 1-100 (default 20)"
6983
+ limit: {
6984
+ type: "integer",
6985
+ minimum: 1,
6986
+ maximum: 100,
6987
+ default: 10,
6988
+ description: "Max results per page (default 10, max 100)."
6519
6989
  }
6520
6990
  }
6521
6991
  },
6522
6992
  handler: async (rawArgs, context) => {
6523
6993
  const args = asRecord(rawArgs);
6524
- const dv = readString(args, "dataVersion");
6525
- const ts = readString(args, "ts");
6526
- if (!dv && !ts) {
6527
- throw new ValidationError('Either "dataVersion" or "ts" is required for smartmoney_get_overview.');
6528
- }
6994
+ const limit = readNumber(args, "limit");
6529
6995
  const response = await context.client.privateGet(
6530
- PATH_OVERVIEW,
6996
+ PATH_LEADERBOARD,
6531
6997
  compactObject({
6532
- dataVersion: dv,
6533
- ts,
6534
- instType: readString(args, "instType"),
6998
+ updateTime: readString(args, "updateTime"),
6535
6999
  ...readPoolFilters(args),
6536
- lmtNum: readString(args, "lmtNum"),
6537
- instCcyList: readString(args, "instCcyList"),
6538
- instCcy: readString(args, "instCcy"),
6539
- topInstruments: readString(args, "topInstruments")
7000
+ after: readString(args, "after"),
7001
+ before: readString(args, "before"),
7002
+ limit
6540
7003
  }),
6541
- publicRateLimit("smartmoney_get_overview", 5)
7004
+ publicRateLimit("smartmoney_get_traders_by_filter", SMARTMONEY_RPS)
6542
7005
  );
6543
- return normalizeResponse(response);
7006
+ const normalized = normalizeResponse(response);
7007
+ const { items, updateTime } = extractLeaderboardEnvelope(normalized.data);
7008
+ return {
7009
+ ...normalized,
7010
+ data: items,
7011
+ ...updateTime ? { updateTime } : {},
7012
+ pagination: buildPagination(items, limit ?? 10, "authorId")
7013
+ };
6544
7014
  }
6545
7015
  },
6546
- /* ---------- 2. Signal ---------- */
7016
+ /* ---------- T2. Trader performance (by authorIds) ---------- */
6547
7017
  {
6548
- name: "smartmoney_get_signal",
7018
+ name: "smartmoney_get_performance_by_trader",
6549
7019
  module: "smartmoney",
6550
- 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.",
7020
+ 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).",
6551
7021
  isWrite: false,
7022
+ outputSchema: envelope(
7023
+ { type: "array", items: { type: "object", properties: TRADER_ITEM_PROPS } },
7024
+ {
7025
+ updateTime: {
7026
+ type: "string",
7027
+ 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."
7028
+ }
7029
+ }
7030
+ ),
6552
7031
  inputSchema: {
6553
7032
  type: "object",
6554
7033
  properties: {
6555
- instId: {
6556
- type: "string",
6557
- description: "Recommended. e.g. BTC-USDT-SWAP"
6558
- },
6559
- instCcy: {
6560
- type: "string",
6561
- description: "e.g. BTC (SPOT/SWAP only); instId takes precedence if both set"
6562
- },
6563
- ts: {
6564
- type: "string",
6565
- description: "Recommended. Timestamp ms \u2014 use Date.now() for latest."
6566
- },
6567
- dataVersion: {
6568
- type: "string",
6569
- description: "Alternative. yyyyMMddHHmm UTC for prior snapshot. If both sent, ts wins."
6570
- },
6571
- ...SIGNAL_POOL_FILTER_PROPS,
6572
- lmtNum: {
6573
- type: "string",
6574
- description: "Trader pool size 1-500 (default 100)"
6575
- },
6576
7034
  authorIds: {
7035
+ type: "array",
7036
+ items: { type: "string" },
7037
+ minItems: 1,
7038
+ description: 'Trader IDs to look up, e.g. `["1001", "1002"]`. Required.'
7039
+ },
7040
+ period: {
6577
7041
  type: "string",
6578
- description: "Comma-separated user IDs e.g. 1001,1002 \u2014 restricts the trader pool to these IDs only (precise filter)"
7042
+ enum: PERIOD_DAYS,
7043
+ default: "90",
7044
+ description: 'Performance lookback window in days. Pass as a quoted string: `"3"` / `"7"` / `"30"` / `"90"` (NOT integer). Default `"90"`.'
6579
7045
  }
6580
- }
7046
+ },
7047
+ required: ["authorIds"]
6581
7048
  },
6582
7049
  handler: async (rawArgs, context) => {
6583
7050
  const args = asRecord(rawArgs);
6584
- const instId = readString(args, "instId");
6585
- const instCcy = readString(args, "instCcy");
6586
- if (!instId && !instCcy) {
6587
- throw new ValidationError('Either "instId" or "instCcy" is required for smartmoney_get_signal.');
6588
- }
6589
- const dv = readString(args, "dataVersion");
6590
- const ts = readString(args, "ts");
6591
- if (!dv && !ts) {
6592
- throw new ValidationError('Either "dataVersion" or "ts" is required for smartmoney_get_signal.');
7051
+ const authorIds = readArrayAsCsv(args, "authorIds");
7052
+ if (!authorIds) {
7053
+ throw actionableError(
7054
+ '"authorIds" is required and must be a non-empty array.',
7055
+ 'Discover trader IDs via `smartmoney_get_traders_by_filter`, then pass them as an array (e.g. ["1001", "1002"]).'
7056
+ );
6593
7057
  }
6594
7058
  const response = await context.client.privateGet(
6595
- PATH_SIGNAL,
7059
+ PATH_LEADERBOARD,
6596
7060
  compactObject({
6597
- instId,
6598
- instCcy,
6599
- dataVersion: dv,
6600
- ts,
6601
- ...readPoolFilters(args),
6602
- lmtNum: readString(args, "lmtNum"),
6603
- authorIds: readString(args, "authorIds")
7061
+ authorIds,
7062
+ period: readString(args, "period")
6604
7063
  }),
6605
- publicRateLimit("smartmoney_get_signal", 5)
7064
+ publicRateLimit("smartmoney_get_performance_by_trader", SMARTMONEY_RPS)
6606
7065
  );
6607
- return normalizeResponse(response);
7066
+ const normalized = normalizeResponse(response);
7067
+ const { items, updateTime } = extractLeaderboardEnvelope(normalized.data);
7068
+ return {
7069
+ ...normalized,
7070
+ data: items,
7071
+ ...updateTime ? { updateTime } : {}
7072
+ };
6608
7073
  }
6609
7074
  },
6610
- /* ---------- 3. Signal History ---------- */
7075
+ /* ---------- T3. Trader current positions ---------- */
6611
7076
  {
6612
- name: "smartmoney_get_signal_history",
7077
+ name: "smartmoney_get_trader_positions",
6613
7078
  module: "smartmoney",
6614
- 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.",
7079
+ 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).",
6615
7080
  isWrite: false,
7081
+ outputSchema: envelope({
7082
+ type: "array",
7083
+ items: {
7084
+ type: "object",
7085
+ properties: {
7086
+ posId: { type: "string", description: "Unique position ID. Stable across the position's lifetime." },
7087
+ instId: { type: "string", description: "Instrument ID e.g. BTC-USDT-SWAP." },
7088
+ instType: {
7089
+ type: "string",
7090
+ description: "Instrument business line: `SWAP` (perpetual) | `SPOT` | `FUTURES` (delivery) | `MARGIN` | `OPTION`."
7091
+ },
7092
+ posSide: {
7093
+ type: "string",
7094
+ description: "Raw upstream position direction. `long` = long-side position (buy-to-open); `short` = short-side position (sell-to-open); `both` = net/one-way position mode where the sign of `pos` encodes direction. Prefer the derived `direction` field below for agent logic."
7095
+ },
7096
+ direction: {
7097
+ type: "string",
7098
+ enum: ["long", "short"],
7099
+ description: 'Derived clean direction (`long` | `short`) \u2014 handler computes this from `posSide` + sign of `pos` so agents do not have to branch on the `posSide="both"` net-mode case.'
7100
+ },
7101
+ posCcy: { type: "string", description: 'Position currency \u2014 the asset being held, e.g. "BTC".' },
7102
+ quoteCcy: { type: "string", description: 'Quote currency the position is priced/settled in, e.g. "USDT".' },
7103
+ pos: {
7104
+ type: "string",
7105
+ description: "Position size (numeric string). Unit depends on instType: coins for SPOT/MARGIN, contracts (\u5F20) for SWAP/FUTURES/OPTION."
7106
+ },
7107
+ lever: { type: "string", description: 'Leverage multiplier (numeric string; "1" for spot).' },
7108
+ avgPx: { type: "string", description: "Volume-weighted average entry price (numeric string)." },
7109
+ last: { type: "string", description: "Latest market/mark price for the instrument (numeric string)." },
7110
+ notionalUsd: { type: "string", description: "Current position notional value in USD." },
7111
+ upl: { type: "string", description: "Unrealized (floating) PnL, denominated in `quoteCcy`." },
7112
+ pnl: { type: "string", description: "Realized PnL accrued on this position so far, denominated in `quoteCcy`." },
7113
+ cTime: { type: "string", description: "Position open time as Unix milliseconds (numeric string)." },
7114
+ positionIntensity: {
7115
+ type: "string",
7116
+ 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."
7117
+ }
7118
+ }
7119
+ }
7120
+ }),
6616
7121
  inputSchema: {
6617
7122
  type: "object",
6618
7123
  properties: {
6619
- instId: {
7124
+ authorId: {
6620
7125
  type: "string",
6621
- description: "e.g. BTC-USDT-SWAP"
7126
+ description: "Trader's unique ID (obtain from `smartmoney_get_traders_by_filter`)."
6622
7127
  },
6623
- ts: {
7128
+ instId: {
7129
+ type: "string",
7130
+ 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.'
7131
+ }
7132
+ },
7133
+ required: ["authorId"]
7134
+ },
7135
+ handler: async (rawArgs, context) => {
7136
+ const args = asRecord(rawArgs);
7137
+ const authorId = readString(args, "authorId");
7138
+ if (!authorId) {
7139
+ throw actionableError(
7140
+ '"authorId" is required.',
7141
+ "Discover trader IDs via `smartmoney_get_traders_by_filter` and pass one ID here."
7142
+ );
7143
+ }
7144
+ const response = await context.client.privateGet(
7145
+ PATH_POSITION_CURRENT,
7146
+ compactObject({
7147
+ authorId,
7148
+ instCcy: extractBaseCcy(readString(args, "instId"))
7149
+ }),
7150
+ publicRateLimit("smartmoney_get_trader_positions", SMARTMONEY_RPS)
7151
+ );
7152
+ const normalized = normalizeResponse(response);
7153
+ return { ...normalized, data: extractPositionData(normalized.data) };
7154
+ }
7155
+ },
7156
+ /* ---------- T4. Trader closed-position history ---------- */
7157
+ {
7158
+ name: "smartmoney_get_trader_positions_history",
7159
+ module: "smartmoney",
7160
+ 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).",
7161
+ isWrite: false,
7162
+ outputSchema: envelope(
7163
+ {
7164
+ type: "array",
7165
+ items: {
7166
+ type: "object",
7167
+ properties: {
7168
+ posId: { type: "string", description: "Unique closed-position ID. Use as the `after` / `before` cursor when paginating." },
7169
+ instId: { type: "string", description: "Instrument ID e.g. BTC-USDT-SWAP." },
7170
+ instType: {
7171
+ type: "string",
7172
+ description: "Instrument business line: `SWAP` (perpetual) | `FUTURES` (delivery) | `MARGIN` | `SPOT`."
7173
+ },
7174
+ ctVal: {
7175
+ type: "string",
7176
+ description: "Contract face value \u2014 USD value of a single contract (\u5F20). Numeric string. Empty/0 for non-contract instruments."
7177
+ },
7178
+ posSide: {
7179
+ type: "string",
7180
+ description: "Position direction at close. `long` = was long-side; `short` = was short-side."
7181
+ },
7182
+ lever: { type: "string", description: "Leverage multiplier used for this position (numeric string)." },
7183
+ mgnMode: {
7184
+ type: "string",
7185
+ description: "Margin mode used for this position. `cross` = cross-margin (shared collateral pool); `isolated` = isolated-margin (per-position collateral)."
7186
+ },
7187
+ marginCcy: {
7188
+ type: "string",
7189
+ description: 'Margin currency held as collateral for this position, e.g. "BTC" or "USDT".'
7190
+ },
7191
+ quoteCcy: { type: "string", description: 'Quote currency the position settled in, e.g. "USDT".' },
7192
+ openAvgPx: { type: "string", description: "Volume-weighted average price across all open fills (numeric string)." },
7193
+ closeAvgPx: { type: "string", description: "Volume-weighted average price across all close fills (numeric string)." },
7194
+ openMaxAmount: {
7195
+ type: "string",
7196
+ description: "Peak position size held during the position's lifetime, in contracts (\u5F20)."
7197
+ },
7198
+ closeAmount: {
7199
+ type: "string",
7200
+ description: "Total amount closed across all close fills, in contracts for SWAP/FUTURES or in base currency for SPOT/MARGIN (numeric string)."
7201
+ },
7202
+ realizedPnl: { type: "string", description: "Cumulative realized PnL during the position's lifetime, in `quoteCcy` units." },
7203
+ pnl: {
7204
+ type: "string",
7205
+ description: "Total realized PnL for this position including fees and funding, denominated in quoteCcy (numeric string). Differs from `realizedPnl` which may exclude fees."
7206
+ },
7207
+ pnlRatio: {
7208
+ type: "string",
7209
+ description: 'Realized PnL as a decimal ratio of cost basis (e.g. "0.15" = +15%, "-0.20" = \u221220%).'
7210
+ },
7211
+ fee: {
7212
+ type: "string",
7213
+ description: "Cumulative trading fee paid over the position's lifetime, denominated in quoteCcy (numeric string, negative = cost)."
7214
+ },
7215
+ fundingFee: {
7216
+ type: "string",
7217
+ description: "Cumulative funding fee paid or received over the position's lifetime, denominated in quoteCcy (numeric string; negative = paid, positive = received)."
7218
+ },
7219
+ liquidationStatus: {
7220
+ type: "string",
7221
+ 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.'
7222
+ },
7223
+ closeType: {
7224
+ type: "string",
7225
+ 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."
7226
+ },
7227
+ cTime: { type: "string", description: "Position open time as Unix milliseconds (numeric string)." },
7228
+ uTime: { type: "string", description: "Position close time as Unix milliseconds (numeric string)." }
7229
+ }
7230
+ }
7231
+ },
7232
+ { pagination: PAGINATION_PROP }
7233
+ ),
7234
+ inputSchema: {
7235
+ type: "object",
7236
+ properties: {
7237
+ authorId: {
6624
7238
  type: "string",
6625
- description: "Recommended. Timestamp ms \u2014 use Date.now() for latest."
7239
+ description: "Trader's unique ID (obtain from `smartmoney_get_traders_by_filter`)."
6626
7240
  },
6627
- dataVersion: {
7241
+ instId: {
6628
7242
  type: "string",
6629
- description: "Alternative. yyyyMMddHHmm UTC for prior snapshot. If both sent, ts wins."
7243
+ 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.'
6630
7244
  },
6631
- granularity: {
7245
+ after: {
6632
7246
  type: "string",
6633
- description: "1h or 1d (default 1h)"
7247
+ description: "Cursor: returns positions with `posId` smaller than this value (older \u2014 paginate backwards). Pass as quoted string (the `posId` value verbatim, NOT a number \u2014 `posId` is a 19-digit ID and number coercion can lose precision)."
6634
7248
  },
6635
- limit: {
7249
+ before: {
6636
7250
  type: "string",
6637
- description: "Data points 1-500 (default 24)"
7251
+ description: "Cursor: returns positions with `posId` greater than this value (newer \u2014 paginate forwards). Pass as quoted string."
6638
7252
  },
6639
- ...SIGNAL_POOL_FILTER_PROPS
7253
+ limit: {
7254
+ type: "integer",
7255
+ minimum: 1,
7256
+ maximum: 100,
7257
+ default: 10,
7258
+ description: "Max positions per page (default 10, max 100)."
7259
+ }
6640
7260
  },
6641
- required: ["instId"]
7261
+ required: ["authorId"]
6642
7262
  },
6643
7263
  handler: async (rawArgs, context) => {
6644
7264
  const args = asRecord(rawArgs);
6645
- const dv = readString(args, "dataVersion");
6646
- const ts = readString(args, "ts");
6647
- if (!dv && !ts) {
6648
- throw new ValidationError('Either "dataVersion" or "ts" is required for smartmoney_get_signal_history.');
7265
+ const authorId = readString(args, "authorId");
7266
+ if (!authorId) {
7267
+ throw actionableError(
7268
+ '"authorId" is required.',
7269
+ "Discover trader IDs via `smartmoney_get_traders_by_filter`."
7270
+ );
6649
7271
  }
7272
+ const limit = readNumber(args, "limit");
6650
7273
  const response = await context.client.privateGet(
6651
- PATH_SIGNAL_HISTORY,
7274
+ PATH_POSITION_HISTORY,
6652
7275
  compactObject({
6653
- instId: requireString(args, "instId"),
6654
- dataVersion: dv,
6655
- ts,
6656
- granularity: readString(args, "granularity"),
6657
- limit: readString(args, "limit"),
6658
- ...readPoolFilters(args)
7276
+ authorId,
7277
+ instCcy: extractBaseCcy(readString(args, "instId")),
7278
+ after: readString(args, "after"),
7279
+ before: readString(args, "before"),
7280
+ limit
6659
7281
  }),
6660
- publicRateLimit("smartmoney_get_signal_history", 5)
7282
+ publicRateLimit("smartmoney_get_trader_positions_history", SMARTMONEY_RPS)
6661
7283
  );
6662
- return normalizeResponse(response);
7284
+ const normalized = normalizeResponse(response);
7285
+ const data = Array.isArray(normalized.data) ? normalized.data : [];
7286
+ return {
7287
+ ...normalized,
7288
+ data,
7289
+ pagination: buildPagination(data, limit ?? 10, "posId")
7290
+ };
6663
7291
  }
6664
7292
  },
6665
- /* ---------- 4. Traders (list) ---------- */
7293
+ /* ---------- T5. Trader order history ---------- */
6666
7294
  {
6667
- name: "smartmoney_get_traders",
7295
+ name: "smartmoney_get_trader_orders_history",
6668
7296
  module: "smartmoney",
6669
- description: "List/filter leaderboard traders. For single trader detail: smartmoney_get_trader_detail.",
7297
+ 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).",
6670
7298
  isWrite: false,
7299
+ outputSchema: envelope(
7300
+ {
7301
+ type: "array",
7302
+ items: {
7303
+ type: "object",
7304
+ properties: {
7305
+ ordId: { type: "string", description: "Unique order ID. Use as the `after` / `before` cursor when paginating." },
7306
+ instId: { type: "string", description: "Instrument ID e.g. BTC-USDT-SWAP." },
7307
+ displayId: { type: "string", description: "Display-form instrument ID used in OKX UI." },
7308
+ instType: {
7309
+ type: "string",
7310
+ description: "Instrument business line: `SWAP` (perpetual contract) | `SPOT`."
7311
+ },
7312
+ baseName: { type: "string", description: 'Base currency symbol, e.g. "BTC".' },
7313
+ quoteName: { type: "string", description: 'Quote currency symbol, e.g. "USD".' },
7314
+ tradeQuoteCcy: { type: "string", description: "Quote currency the fill actually settled in." },
7315
+ side: {
7316
+ type: "string",
7317
+ description: "Order side. `buy` = open long / close short; `sell` = open short / close long."
7318
+ },
7319
+ posSide: {
7320
+ type: "string",
7321
+ description: "Position direction the order applies to: `long` | `short`. Indicates whether the trader was opening/closing a long-side or short-side position."
7322
+ },
7323
+ ordType: {
7324
+ type: "string",
7325
+ description: "Order type. `limit` = price-protected limit order; `market` = immediate at best available price."
7326
+ },
7327
+ lever: { type: "string", description: 'Leverage multiplier used for this order (numeric string; "1" for spot).' },
7328
+ px: { type: "string", description: "Submitted order price (numeric string). For market orders this may be empty/0." },
7329
+ avgPx: { type: "string", description: "Volume-weighted average fill price (numeric string)." },
7330
+ sz: {
7331
+ type: "string",
7332
+ description: "Order size. Unit depends on instType: coins (\u5E01) for SPOT, contracts (\u5F20) for SWAP/FUTURES."
7333
+ },
7334
+ value: {
7335
+ type: "string",
7336
+ description: "Order notional value, denominated in `quoteName` units."
7337
+ },
7338
+ cTime: { type: "string", description: "Order creation time as Unix milliseconds (numeric string)." },
7339
+ fillTime: {
7340
+ type: "string",
7341
+ description: "Timestamp when the order was last filled, as Unix milliseconds (numeric string)."
7342
+ },
7343
+ uTime: {
7344
+ type: "string",
7345
+ description: "Timestamp when the order record was last updated, as Unix milliseconds (numeric string)."
7346
+ }
7347
+ }
7348
+ }
7349
+ },
7350
+ { pagination: PAGINATION_PROP }
7351
+ ),
6671
7352
  inputSchema: {
6672
7353
  type: "object",
6673
7354
  properties: {
6674
- dataVersion: {
7355
+ authorId: {
6675
7356
  type: "string",
6676
- description: "yyyyMMddHHmm, omit=latest"
7357
+ description: "Trader's unique ID (obtain from `smartmoney_get_traders_by_filter`)."
6677
7358
  },
6678
- ...LEADERBOARD_POOL_FILTER_PROPS,
6679
- authorIds: {
7359
+ instId: {
6680
7360
  type: "string",
6681
- description: "Comma-separated author IDs"
7361
+ 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.'
6682
7362
  },
6683
7363
  after: {
6684
7364
  type: "string",
6685
- description: "Cursor after this authorId"
7365
+ description: "Cursor: returns trades with `ordId` smaller than this value (older \u2014 paginate backwards). Pass as quoted string (the `ordId` value verbatim, NOT a number \u2014 `ordId` is a 19-digit ID and number coercion can lose precision)."
6686
7366
  },
6687
7367
  before: {
6688
7368
  type: "string",
6689
- description: "Cursor before this authorId"
7369
+ description: "Cursor: returns trades with `ordId` greater than this value (newer \u2014 paginate forwards). Pass as quoted string."
6690
7370
  },
6691
7371
  limit: {
6692
- type: "string",
6693
- description: "Max results 1-100"
7372
+ type: "integer",
7373
+ minimum: 1,
7374
+ maximum: 100,
7375
+ default: 10,
7376
+ description: "Max trades per page (default 10, max 100)."
6694
7377
  }
6695
- }
7378
+ },
7379
+ required: ["authorId"]
6696
7380
  },
6697
7381
  handler: async (rawArgs, context) => {
6698
7382
  const args = asRecord(rawArgs);
7383
+ const authorId = readString(args, "authorId");
7384
+ if (!authorId) {
7385
+ throw actionableError(
7386
+ '"authorId" is required.',
7387
+ "Discover trader IDs via `smartmoney_get_traders_by_filter`."
7388
+ );
7389
+ }
7390
+ const limit = readNumber(args, "limit");
6699
7391
  const response = await context.client.privateGet(
6700
- PATH_LEADERBOARD,
7392
+ PATH_TRADE_RECORDS,
6701
7393
  compactObject({
6702
- dataVersion: readString(args, "dataVersion"),
6703
- ...readPoolFilters(args),
6704
- authorIds: readString(args, "authorIds"),
7394
+ authorId,
7395
+ instCcy: extractBaseCcy(readString(args, "instId")),
6705
7396
  after: readString(args, "after"),
6706
7397
  before: readString(args, "before"),
6707
- limit: readString(args, "limit")
7398
+ limit
6708
7399
  }),
6709
- publicRateLimit("smartmoney_get_traders", 5)
7400
+ publicRateLimit("smartmoney_get_trader_orders_history", SMARTMONEY_RPS)
6710
7401
  );
6711
7402
  const normalized = normalizeResponse(response);
6712
- return { ...normalized, data: extractLeaderboardData(normalized.data) };
7403
+ const data = Array.isArray(normalized.data) ? normalized.data : [];
7404
+ return {
7405
+ ...normalized,
7406
+ data,
7407
+ pagination: buildPagination(data, limit ?? 10, "ordId")
7408
+ };
6713
7409
  }
6714
7410
  },
6715
- /* ---------- 5. Trader Detail (composite) ---------- */
7411
+ /* ---------- T6. Search top traders by nickname keyword ---------- */
6716
7412
  {
6717
- name: "smartmoney_get_trader_detail",
7413
+ name: "smartmoney_search_trader",
6718
7414
  module: "smartmoney",
6719
- description: "Trader portrait: profile + positions + trades. Requires authorId from smartmoney_get_traders. Do NOT use for listing \u2014 use smartmoney_get_traders.",
7415
+ 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).",
6720
7416
  isWrite: false,
7417
+ outputSchema: envelope({
7418
+ type: "array",
7419
+ description: "Matched Top Traders (\u226410), sorted by `followerCount` DESC. Empty array when no recall intersects the Top Trader set.",
7420
+ items: {
7421
+ type: "object",
7422
+ properties: {
7423
+ authorId: { type: "string", description: "Trader's unique ID \u2014 pass to other `smartmoney_get_trader_*` tools." },
7424
+ nickName: { type: "string", description: "Display nickname matched against the keyword." },
7425
+ followerCount: { type: "string", description: "OKX-platform follower count (numeric string; Twitter followers excluded). Sort key." }
7426
+ }
7427
+ }
7428
+ }),
6721
7429
  inputSchema: {
6722
7430
  type: "object",
6723
7431
  properties: {
6724
- authorId: {
7432
+ keyword: {
6725
7433
  type: "string",
6726
- description: "Trader author ID"
7434
+ description: "Nickname search keyword. Required, must be non-empty / non-whitespace. Matched candidates are intersected with the Top Trader set."
7435
+ }
7436
+ },
7437
+ required: ["keyword"]
7438
+ },
7439
+ handler: async (rawArgs, context) => {
7440
+ const args = asRecord(rawArgs);
7441
+ const keyword = readString(args, "keyword");
7442
+ if (!keyword || keyword.trim() === "") {
7443
+ throw actionableError(
7444
+ '"keyword" is required and must be non-empty.',
7445
+ 'Pass a nickname fragment (e.g. "alice", "\u5C0F\u660E"). For known author IDs use `smartmoney_get_performance_by_trader` instead.'
7446
+ );
7447
+ }
7448
+ const response = await context.client.privateGet(
7449
+ PATH_TOP_TRADER_SEARCH,
7450
+ compactObject({ keyword }),
7451
+ publicRateLimit("smartmoney_search_trader", SMARTMONEY_RPS)
7452
+ );
7453
+ const normalized = normalizeResponse(response);
7454
+ const data = Array.isArray(normalized.data) ? normalized.data : [];
7455
+ return { ...normalized, data };
7456
+ }
7457
+ },
7458
+ /* ===================================================== */
7459
+ /* Signal/Coin family (4) */
7460
+ /* ===================================================== */
7461
+ /* ---------- S1. Signal overview by filter (multi-asset, tier-filtered pool) ---------- */
7462
+ {
7463
+ name: "smartmoney_get_signal_overview_by_filter",
7464
+ module: "smartmoney",
7465
+ description: "Multi-asset smart-money consensus signals (long/short ratio, weighted entry, capital flow, deltas vs 1h/24h/7d), aggregated over a tier-filtered trader pool (PnL / win-rate / drawdown / AUM). Pick instruments via `topInstruments` OR `instCcyList` \u2014 exactly one. Snapshot time auto-resolved to current hour. Use when: latest cross-asset consensus from a criteria-defined pool. See also: `smartmoney_get_signal_overview_by_trader` (restrict pool to specific traders), `smartmoney_get_signal_trend_by_filter` (time-series instead of latest snapshot).",
7466
+ isWrite: false,
7467
+ outputSchema: envelope({
7468
+ type: "array",
7469
+ description: "Per-instrument snapshot, one element per requested coin.",
7470
+ items: { type: "object", properties: SIGNAL_ITEM_PROPS }
7471
+ }),
7472
+ inputSchema: {
7473
+ type: "object",
7474
+ properties: {
7475
+ topInstruments: {
7476
+ type: "integer",
7477
+ minimum: 1,
7478
+ maximum: 100,
7479
+ default: 20,
7480
+ 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)."
6727
7481
  },
6728
- period: {
6729
- type: "string",
6730
- description: "3|7|30|90 days, omit=all"
7482
+ instCcyList: {
7483
+ type: "array",
7484
+ items: { type: "string" },
7485
+ minItems: 1,
7486
+ description: 'Base currencies to aggregate, e.g. `["BTC", "ETH", "SOL"]`. Mutually exclusive with `topInstruments`.'
6731
7487
  },
7488
+ ...SIGNAL_POOL_FILTER_PROPS,
7489
+ lmtNum: {
7490
+ type: "integer",
7491
+ minimum: 1,
7492
+ maximum: 2e3,
7493
+ default: 100,
7494
+ 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."
7495
+ }
7496
+ }
7497
+ },
7498
+ handler: async (rawArgs, context) => {
7499
+ const args = asRecord(rawArgs);
7500
+ const instCcyList = readArrayAsCsv(args, "instCcyList");
7501
+ const topInstrumentsRaw = readNumber(args, "topInstruments");
7502
+ if (instCcyList && topInstrumentsRaw !== void 0) {
7503
+ throw actionableError(
7504
+ '"topInstruments" and "instCcyList" are mutually exclusive.',
7505
+ "Pass exactly one \u2014 `topInstruments` for top-N hottest coins, or `instCcyList` for specific coins."
7506
+ );
7507
+ }
7508
+ const response = await context.client.privateGet(
7509
+ PATH_OVERVIEW,
7510
+ compactObject({
7511
+ ...instCcyList ? { instCcyList } : { topInstruments: topInstrumentsRaw ?? 20 },
7512
+ ...readSignalPoolFilters(args),
7513
+ lmtNum: readNumber(args, "lmtNum")
7514
+ }),
7515
+ publicRateLimit("smartmoney_get_signal_overview_by_filter", SMARTMONEY_RPS)
7516
+ );
7517
+ return normalizeResponse(response);
7518
+ }
7519
+ },
7520
+ /* ---------- S2. Signal overview by trader (multi-asset, authorIds-restricted) ---------- */
7521
+ {
7522
+ name: "smartmoney_get_signal_overview_by_trader",
7523
+ module: "smartmoney",
7524
+ description: "Multi-asset smart-money signals aggregated over a hand-picked set of traders (`authorIds`). Pick instruments via `topInstruments` OR `instCcyList`. Pool sizing / ranking / capability filters not exposed \u2014 backend uses defaults for direct-lookup scenarios. Use when: caller already knows which traders to follow and wants their cross-asset consensus at the latest hour. See also: `smartmoney_get_signal_overview_by_filter` (criteria-defined pool), `smartmoney_get_signal_trend_by_trader` (time-series), `smartmoney_get_traders_by_filter` / `smartmoney_search_trader` (discover authorIds).",
7525
+ isWrite: false,
7526
+ outputSchema: envelope({
7527
+ type: "array",
7528
+ description: "Per-instrument snapshot, one element per requested coin.",
7529
+ items: { type: "object", properties: SIGNAL_ITEM_PROPS }
7530
+ }),
7531
+ inputSchema: {
7532
+ type: "object",
7533
+ properties: {
7534
+ authorIds: {
7535
+ type: "array",
7536
+ items: { type: "string" },
7537
+ minItems: 1,
7538
+ description: 'Trader IDs to aggregate over, e.g. `["1001", "1002"]`. Required.'
7539
+ },
7540
+ topInstruments: {
7541
+ type: "integer",
7542
+ minimum: 1,
7543
+ maximum: 100,
7544
+ default: 20,
7545
+ 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)."
7546
+ },
7547
+ instCcyList: {
7548
+ type: "array",
7549
+ items: { type: "string" },
7550
+ minItems: 1,
7551
+ description: 'Base currencies to aggregate, e.g. `["BTC", "ETH", "SOL"]`. Mutually exclusive with `topInstruments`.'
7552
+ }
7553
+ },
7554
+ required: ["authorIds"]
7555
+ },
7556
+ handler: async (rawArgs, context) => {
7557
+ const args = asRecord(rawArgs);
7558
+ const authorIds = readArrayAsCsv(args, "authorIds");
7559
+ if (!authorIds) {
7560
+ throw actionableError(
7561
+ '"authorIds" is required and must be a non-empty array.',
7562
+ 'Pass IDs from `smartmoney_get_traders_by_filter` as an array (e.g. ["1001", "1002"]).'
7563
+ );
7564
+ }
7565
+ const instCcyList = readArrayAsCsv(args, "instCcyList");
7566
+ const topInstrumentsRaw = readNumber(args, "topInstruments");
7567
+ if (instCcyList && topInstrumentsRaw !== void 0) {
7568
+ throw actionableError(
7569
+ '"topInstruments" and "instCcyList" are mutually exclusive.',
7570
+ "Pass exactly one \u2014 `topInstruments` for top-N hottest coins, or `instCcyList` for specific coins."
7571
+ );
7572
+ }
7573
+ const response = await context.client.privateGet(
7574
+ PATH_OVERVIEW,
7575
+ compactObject({
7576
+ authorIds,
7577
+ ...instCcyList ? { instCcyList } : { topInstruments: topInstrumentsRaw ?? 20 }
7578
+ }),
7579
+ publicRateLimit("smartmoney_get_signal_overview_by_trader", SMARTMONEY_RPS)
7580
+ );
7581
+ return normalizeResponse(response);
7582
+ }
7583
+ },
7584
+ /* ---------- S3. Signal trend by filter (single-asset, tier-filtered pool, asOfTime anchor) ---------- */
7585
+ {
7586
+ name: "smartmoney_get_signal_trend_by_filter",
7587
+ module: "smartmoney",
7588
+ description: "Time-series of single-asset smart-money signal across hourly/daily buckets, aggregated over a tier-filtered trader pool. Returns the latest `limit` buckets ending at `asOfTime` (defaults to current UTC hour). Use when: tracking how long/short conviction and capital evolve over time (smart money adding exposure or retreating). See also: `smartmoney_get_signal_overview_by_filter` (latest snapshot only), `smartmoney_get_signal_trend_by_trader` (restrict to specific traders). Note: `asOfTime` is 10-digit `yyyyMMddHH` UTC, different from leaderboard tools' 12-digit UTC+8 `updateTime` \u2014 do not cross-pass.",
7589
+ isWrite: false,
7590
+ outputSchema: envelope({
7591
+ type: "array",
7592
+ description: "Time-bucket series for the requested instrument, sorted by time DESC (newest first).",
7593
+ items: { type: "object", properties: SIGNAL_HISTORY_ITEM_PROPS }
7594
+ }),
7595
+ inputSchema: {
7596
+ type: "object",
7597
+ properties: {
6732
7598
  instCcy: {
6733
7599
  type: "string",
6734
- description: "Currency filter e.g. BTC"
7600
+ description: 'Base currency to scope the time-series, e.g. "BTC". Required.'
6735
7601
  },
6736
- tradeLimit: {
7602
+ asOfTime: {
6737
7603
  type: "string",
6738
- description: "Max trades 1-100"
7604
+ description: 'Anchor snapshot time. Pass as a quoted 10-digit string `yyyyMMddHH` UTC, e.g. `"2026050100"` (NOT integer 2026050100). Returns the latest `limit` buckets ending at this anchor. Omit to use the current UTC hour.'
7605
+ },
7606
+ granularity: {
7607
+ type: "string",
7608
+ enum: ["1h", "1d"],
7609
+ default: "1h",
7610
+ description: "Time-bucket size. `1h` = hourly snapshots (intraday/short-term trend), `1d` = daily snapshots (multi-day trend)."
7611
+ },
7612
+ limit: {
7613
+ type: "integer",
7614
+ minimum: 1,
7615
+ maximum: 500,
7616
+ default: 24,
7617
+ description: "Number of buckets to return (newest first), ending at `asOfTime`. Default 24, max 500."
7618
+ },
7619
+ ...SIGNAL_POOL_FILTER_PROPS,
7620
+ lmtNum: {
7621
+ type: "integer",
7622
+ minimum: 1,
7623
+ maximum: 2e3,
7624
+ default: 100,
7625
+ description: "Top-N traders to pull into the aggregation pool, ranked by `sortBy` (DESC). Default 100, max 2000."
6739
7626
  }
6740
7627
  },
6741
- required: ["authorId"]
7628
+ required: ["instCcy"]
6742
7629
  },
6743
7630
  handler: async (rawArgs, context) => {
6744
7631
  const args = asRecord(rawArgs);
6745
- const authorId = requireString(args, "authorId");
6746
- const period = readString(args, "period");
6747
7632
  const instCcy = readString(args, "instCcy");
6748
- const tradeLimit = readString(args, "tradeLimit");
6749
- const [profileRes, positionsRes, tradesRes] = await Promise.all([
6750
- context.client.privateGet(
6751
- PATH_LEADERBOARD,
6752
- compactObject({ authorIds: authorId, period }),
6753
- publicRateLimit("smartmoney_get_traders", 5)
6754
- ),
6755
- context.client.privateGet(
6756
- PATH_POSITION_CURRENT,
6757
- compactObject({ authorId, instCcy }),
6758
- publicRateLimit("smartmoney_trader_positions", 5)
6759
- ),
6760
- context.client.privateGet(
6761
- PATH_TRADE_RECORDS,
6762
- compactObject({ authorId, instCcy, limit: tradeLimit }),
6763
- publicRateLimit("smartmoney_trade_records", 5)
6764
- )
6765
- ]);
6766
- const profileNorm = normalizeResponse(profileRes);
6767
- const positionsNorm = normalizeResponse(positionsRes);
6768
- const tradesNorm = normalizeResponse(tradesRes);
6769
- return {
6770
- endpoint: "smartmoney_get_trader_detail (composite)",
6771
- requestTime: (/* @__PURE__ */ new Date()).toISOString(),
6772
- data: {
6773
- profile: extractLeaderboardData(profileNorm.data),
6774
- positions: positionsNorm.data,
6775
- trades: tradesNorm.data
7633
+ if (!instCcy) {
7634
+ throw actionableError(
7635
+ '"instCcy" is required.',
7636
+ 'Pass a base currency, e.g. "BTC".'
7637
+ );
7638
+ }
7639
+ const response = await context.client.privateGet(
7640
+ PATH_SIGNAL_HISTORY,
7641
+ compactObject({
7642
+ instCcy,
7643
+ asOfTime: readString(args, "asOfTime"),
7644
+ granularity: readString(args, "granularity"),
7645
+ limit: readNumber(args, "limit"),
7646
+ ...readSignalPoolFilters(args),
7647
+ lmtNum: readNumber(args, "lmtNum")
7648
+ }),
7649
+ publicRateLimit("smartmoney_get_signal_trend_by_filter", SMARTMONEY_RPS)
7650
+ );
7651
+ return normalizeResponse(response);
7652
+ }
7653
+ },
7654
+ /* ---------- S4. Signal trend by trader (single-asset, authorIds-restricted) ---------- */
7655
+ {
7656
+ name: "smartmoney_get_signal_trend_by_trader",
7657
+ module: "smartmoney",
7658
+ description: "Time-series of single-asset smart-money signal aggregated over a hand-picked set of traders (`authorIds`). Returns the latest `limit` buckets ending at `asOfTime` (defaults to current UTC hour). Pool sizing / ranking / capability filters not exposed \u2014 backend uses defaults for direct-lookup scenarios. Use when: tracking how a specific group of traders has evolved their long/short consensus over time on one coin. See also: `smartmoney_get_signal_trend_by_filter` (criteria-defined pool), `smartmoney_get_signal_overview_by_trader` (latest snapshot only), `smartmoney_get_traders_by_filter` / `smartmoney_search_trader` (discover authorIds). Note: `asOfTime` is 10-digit `yyyyMMddHH` UTC, different from leaderboard tools' 12-digit UTC+8 `updateTime` \u2014 do not cross-pass.",
7659
+ isWrite: false,
7660
+ outputSchema: envelope({
7661
+ type: "array",
7662
+ description: "Time-bucket series for the requested instrument, sorted by time DESC (newest first).",
7663
+ items: { type: "object", properties: SIGNAL_HISTORY_ITEM_PROPS }
7664
+ }),
7665
+ inputSchema: {
7666
+ type: "object",
7667
+ properties: {
7668
+ authorIds: {
7669
+ type: "array",
7670
+ items: { type: "string" },
7671
+ minItems: 1,
7672
+ description: 'Trader IDs to aggregate over, e.g. `["1001", "1002"]`. Required.'
7673
+ },
7674
+ instCcy: {
7675
+ type: "string",
7676
+ description: 'Base currency to scope the time-series, e.g. "BTC". Required.'
7677
+ },
7678
+ asOfTime: {
7679
+ type: "string",
7680
+ description: 'Anchor snapshot time. Pass as a quoted 10-digit string `yyyyMMddHH` UTC, e.g. `"2026050100"` (NOT integer 2026050100). Returns the latest `limit` buckets ending at this anchor. Omit to use the current UTC hour.'
7681
+ },
7682
+ granularity: {
7683
+ type: "string",
7684
+ enum: ["1h", "1d"],
7685
+ default: "1h",
7686
+ description: "Time-bucket size. `1h` = hourly snapshots (intraday/short-term trend), `1d` = daily snapshots (multi-day trend)."
7687
+ },
7688
+ limit: {
7689
+ type: "integer",
7690
+ minimum: 1,
7691
+ maximum: 500,
7692
+ default: 24,
7693
+ description: "Number of buckets to return (newest first), ending at `asOfTime`. Default 24, max 500."
6776
7694
  }
6777
- };
7695
+ },
7696
+ required: ["authorIds", "instCcy"]
7697
+ },
7698
+ handler: async (rawArgs, context) => {
7699
+ const args = asRecord(rawArgs);
7700
+ const authorIds = readArrayAsCsv(args, "authorIds");
7701
+ const instCcy = readString(args, "instCcy");
7702
+ if (!authorIds) {
7703
+ throw actionableError(
7704
+ '"authorIds" is required and must be a non-empty array.',
7705
+ 'Pass IDs from `smartmoney_get_traders_by_filter` as an array (e.g. ["1001", "1002"]).'
7706
+ );
7707
+ }
7708
+ if (!instCcy) {
7709
+ throw actionableError(
7710
+ '"instCcy" is required.',
7711
+ 'Pass a base currency, e.g. "BTC".'
7712
+ );
7713
+ }
7714
+ const response = await context.client.privateGet(
7715
+ PATH_SIGNAL_HISTORY,
7716
+ compactObject({
7717
+ authorIds,
7718
+ instCcy,
7719
+ asOfTime: readString(args, "asOfTime"),
7720
+ granularity: readString(args, "granularity"),
7721
+ limit: readNumber(args, "limit")
7722
+ }),
7723
+ publicRateLimit("smartmoney_get_signal_trend_by_trader", SMARTMONEY_RPS)
7724
+ );
7725
+ return normalizeResponse(response);
6778
7726
  }
6779
7727
  }
6780
7728
  ];
@@ -6833,8 +7781,12 @@ function buildContractTradeTools(cfg) {
6833
7781
  clOrdId: { type: "string", description: "Client order ID (max 32 chars)" },
6834
7782
  tpTriggerPx: { type: "string", description: "TP trigger price" },
6835
7783
  tpOrdPx: { type: "string", description: "TP order price; -1=market" },
7784
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
7785
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
6836
7786
  slTriggerPx: { type: "string", description: "SL trigger price" },
6837
- slOrdPx: { type: "string", description: "SL order price; -1=market" }
7787
+ slOrdPx: { type: "string", description: "SL order price; -1=market" },
7788
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
7789
+ stpMode: STP_MODE_SCHEMA
6838
7790
  },
6839
7791
  required: ["instId", "tdMode", "side", "ordType", "sz"]
6840
7792
  },
@@ -6863,6 +7815,9 @@ function buildContractTradeTools(cfg) {
6863
7815
  px: readString(args, "px"),
6864
7816
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
6865
7817
  clOrdId: readString(args, "clOrdId"),
7818
+ stpMode: readString(args, "stpMode"),
7819
+ // Phase 3c CLI power-user flag (issue #182, CLI-only no MCP/skill exposure)
7820
+ pxAmendType: readString(args, "pxAmendType"),
6866
7821
  tag: context.config.sourceTag,
6867
7822
  attachAlgoOrds
6868
7823
  }),
@@ -8862,6 +9817,8 @@ function registerOptionTools() {
8862
9817
  type: "string",
8863
9818
  description: "TP order price; -1=market"
8864
9819
  },
9820
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
9821
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
8865
9822
  slTriggerPx: {
8866
9823
  type: "string",
8867
9824
  description: "SL trigger price"
@@ -8869,7 +9826,9 @@ function registerOptionTools() {
8869
9826
  slOrdPx: {
8870
9827
  type: "string",
8871
9828
  description: "SL order price; -1=market"
8872
- }
9829
+ },
9830
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
9831
+ stpMode: STP_MODE_SCHEMA
8873
9832
  },
8874
9833
  required: ["instId", "tdMode", "side", "ordType", "sz"]
8875
9834
  },
@@ -8897,6 +9856,7 @@ function registerOptionTools() {
8897
9856
  px: readString(args, "px"),
8898
9857
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
8899
9858
  clOrdId: readString(args, "clOrdId"),
9859
+ stpMode: readString(args, "stpMode"),
8900
9860
  tag: context.config.sourceTag,
8901
9861
  attachAlgoOrds
8902
9862
  }),
@@ -9265,6 +10225,8 @@ function registerSpotTradeTools() {
9265
10225
  type: "string",
9266
10226
  description: "TP order price, -1=market"
9267
10227
  },
10228
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
10229
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
9268
10230
  slTriggerPx: {
9269
10231
  type: "string",
9270
10232
  description: "SL trigger price"
@@ -9272,13 +10234,16 @@ function registerSpotTradeTools() {
9272
10234
  slOrdPx: {
9273
10235
  type: "string",
9274
10236
  description: "SL order price, -1=market"
9275
- }
10237
+ },
10238
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
10239
+ stpMode: STP_MODE_SCHEMA
9276
10240
  },
9277
10241
  required: ["instId", "tdMode", "side", "ordType", "sz"]
9278
10242
  },
9279
10243
  handler: async (rawArgs, context) => {
9280
10244
  const args = asRecord(rawArgs);
9281
10245
  const attachAlgoOrds = buildAttachAlgoOrds(args);
10246
+ const banAmend = args.banAmend;
9282
10247
  const response = await context.client.privatePost(
9283
10248
  "/api/v5/trade/order",
9284
10249
  compactObject({
@@ -9290,6 +10255,11 @@ function registerSpotTradeTools() {
9290
10255
  tgtCcy: readString(args, "tgtCcy"),
9291
10256
  px: readString(args, "px"),
9292
10257
  clOrdId: readString(args, "clOrdId"),
10258
+ stpMode: readString(args, "stpMode"),
10259
+ // Phase 3c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
10260
+ tradeQuoteCcy: readString(args, "tradeQuoteCcy"),
10261
+ banAmend: typeof banAmend === "boolean" ? String(banAmend) : void 0,
10262
+ pxAmendType: readString(args, "pxAmendType"),
9293
10263
  tag: context.config.sourceTag,
9294
10264
  attachAlgoOrds
9295
10265
  }),
@@ -9454,7 +10424,7 @@ function registerSpotTradeTools() {
9454
10424
  {
9455
10425
  name: "spot_place_algo_order",
9456
10426
  module: "spot",
9457
- description: "Place a spot algo order: TP/SL (conditional/oco) or trailing stop (move_order_stop). [CAUTION] Executes real trades.",
10427
+ 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.",
9458
10428
  isWrite: true,
9459
10429
  inputSchema: {
9460
10430
  type: "object",
@@ -9474,8 +10444,8 @@ function registerSpotTradeTools() {
9474
10444
  },
9475
10445
  ordType: {
9476
10446
  type: "string",
9477
- enum: ["conditional", "oco", "move_order_stop"],
9478
- description: "conditional=single TP/SL, oco=TP+SL pair, move_order_stop=trailing stop"
10447
+ enum: ["conditional", "oco", "move_order_stop", "trigger", "chase", "iceberg", "twap"],
10448
+ 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"
9479
10449
  },
9480
10450
  sz: {
9481
10451
  type: "string",
@@ -9489,6 +10459,8 @@ function registerSpotTradeTools() {
9489
10459
  type: "string",
9490
10460
  description: "TP order price, -1=market (conditional/oco only)"
9491
10461
  },
10462
+ tpOrdKind: TP_ORD_KIND_SCHEMA,
10463
+ tpTriggerPxType: TP_TRIGGER_PX_TYPE_SCHEMA,
9492
10464
  slTriggerPx: {
9493
10465
  type: "string",
9494
10466
  description: "SL trigger price (conditional/oco only)"
@@ -9497,6 +10469,8 @@ function registerSpotTradeTools() {
9497
10469
  type: "string",
9498
10470
  description: "SL order price, -1=market (conditional/oco only)"
9499
10471
  },
10472
+ slTriggerPxType: SL_TRIGGER_PX_TYPE_SCHEMA,
10473
+ stpMode: STP_MODE_SCHEMA,
9500
10474
  tgtCcy: {
9501
10475
  type: "string",
9502
10476
  enum: ["base_ccy", "quote_ccy"],
@@ -9513,30 +10487,50 @@ function registerSpotTradeTools() {
9513
10487
  activePx: {
9514
10488
  type: "string",
9515
10489
  description: "Activation price, trailing starts when market hits this (move_order_stop only)"
9516
- }
10490
+ },
10491
+ ...TRIGGER_FLAGS_SCHEMA,
10492
+ ...CHASE_FLAGS_SCHEMA,
10493
+ ...ICEBERG_TWAP_FLAGS_SCHEMA
9517
10494
  },
9518
10495
  required: ["instId", "side", "ordType", "sz"]
9519
10496
  },
9520
10497
  handler: async (rawArgs, context) => {
9521
10498
  const args = asRecord(rawArgs);
10499
+ const ordType = requireString(args, "ordType");
10500
+ const base = compactObject({
10501
+ instId: requireString(args, "instId"),
10502
+ tdMode: readString(args, "tdMode") ?? "cash",
10503
+ side: requireString(args, "side"),
10504
+ ordType,
10505
+ sz: requireString(args, "sz"),
10506
+ tgtCcy: readString(args, "tgtCcy"),
10507
+ stpMode: readString(args, "stpMode"),
10508
+ // Phase 3a+c CLI power-user flags (issue #182, CLI-only no MCP/skill exposure)
10509
+ pxAmendType: readString(args, "pxAmendType"),
10510
+ tag: context.config.sourceTag
10511
+ });
10512
+ switch (ordType) {
10513
+ case "trigger":
10514
+ Object.assign(base, buildTriggerOrdTypeBody(args));
10515
+ break;
10516
+ case "chase":
10517
+ Object.assign(base, buildChaseOrdTypeBody(args));
10518
+ break;
10519
+ case "iceberg":
10520
+ case "twap":
10521
+ Object.assign(base, buildIcebergTwapOrdTypeBody(args));
10522
+ break;
10523
+ default:
10524
+ Object.assign(base, compactObject({
10525
+ ...buildAlgoConditionalCommonFields(args),
10526
+ callbackRatio: readString(args, "callbackRatio"),
10527
+ callbackSpread: readString(args, "callbackSpread")
10528
+ }));
10529
+ break;
10530
+ }
9522
10531
  const response = await context.client.privatePost(
9523
10532
  "/api/v5/trade/order-algo",
9524
- compactObject({
9525
- instId: requireString(args, "instId"),
9526
- tdMode: readString(args, "tdMode") ?? "cash",
9527
- side: requireString(args, "side"),
9528
- ordType: requireString(args, "ordType"),
9529
- sz: requireString(args, "sz"),
9530
- tgtCcy: readString(args, "tgtCcy"),
9531
- tpTriggerPx: readString(args, "tpTriggerPx"),
9532
- tpOrdPx: readString(args, "tpOrdPx"),
9533
- slTriggerPx: readString(args, "slTriggerPx"),
9534
- slOrdPx: readString(args, "slOrdPx"),
9535
- callbackRatio: readString(args, "callbackRatio"),
9536
- callbackSpread: readString(args, "callbackSpread"),
9537
- activePx: readString(args, "activePx"),
9538
- tag: context.config.sourceTag
9539
- }),
10533
+ base,
9540
10534
  privateRateLimit("spot_place_algo_order", 20)
9541
10535
  );
9542
10536
  return normalizeResponse(response);
@@ -10130,6 +11124,7 @@ function toMcpTool(tool) {
10130
11124
  name: tool.name,
10131
11125
  description: tool.description,
10132
11126
  inputSchema: tool.inputSchema,
11127
+ ...tool.outputSchema ? { outputSchema: tool.outputSchema } : {},
10133
11128
  annotations: {
10134
11129
  readOnlyHint: !tool.isWrite,
10135
11130
  destructiveHint: tool.isWrite,
@@ -10563,7 +11558,7 @@ var _require = createRequire(import.meta.url);
10563
11558
  var pkg = _require("../package.json");
10564
11559
  var SERVER_NAME = "okx-trade-mcp";
10565
11560
  var SERVER_VERSION = pkg.version;
10566
- var GIT_HASH = true ? "1bb94dcd" : "dev";
11561
+ var GIT_HASH = true ? "d0f8ad6" : "dev";
10567
11562
 
10568
11563
  // src/server.ts
10569
11564
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";