#!/usr/bin/env python3
"""
SMC 结构识别器
- Swing High/Low (多级别)
- BOS / CHoCH 识别
- Order Block (OB) 识别
- Fair Value Gap (FVG) 识别
- 流动性地图
- 折价/溢价区域
"""
import pandas as pd
import numpy as np
from typing import List, Dict, Tuple, Optional
from dataclasses import dataclass, field
from enum import Enum
import json
from datetime import datetime


class NumpyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (np.integer, np.int64, np.int32)):
            return int(obj)
        if isinstance(obj, (np.floating, np.float64, np.float32)):
            return float(obj)
        if isinstance(obj, np.ndarray):
            return obj.tolist()
        if isinstance(obj, np.bool_):
            return bool(obj)
        if isinstance(obj, pd.Timestamp):
            return obj.isoformat()
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)


class TrendDirection(Enum):
    UP = "UP"
    DOWN = "DOWN"
    NEUTRAL = "NEUTRAL"


class StructureType(Enum):
    BOS = "BOS"           # Break of Structure
    CHoCH = "CHoCH"       # Change of Character


@dataclass
class SwingPoint:
    idx: int
    timestamp: pd.Timestamp
    price: float
    type: str  # 'HIGH' or 'LOW'
    level: str  # 'MINOR', 'INTERMEDIATE', 'MAJOR'
    left_bars: int
    right_bars: int


@dataclass
class StructureEvent:
    idx: int
    timestamp: pd.Timestamp
    price: float
    type: StructureType
    direction: TrendDirection
    broken_level_idx: int  # 被突破的 swing 索引
    broken_level_price: float
    level: str  # MINOR/INTERMEDIATE/MAJOR
    volume_confirm: bool
    strength: float  # 0-1


@dataclass
class OrderBlock:
    idx: int
    timestamp: pd.Timestamp
    type: str  # 'BULLISH' or 'BEARISH'
    top: float
    bottom: float
    open_price: float
    close_price: float
    volume: float
    is_mitigated: bool = False
    mitigation_idx: Optional[int] = None
    mitigation_pct: float = 0.0
    level: str = 'INTERMEDIATE'  # MINOR/INTERMEDIATE/MAJOR


@dataclass
class FairValueGap:
    idx: int  # 中间那根 K 线的索引
    timestamp: pd.Timestamp
    type: str  # 'BULLISH' or 'BEARISH'
    top: float
    bottom: float
    is_mitigated: bool = False
    mitigation_idx: Optional[int] = None
    mitigation_pct: float = 0.0


@dataclass
class LiquidityLevel:
    price: float
    type: str  # 'BSL' (Buy Side) or 'SSL' (Sell Side)
    source: str  # 'EQUAL_HIGH', 'EQUAL_LOW', 'SWING_HIGH', 'SWING_LOW', 'SESSION_HIGH', 'SESSION_LOW'
    timestamps: List[pd.Timestamp]
    strength: int  # 被测试次数
    is_swept: bool = False
    sweep_idx: Optional[int] = None


