Skip to content

Commit d1aff5d

Browse files
committed
POI pybind11
1 parent 05fddfe commit d1aff5d

File tree

5 files changed

+377
-6
lines changed

5 files changed

+377
-6
lines changed

lightmatchingengine/__init__.py

Whitespace-only changes.

setup.py

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,97 @@
11
from setuptools import setup, find_packages, Extension
2+
from setuptools.command.build_ext import build_ext
3+
import sys
4+
import setuptools
5+
6+
class get_pybind_include(object):
7+
"""Helper class to determine the pybind11 include path
8+
The purpose of this class is to postpone importing pybind11
9+
until it is actually installed, so that the ``get_include()``
10+
method can be invoked. """
11+
12+
def __init__(self, user=False):
13+
self.user = user
14+
15+
def __str__(self):
16+
import pybind11
17+
return pybind11.get_include(self.user)
18+
19+
20+
ext_modules = [
21+
Extension(
22+
'lightmatchingengine',
23+
['src/lightmatchingengine.cpp'],
24+
include_dirs=[
25+
# Path to pybind11 headers
26+
get_pybind_include(),
27+
get_pybind_include(user=True)
28+
],
29+
language='c++'
30+
),
31+
]
32+
33+
34+
# As of Python 3.6, CCompiler has a `has_flag` method.
35+
# cf http://bugs.python.org/issue26689
36+
def has_flag(compiler, flagname):
37+
"""Return a boolean indicating whether a flag name is supported on
38+
the specified compiler.
39+
"""
40+
import tempfile
41+
with tempfile.NamedTemporaryFile('w', suffix='.cpp') as f:
42+
f.write('int main (int argc, char **argv) { return 0; }')
43+
try:
44+
compiler.compile([f.name], extra_postargs=[flagname])
45+
except setuptools.distutils.errors.CompileError:
46+
return False
47+
return True
48+
49+
50+
def cpp_flag(compiler):
51+
"""Return the -std=c++[11/14/17] compiler flag.
52+
The newer version is prefered over c++11 (when it is available).
53+
"""
54+
flags = ['-std=c++17', '-std=c++14', '-std=c++11']
55+
56+
for flag in flags:
57+
if has_flag(compiler, flag): return flag
58+
59+
raise RuntimeError('Unsupported compiler -- at least C++11 support '
60+
'is needed!')
61+
62+
63+
class BuildExt(build_ext):
64+
"""A custom build extension for adding compiler-specific options."""
65+
c_opts = {
66+
'msvc': ['/EHsc'],
67+
'unix': [],
68+
}
69+
l_opts = {
70+
'msvc': [],
71+
'unix': [],
72+
}
73+
74+
if sys.platform == 'darwin':
75+
darwin_opts = ['-stdlib=libc++', '-mmacosx-version-min=10.7']
76+
c_opts['unix'] += darwin_opts
77+
l_opts['unix'] += darwin_opts
78+
79+
def build_extensions(self):
80+
ct = self.compiler.compiler_type
81+
opts = self.c_opts.get(ct, [])
82+
link_opts = self.l_opts.get(ct, [])
83+
if ct == 'unix':
84+
opts.append('-DVERSION_INFO="%s"' % self.distribution.get_version())
85+
opts.append(cpp_flag(self.compiler))
86+
if has_flag(self.compiler, '-fvisibility=hidden'):
87+
opts.append('-fvisibility=hidden')
88+
elif ct == 'msvc':
89+
opts.append('/DVERSION_INFO=\\"%s\\"' % self.distribution.get_version())
90+
for ext in self.extensions:
91+
ext.extra_compile_args = opts
92+
ext.extra_link_args = link_opts
93+
build_ext.build_extensions(self)
94+
295

