Python金融_使用Pandas进行股票量化回测

Python金融_使用Pandas进行股票量化回测

1. 前言

金融量化交易的回测是一种评估投资策略有效性的方法。它涉及在已知的市场数据上运行交易策略,并估计该策略在未来可能产生的收益。回测的目的是了解策略在历史数据上的表现,并预测其在未来实际交易中的表现。

回测的过程通常包括以下步骤:

  • 确定投资策略:根据投资者的风险偏好、投资目标等因素,制定合适的投资策略,包括投资品种、投资比例、买卖规则等。

  • 收集市场数据:收集与投资策略相关的市场数据,如股票价格、成交量、利率等。

  • 构建回测模型:利用历史数据和量化分析工具,构建回测模型,模拟投资策略的执行和收益情况。

  • 运行回测模型:将回测模型应用于历史数据,模拟投资策略的执行过程,并计算出模拟收益。

  • 分析回测结果:根据回测结果,分析投资策略的有效性,包括收益率、波动性、最大回撤等指标,以及策略在不同市场环境下的表现。

  • 调整投资策略:根据回测结果和分析结果,对投资策略进行优化和调整,以提高策略的有效性和适应性。

需要注意的是,回测结果是历史结果并不代表未来实际交易的结果,因为市场环境是不断变化的。因此,投资者在进行量化交易时,需要谨慎对待回测结果,并结合实际情况进行决策。

Python 上用于回测的量化框架有 Backtrader,这也是我在2020年最早时候接触到的量化框架,已经很久没更新了,并且与新版 Matplotlib 不兼容。

使用第三方 Python 库,其实并没有想象中方便,因为你需要理解作者逻辑,查阅作者文档,可能仅仅为了回测这一件小事,而花费大量精力和时间阅读文档,查阅教程,得不偿失。

我写这篇文章的目的就是为了实现掌握较少的工具花费较少的精力,少走弯路,达到目的。

2. 准备

  • 熟悉 Excel 等表格类型的工具 (便于理解股票回测的过程)

  • 安装 Python 3.8.0

  • 安装 Pandas 1.4.1

2.1 安装 Python

由于本人使用的 Python 3.8.0 为了避免兼容性问题,推荐安装这个版本,本文不过多涉及 Python 的安装,下载官方的安装包安装即可。

https://www.python.org/

2.2 安装 Pandas

由于本人使用的 Pandas 1.4.1 为了避免函数改动的问题,推荐安装这个版本,命令行使用 Pip 安装即可。

pip install pandas==1.4.1

3. 准备股票数据

本篇文章以梳理思路,演示方法为主,所以直接使用以下简单的测试数据即可。

正常情况下的股票量化回测,可通过 API 等接口获取股票历年数据。

测试数据 (前 5 行和后 5 行预览):

[{"Date":"2015/12/31", "Code":"'000422", "Open":7.93, "High":7.95, "Low":7.76, "Close":7.77, "Pre_Close":7.93, "Change":-0.020177, "Volume":13915200},
 {"Date":"2015/12/30", "Code":"'000422", "Open":7.86, "High":7.93, "Low":7.75, "Close":7.93, "Pre_Close":7.84, "Change":0.01148,   "Volume":16755900},
 {"Date":"2015/12/29", "Code":"'000422", "Open":7.72, "High":7.85, "Low":7.69, "Close":7.84, "Pre_Close":7.71, "Change":0.016861,  "Volume":14263800},
 {"Date":"2015/12/28", "Code":"'000422", "Open":8.03, "High":8.08, "Low":7.7,  "Close":7.71, "Pre_Close":8.03, "Change":-0.039851, "Volume":27672800},
 {"Date":"2015/12/25", "Code":"'000422", "Open":8.03, "High":8.05, "Low":7.93, "Close":8.03, "Pre_Close":7.99, "Change":0.005006,  "Volume":18974000},
 {"Date":"2015/12/24", "Code":"'000422", "Open":7.93, "High":8.16, "Low":7.87, "Close":7.99, "Pre_Close":7.92, "Change":0.008838,  "Volume":23781900},
 ......
 {"Date":"2015/08/11", "Code":"'000422", "Open":8.41, "High":8.68, "Low":8.32, "Close":8.48, "Pre_Close":8.49, "Change":-0.001178, "Volume":42343900},
 {"Date":"2015/08/10", "Code":"'000422", "Open":8.28, "High":8.58, "Low":8.18, "Close":8.49, "Pre_Close":8.21, "Change":0.034105,  "Volume":36071600},
 {"Date":"2015/08/07", "Code":"'000422", "Open":8.15, "High":8.28, "Low":8.08, "Close":8.21, "Pre_Close":8.07, "Change":0.017348,  "Volume":22599800},
 {"Date":"2015/08/06", "Code":"'000422", "Open":7.88, "High":8.21, "Low":7.8,  "Close":8.07, "Pre_Close":8.03, "Change":0.004981,  "Volume":17546700},
 {"Date":"2015/08/05", "Code":"'000422", "Open":8.1,  "High":8.35, "Low":7.95, "Close":8.03, "Pre_Close":8.18, "Change":-0.018337, "Volume":23875500}]

下面使用 Pandas 加载数据时会有完整的数据,方便排版,这里只给出数据前 5 行和后 5 行的预览。

4. Pandas 加载数据

代码释义:

  • import pandas as pd: 导入 Pandas 库,并取别名为 pd。

  • pd.DataFrame(): 使用 Pandas 的数据框函数构建数据,可传入字典 (Dict),也可传入列表 (List),这里以列表的形式传入键值对 (Key : Value) 的行。

  • Stock.head(5): 显示前 5 行数据。

import pandas as pd

