# -*- coding: utf-8 -*-
# cyb50_kalman_filter_daily.py

import akshare as ak
import pandas as pd
import numpy as np
from pykalman import KalmanFilter

# === 策略参数 ===
TREND_THRESHOLD_BUY = 0.00     # 趋势死区：trend必须超过此值才触发买入（扫描最优：去掉死区）
TREND_THRESHOLD_SELL = -0.10   # 趋势死区：trend必须低于此值才触发卖出
CONFIRMATION_DAYS_BUY = 1      # 买入确认天数
CONFIRMATION_DAYS_SELL = 2     # 卖出确认天数
MIN_HOLDING_DAYS = 10          # 最小持仓天数
TRAILING_STOP_PCT = 0.08       # 追踪止损（基于卡尔曼滤波价格）
MAX_LOSS_STOP = 0.12           # 最大亏损止损（基于入场价）
TREND_PEAK_DECAY = 0.85        # 趋势衰减退出阈值（修复后重新优化：0.80→0.85）
KALMAN_SLOPE_THRESH = -0.05    # Kalman偏离过滤：kalman价格相对60日均线偏离阈值
VOL_THRESHOLD_FIXED = 0.30     # 波动率过滤：年化波动率超过此值时不买入（A股高波动分界线）
EARLY_STOP_DAYS = 3            # 早期止损观察天数（修复后重新优化：5→3）
EARLY_STOP_LOSS = 0.025        # 早期止损阈值（2.5%）
SCALE_IN_DAYS = 3              # 加仓观察天数：持仓N天后判断是否加仓（修复后重新优化：5→3）
SCALE_IN_THRESH = 0.025        # 加仓阈值：前N天涨幅超过此值才加仓（修复后重新优化：3%→2.5%）
SCALE_IN_SIZE = 0.8            # 加仓比例：加仓后总仓位 = 1.0 + SCALE_IN_SIZE = 1.8（上限1.8）

# --- 1. 数据获取 (日线级别 + 实时数据) ---
def get_index_data_with_realtime(index_code, start_date="2018-01-01", manual_today_price=None):
    """
    获取指数历史数据 + 当天实时数据

    Args:
        index_code: 指数代码 (如 "sz399673")
        start_date: 起始日期
        manual_today_price: 手动输入的今日价格（如果提供，将使用该价格）

    Returns:
        DataFrame: 包含历史数据和当天数据的完整数据集
    """
    import datetime

    print(f"正在获取指数 {index_code} 的数据...")

    # 1. 获取历史日线数据 - 尝试多个数据源
    print("  - 获取历史日线数据...")
    hist_df = pd.DataFrame()

    # 尝试使用 stock_zh_index_daily_em
    try:
        hist_df = ak.stock_zh_index_daily_em(symbol=index_code)
        hist_df['date'] = pd.to_datetime(hist_df['date'])
        hist_df.set_index('date', inplace=True)
        hist_df = hist_df[hist_df.index >= start_date]
        print(f"    历史数据: {hist_df.index[0]} 到 {hist_df.index[-1]} ({len(hist_df)} 条)")
    except Exception as e:
        print(f"    stock_zh_index_daily_em 失败: {e}")

    # 如果失败，尝试使用原始接口
    if hist_df.empty:
        try:
            hist_df = ak.stock_zh_index_daily(symbol=index_code)
            hist_df['date'] = pd.to_datetime(hist_df['date'])
            hist_df.set_index('date', inplace=True)
            hist_df = hist_df[hist_df.index >= start_date]
            print(f"    历史数据(备用源): {hist_df.index[0]} 到 {hist_df.index[-1]} ({len(hist_df)} 条)")
        except Exception as e:
            print(f"    stock_zh_index_daily 失败: {e}")

    if hist_df.empty:
        raise ValueError(f"无法获取指数 {index_code} 的历史数据")

    # 2. 获取当天实时数据
    print("  - 获取当天实时数据...")
    today = datetime.datetime.now().date()
    has_today_data = not hist_df.empty and hist_df.index[-1].date() == today

    if has_today_data:
        print(f"    历史数据已包含今日数据，收盘价: {hist_df.iloc[-1]['close']:.2f}")
    elif manual_today_price is not None:
        # 使用手动输入的价格
        print(f"    使用手动输入的今日价格: {manual_today_price:.2f}")
        today_df = pd.DataFrame({
            'close': [manual_today_price],
            'open': [manual_today_price],  # 如果只有收盘价，就用收盘价填充
            'high': [manual_today_price],
            'low': [manual_today_price],
            'volume': [hist_df['volume'].iloc[-1] if len(hist_df) > 0 else 0],
        }, index=[pd.Timestamp(today)])
        hist_df = pd.concat([hist_df, today_df])
    else:
        # 尝试获取实时数据
        # 方法1: 东方财富接口
        realtime_ok = False
        try:
            spot_df = ak.stock_zh_index_spot_em()
            current_data = spot_df[spot_df['代码'] == index_code]

            if not current_data.empty:
                today_df = pd.DataFrame({
                    'close': [float(current_data.iloc[0]['最新价'])],
                    'open': [float(current_data.iloc[0]['开盘'])],
                    'high': [float(current_data.iloc[0]['最高'])],
                    'low': [float(current_data.iloc[0]['最低'])],
                    'volume': [float(current_data.iloc[0]['成交量']) if '成交量' in current_data.columns else 0],
                }, index=[pd.Timestamp(today)])

                print(f"    [东财] 获取到今日数据: {today}, 最新价 {today_df.iloc[0]['close']:.2f}")
                hist_df = pd.concat([hist_df, today_df])
                realtime_ok = True
            else:
                print(f"    [东财] 未找到指数 {index_code} 的实时数据")
        except Exception as e:
            print(f"    [东财] 实时数据获取失败: {e}")

        # 方法2: 腾讯财经接口（备用）
        if not realtime_ok:
            try:
                import urllib.request
                pure_code = index_code.replace("sz", "").replace("sh", "")
                prefix = "sz" if index_code.startswith("sz") else "sh"
                qq_url = f"https://qt.gtimg.cn/q={prefix}{pure_code}"
                req = urllib.request.Request(qq_url, headers={"User-Agent": "Mozilla/5.0"})
                resp = urllib.request.urlopen(req, timeout=10)
                raw = resp.read().decode("gbk")
                parts = raw.split("~")
                if len(parts) > 35:
                    latest_price = float(parts[3])
                    open_price = float(parts[5])
                    high_price = float(parts[33])
                    low_price = float(parts[34])
                    volume = int(parts[6]) if parts[6].isdigit() else 0

                    today_df = pd.DataFrame({
                        'close': [latest_price],
                        'open': [open_price],
                        'high': [high_price],
                        'low': [low_price],
                        'volume': [volume],
                    }, index=[pd.Timestamp(today)])

                    print(f"    [腾讯] 获取到今日数据: {today}, 最新价 {latest_price:.2f}")
                    hist_df = pd.concat([hist_df, today_df])
                    realtime_ok = True
                else:
                    print(f"    [腾讯] 数据格式异常")
            except Exception as e:
                print(f"    [腾讯] 实时数据获取失败: {e}")

        if not realtime_ok:
            print(f"    所有实时数据源均失败")
            print(f"    提示: 可以通过 manual_today_price 参数手动输入今日价格")

    return hist_df


