传统观念:牛市任何策略都赚钱。编程同一策略在牛,熊,震荡市分别回测,量化行情对策略有效性影响。

📅 2026/6/18 20:43:02
传统观念:牛市任何策略都赚钱。编程同一策略在牛,熊,震荡市分别回测,量化行情对策略有效性影响。
同一策略在牛、熊、震荡市分别回测工具教学版定位去营销化、中立、可教学、可扩展⚠️ 全文含免责声明与风险提示不荐股、不承诺收益、不引导开户、无任何引流一、实际应用场景描述在智能证券投资课程中策略在不同市场环境下的适应性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 / sidewaynav_list: 完整净值序列start_idx: 区间起始索引end_idx: 区间结束索引self.regime regimeself.index_name index_nameself.nav_list nav_listself.start_idx start_idxself.end_idx end_idx✅ regime_classifier.py行情状态分类器regime_classifier.py牛 / 熊 / 震荡市判定import numpy as npdef classify_regime(nav_list, window20, bull_thresh20.0, bear_thresh-20.0):基于滚动累计涨跌幅判定行情状态window: 滚动窗口大小bull_thresh: 牛市阈值%bear_thresh: 熊市阈值%regimes []for i in range(len(nav_list)):if i window:regimes.append(warmup) # 预热期continuestart_nav nav_list[i - window]end_nav nav_list[i]change_pct (end_nav - start_nav) / start_nav * 100if change_pct bull_thresh:regimes.append(bull)elif change_pct bear_thresh:regimes.append(bear)else:regimes.append(sideway)return regimesdef extract_regime_periods(nav_list, regimes):将连续的同种行情合并为区间返回: [(regime, start_idx, end_idx), ...]periods []i 0n len(regimes)while i n:if regimes[i] warmup:i 1continuecurrent_regime regimes[i]start iwhile i n and regimes[i] current_regime:i 1end i - 1periods.append((current_regime, start, end))return periods✅ backtester.py核心回测引擎backtester.py均线交叉策略回测引擎教学版import numpy as npclass MABacktester:简单均线交叉策略def __init__(self, short_window5, long_window20):self.short_window short_windowself.long_window long_windowdef 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_windowcash 10000.0shares 0.0in_position Falsetrades []for i in range(max(offset, 0), len(segment)):if i self.short_window len(segment):breakshort_val short_ma[i] if i len(short_ma) else Nonelong_val long_ma[i] if i len(long_ma) else Noneif short_val is None or long_val is None:continueprice segment[i]if not in_position and short_val long_val:# 买入shares cash / pricecash 0.0in_position Truetrades.append({type: buy, price: price, day: i})elif in_position and short_val long_val:# 卖出cash shares * priceshares 0.0in_position Falsetrades.append({type: sell, price: price, day: i})# 期末清仓if in_position and len(segment) 0:cash shares * segment[-1]shares 0.0final_value cashtotal_return (final_value - 10000) / 10000 * 100# 计算最大回撤peak 10000max_dd 0portfolio_value 10000for i in range(max(offset, 0), len(segment)):price segment[i]if in_position:val shares * priceelse:val cashif val peak:peak valdd (peak - val) / peak * 100if dd max_dd:max_dd dd# 胜率有买有卖的交易对win_count 0trade_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 1total_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 MABacktesterdef 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 Nonesummary[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 0bear_ret summary.get(bear, {}).get(avg_return_pct, 0) or 0side_ret summary.get(sideway, {}).get(avg_return_pct, 0) or 0bull_dd summary.get(bull, {}).get(avg_max_drawdown_pct, 0) or 0bear_dd summary.get(bear, {}).get(avg_max_drawdown_pct, 0) or 0print(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.pyJSON 本地存储import jsonFILE_PATH regime_backtest_result.jsondef save_result(data):with open(FILE_PATH, w, encodingutf-8) as f:json.dump(data, f, ensure_asciiFalse, indent2)✅ main.py交互入口main.py牛 / 熊 / 震荡市策略回测对比工具from models import MarketRegimeSessionfrom regime_classifier import classify_regime, extract_regime_periodsfrom comparator import run_regime_comparisonfrom reporter import reportfrom storage import save_resultdef 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, window20)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 与使用说明# 同一策略 × 多行情回测对比工具教学版## 项目说明将行情划分为牛 / 熊 / 震荡三种状态用同一策略分别回测量化行情对策略有效性的影响。## 使用方式bashpip install numpypython 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解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