@pionex/pionex-ai-kit 0.2.27 → 0.2.29

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,82 +1581,236 @@ 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 decimalPlaces(value) {
1550
- const idx = value.indexOf(".");
1551
- return idx === -1 ? 0 : value.length - idx - 1;
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;
1552
1594
  }
1553
- function tenPow(exp) {
1554
- let out = 1n;
1555
- for (let i = 0; i < exp; i++) out *= 10n;
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 leverage = 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,
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");
1556
1676
  return out;
1557
1677
  }
1558
- function decimalStringToInt(value) {
1559
- const [whole, frac = ""] = value.split(".");
1560
- return { intVal: BigInt(`${whole}${frac}`), scale: frac.length };
1561
- }
1562
- function intToDecimalString(value, scale) {
1563
- const negative = value < 0n;
1564
- const abs = negative ? -value : value;
1565
- const digits = abs.toString().padStart(scale + 1, "0");
1566
- const whole = digits.slice(0, digits.length - scale);
1567
- const frac = scale > 0 ? digits.slice(digits.length - scale) : "";
1568
- const merged = scale > 0 ? `${whole}.${frac}` : whole;
1569
- const trimmed = merged.replace(/\.?0+$/, "");
1570
- return negative ? `-${trimmed}` : trimmed;
1571
- }
1572
- function multiplyDecimalByRatio(value, numerator, denominator, outScale) {
1573
- const { intVal, scale } = decimalStringToInt(value);
1574
- const n = BigInt(numerator);
1575
- const d = BigInt(denominator);
1576
- const scaledNumerator = intVal * n * tenPow(outScale);
1577
- const scaledDenominator = d * tenPow(scale);
1578
- const rounded = (scaledNumerator + scaledDenominator / 2n) / scaledDenominator;
1579
- return intToDecimalString(rounded, outScale);
1580
- }
1581
- function maybePositiveDecimalString(value) {
1582
- if (typeof value === "string" && /^\d+(\.\d+)?$/.test(value)) {
1583
- const n = Number(value);
1584
- if (Number.isFinite(n) && n > 0) return value;
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" }
1585
1743
  }
1586
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
1587
- 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
+ exchange: { type: "string", description: "Optional. Futures exchange identifier (temporary compatibility)." },
1753
+ copyFrom: { type: "string", description: "Optional. Copy source order ID" },
1754
+ copyType: { type: "string", description: "Optional. Copy type" },
1755
+ copyBotOrderId: { type: "string", description: "Optional. Copy bot order ID" },
1756
+ buOrderData: createFuturesGridOrderDataJsonSchema,
1757
+ __dryRun: { type: "boolean", description: "Internal: when true, return resolved body without POST" }
1758
+ }
1759
+ };
1760
+ function asNonEmptyString2(value, field) {
1761
+ if (typeof value !== "string" || value.trim().length === 0) {
1762
+ throw new Error(`Invalid "${field}": expected non-empty string.`);
1588
1763
  }
1589
- return void 0;
1764
+ return value.trim();
1590
1765
  }
1591
- function normalizePerpBase(base) {
1592
- return base.endsWith(".PERP") ? base : `${base}.PERP`;
1766
+ function asFiniteNumber2(value, field) {
1767
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1768
+ throw new Error(`Invalid "${field}": expected finite number.`);
1769
+ }
1770
+ return value;
1593
1771
  }
