""" Hyperliquid 真实 API 集成测试 ⚠️ 警告:此测试会使用真实 API 调用和真实订单! - 使用最小下单量(ETH,szDecimals=3) - 市价单会立即成交,产生实际盈亏 - 测试后自动清理所有订单和持仓 覆盖接口: - 账户状态查询 - 杠杆设置 - 持仓查询 - 市价开仓 - 止盈止损设置(TP limit 单 + SL trigger 单) - 止盈止损验证(读取挂单确认 TP 和 SL 都存在) - 市价平仓 运行方式: cd backend python3 tests/test_hyperliquid_live_integration.py """ import os import sys import time import traceback from datetime import datetime # 添加项目路径 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from dotenv import load_dotenv load_dotenv(os.path.join(os.path.dirname(__file__), '..', '..', '.env')) # ==================== 测试配置 ==================== TEST_SYMBOL = 'ETH' # ETH 精度好(szDecimals=3),手续费低 TEST_SIZE = 0.01 # 最小下单量 TEST_LEVERAGE = 5 # 测试杠杆倍数 class TestResult: """测试结果收集器""" def __init__(self): self.results = [] def record(self, name: str, passed: bool, detail: str = ""): self.results.append((name, passed, detail)) status = "✅ PASS" if passed else "❌ FAIL" print(f" {status}: {name}") if detail: print(f" {detail}") def summary(self): print(f"\n{'='*60}") print("测试结果汇总") print(f"{'='*60}") passed = sum(1 for _, p, _ in self.results if p) total = len(self.results) for name, p, detail in self.results: status = "✅" if p else "❌" line = f" {status} {name}" if not p and detail: line += f" — {detail}" print(line) print(f"\n 总计: {passed}/{total} 通过") print(f"{'='*60}") return passed == total # ==================== 测试函数 ==================== def test_account_state(service, r: TestResult): """查询账户状态""" try: state = service.get_account_state() av = state['account_value'] ab = state['available_balance'] r.record( "查询账户状态", av > 0, f"权益=${av:,.2f}, 可用=${ab:,.2f}" ) except Exception as e: r.record("查询账户状态", False, str(e)) def test_update_leverage(service, r: TestResult): """设置杠杆""" try: result = service.update_leverage(TEST_SYMBOL, TEST_LEVERAGE) r.record("设置杠杆", True, f"{TEST_SYMBOL} → {TEST_LEVERAGE}x") except Exception as e: r.record("设置杠杆", False, str(e)) def test_get_positions(service, r: TestResult): """查询持仓""" try: positions = service.get_open_positions() count = len(positions) r.record("查询持仓", True, f"当前活跃持仓: {count} 个") except Exception as e: r.record("查询持仓", False, str(e)) def test_market_order_with_tp_sl(service, r: TestResult): """市价开仓 → 设置止盈止损 → 验证 → 平仓""" opened = False try: # 0. 先清理已有持仓和挂单 try: service.cancel_all_orders(TEST_SYMBOL) except: pass try: pos = service.get_position_for_symbol(TEST_SYMBOL) if pos: service.market_close_position(TEST_SYMBOL) time.sleep(1) except: pass # 1. 获取当前价格 all_mids = service.info.all_mids() current_price = float(all_mids.get(TEST_SYMBOL, 0)) if current_price <= 0: r.record("获取当前价格", False, f"无法获取 {TEST_SYMBOL} 价格") return print(f"\n 当前 {TEST_SYMBOL}: ${current_price:,.2f}") # 2. 计算最小下单量 sz_decimals = service.get_sz_decimals(TEST_SYMBOL) import math size = max(math.floor(TEST_SIZE * (10 ** sz_decimals)) / (10 ** sz_decimals), 1 / (10 ** sz_decimals)) print(f" 下单量: {size} ({sz_decimals} 位精度)") # 3. 设置杠杆 service.update_leverage(TEST_SYMBOL, TEST_LEVERAGE) # 4. 市价开多 result = service.place_market_order( symbol=TEST_SYMBOL, is_buy=True, size=size, reduce_only=False ) if not result.get('success'): r.record("市价开仓", False, result.get('error', '未知错误')) return r.record("市价开仓", True, f"{TEST_SYMBOL} buy {size}") opened = True time.sleep(2) # 5. 验证持仓 position = service.get_position_for_symbol(TEST_SYMBOL) r.record("验证持仓存在", position is not None, f"size={position['size']}, entry=${position['entry_price']:,.2f}" if position else "未找到持仓") # 6. 设置止盈止损 tp_price = round(current_price * 1.02, 2) # +2% sl_price = round(current_price * 0.98, 2) # -2% print(f" 设置 TP=${tp_price:,.2f}, SL=${sl_price:,.2f}") tp_sl_result = service.set_tp_sl( symbol=TEST_SYMBOL, is_long=True, size=size, tp_price=tp_price, sl_price=sl_price ) tp_set = tp_sl_result.get('tp_set', False) sl_set = tp_sl_result.get('sl_set', False) errors = tp_sl_result.get('errors', []) detail = f"TP=${tp_price:,.2f}({'✅' if tp_set else '❌'}), SL=${sl_price:,.2f}({'✅' if sl_set else '❌'})" if errors: detail += f" errors={errors}" r.record("设置止盈止损", tp_set and sl_set, detail) # 7. 验证止盈止损挂单 time.sleep(1) tp_sl_prices = service.get_tp_sl_prices(TEST_SYMBOL) has_tp = tp_sl_prices.get('take_profit') is not None has_sl = tp_sl_prices.get('stop_loss') is not None r.record("验证 TP/SL 挂单", has_tp and has_sl, f"TP={tp_sl_prices.get('take_profit')}({'✅' if has_tp else '❌'}), " f"SL={tp_sl_prices.get('stop_loss')}({'✅' if has_sl else '❌'})") # 8. 取消止盈止损 try: service.cancel_tp_sl_orders(TEST_SYMBOL) except: pass time.sleep(1) # 9. 市价平仓 close_result = service.market_close_position(TEST_SYMBOL) r.record("市价平仓", close_result.get('success', False), close_result.get('error', f"成功")) if close_result.get('success'): opened = False time.sleep(2) # 10. 验证已平仓 position_after = service.get_position_for_symbol(TEST_SYMBOL) r.record("验证已平仓", position_after is None) except Exception as e: r.record("市价单流程异常", False, f"{e}\n{traceback.format_exc()}") finally: if opened: try: service.cancel_all_orders(TEST_SYMBOL) time.sleep(0.5) service.market_close_position(TEST_SYMBOL) print(" 🧹 已自动清理残留持仓") except Exception as cleanup_err: print(f" ⚠️ 清理失败,请手动检查: {cleanup_err}") def test_set_tp_sl_partial_failure(service, r: TestResult): """测试 set_tp_sl: 第一个失败不影响第二个""" # 这个测试验证我们的修复:独立的 try-except # 如果 TP 失败(例如价格为 0),SL 应该仍然被设置 opened = False try: # 先清理 try: service.cancel_all_orders(TEST_SYMBOL) except: pass pos = service.get_position_for_symbol(TEST_SYMBOL) if pos: service.market_close_position(TEST_SYMBOL) time.sleep(1) # 1. 市价开仓 sz_decimals = service.get_sz_decimals(TEST_SYMBOL) import math size = max(math.floor(TEST_SIZE * (10 ** sz_decimals)) / (10 ** sz_decimals), 1 / (10 ** sz_decimals)) service.update_leverage(TEST_SYMBOL, TEST_LEVERAGE) result = service.place_market_order( symbol=TEST_SYMBOL, is_buy=True, size=size, reduce_only=False ) if not result.get('success'): r.record("部分失败测试: 开仓", False, result.get('error', '未知')) return opened = True time.sleep(2) # 2. 设置一个有效的 SL(但故意不设置 TP → tp_price=None) all_mids = service.info.all_mids() current_price = float(all_mids.get(TEST_SYMBOL, 0)) sl_price = round(current_price * 0.98, 2) tp_sl_result = service.set_tp_sl( symbol=TEST_SYMBOL, is_long=True, size=size, tp_price=None, # 不设 TP sl_price=sl_price # 只设 SL ) sl_set = tp_sl_result.get('sl_set', False) r.record("部分设置测试 (仅 SL)", sl_set, f"sl_set={sl_set}, errors={tp_sl_result.get('errors', [])}") # 3. 验证 SL 挂单存在 time.sleep(1) tp_sl_prices = service.get_tp_sl_prices(TEST_SYMBOL) has_sl = tp_sl_prices.get('stop_loss') is not None r.record("验证 SL 挂单", has_sl, f"SL={tp_sl_prices.get('stop_loss')}") # 4. 清理 service.cancel_all_orders(TEST_SYMBOL) time.sleep(1) service.market_close_position(TEST_SYMBOL) opened = False except Exception as e: r.record("部分失败测试异常", False, f"{e}\n{traceback.format_exc()}") finally: if opened: try: service.cancel_all_orders(TEST_SYMBOL) time.sleep(0.5) service.market_close_position(TEST_SYMBOL) print(" 🧹 已自动清理残留持仓") except: pass # ==================== 主入口 ==================== def main(): print(f"\n{'='*60}") print(f" Hyperliquid 实盘接口集成测试") print(f"{'='*60}") print(f" 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f" 交易对: {TEST_SYMBOL}") print(f" 下单量: {TEST_SIZE}") print(f" 杠杆: {TEST_LEVERAGE}x") print(f"{'='*60}") r = TestResult() # 初始化 try: from app.services.hyperliquid_trading_service import HyperliquidTradingService service = HyperliquidTradingService() print(f" 钱包: {service.wallet_address[:10]}...") except Exception as e: print(f"\n❌ 初始化失败: {e}") traceback.print_exc() sys.exit(1) # ---- 基础测试 ---- print(f"\n{'─'*40}") print(" 基础接口") print(f"{'─'*40}") test_account_state(service, r) time.sleep(0.3) test_update_leverage(service, r) time.sleep(0.3) test_get_positions(service, r) time.sleep(0.3) # ---- 核心测试: 开仓 → TP/SL → 平仓 ---- print(f"\n{'─'*40}") print(" 开仓 → 止盈止损 → 验证 → 平仓") print(f"{'─'*40}") test_market_order_with_tp_sl(service, r) time.sleep(1) # ---- 边界测试: 部分设置 ---- print(f"\n{'─'*40}") print(" 部分设置测试") print(f"{'─'*40}") test_set_tp_sl_partial_failure(service, r) # ---- 汇总 ---- all_passed = r.summary() sys.exit(0 if all_passed else 1) if __name__ == '__main__': print("\n⚠️ 此测试会产生真实订单和手续费!") print(f" 使用 {TEST_SYMBOL} 最小量 {TEST_SIZE}") confirm = input("\n是否继续?(yes/no): ") if confirm.strip().lower() != 'yes': print("已取消") sys.exit(0) main()