传统观念:牛市任何策略都赚钱。编程同一策略在牛,熊,震荡市分别回测,量化行情对策略有效性影响。
2026/6/18 20:42:53 网站建设 项目流程

同一策略在牛、熊、震荡市分别回测工具(教学版)

定位:去营销化、中立、可教学、可扩展

⚠️ 全文含免责声明与风险提示,不荐股、不承诺收益、不引导开户、无任何引流

一、实际应用场景描述

在智能证券投资课程中,策略在不同市场环境下的适应性(Regime Dependence)是区分"幸运"与"能力"的关键课题。

本程序适用于:

- 高校量化投资、策略开发课程实验

- 多市场环境策略压力测试教学

- 投资者认知训练:打破"牛市啥都灵"的迷思

- 量化竞赛中的策略鲁棒性验证

核心目标:

- 将历史行情标记为牛市、熊市、震荡市三种状态

- 用同一策略分别回测三种行情

- 量化行情对策略有效性的影响

- 用数据回答:"牛市赚钱,到底是策略牛,还是行情牛?"

✅ 不做未来预测

✅ 不构成投资建议

✅ 仅作为历史回测教学示例

二、痛点引入(真实可感知)

痛点 表现

"牛市什么策略都赚" 2014–2015 年人人是股神

行情归因缺失 不知道赚的是 α 还是 β

策略盲目推广 牛市回测好就以为万能

熊市裸奔 没测过下跌市,一跌就崩

工具门槛高 专业多状态回测框架复杂

👉 需要一个轻量、本地、可解释、可复现的分行情回测工具

三、核心逻辑讲解(工程视角)

1️⃣ 数据模型设计

MarketRegimeSession

├── regime 行情状态(bull/bear/sideway)

├── index_name 指数名称

├── nav_list 净值 / 价格序列

└── regime_periods 各状态区间列表

2️⃣ 行情状态判定(教学用简化规则)

基于累计涨跌幅划分:

状态 判定条件

牛市 区间累计涨幅 > +20%

熊市 区间累计跌幅 < −20%

震荡市 介于两者之间

⚠️ 教学中强调:行情划分本身有主观性,不同标准结论可能不同。

3️⃣ 核心回测逻辑

策略(教学用):均线交叉策略

规则 操作

短均线上穿长均线 买入(全仓)

短均线下穿长均线 卖出(清仓)

持币期间 收益 = 0%

4️⃣ 分行情回测流程

读取完整行情序列

按涨跌幅划分为牛 / 熊 / 震荡区间

对每个区间:

执行同一策略回测

记录收益、回撤、胜率

输出三行情对比报告

5️⃣ 关键指标对比

指标 牛市长啥样 熊市长啥样 震荡市长啥样

累计收益 应该高 应该低甚至负 中等

最大回撤 通常较小 暴露风险 频繁止损

交易次数 较少(趋势明确) 中等 很多(反复打脸)

胜率 可能虚高 暴露真实胜率 关键分水岭

四、Python 模块化代码(可直接运行)

📁 项目结构

regime_backtest_tool/

├── main.py

├── models.py

├── regime_classifier.py

├── backtester.py

├── comparator.py

├── reporter.py

├── storage.py

├── README.md

└── DISCLAIMER.md

✅ models.py(数据建模)

"""

models.py

多行情状态回测数据模型

"""

class MarketRegimeSession:

"""单段行情区间"""

def __init__(self, regime, index_name, nav_list, start_idx, end_idx):

"""

regime: "bull" / "bear" / "sideway"

nav_list: 完整净值序列

start_idx: 区间起始索引

end_idx: 区间结束索引

"""

self.regime = regime

self.index_name = index_name

self.nav_list = nav_list

self.start_idx = start_idx

self.end_idx = end_idx

✅ regime_classifier.py(行情状态分类器)

"""

regime_classifier.py

牛 / 熊 / 震荡市判定

"""

import numpy as np

def classify_regime(nav_list, window=20, bull_thresh=20.0, bear_thresh=-20.0):

"""

基于滚动累计涨跌幅判定行情状态

window: 滚动窗口大小

bull_thresh: 牛市阈值(%)

bear_thresh: 熊市阈值(%)

"""

regimes = []

for i in range(len(nav_list)):

if i < window:

regimes.append("warmup") # 预热期

continue

start_nav = nav_list[i - window]

end_nav = nav_list[i]

change_pct = (end_nav - start_nav) / start_nav * 100

if change_pct >= bull_thresh:

regimes.append("bull")

elif change_pct <= bear_thresh:

