【可转债量化模型】基于python的可转债数据采集处理(开源代码)

【之前考虑不周,现在我把采集集思录数据的部分删除了,保留从新浪财经采集数据的代码,如果各位老师觉得还有什么不妥请告诉我~】写这篇文章的起因还是之前有人留言想知道我怎么每天自动计算双低策略轮动标的。在集思录潜水很久,终于碰到我会的领域,所以打算公开一下代码。

#

数据获取渠道
数据的获取渠道有很多,包括许多量化平台(如聚宽、优矿等)都会提供各种量化数据。但是使用量化平台有着许多限制:
1.量化平台往往要收取高额的费用,对于个人投资者而言负担较重。(当然,如果您所在的单位/学校/研究组采购了这些数据库,那么它自然是最方便的数据获取方式)
2.可转债市场在A股市场仍属于小众市场(成交量仅在百亿级别),因此许多量化平台的可转债数据种类都不太全面,甚至没有可转债数据。
3.量化平台数据库有更新延迟:以聚宽为例,可转债行情数据每日19:00、22:00更新,在盘中交易时间无法进行实时检测。

因此,我会使用简单的爬虫技术,从各个财经网站上获取实时数据,本篇文章将会详细介绍各种数据的采集方法和代码。

#

可转债数据采集

这里将介绍从各种网站采集数据的通用基本方法流程,如果想看采集代码可以直接往下滑。

#

可转债实时数据和基本面数据

首先打开想采集的数据所在的网页页面,以火狐浏览器为例,按F12调出开发者工具,切换到网络选项卡,刷新页面,可以看到我们发送的请求和返回值;一般数据都会以json或者csv格式返回,将消息头复制到新标签也打开,发现得到了一个json文件,粗略查看一下,确认是想要的数据,因此我们就可以得到一条获得数据的请求。

现在我们可以开始编写代码了:(经过考虑,将从集思录请求数据的代码删除了)

根据数据结构和我们的需要对采集的数据进行简单的处理:

{{{
​def process_data(self,data):

bond_list=[]
for one in data['rows']:
row=one['cell']
bond_info = {
'convert_value':float(row['convert_value']),
'pre_bond_id':row['pre_bond_id'],#带前缀
'code': row['bond_id'],
'short_name': row['bond_nm'],
'stock_name':row['stock_nm'] ,
'stock_code': row['stock_cd'],
'convert_price': float(row['convert_price']),
'last_cash_date': datetime.strptime(row['maturity_dt'],"%Y-%m-%d").date(),
# 发行总量
'raise_fund_volume':float(row['orig_iss_amt']),
'current_fund_volume':float(row['curr_iss_amt']),
# 当前市场收盘价格
'price':float(row['price']),
# 成交额
'day_market_volume':float(row['volume']),
# 溢价率
'convert_premium_ratio':float(row['premium_rt'].strip("%"))/100,
# 双低值
'double_low':float(row['dblow']),
'stock_price':float(row['sprice']),
'circulating_market_cap':float(row['float_shares'])*float(row['sprice']),
# 转债占流动市值比
'market_cap_ratio':float(row['convert_amt_ratio'].strip("%"))/100
}
if bond_info['price'] < 1:
# 停牌
continue

bond_list.append(bond_info)
bond_list = sorted(bond_list, key=lambda x: x['double_low'])
return bond_list

}}}

这样我们就得到了基础的可转债数据了。

#

可转债历史行情数据

同样的方法,我们也可以获取可转债的历史行情数据,来源是新浪财经:

{{{
from py_mini_racer import py_mini_racer
import pandas as pd
import requests
import datetime
import re
def bond_zh_hs_cov_daily(symbol: str = "sh113542") -> pd.DataFrame:
"""
新浪财经-债券-沪深可转债的历史行情数据, 大量抓取容易封IP
http://vip.stock.finance.sina.com.cn/mkt/#hskzz_z
:param symbol: 沪深可转债代码; e.g., sh010107
:type symbol: str
:return: 指定沪深可转债代码的日 K 线数据
:rtype: pandas.DataFrame
"""

res = requests.get(
zh_sina_bond_hs_cov_hist_url.format(
symbol, datetime.datetime.now().strftime("%Y_%m_%d")
)
)
js_code = py_mini_racer.MiniRacer()
js_code.eval(hk_js_decode)
dict_list = js_code.call(
"d", res.text.split("=")[1].split(";")[0].replace('"', "")
) # 执行js解密代码
data_df = pd.DataFrame(dict_list)
data_df.index = pd.to_datetime(data_df["date"])
del data_df["date"]
data_df = data_df.astype("float")
return data_df

}}}

