# 基础库导入

from __future__ import print_function
from __future__ import division

import warnings
warnings.filterwarnings('ignore')
warnings.simplefilter('ignore')

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

import os
import sys
# 使用insert 0即只使用github，避免交叉使用了pip安装的abupy，导致的版本不一致问题
sys.path.insert(0, os.path.abspath('../'))
import abupy

# 使用沙盒数据，目的是和书中一样的数据环境
abupy.env.enable_example_env_ipython()

from abupy import abu, EMarketTargetType, AbuMetricsBase, ABuMarketDrawing, ABuProgress, ABuSymbolPd, get_price, ABuIndustries
from abupy import EMarketDataFetchMode, EDataCacheType, EMarketSourceType, FuturesBaseMarket, TCBaseMarket, ABuDateUtil
from abupy import AbuDataParseWrap, StockBaseMarket, SupportMixin, ABuNetWork, Symbol, code_to_symbol

"""
Explanation: ABU量化系统使用文档
<center>
        <img src="./image/abu_logo.png" alt="" style="vertical-align:middle;padding:10px 20px;"><font size="6" color="black"><b>第19节 数据源</b></font>
</center>

作者: 阿布
阿布量化版权所有 未经允许 禁止转载
abu量化系统github地址 (欢迎+star)
本节ipython notebook
abupy除了之前章节讲解示例时使用的内置沙盒数据外，也内置了几个数据源，支持实时获取美股，A股，港股，期货和比特币等类型数据，内置的数据源仅供学习使用，abupy提供了接入外部数据源的接口和规范，本节的示例将讲解内置数据源的切换，以及接入用户外部的数据源的接口规范。
首先导入abupy中本节使用的模块：
End of explanation
"""


abupy.env.disable_example_env_ipython()

"""
Explanation: 和之前的章节不一样，关闭沙盒数据接口，使用实时数据源提供数据：
End of explanation
"""


abupy.env.g_data_fetch_mode

"""
Explanation: 1. 数据模式的切换
使用g_data_fetch_mode可以查看当前的数据模式：
End of explanation
"""


abupy.env.g_data_fetch_mode = EMarketDataFetchMode.E_DATA_FETCH_FORCE_NET

ABuSymbolPd.make_kl_df('usBIDU').tail()

"""
Explanation: 默认使用模式为E_DATA_FETCH_NORMAL，NORMAL的意义是默认优先从缓存中获取，如果缓存中不存在，再访问网络，尝试从网络获取，除此之外还有一些优化，比如虽然缓存中的数据也无法满足要求，但是缓存索引纪录今天已经尝试从网络获取，这种情况下也不再访问网络。
更多详情请阅读ABuDataSource代码
E_DATA_FETCH_FORCE_NET为强制使用网络进行数据更新，一般不推荐使用，如果切换了数据源，或者缓存中的数据存在问题的情况下会使用：
End of explanation
"""


abupy.env.g_data_fetch_mode = EMarketDataFetchMode.E_DATA_FETCH_FORCE_LOCAL

ABuSymbolPd.make_kl_df('usBIDU').tail(1)

"""
Explanation: E_DATA_FETCH_FORCE_LOCAL为强制从缓存获取，实际上在做回测的时候，使用的一般都是这种模式，因为比如编写了一个策略进行回测结果度量，通常情况下需要反复的修改策略，重新进行回测，强制使用缓存的好处是：

保证使用的数据集没有发生变化，度量结果有可比性
提高回测运行效率，特别是针对全市场回测
分类数据获取和回测，方便问题排除
End of explanation
"""


abupy.env.g_data_fetch_mode = EMarketDataFetchMode.E_DATA_FETCH_NORMAL

"""
Explanation: 下面把数据获取模式恢复为默认的E_DATA_FETCH_NORMAL：
End of explanation
"""


abupy.env.g_data_cache_type

"""
Explanation: 2. 数据存储的切换
默认的缓存数据存储模式为CSV，如下所示：
End of explanation
"""


if abupy.env.g_is_mac_os:
    !open $abupy.env.g_project_kl_df_data_csv
else:
    !echo $abupy.env.g_project_kl_df_data_csv