regimes.append("bear")

else:

regimes.append("sideway")

return regimes

def extract_regime_periods(nav_list, regimes):

"""

将连续的同种行情合并为区间

返回: [(regime, start_idx, end_idx), ...]

"""

periods = []

i = 0

n = len(regimes)

while i < n:

if regimes[i] == "warmup":

i += 1

continue

current_regime = regimes[i]

start = i

while i < n and regimes[i] == current_regime:

i += 1

end = i - 1

periods.append((current_regime, start, end))

return periods

✅ backtester.py(核心回测引擎)

"""

backtester.py

均线交叉策略回测引擎(教学版)

"""

import numpy as np

class MABacktester:

"""简单均线交叉策略"""

def __init__(self, short_window=5, long_window=20):

self.short_window = short_window

self.long_window = long_window

def run(self, nav_list, start_idx, end_idx):

"""

在指定区间内执行均线策略

"""

# 截取区间(需要额外 long_window 根用于计算均线)

actual_start = max(0, start_idx - self.long_window)

segment = nav_list[actual_start:end_idx + 1]

if len(segment) < self.long_window + 1:

return self._empty_result()

short_ma = self._moving_average(segment, self.short_window)

long_ma = self._moving_average(segment, self.long_window)

# 对齐:short_ma[i] 和 long_ma[i] 对应 segment 的第 i 个点

# 从第 long_window 根开始比较

offset = self.long_window - self.short_window

cash = 10000.0

shares = 0.0

in_position = False

trades = []

for i in range(max(offset, 0), len(segment)):

if i + self.short_window > len(segment):

break

short_val = short_ma[i] if i < len(short_ma) else None

long_val = long_ma[i] if i < len(long_ma) else None

if short_val is None or long_val is None:

continue

price = segment[i]

if not in_position and short_val > long_val:

# 买入

shares = cash / price

cash = 0.0

in_position = True

trades.append({"type": "buy", "price": price, "day": i})

elif in_position and short_val < long_val:

# 卖出

cash = shares * price

shares = 0.0

in_position = False

trades.append({"type": "sell", "price": price, "day": i})

# 期末清仓

if in_position and len(segment) > 0:

cash = shares * segment[-1]

shares = 0.0

final_value = cash

total_return = (final_value - 10000) / 10000 * 100

# 计算最大回撤

peak = 10000

max_dd = 0

portfolio_value = 10000

for i in range(max(offset, 0), len(segment)):

price = segment[i]

if in_position:

val = shares * price

else:

val = cash

if val > peak:

peak = val

dd = (peak - val) / peak * 100

if dd > max_dd:

max_dd = dd

# 胜率(有买有卖的交易对)

win_count = 0

trade_pairs = []

for t in trades:

if t["type"] == "buy":

trade_pairs.append(t)

elif t["type"] == "sell" and trade_pairs:

buy_t = trade_pairs.pop(0)

if t["price"] > buy_t["price"]:

win_count += 1

total_trades = len([t for t in trades if t["type"] == "sell"])

return {

"final_value": round(final_value, 2),

"total_return_pct": round(total_return, 2),

"max_drawdown_pct": round(max_dd, 2),

"total_trades": total_trades,

"winning_trades": win_count,

"win_rate_pct": round(win_count / total_trades * 100, 2) if total_trades > 0 else None,

"days": end_idx - start_idx + 1

}

def _empty_result(self):

return {

"final_value": 10000,

"total_return_pct": 0,

"max_drawdown_pct": 0,

"total_trades": 0,

"winning_trades": 0,

"win_rate_pct": None,

"days": 0

}

def _moving_average(self, data, window):

"""简单移动平均"""

if len(data) < window:

return []

return [np.mean(data[i-window:i]) for i in range(window, len(data)+1)]

✅ comparator.py(三行情对比)

"""

comparator.py

牛 / 熊 / 震荡市策略表现对比

"""

from backtester import MABacktester

def run_regime_comparison(nav_list, regime_periods, index_name):

"""

对同一策略在三种行情下分别回测

"""

backtester = MABacktester()

results = {}

for regime, start, end in regime_periods:

result = backtester.run(nav_list, start, end)

if regime not in results:

results[regime] = []

results[regime].append(result)

# 汇总每种行情

summary = {}

for regime, rlist in results.items():

avg_return = sum(r["total_return_pct"] for r in rlist) / len(rlist)

avg_dd = sum(r["max_drawdown_pct"] for r in rlist) / len(rlist)

total_trades = sum(r["total_trades"] for r in rlist)

