VeighNa进阶EP01:TuShare数据源接入

前言

上次我们介绍了一下vnpy量化框架的搭建,今天我们来说说TuShare数据源的接入。因为公司之前一直是从一些金融网站或者证券服务商获取的,公司最近决定改变策略通过TuShare获取。其实在VeighNa开源网站上官方也提供了一些数据服务商。本着能白嫖绝不花钱的准则,就试着接入下。接下来我们开始进入正题吧

账号注册

  1. 通过TuShare官网进入,注册并登陆个人账户,进入个人主页

    image-20220919151510855)

  2. 积分查询与获取

    新用户的积分是100,通过更新个人资料,可以增加20的积分

    image-20220919151510855

  3. 积分其他获取方式

    • 分享

      通过分享推荐链接,成功注册一个有效用户(指真正会使用tushare数据的用户)可获得50积分,虚假用户带来的积分会被定期回收!

    • 捐助(付费)

      通过捐助社区捐助一定的金额可提高个人的积分。捐助积分与频次关系图如下:

      积分数每分钟频次每天总量上限可以访问的接口捐助(元)
      120508000次股票基础信息、股票非复权日线行情,其他接口无法调取0
      2000以上200100000次/个APItushare.pro 60%的API可以调取,可参考每个接口的积分要求200
      5000以上500常规数据无上限tushare.pro 90%的API可以调取,可参考每个接口的积分要求500
      10000以上1000常规数据无上限,特色数据300次每分钟特色数据权限,包括盈利预测数据、每日筹码和胜率、筹码分布、券商每月金股等数据1000
      15000以上1000特色数据无总量限制特色数据专属权限1500

      10000积分以上可以有更高的API频次和权限,比如股票特色数据

      此外,分钟和港美股数据权限不在积分范畴内,各类分钟单独开权限

      类型包含数据历史起始捐助(元)频次
      沪深A股1、5、15、30、60分钟2009年1000每分钟500次,每次8000行数据,总量不限制
      期货同上2010年1000同上
      期权同上2010年2000,包含股指和商品期权同上
      港股日线全历史1000每分钟500次,每次3000行,总量不限制
      美股日线,包含估值指标、换手率等全股票全历史1000每分钟500次,每次6000行,总量不限制
      数字货币1~60分钟2020年开始1000每分钟500次,每次8000行,总量不限制
      新闻资讯快讯、长篇新闻、新闻联播、公司公告3年以上1000每分钟400次,总量不限制
    • 高校师生,确认身份后,可以免费获得2000积分(根据实际情况可以调整)

    • 参与社区贡献,比如提交数据问题、参与数据贡献、编写文章发给群主或积分管理员

  4. 接口调用与测试

    在本地编写Python测试脚本

    import tushare as ts
    ts.set_token('你的token')
    pro = ts.pro_api()
    df = pro.fut_daily(ts_code='CU1811.SHF', start_date='20180101', end_date='20181113')
    print(df)
    

    image-20220919151510855

  5. 接入vnpy

    • 安装

      pip install vnpy_tushare
      
    • vnpy项目根目录下创建vnpy_tushare\__init__.py

    import importlib_metadata
    
    from .tushare_datafeed import TushareDatafeed as Datafeed
    
    
    try:
        __version__ = importlib_metadata.version("vnpy_tushare")
    except importlib_metadata.PackageNotFoundError:
        __version__ = "dev"
    
    • vnpy_tushare\tushare_datafeed.py
    from datetime import timedelta, datetime
    from typing import Dict, List, Optional
    from copy import deepcopy
    
    import pandas as pd
    from pandas import DataFrame
    import tushare as ts
    from tushare.pro.client import DataApi
    
    from vnpy.trader.setting import SETTINGS
    from vnpy.trader.datafeed import BaseDatafeed
    from vnpy.trader.constant import Exchange, Interval
    from vnpy.trader.object import BarData, HistoryRequest
    from vnpy.trader.utility import round_to, ZoneInfo
    
    # 数据频率映射
    INTERVAL_VT2TS: Dict[Interval, str] = {
        Interval.MINUTE: "1min",
        Interval.HOUR: "60min",
        Interval.DAILY: "D",
    }
    
    # 股票支持列表
    STOCK_LIST: List[Exchange] = [
        Exchange.SSE,
        Exchange.SZSE,
        Exchange.BSE,
    ]
    
    # 期货支持列表
    FUTURE_LIST: List[Exchange] = [
        Exchange.CFFEX,
        Exchange.SHFE,
        Exchange.CZCE,
        Exchange.DCE,
        Exchange.INE,
    ]
    
    # 交易所映射
    EXCHANGE_VT2TS: Dict[Exchange, str] = {
        Exchange.CFFEX: "CFX",
        Exchange.SHFE: "SHF",
        Exchange.CZCE: "ZCE",
        Exchange.DCE: "DCE",
        Exchange.INE: "INE",
        Exchange.SSE: "SH",
        Exchange.SZSE: "SZ",
    }
    
    # 时间调整映射
    INTERVAL_ADJUSTMENT_MAP: Dict[Interval, timedelta] = {
        Interval.MINUTE: timedelta(minutes=1),
        Interval.HOUR: timedelta(hours=1),
        Interval.DAILY: timedelta()
    }
    
    # 中国上海时区
    CHINA_TZ = ZoneInfo("Asia/Shanghai")
    
    
    def to_ts_symbol(symbol, exchange) -> Optional[str]:
        """将交易所代码转换为tushare代码"""
        # 股票
        if exchange in STOCK_LIST:
            ts_symbol: str = f"{symbol}.{EXCHANGE_VT2TS[exchange]}"
        # 期货
        elif exchange in FUTURE_LIST:
            if exchange is not Exchange.CZCE:
                ts_symbol: str = f"{symbol}.{EXCHANGE_VT2TS[exchange]}".upper()
            else:
                for count, word in enumerate(symbol):
                    if word.isdigit():
                        break
    
                year: str = symbol[count]
                month: str = symbol[count + 1:]
                if year == "9":
                    year = "1" + year
                else:
                    year = "2" + year
    
                product: str = symbol[:count]
                ts_symbol: str = f"{product}{year}{month}.ZCE".upper()
        else:
            return None
    
        return ts_symbol
    
    
    def to_ts_asset(symbol, exchange) -> Optional[str]:
        """生成tushare资产类别"""
        # 股票
        if exchange in STOCK_LIST:
            if exchange is Exchange.SSE and symbol[0] == "6":
                asset: str = "E"
            elif exchange is Exchange.SZSE and symbol[0] == "0" or symbol[0] == "3":
                asset: str = "E"
            else:
                asset: str = "I"
        # 期货
        elif exchange in FUTURE_LIST:
            asset: str = "FT"
        else:
            return None
    
        return asset
    
    
    class TushareDatafeed(BaseDatafeed):
        """TuShare数据服务接口"""
    
        def __init__(self):
            """"""
            self.username: str = SETTINGS["datafeed.username"]
            self.password: str = SETTINGS["datafeed.password"]
    
            self.inited: bool = False
    
        def init(self) -> bool:
            """初始化"""
            if self.inited:
                return True
    
            ts.set_token(self.password)
            self.pro: Optional[DataApi] = ts.pro_api()
            self.inited = True
    
            return True
    
        def query_bar_history(self, req: HistoryRequest) -> Optional[List[BarData]]:
            """查询k线数据"""
            if not self.inited:
                self.init()
    
            symbol: str = req.symbol
            exchange: Exchange = req.exchange
            interval: Interval = req.interval
            start: datetime = req.start.strftime("%Y-%m-%d %H:%M:%S")
            end: datetime = req.end.strftime("%Y-%m-%d %H:%M:%S")
    
            ts_symbol: str = to_ts_symbol(symbol, exchange)
            if not ts_symbol:
                return None
    
            asset: str = to_ts_asset(symbol, exchange)
            if not asset:
                return None
    
            ts_interval: str = INTERVAL_VT2TS.get(interval)
            if not ts_interval:
                return None
    
            adjustment: timedelta = INTERVAL_ADJUSTMENT_MAP[interval]
    
            try:
                d1: DataFrame = ts.pro_bar(
                    ts_code=ts_symbol,
                    start_date=start,
                    end_date=end,
                    asset=asset,
                    freq=ts_interval
                )
            except IOError:
                return []
    
            df: DataFrame = deepcopy(d1)
    
            while True:
                if len(d1) != 8000:
                    break
                tmp_end: str = d1["trade_time"].values[-1]
    
                d1 = ts.pro_bar(
                    ts_code=ts_symbol,
                    start_date=start,
                    end_date=tmp_end,
                    asset=asset,
                    freq=ts_interval
                )
                df = pd.concat([df[:-1], d1])
    
            bar_keys: List[datetime] = []
            bar_dict: Dict[datetime, BarData] = {}
            data: List[BarData] = []
    
            # 处理原始数据中的NaN值
            df.fillna(0, inplace=True)
    
            if df is not None:
                for ix, row in df.iterrows():
                    if row["open"] is None:
                        continue
    
                    if interval.value == "d":
                        dt: str = row["trade_date"]
                        dt: datetime = datetime.strptime(dt, "%Y%m%d")
                    else:
                        dt: str = row["trade_time"]
                        dt: datetime = datetime.strptime(dt, "%Y-%m-%d %H:%M:%S") - adjustment
    
                    dt = dt.replace(tzinfo=CHINA_TZ)
    
                    turnover = row.get("amount", 0)
                    if turnover is None:
                        turnover = 0
    
                    open_interest = row.get("oi", 0)
                    if open_interest is None:
                        open_interest = 0
    
                    bar: BarData = BarData(
                        symbol=symbol,
                        exchange=exchange,
                        interval=interval,
                        datetime=dt,
                        open_price=round_to(row["open"], 0.000001),
                        high_price=round_to(row["high"], 0.000001),
                        low_price=round_to(row["low"], 0.000001),
                        close_price=round_to(row["close"], 0.000001),
                        volume=row["vol"],
                        turnover=turnover,
                        open_interest=open_interest,
                        gateway_name="TS"
                    )
    
                    bar_dict[dt] = bar
    
            bar_keys: list = bar_dict.keys()
            bar_keys = sorted(bar_keys, reverse=False)
            for i in bar_keys:
                data.append(bar_dict[i])
    
            return data
    
    • 使用

      在VeighNa中使用TuShare时,需要在全局配置中填写以下字段信息:

      名称含义必填举例
      datafeed.name名称tushare
      datafeed.username用户名token
      datafeed.password密码c3a110417f08f26d2c221edc0c50d4a8a5001502eea89cf5
  6. 测试

    • 查看配置项

      image-20220919151510855

      这里的数据库选择默认的sqlite

    • 填写参数

      周期没有tick级的

      image-20220919151510855

    • 下载数据

      image-20220919151510855

      数据库查询

      image-20220919151510855

    • 开始回测

      image-20220919151510855

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值