hvip-mcp-server 0.3.2 → 0.4.0
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/README.md +11 -1
- package/dist/hub-server.js +5769 -0
- package/dist/hub-worker.js +4039 -0
- package/dist/index.js +683 -34
- package/package.json +67 -64
package/dist/index.js
CHANGED
|
@@ -27282,7 +27282,6 @@ function classifyRisk(toolNameOrDesc) {
|
|
|
27282
27282
|
const readSpecials = [
|
|
27283
27283
|
"agent_simulate_order",
|
|
27284
27284
|
"okx_preflight_check",
|
|
27285
|
-
"okx_agent_feedback",
|
|
27286
27285
|
"agent_catalog",
|
|
27287
27286
|
"agent_catalog_detail",
|
|
27288
27287
|
"agent_hub_status",
|
|
@@ -27305,12 +27304,12 @@ function classifyRisk(toolNameOrDesc) {
|
|
|
27305
27304
|
"codegraph_status",
|
|
27306
27305
|
"codegraph_query",
|
|
27307
27306
|
"agent_get_preference",
|
|
27308
|
-
"agent_set_preference",
|
|
27309
27307
|
"agent_simulate_transfer",
|
|
27310
27308
|
"agent_read_only_trade",
|
|
27311
27309
|
"okx_event_instruments"
|
|
27312
27310
|
];
|
|
27313
27311
|
if (readSpecials.includes(toolName)) return "READ";
|
|
27312
|
+
if (["okx_agent_feedback", "agent_set_preference"].includes(toolName)) return "WRITE";
|
|
27314
27313
|
if (toolName.startsWith("xlayer_call")) return "WRITE";
|
|
27315
27314
|
return "READ";
|
|
27316
27315
|
}
|
|
@@ -28517,25 +28516,6 @@ function registerAccountTools(server, auth) {
|
|
|
28517
28516
|
}
|
|
28518
28517
|
}
|
|
28519
28518
|
);
|
|
28520
|
-
registerTool(
|
|
28521
|
-
server,
|
|
28522
|
-
"okx_get_order",
|
|
28523
|
-
"READ",
|
|
28524
|
-
"CAT:[\u8D26\u6237] | \u2192 \u8BF7\u5148\u8C03\u7528 agent_catalog",
|
|
28525
|
-
{
|
|
28526
|
-
instId: external_exports.string().describe("\u4EA7\u54C1ID"),
|
|
28527
|
-
ordId: external_exports.string().describe("\u8BA2\u5355ID")
|
|
28528
|
-
},
|
|
28529
|
-
async ({ instId, ordId }) => {
|
|
28530
|
-
if (!auth) return toError(AUTH_REQUIRED);
|
|
28531
|
-
try {
|
|
28532
|
-
const data = await privateApi.getOrder(auth, instId, ordId);
|
|
28533
|
-
return toResult(data);
|
|
28534
|
-
} catch (e) {
|
|
28535
|
-
return toError(e);
|
|
28536
|
-
}
|
|
28537
|
-
}
|
|
28538
|
-
);
|
|
28539
28519
|
registerTool(
|
|
28540
28520
|
server,
|
|
28541
28521
|
"okx_get_orders_history",
|
|
@@ -34709,8 +34689,18 @@ function registerAgentUtils(server, auth) {
|
|
|
34709
34689
|
},
|
|
34710
34690
|
async ({ title, what, tools, pain, suggestion }) => {
|
|
34711
34691
|
try {
|
|
34712
|
-
const logDir =
|
|
34692
|
+
const logDir = path.join(os.homedir(), ".hvip");
|
|
34693
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
34713
34694
|
const logFile = path.join(logDir, "hvip-mcp-feedback.log");
|
|
34695
|
+
try {
|
|
34696
|
+
const stat = fs.statSync(logFile);
|
|
34697
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
34698
|
+
const rotated = logFile + ".1";
|
|
34699
|
+
if (fs.existsSync(rotated)) fs.unlinkSync(rotated);
|
|
34700
|
+
fs.renameSync(logFile, rotated);
|
|
34701
|
+
}
|
|
34702
|
+
} catch {
|
|
34703
|
+
}
|
|
34714
34704
|
const entry = JSON.stringify({
|
|
34715
34705
|
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34716
34706
|
title,
|
|
@@ -35185,11 +35175,615 @@ function registerAgentUtils(server, auth) {
|
|
|
35185
35175
|
}
|
|
35186
35176
|
}
|
|
35187
35177
|
);
|
|
35178
|
+
registerTool(
|
|
35179
|
+
server,
|
|
35180
|
+
"agent_market_sentiment",
|
|
35181
|
+
"READ",
|
|
35182
|
+
"CAT:[\u5E02\u573A\u626B\u63CF] | \u5E02\u573A\u60C5\u7EEA\u7EFC\u5408\u5206\u6790\uFF1A\u591A\u7A7A\u6BD4+PCR+\u8D44\u91D1\u8D39\u7387+\u5927\u6237\u60C5\u7EEA\u2192\u65B9\u5411\u8BC4\u5206\uFF0C\u6216\u626B\u63CF\u6781\u7AEF\u8D39\u7387\u5957\u5229\u673A\u4F1A",
|
|
35183
|
+
{
|
|
35184
|
+
mode: external_exports.enum(["sentiment", "funding"]).optional().default("sentiment").describe("sentiment=\u5E02\u573A\u60C5\u7EEA\u8BC4\u5206, funding=\u8D44\u91D1\u8D39\u7387\u5F02\u5E38\u626B\u63CF"),
|
|
35185
|
+
instType: external_exports.enum(["SWAP", "FUTURES", "SPOT"]).optional().default("SWAP").describe("\u4EA7\u54C1\u7C7B\u578B"),
|
|
35186
|
+
topN: external_exports.number().int().min(5).max(50).optional().default(10).describe("\u5206\u6790\u54C1\u79CD\u6570\u91CF"),
|
|
35187
|
+
sampleCcy: external_exports.string().optional().describe("\u57FA\u51C6\u5E01\u79CD(BTC/ETH)\uFF0C\u4EC5 sentiment \u6A21\u5F0F"),
|
|
35188
|
+
threshold: external_exports.number().optional().default(1e-3).describe("\u8D39\u7387\u5F02\u5E38\u9608\u503C\uFF0C\u4EC5 funding \u6A21\u5F0F")
|
|
35189
|
+
},
|
|
35190
|
+
async ({ mode, instType, topN, sampleCcy, threshold }) => {
|
|
35191
|
+
try {
|
|
35192
|
+
const type = instType || "SWAP";
|
|
35193
|
+
const n = topN || 10;
|
|
35194
|
+
const tickersAll = await publicApi.getTickers(type);
|
|
35195
|
+
const tickers = (tickersAll || []).slice(0, n);
|
|
35196
|
+
if (mode === "funding") {
|
|
35197
|
+
const fetchers = {};
|
|
35198
|
+
for (const t of tickers) fetchers[t.instId] = publicApi.getFundingRate(t.instId);
|
|
35199
|
+
const { get: get2, errors: errors2 } = await fetchAllSettled(fetchers);
|
|
35200
|
+
const results = tickers.map((t) => {
|
|
35201
|
+
const fr = get2(t.instId)?.[0] || {};
|
|
35202
|
+
const rate = parseFloat(fr.fundingRate || "0");
|
|
35203
|
+
const annualized = rate * 365 * 3;
|
|
35204
|
+
return {
|
|
35205
|
+
instId: t.instId,
|
|
35206
|
+
last: t.last,
|
|
35207
|
+
fundingRate: fr.fundingRate || "N/A",
|
|
35208
|
+
annualizedRate: `${(annualized * 100).toFixed(2)}%`,
|
|
35209
|
+
nextFundingTime: fr.nextFundingTime || "N/A",
|
|
35210
|
+
isExtreme: Math.abs(rate) > (threshold || 1e-3),
|
|
35211
|
+
direction: rate > 0 ? "\u7A7A\u5934\u4ED8\u591A\u5934" : "\u591A\u5934\u4ED8\u7A7A\u5934",
|
|
35212
|
+
arbitrageHint: rate > (threshold || 1e-3) ? `\u8D44\u91D1\u8D39\u7387\u6781\u9AD8(${(annualized * 100).toFixed(0)}%\u5E74\u5316)\uFF0C\u73B0\u8D27\u4E70\u5165+\u5408\u7EA6\u505A\u7A7A\u53EF\u5957\u5229` : rate < -(threshold || 1e-3) ? `\u8D44\u91D1\u8D39\u7387\u6781\u8D1F\uFF0C\u73B0\u8D27\u5356\u51FA+\u5408\u7EA6\u505A\u591A\u53EF\u5957\u5229` : null
|
|
35213
|
+
};
|
|
35214
|
+
}).sort((a, b) => Math.abs(parseFloat(b.fundingRate === "N/A" ? "0" : b.fundingRate)) - Math.abs(parseFloat(a.fundingRate === "N/A" ? "0" : a.fundingRate)));
|
|
35215
|
+
const opportunities = results.filter((r) => r.isExtreme);
|
|
35216
|
+
return toResult({
|
|
35217
|
+
tsIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35218
|
+
mode,
|
|
35219
|
+
scanned: results.length,
|
|
35220
|
+
extremeCount: opportunities.length,
|
|
35221
|
+
threshold: `${((threshold || 1e-3) * 365 * 3 * 100).toFixed(0)}% \u5E74\u5316`,
|
|
35222
|
+
topOpportunities: opportunities.slice(0, 10),
|
|
35223
|
+
allResults: results,
|
|
35224
|
+
errors: errors2,
|
|
35225
|
+
_summary: opportunities.length > 0 ? `\u53D1\u73B0 ${opportunities.length} \u4E2A\u5F02\u5E38\u8D39\u7387\uFF1A${opportunities.slice(0, 3).map((o) => `${o.instId}(${o.annualizedRate})`).join(", ")}\u3002` : `\u5DF2\u626B\u63CF ${results.length} \u4E2A\u54C1\u79CD\uFF0C\u65E0\u6781\u7AEF\u8D39\u7387\u3002`
|
|
35226
|
+
});
|
|
35227
|
+
}
|
|
35228
|
+
const ccy = sampleCcy || "BTC";
|
|
35229
|
+
const firstInstId = tickers[0]?.instId || "BTC-USDT-SWAP";
|
|
35230
|
+
const { get, errors } = await fetchAllSettled({
|
|
35231
|
+
lsRatio: publicApi.getLongShortRatio(ccy),
|
|
35232
|
+
pcr: publicApi.getOptionPutCallRatio(ccy),
|
|
35233
|
+
takerVol: publicApi.getTakerVolume(ccy, type),
|
|
35234
|
+
oi: publicApi.getOpenInterest(type),
|
|
35235
|
+
topLS: publicApi.getTopTraderLongShortRatio(firstInstId)
|
|
35236
|
+
});
|
|
35237
|
+
const lsData = get("lsRatio")?.[0] || {};
|
|
35238
|
+
const pcrData = get("pcr")?.[0] || {};
|
|
35239
|
+
const takerData = get("takerVol")?.[0] || {};
|
|
35240
|
+
const oiData = get("oi");
|
|
35241
|
+
const topLSData = get("topLS")?.[0] || {};
|
|
35242
|
+
const frFetchers = {};
|
|
35243
|
+
for (const t of tickers) frFetchers[t.instId] = publicApi.getFundingRate(t.instId);
|
|
35244
|
+
const frResults = await fetchAllSettled(frFetchers);
|
|
35245
|
+
const perCoin = tickers.map((t) => {
|
|
35246
|
+
const frData = frResults.get(t.instId)?.[0] || {};
|
|
35247
|
+
const rate = parseFloat(frData.fundingRate || "0");
|
|
35248
|
+
const changePct = t.open24h && t.last ? (parseFloat(t.last) - parseFloat(t.open24h)) / parseFloat(t.open24h) * 100 : 0;
|
|
35249
|
+
let score = 0;
|
|
35250
|
+
const reasons = [];
|
|
35251
|
+
if (rate > 5e-4) {
|
|
35252
|
+
score -= 1;
|
|
35253
|
+
reasons.push(`\u8D44\u91D1\u8D39\u7387\u6781\u9AD8\u2192\u504F\u7A7A`);
|
|
35254
|
+
} else if (rate > 1e-4) {
|
|
35255
|
+
score -= 0.5;
|
|
35256
|
+
reasons.push(`\u8D39\u7387\u504F\u9AD8`);
|
|
35257
|
+
} else if (rate < -5e-4) {
|
|
35258
|
+
score += 1;
|
|
35259
|
+
reasons.push(`\u8D39\u7387\u6781\u8D1F\u2192\u504F\u591A`);
|
|
35260
|
+
}
|
|
35261
|
+
if (changePct > 3) {
|
|
35262
|
+
score += 1;
|
|
35263
|
+
reasons.push(`24h\u6DA8${changePct.toFixed(1)}%`);
|
|
35264
|
+
} else if (changePct < -3) {
|
|
35265
|
+
score -= 1;
|
|
35266
|
+
reasons.push(`24h\u8DCC${Math.abs(changePct).toFixed(1)}%`);
|
|
35267
|
+
}
|
|
35268
|
+
return {
|
|
35269
|
+
instId: t.instId,
|
|
35270
|
+
last: t.last,
|
|
35271
|
+
change24h: `${changePct.toFixed(2)}%`,
|
|
35272
|
+
fundingRate: frData.fundingRate || "N/A",
|
|
35273
|
+
fundingRateAnnual: rate ? `${(rate * 365 * 3 * 100).toFixed(2)}%` : "N/A",
|
|
35274
|
+
sentimentScore: score,
|
|
35275
|
+
sentiment: score > 1 ? "\u{1F7E2} \u504F\u591A" : score < -1 ? "\u{1F534} \u504F\u7A7A" : "\u{1F7E1} \u4E2D\u6027",
|
|
35276
|
+
reasons
|
|
35277
|
+
};
|
|
35278
|
+
});
|
|
35279
|
+
let globalScore = 0;
|
|
35280
|
+
const lsValue = parseFloat(lsData.longShortRatio || "1");
|
|
35281
|
+
if (lsValue > 1.2) globalScore += 1;
|
|
35282
|
+
else if (lsValue < 0.8) globalScore -= 1;
|
|
35283
|
+
const pcrValue = parseFloat(pcrData.oiRatio || "1");
|
|
35284
|
+
if (pcrValue > 0.8) globalScore -= 1;
|
|
35285
|
+
else if (pcrValue < 0.5) globalScore += 1;
|
|
35286
|
+
return toResult({
|
|
35287
|
+
tsIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35288
|
+
mode,
|
|
35289
|
+
globalSentiment: globalScore > 0 ? "\u{1F7E2} \u504F\u5411\u591A\u5934" : globalScore < 0 ? "\u{1F534} \u504F\u5411\u7A7A\u5934" : "\u{1F7E1} \u4E2D\u6027",
|
|
35290
|
+
globalMetrics: {
|
|
35291
|
+
longShortRatio: lsData.longShortRatio || "N/A",
|
|
35292
|
+
putCallOiRatio: pcrData.oiRatio || "N/A",
|
|
35293
|
+
takerBuyVol: takerData.buyVol || "N/A",
|
|
35294
|
+
takerSellVol: takerData.sellVol || "N/A",
|
|
35295
|
+
contractTopLS: topLSData.longShortRatio || "N/A",
|
|
35296
|
+
openInterest: oiData.length > 0 ? oiData[0]?.oi : "N/A"
|
|
35297
|
+
},
|
|
35298
|
+
perCoin: perCoin.slice(0, n),
|
|
35299
|
+
errors,
|
|
35300
|
+
_summary: `${mode === "sentiment" ? "\u5E02\u573A\u60C5\u7EEA" : "\u8D39\u7387\u626B\u63CF"}\uFF1A${perCoin.filter((c) => c.sentimentScore > 1).length} \u4E2A\u504F\u591A\uFF0C${perCoin.filter((c) => c.sentimentScore < -1).length} \u4E2A\u504F\u7A7A\u3002`
|
|
35301
|
+
});
|
|
35302
|
+
} catch (e) {
|
|
35303
|
+
return toError(e);
|
|
35304
|
+
}
|
|
35305
|
+
}
|
|
35306
|
+
);
|
|
35307
|
+
registerTool(
|
|
35308
|
+
server,
|
|
35309
|
+
"agent_asset_center",
|
|
35310
|
+
"READ",
|
|
35311
|
+
"CAT:[\u8D26\u6237\u8D44\u4EA7] | \u8D44\u4EA7\u6307\u6325\u4E2D\u5FC3\uFF1A\u8D44\u91D1\u5206\u5E03\u3001\u7406\u8D22\u6536\u76CA\u3001\u5B50\u8D26\u6237\u4E00\u89C8\uFF0C\u4E09\u5408\u4E00",
|
|
35312
|
+
{
|
|
35313
|
+
mode: external_exports.enum(["all", "funds", "earn", "subaccounts"]).optional().default("all").describe("all=\u5168\u666F, funds=\u8D44\u91D1\u5206\u5E03\u4E0E\u5212\u8F6C, earn=\u7406\u8D22\u6536\u76CA, subaccounts=\u5B50\u8D26\u6237"),
|
|
35314
|
+
ccy: external_exports.string().optional().describe("\u5E01\u79CD\u8FC7\u6EE4\uFF0C\u4EC5 funds \u6A21\u5F0F"),
|
|
35315
|
+
subMode: external_exports.enum(["list", "detail"]).optional().default("list").describe("\u5B50\u8D26\u6237\u6A21\u5F0F\uFF1Alist=\u5217\u8868, detail=\u542B\u4F59\u989D")
|
|
35316
|
+
},
|
|
35317
|
+
async ({ mode, ccy, subMode }) => {
|
|
35318
|
+
if (!auth) return toError(AUTH_REQUIRED);
|
|
35319
|
+
try {
|
|
35320
|
+
const fetchers = {};
|
|
35321
|
+
if (mode === "all" || mode === "funds") {
|
|
35322
|
+
fetchers.funding = privateApi.getBalance_funding(auth, ccy);
|
|
35323
|
+
fetchers.trading = privateApi.getBalance(auth, ccy);
|
|
35324
|
+
}
|
|
35325
|
+
if (mode === "all" || mode === "earn") {
|
|
35326
|
+
fetchers.savings = privateApi.getSavingsBalance(auth);
|
|
35327
|
+
fetchers.ethStaking = privateApi.getEthStakingBalance(auth);
|
|
35328
|
+
fetchers.solStaking = privateApi.getSolStakingBalance(auth);
|
|
35329
|
+
fetchers.stableRewards = privateApi.getStableRewardsProductInfo(auth);
|
|
35330
|
+
fetchers.stakingOrders = privateApi.getStakingOrders(auth);
|
|
35331
|
+
}
|
|
35332
|
+
if (mode === "all" || mode === "subaccounts") {
|
|
35333
|
+
fetchers.subList = privateApi.listSubAccounts(auth);
|
|
35334
|
+
}
|
|
35335
|
+
const { get, errors } = await fetchAllSettled(fetchers);
|
|
35336
|
+
const result = { tsIso: (/* @__PURE__ */ new Date()).toISOString(), mode, errors };
|
|
35337
|
+
if (mode === "all" || mode === "funds") {
|
|
35338
|
+
const fundData = get("funding")?.[0] || {};
|
|
35339
|
+
const tradeData = get("trading")?.[0] || {};
|
|
35340
|
+
const fundDetails = fundData.details || fundData.detail || [];
|
|
35341
|
+
const tradeDetails = tradeData.details || tradeData.detail || [];
|
|
35342
|
+
const map = /* @__PURE__ */ new Map();
|
|
35343
|
+
for (const d of fundDetails) map.set(d.ccy, { ccy: d.ccy, fundingAvail: d.availBal || d.cashBal || "0", tradingAvail: "0" });
|
|
35344
|
+
for (const d of tradeDetails) {
|
|
35345
|
+
const e = map.get(d.ccy);
|
|
35346
|
+
if (e) e.tradingAvail = d.availBal || d.cashBal || "0";
|
|
35347
|
+
else map.set(d.ccy, { ccy: d.ccy, fundingAvail: "0", tradingAvail: d.availBal || d.cashBal || "0" });
|
|
35348
|
+
}
|
|
35349
|
+
result.funds = {
|
|
35350
|
+
fundingTotalEq: fundData.totalEq || "N/A",
|
|
35351
|
+
tradingTotalEq: tradeData.totalEq || "N/A",
|
|
35352
|
+
currencies: [...map.values()].filter((d) => parseFloat(d.fundingAvail) > 0 || parseFloat(d.tradingAvail) > 0)
|
|
35353
|
+
};
|
|
35354
|
+
}
|
|
35355
|
+
if (mode === "all" || mode === "earn") {
|
|
35356
|
+
const savings = get("savings");
|
|
35357
|
+
const eth = get("ethStaking");
|
|
35358
|
+
const sol = get("solStaking");
|
|
35359
|
+
const earnProducts = [];
|
|
35360
|
+
for (const arr of [savings, eth, sol]) {
|
|
35361
|
+
if (Array.isArray(arr)) for (const s of arr) earnProducts.push({
|
|
35362
|
+
ccy: s.ccy || "?",
|
|
35363
|
+
product: s.productId || s.investType || "earn",
|
|
35364
|
+
balance: s.amt || s.investAmt || "0",
|
|
35365
|
+
apy: s.rate || s.apy || "N/A"
|
|
35366
|
+
});
|
|
35367
|
+
}
|
|
35368
|
+
result.earn = {
|
|
35369
|
+
savingsCount: Array.isArray(savings) ? savings.length : 0,
|
|
35370
|
+
ethStakingCount: Array.isArray(eth) ? eth.length : 0,
|
|
35371
|
+
solStakingCount: Array.isArray(sol) ? sol.length : 0,
|
|
35372
|
+
stakingOrderCount: Array.isArray(get("stakingOrders")) ? get("stakingOrders").length : 0,
|
|
35373
|
+
products: earnProducts
|
|
35374
|
+
};
|
|
35375
|
+
}
|
|
35376
|
+
if (mode === "all" || mode === "subaccounts") {
|
|
35377
|
+
const subList = get("subList") || [];
|
|
35378
|
+
if (subMode === "detail" && subList.length > 0) {
|
|
35379
|
+
const balFetchers = {};
|
|
35380
|
+
for (const s of subList) balFetchers[s.subAcct] = privateApi.getSubAccountFundingBalance(auth, s.subAcct);
|
|
35381
|
+
const balRes = await fetchAllSettled(balFetchers);
|
|
35382
|
+
result.subaccounts = subList.map((s) => {
|
|
35383
|
+
const bal = balRes.get(s.subAcct)?.[0] || {};
|
|
35384
|
+
return { subAcct: s.subAcct, label: s.label, totalEq: bal.totalEq || "N/A" };
|
|
35385
|
+
});
|
|
35386
|
+
} else {
|
|
35387
|
+
result.subaccounts = subList.map((s) => ({ subAcct: s.subAcct, label: s.label, enable: s.enable }));
|
|
35388
|
+
}
|
|
35389
|
+
result.subaccountCount = subList.length;
|
|
35390
|
+
}
|
|
35391
|
+
const parts = [];
|
|
35392
|
+
if (result.funds) parts.push(`\u8D44\u91D1\u8D26\u6237${result.funds.currencies?.length || 0}\u4E2A\u5E01\u79CD`);
|
|
35393
|
+
if (result.earn) parts.push(`\u7406\u8D22${result.earn.products?.length || 0}\u4E2A\u6301\u4ED3`);
|
|
35394
|
+
if (result.subaccounts) parts.push(`${result.subaccountCount}\u4E2A\u5B50\u8D26\u6237`);
|
|
35395
|
+
result._summary = "\u8D44\u4EA7\u5168\u666F\uFF1A" + parts.join("\uFF0C") + "\u3002";
|
|
35396
|
+
return toResult(result);
|
|
35397
|
+
} catch (e) {
|
|
35398
|
+
return toError(e);
|
|
35399
|
+
}
|
|
35400
|
+
}
|
|
35401
|
+
);
|
|
35402
|
+
registerTool(
|
|
35403
|
+
server,
|
|
35404
|
+
"agent_strategy_center",
|
|
35405
|
+
"READ",
|
|
35406
|
+
"CAT:[\u7B56\u7565\u4EA4\u6613] | \u7B56\u7565\u4EA4\u6613\u4E2D\u5FC3\uFF1A\u6D3B\u8DC3\u7B56\u7565\u4EEA\u8868\u76D8 or \u7F51\u683C\u53C2\u6570\u667A\u80FD\u63A8\u8350",
|
|
35407
|
+
{
|
|
35408
|
+
mode: external_exports.enum(["dashboard", "grid_advice"]).default("dashboard").describe("dashboard=\u6240\u6709\u6D3B\u8DC3\u7B56\u7565\u4E00\u89C8, grid_advice=\u6CE2\u52A8\u7387\u5206\u6790+AI\u7F51\u683C\u63A8\u8350"),
|
|
35409
|
+
instType: external_exports.enum(["SPOT", "SWAP", "FUTURES"]).optional().describe("\u4EA7\u54C1\u7C7B\u578B\u8FC7\u6EE4"),
|
|
35410
|
+
instId: external_exports.string().optional().describe("\u4EA4\u6613\u54C1\u79CD(grid_advice\u6A21\u5F0F\u5FC5\u586B)\uFF0C\u5982 BTC-USDT"),
|
|
35411
|
+
direction: external_exports.enum(["long", "short", "neutral"]).optional().default("neutral").describe("\u7F51\u683C\u65B9\u5411(grid_advice)")
|
|
35412
|
+
},
|
|
35413
|
+
async ({ mode, instType, instId, direction }) => {
|
|
35414
|
+
if (!auth) return toError(AUTH_REQUIRED);
|
|
35415
|
+
try {
|
|
35416
|
+
if (mode === "grid_advice") {
|
|
35417
|
+
if (!instId) return toError(new Error("grid_advice \u6A21\u5F0F\u9700\u8981 instId \u53C2\u6570"));
|
|
35418
|
+
const { get: get2, errors: errors2 } = await fetchAllSettled({
|
|
35419
|
+
aiParam: publicApi.getGridAiParam(instId, "grid"),
|
|
35420
|
+
candles: publicApi.getCandles(instId, "1D", 30),
|
|
35421
|
+
ticker: publicApi.getTicker(instId)
|
|
35422
|
+
});
|
|
35423
|
+
const aiParam = get2("aiParam")?.[0] || {};
|
|
35424
|
+
const candles = get2("candles") || [];
|
|
35425
|
+
const tickerData = get2("ticker")?.[0] || {};
|
|
35426
|
+
const closes = candles.map((c) => parseFloat(c[4] || "0")).filter((n) => n > 0);
|
|
35427
|
+
let volatility = 0;
|
|
35428
|
+
if (closes.length >= 2) {
|
|
35429
|
+
const returns = closes.slice(1).map((c, i) => (c - closes[i]) / closes[i]);
|
|
35430
|
+
const mean = returns.reduce((a, b) => a + b, 0) / returns.length;
|
|
35431
|
+
volatility = Math.sqrt(returns.reduce((a, r) => a + (r - mean) ** 2, 0) / returns.length) * Math.sqrt(365);
|
|
35432
|
+
}
|
|
35433
|
+
const lastPrice = parseFloat(tickerData.last || "0");
|
|
35434
|
+
const high30 = closes.length > 0 ? Math.max(...closes) : lastPrice * 1.1;
|
|
35435
|
+
const low30 = closes.length > 0 ? Math.min(...closes) : lastPrice * 0.9;
|
|
35436
|
+
const gUpper = aiParam.upperPx || (high30 * 1.05).toFixed(2);
|
|
35437
|
+
const gLower = aiParam.lowerPx || (low30 * 0.95).toFixed(2);
|
|
35438
|
+
const gCount = aiParam.gridNum || Math.min(20, Math.floor((parseFloat(String(gUpper)) - parseFloat(String(gLower))) / (lastPrice * 0.01)) || 10);
|
|
35439
|
+
return toResult({
|
|
35440
|
+
tsIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35441
|
+
mode,
|
|
35442
|
+
instId,
|
|
35443
|
+
currentPrice: lastPrice,
|
|
35444
|
+
volatility: `${(volatility * 100).toFixed(1)}%`,
|
|
35445
|
+
range30d: { high: high30, low: low30 },
|
|
35446
|
+
recommendation: { direction: direction || "neutral", upperPrice: gUpper, lowerPrice: gLower, gridCount: gCount },
|
|
35447
|
+
errors: errors2,
|
|
35448
|
+
_summary: `${instId} 30\u65E5\u5E74\u5316\u6CE2\u52A8\u7387 ${(volatility * 100).toFixed(1)}%\u3002AI\u63A8\u8350\u7F51\u683C [${gLower}, ${gUpper}]\uFF0C${gCount} \u6863\u3002`,
|
|
35449
|
+
tip: "\u786E\u8BA4\u540E\u7528 okx_create_grid_order \u521B\u5EFA\u7F51\u683C\u3002"
|
|
35450
|
+
});
|
|
35451
|
+
}
|
|
35452
|
+
const { get, errors } = await fetchAllSettled({
|
|
35453
|
+
algoOrders: privateApi.getOrdersAlgoPending(auth, void 0, instType || void 0),
|
|
35454
|
+
gridOrders: privateApi.getGridOrdersPending(auth),
|
|
35455
|
+
recurringOrders: privateApi.getRecurringOrdersPending(auth),
|
|
35456
|
+
signalBots: privateApi.getSignalBotsPending(auth, instType || void 0),
|
|
35457
|
+
spreadOrders: privateApi.getSpreadOrdersPending(auth)
|
|
35458
|
+
});
|
|
35459
|
+
const counts = {
|
|
35460
|
+
algo: Array.isArray(get("algoOrders")) ? get("algoOrders").length : 0,
|
|
35461
|
+
grid: Array.isArray(get("gridOrders")) ? get("gridOrders").length : 0,
|
|
35462
|
+
recurring: Array.isArray(get("recurringOrders")) ? get("recurringOrders").length : 0,
|
|
35463
|
+
signal: Array.isArray(get("signalBots")) ? get("signalBots").length : 0,
|
|
35464
|
+
spread: Array.isArray(get("spreadOrders")) ? get("spreadOrders").length : 0
|
|
35465
|
+
};
|
|
35466
|
+
const totalActive = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
35467
|
+
return toResult({
|
|
35468
|
+
tsIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35469
|
+
mode,
|
|
35470
|
+
totalActive,
|
|
35471
|
+
breakdown: counts,
|
|
35472
|
+
details: {
|
|
35473
|
+
algoOrders: Array.isArray(get("algoOrders")) ? get("algoOrders").slice(0, 15).map((o) => ({ algoId: o.algoId, instId: o.instId, ordType: o.ordType, side: o.side, state: o.state })) : [],
|
|
35474
|
+
gridOrders: Array.isArray(get("gridOrders")) ? get("gridOrders").slice(0, 10).map((o) => ({ algoId: o.algoId, instId: o.instId, state: o.state })) : [],
|
|
35475
|
+
recurringOrders: Array.isArray(get("recurringOrders")) ? get("recurringOrders").slice(0, 10).map((o) => ({ algoId: o.algoId, instId: o.instId, period: o.period, state: o.state })) : [],
|
|
35476
|
+
signalBots: Array.isArray(get("signalBots")) ? get("signalBots").slice(0, 10).map((o) => ({ signalId: o.signalId || o.algoId, instId: o.instId, state: o.state })) : [],
|
|
35477
|
+
spreadOrders: Array.isArray(get("spreadOrders")) ? get("spreadOrders").slice(0, 10).map((o) => ({ algoId: o.algoId, sprdId: o.sprdId, state: o.state })) : []
|
|
35478
|
+
},
|
|
35479
|
+
errors,
|
|
35480
|
+
_summary: totalActive > 0 ? `\u6D3B\u8DC3\u7B56\u7565 ${totalActive} \u4E2A\uFF1A\u6761\u4EF6\u59D4\u6258 ${counts.algo} / \u7F51\u683C ${counts.grid} / \u5B9A\u6295 ${counts.recurring} / \u4FE1\u53F7 ${counts.signal} / \u4EF7\u5DEE ${counts.spread}` : "\u5F53\u524D\u65E0\u6D3B\u8DC3\u7B56\u7565\u3002",
|
|
35481
|
+
tip: "\u9700\u8981\u7F51\u683C\u5EFA\u8BAE\uFF1F\u7528 mode=grid_advice\u3002"
|
|
35482
|
+
});
|
|
35483
|
+
} catch (e) {
|
|
35484
|
+
return toError(e);
|
|
35485
|
+
}
|
|
35486
|
+
}
|
|
35487
|
+
);
|
|
35488
|
+
registerTool(
|
|
35489
|
+
server,
|
|
35490
|
+
"agent_stop_loss_master",
|
|
35491
|
+
"WRITE",
|
|
35492
|
+
"CAT:[\u98CE\u9669\u98CE\u63A7] | \u5168\u5C40\u98CE\u63A7\uFF1A\u6279\u91CF\u8BBE\u6B62\u635F\u6761\u4EF6\u5355 / \u4E00\u952E\u5168\u90E8\u64A4\u5355 / \u67E5\u770B\u98CE\u63A7\u72B6\u6001",
|
|
35493
|
+
{
|
|
35494
|
+
instId: external_exports.string().optional().describe("\u6307\u5B9A\u54C1\u79CD\uFF0C\u4E0D\u586B\u5219\u5BF9\u6240\u6709\u6301\u4ED3\u64CD\u4F5C"),
|
|
35495
|
+
stopLossPct: external_exports.string().optional().describe("\u6B62\u635F\u767E\u5206\u6BD4\uFF0C\u5982 '5' \u8868\u793A\u4E8F\u635F 5% \u6B62\u635F"),
|
|
35496
|
+
mode: external_exports.enum(["preview", "set_stop_loss", "cancel_all"]).describe("preview=\u67E5\u770B\u98CE\u63A7\u72B6\u6001, set_stop_loss=\u8BBE\u6B62\u635F\u5355, cancel_all=\u5168\u90E8\u64A4\u5355")
|
|
35497
|
+
},
|
|
35498
|
+
async ({ instId, stopLossPct, mode }) => {
|
|
35499
|
+
if (!auth) return toError(AUTH_REQUIRED);
|
|
35500
|
+
try {
|
|
35501
|
+
const positions = await privateApi.getPositions(auth);
|
|
35502
|
+
const active = (positions || []).filter(
|
|
35503
|
+
(p) => parseFloat(p.avgPx || "0") > 0 && parseFloat(p.pos || "0") !== "0"
|
|
35504
|
+
);
|
|
35505
|
+
const targets = instId ? active.filter((p) => p.instId === instId) : active;
|
|
35506
|
+
if (targets.length === 0) {
|
|
35507
|
+
return toResult({ tsIso: (/* @__PURE__ */ new Date()).toISOString(), mode, message: instId ? `${instId} \u65E0\u6D3B\u8DC3\u6301\u4ED3` : "\u65E0\u4EFB\u4F55\u6D3B\u8DC3\u6301\u4ED3" });
|
|
35508
|
+
}
|
|
35509
|
+
if (mode === "preview") {
|
|
35510
|
+
const pct = parseFloat(stopLossPct || "5");
|
|
35511
|
+
return toResult({
|
|
35512
|
+
tsIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35513
|
+
mode,
|
|
35514
|
+
positions: targets.map((p) => ({
|
|
35515
|
+
instId: p.instId,
|
|
35516
|
+
posSide: p.posSide,
|
|
35517
|
+
pos: p.pos,
|
|
35518
|
+
avgPx: p.avgPx,
|
|
35519
|
+
markPx: p.markPx,
|
|
35520
|
+
upl: p.upl,
|
|
35521
|
+
uplRatio: p.uplRatio,
|
|
35522
|
+
liqPx: p.liqPx,
|
|
35523
|
+
lever: p.lever,
|
|
35524
|
+
suggestedStopPx: p.avgPx ? p.posSide === "long" ? (parseFloat(p.avgPx) * (1 - pct / 100)).toFixed(2) : (parseFloat(p.avgPx) * (1 + pct / 100)).toFixed(2) : "N/A"
|
|
35525
|
+
})),
|
|
35526
|
+
_summary: `\u98CE\u63A7\u9884\u89C8\uFF1A${targets.length} \u4E2A\u6301\u4ED3\uFF0C\u5EFA\u8BAE\u6B62\u635F\u5E45\u5EA6 ${pct}%\u3002`
|
|
35527
|
+
});
|
|
35528
|
+
}
|
|
35529
|
+
if (mode === "set_stop_loss") {
|
|
35530
|
+
if (!stopLossPct) return toError(new Error("set_stop_loss \u6A21\u5F0F\u9700\u8981 stopLossPct \u53C2\u6570"));
|
|
35531
|
+
const pct = parseFloat(stopLossPct);
|
|
35532
|
+
if (isNaN(pct) || pct <= 0 || pct >= 50) return toError(new Error("stopLossPct \u5FC5\u987B\u5728 1-50 \u4E4B\u95F4"));
|
|
35533
|
+
const actions = [];
|
|
35534
|
+
for (const p of targets) {
|
|
35535
|
+
const avgPx = parseFloat(p.avgPx);
|
|
35536
|
+
const isLong = p.posSide === "long";
|
|
35537
|
+
const stopPx = isLong ? (avgPx * (1 - pct / 100)).toFixed(2) : (avgPx * (1 + pct / 100)).toFixed(2);
|
|
35538
|
+
try {
|
|
35539
|
+
await privateApi.placeAlgoOrder(auth, {
|
|
35540
|
+
instId: p.instId,
|
|
35541
|
+
tdMode: "cross",
|
|
35542
|
+
side: isLong ? "sell" : "buy",
|
|
35543
|
+
ordType: "conditional",
|
|
35544
|
+
sz: Math.abs(parseFloat(p.pos)).toString(),
|
|
35545
|
+
tpTriggerPx: stopPx,
|
|
35546
|
+
tpOrdPx: "-1"
|
|
35547
|
+
});
|
|
35548
|
+
actions.push({ instId: p.instId, posSide: p.posSide, stopPx, status: "placed" });
|
|
35549
|
+
} catch (e) {
|
|
35550
|
+
actions.push({ instId: p.instId, posSide: p.posSide, stopPx, status: "failed", error: e.message });
|
|
35551
|
+
}
|
|
35552
|
+
}
|
|
35553
|
+
return toResult({
|
|
35554
|
+
tsIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35555
|
+
mode,
|
|
35556
|
+
actions,
|
|
35557
|
+
_summary: `\u5DF2\u4E3A ${actions.filter((a) => a.status === "placed").length}/${actions.length} \u4E2A\u6301\u4ED3\u8BBE\u6B62\u635F\uFF08${stopLossPct}%\uFF09\u3002`
|
|
35558
|
+
});
|
|
35559
|
+
}
|
|
35560
|
+
await privateApi.massCancel(auth, instId ? "SWAP" : "SWAP");
|
|
35561
|
+
return toResult({ tsIso: (/* @__PURE__ */ new Date()).toISOString(), mode, cancelled: true, _summary: "\u5DF2\u5168\u90E8\u64A4\u5355\u3002" });
|
|
35562
|
+
} catch (e) {
|
|
35563
|
+
return toError(e);
|
|
35564
|
+
}
|
|
35565
|
+
}
|
|
35566
|
+
);
|
|
35567
|
+
registerTool(
|
|
35568
|
+
server,
|
|
35569
|
+
"agent_copy_trader_search",
|
|
35570
|
+
"READ",
|
|
35571
|
+
"CAT:[\u8DDF\u5355] | \u6309\u6536\u76CA\u7387/\u80DC\u7387/\u56DE\u64A4\u7B5B\u9009\u6700\u4F18\u5E26\u5355\u5458\uFF0C\u9644\u8DDF\u5355\u6307\u5F15",
|
|
35572
|
+
{
|
|
35573
|
+
instType: external_exports.enum(["SPOT", "SWAP"]).optional().default("SWAP"),
|
|
35574
|
+
sortBy: external_exports.enum(["pnl", "winRate", "copyCount"]).optional().default("pnl"),
|
|
35575
|
+
topN: external_exports.number().int().min(1).max(20).optional().default(10)
|
|
35576
|
+
},
|
|
35577
|
+
async ({ instType, sortBy, topN }) => {
|
|
35578
|
+
try {
|
|
35579
|
+
const type = instType || "SWAP";
|
|
35580
|
+
const n = topN || 10;
|
|
35581
|
+
const traders = await publicApi.getPublicLeadTraders(type);
|
|
35582
|
+
if (!Array.isArray(traders) || traders.length === 0) {
|
|
35583
|
+
return toResult({ tsIso: (/* @__PURE__ */ new Date()).toISOString(), traders: [], _summary: "\u5F53\u524D\u65E0\u53EF\u7528\u5E26\u5355\u5458\u3002" });
|
|
35584
|
+
}
|
|
35585
|
+
const batch = traders.slice(0, n);
|
|
35586
|
+
const fetchers = {};
|
|
35587
|
+
for (const t of batch) {
|
|
35588
|
+
const code = t.uniqueCode || t.copyTraderId;
|
|
35589
|
+
if (code) fetchers[code] = publicApi.getLeadTraderStats(code, type, "30");
|
|
35590
|
+
}
|
|
35591
|
+
const statsRes = await fetchAllSettled(fetchers);
|
|
35592
|
+
const ranked = batch.map((t) => {
|
|
35593
|
+
const code = t.uniqueCode || t.copyTraderId;
|
|
35594
|
+
const stats = statsRes.get(code)?.[0] || {};
|
|
35595
|
+
return {
|
|
35596
|
+
name: t.nickName || t.name || code,
|
|
35597
|
+
uniqueCode: code,
|
|
35598
|
+
pnl: stats.pnl || "N/A",
|
|
35599
|
+
winRate: stats.winRate || "N/A",
|
|
35600
|
+
sharpeRatio: stats.sharpeRatio || "N/A",
|
|
35601
|
+
copyCount: stats.copyCount || "0",
|
|
35602
|
+
maxDrawdown: stats.maxDrawdown || t.maxDrawdown || "N/A"
|
|
35603
|
+
};
|
|
35604
|
+
});
|
|
35605
|
+
if (sortBy === "pnl") ranked.sort((a, b) => parseFloat(b.pnl === "N/A" ? "0" : b.pnl) - parseFloat(a.pnl === "N/A" ? "0" : a.pnl));
|
|
35606
|
+
else if (sortBy === "winRate") ranked.sort((a, b) => parseFloat(b.winRate === "N/A" ? "0" : b.winRate) - parseFloat(a.winRate === "N/A" ? "0" : a.winRate));
|
|
35607
|
+
else ranked.sort((a, b) => parseInt(b.copyCount) - parseInt(a.copyCount));
|
|
35608
|
+
return toResult({
|
|
35609
|
+
tsIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35610
|
+
instType: type,
|
|
35611
|
+
sortBy,
|
|
35612
|
+
topTraders: ranked,
|
|
35613
|
+
_summary: `Top ${ranked.length} \u5E26\u5355\u5458 (\u6309${sortBy === "pnl" ? "\u6536\u76CA" : sortBy === "winRate" ? "\u80DC\u7387" : "\u8DDF\u5355\u4EBA\u6570"}\u6392\u5E8F)\u3002`,
|
|
35614
|
+
tip: "\u786E\u8BA4\u5E26\u5355\u5458\u540E\uFF0C\u4F7F\u7528 okx_first_copy_settings { uniqueCode } \u2192 okx_copy_trader \u5F00\u59CB\u8DDF\u5355\u3002"
|
|
35615
|
+
});
|
|
35616
|
+
} catch (e) {
|
|
35617
|
+
return toError(e);
|
|
35618
|
+
}
|
|
35619
|
+
}
|
|
35620
|
+
);
|
|
35621
|
+
registerTool(
|
|
35622
|
+
server,
|
|
35623
|
+
"agent_option_scanner",
|
|
35624
|
+
"READ",
|
|
35625
|
+
"CAT:[\u5E02\u573A\u626B\u63CF] | \u671F\u6743\u5168\u666F\u626B\u63CF\uFF1A\u6982\u8981+OI\u5230\u671F/\u884C\u6743\u4EF7\u5206\u5E03+PCR+\u5927\u5B97\u4EA4\u6613\u91CF",
|
|
35626
|
+
{ uly: external_exports.string().describe("\u6807\u7684\uFF0C\u5982 BTC-USD\u3001ETH-USD\u3002\u5FC5\u586B") },
|
|
35627
|
+
async ({ uly }) => {
|
|
35628
|
+
try {
|
|
35629
|
+
const { get, errors } = await fetchAllSettled({
|
|
35630
|
+
summary: publicApi.getOptSummary(uly),
|
|
35631
|
+
oiExpiry: publicApi.getOptionOiExpiry(uly),
|
|
35632
|
+
pcr: publicApi.getOptionPutCallRatio(uly),
|
|
35633
|
+
blockVol: publicApi.getOptionTakerBlockVolume(uly)
|
|
35634
|
+
});
|
|
35635
|
+
const summary = get("summary") || [];
|
|
35636
|
+
const oiExpiry = get("oiExpiry") || [];
|
|
35637
|
+
const pcrData = get("pcr")?.[0] || {};
|
|
35638
|
+
const blockData = get("blockVol")?.[0] || {};
|
|
35639
|
+
return toResult({
|
|
35640
|
+
tsIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35641
|
+
underlying: uly,
|
|
35642
|
+
summary: summary.slice(0, 20).map((s) => ({
|
|
35643
|
+
instId: s.instId,
|
|
35644
|
+
expTime: s.expTime,
|
|
35645
|
+
strike: s.strike,
|
|
35646
|
+
optionType: s.optType,
|
|
35647
|
+
markPx: s.markPx,
|
|
35648
|
+
delta: s.delta,
|
|
35649
|
+
oi: s.oi,
|
|
35650
|
+
vol24h: s.vol24h
|
|
35651
|
+
})),
|
|
35652
|
+
oiByExpiry: oiExpiry.map((e) => ({ expTime: e.expTime, callOi: e.callOi, putOi: e.putOi })),
|
|
35653
|
+
putCallRatio: { oiRatio: pcrData.oiRatio || "N/A", volRatio: pcrData.volRatio || "N/A" },
|
|
35654
|
+
blockTrades: blockData.callVol !== void 0 ? { callVol: blockData.callVol, putVol: blockData.putVol } : null,
|
|
35655
|
+
errors,
|
|
35656
|
+
_summary: `${uly} \u671F\u6743\u626B\u63CF\uFF1A${summary.length} \u4E2A\u5408\u7EA6\uFF0CPCR(\u6301\u4ED3) ${pcrData.oiRatio || "N/A"}\u3002`
|
|
35657
|
+
});
|
|
35658
|
+
} catch (e) {
|
|
35659
|
+
return toError(e);
|
|
35660
|
+
}
|
|
35661
|
+
}
|
|
35662
|
+
);
|
|
35663
|
+
registerTool(
|
|
35664
|
+
server,
|
|
35665
|
+
"agent_prediction_arbitrage",
|
|
35666
|
+
"READ",
|
|
35667
|
+
"CAT:[\u9884\u6D4B\u5E02\u573A] | \u626B\u63CF\u9884\u6D4B\u5E02\u573A\u4E8B\u4EF6\uFF0C\u53D1\u73B0 YES+NO \u4EF7\u5DEE < 1.0 \u7684\u65E0\u98CE\u9669\u5957\u5229\u673A\u4F1A",
|
|
35668
|
+
{},
|
|
35669
|
+
async () => {
|
|
35670
|
+
try {
|
|
35671
|
+
const events = await publicApi.searchPredictionsEvents("");
|
|
35672
|
+
const evtList = Array.isArray(events) ? events.slice(0, 10) : [];
|
|
35673
|
+
const fetchers = {};
|
|
35674
|
+
for (const evt of evtList) {
|
|
35675
|
+
const id = evt.eventId || evt.event_id;
|
|
35676
|
+
if (id) fetchers[`evt_${id}`] = publicApi.getPredictionsEvent(id);
|
|
35677
|
+
const mktId = evt.marketId || evt.market_id;
|
|
35678
|
+
if (mktId) fetchers[`mkt_${mktId}`] = publicApi.getPredictionsMarket(mktId);
|
|
35679
|
+
}
|
|
35680
|
+
const { get } = await fetchAllSettled(fetchers);
|
|
35681
|
+
const opportunities = [];
|
|
35682
|
+
for (const evt of evtList) {
|
|
35683
|
+
const id = evt.eventId || evt.event_id;
|
|
35684
|
+
const mktId = evt.marketId || evt.market_id;
|
|
35685
|
+
const mktData = get(`mkt_${mktId}`);
|
|
35686
|
+
const yesPrice = parseFloat(mktData?.yesPrice || evt.yesPrice || "0");
|
|
35687
|
+
const noPrice = parseFloat(mktData?.noPrice || evt.noPrice || "0");
|
|
35688
|
+
const total = yesPrice + noPrice;
|
|
35689
|
+
if (total > 0 && total < 0.99) {
|
|
35690
|
+
opportunities.push({
|
|
35691
|
+
eventId: id,
|
|
35692
|
+
title: evt.title || "?",
|
|
35693
|
+
yesPrice: yesPrice.toFixed(4),
|
|
35694
|
+
noPrice: noPrice.toFixed(4),
|
|
35695
|
+
total: total.toFixed(4),
|
|
35696
|
+
discount: ((1 - total) * 100).toFixed(2) + "%"
|
|
35697
|
+
});
|
|
35698
|
+
}
|
|
35699
|
+
}
|
|
35700
|
+
return toResult({
|
|
35701
|
+
tsIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35702
|
+
eventsScanned: evtList.length,
|
|
35703
|
+
opportunities,
|
|
35704
|
+
_summary: opportunities.length > 0 ? `\u53D1\u73B0 ${opportunities.length} \u4E2A\u5957\u5229\u673A\u4F1A\uFF1A${opportunities.slice(0, 3).map((o) => `${o.title}(${o.discount})`).join(", ")}` : `\u5DF2\u626B\u63CF ${evtList.length} \u4E2A\u4E8B\u4EF6\uFF0C\u5F53\u524D\u65E0 YES+NO<1.0 \u7684\u5957\u5229\u673A\u4F1A\u3002`,
|
|
35705
|
+
tip: "\u786E\u8BA4\u673A\u4F1A\u540E\u7528 okx_predictions_place_order \u6216 okx_event_place_order \u5206\u522B\u4E70\u5165 YES \u548C NO\u3002"
|
|
35706
|
+
});
|
|
35707
|
+
} catch (e) {
|
|
35708
|
+
return toError(e);
|
|
35709
|
+
}
|
|
35710
|
+
}
|
|
35711
|
+
);
|
|
35712
|
+
registerTool(
|
|
35713
|
+
server,
|
|
35714
|
+
"agent_technical_report",
|
|
35715
|
+
"READ",
|
|
35716
|
+
"CAT:[\u6280\u672F\u6307\u6807] | \u591A\u5468\u671F(1H/4H/1D)\u6280\u672F\u6307\u6807\u7EFC\u5408\u8BA1\u7B97\uFF0C\u8F93\u51FA\u65B9\u5411\u5171\u8BC6\u4FE1\u53F7",
|
|
35717
|
+
{
|
|
35718
|
+
instId: external_exports.string().describe("\u4EA4\u6613\u54C1\u79CD\uFF0C\u5982 BTC-USDT\u3002\u5FC5\u586B"),
|
|
35719
|
+
bars: external_exports.array(external_exports.enum(["1H", "4H", "1D", "1W"])).optional().default(["1H", "4H", "1D"]).describe("K\u7EBF\u5468\u671F")
|
|
35720
|
+
},
|
|
35721
|
+
async ({ instId, bars }) => {
|
|
35722
|
+
try {
|
|
35723
|
+
const periods = bars || ["1H", "4H", "1D"];
|
|
35724
|
+
const fetchers = { ticker: publicApi.getTicker(instId) };
|
|
35725
|
+
for (const bar of periods) fetchers[`c_${bar}`] = publicApi.getCandles(instId, bar, 100);
|
|
35726
|
+
const { get, errors } = await fetchAllSettled(fetchers);
|
|
35727
|
+
const tickerData = get("ticker")?.[0] || {};
|
|
35728
|
+
const lastPrice = parseFloat(tickerData.last || "0");
|
|
35729
|
+
const periodResults = [];
|
|
35730
|
+
for (const bar of periods) {
|
|
35731
|
+
const candles = get(`c_${bar}`) || [];
|
|
35732
|
+
if (candles.length < 20) {
|
|
35733
|
+
periodResults.push({ period: bar, status: "\u6570\u636E\u4E0D\u8DB3" });
|
|
35734
|
+
continue;
|
|
35735
|
+
}
|
|
35736
|
+
const closes = candles.map((c) => parseFloat(c[4] || "0"));
|
|
35737
|
+
const sma20 = closes.slice(-20).reduce((a, b) => a + b, 0) / 20;
|
|
35738
|
+
const ema = (data, period) => {
|
|
35739
|
+
const k = 2 / (period + 1);
|
|
35740
|
+
let e = data[0];
|
|
35741
|
+
for (let i = 1; i < data.length; i++) e = data[i] * k + e * (1 - k);
|
|
35742
|
+
return e;
|
|
35743
|
+
};
|
|
35744
|
+
const macd = ema(closes, 12) - ema(closes, 26);
|
|
35745
|
+
const rsiData = closes.slice(-15);
|
|
35746
|
+
let gain = 0, loss = 0;
|
|
35747
|
+
for (let i = 1; i < rsiData.length; i++) {
|
|
35748
|
+
const d = rsiData[i] - rsiData[i - 1];
|
|
35749
|
+
if (d > 0) gain += d;
|
|
35750
|
+
else loss -= d;
|
|
35751
|
+
}
|
|
35752
|
+
const rsi = loss === 0 ? 100 : 100 - 100 / (1 + gain / 14 / (loss / 14));
|
|
35753
|
+
const signals = [];
|
|
35754
|
+
if (lastPrice > sma20) signals.push("\u4EF7\u683C>SMA20(\u504F\u591A)");
|
|
35755
|
+
else signals.push("\u4EF7\u683C<SMA20(\u504F\u7A7A)");
|
|
35756
|
+
if (macd > 0) signals.push("MACD>0(\u504F\u591A)");
|
|
35757
|
+
else signals.push("MACD<0(\u504F\u7A7A)");
|
|
35758
|
+
if (rsi > 70) signals.push("RSI\u8D85\u4E70(\u504F\u7A7A)");
|
|
35759
|
+
else if (rsi < 30) signals.push("RSI\u8D85\u5356(\u504F\u591A)");
|
|
35760
|
+
const bull = signals.filter((s) => s.includes("\u504F\u591A")).length;
|
|
35761
|
+
const bear = signals.filter((s) => s.includes("\u504F\u7A7A")).length;
|
|
35762
|
+
periodResults.push({ period: bar, lastPrice, sma20: sma20.toFixed(2), macd: macd.toFixed(6), rsi: rsi.toFixed(1), signals, direction: bull > bear ? "\u{1F7E2} \u504F\u591A" : bear > bull ? "\u{1F534} \u504F\u7A7A" : "\u{1F7E1} \u4E2D\u6027" });
|
|
35763
|
+
}
|
|
35764
|
+
const bullPeriods = periodResults.filter((p) => p.direction?.includes("\u504F\u591A")).map((p) => p.period);
|
|
35765
|
+
const bearPeriods = periodResults.filter((p) => p.direction?.includes("\u504F\u7A7A")).map((p) => p.period);
|
|
35766
|
+
const consensus = bullPeriods.length > bearPeriods.length ? `\u591A\u6570\u5468\u671F\u504F\u591A(${bullPeriods.join(",")})` : bearPeriods.length > bullPeriods.length ? `\u591A\u6570\u5468\u671F\u504F\u7A7A(${bearPeriods.join(",")})` : "\u591A\u7A7A\u5206\u6B67";
|
|
35767
|
+
return toResult({
|
|
35768
|
+
tsIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35769
|
+
instId,
|
|
35770
|
+
currentPrice: lastPrice,
|
|
35771
|
+
consensus,
|
|
35772
|
+
periods: periodResults,
|
|
35773
|
+
errors,
|
|
35774
|
+
_summary: `${instId} \u591A\u5468\u671FTA\uFF1A${consensus}\u3002\u5F53\u524D\u4EF7 $${lastPrice}\u3002`,
|
|
35775
|
+
disclaimer: "\u4EE5\u4E0A\u4E3AL1\u57FA\u7840\u6307\u6807\u8BA1\u7B97\uFF0C\u4E0D\u6784\u6210\u6295\u8D44\u5EFA\u8BAE\u3002\u8BE6\u7EC6\u591A\u6307\u6807\u5206\u6790\u53EF\u4F7F\u7528 okx_indicator_batch\u3002"
|
|
35776
|
+
});
|
|
35777
|
+
} catch (e) {
|
|
35778
|
+
return toError(e);
|
|
35779
|
+
}
|
|
35780
|
+
}
|
|
35781
|
+
);
|
|
35188
35782
|
registerTool(
|
|
35189
35783
|
server,
|
|
35190
35784
|
"agent_get_preference",
|
|
35191
35785
|
"READ",
|
|
35192
|
-
|
|
35786
|
+
"CAT:[\u7CFB\u7EDF] | \u8BFB\u53D6 Agent \u6301\u4E45\u5316\u504F\u597D\uFF0C\u8DE8\u4F1A\u8BDD\u4FDD\u7559",
|
|
35193
35787
|
{
|
|
35194
35788
|
key: external_exports.string().optional().describe("\u504F\u597D\u952E\u540D\uFF0C\u4E0D\u586B\u8FD4\u56DE\u5168\u90E8")
|
|
35195
35789
|
},
|
|
@@ -35201,7 +35795,14 @@ function registerAgentUtils(server, auth) {
|
|
|
35201
35795
|
if (fs.existsSync(prefPath)) {
|
|
35202
35796
|
prefs = JSON.parse(fs.readFileSync(prefPath, "utf-8"));
|
|
35203
35797
|
}
|
|
35204
|
-
} catch {
|
|
35798
|
+
} catch (e) {
|
|
35799
|
+
const corrupted = prefPath + ".corrupted." + (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
35800
|
+
try {
|
|
35801
|
+
fs.renameSync(prefPath, corrupted);
|
|
35802
|
+
} catch {
|
|
35803
|
+
}
|
|
35804
|
+
process.stderr.write(`[hvip] \u26A0\uFE0F preferences.json \u635F\u574F\uFF0C\u5DF2\u5907\u4EFD\u4E3A ${corrupted}\uFF0C\u91CD\u7F6E\u4E3A\u7A7A
|
|
35805
|
+
`);
|
|
35205
35806
|
}
|
|
35206
35807
|
const result = key ? { key, value: prefs[key] ?? null, found: key in prefs } : { all: prefs, count: Object.keys(prefs).length };
|
|
35207
35808
|
return toResult({
|
|
@@ -35233,7 +35834,14 @@ function registerAgentUtils(server, auth) {
|
|
|
35233
35834
|
if (fs.existsSync(prefPath)) {
|
|
35234
35835
|
prefs = JSON.parse(fs.readFileSync(prefPath, "utf-8"));
|
|
35235
35836
|
}
|
|
35236
|
-
} catch {
|
|
35837
|
+
} catch (e) {
|
|
35838
|
+
const corrupted = prefPath + ".corrupted." + (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
35839
|
+
try {
|
|
35840
|
+
fs.renameSync(prefPath, corrupted);
|
|
35841
|
+
} catch {
|
|
35842
|
+
}
|
|
35843
|
+
process.stderr.write(`[hvip] \u26A0\uFE0F preferences.json \u635F\u574F\uFF0C\u5DF2\u5907\u4EFD\u4E3A ${corrupted}\uFF0C\u91CD\u7F6E\u4E3A\u7A7A
|
|
35844
|
+
`);
|
|
35237
35845
|
}
|
|
35238
35846
|
prefs[key] = value;
|
|
35239
35847
|
fs.writeFileSync(prefPath, JSON.stringify(prefs, null, 2), "utf-8");
|
|
@@ -36691,6 +37299,8 @@ var WsManager = class {
|
|
|
36691
37299
|
}
|
|
36692
37300
|
}
|
|
36693
37301
|
} catch {
|
|
37302
|
+
process.stderr.write(`[WS] \u26A0\uFE0F \u7578\u5F62 WS \u6D88\u606F: ${raw.toString().slice(0, 80)}
|
|
37303
|
+
`);
|
|
36694
37304
|
}
|
|
36695
37305
|
});
|
|
36696
37306
|
ws.on("error", (err) => {
|
|
@@ -36956,6 +37566,8 @@ var AgentHub = class {
|
|
|
36956
37566
|
version = "0.0.0";
|
|
36957
37567
|
port = 0;
|
|
36958
37568
|
db = null;
|
|
37569
|
+
token = "";
|
|
37570
|
+
// PSK 鉴权令牌
|
|
36959
37571
|
// ── 持久化绑定 ──
|
|
36960
37572
|
setDB(db) {
|
|
36961
37573
|
this.db = db;
|
|
@@ -36974,8 +37586,9 @@ var AgentHub = class {
|
|
|
36974
37586
|
}
|
|
36975
37587
|
}
|
|
36976
37588
|
// ── 启动 ──
|
|
36977
|
-
start(port, host = "0.0.0.0", version2 = "0.0.0") {
|
|
37589
|
+
start(port, host = "0.0.0.0", version2 = "0.0.0", token = "") {
|
|
36978
37590
|
this.version = version2;
|
|
37591
|
+
this.token = token;
|
|
36979
37592
|
const startWss = (p) => {
|
|
36980
37593
|
return new Promise((resolve2) => {
|
|
36981
37594
|
const wss = new import_websocket_server.default({ port: p, host });
|
|
@@ -37023,7 +37636,16 @@ var AgentHub = class {
|
|
|
37023
37636
|
if (!this.wss) return;
|
|
37024
37637
|
this.ensureRoom("#lobby");
|
|
37025
37638
|
this.ensureRoom("#review");
|
|
37026
|
-
this.wss.on("connection", (ws) => {
|
|
37639
|
+
this.wss.on("connection", (ws, req) => {
|
|
37640
|
+
if (this.token) {
|
|
37641
|
+
const urlParams = new URL(req.url || "/", "http://localhost").searchParams;
|
|
37642
|
+
const provided = urlParams.get("token") || "";
|
|
37643
|
+
if (provided !== this.token) {
|
|
37644
|
+
ws.send(JSON.stringify({ type: "error", message: "\u672A\u6388\u6743 \u2014 \u8BF7\u5728 WS URL \u4E2D\u643A\u5E26 ?token=..." }));
|
|
37645
|
+
ws.close();
|
|
37646
|
+
return;
|
|
37647
|
+
}
|
|
37648
|
+
}
|
|
37027
37649
|
let agentId = null;
|
|
37028
37650
|
ws.on("message", (raw) => {
|
|
37029
37651
|
try {
|
|
@@ -37037,7 +37659,9 @@ var AgentHub = class {
|
|
|
37037
37659
|
ws.on("close", () => {
|
|
37038
37660
|
if (agentId) this.handleDisconnect(agentId);
|
|
37039
37661
|
});
|
|
37040
|
-
ws.on("error", () => {
|
|
37662
|
+
ws.on("error", (e) => {
|
|
37663
|
+
process.stderr.write(`[AgentHub] WS \u9519\u8BEF: ${e.message}
|
|
37664
|
+
`);
|
|
37041
37665
|
});
|
|
37042
37666
|
});
|
|
37043
37667
|
this.heartbeatTimer = setInterval(() => {
|
|
@@ -37220,7 +37844,7 @@ var AgentHub = class {
|
|
|
37220
37844
|
message: `${agentId} \u5DF2\u5B8C\u6210 ${taskId}\uFF0C\u7B49\u5F85\u5BA1\u6838`
|
|
37221
37845
|
});
|
|
37222
37846
|
}
|
|
37223
|
-
// ──
|
|
37847
|
+
// ── 注册任务(自动派发给空闲 Agent) ──
|
|
37224
37848
|
registerTask(taskId, title) {
|
|
37225
37849
|
if (!this.tasks.has(taskId)) {
|
|
37226
37850
|
this.tasks.set(taskId, { status: "unassigned" });
|
|
@@ -37230,6 +37854,17 @@ var AgentHub = class {
|
|
|
37230
37854
|
t.title = title;
|
|
37231
37855
|
}
|
|
37232
37856
|
console.log(`[AgentHub] \u4EFB\u52A1\u6CE8\u518C: ${taskId} "${title || taskId}"`);
|
|
37857
|
+
this.broadcast({ type: "task:announced", taskId, title: title || taskId });
|
|
37858
|
+
const idleAgents = [...this.agents.entries()].filter(([, a]) => a.status === "idle" && !a.agentId.startsWith("dashboard"));
|
|
37859
|
+
if (idleAgents.length === 0) {
|
|
37860
|
+
console.log(`[AgentHub] \u6CA1\u6709\u7A7A\u95F2 Agent\uFF0C\u4EFB\u52A1 ${taskId} \u7B49\u5F85\u624B\u52A8 spawn`);
|
|
37861
|
+
return;
|
|
37862
|
+
}
|
|
37863
|
+
const match = idleAgents.find(([, a]) => a.capabilities.includes(taskId)) || idleAgents[0];
|
|
37864
|
+
if (match) {
|
|
37865
|
+
console.log(`[AgentHub] \u81EA\u52A8\u6D3E\u53D1 ${taskId} \u2192 ${match[1].name} (${match[0]})`);
|
|
37866
|
+
this.dispatchTaskTo(taskId, match[0]);
|
|
37867
|
+
}
|
|
37233
37868
|
}
|
|
37234
37869
|
// ── 派发任务 ──
|
|
37235
37870
|
dispatchTaskTo(taskId, agentId) {
|
|
@@ -37423,8 +38058,8 @@ var AgentHub = class {
|
|
|
37423
38058
|
}
|
|
37424
38059
|
};
|
|
37425
38060
|
var agentHub = new AgentHub();
|
|
37426
|
-
function startAgentHub(port, host = "0.0.0.0", version2 = "0.0.0") {
|
|
37427
|
-
agentHub.start(port, host, version2);
|
|
38061
|
+
function startAgentHub(port, host = "0.0.0.0", version2 = "0.0.0", token = "") {
|
|
38062
|
+
agentHub.start(port, host, version2, token);
|
|
37428
38063
|
}
|
|
37429
38064
|
|
|
37430
38065
|
// src/tools/agent-hub.ts
|
|
@@ -38098,16 +38733,29 @@ function registerAllTools(server, auth, readOnly) {
|
|
|
38098
38733
|
}
|
|
38099
38734
|
async function startHttp(server, version2, auth, readOnly, skipped) {
|
|
38100
38735
|
const port = parseInt(process.env.PORT || "3000", 10);
|
|
38736
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
38737
|
+
process.stderr.write(`[hvip] \u274C \u65E0\u6548\u7AEF\u53E3: ${process.env.PORT}\uFF0C\u4F7F\u7528\u9ED8\u8BA4 3000
|
|
38738
|
+
`);
|
|
38739
|
+
}
|
|
38101
38740
|
const host = process.env.HOST || "127.0.0.1";
|
|
38741
|
+
const token = process.env.MCP_AUTH_TOKEN || "";
|
|
38102
38742
|
const transport = new StreamableHTTPServerTransport({
|
|
38103
38743
|
sessionIdGenerator: void 0
|
|
38104
38744
|
// stateless
|
|
38105
38745
|
});
|
|
38106
38746
|
await server.connect(transport);
|
|
38107
38747
|
const httpServer = (0, import_node_http.createServer)(async (req, res) => {
|
|
38748
|
+
if (token && req.url !== "/health") {
|
|
38749
|
+
const provided = req.headers["authorization"]?.replace(/^Bearer\s+/i, "") || "";
|
|
38750
|
+
if (provided !== token) {
|
|
38751
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
38752
|
+
res.end(JSON.stringify({ error: "Unauthorized \u2014 \u8BF7\u5728 Authorization \u5934\u643A\u5E26\u6709\u6548 token" }));
|
|
38753
|
+
return;
|
|
38754
|
+
}
|
|
38755
|
+
}
|
|
38108
38756
|
res.setHeader("Access-Control-Allow-Origin", `http://${host}:${port}`);
|
|
38109
38757
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
38110
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
|
|
38758
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id, Authorization");
|
|
38111
38759
|
if (req.method === "OPTIONS") {
|
|
38112
38760
|
res.writeHead(204);
|
|
38113
38761
|
res.end();
|
|
@@ -38214,12 +38862,13 @@ async function startStdio(server, version2, auth, readOnly, skipped, skipLog) {
|
|
|
38214
38862
|
`);
|
|
38215
38863
|
}
|
|
38216
38864
|
const wsHost = process.env.WS_BIND_HOST || "127.0.0.1";
|
|
38217
|
-
|
|
38865
|
+
const wsToken = process.env.HUB_AUTH_TOKEN || "";
|
|
38866
|
+
startAgentHub(parseInt(process.env.WS_AGENT_PORT || "9321"), wsHost, version2, wsToken);
|
|
38218
38867
|
const transport = new StdioServerTransport();
|
|
38219
38868
|
await server.connect(transport);
|
|
38220
38869
|
}
|
|
38221
38870
|
async function main() {
|
|
38222
|
-
const VERSION = "0.3.
|
|
38871
|
+
const VERSION = "0.3.2";
|
|
38223
38872
|
const argv = process.argv.slice(2);
|
|
38224
38873
|
if (argv.includes("--help") || argv.includes("-h")) printHelpAndExit(VERSION);
|
|
38225
38874
|
if (argv.includes("--version") || argv.includes("-v")) {
|