class SMCStructureScanner:
    """
    SMC 结构扫描器
    输入：OHLCV DataFrame (索引为时间戳)
    输出：结构化的 SMC 对象列表
    """
    
    def __init__(self, df: pd.DataFrame, swing_lengths: List[int] = [2, 5, 10]):
        self.df = df.copy()
        self.swing_lengths = swing_lengths
        self.level_names = {2: 'MINOR', 5: 'INTERMEDIATE', 10: 'MAJOR'}
        
        # 确保索引是时间戳
        if not isinstance(self.df.index, pd.DatetimeIndex):
            self.df.index = pd.to_datetime(self.df.index)
        
        # 结果存储
        self.swings: Dict[str, List[SwingPoint]] = {'HIGH': [], 'LOW': []}
        self.structures: List[StructureEvent] = []
        self.order_blocks: List[OrderBlock] = []
        self.fvgs: List[FairValueGap] = []
        self.liquidity: List[LiquidityLevel] = []
        
        # 计算辅助列
        self._prepare_data()
    
    def _prepare_data(self):
        """准备数据，计算所需指标"""
        # 真实波幅
        self.df['tr'] = np.maximum(
            self.df['h'] - self.df['l'],
            np.maximum(
                abs(self.df['h'] - self.df['c'].shift(1)),
                abs(self.df['l'] - self.df['c'].shift(1))
            )
        )
        self.df['atr_14'] = self.df['tr'].rolling(14).mean()
        
        # 成交量均值
        self.df['vol_ma_20'] = self.df['v'].rolling(20).mean()
        
    def find_swings(self) -> Dict[str, List[SwingPoint]]:
        """寻找多级别 Swing High/Low"""
        highs = self.df['h'].values
        lows = self.df['l'].values
        timestamps = self.df.index
        
        for length in self.swing_lengths:
            level_name = self.level_names[length]
            
            # Swing High
            for i in range(length, len(highs) - length):
                if highs[i] == max(highs[i-length:i+length+1]):
                    # 检查是否已存在（避免重复）
                    existing = [s for s in self.swings['HIGH'] 
                              if s.idx == i and s.level == level_name]
                    if not existing:
                        self.swings['HIGH'].append(SwingPoint(
                            idx=i,
                            timestamp=timestamps[i],
                            price=highs[i],
                            type='HIGH',
                            level=level_name,
                            left_bars=length,
                            right_bars=length
                        ))
            
            # Swing Low
            for i in range(length, len(lows) - length):
                if lows[i] == min(lows[i-length:i+length+1]):
                    existing = [s for s in self.swings['LOW'] 
                              if s.idx == i and s.level == level_name]
                    if not existing:
                        self.swings['LOW'].append(SwingPoint(
                            idx=i,
                            timestamp=timestamps[i],
                            price=lows[i],
                            type='LOW',
                            level=level_name,
                            left_bars=length,
                            right_bars=length
                        ))
        
        # 按时间排序
        for key in self.swings:
            self.swings[key].sort(key=lambda x: x.idx)
        
        return self.swings
    
    def detect_bos_choch(self) -> List[StructureEvent]:
        """检测 BOS 和 CHoCH"""
        if not self.swings['HIGH'] or not self.swings['LOW']:
            self.find_swings()
        
        highs = [s for s in self.swings['HIGH'] if s.level in ['INTERMEDIATE', 'MAJOR']]
        lows = [s for s in self.swings['LOW'] if s.level in ['INTERMEDIATE', 'MAJOR']]
        
        if len(highs) < 2 or len(lows) < 2:
            return []
        
        close = self.df['c'].values
        volume = self.df['v'].values
        vol_ma = self.df['vol_ma_20'].values
        
        # 当前趋势判断：最近的 HH/HL vs LL/LH
        last_high = highs[-1]
        last_low = lows[-1]
        prev_high = highs[-2] if len(highs) > 1 else None
        prev_low = lows[-2] if len(lows) > 1 else None
        
        # 判断趋势方向
        if prev_high and prev_low:
            if last_high.price > prev_high.price and last_low.price > prev_low.price:
                current_trend = TrendDirection.UP
            elif last_high.price < prev_high.price and last_low.price < prev_low.price:
                current_trend = TrendDirection.DOWN
            else:
                current_trend = TrendDirection.NEUTRAL
        else:
            current_trend = TrendDirection.NEUTRAL
        
        # 从最后一个确定的结构点开始扫描
        start_idx = max(last_high.idx, last_low.idx)
        
        for i in range(start_idx + 1, len(close)):
            curr_close = close[i]
            curr_vol = volume[i] if i < len(volume) else 0
            avg_vol = vol_ma[i] if i < len(vol_ma) and not np.isnan(vol_ma[i]) else curr_vol
            vol_confirm = curr_vol > avg_vol * 1.2
            
            # 多头 BOS：收盘价突破前高
            for h in highs:
                if h.idx < i and not any(s.broken_level_idx == h.idx and s.type == StructureType.BOS for s in self.structures):
                    if curr_close > h.price:
                        strength = min((curr_close - h.price) / h.price * 100, 1.0)
                        self.structures.append(StructureEvent(
                            idx=i,
                            timestamp=self.df.index[i],
                            price=curr_close,
                            type=StructureType.BOS,
                            direction=TrendDirection.UP,
                            broken_level_idx=h.idx,
                            broken_level_price=h.price,
                            level=h.level,
                            volume_confirm=vol_confirm,
                            strength=strength
                        ))
            
            # 空头 BOS：收盘价跌破前低
            for l in lows:
                if l.idx < i and not any(s.broken_level_idx == l.idx and s.type == StructureType.BOS for s in self.structures):
                    if curr_close < l.price:
                        strength = min((l.price - curr_close) / l.price * 100, 1.0)
                        self.structures.append(StructureEvent(
                            idx=i,
                            timestamp=self.df.index[i],
                            price=curr_close,
                            type=StructureType.BOS,
                            direction=TrendDirection.DOWN,
                            broken_level_idx=l.idx,
                            broken_level_price=l.price,
                            level=l.level,
                            volume_confirm=vol_confirm,
                            strength=strength
                        ))
            
            # 多头 CHoCH：上升趋势中跌破最近 HL
            if current_trend == TrendDirection.UP and prev_low:
                if curr_close < prev_low.price:
                    if not any(s.broken_level_idx == prev_low.idx and s.type == StructureType.CHoCH for s in self.structures):
                        strength = min((prev_low.price - curr_close) / prev_low.price * 100, 1.0)
                        self.structures.append(StructureEvent(
                            idx=i,
                            timestamp=self.df.index[i],
                            price=curr_close,
                            type=StructureType.CHoCH,
                            direction=TrendDirection.DOWN,
                            broken_level_idx=prev_low.idx,
                            broken_level_price=prev_low.price,
                            level=prev_low.level,
                            volume_confirm=vol_confirm,
                            strength=strength
                        ))
            
            # 空头 CHoCH：下降趋势中突破最近 LH
            if current_trend == TrendDirection.DOWN and prev_high:
                if curr_close > prev_high.price:
                    if not any(s.broken_level_idx == prev_high.idx and s.type == StructureType.CHoCH for s in self.structures):
                        strength = min((curr_close - prev_high.price) / prev_high.price * 100, 1.0)
                        self.structures.append(StructureEvent(
                            idx=i,
                            timestamp=self.df.index[i],
                            price=curr_close,
                            type=StructureType.CHoCH,
                            direction=TrendDirection.UP,
                            broken_level_idx=prev_high.idx,
                            broken_level_price=prev_high.price,
                            level=prev_high.level,
                            volume_confirm=vol_confirm,
                            strength=strength
                        ))
        
        self.structures.sort(key=lambda x: x.idx)
        return self.structures
    
    def detect_order_blocks(self, lookback: int = 20) -> List[OrderBlock]:
        """检测 Order Block
        逻辑：导致 BOS/CHoCH 的那根反向 K 线
        """
        if not self.structures:
            self.detect_bos_choch()
        
        opens = self.df['o'].values
        closes = self.df['c'].values
        highs = self.df['h'].values
        lows = self.df['l'].values
        volumes = self.df['v'].values
        
        for struct in self.structures:
            if struct.type == StructureType.BOS and struct.direction == TrendDirection.UP:
                # 多头 BOS：找突破前的最后一根阴线
                for j in range(struct.idx - 1, max(struct.idx - lookback, 0), -1):
                    if closes[j] < opens[j]:  # 阴线
                        ob = OrderBlock(
                            idx=j,
                            timestamp=self.df.index[j],
                            type='BULLISH',
                            top=max(opens[j], closes[j]),
                            bottom=min(opens[j], closes[j]),
                            open_price=opens[j],
                            close_price=closes[j],
                            volume=volumes[j],
                            level=struct.level
                        )
                        self.order_blocks.append(ob)
                        break
            
            elif struct.type == StructureType.BOS and struct.direction == TrendDirection.DOWN:
                # 空头 BOS：找跌破前的最后一根阳线
                for j in range(struct.idx - 1, max(struct.idx - lookback, 0), -1):
                    if closes[j] > opens[j]:  # 阳线
                        ob = OrderBlock(
                            idx=j,
                            timestamp=self.df.index[j],
                            type='BEARISH',
                            top=max(opens[j], closes[j]),
                            bottom=min(opens[j], closes[j]),
                            open_price=opens[j],
                            close_price=closes[j],
                            volume=volumes[j],
                            level=struct.level
                        )
                        self.order_blocks.append(ob)
                        break
            
            elif struct.type == StructureType.CHoCH and struct.direction == TrendDirection.UP:
                # 空转多 CHoCH：找突破前的最后一根阴线
                for j in range(struct.idx - 1, max(struct.idx - lookback, 0), -1):
                    if closes[j] < opens[j]:
                        ob = OrderBlock(
                            idx=j,
                            timestamp=self.df.index[j],
                            type='BULLISH',
                            top=max(opens[j], closes[j]),
                            bottom=min(opens[j], closes[j]),
                            open_price=opens[j],
                            close_price=closes[j],
                            volume=volumes[j],
                            level=struct.level
                        )
                        self.order_blocks.append(ob)
                        break
            
            elif struct.type == StructureType.CHoCH and struct.direction == TrendDirection.DOWN:
                # 多转空 CHoCH：找跌破前的最后一根阳线
                for j in range(struct.idx - 1, max(struct.idx - lookback, 0), -1):
                    if closes[j] > opens[j]:
                        ob = OrderBlock(
                            idx=j,
                            timestamp=self.df.index[j],
                            type='BEARISH',
                            top=max(opens[j], closes[j]),
                            bottom=min(opens[j], closes[j]),
                            open_price=opens[j],
                            close_price=closes[j],
                            volume=volumes[j],
                            level=struct.level
                        )
                        self.order_blocks.append(ob)
                        break
        
        # 去重（同一根 K 线可能被多个结构关联）
        seen = set()
        unique_obs = []
        for ob in self.order_blocks:
            key = (ob.idx, ob.type)
            if key not in seen:
                seen.add(key)
                unique_obs.append(ob)
        
        self.order_blocks = unique_obs
        self.order_blocks.sort(key=lambda x: x.idx)
        return self.order_blocks
    
    def check_ob_mitigation(self, current_idx: int) -> List[OrderBlock]:
        """检查 OB 是否被缓解"""
        lows = self.df['l'].values
        highs = self.df['h'].values
        
        for ob in self.order_blocks:
            if ob.is_mitigated:
                continue
            if ob.idx >= current_idx:
                continue
            
            if ob.type == 'BULLISH':
                # 价格回测到 OB 底部 50% 以下
                ob_50 = ob.bottom + (ob.top - ob.bottom) * 0.5
                if lows[current_idx] <= ob_50:
                    ob.is_mitigated = True
                    ob.mitigation_idx = current_idx
                    penetration = (ob_50 - lows[current_idx]) / (ob.top - ob.bottom) if ob.top != ob.bottom else 0
                    ob.mitigation_pct = max(0, min(penetration * 2, 1.0))
            
            elif ob.type == 'BEARISH':
                ob_50 = ob.top - (ob.top - ob.bottom) * 0.5
                if highs[current_idx] >= ob_50:
                    ob.is_mitigated = True
                    ob.mitigation_idx = current_idx
                    penetration = (highs[current_idx] - ob_50) / (ob.top - ob.bottom) if ob.top != ob.bottom else 0
                    ob.mitigation_pct = max(0, min(penetration * 2, 1.0))
        
        return self.order_blocks
    
    def detect_fvg(self) -> List[FairValueGap]:
        """检测 Fair Value Gap (三根 K 线)"""
        opens = self.df['o'].values
        closes = self.df['c'].values
        highs = self.df['h'].values
        lows = self.df['l'].values
        
        for i in range(1, len(closes) - 1):
            # Bullish FVG: K1 阳, K2 任意, K3 阳, K1高 < K3低
            if closes[i-1] > opens[i-1] and closes[i+1] > opens[i+1]:
                gap_top = highs[i-1]
                gap_bottom = lows[i+1]
                if gap_top < gap_bottom:  # 有真空
                    fvg = FairValueGap(
                        idx=i,
                        timestamp=self.df.index[i],
                        type='BULLISH',
                        top=gap_bottom,
                        bottom=gap_top
                    )
                    self.fvgs.append(fvg)
            
            # Bearish FVG: K1 阴, K2 任意, K3 阴, K3高 < K1低
            if closes[i-1] < opens[i-1] and closes[i+1] < opens[i+1]:
                gap_top = highs[i+1]
                gap_bottom = lows[i-1]
                if gap_top < gap_bottom:
                    fvg = FairValueGap(
                        idx=i,
                        timestamp=self.df.index[i],
                        type='BEARISH',
                        top=gap_bottom,
                        bottom=gap_top
                    )
                    self.fvgs.append(fvg)
        
        self.fvgs.sort(key=lambda x: x.idx)
        return self.fvgs
    
    def check_fvg_mitigation(self, current_idx: int) -> List[FairValueGap]:
        """检查 FVG 缓解"""
        lows = self.df['l'].values
        highs = self.df['h'].values
        
        for fvg in self.fvgs:
            if fvg.is_mitigated:
                continue
            if fvg.idx >= current_idx:
                continue
            
            if fvg.type == 'BULLISH':
                # 价格进入缺口区间
                if lows[current_idx] <= fvg.top and highs[current_idx] >= fvg.bottom:
                    fvg.is_mitigated = True
                    fvg.mitigation_idx = current_idx
                    # 计算缓解程度
                    if fvg.top != fvg.bottom:
                        penetration = (fvg.top - lows[current_idx]) / (fvg.top - fvg.bottom)
                        fvg.mitigation_pct = max(0, min(penetration, 1.0))
            
            elif fvg.type == 'BEARISH':
                if highs[current_idx] >= fvg.bottom and lows[current_idx] <= fvg.top:
                    fvg.is_mitigated = True
                    fvg.mitigation_idx = current_idx
                    if fvg.top != fvg.bottom:
                        penetration = (highs[current_idx] - fvg.bottom) / (fvg.top - fvg.bottom)
                        fvg.mitigation_pct = max(0, min(penetration, 1.0))
        
        return self.fvgs
    
    def build_liquidity_map(self, lookback: int = 100) -> List[LiquidityLevel]:
        """构建流动性地图"""
        highs = self.df['h'].values
        lows = self.df['l'].values
        timestamps = self.df.index
        
        # 1. 相等高点/低点 (容差 0.02%)
        threshold = 0.0002
        
        # 最近 N 根 K 线
        start = max(0, len(highs) - lookback)
        
        # BSL: 相等高点、Swing High
        high_levels = {}
        for i in range(start, len(highs)):
            price = highs[i]
            # 找相近价格
            matched = False
            for existing_price in high_levels:
                if abs(price - existing_price) / existing_price < threshold:
                    high_levels[existing_price]['timestamps'].append(timestamps[i])
                    high_levels[existing_price]['strength'] += 1
                    matched = True
                    break
            if not matched:
                high_levels[price] = {
                    'timestamps': [timestamps[i]],
                    'strength': 1,
                    'type': 'BSL',
                    'source': 'EQUAL_HIGH' if len(high_levels) > 0 else 'SWING_HIGH'
                }
        
        # SSL: 相等低点、Swing Low
        low_levels = {}
        for i in range(start, len(lows)):
            price = lows[i]
            matched = False
            for existing_price in low_levels:
                if abs(price - existing_price) / existing_price < threshold:
                    low_levels[existing_price]['timestamps'].append(timestamps[i])
                    low_levels[existing_price]['strength'] += 1
                    matched = True
                    break
            if not matched:
                low_levels[price] = {
                    'timestamps': [timestamps[i]],
                    'strength': 1,
                    'type': 'SSL',
                    'source': 'EQUAL_LOW' if len(low_levels) > 0 else 'SWING_LOW'
                }
        
        # 转换为对象
        for price, data in high_levels.items():
            if data['strength'] >= 2 or data['source'] == 'SWING_HIGH':
                self.liquidity.append(LiquidityLevel(
                    price=price,
                    type=data['type'],
                    source=data['source'],
                    timestamps=data['timestamps'],
                    strength=data['strength']
                ))
        
        for price, data in low_levels.items():
            if data['strength'] >= 2 or data['source'] == 'SWING_LOW':
                self.liquidity.append(LiquidityLevel(
                    price=price,
                    type=data['type'],
                    source=data['source'],
                    timestamps=data['timestamps'],
                    strength=data['strength']
                ))
        
        # 会话高低点 (简化：每日高低点)
        daily_highs = self.df['h'].resample('D').max()
        daily_lows = self.df['l'].resample('D').min()
        for ts, price in daily_highs.items():
            if len(self.liquidity) < 50:  # 限制数量
                self.liquidity.append(LiquidityLevel(
                    price=price,
                    type='BSL',
                    source='SESSION_HIGH',
                    timestamps=[ts],
                    strength=1
                ))
        for ts, price in daily_lows.items():
            if len(self.liquidity) < 50:
                self.liquidity.append(LiquidityLevel(
                    price=price,
                    type='SSL',
                    source='SESSION_LOW',
                    timestamps=[ts],
                    strength=1
                ))
        
        return self.liquidity
    
    def check_liquidity_sweep(self, current_idx: int) -> List[LiquidityLevel]:
        """检查流动性扫荡"""
        high = self.df['h'].values[current_idx]
        low = self.df['l'].values[current_idx]
        
        for liq in self.liquidity:
            if liq.is_swept:
                continue
            
            if liq.type == 'BSL' and high >= liq.price:
                liq.is_swept = True
                liq.sweep_idx = current_idx
            elif liq.type == 'SSL' and low <= liq.price:
                liq.is_swept = True
                liq.sweep_idx = current_idx
        
        return self.liquidity
    
    def get_discount_premium_zone(self, current_idx: int, lookback: int = 100) -> Tuple[str, float, float, float]:
        """计算折价/溢价区域
        返回: (zone_type, low, equilibrium, high)
        zone_type: 'DISCOUNT', 'EQUILIBRIUM', 'PREMIUM'
        """
        start = max(0, current_idx - lookback)
        recent_high = self.df['h'].values[start:current_idx+1].max()
        recent_low = self.df['l'].values[start:current_idx+1].min()
        
        equilibrium = (recent_high + recent_low) / 2
        current_price = self.df['c'].values[current_idx]
        
        if current_price < equilibrium * 0.995:  # 稍微容差
            zone = 'DISCOUNT'
        elif current_price > equilibrium * 1.005:
            zone = 'PREMIUM'
        else:
            zone = 'EQUILIBRIUM'
        
        return zone, recent_low, equilibrium, recent_high
    
    def get_kill_zone(self, timestamp: pd.Timestamp) -> str:
        """判断当前 Kill Zone"""
        hour = timestamp.hour
        minute = timestamp.minute
        total_min = hour * 60 + minute
        
        # UTC 时间
        if 7*60 <= total_min < 9*60:
            return 'LONDON_OPEN'
        elif 13*60 <= total_min < 15*60:
            return 'NY_OPEN'
        elif 16*60 <= total_min < 17*60:
            return 'LONDON_CLOSE'
        elif 0 <= total_min < 8*60:
            return 'ASIAN'
        elif 20*60 <= total_min < 24*60:
            return 'LATE_NIGHT'
        else:
            return 'OTHER'
    
    def scan_all(self) -> Dict:
        """完整扫描流程"""
        print("开始完整 SMC 结构扫描...")
        
        # 1. Swing Points
        print("  1/6 寻找 Swing Points...")
        self.find_swings()
        print(f"     HIGH: {len(self.swings['HIGH'])}, LOW: {len(self.swings['LOW'])}")
        
        # 2. BOS/CHoCH
        print("  2/6 检测 BOS/CHoCH...")
        self.detect_bos_choch()
        print(f"     结构事件: {len(self.structures)}")
        
        # 3. Order Blocks
        print("  3/6 检测 Order Blocks...")
        self.detect_order_blocks()
        print(f"     OB: {len(self.order_blocks)}")
        
        # 4. FVG
        print("  4/6 检测 FVG...")
        self.detect_fvg()
        print(f"     FVG: {len(self.fvgs)}")
        
        # 5. Liquidity
        print("  5/6 构建流动性地图...")
        self.build_liquidity_map()
        print(f"     流动性池: {len(self.liquidity)}")
        
        # 6. 最后一根的区域判断
        last_idx = len(self.df) - 1
        zone, low, eq, high = self.get_discount_premium_zone(last_idx)
        kz = self.get_kill_zone(self.df.index[-1])
        
        print("  6/6 完成！")
        
        return {
            'swings': self.swings,
            'structures': self.structures,
            'order_blocks': self.order_blocks,
            'fvgs': self.fvgs,
            'liquidity': self.liquidity,
            'current_zone': zone,
            'zone_bounds': {'low': low, 'equilibrium': eq, 'high': high},
            'current_kill_zone': kz,
            'last_price': self.df['c'].values[-1],
            'last_timestamp': self.df.index[-1]
        }


