@okx_ai/okx-trade-cli 1.2.7-beta.1 → 1.2.7-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3131,58 +3131,121 @@ function registerDcaTools() {
3131
3131
  {
3132
3132
  name: "dca_create_order",
3133
3133
  module: "bot.dca",
3134
- description: "Create Contract DCA (Martingale) bot. [CAUTION] Real trades. When maxSafetyOrds > 0: also need safetyOrdAmt, pxSteps, pxStepsMult, volMult.",
3134
+ description: "Create a DCA (Martingale) bot. [CAUTION] Real trades. contract_dca requires lever; spot_dca must be long. If maxSafetyOrds>0: need safetyOrdAmt, pxSteps.",
3135
3135
  isWrite: true,
3136
3136
  inputSchema: {
3137
3137
  type: "object",
3138
3138
  properties: {
3139
- instId: { type: "string", description: "e.g. BTC-USDT-SWAP" },
3140
- lever: { type: "string", description: "Leverage, e.g. '3'" },
3139
+ instId: { type: "string", description: "BTC-USDT (spot) or BTC-USDT-SWAP (contract)" },
3140
+ algoOrdType: { type: "string", enum: ["spot_dca", "contract_dca"] },
3141
+ lever: { type: "string", description: "Required for contract_dca" },
3141
3142
  direction: { type: "string", enum: ["long", "short"] },
3142
- initOrdAmt: { type: "string", description: "Initial order amount (USDT)" },
3143
- maxSafetyOrds: { type: "string", description: "Max safety orders, e.g. '3'" },
3144
- tpPct: { type: "string", description: "Take-profit ratio, e.g. '0.03' = 3%" },
3145
- safetyOrdAmt: { type: "string", description: "Safety order amount (USDT). Need when maxSafetyOrds > 0" },
3146
- pxSteps: { type: "string", description: "Price drop % per safety order, e.g. '0.03'. Need when maxSafetyOrds > 0" },
3147
- pxStepsMult: { type: "string", description: "Price step multiplier, e.g. '1.2'. Need when maxSafetyOrds > 0" },
3148
- volMult: { type: "string", description: "Safety order size multiplier, e.g. '1.5'. Need when maxSafetyOrds > 0" },
3149
- slPct: { type: "string", description: "Stop-loss ratio, e.g. '0.05' = 5%" },
3150
- slMode: { type: "string", enum: ["limit", "market"], description: "Stop-loss type. Default: market" },
3151
- allowReinvest: { type: "string", enum: ["true", "false"], description: "Reinvest profit. Default: 'true'" },
3152
- triggerStrategy: { type: "string", enum: ["instant", "price", "rsi"], default: "instant", description: "How bot starts. Default: instant" },
3153
- triggerPx: { type: "string", description: "Required when triggerStrategy='price'" }
3143
+ initOrdAmt: { type: "string", description: "Initial amount in quote ccy" },
3144
+ maxSafetyOrds: { type: "string", description: "0=no DCA, max 100" },
3145
+ tpPct: { type: "string", description: "Take-profit ratio, 0.03=3%" },
3146
+ safetyOrdAmt: { type: "string", description: "Safety order amount. Need if maxSafetyOrds>0" },
3147
+ pxSteps: { type: "string", description: "Price drop per safety order. Need if maxSafetyOrds>0" },
3148
+ pxStepsMult: { type: "string", description: "Step multiplier. Required when maxSafetyOrds>0, e.g. '1'" },
3149
+ volMult: { type: "string", description: "Size multiplier. Required when maxSafetyOrds>0, e.g. '1'" },
3150
+ slPct: { type: "string", description: "Stop-loss ratio, 0.05=5%" },
3151
+ slMode: { type: "string", enum: ["limit", "market"] },
3152
+ allowReinvest: { type: "boolean", description: "Default true" },
3153
+ triggerStrategy: { type: "string", enum: ["instant", "price", "rsi"], description: "contract_dca: instant|price|rsi; spot_dca: instant|rsi. Default: instant" },
3154
+ triggerPx: { type: "string", description: "Need if triggerStrategy=price (contract_dca only)" },
3155
+ triggerCond: { type: "string", enum: ["cross_up", "cross_down"], description: "Required when triggerStrategy=rsi. Optional when triggerStrategy=price" },
3156
+ thold: { type: "string", description: "RSI threshold (e.g. '30'). Need if triggerStrategy=rsi" },
3157
+ timeframe: { type: "string", description: "RSI timeframe (e.g. '15m'). Need if triggerStrategy=rsi" },
3158
+ timePeriod: { type: "string", description: "RSI period. Default '14'. Optional when triggerStrategy=rsi" },
3159
+ algoClOrdId: { type: "string", description: "Client order ID, 1-32 chars" },
3160
+ // Backend expects boolean, but kept as string for backward compatibility with older clients.
3161
+ reserveFunds: { type: "string", description: "'true' or 'false', default 'true'" },
3162
+ tradeQuoteCcy: { type: "string" }
3154
3163
  },
3155
- required: ["instId", "lever", "direction", "initOrdAmt", "maxSafetyOrds", "tpPct"]
3164
+ required: ["instId", "algoOrdType", "direction", "initOrdAmt", "maxSafetyOrds", "tpPct"]
3156
3165
  },
3157
3166
  handler: async (rawArgs, context) => {
3158
3167
  const args = asRecord(rawArgs);
3159
3168
  const instId = requireString(args, "instId");
3169
+ const algoOrdType = requireString(args, "algoOrdType");
3170
+ if (algoOrdType === "contract_dca" && !readString(args, "lever")) {
3171
+ throw new OkxApiError("lever is required for contract_dca", {
3172
+ code: "VALIDATION",
3173
+ endpoint: `${BASE}/create`
3174
+ });
3175
+ }
3160
3176
  const triggerStrategy = readString(args, "triggerStrategy") ?? "instant";
3177
+ if (triggerStrategy === "price" && algoOrdType === "spot_dca") {
3178
+ throw new OkxApiError("triggerStrategy 'price' is only supported for contract_dca. spot_dca supports: instant, rsi", {
3179
+ code: "VALIDATION",
3180
+ endpoint: `${BASE}/create`
3181
+ });
3182
+ }
3161
3183
  const triggerParam = {
3162
3184
  triggerAction: "start",
3163
3185
  triggerStrategy
3164
3186
  };
3165
3187
  if (triggerStrategy === "price") {
3166
3188
  triggerParam["triggerPx"] = requireString(args, "triggerPx");
3189
+ const triggerCond = readString(args, "triggerCond");
3190
+ if (triggerCond) {
3191
+ triggerParam["triggerCond"] = triggerCond;
3192
+ }
3193
+ } else if (triggerStrategy === "rsi") {
3194
+ triggerParam["triggerCond"] = requireString(args, "triggerCond");
3195
+ triggerParam["thold"] = requireString(args, "thold");
3196
+ triggerParam["timeframe"] = requireString(args, "timeframe");
3197
+ const timePeriod = readString(args, "timePeriod");
3198
+ triggerParam["timePeriod"] = timePeriod ?? "14";
3199
+ }
3200
+ const maxSafetyOrds = requireString(args, "maxSafetyOrds");
3201
+ if (Number(maxSafetyOrds) > 0) {
3202
+ if (!readString(args, "safetyOrdAmt")) {
3203
+ throw new OkxApiError("safetyOrdAmt is required when maxSafetyOrds > 0", {
3204
+ code: "VALIDATION",
3205
+ endpoint: `${BASE}/create`
3206
+ });
3207
+ }
3208
+ if (!readString(args, "pxSteps")) {
3209
+ throw new OkxApiError("pxSteps is required when maxSafetyOrds > 0", {
3210
+ code: "VALIDATION",
3211
+ endpoint: `${BASE}/create`
3212
+ });
3213
+ }
3214
+ if (!readString(args, "pxStepsMult")) {
3215
+ throw new OkxApiError("pxStepsMult is required when maxSafetyOrds > 0", {
3216
+ code: "VALIDATION",
3217
+ endpoint: `${BASE}/create`
3218
+ });
3219
+ }
3220
+ if (!readString(args, "volMult")) {
3221
+ throw new OkxApiError("volMult is required when maxSafetyOrds > 0", {
3222
+ code: "VALIDATION",
3223
+ endpoint: `${BASE}/create`
3224
+ });
3225
+ }
3167
3226
  }
3168
3227
  const response = await context.client.privatePost(
3169
3228
  `${BASE}/create`,
3170
3229
  compactObject({
3171
3230
  instId,
3172
- algoOrdType: "contract_dca",
3173
- lever: requireString(args, "lever"),
3231
+ algoOrdType,
3232
+ lever: readString(args, "lever"),
3174
3233
  direction: requireString(args, "direction"),
3175
3234
  initOrdAmt: requireString(args, "initOrdAmt"),
3176
3235
  safetyOrdAmt: readString(args, "safetyOrdAmt"),
3177
- maxSafetyOrds: requireString(args, "maxSafetyOrds"),
3236
+ maxSafetyOrds,
3178
3237
  pxSteps: readString(args, "pxSteps"),
3179
3238
  pxStepsMult: readString(args, "pxStepsMult"),
3180
3239
  volMult: readString(args, "volMult"),
3181
3240
  tpPct: requireString(args, "tpPct"),
3182
3241
  slPct: readString(args, "slPct"),
3183
3242
  slMode: readString(args, "slMode"),
3184
- allowReinvest: readString(args, "allowReinvest"),
3185
- triggerParams: [triggerParam]
3243
+ allowReinvest: args["allowReinvest"] !== void 0 ? args["allowReinvest"] === true || args["allowReinvest"] === "true" : void 0,
3244
+ triggerParams: [triggerParam],
3245
+ tag: context.config.sourceTag,
3246
+ algoClOrdId: readString(args, "algoClOrdId"),
3247
+ reserveFunds: readString(args, "reserveFunds"),
3248
+ tradeQuoteCcy: readString(args, "tradeQuoteCcy")
3186
3249
  }),
3187
3250
  privateRateLimit("dca_create_order", 20)
3188
3251
  );
@@ -3192,21 +3255,31 @@ function registerDcaTools() {
3192
3255
  {
3193
3256
  name: "dca_stop_order",
3194
3257
  module: "bot.dca",
3195
- description: "Stop a running Contract DCA bot. [CAUTION] This will stop the bot.",
3258
+ description: "Stop a running DCA bot. [CAUTION] spot_dca needs stopType: 1=sell, 2=keep.",
3196
3259
  isWrite: true,
3197
3260
  inputSchema: {
3198
3261
  type: "object",
3199
3262
  properties: {
3200
- algoId: { type: "string", description: "DCA bot algo order ID (not a trade ordId)" }
3263
+ algoId: { type: "string", description: "Algo order ID" },
3264
+ algoOrdType: { type: "string", enum: ["spot_dca", "contract_dca"] },
3265
+ stopType: { type: "string", enum: ["1", "2"], description: "Required for spot_dca: 1=sell all, 2=keep tokens" }
3201
3266
  },
3202
- required: ["algoId"]
3267
+ required: ["algoId", "algoOrdType"]
3203
3268
  },
3204
3269
  handler: async (rawArgs, context) => {
3205
3270
  const args = asRecord(rawArgs);
3206
3271
  const algoId = requireString(args, "algoId");
3272
+ const algoOrdType = requireString(args, "algoOrdType");
3273
+ const stopType = readString(args, "stopType");
3274
+ if (algoOrdType === "spot_dca" && !stopType) {
3275
+ throw new OkxApiError(
3276
+ "stopType is required for spot_dca. Use '1' (sell all tokens) or '2' (keep tokens)",
3277
+ { code: "VALIDATION", endpoint: `${BASE}/stop` }
3278
+ );
3279
+ }
3207
3280
  const response = await context.client.privatePost(
3208
3281
  `${BASE}/stop`,
3209
- { algoId, algoOrdType: "contract_dca" },
3282
+ compactObject({ algoId, algoOrdType, stopType }),
3210
3283
  privateRateLimit("dca_stop_order", 20)
3211
3284
  );
3212
3285
  return normalizeWrite2(response);
@@ -3215,21 +3288,18 @@ function registerDcaTools() {
3215
3288
  {
3216
3289
  name: "dca_get_orders",
3217
3290
  module: "bot.dca",
3218
- description: "List DCA bots. status='active' for running; 'history' for stopped.",
3291
+ description: "List DCA bots. Default: active (running). Use status=history for stopped.",
3219
3292
  isWrite: false,
3220
3293
  inputSchema: {
3221
3294
  type: "object",
3222
3295
  properties: {
3223
- status: {
3224
- type: "string",
3225
- enum: ["active", "history"],
3226
- description: "active=running (default); history=stopped"
3227
- },
3228
- algoId: { type: "string", description: "DCA bot algo order ID (not a trade ordId)" },
3229
- instId: { type: "string", description: "e.g. BTC-USDT-SWAP" },
3230
- after: { type: "string", description: "Cursor for older records" },
3231
- before: { type: "string", description: "Cursor for newer records" },
3232
- limit: { type: "number", description: "Default 100" }
3296
+ status: { type: "string", enum: ["active", "history"] },
3297
+ algoOrdType: { type: "string", enum: ["spot_dca", "contract_dca"], description: "Default: contract_dca" },
3298
+ algoId: { type: "string", description: "Algo order ID" },
3299
+ instId: { type: "string" },
3300
+ after: { type: "string", description: "Pagination cursor" },
3301
+ before: { type: "string", description: "Pagination cursor" },
3302
+ limit: { type: "number" }
3233
3303
  },
3234
3304
  required: []
3235
3305
  },
@@ -3237,10 +3307,11 @@ function registerDcaTools() {
3237
3307
  const args = asRecord(rawArgs);
3238
3308
  const status = readString(args, "status") ?? "active";
3239
3309
  const path42 = status === "history" ? `${BASE}/history-list` : `${BASE}/ongoing-list`;
3310
+ const algoOrdType = readString(args, "algoOrdType") ?? "contract_dca";
3240
3311
  const response = await context.client.privateGet(
3241
3312
  path42,
3242
3313
  compactObject({
3243
- algoOrdType: "contract_dca",
3314
+ algoOrdType,
3244
3315
  algoId: readString(args, "algoId"),
3245
3316
  instId: readString(args, "instId"),
3246
3317
  after: readString(args, "after"),
@@ -3255,21 +3326,23 @@ function registerDcaTools() {
3255
3326
  {
3256
3327
  name: "dca_get_order_details",
3257
3328
  module: "bot.dca",
3258
- description: "Get DCA bot detail by algo ID. Returns current position details.",
3329
+ description: "Get DCA bot position details (avgPx, upl, liqPx, etc).",
3259
3330
  isWrite: false,
3260
3331
  inputSchema: {
3261
3332
  type: "object",
3262
3333
  properties: {
3263
- algoId: { type: "string", description: "DCA bot algo order ID (not a trade ordId)" }
3334
+ algoId: { type: "string", description: "Algo order ID" },
3335
+ algoOrdType: { type: "string", enum: ["spot_dca", "contract_dca"] }
3264
3336
  },
3265
- required: ["algoId"]
3337
+ required: ["algoId", "algoOrdType"]
3266
3338
  },
3267
3339
  handler: async (rawArgs, context) => {
3268
3340
  const args = asRecord(rawArgs);
3269
3341
  const algoId = requireString(args, "algoId");
3342
+ const algoOrdType = requireString(args, "algoOrdType");
3270
3343
  const response = await context.client.privateGet(
3271
3344
  `${BASE}/position-details`,
3272
- { algoId, algoOrdType: "contract_dca" },
3345
+ { algoId, algoOrdType },
3273
3346
  privateRateLimit("dca_get_order_details", 20)
3274
3347
  );
3275
3348
  return normalizeResponse(response);
@@ -3278,29 +3351,31 @@ function registerDcaTools() {
3278
3351
  {
3279
3352
  name: "dca_get_sub_orders",
3280
3353
  module: "bot.dca",
3281
- description: "Query DCA bot cycles or orders within a cycle. Omit cycleId for cycle list; provide cycleId for orders.",
3354
+ description: "Get DCA cycles or orders in a cycle. Omit cycleId=cycle list; with cycleId=orders.",
3282
3355
  isWrite: false,
3283
3356
  inputSchema: {
3284
3357
  type: "object",
3285
3358
  properties: {
3286
- algoId: { type: "string", description: "DCA bot algo order ID (not a trade ordId)" },
3287
- cycleId: { type: "string", description: "Omit for cycle list; provide for orders within a cycle" },
3288
- after: { type: "string", description: "Cursor for older records (cycle-list mode only)" },
3289
- before: { type: "string", description: "Cursor for newer records (cycle-list mode only)" },
3290
- limit: { type: "number", description: "Default 100" }
3359
+ algoId: { type: "string", description: "Algo order ID" },
3360
+ algoOrdType: { type: "string", enum: ["spot_dca", "contract_dca"] },
3361
+ cycleId: { type: "string", description: "Omit for cycles; provide for orders" },
3362
+ after: { type: "string", description: "Pagination cursor" },
3363
+ before: { type: "string", description: "Pagination cursor" },
3364
+ limit: { type: "number" }
3291
3365
  },
3292
- required: ["algoId"]
3366
+ required: ["algoId", "algoOrdType"]
3293
3367
  },
3294
3368
  handler: async (rawArgs, context) => {
3295
3369
  const args = asRecord(rawArgs);
3296
3370
  const algoId = requireString(args, "algoId");
3371
+ const algoOrdType = requireString(args, "algoOrdType");
3297
3372
  const cycleId = readString(args, "cycleId");
3298
3373
  if (cycleId) {
3299
3374
  const response2 = await context.client.privateGet(
3300
3375
  `${BASE}/orders`,
3301
3376
  compactObject({
3302
3377
  algoId,
3303
- algoOrdType: "contract_dca",
3378
+ algoOrdType,
3304
3379
  cycleId,
3305
3380
  limit: readNumber(args, "limit")
3306
3381
  }),
@@ -3312,7 +3387,7 @@ function registerDcaTools() {
3312
3387
  `${BASE}/cycle-list`,
3313
3388
  compactObject({
3314
3389
  algoId,
3315
- algoOrdType: "contract_dca",
3390
+ algoOrdType,
3316
3391
  after: readString(args, "after"),
3317
3392
  before: readString(args, "before"),
3318
3393
  limit: readNumber(args, "limit")
@@ -7196,6 +7271,12 @@ function fail(label, detail, hints) {
7196
7271
  outputLine(` \u2192 ${hint}`);
7197
7272
  }
7198
7273
  }
7274
+ function warn(label, detail, hints = []) {
7275
+ outputLine(` \u26A0 ${label.padEnd(14)} ${detail}`);
7276
+ for (const hint of hints) {
7277
+ outputLine(` \u2192 ${hint}`);
7278
+ }
7279
+ }
7199
7280
  function section(title) {
7200
7281
  outputLine("");
7201
7282
  outputLine(` ${title}`);
@@ -7306,59 +7387,154 @@ function checkMcpEntryPoint(report) {
7306
7387
  return { entryPath: null, passed: false };
7307
7388
  }
7308
7389
  }
7309
- function checkClaudeDesktopConfig(report) {
7310
- section("Claude Desktop Config");
7311
- const configPath = getConfigPath("claude-desktop");
7312
- if (!configPath) {
7313
- ok("config path", "(not applicable on this platform)");
7314
- report.add("claude_cfg", "n/a");
7315
- return true;
7316
- }
7317
- if (!fs4.existsSync(configPath)) {
7318
- fail("config file", `not found: ${configPath}`, [
7319
- "Claude Desktop may not be installed",
7320
- "Run: okx setup --client claude-desktop to configure"
7321
- ]);
7322
- report.add("claude_cfg", `MISSING ${sanitize(configPath)}`);
7323
- return false;
7324
- }
7325
- ok("config file", configPath);
7326
- report.add("claude_cfg", sanitize(configPath));
7390
+ var MCP_SERVER_NAME = "okx-trade-mcp";
7391
+ var CLIENT_LIMITS = {
7392
+ cursor: { perServer: 40, total: 80 }
7393
+ };
7394
+ function checkJsonMcpConfig(configPath) {
7395
+ if (!fs4.existsSync(configPath)) return "missing";
7327
7396
  try {
7328
7397
  const raw = fs4.readFileSync(configPath, "utf8");
7329
7398
  const parsed = JSON.parse(raw);
7330
7399
  const mcpServers = parsed["mcpServers"];
7331
- if (!mcpServers) {
7332
- fail("mcp entry", "no mcpServers section found", [
7333
- "Run: okx setup --client claude-desktop"
7334
- ]);
7335
- report.add("claude_mcp", "NO_SECTION");
7336
- return false;
7337
- }
7400
+ if (!mcpServers) return "not-configured";
7338
7401
  const entries = Object.entries(mcpServers);
7339
- const mcpEntry = entries.find(([, v]) => {
7402
+ const found = entries.find(([name, v]) => {
7403
+ if (name.includes(MCP_SERVER_NAME)) return true;
7340
7404
  const val = v;
7341
7405
  const cmd = String(val["command"] ?? "");
7342
7406
  const args = val["args"] ?? [];
7343
- return cmd.includes("okx-trade-mcp") || args.some((a) => a.includes("okx-trade-mcp"));
7407
+ return cmd.includes(MCP_SERVER_NAME) || args.some((a) => a.includes(MCP_SERVER_NAME));
7344
7408
  });
7345
- if (mcpEntry) {
7346
- ok("mcp entry", `found: "${mcpEntry[0]}"`);
7347
- report.add("claude_mcp", `found:${mcpEntry[0]}`);
7348
- return true;
7409
+ return found ? "found" : "not-configured";
7410
+ } catch (_e) {
7411
+ return "parse-error";
7412
+ }
7413
+ }
7414
+ function checkClaudeCodeConfig() {
7415
+ const home = os2.homedir();
7416
+ const candidates = [
7417
+ path2.join(home, ".claude", "settings.json"),
7418
+ path2.join(home, ".claude.json")
7419
+ ];
7420
+ let anyFound = false;
7421
+ let anyParseError = false;
7422
+ for (const cfgPath of candidates) {
7423
+ if (!fs4.existsSync(cfgPath)) continue;
7424
+ anyFound = true;
7425
+ const result = checkJsonMcpConfig(cfgPath);
7426
+ if (result === "found") return "found";
7427
+ if (result === "parse-error") anyParseError = true;
7428
+ }
7429
+ if (!anyFound) return "missing";
7430
+ if (anyParseError) return "parse-error";
7431
+ return "not-configured";
7432
+ }
7433
+ function checkMcpClients(report) {
7434
+ section("MCP Client Config");
7435
+ const jsonClients = ["claude-desktop", "cursor", "windsurf"];
7436
+ const configuredClients = [];
7437
+ let anyFailed = false;
7438
+ for (const clientId of jsonClients) {
7439
+ const configPath = getConfigPath(clientId);
7440
+ if (!configPath) continue;
7441
+ const name = CLIENT_NAMES[clientId];
7442
+ const status = checkJsonMcpConfig(configPath);
7443
+ if (status === "missing") {
7444
+ continue;
7445
+ }
7446
+ if (status === "found") {
7447
+ ok(name, `configured (${sanitize(configPath)})`);
7448
+ report.add(`client_${clientId}`, `OK ${sanitize(configPath)}`);
7449
+ configuredClients.push(clientId);
7450
+ } else if (status === "not-configured") {
7451
+ fail(name, "okx-trade-mcp not found in mcpServers", [
7452
+ `Run: okx setup --client ${clientId}`
7453
+ ]);
7454
+ report.add(`client_${clientId}`, "NOT_CONFIGURED");
7455
+ anyFailed = true;
7349
7456
  } else {
7350
- fail("mcp entry", "okx-trade-mcp not found in mcpServers", [
7351
- "Run: okx setup --client claude-desktop"
7457
+ fail(name, `JSON parse error in ${sanitize(configPath)}`, [
7458
+ `Check ${sanitize(configPath)} for JSON syntax errors`,
7459
+ `Then run: okx setup --client ${clientId}`
7352
7460
  ]);
7353
- report.add("claude_mcp", "NOT_CONFIGURED");
7354
- return false;
7461
+ report.add(`client_${clientId}`, "PARSE_ERROR");
7462
+ anyFailed = true;
7463
+ }
7464
+ }
7465
+ const claudeCodeStatus = checkClaudeCodeConfig();
7466
+ if (claudeCodeStatus !== "missing") {
7467
+ const name = CLIENT_NAMES["claude-code"];
7468
+ if (claudeCodeStatus === "found") {
7469
+ ok(name, "configured");
7470
+ report.add("client_claude-code", "OK");
7471
+ configuredClients.push("claude-code");
7472
+ } else if (claudeCodeStatus === "not-configured") {
7473
+ warn(name, "installed but okx-trade-mcp not configured", [
7474
+ "Run: okx setup --client claude-code"
7475
+ ]);
7476
+ report.add("client_claude-code", "NOT_CONFIGURED");
7477
+ } else {
7478
+ fail(name, "settings file has JSON parse error", [
7479
+ "Run: okx setup --client claude-code"
7480
+ ]);
7481
+ report.add("client_claude-code", "PARSE_ERROR");
7482
+ anyFailed = true;
7355
7483
  }
7356
- } catch (e) {
7357
- fail("config parse", `JSON parse error: ${e instanceof Error ? e.message : String(e)}`, [
7358
- `Check ${configPath} for JSON syntax errors`
7484
+ }
7485
+ if (configuredClients.length === 0 && !anyFailed) {
7486
+ fail("no client", "no MCP client configuration found", [
7487
+ "Run: okx setup --client <client>",
7488
+ "Supported clients: claude-desktop, cursor, windsurf, claude-code"
7359
7489
  ]);
7360
- report.add("claude_cfg_parse", "FAIL");
7361
- return false;
7490
+ report.add("client_cfg", "NONE_FOUND");
7491
+ return { passed: false, configuredClients };
7492
+ }
7493
+ const passed = configuredClients.length > 0 && !anyFailed;
7494
+ report.add("client_cfg", passed ? `OK (${configuredClients.join(",")})` : "FAIL");
7495
+ return { passed, configuredClients };
7496
+ }
7497
+ function checkToolCount(report, configuredClients, getSpecs = allToolSpecs) {
7498
+ section("Tool Count");
7499
+ const specs = getSpecs();
7500
+ const totalCount = specs.length;
7501
+ const defaultModuleSet = new Set(DEFAULT_MODULES);
7502
+ const defaultCount = specs.filter((s) => defaultModuleSet.has(s.module)).length;
7503
+ const defaultModulesArg = DEFAULT_MODULES.join(",");
7504
+ const applicableLimits = configuredClients.map((id) => ({ id, limits: CLIENT_LIMITS[id] })).filter((x) => x.limits !== void 0);
7505
+ if (applicableLimits.length === 0) {
7506
+ ok("total tools", `${totalCount} tools loaded`);
7507
+ report.add("tool_count", `${totalCount}`);
7508
+ return;
7509
+ }
7510
+ let anyExceeded = false;
7511
+ for (const { id, limits } of applicableLimits) {
7512
+ const name = CLIENT_NAMES[id];
7513
+ if (totalCount > limits.total) {
7514
+ warn(
7515
+ "tool count",
7516
+ `${totalCount} tools loaded \u2014 exceeds ${name} limit (${limits.total} total / ${limits.perServer} per server)`,
7517
+ [
7518
+ `Use --modules to reduce: okx-trade-mcp --modules ${defaultModulesArg} (${defaultCount} tools)`
7519
+ ]
7520
+ );
7521
+ report.add("tool_count", `${totalCount} EXCEEDS_${id.toUpperCase()}_LIMIT`);
7522
+ anyExceeded = true;
7523
+ } else if (totalCount > limits.perServer) {
7524
+ warn(
7525
+ "tool count",
7526
+ `${totalCount} tools loaded \u2014 exceeds ${name} per-server limit (${limits.perServer})`,
7527
+ [
7528
+ `Use --modules to reduce: okx-trade-mcp --modules ${defaultModulesArg} (${defaultCount} tools)`
7529
+ ]
7530
+ );
7531
+ report.add("tool_count", `${totalCount} EXCEEDS_${id.toUpperCase()}_PER_SERVER_LIMIT`);
7532
+ anyExceeded = true;
7533
+ }
7534
+ }
7535
+ if (!anyExceeded) {
7536
+ ok("total tools", `${totalCount} tools loaded (within limits for all configured clients)`);
7537
+ report.add("tool_count", `${totalCount} OK`);
7362
7538
  }
7363
7539
  }
7364
7540
  function readLogTail(logPath) {
@@ -7551,9 +7727,10 @@ async function cmdDiagnoseMcp(options = {}) {
7551
7727
  checkMcpPackageVersion(report);
7552
7728
  const nodePassed = checkNodeCompat(report);
7553
7729
  const { entryPath, passed: entryPassed } = checkMcpEntryPoint(report);
7554
- const cfgPassed = checkClaudeDesktopConfig(report);
7730
+ const { passed: cfgPassed, configuredClients } = checkMcpClients(report);
7555
7731
  checkMcpLogs(report);
7556
7732
  const moduleLoadPassed = checkModuleLoading(entryPath, report);
7733
+ checkToolCount(report, configuredClients);
7557
7734
  let handshakePassed = false;
7558
7735
  if (entryPath && entryPassed && moduleLoadPassed) {
7559
7736
  handshakePassed = await checkStdioHandshake(entryPath, report);
@@ -7578,7 +7755,7 @@ async function cmdDiagnoseMcp(options = {}) {
7578
7755
 
7579
7756
  // src/commands/diagnose.ts
7580
7757
  var CLI_VERSION = readCliVersion();
7581
- var GIT_HASH = true ? "7ff8d64" : "dev";
7758
+ var GIT_HASH = true ? "35c609e" : "dev";
7582
7759
  function maskKey2(key) {
7583
7760
  if (!key) return "(not set)";
7584
7761
  if (key.length <= 8) return "****";
@@ -8421,27 +8598,27 @@ var HELP_TREE = {
8421
8598
  }
8422
8599
  },
8423
8600
  dca: {
8424
- description: "Contract DCA (Martingale) bot \u2014 leveraged recurring buys on futures/swaps",
8601
+ description: "DCA (Martingale) bot \u2014 spot or contract recurring buys",
8425
8602
  commands: {
8426
8603
  orders: {
8427
- usage: "okx bot dca orders [--algoId <id>] [--instId <id>] [--history]",
8428
- description: "List active or historical Contract DCA bot orders"
8604
+ usage: "okx bot dca orders [--algoOrdType <spot_dca|contract_dca>] [--algoId <id>] [--instId <id>] [--history]",
8605
+ description: "List DCA bots (spot and/or contract)"
8429
8606
  },
8430
8607
  details: {
8431
- usage: "okx bot dca details --algoId <id>",
8432
- description: "Get details of a specific Contract DCA bot order"
8608
+ usage: "okx bot dca details --algoOrdType <spot_dca|contract_dca> --algoId <id>",
8609
+ description: "Get DCA bot details (spot or contract)"
8433
8610
  },
8434
8611
  "sub-orders": {
8435
- usage: "okx bot dca sub-orders --algoId <id> [--cycleId <id>]",
8436
- description: "List cycles or orders within a cycle of a Contract DCA bot"
8612
+ usage: "okx bot dca sub-orders --algoOrdType <spot_dca|contract_dca> --algoId <id> [--cycleId <id>]",
8613
+ description: "Get DCA cycles/orders (spot or contract)"
8437
8614
  },
8438
8615
  create: {
8439
- usage: "okx bot dca create --instId <id> --lever <n> --direction <long|short>\n --initOrdAmt <n> --maxSafetyOrds <n> --tpPct <n>\n [--safetyOrdAmt <n>] [--pxSteps <n>] [--pxStepsMult <n>] [--volMult <n>]\n [--slPct <n>] [--slMode <limit|market>]\n [--allowReinvest <true|false>] [--triggerStrategy <instant|price|rsi>] [--triggerPx <price>]\n Note: safetyOrdAmt, pxSteps, pxStepsMult, volMult are required when maxSafetyOrds > 0",
8440
- description: "Create a new Contract DCA bot order"
8616
+ usage: "okx bot dca create --algoOrdType <spot_dca|contract_dca> --instId <id> --direction <long|short>\n --initOrdAmt <n> --maxSafetyOrds <n> --tpPct <n>\n [--lever <n>] [--safetyOrdAmt <n>] [--pxSteps <n>] [--pxStepsMult <n>] [--volMult <n>]\n [--slPct <n>] [--slMode <limit|market>] [--allowReinvest <true|false>]\n [--triggerStrategy <instant|price|rsi>] [--triggerPx <price>]\n [--triggerCond <cross_up|cross_down>] [--thold <n>] [--timeframe <tf>] [--timePeriod <n>]\n [--algoClOrdId <id>] [--reserveFunds <true|false>] [--tradeQuoteCcy <ccy>]\n Note: --lever required for contract_dca; safetyOrdAmt, pxSteps, pxStepsMult, volMult required when maxSafetyOrds > 0\n triggerStrategy: contract_dca supports instant|price|rsi; spot_dca supports instant|rsi",
8617
+ description: "Create a DCA (Martingale) bot (spot or contract)"
8441
8618
  },
8442
8619
  stop: {
8443
- usage: "okx bot dca stop --algoId <id>",
8444
- description: "Stop a running Contract DCA bot order"
8620
+ usage: "okx bot dca stop --algoOrdType <spot_dca|contract_dca> --algoId <id> [--stopType <1|2>]\n Note: --stopType required for spot_dca (1=sell all, 2=keep tokens)",
8621
+ description: "Stop a DCA bot (spot or contract)"
8445
8622
  }
8446
8623
  }
8447
8624
  }
@@ -8676,7 +8853,7 @@ var CLI_OPTIONS = {
8676
8853
  autoCxl: { type: "boolean", default: false },
8677
8854
  clOrdId: { type: "string" },
8678
8855
  newPx: { type: "string" },
8679
- // dca bot (contract only)
8856
+ // dca bot (spot & contract)
8680
8857
  initOrdAmt: { type: "string" },
8681
8858
  safetyOrdAmt: { type: "string" },
8682
8859
  maxSafetyOrds: { type: "string" },
@@ -8689,7 +8866,13 @@ var CLI_OPTIONS = {
8689
8866
  allowReinvest: { type: "string" },
8690
8867
  triggerStrategy: { type: "string" },
8691
8868
  triggerPx: { type: "string" },
8869
+ triggerCond: { type: "string" },
8870
+ thold: { type: "string" },
8871
+ timeframe: { type: "string" },
8872
+ timePeriod: { type: "string" },
8692
8873
  cycleId: { type: "string" },
8874
+ reserveFunds: { type: "string" },
8875
+ tradeQuoteCcy: { type: "string" },
8693
8876
  // i18n
8694
8877
  lang: { type: "string" },
8695
8878
  // option
@@ -8909,10 +9092,15 @@ async function cmdMarketTicker(run, instId, json) {
8909
9092
  printKv({
8910
9093
  instId: t["instId"],
8911
9094
  last: t["last"],
8912
- "24h change %": t["sodUtc8"],
9095
+ "24h open": t["open24h"],
8913
9096
  "24h high": t["high24h"],
8914
9097
  "24h low": t["low24h"],
8915
9098
  "24h vol": t["vol24h"],
9099
+ "24h change %": (() => {
9100
+ const last = Number(t["last"]);
9101
+ const open24h = Number(t["open24h"]);
9102
+ return open24h !== 0 ? ((last - open24h) / open24h * 100).toFixed(2) + "%" : "N/A";
9103
+ })(),
8916
9104
  time: new Date(Number(t["ts"])).toLocaleString()
8917
9105
  });
8918
9106
  }
@@ -10949,6 +11137,7 @@ async function cmdGridStop(run, opts) {
10949
11137
  async function cmdDcaCreate(run, opts) {
10950
11138
  const result = await run("dca_create_order", {
10951
11139
  instId: opts.instId,
11140
+ algoOrdType: opts.algoOrdType,
10952
11141
  lever: opts.lever,
10953
11142
  direction: opts.direction,
10954
11143
  initOrdAmt: opts.initOrdAmt,
@@ -10962,25 +11151,33 @@ async function cmdDcaCreate(run, opts) {
10962
11151
  slMode: opts.slMode,
10963
11152
  allowReinvest: opts.allowReinvest,
10964
11153
  triggerStrategy: opts.triggerStrategy,
10965
- triggerPx: opts.triggerPx
11154
+ triggerPx: opts.triggerPx,
11155
+ triggerCond: opts.triggerCond,
11156
+ thold: opts.thold,
11157
+ timeframe: opts.timeframe,
11158
+ timePeriod: opts.timePeriod,
11159
+ algoClOrdId: opts.algoClOrdId,
11160
+ reserveFunds: opts.reserveFunds,
11161
+ tradeQuoteCcy: opts.tradeQuoteCcy
10966
11162
  });
10967
11163
  const data = getData7(result);
10968
11164
  if (opts.json) return printJson(data);
10969
- const r = data?.[0];
10970
11165
  emitWriteResult5(data?.[0], "DCA bot created", "algoId");
10971
11166
  }
10972
11167
  async function cmdDcaStop(run, opts) {
10973
11168
  const result = await run("dca_stop_order", {
10974
- algoId: opts.algoId
11169
+ algoId: opts.algoId,
11170
+ algoOrdType: opts.algoOrdType,
11171
+ stopType: opts.stopType
10975
11172
  });
10976
11173
  const data = getData7(result);
10977
11174
  if (opts.json) return printJson(data);
10978
- const r = data?.[0];
10979
11175
  emitWriteResult5(data?.[0], "DCA bot stopped", "algoId");
10980
11176
  }
10981
11177
  async function cmdDcaOrders(run, opts) {
10982
11178
  const result = await run("dca_get_orders", {
10983
11179
  status: opts.history ? "history" : "active",
11180
+ algoOrdType: opts.algoOrdType,
10984
11181
  algoId: opts.algoId,
10985
11182
  instId: opts.instId
10986
11183
  });
@@ -10994,6 +11191,7 @@ async function cmdDcaOrders(run, opts) {
10994
11191
  orders.map((o) => ({
10995
11192
  algoId: o["algoId"],
10996
11193
  instId: o["instId"],
11194
+ type: o["algoOrdType"],
10997
11195
  state: o["state"],
10998
11196
  pnl: o["pnl"],
10999
11197
  pnlRatio: o["pnlRatio"],
@@ -11003,7 +11201,8 @@ async function cmdDcaOrders(run, opts) {
11003
11201
  }
11004
11202
  async function cmdDcaDetails(run, opts) {
11005
11203
  const result = await run("dca_get_order_details", {
11006
- algoId: opts.algoId
11204
+ algoId: opts.algoId,
11205
+ algoOrdType: opts.algoOrdType
11007
11206
  });
11008
11207
  const detail = (getData7(result) ?? [])[0];
11009
11208
  if (!detail) {
@@ -11013,6 +11212,7 @@ async function cmdDcaDetails(run, opts) {
11013
11212
  if (opts.json) return printJson(detail);
11014
11213
  printKv({
11015
11214
  algoId: detail["algoId"],
11215
+ algoOrdType: detail["algoOrdType"],
11016
11216
  instId: detail["instId"],
11017
11217
  sz: detail["sz"],
11018
11218
  avgPx: detail["avgPx"],
@@ -11030,26 +11230,42 @@ async function cmdDcaDetails(run, opts) {
11030
11230
  async function cmdDcaSubOrders(run, opts) {
11031
11231
  const result = await run("dca_get_sub_orders", {
11032
11232
  algoId: opts.algoId,
11233
+ algoOrdType: opts.algoOrdType,
11033
11234
  cycleId: opts.cycleId
11034
11235
  });
11035
- const orders = getData7(result) ?? [];
11036
- if (opts.json) return printJson(orders);
11037
- if (!orders.length) {
11236
+ const rows = getData7(result) ?? [];
11237
+ if (opts.json) return printJson(rows);
11238
+ if (!rows.length) {
11038
11239
  outputLine("No sub-orders");
11039
11240
  return;
11040
11241
  }
11041
- printTable(
11042
- orders.map((o) => ({
11043
- cycleId: o["cycleId"],
11044
- status: o["cycleStatus"],
11045
- current: o["currentCycle"] ? "yes" : "",
11046
- avgPx: o["avgPx"],
11047
- tpPx: o["tpPx"],
11048
- realizedPnl: o["realizedPnl"],
11049
- fee: o["fee"],
11050
- startTime: o["startTime"] ? new Date(Number(o["startTime"])).toLocaleString() : ""
11051
- }))
11052
- );
11242
+ if (opts.cycleId) {
11243
+ printTable(
11244
+ rows.map((o) => ({
11245
+ ordId: o["ordId"],
11246
+ side: o["side"],
11247
+ ordType: o["ordType"],
11248
+ px: o["px"],
11249
+ filledSz: o["filledSz"],
11250
+ avgFillPx: o["avgFillPx"],
11251
+ state: o["state"],
11252
+ fee: o["fee"]
11253
+ }))
11254
+ );
11255
+ } else {
11256
+ printTable(
11257
+ rows.map((o) => ({
11258
+ cycleId: o["cycleId"],
11259
+ status: o["cycleStatus"],
11260
+ current: o["currentCycle"] ? "yes" : "",
11261
+ avgPx: o["avgPx"],
11262
+ tpPx: o["tpPx"],
11263
+ realizedPnl: o["realizedPnl"],
11264
+ fee: o["fee"],
11265
+ startTime: o["startTime"] ? new Date(Number(o["startTime"])).toLocaleString() : ""
11266
+ }))
11267
+ );
11268
+ }
11053
11269
  }
11054
11270
 
11055
11271
  // src/commands/onchain-earn.ts
@@ -11349,7 +11565,7 @@ async function cmdDcdQuoteAndBuy(run, opts) {
11349
11565
  // src/index.ts
11350
11566
  var _require3 = createRequire3(import.meta.url);
11351
11567
  var CLI_VERSION2 = _require3("../package.json").version;
11352
- var GIT_HASH2 = true ? "7ff8d64" : "dev";
11568
+ var GIT_HASH2 = true ? "35c609e" : "dev";
11353
11569
  function handleConfigCommand(action, rest, json, lang, force) {
11354
11570
  if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
11355
11571
  if (action === "show") return cmdConfigShow(json);
@@ -11956,15 +12172,17 @@ function handleBotGridCommand(run, v, rest, json) {
11956
12172
  });
11957
12173
  }
11958
12174
  function handleBotDcaCommand(run, subAction, v, json) {
12175
+ const algoOrdType = v.algoOrdType ?? "contract_dca";
11959
12176
  if (subAction === "orders")
11960
- return cmdDcaOrders(run, { algoId: v.algoId, instId: v.instId, history: v.history ?? false, json });
12177
+ return cmdDcaOrders(run, { algoOrdType, algoId: v.algoId, instId: v.instId, history: v.history ?? false, json });
11961
12178
  if (subAction === "details")
11962
- return cmdDcaDetails(run, { algoId: v.algoId, json });
12179
+ return cmdDcaDetails(run, { algoId: v.algoId, algoOrdType, json });
11963
12180
  if (subAction === "sub-orders")
11964
- return cmdDcaSubOrders(run, { algoId: v.algoId, cycleId: v.cycleId, json });
12181
+ return cmdDcaSubOrders(run, { algoId: v.algoId, algoOrdType, cycleId: v.cycleId, json });
11965
12182
  if (subAction === "create")
11966
12183
  return cmdDcaCreate(run, {
11967
12184
  instId: v.instId,
12185
+ algoOrdType,
11968
12186
  lever: v.lever,
11969
12187
  direction: v.direction,
11970
12188
  initOrdAmt: v.initOrdAmt,
@@ -11979,10 +12197,17 @@ function handleBotDcaCommand(run, subAction, v, json) {
11979
12197
  allowReinvest: v.allowReinvest,
11980
12198
  triggerStrategy: v.triggerStrategy,
11981
12199
  triggerPx: v.triggerPx,
12200
+ triggerCond: v.triggerCond,
12201
+ thold: v.thold,
12202
+ timeframe: v.timeframe,
12203
+ timePeriod: v.timePeriod,
12204
+ algoClOrdId: v.algoClOrdId,
12205
+ reserveFunds: v.reserveFunds,
12206
+ tradeQuoteCcy: v.tradeQuoteCcy,
11982
12207
  json
11983
12208
  });
11984
12209
  if (subAction === "stop")
11985
- return cmdDcaStop(run, { algoId: v.algoId, json });
12210
+ return cmdDcaStop(run, { algoId: v.algoId, algoOrdType, stopType: v.stopType, json });
11986
12211
  }
11987
12212
  function handleBotCommand(run, action, rest, v, json) {
11988
12213
  if (action === "grid") return handleBotGridCommand(run, v, rest, json);