"""
Explanation: 缓存的csv数据存贮文件路径在~/abu/data/csv/，可使运行下面命令直接打开目录：
End of explanation
"""


abupy.env.g_data_cache_type = EDataCacheType.E_DATA_CACHE_HDF5

"""
Explanation: 可以通过g_data_cache_type切换其它存贮模式，如下使用HDF5进行数据存贮：
End of explanation
"""


if abupy.env.g_is_mac_os:
    !open $abupy.env.g_project_data_dir
else:
    !echo $abupy.env.g_project_data_dir

ABuSymbolPd.make_kl_df('usTSLA').tail()

"""
Explanation: 缓存的hdf5文件路径在~/abu/data/df_kl.h5，可使运行下面命令直接打开目录, 或者显示完整路径：
End of explanation
"""


abupy.env.g_data_cache_type = EDataCacheType.E_DATA_CACHE_CSV

"""
Explanation: 下面仍然切换回默认的csv模式，csv的优点是存贮空间需要小，可以并行读写，且针对不同平台兼容性好：
End of explanation
"""


abupy.env.g_market_source

"""
Explanation: 3. 数据源的切换
如下显示当前的数据源g_market_source为百度数据源：
End of explanation
"""


ABuSymbolPd.make_kl_df('btc')

"""
Explanation: 如下想要获取比特币的数据，但是输出显示百度的数据源不支持比特币：
End of explanation
"""


abupy.env.g_market_source = EMarketSourceType.E_MARKET_SOURCE_hb_tc
ABuSymbolPd.make_kl_df('btc').tail()

"""
Explanation: 切换数据源为火币数据源，即可正常获取数据，如下：
End of explanation
"""


ABuSymbolPd.make_kl_df('jd0')

"""
Explanation: 类似，下面想要获取期货鸡蛋的数据，但是输出显示火币的数据源不支持期货市场：
End of explanation
"""


abupy.env.g_market_source = EMarketSourceType.E_MARKET_SOURCE_sn_futures

ABuSymbolPd.make_kl_df('jd0').tail()

"""
Explanation: 切换数据源为新浪期货数据源，即可正常获取数据，如下：
End of explanation
"""


if abupy.env.g_is_mac_os:
    !open $abupy.env.g_project_data_dir
else:
    !echo $abupy.env.g_project_data_dir

"""
Explanation: 4. 全市场数据的更新
在之前的章节使用abu.run_loop_back进行交易回测，使用的都是沙盒数据，如果需要使用实时数据，特别是回测交易多的情况下，比如全市场测试，推荐在使用abu.run_loop_back进行回测前先使用abu.run_kl_update将数据进行更新。
在run_kl_update中会首先强制使用网络数据进行全市场数据更新，在更新完毕后会将全市场的交易数据都写入缓存，再abu.run_loop_back运行回测的时候使用本地数据模式，即实现数据更新与策略回测分离，运行效率提高。
下面的代码将分别获取美股，A股，港股，期货，比特币，莱特币6年的交易数据，在后面的章节将分别使用这些数据做回测示例，读者可只获取自己关心的市场的交易数据，不必全部运行。
所有获取的数据已经存放在百度云盘上，后面的章节使用的数据都是本节更新的数据，建议直接从云盘下载入库完毕的数据库，不需要从各个数据源再一个一个的下载数据进行入库，百度云地址如下：
csv格式美股，A股，港股，币类，期货6年日k数据 密码: gvtr
下面数据存贮格式为hdf5数据，由于hdf5文件解压后非常大，还需要区分python版本，所以如果没有足够的存贮空间
特别是python2下，建议使用csv格式的缓存文件：
mac系统python3 美股，A股，港股，币类，期货6年日k数据 密码: ecyp
mac系统python2 A股6年日k数据:  密码: sid8
mac系统python2 美股6年日k数据:  密码: uaww
windows python3 美股，A股，港股，币类，期货6年日k数据 密码: 3cwe
windows python2 A股6年日k数据:  密码: 78mb
windows python2 美股6年日k数据:  密码: 63r3
下载完毕上述数据后，hdf5解压得到df_kl.h5文件，csv解压得到csv文件夹，解压后放到下面路径下即可
End of explanation
"""


