回归模型在很多的时候被应用于对股票的基本面数据进行分析,例如经典的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 tools
set_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"].values
target_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_list
def 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)] = 0
symbol_roe[np.isinf(symbol_roe)] = 0
symbol_pb[np.isnan(symbol_pb)] = 0
symbol_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"].values
symbol_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-8
def 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 = 1
context.num = 5
context.fields = "open,high,low,close"
def algo(context):
now = context.now
last_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"].values
close = data["close"].values
if 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_BACKTEST
token绑定计算机的ID, 可在系统设置-密钥管理中生成
backtest_start_time回测开始时间
backtest_end_time回测结束时间
backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
backtest_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散点图我们也可以看到,两者之间的关系并不是很明显。