Stock = pd.DataFrame([{"Date":"2015/12/31", "Code":"'000422", "Open":7.93, "High":7.95, "Low":7.76, "Close":7.77, "Pre_Close":7.93, "Change":-0.020177, "Volume":13915200},
                      {"Date":"2015/12/30", "Code":"'000422", "Open":7.86, "High":7.93, "Low":7.75, "Close":7.93, "Pre_Close":7.84, "Change":0.01148,   "Volume":16755900},
                      {"Date":"2015/12/29", "Code":"'000422", "Open":7.72, "High":7.85, "Low":7.69, "Close":7.84, "Pre_Close":7.71, "Change":0.016861,  "Volume":14263800},
                      {"Date":"2015/12/28", "Code":"'000422", "Open":8.03, "High":8.08, "Low":7.7,  "Close":7.71, "Pre_Close":8.03, "Change":-0.039851, "Volume":27672800},
                      {"Date":"2015/12/25", "Code":"'000422", "Open":8.03, "High":8.05, "Low":7.93, "Close":8.03, "Pre_Close":7.99, "Change":0.005006,  "Volume":18974000},
                      {"Date":"2015/12/24", "Code":"'000422", "Open":7.93, "High":8.16, "Low":7.87, "Close":7.99, "Pre_Close":7.92, "Change":0.008838,  "Volume":23781900},
                      {"Date":"2015/12/23", "Code":"'000422", "Open":7.97, "High":8.11, "Low":7.88, "Close":7.92, "Pre_Close":7.89, "Change":0.003802,  "Volume":38033600},
                      {"Date":"2015/12/22", "Code":"'000422", "Open":7.86, "High":7.93, "Low":7.76, "Close":7.89, "Pre_Close":7.83, "Change":0.007663,  "Volume":24178700},
                      {"Date":"2015/12/21", "Code":"'000422", "Open":7.59, "High":7.89, "Low":7.56, "Close":7.83, "Pre_Close":7.63, "Change":0.026212,  "Volume":27633600},
                      {"Date":"2015/12/18", "Code":"'000422", "Open":7.71, "High":7.74, "Low":7.57, "Close":7.63, "Pre_Close":7.74, "Change":-0.014212, "Volume":22234900},
                      {"Date":"2015/12/17", "Code":"'000422", "Open":7.58, "High":7.75, "Low":7.57, "Close":7.74, "Pre_Close":7.55, "Change":0.025166,  "Volume":25188400},
                      {"Date":"2015/12/16", "Code":"'000422", "Open":7.57, "High":7.62, "Low":7.53, "Close":7.55, "Pre_Close":7.55, "Change":0,         "Volume":18601600},
                      {"Date":"2015/12/15", "Code":"'000422", "Open":7.63, "High":7.66, "Low":7.52, "Close":7.55, "Pre_Close":7.62, "Change":-0.009186, "Volume":23256600},
                      {"Date":"2015/12/14", "Code":"'000422", "Open":7.4,  "High":7.64, "Low":7.36, "Close":7.62, "Pre_Close":7.51, "Change":0.014647,  "Volume":18860100},
                      {"Date":"2015/12/11", "Code":"'000422", "Open":7.65, "High":7.7,  "Low":7.41, "Close":7.51, "Pre_Close":7.67, "Change":-0.02086,  "Volume":18385900},
                      {"Date":"2015/12/10", "Code":"'000422", "Open":7.78, "High":7.87, "Low":7.65, "Close":7.67, "Pre_Close":7.83, "Change":-0.020434, "Volume":17931900},
                      {"Date":"2015/12/09", "Code":"'000422", "Open":7.76, "High":8,    "Low":7.75, "Close":7.83, "Pre_Close":7.77, "Change":0.007722,  "Volume":22569700},
                      {"Date":"2015/12/08", "Code":"'000422", "Open":8.08, "High":8.18, "Low":7.76, "Close":7.77, "Pre_Close":8.24, "Change":-0.057039, "Volume":32948200},
                      {"Date":"2015/12/07", "Code":"'000422", "Open":8.12, "High":8.39, "Low":7.94, "Close":8.24, "Pre_Close":8.23, "Change":0.001215,  "Volume":57993100},
                      {"Date":"2015/12/04", "Code":"'000422", "Open":7.85, "High":8.48, "Low":7.8,  "Close":8.23, "Pre_Close":7.92, "Change":0.039141,  "Volume":89881900},
                      {"Date":"2015/12/03", "Code":"'000422", "Open":7.42, "High":8.09, "Low":7.38, "Close":7.92, "Pre_Close":7.43, "Change":0.065949,  "Volume":40777500},
                      {"Date":"2015/12/02", "Code":"'000422", "Open":7.35, "High":7.48, "Low":7.2,  "Close":7.43, "Pre_Close":7.36, "Change":0.009511,  "Volume":14337600},
                      {"Date":"2015/12/01", "Code":"'000422", "Open":7.28, "High":7.39, "Low":7.23, "Close":7.36, "Pre_Close":7.33, "Change":0.004093,  "Volume":11050700},
                      {"Date":"2015/11/30", "Code":"'000422", "Open":7.18, "High":7.36, "Low":6.95, "Close":7.33, "Pre_Close":7.11, "Change":0.030942,  "Volume":18247500},
                      {"Date":"2015/11/27", "Code":"'000422", "Open":7.59, "High":7.59, "Low":6.95, "Close":7.11, "Pre_Close":7.6,  "Change":-0.064474, "Volume":24846700},
                      {"Date":"2015/11/26", "Code":"'000422", "Open":7.63, "High":7.73, "Low":7.58, "Close":7.6,  "Pre_Close":7.63, "Change":-0.003932, "Volume":22299800},
                      {"Date":"2015/11/25", "Code":"'000422", "Open":7.56, "High":7.64, "Low":7.51, "Close":7.63, "Pre_Close":7.59, "Change":0.00527,   "Volume":18782900},
                      {"Date":"2015/11/24", "Code":"'000422", "Open":7.6,  "High":7.63, "Low":7.48, "Close":7.59, "Pre_Close":7.62, "Change":-0.003937, "Volume":13348200},
                      {"Date":"2015/11/23", "Code":"'000422", "Open":7.59, "High":7.72, "Low":7.55, "Close":7.62, "Pre_Close":7.61, "Change":0.001314,  "Volume":25505000},
                      {"Date":"2015/11/20", "Code":"'000422", "Open":7.59, "High":7.71, "Low":7.53, "Close":7.61, "Pre_Close":7.59, "Change":0.002635,  "Volume":25389100},
                      {"Date":"2015/11/19", "Code":"'000422", "Open":7.45, "High":7.62, "Low":7.41, "Close":7.59, "Pre_Close":7.39, "Change":0.027064,  "Volume":34691700},
                      {"Date":"2015/11/18", "Code":"'000422", "Open":7.53, "High":7.54, "Low":7.38, "Close":7.39, "Pre_Close":7.51, "Change":-0.015979, "Volume":12725000},
                      {"Date":"2015/11/17", "Code":"'000422", "Open":7.53, "High":7.63, "Low":7.44, "Close":7.51, "Pre_Close":7.5,  "Change":0.001333,  "Volume":25714500},
                      {"Date":"2015/11/16", "Code":"'000422", "Open":7.27, "High":7.52, "Low":7.24, "Close":7.5,  "Pre_Close":7.38, "Change":0.01626,   "Volume":14572000},
                      {"Date":"2015/11/13", "Code":"'000422", "Open":7.49, "High":7.55, "Low":7.36, "Close":7.38, "Pre_Close":7.54, "Change":-0.02122,  "Volume":26214400},
                      {"Date":"2015/11/12", "Code":"'000422", "Open":7.65, "High":7.68, "Low":7.49, "Close":7.54, "Pre_Close":7.61, "Change":-0.009198, "Volume":23794800},
                      {"Date":"2015/11/11", "Code":"'000422", "Open":7.57, "High":7.64, "Low":7.52, "Close":7.61, "Pre_Close":7.57, "Change":0.005284,  "Volume":23445900},
                      {"Date":"2015/11/10", "Code":"'000422", "Open":7.51, "High":7.61, "Low":7.45, "Close":7.57, "Pre_Close":7.55, "Change":0.002649,  "Volume":22427700},
                      {"Date":"2015/11/09", "Code":"'000422", "Open":7.51, "High":7.62, "Low":7.45, "Close":7.55, "Pre_Close":7.53, "Change":0.002656,  "Volume":29959500},
                      {"Date":"2015/11/06", "Code":"'000422", "Open":7.47, "High":7.53, "Low":7.37, "Close":7.53, "Pre_Close":7.45, "Change":0.010738,  "Volume":33273100},
                      {"Date":"2015/11/05", "Code":"'000422", "Open":7.34, "High":7.54, "Low":7.32, "Close":7.45, "Pre_Close":7.37, "Change":0.010855,  "Volume":36330200},
                      {"Date":"2015/11/04", "Code":"'000422", "Open":7.1,  "High":7.38, "Low":7.07, "Close":7.37, "Pre_Close":7.05, "Change":0.04539,   "Volume":31260800},
                      {"Date":"2015/11/03", "Code":"'000422", "Open":7.08, "High":7.13, "Low":7.02, "Close":7.05, "Pre_Close":7.06, "Change":-0.001416, "Volume":13412400},
                      {"Date":"2015/11/02", "Code":"'000422", "Open":7.11, "High":7.26, "Low":7.05, "Close":7.06, "Pre_Close":7.26, "Change":-0.027548, "Volume":15142100},
                      {"Date":"2015/10/30", "Code":"'000422", "Open":7.22, "High":7.38, "Low":7.1,  "Close":7.26, "Pre_Close":7.24, "Change":0.002762,  "Volume":20490200},
                      {"Date":"2015/10/29", "Code":"'000422", "Open":7.27, "High":7.33, "Low":7.16, "Close":7.24, "Pre_Close":7.16, "Change":0.011173,  "Volume":23098500},
                      {"Date":"2015/10/28", "Code":"'000422", "Open":7.32, "High":7.4,  "Low":7.09, "Close":7.16, "Pre_Close":7.42, "Change":-0.03504,  "Volume":31938500},
                      {"Date":"2015/10/27", "Code":"'000422", "Open":7.21, "High":7.48, "Low":7.08, "Close":7.42, "Pre_Close":7.18, "Change":0.033426,  "Volume":51769300},
                      {"Date":"2015/10/26", "Code":"'000422", "Open":7.2,  "High":7.25, "Low":7.01, "Close":7.18, "Pre_Close":7.17, "Change":0.001395,  "Volume":33077800},
                      {"Date":"2015/10/23", "Code":"'000422", "Open":6.84, "High":7.22, "Low":6.81, "Close":7.17, "Pre_Close":6.8,  "Change":0.054412,  "Volume":42351500},
                      {"Date":"2015/10/22", "Code":"'000422", "Open":6.68, "High":6.81, "Low":6.64, "Close":6.8,  "Pre_Close":6.65, "Change":0.022556,  "Volume":18503800},
                      {"Date":"2015/10/21", "Code":"'000422", "Open":7.08, "High":7.11, "Low":6.61, "Close":6.65, "Pre_Close":7.09, "Change":-0.062059, "Volume":35365300},
                      {"Date":"2015/10/20", "Code":"'000422", "Open":7,    "High":7.09, "Low":6.94, "Close":7.09, "Pre_Close":7.03, "Change":0.008535,  "Volume":21972900},
                      {"Date":"2015/10/19", "Code":"'000422", "Open":7.09, "High":7.13, "Low":6.92, "Close":7.03, "Pre_Close":7.08, "Change":-0.007062, "Volume":28068800},
                      {"Date":"2015/10/16", "Code":"'000422", "Open":6.97, "High":7.08, "Low":6.91, "Close":7.08, "Pre_Close":6.93, "Change":0.021645,  "Volume":35584700},
                      {"Date":"2015/10/15", "Code":"'000422", "Open":6.77, "High":6.94, "Low":6.75, "Close":6.93, "Pre_Close":6.77, "Change":0.023634,  "Volume":28412700},
                      {"Date":"2015/10/14", "Code":"'000422", "Open":6.87, "High":6.94, "Low":6.74, "Close":6.77, "Pre_Close":6.89, "Change":-0.017417, "Volume":24445500},
                      {"Date":"2015/10/13", "Code":"'000422", "Open":6.86, "High":6.96, "Low":6.8,  "Close":6.89, "Pre_Close":6.88, "Change":0.001453,  "Volume":25771900},
                      {"Date":"2015/10/12", "Code":"'000422", "Open":6.62, "High":6.91, "Low":6.58, "Close":6.88, "Pre_Close":6.61, "Change":0.040847,  "Volume":33254300},
                      {"Date":"2015/10/09", "Code":"'000422", "Open":6.54, "High":6.65, "Low":6.45, "Close":6.61, "Pre_Close":6.54, "Change":0.010703,  "Volume":16635900},
                      {"Date":"2015/10/08", "Code":"'000422", "Open":6.45, "High":6.7,  "Low":6.37, "Close":6.54, "Pre_Close":6.26, "Change":0.044728,  "Volume":16931000},
                      {"Date":"2015/09/30", "Code":"'000422", "Open":6.25, "High":6.3,  "Low":6.22, "Close":6.26, "Pre_Close":6.23, "Change":0.004815,  "Volume":6579090},
                      {"Date":"2015/09/29", "Code":"'000422", "Open":6.3,  "High":6.32, "Low":6.18, "Close":6.23, "Pre_Close":6.4,  "Change":-0.026562, "Volume":8072900},
                      {"Date":"2015/09/28", "Code":"'000422", "Open":6.35, "High":6.42, "Low":6.25, "Close":6.4,  "Pre_Close":6.34, "Change":0.009464,  "Volume":7922890},
                      {"Date":"2015/09/25", "Code":"'000422", "Open":6.51, "High":6.56, "Low":6.25, "Close":6.34, "Pre_Close":6.53, "Change":-0.029096, "Volume":11298800},
                      {"Date":"2015/09/24", "Code":"'000422", "Open":6.48, "High":6.56, "Low":6.45, "Close":6.53, "Pre_Close":6.45, "Change":0.012403,  "Volume":10180900},
                      {"Date":"2015/09/23", "Code":"'000422", "Open":6.51, "High":6.6,  "Low":6.41, "Close":6.45, "Pre_Close":6.67, "Change":-0.032984, "Volume":14294100},
                      {"Date":"2015/09/22", "Code":"'000422", "Open":6.58, "High":6.73, "Low":6.54, "Close":6.67, "Pre_Close":6.58, "Change":0.013678,  "Volume":20970200},
                      {"Date":"2015/09/21", "Code":"'000422", "Open":6.34, "High":6.61, "Low":6.29, "Close":6.58, "Pre_Close":6.44, "Change":0.021739,  "Volume":15295900},
                      {"Date":"2015/09/18", "Code":"'000422", "Open":6.52, "High":6.58, "Low":6.3,  "Close":6.44, "Pre_Close":6.44, "Change":0,         "Volume":14924700},
                      {"Date":"2015/09/17", "Code":"'000422", "Open":6.59, "High":6.76, "Low":6.43, "Close":6.44, "Pre_Close":6.68, "Change":-0.035928, "Volume":17523900},
                      {"Date":"2015/09/16", "Code":"'000422", "Open":6.21, "High":6.76, "Low":6.17, "Close":6.68, "Pre_Close":6.15, "Change":0.086179,  "Volume":17662300},
                      {"Date":"2015/09/15", "Code":"'000422", "Open":6.24, "High":6.38, "Low":6.05, "Close":6.15, "Pre_Close":6.26, "Change":-0.017572, "Volume":13771200},
                      {"Date":"2015/09/14", "Code":"'000422", "Open":6.89, "High":6.95, "Low":6.18, "Close":6.26, "Pre_Close":6.87, "Change":-0.088792, "Volume":18559600},
                      {"Date":"2015/09/11", "Code":"'000422", "Open":6.87, "High":6.96, "Low":6.77, "Close":6.87, "Pre_Close":6.84, "Change":0.004386,  "Volume":9486290},
                      {"Date":"2015/09/10", "Code":"'000422", "Open":6.95, "High":7.01, "Low":6.76, "Close":6.84, "Pre_Close":7.06, "Change":-0.031161, "Volume":15229100},
                      {"Date":"2015/09/09", "Code":"'000422", "Open":6.9,  "High":7.09, "Low":6.86, "Close":7.06, "Pre_Close":6.88, "Change":0.026163,  "Volume":25325600},
                      {"Date":"2015/09/08", "Code":"'000422", "Open":6.65, "High":6.91, "Low":6.55, "Close":6.88, "Pre_Close":6.62, "Change":0.039275,  "Volume":15609100},
                      {"Date":"2015/09/07", "Code":"'000422", "Open":6.5,  "High":6.81, "Low":6.5,  "Close":6.62, "Pre_Close":6.38, "Change":0.037618,  "Volume":15602600},
                      {"Date":"2015/09/02", "Code":"'000422", "Open":6.45, "High":6.88, "Low":6.3,  "Close":6.38, "Pre_Close":6.74, "Change":-0.053412, "Volume":19480100},
                      {"Date":"2015/09/01", "Code":"'000422", "Open":6.88, "High":6.99, "Low":6.67, "Close":6.74, "Pre_Close":6.81, "Change":-0.010279, "Volume":22576700},
                      {"Date":"2015/08/31", "Code":"'000422", "Open":6.9,  "High":6.97, "Low":6.71, "Close":6.81, "Pre_Close":7.07, "Change":-0.036775, "Volume":16069600},
                      {"Date":"2015/08/28", "Code":"'000422", "Open":6.75, "High":7.08, "Low":6.71, "Close":7.07, "Pre_Close":6.67, "Change":0.05997,   "Volume":23330800},
                      {"Date":"2015/08/27", "Code":"'000422", "Open":6.53, "High":6.67, "Low":6.34, "Close":6.67, "Pre_Close":6.32, "Change":0.05538,   "Volume":19627900},
                      {"Date":"2015/08/26", "Code":"'000422", "Open":6.31, "High":6.77, "Low":6.09, "Close":6.32, "Pre_Close":6.25, "Change":0.0112,    "Volume":26190200},
                      {"Date":"2015/08/25", "Code":"'000422", "Open":6.4,  "High":6.77, "Low":6.25, "Close":6.25, "Pre_Close":6.94, "Change":-0.099424, "Volume":25778600},
                      {"Date":"2015/08/24", "Code":"'000422", "Open":7.49, "High":7.49, "Low":6.94, "Close":6.94, "Pre_Close":7.71, "Change":-0.09987,  "Volume":31949900},
                      {"Date":"2015/08/21", "Code":"'000422", "Open":8,    "High":8.11, "Low":7.6,  "Close":7.71, "Pre_Close":8.17, "Change":-0.056304, "Volume":28144800},
                      {"Date":"2015/08/20", "Code":"'000422", "Open":8.38, "High":8.56, "Low":8.14, "Close":8.17, "Pre_Close":8.53, "Change":-0.042204, "Volume":27764200},
                      {"Date":"2015/08/19", "Code":"'000422", "Open":7.73, "High":8.57, "Low":7.72, "Close":8.53, "Pre_Close":7.96, "Change":0.071608,  "Volume":45619900},
                      {"Date":"2015/08/18", "Code":"'000422", "Open":8.81, "High":8.86, "Low":7.92, "Close":7.96, "Pre_Close":8.8,  "Change":-0.095455, "Volume":49105500},
                      {"Date":"2015/08/17", "Code":"'000422", "Open":8.49, "High":8.83, "Low":8.42, "Close":8.8,  "Pre_Close":8.52, "Change":0.032864,  "Volume":42096900},
                      {"Date":"2015/08/14", "Code":"'000422", "Open":8.48, "High":8.65, "Low":8.43, "Close":8.52, "Pre_Close":8.44, "Change":0.009479,  "Volume":35985000},
                      {"Date":"2015/08/13", "Code":"'000422", "Open":8.2,  "High":8.45, "Low":8.15, "Close":8.44, "Pre_Close":8.24, "Change":0.024272,  "Volume":26019600},
                      {"Date":"2015/08/12", "Code":"'000422", "Open":8.38, "High":8.48, "Low":8.21, "Close":8.24, "Pre_Close":8.48, "Change":-0.028302, "Volume":30960700},
                      {"Date":"2015/08/11", "Code":"'000422", "Open":8.41, "High":8.68, "Low":8.32, "Close":8.48, "Pre_Close":8.49, "Change":-0.001178, "Volume":42343900},
                      {"Date":"2015/08/10", "Code":"'000422", "Open":8.28, "High":8.58, "Low":8.18, "Close":8.49, "Pre_Close":8.21, "Change":0.034105,  "Volume":36071600},
                      {"Date":"2015/08/07", "Code":"'000422", "Open":8.15, "High":8.28, "Low":8.08, "Close":8.21, "Pre_Close":8.07, "Change":0.017348,  "Volume":22599800},
                      {"Date":"2015/08/06", "Code":"'000422", "Open":7.88, "High":8.21, "Low":7.8,  "Close":8.07, "Pre_Close":8.03, "Change":0.004981,  "Volume":17546700},
                      {"Date":"2015/08/05", "Code":"'000422", "Open":8.1,  "High":8.35, "Low":7.95, "Close":8.03, "Pre_Close":8.18, "Change":-0.018337, "Volume":23875500}])

