This repository has been archived by the owner on Jan 12, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
27e38c8
commit 53a4242
Showing
12 changed files
with
297 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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']) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
class OrderBook(object): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |