我知道我可以继续挖掘,以而进一步提高胜率——但我决定不再继续下去了。
策略的第一个关键问题,是对(现在)订单流数据结构的理解。在实际构建高频模型前,我们必须明确:当用户以市价扫单,吃掉多个挂单时,交易所返回的订单流数据,是将其合并成一笔均价订单,还是逐笔拆分记录每一档成交?我打印出示例订单流(为简洁仅展示部分):
{"p":"0.22970000","q":"227.00000000","T":1748246611586}
{"p":"0.22969000","q":"120.00000000","T":1748246611975}
{"p":"0.22968000","q":"25.00000000","T":1748246611975}
{"p":"0.22967000","q":"9713.00000000","T":1748246613038}
{"p":"0.22966000","q":"10398.00000000","T":1748246613038}
{"p":"0.22965000","q":"16900.00000000","T":1748246613038}
分析订单流结果可以看到,大部分订单流的时间间隔在数十毫秒以上,而少部分时候会出现同毫秒的订单流,极少数情况会有相邻毫秒的订单,因此:
在同一毫秒(例如 1748246613038)内,存在多个成交价格与数量,表明这是一次扫单行为被拆分成多笔成交。
因为如果数据流是发布均价的话,那么数据分布不会不均匀——应当是相邻毫秒订单流数量与同毫秒的订单流出现的频率差不多。同时查阅文档可以看到交易所内部的数据精度达到微秒甚至更精细的级别,进一步验证了该判断。
由此构建出我的获取订单流部分代码,我采用了合并同毫秒订单计算并存储高精度均价的方式,我现在想来可能可以直接存储扫单的全部成交信息,精确根据实时订单簿处理开单,不过我也不想继续做下去了:
json json_msg = json::parse(msg->get_payload());
if ((json_msg.contains("ping")) || (json_msg.contains("PING"))) {
std::cout << "收到PING消息,发送PONG响应..." << std::endl;
ws_client_trade.pong(hdl, msg->get_payload()); // 使用相同的payload回复Pong
}
// 提取字段
long long timestamp = json_msg["T"];
double price = std::stod(json_msg["p"].get<std::string>());
double quantity = std::stod(json_msg["q"].get<std::string>());
bool m = json_msg["m"];
// 创建 Order 对象并设置方向
Order new_orderyubei(timestamp, price, quantity);//把数据给neworderyubei
new_orderyubei.direction = m ? "SELL" : "BUY";//根据交易所规则 如果订单流方向是True 意味着是主动卖出的订单
new_orderyubei.mark_price = current_mark_price.load();//有另一个函数同时接收标记价格
safe_push_back(existing_ordersyubei, new_orderyubei, max_existing_ordersyubei, mtx_existing_ordersyubei);
bool yubeiwanbi = false;
{
std::lock_guard<std::mutex> lock(mtx_existing_ordersyubei);
if (existing_ordersyubei.size() > 2)
{ // 确保队列长度大于 2
Order& last_order = existing_ordersyubei[existing_ordersyubei.size() - 1];
Order& second_last_order = existing_ordersyubei[existing_ordersyubei.size() - 2];
long long time_diff = last_order.timestamp - second_last_order.timestamp;
if (time_diff == 0 &&
last_order.direction == second_last_order.direction) {
double total_quantity = second_last_order.quantity + last_order.quantity;
double weighted_price = (second_last_order.price * second_last_order.quantity +
last_order.price * last_order.quantity) / total_quantity;
weighted_price = std::round(weighted_price * 1e11) / 1e11; // 高精度存储均价
// 更新第二个订单
second_last_order.price = weighted_price;
second_last_order.quantity = total_quantity;
// 删除最后一个订单
existing_ordersyubei.pop_back();
}
else {
yubeiwanbi = true;
}
}
}
if (yubeiwanbi) { // 将新订单加入现有订单队列
Order new_order = existing_ordersyubei[existing_ordersyubei.size() - 2];
// 线程安全地将新订单添加到 existing_orders 下面是我定义的安全添加函数
safe_push_back(existing_orders, new_order, max_existing_orders, mtx_existing_orders);
构建高频量化策略的另一个关键,是准确识别交易者的实际开仓数量与背后的资金规模。虽然部分交易所公开了开仓计算公式,但由于其涉及较多隐藏参数与边界条件,理论公式未必与实战一致。因此,我尝试通过实证方法加以验证,并收集了大量真实下单数据,构造如下实验。为方便,这里展示我收集的10几条关于狗狗币的数据——简单写了个Python代码加以验证
输入包含标记价格、最新价格、余额、杠杆、和最大可做多做空量的实验结果。
从实验结果看,该估算公式在部分场景下预测准确,尤其在小额账户或中低杠杆时表现较佳。然而当杠杆较高或余额增大后,预测误差迅速放大。在18组样本中,仅有5组完全命中(误差为0),部分样本误差较大,说明实际成交数量存在非线性边界或隐藏规则,可能与交易所的整数截断、滑点、风险缓释机制等有关。一开始我是想用拟合手段拟合的,使用了基于遗传算法的符号回归等方法,依旧拟合不出来,我想这是交易所的黑箱。
长时间的建模停滞,使我逐渐意识到:我无需准确还原每一个交易者的仓位结构。策略的成功不依赖于全面识别,而是抓住那些可以确定的机会即可。因此我在实用中放弃了对100%拟合的执念,转而允许 ±1 单位的误差窗口,只要最终的反推落入这个容差区间,即视为有效识别。
同理还有关于做多的公式,较为复杂,在文章发布日期是:
int(balance / ((latest_price * 1.001) / leverage + abs(mark_price - (latest_price * 1.001))))
此处不再赘述。总的来说,策略的关键在于:不是拟合一切,而是精准捕捉可以识别的那部分机会。
评论 (0)