Stock.head(5)

输出:

>>> Stock.head(5)
         Date     Code  Open  High   Low  Close  Pre_Close     Change    Volume
0  2015/12/31  '000422  7.93  7.95  7.76   7.77       7.93  -0.020177  13915200
1  2015/12/30  '000422  7.86  7.93  7.75   7.93       7.84   0.011480  16755900
2  2015/12/29  '000422  7.72  7.85  7.69   7.84       7.71   0.016861  14263800
3  2015/12/28  '000422  8.03  8.08  7.70   7.71       8.03  -0.039851  27672800
4  2015/12/25  '000422  8.03  8.05  7.93   8.03       7.99   0.005006  18974000

5. 回测第 1 步: 买卖信号

为了方便在测试数据上演示用 Pandas 做量化回测的方法,这里我将使用简单的买卖信号作为入场点出场点

买入信号:

Close <= 7.00

卖出信号:

Close >= 8.00

由于测试数据的最低价为 6.15,最高价为 8.80,我们低买高卖就行了,也就是收盘价 (Close) 低于 7.00 就一直买,高于 8.00 就一直卖。

接下来,我们将买卖信号标记出来,符合条件的用 1 表示,不符合条件的用 0 表示。

代码释义:

  • Stock[“Signal_Buy”] = None: 直接对 Stock 数据框对象不存在的列名赋值,则新建一列,列名为 Signal_Buy,这一列的值全部是 None 值。

  • Stock[“Signal_Buy”] = Stock[“Close”].apply(): 这里调用了 Pandas 的 apply 方法,意思是映射生效到 Close 列中的每一个值,并把生效后返回的新列赋值给 Signal_Buy 列。

  • Stock[“Signal_Buy”] = Stock[“Close”].apply(lambda X: 1 if X <= 7.00 else 0): 这里往 Pandas 的 apply 方法中传入了一个 lambda 函数 (一次性函数),整个操作相当于 Excel 中,Close 在 D 列,而你在 E 列写了个 IF(A1 <= 7.00, 1, 0) 然后下拉填充到最后一行。

  • Stock.tail(15): 显示 Stock 数据框对象的最后 15 行。