try:
    index_code = "sz399673"

    # 检查是否需要手动输入今日价格
    import datetime
    import sys
    today = datetime.datetime.now().date()
    manual_price = None

    # 尝试获取数据，看是否包含今日
    try:
        temp_df = ak.stock_zh_index_daily_em(symbol=index_code)
        temp_df['date'] = pd.to_datetime(temp_df['date'])

        if temp_df['date'].max().date() < today:
            print(f"\n[提示] 历史数据最新日期为 {temp_df['date'].max().date()}，早于今天 {today}")
            print(f"[提示] 可以在代码中设置 manual_today_price 参数来添加今日数据")

            # 只有在交互式终端下才尝试获取用户输入
            if sys.stdin.isatty():
                try:
                    user_input = input("请输入今日收盘价 (直接回车跳过): ").strip()
                    if user_input and user_input.replace('.', '').replace('-', '').isdigit():
                        manual_price = float(user_input)
                        print(f"已设置今日收盘价为: {manual_price}")
                except (EOFError, ValueError):
                    print("跳过手动输入，使用历史数据")
    except Exception as e:
        print(f"检查数据日期失败: {e}")

    stock_zh_index_daily_df = get_index_data_with_realtime(index_code, "2018-01-01", manual_price)

    if stock_zh_index_daily_df.empty:
        raise ValueError(f"无法获取指数 {index_code} 的数据，请检查代码或网络。")

    print(f"\n[OK] 数据获取成功，期间为 {stock_zh_index_daily_df.index[0]} 到 {stock_zh_index_daily_df.index[-1]}")
    print(f"   数据量: {len(stock_zh_index_daily_df)} 条")
    print(f"   最新收盘价: {stock_zh_index_daily_df['close'].iloc[-1]:.2f}")

    observations = stock_zh_index_daily_df['close']
    dates = stock_zh_index_daily_df.index

except Exception as e:
    print(f"数据获取失败: {e}")
    # 如果数据获取失败，则退出
    exit()

# --- 2. 构建卡尔曼滤波模型 ---
print("\n正在构建卡尔曼滤波模型...")
# 状态转移矩阵 F: level(t) = level(t-1) + trend(t-1), trend(t) = trend(t-1)
transition_matrix = [[1, 1], [0, 1]]

# 观测矩阵 H: 我们只能观测到价格 level
observation_matrix = [[1, 0]]

# 初始状态均值
initial_state_mean = [observations.iloc[0], 0]

# 创建卡尔曼滤波器
kf = KalmanFilter(
    transition_matrices=transition_matrix,
    observation_matrices=observation_matrix,
    initial_state_mean=initial_state_mean,
    initial_state_covariance=np.eye(2),
    transition_covariance=np.eye(2) * 0.005,
    observation_covariance=2.0
)

# --- 3. 应用滤波器 ---
print("\n正在应用滤波器...")
# 使用前 EM_TRAIN_DAYS 个交易日的数据训练协方差参数，避免未来信息泄露
EM_TRAIN_DAYS = 250  # 约一年的交易日
em_train_data = observations.iloc[:EM_TRAIN_DAYS]
kf = kf.em(em_train_data, n_iter=5)
print(f"  EM训练期: {dates[0].strftime('%Y-%m-%d')} ~ {dates[min(EM_TRAIN_DAYS-1, len(dates)-1)].strftime('%Y-%m-%d')} ({len(em_train_data)}天)")
# 对全量观测值应用卡尔曼滤波（filter只用过去数据，不泄露未来信息）
(filtered_state_means, filtered_state_covariances) = kf.filter(observations)


# --- 4. 信号生成与回测 ---

# --- 4.1. 计算波动率 ---
daily_returns = observations.pct_change()
# 使用20天窗口计算滚动波动率
rolling_vol = daily_returns.rolling(window=20).std() * np.sqrt(252) # 年化波动率
# 使用固定波动率阈值（基于A股长期波动率分布的先验知识，无未来信息泄露）
vol_threshold = VOL_THRESHOLD_FIXED

# --- 4.2. 信号生成与过滤 (优化版) ---
print("\n正在分析交易信号 (应用趋势死区 + 确认期 + 波动率过滤器 + Kalman偏离过滤)...")
trend_series = pd.Series(filtered_state_means[:, 1], index=dates)
kalman_price_series = pd.Series(filtered_state_means[:, 0], index=dates)

# 计算趋势加速度（一阶差分）
trend_acceleration = trend_series.diff()

# 计算Kalman价格相对60日均线的偏离度
kalman_ma60 = kalman_price_series.rolling(60).mean()
kalman_slope_60 = (kalman_price_series - kalman_ma60) / kalman_ma60

# === 买入信号生成 ===
# 1. 趋势确认期：trend必须连续N天保持在阈值之上
trend_confirmed_buy = trend_series.rolling(window=CONFIRMATION_DAYS_BUY).min() >= TREND_THRESHOLD_BUY

# 2. 确认期刚刚满足（前一天未满足，今天满足 = 确认完成的那一天触发）
trend_just_confirmed_buy = trend_confirmed_buy & (~trend_confirmed_buy.shift(1, fill_value=False))

# 3. 趋势加速度过滤：买入时trend必须在增强（加速度≥0）
trend_accelerating = trend_acceleration >= 0

# 4. 波动率过滤器：只在低波动率时买入
vol_below_threshold = rolling_vol < vol_threshold

# 5. Kalman偏离过滤：kalman价格不能偏离60日均线太多（过滤深度熊市）
kalman_above_threshold = kalman_slope_60 > KALMAN_SLOPE_THRESH

# 综合买入信号：确认期刚满足 + 加速度 + 低波动率 + Kalman偏离
valid_buy_signals = trend_just_confirmed_buy & trend_accelerating & vol_below_threshold & kalman_above_threshold
up_dates = trend_series[valid_buy_signals].index

# === 卖出信号生成 ===
# 1. 趋势确认期：trend必须连续N天保持在阈值之下
trend_confirmed_sell = trend_series.rolling(window=CONFIRMATION_DAYS_SELL).max() <= TREND_THRESHOLD_SELL

# 2. 确认期刚刚满足
trend_just_confirmed_sell = trend_confirmed_sell & (~trend_confirmed_sell.shift(1, fill_value=False))

# 综合卖出信号：确认期刚满足
valid_sell_signals = trend_just_confirmed_sell
down_dates = trend_series[valid_sell_signals].index

# --- 4.3. 生成详细交易日志 ---
print("\n--- 详细交易信号日志 ---")
print(f"  信号参数: 买入确认={CONFIRMATION_DAYS_BUY}天, 卖出确认={CONFIRMATION_DAYS_SELL}天, "
      f"趋势买入阈值={TREND_THRESHOLD_BUY}, 趋势卖出阈值={TREND_THRESHOLD_SELL}")
print(f"  过滤条件: 波动率<{VOL_THRESHOLD_FIXED:.2f}, Kalman偏离>{KALMAN_SLOPE_THRESH}")
print(f"  风控参数: 早期止损={EARLY_STOP_DAYS}天/{EARLY_STOP_LOSS:.1%}, "
      f"追踪止损={TRAILING_STOP_PCT:.0%}, 最大亏损={MAX_LOSS_STOP:.0%}, "
      f"趋势衰减={TREND_PEAK_DECAY:.0%}, 最小持仓={MIN_HOLDING_DAYS}天")
print(f"  加仓逻辑: {SCALE_IN_DAYS}天涨幅>{SCALE_IN_THRESH:.1%}时加仓{SCALE_IN_SIZE:.0%}(总仓位{1.0+SCALE_IN_SIZE:.1f}x)")
buy_signals_df = pd.DataFrame({
    'Signal': 'Buy',
    'Price': observations.loc[up_dates],
    'Kalman Trend': trend_series.loc[up_dates],
    'Volatility': rolling_vol.loc[up_dates]
})

sell_signals_df = pd.DataFrame({
    'Signal': 'Sell',
    'Price': observations.loc[down_dates],
    'Kalman Trend': trend_series.loc[down_dates],
    'Volatility': rolling_vol.loc[down_dates]
})

all_trades_log = pd.concat([buy_signals_df, sell_signals_df]).sort_index()

# 打印完整的交易日志
if not all_trades_log.empty:
    with pd.option_context('display.max_rows', None, 'display.max_columns', None, 'display.width', 1000):
        print(all_trades_log)
else:
    print("在当前过滤条件下没有生成任何交易信号。")

# --- 5. 可视化分析 ---
import matplotlib.pyplot as plt
from matplotlib import rcParams

# 设置中文字体
rcParams['font.sans-serif'] = ['SimHei']  
rcParams['axes.unicode_minus'] = False

def plot_kalman_analysis(prices, smoothed_means, trend_series, up_dates, down_dates, equity_curve):
    """绘制卡尔曼滤波分析图表"""
    fig, axes = plt.subplots(4, 1, figsize=(14, 16))
    
    # 图1: 价格与卡尔曼滤波估计
    axes[0].plot(prices.index, prices, 'o-', alpha=0.3, label='原始价格', markersize=2, color='gray')
    axes[0].plot(prices.index, smoothed_means[:, 0], '-', label='卡尔曼滤波估计', linewidth=2, color='blue')
    axes[0].set_title('创业板50指数 - 卡尔曼滤波价格估计', fontsize=14, fontweight='bold')
    axes[0].set_ylabel('价格', fontsize=12)
    axes[0].legend(loc='upper left')
    axes[0].grid(True, alpha=0.3)
    
    # 图2: 趋势分析
    axes[1].plot(trend_series.index, trend_series, '-', label='卡尔曼趋势', linewidth=2, color='green')
    axes[1].axhline(y=0, color='red', linestyle='--', linewidth=1.5, label='0轴')
    
    # 标记买卖信号
    if not up_dates.empty:
        buy_prices = prices.loc[up_dates]
        axes[1].scatter(up_dates, trend_series.loc[up_dates], color='red', s=100, zorder=5, 
                       marker='^', label=f'买入信号({len(up_dates)}次)')
    
    if not down_dates.empty:
        axes[1].scatter(down_dates, trend_series.loc[down_dates], color='blue', s=100, zorder=5, 
                       marker='v', label=f'卖出信号({len(down_dates)}次)')
    
    # 填充正负趋势区域
    axes[1].fill_between(trend_series.index, 0, trend_series, where=trend_series >= 0, 
                        alpha=0.2, color='green')
    axes[1].fill_between(trend_series.index, 0, trend_series, where=trend_series < 0, 
                        alpha=0.2, color='red')
    
    axes[1].set_title('卡尔曼趋势分析与交易信号', fontsize=14, fontweight='bold')
    axes[1].set_ylabel('趋势值', fontsize=12)
    axes[1].legend(loc='upper left')
    axes[1].grid(True, alpha=0.3)
    
    # 图3: 波动率分析
    daily_returns = prices.pct_change()
    rolling_vol = daily_returns.rolling(window=20).std() * np.sqrt(252)

    axes[2].plot(rolling_vol.index, rolling_vol, '-', label='滚动波动率(20日)', linewidth=2, color='orange')
    axes[2].axhline(y=VOL_THRESHOLD_FIXED, color='red', linestyle='--', linewidth=1.5, label=f'波动率阈值({VOL_THRESHOLD_FIXED:.2f})')
    axes[2].fill_between(rolling_vol.index, 0, rolling_vol, alpha=0.3, color='yellow')
    axes[2].set_title('市场波动率分析', fontsize=14, fontweight='bold')
    axes[2].set_ylabel('年化波动率', fontsize=12)
    axes[2].legend(loc='upper left')
    axes[2].grid(True, alpha=0.3)
    
    # 图4: 资产曲线
    axes[3].plot(equity_curve.index, equity_curve, '-', label='资产曲线', linewidth=2, color='purple')
    axes[3].fill_between(equity_curve.index, 100000, equity_curve, where=equity_curve >= 100000, 
                        alpha=0.3, color='green')
    axes[3].fill_between(equity_curve.index, 100000, equity_curve, where=equity_curve < 100000, 
                        alpha=0.3, color='red')
    axes[3].axhline(y=100000, color='black', linestyle='--', linewidth=1, label='初始资金')
    
    # 计算最大回撤区域
    running_max = equity_curve.cummax()
    drawdown = (equity_curve - running_max) / running_max
    max_dd_idx = drawdown.idxmin()
    max_dd_value = drawdown.min()
    axes[3].scatter([max_dd_idx], [equity_curve.loc[max_dd_idx]], color='red', s=200, zorder=5, 
                   marker='v', label=f'最大回撤({max_dd_value:.1%})')
    
    axes[3].set_title('策略资产曲线', fontsize=14, fontweight='bold')
    axes[3].set_xlabel('日期', fontsize=12)
    axes[3].set_ylabel('资产价值 ($)', fontsize=12)
    axes[3].legend(loc='upper left')
    axes[3].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('kalman_filter_analysis.png', dpi=300, bbox_inches='tight')
    print("卡尔曼滤波分析图表已保存为 kalman_filter_analysis.png")
    plt.close()  # 关闭图形，避免阻塞

