小散量化炒股记|量化系统中数据是源头,教你搭建一款普适的数据源框架

aa5d8300e3fe05805d3d62aa4d68f6f9.png

前言

8954afbcc80920aef4b8a0a4743b3f2a.png

在量化交易系统中,数据是第一环节。虽然目前市面上的数据源多种多样,比如tushare、baostock、JQData、pytdx、akshare等等,但是无论什么数据源,必须要满足我们量化系统最基本的几种数据,比如股票代码表、个股行情数据等等。

于是,搭建一款普适性的数据源框架就显得尤为重要。这样一来,我们可以随意搭配不同的数据源,也能灵活替换不同的数据源,让我们的量化系统因为数据源的变动,改动点足够小。

接下来就给大家分享一下如何搭建一个数据源框架。

71ad82a3bcf1fbcf32ab17dae9a156d6.png

搭建过程

78a57799dd5c723ecadec5cf80b650c8.png

首先定一个父类DataBackend,在这个父类中定义量化系统中所必须的几个接口函数,比如:

def get_price(self, code, start, end, freq) 
def get_codes_list(self)
def get_trading_dates(self, start, end)
def symbol(self, code)

不过,父类中只是预留这几个接口并不实现,要求在子类中必须实现,如果子类中未实现该方法,我们使用raise NotImplementedError报错。如下所示:

def get_trading_dates(self, start, end):
    """
    获取所有的交易日
    :param start: '2009-01-01'
    :param end: '2019-06-01'
    """
    raise NotImplementedError

这样一来,如果子类没有实现父类中指定要实现的方法,则会自动调用父类中的方法,于是父类方法就会raise将错误抛出,这样替换数据源的时候就会发现是缺少了对指定接口的实现。

关于Python面向对象编程的详细介绍可以看书籍《Python股票量化交易从入门到实践》的第三章,此处不再赘述。

接下来再实现子类MyDataBackend,子类是继承父类DataBackend的。

子类中我们使用了tushare pro的stock_basic接口,以及baostock的query_history_k_data_plus接口。

这两个接口已经可以满足我们量化系统所必须的基础数据了。比如全市场的股票代码映射表、个股历史行情数据、A股市场开市的交易日等等。具体实现代码如下所示:

class MyDataBackend(DataBackend):


    def __init__(self):
        self._stock_codes_table = {}


    @lru_cache(maxsize=4096)
    def stock_basics(self):
        return pro.stock_basic(exchange='', list_status='L', fields='ts_code,symbol,name,area,industry,list_date')


    @lru_cache(maxsize=4096)
    def get_price(self, code, start, end, freq):
        """
        :param code: e.g. 000002.SH
        :param start: '2009-01-01'
        :param end: '2019-06-01'
        :returns:
        :rtype: numpy.rec.array
        """
        code = self.convert_code(code)
        # 登陆系统
        lg = bs.login()
        # 获取指数历史行情数据
        fields = "date,open,high,low,close,volume,pctChg"


        df_bs = bs.query_history_k_data_plus(code, fields, start_date=start, end_date=end,
                                             frequency=freq,
                                             adjustflag="3")  # <class 'baostock.data.resultset.ResultData'>
        # frequency="d"取日k线,adjustflag="3"默认不复权,1:后复权;2:前复权


        data_list = []


        while (df_bs.error_code == '0') & df_bs.next():
            # 获取一条记录,将记录合并在一起
            data_list.append(df_bs.get_row_data())
        result = pd.DataFrame(data_list, columns=df_bs.fields)


        result = result.astype({'volume': 'uint64', 'pctChg': 'float64',
                                'close': 'float64', 'open': 'float64', 'low': 'float64', 'high': 'float64'})


        result.volume = result.volume / 100  # 单位转换:股-手
        result.volume = result.volume.astype('uint64')
        result.date = pd.DatetimeIndex(result.date)
        result.set_index("date", drop=True, inplace=True)
        result.index = result.index.set_names('Date')


        recon_data = {'high': result.high, 'low': result.low, 'open': result.open, 'close': result.close,
                      'volume': result.volume, 'pctChg': result.pctChg}


        df_recon = pd.DataFrame(recon_data)
        # 登出系统
        bs.logout()
        return df_recon


    @lru_cache()
    def get_codes_list(self):
        """
        获取所有的股票代码列表
        """
        code_list = list(self.code_name_map.keys())
        return code_list


    @property
    def code_name_map(self):
        code_group = self.stock_basics.loc[:, ['ts_code', 'name']]
        if code_group.empty != True:
            codes = code_group.ts_code.values
            names = code_group.name.values
            self._stock_codes_table = dict(zip(codes, names))
        else:
            raise AttributeError('股票基本信息为空!!!检查tushare的pro.stock_basic接口')
        return self._stock_codes_table


    def convert_code(self, code):
        num, sym = code.lower().split(".")
        return sym + "." + num


    @lru_cache()
    def get_trading_dates(self, start, end):
        """
        获取所有的交易日
        :param start: 20160101
        :param end: 20160201
        """
        df = self.get_price("000001.SZ", start=start, end=end, freq="d")
        trading_dates = [datetime.datetime.strftime(date, "%Y-%m-%d") for date in df.index.tolist()]


        return trading_dates


    @lru_cache(maxsize=4096)
    def symbol(self, code):
        """
        获取 code 对应的名字
        :param code str: 股票代码
        :returns: 名字
        :rtype: str
        """
        symbol = self.code_name_map.get(code)
        return "{}[{}]".format(code, symbol)

