唐宇迪量化交易课笔记-全-
唐宇迪量化交易课笔记(全)
001:课程内容与大纲介绍 📚
在本节课中,我们将从整体上介绍这门“零基础Python金融分析与量化交易实战课程”的内容安排与核心模块。课程将完全聚焦于代码实战,通过处理真实金融数据来学习量化交易的核心技能。
课程核心模块概述

课程内容主要围绕以下三大核心模块展开。
模块一:Python数据分析与处理
本模块将讲解如何使用Python处理和分析数据。我们将学习如何操作数据、清洗数据以及进行基础的数据分析,为后续的金融应用打下坚实的Python基础。
模块二:金融数据分析
在掌握数据处理技能后,我们将进入金融数据分析领域。本模块将使用真实的股票数据,讲解对金融时间序列数据可以执行哪些操作和分析,例如计算指标、可视化等。
模块三:量化交易策略与回测
最后,我们将学习量化交易策略。核心是学习如何将策略思想转化为代码,并在历史数据上进行回测,以评估策略的盈利能力与风险。我们将分析各项回测指标,判断策略的优劣。
课程工具与实战风格
接下来,我们来看看课程讲解中将使用的主要工具和教学风格。
实战工具介绍
课程全程采用代码实战,没有传统PPT。重点在于如何在Python中实现金融分析任务,包括数据处理、策略回测和结果分析。
我们将主要使用Jupyter Notebook等工具进行演示,核心是掌握用Python“玩转”数据并完成量化交易项目的方法。
回测平台演示
当我们设计出一个交易策略后,需要验证其历史表现。这时就需要使用回测平台。
回测平台允许我们将策略代码应用到历史数据中,模拟交易过程,并生成详细的报告。报告会包含以下关键信息:
- 收益曲线:展示策略随时间变化的资产增长情况。
- 与基准比较:将策略收益与市场大盘(如沪深300指数)收益进行对比,计算超额收益。
- 交易详情:记录每一天的买卖操作、持仓情况。
- 账户信息:展示初始资金、最终资产、最大回撤等关键指标。

通过分析这些指标,我们可以科学地评估一个策略是盈利还是亏损,以及其风险收益特征。
课程特点与学习资源
本课程从零基础开始,即使不熟悉Python也能跟上。教学风格力求通俗易懂,用接地气的方式讲解工具使用和策略设计。
为了方便练习,课程将提供所有涉及到的数据和代码。

详细课程大纲 📋
以下是课程三个模块的详细内容规划。
模块一:Python必备工具包实战
本模块是基础篇,旨在打好Python编程与数据分析的基础。我们将学习:
- Python核心知识点与环境配置。
- 数值计算核心库
NumPy的使用。 - 数据分析核心库
Pandas的使用。
模块二:Python金融数据分析实战
本模块将理论与实践结合,进入金融领域。我们将学习:
- 获取并分析真实股票数据。
- 对金融时间序列进行统计与建模。
- 在量化平台中完成第一个回测项目,评估策略历史表现。
模块三:量化交易策略深入实战
本模块是进阶篇,深入量化交易的核心。我们将学习:
- 量化交易中的经典策略。
- 实际操作中的数据预处理与特征工程。
- 如何分析、改进策略,并将其应用于回测以获取更优的收益结果。


总结

本节课中,我们一起学习了本课程的核心框架。我们了解到,这是一门注重代码实现的纯实战课程,包含Python数据处理、金融数据分析和量化交易回测三大模块。课程从零开始,提供全部资料,旨在帮助大家通过动手实践掌握Python金融分析与量化交易的完整流程。从下一节课开始,我们将正式进入模块一的学习,从Python基础与环境搭建起步。
002:1-fbprophet股价预测任务概述 📈


在本节课中,我们将学习如何使用Facebook开源的Prophet框架进行时间序列预测,并应用于股价分析与预测任务。我们将从理解时间序列的基本概念开始,逐步介绍Prophet框架的特点、优势以及基本使用流程。


时间序列预测简介
上一节我们介绍了时间序列预测的基本概念。本节中,我们来看看什么是时间序列预测。
股价分析与预测,本质上是基于日期和价格数据,对未来趋势进行推断的任务。因此,这是一个典型的时间序列预测问题。时间序列数据是指按时间顺序排列的数据点序列。
为什么选择Prophet?🤔
在时间序列预测领域,存在多种模型,例如ARIMA模型,但其使用可能较为复杂。因此,我们选择介绍Facebook开源的Prophet框架,它是一个强大且易于使用的时间序列预测工具。
以下是Prophet框架的主要亮点:

- 使用便捷:该框架封装良好,用户只需调整少量参数即可快速构建模型。
- 灵活性高:支持按小时、天、周、月等多种时间粒度进行预测。
- 成分分解:能够自动处理时间序列中的趋势、周期性(如年、季节、周)和节假日效应。
- 鲁棒性强:可以自动处理数据中的异常值和缺失值。
- 趋势转折点:模型能够识别并适应历史数据中的趋势变化点,这有助于更好地拟合训练数据。但需注意,过多的转折点可能导致对测试数据的过拟合。
Prophet框架的核心算法结合了自回归、移动平均和整合模型的思想,但其优势在于提供了更友好、更高级的封装接口。

Prophet框架初探 🌐



Prophet提供了Python和R两种编程语言的接口。考虑到通用性,本教程将重点介绍Python接口的使用。


访问Prophet官方文档是学习的第一步。文档结构清晰,内容精炼,建议初学者花时间阅读以了解全貌。


基本使用流程 🔄


Prophet的使用流程与Scikit-learn等库的API设计类似,遵循“实例化模型 -> 拟合数据 -> 进行预测”的模式。
以下是使用Prophet的核心步骤:


- 数据准备:输入数据需要包含两列:
ds(日期时间列)和y(需要预测的数值指标列)。这是模型要求的固定格式。 - 模型创建与训练:首先实例化
Prophet类,然后调用其.fit()方法传入训练数据进行模型训练。 - 构建未来时间框:使用
.make_future_dataframe()函数创建需要预测的未来时间范围。 - 进行预测:调用
.predict()方法对未来的时间点进行预测。预测结果DataFrame中会包含预测值yhat以及置信区间yhat_lower和yhat_upper。 - 结果可视化:Prophet内置了便捷的可视化功能,例如
.plot()方法可以绘制预测结果及成分分解图,帮助理解趋势、周期性和节假日等因素的影响。
环境配置与数据获取 ⚙️
为了完成本课的股价预测示例,我们需要配置相应的Python环境并获取数据。

以下是需要安装的库和工具:

yfinance:一个用于从雅虎财经获取股票历史数据的Python库。可以通过命令pip install yfinance进行安装。prophet:Facebook的Prophet预测库。可以通过命令pip install prophet进行安装。matplotlib:用于数据可视化的绘图库。通常Anaconda环境已内置,如需安装可使用pip install matplotlib。
注意:运行数据获取代码时需要保持网络连接畅通。如果使用pip安装遇到问题,可以尝试通过下载源码包(如.whl文件或setup.py文件)进行本地安装。
课程代码结构预览 📁
本课程的演示代码主要封装在一个类中,核心功能包括:
- 数据获取与预处理函数。
- 模型构建、训练与预测函数。
- 结果可视化函数。
我们将首先在Jupyter Notebook中演示完整的预测流程并查看结果。随后,会在集成开发环境(如PyCharm或VSCode)中通过Debug模式深入代码内部,逐步解析每个函数的具体实现和工作原理。

本节课中我们一起学习了时间序列预测的基本概念,认识了Facebook Prophet框架的核心优势与特点,并概述了其基本使用流程以及完成本课实践所需的环境配置。接下来,我们将动手实践,使用Prophet对真实股价数据进行预测分析。
003:数据读取与初步探索 📈
在本节课中,我们将学习如何读取股票数据,并进行初步的可视化分析,以了解时间序列数据的基本形态。
数据读取与查看
首先,我们需要读取数据。这里我们选择微软(MSFT)的股票数据,获取其历史记录。
# 示例代码:读取股票数据
import yfinance as yf
stock = yf.Ticker("MSFT")
data = stock.history(period="max")


执行上述代码后,我们可以查看数据的结构。数据包含多个指标,例如日期(Date)、开盘价(Open)、收盘价(Close)等。我们主要关注的是收盘价(Close),这是我们后续要预测的目标变量。
初次执行代码时可能会稍慢,因为需要加载相关库。如果代码报错,通常是因为未安装所需的库(如 yfinance, pandas, matplotlib),需要先安装它们。
数据预处理
读取数据后,我们需要进行一些预处理。首先,重置索引以确保日期列是独立的。
data = data.reset_index()
接下来,我们提取关键列:日期(ds)和目标变量(y,即收盘价)。同时,可以计算一些衍生指标,例如每日价格变化(change)。
data['ds'] = data['Date']
data['y'] = data['Close']
data['change'] = data['Close'] - data['Open']
我们还可以确定数据的时间范围,即最早和最晚的日期。
start_date = data['ds'].min()
end_date = data['ds'].max()
根据这些日期,可以计算出起始价格、最近价格、最高/最低价格及其发生的日期。
数据可视化
在获得数据后,通常第一步是将其可视化,以观察整体趋势和模式。
以下是绘制股票价格趋势图的基本步骤:
- 指定要绘制的指标(例如收盘价
Close)。 - 可选地指定绘图的时间范围(
start_date,end_date)。 - 使用绘图库(如
matplotlib)绘制折线图。
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.plot(data['ds'], data['Close'], label='Close Price', color='red')
plt.xlabel('Date')
plt.ylabel('Price (USD)')
plt.title('Microsoft Stock Price Trend')
plt.legend()
plt.grid(True)
plt.show()
通过图表,我们可以看到微软股票从1986年到2018年初的整体上升趋势,期间虽有波动,但长期看是增长的。
多指标与自定义分析


除了绘制单一指标,我们还可以同时绘制多个指标进行比较,例如将收盘价和交易量(Volume)画在一起。



fig, ax1 = plt.subplots(figsize=(12, 6))
color = 'tab:red'
ax1.set_xlabel('Date')
ax1.set_ylabel('Close Price', color=color)
ax1.plot(data['ds'], data['Close'], color=color, label='Close Price')
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx()
color = 'tab:blue'
ax2.set_ylabel('Volume', color=color)
ax2.plot(data['ds'], data['Volume'], color=color, label='Volume', alpha=0.5)
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout()
plt.title('Close Price and Trading Volume')
plt.show()

此外,我们可以编写一个简单的函数来模拟投资回报。例如,计算在特定日期买入一定数量股票,并在另一日期卖出后的盈利情况。
def calculate_profit(data, start_date, end_date, shares=100):
buy_price = data.loc[data['ds'] == start_date, 'Close'].values[0]
sell_price = data.loc[data['ds'] == end_date, 'Close'].values[0]
profit = (sell_price - buy_price) * shares
return profit


这个函数可以帮助我们直观地理解在不同时间点投资的可能结果。



总结


本节课中我们一起学习了时间序列分析的初始步骤:
- 我们学习了如何使用
yfinance库读取股票数据。 - 我们进行了基本的数据预处理,包括提取日期、目标变量和计算衍生指标。
- 我们掌握了将时间序列数据可视化的方法,包括绘制单一趋势图和多个指标的对比图。
- 最后,我们通过一个简单的投资回报计算示例,加深了对数据应用的理解。



这些步骤是分析任何时间序列数据的基础,为我们后续建立预测模型做好了准备。
004:3-fbprophet时间序列预测实例 📈
在本节课中,我们将学习如何使用Facebook的Prophet库进行时间序列预测。我们将通过一个实例,从数据加载、模型构建、训练、预测到结果可视化,完整地走一遍流程。课程将重点解释核心参数的作用,并展示如何解读预测结果。
模型构建与初步预测 🏗️
上一节我们介绍了Prophet库的基本概念,本节中我们来看看如何具体构建一个预测模型并进行初步预测。
首先,我们执行create_model函数来构建模型。该函数会先实例化模型,然后进行拟合训练,最后生成预测。预测结果会以图表形式展示。
以下是构建和训练模型的核心步骤:
- 导入与设置:首先导入必要的库并设置绘图工具。
- 实例化模型:创建Prophet模型实例,并传入初始参数。
其中,model = Prophet( yearly_seasonality=True, weekly_seasonality=True, changepoint_prior_scale=0.05 )changepoint_prior_scale是一个关键参数,默认值为0.05,它控制模型对趋势变化的灵活度。 - 训练模型:使用历史数据对模型进行训练。这里我们指定使用最近3年的数据进行训练。
train_df = df.tail(3*365) # 假设df是包含日期‘ds’和值‘y’的DataFrame model.fit(train_df) - 生成预测:使用训练好的模型对历史数据进行“预测”(即模型拟合值),此时设置预测天数
periods=0。
预测结果forecast = model.predict(df)forecast是一个DataFrame,包含yhat(预测值)、yhat_lower和yhat_upper(置信区间上下界)等列。 - 绘制结果:将观测值(历史数据)和模型拟合值绘制在同一张图上进行对比。
在生成的图中,黑色圆点代表观测值,绿色线条代表模型的拟合值,浅绿色区域代表预测的置信区间。fig = model.plot(forecast) plt.show()
通过以上步骤,我们得到了一个基本的时间序列模型及其在历史数据上的拟合情况。
季节性分解分析 📊
在构建了基础模型之后,我们可以进一步分析数据中的季节性规律。Prophet提供了便捷的功能来可视化数据的年、周、月等季节性趋势。
以下是生成季节性分解图的步骤:
- 调用组件分析:使用
model.plot_components(forecast)方法。 - 解读图表:该方法会生成一系列子图。
- 趋势图:展示数据整体的长期变化趋势。
- 年度季节性:展示数据在一年内不同月份的变化规律。例如,可能发现12月和1月数值上浮最大,而7月、9月和10月有下降趋势。
- 周度季节性:展示数据在一周内不同日期的变化规律。对于股票数据,通常只关注周一至周五的趋势。
通过季节性分解,我们可以更清晰地理解影响时间序列的周期性因素。
识别趋势突变点 🔍
时间序列的趋势并非一成不变,可能存在一些转折点。Prophet可以自动识别这些趋势发生显著变化的点,称为“突变点”。
上一节我们分析了季节性,本节中我们来看看如何识别和分析趋势的突变点。
以下是识别和可视化突变点的过程:
- 获取突变点:模型训练后,突变点信息存储在
model.changepoints中。 - 计算变化方向:对于每个突变点,可以检查其趋势变化率(可近似理解为二阶导数的符号)。正数表示趋势加速上升或由降转升,负数则表示趋势加速下降或由升转降。
- 可视化:在原始序列图上,可以用不同颜色的竖线标记出正向和负向的突变点。
# 获取突变点日期
changepoints = model.changepoints
# 此处示例逻辑:计算变化率并分类(实际需根据模型输出计算)
positive_changepoints = [...] # 趋势增强的点
negative_changepoints = [...] # 趋势减弱的点
# 在图上标注
for cp in positive_changepoints:
plt.axvline(cp, color='g', linestyle='--', alpha=0.5)
for cp in negative_changepoints:
plt.axvline(cp, color='r', linestyle='--', alpha=0.5)
分析突变点有助于理解序列动态变化的关键时刻。
进行未来预测 🔮
模型的核心用途是对未来进行预测。接下来,我们将演示如何使用训练好的模型预测未来的时间点。
以下是进行未来预测的步骤:
- 构建未来时间框:使用
make_future_dataframe方法,指定需要预测的天数,例如periods=180天。future = model.make_future_dataframe(periods=180) - 执行预测:对包含未来日期的
future数据框进行预测。forecast_future = model.predict(future) - 可视化预测结果:再次调用
model.plot方法,此时图表会包含历史数据、历史拟合值以及未来阶段的预测值及置信区间。
在结果图中,黑色点(观测值)在历史截止点后不再出现,而绿色的预测线会向后延伸,展示未来180天的预测趋势。fig = model.plot(forecast_future) plt.show()
预测结果显示,模型会基于历史规律推断未来趋势。预测的保守或激进程度,可以通过调整changepoint_prior_scale等参数来影响。
总结 📝
本节课中我们一起学习了使用Facebook Prophet库进行时间序列预测的完整流程。

我们首先构建并训练了一个基础模型,观察了其在历史数据上的拟合效果。接着,我们分解并分析了数据的季节性成分。然后,我们探索了如何识别序列中的趋势突变点。最后,我们利用模型对未来180天进行了预测,并可视化了预测结果。


整个过程中,changepoint_prior_scale是一个关键参数,它影响着模型对趋势变化的敏感度。通过这个实例,我们掌握了使用Prophet进行端到端时间序列分析的基本方法。
005:亚马逊股价趋势预测与模型评估 📈
在本节课中,我们将学习如何对一个基本的股价预测模型进行评估,并分析其预测效果。我们将以亚马逊(AMZN)的股票数据为例,通过计算误差和趋势预测准确率来量化模型的性能,并观察其预测结果与实际走势的差异。


数据准备与模型构建
上一节我们介绍了基础模型的构建方法,本节中我们来看看如何将其应用于新的数据。首先,我们更换股票数据,使用亚马逊(AMZN)的股价进行分析。数据时间跨度从1997年5月22日的最低点,到我们执行代码时的近期高点。
以下是构建基础模型的核心步骤代码:
# 导入必要的库(假设已安装)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from prophet import Prophet
# 1. 加载并准备亚马逊股票数据
# df 应包含两列:'ds' (日期) 和 'y' (股价,如收盘价)
# 此处为示例,实际数据需从文件或API获取
# df = pd.read_csv('amzn_stock.csv')
# 2. 初始化并拟合Prophet模型
model = Prophet()
model.fit(df)



# 3. 构建未来时间框架并进行预测
future = model.make_future_dataframe(periods=90) # 预测未来90天
forecast = model.predict(future)



模型拟合后,我们可以得到预测结果。基础的预测模型倾向于产生更平滑、更保守的趋势线,这有助于避免对历史数据中的噪声和突变点过度拟合。
模型评估方法
仅仅得到预测结果是不够的,我们需要一套方法来评估模型的优劣。评估主要围绕两个核心方面进行:预测数值的误差以及预测趋势方向的准确性。


以下是评估模型时需要考虑的两个主要方面:

- 预测误差:计算预测值与真实值之间的差异。通常,我们会将数据集划分为训练集和测试集。在训练集上评估模型的学习能力,在测试集(模型未见过的数据)上评估其泛化能力。误差可以用平均绝对误差等公式表示:
error = |y_true - y_pred| - 趋势预测准确率:股票预测中,判断价格是“上涨”还是“下跌”往往比精确的数值更重要。因此,我们需要统计模型正确预测趋势方向(上升或下降)的比例。
实施评估与结果分析
现在,我们将上述评估方法付诸实践。我们以2017年1月24日作为分界点,将此日期之前三年的数据作为训练集,之后一年的数据作为测试集。
以下是实施评估的关键步骤代码片段:
# 划分训练集和测试集
train = df[df['ds'] < '2017-01-24']
test = df[df['ds'] >= '2017-01-24']



# 在训练集上拟合模型
model = Prophet()
model.fit(train)
# 为测试集日期构建未来数据框并预测
future_test = model.make_future_dataframe(periods=len(test))
forecast_test = model.predict(future_test)
# 从预测结果中提取测试集对应的部分
predicted_test = forecast_test[forecast_test['ds'].isin(test['ds'])]['yhat'].values
true_test = test['y'].values

# 计算测试集平均绝对误差
test_error = np.mean(np.abs(true_test - predicted_test))
print(f"测试集平均绝对误差: {test_error}")
# 计算趋势预测准确率
def calculate_trend_accuracy(true_series, pred_series):
# 计算每日变化的方向(1为上涨,0为下跌)
true_trend = np.sign(np.diff(true_series)) # np.sign: 正数为1,负数为-1,零为0
pred_trend = np.sign(np.diff(pred_series))
# 只比较有变化的日子(忽略变化为0的情况)
mask = true_trend != 0
accuracy = np.mean((true_trend[mask] == pred_trend[mask]).astype(float))
return accuracy
trend_accuracy = calculate_trend_accuracy(true_test, predicted_test)
print(f"趋势方向预测准确率: {trend_accuracy:.2%}")

运行评估代码后,我们得到了量化的结果。例如,测试集的平均绝对误差可能达到160美元以上,而趋势预测的准确率可能在50%左右徘徊,这接近随机猜测的水平。可视化图表也清晰地显示,模型的预测线(如蓝色线)较为平缓,而真实的股价走势(黑色线)波动更大且整体位置更高,两者存在显著差距。

总结与后续方向
本节课中我们一起学习了如何对一个时间序列预测模型进行基本评估。我们以亚马逊股价为例,实施了从数据划分、模型拟合到计算预测误差和趋势准确率的完整流程。评估结果表明,我们的基础模型虽然能捕捉大致趋势,但在数值精确度和波动性捕捉上表现不佳,预测结果过于保守和平滑。


这个结果明确指出了模型的不足,也为我们指明了下一步的方向:模型调参与优化。我们需要调整模型的参数,使其能够更好地捕捉数据中的变化模式和季节性,从而提升预测的准确性和对趋势转折点的判断能力。这正是我们下一节课将要深入探讨的内容。
006:突变点调参 🎯
在本节课中,我们将学习如何调整 Prophet 模型中的一个关键参数——突变点(change point)的权重,并观察其对预测结果的影响。我们将通过实验对比不同参数值下的模型表现,并学习如何选择最优参数。
概述
Prophet 模型中的 changepoint_prior_scale 参数用于控制模型对历史数据中突变趋势的敏感度。调整此参数可以平衡模型的拟合能力与过拟合风险。
核心概念:突变点权重
突变点权重参数 changepoint_prior_scale 决定了模型对数据中突变趋势的重视程度。
- 公式/参数:
changepoint_prior_scale = value - 值越大:模型会更努力地拟合训练数据中的每一个波动和突变,可能导致过拟合。
- 值越小:模型会更保守,倾向于平滑的趋势,可能导致欠拟合。
- 默认值:在 Prophet 框架中,此参数的默认值为
0.05。
上一节我们介绍了 Prophet 模型的基本构建,本节中我们来看看如何通过调整突变点权重来优化模型。
参数影响实验
为了直观展示不同 changepoint_prior_scale 值的影响,我们进行了以下实验。
以下是实验选取的四组参数值:
- 0.001
- 0.005
- 0.1
- 0.2
实验代码的核心逻辑如下:
- 加载历史数据(例如2015年至2017年的股票数据)。
- 循环遍历每一个参数值。
- 为每个参数值创建一个新的 Prophet 模型并进行配置。
- 使用该模型进行未来180天的预测。
- 将不同参数下的预测结果与真实值绘制在同一张图中进行对比。
# 示例代码逻辑
for cp_value in [0.001, 0.005, 0.1, 0.2]:
model = Prophet(changepoint_prior_scale=cp_value)
model.fit(train_df)
future = model.make_future_dataframe(periods=180)
forecast = model.predict(future)
# 绘图...
通过对比预测曲线,我们可以观察到:
- 蓝色线 (0.001):参数值很小。模型未能捕捉到训练数据中的多个突变点,预测趋势非常平稳,表现出明显的欠拟合。
- 红色线 (0.05, 默认值):效果有所改善,但预测的上升趋势依然保守。
- 灰色/黄色线 (0.1, 0.2):参数值较大。模型更好地拟合了训练数据中的突变点,预测趋势更加激进。但过大的权重可能带来过拟合风险,即模型过于“记忆”训练数据中的噪声,而在未知数据上表现变差。
模型评估与参数选择
仅仅观察拟合曲线不够客观,我们需要量化评估不同参数下模型的预测性能。
以下是评估不同参数模型在训练集和测试集上误差的步骤:
- 将数据划分为训练集和测试集。
- 用不同参数训练模型,并在测试集上进行预测。
- 计算预测值与真实值之间的误差(如均方误差 MSE)。
- 选择在测试集上误差最小的参数作为最优参数。
实验结果表明,随着 changepoint_prior_scale 值增大:
- 训练误差 (Train Error) 持续下降,因为模型越来越贴合训练数据。
- 测试误差 (Test Error) 先下降后可能上升。下降阶段说明模型泛化能力增强;若继续增大参数导致测试误差上升,则意味着出现了过拟合。
在我们的实验中,当参数值增加到 0.7 左右时,测试误差达到了最低点(例如66)。因此,0.7 是本实验场景下的较优选择。

使用优化后的参数 (changepoint_prior_scale=0.7) 重新训练模型并进行预测,可以发现预测值与真实值的差距(如1263 vs 1294)比使用默认参数时更小,模型效果得到了提升。


进阶探索与总结



本节课中我们一起学习了 Prophet 模型中 changepoint_prior_scale 参数的核心作用与调优方法。我们了解到:
- 该参数控制模型对趋势突变的灵活度。
- 参数值过小会导致欠拟合,过大则可能导致过拟合。
- 应基于测试集误差来选择最优参数,而不仅仅是追求对训练数据的高拟合度。



要深入了解 Prophet 的每个参数和功能,最好的方法是查阅其官方文档和 GitHub 仓库,并亲自动手实践。
提示:时间序列预测,尤其是像股价预测这类问题,本身具有很大挑战性。即使经过参数调优,模型的预测准确率也可能存在瓶颈,这在实际应用中是需要理性看待的。

通过本教程,你掌握了调整 Prophet 模型关键参数以改善预测效果的基本技能。你可以将此方法应用到自己的时间序列数据集上,探索最适合你数据的模型配置。
007:连续指标变化情况分析 📈


在本节课中,我们将要学习如何分析金融时间序列的连续变化情况。我们将探讨为什么简单的增长率累加会失效,并引入对数方法来解决这个问题,最终计算出“初始投资相当于未来某一天的价值”这一核心指标。
简单增长率累加的问题
上一节我们介绍了如何计算增长率。本节中我们来看看,能否将一段时间内每天的增长率简单累加,来代表这段时间的总增长率。

例如,假设有一只股票:
- 第一天价格:
100 - 第二天价格:
50 - 第三天价格:
75
以下是两天的增长率计算:
- 第二天增长率:
(50 - 100) / 100 = -0.5 - 第三天增长率:
(75 - 50) / 50 = 0.5
如果简单累加:-0.5 + 0.5 = 0。这个结果为0,似乎意味着股票从第一天到第三天“没有涨也没有跌”。但观察原始数据,股价从100变为75,实际上是下跌的。这说明简单的增长率累加无法正确反映连续时间内的整体变化。
引入对数计算连续变化

对于连续时间的序列,如果想进行有意义的累加操作,我们需要借助数学中的对数。

核心思路是:计算每日价格比的对数,然后进行累加。对数的加法等价于原始数值的乘法。
沿用上面的例子,我们计算对数增长率:
log(50/100) + log(75/50) = log( (50/100) * (75/50) ) = log(75/100)

最终,log(75/100) 这个结果直接反映了第三天价格与第一天价格的总体比例关系。将这个思想推广到整个时间序列:将每一天相对于前一天的对数收益率累加起来,就得到了从第一天到最后一天的总对数收益率,它精确描述了资产在整个期间的整体表现。
形象地说,这解决了“第一天投入的一块钱,相当于最后一天是多少钱”的问题。

代码实现:计算对数收益率与累积收益



我们将使用Python的Pandas和NumPy库来实现上述计算。假设我们有一个包含多家公司股价的DataFrame data。

1. 计算每日对数收益率


首先,我们需要计算每一天价格相对于前一天价格的对数。


import numpy as np
import pandas as pd
# 计算对数收益率
# data.shift(1) 将数据整体向下移动一行,得到“前一天”的价格
returns = np.log(data / data.shift(1))



# 查看前几行结果
print(returns.head().round(6))
代码解释:
data.shift(1):将数据向下移动1行,这样每一行就对应了原始数据中前一日的价格。data / data.shift(1):计算当日价格与前一日价格的比值。np.log(...):计算比值的自然对数,得到对数收益率。

2. 计算累积对数收益率

接下来,我们对对数收益率进行累加,得到从期初到任意一天的累积对数收益率。


# 计算累积对数收益率
cumulative_log_returns = returns.cumsum()

3. 还原为实际价值比例(累积收益)

累积对数收益率是数值,为了更直观地理解(例如,期初1元变成了多少元),我们需要将其还原。

# 将累积对数收益率还原为累积收益(即价值倍数)
cumulative_returns = np.exp(cumulative_log_returns)

# 绘制累积收益曲线图
cumulative_returns.plot(figsize=(16, 8))

代码解释:
returns.cumsum():对returns的每一列进行累加求和。np.exp(...):指数函数exp是自然对数log的逆运算,它将累加的对数收益率还原为总的价值乘数。例如,结果为2.5,表示期初的1元变成了2.5元。


生成的图表会清晰展示:以起始日为基准(标准化为1),各家公司的股价在后续每一天相当于期初的多少倍。这直接反映了长期投资的增长情况。
常用时间序列操作总结


本节课中我们一起学习了金融时间序列分析中的几个核心操作:


- 差值计算:
data.diff()可以方便地计算相邻数据的差值。 - 简单增长率:
(后值 - 前值) / 前值,可以通过data.pct_change()快速计算。 - 对数收益率:
np.log(后值 / 前值)。这是分析连续复利增长的关键,因为它具有可加性。 - 累积收益计算:
- 先计算对数收益率序列。
- 使用
.cumsum()对其累加,得到累积对数收益率。 - 使用
np.exp()将累积对数收益率还原为累积收益倍数。


这些工具和方法是金融数据分析的基础,能够帮助我们准确评估资产价格在不同时间尺度上的变化行为和长期增长趋势。
008:时间序列重采样与窗口操作 📊
在本节课中,我们将学习时间序列数据处理中的两个核心操作:重采样与时间窗口操作。重采样用于改变数据的时间频率,而窗口操作则用于在滑动的时间段内进行统计分析。掌握这些技能对于时间序列数据的分析和特征工程至关重要。


时间序列分析:P8-1:重采样操作 🔄
上一节我们介绍了时间序列的基础概念。本节中,我们来看看如何对时间序列数据进行重采样。

重采样是指将数据从一个时间频率转换到另一个时间频率。例如,原始数据可能是按天记录的,我们可以将其转换为按周、按月或按年进行统计。


进行重采样操作时,至少需要指定一个参数,即新的时间频率单位(如“W”代表周,“M”代表月)。同时,你需要指定在聚合数据时使用哪种统计指标,例如均值、第一个值或中位数。
以下是重采样的基本代码示例:

# 假设 df 是一个以日期为索引的DataFrame
# 按周进行重采样,并计算每周的均值
weekly_resampled = df.resample('W').mean()
print(weekly_resampled.head())
执行上述代码后,你会得到以周为单位的聚合数据。结果中的日期标签默认是每个时间窗口的最后一天。

关于标签位置的说明

在重采样时,我们可以通过 label 参数控制聚合结果的日期标签是使用时间窗口的起始点(left)还是结束点(right)。
以下是两种不同标签设置的对比:


# 默认使用右标签(时间窗口的结束日期)
resampled_right = df.resample('M', label='right').mean()
print(resampled_right.head())

# 使用左标签(时间窗口的起始日期)
resampled_left = df.resample('M', label='left').mean()
print(resampled_left.head())

选择 left 或 right 取决于具体分析需求。通常,使用 right(默认值)更符合逻辑,因为它表示该统计值所描述的时间段已经结束。
时间序列分析:P8-2:时间窗口(滑动窗口)操作 🪟
上一节我们介绍了重采样,它改变了数据的观察频率。本节中,我们来看看时间窗口操作,它用于在连续滑动的固定时间段内进行动态分析。
时间窗口操作,常被称为滑动窗口,其概念是:设定一个固定长度的窗口(例如10天),从序列起始点开始,计算该窗口内的统计量(如最小值、均值)。然后,窗口向后滑动一个时间单位(例如1天),再计算新窗口内的统计量,如此重复。
以下是实现滑动窗口操作的基本步骤。首先,我们需要处理数据中的缺失值,因为缺失值会影响窗口计算。
# 1. 处理缺失值
df_cleaned = df.dropna()

# 2. 定义窗口大小
window_size = 10

# 3. 计算滑动窗口的最小值
rolling_min = df_cleaned['column_name'].rolling(window=window_size).min()
print(rolling_min.head(15))
在计算结果中,前 window_size - 1 个值通常是 NaN,因为最初的几个窗口数据不足。

计算多个窗口统计量


我们可以一次性为每个滑动窗口计算多个统计指标,并将其作为新列添加到数据中。
以下是同时计算最小值、最大值、均值和标准差的示例:
# 在清理后的数据上计算多个滚动统计量
df_cleaned['rolling_min'] = df_cleaned['column_name'].rolling(window=window_size).min()
df_cleaned['rolling_max'] = df_cleaned['column_name'].rolling(window=window_size).max()
df_cleaned['rolling_mean'] = df_cleaned['column_name'].rolling(window=window_size).mean()
df_cleaned['rolling_std'] = df_cleaned['column_name'].rolling(window=window_size).std()


# 查看结果
print(df_cleaned.tail())
通过这种方式,我们可以基于滑动窗口生成丰富的特征,用于后续的时间序列建模或分析。


总结 📝


本节课中我们一起学习了时间序列的两个重要操作:
- 重采样:通过
df.resample()方法改变数据的时间频率,并可使用label参数控制聚合结果的标签位置。 - 时间窗口操作:通过
df.rolling()方法创建滑动窗口,可以动态计算窗口内的各种统计量(如min(),max(),mean(),std()),是构建时间序列特征的关键技术。

进行这些操作前,务必记得预处理数据,特别是处理缺失值,以确保计算结果的准确性。
009:3-短均与长均计算实例 📈

在本节课中,我们将学习如何通过计算股票的短期和长期移动平均线,来构建一个简单的分析策略。我们将重点理解“黄金交叉”和“死亡交叉”这两个核心概念,并通过代码实现和可视化来直观地展示它们。


概述
移动平均线是分析股票趋势的常用工具。通过比较短期和长期移动平均线,我们可以判断市场的潜在买入或卖出信号。具体来说,当短期均线从下方上穿长期均线时,形成“黄金交叉”,通常被视为买入信号;反之,当短期均线从上方下穿长期均线时,形成“死亡交叉”,通常被视为卖出信号。


计算短期与长期移动平均线
要识别交叉点,我们首先需要计算两条移动平均线:一条代表短期趋势,另一条代表长期趋势。
在金融分析中,常见的短期窗口是5天(代表一周的交易日),长期窗口是20天或30天(代表一个月左右的交易日)。由于我们的示例数据时间跨度较长,为了更清晰地展示,我们将适当放大窗口。
以下是计算步骤。我们以苹果公司(AAPL)的股票数据为例。


首先,我们计算短期移动平均线(例如10天窗口)和长期移动平均线(例如30天窗口)。在Pandas中,这可以通过.rolling()和.mean()方法轻松实现。


# 假设 df 是包含苹果股价的DataFrame,其中‘Close’是收盘价列
# 计算短期移动平均线 (M1)
df[‘M1’] = df[‘Close’].rolling(window=10).mean()
# 计算长期移动平均线 (M2)
df[‘M2’] = df[‘Close’].rolling(window=30).mean()

可视化移动平均线与股价


计算完成后,我们可以将股价与两条移动平均线一同绘制出来,以便直观观察它们的走势关系。


为了使图表更清晰,我们可以只选取一段时间内的数据进行绘图。
import matplotlib.pyplot as plt
# 选取最近一年的数据用于绘图(假设一年有约252个交易日)
df_recent = df.tail(252)
plt.figure(figsize=(12, 6))
plt.plot(df_recent[‘Close’], label=‘Apple Stock Price’, alpha=0.5)
plt.plot(df_recent[‘M1’], label=‘Short-term MA (10 days)’, color=‘green’)
plt.plot(df_recent[‘M2’], label=‘Long-term MA (30 days)’, color=‘red’)
plt.legend()
plt.show()


在生成的图表中,蓝色线(或原色线)代表股价,绿线代表短期均线(M1),红线代表长期均线(M2)。

识别黄金交叉与死亡交叉


现在,我们来观察图表中的交叉点。

- 死亡交叉:当短期均线(绿色)从上方向下穿越长期均线(红色)时,表明短期趋势转弱,可能预示着下跌行情,此时不适合买入,应考虑卖出。
- 黄金交叉:当短期均线(绿色)从下方向上穿越长期均线(红色)时,表明短期趋势走强,可能预示着上涨行情,此时可以考虑买入。
通过观察图表,你可以清晰地找到这些交叉点,并理解其市场含义。

使用信号指标进行增强分析

为了更精确地定位交叉点,我们可以创建一个信号指标。这个指标明确标出短期均线高于或低于长期均线的区域。

我们可以使用np.where函数来创建这个位置信号:当短期均线大于长期均线时,标记为1;否则标记为-1。


import numpy as np

# 生成位置信号
df[‘Position’] = np.where(df[‘M1’] > df[‘M2’], 1, -1)

为了在图表中同时展示股价和这个信号指标,我们需要使用双Y轴。左边Y轴显示股价和移动平均线,右边Y轴显示我们的位置信号(1或-1)。

fig, ax1 = plt.subplots(figsize=(14, 7))
# 第一个Y轴:绘制股价和均线
ax1.plot(df_recent[‘Close’], label=‘Price’, color=‘blue’, alpha=0.5)
ax1.plot(df_recent[‘M1’], label=‘M1 (10-day)’, color=‘green’)
ax1.plot(df_recent[‘M2’], label=‘M2 (30-day)’, color=‘red’)
ax1.set_ylabel(‘Price’)
ax1.legend(loc=‘upper left’)

# 第二个Y轴:绘制位置信号
ax2 = ax1.twinx()
ax2.plot(df_recent[‘Position’], label=‘Position Signal’, color=‘purple’, linestyle=‘--’)
ax2.set_ylabel(‘Signal (1 or -1)’)
ax2.set_ylim([-1.5, 1.5])
ax2.legend(loc=‘upper right’)


plt.title(‘Stock Price with Moving Averages and Position Signal’)
plt.show()
在这张增强的图表中,紫色虚线代表我们的位置信号。当信号从-1变为1时,对应着“黄金交叉”;当信号从1变为-1时,对应着“死亡交叉”。这使得交叉点的识别变得更加直接和自动化。
总结

本节课我们一起学习了如何利用移动平均线进行简单的股票趋势分析。我们掌握了以下核心技能:


- 计算移动平均线:使用
.rolling().mean()计算指定窗口的短期和长期移动平均线。 - 核心概念:理解了黄金交叉(买入信号)和死亡交叉(卖出信号)的市场含义。
- 数据可视化:绘制股价与移动平均线图表,直观识别交叉点。
- 信号生成:利用
np.where创建位置信号指标,并通过双Y轴图表进行增强展示。


通过这个实例,你可以看到,借助Pandas和Matplotlib等工具,常见的金融数据分析可以变得非常直观和易于实现。你可以尝试调整不同的时间窗口,或将其应用于其他股票数据,来验证策略的有效性。
010:4-指标相关情况分析 📊
在本节课中,我们将学习如何分析两个金融指标(标普500指数和恐慌指数)之间的关系。我们将通过数据可视化(折线图、散点图)和回归分析来探索它们之间的相关性,特别是负相关关系。





第一步:数据准备与提取


首先,我们需要从数据集中提取出要分析的两个指标列。这里我们选择 SPX(标普500指数)和 VIX(恐慌指数)。


# 假设我们有一个名为 data2 的 DataFrame
data = data2[['SPX', 'VIX']]
print(data.head())
执行以上代码后,我们将得到一个只包含 SPX 和 VIX 两列数据的新 DataFrame。




第二步:初步可视化观察


为了直观地观察两个指标随时间的变化趋势,我们首先尝试将它们绘制在同一张图中。

data.plot()


然而,直接将两个量级和单位不同的指标画在同一坐标系下,趋势并不清晰。因此,一个更好的方法是使用双Y轴图,让每个指标拥有自己的纵坐标轴。


ax = data.plot(secondary_y=['VIX'], figsize=(10, 6))

通过指定 secondary_y 参数,VIX 数据将使用右侧的Y轴。这样,两个指标的走势对比就清晰多了。我们可以观察到,当 SPX 下降时,VIX 往往上升,初步暗示了负相关关系。


为了更清晰地观察短期关系,我们可以只选取一段时间的数据(例如2010年至2012年)进行绘图。


data_subset = data['2010-01-01':'2012-12-31']
ax = data_subset.plot(secondary_y=['VIX'], figsize=(12, 6))



第三步:计算变化率并绘制散点图矩阵

为了更深入地分析关系,我们计算指标的日变化率。这可以通过当前值除以前一日值(使用 .shift() 方法)来实现。
returns = np.log(data / data.shift(1))
print(returns.head())

接下来,我们使用散点图矩阵来同时观察单个指标的分布以及两个指标两两之间的关系。Pandas 提供了便捷的绘图函数。

以下是绘制包含直方图(对角线)的散点图矩阵的代码:
pd.plotting.scatter_matrix(returns,
alpha=0.2, # 设置点的透明度,避免重叠
diagonal='hist', # 对角线绘制直方图
hist_kwds={'bins': 50}, # 直方图分为50个区间
figsize=(10, 10))


执行后,我们会得到一个2x2的矩阵图:
- 对角线:分别是
SPX和VIX变化率的分布直方图。 - 非对角线:是
SPX与VIX变化率之间的散点图,可以直观看到数据点的分布形态。
我们也可以将对角线的图换成更平滑的核密度估计图:



pd.plotting.scatter_matrix(returns,
alpha=0.2,
diagonal='kde', # 对角线绘制核密度估计图
figsize=(10, 10))

核密度估计图通过平滑处理,能更清晰地展示数据的分布轮廓。从散点图中,我们可以更明确地看到 SPX 变化率与 VIX 变化率呈现的负相关趋势(一个上升,另一个倾向于下降)。





总结

本节课中我们一起学习了如何分析两个变量间的相关关系:
- 数据提取:首先从数据集中筛选出目标指标。
- 趋势观察:通过绘制双Y轴折线图,直观对比两个指标随时间的变化趋势。
- 深入分析:计算指标的变化率,并利用散点图矩阵同时观察单变量分布和双变量关系。散点图能清晰地揭示变量间是否存在线性相关(如我们观察到的负相关),而对角线上的直方图或核密度图则展示了每个变量自身的分布情况。


这些可视化方法是探索性数据分析的基础,能帮助我们快速把握数据特征,为后续的定量分析(如计算相关系数、进行回归建模)提供重要依据。
011:回归方程与相关系数实例 📈



在本节课中,我们将学习如何使用Python工具包构建回归方程、计算相关系数,并实现数据的可视化。我们将通过一个金融时间序列的实例,演示从数据处理到模型构建与结果展示的完整流程。
构建回归方程


上一节我们介绍了数据处理的基本方法,本节中我们来看看如何构建回归方程。构建回归方程的方法很多,许多工具包都可以完成。这里我们选择使用NumPy的polyfit函数来计算回归系数。



polyfit函数可以帮助我们计算回归系数。它需要传入几个参数。


以下是调用polyfit函数的关键参数:
- x: 自变量数据。
- y: 因变量数据。
- deg: 回归方程的阶数。
deg=1表示方程包含X的一次项和零次项(即y = kx + b)。deg=2则表示方程包含X的二次项、一次项和零次项。

本任务不涉及曲线拟合,因此将阶数deg指定为1即可。

在构建回归方程前,需要确保数据中没有空值。我们可以使用dropna方法处理数据。

# 假设 df 是包含数据的DataFrame,x_col 和 y_col 是列名
x = df[x_col].dropna()
y = df[y_col].dropna()
coeff = np.polyfit(x, y, deg=1)
执行上述代码后,我们就得到了回归方程 y = kx + b 中的系数 k 和 b。



可视化回归方程

现在我们已经求解出回归方程,接下来可以在图中将结果画出来。


首先,我们需要画出原始数据的散点图。
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
plt.scatter(x, y, s=16) # 绘制散点图



然后,我们需要在这张图上继续绘制回归直线。回归直线由一系列点构成,我们需要计算这些点的Y值。


利用之前求得的回归系数 coeff,我们可以根据X值计算出对应的预测Y值。


# 计算回归直线上的y值
y_pred = np.polyval(coeff, x)
# 绘制回归直线
plt.plot(x, y_pred, color='red')
plt.show()



现在,我们就把回归方程在图中画出来了。结果一目了然,可以清晰地看到两个指标之间的关系。

需要注意的是,在构建回归方程或进行统计前,最好对数据进行适当的预处理,例如本实例中使用的就是增长率数据。


计算相关系数


除了回归方程,两个数值之间还可以计算相关系数。相关系数的计算和展示也非常简单。
如果数据集中只有两个指标,可以直接计算它们的相关系数矩阵。



# 假设 df 是包含两列数据的DataFrame
correlation_matrix = df.corr()
print(correlation_matrix)

这个表格会显示相关系数。自己与自己的相关系数是1,非对角线上的值是两两指标间的相关系数,并且矩阵是对称的。

附加任务:随时间变化的相关系数

现在,我们增加一道附加题:分析随着年份变化,两个指标间相关系数的变化情况,并进行展示。


这需要考虑时间窗口。例如,我们可以设定一个窗口大小(如250天),然后在时间序列上滑动这个窗口。对于每一个窗口内的数据,计算两个指标在该时间段内的相关系数。


以下是实现代码的关键步骤:


# 假设 df[‘col1‘] 和 df[‘col2‘] 是两个指标的时间序列数据
window_size = 250
# 计算滚动窗口相关系数
rolling_corr = df[‘col1‘].rolling(window=window_size).corr(df[‘col2‘])
# 可视化结果
rolling_corr.plot(figsize=(11, 6))
plt.show()


通过以上操作,我们就能计算出相关系数随时间的变化情况,并将其可视化。这比计算一个整体的相关系数能提供更多动态信息。




本节课中我们一起学习了针对金融时间序列的基本处理方法。我们利用Pandas和NumPy工具包进行了统计分析、模型计算与结果展示。建议大家在课后对照练习,理解代码的用途,并尝试修改参数(如窗口大小)来巩固所学知识。本节内容较为丰富,涵盖了时间序列分析的多个实用技巧。
012:金叉与死叉介绍 📈



在本节课中,我们将要学习双均线策略的核心概念:金叉与死叉。我们将了解如何通过计算短期和长期移动平均线来识别股票的趋势变化点,并以此作为买卖决策的依据。


双均线概念



上一节我们介绍了交易数据的基本形式,本节中我们来看看如何从这些数据中提取趋势信息。仅观察每日收盘价,我们很难把握整体趋势,因为它只反映了单个时间点的价格波动。


为了更清晰地描述价格的整体变化趋势,我们引入“双均线”的概念。双均线策略涉及两条线:一条是短期移动平均线,另一条是长期移动平均线。


短期与长期均线


以下是短期均线的计算方法:



短期均线通过计算一个较短时间窗口内价格的平均值来反映近期趋势。例如,我们可以选择5个交易日(即一周)作为一个窗口。


假设我们有一系列收盘价数据,对于每个时间点,我们计算包含该点在内的前5个交易日的收盘价平均值。用公式表示,在时间点 t 的5日简单移动平均(SMA)为:



SMA(5)t = (Price_t + Price + ... + Price_{t-4}) / 5


通过计算每个连续5日窗口的平均值并连接起来,我们就能得到一条平滑的曲线,它比原始日线图更能清晰地显示短期价格趋势。


长期均线的计算原理与短期均线完全相同,唯一的区别在于所选取的时间窗口更长。常见的长期窗口有20日(约一个月)、40日或60日等。其公式为:


SMA(N)t = (Price_t + Price + ... + Price_{t-(N-1)}) / N



其中 N 代表长期窗口的大小,例如20。长周期均线对价格短期波动的敏感性较低,更能反映市场的长期趋势。


金叉与死叉


在绘制出短期均线(例如5日均线)和长期均线(例如20日均线)后,我们可以观察它们的相对位置变化,这引出了两个关键的交易信号:金叉和死叉。
以下是金叉与死叉的定义:

- 金叉(黄金交叉 / 买点):当短期均线从下方向上穿越长期均线时,形成金叉。这通常被视为一个买入信号。它表明近期价格上涨动能开始超越长期趋势,市场可能进入上升通道。
- 死叉(死亡交叉 / 卖点):当短期均线从上方向下穿越长期均线时,形成死叉。这通常被视为一个卖出信号。它表明近期价格下跌动能开始显现,市场可能进入下降通道。

双均线交易策略


基于金叉和死叉的信号,我们可以构建一个简单的交易策略:在金叉出现时买入股票,在死叉出现时卖出股票。理想情况下,如果每次信号都预测准确,通过“低买高卖”的循环,投资者可以实现持续盈利。


本节课中我们一起学习了双均线策略的基础,包括短期与长期移动平均线的计算,以及如何利用它们的交叉点——金叉和死叉——来识别市场的潜在买卖时机。在接下来的实践中,我们将使用Python代码来演示如何实现这一策略。
013:买点与卖点可视化分析 📈


在本节课中,我们将学习如何利用移动平均线(MA)策略,对股票价格数据进行可视化分析,并识别潜在的买入和卖出信号。我们将使用苹果公司的股价数据作为示例,通过计算短期和长期移动平均线,并观察它们的交叉点来制定交易策略。


第一步:导入工具包与数据读取


首先,我们需要导入必要的Python库,并加载我们的股票价格数据集。

import pandas as pd
import matplotlib.pyplot as plt
接下来,我们读取包含多只股票价格的历史数据文件。数据集的第一列是时间,我们将它设置为索引并解析为日期时间格式。

# 读取数据,指定第一列为索引(时间)
data = pd.read_csv('data.csv', index_col=0, parse_dates=True)

读取数据后,我们选择苹果公司(AAPL)的股价列作为本次分析的对象。


# 选择苹果公司的股价列
apple_data = data[['AAPL']].copy()


第二步:数据预处理与缺失值处理

在开始计算之前,我们需要确保数据的完整性。金融数据中常存在缺失值,这会影响移动平均线的计算。因此,我们的首要任务是清除这些缺失值。
# 删除缺失值
apple_data = apple_data.dropna()

处理完成后,我们可以查看一下数据的前几行,确认数据已准备就绪。

print(apple_data.head())

第三步:计算短期与长期移动平均线

移动平均线是技术分析的核心工具。我们将计算两个不同时间窗口的移动平均线:一个短期窗口和一个长期窗口。它们的交叉点通常被视为交易信号。
我们首先定义短期和长期窗口的大小。考虑到我们的数据集跨度较长(约7-8年),为了在图表中更清晰地观察趋势,我们将窗口设置得比通常的5日和20日更大一些。
# 定义短期和长期窗口大小
short_window = 42
long_window = 252

接下来,我们使用Pandas的rolling和mean方法计算移动平均线,并将结果作为新列添加到数据框中。
# 计算短期移动平均线
apple_data['short_ma'] = apple_data['AAPL'].rolling(window=short_window).mean()

# 计算长期移动平均线
apple_data['long_ma'] = apple_data['AAPL'].rolling(window=long_window).mean()


计算完成后,数据的前期部分会因为窗口不足而出现缺失值,这是正常现象。我们可以查看数据的尾部来确认计算成功。

print(apple_data.tail())


第四步:可视化移动平均线与交叉点

现在,让我们将原始股价和两条移动平均线绘制在同一张图上,直观地观察它们的走势和交叉情况。


# 绘制股价与移动平均线
apple_data.plot(figsize=(14, 8))
plt.title('Apple Stock Price with Moving Averages')
plt.ylabel('Price')
plt.show()
在生成的图表中,您将看到:
- 原始股价线(通常为蓝色或红色)。
- 短期移动平均线(例如红色)。
- 长期移动平均线(例如绿色)。


关键观察点在于两条移动平均线的交叉:
- 黄金交叉(买入信号):当短期均线从下方上穿长期均线时,通常被视为上涨趋势的开始,是潜在的买入点。
- 死亡交叉(卖出信号):当短期均线从上方下穿长期均线时,通常被视为下跌趋势的开始,是潜在的卖出点。
在图表中仔细寻找这些交叉点,它们是我们策略的基础。
第五步:标记均线位置关系

为了量化这些信号,我们需要创建一个标志位,来明确记录每一天短期均线是高于还是低于长期均线。

首先,我们需要剔除在计算移动平均线时产生的缺失值,以确保数据的一致性。

# 删除因计算移动平均线而产生的缺失值
apple_data = apple_data.dropna()


然后,我们使用np.where函数创建标志列。当短期均线大于长期均线时,标记为1;否则标记为0。

import numpy as np
# 创建位置标志位
apple_data['position'] = np.where(apple_data['short_ma'] > apple_data['long_ma'], 1, 0)
我们可以查看数据的前几行,确认新列已添加。

print(apple_data.head())
我们也可以将这个标志位绘制在图表上(使用次坐标轴),以便更直观地看到策略的“持仓”状态。

# 绘制股价、均线及持仓标志
fig, ax1 = plt.subplots(figsize=(14, 8))

ax1.plot(apple_data['AAPL'], label='AAPL Price', alpha=0.5)
ax1.plot(apple_data['short_ma'], label=f'Short MA ({short_window})')
ax1.plot(apple_data['long_ma'], label=f'Long MA ({long_window})')
ax1.set_ylabel('Price')
ax1.legend(loc='upper left')
ax2 = ax1.twinx()
ax2.plot(apple_data['position'], label='Position (1=Hold)', color='black', linestyle='--', alpha=0.7)
ax2.set_ylabel('Position')
ax2.legend(loc='upper right')
plt.title('Apple Stock with MA Crossover Strategy Signal')
plt.show()


第六步:理解策略逻辑与收益

最后,我们来探讨一下这个简单策略背后的逻辑。
被动持有策略:如果投资者在最早的时间点买入股票并一直持有到最后,其收益完全取决于股价的最终涨跌。
移动平均线交叉策略:我们的策略提供了动态的信号。
- 当出现 “黄金交叉”(
position从0变为1)时,策略发出买入信号。 - 当出现 “死亡交叉”(
position从1变为0)时,策略发出卖出信号。
策略优势的简单比喻:
假设初始资金为100元。
- 被动持有:如果股价先跌后涨,净值可能从100元跌至80元再涨回100元,最终不赚不赔。
- 交叉策略:在死亡交叉点(股价开始下跌前)以100元卖出,在黄金交叉点(股价开始上涨前)以80元买入。这样,我们不仅避免了下跌损失,还用同样的资金买入了更多股份,当股价涨回100元时,我们的资产将超过100元,从而实现盈利。
这个策略的核心思想在于试图通过均线交叉来捕捉趋势的转折点,在上升趋势开始时持有,在下跌趋势开始时离场,以期获得超越简单买入持有的回报。

本节课总结
在本节课中,我们一起学习了:
- 数据准备:如何导入并预处理股票价格数据,处理缺失值。
- 指标计算:如何计算短期和长期简单移动平均线(SMA)。
- 信号识别:通过可视化图表识别移动平均线的“黄金交叉”和“死亡交叉”,它们分别对应潜在的买入和卖出信号。
- 策略标记:如何使用
np.where函数创建交易标志位,量化持仓状态。 - 逻辑理解:理解了移动平均线交叉策略如何试图通过趋势跟踪来优化买卖时机,从而在理论上获得更好的风险调整后收益。

这为我们后续进行更复杂的策略回测和绩效分析打下了坚实的基础。
014:3-策略收益效果分析 📈
在本节课中,我们将学习如何计算和比较交易策略的收益。我们将从计算股价的原始增长率开始,然后应用我们之前构建的“双均线策略”来计算策略收益,最后对比两种方式的最终收益效果。


计算原始增长率
上一节我们介绍了如何获取和处理股价数据,本节中我们来看看如何计算股价的每日增长率。
在量化分析中,我们通常使用对数收益率来计算增长率,因为它具有更好的数学性质(如可加性)。计算对数收益率的公式如下:
公式: log_return = log(price_t / price_{t-1})
其中,price_t 代表当前交易日的股价,price_{t-1} 代表前一个交易日的股价。

以下是使用Python代码计算对数收益率的步骤:

# 假设 df 是包含股价的DataFrame,列名为 ‘Close’
df[‘returns’] = np.log(df[‘Close’] / df[‘Close’].shift(1))
这段代码的作用是:
- 使用
.shift(1)将股价序列向下移动一位,得到前一天的股价。 - 用当天的股价除以前一天的股价。
- 对结果取自然对数
np.log,得到每日的对数收益率。

执行后,我们会在数据框中得到一列名为 returns 的增长率数据。
应用策略计算策略收益


我们已经有了股价的原始增长率,现在需要将我们的交易策略应用进去。回顾一下,我们的“双均线策略”会生成一个交易信号 position,这个信号指示我们应该如何操作。
position 的值代表我们的持仓状态:
- 当
position = 1时,表示我们预测股价会上涨,因此跟随原始走势(做多)。 - 当
position = -1时,表示我们预测股价会下跌,因此反向操作原始走势(例如做空或空仓,在简化模型中我们视为收益取反)。

因此,策略的每日收益可以简单地用信号值乘以原始收益率来计算。
公式: strategy_return = position * return

同样,在计算时,我们需要确保 position 信号与收益率在时间上对齐,因此也需要对 position 序列进行移位操作。
以下是计算策略收益的代码:

# 假设 df 中已有 ‘returns’(原始收益率)和 ‘position’(策略信号)列
df[‘strategy’] = df[‘position’].shift(1) * df[‘returns’]

这段代码将前一天的交易信号 position 应用到当天的收益率上,计算出策略当天的收益。

比较策略与原始收益

计算完每日的策略收益后,我们需要评估整个时间段内策略的总体表现,并与什么都不做的“买入并持有”策略进行对比。

由于我们使用的是对数收益率,要得到最终的总收益,需要将所有每日的收益率相加,然后通过指数函数 exp() 将其还原为实际的资金倍数。

公式:
- 总对数收益率 =
sum(每日收益率) - 最终资金倍数 =
exp(总对数收益率)
以下是计算并比较两种方式最终收益的代码:
# 计算总对数收益率
total_log_return_buy_hold = df[‘returns’].sum()
total_log_return_strategy = df[‘strategy’].sum()

# 还原为最终资金倍数
final_multiple_buy_hold = np.exp(total_log_return_buy_hold)
final_multiple_strategy = np.exp(total_log_return_strategy)

print(f“买入并持有策略:1 元最终变为 {final_multiple_buy_hold:.2f} 元”)
print(f“双均线策略:1 元最终变为 {final_multiple_strategy:.2f} 元”)

通过对比这两个结果,我们可以直观地看到应用交易策略是否带来了超额收益。例如,在示例中:
- 买入并持有:1元变成了约4元。
- 双均线策略:1元变成了约5.8元。

这表明,在该历史数据上,我们的双均线策略比简单的买入并持有策略获得了更高的收益。
总结
本节课中我们一起学习了量化策略收益分析的核心步骤:
- 计算原始增长率:使用对数收益率公式计算股价的每日涨跌。
- 计算策略收益:将交易策略生成的信号(
position)与原始收益率结合,得到策略的每日收益。关键公式为strategy_return = position * return。 - 评估策略效果:通过累加对数收益率并还原为资金倍数,对比策略收益与基准收益(买入并持有),从而量化策略的有效性。

这个过程是回测任何交易策略的基础,帮助你用数据客观地评估一个策略的潜在盈利能力。
015:4-均线调参实例 📈



在本节课中,我们将学习如何通过调整双均线策略的参数来优化交易策略。我们将使用Python的itertools工具遍历不同的参数组合,并评估每种组合的表现,从而找到更优的参数设置。

上一节我们介绍了双均线策略的基本实现,本节中我们来看看如何通过参数调优来提升策略效果。策略的表现与短期均线(SMA1)和长期均线(SMA2)的窗口大小密切相关。我们需要找到最合适的参数组合。

参数空间定义与遍历



为了找到最优的短期和长期均线窗口,我们需要测试一系列参数组合。以下是定义参数空间并使用itertools.product进行遍历的步骤。



首先,导入必要的工具。



from itertools import product



接下来,定义短期均线(SMA1)和长期均线(SMA2)的参数测试范围。

# 定义短期均线参数空间,例如从20天到60天,步长为4
sma1_range = range(20, 61, 4)
# 定义长期均线参数空间,例如从180天到280天,步长为10
sma2_range = range(180, 281, 10)


itertools.product函数可以生成两个参数范围的所有可能组合。


# 生成所有参数组合
param_combinations = product(sma1_range, sma2_range)

以下是遍历过程的示例组合:
- 20天短期均线与180天长期均线
- 20天短期均线与190天长期均线
- 24天短期均线与180天长期均线
- 24天短期均线与190天长期均线

策略评估循环
定义了参数空间后,我们需要在一个循环中对每一组参数执行完整的策略计算,并记录结果。
首先,重新加载并准备数据。

import pandas as pd



# 重新加载苹果股价数据
data = pd.read_csv('你的数据文件路径.csv', index_col=0, parse_dates=True)['Adj Close']
data = data.dropna() # 去除缺失值


然后,开始遍历每一组参数,计算策略收益。


results_list = [] # 用于存储所有结果的列表



for sma1, sma2 in param_combinations:
# 计算收益率
returns = data.pct_change().dropna()
# 计算短期和长期移动平均线
sma1_series = data.rolling(window=sma1).mean().dropna()
sma2_series = data.rolling(window=sma2).mean().dropna()
# 生成交易信号:短期均线上穿长期均线为1,否则为0
position = (sma1_series > sma2_series).astype(int)
# 将信号与未来一期的收益率对齐,计算策略收益
strategy_returns = position.shift(1) * returns
strategy_returns = strategy_returns.dropna()
# 计算策略超额收益(策略收益 - 基准收益)
excess_return = strategy_returns.sum() - returns.loc[strategy_returns.index].sum()
# 将当前参数组合及结果保存到字典中
result_dict = {
'SMA1': sma1,
'SMA2': sma2,
'基准收益': returns.loc[strategy_returns.index].sum(),
'策略收益': strategy_returns.sum(),
'超额收益': excess_return
}
results_list.append(result_dict)


