@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 +30 -11
- package/dist/index.js +312 -167
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
44
|
-
|
|
45
|
-
- `
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
1280
|
-
|
|
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
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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
|
-
|
|
1288
|
-
|
|
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
|
-
|
|
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
|
|
1293
|
-
|
|
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
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
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
|
|
1514
|
+
return n;
|
|
1315
1515
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1383
|
-
const
|
|
1384
|
-
|
|
1385
|
-
const
|
|
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.
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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 =
|
|
1498
|
-
const type =
|
|
1499
|
-
|
|
1500
|
-
const extraMargin =
|
|
1501
|
-
const 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
|
-
|
|
1648
|
+
asPositiveNumber2(args.quoteInvestment, "quoteInvestment");
|
|
1504
1649
|
}
|
|
1505
1650
|
if (type === "adjust_params") {
|
|
1506
|
-
const bottom =
|
|
1507
|
-
const 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
|
-
|
|
1656
|
+
asPositiveInteger2(args.row, "row");
|
|
1512
1657
|
}
|
|
1513
1658
|
if (type === "invest_in_trigger") {
|
|
1514
|
-
|
|
1515
|
-
const conditionDirection =
|
|
1516
|
-
|
|
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 =
|
|
1525
|
-
if (args.bottom != null) body.bottom =
|
|
1526
|
-
if (args.top != null) body.top =
|
|
1527
|
-
if (args.row != null) body.row =
|
|
1528
|
-
if (args.extraMarginAmount != null) body.extraMarginAmount =
|
|
1529
|
-
if (args.isRecommend != null) body.isRecommend =
|
|
1530
|
-
if (args.isReinvest != null) body.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 =
|
|
1534
|
-
|
|
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 =
|
|
1682
|
+
if (args.condition != null) body.condition = asPositiveDecimalString2(args.condition, "condition");
|
|
1538
1683
|
if (args.conditionDirection != null) {
|
|
1539
|
-
const conditionDirection =
|
|
1540
|
-
|
|
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 =
|
|
1571
|
-
const openPrice =
|
|
1572
|
-
const 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 =
|
|
1582
|
-
|
|
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 =
|
|
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 =
|
|
1614
|
-
|
|
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 =
|
|
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
|
}
|