# 用 Pandas 的 apply 方法传入 lambda 一次性函数计算买点。
Stock["Signal_Buy"] = Stock["Close"].apply(lambda X: 1 if X <= 7.00 else 0)

# 用 Pandas 的 apply 方法传入 lambda 一次性函数计算卖点。
Stock["Signal_Sell"] = Stock["Close"].apply(lambda X: 1 if X >= 8.00 else 0)

Stock.tail(15)

输出:

>>> Stock.tail(15)
          Date     Code  Open  ...    Volume  Signal_Buy  Signal_Sell
85  2015/08/25  '000422  6.40  ...  25778600           1            0
86  2015/08/24  '000422  7.49  ...  31949900           1            0
87  2015/08/21  '000422  8.00  ...  28144800           0            0
88  2015/08/20  '000422  8.38  ...  27764200           0            1
89  2015/08/19  '000422  7.73  ...  45619900           0            1
90  2015/08/18  '000422  8.81  ...  49105500           0            0
91  2015/08/17  '000422  8.49  ...  42096900           0            1
92  2015/08/14  '000422  8.48  ...  35985000           0            1
93  2015/08/13  '000422  8.20  ...  26019600           0            1
94  2015/08/12  '000422  8.38  ...  30960700           0            1
95  2015/08/11  '000422  8.41  ...  42343900           0            1
96  2015/08/10  '000422  8.28  ...  36071600           0            1
97  2015/08/07  '000422  8.15  ...  22599800           0            1
98  2015/08/06  '000422  7.88  ...  17546700           0            1
99  2015/08/05  '000422  8.10  ...  23875500           0            1

