@pionex/pionex-ai-kit 0.2.26 → 0.2.28

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
@@ -1480,6 +1480,44 @@ function registerOrdersTools() {
1480
1480
  }
1481
1481
  ];
1482
1482
  }
1483
+ var CREATE_FUTURES_GRID_ORDER_DATA_KEYS = [
1484
+ "top",
1485
+ "bottom",
1486
+ "row",
1487
+ "grid_type",
1488
+ "trend",
1489
+ "leverage",
1490
+ "extraMargin",
1491
+ "quoteInvestment",
1492
+ "condition",
1493
+ "conditionDirection",
1494
+ "lossStopType",
1495
+ "lossStop",
1496
+ "lossStopDelay",
1497
+ "profitStopType",
1498
+ "profitStop",
1499
+ "profitStopDelay",
1500
+ "lossStopHigh",
1501
+ "shareRatio",
1502
+ "investCoin",
1503
+ "investmentFrom",
1504
+ "uiInvestCoin",
1505
+ "lossStopLimitPrice",
1506
+ "lossStopLimitHighPrice",
1507
+ "profitStopLimitPrice",
1508
+ "slippage",
1509
+ "bonusId",
1510
+ "uiExtraData",
1511
+ "movingIndicatorType",
1512
+ "movingIndicatorInterval",
1513
+ "movingIndicatorParam",
1514
+ "movingTrailingUpParam",
1515
+ "cateType",
1516
+ "movingTop",
1517
+ "movingBottom",
1518
+ "enableFollowClosed"
1519
+ ];
1520
+ var ORDER_DATA_KEY_SET = new Set(CREATE_FUTURES_GRID_ORDER_DATA_KEYS);
1483
1521
  function asNonEmptyString(value, field) {
1484
1522
  if (typeof value !== "string" || value.trim().length === 0) {
1485
1523
  throw new Error(`Invalid "${field}": expected non-empty string.`);
@@ -1515,12 +1553,6 @@ function assertEnum(value, field, allowed) {
1515
1553
  throw new Error(`Invalid "${field}": expected one of ${allowed.join(", ")}.`);
1516
1554
  }
1517
1555
  }
1518
- function asObject(value, field) {
1519
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1520
- throw new Error(`Invalid "${field}": expected JSON object.`);
1521
- }
1522
- return value;
1523
- }
1524
1556
  function asPositiveDecimalString(value, field) {
1525
1557
  const s = asNonEmptyString(value, field);
1526
1558
  if (!/^\d+(\.\d+)?$/.test(s)) {
@@ -1532,6 +1564,12 @@ function asPositiveDecimalString(value, field) {
1532
1564
  }
1533
1565
  return s;
1534
1566
  }
1567
+ function asPositiveDecimalStringLoose(value, field) {
1568
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
1569
+ return String(value);
1570
+ }
1571
+ return asPositiveDecimalString(value, field);
1572
+ }
1535
1573
  function asNonNegativeDecimalString(value, field) {
1536
1574
  const s = asNonEmptyString(value, field);
1537
1575
  if (!/^\d+(\.\d+)?$/.test(s)) {
@@ -1543,50 +1581,235 @@ function asNonNegativeDecimalString(value, field) {
1543
1581
  }
1544
1582
  return s;
1545
1583
  }
1546
- function toTrimmedDecimal(value) {
1547
- return value.toFixed(8).replace(/\.?0+$/, "");
1584
+ function asOptionalString(value, field) {
1585
+ if (typeof value !== "string") {
1586
+ throw new Error(`Invalid "${field}": expected string.`);
1587
+ }
1588
+ return value;
1548
1589
  }
1549
- function maybePositiveDecimalString(value) {
1550
- if (typeof value === "string" && /^\d+(\.\d+)?$/.test(value)) {
1551
- const n = Number(value);
1552
- if (Number.isFinite(n) && n > 0) return value;
1590
+ function asOptionalNonNegativeNumber(value, field) {
1591
+ const n = asFiniteNumber(value, field);
1592
+ if (n < 0) throw new Error(`Invalid "${field}": expected number >= 0.`);
1593
+ return n;
1594
+ }
1595
+ function parseAndValidateCreateFuturesGridBuOrderData(raw) {
1596
+ const data = { ...raw };
1597
+ delete data.openPrice;
1598
+ delete data.keyId;
1599
+ delete data.key_id;
1600
+ for (const k of Object.keys(data)) {
1601
+ if (!ORDER_DATA_KEY_SET.has(k)) {
1602
+ throw new Error(`Unknown buOrderData property "${k}". Allowed keys: ${CREATE_FUTURES_GRID_ORDER_DATA_KEYS.join(", ")}.`);
1603
+ }
1604
+ }
1605
+ const top = asPositiveDecimalStringLoose(data.top, "buOrderData.top");
1606
+ const bottom = asPositiveDecimalStringLoose(data.bottom, "buOrderData.bottom");
1607
+ if (Number(top) <= Number(bottom)) {
1608
+ throw new Error('Invalid "buOrderData.top": expected top > bottom.');
1609
+ }
1610
+ const row = asPositiveInteger(data.row, "buOrderData.row");
1611
+ const gridType = asNonEmptyString(data.grid_type, "buOrderData.grid_type");
1612
+ assertEnum(gridType, "buOrderData.grid_type", ["arithmetic", "geometric"]);
1613
+ const trend = asNonEmptyString(data.trend, "buOrderData.trend");
1614
+ assertEnum(trend, "buOrderData.trend", ["long", "short", "no_trend"]);
1615
+ const leverage2 = asPositiveNumber(data.leverage, "buOrderData.leverage");
1616
+ const quoteInvestment = asPositiveDecimalStringLoose(data.quoteInvestment, "buOrderData.quoteInvestment");
1617
+ const out = {
1618
+ top,
1619
+ bottom,
1620
+ row,
1621
+ grid_type: gridType,
1622
+ trend,
1623
+ leverage: leverage2,
1624
+ quoteInvestment
1625
+ };
1626
+ if (data.extraMargin != null) {
1627
+ out.extraMargin = asNonNegativeDecimalString(data.extraMargin, "buOrderData.extraMargin");
1628
+ }
1629
+ if (data.condition != null) out.condition = asOptionalString(data.condition, "buOrderData.condition");
1630
+ if (data.conditionDirection != null) {
1631
+ const v = asNonEmptyString(data.conditionDirection, "buOrderData.conditionDirection");
1632
+ assertEnum(v, "buOrderData.conditionDirection", ["-1", "1"]);
1633
+ out.conditionDirection = v;
1634
+ }
1635
+ if (data.lossStopType != null) {
1636
+ const v = asNonEmptyString(data.lossStopType, "buOrderData.lossStopType");
1637
+ assertEnum(v, "buOrderData.lossStopType", ["price", "profit_amount", "profit_ratio", "price_limit"]);
1638
+ out.lossStopType = v;
1639
+ }
1640
+ if (data.lossStop != null) out.lossStop = asOptionalString(data.lossStop, "buOrderData.lossStop");
1641
+ if (data.lossStopDelay != null) out.lossStopDelay = asOptionalNonNegativeNumber(data.lossStopDelay, "buOrderData.lossStopDelay");
1642
+ if (data.profitStopType != null) {
1643
+ const v = asNonEmptyString(data.profitStopType, "buOrderData.profitStopType");
1644
+ assertEnum(v, "buOrderData.profitStopType", ["price", "profit_amount", "profit_ratio", "price_limit"]);
1645
+ out.profitStopType = v;
1646
+ }
1647
+ if (data.profitStop != null) out.profitStop = asOptionalString(data.profitStop, "buOrderData.profitStop");
1648
+ if (data.profitStopDelay != null) out.profitStopDelay = asOptionalNonNegativeNumber(data.profitStopDelay, "buOrderData.profitStopDelay");
1649
+ if (data.lossStopHigh != null) out.lossStopHigh = asOptionalString(data.lossStopHigh, "buOrderData.lossStopHigh");
1650
+ if (data.shareRatio != null) out.shareRatio = asOptionalString(data.shareRatio, "buOrderData.shareRatio");
1651
+ if (data.investCoin != null) out.investCoin = asOptionalString(data.investCoin, "buOrderData.investCoin");
1652
+ if (data.investmentFrom != null) {
1653
+ const v = asNonEmptyString(data.investmentFrom, "buOrderData.investmentFrom");
1654
+ assertEnum(v, "buOrderData.investmentFrom", ["USER", "LOCK_ACTIVITY", "FUTURE_GRID_BONUS"]);
1655
+ out.investmentFrom = v;
1656
+ }
1657
+ if (data.uiInvestCoin != null) out.uiInvestCoin = asOptionalString(data.uiInvestCoin, "buOrderData.uiInvestCoin");
1658
+ if (data.lossStopLimitPrice != null) out.lossStopLimitPrice = asOptionalString(data.lossStopLimitPrice, "buOrderData.lossStopLimitPrice");
1659
+ if (data.lossStopLimitHighPrice != null) out.lossStopLimitHighPrice = asOptionalString(data.lossStopLimitHighPrice, "buOrderData.lossStopLimitHighPrice");
1660
+ if (data.profitStopLimitPrice != null) out.profitStopLimitPrice = asOptionalString(data.profitStopLimitPrice, "buOrderData.profitStopLimitPrice");
1661
+ if (data.slippage != null) out.slippage = asOptionalString(data.slippage, "buOrderData.slippage");
1662
+ if (data.bonusId != null) out.bonusId = asOptionalString(data.bonusId, "buOrderData.bonusId");
1663
+ if (data.uiExtraData != null) out.uiExtraData = asOptionalString(data.uiExtraData, "buOrderData.uiExtraData");
1664
+ if (data.movingIndicatorType != null) out.movingIndicatorType = asOptionalString(data.movingIndicatorType, "buOrderData.movingIndicatorType");
1665
+ if (data.movingIndicatorInterval != null) out.movingIndicatorInterval = asOptionalString(data.movingIndicatorInterval, "buOrderData.movingIndicatorInterval");
1666
+ if (data.movingIndicatorParam != null) out.movingIndicatorParam = asOptionalString(data.movingIndicatorParam, "buOrderData.movingIndicatorParam");
1667
+ if (data.movingTrailingUpParam != null) out.movingTrailingUpParam = asOptionalString(data.movingTrailingUpParam, "buOrderData.movingTrailingUpParam");
1668
+ if (data.cateType != null) {
1669
+ const v = asNonEmptyString(data.cateType, "buOrderData.cateType");
1670
+ assertEnum(v, "buOrderData.cateType", ["FULLY_HEDGING", "LOAN_GRID", "LEVERAGE_GRID", "FUTURE_GRID_COIN_MARGINED"]);
1671
+ out.cateType = v;
1672
+ }
1673
+ if (data.movingTop != null) out.movingTop = asOptionalString(data.movingTop, "buOrderData.movingTop");
1674
+ if (data.movingBottom != null) out.movingBottom = asOptionalString(data.movingBottom, "buOrderData.movingBottom");
1675
+ if (data.enableFollowClosed != null) out.enableFollowClosed = asBoolean(data.enableFollowClosed, "buOrderData.enableFollowClosed");
1676
+ return out;
1677
+ }
1678
+ var createFuturesGridOrderDataJsonSchema = {
1679
+ type: "object",
1680
+ additionalProperties: false,
1681
+ description: "CreateFuturesGridOrderData (openapi_bot.yaml). Required: top, bottom, row, grid_type, trend, leverage, quoteInvestment.",
1682
+ required: ["top", "bottom", "row", "grid_type", "trend", "leverage", "quoteInvestment"],
1683
+ properties: {
1684
+ top: { type: "string", description: "Grid upper price" },
1685
+ bottom: { type: "string", description: "Grid lower price" },
1686
+ row: { type: "number", description: "Number of grid levels" },
1687
+ grid_type: {
1688
+ type: "string",
1689
+ enum: ["arithmetic", "geometric"],
1690
+ description: "Grid spacing: arithmetic (equal difference) or geometric (equal ratio)"
1691
+ },
1692
+ trend: {
1693
+ type: "string",
1694
+ enum: ["long", "short", "no_trend"],
1695
+ description: "Grid direction"
1696
+ },
1697
+ leverage: { type: "number", description: "Leverage multiplier" },
1698
+ extraMargin: { type: "string", description: "Extra margin amount (optional)" },
1699
+ quoteInvestment: { type: "string", description: "Investment amount" },
1700
+ condition: { type: "string", description: "Trigger price (conditional orders)" },
1701
+ conditionDirection: { type: "string", enum: ["-1", "1"], description: "Trigger direction" },
1702
+ lossStopType: {
1703
+ type: "string",
1704
+ enum: ["price", "profit_amount", "profit_ratio", "price_limit"],
1705
+ description: "Stop loss type"
1706
+ },
1707
+ lossStop: { type: "string", description: "Stop loss value" },
1708
+ lossStopDelay: { type: "number", description: "Stop loss delay (seconds)" },
1709
+ profitStopType: {
1710
+ type: "string",
1711
+ enum: ["price", "profit_amount", "profit_ratio", "price_limit"],
1712
+ description: "Take profit type"
1713
+ },
1714
+ profitStop: { type: "string", description: "Take profit value" },
1715
+ profitStopDelay: { type: "number", description: "Take profit delay (seconds)" },
1716
+ lossStopHigh: { type: "string", description: "Upper stop loss price for neutral grid" },
1717
+ shareRatio: { type: "string", description: "Profit sharing ratio" },
1718
+ investCoin: { type: "string", description: "Investment currency" },
1719
+ investmentFrom: {
1720
+ type: "string",
1721
+ enum: ["USER", "LOCK_ACTIVITY", "FUTURE_GRID_BONUS"],
1722
+ description: "Funding source"
1723
+ },
1724
+ uiInvestCoin: { type: "string", description: "Frontend-recorded investment currency" },
1725
+ lossStopLimitPrice: { type: "string", description: "Limit SL price (lossStopType=price_limit)" },
1726
+ lossStopLimitHighPrice: { type: "string", description: "Upper limit SL for neutral grid" },
1727
+ profitStopLimitPrice: { type: "string", description: "Limit TP price (profitStopType=price_limit)" },
1728
+ slippage: { type: "string", description: "Open slippage e.g. 0.01 = 1%" },
1729
+ bonusId: { type: "string", description: "Bonus UUID" },
1730
+ uiExtraData: { type: "string", description: "Frontend extra (coin-margined)" },
1731
+ movingIndicatorType: { type: "string", description: "e.g. sma" },
1732
+ movingIndicatorInterval: { type: "string", description: "e.g. 1m, 15m" },
1733
+ movingIndicatorParam: { type: "string", description: "JSON params e.g. length" },
1734
+ movingTrailingUpParam: { type: "string", description: "SMA trailing up ratio" },
1735
+ cateType: {
1736
+ type: "string",
1737
+ enum: ["FULLY_HEDGING", "LOAN_GRID", "LEVERAGE_GRID", "FUTURE_GRID_COIN_MARGINED"],
1738
+ description: "Category type"
1739
+ },
1740
+ movingTop: { type: "string", description: "Moving grid upper limit" },
1741
+ movingBottom: { type: "string", description: "Moving grid lower limit" },
1742
+ enableFollowClosed: { type: "boolean", description: "Follow close" }
1553
1743
  }
1554
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
1555
- return toTrimmedDecimal(value);
1744
+ };
1745
+ var createFuturesGridCreateToolInputSchema = {
1746
+ type: "object",
1747
+ additionalProperties: false,
1748
+ required: ["base", "quote", "buOrderData"],
1749
+ properties: {
1750
+ base: { type: "string", description: "Base currency (e.g. BTC); *.PERP normalized in handler" },
1751
+ quote: { type: "string", description: "Quote currency (e.g. USDT)" },
1752
+ copyFrom: { type: "string", description: "Optional. Copy source order ID" },
1753
+ copyType: { type: "string", description: "Optional. Copy type" },
1754
+ copyBotOrderId: { type: "string", description: "Optional. Copy bot order ID" },
1755
+ buOrderData: createFuturesGridOrderDataJsonSchema,
1756
+ __dryRun: { type: "boolean", description: "Internal: when true, return resolved body without POST" }
1757
+ }
1758
+ };
1759
+ function asNonEmptyString2(value, field) {
1760
+ if (typeof value !== "string" || value.trim().length === 0) {
1761
+ throw new Error(`Invalid "${field}": expected non-empty string.`);
1556
1762
  }
1557
- return void 0;
1763
+ return value.trim();
1558
1764
  }
1559
- function normalizePerpBase(base) {
1560
- return base.endsWith(".PERP") ? base : `${base}.PERP`;
1765
+ function asFiniteNumber2(value, field) {
1766
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1767
+ throw new Error(`Invalid "${field}": expected finite number.`);
1768
+ }
1769
+ return value;
1561
1770
  }
1562
- function tryExtractCurrentPrice(payload, symbol) {
1563
- const stacks = [payload];
1564
- while (stacks.length > 0) {
1565
- const node = stacks.pop();
1566
- if (!node || typeof node !== "object") continue;
1567
- const obj = node;
1568
- const nodeSymbol = typeof obj.symbol === "string" ? obj.symbol : void 0;
1569
- const candidateClose = maybePositiveDecimalString(obj.close) ?? maybePositiveDecimalString(obj.last) ?? maybePositiveDecimalString(obj.lastPrice) ?? maybePositiveDecimalString(obj.price);
1570
- if (candidateClose && (!nodeSymbol || nodeSymbol === symbol)) {
1571
- return candidateClose;
1572
- }
1573
- for (const v of Object.values(obj)) {
1574
- if (Array.isArray(v)) {
1575
- for (const item of v) stacks.push(item);
1576
- } else if (v && typeof v === "object") {
1577
- stacks.push(v);
1578
- }
1579
- }
1771
+ function asPositiveNumber2(value, field) {
1772
+ const n = asFiniteNumber2(value, field);
1773
+ if (n <= 0) throw new Error(`Invalid "${field}": expected number > 0.`);
1774
+ return n;
1775
+ }
1776
+ function asPositiveInteger2(value, field) {
1777
+ const n = asPositiveNumber2(value, field);
1778
+ if (!Number.isInteger(n)) {
1779
+ throw new Error(`Invalid "${field}": expected positive integer.`);
1580
1780
  }
1581
- return void 0;
1781
+ return n;
1582
1782
  }
1583
- async function getCurrentSymbolPrice(client, symbol) {
1584
- const tickerPayload = (await client.publicGet("/api/v1/market/tickers", { symbol })).data;
1585
- const extracted = tryExtractCurrentPrice(tickerPayload, symbol);
1586
- if (!extracted) {
1587
- throw new Error(`Unable to infer current market price for ${symbol} from ticker response. Please provide buOrderData.top and buOrderData.bottom explicitly.`);
1783
+ function asBoolean2(value, field) {
1784
+ if (typeof value !== "boolean") {
1785
+ throw new Error(`Invalid "${field}": expected boolean.`);
1588
1786
  }
1589
- return extracted;
1787
+ return value;
1788
+ }
1789
+ function assertEnum2(value, field, allowed) {
1790
+ if (!allowed.includes(value)) {
1791
+ throw new Error(`Invalid "${field}": expected one of ${allowed.join(", ")}.`);
1792
+ }
1793
+ }
1794
+ function asObject(value, field) {
1795
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1796
+ throw new Error(`Invalid "${field}": expected JSON object.`);
1797
+ }
1798
+ return value;
1799
+ }
1800
+ function asPositiveDecimalString2(value, field) {
1801
+ const s = asNonEmptyString2(value, field);
1802
+ if (!/^\d+(\.\d+)?$/.test(s)) {
1803
+ throw new Error(`Invalid "${field}": expected positive decimal string.`);
1804
+ }
1805
+ const n = Number(s);
1806
+ if (!Number.isFinite(n) || n <= 0) {
1807
+ throw new Error(`Invalid "${field}": expected positive decimal string.`);
1808
+ }
1809
+ return s;
1810
+ }
1811
+ function normalizePerpBase(base) {
1812
+ return base.endsWith(".PERP") ? base : `${base}.PERP`;
1590
1813
  }
1591
1814
  function registerBotTools() {
1592
1815
  return [
@@ -1615,113 +1838,35 @@ function registerBotTools() {
1615
1838
  name: "pionex_bot_create_futures_grid_order",
1616
1839
  module: "bot",
1617
1840
  isWrite: true,
1618
- description: "Create a futures grid bot order.",
1619
- inputSchema: {
1620
- type: "object",
1621
- additionalProperties: false,
1622
- properties: {
1623
- keyId: { type: "string" },
1624
- exchange: { type: "string", description: "e.g. pionex.v2" },
1625
- base: { type: "string", description: "e.g. BTC" },
1626
- quote: { type: "string", description: "e.g. USDT" },
1627
- copyFrom: { type: "string" },
1628
- copyType: { type: "string" },
1629
- groupId: { type: "string" },
1630
- copyBotOrderId: { type: "string" },
1631
- lang: { type: "string" },
1632
- buOrderData: {
1633
- type: "object",
1634
- additionalProperties: true,
1635
- description: "CreateFuturesGridOrderData payload from openapi_bot.yaml."
1636
- }
1637
- },
1638
- required: ["base", "buOrderData"]
1639
- },
1841
+ 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.",
1842
+ inputSchema: createFuturesGridCreateToolInputSchema,
1640
1843
  async handler(args, { client, config }) {
1641
1844
  if (config.readOnly) {
1642
1845
  throw new Error("Server is running in --read-only mode; bot create is disabled.");
1643
1846
  }
1644
- const defaultsApplied = {};
1645
- const exchange = asNonEmptyString(args.exchange ?? "pionex.v2", "exchange");
1646
- if (args.exchange == null) defaultsApplied.exchange = exchange;
1647
- const rawBase = asNonEmptyString(args.base, "base");
1847
+ const rawBase = asNonEmptyString2(args.base, "base");
1648
1848
  const base = normalizePerpBase(rawBase);
1649
- if (base !== rawBase) defaultsApplied.base = base;
1650
- const quote = asNonEmptyString(args.quote ?? "USDT", "quote");
1651
- if (args.quote == null) defaultsApplied.quote = quote;
1652
- const buOrderData = asObject(args.buOrderData, "buOrderData");
1653
- const symbol = `${base}_${quote}`;
1654
- const needsTickerForTopBottom = buOrderData.top == null || buOrderData.bottom == null;
1655
- const shouldTryTicker = needsTickerForTopBottom;
1656
- let currentPrice;
1657
- if (shouldTryTicker) {
1658
- try {
1659
- currentPrice = Number(await getCurrentSymbolPrice(client, symbol));
1660
- } catch {
1661
- currentPrice = void 0;
1662
- }
1663
- }
1664
- if (needsTickerForTopBottom && (currentPrice == null || !Number.isFinite(currentPrice) || currentPrice <= 0)) {
1665
- throw new Error(`Unable to infer current market price for ${symbol} from ticker response. Please provide buOrderData.top and buOrderData.bottom explicitly.`);
1666
- }
1667
- const top = asPositiveDecimalString(
1668
- buOrderData.top ?? toTrimmedDecimal(currentPrice * 1.05),
1669
- "buOrderData.top"
1670
- );
1671
- if (buOrderData.top == null) defaultsApplied.top = top;
1672
- const bottom = asPositiveDecimalString(
1673
- buOrderData.bottom ?? toTrimmedDecimal(currentPrice * 0.95),
1674
- "buOrderData.bottom"
1675
- );
1676
- if (buOrderData.bottom == null) defaultsApplied.bottom = bottom;
1677
- if (Number(top) <= Number(bottom)) {
1678
- throw new Error('Invalid "buOrderData.top": expected top > bottom.');
1679
- }
1680
- const row = asPositiveInteger(buOrderData.row ?? 10, "buOrderData.row");
1681
- if (buOrderData.row == null) defaultsApplied.row = row;
1682
- const gridType = asNonEmptyString(buOrderData.grid_type ?? "arithmetic", "buOrderData.grid_type");
1683
- assertEnum(gridType, "buOrderData.grid_type", ["arithmetic", "geometric"]);
1684
- if (buOrderData.grid_type == null) defaultsApplied.grid_type = gridType;
1685
- const openPrice = buOrderData.openPrice == null ? void 0 : asPositiveDecimalString(buOrderData.openPrice, "buOrderData.openPrice");
1686
- const trend = asNonEmptyString(buOrderData.trend, "buOrderData.trend");
1687
- assertEnum(trend, "buOrderData.trend", ["long", "short", "no_trend"]);
1688
- const leverage = asPositiveNumber(buOrderData.leverage ?? 2, "buOrderData.leverage");
1689
- if (buOrderData.leverage == null) defaultsApplied.leverage = leverage;
1690
- const extraMargin = asNonNegativeDecimalString(buOrderData.extraMargin ?? "0", "buOrderData.extraMargin");
1691
- if (buOrderData.extraMargin == null) defaultsApplied.extraMargin = extraMargin;
1692
- const quoteInvestment = asPositiveDecimalString(buOrderData.quoteInvestment, "buOrderData.quoteInvestment");
1849
+ const quote = asNonEmptyString2(args.quote, "quote");
1850
+ const buOrderDataOut = parseAndValidateCreateFuturesGridBuOrderData(asObject(args.buOrderData, "buOrderData"));
1851
+ const row = buOrderDataOut.row;
1852
+ const gridType = buOrderDataOut.grid_type;
1693
1853
  const body = {
1694
- exchange,
1695
1854
  base,
1696
1855
  quote,
1697
- buOrderData: {
1698
- ...buOrderData,
1699
- top,
1700
- bottom,
1701
- row,
1702
- grid_type: gridType,
1703
- trend,
1704
- leverage,
1705
- extraMargin,
1706
- quoteInvestment
1707
- }
1856
+ buOrderData: buOrderDataOut
1708
1857
  };
1709
- if (openPrice != null) {
1710
- body.buOrderData.openPrice = openPrice;
1711
- }
1712
- if (args.keyId != null) body.keyId = asNonEmptyString(args.keyId, "keyId");
1713
1858
  if (args.copyFrom != null) body.copyFrom = String(args.copyFrom);
1714
1859
  if (args.copyType != null) body.copyType = String(args.copyType);
1715
- if (args.groupId != null) body.groupId = String(args.groupId);
1716
1860
  if (args.copyBotOrderId != null) body.copyBotOrderId = String(args.copyBotOrderId);
1717
- if (args.lang != null) body.lang = String(args.lang);
1718
1861
  if (args.__dryRun === true) {
1719
1862
  return {
1720
1863
  dryRun: true,
1721
- note: "No order was sent. This is the resolved request body after applying defaults.",
1722
- marketSymbol: symbol,
1723
- marketPriceUsed: currentPrice == null ? void 0 : toTrimmedDecimal(currentPrice),
1724
- defaultsApplied,
1864
+ note: "No order was sent. Body matches openapi_bot.yaml CreateFuturesGridRequest (no keyId/openPrice/exchange; groupId/lang not in create schema).",
1865
+ resolvedParams: {
1866
+ row,
1867
+ grid_type: gridType,
1868
+ leverage
1869
+ },
1725
1870
  resolvedBody: body
1726
1871
  };
1727
1872
  }
@@ -1761,26 +1906,26 @@ function registerBotTools() {
1761
1906
  if (config.readOnly) {
1762
1907
  throw new Error("Server is running in --read-only mode; bot adjust is disabled.");
1763
1908
  }
1764
- const buOrderId = asNonEmptyString(args.buOrderId, "buOrderId");
1765
- const type = asNonEmptyString(args.type, "type");
1766
- assertEnum(type, "type", ["invest_in", "adjust_params", "invest_in_trigger"]);
1767
- const extraMargin = asBoolean(args.extraMargin, "extraMargin");
1768
- const openPrice = asFiniteNumber(args.openPrice, "openPrice");
1909
+ const buOrderId = asNonEmptyString2(args.buOrderId, "buOrderId");
1910
+ const type = asNonEmptyString2(args.type, "type");
1911
+ assertEnum2(type, "type", ["invest_in", "adjust_params", "invest_in_trigger"]);
1912
+ const extraMargin = asBoolean2(args.extraMargin, "extraMargin");
1913
+ const openPrice = asFiniteNumber2(args.openPrice, "openPrice");
1769
1914
  if (type === "invest_in" && args.quoteInvestment != null) {
1770
- asPositiveNumber(args.quoteInvestment, "quoteInvestment");
1915
+ asPositiveNumber2(args.quoteInvestment, "quoteInvestment");
1771
1916
  }
1772
1917
  if (type === "adjust_params") {
1773
- const bottom = asPositiveDecimalString(args.bottom, "bottom");
1774
- const top = asPositiveDecimalString(args.top, "top");
1918
+ const bottom = asPositiveDecimalString2(args.bottom, "bottom");
1919
+ const top = asPositiveDecimalString2(args.top, "top");
1775
1920
  if (Number(top) <= Number(bottom)) {
1776
1921
  throw new Error('Invalid "top": expected top > bottom.');
1777
1922
  }
1778
- asPositiveInteger(args.row, "row");
1923
+ asPositiveInteger2(args.row, "row");
1779
1924
  }
1780
1925
  if (type === "invest_in_trigger") {
1781
- asPositiveDecimalString(args.condition, "condition");
1782
- const conditionDirection = asNonEmptyString(args.conditionDirection, "conditionDirection");
1783
- assertEnum(conditionDirection, "conditionDirection", ["1", "-1"]);
1926
+ asPositiveDecimalString2(args.condition, "condition");
1927
+ const conditionDirection = asNonEmptyString2(args.conditionDirection, "conditionDirection");
1928
+ assertEnum2(conditionDirection, "conditionDirection", ["1", "-1"]);
1784
1929
  }
1785
1930
  const body = {
1786
1931
  buOrderId,
@@ -1788,23 +1933,23 @@ function registerBotTools() {
1788
1933
  extraMargin,
1789
1934
  openPrice
1790
1935
  };
1791
- if (args.quoteInvestment != null) body.quoteInvestment = asFiniteNumber(args.quoteInvestment, "quoteInvestment");
1792
- if (args.bottom != null) body.bottom = asPositiveDecimalString(args.bottom, "bottom");
1793
- if (args.top != null) body.top = asPositiveDecimalString(args.top, "top");
1794
- if (args.row != null) body.row = asPositiveInteger(args.row, "row");
1795
- if (args.extraMarginAmount != null) body.extraMarginAmount = asFiniteNumber(args.extraMarginAmount, "extraMarginAmount");
1796
- if (args.isRecommend != null) body.isRecommend = asBoolean(args.isRecommend, "isRecommend");
1797
- if (args.isReinvest != null) body.isReinvest = asBoolean(args.isReinvest, "isReinvest");
1936
+ if (args.quoteInvestment != null) body.quoteInvestment = asFiniteNumber2(args.quoteInvestment, "quoteInvestment");
1937
+ if (args.bottom != null) body.bottom = asPositiveDecimalString2(args.bottom, "bottom");
1938
+ if (args.top != null) body.top = asPositiveDecimalString2(args.top, "top");
1939
+ if (args.row != null) body.row = asPositiveInteger2(args.row, "row");
1940
+ if (args.extraMarginAmount != null) body.extraMarginAmount = asFiniteNumber2(args.extraMarginAmount, "extraMarginAmount");
1941
+ if (args.isRecommend != null) body.isRecommend = asBoolean2(args.isRecommend, "isRecommend");
1942
+ if (args.isReinvest != null) body.isReinvest = asBoolean2(args.isReinvest, "isReinvest");
1798
1943
  if (args.investCoin != null) body.investCoin = String(args.investCoin);
1799
1944
  if (args.investmentFrom != null) {
1800
- const investmentFrom = asNonEmptyString(args.investmentFrom, "investmentFrom");
1801
- assertEnum(investmentFrom, "investmentFrom", ["USER", "LOCK_ACTIVITY"]);
1945
+ const investmentFrom = asNonEmptyString2(args.investmentFrom, "investmentFrom");
1946
+ assertEnum2(investmentFrom, "investmentFrom", ["USER", "LOCK_ACTIVITY"]);
1802
1947
  body.investmentFrom = investmentFrom;
1803
1948
  }
1804
- if (args.condition != null) body.condition = asPositiveDecimalString(args.condition, "condition");
1949
+ if (args.condition != null) body.condition = asPositiveDecimalString2(args.condition, "condition");
1805
1950
  if (args.conditionDirection != null) {
1806
- const conditionDirection = asNonEmptyString(args.conditionDirection, "conditionDirection");
1807
- assertEnum(conditionDirection, "conditionDirection", ["1", "-1"]);
1951
+ const conditionDirection = asNonEmptyString2(args.conditionDirection, "conditionDirection");
1952
+ assertEnum2(conditionDirection, "conditionDirection", ["1", "-1"]);
1808
1953
  body.conditionDirection = conditionDirection;
1809
1954
  }
1810
1955
  if (args.slippage != null) body.slippage = String(args.slippage);
@@ -1834,9 +1979,9 @@ function registerBotTools() {
1834
1979
  if (config.readOnly) {
1835
1980
  throw new Error("Server is running in --read-only mode; bot reduce is disabled.");
1836
1981
  }
1837
- const buOrderId = asNonEmptyString(args.buOrderId, "buOrderId");
1838
- const openPrice = asPositiveDecimalString(args.openPrice, "openPrice");
1839
- const reduceNum = asPositiveInteger(args.reduceNum, "reduceNum");
1982
+ const buOrderId = asNonEmptyString2(args.buOrderId, "buOrderId");
1983
+ const openPrice = asPositiveDecimalString2(args.openPrice, "openPrice");
1984
+ const reduceNum = asPositiveInteger2(args.reduceNum, "reduceNum");
1840
1985
  const body = {
1841
1986
  buOrderId,
1842
1987
  openPrice,
@@ -1845,8 +1990,8 @@ function registerBotTools() {
1845
1990
  if (args.slippage != null) body.slippage = String(args.slippage);
1846
1991
  if (args.condition != null) body.condition = String(args.condition);
1847
1992
  if (args.conditionDirection != null) {
1848
- const conditionDirection = asNonEmptyString(args.conditionDirection, "conditionDirection");
1849
- assertEnum(conditionDirection, "conditionDirection", ["1", "-1"]);
1993
+ const conditionDirection = asNonEmptyString2(args.conditionDirection, "conditionDirection");
1994
+ assertEnum2(conditionDirection, "conditionDirection", ["1", "-1"]);
1850
1995
  body.conditionDirection = conditionDirection;
1851
1996
  }
1852
1997
  return (await client.signedPost("/api/v1/bot/orders/futuresGrid/reduce", body)).data;
@@ -1873,15 +2018,15 @@ function registerBotTools() {
1873
2018
  if (config.readOnly) {
1874
2019
  throw new Error("Server is running in --read-only mode; bot cancel is disabled.");
1875
2020
  }
1876
- const buOrderId = asNonEmptyString(args.buOrderId, "buOrderId");
2021
+ const buOrderId = asNonEmptyString2(args.buOrderId, "buOrderId");
1877
2022
  const body = { buOrderId };
1878
2023
  if (args.closeNote != null) body.closeNote = String(args.closeNote);
1879
2024
  if (args.closeSellModel != null) {
1880
- const closeSellModel = asNonEmptyString(args.closeSellModel, "closeSellModel");
1881
- assertEnum(closeSellModel, "closeSellModel", ["TO_QUOTE", "TO_USDT"]);
2025
+ const closeSellModel = asNonEmptyString2(args.closeSellModel, "closeSellModel");
2026
+ assertEnum2(closeSellModel, "closeSellModel", ["TO_QUOTE", "TO_USDT"]);
1882
2027
  body.closeSellModel = closeSellModel;
1883
2028
  }
1884
- if (args.immediate != null) body.immediate = asBoolean(args.immediate, "immediate");
2029
+ if (args.immediate != null) body.immediate = asBoolean2(args.immediate, "immediate");
1885
2030
  if (args.closeSlippage != null) body.closeSlippage = String(args.closeSlippage);
1886
2031
  return (await client.signedPost("/api/v1/bot/orders/futuresGrid/cancel", body)).data;
1887
2032
  }
@@ -2060,27 +2205,29 @@ Examples:
2060
2205
  pionex orders new --symbol BTC_USDT --side BUY --type MARKET --amount 10
2061
2206
  pionex orders cancel --symbol BTC_USDT --order-id 123
2062
2207
  pionex bot get --bu-order-id <id>
2063
- pionex bot create --base BTC --bu-order-data-json '{"top":"110000","bottom":"90000","row":100,"grid_type":"arithmetic","openPrice":"100000","trend":"long","leverage":5,"extraMargin":"0","quoteInvestment":"100"}'
2208
+ pionex bot create --base BTC --quote USDT --bu-order-data-json '{"top":"110000","bottom":"90000","row":100,"grid_type":"arithmetic","trend":"long","leverage":5,"quoteInvestment":"100"}'
2064
2209
 
2065
2210
  Global flags:
2066
2211
  --profile <name> Profile in ~/.pionex/config.toml
2067
2212
  --modules <list> Comma-separated modules (market,account,orders or all)
2068
2213
  --base-url <url> Override API base URL
2069
2214
  --read-only Disable write operations (orders new/cancel)
2070
- --dry-run Print the tool call payload without executing (write ops only)
2215
+ --dry-run Print resolved futures-grid create body without executing (bot create only)
2071
2216
 
2072
- Bot create defaults:
2073
- exchange Defaults to pionex.v2 when missing
2074
- quote Defaults to USDT when missing
2075
- base Auto-normalized to <BASE>.PERP when missing suffix
2076
- --key-id Optional
2077
- buOrderData.top Defaults to current price * 1.05 when missing
2078
- buOrderData.bottom Defaults to current price * 0.95 when missing
2079
- buOrderData.row Defaults to 10 when missing
2080
- buOrderData.grid_type Defaults to arithmetic when missing
2081
- buOrderData.leverage Defaults to 2 when missing
2082
- buOrderData.openPrice Optional. If omitted, it is not sent
2083
- buOrderData.extraMargin Defaults to "0" when missing
2217
+ Futures grid create (pionex bot create) \u2014 strict OpenAPI (same validation as MCP):
2218
+ --base Required; normalized to <BASE>.PERP if suffix missing
2219
+ --quote Required (e.g. USDT)
2220
+ --bu-order-data-json Required JSON object \u2014 ONLY keys from CreateFuturesGridOrderData in openapi_bot.yaml
2221
+ Optional: --copy-from, --copy-type, --copy-bot-order-id
2222
+ buOrderData required: top, bottom, row, grid_type, trend, leverage, quoteInvestment
2223
+ buOrderData optional (names only): extraMargin, condition, conditionDirection, lossStopType, lossStop,
2224
+ lossStopDelay, profitStopType, profitStop, profitStopDelay, lossStopHigh, shareRatio, investCoin,
2225
+ investmentFrom, uiInvestCoin, lossStopLimitPrice, lossStopLimitHighPrice, profitStopLimitPrice,
2226
+ slippage, bonusId, uiExtraData, movingIndicatorType, movingIndicatorInterval, movingIndicatorParam,
2227
+ movingTrailingUpParam, cateType, movingTop, movingBottom, enableFollowClosed
2228
+ Unknown keys or openPrice/keyId \u2192 error (keyId/openPrice stripped before check if present)
2229
+ YAML: https://github.com/pionex-official/pionex-open-api/blob/main/openapi_bot.yaml
2230
+ Docs: https://www.pionex.com/docs/api-docs/bot-api/futures-grid
2084
2231
  `);
2085
2232
  }
2086
2233
  function parseJsonFlag(raw, flagName) {
@@ -2255,21 +2402,17 @@ async function runPionexCommand(argv) {
2255
2402
  return;
2256
2403
  }
2257
2404
  if (command === "create") {
2258
- const keyId = typeof flags["key-id"] === "string" ? flags["key-id"] : typeof flags.keyId === "string" ? flags.keyId : void 0;
2259
- const exchange = typeof flags.exchange === "string" ? flags.exchange : void 0;
2260
2405
  const base = typeof flags.base === "string" ? flags.base : void 0;
2261
2406
  const quote = typeof flags.quote === "string" ? flags.quote : void 0;
2262
2407
  const copyFrom = typeof flags["copy-from"] === "string" ? flags["copy-from"] : typeof flags.copyFrom === "string" ? flags.copyFrom : void 0;
2263
2408
  const copyType = typeof flags["copy-type"] === "string" ? flags["copy-type"] : typeof flags.copyType === "string" ? flags.copyType : void 0;
2264
- const groupId = typeof flags["group-id"] === "string" ? flags["group-id"] : typeof flags.groupId === "string" ? flags.groupId : void 0;
2265
2409
  const copyBotOrderId = typeof flags["copy-bot-order-id"] === "string" ? flags["copy-bot-order-id"] : typeof flags.copyBotOrderId === "string" ? flags.copyBotOrderId : void 0;
2266
- const lang = typeof flags.lang === "string" ? flags.lang : void 0;
2267
- const buOrderData = parseJsonFlag(flags["bu-order-data-json"] ?? flags.buOrderDataJson, "bu-order-data-json");
2268
- if (!base) {
2269
- throw new Error("Missing required flags: --base --bu-order-data-json");
2410
+ const buOrderDataRaw = parseJsonFlag(flags["bu-order-data-json"] ?? flags.buOrderDataJson, "bu-order-data-json");
2411
+ if (!base || !quote) {
2412
+ throw new Error("Missing required flags: --base --quote --bu-order-data-json");
2270
2413
  }
2271
- const payload = { exchange, base, quote, copyFrom, copyType, groupId, copyBotOrderId, lang, buOrderData };
2272
- if (keyId) payload.keyId = keyId;
2414
+ const buOrderData = parseAndValidateCreateFuturesGridBuOrderData(buOrderDataRaw);
2415
+ const payload = { base, quote, copyFrom, copyType, copyBotOrderId, buOrderData };
2273
2416
  if (dryRun) {
2274
2417
  const out2 = await runTool("pionex_bot_create_futures_grid_order", { ...payload, __dryRun: true });
2275
2418
  process.stdout.write(JSON.stringify(out2.data, null, 2) + "\n");