# 注释掉临时图表绘制，将在回测完成后绘制完整图表
# plot_kalman_analysis(observations, filtered_state_means, trend_series, up_dates, down_dates, 
#                     pd.Series(100000, index=observations.index).cumprod())

# --- 6. 高级回测与统计 ---
def run_advanced_backtest(up_dates, down_dates, prices, trend_series, initial_capital=100000.0):
    print("\n--- 开始高级回测与统计 (日线级别) ---")
    print(f"  风控参数: 追踪止损={TRAILING_STOP_PCT:.0%}, 最大亏损止损={MAX_LOSS_STOP:.0%}, "
          f"最小持仓={MIN_HOLDING_DAYS}天, 趋势衰减={TREND_PEAK_DECAY:.0%}")
    print(f"  早期止损: {EARLY_STOP_DAYS}天/{EARLY_STOP_LOSS:.1%}")
    print(f"  加仓逻辑: {SCALE_IN_DAYS}天涨幅>{SCALE_IN_THRESH:.1%}时加仓{SCALE_IN_SIZE:.0%}(总仓位{1.0+SCALE_IN_SIZE:.1f}x)")

    # 1. 构建原始信号序列
    signals = pd.Series(0, index=prices.index)
    signals.loc[up_dates] = 1   # 买入信号
    signals.loc[down_dates] = -1  # 卖出信号

    # 2. 逐日模拟：T日产生信号，T+1日以收盘价执行
    position_state = 0       # 0=空仓, 1=持仓
    entry_price = 0.0        # 实际入场价格（T+1日收盘价）
    entry_date = None         # 实际入场日期
    holding_days = 0          # 持仓天数（从实际入场日开始计）
    kalman_high = 0.0         # 持仓期间卡尔曼滤波价格最高点
    trend_peak = 0.0          # 持仓期间趋势峰值
    position_size = 0.0       # 仓位大小: 0, 1.0, 或 1.0+SCALE_IN_SIZE
    scaled_in = False         # 是否已加仓
    pending_entry = False     # 待执行的买入信号（T日产生，T+1执行）
    pending_exit = False      # 待执行的卖出信号
    pending_exit_reason = ''  # 待执行卖出的原因

    positions = pd.Series(0.0, index=prices.index)
    exit_reasons = {}         # 记录每次退出原因（key=实际卖出日）
    trade_records = []        # 逐笔交易记录

    for i in range(len(prices)):
        date = prices.index[i]
        price = prices.iloc[i]
        trend = trend_series.iloc[i] if i < len(trend_series) else 0

        # === 执行前一天的待处理卖出信号 ===
        # 卖出日仍持仓（承受当天涨跌），以收盘价卖出
        if pending_exit and position_state == 1:
            sell_position_size = 1.0 + (SCALE_IN_SIZE if scaled_in else 0)
            trade_return = (price - entry_price) / entry_price if entry_price > 0 else 0
            trade_records.append({
                'buy_date': entry_date, 'buy_price': entry_price,
                'sell_date': date, 'sell_price': price,
                'return': trade_return, 'reason': pending_exit_reason,
                'scaled': scaled_in, 'holding_days': holding_days + 1,
                'position_size': sell_position_size
            })
            exit_reasons[date] = pending_exit_reason
            positions.iloc[i] = sell_position_size  # 卖出日仍按原仓位计算收益
            position_state = 0
            position_size = 0.0
            pending_exit = False
            pending_exit_reason = ''
            continue  # 卖出日处理完毕，不再进入持仓逻辑

        # === 执行前一天的待处理买入信号 ===
        # 买入日以收盘价买入，当天不计收益
        if pending_entry and position_state == 0:
            position_state = 1
            entry_price = price  # 以T+1日收盘价作为实际入场价
            entry_date = date
            holding_days = 0
            kalman_high = filtered_state_means[i, 0] if i < len(filtered_state_means) else price
            trend_peak = trend
            position_size = 1.0
            scaled_in = False
            pending_entry = False
            positions.iloc[i] = 0  # 买入日不计收益
            continue  # 买入日处理完毕，不进入持仓逻辑（修复holding_days多算1天）

        # === 持仓中的逻辑 ===
        if position_state == 1:
            holding_days += 1
            kalman_price = filtered_state_means[i, 0] if i < len(filtered_state_means) else price
            kalman_high = max(kalman_high, kalman_price)
            trend_peak = max(trend_peak, trend)
            pnl = (price - entry_price) / entry_price if entry_price > 0 else 0

            # === 加仓逻辑 ===
            if not scaled_in and holding_days == SCALE_IN_DAYS and pnl >= SCALE_IN_THRESH:
                position_size = 1.0 + SCALE_IN_SIZE
                scaled_in = True

            # === 止损检查（T日产生信号，T+1执行）===
            exit_reason = None

            # 检查0: 早期止损
            if holding_days >= EARLY_STOP_DAYS and holding_days < MIN_HOLDING_DAYS:
                if pnl <= -EARLY_STOP_LOSS:
                    exit_reason = f'早期止损({holding_days}天亏损{pnl:.2%})'

            if exit_reason is None and holding_days >= MIN_HOLDING_DAYS:
                # 检查1: 追踪止损
                if kalman_high > 0 and (kalman_price - kalman_high) / kalman_high <= -TRAILING_STOP_PCT:
                    exit_reason = f'追踪止损(卡尔曼价从{kalman_high:.1f}回撤{TRAILING_STOP_PCT:.0%})'
                # 检查2: 趋势衰减退出
                if exit_reason is None and trend_peak > 0 and trend <= trend_peak * (1 - TREND_PEAK_DECAY):
                    exit_reason = f'趋势衰减(峰值{trend_peak:.3f}→{trend:.3f})'

            # 检查3: 最大亏损止损 - 不受最小持仓天数限制
            if exit_reason is None and entry_price > 0 and pnl <= -MAX_LOSS_STOP:
                exit_reason = f'最大亏损止损({MAX_LOSS_STOP:.0%})'

            # 检查4: 原始卖出信号（需满足最小持仓天数）
            if exit_reason is None and signals.iloc[i] == -1 and holding_days >= MIN_HOLDING_DAYS:
                exit_reason = '趋势卖出信号'

            if exit_reason:
                pending_exit = True
                pending_exit_reason = exit_reason

        # === 开仓信号（T日产生，T+1执行）===
        if signals.iloc[i] == 1 and position_state == 0 and not pending_entry:
            pending_entry = True

        positions.iloc[i] = position_size

    # 3. 计算策略每日收益率（positions已经是实际持仓，无需shift）
    daily_returns = prices.pct_change().fillna(0)
    strategy_returns = daily_returns * positions

    # 4. 计算资产曲线
    equity_curve = (1 + strategy_returns).cumprod() * initial_capital

    # 5. 计算各项统计指标
    final_value = equity_curve.iloc[-1]
    total_return = (final_value / initial_capital) - 1

    # 最大回撤
    running_max = equity_curve.cummax()
    drawdown = (equity_curve - running_max) / running_max
    max_drawdown = drawdown.min()

    # 年度收益率
    yearly_returns = equity_curve.resample('YE').ffill().pct_change().fillna(0)

    # 夏普比率 (日线级别年化因子)
    sharpe_ratio = (strategy_returns.mean() / strategy_returns.std()) * np.sqrt(252) if strategy_returns.std() > 0 else 0

    # --- 逐笔交易分析（直接使用trade_records，不再从positions推断）---
    num_trades = len(trade_records)
    winning_trades = sum(1 for t in trade_records if t['return'] > 0)

    print("\n--- 逐笔交易盈亏分析 (日线级别) ---")
    print(f"  风控参数: 早期止损={EARLY_STOP_DAYS}天/{EARLY_STOP_LOSS:.1%}, "
          f"追踪止损={TRAILING_STOP_PCT:.0%}, 最大亏损={MAX_LOSS_STOP:.0%}, "
          f"趋势衰减={TREND_PEAK_DECAY:.0%}, 最小持仓={MIN_HOLDING_DAYS}天")
    print(f"  加仓逻辑: {SCALE_IN_DAYS}天涨幅>{SCALE_IN_THRESH:.1%}时加仓{SCALE_IN_SIZE:.0%}(总仓位{1.0+SCALE_IN_SIZE:.1f}x)")
    print(f"{'#':<4} {'买入时间':<12} {'买入价':<10} {'卖出时间':<12} {'卖出价':<10} {'持仓天':<7} {'仓位':<6} {'收益率':<10} {'退出原因'}")
    print("-" * 110)

    for idx, t in enumerate(trade_records):
        pos_str = f"{t['position_size']:.1f}x" if t['scaled'] else '1.0x'
        print(f"{idx+1:<4} {str(t['buy_date'].date()):<12} {t['buy_price']:<10.2f} "
              f"{str(t['sell_date'].date()):<12} {t['sell_price']:<10.2f} "
              f"{t['holding_days']:<7} {pos_str:<6} {t['return']:<+10.2%} {t['reason']}")

    print("-" * 110)
    win_rate = winning_trades / num_trades if num_trades > 0 else 0

    # 6. 打印结果
    print("\n--- 回测结果摘要 (日线级别) ---")
    print(f"时间范围: {prices.index[0]} to {prices.index[-1]}")
    print(f"初始资金: {initial_capital:,.2f}")
    print(f"最终资产: {final_value:,.2f}")
    print(f"总收益率: {total_return:.2%}")
    print(f"最大回撤: {max_drawdown:.2%}")
    print(f"夏普比率: {sharpe_ratio:.2f}")
    print(f"总交易对数 (买入-卖出): {num_trades}")
    print(f"胜率: {win_rate:.2%}")

    # 统计退出原因
    if trade_records:
        print("\n--- 退出原因统计 ---")
        reason_counts = {}
        for t in trade_records:
            key = t['reason'].split('(')[0]
            reason_counts[key] = reason_counts.get(key, 0) + 1
        for reason, count in sorted(reason_counts.items(), key=lambda x: -x[1]):
            print(f"  {reason}: {count}次")

    print("\n各年度收益率:")
    if not yearly_returns.empty:
        for year, ret in yearly_returns.items():
            if year.year == equity_curve.index[0].year and ret == 0:
                first_year_end = equity_curve[equity_curve.index.year == year.year].index[-1]
                first_year_start_val = initial_capital
                first_year_end_val = equity_curve[first_year_end]
                ret = (first_year_end_val / first_year_start_val) - 1
            print(f"  {year.year}: {ret:.2%}")
    else:
        print("  没有足够的数据来计算年度收益。")

    return equity_curve