##
正股数据采集

#

PE、PB指标

在《基于凯利公式的仓位控制及股性因子》一文(发在公众号了)中提到过,正股的市盈率、市净率等指标也是我们构建量化模型的因子,这些数据的获取过程和前面可转债数据采集的过程类似,下面直接给出代码。

{{{
import pandas as pd
import requests
def get_dat_stock_indicator(stock: str) -> pd.DataFrame:
"""
市盈率, 市净率, 股息率数据接口
:param stock: 通过 stock_a_indicator(stock="all") 来获取所有股票的代码
:type stock: str
:return: 市盈率, 市净率, 股息率查询
:rtype: pandas.DataFrame
"""

url = f"https://www.legulegu.com/s/base-info/{stock}"
r = requests.get(url)
temp_json = r.json()
temp_df = pd.DataFrame(temp_json["data"]["items"], columns=temp_json["data"]["fields"])
temp_df["trade_date"] = pd.to_datetime(temp_df["trade_date"])
temp_df.iloc[:, 1:] = temp_df.iloc[:, 1:].astype(float)
return temp_df

}}}

###
正股历史行情数据

正股的历史行情数据在溢价率的时间修正中也有着重要的作用,下面给出代码,数据来源是新浪财经。

{{{
​def stock_zh_a_daily(
symbol: str = "sz000001",
start_date: str = "19900101",
end_date: str = "21000118",
adjust: str = "",
) -> pd.DataFrame:
"""
新浪财经-A股-个股的历史行情数据, 大量抓取容易封 IP
https://finance.sina.com.cn/realstock/company/sh689009/nc.shtml
:param start_date: 20201103; 开始日期
:type start_date: str
:param end_date: 20201103; 结束日期
:type end_date: str
:param symbol: sh600000
:type symbol: str
:param adjust: 默认为空: 返回不复权的数据; qfq: 返回前复权后的数据; hfq: 返回后复权后的数据; hfq-factor: 返回后复权因子; hfq-factor: 返回前复权因子
:type adjust: str
:return: specific data
:rtype: pandas.DataFrame
"""
def _fq_factor(method):
if method == "hfq":
res = requests.get(zh_sina_a_stock_hfq_url.format(symbol))
hfq_factor_df = pd.DataFrame(
eval(res.text.split("=")[1].split("\n")[0])["data"]
)
if hfq_factor_df.shape[0] == 0:
raise ValueError("sina hfq factor not available")
hfq_factor_df.columns = ["date", "hfq_factor"]
hfq_factor_df.index = pd.to_datetime(hfq_factor_df.date)
del hfq_factor_df["date"]
return hfq_factor_df
else:
res = requests.get(zh_sina_a_stock_qfq_url.format(symbol))
qfq_factor_df = pd.DataFrame(
eval(res.text.split("=")[1].split("\n")[0])["data"]
)
if qfq_factor_df.shape[0] == 0:
raise ValueError("sina hfq factor not available")
qfq_factor_df.columns = ["date", "qfq_factor"]
qfq_factor_df.index = pd.to_datetime(qfq_factor_df.date)
del qfq_factor_df["date"]
return qfq_factor_df

if adjust in ("hfq-factor", "qfq-factor"):
return _fq_factor(adjust.split("-")[0])

res = requests.get(zh_sina_a_stock_hist_url.format(symbol))
js_code = py_mini_racer.MiniRacer()
js_code.eval(hk_js_decode)
dict_list = js_code.call(
"d", res.text.split("=")[1].split(";")[0].replace('"', "")
) # 执行js解密代码
data_df = pd.DataFrame(dict_list)
data_df.index = pd.to_datetime(data_df["date"])
del data_df["date"]
data_df = data_df.astype("float")
r = requests.get(zh_sina_a_stock_amount_url.format(symbol, symbol))
amount_data_json = demjson.decode(r.text[r.text.find("["): r.text.rfind("]") + 1])
amount_data_df = pd.DataFrame(amount_data_json)
amount_data_df.index = pd.to_datetime(amount_data_df.date)
del amount_data_df["date"]
temp_df = pd.merge(
data_df, amount_data_df, left_index=True, right_index=True, how="outer"
)
temp_df.fillna(method="ffill", inplace=True)
temp_df = temp_df.astype(float)
temp_df["amount"] = temp_df["amount"] * 10000
temp_df["turnover"] = temp_df["volume"] / temp_df["amount"]
temp_df.columns = [
"open",
"high",
"low",
"close",
"volume",
"outstanding_share",
"turnover",
]
if adjust == "":
temp_df = temp_df[start_date:end_date]
temp_df.drop_duplicates(subset=["open", "high", "low", "close"], inplace=True)
temp_df["open"] = round(temp_df["open"], 2)
temp_df["high"] = round(temp_df["high"], 2)
temp_df["low"] = round(temp_df["low"], 2)
temp_df["close"] = round(temp_df["close"], 2)
temp_df.dropna(inplace=True)
temp_df.drop_duplicates(inplace=True)
return temp_df
if adjust == "hfq":
res = requests.get(zh_sina_a_stock_hfq_url.format(symbol))
hfq_factor_df = pd.DataFrame(
eval(res.text.split("=")[1].split("\n")[0])["data"]
)
hfq_factor_df.columns = ["date", "hfq_factor"]
hfq_factor_df.index = pd.to_datetime(hfq_factor_df.date)
del hfq_factor_df["date"]
temp_df = pd.merge(
temp_df, hfq_factor_df, left_index=True, right_index=True, how="outer"
)
temp_df.fillna(method="ffill", inplace=True)
temp_df = temp_df.astype(float)
temp_df.dropna(inplace=True)
temp_df.drop_duplicates(subset=["open", "high", "low", "close", "volume"], inplace=True)
temp_df["open"] = temp_df["open"] * temp_df["hfq_factor"]
temp_df["high"] = temp_df["high"] * temp_df["hfq_factor"]
temp_df["close"] = temp_df["close"] * temp_df["hfq_factor"]
temp_df["low"] = temp_df["low"] * temp_df["hfq_factor"]
temp_df = temp_df.iloc[:, :-1]
temp_df = temp_df[start_date:end_date]
temp_df["open"] = round(temp_df["open"], 2)
temp_df["high"] = round(temp_df["high"], 2)
temp_df["low"] = round(temp_df["low"], 2)
temp_df["close"] = round(temp_df["close"], 2)
temp_df.dropna(inplace=True)
return temp_df

if adjust == "qfq":
res = requests.get(zh_sina_a_stock_qfq_url.format(symbol))
qfq_factor_df = pd.DataFrame(
eval(res.text.split("=")[1].split("\n")[0])["data"]
)
qfq_factor_df.columns = ["date", "qfq_factor"]
qfq_factor_df.index = pd.to_datetime(qfq_factor_df.date)
del qfq_factor_df["date"]

temp_df = pd.merge(
temp_df, qfq_factor_df, left_index=True, right_index=True, how="outer"
)
temp_df.fillna(method="ffill", inplace=True)
temp_df = temp_df.astype(float)
temp_df.dropna(inplace=True)
temp_df.drop_duplicates(subset=["open", "high", "low", "close", "volume"], inplace=True)
temp_df["open"] = temp_df["open"] / temp_df["qfq_factor"]
temp_df["high"] = temp_df["high"] / temp_df["qfq_factor"]
temp_df["close"] = temp_df["close"] / temp_df["qfq_factor"]
temp_df["low"] = temp_df["low"] / temp_df["qfq_factor"]
temp_df = temp_df.iloc[:, :-1]
temp_df = temp_df[start_date:end_date]
temp_df["open"] = round(temp_df["open"], 2)
temp_df["high"] = round(temp_df["high"], 2)
temp_df["low"] = round(temp_df["low"], 2)
temp_df["close"] = round(temp_df["close"], 2)
temp_df.dropna(inplace=True)
return temp_df

}}}

