"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);
}

});

HAMI_9756637300 · 4 天前

回答

Hamibot AI
1
· 4 天前

这段脚本实现了 OKX 交易员持仓监控功能,逻辑清晰,但在 Hamibot 的 Rhino 1.7.15 JavaScript 引擎环境下存在兼容性风险和运行稳定性问题。以下是具体的问题分析、优化建议及修正后的代码。

主要问题分析

  1. JavaScript 引擎兼容性

    • String.startsWith():Rhino 1.7.15 不支持 ES6 的 startsWith 方法,会导致脚本报错。需替换为 indexOf
    • 箭头函数:虽然你的代码中主要使用了 function,但需确保后续修改不使用箭头函数 =>
    • 变量声明:建议统一使用 var 避免潜在作用域问题。
  2. 脚本保活机制

    • 主线程在启动子线程后会立即结束。虽然使用了 "ui" 模式,但未创建标准 UI 布局,仅靠悬浮窗可能导致脚本在某些情况下意外退出。建议添加 setInterval 保持脚本运行。
  3. 网络请求稳定性

    • OKX API 可能对请求头有要求,建议添加 User-Agent
    • 网络波动可能导致 JSON 解析失败,需增强异常处理。
  4. 权限要求

    • 悬浮窗权限:必须授予 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);

使用建议

  1. 权限授予

    • 在 Hamibot 控制台或手机设置中,确保已授予“悬浮窗权限”和“网络权限”。
    • 首次运行振动功能时,系统可能会弹出权限请求,需手动确认。
  2. API 限制

    • OKX 公共接口可能有频率限制,若刷新间隔过短(如低于 3 秒),可能会触发 IP 限制导致数据获取失败。建议保持 6 秒或以上间隔。
    • 若接口返回频繁报错,可能是网络环境问题,需确保设备能正常访问 OKX 域名。
  3. 悬浮窗交互

    • 脚本运行后,悬浮窗会显示在屏幕上层。若需关闭脚本,可通过 Hamibot 控制台停止,或修改代码添加退出按钮。
    • floatyWindow.exitOnClose = false 表示关闭悬浮窗不会停止脚本,若希望关闭悬浮窗即停止脚本,可将其设为 true 并在关闭事件中调用 hamibot.exit()
  4. 异常处理

    • 代码已增加全局 try-catch,遇到网络波动或解析错误时会 toast 提示并等待 2 秒后重试,避免死循环报错消耗资源。

此版本代码已针对 Hamibot 运行环境进行了兼容性修正,可直接创建为新建脚本运行。

ℹ️ 本回答由 AI 生成,内容仅供参考