""" 实盘交易服务 - Bitget 合约交易 提供与模拟交易服务类似的接口,但执行的是真实交易 """ import uuid from datetime import datetime, timedelta from typing import Dict, Any, List, Optional from app.models.real_trading import RealOrder from app.models.paper_trading import OrderStatus, OrderSide, SignalGrade, EntryType from app.services.db_service import db_service from app.config import get_settings from app.utils.logger import logger class RealTradingService: """实盘交易服务""" def __init__(self): """初始化实盘交易服务""" self.settings = get_settings() self.active_orders: Dict[str, RealOrder] = {} # 内存缓存活跃订单 # 实盘交易配置 self.max_single_position = self.settings.real_trading_max_single_position self.max_total_ratio = self.settings.real_trading_max_total_ratio self.default_leverage = self.settings.real_trading_default_leverage self.risk_per_trade = self.settings.real_trading_risk_per_trade self.max_orders = self.settings.real_trading_max_orders # 获取交易 API (使用 CCXT SDK 版本) from app.services.bitget_trading_api_sdk import get_bitget_trading_api self.trading_api = get_bitget_trading_api() if not self.trading_api: logger.error("Bitget 交易 API 未初始化,实盘交易功能不可用") return # 确保表已创建 self._ensure_table_exists() # 加载活跃订单 self._load_active_orders() logger.info(f"实盘交易服务初始化完成(最大单笔: ${self.max_single_position}," f"杠杆: {self.default_leverage}x,最大持仓: {self.max_orders})") def _ensure_table_exists(self): """确保数据表已创建""" from app.models.real_trading import RealOrder from app.models.database import Base Base.metadata.create_all(bind=db_service.engine) def _load_active_orders(self): """从数据库加载活跃订单""" db = db_service.get_session() try: orders = db.query(RealOrder).filter( RealOrder.status.in_([OrderStatus.PENDING, OrderStatus.OPEN]) ).all() for order in orders: self.active_orders[order.order_id] = order logger.info(f"实盘交易: 加载了 {len(orders)} 个活跃订单") finally: db.close() def create_order_from_signal(self, signal: Dict[str, Any], current_price: float = None) -> Dict[str, Any]: """ 从信号创建实盘订单 Args: signal: LLM 分析信号 current_price: 当前价格 Returns: 创建结果 """ if not self.trading_api: return { 'success': False, 'message': '交易 API 未初始化' } db = db_service.get_session() result = { 'success': False, 'message': '', 'order_id': None } try: # 检查实盘交易是否启用 if not self.settings.real_trading_enabled: return { 'success': False, 'message': '实盘交易未启用,请检查配置 REAL_TRADING_ENABLED=true' } # 获取信号信息 symbol = signal.get('symbol') side = signal.get('side') # 'long' or 'short' entry_type = signal.get('entry_type', 'market') # 'market' or 'limit' entry_price = signal.get('entry_price') stop_loss = signal.get('stop_loss') take_profit = signal.get('take_profit') grade = signal.get('grade', 'D') confidence = signal.get('confidence', 0) # 验证必需参数 if not all([symbol, side, stop_loss, take_profit]): return { 'success': False, 'message': '信号缺少必需参数' } # 获取当前价格 if not current_price: from app.services.bitget_service import bitget_service current_price = bitget_service.get_current_price(symbol) if not current_price: return { 'success': False, 'message': '无法获取当前价格' } # 设置入场价 if entry_type == 'market': entry_price = current_price elif not entry_price: entry_price = current_price # 风险检查 - 检查订单数量 if len(self.active_orders) >= self.max_orders: return { 'success': False, 'message': f'已达最大持仓数 {self.max_orders}' } # 风险检查 - 检查账户余额 balance_info = self._check_balance_risk(symbol, entry_price, stop_loss) if not balance_info['can_trade']: return { 'success': False, 'message': balance_info['reason'] } # 创建订单对象 order_id = str(uuid.uuid4()) order = RealOrder( order_id=order_id, symbol=symbol, side=OrderSide.LONG if side == 'long' else OrderSide.SHORT, entry_price=entry_price, stop_loss=stop_loss, take_profit=take_profit, quantity=balance_info['quantity'], leverage=self.default_leverage, signal_grade=SignalGrade[grade.upper()] if grade.upper() in ['A', 'B', 'C', 'D'] else SignalGrade.D, signal_type=signal.get('signal_type', 'swing'), confidence=confidence, trend=signal.get('trend'), entry_type=EntryType.MARKET if entry_type == 'market' else EntryType.LIMIT, status=OrderStatus.PENDING ) db.add(order) db.commit() db.refresh(order) # 调用 Bitget API 下单 exchange_result = self._place_bitget_order(order, current_price) if exchange_result['success']: # 更新订单状态 order.exchange_order_id = exchange_result.get('order_id') order.client_order_id = exchange_result.get('client_order_id') order.filled_price = exchange_result.get('filled_price', entry_price) order.filled_at = datetime.now() order.status = OrderStatus.OPEN db.commit() # 添加到内存缓存 self.active_orders[order_id] = order result['success'] = True result['message'] = '实盘订单创建成功' result['order_id'] = order_id result['exchange_order_id'] = exchange_result.get('order_id') logger.info(f"✅ 实盘订单创建成功: {symbol} {side} ${entry_price} -> {exchange_result.get('order_id')}") else: # 下单失败,删除记录 db.delete(order) db.commit() result['message'] = f"下单失败: {exchange_result.get('message', '未知错误')}" logger.error(f"❌ 实盘订单下单失败: {exchange_result.get('message')}") except Exception as e: db.rollback() result['message'] = f"创建订单失败: {str(e)}" logger.error(f"创建实盘订单失败: {e}") finally: db.close() return result def _check_balance_risk(self, symbol: str, entry_price: float, stop_loss: float) -> Dict: """ 检查余额和风险 Returns: {'can_trade': bool, 'reason': str, 'quantity': float} """ try: # 获取账户余额 balance_info = self.trading_api.get_balance() usdt_balance = balance_info.get('USDT', {}) available = float(usdt_balance.get('available', 0)) if available < 10: return { 'can_trade': False, 'reason': f'可用余额不足 (${available:.2f})', 'quantity': 0 } # 计算风险 risk_amount = entry_price - stop_loss risk_percent = (risk_amount / entry_price) * 100 # 根据风险计算仓位 max_quantity = (available * self.risk_per_trade) / (risk_percent / 100) # 限制单笔最大仓位 max_quantity = min(max_quantity, self.max_single_position) # 最小仓位限制 min_quantity = 10 if max_quantity < min_quantity: return { 'can_trade': False, 'reason': f'风险过高,计算仓位 ${max_quantity:.2f} 小于最小值 ${min_quantity}', 'quantity': 0 } return { 'can_trade': True, 'reason': '', 'quantity': round(max_quantity, 2) } except Exception as e: logger.error(f"检查余额风险失败: {e}") return { 'can_trade': False, 'reason': f'风险检查失败: {str(e)}', 'quantity': 0 } def _place_bitget_order(self, order: RealOrder, current_price: float) -> Dict: """ 调用 Bitget API 下单 (使用 CCXT SDK) Returns: {'success': bool, 'order_id': str, 'client_order_id': str, 'filled_price': float, 'message': str} """ try: # CCXT 使用标准的 buy/sell 方向 # 对于合约:buy = 开多/平空,sell = 开空/平多 # 这里我们简化为:long = buy, short = sell side_map = { OrderSide.LONG: 'buy', OrderSide.SHORT: 'sell' } # 映射订单类型 order_type = 'market' if order.entry_type == EntryType.MARKET else 'limit' # 计算合约数量(张数) # Bitget U本位合约:1张 = 1 USDT(大多数情况) size = order.quantity # 简化处理,实际应该根据合约规格计算 # 生成自定义订单ID client_order_id = f"real_{order.order_id[:8]}" # 调用 API 下单 result = self.trading_api.place_order( symbol=order.symbol, side=side_map[order.side], order_type=order_type, size=size, price=order.entry_price if order_type == 'limit' else None, client_order_id=client_order_id ) if result: # CCXT 返回的订单对象格式 # result['id'] 是交易所订单ID # result['price'] 是委托价格 # result['average'] 是成交均价(如果已成交) order_id = result.get('id') filled_price = float(result.get('average', 0)) or current_price return { 'success': True, 'order_id': order_id, 'client_order_id': client_order_id, 'filled_price': filled_price } else: return { 'success': False, 'message': 'API 调用失败' } except Exception as e: logger.error(f"Bitget 下单失败: {e}") return { 'success': False, 'message': str(e) } def get_active_orders(self) -> List[Dict]: """获取活跃订单列表""" return [order.to_dict() for order in self.active_orders.values()] def get_order(self, order_id: str) -> Optional[Dict]: """获取指定订单""" order = self.active_orders.get(order_id) if order: return order.to_dict() return None def sync_positions_from_exchange(self) -> List[Dict]: """ 从交易所同步持仓状态 Returns: 同步后的持仓列表 """ if not self.trading_api: return [] try: # 获取交易所实际持仓 positions = self.trading_api.get_position() logger.info(f"从交易所同步了 {len(positions)} 个持仓") return positions except Exception as e: logger.error(f"同步持仓失败: {e}") return [] def get_account_status(self) -> Dict: """获取账户状态""" if not self.trading_api: return { 'current_balance': 0, 'used_margin': 0, 'total_position_value': 0, 'available': 0 } try: balance_info = self.trading_api.get_balance() usdt_info = balance_info.get('USDT', {}) available = float(usdt_info.get('available', 0)) frozen = float(usdt_info.get('frozen', 0)) locked = float(usdt_info.get('locked', 0)) # 计算持仓价值 total_position_value = 0 for order in self.active_orders.values(): if order.status == OrderStatus.OPEN: total_position_value += order.quantity return { 'current_balance': available + frozen + locked, 'available': available, 'used_margin': frozen + locked, 'total_position_value': total_position_value } except Exception as e: logger.error(f"获取账户状态失败: {e}") return { 'current_balance': 0, 'used_margin': 0, 'total_position_value': 0, 'available': 0 } # 全局实例 _real_trading_service: Optional[RealTradingService] = None def get_real_trading_service() -> Optional[RealTradingService]: """ 获取实盘交易服务实例(单例) Returns: RealTradingService 实例或 None(如果未配置或未启用) """ global _real_trading_service if _real_trading_service: return _real_trading_service settings = get_settings() # 检查是否启用实盘交易 if not settings.real_trading_enabled: return None # 检查是否配置了 API Key if not settings.bitget_api_key or not settings.bitget_api_secret: logger.warning("Bitget API Key 未配置,实盘交易功能不可用") return None _real_trading_service = RealTradingService() return _real_trading_service