结果分析与展示


计算完成后,我们将所有结果汇总到一个DataFrame中,以便进行分析和比较。

# 将所有结果转换为DataFrame
results_df = pd.DataFrame(results_list)
# 重置索引,使其更整洁
results_df = results_df.reset_index(drop=True)

# 查看结果的前10行
print(results_df.head(10))


输出结果将展示不同参数组合下的基准收益、策略收益和超额收益。通过观察超额收益列,我们可以判断哪些参数组合使策略表现优于简单持有(基准),哪些组合反而更差。





本节课中我们一起学习了如何对双均线策略进行参数调优。我们通过定义参数空间、遍历所有组合并计算策略表现,最终能够量化评估不同参数的效果,从而为选择更优的策略参数提供了数据支持。这是量化策略开发中优化环节的重要一步。
016:1-回测收益率指标解读 📈

在本节课中,我们将要学习如何解读策略回测的核心评估指标。当我们完成一个投资策略后,需要在历史数据中进行测试,以评估其表现。评估不仅仅是判断“赚”或“赔”,而是需要一系列量化指标来衡量收益、风险以及与市场基准的对比优势。本节课将重点介绍最基础的指标——回测收益率,并解释其含义与计算方法。
上一节我们介绍了策略评估的重要性,本节中我们来看看最核心的收益指标。
回测收益率

回测收益率是最基本、最常用的策略评估指标。它衡量了在回测周期内,策略最终获得的收益相对于初始本金的增长比例。例如,一个4%的回测收益率意味着,相对于初始投入的资金,策略最终带来了4%的增值。


其计算公式如下:

公式:
回测收益率 = (期末资产总值 / 期初资产总值) - 1

其中:
期末资产总值代表回测结束时卖出所有资产后的总金额。期初资产总值代表回测开始时投入的本金。


这个指标直观地反映了策略在测试期间的整体盈利能力。


接下来,我们通过代码来演示如何计算并可视化回测收益率的变化过程。
代码演示

首先,我们导入必要的工具包并加载示例数据。
import pandas as pd
import matplotlib.pyplot as plt


# 设置中文字体(示例,根据实际环境调整)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 读取数据,假设数据包含策略每日的资产净值
data = pd.read_csv('your_strategy_data.csv', index_col=0, parse_dates=True)
# 假设我们关注‘net_value’这一列,它代表每日的资产净值
strategy_net_value = data['net_value']
print(strategy_net_value.head())
假设我们的数据 strategy_net_value 是一个时间序列,记录了策略从开始到结束每一天的资产净值。
计算回测收益率的第一步,是计算每日净值相对于期初净值的比值。
以下是计算步骤:


- 计算每日净值与期初净值的比值。
- 将此比值序列可视化,观察资产相对增长过程。
- 根据公式计算最终的回测收益率。


# 1. 计算每日净值相对于期初净值的比值
relative_value = strategy_net_value / strategy_net_value.iloc[0]


# 2. 绘制资产相对增长曲线
plt.figure(figsize=(10, 6))
relative_value.plot()
plt.title('策略资产相对增长曲线')
plt.ylabel('相对净值 (期初=1)')
plt.xlabel('日期')
plt.grid(True)
plt.show()
# 3. 计算最终的回测收益率
final_return = relative_value.iloc[-1] - 1


# 将结果整理成DataFrame展示
result_df = pd.DataFrame({
'回测收益率': [final_return]
}, index=['策略表现'])
print(result_df)


执行以上代码后,我们将得到一条曲线,展示了资产净值随时间相对于初始值(设为1)的变化情况。同时,会输出一个表格,清晰地显示该策略在整个回测周期内的最终回测收益率。

本节课中我们一起学习了策略评估的基础——回测收益率。我们理解了它的定义是衡量策略相对于初始本金的最终盈利比例,并掌握了其计算公式 (期末资产/期初资产) - 1。通过Python代码,我们实践了如何从净值数据中计算并可视化这一指标。回测收益率是评估策略表现的起点,在后续课程中,我们将以此为基础,学习更多衡量风险和比较优势的复杂指标。
017:年化指标分析 📈


在本节课中,我们将要学习如何将回测收益率转换为年化收益率。这是评估投资策略表现时一个非常关键且常用的指标,它能让不同时间长度的策略收益具有可比性。

上一节我们介绍了如何计算策略在整个回测周期内的总收益率(回测收益率)。本节中我们来看看如何将这个总收益标准化,转化为易于理解和比较的年化收益率。


年化收益率的概念

回测收益率反映了策略在整个测试期间的总收益。然而,如果一个策略运行了10年,另一个策略只运行了1年,直接比较它们的总收益是不公平的。年化收益率的作用就是将总收益“压缩”或“折算”到一年的标准时间框架内,以便进行横向比较。


在金融分析中,我们更关注年化收益,因为它是一个标准化的绩效指标。

年化收益率的计算公式

年化收益率的计算基于复利思想。其核心公式如下:
年化收益率 = (1 + 总收益率) ^ (年交易日数 / 策略总交易日数) - 1


用变量表示为:
Annualized_Return = (1 + R) ^ (M / N) - 1

以下是公式中各个参数的含义:
- R:代表策略的总收益率(即上节课计算的回测收益率)。
- N:代表策略运行的总交易日数。例如,在我们的示例数据中,共有730个交易日。
- M:代表一年的标准交易日数。在金融市场中,通常取 250 或 252 天。

计算示例


让我们通过代码来实际计算一下。假设我们已计算出总收益率 R,总交易日数 N=730,年交易日数 M=250。
计算年化收益率的Python代码示例如下:


# 假设已有总收益率 R 和交易日数 N
R = 0.5 # 例如,总收益率为50%
N = 730
M = 250


# 计算年化收益率
annualized_return = (1 + R) ** (M / N) - 1
print(f"年化收益率为: {annualized_return:.2%}")

执行上述计算后,我们会得到每个策略对应的年化收益率。与原始的总收益率对比,你会发现数值发生了变化。例如,一个在730天内获得113%总收益的策略,其年化收益率可能约为20%左右。而一个总收益为负的策略,其年化亏损率也会相应调整。

这个转换使得所有策略的收益表现都被放在了“每年”的同一尺度下,比较起来更加直观和公平。


核心指标总结

本节课中我们一起学习了如何计算和理解年化收益率。在量化策略分析中,有两个关于收益的核心指标至关重要:
- 回测收益率:告诉你策略在整个测试周期内最终赚了或亏了多少。
- 年化收益率:告诉你策略如果按一年来算,平均的盈利能力如何,这是进行策略间比较的黄金标准。


理解并计算这两个指标,是评估任何投资策略有效性的第一步。
018:最大回撤区间 📉

在本节课中,我们将要学习一个重要的风险指标——最大回撤。它描述的是在投资过程中,资产净值从最高点到之后最低点的最大跌幅,直观地反映了投资者可能经历的最糟糕时期。

什么是最大回撤?📊
上一节我们介绍了策略分析的基本概念,本节中我们来看看如何衡量投资风险。最大回撤不能仅从字面理解,它描述的是投资过程中“最惨”的阶段。
具体来说,在炒股过程中,价格必然有涨有跌。最大回撤指的就是从某个高点开始,到后续某个低点为止,资产价值下跌最严重、持续时间最长的那段区间。它衡量的是你资产“缩水”的程度。

这个指标的核心在于评估风险,而非单纯判断策略好坏。一个策略可能最终盈利,但中间如果存在巨大的回撤,意味着投资者需要承受极大的心理压力和资金风险。例如,2008年股市从高点暴跌,就体现了巨大的回撤风险。
最大回撤的计算公式 🧮
理解了概念后,我们来看看如何用数学语言描述它。最大回撤的计算涉及寻找历史数据中跌幅最大的区间。
其核心公式可以表示为:
最大回撤 = max[ (Pi - Pj) / Pi ],其中 i < j
这里:
- Pi 代表时间点 i 的资产价格(或净值)。
- Pj 代表时间点 i 之后某个时间点 j 的资产价格。
- 公式的含义是:遍历所有可能的 i 和 j (i < j),计算从 i 点到 j 点的相对跌幅 (Pi - Pj) / Pi,然后取其中的最大值。



这个公式计算的就是那个“最惨”阶段的资产缩水比例。
使用Python计算最大回撤 💻

理论需要实践来巩固。接下来,我们使用Python的Pandas库来演示如何计算最大回撤。我们将通过一个简单的序列操作来理解这个过程。


首先,我们需要计算到每个时间点为止的历史最高值。这可以通过 cummax() 函数实现。
import pandas as pd

# 假设 df[‘close’] 是每日收盘价序列
historical_max = df[‘close’].cummax()


historical_max 序列的每个值,都代表从起始日到当前日的最高收盘价。

接着,我们计算每日的“潜在回撤”,即当日历史最高价与当日实际收盘价的差值。


drawdown = historical_max - df[‘close’]

然后,我们计算每日的回撤率,即回撤值除以对应的历史最高价。

drawdown_pct = drawdown / historical_max

最后,最大回撤就是这个回撤率序列中的最大值。

max_drawdown = drawdown_pct.max()



上述步骤可以合并为一行的Pandas链式操作:
max_drawdown = (df[‘close’].cummax() - df[‘close’]).div(df[‘close’].cummax()).max()


执行这段代码,我们就能得到该资产在这段时间内的最大回撤值,例如 0.0307 表示最大回撤为 3.07%。
总结 📝
本节课中我们一起学习了最大回撤这个关键的风险评估指标。

我们首先明确了最大回撤描述的是投资过程中资产净值从峰值到谷底的最大跌幅,反映了投资者可能面临的最大阶段性损失。然后,我们学习了其计算公式 max[ (Pi - Pj) / Pi ]。最后,我们使用Python的Pandas库,通过计算历史最高序列 (cummax())、回撤序列和回撤率序列,最终求得最大回撤值。


理解并监控最大回撤,对于管理投资风险、评估策略稳健性至关重要。它提醒我们,不仅要关注最终收益,更要关注通往收益道路上的波折与风险。


019:夏普比率的作用 📈
在本节课中,我们将要学习一个在投资领域中至关重要的风险调整后收益指标——夏普比率。我们将了解它的定义、作用以及如何通过Python代码进行计算。
概述
夏普比率用于衡量一项投资在承担单位风险时,所能获得的超额回报。它帮助投资者判断,为了获取更高的收益,所承担的额外风险是否值得。简单来说,它回答了“这笔买卖划不划算”的问题。
夏普比率的现实意义
上一节我们介绍了风险的概念,本节中我们来看看如何量化风险与收益的关系。

我们可以通过一个现实例子来理解:假设一份工作的日薪高达数万元,但工作地点在战乱地区,风险极高。此时,我们需要权衡高收入与高风险是否匹配。夏普比率正是描述这种“单位风险所对应的收益”的指标。

对于投资者而言,当面对多个投资选择(如多支股票)时,仅看收益率是不够的,因为高收益可能伴随着高风险。夏普比率越高,意味着在承担相同风险的情况下,获得的超额回报越高,因此该投资选项通常更优。

夏普比率的计算原理
理解了夏普比率的作用后,我们来看看它是如何计算的。
计算夏普比率的核心思想是:比较投资组合的收益与无风险收益(如国债、银行存款利率)之间的差额,再除以投资组合收益的波动性(即风险)。
其计算公式如下:
Sharpe Ratio = (Rp - Rf) / σp
其中:
- Rp:投资组合的平均收益率。
- Rf:无风险收益率。
- σp:投资组合收益率的标准差(代表风险)。
这个公式清晰地表明:分子 (Rp - Rf) 代表投资获得的超额收益;分母 σp 代表为获得收益所承担的风险。两者的比值即为单位风险带来的超额收益。
使用Python计算夏普比率
理论部分已经介绍完毕,现在让我们动手,使用Python代码实际计算一组股票的夏普比率。
在计算前,我们需要准备好数据。通常,我们会计算每只股票的历史日收益率,并处理可能存在的缺失值(例如,用前一天的收益率填充)。
以下是计算夏普比率的关键步骤代码:
import numpy as np
import pandas as pd
# 假设 `returns` 是一个Pandas DataFrame,每一列代表一只股票的历史日收益率序列
# 步骤1: 定义年化无风险利率,例如5%
risk_free_rate = 0.05

# 步骤2: 计算年化超额收益率(假设一年有250个交易日)
excess_returns = returns - risk_free_rate / 250
# 步骤3: 计算夏普比率
# 先计算超额收益的均值,再除以其标准差,最后进行年化调整(乘以根号250)
sharpe_ratios = (excess_returns.mean() / excess_returns.std()) * np.sqrt(250)
print(sharpe_ratios)
代码说明:
excess_returns = returns - risk_free_rate / 250:计算每日的超额收益率(投资日收益率减去日化的无风险利率)。excess_returns.mean():计算超额收益率的平均值。excess_returns.std():计算超额收益率的标准差,代表风险。np.sqrt(250):将日度的夏普比率年化(乘以交易天数的平方根)。
运行上述代码后,我们将得到每只股票对应的夏普比率。
结果解读与选择
计算完成后,如何根据结果做出决策呢?

以下是解读夏普比率结果的关键点:
- 数值越大越好:夏普比率越高,说明该资产在承担单位风险时获得的超额回报越高,投资效率越高。
- 比较正负:通常我们关注夏普比率为正的资产。负的夏普比率意味着收益尚不及无风险利率,一般不予考虑。
- 横向对比:在所有夏普比率为正的资产中,选择数值最大的那个。

例如,假设我们计算了A、B、C三只股票的夏普比率分别为0.8、1.5和-0.2。那么,B股票是最优选择,因为它单位风险带来的超额回报最高;A股票次之;C股票则不应考虑。

总结

本节课中我们一起学习了夏普比率这一核心指标。
我们首先通过类比理解了它衡量“风险收益性价比”的本质。
随后,我们学习了其计算公式 Sharpe Ratio = (Rp - Rf) / σp,明确了超额收益与风险的比值关系。
最后,我们使用Python代码演示了如何从历史数据中计算夏普比率,并学会了如何根据计算结果做出更优的投资选择。
掌握夏普比率,能帮助我们在追求收益时,更加理性地评估和承担风险。
020:阿尔法(α)与贝塔(β)概述 📊
在本节课中,我们将要学习量化投资中两个核心概念:阿尔法(α)与贝塔(β)。理解这两个指标对于评估投资策略的优劣至关重要。
核心概念解析
上一节我们介绍了策略评估的基本框架,本节中我们来看看如何区分策略收益的来源。
阿尔法(α)与贝塔(β)是用于分解投资收益来源的两个关键指标。我们可以将投资总收益想象成由两部分组成:一部分来自整体市场环境,另一部分则来自投资者自身的独特策略或能力。
贝塔(β):市场相关性收益
贝塔衡量的是你的投资策略与市场整体波动的关联程度,即“随大流”所获得的收益。它反映了策略对大盘走势的敏感性。
- 定义:贝塔(β)衡量的是系统性风险或市场风险。它表示当市场收益变动1%时,你的策略收益预期会变动多少百分比。
- 核心:这部分收益与你的个人能力无关,主要取决于市场大环境的好坏。市场上涨,这部分收益通常为正;市场下跌,这部分收益可能为负。
- 公式表示:在资本资产定价模型(CAPM)中,市场收益部分可以表示为
市场收益 = β * 市场基准收益率。
阿尔法(α):超额收益
阿尔法衡量的是超越市场平均水平的收益,即通过你的独特策略、选股能力或择时技巧所获得的额外回报。
- 定义:阿尔法(α)衡量的是非系统性风险或特异风险带来的回报。它代表了与市场波动无关的、由管理者技能带来的收益。
- 核心:这是投资者真正追求的目标,因为它体现了策略的附加价值。无论市场涨跌,一个优秀的策略都应努力创造正的阿尔法。
- 公式表示:总收益可以分解为
总收益 = α + β * 市场基准收益率。其中,α就是超额收益。

收益分解图示
为了更好地理解,我们来看一个收益分解图。

图中展示了三种收益:
- 策略收益:你的投资策略实际获得的总收益。
- 基准收益(市场收益):代表市场整体表现的收益,例如沪深300指数的收益率。这就是“贝塔”部分。
- 超额收益:策略收益减去基准收益后的部分。这就是“阿尔法”部分,图中策略收益线高于基准收益线的区域即代表了正阿尔法。


如何计算阿尔法与贝塔
阿尔法和贝塔通常通过统计模型(如线性回归)计算得出。
我们可以将策略收益与市场基准收益的关系拟合成一个线性方程:
策略收益率 = α + β * 市场基准收益率 + ε
其中,ε为误差项。通过历史数据回归分析,即可估算出α和β的值。
在Python中,可以使用statsmodels或scikit-learn等库快速计算:
# 示例:使用statsmodels进行线性回归计算Alpha和Beta
import statsmodels.api as sm
# 假设 strategy_returns 和 benchmark_returns 是收益率序列
X = sm.add_constant(benchmark_returns) # 添加常数项(用于计算Alpha)
model = sm.OLS(strategy_returns, X).fit()
alpha = model.params[0] # 常数项系数即为Alpha
beta = model.params[1] # 市场收益率的系数即为Beta
(注:此为简化示例,实际计算需考虑无风险利率等因素。)
投资者的关注重点
对于投资者而言,市场贝塔收益难以控制,因为没有人能左右整体市场的走向。因此,核心关注点应放在如何获取稳定的、正的阿尔法收益上。构建量化策略的本质,就是设计一套能够持续产生阿尔法的方法。
其他常见评估指标
在策略评估中,除了α和β,还有其他重要指标。以下是几个关键指标简介,初学者了解其含义即可,无需死记公式:
- 夏普比率:衡量每承受一单位总风险,能产生多少超额回报。比率越高,风险调整后收益越好。
- 最大回撤:策略从历史最高点到最低点的最大跌幅。用于衡量策略可能面临的最坏情况。
- 索提诺比率:类似于夏普比率,但只考虑下行风险(坏波动),对追求稳定收益的策略评估更友好。
- 信息比率:衡量超额收益的稳定性,即单位跟踪误差所带来的超额收益。常用于衡量主动管理能力。

总结
本节课中我们一起学习了量化投资的基石概念——阿尔法(α)与贝塔(β)。
- 贝塔(β) 代表了与市场共舞的收益,是系统性风险的体现。
- 阿尔法(α) 代表了超越市场的超额收益,是策略能力和价值的核心体现。
- 投资者的核心目标是构建能持续产生正阿尔法的策略。
- 评估策略时,需结合夏普比率、最大回撤等多个指标综合判断。

理解α和β,能帮助你清晰地分辨收益的来源,从而更客观地评估策略的有效性及基金经理的真实能力。在后续的课程中,我们将深入探讨如何构建和优化以获取阿尔法为目标的量化策略。
021:量化交易概述 🚀


在本节课中,我们将要学习量化交易的基本概念,并了解从事量化交易所需要掌握的核心技能。我们将抛开复杂的历史和前景介绍,直接探讨其本质。

什么是量化交易?
上一节我们介绍了课程目标,本节中我们来看看量化交易到底做了一件什么事。
传统的人工炒股方式,需要交易者长时间紧盯屏幕,关注各种指标变化。这种方式存在两个主要问题:第一,人的主观判断可能导致决策不准确;第二,个人的精力、时间和计算能力有限,无法全面、深入地分析海量的历史数据。
量化交易的核心目标同样是赚钱,但实现方式不同。它并非由人类直接思考如何操作,而是将制定交易策略的任务交给计算机。例如,我们可以让计算机基于历史数据,找出在特定时间段内(如两年)能实现收益最大化的“买入”和“卖出”时机。这种策略就是量化交易的研究对象。
究其本质,量化交易是基于历史数据进行分析,这个过程常被称为回测。其核心任务是:让计算机在历史数据中进行数据挖掘,以发现能够带来盈利的有效策略。
简而言之,量化交易是利用数据和智能算法来最大化投资收益的一种方法。
量化交易需要哪些核心技能?
了解了量化交易的本质后,本节我们来看看实践量化交易需要掌握哪些核心技能。
量化交易是一个交叉学科领域,它确实需要多方面的知识。但值得注意的是,金融专业知识在这里更多是“了解”层面,即理解基本概念和术语即可,并非学习的绝对重点。

真正的重点应放在与计算机和数据处理相关的技能上。以下是几个关键方向:
- 机器学习算法:用于基于数据预测未来的价格走势或市场行为。其核心目标是找到一个函数
f(X) -> Y,其中X是历史特征数据,Y是预测目标(如未来价格)。 - 统计方法:用于计算和分析数据中的各种指标,揭示数据背后的信息和规律。例如,计算收益率、波动率等。
- 编程实践(如Python):这是将想法付诸实现的必备工具。我们需要编程来处理数据、实现策略并进行回测。
在实践中,并没有绝对的“必备工具”。只要某种方法或工具能帮助你更有效地分析数据、优化策略以实现收益最大化,它就是好工具。因此,量化交易在很大程度上可以视为数据挖掘在金融领域的一个热门应用。
总结

本节课中我们一起学习了量化交易的核心概念。我们了解到,量化交易是通过计算机对历史数据进行回测和挖掘,以发现盈利策略的数据驱动方法。其关键技能不在于深奥的金融理论,而在于数据处理、机器学习算法应用和编程实践能力。
022:量化交易所需技能分析 📊

在本节课中,我们将要学习从事量化交易所需要掌握的核心技能。我们将从算法、数据处理、数学基础、工具平台等多个维度进行分析,并明确本课程的重点方向。
核心技能概览
量化交易是一个综合性领域,需要结合多种技能。以下是成功进行量化交易所必需的核心能力。
1. 机器学习算法与特征工程
上一节我们介绍了量化交易的基本概念,本节中我们来看看其技术核心。机器学习算法是量化策略的基础,但更重要的是特征工程。
- 机器学习算法:包括回归、分类、聚类等常规算法。算法的作用是帮助我们逼近模型性能的上限。
- 特征工程:这是数据处理的核心。其目标是从海量数据中提取最有价值的信息。在金融领域,数据维度非常庞大,例如股票数据不仅包含开盘价、收盘价,还涉及公司财务数据、市场宏观数据等。如何将这些多层面、复杂的数据有效融合并输入模型,是特征工程要解决的难题。可以说,数据决定了模型性能的上限,而特征工程决定了我们能否充分利用数据。
2. 数学与统计学基础
量化交易的本质是将数学公式和统计模型应用于金融数据。因此,坚实的数学基础不可或缺。
以下是需要重点掌握的数学知识点:
- 统计学:用于分析数据分布、检验假设、评估策略显著性。
- 概率论:用于建模市场不确定性、计算风险。
- 线性代数:是许多机器学习算法(如主成分分析PCA)的运算基础。
- 微积分:在优化算法、计算梯度时至关重要。
3. 平台与框架的使用
工欲善其事,必先利其器。选择合适的工具平台可以极大提升策略开发和回测的效率。

本课程将选择一款API简单、可视化清晰、便于初学者上手的量化交易平台进行教学。在该平台上,你可以:
- 编写Python策略代码。
- 对历史数据进行回测,模拟策略在特定时间段(如2010年至2020年)的表现。
- 通过图表清晰查看策略每日的执行情况、收益曲线和最终结果。

平台和框架属于工具范畴,核心在于“会用”,无需死记硬背。

4. 交易策略与算法
策略算法是量化交易的灵魂。本课程将聚焦于最常用、最经典的策略算法。
我们将重点讲解以下内容:
- 经典策略的原理:例如均值回归、动量策略等。
- Python实现:如何用代码将策略思想落地。这正是本课程“量化交易与Python”名称的体现——重点在于如何使用Python完成实践,而非教授具体的炒股技巧。

课程重点:股票数据挖掘
有同学可能会问,量化交易可应用于股票、期货等多个市场,本课程的重点是什么?
我们的重点将放在股票相关的数据挖掘上。原因如下:
- 数据丰富性:股票数据(价格、成交量、财务指标等)维度多、结构化程度高,非常适合进行数据挖掘和分析。
- 客观性相对较强:虽然也受市场情绪影响,但股票分析可以更多地依赖公司基本面和历史数据。
- 更适合教学案例:便于用Python构建从数据处理到策略回测的完整案例。

相比之下,期货市场受短期供需、宏观政策等主观判断因素影响更大,对特定行业的专业知识要求更高。因此,本课程仅会以期货为例进行简要说明,不作为重点。
量化交易的本质:数据挖掘
综合以上技能,我们可以将量化交易的本质理解为:将数据挖掘与机器学习算法应用于金融数据,以构建系统性的交易策略,追求收益最大化或风险调整后收益最优。
这个过程通常包含以下步骤:
- 数据处理:获取并清洗金融数据。
- 策略设计:基于数据洞察设计交易逻辑。
- 回测验证:在历史数据上测试策略的有效性。
- 实盘指导:将经过验证的策略用于实际投资决策。

它不仅仅是预测股票涨跌的单一问题,而是一个综合优化问题。例如,如何从300只股票中选出最佳投资组合,在既定风险下追求最高收益,这正是数据挖掘能够发挥作用的领域。


总结与建议
本节课中我们一起学习了量化交易所需的核心技能,并明确了课程方向。

总结如下:
- 核心技能:包括机器学习(重特征工程)、数学统计、平台工具使用和策略算法。
- 课程重点:使用Python进行股票数据挖掘与策略实践。
- 学习建议:对于初学者,无需深究量化交易的复杂历史或理论,关键在于理解其数据挖掘的本质,知道需要用什么工具,以及清楚后续课程将要学习什么。把握好“用Python解决实际问题”这一主线即可。


掌握了这些基础认知后,我们就可以准备好进入具体的实践环节了。
023:Ricequant交易平台简介 🚀
在本节课中,我们将学习如何使用Ricequant量化交易平台。我们将了解平台的基本结构、核心函数的作用以及如何配置回测参数,从而为编写自己的量化策略打下基础。

平台界面与策略模板
首先,我们演示量化平台的使用方法。点击“量化平台”,然后选择“免费使用”。进入后,平台会提供一些已编写好的简单策略示例。这些示例策略可以帮助你熟悉策略的编写方式。
这里有一个默认的入门策略。

点开后,界面类似于Jupyter Notebook,可以在此编写Python代码。但平台对代码结构有特定要求。
以下是平台要求必须使用的三个核心函数:
init函数:用于初始化。before_trading函数:用于每日交易前的预处理。handle_bar函数:用于执行每日(或每分钟)的核心策略逻辑。
所有操作都需要围绕这三个函数来实现。接下来,我们将逐一解释它们的作用。
核心函数详解
上一节我们介绍了平台的三个核心函数,本节中我们来详细看看每个函数的具体含义和工作机制。
初始化函数:init
第一个函数是 init,意为初始化。在Python中,类有一个构造函数 __init__,用于在实例化对象时设置初始参数和进行基本操作。init 函数的作用与此类似。
当执行策略时,init 函数会最先被调用,且在整个回测过程中只执行一次。
这个函数有一个重要参数 context。观察三个函数,你会发现它们都包含 context 参数。context 是一个全局上下文对象,用于在不同函数间传递数据和状态。
例如,在 init 函数中,你可以指定股票代码、获取初始数据,并将其存入 context 对象。
def init(context):
# 在初始化时选定一只股票,并将其代码存入context
context.s1 = '000001.XSHE'
之后,在 before_trading 或 handle_bar 函数中,你可以通过 context.s1 来访问之前存储的股票代码。context 的主要作用就是充当数据传递的桥梁。

预处理函数:before_trading
初始化函数只执行一次,用于设定初始状态。而接下来的两个函数 before_trading 和 handle_bar 会被重复执行多次。
举个例子,在 init 函数中选定了一只股票。那么,在策略运行期间,我们可能需要每天获取这只股票的收盘价,并基于此做出买卖判断。这些每日重复的操作就写在 before_trading 和 handle_bar 函数中。

before_trading 函数在每个交易日开始前、实际交易发生前被执行。你可以在此函数中进行数据预处理工作,例如获取最新的财务数据、计算技术指标等。
策略执行函数:handle_bar
handle_bar 函数是策略逻辑的核心。它会在每个交易日(或每分钟)被调用,用于执行具体的策略判断和下单操作。
def handle_bar(context, bar_dict):
# 在此处编写策略逻辑,例如:
# 判断条件,然后决定买入或卖出 context.s1 这只股票
pass
简单总结一下:
init:用于一次性初始化。before_trading:用于每个交易日开始前的数据预处理。handle_bar:用于执行每个交易日的核心策略与买卖逻辑。
回测参数配置
刚才提到 handle_bar 函数每天都会执行,那么“每天”这个概念是如何体现的呢?这需要通过配置回测参数来实现。
界面右侧的配置区域非常重要,你需要在此设置回测的关键参数,这些参数不是自动生成的结果,而是必须由你手动指定的。
以下是需要配置的主要参数:
- 起止日期:你需要指定回测的时间范围,包括开始日期和结束日期。平台将基于此时间段的历史数据进行策略模拟。
- 初始资金:例如“100000”,代表你用于启动策略的初始资金金额。回测结束后,将基于此初始资金计算收益率等各种指标。
- 频率:可选择“每日”或“每分钟”。这决定了
handle_bar函数的调用频率。在大多数策略中,我们选择“每日”即可。实际应用中可根据策略需求选择。
平台扩展性与数据格式
在代码片段中,你可以自行导入所需的Python库,例如 pandas as pd 或 numpy as np。平台提供的API接口返回的数据格式,如 DataFrame 或 ndarray,与 pandas 和 numpy 的数据结构是兼容的。
这意味着,你不仅可以调用平台的原生API,还可以自由地结合 pandas、numpy 乃至 scikit-learn 等强大的第三方库来进行复杂的数据分析和机器学习建模。平台API与通用Python生态库可以无缝结合使用。
总结

本节课中我们一起学习了Ricequant量化交易平台的基本使用方法。我们了解了三个核心函数 init、before_trading 和 handle_bar 的分工与作用,掌握了如何配置回测的起止日期、初始资金和运行频率。同时,我们也认识到该平台具有良好的扩展性,能够方便地整合主流的Python数据分析库。这些知识是开始编写和回测你自己的量化策略的基础。
024:1-策略任务分析 📊

