量化必备:Level2实时行情接口和交易报单接口,以及历史数据回测

相同的二级市场,相同的市场数据,为什么自己的账户总是亏的。真正拉开差距的,往往是信息的获取速度和执行效率。 你还在手动盯盘、手动下单的时候,别人已经用程序化交易在毫秒间完成了买卖。

可能他们有更好的策略、更多的资金,或者更丰富的经验。但说实话,这些都不是最核心的原因。

普通投资者接入了实时行情和交易接口,也能搭建自己的量化交易系统。

实时行情接入:为什么Level2行情是量化交易的“刚需”?
很多量化新手一开始都会去抓取基础行情数据,比如Akshare或者一些券商的普通行情接口。但很快就会发现,这些数据远远不够,并发抓取更是灾难。

而且Level2行情根本抓不到,对于高频交易和策略优化来说,Level2行情是必需的数据源。

之前也抓过不少行情源,刷新间隔太大,基本都在3~6s,再刷新拉板都封死了。

Wind 恒生等服务商只对机构合作。只有搞到Level2高速行情,才算是拉平了数据源的差距。用WebSocket接口非常稳定,延迟低,数据全,接入也很简单,没有那么复杂的协议编码。下面是找的一个C++示例,通过WebSocket来接入Level2行情:

#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/client.hpp>
#include <string>
#include <iostream>
#include <memory>
#include <assert.h>
#include <cstring>
#include "zlib.h"
#define CHUNK 16384
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
typedef websocketpp::client <websocketpp::config::asio_client> client;
typedef websocketpp::config::asio_client::message_type::ptr message_ptr;
int DecompressString(const char *in_str, size_t in_len, std::string &out_str);
 
/**
 * 接收处理
 */
void on_message(client *c, websocketpp::connection_hdl hdl, message_ptr msg) {
    //文本消息
    if (msg->get_opcode()==websocketpp::frame::opcode::text){
        std::cout <<"Text响应:"<<msg->get_payload().c_str()<< std::endl;
    }
    //二进制消息
    if (msg->get_opcode()==websocketpp::frame::opcode::binary){
        std::string tmp = "";
        std::string &out_decompress = tmp;
        DecompressString( msg->get_payload().c_str(), msg->get_payload().size(), out_decompress);
        std::cout <<"Binary响应:"<<out_decompress<< std::endl;
    }
}
 
/**
 * 连接处理
 */
void on_open(client *c, websocketpp::connection_hdl hdl) {
    //发送订阅指令
    c->send(hdl, "add=lv1_600519,lv2_600519", websocketpp::frame::opcode::text);
    std::cout << "连接成功" << std::endl;
}
 
int main(int argc, char *argv[]) {
    //服务地址。 注意:C++版本的地址 问号前需加斜杠
    std::string wsUrl = "ws://<服务器地址>/?token=<jvQuant token>";
 
    client c;
    //连接相关
    try {
        //debug日志开关
//        c.set_access_channels(websocketpp::log::alevel::all);
        c.clear_access_channels(websocketpp::log::alevel::all);
        c.init_asio();
 
        // 注册处理函数
        c.set_message_handler(bind(&on_message, &c, ::_1, ::_2));
        c.set_open_handler(bind(&on_open, &c, _1));
 
        websocketpp::lib::error_code ec;
        client::connection_ptr con = c.get_connection(wsUrl, ec);
        if (ec) {
            std::cout << "连接失败: " << ec.message() << std::endl;
            return 0;
        }
        c.connect(con);
        c.run();
    } catch (websocketpp::exception const &e) {
        std::cout << e.what() << std::endl;
    }
}
/**
 *解压缩方法
 */
