基于高股息高分红优化的量化选股模型

编程入门 行业动态 更新时间:2024-10-25 08:27:09

基于高股息高分红优化的量化<a href=https://www.elefans.com/category/jswz/34/1767050.html style=选股模型"/>

基于高股息高分红优化的量化选股模型

注:以下内容不构成任何投资建议,仅作为数据分析学习用途公开
该模型是中金公司提出的,自然语言描述如下:

构造基于高分红高股息优化的量化选股思路。我们基于前述分析,构造基于分红、股息率、自由现金流等指标的选股模型,具体要求:1)筛选时间点:每年4月底年报发布完毕后,统一按年度频率筛选。2)市值、市盈率要求:市值大于50亿元,市盈率为正但小于25倍。3)股息率和分红标准:非金融公司股息率大于3%,分红比例当年大于45%或者3年平均大于45%;金融股息率大于5%,当年分红比例大于35%或3年平均大于35%。4)自由现金流标准:金融无现金流要求;非金融自由现金流/所有者权益大于8%;5)其它:3年平均ROE金融大于10%,非金融大于8%。

全文见这里
基于中金公司的模型描述,以及Baostock金融数据接口和某花顺金融数据,使用Python3.9实现,直接上代码:

import time
import baostock as bs
import pandas as pd
import requests
import json
import datetime
from bs4 import BeautifulSoup
import pickle
# 获取两市股票列表,注:sz代表深市,sh代表沪市
pd.set_option('display.max_columns', None)
myruilicence = 'bfbfudsfuoisuiblblkj7b'
year = 2023 # 计算的年份
# 每日更新股票列表并写入文件,覆盖上次内容
get_stock_list_api = '/hslt/list/'+myruilicence
# resopnse = requests.get(get_stock_list_api)
# f = open('stocks.list', 'w')
# f.write(resopnse.text)
# f.close()
f = open('stocks2.list', 'r')
json_data = f.readline()
# 读取list格式的两市所有上市公司股票代码
stock_lists = json.loads(json_data)
f.close()
#### 基于高分红高股息优化的量化选股模型 ####
# 计算滚动市盈率peTTM
def getPeTTM(stock_code,start_date,end_date):rs = bs.query_history_k_data_plus(stock_code,"date,code,close,peTTM,pbMRQ,psTTM,pcfNcfTTM",start_date=start_date, end_date=end_date,frequency="d", adjustflag="3")result_list = []while (rs.error_code == '0') & rs.next():# 获取一条记录,将记录合并在一起result_list.append(rs.get_row_data())result = pd.DataFrame(result_list, columns=rs.fields)peTTM = float(result['peTTM'][0])return peTTM
# 计算上市公司已上市时间(天为单位,截至日期为程序运行时的当天)
def getIPODate(stock_code):rs = bs.query_stock_basic(code=stock_code)data_list = []while (rs.error_code == '0') & rs.next():# 获取一条记录,将记录合并在一起data_list.append(rs.get_row_data())result = pd.DataFrame(data_list, columns=rs.fields)ipoDate = datetime.datetime.strptime(result['ipoDate'][0], "%Y-%m-%d").date()nowDate = datetime.datetime.now().date()ipoDays = (nowDate-ipoDate).daysreturn ipoDays
# 计算最新总市值
def getMK(stock_code,start_date,end_date,year):profit_list = []rs_profit = bs.query_profit_data(code=stock_code, year=year, quarter=1)while (rs_profit.error_code == '0') & rs_profit.next():profit_list.append(rs_profit.get_row_data())result_profit = pd.DataFrame(profit_list, columns=rs_profit.fields)totalShare = result_profit['totalShare']  # 提取总股本rs = bs.query_history_k_data_plus(stock_code,"date,code,open,high,low,close,preclose,volume,amount,adjustflag,turn,tradestatus,pctChg,isST",start_date=start_date, end_date=end_date,frequency="d", adjustflag="3")data_list = []while (rs.error_code == '0') & rs.next():data_list.append(rs.get_row_data())result = pd.DataFrame(data_list, columns=rs.fields)close_price = result['close']  # 提取最新收盘股价if result.empty or result_profit.empty:return [0,0,0]# 计算总市值totalMK = 0try:totalMK = float(close_price[0]) * float(totalShare[0])except KeyError as e:print("股票代码", stock_code, "市值计算错误")return [totalMK, close_price[0], totalShare[0]]
# 计算近三年平均股息率,以百分数点数形势返回,返回8.9表明股息率为8.9%
def getYield(stock_code,price,years):with open("stock_bonuses.pkl", "rb") as file:dict = pickle.load(file)file.close()# 提取最近三年的分红数据data = []try:for year in years:for item in dict[stock_code]:if str(item[0]).find(str(year)) != -1:data.append(item)except KeyError as e:return 0Yield = 0for item in data:zeroindex = str(item[1]).find("不分配不转增")zgindex = str(item[1]).find("10转")sp1 = str(item[1]).find("10送")sp2 = str(item[1]).find("股派")# 计算派股的情形,派股暂时按0%计算股息if zeroindex == -1:try:bonous = 0if zgindex == -1:if sp1 == -1 and sp2 == -1:bonous = float(str(item[1]).replace("10派", "").replace("元(含税)", ""))# 计算又送股又派股的情形if sp1 != -1 and sp2 != -1:bonous = float(str(item[1]).split("股派")[1].replace("元(含税)", ""))Yield  = Yield + bonousexcept ValueError as e:print("分红比例计算错误:", stock_code)print("错误结果", ((Yield/3)/float(price))*10)print(e)return ((Yield/3)/float(price))*10# 提取同花顺的上市公司分红数据
def getBonus(stock_list):url_pre = "/"url_last = "/bonusn.html"headers = {'cookie':'v=g6','sec-ch-ua': '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"','sec-ch-ua-mobile':'?0','sec-ch-ua-platform':'windows','Upgrade-Insecure-Requests':'1','User-Agent': 'Mozilla / 5.0(Windows NT 10.0; Win64; x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 116.0.0.0 Safari / 537.36','Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7','Sec-Fetch-Site': 'none','Sec-Fetch-Mode': 'navigate','Sec-Fetch-User': '?1','Sec-Fetch-Dest': 'document','Accept-Encoding': 'gzip, deflate','Accept-Language': 'en, en - IN;q = 0.9'}dict = {}for stock_code in stock_list:stock_bonuses = []url = url_pre+stock_code+url_last# url = ''response = requests.get(url=url, headers=headers)time.sleep(0.5)response.encoding = 'utf8'html = response.contentsoup = BeautifulSoup(html, 'html.parser')years = soup.select(".trHead")  #财报年度和类型(年报/中报)tl = soup.select(".tl")   #分红情况for i in range(1, len(years)):key = str(years[i]).replace('<td class="trHead">', "").replace('<td class="tl">', '').replace("</td>", "")value = str(tl[i]).replace("</td>", "").replace('<td class="tl">', '')stock_bonuses.append([key, value])dict[stock_code] = stock_bonuseswith open("stock_bonuses1.pkl", "wb") as file:pickle.dump(dict, file)file.close()# 提取上市公司每年扣非净利润
def getDeductionOfNonNetProfit(stock_list):url_pre = "/?code="url_last = "&market=33&id=index_deduct_holder_net_profit&period=-1&locale=zh_CN"headers = {'cookie': 'v=f3','sec-ch-ua': '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"','sec-ch-ua-mobile': '?0','sec-ch-ua-platform': 'windows','Upgrade-Insecure-Requests': '1','User-Agent': 'Mozilla / 5.0(Windows NT 10.0; Win64; x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 116.0.0.0 Safari / 537.36','Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7','Sec-Fetch-Site': 'none','Sec-Fetch-Mode': 'navigate','Sec-Fetch-User': '?1','Sec-Fetch-Dest': 'document','Accept-Encoding': 'gzip, deflate','Accept-Language': 'en, en - IN;q = 0.9'}dict = {}for stock_code in stock_list:DeductionOfNonNetProfit = []url = url_pre + stock_code + url_lastresponse = requests.get(url=url, headers=headers)# time.sleep(0.5)response.encoding = 'utf8'json_data = response.textstock_lists = json.loads(json_data)for item in stock_lists["data"]["data"]:DeductionOfNonNetProfit.append(item)dict[stock_code] = DeductionOfNonNetProfitwith open("DeductionOfNonNetProfit1.pkl", "wb") as file:pickle.dump(dict, file)file.close()
# 提取上市公司ROE数据
def getROE(stock_list):url_pre = "/?code="url_last = "&market=33&id=index_weighted_avg_roe&period=-1&locale=zh_CN"headers = {'cookie': 'v=d7','sec-ch-ua': '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"','sec-ch-ua-mobile': '?0','sec-ch-ua-platform': 'windows','Upgrade-Insecure-Requests': '1','User-Agent': 'Mozilla / 5.0(Windows NT 10.0; Win64; x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 116.0.0.0 Safari / 537.36','Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7','Sec-Fetch-Site': 'none','Sec-Fetch-Mode': 'navigate','Sec-Fetch-User': '?1','Sec-Fetch-Dest': 'document','Accept-Encoding': 'gzip, deflate','Accept-Language': 'en, en - IN;q = 0.9'}dict = {}for stock_code in stock_list:ROE = []url = url_pre + stock_code + url_lastresponse = requests.get(url=url, headers=headers)# time.sleep(0.5)response.encoding = 'utf8'json_data = response.textstock_lists = json.loads(json_data)for item in stock_lists["data"]["data"]:ROE.append(item)dict[stock_code] = ROEprint(dict)with open("ROE1.pkl", "wb") as file:pickle.dump(dict, file)file.close()
# 提取上市公司所属行业
def queryIndustry(stock_code):rs = bs.query_stock_industry(code=stock_code)industry_list = []while (rs.error_code == '0') & rs.next():industry_list.append(rs.get_row_data())result = pd.DataFrame(industry_list, columns=rs.fields)return(result.industry[0])# 计算一只股票指定年份的分红比例=(总股本*年报每股派息)/年报扣非净利润
def getDividendRatio(stock_code, totalShare, year):# 读取年报每股派息表with open("stock_bonuses.pkl", "rb") as file:bonusesData = pickle.load(file)file.close()# 读取净利润表with open("DeductionOfNonNetProfit.pkl", "rb") as file:deductionOfNonNetProfitData = pickle.load(file)bonuseindex = []for i in range(0, len(bonusesData[stock_code])):if str(bonusesData[stock_code][i][0]).find(str(year)) != -1 :bonuseindex.append(i)file.close()bonous = 0# 计算年度每股派息for i in bonuseindex:if bonusesData[stock_code][i][1].find("不分配不转增") == -1:zgindex = bonusesData[stock_code][i][1].find("10转")sp1 = str(bonusesData[stock_code][i]).find("10送")sp2 = str(bonusesData[stock_code][i]).find("股派")if zgindex == -1:if sp1 == -1 and sp2 == -1:bonous = bonous + float(str(bonusesData[stock_code][i][1]).replace("10派", "").replace("元(含税)", ""))# 计算又送股又派股的情形if sp1 != -1 and sp2 != -1:bonous = bonous + float(str(bonusesData[stock_code][i][1]).split("股派")[1].replace("元(含税)", ""))else:bonous = bonous + 0# 计算年度总分红金额totalBonous = (bonous/10)*totalShare# 提取年度扣非净利润deductionOfNonNetProfit = 0for item in deductionOfNonNetProfitData[stock_code]:if item["name"] == str(year)+"年报":deductionOfNonNetProfit = item["value"]# 计算指定年度的分红比例DividendRatio = totalBonous/float(deductionOfNonNetProfit)return DividendRatio*100# 计算一只股票指定年份的ROE
def getROEbyYear(stock_code, year):# 读取ROE表with open("ROE.pkl", "rb") as file:ROEData = pickle.load(file)file.close()ROE = 0for item in ROEData[stock_code]:if item["name"] == str(year) + "年报":ROE = item["value"]return float(ROE)
# 提取上市公司每股经营现金流数据
def getOperatingCashFlow(stock_list):url_pre = "/?code="url_last = "&market=33&id=index_per_operating_cash_flow_net&period=-1&locale=zh_CN"headers = {'cookie': 'v=A123','sec-ch-ua': '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"','sec-ch-ua-mobile': '?0','sec-ch-ua-platform': 'windows','Upgrade-Insecure-Requests': '1','User-Agent': 'Mozilla / 5.0(Windows NT 10.0; Win64; x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 116.0.0.0 Safari / 537.36','Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7','Sec-Fetch-Site': 'none','Sec-Fetch-Mode': 'navigate','Sec-Fetch-User': '?1','Sec-Fetch-Dest': 'document','Accept-Encoding': 'gzip, deflate','Accept-Language': 'en, en - IN;q = 0.9'}dict = {}for stock_code in stock_list:OperatingCashFlow = []url = url_pre + stock_code + url_lastresponse = requests.get(url=url, headers=headers)# time.sleep(0.5)response.encoding = 'utf8'json_data = response.textstock_lists = json.loads(json_data)for item in stock_lists["data"]["data"]:OperatingCashFlow.append(item)dict[stock_code] = OperatingCashFlowprint(dict)with open("OperatingCashFlow1.pkl", "wb") as file:pickle.dump(dict, file)file.close()#判断一只股票是否最近三年经营现金流持续为正
def checkOperatingCashFlow(stock_code,year_list):# 读取每股经营现金流表with open("OperatingCashFlow.pkl", "rb") as file:OperatingCashFlowData = pickle.load(file)file.close()index = []for year in year_list:for i in range(0, len(OperatingCashFlowData[stock_code])):if OperatingCashFlowData[stock_code][i]["name"] == str(year) + "年报":index.append(i)breakfor i in index:value = float(OperatingCashFlowData[stock_code][i]["value"])if value < 0:return Falseelse:passreturn Trueif __name__ == '__main__':# 登陆baostock APIlg = bs.login()# 显示登陆返回信息if lg.error_code != '0':print('login respond error_code:' + lg.error_code)print('login respond  error_msg:' + lg.error_msg)else:for stock in stock_lists:code_pre = stock['dm']  # 读取股票代码exchange = stock['jys']  # 读取交易所stock_name = stock["mc"]      #读取股票名称stock_code = exchange + '.' + code_pre  # 合并成baostock API要求的股票代码date = '2023-11-07'rs = bs.query_stock_basic(code=stock_code)data_list = []while (rs.error_code == '0') & rs.next():# 获取一条记录,将记录合并在一起data_list.append(rs.get_row_data())result = pd.DataFrame(data_list, columns=rs.fields)try:status = str(result['status'][0])type = str(result['type'][0])except KeyError as e:status = -1type = -1# 剔除退市股以及非股票类权益if status == '1' and type == '1':totalMK = getMK(stock_code=stock_code, start_date=date, end_date=date, year=year)# 读取最新股价price = float(totalMK[1])# 读取总股本share = float(totalMK[2])# 条件一:筛掉市值小于50亿的股票if totalMK[0] > 5000000000:# 条件二:筛选出滚动市盈率为正且小于25倍的公司peTTM = getPeTTM(stock_code=stock_code, start_date=date, end_date=date)if peTTM > 0 and peTTM < 25:# 条件三:筛选出上市日期短于三年的股票ipoDays = getIPODate(stock_code=stock_code)if ipoDays > 365 * 3:# 计算股息率bonous = getYield(stock_code=code_pre, price=price, years=[2020, 2021, 2022])# 判断行业industry = queryIndustry(stock_code=stock_code)# 金融股:股息率大于5%,当年分红比例大于35%或3年平均大于35%if industry == "非银金融" or industry == "银行":if bonous >= 5:# 计算当年分红比例和三年平均分红比例DividendRatio = getDividendRatio(stock_code=code_pre, totalShare=share, year=2022)if DividendRatio >= 0:# 计算年度ROEROE = getROEbyYear(stock_code=code_pre, year=2022)if ROE > 10:print(stock_code, stock_name, industry, price, str(float(totalMK[0]) / 100000000) + "亿", peTTM,str(ipoDays / 365) + "年", str(bonous) + "%", str(DividendRatio) + "%",str(ROE))else:# 非金融股:股息率大于3%,分红比例当年大于45%或者3年平均大于45%if bonous >= 3:# 计算当年分红比例和三年平均分红比例DividendRatio = getDividendRatio(stock_code=code_pre, totalShare=share, year=2022)if DividendRatio >= 45:# 计算三年平均ROEROE = getROEbyYear(stock_code=stock_code.split(".")[1], year=2022)if ROE > 8:# 计算近三年经营现金流result = checkOperatingCashFlow(stock_code=code_pre,year_list=[2020, 2021, 2022])if result == True:print(stock_code, stock_name, industry,price, str(float(totalMK[0]) / 100000000) + "亿",peTTM,str(ipoDays / 365) + "年", str(bonous) + "%",str(DividendRatio) + "%",str(ROE))#### 登出系统 ####bs.logout()

更多推荐

基于高股息高分红优化的量化选股模型

本文发布于:2024-03-13 23:20:58,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1735115.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:股息   选股   模型

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!