@okx_ai/okx-trade-cli 1.3.0 → 1.3.1-beta.1
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 +1300 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1393,6 +1393,9 @@ function readNumber(args, key) {
|
|
|
1393
1393
|
if (value === void 0 || value === null) {
|
|
1394
1394
|
return void 0;
|
|
1395
1395
|
}
|
|
1396
|
+
if (typeof value === "string" && /^-?\d+(\.\d+)?$/.test(value)) {
|
|
1397
|
+
return parseFloat(value);
|
|
1398
|
+
}
|
|
1396
1399
|
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
1397
1400
|
throw new ValidationError(`Parameter "${key}" must be a number.`);
|
|
1398
1401
|
}
|
|
@@ -1472,7 +1475,8 @@ var OKX_INST_TYPES = [
|
|
|
1472
1475
|
"SWAP",
|
|
1473
1476
|
"FUTURES",
|
|
1474
1477
|
"OPTION",
|
|
1475
|
-
"MARGIN"
|
|
1478
|
+
"MARGIN",
|
|
1479
|
+
"EVENTS"
|
|
1476
1480
|
];
|
|
1477
1481
|
function publicRateLimit(key, rps = 20) {
|
|
1478
1482
|
return {
|
|
@@ -1770,6 +1774,7 @@ var MODULES = [
|
|
|
1770
1774
|
"futures",
|
|
1771
1775
|
"option",
|
|
1772
1776
|
"account",
|
|
1777
|
+
"event",
|
|
1773
1778
|
...EARN_SUB_MODULE_IDS,
|
|
1774
1779
|
...BOT_SUB_MODULE_IDS,
|
|
1775
1780
|
"skills"
|
|
@@ -1963,7 +1968,7 @@ function registerAccountTools() {
|
|
|
1963
1968
|
properties: {
|
|
1964
1969
|
instType: {
|
|
1965
1970
|
type: "string",
|
|
1966
|
-
enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"]
|
|
1971
|
+
enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION", "EVENTS"]
|
|
1967
1972
|
},
|
|
1968
1973
|
ccy: {
|
|
1969
1974
|
type: "string",
|
|
@@ -2029,7 +2034,7 @@ function registerAccountTools() {
|
|
|
2029
2034
|
properties: {
|
|
2030
2035
|
instType: {
|
|
2031
2036
|
type: "string",
|
|
2032
|
-
enum: ["SWAP", "FUTURES", "MARGIN", "OPTION"],
|
|
2037
|
+
enum: ["SWAP", "FUTURES", "MARGIN", "OPTION", "EVENTS"],
|
|
2033
2038
|
description: "Default SWAP"
|
|
2034
2039
|
},
|
|
2035
2040
|
instId: {
|
|
@@ -2090,7 +2095,7 @@ function registerAccountTools() {
|
|
|
2090
2095
|
properties: {
|
|
2091
2096
|
instType: {
|
|
2092
2097
|
type: "string",
|
|
2093
|
-
enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"]
|
|
2098
|
+
enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION", "EVENTS"]
|
|
2094
2099
|
},
|
|
2095
2100
|
instId: {
|
|
2096
2101
|
type: "string",
|
|
@@ -2201,14 +2206,14 @@ function registerAccountTools() {
|
|
|
2201
2206
|
{
|
|
2202
2207
|
name: "account_get_positions",
|
|
2203
2208
|
module: "account",
|
|
2204
|
-
description: "Get current open positions across all instrument types (MARGIN, SWAP, FUTURES, OPTION). Use swap_get_positions for SWAP/FUTURES-only queries.",
|
|
2209
|
+
description: "Get current open positions across all instrument types (MARGIN, SWAP, FUTURES, OPTION, EVENTS). Use swap_get_positions for SWAP/FUTURES-only queries.",
|
|
2205
2210
|
isWrite: false,
|
|
2206
2211
|
inputSchema: {
|
|
2207
2212
|
type: "object",
|
|
2208
2213
|
properties: {
|
|
2209
2214
|
instType: {
|
|
2210
2215
|
type: "string",
|
|
2211
|
-
enum: ["MARGIN", "SWAP", "FUTURES", "OPTION"]
|
|
2216
|
+
enum: ["MARGIN", "SWAP", "FUTURES", "OPTION", "EVENTS"]
|
|
2212
2217
|
},
|
|
2213
2218
|
instId: {
|
|
2214
2219
|
type: "string",
|
|
@@ -5118,6 +5123,765 @@ function registerAllEarnTools() {
|
|
|
5118
5123
|
];
|
|
5119
5124
|
return tools.map(withDemoGuard);
|
|
5120
5125
|
}
|
|
5126
|
+
function findDateIdx(parts) {
|
|
5127
|
+
for (let i = 1; i < parts.length; i++) {
|
|
5128
|
+
if (/^\d{6}$/.test(parts[i])) return i;
|
|
5129
|
+
}
|
|
5130
|
+
return -1;
|
|
5131
|
+
}
|
|
5132
|
+
function inferExpiryMsFromInstId(instId) {
|
|
5133
|
+
const parts = instId.split("-");
|
|
5134
|
+
const upper = instId.toUpperCase();
|
|
5135
|
+
const dateIdx = findDateIdx(parts);
|
|
5136
|
+
if (dateIdx < 0) return null;
|
|
5137
|
+
const dp = parts[dateIdx];
|
|
5138
|
+
const year = 2e3 + parseInt(dp.slice(0, 2), 10);
|
|
5139
|
+
const month = parseInt(dp.slice(2, 4), 10) - 1;
|
|
5140
|
+
const day = parseInt(dp.slice(4, 6), 10);
|
|
5141
|
+
const isUpDown = upper.includes("UPDOWN");
|
|
5142
|
+
let timePart;
|
|
5143
|
+
if (isUpDown) {
|
|
5144
|
+
const candidate = parts[dateIdx + 2];
|
|
5145
|
+
timePart = candidate && /^\d{4}$/.test(candidate) ? candidate : parts[dateIdx + 1];
|
|
5146
|
+
} else {
|
|
5147
|
+
timePart = parts[dateIdx + 1];
|
|
5148
|
+
}
|
|
5149
|
+
if (!timePart || !/^\d{4}$/.test(timePart)) return null;
|
|
5150
|
+
const hour = parseInt(timePart.slice(0, 2), 10);
|
|
5151
|
+
const min = parseInt(timePart.slice(2, 4), 10);
|
|
5152
|
+
return Date.UTC(year, month, day, hour - 8, min, 0, 0);
|
|
5153
|
+
}
|
|
5154
|
+
function extractSeriesId(instId) {
|
|
5155
|
+
const parts = instId.split("-");
|
|
5156
|
+
for (let i = 0; i < parts.length; i++) {
|
|
5157
|
+
if (/^\d{6}$/.test(parts[i])) {
|
|
5158
|
+
return parts.slice(0, i).join("-");
|
|
5159
|
+
}
|
|
5160
|
+
}
|
|
5161
|
+
return instId;
|
|
5162
|
+
}
|
|
5163
|
+
function fmtTimeToken(t) {
|
|
5164
|
+
return t.length === 4 ? `${t.slice(0, 2)}:${t.slice(2)}` : t;
|
|
5165
|
+
}
|
|
5166
|
+
function fmtUpDownName(seriesId, dateStr, parts, dateIdx) {
|
|
5167
|
+
const t1 = parts[dateIdx + 1] ?? "";
|
|
5168
|
+
const t2 = parts[dateIdx + 2] ?? "";
|
|
5169
|
+
const timeRange = t1 && t2 ? ` ${fmtTimeToken(t1)}-${fmtTimeToken(t2)}` : "";
|
|
5170
|
+
return `${seriesId} Up/Down \xB7 ${dateStr}${timeRange}`;
|
|
5171
|
+
}
|
|
5172
|
+
function fmtStrikeName(seriesId, dateStr, label, parts, dateIdx) {
|
|
5173
|
+
const strike = parts[dateIdx + 2] ?? "";
|
|
5174
|
+
const strikeStr = strike && /^\d+$/.test(strike) ? Number(strike).toLocaleString("en-US") : "";
|
|
5175
|
+
return strikeStr ? `${seriesId} ${label} ${strikeStr} \xB7 ${dateStr}` : `${seriesId} \xB7 ${dateStr}`;
|
|
5176
|
+
}
|
|
5177
|
+
function formatDisplayTitle(instId) {
|
|
5178
|
+
const parts = instId.split("-");
|
|
5179
|
+
const upper = instId.toUpperCase();
|
|
5180
|
+
const seriesId = parts[0] ?? instId;
|
|
5181
|
+
const dateIdx = findDateIdx(parts);
|
|
5182
|
+
if (dateIdx < 0) return instId;
|
|
5183
|
+
const d = parts[dateIdx];
|
|
5184
|
+
const month = parseInt(d.slice(2, 4), 10);
|
|
5185
|
+
const day = parseInt(d.slice(4, 6), 10);
|
|
5186
|
+
const dateStr = `${month}/${day}`;
|
|
5187
|
+
if (upper.includes("UPDOWN") || upper.includes("UP-DOWN")) {
|
|
5188
|
+
return fmtUpDownName(seriesId, dateStr, parts, dateIdx);
|
|
5189
|
+
}
|
|
5190
|
+
if (upper.includes("ABOVE")) {
|
|
5191
|
+
return fmtStrikeName(seriesId, dateStr, "above", parts, dateIdx);
|
|
5192
|
+
}
|
|
5193
|
+
if (upper.includes("TOUCH")) {
|
|
5194
|
+
return fmtStrikeName(seriesId, dateStr, "touch", parts, dateIdx);
|
|
5195
|
+
}
|
|
5196
|
+
return `${seriesId} \xB7 ${dateStr}`;
|
|
5197
|
+
}
|
|
5198
|
+
var OUTCOME_LABELS = {
|
|
5199
|
+
"0": "pending",
|
|
5200
|
+
"1": "YES",
|
|
5201
|
+
"2": "NO"
|
|
5202
|
+
};
|
|
5203
|
+
var ORDER_STATE_MAP = {
|
|
5204
|
+
live: "Unfilled",
|
|
5205
|
+
partially_filled: "Partially filled",
|
|
5206
|
+
filled: "Filled",
|
|
5207
|
+
canceled: "Canceled",
|
|
5208
|
+
mmp_canceled: "Canceled"
|
|
5209
|
+
};
|
|
5210
|
+
function mapOrderState(raw) {
|
|
5211
|
+
return ORDER_STATE_MAP[raw] ?? raw;
|
|
5212
|
+
}
|
|
5213
|
+
var TIMESTAMP_FIELDS = /* @__PURE__ */ new Set([
|
|
5214
|
+
"expTime",
|
|
5215
|
+
"settleTime",
|
|
5216
|
+
"listTime",
|
|
5217
|
+
"uTime",
|
|
5218
|
+
"cTime",
|
|
5219
|
+
"fixTime"
|
|
5220
|
+
]);
|
|
5221
|
+
var DEFAULT_SETTLE_CCY = "USDT";
|
|
5222
|
+
function convertTimestamps(item) {
|
|
5223
|
+
const result = { ...item };
|
|
5224
|
+
for (const key of TIMESTAMP_FIELDS) {
|
|
5225
|
+
if (!(key in result)) continue;
|
|
5226
|
+
const v = Number(result[key]);
|
|
5227
|
+
if (v > 0) {
|
|
5228
|
+
const d = new Date(v + 8 * 60 * 60 * 1e3);
|
|
5229
|
+
const yyyy = d.getUTCFullYear();
|
|
5230
|
+
const mo = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
5231
|
+
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
5232
|
+
const hh = String(d.getUTCHours()).padStart(2, "0");
|
|
5233
|
+
const mi = String(d.getUTCMinutes()).padStart(2, "0");
|
|
5234
|
+
result[key] = `${yyyy}-${mo}-${dd} ${hh}:${mi} UTC+8`;
|
|
5235
|
+
} else {
|
|
5236
|
+
delete result[key];
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
return result;
|
|
5240
|
+
}
|
|
5241
|
+
function normalizeWrite3(response) {
|
|
5242
|
+
const data = response.data;
|
|
5243
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
5244
|
+
const failed = data.filter(
|
|
5245
|
+
(item) => item !== null && typeof item === "object" && "sCode" in item && item["sCode"] !== "0"
|
|
5246
|
+
);
|
|
5247
|
+
if (failed.length > 0) {
|
|
5248
|
+
const messages2 = failed.map(
|
|
5249
|
+
(item) => `[${item["sCode"]}] ${item["sMsg"] ?? "Operation failed"}`
|
|
5250
|
+
);
|
|
5251
|
+
throw new OkxApiError(messages2.join("; "), {
|
|
5252
|
+
code: String(failed[0]["sCode"] ?? ""),
|
|
5253
|
+
endpoint: response.endpoint
|
|
5254
|
+
});
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
return { endpoint: response.endpoint, requestTime: response.requestTime, data };
|
|
5258
|
+
}
|
|
5259
|
+
function extractQuoteCcy(underlying) {
|
|
5260
|
+
if (!underlying) return DEFAULT_SETTLE_CCY;
|
|
5261
|
+
const parts = underlying.split("-");
|
|
5262
|
+
return parts.length >= 2 ? parts[parts.length - 1].toUpperCase() : DEFAULT_SETTLE_CCY;
|
|
5263
|
+
}
|
|
5264
|
+
async function fetchAvailableBalance(client, ccy = DEFAULT_SETTLE_CCY) {
|
|
5265
|
+
try {
|
|
5266
|
+
const r = await client.privateGet(
|
|
5267
|
+
"/api/v5/account/balance",
|
|
5268
|
+
{ ccy }
|
|
5269
|
+
);
|
|
5270
|
+
const data = r["data"];
|
|
5271
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
5272
|
+
const details = data[0]["details"];
|
|
5273
|
+
if (Array.isArray(details) && details.length > 0) {
|
|
5274
|
+
const entry = details.find(
|
|
5275
|
+
(d) => String(d["ccy"] ?? "").toUpperCase() === ccy.toUpperCase()
|
|
5276
|
+
);
|
|
5277
|
+
if (!entry) return { balance: null, ccy };
|
|
5278
|
+
const bal = String(entry["availBal"] ?? "") || null;
|
|
5279
|
+
return { balance: bal, ccy };
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
} catch {
|
|
5283
|
+
}
|
|
5284
|
+
return { balance: null, ccy };
|
|
5285
|
+
}
|
|
5286
|
+
var KNOWN_UNDERLYINGS = /^(BTC|ETH|TRX|EOS|SOL|IOTA|KISHU|SUSHI|BTG|XTZ|SOLVU)/i;
|
|
5287
|
+
function extractUnderlying(seriesId) {
|
|
5288
|
+
const m = seriesId.match(KNOWN_UNDERLYINGS);
|
|
5289
|
+
return m ? m[1].toUpperCase() : null;
|
|
5290
|
+
}
|
|
5291
|
+
function resolveOutcome(value) {
|
|
5292
|
+
const map = {
|
|
5293
|
+
up: "yes",
|
|
5294
|
+
yes: "yes",
|
|
5295
|
+
down: "no",
|
|
5296
|
+
no: "no"
|
|
5297
|
+
};
|
|
5298
|
+
const resolved = map[value.toLowerCase()];
|
|
5299
|
+
if (!resolved) {
|
|
5300
|
+
throw new Error(
|
|
5301
|
+
`Invalid outcome "${value}". Use: UP or YES for Up/Yes, DOWN or NO for Down/No.`
|
|
5302
|
+
);
|
|
5303
|
+
}
|
|
5304
|
+
return resolved;
|
|
5305
|
+
}
|
|
5306
|
+
function filterBrowseCandidates(allSeries, underlyingFilter) {
|
|
5307
|
+
const isHumanReadable = (id) => /^(BTC|ETH|TRX|EOS|SOL|IOTA|KISHU|SUSHI|BTG|XTZ|SOLVU)-/.test(id);
|
|
5308
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5309
|
+
const candidates = [];
|
|
5310
|
+
for (const s of allSeries) {
|
|
5311
|
+
const settlement = s["settlement"];
|
|
5312
|
+
const method = String(settlement?.["method"] ?? "");
|
|
5313
|
+
const uly = String(settlement?.["underlying"] ?? "");
|
|
5314
|
+
if (underlyingFilter && !uly.startsWith(underlyingFilter)) continue;
|
|
5315
|
+
const key = `${method}:${uly}`;
|
|
5316
|
+
if (!seen.has(key) && isHumanReadable(String(s["seriesId"] ?? ""))) {
|
|
5317
|
+
seen.add(key);
|
|
5318
|
+
candidates.push(s);
|
|
5319
|
+
}
|
|
5320
|
+
}
|
|
5321
|
+
return candidates;
|
|
5322
|
+
}
|
|
5323
|
+
function isActiveMarket(m, isUpDown, now) {
|
|
5324
|
+
if (!isUpDown && (!m["floorStrike"] || m["floorStrike"] === "")) return false;
|
|
5325
|
+
const expMs = Number(m["expTime"] ?? 0);
|
|
5326
|
+
return expMs <= 0 || expMs > now;
|
|
5327
|
+
}
|
|
5328
|
+
function toContractSummary(m) {
|
|
5329
|
+
const converted = convertTimestamps(m);
|
|
5330
|
+
const id = String(m["instId"] ?? "");
|
|
5331
|
+
return {
|
|
5332
|
+
instId: id,
|
|
5333
|
+
displayTitle: formatDisplayTitle(id),
|
|
5334
|
+
expTime: converted["expTime"],
|
|
5335
|
+
floorStrike: m["floorStrike"],
|
|
5336
|
+
px: m["px"],
|
|
5337
|
+
outcome: OUTCOME_LABELS[String(m["outcome"] ?? "")] ?? m["outcome"]
|
|
5338
|
+
};
|
|
5339
|
+
}
|
|
5340
|
+
async function fetchActiveContractsForSeries(client, s) {
|
|
5341
|
+
const seriesId = String(s["seriesId"] ?? "");
|
|
5342
|
+
const settlement = s["settlement"];
|
|
5343
|
+
const method = String(settlement?.["method"] ?? "");
|
|
5344
|
+
const isUpDown = method === "price_up_down";
|
|
5345
|
+
try {
|
|
5346
|
+
const r = await client.publicGet(
|
|
5347
|
+
"/api/v5/public/event-contract/markets",
|
|
5348
|
+
compactObject({ seriesId, state: "live" }),
|
|
5349
|
+
publicRateLimit("event_browse", 20)
|
|
5350
|
+
);
|
|
5351
|
+
const normalized = normalizeResponse(r);
|
|
5352
|
+
const markets = Array.isArray(normalized["data"]) ? normalized["data"] : [];
|
|
5353
|
+
const now = Date.now();
|
|
5354
|
+
const active = markets.filter((m) => isActiveMarket(m, isUpDown, now)).map(toContractSummary);
|
|
5355
|
+
if (active.length === 0) return null;
|
|
5356
|
+
return {
|
|
5357
|
+
seriesId,
|
|
5358
|
+
method: String(settlement?.["method"] ?? ""),
|
|
5359
|
+
underlying: String(settlement?.["underlying"] ?? ""),
|
|
5360
|
+
freq: String(s["freq"] ?? ""),
|
|
5361
|
+
contracts: active
|
|
5362
|
+
};
|
|
5363
|
+
} catch {
|
|
5364
|
+
return null;
|
|
5365
|
+
}
|
|
5366
|
+
}
|
|
5367
|
+
function resolveUnderlyingFromSeriesResp(seriesResp) {
|
|
5368
|
+
if (!seriesResp) return null;
|
|
5369
|
+
const sResp = seriesResp;
|
|
5370
|
+
const sData = Array.isArray(sResp["data"]) ? sResp["data"] : [];
|
|
5371
|
+
if (sData.length > 0) {
|
|
5372
|
+
const settlement = sData[0]["settlement"];
|
|
5373
|
+
return String(settlement?.["underlying"] ?? "") || null;
|
|
5374
|
+
}
|
|
5375
|
+
return null;
|
|
5376
|
+
}
|
|
5377
|
+
function translateAndSortMarkets(rawData, limit) {
|
|
5378
|
+
const sorted = [...rawData].sort((a, b) => {
|
|
5379
|
+
const tA = Number(a["expTime"] ?? 0);
|
|
5380
|
+
const tB = Number(b["expTime"] ?? 0);
|
|
5381
|
+
return tA - tB;
|
|
5382
|
+
});
|
|
5383
|
+
const sliced = limit && limit > 0 ? sorted.slice(0, limit) : sorted;
|
|
5384
|
+
return sliced.map((item) => {
|
|
5385
|
+
const converted = convertTimestamps(item);
|
|
5386
|
+
if (typeof converted["outcome"] === "string") {
|
|
5387
|
+
converted["outcome"] = OUTCOME_LABELS[converted["outcome"]] ?? converted["outcome"];
|
|
5388
|
+
}
|
|
5389
|
+
converted["displayTitle"] = formatDisplayTitle(String(item["instId"] ?? ""));
|
|
5390
|
+
return converted;
|
|
5391
|
+
});
|
|
5392
|
+
}
|
|
5393
|
+
function enrichFill(item) {
|
|
5394
|
+
const subType = String(item["subType"] ?? "");
|
|
5395
|
+
const isSettle = subType === "414" || subType === "415";
|
|
5396
|
+
const enriched = {
|
|
5397
|
+
...item,
|
|
5398
|
+
displayTitle: formatDisplayTitle(String(item["instId"] ?? "")),
|
|
5399
|
+
outcome: OUTCOME_LABELS[String(item["outcome"] ?? "")] ?? item["outcome"],
|
|
5400
|
+
type: isSettle ? "settlement" : "fill"
|
|
5401
|
+
};
|
|
5402
|
+
if (isSettle) {
|
|
5403
|
+
const fillPnl = parseFloat(String(item["fillPnl"] ?? "NaN"));
|
|
5404
|
+
const isWin = subType === "414";
|
|
5405
|
+
enriched["settlementResult"] = isWin ? "win" : "loss";
|
|
5406
|
+
enriched["pnl"] = isNaN(fillPnl) ? void 0 : fillPnl;
|
|
5407
|
+
}
|
|
5408
|
+
return enriched;
|
|
5409
|
+
}
|
|
5410
|
+
async function fetchIdxPx(client, underlying) {
|
|
5411
|
+
try {
|
|
5412
|
+
const r = await client.publicGet(
|
|
5413
|
+
"/api/v5/market/index-tickers",
|
|
5414
|
+
{ instId: underlying },
|
|
5415
|
+
publicRateLimit("fetchIdxPx", 20)
|
|
5416
|
+
);
|
|
5417
|
+
const data = r["data"];
|
|
5418
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
5419
|
+
return String(data[0]["idxPx"] ?? "") || null;
|
|
5420
|
+
}
|
|
5421
|
+
} catch {
|
|
5422
|
+
}
|
|
5423
|
+
return null;
|
|
5424
|
+
}
|
|
5425
|
+
function handlePlaceOrderError(base, rawArgs, endpoint) {
|
|
5426
|
+
if (!Array.isArray(base["data"])) return;
|
|
5427
|
+
const item = base["data"][0];
|
|
5428
|
+
const sCode = item && String(item["sCode"] ?? "");
|
|
5429
|
+
if (!sCode || sCode === "0") return;
|
|
5430
|
+
const sMsg = String(item["sMsg"] ?? "Order failed");
|
|
5431
|
+
if (sCode === "51001") {
|
|
5432
|
+
const instId = requireString(asRecord(rawArgs), "instId");
|
|
5433
|
+
const seriesId = extractSeriesId(instId);
|
|
5434
|
+
const expiryMs = inferExpiryMsFromInstId(instId);
|
|
5435
|
+
const isExpired = expiryMs !== null && expiryMs < Date.now();
|
|
5436
|
+
const reason = isExpired ? `The contract (${instId}) has expired.` : `The contract (${instId}) was not found \u2014 it may not exist or has not started yet.`;
|
|
5437
|
+
throw new OkxApiError(
|
|
5438
|
+
`${reason} Ask the user if they'd like to place the same order on the next session. If yes, call event_get_markets with seriesId=${seriesId} and state=live to find available contracts.`,
|
|
5439
|
+
{ code: sCode, endpoint }
|
|
5440
|
+
);
|
|
5441
|
+
}
|
|
5442
|
+
throw new OkxApiError(`[${sCode}] ${sMsg}`, { code: sCode, endpoint });
|
|
5443
|
+
}
|
|
5444
|
+
var OUTCOME_SCHEMA = {
|
|
5445
|
+
type: "string",
|
|
5446
|
+
enum: ["UP", "YES", "DOWN", "NO"],
|
|
5447
|
+
description: `Which outcome to trade.
|
|
5448
|
+
UP/DOWN direction contracts: UP (price rises during the period) or DOWN (price falls).
|
|
5449
|
+
YES/NO price-target or touch contracts: YES (condition met) or NO (condition not met).
|
|
5450
|
+
Check the series type from event_get_series to determine which applies.
|
|
5451
|
+
NOTE: px is the event contract price (0.01\u20130.99), NOT the underlying asset price. It reflects market-implied probability when actively trading.`
|
|
5452
|
+
};
|
|
5453
|
+
function registerEventContractTools() {
|
|
5454
|
+
return [
|
|
5455
|
+
// -----------------------------------------------------------------------
|
|
5456
|
+
// Read-only — browse (user-facing) + series / events / markets (internal)
|
|
5457
|
+
// -----------------------------------------------------------------------
|
|
5458
|
+
{
|
|
5459
|
+
name: "event_browse",
|
|
5460
|
+
module: "event",
|
|
5461
|
+
description: "Browse currently active (in-progress) event contracts. Call when user asks what event contracts are available to trade. Internally fetches series and live markets in parallel, returns only in-progress contracts (floorStrike set). If a live quote field px is present, it is the event contract price (0.01\u20130.99), not the underlying asset price; it reflects the market-implied probability when actively trading. Grouped by settlement type and underlying.",
|
|
5462
|
+
isWrite: false,
|
|
5463
|
+
inputSchema: {
|
|
5464
|
+
type: "object",
|
|
5465
|
+
properties: {
|
|
5466
|
+
underlying: {
|
|
5467
|
+
type: "string",
|
|
5468
|
+
description: "Filter by underlying asset, e.g. BTC-USD, ETH-USD. Omit for all."
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5471
|
+
},
|
|
5472
|
+
handler: async (rawArgs, context) => {
|
|
5473
|
+
const args = asRecord(rawArgs);
|
|
5474
|
+
const underlyingFilter = readString(args, "underlying");
|
|
5475
|
+
const seriesResp = await context.client.publicGet(
|
|
5476
|
+
"/api/v5/public/event-contract/series",
|
|
5477
|
+
compactObject({}),
|
|
5478
|
+
publicRateLimit("event_browse", 10)
|
|
5479
|
+
);
|
|
5480
|
+
const normalizedSeries = normalizeResponse(seriesResp);
|
|
5481
|
+
const allSeries = Array.isArray(normalizedSeries["data"]) ? normalizedSeries["data"] : [];
|
|
5482
|
+
const candidates = filterBrowseCandidates(allSeries, underlyingFilter);
|
|
5483
|
+
const marketResults = await Promise.all(
|
|
5484
|
+
candidates.map((s) => fetchActiveContractsForSeries(context.client, s))
|
|
5485
|
+
);
|
|
5486
|
+
const results = marketResults.filter(Boolean);
|
|
5487
|
+
return {
|
|
5488
|
+
data: results,
|
|
5489
|
+
total: results.reduce((n, r) => n + (r?.contracts?.length ?? 0), 0)
|
|
5490
|
+
};
|
|
5491
|
+
}
|
|
5492
|
+
},
|
|
5493
|
+
{
|
|
5494
|
+
name: "event_get_series",
|
|
5495
|
+
module: "event",
|
|
5496
|
+
description: "List event contract series. Returns all available series with settlement type and underlying. Use event_browse to see currently active contracts.",
|
|
5497
|
+
isWrite: false,
|
|
5498
|
+
inputSchema: {
|
|
5499
|
+
type: "object",
|
|
5500
|
+
properties: {
|
|
5501
|
+
seriesId: {
|
|
5502
|
+
type: "string",
|
|
5503
|
+
description: "Filter by series ID, e.g. BTC-ABOVE-DAILY. Omit for all."
|
|
5504
|
+
}
|
|
5505
|
+
}
|
|
5506
|
+
},
|
|
5507
|
+
handler: async (rawArgs, context) => {
|
|
5508
|
+
const args = asRecord(rawArgs);
|
|
5509
|
+
const response = await context.client.publicGet(
|
|
5510
|
+
"/api/v5/public/event-contract/series",
|
|
5511
|
+
compactObject({ seriesId: readString(args, "seriesId") }),
|
|
5512
|
+
publicRateLimit("event_get_series", 20)
|
|
5513
|
+
);
|
|
5514
|
+
return normalizeResponse(response);
|
|
5515
|
+
}
|
|
5516
|
+
},
|
|
5517
|
+
{
|
|
5518
|
+
name: "event_get_events",
|
|
5519
|
+
module: "event",
|
|
5520
|
+
description: "List expiry periods within a series. state: preopen|live|settling|expired. expTime is pre-formatted UTC+8.",
|
|
5521
|
+
isWrite: false,
|
|
5522
|
+
inputSchema: {
|
|
5523
|
+
type: "object",
|
|
5524
|
+
properties: {
|
|
5525
|
+
seriesId: {
|
|
5526
|
+
type: "string",
|
|
5527
|
+
description: "Series ID, e.g. BTC-ABOVE-DAILY (required)"
|
|
5528
|
+
},
|
|
5529
|
+
eventId: {
|
|
5530
|
+
type: "string",
|
|
5531
|
+
description: "Filter by event ID, e.g. BTC-ABOVE-DAILY-260224-1600"
|
|
5532
|
+
},
|
|
5533
|
+
state: {
|
|
5534
|
+
type: "string",
|
|
5535
|
+
enum: ["preopen", "live", "settling", "expired"],
|
|
5536
|
+
description: "preopen=markets not yet trading; live=active; settling=awaiting settlement; expired=done"
|
|
5537
|
+
},
|
|
5538
|
+
limit: {
|
|
5539
|
+
type: "number",
|
|
5540
|
+
description: "Max results (default 100, max 100)"
|
|
5541
|
+
},
|
|
5542
|
+
before: {
|
|
5543
|
+
type: "string",
|
|
5544
|
+
description: "Pagination: return records newer than this expTime"
|
|
5545
|
+
},
|
|
5546
|
+
after: {
|
|
5547
|
+
type: "string",
|
|
5548
|
+
description: "Pagination: return records older than this expTime"
|
|
5549
|
+
}
|
|
5550
|
+
},
|
|
5551
|
+
required: ["seriesId"]
|
|
5552
|
+
},
|
|
5553
|
+
handler: async (rawArgs, context) => {
|
|
5554
|
+
const args = asRecord(rawArgs);
|
|
5555
|
+
const response = await context.client.publicGet(
|
|
5556
|
+
"/api/v5/public/event-contract/events",
|
|
5557
|
+
compactObject({
|
|
5558
|
+
seriesId: requireString(args, "seriesId"),
|
|
5559
|
+
eventId: readString(args, "eventId"),
|
|
5560
|
+
state: readString(args, "state"),
|
|
5561
|
+
limit: readNumber(args, "limit"),
|
|
5562
|
+
before: readString(args, "before"),
|
|
5563
|
+
after: readString(args, "after")
|
|
5564
|
+
}),
|
|
5565
|
+
publicRateLimit("event_get_events", 20)
|
|
5566
|
+
);
|
|
5567
|
+
const base = normalizeResponse(response);
|
|
5568
|
+
const data = Array.isArray(base["data"]) ? base["data"].map(convertTimestamps) : base["data"];
|
|
5569
|
+
return { ...base, data };
|
|
5570
|
+
}
|
|
5571
|
+
},
|
|
5572
|
+
{
|
|
5573
|
+
name: "event_get_markets",
|
|
5574
|
+
module: "event",
|
|
5575
|
+
description: "List tradeable contracts within a series. state=live for active contracts, state=expired for settlement results. floorStrike=strike price; px (when present) is the event contract price (0.01\u20130.99), not the underlying asset price \u2014 reflects the market-implied probability when actively trading; outcome pre-translated (pending/YES/NO/UP/DOWN); timestamps UTC+8.",
|
|
5576
|
+
isWrite: false,
|
|
5577
|
+
inputSchema: {
|
|
5578
|
+
type: "object",
|
|
5579
|
+
properties: {
|
|
5580
|
+
seriesId: {
|
|
5581
|
+
type: "string",
|
|
5582
|
+
description: "Series ID, e.g. BTC-ABOVE-DAILY (required)"
|
|
5583
|
+
},
|
|
5584
|
+
eventId: {
|
|
5585
|
+
type: "string",
|
|
5586
|
+
description: "Filter by event ID, e.g. BTC-ABOVE-DAILY-260224-1600"
|
|
5587
|
+
},
|
|
5588
|
+
instId: {
|
|
5589
|
+
type: "string",
|
|
5590
|
+
description: "Filter by instrument ID"
|
|
5591
|
+
},
|
|
5592
|
+
state: {
|
|
5593
|
+
type: "string",
|
|
5594
|
+
enum: ["preopen", "live", "settling", "expired"],
|
|
5595
|
+
description: "preopen=not yet trading; live=active; settling=awaiting settlement; expired=settled"
|
|
5596
|
+
},
|
|
5597
|
+
limit: {
|
|
5598
|
+
type: "number",
|
|
5599
|
+
description: "Max results (default 100, max 100)"
|
|
5600
|
+
},
|
|
5601
|
+
before: {
|
|
5602
|
+
type: "string",
|
|
5603
|
+
description: "Pagination: return records newer than this expTime"
|
|
5604
|
+
},
|
|
5605
|
+
after: {
|
|
5606
|
+
type: "string",
|
|
5607
|
+
description: "Pagination: return records older than this expTime"
|
|
5608
|
+
}
|
|
5609
|
+
},
|
|
5610
|
+
required: ["seriesId"]
|
|
5611
|
+
},
|
|
5612
|
+
handler: async (rawArgs, context) => {
|
|
5613
|
+
const args = asRecord(rawArgs);
|
|
5614
|
+
const seriesId = requireString(args, "seriesId");
|
|
5615
|
+
const knownUnderlying = extractUnderlying(seriesId);
|
|
5616
|
+
const [marketsResp, seriesResp, idxPxFromKnown] = await Promise.all([
|
|
5617
|
+
context.client.publicGet(
|
|
5618
|
+
"/api/v5/public/event-contract/markets",
|
|
5619
|
+
compactObject({
|
|
5620
|
+
seriesId,
|
|
5621
|
+
eventId: readString(args, "eventId"),
|
|
5622
|
+
instId: readString(args, "instId"),
|
|
5623
|
+
state: readString(args, "state"),
|
|
5624
|
+
before: readString(args, "before"),
|
|
5625
|
+
after: readString(args, "after")
|
|
5626
|
+
}),
|
|
5627
|
+
publicRateLimit("event_get_markets", 20)
|
|
5628
|
+
),
|
|
5629
|
+
knownUnderlying ? Promise.resolve(null) : context.client.publicGet(
|
|
5630
|
+
"/api/v5/public/event-contract/series",
|
|
5631
|
+
compactObject({ seriesId }),
|
|
5632
|
+
publicRateLimit("event_get_series", 20)
|
|
5633
|
+
),
|
|
5634
|
+
knownUnderlying ? fetchIdxPx(context.client, knownUnderlying + "-USDT") : Promise.resolve(null)
|
|
5635
|
+
]);
|
|
5636
|
+
let underlying = knownUnderlying ? knownUnderlying + "-USDT" : null;
|
|
5637
|
+
if (!underlying) {
|
|
5638
|
+
underlying = resolveUnderlyingFromSeriesResp(seriesResp);
|
|
5639
|
+
}
|
|
5640
|
+
const idxPx = idxPxFromKnown ?? (underlying && !knownUnderlying ? await fetchIdxPx(context.client, underlying) : null);
|
|
5641
|
+
const base = normalizeResponse(marketsResp);
|
|
5642
|
+
const limit = readNumber(args, "limit");
|
|
5643
|
+
const rawData = Array.isArray(base["data"]) ? base["data"] : [];
|
|
5644
|
+
const translated = translateAndSortMarkets(rawData, limit);
|
|
5645
|
+
return {
|
|
5646
|
+
...base,
|
|
5647
|
+
data: translated,
|
|
5648
|
+
currentIdxPx: idxPx,
|
|
5649
|
+
underlying
|
|
5650
|
+
};
|
|
5651
|
+
}
|
|
5652
|
+
},
|
|
5653
|
+
{
|
|
5654
|
+
name: "event_get_orders",
|
|
5655
|
+
module: "event",
|
|
5656
|
+
description: "Query event contract orders. state=live for open orders; omit for history. outcome pre-translated (YES/NO/UP/DOWN).",
|
|
5657
|
+
isWrite: false,
|
|
5658
|
+
inputSchema: {
|
|
5659
|
+
type: "object",
|
|
5660
|
+
properties: {
|
|
5661
|
+
instId: {
|
|
5662
|
+
type: "string",
|
|
5663
|
+
description: "Event contract instrument ID"
|
|
5664
|
+
},
|
|
5665
|
+
state: {
|
|
5666
|
+
type: "string",
|
|
5667
|
+
description: "live=pending orders; omit for history"
|
|
5668
|
+
},
|
|
5669
|
+
limit: {
|
|
5670
|
+
type: "number",
|
|
5671
|
+
description: "Max results (default 20)"
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
},
|
|
5675
|
+
handler: async (rawArgs, context) => {
|
|
5676
|
+
const args = asRecord(rawArgs);
|
|
5677
|
+
const state = readString(args, "state");
|
|
5678
|
+
const isPending = state === "live";
|
|
5679
|
+
const endpoint = isPending ? "/api/v5/trade/orders-pending" : "/api/v5/trade/orders-history";
|
|
5680
|
+
const response = await context.client.privateGet(
|
|
5681
|
+
endpoint,
|
|
5682
|
+
compactObject({
|
|
5683
|
+
instType: "EVENTS",
|
|
5684
|
+
instId: readString(args, "instId"),
|
|
5685
|
+
limit: readNumber(args, "limit")
|
|
5686
|
+
}),
|
|
5687
|
+
privateRateLimit("event_get_orders", 20)
|
|
5688
|
+
);
|
|
5689
|
+
const base = normalizeResponse(response);
|
|
5690
|
+
const data = Array.isArray(base["data"]) ? base["data"].map((item) => ({
|
|
5691
|
+
...item,
|
|
5692
|
+
displayTitle: formatDisplayTitle(String(item["instId"] ?? "")),
|
|
5693
|
+
outcome: OUTCOME_LABELS[String(item["outcome"] ?? "")] ?? item["outcome"],
|
|
5694
|
+
state: String(item["state"] ?? ""),
|
|
5695
|
+
stateLabel: mapOrderState(String(item["state"] ?? ""))
|
|
5696
|
+
})) : base["data"];
|
|
5697
|
+
return { ...base, data };
|
|
5698
|
+
}
|
|
5699
|
+
},
|
|
5700
|
+
{
|
|
5701
|
+
name: "event_get_fills",
|
|
5702
|
+
module: "event",
|
|
5703
|
+
description: "Get event contract fill history. outcome pre-translated (YES/NO/UP/DOWN). Each record includes a 'type' field: 'fill' (subType 410, opening trade) or 'settlement' (subType 414 win / subType 415 loss, contract expiry payout). Settlement records include 'settlementResult' (win/loss) and 'pnl' fields \u2014 no separate market lookup needed to determine outcome.",
|
|
5704
|
+
isWrite: false,
|
|
5705
|
+
inputSchema: {
|
|
5706
|
+
type: "object",
|
|
5707
|
+
properties: {
|
|
5708
|
+
instId: {
|
|
5709
|
+
type: "string",
|
|
5710
|
+
description: "Event contract instrument ID"
|
|
5711
|
+
},
|
|
5712
|
+
limit: {
|
|
5713
|
+
type: "number",
|
|
5714
|
+
description: "Max results (default 20)"
|
|
5715
|
+
}
|
|
5716
|
+
}
|
|
5717
|
+
},
|
|
5718
|
+
handler: async (rawArgs, context) => {
|
|
5719
|
+
const args = asRecord(rawArgs);
|
|
5720
|
+
const response = await context.client.privateGet(
|
|
5721
|
+
"/api/v5/trade/fills",
|
|
5722
|
+
compactObject({
|
|
5723
|
+
instType: "EVENTS",
|
|
5724
|
+
instId: readString(args, "instId"),
|
|
5725
|
+
limit: readNumber(args, "limit")
|
|
5726
|
+
}),
|
|
5727
|
+
privateRateLimit("event_get_fills", 20)
|
|
5728
|
+
);
|
|
5729
|
+
const base = normalizeResponse(response);
|
|
5730
|
+
const data = Array.isArray(base["data"]) ? base["data"].map(enrichFill) : base["data"];
|
|
5731
|
+
return { ...base, data };
|
|
5732
|
+
}
|
|
5733
|
+
},
|
|
5734
|
+
// -----------------------------------------------------------------------
|
|
5735
|
+
// Private — write
|
|
5736
|
+
// -----------------------------------------------------------------------
|
|
5737
|
+
{
|
|
5738
|
+
name: "event_place_order",
|
|
5739
|
+
module: "event",
|
|
5740
|
+
description: `Place an event contract order. [CAUTION] Places a real order.
|
|
5741
|
+
- outcome: UP/YES (bet price goes up/condition met) or DOWN/NO (bet price goes down/condition not met)
|
|
5742
|
+
- For limit orders: px is the event contract price (0.01\u20130.99), NOT the underlying asset price. It reflects market-implied probability when actively trading
|
|
5743
|
+
- tdMode is always isolated; speedBump is auto-set per exchange requirement \u2014 do not pass either`,
|
|
5744
|
+
isWrite: true,
|
|
5745
|
+
inputSchema: {
|
|
5746
|
+
type: "object",
|
|
5747
|
+
properties: {
|
|
5748
|
+
instId: {
|
|
5749
|
+
type: "string",
|
|
5750
|
+
description: "Event contract instrument ID, e.g. BTC-ABOVE-DAILY-260224-1600-120000"
|
|
5751
|
+
},
|
|
5752
|
+
side: {
|
|
5753
|
+
type: "string",
|
|
5754
|
+
enum: ["buy", "sell"],
|
|
5755
|
+
description: "buy=open position, sell=close position"
|
|
5756
|
+
},
|
|
5757
|
+
outcome: OUTCOME_SCHEMA,
|
|
5758
|
+
ordType: {
|
|
5759
|
+
type: "string",
|
|
5760
|
+
enum: ["market", "limit", "post_only"],
|
|
5761
|
+
description: "Order type (default market)"
|
|
5762
|
+
},
|
|
5763
|
+
sz: {
|
|
5764
|
+
type: "string",
|
|
5765
|
+
description: "For limit/post_only: number of contracts. For market: quote currency amount (server converts to contracts using best available price; actual fill count may differ)."
|
|
5766
|
+
},
|
|
5767
|
+
px: {
|
|
5768
|
+
type: "string",
|
|
5769
|
+
description: "Event contract price (0.01\u20130.99). Required when ordType=limit. Do NOT use for market orders."
|
|
5770
|
+
}
|
|
5771
|
+
},
|
|
5772
|
+
required: ["instId", "side", "outcome", "sz"]
|
|
5773
|
+
},
|
|
5774
|
+
handler: async (rawArgs, context) => {
|
|
5775
|
+
const args = asRecord(rawArgs);
|
|
5776
|
+
const ordType = readString(args, "ordType") ?? "market";
|
|
5777
|
+
const speedBump = ordType !== "post_only" ? "1" : void 0;
|
|
5778
|
+
const instId = requireString(args, "instId");
|
|
5779
|
+
const response = await context.client.privatePost(
|
|
5780
|
+
"/api/v5/trade/order",
|
|
5781
|
+
compactObject({
|
|
5782
|
+
instId,
|
|
5783
|
+
tdMode: "isolated",
|
|
5784
|
+
side: requireString(args, "side"),
|
|
5785
|
+
outcome: resolveOutcome(requireString(args, "outcome")),
|
|
5786
|
+
ordType,
|
|
5787
|
+
sz: requireString(args, "sz"),
|
|
5788
|
+
px: readString(args, "px"),
|
|
5789
|
+
speedBump,
|
|
5790
|
+
tag: context.config.sourceTag
|
|
5791
|
+
}),
|
|
5792
|
+
privateRateLimit("event_place_order", 60)
|
|
5793
|
+
);
|
|
5794
|
+
const base = normalizeResponse(response);
|
|
5795
|
+
handlePlaceOrderError(base, asRecord(rawArgs), response.endpoint);
|
|
5796
|
+
const data = Array.isArray(base["data"]) ? base["data"].map(({ tag: _t, ...rest }) => rest) : base["data"];
|
|
5797
|
+
const placeSeriesId = extractSeriesId(instId);
|
|
5798
|
+
const placeUnderlying = extractUnderlying(placeSeriesId);
|
|
5799
|
+
const placeCcy = extractQuoteCcy(placeUnderlying ? placeUnderlying + "-USDT" : null);
|
|
5800
|
+
const balResult = await fetchAvailableBalance(context.client, placeCcy);
|
|
5801
|
+
const result = { ...base, data };
|
|
5802
|
+
if (balResult.balance) {
|
|
5803
|
+
result["availableBalance"] = balResult.balance;
|
|
5804
|
+
result["availableBalanceCcy"] = balResult.ccy;
|
|
5805
|
+
}
|
|
5806
|
+
if (ordType === "market") {
|
|
5807
|
+
result["orderNote"] = "Market order: sz is a quote currency amount. The exchange converts it to contracts based on best available price.";
|
|
5808
|
+
}
|
|
5809
|
+
return result;
|
|
5810
|
+
}
|
|
5811
|
+
},
|
|
5812
|
+
{
|
|
5813
|
+
name: "event_amend_order",
|
|
5814
|
+
module: "event",
|
|
5815
|
+
description: "Amend a pending event contract order (change price or size). [CAUTION] Modifies a real order. Only limit/post_only orders can be amended.",
|
|
5816
|
+
isWrite: true,
|
|
5817
|
+
inputSchema: {
|
|
5818
|
+
type: "object",
|
|
5819
|
+
properties: {
|
|
5820
|
+
instId: { type: "string", description: "Event contract instrument ID" },
|
|
5821
|
+
ordId: { type: "string", description: "Order ID to amend" },
|
|
5822
|
+
newPx: { type: "string", description: "New event contract price (0.01\u20130.99). Omit to keep current." },
|
|
5823
|
+
newSz: { type: "string", description: "New size in contracts (omit to keep current)" }
|
|
5824
|
+
},
|
|
5825
|
+
required: ["instId", "ordId"]
|
|
5826
|
+
},
|
|
5827
|
+
handler: async (rawArgs, context) => {
|
|
5828
|
+
const args = asRecord(rawArgs);
|
|
5829
|
+
const response = await context.client.privatePost(
|
|
5830
|
+
"/api/v5/trade/amend-order",
|
|
5831
|
+
compactObject({
|
|
5832
|
+
instId: requireString(args, "instId"),
|
|
5833
|
+
ordId: requireString(args, "ordId"),
|
|
5834
|
+
newPx: readString(args, "newPx"),
|
|
5835
|
+
newSz: readString(args, "newSz"),
|
|
5836
|
+
speedBump: "1"
|
|
5837
|
+
}),
|
|
5838
|
+
privateRateLimit("event_amend_order", 60)
|
|
5839
|
+
);
|
|
5840
|
+
return normalizeWrite3(response);
|
|
5841
|
+
}
|
|
5842
|
+
},
|
|
5843
|
+
{
|
|
5844
|
+
name: "event_cancel_order",
|
|
5845
|
+
module: "event",
|
|
5846
|
+
description: "Cancel a pending event contract order. [CAUTION] Cancels a real order. instId must be the full event contract instrument ID (e.g. BTC-ABOVE-DAILY-260224-1600-69700), NOT a spot trading pair.",
|
|
5847
|
+
isWrite: true,
|
|
5848
|
+
inputSchema: {
|
|
5849
|
+
type: "object",
|
|
5850
|
+
properties: {
|
|
5851
|
+
instId: {
|
|
5852
|
+
type: "string",
|
|
5853
|
+
description: "Event contract instrument ID"
|
|
5854
|
+
},
|
|
5855
|
+
ordId: {
|
|
5856
|
+
type: "string",
|
|
5857
|
+
description: "Order ID to cancel"
|
|
5858
|
+
}
|
|
5859
|
+
},
|
|
5860
|
+
required: ["instId", "ordId"]
|
|
5861
|
+
},
|
|
5862
|
+
handler: async (rawArgs, context) => {
|
|
5863
|
+
const args = asRecord(rawArgs);
|
|
5864
|
+
const instId = requireString(args, "instId");
|
|
5865
|
+
const response = await context.client.privatePost(
|
|
5866
|
+
"/api/v5/trade/cancel-order",
|
|
5867
|
+
{ instId, ordId: requireString(args, "ordId") },
|
|
5868
|
+
privateRateLimit("event_cancel_order", 60)
|
|
5869
|
+
);
|
|
5870
|
+
if (Array.isArray(response.data) && response.data.length > 0) {
|
|
5871
|
+
const item = response.data[0];
|
|
5872
|
+
const sCode = item && String(item["sCode"] ?? "");
|
|
5873
|
+
if (sCode === "51001") {
|
|
5874
|
+
const expiryMs = inferExpiryMsFromInstId(instId);
|
|
5875
|
+
const isExpired = expiryMs !== null && expiryMs < Date.now();
|
|
5876
|
+
const reason = isExpired ? `The contract (${instId}) has already expired \u2014 the order was auto-cancelled at settlement. Check event_get_fills to confirm the outcome.` : `Instrument (${instId}) not found. Verify the instId with event_get_markets before retrying.`;
|
|
5877
|
+
throw new OkxApiError(reason, { code: sCode, endpoint: response.endpoint });
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
return normalizeWrite3(response);
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
];
|
|
5884
|
+
}
|
|
5121
5885
|
function buildContractTradeTools(cfg) {
|
|
5122
5886
|
const { prefix, module, label, instTypes, instIdExample } = cfg;
|
|
5123
5887
|
const [defaultType, otherType] = instTypes;
|
|
@@ -7756,6 +8520,7 @@ function allToolSpecs() {
|
|
|
7756
8520
|
...registerOptionAlgoTools(),
|
|
7757
8521
|
...registerAlgoTradeTools(),
|
|
7758
8522
|
...registerAccountTools(),
|
|
8523
|
+
...registerEventContractTools(),
|
|
7759
8524
|
...registerBotTools(),
|
|
7760
8525
|
...registerAllEarnTools(),
|
|
7761
8526
|
...registerAuditTools(),
|
|
@@ -8848,7 +9613,7 @@ async function cmdDiagnoseMcp(options = {}) {
|
|
|
8848
9613
|
|
|
8849
9614
|
// src/commands/diagnose.ts
|
|
8850
9615
|
var CLI_VERSION = readCliVersion();
|
|
8851
|
-
var GIT_HASH = true ? "
|
|
9616
|
+
var GIT_HASH = true ? "244dce7" : "dev";
|
|
8852
9617
|
function maskKey2(key) {
|
|
8853
9618
|
if (!key) return "(not set)";
|
|
8854
9619
|
if (key.length <= 8) return "****";
|
|
@@ -9837,6 +10602,47 @@ var HELP_TREE = {
|
|
|
9837
10602
|
}
|
|
9838
10603
|
}
|
|
9839
10604
|
},
|
|
10605
|
+
event: {
|
|
10606
|
+
description: "Event contract trading \u2014 binary outcome prediction markets (Yes/No, Up/Down)",
|
|
10607
|
+
commands: {
|
|
10608
|
+
browse: {
|
|
10609
|
+
usage: "okx event browse [--underlying <asset>]",
|
|
10610
|
+
description: "Browse all active (in-progress) event contracts; px is event contract price (0.01\u20130.99), not the underlying asset price \u2014 reflects market-implied probability when actively trading"
|
|
10611
|
+
},
|
|
10612
|
+
series: {
|
|
10613
|
+
usage: "okx event series [--seriesId <id>]",
|
|
10614
|
+
description: "List event contract series grouped by type (UP/DOWN direction or YES/NO price-target)"
|
|
10615
|
+
},
|
|
10616
|
+
events: {
|
|
10617
|
+
usage: "okx event events <seriesId> [--state <preopen|live|settling|expired>] [--limit <n>]",
|
|
10618
|
+
description: "List events in a series (each event = one expiry)"
|
|
10619
|
+
},
|
|
10620
|
+
markets: {
|
|
10621
|
+
usage: "okx event markets <seriesId> [--eventId <id>] [--state <preopen|live|settling|expired>] [--limit <n>]",
|
|
10622
|
+
description: "List markets; px is event contract price (0.01\u20130.99), not the underlying asset price \u2014 reflects market-implied probability when actively trading; expired includes translated outcome and settleValue"
|
|
10623
|
+
},
|
|
10624
|
+
place: {
|
|
10625
|
+
usage: "okx event place <instId> <side> <outcome> <sz> [--px <price>] [--ordType <market|limit|post_only>]",
|
|
10626
|
+
description: "[CAUTION] Place an event contract order. outcome: UP/YES or DOWN/NO. limit: sz=contracts, px required. market: sz=quote currency amount, no px."
|
|
10627
|
+
},
|
|
10628
|
+
amend: {
|
|
10629
|
+
usage: "okx event amend <instId> <ordId> [--px <price>] [--sz <n>]",
|
|
10630
|
+
description: "[CAUTION] Amend a pending limit order (change price or size)"
|
|
10631
|
+
},
|
|
10632
|
+
cancel: {
|
|
10633
|
+
usage: "okx event cancel <instId> <ordId>",
|
|
10634
|
+
description: "Cancel a pending event contract order"
|
|
10635
|
+
},
|
|
10636
|
+
orders: {
|
|
10637
|
+
usage: "okx event orders [--instId <id>] [--state live] [--limit <n>]",
|
|
10638
|
+
description: "List event orders (state=live for pending; omit for history)"
|
|
10639
|
+
},
|
|
10640
|
+
fills: {
|
|
10641
|
+
usage: "okx event fills [--instId <id>] [--limit <n>]",
|
|
10642
|
+
description: "Get event contract fill history"
|
|
10643
|
+
}
|
|
10644
|
+
}
|
|
10645
|
+
},
|
|
9840
10646
|
config: {
|
|
9841
10647
|
description: "Manage CLI configuration profiles",
|
|
9842
10648
|
commands: {
|
|
@@ -10195,6 +11001,11 @@ var CLI_OPTIONS = {
|
|
|
10195
11001
|
dir: { type: "string" },
|
|
10196
11002
|
page: { type: "string" },
|
|
10197
11003
|
format: { type: "string" },
|
|
11004
|
+
// event contract
|
|
11005
|
+
underlying: { type: "string" },
|
|
11006
|
+
seriesId: { type: "string" },
|
|
11007
|
+
eventId: { type: "string" },
|
|
11008
|
+
outcome: { type: "string" },
|
|
10198
11009
|
// diagnostics — cli/mcp/all/output are diagnose-specific; verbose is shared
|
|
10199
11010
|
verbose: { type: "boolean", default: false },
|
|
10200
11011
|
mcp: { type: "boolean", default: false },
|
|
@@ -13213,10 +14024,462 @@ function printSkillInstallResult(meta, json) {
|
|
|
13213
14024
|
}
|
|
13214
14025
|
}
|
|
13215
14026
|
|
|
14027
|
+
// src/commands/event-contract.ts
|
|
14028
|
+
function getData8(result) {
|
|
14029
|
+
return result.data;
|
|
14030
|
+
}
|
|
14031
|
+
function fmtMethod(raw) {
|
|
14032
|
+
const map = {
|
|
14033
|
+
// API returns values in various cases; normalise to lowercase for lookup
|
|
14034
|
+
price_above: "Price Above",
|
|
14035
|
+
price_up_down: "Up/Down",
|
|
14036
|
+
price_once_touch: "One Touch",
|
|
14037
|
+
// Uppercase variants (in case API returns them)
|
|
14038
|
+
PRICE_ABOVE: "Price Above",
|
|
14039
|
+
PRICE_UP_DOWN: "Up/Down",
|
|
14040
|
+
PRICE_ONCE_TOUCH: "One Touch"
|
|
14041
|
+
};
|
|
14042
|
+
return map[String(raw)] ?? String(raw ?? "");
|
|
14043
|
+
}
|
|
14044
|
+
function fmtFreq(raw) {
|
|
14045
|
+
const map = {
|
|
14046
|
+
fifteen_min: "15min",
|
|
14047
|
+
daily: "Daily",
|
|
14048
|
+
hourly: "1h",
|
|
14049
|
+
weekly: "Weekly",
|
|
14050
|
+
FIFTEEN_MIN: "15min",
|
|
14051
|
+
DAILY: "Daily",
|
|
14052
|
+
HOURLY: "1h",
|
|
14053
|
+
WEEKLY: "Weekly"
|
|
14054
|
+
};
|
|
14055
|
+
return map[String(raw)] ?? String(raw ?? "");
|
|
14056
|
+
}
|
|
14057
|
+
function fmtOutcome(raw) {
|
|
14058
|
+
const s = String(raw ?? "").toLowerCase();
|
|
14059
|
+
if (s === "" || s === "pending") return "";
|
|
14060
|
+
return String(raw ?? "");
|
|
14061
|
+
}
|
|
14062
|
+
function fmtMarketOutcome(instId, outcome) {
|
|
14063
|
+
const formatted = fmtOutcome(outcome);
|
|
14064
|
+
if (formatted === "") return "";
|
|
14065
|
+
return fmtOrderOutcome(instId, formatted);
|
|
14066
|
+
}
|
|
14067
|
+
function fmtOrderOutcome(instId, outcome) {
|
|
14068
|
+
const id = String(instId ?? "").toUpperCase();
|
|
14069
|
+
const isUpDown = id.includes("UPDOWN") || id.includes("UP-DOWN");
|
|
14070
|
+
const normalized = String(outcome ?? "").toLowerCase();
|
|
14071
|
+
if (outcome === "1" || outcome === 1 || normalized === "yes") return isUpDown ? "UP" : "YES";
|
|
14072
|
+
if (outcome === "2" || outcome === 2 || normalized === "no") return isUpDown ? "DOWN" : "NO";
|
|
14073
|
+
return String(outcome ?? "");
|
|
14074
|
+
}
|
|
14075
|
+
function fmtPeriodFromInstId(instId) {
|
|
14076
|
+
const parts = instId.split("-");
|
|
14077
|
+
const upper = instId.toUpperCase();
|
|
14078
|
+
const dateIdx = findDateIdx(parts);
|
|
14079
|
+
if (dateIdx < 0) return instId;
|
|
14080
|
+
const dp = parts[dateIdx];
|
|
14081
|
+
const date = `20${dp.slice(0, 2)}-${dp.slice(2, 4)}-${dp.slice(4, 6)}`;
|
|
14082
|
+
const fmt = (t) => t.length === 4 ? `${t.slice(0, 2)}:${t.slice(2)}` : t;
|
|
14083
|
+
const t1 = parts[dateIdx + 1] ?? "";
|
|
14084
|
+
const t2 = parts[dateIdx + 2] ?? "";
|
|
14085
|
+
const isUpDown = upper.includes("UPDOWN");
|
|
14086
|
+
if (isUpDown && /^\d{4}$/.test(t1) && /^\d{4}$/.test(t2)) {
|
|
14087
|
+
return `${date} ${fmt(t1)} ~ ${fmt(t2)} UTC+8`;
|
|
14088
|
+
}
|
|
14089
|
+
if (/^\d{4}$/.test(t1)) {
|
|
14090
|
+
return `${date} ${fmt(t1)} UTC+8`;
|
|
14091
|
+
}
|
|
14092
|
+
return instId;
|
|
14093
|
+
}
|
|
14094
|
+
function fmtTs(raw) {
|
|
14095
|
+
if (!raw) return "";
|
|
14096
|
+
const n = Number(raw);
|
|
14097
|
+
if (Number.isNaN(n)) return String(raw);
|
|
14098
|
+
return new Date(n).toISOString().replace("T", " ").replace(".000Z", " UTC");
|
|
14099
|
+
}
|
|
14100
|
+
function fmtProbability(raw) {
|
|
14101
|
+
if (raw === "" || raw == null) return "";
|
|
14102
|
+
const n = Number(raw);
|
|
14103
|
+
if (Number.isNaN(n)) return String(raw);
|
|
14104
|
+
return `${(n * 100).toFixed(1)}%`;
|
|
14105
|
+
}
|
|
14106
|
+
async function cmdEventBrowse(run, opts) {
|
|
14107
|
+
const result = await run("event_browse", { underlying: opts.underlying });
|
|
14108
|
+
const data = getData8(result);
|
|
14109
|
+
if (opts.json) return printJson(data);
|
|
14110
|
+
if (!data || data.length === 0) {
|
|
14111
|
+
process.stdout.write("No active event contracts found.\n");
|
|
14112
|
+
return;
|
|
14113
|
+
}
|
|
14114
|
+
for (const group of data) {
|
|
14115
|
+
const contracts = group["contracts"] ?? [];
|
|
14116
|
+
const methodLabel = fmtMethod(group["method"]);
|
|
14117
|
+
const freqLabel = fmtFreq(group["freq"]);
|
|
14118
|
+
process.stdout.write(`
|
|
14119
|
+
[${methodLabel}] ${group["underlying"]} (${freqLabel})
|
|
14120
|
+
`);
|
|
14121
|
+
printTable(
|
|
14122
|
+
contracts.map((c) => ({
|
|
14123
|
+
"Contract": formatDisplayTitle(String(c["instId"] ?? "")),
|
|
14124
|
+
"Expiry": c["expTime"] ?? "",
|
|
14125
|
+
"Target Price": c["floorStrike"] ? String(c["floorStrike"]) : "\u2014",
|
|
14126
|
+
"Probability": fmtProbability(c["px"]),
|
|
14127
|
+
"Outcome": fmtOutcome(c["outcome"]) || "\u2014",
|
|
14128
|
+
"instId": c["instId"]
|
|
14129
|
+
}))
|
|
14130
|
+
);
|
|
14131
|
+
}
|
|
14132
|
+
const total = data.reduce((n, g) => n + (g["contracts"]?.length ?? 0), 0);
|
|
14133
|
+
process.stdout.write(`
|
|
14134
|
+
${total} active contract(s) across ${data.length} series.
|
|
14135
|
+
`);
|
|
14136
|
+
}
|
|
14137
|
+
var FEATURED_SERIES = /* @__PURE__ */ new Set([
|
|
14138
|
+
"BTC-UPDOWN-15MIN",
|
|
14139
|
+
"ETH-UPDOWN-15MIN",
|
|
14140
|
+
"TRX-UPDOWN-15MIN",
|
|
14141
|
+
"BTC-ABOVE-DAILY",
|
|
14142
|
+
"ETH-ABOVE-DAILY"
|
|
14143
|
+
]);
|
|
14144
|
+
var KNOWN_PREFIXES = /^(BTC|ETH|TRX|SOL|EOS|BNB|XRP|ADA|DOGE|IOTA|SUSHI|KISHU|BTG|XTZ)-/i;
|
|
14145
|
+
function getSeriesMethod(s) {
|
|
14146
|
+
const settlement = s["settlement"];
|
|
14147
|
+
const method = settlement?.["method"] ?? s["method"];
|
|
14148
|
+
return String(method ?? "").toLowerCase();
|
|
14149
|
+
}
|
|
14150
|
+
function isUpDownSeries(s) {
|
|
14151
|
+
const m = getSeriesMethod(s);
|
|
14152
|
+
return m.includes("up_down") || m.includes("updown");
|
|
14153
|
+
}
|
|
14154
|
+
function isFeatured(s) {
|
|
14155
|
+
return FEATURED_SERIES.has(String(s["seriesId"] ?? ""));
|
|
14156
|
+
}
|
|
14157
|
+
function isKnownSeries(s) {
|
|
14158
|
+
return KNOWN_PREFIXES.test(String(s["seriesId"] ?? ""));
|
|
14159
|
+
}
|
|
14160
|
+
function classifySeries(all) {
|
|
14161
|
+
const featured = [];
|
|
14162
|
+
const standard = [];
|
|
14163
|
+
const testSeries = [];
|
|
14164
|
+
for (const s of all) {
|
|
14165
|
+
if (isFeatured(s)) featured.push(s);
|
|
14166
|
+
else if (isKnownSeries(s)) standard.push(s);
|
|
14167
|
+
else testSeries.push(s);
|
|
14168
|
+
}
|
|
14169
|
+
return { featured, standard, testSeries };
|
|
14170
|
+
}
|
|
14171
|
+
async function cmdEventSeries(run, opts) {
|
|
14172
|
+
const result = await run("event_get_series", { seriesId: opts.seriesId });
|
|
14173
|
+
const data = getData8(result);
|
|
14174
|
+
if (opts.json) return printJson(data);
|
|
14175
|
+
const toRow = (s, featured2) => {
|
|
14176
|
+
const settlement = s["settlement"];
|
|
14177
|
+
const method = settlement?.["method"] ?? s["method"];
|
|
14178
|
+
const underlying = settlement?.["underlying"] ?? s["underlying"] ?? s["baseCcy"];
|
|
14179
|
+
return {
|
|
14180
|
+
Series: `${featured2 ? "\u2B50 " : ""}${String(s["seriesId"] ?? "")}`,
|
|
14181
|
+
Type: fmtMethod(method),
|
|
14182
|
+
Freq: fmtFreq(s["freq"]),
|
|
14183
|
+
Underlying: String(underlying ?? ""),
|
|
14184
|
+
State: String(s["state"] ?? "")
|
|
14185
|
+
};
|
|
14186
|
+
};
|
|
14187
|
+
const all = data ?? [];
|
|
14188
|
+
const { featured, standard, testSeries } = classifySeries(all);
|
|
14189
|
+
const mainSeries = [...featured, ...standard];
|
|
14190
|
+
const updown = mainSeries.filter(isUpDownSeries);
|
|
14191
|
+
const above = mainSeries.filter((s) => !isUpDownSeries(s));
|
|
14192
|
+
if (updown.length > 0) {
|
|
14193
|
+
process.stdout.write("\n\u2500\u2500 Up/Down \u2500\u2500\n");
|
|
14194
|
+
printTable(updown.map((s) => toRow(s, isFeatured(s))));
|
|
14195
|
+
}
|
|
14196
|
+
if (above.length > 0) {
|
|
14197
|
+
process.stdout.write("\n\u2500\u2500 Price Above \u2500\u2500\n");
|
|
14198
|
+
printTable(above.map((s) => toRow(s, isFeatured(s))));
|
|
14199
|
+
}
|
|
14200
|
+
if (testSeries.length > 0) {
|
|
14201
|
+
if (opts.all) {
|
|
14202
|
+
process.stdout.write("\n\u2500\u2500 Test / Other \u2500\u2500\n");
|
|
14203
|
+
printTable(testSeries.map((s) => toRow(s, false)));
|
|
14204
|
+
} else {
|
|
14205
|
+
process.stdout.write(`
|
|
14206
|
+
${testSeries.length} test series hidden (use --all to show)
|
|
14207
|
+
`);
|
|
14208
|
+
}
|
|
14209
|
+
}
|
|
14210
|
+
}
|
|
14211
|
+
async function cmdEventEvents(run, opts) {
|
|
14212
|
+
const result = await run("event_get_events", {
|
|
14213
|
+
seriesId: opts.seriesId,
|
|
14214
|
+
state: opts.state,
|
|
14215
|
+
limit: opts.limit
|
|
14216
|
+
});
|
|
14217
|
+
const data = getData8(result);
|
|
14218
|
+
if (opts.json) return printJson(data);
|
|
14219
|
+
printTable(
|
|
14220
|
+
(data ?? []).map((e) => ({
|
|
14221
|
+
eventId: e["eventId"],
|
|
14222
|
+
state: e["state"],
|
|
14223
|
+
expTime: fmtTs(e["expTime"]),
|
|
14224
|
+
settleTime: fmtTs(e["settleTime"])
|
|
14225
|
+
}))
|
|
14226
|
+
);
|
|
14227
|
+
}
|
|
14228
|
+
function sortByExpiry(data) {
|
|
14229
|
+
return [...data].sort((a, b) => {
|
|
14230
|
+
const ea = inferExpiryMsFromInstId(String(a["instId"] ?? "")) ?? Infinity;
|
|
14231
|
+
const eb = inferExpiryMsFromInstId(String(b["instId"] ?? "")) ?? Infinity;
|
|
14232
|
+
return ea - eb;
|
|
14233
|
+
});
|
|
14234
|
+
}
|
|
14235
|
+
async function cmdEventMarkets(run, opts) {
|
|
14236
|
+
const result = await run("event_get_markets", {
|
|
14237
|
+
seriesId: opts.seriesId,
|
|
14238
|
+
eventId: opts.eventId,
|
|
14239
|
+
instId: opts.instId,
|
|
14240
|
+
state: opts.state,
|
|
14241
|
+
limit: opts.limit
|
|
14242
|
+
});
|
|
14243
|
+
const data = getData8(result);
|
|
14244
|
+
if (opts.json) return printJson(data);
|
|
14245
|
+
const ext = result;
|
|
14246
|
+
const currentIdxPx = ext["currentIdxPx"];
|
|
14247
|
+
const underlying = ext["underlying"];
|
|
14248
|
+
if (currentIdxPx != null) {
|
|
14249
|
+
process.stdout.write(
|
|
14250
|
+
`${underlying ?? ""} current index price: $${currentIdxPx}
|
|
14251
|
+
`
|
|
14252
|
+
);
|
|
14253
|
+
}
|
|
14254
|
+
const sorted = sortByExpiry(data ?? []);
|
|
14255
|
+
printTable(
|
|
14256
|
+
sorted.map((m) => {
|
|
14257
|
+
const id = String(m["instId"] ?? "");
|
|
14258
|
+
const outcome = fmtMarketOutcome(m["instId"], m["outcome"]);
|
|
14259
|
+
return {
|
|
14260
|
+
contract: formatDisplayTitle(id),
|
|
14261
|
+
expTime: m["expTime"] ?? "",
|
|
14262
|
+
targetPrice: m["floorStrike"] ?? "",
|
|
14263
|
+
probability: fmtProbability(m["px"]),
|
|
14264
|
+
outcome: outcome.toLowerCase() === "pending" ? "\u2014" : outcome,
|
|
14265
|
+
settleValue: m["settleValue"] ?? "",
|
|
14266
|
+
instId: id
|
|
14267
|
+
};
|
|
14268
|
+
})
|
|
14269
|
+
);
|
|
14270
|
+
}
|
|
14271
|
+
async function cmdEventOrders(run, opts) {
|
|
14272
|
+
const result = await run("event_get_orders", {
|
|
14273
|
+
instId: opts.instId,
|
|
14274
|
+
state: opts.state,
|
|
14275
|
+
limit: opts.limit
|
|
14276
|
+
});
|
|
14277
|
+
const data = getData8(result);
|
|
14278
|
+
if (opts.json) return printJson(data);
|
|
14279
|
+
printTable(
|
|
14280
|
+
(data ?? []).map((o) => ({
|
|
14281
|
+
"Contract": formatDisplayTitle(String(o["instId"] ?? "")),
|
|
14282
|
+
"Time": fmtTs(o["cTime"]),
|
|
14283
|
+
"Direction": `${String(o["side"] ?? "").toUpperCase()} ${fmtOrderOutcome(o["instId"], o["outcome"]).toUpperCase()}`,
|
|
14284
|
+
"Price": o["px"],
|
|
14285
|
+
"Size": `${o["fillSz"] ?? 0} / ${o["sz"]}`,
|
|
14286
|
+
"Status": o["stateLabel"] ?? o["state"],
|
|
14287
|
+
"Order number": o["ordId"]
|
|
14288
|
+
}))
|
|
14289
|
+
);
|
|
14290
|
+
}
|
|
14291
|
+
async function cmdEventFills(run, opts) {
|
|
14292
|
+
const result = await run("event_get_fills", {
|
|
14293
|
+
instId: opts.instId,
|
|
14294
|
+
limit: opts.limit
|
|
14295
|
+
});
|
|
14296
|
+
const data = getData8(result);
|
|
14297
|
+
if (opts.json) return printJson(data);
|
|
14298
|
+
printTable(
|
|
14299
|
+
(data ?? []).map((f) => ({
|
|
14300
|
+
"Contract": formatDisplayTitle(String(f["instId"] ?? "")),
|
|
14301
|
+
"Direction": (() => {
|
|
14302
|
+
const side = String(f["side"] ?? "").toUpperCase();
|
|
14303
|
+
const outcome = fmtOrderOutcome(f["instId"], f["outcome"]).toUpperCase();
|
|
14304
|
+
const dir = `${side} ${outcome}`.trim();
|
|
14305
|
+
return dir || "\u2014";
|
|
14306
|
+
})(),
|
|
14307
|
+
"Fill Price": f["fillPx"],
|
|
14308
|
+
"Fill Size": f["fillSz"],
|
|
14309
|
+
"Time": fmtTs(f["ts"]),
|
|
14310
|
+
"Order number": f["ordId"]
|
|
14311
|
+
}))
|
|
14312
|
+
);
|
|
14313
|
+
}
|
|
14314
|
+
async function handleExpiredContractFallback(run, opts) {
|
|
14315
|
+
process.stdout.write(
|
|
14316
|
+
`Order failed: Contract ${opts.instId} has expired.
|
|
14317
|
+
Checking next available contracts in this series...
|
|
14318
|
+
`
|
|
14319
|
+
);
|
|
14320
|
+
const seriesId = extractSeriesId(opts.instId);
|
|
14321
|
+
try {
|
|
14322
|
+
const mkts = await run("event_get_markets", { seriesId, state: "live" });
|
|
14323
|
+
const mData = getData8(mkts) ?? [];
|
|
14324
|
+
const active = mData.filter((m) => m["floorStrike"] && m["floorStrike"] !== "");
|
|
14325
|
+
if (active.length > 0) {
|
|
14326
|
+
printTable(
|
|
14327
|
+
active.slice(0, 3).map((m) => ({
|
|
14328
|
+
instId: m["instId"],
|
|
14329
|
+
expTime: m["expTime"] ?? "",
|
|
14330
|
+
targetPrice: m["floorStrike"] ?? ""
|
|
14331
|
+
}))
|
|
14332
|
+
);
|
|
14333
|
+
const next = active[0];
|
|
14334
|
+
const nextInstId = String(next["instId"]);
|
|
14335
|
+
const pxFlag = opts.px ? ` --px ${opts.px}` : "";
|
|
14336
|
+
const ordTypeFlag = opts.ordType ? ` --ordType ${opts.ordType}` : "";
|
|
14337
|
+
process.stdout.write(
|
|
14338
|
+
`
|
|
14339
|
+
To place the same order on the next contract:
|
|
14340
|
+
okx event place ${nextInstId} ${opts.side} ${opts.outcome} ${opts.sz}${pxFlag}${ordTypeFlag}
|
|
14341
|
+
`
|
|
14342
|
+
);
|
|
14343
|
+
} else {
|
|
14344
|
+
process.stdout.write(`No active contracts found in this series.
|
|
14345
|
+
`);
|
|
14346
|
+
}
|
|
14347
|
+
} catch {
|
|
14348
|
+
}
|
|
14349
|
+
}
|
|
14350
|
+
function handlePlaceOrderError2(msg, instId) {
|
|
14351
|
+
if (msg.includes("not found") || msg.includes("51001")) {
|
|
14352
|
+
process.stdout.write(`Order failed: Contract ${instId} not found or not yet available.
|
|
14353
|
+
`);
|
|
14354
|
+
} else {
|
|
14355
|
+
process.stdout.write(`Order failed: ${msg}
|
|
14356
|
+
`);
|
|
14357
|
+
}
|
|
14358
|
+
}
|
|
14359
|
+
function buildPlaceConfirmation(opts, ordType) {
|
|
14360
|
+
const contractName = formatDisplayTitle(opts.instId);
|
|
14361
|
+
const direction = `${opts.side.toUpperCase()} ${opts.outcome.toUpperCase()}`;
|
|
14362
|
+
if (ordType === "market") {
|
|
14363
|
+
return `Placing: ${contractName} ${direction} sz=${opts.sz} (market order, exchange converts to contracts)
|
|
14364
|
+
`;
|
|
14365
|
+
}
|
|
14366
|
+
const px = parseFloat(opts.px ?? "0");
|
|
14367
|
+
const sz = parseFloat(opts.sz);
|
|
14368
|
+
const cost = (sz * px).toFixed(2);
|
|
14369
|
+
const maxGain = (sz * (1 - px)).toFixed(2);
|
|
14370
|
+
return `Placing: ${contractName} ${direction} ${opts.sz} contracts at px=${opts.px} (cost \u2248 ${cost}, max gain \u2248 ${maxGain})
|
|
14371
|
+
`;
|
|
14372
|
+
}
|
|
14373
|
+
async function cmdEventPlace(run, opts) {
|
|
14374
|
+
const ordType = opts.ordType ?? "market";
|
|
14375
|
+
if (ordType === "limit" && !opts.px) {
|
|
14376
|
+
process.stderr.write("Error: --px is required for limit orders.\n");
|
|
14377
|
+
process.exitCode = 1;
|
|
14378
|
+
return;
|
|
14379
|
+
}
|
|
14380
|
+
if (!opts.json) {
|
|
14381
|
+
process.stdout.write(buildPlaceConfirmation(opts, ordType));
|
|
14382
|
+
}
|
|
14383
|
+
let result;
|
|
14384
|
+
try {
|
|
14385
|
+
result = await run("event_place_order", {
|
|
14386
|
+
instId: opts.instId,
|
|
14387
|
+
side: opts.side,
|
|
14388
|
+
outcome: opts.outcome,
|
|
14389
|
+
sz: opts.sz,
|
|
14390
|
+
px: opts.px,
|
|
14391
|
+
ordType: opts.ordType
|
|
14392
|
+
});
|
|
14393
|
+
} catch (err) {
|
|
14394
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14395
|
+
const expiryMs = inferExpiryMsFromInstId(opts.instId);
|
|
14396
|
+
const isExpired = expiryMs !== null && expiryMs < Date.now();
|
|
14397
|
+
if (isExpired) {
|
|
14398
|
+
await handleExpiredContractFallback(run, opts);
|
|
14399
|
+
} else {
|
|
14400
|
+
handlePlaceOrderError2(msg, opts.instId);
|
|
14401
|
+
}
|
|
14402
|
+
return;
|
|
14403
|
+
}
|
|
14404
|
+
const data = getData8(result);
|
|
14405
|
+
if (opts.json) return printJson(data);
|
|
14406
|
+
const order = data?.[0];
|
|
14407
|
+
const stateHint = ordType === "market" ? "market order \u2014 typically fills immediately" : `${ordType} order \u2014 may still be live; verify with: okx event orders --instId ${opts.instId} --state live`;
|
|
14408
|
+
const period = fmtPeriodFromInstId(opts.instId);
|
|
14409
|
+
const pxPart = opts.px ? ` px: ${opts.px}` : "";
|
|
14410
|
+
process.stdout.write(
|
|
14411
|
+
`Order submitted: ${order?.["ordId"]}
|
|
14412
|
+
Period: ${period}
|
|
14413
|
+
${opts.side.toUpperCase()} ${opts.outcome.toUpperCase()} sz: ${opts.sz}${pxPart} type: ${ordType}
|
|
14414
|
+
(${stateHint})
|
|
14415
|
+
`
|
|
14416
|
+
);
|
|
14417
|
+
if (ordType === "market") {
|
|
14418
|
+
process.stdout.write(" Note: exchange converts sz (amount) to contracts based on best available price\n");
|
|
14419
|
+
}
|
|
14420
|
+
}
|
|
14421
|
+
async function cmdEventAmend(run, opts) {
|
|
14422
|
+
let result;
|
|
14423
|
+
try {
|
|
14424
|
+
result = await run("event_amend_order", {
|
|
14425
|
+
instId: opts.instId,
|
|
14426
|
+
ordId: opts.ordId,
|
|
14427
|
+
newPx: opts.px,
|
|
14428
|
+
newSz: opts.sz
|
|
14429
|
+
});
|
|
14430
|
+
} catch (err) {
|
|
14431
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14432
|
+
process.stdout.write(`Failed to amend order ${opts.ordId}: ${msg}
|
|
14433
|
+
`);
|
|
14434
|
+
return;
|
|
14435
|
+
}
|
|
14436
|
+
const data = getData8(result);
|
|
14437
|
+
if (opts.json) return printJson(data);
|
|
14438
|
+
const r = data?.[0];
|
|
14439
|
+
const pxPart = opts.px ? ` new px: ${opts.px}` : "";
|
|
14440
|
+
const szPart = opts.sz ? ` new sz: ${opts.sz}` : "";
|
|
14441
|
+
process.stdout.write(
|
|
14442
|
+
`Amended: ${r?.["ordId"] ?? opts.ordId}${pxPart}${szPart}
|
|
14443
|
+
`
|
|
14444
|
+
);
|
|
14445
|
+
}
|
|
14446
|
+
function handleCancelCatchError(instId, ordId, err) {
|
|
14447
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14448
|
+
const expiryMs = inferExpiryMsFromInstId(instId);
|
|
14449
|
+
const isExpired = expiryMs !== null && expiryMs < Date.now();
|
|
14450
|
+
if (isExpired) {
|
|
14451
|
+
process.stdout.write(
|
|
14452
|
+
`Cannot cancel: contract ${instId} has already expired.
|
|
14453
|
+
The order was auto-cancelled at settlement \u2014 no action needed.
|
|
14454
|
+
`
|
|
14455
|
+
);
|
|
14456
|
+
} else {
|
|
14457
|
+
process.stdout.write(`Failed to cancel order ${ordId}: ${msg}
|
|
14458
|
+
`);
|
|
14459
|
+
}
|
|
14460
|
+
}
|
|
14461
|
+
async function cmdEventCancel(run, opts) {
|
|
14462
|
+
let result;
|
|
14463
|
+
try {
|
|
14464
|
+
result = await run("event_cancel_order", {
|
|
14465
|
+
instId: opts.instId,
|
|
14466
|
+
ordId: opts.ordId
|
|
14467
|
+
});
|
|
14468
|
+
} catch (err) {
|
|
14469
|
+
handleCancelCatchError(opts.instId, opts.ordId, err);
|
|
14470
|
+
return;
|
|
14471
|
+
}
|
|
14472
|
+
const data = getData8(result);
|
|
14473
|
+
if (opts.json) return printJson(data);
|
|
14474
|
+
const r = data?.[0];
|
|
14475
|
+
process.stdout.write(`Cancelled: ${r?.["ordId"] ?? opts.ordId}
|
|
14476
|
+
`);
|
|
14477
|
+
}
|
|
14478
|
+
|
|
13216
14479
|
// src/index.ts
|
|
13217
14480
|
var _require3 = createRequire3(import.meta.url);
|
|
13218
14481
|
var CLI_VERSION2 = _require3("../package.json").version;
|
|
13219
|
-
var GIT_HASH2 = true ? "
|
|
14482
|
+
var GIT_HASH2 = true ? "244dce7" : "dev";
|
|
13220
14483
|
function handleConfigCommand(action, rest, json, lang, force) {
|
|
13221
14484
|
if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
|
|
13222
14485
|
if (action === "show") return cmdConfigShow(json);
|
|
@@ -14026,6 +15289,33 @@ function handleSkillCommand(run, action, rest, v, json, config) {
|
|
|
14026
15289
|
errorLine("Valid: search, categories, add, download, remove, check, list");
|
|
14027
15290
|
process.exitCode = 1;
|
|
14028
15291
|
}
|
|
15292
|
+
function handleEventCommand(run, action, rest, v, json) {
|
|
15293
|
+
const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
|
|
15294
|
+
const handlers = {
|
|
15295
|
+
browse: () => cmdEventBrowse(run, { underlying: v.underlying ?? rest[0], json }),
|
|
15296
|
+
series: () => cmdEventSeries(run, { seriesId: v.seriesId, all: v.all, json }),
|
|
15297
|
+
events: () => cmdEventEvents(run, { seriesId: v.seriesId ?? rest[0], state: v.state, limit, json }),
|
|
15298
|
+
markets: () => cmdEventMarkets(run, { seriesId: v.seriesId ?? rest[0], eventId: v.eventId, instId: v.instId, state: v.state, limit, json }),
|
|
15299
|
+
place: () => cmdEventPlace(run, {
|
|
15300
|
+
instId: v.instId ?? rest[0],
|
|
15301
|
+
side: v.side ?? rest[1],
|
|
15302
|
+
outcome: v.outcome ?? rest[2],
|
|
15303
|
+
sz: v.sz ?? rest[3],
|
|
15304
|
+
px: v.px,
|
|
15305
|
+
ordType: v.ordType,
|
|
15306
|
+
json
|
|
15307
|
+
}),
|
|
15308
|
+
amend: () => cmdEventAmend(run, { instId: v.instId ?? rest[0], ordId: v.ordId ?? rest[1], px: v.px, sz: v.sz, json }),
|
|
15309
|
+
cancel: () => cmdEventCancel(run, { instId: v.instId ?? rest[0], ordId: v.ordId ?? rest[1], json }),
|
|
15310
|
+
orders: () => cmdEventOrders(run, { instId: v.instId, state: v.state, limit, json }),
|
|
15311
|
+
fills: () => cmdEventFills(run, { instId: v.instId, limit, json })
|
|
15312
|
+
};
|
|
15313
|
+
const handler = handlers[action];
|
|
15314
|
+
if (handler) return handler();
|
|
15315
|
+
process.stderr.write(`Unknown event command: ${action}
|
|
15316
|
+
`);
|
|
15317
|
+
process.exitCode = 1;
|
|
15318
|
+
}
|
|
14029
15319
|
function outputResult(result, json) {
|
|
14030
15320
|
if (json) {
|
|
14031
15321
|
outputLine(JSON.stringify(result, null, 2));
|
|
@@ -14104,6 +15394,7 @@ async function main() {
|
|
|
14104
15394
|
swap: () => handleSwapCommand(run, action, rest, v, json),
|
|
14105
15395
|
futures: () => handleFuturesCommand(run, action, rest, v, json),
|
|
14106
15396
|
option: () => handleOptionCommand(run, action, rest, v, json),
|
|
15397
|
+
event: () => handleEventCommand(run, action, rest, v, json),
|
|
14107
15398
|
bot: () => handleBotCommand(run, action, rest, v, json),
|
|
14108
15399
|
earn: () => handleEarnCommand(run, action, rest, v, json),
|
|
14109
15400
|
skill: () => handleSkillCommand(run, action, rest, v, json, config)
|
|
@@ -14128,6 +15419,7 @@ export {
|
|
|
14128
15419
|
handleBotGridCommand,
|
|
14129
15420
|
handleConfigCommand,
|
|
14130
15421
|
handleEarnCommand,
|
|
15422
|
+
handleEventCommand,
|
|
14131
15423
|
handleFuturesAlgoCommand,
|
|
14132
15424
|
handleFuturesCommand,
|
|
14133
15425
|
handleMarketCommand,
|