需要注意的是我们在函数上加了装饰器@lru_cache()。具体可以参考星球的这篇主题介绍,里面也有例程代码。@lru_cache()在爬虫应用上也特别有效。b8c5b6a1b789e12c65a2fdb08ef28331.png

调用方式和结果如下所示:

data_org = MyDataBackend()


print(data_org.stock_basics)
"""
        ts_code  symbol     name  area industry list_date
0     000001.SZ  000001     平安银行    深圳       银行  19910403
1     000002.SZ  000002      万科A    深圳     全国地产  19910129
......
"""
print(data_org.code_name_map)
# {'000001.SZ': '平安银行', '000002.SZ': '万科A'......}
print(data_org.convert_code('000001.SZ')) 
# sz.000001
print(data_org.get_price('000001.SZ', '2021-01-01', '2022-01-01', "d"))
"""
             high    low   open  close   volume  pctChg
Date                                                   
2021-01-04  19.10  18.44  19.10  18.60  1554216 -3.8263
2021-01-05  18.48  17.80  18.40  18.17  1821352 -2.3118
2021-01-06  19.56  18.00  18.08  19.56  1934945  7.6500
......
"""
print(data_org.get_codes_list()) 
# ['000001.SZ', '000002.SZ', '000004.SZ', '000005.SZ'......]
print(data_org.get_trading_dates('2021-01-01', '2022-01-01'))
# ['2021-01-04', '2021-01-05', '2021-01-06', '2021-01-07'......]
print(data_org.symbol('000001.SZ'))
# 000001.SZ[平安银行]

7286fc41c47db464f4c8e69e245e911c.png

说明

b1c97047fcbd7235776cc3dfdf58ba75.png

1. 我们会把以上完整的源码上传到知识星球《Python量化场景编程技巧与方法》,帮助小伙伴们更好地掌握这个方法。这个星球的用途是分享搭建量化系统中所涉及到的Python及常用第三方库方面的编程方法和技巧。

还能加入高质量的Python量化编程答疑群。

星球会员好消息!邀请您加入高质量的Python量化编程答疑群

2. 想要加入知识星球《玩转股票量化交易》或者《Python量化场景编程技巧与方法》的小伙伴记得先微信call我获取福利!

347a9f6aa205c2a4054f598da1fada56.jpeg

元宵大师的量化交易书籍开售!!
京东、当当、天猫有售!!

3244269bd29fde894d343fbb4046302a.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值