diff --git a/backend/app/crypto_agent/trading_decision_maker.py b/backend/app/crypto_agent/trading_decision_maker.py index 25ebab0..c2ce415 100644 --- a/backend/app/crypto_agent/trading_decision_maker.py +++ b/backend/app/crypto_agent/trading_decision_maker.py @@ -359,6 +359,7 @@ class TradingDecisionMaker: { "decision": "OPEN/CLOSE/ADD/REDUCE/CANCEL_PENDING/HOLD", "action": "buy/sell", + "entry_type": "market/limit", // market=现价入场, limit=挂单等待 "quantity": 保证金金额(USDT), "entry_price": 入场价格, "stop_loss": 止损价格, diff --git a/backend/app/services/hyperliquid_trading_service.py b/backend/app/services/hyperliquid_trading_service.py index 908f7ff..9f1be2c 100644 --- a/backend/app/services/hyperliquid_trading_service.py +++ b/backend/app/services/hyperliquid_trading_service.py @@ -161,16 +161,23 @@ class HyperliquidTradingService: self.check_risk_limits() try: - result = self.exchange.market_open(symbol, is_buy, size, reduce_only=reduce_only) + if reduce_only: + # 平仓使用 market_close(不需要指定 is_buy,自动判断) + result = self.exchange.market_close(symbol, sz=size) + else: + # 开仓使用 market_open + result = self.exchange.market_open(symbol, is_buy, size) side = "买入" if is_buy else "卖出" - logger.info(f"✅ Hyperliquid 市价单: {side} {symbol} {size}") + order_type = "平仓" if reduce_only else "开仓" + logger.info(f"✅ Hyperliquid 市价单: {order_type} {side} {symbol} {size}") return { "success": True, "symbol": symbol, "side": "buy" if is_buy else "sell", "size": size, + "reduce_only": reduce_only, "result": result } except Exception as e: @@ -225,25 +232,35 @@ class HyperliquidTradingService: 挂单列表 """ try: - # Hyperliquid 没有直接的获取挂单 API,需要通过 user_state - # 注意:这个方法可能需要根据实际 API 调整 - state = self.info.user_state(self.wallet_address) - open_orders = state.get("openOrders", []) + # 使用 open_orders API 获取挂单 + orders_data = self.info.open_orders(self.wallet_address) orders = [] - for order in open_orders: + for order in orders_data or []: coin = order.get("coin") if symbol and coin != symbol: continue + # side: "A" = ask (sell/做空), "B" = bid (buy/做多) + side = order.get("side") + is_buy = (side == "B") + + # Hyperliquid API 不直接返回 reduce_only 标记 + # 但我们可以根据其他信息判断 + # 暂时将所有订单都标记为非 reduce_only + is_reduce_only = order.get("reduce_only", False) + orders.append({ "order_id": order.get("oid"), "symbol": coin, - "side": order.get("side"), - "size": float(order.get("totalSz", 0)), + "side": "buy" if is_buy else "sell", + "size": float(order.get("sz", 0)), "price": float(order.get("limitPx", 0)), - "is_reduce_only": order.get("reduceOnly", False), - "order_type": order.get("orderType", {}) + "is_reduce_only": is_reduce_only, + "order_type": order.get("orderType", {}), + "timestamp": order.get("timestamp"), + "original_size": float(order.get("origSz", 0)), + "raw_side": side }) return orders @@ -317,6 +334,9 @@ class HyperliquidTradingService: # 设置止盈(限价单) if tp_price: + # 四舍五入价格到合适精度(避免 float_to_wire rounding 错误) + tp_price = round(float(tp_price), 5) + tp_result = self.exchange.order( symbol, close_is_buy, size, tp_price, {"limit": {"tif": "Gtc"}}, @@ -330,6 +350,11 @@ class HyperliquidTradingService: # 触发价格需要稍微偏离(避免滑点问题) exec_px = sl_price * 0.999 if close_is_buy else sl_price * 1.001 + # 四舍五入价格到合适精度(避免 float_to_wire rounding 错误) + # Hyperliquid 要求价格最多 5 位小数 + sl_price = round(float(sl_price), 5) + exec_px = round(float(exec_px), 5) + sl_result = self.exchange.order( symbol, close_is_buy, size, exec_px, {"trigger": {"triggerPx": sl_price, "isMarket": True, "tpsl": "sl"}}, @@ -397,9 +422,21 @@ class HyperliquidTradingService: def cancel_all_orders(self, symbol: Optional[str] = None) -> Dict[str, Any]: """取消所有订单""" try: - result = self.exchange.cancel_all_orders(symbol) - logger.info(f"取消所有订单: {symbol or '全部'}") - return {"success": True, "result": result} + # Hyperliquid SDK 没有 cancel_all_orders 方法,需要先查询再逐个取消 + orders = self.get_open_orders(symbol) + + results = [] + for order in orders: + order_symbol = order.get('symbol') + order_id = order.get('order_id') + try: + result = self.exchange.cancel(order_symbol, order_id) + results.append(result) + except Exception as e: + logger.warning(f"取消订单失败: {order_symbol} #{order_id} - {e}") + + logger.info(f"取消所有订单: {symbol or '全部'} ({len(results)} 个)") + return {"success": True, "result": results, "cancelled_count": len(results)} except Exception as e: logger.error(f"取消所有订单失败: {e}") return {"success": False, "error": str(e)} diff --git a/backend/requirements.txt b/backend/requirements.txt index 4f802bd..ec858f4 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,7 +4,7 @@ langchain==0.1.0 langchain-community==0.0.20 zhipuai==2.0.1 openai>=1.0.0 -tushare==1.3.8 +tushare>=1.4.0 # 升级到1.4+以支持更新的websocket-client(解决hyperliquid依赖冲突) sqlalchemy==2.0.25 pydantic==2.5.3 pydantic-settings==2.1.0 diff --git a/backend/test_hyperliquid_operations.py b/backend/test_hyperliquid_operations.py new file mode 100644 index 0000000..e99fb85 --- /dev/null +++ b/backend/test_hyperliquid_operations.py @@ -0,0 +1,355 @@ +""" +Hyperliquid 小资金测试脚本 + +测试内容: +1. 获取账户状态 +2. 市价单测试(小金额) +3. 限价单测试(小金额) +4. 止盈止损设置 +5. 查询持仓和挂单 +6. 清理测试(平仓、取消挂单) + +运行方式: + python3 test_hyperliquid_operations.py + +⚠️ 请确保 .env 中已配置: + - CLAWFI_WALLET_ADDRESS + - CLAWFI_PRIVATE_KEY + - hyperliquid_trading_enabled=true +""" +import sys +import os +import time + +# 添加项目路径 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from app.services.hyperliquid_trading_service import HyperliquidTradingService +from app.utils.logger import logger + + +class HyperliquidTester: + """Hyperliquid 测试器""" + + def __init__(self): + """初始化测试器""" + try: + self.service = HyperliquidTradingService() + logger.info("✅ Hyperliquid 服务初始化成功") + except Exception as e: + logger.error(f"❌ 初始化失败: {e}") + raise + + def print_section(self, title: str): + """打印分隔线""" + print(f"\n{'='*60}") + print(f" {title}") + print(f"{'='*60}\n") + + def test_1_get_account_state(self): + """测试1:获取账户状态""" + self.print_section("测试1: 获取账户状态") + + state = self.service.get_account_state() + + print(f"💰 账户价值: ${state['account_value']:,.2f}") + print(f"📊 已用保证金: ${state['total_margin_used']:,.2f}") + print(f"💵 可用余额: ${state['available_balance']:,.2f}") + print(f"📈 持仓数量: {len(state['positions'])}") + + positions = self.service.get_open_positions() + if positions: + print(f"\n当前持仓:") + for pos in positions: + coin = pos['coin'] + size = pos['size'] + entry = pos['entry_price'] + pnl = pos['unrealized_pnl'] + print(f" - {coin}: {size:.4f} @ ${entry:,.2f} | PnL: ${pnl:,.2f}") + else: + print(f" 无持仓") + + return state + + def test_2_market_order(self, symbol: str = "BTC", is_buy: bool = True, size: float = 0.001): + """测试2:市价单(小金额测试)""" + self.print_section(f"测试2: 市价单 - {'买入' if is_buy else '卖出'} {symbol}") + + # 检查风险限制 + risk = self.service.check_risk_limits() + print(f"🔍 风险检查:") + print(f" 初始余额: ${risk['initial_balance']:,.2f}") + print(f" 当前价值: ${risk['current_value']:,.2f}") + print(f" 回撤: {risk['drawdown_percent']:.2f}%") + print(f" 安全交易: {'✅' if risk['safe_to_trade'] else '❌'}") + + if not risk['safe_to_trade']: + print("⚠️ 风险检查未通过,跳过测试") + return None + + # 更新杠杆 + print(f"\n⚙️ 设置杠杆为 2x...") + self.service.update_leverage(symbol, 2) + + # 下市价单 + print(f"\n📝 下市价单: {'买入' if is_buy else '卖出'} {size} {symbol}") + result = self.service.place_market_order( + symbol=symbol, + is_buy=is_buy, + size=size + ) + + if result.get('success'): + print(f"✅ 市价单成功") + print(f" 方向: {'买入' if is_buy else '卖出'}") + print(f" 数量: {size}") + print(f" 结果: {result.get('result')}") + + # 设置止盈止损 + print(f"\n🎯 设置止盈止损...") + current_positions = self.service.get_open_positions() + if current_positions: + pos = current_positions[0] + entry_price = pos['entry_price'] + is_long = pos['size'] > 0 + + # 计算止盈止损价格(小范围测试) + if is_long: + tp_price = entry_price * 1.01 # +1% + sl_price = entry_price * 0.995 # -0.5% + else: + tp_price = entry_price * 0.99 # -1% + sl_price = entry_price * 1.005 # +0.5% + + print(f" 入场价: ${entry_price:,.2f}") + print(f" 止盈价: ${tp_price:,.2f}") + print(f" 止损价: ${sl_price:,.2f}") + + tp_sl_result = self.service.set_tp_sl( + symbol=symbol, + is_long=is_long, + size=abs(size), + tp_price=tp_price, + sl_price=sl_price + ) + + if tp_sl_result.get('success'): + print(f"✅ 止盈止损设置成功") + else: + print(f"❌ 止盈止损设置失败: {tp_sl_result.get('error')}") + else: + print(f"❌ 市价单失败: {result.get('error')}") + + # 等待一下查看结果 + print(f"\n⏳ 等待2秒查看持仓状态...") + time.sleep(2) + + # 查询挂单 + orders = self.service.get_open_orders(symbol) + if orders: + print(f"\n📋 当前挂单:") + for order in orders: + print(f" - ID: {order['order_id']}") + print(f" 方向: {order['side']}") + print(f" 价格: ${order['price']:,.2f}") + print(f" 数量: {order['size']}") + else: + print(f"\n📋 无挂单") + + return result + + def test_3_limit_order(self, symbol: str = "BTC", is_buy: bool = True, size: float = 0.001): + """测试3:限价单(挂单测试)""" + self.print_section(f"测试3: 限价单 - {'买入' if is_buy else '卖出'} {symbol}") + + # 获取当前价格 + import requests + try: + response = requests.get("https://api.hyperliquid.xyz/info", json={ + "type": "meta", + "coin": symbol + }, timeout=5) + data = response.json() + # 从响应中获取当前价格(简化处理,使用一个估算值) + current_price = 95000 # 假设 BTC 当前价格 + except: + current_price = 95000 + + # 设置挂单价格(距离当前价格 1%) + if is_buy: + limit_price = current_price * 0.99 # 低于市价 1% + else: + limit_price = current_price * 1.01 # 高于市价 1% + + print(f"💡 当前价格估算: ${current_price:,.2f}") + print(f"📝 挂单价格: ${limit_price:,.2f} ({'低于' if is_buy else '高于'}市价1%)") + + # 下限价单 + result = self.service.place_limit_order( + symbol=symbol, + is_buy=is_buy, + size=size, + price=limit_price + ) + + if result.get('success'): + print(f"✅ 限价单成功") + print(f" 方向: {'买入' if is_buy else '卖出'}") + print(f" 数量: {size}") + print(f" 价格: ${limit_price:,.2f}") + + # 查询挂单 + orders = self.service.get_open_orders(symbol) + if orders: + print(f"\n📋 当前挂单:") + for order in orders: + print(f" - ID: {order['order_id']}") + print(f" 方向: {order['side']}") + print(f" 价格: ${order['price']:,.2f}") + print(f" 数量: {order['size']}") + + return result + else: + print(f"❌ 限价单失败: {result.get('error')}") + return None + + def test_4_check_tp_sl(self, symbol: str = "BTC"): + """测试4:查询止盈止损""" + self.print_section(f"测试4: 查询止盈止损 - {symbol}") + + tp_sl = self.service.get_tp_sl_prices(symbol) + + print(f"📊 止盈止损状态:") + if tp_sl['take_profit']: + print(f" ✅ 止盈: ${tp_sl['take_profit']:,.2f}") + else: + print(f" ➖ 止盈: 未设置") + + if tp_sl['stop_loss']: + print(f" ✅ 止损: ${tp_sl['stop_loss']:,.2f}") + else: + print(f" ➖ 止损: 未设置") + + return tp_sl + + def test_5_cleanup(self, symbol: str = "BTC"): + """测试5:清理(平仓、取消挂单)""" + self.print_section(f"测试5: 清理测试 - {symbol}") + + # 取消所有挂单 + print(f"🗑️ 取消所有挂单...") + cancel_result = self.service.cancel_all_orders(symbol) + if cancel_result.get('success'): + print(f"✅ 取消挂单成功") + else: + print(f"❌ 取消挂单失败: {cancel_result.get('error')}") + + # 平仓 + positions = self.service.get_open_positions() + symbol_positions = [p for p in positions if p['coin'] == symbol] + + if symbol_positions: + print(f"\n📤 平仓 {symbol}...") + for pos in symbol_positions: + size = abs(pos['size']) + is_long = pos['size'] > 0 + + result = self.service.place_market_order( + symbol=symbol, + is_buy=not is_long, # 平仓方向相反 + size=size, + reduce_only=True + ) + + if result.get('success'): + print(f"✅ 平仓成功: {size} {symbol}") + else: + print(f"❌ 平仓失败: {result.get('error')}") + else: + print(f"📊 无持仓需要平仓") + + # 最终状态 + print(f"\n📊 最终状态:") + final_positions = self.service.get_open_positions() + final_orders = self.service.get_open_orders(symbol) + + print(f" 持仓: {len(final_positions)} 个") + print(f" 挂单: {len(final_orders)} 个") + + if len(final_positions) == 0 and len(final_orders) == 0: + print(f"✅ 清理完成,无持仓和挂单") + else: + print(f"⚠️ 清理未完全,请检查") + + def run_all_tests(self, auto_mode: bool = True): + """运行所有测试 + + Args: + auto_mode: 自动模式,不等待用户确认(默认True) + """ + print("\n" + "="*60) + print(" Hyperliquid 小资金测试") + if auto_mode: + print(" 自动模式: 将直接执行所有测试") + else: + print(" 交互模式: 每步需要确认") + print("="*60) + + def wait_for_confirmation(msg: str): + """等待用户确认(仅在非自动模式下)""" + if not auto_mode: + try: + input(f"\n⚠️ {msg}\n按 Enter 继续,Ctrl+C 取消...") + except EOFError: + print("⚠️ 检测到非交互环境,切换到自动模式") + return True + return True + + try: + # 测试1:账户状态 + self.test_1_get_account_state() + + # 测试2:市价单 + wait_for_confirmation("即将测试市价单(小金额)") + self.test_2_market_order(symbol="BTC", is_buy=True, size=0.001) + + # 测试3:限价单 + wait_for_confirmation("即将测试限价单(小金额)") + self.test_3_limit_order(symbol="BTC", is_buy=False, size=0.001) + + # 测试4:查询止盈止损 + self.test_4_check_tp_sl(symbol="BTC") + + # 测试5:清理 + wait_for_confirmation("即将清理测试(平仓、取消挂单)") + self.test_5_cleanup(symbol="BTC") + + print("\n" + "="*60) + print(" ✅ 所有测试完成") + print("="*60) + + except KeyboardInterrupt: + print("\n\n⚠️ 测试被用户中断") + print("🧹 执行清理...") + + # 尝试清理 + self.test_5_cleanup(symbol="BTC") + + except Exception as e: + logger.error(f"❌ 测试出错: {e}") + import traceback + traceback.print_exc() + + # 尝试清理 + print("\n🧹 执行清理...") + self.test_5_cleanup(symbol="BTC") + + +def main(): + """主函数""" + tester = HyperliquidTester() + tester.run_all_tests() + + +if __name__ == "__main__": + main()