Exemplo n.º 1
0
def build_minimal_height_bst_bintrees(nums):
    # here SelfBalancingBST would be your implementaion of BST with self balancing property.
    # tree = SelfBalancingBST()
    tree = RBTree()
    for i in nums:
        tree.insert(i, i)
    return tree
Exemplo n.º 2
0
def calc(arr, max_size):
    import  heapq;

    print max_size
    nxt = arr[:];
    pos = {};

    for i in range(len(arr)):
        pos[arr[i]] = len(arr);

    for i in reversed(range(len(arr))):
        nxt[i] = pos[arr[i]];
        pos[arr[i]] = i;

    print nxt;
    prio_queue = RBTree();
    in_queue = {};
    cache_miss = 0;
    cur_in_queue_cnt = 0;
    print arr;
    
    for i in range(len(arr)):
        out_str = str(i)+" ";
        print prio_queue;
        #cache missed
        if in_queue.has_key(arr[i]) == False:
            #while( space need):
            out_str += "Missed: ";
            cache_miss +=1;
            #add into cache
            
            if (cur_in_queue_cnt+1>max_size):
        
                del in_queue[prio_queue.min_item()[1]];
                
                cur_in_queue_cnt-=1;
                out_str+= str(prio_queue.min_item()[1])+" is kicked. And ";
                prio_queue.discard(prio_queue.min_key());
              
                
                
            out_str+= str(arr[i])+" is added into Q.";
            prio_queue.insert((-nxt[i],i),arr[i]);

            cur_in_queue_cnt+=1;
            in_queue[arr[i]] = 1;
        else :
            prio_queue.discard(prio_queue.ceiling_key((-i,0)));
            prio_queue.insert((-nxt[i],i),arr[i]);
            
            out_str += str(arr[i])+" hit.";

        print out_str;
        print cur_in_queue_cnt;
        #print prio_queue.q;

    print "Cache hit: "+str(len(arr)-cache_miss);
    print "Cache miss: "+str(cache_miss);
    print "Ratio: "+ str((len(arr)-cache_miss)*1.0/len(arr));
def median_maintenance_rbtree(arr):
    median_sum = arr[0]
    rbtree = RBTree([(arr[0], 1)])  # (number, subtree_size)

    for i in range(1, len(arr)):
        ele = arr[i]
        rbtree.insert(ele, 1)
        post_order_traverse(rbtree._root)
        # assert rbtree._root.value == (i+1)
        median_order = int(i / 2) + 1
        median_sum += select_order_statisic(rbtree._root, median_order)

    return median_sum
    def __sweep_graph(self, graph):
        tree = RBTree()
        last_y = -1

        for entry in graph.entries:
            if entry.vertex.point.y != last_y:
                self.stripes.append(Stripe(last_y, [edge for edge in tree]))
                last_y = entry.vertex.point.y

            for edge in entry.outbound_edges:
                tree.insert(edge, edge)
            for edge in entry.inbound_edges:
                tree.remove(edge)

        self.stripes.append(Stripe(last_y, []))
Exemplo n.º 5
0
class BrownianVariableHistory(object):
    ''' Represents the set of known time value pairs for a particular Brownian variable '''

    def __init__(self):
        self._historyTree = RBTree()

    def insertData(self, t, val):
        '''
        Inserts a data point into the history object

        t (float) : time
        val (float) : value
        '''
        self._historyTree.insert(t, val)

    def getMartingaleRelevantPoints(self, t):
        '''
        Returns 2 data points. The first will be the data point with the
        largest 't' in the history that is still smaller than the given user
        provided argument 't'. The second will be the datapoint with the
        smallest 't' that is still larger than the user provided 't'. If one
        or both of the data points do not exist, this function will return
        None in that data point's place.

        t (float) : time
        returns ((t1,val1), (t2,val2)) where t1, t2, val1, val2 are floats : 2 data points

        Ex: bh.getMartingaleRelevantPoints(3.1) == ((3.0, 0.07), (3.5, 0.21))
            bh.getMartingaleRelevantPoints(3.6) == ((3.5, 0.21), None)
        '''
        if self._historyTree.is_empty():
            return None, None

        leftPoint = None
        rightPoint = None
        if self._historyTree.min_key() <= t:
            leftPoint = self._historyTree.floor_item(t)
        if self._historyTree.max_key() >= t:
            rightPoint = self._historyTree.ceiling_item(t)
        return leftPoint, rightPoint
Exemplo n.º 6
0
def max_sum_mod(array, size, m):

    sum = [0] * size
    # Első elem létrehozása, ez után ciklus kell
    sum[0] = array[0] % m
    RB = RBTree()
    RB.insert(sum[0], sum[0])  #key, value páros
    result = sum[0]
    for i in range(1, size):
        #flag azért kell, mert implementáció szerint ha egy elemnek nincs rákövetkezője,
        #akkor kivételt dobna

        flag = True
        sum[i] = sum[i - 1] + array[i]
        sum[i] %= m

        try:
            (k, v) = RB.ceiling_item(sum[i])  #következő elem
        except KeyError as e:
            #ha nincs rákövetkező akkor nem tudjuk a különbségét venni
            print(e)
            result = max(sum[i], result)
            flag = False
        if flag:
            #pythonban a % művelet negatív számokra is úgy működik, ahogy azt elvárjuk
            #C-ben például a sum[i] -k -hoz hozzá kéne adni az m-et.
            result = max((sum[i] - k) % m, result)
        #ez egy potenciális gyorsítás, ha elértük a maximális értéket (mod m), ami m-1
        #akkor nincs értelme tovább keresni, kiléphetünk a ciklusból
        if result == (m - 1):

            break
        RB.insert(sum[i], sum[i])
    #RB.foreach(print, 0)

    return result
def calculate_medians_with_rbt(numbers):
    """Calculate the sum of all 10,000 medians, modulo 10000"""
    smaller = RBTree()  # For storing the smaller half of numbers
    larger = RBTree()  # For storing the larger half of numbers
    medians = []
    for number in numbers:
        if not len(smaller) or smaller.max_item()[0] > number:
            smaller.insert(number, None)
            if len(smaller) > len(larger) + 1:
                larger.insert(smaller.max_item()[0], None)
        else:
            larger.insert(number, None)
            if len(larger) > len(smaller) + 1:
                smaller.insert(larger.min_item()[0], None)
        if len(smaller) >= len(larger):
            medians.append(smaller.max_item()[0])
        else:
            medians.append(larger.min_item()[0])
    return medians
Exemplo n.º 8
0
 def __init__(self, rules):
     t = RBTree()
     ## to avoid exceptions during LE lookup
     t.insert(0, [])
     nets = []
     for rule in rules:
         net = IPNetwork(rule.net)
         t.insert(net.first, [])
         t.insert(net.last+1, [])
     for prio, rule in enumerate(rules):
         net = IPNetwork(rule.net)
         for k,v in t.iter_items(net.first, net.last+1):
             v.append((prio, rule))
     self.t = t
            print(seen)
 
PrintUnique1(input) #1,5


#Code: Solution 2 (using using design #1 and red black tree)
'''
Why red and black trees are better than hashtables?
Source: http://www.quora.com/Why-would-anyone-like-to-use-a-red-black-tree-when-a-hash-table-can-do-the-job-perfectly 
1. Simple memory management - simplifies error handling in concurrent code, less I/O hits, 
2. Consistent performance because rehashing (expanding the hash table's array) happens on some insertions.
   This is important in real-time systems where you want to provide bounds on how long each operation takes
3. Keys are sorted
'''
#https://bitbucket.org/mozman/bintrees
from bintrees import RBTree
def PrintUnique2(k,v):
    if (v == 1): #unique
        print(k) #print unique
#Initialize
tree = RBTree()        
#Insert
for x in input:
    if (tree.get(x)): #duplicate
         tree.insert(x,tree.get(x)+1) #increment occurrence
    else: #new
        tree.insert(x,1)
#Iterate        
tree.foreach(PrintUnique2,0)  #0=inorder