def scanner_to_dict(scanner: SMCStructureScanner) -> dict:
    """将扫描结果转为可序列化字典"""
    def swing_to_dict(s: SwingPoint):
        return {
            'idx': s.idx, 'timestamp': str(s.timestamp), 'price': s.price,
            'type': s.type, 'level': s.level
        }
    
    def struct_to_dict(s: StructureEvent):
        return {
            'idx': s.idx, 'timestamp': str(s.timestamp), 'price': s.price,
            'type': s.type.value, 'direction': s.direction.value,
            'broken_level_idx': s.broken_level_idx, 'broken_level_price': s.broken_level_price,
            'level': s.level, 'volume_confirm': s.volume_confirm, 'strength': s.strength
        }
    
    def ob_to_dict(ob: OrderBlock):
        return {
            'idx': ob.idx, 'timestamp': str(ob.timestamp), 'type': ob.type,
            'top': ob.top, 'bottom': ob.bottom, 'open': ob.open_price, 'close': ob.close_price,
            'volume': ob.volume, 'level': ob.level,
            'is_mitigated': ob.is_mitigated, 'mitigation_pct': ob.mitigation_pct
        }
    
    def fvg_to_dict(f: FairValueGap):
        return {
            'idx': f.idx, 'timestamp': str(f.timestamp), 'type': f.type,
            'top': f.top, 'bottom': f.bottom,
            'is_mitigated': f.is_mitigated, 'mitigation_pct': f.mitigation_pct
        }
    
    def liq_to_dict(l: LiquidityLevel):
        return {
            'price': l.price, 'type': l.type, 'source': l.source,
            'strength': l.strength, 'is_swept': l.is_swept, 'sweep_idx': l.sweep_idx
        }
    
    return {
        'swings': {'HIGH': [swing_to_dict(s) for s in scanner.swings['HIGH']],
                   'LOW': [swing_to_dict(s) for s in scanner.swings['LOW']]},
        'structures': [struct_to_dict(s) for s in scanner.structures],
        'order_blocks': [ob_to_dict(ob) for ob in scanner.order_blocks],
        'fvgs': [fvg_to_dict(fvg) for fvg in scanner.fvgs],
        'liquidity': [liq_to_dict(l) for l in scanner.liquidity]
    }