%%time
abupy.env.g_market_source = EMarketSourceType.E_MARKET_SOURCE_tx
abupy.env.g_data_cache_type = EDataCacheType.E_DATA_CACHE_CSV
abu.run_kl_update(start='2011-08-08', end='2017-08-08', market=EMarketTargetType.E_MARKET_TARGET_US, n_jobs=10)

"""
Explanation: 如果不想通过直接下载数据文件的方式，也可下面通过切换至腾讯数据源，然后进行美股数据全市场更新：
备注：耗时操作，大概需要运行15分钟左右，可以在做其它事情的时候运行
End of explanation
"""


%%time
abupy.env.g_market_source = EMarketSourceType.E_MARKET_SOURCE_bd
abupy.env.g_data_cache_type = EDataCacheType.E_DATA_CACHE_CSV
abu.run_kl_update(start='2011-08-08', end='2017-08-08', market=EMarketTargetType.E_MARKET_TARGET_CN, n_jobs=10)

"""
Explanation: 如果不想通过直接下载数据文件的方式，也可切换至百度数据源，然后进行A股数据全市场更新：
备注：耗时操作，大概需要运行20分钟左右，可以在做其它事情的时候运行
End of explanation
"""


%%time
abupy.env.g_market_source = EMarketSourceType.E_MARKET_SOURCE_nt
abupy.env.g_data_cache_type = EDataCacheType.E_DATA_CACHE_CSV
abu.run_kl_update(start='2011-08-08', end='2017-08-08', market=EMarketTargetType.E_MARKET_TARGET_HK, n_jobs=10)

"""
Explanation: 如果不想通过直接下载数据文件的方式，也可切换至网易数据源，然后进行港股数据全市场更新：
备注：耗时操作，大概需要运行5分钟左右，可以在做其它事情的时候运行
End of explanation
"""


%%time
abupy.env.g_market_source = EMarketSourceType.E_MARKET_SOURCE_sn_futures
abupy.env.g_data_cache_type = EDataCacheType.E_DATA_CACHE_CSV
abu.run_kl_update(start='2011-08-08', end='2017-08-08', market=EMarketTargetType.E_MARKET_TARGET_FUTURES_CN, n_jobs=4)

"""
Explanation: 切换至新浪期货数据源，然后进行期货数据全市场更新：
备注：非耗时操作，大概30秒
End of explanation
"""


%%time
abupy.env.g_market_source = EMarketSourceType.E_MARKET_SOURCE_hb_tc
abupy.env.g_data_cache_type = EDataCacheType.E_DATA_CACHE_CSV
abu.run_kl_update(start='2011-08-08', end='2017-08-08', market=EMarketTargetType.E_MARKET_TARGET_TC, n_jobs=2)

"""
Explanation: 切换至火币数据源，然后进行比特币，莱特币数据全市场更新：
备注：非耗时操作，大概需要5秒
End of explanation
"""


@AbuDataParseWrap()
class SNUSParser(object):
    """snus数据源解析类，被类装饰器AbuDataParseWrap装饰"""
    def __init__(self, symbol, json_dict):
        """
        :param symbol: 请求的symbol str对象
        :param json_dict: 请求返回的json数据
        """
        data = json_dict
        # 为AbuDataParseWrap准备类必须的属性序列
        if len(data) > 0:
            # 时间日期序列
            self.date = [item['d'] for item in data]
            # 开盘价格序列
            self.open = [item['o'] for item in data]
            # 收盘价格序列
            self.close = [item['c'] for item in data]
            # 最高价格序列
            self.high = [item['h'] for item in data]
            # 最低价格序列
            self.low = [item['l'] for item in data]
            # 成交量序列
            self.volume = [item['v'] for item in data]