win_rates = [r["win_rate_pct"] for r in rlist if r["win_rate_pct"] is not None]

avg_win_rate = sum(win_rates) / len(win_rates) if win_rates else None

summary[regime] = {

"periods": len(rlist),

"avg_return_pct": round(avg_return, 2),

"avg_max_drawdown_pct": round(avg_dd, 2),

"total_trades": total_trades,

"avg_win_rate_pct": round(avg_win_rate, 2) if avg_win_rate else None,

"details": rlist

}

return summary

✅ reporter.py(对比报告输出)

"""

reporter.py

牛 / 熊 / 震荡市策略表现对比报告

"""

def report(index_name, summary):

print("\n" + "=" * 65)

print(f"【同一策略 × 多行情回测对比报告】")

print(f"指数:{index_name}")

print("=" * 65)

regime_names = {

"bull": "🐂 牛市",

"bear": "🐻 熊市",

"sideway": "📊 震荡市"

}

for regime, data in summary.items():

name = regime_names.get(regime, regime)

print(f"\n{name}(共 {data['periods']} 个区间)")

print("-" * 65)

print(f" 平均收益:{data['avg_return_pct']}%")

print(f" 平均最大回撤:{data['avg_max_drawdown_pct']}%")

print(f" 总交易次数:{data['total_trades']}")

if data['avg_win_rate_pct'] is not None:

print(f" 平均胜率:{data['avg_win_rate_pct']}%")

else:

print(f" 平均胜率:N/A(无完整交易对)")

# 教学结论

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

print(f"\n💡 教学启示:")

print("-" * 65)

bull_ret = summary.get("bull", {}).get("avg_return_pct", 0) or 0

bear_ret = summary.get("bear", {}).get("avg_return_pct", 0) or 0

side_ret = summary.get("sideway", {}).get("avg_return_pct", 0) or 0

bull_dd = summary.get("bull", {}).get("avg_max_drawdown_pct", 0) or 0

bear_dd = summary.get("bear", {}).get("avg_max_drawdown_pct", 0) or 0

print(f"\n ① 牛市平均收益:{bull_ret}% | 熊市平均收益:{bear_ret}%")

print(f" → 差距 {round(bull_ret - bear_ret, 2)} 个百分点")

if bull_ret > 0 and bear_ret < 0:

print(f" ⚠️ 策略在牛市赚、熊市亏,说明:")

print(f" 「赚的是行情的钱,不是策略的能耐」")

elif bull_ret > 0 and bear_ret > 0:

print(f" ✅ 策略在牛熊市均盈利,具备一定鲁棒性")

elif bear_ret < -10:

print(f" ⚠️ 熊市平均亏损 {abs(bear_ret)}%,策略需改进或加风控")

print(f"\n ② 牛市回撤:{bull_dd}% | 熊市回撤:{bear_dd}%")

if bear_dd > bull_dd * 2:

print(f" ⚠️ 熊市回撤是牛市的 {round(bear_dd / max(bull_dd, 0.1), 1)} 倍")

print(f" → 同一策略在不同行情下风险暴露差异巨大")

# 震荡市分析

side_trades = summary.get("sideway", {}).get("total_trades", 0)

if side_trades > 10:

print(f"\n ③ 震荡市交易 {side_trades} 次")

print(f" ⚠️ 频繁交叉 → 反复止损 → 震荡市是均线策略的「照妖镜」")

print(f"\n 核心结论:")

print(f" 牛市什么策略都赚钱 ≠ 策略有效。")

print(f" 真正的策略能力 = 熊市和震荡市的表现。")

print("=" * 65)

✅ storage.py(本地存储)

"""

storage.py

JSON 本地存储

"""

import json

FILE_PATH = "regime_backtest_result.json"

def save_result(data):

with open(FILE_PATH, "w", encoding="utf-8") as f:

json.dump(data, f, ensure_ascii=False, indent=2)

✅ main.py(交互入口)

"""

main.py

牛 / 熊 / 震荡市策略回测对比工具

"""

from models import MarketRegimeSession

from regime_classifier import classify_regime, extract_regime_periods

from comparator import run_regime_comparison

from reporter import report

from storage import save_result

def main():

print("=== 同一策略 × 多行情回测对比工具(教学版)===")

print("量化「牛市什么策略都赚钱」是否成立\n")

index_name = input("指数名称(如 沪深300):")

print(f"\n📌 请输入净值 / 价格序列(空格分隔):")

navs = list(map(float, input().split()))

if len(navs) < 40:

