模拟盘数据统计与月度复盘方法

模拟盘数据统计与月度复盘方法

模拟盘跑了一段时间,交易记录攒了几百条,但很多人只是看看总盈亏就结束了。这样跑模拟盘的意义不大------真正有价值的是从交易记录中提取统计指标,做系统性复盘,找到策略的衰减点和改进方向。

这篇文章分享一套完整的模拟盘数据统计与月度复盘方法,包含可复用的Python代码。

一、数据准备

假设你的模拟盘交易记录已经导出为CSV,字段大致如下:

csv 复制代码
trade_id,symbol,direction,open_time,close_time,open_price,close_price,volume,pnl,commission
1,RB2405,long,2024-01-02 09:35,2024-01-02 14:20,3980,4010,10,300,12
2,RB2405,short,2024-01-03 10:10,2024-01-03 11:05,4020,4035,10,-150,12
...

先做统一的数据加载和预处理:

python 复制代码
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

def load_trades(filepath):
    df = pd.read_csv(filepath, parse_dates=['open_time', 'close_time'])
    # 净盈亏 = 毛利 - 手续费
    df['net_pnl'] = df['pnl'] - df['commission']
    # 持仓时长(分钟)
    df['hold_minutes'] = (df['close_time'] - df['open_time']).dt.total_seconds() / 60
    # 按平仓时间排序
    df = df.sort_values('close_time').reset_index(drop=True)
    return df

trades = load_trades('sim_trades.csv')
print(f"总交易数: {len(trades)}")
print(f"时间范围: {trades['close_time'].min()} ~ {trades['close_time'].max()}")

二、核心统计指标

2.1 胜率与盈亏比

胜率单独看没有意义,必须和盈亏比结合。一个胜率40%但盈亏比3:1的策略,远好于胜率70%但盈亏比0.5:1的策略。

python 复制代码
def calc_basic_stats(df):
    wins = df[df['net_pnl'] > 0]
    losses = df[df['net_pnl'] < 0]

    win_rate = len(wins) / len(df) if len(df) > 0 else 0
    avg_win = wins['net_pnl'].mean() if len(wins) > 0 else 0
    avg_loss = abs(losses['net_pnl'].mean()) if len(losses) > 0 else 0
    profit_loss_ratio = avg_win / avg_loss if avg_loss > 0 else np.inf

    # 期望值 = 胜率 * 平均盈利 - 败率 * 平均亏损
    expectancy = win_rate * avg_win - (1 - win_rate) * avg_loss

    stats = {
        '总交易数': len(df),
        '盈利笔数': len(wins),
        '亏损笔数': len(losses),
        '胜率': round(win_rate, 4),
        '平均盈利': round(avg_win, 2),
        '平均亏损': round(avg_loss, 2),
        '盈亏比': round(profit_loss_ratio, 2),
        '单笔期望值': round(expectancy, 2),
        '总净盈亏': round(df['net_pnl'].sum(), 2),
        '最大单笔盈利': round(df['net_pnl'].max(), 2),
        '最大单笔亏损': round(df['net_pnl'].min(), 2),
    }
    return stats

stats = calc_basic_stats(trades)
for k, v in stats.items():
    print(f"{k}: {v}")

2.2 期望值的意义

单笔期望值是策略能否持续盈利的核心判断标准。如果期望值 ≤ 0,无论胜率多高,长期跑下去必然亏损。这个指标比总盈亏更能反映策略质量,因为它不受交易次数的影响。

2.3 最大连亏与最大回撤

python 复制代码
def calc_drawdown_series(df):
    """计算资金曲线和回撤序列"""
    df = df.copy()
    df['cum_pnl'] = df['net_pnl'].cumsum()
    df['peak'] = df['cum_pnl'].cummax()
    df['drawdown'] = df['cum_pnl'] - df['peak']
    df['drawdown_pct'] = df['drawdown'] / (df['peak'].abs() + 1e-9)
    return df

def calc_streak(df):
    """计算最大连胜和最大连亏"""
    pnl_sign = np.sign(df['net_pnl'].values)
    max_win_streak = max_loss_streak = 0
    cur_win = cur_loss = 0

    for s in pnl_sign:
        if s > 0:
            cur_win += 1
            cur_loss = 0
        elif s < 0:
            cur_loss += 1
            cur_win = 0
        else:
            cur_win = cur_loss = 0
        max_win_streak = max(max_win_streak, cur_win)
        max_loss_streak = max(max_loss_streak, cur_loss)

    return max_win_streak, max_loss_streak

