基于 qstock 实现条件选股回测 您所在的位置:网站首页 股票回测英文翻译 基于 qstock 实现条件选股回测

基于 qstock 实现条件选股回测

2024-06-03 16:53| 来源: 网络整理| 查看: 265

基于 qstock 实现条件选股回测¶

基于 qstock 获取多只股票的历史价格数据,对选股结果进行定期调仓回测。

导入包和定义获取、处理数据、绩效评价和绘图等函数¶

qstock由“Python 金融量化”公众号开发,可用于获取各个市场的实时和历史数据。本文基于 qstock 获取历史数据,对选股结果进行回测。

Python# 导入包 import pandas as pd import qstock as qs from tqdm import tqdm import warnings from highcharts import Highstock # 定义函数 # 获取调仓日以及持仓的股票 def get_change_position_date_and_stocks(selected_stocks): # 根据报告期对应的截至日期,提取出每季度选出的股票代码 report_date_and_stocks = ( selected_stocks.groupby("报告日期的截止日")["股票代码"] .apply(lambda g: g.values.tolist()) .to_dict() ) # 将报告报告期对应的截至日期转换为调仓日期 change_position_date_list = [] for report_date in report_date_and_stocks.keys(): # 若报告期的截止日是第一季度的最后一天,则对应的调仓日是4月30日 if report_date.month == 3: change_position_date = "{}{}{}".format(report_date.year, "04", "30") # 若报告期的截止日是第二季度的最后一天,则对应的调仓日是8月31日 elif report_date.month == 6: change_position_date = "{}{}{}".format(report_date.year, "08", "31") # 若报告期的截止日是第三季度的最后一天,则对应的调仓日是10月31日 elif report_date.month == 9: change_position_date = "{}{}{}".format(report_date.year, "10", "31") else: raise ValueError change_position_date_list.append(change_position_date) change_position_date_and_stocks = dict( zip(change_position_date_list, report_date_and_stocks.values()) ) return change_position_date_and_stocks # 获取多只股票的每日收盘价格 def get_price_of_many_stocks(code_list, start, end): price = pd.DataFrame() for code in tqdm(code_list, leave=False): price_stock = qs.get_data( code_list=code, start=start, end=end, freq="d", fqt=2 )["close"] price_stock.name = code price = pd.concat([price, price_stock], axis=1) return price # 将收益率数据转换为价格数据 def prices_from_returns(returns): ret = 1 + returns ret.iloc[0] = 1 return ret.cumprod() # 计算策略评价指标 def strategy_evaluate(equity): """ :param equity: 每天的资金曲线 :return: """ # ===新建一个dataframe保存回测指标 results = pd.DataFrame() # 将数字转为百分数 def num_to_pct(value): return "%.2f%%" % (value * 100) # ===计算累积净值 results.loc[0, "累积净值"] = round(equity["组合净值"].iloc[-1], 2) # ===计算年化收益 annual_return = (equity["组合净值"].iloc[-1]) ** ( 1 / (equity["交易日期"].iloc[-1] - equity["交易日期"].iloc[0]).days * 365 ) - 1 results.loc[0, "年化收益"] = str(round(annual_return * 100, 2)) + "%" # ===计算最大回撤,最大回撤的含义:《如何通过3行代码计算最大回撤》https://mp.weixin.qq.com/s/Dwt4lkKR_PEnWRprLlvPVw # 计算当日之前的资金曲线的最高点 equity["max2here"] = equity["组合净值"].expanding().max() # 计算到历史最高值到当日的跌幅,drowdwon equity["dd2here"] = equity["组合净值"] / equity["max2here"] - 1 # 计算最大回撤,以及最大回撤结束时间 end_date, max_draw_down = tuple( equity.sort_values(by=["dd2here"]).iloc[0][["交易日期", "dd2here"]] ) # 计算最大回撤开始时间 start_date = ( equity[equity["交易日期"] 0 ? ' + '可以在正收益的数值前加上“+” # 绘制纵轴为0的横线 "plotLines": [{"value": 0, "width": 2, "color": "silver"}], }, "plotOptions": {"series": {"compare": "percent"}}, "tooltip": { "pointFormat": '{series.name}: {point.y} ({point.change}%)', "valueDecimals": 2, # 默认显示的小数位 }, } # 应用绘图参数 H.set_dict_options(options) # 如果指定了输出路径,则输出html文件到这个路径 if output_path: f = open("{}.html".format(output_path), "w") f.write(H.htmlcontent) f.close() return H Python# 读取历史选股数据(每季度 15 只) selected_stocks = pd.read_csv( "./selected_stocks.csv", parse_dates=["报告日期的截止日"], encoding="gbk" ) # parse_dates 将'报告日期的截止日'这一列识别为日期格式,gbk 编码支持中文 # 截取“股票代码”和“报告日期的截止日”这两列 selected_stocks = selected_stocks[["股票代码", "报告日期的截止日"]] # 将“股票代码”的后缀“.SH”去掉 selected_stocks["股票代码"] = selected_stocks["股票代码"].apply(lambda x: x[:-3]) # 获取调仓日以及持仓的股票 change_position_date_and_stocks = get_change_position_date_and_stocks(selected_stocks) # 获取调仓日的列表 change_position_date_list = list(change_position_date_and_stocks.keys()) 计算组合收益率(使用简单加权的粗略算法)¶

使用简单加权的粗略算法计算组合收益率,这一方法存在一定的缺陷。

例如,若资产组合包含两个资产,第一个资产每天的收益率都是\(-10\%\),第二个资产每天的收益率都是\(+10\%\)。

按照简单加权的粗略算法,第一天,第一个资产的价格变成了\(1\times(1-10\%)=0.9\),第二个资产的价格变成了\(1\times(1+10\%)=1.1\),因此资产组合的净值是\(\frac{0.9+1.1}{2}=1\)。根据简单加权的粗略算法,资产组合在第一天的收益率是\(\frac{-10\%+10\%}{2}=0\)。这个结果在第一天确实是对的。

但是,第二天的时候,第一个资产的价格变成了\(0.9\times(1-10\%)=0.81\),第二个资产的价格变成了\(1.1\times(1+10\%)=1.21\),因此资产组合的净值是\(\frac{0.81+1.21}{2}=1.005\),因此资产组合在第二天的收益率是 0.5%。但是,根据简单加权的粗略算法,资产组合在第一天的收益率仍然是\(\frac{-10\%+10\%}{2}=0\)。

造成这一差异的根本原因在于,资产组合内部的权重是会随着各资产的涨跌而改变的。一个资产上涨得越多,它的涨跌幅对整个资产组合涨跌幅的影响也越大。

这里为了简便,只使用简单加权的粗略算法计算组合收益率。更为精确的算法可以是:将各股票看作是投资组合的一部分,每天跟踪这一部分净值的变化,到下一调仓日再将各部分的净值相加,即得到整个投资组合的净值。

