diff --git a/backend/database/init.sql b/backend/database/init.sql index 6c33068..c45e0e3 100644 --- a/backend/database/init.sql +++ b/backend/database/init.sql @@ -114,6 +114,9 @@ INSERT INTO `trading_config` (`config_key`, `config_value`, `config_type`, `cate ('TRAILING_STOP_ACTIVATION', '0.01', 'number', 'strategy', '移动止损激活阈值(盈利1%后激活)'), ('TRAILING_STOP_PROTECT', '0.01', 'number', 'strategy', '移动止损保护利润(保护1%利润)'), +-- 持仓同步 +('POSITION_SYNC_INTERVAL', '300', 'number', 'scan', '持仓状态同步间隔(秒),默认5分钟,用于同步币安实际持仓与数据库状态'), + -- API配置 ('BINANCE_API_KEY', '', 'string', 'api', '币安API密钥'), diff --git a/backend/init_config.py b/backend/init_config.py index b7e3ad3..32b380e 100644 --- a/backend/init_config.py +++ b/backend/init_config.py @@ -51,11 +51,13 @@ def init_configs(): config_type = 'string' # 确定分类 - if 'POSITION' in key: + if key == 'TOP_N_SYMBOLS' or key == 'MAX_SCAN_SYMBOLS' or key == 'POSITION_SYNC_INTERVAL': + category = 'scan' + elif 'POSITION' in key and 'SYNC' not in key: category = 'position' elif 'RISK' in key or 'STOP' in key or 'PROFIT' in key: category = 'risk' - elif 'SCAN' in key or 'INTERVAL' in key or 'VOLUME' in key or 'TOP_N_SYMBOLS' in key or 'MAX_SCAN_SYMBOLS' in key: + elif 'SCAN' in key or 'INTERVAL' in key or 'VOLUME' in key: category = 'scan' else: category = 'strategy' diff --git a/trading_system/config.py b/trading_system/config.py index c6c8b02..aeab5ef 100644 --- a/trading_system/config.py +++ b/trading_system/config.py @@ -156,6 +156,7 @@ def _get_trading_config(): 'USE_TRAILING_STOP': True, 'TRAILING_STOP_ACTIVATION': 0.01, 'TRAILING_STOP_PROTECT': 0.01, + 'POSITION_SYNC_INTERVAL': 300, # 持仓状态同步间隔(秒),默认5分钟 } # 币安API配置(优先从数据库,回退到环境变量和默认值) diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index be81661..b5e5c7f 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -653,6 +653,79 @@ class PositionManager: f"发现 {len(missing_in_db)} 个持仓在币安存在但数据库中没有记录: " f"{', '.join(missing_in_db)} (可能是手动开仓)" ) + + # 为手动开仓的持仓创建数据库记录并启动监控 + for symbol in missing_in_db: + try: + # 获取币安持仓详情 + binance_position = next( + (p for p in binance_positions if p['symbol'] == symbol), + None + ) + if not binance_position: + continue + + position_amt = binance_position['positionAmt'] + entry_price = binance_position['entryPrice'] + quantity = abs(position_amt) + side = 'BUY' if position_amt > 0 else 'SELL' + + logger.info( + f"{symbol} [状态同步] 检测到手动开仓,创建数据库记录... " + f"({side} {quantity:.4f} @ {entry_price:.4f})" + ) + + # 创建数据库记录 + trade_id = Trade.create( + symbol=symbol, + side=side, + quantity=quantity, + entry_price=entry_price, + leverage=binance_position.get('leverage', 10), + entry_reason='manual_entry' # 标记为手动开仓 + ) + + logger.info(f"{symbol} [状态同步] ✓ 数据库记录已创建 (ID: {trade_id})") + + # 创建本地持仓记录(用于监控) + ticker = await self.client.get_ticker_24h(symbol) + current_price = ticker['price'] if ticker else entry_price + + # 计算止损止盈 + stop_loss_price = self.risk_manager.get_stop_loss_price(entry_price, side) + take_profit_price = self.risk_manager.get_take_profit_price(entry_price, side) + + position_info = { + 'symbol': symbol, + 'side': side, + 'quantity': quantity, + 'entryPrice': entry_price, + 'changePercent': 0, # 手动开仓,无法计算涨跌幅 + 'orderId': None, + 'tradeId': trade_id, + 'stopLoss': stop_loss_price, + 'takeProfit': take_profit_price, + 'initialStopLoss': stop_loss_price, + 'leverage': binance_position.get('leverage', 10), + 'entryReason': 'manual_entry', + 'atr': None, + 'maxProfit': 0.0, + 'trailingStopActivated': False + } + + self.active_positions[symbol] = position_info + + # 启动WebSocket监控 + if self._monitoring_enabled: + await self._start_position_monitoring(symbol) + logger.info(f"{symbol} [状态同步] ✓ 已启动实时监控") + + logger.info(f"{symbol} [状态同步] ✓ 手动开仓同步完成") + + except Exception as e: + logger.error(f"{symbol} [状态同步] ❌ 处理手动开仓失败: {e}") + import traceback + logger.error(f" 错误详情:\n{traceback.format_exc()}") logger.info("持仓状态同步完成") diff --git a/trading_system/strategy.py b/trading_system/strategy.py index 725dd11..3e93712 100644 --- a/trading_system/strategy.py +++ b/trading_system/strategy.py @@ -45,6 +45,7 @@ class TradingStrategy: self.position_manager = position_manager self.running = False self._monitoring_started = False # 是否已启动监控 + self._sync_task = None # 定期同步任务 async def execute_strategy(self): """ @@ -61,6 +62,10 @@ class TradingStrategy: except Exception as e: logger.warning(f"启动持仓监控失败: {e},将使用定时检查模式") + # 启动定期同步任务(独立于市场扫描) + self._sync_task = asyncio.create_task(self._periodic_sync_positions()) + logger.info("定期持仓同步任务已启动") + try: while self.running: # 1. 扫描市场,找出涨跌幅最大的前N个货币对 @@ -216,6 +221,14 @@ class TradingStrategy: logger.error(f"策略执行出错: {e}", exc_info=True) finally: self.running = False + # 停止定期同步任务 + if self._sync_task: + self._sync_task.cancel() + try: + await self._sync_task + except asyncio.CancelledError: + pass + logger.info("定期持仓同步任务已停止") # 停止所有持仓的WebSocket监控 try: await self.position_manager.stop_all_position_monitoring() @@ -366,6 +379,31 @@ class TradingStrategy: 'strength': signal_strength } + async def _periodic_sync_positions(self): + """ + 定期同步币安持仓状态(独立任务,不依赖市场扫描) + """ + sync_interval = config.TRADING_CONFIG.get('POSITION_SYNC_INTERVAL', 300) # 默认5分钟 + logger.info(f"定期持仓同步任务启动,同步间隔: {sync_interval}秒") + + while self.running: + try: + await asyncio.sleep(sync_interval) + + if not self.running: + break + + logger.debug("执行定期持仓状态同步...") + await self.position_manager.sync_positions_with_binance() + + except asyncio.CancelledError: + logger.info("定期持仓同步任务已取消") + break + except Exception as e: + logger.error(f"定期持仓同步任务出错: {e}") + # 出错后等待一段时间再继续 + await asyncio.sleep(60) + def stop(self): """停止策略""" self.running = False