# 运行主逻辑
equity_curve = run_advanced_backtest(up_dates, down_dates, observations, trend_series)

# 重新绘制包含正确资产曲线的图表
print("\n正在重新生成包含完整资产曲线的分析图表...")
plot_kalman_analysis(observations, filtered_state_means, trend_series, up_dates, down_dates, equity_curve)

# --- 导出交易信号到CSV ---
if not all_trades_log.empty:
    csv_file_path = 'kalman_daily_signals.csv'
    all_trades_log.to_csv(csv_file_path, encoding='utf-8-sig')
    print(f"\n交易信号已成功导出到 {csv_file_path}")
else:
    print("\n没有交易信号可导出。")

# --- 打印所有交易信号详情 ---
def print_all_signals(prices, trend_series, rolling_vol, up_dates, down_dates):
    """
    打印所有交易信号的详情

    Args:
        prices: 价格序列
        trend_series: 趋势序列
        rolling_vol: 波动率序列
        up_dates: 买入信号日期
        down_dates: 卖出信号日期
    """
    print(f"\n{'='*90}")
    print(f"所有交易信号详情 (共 {len(up_dates)} 个买入 + {len(down_dates)} 个卖出 = {len(up_dates)+len(down_dates)} 个信号)")
    print(f"{'='*90}")

    print(f"\n{'-'*90}")
    print(f"{'#':<5} {'日期':<12} {'信号':<10} {'收盘价':<10} {'Trend':<12} {'波动率':<12}")
    print(f"{'-'*90}")

    # 合并所有信号日期并排序
    all_signal_dates = sorted(set(up_dates) | set(down_dates))

    for idx, date in enumerate(all_signal_dates):
        date_str = date.strftime('%Y-%m-%d')
        price = prices.loc[date]
        trend = trend_series.loc[date]
        vol = rolling_vol.loc[date] if date in rolling_vol.index and not np.isnan(rolling_vol.loc[date]) else 0.0

        if date in up_dates:
            signal_type = '买入'
        else:
            signal_type = '卖出'

        print(f"{idx+1:<5} {date_str:<12} {signal_type:<10} {price:<10.2f} {trend:<12.4f} {vol:<12.4f}")

    print(f"{'-'*90}")

    # 当前市场状态
    last_trend = trend_series.iloc[-1]
    last_vol = rolling_vol.iloc[-1]
    last_price = prices.iloc[-1]

    print(f"\n当前市场状态 ({prices.index[-1].strftime('%Y-%m-%d')}):")
    print(f"  收盘价: {last_price:.2f}")
    print(f"  趋势值: {last_trend:.4f}")
    print(f"  波动率: {last_vol:.4f}")

    if last_trend > 0:
        print(f"  趋势判断: 上升趋势")
        if len(down_dates) > 0:
            last_sell = down_dates[-1]
            days_since_sell = (prices.index[-1] - last_sell).days
            print(f"  距上次卖出: {days_since_sell} 天 ({last_sell.strftime('%Y-%m-%d')})")
    else:
        print(f"  趋势判断: 下降趋势")
        if len(up_dates) > 0:
            last_buy = up_dates[-1]
            days_since_buy = (prices.index[-1] - last_buy).days
            print(f"  距上次买入: {days_since_buy} 天 ({last_buy.strftime('%Y-%m-%d')})")

    if last_trend < 0:
        print(f"\n下次买入条件: Trend需要从负值穿越到正值，且波动率 < {VOL_THRESHOLD_FIXED:.4f}")

    print(f"{'='*90}\n")