trades = calc_drawdown_series(trades)
max_win_streak, max_loss_streak = calc_streak(trades)
max_dd = trades['drawdown'].min()

print(f"最大连胜: {max_win_streak}")
print(f"最大连亏: {max_loss_streak}")
print(f"最大回撤(绝对值): {max_dd:.2f}")

最大连亏决定了你需要的心理承受能力和资金储备。如果一个策略历史上出现过连续12笔亏损,那么你至少要做好连亏15笔的准备。

三、资金曲线分析

3.1 资金曲线绘制

python 复制代码
def plot_equity_curve(df, save_path='equity_curve.png'):
    fig, axes = plt.subplots(2, 1, figsize=(14, 8), 
                              gridspec_kw={'height_ratios': [3, 1]})

    # 资金曲线
    axes[0].plot(df['close_time'], df['cum_pnl'], 
                 color='#2196F3', linewidth=1.5)
    axes[0].fill_between(df['close_time'], df['cum_pnl'], 
                          alpha=0.15, color='#2196F3')
    axes[0].set_title('模拟盘资金曲线', fontsize=14)
    axes[0].set_ylabel('累计净盈亏')
    axes[0].grid(True, alpha=0.3)

    # 回撤
    axes[1].fill_between(df['close_time'], df['drawdown'], 0, 
                          color='#F44336', alpha=0.5)
    axes[1].set_title('回撤曲线', fontsize=12)
    axes[1].set_ylabel('回撤')
    axes[1].grid(True, alpha=0.3)

    plt.tight_layout()
    plt.savefig(save_path, dpi=150)
    plt.close()

plot_equity_curve(trades)

3.2 资金曲线的形态判断

看资金曲线不只是看涨跌,要关注形态:

  • 平滑上升:策略稳定,回撤可控
  • 阶梯式上升:策略有周期性,某些行情下表现好
  • 高位震荡:策略可能已经失效,需要警惕
  • 持续新低:策略逻辑有问题,应立即停止

可以用线性回归斜率来量化资金曲线的趋势:

python 复制代码
from scipy import stats as sp_stats

def equity_trend(df):
    """用线性回归斜率衡量资金曲线趋势"""
    x = np.arange(len(df))
    y = df['cum_pnl'].values
    slope, intercept, r_value, p_value, std_err = sp_stats.linregress(x, y)
    return {
        'slope': round(slope, 4),
        'r_squared': round(r_value**2, 4),
        'p_value': round(p_value, 6)
    }

trend = equity_trend(trades)
print(f"资金曲线斜率: {trend['slope']} (每笔交易平均盈亏)")
print(f"R²: {trend['r_squared']}")

斜率代表每笔交易的平均贡献,R²代表资金曲线的稳定性。R²越高,说明盈利越稳定。

四、月度复盘报告

4.1 按月分组统计

python 复制代码
def monthly_report(df):
    df = df.copy()
    df['month'] = df['close_time'].dt.to_period('M')

    monthly = df.groupby('month').agg(
        trades=('net_pnl', 'count'),
        total_pnl=('net_pnl', 'sum'),
        avg_pnl=('net_pnl', 'mean'),
        win_count=('net_pnl', lambda x: (x > 0).sum()),
        max_win=('net_pnl', 'max'),
        max_loss=('net_pnl', 'min'),
        avg_hold=('hold_minutes', 'mean'),
    ).reset_index()

    monthly['win_rate'] = (monthly['win_count'] / monthly['trades']).round(4)
    monthly['total_pnl'] = monthly['total_pnl'].round(2)
    monthly['avg_pnl'] = monthly['avg_pnl'].round(2)

    return monthly

monthly = monthly_report(trades)
print(monthly.to_string(index=False))

4.2 月度对比可视化

