Python量化交易学习——Part12:回归模型的典型应用
回归模型在很多的时候被应用于对股票的基本面数据进行分析,例如经典的CAPM模型、Fama-French三因子模型以及最新的PB_ROE模型等。这些都是已经应用于现实中的金融市场并获得较好收益的经典模型。本章将通过介绍PB_ROE模型,进一步讲解回归分析在实战过程中的使用。
PB-ROE 回归模型的使用
选股的核心思想在于寻找价格低于内在价值的股票,从而获取未来价格修复的收益。具体来看,符合价值投资理念的标的首先要有良好的基本面,其次要有合理的价格。PB-ROE选股模型便是价值投资中的一种经典方法。该模型中,ROE作为基本面指标,用于衡量公司质地是否优良,PE作为估值指标,用于衡量当前价格是否低估。
市净率(Price-to-Book Ratio,简称P/B PBR)指的是每股股价与每股净资产的比率。 市净率可用于股票投资分析,一般来说市净率较低的股票,投资价值较高,相反,则投资价值较低;但在判断投资价值时还要考虑当时的市场环境以及公司经营情况、盈利能力等因素。
股东权益报酬率=可供普通股东分配的净利润/平均普通股东权益×100%,这个比率通常也被称为净资产收益率,英文缩写ROE
因此PB-ROE选股策略可以概况为以下几点:
(1)获取股票的PB\ROE数据,建立PB-ROE数据之间的回归模型;
(2)一般来说,净资产收益率高的股票,市净率也应该高一些,因此可以根据回归模型计算单个股票拟合后的PB值,与真实PB值进行比较,并对差值进行排序,当(真实PB-预测PB)的值越大,说明股票越被高估,当值越小,说明越被低估。
(3)根据排序后的结果进行股票买卖
(4)在导入股票的时候,我们只选择了沪深主板的股票,原因很简单,因为我没有创业板和科创板买卖权限…
# coding=utf-8
from __future__ import print_function, absolute_import
import statsmodels.api as sm
import numpy as np
import matplotlib.pyplot as plt
import random
import pandas as pd
from gm.api import *
import datetime
import os
import statsmodels.api as sm
# from MSCI_tools import mcsi_tools as toolsset_token('a7f3404da7e8c9a3aea631b5ae0c893b1d57161d')
now, hour_and_mins = str(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')).split(" ")# 获取A股除了流动性不足的股票以及ST股票外,入选中证组合的几乎全部的股票清单(沪深主板)
def get_symbol_list_all():A_share_list = {"SHSE.000010": "SHANG_ZHENG180", "SHSE.000016": "SHANG_ZHENG50", "SHSE.000300": "HU_SHEN300","SHSE.000903": "ZHONG_ZHENG100","SHSE.000904": "ZHONG_ZHENG200", "SHSE.000905": "ZHONG_ZHENG500", "SHSE.000906": "ZHONG_ZHENG800","SHSE.000907": "ZHONG_ZHENG700", "SHSE.000852": "ZHONG_ZHENG1000"}A_share_main = {"600", "601", "602", "603", "000", "001", "002", "003"} #沪深主板股票代码的开头symbol_list_all = pd.DataFrame([])for key in A_share_list.keys():symbol_list = stk_get_index_constituents(key) # 获取指数成分股数据symbol_list_all = pd.concat([symbol_list_all, symbol_list], ignore_index=True)# symbol_list_all =symbol_list_all.append(symbol_list) #新语句中append已经弃用symbol_set = symbol_list_all["symbol"].valuestarget_symbol_list= []for symbol in symbol_set:#从股票池中寻找主板股票if symbol[5:8] in A_share_main and symbol not in target_symbol_list:target_symbol_list.append(symbol)print("沪深主板股票资源池数量",len(target_symbol_list))return target_symbol_listdef PB_ROE_strategy(symbol_list,now):last_day = get_previous_trading_date("SHSE",now)ROE = stk_get_finance_deriv_pt(symbols=symbol_list,fields="roe",date=last_day,df=True)ROE = ROE.sort_values(["symbol"],ascending=False)ROE = ROE.reset_index(drop=True)PB = stk_get_daily_valuation_pt(symbols=symbol_list, fields="pb_mrq", trade_date=last_day, df=True)PB = PB.sort_values(["symbol"],ascending=False)PB = PB.reset_index(drop=True)ROE_PB_list = pd.merge(ROE, PB)drop_num =[]for num in range(len(ROE_PB_list)-1):if ROE_PB_list["roe"].values[num] < 0 or ROE_PB_list["pb_mrq"].values[num] > 6:drop_num.append(num)ROE_PB_list = ROE_PB_list.drop(drop_num)ROE_PB_list.dropna()symbol_roe = ROE_PB_list["roe"].values #自变量symbol_pb = ROE_PB_list["pb_mrq"].values #因变量symbol_roe[np.isnan(symbol_roe)] = 0symbol_roe[np.isinf(symbol_roe)] = 0symbol_pb[np.isnan(symbol_pb)] = 0symbol_pb[np.isinf(symbol_pb)] = 0#进行回归分析symbol_roe = sm.add_constant(symbol_roe)model = sm.OLS(symbol_pb, symbol_roe) # 前面为因变量,后面为自变量result = model.fit()bias, weight = (result.params) # 前面为截距,后面为斜率ROE_PB_list["PB_new"] = bias + ROE_PB_list["roe"]*weight# print(ROE_PB_list)ROE_PB_list["value"] = ROE_PB_list["PB_new"] -ROE_PB_list["pb_mrq"] #大于0说明是低估ROE_PB_list = ROE_PB_list.sort_values(["value"],ascending=False)ROE_PB_list = ROE_PB_list.reset_index(drop=True)# print(ROE_PB_list)#绘制ROE-PB关系曲线# fig = plt.figure()# #将画图窗口分为1行1列,选择第一块区域做子图# ax = fig.add_subplot(111)# ax.set_title("ROE-PB model")# ax.set_xlabel("ROE")# ax.set_ylabel("PB")# ax.scatter(ROE_PB_list["roe"].values,ROE_PB_list["pb_mrq"].values,c="k",marker=".")# ax.plot(ROE_PB_list["roe"].values,ROE_PB_list["PB_new"].values,c="b")# plt.show()return ROE_PB_list["symbol"].valuessymbol_list = get_symbol_list_all()
PB_ROE_strategy(symbol_list,now)
# symbol_list1 = stk_get_index_constituents("SHSE.000903")
# symbol_list2 = stk_get_index_constituents("SHSE.000016")
# print(symbol_list1)
# print(symbol_list2)
# print(symbol_list1.append(symbol_list2))# coding=utf-8def init(context):# 每天14:50 定时执行algo任务,# algo执行定时任务函数,只能传context参数# date_rule执行频率,目前暂时支持1d、1w、1m,其中1w、1m仅用于回测,实时模式1d以上的频率,需要在algo判断日期# time_rule执行时间, 注意多个定时任务设置同一个时间点,前面的定时任务会被后面的覆盖schedule(schedule_func=algo, date_rule='1m', time_rule='09:31:00')context.count = 1context.num = 5context.fields = "open,high,low,close"def algo(context):now = context.nowlast_day = get_previous_trading_date("SHSE",now)target_symbol_list = get_symbol_list_all()ROE_PB_list = PB_ROE_strategy(target_symbol_list,now)target_list = ROE_PB_list[0:context.num]positions = context.account().positions()for position in positions:symbol = position["symbol"]if symbol not in target_list:order_target_percent(symbol=symbol,percent=0,order_type=OrderType_Market,position_side=PositionSide_Long)positions = context.account().positions()holded_symbol = []for position in positions:symbol = position["symbol"]holded_symbol.append(symbol)print(holded_symbol)for symbol in target_list:if symbol not in holded_symbol:print(symbol)data = history_n(symbol=symbol,frequency='1d',count=2,end_time=now,fields=context.fields,adjust=ADJUST_PREV, df=True)open =data["open"].valuesclose = data["close"].valuesif open[-1] < close[0]*1.08: #没有涨停,涨停就无法买入了order_target_percent(symbol=symbol,percent=(1/context.num)*0.95,order_type=OrderType_Market,position_side=PositionSide_Long)else:pass# 查看最终的回测结果
def on_backtest_finished(context, indicator):print(indicator)if __name__ == '__main__':'''strategy_id策略ID, 由系统生成filename文件名, 请与本文件名保持一致mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTESTtoken绑定计算机的ID, 可在系统设置-密钥管理中生成backtest_start_time回测开始时间backtest_end_time回测结束时间backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POSTbacktest_initial_cash回测初始资金backtest_commission_ratio回测佣金比例backtest_slippage_ratio回测滑点比例backtest_match_mode市价撮合模式,以下一tick/bar开盘价撮合:0,以当前tick/bar收盘价撮合:1'''run(strategy_id='8e30d6a2-3b4c-11ef-94ca-e073e7f1ba05',filename='pb_roe_strategy.py',mode=MODE_BACKTEST,token='自己的token码',backtest_start_time='2022-11-01 09:30:00',backtest_end_time='2024-06-20 15:00:00',backtest_adjust=ADJUST_PREV,backtest_initial_cash=10000,backtest_commission_ratio=0.0001,backtest_slippage_ratio=0.0001,backtest_match_mode=1)
结果如下:从结果看,获得了一定的超额收益,但收益并不明显,通过绘制PB-ROE散点图我们也可以看到,两者之间的关系并不是很明显。