# src/backtest_engine.py
from datetime import datetime
from typing import Type, Dict, Any, List, Optional
import pandas as pd

# 导入所有需要协调的模块
from .core_data import Bar, Order, Trade, PortfolioSnapshot
from .data_manager import DataManager
from .execution_simulator import ExecutionSimulator
from .backtest_context import BacktestContext
from .strategies.base_strategy import Strategy

class BacktestEngine:
    """
    回测引擎：协调数据流、策略执行、订单模拟和结果记录。
    """
    def __init__(self,
                 data_manager: DataManager,
                 strategy_class: Type[Strategy],
                 strategy_params: Dict[str, Any],
                 # current_segment_symbol: str, # 这个参数不再需要，因为 symbol 会动态更新
                 initial_capital: float = 100000.0,
                 slippage_rate: float = 0.0001,
                 commission_rate: float = 0.0002,
                 roll_over_mode: bool = False,
                 start_time: Optional[datetime] = None,  # 新增开始时间
                 end_time: Optional[datetime] = None  # 新增结束时间
                 ): # 新增换月模式参数
        """
        初始化回测引擎。

        Args:
            data_manager (DataManager): 已经初始化好的数据管理器实例。
            strategy_class (Type[Strategy]): 策略类（而不是实例），引擎会负责实例化。
            strategy_params (Dict[str, Any]): 传递给策略的参数字典。
            initial_capital (float): 初始交易资金。
            slippage_rate (float): 交易滑点率。
            commission_rate (float): 交易佣金率。
            roll_over_mode (bool): 是否启用主连合约换月模式。
        """
        self.data_manager = data_manager
        self.initial_capital = initial_capital
        self.simulator = ExecutionSimulator(
            initial_capital=initial_capital,
            slippage_rate=slippage_rate,
            commission_rate=commission_rate
        )
        # 传入引擎自身给 context，以便 context 可以获取引擎的状态（如 is_rollover_bar）
        self.context = BacktestContext(self.data_manager, self.simulator)
        self.context.set_engine(self) # 建立 Context 到 Engine 的引用

        # self.current_segment_symbol = current_segment_symbol # 此行移除或作为内部变量动态管理

        # 实例化策略。初始 symbol 会在 run_backtest 中根据第一根 Bar 动态设置。
        self.strategy = strategy_class(self.context, symbol="INITIAL_PLACEHOLDER_SYMBOL", **strategy_params)

        self.portfolio_snapshots: List[PortfolioSnapshot] = []
        self.trade_history: List[Trade] = []
        self.all_bars: List[Bar] = []

        self._history_bars: List[Bar] = [] # 引擎层面保留的历史 Bar，通常供策略在 on_bar 中使用
        self._max_history_bars: int = strategy_params.get('history_bars_limit', 200)

        # 换月相关状态
        self.roll_over_mode = roll_over_mode # 是否启用换月模式
        self._last_processed_bar_symbol: Optional[str] = None # 记录上一根 K 线的 symbol
        self.is_rollover_bar: bool = False # 标记当前 K 线是否为换月 K 线（禁止开仓）

        # 新增时间过滤属性
        self.start_time = start_time
        self.end_time = end_time

        print("\n--- 回测引擎初始化完成 ---")
        print(f"  策略: {strategy_class.__name__}")
        print(f"  初始资金: {initial_capital:.2f}")
        print(f"  换月模式: {'启用' if roll_over_mode else '禁用'}")

    def run_backtest(self):
        """
        运行整个回测流程，包含换月逻辑。
        """
        print("\n--- 回测开始 ---")

        # 调用策略的初始化方法
        self.strategy.on_init()

        last_processed_bar: Optional[Bar] = None # 用于在换月时引用旧合约的最后一根 K 线

        # 主回测循环
        while True:
            current_bar = self.data_manager.get_next_bar()

            if current_bar is None:
                break  # 没有更多数据，回测结束

            self.all_bars.append(current_bar)

            if self.start_time and current_bar.datetime < self.start_time:
                continue

            # 如果设置了结束时间，且当前K线在结束时间之后，则终止回测
            if self.end_time and current_bar.datetime >= self.end_time:
                print(f"到达结束时间 {self.end_time}，回测终止。")
                break

            # --- 换月逻辑判断和处理 (在处理 current_bar 之前进行) ---
            # 1. 重置 is_rollover_bar 标记
            self.is_rollover_bar = False 

            # 4. 更新 Context 和 Simulator 的当前 Bar 和时间
            self.context.set_current_bar(current_bar)
            self.simulator.update_time(current_time=current_bar.datetime)

            # 2. 如果启用换月模式，并且检测到合约 symbol 变化
            if self.roll_over_mode and \
               self._last_processed_bar_symbol is not None and \
               current_bar.symbol != self._last_processed_bar_symbol:
                
                old_symbol = self._last_processed_bar_symbol
                new_symbol = current_bar.symbol
                
                # 确认 last_processed_bar 确实是旧合约的最后一根 K 线
                if last_processed_bar and last_processed_bar.symbol == old_symbol:
                    self.strategy.log(f"检测到换月！从 [{old_symbol}] 切换到 [{new_symbol}]。"
                                     f"在旧合约最后一根K线 ({last_processed_bar.datetime}) 执行强制平仓和取消操作。")
                    
                    # A. 强制平仓旧合约的所有持仓
                    self.simulator.force_close_all_positions_for_symbol(old_symbol, last_processed_bar)
                    
                    # B. 取消旧合约的所有挂单
                    self.simulator.cancel_all_pending_orders_for_symbol(old_symbol)

                    # C. 标记【当前这根 Bar (即新合约的第一根 K 线)】为换月 K 线
                    # 此时 self.is_rollover_bar 变为 True，将通过 Context 传递给策略，
                    # 策略在该 K 线周期内不能开仓。
                    self.is_rollover_bar = True
                    
                    # D. 通知策略换月事件，让策略有机会重置内部状态
                    self.strategy.on_rollover(old_symbol, new_symbol)
                else:
                    self.strategy.log(f"警告: 检测到换月从 {old_symbol} 到 {new_symbol}，但 last_processed_bar 为空或与旧合约不符。"
                                     "强制平仓/取消操作可能未正确执行。")
            
            # 3. 更新策略关注的当前合约 symbol
            self.strategy.symbol = current_bar.symbol

            # 5. 更新引擎内部的历史 Bar 缓存
            self._history_bars.append(current_bar)
            if len(self._history_bars) > self._max_history_bars:
                self._history_bars.pop(0)

            # 6. 处理待撮合订单 (在调用策略 on_bar 之前，确保订单在当前 K 线开盘价撮合)
            # self.simulator.process_pending_orders(current_bar)
            self.strategy.on_open_bar(current_bar)

            # 7. 调用策略的 on_bar 方法
            # self.strategy.on_bar(current_bar)
            self.simulator.process_pending_orders(current_bar)

            self.strategy.on_close_bar(current_bar)
            self.simulator.process_pending_orders(current_bar)


            # 8. 记录投资组合快照
            current_portfolio_value = self.simulator.get_portfolio_value(current_bar)
            current_positions = self.simulator.get_current_positions()

            price_at_snapshot = {current_bar.symbol: current_bar.close} # 使用当前 Bar 的收盘价记录快照

            snapshot = PortfolioSnapshot(
                datetime=current_bar.datetime,
                total_value=current_portfolio_value,
                cash=self.simulator.cash,
                positions=current_positions,
                price_at_snapshot=price_at_snapshot
            )
            self.portfolio_snapshots.append(snapshot)

            # 9. 更新 `_last_processed_bar_symbol` 和 `last_processed_bar` 为当前 Bar，为下一轮循环做准备
            self._last_processed_bar_symbol = current_bar.symbol
            last_processed_bar = current_bar

        # --- 回测结束后的清理工作 ---
        print("\n--- 回测结束，检查并平仓所有剩余持仓 ---")
        if last_processed_bar: # 确保至少有一根 Bar 被处理过
            # 在回测结束时，强制平仓所有可能存在的剩余持仓
            # 遍历所有持仓，确保全部清算
            remaining_positions_symbols = list(self.simulator.get_current_positions().keys())
            for symbol_held in remaining_positions_symbols:
                if self.simulator.get_current_positions().get(symbol_held, 0) != 0:
                    self.strategy.log(f"回测结束清理: 强制平仓合约 {symbol_held} 的剩余持仓。")
                    # 使用 simulator 的 force_close_all_positions_for_symbol 方法进行清理
                    self.simulator.force_close_all_positions_for_symbol(symbol_held, last_processed_bar)
                    self.simulator.cancel_all_pending_orders_for_symbol(symbol_held)
        else:
            print("没有处理任何 Bar，无需平仓。")

        # 回测结束后，获取所有交易记录
        self.trade_history = self.simulator.get_trade_history()

        print("--- 回测结束 ---")
        print(f"总计处理了 {len(self.all_bars)} 根K线。")
        print(f"总计发生了 {len(self.trade_history)} 笔交易。")

        final_portfolio_value = 0.0
        if last_processed_bar:
            final_portfolio_value = self.simulator.get_portfolio_value(last_processed_bar)
        else:
            final_portfolio_value = self.initial_capital

        total_return_percentage = ((final_portfolio_value - self.initial_capital) / self.initial_capital) * 100

        print(f"最终总净值: {final_portfolio_value:.2f}")
        print(f"总收益率: {total_return_percentage:.2f}%")

    def get_backtest_results(self) -> Dict[str, Any]:
        """
        返回回测结果数据，供结果分析模块使用。
        """
        return {
            "portfolio_snapshots": self.portfolio_snapshots,
            "trade_history": self.trade_history,
            "initial_capital": self.simulator.initial_capital,
            "all_bars": self.all_bars
        }

    def get_simulator(self) -> ExecutionSimulator:
        return self.simulator
    
    def get_bar_history(self):
        return self.all_bars
    