在本节课中,我们将学习如何使用交易平台构建一个简单的量化交易策略,并熟悉相关的API接口。我们将通过一个具体案例——从沪深300指数中动态选择并持有表现最佳的10只股票——来演示策略开发的完整流程。
策略目标概述 🎯
我们的核心目标是:构建一个策略,使其始终持有沪深300指数中表现最好的10只股票。策略需要定期(例如每日)根据选定的财务指标(如盈利能力)对所有股票进行排序,更新持仓,卖出不再属于前十的股票,并买入新进入前十的股票。
策略开发步骤详解 🔧
上一节我们概述了策略目标,本节中我们来看看实现这个策略的具体步骤和代码模块。
第一步:创建策略文件
在交易平台中,点击“新建策略”。在弹出窗口中,为策略命名,例如 simple_demo。选择股票作为交易品种,点击确定后,平台会生成一个可编辑的代码界面。
第二步:理解代码结构
生成的代码模板包含几个关键函数模块,我们需要在其中填充逻辑:
- 初始化函数 (
__init__): 策略启动时执行一次,用于设置初始参数。 - 盘前处理函数 (
before_trading): 在每个交易日开始前执行,用于数据准备和计算。 - 行情处理函数 (
handle_bar): 在每次收到新的K线数据(如每分钟或每日)时执行,是核心的交易逻辑所在。
第三步:在初始化中设定股票池
在 __init__ 函数中,我们需要获取沪深300指数的所有成分股作为我们的初始股票池。
def __init__(self):
# 获取沪深300指数成分股代码列表
self.stock_pool = get_index_stocks('000300.SH')
第四步:在盘前处理中筛选股票
在 before_trading 函数中,我们需要完成每日的数据查询和排序工作。以下是该步骤的核心任务列表:
- 查询数据:获取股票池中每只股票的选定财务指标(例如,净利润)。
- 进行排序:根据该指标对股票进行降序排序。
- 选取前十:取出排名前十的股票代码。
def before_trading(self):
# 1. 查询财务数据(示例:净利润)
# 假设 `get_fundamental` 是查询财务数据的函数
fundamental_data = get_fundamental(self.stock_pool, ‘net_profit’)
# 2. 按净利润降序排序
sorted_stocks = fundamental_data.sort_values(ascending=False)
# 3. 选取前十名股票代码
self.top_10_stocks = sorted_stocks.index[:10].tolist()
第五步:在行情处理中执行交易
在 handle_bar 函数中,我们将实施具体的交易逻辑。核心是比对当前持仓与我们计算出的最佳前十股票列表。
以下是该步骤的核心任务列表:
- 获取当前持仓:查询账户当前持有的所有股票。
- 比对与调整:
- 对于当前持仓中不在
self.top_10_stocks列表里的股票,执行卖出操作。 - 对于
self.top_10_stocks列表里尚未持有的股票,执行买入操作。
- 对于当前持仓中不在
- 资金管理:在买入时,将可用资金平均分配给要买入的股票。
def handle_bar(self, bar):
# 获取当前持仓的股票代码列表
current_positions = list(self.context.portfolio.positions.keys())
# 卖出不再属于前十的股票
for stock in current_positions:
if stock not in self.top_10_stocks:
# 卖出全部持仓
order_target_value(stock, 0)
# 买入新的前十股票(尚未持有的部分)
stocks_to_buy = [s for s in self.top_10_stocks if s not in current_positions]
if stocks_to_buy:
# 计算可用于购买新股票的资金(平均分配)
available_cash = self.context.portfolio.available_cash
cash_per_stock = available_cash / len(stocks_to_buy)
for stock in stocks_to_buy:
# 买入股票,目标价值为平均分配的资金
order_target_value(stock, cash_per_stock)
总结 📝


本节课中我们一起学习了如何构建一个完整的简单量化交易策略。我们首先明确了策略目标——动态持有沪深300中财务指标最佳的前十只股票。随后,我们分解了实现步骤:在 __init__ 中初始化股票池,在 before_trading 中完成每日的数据筛选和排序,最后在 handle_bar 中执行具体的调仓交易逻辑。这个流程清晰地展示了策略开发中“数据准备-逻辑判断-执行交易”的核心循环,为后续学习更复杂的策略奠定了基础。
025:2-股票池筛选 📊
在本节课中,我们将学习如何构建一个股票池筛选策略。我们将从一个基础的沪深300股票池出发,通过查询财务数据(如营业收入)对股票进行排序和筛选,最终选出排名靠前的股票。整个过程将演示如何在量化框架中实现数据查询、条件过滤和排序。

构造函数与股票池初始化
首先,在策略的构造函数中,我们需要初始化我们的股票池。这里我们选择沪深300指数作为初始的股票池。你可以使用指数名称或代码来指定。
def initialize(context):
# 设定初始股票池为沪深300指数成分股
context.stock_pool = index_components('沪深300')

初始化完成后,我们不再需要打印额外的信息。接下来,我们将进入核心的数据预处理部分。
数据预处理与财务指标查询

上一节我们初始化了股票池,本节中我们来看看如何获取并处理股票的财务数据。这需要使用平台提供的查询功能。
所有可用的查询指标和函数都在官方帮助文档中。量化交易本质上是数据挖掘,涉及大量指标。本次示例我们以查询“营业收入”这个财务指标为例。


以下是查询财务数据的基本函数结构:

get_fundamentals(query, date=None)

该函数需要传入一个 query 参数来指定查询内容,并可以使用 filter 进行条件过滤,这与我们的筛选需求相符。

构建查询语句
以下是构建查询、过滤、排序和限制结果数量的完整步骤。
首先,我们需要定义查询(query)。我们想查询的是基础财务数据中的“营业收入”指标。
# 定义查询,选择营业收入指标
q = query(
fundamentals.financial_indicator.operating_revenue
)
查询定义好后,我们需要指定在哪些股票上执行这个查询。这通过 filter 实现,我们只筛选属于沪深300股票池的股票。
# 过滤条件:股票代码在初始化的股票池中
q = q.filter(
fundamentals.financial_indicator.code.in_(context.stock_pool)
)
接下来,我们对结果进行排序。我们希望选出营业收入最高的股票,因此按营业收入降序排列。
# 按营业收入降序排列
q = q.order_by(
fundamentals.financial_indicator.operating_revenue.desc()
)
排序后,结果可能仍然很多。我们通常只关注排名最靠前的几只股票,因此使用 limit 来限制返回的数量。
# 限制只返回前10条结果
q = q.limit(10)

最后,我们执行查询,并将返回的 DataFrame 结果保存在上下文(context)中,以便后续使用。

# 执行查询并保存结果
df = get_fundamentals(q)
context.selected_stocks = df

为了验证查询是否成功,我们可以打印出结果。由于 before_trading 函数在每个交易日都会运行,因此会每日打印筛选结果。

def before_trading(context):
# 执行上述查询并打印结果
df = get_fundamentals(q)
print(df)
context.selected_stocks = df
调试与修正

运行回测后,如果发现打印的结果为空,需要检查问题。一个常见原因是股票池的代码不正确。我们最初可能错误地使用了股票列表,而不是指数成分股函数。

正确的初始化方式应使用 index_components 函数来获取指数成分股:

# 修正后的股票池初始化
context.stock_pool = index_components('沪深300')

修正后重新运行回测,即可在日志中看到每日筛选出的股票列表。输出结果是一个 DataFrame,横向是股票名称,纵向是具体的财务数据。

总结

本节课中我们一起学习了量化策略中股票池筛选的基本流程。我们从初始化一个基于沪深300的股票池开始,然后利用 get_fundamentals 函数查询财务指标,并通过 filter、order_by 和 limit 完成了股票的过滤、排序和精选。这个过程是构建更复杂选股策略的基础。记住,所有可用的数据字段和函数都应参考官方文档,这是进行有效数据挖掘的关键。
026:3-策略效果演示与指标分析

在本节课中,我们将学习如何将筛选出的股票列表进行格式转换,并基于此实现一个完整的交易策略。我们将编写策略的核心交易逻辑,包括买入和卖出操作,并最终运行回测,分析策略的各项关键指标。

数据格式转换

上一节我们介绍了如何筛选出目标股票。本节中我们来看看如何将筛选结果转换为更易处理的格式。

我们最初得到的股票数据是横向排列的,即每一列是一个股票,每一行是一个财务指标。这种格式不便于我们按股票进行后续操作。我们通常更喜欢纵向排列,即每一行代表一只股票,每一列代表其各项指标。
因此,我们需要对数据框(DataFrame)进行转置操作。以下是实现转置的代码:

# 假设 df 是包含筛选结果的原始 DataFrame
df_transposed = df.T



转置后,数据将变为纵向排列。此时,每一行的索引(index)就是股票代码,这正是我们后续步骤中需要获取的信息。

存储筛选结果


转置完成后,我们需要将筛选出的股票列表存储起来,以便在交易函数中使用。我们将股票代码列表保存在 context 对象中。


# 获取转置后 DataFrame 的索引,即股票代码列表
selected_stocks = df_transposed.index.tolist()
# 将股票列表存入 context
context.selected_stocks = selected_stocks


至此,我们在交易开始前(before_trading)的准备工作已经完成。


实现交易逻辑

准备工作完成后,我们进入核心的交易函数(handle_data)。本节中,我们将根据持仓情况和当前筛选出的股票池,执行买入和卖出操作。
首先,我们需要判断当前账户的持仓情况。我们可以通过 context.portfolio.positions 来获取一个包含所有持仓信息的字典。
以下是交易逻辑的实现步骤:


- 判断持仓状态:检查当前是否有持仓。
- 处理卖出:如果当前有持仓,则遍历持仓中的每只股票。如果某只股票不在当前筛选出的股票池中,则将其全部卖出。
- 处理买入:遍历当前筛选出的股票池。对于池中的每只股票,执行买入操作。为了简化,我们采用等权重买入策略,即每只股票分配相同的资金比例。

以下是具体的代码实现:

def handle_data(context):
# 1. 获取当前持仓
current_positions = context.portfolio.positions
# 2. 卖出不在当前股票池中的持仓
if current_positions:
for stock in current_positions:
if stock not in context.selected_stocks:
# 卖出该股票的全部持仓
order_target_percent(stock, 0)
# 3. 等权重买入当前股票池中的股票
for stock in context.selected_stocks:
# 计算每只股票应占的资金比例
weight = 1.0 / len(context.selected_stocks)
# 买入股票,调整至目标权重
order_target_percent(stock, weight)
策略回测与指标分析
代码编写完成后,我们可以运行回测来评估策略的表现。回测结果会提供一系列关键指标,帮助我们理解策略的盈利能力和风险水平。
以下是几个需要关注的核心指标:

- 累计收益率:策略在整个回测期间的总收益。可以与基准(如沪深300指数)的收益率进行对比。
- 年化收益率:将累计收益率折算为每年的平均收益率,便于不同周期策略的比较。
- 最大回撤:策略净值从最高点到最低点的最大跌幅,用于衡量策略可能面临的最大亏损风险。
- 夏普比率:衡量策略承担单位风险所获得的超额回报。该值为正且越大越好,若为负则说明风险调整后的收益为负。

分析回测结果时,应综合看待这些指标。例如,一个策略可能累计收益很高,但最大回撤也很大,说明其波动性高,风险较大。夏普比率则能更综合地反映收益与风险的平衡情况。
总结

本节课中我们一起学习了量化交易策略从数据处理到执行回测的完整流程。我们首先将横向的股票数据转置为纵向格式以方便处理,并将筛选出的股票列表存储在上下文对象中。接着,我们实现了策略的核心交易逻辑:卖出不再符合筛选条件的持仓,并以等权重方式买入新筛选出的股票。最后,我们运行回测并学习了如何解读累计收益率、年化收益率、最大回撤和夏普比率等关键绩效指标,从而全面评估一个策略的有效性。
027:定时器功能与作用 ⏰
在本节课中,我们将学习如何使用定时器功能来优化交易策略的执行频率。我们将看到,通过自定义执行时间点,可以改变策略的调仓节奏,从而可能影响最终的交易结果。
回顾每日执行策略
上一节我们介绍了基于每日数据筛选股票并进行交易的基本策略。在那个策略中,before_trading 函数和选股逻辑在每一个交易日都会执行。
以下是每日执行策略的核心流程:
- 每个交易日开盘前,执行
before_trading函数。 - 在
before_trading中,通过query函数筛选出排名靠前的股票。 - 根据筛选结果进行买入或卖出操作,以维持固定的持仓股票数量。
这种每日调仓的方式交易频率较高。接下来,我们将探讨如何通过定时器来调整这个频率。
引入定时器功能
本节中我们来看看如何通过定时器(Scheduler)来控制策略中特定函数的执行时间。定时器允许我们按照自定义的时间间隔(如每周、每月)来执行函数,而不是默认的每个交易日。

在平台的API中,定时器在策略的初始化函数 __init__ 中设置。其核心作用是:在指定的时间点,触发指定的函数。

例如,我们可以将每日选股改为每月初选股。首先,我们需要将原来写在 before_trading 中的选股逻辑移到一个独立的函数中,比如叫做 filter_data。
def filter_data(context):
# 这里是原来的选股逻辑,例如:
# 1. 查询股票数据
# 2. 按某些指标排序
# 3. 选取前N只股票
# 4. 调整持仓至这些股票
pass
然后,在 __init__ 函数中使用定时器API来调度这个函数。
def __init__(context):
# 每月第一个交易日执行 filter_data 函数
scheduler.run_monthly(filter_data, tradingday=1)
通过以上设置,filter_data 函数只会在每月的第一个交易日被执行,从而实现了每月调仓一次的策略逻辑。
定时器使用示例与结果分析
让我们将理论付诸实践,修改之前的策略,从每日调仓改为每月调仓,并观察其影响。
首先,我们注释掉 before_trading 函数中的每日选股代码。然后,如上一小节所述,创建一个独立的 filter_data 函数,并将选股逻辑复制进去。最后,在 __init__ 中通过 scheduler.run_monthly(filter_data, tradingday=1) 进行调度。
使用相同的时间段(2016-01-04 至 2016-10-04)回测,我们发现策略的收益结果发生了变化。之前每日调仓的策略最终亏损约4%,而改为每月调仓后,亏损可能变得更多(例如-28%)。这说明改变策略的执行频率会显著影响其表现,但效果的好坏需要具体分析。
为了进一步探索,我们可以尝试更换回测的时间段。例如,测试2018年至2020年的数据,结果可能依然不理想。再尝试2015年至2016年的数据,可能会发现策略在某个阶段表现优异,但后期回撤较大。
这个实验过程说明了以下几点:
- 策略性能具有时段依赖性:同一个策略在不同市场时期表现可能天差地别。
- 参数与频率需要测试:调仓频率(每日、每月)是一个重要的策略参数,需要通过回测寻找较优设置。
- 平台工具的使用:核心是学会使用
scheduler.run_monthly、run_weekly等API来控制函数执行。
核心要点与学习方法总结
本节课中我们一起学习了量化交易平台中定时器功能的核心概念与应用。
我们了解到,定时器是控制策略逻辑执行节奏的关键工具。通过将 scheduler.run_xxx 函数置于策略的 __init__ 方法中,我们可以轻松实现按日、周、月等周期执行特定的选股或调仓函数。
以下是快速上手的核心步骤:
- 封装逻辑:将需要周期性执行的代码封装成独立的函数。
- 设置调度:在
__init__函数中,使用scheduler.run_monthly(你的函数, tradingday=1)等语句进行调度。 - 回测验证:修改参数或时间段,反复回测,观察策略表现的变化。

最后,最重要的是掌握学习方法:勤查官方API文档。所有函数(如 query, order, scheduler)的详细用法和参数都在文档中有明确说明。通过参考文档并结合实际代码实验,是快速掌握这个平台乃至任何编程工具的最佳途径。
028:1-百分位去极值方法
在本节课中,我们将学习如何对量化投资中的因子数据进行预处理。我们将重点介绍三种核心的预处理方法:去极值、标准化和中性化。理解并正确应用这些方法,是构建有效多因子模型的基础。
什么是因子?
在开始之前,我们需要明确“因子”的概念。当您决定购买股票时,通常会依据某些标准进行筛选,例如选择市净率低的股票,或者选择营收增长率高的公司。这些用于决策的标准或指标,就是我们所说的“因子”。因子可以理解为对最终投资结果有影响的各类指标。
我们的目标通常是建立一个模型,例如 Y = f(X1, X2, X3, ...),其中 Y 代表收益,X1, X2, X3... 代表不同的因子。通过数据挖掘,我们可以分析每个因子对结果的影响,并从中选择有效的因子来构建投资策略。本节课将首先聚焦于因子数据的预处理步骤。
预处理三步走策略

在直接使用因子数据进行建模前,通常需要进行预处理。我们将遵循一个“三步走”策略:
- 去极值:处理数据中的离群点或异常值。
- 标准化:将不同量纲和范围的因子数据转换到统一的尺度上。
- 中性化:消除因子数据中与某些特定风险(如行业、市值)相关的系统性偏差。
前两步在机器学习和数据挖掘中很常见,而第三步“中性化”在量化因子策略中尤为重要。接下来,我们将按照这个顺序,首先详细讲解如何去极值。

去极值方法:百分位法

拿到一份因子数据后,第一步往往是处理其中的极值。极值是指那些远离数据主体分布的点。直接删除这些数据点可能造成信息损失,因此更常见的做法是“拉回”这些极值,使其不超过我们设定的合理边界。


有多种方法可以设定这个边界,本节我们介绍第一种:百分位法。

核心概念:分位数


在介绍百分位法之前,需要理解分位数的概念。大家最熟悉的分位数是中位数,它将数据一分为二。与均值相比,中位数对极值不敏感,能更好地代表数据的“中心”位置。
除了中位数,常用的还有四分位数:
- 第一四分位数 (Q1):所有数据由小到大排列后,处于25%位置的值。
- 中位数 (Q2):处于50%位置的值。
- 第三四分位数 (Q3):处于75%位置的值。

百分位法原理
百分位法的思路很简单:我们设定一个下限和一个上限,所有低于下限的值被提升至下限,所有高于上限的值被降低至上限。
如何确定这个上下限呢?我们使用分位数。例如,我们可以将下限设定为 Q1 - k * IQR,将上限设定为 Q3 + k * IQR。其中:
IQR是四分位距,计算公式为IQR = Q3 - Q1。k是一个常数,通常取 1.5。k值越大,边界越宽松,被处理的极值点越少;k值越小,边界越严格。
以下是确定边界和处理极值的代码逻辑:

# 假设 factor_data 是一个包含因子值的Pandas Series或DataFrame列
Q1 = factor_data.quantile(0.25) # 计算第一四分位数
Q3 = factor_data.quantile(0.75) # 计算第三四分位数
IQR = Q3 - Q1 # 计算四分位距
# 设定上下限,这里 k=1.5
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 处理极值:将小于下限的值设为下限,大于上限的值设为上限
factor_data_clipped = factor_data.clip(lower_bound, upper_bound)
通过以上操作,我们就完成了基于百分位的去极值处理。这种方法简单直观,是实践中常用的方法之一。
本节总结

本节课中,我们一起学习了量化因子预处理的第一步:去极值。我们首先明确了“因子”在投资决策中的含义,然后介绍了数据预处理的“三步走”策略。接着,我们深入讲解了第一种去极值方法——百分位法,包括其核心概念(分位数、IQR)和具体的实现步骤。下一节,我们将继续学习第二步:标准化。
029:基于百分位去极值实例 📊

在本节课中,我们将学习如何使用分位数(例如Q1和Q3)来识别和处理数据集中的极端值。我们将通过一个具体的例子,编写一个函数,将超出指定百分位范围的数据“截断”到边界值,从而实现数据的规范化处理。

上一节我们介绍了数据清洗的基本概念,本节中我们来看看如何具体实现基于百分位的去极值操作。

操作目标与思路 🎯

我们的目标是:给定一个数据集,计算其第一四分位数(Q1,25%)和第三四分位数(Q3,75%)。将所有小于Q1的值设置为Q1,将所有大于Q3的值设置为Q3。这样,数据就被规范到了一个由Q1和Q3确定的范围内。

以下是实现此目标的核心步骤:
- 选择需要处理的特定数据列。
- 编写一个函数,该函数能够接收一列数据以及指定的下限和上限百分位。
- 在函数内部,计算对应的百分位数值。
- 使用这些计算出的边界值来“截断”原始数据。
- 最后,通过可视化对比处理前后的数据分布。
代码实现步骤 💻

接下来,我们将分步实现上述思路。

第一步:选择数据列

首先,我们需要指定要对数据框中的哪一列进行操作。这只是一个示例,因此我们可以任意选择一列。
column_name = ‘your_column_name‘ # 替换为实际列名
data_series = data[column_name]
第二步:编写去极值函数
我们将编写一个名为 clip_by_percentile 的函数。这个函数的核心逻辑是接收一个数据序列(Series)以及最小和最大百分位参数,然后返回处理后的序列。
以下是该函数的详细代码:
import pandas as pd
import numpy as np
def clip_by_percentile(series, low_percentile=0.25, high_percentile=0.75):
"""
基于百分位截断极值的函数。
参数:
series (pd.Series): 待处理的数据序列。
low_percentile (float): 下限百分位,例如0.25代表Q1。
high_percentile (float): 上限百分位,例如0.75代表Q3。
返回:
pd.Series: 处理后的数据序列。
"""
# 1. 计算指定百分位对应的值
# 使用 np.percentile 或 series.quantile 计算
low_value = np.percentile(series, low_percentile * 100)
high_value = np.percentile(series, high_percentile * 100)
# 2. 使用 np.clip 函数进行截断
# np.clip(数组, 最小值, 最大值) 会将数组中小于最小值的元素替换为最小值,大于最大值的替换为最大值。
clipped_series = np.clip(series, low_value, high_value)
# 将结果转换回Pandas Series,保持索引一致
return pd.Series(clipped_series, index=series.index)
函数逻辑解析:
- 计算边界值:
np.percentile(series, 25)计算序列中25%位置的值(即Q1),np.percentile(series, 75)计算Q3。 - 截断操作:
np.clip()函数是完成核心操作的关键。它接受三个参数:原始数据、最小允许值和最大允许值。函数会遍历原始数据,将所有低于最小值的数提升到最小值,将所有高于最大值的数降低到最大值,处于中间的值则保持不变。

第三步:应用函数并查看结果
编写好函数后,我们可以将其应用到选定的数据列上。
# 应用去极值函数,使用默认的25%和75%分位
clipped_data = clip_by_percentile(data_series)


# 查看处理前后的基本统计信息对比
print(“原始数据描述:”)
print(data_series.describe())
print(“\n处理后的数据描述:”)
print(clipped_data.describe())

第四步:结果可视化
为了直观地感受去极值的效果,我们可以将处理前后的数据分布绘制出来进行对比。

import matplotlib.pyplot as plt

# 创建画布和子图
fig, axes = plt.subplots(2, 1, figsize=(10, 8))

# 绘制原始数据
axes[0].plot(data_series.values, ‘o‘, alpha=0.5, label=‘Original Data‘)
axes[0].axhline(y=np.percentile(data_series, 25), color=‘r‘, linestyle=‘--‘, label=‘Q1 (25%)‘)
axes[0].axhline(y=np.percentile(data_series, 75), color=‘g‘, linestyle=‘--‘, label=‘Q3 (75%)‘)
axes[0].set_title(‘原始数据分布‘)
axes[0].legend()
axes[0].grid(True)
# 绘制处理后的数据
axes[1].plot(clipped_data.values, ‘o‘, alpha=0.5, color=‘orange‘, label=‘Clipped Data‘)
axes[1].axhline(y=np.percentile(data_series, 25), color=‘r‘, linestyle=‘--‘, label=‘Q1 (25%)‘)
axes[1].axhline(y=np.percentile(data_series, 75), color=‘g‘, linestyle=‘--‘, label=‘Q3 (75%)‘)
axes[1].set_title(‘去极值后数据分布‘)
axes[1].legend()
axes[1].grid(True)

plt.tight_layout()
plt.show()

在生成的图表中,您将看到:
- 第一张图显示原始数据点,并用虚线标出了Q1和Q3的位置。
- 第二张图显示处理后的数据。可以观察到,所有原来低于红色虚线(Q1)的点现在都“抬升”到了红线上;所有原来高于绿色虚线(Q3)的点都“降低”到了绿线上。中间的数据点则保持不变。

实际应用注意事项 ⚠️

在理论示例中,我们使用了25%和75%作为边界,这会将相当一部分数据视为“极值”并进行调整,主要是为了演示效果更明显。


在实际数据分析或因子处理中,通常会使用更严格的边界,例如:
- 下限:
low_percentile=0.025(2.5%) - 上限:
high_percentile=0.975(97.5%)

这样只处理分布两端各2.5%的极端数据,对主体数据的影响更小,也更符合多数场景下去除异常值的需求。您可以通过向 clip_by_percentile 函数传递不同的参数来轻松调整这个范围。

总结 📝


本节课中我们一起学习了基于百分位进行去极值处理的完整流程:
- 明确目标:将数据限制在由指定分位数(如Q1和Q3)确定的范围内。
- 核心工具:利用
np.percentile计算边界值,利用np.clip函数高效地进行数据截断。 - 代码实现:我们封装了一个可复用的函数
clip_by_percentile,并通过可视化对比验证了其效果。 - 灵活调整:该方法允许通过修改百分位参数来适应不同的业务场景和严格程度。

这种方法简单有效,是数据预处理中处理异常值的常用技术之一。
030:MAD法去极值演示 🧮


在本节课中,我们将要学习第二种数据去极值方法——MAD法。这种方法通过计算数据的中位数绝对偏差来设定合理的上下界,从而识别并处理异常值。
上一节我们介绍了百分位法去极值,本节中我们来看看另一种基于中位数的方法。

MAD法原理 📊
MAD法的核心思想是使用数据的中位数和“中位数绝对偏差”来确定数据的正常范围。其计算过程分为两步。
首先,计算原始数据序列的中位数。接着,计算序列中每个数据点与该中位数差值的绝对值,从而得到一个新的序列。最后,计算这个新序列的中位数,这个值就是“中位数绝对偏差”(MAD)。


以下是MAD值的计算公式:
MAD = median( |Xi - median(X)| )
其中,Xi代表序列中的每一个数据点,median(X)代表原始序列的中位数。

有了MAD值后,我们就可以设定数据的正常范围边界。通常,上界和下界由以下公式确定:
上界 = median(X) + N * MAD
下界 = median(X) - N * MAD
公式中的N是一个常数,通常取值为1.486。任何落在这个范围之外的数据点,都可以被视为极值并进行处理。

代码实现 💻

理解了原理后,我们来看看如何用代码实现MAD法去极值。我们将按照上述步骤,逐步计算中位数、MAD值以及最终的上下界。


以下是实现MAD去极值功能的Python函数:


import pandas as pd
import numpy as np

def filter_extreme_MAD(series, n):
"""
使用MAD法过滤极值。
:param series: 输入的数据序列(pandas Series)
:param n: 用于计算边界的倍数,通常为1.486
:return: 处理后的数据序列
"""
# 1. 计算原始序列的中位数
median = series.quantile(0.5)
# 2. 计算每个数据点与中位数差值的绝对值,然后求该绝对值序列的中位数(即MAD)
mad = (series - median).abs().quantile(0.5)
# 3. 计算上界和下界
upper_bound = median + n * mad
lower_bound = median - n * mad
# 4. 将超出边界的数据替换为边界值
filtered_series = series.clip(lower=lower_bound, upper=upper_bound)
return filtered_series
现在,我们可以使用这个函数来处理数据。假设我们有一个名为data的数据序列,并取N=1.486。
# 假设 data 是一个 pandas Series
filtered_data = filter_extreme_MAD(data, n=1.486)

# 可视化对比原始数据与处理后的数据
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
plt.plot(data.index, data.values, 'o', label='原始数据', alpha=0.6)
plt.plot(filtered_data.index, filtered_data.values, 'x', label='MAD处理后的数据', alpha=0.8)
plt.axhline(y=upper_bound, color='r', linestyle='--', label='上界')
plt.axhline(y=lower_bound, color='g', linestyle='--', label='下界')
plt.legend()
plt.title('MAD法去极值效果对比')
plt.show()


执行代码后,蓝色点代表原始数据,处理后的数据以及红色的上界和绿色的下界会清晰地展示在图中。超出边界的数据点已被修正到边界值。
总结 ✨


本节课中我们一起学习了MAD法去极值。我们首先了解了该方法的原理,即通过中位数和MAD值来定义数据的合理范围。然后,我们一步步实现了对应的代码,并进行了可视化演示。


与百分位法相比,MAD法对极端值不敏感,因为它基于中位数而非均值,这使得它在处理包含异常值的数据时更加稳健。掌握这种方法,能为数据清洗和预处理提供又一个有效的工具。
031:3-Sigma方法实例 📊
在本节课中,我们将要学习一种基于统计学原理的数据去极值方法——3-Sigma方法。我们将理解其背后的正态分布思想,并通过代码实现来掌握如何应用此方法识别和处理数据中的异常值。
上一节我们介绍了其他去极值方法,本节中我们来看看基于正态分布假设的3-Sigma方法。
方法原理 📈
3-Sigma方法的原理基于正态分布(也称高斯分布)。正态分布是一种常见的概率分布,其图形呈对称的钟形曲线。
为了便于理解,可以想象一个简单的例子:你去银行贷款,银行批准给你的额度围绕一个平均值上下波动。如果额度比平均值多或少几块钱,这很常见;但如果多或少了几万块,这就显得“不正常”了。这些“不正常”的、偏离平均值很远的数值,就是我们要处理的极值或离群点。
在正态分布中,数据点距离均值(平均值)越远,其出现的概率就越低。下图展示了一个标准正态分布(均值为0,标准差为1)。图中阴影部分面积代表数据落在该区间的概率。


可以看到,靠近均值(红色区域)的数据点非常多,发生的概率很高。而越向两侧(蓝色、绿色区域)延伸,数据点出现的概率就越低。极值通常就落在这些远离均值的低概率区域。
3-Sigma方法的核心思想是:利用均值和标准差,计算出一个数据区间,认为落在这个区间内的数据是“正常”的,区间外的则是需要处理的“异常”值。
具体来说,我们计算数据的均值(μ)和标准差(σ)。然后,我们设定一个倍数N(通常N=3),并定义:
- 上界 = μ + N * σ
- 下界 = μ - N * σ
在标准正态分布中,当N=3时,数据落在 [μ-3σ, μ+3σ] 区间内的概率约为99.7%。这意味着,如果我们假设数据服从正态分布,那么大约99.7%的数据是正常的,只有约0.3%的数据可能被视为异常值并进行处理(如缩放到边界值)。

代码实现 💻

理解了原理后,我们来看看如何用代码实现3-Sigma方法。我们将编写一个函数,输入一列数据和倍数N,输出处理后的数据序列以及计算出的上下界。
以下是实现步骤:

首先,我们定义函数,并计算输入数据的均值和标准差。
def sigma_method(data, n=3):
# 计算均值和标准差
mean = data.mean()
std = data.std()



接着,利用均值和标准差计算上界和下界。
# 计算上界和下界
upper_bound = mean + n * std
lower_bound = mean - n * std



然后,将原始数据中超出边界的值替换为边界值,实现“去极值”而非“删除极值”。

# 将超出边界的数据规范到边界上
data_clipped = data.clip(lower=lower_bound, upper=upper_bound)
# 返回处理后的数据及边界
return data_clipped, lower_bound, upper_bound



