定单簿(Order Book) 的一种实现
定单簿的简单业务背景介绍
最近笔者需要根据业务需要实现一个“快速”的定单簿,首先在此啰嗦一下什么是“定单簿”和有关“定单簿”的一些简单业务场景描述。我们都知道在股票交易的时候,都会在具体一个股票操作界面的右上角处(很多交易专业人员称之为“盘口”)看到多档价位行情(通常是买5档卖5档),这个“盘口”实际上是定单簿的一定程度的外表反应;顾名思义,“定单簿”一定是包含某“交易标的”的所有的定单,并且将这些定单按照一定“优先级”进行排序的。被排序之后的定单簿可以根据成交条件(买价>=卖价)进行买卖方向的定单交易撮合。所谓交易撮合简单说就是对满足成交条件的定单进行成交定价以及成交量的分配。
实际上从计算机的内部实现上可以把“定单簿”看做是一个拥有成交定价策略,成交量分配策略的排序定单集合;那么简要地分析上述“定单簿”的功能,就会是下面这样子了:
1. 定单簿是定单的集合;
2. 集合中的定单可以按照业务指定的“优先级”进行排序;比如,买方向的定单按照价格降序排列,卖方向的定单按照价格升序排列;相同价格的定单,按照定单的时间优先级进行排序,时间越早的定单排在前面。业务往往还有其他的排序规则,比如,止损单在同等价格的基础上,优先级比时间优先级还要高。
3. 如果我们把同一价格上的定单的一些信息汇总起来,就形成了"价位“。在一个价位中,包含了该价位的买卖方向,价格,以及该价位的申买量及申卖量;其中价位的申买或申卖量实际上是所有该价位的定单的申买或者申卖量总和,专业术语叫“市场深度”,可以从一定程度上反应出市场在一个时刻上对某一价格上的认同度。多档价位则称为市场的“厚度”。往往通过市场厚度以及深度,判断市场多空双方力量角逐的情况;
4. 定单簿中的定单成交条件,自然是买价>=卖价;
5. 一旦发生成交,定单簿可以按照预先定义的成交价定价原则,确定成交价;
6. 确定成交价的同时,还要确定最终的成交量;
7. 确定了成交价和成交量以后,要把成交量分配给哪些满足成交条件的定单;(告诉交易者,你的定单此次成交了多少量)
技术上着重分析上述功能有如下几点需要注意:
0、定单有一个序号,可以在集合中唯一标识自己,同时序号也表示了时间优先级;序号越小,时间越早;
1、定单簿的“优先级”可以任意指定;
2、定单簿的成交定价原则也可以任意指定;
3、定单簿的成交量分配规则也可以任意指定;
4、成交时,如果进行快速定位“满足成交条件的”定单
5、汇总的价位信息如何随着定单的增加或者减少而快速变化反应?
根据上述的技术需求
定单簿可以是四个“排序”队列,即买队列,卖队列,定单集合,内部数据结构可以用std::map和std::unordered_map实现;无论是map或者multimap均可以按照指定的key(优先级)进行排序;如这样:
/*! * price_level类中,可以存对应该价格中的定单序号(uint32_t类型); * std::set本身可以按照序号排序; */ struct price_level { /* * methods ... ... */ int32_t price_; uint32_t vol_; std::set<order_priority> sys_nos_; }; struct order_priority { int32_t price_; flag_t stop_loss_; uint32_t sys_no_; friend bool operator < (const order_priority&, const order_priority&){ //.... } }; struct order { uint32_t sys_no_; //... }; std::map<price, price_level> //卖队列; std::map<price, price_level, std::greater<price>> //买队列 std::multimap<order_priority, order*> // 定单排序集合;
上述的代码只是粗略的轮廓,但是细细地考虑上面的数据结构,其时间效率可能还是不够好的。因为map异或multimap底层数据结构是红黑树,当红黑树的结点数很多,删除一个结点的时候,树至少需要多次旋转再平衡,且不细究具体的复杂度,想像下删除一个定单或者频繁查找定单的场景,每每都要经过从树根结点到叶子结点的多次遍历,损耗的代价其实还是很大的。那么是否有这样一种可能性,查找和删除定单的时间复杂度为O(1)呢?
经过思考,如果把定单的大集合按照价位分组到每一个对应价位队列中,那么查找定单的时间复杂度可以被消化在查找价位中。价位中用以存储定单的数据结构用链表,链表本身逻辑上用“FIFO”的方式,就可以保证时间优先级,因此我们在只考虑“价格-时间”优先级的前提下,查找的时间复杂度可以保证的。删除呢?比如有交易者进行“撤单”处理,怎么办?呃呃呃……,貌似不得不遍历链表了,这样时间复杂度就变为O(n)了呢?
好吧,只能用空间换时间了。我们额外再增加一个unordered_map, 用定单序号做key, 存放的value就是定单在链表中的指针地址,那样就可以一旦有撤单的场景出现的时候,先从unordered_map中,查找的定单在链表的指针,然后链表直接删除操作,即修改前继和后继结点的指针,就可以快速完成删除操作;这样时间复杂度可以O(1); 大多数需求基本解决了,但是还差个“优先级”,就是定单保存到对应的队列中时,需要按照指定优先级进行保存,此时如果保证时间复杂度最低呢? …… …… ……
仿照“优先级队列“的思路,我们可以单独创建特殊优先级队列,针对除了价格和时间以外的其他优先级队列,队列中存放对具有此优先级的最后一笔定单的位置(像个优先级索引),这样一旦具有优先级的定单报入定单簿中时,可以按照优先级索引中存放的结点后面插入报入的新定单,当然同时更新索引!不具备优先级的定单(比如非止损单)就直接插入链表的后端即可。这样感觉时间复杂度是很低的了,实际应用场景下,具有优先级的定单占比很少的!
好了,思路既然都想好了,开始动手死磕代码吧。
先动手编写一个链表吧,虽然STL提供了一个std::list,但是担心iterator在结点删除时失效的场景,所以还是自己动手写一个吧。(后来细想过,std::list<T>::iterator删除的时候,其实不会失效)
#pragma once #include <new> #include <type_traits> #include "util/recycling_pool.hpp" namespace nm_util { template< class T, bool Is_Pointer > struct list_node_impl; template< class T> struct list_node_impl< T, false >{ typedef T value_t; typedef T* pointer_t; }; template< class T > struct list_node : list_node_impl<T, std::is_pointer<T>::value> { typedef list_node_impl<T, std::is_pointer<T>::value> base_t; typedef typename base_t::value_t value_t; typedef typename base_t::pointer_t pointer_t; typedef list_node<T> type; typedef type* list_node_ptr_t; list_node() = default; explicit list_node(pointer_t __data):data_(__data), prev_(nullptr), next_(nullptr){} ~list_node() = default; pointer_t data_; list_node_ptr_t prev_; list_node_ptr_t next_; }; template< typename T > struct doubly_linked_list { typedef typename list_node<T>::type list_node_t; typedef typename list_node_t::pointer_t pointer_t; typedef typename list_node_t::list_node_ptr_t list_node_ptr_t; doubly_linked_list():length_(0){ head_ = pool_.allocate(nullptr); tail_ = pool_.allocate(nullptr); head_->prev_ = tail_; head_->next_ = tail_; tail_->next_ = head_; tail_->prev_ = head_; } ~doubly_linked_list() = default; list_node_ptr_t head() { return head_; } list_node_ptr_t tail() { return tail_; } list_node_ptr_t front() const { return empty() ? nullptr : head_->next_; } list_node_ptr_t back() { return empty() ? nullptr : tail_->prev_; } list_node_ptr_t push_back(pointer_t __node_data){ list_node_ptr_t _node = pool_.allocate(__node_data); if( _node == nullptr ) return _node; _node->data_ = __node_data; _node->next_ = tail_; list_node_ptr_t _back_node = back(); if( nullptr == _back_node ){ _node->prev_ = head_; head_->next_ = _node; }else{ _node->prev_ = _back_node; _back_node->next_ = _node; } tail_->prev_ = _node; ++length_; return _node; } list_node_ptr_t push_front(pointer_t __node_data){ list_node_ptr_t _node = pool_.allocate(__node_data); if( _node == nullptr ) throw std::bad_alloc(); _node->data_ = __node_data; _node->prev_ = head_; list_node_ptr_t _front_node = front(); if( nullptr == _front_node ){ _node->next_ = tail_; tail_->prev_ = _node; }else{ _node->next_ = _front_node; _front_node->prev_ = _node; } head_->next_ = _node; ++length_; return _node; } list_node_ptr_t front_insert(list_node_ptr_t __mark_node, pointer_t __node_data){ list_node_ptr_t _node; if( empty() ){ _node = push_front(__node_data); return _node; } _node = pool_.allocate(__node_data); if( _node == nullptr ) throw std::bad_alloc(); _node->data_ = __node_data; list_node_ptr_t prev = __mark_node->prev_; prev->next_ = _node; _node->prev_ = prev; __mark_node->prev_ = _node; _node->next_ = __mark_node; ++length_; return _node; } list_node_ptr_t back_insert(list_node_ptr_t __mark_node, pointer_t __node_data){ list_node_ptr_t _node; if( empty() ){ _node = push_back(__node_data); return _node; } _node = pool_.allocate(__node_data); if( _node == nullptr ) throw std::bad_alloc(); _node->data_ = __node_data; list_node_ptr_t next = __mark_node->next_; next->prev_ = _node; _node->next_ = next; __mark_node->next_ = _node; _node->prev_ = __mark_node; ++length_; return _node; } list_node_ptr_t pop_front(){ if( empty() ) return nullptr; list_node_ptr_t _front_node = front(); _front_node->next_->prev_ = head_; head_->next_ = _front_node->next_; pool_.deallocate(_front_node); --length_; return _front_node; } list_node_ptr_t pop_back(){ if( empty() ) return nullptr; list_node_ptr_t _back_node = back(); _back_node->prev_->next_ = tail_; tail_->prev_ = _back_node->prev_; pool_.deallocate(_back_node); --length_; return _back_node; } void erase(list_node_ptr_t __node){ list_node_ptr_t _prev = __node->prev_; list_node_ptr_t _next = __node->next_; _prev->next_ = _next; _next->prev_ = _prev; pool_.deallocate(__node); --length_; } bool empty() const { return length_ == 0 ? true : false ;} uint32_t size() const { return length_ ; } private: list_node_ptr_t head_; list_node_ptr_t tail_; uint32_t length_; recycling_pool<list_node_t, 512> pool_; }; }// namespace nm_util ends
上述的代码中,编译器判断链表只存T*,同时存放链表结点list_node_ptr_t是一个自己编写的简单的可回收内存池中获取;可回收内存池,在实际应用程序中,可以单独提出以提高时间效率。
下面是我自己写的简单可回收的内存池:
#pragma once #include <list> #include <type_traits> namespace nm_util { /* * For saving 'dynamic memory allocation' time, * Size of "T" should be pre-allocated. */ template< typename T, uint32_t Size> class recycling_pool { public: static constexpr uint32_t capacity = Size; typedef T value_t; typedef T* pointer_t; typedef std::list<pointer_t> list_t; public: template<typename... Args> explicit recycling_pool(Args&&... __args){ for( uint32_t i=0; i<capacity; ++i) pool_.push_back(new value_t(std::forward<Args>(__args)...)); } ~recycling_pool() = default; template<typename... Args> pointer_t allocate(Args&&... __args) { pointer_t ret = nullptr; if( pool_.empty() ){ ret = new (std::nothrow) value_t(std::forward<Args>(__args)...); return ret; } ret = pool_.front(); pool_.pop_front(); return ret; } void deallocate(pointer_t& __location){ pool_.push_front(__location); } uint32_t size(){ return pool_.size(); } private: list_t pool_; }; /* * 此内存池的实现可以再优化;因为其作为辅助实现,所以在此不做过多优化和说明。 */ }好啦,链表写完了,这个双向链表可以用来存储定单了。定单的保存需要按照一定的优先级保存,下面的实现是带”优先级索引“的双向链表,可以满足按照指定优先级进行保存定单的需求编写;我暂称“priority_list";
#pragma once #include <iterator> #include <map> #include "util/doubly_linked_list.hpp" namespace nm_util { /* * The support of std::less<T> semantics must be defined in T. */ template< class T > class priority_list { public: typedef T value_t; typedef doubly_linked_list<value_t> list_t; typedef typename list_t::pointer_t pointer_t; typedef typename list_t::list_node_ptr_t list_node_ptr_t; typedef std::map<value_t, list_node_ptr_t> mark_map_t; typedef typename mark_map_t::iterator iterator_t; public: priority_list() = default; ~priority_list() = default; list_node_ptr_t front() const{ return list_.front(); } list_node_ptr_t back() { return list_.back(); } /* * highest priority is inserted as the minimum element in the map. */ list_node_ptr_t insert(pointer_t __data){ list_node_ptr_t _node = nullptr; if( __data->has_default_priority() ){ _node = list_.push_back(__data); return _node; } auto _iter = marks_.find(*__data); auto _end = std::end(marks_); if( _iter != _end ){ list_node_ptr_t _mark = _iter->second; _node = list_.back_insert(_mark, __data); _iter->second = _node; return _node; } auto _lower_bound = marks_.lower_bound(*__data); if( _lower_bound == _end ){ _node = list_.push_front(__data); marks_.insert(std::make_pair(*__data, _node)); }else{ list_node_ptr_t _mark = _lower_bound->second; _node = list_.back_insert(_mark, __data); marks_.insert(std::make_pair(*__data, _node)); } return _node; } void erase(list_node_ptr_t __node){ /* * _node has default priority, just erasing it from * list should be ok. */ if( __node->data_->has_default_priority() ){ list_.erase(__node); return; } auto _iter = marks_.find(*(__node->data_)); if( _iter == std::end(marks_) ) { /* * if the node to be deleted does not has priority * mark, it should be sth. wrong with mark map. */ return; } /* * the node to be erased is the last element in * the list, the mark of the corresponding priority * is able to be erased as well. */ if( __node->next_ == list_.tail() ){ marks_.erase(_iter); list_.erase(__node); return; } /* finding the node which has the same * priority, the mark should be updated to the next * node. */ auto _iter_next = marks_.find(*(__node->next_->data_)); if( _iter == _iter_next ){ _iter->second = __node->next_; list_.erase(__node); return; } marks_.erase(_iter); list_.erase(__node); } bool empty() const { return list_.empty(); } uint32_t size() const{ return list_.size(); } private: doubly_linked_list<T> list_; mark_map_t marks_; }; }//namespace nm_util ends由于优先级队列的存放类型T,往往优先级指定的字段或者key均来自于T,所以这里语义上仿照set的实现,要求T类型提供操作符< 重定义。这么做有二则好处,一来Key中的字段类型可以不一致,用户可根据定单自定义;二则,保存时不需要另构建一个key类型的对象; 优先级列表中用于存储优先级索引的类型是std::map<value_t, list_node_ptr_t>,list_node_ptr_t指向具有某优先级的最后一笔定单在链表中的指针;一旦具有特殊优先级的定单保存入链表前,在优先级索引中找到比自身优先级高的索引,在其后插入定单即可,当然插入时,需要更新索引;另外,具备优先级的定单一旦从链表中删除时,对应的索引需要根据情况删除或者更新。如果链表中还存在具备相同优先级的其他定单,索引需要更新;如果不存在,索引则可删除; 有了优先级列表,这样可以按照既定需求”根据指定优先级保存定单“后,该实现价位队列了。
#pragma once #include <functional> #include <map> #include <unordered_map> #include "busi/traits/price_traits.hpp" #include "busi/traits/volume_traits.hpp" #include "util/priority_list.hpp" #include "util/tags.hpp" namespace nm_util { /* * the order contained in the order list might be "implied order"; * ref: http://www.cmegroup.com/confluence/display/EPICSANDBOX/Implied+Orders */ template< typename _Order> class price_level { public: typedef _Order value_t; typedef typename nm_jaguar::nm_traits::price_traits<value_t>::price_t price_t; typedef typename nm_jaguar::nm_traits::volume_traits<value_t>::volume_t volume_t; typedef priority_list<value_t> order_priority_list_t; typedef typename order_priority_list_t::pointer_t pointer_t; typedef typename order_priority_list_t::list_node_ptr_t list_node_ptr_t; public: explicit price_level(price_t __price): price_(__price), volume_(0), implied_volume_(0){} ~price_level() = default; const price_t& get_price() { return price_ ; } const volume_t& get_volume() { return volume_; } const volume_t& get_implied_volume() { return implied_volume_;} list_node_ptr_t add(pointer_t __data){ list_node_ptr_t _node = orders_.insert(__data); if( __data->is_implied() ){ implied_volume_ += __data->get_left_volume(); } volume_ += __data->get_left_volume(); return _node; } bool erase(list_node_ptr_t __node){ pointer_t _order = __node->data_; if( _order->is_implied() ){ implied_volume_ -= _order->get_left_volume(); } volume_ -= _order->get_left_volume(); orders_.erase(__node); return true; } bool reduce(list_node_ptr_t __node, volume_t __reduced_vol){ pointer_t _order = __node->data_; if( !_order->decrement_volume(__reduced_vol) ) return false; if( _order->is_implied() ) implied_volume_ -= __reduced_vol; volume_ -= __reduced_vol; return true; } /* * represents the whole price_level is implied or not? */ bool is_implied() const { return volume_ - implied_volume_ == 0 ? true : false; } bool has_implied_orders() const{ return implied_volume_ > 0 ? true : false; } list_node_ptr_t get_first_order() const{ return orders_.front(); } bool is_empty() const { return volume_ == 0 ? true : false; } friend std::ostream& operator<<(std::ostream& __os, const price_level<value_t>& __pl){ __os << "price_level at " << __pl.price_ << ": " << "volume= " << __pl.volume_ << " " << "implied_volume= " << __pl.implied_volume_ <<" " << "is_implied=" ; if( __pl.is_implied() ) __os << "true" << " " ; else __os << "false" << " "; __os << "has_implied_orders="; if( __pl.has_implied_orders() ) __os << "true" << " "; else __os << "false" << " "; __os << "\n" << "orders: " << std::endl; list_node_ptr_t _node = __pl.orders_.front(); uint32_t _size = __pl.orders_.size(); for( uint32_t i=0; i<_size; ++i){ __os << *(_node->data_) << std::endl; _node = _node->next_; } return __os; } private: price_t price_; /* total volume */ volume_t volume_; volume_t implied_volume_; order_priority_list_t orders_; }; /* * implicator event: * * K_BB: Better Best Level. Caused by a new order that * establishes a new best level. * K_WB: Worse Best Level. Caused by pulling an order from * a best level where it was the only order, or by * a trade that eliminates the best level. * K_GVB: Greater Volume in Best Level. Caused by adding an * order to the best level or modifying an order that * was already there, although the sorting rank might * be changed. * K_LVB: Less Volume in Best Level. Caused by pulling or * modifying an order from the best level, or by a * trade does not eliminates the best level. * * K_DEFAULT: error; */ enum class implicator_event { K_BB, K_WB, K_GVB, K_LVB, K_NONE, K_DEFAULT }; /* * by default, price_level_queue is on the long side. * template template parameter makes it possible to specify * another kind of 'sorting map', e.g., splay_tree based map, * to be its underlying data structure. */ template< typename _PL, typename _Side_Tag = nm_tags::side_long, template< typename _K, typename _V, typename _Compare = std::greater<_K>, typename _Alloc = std::allocator< std::pair<const _K, _V> > > class _Queue = std::map > class price_level_queue { public: static constexpr int side_ = _Side_Tag::value; typedef _PL value_t; typedef typename value_t::price_t price_t; typedef typename value_t::volume_t volume_t; typedef typename _PL::pointer_t pointer_t; typedef typename _PL::list_node_ptr_t list_node_ptr_t; typedef _Queue<price_t, value_t> pl_queue_t; public: price_level_queue() = default; ~price_level_queue() = default; implicator_event emplace(pointer_t __data, list_node_ptr_t& __node){ if(__data->get_side() != side_ ) return implicator_event::K_DEFAULT; price_t _price = __data->get_price(); if( !__data->is_implied() ){ if( _price > first_price_ ){ first_price_ = _price; im_event_ = implicator_event::K_BB; } else if ( _price == first_price_ ){ im_event_ = implicator_event::K_GVB; } } else { im_event_ = implicator_event::K_NONE; } auto _iter = pl_.find(_price); if( _iter == pl_.end() ){ value_t pl(_price); __node = pl.add(__data); pl_.insert(std::make_pair(_price, pl)); } else { __node = _iter->second.add(__data); } return im_event_; } implicator_event cancel(list_node_ptr_t __node){ pointer_t _order = __node->data_; if( _order->get_side() != side_ ) return im_event_ = implicator_event::K_DEFAULT; price_t _price = _order->get_price(); auto _iter = pl_.find(_price); if( _iter == std::end(pl_) ) return im_event_ = implicator_event::K_DEFAULT; _iter->second.erase(__node); if( _iter->second.is_empty() ){ pl_.erase(_iter); if( _order->is_implied() ){ return im_event_ = implicator_event::K_NONE; } if( !get_next_first_price(first_price_) ){ first_price_ = 0; } im_event_ = implicator_event::K_WB; } else { if( _price == first_price_ ){ im_event_ = implicator_event::K_LVB; } im_event_ = implicator_event::K_NONE; } return im_event_; } implicator_event add_volume(list_node_ptr_t __node, pointer_t __new_data, list_node_ptr_t& __new_node){ cancel(__node); return emplace(__new_data, __new_node); } implicator_event reduce_volume(list_node_ptr_t __node, volume_t __reduced_vol){ pointer_t _order = __node->data_; if( _order->get_side() != side_ ) return implicator_event::K_DEFAULT; price_t _price = _order->get_price(); if( _price == first_price_ ){ im_event_ = implicator_event::K_LVB; } auto _iter = pl_.find(_price); if( _iter == std::end(pl_) ) return implicator_event::K_DEFAULT; _iter->second.reduce(__node, __reduced_vol); return im_event_; } implicator_event modify(list_node_ptr_t __node, pointer_t __new_data, list_node_ptr_t& __new_location){ /* * implied order can not be modified. */ pointer_t _old_order = __node->data_; price_t _old_price = _old_order->get_price(); price_t _new_price = __new_data->get_price(); if( _new_price == _old_price){ volume_t _new_volume = __new_data->get_left_volume(); volume_t _old_volume = _old_order->get_left_volume(); volume_t _diff_vol = _new_volume - _old_volume; if( _diff_vol > 0 ){ add_volume(__node, __new_data, __new_location ); } else if ( _diff_vol < 0 ){ reduce_volume(__node, 0-_diff_vol); } else im_event_ = implicator_event::K_DEFAULT; } else if( _new_price > _old_price ){ cancel(__node); emplace(__new_data, __new_location); } else { emplace(__new_data, __new_location); cancel(__node); } return im_event_; } const price_t& top_price() { return std::begin(pl_)->first; } friend std::ostream& operator<<(std::ostream& __os, const price_level_queue<value_t, _Side_Tag>& __plq){ for(auto& i : __plq.pl_){ __os << i.first << ": " << i.second << std::endl; } return __os; } private: bool get_next_first_price(price_t& __first_price){ auto _it = pl_.begin(); while(_it != std::end(pl_)){ if ( !_it->second.is_implied() ){ list_node_ptr_t _first_order = _it->second.get_first_order(); __first_price = _first_order->data_->get_price(); return true; } } return false; } /* basic price */ price_t first_price_; implicator_event im_event_; pl_queue_t pl_; }; /* * specialization for short price level queue. */ template< typename _PL > class price_level_queue < _PL, nm_tags::side_short, std::map > { public: static constexpr int side_ = nm_tags::side_short::value; typedef _PL value_t; typedef typename value_t::price_t price_t; typedef typename value_t::volume_t volume_t; typedef typename _PL::pointer_t pointer_t; typedef typename _PL::list_node_ptr_t list_node_ptr_t; typedef std::map<price_t, value_t> pl_queue_t; public: price_level_queue() = default; ~price_level_queue() = default; implicator_event emplace(pointer_t __data, list_node_ptr_t& __node){ if(__data->get_side() != side_ ) return implicator_event::K_DEFAULT; price_t _price = __data->get_price(); if( !__data->is_implied() ){ if( _price < first_price_ ){ first_price_ = _price; im_event_ = implicator_event::K_BB; } else if ( _price == first_price_ ){ im_event_ = implicator_event::K_GVB; } } else { im_event_ = implicator_event::K_NONE; } auto _iter = pl_.find(_price); if( _iter == pl_.end() ){ value_t pl(_price); __node = pl.add(__data); auto _ret = pl_.insert(std::make_pair(_price, pl)); if( !_ret.second ) return implicator_event::K_DEFAULT; } else { __node = _iter->second.add(__data); } return im_event_; } implicator_event cancel(list_node_ptr_t __node){ pointer_t _order = __node->data_; if( _order->get_side() != side_ ) return im_event_ = implicator_event::K_DEFAULT; price_t _price = _order->get_price(); auto _iter = pl_.find(_price); if( _iter == std::end(pl_) ) return im_event_ = implicator_event::K_DEFAULT; _iter->second.erase(__node); if( _iter->second.is_empty() ){ pl_.erase(_iter); if( _order->is_implied() ){ return im_event_ = implicator_event::K_NONE; } if( !get_next_first_price(first_price_) ){ first_price_ = 0; } im_event_ = implicator_event::K_WB; } else { if( _price == first_price_ ){ im_event_ = implicator_event::K_LVB; } im_event_ = implicator_event::K_NONE; } return im_event_; } implicator_event add_volume(list_node_ptr_t __node, pointer_t __new_data, list_node_ptr_t& __new_node){ cancel(__node); return emplace(__new_data, __new_node); } implicator_event reduce_volume(list_node_ptr_t __node, volume_t __reduced_vol){ pointer_t _order = __node->data_; if( _order->get_side() != side_ ) return implicator_event::K_DEFAULT; price_t _price = _order->get_price(); if( _price == first_price_ ){ im_event_ = implicator_event::K_LVB; } auto _iter = pl_.find(_price); if( _iter == std::end(pl_) ) return implicator_event::K_DEFAULT; _iter->second.reduce(__node, __reduced_vol); return im_event_; } implicator_event modify(list_node_ptr_t __node, pointer_t __new_data, list_node_ptr_t& __new_location){ /* * implied order can not be modified. */ pointer_t _old_order = __node->data_; price_t _old_price = _old_order->get_price(); price_t _new_price = __new_data->get_price(); if( _new_price == _old_price){ volume_t _new_volume = __new_data->get_left_volume(); volume_t _old_volume = _old_order->get_left_volume(); volume_t _diff_vol = _new_volume - _old_volume; if( _diff_vol > 0 ){ add_volume(__node, __new_data, __new_location ); } else if ( _diff_vol < 0 ){ reduce_volume(__node, 0-_diff_vol); } else im_event_ = implicator_event::K_DEFAULT; } else if( _new_price < _old_price ){ cancel(__node); emplace(__new_data, __new_location); } else { emplace(__new_data, __new_location); cancel(__node); } return im_event_; } const price_t& top_price() { return std::begin(pl_)->first; } friend std::ostream& operator<<(std::ostream& __os, const price_level_queue<value_t, nm_tags::side_short, std::map >& __plq){ for(auto& i : __plq.pl_){ __os << i.first << ": " << i.second << std::endl; } return __os; } private: bool get_next_first_price(price_t& __first_price){ auto _it = pl_.begin(); while(_it != std::end(pl_)){ if ( !_it->second.is_implied() ){ list_node_ptr_t _first_order = _it->second.get_first_order(); __first_price = _first_order->data_->get_price(); return true; } } return false; } /* basic price */ price_t first_price_; implicator_event im_event_; pl_queue_t pl_; }; }//namespace nm_util ends定单簿的大构件已经基本完成了,下面就真正的开始封装order_book了。
#pragma once #include <unordered_map> #include "busi/traits/sys_no_traits.hpp" #include "busi/error_code.hpp" #include "util/hash_dictionary.hpp" #include "util/price_level_queue.hpp" #include "util/tags.hpp" namespace nm_jaguar { /* * rules: Price/Time Priority by default, with the same price, * orders are sorted based upon time priority. * For efficiency, time sorting is translated into * 'FIFO Queue' sematics, where 'Queue''s data * structure is selected as 'instrusive doubly linked list', * so as to removing (cancelling) an order from the FIFO * queue is guaruanteed to be in constant time. */ namespace nu = nm_util; namespace ntraits = nm_traits; namespace ntags = nm_util::nm_tags; /* * Policy-based order_book * Design Principle: "Modern C++ Design" Andrei Alexanderscu. */ template< typename _Order, //typename _Pricing_Policy, //typename _Asset_Allocation_Policy, > class order_book { public: typedef _Order value_t; typedef typename ntraits::sys_no_traits<value_t>::sys_no_t sys_no_t; typedef nu::price_level<value_t> pl_t; typedef typename pl_t::price_t price_t; typedef typename pl_t::volume_t volume_t; typedef typename pl_t::pointer_t pointer_t; typedef typename pl_t::list_node_ptr_t list_node_ptr_t; typedef nu::price_level_queue<pl_t> buy_pl_queue_t; typedef nu::price_level_queue<pl_t, ntags::side_short, std::map> sell_pl_queue_t; typedef nu::hash_dictionary<sys_no_t, list_node_ptr_t> orders_map_t; public: order_book() = default; ~order_book() = default; public: bool is_tradable(){ if( event_ != nu::implicator_event::K_BB ) return false; return buy_pl_q_.top_price() >= sell_pl_q_.top_price() ? true : false; } nu::implicator_event get_implicator_event() { return event_; } error_code place (pointer_t __order){ list_node_ptr_t _node = nullptr; event_ = nu::implicator_event::K_NONE; if( __order->get_side() == ntags::side_long::value){ event_ = buy_pl_q_.emplace(__order, _node); } else { event_ = sell_pl_q_.emplace(__order, _node); } if( event_ == nu::implicator_event::K_DEFAULT ) return error_code::E_BUS_FAIL; return orders_.insert(__order->get_sys_no(), _node) ? error_code::E_SUC : error_code::E_INSERT_FAIL; } error_code cancel (sys_no_t __sys_no){ list_node_ptr_t _node = nullptr; event_ = nu::implicator_event::K_NONE; if( orders_.find(__sys_no, _node) ){ if( _node->data_->get_side() == ntags::side_long::value ){ event_ = buy_pl_q_.cancel(_node); } else { event_ = sell_pl_q_.cancel(_node); } if( event_ == nu::implicator_event::K_DEFAULT ) return error_code::E_BUS_FAIL; return orders_.erase(__sys_no) ? error_code::E_SUC : error_code::E_ERASE_FAIL; } return error_code::E_BUS_FAIL; } error_code add_volume(sys_no_t __sys_no, pointer_t __updated_order){ list_node_ptr_t _node = nullptr; event_ = nu::implicator_event::K_NONE; list_node_ptr_t _newly_created_node = nullptr; if( orders_.find(__sys_no, _node) ){ if( _node->data_->get_side() == ntags::side_long::value ){ event_ = buy_pl_q_.add_volume(_node, __updated_order, _newly_created_node); } else { event_ = sell_pl_q_.add_volume(_node, __updated_order, _newly_created_node); } if( event_ == nu::implicator_event::K_DEFAULT ) return error_code::E_BUS_FAIL; if( !orders_.erase(__sys_no) ) return error_code::E_ERASE_FAIL; if( orders_.insert(__updated_order->get_sys_no(), _newly_created_node) ) return error_code::E_SUC; return error_code::E_INSERT_FAIL; } return error_code::E_BUS_FAIL; } error_code reduce_volume(sys_no_t __sys_no, volume_t __reduced_vol){ list_node_ptr_t _node = nullptr; event_ = nu::implicator_event::K_NONE; if( orders_.find(__sys_no, _node) ){ if( _node->data_->get_side() == ntags::side_long::value ){ event_ = buy_pl_q_.reduce_volume(_node, __reduced_vol); } else { event_ = sell_pl_q_.reduce_volume(_node, __reduced_vol); } if( event_ == nu::implicator_event::K_DEFAULT ) return error_code::E_BUS_FAIL; return error_code::E_SUC; } return error_code::E_BUS_FAIL; } error_code update_price(sys_no_t __sys_no, pointer_t __updated_order){ list_node_ptr_t _node = nullptr; event_ = nu::implicator_event::K_NONE; list_node_ptr_t _newly_created_node = nullptr; if( orders_.find(__sys_no, _node) ){ if( _node->data_->get_side() == ntags::side_long::value ){ event_ = buy_pl_q_.modify(_node, __updated_order, _newly_created_node); } else { event_ = sell_pl_q_.modify(_node, __updated_order, _newly_created_node); } if( !orders_.erase(__sys_no) ) return error_code::E_ERASE_FAIL; if( orders_.insert(__updated_order->get_sys_no(), _newly_created_node) ) return error_code::E_SUC; return error_code::E_INSERT_FAIL; } return error_code::E_BUS_FAIL; } friend std::ostream& operator<<(std::ostream& __os, const order_book<value_t>& __order_book){ __os << __order_book.orders_ << std::endl; __os << __order_book.buy_pl_q_ << "\n" << "=-=-=-=-=-=-=-=-=-=" << std::endl; __os << __order_book.sell_pl_q_ << "\n" << "=-=-=-=-=-=-=-=-=-=" << std::endl; return __os; } private: buy_pl_queue_t buy_pl_q_; sell_pl_queue_t sell_pl_q_; orders_map_t orders_; nu::implicator_event event_; };//order_book ends }//namespace nm_jaguar ends总算大工告成了。 写个简单的小测试吧。 测试类首先需要设计一个满足constraints的定单类;代码如下
#pragma once struct order { order(){} order(uint32_t __sys_no, int __contract_no, short __side, int32_t __p, uint32_t __v, int __force_flag = 0, int __offset_flag = 0, short implied_ = 0): sys_no_(__sys_no), contract_no_(__contract_no), side_(__side), price_(__p), volume_(__v), force_flag_(__force_flag), offset_flag_(__offset_flag){} ~order() = default; bool has_default_priority(){ return ( force_flag_ || offset_flag_ ) ? false : true; } uint32_t get_sys_no() const { return sys_no_ ; } short get_side() { return side_ ; } int32_t get_price() { return price_; } bool is_implied() { return implied_ == 1 ? true : false ; } uint32_t get_left_volume(){ return volume_; } bool decrement_volume(uint32_t __rv){ if( volume_ > __rv ){ volume_ -= __rv; return true; } return false; } /* sorting order: lowest priority -> highest priority */ friend bool operator< (const order& __lhs, const order& __rhs){ if( __lhs.implied_ > __rhs.implied_ ) return true; else if ( __lhs.implied_ == __rhs.implied_ ){ if( __lhs.force_flag_ < __rhs.force_flag_ ){ return true; } else if( __lhs.force_flag_ == __rhs.force_flag_ ){ if( __lhs.offset_flag_ < __rhs.offset_flag_ ) return true; else if( __lhs.offset_flag_ == __rhs.offset_flag_ ) return false; else return false; } else return false; } else { if( __lhs.force_flag_ < __rhs.force_flag_ ){ return true; } else if( __lhs.force_flag_ == __rhs.force_flag_ ){ if( __lhs.offset_flag_ < __rhs.offset_flag_ ) return true; else if( __lhs.offset_flag_ == __rhs.offset_flag_ ) return false; } } } friend std::ostream& operator<<(std::ostream& __os, const order& __order){ __os<<"sys_no:"<<__order.sys_no_ << " " <<"contract_no:"<<__order.contract_no_<<" " <<"side:"<< __order.side_ <<" " <<"price:"<<__order.price_<<" " <<"volume:"<<__order.volume_<<" " <<"force_flag:" << __order.force_flag_ << " " <<"offset_flag: " << __order.offset_flag_ << " " <<"implied: " << __order.implied_ ; return __os; } uint32_t sys_no_; int contract_no_; short side_; int32_t price_; uint32_t volume_; int force_flag_; int offset_flag_; short implied_; };
有了order类,测试代码test_order_book.cpp如下:
#include <iostream> #include "order.hpp" #include "util/order_book.hpp" int main(int argc, char *argv[]) { order *o1 = new order(1, 1, -1, 15, 2, 1, 0); order *o2 = new order(2, 1, -1, 16, 2, 0, 1); order *o3 = new order(3, 1, -1, 13, 2, 1, 1); order *o4 = new order(4, 1, -1, 12, 3); order *o5 = new order(5, 1, -1, 15, 3); order *o6 = new order(6, 1, 1, 13, 5); order *o7 = new order(7, 1, 1, 14, 1); order *o8 = new order(8, 1, 1, 15, 6); order *o9 = new order(9, 1, 1, 12, 2); order *o10= new order(10,1, 1, 11, 3); typedef nm_jaguar::order_book<order> order_book_t; order_book_t book_; book_.place(o1); book_.place(o2); book_.place(o3); book_.place(o4); book_.place(o5); book_.place(o6); book_.place(o7); book_.place(o8); book_.place(o9); book_.place(o10); book_.cancel(1); book_.cancel(10); order *o11 = new order(11, 1, -1, 16, 5, 0, 1); if( book_.add_volume(2, o11) == nm_jaguar::error_code::E_SUC ) std::cout << "add_order_volume successfully" << std::endl; book_.reduce_volume(2, 1); std::cout << book_ << std::endl; return 0; }
在实际应用中,order类建议写自己的内存池,这样可以提高效率;