print("⚠️ 数据量不足(至少需要 40 个数据点),无法可靠回测")

return

# 行情分类

regimes = classify_regime(navs, window=20)

regime_periods = extract_regime_periods(navs, regimes)

if not regime_periods:

print("⚠️ 未检测到有效行情区间")

return

# 统计各行情数量

bull_count = sum(1 for r, _, _ in regime_periods if r == "bull")

bear_count = sum(1 for r, _, _ in regime_periods if r == "bear")

side_count = sum(1 for r, _, _ in regime_periods if r == "sideway")

print(f"\n📊 行情分布:牛市 {bull_count} 段 | 熊市 {bear_count} 段 | 震荡 {side_count} 段")

# 执行回测

summary = run_regime_comparison(navs, regime_periods, index_name)

# 输出报告

report(index_name, summary)

# 保存结果

result_data = {

"index": index_name,

"total_data_points": len(navs),

"regime_distribution": {

"bull": bull_count,

"bear": bear_count,

"sideway": side_count

},

"summary": summary

}

save_result(result_data)

print("\n✅ 回测结果已保存")

if __name__ == "__main__":

main()

五、README 与使用说明

# 同一策略 × 多行情回测对比工具(教学版)

## 项目说明

将行情划分为牛 / 熊 / 震荡三种状态,用同一策略分别回测,量化行情对策略有效性的影响。

## 使用方式

bash

pip install numpy

python main.py

## 输入示例

指数名称:沪深300

净值序列:1.0 1.05 1.12 1.08 0.95 0.88 0.92 1.01 1.10 1.18 1.15 1.22 ...

## 策略说明

采用**均线交叉策略**(短均线上穿买入、下穿卖出):

- 短均线:5 期

- 长均线:20 期

- 初始资金:10000 元(模拟)

## 适用范围

- 量化投资策略课程

- 策略鲁棒性教学

- 行情归因分析演示

## 注意事项

- 仅基于历史数据

- 不构成任何投资建议

- 使用前请阅读 DISCLAIMER.md

六、DISCLAIMER.md(免责声明与风险提示)

# 免责声明与风险提示

## 免责声明

本程序仅供**教学与科研用途**,用于演示策略在不同行情下的表现差异。

作者不提供任何证券交易建议,不推荐任何策略,不承诺任何收益。

## 风险提示

1. 行情状态判定具有主观性,不同阈值 / 方法会导致不同结论

2. 均线交叉策略仅为教学示例,不代表"最优策略"

3. 历史回测不代表未来表现,策略可能过拟合

4. 未考虑手续费、滑点、流动性等现实约束

5. 牛市赚钱 ≠ 策略有效,需结合熊市 / 震荡市综合判断

6. 策略鲁棒性需在多种市场环境下验证,单一行情回测不足为据

使用本工具产生的任何后果,作者概不负责。

七、核心知识点卡片(教学向)

分类 内容

Python 类、滑动窗口、列表切片、字典聚合

量化金融 行情状态分类、策略鲁棒性、行情归因

投资理念 打破"牛市策略万能"迷思

数据分析 分组统计、跨组对比、胜率分析

工程思想 模块化(分类器 / 回测器 / 对比器解耦)

风险教育 同一策略在不同行情下表现差异可能巨大

可扩展性 可接入真实行情 API、支持多策略并行对比

八、总结(工程师视角)

这是一个完全中立、去营销化、可教学的原型系统:

✅ 不鼓吹任何策略

✅ 不妖魔化任何行情

✅ 不伪装成策略甄选神器

它真正展示的是:

如何用 Python 把"牛市赚钱到底是运气还是实力"变成可量化、可对比、可反思的教学实验

核心教学价值:

传统观念 数据可能揭示的真相

"牛市什么策略都赚" 熊市可能亏得更多,赚的是 β 不是 α

"回测收益率 50% 就是好策略" 可能只在牛市有效,震荡市疯狂止损

"策略通过了回测" 没分行情测 = 没真正测过

"均线策略很稳" 震荡市频繁交叉 → 反复打脸

典型回测结果参考(教学示例):

行情 均线策略平均收益 最大回撤

牛市 +15% ~ +35% 5% ~ 12%

熊市 −5% ~ −25% 15% ~ 40%

震荡市 −3% ~ +5% 8% ~ 18%

⚠️ 以上为教学示意,不代表任何真实策略表现。实际结果取决于参数、数据区间和手续费假设。

本文代码仅供学习与技术交流,不构成任何投资建议,股市有风险,入市需谨慎!

利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询