"ui";
toast("OKX 三大佬监控启动");
// ==================== 配置区 ====================
var traders = [
{ name: "老恶魔", code: "E512EAA2C34FAF44" },
{ name: "BTC星辰", code: "2831B237B23802D3" },
{ name: "宝宝哥", code: "8DE3E9294C1A2440" }
// 要加更多大佬,就在这里继续加一行:{ name: "新名字", code: "新ID" },
];
var refreshInterval = 6000; // 6秒刷新一次
var lastKeys = {}; // 每个大佬的上次持仓key
var floatyWindow = null;
// ==================== 主循环 ====================
threads.start(function() {
while (true) {
try {
var allDisplayText = "OKX 实时监控 - " + new Date().toLocaleTimeString() + "\n\n";
var hasChange = false;
traders.forEach(function(trader) {
var uniqueCode = trader.code;
var url = "https://www.okx.com/api/v5/copytrading/public-current-subpositions?instType=SWAP&uniqueCode=" + uniqueCode;
var res = http.get(url);
if (res.statusCode !== 200) {
allDisplayText += trader.name + ":网络错误 (" + res.statusCode + ")\n\n";
return;
}
var jsonStr = res.body.string();
var data = JSON.parse(jsonStr);
if (data.code !== "0") {
allDisplayText += trader.name + ":API错误 (" + data.msg + ")\n\n";
return;
}
var positions = data.data || [];
var traderText = "<b>【" + trader.name + "】</b>\n";
if (positions.length === 0) {
traderText += "当前无持仓\n\n";
} else {
positions.forEach(function(pos, index) {
var upl = pos.uplRatio || "0";
var color = upl.startsWith("+") ? "#00FF00" : (upl.startsWith("-") ? "#FF0000" : "#FFFFFF");
var line = (index + 1) + ". " + pos.instId + " " + pos.posSide +
" @ " + (pos.openAvgPx || "?") + "\n" +
"数量: " + (pos.subPos || "0") + " 张 盈亏: " +
"<font color='" + color + "'>" + upl + "%</font>\n";
traderText += line;
});
traderText += "\n";
}
// 变化检测
var currentKey = positions.map(function(p) {
return p.instId + p.posSide + (p.subPos || "0");
}).join("|");
if (lastKeys[uniqueCode] && currentKey !== lastKeys[uniqueCode]) {
device.vibrate(500);
traderText = "<font color='#FFFF00'>【变化!】</font> " + traderText;
toast(trader.name + " 有开/平仓!");
hasChange = true;
}
lastKeys[uniqueCode] = currentKey;
allDisplayText += traderText;
});
// 更新悬浮窗(支持简单HTML颜色)
ui.run(function() {
if (!floatyWindow) {
floatyWindow = floaty.rawWindow(
<frame bg="#80000000" padding="12">
<text id="content" textSize="13sp" textColor="#FFFFFF" gravity="left"/>
</frame>
);
floatyWindow.setSize(500, 450); // 窗口更大一点
floatyWindow.setPosition(device.width - 540, 80);
floatyWindow.setAdjustable(true);
floatyWindow.exitOnClose = false;
}
floatyWindow.findView("content").setText(allDisplayText);
});
} catch (e) {
toast("异常: " + e);
}
sleep(refreshInterval);
}
});
回答
这段脚本实现了 OKX 交易员持仓监控功能,逻辑清晰,但在 Hamibot 的 Rhino 1.7.15 JavaScript 引擎环境下存在兼容性风险和运行稳定性问题。以下是具体的问题分析、优化建议及修正后的代码。
主要问题分析
-
JavaScript 引擎兼容性
- String.startsWith():Rhino 1.7.15 不支持 ES6 的
startsWith方法,会导致脚本报错。需替换为indexOf。 - 箭头函数:虽然你的代码中主要使用了
function,但需确保后续修改不使用箭头函数=>。 - 变量声明:建议统一使用
var避免潜在作用域问题。
- String.startsWith():Rhino 1.7.15 不支持 ES6 的
-
脚本保活机制
- 主线程在启动子线程后会立即结束。虽然使用了
"ui"模式,但未创建标准 UI 布局,仅靠悬浮窗可能导致脚本在某些情况下意外退出。建议添加setInterval保持脚本运行。
- 主线程在启动子线程后会立即结束。虽然使用了
-
网络请求稳定性
- OKX API 可能对请求头有要求,建议添加
User-Agent。 - 网络波动可能导致 JSON 解析失败,需增强异常处理。
- OKX API 可能对请求头有要求,建议添加
-
权限要求
- 悬浮窗权限:必须授予 Hamibot 显示悬浮窗权限。
- 网络权限:脚本需访问互联网。
- 振动权限:使用
device.vibrate需要振动权限。
优化后的代码
// 文档地址 https://docs.hamibot.com/
"ui";
toast("OKX 三大佬监控启动");
// ==================== 配置区 ====================
var traders = [
{ name: "老恶魔", code: "E512EAA2C34FAF44" },
{ name: "BTC 星辰", code: "2831B237B23802D3" },
{ name: "宝宝哥", code: "8DE3E9294C1A2440" }
];
var refreshInterval = 6000; // 6 秒刷新一次
var lastKeys = {}; // 每个大佬的上次持仓 key
var floatyWindow = null;
// ==================== 初始化悬浮窗 ====================
function initFloaty() {
ui.run(function() {
if (!floatyWindow) {
floatyWindow = floaty.rawWindow(
<frame bg="#80000000" padding="12">
<text id="content" textSize="13sp" textColor="#FFFFFF" gravity="left"/>
</frame>
);
floatyWindow.setSize(500, 450);
floatyWindow.setPosition(device.width - 540, 80);
floatyWindow.setAdjustable(true);
floatyWindow.exitOnClose = false;
}
});
}
// ==================== 主循环 ====================
threads.start(function() {
initFloaty();
while (true) {
try {
var allDisplayText = "OKX 实时监控 - " + new Date().toLocaleTimeString() + "\n\n";
var hasChange = false;
traders.forEach(function(trader) {
var uniqueCode = trader.code;
var url = "https://www.okx.com/api/v5/copytrading/public-current-subpositions?instType=SWAP&uniqueCode=" + uniqueCode;
// 添加请求头以防被拦截
var res = http.get(url, {
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}
});
if (res.statusCode !== 200) {
allDisplayText += trader.name + ":网络错误 (" + res.statusCode + ")\n\n";
return;
}
var jsonStr = res.body.string();
var data = JSON.parse(jsonStr);
if (data.code !== "0") {
allDisplayText += trader.name + ":API 错误 (" + data.msg + ")\n\n";
return;
}
var positions = data.data || [];
var traderText = "<b>【" + trader.name + "】</b>\n";
if (positions.length === 0) {
traderText += "当前无持仓\n\n";
} else {
positions.forEach(function(pos, index) {
var upl = pos.uplRatio || "0";
// 兼容 Rhino 引擎,不使用 startsWith
var color = upl.indexOf("+") === 0 ? "#00FF00" : (upl.indexOf("-") === 0 ? "#FF0000" : "#FFFFFF");
var line = (index + 1) + ". " + pos.instId + " " + pos.posSide +
" @ " + (pos.openAvgPx || "?") + "\n" +
"数量:" + (pos.subPos || "0") + " 张 盈亏:" +
"<font color='" + color + "'>" + upl + "%</font>\n";
traderText += line;
});
traderText += "\n";
}
// 变化检测
var currentKey = positions.map(function(p) {
return p.instId + p.posSide + (p.subPos || "0");
}).join("|");
if (lastKeys[uniqueCode] && currentKey !== lastKeys[uniqueCode]) {
device.vibrate(500);
traderText = "<font color='#FFFF00'>【变化!】</font> " + traderText;
toast(trader.name + " 有开/平仓!");
hasChange = true;
}
lastKeys[uniqueCode] = currentKey;
allDisplayText += traderText;
});
// 更新悬浮窗
ui.run(function() {
if (floatyWindow) {
floatyWindow.findView("content").setText(allDisplayText);
}
});
} catch (e) {
toast("异常:" + e);
sleep(2000);
}
sleep(refreshInterval);
}
});
// 保持脚本运行,防止主线程结束导致脚本退出
setInterval(function() {}, 1000);
使用建议
-
权限授予:
- 在 Hamibot 控制台或手机设置中,确保已授予“悬浮窗权限”和“网络权限”。
- 首次运行振动功能时,系统可能会弹出权限请求,需手动确认。
-
API 限制:
- OKX 公共接口可能有频率限制,若刷新间隔过短(如低于 3 秒),可能会触发 IP 限制导致数据获取失败。建议保持 6 秒或以上间隔。
- 若接口返回频繁报错,可能是网络环境问题,需确保设备能正常访问 OKX 域名。
-
悬浮窗交互:
- 脚本运行后,悬浮窗会显示在屏幕上层。若需关闭脚本,可通过 Hamibot 控制台停止,或修改代码添加退出按钮。
floatyWindow.exitOnClose = false表示关闭悬浮窗不会停止脚本,若希望关闭悬浮窗即停止脚本,可将其设为true并在关闭事件中调用hamibot.exit()。
-
异常处理:
- 代码已增加全局
try-catch,遇到网络波动或解析错误时会 toast 提示并等待 2 秒后重试,避免死循环报错消耗资源。
- 代码已增加全局
此版本代码已针对 Hamibot 运行环境进行了兼容性修正,可直接创建为新建脚本运行。