拉取并且mongo保存聚宽的期货分钟线数据

聚宽服务

在聚宽官网申请账号,然后阅读相关 api 文档。
https://www.joinquant.com/view/user/floor?type=mainFloor

安装 SDK 并且登录

使用 pip install jqdatasdk 进行安装, 已经安装之后进行更新安装。
在这里插入图片描述

登录

def login(user_name, password):
    try:
        auth(user_name, password)
    except Exception as e:
        logger.info(e)
        return False
    # 判断是否登录成功
    if not is_auth():
        return False
    return True

在官网申请之后, user_name 一般是手机号, password 默认是手机号的后 6 位。 当前使用期 15 天内,每天有 100w 条的数据权限,100w 条数到了的时候给出提醒,自动升级到 1000 w 条。 需要更多的话可能即需要进行付费。

调用 get_price 接口

对调用聚宽 sdk 的 get_price 接口进行封装。首先查看一下接口的说明,可以参照官网文档,或直接点进代码调用的地方进行查看。

相关说明如下:

@assert_auth
def get_price(security, start_date=None, end_date=None, frequency='daily',
              fields=None, skip_paused=False, fq='pre', count=None, panel=True, fill_paused=True):
    """
    获取一支或者多只证券的行情数据

    :param security 一支证券代码或者一个证券代码的list
    :param count 与 start_date 二选一,不可同时使用.数量, 返回的结果集的行数, 即表示获取 end_date 之前几个 frequency 的数据
    :param start_date 与 count 二选一,不可同时使用. 字符串或者 datetime.datetime/datetime.date 对象, 开始时间
    :param end_date 格式同上, 结束时间, 默认是'2015-12-31', 包含此日期.
    :param frequency 单位时间长度, 几天或者几分钟, 现在支持'Xd','Xm', 'daily'(等同于'1d'), 'minute'(等同于'1m'), X是一个正整数, 分别表示X天和X分钟
    :param fields 字符串list, 默认是None(表示['open', 'close', 'high', 'low', 'volume', 'money']这几个标准字段), 支持以下属性 ['open', 'close', 'low', 'high', 'volume', 'money', 'factor', 'high_limit', 'low_limit', 'avg', 'pre_close', 'paused']
    :param skip_paused 是否跳过不交易日期(包括停牌, 未上市或者退市后的日期). 如果不跳过, 停牌时会使用停牌前的数据填充, 上市前或者退市后数据都为 nan
    :param panel: 当传入一个标的列表的时候,是否返回一个panel对象,默认为True,表示返回一哥panel对象
           注意:
               当security为一个标的列表,且panel=False的时候,会返回一个dataframe对象,
               在这个对象中额外多出code、time两个字段,分别表示该条数据对应的标的、时间
    :param fill_paused : False 表示使用NAN填充停牌的数据,True表示用close价格填充,默认True
    :return 如果是一支证券, 则返回pandas.DataFrame对象, 行索引是datetime.datetime对象, 列索引是行情字段名字; 如果是多支证券, 则返回pandas.Panel对象, 里面是很多pandas.DataFrame对象, 索引是行情字段(open/close/…), 每个pandas.DataFrame的行索引是datetime.datetime对象, 列索引是证券代号.
    """

因为不同的期货一段时间内的根数是不一致的,所以是决定 security 参数每次只传入一只期货。 因为我们需要的是分钟线,所以传入的 frequency 是 “1m”, 对应的字段 fields 没有传入, 默认是期货的全部字段,在这里是
[‘open’, ‘close’, ‘low’, ‘high’, ‘volume’, ‘money’]

因为是免费账号,调用是计算次数的,为了避免次数用完,我们可以将数据先在 csv 文件中进行备份,然后下次直接读取 cvs 文件,避免浪费掉调用次数。缺点就是这个csv 文件也是有点大的,我是保存在本地,然后 IDE 就卡住了。所以根据自己的需求对关键数据进行保存。

保存的时候,要指明这是哪一只合约的哪段时间的数据。

相关的代码:

def jz_get_price(security, start_date: datetime.datetime, end_date: datetime.datetime,
                 frequency='1m'):
    # 调用聚宽的接口
    df = get_price(security, start_date, end_date, frequency)
    if save_csv:
        # 将结果写入 csv 文件 (因为聚宽每天的条数是有限制的)
        dt_format = "%Y-%m-%d-%H-%M-%S"
        file_name = "_".join([security, start_date.strftime(dt_format), end_date.strftime(dt_format)])
        file_name = os.path.join("./csv", file_name)
        # 保存
        df.to_csv(file_name, index=True, sep=',')
    return df


def read_df_from_csv(csv_file):
    # index_col = 0 的意思是直接使用第一列作为索引
    df = pd.read_csv(csv_file, index_col=0)
    return df

关于 mongo 数据库

在我们的 mongo 数据库里面已经存在了一个叫做 info 的集合,可以利用它筛选出每天未过期的期货合约。
mongo 数据库区别一下,可能是线上的,也可能是本地的。所以写了两个 get_coll 函数:

