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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3131,32 +3131,44 @@ 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"] },
3154
+ triggerPx: { type: "string", description: "Need if triggerStrategy=price" },
3155
+ algoClOrdId: { type: "string", description: "Client order ID, 1-32 chars" },
3156
+ // Backend expects boolean, but kept as string for backward compatibility with older clients.
3157
+ reserveFunds: { type: "string", description: "'true' or 'false', default 'true'" },
3158
+ tradeQuoteCcy: { type: "string" }
3154
3159
  },
3155
- required: ["instId", "lever", "direction", "initOrdAmt", "maxSafetyOrds", "tpPct"]
3160
+ required: ["instId", "algoOrdType", "direction", "initOrdAmt", "maxSafetyOrds", "tpPct"]
3156
3161
  },
3157
3162
  handler: async (rawArgs, context) => {
3158
3163
  const args = asRecord(rawArgs);
3159
3164
  const instId = requireString(args, "instId");
3165
+ const algoOrdType = requireString(args, "algoOrdType");
3166
+ if (algoOrdType === "contract_dca" && !readString(args, "lever")) {
3167
+ throw new OkxApiError("lever is required for contract_dca", {
3168
+ code: "VALIDATION",
3169
+ endpoint: `${BASE}/create`
3170
+ });
3171
+ }
3160
3172
  const triggerStrategy = readString(args, "triggerStrategy") ?? "instant";
3161
3173
  const triggerParam = {
3162
3174
  triggerAction: "start",
@@ -3165,24 +3177,55 @@ function registerDcaTools() {
3165
3177
  if (triggerStrategy === "price") {
3166
3178
  triggerParam["triggerPx"] = requireString(args, "triggerPx");
3167
3179
  }
3180
+ const maxSafetyOrds = requireString(args, "maxSafetyOrds");
3181
+ if (Number(maxSafetyOrds) > 0) {
3182
+ if (!readString(args, "safetyOrdAmt")) {
3183
+ throw new OkxApiError("safetyOrdAmt is required when maxSafetyOrds > 0", {
3184
+ code: "VALIDATION",
3185
+ endpoint: `${BASE}/create`
3186
+ });
3187
+ }
3188
+ if (!readString(args, "pxSteps")) {
3189
+ throw new OkxApiError("pxSteps is required when maxSafetyOrds > 0", {
3190
+ code: "VALIDATION",
3191
+ endpoint: `${BASE}/create`
3192
+ });
3193
+ }
3194
+ if (!readString(args, "pxStepsMult")) {
3195
+ throw new OkxApiError("pxStepsMult is required when maxSafetyOrds > 0", {
3196
+ code: "VALIDATION",
3197
+ endpoint: `${BASE}/create`
3198
+ });
3199
+ }
3200
+ if (!readString(args, "volMult")) {
3201
+ throw new OkxApiError("volMult is required when maxSafetyOrds > 0", {
3202
+ code: "VALIDATION",
3203
+ endpoint: `${BASE}/create`
3204
+ });
3205
+ }
3206
+ }
3168
3207
  const response = await context.client.privatePost(
3169
3208
  `${BASE}/create`,
3170
3209
  compactObject({
3171
3210
  instId,
3172
- algoOrdType: "contract_dca",
3173
- lever: requireString(args, "lever"),
3211
+ algoOrdType,
3212
+ lever: readString(args, "lever"),
3174
3213
  direction: requireString(args, "direction"),
3175
3214
  initOrdAmt: requireString(args, "initOrdAmt"),
3176
3215
  safetyOrdAmt: readString(args, "safetyOrdAmt"),
3177
- maxSafetyOrds: requireString(args, "maxSafetyOrds"),
3216
+ maxSafetyOrds,
3178
3217
  pxSteps: readString(args, "pxSteps"),
3179
3218
  pxStepsMult: readString(args, "pxStepsMult"),
3180
3219
  volMult: readString(args, "volMult"),
3181
3220
  tpPct: requireString(args, "tpPct"),
3182
3221
  slPct: readString(args, "slPct"),
3183
3222
  slMode: readString(args, "slMode"),
3184
- allowReinvest: readString(args, "allowReinvest"),
3185
- triggerParams: [triggerParam]
3223
+ allowReinvest: args["allowReinvest"] !== void 0 ? args["allowReinvest"] === true || args["allowReinvest"] === "true" : void 0,
3224
+ triggerParams: [triggerParam],
3225
+ tag: context.config.sourceTag,
3226
+ algoClOrdId: readString(args, "algoClOrdId"),
3227
+ reserveFunds: readString(args, "reserveFunds"),
3228
+ tradeQuoteCcy: readString(args, "tradeQuoteCcy")
3186
3229
  }),
3187
3230
  privateRateLimit("dca_create_order", 20)
3188
3231
  );
@@ -3192,21 +3235,31 @@ function registerDcaTools() {
3192
3235
  {
3193
3236
  name: "dca_stop_order",
3194
3237
  module: "bot.dca",
3195
- description: "Stop a running Contract DCA bot. [CAUTION] This will stop the bot.",
3238
+ description: "Stop a running DCA bot. [CAUTION] spot_dca needs stopType: 1=sell, 2=keep.",
3196
3239
  isWrite: true,
3197
3240
  inputSchema: {
3198
3241
  type: "object",
3199
3242
  properties: {
3200
- algoId: { type: "string", description: "DCA bot algo order ID (not a trade ordId)" }
3243
+ algoId: { type: "string", description: "Algo order ID" },
3244
+ algoOrdType: { type: "string", enum: ["spot_dca", "contract_dca"] },
3245
+ stopType: { type: "string", enum: ["1", "2"], description: "Required for spot_dca: 1=sell all, 2=keep tokens" }
3201
3246
  },
3202
- required: ["algoId"]
3247
+ required: ["algoId", "algoOrdType"]
3203
3248
  },
3204
3249
  handler: async (rawArgs, context) => {
3205
3250
  const args = asRecord(rawArgs);
3206
3251
  const algoId = requireString(args, "algoId");
3252
+ const algoOrdType = requireString(args, "algoOrdType");
3253
+ const stopType = readString(args, "stopType");
3254
+ if (algoOrdType === "spot_dca" && !stopType) {
3255
+ throw new OkxApiError(
3256
+ "stopType is required for spot_dca. Use '1' (sell all tokens) or '2' (keep tokens)",
3257
+ { code: "VALIDATION", endpoint: `${BASE}/stop` }
3258
+ );
3259
+ }
3207
3260
  const response = await context.client.privatePost(
3208
3261
  `${BASE}/stop`,
3209
- { algoId, algoOrdType: "contract_dca" },
3262
+ compactObject({ algoId, algoOrdType, stopType }),
3210
3263
  privateRateLimit("dca_stop_order", 20)
3211
3264
  );
3212
3265
  return normalizeWrite2(response);
@@ -3215,21 +3268,18 @@ function registerDcaTools() {
3215
3268
  {
3216
3269
  name: "dca_get_orders",
3217
3270
  module: "bot.dca",
3218
- description: "List DCA bots. status='active' for running; 'history' for stopped.",
3271
+ description: "List DCA bots. Default: active (running). Use status=history for stopped.",
3219
3272
  isWrite: false,
3220
3273
  inputSchema: {
3221
3274
  type: "object",
3222
3275
  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" }
3276
+ status: { type: "string", enum: ["active", "history"] },
3277
+ algoOrdType: { type: "string", enum: ["spot_dca", "contract_dca"], description: "Default: contract_dca" },
3278
+ algoId: { type: "string", description: "Algo order ID" },
3279
+ instId: { type: "string" },
3280
+ after: { type: "string", description: "Pagination cursor" },
3281
+ before: { type: "string", description: "Pagination cursor" },
3282
+ limit: { type: "number" }
3233
3283
  },
3234
3284
  required: []
3235
3285
  },
@@ -3237,10 +3287,11 @@ function registerDcaTools() {
3237
3287
  const args = asRecord(rawArgs);
3238
3288
  const status = readString(args, "status") ?? "active";
3239
3289
  const path42 = status === "history" ? `${BASE}/history-list` : `${BASE}/ongoing-list`;
3290
+ const algoOrdType = readString(args, "algoOrdType") ?? "contract_dca";
3240
3291
  const response = await context.client.privateGet(
3241
3292
  path42,
3242
3293
  compactObject({
3243
- algoOrdType: "contract_dca",
3294
+ algoOrdType,
3244
3295
  algoId: readString(args, "algoId"),
3245
3296
  instId: readString(args, "instId"),
3246
3297
  after: readString(args, "after"),
@@ -3255,21 +3306,23 @@ function registerDcaTools() {
3255
3306
  {
3256
3307
  name: "dca_get_order_details",
3257
3308
  module: "bot.dca",
3258
- description: "Get DCA bot detail by algo ID. Returns current position details.",
3309
+ description: "Get DCA bot position details (avgPx, upl, liqPx, etc).",
3259
3310
  isWrite: false,
3260
3311
  inputSchema: {
3261
3312
  type: "object",
3262
3313
  properties: {
3263
- algoId: { type: "string", description: "DCA bot algo order ID (not a trade ordId)" }
3314
+ algoId: { type: "string", description: "Algo order ID" },
3315
+ algoOrdType: { type: "string", enum: ["spot_dca", "contract_dca"] }
3264
3316
  },
3265
- required: ["algoId"]
3317
+ required: ["algoId", "algoOrdType"]
3266
3318
  },
3267
3319
  handler: async (rawArgs, context) => {
3268
3320
  const args = asRecord(rawArgs);
3269
3321
  const algoId = requireString(args, "algoId");
3322
+ const algoOrdType = requireString(args, "algoOrdType");
3270
3323
  const response = await context.client.privateGet(
3271
3324
  `${BASE}/position-details`,
3272
- { algoId, algoOrdType: "contract_dca" },
3325
+ { algoId, algoOrdType },
3273
3326
  privateRateLimit("dca_get_order_details", 20)
3274
3327
  );
3275
3328
  return normalizeResponse(response);
@@ -3278,29 +3331,31 @@ function registerDcaTools() {
3278
3331
  {
3279
3332
  name: "dca_get_sub_orders",
3280
3333
  module: "bot.dca",
3281
- description: "Query DCA bot cycles or orders within a cycle. Omit cycleId for cycle list; provide cycleId for orders.",
3334
+ description: "Get DCA cycles or orders in a cycle. Omit cycleId=cycle list; with cycleId=orders.",
3282
3335
  isWrite: false,
3283
3336
  inputSchema: {
3284
3337
  type: "object",
3285
3338
  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" }
3339
+ algoId: { type: "string", description: "Algo order ID" },
3340
+ algoOrdType: { type: "string", enum: ["spot_dca", "contract_dca"] },
3341
+ cycleId: { type: "string", description: "Omit for cycles; provide for orders" },
3342
+ after: { type: "string", description: "Pagination cursor" },
3343
+ before: { type: "string", description: "Pagination cursor" },
3344
+ limit: { type: "number" }
3291
3345
  },
3292
- required: ["algoId"]
3346
+ required: ["algoId", "algoOrdType"]
3293
3347
  },
3294
3348
  handler: async (rawArgs, context) => {
3295
3349
  const args = asRecord(rawArgs);
3296
3350
  const algoId = requireString(args, "algoId");
3351
+ const algoOrdType = requireString(args, "algoOrdType");
3297
3352
  const cycleId = readString(args, "cycleId");
3298
3353
  if (cycleId) {
3299
3354
  const response2 = await context.client.privateGet(
3300
3355
  `${BASE}/orders`,
3301
3356
  compactObject({
3302
3357
  algoId,
3303
- algoOrdType: "contract_dca",
3358
+ algoOrdType,
3304
3359
  cycleId,
3305
3360
  limit: readNumber(args, "limit")
3306
3361
  }),
@@ -3312,7 +3367,7 @@ function registerDcaTools() {
3312
3367
  `${BASE}/cycle-list`,
3313
3368
  compactObject({
3314
3369
  algoId,
3315
- algoOrdType: "contract_dca",
3370
+ algoOrdType,
3316
3371
  after: readString(args, "after"),
3317
3372
  before: readString(args, "before"),
3318
3373
  limit: readNumber(args, "limit")
@@ -7196,6 +7251,12 @@ function fail(label, detail, hints) {
7196
7251
  outputLine(` \u2192 ${hint}`);
7197
7252
  }
7198
7253
  }
7254
+ function warn(label, detail, hints = []) {
7255
+ outputLine(` \u26A0 ${label.padEnd(14)} ${detail}`);
7256
+ for (const hint of hints) {
7257
+ outputLine(` \u2192 ${hint}`);
7258
+ }
7259
+ }
7199
7260
  function section(title) {
7200
7261
  outputLine("");
7201
7262
  outputLine(` ${title}`);
@@ -7306,59 +7367,154 @@ function checkMcpEntryPoint(report) {
7306
7367
  return { entryPath: null, passed: false };
7307
7368
  }
7308
7369
  }
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));
7370
+ var MCP_SERVER_NAME = "okx-trade-mcp";
7371
+ var CLIENT_LIMITS = {
7372
+ cursor: { perServer: 40, total: 80 }
7373
+ };
7374
+ function checkJsonMcpConfig(configPath) {
7375
+ if (!fs4.existsSync(configPath)) return "missing";
7327
7376
  try {
7328
7377
  const raw = fs4.readFileSync(configPath, "utf8");
7329
7378
  const parsed = JSON.parse(raw);
7330
7379
  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
- }
7380
+ if (!mcpServers) return "not-configured";
7338
7381
  const entries = Object.entries(mcpServers);
7339
- const mcpEntry = entries.find(([, v]) => {
7382
+ const found = entries.find(([name, v]) => {
7383
+ if (name.includes(MCP_SERVER_NAME)) return true;
7340
7384
  const val = v;
7341
7385
  const cmd = String(val["command"] ?? "");
7342
7386
  const args = val["args"] ?? [];
7343
- return cmd.includes("okx-trade-mcp") || args.some((a) => a.includes("okx-trade-mcp"));
7387
+ return cmd.includes(MCP_SERVER_NAME) || args.some((a) => a.includes(MCP_SERVER_NAME));
7344
7388
  });
7345
- if (mcpEntry) {
7346
- ok("mcp entry", `found: "${mcpEntry[0]}"`);
7347
- report.add("claude_mcp", `found:${mcpEntry[0]}`);
7348
- return true;
7389
+ return found ? "found" : "not-configured";
7390
+ } catch (_e) {
7391
+ return "parse-error";
7392
+ }
7393
+ }
7394
+ function checkClaudeCodeConfig() {
7395
+ const home = os2.homedir();
7396
+ const candidates = [
7397
+ path2.join(home, ".claude", "settings.json"),
7398
+ path2.join(home, ".claude.json")
7399
+ ];
7400
+ let anyFound = false;
7401
+ let anyParseError = false;
7402
+ for (const cfgPath of candidates) {
7403
+ if (!fs4.existsSync(cfgPath)) continue;
7404
+ anyFound = true;
7405
+ const result = checkJsonMcpConfig(cfgPath);
7406
+ if (result === "found") return "found";
7407
+ if (result === "parse-error") anyParseError = true;
7408
+ }
7409
+ if (!anyFound) return "missing";
7410
+ if (anyParseError) return "parse-error";
7411
+ return "not-configured";
7412
+ }
7413
+ function checkMcpClients(report) {
7414
+ section("MCP Client Config");
7415
+ const jsonClients = ["claude-desktop", "cursor", "windsurf"];
7416
+ const configuredClients = [];
7417
+ let anyFailed = false;
7418
+ for (const clientId of jsonClients) {
7419
+ const configPath = getConfigPath(clientId);
7420
+ if (!configPath) continue;
7421
+ const name = CLIENT_NAMES[clientId];
7422
+ const status = checkJsonMcpConfig(configPath);
7423
+ if (status === "missing") {
7424
+ continue;
7425
+ }
7426
+ if (status === "found") {
7427
+ ok(name, `configured (${sanitize(configPath)})`);
7428
+ report.add(`client_${clientId}`, `OK ${sanitize(configPath)}`);
7429
+ configuredClients.push(clientId);
7430
+ } else if (status === "not-configured") {
7431
+ fail(name, "okx-trade-mcp not found in mcpServers", [
7432
+ `Run: okx setup --client ${clientId}`
7433
+ ]);
7434
+ report.add(`client_${clientId}`, "NOT_CONFIGURED");
7435
+ anyFailed = true;
7349
7436
  } else {
7350
- fail("mcp entry", "okx-trade-mcp not found in mcpServers", [
7351
- "Run: okx setup --client claude-desktop"
7437
+ fail(name, `JSON parse error in ${sanitize(configPath)}`, [
7438
+ `Check ${sanitize(configPath)} for JSON syntax errors`,
7439
+ `Then run: okx setup --client ${clientId}`
7352
7440
  ]);
7353
- report.add("claude_mcp", "NOT_CONFIGURED");
7354
- return false;
7441
+ report.add(`client_${clientId}`, "PARSE_ERROR");
7442
+ anyFailed = true;
7443
+ }
7444
+ }
7445
+ const claudeCodeStatus = checkClaudeCodeConfig();
7446
+ if (claudeCodeStatus !== "missing") {
7447
+ const name = CLIENT_NAMES["claude-code"];
7448
+ if (claudeCodeStatus === "found") {
7449
+ ok(name, "configured");
7450
+ report.add("client_claude-code", "OK");
7451
+ configuredClients.push("claude-code");
7452
+ } else if (claudeCodeStatus === "not-configured") {
7453
+ warn(name, "installed but okx-trade-mcp not configured", [
7454
+ "Run: okx setup --client claude-code"
7455
+ ]);
7456
+ report.add("client_claude-code", "NOT_CONFIGURED");
7457
+ } else {
7458
+ fail(name, "settings file has JSON parse error", [
7459
+ "Run: okx setup --client claude-code"
7460
+ ]);
7461
+ report.add("client_claude-code", "PARSE_ERROR");
7462
+ anyFailed = true;
7355
7463
  }
7356
- } catch (e) {
7357
- fail("config parse", `JSON parse error: ${e instanceof Error ? e.message : String(e)}`, [
7358
- `Check ${configPath} for JSON syntax errors`
7464
+ }
7465
+ if (configuredClients.length === 0 && !anyFailed) {
7466
+ fail("no client", "no MCP client configuration found", [
7467
+ "Run: okx setup --client <client>",
7468
+ "Supported clients: claude-desktop, cursor, windsurf, claude-code"
7359
7469
  ]);
7360
- report.add("claude_cfg_parse", "FAIL");
7361
- return false;
7470
+ report.add("client_cfg", "NONE_FOUND");
7471
+ return { passed: false, configuredClients };
7472
+ }
7473
+ const passed = configuredClients.length > 0 && !anyFailed;
7474
+ report.add("client_cfg", passed ? `OK (${configuredClients.join(",")})` : "FAIL");
7475
+ return { passed, configuredClients };
7476
+ }
7477
+ function checkToolCount(report, configuredClients, getSpecs = allToolSpecs) {
7478
+ section("Tool Count");
7479
+ const specs = getSpecs();
7480
+ const totalCount = specs.length;
7481
+ const defaultModuleSet = new Set(DEFAULT_MODULES);
7482
+ const defaultCount = specs.filter((s) => defaultModuleSet.has(s.module)).length;
7483
+ const defaultModulesArg = DEFAULT_MODULES.join(",");
7484
+ const applicableLimits = configuredClients.map((id) => ({ id, limits: CLIENT_LIMITS[id] })).filter((x) => x.limits !== void 0);
7485
+ if (applicableLimits.length === 0) {
7486
+ ok("total tools", `${totalCount} tools loaded`);
7487
+ report.add("tool_count", `${totalCount}`);
7488
+ return;
7489
+ }
7490
+ let anyExceeded = false;
7491
+ for (const { id, limits } of applicableLimits) {
7492
+ const name = CLIENT_NAMES[id];
7493
+ if (totalCount > limits.total) {
7494
+ warn(
7495
+ "tool count",
7496
+ `${totalCount} tools loaded \u2014 exceeds ${name} limit (${limits.total} total / ${limits.perServer} per server)`,
7497
+ [
7498
+ `Use --modules to reduce: okx-trade-mcp --modules ${defaultModulesArg} (${defaultCount} tools)`
7499
+ ]
7500
+ );
7501
+ report.add("tool_count", `${totalCount} EXCEEDS_${id.toUpperCase()}_LIMIT`);
7502
+ anyExceeded = true;
7503
+ } else if (totalCount > limits.perServer) {
7504
+ warn(
7505
+ "tool count",
7506
+ `${totalCount} tools loaded \u2014 exceeds ${name} per-server limit (${limits.perServer})`,
7507
+ [
7508
+ `Use --modules to reduce: okx-trade-mcp --modules ${defaultModulesArg} (${defaultCount} tools)`
7509
+ ]
7510
+ );
7511
+ report.add("tool_count", `${totalCount} EXCEEDS_${id.toUpperCase()}_PER_SERVER_LIMIT`);
7512
+ anyExceeded = true;
7513
+ }
7514
+ }
7515
+ if (!anyExceeded) {
7516
+ ok("total tools", `${totalCount} tools loaded (within limits for all configured clients)`);
7517
+ report.add("tool_count", `${totalCount} OK`);
7362
7518
  }
7363
7519
  }
7364
7520
  function readLogTail(logPath) {
@@ -7551,9 +7707,10 @@ async function cmdDiagnoseMcp(options = {}) {
7551
7707
  checkMcpPackageVersion(report);
7552
7708
  const nodePassed = checkNodeCompat(report);
7553
7709
  const { entryPath, passed: entryPassed } = checkMcpEntryPoint(report);
7554
- const cfgPassed = checkClaudeDesktopConfig(report);
7710
+ const { passed: cfgPassed, configuredClients } = checkMcpClients(report);
7555
7711
  checkMcpLogs(report);
7556
7712
  const moduleLoadPassed = checkModuleLoading(entryPath, report);
7713
+ checkToolCount(report, configuredClients);
7557
7714
  let handshakePassed = false;
7558
7715
  if (entryPath && entryPassed && moduleLoadPassed) {
7559
7716
  handshakePassed = await checkStdioHandshake(entryPath, report);
@@ -7578,7 +7735,7 @@ async function cmdDiagnoseMcp(options = {}) {
7578
7735
 
7579
7736
  // src/commands/diagnose.ts
7580
7737
  var CLI_VERSION = readCliVersion();
7581
- var GIT_HASH = true ? "7ff8d64" : "dev";
7738
+ var GIT_HASH = true ? "c270a0b" : "dev";
7582
7739
  function maskKey2(key) {
7583
7740
  if (!key) return "(not set)";
7584
7741
  if (key.length <= 8) return "****";
@@ -8421,27 +8578,27 @@ var HELP_TREE = {
8421
8578
  }
8422
8579
  },
8423
8580
  dca: {
8424
- description: "Contract DCA (Martingale) bot \u2014 leveraged recurring buys on futures/swaps",
8581
+ description: "DCA (Martingale) bot \u2014 spot or contract recurring buys",
8425
8582
  commands: {
8426
8583
  orders: {
8427
- usage: "okx bot dca orders [--algoId <id>] [--instId <id>] [--history]",
8428
- description: "List active or historical Contract DCA bot orders"
8584
+ usage: "okx bot dca orders [--algoOrdType <spot_dca|contract_dca>] [--algoId <id>] [--instId <id>] [--history]",
8585
+ description: "List DCA bots (spot and/or contract)"
8429
8586
  },
8430
8587
  details: {
8431
- usage: "okx bot dca details --algoId <id>",
8432
- description: "Get details of a specific Contract DCA bot order"
8588
+ usage: "okx bot dca details --algoOrdType <spot_dca|contract_dca> --algoId <id>",
8589
+ description: "Get DCA bot details (spot or contract)"
8433
8590
  },
8434
8591
  "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"
8592
+ usage: "okx bot dca sub-orders --algoOrdType <spot_dca|contract_dca> --algoId <id> [--cycleId <id>]",
8593
+ description: "Get DCA cycles/orders (spot or contract)"
8437
8594
  },
8438
8595
  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"
8596
+ 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 [--algoClOrdId <id>] [--reserveFunds <true|false>] [--tradeQuoteCcy <ccy>]\n Note: --lever required for contract_dca; safetyOrdAmt, pxSteps, pxStepsMult, volMult required when maxSafetyOrds > 0",
8597
+ description: "Create a DCA (Martingale) bot (spot or contract)"
8441
8598
  },
8442
8599
  stop: {
8443
- usage: "okx bot dca stop --algoId <id>",
8444
- description: "Stop a running Contract DCA bot order"
8600
+ 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)",
8601
+ description: "Stop a DCA bot (spot or contract)"
8445
8602
  }
8446
8603
  }
8447
8604
  }
@@ -8676,7 +8833,7 @@ var CLI_OPTIONS = {
8676
8833
  autoCxl: { type: "boolean", default: false },
8677
8834
  clOrdId: { type: "string" },
8678
8835
  newPx: { type: "string" },
8679
- // dca bot (contract only)
8836
+ // dca bot (spot & contract)
8680
8837
  initOrdAmt: { type: "string" },
8681
8838
  safetyOrdAmt: { type: "string" },
8682
8839
  maxSafetyOrds: { type: "string" },
@@ -8690,6 +8847,8 @@ var CLI_OPTIONS = {
8690
8847
  triggerStrategy: { type: "string" },
8691
8848
  triggerPx: { type: "string" },
8692
8849
  cycleId: { type: "string" },
8850
+ reserveFunds: { type: "string" },
8851
+ tradeQuoteCcy: { type: "string" },
8693
8852
  // i18n
8694
8853
  lang: { type: "string" },
8695
8854
  // option
@@ -8909,10 +9068,15 @@ async function cmdMarketTicker(run, instId, json) {
8909
9068
  printKv({
8910
9069
  instId: t["instId"],
8911
9070
  last: t["last"],
8912
- "24h change %": t["sodUtc8"],
9071
+ "24h open": t["open24h"],
8913
9072
  "24h high": t["high24h"],
8914
9073
  "24h low": t["low24h"],
8915
9074
  "24h vol": t["vol24h"],
9075
+ "24h change %": (() => {
9076
+ const last = Number(t["last"]);
9077
+ const open24h = Number(t["open24h"]);
9078
+ return open24h !== 0 ? ((last - open24h) / open24h * 100).toFixed(2) + "%" : "N/A";
9079
+ })(),
8916
9080
  time: new Date(Number(t["ts"])).toLocaleString()
8917
9081
  });
8918
9082
  }
@@ -10949,6 +11113,7 @@ async function cmdGridStop(run, opts) {
10949
11113
  async function cmdDcaCreate(run, opts) {
10950
11114
  const result = await run("dca_create_order", {
10951
11115
  instId: opts.instId,
11116
+ algoOrdType: opts.algoOrdType,
10952
11117
  lever: opts.lever,
10953
11118
  direction: opts.direction,
10954
11119
  initOrdAmt: opts.initOrdAmt,
@@ -10962,25 +11127,29 @@ async function cmdDcaCreate(run, opts) {
10962
11127
  slMode: opts.slMode,
10963
11128
  allowReinvest: opts.allowReinvest,
10964
11129
  triggerStrategy: opts.triggerStrategy,
10965
- triggerPx: opts.triggerPx
11130
+ triggerPx: opts.triggerPx,
11131
+ algoClOrdId: opts.algoClOrdId,
11132
+ reserveFunds: opts.reserveFunds,
11133
+ tradeQuoteCcy: opts.tradeQuoteCcy
10966
11134
  });
10967
11135
  const data = getData7(result);
10968
11136
  if (opts.json) return printJson(data);
10969
- const r = data?.[0];
10970
11137
  emitWriteResult5(data?.[0], "DCA bot created", "algoId");
10971
11138
  }
10972
11139
  async function cmdDcaStop(run, opts) {
10973
11140
  const result = await run("dca_stop_order", {
10974
- algoId: opts.algoId
11141
+ algoId: opts.algoId,
11142
+ algoOrdType: opts.algoOrdType,
11143
+ stopType: opts.stopType
10975
11144
  });
10976
11145
  const data = getData7(result);
10977
11146
  if (opts.json) return printJson(data);
10978
- const r = data?.[0];
10979
11147
  emitWriteResult5(data?.[0], "DCA bot stopped", "algoId");
10980
11148
  }
10981
11149
  async function cmdDcaOrders(run, opts) {
10982
11150
  const result = await run("dca_get_orders", {
10983
11151
  status: opts.history ? "history" : "active",
11152
+ algoOrdType: opts.algoOrdType,
10984
11153
  algoId: opts.algoId,
10985
11154
  instId: opts.instId
10986
11155
  });
@@ -10994,6 +11163,7 @@ async function cmdDcaOrders(run, opts) {
10994
11163
  orders.map((o) => ({
10995
11164
  algoId: o["algoId"],
10996
11165
  instId: o["instId"],
11166
+ type: o["algoOrdType"],
10997
11167
  state: o["state"],
10998
11168
  pnl: o["pnl"],
10999
11169
  pnlRatio: o["pnlRatio"],
@@ -11003,7 +11173,8 @@ async function cmdDcaOrders(run, opts) {
11003
11173
  }
11004
11174
  async function cmdDcaDetails(run, opts) {
11005
11175
  const result = await run("dca_get_order_details", {
11006
- algoId: opts.algoId
11176
+ algoId: opts.algoId,
11177
+ algoOrdType: opts.algoOrdType
11007
11178
  });
11008
11179
  const detail = (getData7(result) ?? [])[0];
11009
11180
  if (!detail) {
@@ -11013,6 +11184,7 @@ async function cmdDcaDetails(run, opts) {
11013
11184
  if (opts.json) return printJson(detail);
11014
11185
  printKv({
11015
11186
  algoId: detail["algoId"],
11187
+ algoOrdType: detail["algoOrdType"],
11016
11188
  instId: detail["instId"],
11017
11189
  sz: detail["sz"],
11018
11190
  avgPx: detail["avgPx"],
@@ -11030,26 +11202,42 @@ async function cmdDcaDetails(run, opts) {
11030
11202
  async function cmdDcaSubOrders(run, opts) {
11031
11203
  const result = await run("dca_get_sub_orders", {
11032
11204
  algoId: opts.algoId,
11205
+ algoOrdType: opts.algoOrdType,
11033
11206
  cycleId: opts.cycleId
11034
11207
  });
11035
- const orders = getData7(result) ?? [];
11036
- if (opts.json) return printJson(orders);
11037
- if (!orders.length) {
11208
+ const rows = getData7(result) ?? [];
11209
+ if (opts.json) return printJson(rows);
11210
+ if (!rows.length) {
11038
11211
  outputLine("No sub-orders");
11039
11212
  return;
11040
11213
  }
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
- );
11214
+ if (opts.cycleId) {
11215
+ printTable(
11216
+ rows.map((o) => ({
11217
+ ordId: o["ordId"],
11218
+ side: o["side"],
11219
+ ordType: o["ordType"],
11220
+ px: o["px"],
11221
+ filledSz: o["filledSz"],
11222
+ avgFillPx: o["avgFillPx"],
11223
+ state: o["state"],
11224
+ fee: o["fee"]
11225
+ }))
11226
+ );
11227
+ } else {
11228
+ printTable(
11229
+ rows.map((o) => ({
11230
+ cycleId: o["cycleId"],
11231
+ status: o["cycleStatus"],
11232
+ current: o["currentCycle"] ? "yes" : "",
11233
+ avgPx: o["avgPx"],
11234
+ tpPx: o["tpPx"],
11235
+ realizedPnl: o["realizedPnl"],
11236
+ fee: o["fee"],
11237
+ startTime: o["startTime"] ? new Date(Number(o["startTime"])).toLocaleString() : ""
11238
+ }))
11239
+ );
11240
+ }
11053
11241
  }
11054
11242
 
11055
11243
  // src/commands/onchain-earn.ts
@@ -11349,7 +11537,7 @@ async function cmdDcdQuoteAndBuy(run, opts) {
11349
11537
  // src/index.ts
11350
11538
  var _require3 = createRequire3(import.meta.url);
11351
11539
  var CLI_VERSION2 = _require3("../package.json").version;
11352
- var GIT_HASH2 = true ? "7ff8d64" : "dev";
11540
+ var GIT_HASH2 = true ? "c270a0b" : "dev";
11353
11541
  function handleConfigCommand(action, rest, json, lang, force) {
11354
11542
  if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
11355
11543
  if (action === "show") return cmdConfigShow(json);
@@ -11956,15 +12144,17 @@ function handleBotGridCommand(run, v, rest, json) {
11956
12144
  });
11957
12145
  }
11958
12146
  function handleBotDcaCommand(run, subAction, v, json) {
12147
+ const algoOrdType = v.algoOrdType ?? "contract_dca";
11959
12148
  if (subAction === "orders")
11960
- return cmdDcaOrders(run, { algoId: v.algoId, instId: v.instId, history: v.history ?? false, json });
12149
+ return cmdDcaOrders(run, { algoOrdType, algoId: v.algoId, instId: v.instId, history: v.history ?? false, json });
11961
12150
  if (subAction === "details")
11962
- return cmdDcaDetails(run, { algoId: v.algoId, json });
12151
+ return cmdDcaDetails(run, { algoId: v.algoId, algoOrdType, json });
11963
12152
  if (subAction === "sub-orders")
11964
- return cmdDcaSubOrders(run, { algoId: v.algoId, cycleId: v.cycleId, json });
12153
+ return cmdDcaSubOrders(run, { algoId: v.algoId, algoOrdType, cycleId: v.cycleId, json });
11965
12154
  if (subAction === "create")
11966
12155
  return cmdDcaCreate(run, {
11967
12156
  instId: v.instId,
12157
+ algoOrdType,
11968
12158
  lever: v.lever,
11969
12159
  direction: v.direction,
11970
12160
  initOrdAmt: v.initOrdAmt,
@@ -11979,10 +12169,13 @@ function handleBotDcaCommand(run, subAction, v, json) {
11979
12169
  allowReinvest: v.allowReinvest,
11980
12170
  triggerStrategy: v.triggerStrategy,
11981
12171
  triggerPx: v.triggerPx,
12172
+ algoClOrdId: v.algoClOrdId,
12173
+ reserveFunds: v.reserveFunds,
12174
+ tradeQuoteCcy: v.tradeQuoteCcy,
11982
12175
  json
11983
12176
  });
11984
12177
  if (subAction === "stop")
11985
- return cmdDcaStop(run, { algoId: v.algoId, json });
12178
+ return cmdDcaStop(run, { algoId: v.algoId, algoOrdType, stopType: v.stopType, json });
11986
12179
  }
11987
12180
  function handleBotCommand(run, action, rest, v, json) {
11988
12181
  if (action === "grid") return handleBotGridCommand(run, v, rest, json);