if __name__ == "__main__":
    # 测试
    print("加载数据...")
    df = pd.read_parquet('/root/quant_pipeline/data/btc_15m_with_fr_oi.parquet')
    print(f"数据形状: {df.shape}")
    print(f"列: {df.columns.tolist()}")
    
    # 只扫描最近 2000 根测试
    test_df = df.tail(2000).copy()
    
    scanner = SMCStructureScanner(test_df)
    result = scanner.scan_all()
    
    print(f"\n=== 扫描结果摘要 ===")
    print(f"当前价格: {result['last_price']}")
    print(f"当前区域: {result['current_zone']} (低:{result['zone_bounds']['low']:.1f}, 均:{result['zone_bounds']['equilibrium']:.1f}, 高:{result['zone_bounds']['high']:.1f})")
    print(f"当前 Kill Zone: {result['current_kill_zone']}")
    print(f"Swing HIGH: {len(result['swings']['HIGH'])}, LOW: {len(result['swings']['LOW'])}")
    print(f"结构事件: {len(result['structures'])}")
    print(f"Order Blocks: {len(result['order_blocks'])}")
    print(f"FVG: {len(result['fvgs'])}")
    print(f"流动性池: {len(result['liquidity'])}")
    
    # 保存结果
    import json
    out = scanner_to_dict(scanner)
    with open('/root/quant_pipeline/smc_agent/scanner/smc_scan_result.json', 'w') as f:
        json.dump(out, f, indent=2, cls=NumpyEncoder)
    print("\n结果已保存到 smc_scan_result.json")