Exemplo n.º 10
0
class BitMEXBook:
    def __init__(self,
                 endpoint="https://www.bitmex.com/api/v1",
                 symbol='XBTUSD'):
        '''Connect to the websocket and initialize data stores.'''
        self.logger = logging.getLogger(__name__)
        self.logger.debug("Initializing WebSocket.")

        self.endpoint = endpoint
        self.symbol = symbol

        self.data = {}
        self.keys = {}
        self.exited = False
        self._asks = RBTree()
        self._bids = RBTree()

        # We can subscribe right in the connection querystring, so let's build that.
        # Subscribe to all pertinent endpoints
        wsURL = self.__get_url()
        self.logger.debug("Connecting to %s" % wsURL)
        self.__connect(wsURL, symbol)
        self.logger.info('Connected to WS, waiting for partials.')

        # Connected. Wait for partials
        self.__wait_for_symbol(symbol)
        self.logger.info('Got all market data. Starting.')

    def init(self):
        self.logger.debug("Initializing WebSocket...")

        self.data = {}
        self.keys = {}
        self.exited = False

        wsURL = self.__get_url()
        self.logger.debug("Connecting to URL -- %s" % wsURL)
        self.__connect(wsURL, self.symbol)
        self.logger.info('Connected to WS, waiting for partials.')

        # Connected. Wait for partials
        self.__wait_for_symbol(self.symbol)
        self.logger.info('Got all market data. Starting.')

    def error(self, err):
        self._error = err
        self.logger.error(err)
        #self.exit()

    def __del__(self):
        self.exit()

    def reset(self):
        self.logger.warning('Websocket resetting...')
        self.ws.close()
        self.logger.info('Weboscket closed.')
        self.logger.info('Restarting...')
        self.init()

    def exit(self):
        '''Call this to exit - will close websocket.'''
        self.exited = True
        self.ws.close()

    ### Main orderbook function
    def get_current_book(self):
        result = {'asks': [], 'bids': []}
        for ask in self._asks:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_ask = self._asks[ask]
            except KeyError:
                continue
            for order in this_ask:
                result['asks'].append(
                    [order['price'], order['size'],
                     order['id']])  #(order['size']/Decimal(order['price']))
        # Same procedure for bids
        for bid in self._bids:
            try:
                this_bid = self._bids[bid]
            except KeyError:
                continue

            for order in this_bid:
                result['bids'].append(
                    [order['price'], order['size'],
                     order['id']])  #(order['size']/Decimal(order['price']))
        return result

    # -----------------------------------------------------------------------------------------
    # ----------------------RBTrees Handling---------------------------------------------------
    # -----------------------------------------------------------------------------------------
    # -----------------------------------------------------------------------------------------

    # Get current minimum ask price from tree
    def get_ask(self):
        return self._asks.min_key()

    # Get ask given id
    def get_asks(self, id):
        return self._asks.get(id)

    # Remove ask form tree
    def remove_asks(self, id):
        self._asks.remove(id)

    # Insert ask into tree
    def set_asks(self, id, asks):
        self._asks.insert(id, asks)

    # Get current maximum bid price from tree
    def get_bid(self):
        return self._bids.max_key()

    # Get bid given id
    def get_bids(self, id):
        return self._bids.get(id)

    # Remove bid form tree
    def remove_bids(self, id):
        self._bids.remove(id)

    # Insert bid into tree
    def set_bids(self, id, bids):
        self._bids.insert(id, bids)

    # Add order to out watched orders
    def add(self, order):
        order = {
            'id': order['id'],  # Order id data
            'side': order['side'],  # Order side data
            'size': Decimal(order['size']),  # Order size data
            'price': order['price']  # Order price data
        }
        if order['side'] == 'Buy':
            bids = self.get_bids(order['id'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['id'], bids)
        else:
            asks = self.get_asks(order['id'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['id'], asks)

    # Order is done, remove it from watched orders
    def remove(self, order):
        oid = order['id']
        if order['side'] == 'Buy':
            bids = self.get_bids(oid)
            if bids is not None:
                bids = [o for o in bids if o['id'] != order['id']]
                if len(bids) > 0:
                    self.set_bids(oid, bids)
                else:
                    self.remove_bids(oid)
        else:
            asks = self.get_asks(oid)
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['id']]
                if len(asks) > 0:
                    self.set_asks(oid, asks)
                else:
                    self.remove_asks(oid)

    # Updating order price and size
    def change(self, order):
        new_size = Decimal(order['size'])
        # Bitmex updates don't come with price, so we use the id to match it instead
        oid = order['id']

        if order['side'] == 'Buy':
            bids = self.get_bids(oid)
            if bids is None or not any(o['id'] == order['id'] for o in bids):
                return
            index = list(map(itemgetter('id'), bids)).index(order['id'])
            bids[index]['size'] = new_size
            self.set_bids(oid, bids)
        else:
            asks = self.get_asks(oid)
            if asks is None or not any(o['id'] == order['id'] for o in asks):
                return
            index = list(map(itemgetter('id'), asks)).index(order['id'])
            asks[index]['size'] = new_size
            self.set_asks(oid, asks)

        tree = self._asks if order['side'] == 'Sell' else self._bids
        node = tree.get(oid)

        if node is None or not any(o['id'] == order['id'] for o in node):
            return

    # -----------------------------------------------------------------------------------------
    # ----------------------WS Private Methods-------------------------------------------------
    # -----------------------------------------------------------------------------------------
    # -----------------------------------------------------------------------------------------

    def __connect(self, wsURL, symbol):
        '''Connect to the websocket in a thread.'''
        self.logger.debug("Starting thread")

        self.ws = websocket.WebSocketApp(wsURL,
                                         on_message=self.__on_message,
                                         on_close=self.__on_close,
                                         on_open=self.__on_open,
                                         on_error=self.__on_error)

        self.wst = threading.Thread(target=lambda: self.ws.run_forever())
        self.wst.daemon = True
        self.wst.start()
        self.logger.debug("Started thread")

        # Wait for connect before continuing
        conn_timeout = CONN_TIMEOUT
        while (not self.ws.sock
               or not self.ws.sock.connected) and conn_timeout:
            sleep(1)
            conn_timeout -= 1
        if not conn_timeout:
            self.logger.error("Couldn't connect to WS! Exiting.")
            # self.exit()
            # raise websocket.WebSocketTimeoutException('Couldn\'t connect to WS! Exiting.')
            self.reset()

    def __get_url(self):
        '''
        Generate a connection URL. We can define subscriptions right in the querystring.
        Most subscription topics are scoped by the symbol we're listening to.
        '''
        symbolSubs = ["orderBookL2"]
        subscriptions = [sub + ':' + self.symbol for sub in symbolSubs]

        urlParts = list(urllib.parse.urlparse(self.endpoint))
        urlParts[0] = urlParts[0].replace('http', 'ws')
        urlParts[2] = "/realtime?subscribe={}".format(','.join(subscriptions))
        return urllib.parse.urlunparse(urlParts)

    def __wait_for_symbol(self, symbol):
        '''On subscribe, this data will come down. Wait for it.'''
        pbar = tqdm(total=160)

        # Wait until data reaches our RBTrees
        while self._asks.is_empty() and self._bids.is_empty():
            sleep(0.1)
            pbar.update(3)
        pbar.close()

    def __send_command(self, command, args=None):
        '''Send a raw command.'''
        if args is None:
            args = []
        self.ws.send(json.dumps({"op": command, "args": args}))

    def __on_message(self, message):
        '''Handler for parsing WS messages.'''
        message = json.loads(message)
        # self.logger.debug(json.dumps(message))

        table = message['table'] if 'table' in message else None
        action = message['action'] if 'action' in message else None
        # table = message.get("table")
        # action = message.get("action")

        try:
            # RBTrees for orderBook
            if table == 'orderBookL2':
                # For every order received
                try:
                    for order in message['data']:
                        if action == 'partial':
                            self.logger.debug('%s: adding partial %s' %
                                              (table, order))
                            self.add(order)
                        elif action == 'insert':
                            self.logger.debug('%s: inserting %s' %
                                              (table, order))
                            self.add(order)
                        elif action == 'update':
                            self.logger.debug('%s: updating %s' %
                                              (table, order))
                            self.change(order)
                        elif action == 'delete':
                            self.logger.debug('%s: deleting %s' %
                                              (table, order))
                            self.remove(order)
                        else:
                            raise Exception("Unknown action: %s" % action)
                except:
                    self.logger.error('Error handling RBTrees: %s' %
                                      traceback.format_exc())

            # Uncomment this to watch RBTrees evolution in real time
            # self.logger.info('==============================================================')
            # self.logger.info('=============================ASKS=============================')
            # self.logger.info('==============================================================')
            # self.logger.info(self._asks)
            # self.logger.info('==============================================================')
            # self.logger.info('=============================BIDS=============================')
            # self.logger.info('==============================================================')
            # self.logger.info(self._bids)

        except:
            self.logger.error(traceback.format_exc())

    def __on_error(self, error):
        '''Called on fatal websocket errors. We exit on these.'''
        if not self.exited:
            self.logger.error("Error : %s" % error)
            raise websocket.WebSocketException(error)

    def __on_open(self):
        '''Called when the WS opens.'''
        self.logger.debug("Websocket Opened.")

    def __on_close(self):
        '''Called on websocket close.'''
        self.logger.info('Websocket Closed')
Exemplo n.º 11
0
class GdaxBookFeed(GdaxWebsocketClient):
    def __init__(self,
                 product_id='LTC-USD',
                 log_to=None,
                 gdax=None,
                 auth=True):

        if gdax is None:
            from stocklook.crypto.gdax.api import Gdax
            gdax = Gdax()

        super(GdaxBookFeed, self).__init__(products=product_id,
                                           auth=auth,
                                           api_key=gdax.api_key,
                                           api_secret=gdax.api_secret,
                                           api_passphrase=gdax.api_passphrase)
        self._asks = None
        self._bids = None
        self._client = gdax
        self._sequence = -1
        self._log_to = log_to
        if self._log_to:
            assert hasattr(self._log_to, 'write')
        self._current_ticker = None
        self._key_errs = 0
        self._errs = 0
        self.message_count = 0

    @property
    def product_id(self):
        '''
        Currently OrderBook only supports a single product even
        though it is stored as a list of products.
        '''
        return self.products[0]

    def on_message(self, message):
        self.message_count += 1
        if self._log_to:
            pickle.dump(message, self._log_to)

        try:
            sequence = message['sequence']
        except KeyError:
            print("Error: {}".format(message))
            self._key_errs += 1
            if self._key_errs >= 3:
                print("3 errors retrieving sequence. Restarting....")
                self.close()
                self._key_errs = 0
                self.start()
            return

        if self._sequence == -1:
            self._asks = RBTree()
            self._bids = RBTree()
            res = self._client.get_book(self.product_id, level=3)
            for bid in res['bids']:
                self.add({
                    'id': bid[2],
                    'side': 'buy',
                    'price': float(bid[0]),
                    'size': float(bid[1])
                })
            for ask in res['asks']:
                self.add({
                    'id': ask[2],
                    'side': 'sell',
                    'price': float(ask[0]),
                    'size': float(ask[1])
                })
            self._sequence = res['sequence']

        if sequence <= self._sequence:
            # ignore older messages (e.g. before order book
            # initialization from getProductOrderBook)
            return
        elif sequence > self._sequence + 1:
            print('Error: messages missing ({} - {}). '
                  'Re-initializing websocket.'.format(sequence,
                                                      self._sequence))
            self.close()
            self.start()
            return

        msg_type = message['type']
        if msg_type == 'open':
            self.add(message)
        elif msg_type == 'done' and 'price' in message:
            self.remove(message)
        elif msg_type == 'match':
            self.match(message)
            self._current_ticker = message
        elif msg_type == 'change':
            self.change(message)

        self._sequence = sequence

        # bid = self.get_bid()
        # bids = self.get_bids(bid)
        # bid_depth = sum([b['size'] for b in bids])
        # ask = self.get_ask()
        # asks = self.get_asks(ask)
        # ask_depth = sum([a['size'] for a in asks])
        # print('bid: %f @ %f - ask: %f @ %f' % (bid_depth, bid, ask_depth, ask))

    def on_error(self, e):
        self._sequence = -1
        self._errs += 1
        self.close()
        if self._errs >= 3:
            raise Exception(e)
        self.start()

    def add(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': float(order['price']),
            'size': float(order.get('size') or order['remaining_size'])
        }
        if order['side'] == 'buy':
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['price'], bids)
        else:
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['price'], asks)

    def remove(self, order):
        price = float(order['price'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is not None:
                bids = [o for o in bids if o['id'] != order['order_id']]
                if len(bids) > 0:
                    self.set_bids(price, bids)
                else:
                    self.remove_bids(price)
        else:
            asks = self.get_asks(price)
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['order_id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)

    def match(self, order):
        size = float(order['size'])
        price = float(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if not bids:
                return
            assert bids[0]['id'] == order['maker_order_id']
            if bids[0]['size'] == size:
                self.set_bids(price, bids[1:])
            else:
                bids[0]['size'] -= size
                self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if not asks:
                return
            assert asks[0]['id'] == order['maker_order_id']
            if asks[0]['size'] == size:
                self.set_asks(price, asks[1:])
            else:
                asks[0]['size'] -= size
                self.set_asks(price, asks)

    def change(self, order):
        try:
            new_size = float(order['new_size'])
        except KeyError:
            return

        price = float(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is None or not any(o['id'] == order['order_id']
                                       for o in bids):
                return
            index = [b['id'] for b in bids].index(order['order_id'])
            bids[index]['size'] = new_size
            self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if asks is None or not any(o['id'] == order['order_id']
                                       for o in asks):
                return
            index = [a['id'] for a in asks].index(order['order_id'])
            asks[index]['size'] = new_size
            self.set_asks(price, asks)

        tree = self._asks if order['side'] == 'sell' else self._bids
        node = tree.get(price)

        if node is None or not any(o['id'] == order['order_id'] for o in node):
            return

    def get_current_ticker(self):
        return self._current_ticker

    def get_current_book(self):
        result = {
            'sequence': self._sequence,
            'asks': [],
            'bids': [],
        }
        for ask in self._asks:
            try:
                # There can be a race condition here,
                # where a price point is removed
                # between these two ops.
                this_ask = self._asks[ask]
            except KeyError:
                continue
            for order in this_ask:
                bit = [order['price'], order['size'], order['id']]
                result['asks'].append(bit)
        for bid in self._bids:
            try:
                # There can be a race condition here,
                # where a price point is removed
                # between these two ops.
                this_bid = self._bids[bid]
            except KeyError:
                continue

            for order in this_bid:
                result['bids'].append(
                    [order['price'], order['size'], order['id']])
        return result

    def get_orders_matching_ids(self, order_ids):
        return [a for a in self._asks
                if a['id'] in order_ids] +\
               [b for b in self._bids
                if b['id'] in order_ids
        ]

    def get_ask(self):
        return self._asks.min_key()

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        self._asks.insert(price, asks)

    def get_bid(self):
        return self._bids.max_key()

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        self._bids.insert(price, bids)
Exemplo n.º 12
0
class Dijkstra:
    def __init__(self, edgesDict, uToV, origNode, destNode, estimationModel):
        #self.pointList = []
        self.passedNodesSet = set()
        self.notPassedNodeDict = dict()
        #self.notPassedNodeHeapq = []
        #self.notPassedNodeDict = dict()
        self.edgesDict = edgesDict
        self.uToV = uToV
        self.dummyWindow = Window(-1, -1, -1, -1)
        self.origNode = origNode
        self.destNode = destNode
        self.estimationModel = estimationModel
        self.dummyOriNodeInPathGraph = self.generateDummyOirNode()
        self.notPassedNodeDict[self.dummyOriNodeInPathGraph] = 0
        self.dummyDestNodeInPathGraph = self.generateDummyDestNode()
        self.notPassedNodeQ = RBTree()
        self.notPassedNodeQ.insert(self.dummyOriNodeInPathGraph, 0)
        listOfNodesFromOrig = self.uToV[self.origNode]
        self.initializeQ(listOfNodesFromOrig)
        if not len(listOfNodesFromOrig):
            print("not path from node:", self.origNode)
            self.__initializedStatus = False
        else:
            self.__initializedStatus = True

    def generateDummyOirNode(self):
        return NodeInPathGraph(self.dummyWindow, self.origNode, None, -1, 0)

    def generateDummyDestNode(self):
        return NodeInPathGraph(self.dummyWindow, -1, None, -1, inf)

    def initializeQ(self, listOfNodesFromOrig):
        # print(edgesGdfFromNode.iloc[0])
        for edgeIdAndV in listOfNodesFromOrig:
            edgeIdInGdf = edgeIdAndV[0]
            nextNodeId = edgeIdAndV[1]
            nextWindow = Window(self.dummyWindow.prevSeg,
                                self.dummyWindow.midSeg,
                                self.dummyWindow.sucSeg, edgeIdInGdf)
            nextNode = NodeInPathGraph(nextWindow, nextNodeId,
                                       self.dummyOriNodeInPathGraph,
                                       edgeIdInGdf, 0)
            self.notPassedNodeQ.insert(nextNode, 0)
            self.notPassedNodeDict[nextNode] = 0
        return

    def routing(self):
        steps = 0
        if self.checkIniStatus():
            while not self.ifFinished():
                if self.noNodeToExplore():
                    print("No route")
                    return
                else:
                    steps += 1
                    self.onePaceUpdate()

            pathWitMinVal = self.generateMinValNodePath()
            edgePathWithMinVal = self.generateMinValEdgePath()
            print("num of steps:", steps)
            return pathWitMinVal, self.minVal, edgePathWithMinVal
        return

    def checkIniStatus(self):
        return self.__initializedStatus

    def ifFinished(self):
        return self.dummyDestNodeInPathGraph in self.passedNodesSet

    def noNodeToExplore(self):
        return self.notPassedNodeQ.is_empty()

    def onePaceUpdate(self):
        curNodeInPathGraph, valOfCurNode = self.notPassedNodeQ.min_item()
        self.notPassedNodeQ.remove(curNodeInPathGraph)
        _ = self.notPassedNodeDict.pop(curNodeInPathGraph)
        self.passedNodesSet.add(curNodeInPathGraph)
        if self.isDestNode(curNodeInPathGraph):
            self.minVal = valOfCurNode
            self.destNodeGenerated = curNodeInPathGraph
            return
        self.exploreNextNode(curNodeInPathGraph, valOfCurNode)

    def isDestNode(self, curNodeInPathGraph):
        return curNodeInPathGraph == self.dummyDestNodeInPathGraph

    def exploreNextNode(self, curNodeInPathGraph, valOfCurNode):
        if self.isNoSucNode(curNodeInPathGraph):
            self.exploreDummySucNode(curNodeInPathGraph, valOfCurNode)
        else:
            self.exploreNextNodeFromEdge(curNodeInPathGraph, valOfCurNode)
        return

    def isNoSucNode(self, curNodeInPathGraph):
        return curNodeInPathGraph.node == -1 or curNodeInPathGraph.node == self.destNode

    def exploreDummySucNode(self, curNodeInPathGraph, valOfCurNode):
        nextNodeId = -1
        nextWindow = Window(curNodeInPathGraph.window.prevSeg,
                            curNodeInPathGraph.window.midSeg,
                            curNodeInPathGraph.window.sucSeg, -1)
        nextNodes = NodeInPathGraph(nextWindow, nextNodeId, curNodeInPathGraph,
                                    -1)
        valOfNextNode = self.calVal(nextNodes)
        self.updateQ(nextNodes, valOfCurNode + valOfNextNode)
        return

    def exploreNextNodeFromEdge(self, curNodeInPathGraph, valOfCurNode):
        listOfNodes = self.uToV[curNodeInPathGraph.node]
        for edgeIdAndV in listOfNodes:
            edgeIdInGdf = edgeIdAndV[0]
            nextNodeId = edgeIdAndV[1]
            nextWindow = Window(curNodeInPathGraph.window.prevSeg,
                                curNodeInPathGraph.window.midSeg,
                                curNodeInPathGraph.window.sucSeg, edgeIdInGdf)
            nextNodeInPathGraph = NodeInPathGraph(nextWindow, nextNodeId,
                                                  curNodeInPathGraph,
                                                  edgeIdInGdf)
            if nextWindow.valid(
            ) and nextNodeInPathGraph not in self.passedNodesSet:
                valOfNextNode = self.calVal(nextNodeInPathGraph)
                self.updateQ(nextNodeInPathGraph, valOfNextNode + valOfCurNode)

    def updateQ(self, nextNodeInPathGraph, curShortPathEst):
        if nextNodeInPathGraph not in self.notPassedNodeDict:
            nextNodeInPathGraph.shortPathEst = curShortPathEst
            self.notPassedNodeQ.insert(nextNodeInPathGraph,
                                       nextNodeInPathGraph.shortPathEst)
            self.notPassedNodeDict[nextNodeInPathGraph] = curShortPathEst
        else:
            nextNodeInPathGraph.shortPathEst = self.notPassedNodeDict[
                nextNodeInPathGraph]
            if curShortPathEst < nextNodeInPathGraph.shortPathEst:
                self.notPassedNodeQ.remove(nextNodeInPathGraph)
                nextNodeInPathGraph.shortPathEst = curShortPathEst
                self.notPassedNodeQ.insert(nextNodeInPathGraph,
                                           nextNodeInPathGraph.shortPathEst)
                self.notPassedNodeDict[nextNodeInPathGraph] = curShortPathEst

    def calVal(self, curNodeInPathGraph):
        if curNodeInPathGraph.window.midSeg == -1:
            return 0
        else:
            numericalFeatures, categoricalFeatures = curNodeInPathGraph.window.extractFeatures(
                self.edgesDict)
            return self.estimationModel.predictFromData(
                numericalFeatures, categoricalFeatures)

    def generateMinValNodePath(self):
        dNode = self.destNodeGenerated
        pathWitMinVal = [dNode.node]
        while dNode.prevNode:
            dNode = dNode.prevNode
            pathWitMinVal.append(dNode.node)
        return pathWitMinVal[:2:-1]

    def generateMinValEdgePath(self):
        dNode = self.destNodeGenerated
        edgePathWithMinVal = [dNode.edge]
        while dNode.prevNode:
            dNode = dNode.prevNode
            edgePathWithMinVal.append(dNode.edge)
        return edgePathWithMinVal[-2:2:-1]
Exemplo n.º 13
0
class OrderBook(object):
    def __init__(self, product_id, log_to=None):
        self._asks = RBTree()
        self._bids = RBTree()
        self._client = PublicClient()
        self._sequence = -1
        self.sync = -1
        self.product_id = product_id
        self._log_to = log_to
        if self._log_to:
            assert hasattr(self._log_to, 'write')
        self._current_ticker = None

    def on_open(self):
        self._sequence = -1
        #print("-- Subscribed to " + self.product_id + " OrderBook! --\n")

    def on_close(self):
        print("\n-- OrderBook " + self.product_id + " Socket Closed! --")

    def reset_book(self):
        self._asks = RBTree()
        self._bids = RBTree()
        res = self._client.get_product_order_book(product_id=self.product_id,
                                                  level=3)
        for bid in res['bids']:
            self.add({
                'id': bid[2],
                'side': 'buy',
                'price': Decimal(bid[0]),
                'size': Decimal(bid[1])
            })
        for ask in res['asks']:
            self.add({
                'id': ask[2],
                'side': 'sell',
                'price': Decimal(ask[0]),
                'size': Decimal(ask[1])
            })
        self._sequence = res['sequence']

    def on_message(self, message):
        if self._log_to:
            pickle.dump(message, self._log_to)

        sequence = message['sequence']
        if self._sequence == -1:
            self.sync = -1
            self.reset_book()
            return
        if sequence <= self._sequence:
            # ignore older messages (e.g. before order book initialization from getProductOrderBook)
            return
        elif sequence > self._sequence + 1:
            self.on_sequence_gap(self._sequence, sequence)
            self.sync = -1
            return

        self.sync = 1
        msg_type = message['type']
        if msg_type == 'open':
            self.add(message)
        elif msg_type == 'done' and 'price' in message:
            self.remove(message)
        elif msg_type == 'match':
            self.match(message)
            self._current_ticker = message
        elif msg_type == 'change':
            self.change(message)

        self._sequence = sequence

    def on_sequence_gap(self, gap_start, gap_end):
        self.reset_book()
        print(
            'Error: messages missing ({} - {}). Re-initializing  book at sequence.'
            .format(gap_start, gap_end, self._sequence))

    def add(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }
        if order['side'] == 'buy':
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['price'], bids)
        else:
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['price'], asks)

    def remove(self, order):
        price = Decimal(order['price'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is not None:
                bids = [o for o in bids if o['id'] != order['order_id']]
                if len(bids) > 0:
                    self.set_bids(price, bids)
                else:
                    self.remove_bids(price)
        else:
            asks = self.get_asks(price)
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['order_id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)

    def match(self, order):
        size = Decimal(order['size'])
        price = Decimal(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if not bids:
                return
            assert bids[0]['id'] == order['maker_order_id']
            if bids[0]['size'] == size:
                self.set_bids(price, bids[1:])
            else:
                bids[0]['size'] -= size
                self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if not asks:
                return
            assert asks[0]['id'] == order['maker_order_id']
            if asks[0]['size'] == size:
                self.set_asks(price, asks[1:])
            else:
                asks[0]['size'] -= size
                self.set_asks(price, asks)

    def change(self, order):
        try:
            new_size = Decimal(order['new_size'])
        except KeyError:
            return

        try:
            price = Decimal(order['price'])
        except KeyError:
            return

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is None or not any(o['id'] == order['order_id']
                                       for o in bids):
                return
            index = [b['id'] for b in bids].index(order['order_id'])
            bids[index]['size'] = new_size
            self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if asks is None or not any(o['id'] == order['order_id']
                                       for o in asks):
                return
            index = [a['id'] for a in asks].index(order['order_id'])
            asks[index]['size'] = new_size
            self.set_asks(price, asks)

        tree = self._asks if order['side'] == 'sell' else self._bids
        node = tree.get(price)

        if node is None or not any(o['id'] == order['order_id'] for o in node):
            return

    def get_current_ticker(self):
        return self._current_ticker

    def get_current_book(self):
        result = {
            'sequence': self._sequence,
            'asks': [],
            'bids': [],
        }
        for ask in self._asks:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_ask = self._asks[ask]
            except KeyError:
                continue
            for order in this_ask:
                result['asks'].append(
                    [order['price'], order['size'], order['id']])
        for bid in self._bids:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_bid = self._bids[bid]
            except KeyError:
                continue

            for order in this_bid:
                result['bids'].append(
                    [order['price'], order['size'], order['id']])
        return result

    def get_ask(self):
        return self._asks.min_key()

    def get_ask_size(self):
        return sum(order['size'] for order in self._asks.min_item()[1])

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        self._asks.insert(price, asks)

    def get_bid(self):
        return self._bids.max_key()

    def get_bid_size(self):
        return sum(order['size'] for order in self._bids.min_item()[1])

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        self._bids.insert(price, bids)
Exemplo n.º 14
0
class CompletedKeys(object):
    def __init__(self, max_index, min_index=0):
        self._max_index = max_index
        self._min_index = min_index
        self.num_remaining = max_index - min_index
        self._slabs = RBTree()

    def _get_previous_or_none(self, index):
        try:
            return self._slabs.floor_item(index)
        except KeyError:
            return None

    def is_available(self, index):
        logger.debug("Testing index %s", index)
        if index >= self._max_index or index < self._min_index:
            logger.debug("Index out of range")
            return False

        try:
            prev_start, prev_length = self._slabs.floor_item(index)
            logger.debug("Prev range: %s-%s", prev_start,
                         prev_start + prev_length)
            return (prev_start + prev_length) <= index
        except KeyError:
            return True

    def mark_completed(self, start_index, past_last_index):
        logger.debug("Marking the range completed: %s-%s", start_index,
                     past_last_index)
        num_completed = min(past_last_index, self._max_index) - max(
            start_index, self._min_index)

        # Find the item directly before this and see if there is overlap
        to_discard = set()
        try:
            prev_start, prev_length = self._slabs.floor_item(start_index)
            max_prev_completed = prev_start + prev_length
            if max_prev_completed >= start_index:
                # we are going to merge with the range before us
                logger.debug("Merging with the prev range: %s-%s", prev_start,
                             prev_start + prev_length)
                to_discard.add(prev_start)
                num_completed = max(
                    num_completed - (max_prev_completed - start_index), 0)
                start_index = prev_start
                past_last_index = max(past_last_index,
                                      prev_start + prev_length)
        except KeyError:
            pass

        # Find all keys between the start and last index and merge them into one block
        for merge_start, merge_length in self._slabs.iter_items(
                start_index, past_last_index + 1):
            if merge_start in to_discard:
                logger.debug("Already merged with block %s-%s", merge_start,
                             merge_start + merge_length)
                continue

            candidate_next_index = merge_start + merge_length
            logger.debug("Merging with block %s-%s", merge_start,
                         candidate_next_index)
            num_completed -= merge_length - max(
                candidate_next_index - past_last_index, 0)
            to_discard.add(merge_start)
            past_last_index = max(past_last_index, candidate_next_index)

        # write the new block which is fully merged
        discard = False
        if past_last_index >= self._max_index:
            logger.debug("Discarding block and setting new max to: %s",
                         start_index)
            self._max_index = start_index
            discard = True

        if start_index <= self._min_index:
            logger.debug("Discarding block and setting new min to: %s",
                         past_last_index)
            self._min_index = past_last_index
            discard = True

        if to_discard:
            logger.debug("Discarding %s obsolete blocks", len(to_discard))
            self._slabs.remove_items(to_discard)

        if not discard:
            logger.debug("Writing new block with range: %s-%s", start_index,
                         past_last_index)
            self._slabs.insert(start_index, past_last_index - start_index)

        # Update the number of remaining items with the adjustments we've made
        assert num_completed >= 0
        self.num_remaining -= num_completed
        logger.debug("Total blocks: %s", len(self._slabs))

    def get_block_start_index(self, block_size_estimate):
        logger.debug("Total range: %s-%s", self._min_index, self._max_index)
        if self._max_index <= self._min_index:
            raise NoAvailableKeysError(
                "All indexes have been marked completed")

        num_holes = len(self._slabs) + 1
        random_hole = random.randint(0, num_holes - 1)
        logger.debug("Selected random hole %s with %s total holes",
                     random_hole, num_holes)

        hole_start = self._min_index
        past_hole_end = self._max_index

        # Now that we have picked a hole, we need to define the bounds
        if random_hole > 0:
            # There will be a slab before this hole, find where it ends
            bound_entries = self._slabs.nsmallest(random_hole + 1)[-2:]
            left_index, left_len = bound_entries[0]
            logger.debug("Left range %s-%s", left_index, left_index + left_len)
            hole_start = left_index + left_len

            if len(bound_entries) > 1:
                right_index, right_len = bound_entries[1]
                logger.debug("Right range %s-%s", right_index,
                             right_index + right_len)
                past_hole_end, _ = bound_entries[1]
        elif not self._slabs.is_empty():
            right_index, right_len = self._slabs.nsmallest(1)[0]
            logger.debug("Right range %s-%s", right_index,
                         right_index + right_len)
            past_hole_end, _ = self._slabs.nsmallest(1)[0]

        # Now that we have our hole bounds, select a random block from [0:len - block_size_estimate]
        logger.debug("Selecting from hole range: %s-%s", hole_start,
                     past_hole_end)
        rand_max_bound = max(hole_start, past_hole_end - block_size_estimate)
        logger.debug("Rand max bound: %s", rand_max_bound)
        return random.randint(hole_start, rand_max_bound)
class MarinerOrderBook(gdax.OrderBook):

    current_milli_time = lambda self: int(time.time() * 1000)

    def __init__(self, ticker, threshold):
        gdax.OrderBook.__init__(self, product_id=ticker)
        self._threshold = threshold
        self.bookChanged = None
        self.whaleEnteredMarket = None
        self.whaleExitedMarket = None

    def registerHandlers(self, bookChangedHandler, whaleEnteredMarketHandler,
                         whaleExitedMarketHandler, whaleChangedHandler):
        Logging.logger.info("registering callbacks...")
        self.bookChanged = bookChangedHandler
        self.whaleEnteredMarket = whaleEnteredMarketHandler
        self.whaleExitedMarket = whaleExitedMarketHandler
        self.whaleChanged = whaleChangedHandler

    def onMessage(self, message):
        #Logging.logger.info(message)
        sequence = message['sequence']
        if self._sequence == -1:
            self._asks = RBTree()
            self._bids = RBTree()
            res = self._client.getProductOrderBook(level=3)
            for bid in res['bids']:
                self.add({
                    'id': bid[2],
                    'side': 'buy',
                    'price': Decimal(bid[0]),
                    'size': Decimal(bid[1])
                })
            for ask in res['asks']:
                self.add({
                    'id': ask[2],
                    'side': 'sell',
                    'price': Decimal(ask[0]),
                    'size': Decimal(ask[1])
                })
            self._sequence = res['sequence']

        if sequence <= self._sequence:
            # ignore older messages (e.g. before order book initialization from getProductOrderBook)
            return
        elif sequence > self._sequence + 1:
            Logging.logger.error(
                'Error: messages missing ({} - {}). Re-initializing websocket.'
                .format(sequence, self._sequence))
            self.close()
            self.start()
            return

        msg_type = message['type']
        if msg_type == 'open':
            self.add(message)
        elif msg_type == 'done' and 'price' in message:
            self.remove(message)
        elif msg_type == 'match':
            self.match(message)
        elif msg_type == 'change':
            self.change(message)

        self._sequence = sequence

        if not self.bookChanged is None:
            self.bookChanged()

    def add(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }

        if order['side'] == 'buy':
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['price'], bids)
        else:
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['price'], asks)

        if not self.whaleEnteredMarket == None and self.isWhale(order['size']):
            self.whaleEnteredMarket(order)

    def remove(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }
        price = Decimal(order['price'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is not None:
                bids = [o for o in bids if o['id'] != order['id']]
                if len(bids) > 0:
                    self.set_bids(price, bids)
                else:
                    self.remove_bids(price)
        else:
            asks = self.get_asks(price)
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)

        if not self.whaleExitedMarket == None and self.isWhale(order['size']):
            self.whaleExitedMarket(order)

    def match(self, order):
        size = Decimal(order['size'])
        price = Decimal(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if not bids:
                return
            assert bids[0]['id'] == order['maker_order_id']
            if bids[0]['size'] == size:
                self.set_bids(price, bids[1:])
            else:
                bids[0]['size'] -= size
                self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if not asks:
                return
            assert asks[0]['id'] == order['maker_order_id']
            if asks[0]['size'] == size:
                self.set_asks(price, asks[1:])
            else:
                asks[0]['size'] -= size
                self.set_asks(price, asks)

    def change(self, order):
        price = Decimal(order['price'])
        new_volume = Decimal(order['new_size'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is None or not any(o['id'] == order['order_id']
                                       for o in bids):
                return
            index = map(itemgetter('id'), bids).index(order['order_id'])
            bids[index]['size'] = new_size
            self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if asks is None or not any(o['id'] == order['order_id']
                                       for o in asks):
                return
            index = map(itemgetter('id'), asks).index(order['order_id'])
            asks[index]['size'] = new_size
            self.set_asks(price, asks)

        tree = self._asks if order['side'] == 'sell' else self._bids
        node = tree.get(price)

        if node is None or not any(o['id'] == order['order_id'] for o in node):
            return

        if not self.whaleChanged == None and self.isWhale(order['size']):
            self.whaleChanged(order)

    def get_current_book(self,
                         num_levels=10
                         ):  #fetch only 100 levels off book either way
        book = []

        for index, bid_entry in enumerate(self._bids.items([True])):
            if index == num_levels:
                break
            else:
                try:
                    # There can be a race condition here, where a price point is removed
                    # between these two ops
                    thisBid = bid_entry[1]
                except KeyError:
                    continue

                for order in thisBid:
                    order['price'] = Decimal128(order['price'])
                    order['size'] = Decimal128(order['size'])
                    book.append(order)

        for index, ask_entry in enumerate(self._asks.items()):
            if index == num_levels:
                break
            else:
                try:
                    # There can be a race condition here, where a price point is removed
                    # between these two ops
                    thisAsk = ask_entry[1]
                except KeyError:
                    continue

                for order in thisAsk:
                    order['price'] = Decimal128(order['price'])
                    order['size'] = Decimal128(order['size'])
                    book.append(order)

        book_frame = pd.DataFrame(book)
        return book_frame

    def isWhale(self, aVolume):
        return aVolume >= self._threshold

    #For general book management purposes
    def get_bid(self):
        if not self._bids.is_empty():
            return self._bids.max_key()
        else:
            return None

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        self._bids.insert(price, bids)

    def get_ask(self):
        if not self._asks.is_empty():
            return self._asks.min_key()
        else:
            return None

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        self._asks.insert(price, asks)
Exemplo n.º 16
0
class OrderBook(WebsocketClient):
    def __init__(self, product_id='BTC-USD'):
        WebsocketClient.__init__(self, products=product_id)
        self._asks = RBTree()
        self._bids = RBTree()
        self._client = PublicClient(product_id=product_id)
        self._sequence = -1

    def onMessage(self, message):
        sequence = message['sequence']
        if self._sequence == -1:
            self._asks = RBTree()
            self._bids = RBTree()
            res = self._client.getProductOrderBook(level=3)
            for bid in res['bids']:
                self.add({
                    'id': bid[2],
                    'side': 'buy',
                    'price': float(bid[0]),
                    'size': float(bid[1])
                })
            for ask in res['asks']:
                self.add({
                    'id': ask[2],
                    'side': 'sell',
                    'price': float(ask[0]),
                    'size': float(ask[1])
                })
            self._sequence = res['sequence']

        if sequence <= self._sequence:
            return
        elif sequence > self._sequence + 1:
            self.close()
            self.start()
            return

        # print(message)
        msg_type = message['type']
        if msg_type == 'open':
            self.add(message)
        elif msg_type == 'done' and 'price' in message:
            self.remove(message)
        elif msg_type == 'match':
            self.match(message)
        elif msg_type == 'change':
            self.change(message)
        self._sequence = sequence

        # bid = self.get_bid()
        # bids = self.get_bids(bid)
        # bid_depth = sum([b['size'] for b in bids])
        # ask = self.get_ask()
        # asks = self.get_asks(ask)
        # ask_depth = sum([a['size'] for a in asks])
        # print('bid: %f @ %f - ask: %f @ %f' % (bid_depth, bid, ask_depth, ask))

    def add(self, order):
        order = {
            'id':
            order['order_id'] if 'order_id' in order else order['id'],
            'side':
            order['side'],
            'price':
            float(order['price']),
            'size':
            float(order['size'])
            if 'size' in order else float(order['remaining_size'])
        }
        if order['side'] == 'buy':
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['price'], bids)
        else:
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['price'], asks)

    def remove(self, order):
        price = float(order['price'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is not None:
                bids = [o for o in bids if o['id'] != order['order_id']]
                if len(bids) > 0:
                    self.set_bids(price, bids)
                else:
                    self.remove_bids(price)
        else:
            asks = self.get_asks(price)
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['order_id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)

    def match(self, order):
        size = float(order['size'])
        price = float(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            assert bids[0]['id'] == order['maker_order_id']
            if bids[0]['size'] == size:
                self.set_bids(price, bids[1:])
            else:
                bids[0]['size'] -= size
                self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            assert asks[0]['id'] == order['maker_order_id']
            if asks[0]['size'] == size:
                self.set_asks(price, asks[1:])
            else:
                asks[0]['size'] -= size
                self.set_asks(price, asks)

    def change(self, order):
        new_size = float(order['new_size'])
        price = float(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is None or not any(o['id'] == order['order_id']
                                       for o in bids):
                return
            index = map(itemgetter('id'), bids).index(order['order_id'])
            bids[index]['size'] = new_size
            self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if asks is None or not any(o['id'] == order['order_id']
                                       for o in asks):
                return
            index = map(itemgetter('id'), asks).index(order['order_id'])
            asks[index]['size'] = new_size
            self.set_asks(price, asks)

        tree = self._asks if order['side'] == 'sell' else self._bids
        node = tree.get(price)

        if node is None or not any(o['id'] == order['order_id'] for o in node):
            return

    def get_ask(self):
        return self._asks.min_key()

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        self._asks.insert(price, asks)

    def get_bid(self):
        return self._bids.max_key()

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        self._bids.insert(price, bids)
Exemplo n.º 17
0
class OrderBook(WebsocketClient):
    def __init__(self, product_id='BTC-USD'):
        super(OrderBook, self).__init__(products=product_id)
        self._asks = RBTree()
        self._bids = RBTree()
        self._client = PublicClient(product_id=product_id)
        self._sequence = -1

    def on_message(self, message):
        sequence = message['sequence']
        if self._sequence == -1:
            self._asks = RBTree()
            self._bids = RBTree()
            res = self._client.get_product_order_book(level=3)
            for bid in res['bids']:
                self.add({
                    'id': bid[2],
                    'side': 'buy',
                    'price': Decimal(bid[0]),
                    'size': Decimal(bid[1])
                })
            for ask in res['asks']:
                self.add({
                    'id': ask[2],
                    'side': 'sell',
                    'price': Decimal(ask[0]),
                    'size': Decimal(ask[1])
                })
            self._sequence = res['sequence']

        if sequence <= self._sequence:
            # ignore older messages (e.g. before order book initialization from getProductOrderBook)
            return
        elif sequence > self._sequence + 1:
            print('Error: messages missing ({} - {}). Re-initializing websocket.'.format(sequence, self._sequence))
            self.close()
            self.start()
            return

        # print(message)
        msg_type = message['type']
        if msg_type == 'open':
            self.add(message)
        elif msg_type == 'done' and 'price' in message:
            self.remove(message)
        elif msg_type == 'match':
            self.match(message)
        elif msg_type == 'change':
            self.change(message)

        self._sequence = sequence

        # bid = self.get_bid()
        # bids = self.get_bids(bid)
        # bid_depth = sum([b['size'] for b in bids])
        # ask = self.get_ask()
        # asks = self.get_asks(ask)
        # ask_depth = sum([a['size'] for a in asks])
        # print('bid: %f @ %f - ask: %f @ %f' % (bid_depth, bid, ask_depth, ask))

    def add(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }
        if order['side'] == 'buy':
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['price'], bids)
        else:
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['price'], asks)

    def remove(self, order):
        price = Decimal(order['price'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is not None:
                bids = [o for o in bids if o['id'] != order['order_id']]
                if len(bids) > 0:
                    self.set_bids(price, bids)
                else:
                    self.remove_bids(price)
        else:
            asks = self.get_asks(price)
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['order_id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)

    def match(self, order):
        size = Decimal(order['size'])
        price = Decimal(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if not bids:
                return
            assert bids[0]['id'] == order['maker_order_id']
            if bids[0]['size'] == size:
                self.set_bids(price, bids[1:])
            else:
                bids[0]['size'] -= size
                self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if not asks:
                return
            assert asks[0]['id'] == order['maker_order_id']
            if asks[0]['size'] == size:
                self.set_asks(price, asks[1:])
            else:
                asks[0]['size'] -= size
                self.set_asks(price, asks)

    def change(self, order):
        new_size = Decimal(order['new_size'])
        price = Decimal(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is None or not any(o['id'] == order['order_id'] for o in bids):
                return
            index = map(itemgetter('id'), bids).index(order['order_id'])
            bids[index]['size'] = new_size
            self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if asks is None or not any(o['id'] == order['order_id'] for o in asks):
                return
            index = map(itemgetter('id'), asks).index(order['order_id'])
            asks[index]['size'] = new_size
            self.set_asks(price, asks)

        tree = self._asks if order['side'] == 'sell' else self._bids
        node = tree.get(price)

        if node is None or not any(o['id'] == order['order_id'] for o in node):
            return

    def get_current_book(self):
        result = {
            'sequence': self._sequence,
            'asks': [],
            'bids': [],
        }
        for ask in self._asks:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_ask = self._asks[ask]
            except KeyError:
                continue
            for order in this_ask:
                result['asks'].append([order['price'], order['size'], order['id']])
        for bid in self._bids:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_bid = self._bids[bid]
            except KeyError:
                continue

            for order in this_bid:
                result['bids'].append([order['price'], order['size'], order['id']])
        return result

    def get_ask(self):
        return self._asks.min_key()

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        self._asks.insert(price, asks)

    def get_bid(self):
        return self._bids.max_key()

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        self._bids.insert(price, bids)
Exemplo n.º 18
0
class OrderTree(object):
    '''A red-black tree used to store std::list<std::shared_ptr<Order>>s in price order

    The exchange will be using the OrderTree to hold bid and ask data (one OrderTree for each side).
    Keeping the information in a red black tree makes it easier/faster to detect a match.
    '''

    def __init__(self):
        self.price_tree = RBTree()
        self.price_map = {} # Dictionary containing price : std::list<std::shared_ptr<Order>> object
        self.order_map = {} # Dictionary containing order_id : Order object
        self.volume = 0 # Contains total quantity from all Orders in tree
        self.num_orders = 0 # Contains count of Orders in tree
        self.depth = 0 # Number of different prices in tree (http://en.wikipedia.org/wiki/Order_book_(trading)#Book_depth)

    def __len__(self):
        return len(self.order_map)

    def get_price_list(self, price):
        return self.price_map[price]

    def get_order(self, order_id):
        return self.order_map[order_id]

    def create_price(self, price):
        self.depth += 1 # Add a price depth level to the tree
        new_list = std::list<std::shared_ptr<Order>>()
        self.price_tree.insert(price, new_list) # Insert a new price into the tree
        self.price_map[price] = new_list # Can i just get this by using self.price_tree.get_value(price)? Maybe this is faster though.

    def remove_price(self, price):
        self.depth -= 1 # Remove a price depth level
        self.price_tree.remove(price)
        del self.price_map[price]

    def price_exists(self, price):
        return price in self.price_map

    def order_exists(self, order):
        return order in self.order_map

    def insert_order(self, quote):
        if self.order_exists(quote['order_id']):
            self.remove_order_by_id(quote['order_id'])
        self.num_orders += 1
        if quote['price'] not in self.price_map:
            self.create_price(quote['price']) # If price not in Price Map, create a node in RBtree
        order = Order(quote, self.price_map[quote['price']]) # Create an order
        self.price_map[order.price].append_order(order) # Add the order to the std::list<std::shared_ptr<Order>> in Price Map
        self.order_map[order.order_id] = order
        self.volume += order.quantity

    def update_order(self, order_update):
        order = self.order_map[order_update['order_id']]
        original_quantity = order.quantity
        if order_update['price'] != order.price:
            # Price changed. Remove order and update tree.
            order_list = self.price_map[order.price]
            order_list.remove_order(order)
            if len(order_list) == 0: # If there is nothing else in the std::list<std::shared_ptr<Order>>, remove the price from RBtree
                self.remove_price(order.price)
            self.insert_order(order_update)
        else:
            # Quantity changed. Price is the same.
            order.update_quantity(order_update['quantity'], order_update['timestamp'])
        self.volume += order.quantity - original_quantity

    def remove_order_by_id(self, order_id):
        self.num_orders -= 1
        order = self.order_map[order_id]
        self.volume -= order.quantity
        order.order_list.remove_order(order)
        if len(order.order_list) == 0:
            self.remove_price(order.price)
        del self.order_map[order_id]

    def max_price(self):
        if self.depth > 0:
            return self.price_tree.max_key()
        else:
            return None

    def min_price(self):
        if self.depth > 0:
            return self.price_tree.min_key()
        else:
            return None

    def max_price_list(self):
        if self.depth > 0:
            return self.get_price_list(self.max_price())
        else:
            return None

    def min_price_list(self):
        if self.depth > 0:
            return self.get_price_list(self.min_price())
        else:
            return None
Exemplo n.º 19
0
class OrderBook():
    def __init__(self, market=None, bids=None, asks=None, log_to=None):
        self._asks = RBTree()
        self._bids = RBTree()
        self.book_valid = False
        self.new_book(bids, asks)
        self._sequence = -1
        self.market = market
        self._log_to = log_to
        if self._log_to:
            assert hasattr(self._log_to, 'write')
        # My order Details
        self.total_order_count = 0
        self.total_open_order_count = 0
        self.pending_buy_orders_db = {}
        self.pending_sell_orders_db = {}
        self.traded_buy_orders_db = {}
        self.traded_sell_orders_db = {}

        # positions
        self.all_positions = []
        self.open_positions = []
        self.close_pending_positions = {}
        self.closed_positions = []

        self.orderDb = db.OrderDb(Order, market.exchange_name,
                                  market.product_id)
        self.positionsDb = db.PositionDb(Position, market.exchange_name,
                                         market.product_id)
        #stop loss handling
        self.sl_dict = sorteddict.SortedDict()

        #take profit handling
        self.tp_dict = sorteddict.SortedDict()

        #trade Reqs
        self.pending_trade_req = [
        ]  # TODO: FIXME: jork: this better be a nice AVL tree of sort

    def __str__(self):
        return """
{"position_all": %d, "open": %d, "close_pending": %d, "closed": %d}""" % (
            len(self.all_positions), len(self.open_positions),
            len(self.close_pending_positions), len(self.closed_positions))

    def add_pending_trade_req(self, trade_req):
        self.pending_trade_req.append(trade_req)

    def remove_pending_trade_req(self, trade_req):
        # primitive
        self.pending_trade_req.remove(trade_req)

    def open_position(self, buy_order):
        #open positions with buy orders only(we don't support 'short' now)
        #         log.debug("open_position order: %s"%(buy_order))
        position = Position(id=buy_order.id)
        position.add_buy(buy_order)
        if self.market.tradeConfig["stop_loss_enabled"]:
            self.add_stop_loss_position(position, buy_order.get_price(),
                                        self.market, buy_order.stop)
        if self.market.tradeConfig["take_profit_enabled"]:
            self.add_take_profit_position(position, buy_order.get_price(),
                                          self.market, buy_order.profit)

        self.all_positions.append(position)
        self.open_positions.append(position)
        #         log.debug("\n\n\n***open_position: open(%d) closed(%d) close_pend(%d)"%(len(self.open_positions), len(self.closed_positions), len(self.close_pending_positions)))
        # save pos to db
        self.positionsDb.db_save_position(position)

    def get_closable_position(self):
        log.info("get_closable_position ")

        #get last open position for now
        # TODO: FIXME: This may not be the best way. might cause race with below api with multi thread/multi exch
        pos = None
        if len(self.open_positions):
            if self.market.tradeConfig["stop_loss_enabled"]:
                log.info("Finding closable position from _stop_loss_ pool.")
                pos = self.pop_stop_loss_position()
            if pos == None:
                try:
                    pos = self.open_positions.pop()
                except IndexError:
                    log.error("unable to find open position to close")
                    return None
                log.info(
                    "Found closable position from _regular_ pool. pos: %s" %
                    (str(pos)))
            else:
                log.info(
                    "Found closable position from _stop_loss_ pool. pos: %s" %
                    (str(pos)))
                self.open_positions.remove(pos)
            if (self.close_pending_positions.get(pos.id)):
                log.critical("""Position already close pending \npos:%s
                     close_pending_positions: %s
                     open_positions: %s""" %
                             (str(pos), str(self.close_pending_positions),
                              str(self.open_positions)))
                raise Exception("Duplicate close pending position")

            if self.market.tradeConfig["take_profit_enabled"]:
                self.pop_take_profit_position(pos)

            self.close_pending_positions[pos.id] = pos
#         log.debug("\n\n\n***get_closable_position: open(%d) closed(%d) close_pend(%d) \n pos: %s"%(
#             len(self.open_positions), len(self.closed_positions), len(self.close_pending_positions), pos))
        return pos

    def close_position_pending(self, sell_order):
        # Intentionally _not_ updating the pos_db here. Yes, There is a case of wrong pos state if we go down come back during this state
        # TODO: FIXME: This may not be the best way. might cause race with below api with multi thread/multi exch
        log.info(" order:%s" % (sell_order.id))
        #         log.debug("\n\n\n***: open(%d) closed(%d) close_pend(%d)\n"%(
        #             len(self.open_positions), len(self.closed_positions), len(self.close_pending_positions)))
        pos = self.close_pending_positions.get(sell_order.id)
        if pos:
            log.info(": sell order(%s) already in pending_list. do nothing" %
                     (sell_order.id))
            return pos
        #find a close_pending pos without sell attached.
        k = sell_order._pos_id
        if not k:
            log.critical(
                "*****Invalid pos_id attached to order:%s**** \n may be an outside order"
                % (sell_order.id))
            raise Exception("Invalid pos_id attached to order")
        pos = self.close_pending_positions.get(k)
        if pos:
            #find the pos without sell attached. and reinsert after attach
            #             log.debug("pos:\n%s"%(pos))
            if pos.sell == None:
                pos.add_sell(sell_order)
                pos.update_state("close_pending")
                del (self.close_pending_positions[k])
                self.close_pending_positions[sell_order.id] = pos

                #                 log.debug("\n\n\n***: open(%d) closed(%d) close_pend(%d)"%(len(self.open_positions), len(self.closed_positions), len(self.close_pending_positions)))
                return pos
            else:
                log.critical("Wrong sell attached to pos:%s" % (pos))
                raise Exception("Wrong sell attached to pos")
        else:
            #something is very wrong
            log.critical("Unable to find pending position for close id: %s" %
                         (sell_order.id))
            raise Exception("Unable to find pending position for close")

    def close_position_failed(self, pos_id):
        log.info("close_position_failed order: %s" % (pos_id))
        #         log.debug("\n\n\n***close_position_failed: open(%d) closed(%d) close_pend(%d)"%(len(self.open_positions), len(self.closed_positions), len(self.close_pending_positions)))

        #         id = sell_order.id
        position = self.close_pending_positions.get(pos_id)
        if position:
            position.sell = None
            self.close_pending_positions.pop(pos_id, None)
            self.open_positions.append(position)
            if self.market.tradeConfig["stop_loss_enabled"]:
                self.add_stop_loss_position(position, position.buy.get_price(),
                                            self.market,
                                            position.get_stop_loss())
            if self.market.tradeConfig["take_profit_enabled"]:
                self.add_take_profit_position(position,
                                              position.buy.get_price(),
                                              self.market,
                                              position.get_take_profit())
        else:
            log.critical("Unable to get close_pending position. order_id: %s" %
                         (pos_id))

    def close_position(self, sell_order):
        log.info("close_position order: %s" % (sell_order.id))
        id = sell_order.id
        position = self.close_pending_positions.pop(id, None)
        if position:
            position.add_sell(sell_order)
            position.update_state("closed")
            profit = position.get_profit()
            self.market.fund.current_realized_profit += profit
            if profit > 0:
                self.market.num_success_trade += 1
            else:
                self.market.num_failed_trade += 1
            self.closed_positions.append(position)

            #update position
            self.positionsDb.db_save_position(position)
            log.info("position closed pos: %s" % (str(position)))
        else:
            log.critical("Unable to get close_pending position. order_id: %s" %
                         (sell_order.id))
#         log.debug("\n\n\n***close_position: open(%d) closed(%d) close_pend(%d)\n pos:%s"%(
#             len(self.open_positions), len(self.closed_positions), len(self.close_pending_positions), position))

    def add_stop_loss_position(self,
                               position,
                               market_rate,
                               market,
                               stop_price=0):

        sl_kind = market.tradeConfig['stop_loss_kind']
        if sl_kind in ['trailing', 'simple']:
            sl_rate = market.tradeConfig["stop_loss_rate"]
            stop_price = float(
                round(market_rate * (1 - sl_rate * float(.01)), 4))
        elif 'ATR' in sl_kind:
            atr = market.get_cur_indicators()[
                market.tradeConfig['stop_loss_kind']]
            stop_price = float(round(market_rate - 2 * atr, 4))
        elif sl_kind == "strategy":
            if stop_price == 0:
                log.critical("strategy provided invalid stop loss value")
                return
        else:
            raise Exception("Unknown  stop_loss kind - " +
                            market.tradeConfig['stop_loss_kind'])

        position.set_stop_loss(stop_price)

        pos_list = self.sl_dict.get(stop_price, None)
        if pos_list == None:
            pos_list = []
            self.sl_dict[stop_price] = pos_list
        pos_list.append(position)

    def add_stop_loss_position_list(self, stop_price, position_l):
        [pos.set_stop_loss(stop_price) for pos in position_l]
        pos_list = self.sl_dict.get(stop_price, None)
        if pos_list == None:
            pos_list = []
            self.sl_dict[stop_price] = pos_list
        pos_list += position_l

    def remove_all_positions_at_stop(self, stop_price):
        return self.sl_dict.pop(stop_price, None)

    def smart_stop_loss_update_positions(self, cur_indicators, market_rate,
                                         tcfg):

        if tcfg['stop_loss_kind'] == 'trailing':
            sl_rate = tcfg["stop_loss_rate"]
            new_sl = float(round(market_rate * (1 - sl_rate * float(.01)), 4))
        elif 'ATR' in tcfg['stop_loss_kind']:
            atr = cur_indicators[tcfg['stop_loss_kind']]
            new_sl = float(round(market_rate - 2 * atr, 4))
        else:
            raise Exception("Unknown  smart stop_loss kind %s" %
                            (tcfg['stop_loss_kind']))

        key_list = list(
            self.sl_dict.irange(maximum=new_sl, inclusive=(False, False)))

        for key in key_list:
            #             log.critical("new_sl: %d key: %d"%(new_sl, key))
            pos_list = self.sl_dict.pop(key)
            self.add_stop_loss_position_list(new_sl, pos_list)

    def db_commit_dirty_positions(self):
        dirty_pos_list = []
        for pos in self.all_positions:
            if pos._dirty == True:
                pos._dirty = False
                dirty_pos_list.append(pos)

        if len(dirty_pos_list):
            log.debug("commit positions to db")
            # save pos to db
            self.positionsDb.db_save_positions(dirty_pos_list)

    def pop_stop_loss_position(self, pos=None):
        try:
            sl_price, pos_list = 0, None
            if pos:
                sl_price = pos.get_stop_loss()
                pos_list = self.sl_dict.get(sl_price)
            else:
                #get the lowest in the sorted SL list
                sl_price, pos_list = self.sl_dict.peekitem(index=0)
#             log.debug("pop position at sl_price:%d"%(sl_price))
            if pos_list and len(pos_list):
                if pos:
                    pos_list.remove(pos)
                else:
                    pos = pos_list.pop()
                if len(pos_list) == 0:
                    del (self.sl_dict[sl_price])
            return pos
        except (IndexError, ValueError):
            return None

    def get_stop_loss_positions(self, market_rate):
        sl_pos_list = []

        key_list = list(
            self.sl_dict.irange(minimum=market_rate, inclusive=(True, True)))
        #         log.critical("slPrice: %d"%market_rate)
        #         log.critical("key_list :%s"%(key_list))

        for key in key_list:
            pos_list = self.sl_dict.pop(key)
            sl_pos_list += pos_list
            for pos in pos_list:
                self.open_positions.remove(pos)
                if (self.close_pending_positions.get(pos.id)):
                    log.critical("""Position already close pending \npos:%s
                     close_pending_positions: %s
                     open_positions: %s""" %
                                 (str(pos), str(self.close_pending_positions),
                                  str(self.open_positions)))
                    raise Exception("Duplicate close pending position")
                self.close_pending_positions[pos.id] = pos
                # remove pos from take profit points
                self.pop_take_profit_position(pos)
        self.market.num_stop_loss_hit += len(sl_pos_list)

        #         if len(sl_pos_list):
        #             log.critical("num_stop_loss_hit: %d slPrice: %d"%(len(sl_pos_list), market_rate))

        return sl_pos_list

    def get_take_profit_positions(self, market_rate):
        tp_pos_list = []

        key_list = list(
            self.tp_dict.irange(maximum=market_rate, inclusive=(True, True)))

        for key in key_list:
            pos_list = self.tp_dict.pop(key)
            tp_pos_list += pos_list
            for pos in pos_list:
                self.open_positions.remove(pos)
                if (self.close_pending_positions.get(pos.id)):
                    log.critical("""Position already close pending \npos:%s
                     close_pending_positions: %s
                     open_positions: %s""" %
                                 (str(pos), str(self.close_pending_positions),
                                  str(self.open_positions)))
                    raise Exception("Duplicate close pending position")
                self.close_pending_positions[pos.id] = pos
                # remove pos from take profit points
                self.pop_stop_loss_position(pos)

        self.market.num_take_profit_hit += len(tp_pos_list)
        return tp_pos_list

    def add_take_profit_position(self,
                                 position,
                                 market_rate,
                                 market,
                                 new_tp=0):
        tp_kind = market.tradeConfig['take_profit_kind']
        if tp_kind == 'simple':
            tp_rate = market.tradeConfig["take_profit_rate"]
            new_tp = float(round(market_rate * (1 + tp_rate * float(.01)), 4))
        elif tp_kind == 'strategy':
            if new_tp == 0:
                log.critical("strategy provided invalid take profit value")
                return
        else:
            raise Exception("Unknown  take profit kind - " +
                            market.tradeConfig['take_profit_kind'])

        position.set_take_profit(new_tp)
        pos_list = self.tp_dict.get(new_tp, None)
        if pos_list == None:
            pos_list = []
            self.tp_dict[new_tp] = pos_list
        pos_list.append(position)
        log.debug("add take profit(%d) market_rate:(%d)" %
                  (new_tp, market_rate))

    def pop_take_profit_position(self, pos=None):
        try:
            tp_price, pos_list = 0, None
            if pos:
                tp_price = pos.get_take_profit()
                pos_list = self.tp_dict.get(tp_price)
            else:
                tp_price, pos_list = self.tp_dict.peekitem(0)
#             log.debug("pop position at sl_price:%d"%(sl_price))
            if pos_list and len(pos_list):
                if pos:
                    pos_list.remove(pos)
                else:
                    pos = pos_list.pop()
                if len(pos_list) == 0:
                    del (self.tp_dict[tp_price])
            return pos
        except (IndexError, ValueError):
            return None

    def get_all_pending_orders(self):
        pending_order_list = []
        pending_order_list += list(self.pending_buy_orders_db.values())
        pending_order_list += list(self.pending_sell_orders_db.values())
        return pending_order_list

    def add_or_update_pending_buy_order(self, order):
        id = order.id
        cur_order = self.pending_buy_orders_db.get(id)
        if not cur_order:
            self.total_open_order_count += 1
            self.total_order_count += 1
        else:
            #copy required fields
            order.stop = cur_order.stop
            order.profit = cur_order.profit
        self.pending_buy_orders_db[id] = order

    def get_pending_buy_order(self, order_id):
        return self.pending_buy_orders_db.get(order_id)

    def add_traded_buy_order(self, order):
        cur_order = self.pending_buy_orders_db.get(order.id)
        if cur_order:
            #copy required fields
            order.stop = cur_order.stop
            order.profit = cur_order.profit
        self.total_open_order_count -= 1
        del (self.pending_buy_orders_db[order.id])
        self.traded_buy_orders_db[order.id] = order
        #if this is a successful order, we have a new position open
        if order.status == "filled":
            self.open_position(order)

    def get_traded_buy_order(self, order_id):
        return self.traded_buy_orders_db.get(order_id)

    def add_or_update_pending_sell_order(self, order):
        id = order.id
        if not self.pending_sell_orders_db.get(id):
            self.total_open_order_count += 1
            self.total_order_count += 1
        self.pending_sell_orders_db[id] = order

    def get_pending_sell_order(self, order_id):
        self.pending_sell_orders_db.get(order_id)

    def add_traded_sell_order(self, order):
        del (self.pending_sell_orders_db[order.id])
        self.total_open_order_count -= 1
        self.traded_sell_orders_db[order.id] = order
        #close/reopen position
        #TODO: TBD: more checks required??
        if order.status == "filled":
            log.debug("closed position order: %s" % (order.id))
            self.close_position(order)
        else:
            log.critical("closed position failed order: %s" % (order))
            self.close_position_failed(order.id)

    def get_traded_sell_order(self, order_id):
        return self.traded_sell_orders_db.get(order_id)

    def add_order_list(self, bids, asks):
        if (asks):
            self.add_asks(asks)
        if (bids):
            self.add_bids(bids)

    def clear_order_book(self):
        log.info("clearing older states")
        self.orderDb.clear_order_db()
        self.positionsDb.clear_position_db()

    def restore_order_book(self):
        # TODO: FIXME:  Not considering pending state orders

        if (sims.simulator_on):
            #don't do order db init for sim
            return None

        log.info("Restoring positions and orders")

        #1. Retrieve states back from Db
        order_list = self.orderDb.get_all_orders()

        if not order_list:
            log.info("no orders to restore")
        else:
            # restore orders
            log.info("Restoring %d orders" % (len(order_list)))
            for order in order_list:
                #             order_status = order.status_type
                order_side = order.side

                log.info("restoring order: %s side: %s" %
                         (order.id, order_side))
                self.total_order_count += 1
                if order_side == 'buy':
                    self.traded_buy_orders_db[order.id] = order
                else:
                    self.traded_sell_orders_db[order.id] = order

        # restore positions
        pos_list = self.positionsDb.db_get_all_positions(self.orderDb)
        #         log.critical("mapping: %s"%(str(self.positionsDb.mapping)))
        if not pos_list:
            log.info("no positions to restore")
            return

        log.info("Restoring %d positions" % (len(pos_list)))
        for pos in pos_list:
            log.debug("restoring position(%s)" % (pos.id))
            self.all_positions.append(pos)
            if pos.status == "open":
                self.open_positions.append(pos)
                if self.market.tradeConfig["stop_loss_enabled"]:
                    self.add_stop_loss_position(pos, pos.buy.get_price(),
                                                self.market,
                                                pos.get_stop_loss())
                if self.market.tradeConfig["take_profit_enabled"]:
                    self.add_take_profit_position(pos, pos.buy.get_price(),
                                                  self.market,
                                                  pos.get_take_profit())
            else:
                self.closed_positions.append(pos)

        log.info("all positions and orders are restored")
#         sys.exit()

    def get_positions(self, from_time=0, to_time=0):
        log.info("get positions ", from_time, to_time)
        return self.all_positions[:]

    def dump_traded_orders(self, fd=sys.stdout):
        traded = str(
            list(self.traded_buy_orders_db.values()) +
            list(self.traded_sell_orders_db.values()))
        fd.write(traded)

    def dump_positions(self, fd=sys.stdout):
        fd.write(str(self.all_positions))
#
#     def on_sequence_gap(self, gap_start, gap_end):
#         self.reset_book()
#         print('Error: messages missing({} - {}). Re-initializing  book at sequence.'.format(
#             gap_start, gap_end, self._sequence))

####### Public API #######

    def add_or_update_my_order(self, order):
        # simplified order state machine :[open, filled, canceled]
        # this rework is assumed an abstraction and handles only simplified order status
        # if there are more order states, it should be handled/translated in the exch impl.
        # linked to market.order_status_update()
        #         '''
        #         Handle a new order update msg
        #         return : order
        #         '''
        if (not order):
            return None
        order_id = order.id
        order_status = order.status
        order_side = order.side
        if (not order_id):
            log.critical("Invalid order_id: status:%s side: %s" %
                         (order_status, order_side))
            raise Exception("Invalid order_id: status:%s side: %s" %
                            (order_status, order_side))

        if (order_side == 'buy'):
            # see if this is a late/mixed up state msg for an already done order. What we do here, may not be correct
            if self.get_traded_buy_order(order_id):
                log.critical(
                    "********(%s) order done already, but(%s) state msg recvd, ignore for now, FIXME: FIXME:"
                    % (order_side, order_status))
                return None
            # insert/replace the order
            if (order_status == 'filled' or order_status == 'canceled'):
                # a previously placed order is completed, remove from open order, add to completed orderlist
                self.add_traded_buy_order(order)
                log.debug("Buy order Done: total_order_count: %d "
                          "total_open_order_count: %d "
                          "traded_buy_orders_count: %d" %
                          (self.total_order_count, self.total_open_order_count,
                           len(self.traded_buy_orders_db)))
            elif (order_status == 'open'):
                # Nothing much to do for us here
                log.info("Buy order_id(%s) Status: %s" %
                         (str(order_id), order_status))
                self.add_or_update_pending_buy_order(order)
            else:
                log.critical("UNKNOWN buy order status: %s" % (order_status))
                raise Exception("UNKNOWN buy order status: %s" %
                                (order_status))
        elif (order_side == 'sell'):
            # see if this is a late/mixed up state msg for an already done order. What we do here, may not be correct
            if self.get_traded_sell_order(order_id):
                log.critical(
                    "********(%s) order done already, but(%s) state msg recvd, ignore for now, FIXME: FIXME:"
                    % (order_side, order_status))
                return None
            # insert/replace the order
            if (order_status == 'filled' or order_status == 'canceled'):
                # a previously placed order is completed, remove from open order, add to completed orderlist
                self.add_traded_sell_order(order)
                log.debug("Sell order Done: total_order_count: %d "
                          "total_open_order_count: %d "
                          "traded_sell_orders_count: %d" %
                          (self.total_order_count, self.total_open_order_count,
                           len(self.traded_sell_orders_db)))
            elif (order_status == 'open'):
                # Nothing much to do for us here
                log.info("Sell order_id(%s) Status: %s" %
                         (str(order_id), order_status))
                self.add_or_update_pending_sell_order(order)
                self.close_position_pending(order)
            else:
                log.critical("UNKNOWN sell order status: %s" % (order_status))
                raise Exception("UNKNOWN buy order status: %s" %
                                (order_status))
        else:
            log.critical("Invalid order :%s" % (order))
            raise Exception("Invalid order :%s" % (order))
#         log.debug("Order: %s\n"%(str(order)))

#Add the successful order to the db
        self.orderDb.db_add_or_update_order(order)
        stats.stats_update_order(self.market, order)
        return order

    def new_book(self, bids, asks):
        log.info("Building new order book")
        if (bids and len(bids)) or (asks and len(asks)):
            self.add_order_list(bids, asks)
            self.book_valid = True
        else:
            self.book_valid = False

    def reset_book(self):
        self._asks = RBTree()
        self._bids = RBTree()
        res = self.market.exchange.get_product_order_book(
            self.market.product_id, level=3)
        # log.debug("%s"%(str(res)))
        if res == None:
            log.error("Unable to get orderbook for exchange(%s) product: %s" %
                      (self.market.exchange.name, self.market.product_id))
            return
        for bid in res['bids']:
            new_size = float(bid[1])
            price = float(bid[0])
            new_size += float((self.get_bids(price) or 0))
            self.set_bids(price, new_size)
        for ask in res['asks']:
            new_size = float(ask[1])
            price = float(ask[0])
            new_size += float((self.get_asks(price) or 0))
            self.set_asks(price, new_size)
        self._sequence = float(res['sequence'])
        self.book_valid = True
#         print("asks: %s"%(str(self._asks)))
#         print("bids: %s"%(str(self._bids)))

    def add_asks(self, asks):
        '''
        asks =[[price, size]]
        '''
        for ask in asks:
            price = float(ask[0])
            size = float(ask[1])
            if size > 0:  # size > 0 add, size = 0 remove
                self.set_asks(price, size)
            else:
                if (self.get_asks(price)):
                    self.remove_asks(price)

    def get_ask(self):
        return self._asks.min_key()

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        price = round(price, 8)
        asks = round(asks, 8)
        log.debug("set_asks: price: %g size: %g" % (price, asks))
        self._asks.insert(price, asks)

    def add_bids(self, bids):
        '''
        bids =[[price, size]]
        '''
        for bid in bids:
            price = float(bid[0])
            size = float(bid[1])
            if size > 0:  # size > 0 add, size = 0 remove
                self.set_bids(price, size)
            else:
                if (self.get_bids(price)):
                    self.remove_bids(price)

    def get_bid(self):
        return self._bids.max_key()

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        price = round(price, 8)
        bids = round(bids, 8)
        log.debug("set_bid: price: %g size: %g" % (price, bids))
        self._bids.insert(
            price, bids)  # insert on RBtree is a replace for existing keys
Exemplo n.º 20
0
class OrderBookL2():
    def __init__(self, product_id='BTC-USD'):
        self._asks = RBTree()
        self._bids = RBTree()
        self._product_id = product_id

    @property
    def product_id(self):
        return self._product_id

    def process_snapshot(self, message):
        ''' Process a snapshot message '''

        self._asks = RBTree()
        self._bids = RBTree()

        for ask in message['asks']:
            price, size = ask
            price = Decimal(price)
            size = Decimal(size)

            self._asks.insert(price, size)

        for bid in message['bids']:
            price, size = bid
            price = Decimal(price)
            size = Decimal(size)

            self._bids.insert(price, size)

    def process_update(self, message):
        ''' Process an update message '''

        changes = message['changes']

        for change in changes:
            side, price, size = change

            price = Decimal(price)
            size = Decimal(size)

            if side == 'buy':
                if size <= 0:
                    self._bids.remove(price)
                else:
                    self._bids.insert(price, size)
            elif side == 'sell':
                if size <= 0:
                    self._asks.remove(price)
                else:
                    self._asks.insert(price, size)

    def process_message(self, message):
        msg_type = message['type']

        # dropped - not same product id
        if (message.get('product_id', None) != self._product_id):
            return

        if msg_type == 'snapshot':
            self.process_snapshot(message)
        elif msg_type == 'l2update':
            self.process_update(message)

    def get_current_book(self):
        result = {'asks': [], 'bids': []}

        for ask in self._asks:
            try:
                size = self._asks[ask]
            except KeyError:
                continue
            result['asks'].append([ask, size])

        for bid in self._bids:
            try:
                size = self._bids[bid]
            except KeyError:
                continue
            result['bids'].append([bid, size])
        return result

    def get_ask(self):
        price = self._asks.min_key()

        try:
            size = self._asks[price]
        except KeyError:
            return (price, 0)

        return (price, size)

    def get_bid(self):
        price = self._bids.max_key()

        try:
            size = self._bids[price]
        except KeyError:
            return (price, 0)

        return (price, size)
Exemplo n.º 21
0
class OrderBook(object):
    def __init__(self, product_id='BTC-USD', feed=None, log_to=None):
        self._product_id = product_id
        self._asks = RBTree()
        self._bids = RBTree()
        self._sequence = -1
        self._current_ticker = None
        self._feed = feed

    @property
    def product_id(self):
        """ Currently OrderBook only supports a single product even though it is stored as a list of products. """
        return self.product_id

    def reset_book(self, snapshot):
        self._asks = RBTree()
        self._bids = RBTree()
        for bid in snapshot['bids']:
            self._add({
                'id': bid[2],
                'side': 'buy',
                'price': Decimal(bid[0]),
                'size': Decimal(bid[1])
            })
        for ask in snapshot['asks']:
            self._add({
                'id': ask[2],
                'side': 'sell',
                'price': Decimal(ask[0]),
                'size': Decimal(ask[1])
            })
        self._sequence = snapshot['sequence']

    def on_message(self, message):
        sequence = message['sequence']
        if self._sequence == -1:
            logger.error("Expected snapshot before any message")
            sys.exit()
            # self.reset_book()
            return
        if sequence <= self._sequence:
            # ignore older messages (e.g. before order book initialization from getProductOrderBook)
            return
        elif sequence > self._sequence + 1:
            self.on_sequence_gap(self._sequence, sequence)
            # return

        msg_type = message['type']
        if msg_type == 'open':
            self._add(message)
        elif msg_type == 'done' and 'price' in message:
            self._remove(message)
        elif msg_type == 'match':
            self._match(message)
            self._current_ticker = message
        elif msg_type == 'change':
            self._change(message)

        self._sequence = sequence

    def on_sequence_gap(self, gap_start, gap_end):
        # self.reset_book()
        logger.error(
            'Error: messages missing ({} - {}). ignoring the gap.'.format(
                gap_start, gap_end, self._sequence))

    def get_current_ticker(self):
        return self._current_ticker

    def get_current_book(self):
        result = {
            'sequence': self._sequence,
            'asks': [],
            'bids': [],
        }
        for ask in self._asks:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_ask = self._asks[ask]
            except KeyError:
                continue
            for order in this_ask:
                result['asks'].append(
                    [order['price'], order['size'], order['id']])
        for bid in self._bids:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_bid = self._bids[bid]
            except KeyError:
                continue

            for order in this_bid:
                result['bids'].append(
                    [order['price'], order['size'], order['id']])
        return result

    def get_ask(self):
        return self._asks.min_key()

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        self._asks.insert(price, asks)

    def get_bid(self):
        return self._bids.max_key()

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        self._bids.insert(price, bids)

    @staticmethod
    def meet_min_diff_price(low_price, high_price, min_diff_price):
        if low_price is None or high_price is None or min_diff_price is None:
            return False
        return high_price - low_price > min_diff_price

    @staticmethod
    def meet_max_size(total_size, max_size):
        if total_size is None or max_size is None:
            return False
        return total_size > max_size

    def get_aggr_bids(self, min_diff_price=None, max_size=None):
        max_bid = self.get_bid()
        total_size = 0
        aggr_bids = list()
        for price, bid_orders in reversed(list(self._bids.items())):
            if (OrderBook.meet_min_diff_price(price, max_bid, min_diff_price)
                    and OrderBook.meet_max_size(total_size, max_size)):
                break
            size = sum(bid_order['size'] for bid_order in bid_orders)
            num_orders = len(bid_orders)
            aggr_bids.append({
                'price': price,
                'size': size,
                'num_orders': num_orders
            })
            total_size += size
        return aggr_bids

    def get_aggr_asks(self, min_diff_price=None, max_size=None):
        min_ask = self.get_ask()
        total_size = 0
        aggr_asks = list()
        for price, ask_orders in self._asks.items():
            if (OrderBook.meet_min_diff_price(min_ask, price, min_diff_price)
                    and OrderBook.meet_max_size(total_size, max_size)):
                break
            size = sum(ask_order['size'] for ask_order in ask_orders)
            num_orders = len(ask_orders)
            aggr_asks.append({
                'price': price,
                'size': size,
                'num_orders': num_orders
            })
            total_size += size
        return aggr_asks

    # Internal operations
    def _add(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }
        if order['side'] == 'buy':
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['price'], bids)
        else:
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['price'], asks)

    def _remove(self, order):
        price = Decimal(order['price'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is not None:
                bids = [o for o in bids if o['id'] != order['order_id']]
                if len(bids) > 0:
                    self.set_bids(price, bids)
                else:
                    self.remove_bids(price)
        else:
            asks = self.get_asks(price)
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['order_id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)

    def _match(self, order):
        if self._feed is not None:
            self._feed._match(order)

        size = Decimal(order['size'])
        price = Decimal(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if not bids:
                return
            assert bids[0]['id'] == order['maker_order_id']
            if bids[0]['size'] == size:
                self.set_bids(price, bids[1:])
            else:
                bids[0]['size'] -= size
                self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if not asks:
                return
            assert asks[0]['id'] == order['maker_order_id']
            if asks[0]['size'] == size:
                self.set_asks(price, asks[1:])
            else:
                asks[0]['size'] -= size
                self.set_asks(price, asks)

    def _change(self, order):
        try:
            new_size = Decimal(order['new_size'])
        except KeyError:
            return

        try:
            price = Decimal(order['price'])
        except KeyError:
            return

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is None or not any(o['id'] == order['order_id']
                                       for o in bids):
                return
            index = [b['id'] for b in bids].index(order['order_id'])
            bids[index]['size'] = new_size
            self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if asks is None or not any(o['id'] == order['order_id']
                                       for o in asks):
                return
            index = [a['id'] for a in asks].index(order['order_id'])
            asks[index]['size'] = new_size
            self.set_asks(price, asks)

        tree = self._asks if order['side'] == 'sell' else self._bids
        node = tree.get(price)

        if node is None or not any(o['id'] == order['order_id'] for o in node):
            return
Exemplo n.º 22
0
class Book(object):
    def __init__(self):
        self.tree = RBTree()
        self.prices = dict()  # { price : Queue } objects
        self.orders = dict()  # { order_id : Order } objects
        self.volume = 0  # Contains total quantity from all Orders in tree
        self.num_orders = 0  # Contains count of Orders in tree
        self.depth = 0  # Number of different prices in tree (http://en.wikipedia.org/wiki/Order_book_(trading)#Book_depth)

    def get_price_list(self, price):

        return self.prices[price]

    def get_order(self, order_id):

        return self.orders[order_id]

    def create_price(self, price):
        self.depth += 1
        new_price = Queue()
        self.tree.insert(price, new_price)
        self.prices[price] = new_price

    def remove_price(self, price):
        self.depth -= 1
        self.tree.remove(price)

        del self.prices[price]

    def price_exists(self, price):

        return price in self.prices

    def order_exists(self, order):

        return order in self.orders

    def insert_order(self, quote):
        if self.order_exists(quote['order_id']):
            self.remove_order_by_id(quote['order_id'])

        self.num_orders += 1

        if quote['price'] not in self.prices:
            # If price not in price list, create a node in tree
            self.create_price(quote['price'])

        order = Order(quote, self.prices[quote['price']])  # Create an order
        
        self.prices[order.price].append(order)  # Add the order to the prices queue
        self.orders[order.order_id] = order
        self.volume += order.quantity

    def update_order(self, order_update):
        order = self.orders[order_update['order_id']]
        original_quantity = order.quantity
        if order_update['price'] != order.price:
            # Price changed. Remove order and update tree.
            order_list = self.prices[order.price]
            order_list.remove(order)
            if len(order_list) == 0: # If there is nothing else in the OrderList, remove the price from RBtree
                self.remove_price(order.price)
            self.insert_order(order_update)
        else:
            # Quantity changed. Price is the same.
            order.update_quantity(order_update['quantity'], order_update['timestamp'])

        self.volume += order.quantity - original_quantity

    def remove_order_by_id(self, order_id):
        self.num_orders -= 1
        order = self.orders[order_id]
        self.volume -= order.quantity
        order.order_list.remove(order)

        if len(order.order_list) == 0:
            self.remove_price(order.price)

        del self.orders[order_id]

    def max_price(self):
        if self.depth > 0:
            return self.tree.max_key()
        else:
            return None

    def min_price(self):
        if self.depth > 0:
            return self.tree.min_key()
        else:
            return None

    def max_price_list(self):
        if self.depth > 0:
            return self.get_price_list(self.max_price())
        else:
            return None

    def min_price_list(self):
        if self.depth > 0:
            return self.get_price_list(self.min_price())
        else:
            return None

    def __len__(self):

        return len(self.orders)
Exemplo n.º 23
0
class OrderTree(object):
    ''' An RBtree used to store OrderLists in price order '''
    def __init__(self):
        '''
        @summary: initialization of Ordertree class
        @mem price_tree: a red-black tree
        @mem price_map: Dictionary containing price : OrderList object
        @mem order_map: Dictionary containing order_id : Order object
        @mem volume: Contains total quantity from all Orders in tree
        @mem num_orders: Contains count of Orders in tree
        @mem depth: Number of different prices in tree (http://en.wikipedia.org/wiki/Order_book_(trading)#Book_depth)
        '''
        self.price_tree = RBTree()
        self.price_map = {}
        self.order_map = {}
        self.volume = 0
        self.num_orders = 0
        self.depth = 0

    def __len__(self):
        ''' return the length of order_map '''
        return len(self.order_map)

    def get_price_list(self, price):
        ''' get the price list from the price map '''
        return self.price_map[price]

    def get_order(self, order_id):
        ''' get order list from order map '''
        return self.order_map[order_id]

    def create_price(self, price):
        ''' create a new price if adding an order without price in the tree '''
        self.depth += 1
        new_list = OrderList()
        self.price_tree.insert(price, new_list)
        self.price_map[price] = new_list

    def remove_price(self, price):
        ''' remove a price from the tree '''
        self.depth -= 1
        self.price_tree.remove(price)
        del self.price_map[price]

    def price_exists(self, price):
        ''' check whether price exists in price map '''
        return price in self.price_map

    def order_exists(self, order):
        ''' check whether an order exists in order map '''
        return order in self.order_map

    def insert_order(self, quote):
        ''' insert an order into the order map '''
        if self.order_exists(quote['order_id']):
            self.remove_order_by_id(quote['order_id'])
        self.num_orders += 1
        if quote['price'] not in self.price_map:
            self.create_price(
                quote['price']
            )  # If price not in Price Map, create a node in RBtree
        order = Order(quote, self.price_map[quote['price']])  # Create an order
        self.price_map[order.price].append_order(
            order)  # Add the order to the OrderList in Price Map
        self.order_map[order.order_id] = order
        self.volume += order.quantity

    def remove_order_by_id(self, order_id):
        ''' remove an order from the order map '''
        self.num_orders -= 1
        order = self.order_map[order_id]
        self.volume -= order.quantity
        order.order_list.remove_order(order)
        if len(order.order_list) == 0:
            self.remove_price(order.price)
        del self.order_map[order_id]

    def max_price(self):
        ''' return max price in price tree '''
        if self.depth > 0:
            return self.price_tree.max_key()
        else:
            return None

    def min_price(self):
        ''' return min price in price tree '''
        if self.depth > 0:
            return self.price_tree.min_key()
        else:
            return None

    def max_price_list(self):
        ''' return max price in price tree '''
        if self.depth > 0:
            return self.get_price_list(self.max_price())
        else:
            return None

    def min_price_list(self):
        ''' return min price in price tree '''
        if self.depth > 0:
            return self.get_price_list(self.min_price())
        else:
            return None
Exemplo n.º 24
0
class OrderBook(WebsocketClient):
    def __init__(self,
                 product_id='BTC-USD',
                 log_to=None,
                 autoreconnect=True,
                 max_retries=3):
        super(OrderBook, self).__init__(products=[product_id])

        self.channels = ['full']

        self._asks = RBTree()
        self._bids = RBTree()
        self._client = PublicClient()
        self._sequence = -1
        self._log_to = log_to
        self._retries = 0
        self._max_retries = max_retries
        self._autoreconnect = autoreconnect
        self._is_ready = False
        if self._log_to:
            assert hasattr(self._log_to, 'write')
        self._current_ticker = None

    @property
    def product_id(self):
        ''' Currently OrderBook only supports a single product even though it is stored as a list of products. '''
        return self.products[0]

    def is_ready(self):
        return self._is_ready

    def on_open(self):
        self._sequence = -1
        self._retries = 0
        print("-- Subscribed to OrderBook! --\n")

    def on_close(self):
        print("\n-- OrderBook Socket Closed! --")
        self._is_ready = False
        if not self.ws.sock or not self.ws.sock.connected and self._autoreconnect:
            self._sequence = -1

            if self._retries < self._max_retries:
                print('-- OrderBook Reconnecting... --')
                self._retries += 1
                self.start()
            else:
                print('-- Could not reconnect, stopping... --')

    def reset_book(self):
        self._is_ready = False
        self._asks = RBTree()
        self._bids = RBTree()
        res = self._client.get_product_order_book(product_id=self.product_id,
                                                  level=3)
        for bid in res['bids']:
            self.add({
                'id': bid[2],
                'side': 'buy',
                'price': Decimal(bid[0]),
                'size': Decimal(bid[1])
            })
        for ask in res['asks']:
            self.add({
                'id': ask[2],
                'side': 'sell',
                'price': Decimal(ask[0]),
                'size': Decimal(ask[1])
            })
        print("\n-- OrderBook Data Initialized --")
        self._is_ready = True
        self._sequence = res['sequence']

    def on_message(self, message):
        if self._log_to:
            pickle.dump(message, self._log_to)

        if 'sequence' not in message:
            return

        sequence = message['sequence']
        if self._sequence == -1:
            print("\n-- Initializing OrderBook --")
            self.reset_book()
            return
        if sequence <= self._sequence:
            # ignore older messages (e.g. before order book initialization from getProductOrderBook)
            return
        elif sequence > self._sequence + 1:
            self.on_sequence_gap(self._sequence, sequence)
            return

        msg_type = message['type']

        if msg_type == 'open':
            self.add(message)
        elif msg_type == 'done' and 'price' in message:
            self.remove(message)
        elif msg_type == 'match':
            self.match(message)
            self._current_ticker = message
        elif msg_type == 'change':
            self.change(message)

        self._sequence = sequence

    def on_error(self, ws, e):
        super(OrderBook, self).on_error(ws, e)

        if not ws.sock or not ws.sock.connected:
            self._sequence = -1
            self._is_ready = False

            if self._retries < self._max_retries:
                print('-- OrderBook Disconnected, reconnecting... --')
                self._retries += 1
                self.start()
            else:
                print('-- Could not reconnect, stopping... --')

    def on_sequence_gap(self, gap_start, gap_end):
        self._sequence = -1
        self.reset_book()

        print('-- Error: messages missing, re-initializing book. --'.format(
            gap_start, gap_end, self._sequence))

    def add(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }
        if order['side'] == 'buy':
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['price'], bids)
        else:
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['price'], asks)

    def remove(self, order):
        price = Decimal(order['price'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is not None:
                bids = [o for o in bids if o['id'] != order['order_id']]
                if len(bids) > 0:
                    self.set_bids(price, bids)
                else:
                    self.remove_bids(price)
        else:
            asks = self.get_asks(price)
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['order_id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)

    def match(self, order):
        size = Decimal(order['size'])
        price = Decimal(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if not bids:
                return
            assert bids[0]['id'] == order['maker_order_id']
            if bids[0]['size'] == size:
                self.set_bids(price, bids[1:])
            else:
                bids[0]['size'] -= size
                self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if not asks:
                return
            assert asks[0]['id'] == order['maker_order_id']
            if asks[0]['size'] == size:
                self.set_asks(price, asks[1:])
            else:
                asks[0]['size'] -= size
                self.set_asks(price, asks)

    def change(self, order):
        try:
            new_size = Decimal(order['new_size'])
        except KeyError:
            return

        try:
            price = Decimal(order['price'])
        except KeyError:
            return

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is None or not any(o['id'] == order['order_id']
                                       for o in bids):
                return
            index = [b['id'] for b in bids].index(order['order_id'])
            bids[index]['size'] = new_size
            self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if asks is None or not any(o['id'] == order['order_id']
                                       for o in asks):
                return
            index = [a['id'] for a in asks].index(order['order_id'])
            asks[index]['size'] = new_size
            self.set_asks(price, asks)

        tree = self._asks if order['side'] == 'sell' else self._bids
        node = tree.get(price)

        if node is None or not any(o['id'] == order['order_id'] for o in node):
            return

    def get_current_ticker(self):
        return self._current_ticker

    def get_current_book(self):
        result = {
            'sequence': self._sequence,
            'asks': [],
            'bids': [],
        }
        for ask in self._asks:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_ask = self._asks[ask]
            except KeyError:
                continue
            for order in this_ask:
                result['asks'].append(
                    [order['price'], order['size'], order['id']])
        for bid in self._bids:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_bid = self._bids[bid]
            except KeyError:
                continue

            for order in this_bid:
                result['bids'].append(
                    [order['price'], order['size'], order['id']])
        return result

    def get_ask(self):
        return self._asks.min_key()

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        self._asks.insert(price, asks)

    def get_bid(self):
        return self._bids.max_key()

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        self._bids.insert(price, bids)
Exemplo n.º 25
0
class MarinerOrderBook(GDAX.OrderBook):
    def __init__(self, ticker, threshold):
        GDAX.OrderBook.__init__(self, product_id = ticker)
        self._threshold = threshold
        self._buyWhales = RBTree()
        self._sellWhales = RBTree()
        self._topBuyWhale = None
        self._topSellWhale = None

    def registerHandlers(self, topWhaleChangedHandler):
        print("    registering callbacks...")
        self.topWhaleChangedHandler = topWhaleChangedHandler

    def onMessage(self, message):
        sequence = message['sequence']
        if self._sequence == -1:
            self._asks = RBTree()
            self._bids = RBTree()
            res = self._client.getProductOrderBook(level=3)
            for bid in res['bids']:
                self.add({
                    'id': bid[2],
                    'side': 'buy',
                    'price': Decimal(bid[0]),
                    'size': Decimal(bid[1])
                })
            for ask in res['asks']:
                self.add({
                    'id': ask[2],
                    'side': 'sell',
                    'price': Decimal(ask[0]),
                    'size': Decimal(ask[1])
                })
            self._sequence = res['sequence']

        if sequence <= self._sequence:
            # ignore older messages (e.g. before order book initialization from getProductOrderBook)
            return
        elif sequence > self._sequence + 1:
            print('Error: messages missing ({} - {}). Re-initializing websocket.'.format(sequence, self._sequence))
            self.close()
            self.start()
            return

        msg_type = message['type']
        if msg_type == 'open':
            self.add(message)
        elif msg_type == 'done' and 'price' in message:
            self.remove(message)
        elif msg_type == 'match':
            self.match(message)
        elif msg_type == 'change':
            self.change(message)

        self._sequence = sequence


    def add(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }

        if order['side'] == 'buy':
            self.checkWhaleAddBuy(order)
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['price'], bids)
        else:
            self.checkWhaleAddSell(order)
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['price'], asks)


    def checkWhaleAddBuy(self, order):
        price = Decimal(order['price'])
        volume = Decimal(order['size'])
        if self.isWhale(volume): #too low in practice
            print("WHALE ENTERED (BUY): price=" + str(price) + " volume=" + str(volume))
            whale_bid_level = self.get_whale_bids(price)
            if whale_bid_level is None:
                whale_bid_level = WhaleLevel(price, volume)
            else:
                whale_bid_level.add_volume(volume)
            self.set_whale_bids(price, whale_bid_level)
            top_whale_price = self.get_whale_bid()
            if  self._topBuyWhale is None or top_whale_price != self._topBuyWhale.get_price():
                self._topBuyWhale = self._buyWhales.get(top_whale_price)
                self.topWhaleChangedHandler(self._topBuyWhale, "BUY")
            else:
                print("    top BUY whale is still " + str(self._topBuyWhale))

    def checkWhaleAddSell(self, order):
        price = Decimal(order['price'])
        volume = Decimal(order['size'])
        if self.isWhale(volume): #too low in practice
            print("WHALE ENTERED (SELL): price=" + str(price) + " volume=" + str(volume))
            whale_ask_level = self.get_whale_asks(price)
            if whale_ask_level is None:
                whale_ask_level = WhaleLevel(price, volume)
            else:
                whale_ask_level.add_volume(volume)
            self.set_whale_asks(price, whale_ask_level)
            top_whale_price = self.get_whale_ask()
            if self._topSellWhale is None or top_whale_price != self._topSellWhale.get_price():
                self._topSellWhale = self._sellWhales.get(top_whale_price)
                self.topWhaleChangedHandler(self._topSellWhale, "SELL")
            else:
                print("    top SELL whale is still " + str(self._topSellWhale))


    def remove(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }
        price = Decimal(order['price'])
        if order['side'] == 'buy':
            self.checkWhaleRemoveBuy(order)
            bids = self.get_bids(price)
            if bids is not None:
                bids = [o for o in bids if o['id'] != order['id']]
                if len(bids) > 0:
                    self.set_bids(price, bids)
                else:
                    self.remove_bids(price)
        else:
            self.checkWhaleRemoveSell(order)
            asks = self.get_asks(price)
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)


    def checkWhaleRemoveBuy(self, order):
        price = Decimal(order['price'])
        volume = Decimal(order['size'])
        whale_bid_level = self.get_whale_bids(price)
        if whale_bid_level is not None:
            whale_bid_level.remove_volume(volume)
            print("WHALE LEFT (BUY): price=" + str(price) + " volume=" + str(volume))
            if not self.isWhale(whale_bid_level.get_volume()):
                self.remove_whale_bids(price)
                top_whale_price = self.get_whale_bid()
                self._topBuyWhale = self._buyWhales.get(top_whale_price)
                self.topWhaleChangedHandler(self._topBuyWhale, "BUY")
            else:
                print("    top BUY whale is still " + str(self._topBuyWhale))


    def checkWhaleRemoveSell(self, order):
        price = Decimal(order['price'])
        volume = Decimal(order['size'])
        whale_ask_level = self.get_whale_asks(price)
        if whale_ask_level is not None:
            whale_ask_level.remove_volume(volume)
            print("WHALE ENTERED (SELL): price=" + str(price) + " volume=" + str(volume))
            if not self.isWhale(whale_ask_level.get_volume()):
                self.remove_whale_asks(price)
                top_whale_price = self.get_whale_ask()
                self._topSellWhale = self._sellWhales.get(top_whale_price)
                self.topWhaleChangedHandler(self._topSellWhale, "SELL")
            else:
                print("    top SELL whale is still " + str(self._topSellWhale))


    def match(self, order):
        size = Decimal(order['size'])
        price = Decimal(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if not bids:
                return
            assert bids[0]['id'] == order['maker_order_id']
            if bids[0]['size'] == size:
                self.set_bids(price, bids[1:])
            else:
                bids[0]['size'] -= size
                self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if not asks:
                return
            assert asks[0]['id'] == order['maker_order_id']
            if asks[0]['size'] == size:
                self.set_asks(price, asks[1:])
            else:
                asks[0]['size'] -= size
                self.set_asks(price, asks)


    def change(self, order):
        price = Decimal(order['price'])
        new_volume = Decimal(order['new_size'])
        if order['side'] == 'buy':
            self.checkWhaleChangeBuy(order)
            bids = self.get_bids(price)
            if bids is None or not any(o['id'] == order['order_id'] for o in bids):
                return
            index = map(itemgetter('id'), bids).index(order['order_id'])
            bids[index]['size'] = new_size
            self.set_bids(price, bids)
        else:
            self.checkWhaleChangeSell(order)
            asks = self.get_asks(price)
            if asks is None or not any(o['id'] == order['order_id'] for o in asks):
                return
            index = map(itemgetter('id'), asks).index(order['order_id'])
            asks[index]['size'] = new_size
            self.set_asks(price, asks)

        tree = self._asks if order['side'] == 'sell' else self._bids
        node = tree.get(price)

        if node is None or not any(o['id'] == order['order_id'] for o in node):
            return


    def checkWhaleChangeBuy(self, order):
        price = Decimal(order['price'])
        new_volume = order['new_size']
        whale_bid_level = self.get_whale_bids(price)
        if not whale_bid_level == None:
            print("BUY WHALE CHANGED: price=" + price + " new_volume=" + new_volume)
            whale_bid_level.setVolume(new_volume)
            self.set_whale_bids(price, whale_bid_level)


    def checkWhaleChangeSell(self, order):
        price = Decimal(order['price'])
        new_volume = order['new_size']
        whale_ask_level = self.get_whale_asks(price)
        if not whale_ask_level == None:
            print("SELL WHALE CHANGED: price=" + price + " new_volume=" + new_volume)
            whale_ask_level.setVolume(new_volume)
            self.set_whale_asks(price, whale_ask_level)


    def get_current_book(self):
        result = {
            'sequence': self._sequence,
            'asks': [],
            'bids': [],
        }
        for ask in self._asks:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                thisAsk = self._asks[ask]
            except KeyError:
                continue
            for order in thisAsk:
                result['asks'].append([
                    order['price'],
                    order['size'],
                    order['id'],
                ])
        for bid in self._bids:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                thisBid = self._bids[bid]
            except KeyError:
                continue

            for order in thisBid:
                result['bids'].append([
                    order['price'],
                    order['size'],
                    order['id'],
                ])
        return result


    def isWhale(self, aVolume):
        return aVolume >= self._threshold

    #For general book management purposes
    def get_ask(self):
        return self._asks.min_key()


    def get_asks(self, price):
        return self._asks.get(price)


    def remove_asks(self, price):
        self._asks.remove(price)


    def set_asks(self, price, asks):
        self._asks.insert(price, asks)


    def get_bid(self):
        return self._bids.max_key()


    def get_bids(self, price):
        return self._bids.get(price)


    def remove_bids(self, price):
        self._bids.remove(price)


    def set_bids(self, price, bids):
        self._bids.insert(price, bids)


    #For whale book management purposes

    def get_whale_ask(self):
        return self._sellWhales.min_key()


    def get_whale_asks(self, price):
        return self._sellWhales.get(price)


    def remove_whale_asks(self, price):
        self._sellWhales.remove(price)


    def set_whale_asks(self, price, asks):
        self._sellWhales.insert(price, asks)


    def get_whale_bid(self):
        return self._buyWhales.max_key()

    def get_whale_bids(self, price):
        return self._buyWhales.get(price)


    def remove_whale_bids(self, price):
        self._buyWhales.remove(price)


    def set_whale_bids(self, price, bids):
        self._buyWhales.insert(price, bids)
class WhaleTracker():

    current_milli_time = lambda self: int(time.time() * 1000)

    def __init__(self, ticker):
        self._ticker = ticker
        self._bid_whales = RBTree()
        self._ask_whales = RBTree()

    def whaleEnteredMarketHandler(self, order):
        if order['side'] == 'buy':
            self.addBidWhale(order)
        else:
            self.addAskWhale(order)

    def whaleExitedMarketHandler(self, order):
        if order['side'] == 'buy':
            self.removeBidWhale(order)
        else:
            self.removeAskWhale(order)

    def whaleChangedHandler(self, order):
        if order['side'] == 'buy':
            self.changeBidWhale(order)
        else:
            self.changeAskWhale(order)

    def addBidWhale(self, order):
        ID = order['id']
        price = Decimal(order['price'])
        volume = Decimal(order['size'])
        bid_whale_order = self.get_bid_whale(price)
        if bid_whale_order is None or volume > bid_whale_order.get_volume():
            #Logging.logger.info("NEW WHALE ENTERED (BID): price=" + str(price) + " volume=" + str(volume))
            bid_whale_order = WhaleOrder(ID, price, volume)
            self.set_bid_whale(price, bid_whale_order)

    def addAskWhale(self, order):
        ID = order['id']
        price = Decimal(order['price'])
        volume = Decimal(order['size'])
        ask_whale_order = self.get_ask_whale(price)
        if ask_whale_order is None or volume > ask_whale_order.get_volume():
            #Logging.logger.info("NEW WHALE ENTERED (ASK): price=" + str(price) + " volume=" + str(volume))
            ask_whale_order = WhaleOrder(ID, price, volume)
            self.set_ask_whale(price, ask_whale_order)

    def removeBidWhale(self, order):
        ID = order['id']
        price = Decimal(order['price'])
        volume = Decimal(order['size'])
        bid_whale_order = self.get_bid_whale(price)
        if bid_whale_order is not None and bid_whale_order.get_id() == ID:
            #Logging.logger.info("WHALE LEFT (BID): price=" + str(price) + " volume=" + str(volume))
            self.remove_bid_whale(price)

    def removeAskWhale(self, order):
        ID = order['id']
        price = Decimal(order['price'])
        volume = Decimal(order['size'])
        ask_whale_order = self.get_ask_whale(price)
        if ask_whale_order is not None and ask_whale_order.get_id() == ID:
            #Logging.logger.info("WHALE LEFT (ASK): price=" + str(price) + " volume=" + str(volume))
            self.remove_ask_whale(price)

    def changeBidWhale(self, order):
        ID = order['id']
        price = Decimal(order['price'])
        new_volume = order['new_size']
        bid_whale_order = self.get_bid_whale(price)
        if not bid_whale_order == None:
            #Logger.logging.info("WHALE CHANGED (BID): price=" + price + " new_volume=" + new_volume)
            if self.isWhale(new_volume):
                bid_whale_order.setVolume(new_volume)
                self.set_bid_whale(price, bid_whale_order)
            else:
                self.removeBidWhale(order)

    def changeAskWhale(self, order):
        ID = order['id']
        price = Decimal(order['price'])
        new_volume = order['new_size']
        ask_whale_order = self.get_ask_whale(price)
        if not ask_whale_order == None:
            #Logger.logging.info("WHALE CHANGED (ASK): price=" + price + " new_volume=" + new_volume)
            if self.isWhale(new_volume):
                ask_whale_order.setVolume(new_volume)
                self.set_ask_whale(price, ask_whale_order)
            else:
                self.removeAskWhale(order)

    def get_current_whales(
        self,
        num_whales=30
    ):  #by default, fetch only 30 whales off the book either way
        whales = []

        for index, bid_whale in self._bid_whales.items(True):
            if index == num_whales:
                break
            else:
                whales.append({
                    'price': Decimal128(bid_whale.get_price()),
                    'volume': Decimal128(bid_whale.get_volume()),
                    'id': bid_whale.get_id(),
                })

        for index, ask_whale in self._ask_whales.items():
            if index == num_whales:
                break
            else:
                whales.append({
                    'price': Decimal128(ask_whale.get_price()),
                    'volume': Decimal128(ask_whale.get_volume()),
                    'id': ask_whale.get_id(),
                })

        whales_frame = pd.DataFrame(whales)
        return whales_frame

    def get_top_bid_whale(self):
        if not self._bid_whales.is_empty():
            top_bid = self._bid_whales.max_key()
            return self.get_bid_whale(top_bid)
        else:
            return None

    def get_bid_whale(self, price):
        return self._bid_whales.get(price)

    def remove_bid_whale(self, price):
        self._bid_whales.remove(price)

    def set_bid_whale(self, price, whale):
        self._bid_whales.insert(price, whale)

    def get_top_ask_whale(self):
        if not self._ask_whales.is_empty():
            top_ask = self._ask_whales.min_key()
            return self.get_ask_whale(top_ask)
        else:
            return None

    def get_ask_whale(self, price):
        return self._ask_whales.get(price)

    def remove_ask_whale(self, price):
        self._ask_whales.remove(price)

    def set_ask_whale(self, price, whale):
        self._ask_whales.insert(price, whale)
Exemplo n.º 27
0
class OrderBook(WebsocketClient):
    def __init__(self, product_id='BTC-USD', log_to=None):
        super(OrderBook, self).__init__(products=product_id)
        self._asks = RBTree()
        self._bids = RBTree()
        self._client = PublicClient()
        self._sequence = -1
        self._log_to = log_to
        if self._log_to:
            assert hasattr(self._log_to, 'write')
        self._current_ticker = None

    @property
    def product_id(self):
        ''' Currently OrderBook only supports a single product even though it is stored as a list of products. '''
        return self.products[0]

    def on_open(self):
        self._sequence = -1
        print("-- Subscribed to OrderBook! --\n")

    def on_close(self):
        print("\n-- OrderBook Socket Closed! --")

    def reset_book(self):
        self._asks = RBTree()
        self._bids = RBTree()
        res = self._client.get_product_order_book(
            product_id=self.product_id, level=3)
        for bid in res['bids']:
            self.add({
                'id': bid[2],
                'side': 'buy',
                'price': Decimal(bid[0]),
                'size': Decimal(bid[1])
            })
        for ask in res['asks']:
            self.add({
                'id': ask[2],
                'side': 'sell',
                'price': Decimal(ask[0]),
                'size': Decimal(ask[1])
            })
        self._sequence = res['sequence']

    def on_message(self, message):
        if self._log_to:
            pickle.dump(message, self._log_to)

        sequence = message['sequence']
        if self._sequence == -1:
            self.reset_book()
            return
        if sequence <= self._sequence:
            # ignore older messages (e.g. before order book initialization from getProductOrderBook)
            return
        elif sequence > self._sequence + 1:
            self.on_sequence_gap(self._sequence, sequence)
            return

        msg_type = message['type']
        if msg_type == 'open':
            self.add(message)
        elif msg_type == 'done' and 'price' in message:
            self.remove(message)
        elif msg_type == 'match':
            self.match(message)
            self._current_ticker = message
        elif msg_type == 'change':
            self.change(message)

        self._sequence = sequence

    def on_sequence_gap(self, gap_start, gap_end):
        self.reset_book()
        print('Error: messages missing ({} - {}). Re-initializing  book at sequence.'.format(
            gap_start, gap_end, self._sequence))

    def add(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }
        if order['side'] == 'buy':
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = collections.OrderedDict()
            bids[order['id']] = order
            self.set_bids(order['price'], bids)
        else:
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = collections.OrderedDict()
            asks[order['id']] = order
            self.set_asks(order['price'], asks)

    def remove(self, order):
        price = Decimal(order['price'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is not None:
                if order['order_id'] in bids:
                    del bids[order['order_id']]
                if len(bids) > 0:
                    self.set_bids(price, bids)
                else:
                    self.remove_bids(price)
        else:
            asks = self.get_asks(price)
            if asks is not None:
                if order['order_id'] in asks:
                    del asks[order['order_id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)

    def match(self, order):
        size = Decimal(order['size'])
        price = Decimal(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if not bids:
                return
            top_order = bids.items()[0]
            assert top_order[1]['id'] == order['maker_order_id']
            if top_order[1]['size'] == size:
                del bids[order['maker_order_id']]
                self.set_bids(price, bids)
            else:
                top_order[1]['size'] -= size
                self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if not asks:
                return
            top_order = asks.items()[0]
            assert top_order[1]['id'] == order['maker_order_id']
            if top_order[1]['size'] == size:
                del asks[order['maker_order_id']]
                self.set_asks(price, asks)
            else:
                top_order[1]['size'] -= size
                self.set_asks(price, asks)

    def change(self, order):
        try:
            new_size = Decimal(order['new_size'])
        except KeyError:
            return

        try:
            price = Decimal(order['price'])
        except KeyError:
            return

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is None or order['order_id'] not in bids:
                return
            bids[order['order_id']]['size'] = new_size
            self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if asks is None or order['order_id'] not in asks:
                return
            asks[order['order_id']]['size'] = new_size
            self.set_asks(price, asks)

    def get_current_ticker(self):
        return self._current_ticker

    def get_current_book(self):
        result = {
            'sequence': self._sequence,
            'asks': [],
            'bids': [],
        }
        for ask in self._asks:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_ask = self._asks[ask]
            except KeyError:
                continue
            for order in this_ask:
                result['asks'].append(
                    [order['price'], order['size'], order['id']])
        for bid in self._bids:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_bid = self._bids[bid]
            except KeyError:
                continue

            for order in this_bid:
                result['bids'].append(
                    [order['price'], order['size'], order['id']])
        return result

    def get_ask(self):
        return self._asks.min_key()

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        self._asks.insert(price, asks)

    def get_bid(self):
        return self._bids.max_key()

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        self._bids.insert(price, bids)
Exemplo n.º 28
0
class OrderTree(object):
    def __init__(self):
        self.priceTree = RBTree()
        self.priceMap = {}  # Map from price -> orderList object
        self.orderMap = {}  # Order ID to Order object
        self.volume = 0  # How much volume on this side?
        self.nOrders = 0  # How many orders?
        self.lobDepth = 0  # How many different prices on lob?

    def __len__(self):
        return len(self.orderMap)

    def getPrice(self, price):
        return self.priceMap[price]

    def getOrder(self, idNum):
        return self.orderMap[idNum]

    def createPrice(self, price):
        self.lobDepth += 1
        newList = OrderList()
        self.priceTree.insert(price, newList)
        self.priceMap[price] = newList

    def removePrice(self, price):
        self.lobDepth -= 1
        self.priceTree.remove(price)
        del self.priceMap[price]

    def priceExists(self, price):
        return price in self.priceMap

    def orderExists(self, idNum):
        return idNum in self.orderMap

    def insertOrder(self, quote):
        if self.orderExists(quote['idNum']):
            self.removeOrderById(quote['idNum'])
        self.nOrders += 1
        if quote['price'] not in self.priceMap:
            self.createPrice(quote['price'])
        order = Order(quote, self.priceMap[quote['price']])
        self.priceMap[order.price].appendOrder(order)
        self.orderMap[order.idNum] = order
        self.volume += order.qty

    def updateOrder(self, orderUpdate):
        order = self.orderMap[orderUpdate['idNum']]
        originalVolume = order.qty
        if orderUpdate['price'] != order.price:
            # Price changed
            orderList = self.priceMap[order.price]
            orderList.removeOrder(order)
            if len(orderList) == 0:
                self.removePrice(order.price)
            self.insertOrder(orderUpdate)
        else:
            # Quantity changed
            order.updateQty(orderUpdate['qty'], orderUpdate['timestamp'])
        self.volume += order.qty - originalVolume

    def removeOrderById(self, idNum):
        self.nOrders -= 1
        order = self.orderMap[idNum]
        self.volume -= order.qty
        order.orderList.removeOrder(order)
        if len(order.orderList) == 0:
            self.removePrice(order.price)
        del self.orderMap[idNum]

    def maxPrice(self):
        if self.lobDepth > 0:
            return self.priceTree.max_key()
        else:
            return None

    def minPrice(self):
        if self.lobDepth > 0:
            return self.priceTree.min_key()
        else:
            return None

    def maxPriceList(self):
        if self.lobDepth > 0:
            return self.getPrice(self.maxPrice())
        else:
            return None

    def minPriceList(self):
        if self.lobDepth > 0:
            return self.getPrice(self.minPrice())
        else:
            return None
Exemplo n.º 29
0
class OrderBook:
    def __init__(self,
                 API_KEY,
                 API_SECRET,
                 API_PASS,
                 API_URL,
                 WSS_URL,
                 products,
                 M_bidask,
                 auth_user_msg_q,
                 M_get_depth,
                 authclient,
                 message_type="subscribe",
                 auth=True,
                 level=True):
        self.authclient = authclient
        self.WSS_URL = WSS_URL
        self.API_URL = API_URL
        self.products = products
        self.type = message_type
        self.stop = False
        self.ws = None
        self.thread = None
        self.lock = Lock()
        self.auth = auth
        self.API_KEY = API_KEY
        self.API_SECRET = API_SECRET
        self.API_PASS = API_PASS
        self._sequence = -1
        self._bid = None
        self._ask = None
        self._bid_depth = None
        self._ask_depth = None
        self.price = None
        self.level = level
        self.pd_data_set = None
        self.buyop = 0
        self.sellop = 0
        self._bid = None
        self._ask = None
        self.M_bidask = M_bidask
        self.auth_user_msg_q = auth_user_msg_q
        self.M_get_depth = M_get_depth
        self.count01 = 0
        self.manager = Manager()

    def start(self):
        logging.info('start called')
        self.on_open()
        self.process = Process(target=self._go,
                               args=(self.M_bidask, self.M_get_depth))
        self.process.start()

    def _go(self, M_bidask, M_get_depth):
        self.stop = False
        self._sequence = -1
        self.M_get_depth = M_get_depth
        self._connect()
        self._listen(M_bidask)

    def _connect(self):
        logging.info('_connect called')
        if self.WSS_URL[-1] == "/":
            self.WSS_URL = self.WSS_URL[:-1]  # If url ends in a "/" strip it
        sub_params = {'type': 'subscribe', 'product_ids': [self.products]}
        if self.auth:
            timestamp = str(time.time())
            message = timestamp + 'GET' + '/users/self'
            message = message.encode('ascii')
            hmac_key = base64.b64decode(self.API_SECRET)
            signature = hmac.new(hmac_key, message, hashlib.sha256)
            digest = signature.digest()
            signature_b64 = base64.b64encode(digest).decode('utf-8').rstrip(
                '\n')
            sub_params['signature'] = signature_b64
            sub_params['key'] = self.API_KEY
            sub_params['passphrase'] = self.API_PASS
            sub_params['timestamp'] = timestamp
        self.ws = create_connection(self.WSS_URL)
        self.ws.send(json.dumps(sub_params))

        if self.level:
            sub_params = {
                "type": "subscribe",
                "product_ids": [self.products],
                "channels": ["matches"]
            }
            self.ws.send(json.dumps(sub_params))

    def _listen(self, M_bidask):
        self.M_bidask2 = M_bidask  # setting self.p1_w2 within this process so as to not confuse with self.p1_w. they are the came Pipe object
        logging.info('_listen called')
        while not self.stop:
            try:
                msg = self.ws.recv()
            except Exception as e:

                print('exception 1')
                gv.logger.exception(e)
                gv.logger.info('sleeping for 5 seconds and running _connect()')
                self.close()
                time.sleep(5)
                self._go(self.M_bidask, self.M_get_depth)
            else:
                if msg:
                    msg = json.loads(msg)
                    self.on_message(msg)

    def on_message(self, message):
        try:
            if 'user_id' in message:
                self.auth_user_msg_q.put(message)
            if 'sequence' not in message:
                return
            sequence = message['sequence']
            if self._sequence == -1:
                self._asks = RBTree()
                self._bids = RBTree()
                res = self.get_product_order_book(level=3)
                for bid in res['bids']:
                    self.add({
                        'id': bid[2],
                        'side': 'buy',
                        'price': Decimal(bid[0]),
                        'size': Decimal(bid[1])
                    })
                for ask in res['asks']:
                    self.add({
                        'id': ask[2],
                        'side': 'sell',
                        'price': Decimal(ask[0]),
                        'size': Decimal(ask[1])
                    })
                self._sequence = res['sequence']

            if sequence <= self._sequence:
                # ignore older messages (e.g. before order book initialization from getProductOrderBook)
                return
            elif sequence > self._sequence + 1:
                print(
                    'Error: messages missing ({} - {}). Re-initializing websocket.'
                    .format(sequence, self._sequence))
                self.close()
                time.sleep(5)
                self._go(self.M_bidask, self.M_get_depth)
                return

            msg_type = message['type']
            if msg_type == 'open':
                self.add(message)
            elif msg_type == 'done' and 'price' in message:
                self.remove(message)
            elif msg_type == 'match':
                self.match(message)
            elif msg_type == 'change':
                self.change(message)
            self._sequence = sequence

            bid = self.get_bid(
            )  # Update Multiprocessing.Value variables with the new bid and ask
            ask = self.get_ask()
            if self.M_get_depth.value == 'bid':
                bids = self.get_bids(bid)
                bid_depth = sum([b['size'] for b in bids])
                self.M_get_depth.value = bid_depth
            if self.M_get_depth.value == 'ask':
                asks = self.get_asks(ask)
                ask_depth = sum([a['size'] for a in asks])
                self.M_get_depth.value = ask_depth

            if not self._bid == bid or not self._ask == ask:
                # lock these vars
                self.M_bidask2['bid'] = float(bid)
                self.M_bidask2['ask'] = float(ask)
                self._bid = bid
                self._ask = ask

        except Exception as e:
            print(e)
            print('exception 2')
            gv.logger.exception(e)
            self.close()
            time.sleep(5)
            self._go(self.M_bidask, self.M_get_depth)

        # bids = self.get_bids(bid)
        # bid_depth = sum([b['size'] for b in bids])
        # asks = self.get_asks(ask)
        # ask_depth = sum([a['size'] for a in asks])
        # print('bid: %f @ %f - ask: %f @ %f' % (bid_depth, bid, ask_depth, ask))

    def on_error(self, e):
        self._sequence.value = -1
        print('on_error called')
        self.close()
        self._go(self.M_bidask, self.M_get_depth)

    def close(self):
        logging.info('close called')
        if not self.stop:

            print('in not self.stop')
            self.on_close()
            self.stop = True
            #self.process.join()
            try:
                if self.ws:
                    print('if self.ws')
                    self.ws.close()
            except WebSocketConnectionClosedException as e:
                gv.logger.debug(e)

    def add(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }
        if order['side'] == 'buy':
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['price'], bids)
        else:
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['price'], asks)

    def remove(
        self, order
    ):  # order is the instance of one message receded from the websockit
        price = Decimal(
            order['price']
        )  # get the price from the order and saves it as a decimal in variable price
        if order['side'] == 'buy':  # if its a buy order continue
            bids = self.get_bids(
                price
            )  # returns all the bids at specified (price) from orderbook, geting ready to compare with current order.
            if bids is not None:  # bids being None does not happen often
                bidslist = [
                    o for o in bids if o['id'] != order['order_id']
                ]  # creates a list minus the current order to be removed
                if len(
                        bidslist
                ) > 0:  # when bidslist still contains items in its list send list and price to set_bids
                    self.set_bids(price, bidslist)
                else:
                    self.remove_bids(
                        price
                    )  # when bidslist returns nothing in its list send price to remove_bids(price) to be removed from orderbook.
        else:
            asks = self.get_asks(
                price
            )  # same as the first part of this function except on the ask side
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['order_id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)

                    # An order was matched so it needs to be removed from our orderbook \
                    # and keep the remaining
    def match(
        self, order
    ):  # order is the instance of one message receded from the websockit
        size = Decimal(order['size'])  # order size how many ETH or BTC etc..
        price = Decimal(order['price'])
        if order['side'] == 'buy':  # if the order is a buy order \
            bids = self.get_bids(
                price
            )  # returns all the bids at specified (price) from orderbook \
            if not bids:  # if there are no orders at this price on the bid/buy side than just return
                return
            assert bids[0]['id'] == order[
                'maker_order_id']  # if condition returns False trigger an error
            # this is taking the trade id of the order on our orderbook and comparing \
            # it to the market makers order id to varify the data.
            # this should always return True if it returns false are market data \
            # we have saved is incorrect
            if bids[0][
                    'size'] == size:  # The first order on our books at this pirce is bids[0] and if the size \
                # of that order matches the incoming match order then we need to remove \
                # the order from our books at this price
                # we use bids[0] because the market works on a first come first serve \
                # and bids[0] has been here the longest
                self.set_bids(
                    price, bids[1:]
                )  # excludes the first order in the list and sets the remaining \
                # back in the orderbook list
            else:
                bids[0][
                    'size'] -= size  # subtract size of current trade from the position held on the orderbook "bids[0]"
                self.set_bids(price, bids)
        else:  # same as above for the match function but on the ask/sell side
            asks = self.get_asks(price)
            if not asks:
                return
            assert asks[0]['id'] == order['maker_order_id']
            if asks[0]['size'] == size:
                self.set_asks(price, asks[1:])
            else:
                asks[0]['size'] -= size
                self.set_asks(price, asks)

    def change(self, order):
        try:
            new_size = Decimal(order['new_size'])
        except KeyError as e:
            gv.logger.debug(e)
            return
        price = Decimal(order['price'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is None or not any(o['id'] == order['order_id']
                                       for o in bids):
                return
            index = [b['id'] for b in bids].index(order['order_id'])
            bids[index]['size'] = new_size
            self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if asks is None or not any(o['id'] == order['order_id']
                                       for o in asks):
                return
            index = [a['id'] for a in asks].index(order['order_id'])
            asks[index]['size'] = new_size
            self.set_asks(price, asks)
        tree = self._asks if order['side'] == 'sell' else self._bids
        node = tree.get(price)
        if node is None or not any(o['id'] == order['order_id'] for o in node):
            return

    def get_ask(self):
        return self._asks.min_key()

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        self._asks.insert(price, asks)

    def get_bid(self):
        return self._bids.max_key()

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        self._bids.insert(price, bids)

    # _bids is the is the binary data structure of the entire buy side of the overbook
    # this function is returning every order from the buy side with a specific price
    # get is a funcntion of an RBTree data structure

    def get_product_order_book(self, level):
        params = {'level': level}
        r = requests.get(self.API_URL +
                         '/products/{}/book'.format(self.products),
                         params=params,
                         timeout=30)
        return r.json()

    def on_open(self):
        print("-- Subscribed! --\n")

    def on_close(self):
        print("\n-- Socket Closed --")

    def bid_ask_spread(self):
        lock.acquire()
        try:
            bid = self.get_bid()
            ask = self.get_ask()
            print((bid - ask) * -1)
        except Exception as e:
            gv.logger.debug(e)
        finally:
            lock.release()

    #def bid_ask(self):
    #    try:
    #        bid = self.get_bid()
    #        bids = self.get_bids(bid)
    #        ask = self.get_ask()
    #        asks = self.get_asks(ask)
#
#        bid_depth = sum([b['size'] for b in bids])              # If there are no changes to the bid-ask spread \
#        ask_depth = sum([a['size'] for a in asks])              # since the last update, no need to print
#
#                                                                # if we end up logging a type error we may need to \
#                                                                # lock the rbtree(_bid and _ask)
#        if gv._bid == bid and gv._ask == ask and self._bid_depth == bid_depth and self._ask_depth == ask_depth:
#            pass
#        else:                                                   # If there are differences, update the cache
#            gv._bid = bid
#            gv._ask = ask
#            self._bid_depth = bid_depth
#            self._ask_depth = ask_depth
#            print('{}\tbid: {:.3f} @ {:.2f}\task: {:.3f} @ {:.2f}'.format(dt.datetime.now(), bid_depth, bid, ask_depth, ask))
#    except Exception as e:
#        gv.logger.debug(e)

    def user_orders_on_book(self):
        self.lock.acquire()
        try:
            print('user orders on book {}'.format(gv.transactions_onbook))
        finally:
            self.lock.release()

    def test2(self):
        return 'test'

    def market_price(self):
        self.lock.acquire()
        try:
            if gv.market_price is not None:
                print('match {}'.format(gv.market_price))
                pass

        finally:
            self.lock.release()
Exemplo n.º 30
0
class OrderTree(object):
    '''A red-black tree used to store OrderLists in price order

    The exchange will be using the OrderTree to hold bid and ask data (one OrderTree for each side).
    Keeping the information in a red black tree makes it easier/faster to detect a match.
    '''

    def __init__(self):
        self.price_tree = RBTree()
        self.price_map = {} # Dictionary containing price : OrderList object
        self.order_map = {} # Dictionary containing order_id : Order object
        self.volume = 0 # Contains total quantity from all Orders in tree
        self.num_orders = 0 # Contains count of Orders in tree
        self.depth = 0 # Number of different prices in tree (http://en.wikipedia.org/wiki/Order_book_(trading)#Book_depth)

    def __len__(self):
        return len(self.order_map)

    def get_price_list(self, price):
        return self.price_map[price]

    def get_order(self, order_id):
        return self.order_map[order_id]

    def create_price(self, price):
        self.depth += 1 # Add a price depth level to the tree
        new_list = OrderList()
        self.price_tree.insert(price, new_list) # Insert a new price into the tree
        self.price_map[price] = new_list # Can i just get this by using self.price_tree.get_value(price)? Maybe this is faster though.

    def remove_price(self, price):
        self.depth -= 1 # Remove a price depth level
        self.price_tree.remove(price)
        del self.price_map[price]

    def price_exists(self, price):
        return price in self.price_map

    def order_exists(self, order):
        return order in self.order_map

    def insert_order(self, quote):
        print quote
        if self.order_exists(quote['order_id']):
            self.remove_order_by_id(quote['order_id'])
        self.num_orders += 1
        if quote['price'] not in self.price_map:
            self.create_price(quote['price']) # If price not in Price Map, create a node in RBtree
        order = Order(quote, self.price_map[quote['price']]) # Create an order
        self.price_map[order.price].append_order(order) # Add the order to the OrderList in Price Map
        self.order_map[order.order_id] = order
        self.volume += order.quantity

    def update_order(self, order_update):
        order = self.order_map[order_update['order_id']]
        original_quantity = order.quantity
        if order_update['price'] != order.price:
            # Price changed. Remove order and update tree.
            order_list = self.price_map[order.price]
            order_list.remove_order(order)
            if len(order_list) == 0: # If there is nothing else in the OrderList, remove the price from RBtree
                self.remove_price(order.price)
            self.insert_order(order_update)
        else:
            # Quantity changed. Price is the same.
            order.update_quantity(order_update['quantity'], order_update['timestamp'])
        self.volume += order.quantity - original_quantity

    def remove_order_by_id(self, order_id):
        self.num_orders -= 1
        order = self.order_map[order_id]
        self.volume -= order.quantity
        order.order_list.remove_order(order)
        if len(order.order_list) == 0:
            self.remove_price(order.price)
        del self.order_map[order_id]

    def max_price(self):
        if self.depth > 0:
            return self.price_tree.max_key()
        else:
            return None

    def min_price(self):
        if self.depth > 0:
            return self.price_tree.min_key()
        else:
            return None

    def max_price_list(self):
        if self.depth > 0:
            return self.get_price_list(self.max_price())
        else:
            return None

    def min_price_list(self):
        if self.depth > 0:
            return self.get_price_list(self.min_price())
        else:
            return None
Exemplo n.º 31
0
1. Simple memory management - simplifies error handling in concurrent code, less I/O hits, 
2. Consistent performance because rehashing (expanding the hash table's array) happens on some insertions.
   This is important in real-time systems where you want to provide bounds on how long each operation takes
3. Keys are sorted
'''
#https://bitbucket.org/mozman/bintrees
from bintrees import RBTree
def PrintUnique2(k,v):
    if (v == 1): #unique
        print(k) #print unique
#Initialize
tree = RBTree()        
#Insert
for x in input:
    if (tree.get(x)): #duplicate
         tree.insert(x,tree.get(x)+1) #increment occurrence
    else: #new
        tree.insert(x,1)
#Iterate        
tree.foreach(PrintUnique2,0)  #0=inorder



#Solution 3
'''
REFERENCE:
http://www.geeksforgeeks.org/find-duplicates-in-on-time-and-constant-extra-space/

LIMITATIONS:
1. n elements with value > 0 and < n
2. Logic needs to be modified if 0 is one of the values
Exemplo n.º 32
0
        random.shuffle(next_orders)
        for order in next_orders:
            x_traded = [initial_price]
            y_traded = [0]
            order_side = order['side'] if order['type'] == 'market' else ''
            # market_order = order['side'] if order['type'] == 'market' else ''

            trades, order = lob.processOrder(order, False, False)
            total_orders += 1

            if order:
                # order was created or not fully executed
                idNum = order['idNum']
                order['time_limit'] = t + np.random.geometric(
                    float(df[df['price'] == order['price']]['gamma']))
                limit_orders.insert(idNum, order)
                born_and_dead_history[idNum] = {
                    'idNum': idNum,
                    'born': t,
                    'dead': order['time_limit']
                }

            if trades:
                for trade in trades:
                    idNum = trade['party1'][
                        2]  # trade['party1'] = [tid, side, idNum]
                    born_and_dead_history[idNum]['dead'] = t
                    x_traded.append(trade['price'])
                    y_traded.append(trade['qty'])
                total_trades += 1
            total_traded = add_trades(total_traded, trades)
Exemplo n.º 33
0
class OrderTree(object):
    ''' An RBtree used to store OrderLists in price order '''

    def __init__(self):
        '''
        @summary: initialization of Ordertree class
        @mem price_tree: a red-black tree
        @mem price_map: Dictionary containing price : OrderList object
        @mem order_map: Dictionary containing order_id : Order object
        @mem volume: Contains total quantity from all Orders in tree
        @mem num_orders: Contains count of Orders in tree
        @mem depth: Number of different prices in tree (http://en.wikipedia.org/wiki/Order_book_(trading)#Book_depth)
        '''
        self.price_tree = RBTree()
        self.price_map = {} 
        self.order_map = {} 
        self.volume = 0
        self.num_orders = 0 
        self.depth = 0 

    def __len__(self):
        ''' return the length of order_map '''
        return len(self.order_map)

    def get_price_list(self, price):
        ''' get the price list from the price map '''
        return self.price_map[price]

    def get_order(self, order_id):
        ''' get order list from order map '''
        return self.order_map[order_id]

    def create_price(self, price):
        ''' create a new price if adding an order without price in the tree '''
        self.depth += 1
        new_list = OrderList()
        self.price_tree.insert(price, new_list) 
        self.price_map[price] = new_list 

    def remove_price(self, price):
        ''' remove a price from the tree '''
        self.depth -= 1 
        self.price_tree.remove(price)
        del self.price_map[price]

    def price_exists(self, price):
        ''' check whether price exists in price map '''
        return price in self.price_map

    def order_exists(self, order):
        ''' check whether an order exists in order map '''
        return order in self.order_map

    def insert_order(self, quote):
        ''' insert an order into the order map '''
        if self.order_exists(quote['order_id']):
            self.remove_order_by_id(quote['order_id'])
        self.num_orders += 1
        if quote['price'] not in self.price_map:
            self.create_price(quote['price']) # If price not in Price Map, create a node in RBtree
        order = Order(quote, self.price_map[quote['price']]) # Create an order
        self.price_map[order.price].append_order(order) # Add the order to the OrderList in Price Map
        self.order_map[order.order_id] = order
        self.volume += order.quantity

    def remove_order_by_id(self, order_id):
        ''' remove an order from the order map '''
        self.num_orders -= 1
        order = self.order_map[order_id]
        self.volume -= order.quantity
        order.order_list.remove_order(order)
        if len(order.order_list) == 0:
            self.remove_price(order.price)
        del self.order_map[order_id]

    def max_price(self):
        ''' return max price in price tree '''
        if self.depth > 0:
            return self.price_tree.max_key()
        else:
            return None

    def min_price(self):
        ''' return min price in price tree '''
        if self.depth > 0:
            return self.price_tree.min_key()
        else:
            return None

    def max_price_list(self):
        ''' return max price in price tree '''
        if self.depth > 0:
            return self.get_price_list(self.max_price())
        else:
            return None

    def min_price_list(self):
        ''' return min price in price tree '''
        if self.depth > 0:
            return self.get_price_list(self.min_price())
        else:
            return None
Exemplo n.º 34
0
class OrderBook(WebsocketClient):
    def __init__(self, product_id='BTC-USD', log_to=None):
        super(OrderBook, self).__init__(products=product_id)
        self._asks = RBTree()
        self._bids = RBTree()
        self._client = PublicClient()
        self._sequence = -1
        self._log_to = log_to
        if self._log_to:
            assert hasattr(self._log_to, 'write')
        self._current_ticker = None

    @property
    def product_id(self):
        ''' Currently OrderBook only supports a single product even though it is stored as a list of products. '''
        return self.products[0]

    def on_message(self, message):
        if self._log_to:
            pickle.dump(message, self._log_to)

        sequence = message['sequence']
        if self._sequence == -1:
            self._asks = RBTree()
            self._bids = RBTree()
            res = self._client.get_product_order_book(product_id=self.product_id, level=3)
            for bid in res['bids']:
                self.add({
                    'id': bid[2],
                    'side': 'buy',
                    'price': Decimal(bid[0]),
                    'size': Decimal(bid[1])
                })
            for ask in res['asks']:
                self.add({
                    'id': ask[2],
                    'side': 'sell',
                    'price': Decimal(ask[0]),
                    'size': Decimal(ask[1])
                })
            self._sequence = res['sequence']

        if sequence <= self._sequence:
            # ignore older messages (e.g. before order book initialization from getProductOrderBook)
            return
        elif sequence > self._sequence + 1:
            print('Error: messages missing ({} - {}). Re-initializing websocket.'.format(sequence, self._sequence))
            self.close()
            self.start()
            return

        msg_type = message['type']
        if msg_type == 'open':
            self.add(message)
        elif msg_type == 'done' and 'price' in message:
            self.remove(message)
        elif msg_type == 'match':
            self.match(message)
            self._current_ticker = message
        elif msg_type == 'change':
            self.change(message)

        self._sequence = sequence

        # bid = self.get_bid()
        # bids = self.get_bids(bid)
        # bid_depth = sum([b['size'] for b in bids])
        # ask = self.get_ask()
        # asks = self.get_asks(ask)
        # ask_depth = sum([a['size'] for a in asks])
        # print('bid: %f @ %f - ask: %f @ %f' % (bid_depth, bid, ask_depth, ask))

    def on_error(self, e):
        self._sequence = -1
        self.close()
        self.start()

    def add(self, order):
        order = {
            'id': order.get('order_id') or order['id'],
            'side': order['side'],
            'price': Decimal(order['price']),
            'size': Decimal(order.get('size') or order['remaining_size'])
        }
        if order['side'] == 'buy':
            bids = self.get_bids(order['price'])
            if bids is None:
                bids = [order]
            else:
                bids.append(order)
            self.set_bids(order['price'], bids)
        else:
            asks = self.get_asks(order['price'])
            if asks is None:
                asks = [order]
            else:
                asks.append(order)
            self.set_asks(order['price'], asks)

    def remove(self, order):
        price = Decimal(order['price'])
        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is not None:
                bids = [o for o in bids if o['id'] != order['order_id']]
                if len(bids) > 0:
                    self.set_bids(price, bids)
                else:
                    self.remove_bids(price)
        else:
            asks = self.get_asks(price)
            if asks is not None:
                asks = [o for o in asks if o['id'] != order['order_id']]
                if len(asks) > 0:
                    self.set_asks(price, asks)
                else:
                    self.remove_asks(price)

    def match(self, order):
        size = Decimal(order['size'])
        price = Decimal(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if not bids:
                return
            assert bids[0]['id'] == order['maker_order_id']
            if bids[0]['size'] == size:
                self.set_bids(price, bids[1:])
            else:
                bids[0]['size'] -= size
                self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if not asks:
                return
            assert asks[0]['id'] == order['maker_order_id']
            if asks[0]['size'] == size:
                self.set_asks(price, asks[1:])
            else:
                asks[0]['size'] -= size
                self.set_asks(price, asks)

    def change(self, order):
        try:
            new_size = Decimal(order['new_size'])
        except KeyError:
            return
            
        price = Decimal(order['price'])

        if order['side'] == 'buy':
            bids = self.get_bids(price)
            if bids is None or not any(o['id'] == order['order_id'] for o in bids):
                return
            index = [b['id'] for b in bids].index(order['order_id'])
            bids[index]['size'] = new_size
            self.set_bids(price, bids)
        else:
            asks = self.get_asks(price)
            if asks is None or not any(o['id'] == order['order_id'] for o in asks):
                return
            index = [a['id'] for a in asks].index(order['order_id'])
            asks[index]['size'] = new_size
            self.set_asks(price, asks)

        tree = self._asks if order['side'] == 'sell' else self._bids
        node = tree.get(price)

        if node is None or not any(o['id'] == order['order_id'] for o in node):
            return

    def get_current_ticker(self):
        return self._current_ticker

    def get_current_book(self):
        result = {
            'sequence': self._sequence,
            'asks': [],
            'bids': [],
        }
        for ask in self._asks:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_ask = self._asks[ask]
            except KeyError:
                continue
            for order in this_ask:
                result['asks'].append([order['price'], order['size'], order['id']])
        for bid in self._bids:
            try:
                # There can be a race condition here, where a price point is removed
                # between these two ops
                this_bid = self._bids[bid]
            except KeyError:
                continue

            for order in this_bid:
                result['bids'].append([order['price'], order['size'], order['id']])
        return result

    def get_ask(self):
        return self._asks.min_key()

    def get_asks(self, price):
        return self._asks.get(price)

    def remove_asks(self, price):
        self._asks.remove(price)

    def set_asks(self, price, asks):
        self._asks.insert(price, asks)

    def get_bid(self):
        return self._bids.max_key()

    def get_bids(self, price):
        return self._bids.get(price)

    def remove_bids(self, price):
        self._bids.remove(price)

    def set_bids(self, price, bids):
        self._bids.insert(price, bids)
class OrderTree(object):
    """
    A red-black tree used to store OrderLists in price order

    The exchange will be using the OrderTree to hold bid and ask data (one OrderTree for each side).
    Keeping the information in a red black tree makes it easier/faster to detect a match.
    """
    def __init__(self):
        self.price_tree = RBTree()
        self.price_map = {}  # Dictionary containing price : OrderList object
        self.order_map = {}  # Dictionary containing order_id : Order object
        self.volume = 0  # Contains total quantity from all Orders in tree
        self.num_orders = 0  # Contains count of Orders in tree
        self.depth = 0  # Number of different prices in tree

    def __len__(self):
        return len(self.order_map)

    def get_price_list(self, price):
        return self.price_map[price]

    def get_order(self, order_id):
        return self.order_map[order_id]

    def create_price(self, price):
        # Add a price depth level to the tree
        self.depth += 1
        new_list = OrderList()
        # Insert a new price into the tree
        self.price_tree.insert(price, new_list)
        self.price_map[price] = new_list

    def remove_price(self, price):
        self.depth -= 1  # Remove a price depth level
        self.price_tree.remove(price)
        del self.price_map[price]

    def price_exists(self, price):
        return price in self.price_map

    def order_exists(self, order):
        return order in self.order_map

    def insert_order(self, quote):
        if self.order_exists(quote['order_id']):
            self.remove_order_by_id(quote['order_id'])
        self.num_orders += 1
        # If price not in Price Map, create a node in RBtree
        # Create an order
        # Add the order to the OrderList in Price Map
        if quote['price'] not in self.price_map:
            self.create_price(quote['price'])
        order = Order(quote, self.price_map[quote['price']])
        self.price_map[order.price].append_order(order)
        self.order_map[order.order_id] = order
        self.volume += order.quantity

    def update_order(self, order_update):
        order = self.order_map[order_update['order_id']]
        original_quantity = order.quantity
        if order_update['price'] != order.price:
            # Price changed. Remove order and update tree.
            order_list = self.price_map[order.price]
            order_list.remove_order(order)
            # If there is nothing else in the OrderList, remove the price from RBtree
            if len(order_list) == 0:
                self.remove_price(order.price)
            self.insert_order(order_update)
        else:
            # Quantity changed. Price is the same.
            order.update_quantity(order_update['quantity'],
                                  order_update['timestamp'])
        self.volume += order.quantity - original_quantity

    def remove_order_by_id(self, order_id):
        self.num_orders -= 1
        order = self.order_map[order_id]
        self.volume -= order.quantity
        order.order_list.remove_order(order)
        if len(order.order_list) == 0:
            self.remove_price(order.price)
        del self.order_map[order_id]

    def max_price(self):
        if self.depth > 0:
            return self.price_tree.max_key()
        else:
            return None

    def min_price(self):
        if self.depth > 0:
            return self.price_tree.min_key()
        else:
            return None

    def max_price_list(self):
        if self.depth > 0:
            return self.get_price_list(self.max_price())
        else:
            return None

    def min_price_list(self):
        if self.depth > 0:
            return self.get_price_list(self.min_price())
        else:
            return None