python 复制代码
def plot_monthly_comparison(monthly_df, save_path='monthly_report.png'):
    fig, axes = plt.subplots(1, 3, figsize=(16, 5))

    months = monthly_df['month'].astype(str)

    # 月度盈亏
    colors = ['#4CAF50' if x > 0 else '#F44336' for x in monthly_df['total_pnl']]
    axes[0].bar(months, monthly_df['total_pnl'], color=colors)
    axes[0].set_title('月度净盈亏')
    axes[0].tick_params(axis='x', rotation=45)
    axes[0].axhline(y=0, color='black', linewidth=0.8)

    # 月度胜率
    axes[1].plot(months, monthly_df['win_rate'], 
                 marker='o', color='#2196F3', linewidth=2)
    axes[1].axhline(y=0.5, color='gray', linestyle='--', alpha=0.5)
    axes[1].set_title('月度胜率')
    axes[1].set_ylim(0, 1)
    axes[1].tick_params(axis='x', rotation=45)

    # 月度交易次数
    axes[2].bar(months, monthly_df['trades'], color='#FF9800', alpha=0.7)
    axes[2].set_title('月度交易次数')
    axes[2].tick_params(axis='x', rotation=45)

    plt.tight_layout()
    plt.savefig(save_path, dpi=150)
    plt.close()

plot_monthly_comparison(monthly)

4.3 复盘报告模板

每月复盘时,按以下结构输出:

python 复制代码
def generate_monthly_review(df, target_month):
    """生成指定月份的复盘报告"""
    df['month'] = df['close_time'].dt.to_period('M')
    mdf = df[df['month'] == target_month]

    if len(mdf) == 0:
        return f"{target_month} 无交易记录"

    stats = calc_basic_stats(mdf)
    max_win_streak, max_loss_streak = calc_streak(mdf)

    # 分方向统计
    long_trades = mdf[mdf['direction'] == 'long']
    short_trades = mdf[mdf['direction'] == 'short']

    report = f"""
{'='*50}
{target_month} 月度复盘报告
{'='*50}

【整体表现】
  交易次数: {stats['总交易数']}
  净盈亏: {stats['总净盈亏']}
  胜率: {stats['胜率']*100:.1f}%
  盈亏比: {stats['盈亏比']}
  单笔期望值: {stats['单笔期望值']}

【风险指标】
  最大连胜: {max_win_streak}
  最大连亏: {max_loss_streak}
  最大单笔盈利: {stats['最大单笔盈利']}
  最大单笔亏损: {stats['最大单笔亏损']}

【分方向】
  多头: {len(long_trades)}笔, 盈亏 {long_trades['net_pnl'].sum():.2f}
  空头: {len(short_trades)}笔, 盈亏 {short_trades['net_pnl'].sum():.2f}

【持仓分析】
  平均持仓时长: {mdf['hold_minutes'].mean():.0f} 分钟
  盈利单平均持仓: {mdf[mdf['net_pnl']>0]['hold_minutes'].mean():.0f} 分钟
  亏损单平均持仓: {mdf[mdf['net_pnl']<0]['hold_minutes'].mean():.0f} 分钟
"""
    return report

# 使用示例
print(generate_monthly_review(trades, pd.Period('2024-01', 'M')))

五、策略表现趋势追踪

单月数据可能有偶然性,需要追踪指标的月度变化趋势,判断策略是否在衰减。

python 复制代码
def track_performance_trend(df):
    """追踪策略关键指标的月度趋势"""
    df = df.copy()
    df['month'] = df['close_time'].dt.to_period('M')

    monthly = df.groupby('month').apply(
        lambda x: pd.Series({
            'win_rate': (x['net_pnl'] > 0).mean(),
            'avg_win': x[x['net_pnl'] > 0]['net_pnl'].mean() if (x['net_pnl'] > 0).any() else 0,
            'avg_loss': abs(x[x['net_pnl'] < 0]['net_pnl'].mean()) if (x['net_pnl'] < 0).any() else 0,
            'expectancy': x['net_pnl'].mean(),
            'trade_count': len(x),
        })
    ).reset_index()

    monthly['pl_ratio'] = monthly['avg_win'] / monthly['avg_loss'].replace(0, np.nan)

    # 计算滚动均值(3个月)
    for col in ['win_rate', 'pl_ratio', 'expectancy']:
        monthly[f'{col}_ma3'] = monthly[col].rolling(3, min_periods=1).mean()

    return monthly

trend_df = track_performance_trend(trades)
print(trend_df.to_string(index=False))

5.1 衰减信号识别