这贴好像太长了,明天再发两个数据处理的案例吧
发表时间 2021-05-04 18:41     最后修改时间 2021-05-05 11:12

赞同来自: 中国梦蓝天白云 采女孩的蘑菇 老白汾 vickydai 食肉动物 蓝河谷 gwards sneakingbear jin2lv cwd703 qepwq_wu 切切人生 Tianach 冰糖葫芦娃8 奋斗k 秃顶的伯爵 佛系投资 佳源 葱花饼 YongGZ更多 »

1

Hickeyhsu - 科研民工|公众号:花魁莫吉托(Mojito_LAB )

赞同来自: zhong028

啊这,怎么发出来排版就乱了
2021-05-04 18:42 0 条评论
0

宁不烦 - 在广阔的投资世界里,我就是个井底之蛙。

赞同来自:

牛,学习。楼主问一下,用这些代码需要安装什么python模块不?
2021-05-04 19:19 1 条评论
0

BeeBeeSee - 2B or not 2B, that's a question.

赞同来自:

楼主可以发个txt格式的吗?晚上来学习一下。
2021-05-04 19:22 0 条评论
0

keaven

赞同来自:

不明觉厉
2021-05-04 19:30 0 条评论
0

lwhuq

赞同来自:

github地址有吗,谢谢!
2021-05-04 19:30 1 条评论
0