现在,我们可以使用这个函数来处理数据。例如,我们有一列数据 data_series,我们设置N=1(为了在图表中更明显地展示效果),看看处理结果。

# 应用函数,N=1时对应约68%的数据区间
processed_data, low, high = sigma_method(data_series, n=1)
print(f"下界: {low:.2f}, 上界: {high:.2f}")



执行代码并绘制图表,可以直观地看到处理效果。图中绿色线代表原始数据,蓝色线代表处理后的数据。所有超出蓝色边界(即[μ-1σ, μ+1σ])的原始数据点都被拉回到了边界上。





不同的去极值方法(如百分位法、MAD法、3-Sigma法)会计算出不同的边界,因此处理后的数据也会略有差异。例如,某种方法的下界可能在60左右,而3-Sigma法(N=1时)的下界可能在50左右。


选择哪种方法取决于实际任务的需求和数据本身的特性,在实际应用中可以进行对比实验。

核心要点总结 ✨

本节课中我们一起学习了3-Sigma去极值方法。

- 原理基础:该方法假设数据服从或近似服从正态分布,利用均值(μ)和标准差(σ)来定义正常数据的范围。
- 核心公式:正常值区间定义为 [μ - Nσ, μ + Nσ],其中N常取3(对应99.7%的数据)。区间外的值被视为异常。
- 操作实质:“去极值”不是删除数据,而是通过设定上界和下界,将所有数据规范到边界之内,从而削弱极端值的影响。
- 方法对比:这是数据预处理“三步走”(缺失值处理、去极值、标准化)中,去极值环节的一种有效策略,与百分位法、MAD法各有适用场景。

通过本教程,你应该能够理解3-Sigma方法的思想,并能够使用代码实现它来处理数据中的异常值。
032:标准化处理方法 📊

在本节课中,我们将要学习数据预处理中一个非常重要的步骤——标准化。标准化能够调整数据,使其在不同维度上具有可比性,从而避免因数值范围差异过大而对后续分析或模型训练造成不良影响。

标准化的目的与原理
上一节我们介绍了数据的基本处理,本节中我们来看看如何通过标准化来统一数据的尺度。
观察下图,假设我们有一个包含两个指标(X1和X2)的数据集。X1的取值范围较大,而X2的取值范围较小。如果直接使用这些原始数据,数值范围大的指标可能会在计算中占据主导地位,从而影响结果的公平性。

标准化的核心目的,就是为了让不同指标(或维度)的数值取值范围尽可能相同,以便我们用统一的尺度去观察和分析它们。
标准化的公式如下:
公式:z = (x - μ) / σ
其中:
x是原始数据。μ是该维度数据的均值。σ是该维度数据的标准差。
这个公式可以分解为两个步骤来理解:
-
去均值:
x - μ- 目的:将数据集的中心移动到零点。经过这一步,数据在各个维度上将以零为中心对称分布。
- 如下图所示,去均值后,数据不再以原始位置为中心,而是以坐标原点为中心。
![]()
-
除标准差:
(x - μ) / σ- 目的:调整数据的取值范围,使其尺度一致。
- 原理:标准差(σ)反映了数据的离散程度。取值范围大、个体差异大的数据组,其标准差也大;反之则小。
- 例如,一组工资数据如果都是1-2万元,其标准差较小。
- 另一组工资数据如果从1万元到数千亿元,其标准差会非常大。
- 用数据减去均值后,再除以各自的标准差,原本取值范围大的数据会被一个较大的数压缩,取值范围小的数据则被一个较小的数调整。最终,所有维度的数据都会被缩放至一个相近的数值范围内。
经过完整的标准化处理后,数据会呈现出以下特征:

- 以零为中心对称。
- 各维度的数值范围基本一致。
标准化的代码实现
理解了原理后,我们来看看如何用代码实现标准化操作。代码实现非常简单直接。
以下是手动实现标准化处理的函数:

def standardize(series):
"""
对输入的序列进行标准化处理。
参数:
series: 待处理的数据序列(例如 pandas Series)。
返回:
标准化后的数据序列。
"""
# 计算序列的均值和标准差
mean_val = series.mean()
std_val = series.std()
# 应用标准化公式: (x - μ) / σ
standardized_series = (series - mean_val) / std_val
return standardized_series

代码解析:
- 首先计算输入数据序列的均值(
mean_val)和标准差(std_val)。 - 然后,将序列中的每个值减去均值,再除以标准差。
- 返回处理后的新序列。
使用工具包进行标准化

除了手动实现,我们也可以利用成熟的机器学习库来快速完成标准化,这通常只需一行代码。

例如,在Python著名的机器学习库scikit-learn中,提供了专门的数据预处理模块。

使用方法:
- 访问scikit-learn官网。
- 在API文档中搜索
StandardScaler。 - 调用其
fit_transform方法即可一键完成标准化。
使用工具包的优势是高效、稳定,并且与机器学习流程的其他环节集成度更高。


标准化效果演示

让我们通过一个简单的例子来直观感受标准化的效果。
假设我们有以下原始数据:
原始数据: [30, 25, 100, 45, 60]
可以看到,数据点之间的取值范围差异较大(从25到100)。

现在,我们应用上面编写的standardize函数对这个数据进行处理:

标准化后数据: [-0.70, -0.87, 1.73, -0.36, 0.20]

效果对比:
- 原始数据:数值分散,尺度不统一。
- 标准化后数据:所有数值被转换到以0为中心、尺度相近的范围内(大部分在-1到2之间)。这消除了原始量纲和数值大小的影响。




总结

本节课中我们一起学习了数据标准化处理。

- 核心目的:消除不同特征(维度)之间因量纲和取值范围不同带来的影响,使数据具有可比性。
- 核心公式:
z = (x - μ) / σ。 - 处理过程:分为两步——去均值使数据以零为中心对称;除标准差使各维度数据尺度一致。
- 实现方式:可以手动根据公式计算,也可以使用
scikit-learn等库的StandardScaler工具快速实现。 - 应用场景:在数据挖掘和机器学习中,标准化是一项极为常见且重要的预处理步骤,通常拿到数据后即可考虑使用。

标准化后的数据更加“规范”,能为后续的数据分析、模型训练打下良好的基础。
033:中性化处理方法通俗解释
在本节课中,我们将要学习量化交易中的一个重要概念——中性化。我们将通过简单的例子理解它的目的,并了解其基本计算方法。
概述:什么是中性化?
上一节我们介绍了因子分析的基本概念,本节中我们来看看如何“提纯”因子。中性化的核心目的就是提纯。
为了理解“提纯”,我们先看一个例子。假设我们设计了一个选股策略,其中使用了四个不同的因子(例如A、B、C、D)。理论上,这四个因子应该从不同角度帮助我们筛选股票。
但实际操作中,你可能会发现,无论怎么调整这四个因子的组合,最终选出的股票池总是高度相似。为什么会这样?
原因可能在于,这四个因子虽然名义上不同,但其内部绝大部分信息都指向了同一个共同因素,比如市值。因子A(如市净率)与市值高度相关,因子B、C、D也主要受市值影响。
这样一来,你的四个因子实际上在“说同一件事”。选股结果自然总是倾向于市值特征相似的股票,无法体现各个因子独特的、有价值的信息。这就像从四个不同的人身上,只看到了他们共同的身高特征,而忽略了每个人独特的技能。
因此,中性化要做的事,就是从每个因子中,剔除掉那些普遍的、共性的影响(如市值影响),提取出该因子独有的、有价值的信息。这个过程就是“提纯”。
量化交易中的中性化
在量化交易中,我们经常使用多个指标(因子)对股票池进行筛选和调仓。在使用这些因子选股时,常常会因为某些共性因素(如行业、市值)的强烈影响,导致选股结果带有我们不希望的倾向性,无法分散风险或捕捉多样化的机会。
例如,市净率(PB)因子就与市值有很高的相关性。如果不做中性化处理,直接使用市净率选股,选出的股票可能会集中在某些特定市值区间,无法有效发挥市净率因子本身的选股能力。
这里简要介绍市净率(PB):
- 公式:
市净率(PB) = 每股股价 / 每股净资产 - 说明:净资产是公司总资产减去总负债后的净值。通常认为,较低的市净率可能意味着股票被低估,投资价值更高。
中性化的计算方法

理解了中性化的目的后,我们来看其计算方法。由于计算需要具体的因子数据(如市值、市净率),这通常在量化平台中完成。这里先介绍原理。
中性化处理通常通过回归分析来实现。基本思想是:将需要处理的因子(如市净率)作为因变量(Y),将需要剔除的共性因素(如市值)作为自变量(X),建立回归模型。
以下是核心步骤:
-
建立回归方程。例如,我们想剔除市值对市净率的影响:
市净率 = α + β * 市值 + ε
其中,ε 是回归残差。 -
提取残差。进行回归后,得到的残差 ε 就是中性化后的因子值。
中性化后的市净率 = ε = 市净率 - (α + β * 市值) -
结果解释。残差 ε 代表了市净率中无法被市值解释的部分,即剥离了市值影响后,市净率独有的信息。这个新值就可以用于后续的选股模型,它更“纯净”地反映了市净率因子本身的特性。
总结
本节课中我们一起学习了中性化处理。
- 我们首先通过一个例子理解了中性化的核心目的是提纯,即从因子中剔除共性影响,提取独特信息。
- 接着,我们探讨了在量化交易中,不做中性化可能导致选股偏差,例如市净率因子受市值干扰。
- 最后,我们介绍了中性化的基本计算方法——通过回归分析提取残差,从而得到剥离了指定共性因素(如市值)影响后的因子值。

通过中性化处理,我们可以让因子更有效地发挥其独特作用,构建出多样性更好、逻辑更清晰的选股策略。
034:策略任务概述 📊

在本节课中,我们将要学习如何对因子进行“提纯”或“中性化”处理,并基于处理后的因子构建一个简单的选股策略。核心目标是理解如何剔除因子中我们不希望包含的干扰信息(例如市值的影响),从而获得更“纯净”的因子信号。
因子提纯的核心思想 🎯
上一节我们介绍了因子处理的重要性,本节中我们来看看如何具体实现因子的“提纯”。
因子提纯的目的是从原始因子中,剔除掉与其他变量(如市值)相关的部分。例如,我们发现市净率(PB)因子与市值(Size)有较大关联。我们希望从PB因子中剔除掉能被市值解释的那部分信息,从而得到一个与市值无关的、更纯粹的PB因子。
这个过程可以类比于一个回归问题。我们将原始因子(如PB)视为因变量 Y,将希望剔除的变量(如市值)视为自变量 X。通过建立回归方程,我们可以估计出市值能解释PB因子的部分。
以下是建立回归模型的核心步骤:
- 建立回归方程:
Y = W * X + B,其中 Y 是原始因子,X 是干扰变量(如市值)。 - 求解回归系数 W 和截距 B。
- 计算预测值:
Y_hat = W * X + B。这个预测值代表了市值所能解释的PB部分。 - 计算残差(提纯后的因子):
Y_residual = Y - Y_hat。这个残差就是剔除了市值影响后的“纯净”因子。

公式描述:
- 回归方程:
Y = W * X + B + ε - 提纯后因子:
Y_pure = Y - (W * X + B) = ε
其中,ε 是回归的残差项,即市值无法解释的部分,也就是我们想要的“提纯”结果。
策略构建流程 📈


理解了因子提纯(中性化)的方法后,我们来看如何将其融入一个完整的选股策略。

一个典型的基于因子的策略流程如下。我们将按照这个顺序,在后续实践中逐步实现。

以下是策略构建的主要步骤:
- 数据获取:获取所需的因子数据(如市盈率PE、市值Size)和股票基础信息。
- 股票池筛选:剔除不符合条件的股票,例如ST股、停牌股、上市时间过短(如少于半年)的股票。
- 因子处理:对筛选后的股票因子数据进行处理。
- 去极值:处理异常值。
- 标准化:使不同因子的量纲统一。
- 中性化:使用上述回归方法,剔除特定变量(如行业、市值)的影响。
- 信号生成与选股:基于处理后的“纯净”因子生成交易信号。例如,设定规则“选择市盈率小于0.2的股票”。
- 执行策略:根据生成的信号,决定买入或卖出哪些股票。

课程总结 ✨


本节课中我们一起学习了因子投资策略中的一个关键预处理步骤——因子中性化。我们通过建立回归模型,从原始因子中剥离了与干扰变量(如市值)相关的部分,从而得到了更有效的因子信号。同时,我们也概述了一个完整的因子选股策略流程,包括数据准备、数据处理、信号生成等关键环节。掌握这些基础概念和方法,是构建更复杂量化策略的重要第一步。
035:1-股票数据获取 📈
在本节课中,我们将学习如何获取股票数据,这是构建因子选股策略的第一步。我们将介绍如何初始化策略、导入必要的工具包,以及如何从数据源中获取并筛选股票列表。


策略初始化与工具包导入
上一节我们介绍了因子选股策略的基本流程,本节中我们来看看如何开始编写代码。首先,我们需要创建一个新的策略文件,并导入必要的工具包。
以下是需要导入的核心工具包:
- numpy:用于高效的数值计算。
- pandas:用于数据处理和分析。
- statsmodels:用于统计分析,特别是回归分析,这在后续因子中性化操作中会用到。


import numpy as np
import pandas as pd
from statsmodels import regression
设置策略运行周期
初始化策略后,我们需要定义策略的运行周期。因子选股策略通常需要定期调仓,即每隔一段时间重新筛选并调整持仓的股票。

在策略的构造函数中,我们使用定时器来设定调仓频率。考虑到交易成本和市场稳定性,按月调仓是一个常见且合理的选择。


def initialize(context):
# 设置按月调仓
run_monthly(rebalance, monthday=1, time='open')
这里的 rebalance 函数是我们即将定义的核心函数,它将在每个月的第一个交易日开盘时执行,负责完成选股和调仓逻辑。


获取与筛选股票池
在 rebalance 函数中,第一步是获取一个初始的股票池。我们通常从一个广泛的市场股票列表开始,然后根据特定规则进行筛选,以缩小我们的选股范围。

以下是获取和初步处理股票池的步骤:

- 获取全市场股票:使用API函数获取指定市场(如A股)的所有股票合约信息。
- 筛选股票:对获取到的股票列表进行初步过滤,例如剔除ST股票、上市时间过短的股票等,以得到一个更干净、可投资的股票池。

def rebalance(context):
# 1. 获取全市场股票
all_stocks = get_all_securities(types=[‘stock‘], date=None)
# 2. 对股票进行初步筛选 (此处为示例,筛选逻辑需自定义)
# 例如,过滤掉ST股票和上市不足60天的股票
filtered_stocks = [stock for stock in all_stocks.index
if not is_st_stock(stock) and days_since_listed(stock) > 60]

通过以上步骤,我们就完成了策略的初始化、数据获取和初步的股票池构建工作,为后续的因子计算和选股策略实施打下了基础。

本节课中我们一起学习了量化策略的起步步骤:创建策略文件、导入工具包、设置策略运行周期以及获取并筛选初始股票池。在接下来的课程中,我们将在此基础上,进行因子的预处理和具体的选股逻辑实现。
036:2-过滤筛选因子指标数据 📊

在本节课中,我们将学习如何对股票池进行初步筛选,并获取我们所需的财务指标数据。这是构建量化因子策略的关键一步,目的是剔除不符合交易条件的股票,并准备好用于后续分析的数据。


上一节我们介绍了如何获取初始的股票池,本节中我们来看看如何对这些股票进行过滤,并提取关键的财务指标。
过滤不符合条件的股票 🚫


在构建股票池时,我们需要剔除一些不符合交易条件的股票,例如停牌股、ST股和刚上市的新股。以下是实现这一目标的三个核心过滤函数。


1. 过滤停牌股票


首先,我们需要过滤掉全天停牌的股票。我们使用 is_suspended 函数来判断一只股票是否停牌。

def filter_suspended(stock_list):
"""
过滤掉停牌的股票。
:param stock_list: 初始股票列表
:return: 过滤后的股票列表
"""
filtered_stocks = []
for stock in stock_list:
if not is_suspended(stock):
filtered_stocks.append(stock)
return filtered_stocks


2. 过滤ST股票



接下来,我们需要过滤掉被标记为ST(特别处理)的股票。我们使用 is_st_stock 函数进行判断。

def filter_st(stock_list):
"""
过滤掉ST股票。
:param stock_list: 经过停牌过滤后的股票列表
:return: 过滤后的股票列表
"""
filtered_stocks = []
for stock in stock_list:
if not is_st_stock(stock):
filtered_stocks.append(stock)
return filtered_stocks

3. 过滤新股



最后,我们过滤掉上市时间过短的新股。这里我们假设需要股票上市超过180天。我们使用 get_days_from_listed 函数获取上市天数。

def filter_new(stock_list):
"""
过滤掉上市天数少于180天的新股。
:param stock_list: 经过ST过滤后的股票列表
:return: 过滤后的股票列表
"""
filtered_stocks = []
for stock in stock_list:
if get_days_from_listed(stock) > 180:
filtered_stocks.append(stock)
return filtered_stocks

完成以上三个过滤函数的定义后,我们可以将它们串联起来,对初始股票池进行逐步筛选。

# 假设 initial_stocks 是初始获取的所有股票列表
filtered_by_suspend = filter_suspended(initial_stocks)
filtered_by_st = filter_st(filtered_by_suspend)
final_filtered_stocks = filter_new(filtered_by_st)


查询所需的财务指标 📈

过滤出合格的股票池后,下一步是获取这些股票的财务指标数据。在本策略中,我们关注市净率(PB)和市值(Market Cap)这两个因子。


我们使用 get_fundamentals 函数来查询数据。该函数需要传入一个查询语句(query)和过滤条件。


以下是查询代码:

# 构建查询语句,查询市净率(pb_ratio)和市值(market_cap)
query = query(
fundamentals.eod_derivative_indicator.pb_ratio,
fundamentals.eod_derivative_indicator.market_cap
)

# 执行查询,仅查询我们最终过滤出的股票池中的股票
fundamental_data = get_fundamentals(query, filter=final_filtered_stocks)


查询返回的数据 fundamental_data 通常是一个 DataFrame,其索引是股票代码,列是我们查询的指标。但有时数据的排列方式(横表)可能不便于后续分析,我们通常需要将其转换为每行代表一只股票的格式(纵表),这可以通过转置操作 .T 来实现。同时,我们可能需要处理缺失值。

# 转置数据,使每行代表一只股票
transposed_data = fundamental_data.T

# 删除包含空值的行(简单处理)
cleaned_data = transposed_data.dropna()


本节课中我们一起学习了如何对股票池进行三步关键过滤(停牌、ST、新股),以及如何使用 get_fundamentals API 查询所需的财务指标数据(市净率和市值),并对数据进行了初步清洗(转置和删除空值)。这些步骤为我们后续的因子计算和选股策略奠定了坚实的数据基础。
037:3-因子数据预处理 📊




在本节课中,我们将要学习因子数据预处理的三个核心步骤:去极值、标准化和中性化。这些步骤对于清洗和准备金融数据至关重要,能帮助我们构建更稳健的量化模型。


上一节我们介绍了如何计算和获取因子数据,本节中我们来看看如何对这些原始数据进行预处理。

第一步:去极值操作 🧹


去极值操作的目的是处理数据中的异常值或离群点,防止它们对后续分析产生过大影响。我们将使用“三西格玛”法则来实现。
以下是去极值函数 filter_three_sigma 的实现步骤:


- 计算传入数据序列的均值(
mean)和标准差(std)。 - 根据三西格玛法则,计算数据的上下限:
- 上限 =
mean + n * std - 下限 =
mean - n * std
- 上限 =
- 使用
np.clip函数将超出上下限的值截断至边界,而不是直接删除。


def filter_three_sigma(series, n=3):
mean = series.mean()
std = series.std()
upper = mean + n * std
lower = mean - n * std
return np.clip(series, lower, upper)


第二步:标准化操作 📏


标准化操作的目的是将不同量纲或量级的指标数据转换到同一尺度,便于比较和计算。我们采用最常见的Z-Score标准化方法。


以下是标准化函数 standardize 的实现步骤:


- 同样,计算数据序列的均值(
mean)和标准差(std)。 - 对序列中的每一个值进行变换:
(值 - 均值) / 标准差。

def standardize(series):
mean = series.mean()
std = series.std()
return (series - mean) / std
第三步:中性化操作 ⚖️



中性化操作的目的是剔除因子中与其他风格因子(如市值)相关的部分,从而得到因子本身的“纯净”收益。我们通过线性回归来实现。

在本例中,我们以“市净率”作为待处理的因子(factor),以“市值”作为需要被剥离的风格因子(x)。


以下是中性化函数 neutralize 的实现步骤:


- 导入
statsmodels库,使用其普通最小二乘法(OLS)进行线性回归。 - 构建回归方程:
factor = a * market_cap + b。 - 拟合模型后,模型的残差(
resid)即为剔除了市值影响后的“中性化”因子值。
import statsmodels.api as sm
def neutralize(factor, market_cap):
# 为自变量添加常数项(截距)
X = sm.add_constant(market_cap.astype(float))
y = factor.astype(float)
# 构建并拟合OLS模型
model = sm.OLS(y, X).fit()
# 返回残差,即中性化后的因子
return model.resid



本节课中我们一起学习了因子数据预处理的三个关键步骤。首先,我们使用三西格玛法去除极端值;其次,通过Z-Score方法对数据进行标准化;最后,利用线性回归进行因子中性化,以剥离特定风格(如市值)的影响。掌握这三步是构建有效量化因子模型的重要基础。
038:股票池筛选 📊




在本节课中,我们将学习如何基于处理后的因子数据,构建一个用于投资的股票池。我们将完成从因子预处理到股票筛选,再到模拟调仓的完整流程。
上一节我们介绍了因子的中性化处理,本节中我们来看看如何利用处理好的因子来筛选股票。

因子预处理流程回顾


首先,我们需要对原始因子数据进行一系列预处理操作,包括去除离群值、标准化和中性化。以下是完整的预处理步骤:


- 去除离群值:使用
winsorize方法处理因子数据中的极端值。 - 标准化:使用
standardize方法将因子数据转换为均值为0、标准差为1的标准分布。 - 中性化:使用
neutralize方法消除因子对行业或市值等风格的暴露。



我们将这些步骤串联起来,形成一个完整的预处理函数。

# 假设 fundamental 是原始的因子DataFrame,sector 是行业分类数据
processed_factor = winsorize(fundamental)
processed_factor = standardize(processed_factor)
processed_factor = neutralize(processed_factor, sector)

基于因子进行股票筛选
预处理完成后,我们得到一个干净的因子值。接下来,我们需要根据这个因子值来筛选股票。通常,我们希望买入因子值较小(例如市净率较低)的股票。
以下是筛选股票池的具体步骤:


- 确定筛选阈值:计算因子值的分位数,例如选择因子值最小的前20%的股票。
- 执行筛选:根据阈值,从所有股票中筛选出符合条件的股票代码。

# 计算20%分位点的值
threshold = processed_factor.quantile(0.2)
# 筛选出因子值小于等于该阈值的股票
selected_stocks = processed_factor[processed_factor <= threshold].index.tolist()


模拟交易调仓操作
筛选出目标股票池后,我们需要模拟交易操作,即卖出不在新股票池中的持仓,并买入新股票池中的股票。


以下是调仓逻辑的实现步骤:

- 获取当前持仓:从交易上下文
context中获取当前账户持有的所有股票。 - 计算需要卖出的股票:找出当前持仓中,不在新股票池
selected_stocks里的股票。 - 执行卖出操作:对这部分股票下达卖出指令。
- 执行买入操作:将资金分配到新股票池中的股票上(具体分配策略需另行定义)。
# 获取当前持仓的股票列表
current_holdings = context.portfolio.positions.keys()
# 计算需要卖出的股票:当前有但新池子里没有的股票
stocks_to_sell = set(current_holdings).difference(set(selected_stocks))
# 执行卖出操作(此处为逻辑示意)
for stock in stocks_to_sell:
order_target_percent(stock, 0) # 将持仓比例调整为0,即卖出

本节课中我们一起学习了量化策略中股票池筛选的核心流程。我们首先回顾并串联了因子预处理的三个步骤,然后学习了如何根据处理后的因子值筛选出目标股票,最后模拟了根据新股票池进行调仓的交易逻辑。这个过程是构建自动化选股策略的基础。
039:5-策略效果评估分析 📊

在本节课中,我们将学习如何实现一个完整的因子选股策略,并对其效果进行评估分析。我们将编写一个每月调仓的策略,通过筛选特定因子(如市值、市盈率)来构建股票池,并执行买卖操作。课程将涵盖策略逻辑构建、代码实现、错误调试以及最终的回测结果分析。

上一节我们介绍了策略的选股逻辑和预处理步骤。本节中,我们来看看如何实现具体的调仓操作,并运行策略进行回测。

调仓逻辑实现



首先,我们需要判断当前持有的股票中,哪些已经不在最新的选股池里。如果存在需要卖出的股票,则执行调仓操作。



以下是调仓操作的具体步骤:


- 判断是否需要调仓:比较当前持仓股票列表与最新选股池的差异。如果存在差异(即需要卖出的股票数量大于0),则执行调仓。
if len(delete_list) > 0: print(f"执行调仓操作,时间:{context.current_dt}")



- 卖出不在池中的股票:遍历需要删除的股票列表,使用
order_target_percent函数将其持仓比例调整为0,即全部卖出。for stock in delete_list: order_target_percent(stock, 0)

- 买入池中的股票:遍历最新的选股池,使用
order_target_percent函数平均分配资金买入所有股票。for stock in stock_list: order_target_percent(stock, 1.0 / len(stock_list))



通过以上步骤,我们完成了每月一次的调仓逻辑,确保持仓始终与最新的选股标准保持一致。




代码调试与运行



在编写完核心逻辑后,运行代码时可能会遇到各种错误。以下是调试过程中遇到的一些典型问题及其解决方法。


以下是调试过程中遇到的主要问题:


- 中性化函数参数缺失:在调用中性化处理函数时,缺少了必要的参数(如因子列名)。需要检查函数定义并传入正确的参数。
# 错误示例:缺少列名参数 # neutralized_data = neutralize(factor_data) # 正确示例 neutralized_data = neutralize(factor_data, factor_column_name)



- 函数名拼写错误:例如,将
quantile误写为qantile。需要仔细检查代码中的拼写。# 错误示例 # selected = factor_data[factor_data[‘factor‘] <= factor_data[‘factor‘].qantile(0.2)] # 正确示例 selected = factor_data[factor_data[‘factor‘] <= factor_data[‘factor‘].quantile(0.2)]


- 变量未定义或类型错误:例如,使用了未定义的列表变量
stock_list。需要确保所有变量在使用前都已正确定义和赋值。# 确保 stock_list 在使用前已定义 stock_list = get_selected_stocks(context) for stock in stock_list: ...


经过一系列调试和修正,代码最终成功通过编译并开始执行回测。

回测结果分析
代码运行后,我们可以在回测进度条和日志中观察策略的执行情况。策略会按照设定的每月频率进行调仓,并在日志中打印调仓时间点。

以下是回测结果的关键观察点:


- 策略执行日志:控制台会打印每次调仓的日期,这验证了定时器在正常工作。
- 收益曲线对比:回测界面会展示策略收益曲线与基准收益曲线(如上证指数)的对比。目标是策略收益能跑赢基准。
- 关键绩效指标:
- 年化收益率:策略在一年内获得的平均收益率。
- 夏普比率:衡量每承担一单位风险所获得的超额回报。越高越好。
- 最大回撤:策略净值从高点下降到低点的最大幅度。越小越好。
- 交易详情:可以查看每日的交易记录和持仓变化,用于深入分析策略行为。


在本例的回测中,策略在所选时间段内取得了相对于基准指数更好的表现(尽管绝对收益可能不高),这初步验证了基于市值和市盈率因子进行选股的有效性。




策略流程总结



本节课中我们一起学习并实现了一个完整的因子选股策略流程。



以下是策略开发的核心步骤总结:



- 初始化与定时器设置:在
initialize函数中设置策略的定时调仓频率(如每月)。 - 股票池预处理:在
before_trading_start或类似函数中,剔除不符合基本条件的股票(如ST股、停牌股)。 - 因子计算与处理:获取所需的因子数据(如市值、市盈率),并进行必要的预处理(如去极值、标准化、中性化)。
- 选股逻辑:根据因子值对股票进行排序和筛选(例如,选择市盈率最低的20%的股票),形成最终的投资组合。
- 调仓执行:在定时触发的函数中,比较当前持仓与目标持仓的差异,执行卖出和买入操作,使持仓与目标组合一致。
- 回测与评估:运行策略回测,通过收益曲线和各项绩效指标(年化收益、夏普比率、最大回撤)来评估策略效果。


通过这个流程,我们构建了一个系统化的量化策略框架。需要注意的是,本例仅为教学演示,实际应用中需要更严谨的因子研究、风险控制和市场环境分析。
040:1-因子分析概述
在本节课中,我们将要学习因子分析的基本概念与目标。我们将了解如何从众多股票因子中筛选出有效的因子,并介绍两种核心的分析方法:IC值计算与IC值序列分析。通过本课,你将理解如何量化评估一个因子对股票收益率的预测能力。
因子分析的目标
上一节我们介绍了因子分析的整体目标,本节中我们来看看具体的分析场景。
假设我们拥有某只股票A的众多因子数据。每个股票都可以提取出各种各样的指标。
以下是两个常见的指标示例:
- 基本面信息指标,例如市盈率(PE)。
- 技术指标,例如移动平均线(MA)。
因子可以划分为多个大类,每个大类下又能细分为更多具体指标。问题在于,如果我们手中有300个不同的因子或指标,在设计投资策略或进行回测时,我们需要判断哪些因子是有效的,哪些是无效的。
换句话说,我们需要对因子进行筛选和排序。排序的依据是因子对最终投资收益的影响程度,可以理解为给每个因子打分。例如,某个因子可能得98分,表示它很好;另一个可能得0分,表示它无效。这样,我们就能将所有因子按照效用从高到低进行排序。因为每个因子对结果的影响确实不同。我们现在要做的就是找出哪些因子是“好”的。
评估因子的基本方法
那么,如何判断一个因子的好坏呢?最简单的方法有以下几种,这里介绍两种最基本的方法:观察因子与收益率的关系。
既然提到收益率,我们先解释一下收益率的定义。这些知识点很简单,无需死记硬背。
收益率的计算如下:例如,我们想计算每日收益率。每日收益率等于当日的收盘价减去上一个交易日的收盘价,再除以上一个交易日的收盘价。
公式表示为:
日收益率 = (当日收盘价 - 前一日收盘价) / 前一日收盘价
投资股票的目标是积少成多,希望每天都盈利。因此,我们希望观察因子值的变化与收益率变化之间的关系。我们拥有很多因子指标。假设我们连续取了一年的数据,大约250个交易日。在这250天中,我们有一个因子F和收益率R,它们每天都有对应的数值。
我们需要观察的是:这个因子的走势与收益率的走势之间存在什么样的关系?是正相关、负相关,还是不相关?如果相关,相关性有多强?是正相关还是负相关?我们需要将这些指标量化。本质上,我们要对因子做两类分析。
第一类分析:IC值分析
第一类分析是因子的IC分析。IC代表信息系数(Information Coefficient),它是一个衡量相关性的指标。
IC值的计算方法是:计算你所选的某个因子指标(例如市盈率PE)与股票收益率之间的相关性。这里使用的相关性指标通常是斯皮尔曼秩相关系数。它的取值范围在-1到1之间。值越接近1,表示正相关性越强;越接近-1,表示负相关性越强;越接近0,表示几乎没有线性关系。
我们要计算的就是因子与收益率之间的这种斯皮尔曼相关系数。这也可以称为单因子分析。为什么是单因子分析?因为在我们假设的300个指标中,收益率是相对固定的。在收益率固定的前提下,我们需要逐一考察每个因子与收益率的关系。第一个因子、第二个因子,一直到第300个因子。