1594
- function tryExtractCurrentPrice(payload, symbol) {
1595
- const stacks = [payload];
1596
- while (stacks.length > 0) {
1597
- const node = stacks.pop();
1598
- if (!node || typeof node !== "object") continue;
1599
- const obj = node;
1600
- const nodeSymbol = typeof obj.symbol === "string" ? obj.symbol : void 0;
1601
- const candidateClose = maybePositiveDecimalString(obj.close) ?? maybePositiveDecimalString(obj.last) ?? maybePositiveDecimalString(obj.lastPrice) ?? maybePositiveDecimalString(obj.price);
1602
- if (candidateClose && (!nodeSymbol || nodeSymbol === symbol)) {
1603
- return candidateClose;
1604
- }
1605
- for (const v of Object.values(obj)) {
1606
- if (Array.isArray(v)) {
1607
- for (const item of v) stacks.push(item);
1608
- } else if (v && typeof v === "object") {
1609
- stacks.push(v);
1610
- }
1611
- }
1772
+ function asPositiveNumber2(value, field) {
1773
+ const n = asFiniteNumber2(value, field);
1774
+ if (n <= 0) throw new Error(`Invalid "${field}": expected number > 0.`);
1775
+ return n;
1776
+ }
1777
+ function asPositiveInteger2(value, field) {
1778
+ const n = asPositiveNumber2(value, field);
1779
+ if (!Number.isInteger(n)) {
1780
+ throw new Error(`Invalid "${field}": expected positive integer.`);
1612
1781
  }
1613
- return void 0;
1782
+ return n;
1614
1783
  }
1615
- async function getCurrentSymbolPrice(client, symbol) {
1616
- const tickerPayload = (await client.publicGet("/api/v1/market/tickers", { symbol })).data;
1617
- const extracted = tryExtractCurrentPrice(tickerPayload, symbol);
1618
- if (!extracted) {
1619
- throw new Error(`Unable to infer current market price for ${symbol} from ticker response. Please provide buOrderData.top and buOrderData.bottom explicitly.`);
1784
+ function asBoolean2(value, field) {
1785
+ if (typeof value !== "boolean") {
1786
+ throw new Error(`Invalid "${field}": expected boolean.`);
1620
1787
  }
1621
- return extracted;
1788
+ return value;
1789
+ }
1790
+ function assertEnum2(value, field, allowed) {
1791
+ if (!allowed.includes(value)) {
1792
+ throw new Error(`Invalid "${field}": expected one of ${allowed.join(", ")}.`);
1793
+ }
1794
+ }
1795
+ function asObject(value, field) {
1796
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1797
+ throw new Error(`Invalid "${field}": expected JSON object.`);
1798
+ }
1799
+ return value;
1800
+ }
1801
+ function asPositiveDecimalString2(value, field) {
1802
+ const s = asNonEmptyString2(value, field);
1803
+ if (!/^\d+(\.\d+)?$/.test(s)) {
1804
+ throw new Error(`Invalid "${field}": expected positive decimal string.`);
1805
+ }
1806
+ const n = Number(s);
1807
+ if (!Number.isFinite(n) || n <= 0) {
1808
+ throw new Error(`Invalid "${field}": expected positive decimal string.`);
1809
+ }
1810
+ return s;
1811
+ }
1812
+ function normalizePerpBase(base) {
1813
+ return base.endsWith(".PERP") ? base : `${base}.PERP`;
1622
1814
  }
1623
1815
  function registerBotTools() {
1624
1816
  return [
@@ -1647,121 +1839,38 @@ function registerBotTools() {
1647
1839
  name: "pionex_bot_create_futures_grid_order",
1648
1840
  module: "bot",
1649
1841
  isWrite: true,
1650
- description: "Create a futures grid bot order.",
1651
- inputSchema: {
1652
- type: "object",
1653
- additionalProperties: false,
1654
- properties: {
1655
- keyId: { type: "string" },
1656
- exchange: { type: "string", description: "e.g. pionex.v2" },
1657
- base: { type: "string", description: "e.g. BTC" },
1658
- quote: { type: "string", description: "e.g. USDT" },
1659
- copyFrom: { type: "string" },
1660
- copyType: { type: "string" },
1661
- groupId: { type: "string" },
1662
- copyBotOrderId: { type: "string" },
1663
- lang: { type: "string" },
1664
- buOrderData: {
1665
- type: "object",
1666
- additionalProperties: true,
1667
- description: "CreateFuturesGridOrderData payload from openapi_bot.yaml."
1668
- }
1669
- },
1670
- required: ["base", "buOrderData"]
1671
- },
1842
+ 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; unknown keys rejected.",
1843
+ inputSchema: createFuturesGridCreateToolInputSchema,
1672
1844
  async handler(args, { client, config }) {
1673
1845
  if (config.readOnly) {
1674
1846
  throw new Error("Server is running in --read-only mode; bot create is disabled.");
1675
1847
  }
1676
- const defaultsApplied = {};
1677
- const exchange = asNonEmptyString(args.exchange ?? "pionex.v2", "exchange");
1678
- if (args.exchange == null) defaultsApplied.exchange = exchange;
1679
- const rawBase = asNonEmptyString(args.base, "base");
1848
+ const rawBase = asNonEmptyString2(args.base, "base");
1680
1849
  const base = normalizePerpBase(rawBase);
1681
- if (base !== rawBase) defaultsApplied.base = base;
1682
- const quote = asNonEmptyString(args.quote ?? "USDT", "quote");
1683
- if (args.quote == null) defaultsApplied.quote = quote;
1684
- const buOrderData = asObject(args.buOrderData, "buOrderData");
1685
- const symbol = `${base}_${quote}`;
1686
- const needsTickerForTopBottom = buOrderData.top == null || buOrderData.bottom == null;
1687
- const shouldTryTicker = needsTickerForTopBottom;
1688
- let currentPrice;
1689
- if (shouldTryTicker) {
1690
- try {
1691
- currentPrice = asPositiveDecimalString(await getCurrentSymbolPrice(client, symbol), "currentPrice");
1692
- } catch {
1693
- currentPrice = void 0;
1694
- }
1695
- }
1696
- if (needsTickerForTopBottom && currentPrice == null) {
1697
- throw new Error(`Unable to infer current market price for ${symbol} from ticker response. Please provide buOrderData.top and buOrderData.bottom explicitly.`);
1698
- }
1699
- const autoPriceScale = currentPrice == null ? void 0 : Math.min(8, Math.max(2, decimalPlaces(currentPrice) + 2));
1700
- const top = asPositiveDecimalString(
1701
- buOrderData.top ?? multiplyDecimalByRatio(currentPrice, 105, 100, autoPriceScale),
1702
- "buOrderData.top"
1703
- );
1704
- if (buOrderData.top == null) defaultsApplied.top = top;
1705
- const bottom = asPositiveDecimalString(
1706
- buOrderData.bottom ?? multiplyDecimalByRatio(currentPrice, 95, 100, autoPriceScale),
1707
- "buOrderData.bottom"
1708
- );
1709
- if (buOrderData.bottom == null) defaultsApplied.bottom = bottom;
1710
- if (Number(top) <= Number(bottom)) {
1711
- throw new Error('Invalid "buOrderData.top": expected top > bottom.');
1712
- }
1713
- const row = asPositiveInteger(buOrderData.row ?? 10, "buOrderData.row");
1714
- if (buOrderData.row == null) defaultsApplied.row = row;
1715
- const gridType = asNonEmptyString(buOrderData.grid_type ?? "arithmetic", "buOrderData.grid_type");
1716
- assertEnum(gridType, "buOrderData.grid_type", ["arithmetic", "geometric"]);
1717
- if (buOrderData.grid_type == null) defaultsApplied.grid_type = gridType;
1718
- const openPrice = buOrderData.openPrice == null ? void 0 : asPositiveDecimalString(buOrderData.openPrice, "buOrderData.openPrice");
1719
- const trend = asNonEmptyString(buOrderData.trend, "buOrderData.trend");
1720
- assertEnum(trend, "buOrderData.trend", ["long", "short", "no_trend"]);
1721
- const leverage = asPositiveNumber(buOrderData.leverage ?? 2, "buOrderData.leverage");
1722
- if (buOrderData.leverage == null) defaultsApplied.leverage = leverage;
1723
- const extraMargin = asNonNegativeDecimalString(buOrderData.extraMargin ?? "0", "buOrderData.extraMargin");
1724
- if (buOrderData.extraMargin == null) defaultsApplied.extraMargin = extraMargin;
1725
- const quoteInvestment = asPositiveDecimalString(buOrderData.quoteInvestment, "buOrderData.quoteInvestment");
1850
+ const quote = asNonEmptyString2(args.quote, "quote");
1851
+ const exchange = args.exchange != null ? asNonEmptyString2(args.exchange, "exchange") : void 0;
1852
+ const buOrderDataOut = parseAndValidateCreateFuturesGridBuOrderData(asObject(args.buOrderData, "buOrderData"));
1853
+ const row = buOrderDataOut.row;
1854
+ const gridType = buOrderDataOut.grid_type;
1855
+ const leverage = buOrderDataOut.leverage;
1726
1856
  const body = {
1727
- exchange,
1728
1857
  base,
1729
1858
  quote,
1730
- buOrderData: {
1731
- ...buOrderData,
1732
- top,
1733
- bottom,
1734
- row,
1735
- grid_type: gridType,
1736
- trend,
1737
- leverage,
1738
- extraMargin,
1739
- quoteInvestment
1740
- }
1859
+ buOrderData: buOrderDataOut
1741
1860
  };
1742
- if (openPrice != null) {
1743
- body.buOrderData.openPrice = openPrice;
1744
- }
1745
- if (args.keyId != null) body.keyId = asNonEmptyString(args.keyId, "keyId");
1861
+ if (exchange != null) body.exchange = exchange;
1746
1862
  if (args.copyFrom != null) body.copyFrom = String(args.copyFrom);
1747
1863
  if (args.copyType != null) body.copyType = String(args.copyType);
1748
- if (args.groupId != null) body.groupId = String(args.groupId);
1749
1864
  if (args.copyBotOrderId != null) body.copyBotOrderId = String(args.copyBotOrderId);
1750
- if (args.lang != null) body.lang = String(args.lang);
1751
1865
  if (args.__dryRun === true) {
1752
- const rowSource = buOrderData.row == null ? "default" : "user";
1753
1866
  return {
1754
1867
  dryRun: true,
1755
- note: "No order was sent. This is the resolved request body after applying defaults.",
1756
- marketSymbol: symbol,
1757
- marketPriceUsed: currentPrice,
1868
+ note: "No order was sent. Body matches openapi_bot.yaml CreateFuturesGridRequest.",
1758
1869
  resolvedParams: {
1759
1870
  row,
1760
- rowSource,
1761
1871
  grid_type: gridType,
1762
1872
  leverage
1763
1873
  },
1764
- defaultsApplied,
1765
1874
  resolvedBody: body
1766
1875
  };
1767
1876
  }
@@ -1801,26 +1910,26 @@ function registerBotTools() {
1801
1910
  if (config.readOnly) {
1802
1911
  throw new Error("Server is running in --read-only mode; bot adjust is disabled.");
1803
1912
  }
1804
- const buOrderId = asNonEmptyString(args.buOrderId, "buOrderId");
1805
- const type = asNonEmptyString(args.type, "type");
1806
- assertEnum(type, "type", ["invest_in", "adjust_params", "invest_in_trigger"]);
1807
- const extraMargin = asBoolean(args.extraMargin, "extraMargin");
1808
- const openPrice = asFiniteNumber(args.openPrice, "openPrice");
1913
+ const buOrderId = asNonEmptyString2(args.buOrderId, "buOrderId");
1914
+ const type = asNonEmptyString2(args.type, "type");
1915
+ assertEnum2(type, "type", ["invest_in", "adjust_params", "invest_in_trigger"]);
1916
+ const extraMargin = asBoolean2(args.extraMargin, "extraMargin");
1917
+ const openPrice = asFiniteNumber2(args.openPrice, "openPrice");
1809
1918
  if (type === "invest_in" && args.quoteInvestment != null) {
1810
- asPositiveNumber(args.quoteInvestment, "quoteInvestment");
1919
+ asPositiveNumber2(args.quoteInvestment, "quoteInvestment");
1811
1920
  }
1812
1921
  if (type === "adjust_params") {
1813
- const bottom = asPositiveDecimalString(args.bottom, "bottom");
1814
- const top = asPositiveDecimalString(args.top, "top");
1922
+ const bottom = asPositiveDecimalString2(args.bottom, "bottom");
1923
+ const top = asPositiveDecimalString2(args.top, "top");
1815
1924
  if (Number(top) <= Number(bottom)) {
1816
1925
  throw new Error('Invalid "top": expected top > bottom.');
1817
1926
  }
1818
- asPositiveInteger(args.row, "row");
1927
+ asPositiveInteger2(args.row, "row");
1819
1928
  }
1820
1929
  if (type === "invest_in_trigger") {
1821
- asPositiveDecimalString(args.condition, "condition");
1822
- const conditionDirection = asNonEmptyString(args.conditionDirection, "conditionDirection");
1823
- assertEnum(conditionDirection, "conditionDirection", ["1", "-1"]);
1930
+ asPositiveDecimalString2(args.condition, "condition");
1931
+ const conditionDirection = asNonEmptyString2(args.conditionDirection, "conditionDirection");
1932
+ assertEnum2(conditionDirection, "conditionDirection", ["1", "-1"]);
1824
1933
  }
1825
1934
  const body = {
1826
1935
  buOrderId,
@@ -1828,23 +1937,23 @@ function registerBotTools() {
1828
1937
  extraMargin,
1829
1938
  openPrice
1830
1939
  };
1831
- if (args.quoteInvestment != null) body.quoteInvestment = asFiniteNumber(args.quoteInvestment, "quoteInvestment");
1832
- if (args.bottom != null) body.bottom = asPositiveDecimalString(args.bottom, "bottom");
1833
- if (args.top != null) body.top = asPositiveDecimalString(args.top, "top");
1834
- if (args.row != null) body.row = asPositiveInteger(args.row, "row");
1835
- if (args.extraMarginAmount != null) body.extraMarginAmount = asFiniteNumber(args.extraMarginAmount, "extraMarginAmount");
1836
- if (args.isRecommend != null) body.isRecommend = asBoolean(args.isRecommend, "isRecommend");
1837
- if (args.isReinvest != null) body.isReinvest = asBoolean(args.isReinvest, "isReinvest");
1940
+ if (args.quoteInvestment != null) body.quoteInvestment = asFiniteNumber2(args.quoteInvestment, "quoteInvestment");
1941
+ if (args.bottom != null) body.bottom = asPositiveDecimalString2(args.bottom, "bottom");
1942
+ if (args.top != null) body.top = asPositiveDecimalString2(args.top, "top");
1943
+ if (args.row != null) body.row = asPositiveInteger2(args.row, "row");
1944
+ if (args.extraMarginAmount != null) body.extraMarginAmount = asFiniteNumber2(args.extraMarginAmount, "extraMarginAmount");
1945
+ if (args.isRecommend != null) body.isRecommend = asBoolean2(args.isRecommend, "isRecommend");
1946
+ if (args.isReinvest != null) body.isReinvest = asBoolean2(args.isReinvest, "isReinvest");
1838
1947
  if (args.investCoin != null) body.investCoin = String(args.investCoin);
1839
1948
  if (args.investmentFrom != null) {
1840
- const investmentFrom = asNonEmptyString(args.investmentFrom, "investmentFrom");
1841
- assertEnum(investmentFrom, "investmentFrom", ["USER", "LOCK_ACTIVITY"]);
1949
+ const investmentFrom = asNonEmptyString2(args.investmentFrom, "investmentFrom");
1950
+ assertEnum2(investmentFrom, "investmentFrom", ["USER", "LOCK_ACTIVITY"]);
1842
1951
  body.investmentFrom = investmentFrom;
1843
1952
  }
1844
- if (args.condition != null) body.condition = asPositiveDecimalString(args.condition, "condition");
1953
+ if (args.condition != null) body.condition = asPositiveDecimalString2(args.condition, "condition");
1845
1954
  if (args.conditionDirection != null) {
1846
- const conditionDirection = asNonEmptyString(args.conditionDirection, "conditionDirection");
1847
- assertEnum(conditionDirection, "conditionDirection", ["1", "-1"]);
1955
+ const conditionDirection = asNonEmptyString2(args.conditionDirection, "conditionDirection");
1956
+ assertEnum2(conditionDirection, "conditionDirection", ["1", "-1"]);
1848
1957
  body.conditionDirection = conditionDirection;
1849
1958
  }
1850
1959
  if (args.slippage != null) body.slippage = String(args.slippage);
@@ -1874,9 +1983,9 @@ function registerBotTools() {
1874
1983
  if (config.readOnly) {
1875
1984
  throw new Error("Server is running in --read-only mode; bot reduce is disabled.");
1876
1985
  }
1877
- const buOrderId = asNonEmptyString(args.buOrderId, "buOrderId");
1878
- const openPrice = asPositiveDecimalString(args.openPrice, "openPrice");
1879
- const reduceNum = asPositiveInteger(args.reduceNum, "reduceNum");
1986
+ const buOrderId = asNonEmptyString2(args.buOrderId, "buOrderId");
1987
+ const openPrice = asPositiveDecimalString2(args.openPrice, "openPrice");
1988
+ const reduceNum = asPositiveInteger2(args.reduceNum, "reduceNum");
1880
1989
  const body = {
1881
1990
  buOrderId,
1882
1991
  openPrice,
@@ -1885,8 +1994,8 @@ function registerBotTools() {
1885
1994
  if (args.slippage != null) body.slippage = String(args.slippage);
1886
1995
  if (args.condition != null) body.condition = String(args.condition);
1887
1996
  if (args.conditionDirection != null) {
1888
- const conditionDirection = asNonEmptyString(args.conditionDirection, "conditionDirection");
1889
- assertEnum(conditionDirection, "conditionDirection", ["1", "-1"]);
1997
+ const conditionDirection = asNonEmptyString2(args.conditionDirection, "conditionDirection");
1998
+ assertEnum2(conditionDirection, "conditionDirection", ["1", "-1"]);
1890
1999
  body.conditionDirection = conditionDirection;
1891
2000
  }
1892
2001
  return (await client.signedPost("/api/v1/bot/orders/futuresGrid/reduce", body)).data;
@@ -1913,15 +2022,15 @@ function registerBotTools() {
1913
2022
  if (config.readOnly) {
1914
2023
  throw new Error("Server is running in --read-only mode; bot cancel is disabled.");
1915
2024
  }
1916
- const buOrderId = asNonEmptyString(args.buOrderId, "buOrderId");
2025
+ const buOrderId = asNonEmptyString2(args.buOrderId, "buOrderId");
1917
2026
  const body = { buOrderId };
1918
2027
  if (args.closeNote != null) body.closeNote = String(args.closeNote);
1919
2028
  if (args.closeSellModel != null) {
1920
- const closeSellModel = asNonEmptyString(args.closeSellModel, "closeSellModel");
1921
- assertEnum(closeSellModel, "closeSellModel", ["TO_QUOTE", "TO_USDT"]);
2029
+ const closeSellModel = asNonEmptyString2(args.closeSellModel, "closeSellModel");
2030
+ assertEnum2(closeSellModel, "closeSellModel", ["TO_QUOTE", "TO_USDT"]);
1922
2031
  body.closeSellModel = closeSellModel;
1923
2032
  }
1924
- if (args.immediate != null) body.immediate = asBoolean(args.immediate, "immediate");
2033
+ if (args.immediate != null) body.immediate = asBoolean2(args.immediate, "immediate");
1925
2034
  if (args.closeSlippage != null) body.closeSlippage = String(args.closeSlippage);
1926
2035
  return (await client.signedPost("/api/v1/bot/orders/futuresGrid/cancel", body)).data;
1927
2036
  }
@@ -2100,27 +2209,29 @@ Examples:
2100
2209
  pionex orders new --symbol BTC_USDT --side BUY --type MARKET --amount 10
2101
2210
  pionex orders cancel --symbol BTC_USDT --order-id 123
2102
2211
  pionex bot get --bu-order-id <id>
2103
- 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"}'
2212
+ 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"}'
2104
2213
 
2105
2214
  Global flags:
2106
2215
  --profile <name> Profile in ~/.pionex/config.toml
2107
2216
  --modules <list> Comma-separated modules (market,account,orders or all)
2108
2217
  --base-url <url> Override API base URL
2109
2218
  --read-only Disable write operations (orders new/cancel)
2110
- --dry-run Print the tool call payload without executing (write ops only)
2219
+ --dry-run Print resolved futures-grid create body without executing (bot create only)
2111
2220
 
2112
- Bot create defaults:
2113
- exchange Defaults to pionex.v2 when missing
2114
- quote Defaults to USDT when missing
2115
- base Auto-normalized to <BASE>.PERP when missing suffix
2116
- --key-id Optional
2117
- buOrderData.top Defaults to current price * 1.05 when missing
2118
- buOrderData.bottom Defaults to current price * 0.95 when missing
2119
- buOrderData.row Defaults to 10 when missing
2120
- buOrderData.grid_type Defaults to arithmetic when missing
2121
- buOrderData.leverage Defaults to 2 when missing
2122
- buOrderData.openPrice Optional. If omitted, it is not sent
2123
- buOrderData.extraMargin Defaults to "0" when missing
2221
+ Futures grid create (pionex bot create) \u2014 strict OpenAPI (same validation as MCP):
2222
+ --base Required; normalized to <BASE>.PERP if suffix missing
2223
+ --quote Required (e.g. USDT)
2224
+ --bu-order-data-json Required JSON object \u2014 ONLY keys from CreateFuturesGridOrderData in openapi_bot.yaml
2225
+ Optional: --copy-from, --copy-type, --copy-bot-order-id
2226
+ buOrderData required: top, bottom, row, grid_type, trend, leverage, quoteInvestment
2227
+ buOrderData optional (names only): extraMargin, condition, conditionDirection, lossStopType, lossStop,
2228
+ lossStopDelay, profitStopType, profitStop, profitStopDelay, lossStopHigh, shareRatio, investCoin,
2229
+ investmentFrom, uiInvestCoin, lossStopLimitPrice, lossStopLimitHighPrice, profitStopLimitPrice,
2230
+ slippage, bonusId, uiExtraData, movingIndicatorType, movingIndicatorInterval, movingIndicatorParam,
2231
+ movingTrailingUpParam, cateType, movingTop, movingBottom, enableFollowClosed
2232
+ Unknown keys \u2192 error
2233
+ YAML: https://github.com/pionex-official/pionex-open-api/blob/main/openapi_bot.yaml
2234
+ Docs: https://www.pionex.com/docs/api-docs/bot-api/futures-grid
2124
2235
  `);
2125
2236
  }
2126
2237
  function parseJsonFlag(raw, flagName) {
@@ -2295,21 +2406,18 @@ async function runPionexCommand(argv) {
2295
2406
  return;
2296
2407
  }
2297
2408
  if (command === "create") {
2298
- const keyId = typeof flags["key-id"] === "string" ? flags["key-id"] : typeof flags.keyId === "string" ? flags.keyId : void 0;
2299
- const exchange = typeof flags.exchange === "string" ? flags.exchange : void 0;
2300
2409
  const base = typeof flags.base === "string" ? flags.base : void 0;
2301
2410
  const quote = typeof flags.quote === "string" ? flags.quote : void 0;
2411
+ const exchange = typeof flags.exchange === "string" ? flags.exchange : void 0;
2302
2412
  const copyFrom = typeof flags["copy-from"] === "string" ? flags["copy-from"] : typeof flags.copyFrom === "string" ? flags.copyFrom : void 0;
2303
2413
  const copyType = typeof flags["copy-type"] === "string" ? flags["copy-type"] : typeof flags.copyType === "string" ? flags.copyType : void 0;
2304
- const groupId = typeof flags["group-id"] === "string" ? flags["group-id"] : typeof flags.groupId === "string" ? flags.groupId : void 0;
2305
2414
  const copyBotOrderId = typeof flags["copy-bot-order-id"] === "string" ? flags["copy-bot-order-id"] : typeof flags.copyBotOrderId === "string" ? flags.copyBotOrderId : void 0;
2306
- const lang = typeof flags.lang === "string" ? flags.lang : void 0;
2307
- const buOrderData = parseJsonFlag(flags["bu-order-data-json"] ?? flags.buOrderDataJson, "bu-order-data-json");
2308
- if (!base) {
2309
- throw new Error("Missing required flags: --base --bu-order-data-json");
2415
+ const buOrderDataRaw = parseJsonFlag(flags["bu-order-data-json"] ?? flags.buOrderDataJson, "bu-order-data-json");
2416
+ if (!base || !quote) {
2417
+ throw new Error("Missing required flags: --base --quote --bu-order-data-json");
2310
2418
  }
2311
- const payload = { exchange, base, quote, copyFrom, copyType, groupId, copyBotOrderId, lang, buOrderData };
2312
- if (keyId) payload.keyId = keyId;
2419
+ const buOrderData = parseAndValidateCreateFuturesGridBuOrderData(buOrderDataRaw);
2420
+ const payload = { base, quote, exchange, copyFrom, copyType, copyBotOrderId, buOrderData };
2313
2421
  if (dryRun) {
2314
2422
  const out2 = await runTool("pionex_bot_create_futures_grid_order", { ...payload, __dryRun: true });
2315
2423
  process.stdout.write(JSON.stringify(out2.data, null, 2) + "\n");