"""
Explanation: 5. 接入外部数据源，股票数据源
abupy中内置的数据源：

美股市场：腾讯数据源，百度数据源，网易数据源，新浪数据源
港股市场：腾讯数据源，百度数据源，网易数据源
A股市场： 腾讯数据源，百度数据源，网易数据源
期货市场：新浪期货数据源，新浪国际期货数据源
比特币，莱特币：火币网数据源

这些数据源都只是为用户学习使用，并不能保证数据一直通畅，而且如果用户很在乎数据质量，比如有些数据源会有前复权数据错误问题，有些数据源成交量不准确等问题，那么就需要接入用户自己的数据源。
下面首先示例接入股票类型的数据源，首先实现一个数据源返回数据解析类，如下所示：
End of explanation
"""


class SNUSApi(StockBaseMarket, SupportMixin):
    """snus数据源，支持美股"""
    K_NET_BASE = "http://stock.finance.sina.com.cn/usstock/api/json_v2.php/US_MinKService.getDailyK?" \
                 "symbol=%s&___qn=3n"

    def __init__(self, symbol):
        """
        :param symbol: Symbol类型对象
        """
        super(SNUSApi, self).__init__(symbol)
        # 设置数据源解析对象类
        self.data_parser_cls = SNUSParser

    def _support_market(self):
        """声明数据源支持美股"""
        return [EMarketTargetType.E_MARKET_TARGET_US]

    def kline(self, n_folds=2, start=None, end=None):
        """日k线接口"""
        url = SNUSApi.K_NET_BASE % self._symbol.symbol_code
        data = ABuNetWork.get(url=url, timeout=(10, 60)).json()
        kl_df = self.data_parser_cls(self._symbol, data).df
        if kl_df is None:
            return None
        return StockBaseMarket._fix_kline_pd(kl_df, n_folds, start, end)

    def minute(self, n_fold=5, *args, **kwargs):
        """分钟k线接口"""
        raise NotImplementedError('SNUSApi minute NotImplementedError!')

"""
Explanation: 上面编写的SNUSParser即为一个数据源返回数据解析类：

数据源解析类需要被类装饰器AbuDataParseWrap装饰
完成__init__函数，根据这里的数据json_dict来拆分成self.date，self.open，self.close，self.high，self.low，self.volume

如本例中每一条json数据的格式为：
{'d': '2017-08-08', 'o': '102.29', 'h': '102.35', 'l': '99.16', 'c': '100.07', 'v': '1834706'}

init函数目的就是通过拆解网络原始数据形成上述的五个基本序列，之后在类装饰器AbuDataParseWrap中会进行数据的再次加工以及规范标准化处理
更多详情请阅读源代码ABuDataParser
备注：这里拆解的过程没有在乎效率，只为好理解过程
在编写数据解析类后就需要编写个数据源类，如下所示，以新浪美股数据源为例：
End of explanation
"""


SNUSApi(code_to_symbol('sh601766')).check_support(rs=False)

"""
Explanation: 上面编写的SNUSApi即为一个股票数据源类：

股票类型数据源类需要继承StockBaseMarket
__init__函数中指定数据源解析类
数据源类需要混入SupportMixin类，实现_support_market方法，声明支持的市场，本例只支持美股市场
数据源类需要实现kline接口，完成获取指定symbol的日线数据，将日线数据交给数据源解析类进行处理
数据源类需要实现分钟k线接口，也可以直接raise NotImplementedError

下面示例使用，如下通过check_support检测是否支持A股，结果显示False:
End of explanation
"""


SNUSApi(code_to_symbol('usSINA')).check_support(rs=False)

"""
Explanation: 如下通过check_support检测是否支持美股，结果显示True:
End of explanation
"""


SNUSApi(code_to_symbol('usSINA')).kline().tail()

"""
Explanation: 如下通过kline接口获取数据，如下所示：
End of explanation
"""


abupy.env.g_private_data_source = SNUSApi

"""
Explanation: 接入SNUSApi至abupy系统中，只需要将数据源类名称直接赋予abupy.env.g_private_data_source：
End of explanation
"""


ABuSymbolPd.make_kl_df('sh601766')

"""
Explanation: 下面使用make_kl_df接口获取sh601766数据，显示SNUSApi不支持：
End of explanation
"""


ABuSymbolPd.make_kl_df('usSINA').tail()

"""
Explanation: 下面使用make_kl_df接口获取美股数据, 返回的数据即是通过上面实现的SNUSApi返回的：
End of explanation
"""