# 打印所有交易信号详情
print_all_signals(
    observations,
    trend_series,
    rolling_vol,
    up_dates,
    down_dates,
)

# --- 打印最近20天逐日信号判断详情 ---
def print_recent_daily_detail(prices, trend_series, rolling_vol, kalman_slope_60,
                               trend_acceleration, up_dates, down_dates, n_days=20):
    """
    打印最近N个交易日的逐日信号判断详情，帮助做出交易决策

    每天显示各项买入/卖出条件是否满足，以及最终信号结果
    """
    print(f"\n{'='*120}")
    print(f"最近 {n_days} 个交易日信号判断详情")
    print(f"{'='*120}")
    print(f"  买入条件: Trend>={TREND_THRESHOLD_BUY}(确认{CONFIRMATION_DAYS_BUY}天) + 加速度>=0 + 波动率<{VOL_THRESHOLD_FIXED} + Kalman偏离>{KALMAN_SLOPE_THRESH}")
    print(f"  卖出条件: Trend<={TREND_THRESHOLD_SELL}(确认{CONFIRMATION_DAYS_SELL}天)")

    print(f"\n{'-'*120}")
    print(f"{'日期':<12} {'收盘价':<10} {'Trend':<10} {'加速度':<10} {'波动率':<10} {'K偏离':<10} "
          f"{'趋势':<6} {'加速':<6} {'波动':<6} {'偏离':<6} {'信号':<10} {'说明'}")
    print(f"{'-'*120}")

    recent_idx = prices.index[-n_days:]

    for date in recent_idx:
        price = prices.loc[date]
        trend = trend_series.loc[date]
        accel = trend_acceleration.loc[date] if date in trend_acceleration.index else np.nan
        vol = rolling_vol.loc[date] if date in rolling_vol.index else np.nan
        k_slope = kalman_slope_60.loc[date] if date in kalman_slope_60.index else np.nan

        # 各条件判断
        trend_ok = trend >= TREND_THRESHOLD_BUY
        accel_ok = (not np.isnan(accel)) and accel >= 0
        vol_ok = (not np.isnan(vol)) and vol < VOL_THRESHOLD_FIXED
        slope_ok = (not np.isnan(k_slope)) and k_slope > KALMAN_SLOPE_THRESH

        # 信号判断
        signal = ''
        reason = ''
        if date in up_dates:
            signal = '>>买入'
            reason = '所有买入条件满足'
        elif date in down_dates:
            signal = '>>卖出'
            reason = f'Trend连续{CONFIRMATION_DAYS_SELL}天<={TREND_THRESHOLD_SELL}'
        else:
            signal = '-'
            # 分析未触发原因
            if trend >= TREND_THRESHOLD_BUY:
                blocked = []
                if not accel_ok:
                    blocked.append('加速度<0')
                if not vol_ok:
                    blocked.append(f'波动率过高')
                if not slope_ok:
                    blocked.append('K偏离过低')
                if blocked:
                    reason = '趋势正但' + '+'.join(blocked)
                else:
                    reason = '非确认触发日'
            elif trend <= TREND_THRESHOLD_SELL:
                reason = '下降趋势(未满足确认期)'
            else:
                reason = '趋势在死区内' if trend < TREND_THRESHOLD_BUY else '观望'

        date_str = date.strftime('%Y-%m-%d')
        t_mark = 'Y' if trend_ok else 'N'
        a_mark = 'Y' if accel_ok else 'N'
        v_mark = 'Y' if vol_ok else 'N'
        s_mark = 'Y' if slope_ok else 'N'

        vol_str = f"{vol:.4f}" if not np.isnan(vol) else "N/A"
        accel_str = f"{accel:.4f}" if not np.isnan(accel) else "N/A"
        k_slope_str = f"{k_slope:.4f}" if not np.isnan(k_slope) else "N/A"

        print(f"{date_str:<12} {price:<10.2f} {trend:<10.4f} {accel_str:<10} {vol_str:<10} {k_slope_str:<10} "
              f"{t_mark:<6} {a_mark:<6} {v_mark:<6} {s_mark:<6} {signal:<10} {reason}")

    print(f"{'-'*120}")
    print(f"{'='*120}\n")


