定单簿(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类建议写自己的内存池,这样可以提高效率;
分享到:
相关推荐
在金融交易领域,量化交易是一种依赖于数学模型和计算机程序来制定交易决策的方法,它将复杂的市场数据转化为可执行的交易指令。 首先,我们要理解什么是CTA(Commodity Trading Advisor)程序化交易。CTA是指商品...
订单簿在IT行业中,特别是在金融领域,是一种至关重要的数据结构,用于记录市场中买卖订单的情况。它是金融市场交易系统的核心组成部分,允许参与者查看当前的买方和卖方报价,并执行交易。在这个场景中,"订单簿:...
订单簿是金融市场中的一种核心机制,它记录了投资者对证券(如股票、期货或外汇)的买卖意愿。在电子交易平台中,订单簿通常由Limit Orders(限价订单)和Market Orders(市价订单)组成。这两种订单类型是理解金融...
本项目“以Python和C++编写的限制订单簿和匹配引擎”结合了两种编程语言的优势,提供了高效且灵活的解决方案。让我们深入探讨这个项目中的关键知识点。 1. **限制订单簿(Limit Order Book)**:订单簿是金融市场中...
C++是一种强大的、通用的编程语言,特别适合实现这种需要高效处理大量数据和实时更新的系统。在C++中,可以使用数据结构如队列或优先队列来实现订单簿的管理。买入订单通常按照价格由高到低排序,而卖出订单则按价格...
在A股市场中,订单簿是核心机制之一,通过它交易撮合得以实现。订单簿记录了股票的买卖指令,是交易活动的实时记录。由于上海证券交易所和深圳证券交易所分别提供了逐笔成交和逐笔委托数据,这些数据的信息量十分...
在数字货币交易领域,GDAX(现已更名为Coinbase Pro)是一个重要的交易平台,而“gdax-orderbook-ml”项目则是将机器学习技术应用到这个平台的订单簿数据上,以实现更智能、更高效的交易策略。本文将深入探讨该领域...
这个策略在外汇交易中是一种常见且重要的技术,旨在寻找市场波动中的有利入场和出场点,以最大化盈利并控制风险。 在外汇交易中,“停止搜寻”策略通常涉及设置止损和止盈订单,这些订单是预先设定的价格点,当市场...
该程序期望一个参数(即文件名)输入CSV文件,并在输出中的任何一种更改为以下格式时输出最佳出价并询问价格和数量: ,,,,, 其中是自交易日开始以来的毫秒数, 是完成更新的代码, 和是最佳出价水平的价格和数量,...
在这个“OrderBook:JS 中的订单簿 POC”项目中,我们看到一个用 JavaScript 实现的订单簿概念验证(Proof of Concept,POC)。这个实现可以帮助开发者理解如何在前端或后端处理实时交易数据。 JavaScript 是一种...
【NodeJS订单簿匹配引擎】是一种基于Node.js技术构建的高效、实时的金融交易系统核心组件,主要用于撮合市场中的买方和卖方订单。在金融交易领域,订单簿是记录所有未成交订单的地方,包括买入订单(bid)和卖出订单...
C++是一种强大的、面向对象的编程语言,以其性能和灵活性著称,特别适合于开发对时间和空间效率要求极高的应用,如交易匹配引擎。C++允许开发者直接操作内存,创建高效的数据结构和算法,这对于处理大规模、高频率的...
5. **跳表**:跳表是一种基于概率的高效查找结构,支持快速的插入、删除和查找,平均时间复杂度为O(log n)。 为了处理订单更新和删除,`SimpleOrderBook`需要考虑以下几点: - **订单匹配**:当新的买入订单价格...
Java Swing是Java提供的一种图形用户界面(GUI)工具包,用于构建桌面应用。它允许开发者创建美观且功能丰富的用户界面,包括窗口、按钮、文本框等组件。在订单管理系统中,Swing被用来设计和实现交互式的订单管理...
3. **Golang语言特点**:Golang是一种静态类型的、编译型的语言,以其简洁的语法、强大的并发模型(goroutines和channels)以及内存安全而著名。这些特性使其特别适合构建高并发、低延迟的系统,如订单匹配引擎。 4...
综上所述,"Excel模板产品订单记录表"是一种用于高效管理产品订单的工具,通过结构化的数据输入、计算、格式化和分析,为企业提供了一种便捷、规范的订单管理方案。使用这样的模板,不仅可以提高工作效率,还可以...
"StocksImport"项目就是这样一个工具,它专门设计用于从XML文件中导入库存数据,并能有效地与订单进行匹配,以支持执行服务在订单簿中的并行计算。本文将深入探讨这一项目的实现细节、技术栈以及相关知识点。 1. **...
3. **订单支付**:微信支付是一种流行的移动支付方式,尤其在中国市场广泛使用。在实现微信支付的过程中,开发者需要与微信支付API进行交互,完成以下步骤: - **预支付请求**:向微信服务器发送请求,获取预支付...
2. **采购订单**:采购订单是商业交易过程中,买方向卖方发出的一种正式文件,用于明确购买的商品或服务的种类、数量、价格、交货日期等信息。在Excel中,一个采购订单模板通常会包括以下列: - **供应商信息**:...
在金融市场中,一个完整的订单簿通常会显示最优买价(Bid)和最优卖价(Ask),以及这些价格下的订单数量。深度拆单算法则进一步关注订单簿的深层次结构,即更远离市场价格的挂单。它会将大额订单拆分成多个小额订单...