@pionex/pionex-trade-mcp 0.2.22 → 0.2.24

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/README.md CHANGED
@@ -40,14 +40,33 @@ Credentials are read from `~/.pionex/config.toml` when the server starts; the cl
40
40
  - **Orders** (auth): `pionex_orders_new_order`, `pionex_orders_get_order`, `pionex_orders_get_order_by_client_order_id`, `pionex_orders_get_open_orders`, `pionex_orders_get_all_orders`, `pionex_orders_cancel_order`, `pionex_orders_get_fills`, `pionex_orders_cancel_all_orders`
41
41
  - **Bot / Futures Grid** (auth): `pionex_bot_get_futures_grid_order`, `pionex_bot_create_futures_grid_order`, `pionex_bot_adjust_futures_grid_params`, `pionex_bot_reduce_futures_grid_position`, `pionex_bot_cancel_futures_grid_order`
42
42
 
43
- For `pionex_bot_create_futures_grid_order`:
44
- - `keyId` is optional.
45
- - `exchange` defaults to `pionex.v2` if missing.
46
- - `quote` defaults to `USDT` if missing.
47
- - `base` is normalized to `<BASE>.PERP` when the suffix is missing.
48
- - If `buOrderData.top`/`bottom` are missing, defaults are computed from current market price (`+5%`/`-5%`).
49
- - `buOrderData.openPrice` is optional; if missing, it is not sent.
50
- - If `buOrderData.row` is missing, default is `10`.
51
- - If `buOrderData.grid_type` is missing, default is `arithmetic`.
52
- - If `buOrderData.leverage` is missing, default is `2`.
53
- - If `buOrderData.extraMargin` is missing, default is `"0"`.
43
+ ### `pionex_bot_create_futures_grid_order` (strict OpenAPI)
44
+
45
+ Source: [openapi_bot.yaml](https://github.com/pionex-official/pionex-open-api/blob/main/openapi_bot.yaml) `CreateFuturesGridRequest` / `CreateFuturesGridOrderData`.
46
+
47
+ Implementation (JSON Schema + runtime validation): `@pionex-ai/core` `schemas/futures-grid-create.ts` (exported as `createFuturesGridCreateToolInputSchema`, `parseAndValidateCreateFuturesGridBuOrderData`).
48
+
49
+ **Tool arguments = `CreateFuturesGridRequest` (+ internal `__dryRun` optional)**
50
+
51
+ | Field | Required | OpenAPI |
52
+ |-------|----------|---------|
53
+ | `base` | yes | yes |
54
+ | `quote` | yes | yes |
55
+ | `buOrderData` | yes | yes |
56
+ | `copyFrom` | no | no |
57
+ | `copyType` | no | no |
58
+ | `copyBotOrderId` | no | no |
59
+ | `__dryRun` | no | internal (CLI): preview body without POST |
60
+
61
+ **`buOrderData` = `CreateFuturesGridOrderData` — only these keys allowed** (`additionalProperties: false` in schema; unknown keys rejected at runtime).
62
+
63
+ **Required in `buOrderData`:** `top`, `bottom`, `row`, `grid_type`, `trend`, `leverage`, `quoteInvestment`
64
+
65
+ **Optional in `buOrderData` (if present, types must match YAML):**
66
+ `extraMargin`, `condition`, `conditionDirection`, `lossStopType`, `lossStop`, `lossStopDelay`, `profitStopType`, `profitStop`, `profitStopDelay`, `lossStopHigh`, `shareRatio`, `investCoin`, `investmentFrom`, `uiInvestCoin`, `lossStopLimitPrice`, `lossStopLimitHighPrice`, `profitStopLimitPrice`, `slippage`, `bonusId`, `uiExtraData`, `movingIndicatorType`, `movingIndicatorInterval`, `movingIndicatorParam`, `movingTrailingUpParam`, `cateType`, `movingTop`, `movingBottom`, `enableFollowClosed`
67
+
68
+ **Not in create schema / stripped:** `openPrice`, `keyId` (if sent, removed before validation; unknown keys then error).
69
+
70
+ **Handler:** `base` is normalized to `*.PERP` when the futures suffix is missing.
71
+
72
+ Docs: [Futures Grid API](https://www.pionex.com/docs/api-docs/bot-api/futures-grid)
package/dist/index.js CHANGED
@@ -1213,6 +1213,44 @@ function registerOrdersTools() {
1213
1213
  }
1214
1214
  ];
1215
1215
  }
