私有频道WebSocket连接测试模板


创建日期: 2025-06-20 15:32:38 最后修改: 2025-06-20 15:32:47
复制: 0 点击次数: 212
avatar of 算法007 算法007
3
关注
3
关注者

🚀 FMZ可用HMAC私有频道WebSocket测试脚本

📖 脚本简介

这是一个专门为FMZ平台开发的OKX私有频道WebSocket连接测试脚本,使用经过验证的exchange.HMAC函数实现安全认证,帮助开发者快速验证私有频道连接和数据推送功能。

✨ 主要特性

  • 验证可用性 - 使用FMZ平台已验证的exchange.HMAC函数
  • 🔐 安全认证 - 标准HMAC-SHA256签名认证流程
  • 📡 多频道订阅 - 支持账户余额和持仓信息实时推送
  • 🛡️ 错误处理 - 完整的错误分析和解决建议
  • 💓 心跳维护 - 自动发送心跳包保持连接稳定
  • 📊 详细日志 - 完整的测试过程和结果分析

🎯 适用场景

  • API连接验证 - 测试OKX API凭证是否正确配置
  • 私有数据获取 - 验证实时账户和持仓数据推送
  • 策略开发前置 - 在开发实盘策略前验证WebSocket连接
  • 问题诊断 - 快速定位私有频道连接问题

🔧 使用方法

1. 配置API凭证

// ⚠️ 重要:请替换为您的实际API凭证
var accessKey = "your_api_key_here";      // 🔑 您的OKX API Key
var secretKey = "your_secret_key_here";   // 🔑 您的OKX Secret Key  
var passphrase = "your_passphrase_here";  // 🔑 您的OKX Passphrase

2. 获取API凭证步骤

  1. 登录OKX官网: https://www.okx.com/account/my-api
  2. 创建或查看API Key
  3. 复制API Key、Secret Key、Passphrase到代码中
  4. 确保API权限包含’读取’权限
  5. 如设置IP白名单,确保包含FMZ服务器IP

3. 运行测试

  • 直接在FMZ平台运行脚本
  • 观察日志输出了解连接状态
  • 测试时长45秒,完整验证各项功能

📊 测试结果说明

✅ 成功标志

  • 🔐 认证成功 - 显示”私有频道登录认证成功”
  • 📡 订阅成功 - 显示”私有频道订阅成功”
  • 🔑 数据推送 - 收到账户或持仓数据(如有变化)

❌ 常见错误及解决方案

错误码 说明 解决方案
50111 API Key无效 检查API Key是否完整正确
50113 签名验证失败 检查Secret Key和Passphrase
50102 时间戳错误 确认系统时间准确(偏差<30秒)
50103 请求过于频繁 降低请求频率,等待后重试

🎯 实际应用价值

对新手开发者

  • 快速验证API配置是否正确
  • 了解私有频道连接完整流程
  • 学习WebSocket消息处理方法

对经验开发者

  • 作为策略开发的前置验证工具
  • 快速诊断生产环境连接问题
  • 作为私有频道集成的参考代码

🔄 后续集成建议

测试成功后,您可以: 1. 将认证逻辑集成到实盘策略中 2. 扩展订阅更多私有频道(订单、交易等) 3. 添加业务逻辑处理实时数据推送 4. 优化错误处理和重连机制

⚠️ 重要提醒

  • 🔒 API安全 - 请勿在公开场合泄露API凭证
  • 🌐 网络稳定 - 确保网络连接稳定,避免频繁重连
  • 📝 权限配置 - 确认API权限设置正确
  • 🕒 时间同步 - 保持系统时间准确

💡 技术亮点

  • 使用FMZ平台原生exchange.HMAC函数,兼容性最佳
  • 完整的错误处理和用户友好的提示信息
  • 标准化的WebSocket连接和心跳维护
  • 详细的日志输出便于问题定位

作者说明: 这个脚本是基于FMZ平台特性和OKX API官方文档开发的测试工具,经过实际验证可用。如果您在使用过程中遇到问题,欢迎交流讨论!