def get_124_coll():
    return pymongo.MongoClient("127.0.0.1:27017")


def get_local_coll():
    return pymongo.MongoClient("127.0.0.1:27018")


def fetch_un_expire_codes(dt: datetime.datetime):
    # 获取到每日的未过期合约;统一 code 的格式
    cli = get_124_coll()
    futures = cli.test_future.info.find({"expire_date": {"$gte": dt}})
    codes = [future.get("code") for future in futures]
    return codes

统一合约代码的格式

聚宽的合约代码是有 交易所后缀的,我们自己的需求是不需要的。从 info 数据库中筛选出来的合约要经过转换才能作为聚宽 get_price 接口的参数。

def _jq_code_format(code):
    # 转换合约代码为聚宽要求的后缀模式
    CON_EXCHANGE_DICT = {'SH': 'XSHG', 'SZ': 'XSHE', 'IX': 'INDX', 'SF': 'XSGE', 'DF': 'XDCE',
                         'ZF': 'XZCE', 'CF': 'CCFX', 'IF': 'XINE'}

    exchange, id = code[:2], code[2:]
    assert exchange in ("CF", "DF", "SF", "ZF", "IF")
    con_exchange = CON_EXCHANGE_DICT.get(exchange, "")
    if con_exchange:
        return ".".join([id, con_exchange])

根据要求对每一行数据进行修正

在拿到的 df 结构中,时间是索引。但是在插入的 mongo 数据中, 时间是作为每一条数据中的一个字段的。首先我们就要把索引字段作为一个正常的列加入 df 中。

另外,聚宽的分钟线标识规则是 用 9:01 这个时间点来标识 9:00 - 9:01 这个时间段内的分钟线数据,我们自己的规则是用 9:00 来标识同样的一段分钟线。

所以,对于聚宽的索引要进行减 1 min 的操作。

同时,我们拿聚宽某一天的分钟线,就应该是从 00:01 到下一天的 00:00 , 对应于我们系统的 00:00到同一天的 00:59 。

最后,聚宽的字段是: [‘open’, ‘close’, ‘low’, ‘high’, ‘volume’, ‘money’]
我们系统的字段是: [‘open’, ‘close’, ‘low’, ‘high’, ‘volume’, ‘amount’]

也就是说我们要把 money 字段改名为 amount 字段插入数据库中。

最后注意我们还要把字符串形式的时间转换 datetime ,这样我们使用 python 的库进行插入的时候,才会自动转换为 ISODate 类型。

def generate_inserts(df: pd.DataFrame):
    # 将 money 列重命名为 amount
    df = df.rename(columns={'money': 'amount'})
    # 将索引转换为其中的一列
    df['time'] = pd.to_datetime(df.index)
    # 将聚宽的时间整体减1min
    df['time'] = df['time'].map(lambda dt: dt - pd.Timedelta(minutes=1))
    # 转换时间格式
    df.time = df.time.astype(str)
    inserts = list(json.loads(df.to_json(orient="index", date_format="iso")).values())
    for insert in inserts:
        insert.update({"time": datetime.datetime.strptime(insert['time'], "%Y-%m-%d %H:%M:%S")})
    # 生成待插入的列表
    return inserts

插入数据

数据准备好之后,就要向 mongo 数据库中进行插入了。

首先我们要确定好数据库的索引。在本例中,很显然,我们要使用 code 和 time 作为联合索引。
创建 mongo 索引可以直接在终端进行,也可以使用 pymongo 进行。

终端方式:

> db.price.ensureIndex({"code": 1, "time": 1}, {unique: true})
{
	"createdCollectionAutomatically" : true,
	"numIndexesBefore" : 1,
	"numIndexesAfter" : 2,
	"ok" : 1
}

主程序

将以上步骤整合起来即可:
在这里插入图片描述

def main(start: datetime.datetime, end: datetime.datetime):
    t1 = time.time()
    # 登录
    login("xxxxxxxxxxx", "xxxxxx")
    # 待插入的数据库
    conn = get_local_coll().test_futures.prices

    for dt in pd.date_range(start, end, freq="1d"):
        dt = dt.to_pydatetime()
        # 当天筛选出的合约
        futures = fetch_un_expire_codes(dt)
        logger.info(f"{dt}, {futures}")

        for info_future in futures:
            future = _jq_code_format(info_future)
            df = jz_get_price(future, dt+datetime.timedelta(minutes=1), dt+datetime.timedelta(days=1))
            datas = generate_inserts(df)
            mongo_bulk_insert(conn, info_future, datas)
            logger.info(f"{future} {dt} 插入成功 ")
    t2 = time.time()
    logger.info(f"耗时是: {(t2 - t1) / 60} min")


main(datetime.datetime(2019, 11, 1), datetime.datetime(2019, 11, 20))

导出数据库文件

在这里插入图片描述

完整代码: https://github.com/furuiyang0715/JQFuturesLoader/blob/master/loader.py

更新时间: 2019-11-22

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值