掘金量化数据的提供历史bar和tick历史股票、期货、基金等数据接口,包含免费和付费bar和tick数据读取函数,其中bar可以提供以秒为单位的,tick数据一次最多可以读取5天的,付费用户能够提供更长时间的数据读取。免费的已经够用已经可以满足我们量化策略对历史数据的基本需求。

1、安装掘金量化数据的python SDK

这一步是必须的,安装完成后,您的lib\site_packages会多出一个gm子目录,其中包含的是掘金量化数据的python SDK,这是我们实现。

  • 如果未使用uv等包管理器,使用下面命令:

    pip install gm -i https://mirrors.aliyun.com/pypi/simple/ -U
  • 如果使用uv包管理器,使用下面命令:

    uv pip install gm -i https://mirrors.aliyun.com/pypi/simple/ -U

2、创建vnpy_gmdata

  • 创建vnpy_gmdata文件夹
  • 添加init.py到vnpy_gmdata目录下,内容如下:
# The MIT License (MIT)
#
# Copyright (c) 2015-present, Xiaoyou Chen
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


import importlib_metadata

from .gm_datafeed import GmDatafeed as Datafeed

try:
    __version__ = importlib_metadata.version("vnpy_gmdata")
except importlib_metadata.PackageNotFoundError:
    __version__ = "dev"
  • 添加gm_datafeed.py到vnpy_gmdata目录下,内容如下:
from datetime import datetime
from typing import List, Callable

from gm.api import *
from tzlocal import get_localzone
from vnpy.trader.utility import ZoneInfo    # hxxjava add

from vnpy.trader.constant import Exchange, Interval
from vnpy.trader.datafeed import BaseDatafeed
from vnpy.trader.object import BarData, TickData, HistoryRequest
from vnpy.trader.setting import SETTINGS
from datetime import timedelta

INTERVAL_VT2GM = {
    Interval.MINUTE: "60s",
    Interval.HOUR: "3600s",
    Interval.DAILY: "1d",
}

def to_gm_symbol(symbol: str, exchange: Exchange) -> str:
    """
    把合约名称与交易所构建成为掘金的格式:‘交易所大写.合约名称小写’
    """
    gm_symbol = f"{exchange.value.upper()}.{symbol.lower()}"
    return gm_symbol


