NEWS
量化交易
说明
·这一系列文章准备记录和介绍我在研究构建可转债量化交易模型的过程中进行的分析和设计。我自己花了2个月的业余时间摸索开发了一套基于Python语言的可转债量化选债模型简单原型,并准备分模块开源我的代码供大家参考交流。如果有相关经验和想法的欢迎和我交流,十分希望能得到更多的启发和指导。
·点击查看本系列文章:
第1部分:因子分析1
第2部分:75种简单策略回测
第3部分:溢价率的时空修正
第4部分:仓位控制和股性因子
想法来源
摘要
·本章主要介绍从各类财经网站爬取量化模型所需数据的方法及一些数据处理的案例和Python代码。全文共5706字。
数据采集
数据驱动模型
量化交易是指以先进的数学模型替代人为的主观判断,利用计算机技术从庞大的历史数据中海选能带来超额收益的多种“大概率”事件以制定策略。
因此,简单来说,量化模型是一种数据驱动的交易策略模型,它有许多分支,包括量化选股、策略回测、自动交易等。目前我在研究的是可转债市场的量化选债,需要各种各样的数据进行支撑,包括即时数据和历史数据。
目前,我关注的十几个可转债投资大V公众号,有部分也使用了量化投资的方法(其实双低策略就是一种最简单的量化策略),但是所使用的的策略因子一般都很少(价格、溢价率为主),而且除了价格和溢价率以外的因子往往只用作筛选(如去除PB小于1.3的正股对应的可转债)。我仔细观察了他们的策略,和其中一部分作者进行过交流,有许多作者表示自己并不会编程,还是使用人工排序、excel计算等方式进行选债。
我不否认这些作者的成果有效性,但作为一个理工科研究生,自然希望能将这个量化的过程做得系统化。因此,我自己花了2个月的业余时间摸索开发了一套基于Python语言的可转债量化选债模型简单原型,并准备分模块开源我的代码供大家参考交流。
数据获取渠道
数据的获取渠道有很多,包括许多量化平台(如聚宽、优矿等)都会提供各种量化数据。但是使用量化平台有着许多限制:
1.量化平台往往要收取高额的费用,对于个人投资者而言负担较重。(当然,如果您所在的单位/学校/研究组采购了这些数据库,那么它自然是最方便的数据获取方式)
2.可转债市场在A股市场仍属于小众市场(成交量仅在百亿级别),因此许多量化平台的可转债数据种类都不太全面,甚至没有可转债数据。
3.量化平台数据库有更新延迟:以聚宽为例,可转债行情数据每日19:00、22:00更新,在盘中交易时间无法进行实时检测。
因此,我会使用简单的爬虫技术,从各个财经网站上获取实时数据,本篇文章将会详细介绍各种数据的采集方法和代码。
可转债数据采集
集思录是可转债投资者最常用的数据网站,我们可以从中采集可转债实时数据和基础数据。
这里将介绍从各种网站采集数据的通用基本方法流程,如果想看采集代码可以直接往下滑。
可转债实时数据和基本面数据
集思录数据包括实时行情数据、可转债的基本面信息和对应正股的实时信息。首先打开集思录网站可转债数据页面:
以火狐浏览器为例,按F12调出开发者工具,切换到网络选项卡,刷新页面,可以看到我们发送的请求和返回值;一般数据都会以json或者csv格式返回,如图所示:
将消息头复制到新标签也打开:
发现得到了一个json文件:
粗略查看一下,可以确认就是可转债的数据,因此我们就可以得到一条获得可转债数据的请求。
现在我们可以开始编写代码了:
- payload = {'listed': 'Y'}
复制代码- newUrl = 'https://www.jisilu.cn/data/cbnew/cb_list/?___jsl=LST___t=%s' % int(
复制代码- # 最简单的爬虫请求.也可以加上headers字段,防止部分网址的反爬虫机制
复制代码- # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
复制代码- response = requests.post(newUrl, data=payload)
复制代码- # 当爬取的界面需要用户名密码登录时候,构建的请求需要包含auth字段
复制代码- data = response.content.decode('utf-8')
复制代码
根据数据结构和我们的需要对采集的数据进行简单的处理:
- def process_data(self,data):
复制代码- 'convert_value':float(row['convert_value']),
复制代码- 'pre_bond_id':row['pre_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:
复制代码- bond_list.append(bond_info)
复制代码- bond_list = sorted(bond_list, key=lambda x: x['double_low'])
复制代码 这样我们就得到了基础的可转债数据了。
可转债历史行情数据
同样的方法,我们也可以获取可转债的历史行情数据,来源是新浪财经:
- from py_mini_racer import py_mini_racer
复制代码- 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
复制代码- :return: 指定沪深可转债代码的日 K 线数据
复制代码- 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('"', "")
复制代码- data_df = pd.DataFrame(dict_list)
复制代码- data_df.index = pd.to_datetime(data_df["date"])
复制代码- data_df = data_df.astype("float")
复制代码
正股数据采集
PE、PB指标
在《基于凯利公式的仓位控制及股性因子》一文中提到过,正股的市盈率、市净率等指标也是我们构建量化模型的因子,这些数据的获取过程和前面可转债数据采集的过程类似,下面直接给出代码。- def get_dat_stock_indicator(stock: str) -> pd.DataFrame:
复制代码- :param stock: 通过 stock_a_indicator(stock="all") 来获取所有股票的代码
复制代码- url = f"https://www.legulegu.com/s/base-info/{stock}"
复制代码- 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)
复制代码 正股历史行情数据
正股的历史行情数据在溢价率的时间修正中也有着重要的作用,下面给出代码,数据来源是新浪财经。
- symbol: str = "sz000001",
复制代码- start_date: str = "19900101",
复制代码- end_date: str = "21000118",
复制代码- 新浪财经-A股-个股的历史行情数据, 大量抓取容易封 IP
复制代码- https://finance.sina.com.cn/realstock/company/sh689009/nc.shtml
复制代码- :param start_date: 20201103; 开始日期
复制代码- :param end_date: 20201103; 结束日期
复制代码- :param adjust: 默认为空: 返回不复权的数据; qfq: 返回前复权后的数据; hfq: 返回后复权后的数据; hfq-factor: 返回后复权因子; hfq-factor: 返回前复权因子
复制代码- 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"]
复制代码- 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"]
复制代码- 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('"', "")
复制代码- data_df = pd.DataFrame(dict_list)
复制代码- data_df.index = pd.to_datetime(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"]
复制代码- 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 = 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)
复制代码- 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, 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)
复制代码- 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, 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)
复制代码
数据处理
例1:溢价率的空间修正
上面给出了基础的数据采集案例。在采集到我们所需要的数据之后,要对数据进行简单的处理,例如《溢价率的时空修正》中提到的对全市场的可转债价格和转股价值的二次拟合:
- def __init__(self,bond_list) :
复制代码- self.bond_list=bond_list # 输入采集到的可转债数据
复制代码- self.filter_bond_list=self.price_filter()# 过滤高价转债
复制代码- filter_bond_list = filter(
复制代码- lambda x: x['price'] < 150 ,
复制代码- filter_bond_list=list(filter_bond_list)
复制代码- x=np.array([bond['convert_value'] for bond in self.filter_bond_list])
复制代码- y=np.array([bond['price'] for bond in self.filter_bond_list])
复制代码- z1 = np.polyfit(x,y, 2) # 用4次多项式拟合ax.set_xlabel('price')
复制代码- plot1=plt.plot(x, y, '*',label='original values')
复制代码- X=np.linspace(0,150,100) ##在0-15直接画100个连续点
复制代码- plot2=plt.plot(X, Y, 'r',label='polyfit values')
复制代码- plt.xlabel('convert_value')
复制代码- plt.savefig("/home/hickey/OneDrive/Pictures/dev_market_jsl.jpg")
复制代码 通过上述方法获得拟合的二次函数之后,就可以计算每只转债的实际价格和拟合价格的偏离程度,对溢价率进行修正:
- def append_dev_factor(self,p1,bond_list):
复制代码- x=bond['convert_value']# 输入转股价值
复制代码- dev = (bond['price']-y)/y# 实际价格和拟合价格的差
复制代码- bond['price_devR']=dev# 将差值保存到可转债数据中
复制代码 例2:基于凯利公式的正股因子处理
- class KLYHStrategy(object):
复制代码- EXPECTED_EARN_RATE = 0.15
复制代码- def __init__(self, index_stock,beta_factors,history_factors):
复制代码- self._index_stock = index_stock
复制代码- self._pe, self._pb, self._roe = beta_factors
复制代码- self._history_factors = history_factors
复制代码- def get_trading_position(self, national_debt_rate=0.035):
复制代码- national_debt_rate: 当前国债利率
复制代码- -1 ~~ 1, -1代表清仓,0代表持仓不动, 1代表全仓买入; -0.5代表清半仓,0.5代表半仓买入
复制代码- pe_quantile = self._index_stock.get_quantile_of_history_factors(
复制代码- self._pe, self._history_factors['pe'])
复制代码- pb_quantile = self._index_stock.get_quantile_of_history_factors(
复制代码- self._pb, self._history_factors['pb'])
复制代码- avg_roe = self._history_factors['roe'].mean()
复制代码- # stock_code = self._index_stock.get_stock_code()
复制代码- stock_name = self._index_stock.get_stock_name()
复制代码- position = self.kelly(self._pe, avg_roe, national_debt_rate, action=1)
复制代码- def kelly(self, pe, history_avg_roe, national_debt_rate, action=1):
复制代码- 卖出时简单的用 95% 清仓1成, 97%清仓2成,99%清仓7成
复制代码- national_debt_rate: 当前国债利率
复制代码- pe_quantile = self._index_stock.get_quantile_of_history_factors(
复制代码- pe, self._history_factors['pe'])
复制代码- if pe_quantile>=0.95 and pe_quantile=0.97 and pe_quantile=0.99:
复制代码- odds = pow(1 + self.EXPECTED_EARN_RATE, self.EXPECTED_EARN_YEAR)
复制代码- except_sell_pe = odds / pow(1+history_avg_roe, self.EXPECTED_EARN_YEAR) * pe
复制代码- win_rate = 1.0 - self._index_stock.get_quantile_of_history_factors(except_sell_pe,self._history_factors['pe'])
复制代码- position = (odds * win_rate - (1.0 - win_rate)) * 1.0 / odds
复制代码 通过上述代码可以求得可转债对应正股基于凯利公式的推荐仓位;我们在选债时如果想使用比较保守的策略,可以设定一个阈值(如10%),如果正股推荐仓位小于阈值则排除该对应可转债。
根据理解和回测实验,对各种因子进行处理、加权和筛选,是构建量化选债模型的核心步骤。
小结
本章介绍了从各类财经网站爬取量化模型所需数据的方法及一些数据处理的案例和Python代码,仅供参考。不涉及具体参数设置,不形成任何投资操作建议。
基础理论部分基本介绍完毕,下一篇文章打算介绍如果通过python获取可转债数据,建立可转债量化模型,敬请关注!
本公众号仅为个人投资记录,不构成任何投资建议!如有侵权请联系删除!
本周纸条
花魁莫吉托
五一这就过了一大半了orz
|
|