[15 rows x 11 columns]

这样一来,我们的数据中就有买卖信号了,下一步我们将使用这些买卖信号。

6. 回测第 2 步: 编写函数

因为我们现在能看到整体数据上价格的变化趋势,是完全知道什么价格买入、什么价格卖出是最合适的。

我们还是以演示用 Pandas 做量化回测的方法为主,一些变量我们就把它固定下来,最简化函数,以便于理解。

根据前面买卖信号的确定,我们现在有如下交易思路:

(1) 回测周期: 从2015年08月05日开始,到2015年12月31日结束,历时100个交易日

(2) 单笔数量: 沪深市场的默认单笔交易数量 (手数) 为 100 股,我们沿用这个规则就行了。

(3) 起始资金: 这里我们假设带着 10,000 元于2015年08月05日入市交易,希望在2015年12月31日时,资产能够大于 10,000 元。

(4) 买入次数: 这里我们做个简单的资金管理,分 6 次买入,每次买入 200 股,因为可能在买入后,后续还会出现更低的价格。

(5) 卖出次数: 这里我们做个简单的资产管理,分 3 次卖出,每次卖出 400 股,因为可能在卖出后,后续还会出现更高的价格。

(6) 买入链条: [信号成立] -> [买入 200 股] -> [花费资金 = 收盘价 (Close) * 200] -> [起始资金 - 花费资金] -> [持仓数 + 200] -> [买入完成]