python 复制代码
def detect_decay(trend_df, lookback=3):
    """检测策略是否出现衰减"""
    if len(trend_df) < lookback + 1:
        return "数据不足,无法判断"

    recent = trend_df.tail(lookback)
    signals = []

    # 期望值连续为负
    if (recent['expectancy'] < 0).all():
        signals.append("⚠️ 近{}个月期望值持续为负".format(lookback))

    # 胜率持续下降
    if len(trend_df) >= lookback * 2:
        prev_wr = trend_df['win_rate'].iloc[-lookback*2:-lookback].mean()
        curr_wr = recent['win_rate'].mean()
        if curr_wr < prev_wr * 0.8:
            signals.append(f"⚠️ 胜率下降超过20% ({prev_wr:.2%} → {curr_wr:.2%})")

    # 盈亏比恶化
    if len(trend_df) >= lookback * 2:
        prev_pl = trend_df['pl_ratio'].iloc[-lookback*2:-lookback].mean()
        curr_pl = recent['pl_ratio'].mean()
        if curr_pl < prev_pl * 0.7:
            signals.append(f"⚠️ 盈亏比恶化 ({prev_pl:.2f} → {curr_pl:.2f})")

    if not signals:
        return "✅ 策略表现正常,未检测到明显衰减"

    return "\n".join(signals)

print(detect_decay(trend_df))

5.2 按品种/方向拆分

如果策略同时交易多个品种,需要拆分统计,找出哪些品种在赚钱、哪些在亏钱:

python 复制代码
def breakdown_by_symbol(df):
    """按品种拆分统计"""
    result = df.groupby('symbol').agg(
        trades=('net_pnl', 'count'),
        total_pnl=('net_pnl', 'sum'),
        win_rate=('net_pnl', lambda x: (x > 0).mean()),
        avg_pnl=('net_pnl', 'mean'),
    ).round(4).sort_values('total_pnl', ascending=False)
    return result

symbol_stats = breakdown_by_symbol(trades)
print(symbol_stats.to_string())

如果一个品种连续三个月亏损,考虑是否要把它从策略池中剔除。如果某个品种贡献了80%的利润,要警惕过度集中。

六、自动化复盘流程

把上面的逻辑串起来,每月初自动生成上月复盘:

```python

def auto_monthly_review(filepath):

"""一键生成月度复盘"""

trades = load_trades(filepath)

trades = calc_drawdown_series(trades)

复制代码
# 上月
last_month = (datetime.now() - pd.DateOffset(months=1)).to_period('

⭐ 今日观察列表

以下标的来自多周期趋势扫描系统(Gate.io合约),仅供技术分析学习参考,不构成任何投资建议。读者请自行判断。

数据更新时间:2026-10-06 09:01:31

标的 方向 信号类型 现价 参考止盈 参考止损 盈亏比
NEAR 🔼 做多 高质量标的做多 $5.2992 $5.5059 $5.1932 1:1.9

共 1 个标的。扫描频率:每15分钟更新。


以上数据由量化扫描系统自动生成,如有兴趣了解更多技术细节,请关注本站后续文章。

相关推荐
贾伟康1 个月前
【中国方言题库|11】HarmonyOS ArkTS 学习统计实战:计算地区学习进度与收藏数量
harmonyos·arkts·arkui·数据统计·多设备适配
日取其半万世不竭3 个月前
Umami 自建网站统计:能替代复杂分析平台吗
vps·数据统计·vps主机·umami·网页埋点·网站流量统计·开源统计
小王毕业啦8 个月前
2010-2024年 上市公司-突破性创新和渐进性创新(数据+代码+文献)
大数据·人工智能·数据挖掘·数据分析·数据统计·社科数据·经管数据
小王毕业啦8 个月前
2007-2024年 上市公司-投资者情绪数据(xlsx)
大数据·人工智能·数据挖掘·数据分析·数据统计·社科数据·经管数据
小王毕业啦8 个月前
2010-2024年 非常规高技能劳动力(+文献)
大数据·人工智能·数据挖掘·数据分析·数据统计·社科数据·经管数据
刘大猫.8 个月前
XNMS项目-mysql数据库同步
数据库·统计·同步·数据同步·数据统计·数据库同步·业务统计
2401_841495649 个月前
【Python高级编程】学习通签到统计工具
python·pandas·gui·tkinter·pyinstaller·数据统计·exe程序
小王毕业啦9 个月前
2003-2023年 285个地级市邻接矩阵、经济地理矩阵等8个矩阵数据
大数据·人工智能·数据挖掘·数据分析·数据统计·社科数据·实证数据
小王毕业啦9 个月前
2011-2022年 国内光伏电站遥感矢量数据
大数据·人工智能·数据挖掘·数据分析·数据统计·社科数据·实证数据