交易所撮合交易【一】
- 2019 年 11 月 30 日
- 笔记

前言
最近一直在研究股票(币币)交易所撮合引擎,从一个连撮合概念都不太清晰我逐步摸索渐渐的有了一些自己的理解和概念。所以打算写一个撮合引擎,希望集众人智慧做出一个不错的引擎。
撮合规则
本次主要是实现,限价和市价交易;
撮合规则: (一)买价大于等于卖价,可以成交,先挂单为价格定制者,即为成交价; (二)相同价格,以时间早的先成交。 (三)市价最先成交,以对手价为成交价。
撮合架构与技术定性
架构与技术定性的因素: 1、撮合的本质是一个单一线性过程,没有办法并发的。(如果有请大神指点…) 2、以单一订单为撮合逻辑(吃完一个单,再从订单薄拿出下一个订单吃),评率太高,耗时长。 3、数据库撮合,不考虑,只考虑内存撮合。 4、撮合的时候:只需要拿出最优,不需要排序。 5、撮合结果:不需要关心和谁交易。结合“第二点”让每一个price level是可以在逻辑上独立的。降低频率。
框架与技术选择: 线程框架选择:“伦敦外汇交易所LMAX开源的Disruptor框架”,性能这些就在多做阐述了。 内存框架选择:开始考虑的是redis,最终决定用Hazelcast主要原因是简单,快,集群方便。 数据库的选择:因为项目基本上是数据流开发,根据业内对数据流支持毕竟友好的方面考虑选择mongodb 吃单的时候,以并行流计算的方式获得最优匹配。 撮合完成后用Flink或规则引擎完成k线数据盘口深度数据生成。(还在考虑中)
部分技术的实现
因为之前一些朋友是以订单薄为撮合薄的,我也实现了下,下面是一些重要细节的代码:
//获取匹配的订单薄数据 IMap<Long, Order> outMap = hzInstance.getMap(HzltUtil.getMatchKey(coinTeam, isBuy)); /** * -★ * -使用Java 8 Stream API中的并行流来计算最优 * -能快速的拿到撮合对象,不用排序取值,降低性能消耗 */ Order outOrder = outMap.values().parallelStream().min(HzltUtil::compareOrder).get(); //这种方式最难的,就是整理盘口深度数据了 /** * -★ * -获取行情深度 * * @param coinTeam 交易队 * @param isBuy 是否是买 * @return List<Depth> */ public List<Depth> getMarketDepth(String coinTeam, Boolean isBuy) { List<Depth> depths = new ArrayList<Depth>(); IMap<Long, Order> map = hzInstance.getMap(HzltUtil.getMatchKey(coinTeam, isBuy)); if (map.size() > 0) { /** * -这个流:主要是安价格分组和统计,使用并行流快速归集。 */ List<Depth> list = map.entrySet().parallelStream().map(mo -> mo.getValue()) .collect(Collectors.groupingBy(Order::getPrice)).entrySet().parallelStream() .map(ml -> new Depth(ml.getKey().toString(), ml.getValue().stream().map(o -> o.getUnFinishNumber()).reduce(BigDecimal.ZERO, BigDecimal::add) .toString(), "0", 1, coinTeam, isBuy)) .sorted((d1, d2) -> HzltUtil.compareTo(d1, d2)).collect(Collectors.toList()); /** * -这个流:主要是盘口的累计计算,因涉及排序选择串行流 */ list.stream().reduce(new Depth("0", "0", "0", 1, coinTeam, isBuy), (one, two) -> { one.setTotal((new BigDecimal(one.getTotal()).add(new BigDecimal(two.getNumber()))).toString()); depths.add(new Depth(two.getPrice(), two.getNumber(), one.getTotal(), two.getPlatform(), two.getCoinTeam(), two.getIsBuy())); return one; }); } else { Depth depth = new Depth("0", "0", "0", 1, coinTeam, isBuy); depths.add(depth); } return depths; }
这种方式撮合记录非常多,对订单的修改也很频繁,对数据库的压力很大,而且计算的量级也会很庞大,所以抛弃…
未解决的问题
问题一: 当市价把对手盘吃空,是以最后成交价挂单,还是直接撤销?如果是挂单,是变成限价挂单吗? 问题二:市价买入后,撮合到最后,剩下的钱连最小单位都买不了,怎么处理呢?直接撤销么?有撤销记录吗?
问题三:我看过有的朋友把买卖盘分成2个撮合队列,那么同时来买卖市价,是两个以上次成交价对吃吗,还是各自吃对手盘呢?
(adsbygoogle = window.adsbygoogle || []).push({});