因此,在程序实现上,我们可能会使用一个循环来遍历每个因子,分别计算它们与收益率之间的斯皮尔曼秩相关系数。
计算出的因子与收益率之间的斯皮尔曼相关系数结果,就被称为该因子的IC值。IC是Information Coefficient的缩写。
第二类分析:IC值序列分析
计算出IC值之后,我们还需要观察IC值序列的表现。
例如,左侧图表展示了我们稍后要分析的结果之一:它统计了每一天的IC值及其变化情况。右侧图表中,蓝色线条绘制了几个月时间内IC值的折线图,绿色线条则是IC值的移动平均线。移动平均线意味着不是看单日值,而是以一定窗口期(例如10天)计算IC值的均值,这有助于观察趋势。图中前段出现的空值是因为移动平均计算需要窗口期数据。
以下是IC值序列分析中关注的点:
- IC值序列中既有正值也有负值,正如之前所说,其取值范围在-1到1之间。
- 我们的目标是找出那些与收益率关系(绝对值)较大的因子。关系越强,越值得深入挖掘其逻辑。
- 对于那些与收益率关系微弱,甚至可能产生误导的因子,我们可以选择忽略。

本节课中,我们一起学习了因子分析的核心目标:从海量因子中筛选有效因子。我们介绍了两种基本评估方法:一是计算单个因子与收益率的IC值(斯皮尔曼相关系数),以衡量其静态预测能力;二是分析IC值序列的走势与稳定性,以评估其动态表现。通过这两种分析,我们可以对因子进行有效的排序和筛选,为构建量化策略打下基础。
041:Alphalens工具包介绍 📊
在本节课中,我们将学习一个名为Alphalens的强大工具包。它专门用于因子分析,能够帮助我们高效地计算各类指标并绘制图表,从而省去大量手动编码的工作。
工具包简介与获取
上一节我们介绍了因子分析的基本概念,本节中我们来看看实现这些分析的具体工具。

Alphalens是一个专门用于因子分析的Python工具包。它集成了核心的计算与可视化功能,用户无需从零开始编写代码。

以下是获取和安装Alphalens的步骤:
- GitHub地址:可以访问其GitHub页面查看源码和说明。
- 安装方法:通过pip命令
pip install alphalens即可完成安装,过程非常简单。 - 使用文档:官方提供了详细的使用文档和API说明,供用户查阅学习。
如果学员在本地环境操作,可以按上述步骤安装。但本课程将使用现成的量化平台,该平台已预装好Alphalens,因此我们可以直接开始使用。


学习方法与官方资源
了解工具的基本信息后,我们来看看如何有效地学习使用它。

学习Alphalens的最佳途径是查阅其官方文档和示例代码。官方提供了编写良好的示例(examples),这些示例演示了工具包的基本使用方法。
对于想深入学习的学员,建议仔细阅读这些官方示例和API文档。本课程的内容也主要参考了这些官方资料,并将核心知识点进行了总结和梳理,以帮助大家快速上手完成课程任务。

实践环境准备
理论介绍完毕,接下来我们需要准备编写代码的实践环境。
由于因子分析需要获取特定的金融数据,在个人电脑上操作较为麻烦。因此,我们将在一个在线的量化研究平台中编写和运行代码。

在该平台中,我们无需进行策略回测,而是专注于数据分析。请按照以下步骤进入分析环境:
- 在平台左侧导航栏找到并点击 “投资研究” 功能。
- 点击后,界面会启动一个类似于Jupyter Notebook的在线编程环境。
- 在该环境中新建一个Python 3笔记文件,即可开始编写代码。

后续课程中的代码都将在该平台的这个环境中运行。我会将代码提供给大家,学员可以上传或复制到自己的平台中,并跟随视频逐步操作。

本节课中我们一起学习了Alphalens工具包的作用、安装方法、学习资源以及进行因子分析的实践环境搭建。接下来,我们就可以在这个准备好的环境中,开始利用Alphalens进行具体的因子分析操作了。
042:获取因子指标数据 📊
在本节课中,我们将学习如何使用量化分析工具包,获取指定时间段内所有股票的特定因子指标数据。我们将从导入工具包开始,逐步完成日期选择、股票池构建、数据查询等步骤。
导入工具包
首先,我们需要导入必要的工具包。alpharies 中的 utils 模块用于数据处理,plotting 模块用于绘图,其他模块将用于后续的分析任务。
import alpharies.utils as utils
import alpharies.plotting as plotting
# 其他用于分析的模块
选择日期范围
上一节我们介绍了工具包的导入。本节中,我们来看看如何获取分析所需的时间段数据。与回测框架不同,当前模块没有内置的每日执行功能。因此,我们需要手动指定并获取一个日期序列。
以下是获取交易日期的步骤:
- 定义函数
get_trading_dates,它接受开始日期和结束日期两个参数。 - 函数内部调用相关方法,返回该时间段内所有的交易日列表。
def get_trading_dates(start_date, end_date):
"""
获取指定时间段内的所有交易日。
参数:
start_date (str): 开始日期,格式为'YYYY-MM-DD'。
end_date (str): 结束日期,格式为'YYYY-MM-DD'。
返回:
list: 交易日期的列表。
"""
# 此处调用工具包方法获取日期列表,示例中使用固定值说明
trading_dates = ['2019-01-02', '2019-01-03', ...] # 实际应由API返回
return trading_dates
# 指定我们需要分析的日期范围
start = '2019-01-01'
end = '2020-01-01'
date_list = get_trading_dates(start, end)
print(date_list[:5]) # 检查前5个日期

运行上述代码后,我们就获得了从2019年1月1日到2020年1月1日之间所有的交易日列表。

构建股票池

获取日期后,下一步是确定我们要分析哪些股票。为了简化,我们将获取所有A股股票作为我们的股票池。

以下是构建股票池的步骤:
- 调用API获取所有A股股票的代码列表。
- 将列表存储在变量中,供后续查询使用。
# 获取所有A股股票代码
stock_pool = get_all_a_stocks() # 假设的API函数
print(f"股票池中共有 {len(stock_pool)} 只股票")
print(stock_pool[:10]) # 查看前10只股票代码


查询因子指标数据

现在,我们有了日期和股票列表。接下来,我们将学习如何查询具体的因子指标数据。本节以市盈率(PE Ratio)因子为例。
以下是查询单日因子数据的步骤:

- 使用
query语句指定要查询的因子(例如fundamentals.eod_derivative_indicator.pe_ratio)。 - 通过
filter条件限制只查询我们股票池中的股票。 - 执行查询,并传入具体的查询日期。

# 1. 构建查询语句
q = query(
fundamentals.eod_derivative_indicator.pe_ratio # 查询PE比率因子
).filter(
fundamentals.eod_derivative_indicator.code.in_(stock_pool) # 过滤股票池
)
# 2. 执行查询(以日期列表中的第一天为例)
single_date = date_list[0]
factor_data = get_fundamentals(q, date=single_date)
# 3. 查看查询结果
print(factor_data.iloc[0, 0, :5]) # 查看前5只股票的PE数据
执行代码后,我们便得到了指定日期(2019年1月2日)所有A股股票的市盈率数据。数据是一个多维结构,我们通常只关心最后一个维度的具体数值。
总结
本节课中我们一起学习了因子分析数据准备的核心流程。
- 选择日期:我们定义了
get_trading_dates函数,用于获取特定时间段的交易日序列。 - 构建股票池:我们通过API获取了全市场A股代码,构建了分析范围。
- 查询数据:我们使用
query()和get_fundamentals()方法,成功查询到了指定日期、指定股票池的单一因子(PE比率)数据。

目前,我们仅获取了单日的因子数据。在接下来的课程中,我们将扩展此流程,循环获取整个时间段内每一天的因子数据,为后续的因子有效性分析和策略构建打下基础。
043:获取给定区间全部数据 📊




在本节课中,我们将学习如何获取一个指定时间区间内的全部因子数据。我们将通过循环遍历每一天,获取当天的数据,并将所有数据整合到一个统一的表格中,以便后续分析。
上一节我们介绍了如何获取单日的因子数据,本节中我们来看看如何获取一个连续时间段内的所有数据。

核心思路是使用循环。既然我们有一个日期序列,要获取其中每一天的数据,最直接的方法就是写一个 for 循环。

以下是实现步骤:


- 确定日期范围:首先,我们需要知道要获取多少天的数据。这取决于我们设定的起始和结束日期。
- 循环遍历每一天:使用
for循环遍历日期序列中的每一天。 - 获取每日数据:在循环体内,调用获取单日数据的函数,传入当前循环到的日期索引
i,而不是固定的0。 - 提取有效数据:获取到的每日数据是一个三维结构。我们通常只关心第三个维度(指标值),因此使用索引
[0, 2, :]来提取我们需要的部分。 - 构建每日数据表:将提取出的数据(指标值)与对应的日期、股票代码等信息组合,构建成一个小的
DataFrame。这个DataFrame至少应包含“日期”和“因子值”两列。 - 汇总所有数据:在循环开始前,创建一个空的“总表”
DataFrame。在每次循环中,将构建好的每日数据表拼接到这个总表中。
以下是核心循环和拼接操作的代码框架:

import pandas as pd

# 假设 dates 是日期序列,get_daily_factor 是获取单日数据的函数
total_df = pd.DataFrame() # 创建空的总表

for i in range(len(dates)):
# 1. 获取第 i 天的原始数据
daily_raw_data = get_daily_factor(i)
# 2. 提取有效的因子值(假设在第三个维度)
factor_values = daily_raw_data[0, 2, :]
# 3. 构建每日数据的小表格
daily_df = pd.DataFrame({
'date': dates[i], # 当前日期
'factor_value': factor_values # 提取出的因子值
# 可以在这里添加股票代码列,如果原始数据包含的话
})
# 4. 将每日数据拼接到总表
total_df = pd.concat([total_df, daily_df], ignore_index=True)

这个过程虽然步骤清晰,但执行起来可能较慢,特别是当时间区间较长(例如一年)时。因为需要向数据源发起多次请求并处理大量数据。
执行完毕后,我们可以打印 total_df 来查看结果。表格中应该按行清晰地列出了每一天、每一只股票(或每一个标的)对应的因子值。这样,我们就成功地将一个时间区间内的分散数据整合到了一个结构化的表格中,为下一步的数据分析(如回测、筛选)做好了准备。





本节课中我们一起学习了如何批量获取指定时间区间内的因子数据。我们掌握了通过循环遍历日期、逐日获取并提取数据、最后将所有数据拼接汇总到一张总表的核心方法。这是处理时间序列金融数据的基础且关键的一步。
044:数据格式转换与预处理 📊
在本节课中,我们将学习如何将数据处理成特定工具包(如阿尔法伦斯)所要求的格式,并完成数据预处理的关键步骤。我们将重点讲解数据格式的转换、索引设置以及异常值处理和标准化操作。
数据格式转换 🔄
上一节我们介绍了数据的基本处理,本节中我们来看看如何转换数据格式。当前的数据格式与阿尔法伦斯工具包的要求不一致。该工具包要求的数据格式是多级索引结构,第一级是日期,第二级是股票代码,对应的值是指标数据。
为了满足工具包的要求,我们必须将数据转换成这种特定格式。工具包不会自动进行格式转换,因此我们需要手动完成。


以下是转换数据格式的核心步骤:
- 设置多级索引:我们需要将
date列和股票代码设置为数据的索引。 - 重塑数据结构:确保数据框的每一行对应一个日期和一个股票的唯一组合,其值是指标数值。


我们通过以下代码来实现格式转换:
# 假设 df 是原始数据框,包含 ‘date‘, ‘stock_code‘, ‘indicator‘ 等列
# 设置多级索引:第一级为日期(date),第二级为股票代码(stock_code)
df_transformed = df.set_index(['date', 'stock_code'])['indicator']
# 查看转换后的数据
print(df_transformed.head())
执行上述代码后,数据格式将变为工具包要求的样式:索引的第一层是日期,第二层是股票代码,对应的值是指标数值。

数据预处理 🧹
完成格式转换后,我们还需要对数据进行预处理,以确保后续分析的准确性和稳定性。预处理主要包括两个操作:去除异常值和数据标准化。

以下是数据预处理的两个核心操作:

- 去除异常值:通过设定上下限(例如,使用分位数),将超出合理范围的极端值进行截断或替换,以防止它们对整体分析产生过大影响。
- 数据标准化:将数据按日期进行标准化处理,使其均值为0,标准差为1。这有助于不同日期、不同股票之间的指标具有可比性。

我们通过以下代码来实现预处理:

# 1. 去除异常值 (以3倍标准差为例)
def remove_outliers(series):
mean = series.mean()
std = series.std()
upper_limit = mean + 3 * std
lower_limit = mean - 3 * std
# 将超出上下限的值替换为边界值
series_clipped = series.clip(lower=lower_limit, upper=upper_limit)
return series_clipped

# 2. 数据标准化 (按日期分组后标准化)
def standardize_data(series):
# 按索引的第一级(日期)进行分组,然后对每个组内的数据进行标准化
standardized = series.groupby(level=0).apply(lambda x: (x - x.mean()) / x.std())
return standardized

# 应用预处理
df_cleaned = remove_outliers(df_transformed)
df_final = standardize_data(df_cleaned)
# 可选:可视化处理后的数据分布
df_final.groupby(level=0).plot(kind='hist', alpha=0.5, legend=False)

这些是数据准备的基本操作,能有效提升后续量化分析模型的质量。


总结 📝

本节课中我们一起学习了为满足特定工具包要求而进行的数据格式转换,以及数据预处理的关键步骤。我们首先将数据转换为多级索引格式,然后进行了异常值处理和标准化。完成这些步骤后,数据就准备好了,可以输入到阿尔法伦斯等工具包中进行下一步的统计分析(如IC值计算)。
045:IC指标值计算
在本节课中,我们将学习如何计算IC(信息系数)指标值。IC值是衡量因子预测能力的关键指标,通过计算因子值与未来收益率之间的相关性来评估因子的有效性。我们将从获取数据开始,逐步完成数据格式转换,并最终计算出IC值。
数据准备:获取收盘价
上一节我们介绍了IC值的基本概念,本节中我们来看看如何获取计算所需的基础数据。计算IC值需要两个核心数据:因子值和股票的实际收益率。而计算收益率的基础是股票的收盘价。
以下是获取收盘价数据的步骤:
- 使用
get_price函数获取指定股票池在特定时间段内的价格数据。 - 函数参数包括股票代码列表、起始日期和结束日期。
- 从返回的多维价格数据中,提取出我们需要的“收盘价”数据,并将其整理成二维的
DataFrame格式。
# 示例:获取收盘价数据
price_data = get_price(
securities=stock_pool, # 股票代码列表
start_date='2019-01-01',
end_date='2020-01-01',
fields=['close'] # 指定获取收盘价
)
# 设置DataFrame的索引和列名
price_data.index.name = 'date'
price_data.columns.name = 'code'
执行上述代码后,我们得到一个以日期为索引、股票代码为列名的收盘价数据表,为后续计算收益率做好了准备。
数据格式转换
在获取了收盘价数据后,我们需要将因子数据和价格数据转换成计算IC值所需的特定格式。这个转换过程通常由一个工具函数完成。
以下是进行数据格式转换的要点:
- 调用专门的格式转换函数,该函数通常接受两个主要参数:处理好的因子数据和处理好的价格数据。
- 转换后的数据结构会包含日期、股票代码、因子值、未来不同周期的收益率(如1D、5D、10D)以及因子分组信息。
- 因子分组是将所有股票的因子值从小到大排序后,等分为若干组(如5组),并为每个股票标记其所属的组别(1到5)。组别数字越大,代表该股票的因子值越大。
# 示例:数据格式转换
formatted_data = transform_format(
factor_data=processed_factor_df, # 处理好的因子数据
price_data=price_data # 处理好的收盘价数据
)
转换完成后,我们得到一份包含多列信息的结构化数据。其中,1D 列代表下一交易日的收益率,其计算公式为:
收益率 = (今日收盘价 - 昨日收盘价) / 昨日收盘价
我们将主要使用 1D 收益率与因子值进行计算。
计算IC指标值
现在,我们拥有了格式正确的因子数据和收益率数据,可以开始计算核心的IC指标值了。IC值本质上是因子值与未来收益率之间的相关系数。
以下是计算IC值的步骤:

- 使用量化分析库中提供的
factor_information_coefficient或类似函数。 - 将上一步转换好的
formatted_data作为参数传入该函数。 - 函数会返回一个包含IC值序列的结果。我们通常关注
IC这一列,它代表了每日因子值与下一期收益率之间的相关性。

# 示例:计算IC值
ic_series = factor_information_coefficient(formatted_data)
# 查看前几日的IC值
print(ic_series.head())
执行计算后,我们会得到一个时间序列的IC值。这个值通常在 -1 到 1 之间。IC > 0 表示因子值与未来收益率正相关,因子值越大,预期收益越高;IC < 0 则表示负相关。IC值的绝对值越大,说明因子的预测能力越强。

课程总结

本节课中我们一起学习了IC指标值的完整计算流程。我们首先通过 get_price 函数获取了股票的收盘价数据,并从中计算出未来收益率。接着,利用专门的工具函数将因子数据和价格数据转换为计算所需的统一格式。最后,通过调用 factor_information_coefficient 函数,计算出了衡量因子预测能力的核心指标——IC值。理解并掌握IC值的计算,是评估和筛选有效量化因子的基础。
046:工具包绘图展示 📊
在本节课中,我们将学习如何使用工具包对计算出的因子IC值进行可视化分析。通过绘制图表,我们可以更直观地评估因子的有效性和稳定性。
上一节我们介绍了如何计算因子的IC值,本节中我们来看看如何将这些数值结果转化为直观的图表。
绘制IC值时间序列图
计算出的IC值每天都会变化。为了更清晰地观察其走势,我们可以将其绘制成时间序列图。以下是使用工具包进行绘图的步骤。
首先,我们需要导入绘图工具包 LOATING。然后,调用 IC 时间序列绘图函数。

# 导入绘图工具并绘制IC时间序列图
from LOATING import DPLOAD
DPLOAD.IC_time_series(SD)
执行上述代码后,系统会生成一张图表。

解读图表结果

生成的图表包含了丰富的信息,帮助我们分析因子。以下是对图表中主要元素的解读。
图表中主要包含两条线:
- 蓝色线:代表每日计算出的实际IC值,其波动范围通常较大。
- 绿色线:代表以月为周期计算的平均IC值,它平滑了日度波动,更能反映趋势。
此外,图表还标注了两个重要的统计指标:
- 均值 (Mean):IC值的平均值。
- 标准差 (STD):IC值波动的标准差。

分析因子表现
观察图表,我们可以对因子的表现进行初步评估。我们主要关注平滑后的绿色平均线。
绿色平均线的走势整体较为平稳,没有显示出明显的上升或下降趋势。同时,其数值也相对较小。在因子分析中,我们通常希望IC值越大越好,并且能观察到稳定的正向趋势。当前这个因子的表现可能不够理想。
了解信息比率
除了IC值,还有一个辅助指标可以帮助我们评估因子。这个指标叫做信息比率。
信息比率(Information Ratio)的计算公式为:
信息比率 = 均值 / 标准差
它用于衡量因子IC值的稳定性。比值越大,说明因子的表现越稳定;比值越小,则说明波动性较大。这个指标可以作为IC值分析的补充参考。
探索其他分析图表
工具包提供了多种绘图函数,可以进行更深入的分析。以下是其他几种可用的图表类型,大家可以根据需要自行探索。
- 直方图 (Histogram):查看IC值的分布情况。
- Q-Q图 (Q-Q Plot):检验IC值是否服从正态分布。
- 热力图 (Heatmap):用颜色直观展示不同周期或条件下的IC值。

这些函数在API文档中均有列出。在实际进行深入的因子策略分析时,这些工具能提供全面的可视化支持。



例如,热力图可以非常直观地展示数据。


本节课中我们一起学习了如何使用绘图工具包对因子IC值进行可视化。我们绘制了时间序列图,解读了图表中的关键信息,并了解了信息比率这一稳定性指标。最后,我们还预览了其他可用的分析图表,为后续深入的因子评估奠定了基础。
047:因子收益率简介 📈


在本节课中,我们将要学习因子收益率的概念、计算方法及其在量化投资中的应用。因子收益率是评估因子有效性的重要指标之一,它不同于之前介绍的IC值,而是直接衡量因子本身能带来的收益水平。

因子收益率的定义
上一节我们介绍了IC值,它衡量的是因子与收益率之间的相关性。本节中我们来看看因子收益率,这是一个具体的数值,而非关系度量。
因子收益率通过一个回归模型来体现。其核心思想是将股票的收益率表示为因子的线性函数。具体公式如下:
收益率 = 因子 × 因子收益率 + 偏置项

在这个公式中:
- 收益率 是目标变量(例如,某只股票今日的收益率)。
- 因子 是输入变量(例如,市盈率、市值等)。
- 因子收益率 就是我们要求解的权重参数
W,它代表了该因子对收益率的贡献程度,即单位因子变化能带来多少收益。 - 偏置项 是模型的常数项。
因此,因子收益率 W 是一个具体的数值。对于每一个因子,我们都可以计算其对应的因子收益率,从而比较不同因子的“盈利能力”。
因子收益率的可视化分析

计算出因子收益率后,我们可以通过可视化来直观地分析因子的表现。以下是常见的分析步骤:
首先,将因子值从小到大划分为若干个区间(例如5组)。然后,计算每个区间内股票的平均收益率,并绘制成柱状图或折线图。

通过观察图表,我们可以判断:
- 因子在哪个取值区间内,能带来最高的平均收益。
- 因子与收益率之间是否存在单调关系(例如,因子值越大,收益越高)。

这种可视化方法帮助我们快速识别因子的有效区间和大致形态。

因子收益率的实际应用


因子收益率是一个重要的筛选指标。在实际构建多因子模型时,我们通常需要从数百个候选因子中挑选出最有效的几十个。

此时,因子收益率可以作为关键的评估依据之一。然而,因子筛选并非只依赖单一指标。我们需要综合考量多个评估指标:


以下是评估因子时常用的指标组合:
- 因子收益率:衡量因子的收益贡献能力。
- IC值(信息系数):衡量因子预测未来收益的准确性。
- IR值(信息比率):由IC的均值除以标准差得到,衡量因子预测能力的稳定性。


最终,我们会综合评比这些指标,选出在收益性、预测性和稳定性上都表现优异的因子,构建更稳健的投资策略。

代码实现与工具


计算和可视化因子收益率的过程可以借助专业的量化分析库来简化。这里我们使用 alphalens 库进行演示。

以下是一个调用 alphalens API 计算因子收益率的示例代码框架:


# 假设 factor_data 是经过预处理的因子数据,prices 是股票价格数据
from alphalens import performance
# 调用函数进行因子收益率分析
factor_returns, mean_return_by_quantile, std_quantile = performance.factor_returns(factor_data, prices)
# 结果 factor_returns 就是一个时间序列,记录了该因子每日的收益率
# 我们可以对其取均值,得到该因子的平均因子收益率
mean_factor_return = factor_returns.mean()


执行代码后,alphalens 会自动生成包含因子分组收益柱状图、各分组收益时间序列对比图等在内的完整分析图表。这些图表与之前描述的可视化效果一致,能够清晰地展示因子的表现。

通过计算得到的 mean_factor_return,我们可以对多个因子进行横向比较,因子收益率高的因子在初步筛选中更具吸引力。

总结


本节课中我们一起学习了因子收益率。我们明确了因子收益率是一个具体的数值,它通过回归模型 收益率 = 因子 × W + 偏置项 中的权重参数 W 来体现。我们学会了如何通过可视化分析因子在不同区间的表现,并理解了因子收益率在综合评估和筛选因子中的重要作用。最后,我们了解到可以借助像 alphalens 这样的专业工具来高效地完成因子收益率的计算和分析。掌握因子收益率,是构建科学量化模型的重要一步。
048:1-打分法选股策略概述
在本节课中,我们将要学习一种在量化选股中常用的策略——因子打分选股法。我们将了解其核心思想、实施前提以及基本流程,为后续的实践操作打下基础。
🧠 策略核心思想
上一节我们介绍了因子分析,但通常只关注单一因子对股票的影响。本节中我们来看看如何综合多个因子进行选股。
因子打分选股的核心思想是:综合多个因子对股票进行评分,根据总分排名来筛选股票。这类似于学生的总成绩由各科成绩加总决定,我们通过计算每只股票在所有选定因子上的综合得分,来评估其整体“好坏”。
假设我们有300只股票(例如沪深300成分股)和A、B、C、D四个因子指标。我们的目标不是单独看某个因子,而是为每只股票的每个因子打分,然后计算一个总分。最后,我们根据总分对所有股票进行排名,选取排名靠前的股票(如前10名)作为投资组合。每月调仓时,都重复这一过程。
📋 策略实施前提

在开始打分之前,我们必须明确一个关键的先验知识:每个因子与预期收益率的相关性方向。
以下是理解这一前提的关键点:
- 相关性方向:我们需要知道每个因子是“正向因子”还是“负向因子”。
- 正向因子:因子值越大,预期收益率可能越高。例如,每股收益、净资产收益率通常被认为是正向因子。
- 负向因子:因子值越小,预期收益率可能越高。例如,市盈率、市净率在某些情况下被视为负向因子(估值越低越好)。
- 知识来源:这个判断可以来源于金融领域的经验、券商的研究报告,或者我们自己通过历史数据计算出的因子IC值(信息系数)等分析结果。
这个先验知识至关重要,因为它决定了我们打分的规则:对于正向因子,数值越大得分应越高;对于负向因子,数值越小得分应越高。
🎯 本节总结

本节课我们一起学习了因子打分选股策略的概述。我们了解到,该策略通过综合多个因子对股票进行评分并排序来替代单一的因子分析,使得选股依据更为全面。同时,我们明确了实施该策略的一个必要前提:必须事先知道每个因子与收益率的相关性方向(正或负),这是后续进行正确打分的基础。在接下来的课程中,我们将进入实践环节,学习如何在平台上具体实现这一策略。
049:整体任务流程梳理 📊


在本节课中,我们将学习如何为股票因子进行打分,并梳理一个完整的量化投资策略流程。我们将通过一个具体的例子,演示如何根据多个指标对股票进行评分和排序,从而选出最具潜力的投资标的。
上一节我们介绍了如何获取和预处理股票因子数据。本节中,我们来看看如何为这些因子打分,并整合成一个可执行的策略流程。
打分方法详解
有了预处理后的因子数据,接下来需要为每只股票进行综合打分。打分的方法有很多种,我们将通过一个具体的例子来演示一种基于区间划分的常用方法。
以下是打分的基本步骤:
- 准备示例数据:假设我们有一个股票池,包含ID1、ID2、ID3……直到ID300,共300只股票。我们关注四个指标:A、B、C、D。其中,A和B是越大越好的因子(例如盈利能力),C和D是越小越好的因子(例如负债率、市值)。
- 获取因子数值:每只股票都有对应的A、B、C、D指标值。这些值通常是经过标准化处理后的结果,范围在0到1之间。例如:
- ID1: A=0.71, B=0.28, C=0.39, D=0.11
- ID2: A=0.45, B=0.17, C=0.81, D=0.02
- 划分区间并设定分值:将0到1的数值范围(或百分位)划分为若干个区间,并为每个区间赋予一个分数。区间的划分和分数设定需根据因子的好坏方向来决定。
- 对于“越大越好”的因子(A、B):数值越大的区间,分数越高。
- 区间 [0.8, 1.0] -> 5分
- 区间 [0.6, 0.8) -> 4分
- 区间 [0.4, 0.6) -> 3分
- 区间 [0.2, 0.4) -> 2分
- 区间 [0.0, 0.2) -> 1分
- 对于“越小越好”的因子(C、D):数值越小的区间,分数越高。
- 区间 [0.0, 0.2) -> 5分
- 区间 [0.2, 0.4) -> 4分
- 区间 [0.4, 0.6) -> 3分
- 区间 [0.6, 0.8) -> 2分
- 区间 [0.8, 1.0] -> 1分
- 对于“越大越好”的因子(A、B):数值越大的区间,分数越高。
- 为每只股票的各因子打分:根据每只股票的因子值落入的区间,给出对应分数。
- ID1打分:
- A=0.71 -> 落入[0.6, 0.8) -> 4分
- B=0.28 -> 落入[0.2, 0.4) -> 2分
- C=0.39 -> 落入[0.2, 0.4) -> 4分 (注意:C越小越好)
- D=0.11 -> 落入[0.0, 0.2) -> 5分 (注意:D越小越好)
- 总分 = 4 + 2 + 4 + 5 = 15分
- ID2打分:
- A=0.45 -> 落入[0.4, 0.6) -> 3分
- B=0.17 -> 落入[0.0, 0.2) -> 1分
- C=0.81 -> 落入[0.8, 1.0] -> 1分
- D=0.02 -> 落入[0.0, 0.2) -> 5分
- 总分 = 3 + 1 + 1 + 5 = 10分
- ID1打分:
- 排序与选股:计算所有300只股票的总分,然后按照总分从高到低进行排序。选择排名最高的前10只股票,作为下一次调仓时买入或重点关注的对象。
其他打分方法:除了区间划分法,也可以直接根据因子值在所有股票中的排名来打分。例如,对于“越大越好”的因子,排名第1的给300分,排名第300的给1分。核心目标是得到一个可以汇总和排序的综合分数。
这种打分法在因子测试和策略构建中非常常用,效果通常不错。
上一节我们详细介绍了打分法的计算过程。本节中,我们来看看如何将这个方法嵌入到一个完整的策略流程中。
整体策略流程梳理 🔄


以下是基于打分法构建的量化策略整体执行流程,也是我们接下来将要实现的策略框架:

- 确定股票池:首先,需要指定在哪个范围内选股。在本策略中,我们使用沪深300指数的成分股作为我们的初始股票池。
- 设置调仓周期:确定策略的调仓频率。在实际操作中,每月或每季度调仓较为常见。我们将设置一个定时器,例如每月调仓一次。
- 实现调仓函数:策略的核心是一个名为
rebalance的调仓函数。我们需要重点实现这个函数,其内部逻辑如下:- 数据获取与预处理:在调仓日,获取股票池中所有股票的最新因子数据(A, B, C, D)。根据先验知识,我们知道A、B、C因子越大越好,D、E、F因子越小越好(此处为示例,因子可增减)。
- 因子打分:使用上文介绍的区间划分法(或其他方法),为每只股票的每个因子进行独立打分。
- 计算综合得分:将每只股票的所有因子得分相加,得到该股票的综合总分。
- 生成交易信号:将所有股票按综合总分从高到低排序,选取排名前10的股票。这些股票就是本次调仓周期内要买入或持有的标的。
- 回测与评估:将上述流程在历史数据中运行,观察策略的收益、风险等指标,评估打分法的选股效果。
这个流程看起来相对清晰和简单。接下来,我们将尝试用这种打分法,结合几个示例因子,构建一个策略,并在回测中看看它能否带来超越基准的收益。


本节课中我们一起学习了量化策略中的核心环节——因子打分法。我们通过一个具体案例,演示了如何根据因子方向(越大越好/越小越好)划分区间并赋值,从而计算每只股票的综合得分。最后,我们梳理了将打分法嵌入完整策略的流程,包括确定股票池、设置调仓周期、实现调仓逻辑等步骤。这是构建多因子选股策略非常实用和基础的一步。
050:3-策略初始化与数据读取 📊
在本节课中,我们将学习如何初始化一个量化交易策略,并读取用于因子打分选股所需的财务数据。我们将从指定股票池开始,逐步完成数据查询和准备步骤。

策略初始化

首先,我们需要在策略的 context 对象中指定股票池。为了获得更好的回测效果,我们选择在沪深300指数的成分股中进行选股。

# 在context中指定股票池为沪深300
context.stock_pool = '沪深300'
接下来,我们需要定义一个变量来存储股票的排名结果,以便在调仓时使用。
# 定义一个变量来存储股票排名
context.ranked_stocks = None

设置调仓定时器


上一节我们指定了股票池,本节中我们来看看如何设置调仓频率。我们将使用一个定时器,按月执行调仓操作。
# 设置按月调仓的定时器
schedule_function(rebalance, date_rule=monthly(), time_rule=market_open())

这里,rebalance 是我们即将定义的核心调仓函数。


定义调仓函数

现在,我们来定义 rebalance 函数。这个函数的核心任务是获取股票评分并据此进行调仓。

def rebalance(context, data):
# 调用函数获取评分后的股票
ranked_stocks = get_ranked_stocks(context)
# 将排名结果存入context
context.ranked_stocks = ranked_stocks
# 后续可根据排名进行交易操作(此处省略)

数据获取与处理

调仓函数依赖于股票评分,而评分需要财务数据。因此,我们需要一个函数来获取和处理这些数据。
以下是获取财务数据的步骤,我们将分别获取“越高越好”和“越低越好”两类指标。

获取“越高越好”的指标

我们首先查询那些数值越大代表股票越好的财务指标。

def get_ranked_stocks(context):
# 构建查询语句,获取“越高越好”的指标
query_up = query(
fundamentals.eod_derivative_indicator.eps, # 每股收益 (EPS),越高越好
fundamentals.financial_indicator.roe, # 净资产收益率 (ROE),越高越好
fundamentals.financial_indicator.roa # 总资产收益率 (ROA),越高越好
).filter(
fundamentals.stockcode.in_(context.stock_pool) # 过滤股票池
)
# 执行查询并转置DataFrame,使股票代码成为行索引
df_up = get_fundamentals(query_up).T
获取“越低越好”的指标

接下来,我们查询那些数值越小代表股票越好的财务指标。


# 构建查询语句,获取“越低越好”的指标
query_down = query(
fundamentals.financial_indicator.debt_to_asset, # 资产负债率,越低越好
fundamentals.eod_derivative_indicator.pb_ratio, # 市净率 (PB),越低越好
fundamentals.eod_derivative_indicator.market_cap # 市值,越低越好(小市值效应)
).filter(
fundamentals.stockcode.in_(context.stock_pool) # 过滤股票池
)
# 执行查询并转置DataFrame
df_down = get_fundamentals(query_down).T


数据整合
现在,我们得到了两个DataFrame:df_up 和 df_down。每个DataFrame的形状大致为 (股票数量, 指标数量),例如 (300, 3)。这些数据将用于后续的因子打分计算。
# 此时,df_up 和 df_down 包含了我们所需的因子数据
# df_up: 包含每股收益、净资产收益率、总资产收益率
# df_down: 包含资产负债率、市净率、市值
# 后续步骤将基于这些数据进行评分(本节课不展开)
return df_up, df_down

总结

本节课中我们一起学习了量化策略初始化的关键步骤:
- 指定股票池:确定了在沪深300成分股内进行选股。
- 设置调仓频率:使用定时器设定了按月调仓的规则。
- 定义核心函数:构建了
rebalance调仓函数的框架。 - 获取财务数据:成功查询并获取了用于因子打分的两大类(“越高越好”和“越低越好”)财务指标数据,并进行了适当的格式处理(转置)。


我们已经准备好了策略的骨架和所需的数据,为下一节课实现具体的因子打分逻辑打下了坚实的基础。
051:4-因子打分与排序


在本节课中,我们将学习如何对多个选股因子进行打分与排序。我们将处理两类因子:越高越好的因子和越低越好的因子,通过遍历数据框、为每个因子排序、分配分数,最终合并结果以计算每只股票的总分。

遍历因子并排序
上一节我们准备好了两个数据框,分别包含“越高越好”和“越低越好”的因子。本节中,我们将分别遍历这两个数据框,对每个因子进行排序。
以下是遍历“越高越好”因子数据框的步骤:
- 获取数据框的列名列表,以遍历每一个因子。
- 对当前因子列的数据进行排序。
- 使用
inplace=True参数,使排序结果直接作用于原数据框,无需重新赋值。
# 假设 fundamental_up 是“越高越好”因子的数据框
for factor in fundamental_up.columns:
fundamental_up.sort_values(by=factor, inplace=True)
为排序后的因子分配分数

排序完成后,我们需要为每只股票在该因子上的表现打分。为简化操作,我们直接根据排名顺序分配分数:排名第一得最高分,排名最后得最低分。
我们将使用 NumPy 的 linspace 函数来生成一个等差数列作为分数。该函数的三个关键参数是:起始值、结束值和元素数量。


以下是 linspace 函数的使用示例:

import numpy as np
# 生成从1到300的300个等间隔数字
scores = np.linspace(1, 300, 300)
print(scores) # 输出: [ 1. 2. 3. ... 298. 299. 300.]


对于“越高越好”的因子,排名第一(值最大)的股票应得最高分。因此,我们使用 np.linspace(1, total_stocks, total_stocks) 来生成分数。

对于“越低越好”的因子,排名第一(值最小)的股票应得最高分。因此,我们需要生成一个从高到低的分数序列,即 np.linspace(total_stocks, 1, total_stocks)。

以下是完整的打分代码:


import numpy as np
import pandas as pd

# 假设 fundamental_up 和 fundamental_down 是两个因子数据框,total_stocks 是股票总数
total_stocks = len(fundamental_up)

# 为“越高越好”的因子打分
for factor in fundamental_up.columns:
fundamental_up.sort_values(by=factor, inplace=True)
fundamental_up[factor] = np.linspace(1, total_stocks, total_stocks)
# 为“越低越好”的因子打分
for factor in fundamental_down.columns:
fundamental_down.sort_values(by=factor, inplace=True)
fundamental_down[factor] = np.linspace(total_stocks, 1, total_stocks)

合并数据框并计算总分
现在,我们得到了两个包含各因子分数的数据框。为了计算每只股票的综合得分,需要将这两个数据框合并。
合并后,将所有因子的分数相加,即可得到每只股票的总分。这可以通过 Pandas 的 sum 方法按行(axis=1)轻松实现。
# 合并两个分数数据框
all_scores = pd.concat([fundamental_up, fundamental_down], axis=1)
# 计算每只股票的总分
total_score = all_scores.sum(axis=1)

本节课中我们一起学习了量化因子处理的完整流程:首先区分因子类型并分别排序,然后利用 np.linspace 函数根据排名分配分数,最后合并所有因子分数并计算总分。这套方法是构建多因子选股模型的基础步骤。
052:完成选股方法 🧮

在本节课中,我们将学习如何将多个股票指标数据表进行拼接,并基于这些指标计算综合得分,最终筛选出排名前十的股票。整个过程涉及数据合并、分数计算与排序等核心操作。
上一节我们介绍了如何获取并处理单个指标的数据。本节中,我们来看看如何整合所有指标,并完成最终的选股排名。
数据拼接操作
首先,我们需要将两个包含不同指标数据的 DataFrame 进行拼接。这可以通过 pandas 库的 concat 函数轻松完成。
以下是拼接两个 DataFrame 的代码:

# 假设 df1 和 df2 是两个需要拼接的 DataFrame
combined_df = pd.concat([df1, df2], axis=1)
拼接完成后,我们得到了一个包含所有指标的新 DataFrame,可以为其指定一个新的名称,例如 rank_df。
计算综合得分并排序
现在,我们有了包含所有指标的大表,但还没有综合排名信息。接下来,我们需要新建一列来计算每个股票的综合得分。
首先,初始化一个名为“得分值”的列,其值暂时全部设为0。
rank_df['得分值'] = 0
然后,我们需要实际计算这个得分值。假设我们有6个指标,综合得分就是将这6个指标的分数相加。

以下是计算总分并排序的步骤:

- 计算总分:将每个股票在所有指标上的得分进行求和。
- 进行排序:根据总分对股票进行降序排列,以找到得分最高的股票。
以下是实现代码:

# 计算总分,假设指标列名为 ‘indicator1‘, ‘indicator2‘, ... ‘indicator6‘
rank_df['总分'] = rank_df[['indicator1', 'indicator2', 'indicator3', 'indicator4', 'indicator5', 'indicator6']].sum(axis=1)
# 根据‘总分‘列进行降序排序
ranked_df = rank_df.sort_values(by='总分', ascending=False)
排序完成后,ranked_df 中的股票已经按照综合得分从高到低排列。
提取最终结果

我们最终的目标是获取排名前十的股票代码,而不是所有指标数据。
因此,我们从排序后的 DataFrame 中提取股票代码(索引)列,并转换为列表,然后取前十个元素。

以下是提取前十名股票代码的代码:

# 提取股票代码索引,并转换为列表
stock_list = ranked_df.index.tolist()

# 获取前十名股票
top_10_stocks = stock_list[:10]
这样,top_10_stocks 就是根据当前所有指标计算出的综合排名前十的股票列表。


本节课中我们一起学习了量化选股的关键步骤。我们首先将多个指标数据表拼接成一个完整的 DataFrame,然后通过求和计算每个股票的综合得分,接着对得分进行降序排序以确定排名,最后提取出排名前十的股票代码。这个过程构成了一个完整的、基于多因子打分的选股方法核心逻辑。
053:6-完成策略交易展示结果 📊

在本节课中,我们将要学习如何完成一个量化交易策略的核心部分:根据新的股票排名结果,计算需要买入和卖出的股票,并执行相应的交易操作。我们将一步步构建一个完整的调仓函数。


概述
上一节我们介绍了如何获取并筛选股票数据,本节中我们来看看如何基于新的排名结果,与当前持仓进行比较,从而生成具体的买卖指令,完成策略的闭环。
获取当前持仓
首先,我们需要一个函数来获取当前投资组合中持有的所有股票。以下是实现该功能的步骤:

以下是获取当前持仓的代码实现:
def get_holdings(context):
holdings = []
for position in context.portfolio.positions:
if context.portfolio.positions[position].quantity > 0:
holdings.append(position)
return holdings
这段代码遍历投资组合中的所有持仓,检查其数量是否大于零,如果是,则将该股票代码加入持仓列表。


计算买卖清单
获取了当前持仓后,下一步是将其与新的股票排名结果进行比较,计算出需要买入和卖出的股票清单。
以下是计算买卖清单的逻辑:
- 计算买入清单 (
to_buy):新的股票排名 (stocks) 减去当前持仓 (holdings)。这代表了排名靠前但尚未持有的股票。to_buy = set(stocks) - set(holdings)
- 计算卖出清单 (
to_sell):当前持仓 (holdings) 减去新的股票排名 (stocks)。这代表了已持有但已不在新排名中的股票。to_sell = set(holdings) - set(stocks)
执行交易操作

计算出买卖清单后,接下来需要执行具体的交易指令。通常的策略是先卖出,再买入。

以下是执行交易的步骤:


- 执行卖出操作:遍历卖出清单,将每只股票的持仓比例调整为0%,即清仓。
for stock in to_sell: order_target_percent(stock, 0) - 执行买入操作:首先检查买入清单是否为空。如果不为空,则计算可用于投资的现金,并平均分配到每只要买入的股票上。
- 计算可用资金:
cash = context.portfolio.cash - 计算每只股票应分配的资金比例:
average_percent = 1.0 / len(to_buy) - 遍历买入清单,为每只股票下单。
if len(to_buy) > 0: average_percent = 1.0 / len(to_buy) for stock in to_buy: order_target_percent(stock, average_percent) - 计算可用资金:


整合与调试

现在,我们将上述步骤整合到每月调仓的主函数中。同时,需要注意代码中的细节,例如标点符号必须使用英文格式,以及变量类型转换(如将列表转换为集合进行差集运算)。


以下是整合后的核心流程回顾:

- 每月初,获取新的股票排名 (
stocks)。 - 调用
get_holdings函数获取当前持仓 (holdings)。 - 通过集合运算,计算出
to_buy和to_sell。 - 先执行卖出操作,再执行买入操作。


运行代码时可能会遇到错误,常见的包括:
- 无效字符错误:检查代码中是否误用了中文标点(如逗号、括号)。
- 类型错误:确保在进行集合差集运算(
-)前,已将列表转换为集合(set())。

通过逐步调试和修正这些错误,最终使策略能够顺利运行。

总结

本节课中我们一起学习了如何完成一个量化交易策略的交易执行部分。我们掌握了三个关键环节:获取当前持仓、通过比较新旧排名计算买卖清单、以及执行具体的买入和卖出订单。这个过程是策略从信号生成到实际交易的关键一步,确保了策略逻辑的闭环。记住,在编写代码时,注意细节和调试是成功实现策略的重要保障。
054:策略总结与分析 📊



在本节课中,我们将对一个基于财务指标的打分选股策略进行回测总结与分析。我们将通过调整回测时间段和选股范围,来观察策略在不同市场环境下的表现,并探讨其有效性与局限性。

回测执行与初步观察

上一节我们完成了策略的代码编写,本节中我们来看看它的实际运行效果。代码执行后没有报错,表明程序逻辑基本正确。虽然存在一些警告信息,但通常不影响核心回测流程。


我们首先观察一个较短时间段的回测结果。基准收益表现不佳,处于亏损状态。相比之下,我们的策略虽然盈利不多,但成功稳住了净值。策略的最大回撤控制在17%左右,这是一个可以接受的数值。更重要的是,策略相对于基准的超额收益维持在67%左右,初步看来效果不错。


延长回测时间以检验稳健性


然而,短期回测结果可能具有偶然性。为了更全面地评估策略,我们需要进行更长时间的回测。在量化投资中,回测周期通常为3到5年,几个月的时间往往不足以说明问题。


以下是调整回测时间窗口的尝试与观察:
- 2016年至2018年:将回测时间设置为2016年1月4日至2018年1月4日。结果显示,策略收益与基准收益相差无几,没有展现出明显的优势。
- 2018年至2020年:将回测时间调整为2018年1月4日至2020年1月4日。策略的逻辑本身没有问题,所有预设的指标都已计算并应用。但在此区间内,策略效果依然不显著:基准收益约为0.xx%,策略收益约为2.xx%,提升有限。这可能与2018年股市整体处于熊市有关。
- 2016年至2017年:为了避开2018年的熊市影响,我们将回测时间改为2016年1月4日至2017年1月4日。在这段市场相对较好的时期,策略表现有所改善,回测收益明显高于基准收益。


探索策略的选股逻辑有效性


在观察了不同时间段的表现后,我们进一步探究策略选股逻辑本身是否有效。我们的策略原本是选取综合打分排名前十的股票。


一个自然的疑问是:如果选择排名靠后的股票,结果会怎样?
我们将选股范围从排名前10改为从第10名之后(即排名靠后的股票),重新进行回测。


结果对比非常清晰:
- 当选择排名前十的股票时,策略年化收益约为10.xx%。
- 当选择排名靠后的股票时,策略收益为负,约为-10.xx%,表现甚至比基准收益更差。


这个对比实验有力地证明了我们的打分指标是有效的:得分高的股票组合确实能跑赢市场,而得分低的股票组合则跑输市场。


策略细节与报告解读


除了收益曲线,回测系统还提供了丰富的细节报告,帮助我们深入理解策略的运行。


以下是报告中的关键部分:
- 交易详情:报告显示策略在4月、5月、6月直至11月都进行了调仓,这验证了我们的策略是按月定期调仓的。
- 持仓详情:展示了每个调仓日具体的股票持仓列表和权重。
- 账户信息:记录了初始资金和最终资产的变化。例如,一次回测中,100万初始资金最终变为约77万,直观地反映了该时间段内的亏损情况。

课程总结与策略评价


本节课中,我们一起学习了如何对一个量化策略进行全面的回测分析与总结。

我们基于研报中提到的“指标越高越好”或“越低越好”的规则,构建了一个简单的打分选股策略。通过实践,我们发现:
- 打分法是一种直观且常用的多因子选股方法,能够相对直接地获得一个还不错的结果。
- 市场环境对策略表现有巨大影响。在熊市中,任何策略都可能表现平平甚至亏损,这是由市场系统性风险决定的常见现象。
- 回测需要多周期验证。不能仅凭一个短期的优秀回测结果就认定策略有效,必须在不同的市场阶段(牛市、熊市、震荡市)进行检验。
- 反向测试验证逻辑。通过买入低分股票组合表现更差这一结果,反过来证明了我们选股逻辑的有效性。


最终,我们将代码中的选股范围恢复为默认的“前10名”,以确保策略能取得相对较好的效果。这个策略案例为大家提供了一个起点,可以在此基础上调整指标、权重或风控规则,进一步探索和完善自己的量化交易体系。
055:聚类分析实例 📊

在本节课中,我们将学习如何在量化交易中使用聚类分析。我们将通过一个具体的实例,演示如何利用K-Means算法对股票数据进行聚类,并尝试基于聚类结果构建一个简单的交易策略。


第一步:导入工具包

首先,我们需要导入所有必要的Python工具包。这些工具包与之前课程中使用的保持一致。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans


第二步:加载与准备数据
上一节我们导入了工具包,本节中我们来看看如何准备数据。我们使用一份股票数据,并从中提取两个特征用于建模。


我们选择的两个特征是:
- 股票前一天的收益率。
- 股票前两天的收益率。
这两个指标将作为我们聚类模型的输入特征。


# 假设 `df` 是已经加载的包含股票历史数据的DataFrame
# 这里我们创建两个特征列
data = df[['returns_lag1', 'returns_lag2']].dropna()


第三步:建立K-Means聚类模型
有了特征数据后,我们就可以开始建模了。这次的任务与之前的回归分析不同,我们将使用聚类方法。
在Scikit-learn库中,有一个非常实用的聚类算法叫做K-Means。以下是建立模型的步骤:
- 导入模块:从
sklearn.cluster中导入KMeans。 - 实例化模型:创建一个K-Means模型实例。我们需要指定一个关键参数
n_clusters,即我们希望将数据分成多少个簇(K值)。 - 拟合模型:使用我们的特征数据来训练模型。

在我们的任务中,目标相对简单:我们希望判断股票是上涨还是下跌。因此,我们将簇的数量设定为2。

# 1. 实例化K-Means模型,设定簇的数量为2
model = KMeans(n_clusters=2, random_state=42)
# 2. 使用特征数据拟合模型
model.fit(data)

模型拟合完成后,会显示一些默认参数,例如初始化中心点的方法、最大迭代次数等。K-Means算法具有随机性,因此Scikit-learn默认会运行多次(n_init参数控制)并选择最佳结果。

第四步:预测与结果处理
模型建立后,我们可以用它来预测现有数据点所属的簇。
以下是预测和结果处理的步骤:
- 使用训练好的模型对数据进行预测,得到每个样本的簇标签(0或1)。
- 为了方便理解,我们可以将簇标签进行映射。例如,将原本的
0映射为-1(代表下跌),将1映射为1(代表上涨)。

# 对数据进行预测,得到簇标签
cluster_labels = model.predict(data)
# 将簇标签映射为更直观的涨跌信号(例如:0 -> -1, 1 -> 1)
# 注意:这里需要根据实际聚类中心的意义来判断映射关系,可能需要调整
data['cluster_signal'] = np.where(cluster_labels == 1, 1, -1)

现在,数据框中新增了一列cluster_signal,其值为1或-1,代表模型对该数据点所属类别的判断。



第五步:可视化聚类结果

为了直观地观察聚类效果,我们可以将数据点绘制在散点图上,并用不同颜色区分不同的簇。
以下是绘制散点图的代码:


plt.figure(figsize=(10, 6))
# 绘制散点图,x轴为‘returns_lag1’, y轴为‘returns_lag2’
# 颜色‘c’根据簇标签‘cluster_signal’区分
plt.scatter(data['returns_lag1'], data['returns_lag2'], c=data['cluster_signal'], cmap='bwr', alpha=0.6)
plt.xlabel('Returns (t-1)')
plt.ylabel('Returns (t-2)')
plt.title('Stock Returns Clustering with K-Means')
plt.colorbar(label='Cluster Signal (1: Up, -1: Down)')
plt.show()
在生成的图中,蓝色和红色的点分别代表两个不同的簇。我们可以观察数据点的分布,例如,当两个滞后收益率都为负值时(左下角区域),数据点可能更多地被划分到代表“下跌”的簇中。

第六步:评估策略效果

仅仅完成聚类还不够,我们需要评估基于此聚类结果的简单策略是否有效。

我们将进行一个简单的回测:
- 假设我们根据
cluster_signal进行交易:信号为1时买入(或做多),信号为-1时卖出(或做空)。 - 计算在此策略下的累计收益,并与基准(如买入并持有)进行比较。

注意:由于K-Means的随机性,每次运行的结果可能略有不同,因此评估时需要谨慎,可能需要多次运行或使用更稳健的评估方法。


# 计算策略收益:假设信号直接作为每日收益率的方向
data['strategy_returns'] = data['cluster_signal'] * data['actual_returns'] # 需要实际的‘actual_returns’列

# 计算累计收益
cumulative_strategy_returns = (1 + data['strategy_returns']).cumprod()
cumulative_baseline_returns = (1 + data['actual_returns']).cumprod()

# 绘制收益曲线对比
plt.figure(figsize=(12, 6))
plt.plot(cumulative_strategy_returns, label='Clustering Strategy')
plt.plot(cumulative_baseline_returns, label='Buy & Hold', linestyle='--')
plt.xlabel('Time')
plt.ylabel('Cumulative Returns')
plt.title('Strategy Backtest')
plt.legend()
plt.grid(True)
plt.show()

在实际运行中,聚类策略的效果可能时好时坏。这正说明了聚类分析在量化交易中的局限性:它是一种无监督学习方法,缺乏明确的标签(涨/跌)来指导优化,因此结果难以评估和稳定复现。


总结

本节课中我们一起学习了如何在量化交易中应用K-Means聚类分析。我们完成了从数据准备、模型构建、结果预测到可视化和简单回测的全过程。


核心要点总结:
- 方法:使用
KMeans(n_clusters=2)对股票滞后收益率特征进行聚类。 - 过程:包括数据导入、特征工程、模型拟合(
model.fit)、预测(model.predict)和结果映射。 - 可视化:通过散点图直观展示聚类效果。
- 评估:基于聚类信号构建简单交易策略并进行回测,但结果受随机性影响较大。

需要强调的是,聚类分析作为一种无监督学习,在缺乏先验标签的金融预测中通常不是首选方法,因为它难以进行严格的性能评估和逻辑解释。本实例主要用于演示技术流程,在实际应用中需要结合更多有监督学习方法和严谨的风险控制。
056:2-统计分析所需数据准备 📊
在本节课中,我们将学习如何为统计分析准备数据。我们将基于股票价格的前两天和前一天的变化情况,来统计第三天涨跌的概率。这种方法虽然传统,但能帮助我们熟悉在Python中处理和分析数据的基本流程。
数据准备思路
上一节我们介绍了数据分析的基本概念,本节中我们来看看如何具体实现统计分析所需的数据准备。核心思路是:基于历史数据,统计在“前两天”和“前一天”的特定涨跌组合下,第三天出现“涨”、“跌”或“不变”的频率。


创建涨跌判断函数

首先,我们需要一个函数来判断每个交易日的价格变化是涨(标记为1)还是跌(标记为0)。以下是实现该功能的函数:


def create_binary_signal(data, base=0):
global col_names
col_names = []
for col in [‘lag1‘, ‘lag2‘]:
new_col_name = col + ‘_binary‘
data[new_col_name] = np.where(data[col] > base, 1, 0)
col_names.append(new_col_name)



这个函数遍历代表“前一天”(lag1)和“前两天”(lag2)的列。对于每一列,它创建一个新的列名(在原列名后加“_binary”),并使用np.where函数进行判断:如果值大于base(默认为0),则标记为1(涨),否则标记为0(跌)。结果会存储在新的DataFrame列中。


应用函数并查看结果

执行上述函数后,我们可以查看处理后的数据。以下是查看数据前几行的代码:

create_binary_signal(data)
print(data[[‘lag1_binary‘, ‘lag2_binary‘]].head())
执行后,数据中会新增两列,例如:
lag1_binary = 1表示前一天上涨。lag2_binary = 0表示前两天下跌。

这样,我们就将连续的价格变化数据转换成了离散的涨跌信号,便于后续的统计分析。

整合实际涨跌方向


为了进行统计,我们还需要第三天的实际涨跌情况。原始数据中通常有一列名为direction,表示价格的实际变动方向(例如:1代表涨,-1代表跌,0代表不变)。我们需要将这列数据整合进来。

现在,我们的数据中包含了:
lag1_binary: 前一天的涨跌信号。lag2_binary: 前两天的涨跌信号。direction: 当天的实际涨跌方向。

接下来,我们将基于这三个字段进行分组统计。


使用GroupBy进行分组统计
在Pandas中,groupby方法是进行分组统计的强大工具。我们将使用它来统计在各种“前两天+前一天”的信号组合下,第三天实际出现各种方向的次数。


以下是执行分组统计的代码:


grouped = data.groupby([‘lag1_binary‘, ‘lag2_binary‘, ‘direction‘])
result = grouped.size()
print(result)


这段代码的含义是:按照lag1_binary(前一天信号)、lag2_binary(前两天信号)和direction(实际方向)这三个列进行分组,然后计算每个唯一组合在数据中出现的次数(size)。


执行后,我们会得到一个类似如下的结果(示例):
(0, 0, -1): 120表示前两天跌、前一天跌、当天实际也跌的情况出现了120次。(0, 1, 1): 85表示前两天涨、前一天跌、当天实际涨的情况出现了85次。

这个结果清晰地展示了历史数据中各种情况发生的频次,为后续计算概率奠定了基础。

总结

本节课中我们一起学习了为统计分析准备数据的关键步骤。我们首先创建了一个函数,将连续的价格变化转换为二进制的涨跌信号。然后,我们整合了实际的涨跌方向数据。最后,利用Pandas的groupby方法,我们成功地统计了在各种历史信号组合下,后续涨跌情况出现的频率。这些处理好的数据是进行概率分析和制定简单交易策略的基础。
057:3-统计效果展示 📊

在本节课中,我们将学习如何对股票数据进行统计分析,以探索不同特征组合下股价涨跌的规律。我们将使用groupby方法进行分组统计,并根据统计结果制定一个简单的交易策略,最后评估该策略的收益表现。


上一节我们介绍了数据的基本处理,本节中我们来看看如何对处理后的数据进行统计分析。

首先,我们需要对数据进行变换,以便观察不同特征组合下的结果分布。具体来说,我们关注“前一天涨跌”和“前两天涨跌”这两个特征,以及它们对应的“当天涨跌”结果。

以下是数据变换的步骤:
- 使用
groupby方法,按“前一天涨跌”和“前两天涨跌”这两个特征进行分组。 - 对分组后的“当天涨跌”结果列使用
.size()方法进行计数。 - 使用
.unstack()方法将结果重塑为更易读的表格形式。 - 使用
fill_value=0参数填充表格中可能出现的空值。

# 对特征组合进行分组统计
grouped_result = data.groupby(['feature_day-1', 'feature_day-2'])['direction'].size().unstack(fill_value=0)

执行上述代码后,我们得到一个统计表格。表格的行和列分别代表“前一天涨跌”和“前两天涨跌”的特征值(例如,1代表涨,-1代表跌),表格中的数字则代表了在该特征组合下,“当天涨跌”结果出现的次数。

基于上一步得到的统计表格,我们可以观察不同特征组合下,哪种结果(涨或跌)出现的次数更多。这构成了我们制定策略的基础。
以下是策略制定的逻辑:
- 观察统计表格,找出每个特征组合下出现次数最多的结果。
- 根据观察,我们发现当“前一天涨”且“前两天也涨”(即两个特征值之和等于2)时,第三天“跌”的可能性更大。
- 在其他特征组合下,“涨”的可能性更大。
根据这个观察,我们制定一个简单的策略规则:
- 如果
(前一天涨跌 + 前两天涨跌) == 2,则预测当天为“跌”(用-1表示)。 - 否则,预测当天为“涨”(用1表示)。
# 根据统计规律生成预测信号
data['position_stat'] = np.where((data['feature_day-1'] + data['feature_day-2']) == 2, -1, 1)

上一节我们根据统计规律生成了预测信号,本节中我们来看看这个策略的实际效果如何。
我们将策略预测的涨跌方向position_stat与实际的涨跌方向direction进行比较,计算预测正确的天数。

# 计算预测正确的次数
correct_predictions = (data['position_stat'] == data['direction']).value_counts()
print(correct_predictions)

结果显示,在总共的样本天数中,预测正确的次数略多于预测错误的次数。这表明基于简单统计的策略具有一定的预测能力,但并非完全准确。


最后,我们来评估这个统计策略在模拟交易中的收益表现。我们将基于策略信号进行买卖,并计算其累计收益率,同时与单纯跟随大盘走势的收益率进行对比。


以下是计算收益的步骤:
- 策略收益率:使用策略生成的
position_stat信号(1代表买入/持有,-1代表卖出/做空)乘以股票的实际日收益率。 - 基准收益率:假设始终持有股票(即信号始终为1),计算其累计收益率。
- 比较两种收益率的最终结果。
# 计算策略收益率
strategy_returns = data['returns'] * data['position_stat'].shift(1)
cumulative_strategy_returns = (1 + strategy_returns).cumprod()