396
setup(
497
name="lightmatchingengine",
@@ -12,12 +105,13 @@
12105

13106
packages=find_packages(exclude=('tests',)),
14107

15-
use_scm_version=True,
16-
install_requires=[],
17-
setup_requires=['setuptools_scm', 'cython'],
18-
ext_modules=[Extension(
19-
'lightmatchingengine.lightmatchingengine',
20-
['lightmatchingengine/lightmatchingengine.pyx'])],
108+
# use_scm_version=True,
109+
version='0.1.0',
110+
install_requires=['pybind11'],
111+
# setup_requires=['setuptools_scm', 'cython'],
112+
setup_requires=['setuptools_scm', 'pybind11'],
113+
ext_modules=ext_modules,
114+
cmdclass={'build_ext': BuildExt},
21115
tests_require=[
22116
'pytest'
23117
],

src/lightmatchingengine.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include <pybind11/pybind11.h>
2+
#include "lightmatchingengine.h"
3+
4+
namespace py = pybind11;
5+
6+
PYBIND11_MODULE(lightmatchingengine, m) {
7+
// Enum Side
8+
py::enum_<Side>(m, "Side")
9+
.value("BUY", Side::BUY)
10+
.value("SELL", Side::SELL)
11+
.export_values();
12+
13+
// Struct Order
14+
py::class_<Order>(m, "Order")
15+
.def(py::init<
16+
int,
17+
string&,
18+
double,
19+
double,
20+
Side>());
21+
22+
// Struct Trade
23+
py::class_<Trade>(m, "Trade")
24+
.def(py::init<
25+
int,
26+
const string&,
27+
double,
28+
double,
29+
Side,
30+
int>());
31+
}

src/lightmatchingengine.h

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
#include <cstdlib>
2+
#include <string>
3+
#include <limits>
4+
#include <tuple>
5+
#include <map>
6+
#include <unordered_map>
7+
#include <deque>
8+
#include <deque>
9+
#include <algorithm>
10+
11+
using namespace std;
12+
13+
#define MIN_NPRICE LLONG_MIN
14+
#define MAX_NPRICE LLONG_MAX
15+
#define MIN_QUANTITY 1e-9
16+
#define MIN_TICK_SIZE 1e-9
17+
#define EPILSON 5e-10
18+
#define NORMALIZE_PRICE( x ) static_cast<int>( x / MIN_TICK_SIZE + EPILSON )
19+
#define DENORMALIZE_PRICE( x ) ( x * MIN_TICK_SIZE )
20+
#define nprice_t long long
21+
#define price_t double
22+
#define qty_t double
23+
#define id_t long long
24+
25+
enum Side {
26+
BUY = 1,
27+
SELL = 2
28+
};
29+
30+
struct Order {
31+
id_t order_id;
32+
const string& instmt;
33+
price_t price;
34+
qty_t qty;
35+
qty_t cum_qty;
36+
qty_t leaves_qty;
37+
Side side;
38+
39+
Order(id_t order_id, const string& instmt, price_t price, qty_t qty, Side side):
40+
order_id(order_id),
41+
instmt(instmt),
42+
price(price),
43+
qty(qty),
44+
cum_qty(0),
45+
leaves_qty(qty),
46+
side(side) {}
47+
};
48+
49+
struct Trade {
50+
id_t order_id;
51+
const string& instmt;
52+
price_t trade_price;
53+
qty_t trade_qty;
54+
Side trade_side;
55+
id_t trade_id;
56+
57+
Trade(id_t order_id, const string& instmt, price_t trade_price, qty_t trade_qty,
58+
Side trade_side, id_t trade_id):
59+
order_id(order_id),
60+
instmt(instmt),
61+
trade_price(trade_price),
62+
trade_qty(trade_qty),
63+
trade_side(trade_side),
64+
trade_id(trade_id) {}
65+
};
66+
67+
struct OrderBook {
68+
map<nprice_t, deque<Order>> bids;
69+
map<nprice_t, deque<Order>> asks;
70+
unordered_map<id_t, Order> order_id_map;
71+
};
72+
73+
class LightMatchingEngine {
74+
public:
75+
unordered_map<string, OrderBook>& order_books() {
76+
return __order_books;
77+
}
78+
79+
int curr_order_id() {
80+
return __curr_order_id;
81+
}
82+
83+
int curr_trade_id() {
84+
return __curr_trade_id;
85+
}
86+
87+
tuple<Order, vector<Trade>> add_order(
88+
const string& instmt, price_t price, qty_t qty, Side side)
89+
{
90+
vector<Trade> trades;
91+
id_t order_id = (__curr_order_id += 1);
92+
Order order = Order(order_id, instmt, price, qty, side);
93+
nprice_t nprice = NORMALIZE_PRICE(price);
94+
95+
// Find the order book
96+
auto order_book_it = __order_books.find(instmt);
97+
if (order_book_it == __order_books.end()) {
98+
order_book_it = __order_books.emplace(instmt, OrderBook()).first;
99+
}
100+
101+
auto order_book = order_book_it->second;
102+
103+
if (side == Side::BUY) {
104+
nprice_t best_nprice = MAX_NPRICE;
105+
if (order_book.asks.size() > 0) {
106+
best_nprice = order_book.asks.begin()->first;
107+
}
108+
109+
while (nprice >= best_nprice && order.leaves_qty > MIN_QUANTITY) {
110+
auto nbbo = order_book.asks.begin()->second;
111+
auto original_leaves_qty = order.leaves_qty;
112+
113+
// Matching the ask queue
114+
while (nbbo.size() > 0) {
115+
auto front_nbbo = nbbo.front();
116+
qty_t matching_qty = min(order.leaves_qty, nbbo[0].leaves_qty);
117+
order.leaves_qty -= matching_qty;
118+
front_nbbo.leaves_qty -= matching_qty;
119+
120+
// Trades on the passive order
121+
trades.push_back(Trade(
122+
front_nbbo.order_id,
123+
instmt,
124+
best_nprice,
125+
matching_qty,
126+
front_nbbo.side,
127+
++__curr_trade_id));
128+
129+
// Remove the order if it is fully executed
130+
if (front_nbbo.leaves_qty < MIN_QUANTITY) {
131+
order_book.order_id_map.erase(front_nbbo.order_id);
132+
nbbo.pop_front();
133+
}
134+
}
135+
136+
// Trades from the original order
137+
trades.push_back(Trade(
138+
order.order_id,
139+
instmt,
140+
best_nprice,
141+
original_leaves_qty - order.leaves_qty,
142+
order.side,
143+
++__curr_trade_id));
144+
145+
// Remove the ask queue if the size = 0
146+
if (nbbo.size() == 0) {
147+
order_book.asks.erase(order_book.asks.begin());
148+
}
149+
150+
// Update the ask best prices
151+
if (order_book.asks.size() > 0) {
152+
best_nprice = order_book.asks.begin()->first;
153+
} else {
154+
best_nprice = MAX_NPRICE;
155+
}
156+
}
157+
158+
// After matching the order, place the leaving order to the end
159+
// of the order book queue, and create the order id mapping
160+
if (order.leaves_qty > MIN_QUANTITY) {
161+
auto nbbo_it = order_book.bids.find(nprice);
162+
if (nbbo_it == order_book.bids.end()){
163+
nbbo_it = order_book.bids.emplace(nprice, deque<Order>()).first;
164+
}
165+
166+
auto nbbo = nbbo_it->second;
167+
nbbo.emplace_back(order);
168+
order_book.order_id_map.emplace(order.order_id, order);
169+
}
170+
} else {
171+
nprice_t best_nprice = MIN_NPRICE;
172+
if (order_book.bids.size() > 0) {
173+
best_nprice = order_book.bids.begin()->first;
174+
}
175+
176+
while (nprice <= best_nprice && order.leaves_qty > MIN_QUANTITY) {
177+
auto nbbo = order_book.bids.begin()->second;
178+
auto original_leaves_qty = order.leaves_qty;
179+
180+
// Matching the ask queue
181+
while (nbbo.size() > 0) {
182+
auto front_nbbo = nbbo.front();
183+
qty_t matching_qty = min(order.leaves_qty, nbbo[0].leaves_qty);
184+
order.leaves_qty -= matching_qty;
185+
front_nbbo.leaves_qty -= matching_qty;
186+
187+
// Trades on the passive order
188+
trades.push_back(Trade(
189+
front_nbbo.order_id,
190+
instmt,
191+
best_nprice,
192+
matching_qty,
193+
front_nbbo.side,
194+
++__curr_trade_id));
195+
196+
// Remove the order if it is fully executed
197+
if (front_nbbo.leaves_qty < MIN_QUANTITY) {
198+
order_book.order_id_map.erase(front_nbbo.order_id);
199+
nbbo.pop_front();
200+
}
201+
}
202+
203+
// Trades from the original order
204+
trades.push_back(Trade(
205+
order.order_id,
206+
instmt,
207+
best_nprice,
208+
original_leaves_qty - order.leaves_qty,
209+
order.side,
210+
++__curr_trade_id));
211+
212+
// Remove the bid queue if the size = 0
213+
if (nbbo.size() == 0) {
214+
order_book.bids.erase(order_book.bids.begin());
215+
}
216+
217+
// Update the bid best prices
218+
if (order_book.bids.size() > 0) {
219+
best_nprice = order_book.bids.begin()->first;
220+
} else {
221+
best_nprice = MIN_NPRICE;
222+
}
223+
}
224+
225+
// After matching the order, place the leaving order to the end
226+
// of the order book queue, and create the order id mapping
227+
if (order.leaves_qty > MIN_QUANTITY) {
228+
auto nbbo_it = order_book.asks.find(nprice);
229+
if (nbbo_it == order_book.asks.end()){
230+
nbbo_it = order_book.asks.emplace(nprice, deque<Order>()).first;
231+
}
232+
233+
auto nbbo = nbbo_it->second;
234+
nbbo.emplace_back(order);
235+
order_book.order_id_map.emplace(order.order_id, order);
236+
}
237+
}
238+
239+
return make_tuple(order, trades);
240+
}
241+
242+
private:
243+
unordered_map<string, OrderBook> __order_books;
244+
int __curr_order_id;
245+
int __curr_trade_id;
246+
};
File renamed without changes.

0 commit comments

Comments
 (0)