int DecompressString(const char *in_str, size_t in_len, std::string &out_str) {
    if (!in_str)
        return Z_DATA_ERROR;
    int ret;
    unsigned have;
    z_stream strm;
    unsigned char out[CHUNK];
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = 0;
    strm.next_in = Z_NULL;
    ret = inflateInit2(&strm, -MAX_WBITS);
    if (ret != Z_OK)
        return ret;
    std::shared_ptr <z_stream> sp_strm(&strm, [](z_stream *strm) {
        (void) inflateEnd(strm);
    });
    const char *end = in_str + in_len;
    size_t pos_index = 0;
    size_t distance = 0;
    int flush = 0;
    do {
        distance = end - in_str;
        strm.avail_in = (distance >= CHUNK) ? CHUNK : distance;
        strm.next_in = (Bytef *) in_str;
        in_str += strm.avail_in;
        flush = (in_str == end) ? Z_FINISH : Z_NO_FLUSH;
        do {
            strm.avail_out = CHUNK;
            strm.next_out = out;
            ret = inflate(&strm, Z_NO_FLUSH);
            if (ret == Z_STREAM_ERROR)
                break;
            switch (ret) {
                case Z_NEED_DICT:
                    ret = Z_DATA_ERROR;
                case Z_DATA_ERROR:
                case Z_MEM_ERROR:
                    return ret;
            }
            have = CHUNK - strm.avail_out;
            out_str.append((const char *) out, have);
        } while (strm.avail_out == 0);
    } while (flush != Z_FINISH);
    return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}
                    

copy下来运行就可以实时获取到Level2行情数据,包括买卖盘口、逐笔成交等细节信息。

Python版的Demo请参考:Python 示例 · 开发文档

交易接口:如何实现自动下单?
有了行情数据,接下来就是如何自动下单了。很多量化新手在这一步卡住,因为市面上的交易接口要么不稳定,要么接入复杂。我用的这家服务商提供的交易接口非常简单易用,支持港股、美股和A股的自动下单。

下面是一个简单的交易委托示例:

# 交易委托示例
# 报单模版
http://<柜台地址>/<type>?&token=<token>&ticket=<交易凭证>&code=<证券代码>&name=<证券名称>&price=<委托价格>&volume=<委托数量>
简单的请求,就可以实现自动买入、卖出操作。接口返回的order_id用来跟踪订单状态。

如何快速查询历史数据和生成选股策略?
除了实时行情和交易接口,研究交易策略还需要大量的历史数据进行回测和分析。


K线级别的历史数据很好弄到,各大财经网站都有数据,更细粒度的历史行情就不好弄了。

有些券商提供分时回放功能,这家服务商也有类似功能,历史指数行情也有回放,可以用市场大盘来协同回测股票。


数据库服务还有了智能语义查询功能,类似于智能选股,比写筛选逻辑更简单,校验过,数据都很靠谱。

比如,你想查询沪深主板上非ST股票,且市盈率大于60的股票,用语义化语句这样写:

查询沪深主板上非ST股票,市盈率大于60 query=沪深主板,非ST,市盈率大于60
数据库还支持多条件组合查询、模糊查询等功能,非常适合需要复杂数据筛选的场景。官方的查询语句示例:

字段精确条件
查询沪深主板上非ST股票,要求市盈率、行业,并且昨日最高涨幅大于4%,前2日最低涨幅小于8%:
示例:query=沪深主板,非ST,市盈率,行业,昨日最高涨幅大于4,前2日最低涨幅小于8
 
字段模糊条件
查询包含“集合竞价抢筹”或“30日均线向上”等模糊条件的股票:
示例:query=集合竞价抢筹,30日均线向上,macd底背离
 
多个指定日期条件查询(历史数据支持最近3年)
查询在2015年9月13日跌停,且在2015年9月14日涨停的股票(示例中的日期应替换为实际查询的日期):
示例:query=2015-09-13跌停,2015-09-14涨停
 
多个条件组合查询
查询主板上市盈率大于60的股票,或者属于华为概念且市盈率小于50的股票:
示例:主板,市盈率大于60,或者(华为概念并且市盈率小于50))
实时行情和交易接口以及数据库的更多细节可以参考 官方文档

其实,量化交易的核心在于信息的速度和执行的效率。手动盯盘、手动下单,已经比别人慢了很多。用程序接入实时行情、交易接口和金融数据库,大幅提升交易效率和策略稳定性,剩下的,你只需要研究和检验策略执行效果即可,其他的交给时间。

原文链接:https://zhuanlan.zhihu.com/p/29605505797
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/FuckTheWindows/article/details/146196431

posted @ 2025-03-12 09:34  chromeplugin  阅读(738)  评论(0)    收藏  举报