我闲逛我快乐 - 投机是小钱变大钱的努力,投资是避免大钱变小钱的努力。

赞同来自:

看了2天python的书 居然能慢慢开始看懂了
2021-05-04 19:36 3 条评论
0

东方龙2014

赞同来自:

我学废了
2021-05-04 20:00 0 条评论
0

baixiqiang

赞同来自:

太牛了,很多语句都看不懂,大神能否说得详细点?
2021-05-04 20:19 0 条评论
0

攻击型核潜艇 - 学习亏钱手艺,长期8折收金币

赞同来自:

也想学Python,奈何看书就犯困,似乎有点懂,但是又莫法下手,惭愧!
2021-05-04 20:24 0 条评论
6

主任卡员

赞同来自: 风云紫轩 沙丁2012 趋势交易者 luckzpz lorzen Dio00更多 »

这位大哥,我真服了你了。
你在人家网站发爬人家数据的方法,是嫌人家不删你贴子,还是嫌人家数据没加密?
2021-05-04 21:18 1 条评论
10

kevinsheng

赞同来自: ppppp74 食肉动物 景阳的幸福生活 sneakingbear JasonNewMe 趋势交易者 lorzen 米克斯 陈雨 葱花饼更多 »

集思录大气一点直接开发API接口
2021-05-04 21:29 0 条评论
0

未来好 - 漫漫期权路,是甜还是苦~

赞同来自:

最近也在开始学python,主要侧重实现程序化交易这块(自动交易)~感觉比预想的简单多了~
量化选股,选债的没怎么研究过,感觉好像容易过拟合,还是用最简单的大思路选选就好了~
2021-05-04 23:13 1 条评论
1

lhy5507

赞同来自: 趋势交易者

这个让集思录很尴尬啊……
2021-05-05 08:49 0 条评论
0

Hickeyhsu - 科研民工|公众号:花魁莫吉托(Mojito_LAB )

赞同来自:

啊要是官方觉得我这个不太好就联系我删除/编辑吧…
2021-05-05 10:31 2 条评论
0

趋势交易者 - 顺势而为

赞同来自:

牛逼,在集思录发如何爬集思录数据的帖子,让天书作何感想!
2021-05-05 10:37 2 条评论
0

李小样

赞同来自:

其实我更想做个历史的收益回测,不过集思录的历史数据是图片,很难识别,只是有一些简单的指标能直接使用。开了会员就能看到,五一把数据都扒下来了,回头慢慢看。后面买入只要设置条件单就行。
2021-05-05 10:53 5 条评论
1

lorzen

赞同来自: 随机漫步的胖子

发这确实有点尴尬,不如主动删了,爬虫也不是量化里的难点吧,想学网上也挺多的,集思录加密就都失效了
2021-05-05 11:09 1 条评论
0

Hickeyhsu - 科研民工|公众号:花魁莫吉托(Mojito_LAB )

赞同来自:

确实之前考虑不周,现在我把爬虫集思录的部分删除了
2021-05-05 11:14 0 条评论
0

buffonyu - 无业游民

赞同来自:

感谢分享!
祝楼主的股债齐犇!
2021-05-05 11:58 0 条评论
0

没钱个子矮

赞同来自:

学习了
2021-05-05 14:12 0 条评论

要回复问题请先登录注册