# 打印最近20天逐日信号判断详情
print_recent_daily_detail(
    observations,
    trend_series,
    rolling_vol,
    kalman_slope_60,
    trend_acceleration,
    up_dates,
    down_dates,
    n_days=20,
)

print("\n分析完成。")

# --- 如何解读和评估 ---
#
# 1. **价格平滑**:
#    平滑后的价格代表了我们估计的指数"真实内在价值"，过滤掉了大部分短期市场噪声。
#
# 2. **趋势判断**:
#    估计的趋势（速度）用于判断市场方向。
#    - **当 Trend > 0**: 表明目前处于上升趋势。
#    - **当 Trend < 0**: 表明目前处于下降趋势。
#    - **Trend 从负值上穿0轴**: 可能是一个潜在的买入信号。
#    - **Trend 从正值下穿0轴**: 可能是一个潜在的卖出信号。
#
# 3. **波动率过滤器**:
#    当市场波动率低于特定阈值时，才允许买入信号生效。旨在避免在混乱、高风险的市场环境中入场。
#
# 4. **免责声明**:
#    此模型是一个简化的分析工具，而非精准的预测系统。
#    滤波器的表现依赖于参数设置和市场状态，历史规律不代表未来。
#    在实际应用中，需要结合更多指标和风险管理策略。