@AbuDataParseWrap()
class SNFuturesParser(object):
    """示例期货数据源解析类，被类装饰器AbuDataParseWrap装饰"""
    # noinspection PyUnusedLocal
    def __init__(self, symbol, json_dict):
        """
        :param symbol: 请求的symbol str对象
        :param json_dict: 请求返回的json数据
        """
        data = json_dict
        # 为AbuDataParseWrap准备类必须的属性序列
        if len(data) > 0:
            # 时间日期序列
            self.date = [item[0] for item in data]
            # 开盘价格序列
            self.open = [item[1] for item in data]
            # 最高价格序列
            self.high = [item[2] for item in data]
            # 最低价格序列
            self.low = [item[3] for item in data]
            # 收盘价格序列
            self.close = [item[4] for item in data]
            # 成交量序列
            self.volume = [item[5] for item in data]

"""
Explanation: 6. 接入外部数据源，期货数据源
下面示例接入期货类型的数据源，首先实现一个数据源返回数据解析类，如下所示：
End of explanation
"""


class SNFuturesApi(FuturesBaseMarket, SupportMixin):
    """sn futures数据源，支持国内期货"""

    K_NET_BASE = "http://stock.finance.sina.com.cn/futures/api/json_v2.php/" \
                 "IndexService.getInnerFuturesDailyKLine?symbol=%s"

    def __init__(self, symbol):
        """
        :param symbol: Symbol类型对象
        """
        super(SNFuturesApi, self).__init__(symbol)
        # 设置数据源解析对象类
        self.data_parser_cls = SNFuturesParser

    def _support_market(self):
        """声明数据源支持期货数据"""
        return [EMarketTargetType.E_MARKET_TARGET_FUTURES_CN]

    def kline(self, n_folds=2, start=None, end=None):
        """日k线接口"""
        url = SNFuturesApi.K_NET_BASE % self._symbol.symbol_code
        data = ABuNetWork.get(url=url, timeout=(10, 60)).json()
        kl_df = self.data_parser_cls(self._symbol, data).df
        if kl_df is None:
            return None
        return FuturesBaseMarket._fix_kline_pd(kl_df, n_folds, start, end)

"""
Explanation: 上面编写的SNFuturesParser与SNUSParser基本相同：被类装饰器AbuDataParseWrap装饰，实现__init__函数
如本例中每一条json数据的格式为, 所以通过序号解析对应的值：
['2017-08-08', '4295.000', '4358.000', '4281.000', '4345.000', '175570']

在编写数据解析类后就需要编写个数据源类，如下所示，以新浪期货数据源为例：
End of explanation
"""


SNFuturesApi(code_to_symbol('usSINA')).check_support(rs=False)

"""
Explanation: 上面编写的SNFuturesApi即为一个期货数据源类：

期货类型数据源类需要继承FuturesBaseMarket
__init__函数中指定数据源解析类
数据源类需要混入SupportMixin类，实现_support_market方法，声明支持的市场，本例只支持期货市场
数据源类需要实现kline接口，完成获取指定symbol的日线数据，将日线数据交给数据源解析类进行处理

下面示例使用，如下通过check_support检测是否支持美股，结果显示False:
End of explanation
"""


SNFuturesApi(code_to_symbol('jd0')).check_support(rs=False)

"""
Explanation: 如下通过check_support检测是否支持期货，结果显示True：
End of explanation
"""


abupy.env.g_private_data_source = SNFuturesApi
ABuSymbolPd.make_kl_df('jd0').tail()

"""
Explanation: 接入SNFuturesApi至abupy系统中, 使用make_kl_df接口获取期货鸡蛋连续数据：
End of explanation
"""