(7) 卖出链条: [信号成立] -> [卖出 400 股] -> [收入资金 = 收盘价 (Close) * 400] -> [起始资金 + 收入资金] -> [持仓数 - 400] -> [卖出完成]

这里需要完善的部分其实还有很多,比如计算剩余资金是否足够买入,判断可卖数量是否小于或等于持仓数量,是否全仓进出, 卖出后计算单笔盈亏等等…

但我们为了简化逻辑,便于演示,不做过多讨论。

接下来,我们编写买卖的函数:

# 全局变量: 起始资金 10000.00 元。
Funds:float = 10000.00
# 全局变量: 持仓数量 0 (初始状态)。
Holdings:int = 0
# 全局变量: 买入计数器。
Buy_Count:int = 1
# 全局变量: 卖出计数器。
Sell_Count:int = 1

def Trade(Signal_Buy:int, Signal_Sell:int, Close:float) -> tuple:

    # global 关键字用于访问全局变量。
    global Funds
    global Holdings
    global Buy_Count
    global Sell_Count

    # 首先判断: 买入信号为 1,卖出信号为 0,买入计数器小于等于 6。
    if ((Signal_Buy == 1) and (Signal_Sell == 0) and (Buy_Count <= 6)):
        # 花费资金。
        Spend = (Close * 200)
        # 起始资金变化。
        Funds = (Funds - Spend)
        # 持仓数量变化。
        Holdings = (Holdings + 200)
        # 买入计数器 + 1。
        Buy_Count = (Buy_Count + 1)

    # 然后判断: 买入信号为 0,卖出信号为 1,卖出计数器小于等于 3,持仓大于等于 400。
    if ((Signal_Buy == 0) and (Signal_Sell == 1) and (Sell_Count <= 3) and (Holdings >= 400)):
        # 收入资金。
        Incom = (Close * 400)
        # 起始资金变化。
        Funds = (Funds + Incom)
        # 持仓数量变化。
        Holdings = (Holdings - 400)
        # 卖出计数器 + 1。
        Sell_Count = (Sell_Count + 1)

    # 返回值 (元组)。
    return (Funds, Holdings)

7. 回测第 3 步: 调用函数

经过前面的步骤,股票量化回测所需要用到的行情数据买卖信号交易函数,都已经准备好了,接下来就是查看实时的资金持仓的变动情况了。

7.1 初阶: 使用 for 循环调用函数

代码释义:

  • Stock.index: 返回 Stock 数据框的索引列表,是一个可迭代对象,Stock 有 100 行,则返回 [0, 1, 2, … 98, 99] 这样的 NdArray 列表。

  • Stock.loc[Idx, “Signal_Buy”]: 定位 Stock 数据框中的值,如 Stock.loc[1, “Signal_Buy”],则定位到 Signal_Buy 列下的第 2 行。

  • Curr_Sig_Buy = Stock.loc[Idx, “Signal_Buy”]:将定位到的 Stock 数据框的值赋值给 Curr_Sig_Buy 变量。

  • Stock.loc[1, “Funds”] = Account[0]: 将 Account 元组中的第 1 个值赋值给 Stock 数据框的 Funds 列的第 2 行。

  • Stock = Stock[[“Date”, “Code”, … “Value”, “Asset”]]: 筛选列,由于列数太多,横向显示不方便查看,只筛选出指定几列查看。