策略源码
// ============================================================================
// FMZ可用HMAC私有频道WebSocket测试脚本
// 使用已验证可用的exchange.HMAC函数
// ============================================================================

function getLogin(pAccessKey, pSecretKey, pPassphrase) {
    // 签名函数,用于登录 - 使用已验证可用的exchange.HMAC
    var ts = (new Date().getTime() / 1000).toString();
    var message = ts + "GET" + "/users/self/verify";
    
    var signature = "";
    try {
        // 使用已验证可用的exchange.HMAC函数
        signature = exchange.HMAC("sha256", "base64", message, pSecretKey);
        Log("✅ 使用exchange.HMAC生成签名成功");
    } catch (e) {
        Log("❌ 签名生成失败:", e.toString());
        return null;
    }
    
    var login = {
        "op": "login",
        "args":[{
            "apiKey"    : pAccessKey,
            "passphrase" : pPassphrase,
            "timestamp" : ts,
            "sign" : signature
        }]
    };    
    return login;
}                

var client_private = null;

function main() {
    Log("🚀 开始FMZ可用HMAC私有频道测试");
    
    SetErrorFilter("timeout");
    
    var exchangeName = exchange.GetName();
    var currency = exchange.GetCurrency();
    
    Log("📊 交易所:", exchangeName);
    Log("💰 交易对:", currency);
    
    // 验证HMAC函数可用性
    if (typeof exchange.HMAC === 'function') {
        Log("✅ exchange.HMAC 函数可用");
        
        // 测试签名生成
        try {
            var testSig = exchange.HMAC("sha256", "base64", "test_message", "test_secret");
            Log("✅ HMAC签名测试成功:", testSig.substring(0, 16) + "...");
        } catch (e) {
            Log("❌ HMAC签名测试失败:", e.toString());
            return;
        }
    } else {
        Log("❌ exchange.HMAC 函数不可用,无法继续");
        return;
    }
    
    // ⚠️ 重要:请替换为您的实际API凭证
    var accessKey = "your_api_key_here";      // 🔑 替换为您的OKX API Key
    var secretKey = "your_secret_key_here";   // 🔑 替换为您的OKX Secret Key  
    var passphrase = "your_passphrase_here";  // 🔑 替换为您的OKX Passphrase
    
    // 检查API凭证是否已配置
    if (accessKey === "your_api_key_here" || secretKey === "your_secret_key_here") {
        Log("❌ 请先配置API凭证!");
        Log("💡 请在代码中将以下变量替换为您的实际API凭证:");
        Log("   1. 登录OKX官网: https://www.okx.com/account/my-api");
        Log("   2. 创建或查看API Key");
        Log("   3. 复制以下信息到代码中:");
        Log("      - API Key (accessKey)");
        Log("      - Secret Key (secretKey)");
        Log("      - Passphrase (passphrase)");
        Log("   4. 确保API权限包含'读取'权限");
        Log("   5. 如果设置了IP白名单,确保包含FMZ服务器IP");
        return;
    }
    
    // 测试登录消息生成
    var testLogin = getLogin(accessKey, secretKey, passphrase);
    if (!testLogin) {
        Log("❌ 登录消息生成失败,无法继续");
        return;
    }
    
    Log("🔐 API凭证已配置,登录消息生成成功");
    Log("📤 登录消息预览:", JSON.stringify({
        "op": "login",
        "args": [{
            "apiKey": accessKey.substring(0, 8) + "...",
            "passphrase": "***",
            "timestamp": testLogin.args[0].timestamp,
            "sign": testLogin.args[0].sign.substring(0, 16) + "..."
        }]
    }, null, 2));
    
    // 订阅配置
    var posSubscribe = {
        "op": "subscribe",
        "args": [{
            "channel": "positions",
            "instType": "ANY"
        }]
    };
    
    var accountSubscribe = {
        "op": "subscribe",
        "args": [{
            "channel": "account"
        }]
    };
    
    // 连接私有频道
    Log("⏳ 连接私有频道WebSocket...");
    client_private = Dial("wss://ws.okx.com:8443/ws/v5/private");
    
    if (client_private) {
        Log("✅ 私有频道WebSocket连接成功!");
        
        // 发送登录认证
        Log("🔐 发送登录认证...");
        client_private.write(JSON.stringify(testLogin));
        
        // 等待服务器响应 - 官方要求3秒
        Log("⏳ 等待服务器认证响应(3秒)...");
        Sleep(3000);
        
        // 订阅频道
        Log("📡 订阅持仓频道...");
        client_private.write(JSON.stringify(posSubscribe));
        
        Log("📡 订阅账户频道...");
        client_private.write(JSON.stringify(accountSubscribe));
        
        // 开始监听
        monitorMessages();
        
    } else {
        Log("❌ 私有频道WebSocket连接失败");
        Log("💡 可能的原因:");
        Log("   1. 网络连接问题");
        Log("   2. OKX服务器维护");
        Log("   3. 防火墙阻止连接");
    }
}