1216
+ var CREATE_FUTURES_GRID_ORDER_DATA_KEYS = [
1217
+ "top",
1218
+ "bottom",
1219
+ "row",
1220
+ "grid_type",
1221
+ "trend",
1222
+ "leverage",
1223
+ "extraMargin",
1224
+ "quoteInvestment",
1225
+ "condition",
1226
+ "conditionDirection",
1227
+ "lossStopType",
1228
+ "lossStop",
1229
+ "lossStopDelay",
1230
+ "profitStopType",
1231
+ "profitStop",
1232
+ "profitStopDelay",
1233
+ "lossStopHigh",
1234
+ "shareRatio",
1235
+ "investCoin",
1236
+ "investmentFrom",
1237
+ "uiInvestCoin",
1238
+ "lossStopLimitPrice",
1239
+ "lossStopLimitHighPrice",
1240
+ "profitStopLimitPrice",
1241
+ "slippage",
1242
+ "bonusId",
1243
+ "uiExtraData",
1244
+ "movingIndicatorType",
1245
+ "movingIndicatorInterval",
1246
+ "movingIndicatorParam",
1247
+ "movingTrailingUpParam",
1248
+ "cateType",
1249
+ "movingTop",
1250
+ "movingBottom",
1251
+ "enableFollowClosed"
1252
+ ];
1253
+ var ORDER_DATA_KEY_SET = new Set(CREATE_FUTURES_GRID_ORDER_DATA_KEYS);
1216
1254
  function asNonEmptyString(value, field) {
1217
1255
  if (typeof value !== "string" || value.trim().length === 0) {
1218
1256
  throw new Error(`Invalid "${field}": expected non-empty string.`);
@@ -1248,12 +1286,6 @@ function assertEnum(value, field, allowed) {
1248
1286
  throw new Error(`Invalid "${field}": expected one of ${allowed.join(", ")}.`);
1249
1287
  }
1250
1288
  }
1251
- function asObject(value, field) {
1252
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1253
- throw new Error(`Invalid "${field}": expected JSON object.`);
1254
- }
1255
- return value;
1256
- }
1257
1289
  function asPositiveDecimalString(value, field) {
1258
1290
  const s = asNonEmptyString(value, field);
1259
1291
  if (!/^\d+(\.\d+)?$/.test(s)) {
@@ -1265,6 +1297,12 @@ function asPositiveDecimalString(value, field) {
1265
1297
  }
1266
1298
  return s;
1267
1299
  }
1300
+ function asPositiveDecimalStringLoose(value, field) {
1301
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
1302
+ return String(value);
1303
+ }
1304
+ return asPositiveDecimalString(value, field);
1305
+ }
1268
1306
  function asNonNegativeDecimalString(value, field) {
1269
1307
  const s = asNonEmptyString(value, field);
1270
1308
  if (!/^\d+(\.\d+)?$/.test(s)) {
@@ -1276,50 +1314,235 @@ function asNonNegativeDecimalString(value, field) {
1276
1314
  }
1277
1315
  return s;
1278
1316
  }
1279
- function toTrimmedDecimal(value) {
1280
- return value.toFixed(8).replace(/\.?0+$/, "");
1317
+ function asOptionalString(value, field) {
1318
+ if (typeof value !== "string") {
1319
+ throw new Error(`Invalid "${field}": expected string.`);
1320
+ }
1321
+ return value;
1281
1322
  }
1282
- function maybePositiveDecimalString(value) {
1283
- if (typeof value === "string" && /^\d+(\.\d+)?$/.test(value)) {
1284
- const n = Number(value);
1285
- if (Number.isFinite(n) && n > 0) return value;
1323
+ function asOptionalNonNegativeNumber(value, field) {
1324
+ const n = asFiniteNumber(value, field);
1325
+ if (n < 0) throw new Error(`Invalid "${field}": expected number >= 0.`);
1326
+ return n;
1327
+ }
1328
+ function parseAndValidateCreateFuturesGridBuOrderData(raw) {
1329
+ const data = { ...raw };
1330
+ delete data.openPrice;
1331
+ delete data.keyId;
1332
+ delete data.key_id;
1333
+ for (const k of Object.keys(data)) {
1334
+ if (!ORDER_DATA_KEY_SET.has(k)) {
1335
+ throw new Error(`Unknown buOrderData property "${k}". Allowed keys: ${CREATE_FUTURES_GRID_ORDER_DATA_KEYS.join(", ")}.`);
1336
+ }
1286
1337
  }
1287
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
1288
- return toTrimmedDecimal(value);
1338
+ const top = asPositiveDecimalStringLoose(data.top, "buOrderData.top");
1339
+ const bottom = asPositiveDecimalStringLoose(data.bottom, "buOrderData.bottom");
1340
+ if (Number(top) <= Number(bottom)) {
1341
+ throw new Error('Invalid "buOrderData.top": expected top > bottom.');
1342
+ }
1343
+ const row = asPositiveInteger(data.row, "buOrderData.row");
1344
+ const gridType = asNonEmptyString(data.grid_type, "buOrderData.grid_type");
1345
+ assertEnum(gridType, "buOrderData.grid_type", ["arithmetic", "geometric"]);
1346
+ const trend = asNonEmptyString(data.trend, "buOrderData.trend");
1347
+ assertEnum(trend, "buOrderData.trend", ["long", "short", "no_trend"]);
1348
+ const leverage2 = asPositiveNumber(data.leverage, "buOrderData.leverage");
1349
+ const quoteInvestment = asPositiveDecimalStringLoose(data.quoteInvestment, "buOrderData.quoteInvestment");
1350
+ const out = {
1351
+ top,
1352
+ bottom,
1353
+ row,
1354
+ grid_type: gridType,
1355
+ trend,
1356
+ leverage: leverage2,
1357
+ quoteInvestment
1358
+ };
1359
+ if (data.extraMargin != null) {
1360
+ out.extraMargin = asNonNegativeDecimalString(data.extraMargin, "buOrderData.extraMargin");
1361
+ }
1362
+ if (data.condition != null) out.condition = asOptionalString(data.condition, "buOrderData.condition");
1363
+ if (data.conditionDirection != null) {
1364
+ const v = asNonEmptyString(data.conditionDirection, "buOrderData.conditionDirection");
1365
+ assertEnum(v, "buOrderData.conditionDirection", ["-1", "1"]);
1366
+ out.conditionDirection = v;
1367
+ }
1368
+ if (data.lossStopType != null) {
1369
+ const v = asNonEmptyString(data.lossStopType, "buOrderData.lossStopType");
1370
+ assertEnum(v, "buOrderData.lossStopType", ["price", "profit_amount", "profit_ratio", "price_limit"]);
1371
+ out.lossStopType = v;
1372
+ }
1373
+ if (data.lossStop != null) out.lossStop = asOptionalString(data.lossStop, "buOrderData.lossStop");
1374
+ if (data.lossStopDelay != null) out.lossStopDelay = asOptionalNonNegativeNumber(data.lossStopDelay, "buOrderData.lossStopDelay");
1375
+ if (data.profitStopType != null) {
1376
+ const v = asNonEmptyString(data.profitStopType, "buOrderData.profitStopType");
1377
+ assertEnum(v, "buOrderData.profitStopType", ["price", "profit_amount", "profit_ratio", "price_limit"]);
1378
+ out.profitStopType = v;
1379
+ }
1380
+ if (data.profitStop != null) out.profitStop = asOptionalString(data.profitStop, "buOrderData.profitStop");
1381
+ if (data.profitStopDelay != null) out.profitStopDelay = asOptionalNonNegativeNumber(data.profitStopDelay, "buOrderData.profitStopDelay");
1382
+ if (data.lossStopHigh != null) out.lossStopHigh = asOptionalString(data.lossStopHigh, "buOrderData.lossStopHigh");
1383
+ if (data.shareRatio != null) out.shareRatio = asOptionalString(data.shareRatio, "buOrderData.shareRatio");
1384
+ if (data.investCoin != null) out.investCoin = asOptionalString(data.investCoin, "buOrderData.investCoin");
1385
+ if (data.investmentFrom != null) {
1386
+ const v = asNonEmptyString(data.investmentFrom, "buOrderData.investmentFrom");
1387
+ assertEnum(v, "buOrderData.investmentFrom", ["USER", "LOCK_ACTIVITY", "FUTURE_GRID_BONUS"]);
1388
+ out.investmentFrom = v;
1389
+ }
1390
+ if (data.uiInvestCoin != null) out.uiInvestCoin = asOptionalString(data.uiInvestCoin, "buOrderData.uiInvestCoin");
1391
+ if (data.lossStopLimitPrice != null) out.lossStopLimitPrice = asOptionalString(data.lossStopLimitPrice, "buOrderData.lossStopLimitPrice");
1392
+ if (data.lossStopLimitHighPrice != null) out.lossStopLimitHighPrice = asOptionalString(data.lossStopLimitHighPrice, "buOrderData.lossStopLimitHighPrice");
1393
+ if (data.profitStopLimitPrice != null) out.profitStopLimitPrice = asOptionalString(data.profitStopLimitPrice, "buOrderData.profitStopLimitPrice");
1394
+ if (data.slippage != null) out.slippage = asOptionalString(data.slippage, "buOrderData.slippage");
1395
+ if (data.bonusId != null) out.bonusId = asOptionalString(data.bonusId, "buOrderData.bonusId");
1396
+ if (data.uiExtraData != null) out.uiExtraData = asOptionalString(data.uiExtraData, "buOrderData.uiExtraData");
1397
+ if (data.movingIndicatorType != null) out.movingIndicatorType = asOptionalString(data.movingIndicatorType, "buOrderData.movingIndicatorType");
1398
+ if (data.movingIndicatorInterval != null) out.movingIndicatorInterval = asOptionalString(data.movingIndicatorInterval, "buOrderData.movingIndicatorInterval");
1399
+ if (data.movingIndicatorParam != null) out.movingIndicatorParam = asOptionalString(data.movingIndicatorParam, "buOrderData.movingIndicatorParam");
1400
+ if (data.movingTrailingUpParam != null) out.movingTrailingUpParam = asOptionalString(data.movingTrailingUpParam, "buOrderData.movingTrailingUpParam");
1401
+ if (data.cateType != null) {
1402
+ const v = asNonEmptyString(data.cateType, "buOrderData.cateType");
1403
+ assertEnum(v, "buOrderData.cateType", ["FULLY_HEDGING", "LOAN_GRID", "LEVERAGE_GRID", "FUTURE_GRID_COIN_MARGINED"]);
1404
+ out.cateType = v;
1405
+ }
1406
+ if (data.movingTop != null) out.movingTop = asOptionalString(data.movingTop, "buOrderData.movingTop");
1407
+ if (data.movingBottom != null) out.movingBottom = asOptionalString(data.movingBottom, "buOrderData.movingBottom");
1408
+ if (data.enableFollowClosed != null) out.enableFollowClosed = asBoolean(data.enableFollowClosed, "buOrderData.enableFollowClosed");
1409
+ return out;
1410
+ }
1411
+ var createFuturesGridOrderDataJsonSchema = {
1412
+ type: "object",
1413
+ additionalProperties: false,
1414
+ description: "CreateFuturesGridOrderData (openapi_bot.yaml). Required: top, bottom, row, grid_type, trend, leverage, quoteInvestment.",
1415
+ required: ["top", "bottom", "row", "grid_type", "trend", "leverage", "quoteInvestment"],
1416
+ properties: {
1417
+ top: { type: "string", description: "Grid upper price" },
1418
+ bottom: { type: "string", description: "Grid lower price" },
1419
+ row: { type: "number", description: "Number of grid levels" },
1420
+ grid_type: {
1421
+ type: "string",
1422
+ enum: ["arithmetic", "geometric"],
1423
+ description: "Grid spacing: arithmetic (equal difference) or geometric (equal ratio)"
1424
+ },
1425
+ trend: {
1426
+ type: "string",
1427
+ enum: ["long", "short", "no_trend"],
1428
+ description: "Grid direction"
1429
+ },
1430
+ leverage: { type: "number", description: "Leverage multiplier" },
1431
+ extraMargin: { type: "string", description: "Extra margin amount (optional)" },
1432
+ quoteInvestment: { type: "string", description: "Investment amount" },
1433
+ condition: { type: "string", description: "Trigger price (conditional orders)" },
1434
+ conditionDirection: { type: "string", enum: ["-1", "1"], description: "Trigger direction" },
1435
+ lossStopType: {
1436
+ type: "string",
1437
+ enum: ["price", "profit_amount", "profit_ratio", "price_limit"],
1438
+ description: "Stop loss type"
1439
+ },
1440
+ lossStop: { type: "string", description: "Stop loss value" },
1441
+ lossStopDelay: { type: "number", description: "Stop loss delay (seconds)" },
1442
+ profitStopType: {
1443
+ type: "string",
1444
+ enum: ["price", "profit_amount", "profit_ratio", "price_limit"],
1445
+ description: "Take profit type"
1446
+ },
1447
+ profitStop: { type: "string", description: "Take profit value" },
1448
+ profitStopDelay: { type: "number", description: "Take profit delay (seconds)" },
1449
+ lossStopHigh: { type: "string", description: "Upper stop loss price for neutral grid" },
1450
+ shareRatio: { type: "string", description: "Profit sharing ratio" },
1451
+ investCoin: { type: "string", description: "Investment currency" },
1452
+ investmentFrom: {
1453
+ type: "string",
1454
+ enum: ["USER", "LOCK_ACTIVITY", "FUTURE_GRID_BONUS"],
1455
+ description: "Funding source"
1456
+ },
1457
+ uiInvestCoin: { type: "string", description: "Frontend-recorded investment currency" },
1458
+ lossStopLimitPrice: { type: "string", description: "Limit SL price (lossStopType=price_limit)" },
1459
+ lossStopLimitHighPrice: { type: "string", description: "Upper limit SL for neutral grid" },
1460
+ profitStopLimitPrice: { type: "string", description: "Limit TP price (profitStopType=price_limit)" },
1461
+ slippage: { type: "string", description: "Open slippage e.g. 0.01 = 1%" },
1462
+ bonusId: { type: "string", description: "Bonus UUID" },
1463
+ uiExtraData: { type: "string", description: "Frontend extra (coin-margined)" },
1464
+ movingIndicatorType: { type: "string", description: "e.g. sma" },
1465
+ movingIndicatorInterval: { type: "string", description: "e.g. 1m, 15m" },
1466
+ movingIndicatorParam: { type: "string", description: "JSON params e.g. length" },
1467
+ movingTrailingUpParam: { type: "string", description: "SMA trailing up ratio" },
1468
+ cateType: {
1469
+ type: "string",
1470
+ enum: ["FULLY_HEDGING", "LOAN_GRID", "LEVERAGE_GRID", "FUTURE_GRID_COIN_MARGINED"],
1471
+ description: "Category type"
1472
+ },
1473
+ movingTop: { type: "string", description: "Moving grid upper limit" },
1474
+ movingBottom: { type: "string", description: "Moving grid lower limit" },
1475
+ enableFollowClosed: { type: "boolean", description: "Follow close" }
1289
1476
  }
1290
- return void 0;
1477
+ };
1478
+ var createFuturesGridCreateToolInputSchema = {
1479
+ type: "object",
1480
+ additionalProperties: false,
1481
+ required: ["base", "quote", "buOrderData"],
1482
+ properties: {
1483
+ base: { type: "string", description: "Base currency (e.g. BTC); *.PERP normalized in handler" },
1484
+ quote: { type: "string", description: "Quote currency (e.g. USDT)" },
1485
+ copyFrom: { type: "string", description: "Optional. Copy source order ID" },
1486
+ copyType: { type: "string", description: "Optional. Copy type" },
1487
+ copyBotOrderId: { type: "string", description: "Optional. Copy bot order ID" },
1488
+ buOrderData: createFuturesGridOrderDataJsonSchema,
1489
+ __dryRun: { type: "boolean", description: "Internal: when true, return resolved body without POST" }
1490
+ }
1491
+ };
1492
+ function asNonEmptyString2(value, field) {
1493
+ if (typeof value !== "string" || value.trim().length === 0) {
1494
+ throw new Error(`Invalid "${field}": expected non-empty string.`);
1495
+ }
1496
+ return value.trim();
1291
1497
  }
1292
- function normalizePerpBase(base) {
1293
- return base.endsWith(".PERP") ? base : `${base}.PERP`;
1498
+ function asFiniteNumber2(value, field) {
1499
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1500
+ throw new Error(`Invalid "${field}": expected finite number.`);
1501
+ }
1502
+ return value;
1294
1503
  }
1295
- function tryExtractCurrentPrice(payload, symbol) {
1296
- const stacks = [payload];
1297
- while (stacks.length > 0) {
1298
- const node = stacks.pop();
1299
- if (!node || typeof node !== "object") continue;
1300
- const obj = node;
1301
- const nodeSymbol = typeof obj.symbol === "string" ? obj.symbol : void 0;
1302
- const candidateClose = maybePositiveDecimalString(obj.close) ?? maybePositiveDecimalString(obj.last) ?? maybePositiveDecimalString(obj.lastPrice) ?? maybePositiveDecimalString(obj.price);
1303
- if (candidateClose && (!nodeSymbol || nodeSymbol === symbol)) {
1304
- return candidateClose;
1305
- }
1306
- for (const v of Object.values(obj)) {
1307
- if (Array.isArray(v)) {
1308
- for (const item of v) stacks.push(item);
1309
- } else if (v && typeof v === "object") {
1310
- stacks.push(v);
1311
- }
1312
- }
1504
+ function asPositiveNumber2(value, field) {
1505
+ const n = asFiniteNumber2(value, field);
1506
+ if (n <= 0) throw new Error(`Invalid "${field}": expected number > 0.`);
1507
+ return n;
1508
+ }
1509
+ function asPositiveInteger2(value, field) {
1510
+ const n = asPositiveNumber2(value, field);
1511
+ if (!Number.isInteger(n)) {
1512
+ throw new Error(`Invalid "${field}": expected positive integer.`);
1313
1513
  }
1314
- return void 0;
1514
+ return n;
1315
1515
  }
1316
- async function getCurrentSymbolPrice(client, symbol) {
1317
- const tickerPayload = (await client.publicGet("/api/v1/market/tickers", { symbol })).data;
1318
- const extracted = tryExtractCurrentPrice(tickerPayload, symbol);
1319
- if (!extracted) {
1320
- throw new Error(`Unable to infer current market price for ${symbol} from ticker response. Please provide buOrderData.top and buOrderData.bottom explicitly.`);
1516
+ function asBoolean2(value, field) {
1517
+ if (typeof value !== "boolean") {
1518
+ throw new Error(`Invalid "${field}": expected boolean.`);
1519
+ }
1520
+ return value;
1521
+ }
1522
+ function assertEnum2(value, field, allowed) {
1523
+ if (!allowed.includes(value)) {
1524
+ throw new Error(`Invalid "${field}": expected one of ${allowed.join(", ")}.`);
1525
+ }
1526
+ }
1527
+ function asObject(value, field) {
1528
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1529
+ throw new Error(`Invalid "${field}": expected JSON object.`);
1530
+ }
1531
+ return value;
1532
+ }
1533
+ function asPositiveDecimalString2(value, field) {
1534
+ const s = asNonEmptyString2(value, field);
1535
+ if (!/^\d+(\.\d+)?$/.test(s)) {
1536
+ throw new Error(`Invalid "${field}": expected positive decimal string.`);
1321
1537
  }
1322
- return extracted;
1538
+ const n = Number(s);
1539
+ if (!Number.isFinite(n) || n <= 0) {
1540
+ throw new Error(`Invalid "${field}": expected positive decimal string.`);
1541
+ }
1542
+ return s;
1543
+ }
1544
+ function normalizePerpBase(base) {
1545
+ return base.endsWith(".PERP") ? base : `${base}.PERP`;
1323
1546
  }
1324
1547
  function registerBotTools() {
1325
1548
  return [
@@ -1348,113 +1571,35 @@ function registerBotTools() {
1348
1571
  name: "pionex_bot_create_futures_grid_order",
1349
1572
  module: "bot",
1350
1573
  isWrite: true,
1351
- description: "Create a futures grid bot order.",
1352
- inputSchema: {
1353
- type: "object",
1354
- additionalProperties: false,
1355
- properties: {
1356
- keyId: { type: "string" },
1357
- exchange: { type: "string", description: "e.g. pionex.v2" },
1358
- base: { type: "string", description: "e.g. BTC" },
1359
- quote: { type: "string", description: "e.g. USDT" },
1360
- copyFrom: { type: "string" },
1361
- copyType: { type: "string" },
1362
- groupId: { type: "string" },
1363
- copyBotOrderId: { type: "string" },
1364
- lang: { type: "string" },
1365
- buOrderData: {
1366
- type: "object",
1367
- additionalProperties: true,
1368
- description: "CreateFuturesGridOrderData payload from openapi_bot.yaml."
1369
- }
1370
- },
1371
- required: ["base", "buOrderData"]
1372
- },
1574
+ description: "Create a futures grid order (openapi_bot.yaml CreateFuturesGridRequest / CreateFuturesGridOrderData). https://github.com/pionex-official/pionex-open-api/blob/main/openapi_bot.yaml \u2014 Required: base, quote, buOrderData. Optional: copyFrom, copyType, copyBotOrderId. buOrderData required: top, bottom, row, grid_type, trend, leverage, quoteInvestment; openPrice/keyId stripped if present.",
1575
+ inputSchema: createFuturesGridCreateToolInputSchema,
1373
1576
  async handler(args, { client, config }) {
1374
1577
  if (config.readOnly) {
1375
1578
  throw new Error("Server is running in --read-only mode; bot create is disabled.");
1376
1579
  }
1377
- const defaultsApplied = {};
1378
- const exchange = asNonEmptyString(args.exchange ?? "pionex.v2", "exchange");
1379
- if (args.exchange == null) defaultsApplied.exchange = exchange;
1380
- const rawBase = asNonEmptyString(args.base, "base");
1580
+ const rawBase = asNonEmptyString2(args.base, "base");
1381
1581
  const base = normalizePerpBase(rawBase);
1382
- if (base !== rawBase) defaultsApplied.base = base;
1383
- const quote = asNonEmptyString(args.quote ?? "USDT", "quote");
1384
- if (args.quote == null) defaultsApplied.quote = quote;
1385
- const buOrderData = asObject(args.buOrderData, "buOrderData");
1386
- const symbol = `${base}_${quote}`;
1387
- const needsTickerForTopBottom = buOrderData.top == null || buOrderData.bottom == null;
1388
- const shouldTryTicker = needsTickerForTopBottom;
1389
- let currentPrice;
1390
- if (shouldTryTicker) {
1391
- try {
1392
- currentPrice = Number(await getCurrentSymbolPrice(client, symbol));
1393
- } catch {
1394
- currentPrice = void 0;
1395
- }
1396
- }
1397
- if (needsTickerForTopBottom && (currentPrice == null || !Number.isFinite(currentPrice) || currentPrice <= 0)) {
1398
- throw new Error(`Unable to infer current market price for ${symbol} from ticker response. Please provide buOrderData.top and buOrderData.bottom explicitly.`);
1399
- }
1400
- const top = asPositiveDecimalString(
1401
- buOrderData.top ?? toTrimmedDecimal(currentPrice * 1.05),
1402
- "buOrderData.top"
1403
- );
1404
- if (buOrderData.top == null) defaultsApplied.top = top;
1405
- const bottom = asPositiveDecimalString(
1406
- buOrderData.bottom ?? toTrimmedDecimal(currentPrice * 0.95),
1407
- "buOrderData.bottom"
1408
- );
1409
- if (buOrderData.bottom == null) defaultsApplied.bottom = bottom;
1410
- if (Number(top) <= Number(bottom)) {
1411
- throw new Error('Invalid "buOrderData.top": expected top > bottom.');
1412
- }
1413
- const row = asPositiveInteger(buOrderData.row ?? 10, "buOrderData.row");
1414
- if (buOrderData.row == null) defaultsApplied.row = row;
1415
- const gridType = asNonEmptyString(buOrderData.grid_type ?? "arithmetic", "buOrderData.grid_type");
1416
- assertEnum(gridType, "buOrderData.grid_type", ["arithmetic", "geometric"]);
1417
- if (buOrderData.grid_type == null) defaultsApplied.grid_type = gridType;
1418
- const openPrice = buOrderData.openPrice == null ? void 0 : asPositiveDecimalString(buOrderData.openPrice, "buOrderData.openPrice");
1419
- const trend = asNonEmptyString(buOrderData.trend, "buOrderData.trend");
1420
- assertEnum(trend, "buOrderData.trend", ["long", "short", "no_trend"]);
1421
- const leverage = asPositiveNumber(buOrderData.leverage ?? 2, "buOrderData.leverage");
1422
- if (buOrderData.leverage == null) defaultsApplied.leverage = leverage;
1423
- const extraMargin = asNonNegativeDecimalString(buOrderData.extraMargin ?? "0", "buOrderData.extraMargin");
1424
- if (buOrderData.extraMargin == null) defaultsApplied.extraMargin = extraMargin;
1425
- const quoteInvestment = asPositiveDecimalString(buOrderData.quoteInvestment, "buOrderData.quoteInvestment");
1582
+ const quote = asNonEmptyString2(args.quote, "quote");
1583
+ const buOrderDataOut = parseAndValidateCreateFuturesGridBuOrderData(asObject(args.buOrderData, "buOrderData"));
1584
+ const row = buOrderDataOut.row;
1585
+ const gridType = buOrderDataOut.grid_type;
1426
1586
  const body = {
1427
- exchange,
1428
1587
  base,
1429
1588
  quote,
1430
- buOrderData: {
1431
- ...buOrderData,
1432
- top,
1433
- bottom,
1434
- row,
1435
- grid_type: gridType,
1436
- trend,
1437
- leverage,
1438
- extraMargin,
1439
- quoteInvestment
1440
- }
1589
+ buOrderData: buOrderDataOut
1441
1590
  };
1442
- if (openPrice != null) {
1443
- body.buOrderData.openPrice = openPrice;
1444
- }
1445
- if (args.keyId != null) body.keyId = asNonEmptyString(args.keyId, "keyId");
1446
1591
  if (args.copyFrom != null) body.copyFrom = String(args.copyFrom);
1447
1592
  if (args.copyType != null) body.copyType = String(args.copyType);
1448
- if (args.groupId != null) body.groupId = String(args.groupId);
1449
1593
  if (args.copyBotOrderId != null) body.copyBotOrderId = String(args.copyBotOrderId);
1450
- if (args.lang != null) body.lang = String(args.lang);
1451
1594
  if (args.__dryRun === true) {
1452
1595
  return {
1453
1596
  dryRun: true,
1454
- note: "No order was sent. This is the resolved request body after applying defaults.",
1455
- marketSymbol: symbol,
1456
- marketPriceUsed: currentPrice == null ? void 0 : toTrimmedDecimal(currentPrice),
1457
- defaultsApplied,
1597
+ note: "No order was sent. Body matches openapi_bot.yaml CreateFuturesGridRequest (no keyId/openPrice/exchange; groupId/lang not in create schema).",
1598
+ resolvedParams: {
1599
+ row,
1600
+ grid_type: gridType,
1601
+ leverage
1602
+ },
1458
1603
  resolvedBody: body
1459
1604
  };
1460
1605
  }
@@ -1494,26 +1639,26 @@ function registerBotTools() {
1494
1639
  if (config.readOnly) {
1495
1640
  throw new Error("Server is running in --read-only mode; bot adjust is disabled.");
1496
1641
  }
1497
- const buOrderId = asNonEmptyString(args.buOrderId, "buOrderId");
1498
- const type = asNonEmptyString(args.type, "type");
1499
- assertEnum(type, "type", ["invest_in", "adjust_params", "invest_in_trigger"]);
1500
- const extraMargin = asBoolean(args.extraMargin, "extraMargin");
1501
- const openPrice = asFiniteNumber(args.openPrice, "openPrice");
1642
+ const buOrderId = asNonEmptyString2(args.buOrderId, "buOrderId");
1643
+ const type = asNonEmptyString2(args.type, "type");
1644
+ assertEnum2(type, "type", ["invest_in", "adjust_params", "invest_in_trigger"]);
1645
+ const extraMargin = asBoolean2(args.extraMargin, "extraMargin");
1646
+ const openPrice = asFiniteNumber2(args.openPrice, "openPrice");
1502
1647
  if (type === "invest_in" && args.quoteInvestment != null) {
1503
- asPositiveNumber(args.quoteInvestment, "quoteInvestment");
1648
+ asPositiveNumber2(args.quoteInvestment, "quoteInvestment");
1504
1649
  }
1505
1650
  if (type === "adjust_params") {
1506
- const bottom = asPositiveDecimalString(args.bottom, "bottom");
1507
- const top = asPositiveDecimalString(args.top, "top");
1651
+ const bottom = asPositiveDecimalString2(args.bottom, "bottom");
1652
+ const top = asPositiveDecimalString2(args.top, "top");
1508
1653
  if (Number(top) <= Number(bottom)) {
1509
1654
  throw new Error('Invalid "top": expected top > bottom.');
1510
1655
  }
1511
- asPositiveInteger(args.row, "row");
1656
+ asPositiveInteger2(args.row, "row");
1512
1657
  }
1513
1658
  if (type === "invest_in_trigger") {
1514
- asPositiveDecimalString(args.condition, "condition");
1515
- const conditionDirection = asNonEmptyString(args.conditionDirection, "conditionDirection");
1516
- assertEnum(conditionDirection, "conditionDirection", ["1", "-1"]);
1659
+ asPositiveDecimalString2(args.condition, "condition");
1660
+ const conditionDirection = asNonEmptyString2(args.conditionDirection, "conditionDirection");
1661
+ assertEnum2(conditionDirection, "conditionDirection", ["1", "-1"]);
1517
1662
  }
1518
1663
  const body = {
1519
1664
  buOrderId,
@@ -1521,23 +1666,23 @@ function registerBotTools() {
1521
1666
  extraMargin,
1522
1667
  openPrice
1523
1668
  };
1524
- if (args.quoteInvestment != null) body.quoteInvestment = asFiniteNumber(args.quoteInvestment, "quoteInvestment");
1525
- if (args.bottom != null) body.bottom = asPositiveDecimalString(args.bottom, "bottom");
1526
- if (args.top != null) body.top = asPositiveDecimalString(args.top, "top");
1527
- if (args.row != null) body.row = asPositiveInteger(args.row, "row");
1528
- if (args.extraMarginAmount != null) body.extraMarginAmount = asFiniteNumber(args.extraMarginAmount, "extraMarginAmount");
1529
- if (args.isRecommend != null) body.isRecommend = asBoolean(args.isRecommend, "isRecommend");
1530
- if (args.isReinvest != null) body.isReinvest = asBoolean(args.isReinvest, "isReinvest");
1669
+ if (args.quoteInvestment != null) body.quoteInvestment = asFiniteNumber2(args.quoteInvestment, "quoteInvestment");
1670
+ if (args.bottom != null) body.bottom = asPositiveDecimalString2(args.bottom, "bottom");
1671
+ if (args.top != null) body.top = asPositiveDecimalString2(args.top, "top");
1672
+ if (args.row != null) body.row = asPositiveInteger2(args.row, "row");
1673
+ if (args.extraMarginAmount != null) body.extraMarginAmount = asFiniteNumber2(args.extraMarginAmount, "extraMarginAmount");
1674
+ if (args.isRecommend != null) body.isRecommend = asBoolean2(args.isRecommend, "isRecommend");
1675
+ if (args.isReinvest != null) body.isReinvest = asBoolean2(args.isReinvest, "isReinvest");
1531
1676
  if (args.investCoin != null) body.investCoin = String(args.investCoin);
1532
1677
  if (args.investmentFrom != null) {
1533
- const investmentFrom = asNonEmptyString(args.investmentFrom, "investmentFrom");
1534
- assertEnum(investmentFrom, "investmentFrom", ["USER", "LOCK_ACTIVITY"]);
1678
+ const investmentFrom = asNonEmptyString2(args.investmentFrom, "investmentFrom");
1679
+ assertEnum2(investmentFrom, "investmentFrom", ["USER", "LOCK_ACTIVITY"]);
1535
1680
  body.investmentFrom = investmentFrom;
1536
1681
  }
1537
- if (args.condition != null) body.condition = asPositiveDecimalString(args.condition, "condition");
1682
+ if (args.condition != null) body.condition = asPositiveDecimalString2(args.condition, "condition");
1538
1683
  if (args.conditionDirection != null) {
1539
- const conditionDirection = asNonEmptyString(args.conditionDirection, "conditionDirection");
1540
- assertEnum(conditionDirection, "conditionDirection", ["1", "-1"]);
1684
+ const conditionDirection = asNonEmptyString2(args.conditionDirection, "conditionDirection");
1685
+ assertEnum2(conditionDirection, "conditionDirection", ["1", "-1"]);
1541
1686
  body.conditionDirection = conditionDirection;
1542
1687
  }
1543
1688
  if (args.slippage != null) body.slippage = String(args.slippage);
@@ -1567,9 +1712,9 @@ function registerBotTools() {
1567
1712
  if (config.readOnly) {
1568
1713
  throw new Error("Server is running in --read-only mode; bot reduce is disabled.");
1569
1714
  }
1570
- const buOrderId = asNonEmptyString(args.buOrderId, "buOrderId");
1571
- const openPrice = asPositiveDecimalString(args.openPrice, "openPrice");
1572
- const reduceNum = asPositiveInteger(args.reduceNum, "reduceNum");
1715
+ const buOrderId = asNonEmptyString2(args.buOrderId, "buOrderId");
1716
+ const openPrice = asPositiveDecimalString2(args.openPrice, "openPrice");
1717
+ const reduceNum = asPositiveInteger2(args.reduceNum, "reduceNum");
1573
1718
  const body = {
1574
1719
  buOrderId,
1575
1720
  openPrice,
@@ -1578,8 +1723,8 @@ function registerBotTools() {
1578
1723
  if (args.slippage != null) body.slippage = String(args.slippage);
1579
1724
  if (args.condition != null) body.condition = String(args.condition);
1580
1725
  if (args.conditionDirection != null) {
1581
- const conditionDirection = asNonEmptyString(args.conditionDirection, "conditionDirection");
1582
- assertEnum(conditionDirection, "conditionDirection", ["1", "-1"]);
1726
+ const conditionDirection = asNonEmptyString2(args.conditionDirection, "conditionDirection");
1727
+ assertEnum2(conditionDirection, "conditionDirection", ["1", "-1"]);
1583
1728
  body.conditionDirection = conditionDirection;
1584
1729
  }
1585
1730
  return (await client.signedPost("/api/v1/bot/orders/futuresGrid/reduce", body)).data;
@@ -1606,15 +1751,15 @@ function registerBotTools() {
1606
1751
  if (config.readOnly) {
1607
1752
  throw new Error("Server is running in --read-only mode; bot cancel is disabled.");
1608
1753
  }
1609
- const buOrderId = asNonEmptyString(args.buOrderId, "buOrderId");
1754
+ const buOrderId = asNonEmptyString2(args.buOrderId, "buOrderId");
1610
1755
  const body = { buOrderId };
1611
1756
  if (args.closeNote != null) body.closeNote = String(args.closeNote);
1612
1757
  if (args.closeSellModel != null) {
1613
- const closeSellModel = asNonEmptyString(args.closeSellModel, "closeSellModel");
1614
- assertEnum(closeSellModel, "closeSellModel", ["TO_QUOTE", "TO_USDT"]);
1758
+ const closeSellModel = asNonEmptyString2(args.closeSellModel, "closeSellModel");
1759
+ assertEnum2(closeSellModel, "closeSellModel", ["TO_QUOTE", "TO_USDT"]);
1615
1760
  body.closeSellModel = closeSellModel;
1616
1761
  }
1617
- if (args.immediate != null) body.immediate = asBoolean(args.immediate, "immediate");
1762
+ if (args.immediate != null) body.immediate = asBoolean2(args.immediate, "immediate");
1618
1763
  if (args.closeSlippage != null) body.closeSlippage = String(args.closeSlippage);
1619
1764
  return (await client.signedPost("/api/v1/bot/orders/futuresGrid/cancel", body)).data;
1620
1765
  }