@AbuDataParseWrap()
class HBTCParser(object):
    """示例币类市场数据源解析类，被类装饰器AbuDataParseWrap装饰"""
    def __init__(self, symbol, json_dict):
        """
        :param symbol: 请求的symbol str对象
        :param json_dict: 请求返回的json数据
        """
        data = json_dict
        # 为AbuDataParseWrap准备类必须的属性序列
        if len(data) > 0:
            # 时间日期序列
            self.date = [item[0] for item in data]
            # 开盘价格序列
            self.open = [item[1] for item in data]
            # 最高价格序列
            self.high = [item[2] for item in data]
            # 最低价格序列
            self.low = [item[3] for item in data]
            # 收盘价格序列
            self.close = [item[4] for item in data]
            # 成交量序列
            self.volume = [item[5] for item in data]

            # 时间日期进行格式转化，转化为如2017-07-26格式字符串
            self.date = list(map(lambda date: ABuDateUtil.fmt_date(date), self.date))

"""
Explanation: 与期货市场类似的是美股期权市场，abupy同样支持美股期权市场的回测分析等操作，但由于暂时没有合适的可对外的数据源提供，所以暂时无示例，用户也可以在abupy中接入自己的美股期权数据源。
7. 接入外部数据源，比特币，莱特币数据源
下面示例接入币类市场数据源，首先实现一个数据源返回数据解析类，如下所示：
End of explanation
"""


class HBApi(TCBaseMarket, SupportMixin):
    """hb数据源，支持币类，比特币，莱特币"""

    K_NET_BASE = 'https://www.huobi.com/qt/staticmarket/%s_kline_100_json.js?length=%d'

    def __init__(self, symbol):
        """
        :param symbol: Symbol类型对象
        """
        super(HBApi, self).__init__(symbol)
        # 设置数据源解析对象类
        self.data_parser_cls = HBTCParser

    def _support_market(self):
        """只支持币类市场"""
        return [EMarketTargetType.E_MARKET_TARGET_TC]

    def kline(self, n_folds=2, start=None, end=None):
        """日k线接口"""
        req_cnt = n_folds * 365
        if start is not None and end is not None:
            # 向上取整数，下面使用_fix_kline_pd再次进行剪裁, 要使用current_str_date不能是end
            folds = math.ceil(ABuDateUtil.diff(ABuDateUtil.date_str_to_int(start),
                                               ABuDateUtil.current_str_date()) / 365)
            req_cnt = folds * 365

        url = HBApi.K_NET_BASE % (self._symbol.symbol_code, req_cnt)
        data = ABuNetWork.get(url=url, timeout=(10, 60)).json()
        kl_df = self.data_parser_cls(self._symbol, data).df
        if kl_df is None:
            return None
        return TCBaseMarket._fix_kline_pd(kl_df, n_folds, start, end)

    def minute(self, *args, **kwargs):
        """分钟k线接口"""
        raise NotImplementedError('HBApi minute NotImplementedError!')

"""
Explanation: 上面编写的HBTCParser与上面的数据解析类基本相同：被类装饰器AbuDataParseWrap装饰，实现__init__函数
如本例中每一条json数据的格式为, 所以通过序号解析对应的值：
['20170809000000000', 22588.08, 23149.99, 22250.0, 22730.0, 7425.5134]

所以需要使用ABuDateUtil.fmt_date将时间进行格式转化，下面编写对应的数据源类，如下所示，以火币数据源为例：
End of explanation
"""


HBApi(code_to_symbol('usSINA')).check_support(rs=False)

"""
Explanation: 上面编写的HBApi即为一个支持比特币，莱特币数据源类：

期货类型数据源类需要继承TCBaseMarket
__init__函数中指定数据源解析类
数据源类需要混入SupportMixin类，实现_support_market方法，声明支持的市场，本例只支持币类市场
数据源类需要实现kline接口，完成获取指定symbol的日线数据，将日线数据交给数据源解析类进行处理
数据源类需要实现分钟k线接口minute，也可raise NotImplementedError

下面示例使用，如下通过check_support检测是否支持美股，结果显示False:
End of explanation
"""


HBApi(code_to_symbol('btc')).check_support(rs=False)

"""
Explanation: 如下通过check_support检测是否支持比特币，结果显示True：
End of explanation
"""


abupy.env.g_private_data_source = HBApi
ABuSymbolPd.make_kl_df('btc').tail()

"""
Explanation: 接入HBApi至abupy系统中, 使用make_kl_df接口获取期货比特币数据：
End of explanation
"""