# 计算基准(买入持有)收益率
benchmark_returns = data['returns']
cumulative_benchmark_returns = (1 + benchmark_returns).cumprod()


# 输出最终累计收益率
print(f"策略最终净值: {cumulative_strategy_returns.iloc[-1]:.4f}")
print(f"基准最终净值: {cumulative_benchmark_returns.iloc[-1]:.4f}")


结果显示,基于统计的策略其最终收益率可能仍然为负(即亏损),但亏损幅度小于单纯跟随大盘走势的基准策略。这说明该简单统计策略起到了一定的风险控制作用,但未能实现盈利。



本节课中我们一起学习了如何对股票数据进行统计分析并制定交易策略。我们首先使用groupby方法统计了不同特征组合下的结果分布,然后根据“少数服从多数”的原则制定了一个简单的预测规则。最后,我们通过回测发现,这个基于统计的策略虽然比盲目跟随大盘表现稍好,能够减少部分亏损,但总体上并不可靠。本课的主要目的是演示数据分析的基本流程和groupby等工具的使用方法,以及理解简单统计策略的构建思路与局限性。
058:1-KMEANS算法概述

在本节课中,我们将要学习机器学习中一个非常重要的分支——聚类。我们将重点介绍最经典、最常用的聚类算法之一:K-Means算法。我们会从聚类的基本概念讲起,逐步深入到K-Means算法的核心思想、关键概念和优化目标,力求让初学者能够轻松理解。
聚类与无监督学习
上一节我们介绍了机器学习的基本框架,本节中我们来看看聚类任务。聚类与我们之前讨论的分类任务不同。分类属于有监督学习,我们手里有每个数据对应的类别标签,目标是优化一个目标函数来预测新数据的标签。
现在问题难度增加了,我们面对的是无监督学习问题。这意味着我们手里没有每个数据属于哪个类别的标签。聚类是无监督学习中最典型的代表。聚类的目标与分类类似,都是将看似杂乱的数据分成若干组,即把相似的东西分到一组。
例如,观察右侧的图表。在原始数据集中,并没有用颜色标记不同的组,也没有告诉我们哪些点属于绿色、红色或蓝色组。我们需要根据一种相似度度量的方式,将相似的点归到一起。根据数据分布的不同,我们将相似的点聚集在一起,最终得到三个不同的类别:绿色、红色和蓝色。
聚类算法从原理上看似乎比较简单,但它仍然面临许多困难。一个主要难点在于评估。在有监督学习中,我们可以通过预测值与真实标签的差异,使用交叉验证、准确率、召回率、F1值等多种标准进行评估。但在无监督学习中,由于没有标签,我们失去了“标准答案”,因此评估聚类结果的好坏变得非常困难。

另一个难点是参数调节。例如,使用第一组参数得到一个聚类结果,使用第二组参数得到另一个结果。哪个结果更好?由于没有标签作为评判标准,这个问题同样难以回答。评估和调参是聚类任务中的主要挑战。
接下来,我们将介绍两个经典的聚类算法。首先来看应用最广泛、最简单的K-Means算法。

K-Means算法核心概念

K-Means算法是聚类算法中最简单且最实用的算法之一。首先,我们需要明确K-Means算法的一个基本要求:必须指定一个K值。
K值代表我们希望将数据聚成多少个簇。例如,在上方的图表中,设定K=3,算法最终会将所有数据聚成三个簇(绿色、红色和蓝色)。如果设定K=4,则会聚成四个簇,以此类推。因此,在使用K-Means算法前,你需要明确告诉算法希望得到多少个簇。指定K值是K-Means算法中最重要的一个步骤。

下一个核心概念是质心。质心是K-Means算法中一个非常重要的概念。质心本质上就是一个簇中所有数据点的平均值。想象一下,我们把一堆数据聚成了一个簇,这个簇应该有一个中心位置,即均值点。


对于一个二维数据(X轴和Y轴),质心的计算方法是:分别对簇内所有数据点的X坐标取平均值,再对Y坐标取平均值,得到的点(X_mean, Y_mean)就是该簇的质心。
例如,观察绿色的簇,其质心大致位于该簇中心的位置。红色簇和蓝色簇同样可以计算出各自的质心。每个簇都需要计算其质心。质心在后续的算法迭代过程中会起到关键作用,大家需要先记住这个概念。

距离度量与数据标准化
我们提到聚类要把相似的东西分到一组,那么如何判断两个东西是否相似呢?这需要基于距离进行计算。
距离的含义是衡量两个样本点之间的差异程度。例如,左上角有两个样本点,要判断它们有多相似,可以计算它们之间的欧式距离。欧式距离是最常见的计算方式,公式为:distance = sqrt((x1 - x2)^2 + (y1 - y2)^2)。这是一种非常经典的距离计算方式。
欧式距离是其中一种计算方式,我们还可以计算曼哈顿距离等其他相似度度量方法。但对于K-Means算法,普遍使用欧式距离作为评估标准。
这里存在一个问题:当我们使用欧式距离时,通常需要先对数据进行标准化处理。什么是标准化?假设我们有两个维度:X轴和Y轴。X轴上的数据值都很小(例如0.01, 0.04, 0.03),而Y轴上的数据值都很大(例如105, 161, 267)。在计算相似度时,X轴上的差异对总距离的贡献会非常小,而Y轴上的差异贡献会非常大。这会导致我们潜意识里认为相似度主要由Y轴决定,X轴几乎不起作用。
因此,在使用距离度量时,基本上所有情况下都需要对特征数据进行标准化处理。标准化(例如归一化)的目的是让不同特征(如X轴和Y轴)的数值范围处于同一量级,比如都缩放到0到1之间或-1到1之间。这样,每个特征对距离计算的贡献是均衡的。数据标准化是使用距离度量前几乎必做的一项准备工作。

K-Means的优化目标
与机器学习的通用套路一样,K-Means算法也需要一个优化目标。我们通常通过优化一个目标函数,并不断迭代来求解问题。


K-Means算法的优化目标是什么呢?最外层是求和符号 Σ(从i=1到K)。这里的K代表我们设定的簇的数量(例如K=3)。这意味着我们要对每一个簇分别进行优化。
对于每一个簇(例如绿色的簇),内部优化的是簇内每一个样本点到该簇质心距离的总和。我们希望这个总和越小越好。用公式可以表示为:
目标:最小化 Σ_{i=1}^{K} Σ_{x∈C_i} ||x - μ_i||^2
其中:
C_i代表第i个簇。μ_i代表第i个簇的质心。||x - μ_i||^2代表样本点x到其质心μ_i的欧式距离的平方。
为什么希望这个距离总和越小越好呢?因为距离越小,说明簇内的样本点彼此越相似。我们聚类的目的正是把相似的东西归到一组。我们不希望出现这样的情况:一个本应属于红色簇的点被错误地划分到了绿色簇,导致这个点到绿色质心的距离非常大。在算法迭代优化过程中,算法会发现这个点离红色质心更近,从而将其重新划分到红色簇中。

这就是K-Means算法的优化目标:使得每个簇内所有样本点到其质心的距离之和最小化。可以看出,K-Means算法的基本概念非常直观,没有复杂的数学公式,我们可以用通俗的方式来理解它。

本节课中我们一起学习了聚类的基本概念以及K-Means算法的核心思想。我们了解到聚类是无监督学习任务,需要指定簇的数量K,并依赖于距离度量和质心迭代来优化分组结果。理解这些基础概念是掌握更复杂聚类算法的第一步。
059:2-KMEANS工作流程 🧠

在本节课中,我们将学习K-Means聚类算法的工作流程。我们将通过图解的方式,一步步理解这个无监督学习算法是如何将数据点自动分组的。整个过程包括初始化、分配、更新和迭代,直到结果稳定。
原始数据与初始化
首先,我们有一组原始数据点。由于这是一个无监督学习问题,我们事先并不知道每个点应该属于哪个组(簇)。
假设我们使用K-Means算法,并指定参数 K=2。这意味着我们希望将数据分成两个簇。算法开始时,会随机初始化两个点作为初始的“质心”,例如一个红色质心和一个蓝色质心。
分配数据点到簇
初始化质心后,算法需要决定每个数据点属于哪个簇。判断依据是距离:计算每个点到两个质心的距离。
对于一个绿色的数据点,我们计算它到红色质心的距离 D1 和到蓝色质心的距离 D2。如果 D1 < D2,我们就认为该点属于红色簇,因为距离越小,相似度越高。
公式: 对于点 P 和质心 C_i,计算欧氏距离 distance(P, C_i) = sqrt((x_p - x_c)^2 + (y_p - y_c)^2)。
算法会遍历所有样本点,为每个点计算到各个质心的距离,并将其分配给距离最近的质心所在的簇。完成这一步后,所有点都被临时标记为红色或蓝色。
更新质心位置
第一次分配完成后,我们得到了两堆点:一堆红色,一堆蓝色。但此时的分组效果可能并不理想,因为初始质心是随机选择的。
因此,我们需要更新质心。更新方法是:对于每个簇(如红色簇),取出该簇所有点的坐标,计算这些点的平均值(均值点),这个新的均值点就成为该簇的新质心。
公式: 新质心 C_new = (1/n) * Σ (P_i),其中 n 是该簇中点的数量,P_i 是每个点的坐标。
更新后,红色质心和蓝色质心会移动到新的位置,更接近各自簇的中心。
重新分配与迭代
质心更新后,算法需要重新进行分配。所有数据点需要再次计算到新质心的距离,并根据距离重新划分簇归属。
例如,某个原本属于红色簇的点,在新质心下可能离蓝色质心更近,那么它就会被重新分配到蓝色簇。

这个过程(分配 -> 更新质心 -> 重新分配)会不断重复迭代。

收敛与停止

算法持续迭代,直到满足停止条件。最常见的停止条件是:连续两次迭代之间,所有数据点的簇归属不再发生变化,或者质心的移动距离小于一个很小的阈值。
此时,我们认为算法已经收敛,得到了稳定的聚类结果。
K值的影响

以上我们以 K=2 为例。如果 K=3,算法就会初始化三个质心(例如红、蓝、黄),最终将数据分成三个簇。K值决定了最终簇的数量。

算法优缺点总结
上一节我们介绍了K-Means的工作流程,本节中我们来看看它的优点和缺点。
优点
以下是K-Means算法的主要优点:
- 原理简单易懂:流程直观,易于理解和实现。
- 执行效率较高:对于常规数据集,收敛速度较快。
- 应用广泛:是聚类分析中最常用的算法之一。
缺点
以下是K-Means算法的主要缺点:
- 需要预先指定K值:在实际应用中,确定最佳的簇数量K通常很困难,需要多次尝试或借助其他方法(如肘部法则)。
- 对初始值敏感:不同的随机初始质心可能导致不同的最终结果。
- 对异常值敏感:极端值(离群点)会显著影响质心的计算。
- 只能发现球状簇:算法基于距离度量,难以有效识别非凸形状(如环形、交叉形)的簇。对于这类“奇形怪状”或相互缠绕的数据分布,K-Means的聚类效果往往不佳。
- 复杂度与样本量相关:每次迭代都需要计算所有样本点到所有质心的距离,当样本量极大时(如数千万),计算复杂度会变得很高。



本节课中,我们一起学习了K-Means聚类算法的工作流程。我们从初始化质心开始,经历了分配数据点、更新质心和迭代收敛的完整过程。同时,我们也分析了该算法简单高效、易于理解的优点,以及需要预设K值、难以处理复杂形状簇等缺点。理解这些核心概念和流程,是掌握和应用K-Means算法的基础。
060:迭代过程可视化展示 🎨
在本节课中,我们将通过一个交互式网页工具,直观地观察K-Means聚类算法在迭代过程中的动态变化。我们将重点关注算法的核心流程、初始值对结果的影响,以及算法在不同数据分布下的表现。
概述

K-Means算法是一种基于距离的经典聚类方法。其核心思想是:通过迭代更新簇中心点,将数据点划分到距离最近的簇中。我们将通过可视化演示,深入理解其工作流程和关键特性。
算法流程回顾与可视化

上一节我们介绍了K-Means算法的基本概念和数学原理。本节中,我们来看看它在实际运行中的动态效果。

我们使用一个网页版K-Means可视化工具进行演示。该工具可以清晰地展示算法每一步的聚类状态。


工具的网址已附在资料中,大家可以自行访问体验K-Means以及DBSCAN等算法的可视化效果。

以下是使用该工具的基本步骤:
- 选择一个数据集。
- 在选项中设置初始簇中心(K值)。
- 观察迭代过程中簇的分配与中心点的更新。
初始值敏感性的演示
我们首先观察一个简单的三簇数据集。算法第一步是随机选择三个点作为初始簇中心。

从图中可见,我们随机添加了三个点。

点击“Go”开始第一次迭代。算法计算每个点到三个中心的距离,并将其分配到最近的簇。图中用不同颜色区分了归属。
接着,点击“Update”更新中心点。新中心点是当前簇内所有点的均值。中心点位置发生了变化。
然后,再次点击“Go”,根据新的中心点重新分配所有数据点。原来的一些红色点可能变成了绿色。
不断重复“Update”和“Go”的步骤。然而,最终聚类结果并未将数据正确地分为三堆(上、左、右),而是分成了两堆。算法达到饱和,不再更新。

这个例子证明了K-Means算法对初始值非常敏感。糟糕的初始值会导致不理想的最终结果。
因此,在实际使用K-Means时,通常需要运行多次(例如10次),然后取平均结果或选择最优的一次,因为每次随机初始化的结果都可能不同。这是K-Means算法的一个主要问题。
我们重新开始(Restart),尝试另一组初始值。

选择另一组三个初始化点,点击“Go”和“Update”进行迭代。
这次,算法成功地将数据点分成了三个清晰的簇。



再次重启,尝试第三组初始值。

添加三个点并进行迭代,同样能得到正确的聚类结果。


由此可见,不同的初始点会对聚类结果产生较大影响。虽然多数情况下能得到正确聚类,但效果仍会因初始值不同而变化。

复杂形状数据的聚类挑战
接下来,我们观察算法在面对不规则形状数据时的表现。我们选择一个“笑脸”形状的数据集。


这个笑脸大致可以分为四个部分:外圈、两个眼睛和嘴巴。因此,我们设置K=4。


添加四个初始中心点。



进行迭代。结果并不理想。算法基于距离聚类,未能按语义(外圈、眼睛、嘴)划分,而是得到了另一种分割方式。

这与初始值无关。我们更换初始点重新运行。


无论初始点如何变化,对于这个数据集,聚类结果都类似,只是簇的“颜色”分配像旋转了一样发生变化。
这说明,对于某些不规则分布的数据,基于距离的K-Means算法效果可能不佳。

K值选择的难题
最后,我们探讨K值的选择问题。观察另一个数据集。


对于这个数据集,簇的数量(K值)难以确定。分为3、4、5、6甚至8个簇似乎都有道理。
首先尝试K=3。

迭代后得到这样的划分,看起来有一定意义。


然后尝试K=6。



迭代后也能划分出有意义的簇。

分成8个簇似乎也没有太大问题。
因此,对于此类任务,很难预先指定一个“正确”的K值。例如,我们指定K=3,期望分成左、中、右三部分,但算法基于距离的划分结果与我们的预期不同。


算法很快达到饱和。


更换初始点再次运行K=3,得到的是另一种划分结果。


这再次印证了K-Means对初始值的敏感性。不同初值会导致完全不同的聚类结果。
因此,在使用K-Means时,必须考虑其稳定性差的特点,通常需要运行多次实验并综合评估(例如,对每个点多次运行后的归属进行统计平均)。

总结
本节课中,我们一起学习了K-Means算法的可视化迭代过程。通过演示,我们清晰地看到了以下关键点:
- 算法流程:包括初始化中心点、分配数据点、更新中心点的循环过程。
- 初始值敏感性:不同的随机初始点可能导致截然不同的聚类结果,这是K-Means的主要缺点之一。实践中需要通过多次运行来缓解。
- 数据分布限制:对于非球形、不规则形状的簇,基于欧氏距离的K-Means可能无法有效识别。
- K值选择:聚类数量K需要预先指定,但对于许多真实数据集,确定合适的K值本身就是一个挑战。
这个可视化工具生动地展示了K-Means的优缺点,有助于我们更深刻地理解其应用场景和局限性。


以上就是对K-Means算法可视化效果的观察。



该工具还包含其他有趣的算法演示。

以及K-Means的英文介绍,内容与我们所讲类似。这个工具的可视化效果非常出色,推荐大家亲自尝试。
061:DBSCAN密度聚类算法 🎯

在本节课中,我们将要学习一种非常强大且实用的聚类算法——DBSCAN。与上一节介绍的K-Means算法不同,DBSCAN是一种基于密度的聚类方法,它能够自动发现任意形状的簇,并且能够有效识别噪声点。我们将从核心概念入手,逐步理解其工作原理和优势。
核心概念
DBSCAN的全称是“基于密度的带有噪声点的空间聚类应用”。它的核心思想是:一个簇是由密度足够高的区域组成的,这些区域被密度较低的区域分隔开。
核心参数
DBSCAN算法主要需要指定两个参数:
- 半径 (R):用于定义每个点的邻域范围。
- 最小点数 (MinPts):在指定半径的邻域内,成为一个“核心对象”所需的最少点数。
核心对象
如果一个点在以它为圆心、半径为R的圆形邻域内,包含的其他点的数量不小于我们设定的阈值MinPts,那么这个点就被称为核心对象。
用公式可以表示为:对于一个点P,如果满足 Neighborhood(P, R) 的点数 >= MinPts,则P是核心对象。
直接密度可达
如果点P在核心对象Q的R邻域内,那么称点P是从点Q直接密度可达的。这要求Q必须是一个核心对象。
密度可达
如果存在一个点的序列Q0, Q1, ..., Qk,其中Qi到Qi-1是直接密度可达的,那么称点Qk是从点Q0密度可达的。这可以理解为密度通过一系列核心对象“传播”到了远处的点。
密度相连
如果存在一个核心对象O,使得点P和点K都是从O密度可达的,那么称点P和点K是密度相连的。所有密度相连的点构成一个簇。
边界点与噪声点
- 边界点:属于某个簇,但其本身不是核心对象的点。它位于核心对象的邻域内,但自己的邻域内点数不足
MinPts。 - 噪声点:不属于任何簇的点。即该点既不是核心对象,也不在任何核心对象的密度可达范围内。
上一节我们介绍了DBSCAN的核心概念,本节中我们来看看它的工作流程和实际意义。

算法流程与可视化理解
DBSCAN的工作方式类似于寻找并连接“密度可达”的网络。
以下是算法过程的形象化描述:
- 算法随机选择一个未被访问的点。
- 如果该点是核心对象,则以此点为起点,寻找所有从它密度可达的点,形成一个簇。
- 如果该点不是核心对象(可能是噪声或边界点),则暂时标记为噪声,继续访问下一个点。
- 重复上述过程,直到所有点都被访问过。

如上图所示:
- 点
A是一个核心对象,它发展出了A',A'',A'''等“下线”。 - 这些“下线”如果自身也是核心对象,会继续发展出新的点(如
B和C周围的点),从而将簇扩大。 - 点
B和C是边界点。以它们为圆心画圈,圈内的点数不足MinPts,无法再发展新的下线。 - 点
N是一个噪声点(离群点)。没有任何核心对象的邻域圈能够包含它,因此它不属于任何簇。

DBSCAN的特点与应用
与K-Means的对比
DBSCAN相比K-Means有几个显著优点:
- 无需指定簇数量 (K):簇的数量由算法根据数据密度自动发现,这解决了K-Means中K值难以确定的问题。
- 能发现任意形状的簇:K-Means假设簇是凸形的,而DBSCAN可以识别任意形状的簇,如环形、月牙形等。
- 对噪声鲁棒:能够明确识别并过滤掉噪声点。
当然,DBSCAN也有其挑战:
- 参数敏感:半径
R和MinPts的选择对结果影响很大,需要根据数据特点进行调试。 - 对密度差异大的数据集效果不佳:如果数据中不同簇的密度差异显著,统一的
R和MinPts参数可能难以同时适用于所有簇。
核心应用:异常检测
由于DBSCAN能够有效识别出不属于任何密集区域的点(噪声点/离群点),因此它非常适合用于异常检测任务。在金融欺诈检测、工业故障诊断、网络安全等领域,异常行为的数据点往往远离正常数据点形成的密集簇,DBSCAN可以自动将这些点标记出来。

本节课中我们一起学习了DBSCAN密度聚类算法。我们首先理解了其基于密度的核心思想,以及核心对象、密度可达等关键概念。然后,我们通过形象的“发展下线”比喻,梳理了算法的执行流程,认识了边界点和噪声点。最后,我们总结了DBSCAN无需指定簇数、能识别任意形状簇和噪声点的优势,并了解了它在异常检测中的重要应用。DBSCAN是一种强大且实用的工具,特别适用于数据分布不规则且包含噪声的现实场景。
062:DBSCAN工作流程
在本节课中,我们将学习DBSCAN(基于密度的噪声应用空间聚类)算法的工作流程。我们将详细拆解其核心步骤、参数选择以及算法的优势与劣势,帮助你理解这个强大的密度聚类算法是如何运作的。
🧠 核心概念与输入参数
上一节我们介绍了DBSCAN的基本思想,本节中我们来看看它的具体工作流程。首先,我们需要明确算法运行所需的输入参数。
DBSCAN算法主要需要三个输入参数:
- 数据集:这是任何聚类算法的基础输入,即需要进行聚类的数据点集合。
- 半径(eps):这是算法“画圈”的半径。它定义了邻域的大小,用于判断一个点周围是否足够“密集”。
- 最小点数(min_samples):这是密度阈值。它定义了在以某个点为中心、半径为
eps的圆形邻域内,最少需要有多少个点(包括中心点自身),才能将该点定义为核心对象。
用公式表示,对于一个点 p,如果满足以下条件,则它是一个核心对象:
N_eps(p) >= min_samples
其中,N_eps(p) 表示点 p 在 eps 半径内的邻居点数量(包括 p 自身)。
🔄 算法迭代流程详解
了解了输入参数后,我们进入算法的核心迭代过程。DBSCAN通过不断“发展下线”来形成簇。

以下是DBSCAN算法的工作步骤:
- 初始化标记:算法开始时,将所有数据点标记为“未访问”。
- 随机选择起点:从数据集中随机选择一个未被访问的点
P,并将其标记为“已访问”。 - 判断核心对象:检查点
P在其eps半径邻域内的点数是否大于等于min_samples。- 如果是,则点
P是一个核心对象。此时,创建一个新簇C,并将点P加入该簇。 - 如果否,则点
P被暂时标记为噪声点(后续可能被其他簇吸收),然后返回步骤2选择下一个点。
- 如果是,则点
- 扩展簇(发展下线):如果
P是核心对象,则将其邻域内的所有点收集到一个集合N中。然后,遍历集合N中的每一个点Q:- 如果点
Q是“未访问”的,则将其标记为“已访问”。 - 再次检查点
Q是否为核心对象(即其eps邻域内的点数是否>= min_samples)。- 如果
Q也是核心对象,则将其邻域内的所有点也加入到集合N中(即“发展了下线的下线”)。
- 如果
- 如果点
Q尚未属于任何簇,则将其加入到当前簇C中。
- 如果点
- 完成当前簇:当集合
N中的所有点都被处理完毕后,当前簇C的扩展就完成了。此时,簇C包含了所有从核心对象P出发,通过密度可达关系连接起来的点。 - 处理剩余点:返回步骤2,从剩余的“未访问”点中随机选择新的起点,重复上述过程,直到所有数据点都被标记为“已访问”。
这个过程可以形象地比喻为“传销组织”的发展:一个核心成员(核心对象)发展其直接下线(邻域内的点),如果下线也是核心成员,则会继续发展下线,直到无法发展为止,从而形成一个完整的组织(簇)。未被任何组织发展的点,则成为“散兵游勇”(噪声点或离群点)。
⚙️ 参数选择策略

DBSCAN的效果很大程度上依赖于 eps 和 min_samples 这两个参数的选择。参数选择不当可能导致聚类结果不理想。
- 半径(eps)的选择:这是最关键的参数。
eps值过大会导致多个本应分开的簇被合并;值过小则会导致本应是一个的簇被拆分成多个,并产生大量噪声点。- K-距离图法:一种常用的方法是绘制“K-距离图”。对于数据集中的每个点,计算它到第
k个最近邻点的距离(k通常取min_samples - 1),然后将所有点的这个距离进行排序并绘图。图中距离突然增大的“拐点”所对应的距离值,通常可以作为eps的一个较好估计。
- K-距离图法:一种常用的方法是绘制“K-距离图”。对于数据集中的每个点,计算它到第
- 最小点数(min_samples)的选择:这个参数相对容易设置。一般遵循一个经验法则:
min_samples >= 维度 + 1。对于较低维度的数据,通常设置为 4 到 10 之间的一个较小值即可。在sklearn中,默认值为 5。
✅ 算法优势与劣势
通过前面的介绍,我们可以总结出DBSCAN算法的特点。
优势:
- 无需预先指定簇数:与K-Means等算法不同,DBSCAN能自动发现数据中自然形成的簇的个数。
- 能发现任意形状的簇:它基于密度连接,因此可以识别出球形、环形、带状等任意形状的簇,如上文图示中的笑脸和同心圆。
- 能识别噪声点:算法天然地将不属于任何密集区域的点标记为噪声(离群点),适用于异常检测任务。
- 参数较少:核心参数只有两个(
eps和min_samples)。
劣势:
- 对高维数据效果不佳:“维度灾难”会导致所有点之间的距离都变得相似,使得基于距离的密度定义失效。在高维空间中,选择合适的
eps变得非常困难。 - 对参数敏感:聚类结果严重依赖于
eps和min_samples的选择,而eps的选取通常没有绝对标准。 - 处理密度差异大的簇有困难:如果数据集中不同簇的密度差异很大,很难找到一个全局的
eps参数同时适用于所有簇。 - 大数据集性能:虽然算法本身复杂度尚可,但在某些实现(如
sklearn的默认设置)中,处理大规模、高维数据时可能面临内存消耗过大或计算缓慢的问题。此时可能需要结合数据降维或采样策略。
📝 总结

本节课中我们一起学习了DBSCAN聚类算法的工作流程。我们从其核心概念和输入参数(数据集、半径 eps、最小点数 min_samples)出发,逐步剖析了算法如何通过标记点、寻找核心对象、以及递归地“扩展簇”来将数据点归类。我们还探讨了关键参数的选取策略,并全面分析了该算法的优势(如自动确定簇数、识别任意形状簇和噪声点)与劣势(如对高维数据和参数选择敏感)。总体而言,DBSCAN是一种非常强大且实用的密度聚类算法,尤其适用于簇形状不规则且含有噪声的数据集。
063:DBSCAN可视化展示 🎯


在本节课中,我们将通过可视化的方式,直观地学习DBSCAN算法的工作流程。我们将看到算法如何一步步地“发展下线”来形成聚类簇,以及核心参数如何影响最终的聚类结果。
算法流程演示 🧭
上一节我们介绍了DBSCAN算法的核心概念,本节中我们来看看它在实际数据上是如何运行的。

首先,我们需要指定DBSCAN算法的两个核心参数:邻域半径 eps 和最小样本数 min_samples。这两个参数需要我们自己设定。
以下是算法启动的步骤:
- 随机选择一个未被访问的核心点。
- 以该点为中心,查找其eps邻域内的所有点。
- 如果邻域内的点数达到min_samples,则将这些点归入同一个簇,并递归地对邻域内的新点重复步骤2和3,即“发展下线”。
- 当一个簇无法再扩展时,算法会寻找下一个未被访问的核心点,开始建立一个新的簇。
- 所有点被访问后,不属于任何簇的点被标记为噪声点或离群点。


在演示中,算法从一个点开始,逐步将其邻域内的点“圈”进来。当一个簇完成后,算法会寻找下一个起始点,建立新的“大陆”。最终,有些点未被任何簇包含,这些就是离群点。


参数对结果的影响 ⚙️
上一节我们看到了DBSCAN的基本流程,本节中我们来看看参数如何改变聚类结果。
参数eps和min_samples会显著影响聚类簇的形状和数量,尤其是对离群点的判定。
以下是参数调整的观察:
- 当eps设置较小时,算法对密度的要求更严格,可能会识别出更多的簇和离群点。
- 当eps设置较大时,算法更容易将点连接起来,可能导致离群点减少,甚至所有点被归入少数几个大簇中。
- min_samples设置较小时,形成核心点的门槛更低,更容易形成新的簇。

通过调整参数,我们可以控制算法对“密集”和“稀疏”区域的敏感度。例如,在寻找离群点时,可以将eps和min_samples设置得稍小一些。
与K-Means的对比 🤖
我们之前学习过K-Means算法,它需要预先指定簇的数量K。DBSCAN的优势在于它能发现任意形状的簇,并且不需要指定簇数。

在演示中,对一个“笑脸”形状的数据集进行聚类。K-Means在处理这种非球状簇时效果不佳,而DBSCAN则能很好地根据密度连接关系,将外层轮廓和内层轮廓分别识别为不同的簇。
DBSCAN基于“密度可达”的原则,只要点与点之间能通过一系列核心点相连,它们就会被归为同一簇。这使得它能有效处理复杂形状的数据分布。
算法的局限性 💡

虽然DBSCAN功能强大,但它并非万能。本节我们来看看它可能遇到的问题。
当数据整体分布都非常密集时,DBSCAN可能会将大部分数据点都归入一个或少数几个大簇中,失去了聚类的意义。此外,参数的选择对结果影响巨大,且没有一个通用的“最佳”参数标准,这给实际应用带来了挑战。

在演示中,对一个密集数据集使用默认参数,DBSCAN将所有点归为一类。通过调小eps,虽然能分出更多簇,但结果可能过于碎片化,反而不如预先指定K值的K-Means算法得出的结果有规律。

因此,DBSCAN的效果高度依赖于参数设置和数据本身的分布特性。在实际使用中,需要反复试验和调整参数,并结合业务知识来评估聚类结果的合理性。
总结 📝

本节课中我们一起学习了DBSCAN算法的可视化工作流程。
- 我们了解了DBSCAN通过“发展下线”的方式,基于密度来形成聚类簇,并能自动识别噪声点。
- 我们看到了核心参数eps(邻域半径)和min_samples(最小样本数)如何直接影响簇的数量、形状以及离群点的判定。
- 我们对比了DBSCAN与K-Means的差异,认识到DBSCAN在发现任意形状簇方面的优势,同时也了解了它在处理均匀密集数据和对参数敏感方面的局限性。


DBSCAN是一个强大且直观的密度聚类算法,其可视化演示有助于我们深刻理解“密度”在聚类中的含义。掌握其原理和参数影响,是将其成功应用于实际数据的关键。

浙公网安备 33010602011771号