# 对 Stock 数据框进行排序,因为买卖操作是按时间顺序进行。
Stock = Stock.sort_values("Date", ascending=True)

# 对 Stock 数据框重设索引,因为排序后,原索引被打乱。
Stock = Stock.reset_index(drop=True)

# 为 Stock 数据框新建 1 列,命名为 Funds。
Stock["Funds"] = None

# 为 Stock 数据框新建 1 列,命名为 Holdings。
Stock["Holdings"] = None

# 使用 for 循环遍历 Stock 数据框的索引 (Index)。
for Idx in Stock.index:
    # 取出每 1 行的 Signal_Buy 的值。
    Curr_Sig_Buy = Stock.loc[Idx, "Signal_Buy"]
    # 取出每 1 行的 Signal_Sell 的值。
    Curr_Sig_Sell = Stock.loc[Idx, "Signal_Sell"]
    # 取出每 1 行的 Close 的值。
    Curr_Close = Stock.loc[Idx, "Close"]
    # 调用函数并把函数的返回值放在 Account 元组内。
    Account:tuple = Trade(Signal_Buy=Curr_Sig_Buy, Signal_Sell=Curr_Sig_Sell, Close=Curr_Close)
    # 将 Account 元组内的第 0 个值 (Funds) 赋值给 Funds 列的 Idx 行。
    Stock.loc[Idx, "Funds"] = Account[0]
    # 将 Account 元组内的第 1 个值 (Holdings) 赋值给 Holdings 列的 Idx 行。
    Stock.loc[Idx, "Holdings"] = Account[1]

# 计算持仓市值: 持仓市值 = 当前价 * 持仓数量。
Stock["Value"] = Stock["Close"] * Stock["Holdings"]

# 计算总资产: 总资产 = 剩余资金 + 持仓市值。
Stock["Asset"] = Stock["Funds"] + Stock["Value"]

# 筛选列: 只显示关键信息。
Stock = Stock[["Date", "Code", "Close", "Signal_Buy", "Signal_Sell", "Funds", "Holdings", "Value", "Asset"]]

print(Stock.head(15))
print(Stock.tail(15))

输出:

          Date     Code  Close  Signal_Buy  Signal_Sell    Funds Holdings   Value    Asset
0   2015/08/05  '000422   8.03           0            1  10000.0        0     0.0  10000.0
1   2015/08/06  '000422   8.07           0            1  10000.0        0     0.0  10000.0
2   2015/08/07  '000422   8.21           0            1  10000.0        0     0.0  10000.0
3   2015/08/10  '000422   8.49           0            1  10000.0        0     0.0  10000.0
4   2015/08/11  '000422   8.48           0            1  10000.0        0     0.0  10000.0
5   2015/08/12  '000422   8.24           0            1  10000.0        0     0.0  10000.0
6   2015/08/13  '000422   8.44           0            1  10000.0        0     0.0  10000.0
7   2015/08/14  '000422   8.52           0            1  10000.0        0     0.0  10000.0
8   2015/08/17  '000422   8.80           0            1  10000.0        0     0.0  10000.0
9   2015/08/18  '000422   7.96           0            0  10000.0        0     0.0  10000.0
10  2015/08/19  '000422   8.53           0            1  10000.0        0     0.0  10000.0
11  2015/08/20  '000422   8.17           0            1  10000.0        0     0.0  10000.0
12  2015/08/21  '000422   7.71           0            0  10000.0        0     0.0  10000.0
13  2015/08/24  '000422   6.94           1            0   8612.0      200  1388.0  10000.0
14  2015/08/25  '000422   6.25           1            0   7362.0      400  2500.0   9862.0

          Date     Code  Close  Signal_Buy  Signal_Sell    Funds Holdings   Value    Asset
85  2015/12/11  '000422   7.51           0            0   8642.0      400  3004.0  11646.0
86  2015/12/14  '000422   7.62           0            0   8642.0      400  3048.0  11690.0
87  2015/12/15  '000422   7.55           0            0   8642.0      400  3020.0  11662.0
88  2015/12/16  '000422   7.55           0            0   8642.0      400  3020.0  11662.0
89  2015/12/17  '000422   7.74           0            0   8642.0      400  3096.0  11738.0
90  2015/12/18  '000422   7.63           0            0   8642.0      400  3052.0  11694.0
91  2015/12/21  '000422   7.83           0            0   8642.0      400  3132.0  11774.0
92  2015/12/22  '000422   7.89           0            0   8642.0      400  3156.0  11798.0
93  2015/12/23  '000422   7.92           0            0   8642.0      400  3168.0  11810.0
94  2015/12/24  '000422   7.99           0            0   8642.0      400  3196.0  11838.0
95  2015/12/25  '000422   8.03           0            1  11854.0        0     0.0  11854.0
96  2015/12/28  '000422   7.71           0            0  11854.0        0     0.0  11854.0
97  2015/12/29  '000422   7.84           0            0  11854.0        0     0.0  11854.0
98  2015/12/30  '000422   7.93           0            0  11854.0        0     0.0  11854.0
99  2015/12/31  '000422   7.77           0            0  11854.0        0     0.0  11854.0

7.2 进阶: 使用 Pandas 的 apply 调用函数

代码释义:

  • Stock.apply(lambda Row: …): 与 Stock[“Close”].apply(lambda X: …) 不同,针对数据框的 Stock.apply 将映射每 1 行,映射的每 1 行又可以通过列名取值,需要指定轴线参数 axis = 1 来纵向映射。

  • Stock[“Funds”] = Stock[“Account”].apply(lambda X: X[0]): 取 Account 列中每个元组值中的元素,因为 Account 列包含的值都是元组,使用映射方法传入元素下标取值。

# 对 Stock 数据框进行排序,因为买卖操作是按时间顺序进行。
Stock = Stock.sort_values("Date", ascending=True)

