Skip to content
This repository has been archived by the owner on Jan 12, 2023. It is now read-only.

Commit

Permalink
Basic SyncOrderBook completed (#1)
Browse files Browse the repository at this point in the history
* Add
* Cancel
* Change
* Match

Methods on Bid/Ask side completed and synced to redis 

* .gitignore update for virtualenv

* OrderBook and SyncOrderBook objects

* Add order to orderbook (redis backed)

* Cancel order to orderbook (redis backed)

* Exceptionally simple and naive matching order

* Change order added to orderbook

* Match order refactored to use change method
  • Loading branch information
adamgilman authored Feb 13, 2017
1 parent 27e38c8 commit 53a4242
Show file tree
Hide file tree
Showing 12 changed files with 297 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ __pycache__/
# C extensions
*.so

bin/*
include/*
pip-selfcheck.json
man/*

# Distribution / packaging
.Python
env/
Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,24 @@
# remob
REMote OrderBook - REMOB is a Python based redis backed order book for financial instruments which allows an order book to be kept remotely on redis and accessed for analysis quickly and easily.
**REM**ote **O**rder**B**ook

**remob** is a Python based, redis backed, order book for financial instruments which allows an order book to be kept remotely on redis and accessed for analysis quickly and easily.

**NOTE** : This is meant to be an order book which syncs to an existing market. It will not automatically match maker/taker orders. It is designed to accept matching messages and maintain order book sync with an active marketplace.

* [Bid/Ask] Add orders
* [Bid/Ask] Delete orders
* [Bid/Ask] Change orders
* Match Bid/Ask


## Basic Objects

### SyncOrderBook

The primary interface between real-time market data and remob.

### OrderBook

The primary interface between remob and all outside analysis tools.

![](docs/diagram-overview.png)
Binary file added docs/diagram-overview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions remob/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from syncorderbook import SyncOrderBook
from orderbook import OrderBook
from order import Order, Match
4 changes: 4 additions & 0 deletions remob/order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from collections import namedtuple

Order = namedtuple("Order", ['order_id', 'time', 'price', 'quantity'])
Match = namedtuple("Match", ['bid_id', 'ask_id', 'price', 'quantity'])
2 changes: 2 additions & 0 deletions remob/orderbook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class OrderBook(object):
pass
90 changes: 90 additions & 0 deletions remob/syncorderbook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from uuid import uuid4 as uuid
from collections import OrderedDict
import ast

from order import Order, Match

class BadOrderTypeException(Exception): pass
class NoOrderFound(Exception): pass
class BadMatchTypeException(Exception): pass

class SyncOrderBookSide(object):
def __init__(self, bookname, side, redis):
self.redis = redis
self.bookname = bookname
self.side = side
self.key = "%(bookname)s:%(side)s" % {'bookname': self.bookname, 'side':self.side}
self.key_store = "%(bookname)s:%(order_id)s"

def add(self, new_order):
if type(new_order) is not Order:
raise BadOrderTypeException("Must pass Order type as new order to OrderBook")
hkey = self.key_store % {'bookname':self.bookname, 'order_id':new_order.order_id}
self.redis.hset(hkey, 'order', dict(new_order._asdict()) )
self.redis.hset(hkey, 'price', new_order.price)
self.redis.hset(hkey, 'quantity', new_order.quantity)
self.redis.hset(hkey, 'time', new_order.time)
self.redis.zadd(self.key, hkey, new_order.price)

def cancel(self, order_id):
self.redis.zrem(self.key, order_id)
hkey = self.key_store % {'bookname':self.bookname, 'order_id':order_id}
self.redis.delete(hkey)

def change(self, order_id, quantity=None, price=None):
hkey = self.key_store % {'bookname':self.bookname, 'order_id':order_id}
order = dict( self.info(order_id)._asdict() )
self.cancel(order_id)
if quantity is not None:
order['quantity'] = quantity
if price is not None:
order['price'] = price
order = Order(**order)
self.add(order)

def info(self, order_id):
hkey = self.key_store % {'bookname':self.bookname, 'order_id':order_id}
r_order = self.redis.hget(hkey, 'order')
if r_order is not None:
order = ast.literal_eval( r_order )
return Order(**order)
else:
raise NoOrderFound("No order with that %s found in %s" % (order_id, self.side))

class SyncOrderBook(object):
def __init__(self, bookname=None, redis=None):
self.redis = redis
if bookname is None:
self.bookname = uuid().hex
else:
'''
if a user passes their ownn bookname
it is THEIR responsibilty to guarantee
uniqueness, use at own peril
'''
pass

self.bid = SyncOrderBookSide(self.bookname, 'bid', self.redis)
self.ask = SyncOrderBookSide(self.bookname, 'ask', self.redis)

def match(self, match_order):
if type(match_order) is not Match:
raise BadMatchTypeException("Must pass Match type as matching order")

bid_order = self.bid.info(match_order.bid_id)
ask_order = self.ask.info(match_order.ask_id)

new_quantity = ask_order.quantity - bid_order.quantity
if new_quantity != 0:
self.ask.change(order_id=ask_order.order_id, quantity=new_quantity)
else:
self.ask.cancel(ask_order.order_id)


self.bid.cancel(bid_order.order_id)






11 changes: 11 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
appdirs==1.4.0
colorama==0.3.7
MacFSEvents==0.7
mockredispy==2.9.3
nose==1.3.7
packaging==16.8
pyparsing==2.1.10
python-termstyle==0.1.10
redis==2.10.5
six==1.10.0
sniffer==0.4.0
Empty file added tests/__init__.py
Empty file.
Empty file added tests/syncorderbook/__init__.py
Empty file.
148 changes: 148 additions & 0 deletions tests/syncorderbook/tests_AddCancelExecute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import unittest
import redis
from unittest import skip
from mock import patch
from mockredis import mock_redis_client

from remob import SyncOrderBook, Order, Match
from remob.syncorderbook import NoOrderFound, BadOrderTypeException, BadMatchTypeException

class TestSyncOrderBook(unittest.TestCase):
@patch('redis.Redis', mock_redis_client)
def setUp(self):
red = redis.Redis()
self.sob = SyncOrderBook(redis=red)

def test_book_name(self):
#generate a random bookname, so redis gets a unique keyspace
self.assertTrue(self.sob.bookname is not None)

def test_add_takes_order_type(self):
self.assertRaises(BadOrderTypeException, self.sob.bid.add, None)
self.assertRaises(BadOrderTypeException, self.sob.ask.add, None)

def test_order_on_book(self):
new_order_d = {
'order_id' : "test_1111",
'time' : 1,
'price' : 22.22,
'quantity' : 10
}
new_order = Order(**new_order_d)

self.sob.bid.add(new_order)
check_order = self.sob.bid.info(new_order.order_id)
self.assertEqual(check_order.order_id, new_order.order_id)
self.assertEqual(check_order.price, new_order.price)

self.assertRaises(NoOrderFound, self.sob.bid.info, "invalid_order_id")


def test_cancel_on_book(self):
new_order_d = {
'order_id' : "test_1111",
'time' : 1,
'price' : 22.22,
'quantity' : 10
}
new_order = Order(**new_order_d)
self.sob.bid.add(new_order)
check_order = self.sob.bid.info(new_order.order_id)
self.assertEqual(check_order.order_id, new_order.order_id)
self.sob.bid.cancel(new_order.order_id)
self.assertRaises(NoOrderFound, self.sob.bid.info, new_order.order_id)

def test_match_takes_match_type(self):
self.assertRaises(BadMatchTypeException, self.sob.match, None)

def test_match_on_book_ask_greater(self):
ask_order = {
'order_id' : 'ask_111',
'time' : 1,
'price' : 10,
'quantity' : 100
}
self.sob.ask.add( Order(**ask_order) )

bid_order = {
'order_id' : 'bid_111',
'time' : 1,
'price' : 10,
'quantity' : 50
}
self.sob.bid.add( Order(**bid_order) )

match_order_d = {
'bid_id' : bid_order['order_id'],
'ask_id' : ask_order['order_id'],
'price' : 10,
'quantity' : 50
}
match_order = Match(**match_order_d)
self.sob.match(match_order)

check_order = self.sob.ask.info(ask_order['order_id'])
self.assertEqual(check_order.quantity, 50)

self.assertRaises(NoOrderFound, self.sob.bid.info, bid_order['order_id'])

def test_match_on_book_bid_full_bid(self):
ask_order = {
'order_id' : 'ask_111',
'time' : 1,
'price' : 10,
'quantity' : 50
}
self.sob.ask.add( Order(**ask_order) )

bid_order = {
'order_id' : 'bid_111',
'time' : 1,
'price' : 10,
'quantity' : 50
}
self.sob.bid.add( Order(**bid_order) )

match_order_d = {
'bid_id' : bid_order['order_id'],
'ask_id' : ask_order['order_id'],
'price' : 10,
'quantity' : 50
}
match_order = Match(**match_order_d)
self.sob.match(match_order)

self.assertRaises(NoOrderFound, self.sob.bid.info, bid_order['order_id'])
self.assertRaises(NoOrderFound, self.sob.ask.info, ask_order['order_id'])


def test_change_order_quantity(self):
ask_order = {
'order_id' : 'ask_111',
'time' : 1,
'price' : 10,
'quantity' : 100
}
self.sob.ask.add( Order(**ask_order) )
self.sob.ask.change(order_id=ask_order['order_id'], quantity=50)
check_order = self.sob.ask.info(ask_order['order_id'])
self.assertEqual(check_order.quantity, 50)

def test_change_order_price(self):
ask_order = {
'order_id' : 'ask_111',
'time' : 1,
'price' : 10,
'quantity' : 100
}
self.sob.ask.add( Order(**ask_order) )
self.sob.ask.change(order_id=ask_order['order_id'], price=50)
check_order = self.sob.ask.info(ask_order['order_id'])
self.assertEqual(check_order.price, 50)







11 changes: 11 additions & 0 deletions tests/tests_basic_ob.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import unittest
from remob import SyncOrderBook, OrderBook

class TestBasicObjects(unittest.TestCase):
def setUp(self):
self.sob = SyncOrderBook()
self.ob = OrderBook()

def test_sync_ob_object(self):
self.assertEqual(type(self.sob), SyncOrderBook)
self.assertEqual(type(self.ob), OrderBook)

0 comments on commit 53a4242

Please sign in to comment.