Python# 创建空数据框,用于存放组合收益率 portfolio_return = pd.DataFrame() # 对每一个调仓日,获取当日的股票价格,将价格转换为收益率,并计算组合在这段持有期的收益率 pbar = tqdm(change_position_date_list) for change_position_date in pbar: pbar.set_description("正在计算从%s开始的组合每日收益率" % change_position_date) # 获取这一个调仓日至下一个调仓日之间的持仓股票价格 code_list = change_position_date_and_stocks[change_position_date] # 获取下一个调仓日 if ( change_position_date == change_position_date_list[-1] ): # 如果是最后一个调仓日,则下一个调仓日在change_position_date_list中找不到,需要手动设置为2022年8月31日 next_change_position_date = "20220831" else: # 否则下一个调仓日可以直接在change_position_date_list中找到 next_change_position_date = change_position_date_list[ change_position_date_list.index(change_position_date) + 1 ] # 获取这一个调仓日至下一个调仓日之间的持仓股票价格 stock_price = get_price_of_many_stocks( code_list=code_list, start=change_position_date, end=next_change_position_date ) # fqt=2表示后复权 # 将价格转换为收益率,且删除全是空值的行(即第一行) stock_return = stock_price.pct_change().dropna(how="all") # 对收益率进行加权求和,得到组合收益率,并将结果添加到portfolio_return中 portfolio_return_in_this_quarter = stock_return.mul( 1 / len(stock_return.columns), axis=1 ).sum(axis=1) portfolio_return = pd.concat( [portfolio_return, portfolio_return_in_this_quarter], axis=0 ) 正在计算从20220430开始的组合每日收益率: 100%|██████████| 37/37 [01:11 获取组合净值和基准指数净值¶ Python# 将组合收益率转换为累计净值 portfolio_net_value = prices_from_returns(portfolio_return) # 修改列名 portfolio_net_value.columns = ["组合净值"] # 修改索引名 portfolio_net_value.index.name = "date" Python# 获取中证500指数的价格 index_price = qs.get_price( code_list=["中证500"], start="20100504", end="20220831", freq="d", fqt=2 ) # 将中证500指数的价格归一化 index_price = index_price.div(index_price.iloc[0]) 100%|██████████| 1/1 [00:00 Python# 将组合收益率和中证 500 指数的价格合并 equity = portfolio_net_value.merge( index_price, how="left", left_index=True, right_index=True ) 绘制净值曲线¶ Python# 绘制组合净值和中证500指数的净值曲线 draw_equity_curve(equity)

Python# 修改列名 equity.rename(columns={"中证 500": "指数收益率"}, inplace=True) # 计算组合收益率 equity["组合收益率"] = equity["组合净值"].pct_change() # 计算指数收益率 equity["指数收益率"] = equity["指数收益率"].pct_change() # 生成交易日期列 equity["交易日期"] = equity.index 绩效评价¶ Python# 计算策略评价指标 overall_performance, year_return, month_return = strategy_evaluate(equity) Pythonoverall_performance 0 累积净值 6.31 年化收益 16.11% 最大回撤 -56.75% 最大回撤开始时间 2015-06-12 00:00:00 最大回撤结束时间 2018-10-18 00:00:00 年化收益/回撤比 0.28 Pythonyear_return 组合收益率 指数收益率 超额收益 交易日期 2010-12-31 -3.12% 11.33% -14.45% 2011-12-31 -26.29% -33.83% 7.53% 2012-12-31 32.59% 0.28% 32.31% 2013-12-31 36.37% 16.89% 19.48% 2014-12-31 78.17% 39.01% 39.17% 2015-12-31 68.05% 43.12% 24.93% 2016-12-31 -3.76% -17.78% 14.02% 2017-12-31 -6.39% -0.20% -6.19% 2018-12-31 -36.16% -33.32% -2.84% 2019-12-31 27.43% 26.38% 1.05% 2020-12-31 49.32% 20.87% 28.45% 2021-12-31 44.83% 15.58% 29.24% 2022-12-31 3.05% -16.36% 19.40% Pythonmonth_return 组合收益率 指数收益率 超额收益 交易日期 2010-05-31 -10.60% -7.44% -3.17% 2010-06-30 -8.02% -10.72% 2.70% 2010-07-31 15.30% 14.37% 0.93% 2010-08-31 10.09% 9.50% 0.59% 2010-09-30 -9.61% 1.55% -11.16% ... ... ... ... 2022-04-30 -10.71% -11.02% 0.31% 2022-05-31 4.69% 7.08% -2.38% 2022-06-30 5.59% 7.10% -1.51% 2022-07-31 6.65% -2.48% 9.13% 2022-08-31 -4.40% -2.20% -2.20%

148 rows × 3 columns

评论 回到页面顶部

Copyright © 2022 - Jeremy Feng

湘 ICP 备 2023001413 号 - 1


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有