@okx_ai/okx-trade-cli 1.3.0-beta.3 → 1.3.0-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 +922 -203
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +3 -2
package/dist/index.js
CHANGED
|
@@ -866,6 +866,9 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
|
|
|
866
866
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync4 } from "fs";
|
|
867
867
|
import { join as join7 } from "path";
|
|
868
868
|
import { homedir as homedir5 } from "os";
|
|
869
|
+
import fs2 from "fs";
|
|
870
|
+
import path2 from "path";
|
|
871
|
+
import os2 from "os";
|
|
869
872
|
import * as fs3 from "fs";
|
|
870
873
|
import * as path3 from "path";
|
|
871
874
|
import * as os3 from "os";
|
|
@@ -939,16 +942,8 @@ function writeCache(hostname, entry, cachePath = DOH_CACHE_PATH) {
|
|
|
939
942
|
}
|
|
940
943
|
}
|
|
941
944
|
var FAILED_NODE_TTL_MS = 60 * 60 * 1e3;
|
|
942
|
-
function classifyAndCache(node, hostname, failedNodes) {
|
|
945
|
+
function classifyAndCache(node, hostname, failedNodes, cachePath) {
|
|
943
946
|
if (!node) {
|
|
944
|
-
if (failedNodes.length > 0) {
|
|
945
|
-
writeCache(hostname, {
|
|
946
|
-
mode: "direct",
|
|
947
|
-
node: null,
|
|
948
|
-
failedNodes,
|
|
949
|
-
updatedAt: Date.now()
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
947
|
return { mode: null, node: null };
|
|
953
948
|
}
|
|
954
949
|
if (node.ip === hostname || node.host === hostname) {
|
|
@@ -957,7 +952,7 @@ function classifyAndCache(node, hostname, failedNodes) {
|
|
|
957
952
|
node: null,
|
|
958
953
|
failedNodes,
|
|
959
954
|
updatedAt: Date.now()
|
|
960
|
-
});
|
|
955
|
+
}, cachePath);
|
|
961
956
|
return { mode: "direct", node: null };
|
|
962
957
|
}
|
|
963
958
|
writeCache(hostname, {
|
|
@@ -965,7 +960,7 @@ function classifyAndCache(node, hostname, failedNodes) {
|
|
|
965
960
|
node,
|
|
966
961
|
failedNodes,
|
|
967
962
|
updatedAt: Date.now()
|
|
968
|
-
});
|
|
963
|
+
}, cachePath);
|
|
969
964
|
return { mode: "proxy", node };
|
|
970
965
|
}
|
|
971
966
|
function getActiveFailedNodes(nodes) {
|
|
@@ -973,8 +968,8 @@ function getActiveFailedNodes(nodes) {
|
|
|
973
968
|
const now = Date.now();
|
|
974
969
|
return nodes.filter((n) => now - n.failedAt < FAILED_NODE_TTL_MS);
|
|
975
970
|
}
|
|
976
|
-
|
|
977
|
-
const entry = readCache(hostname);
|
|
971
|
+
function resolveDoh(hostname, cachePath) {
|
|
972
|
+
const entry = readCache(hostname, cachePath);
|
|
978
973
|
if (entry) {
|
|
979
974
|
if (entry.mode === "direct") {
|
|
980
975
|
return { mode: "direct", node: null };
|
|
@@ -985,14 +980,15 @@ async function resolveDoh(hostname) {
|
|
|
985
980
|
}
|
|
986
981
|
return { mode: null, node: null };
|
|
987
982
|
}
|
|
988
|
-
async function reResolveDoh(hostname, failedIp, userAgent) {
|
|
989
|
-
const entry = readCache(hostname);
|
|
983
|
+
async function reResolveDoh(hostname, failedIp, userAgent, cachePath) {
|
|
984
|
+
const entry = readCache(hostname, cachePath);
|
|
990
985
|
const active = getActiveFailedNodes(entry?.failedNodes);
|
|
991
986
|
const now = Date.now();
|
|
992
|
-
const
|
|
987
|
+
const alreadyFailed = failedIp && active.some((n) => n.ip === failedIp);
|
|
988
|
+
const failedNodes = failedIp && !alreadyFailed ? [...active, { ip: failedIp, failedAt: now }] : active;
|
|
993
989
|
const excludeIps = failedNodes.map((n) => n.ip);
|
|
994
990
|
const node = await execDohBinary(hostname, excludeIps, userAgent);
|
|
995
|
-
return classifyAndCache(node, hostname, failedNodes);
|
|
991
|
+
return classifyAndCache(node, hostname, failedNodes, cachePath);
|
|
996
992
|
}
|
|
997
993
|
function getNow() {
|
|
998
994
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1231,12 +1227,12 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1231
1227
|
* Lazily resolve the DoH proxy node on the first request.
|
|
1232
1228
|
* Uses cache-first strategy via the resolver.
|
|
1233
1229
|
*/
|
|
1234
|
-
|
|
1230
|
+
ensureDoh() {
|
|
1235
1231
|
if (this.dohResolved || this.dispatcher) return;
|
|
1236
1232
|
this.dohResolved = true;
|
|
1237
1233
|
try {
|
|
1238
1234
|
const { hostname, protocol } = new URL(this.config.baseUrl);
|
|
1239
|
-
const result =
|
|
1235
|
+
const result = resolveDoh(hostname);
|
|
1240
1236
|
if (!result.mode) {
|
|
1241
1237
|
this.directUnverified = true;
|
|
1242
1238
|
if (this.config.verbose) {
|
|
@@ -1299,6 +1295,7 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1299
1295
|
const result = await reResolveDoh(hostname, failedIp, this.dohUserAgent);
|
|
1300
1296
|
if (result.mode === "proxy" && result.node) {
|
|
1301
1297
|
this.applyDohNode(result.node, protocol);
|
|
1298
|
+
this.dohRetried = false;
|
|
1302
1299
|
return true;
|
|
1303
1300
|
}
|
|
1304
1301
|
} catch {
|
|
@@ -1487,7 +1484,7 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1487
1484
|
* Security: validates Content-Type and enforces maxBytes limit.
|
|
1488
1485
|
*/
|
|
1489
1486
|
async privatePostBinary(path42, body, opts) {
|
|
1490
|
-
|
|
1487
|
+
this.ensureDoh();
|
|
1491
1488
|
const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
|
|
1492
1489
|
const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
|
|
1493
1490
|
const bodyJson = body ? JSON.stringify(body) : "";
|
|
@@ -1564,8 +1561,52 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1564
1561
|
// ---------------------------------------------------------------------------
|
|
1565
1562
|
// JSON request
|
|
1566
1563
|
// ---------------------------------------------------------------------------
|
|
1564
|
+
/**
|
|
1565
|
+
* Handle network error during a JSON request: refresh DoH and maybe retry.
|
|
1566
|
+
* Always either returns a retry result or throws NetworkError.
|
|
1567
|
+
*/
|
|
1568
|
+
async handleRequestNetworkError(error, reqConfig, requestPath, t0) {
|
|
1569
|
+
if (!this.dohRetried) {
|
|
1570
|
+
if (this.config.verbose) {
|
|
1571
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
1572
|
+
vlog(`Network failure, refreshing DoH: ${cause}`);
|
|
1573
|
+
}
|
|
1574
|
+
const shouldRetry = await this.handleDohNetworkFailure();
|
|
1575
|
+
if (shouldRetry && reqConfig.method === "GET") {
|
|
1576
|
+
return this.request(reqConfig);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
if (this.config.verbose) {
|
|
1580
|
+
const elapsed = Date.now() - t0;
|
|
1581
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
1582
|
+
vlog(`\u2717 NetworkError after ${elapsed}ms: ${cause}`);
|
|
1583
|
+
}
|
|
1584
|
+
throw new NetworkError(
|
|
1585
|
+
`Failed to call OKX endpoint ${reqConfig.method} ${requestPath}.`,
|
|
1586
|
+
`${reqConfig.method} ${requestPath}`,
|
|
1587
|
+
error
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* After a successful HTTP response on direct connection, cache mode=direct.
|
|
1592
|
+
* (Even if the business response is an error, the network path is valid.)
|
|
1593
|
+
*/
|
|
1594
|
+
cacheDirectConnectionIfNeeded() {
|
|
1595
|
+
if (!this.directUnverified || this.dohNode) return;
|
|
1596
|
+
this.directUnverified = false;
|
|
1597
|
+
const { hostname } = new URL(this.config.baseUrl);
|
|
1598
|
+
writeCache(hostname, {
|
|
1599
|
+
mode: "direct",
|
|
1600
|
+
node: null,
|
|
1601
|
+
failedNodes: [],
|
|
1602
|
+
updatedAt: Date.now()
|
|
1603
|
+
});
|
|
1604
|
+
if (this.config.verbose) {
|
|
1605
|
+
vlog("DoH: direct connection succeeded, cached mode=direct");
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1567
1608
|
async request(reqConfig) {
|
|
1568
|
-
|
|
1609
|
+
this.ensureDoh();
|
|
1569
1610
|
const queryString = buildQueryString(reqConfig.query);
|
|
1570
1611
|
const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
|
|
1571
1612
|
const url = `${this.activeBaseUrl}${requestPath}`;
|
|
@@ -1591,42 +1632,12 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1591
1632
|
};
|
|
1592
1633
|
response = await fetch(url, fetchOptions);
|
|
1593
1634
|
} catch (error) {
|
|
1594
|
-
|
|
1595
|
-
if (this.config.verbose) {
|
|
1596
|
-
vlog(`Network failure, refreshing DoH: ${error instanceof Error ? error.message : String(error)}`);
|
|
1597
|
-
}
|
|
1598
|
-
const shouldRetry = await this.handleDohNetworkFailure();
|
|
1599
|
-
if (shouldRetry && reqConfig.method === "GET") {
|
|
1600
|
-
return this.request(reqConfig);
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
if (this.config.verbose) {
|
|
1604
|
-
const elapsed2 = Date.now() - t0;
|
|
1605
|
-
const cause = error instanceof Error ? error.message : String(error);
|
|
1606
|
-
vlog(`\u2717 NetworkError after ${elapsed2}ms: ${cause}`);
|
|
1607
|
-
}
|
|
1608
|
-
throw new NetworkError(
|
|
1609
|
-
`Failed to call OKX endpoint ${reqConfig.method} ${requestPath}.`,
|
|
1610
|
-
`${reqConfig.method} ${requestPath}`,
|
|
1611
|
-
error
|
|
1612
|
-
);
|
|
1635
|
+
return await this.handleRequestNetworkError(error, reqConfig, requestPath, t0);
|
|
1613
1636
|
}
|
|
1614
1637
|
const rawText = await response.text();
|
|
1615
1638
|
const elapsed = Date.now() - t0;
|
|
1616
1639
|
const traceId = extractTraceId(response.headers);
|
|
1617
|
-
|
|
1618
|
-
this.directUnverified = false;
|
|
1619
|
-
const { hostname: h } = new URL(this.config.baseUrl);
|
|
1620
|
-
writeCache(h, {
|
|
1621
|
-
mode: "direct",
|
|
1622
|
-
node: null,
|
|
1623
|
-
failedNodes: [],
|
|
1624
|
-
updatedAt: Date.now()
|
|
1625
|
-
});
|
|
1626
|
-
if (this.config.verbose) {
|
|
1627
|
-
vlog("DoH: direct connection succeeded, cached mode=direct");
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1640
|
+
this.cacheDirectConnectionIfNeeded();
|
|
1630
1641
|
return this.processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath);
|
|
1631
1642
|
}
|
|
1632
1643
|
};
|
|
@@ -1766,14 +1777,137 @@ var INDICATOR_BARS = [
|
|
|
1766
1777
|
"1Wutc"
|
|
1767
1778
|
];
|
|
1768
1779
|
var INDICATOR_CODE_OVERRIDES = {
|
|
1780
|
+
// Aliases
|
|
1781
|
+
"boll": "BB",
|
|
1782
|
+
// server supports BB not BOLL
|
|
1783
|
+
// Names where default rule produces underscores but backend uses no separator
|
|
1769
1784
|
"rainbow": "BTCRAINBOW",
|
|
1770
|
-
|
|
1785
|
+
// default: RAINBOW
|
|
1771
1786
|
"stoch-rsi": "STOCHRSI",
|
|
1772
|
-
|
|
1773
|
-
"
|
|
1774
|
-
//
|
|
1775
|
-
"
|
|
1787
|
+
// default: STOCH_RSI
|
|
1788
|
+
"bull-engulf": "BULLENGULF",
|
|
1789
|
+
// default: BULL_ENGULF
|
|
1790
|
+
"bear-engulf": "BEARENGULF",
|
|
1791
|
+
// default: BEAR_ENGULF
|
|
1792
|
+
"bull-harami": "BULLHARAMI",
|
|
1793
|
+
// default: BULL_HARAMI
|
|
1794
|
+
"bear-harami": "BEARHARAMI",
|
|
1795
|
+
// default: BEAR_HARAMI
|
|
1796
|
+
"bull-harami-cross": "BULLHARAMICROSS",
|
|
1797
|
+
// default: BULL_HARAMI_CROSS
|
|
1798
|
+
"bear-harami-cross": "BEARHARAMICROSS",
|
|
1799
|
+
// default: BEAR_HARAMI_CROSS
|
|
1800
|
+
"three-soldiers": "THREESOLDIERS",
|
|
1801
|
+
// default: THREE_SOLDIERS
|
|
1802
|
+
"three-crows": "THREECROWS",
|
|
1803
|
+
// default: THREE_CROWS
|
|
1804
|
+
"hanging-man": "HANGINGMAN",
|
|
1805
|
+
// default: HANGING_MAN
|
|
1806
|
+
"inverted-hammer": "INVERTEDH",
|
|
1807
|
+
// default: INVERTED_HAMMER (backend uses INVERTEDH)
|
|
1808
|
+
"shooting-star": "SHOOTINGSTAR",
|
|
1809
|
+
// default: SHOOTING_STAR
|
|
1810
|
+
"nvi-pvi": "NVIPVI",
|
|
1811
|
+
// default: NVI_PVI
|
|
1812
|
+
"top-long-short": "TOPLONGSHORT"
|
|
1813
|
+
// default: TOP_LONG_SHORT
|
|
1814
|
+
// Note: range-filter → RANGE_FILTER is correct via the default rule; no override needed.
|
|
1776
1815
|
};
|
|
1816
|
+
var KNOWN_INDICATORS = [
|
|
1817
|
+
// Moving Averages
|
|
1818
|
+
{ name: "ma", description: "Simple Moving Average" },
|
|
1819
|
+
{ name: "ema", description: "Exponential Moving Average" },
|
|
1820
|
+
{ name: "wma", description: "Weighted Moving Average" },
|
|
1821
|
+
{ name: "dema", description: "Double Exponential Moving Average" },
|
|
1822
|
+
{ name: "tema", description: "Triple Exponential Moving Average" },
|
|
1823
|
+
{ name: "zlema", description: "Zero-Lag Exponential Moving Average" },
|
|
1824
|
+
{ name: "hma", description: "Hull Moving Average" },
|
|
1825
|
+
{ name: "kama", description: "Kaufman Adaptive Moving Average" },
|
|
1826
|
+
// Trend
|
|
1827
|
+
{ name: "macd", description: "MACD" },
|
|
1828
|
+
{ name: "sar", description: "Parabolic SAR" },
|
|
1829
|
+
{ name: "adx", description: "Average Directional Index" },
|
|
1830
|
+
{ name: "aroon", description: "Aroon Indicator" },
|
|
1831
|
+
{ name: "cci", description: "Commodity Channel Index" },
|
|
1832
|
+
{ name: "dpo", description: "Detrended Price Oscillator" },
|
|
1833
|
+
{ name: "envelope", description: "Envelope" },
|
|
1834
|
+
{ name: "halftrend", description: "HalfTrend" },
|
|
1835
|
+
{ name: "alphatrend", description: "AlphaTrend" },
|
|
1836
|
+
// Momentum
|
|
1837
|
+
{ name: "rsi", description: "Relative Strength Index" },
|
|
1838
|
+
{ name: "stoch-rsi", description: "Stochastic RSI" },
|
|
1839
|
+
{ name: "stoch", description: "Stochastic Oscillator" },
|
|
1840
|
+
{ name: "roc", description: "Rate of Change" },
|
|
1841
|
+
{ name: "mom", description: "Momentum" },
|
|
1842
|
+
{ name: "ppo", description: "Price Percentage Oscillator" },
|
|
1843
|
+
{ name: "trix", description: "TRIX" },
|
|
1844
|
+
{ name: "ao", description: "Awesome Oscillator" },
|
|
1845
|
+
{ name: "uo", description: "Ultimate Oscillator" },
|
|
1846
|
+
{ name: "wr", description: "Williams %R" },
|
|
1847
|
+
// Volatility
|
|
1848
|
+
{ name: "bb", description: "Bollinger Bands" },
|
|
1849
|
+
{ name: "boll", description: "Bollinger Bands (alias for bb)" },
|
|
1850
|
+
{ name: "bbwidth", description: "Bollinger Band Width" },
|
|
1851
|
+
{ name: "bbpct", description: "Bollinger Band %B" },
|
|
1852
|
+
{ name: "atr", description: "Average True Range" },
|
|
1853
|
+
{ name: "keltner", description: "Keltner Channel" },
|
|
1854
|
+
{ name: "donchian", description: "Donchian Channel" },
|
|
1855
|
+
{ name: "hv", description: "Historical Volatility" },
|
|
1856
|
+
{ name: "stddev", description: "Standard Deviation" },
|
|
1857
|
+
// Volume
|
|
1858
|
+
{ name: "obv", description: "On-Balance Volume" },
|
|
1859
|
+
{ name: "vwap", description: "Volume Weighted Average Price" },
|
|
1860
|
+
{ name: "mvwap", description: "Moving VWAP" },
|
|
1861
|
+
{ name: "cmf", description: "Chaikin Money Flow" },
|
|
1862
|
+
{ name: "mfi", description: "Money Flow Index" },
|
|
1863
|
+
{ name: "ad", description: "Accumulation/Distribution" },
|
|
1864
|
+
// Statistical
|
|
1865
|
+
{ name: "lr", description: "Linear Regression" },
|
|
1866
|
+
{ name: "slope", description: "Linear Regression Slope" },
|
|
1867
|
+
{ name: "angle", description: "Linear Regression Angle" },
|
|
1868
|
+
{ name: "variance", description: "Variance" },
|
|
1869
|
+
{ name: "meandev", description: "Mean Deviation" },
|
|
1870
|
+
{ name: "sigma", description: "Sigma" },
|
|
1871
|
+
{ name: "stderr", description: "Standard Error" },
|
|
1872
|
+
// Custom
|
|
1873
|
+
{ name: "kdj", description: "KDJ Stochastic Oscillator" },
|
|
1874
|
+
{ name: "supertrend", description: "Supertrend" },
|
|
1875
|
+
// Ichimoku
|
|
1876
|
+
{ name: "tenkan", description: "Ichimoku Tenkan-sen (Conversion Line)" },
|
|
1877
|
+
{ name: "kijun", description: "Ichimoku Kijun-sen (Base Line)" },
|
|
1878
|
+
{ name: "senkoa", description: "Ichimoku Senkou Span A (Leading Span A)" },
|
|
1879
|
+
{ name: "senkob", description: "Ichimoku Senkou Span B (Leading Span B)" },
|
|
1880
|
+
{ name: "chikou", description: "Ichimoku Chikou Span (Lagging Span)" },
|
|
1881
|
+
// Candlestick Patterns
|
|
1882
|
+
{ name: "doji", description: "Doji candlestick pattern" },
|
|
1883
|
+
{ name: "bull-engulf", description: "Bullish Engulfing pattern" },
|
|
1884
|
+
{ name: "bear-engulf", description: "Bearish Engulfing pattern" },
|
|
1885
|
+
{ name: "bull-harami", description: "Bullish Harami pattern" },
|
|
1886
|
+
{ name: "bear-harami", description: "Bearish Harami pattern" },
|
|
1887
|
+
{ name: "bull-harami-cross", description: "Bullish Harami Cross pattern" },
|
|
1888
|
+
{ name: "bear-harami-cross", description: "Bearish Harami Cross pattern" },
|
|
1889
|
+
{ name: "three-soldiers", description: "Three White Soldiers pattern" },
|
|
1890
|
+
{ name: "three-crows", description: "Three Black Crows pattern" },
|
|
1891
|
+
{ name: "hanging-man", description: "Hanging Man pattern" },
|
|
1892
|
+
{ name: "inverted-hammer", description: "Inverted Hammer pattern" },
|
|
1893
|
+
{ name: "shooting-star", description: "Shooting Star pattern" },
|
|
1894
|
+
// Bitcoin On-Chain
|
|
1895
|
+
{ name: "ahr999", description: "AHR999 Bitcoin accumulation index" },
|
|
1896
|
+
{ name: "rainbow", description: "Bitcoin Rainbow Chart" },
|
|
1897
|
+
// Other
|
|
1898
|
+
{ name: "fisher", description: "Fisher Transform" },
|
|
1899
|
+
{ name: "nvi-pvi", description: "Negative/Positive Volume Index (returns both)" },
|
|
1900
|
+
{ name: "pmax", description: "PMAX" },
|
|
1901
|
+
{ name: "qqe", description: "QQE Mod" },
|
|
1902
|
+
{ name: "tdi", description: "Traders Dynamic Index" },
|
|
1903
|
+
{ name: "waddah", description: "Waddah Attar Explosion" },
|
|
1904
|
+
{ name: "range-filter", description: "Range Filter" },
|
|
1905
|
+
{ name: "cho", description: "Chande Momentum Oscillator" },
|
|
1906
|
+
{ name: "tr", description: "True Range" },
|
|
1907
|
+
{ name: "tp", description: "Typical Price" },
|
|
1908
|
+
{ name: "mp", description: "Median Price" },
|
|
1909
|
+
{ name: "top-long-short", description: "Top Trader Long/Short Ratio (timeframe-independent)" }
|
|
1910
|
+
];
|
|
1777
1911
|
function resolveIndicatorCode(name) {
|
|
1778
1912
|
const lower = name.toLowerCase();
|
|
1779
1913
|
return INDICATOR_CODE_OVERRIDES[lower] ?? name.toUpperCase().replace(/-/g, "_");
|
|
@@ -1800,7 +1934,7 @@ function registerIndicatorTools() {
|
|
|
1800
1934
|
},
|
|
1801
1935
|
indicator: {
|
|
1802
1936
|
type: "string",
|
|
1803
|
-
description: "Indicator name (case-insensitive).
|
|
1937
|
+
description: "Indicator name (case-insensitive). Call market_list_indicators to see all supported names."
|
|
1804
1938
|
},
|
|
1805
1939
|
bar: {
|
|
1806
1940
|
type: "string",
|
|
@@ -1857,6 +1991,14 @@ function registerIndicatorTools() {
|
|
|
1857
1991
|
);
|
|
1858
1992
|
return normalizeResponse(response);
|
|
1859
1993
|
}
|
|
1994
|
+
},
|
|
1995
|
+
{
|
|
1996
|
+
name: "market_list_indicators",
|
|
1997
|
+
module: "market",
|
|
1998
|
+
description: "List all supported technical indicator names and descriptions. Call this before market_get_indicator to discover valid indicator names. No credentials required.",
|
|
1999
|
+
isWrite: false,
|
|
2000
|
+
inputSchema: { type: "object", properties: {} },
|
|
2001
|
+
handler: async () => ({ data: KNOWN_INDICATORS })
|
|
1860
2002
|
}
|
|
1861
2003
|
];
|
|
1862
2004
|
}
|
|
@@ -2454,6 +2596,118 @@ function registerAccountTools() {
|
|
|
2454
2596
|
}
|
|
2455
2597
|
];
|
|
2456
2598
|
}
|
|
2599
|
+
function extractInstrumentParams(instId, data) {
|
|
2600
|
+
const instruments = Array.isArray(data) ? data : [];
|
|
2601
|
+
if (instruments.length === 0) {
|
|
2602
|
+
throw new Error(`Failed to fetch instrument info for ${instId}: empty instrument list. Cannot determine ctVal for conversion.`);
|
|
2603
|
+
}
|
|
2604
|
+
const inst = instruments[0];
|
|
2605
|
+
const ctValStr = String(inst.ctVal ?? "");
|
|
2606
|
+
const ctVal = parseFloat(ctValStr);
|
|
2607
|
+
if (!isFinite(ctVal) || ctVal <= 0) {
|
|
2608
|
+
throw new Error(`Invalid ctVal "${ctValStr}" for ${instId}. ctVal must be a positive number for conversion.`);
|
|
2609
|
+
}
|
|
2610
|
+
const minSzStr = String(inst.minSz ?? "1");
|
|
2611
|
+
const minSz = parseFloat(minSzStr);
|
|
2612
|
+
if (!isFinite(minSz) || minSz <= 0) {
|
|
2613
|
+
throw new Error(`Invalid minSz "${minSzStr}" for ${instId}. minSz must be a positive number for conversion.`);
|
|
2614
|
+
}
|
|
2615
|
+
const lotSzStr = String(inst.lotSz ?? "1");
|
|
2616
|
+
const lotSz = parseFloat(lotSzStr);
|
|
2617
|
+
if (!isFinite(lotSz) || lotSz <= 0) {
|
|
2618
|
+
throw new Error(`Invalid lotSz "${lotSzStr}" for ${instId}. lotSz must be a positive number for conversion.`);
|
|
2619
|
+
}
|
|
2620
|
+
return { ctVal, ctValStr, minSz, minSzStr, lotSz, lotSzStr };
|
|
2621
|
+
}
|
|
2622
|
+
function extractLastPx(instId, data) {
|
|
2623
|
+
const tickers = Array.isArray(data) ? data : [];
|
|
2624
|
+
if (tickers.length === 0) {
|
|
2625
|
+
throw new Error(`Failed to fetch ticker price for ${instId}: empty ticker response. Cannot determine last price for conversion.`);
|
|
2626
|
+
}
|
|
2627
|
+
const lastStr = String(tickers[0].last ?? "");
|
|
2628
|
+
const lastPx = parseFloat(lastStr);
|
|
2629
|
+
if (!isFinite(lastPx) || lastPx <= 0) {
|
|
2630
|
+
throw new Error(`Invalid last price "${lastStr}" for ${instId}. Last price must be a positive number for conversion.`);
|
|
2631
|
+
}
|
|
2632
|
+
return { lastPx, lastStr };
|
|
2633
|
+
}
|
|
2634
|
+
function extractLeverage(instId, mgnMode, data) {
|
|
2635
|
+
const leverageData = Array.isArray(data) ? data : [];
|
|
2636
|
+
if (leverageData.length === 0) {
|
|
2637
|
+
throw new Error(
|
|
2638
|
+
`Failed to fetch leverage info for ${instId} (mgnMode=${mgnMode}): empty response. Cannot determine leverage for margin conversion. Please set leverage first using set_leverage.`
|
|
2639
|
+
);
|
|
2640
|
+
}
|
|
2641
|
+
const leverStr = String(leverageData[0].lever ?? "1");
|
|
2642
|
+
const lever = parseFloat(leverStr);
|
|
2643
|
+
if (!isFinite(lever) || lever <= 0) {
|
|
2644
|
+
throw new Error(`Invalid leverage "${leverStr}" for ${instId}. Leverage must be a positive number for margin conversion.`);
|
|
2645
|
+
}
|
|
2646
|
+
return { lever, leverStr };
|
|
2647
|
+
}
|
|
2648
|
+
function computeContracts(p) {
|
|
2649
|
+
const { instId, sz, isMarginMode, inst, lastPx, lastStr, lever, leverStr } = p;
|
|
2650
|
+
const { ctVal, ctValStr, minSz, minSzStr, lotSz, lotSzStr } = inst;
|
|
2651
|
+
const userAmount = parseFloat(sz);
|
|
2652
|
+
const contractValue = ctVal * lastPx;
|
|
2653
|
+
const effectiveNotional = isMarginMode ? userAmount * lever : userAmount;
|
|
2654
|
+
const lotSzDecimals = lotSzStr.includes(".") ? lotSzStr.split(".")[1].length : 0;
|
|
2655
|
+
const precision = 10 ** (lotSzDecimals + 4);
|
|
2656
|
+
const rawContracts = Math.round(effectiveNotional / contractValue * precision) / precision;
|
|
2657
|
+
const rawLots = Math.round(rawContracts / lotSz * precision) / precision;
|
|
2658
|
+
const contractsRounded = parseFloat((Math.floor(rawLots) * lotSz).toFixed(lotSzDecimals));
|
|
2659
|
+
if (contractsRounded < minSz) {
|
|
2660
|
+
const minAmount = isMarginMode ? (minSz * contractValue / lever).toFixed(2) : (minSz * contractValue).toFixed(2);
|
|
2661
|
+
const unit = isMarginMode ? "USDT margin" : "USDT";
|
|
2662
|
+
throw new Error(
|
|
2663
|
+
`sz=${sz} ${unit} is too small for ${instId}. Minimum order size is ${minSzStr} contracts (\u2248 ${minAmount} ${unit}). (ctVal=${ctValStr}, lastPx=${lastStr}, minSz=${minSzStr}, lotSz=${lotSzStr}` + (isMarginMode ? `, lever=${leverStr}` : "") + `).
|
|
2664
|
+
`
|
|
2665
|
+
);
|
|
2666
|
+
}
|
|
2667
|
+
const contractsStr = contractsRounded.toFixed(lotSzDecimals);
|
|
2668
|
+
const conversionNote = isMarginMode ? `Converting ${sz} USDT margin (${leverStr}x leverage) \u2192 ${contractsStr} contracts (notional value \u2248 ${(contractsRounded * contractValue).toFixed(2)} USDT, ctVal=${ctValStr}, lastPx=${lastStr}, lever=${leverStr}, minSz=${minSzStr}, lotSz=${lotSzStr})` : `Converting ${sz} USDT \u2192 ${contractsStr} contracts (ctVal=${ctValStr}, lastPx=${lastStr}, minSz=${minSzStr}, lotSz=${lotSzStr})`;
|
|
2669
|
+
return { contractsStr, conversionNote };
|
|
2670
|
+
}
|
|
2671
|
+
async function resolveQuoteCcySz(instId, sz, tgtCcy, instType, client, tdMode) {
|
|
2672
|
+
if (tgtCcy === void 0 || tgtCcy === "base_ccy") {
|
|
2673
|
+
return { sz, tgtCcy, conversionNote: void 0 };
|
|
2674
|
+
}
|
|
2675
|
+
if (tgtCcy !== "quote_ccy" && tgtCcy !== "margin") {
|
|
2676
|
+
throw new ValidationError(
|
|
2677
|
+
`Unknown tgtCcy value "${tgtCcy}". Valid values: base_ccy, quote_ccy, margin.`,
|
|
2678
|
+
`Check the --tgtCcy flag. Use base_ccy (default, sz in contracts), quote_ccy (sz in USDT notional), or margin (sz in USDT margin cost).`
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
const isMarginMode = tgtCcy === "margin";
|
|
2682
|
+
if (isMarginMode && !tdMode) {
|
|
2683
|
+
throw new Error(
|
|
2684
|
+
"tdMode (cross or isolated) is required when tgtCcy=margin. Cannot determine leverage without knowing the margin mode."
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
const mgnMode = tdMode === "cross" ? "cross" : "isolated";
|
|
2688
|
+
const fetchPromises = [
|
|
2689
|
+
client.publicGet("/api/v5/public/instruments", { instType, instId }),
|
|
2690
|
+
client.publicGet("/api/v5/market/ticker", { instId })
|
|
2691
|
+
];
|
|
2692
|
+
if (isMarginMode) {
|
|
2693
|
+
fetchPromises.push(client.privateGet("/api/v5/account/leverage-info", { instId, mgnMode }));
|
|
2694
|
+
}
|
|
2695
|
+
const results = await Promise.all(fetchPromises);
|
|
2696
|
+
const inst = extractInstrumentParams(instId, results[0].data);
|
|
2697
|
+
const { lastPx, lastStr } = extractLastPx(instId, results[1].data);
|
|
2698
|
+
const { lever, leverStr } = isMarginMode ? extractLeverage(instId, mgnMode, results[2].data) : { lever: 1, leverStr: "1" };
|
|
2699
|
+
const { contractsStr, conversionNote } = computeContracts({
|
|
2700
|
+
instId,
|
|
2701
|
+
sz,
|
|
2702
|
+
isMarginMode,
|
|
2703
|
+
inst,
|
|
2704
|
+
lastPx,
|
|
2705
|
+
lastStr,
|
|
2706
|
+
lever,
|
|
2707
|
+
leverStr
|
|
2708
|
+
});
|
|
2709
|
+
return { sz: contractsStr, tgtCcy: void 0, conversionNote };
|
|
2710
|
+
}
|
|
2457
2711
|
function registerAlgoTradeTools() {
|
|
2458
2712
|
return [
|
|
2459
2713
|
{
|
|
@@ -2532,8 +2786,8 @@ function registerAlgoTradeTools() {
|
|
|
2532
2786
|
},
|
|
2533
2787
|
tgtCcy: {
|
|
2534
2788
|
type: "string",
|
|
2535
|
-
enum: ["base_ccy", "quote_ccy"],
|
|
2536
|
-
description: "Size unit. base_ccy(default): sz in contracts
|
|
2789
|
+
enum: ["base_ccy", "quote_ccy", "margin"],
|
|
2790
|
+
description: "Size unit. base_ccy(default): sz in contracts; quote_ccy: sz in USDT notional value; margin: sz in USDT margin cost (actual position = sz * leverage)"
|
|
2537
2791
|
},
|
|
2538
2792
|
reduceOnly: {
|
|
2539
2793
|
type: "boolean",
|
|
@@ -2549,6 +2803,14 @@ function registerAlgoTradeTools() {
|
|
|
2549
2803
|
handler: async (rawArgs, context) => {
|
|
2550
2804
|
const args = asRecord(rawArgs);
|
|
2551
2805
|
const reduceOnly = args.reduceOnly;
|
|
2806
|
+
const resolved = await resolveQuoteCcySz(
|
|
2807
|
+
requireString(args, "instId"),
|
|
2808
|
+
requireString(args, "sz"),
|
|
2809
|
+
readString(args, "tgtCcy"),
|
|
2810
|
+
"SWAP",
|
|
2811
|
+
context.client,
|
|
2812
|
+
readString(args, "tdMode")
|
|
2813
|
+
);
|
|
2552
2814
|
const response = await context.client.privatePost(
|
|
2553
2815
|
"/api/v5/trade/order-algo",
|
|
2554
2816
|
compactObject({
|
|
@@ -2557,8 +2819,8 @@ function registerAlgoTradeTools() {
|
|
|
2557
2819
|
side: requireString(args, "side"),
|
|
2558
2820
|
posSide: readString(args, "posSide"),
|
|
2559
2821
|
ordType: requireString(args, "ordType"),
|
|
2560
|
-
sz:
|
|
2561
|
-
tgtCcy:
|
|
2822
|
+
sz: resolved.sz,
|
|
2823
|
+
tgtCcy: resolved.tgtCcy,
|
|
2562
2824
|
tpTriggerPx: readString(args, "tpTriggerPx"),
|
|
2563
2825
|
tpOrdPx: readString(args, "tpOrdPx"),
|
|
2564
2826
|
tpTriggerPxType: readString(args, "tpTriggerPxType"),
|
|
@@ -2574,7 +2836,11 @@ function registerAlgoTradeTools() {
|
|
|
2574
2836
|
}),
|
|
2575
2837
|
privateRateLimit("swap_place_algo_order", 20)
|
|
2576
2838
|
);
|
|
2577
|
-
|
|
2839
|
+
const result = normalizeResponse(response);
|
|
2840
|
+
if (resolved.conversionNote) {
|
|
2841
|
+
result._conversion = resolved.conversionNote;
|
|
2842
|
+
}
|
|
2843
|
+
return result;
|
|
2578
2844
|
}
|
|
2579
2845
|
},
|
|
2580
2846
|
{
|
|
@@ -2865,8 +3131,8 @@ function registerFuturesAlgoTools() {
|
|
|
2865
3131
|
},
|
|
2866
3132
|
tgtCcy: {
|
|
2867
3133
|
type: "string",
|
|
2868
|
-
enum: ["base_ccy", "quote_ccy"],
|
|
2869
|
-
description: "Size unit. base_ccy(default): sz in contracts
|
|
3134
|
+
enum: ["base_ccy", "quote_ccy", "margin"],
|
|
3135
|
+
description: "Size unit. base_ccy(default): sz in contracts; quote_ccy: sz in USDT notional value; margin: sz in USDT margin cost (actual position = sz * leverage)"
|
|
2870
3136
|
},
|
|
2871
3137
|
reduceOnly: {
|
|
2872
3138
|
type: "boolean",
|
|
@@ -2882,6 +3148,14 @@ function registerFuturesAlgoTools() {
|
|
|
2882
3148
|
handler: async (rawArgs, context) => {
|
|
2883
3149
|
const args = asRecord(rawArgs);
|
|
2884
3150
|
const reduceOnly = args.reduceOnly;
|
|
3151
|
+
const resolved = await resolveQuoteCcySz(
|
|
3152
|
+
requireString(args, "instId"),
|
|
3153
|
+
requireString(args, "sz"),
|
|
3154
|
+
readString(args, "tgtCcy"),
|
|
3155
|
+
"FUTURES",
|
|
3156
|
+
context.client,
|
|
3157
|
+
readString(args, "tdMode")
|
|
3158
|
+
);
|
|
2885
3159
|
const response = await context.client.privatePost(
|
|
2886
3160
|
"/api/v5/trade/order-algo",
|
|
2887
3161
|
compactObject({
|
|
@@ -2890,8 +3164,8 @@ function registerFuturesAlgoTools() {
|
|
|
2890
3164
|
side: requireString(args, "side"),
|
|
2891
3165
|
posSide: readString(args, "posSide"),
|
|
2892
3166
|
ordType: requireString(args, "ordType"),
|
|
2893
|
-
sz:
|
|
2894
|
-
tgtCcy:
|
|
3167
|
+
sz: resolved.sz,
|
|
3168
|
+
tgtCcy: resolved.tgtCcy,
|
|
2895
3169
|
tpTriggerPx: readString(args, "tpTriggerPx"),
|
|
2896
3170
|
tpOrdPx: readString(args, "tpOrdPx"),
|
|
2897
3171
|
tpTriggerPxType: readString(args, "tpTriggerPxType"),
|
|
@@ -2907,7 +3181,11 @@ function registerFuturesAlgoTools() {
|
|
|
2907
3181
|
}),
|
|
2908
3182
|
privateRateLimit("futures_place_algo_order", 20)
|
|
2909
3183
|
);
|
|
2910
|
-
|
|
3184
|
+
const result = normalizeResponse(response);
|
|
3185
|
+
if (resolved.conversionNote) {
|
|
3186
|
+
result._conversion = resolved.conversionNote;
|
|
3187
|
+
}
|
|
3188
|
+
return result;
|
|
2911
3189
|
}
|
|
2912
3190
|
},
|
|
2913
3191
|
{
|
|
@@ -3472,7 +3750,7 @@ function registerSkillsTools() {
|
|
|
3472
3750
|
{
|
|
3473
3751
|
name: "skills_download",
|
|
3474
3752
|
module: "skills",
|
|
3475
|
-
description: "Download a skill zip file from OKX Skills Marketplace to a local directory. Always call skills_search first to confirm the skill name exists. Downloads the latest approved version. NOTE:
|
|
3753
|
+
description: "Download a skill zip file from OKX Skills Marketplace to a local directory. Always call skills_search first to confirm the skill name exists. Downloads the latest approved version. NOTE: Downloads third-party developer content as a zip \u2014 does NOT install to agents. For full installation use CLI: okx skill add <name>. Use when the user wants to inspect or manually install a skill package.",
|
|
3476
3754
|
inputSchema: {
|
|
3477
3755
|
type: "object",
|
|
3478
3756
|
properties: {
|
|
@@ -4078,7 +4356,7 @@ function registerEarnTools() {
|
|
|
4078
4356
|
{
|
|
4079
4357
|
name: "earn_get_savings_balance",
|
|
4080
4358
|
module: "earn.savings",
|
|
4081
|
-
description: "Get Simple Earn (savings/flexible earn) balance. Returns current holdings
|
|
4359
|
+
description: "Get Simple Earn (savings/flexible earn) balance. Returns current holdings for all currencies or a specific one. To show market rates alongside balance (\u5E02\u573A\u5747\u5229\u7387), call earn_get_lending_rate_history. earn_get_lending_rate_history also returns fixed-term (\u5B9A\u671F) product offers, so one call gives a complete view of both flexible and fixed options. Do NOT use for fixed-term (\u5B9A\u671F) order queries \u2014 use earn_get_fixed_order_list instead.",
|
|
4082
4360
|
isWrite: false,
|
|
4083
4361
|
inputSchema: {
|
|
4084
4362
|
type: "object",
|
|
@@ -4099,6 +4377,43 @@ function registerEarnTools() {
|
|
|
4099
4377
|
return normalizeResponse(response);
|
|
4100
4378
|
}
|
|
4101
4379
|
},
|
|
4380
|
+
{
|
|
4381
|
+
name: "earn_get_fixed_order_list",
|
|
4382
|
+
module: "earn.savings",
|
|
4383
|
+
description: "Get Simple Earn Fixed (\u5B9A\u671F\u8D5A\u5E01) lending order list. Returns orders sorted by creation time descending. Use this to check status of fixed-term lending orders (pending/earning/expired/settled/cancelled). Do NOT use for flexible earn balance \u2014 use earn_get_savings_balance instead. If the result is empty, do NOT display any fixed-term section in the output.",
|
|
4384
|
+
isWrite: false,
|
|
4385
|
+
inputSchema: {
|
|
4386
|
+
type: "object",
|
|
4387
|
+
properties: {
|
|
4388
|
+
ccy: {
|
|
4389
|
+
type: "string",
|
|
4390
|
+
description: "Currency, e.g. USDT. Omit for all."
|
|
4391
|
+
},
|
|
4392
|
+
state: {
|
|
4393
|
+
type: "string",
|
|
4394
|
+
description: "Order state: pending (\u5339\u914D\u4E2D), earning (\u8D5A\u5E01\u4E2D), expired (\u903E\u671F), settled (\u5DF2\u7ED3\u7B97), cancelled (\u5DF2\u64A4\u9500). Omit for all."
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
},
|
|
4398
|
+
handler: async (rawArgs, context) => {
|
|
4399
|
+
const args = asRecord(rawArgs);
|
|
4400
|
+
const response = await context.client.privateGet(
|
|
4401
|
+
"/api/v5/finance/simple-earn-fixed/order-list",
|
|
4402
|
+
compactObject({
|
|
4403
|
+
ccy: readString(args, "ccy"),
|
|
4404
|
+
state: readString(args, "state")
|
|
4405
|
+
}),
|
|
4406
|
+
privateRateLimit("earn_get_fixed_order_list", 3)
|
|
4407
|
+
);
|
|
4408
|
+
const result = normalizeResponse(response);
|
|
4409
|
+
if (Array.isArray(result["data"])) {
|
|
4410
|
+
result["data"] = result["data"].map(
|
|
4411
|
+
({ finalSettlementDate: _, ...rest }) => rest
|
|
4412
|
+
);
|
|
4413
|
+
}
|
|
4414
|
+
return result;
|
|
4415
|
+
}
|
|
4416
|
+
},
|
|
4102
4417
|
{
|
|
4103
4418
|
name: "earn_savings_purchase",
|
|
4104
4419
|
module: "earn.savings",
|
|
@@ -4243,10 +4558,112 @@ function registerEarnTools() {
|
|
|
4243
4558
|
return normalizeResponse(response);
|
|
4244
4559
|
}
|
|
4245
4560
|
},
|
|
4561
|
+
{
|
|
4562
|
+
name: "earn_fixed_purchase",
|
|
4563
|
+
module: "earn.savings",
|
|
4564
|
+
description: "Purchase Simple Earn Fixed (\u5B9A\u671F) product, two-step flow. First call (confirm omitted or false): returns purchase preview with product details and risk warning. Preview offer fields: lendQuota = remaining quota (\u5269\u4F59\u989D\u5EA6), soldOut = whether product is sold out (lendQuota is 0). YOU MUST display the 'warning' field from the preview response to the user VERBATIM before asking for confirmation \u2014 do NOT omit or summarize it. Second call (confirm=true): executes the purchase. Only proceed after the user explicitly confirms. IMPORTANT: Orders in 'pending' (\u5339\u914D\u4E2D) state can still be cancelled via earn_fixed_redeem; once the status changes to 'earning' (\u8D5A\u5E01\u4E2D), funds are LOCKED until maturity \u2014 no early redemption allowed.",
|
|
4565
|
+
isWrite: true,
|
|
4566
|
+
inputSchema: {
|
|
4567
|
+
type: "object",
|
|
4568
|
+
properties: {
|
|
4569
|
+
ccy: {
|
|
4570
|
+
type: "string",
|
|
4571
|
+
description: "Currency, e.g. USDT"
|
|
4572
|
+
},
|
|
4573
|
+
amt: {
|
|
4574
|
+
type: "string",
|
|
4575
|
+
description: "Purchase amount"
|
|
4576
|
+
},
|
|
4577
|
+
term: {
|
|
4578
|
+
type: "string",
|
|
4579
|
+
description: "Term, e.g. 90D"
|
|
4580
|
+
},
|
|
4581
|
+
confirm: {
|
|
4582
|
+
type: "boolean",
|
|
4583
|
+
description: "Omit or false on the first call to preview the purchase details; set to true on the second call to execute after user confirms."
|
|
4584
|
+
}
|
|
4585
|
+
},
|
|
4586
|
+
required: ["ccy", "amt", "term"]
|
|
4587
|
+
},
|
|
4588
|
+
handler: async (rawArgs, context) => {
|
|
4589
|
+
const args = asRecord(rawArgs);
|
|
4590
|
+
const ccy = requireString(args, "ccy");
|
|
4591
|
+
const amt = requireString(args, "amt");
|
|
4592
|
+
const term = requireString(args, "term");
|
|
4593
|
+
const confirm = readBoolean(args, "confirm") ?? false;
|
|
4594
|
+
if (!confirm) {
|
|
4595
|
+
const [rateResponse, fixedResponse] = await Promise.all([
|
|
4596
|
+
context.client.publicGet(
|
|
4597
|
+
"/api/v5/finance/savings/lending-rate-history",
|
|
4598
|
+
compactObject({ ccy, limit: 1 }),
|
|
4599
|
+
publicRateLimit("earn_get_lending_rate_history", 6)
|
|
4600
|
+
),
|
|
4601
|
+
context.client.privateGet(
|
|
4602
|
+
"/api/v5/finance/simple-earn-fixed/offers",
|
|
4603
|
+
compactObject({ ccy }),
|
|
4604
|
+
privateRateLimit("earn_fixed_purchase_preview_offers", 2)
|
|
4605
|
+
).catch(() => null)
|
|
4606
|
+
]);
|
|
4607
|
+
const rateResult = normalizeResponse(rateResponse);
|
|
4608
|
+
const fixedResult = fixedResponse ? normalizeResponse(fixedResponse) : { data: [] };
|
|
4609
|
+
const rateArr = Array.isArray(rateResult["data"]) ? rateResult["data"] : [];
|
|
4610
|
+
const allOffers = Array.isArray(fixedResult["data"]) ? fixedResult["data"] : [];
|
|
4611
|
+
const matchedOffer = allOffers.find(
|
|
4612
|
+
(o) => o["term"] === term && o["ccy"] === ccy
|
|
4613
|
+
);
|
|
4614
|
+
const { borrowingOrderQuota: _, ...offerWithoutTotal } = matchedOffer ?? {};
|
|
4615
|
+
const offerWithSoldOut = matchedOffer ? { ...offerWithoutTotal, soldOut: offerWithoutTotal["lendQuota"] === "0" } : null;
|
|
4616
|
+
return {
|
|
4617
|
+
preview: true,
|
|
4618
|
+
ccy,
|
|
4619
|
+
amt,
|
|
4620
|
+
term,
|
|
4621
|
+
offer: offerWithSoldOut,
|
|
4622
|
+
currentFlexibleRate: rateArr[0]?.["lendingRate"] ?? null,
|
|
4623
|
+
warning: "\u26A0\uFE0F Orders still in 'pending' state can be cancelled before matching completes. Once the status changes to 'earning', funds are LOCKED until maturity \u2014 early redemption is NOT allowed. Please call again with confirm=true to proceed."
|
|
4624
|
+
};
|
|
4625
|
+
}
|
|
4626
|
+
assertNotDemo(context.config, "earn_fixed_purchase");
|
|
4627
|
+
const response = await context.client.privatePost(
|
|
4628
|
+
"/api/v5/finance/simple-earn-fixed/purchase",
|
|
4629
|
+
{ ccy, amt, term },
|
|
4630
|
+
privateRateLimit("earn_fixed_purchase", 2)
|
|
4631
|
+
);
|
|
4632
|
+
return normalizeResponse(response);
|
|
4633
|
+
}
|
|
4634
|
+
},
|
|
4635
|
+
{
|
|
4636
|
+
name: "earn_fixed_redeem",
|
|
4637
|
+
module: "earn.savings",
|
|
4638
|
+
description: "Redeem Simple Earn Fixed (\u5B9A\u671F\u8D5A\u5E01) order. [CAUTION] Redeems a fixed-term lending order. Always redeems the full order amount. Only orders in 'pending' (\u5339\u914D\u4E2D) state can be redeemed \u2014 orders in 'earning' state are locked until maturity and cannot be redeemed early. Do NOT use for flexible earn redemption \u2014 use earn_savings_redeem instead.",
|
|
4639
|
+
isWrite: true,
|
|
4640
|
+
inputSchema: {
|
|
4641
|
+
type: "object",
|
|
4642
|
+
properties: {
|
|
4643
|
+
reqId: {
|
|
4644
|
+
type: "string",
|
|
4645
|
+
description: "Request ID of the fixed-term order to redeem"
|
|
4646
|
+
}
|
|
4647
|
+
},
|
|
4648
|
+
required: ["reqId"]
|
|
4649
|
+
},
|
|
4650
|
+
handler: async (rawArgs, context) => {
|
|
4651
|
+
assertNotDemo(context.config, "earn_fixed_redeem");
|
|
4652
|
+
const args = asRecord(rawArgs);
|
|
4653
|
+
const response = await context.client.privatePost(
|
|
4654
|
+
"/api/v5/finance/simple-earn-fixed/redeem",
|
|
4655
|
+
{
|
|
4656
|
+
reqId: requireString(args, "reqId")
|
|
4657
|
+
},
|
|
4658
|
+
privateRateLimit("earn_fixed_redeem", 2)
|
|
4659
|
+
);
|
|
4660
|
+
return normalizeResponse(response);
|
|
4661
|
+
}
|
|
4662
|
+
},
|
|
4246
4663
|
{
|
|
4247
4664
|
name: "earn_get_lending_rate_history",
|
|
4248
4665
|
module: "earn.savings",
|
|
4249
|
-
description: "Query Simple Earn lending rates
|
|
4666
|
+
description: "Query Simple Earn lending rates and fixed-term offers. Use this tool when the user asks about Simple Earn products, current or historical lending rates, or when displaying savings balance with market rate context (\u5E02\u573A\u5747\u5229\u7387). Returns lending rate history (lendingRate field, newest-first) AND available fixed-term (\u5B9A\u671F) offers with APR, term, min amount, and quota \u2014 one call gives a complete view of both flexible and fixed options. In fixedOffers: lendQuota = remaining quota (\u5269\u4F59\u989D\u5EA6), soldOut = whether product is sold out (lendQuota is 0). To get current flexible APY: use limit=1 and read lendingRate.",
|
|
4250
4667
|
isWrite: false,
|
|
4251
4668
|
inputSchema: {
|
|
4252
4669
|
type: "object",
|
|
@@ -4265,23 +4682,45 @@ function registerEarnTools() {
|
|
|
4265
4682
|
},
|
|
4266
4683
|
limit: {
|
|
4267
4684
|
type: "number",
|
|
4268
|
-
description: "Max results (default
|
|
4685
|
+
description: "Max results (default 7)"
|
|
4269
4686
|
}
|
|
4270
4687
|
}
|
|
4271
4688
|
},
|
|
4272
4689
|
handler: async (rawArgs, context) => {
|
|
4273
4690
|
const args = asRecord(rawArgs);
|
|
4274
|
-
const
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4691
|
+
const ccy = readString(args, "ccy");
|
|
4692
|
+
const [rateResponse, fixedResponse] = await Promise.all([
|
|
4693
|
+
context.client.publicGet(
|
|
4694
|
+
"/api/v5/finance/savings/lending-rate-history",
|
|
4695
|
+
compactObject({
|
|
4696
|
+
ccy,
|
|
4697
|
+
after: readString(args, "after"),
|
|
4698
|
+
before: readString(args, "before"),
|
|
4699
|
+
limit: readNumber(args, "limit") ?? 7
|
|
4700
|
+
}),
|
|
4701
|
+
publicRateLimit("earn_get_lending_rate_history", 6)
|
|
4702
|
+
),
|
|
4703
|
+
context.client.privateGet(
|
|
4704
|
+
"/api/v5/finance/simple-earn-fixed/offers",
|
|
4705
|
+
compactObject({ ccy }),
|
|
4706
|
+
privateRateLimit("earn_get_lending_rate_history_fixed", 2)
|
|
4707
|
+
).catch(() => null)
|
|
4708
|
+
]);
|
|
4709
|
+
const rateResult = normalizeResponse(rateResponse);
|
|
4710
|
+
const rateData = Array.isArray(rateResult["data"]) ? rateResult["data"].map(
|
|
4711
|
+
({ rate: _, ...rest }) => rest
|
|
4712
|
+
) : [];
|
|
4713
|
+
const fixedResult = fixedResponse ? normalizeResponse(fixedResponse) : { data: [] };
|
|
4714
|
+
const allOffers = fixedResult["data"] ?? [];
|
|
4715
|
+
const fixedOffers = allOffers.map(({ borrowingOrderQuota: _, ...rest }) => ({
|
|
4716
|
+
...rest,
|
|
4717
|
+
soldOut: rest["lendQuota"] === "0"
|
|
4718
|
+
}));
|
|
4719
|
+
return {
|
|
4720
|
+
...rateResult,
|
|
4721
|
+
data: rateData,
|
|
4722
|
+
fixedOffers
|
|
4723
|
+
};
|
|
4285
4724
|
}
|
|
4286
4725
|
}
|
|
4287
4726
|
];
|
|
@@ -4620,7 +5059,7 @@ function registerDcdTools() {
|
|
|
4620
5059
|
{
|
|
4621
5060
|
name: "dcd_get_products",
|
|
4622
5061
|
module: "earn.dcd",
|
|
4623
|
-
description: "Get DCD products with yield and quota info.",
|
|
5062
|
+
description: "Get DCD products with yield and quota info. Yields in response are decimal fractions, not percentages.",
|
|
4624
5063
|
isWrite: false,
|
|
4625
5064
|
inputSchema: {
|
|
4626
5065
|
type: "object",
|
|
@@ -4674,7 +5113,7 @@ function registerDcdTools() {
|
|
|
4674
5113
|
{
|
|
4675
5114
|
name: "dcd_get_orders",
|
|
4676
5115
|
module: "earn.dcd",
|
|
4677
|
-
description: "Get DCD order history.",
|
|
5116
|
+
description: "Get DCD order history. Yields in response are decimal fractions, not percentages.",
|
|
4678
5117
|
isWrite: false,
|
|
4679
5118
|
inputSchema: {
|
|
4680
5119
|
type: "object",
|
|
@@ -4718,7 +5157,7 @@ function registerDcdTools() {
|
|
|
4718
5157
|
{
|
|
4719
5158
|
name: "dcd_subscribe",
|
|
4720
5159
|
module: "earn.dcd",
|
|
4721
|
-
description: "Subscribe to a DCD product: get quote and execute atomically. Confirm product, amount, and currency with user before calling. Optional minAnnualizedYield
|
|
5160
|
+
description: "Subscribe to a DCD product: get quote and execute atomically. Confirm product, amount, and currency with user before calling. Optional minAnnualizedYield rejects the order if quote yield falls below threshold. Returns order result with quote snapshot (minAnnualizedYield is in percent; response yields are decimal fractions).",
|
|
4722
5161
|
isWrite: true,
|
|
4723
5162
|
inputSchema: {
|
|
4724
5163
|
type: "object",
|
|
@@ -4757,13 +5196,23 @@ function registerDcdTools() {
|
|
|
4757
5196
|
});
|
|
4758
5197
|
}
|
|
4759
5198
|
if (minAnnualizedYield !== void 0) {
|
|
4760
|
-
const
|
|
4761
|
-
if (
|
|
5199
|
+
const rawYield = parseFloat(quote["annualizedYield"]);
|
|
5200
|
+
if (isNaN(rawYield)) {
|
|
4762
5201
|
throw new OkxApiError(
|
|
4763
|
-
|
|
5202
|
+
"Quote returned non-numeric annualizedYield, cannot verify minimum yield threshold.",
|
|
5203
|
+
{
|
|
5204
|
+
code: "INVALID_YIELD_VALUE",
|
|
5205
|
+
suggestion: "Order not placed. The quote did not include a valid annualizedYield. Retry or pick a different product."
|
|
5206
|
+
}
|
|
5207
|
+
);
|
|
5208
|
+
}
|
|
5209
|
+
const actualYieldPct = rawYield * 100;
|
|
5210
|
+
if (actualYieldPct < minAnnualizedYield) {
|
|
5211
|
+
throw new OkxApiError(
|
|
5212
|
+
`Quote yield ${actualYieldPct.toFixed(2)}% is below the minimum threshold of ${minAnnualizedYield}%.`,
|
|
4764
5213
|
{
|
|
4765
5214
|
code: "YIELD_BELOW_MIN",
|
|
4766
|
-
suggestion: `Order not placed. Actual: ${
|
|
5215
|
+
suggestion: `Order not placed. Actual: ${actualYieldPct.toFixed(2)}%, required: >= ${minAnnualizedYield}%. Try a different product or lower your minimum yield.`
|
|
4767
5216
|
}
|
|
4768
5217
|
);
|
|
4769
5218
|
}
|
|
@@ -4908,7 +5357,7 @@ function registerAutoEarnTools() {
|
|
|
4908
5357
|
}
|
|
4909
5358
|
var EARN_DEMO_MESSAGE = "Earn features (savings, DCD, on-chain staking, auto-earn) are not available in simulated trading mode.";
|
|
4910
5359
|
var EARN_DEMO_SUGGESTION = "Switch to a live account to use Earn features.";
|
|
4911
|
-
var DEMO_GUARD_SKIP = /* @__PURE__ */ new Set(["dcd_redeem"]);
|
|
5360
|
+
var DEMO_GUARD_SKIP = /* @__PURE__ */ new Set(["dcd_redeem", "earn_fixed_purchase"]);
|
|
4912
5361
|
function withDemoGuard(tool) {
|
|
4913
5362
|
if (!tool.isWrite || DEMO_GUARD_SKIP.has(tool.name)) return tool;
|
|
4914
5363
|
const originalHandler = tool.handler;
|
|
@@ -4969,12 +5418,12 @@ function buildContractTradeTools(cfg) {
|
|
|
4969
5418
|
},
|
|
4970
5419
|
sz: {
|
|
4971
5420
|
type: "string",
|
|
4972
|
-
description: "Number of contracts. Each contract = ctVal units (e.g. 0.1 ETH for ETH-USDT-SWAP). Query market_get_instruments for exact ctVal. Set tgtCcy=quote_ccy to specify sz in USDT
|
|
5421
|
+
description: "Number of contracts. Each contract = ctVal units (e.g. 0.1 ETH for ETH-USDT-SWAP). Query market_get_instruments for exact ctVal. Set tgtCcy=quote_ccy to specify sz in USDT notional value; set tgtCcy=margin to specify sz as margin cost (notional = sz * leverage)."
|
|
4973
5422
|
},
|
|
4974
5423
|
tgtCcy: {
|
|
4975
5424
|
type: "string",
|
|
4976
|
-
enum: ["base_ccy", "quote_ccy"],
|
|
4977
|
-
description: "Size unit. base_ccy(default): sz in contracts
|
|
5425
|
+
enum: ["base_ccy", "quote_ccy", "margin"],
|
|
5426
|
+
description: "Size unit. base_ccy(default): sz in contracts; quote_ccy: sz in USDT notional value; margin: sz in USDT margin cost (actual position = sz * leverage)"
|
|
4978
5427
|
},
|
|
4979
5428
|
px: { type: "string", description: "Required for limit/post_only/fok/ioc" },
|
|
4980
5429
|
reduceOnly: {
|
|
@@ -4993,6 +5442,14 @@ function buildContractTradeTools(cfg) {
|
|
|
4993
5442
|
const args = asRecord(rawArgs);
|
|
4994
5443
|
const reduceOnly = args.reduceOnly;
|
|
4995
5444
|
const attachAlgoOrds = buildAttachAlgoOrds(args);
|
|
5445
|
+
const resolved = await resolveQuoteCcySz(
|
|
5446
|
+
requireString(args, "instId"),
|
|
5447
|
+
requireString(args, "sz"),
|
|
5448
|
+
readString(args, "tgtCcy"),
|
|
5449
|
+
defaultType,
|
|
5450
|
+
context.client,
|
|
5451
|
+
readString(args, "tdMode")
|
|
5452
|
+
);
|
|
4996
5453
|
const response = await context.client.privatePost(
|
|
4997
5454
|
"/api/v5/trade/order",
|
|
4998
5455
|
compactObject({
|
|
@@ -5001,8 +5458,8 @@ function buildContractTradeTools(cfg) {
|
|
|
5001
5458
|
side: requireString(args, "side"),
|
|
5002
5459
|
posSide: readString(args, "posSide"),
|
|
5003
5460
|
ordType: requireString(args, "ordType"),
|
|
5004
|
-
sz:
|
|
5005
|
-
tgtCcy:
|
|
5461
|
+
sz: resolved.sz,
|
|
5462
|
+
tgtCcy: resolved.tgtCcy,
|
|
5006
5463
|
px: readString(args, "px"),
|
|
5007
5464
|
reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
|
|
5008
5465
|
clOrdId: readString(args, "clOrdId"),
|
|
@@ -5011,7 +5468,11 @@ function buildContractTradeTools(cfg) {
|
|
|
5011
5468
|
}),
|
|
5012
5469
|
privateRateLimit(n("place_order"), 60)
|
|
5013
5470
|
);
|
|
5014
|
-
|
|
5471
|
+
const result = normalizeResponse(response);
|
|
5472
|
+
if (resolved.conversionNote) {
|
|
5473
|
+
result._conversion = resolved.conversionNote;
|
|
5474
|
+
}
|
|
5475
|
+
return result;
|
|
5015
5476
|
}
|
|
5016
5477
|
},
|
|
5017
5478
|
// ── cancel_order ─────────────────────────────────────────────────────────
|
|
@@ -6085,8 +6546,8 @@ function registerOptionAlgoTools() {
|
|
|
6085
6546
|
},
|
|
6086
6547
|
tgtCcy: {
|
|
6087
6548
|
type: "string",
|
|
6088
|
-
enum: ["base_ccy", "quote_ccy"],
|
|
6089
|
-
description: "Size unit. base_ccy(default): sz in contracts
|
|
6549
|
+
enum: ["base_ccy", "quote_ccy", "margin"],
|
|
6550
|
+
description: "Size unit. base_ccy(default): sz in contracts; quote_ccy: sz in USDT notional value; margin: sz in USDT margin cost (actual position = sz * leverage)"
|
|
6090
6551
|
},
|
|
6091
6552
|
reduceOnly: {
|
|
6092
6553
|
type: "boolean",
|
|
@@ -6102,6 +6563,14 @@ function registerOptionAlgoTools() {
|
|
|
6102
6563
|
handler: async (rawArgs, context) => {
|
|
6103
6564
|
const args = asRecord(rawArgs);
|
|
6104
6565
|
const reduceOnly = readBoolean(args, "reduceOnly");
|
|
6566
|
+
const resolved = await resolveQuoteCcySz(
|
|
6567
|
+
requireString(args, "instId"),
|
|
6568
|
+
requireString(args, "sz"),
|
|
6569
|
+
readString(args, "tgtCcy"),
|
|
6570
|
+
"OPTION",
|
|
6571
|
+
context.client,
|
|
6572
|
+
readString(args, "tdMode")
|
|
6573
|
+
);
|
|
6105
6574
|
const response = await context.client.privatePost(
|
|
6106
6575
|
"/api/v5/trade/order-algo",
|
|
6107
6576
|
compactObject({
|
|
@@ -6109,8 +6578,8 @@ function registerOptionAlgoTools() {
|
|
|
6109
6578
|
tdMode: requireString(args, "tdMode"),
|
|
6110
6579
|
side: requireString(args, "side"),
|
|
6111
6580
|
ordType: requireString(args, "ordType"),
|
|
6112
|
-
sz:
|
|
6113
|
-
tgtCcy:
|
|
6581
|
+
sz: resolved.sz,
|
|
6582
|
+
tgtCcy: resolved.tgtCcy,
|
|
6114
6583
|
tpTriggerPx: readString(args, "tpTriggerPx"),
|
|
6115
6584
|
tpOrdPx: readString(args, "tpOrdPx"),
|
|
6116
6585
|
tpTriggerPxType: readString(args, "tpTriggerPxType"),
|
|
@@ -6317,7 +6786,12 @@ function registerOptionTools() {
|
|
|
6317
6786
|
},
|
|
6318
6787
|
sz: {
|
|
6319
6788
|
type: "string",
|
|
6320
|
-
description: "Contracts count
|
|
6789
|
+
description: "Contracts count by default. Set tgtCcy=quote_ccy to specify USDT notional value; set tgtCcy=margin to specify USDT margin cost (notional = sz * leverage)."
|
|
6790
|
+
},
|
|
6791
|
+
tgtCcy: {
|
|
6792
|
+
type: "string",
|
|
6793
|
+
enum: ["base_ccy", "quote_ccy", "margin"],
|
|
6794
|
+
description: "Size unit. base_ccy(default): sz in contracts; quote_ccy: sz in USDT notional value; margin: sz in USDT margin cost (actual position = sz * leverage)"
|
|
6321
6795
|
},
|
|
6322
6796
|
px: {
|
|
6323
6797
|
type: "string",
|
|
@@ -6354,6 +6828,14 @@ function registerOptionTools() {
|
|
|
6354
6828
|
const args = asRecord(rawArgs);
|
|
6355
6829
|
const reduceOnly = args.reduceOnly;
|
|
6356
6830
|
const attachAlgoOrds = buildAttachAlgoOrds(args);
|
|
6831
|
+
const resolved = await resolveQuoteCcySz(
|
|
6832
|
+
requireString(args, "instId"),
|
|
6833
|
+
requireString(args, "sz"),
|
|
6834
|
+
readString(args, "tgtCcy"),
|
|
6835
|
+
"OPTION",
|
|
6836
|
+
context.client,
|
|
6837
|
+
readString(args, "tdMode")
|
|
6838
|
+
);
|
|
6357
6839
|
const response = await context.client.privatePost(
|
|
6358
6840
|
"/api/v5/trade/order",
|
|
6359
6841
|
compactObject({
|
|
@@ -6361,7 +6843,8 @@ function registerOptionTools() {
|
|
|
6361
6843
|
tdMode: requireString(args, "tdMode"),
|
|
6362
6844
|
side: requireString(args, "side"),
|
|
6363
6845
|
ordType: requireString(args, "ordType"),
|
|
6364
|
-
sz:
|
|
6846
|
+
sz: resolved.sz,
|
|
6847
|
+
tgtCcy: resolved.tgtCcy,
|
|
6365
6848
|
px: readString(args, "px"),
|
|
6366
6849
|
reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
|
|
6367
6850
|
clOrdId: readString(args, "clOrdId"),
|
|
@@ -7751,6 +8234,69 @@ Run: npm install -g ${packageName}
|
|
|
7751
8234
|
refreshCacheInBackground(packageName);
|
|
7752
8235
|
}
|
|
7753
8236
|
}
|
|
8237
|
+
var LOG_LEVEL_PRIORITY = {
|
|
8238
|
+
error: 0,
|
|
8239
|
+
warn: 1,
|
|
8240
|
+
info: 2,
|
|
8241
|
+
debug: 3
|
|
8242
|
+
};
|
|
8243
|
+
var SENSITIVE_KEY_PATTERN = /apiKey|secretKey|passphrase|password|secret/i;
|
|
8244
|
+
function sanitize(value) {
|
|
8245
|
+
if (value === null || value === void 0) {
|
|
8246
|
+
return value;
|
|
8247
|
+
}
|
|
8248
|
+
if (Array.isArray(value)) {
|
|
8249
|
+
return value.map(sanitize);
|
|
8250
|
+
}
|
|
8251
|
+
if (typeof value === "object") {
|
|
8252
|
+
const result = {};
|
|
8253
|
+
for (const [k, v] of Object.entries(value)) {
|
|
8254
|
+
if (SENSITIVE_KEY_PATTERN.test(k)) {
|
|
8255
|
+
result[k] = "[REDACTED]";
|
|
8256
|
+
} else {
|
|
8257
|
+
result[k] = sanitize(v);
|
|
8258
|
+
}
|
|
8259
|
+
}
|
|
8260
|
+
return result;
|
|
8261
|
+
}
|
|
8262
|
+
return value;
|
|
8263
|
+
}
|
|
8264
|
+
var TradeLogger = class {
|
|
8265
|
+
logLevel;
|
|
8266
|
+
logDir;
|
|
8267
|
+
constructor(logLevel = "info", logDir) {
|
|
8268
|
+
this.logLevel = logLevel;
|
|
8269
|
+
this.logDir = logDir ?? path2.join(os2.homedir(), ".okx", "logs");
|
|
8270
|
+
}
|
|
8271
|
+
getLogPath(date) {
|
|
8272
|
+
const d = date ?? /* @__PURE__ */ new Date();
|
|
8273
|
+
const yyyy = d.getUTCFullYear();
|
|
8274
|
+
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
8275
|
+
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
8276
|
+
return path2.join(this.logDir, `trade-${yyyy}-${mm}-${dd}.log`);
|
|
8277
|
+
}
|
|
8278
|
+
log(level, tool, params, result, durationMs) {
|
|
8279
|
+
if (LOG_LEVEL_PRIORITY[level] > LOG_LEVEL_PRIORITY[this.logLevel]) {
|
|
8280
|
+
return;
|
|
8281
|
+
}
|
|
8282
|
+
const entry = {
|
|
8283
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8284
|
+
level: level.toUpperCase(),
|
|
8285
|
+
tool,
|
|
8286
|
+
durationMs,
|
|
8287
|
+
params: sanitize(params),
|
|
8288
|
+
result: sanitize(result)
|
|
8289
|
+
};
|
|
8290
|
+
try {
|
|
8291
|
+
fs2.mkdirSync(this.logDir, { recursive: true });
|
|
8292
|
+
fs2.appendFileSync(this.getLogPath(), JSON.stringify(entry) + "\n", "utf8");
|
|
8293
|
+
} catch {
|
|
8294
|
+
}
|
|
8295
|
+
}
|
|
8296
|
+
static sanitize(params) {
|
|
8297
|
+
return sanitize(params);
|
|
8298
|
+
}
|
|
8299
|
+
};
|
|
7754
8300
|
var CLIENT_NAMES = {
|
|
7755
8301
|
"claude-desktop": "Claude Desktop",
|
|
7756
8302
|
cursor: "Cursor",
|
|
@@ -7900,11 +8446,11 @@ function runSetup(options) {
|
|
|
7900
8446
|
// src/commands/diagnose.ts
|
|
7901
8447
|
import dns from "dns/promises";
|
|
7902
8448
|
import net from "net";
|
|
7903
|
-
import
|
|
8449
|
+
import os5 from "os";
|
|
7904
8450
|
import tls from "tls";
|
|
7905
8451
|
|
|
7906
8452
|
// src/commands/diagnose-utils.ts
|
|
7907
|
-
import
|
|
8453
|
+
import fs4 from "fs";
|
|
7908
8454
|
import { createRequire } from "module";
|
|
7909
8455
|
|
|
7910
8456
|
// src/formatter.ts
|
|
@@ -7917,6 +8463,10 @@ var activeOutput = stdioOutput;
|
|
|
7917
8463
|
function setOutput(impl) {
|
|
7918
8464
|
activeOutput = impl;
|
|
7919
8465
|
}
|
|
8466
|
+
var envContext = null;
|
|
8467
|
+
function setEnvContext(ctx) {
|
|
8468
|
+
envContext = ctx;
|
|
8469
|
+
}
|
|
7920
8470
|
function output(message) {
|
|
7921
8471
|
activeOutput.out(message);
|
|
7922
8472
|
}
|
|
@@ -7929,10 +8479,23 @@ function outputLine(message) {
|
|
|
7929
8479
|
function errorLine(message) {
|
|
7930
8480
|
activeOutput.err(message + EOL);
|
|
7931
8481
|
}
|
|
8482
|
+
var jsonEnvEnabled = false;
|
|
8483
|
+
function setJsonEnvEnabled(enabled) {
|
|
8484
|
+
jsonEnvEnabled = enabled;
|
|
8485
|
+
}
|
|
7932
8486
|
function printJson(data) {
|
|
7933
|
-
|
|
8487
|
+
const payload = jsonEnvEnabled && envContext ? {
|
|
8488
|
+
env: envContext.demo ? "demo" : "live",
|
|
8489
|
+
profile: envContext.profile,
|
|
8490
|
+
data
|
|
8491
|
+
} : data;
|
|
8492
|
+
activeOutput.out(JSON.stringify(payload, null, 2) + EOL);
|
|
7934
8493
|
}
|
|
7935
8494
|
function printTable(rows) {
|
|
8495
|
+
if (envContext) {
|
|
8496
|
+
const envLabel = envContext.demo ? "demo (simulated trading)" : "live";
|
|
8497
|
+
activeOutput.out(`Environment: ${envLabel}` + EOL + EOL);
|
|
8498
|
+
}
|
|
7936
8499
|
if (rows.length === 0) {
|
|
7937
8500
|
activeOutput.out("(no data)" + EOL);
|
|
7938
8501
|
return;
|
|
@@ -8009,7 +8572,7 @@ var Report = class {
|
|
|
8009
8572
|
lines.push(`${key.padEnd(14)} ${value}`);
|
|
8010
8573
|
}
|
|
8011
8574
|
lines.push(sep2, "");
|
|
8012
|
-
|
|
8575
|
+
fs4.writeFileSync(filePath, lines.join("\n"), "utf8");
|
|
8013
8576
|
return true;
|
|
8014
8577
|
} catch (_e) {
|
|
8015
8578
|
return false;
|
|
@@ -8044,7 +8607,7 @@ function writeReportIfRequested(report, outputPath) {
|
|
|
8044
8607
|
errorLine(` Warning: failed to write report to: ${outputPath}`);
|
|
8045
8608
|
}
|
|
8046
8609
|
}
|
|
8047
|
-
function
|
|
8610
|
+
function sanitize2(value) {
|
|
8048
8611
|
value = value.replace(
|
|
8049
8612
|
/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi,
|
|
8050
8613
|
"****-uuid-****"
|
|
@@ -8055,14 +8618,14 @@ function sanitize(value) {
|
|
|
8055
8618
|
}
|
|
8056
8619
|
|
|
8057
8620
|
// src/commands/diagnose-mcp.ts
|
|
8058
|
-
import
|
|
8059
|
-
import
|
|
8060
|
-
import
|
|
8621
|
+
import fs5 from "fs";
|
|
8622
|
+
import path4 from "path";
|
|
8623
|
+
import os4 from "os";
|
|
8061
8624
|
import { spawnSync, spawn } from "child_process";
|
|
8062
8625
|
import { createRequire as createRequire2 } from "module";
|
|
8063
8626
|
import { fileURLToPath } from "url";
|
|
8064
8627
|
var _require2 = createRequire2(import.meta.url);
|
|
8065
|
-
var __dirname =
|
|
8628
|
+
var __dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
8066
8629
|
function readMcpVersion() {
|
|
8067
8630
|
const candidates = [
|
|
8068
8631
|
// Installed as global or local dependency
|
|
@@ -8115,13 +8678,13 @@ function checkMcpEntryPoint(report) {
|
|
|
8115
8678
|
if (!entryPath) {
|
|
8116
8679
|
const candidates = [
|
|
8117
8680
|
// Installed locally
|
|
8118
|
-
|
|
8681
|
+
path4.join(process.cwd(), "node_modules", ".bin", "okx-trade-mcp"),
|
|
8119
8682
|
// Monorepo workspace (e.g. running from source)
|
|
8120
|
-
|
|
8683
|
+
path4.join(__dirname, "..", "..", "..", "..", "mcp", "dist", "index.js")
|
|
8121
8684
|
];
|
|
8122
8685
|
for (const candidate of candidates) {
|
|
8123
8686
|
try {
|
|
8124
|
-
|
|
8687
|
+
fs5.accessSync(candidate, fs5.constants.X_OK | fs5.constants.R_OK);
|
|
8125
8688
|
entryPath = candidate;
|
|
8126
8689
|
break;
|
|
8127
8690
|
} catch (_e) {
|
|
@@ -8146,9 +8709,9 @@ var CLIENT_LIMITS = {
|
|
|
8146
8709
|
cursor: { perServer: 40, total: 80 }
|
|
8147
8710
|
};
|
|
8148
8711
|
function checkJsonMcpConfig(configPath) {
|
|
8149
|
-
if (!
|
|
8712
|
+
if (!fs5.existsSync(configPath)) return "missing";
|
|
8150
8713
|
try {
|
|
8151
|
-
const raw =
|
|
8714
|
+
const raw = fs5.readFileSync(configPath, "utf8");
|
|
8152
8715
|
const parsed = JSON.parse(raw);
|
|
8153
8716
|
const mcpServers = parsed["mcpServers"];
|
|
8154
8717
|
if (!mcpServers) return "not-configured";
|
|
@@ -8166,15 +8729,15 @@ function checkJsonMcpConfig(configPath) {
|
|
|
8166
8729
|
}
|
|
8167
8730
|
}
|
|
8168
8731
|
function checkClaudeCodeConfig() {
|
|
8169
|
-
const home =
|
|
8732
|
+
const home = os4.homedir();
|
|
8170
8733
|
const candidates = [
|
|
8171
|
-
|
|
8172
|
-
|
|
8734
|
+
path4.join(home, ".claude", "settings.json"),
|
|
8735
|
+
path4.join(home, ".claude.json")
|
|
8173
8736
|
];
|
|
8174
8737
|
let anyFound = false;
|
|
8175
8738
|
let anyParseError = false;
|
|
8176
8739
|
for (const cfgPath of candidates) {
|
|
8177
|
-
if (!
|
|
8740
|
+
if (!fs5.existsSync(cfgPath)) continue;
|
|
8178
8741
|
anyFound = true;
|
|
8179
8742
|
const result = checkJsonMcpConfig(cfgPath);
|
|
8180
8743
|
if (result === "found") return "found";
|
|
@@ -8191,8 +8754,8 @@ function handleJsonClient(clientId, report, configuredClients) {
|
|
|
8191
8754
|
const status = checkJsonMcpConfig(configPath);
|
|
8192
8755
|
if (status === "missing") return false;
|
|
8193
8756
|
if (status === "found") {
|
|
8194
|
-
ok(name, `configured (${
|
|
8195
|
-
report.add(`client_${clientId}`, `OK ${
|
|
8757
|
+
ok(name, `configured (${sanitize2(configPath)})`);
|
|
8758
|
+
report.add(`client_${clientId}`, `OK ${sanitize2(configPath)}`);
|
|
8196
8759
|
configuredClients.push(clientId);
|
|
8197
8760
|
return false;
|
|
8198
8761
|
}
|
|
@@ -8200,8 +8763,8 @@ function handleJsonClient(clientId, report, configuredClients) {
|
|
|
8200
8763
|
fail(name, "okx-trade-mcp not found in mcpServers", [`Run: okx setup --client ${clientId}`]);
|
|
8201
8764
|
report.add(`client_${clientId}`, "NOT_CONFIGURED");
|
|
8202
8765
|
} else {
|
|
8203
|
-
fail(name, `JSON parse error in ${
|
|
8204
|
-
`Check ${
|
|
8766
|
+
fail(name, `JSON parse error in ${sanitize2(configPath)}`, [
|
|
8767
|
+
`Check ${sanitize2(configPath)} for JSON syntax errors`,
|
|
8205
8768
|
`Then run: okx setup --client ${clientId}`
|
|
8206
8769
|
]);
|
|
8207
8770
|
report.add(`client_${clientId}`, "PARSE_ERROR");
|
|
@@ -8294,37 +8857,37 @@ function checkToolCount(report, configuredClients, getSpecs = allToolSpecs) {
|
|
|
8294
8857
|
}
|
|
8295
8858
|
}
|
|
8296
8859
|
function readLogTail(logPath) {
|
|
8297
|
-
const stat =
|
|
8860
|
+
const stat = fs5.statSync(logPath);
|
|
8298
8861
|
const readSize = Math.min(8192, stat.size);
|
|
8299
8862
|
const buffer = Buffer.alloc(readSize);
|
|
8300
|
-
const fd =
|
|
8863
|
+
const fd = fs5.openSync(logPath, "r");
|
|
8301
8864
|
try {
|
|
8302
|
-
|
|
8865
|
+
fs5.readSync(fd, buffer, 0, readSize, Math.max(0, stat.size - readSize));
|
|
8303
8866
|
} finally {
|
|
8304
|
-
|
|
8867
|
+
fs5.closeSync(fd);
|
|
8305
8868
|
}
|
|
8306
8869
|
return buffer.toString("utf8").split("\n").filter((l) => l.trim()).slice(-5);
|
|
8307
8870
|
}
|
|
8308
8871
|
function getMcpLogCandidates() {
|
|
8309
8872
|
if (process.platform === "darwin") {
|
|
8310
|
-
const logsDir =
|
|
8873
|
+
const logsDir = path4.join(os4.homedir(), "Library", "Logs", "Claude");
|
|
8311
8874
|
const candidates = [
|
|
8312
|
-
|
|
8313
|
-
|
|
8875
|
+
path4.join(logsDir, "mcp.log"),
|
|
8876
|
+
path4.join(logsDir, "mcp-server-okx-trade-mcp.log")
|
|
8314
8877
|
];
|
|
8315
8878
|
try {
|
|
8316
|
-
const extra =
|
|
8879
|
+
const extra = fs5.readdirSync(logsDir).filter((f) => f.startsWith("mcp") && f.endsWith(".log")).map((f) => path4.join(logsDir, f));
|
|
8317
8880
|
candidates.push(...extra);
|
|
8318
8881
|
} catch (_e) {
|
|
8319
8882
|
}
|
|
8320
8883
|
return candidates;
|
|
8321
8884
|
}
|
|
8322
8885
|
if (process.platform === "win32") {
|
|
8323
|
-
const appData2 = process.env.APPDATA ??
|
|
8324
|
-
return [
|
|
8886
|
+
const appData2 = process.env.APPDATA ?? path4.join(os4.homedir(), "AppData", "Roaming");
|
|
8887
|
+
return [path4.join(appData2, "Claude", "logs", "mcp.log")];
|
|
8325
8888
|
}
|
|
8326
|
-
const configHome = process.env.XDG_CONFIG_HOME ??
|
|
8327
|
-
return [
|
|
8889
|
+
const configHome = process.env.XDG_CONFIG_HOME ?? path4.join(os4.homedir(), ".config");
|
|
8890
|
+
return [path4.join(configHome, "Claude", "logs", "mcp.log")];
|
|
8328
8891
|
}
|
|
8329
8892
|
function checkMcpLogs(report) {
|
|
8330
8893
|
section("MCP Server Logs (recent)");
|
|
@@ -8338,7 +8901,7 @@ function checkMcpLogs(report) {
|
|
|
8338
8901
|
report.add("mcp_log", logPath);
|
|
8339
8902
|
if (lines.length > 0) {
|
|
8340
8903
|
ok("last lines", `(${lines.length} shown)`);
|
|
8341
|
-
for (const line of lines) outputLine(` ${
|
|
8904
|
+
for (const line of lines) outputLine(` ${sanitize2(line)}`);
|
|
8342
8905
|
} else {
|
|
8343
8906
|
ok("last lines", "(empty log)");
|
|
8344
8907
|
}
|
|
@@ -8464,11 +9027,11 @@ function checkModuleLoading(entryPath, report) {
|
|
|
8464
9027
|
return true;
|
|
8465
9028
|
} else {
|
|
8466
9029
|
const errMsg = result.stderr?.trim() || result.error?.message || "non-zero exit";
|
|
8467
|
-
fail("module load", `failed: ${
|
|
9030
|
+
fail("module load", `failed: ${sanitize2(errMsg)}`, [
|
|
8468
9031
|
"MCP server may have import errors or missing dependencies",
|
|
8469
9032
|
`Try: node ${entryPath} --version`
|
|
8470
9033
|
]);
|
|
8471
|
-
report.add("module_load", `FAIL ${
|
|
9034
|
+
report.add("module_load", `FAIL ${sanitize2(errMsg)}`);
|
|
8472
9035
|
return false;
|
|
8473
9036
|
}
|
|
8474
9037
|
}
|
|
@@ -8479,7 +9042,7 @@ async function cmdDiagnoseMcp(options = {}) {
|
|
|
8479
9042
|
const report = new Report();
|
|
8480
9043
|
report.add("ts", (/* @__PURE__ */ new Date()).toISOString());
|
|
8481
9044
|
report.add("mode", "mcp");
|
|
8482
|
-
report.add("os", `${process.platform} ${process.arch} ${
|
|
9045
|
+
report.add("os", `${process.platform} ${process.arch} ${os4.release()}`);
|
|
8483
9046
|
checkMcpPackageVersion(report);
|
|
8484
9047
|
const nodePassed = checkNodeCompat(report);
|
|
8485
9048
|
const { entryPath, passed: entryPassed } = checkMcpEntryPoint(report);
|
|
@@ -8511,7 +9074,7 @@ async function cmdDiagnoseMcp(options = {}) {
|
|
|
8511
9074
|
|
|
8512
9075
|
// src/commands/diagnose.ts
|
|
8513
9076
|
var CLI_VERSION = readCliVersion();
|
|
8514
|
-
var GIT_HASH = true ? "
|
|
9077
|
+
var GIT_HASH = true ? "caa6dae" : "dev";
|
|
8515
9078
|
function maskKey2(key) {
|
|
8516
9079
|
if (!key) return "(not set)";
|
|
8517
9080
|
if (key.length <= 8) return "****";
|
|
@@ -8591,14 +9154,14 @@ function checkEnvironment(report) {
|
|
|
8591
9154
|
}
|
|
8592
9155
|
ok("CLI", `v${CLI_VERSION} (${GIT_HASH})`);
|
|
8593
9156
|
ok("OS", `${process.platform} ${process.arch}`);
|
|
8594
|
-
ok("OS release",
|
|
9157
|
+
ok("OS release", os5.release());
|
|
8595
9158
|
ok("Shell", process.env.SHELL ?? "(unknown)");
|
|
8596
9159
|
ok("Locale", `${process.env.LANG ?? process.env.LC_ALL ?? "(unknown)"}`);
|
|
8597
9160
|
ok("Timezone", Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
8598
9161
|
report.add("cli", `${CLI_VERSION} (${GIT_HASH})`);
|
|
8599
9162
|
report.add("node", `${nodeVersion} ${process.platform} ${process.arch}`);
|
|
8600
|
-
const machine = typeof
|
|
8601
|
-
report.add("os", `${
|
|
9163
|
+
const machine = typeof os5.machine === "function" ? os5.machine() : process.arch;
|
|
9164
|
+
report.add("os", `${os5.type()} ${os5.release()} ${machine}`);
|
|
8602
9165
|
report.add("shell", process.env.SHELL ?? "-");
|
|
8603
9166
|
report.add("locale", process.env.LANG ?? process.env.LC_ALL ?? "-");
|
|
8604
9167
|
report.add("tz", Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
@@ -8755,10 +9318,10 @@ async function cmdDiagnose(config, profile, options = {}) {
|
|
|
8755
9318
|
}
|
|
8756
9319
|
function checkConfigFile(report) {
|
|
8757
9320
|
section("Config File");
|
|
8758
|
-
const
|
|
9321
|
+
const path6 = configFilePath();
|
|
8759
9322
|
try {
|
|
8760
9323
|
readFullConfig();
|
|
8761
|
-
ok("Config parse", `${
|
|
9324
|
+
ok("Config parse", `${path6} OK`);
|
|
8762
9325
|
report.add("config_parse", "OK");
|
|
8763
9326
|
return true;
|
|
8764
9327
|
} catch (e) {
|
|
@@ -8928,7 +9491,7 @@ function loadProfileConfig(opts) {
|
|
|
8928
9491
|
import { EOL as EOL2 } from "os";
|
|
8929
9492
|
|
|
8930
9493
|
// src/commands/client-setup.ts
|
|
8931
|
-
import * as
|
|
9494
|
+
import * as fs6 from "fs";
|
|
8932
9495
|
var DETECTABLE_CLIENTS = ["claude-desktop", "cursor", "windsurf"];
|
|
8933
9496
|
function cmdSetupClient(options) {
|
|
8934
9497
|
runSetup(options);
|
|
@@ -8937,14 +9500,14 @@ function cmdSetupClients() {
|
|
|
8937
9500
|
const detected = [];
|
|
8938
9501
|
for (const id of DETECTABLE_CLIENTS) {
|
|
8939
9502
|
const p = getConfigPath(id);
|
|
8940
|
-
if (p &&
|
|
9503
|
+
if (p && fs6.existsSync(p)) {
|
|
8941
9504
|
detected.push({ id, path: p });
|
|
8942
9505
|
}
|
|
8943
9506
|
}
|
|
8944
9507
|
if (detected.length > 0) {
|
|
8945
9508
|
outputLine("Detected clients:");
|
|
8946
|
-
for (const { id, path:
|
|
8947
|
-
outputLine(` ${id.padEnd(16)} ${
|
|
9509
|
+
for (const { id, path: path6 } of detected) {
|
|
9510
|
+
outputLine(` ${id.padEnd(16)} ${path6}`);
|
|
8948
9511
|
}
|
|
8949
9512
|
outputLine("");
|
|
8950
9513
|
}
|
|
@@ -9328,7 +9891,7 @@ var HELP_TREE = {
|
|
|
9328
9891
|
description: "Earn products \u2014 Simple Earn, On-chain Earn, and DCD (Dual Currency Deposit)",
|
|
9329
9892
|
subgroups: {
|
|
9330
9893
|
savings: {
|
|
9331
|
-
description: "Simple Earn \u2014 flexible savings and lending",
|
|
9894
|
+
description: "Simple Earn \u2014 flexible savings, fixed-term, and lending",
|
|
9332
9895
|
commands: {
|
|
9333
9896
|
balance: {
|
|
9334
9897
|
usage: "okx earn savings balance [<ccy>]",
|
|
@@ -9352,7 +9915,19 @@ var HELP_TREE = {
|
|
|
9352
9915
|
},
|
|
9353
9916
|
"rate-history": {
|
|
9354
9917
|
usage: "okx earn savings rate-history [--ccy <ccy>] [--limit <n>]",
|
|
9355
|
-
description: "Query Simple Earn lending rates (
|
|
9918
|
+
description: "Query Simple Earn lending rates and fixed-term offers (requires auth)"
|
|
9919
|
+
},
|
|
9920
|
+
"fixed-orders": {
|
|
9921
|
+
usage: "okx earn savings fixed-orders [--ccy <ccy>] [--state <pending|earning|expired|settled|cancelled>]",
|
|
9922
|
+
description: "List fixed-term earn orders"
|
|
9923
|
+
},
|
|
9924
|
+
"fixed-purchase": {
|
|
9925
|
+
usage: "okx earn savings fixed-purchase --ccy <ccy> --amt <n> --term <term> [--confirm]",
|
|
9926
|
+
description: "Purchase Simple Earn Fixed (\u5B9A\u671F). Preview by default; add --confirm to execute. Funds locked until maturity"
|
|
9927
|
+
},
|
|
9928
|
+
"fixed-redeem": {
|
|
9929
|
+
usage: "okx earn savings fixed-redeem <reqId>",
|
|
9930
|
+
description: "Redeem a fixed-term earn order (full amount)"
|
|
9356
9931
|
}
|
|
9357
9932
|
}
|
|
9358
9933
|
},
|
|
@@ -9532,6 +10107,7 @@ function printGlobalHelp() {
|
|
|
9532
10107
|
" --demo Use simulated trading (demo) mode",
|
|
9533
10108
|
" --live Force live trading mode (overrides profile demo=true; mutually exclusive with --demo)",
|
|
9534
10109
|
" --json Output raw JSON",
|
|
10110
|
+
" --env With --json, wrap output as {env, profile, data}",
|
|
9535
10111
|
" --verbose Show detailed network request/response info (stderr)",
|
|
9536
10112
|
" --version, -v Show version",
|
|
9537
10113
|
" --help Show this help",
|
|
@@ -9646,8 +10222,8 @@ function printCommandList(lines, commands) {
|
|
|
9646
10222
|
lines.push("");
|
|
9647
10223
|
}
|
|
9648
10224
|
}
|
|
9649
|
-
function printHelp(...
|
|
9650
|
-
const [moduleName, subgroupName] =
|
|
10225
|
+
function printHelp(...path6) {
|
|
10226
|
+
const [moduleName, subgroupName] = path6;
|
|
9651
10227
|
if (!moduleName) {
|
|
9652
10228
|
printGlobalHelp();
|
|
9653
10229
|
} else if (!subgroupName) {
|
|
@@ -9663,6 +10239,7 @@ var CLI_OPTIONS = {
|
|
|
9663
10239
|
profile: { type: "string" },
|
|
9664
10240
|
demo: { type: "boolean", default: false },
|
|
9665
10241
|
json: { type: "boolean", default: false },
|
|
10242
|
+
env: { type: "boolean", default: false },
|
|
9666
10243
|
help: { type: "boolean", default: false },
|
|
9667
10244
|
version: { type: "boolean", short: "v", default: false },
|
|
9668
10245
|
// setup command
|
|
@@ -9768,6 +10345,8 @@ var CLI_OPTIONS = {
|
|
|
9768
10345
|
orders: { type: "string" },
|
|
9769
10346
|
// earn
|
|
9770
10347
|
rate: { type: "string" },
|
|
10348
|
+
reqId: { type: "string" },
|
|
10349
|
+
confirm: { type: "boolean", default: false },
|
|
9771
10350
|
// audit
|
|
9772
10351
|
since: { type: "string" },
|
|
9773
10352
|
tool: { type: "string" },
|
|
@@ -10043,6 +10622,10 @@ async function cmdMarketCandles(run, instId, opts) {
|
|
|
10043
10622
|
}))
|
|
10044
10623
|
);
|
|
10045
10624
|
}
|
|
10625
|
+
function cmdMarketIndicatorList(json) {
|
|
10626
|
+
if (json) return printJson(KNOWN_INDICATORS);
|
|
10627
|
+
printTable(KNOWN_INDICATORS.map(({ name, description }) => ({ name, description })));
|
|
10628
|
+
}
|
|
10046
10629
|
async function cmdMarketIndicator(run, indicator, instId, opts) {
|
|
10047
10630
|
const params = opts.params ? opts.params.split(",").map((p) => Number(p.trim())).filter((n) => !Number.isNaN(n)) : void 0;
|
|
10048
10631
|
const result = await run("market_get_indicator", {
|
|
@@ -10140,9 +10723,9 @@ async function cmdMarketStockTokens(run, opts) {
|
|
|
10140
10723
|
}
|
|
10141
10724
|
|
|
10142
10725
|
// src/commands/account.ts
|
|
10143
|
-
import * as
|
|
10144
|
-
import * as
|
|
10145
|
-
import * as
|
|
10726
|
+
import * as fs7 from "fs";
|
|
10727
|
+
import * as path5 from "path";
|
|
10728
|
+
import * as os6 from "os";
|
|
10146
10729
|
function getData2(result) {
|
|
10147
10730
|
return result.data;
|
|
10148
10731
|
}
|
|
@@ -10353,10 +10936,10 @@ function readAuditLogs(logDir, days = 7) {
|
|
|
10353
10936
|
const yyyy = d.getUTCFullYear();
|
|
10354
10937
|
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
10355
10938
|
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
10356
|
-
const filePath =
|
|
10939
|
+
const filePath = path5.join(logDir, `trade-${yyyy}-${mm}-${dd}.log`);
|
|
10357
10940
|
let content;
|
|
10358
10941
|
try {
|
|
10359
|
-
content =
|
|
10942
|
+
content = fs7.readFileSync(filePath, "utf8");
|
|
10360
10943
|
} catch {
|
|
10361
10944
|
continue;
|
|
10362
10945
|
}
|
|
@@ -10372,7 +10955,7 @@ function readAuditLogs(logDir, days = 7) {
|
|
|
10372
10955
|
return entries;
|
|
10373
10956
|
}
|
|
10374
10957
|
function cmdAccountAudit(opts) {
|
|
10375
|
-
const logDir =
|
|
10958
|
+
const logDir = path5.join(os6.homedir(), ".okx", "logs");
|
|
10376
10959
|
const limit = Math.min(Number(opts.limit) || 20, 100);
|
|
10377
10960
|
let entries = readAuditLogs(logDir);
|
|
10378
10961
|
if (opts.tool) entries = entries.filter((e) => e.tool === opts.tool);
|
|
@@ -11360,6 +11943,7 @@ async function cmdOptionPlace(run, opts) {
|
|
|
11360
11943
|
side: opts.side,
|
|
11361
11944
|
ordType: opts.ordType,
|
|
11362
11945
|
sz: opts.sz,
|
|
11946
|
+
tgtCcy: opts.tgtCcy,
|
|
11363
11947
|
px: opts.px,
|
|
11364
11948
|
reduceOnly: opts.reduceOnly,
|
|
11365
11949
|
clOrdId: opts.clOrdId,
|
|
@@ -11827,6 +12411,22 @@ async function cmdEarnSavingsBalance(run, ccy, json) {
|
|
|
11827
12411
|
pendingAmt: r["pendingAmt"]
|
|
11828
12412
|
}));
|
|
11829
12413
|
}
|
|
12414
|
+
async function cmdEarnFixedOrderList(run, opts) {
|
|
12415
|
+
const data = extractData(await run("earn_get_fixed_order_list", {
|
|
12416
|
+
ccy: opts.ccy,
|
|
12417
|
+
state: opts.state
|
|
12418
|
+
}));
|
|
12419
|
+
printDataList(data, opts.json, "No fixed earn orders", (r) => ({
|
|
12420
|
+
reqId: r["reqId"],
|
|
12421
|
+
ccy: r["ccy"],
|
|
12422
|
+
amt: r["amt"],
|
|
12423
|
+
rate: r["rate"],
|
|
12424
|
+
term: r["term"],
|
|
12425
|
+
state: r["state"],
|
|
12426
|
+
accruedInterest: r["accruedInterest"],
|
|
12427
|
+
cTime: new Date(Number(r["cTime"])).toLocaleString()
|
|
12428
|
+
}));
|
|
12429
|
+
}
|
|
11830
12430
|
async function cmdEarnSavingsPurchase(run, opts) {
|
|
11831
12431
|
const data = extractData(await run("earn_savings_purchase", { ccy: opts.ccy, amt: opts.amt, rate: opts.rate }));
|
|
11832
12432
|
if (opts.json) {
|
|
@@ -11872,14 +12472,107 @@ async function cmdEarnLendingHistory(run, opts) {
|
|
|
11872
12472
|
ts: new Date(Number(r["ts"])).toLocaleString()
|
|
11873
12473
|
}));
|
|
11874
12474
|
}
|
|
12475
|
+
function printFixedPurchasePreview(rec) {
|
|
12476
|
+
const offer = rec["offer"];
|
|
12477
|
+
outputLine("");
|
|
12478
|
+
outputLine("\u{1F4CB} Fixed Earn Purchase Preview");
|
|
12479
|
+
outputLine(` Currency: ${rec["ccy"]}`);
|
|
12480
|
+
outputLine(` Amount: ${rec["amt"]}`);
|
|
12481
|
+
outputLine(` Term: ${rec["term"]}`);
|
|
12482
|
+
if (rec["currentFlexibleRate"]) {
|
|
12483
|
+
outputLine(` Current flexible rate: ${rec["currentFlexibleRate"]}`);
|
|
12484
|
+
}
|
|
12485
|
+
if (offer) {
|
|
12486
|
+
printKv({
|
|
12487
|
+
rate: offer["rate"],
|
|
12488
|
+
minLend: offer["minLend"],
|
|
12489
|
+
remainingQuota: offer["lendQuota"],
|
|
12490
|
+
soldOut: offer["soldOut"] ? "Yes" : "No"
|
|
12491
|
+
});
|
|
12492
|
+
} else {
|
|
12493
|
+
outputLine(" \u26A0\uFE0F No matching offer found for this term.");
|
|
12494
|
+
}
|
|
12495
|
+
outputLine("");
|
|
12496
|
+
outputLine(rec["warning"] ?? "");
|
|
12497
|
+
outputLine("");
|
|
12498
|
+
outputLine("Re-run with --confirm to execute.");
|
|
12499
|
+
}
|
|
12500
|
+
async function cmdEarnFixedPurchase(run, opts) {
|
|
12501
|
+
const result = await run("earn_fixed_purchase", {
|
|
12502
|
+
ccy: opts.ccy,
|
|
12503
|
+
amt: opts.amt,
|
|
12504
|
+
term: opts.term,
|
|
12505
|
+
confirm: opts.confirm
|
|
12506
|
+
});
|
|
12507
|
+
if (!result || typeof result !== "object") {
|
|
12508
|
+
outputLine("No response data");
|
|
12509
|
+
return;
|
|
12510
|
+
}
|
|
12511
|
+
const rec = result;
|
|
12512
|
+
if (rec["preview"]) {
|
|
12513
|
+
if (opts.json) {
|
|
12514
|
+
printJson(rec);
|
|
12515
|
+
return;
|
|
12516
|
+
}
|
|
12517
|
+
printFixedPurchasePreview(rec);
|
|
12518
|
+
return;
|
|
12519
|
+
}
|
|
12520
|
+
const data = extractData(result);
|
|
12521
|
+
if (opts.json) {
|
|
12522
|
+
printJson(data);
|
|
12523
|
+
return;
|
|
12524
|
+
}
|
|
12525
|
+
const r = data[0];
|
|
12526
|
+
if (!r) {
|
|
12527
|
+
outputLine("No response data");
|
|
12528
|
+
return;
|
|
12529
|
+
}
|
|
12530
|
+
printKv({ reqId: r["reqId"], ccy: r["ccy"], amt: r["amt"], term: r["term"] });
|
|
12531
|
+
}
|
|
12532
|
+
async function cmdEarnFixedRedeem(run, opts) {
|
|
12533
|
+
const data = extractData(await run("earn_fixed_redeem", { reqId: opts.reqId }));
|
|
12534
|
+
if (opts.json) {
|
|
12535
|
+
printJson(data);
|
|
12536
|
+
return;
|
|
12537
|
+
}
|
|
12538
|
+
if (!data.length) {
|
|
12539
|
+
outputLine("No response data");
|
|
12540
|
+
return;
|
|
12541
|
+
}
|
|
12542
|
+
printTable(data.map((r) => ({ reqId: r["reqId"] })));
|
|
12543
|
+
}
|
|
11875
12544
|
async function cmdEarnLendingRateHistory(run, opts) {
|
|
11876
|
-
const
|
|
11877
|
-
|
|
12545
|
+
const result = await run("earn_get_lending_rate_history", { ccy: opts.ccy, limit: opts.limit });
|
|
12546
|
+
const data = extractData(result);
|
|
12547
|
+
const fixedOffers = extractFixedOffers(result);
|
|
12548
|
+
if (opts.json) {
|
|
12549
|
+
printJson({ data, fixedOffers });
|
|
12550
|
+
return;
|
|
12551
|
+
}
|
|
12552
|
+
printDataList(data, false, "No rate history data", (r) => ({
|
|
11878
12553
|
ccy: r["ccy"],
|
|
11879
12554
|
lendingRate: r["lendingRate"],
|
|
11880
|
-
rate: r["rate"],
|
|
11881
12555
|
ts: new Date(Number(r["ts"])).toLocaleString()
|
|
11882
12556
|
}));
|
|
12557
|
+
if (fixedOffers.length > 0) {
|
|
12558
|
+
outputLine("");
|
|
12559
|
+
outputLine("Fixed-term offers:");
|
|
12560
|
+
printTable(fixedOffers.map((r) => ({
|
|
12561
|
+
ccy: r["ccy"],
|
|
12562
|
+
term: r["term"],
|
|
12563
|
+
rate: r["rate"],
|
|
12564
|
+
minLend: r["minLend"],
|
|
12565
|
+
remainingQuota: r["lendQuota"],
|
|
12566
|
+
soldOut: r["soldOut"] ? "Yes" : "No"
|
|
12567
|
+
})));
|
|
12568
|
+
}
|
|
12569
|
+
}
|
|
12570
|
+
function extractFixedOffers(result) {
|
|
12571
|
+
if (result && typeof result === "object") {
|
|
12572
|
+
const offers = result["fixedOffers"];
|
|
12573
|
+
if (Array.isArray(offers)) return offers;
|
|
12574
|
+
}
|
|
12575
|
+
return [];
|
|
11883
12576
|
}
|
|
11884
12577
|
|
|
11885
12578
|
// src/commands/auto-earn.ts
|
|
@@ -12406,7 +13099,7 @@ async function cmdDcdRedeemExecute(run, opts) {
|
|
|
12406
13099
|
ordId: r["ordId"],
|
|
12407
13100
|
state: r["state"],
|
|
12408
13101
|
redeemSz: q["redeemSz"] ? `${parseFloat(q["redeemSz"]).toFixed(8)} ${q["redeemCcy"]}` : "\u2014",
|
|
12409
|
-
termRate: q["termRate"] ? `${q["termRate"]}%` : "\u2014"
|
|
13102
|
+
termRate: q["termRate"] ? `${(parseFloat(q["termRate"]) * 100).toFixed(2)}%` : "\u2014"
|
|
12410
13103
|
});
|
|
12411
13104
|
}
|
|
12412
13105
|
async function cmdDcdOrderState(run, opts) {
|
|
@@ -12459,7 +13152,7 @@ async function cmdDcdOrders(run, opts) {
|
|
|
12459
13152
|
quoteCcy: r["quoteCcy"],
|
|
12460
13153
|
strike: r["strike"],
|
|
12461
13154
|
notionalSz: r["notionalSz"],
|
|
12462
|
-
annualizedYield: r["annualizedYield"],
|
|
13155
|
+
annualizedYield: r["annualizedYield"] ? `${(parseFloat(r["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
|
|
12463
13156
|
yieldSz: r["yieldSz"],
|
|
12464
13157
|
settleTime: r["settleTime"] ? new Date(Number(r["settleTime"])).toLocaleDateString() : "",
|
|
12465
13158
|
// scheduled settlement time
|
|
@@ -12499,7 +13192,7 @@ async function cmdDcdQuoteAndBuy(run, opts) {
|
|
|
12499
13192
|
outputLine("Quote:");
|
|
12500
13193
|
printKv({
|
|
12501
13194
|
quoteId: q["quoteId"],
|
|
12502
|
-
annualizedYield: q["annualizedYield"] ? `${q["annualizedYield"]}%` : "\u2014",
|
|
13195
|
+
annualizedYield: q["annualizedYield"] ? `${(parseFloat(q["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
|
|
12503
13196
|
absYield: q["absYield"],
|
|
12504
13197
|
notionalSz: q["notionalSz"],
|
|
12505
13198
|
notionalCcy: q["notionalCcy"]
|
|
@@ -12533,6 +13226,7 @@ function resolveNpx() {
|
|
|
12533
13226
|
if (existsSync7(sibling)) return sibling;
|
|
12534
13227
|
return "npx";
|
|
12535
13228
|
}
|
|
13229
|
+
var THIRD_PARTY_INSTALL_NOTICE = "Note: This skill was created by a third-party developer, not by OKX. Review SKILL.md before use.";
|
|
12536
13230
|
async function cmdSkillSearch(run, opts) {
|
|
12537
13231
|
const args = {};
|
|
12538
13232
|
if (opts.keyword) args.keyword = opts.keyword;
|
|
@@ -12608,11 +13302,7 @@ async function cmdSkillAdd(name, config, json) {
|
|
|
12608
13302
|
throw e;
|
|
12609
13303
|
}
|
|
12610
13304
|
upsertSkillRecord(meta);
|
|
12611
|
-
|
|
12612
|
-
outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
|
|
12613
|
-
} else {
|
|
12614
|
-
outputLine(`\u2713 Skill "${meta.name}" v${meta.version} installed`);
|
|
12615
|
-
}
|
|
13305
|
+
printSkillInstallResult(meta, json);
|
|
12616
13306
|
} finally {
|
|
12617
13307
|
rmSync(tmpBase, { recursive: true, force: true });
|
|
12618
13308
|
}
|
|
@@ -12705,11 +13395,19 @@ function cmdSkillList(json) {
|
|
|
12705
13395
|
outputLine("");
|
|
12706
13396
|
outputLine(`${skills.length} skills installed.`);
|
|
12707
13397
|
}
|
|
13398
|
+
function printSkillInstallResult(meta, json) {
|
|
13399
|
+
if (json) {
|
|
13400
|
+
outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
|
|
13401
|
+
} else {
|
|
13402
|
+
outputLine(`\u2713 Skill "${meta.name}" v${meta.version} installed`);
|
|
13403
|
+
outputLine(` ${THIRD_PARTY_INSTALL_NOTICE}`);
|
|
13404
|
+
}
|
|
13405
|
+
}
|
|
12708
13406
|
|
|
12709
13407
|
// src/index.ts
|
|
12710
13408
|
var _require3 = createRequire3(import.meta.url);
|
|
12711
13409
|
var CLI_VERSION2 = _require3("../package.json").version;
|
|
12712
|
-
var GIT_HASH2 = true ? "
|
|
13410
|
+
var GIT_HASH2 = true ? "caa6dae" : "dev";
|
|
12713
13411
|
function handleConfigCommand(action, rest, json, lang, force) {
|
|
12714
13412
|
if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
|
|
12715
13413
|
if (action === "show") return cmdConfigShow(json);
|
|
@@ -12759,18 +13457,20 @@ function handleMarketPublicCommand(run, action, rest, v, json) {
|
|
|
12759
13457
|
instId: v.instId,
|
|
12760
13458
|
json
|
|
12761
13459
|
});
|
|
12762
|
-
if (action === "indicator")
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
|
|
12773
|
-
|
|
13460
|
+
if (action === "indicator") return handleIndicatorAction(run, rest, v, json);
|
|
13461
|
+
}
|
|
13462
|
+
function handleIndicatorAction(run, rest, v, json) {
|
|
13463
|
+
if (rest[0] === "list") return cmdMarketIndicatorList(json);
|
|
13464
|
+
const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
|
|
13465
|
+
const backtestTime = v["backtest-time"] !== void 0 ? Number(v["backtest-time"]) : void 0;
|
|
13466
|
+
return cmdMarketIndicator(run, rest[0], rest[1], {
|
|
13467
|
+
bar: v.bar,
|
|
13468
|
+
params: v.params,
|
|
13469
|
+
list: v.list,
|
|
13470
|
+
limit,
|
|
13471
|
+
backtestTime,
|
|
13472
|
+
json
|
|
13473
|
+
});
|
|
12774
13474
|
}
|
|
12775
13475
|
function handleMarketDataCommand(run, action, rest, v, json) {
|
|
12776
13476
|
const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
|
|
@@ -13116,6 +13816,7 @@ function handleOptionCommand(run, action, rest, v, json) {
|
|
|
13116
13816
|
side: v.side,
|
|
13117
13817
|
ordType: v.ordType,
|
|
13118
13818
|
sz: v.sz,
|
|
13819
|
+
tgtCcy: v.tgtCcy,
|
|
13119
13820
|
px: v.px,
|
|
13120
13821
|
reduceOnly: v.reduceOnly,
|
|
13121
13822
|
clOrdId: v.clOrdId,
|
|
@@ -13406,6 +14107,9 @@ function handleEarnSavingsCommand(run, action, rest, v, json) {
|
|
|
13406
14107
|
if (action === "set-rate") return cmdEarnSetLendingRate(run, { ccy: v.ccy, rate: v.rate, json });
|
|
13407
14108
|
if (action === "lending-history") return cmdEarnLendingHistory(run, { ccy: v.ccy, limit, json });
|
|
13408
14109
|
if (action === "rate-history") return cmdEarnLendingRateHistory(run, { ccy: v.ccy, limit, json });
|
|
14110
|
+
if (action === "fixed-orders") return cmdEarnFixedOrderList(run, { ccy: v.ccy, state: v.state, json });
|
|
14111
|
+
if (action === "fixed-purchase") return cmdEarnFixedPurchase(run, { ccy: v.ccy, amt: v.amt, term: v.term, confirm: v.confirm ?? false, json });
|
|
14112
|
+
if (action === "fixed-redeem") return cmdEarnFixedRedeem(run, { reqId: v.reqId, json });
|
|
13409
14113
|
errorLine(`Unknown earn savings command: ${action}`);
|
|
13410
14114
|
process.exitCode = 1;
|
|
13411
14115
|
}
|
|
@@ -13524,6 +14228,31 @@ function printHelpForLevel(positionals) {
|
|
|
13524
14228
|
else if (!subgroup) printHelp(module);
|
|
13525
14229
|
else printHelp(module, subgroup);
|
|
13526
14230
|
}
|
|
14231
|
+
function wrapRunnerWithLogger(baseRunner, logger) {
|
|
14232
|
+
const writeToolNames = new Set(allToolSpecs().filter((t) => t.isWrite).map((t) => t.name));
|
|
14233
|
+
return async (toolName, args) => {
|
|
14234
|
+
const startTime = Date.now();
|
|
14235
|
+
try {
|
|
14236
|
+
const result = await baseRunner(toolName, args);
|
|
14237
|
+
if (writeToolNames.has(toolName)) {
|
|
14238
|
+
markFailedIfSCodeError(result.data);
|
|
14239
|
+
}
|
|
14240
|
+
logger.log("info", toolName, args, result, Date.now() - startTime);
|
|
14241
|
+
return result;
|
|
14242
|
+
} catch (error) {
|
|
14243
|
+
logger.log("error", toolName, args, error, Date.now() - startTime);
|
|
14244
|
+
throw error;
|
|
14245
|
+
}
|
|
14246
|
+
};
|
|
14247
|
+
}
|
|
14248
|
+
async function runDiagnose(v) {
|
|
14249
|
+
let config;
|
|
14250
|
+
try {
|
|
14251
|
+
config = loadProfileConfig({ profile: v.profile, demo: v.demo, live: v.live, verbose: v.verbose, userAgent: `okx-trade-cli/${CLI_VERSION2}`, sourceTag: "CLI" });
|
|
14252
|
+
} catch {
|
|
14253
|
+
}
|
|
14254
|
+
return cmdDiagnose(config, v.profile ?? "default", { mcp: v.mcp, cli: v.cli, all: v.all, output: v.output });
|
|
14255
|
+
}
|
|
13527
14256
|
async function main() {
|
|
13528
14257
|
setOutput({
|
|
13529
14258
|
out: (m) => process.stdout.write(m),
|
|
@@ -13545,25 +14274,14 @@ async function main() {
|
|
|
13545
14274
|
if (module === "config") return handleConfigCommand(action, rest, json, v.lang, v.force);
|
|
13546
14275
|
if (module === "setup") return handleSetupCommand(v);
|
|
13547
14276
|
if (module === "upgrade") return cmdUpgrade(CLI_VERSION2, { beta: v.beta, check: v.check, force: v.force }, json);
|
|
13548
|
-
if (module === "diagnose")
|
|
13549
|
-
let config2;
|
|
13550
|
-
try {
|
|
13551
|
-
config2 = loadProfileConfig({ profile: v.profile, demo: v.demo, live: v.live, verbose: v.verbose, userAgent: `okx-trade-cli/${CLI_VERSION2}`, sourceTag: "CLI" });
|
|
13552
|
-
} catch {
|
|
13553
|
-
}
|
|
13554
|
-
return cmdDiagnose(config2, v.profile ?? "default", { mcp: v.mcp, cli: v.cli, all: v.all, output: v.output });
|
|
13555
|
-
}
|
|
14277
|
+
if (module === "diagnose") return runDiagnose(v);
|
|
13556
14278
|
const config = loadProfileConfig({ profile: v.profile, demo: v.demo, live: v.live, verbose: v.verbose, userAgent: `okx-trade-cli/${CLI_VERSION2}`, sourceTag: "CLI" });
|
|
14279
|
+
setEnvContext({ demo: config.demo, profile: v.profile ?? "default" });
|
|
14280
|
+
setJsonEnvEnabled(v.env ?? false);
|
|
13557
14281
|
const client = new OkxRestClient(config);
|
|
13558
14282
|
const baseRunner = createToolRunner(client, config);
|
|
13559
|
-
const
|
|
13560
|
-
const run =
|
|
13561
|
-
const result = await baseRunner(toolName, args);
|
|
13562
|
-
if (writeToolNames.has(toolName)) {
|
|
13563
|
-
markFailedIfSCodeError(result.data);
|
|
13564
|
-
}
|
|
13565
|
-
return result;
|
|
13566
|
-
};
|
|
14283
|
+
const logger = new TradeLogger("info");
|
|
14284
|
+
const run = wrapRunnerWithLogger(baseRunner, logger);
|
|
13567
14285
|
const moduleHandlers = {
|
|
13568
14286
|
market: () => handleMarketCommand(run, action, rest, v, json),
|
|
13569
14287
|
account: () => handleAccountCommand(run, action, rest, v, json),
|
|
@@ -13608,7 +14326,8 @@ export {
|
|
|
13608
14326
|
handleSpotCommand,
|
|
13609
14327
|
handleSwapAlgoCommand,
|
|
13610
14328
|
handleSwapCommand,
|
|
13611
|
-
printHelp
|
|
14329
|
+
printHelp,
|
|
14330
|
+
wrapRunnerWithLogger
|
|
13612
14331
|
};
|
|
13613
14332
|
/*! Bundled license information:
|
|
13614
14333
|
|