function monitorMessages() {
    Log("📨 开始监听私有频道消息...");
    
    if (client_private) {
        var lastPingTS = new Date().getTime();
        var messageCount = 0;
        var authSuccess = false;
        var subscribeSuccess = false;
        var privateDataReceived = false;
        
        var testStartTime = new Date().getTime();
        var testDuration = 45000; // 测试45秒
        
        Log("⏰ 测试时长:", testDuration / 1000, "秒");
        
        while (new Date().getTime() - testStartTime < testDuration) {
            try {
                var buf = client_private.read(1000); // 1秒超时
                
                if (buf) {
                    messageCount++;
                    Log("📨 消息 #" + messageCount + ":", buf);
                    
                    // 分析消息
                    var analysis = analyzeMessage(buf);
                    if (analysis.isLogin && analysis.success) {
                        authSuccess = true;
                        Log("🎉 认证成功标记已设置");
                    }
                    if (analysis.isSubscribe && analysis.success) {
                        subscribeSuccess = true;
                        Log("✅ 订阅成功标记已设置");
                    }
                    if (analysis.isPrivateData) {
                        privateDataReceived = true;
                        Log("🔑 私有数据接收标记已设置");
                    }
                }
                
                // 发送心跳包
                var nowPingTS = new Date().getTime();
                if (nowPingTS - lastPingTS > 10 * 1000) {
                    client_private.write("ping");
                    lastPingTS = nowPingTS;
                    Log("💓 发送心跳包");
                }
                
                // 每10秒显示一次状态
                var elapsed = new Date().getTime() - testStartTime;
                if (elapsed % 10000 < 1000 && elapsed > 5000) {
                    Log("📊 中间状态 (" + Math.floor(elapsed/1000) + "s):",
                        "消息:" + messageCount,
                        "认证:" + (authSuccess ? "✅" : "❌"),
                        "订阅:" + (subscribeSuccess ? "✅" : "❌"),
                        "数据:" + (privateDataReceived ? "✅" : "❌"));
                }
                
            } catch (e) {
                Log("⚠️ 消息处理异常:", e.toString());
            }
        }
        
        // 最终测试结果
        Log("=== 最终测试结果 ===");
        Log("📊 测试时长:", testDuration / 1000, "秒");
        Log("📨 收到消息数:", messageCount);
        Log("🔐 认证状态:", authSuccess ? "✅ 成功" : "❌ 失败");
        Log("📡 订阅状态:", subscribeSuccess ? "✅ 成功" : "❌ 失败");
        Log("🔑 私有数据:", privateDataReceived ? "✅ 已接收" : "❌ 未接收");
        
        // 结论和建议
        if (authSuccess && subscribeSuccess) {
            Log("🎉 私有频道WebSocket测试成功!");
            Log("💡 您可以在实盘策略中使用此方式获取:");
            Log("   - 实时账户余额变化");
            Log("   - 实时持仓信息更新");
            Log("   - 订单状态变化通知");
            if (!privateDataReceived) {
                Log("ℹ️ 未收到私有数据推送是正常的(没有余额/持仓变化时)");
            }
        } else if (authSuccess) {
            Log("⚠️ 认证成功但订阅失败");
            Log("💡 请检查订阅权限和频道配置");
        } else if (messageCount > 0) {
            Log("❌ 连接成功但认证失败");
            Log("💡 请检查API凭证:");
            Log("   1. API Key是否正确");
            Log("   2. Secret Key是否正确");
            Log("   3. Passphrase是否正确");
            Log("   4. API权限是否包含读取权限");
            Log("   5. IP白名单设置是否正确");
        } else {
            Log("❌ 未收到任何消息");
            Log("💡 可能是网络连接问题");
        }
    }
}

