ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python量化投资:基于市场情绪指标的行业轮动策略构建与回测

Python量化投资:基于市场情绪指标的行业轮动策略构建与回测 你有没有过这样的经历看着A股四千多只股票、几十个行业板块每天消息满天飞却不知道资金到底在往哪里流是追着新闻跑还是跟着感觉走或者干脆把选择权交给那些听起来高大上却看不懂的“神秘指标”更让人头疼的是很多策略听起来头头是道但一说到具体怎么算、数据从哪来、代码怎么写就变得语焉不详。结果就是你兴冲冲地跟着一个“神奇公式”操作最后发现要么数据对不上要么根本跑不通白白浪费了时间和精力。今天要聊的不是又一个“必胜秘籍”而是一套完全透明、可复现的行业轮动观察框架。它源于一份公开的研究思路——用五个可量化的市场情绪指标来刻画资金的“温度”和“流向”试图找出每一阶段市场共识最强的行业。最关键的是我们将把构建这五个指标、进行十年历史回测的全套Python代码和逻辑彻底公开。你不仅能看懂结论更能亲手运行、验证、甚至改进它。这套方法的价值不在于提供一个“圣杯”而在于提供一个扎实的、工程化的分析起点。它把模糊的“市场情绪”变成了可计算的数字把主观的“行业强弱”变成了可回溯的曲线。你会发现投资的很多困惑不是因为缺乏“内幕消息”而是缺少一套把市场噪音转化为结构化信息的工具。1. 为什么是“情绪指标”超越基本面与技术面的第三维度在讨论具体指标之前我们必须先回答一个根本问题为什么要在基本面分析公司业绩、行业景气度和技术分析价格、成交量、形态之外额外关注“情绪指标”答案在于市场的短期驱动机制。基本面决定长期价值但无法解释日度甚至周度的剧烈波动技术面描述价格轨迹但常常是情绪的“果”而非“因”。市场在多数时候尤其是在趋势形成初期和末期是由投资者的集体情绪——贪婪、恐惧、从众、过度乐观或悲观——所驱动的。这种情绪会直接反映在资金的行为上是追涨还是杀跌是集中涌入某个板块还是恐慌性撤离因此情绪指标的本质是试图量化资金行为的“强度”和“一致性”。它不预测公司的未来盈利而是监测当下市场参与者的投票情况。当大量资金以强烈的共识涌入某个行业时这个行业在短期内获得超额收益的概率就会显著增加。我们的目标就是设计一套规则去识别这种“共识强度”最高的时刻和方向。基于这个逻辑我们选取了五个维度来构建情绪指标体系。它们分别从价格强度、资金流向、波动特征和趋势动能等角度捕捉市场的非理性冲动。1.1 五个核心情绪指标的设计逻辑这五个指标并非随意拼凑而是各有侧重共同编织出一张观察市场情绪的网。收益率排名Return Rank这是最直接的情绪体现。我们不是简单看涨跌幅而是计算一个行业指数相对于全市场所有行业指数的收益率百分位排名。比如排名90%意味着它跑赢了90%的同行。高排名不仅代表上涨更代表它在同期“比别的行业涨得更好”这是一种相对强度的确认。成交额占比变化Turnover Change钱在哪里情绪就在哪里。我们计算一个行业成交额占全市场总成交额的比重并观察这个比重的变化率。份额的快速提升往往意味着资金正在加速涌入市场关注度在急剧升温这通常是行情启动或加速的重要信号。振幅收窄Volatility Contraction这是一个反直觉但非常重要的指标。在趋势启动前市场经常会出现一段时间的“沉寂”——价格波动幅度日内高点与低点之差持续收窄。这可以理解为多空力量在某个区间内达到短暂平衡为后续的方向性突破积蓄能量。监测振幅从收窄到突然放大的过程有助于捕捉突破临界点。趋势强度Trend Strength我们使用经过平滑处理的动量指标如一定周期内的收益率来衡量趋势的稳健程度。强劲且稳定的正向动量表明上涨并非一日游而是得到了持续的买盘支撑情绪正在向乐观一侧持续强化。资金流向一致性Flow Consistency这个指标稍微复杂一些它通过分析日内价格与成交量的关系例如类似“资金流”的算法判断推动价格上涨的成交量中有多大比例是主动性买盘。高一致性意味着上涨是由真实的、持续的买入需求推动的而非少数大单或尾盘偷袭所致这代表了更扎实的情绪基础。将这五个指标标准化后等权相加就得到了一个行业的“综合情绪分数”。分数越高代表该行业在多个维度上同时表现出强烈的乐观情绪和资金共识。2. 从理论到数据如何用Python构建可计算的指标思路清晰了下一步就是工程实现。空谈指标毫无意义我们必须能计算出真实、连续的历史数据。这里会涉及数据获取、指标计算、回测框架三个核心部分。我们将使用AKShare这个强大的开源财经数据接口库来获取基础数据。2.1 数据地基获取可靠的行业指数数据行业分析的基础是选择一套具有代表性、历史数据完整的行业指数。常见的分类有申万一级行业、中信一级行业等。这里以申万一级行业2021版为例共31个行业。首先我们需要获取这些行业指数的日线行情数据包括开盘价、收盘价、最高价、最低价、成交额。import akshare as ak import pandas as pd import numpy as np from datetime import datetime, timedelta # 定义回测时间段 start_date 2014-01-01 end_date 2023-12-31 # 申万一级行业列表 (示例实际名称需根据AKShare接口确认) # 注意AKShare的行业指数接口可能随时间变化以下为示例代码逻辑 industry_list [801010, 801020, 801030, ...] # 行业代码 industry_names [农林牧渔, 煤炭, 有色金属, ...] # 行业名称 all_industry_data {} for code, name in zip(industry_list, industry_names): try: # 使用AKShare获取行业指数日线数据 df ak.sw_index_daily(indicatorcode, start_datestart_date, end_dateend_date) # 规范列名 df.rename(columns{日期:date, 开盘价:open, 收盘价:close, 最高价:high, 最低价:low, 成交额:volume}, inplaceTrue) df[date] pd.to_datetime(df[date]) df.set_index(date, inplaceTrue) df[industry_code] code df[industry_name] name all_industry_data[name] df[[open, high, low, close, volume]] print(f已获取 {name} 数据长度{len(df)}) except Exception as e: print(f获取 {name}({code}) 数据失败: {e}) # 将所有行业数据按时间索引对齐填充缺失值前向填充 panel_data pd.concat(all_industry_data, axis1, joinouter) panel_data panel_data.sort_index().ffill() # 按日期排序并前向填充关键点数据质量是回测的生命线。务必检查数据是否连续有无异常值如价格为零、成交额突变并对停牌等导致的缺失值进行合理处理如前向填充。对齐所有行业的时间索引是后续进行横截面比较的基础。2.2 指标工程将五个逻辑转化为Python代码有了基础数据我们就可以逐一实现那五个情绪指标。计算过程涉及大量的横截面同一时间点不同行业间比较和时间序列滚动计算。def calculate_emotion_indicators(panel_data, lookback_period20): 计算五个情绪指标 panel_data: MultiIndex DataFrame, 第一层是行业名第二层是OHLCV等字段 lookback_period: 部分指标如趋势强度的计算周期 results {} # 准备一个空的DataFrame来存储每个行业的每日指标 emotion_df pd.DataFrame(indexpanel_data.index.levels[0]) # 索引为日期 for industry in panel_data.columns.levels[0]: df panel_data[industry] # 1. 收益率排名 (使用过去5日收益率) df[return_5d] df[close].pct_change(5) # 每日横截面排名计算每个交易日该行业收益率在所有行业中的百分位 # 这里需要在循环外进行横截面计算稍后统一处理 # 2. 成交额占比变化 # 先计算全市场每日总成交额 # total_volume panel_data.xs(volume, axis1, level1).sum(axis1) # 该行业成交额占比 # df[volume_share] df[volume] / total_volume # 占比的5日变化率 # df[volume_share_chg] df[volume_share].pct_change(5) # 3. 振幅收窄 (过去10日振幅的标准差 / 均值比值下降表示收窄) df[daily_range] (df[high] - df[low]) / df[close].shift(1) # 日内振幅率 df[range_ma] df[daily_range].rolling(window10).mean() df[range_std] df[daily_range].rolling(window10).std() df[volatility_ratio] df[range_std] / df[range_ma] # 比值小则波动收敛 # 4. 趋势强度 (使用价格在20日均线之上的幅度与稳定性) df[ma20] df[close].rolling(window20).mean() df[trend_strength] (df[close] - df[ma20]) / df[ma20] # 价格偏离均线的百分比 # 可以进一步用过去5日trend_strength的均值或斜率来平滑 # 5. 资金流向一致性 (简易版价涨量增为正价跌量增为负累计) df[price_chg] df[close].pct_change() df[volume_chg] df[volume].pct_change() # 定义一个简单的流向价格变化与成交量变化符号一致时强度大 df[flow_raw] df[price_chg] * df[volume_chg] df[flow_consistency] df[flow_raw].rolling(window5).mean() # 5日平滑 results[industry] df[[return_5d, volatility_ratio, trend_strength, flow_consistency]] # 横截面排名计算 (以收益率排名为例) # 将所有行业的‘return_5d’提取出来形成一个日期*行业的DataFrame return_cross pd.DataFrame({ind: results[ind][return_5d] for ind in results}) # 计算每日的百分位排名 return_rank return_cross.rank(axis1, pctTrue) # 将排名等信息合并回每个行业的数据框 for industry in results: results[industry][return_rank] return_rank[industry] # 同理可以计算其他需要横截面比较的指标... # 计算综合情绪分数 (假设五个指标已准备好这里仅为示例) # 先对每个指标进行归一化z-score或分位数归一化 # emotion_score (return_rank volume_share_chg_norm ... ) / 5 # results[industry][emotion_score] emotion_score return results关键点横截面计算像收益率排名这类指标必须在每个交易日对所有行业进行计算才能体现“相对强弱”。周期选择计算指标所用的时间窗口如5日、10日、20日需要根据市场节奏调整。周期太短噪音大太长则滞后严重。归一化不同指标的量纲和分布不同如百分比变化和比值在合成综合分数前必须进行标准化处理如转换为每日的百分位排名或Z-Score否则量级大的指标会主导结果。2.3 回测引擎验证策略的历史表现计算出每日的情绪分数后我们就可以构建策略了。一个简单的策略是每周或每日收盘后计算所有行业的综合情绪分数买入排名前N如前三的行业持有固定周期后调仓。def backtest_emotion_strategy(emotion_data_dict, top_n3, hold_period5, initial_capital1000000): 情绪策略回测 emotion_data_dict: 字典key为行业名value为包含‘emotion_score’等指标的DataFrame top_n: 每期买入排名前几的行业 hold_period: 持有期交易日 all_dates sorted(list(emotion_data_dict.values())[0].index) # 获取所有交易日 rebalance_dates all_dates[::hold_period] # 按持有期调仓 portfolio {} # 记录当前持仓 {行业: 买入日期} capital initial_capital equity_curve [] # 资金曲线 trade_log [] # 交易记录 for i, current_date in enumerate(all_dates): # 调仓日逻辑 if current_date in rebalance_dates: # 清空现有持仓假设调仓日全部卖出 if portfolio: for industry in list(portfolio.keys()): # 计算卖出收益使用当日收盘价 # ... 简化处理这里需要价格数据 trade_log.append({date:current_date, action:sell, industry:industry}) portfolio.clear() # 选择新标的获取当前日期所有行业的情绪分数 scores_today {} for industry, df in emotion_data_dict.items(): if current_date in df.index: scores_today[industry] df.loc[current_date, emotion_score] # 选出分数最高的top_n个行业 selected sorted(scores_today.items(), keylambda x: x[1], reverseTrue)[:top_n] # 等权重买入 capital_per_industry capital / len(selected) for industry, score in selected: portfolio[industry] {buy_date: current_date, buy_capital: capital_per_industry} trade_log.append({date:current_date, action:buy, industry:industry, score:score}) # 每日更新组合市值简化假设价格使用收盘价需从原始数据获取 # total_value capital sum(持仓市值) # equity_curve.append((current_date, total_value)) # 计算回测指标年化收益率、夏普比率、最大回撤等 # equity_series pd.Series([v for d, v in equity_curve], index[d for d, v in equity_curve]) # ... 计算指标代码 ... return { equity_curve: equity_curve, trade_log: trade_log, # performance_metrics: metrics }关键点回测假设这是一个高度简化的回测忽略了交易成本佣金、印花税、冲击成本、以及买入卖出是否能在收盘价成交等问题。严谨的回测必须考虑这些因素。幸存者偏差我们使用的是当前存在的行业指数回溯历史这忽略了历史上已消失或重组的行业可能高估历史表现。过拟合风险五个指标、它们的权重、计算周期、调仓周期、Top N数量等都是可以优化的参数。但过度优化在历史数据上寻找最佳参数组合会导致策略在未来失效。必须使用样本外数据测试或采用稳健的参数。3. 十年回测结果揭示了什么数据背后的洞察与陷阱运行上述回测框架经过完整实现和数据清洗后我们可能会得到一条从2014年到2023年的净值曲线。这里我们讨论一般性的发现而非具体的收益数字因为数据源、参数和处理细节的差异会导致结果不同。3.1 可能观察到的模式阶段性有效情绪策略在趋势明朗、主线清晰的牛市或结构性行情中如2014-2015上半年2019-2020的消费科技行情2021年的新能源行情往往能快速捕捉到领涨行业表现突出。这是因为强烈的赚钱效应会吸引资金持续涌入形成正反馈情绪指标能很好地度量这种“热度”。震荡市钝化在窄幅震荡、轮动快速的“电风扇”行情中策略可能频繁换仓导致交易成本侵蚀利润甚至左右挨打。因为情绪指标捕捉的是“共识”而震荡市中缺乏持续共识。极端行情下的风险在市场恐慌性下跌的初期如2015年下半年2018年情绪指标最高的行业可能并非避风港反而是之前涨幅过大、情绪最亢奋的板块补跌风险巨大。这说明情绪指标是动量指标而非反转指标它不预测趋势的终结。3.2 必须警惕的回测陷阱看到一条漂亮的净值曲线时务必保持冷静检查以下陷阱前视偏差Look-ahead Bias这是最致命的错误。例如使用了当日收盘价计算指标却在当日开盘就交易。在实际中你只能在收盘后获得全部数据。我们的代码必须严格使用shift(1)等方式确保交易决策基于历史信息。数据窥探Data Snooping如果根据十年全样本数据反复调整参数直到曲线完美那么这个策略几乎肯定在未来失效。正确的做法是将数据分为训练集如2014-2019和测试集2020-2023在训练集上确定逻辑和大致参数范围在测试集上验证。交易成本忽略成本的回测是“纸上富贵”。A股买卖双边佣金加印花税频繁调仓如每周的成本不容小觑。一个年化收益20%的策略如果周度调仓交易成本可能轻易吃掉5%以上。流动性问题策略若应用于行业指数ETF需检查历史成交量是否支持即时买卖。若应用于一篮子个股冲击成本更大。注意公开回测结果的目的绝不是为了展示一个“高收益策略”而是为了完整演示从数据获取、指标构建到回测验证的全过程方法论。任何未经严格样本外检验和实盘模拟的策略都不应直接用于真实投资。4. 从研究到实践如何将情绪指标融入你的决策系统一套完整的分析框架其价值远不止于生成一个买卖信号。更重要的是它为你提供了一个结构化的市场观察工具。以下是如何将其工程化、实用化的建议。4.1 定位辅助工具而非圣杯首先必须明确情绪指标系统应该作为你决策系统的辅助模块而不是唯一依据。它的核心作用是扫描与预警每日/每周帮你快速扫描全行业找出资金情绪最亢奋或最低迷的板块节省手动翻阅的时间。确认与过滤当你基于基本面或技术面看好某个行业时可以用情绪指标来观察市场资金是否也形成了共识。如果看好一个行业但情绪分数持续低迷可能需要反思逻辑是否被市场认可。感知市场水温综合情绪分数的市场平均值或中位数可以作为一个整体市场风险偏好的观测指标。分数普遍极高时提示市场可能过热普遍极低时可能孕育反弹。4.2 工程化部署打造你的自动化看板对于有兴趣的开发者可以将这套系统工程化自动化数据流水线使用定时任务如Cron, Airflow, Celery每日收盘后自动从AKShare等数据源拉取数据计算指标更新数据库。结果可视化用Plotly、Dash或Streamlit构建一个交互式看板。看板可以包括行业情绪分数热力图按时间、行业两个维度。排名前列行业的净值走势与大盘对比。各细分情绪指标的雷达图分析领涨行业的情绪驱动来源是量价齐升还是波动收敛突破。预警通知当某个行业的情绪分数突破历史阈值或排名发生剧烈变动时通过邮件、钉钉、Telegram Bot发送通知。# 示例使用Plotly绘制行业情绪热力图简化版 import plotly.express as px # 假设 emotion_score_matrix 是一个 DataFrame索引为日期列为行业值为情绪分数 # emotion_score_matrix pd.DataFrame({ind: data[emotion_score] for ind, data in emotion_data_dict.items()}) fig px.imshow(emotion_score_matrix.T, # 转置让行业在Y轴 labelsdict(xDate, yIndustry, colorEmotion Score), xemotion_score_matrix.index.strftime(%Y-%m), yemotion_score_matrix.columns, color_continuous_scaleRdBu_r, # 红蓝渐变色红色代表高情绪 aspectauto) fig.update_layout(title行业情绪分数热力图 (2014-2023)) fig.show()4.3 策略迭代与优化方向基础框架搭建完成后可以考虑以下优化方向但这每一步都要警惕过拟合指标加权五个等权指标是否最优或许可以通过历史数据或机器学习方法如IC值分析动态调整权重。但需极度谨慎。多周期融合同时计算短期5日、中期20日、长期60日的情绪分数观察其共振效应。多周期同时看多信号更强。结合宏观与事件在情绪分数的基础上加入宏观因子利率、流动性或事件因子政策发布、财报季进行过滤。例如在货币紧缩期降低高情绪行业的配置权重。风险控制模块引入最大回撤止损、波动率仓位控制等。当组合回撤超过一定幅度或市场整体波动率急剧放大时强制降低仓位。4.4 最重要的提醒理解边界管理预期最后也是最重要的一点是理解任何量化模型的边界它解释过去但不必然预测未来市场参与者的行为模式会变化过去的规律可能失效。它是概率工具不是水晶球高情绪分数只代表上涨的概率可能提升不代表必然上涨。需要与赔率风险收益比结合考虑。极端行情是模型的“盲区”金融危机、政策巨变等系统性风险下所有历史关系都可能被打破。保持透明持续维护这就是我们开源全部代码的原因。只有你完全理解每一行代码在算什么你才能知道策略何时可能失效并做出调整。投资没有一劳永逸的圣杯。这套情绪指标系统更像是一副为你量身定制的“市场体温计”和“资金流向探测仪”。它不能代替你思考但能帮你更清晰、更及时地看到市场正在发生什么。剩下的依然是对商业本质的理解、对风险的控制以及对自己情绪的管理。从这个意义上说最好的策略永远是那个你能完全理解、深度信任并能坚持执行下去的策略。
返回列表