class GmDatafeed(BaseDatafeed):
    """掘金GMData客户端封装类"""

    def __init__(self):
        """"""
        # 加载配置
        self.username = SETTINGS["datafeed.username"]

        self.inited = False

        self.init();

        # print(f"【gm 1 token: {self.username}, inited = {self.inited} 】")

    def init(self, output: Callable = print) -> bool:
        """"""
        if self.inited:
            return True

        try:
            set_token(self.username)
            output("gmdata inited !")

        except Exception as ex:
            output("gm auth fail:" + repr(ex))
            return False

        self.inited = True
        return True


    def to_vn_symbol(self, EXCHANGE_symbol:str) -> str:
        """
        gm symbol list
        ['CZCE.CY501', 'DCE.eb2411', 'DCE.m2501'', 'CFFEX.TL2412', 'DCE.c2501']
        """
        EXCHANGE, symbol = EXCHANGE_symbol.split(".")
        vt_symbol = f"{symbol.lower()}.{EXCHANGE.upper()}"

        return vt_symbol

    def get_dominant_future(self, symbol: str):
        return "gm主力合约查找有待开发"

    def query_bar_history(self, req: HistoryRequest, output: Callable = print) -> list[BarData]: 
        """
        Query history bar data from GMData.
        """
        bars: List[BarData] = []

        shanghai_tz = ZoneInfo("Asia/Shanghai") # hxxjava

        # print(f"[1 req={req}]")
        symbol = req.symbol
        exchange = req.exchange
        interval = req.interval
        start = req.start.replace(tzinfo=shanghai_tz) # hxxjava
        end = req.end.replace(tzinfo=shanghai_tz) # hxxjava
        # print(f"【gm 2 start= {start} end={end}")

        gm_symbol = to_gm_symbol(symbol, exchange)
        # print(f"[2 gm_symbol={gm_symbol}]")

        gm_interval = INTERVAL_VT2GM.get(interval)
        if not gm_interval:
            return bars

        # adjustment = INTERVAL_ADJUSTMENT_MAP_GM.get(interval)

        if start > end:
            return bars
        # now = datetime.now(get_localzone())
        now = datetime.now().replace(tzinfo=shanghai_tz) # hxxjava
        if end >= now:
            end = now
        elif end.year == now.year and end.month == now.month and end.day == now.day:
            end = now

        fields = ['open', 'close', 'low', 'high', 'volume', 'amount', "position",'bob']

        try:
            df = history(symbol = gm_symbol, frequency = gm_interval, start_time = start, end_time = end, fields=fields , skip_suspended=True,
                    fill_missing=None, adjust=ADJUST_NONE, adjust_end_time='', df=True)
        except Exception as ex:
            output(f"[3 df={ex}]")
            return bars

        if df is not None:
            for ix, row in df.iterrows():
                dt = row["bob"].to_pydatetime()
                # dt = CHINA_TZ.localize(dt)
                bar = BarData(
                    symbol=symbol,
                    exchange=exchange,
                    interval=interval,
                    datetime=dt,
                    open_price=row["open"],
                    high_price=row["high"],
                    low_price=row["low"],
                    close_price=row["close"],
                    open_interest= row["position"],
                    volume=row["volume"],
                    turnover= row['amount'],
                    gateway_name="GM"
                )
                bars.append(bar)

        # print(f"【gm 3 bars = {bars}") # hxxjava

        return bars

    def query_tick_history(self, req: HistoryRequest, output: Callable = print) -> list[TickData]:
        """查询Tick数据"""
        if not self.inited:
            n: bool = self.init(output)
            if not n:
                return []

        symbol: str = req.symbol
        exchange: Exchange = req.exchange
        start: datetime = req.start
        end: datetime = req.end

        # 股票期权不添加交易所后缀
        gm_symbol: str = to_gm_symbol(symbol,exchange)

        fields = [
            'symbol','open','high','low','price',
            'cum_volume','cum_amount','iopv','last_amount','last_volume','quotes',
            'cum_position','trade_type','flag','created_at'
        ]

        # print(f"gm_symbol={gm_symbol},start={start},end={end},fields={fields}")

        if (end > start + timedelta(days=180)):
            print(f"读取的时间太长,读取失败!")  
            return []

        try:
            history_data : list = history(symbol=gm_symbol, frequency='tick', start_time=start, end_time = end, fields=fields, skip_suspended=True,
                fill_missing=None, adjust=ADJUST_NONE, adjust_end_time='', df=False)
        except Exception as ex:
            output(f"读取历史tick错误!{ex}")
            return ticks

        ticks: List[TickData] = []

        for td in history_data:
            dt: datetime = td["created_at"]
            # dt: datetime = dt.replace(tzinfo=CHINA_TZ)
            tick : TickData = TickData(
                symbol=symbol,
                exchange=exchange,
                datetime=dt,
                open_price = td['open'],
                high_price = td['high'],
                low_price = td['low'],
                last_price = td['price'],
                turnover = td['last_amount'],
                last_volume= td['last_volume'],
                bid_price_1 = td['quotes'][0]['bid_p'],
                bid_volume_1 = td['quotes'][0]['bid_v'],
                ask_price_1 = td['quotes'][0]['ask_p'],
                ask_volume_1 = td['quotes'][0]['ask_v'],
                open_interest=td['cum_position'],
                gateway_name="GM"
            )
            ticks.append(tick)

        return ticks

3、修改用户目录下C:\users\<用户名>\ .vntrader\vt_setting.json的全局配置项目

方法1:

如下图所示,vt_setting.json文件中有关datafeed的配置项为"datafeed.name": "gmdata", "datafeed.username": "your token","datafeed.password": "",其中your token为你的掘金账户的token:

{
    "font.family": "微软雅黑",
    "font.size": 12,
    "log.active": true,
    "log.level": 50,
    "log.console": true,
    "log.file": true,
    "email.server": "smtp.qq.com",
    "email.port": 465,
    "email.username": "xxxxx@qq.com",
    "email.password": "xxxxx",
    "email.sender": "xxxxx@qq.com",
    "email.receiver": "xxxxxx@qq.com",
    "datafeed.name": "gmdata",
    "datafeed.username": "your token",
    "datafeed.password": "",
    "database.timezone": "Asia/Shanghai",
    "database.name": "mysql",
    "database.database": "vnpy",
    "database.host": "localhost",
    "database.port": 3306,
    "database.user": "root",
    "database.password": "xxxxx"
}

方法2

在vntrader的菜单的配置中有关datafeed的选项做如下配置,其中datafeed.name=rqdata,保证datafeed实现匹配vnpy_gmdata模块,datafeed.username="xxxxx....xxxxxx",为用户申请的掘金数据接口API的token。

description

description

掘金数据接口API的token获取方法:从https:www.myquant.cn下载掘金3基础版本,进入系统设置界面,复制其中的“密钥管理(Token)”,填写到我们的datafeed.username中:

description

4、测试掘金datafeed是否成功

启动vntrader,从主界面中点击数据管理图标进入数据管理界面,然后点击“下载数据”按钮,填写要选择的合约名称、交易所、周期为Minute和开始时间,等待一段时间后就可以完成指定合约的历史数据。数据完成后点击“刷新”按钮刷新已经下载的历史数据,就可以查询到已经下载数据。

description

description

至此,可以证明用掘金数据替代米宽的datafeed已经替换成功。