function analyzeMessage(message) {
    var result = {
        isLogin: false,
        isSubscribe: false,
        isPrivateData: false,
        success: false
    };
    
    try {
        var data = JSON.parse(message);
        
        if (data.event === "login") {
            result.isLogin = true;
            if (data.code === "0") {
                result.success = true;
                Log("🎉 私有频道登录认证成功!");
            } else {
                Log("❌ 私有频道登录认证失败:", data.msg, "错误码:", data.code);
                
                // 详细错误分析和解决建议
                switch(data.code) {
                    case "50111":
                        Log("💡 API Key无效:");
                        Log("   - 检查API Key是否完整正确");
                        Log("   - 确认API Key未被删除或禁用");
                        break;
                    case "50113":
                        Log("💡 签名验证失败:");
                        Log("   - 检查Secret Key是否正确");
                        Log("   - 检查Passphrase是否正确");
                        Log("   - 确认系统时间准确");
                        break;
                    case "50102":
                        Log("💡 时间戳错误:");
                        Log("   - 检查系统时间是否准确");
                        Log("   - 时间偏差不能超过30秒");
                        break;
                    case "50103":
                        Log("💡 请求过于频繁:");
                        Log("   - 降低请求频率");
                        Log("   - 等待一段时间后重试");
                        break;
                    default:
                        Log("💡 其他错误 - 错误码:", data.code);
                        Log("   - 请查看OKX API文档获取详细说明");
                }
            }
        } else if (data.event === "subscribe") {
            result.isSubscribe = true;
            if (data.code === "0") {
                result.success = true;
                Log("✅ 私有频道订阅成功:", data.arg.channel);
            } else {
                Log("❌ 私有频道订阅失败:", data.msg, "频道:", data.arg ? data.arg.channel : "未知");
            }
        } else if (data.arg && (data.arg.channel === "positions" || data.arg.channel === "account")) {
            result.isPrivateData = true;
            Log("🔑 收到私有数据推送:", data.arg.channel);
            
            if (data.data && data.data.length > 0) {
                Log("📊 数据详情 (" + data.arg.channel + "):", 
                    JSON.stringify(data.data[0]).substring(0, 80) + "...");
            } else {
                Log("📊 数据为空(正常 - 表示没有" + data.arg.channel + "变化)");
            }
        } else if (data.event === "error") {
            Log("❌ 服务器错误消息:", JSON.stringify(data));
        } else {
            Log("ℹ️ 其他消息类型:", data.event || "未知事件");
        }
        
    } catch (e) {
        // 非JSON消息处理
        if (message === "pong") {
            Log("💓 收到心跳响应: pong");
        } else if (message.length < 50) {
            Log("⚠️ 短消息:", message);
        } else {
            Log("⚠️ 非JSON消息:", message.substring(0, 30) + "...");
        }
    }
    
    return result;
}

function onexit() {    
    if (client_private) {
        var ret = client_private.close();
        Log("🔒 关闭私有频道连接!", ret);
    }
    Log("🔚 FMZ可用HMAC私有频道测试结束");
    Log("💡 如果测试成功,您可以将此代码集成到实盘策略中");
} 
全部留言
avatar of 发明者量化-小小梦
发明者量化-小小梦
exchange.HMAC已经由exchange.Encode代替了,为您的分享精神点赞。 > https://www.fmz.com/syntax-guide/fun/trade/exchange.encode ```js exchange.Encode("sha256", "string", "hex", "GET/realtime" + expires, "hex", "{{secretkey}}") ``` 可以这样引用exchange中的secretKey。
2025-06-20 21:37:50