"""
交易策略模块 - 实现交易逻辑和优化
"""
import asyncio
import logging
import time
from datetime import datetime, timezone, timedelta
from typing import List, Dict, Optional
try:
    from .binance_client import BinanceClient
    from .market_scanner import MarketScanner
    from .risk_manager import RiskManager
    from .position_manager import PositionManager
    from .signal_filters import SignalFilterContext, apply_signal_filters
    from . import config
    from .symbol_policy import resolve_symbol_trading_policy
    from .shadow_mode import evaluate_shadow_mode, invalidate_cache as shadow_invalidate_cache
except ImportError:
    from binance_client import BinanceClient
    from market_scanner import MarketScanner
    from risk_manager import RiskManager
    from position_manager import PositionManager
    from signal_filters import SignalFilterContext, apply_signal_filters
    import config
    from symbol_policy import resolve_symbol_trading_policy
    from shadow_mode import evaluate_shadow_mode, invalidate_cache as shadow_invalidate_cache

logger = logging.getLogger(__name__)


class TradingStrategy:
    """交易策略类"""
    
    def __init__(
        self,
        client: BinanceClient,
        scanner: MarketScanner,
        risk_manager: RiskManager,
        position_manager: PositionManager,
        recommender=None  # 推荐器（可选）
    ):
        """
        初始化交易策略
        
        Args:
            client: 币安客户端
            scanner: 市场扫描器
            risk_manager: 风险管理器
            position_manager: 仓位管理器
            recommender: 推荐器（可选，如果提供则自动生成推荐）
        """
        self.client = client
        self.scanner = scanner
        self.risk_manager = risk_manager
        self.position_manager = position_manager
        self.recommender = recommender  # 推荐器
        self.running = False
        self._monitoring_started = False  # 是否已启动监控
        self._sync_task = None  # 定期同步任务
    
    async def execute_strategy(self):
        """
        执行交易策略
        """
        self.running = True
        self.last_scan_trigger_ts = int(time.time())  # 初始化触发时间戳
        logger.info("交易策略开始执行...")
        
        # 先同步持仓（补建「币安有、DB 无」的系统单），再启动监控，保证补建后的持仓会被监控
        if not self._monitoring_started:
            try:
                await self.position_manager.sync_positions_with_binance()
                logger.info("启动前持仓同步已完成，开始启动持仓监控...")
            except Exception as e:
                logger.warning(f"启动前持仓同步失败: {e}，继续启动监控")
            try:
                await self.position_manager.start_all_position_monitoring()
                self._monitoring_started = True
            except Exception as e:
                logger.warning(f"启动持仓监控失败: {e}，将使用定时检查模式")
        
        # 启动定期同步任务（独立于市场扫描）
        self._sync_task = asyncio.create_task(self._periodic_sync_positions())
        logger.info("定期持仓同步任务已启动")

        # 多账号错峰：避免多进程同时扫描导致 CPU 打满（2 CPU 4G 等低配服务器）
        import os
        import random
        stagger_enabled = config.TRADING_CONFIG.get('SCAN_STAGGER_BY_ACCOUNT', False)  # 全局开关：单账号关、多账号开
        stagger_random = config.TRADING_CONFIG.get('SCAN_STAGGER_RANDOM', True)
        account_id = int(os.getenv('ATS_ACCOUNT_ID') or os.getenv('ACCOUNT_ID') or 1)
        if stagger_enabled:
            if stagger_random:
                min_sec = max(0, int(config.TRADING_CONFIG.get('SCAN_STAGGER_MIN_SEC', 10) or 10))
                max_sec = max(min_sec, int(config.TRADING_CONFIG.get('SCAN_STAGGER_MAX_SEC', 120) or 120))
                delay = random.randint(min_sec, max_sec)
                logger.info(f"多账号错峰：account_id={account_id}，随机延迟 {delay} 秒后开始首次扫描（范围 {min_sec}～{max_sec}s）")
            else:
                stagger_sec = int(config.TRADING_CONFIG.get('SCAN_STAGGER_SEC', 60) or 60)
                delay = (account_id - 1) * stagger_sec if account_id > 1 else 0
                if delay > 0:
                    logger.info(f"多账号错峰：account_id={account_id}，延迟 {delay} 秒后开始首次扫描")
            if delay > 0:
                await asyncio.sleep(delay)

        try:
            while self.running:
                # 0. 定期从Redis重新加载配置（确保配置修改能即时生效）
                # 每次循环开始时从Redis重新加载，确保使用最新配置
                try:
                    if config._config_manager:
                        config._config_manager.reload_from_redis()
                        config.TRADING_CONFIG = config._get_trading_config()
                        logger.debug("配置已从Redis重新加载")
                    try:
                        shadow_invalidate_cache()
                    except Exception:
                        pass
                except Exception as e:
                    logger.warning(f"从Redis重新加载配置失败: {e}")
                
                # 1. 扫描市场，找出涨跌幅最大的前N个货币对
                top_symbols = await self.scanner.scan_market()
                
                if not top_symbols:
                    logger.warning("未找到符合条件的交易对，等待下次扫描...")
                    await self._wait_for_next_scan(config.TRADING_CONFIG.get('SCAN_INTERVAL', 3600))
                    continue
                
                # 2. 对每个交易对执行交易逻辑（使用技术指标）
                for symbol_info in top_symbols:
                    if not self.running:
                        break
                    
                    symbol = symbol_info['symbol']
                    change_percent = symbol_info['changePercent']
                    direction = symbol_info['direction']
                    market_regime = symbol_info.get('marketRegime') or 'unknown'
                    # unknown 表示 K 线不足或未判定趋势/震荡，属正常；仅生成推荐，不满足 ONLY_TRENDING 时不自动下单
                    logger.info(
                        f"处理交易对: {symbol} "
                        f"({direction} {change_percent:.2f}%, 市场状态: {market_regime})"
                    )
                    
                    # 使用技术指标判断交易信号（高胜率策略）
                    trade_signal = await self._analyze_trade_signal(symbol_info)
                    
                    # 记录交易信号到数据库（只有当有明确方向时才记录）
                    signal_direction = trade_signal.get('direction')
                    if signal_direction:  # 只有当方向不为空时才记录
                        try:
                            import sys
                            from pathlib import Path
                            project_root = Path(__file__).parent.parent
                            backend_path = project_root / 'backend'
                            if backend_path.exists():
                                sys.path.insert(0, str(backend_path))
                                from database.models import TradingSignal
                                TradingSignal.create(
                                    symbol=symbol,
                                    signal_direction=signal_direction,
                                    signal_strength=trade_signal.get('strength', 0),
                                    signal_reason=trade_signal.get('reason', ''),
                                    rsi=symbol_info.get('rsi'),
                                    macd_histogram=symbol_info.get('macd', {}).get('histogram') if symbol_info.get('macd') else None,
                                    market_regime=symbol_info.get('marketRegime')
                                )
                        except Exception as e:
                            logger.debug(f"记录交易信号失败: {e}")
                    
                    # 自动生成推荐（即使不满足自动交易条件，只要信号强度>=5就生成推荐）
                    # 注意：推荐生成不检查是否已有持仓，因为推荐是供手动参考的
                    if self.recommender and signal_direction and trade_signal.get('strength', 0) >= 5:
                        try:
                            await self.recommender._create_recommendation(symbol_info, trade_signal)
                            logger.debug(f"✓ {symbol} 自动生成推荐成功 (信号强度: {trade_signal.get('strength', 0)}/10)")
                        except Exception as e:
                            logger.warning(f"自动生成推荐失败 {symbol}: {e}")

                    # 用户风险旋钮：自动交易总开关（关闭则只生成推荐）
                    auto_enabled = bool(config.TRADING_CONFIG.get("AUTO_TRADE_ENABLED", True))
                    if not auto_enabled:
                        logger.info(f"{symbol} 自动交易已关闭（AUTO_TRADE_ENABLED=false），跳过自动下单（推荐已生成）")
                        continue

                    # 提升胜率：可配置的“仅 trending 自动交易”过滤
                    only_trending = bool(config.TRADING_CONFIG.get("AUTO_TRADE_ONLY_TRENDING", True))
                    allow_ranging = bool(config.TRADING_CONFIG.get("AUTO_TRADE_ALLOW_RANGING", False))
                    allow_unknown = bool(config.TRADING_CONFIG.get("AUTO_TRADE_ALLOW_UNKNOWN", False))
                    if only_trending and market_regime != 'trending':
                        logger.info(
                            f"{symbol} 市场状态={market_regime}，跳过自动交易（仅生成推荐）"
                            f"｜原因：AUTO_TRADE_ONLY_TRENDING=true"
                        )
                        continue
                    if not only_trending:
                        if market_regime == 'ranging' and not allow_ranging:
                            logger.info(
                                f"{symbol} 市场状态={market_regime}，跳过自动交易（仅生成推荐）"
                                f"｜原因：AUTO_TRADE_ALLOW_RANGING=false"
                            )
                            continue
                        if market_regime not in ('trending', 'ranging') and not allow_unknown:
                            logger.info(
                                f"{symbol} 市场状态={market_regime}，跳过自动交易（仅生成推荐）"
                                f"｜原因：AUTO_TRADE_ALLOW_UNKNOWN=false"
                            )
                            continue
                    
                    # 检查是否应该自动交易（已有持仓则跳过自动交易，但推荐已生成）
                    if not await self.risk_manager.should_trade(symbol, change_percent):
                        continue
                    
                    # 优化：结合成交量确认
                    if not await self._check_volume_confirmation(symbol_info):
                        logger.info(f"{symbol} 成交量确认失败，跳过")
                        continue
                    
                    if not trade_signal['should_trade']:
                        logger.info(
                            f"{symbol} 技术指标分析: {trade_signal['reason']}, 跳过自动交易"
                        )
                        continue

                    # 统一的信号过滤插件层（第一阶段：波动率等「进一步收紧」过滤）
                    try:
                        sf_ctx = SignalFilterContext(
                            symbol=symbol,
                            symbol_info=symbol_info,
                            trade_signal=trade_signal,
                            market_regime=market_regime,
                        )
                        sf_result = await apply_signal_filters(sf_ctx)
                        if not sf_result.accepted:
                            # 将过滤原因拼接到 trade_signal 的 reason 里，便于后续分析
                            extra_reason = " | ".join(sf_result.reasons) if sf_result.reasons else "信号过滤未通过"
                            base_reason = trade_signal.get("reason") or ""
                            combined_reason = f"{base_reason} | {extra_reason}" if base_reason else extra_reason
                            trade_signal["reason"] = combined_reason
                            logger.info(f"{symbol} 信号过滤未通过，跳过自动交易: {combined_reason}")
                            continue
                        elif sf_result.reasons:
                            # 若过滤层仅追加了评语（accepted=True），也拼接到 reason 便于排查
                            extra_reason = " | ".join(sf_result.reasons)
                            base_reason = trade_signal.get("reason") or ""
                            trade_signal["reason"] = f"{base_reason} | {extra_reason}" if base_reason else extra_reason
                    except Exception as e:
                        logger.warning(f"{symbol} 应用信号过滤插件时出错（忽略，回退为原逻辑）: {e}")

                    # 影子模式半自动化：优化黑名单 / 差时段（跳过自动开仓；推荐已生成）
                    shadow_mode_result = self._check_shadow_mode_filters(symbol)
                    if not shadow_mode_result.get("allowed", True):
                        sm_ap = shadow_mode_result.get("shadow_mode_applied") or {}
                        if sm_ap.get("blacklist_check") == "blocked":
                            br = shadow_mode_result.get("skip_reason") or ""
                            logger.info(f"{symbol} 命中优化黑名单，跳过自动开仓（建议：{br}）")
                        elif sm_ap.get("hour_check") == "blocked":
                            bj_h = timezone(timedelta(hours=8))
                            hh = datetime.now(bj_h).hour
                            avg_txt = shadow_mode_result.get("log_avg_pnl", "")
                            logger.info(
                                f"{symbol} 命中差时段 ({hh}:00)，跳过自动开仓（平均亏损：{avg_txt} USDT）"
                            )
                        else:
                            logger.info(
                                f"{symbol} 影子模式限制，跳过自动开仓：{shadow_mode_result.get('skip_reason', '')}"
                            )
                        continue

                    # 确定交易方向（基于技术指标）
                    trade_direction = trade_signal['direction']
                    # 4H 下跌禁止开多（熊市/保守方案：避免逆势抄底）
                    block_long_4h_down = bool(config.TRADING_CONFIG.get("BLOCK_LONG_WHEN_4H_DOWN", False))
                    if block_long_4h_down and trade_direction == "BUY" and trade_signal.get("trend_4h") == "down":
                        logger.info(
                            f"{symbol} 4H 趋势下跌，禁止开多（BLOCK_LONG_WHEN_4H_DOWN=true），跳过"
                        )
                        continue
                    # 4H 上涨禁止开空（对称过滤：避免逆势做空，减少上涨行情中空单止损）
                    block_short_4h_up = bool(config.TRADING_CONFIG.get("BLOCK_SHORT_WHEN_4H_UP", True))
                    if block_short_4h_up and trade_direction == "SELL" and trade_signal.get("trend_4h") == "up":
                        logger.info(
                            f"{symbol} 4H 趋势上涨，禁止开空（BLOCK_SHORT_WHEN_4H_UP=true），跳过"
                        )
                        continue
                    # 市场方案=牛市时禁止开空、熊市时禁止开多（与 MARKET_SCHEME 一致）
                    scheme = str(config.TRADING_CONFIG.get("MARKET_SCHEME", "normal") or "normal").lower()
                    block_short_bull = bool(config.TRADING_CONFIG.get("BLOCK_SHORT_WHEN_BULL_MARKET", True))
                    block_long_bear = bool(config.TRADING_CONFIG.get("BLOCK_LONG_WHEN_BEAR_MARKET", True))
                    if scheme == "bull" and block_short_bull and trade_direction == "SELL":
                        logger.info(f"{symbol} 市场方案=牛市，禁止开空（BLOCK_SHORT_WHEN_BULL_MARKET=true），跳过")
                        continue
                    if scheme == "bear" and block_long_bear and trade_direction == "BUY":
                        logger.info(f"{symbol} 市场方案=熊市，禁止开多（BLOCK_LONG_WHEN_BEAR_MARKET=true），跳过")
                        continue
                    # 开仓前资金费率过滤：避免在费率极端不利于己方时进场
                    if not await self._check_funding_rate_filter(symbol, trade_direction):
                        logger.info(f"{symbol} 资金费率过滤未通过，跳过开仓")
                        continue
                    # 开仓前主动买卖量过滤（可选）：与方向一致时通过
                    if not await self._check_taker_ratio_filter(symbol, trade_direction):
                        logger.info(f"{symbol} 主动买卖量过滤未通过，跳过开仓")
                        continue
                    # 开仓前流动性检查（可选）：价差/深度不达标则跳过，低量期减少滑点
                    if not await self._check_liquidity_before_entry(symbol, symbol_info.get('price')):
                        logger.info(f"{symbol} 流动性检查未通过，跳过开仓")
                        continue
                    entry_reason = trade_signal['reason']
                    
                    signal_strength = trade_signal.get('strength', 0)
                    logger.info(
                        f"{symbol} 交易信号: {trade_direction} | "
                        f"原因: {entry_reason} | "
                        f"信号强度: {signal_strength}/10"
                    )
                    # 周日提高信号门槛：只做高信号单
                    try:
                        bj = timezone(timedelta(hours=8))
                        is_sunday = (datetime.now(bj).weekday() == 6)
                        sunday_min = int(config.TRADING_CONFIG.get("SUNDAY_MIN_SIGNAL_STRENGTH", 0) or 0)
                        if is_sunday and sunday_min > 0 and signal_strength < sunday_min:
                            logger.info(
                                f"{symbol} 周日信号门槛未达 {sunday_min}（当前 {signal_strength}/10），跳过开仓"
                            )
                            continue
                    except Exception:
                        pass
                    # 根据信号强度计算动态杠杆（高质量信号使用更高杠杆）
                    # ⚠️ 优化：同时检查交易对支持的最大杠杆限制和小众币限制
                    dynamic_leverage = await self.risk_manager.calculate_dynamic_leverage(
                        signal_strength, symbol, 
                        atr=symbol_info.get('atr'),
                        entry_price=symbol_info.get('price')
                    )
                    logger.info(
                        f"{symbol} 使用动态杠杆: {dynamic_leverage}x "
                        f"(信号强度: {signal_strength}/10)"
                    )
                    
                    # 构建「入场思路/过程」并写入订单，便于事后综合分析策略执行效果
                    entry_context = {
                        'signal_strength': signal_strength,
                        'market_regime': market_regime,
                        'trend_4h': trade_signal.get('trend_4h'),
                        'change_percent': change_percent,
                        'direction': trade_direction,
                        'reason': entry_reason,
                        'rsi': symbol_info.get('rsi'),
                        'volume_confirmed': True,  # 已通过 _check_volume_confirmation
                        'filters_passed': ['only_trending', 'should_trade', 'volume_ok', 'signal_ok', 'rsi_change_ok'],  # rsi_change_ok：已通过做多/做空 RSI 与 24h 涨跌幅过滤
                    }
                    macd_hist = symbol_info.get('macd', {}).get('histogram') if isinstance(symbol_info.get('macd'), dict) else None
                    if macd_hist is not None:
                        entry_context['macd_histogram'] = macd_hist
                    if symbol_info.get('atr') is not None:
                        entry_context['atr'] = symbol_info.get('atr')
                    # 影子模式执行记录（每笔开仓）
                    sm_applied = dict(shadow_mode_result.get("shadow_mode_applied") or {})
                    entry_context["shadow_mode_applied"] = sm_applied
                    
                    # 开仓（使用改进的仓位管理）
                    position = await self.position_manager.open_position(
                        symbol=symbol,
                        change_percent=change_percent,
                        leverage=dynamic_leverage,
                        trade_direction=trade_direction,
                        entry_reason=entry_reason,
                        signal_strength=signal_strength,
                        market_regime=market_regime,
                        trend_4h=trade_signal.get('trend_4h'),
                        atr=symbol_info.get('atr'),
                        klines=symbol_info.get('klines'),  # 传递K线数据用于动态止损
                        bollinger=symbol_info.get('bollinger'),  # 传递布林带数据用于动态止损
                        entry_context=entry_context,
                        shadow_leverage_multiplier=float(shadow_mode_result.get("leverage_multiplier") or 1.0),
                    )
                    
                    if position:
                        logger.info(f"{symbol} 开仓成功: {trade_direction} ({entry_reason})")
                        # 开仓成功后，WebSocket监控会在position_manager中自动启动
                    else:
                        logger.warning(f"{symbol} 开仓失败")
                    
                    # 避免同时处理太多交易对
                    await asyncio.sleep(1)
                
                # 3. 同步币安实际持仓状态与数据库（定期同步，确保状态一致）
                try:
                    await self.position_manager.sync_positions_with_binance()
                    # 同步后，确保所有持仓都有监控（包括手动开仓的）
                    await self.position_manager.start_all_position_monitoring()
                except Exception as e:
                    logger.warning(f"持仓状态同步失败: {e}")
                
                # 4. 检查止损止盈（作为备用检查，WebSocket实时监控是主要方式）
                # 注意：如果启用了WebSocket实时监控，这里主要是作为备用检查
                closed = await self.position_manager.check_stop_loss_take_profit()
                if closed:
                    logger.info(f"定时检查触发平仓: {', '.join(closed)}")
                
                # 5. 打印持仓摘要并记录账户快照
                summary = await self.position_manager.get_position_summary()
                if summary:
                    logger.info(
                        f"持仓摘要: {summary['totalPositions']} 个持仓, "
                        f"总盈亏: {summary['totalPnL']:.2f} USDT, "
                        f"可用余额: {summary['availableBalance']:.2f} USDT"
                    )
                    
                    # 输出价格缓存统计
                    cache_size = len(self.client._price_cache)
                    if cache_size > 0:
                        logger.info(f"价格缓存: {cache_size}个交易对")
                    
                    # 记录账户快照到数据库（带 account_id，仪表板在实时 API 失败时会按账号读快照）
                    try:
                        import sys
                        from pathlib import Path
                        project_root = Path(__file__).parent.parent
                        backend_path = project_root / 'backend'
                        if backend_path.exists():
                            sys.path.insert(0, str(backend_path))
                            from database.models import AccountSnapshot
                            aid = getattr(self.position_manager, 'account_id', None)
                            if aid is None:
                                import os
                                try:
                                    aid = int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1)
                                except Exception:
                                    aid = 1
                            AccountSnapshot.create(
                                total_balance=summary['totalBalance'],
                                available_balance=summary['availableBalance'],
                                total_position_value=sum(abs(p['positionAmt'] * p['entryPrice']) for p in summary.get('positions', [])),
                                total_pnl=summary['totalPnL'],
                                open_positions=summary['totalPositions'],
                                account_id=aid
                            )
                    except Exception as e:
                        logger.debug(f"记录账户快照失败: {e}")
                
                # 重新加载配置（确保使用最新配置）
                try:
                    if hasattr(config, 'reload_config'):
                        config.reload_config()
                        logger.info("配置已重新加载，将使用最新配置进行下次扫描")
                except Exception as e:
                    logger.debug(f"重新加载配置失败: {e}")
                
                # 等待下次扫描（支持手动触发）
                scan_interval = config.TRADING_CONFIG.get('SCAN_INTERVAL', 3600)
                await self._wait_for_next_scan(scan_interval)
                
        except Exception as e:
            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()
            except Exception as e:
                logger.warning(f"停止持仓监控时出错: {e}")
            logger.info("交易策略已停止")
    
    async def _wait_for_next_scan(self, scan_interval: int):
        """
        等待下次扫描，同时监听手动触发信号
        """
        logger.info(f"等待 {scan_interval} 秒后进行下次扫描...")
        
        # 分片等待，每秒检查一次触发信号
        waited = 0
        check_interval = 1  # 检查间隔（秒）
        
        while waited < scan_interval and self.running:
            # 检查是否有手动触发信号
            try:
                if self.client and self.client.redis_cache:
                    trigger_ts = await self.client.redis_cache.get_int("ats:trigger-scan", 0)
                    if trigger_ts > self.last_scan_trigger_ts:
                        logger.info(f"检测到手动扫描触发信号 (ts={trigger_ts})，立即执行扫描")
                        self.last_scan_trigger_ts = trigger_ts
                        break
            except Exception:
                pass
            
            await asyncio.sleep(check_interval)
            waited += check_interval

    async def _check_funding_rate_filter(self, symbol: str, direction: str) -> bool:
        """
        开仓前资金费率过滤：避免在费率极端不利于己方时进场（做多时费率过高、做空时费率过负则跳过）。
        依赖 REST get_premium_index(symbol) 的 lastFundingRate。
        """
        enabled = bool(config.TRADING_CONFIG.get('FUNDING_RATE_FILTER_ENABLED', True))
        if not enabled:
            return True
        try:
            data = await self.client.get_premium_index(symbol)
            if not data or not isinstance(data, dict):
                return True
            rate_raw = data.get('lastFundingRate')
            if rate_raw is None:
                return True
            rate = float(rate_raw)
            max_long = float(config.TRADING_CONFIG.get('MAX_FUNDING_RATE_FOR_LONG', 0.001))
            min_short = float(config.TRADING_CONFIG.get('MIN_FUNDING_RATE_FOR_SHORT', -0.001))
            if direction == 'BUY' and rate > max_long:
                logger.info(f"{symbol} 资金费率过滤：做多跳过（lastFundingRate={rate:.6f} > {max_long}）")
                return False
            if direction == 'SELL' and rate < min_short:
                logger.info(f"{symbol} 资金费率过滤：做空跳过（lastFundingRate={rate:.6f} < {min_short}）")
                return False
            return True
        except Exception as e:
            logger.debug(f"{symbol} 资金费率检查失败（放行）: {e}")
            return True

    async def _check_taker_ratio_filter(self, symbol: str, direction: str) -> bool:
        """
        开仓前主动买卖量过滤（可选）：做多要求近期主动买占比不低于下限，做空要求主动卖占比不低于下限。
        依赖 get_taker_long_short_ratio(symbol, period='1h', limit=1)，取最近一条的 buySellRatio。
        """
        enabled = bool(config.TRADING_CONFIG.get('TAKER_RATIO_FILTER_ENABLED', False))
        if not enabled:
            return True
        try:
            arr = await self.client.get_taker_long_short_ratio(symbol, period='1h', limit=3)
            if not arr or not isinstance(arr, list):
                return True
            latest = arr[0] if arr else {}
            ratio_raw = latest.get('buySellRatio')
            if ratio_raw is None:
                return True
            ratio = float(ratio_raw)
            min_long = float(config.TRADING_CONFIG.get('MIN_TAKER_BUY_RATIO_FOR_LONG', 0.85))
            max_short = float(config.TRADING_CONFIG.get('MAX_TAKER_BUY_RATIO_FOR_SHORT', 1.15))
            if direction == 'BUY' and ratio < min_long:
                logger.info(f"{symbol} 主动买卖量过滤：做多跳过（buySellRatio={ratio:.3f} < {min_long}）")
                return False
            if direction == 'SELL' and ratio > max_short:
                logger.info(f"{symbol} 主动买卖量过滤：做空跳过（buySellRatio={ratio:.3f} > {max_short}）")
                return False
            return True
        except Exception as e:
            logger.debug(f"{symbol} 主动买卖量检查失败（放行）: {e}")
            return True

    async def _check_liquidity_before_entry(self, symbol: str, current_price: Optional[float] = None) -> bool:
        """
        开仓前流动性检查：盘口价差过大或深度过薄则跳过，减少低量期滑点与冲击成本。
        依赖 get_depth(symbol, limit=10)，检查买一卖一价差与前 5 档深度（USDT）。
        """
        enabled = bool(config.TRADING_CONFIG.get('LIQUIDITY_CHECK_BEFORE_ENTRY', False))
        if not enabled:
            return True
        try:
            depth = await self.client.get_depth(symbol, limit=10)
            if not depth or not isinstance(depth, dict):
                return True
            bids = depth.get('bids') or []
            asks = depth.get('asks') or []
            if not bids or not asks:
                logger.info(f"{symbol} 流动性检查：深度为空，跳过开仓")
                return False
            bid1_p = float(bids[0][0])
            ask1_p = float(asks[0][0])
            mid = (bid1_p + ask1_p) / 2.0
            if mid <= 0:
                return True
            spread_pct = (ask1_p - bid1_p) / mid
            max_spread = float(config.TRADING_CONFIG.get('LIQUIDITY_MAX_SPREAD_PCT', 0.005))
            if spread_pct > max_spread:
                logger.info(f"{symbol} 流动性检查：价差 {spread_pct*100:.3f}% > {max_spread*100:.2f}%，跳过开仓")
                return False
            # 前 5 档深度名义价值（USDT）
            depth_usdt = 0.0
            for i, (p, q) in enumerate(bids[:5]):
                depth_usdt += float(p) * float(q)
            for i, (p, q) in enumerate(asks[:5]):
                depth_usdt += float(p) * float(q)
            min_depth = float(config.TRADING_CONFIG.get('LIQUIDITY_MIN_DEPTH_USDT', 30000))
            if depth_usdt < min_depth:
                logger.info(f"{symbol} 流动性检查：前5档深度 {depth_usdt:.0f} USDT < {min_depth:.0f}，跳过开仓")
                return False
            return True
        except Exception as e:
            logger.debug(f"{symbol} 流动性检查失败（放行）: {e}")
            return True

    async def _check_volume_confirmation(self, symbol_info: Dict) -> bool:
        """
        成交量确认 - 确保有足够的成交量支撑
        
        Args:
            symbol_info: 交易对信息
            
        Returns:
            是否通过确认
        """
        volume_24h = symbol_info.get('volume24h', 0)
        min_volume = config.TRADING_CONFIG['MIN_VOLUME_24H']
        
        if volume_24h < min_volume:
            return False
        
        return True

    def _check_shadow_mode_filters(self, symbol: str) -> Dict:
        """
        检查影子模式过滤条件：黑名单、差时段、杠杆乘子（见 config/current_suggestions.json）。
        返回 evaluate_shadow_mode 的字典。
        """
        bj = timezone(timedelta(hours=8))
        hour_bj = datetime.now(bj).hour
        return evaluate_shadow_mode(symbol, hour_bj, config.TRADING_CONFIG)
    
    async def _analyze_trade_signal(self, symbol_info: Dict) -> Dict:
        """
        使用技术指标分析交易信号（高胜率策略）
        实施多周期共振：4H定方向，1H和15min找点位
        
        Args:
            symbol_info: 交易对信息（包含技术指标）
            
        Returns:
            交易信号字典 {'should_trade': bool, 'direction': str, 'reason': str, 'strength': int}
        """
        symbol = symbol_info['symbol']
        
        # ⚠️ 优化1：大盘共振（Beta Filter）- 当BTC/ETH剧烈下跌时，屏蔽所有多单
        beta_filter_enabled = config.TRADING_CONFIG.get('BETA_FILTER_ENABLED', True)
        if beta_filter_enabled:
            beta_filter_result = await self._check_beta_filter()
            if beta_filter_result['should_block_buy']:
                # 如果大盘暴跌，屏蔽所有多单
                if symbol_info.get('direction') == 'BUY' or (symbol_info.get('changePercent', 0) > 0):
                    return {
                        'should_trade': False,
                        'direction': None,
                        'reason': f"❌ 大盘共振过滤：{beta_filter_result['reason']}，屏蔽所有多单",
                        'strength': 0,
                        'trend_4h': symbol_info.get('trend_4h')
                    }
                logger.debug(f"{symbol} 大盘共振检查通过（做空信号不受影响）")
        current_price = symbol_info['price']
        rsi = symbol_info.get('rsi')
        macd = symbol_info.get('macd')
        bollinger = symbol_info.get('bollinger')
        market_regime = symbol_info.get('marketRegime', 'unknown')
        ema20 = symbol_info.get('ema20')
        ema50 = symbol_info.get('ema50')
        
        # 多周期共振检查：4H定方向（使用多指标投票机制）
        price_4h = symbol_info.get('price_4h', current_price)
        ema20_4h = symbol_info.get('ema20_4h')
        ema50_4h = symbol_info.get('ema50_4h')
        macd_4h = symbol_info.get('macd_4h')

        # 优先从 Redis 缓存读取 trend_4h（基于 WS K 线，同根 4H 内减少重复计算）
        try:
            from .trend_4h_cache import get_trend_4h_cached
            redis_cache = self.client.redis_cache if self.client else None
            trend_4h = await get_trend_4h_cached(
                redis_cache, symbol, price_4h, ema20_4h, ema50_4h, macd_4h
            )
        except Exception as e:
            logger.debug(f"{symbol} trend_4h 缓存获取失败，回退本地计算: {e}")
            trend_4h = self._judge_trend_4h(price_4h, ema20_4h, ema50_4h, macd_4h)
        
        signal_strength = 0
        reasons = []
        direction = None
        
        # 多周期共振检查：4H定方向，禁止逆势交易
        if trend_4h == 'up':
            # 4H趋势向上，只允许做多信号
            logger.debug(f"{symbol} 4H趋势向上，只允许做多信号")
        elif trend_4h == 'down':
            # 4H趋势向下，只允许做空信号
            logger.debug(f"{symbol} 4H趋势向下，只允许做空信号")
        elif trend_4h == 'neutral':
            # 4H趋势中性：若已关闭「允许4H中性」则仅 DEBUG，避免刷屏；若开启则 WARNING 提示信号质量可能降低
            allow_neutral = bool(config.TRADING_CONFIG.get("AUTO_TRADE_ALLOW_4H_NEUTRAL", False))
            if allow_neutral:
                logger.warning(f"{symbol} 4H趋势中性，信号质量可能降低")
            else:
                logger.debug(f"{symbol} 4H趋势中性（已关闭4H中性开单，仅记录）")
        
        # 简化策略：只做趋势跟踪，移除均值回归策略（避免信号冲突）
        # 策略权重配置（根据文档建议）
        TREND_SIGNAL_WEIGHTS = {
            'macd_cross': 5,          # MACD金叉/死叉
            'ema_cross': 4,           # EMA20上穿/下穿EMA50
            'price_above_ema20': 3,   # 价格在EMA20之上/下
            '4h_trend_confirmation': 2,  # 4H趋势确认
        }
        
        # 趋势跟踪策略（不再区分市场状态，统一使用趋势指标）
        # MACD金叉/死叉（权重最高）
        if macd and macd['macd'] > macd['signal'] and macd['histogram'] > 0:
            # MACD金叉，做多信号（严格4H趋势向上）
            # OPTIMIZATION: 允许明确向上趋势或中性趋势（如果是中性，后续需依靠高分信号确认）
            if trend_4h in ('up', 'neutral'):
                signal_strength += TREND_SIGNAL_WEIGHTS['macd_cross']
                reasons.append("MACD金叉")
                if direction is None:
                    direction = 'BUY'
            else:
                reasons.append(f"MACD金叉但4H趋势相反({trend_4h})，禁止逆势")
        
        elif macd and macd['macd'] < macd['signal'] and macd['histogram'] < 0:
            # MACD死叉，做空信号（严格4H趋势向下）
            # OPTIMIZATION: 允许明确向下趋势或中性趋势
            if trend_4h in ('down', 'neutral'):
                signal_strength += TREND_SIGNAL_WEIGHTS['macd_cross']
                reasons.append("MACD死叉")
                if direction is None:
                    direction = 'SELL'
            else:
                reasons.append(f"MACD死叉但4H趋势相反({trend_4h})，禁止逆势")
        
        # EMA均线系统
        if ema20 and ema50:
            if current_price > ema20 > ema50:  # 上升趋势
                # OPTIMIZATION: 允许明确向上趋势或中性趋势
                if trend_4h in ('up', 'neutral'):
                    # 冲突检查：如果已有方向且与当前不符，则是严重冲突
                    if direction == 'SELL':
                        reasons.append("❌ 信号冲突：MACD看空但EMA看多")
                        signal_strength = 0
                        direction = None
                    else:
                        signal_strength += TREND_SIGNAL_WEIGHTS['ema_cross']
                        reasons.append("EMA20上穿EMA50，上升趋势")
                        if direction is None:
                            direction = 'BUY'
                else:
                    reasons.append(f"1H均线向上但4H趋势相反({trend_4h})，禁止逆势")
            elif current_price < ema20 < ema50:  # 下降趋势
                # OPTIMIZATION: 允许明确向下趋势或中性趋势
                if trend_4h in ('down', 'neutral'):
                    # 冲突检查：如果已有方向且与当前不符，则是严重冲突
                    if direction == 'BUY':
                        reasons.append("❌ 信号冲突：MACD看多但EMA看空")
                        signal_strength = 0
                        direction = None
                    else:
                        signal_strength += TREND_SIGNAL_WEIGHTS['ema_cross']
                        reasons.append("EMA20下穿EMA50，下降趋势")
                        if direction is None:
                            direction = 'SELL'
                else:
                    reasons.append(f"1H均线向下但4H趋势相反({trend_4h})，禁止逆势")
        
        # 价格与EMA20关系
        if ema20:
            if current_price > ema20:
                if trend_4h in ('up', 'neutral') and direction == 'BUY':
                    signal_strength += TREND_SIGNAL_WEIGHTS['price_above_ema20']
                    reasons.append("价格在EMA20之上")
            elif current_price < ema20:
                if trend_4h in ('down', 'neutral') and direction == 'SELL':
                    signal_strength += TREND_SIGNAL_WEIGHTS['price_above_ema20']
                    reasons.append("价格在EMA20之下")
        
        # 4H趋势确认加分
        if direction and trend_4h:
            if (direction == 'BUY' and trend_4h == 'up') or (direction == 'SELL' and trend_4h == 'down'):
                signal_strength += TREND_SIGNAL_WEIGHTS['4h_trend_confirmation']
                reasons.append("4H周期共振确认")
            elif (direction == 'BUY' and trend_4h == 'down') or (direction == 'SELL' and trend_4h == 'up'):
                # 逆势信号，直接拒绝
                signal_strength = 0
                reasons.append("❌ 逆4H趋势，拒绝交易")
        
        # 判断是否应该交易（信号强度 >= 有效 MIN_SIGNAL_STRENGTH 才交易；低波动期自动提高门槛）
        base_min_signal_strength = config.get_effective_config('MIN_SIGNAL_STRENGTH', 7)

        # 基于交易对分层与按小时过滤：
        # - blocked：直接禁止自动交易（仅允许平仓）
        # - reduced / 差时段：提高信号门槛
        extra_signal_boost = 0
        blocked_by_hard_blacklist = False
        symbol_policy_sources = []
        try:
            from datetime import datetime, timezone, timedelta
            from config_manager import GlobalStrategyConfigManager

            mgr = GlobalStrategyConfigManager()
            stats_hour = mgr.get("STATS_HOUR_FILTERS", {}) or {}

            symbol_policy = resolve_symbol_trading_policy(symbol)
            symbol_policy_sources = symbol_policy.get("sources") or []
            if symbol_policy.get("blocked"):
                blocked_by_hard_blacklist = True
            elif symbol_policy.get("reduced"):
                extra_signal_boost += int(symbol_policy.get("signal_boost") or 0)

            # 按小时过滤：bad 小时再 +1
            try:
                bj = timezone(timedelta(hours=8))
                h = datetime.now(bj).hour
                hours = stats_hour.get("hours") or []
                for item in hours:
                    if int(item.get("hour", -1)) == int(h):
                        if (item.get("bucket") or "").lower() == "bad":
                            extra_signal_boost += int(item.get("signal_boost") or 1)
                        break
            except Exception:
                pass
        except Exception:
            base_min_signal_strength = config.get_effective_config('MIN_SIGNAL_STRENGTH', 7)
            extra_signal_boost = 0
            blocked_by_hard_blacklist = False

        mr = (market_regime or 'unknown').strip().lower()
        if mr == 'ranging':
            try:
                extra_signal_boost += int(config.TRADING_CONFIG.get('RANGING_MARKET_SIGNAL_BOOST', 2) or 0)
            except Exception:
                extra_signal_boost += 2

        min_signal_strength = max(0, min(10, int(base_min_signal_strength) + int(extra_signal_boost)))
        # 强度上限归一到 0-10，避免出现 12/10 这种误导显示
        try:
            signal_strength = max(0, min(int(signal_strength), 10))
        except Exception:
            pass
        # 回退：趋势逻辑过严导致 0 时，采用扫描器回退信号（RSI/MACD/布林综合），避免长期无推荐
        if signal_strength == 0 and direction is None:
            scan_strength = symbol_info.get('signal_strength', 0)
            scan_dir = symbol_info.get('direction')
            try:
                scan_strength = int(scan_strength) if scan_strength is not None else 0
            except (TypeError, ValueError):
                scan_strength = 0
            if scan_strength >= 1 and scan_dir in ('BUY', 'SELL'):
                signal_strength = min(scan_strength, 5)
                direction = scan_dir
                reasons.append("采用扫描器综合信号(弱)")
        should_trade = (not blocked_by_hard_blacklist) and signal_strength >= min_signal_strength and direction is not None
        if blocked_by_hard_blacklist:
            src = " / ".join(symbol_policy_sources) if symbol_policy_sources else "hard blacklist"
            reasons.append(f"❌ 命中停做名单（{src}），自动交易禁用，仅允许手动/解封后再交易")
        elif extra_signal_boost > 0 and direction is not None:
            reasons.append(f"命中减仓观察名单/差时段/震荡市过滤，最低信号门槛提高 +{extra_signal_boost}")

        # ===== RSI / 24h 涨跌幅过滤：做多不追高、做空不杀跌；可选 RSI 极端反向（超买反空/超卖反多）=====
        # 反向属于“逆短期超买超卖”的均值回归，在强趋势里逆势风险大；可选“仅4H中性时反向”以降低风险。
        reversed_by_rsi = False
        try:
            max_rsi_long = config.TRADING_CONFIG.get('MAX_RSI_FOR_LONG', 70)
            max_change_long = config.TRADING_CONFIG.get('MAX_CHANGE_PERCENT_FOR_LONG', 25)
            min_rsi_short = config.TRADING_CONFIG.get('MIN_RSI_FOR_SHORT', 30)
            max_change_short = config.TRADING_CONFIG.get('MAX_CHANGE_PERCENT_FOR_SHORT', 10)
            rsi_extreme_reverse = bool(config.TRADING_CONFIG.get('RSI_EXTREME_REVERSE_ENABLED', False))
            change_pct = symbol_info.get('changePercent')
            if change_pct is not None and not isinstance(change_pct, (int, float)):
                change_pct = float(change_pct) if change_pct else None
            elif change_pct is not None:
                change_pct = float(change_pct)

            if should_trade and direction == 'BUY':
                if rsi is not None:
                    try:
                        rsi_val = float(rsi)
                        if rsi_val >= max_rsi_long:
                            if rsi_extreme_reverse:
                                direction = 'SELL'
                                reversed_by_rsi = True
                                reasons.append(f"RSI超买({rsi_val:.1f}≥{max_rsi_long})反向做空")
                            else:
                                should_trade = False
                                reasons.append(f"❌ 做多RSI过滤：RSI={rsi_val:.1f}≥{max_rsi_long}（超买区不追多）")
                    except (TypeError, ValueError):
                        pass
                if should_trade and change_pct is not None and change_pct > max_change_long:
                    should_trade = False
                    reasons.append(f"❌ 做多涨跌幅过滤：24h涨幅={change_pct:.1f}%>{max_change_long}%（避免追大涨）")

            elif should_trade and direction == 'SELL':
                if rsi is not None:
                    try:
                        rsi_val = float(rsi)
                        if rsi_val <= min_rsi_short:
                            if rsi_extreme_reverse:
                                direction = 'BUY'
                                reversed_by_rsi = True
                                reasons.append(f"RSI超卖({rsi_val:.1f}≤{min_rsi_short})反向做多")
                            else:
                                should_trade = False
                                reasons.append(f"❌ 做空RSI过滤：RSI={rsi_val:.1f}≤{min_rsi_short}（超卖区不追空）")
                    except (TypeError, ValueError):
                        pass
                if should_trade and direction == 'SELL' and change_pct is not None and change_pct > max_change_short:
                    should_trade = False
                    reasons.append(f"❌ 做空涨跌幅过滤：24h涨幅={change_pct:.1f}%>{max_change_short}%（24h仍大涨不做空）")
        except Exception as e:
            logger.debug(f"{symbol} RSI/涨跌幅过滤失败（忽略）: {e}")

        # 安全约束：RSI 反向仅允许在 4H 中性时执行（避免在强趋势里逆势抄底/摸顶）
        if reversed_by_rsi and should_trade and direction:
            only_neutral_4h = bool(config.TRADING_CONFIG.get('RSI_EXTREME_REVERSE_ONLY_NEUTRAL_4H', True))
            if only_neutral_4h and trend_4h != 'neutral':
                should_trade = False
                reasons.append(f"❌ RSI反向仅允许4H中性时执行（当前4H={trend_4h or 'unknown'}，避免逆势风险）")

        # ===== 15m 短周期方向过滤：避免“上涨中做空 / 下跌中做多” =====
        # 思路：
        # - 使用最近 N 根 15m K 线的总涨跌幅来确认短期方向
        # - 做多(BUY)：要求短期合计涨幅 >= ENTRY_SHORT_TREND_MIN_PCT
        # - 做空(SELL)：要求短期合计跌幅 <= -ENTRY_SHORT_TREND_MIN_PCT
        # - 否则认为短期方向与计划方向不一致，降低 should_trade，并在 reason 中标记
        try:
            short_filter_enabled = bool(config.TRADING_CONFIG.get('ENTRY_SHORT_TREND_FILTER_ENABLED', False))
            if short_filter_enabled and should_trade and direction in ('BUY', 'SELL'):
                short_interval = config.TRADING_CONFIG.get('ENTRY_SHORT_INTERVAL', '15m')
                periods = int(config.TRADING_CONFIG.get('ENTRY_SHORT_CONFIRM_CANDLES', 3) or 3)
                min_pct = float(config.TRADING_CONFIG.get('ENTRY_SHORT_TREND_MIN_PCT', 0.003) or 0.003)

                # 仅在4H趋势已经明确向上/向下时启用短周期过滤；中性趋势本身就不适合自动交易
                if trend_4h in ('up', 'down'):
                    recent_change = await self._get_symbol_change_period(symbol, short_interval, periods)
                    if recent_change is not None:
                        # recent_change 是这段时间内的总涨跌幅比例（正为上涨，负为下跌）
                        if direction == 'BUY':
                            # 做多：要求短期是上涨或至少不明显下跌
                            if recent_change < min_pct:
                                should_trade = False
                                reasons.append(
                                    f"❌ 短周期({short_interval}×{periods})合计跌幅/涨幅不足：{recent_change*100:.2f}% "
                                    f"(做多需≥{min_pct*100:.2f}%)"
                                )
                        elif direction == 'SELL':
                            # 做空：要求短期是下跌或至少不明显上涨
                            if recent_change > -min_pct:
                                should_trade = False
                                reasons.append(
                                    f"❌ 短周期({short_interval}×{periods})合计涨幅/跌幅不足：{recent_change*100:.2f}% "
                                    f"(做空需≤-{min_pct*100:.2f}%)"
                                )
        except Exception as e:
            logger.debug(f"{symbol} 短周期方向过滤失败（忽略，不影响其它过滤）: {e}")

        # 提升胜率：4H趋势中性时不做自动交易（只保留推荐/观察）
        # 经验上，中性趋势下“趋势跟踪”信号更容易被来回扫损，导致胜率显著降低与交易次数激增。
        allow_neutral = bool(config.TRADING_CONFIG.get("AUTO_TRADE_ALLOW_4H_NEUTRAL", False))
        # 稳健优化：如果信号非常强（>=8），即使4H中性也允许尝试（通常意味着突破初期）
        is_strong_signal = signal_strength >= 8
        if (trend_4h == 'neutral') and (not allow_neutral) and (not is_strong_signal):
            if should_trade:
                reasons.append("❌ 4H趋势中性且信号强度不足（<8），跳过自动交易")
            should_trade = False
        
        # 如果信号方向与4H趋势相反，直接拒绝交易（除非是RSI极限反转策略）
        if direction and trend_4h and not reversed_by_rsi:
            if (direction == 'BUY' and trend_4h == 'down') or (direction == 'SELL' and trend_4h == 'up'):
                should_trade = False
                reasons.append("❌ 禁止逆4H趋势交易")
        
        # 对于 RSI 极限反转策略，虽然允许逆势，但增加额外提示
        if direction and trend_4h and reversed_by_rsi:
            if (direction == 'BUY' and trend_4h == 'down'):
                 reasons.append("⚠️ 逆4H趋势抄底（RSI超卖反转）")
            elif (direction == 'SELL' and trend_4h == 'up'):
                 reasons.append("⚠️ 逆4H趋势逃顶（RSI超买反转）")
        
        # 只在“真的强度不足”时追加该原因；避免出现“强度>=阈值但被其它过滤挡住”仍提示强度不足
        if direction and (signal_strength < min_signal_strength):
            reasons.append(f"信号强度不足({signal_strength}/10，需要≥{min_signal_strength})")
        
        return {
            'should_trade': should_trade,
            'direction': direction,
            'reason': ', '.join(reasons) if reasons else '无明确信号',
            'strength': signal_strength,
            'trend_4h': trend_4h,  # 添加4H趋势信息，供推荐器使用
            'strategy_type': 'trend_following'  # 标记策略类型
        }
    
    async def _check_beta_filter(self) -> Dict:
        """
        大盘共振（Beta Filter）检查
        当BTC或ETH在15min/1h周期剧烈下跌时，返回应该屏蔽多单的信号
        
        Returns:
            {'should_block_buy': bool, 'reason': str}
        """
        beta_filter_threshold = config.TRADING_CONFIG.get('BETA_FILTER_THRESHOLD', -0.03)  # 默认-3%
        
        try:
            # 检查BTC和ETH的15min和1h周期
            btcusdt_15m = await self._get_symbol_change_period('BTCUSDT', '15m', 5)  # 最近5根K线
            btcusdt_1h = await self._get_symbol_change_period('BTCUSDT', '1h', 3)  # 最近3根K线
            ethusdt_15m = await self._get_symbol_change_period('ETHUSDT', '15m', 5)
            ethusdt_1h = await self._get_symbol_change_period('ETHUSDT', '1h', 3)
            
            # 检查是否有剧烈下跌
            btc_15m_down = btcusdt_15m and btcusdt_15m < beta_filter_threshold
            btc_1h_down = btcusdt_1h and btcusdt_1h < beta_filter_threshold
            eth_15m_down = ethusdt_15m and ethusdt_15m < beta_filter_threshold
            eth_1h_down = ethusdt_1h and ethusdt_1h < beta_filter_threshold
            
            if btc_15m_down or btc_1h_down or eth_15m_down or eth_1h_down:
                reasons = []
                if btc_15m_down:
                    reasons.append(f"BTC 15m下跌{btcusdt_15m*100:.2f}%")
                if btc_1h_down:
                    reasons.append(f"BTC 1h下跌{btcusdt_1h*100:.2f}%")
                if eth_15m_down:
                    reasons.append(f"ETH 15m下跌{ethusdt_15m*100:.2f}%")
                if eth_1h_down:
                    reasons.append(f"ETH 1h下跌{ethusdt_1h*100:.2f}%")
                threshold_pct = beta_filter_threshold * 100
                return {
                    'should_block_buy': True,
                    'reason': f"大盘暴跌：{', '.join(reasons)}（阈值 {threshold_pct:.2f}%，若需放宽可把 BETA_FILTER_THRESHOLD 改为 -0.01 即 -1%，或关闭 BETA_FILTER_ENABLED）"
                }
            
            return {
                'should_block_buy': False,
                'reason': '大盘正常'
            }
        except Exception as e:
            logger.warning(f"大盘共振检查失败: {e}，允许交易（容错）")
            return {
                'should_block_buy': False,
                'reason': f'检查失败: {e}'
            }
    
    async def _get_symbol_change_period(self, symbol: str, interval: str, periods: int) -> Optional[float]:
        """
        获取指定交易对在指定周期内的涨跌幅
        
        Args:
            symbol: 交易对
            interval: K线周期（15m, 1h等）
            periods: 检查最近N根K线
            
        Returns:
            涨跌幅（负数表示下跌），如果获取失败返回None
        """
        try:
            klines = await self.client.get_klines(symbol, interval, limit=periods + 1)
            if not klines or len(klines) < 2:
                return None
            
            # 计算最近N根K线的总涨跌幅
            first_close = float(klines[0][4])  # 第一根K线的收盘价
            last_close = float(klines[-1][4])  # 最后一根K线的收盘价
            
            change = (last_close - first_close) / first_close
            return change
        except Exception as e:
            logger.debug(f"获取{symbol} {interval}周期涨跌幅失败: {e}")
            return None
    
    def _judge_trend_4h(self, price_4h: float, ema20_4h: Optional[float], ema50_4h: Optional[float], macd_4h: Optional[Dict]) -> str:
        """
        使用多指标投票机制判断4H趋势（避免单一指标误导）
        
        Args:
            price_4h: 4H周期价格
            ema20_4h: 4H周期EMA20
            ema50_4h: 4H周期EMA50
            macd_4h: 4H周期MACD数据
            
        Returns:
            'up', 'down', 'neutral'
        """
        if ema20_4h is None:
            return 'neutral'
        
        score = 0
        total_indicators = 0
        
        # 指标1：价格 vs EMA20
        if price_4h > ema20_4h:
            score += 1
        elif price_4h < ema20_4h:
            score -= 1
        total_indicators += 1
        
        # 指标2：EMA20 vs EMA50
        if ema50_4h is not None:
            if ema20_4h > ema50_4h:
                score += 1
            elif ema20_4h < ema50_4h:
                score -= 1
            total_indicators += 1
        
        # 指标3：MACD histogram
        if macd_4h and isinstance(macd_4h, dict):
            macd_hist = macd_4h.get('histogram', 0)
            if macd_hist > 0:
                score += 1
            elif macd_hist < 0:
                score -= 1
            total_indicators += 1
        
        # 多数投票：如果score >= 2，趋势向上；如果score <= -2，趋势向下；否则中性
        if total_indicators == 0:
            return 'neutral'
        
        # 至少需要2个指标确认
        if score >= 2:
            return 'up'
        elif score <= -2:
            return 'down'
        else:
            return 'neutral'
    
    async def _periodic_sync_positions(self):
        """
        定期同步币安持仓状态（独立任务，不依赖市场扫描）
        """
        sync_interval = int(config.TRADING_CONFIG.get('POSITION_SYNC_INTERVAL', 60) or 60)
        logger.info(f"定期持仓同步任务启动，同步间隔: {sync_interval}秒（来自 POSITION_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
        logger.info("正在停止交易策略...")
