Skip to content

Commit

Permalink
Merge branch 'release/2019.1.5'
Browse files Browse the repository at this point in the history
  • Loading branch information
gavincyi committed Aug 7, 2019
2 parents 5563d00 + ced5e08 commit 05fddfe
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 49 deletions.
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ language: python
python:
- 2.7
- 3.4
- 3.5
- 3.6
- 3.7
- pypy

install:
- pip install .[performance]

script:
- make test
- python tests/performance/performance_test.py --freq 20
- python tests/performance/performance_test.py --freq 20 --num-orders 500
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
#!/usr/bin/python3
class Side(object):
"""
Side
"""
cpdef enum Side:
BUY = 1
SELL = 2

class OrderBook(object):
"""
Order book
"""
def __init__(self):
"""
Constructor
"""
self.bids = {}
self.asks = {}
self.order_id_map = {}

cdef class Order:
cdef public int order_id
cdef public str instmt
cdef public double price
cdef public double qty
cdef public double cum_qty
cdef public double leaves_qty
cdef public Side side

class Order(object):
"""
Order
"""
def __init__(self, order_id, instmt, price, qty, side):
"""
Constructor
Expand All @@ -36,10 +26,28 @@ def __init__(self, order_id, instmt, price, qty, side):
self.side = side


class Trade(object):
"""
Trade
"""
cdef class OrderBook:
cdef public dict bids
cdef public dict asks
cdef public dict order_id_map

def __init__(self):
"""
Constructor
"""
self.bids = {}
self.asks = {}
self.order_id_map = {}


cdef class Trade:
cdef public int order_id
cdef public str instmt
cdef public double trade_price
cdef public double trade_qty
cdef public Side trade_side
cdef public int trade_id

def __init__(self, order_id, instmt, trade_price, trade_qty, trade_side, trade_id):
"""
Constructor
Expand All @@ -52,10 +60,11 @@ def __init__(self, order_id, instmt, trade_price, trade_qty, trade_side, trade_i
self.trade_id = trade_id


class LightMatchingEngine(object):
"""
Light matching engine
"""
cdef class LightMatchingEngine:
cdef public dict order_books
cdef public int curr_order_id
cdef public int curr_trade_id

def __init__(self):
"""
Constructor
Expand All @@ -64,7 +73,7 @@ def __init__(self):
self.curr_order_id = 0
self.curr_trade_id = 0

def add_order(self, instmt, price, qty, side):
cpdef add_order(self, str instmt, double price, double qty, Side side):
"""
Add an order
:param instmt Instrument name
Expand All @@ -74,14 +83,17 @@ def add_order(self, instmt, price, qty, side):
:return The order and the list of trades.
Empty list if there is no matching.
"""
cdef list trades = []
cdef int order_id
cdef Order order

assert side == Side.BUY or side == Side.SELL, \
"Invalid side %s" % side

# Locate the order book
order_book = self.order_books.setdefault(instmt, OrderBook())

# Initialization
trades = []
self.curr_order_id += 1
order_id = self.curr_order_id
order = Order(order_id, instmt, price, qty, side)
Expand All @@ -92,7 +104,7 @@ def add_order(self, instmt, price, qty, side):
else None
while best_price is not None and \
(price == 0.0 or price >= best_price ) and \
order.leaves_qty > 0:
order.leaves_qty >= 1e-9:
best_price_qty = sum([ask.leaves_qty for ask in order_book.asks[best_price]])
match_qty = min(best_price_qty, order.leaves_qty)
assert match_qty > 0, "Match quantity must be larger than zero"
Expand All @@ -105,7 +117,7 @@ def add_order(self, instmt, price, qty, side):
Side.BUY, self.curr_trade_id))

# Generate the passive executions
while match_qty > 0:
while match_qty >= 1e-9:
# The order hit
hit_order = order_book.asks[best_price][0]
# The order quantity hit
Expand All @@ -117,7 +129,7 @@ def add_order(self, instmt, price, qty, side):
hit_order.cum_qty += order_match_qty
hit_order.leaves_qty -= order_match_qty
match_qty -= order_match_qty
if hit_order.leaves_qty == 0:
if hit_order.leaves_qty < 1e-9:
del order_book.asks[best_price][0]

# If the price does not have orders, delete the particular price depth
Expand All @@ -129,7 +141,7 @@ def add_order(self, instmt, price, qty, side):
else None

# Add the remaining order into the depth
if order.leaves_qty > 0:
if order.leaves_qty > 0.0:
depth = order_book.bids.setdefault(price, [])
depth.append(order)
order_book.order_id_map[order_id] = order
Expand All @@ -139,10 +151,10 @@ def add_order(self, instmt, price, qty, side):
else None
while best_price is not None and \
(price == 0.0 or price <= best_price) and \
order.leaves_qty > 0:
order.leaves_qty >= 1e-9:
best_price_qty = sum([bid.leaves_qty for bid in order_book.bids[best_price]])
match_qty = min(best_price_qty, order.leaves_qty)
assert match_qty > 0, "Match quantity must be larger than zero"
assert match_qty >= 1e-9, "Match quantity must be larger than zero"

# Generate aggressive order trade first
self.curr_trade_id += 1
Expand All @@ -152,7 +164,7 @@ def add_order(self, instmt, price, qty, side):
Side.SELL, self.curr_trade_id))

# Generate the passive executions
while match_qty > 0:
while match_qty >= 1e-9:
# The order hit
hit_order = order_book.bids[best_price][0]
# The order quantity hit
Expand All @@ -164,7 +176,7 @@ def add_order(self, instmt, price, qty, side):
hit_order.cum_qty += order_match_qty
hit_order.leaves_qty -= order_match_qty
match_qty -= order_match_qty
if hit_order.leaves_qty == 0:
if hit_order.leaves_qty < 1e-9:
del order_book.bids[best_price][0]

# If the price does not have orders, delete the particular price depth
Expand All @@ -176,20 +188,25 @@ def add_order(self, instmt, price, qty, side):
else None

# Add the remaining order into the depth
if order.leaves_qty > 0:
if order.leaves_qty >= 1e-9:
depth = order_book.asks.setdefault(price, [])
depth.append(order)
order_book.order_id_map[order_id] = order

return order, trades

def cancel_order(self, order_id, instmt):
cpdef cancel_order(self, int order_id, str instmt):
"""
Cancel order
:param order_id Order ID
:param instmt Instrument
:return The order if the cancellation is successful
"""
cdef Order order
cdef double order_price
cdef Side side
cdef int index

assert instmt in self.order_books.keys(), \
"Instrument %s is not valid in the order book" % instmt
order_book = self.order_books[instmt]
Expand Down Expand Up @@ -238,6 +255,3 @@ def cancel_order(self, order_id, instmt):
order.leaves_qty = 0

return order



11 changes: 7 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from setuptools import setup, find_packages
from setuptools import setup, find_packages, Extension

setup(
name="lightmatchingengine",
Expand All @@ -14,12 +14,15 @@

use_scm_version=True,
install_requires=[],
setup_requires=['setuptools_scm'],
setup_requires=['setuptools_scm', 'cython'],
ext_modules=[Extension(
'lightmatchingengine.lightmatchingengine',
['lightmatchingengine/lightmatchingengine.pyx'])],
tests_require=[
'pytest'
],
extra_requires={
'performance': ['pandas']
extras_require={
'performance': ['pandas', 'docopt', 'tabulate', 'tqdm']
},

classifiers=[
Expand Down
4 changes: 2 additions & 2 deletions tests/performance/performance_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ def run(args):
order.order_id, order.side, order.price, order.qty)

# Save the order if there is any quantity left
if order.leaves_qty > 0:
if order.leaves_qty > 0.0:
orders[order.order_id] = order

# Remove the trades
for trade in trades:
if (trade.order_id != order.order_id and
orders[trade.order_id].leaves_qty == 0.0):
orders[trade.order_id].leaves_qty < 1e-9):
del orders[trade.order_id]

# Save the statistics
Expand Down

0 comments on commit 05fddfe

Please sign in to comment.