# 用 Pandas 的 apply 方法传入编写好的交易函数计算卖点。
Stock["Account"] = Stock.apply(lambda Row: Trade(Signal_Buy=Row["Signal_Buy"], Signal_Sell=Row["Signal_Sell"], Close=Row["Close"]), axis=1)

# 用 Pandas 的 apply 方法传入 lambda 一次性函数,将 Account 元组中的第 0 个值取出来。
Stock["Funds"] = Stock["Account"].apply(lambda X: X[0])

# 用 Pandas 的 apply 方法传入 lambda 一次性函数,将 Account 元组中的第 1 个值取出来。
Stock["Holdings"] = Stock["Account"].apply(lambda X: X[1])

# 计算持仓市值: 持仓市值 = 当前价 * 持仓数量。
Stock["Value"] = Stock["Close"] * Stock["Holdings"]

# 计算总资产: 总资产 = 剩余资金 + 持仓市值。
Stock["Asset"] = Stock["Funds"] + Stock["Value"]

# 筛选列: 只显示关键信息。
Stock = Stock[["Date", "Code", "Close", "Signal_Buy", "Signal_Sell", "Account", "Funds", "Holdings", "Value", "Asset"]]

print(Stock.head(15))
print(Stock.tail(15))

输出:

          Date     Code  Close  Signal_Buy  Signal_Sell        Account    Funds  Holdings   Value    Asset
0   2015/08/05  '000422   8.03           0            1   (10000.0, 0)  10000.0         0     0.0  10000.0 
1   2015/08/06  '000422   8.07           0            1   (10000.0, 0)  10000.0         0     0.0  10000.0 
2   2015/08/07  '000422   8.21           0            1   (10000.0, 0)  10000.0         0     0.0  10000.0 
3   2015/08/10  '000422   8.49           0            1   (10000.0, 0)  10000.0         0     0.0  10000.0 
4   2015/08/11  '000422   8.48           0            1   (10000.0, 0)  10000.0         0     0.0  10000.0 
5   2015/08/12  '000422   8.24           0            1   (10000.0, 0)  10000.0         0     0.0  10000.0 
6   2015/08/13  '000422   8.44           0            1   (10000.0, 0)  10000.0         0     0.0  10000.0 
7   2015/08/14  '000422   8.52           0            1   (10000.0, 0)  10000.0         0     0.0  10000.0 
8   2015/08/17  '000422   8.80           0            1   (10000.0, 0)  10000.0         0     0.0  10000.0 
9   2015/08/18  '000422   7.96           0            0   (10000.0, 0)  10000.0         0     0.0  10000.0 
10  2015/08/19  '000422   8.53           0            1   (10000.0, 0)  10000.0         0     0.0  10000.0 
11  2015/08/20  '000422   8.17           0            1   (10000.0, 0)  10000.0         0     0.0  10000.0 
12  2015/08/21  '000422   7.71           0            0   (10000.0, 0)  10000.0         0     0.0  10000.0 
13  2015/08/24  '000422   6.94           1            0  (8612.0, 200)   8612.0       200  1388.0  10000.0 
14  2015/08/25  '000422   6.25           1            0  (7362.0, 400)   7362.0       400  2500.0   9862.0 

          Date     Code  Close  Signal_Buy  Signal_Sell        Account    Funds  Holdings   Value    Asset
85  2015/12/11  '000422   7.51           0            0  (8642.0, 400)   8642.0       400  3004.0  11646.0
86  2015/12/14  '000422   7.62           0            0  (8642.0, 400)   8642.0       400  3048.0  11690.0
87  2015/12/15  '000422   7.55           0            0  (8642.0, 400)   8642.0       400  3020.0  11662.0
88  2015/12/16  '000422   7.55           0            0  (8642.0, 400)   8642.0       400  3020.0  11662.0
89  2015/12/17  '000422   7.74           0            0  (8642.0, 400)   8642.0       400  3096.0  11738.0
90  2015/12/18  '000422   7.63           0            0  (8642.0, 400)   8642.0       400  3052.0  11694.0
91  2015/12/21  '000422   7.83           0            0  (8642.0, 400)   8642.0       400  3132.0  11774.0
92  2015/12/22  '000422   7.89           0            0  (8642.0, 400)   8642.0       400  3156.0  11798.0
93  2015/12/23  '000422   7.92           0            0  (8642.0, 400)   8642.0       400  3168.0  11810.0
94  2015/12/24  '000422   7.99           0            0  (8642.0, 400)   8642.0       400  3196.0  11838.0
95  2015/12/25  '000422   8.03           0            1   (11854.0, 0)  11854.0         0     0.0  11854.0
96  2015/12/28  '000422   7.71           0            0   (11854.0, 0)  11854.0         0     0.0  11854.0
97  2015/12/29  '000422   7.84           0            0   (11854.0, 0)  11854.0         0     0.0  11854.0
98  2015/12/30  '000422   7.93           0            0   (11854.0, 0)  11854.0         0     0.0  11854.0
99  2015/12/31  '000422   7.77           0            0   (11854.0, 0)  11854.0         0     0.0  11854.0

8. 回测第 4 步: 其它信息

到此,回测就结束了。

当然还有一些其它有关回测的信息,可以从这份回测数据中扩展出来。

比如资产收益率夏普比率最大回撤等等,只需要通过进一步的计算或即可得到。

再比如买点分布买卖频谱资产变化趋势图等等,只需要通过借助 Matplotlib 即可快速绘制。

本例只是针对如何使用 Pandas 做股票量化回测,其余部分就不展开叙述了,这里只拿资产收益率做个示例。

资产收益率:

print("资产收益率: %.2f" % ((Stock["Asset"][99] - Stock["Asset"][0]) / Stock["Asset"][99]))

输出:

资产收益率: 0.16

9. 总结

以上就是使用 Pandas 进行股票量化回测 (Quantitative Backtesting) 的全部内容。

更多内容可以访问我的代码仓库:

https://gitee.com/goufeng928/finance

https://github.com/goufeng928/finance

  • 44
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mostcow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值