@okx_ai/okx-trade-mcp 1.3.0 → 1.3.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1248,6 +1248,9 @@ function readNumber(args, key) {
1248
1248
  if (value === void 0 || value === null) {
1249
1249
  return void 0;
1250
1250
  }
1251
+ if (typeof value === "string" && /^-?\d+(\.\d+)?$/.test(value)) {
1252
+ return parseFloat(value);
1253
+ }
1251
1254
  if (typeof value !== "number" || Number.isNaN(value)) {
1252
1255
  throw new ValidationError(`Parameter "${key}" must be a number.`);
1253
1256
  }
@@ -1327,7 +1330,8 @@ var OKX_INST_TYPES = [
1327
1330
  "SWAP",
1328
1331
  "FUTURES",
1329
1332
  "OPTION",
1330
- "MARGIN"
1333
+ "MARGIN",
1334
+ "EVENTS"
1331
1335
  ];
1332
1336
  function publicRateLimit(key, rps = 20) {
1333
1337
  return {
@@ -1625,6 +1629,7 @@ var MODULES = [
1625
1629
  "futures",
1626
1630
  "option",
1627
1631
  "account",
1632
+ "event",
1628
1633
  ...EARN_SUB_MODULE_IDS,
1629
1634
  ...BOT_SUB_MODULE_IDS,
1630
1635
  "skills"
@@ -1818,7 +1823,7 @@ function registerAccountTools() {
1818
1823
  properties: {
1819
1824
  instType: {
1820
1825
  type: "string",
1821
- enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"]
1826
+ enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION", "EVENTS"]
1822
1827
  },
1823
1828
  ccy: {
1824
1829
  type: "string",
@@ -1884,7 +1889,7 @@ function registerAccountTools() {
1884
1889
  properties: {
1885
1890
  instType: {
1886
1891
  type: "string",
1887
- enum: ["SWAP", "FUTURES", "MARGIN", "OPTION"],
1892
+ enum: ["SWAP", "FUTURES", "MARGIN", "OPTION", "EVENTS"],
1888
1893
  description: "Default SWAP"
1889
1894
  },
1890
1895
  instId: {
@@ -1945,7 +1950,7 @@ function registerAccountTools() {
1945
1950
  properties: {
1946
1951
  instType: {
1947
1952
  type: "string",
1948
- enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"]
1953
+ enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION", "EVENTS"]
1949
1954
  },
1950
1955
  instId: {
1951
1956
  type: "string",
@@ -2056,14 +2061,14 @@ function registerAccountTools() {
2056
2061
  {
2057
2062
  name: "account_get_positions",
2058
2063
  module: "account",
2059
- description: "Get current open positions across all instrument types (MARGIN, SWAP, FUTURES, OPTION). Use swap_get_positions for SWAP/FUTURES-only queries.",
2064
+ description: "Get current open positions across all instrument types (MARGIN, SWAP, FUTURES, OPTION, EVENTS). Use swap_get_positions for SWAP/FUTURES-only queries.",
2060
2065
  isWrite: false,
2061
2066
  inputSchema: {
2062
2067
  type: "object",
2063
2068
  properties: {
2064
2069
  instType: {
2065
2070
  type: "string",
2066
- enum: ["MARGIN", "SWAP", "FUTURES", "OPTION"]
2071
+ enum: ["MARGIN", "SWAP", "FUTURES", "OPTION", "EVENTS"]
2067
2072
  },
2068
2073
  instId: {
2069
2074
  type: "string",
@@ -4817,6 +4822,765 @@ function registerAllEarnTools() {
4817
4822
  ];
4818
4823
  return tools.map(withDemoGuard);
4819
4824
  }
4825
+ function findDateIdx(parts) {
4826
+ for (let i = 1; i < parts.length; i++) {
4827
+ if (/^\d{6}$/.test(parts[i])) return i;
4828
+ }
4829
+ return -1;
4830
+ }
4831
+ function inferExpiryMsFromInstId(instId) {
4832
+ const parts = instId.split("-");
4833
+ const upper = instId.toUpperCase();
4834
+ const dateIdx = findDateIdx(parts);
4835
+ if (dateIdx < 0) return null;
4836
+ const dp = parts[dateIdx];
4837
+ const year = 2e3 + parseInt(dp.slice(0, 2), 10);
4838
+ const month = parseInt(dp.slice(2, 4), 10) - 1;
4839
+ const day = parseInt(dp.slice(4, 6), 10);
4840
+ const isUpDown = upper.includes("UPDOWN");
4841
+ let timePart;
4842
+ if (isUpDown) {
4843
+ const candidate = parts[dateIdx + 2];
4844
+ timePart = candidate && /^\d{4}$/.test(candidate) ? candidate : parts[dateIdx + 1];
4845
+ } else {
4846
+ timePart = parts[dateIdx + 1];
4847
+ }
4848
+ if (!timePart || !/^\d{4}$/.test(timePart)) return null;
4849
+ const hour = parseInt(timePart.slice(0, 2), 10);
4850
+ const min = parseInt(timePart.slice(2, 4), 10);
4851
+ return Date.UTC(year, month, day, hour - 8, min, 0, 0);
4852
+ }
4853
+ function extractSeriesId(instId) {
4854
+ const parts = instId.split("-");
4855
+ for (let i = 0; i < parts.length; i++) {
4856
+ if (/^\d{6}$/.test(parts[i])) {
4857
+ return parts.slice(0, i).join("-");
4858
+ }
4859
+ }
4860
+ return instId;
4861
+ }
4862
+ function fmtTimeToken(t) {
4863
+ return t.length === 4 ? `${t.slice(0, 2)}:${t.slice(2)}` : t;
4864
+ }
4865
+ function fmtUpDownName(seriesId, dateStr, parts, dateIdx) {
4866
+ const t1 = parts[dateIdx + 1] ?? "";
4867
+ const t2 = parts[dateIdx + 2] ?? "";
4868
+ const timeRange = t1 && t2 ? ` ${fmtTimeToken(t1)}-${fmtTimeToken(t2)}` : "";
4869
+ return `${seriesId} Up/Down \xB7 ${dateStr}${timeRange}`;
4870
+ }
4871
+ function fmtStrikeName(seriesId, dateStr, label, parts, dateIdx) {
4872
+ const strike = parts[dateIdx + 2] ?? "";
4873
+ const strikeStr = strike && /^\d+$/.test(strike) ? Number(strike).toLocaleString("en-US") : "";
4874
+ return strikeStr ? `${seriesId} ${label} ${strikeStr} \xB7 ${dateStr}` : `${seriesId} \xB7 ${dateStr}`;
4875
+ }
4876
+ function formatDisplayTitle(instId) {
4877
+ const parts = instId.split("-");
4878
+ const upper = instId.toUpperCase();
4879
+ const seriesId = parts[0] ?? instId;
4880
+ const dateIdx = findDateIdx(parts);
4881
+ if (dateIdx < 0) return instId;
4882
+ const d = parts[dateIdx];
4883
+ const month = parseInt(d.slice(2, 4), 10);
4884
+ const day = parseInt(d.slice(4, 6), 10);
4885
+ const dateStr = `${month}/${day}`;
4886
+ if (upper.includes("UPDOWN") || upper.includes("UP-DOWN")) {
4887
+ return fmtUpDownName(seriesId, dateStr, parts, dateIdx);
4888
+ }
4889
+ if (upper.includes("ABOVE")) {
4890
+ return fmtStrikeName(seriesId, dateStr, "above", parts, dateIdx);
4891
+ }
4892
+ if (upper.includes("TOUCH")) {
4893
+ return fmtStrikeName(seriesId, dateStr, "touch", parts, dateIdx);
4894
+ }
4895
+ return `${seriesId} \xB7 ${dateStr}`;
4896
+ }
4897
+ var OUTCOME_LABELS = {
4898
+ "0": "pending",
4899
+ "1": "YES",
4900
+ "2": "NO"
4901
+ };
4902
+ var ORDER_STATE_MAP = {
4903
+ live: "Unfilled",
4904
+ partially_filled: "Partially filled",
4905
+ filled: "Filled",
4906
+ canceled: "Canceled",
4907
+ mmp_canceled: "Canceled"
4908
+ };
4909
+ function mapOrderState(raw) {
4910
+ return ORDER_STATE_MAP[raw] ?? raw;
4911
+ }
4912
+ var TIMESTAMP_FIELDS = /* @__PURE__ */ new Set([
4913
+ "expTime",
4914
+ "settleTime",
4915
+ "listTime",
4916
+ "uTime",
4917
+ "cTime",
4918
+ "fixTime"
4919
+ ]);
4920
+ var DEFAULT_SETTLE_CCY = "USDT";
4921
+ function convertTimestamps(item) {
4922
+ const result = { ...item };
4923
+ for (const key of TIMESTAMP_FIELDS) {
4924
+ if (!(key in result)) continue;
4925
+ const v = Number(result[key]);
4926
+ if (v > 0) {
4927
+ const d = new Date(v + 8 * 60 * 60 * 1e3);
4928
+ const yyyy = d.getUTCFullYear();
4929
+ const mo = String(d.getUTCMonth() + 1).padStart(2, "0");
4930
+ const dd = String(d.getUTCDate()).padStart(2, "0");
4931
+ const hh = String(d.getUTCHours()).padStart(2, "0");
4932
+ const mi = String(d.getUTCMinutes()).padStart(2, "0");
4933
+ result[key] = `${yyyy}-${mo}-${dd} ${hh}:${mi} UTC+8`;
4934
+ } else {
4935
+ delete result[key];
4936
+ }
4937
+ }
4938
+ return result;
4939
+ }
4940
+ function normalizeWrite3(response) {
4941
+ const data = response.data;
4942
+ if (Array.isArray(data) && data.length > 0) {
4943
+ const failed = data.filter(
4944
+ (item) => item !== null && typeof item === "object" && "sCode" in item && item["sCode"] !== "0"
4945
+ );
4946
+ if (failed.length > 0) {
4947
+ const messages = failed.map(
4948
+ (item) => `[${item["sCode"]}] ${item["sMsg"] ?? "Operation failed"}`
4949
+ );
4950
+ throw new OkxApiError(messages.join("; "), {
4951
+ code: String(failed[0]["sCode"] ?? ""),
4952
+ endpoint: response.endpoint
4953
+ });
4954
+ }
4955
+ }
4956
+ return { endpoint: response.endpoint, requestTime: response.requestTime, data };
4957
+ }
4958
+ function extractQuoteCcy(underlying) {
4959
+ if (!underlying) return DEFAULT_SETTLE_CCY;
4960
+ const parts = underlying.split("-");
4961
+ return parts.length >= 2 ? parts[parts.length - 1].toUpperCase() : DEFAULT_SETTLE_CCY;
4962
+ }
4963
+ async function fetchAvailableBalance(client, ccy = DEFAULT_SETTLE_CCY) {
4964
+ try {
4965
+ const r = await client.privateGet(
4966
+ "/api/v5/account/balance",
4967
+ { ccy }
4968
+ );
4969
+ const data = r["data"];
4970
+ if (Array.isArray(data) && data.length > 0) {
4971
+ const details = data[0]["details"];
4972
+ if (Array.isArray(details) && details.length > 0) {
4973
+ const entry = details.find(
4974
+ (d) => String(d["ccy"] ?? "").toUpperCase() === ccy.toUpperCase()
4975
+ );
4976
+ if (!entry) return { balance: null, ccy };
4977
+ const bal = String(entry["availBal"] ?? "") || null;
4978
+ return { balance: bal, ccy };
4979
+ }
4980
+ }
4981
+ } catch {
4982
+ }
4983
+ return { balance: null, ccy };
4984
+ }
4985
+ var KNOWN_UNDERLYINGS = /^(BTC|ETH|TRX|EOS|SOL|IOTA|KISHU|SUSHI|BTG|XTZ|SOLVU)/i;
4986
+ function extractUnderlying(seriesId) {
4987
+ const m = seriesId.match(KNOWN_UNDERLYINGS);
4988
+ return m ? m[1].toUpperCase() : null;
4989
+ }
4990
+ function resolveOutcome(value) {
4991
+ const map = {
4992
+ up: "yes",
4993
+ yes: "yes",
4994
+ down: "no",
4995
+ no: "no"
4996
+ };
4997
+ const resolved = map[value.toLowerCase()];
4998
+ if (!resolved) {
4999
+ throw new Error(
5000
+ `Invalid outcome "${value}". Use: UP or YES for Up/Yes, DOWN or NO for Down/No.`
5001
+ );
5002
+ }
5003
+ return resolved;
5004
+ }
5005
+ function filterBrowseCandidates(allSeries, underlyingFilter) {
5006
+ const isHumanReadable = (id) => /^(BTC|ETH|TRX|EOS|SOL|IOTA|KISHU|SUSHI|BTG|XTZ|SOLVU)-/.test(id);
5007
+ const seen = /* @__PURE__ */ new Set();
5008
+ const candidates = [];
5009
+ for (const s of allSeries) {
5010
+ const settlement = s["settlement"];
5011
+ const method = String(settlement?.["method"] ?? "");
5012
+ const uly = String(settlement?.["underlying"] ?? "");
5013
+ if (underlyingFilter && !uly.startsWith(underlyingFilter)) continue;
5014
+ const key = `${method}:${uly}`;
5015
+ if (!seen.has(key) && isHumanReadable(String(s["seriesId"] ?? ""))) {
5016
+ seen.add(key);
5017
+ candidates.push(s);
5018
+ }
5019
+ }
5020
+ return candidates;
5021
+ }
5022
+ function isActiveMarket(m, isUpDown, now) {
5023
+ if (!isUpDown && (!m["floorStrike"] || m["floorStrike"] === "")) return false;
5024
+ const expMs = Number(m["expTime"] ?? 0);
5025
+ return expMs <= 0 || expMs > now;
5026
+ }
5027
+ function toContractSummary(m) {
5028
+ const converted = convertTimestamps(m);
5029
+ const id = String(m["instId"] ?? "");
5030
+ return {
5031
+ instId: id,
5032
+ displayTitle: formatDisplayTitle(id),
5033
+ expTime: converted["expTime"],
5034
+ floorStrike: m["floorStrike"],
5035
+ px: m["px"],
5036
+ outcome: OUTCOME_LABELS[String(m["outcome"] ?? "")] ?? m["outcome"]
5037
+ };
5038
+ }
5039
+ async function fetchActiveContractsForSeries(client, s) {
5040
+ const seriesId = String(s["seriesId"] ?? "");
5041
+ const settlement = s["settlement"];
5042
+ const method = String(settlement?.["method"] ?? "");
5043
+ const isUpDown = method === "price_up_down";
5044
+ try {
5045
+ const r = await client.privateGet(
5046
+ "/api/v5/public/event-contract/markets",
5047
+ compactObject({ seriesId, state: "live" }),
5048
+ privateRateLimit("event_browse", 20)
5049
+ );
5050
+ const normalized = normalizeResponse(r);
5051
+ const markets = Array.isArray(normalized["data"]) ? normalized["data"] : [];
5052
+ const now = Date.now();
5053
+ const active = markets.filter((m) => isActiveMarket(m, isUpDown, now)).map(toContractSummary);
5054
+ if (active.length === 0) return null;
5055
+ return {
5056
+ seriesId,
5057
+ method: String(settlement?.["method"] ?? ""),
5058
+ underlying: String(settlement?.["underlying"] ?? ""),
5059
+ freq: String(s["freq"] ?? ""),
5060
+ contracts: active
5061
+ };
5062
+ } catch {
5063
+ return null;
5064
+ }
5065
+ }
5066
+ function resolveUnderlyingFromSeriesResp(seriesResp) {
5067
+ if (!seriesResp) return null;
5068
+ const sResp = seriesResp;
5069
+ const sData = Array.isArray(sResp["data"]) ? sResp["data"] : [];
5070
+ if (sData.length > 0) {
5071
+ const settlement = sData[0]["settlement"];
5072
+ return String(settlement?.["underlying"] ?? "") || null;
5073
+ }
5074
+ return null;
5075
+ }
5076
+ function translateAndSortMarkets(rawData, limit) {
5077
+ const sorted = [...rawData].sort((a, b) => {
5078
+ const tA = Number(a["expTime"] ?? 0);
5079
+ const tB = Number(b["expTime"] ?? 0);
5080
+ return tA - tB;
5081
+ });
5082
+ const sliced = limit && limit > 0 ? sorted.slice(0, limit) : sorted;
5083
+ return sliced.map((item) => {
5084
+ const converted = convertTimestamps(item);
5085
+ if (typeof converted["outcome"] === "string") {
5086
+ converted["outcome"] = OUTCOME_LABELS[converted["outcome"]] ?? converted["outcome"];
5087
+ }
5088
+ converted["displayTitle"] = formatDisplayTitle(String(item["instId"] ?? ""));
5089
+ return converted;
5090
+ });
5091
+ }
5092
+ function enrichFill(item) {
5093
+ const subType = String(item["subType"] ?? "");
5094
+ const isSettle = subType === "414" || subType === "415";
5095
+ const enriched = {
5096
+ ...item,
5097
+ displayTitle: formatDisplayTitle(String(item["instId"] ?? "")),
5098
+ outcome: OUTCOME_LABELS[String(item["outcome"] ?? "")] ?? item["outcome"],
5099
+ type: isSettle ? "settlement" : "fill"
5100
+ };
5101
+ if (isSettle) {
5102
+ const fillPnl = parseFloat(String(item["fillPnl"] ?? "NaN"));
5103
+ const isWin = subType === "414";
5104
+ enriched["settlementResult"] = isWin ? "win" : "loss";
5105
+ enriched["pnl"] = isNaN(fillPnl) ? void 0 : fillPnl;
5106
+ }
5107
+ return enriched;
5108
+ }
5109
+ async function fetchIdxPx(client, underlying) {
5110
+ try {
5111
+ const r = await client.publicGet(
5112
+ "/api/v5/market/index-tickers",
5113
+ { instId: underlying },
5114
+ publicRateLimit("fetchIdxPx", 20)
5115
+ );
5116
+ const data = r["data"];
5117
+ if (Array.isArray(data) && data.length > 0) {
5118
+ return String(data[0]["idxPx"] ?? "") || null;
5119
+ }
5120
+ } catch {
5121
+ }
5122
+ return null;
5123
+ }
5124
+ function handlePlaceOrderError(base, rawArgs, endpoint) {
5125
+ if (!Array.isArray(base["data"])) return;
5126
+ const item = base["data"][0];
5127
+ const sCode = item && String(item["sCode"] ?? "");
5128
+ if (!sCode || sCode === "0") return;
5129
+ const sMsg = String(item["sMsg"] ?? "Order failed");
5130
+ if (sCode === "51001") {
5131
+ const instId = requireString(asRecord(rawArgs), "instId");
5132
+ const seriesId = extractSeriesId(instId);
5133
+ const expiryMs = inferExpiryMsFromInstId(instId);
5134
+ const isExpired = expiryMs !== null && expiryMs < Date.now();
5135
+ const reason = isExpired ? `The contract (${instId}) has expired.` : `The contract (${instId}) was not found \u2014 it may not exist or has not started yet.`;
5136
+ throw new OkxApiError(
5137
+ `${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.`,
5138
+ { code: sCode, endpoint }
5139
+ );
5140
+ }
5141
+ throw new OkxApiError(`[${sCode}] ${sMsg}`, { code: sCode, endpoint });
5142
+ }
5143
+ var OUTCOME_SCHEMA = {
5144
+ type: "string",
5145
+ enum: ["UP", "YES", "DOWN", "NO"],
5146
+ description: `Which outcome to trade.
5147
+ UP/DOWN direction contracts: UP (price rises during the period) or DOWN (price falls).
5148
+ YES/NO price-target or touch contracts: YES (condition met) or NO (condition not met).
5149
+ Check the series type from event_get_series to determine which applies.
5150
+ NOTE: px is the event contract price (0.01\u20130.99), NOT the underlying asset price. It reflects market-implied probability when actively trading.`
5151
+ };
5152
+ function registerEventContractTools() {
5153
+ return [
5154
+ // -----------------------------------------------------------------------
5155
+ // Read-only — browse (user-facing) + series / events / markets (internal)
5156
+ // -----------------------------------------------------------------------
5157
+ {
5158
+ name: "event_browse",
5159
+ module: "event",
5160
+ 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.",
5161
+ isWrite: false,
5162
+ inputSchema: {
5163
+ type: "object",
5164
+ properties: {
5165
+ underlying: {
5166
+ type: "string",
5167
+ description: "Filter by underlying asset, e.g. BTC-USD, ETH-USD. Omit for all."
5168
+ }
5169
+ }
5170
+ },
5171
+ handler: async (rawArgs, context) => {
5172
+ const args = asRecord(rawArgs);
5173
+ const underlyingFilter = readString(args, "underlying");
5174
+ const seriesResp = await context.client.privateGet(
5175
+ "/api/v5/public/event-contract/series",
5176
+ compactObject({}),
5177
+ privateRateLimit("event_browse", 10)
5178
+ );
5179
+ const normalizedSeries = normalizeResponse(seriesResp);
5180
+ const allSeries = Array.isArray(normalizedSeries["data"]) ? normalizedSeries["data"] : [];
5181
+ const candidates = filterBrowseCandidates(allSeries, underlyingFilter);
5182
+ const marketResults = await Promise.all(
5183
+ candidates.map((s) => fetchActiveContractsForSeries(context.client, s))
5184
+ );
5185
+ const results = marketResults.filter(Boolean);
5186
+ return {
5187
+ data: results,
5188
+ total: results.reduce((n, r) => n + (r?.contracts?.length ?? 0), 0)
5189
+ };
5190
+ }
5191
+ },
5192
+ {
5193
+ name: "event_get_series",
5194
+ module: "event",
5195
+ description: "List event contract series. Returns all available series with settlement type and underlying. Use event_browse to see currently active contracts.",
5196
+ isWrite: false,
5197
+ inputSchema: {
5198
+ type: "object",
5199
+ properties: {
5200
+ seriesId: {
5201
+ type: "string",
5202
+ description: "Filter by series ID, e.g. BTC-ABOVE-DAILY. Omit for all."
5203
+ }
5204
+ }
5205
+ },
5206
+ handler: async (rawArgs, context) => {
5207
+ const args = asRecord(rawArgs);
5208
+ const response = await context.client.privateGet(
5209
+ "/api/v5/public/event-contract/series",
5210
+ compactObject({ seriesId: readString(args, "seriesId") }),
5211
+ privateRateLimit("event_get_series", 20)
5212
+ );
5213
+ return normalizeResponse(response);
5214
+ }
5215
+ },
5216
+ {
5217
+ name: "event_get_events",
5218
+ module: "event",
5219
+ description: "List expiry periods within a series. state: preopen|live|settling|expired. expTime is pre-formatted UTC+8.",
5220
+ isWrite: false,
5221
+ inputSchema: {
5222
+ type: "object",
5223
+ properties: {
5224
+ seriesId: {
5225
+ type: "string",
5226
+ description: "Series ID, e.g. BTC-ABOVE-DAILY (required)"
5227
+ },
5228
+ eventId: {
5229
+ type: "string",
5230
+ description: "Filter by event ID, e.g. BTC-ABOVE-DAILY-260224-1600"
5231
+ },
5232
+ state: {
5233
+ type: "string",
5234
+ enum: ["preopen", "live", "settling", "expired"],
5235
+ description: "preopen=markets not yet trading; live=active; settling=awaiting settlement; expired=done"
5236
+ },
5237
+ limit: {
5238
+ type: "number",
5239
+ description: "Max results (default 100, max 100)"
5240
+ },
5241
+ before: {
5242
+ type: "string",
5243
+ description: "Pagination: return records newer than this expTime"
5244
+ },
5245
+ after: {
5246
+ type: "string",
5247
+ description: "Pagination: return records older than this expTime"
5248
+ }
5249
+ },
5250
+ required: ["seriesId"]
5251
+ },
5252
+ handler: async (rawArgs, context) => {
5253
+ const args = asRecord(rawArgs);
5254
+ const response = await context.client.privateGet(
5255
+ "/api/v5/public/event-contract/events",
5256
+ compactObject({
5257
+ seriesId: requireString(args, "seriesId"),
5258
+ eventId: readString(args, "eventId"),
5259
+ state: readString(args, "state"),
5260
+ limit: readNumber(args, "limit"),
5261
+ before: readString(args, "before"),
5262
+ after: readString(args, "after")
5263
+ }),
5264
+ privateRateLimit("event_get_events", 20)
5265
+ );
5266
+ const base = normalizeResponse(response);
5267
+ const data = Array.isArray(base["data"]) ? base["data"].map(convertTimestamps) : base["data"];
5268
+ return { ...base, data };
5269
+ }
5270
+ },
5271
+ {
5272
+ name: "event_get_markets",
5273
+ module: "event",
5274
+ 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.",
5275
+ isWrite: false,
5276
+ inputSchema: {
5277
+ type: "object",
5278
+ properties: {
5279
+ seriesId: {
5280
+ type: "string",
5281
+ description: "Series ID, e.g. BTC-ABOVE-DAILY (required)"
5282
+ },
5283
+ eventId: {
5284
+ type: "string",
5285
+ description: "Filter by event ID, e.g. BTC-ABOVE-DAILY-260224-1600"
5286
+ },
5287
+ instId: {
5288
+ type: "string",
5289
+ description: "Filter by instrument ID"
5290
+ },
5291
+ state: {
5292
+ type: "string",
5293
+ enum: ["preopen", "live", "settling", "expired"],
5294
+ description: "preopen=not yet trading; live=active; settling=awaiting settlement; expired=settled"
5295
+ },
5296
+ limit: {
5297
+ type: "number",
5298
+ description: "Max results (default 100, max 100)"
5299
+ },
5300
+ before: {
5301
+ type: "string",
5302
+ description: "Pagination: return records newer than this expTime"
5303
+ },
5304
+ after: {
5305
+ type: "string",
5306
+ description: "Pagination: return records older than this expTime"
5307
+ }
5308
+ },
5309
+ required: ["seriesId"]
5310
+ },
5311
+ handler: async (rawArgs, context) => {
5312
+ const args = asRecord(rawArgs);
5313
+ const seriesId = requireString(args, "seriesId");
5314
+ const knownUnderlying = extractUnderlying(seriesId);
5315
+ const [marketsResp, seriesResp, idxPxFromKnown] = await Promise.all([
5316
+ context.client.privateGet(
5317
+ "/api/v5/public/event-contract/markets",
5318
+ compactObject({
5319
+ seriesId,
5320
+ eventId: readString(args, "eventId"),
5321
+ instId: readString(args, "instId"),
5322
+ state: readString(args, "state"),
5323
+ before: readString(args, "before"),
5324
+ after: readString(args, "after")
5325
+ }),
5326
+ privateRateLimit("event_get_markets", 20)
5327
+ ),
5328
+ knownUnderlying ? Promise.resolve(null) : context.client.privateGet(
5329
+ "/api/v5/public/event-contract/series",
5330
+ compactObject({ seriesId }),
5331
+ privateRateLimit("event_get_series", 20)
5332
+ ),
5333
+ knownUnderlying ? fetchIdxPx(context.client, knownUnderlying + "-USDT") : Promise.resolve(null)
5334
+ ]);
5335
+ let underlying = knownUnderlying ? knownUnderlying + "-USDT" : null;
5336
+ if (!underlying) {
5337
+ underlying = resolveUnderlyingFromSeriesResp(seriesResp);
5338
+ }
5339
+ const idxPx = idxPxFromKnown ?? (underlying && !knownUnderlying ? await fetchIdxPx(context.client, underlying) : null);
5340
+ const base = normalizeResponse(marketsResp);
5341
+ const limit = readNumber(args, "limit");
5342
+ const rawData = Array.isArray(base["data"]) ? base["data"] : [];
5343
+ const translated = translateAndSortMarkets(rawData, limit);
5344
+ return {
5345
+ ...base,
5346
+ data: translated,
5347
+ currentIdxPx: idxPx,
5348
+ underlying
5349
+ };
5350
+ }
5351
+ },
5352
+ {
5353
+ name: "event_get_orders",
5354
+ module: "event",
5355
+ description: "Query event contract orders. state=live for open orders; omit for history. outcome pre-translated (YES/NO/UP/DOWN).",
5356
+ isWrite: false,
5357
+ inputSchema: {
5358
+ type: "object",
5359
+ properties: {
5360
+ instId: {
5361
+ type: "string",
5362
+ description: "Event contract instrument ID"
5363
+ },
5364
+ state: {
5365
+ type: "string",
5366
+ description: "live=pending orders; omit for history"
5367
+ },
5368
+ limit: {
5369
+ type: "number",
5370
+ description: "Max results (default 20)"
5371
+ }
5372
+ }
5373
+ },
5374
+ handler: async (rawArgs, context) => {
5375
+ const args = asRecord(rawArgs);
5376
+ const state = readString(args, "state");
5377
+ const isPending = state === "live";
5378
+ const endpoint = isPending ? "/api/v5/trade/orders-pending" : "/api/v5/trade/orders-history";
5379
+ const response = await context.client.privateGet(
5380
+ endpoint,
5381
+ compactObject({
5382
+ instType: "EVENTS",
5383
+ instId: readString(args, "instId"),
5384
+ limit: readNumber(args, "limit")
5385
+ }),
5386
+ privateRateLimit("event_get_orders", 20)
5387
+ );
5388
+ const base = normalizeResponse(response);
5389
+ const data = Array.isArray(base["data"]) ? base["data"].map((item) => ({
5390
+ ...item,
5391
+ displayTitle: formatDisplayTitle(String(item["instId"] ?? "")),
5392
+ outcome: OUTCOME_LABELS[String(item["outcome"] ?? "")] ?? item["outcome"],
5393
+ state: String(item["state"] ?? ""),
5394
+ stateLabel: mapOrderState(String(item["state"] ?? ""))
5395
+ })) : base["data"];
5396
+ return { ...base, data };
5397
+ }
5398
+ },
5399
+ {
5400
+ name: "event_get_fills",
5401
+ module: "event",
5402
+ 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.",
5403
+ isWrite: false,
5404
+ inputSchema: {
5405
+ type: "object",
5406
+ properties: {
5407
+ instId: {
5408
+ type: "string",
5409
+ description: "Event contract instrument ID"
5410
+ },
5411
+ limit: {
5412
+ type: "number",
5413
+ description: "Max results (default 20)"
5414
+ }
5415
+ }
5416
+ },
5417
+ handler: async (rawArgs, context) => {
5418
+ const args = asRecord(rawArgs);
5419
+ const response = await context.client.privateGet(
5420
+ "/api/v5/trade/fills",
5421
+ compactObject({
5422
+ instType: "EVENTS",
5423
+ instId: readString(args, "instId"),
5424
+ limit: readNumber(args, "limit")
5425
+ }),
5426
+ privateRateLimit("event_get_fills", 20)
5427
+ );
5428
+ const base = normalizeResponse(response);
5429
+ const data = Array.isArray(base["data"]) ? base["data"].map(enrichFill) : base["data"];
5430
+ return { ...base, data };
5431
+ }
5432
+ },
5433
+ // -----------------------------------------------------------------------
5434
+ // Private — write
5435
+ // -----------------------------------------------------------------------
5436
+ {
5437
+ name: "event_place_order",
5438
+ module: "event",
5439
+ description: `Place an event contract order. [CAUTION] Places a real order.
5440
+ - outcome: UP/YES (bet price goes up/condition met) or DOWN/NO (bet price goes down/condition not met)
5441
+ - 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
5442
+ - tdMode is always isolated; speedBump is auto-set per exchange requirement \u2014 do not pass either`,
5443
+ isWrite: true,
5444
+ inputSchema: {
5445
+ type: "object",
5446
+ properties: {
5447
+ instId: {
5448
+ type: "string",
5449
+ description: "Event contract instrument ID, e.g. BTC-ABOVE-DAILY-260224-1600-120000"
5450
+ },
5451
+ side: {
5452
+ type: "string",
5453
+ enum: ["buy", "sell"],
5454
+ description: "buy=open position, sell=close position"
5455
+ },
5456
+ outcome: OUTCOME_SCHEMA,
5457
+ ordType: {
5458
+ type: "string",
5459
+ enum: ["market", "limit", "post_only"],
5460
+ description: "Order type (default market)"
5461
+ },
5462
+ sz: {
5463
+ type: "string",
5464
+ 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)."
5465
+ },
5466
+ px: {
5467
+ type: "string",
5468
+ description: "Event contract price (0.01\u20130.99). Required when ordType=limit. Do NOT use for market orders."
5469
+ }
5470
+ },
5471
+ required: ["instId", "side", "outcome", "sz"]
5472
+ },
5473
+ handler: async (rawArgs, context) => {
5474
+ const args = asRecord(rawArgs);
5475
+ const ordType = readString(args, "ordType") ?? "market";
5476
+ const speedBump = ordType !== "post_only" ? "1" : void 0;
5477
+ const instId = requireString(args, "instId");
5478
+ const response = await context.client.privatePost(
5479
+ "/api/v5/trade/order",
5480
+ compactObject({
5481
+ instId,
5482
+ tdMode: "isolated",
5483
+ side: requireString(args, "side"),
5484
+ outcome: resolveOutcome(requireString(args, "outcome")),
5485
+ ordType,
5486
+ sz: requireString(args, "sz"),
5487
+ px: readString(args, "px"),
5488
+ speedBump,
5489
+ tag: context.config.sourceTag
5490
+ }),
5491
+ privateRateLimit("event_place_order", 60)
5492
+ );
5493
+ const base = normalizeResponse(response);
5494
+ handlePlaceOrderError(base, asRecord(rawArgs), response.endpoint);
5495
+ const data = Array.isArray(base["data"]) ? base["data"].map(({ tag: _t, ...rest }) => rest) : base["data"];
5496
+ const placeSeriesId = extractSeriesId(instId);
5497
+ const placeUnderlying = extractUnderlying(placeSeriesId);
5498
+ const placeCcy = extractQuoteCcy(placeUnderlying ? placeUnderlying + "-USDT" : null);
5499
+ const balResult = await fetchAvailableBalance(context.client, placeCcy);
5500
+ const result = { ...base, data };
5501
+ if (balResult.balance) {
5502
+ result["availableBalance"] = balResult.balance;
5503
+ result["availableBalanceCcy"] = balResult.ccy;
5504
+ }
5505
+ if (ordType === "market") {
5506
+ result["orderNote"] = "Market order: sz is a quote currency amount. The exchange converts it to contracts based on best available price.";
5507
+ }
5508
+ return result;
5509
+ }
5510
+ },
5511
+ {
5512
+ name: "event_amend_order",
5513
+ module: "event",
5514
+ description: "Amend a pending event contract order (change price or size). [CAUTION] Modifies a real order. Only limit/post_only orders can be amended.",
5515
+ isWrite: true,
5516
+ inputSchema: {
5517
+ type: "object",
5518
+ properties: {
5519
+ instId: { type: "string", description: "Event contract instrument ID" },
5520
+ ordId: { type: "string", description: "Order ID to amend" },
5521
+ newPx: { type: "string", description: "New event contract price (0.01\u20130.99). Omit to keep current." },
5522
+ newSz: { type: "string", description: "New size in contracts (omit to keep current)" }
5523
+ },
5524
+ required: ["instId", "ordId"]
5525
+ },
5526
+ handler: async (rawArgs, context) => {
5527
+ const args = asRecord(rawArgs);
5528
+ const response = await context.client.privatePost(
5529
+ "/api/v5/trade/amend-order",
5530
+ compactObject({
5531
+ instId: requireString(args, "instId"),
5532
+ ordId: requireString(args, "ordId"),
5533
+ newPx: readString(args, "newPx"),
5534
+ newSz: readString(args, "newSz"),
5535
+ speedBump: "1"
5536
+ }),
5537
+ privateRateLimit("event_amend_order", 60)
5538
+ );
5539
+ return normalizeWrite3(response);
5540
+ }
5541
+ },
5542
+ {
5543
+ name: "event_cancel_order",
5544
+ module: "event",
5545
+ 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.",
5546
+ isWrite: true,
5547
+ inputSchema: {
5548
+ type: "object",
5549
+ properties: {
5550
+ instId: {
5551
+ type: "string",
5552
+ description: "Event contract instrument ID"
5553
+ },
5554
+ ordId: {
5555
+ type: "string",
5556
+ description: "Order ID to cancel"
5557
+ }
5558
+ },
5559
+ required: ["instId", "ordId"]
5560
+ },
5561
+ handler: async (rawArgs, context) => {
5562
+ const args = asRecord(rawArgs);
5563
+ const instId = requireString(args, "instId");
5564
+ const response = await context.client.privatePost(
5565
+ "/api/v5/trade/cancel-order",
5566
+ { instId, ordId: requireString(args, "ordId") },
5567
+ privateRateLimit("event_cancel_order", 60)
5568
+ );
5569
+ if (Array.isArray(response.data) && response.data.length > 0) {
5570
+ const item = response.data[0];
5571
+ const sCode = item && String(item["sCode"] ?? "");
5572
+ if (sCode === "51001") {
5573
+ const expiryMs = inferExpiryMsFromInstId(instId);
5574
+ const isExpired = expiryMs !== null && expiryMs < Date.now();
5575
+ 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.`;
5576
+ throw new OkxApiError(reason, { code: sCode, endpoint: response.endpoint });
5577
+ }
5578
+ }
5579
+ return normalizeWrite3(response);
5580
+ }
5581
+ }
5582
+ ];
5583
+ }
4820
5584
  function buildContractTradeTools(cfg) {
4821
5585
  const { prefix, module, label, instTypes, instIdExample } = cfg;
4822
5586
  const [defaultType, otherType] = instTypes;
@@ -7455,6 +8219,7 @@ function allToolSpecs() {
7455
8219
  ...registerOptionAlgoTools(),
7456
8220
  ...registerAlgoTradeTools(),
7457
8221
  ...registerAccountTools(),
8222
+ ...registerEventContractTools(),
7458
8223
  ...registerBotTools(),
7459
8224
  ...registerAllEarnTools(),
7460
8225
  ...registerAuditTools(),
@@ -7901,7 +8666,7 @@ var _require = createRequire(import.meta.url);
7901
8666
  var pkg = _require("../package.json");
7902
8667
  var SERVER_NAME = "okx-trade-mcp";
7903
8668
  var SERVER_VERSION = pkg.version;
7904
- var GIT_HASH = true ? "6d4d559" : "dev";
8669
+ var GIT_HASH = true ? "765bfa2" : "dev";
7905
8670
 
7906
8671
  // src/server.ts
7907
8672
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";