@okx_ai/okx-trade-mcp 1.3.1 → 1.3.2-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +735 -104
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +9 -9
package/dist/index.js
CHANGED
|
@@ -733,19 +733,19 @@ import * as os3 from "os";
|
|
|
733
733
|
import { execFileSync } from "child_process";
|
|
734
734
|
var EXEC_TIMEOUT_MS = 3e4;
|
|
735
735
|
var ALLOWED_DOMAIN_RE = /^[\w.-]+\.okx\.com$/;
|
|
736
|
-
var
|
|
737
|
-
function
|
|
738
|
-
if (process.env.
|
|
739
|
-
return process.env.
|
|
736
|
+
var PILOT_BIN_DIR = join(homedir(), ".okx", "bin");
|
|
737
|
+
function getPilotBinaryPath() {
|
|
738
|
+
if (process.env.OKX_PILOT_BINARY_PATH) {
|
|
739
|
+
return process.env.OKX_PILOT_BINARY_PATH;
|
|
740
740
|
}
|
|
741
741
|
const ext = process.platform === "win32" ? ".exe" : "";
|
|
742
|
-
return join(
|
|
742
|
+
return join(PILOT_BIN_DIR, `okx-pilot${ext}`);
|
|
743
743
|
}
|
|
744
|
-
function
|
|
744
|
+
function execPilotBinary(domain, exclude = [], userAgent) {
|
|
745
745
|
if (!ALLOWED_DOMAIN_RE.test(domain)) {
|
|
746
746
|
return Promise.resolve(null);
|
|
747
747
|
}
|
|
748
|
-
const binPath =
|
|
748
|
+
const binPath = getPilotBinaryPath();
|
|
749
749
|
const args = ["--domain", domain];
|
|
750
750
|
if (exclude.length > 0) {
|
|
751
751
|
args.push("--exclude", exclude.join(","));
|
|
@@ -778,7 +778,7 @@ function execDohBinary(domain, exclude = [], userAgent) {
|
|
|
778
778
|
});
|
|
779
779
|
}
|
|
780
780
|
function getDefaultCachePath() {
|
|
781
|
-
return process.env.
|
|
781
|
+
return process.env.OKX_PILOT_CACHE_PATH || join2(homedir2(), ".okx", "pilot-cache.json");
|
|
782
782
|
}
|
|
783
783
|
function readCache(hostname, cachePath = getDefaultCachePath()) {
|
|
784
784
|
try {
|
|
@@ -832,7 +832,7 @@ function getActiveFailedNodes(nodes) {
|
|
|
832
832
|
const now = Date.now();
|
|
833
833
|
return nodes.filter((n) => now - n.failedAt < FAILED_NODE_TTL_MS);
|
|
834
834
|
}
|
|
835
|
-
function
|
|
835
|
+
function resolvePilot(hostname, cachePath) {
|
|
836
836
|
const entry = readCache(hostname, cachePath);
|
|
837
837
|
if (entry) {
|
|
838
838
|
if (entry.mode === "direct") {
|
|
@@ -844,53 +844,53 @@ function resolveDoh(hostname, cachePath) {
|
|
|
844
844
|
}
|
|
845
845
|
return { mode: null, node: null };
|
|
846
846
|
}
|
|
847
|
-
async function
|
|
847
|
+
async function reResolvePilot(hostname, failedIp, userAgent, cachePath) {
|
|
848
848
|
const entry = readCache(hostname, cachePath);
|
|
849
849
|
const active = getActiveFailedNodes(entry?.failedNodes);
|
|
850
850
|
const now = Date.now();
|
|
851
851
|
const alreadyFailed = failedIp && active.some((n) => n.ip === failedIp);
|
|
852
852
|
const failedNodes = failedIp && !alreadyFailed ? [...active, { ip: failedIp, failedAt: now }] : active;
|
|
853
853
|
const excludeIps = failedNodes.map((n) => n.ip);
|
|
854
|
-
const node = await
|
|
854
|
+
const node = await execPilotBinary(hostname, excludeIps, userAgent);
|
|
855
855
|
return classifyAndCache(node, hostname, failedNodes, cachePath);
|
|
856
856
|
}
|
|
857
857
|
function vlog(message) {
|
|
858
858
|
process.stderr.write(`[verbose] ${message}
|
|
859
859
|
`);
|
|
860
860
|
}
|
|
861
|
-
var
|
|
861
|
+
var PilotManager = class {
|
|
862
862
|
opts;
|
|
863
|
-
//
|
|
864
|
-
|
|
865
|
-
|
|
863
|
+
// Pilot proxy state (lazy-resolved on first request)
|
|
864
|
+
pilotResolved = false;
|
|
865
|
+
pilotRetried = false;
|
|
866
866
|
directUnverified = false;
|
|
867
867
|
// The first direct connection has not yet been verified
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
868
|
+
pilotNode = null;
|
|
869
|
+
pilotAgent = null;
|
|
870
|
+
pilotBaseUrl = null;
|
|
871
871
|
constructor(opts) {
|
|
872
872
|
this.opts = opts;
|
|
873
873
|
}
|
|
874
874
|
/**
|
|
875
|
-
* Lazily resolve the
|
|
875
|
+
* Lazily resolve the Pilot proxy node on the first request.
|
|
876
876
|
* Uses cache-first strategy via the resolver.
|
|
877
877
|
*/
|
|
878
|
-
|
|
879
|
-
if (this.
|
|
880
|
-
this.
|
|
878
|
+
preparePilot() {
|
|
879
|
+
if (this.pilotResolved || this.opts.hasCustomProxy) return;
|
|
880
|
+
this.pilotResolved = true;
|
|
881
881
|
try {
|
|
882
882
|
const { hostname, protocol } = new URL(this.opts.baseUrl);
|
|
883
|
-
const result =
|
|
883
|
+
const result = resolvePilot(hostname);
|
|
884
884
|
if (!result.mode) {
|
|
885
885
|
this.directUnverified = true;
|
|
886
886
|
if (this.opts.verbose) {
|
|
887
|
-
vlog("
|
|
887
|
+
vlog("Pilot: no cache, trying direct connection first");
|
|
888
888
|
}
|
|
889
889
|
return;
|
|
890
890
|
}
|
|
891
891
|
if (result.mode === "direct") {
|
|
892
892
|
if (this.opts.verbose) {
|
|
893
|
-
vlog("
|
|
893
|
+
vlog("Pilot: mode=direct (overseas or cached), using direct connection");
|
|
894
894
|
}
|
|
895
895
|
return;
|
|
896
896
|
}
|
|
@@ -900,57 +900,57 @@ var DohManager = class {
|
|
|
900
900
|
} catch (err) {
|
|
901
901
|
if (this.opts.verbose) {
|
|
902
902
|
const cause = err instanceof Error ? err.message : String(err);
|
|
903
|
-
vlog(`
|
|
903
|
+
vlog(`Pilot resolution failed, falling back to direct: ${cause}`);
|
|
904
904
|
}
|
|
905
905
|
}
|
|
906
906
|
}
|
|
907
907
|
/** Get connection parameters for the current request. */
|
|
908
908
|
getConnectionParams() {
|
|
909
|
-
const baseUrl = this.
|
|
909
|
+
const baseUrl = this.pilotNode ? this.pilotBaseUrl : this.opts.baseUrl;
|
|
910
910
|
const result = { baseUrl };
|
|
911
|
-
if (this.
|
|
912
|
-
result.dispatcher = this.
|
|
911
|
+
if (this.pilotAgent) {
|
|
912
|
+
result.dispatcher = this.pilotAgent;
|
|
913
913
|
}
|
|
914
|
-
if (this.
|
|
915
|
-
result.userAgent = this.
|
|
914
|
+
if (this.pilotNode) {
|
|
915
|
+
result.userAgent = this.pilotUserAgent;
|
|
916
916
|
}
|
|
917
917
|
return result;
|
|
918
918
|
}
|
|
919
|
-
/** Whether a
|
|
919
|
+
/** Whether a Pilot proxy node is currently active. */
|
|
920
920
|
get isProxyActive() {
|
|
921
|
-
return this.
|
|
921
|
+
return this.pilotNode !== null;
|
|
922
922
|
}
|
|
923
923
|
/** Whether we have already retried after network failure. */
|
|
924
924
|
get hasRetried() {
|
|
925
|
-
return this.
|
|
925
|
+
return this.pilotRetried;
|
|
926
926
|
}
|
|
927
927
|
/**
|
|
928
928
|
* Handle network failure: re-resolve with --exclude and retry once.
|
|
929
929
|
* Returns true if retry should proceed, false if already retried.
|
|
930
930
|
*/
|
|
931
931
|
async handleNetworkFailure() {
|
|
932
|
-
if (this.
|
|
933
|
-
this.
|
|
934
|
-
const failedIp = this.
|
|
932
|
+
if (this.pilotRetried) return false;
|
|
933
|
+
this.pilotRetried = true;
|
|
934
|
+
const failedIp = this.pilotNode?.ip ?? "";
|
|
935
935
|
const { hostname, protocol } = new URL(this.opts.baseUrl);
|
|
936
|
-
this.
|
|
937
|
-
this.
|
|
938
|
-
this.
|
|
936
|
+
this.pilotNode = null;
|
|
937
|
+
this.pilotAgent = null;
|
|
938
|
+
this.pilotBaseUrl = null;
|
|
939
939
|
if (!failedIp) this.directUnverified = false;
|
|
940
940
|
if (this.opts.verbose) {
|
|
941
|
-
vlog(failedIp ? `
|
|
941
|
+
vlog(failedIp ? `Pilot: proxy node ${failedIp} failed, re-resolving with --exclude` : "Pilot: direct connection failed, calling binary for Pilot resolution");
|
|
942
942
|
}
|
|
943
943
|
try {
|
|
944
|
-
const result = await
|
|
944
|
+
const result = await reResolvePilot(hostname, failedIp, this.pilotUserAgent);
|
|
945
945
|
if (result.mode === "proxy" && result.node) {
|
|
946
946
|
this.applyNode(result.node, protocol);
|
|
947
|
-
this.
|
|
947
|
+
this.pilotRetried = false;
|
|
948
948
|
return true;
|
|
949
949
|
}
|
|
950
950
|
} catch {
|
|
951
951
|
}
|
|
952
952
|
if (this.opts.verbose) {
|
|
953
|
-
vlog("
|
|
953
|
+
vlog("Pilot: re-resolution failed or switched to direct, retrying with direct connection");
|
|
954
954
|
}
|
|
955
955
|
return true;
|
|
956
956
|
}
|
|
@@ -959,7 +959,7 @@ var DohManager = class {
|
|
|
959
959
|
* (Even if the business response is an error, the network path is valid.)
|
|
960
960
|
*/
|
|
961
961
|
cacheDirectIfNeeded() {
|
|
962
|
-
if (!this.directUnverified || this.
|
|
962
|
+
if (!this.directUnverified || this.pilotNode) return;
|
|
963
963
|
this.directUnverified = false;
|
|
964
964
|
const { hostname } = new URL(this.opts.baseUrl);
|
|
965
965
|
writeCache(hostname, {
|
|
@@ -969,25 +969,25 @@ var DohManager = class {
|
|
|
969
969
|
updatedAt: Date.now()
|
|
970
970
|
});
|
|
971
971
|
if (this.opts.verbose) {
|
|
972
|
-
vlog("
|
|
972
|
+
vlog("Pilot: direct connection succeeded, cached mode=direct");
|
|
973
973
|
}
|
|
974
974
|
}
|
|
975
|
-
/** User-Agent for
|
|
976
|
-
get
|
|
975
|
+
/** User-Agent for Pilot proxy requests: OKX/@okx_ai/{packageName}/{version} */
|
|
976
|
+
get pilotUserAgent() {
|
|
977
977
|
return `OKX/@okx_ai/${this.opts.packageUserAgent ?? "unknown"}`;
|
|
978
978
|
}
|
|
979
979
|
/**
|
|
980
|
-
* Apply a
|
|
980
|
+
* Apply a Pilot node: set up the custom Agent + base URL.
|
|
981
981
|
*
|
|
982
982
|
* node.ip may be a real IP or a domain (CNAME like *.aliyunddos1021.com).
|
|
983
983
|
* - Real IP → use directly in lookup callback
|
|
984
984
|
* - Domain → dns.lookup on every connection to get a fresh IP
|
|
985
985
|
*/
|
|
986
986
|
applyNode(node, protocol) {
|
|
987
|
-
this.
|
|
988
|
-
this.
|
|
987
|
+
this.pilotNode = node;
|
|
988
|
+
this.pilotBaseUrl = `${protocol}//${node.host}`;
|
|
989
989
|
const nodeIpIsRealIp = !!isIP(node.ip);
|
|
990
|
-
this.
|
|
990
|
+
this.pilotAgent = new Agent({
|
|
991
991
|
connect: {
|
|
992
992
|
lookup: (_hostname, options, callback) => {
|
|
993
993
|
if (nodeIpIsRealIp) {
|
|
@@ -1011,7 +1011,7 @@ var DohManager = class {
|
|
|
1011
1011
|
}
|
|
1012
1012
|
});
|
|
1013
1013
|
if (this.opts.verbose) {
|
|
1014
|
-
vlog(`
|
|
1014
|
+
vlog(`Pilot proxy active: \u2192 ${node.host} (${node.ip}), ttl=${node.ttl}s`);
|
|
1015
1015
|
}
|
|
1016
1016
|
}
|
|
1017
1017
|
};
|
|
@@ -1233,14 +1233,14 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1233
1233
|
config;
|
|
1234
1234
|
rateLimiter;
|
|
1235
1235
|
dispatcher;
|
|
1236
|
-
|
|
1236
|
+
pilot;
|
|
1237
1237
|
constructor(config) {
|
|
1238
1238
|
this.config = config;
|
|
1239
1239
|
this.rateLimiter = new RateLimiter(3e4, config.verbose);
|
|
1240
1240
|
if (config.proxyUrl) {
|
|
1241
1241
|
this.dispatcher = new ProxyAgent(config.proxyUrl);
|
|
1242
1242
|
}
|
|
1243
|
-
this.
|
|
1243
|
+
this.pilot = new PilotManager({
|
|
1244
1244
|
baseUrl: config.baseUrl,
|
|
1245
1245
|
packageUserAgent: config.userAgent,
|
|
1246
1246
|
verbose: config.verbose,
|
|
@@ -1294,13 +1294,14 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1294
1294
|
rateLimit
|
|
1295
1295
|
});
|
|
1296
1296
|
}
|
|
1297
|
-
async privatePost(path4, body, rateLimit) {
|
|
1297
|
+
async privatePost(path4, body, rateLimit, retryOnNetworkError) {
|
|
1298
1298
|
return this.request({
|
|
1299
1299
|
method: "POST",
|
|
1300
1300
|
path: path4,
|
|
1301
1301
|
auth: "private",
|
|
1302
1302
|
body,
|
|
1303
|
-
rateLimit
|
|
1303
|
+
rateLimit,
|
|
1304
|
+
retryOnNetworkError
|
|
1304
1305
|
});
|
|
1305
1306
|
}
|
|
1306
1307
|
setAuthHeaders(headers, method, requestPath, bodyJson, timestamp) {
|
|
@@ -1422,12 +1423,12 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1422
1423
|
* Security: validates Content-Type and enforces maxBytes limit.
|
|
1423
1424
|
*/
|
|
1424
1425
|
async privatePostBinary(path4, body, opts) {
|
|
1425
|
-
this.
|
|
1426
|
+
this.pilot.preparePilot();
|
|
1426
1427
|
const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
|
|
1427
1428
|
const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
|
|
1428
1429
|
const bodyJson = body ? JSON.stringify(body) : "";
|
|
1429
1430
|
const endpoint = `POST ${path4}`;
|
|
1430
|
-
const conn = this.
|
|
1431
|
+
const conn = this.pilot.getConnectionParams();
|
|
1431
1432
|
this.logRequest("POST", `${conn.baseUrl}${path4}`, "private");
|
|
1432
1433
|
const reqConfig = { method: "POST", path: path4, auth: "private" };
|
|
1433
1434
|
const headers = this.buildHeaders(reqConfig, path4, bodyJson, getNow());
|
|
@@ -1439,13 +1440,15 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1439
1440
|
try {
|
|
1440
1441
|
response = await this.fetchBinary(path4, endpoint, headers, bodyJson, t0);
|
|
1441
1442
|
} catch (error) {
|
|
1442
|
-
|
|
1443
|
-
|
|
1443
|
+
try {
|
|
1444
|
+
await this.pilot.handleNetworkFailure();
|
|
1445
|
+
} catch {
|
|
1446
|
+
}
|
|
1444
1447
|
throw error;
|
|
1445
1448
|
}
|
|
1446
1449
|
const elapsed = Date.now() - t0;
|
|
1447
1450
|
const traceId = extractTraceId(response.headers);
|
|
1448
|
-
this.
|
|
1451
|
+
this.pilot.cacheDirectIfNeeded();
|
|
1449
1452
|
if (!response.ok) {
|
|
1450
1453
|
const text = await response.text();
|
|
1451
1454
|
this.logResponse(response.status, text.length, elapsed, traceId, String(response.status));
|
|
@@ -1471,15 +1474,15 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1471
1474
|
/**
|
|
1472
1475
|
* Send an unauthenticated GET request and return the raw binary response.
|
|
1473
1476
|
* Used for pre-signed download URLs where auth is embedded in the token.
|
|
1474
|
-
* Inherits proxy, timeout,
|
|
1477
|
+
* Inherits proxy, timeout, Pilot, and verbose capabilities from the client.
|
|
1475
1478
|
*/
|
|
1476
1479
|
async publicGetBinary(path4, query, opts) {
|
|
1477
|
-
this.
|
|
1480
|
+
this.pilot.preparePilot();
|
|
1478
1481
|
const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
|
|
1479
1482
|
const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
|
|
1480
1483
|
const queryString = buildQueryString(query);
|
|
1481
1484
|
const requestPath = queryString ? `${path4}?${queryString}` : path4;
|
|
1482
|
-
const conn = this.
|
|
1485
|
+
const conn = this.pilot.getConnectionParams();
|
|
1483
1486
|
const url = `${conn.baseUrl}${requestPath}`;
|
|
1484
1487
|
this.logRequest("GET", url, "public");
|
|
1485
1488
|
const headers = new Headers({ Accept: "application/octet-stream" });
|
|
@@ -1495,13 +1498,15 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1495
1498
|
dispatcher: this.dispatcher ?? conn.dispatcher
|
|
1496
1499
|
});
|
|
1497
1500
|
} catch (error) {
|
|
1498
|
-
|
|
1499
|
-
|
|
1501
|
+
try {
|
|
1502
|
+
await this.pilot.handleNetworkFailure();
|
|
1503
|
+
} catch {
|
|
1504
|
+
}
|
|
1500
1505
|
throw new NetworkError(`Failed to call OKX endpoint GET ${path4}.`, `GET ${path4}`, error);
|
|
1501
1506
|
}
|
|
1502
1507
|
const elapsed = Date.now() - t0;
|
|
1503
1508
|
const traceId = extractTraceId(response.headers);
|
|
1504
|
-
this.
|
|
1509
|
+
this.pilot.cacheDirectIfNeeded();
|
|
1505
1510
|
if (!response.ok) {
|
|
1506
1511
|
const text = await response.text();
|
|
1507
1512
|
this.logResponse(response.status, text.length, elapsed, traceId, String(response.status));
|
|
@@ -1526,7 +1531,7 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1526
1531
|
}
|
|
1527
1532
|
/** Execute fetch for binary endpoint, wrapping network errors. */
|
|
1528
1533
|
async fetchBinary(path4, endpoint, headers, bodyJson, t0) {
|
|
1529
|
-
const conn = this.
|
|
1534
|
+
const conn = this.pilot.getConnectionParams();
|
|
1530
1535
|
try {
|
|
1531
1536
|
const fetchOptions = {
|
|
1532
1537
|
method: "POST",
|
|
@@ -1573,17 +1578,17 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1573
1578
|
// JSON request
|
|
1574
1579
|
// ---------------------------------------------------------------------------
|
|
1575
1580
|
/**
|
|
1576
|
-
* Handle network error during a JSON request: refresh
|
|
1581
|
+
* Handle network error during a JSON request: refresh Pilot and maybe retry.
|
|
1577
1582
|
* Always either returns a retry result or throws NetworkError.
|
|
1578
1583
|
*/
|
|
1579
1584
|
async handleRequestNetworkError(error, reqConfig, requestPath, t0) {
|
|
1580
|
-
if (!this.
|
|
1585
|
+
if (!this.pilot.hasRetried) {
|
|
1581
1586
|
if (this.config.verbose) {
|
|
1582
1587
|
const cause = error instanceof Error ? error.message : String(error);
|
|
1583
|
-
vlog2(`Network failure, refreshing
|
|
1588
|
+
vlog2(`Network failure, refreshing Pilot: ${cause}`);
|
|
1584
1589
|
}
|
|
1585
|
-
const shouldRetry = await this.
|
|
1586
|
-
if (shouldRetry && reqConfig.method === "GET") {
|
|
1590
|
+
const shouldRetry = await this.pilot.handleNetworkFailure();
|
|
1591
|
+
if (shouldRetry && (reqConfig.method === "GET" || reqConfig.retryOnNetworkError)) {
|
|
1587
1592
|
return this.request(reqConfig);
|
|
1588
1593
|
}
|
|
1589
1594
|
}
|
|
@@ -1599,10 +1604,10 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1599
1604
|
);
|
|
1600
1605
|
}
|
|
1601
1606
|
async request(reqConfig) {
|
|
1602
|
-
this.
|
|
1607
|
+
this.pilot.preparePilot();
|
|
1603
1608
|
const queryString = buildQueryString(reqConfig.query);
|
|
1604
1609
|
const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
|
|
1605
|
-
const conn = this.
|
|
1610
|
+
const conn = this.pilot.getConnectionParams();
|
|
1606
1611
|
const url = `${conn.baseUrl}${requestPath}`;
|
|
1607
1612
|
const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
|
|
1608
1613
|
const timestamp = getNow();
|
|
@@ -1631,7 +1636,7 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1631
1636
|
const rawText = await response.text();
|
|
1632
1637
|
const elapsed = Date.now() - t0;
|
|
1633
1638
|
const traceId = extractTraceId(response.headers);
|
|
1634
|
-
this.
|
|
1639
|
+
this.pilot.cacheDirectIfNeeded();
|
|
1635
1640
|
return this.processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath);
|
|
1636
1641
|
}
|
|
1637
1642
|
};
|
|
@@ -2059,6 +2064,7 @@ var MODULES = [
|
|
|
2059
2064
|
"account",
|
|
2060
2065
|
"event",
|
|
2061
2066
|
"news",
|
|
2067
|
+
"smartmoney",
|
|
2062
2068
|
...EARN_SUB_MODULE_IDS,
|
|
2063
2069
|
...BOT_SUB_MODULE_IDS,
|
|
2064
2070
|
"skills"
|
|
@@ -2209,6 +2215,10 @@ function registerAccountTools() {
|
|
|
2209
2215
|
showValuation: {
|
|
2210
2216
|
type: "boolean",
|
|
2211
2217
|
description: "Include total asset valuation breakdown by account type (trading/funding/earn). Default false."
|
|
2218
|
+
},
|
|
2219
|
+
valuationCcy: {
|
|
2220
|
+
type: "string",
|
|
2221
|
+
description: "Currency used to denominate the total asset valuation (e.g. USDT, BTC). Default USDT. Only applies when showValuation=true."
|
|
2212
2222
|
}
|
|
2213
2223
|
}
|
|
2214
2224
|
},
|
|
@@ -2216,6 +2226,7 @@ function registerAccountTools() {
|
|
|
2216
2226
|
const args = asRecord(rawArgs);
|
|
2217
2227
|
const ccy = readString(args, "ccy");
|
|
2218
2228
|
const showValuation = readBoolean(args, "showValuation");
|
|
2229
|
+
const valuationCcy = readString(args, "valuationCcy") ?? "USDT";
|
|
2219
2230
|
if (showValuation) {
|
|
2220
2231
|
const balanceResp2 = await context.client.privateGet(
|
|
2221
2232
|
"/api/v5/asset/balances",
|
|
@@ -2223,16 +2234,26 @@ function registerAccountTools() {
|
|
|
2223
2234
|
privateRateLimit("account_get_asset_balance", 6)
|
|
2224
2235
|
);
|
|
2225
2236
|
let valuationData = null;
|
|
2237
|
+
let valuationError;
|
|
2226
2238
|
try {
|
|
2227
2239
|
const valuationResp = await context.client.privateGet(
|
|
2228
2240
|
"/api/v5/asset/asset-valuation",
|
|
2229
|
-
{},
|
|
2241
|
+
{ ccy: valuationCcy },
|
|
2230
2242
|
privateRateLimit("account_get_asset_valuation", 1)
|
|
2231
2243
|
);
|
|
2232
2244
|
valuationData = valuationResp.data;
|
|
2233
|
-
} catch {
|
|
2245
|
+
} catch (err) {
|
|
2246
|
+
valuationError = err instanceof Error ? err.message : String(err);
|
|
2234
2247
|
}
|
|
2235
|
-
|
|
2248
|
+
const valuationResult = {
|
|
2249
|
+
...normalizeResponse(balanceResp2),
|
|
2250
|
+
valuation: valuationData,
|
|
2251
|
+
valuationCcy
|
|
2252
|
+
};
|
|
2253
|
+
if (valuationError !== void 0) {
|
|
2254
|
+
valuationResult["valuationError"] = valuationError;
|
|
2255
|
+
}
|
|
2256
|
+
return valuationResult;
|
|
2236
2257
|
}
|
|
2237
2258
|
const balanceResp = await context.client.privateGet(
|
|
2238
2259
|
"/api/v5/asset/balances",
|
|
@@ -3904,10 +3925,133 @@ function registerGridTools() {
|
|
|
3904
3925
|
return normalizeWrite(response);
|
|
3905
3926
|
}
|
|
3906
3927
|
},
|
|
3928
|
+
{
|
|
3929
|
+
name: "grid_amend_order",
|
|
3930
|
+
module: "bot.grid",
|
|
3931
|
+
description: "Amend a running grid bot. [CAUTION] Modifies a running bot. Use grid_list_orders to confirm the bot is running and obtain the algoId before calling.\nSupports two modes, which can be combined in a single call:\n\u2022 Price-range mode (maxPx+minPx+gridNum): change upper/lower price boundary and grid count. Contract grid: if new range requires more margin, pass topUpAmt; omit to auto-use the minimum required. Spot grid: topUpAmt is not supported.\n\u2022 TP/SL mode (instId + any of tpTriggerPx/slTriggerPx/tpRatio/slRatio): update take-profit and/or stop-loss. Pass '-1' to explicitly clear an existing TP or SL. tpTriggerPx/slTriggerPx are absolute prices; tpRatio/slRatio are profit ratios (e.g. '0.1' = 10%).\nWhen both sets of params are provided, both APIs are called sequentially.\nDo NOT use to create a new grid bot \u2014 use grid_create_order instead. Do NOT use to stop a grid bot \u2014 use grid_stop_order instead.",
|
|
3932
|
+
isWrite: true,
|
|
3933
|
+
inputSchema: {
|
|
3934
|
+
type: "object",
|
|
3935
|
+
properties: {
|
|
3936
|
+
algoId: {
|
|
3937
|
+
type: "string",
|
|
3938
|
+
description: "Grid bot algo order ID (required)"
|
|
3939
|
+
},
|
|
3940
|
+
// ── Price-range mode ──────────────────────────────────────────────
|
|
3941
|
+
maxPx: {
|
|
3942
|
+
type: "string",
|
|
3943
|
+
description: "[Price-range mode] New upper price boundary. Triggers amend-algo-basic-param when provided."
|
|
3944
|
+
},
|
|
3945
|
+
minPx: {
|
|
3946
|
+
type: "string",
|
|
3947
|
+
description: "[Price-range mode] New lower price boundary. Required when maxPx is set."
|
|
3948
|
+
},
|
|
3949
|
+
gridNum: {
|
|
3950
|
+
type: "string",
|
|
3951
|
+
description: "[Price-range mode] New number of grid intervals (integer). Required when maxPx is set."
|
|
3952
|
+
},
|
|
3953
|
+
// ── TP/SL mode ────────────────────────────────────────────────────
|
|
3954
|
+
instId: {
|
|
3955
|
+
type: "string",
|
|
3956
|
+
description: "[TP/SL mode] Instrument ID, e.g. BTC-USDT. Required when setting TP/SL."
|
|
3957
|
+
},
|
|
3958
|
+
tpTriggerPx: {
|
|
3959
|
+
type: "string",
|
|
3960
|
+
description: "[TP/SL mode] Take-profit trigger price (absolute). Pass '-1' to clear."
|
|
3961
|
+
},
|
|
3962
|
+
slTriggerPx: {
|
|
3963
|
+
type: "string",
|
|
3964
|
+
description: "[TP/SL mode] Stop-loss trigger price (absolute). Pass '-1' to clear."
|
|
3965
|
+
},
|
|
3966
|
+
tpRatio: {
|
|
3967
|
+
type: "string",
|
|
3968
|
+
description: "[TP/SL mode] Take-profit ratio, e.g. '0.1' = 10% profit. Pass '-1' to clear."
|
|
3969
|
+
},
|
|
3970
|
+
slRatio: {
|
|
3971
|
+
type: "string",
|
|
3972
|
+
description: "[TP/SL mode] Stop-loss ratio, e.g. '0.1' = 10% drawdown. Pass '-1' to clear."
|
|
3973
|
+
},
|
|
3974
|
+
// ── Shared optional ───────────────────────────────────────────────
|
|
3975
|
+
topUpAmt: {
|
|
3976
|
+
type: "string",
|
|
3977
|
+
description: "Top-up amount. In price-range mode maps to topupAmount (contract grid only; omit to use minimum required). In TP/SL mode maps to topUpAmt."
|
|
3978
|
+
}
|
|
3979
|
+
},
|
|
3980
|
+
required: ["algoId"]
|
|
3981
|
+
},
|
|
3982
|
+
handler: async (rawArgs, context) => {
|
|
3983
|
+
const args = asRecord(rawArgs);
|
|
3984
|
+
const algoId = requireString(args, "algoId");
|
|
3985
|
+
const maxPx = readString(args, "maxPx");
|
|
3986
|
+
const instId = readString(args, "instId");
|
|
3987
|
+
const hasTpsl = readString(args, "tpTriggerPx") || readString(args, "slTriggerPx") || readString(args, "tpRatio") || readString(args, "slRatio");
|
|
3988
|
+
if (!maxPx && !hasTpsl) {
|
|
3989
|
+
throw new OkxApiError(
|
|
3990
|
+
"Nothing to amend. Provide maxPx+minPx+gridNum for price-range mode, or any of tpTriggerPx/slTriggerPx/tpRatio/slRatio (instId also required) for TP/SL mode (both can be combined).",
|
|
3991
|
+
{ code: "", endpoint: "grid_amend_order" }
|
|
3992
|
+
);
|
|
3993
|
+
}
|
|
3994
|
+
if (hasTpsl && !instId) {
|
|
3995
|
+
throw new OkxApiError(
|
|
3996
|
+
"TP/SL mode requires instId. Provide instId alongside the TP/SL parameters.",
|
|
3997
|
+
{ code: "", endpoint: "grid_amend_order" }
|
|
3998
|
+
);
|
|
3999
|
+
}
|
|
4000
|
+
const results = [];
|
|
4001
|
+
if (maxPx) {
|
|
4002
|
+
results.push(normalizeWrite(await context.client.privatePost(
|
|
4003
|
+
"/api/v5/tradingBot/grid/amend-algo-basic-param",
|
|
4004
|
+
compactObject({
|
|
4005
|
+
algoId,
|
|
4006
|
+
maxPx,
|
|
4007
|
+
minPx: requireString(args, "minPx"),
|
|
4008
|
+
gridNum: requireString(args, "gridNum"),
|
|
4009
|
+
// API field is "topupAmount" (lowercase u) — different from TP/SL mode's "topUpAmt"
|
|
4010
|
+
// Contract grid only; omitting lets the API use the minimum required
|
|
4011
|
+
topupAmount: readString(args, "topUpAmt")
|
|
4012
|
+
}),
|
|
4013
|
+
privateRateLimit("grid_amend_order", 20),
|
|
4014
|
+
true
|
|
4015
|
+
// retryOnNetworkError: amend sets fixed values, safe to retry
|
|
4016
|
+
)));
|
|
4017
|
+
}
|
|
4018
|
+
if (hasTpsl) {
|
|
4019
|
+
try {
|
|
4020
|
+
results.push(normalizeWrite(await context.client.privatePost(
|
|
4021
|
+
"/api/v5/tradingBot/grid/amend-order-algo",
|
|
4022
|
+
compactObject({
|
|
4023
|
+
algoId,
|
|
4024
|
+
instId,
|
|
4025
|
+
tpTriggerPx: readString(args, "tpTriggerPx"),
|
|
4026
|
+
slTriggerPx: readString(args, "slTriggerPx"),
|
|
4027
|
+
tpRatio: readString(args, "tpRatio"),
|
|
4028
|
+
slRatio: readString(args, "slRatio"),
|
|
4029
|
+
topUpAmt: readString(args, "topUpAmt")
|
|
4030
|
+
// API field is "topUpAmt" (uppercase U) — different from price-range mode's "topupAmount"
|
|
4031
|
+
}),
|
|
4032
|
+
privateRateLimit("grid_amend_order", 20),
|
|
4033
|
+
true
|
|
4034
|
+
// retryOnNetworkError: amend sets fixed values, safe to retry
|
|
4035
|
+
)));
|
|
4036
|
+
} catch (err) {
|
|
4037
|
+
if (results.length > 0) {
|
|
4038
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4039
|
+
throw new OkxApiError(
|
|
4040
|
+
`TP/SL amend failed (price-range amend already succeeded): ${msg}`,
|
|
4041
|
+
{ code: "", endpoint: "grid_amend_order" }
|
|
4042
|
+
);
|
|
4043
|
+
}
|
|
4044
|
+
throw err;
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
const merged = results.flatMap((r) => Array.isArray(r.data) ? r.data : [r.data]);
|
|
4048
|
+
return { endpoint: results[0].endpoint, requestTime: results[0].requestTime, data: merged };
|
|
4049
|
+
}
|
|
4050
|
+
},
|
|
3907
4051
|
{
|
|
3908
4052
|
name: "grid_stop_order",
|
|
3909
4053
|
module: "bot.grid",
|
|
3910
|
-
description: "Stop a grid bot. [CAUTION]
|
|
4054
|
+
description: "Stop a running grid bot. [CAUTION] This stops the strategy and handles open orders/positions according to stopType. Default (stopType='1') closes all positions immediately \u2014 use this for a clean exit. stopType='2' stops the strategy without selling: spot grid keeps all base assets as-is (no sell-back to quote); contract grid cancels all grid orders but leaves the position open for manual close later.",
|
|
3911
4055
|
isWrite: true,
|
|
3912
4056
|
inputSchema: {
|
|
3913
4057
|
type: "object",
|
|
@@ -3916,13 +4060,13 @@ function registerGridTools() {
|
|
|
3916
4060
|
algoOrdType: {
|
|
3917
4061
|
type: "string",
|
|
3918
4062
|
enum: ["grid", "contract_grid"],
|
|
3919
|
-
description: "grid=Spot, contract_grid=Contract"
|
|
4063
|
+
description: "grid=Spot grid, contract_grid=Contract grid"
|
|
3920
4064
|
},
|
|
3921
|
-
instId: { type: "string", description: "e.g. BTC-USDT, BTC-
|
|
4065
|
+
instId: { type: "string", description: "Instrument ID, e.g. BTC-USDT, BTC-USDT-SWAP" },
|
|
3922
4066
|
stopType: {
|
|
3923
4067
|
type: "string",
|
|
3924
|
-
enum: ["1", "2"
|
|
3925
|
-
description: "1
|
|
4068
|
+
enum: ["1", "2"],
|
|
4069
|
+
description: "'1' (default): stop strategy and sell \u2014 spot grid sells all base assets back to quote; contract grid market-closes all positions. '2': stop strategy without selling \u2014 spot grid keeps base assets as-is; contract grid cancels all grid orders but leaves the position open. After stopType='2', the remaining position can be closed manually from the Positions page."
|
|
3926
4070
|
}
|
|
3927
4071
|
},
|
|
3928
4072
|
required: ["algoId", "algoOrdType", "instId"]
|
|
@@ -3937,7 +4081,9 @@ function registerGridTools() {
|
|
|
3937
4081
|
instId: requireString(args, "instId"),
|
|
3938
4082
|
stopType: readString(args, "stopType") ?? "1"
|
|
3939
4083
|
})],
|
|
3940
|
-
privateRateLimit("grid_stop_order", 20)
|
|
4084
|
+
privateRateLimit("grid_stop_order", 20),
|
|
4085
|
+
true
|
|
4086
|
+
// retryOnNetworkError: safe to retry — already-stopped returns an error but does not harm state
|
|
3941
4087
|
);
|
|
3942
4088
|
return normalizeWrite(response);
|
|
3943
4089
|
}
|
|
@@ -6084,6 +6230,384 @@ function registerEventContractTools() {
|
|
|
6084
6230
|
}
|
|
6085
6231
|
];
|
|
6086
6232
|
}
|
|
6233
|
+
var PATH_LEADERBOARD = "/api/v5/orbit/public/leaderboard";
|
|
6234
|
+
var PATH_POSITION_CURRENT = "/api/v5/orbit/public/position-current";
|
|
6235
|
+
var PATH_TRADE_RECORDS = "/api/v5/orbit/public/trade-records";
|
|
6236
|
+
var PATH_OVERVIEW = "/api/v5/journal/smartmoney/overview";
|
|
6237
|
+
var PATH_SIGNAL = "/api/v5/journal/smartmoney/signal";
|
|
6238
|
+
var PATH_SIGNAL_HISTORY = "/api/v5/journal/smartmoney/signal-history";
|
|
6239
|
+
var SIGNAL_POOL_FILTER_PROPS = {
|
|
6240
|
+
sortType: {
|
|
6241
|
+
type: "string",
|
|
6242
|
+
description: "pnl or pnlRatio"
|
|
6243
|
+
},
|
|
6244
|
+
period: {
|
|
6245
|
+
type: "string",
|
|
6246
|
+
description: "3|7|30|90 days"
|
|
6247
|
+
},
|
|
6248
|
+
pnl: {
|
|
6249
|
+
type: "string",
|
|
6250
|
+
description: "PNL_ANY|PNL_TOP50|PNL_TOP20|PNL_TOP5"
|
|
6251
|
+
},
|
|
6252
|
+
winRatio: {
|
|
6253
|
+
type: "string",
|
|
6254
|
+
description: "WR_ANY|WR_GE_50|WR_GE_80"
|
|
6255
|
+
},
|
|
6256
|
+
maxRetreat: {
|
|
6257
|
+
type: "string",
|
|
6258
|
+
description: "MR_ANY|MR_LE_20|MR_LE_50"
|
|
6259
|
+
},
|
|
6260
|
+
asset: {
|
|
6261
|
+
type: "string",
|
|
6262
|
+
description: "AUM_ANY|AUM_TOP50|AUM_TOP20|AUM_TOP5"
|
|
6263
|
+
}
|
|
6264
|
+
};
|
|
6265
|
+
var LEADERBOARD_POOL_FILTER_PROPS = {
|
|
6266
|
+
sortType: {
|
|
6267
|
+
type: "string",
|
|
6268
|
+
description: "pnl or pnl_ratio"
|
|
6269
|
+
},
|
|
6270
|
+
period: {
|
|
6271
|
+
type: "string",
|
|
6272
|
+
description: "3|7|30|90 days, empty=all"
|
|
6273
|
+
},
|
|
6274
|
+
pnl: {
|
|
6275
|
+
type: "string",
|
|
6276
|
+
description: "Min PnL USD"
|
|
6277
|
+
},
|
|
6278
|
+
winRatio: {
|
|
6279
|
+
type: "string",
|
|
6280
|
+
description: "Min ratio (0.8=80%)"
|
|
6281
|
+
},
|
|
6282
|
+
maxRetreat: {
|
|
6283
|
+
type: "string",
|
|
6284
|
+
description: "Max DD (0.1=10%)"
|
|
6285
|
+
},
|
|
6286
|
+
asset: {
|
|
6287
|
+
type: "string",
|
|
6288
|
+
description: "Min AUM USD"
|
|
6289
|
+
}
|
|
6290
|
+
};
|
|
6291
|
+
var POOL_FILTER_KEYS = ["sortType", "period", "pnl", "winRatio", "maxRetreat", "asset"];
|
|
6292
|
+
function readPoolFilters(args) {
|
|
6293
|
+
const result = {};
|
|
6294
|
+
for (const key of POOL_FILTER_KEYS) {
|
|
6295
|
+
const val = readString(args, key);
|
|
6296
|
+
if (val) result[key] = val;
|
|
6297
|
+
}
|
|
6298
|
+
return result;
|
|
6299
|
+
}
|
|
6300
|
+
function extractLeaderboardData(data) {
|
|
6301
|
+
if (Array.isArray(data)) return data;
|
|
6302
|
+
if (data && typeof data === "object") {
|
|
6303
|
+
const inner = data.data;
|
|
6304
|
+
if (Array.isArray(inner)) return inner;
|
|
6305
|
+
}
|
|
6306
|
+
return [];
|
|
6307
|
+
}
|
|
6308
|
+
function registerSmartmoneyTools() {
|
|
6309
|
+
const tools = [
|
|
6310
|
+
/* ---------- 1. Overview ---------- */
|
|
6311
|
+
{
|
|
6312
|
+
name: "smartmoney_get_overview",
|
|
6313
|
+
module: "smartmoney",
|
|
6314
|
+
description: "Multi-currency smart money overview ranked by most-watched currencies. Pass ts=Date.now() for latest data, or dataVersion (yyyyMMddHHmm) from a prior call. For single-currency signal with entry prices and trend, use smartmoney_get_signal.",
|
|
6315
|
+
isWrite: false,
|
|
6316
|
+
inputSchema: {
|
|
6317
|
+
type: "object",
|
|
6318
|
+
properties: {
|
|
6319
|
+
dataVersion: {
|
|
6320
|
+
type: "string",
|
|
6321
|
+
description: "yyyyMMddHHmm UTC (or use ts)"
|
|
6322
|
+
},
|
|
6323
|
+
ts: {
|
|
6324
|
+
type: "string",
|
|
6325
|
+
description: "Timestamp ms (or use dataVersion)"
|
|
6326
|
+
},
|
|
6327
|
+
instType: {
|
|
6328
|
+
type: "string",
|
|
6329
|
+
description: "SPOT|MARGIN|FUTURES|SWAP|OPTION"
|
|
6330
|
+
},
|
|
6331
|
+
...SIGNAL_POOL_FILTER_PROPS,
|
|
6332
|
+
lmtNum: {
|
|
6333
|
+
type: "string",
|
|
6334
|
+
description: "Trader pool size 1-500"
|
|
6335
|
+
},
|
|
6336
|
+
instCcyList: {
|
|
6337
|
+
type: "string",
|
|
6338
|
+
description: "Comma-separated e.g. BTC,ETH,SOL"
|
|
6339
|
+
},
|
|
6340
|
+
instCcy: {
|
|
6341
|
+
type: "string",
|
|
6342
|
+
description: "Single currency e.g. BTC"
|
|
6343
|
+
},
|
|
6344
|
+
topInstruments: {
|
|
6345
|
+
type: "string",
|
|
6346
|
+
description: "Top N instruments 1-100"
|
|
6347
|
+
}
|
|
6348
|
+
}
|
|
6349
|
+
},
|
|
6350
|
+
handler: async (rawArgs, context) => {
|
|
6351
|
+
const args = asRecord(rawArgs);
|
|
6352
|
+
const dv = readString(args, "dataVersion");
|
|
6353
|
+
const ts = readString(args, "ts");
|
|
6354
|
+
if (!dv && !ts) {
|
|
6355
|
+
throw new ValidationError('Either "dataVersion" or "ts" is required for smartmoney_get_overview.');
|
|
6356
|
+
}
|
|
6357
|
+
const response = await context.client.privateGet(
|
|
6358
|
+
PATH_OVERVIEW,
|
|
6359
|
+
compactObject({
|
|
6360
|
+
dataVersion: dv,
|
|
6361
|
+
ts,
|
|
6362
|
+
instType: readString(args, "instType"),
|
|
6363
|
+
...readPoolFilters(args),
|
|
6364
|
+
lmtNum: readString(args, "lmtNum"),
|
|
6365
|
+
instCcyList: readString(args, "instCcyList"),
|
|
6366
|
+
instCcy: readString(args, "instCcy"),
|
|
6367
|
+
topInstruments: readString(args, "topInstruments")
|
|
6368
|
+
}),
|
|
6369
|
+
publicRateLimit("smartmoney_get_overview", 5)
|
|
6370
|
+
);
|
|
6371
|
+
return normalizeResponse(response);
|
|
6372
|
+
}
|
|
6373
|
+
},
|
|
6374
|
+
/* ---------- 2. Signal ---------- */
|
|
6375
|
+
{
|
|
6376
|
+
name: "smartmoney_get_signal",
|
|
6377
|
+
module: "smartmoney",
|
|
6378
|
+
description: "Single-currency consensus signal: long/short ratio, entry prices, trend, capital flow. Prefer instId (e.g. BTC-USDT-SWAP); instCcy is accepted but may return empty \u2014 use instId for reliable results. Pass ts=Date.now() for latest data, or dataVersion from a prior call. For multi-currency overview, use smartmoney_get_overview. For timeline, use smartmoney_get_signal_history.",
|
|
6379
|
+
isWrite: false,
|
|
6380
|
+
inputSchema: {
|
|
6381
|
+
type: "object",
|
|
6382
|
+
properties: {
|
|
6383
|
+
instId: {
|
|
6384
|
+
type: "string",
|
|
6385
|
+
description: "Recommended. e.g. BTC-USDT-SWAP"
|
|
6386
|
+
},
|
|
6387
|
+
instCcy: {
|
|
6388
|
+
type: "string",
|
|
6389
|
+
description: "e.g. BTC, SPOT/SWAP only. May return empty \u2014 prefer instId."
|
|
6390
|
+
},
|
|
6391
|
+
dataVersion: {
|
|
6392
|
+
type: "string",
|
|
6393
|
+
description: "yyyyMMddHHmm UTC (or use ts)"
|
|
6394
|
+
},
|
|
6395
|
+
ts: {
|
|
6396
|
+
type: "string",
|
|
6397
|
+
description: "Timestamp ms (or use dataVersion)"
|
|
6398
|
+
},
|
|
6399
|
+
...SIGNAL_POOL_FILTER_PROPS,
|
|
6400
|
+
lmtNum: {
|
|
6401
|
+
type: "string",
|
|
6402
|
+
description: "Trader pool size 1-500"
|
|
6403
|
+
},
|
|
6404
|
+
authorIds: {
|
|
6405
|
+
type: "string",
|
|
6406
|
+
description: "Comma-separated user IDs e.g. 1001,1002"
|
|
6407
|
+
}
|
|
6408
|
+
}
|
|
6409
|
+
},
|
|
6410
|
+
handler: async (rawArgs, context) => {
|
|
6411
|
+
const args = asRecord(rawArgs);
|
|
6412
|
+
const instId = readString(args, "instId");
|
|
6413
|
+
const instCcy = readString(args, "instCcy");
|
|
6414
|
+
if (!instId && !instCcy) {
|
|
6415
|
+
throw new ValidationError('Either "instId" or "instCcy" is required for smartmoney_get_signal.');
|
|
6416
|
+
}
|
|
6417
|
+
const dv = readString(args, "dataVersion");
|
|
6418
|
+
const ts = readString(args, "ts");
|
|
6419
|
+
if (!dv && !ts) {
|
|
6420
|
+
throw new ValidationError('Either "dataVersion" or "ts" is required for smartmoney_get_signal.');
|
|
6421
|
+
}
|
|
6422
|
+
const response = await context.client.privateGet(
|
|
6423
|
+
PATH_SIGNAL,
|
|
6424
|
+
compactObject({
|
|
6425
|
+
instId,
|
|
6426
|
+
instCcy,
|
|
6427
|
+
dataVersion: dv,
|
|
6428
|
+
ts,
|
|
6429
|
+
...readPoolFilters(args),
|
|
6430
|
+
lmtNum: readString(args, "lmtNum"),
|
|
6431
|
+
authorIds: readString(args, "authorIds")
|
|
6432
|
+
}),
|
|
6433
|
+
publicRateLimit("smartmoney_get_signal", 5)
|
|
6434
|
+
);
|
|
6435
|
+
return normalizeResponse(response);
|
|
6436
|
+
}
|
|
6437
|
+
},
|
|
6438
|
+
/* ---------- 3. Signal History ---------- */
|
|
6439
|
+
{
|
|
6440
|
+
name: "smartmoney_get_signal_history",
|
|
6441
|
+
module: "smartmoney",
|
|
6442
|
+
description: "Signal history timeline sorted by ts DESC for trend analysis. Requires instId. Pass ts=Date.now() for latest data, or dataVersion from a prior call. For current snapshot, use smartmoney_get_signal.",
|
|
6443
|
+
isWrite: false,
|
|
6444
|
+
inputSchema: {
|
|
6445
|
+
type: "object",
|
|
6446
|
+
properties: {
|
|
6447
|
+
instId: {
|
|
6448
|
+
type: "string",
|
|
6449
|
+
description: "e.g. BTC-USDT-SWAP"
|
|
6450
|
+
},
|
|
6451
|
+
dataVersion: {
|
|
6452
|
+
type: "string",
|
|
6453
|
+
description: "yyyyMMddHHmm UTC (or use ts)"
|
|
6454
|
+
},
|
|
6455
|
+
ts: {
|
|
6456
|
+
type: "string",
|
|
6457
|
+
description: "Timestamp ms (or use dataVersion)"
|
|
6458
|
+
},
|
|
6459
|
+
granularity: {
|
|
6460
|
+
type: "string",
|
|
6461
|
+
description: "1h or 1d (default 1h)"
|
|
6462
|
+
},
|
|
6463
|
+
limit: {
|
|
6464
|
+
type: "string",
|
|
6465
|
+
description: "Data points 1-500 (default 24)"
|
|
6466
|
+
},
|
|
6467
|
+
...SIGNAL_POOL_FILTER_PROPS
|
|
6468
|
+
},
|
|
6469
|
+
required: ["instId"]
|
|
6470
|
+
},
|
|
6471
|
+
handler: async (rawArgs, context) => {
|
|
6472
|
+
const args = asRecord(rawArgs);
|
|
6473
|
+
const dv = readString(args, "dataVersion");
|
|
6474
|
+
const ts = readString(args, "ts");
|
|
6475
|
+
if (!dv && !ts) {
|
|
6476
|
+
throw new ValidationError('Either "dataVersion" or "ts" is required for smartmoney_get_signal_history.');
|
|
6477
|
+
}
|
|
6478
|
+
const response = await context.client.privateGet(
|
|
6479
|
+
PATH_SIGNAL_HISTORY,
|
|
6480
|
+
compactObject({
|
|
6481
|
+
instId: requireString(args, "instId"),
|
|
6482
|
+
dataVersion: dv,
|
|
6483
|
+
ts,
|
|
6484
|
+
granularity: readString(args, "granularity"),
|
|
6485
|
+
limit: readString(args, "limit"),
|
|
6486
|
+
...readPoolFilters(args)
|
|
6487
|
+
}),
|
|
6488
|
+
publicRateLimit("smartmoney_get_signal_history", 5)
|
|
6489
|
+
);
|
|
6490
|
+
return normalizeResponse(response);
|
|
6491
|
+
}
|
|
6492
|
+
},
|
|
6493
|
+
/* ---------- 4. Traders (list) ---------- */
|
|
6494
|
+
{
|
|
6495
|
+
name: "smartmoney_get_traders",
|
|
6496
|
+
module: "smartmoney",
|
|
6497
|
+
description: "List/filter leaderboard traders. For single trader detail: smartmoney_get_trader_detail.",
|
|
6498
|
+
isWrite: false,
|
|
6499
|
+
inputSchema: {
|
|
6500
|
+
type: "object",
|
|
6501
|
+
properties: {
|
|
6502
|
+
dataVersion: {
|
|
6503
|
+
type: "string",
|
|
6504
|
+
description: "yyyyMMddHHmm, omit=latest"
|
|
6505
|
+
},
|
|
6506
|
+
...LEADERBOARD_POOL_FILTER_PROPS,
|
|
6507
|
+
authorIds: {
|
|
6508
|
+
type: "string",
|
|
6509
|
+
description: "Comma-separated author IDs"
|
|
6510
|
+
},
|
|
6511
|
+
after: {
|
|
6512
|
+
type: "string",
|
|
6513
|
+
description: "Cursor after this authorId"
|
|
6514
|
+
},
|
|
6515
|
+
before: {
|
|
6516
|
+
type: "string",
|
|
6517
|
+
description: "Cursor before this authorId"
|
|
6518
|
+
},
|
|
6519
|
+
limit: {
|
|
6520
|
+
type: "string",
|
|
6521
|
+
description: "Max results 1-100"
|
|
6522
|
+
}
|
|
6523
|
+
}
|
|
6524
|
+
},
|
|
6525
|
+
handler: async (rawArgs, context) => {
|
|
6526
|
+
const args = asRecord(rawArgs);
|
|
6527
|
+
const response = await context.client.privateGet(
|
|
6528
|
+
PATH_LEADERBOARD,
|
|
6529
|
+
compactObject({
|
|
6530
|
+
dataVersion: readString(args, "dataVersion"),
|
|
6531
|
+
...readPoolFilters(args),
|
|
6532
|
+
authorIds: readString(args, "authorIds"),
|
|
6533
|
+
after: readString(args, "after"),
|
|
6534
|
+
before: readString(args, "before"),
|
|
6535
|
+
limit: readString(args, "limit")
|
|
6536
|
+
}),
|
|
6537
|
+
publicRateLimit("smartmoney_get_traders", 5)
|
|
6538
|
+
);
|
|
6539
|
+
const normalized = normalizeResponse(response);
|
|
6540
|
+
return { ...normalized, data: extractLeaderboardData(normalized.data) };
|
|
6541
|
+
}
|
|
6542
|
+
},
|
|
6543
|
+
/* ---------- 5. Trader Detail (composite) ---------- */
|
|
6544
|
+
{
|
|
6545
|
+
name: "smartmoney_get_trader_detail",
|
|
6546
|
+
module: "smartmoney",
|
|
6547
|
+
description: "Trader portrait: profile + positions + trades. Requires authorId from smartmoney_get_traders. Do NOT use for listing \u2014 use smartmoney_get_traders.",
|
|
6548
|
+
isWrite: false,
|
|
6549
|
+
inputSchema: {
|
|
6550
|
+
type: "object",
|
|
6551
|
+
properties: {
|
|
6552
|
+
authorId: {
|
|
6553
|
+
type: "string",
|
|
6554
|
+
description: "Trader author ID"
|
|
6555
|
+
},
|
|
6556
|
+
period: {
|
|
6557
|
+
type: "string",
|
|
6558
|
+
description: "3|7|30|90 days, omit=all"
|
|
6559
|
+
},
|
|
6560
|
+
instCcy: {
|
|
6561
|
+
type: "string",
|
|
6562
|
+
description: "Currency filter e.g. BTC"
|
|
6563
|
+
},
|
|
6564
|
+
tradeLimit: {
|
|
6565
|
+
type: "string",
|
|
6566
|
+
description: "Max trades 1-100"
|
|
6567
|
+
}
|
|
6568
|
+
},
|
|
6569
|
+
required: ["authorId"]
|
|
6570
|
+
},
|
|
6571
|
+
handler: async (rawArgs, context) => {
|
|
6572
|
+
const args = asRecord(rawArgs);
|
|
6573
|
+
const authorId = requireString(args, "authorId");
|
|
6574
|
+
const period = readString(args, "period");
|
|
6575
|
+
const instCcy = readString(args, "instCcy");
|
|
6576
|
+
const tradeLimit = readString(args, "tradeLimit");
|
|
6577
|
+
const [profileRes, positionsRes, tradesRes] = await Promise.all([
|
|
6578
|
+
context.client.privateGet(
|
|
6579
|
+
PATH_LEADERBOARD,
|
|
6580
|
+
compactObject({ authorIds: authorId, period }),
|
|
6581
|
+
publicRateLimit("smartmoney_get_traders", 5)
|
|
6582
|
+
),
|
|
6583
|
+
context.client.privateGet(
|
|
6584
|
+
PATH_POSITION_CURRENT,
|
|
6585
|
+
compactObject({ authorId, instCcy }),
|
|
6586
|
+
publicRateLimit("smartmoney_trader_positions", 5)
|
|
6587
|
+
),
|
|
6588
|
+
context.client.privateGet(
|
|
6589
|
+
PATH_TRADE_RECORDS,
|
|
6590
|
+
compactObject({ authorId, instCcy, limit: tradeLimit }),
|
|
6591
|
+
publicRateLimit("smartmoney_trade_records", 5)
|
|
6592
|
+
)
|
|
6593
|
+
]);
|
|
6594
|
+
const profileNorm = normalizeResponse(profileRes);
|
|
6595
|
+
const positionsNorm = normalizeResponse(positionsRes);
|
|
6596
|
+
const tradesNorm = normalizeResponse(tradesRes);
|
|
6597
|
+
return {
|
|
6598
|
+
endpoint: "smartmoney_get_trader_detail (composite)",
|
|
6599
|
+
requestTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6600
|
+
data: {
|
|
6601
|
+
profile: extractLeaderboardData(profileNorm.data),
|
|
6602
|
+
positions: positionsNorm.data,
|
|
6603
|
+
trades: tradesNorm.data
|
|
6604
|
+
}
|
|
6605
|
+
};
|
|
6606
|
+
}
|
|
6607
|
+
}
|
|
6608
|
+
];
|
|
6609
|
+
return tools;
|
|
6610
|
+
}
|
|
6087
6611
|
function buildContractTradeTools(cfg) {
|
|
6088
6612
|
const { prefix, module, label, instTypes, instIdExample } = cfg;
|
|
6089
6613
|
const [defaultType, otherType] = instTypes;
|
|
@@ -6425,31 +6949,58 @@ function buildContractTradeTools(cfg) {
|
|
|
6425
6949
|
{
|
|
6426
6950
|
name: n("set_leverage"),
|
|
6427
6951
|
module,
|
|
6428
|
-
description: `Set leverage for a ${label} instrument or position. [CAUTION] Changes risk parameters
|
|
6952
|
+
description: `Set leverage for a ${label} instrument or position. [CAUTION] Changes risk parameters.
|
|
6953
|
+
Scenarios (SWAP/FUTURES only):
|
|
6954
|
+
\u2022 cross + any instId under the index \u2192 sets leverage at the index level
|
|
6955
|
+
\u2022 isolated + buy-sell (net) posMode \u2192 instId only
|
|
6956
|
+
\u2022 isolated + long-short (hedge) posMode \u2192 instId + posSide=long|short (BOTH directions must be set separately)
|
|
6957
|
+
Not supported: PORTFOLIO MARGIN accounts cannot adjust cross leverage for SWAP/FUTURES \u2014 the request will be rejected by OKX. Use account_get_config first if unsure of the account's margin mode.`,
|
|
6429
6958
|
isWrite: true,
|
|
6430
6959
|
inputSchema: {
|
|
6431
6960
|
type: "object",
|
|
6432
6961
|
properties: {
|
|
6433
6962
|
instId: { type: "string", description: instIdExample },
|
|
6434
|
-
lever: {
|
|
6963
|
+
lever: {
|
|
6964
|
+
type: "string",
|
|
6965
|
+
description: "Leverage multiplier as a positive number string, e.g. '10'. Max value depends on the instrument (query market_get_instruments \u2192 lever)."
|
|
6966
|
+
},
|
|
6435
6967
|
mgnMode: { type: "string", enum: ["cross", "isolated"] },
|
|
6436
6968
|
posSide: {
|
|
6437
6969
|
type: "string",
|
|
6438
|
-
enum: ["long", "short"
|
|
6439
|
-
description: "
|
|
6970
|
+
enum: ["long", "short"],
|
|
6971
|
+
description: "REQUIRED when mgnMode=isolated AND the account is in hedge (long/short) position mode. Use 'long' or 'short' \u2014 setting one side does NOT auto-apply to the other. Omit entirely for one-way (net) position mode or for cross margin."
|
|
6440
6972
|
}
|
|
6441
6973
|
},
|
|
6442
6974
|
required: ["instId", "lever", "mgnMode"]
|
|
6443
6975
|
},
|
|
6444
6976
|
handler: async (rawArgs, context) => {
|
|
6445
6977
|
const args = asRecord(rawArgs);
|
|
6978
|
+
const instId = requireString(args, "instId");
|
|
6979
|
+
const leverRaw = requireString(args, "lever");
|
|
6980
|
+
const leverNum = Number(leverRaw);
|
|
6981
|
+
if (!Number.isFinite(leverNum) || leverNum <= 0) {
|
|
6982
|
+
throw new ValidationError(
|
|
6983
|
+
`Parameter "lever" must be a positive number string, got "${leverRaw}".`
|
|
6984
|
+
);
|
|
6985
|
+
}
|
|
6986
|
+
const mgnMode = requireString(args, "mgnMode");
|
|
6987
|
+
assertEnum(mgnMode, "mgnMode", ["cross", "isolated"]);
|
|
6988
|
+
const posSide = readString(args, "posSide");
|
|
6989
|
+
if (posSide !== void 0) {
|
|
6990
|
+
assertEnum(posSide, "posSide", ["long", "short"]);
|
|
6991
|
+
if (mgnMode === "cross") {
|
|
6992
|
+
throw new ValidationError(
|
|
6993
|
+
`posSide="${posSide}" is only valid with mgnMode="isolated" in hedge mode. Omit posSide for cross margin.`
|
|
6994
|
+
);
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6446
6997
|
const response = await context.client.privatePost(
|
|
6447
6998
|
"/api/v5/account/set-leverage",
|
|
6448
6999
|
compactObject({
|
|
6449
|
-
instId
|
|
6450
|
-
lever:
|
|
6451
|
-
mgnMode
|
|
6452
|
-
posSide
|
|
7000
|
+
instId,
|
|
7001
|
+
lever: leverRaw,
|
|
7002
|
+
mgnMode,
|
|
7003
|
+
posSide
|
|
6453
7004
|
}),
|
|
6454
7005
|
privateRateLimit(n("set_leverage"), 20)
|
|
6455
7006
|
);
|
|
@@ -7322,7 +7873,7 @@ function registerMarketFilterTools() {
|
|
|
7322
7873
|
sortBy: {
|
|
7323
7874
|
type: "string",
|
|
7324
7875
|
enum: ["last", "chg24hPct", "marketCapUsd", "volUsd24h", "fundingRate", "oiUsd", "listTime"],
|
|
7325
|
-
description: "Sort field. Default: volUsd24h. Note: marketCapUsd is only meaningful for SPOT (null for SWAP/FUTURES)."
|
|
7876
|
+
description: "Sort field. Default: volUsd24h. Note: marketCapUsd is only meaningful for SPOT (null for SWAP/FUTURES). To rank by OI *change* (oiDeltaPct / absOiDeltaPct), use market_filter_oi_change \u2014 market_filter only sorts by the current snapshot."
|
|
7326
7877
|
},
|
|
7327
7878
|
sortOrder: {
|
|
7328
7879
|
type: "string",
|
|
@@ -7435,7 +7986,7 @@ function registerMarketFilterTools() {
|
|
|
7435
7986
|
bar: {
|
|
7436
7987
|
type: "string",
|
|
7437
7988
|
enum: [...OI_BARS],
|
|
7438
|
-
description: "Bar window for OI change computation: 5m, 15m, 1H, 4H, 1D. Default: 1H"
|
|
7989
|
+
description: "Bar window for OI change computation: 5m, 15m, 1H, 4H, 1D (case-insensitive on server, but send canonical form here). Default: 1H"
|
|
7439
7990
|
},
|
|
7440
7991
|
// Filters
|
|
7441
7992
|
minOiUsd: {
|
|
@@ -7453,8 +8004,8 @@ function registerMarketFilterTools() {
|
|
|
7453
8004
|
// Sort / pagination
|
|
7454
8005
|
sortBy: {
|
|
7455
8006
|
type: "string",
|
|
7456
|
-
enum: ["oiUsd", "oiDeltaUsd", "oiDeltaPct", "volUsd24h", "last"],
|
|
7457
|
-
description: "Sort field. Default: oiDeltaPct (largest movers first)"
|
|
8007
|
+
enum: ["oiUsd", "oiDeltaUsd", "oiDeltaPct", "absOiDeltaPct", "volUsd24h", "fundingRate", "last"],
|
|
8008
|
+
description: "Sort field. Default: oiDeltaPct (largest movers first, signed \u2014 longs and shorts separate). Use absOiDeltaPct to sort by |oiDeltaPct| (largest-magnitude moves regardless of direction). fundingRate is also supported for SWAP. Do NOT use the market_filter tool's sort fields (chg24hPct, marketCapUsd, listTime) here \u2014 they are not in the OI-change Row."
|
|
7458
8009
|
},
|
|
7459
8010
|
sortOrder: {
|
|
7460
8011
|
type: "string",
|
|
@@ -7510,7 +8061,7 @@ var D_COINS_SENTIMENT = 'Comma-separated uppercase ticker symbols, max 20 (e.g.
|
|
|
7510
8061
|
var D_LANGUAGE = "Content language: zh-CN or en-US. Infer from user's message. No server default.";
|
|
7511
8062
|
var D_BEGIN = "Start time, Unix epoch milliseconds. API defaults to 72 hours ago when omitted. Pass explicitly for older topics (e.g. 'last 30 days'). Max range: 180 days. Parse relative time if given.";
|
|
7512
8063
|
var D_END = "End time, Unix epoch milliseconds. Parse relative time if given. Omit for no upper bound.";
|
|
7513
|
-
var D_IMPORTANCE = "Importance filter: high
|
|
8064
|
+
var D_IMPORTANCE = "Importance filter: 'low' returns all news (both low and high importance); 'high' narrows to major/breaking news only. Omitted \u2192 server default (high-only). Default to 'low' for broad browsing; pass 'high' only when the user explicitly asks for major news.";
|
|
7514
8065
|
var D_PLATFORM = "Filter by news source. Use values from news_get_domains (e.g. blockbeats, odaily_flash). Omit for all sources.";
|
|
7515
8066
|
var D_LIMIT = "Number of results (default 10, max 50).";
|
|
7516
8067
|
function registerNewsTools() {
|
|
@@ -7521,7 +8072,7 @@ function registerNewsTools() {
|
|
|
7521
8072
|
{
|
|
7522
8073
|
name: "news_get_latest",
|
|
7523
8074
|
module: "news",
|
|
7524
|
-
description: "Get crypto news sorted by time.
|
|
8075
|
+
description: "Get crypto news sorted by time. For broad browsing ('what happened recently', 'latest news', 'any big news today'), pass importance='low' to include both high and low importance. Server default (when importance omitted) returns only high-importance news. For coin-specific news, use news_get_by_coin instead.",
|
|
7525
8076
|
isWrite: false,
|
|
7526
8077
|
inputSchema: {
|
|
7527
8078
|
type: "object",
|
|
@@ -9182,6 +9733,82 @@ function registerSpotTradeTools() {
|
|
|
9182
9733
|
);
|
|
9183
9734
|
return normalizeResponse(response);
|
|
9184
9735
|
}
|
|
9736
|
+
},
|
|
9737
|
+
// ── set_leverage (SPOT margin: instId-level isolated OR ccy-level cross) ──
|
|
9738
|
+
// Covers OKX scenarios 1–5 (everything except SWAP/FUTURES, which are in
|
|
9739
|
+
// contract-trade.ts). Callers supply exactly one of {instId, ccy}:
|
|
9740
|
+
// • instId + isolated → scenario 1 (pair-level margin)
|
|
9741
|
+
// • instId + cross → scenario 3 (contract-mode pair-level cross margin)
|
|
9742
|
+
// • ccy + cross → scenarios 2 / 4 / 5 (spot/multi-ccy/PM currency-level cross)
|
|
9743
|
+
// Not applicable: posSide (spot has no long/short hedge).
|
|
9744
|
+
{
|
|
9745
|
+
name: "spot_set_leverage",
|
|
9746
|
+
module: "spot",
|
|
9747
|
+
description: "Set leverage for SPOT margin trading. Provide exactly ONE of instId (pair-level) or ccy (currency-level cross, requires borrow-enabled account / multi-ccy / portfolio margin). [CAUTION] Changes risk parameters.\nScenarios:\n \u2022 instId + mgnMode=isolated \u2192 pair-level isolated margin\n \u2022 instId + mgnMode=cross \u2192 pair-level cross margin (contract-mode account)\n \u2022 ccy + mgnMode=cross \u2192 currency-level cross margin (spot-with-borrow / multi-ccy / portfolio margin)\nWhen ccy is supplied, mgnMode MUST be cross. posSide is never applicable to spot margin.",
|
|
9748
|
+
isWrite: true,
|
|
9749
|
+
inputSchema: {
|
|
9750
|
+
type: "object",
|
|
9751
|
+
properties: {
|
|
9752
|
+
instId: {
|
|
9753
|
+
type: "string",
|
|
9754
|
+
description: "Spot pair, e.g. BTC-USDT. Provide instId OR ccy, not both."
|
|
9755
|
+
},
|
|
9756
|
+
ccy: {
|
|
9757
|
+
type: "string",
|
|
9758
|
+
description: "Margin currency, e.g. BTC. Required only for currency-level cross margin (borrow-enabled / multi-ccy / portfolio margin). Mutually exclusive with instId."
|
|
9759
|
+
},
|
|
9760
|
+
lever: {
|
|
9761
|
+
type: "string",
|
|
9762
|
+
description: "Leverage multiplier as a positive number string, e.g. '3'. Max depends on the pair (query market_get_instruments \u2192 lever) or the account policy for ccy-level."
|
|
9763
|
+
},
|
|
9764
|
+
mgnMode: {
|
|
9765
|
+
type: "string",
|
|
9766
|
+
enum: ["cross", "isolated"],
|
|
9767
|
+
description: "cross or isolated. Must be cross when ccy is supplied."
|
|
9768
|
+
}
|
|
9769
|
+
},
|
|
9770
|
+
required: ["lever", "mgnMode"]
|
|
9771
|
+
},
|
|
9772
|
+
handler: async (rawArgs, context) => {
|
|
9773
|
+
const args = asRecord(rawArgs);
|
|
9774
|
+
const instId = readString(args, "instId");
|
|
9775
|
+
const ccy = readString(args, "ccy");
|
|
9776
|
+
if (!instId && !ccy) {
|
|
9777
|
+
throw new ValidationError(
|
|
9778
|
+
`Missing required parameter: provide either "instId" (pair-level) or "ccy" (currency-level cross margin).`
|
|
9779
|
+
);
|
|
9780
|
+
}
|
|
9781
|
+
if (instId && ccy) {
|
|
9782
|
+
throw new ValidationError(
|
|
9783
|
+
`Parameters "instId" and "ccy" are mutually exclusive \u2014 provide only one. instId sets pair-level leverage; ccy sets currency-level cross margin leverage.`
|
|
9784
|
+
);
|
|
9785
|
+
}
|
|
9786
|
+
const leverRaw = requireString(args, "lever");
|
|
9787
|
+
const leverNum = Number(leverRaw);
|
|
9788
|
+
if (!Number.isFinite(leverNum) || leverNum <= 0) {
|
|
9789
|
+
throw new ValidationError(
|
|
9790
|
+
`Parameter "lever" must be a positive number string, got "${leverRaw}".`
|
|
9791
|
+
);
|
|
9792
|
+
}
|
|
9793
|
+
const mgnMode = requireString(args, "mgnMode");
|
|
9794
|
+
assertEnum(mgnMode, "mgnMode", ["cross", "isolated"]);
|
|
9795
|
+
if (ccy && mgnMode !== "cross") {
|
|
9796
|
+
throw new ValidationError(
|
|
9797
|
+
`When "ccy" is supplied, "mgnMode" must be "cross" (currency-level leverage only applies to cross margin).`
|
|
9798
|
+
);
|
|
9799
|
+
}
|
|
9800
|
+
const response = await context.client.privatePost(
|
|
9801
|
+
"/api/v5/account/set-leverage",
|
|
9802
|
+
compactObject({
|
|
9803
|
+
instId,
|
|
9804
|
+
ccy,
|
|
9805
|
+
lever: leverRaw,
|
|
9806
|
+
mgnMode
|
|
9807
|
+
}),
|
|
9808
|
+
privateRateLimit("spot_set_leverage", 20)
|
|
9809
|
+
);
|
|
9810
|
+
return normalizeResponse(response);
|
|
9811
|
+
}
|
|
9185
9812
|
}
|
|
9186
9813
|
];
|
|
9187
9814
|
}
|
|
@@ -9313,6 +9940,7 @@ function allToolSpecs() {
|
|
|
9313
9940
|
...registerNewsTools(),
|
|
9314
9941
|
...registerBotTools(),
|
|
9315
9942
|
...registerAllEarnTools(),
|
|
9943
|
+
...registerSmartmoneyTools(),
|
|
9316
9944
|
...registerAuditTools(),
|
|
9317
9945
|
...registerSkillsTools()
|
|
9318
9946
|
];
|
|
@@ -9757,7 +10385,7 @@ var _require = createRequire(import.meta.url);
|
|
|
9757
10385
|
var pkg = _require("../package.json");
|
|
9758
10386
|
var SERVER_NAME = "okx-trade-mcp";
|
|
9759
10387
|
var SERVER_VERSION = pkg.version;
|
|
9760
|
-
var GIT_HASH = true ? "
|
|
10388
|
+
var GIT_HASH = true ? "e0ee5a96" : "dev";
|
|
9761
10389
|
|
|
9762
10390
|
// src/server.ts
|
|
9763
10391
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -9927,11 +10555,14 @@ Usage: okx-trade-mcp [options]
|
|
|
9927
10555
|
|
|
9928
10556
|
Options:
|
|
9929
10557
|
--modules <list> Comma-separated list of modules to load
|
|
9930
|
-
Available: market, spot, swap, futures, option, account,
|
|
10558
|
+
Available: market, spot, swap, futures, option, account,
|
|
10559
|
+
event, news, smartmoney, skills,
|
|
10560
|
+
earn.savings, earn.onchain, earn.dcd, earn.autoearn, earn.flash,
|
|
9931
10561
|
bot.grid, bot.dca
|
|
9932
|
-
Alias: "bot" =
|
|
10562
|
+
Alias: "bot" = bot.grid + bot.dca
|
|
10563
|
+
"earn" / "earn.all" = all earn sub-modules
|
|
9933
10564
|
Special: "all" loads all modules
|
|
9934
|
-
Default: spot,swap,option,account,bot.grid
|
|
10565
|
+
Default: spot,swap,option,account,bot.grid,skills
|
|
9935
10566
|
|
|
9936
10567
|
--profile <name> Profile to load from ${configFilePath()}
|
|
9937
10568
|
Falls back to default_profile in config, then "default"
|