@okx_ai/okx-trade-cli 1.3.1-beta.2 → 1.3.1-beta.4
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 +489 -1440
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +198 -2
package/dist/index.js
CHANGED
|
@@ -5,24 +5,39 @@ import { createRequire as createRequire3 } from "module";
|
|
|
5
5
|
|
|
6
6
|
// ../core/dist/index.js
|
|
7
7
|
import { ProxyAgent } from "undici";
|
|
8
|
+
import { isIP } from "net";
|
|
9
|
+
import { lookup as dnsLookup } from "dns";
|
|
10
|
+
import { Agent } from "undici";
|
|
11
|
+
import { execFile } from "child_process";
|
|
12
|
+
import { homedir } from "os";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
import {
|
|
15
|
+
readFileSync,
|
|
16
|
+
writeFileSync,
|
|
17
|
+
mkdirSync,
|
|
18
|
+
unlinkSync,
|
|
19
|
+
renameSync
|
|
20
|
+
} from "fs";
|
|
21
|
+
import { homedir as homedir2 } from "os";
|
|
22
|
+
import { join as join2, dirname } from "path";
|
|
8
23
|
import { createHmac } from "crypto";
|
|
9
24
|
import fs from "fs";
|
|
10
25
|
import path from "path";
|
|
11
26
|
import os from "os";
|
|
12
|
-
import { writeFileSync, renameSync, unlinkSync, mkdirSync } from "fs";
|
|
13
|
-
import { join, resolve, basename, sep } from "path";
|
|
27
|
+
import { writeFileSync as writeFileSync2, renameSync as renameSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
28
|
+
import { join as join3, resolve, basename, sep } from "path";
|
|
14
29
|
import { randomUUID } from "crypto";
|
|
15
30
|
import yauzl from "yauzl";
|
|
16
|
-
import { createWriteStream, mkdirSync as
|
|
17
|
-
import { resolve as resolve2, dirname } from "path";
|
|
18
|
-
import { readFileSync, existsSync } from "fs";
|
|
19
|
-
import { join as
|
|
20
|
-
import { readFileSync as
|
|
21
|
-
import { join as
|
|
22
|
-
import { homedir } from "os";
|
|
23
|
-
import { readFileSync as
|
|
24
|
-
import { join as
|
|
25
|
-
import { homedir as
|
|
31
|
+
import { createWriteStream, mkdirSync as mkdirSync3 } from "fs";
|
|
32
|
+
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
33
|
+
import { readFileSync as readFileSync2, existsSync } from "fs";
|
|
34
|
+
import { join as join4 } from "path";
|
|
35
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync2 } from "fs";
|
|
36
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
37
|
+
import { homedir as homedir3 } from "os";
|
|
38
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync3 } from "fs";
|
|
39
|
+
import { join as join6, dirname as dirname4 } from "path";
|
|
40
|
+
import { homedir as homedir4 } from "os";
|
|
26
41
|
|
|
27
42
|
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
|
|
28
43
|
function getLineColFromPtr(string, ptr) {
|
|
@@ -851,9 +866,9 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
|
|
|
851
866
|
}
|
|
852
867
|
|
|
853
868
|
// ../core/dist/index.js
|
|
854
|
-
import { readFileSync as
|
|
855
|
-
import { join as
|
|
856
|
-
import { homedir as
|
|
869
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync4 } from "fs";
|
|
870
|
+
import { join as join7 } from "path";
|
|
871
|
+
import { homedir as homedir5 } from "os";
|
|
857
872
|
import fs2 from "fs";
|
|
858
873
|
import path2 from "path";
|
|
859
874
|
import os2 from "os";
|
|
@@ -861,6 +876,290 @@ import * as fs3 from "fs";
|
|
|
861
876
|
import * as path3 from "path";
|
|
862
877
|
import * as os3 from "os";
|
|
863
878
|
import { execFileSync } from "child_process";
|
|
879
|
+
var EXEC_TIMEOUT_MS = 3e4;
|
|
880
|
+
var ALLOWED_DOMAIN_RE = /^[\w.-]+\.okx\.com$/;
|
|
881
|
+
var DOH_BIN_DIR = join(homedir(), ".okx", "bin");
|
|
882
|
+
function getDohBinaryPath() {
|
|
883
|
+
if (process.env.OKX_DOH_BINARY_PATH) {
|
|
884
|
+
return process.env.OKX_DOH_BINARY_PATH;
|
|
885
|
+
}
|
|
886
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
887
|
+
return join(DOH_BIN_DIR, `okx-doh-resolver${ext}`);
|
|
888
|
+
}
|
|
889
|
+
function execDohBinary(domain, exclude = [], userAgent) {
|
|
890
|
+
if (!ALLOWED_DOMAIN_RE.test(domain)) {
|
|
891
|
+
return Promise.resolve(null);
|
|
892
|
+
}
|
|
893
|
+
const binPath = getDohBinaryPath();
|
|
894
|
+
const args = ["--domain", domain];
|
|
895
|
+
if (exclude.length > 0) {
|
|
896
|
+
args.push("--exclude", exclude.join(","));
|
|
897
|
+
}
|
|
898
|
+
if (userAgent) {
|
|
899
|
+
args.push("--user-agent", userAgent);
|
|
900
|
+
}
|
|
901
|
+
return new Promise((resolve3) => {
|
|
902
|
+
execFile(
|
|
903
|
+
binPath,
|
|
904
|
+
args,
|
|
905
|
+
{ timeout: EXEC_TIMEOUT_MS, encoding: "utf-8" },
|
|
906
|
+
(error, stdout) => {
|
|
907
|
+
if (error) {
|
|
908
|
+
resolve3(null);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
const result = JSON.parse(stdout);
|
|
913
|
+
if (result.code === 0 && result.data) {
|
|
914
|
+
resolve3(result.data);
|
|
915
|
+
} else {
|
|
916
|
+
resolve3(null);
|
|
917
|
+
}
|
|
918
|
+
} catch {
|
|
919
|
+
resolve3(null);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
);
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
function getDefaultCachePath() {
|
|
926
|
+
return process.env.OKX_DOH_CACHE_PATH || join2(homedir2(), ".okx", "doh-cache.json");
|
|
927
|
+
}
|
|
928
|
+
function readCache(hostname, cachePath = getDefaultCachePath()) {
|
|
929
|
+
try {
|
|
930
|
+
const raw = readFileSync(cachePath, "utf-8");
|
|
931
|
+
const file = JSON.parse(raw);
|
|
932
|
+
return file[hostname] ?? null;
|
|
933
|
+
} catch {
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function writeCache(hostname, entry, cachePath = getDefaultCachePath()) {
|
|
938
|
+
try {
|
|
939
|
+
const dir = dirname(cachePath);
|
|
940
|
+
mkdirSync(dir, { recursive: true });
|
|
941
|
+
let file = {};
|
|
942
|
+
try {
|
|
943
|
+
file = JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
944
|
+
} catch {
|
|
945
|
+
}
|
|
946
|
+
file[hostname] = entry;
|
|
947
|
+
const tmpPath = `${cachePath}.tmp`;
|
|
948
|
+
writeFileSync(tmpPath, JSON.stringify(file));
|
|
949
|
+
renameSync(tmpPath, cachePath);
|
|
950
|
+
} catch {
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
var FAILED_NODE_TTL_MS = 60 * 60 * 1e3;
|
|
954
|
+
function classifyAndCache(node, hostname, failedNodes, cachePath) {
|
|
955
|
+
if (!node) {
|
|
956
|
+
return { mode: null, node: null };
|
|
957
|
+
}
|
|
958
|
+
if (node.ip === hostname || node.host === hostname) {
|
|
959
|
+
writeCache(hostname, {
|
|
960
|
+
mode: "direct",
|
|
961
|
+
node: null,
|
|
962
|
+
failedNodes,
|
|
963
|
+
updatedAt: Date.now()
|
|
964
|
+
}, cachePath);
|
|
965
|
+
return { mode: "direct", node: null };
|
|
966
|
+
}
|
|
967
|
+
writeCache(hostname, {
|
|
968
|
+
mode: "proxy",
|
|
969
|
+
node,
|
|
970
|
+
failedNodes,
|
|
971
|
+
updatedAt: Date.now()
|
|
972
|
+
}, cachePath);
|
|
973
|
+
return { mode: "proxy", node };
|
|
974
|
+
}
|
|
975
|
+
function getActiveFailedNodes(nodes) {
|
|
976
|
+
if (!nodes || nodes.length === 0) return [];
|
|
977
|
+
const now = Date.now();
|
|
978
|
+
return nodes.filter((n) => now - n.failedAt < FAILED_NODE_TTL_MS);
|
|
979
|
+
}
|
|
980
|
+
function resolveDoh(hostname, cachePath) {
|
|
981
|
+
const entry = readCache(hostname, cachePath);
|
|
982
|
+
if (entry) {
|
|
983
|
+
if (entry.mode === "direct") {
|
|
984
|
+
return { mode: "direct", node: null };
|
|
985
|
+
}
|
|
986
|
+
if (entry.mode === "proxy" && entry.node) {
|
|
987
|
+
return { mode: "proxy", node: entry.node };
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return { mode: null, node: null };
|
|
991
|
+
}
|
|
992
|
+
async function reResolveDoh(hostname, failedIp, userAgent, cachePath) {
|
|
993
|
+
const entry = readCache(hostname, cachePath);
|
|
994
|
+
const active = getActiveFailedNodes(entry?.failedNodes);
|
|
995
|
+
const now = Date.now();
|
|
996
|
+
const alreadyFailed = failedIp && active.some((n) => n.ip === failedIp);
|
|
997
|
+
const failedNodes = failedIp && !alreadyFailed ? [...active, { ip: failedIp, failedAt: now }] : active;
|
|
998
|
+
const excludeIps = failedNodes.map((n) => n.ip);
|
|
999
|
+
const node = await execDohBinary(hostname, excludeIps, userAgent);
|
|
1000
|
+
return classifyAndCache(node, hostname, failedNodes, cachePath);
|
|
1001
|
+
}
|
|
1002
|
+
function vlog(message) {
|
|
1003
|
+
process.stderr.write(`[verbose] ${message}
|
|
1004
|
+
`);
|
|
1005
|
+
}
|
|
1006
|
+
var DohManager = class {
|
|
1007
|
+
opts;
|
|
1008
|
+
// DoH proxy state (lazy-resolved on first request)
|
|
1009
|
+
dohResolved = false;
|
|
1010
|
+
dohRetried = false;
|
|
1011
|
+
directUnverified = false;
|
|
1012
|
+
// The first direct connection has not yet been verified
|
|
1013
|
+
dohNode = null;
|
|
1014
|
+
dohAgent = null;
|
|
1015
|
+
dohBaseUrl = null;
|
|
1016
|
+
constructor(opts) {
|
|
1017
|
+
this.opts = opts;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Lazily resolve the DoH proxy node on the first request.
|
|
1021
|
+
* Uses cache-first strategy via the resolver.
|
|
1022
|
+
*/
|
|
1023
|
+
prepareDoh() {
|
|
1024
|
+
if (this.dohResolved || this.opts.hasCustomProxy) return;
|
|
1025
|
+
this.dohResolved = true;
|
|
1026
|
+
try {
|
|
1027
|
+
const { hostname, protocol } = new URL(this.opts.baseUrl);
|
|
1028
|
+
const result = resolveDoh(hostname);
|
|
1029
|
+
if (!result.mode) {
|
|
1030
|
+
this.directUnverified = true;
|
|
1031
|
+
if (this.opts.verbose) {
|
|
1032
|
+
vlog("DoH: no cache, trying direct connection first");
|
|
1033
|
+
}
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
if (result.mode === "direct") {
|
|
1037
|
+
if (this.opts.verbose) {
|
|
1038
|
+
vlog("DoH: mode=direct (overseas or cached), using direct connection");
|
|
1039
|
+
}
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (result.node) {
|
|
1043
|
+
this.applyNode(result.node, protocol);
|
|
1044
|
+
}
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
if (this.opts.verbose) {
|
|
1047
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
1048
|
+
vlog(`DoH resolution failed, falling back to direct: ${cause}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
/** Get connection parameters for the current request. */
|
|
1053
|
+
getConnectionParams() {
|
|
1054
|
+
const baseUrl = this.dohNode ? this.dohBaseUrl : this.opts.baseUrl;
|
|
1055
|
+
const result = { baseUrl };
|
|
1056
|
+
if (this.dohAgent) {
|
|
1057
|
+
result.dispatcher = this.dohAgent;
|
|
1058
|
+
}
|
|
1059
|
+
if (this.dohNode) {
|
|
1060
|
+
result.userAgent = this.dohUserAgent;
|
|
1061
|
+
}
|
|
1062
|
+
return result;
|
|
1063
|
+
}
|
|
1064
|
+
/** Whether a DoH proxy node is currently active. */
|
|
1065
|
+
get isProxyActive() {
|
|
1066
|
+
return this.dohNode !== null;
|
|
1067
|
+
}
|
|
1068
|
+
/** Whether we have already retried after network failure. */
|
|
1069
|
+
get hasRetried() {
|
|
1070
|
+
return this.dohRetried;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Handle network failure: re-resolve with --exclude and retry once.
|
|
1074
|
+
* Returns true if retry should proceed, false if already retried.
|
|
1075
|
+
*/
|
|
1076
|
+
async handleNetworkFailure() {
|
|
1077
|
+
if (this.dohRetried) return false;
|
|
1078
|
+
this.dohRetried = true;
|
|
1079
|
+
const failedIp = this.dohNode?.ip ?? "";
|
|
1080
|
+
const { hostname, protocol } = new URL(this.opts.baseUrl);
|
|
1081
|
+
this.dohNode = null;
|
|
1082
|
+
this.dohAgent = null;
|
|
1083
|
+
this.dohBaseUrl = null;
|
|
1084
|
+
if (!failedIp) this.directUnverified = false;
|
|
1085
|
+
if (this.opts.verbose) {
|
|
1086
|
+
vlog(failedIp ? `DoH: proxy node ${failedIp} failed, re-resolving with --exclude` : "DoH: direct connection failed, calling binary for DoH resolution");
|
|
1087
|
+
}
|
|
1088
|
+
try {
|
|
1089
|
+
const result = await reResolveDoh(hostname, failedIp, this.dohUserAgent);
|
|
1090
|
+
if (result.mode === "proxy" && result.node) {
|
|
1091
|
+
this.applyNode(result.node, protocol);
|
|
1092
|
+
this.dohRetried = false;
|
|
1093
|
+
return true;
|
|
1094
|
+
}
|
|
1095
|
+
} catch {
|
|
1096
|
+
}
|
|
1097
|
+
if (this.opts.verbose) {
|
|
1098
|
+
vlog("DoH: re-resolution failed or switched to direct, retrying with direct connection");
|
|
1099
|
+
}
|
|
1100
|
+
return true;
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* After a successful HTTP response on direct connection, cache mode=direct.
|
|
1104
|
+
* (Even if the business response is an error, the network path is valid.)
|
|
1105
|
+
*/
|
|
1106
|
+
cacheDirectIfNeeded() {
|
|
1107
|
+
if (!this.directUnverified || this.dohNode) return;
|
|
1108
|
+
this.directUnverified = false;
|
|
1109
|
+
const { hostname } = new URL(this.opts.baseUrl);
|
|
1110
|
+
writeCache(hostname, {
|
|
1111
|
+
mode: "direct",
|
|
1112
|
+
node: null,
|
|
1113
|
+
failedNodes: [],
|
|
1114
|
+
updatedAt: Date.now()
|
|
1115
|
+
});
|
|
1116
|
+
if (this.opts.verbose) {
|
|
1117
|
+
vlog("DoH: direct connection succeeded, cached mode=direct");
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
/** User-Agent for DoH proxy requests: OKX/@okx_ai/{packageName}/{version} */
|
|
1121
|
+
get dohUserAgent() {
|
|
1122
|
+
return `OKX/@okx_ai/${this.opts.packageUserAgent ?? "unknown"}`;
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Apply a DoH node: set up the custom Agent + base URL.
|
|
1126
|
+
*
|
|
1127
|
+
* node.ip may be a real IP or a domain (CNAME like *.aliyunddos1021.com).
|
|
1128
|
+
* - Real IP → use directly in lookup callback
|
|
1129
|
+
* - Domain → dns.lookup on every connection to get a fresh IP
|
|
1130
|
+
*/
|
|
1131
|
+
applyNode(node, protocol) {
|
|
1132
|
+
this.dohNode = node;
|
|
1133
|
+
this.dohBaseUrl = `${protocol}//${node.host}`;
|
|
1134
|
+
const nodeIpIsRealIp = !!isIP(node.ip);
|
|
1135
|
+
this.dohAgent = new Agent({
|
|
1136
|
+
connect: {
|
|
1137
|
+
lookup: (_hostname, options, callback) => {
|
|
1138
|
+
if (nodeIpIsRealIp) {
|
|
1139
|
+
if (options?.all) {
|
|
1140
|
+
callback(null, [{ address: node.ip, family: 4 }]);
|
|
1141
|
+
} else {
|
|
1142
|
+
callback(null, node.ip, 4);
|
|
1143
|
+
}
|
|
1144
|
+
} else {
|
|
1145
|
+
dnsLookup(node.ip, { family: 4 }, (err, address, family) => {
|
|
1146
|
+
if (err) {
|
|
1147
|
+
callback(err, "", 0);
|
|
1148
|
+
} else if (options?.all) {
|
|
1149
|
+
callback(null, [{ address, family }]);
|
|
1150
|
+
} else {
|
|
1151
|
+
callback(null, address, family);
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
if (this.opts.verbose) {
|
|
1159
|
+
vlog(`DoH proxy active: \u2192 ${node.host} (${node.ip}), ttl=${node.ttl}s`);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
864
1163
|
function getNow() {
|
|
865
1164
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
866
1165
|
}
|
|
@@ -1071,7 +1370,7 @@ function maskKey(key) {
|
|
|
1071
1370
|
if (key.length <= 8) return "***";
|
|
1072
1371
|
return `${key.slice(0, 3)}***${key.slice(-3)}`;
|
|
1073
1372
|
}
|
|
1074
|
-
function
|
|
1373
|
+
function vlog2(message) {
|
|
1075
1374
|
process.stderr.write(`[verbose] ${message}
|
|
1076
1375
|
`);
|
|
1077
1376
|
}
|
|
@@ -1079,25 +1378,32 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1079
1378
|
config;
|
|
1080
1379
|
rateLimiter;
|
|
1081
1380
|
dispatcher;
|
|
1381
|
+
doh;
|
|
1082
1382
|
constructor(config) {
|
|
1083
1383
|
this.config = config;
|
|
1084
1384
|
this.rateLimiter = new RateLimiter(3e4, config.verbose);
|
|
1085
1385
|
if (config.proxyUrl) {
|
|
1086
1386
|
this.dispatcher = new ProxyAgent(config.proxyUrl);
|
|
1087
1387
|
}
|
|
1388
|
+
this.doh = new DohManager({
|
|
1389
|
+
baseUrl: config.baseUrl,
|
|
1390
|
+
packageUserAgent: config.userAgent,
|
|
1391
|
+
verbose: config.verbose,
|
|
1392
|
+
hasCustomProxy: !!config.proxyUrl
|
|
1393
|
+
});
|
|
1088
1394
|
}
|
|
1089
1395
|
logRequest(method, url, auth) {
|
|
1090
1396
|
if (!this.config.verbose) return;
|
|
1091
|
-
|
|
1397
|
+
vlog2(`\u2192 ${method} ${url}`);
|
|
1092
1398
|
const authInfo = auth === "private" && this.config.apiKey ? `auth=\u2713(${maskKey(this.config.apiKey)})` : `auth=${auth}`;
|
|
1093
|
-
|
|
1399
|
+
vlog2(` ${authInfo} demo=${this.config.demo} timeout=${this.config.timeoutMs}ms`);
|
|
1094
1400
|
}
|
|
1095
1401
|
logResponse(status, rawLen, elapsed, traceId, code, msg) {
|
|
1096
1402
|
if (!this.config.verbose) return;
|
|
1097
1403
|
if (code && code !== "0" && code !== "1") {
|
|
1098
|
-
|
|
1404
|
+
vlog2(`\u2717 ${status} | code=${code} | msg=${msg ?? "-"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
|
|
1099
1405
|
} else {
|
|
1100
|
-
|
|
1406
|
+
vlog2(`\u2190 ${status} | code=${code ?? "0"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
|
|
1101
1407
|
}
|
|
1102
1408
|
}
|
|
1103
1409
|
async publicGet(path42, query, rateLimit, simulatedTrading) {
|
|
@@ -1256,17 +1562,30 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1256
1562
|
* Security: validates Content-Type and enforces maxBytes limit.
|
|
1257
1563
|
*/
|
|
1258
1564
|
async privatePostBinary(path42, body, opts) {
|
|
1565
|
+
this.doh.prepareDoh();
|
|
1259
1566
|
const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
|
|
1260
1567
|
const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
|
|
1261
1568
|
const bodyJson = body ? JSON.stringify(body) : "";
|
|
1262
1569
|
const endpoint = `POST ${path42}`;
|
|
1263
|
-
|
|
1570
|
+
const conn = this.doh.getConnectionParams();
|
|
1571
|
+
this.logRequest("POST", `${conn.baseUrl}${path42}`, "private");
|
|
1264
1572
|
const reqConfig = { method: "POST", path: path42, auth: "private" };
|
|
1265
1573
|
const headers = this.buildHeaders(reqConfig, path42, bodyJson, getNow());
|
|
1574
|
+
if (conn.userAgent) {
|
|
1575
|
+
headers.set("User-Agent", conn.userAgent);
|
|
1576
|
+
}
|
|
1266
1577
|
const t0 = Date.now();
|
|
1267
|
-
|
|
1578
|
+
let response;
|
|
1579
|
+
try {
|
|
1580
|
+
response = await this.fetchBinary(path42, endpoint, headers, bodyJson, t0);
|
|
1581
|
+
} catch (error) {
|
|
1582
|
+
this.doh.handleNetworkFailure().catch(() => {
|
|
1583
|
+
});
|
|
1584
|
+
throw error;
|
|
1585
|
+
}
|
|
1268
1586
|
const elapsed = Date.now() - t0;
|
|
1269
1587
|
const traceId = extractTraceId(response.headers);
|
|
1588
|
+
this.doh.cacheDirectIfNeeded();
|
|
1270
1589
|
if (!response.ok) {
|
|
1271
1590
|
const text = await response.text();
|
|
1272
1591
|
this.logResponse(response.status, text.length, elapsed, traceId, String(response.status));
|
|
@@ -1285,24 +1604,25 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1285
1604
|
throw new OkxApiError(`Response size ${buffer.length} bytes exceeds limit of ${maxBytes} bytes.`, { code: "RESPONSE_TOO_LARGE", endpoint, traceId });
|
|
1286
1605
|
}
|
|
1287
1606
|
if (this.config.verbose) {
|
|
1288
|
-
|
|
1607
|
+
vlog2(`\u2190 ${response.status} | binary ${buffer.length}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
|
|
1289
1608
|
}
|
|
1290
1609
|
return { endpoint, requestTime: (/* @__PURE__ */ new Date()).toISOString(), data: buffer, contentType: ct, contentLength: buffer.length, traceId };
|
|
1291
1610
|
}
|
|
1292
1611
|
/** Execute fetch for binary endpoint, wrapping network errors. */
|
|
1293
1612
|
async fetchBinary(path42, endpoint, headers, bodyJson, t0) {
|
|
1613
|
+
const conn = this.doh.getConnectionParams();
|
|
1294
1614
|
try {
|
|
1295
1615
|
const fetchOptions = {
|
|
1296
1616
|
method: "POST",
|
|
1297
1617
|
headers,
|
|
1298
1618
|
body: bodyJson || void 0,
|
|
1299
|
-
signal: AbortSignal.timeout(this.config.timeoutMs)
|
|
1619
|
+
signal: AbortSignal.timeout(this.config.timeoutMs),
|
|
1620
|
+
dispatcher: this.dispatcher ?? conn.dispatcher
|
|
1300
1621
|
};
|
|
1301
|
-
|
|
1302
|
-
return await fetch(`${this.config.baseUrl}${path42}`, fetchOptions);
|
|
1622
|
+
return await fetch(`${conn.baseUrl}${path42}`, fetchOptions);
|
|
1303
1623
|
} catch (error) {
|
|
1304
1624
|
if (this.config.verbose) {
|
|
1305
|
-
|
|
1625
|
+
vlog2(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
|
|
1306
1626
|
}
|
|
1307
1627
|
throw new NetworkError(`Failed to call OKX endpoint ${endpoint}.`, endpoint, error);
|
|
1308
1628
|
}
|
|
@@ -1330,10 +1650,38 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1330
1650
|
// ---------------------------------------------------------------------------
|
|
1331
1651
|
// JSON request
|
|
1332
1652
|
// ---------------------------------------------------------------------------
|
|
1653
|
+
/**
|
|
1654
|
+
* Handle network error during a JSON request: refresh DoH and maybe retry.
|
|
1655
|
+
* Always either returns a retry result or throws NetworkError.
|
|
1656
|
+
*/
|
|
1657
|
+
async handleRequestNetworkError(error, reqConfig, requestPath, t0) {
|
|
1658
|
+
if (!this.doh.hasRetried) {
|
|
1659
|
+
if (this.config.verbose) {
|
|
1660
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
1661
|
+
vlog2(`Network failure, refreshing DoH: ${cause}`);
|
|
1662
|
+
}
|
|
1663
|
+
const shouldRetry = await this.doh.handleNetworkFailure();
|
|
1664
|
+
if (shouldRetry && reqConfig.method === "GET") {
|
|
1665
|
+
return this.request(reqConfig);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
if (this.config.verbose) {
|
|
1669
|
+
const elapsed = Date.now() - t0;
|
|
1670
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
1671
|
+
vlog2(`\u2717 NetworkError after ${elapsed}ms: ${cause}`);
|
|
1672
|
+
}
|
|
1673
|
+
throw new NetworkError(
|
|
1674
|
+
`Failed to call OKX endpoint ${reqConfig.method} ${requestPath}.`,
|
|
1675
|
+
`${reqConfig.method} ${requestPath}`,
|
|
1676
|
+
error
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1333
1679
|
async request(reqConfig) {
|
|
1680
|
+
this.doh.prepareDoh();
|
|
1334
1681
|
const queryString = buildQueryString(reqConfig.query);
|
|
1335
1682
|
const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
|
|
1336
|
-
const
|
|
1683
|
+
const conn = this.doh.getConnectionParams();
|
|
1684
|
+
const url = `${conn.baseUrl}${requestPath}`;
|
|
1337
1685
|
const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
|
|
1338
1686
|
const timestamp = getNow();
|
|
1339
1687
|
this.logRequest(reqConfig.method, url, reqConfig.auth);
|
|
@@ -1341,6 +1689,9 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1341
1689
|
await this.rateLimiter.consume(reqConfig.rateLimit);
|
|
1342
1690
|
}
|
|
1343
1691
|
const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
|
|
1692
|
+
if (conn.userAgent) {
|
|
1693
|
+
headers.set("User-Agent", conn.userAgent);
|
|
1694
|
+
}
|
|
1344
1695
|
const t0 = Date.now();
|
|
1345
1696
|
let response;
|
|
1346
1697
|
try {
|
|
@@ -1348,27 +1699,17 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1348
1699
|
method: reqConfig.method,
|
|
1349
1700
|
headers,
|
|
1350
1701
|
body: reqConfig.method === "POST" ? bodyJson : void 0,
|
|
1351
|
-
signal: AbortSignal.timeout(this.config.timeoutMs)
|
|
1702
|
+
signal: AbortSignal.timeout(this.config.timeoutMs),
|
|
1703
|
+
dispatcher: this.dispatcher ?? conn.dispatcher
|
|
1352
1704
|
};
|
|
1353
|
-
if (this.dispatcher) {
|
|
1354
|
-
fetchOptions.dispatcher = this.dispatcher;
|
|
1355
|
-
}
|
|
1356
1705
|
response = await fetch(url, fetchOptions);
|
|
1357
1706
|
} catch (error) {
|
|
1358
|
-
|
|
1359
|
-
const elapsed2 = Date.now() - t0;
|
|
1360
|
-
const cause = error instanceof Error ? error.message : String(error);
|
|
1361
|
-
vlog(`\u2717 NetworkError after ${elapsed2}ms: ${cause}`);
|
|
1362
|
-
}
|
|
1363
|
-
throw new NetworkError(
|
|
1364
|
-
`Failed to call OKX endpoint ${reqConfig.method} ${requestPath}.`,
|
|
1365
|
-
`${reqConfig.method} ${requestPath}`,
|
|
1366
|
-
error
|
|
1367
|
-
);
|
|
1707
|
+
return await this.handleRequestNetworkError(error, reqConfig, requestPath, t0);
|
|
1368
1708
|
}
|
|
1369
1709
|
const rawText = await response.text();
|
|
1370
1710
|
const elapsed = Date.now() - t0;
|
|
1371
1711
|
const traceId = extractTraceId(response.headers);
|
|
1712
|
+
this.doh.cacheDirectIfNeeded();
|
|
1372
1713
|
return this.processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath);
|
|
1373
1714
|
}
|
|
1374
1715
|
};
|
|
@@ -1393,9 +1734,6 @@ function readNumber(args, key) {
|
|
|
1393
1734
|
if (value === void 0 || value === null) {
|
|
1394
1735
|
return void 0;
|
|
1395
1736
|
}
|
|
1396
|
-
if (typeof value === "string" && /^-?\d+(\.\d+)?$/.test(value)) {
|
|
1397
|
-
return parseFloat(value);
|
|
1398
|
-
}
|
|
1399
1737
|
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
1400
1738
|
throw new ValidationError(`Parameter "${key}" must be a number.`);
|
|
1401
1739
|
}
|
|
@@ -1475,8 +1813,7 @@ var OKX_INST_TYPES = [
|
|
|
1475
1813
|
"SWAP",
|
|
1476
1814
|
"FUTURES",
|
|
1477
1815
|
"OPTION",
|
|
1478
|
-
"MARGIN"
|
|
1479
|
-
"EVENTS"
|
|
1816
|
+
"MARGIN"
|
|
1480
1817
|
];
|
|
1481
1818
|
function publicRateLimit(key, rps = 20) {
|
|
1482
1819
|
return {
|
|
@@ -1774,7 +2111,6 @@ var MODULES = [
|
|
|
1774
2111
|
"futures",
|
|
1775
2112
|
"option",
|
|
1776
2113
|
"account",
|
|
1777
|
-
"event",
|
|
1778
2114
|
...EARN_SUB_MODULE_IDS,
|
|
1779
2115
|
...BOT_SUB_MODULE_IDS,
|
|
1780
2116
|
"skills"
|
|
@@ -1968,7 +2304,7 @@ function registerAccountTools() {
|
|
|
1968
2304
|
properties: {
|
|
1969
2305
|
instType: {
|
|
1970
2306
|
type: "string",
|
|
1971
|
-
enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"
|
|
2307
|
+
enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"]
|
|
1972
2308
|
},
|
|
1973
2309
|
ccy: {
|
|
1974
2310
|
type: "string",
|
|
@@ -2034,7 +2370,7 @@ function registerAccountTools() {
|
|
|
2034
2370
|
properties: {
|
|
2035
2371
|
instType: {
|
|
2036
2372
|
type: "string",
|
|
2037
|
-
enum: ["SWAP", "FUTURES", "MARGIN", "OPTION"
|
|
2373
|
+
enum: ["SWAP", "FUTURES", "MARGIN", "OPTION"],
|
|
2038
2374
|
description: "Default SWAP"
|
|
2039
2375
|
},
|
|
2040
2376
|
instId: {
|
|
@@ -2095,7 +2431,7 @@ function registerAccountTools() {
|
|
|
2095
2431
|
properties: {
|
|
2096
2432
|
instType: {
|
|
2097
2433
|
type: "string",
|
|
2098
|
-
enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"
|
|
2434
|
+
enum: ["SPOT", "MARGIN", "SWAP", "FUTURES", "OPTION"]
|
|
2099
2435
|
},
|
|
2100
2436
|
instId: {
|
|
2101
2437
|
type: "string",
|
|
@@ -2206,14 +2542,14 @@ function registerAccountTools() {
|
|
|
2206
2542
|
{
|
|
2207
2543
|
name: "account_get_positions",
|
|
2208
2544
|
module: "account",
|
|
2209
|
-
description: "Get current open positions across all instrument types (MARGIN, SWAP, FUTURES, OPTION
|
|
2545
|
+
description: "Get current open positions across all instrument types (MARGIN, SWAP, FUTURES, OPTION). Use swap_get_positions for SWAP/FUTURES-only queries.",
|
|
2210
2546
|
isWrite: false,
|
|
2211
2547
|
inputSchema: {
|
|
2212
2548
|
type: "object",
|
|
2213
2549
|
properties: {
|
|
2214
2550
|
instType: {
|
|
2215
2551
|
type: "string",
|
|
2216
|
-
enum: ["MARGIN", "SWAP", "FUTURES", "OPTION"
|
|
2552
|
+
enum: ["MARGIN", "SWAP", "FUTURES", "OPTION"]
|
|
2217
2553
|
},
|
|
2218
2554
|
instId: {
|
|
2219
2555
|
type: "string",
|
|
@@ -3252,19 +3588,19 @@ function safeWriteFile(targetDir, fileName, data) {
|
|
|
3252
3588
|
throw new Error(`Invalid file name: "${fileName}"`);
|
|
3253
3589
|
}
|
|
3254
3590
|
const resolvedDir = resolve(targetDir);
|
|
3255
|
-
const filePath =
|
|
3591
|
+
const filePath = join3(resolvedDir, safeName);
|
|
3256
3592
|
const resolvedPath = resolve(filePath);
|
|
3257
3593
|
if (!resolvedPath.startsWith(resolvedDir + sep)) {
|
|
3258
3594
|
throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
|
|
3259
3595
|
}
|
|
3260
|
-
|
|
3596
|
+
mkdirSync2(resolvedDir, { recursive: true });
|
|
3261
3597
|
const tmpPath = `${resolvedPath}.${randomUUID()}.tmp`;
|
|
3262
3598
|
try {
|
|
3263
|
-
|
|
3264
|
-
|
|
3599
|
+
writeFileSync2(tmpPath, data);
|
|
3600
|
+
renameSync2(tmpPath, resolvedPath);
|
|
3265
3601
|
} catch (err) {
|
|
3266
3602
|
try {
|
|
3267
|
-
|
|
3603
|
+
unlinkSync2(tmpPath);
|
|
3268
3604
|
} catch {
|
|
3269
3605
|
}
|
|
3270
3606
|
throw err;
|
|
@@ -3330,7 +3666,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
|
|
|
3330
3666
|
const maxFiles = limits?.maxFiles ?? DEFAULT_MAX_FILES;
|
|
3331
3667
|
const maxCompressionRatio = limits?.maxCompressionRatio ?? DEFAULT_MAX_COMPRESSION_RATIO;
|
|
3332
3668
|
const resolvedTarget = resolve2(targetDir);
|
|
3333
|
-
|
|
3669
|
+
mkdirSync3(resolvedTarget, { recursive: true });
|
|
3334
3670
|
return new Promise((resolvePromise, reject) => {
|
|
3335
3671
|
yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
|
|
3336
3672
|
if (err) return reject(err);
|
|
@@ -3353,7 +3689,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
|
|
|
3353
3689
|
zipfile.close();
|
|
3354
3690
|
return reject(streamErr);
|
|
3355
3691
|
}
|
|
3356
|
-
|
|
3692
|
+
mkdirSync3(dirname2(resolvedPath), { recursive: true });
|
|
3357
3693
|
const writeStream = createWriteStream(resolvedPath);
|
|
3358
3694
|
readStream.pipe(writeStream);
|
|
3359
3695
|
writeStream.on("close", () => zipfile.readEntry());
|
|
@@ -3369,11 +3705,11 @@ async function extractSkillZip(zipPath, targetDir, limits) {
|
|
|
3369
3705
|
});
|
|
3370
3706
|
}
|
|
3371
3707
|
function readMetaJson(contentDir) {
|
|
3372
|
-
const metaPath =
|
|
3708
|
+
const metaPath = join4(contentDir, "_meta.json");
|
|
3373
3709
|
if (!existsSync(metaPath)) {
|
|
3374
3710
|
throw new Error(`_meta.json not found in ${contentDir}. Invalid skill package.`);
|
|
3375
3711
|
}
|
|
3376
|
-
const raw =
|
|
3712
|
+
const raw = readFileSync2(metaPath, "utf-8");
|
|
3377
3713
|
let parsed;
|
|
3378
3714
|
try {
|
|
3379
3715
|
parsed = JSON.parse(raw);
|
|
@@ -3395,26 +3731,26 @@ function readMetaJson(contentDir) {
|
|
|
3395
3731
|
};
|
|
3396
3732
|
}
|
|
3397
3733
|
function validateSkillMdExists(contentDir) {
|
|
3398
|
-
const skillMdPath =
|
|
3734
|
+
const skillMdPath = join4(contentDir, "SKILL.md");
|
|
3399
3735
|
if (!existsSync(skillMdPath)) {
|
|
3400
3736
|
throw new Error(`SKILL.md not found in ${contentDir}. Invalid skill package.`);
|
|
3401
3737
|
}
|
|
3402
3738
|
}
|
|
3403
|
-
var DEFAULT_REGISTRY_PATH =
|
|
3739
|
+
var DEFAULT_REGISTRY_PATH = join5(homedir3(), ".okx", "skills", "registry.json");
|
|
3404
3740
|
function readRegistry(registryPath = DEFAULT_REGISTRY_PATH) {
|
|
3405
3741
|
if (!existsSync2(registryPath)) {
|
|
3406
3742
|
return { version: 1, skills: {} };
|
|
3407
3743
|
}
|
|
3408
3744
|
try {
|
|
3409
|
-
const raw =
|
|
3745
|
+
const raw = readFileSync3(registryPath, "utf-8");
|
|
3410
3746
|
return JSON.parse(raw);
|
|
3411
3747
|
} catch {
|
|
3412
3748
|
return { version: 1, skills: {} };
|
|
3413
3749
|
}
|
|
3414
3750
|
}
|
|
3415
3751
|
function writeRegistry(registry, registryPath = DEFAULT_REGISTRY_PATH) {
|
|
3416
|
-
|
|
3417
|
-
|
|
3752
|
+
mkdirSync4(dirname3(registryPath), { recursive: true });
|
|
3753
|
+
writeFileSync3(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
|
|
3418
3754
|
}
|
|
3419
3755
|
function upsertSkillRecord(meta, registryPath = DEFAULT_REGISTRY_PATH) {
|
|
3420
3756
|
const registry = readRegistry(registryPath);
|
|
@@ -5049,838 +5385,79 @@ function registerDcdTools() {
|
|
|
5049
5385
|
termRate: newQuote["termRate"]
|
|
5050
5386
|
}
|
|
5051
5387
|
};
|
|
5052
|
-
}
|
|
5053
|
-
throw error;
|
|
5054
|
-
}
|
|
5055
|
-
});
|
|
5056
|
-
}
|
|
5057
|
-
}
|
|
5058
|
-
];
|
|
5059
|
-
}
|
|
5060
|
-
function registerAutoEarnTools() {
|
|
5061
|
-
return [
|
|
5062
|
-
{
|
|
5063
|
-
name: "earn_auto_set",
|
|
5064
|
-
module: "earn.autoearn",
|
|
5065
|
-
description: "Enable or disable auto-earn for a currency. earnType='0' for auto-lend+stake (most currencies); earnType='1' for USDG earn (USDG, BUIDL). Use account_get_balance first: if autoLendStatus or autoStakingStatus != 'unsupported', use earnType='0'; for USDG/BUIDL use earnType='1'. [CAUTION] Cannot disable within 24h of enabling.",
|
|
5066
|
-
isWrite: true,
|
|
5067
|
-
inputSchema: {
|
|
5068
|
-
type: "object",
|
|
5069
|
-
properties: {
|
|
5070
|
-
ccy: {
|
|
5071
|
-
type: "string",
|
|
5072
|
-
description: "Currency, e.g. SOL, USDG"
|
|
5073
|
-
},
|
|
5074
|
-
action: {
|
|
5075
|
-
type: "string",
|
|
5076
|
-
description: "turn_on or turn_off"
|
|
5077
|
-
},
|
|
5078
|
-
earnType: {
|
|
5079
|
-
type: "string",
|
|
5080
|
-
description: "0=auto-lend+stake (default), 1=USDG earn. Omit to use default."
|
|
5081
|
-
}
|
|
5082
|
-
},
|
|
5083
|
-
required: ["ccy", "action"]
|
|
5084
|
-
},
|
|
5085
|
-
handler: async (rawArgs, context) => {
|
|
5086
|
-
const args = asRecord(rawArgs);
|
|
5087
|
-
const response = await context.client.privatePost(
|
|
5088
|
-
"/api/v5/account/set-auto-earn",
|
|
5089
|
-
compactObject({
|
|
5090
|
-
ccy: requireString(args, "ccy"),
|
|
5091
|
-
action: requireString(args, "action"),
|
|
5092
|
-
earnType: readString(args, "earnType") ?? "0"
|
|
5093
|
-
}),
|
|
5094
|
-
privateRateLimit("earn_auto_set", 10)
|
|
5095
|
-
);
|
|
5096
|
-
return normalizeResponse(response);
|
|
5097
|
-
}
|
|
5098
|
-
}
|
|
5099
|
-
];
|
|
5100
|
-
}
|
|
5101
|
-
var EARN_DEMO_MESSAGE = "Earn features (savings, DCD, on-chain staking, auto-earn) are not available in simulated trading mode.";
|
|
5102
|
-
var EARN_DEMO_SUGGESTION = "Switch to a live account to use Earn features.";
|
|
5103
|
-
var DEMO_GUARD_SKIP = /* @__PURE__ */ new Set(["dcd_redeem", "earn_fixed_purchase"]);
|
|
5104
|
-
function withDemoGuard(tool) {
|
|
5105
|
-
if (!tool.isWrite || DEMO_GUARD_SKIP.has(tool.name)) return tool;
|
|
5106
|
-
const originalHandler = tool.handler;
|
|
5107
|
-
return {
|
|
5108
|
-
...tool,
|
|
5109
|
-
handler: async (args, context) => {
|
|
5110
|
-
if (context.config.demo) {
|
|
5111
|
-
throw new ConfigError(EARN_DEMO_MESSAGE, EARN_DEMO_SUGGESTION);
|
|
5112
|
-
}
|
|
5113
|
-
return originalHandler(args, context);
|
|
5114
|
-
}
|
|
5115
|
-
};
|
|
5116
|
-
}
|
|
5117
|
-
function registerAllEarnTools() {
|
|
5118
|
-
const tools = [
|
|
5119
|
-
...registerEarnTools(),
|
|
5120
|
-
...registerOnchainEarnTools(),
|
|
5121
|
-
...registerDcdTools(),
|
|
5122
|
-
...registerAutoEarnTools()
|
|
5123
|
-
];
|
|
5124
|
-
return tools.map(withDemoGuard);
|
|
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.privateGet(
|
|
5347
|
-
"/api/v5/public/event-contract/markets",
|
|
5348
|
-
compactObject({ seriesId, state: "live" }),
|
|
5349
|
-
privateRateLimit("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.privateGet(
|
|
5476
|
-
"/api/v5/public/event-contract/series",
|
|
5477
|
-
compactObject({}),
|
|
5478
|
-
privateRateLimit("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.privateGet(
|
|
5510
|
-
"/api/v5/public/event-contract/series",
|
|
5511
|
-
compactObject({ seriesId: readString(args, "seriesId") }),
|
|
5512
|
-
privateRateLimit("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.privateGet(
|
|
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
|
-
privateRateLimit("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.privateGet(
|
|
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
|
-
privateRateLimit("event_get_markets", 20)
|
|
5628
|
-
),
|
|
5629
|
-
knownUnderlying ? Promise.resolve(null) : context.client.privateGet(
|
|
5630
|
-
"/api/v5/public/event-contract/series",
|
|
5631
|
-
compactObject({ seriesId }),
|
|
5632
|
-
privateRateLimit("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."
|
|
5388
|
+
}
|
|
5389
|
+
throw error;
|
|
5770
5390
|
}
|
|
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);
|
|
5391
|
+
});
|
|
5841
5392
|
}
|
|
5842
|
-
}
|
|
5393
|
+
}
|
|
5394
|
+
];
|
|
5395
|
+
}
|
|
5396
|
+
function registerAutoEarnTools() {
|
|
5397
|
+
return [
|
|
5843
5398
|
{
|
|
5844
|
-
name: "
|
|
5845
|
-
module: "
|
|
5846
|
-
description: "
|
|
5399
|
+
name: "earn_auto_set",
|
|
5400
|
+
module: "earn.autoearn",
|
|
5401
|
+
description: "Enable or disable auto-earn for a currency. earnType='0' for auto-lend+stake (most currencies); earnType='1' for USDG earn (USDG, BUIDL). Use account_get_balance first: if autoLendStatus or autoStakingStatus != 'unsupported', use earnType='0'; for USDG/BUIDL use earnType='1'. [CAUTION] Cannot disable within 24h of enabling.",
|
|
5847
5402
|
isWrite: true,
|
|
5848
5403
|
inputSchema: {
|
|
5849
5404
|
type: "object",
|
|
5850
5405
|
properties: {
|
|
5851
|
-
|
|
5406
|
+
ccy: {
|
|
5852
5407
|
type: "string",
|
|
5853
|
-
description: "
|
|
5408
|
+
description: "Currency, e.g. SOL, USDG"
|
|
5854
5409
|
},
|
|
5855
|
-
|
|
5410
|
+
action: {
|
|
5411
|
+
type: "string",
|
|
5412
|
+
description: "turn_on or turn_off"
|
|
5413
|
+
},
|
|
5414
|
+
earnType: {
|
|
5856
5415
|
type: "string",
|
|
5857
|
-
description: "
|
|
5416
|
+
description: "0=auto-lend+stake (default), 1=USDG earn. Omit to use default."
|
|
5858
5417
|
}
|
|
5859
5418
|
},
|
|
5860
|
-
required: ["
|
|
5419
|
+
required: ["ccy", "action"]
|
|
5861
5420
|
},
|
|
5862
5421
|
handler: async (rawArgs, context) => {
|
|
5863
5422
|
const args = asRecord(rawArgs);
|
|
5864
|
-
const instId = requireString(args, "instId");
|
|
5865
5423
|
const response = await context.client.privatePost(
|
|
5866
|
-
"/api/v5/
|
|
5867
|
-
{
|
|
5868
|
-
|
|
5424
|
+
"/api/v5/account/set-auto-earn",
|
|
5425
|
+
compactObject({
|
|
5426
|
+
ccy: requireString(args, "ccy"),
|
|
5427
|
+
action: requireString(args, "action"),
|
|
5428
|
+
earnType: readString(args, "earnType") ?? "0"
|
|
5429
|
+
}),
|
|
5430
|
+
privateRateLimit("earn_auto_set", 10)
|
|
5869
5431
|
);
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5432
|
+
return normalizeResponse(response);
|
|
5433
|
+
}
|
|
5434
|
+
}
|
|
5435
|
+
];
|
|
5436
|
+
}
|
|
5437
|
+
var EARN_DEMO_MESSAGE = "Earn features (savings, DCD, on-chain staking, auto-earn) are not available in simulated trading mode.";
|
|
5438
|
+
var EARN_DEMO_SUGGESTION = "Switch to a live account to use Earn features.";
|
|
5439
|
+
var DEMO_GUARD_SKIP = /* @__PURE__ */ new Set(["dcd_redeem", "earn_fixed_purchase"]);
|
|
5440
|
+
function withDemoGuard(tool) {
|
|
5441
|
+
if (!tool.isWrite || DEMO_GUARD_SKIP.has(tool.name)) return tool;
|
|
5442
|
+
const originalHandler = tool.handler;
|
|
5443
|
+
return {
|
|
5444
|
+
...tool,
|
|
5445
|
+
handler: async (args, context) => {
|
|
5446
|
+
if (context.config.demo) {
|
|
5447
|
+
throw new ConfigError(EARN_DEMO_MESSAGE, EARN_DEMO_SUGGESTION);
|
|
5881
5448
|
}
|
|
5449
|
+
return originalHandler(args, context);
|
|
5882
5450
|
}
|
|
5451
|
+
};
|
|
5452
|
+
}
|
|
5453
|
+
function registerAllEarnTools() {
|
|
5454
|
+
const tools = [
|
|
5455
|
+
...registerEarnTools(),
|
|
5456
|
+
...registerOnchainEarnTools(),
|
|
5457
|
+
...registerDcdTools(),
|
|
5458
|
+
...registerAutoEarnTools()
|
|
5883
5459
|
];
|
|
5460
|
+
return tools.map(withDemoGuard);
|
|
5884
5461
|
}
|
|
5885
5462
|
function buildContractTradeTools(cfg) {
|
|
5886
5463
|
const { prefix, module, label, instTypes, instIdExample } = cfg;
|
|
@@ -8520,7 +8097,6 @@ function allToolSpecs() {
|
|
|
8520
8097
|
...registerOptionAlgoTools(),
|
|
8521
8098
|
...registerAlgoTradeTools(),
|
|
8522
8099
|
...registerAccountTools(),
|
|
8523
|
-
...registerEventContractTools(),
|
|
8524
8100
|
...registerBotTools(),
|
|
8525
8101
|
...registerAllEarnTools(),
|
|
8526
8102
|
...registerAuditTools(),
|
|
@@ -8539,12 +8115,12 @@ function createToolRunner(client, config) {
|
|
|
8539
8115
|
};
|
|
8540
8116
|
}
|
|
8541
8117
|
function configFilePath() {
|
|
8542
|
-
return
|
|
8118
|
+
return join6(homedir4(), ".okx", "config.toml");
|
|
8543
8119
|
}
|
|
8544
8120
|
function readFullConfig() {
|
|
8545
8121
|
const path42 = configFilePath();
|
|
8546
8122
|
if (!existsSync3(path42)) return { profiles: {} };
|
|
8547
|
-
const raw =
|
|
8123
|
+
const raw = readFileSync4(path42, "utf-8");
|
|
8548
8124
|
try {
|
|
8549
8125
|
return parse(raw);
|
|
8550
8126
|
} catch (err) {
|
|
@@ -8572,11 +8148,11 @@ var CONFIG_HEADER = `# OKX Trade Kit Configuration
|
|
|
8572
8148
|
`;
|
|
8573
8149
|
function writeFullConfig(config) {
|
|
8574
8150
|
const path42 = configFilePath();
|
|
8575
|
-
const dir =
|
|
8151
|
+
const dir = dirname4(path42);
|
|
8576
8152
|
if (!existsSync3(dir)) {
|
|
8577
|
-
|
|
8153
|
+
mkdirSync5(dir, { recursive: true });
|
|
8578
8154
|
}
|
|
8579
|
-
|
|
8155
|
+
writeFileSync4(path42, CONFIG_HEADER + stringify(config), "utf-8");
|
|
8580
8156
|
}
|
|
8581
8157
|
function expandShorthand(moduleId) {
|
|
8582
8158
|
if (moduleId === "all") return [...MODULES];
|
|
@@ -8690,21 +8266,21 @@ function loadConfig(cli) {
|
|
|
8690
8266
|
verbose: cli.verbose ?? false
|
|
8691
8267
|
};
|
|
8692
8268
|
}
|
|
8693
|
-
var CACHE_FILE =
|
|
8269
|
+
var CACHE_FILE = join7(homedir5(), ".okx", "update-check.json");
|
|
8694
8270
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
8695
|
-
function
|
|
8271
|
+
function readCache2() {
|
|
8696
8272
|
try {
|
|
8697
8273
|
if (existsSync4(CACHE_FILE)) {
|
|
8698
|
-
return JSON.parse(
|
|
8274
|
+
return JSON.parse(readFileSync5(CACHE_FILE, "utf-8"));
|
|
8699
8275
|
}
|
|
8700
8276
|
} catch {
|
|
8701
8277
|
}
|
|
8702
8278
|
return {};
|
|
8703
8279
|
}
|
|
8704
|
-
function
|
|
8280
|
+
function writeCache2(cache) {
|
|
8705
8281
|
try {
|
|
8706
|
-
|
|
8707
|
-
|
|
8282
|
+
mkdirSync6(join7(homedir5(), ".okx"), { recursive: true });
|
|
8283
|
+
writeFileSync5(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
8708
8284
|
} catch {
|
|
8709
8285
|
}
|
|
8710
8286
|
}
|
|
@@ -8751,14 +8327,14 @@ async function fetchLatestVersion(packageName) {
|
|
|
8751
8327
|
function refreshCacheInBackground(packageName) {
|
|
8752
8328
|
fetchLatestVersion(packageName).then((latest) => {
|
|
8753
8329
|
if (!latest) return;
|
|
8754
|
-
const cache =
|
|
8330
|
+
const cache = readCache2();
|
|
8755
8331
|
cache[packageName] = { latestVersion: latest, checkedAt: Date.now() };
|
|
8756
|
-
|
|
8332
|
+
writeCache2(cache);
|
|
8757
8333
|
}).catch(() => {
|
|
8758
8334
|
});
|
|
8759
8335
|
}
|
|
8760
8336
|
function checkForUpdates(packageName, currentVersion) {
|
|
8761
|
-
const cache =
|
|
8337
|
+
const cache = readCache2();
|
|
8762
8338
|
const entry = cache[packageName];
|
|
8763
8339
|
if (entry && isNewerVersion(currentVersion, entry.latestVersion)) {
|
|
8764
8340
|
process.stderr.write(
|
|
@@ -9613,7 +9189,7 @@ async function cmdDiagnoseMcp(options = {}) {
|
|
|
9613
9189
|
|
|
9614
9190
|
// src/commands/diagnose.ts
|
|
9615
9191
|
var CLI_VERSION = readCliVersion();
|
|
9616
|
-
var GIT_HASH = true ? "
|
|
9192
|
+
var GIT_HASH = true ? "980de10" : "dev";
|
|
9617
9193
|
function maskKey2(key) {
|
|
9618
9194
|
if (!key) return "(not set)";
|
|
9619
9195
|
if (key.length <= 8) return "****";
|
|
@@ -9910,24 +9486,24 @@ async function runCliChecks(config, profile, outputPath) {
|
|
|
9910
9486
|
|
|
9911
9487
|
// src/commands/upgrade.ts
|
|
9912
9488
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
9913
|
-
import { readFileSync as
|
|
9914
|
-
import { dirname as
|
|
9915
|
-
import { homedir as
|
|
9489
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "fs";
|
|
9490
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
9491
|
+
import { homedir as homedir7 } from "os";
|
|
9916
9492
|
var PACKAGES = ["@okx_ai/okx-trade-mcp", "@okx_ai/okx-trade-cli"];
|
|
9917
|
-
var CACHE_FILE2 =
|
|
9493
|
+
var CACHE_FILE2 = join9(homedir7(), ".okx", "last_check");
|
|
9918
9494
|
var THROTTLE_MS = 12 * 60 * 60 * 1e3;
|
|
9919
|
-
var NPM_BIN =
|
|
9495
|
+
var NPM_BIN = join9(dirname6(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
|
|
9920
9496
|
function readLastCheck() {
|
|
9921
9497
|
try {
|
|
9922
|
-
return parseInt(
|
|
9498
|
+
return parseInt(readFileSync7(CACHE_FILE2, "utf-8").trim(), 10) || 0;
|
|
9923
9499
|
} catch {
|
|
9924
9500
|
return 0;
|
|
9925
9501
|
}
|
|
9926
9502
|
}
|
|
9927
9503
|
function writeLastCheck() {
|
|
9928
9504
|
try {
|
|
9929
|
-
|
|
9930
|
-
|
|
9505
|
+
mkdirSync8(join9(homedir7(), ".okx"), { recursive: true });
|
|
9506
|
+
writeFileSync7(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
|
|
9931
9507
|
} catch {
|
|
9932
9508
|
}
|
|
9933
9509
|
}
|
|
@@ -10602,47 +10178,6 @@ var HELP_TREE = {
|
|
|
10602
10178
|
}
|
|
10603
10179
|
}
|
|
10604
10180
|
},
|
|
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
|
-
},
|
|
10646
10181
|
config: {
|
|
10647
10182
|
description: "Manage CLI configuration profiles",
|
|
10648
10183
|
commands: {
|
|
@@ -11001,11 +10536,6 @@ var CLI_OPTIONS = {
|
|
|
11001
10536
|
dir: { type: "string" },
|
|
11002
10537
|
page: { type: "string" },
|
|
11003
10538
|
format: { type: "string" },
|
|
11004
|
-
// event contract
|
|
11005
|
-
underlying: { type: "string" },
|
|
11006
|
-
seriesId: { type: "string" },
|
|
11007
|
-
eventId: { type: "string" },
|
|
11008
|
-
outcome: { type: "string" },
|
|
11009
10539
|
// diagnostics — cli/mcp/all/output are diagnose-specific; verbose is shared
|
|
11010
10540
|
verbose: { type: "boolean", default: false },
|
|
11011
10541
|
mcp: { type: "boolean", default: false },
|
|
@@ -13836,13 +13366,13 @@ async function cmdDcdQuoteAndBuy(run, opts) {
|
|
|
13836
13366
|
}
|
|
13837
13367
|
|
|
13838
13368
|
// src/commands/skill.ts
|
|
13839
|
-
import { tmpdir, homedir as
|
|
13840
|
-
import { join as
|
|
13841
|
-
import { mkdirSync as
|
|
13369
|
+
import { tmpdir, homedir as homedir9 } from "os";
|
|
13370
|
+
import { join as join11, dirname as dirname7 } from "path";
|
|
13371
|
+
import { mkdirSync as mkdirSync9, rmSync, existsSync as existsSync7, copyFileSync as copyFileSync2 } from "fs";
|
|
13842
13372
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
13843
13373
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
13844
13374
|
function resolveNpx() {
|
|
13845
|
-
const sibling =
|
|
13375
|
+
const sibling = join11(dirname7(process.execPath), "npx");
|
|
13846
13376
|
if (existsSync7(sibling)) return sibling;
|
|
13847
13377
|
return "npx";
|
|
13848
13378
|
}
|
|
@@ -13896,13 +13426,13 @@ async function cmdSkillCategories(run, json) {
|
|
|
13896
13426
|
outputLine("");
|
|
13897
13427
|
}
|
|
13898
13428
|
async function cmdSkillAdd(name, config, json) {
|
|
13899
|
-
const tmpBase =
|
|
13900
|
-
|
|
13429
|
+
const tmpBase = join11(tmpdir(), `okx-skill-${randomUUID2()}`);
|
|
13430
|
+
mkdirSync9(tmpBase, { recursive: true });
|
|
13901
13431
|
try {
|
|
13902
13432
|
outputLine(`Downloading ${name}...`);
|
|
13903
13433
|
const client = new OkxRestClient(config);
|
|
13904
13434
|
const zipPath = await downloadSkillZip(client, name, tmpBase);
|
|
13905
|
-
const contentDir = await extractSkillZip(zipPath,
|
|
13435
|
+
const contentDir = await extractSkillZip(zipPath, join11(tmpBase, "content"));
|
|
13906
13436
|
const meta = readMetaJson(contentDir);
|
|
13907
13437
|
validateSkillMdExists(contentDir);
|
|
13908
13438
|
outputLine("Installing to detected agents...");
|
|
@@ -13912,7 +13442,7 @@ async function cmdSkillAdd(name, config, json) {
|
|
|
13912
13442
|
timeout: 6e4
|
|
13913
13443
|
});
|
|
13914
13444
|
} catch (e) {
|
|
13915
|
-
const savedZip =
|
|
13445
|
+
const savedZip = join11(process.cwd(), `${name}.zip`);
|
|
13916
13446
|
try {
|
|
13917
13447
|
copyFileSync2(zipPath, savedZip);
|
|
13918
13448
|
} catch {
|
|
@@ -13951,7 +13481,7 @@ function cmdSkillRemove(name, json) {
|
|
|
13951
13481
|
timeout: 6e4
|
|
13952
13482
|
});
|
|
13953
13483
|
} catch {
|
|
13954
|
-
const agentsPath =
|
|
13484
|
+
const agentsPath = join11(homedir9(), ".agents", "skills", name);
|
|
13955
13485
|
try {
|
|
13956
13486
|
rmSync(agentsPath, { recursive: true, force: true });
|
|
13957
13487
|
} catch {
|
|
@@ -14024,462 +13554,10 @@ function printSkillInstallResult(meta, json) {
|
|
|
14024
13554
|
}
|
|
14025
13555
|
}
|
|
14026
13556
|
|
|
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
|
-
|
|
14479
13557
|
// src/index.ts
|
|
14480
13558
|
var _require3 = createRequire3(import.meta.url);
|
|
14481
13559
|
var CLI_VERSION2 = _require3("../package.json").version;
|
|
14482
|
-
var GIT_HASH2 = true ? "
|
|
13560
|
+
var GIT_HASH2 = true ? "980de10" : "dev";
|
|
14483
13561
|
function handleConfigCommand(action, rest, json, lang, force) {
|
|
14484
13562
|
if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
|
|
14485
13563
|
if (action === "show") return cmdConfigShow(json);
|
|
@@ -15289,33 +14367,6 @@ function handleSkillCommand(run, action, rest, v, json, config) {
|
|
|
15289
14367
|
errorLine("Valid: search, categories, add, download, remove, check, list");
|
|
15290
14368
|
process.exitCode = 1;
|
|
15291
14369
|
}
|
|
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
|
-
}
|
|
15319
14370
|
function outputResult(result, json) {
|
|
15320
14371
|
if (json) {
|
|
15321
14372
|
outputLine(JSON.stringify(result, null, 2));
|
|
@@ -15394,7 +14445,6 @@ async function main() {
|
|
|
15394
14445
|
swap: () => handleSwapCommand(run, action, rest, v, json),
|
|
15395
14446
|
futures: () => handleFuturesCommand(run, action, rest, v, json),
|
|
15396
14447
|
option: () => handleOptionCommand(run, action, rest, v, json),
|
|
15397
|
-
event: () => handleEventCommand(run, action, rest, v, json),
|
|
15398
14448
|
bot: () => handleBotCommand(run, action, rest, v, json),
|
|
15399
14449
|
earn: () => handleEarnCommand(run, action, rest, v, json),
|
|
15400
14450
|
skill: () => handleSkillCommand(run, action, rest, v, json, config)
|
|
@@ -15419,7 +14469,6 @@ export {
|
|
|
15419
14469
|
handleBotGridCommand,
|
|
15420
14470
|
handleConfigCommand,
|
|
15421
14471
|
handleEarnCommand,
|
|
15422
|
-
handleEventCommand,
|
|
15423
14472
|
handleFuturesAlgoCommand,
|
|
15424
14473
|
handleFuturesCommand,
|
|
15425
14474
|
handleMarketCommand,
|