Giter Club home page Giter Club logo

orderbook's Introduction

Orderbook

License Python PyPi coverage-lines coverage-functions

A fast L2/L3 orderbook data structure, in C, for Python

Basic Usage

from decimal import Decimal

import requests
from order_book import OrderBook

ob = OrderBook()

# get some orderbook data
data = requests.get("https://api.pro.coinbase.com/products/BTC-USD/book?level=2").json()

ob.bids = {Decimal(price): size for price, size, _ in data['bids']}
ob.asks = {Decimal(price): size for price, size, _ in data['asks']}

# OR

for side in data:
    # there is additional data we need to ignore
    if side in {'bids', 'asks'}:
        ob[side] = {Decimal(price): size for price, size, _ in data[side]}


# Data is accessible by .index(), which returns a tuple of (price, size) at that level in the book
price, size = ob.bids.index(0)
print(f"Best bid price: {price} size: {size}")

price, size = ob.asks.index(0)
print(f"Best ask price: {price} size: {size}")

print(f"The spread is {ob.asks.index(0)[0] - ob.bids.index(0)[0]}\n\n")

# Data is accessible via iteration
# Note: bids/asks are iterators

print("Bids")
for price in ob.bids:
    print(f"Price: {price} Size: {ob.bids[price]}")


print("\n\nAsks")
for price in ob.asks:
    print(f"Price: {price} Size: {ob.asks[price]}")


# Data can be exported to a sorted dictionary
# In Python3.7+ dictionaries remain in insertion ordering. The
# dict returned by .to_dict() has had its keys inserted in sorted order
print("\n\nRaw asks dictionary")
print(ob.asks.to_dict())


# Data can also be exported as an ordered list
# .to_list() returns a list of (price, size) tuples
print("Top 5 Asks")
print(ob.asks.to_list()[:5])
print("\nTop 5 Bids")
print(ob.bids.to_list()[:5])

Main Features

  • Sides maintained in correct order
  • Can perform orderbook checksums
  • Supports max depth and depth truncation

Installation

The preferable way to install is via pip - pip install order-book. Installing from source will require a compiler and can be done with setuptools: python setup.py install.

Running code coverage

The script coverage.sh will compile the source using the -coverage CFLAG, run the unit tests, and build a coverage report in HTML. The script uses tools that may need to be installed (coverage, lcov, genhtml).

Running the performance tests

You can run the performance tests like so: python perf/performance_test.py. The program will profile the time to run for random data samples of various sizes as well as the construction of a sorted orderbook using live L2 orderbook data from Coinbase.

The performance of constructing a sorted orderbook (using live data from Coinbase) using this C library, versus a pure Python sorted dictionary library:

Library Time, in seconds
C Library 0.00021767616271
Python Library 0.00043988227844

The performance of constructing sorted dictionaries using the same libraries, as well as the cost of building unsorted, python dictionaies for dictionaries of random floating point data:

Library Number of Keys Time, in seconds
C Library 100 0.00021600723266
Python Library 100 0.00044703483581
Python Dict 100 0.00022006034851
C Library 500 0.00103306770324
Python Library 500 0.00222206115722
Python Dict 500 0.00097918510437
C Library 1000 0.00202703475952
Python Library 1000 0.00423812866210
Python Dict 1000 0.00176715850830

This represents a roughly 2x speedup compared to a pure python implementation, and in many cases is close to the performance of an unsorted python dictionary.

For other performance metrics, run performance_test.py as well as the other performance tests in perf/

orderbook's People

Contributors

agijsberts avatar bmoscon avatar jamesmurphy-mc avatar leftys avatar plied avatar xingyaoww avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

orderbook's Issues

use skiplist to improve 4x cpu performance

Is your feature request related to a problem? Please describe.
currently, this library use python SortedDict to store orderbook data。 when fetch coinbase、btc-usd data, in l2_book/l3_book callback functions, book.book.bids.index(0)/book.book.asks.index(0) will cost 40%-50% cpu

Describe the solution you'd like
use skiplist to store orderbook, only use 10% cpu
all test code in this zip file.
skiplist.zip

Additional context
test code1, python sorteddict use 43% cpu
list

import os, sys, random
from skiplist import SkipList
from decimal import Decimal

import cryptofeed.types as cryptofeed_types
from cryptofeed import FeedHandler
from cryptofeed.defines import CANDLES, BID, ASK, L2_BOOK, L3_BOOK, LIQUIDATIONS, OPEN_INTEREST, PERPETUAL, TICKER, TRADES
from cryptofeed.exchanges import (Binance, Coinbase)

import logging

LOG_FORMAT = "%(asctime)s %(levelname)-2s %(process)d [%(filename)s:%(lineno)d:%(funcName)s] %(message)s"
logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)

async def book(book, receipt_timestamp):
    bids = book.book.bids
    asks = book.book.asks
    if len(bids) <= 0 or len(asks) <= 0:
        return
    asks.index(0)
    bids.index(0)
    price, size = bids.index(0)
    #logging.info(f"bid price:{price} size:{size}")

def do_test5_coinbase():
    f = FeedHandler()
    f.add_feed(Coinbase(subscription={L2_BOOK: ['BTC-USD']}, callbacks={L2_BOOK: book}))
    f.run()

test code2, skiplist use 9.6% cpu
skiplist


class OrderBookBidAask:
    def __init__(self, max_depth=None, checksum_format=None, max_depth_strict=None):
        self.bids = SkipList(reverse=True)
        self.asks = SkipList()
        self.bid = self.bids
        self.ask = self.asks

    def __getitem__(self, key):
        if key in ['bid', 'bids']:
            return self.bids
        elif key in ['ask', 'asks']:
            return self.asks
        else:
            raise Exception

    def __delitem__(self, key):
        raise Exception

    def __setitem__(self, key, value):
        if key in ['bid', 'bids']:
            self.bids = value
        elif key in ['ask', 'asks']:
            self.asks = value
        else:
            raise Exception


class SkipListOrderBook:
    def __init__(self, exchange, symbol, bids=None, asks=None, max_depth=0, truncate=False, checksum_format=None):
        self.exchange = exchange
        self.symbol = symbol
        self.book = OrderBookBidAask(max_depth=max_depth, checksum_format=checksum_format, max_depth_strict=truncate)
        if bids:
            for k, v in bids.items():
                self.book.bids[k] = v
        if asks:
            for k, v in asks.items():
                self.book.asks[k] = v
        self.delta = None
        self.timestamp = None
        self.sequence_number = None
        self.checksum = None
        self.raw = None

    @staticmethod
    def from_dict(data: dict):
        ob = SkipListOrderBook(data['exchange'], data['symbol'], bids=data['book'][BID], asks=data['book'][ASK])
        ob.timestamp = data['timestamp']
        if 'delta' in data:
            ob.delta = data['delta']
        return ob

    def _delta(self, numeric_type) -> dict:
        return {
            BID: [tuple([numeric_type(v) if isinstance(v, Decimal) else v for v in value]) for value in self.delta[BID]],
            ASK: [tuple([numeric_type(v) if isinstance(v, Decimal) else v for v in value]) for value in self.delta[ASK]]
        }

    def to_dict(self, delta=False, numeric_type=None, none_to=False):
        raise Exception

    def __repr__(self):
        return f"exchange: {self.exchange} symbol: {self.symbol} book: {self.book} timestamp: {self.timestamp}"

    def __eq__(self, cmp):
        return self.exchange == cmp.exchange and self.symbol == cmp.symbol and self.delta == cmp.delta and self.timestamp == cmp.timestamp and self.sequence_number == cmp.sequence_number and self.checksum == cmp.checksum and self.book.to_dict() == cmp.book.to_dict()

    def __hash__(self):
        return hash(self.__repr__())


async def Coinbase_pair_level2_snapshot(self, msg: dict, timestamp: float):
    pair = self.exchange_symbol_to_std_symbol(msg['product_id'])
    bids = {Decimal(price): Decimal(amount) for price, amount in msg['bids']}
    asks = {Decimal(price): Decimal(amount) for price, amount in msg['asks']}
    if pair not in self._l2_book:
        self._l2_book[pair] = SkipListOrderBook(self.id, pair, max_depth=self.max_depth, bids=bids, asks=asks)
    else:
        for k, v in bids.items():
            self._l2_book[pair].book.bids[k] = v
        for k, v in asks.items():
            self._l2_book[pair].book.asks[k] = v
    await self.book_callback(L2_BOOK, self._l2_book[pair], timestamp, raw=msg)


async def book_skiplist(book, receipt_timestamp):
    bids = book.book.bids
    asks = book.book.asks
    if len(bids) <= 0 or len(asks) <= 0:
        return
    range_values = asks.index_slice(0, 101)
    for price, size in range_values:
        pass
    range_values = bids.index_slice(0, 101)
    for price, size in range_values:
        pass
    price, size = bids.index(0)
    #logging.info(f"bid price:{price} size:{size}")


def do_test6_skiplist():
    Coinbase._pair_level2_snapshot = Coinbase_pair_level2_snapshot

    f = FeedHandler()
    f.add_feed(Coinbase(subscription={L2_BOOK: ['BTC-USD']}, callbacks={L2_BOOK: book_skiplist}))
    f.run()

skiplist impl

#
# This file is part of Bluepass. Bluepass is Copyright (c) 2012-2014
# Geert Jansen.
#
# Bluepass is free software available under the GNU General Public License,
# version 3. See the file LICENSE distributed with this file for the exact
# licensing terms.
# this code is copy from https://github.com/geertj/pyskiplist

from __future__ import absolute_import, print_function

import os
import sys
import math
import random


class SkipList(object):
    """An indexable skip list.

    A SkipList provides an ordered sequence of key-value pairs. The list is
    always sorted on key and supports O(1) forward iteration. It has O(log N)
    time complexity for key lookup, pair insertion and pair removal anywhere in
    the list. The list also supports O(log N) element access by position.

    The keys of all pairs you add to the skiplist must be be comparable against
    each other, and define the ``<`` and ``<=`` operators.
    """

    UNSET = object()

    p = int((1<<31) / math.e)
    maxlevel = 20

    # Kudos to http://pythonsweetness.tumblr.com/post/45227295342 for some
    # useful tricks, including using a list for the nodes to save memory.

    # Use the built-in Mersenne Twister random number generator. It is more
    # appropriate than SystemRandom because we don't need cryptographically
    # secure random numbers, and we don't want to do a system call to read
    # /dev/urandom for each random number we need (every insertion needs a new
    # random number).

    _rnd = random.Random()
    _rnd.seed(os.urandom(16))

    __slots__ = ('_reverse', '_level', '_head', '_tail', '_path', '_distance')

    def __init__(self, reverse=False):
        self._reverse = reverse
        self._level = 1
        self._head = self._new_node(self.maxlevel, None, None)
        self._tail = self._new_node(self.maxlevel, None, None)
        for i in range(self.maxlevel):
            self._head[2+i] = self._tail
        self._path = [None] * self.maxlevel
        self._distance = [None] * self.maxlevel

    def _is_key_lt(self, key1, key2):
        if not self._reverse:
            if key1 < key2:
                return True
        else:
            if key1 > key2:
                return True
        return False

    def _is_key_lte(self, key1, key2):
        if not self._reverse:
            if key1 <= key2:
                return True
        else:
            if key1 >= key2:
                return True
        return False

    def _new_node(self, level, key, value):
        # Node layout: [key, value, next*LEVEL, skip?]
        # The "skip" element indicates how many nodes are skipped by the
        # highest level incoming link.
        if level == 1:
            return [key, value, None]
        else:
            return [key, value] + [None]*level + [0]

    def _random_level(self):
        # Exponential distribution as per Pugh's paper.
        l = 1
        maxlevel = min(self.maxlevel, self.level+1)
        while l < maxlevel and self._rnd.getrandbits(31) < self.p:
            l += 1
        return l

    def _create_node(self, key, value):
        # Create a new node, updating the list level if required.
        level = self._random_level()
        if level > self.level:
            self._tail[-1] = len(self)
            self._level = level
            self._path[level-1] = self._head
            self._distance[level-1] = 0
        return self._new_node(level, key, value)

    def _find_lt(self, key):
        # Find path to last node < key
        node = self._head
        distance = 0
        for i in reversed(range(self.level)):
            nnode = node[2+i]
            while nnode is not self._tail and self._is_key_lt(nnode[0], key):
                nnode, node = nnode[2+i], nnode
                distance += 1 if i == 0 else node[-1]
            self._path[i] = node
            self._distance[i] = distance

    def _find_lte(self, key):
        # Find path to last node <= key
        node = self._head
        distance = 0
        for i in reversed(range(self.level)):
            nnode = node[2+i]
            while nnode is not self._tail and self._is_key_lte(nnode[0], key):
                nnode, node = nnode[2+i], nnode
                distance += 1 if i == 0 else node[-1]
            self._path[i] = node
            self._distance[i] = distance

    def _find_pos(self, pos):
        # Create path to node at pos.
        node = self._head
        distance = 0
        for i in reversed(range(self.level)):
            nnode = node[2+i]
            ndistance = distance + (1 if i == 0 else nnode[-1])
            while nnode is not self._tail and ndistance <= pos:
                nnode, node, distance = nnode[2+i], nnode, ndistance
                ndistance += 1 if i == 0 else nnode[-1]
            self._path[i] = node
            self._distance[i] = distance

    def _insert(self, node):
        # Insert a node in the list. The _path and _distance must be set.
        path, distance = self._path, self._distance
        # Update pointers
        level = max(1, len(node) - 3)
        for i in range(level):
            node[2+i] = path[i][2+i]
            path[i][2+i] = node
        if level > 1:
            node[-1] = 1 + distance[0] - distance[level-1]
        # Update skip counts
        node = node[2]
        i = 2; j = min(len(node) - 3, self.level)
        while i <= self.level:
            while j < i:
                node = node[i]
                j = min(len(node) - 3, self.level)
            node[-1] -= distance[0] - distance[j-1] if j <= level else -1
            i = j+1

    def _remove(self, node):
        # Remove a node. The _path and _distance must be set.
        path, distance = self._path, self._distance
        level = max(1, len(node) - 3)
        for i in range(level):
            path[i][2+i] = node[2+i]
        # Update skip counts
        value = node[1]
        node = node[2]
        i = 2; j = min(len(node) - 3, self.level)
        while i <= self.level:
            while j < i:
                node = node[i]
                j = min(len(node) - 3, self.level)
            node[-1] += distance[0] - distance[j-1] if j <= level else -1
            i = j+1
        # Reduce level if last node on current level was removed
        while self.level > 1 and self._head[1+self.level] is self._tail:
            self._level -= 1
            self._tail[-1] += self._tail[-1] - len(self)
        return value

    # PUBLIC API ...

    @property
    def level(self):
        """The current level of the skip list."""
        return self._level

    def insert(self, key, value):
        """Insert a key-value pair in the list.

        The pair is inserted at the correct location so that the list remains
        sorted on *key*. If a pair with the same key is already in the list,
        then the pair is appended after all other pairs with that key.
        """
        self._find_lte(key)
        node = self._create_node(key, value)
        self._insert(node)

    def replace(self, key, value):
        """Replace the value of the first key-value pair with key *key*.

        If the key was not found, the pair is inserted.
        """
        self._find_lt(key)
        node = self._path[0][2]
        if node is self._tail or self._is_key_lt(key, node[0]):
            node = self._create_node(key, value)
            self._insert(node)
        else:
            node[1] = value

    def clear(self):
        """Remove all key-value pairs."""
        for i in range(self.maxlevel):
            self._head[2+i] = self._tail
            self._tail[-1] = 0
        self._level = 1

    def __len__(self):
        """Return the number of pairs in the list."""
        dist = 0
        idx = self.level + 1
        node = self._head[idx]
        while node is not self._tail:
            dist += node[-1] if idx > 2 else 1
            node = node[idx]
        dist += node[-1]
        return dist

    __bool__ = __nonzero__ = lambda self: len(self) > 0

    def __repr__(self):
        return type(self).__name__ + '((' + repr(list(self.items()))[1:-1] + '))'

    def items(self, start=None, stop=None):
        """Return an iterator yielding pairs.

        If *start* is specified, iteration starts at the first pair with a key
        that is larger than or equal to *start*. If not specified, iteration
        starts at the first pair in the list.

        If *stop* is specified, iteration stops at the last pair that is
        smaller than *stop*. If not specified, iteration end with the last pair
        in the list.
        """
        if start is None:
            node = self._head[2]
        else:
            self._find_lt(start)
            node = self._path[0][2]
        while node is not self._tail and (stop is None or node[0] < stop):
            yield (node[0], node[1])
            node = node[2]

    __iter__ = items

    def keys(self, start=None, stop=None):
        """Like :meth:`items` but returns only the keys."""
        return (item[0] for item in self.items(start, stop))

    def values(self, start=None, stop=None):
        """Like :meth:`items` but returns only the values."""
        return (item[1] for item in self.items(start, stop))

    def popitem(self):
        """Removes the first key-value pair and return it.

        This method raises a ``KeyError`` if the list is empty.
        """
        node = self._head[2]
        if node is self._tail:
            raise KeyError('list is empty')
        self._find_lt(node[0])
        self._remove(node)
        return (node[0], node[1])

    # BY KEY API ...

    def search(self, key, default=None):
        """Find the first key-value pair with key *key* and return its value.

        If the key was not found, return *default*. If no default was provided,
        return ``None``. This method never raises a ``KeyError``.
        """
        self._find_lt(key)
        node = self._path[0][2]
        if node is self._tail or self._is_key_lt(key, node[0]):
            return default
        return node[1]

    def remove(self, key):
        """Remove the first key-value pair with key *key*.

        If the key was not found, a ``KeyError`` is raised.
        """
        self._find_lt(key)
        node = self._path[0][2]
        if node is self._tail or self._is_key_lt(key, node[0]):
            raise KeyError('{!r} is not in list'.format(key))
        self._remove(node)

    def pop(self, key, default=UNSET):
        """Remove the first key-value pair with key *key*.

        If a pair was removed, return its value. Otherwise if *default* was
        provided, return *default*. Otherwise a ``KeyError`` is raised.
        """
        self._find_lt(key)
        node = self._path[0][2]
        if node is self._tail or self._is_key_lt(key, node[0]):
            if default is self.UNSET:
                raise KeyError('key {!r} not in list')
            return default
        self._remove(node)
        return node[1]

    def __contains__(self, key):
        """Return whether *key* is contained in the list."""
        self._find_lt(key)
        node = self._path[0][2]
        return node is not self._tail and not self._is_key_lt(key, node[0])

    def index(self, pos):
        size = len(self)
        if pos < 0:
            pos += size
        if not 0 <= pos < size:
            raise IndexError('list index out of range')
        self._find_pos(pos)
        node = self._path[0][2]
        return (node[0], node[1])

    def index_slice(self, start, stop):
        size = len(self)
        if start is None:
            start = 0
        elif start < 0:
            start += size
        if stop is None:
            stop = size
        elif stop < 0:
            stop += size
        self._find_pos(start)
        resulsts = []
        pos = start
        node = self._path[0][2]
        while node is not self._tail and pos < stop:
            resulsts.append((node[0], node[1]))
            #yield (node[0], node[1])
            node = node[2]
            pos += 1
        return resulsts

    def count(self, key):
        """Return the number of pairs with key *key*."""
        count = 0
        pos = self._find_pos(key, -1)
        if pos == -1:
            return count
        count += 1
        for i in range(pos+1, len(self)):
            if self[i][0] != key:
                break
            count += 1
        return count

    def __getitem__(self, key):
        val = self.search(key)
        return val

    def __delitem__(self, key):
        self.remove(key)

    def __setitem__(self, key, value):
        self.replace(key, value)

Issues installing from source

Describe the bug
Errors when trying to install

To Reproduce
Steps to reproduce the behavior:
python3 setup.py install

Expected behavior
Installation of package

Screenshots
running install
/opt/homebrew/Caskroom/miniforge/base/lib/python3.9/site-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.
warnings.warn(
/opt/homebrew/Caskroom/miniforge/base/lib/python3.9/site-packages/setuptools/command/easy_install.py:144: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.
warnings.warn(
running bdist_egg
running egg_info
creating order_book.egg-info
writing order_book.egg-info/PKG-INFO
writing dependency_links to order_book.egg-info/dependency_links.txt
writing top-level names to order_book.egg-info/top_level.txt
writing manifest file 'order_book.egg-info/SOURCES.txt'
reading manifest file 'order_book.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
adding license file 'LICENSE'
writing manifest file 'order_book.egg-info/SOURCES.txt'
installing library code to build/bdist.macosx-11.0-arm64/egg
running install_lib
running build_ext
building 'order_book' extension
creating build
creating build/temp.macosx-11.0-arm64-cpython-39
creating build/temp.macosx-11.0-arm64-cpython-39/orderbook
clang -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -fwrapv -O2 -Wall -fPIC -O2 -isystem /opt/homebrew/Caskroom/miniforge/base/include -arch arm64 -fPIC -O2 -isystem /opt/homebrew/Caskroom/miniforge/base/include -arch arm64 -I/opt/homebrew/Caskroom/miniforge/base/include/python3.9 -c orderbook/orderbook.c -o build/temp.macosx-11.0-arm64-cpython-39/orderbook/orderbook.o -O3
In file included from orderbook/orderbook.c:7:
orderbook/orderbook.h:124:15: warning: incompatible function pointer types initializing 'freefunc' (aka 'void (*)(void *)') with an expression of type 'void (PyObject *)' (aka 'void (struct _object *)') [-Wincompatible-function-pointer-types]
.m_free = order_book_free,
^~~~~~~~~~~~~~~
orderbook/orderbook.c:543:9: warning: incompatible pointer to integer conversion passing 'void *' to parameter of type 'long' [-Wint-conversion]
if (EXPECT(memchr(&data[startpos], (char) 'E', *pos - startpos), 0)) {
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
orderbook/utils.h:13:44: note: expanded from macro 'EXPECT'
#define EXPECT(EXPR, VAL) __builtin_expect((EXPR), (VAL))
^~~~~~
orderbook/orderbook.c:562:25: warning: passing 'uint8_t *' (aka 'unsigned char *') to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not [-Wpointer-sign]
if (EXPECT(!strncmp(&data[startpos], "0.0000", 6) || memchr(&data[startpos], (char) 'E', *pos - startpos), 0)) {
^~~~~~~~~~~~~~~
orderbook/utils.h:13:45: note: expanded from macro 'EXPECT'
#define EXPECT(EXPR, VAL) __builtin_expect((EXPR), (VAL))
^~~~
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/string.h:84:26: note: passing argument to parameter '__s1' here
int strncmp(const char *__s1, const char *__s2, size_t __n);
^
3 warnings generated.
clang -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -fwrapv -O2 -Wall -fPIC -O2 -isystem /opt/homebrew/Caskroom/miniforge/base/include -arch arm64 -fPIC -O2 -isystem /opt/homebrew/Caskroom/miniforge/base/include -arch arm64 -I/opt/homebrew/Caskroom/miniforge/base/include/python3.9 -c orderbook/sorteddict.c -o build/temp.macosx-11.0-arm64-cpython-39/orderbook/sorteddict.o -O3
clang -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -fwrapv -O2 -Wall -fPIC -O2 -isystem /opt/homebrew/Caskroom/miniforge/base/include -arch arm64 -fPIC -O2 -isystem /opt/homebrew/Caskroom/miniforge/base/include -arch arm64 -I/opt/homebrew/Caskroom/miniforge/base/include/python3.9 -c orderbook/utils.c -o build/temp.macosx-11.0-arm64-cpython-39/orderbook/utils.o -O3
creating build/lib.macosx-11.0-arm64-cpython-39
clang -bundle -undefined dynamic_lookup -Wl,-rpath,/opt/homebrew/Caskroom/miniforge/base/lib -L/opt/homebrew/Caskroom/miniforge/base/lib -Wl,-rpath,/opt/homebrew/Caskroom/miniforge/base/lib -L/opt/homebrew/Caskroom/miniforge/base/lib build/temp.macosx-11.0-arm64-cpython-39/orderbook/orderbook.o build/temp.macosx-11.0-arm64-cpython-39/orderbook/sorteddict.o build/temp.macosx-11.0-arm64-cpython-39/orderbook/utils.o -o build/lib.macosx-11.0-arm64-cpython-39/order_book.cpython-39-darwin.so
creating build/bdist.macosx-11.0-arm64
creating build/bdist.macosx-11.0-arm64/egg
copying build/lib.macosx-11.0-arm64-cpython-39/order_book.cpython-39-darwin.so -> build/bdist.macosx-11.0-arm64/egg
creating stub loader for order_book.cpython-39-darwin.so
byte-compiling build/bdist.macosx-11.0-arm64/egg/order_book.py to order_book.cpython-39.pyc
creating build/bdist.macosx-11.0-arm64/egg/EGG-INFO
copying order_book.egg-info/PKG-INFO -> build/bdist.macosx-11.0-arm64/egg/EGG-INFO
copying order_book.egg-info/SOURCES.txt -> build/bdist.macosx-11.0-arm64/egg/EGG-INFO
copying order_book.egg-info/dependency_links.txt -> build/bdist.macosx-11.0-arm64/egg/EGG-INFO
copying order_book.egg-info/top_level.txt -> build/bdist.macosx-11.0-arm64/egg/EGG-INFO
writing build/bdist.macosx-11.0-arm64/egg/EGG-INFO/native_libs.txt
zip_safe flag not set; analyzing archive contents...
pycache.order_book.cpython-39: module references file
creating dist
creating 'dist/order_book-0.6.0-py3.9-macosx-11.0-arm64.egg' and adding 'build/bdist.macosx-11.0-arm64/egg' to it
removing 'build/bdist.macosx-11.0-arm64/egg' (and everything under it)
Processing order_book-0.6.0-py3.9-macosx-11.0-arm64.egg
creating /opt/homebrew/Caskroom/miniforge/base/lib/python3.9/site-packages/order_book-0.6.0-py3.9-macosx-11.0-arm64.egg
Extracting order_book-0.6.0-py3.9-macosx-11.0-arm64.egg to /opt/homebrew/Caskroom/miniforge/base/lib/python3.9/site-packages
Adding order-book 0.6.0 to easy-install.pth file

Installed /opt/homebrew/Caskroom/miniforge/base/lib/python3.9/site-packages/order_book-0.6.0-py3.9-macosx-11.0-arm64.egg
Processing dependencies for order-book==0.6.0
Searching for order-book==0.6.0
Reading https://pypi.org/simple/order-book/
/opt/homebrew/Caskroom/miniforge/base/lib/python3.9/site-packages/pkg_resources/init.py:123: PkgResourcesDeprecationWarning: is an invalid version and will not be supported in a future release
warnings.warn(
No local packages or working download links found for order-book==0.6.0
error: Could not find suitable distribution for Requirement.parse('order-book==0.6.0')

Operating System:

  • macOS Monterey 12.5.1

Orderbook Version
0.6.0

Python Version
3.9 and 3.10

Installation Method
From source

how to use to_list?

Traceback (most recent call last):
  File "/home/ubuntu/.pycharm_helpers/pydev/_pydevd_bundle/pydevd_exec2.py", line 3, in Exec
    exec(exp, global_vars, local_vars)
  File "<input>", line 1, in <module>
AttributeError: 'order_book.SortedDict' object has no attribute 'to_list'
self.bid.to_list()
Traceback (most recent call last):
  File "/home/ubuntu/.pycharm_helpers/pydev/_pydevd_bundle/pydevd_exec2.py", line 3, in Exec
    exec(exp, global_vars, local_vars)
  File "<input>", line 1, in <module>
AttributeError: 'order_book.SortedDict' object has no attribute 'to_list'

could you add an example in Basic Usage section in README ?

Installation Failure on MacBook with Apple M3 Max and macOS Sonoma 14.4.1

Describe the bug
When attempting to install the order-book package via pip on a MacBook equipped with the Apple M3 Max chip and 36GB of memory, running macOS Sonoma version 14.4.1, the installation fails during the build extension phase. The errors include incompatible function pointer types and incompatible pointer to integer conversions, indicating that the package's C extension is not compiling correctly due to type mismatches and warnings that are treated as errors in the compilation process. Notably, similar installation attempts on Intel-based MacBook computers do not produce these errors, which may point to a unique issue with the hardware or software configuration of the newer MacBook model.

To Reproduce
Steps to reproduce the behavior:

Attempt to install the package using pip install order-book on a MacBook with the Apple M3 Max chip, 36GB of memory, running macOS Sonoma 14.4.1.
The installation fails during the "Running setup.py install for order-book" phase, with detailed errors in the terminal.

No specific small code sample is applicable here as the issue occurs at the installation phase.

Expected behavior
The installation of the order-book package was expected to complete without errors, as it typically does on other hardware and operating system configurations. The build process for any C extensions should compile successfully and allow for the package installation.

Screenshots
image

Operating System:

MacBook with Apple M3 Max, 36GB, running macOS Sonoma 14.4.1

Orderbook Version

Version 0.6.0 of order-book was attempted.

Python Version

This issue has been observed across various Python versions, indicating that it is not specific to any particular Python release.

Installation Method

The library was attempted to be installed via pip from PyPI. The process used the legacy 'setup.py install' method, as noted by the deprecation warning regarding the absence of 'pyproject.toml' and the suggestion to use the '--use-pep517' option.

Additional context
The installation process was successfully completed on two different Intel-based MacBook computers, suggesting that the issue may be related to the specific hardware or software environment of the MacBook with the Apple M3 Max chip and macOS Sonoma 14.4.1. This points to a potential compatibility concern that might require attention for those using the library on the latest MacBook models and macOS versions.

error occured when pip install

occured error:

FileNotFoundError: [Errno 2] No such file or directory: '/private/var/folders/rq/kjp4msb55r5bd19srg575ssw0000gn/T/pip-install-yyluc257/order-book/CHANGES.md'

exec ll command under the dir:

total 40
-rw-r--r--  1 zhd  staff   5.6K Dec 28 05:31 PKG-INFO
-rw-r--r--  1 zhd  staff   3.7K Dec 28 05:26 README.md
drwxr-xr-x  6 zhd  staff   192B Jan  6 16:11 order_book.egg-info
drwxr-xr-x  5 zhd  staff   160B Jan  6 16:11 orderbook
-rw-r--r--  1 zhd  staff    38B Dec 28 05:31 setup.cfg
-rw-r--r--  1 zhd  staff   1.6K Dec 28 02:23 setup.py

Methods to return data as list

Provide following methods to export data:

  1. prices() - returns list of prices for each level (would be called on .bids or .asks)
  2. to_list() - like to dict, but returns a list of pairs (price, size) for each level in the side (would be called on .bids or .asks)
  3. ob.to_list() - like above, but would be a dict {bids: [(price, size)..., asks: [....]}

Errors while creating many OrderBook instances

Describe the bug
I got an error when I tried to create multiple OrderBook instances or to initialise it multiple times.

>>> from order_book import OrderBook
>>> for n in range(1000): OrderBook().asks = {}
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: attribute name must be string, not 'order_book.OrderBook'
>>> from order_book import OrderBook
>>> a = OrderBook()
>>> for n in range(1000): a.asks = {}
...
Fatal Python error: deletion of interned string failed
Python runtime state: initialized
[1]    39663 segmentation fault  python

Operating System:
macOS 10.15.2
Ubuntu 16.04.7 LTS

Orderbook Version
order-book==0.2.0 installed with pip

Python Version
Python 3.8.6 (default, Oct 27 2020, 08:57:44)
[Clang 12.0.0 (clang-1200.0.32.21)] on darwin

order_book-0.2.0-cp38-cp38-manylinux2010_x86_64.whl
Python 3.8.8 (default, Feb 20 2021, 21:11:43)
[GCC 5.4.0 20160609] on linux

Build on windows fails

Describe the bug
Cannot install on windows machine (tried 2 different windows server versions).

When trying to install the following error occurs (same with pip install):

python setup.py install
running install
running bdist_egg
running egg_info
writing order_book.egg-info\PKG-INFO
writing dependency_links to order_book.egg-info\dependency_links.txt
writing top-level names to order_book.egg-info\top_level.txt
reading manifest file 'order_book.egg-info\SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: manifest_maker: MANIFEST.in, line 1: path 'orderbook/' cannot end with
'/'

writing manifest file 'order_book.egg-info\SOURCES.txt'
installing library code to build\bdist.win32\egg
running install_lib
running build_ext
building 'order_book' extension
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.2
8.29910\bin\HostX86\x86\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -IC:\Users\Ad
ministrator\AppData\Local\Programs\Python\Python38-32\include -IC:\Users\Adminis
trator\AppData\Local\Programs\Python\Python38-32\include "-IC:\Program Files (x8
6)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\ATLMFC\inclu
de" "-IC:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MS
VC\14.28.29910\include" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.1
9041.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\shar
ed" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\um" "-IC:\Pro
gram Files (x86)\Windows Kits\10\include\10.0.19041.0\winrt" "-IC:\Program Files
(x86)\Windows Kits\10\include\10.0.19041.0\cppwinrt" /Tcorderbook\orderbook.c /
Fobuild\temp.win32-3.8\Release\orderbook\orderbook.obj -O3
cl : Command line warning D9002 : ignoring unknown option '-O3'
orderbook.c
orderbook\orderbook.c(107): warning C4013: '__builtin_expect' undefined; assumin
g extern returning int
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.2
8.29910\bin\HostX86\x86\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -IC:\Users\Ad
ministrator\AppData\Local\Programs\Python\Python38-32\include -IC:\Users\Adminis
trator\AppData\Local\Programs\Python\Python38-32\include "-IC:\Program Files (x8
6)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\ATLMFC\inclu
de" "-IC:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MS
VC\14.28.29910\include" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.1
9041.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\shar
ed" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\um" "-IC:\Pro
gram Files (x86)\Windows Kits\10\include\10.0.19041.0\winrt" "-IC:\Program Files
(x86)\Windows Kits\10\include\10.0.19041.0\cppwinrt" /Tcorderbook\sorteddict.c
/Fobuild\temp.win32-3.8\Release\orderbook\sorteddict.obj -O3
cl : Command line warning D9002 : ignoring unknown option '-O3'
sorteddict.c
orderbook\sorteddict.c(163): warning C4013: '__builtin_expect' undefined; assumi
ng extern returning int
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.2
8.29910\bin\HostX86\x86\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -IC:\Users\Ad
ministrator\AppData\Local\Programs\Python\Python38-32\include -IC:\Users\Adminis
trator\AppData\Local\Programs\Python\Python38-32\include "-IC:\Program Files (x8
6)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\ATLMFC\inclu
de" "-IC:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MS
VC\14.28.29910\include" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.1
9041.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\shar
ed" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\um" "-IC:\Pro
gram Files (x86)\Windows Kits\10\include\10.0.19041.0\winrt" "-IC:\Program Files
(x86)\Windows Kits\10\include\10.0.19041.0\cppwinrt" /Tcorderbook\utils.c /Fobu
ild\temp.win32-3.8\Release\orderbook\utils.obj -O3
cl : Command line warning D9002 : ignoring unknown option '-O3'
utils.c
creating C:\Users\Administrator\Downloads\orderbook\build\lib.win32-3.8
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.2
8.29910\bin\HostX86\x86\link.exe /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EM
BED,ID=2 /MANIFESTUAC:NO /LIBPATH:C:\Users\Administrator\AppData\Local\Programs
Python\Python38-32\libs /LIBPATH:C:\Users\Administrator\AppData\Local\Programs\P
ython\Python38-32\PCbuild\win32 "/LIBPATH:C:\Program Files (x86)\Microsoft Visua
l Studio\2019\Community\VC\Tools\MSVC\14.28.29910\ATLMFC\lib\x86" "/LIBPATH:C:\P
rogram Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29
910\lib\x86" "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.19041.0\u
crt\x86" "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.19041.0\um\x8
6" /EXPORT:PyInit_order_book build\temp.win32-3.8\Release\orderbook\orderbook.ob
j build\temp.win32-3.8\Release\orderbook\sorteddict.obj build\temp.win32-3.8\Rel
ease\orderbook\utils.obj /OUT:build\lib.win32-3.8\order_book.cp38-win32.pyd /IMP
LIB:build\temp.win32-3.8\Release\orderbook\order_book.cp38-win32.lib
Creating library build\temp.win32-3.8\Release\orderbook\order_book.cp38-win32
.lib and object build\temp.win32-3.8\Release\orderbook\order_book.cp38-win32.exp

orderbook.obj : error LNK2001: unresolved external symbol ___builtin_expect
build\lib.win32-3.8\order_book.cp38-win32.pyd : fatal error LNK1120: 1 unresolve
d externals
error: command 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Communit
y\VC\Tools\MSVC\14.28.29910\bin\HostX86\x86\link.exe' failed with exit s
tatus 1120

Wrong length of list in 'to_list' method

Two methods 'to_list' and 'to_dict' give different results when calling on the same object. I'm using cryptofeed but it seems to me that issue is from orderbook.

Steps to reproduce the behavior:

from cryptofeed import FeedHandler
from cryptofeed.defines import L2_BOOK
from cryptofeed.exchanges import Binance
from cryptofeed.backends.aggregate import Throttle

async def book(update, receipt_timestamp):
    print(f'dict sz: {len(update.book.bids)}')
    print(f'to dict: {len(update.book.bids.to_dict())}')
    print(f'to list: {len(update.book.bids.to_list())}\n')
 
def main():
    f = FeedHandler()
    f.add_feed(Binance(max_depth=5000, symbols=['BTC-USDT'],
        channels=[L2_BOOK], callbacks={L2_BOOK:Throttle(book, 1)}))
    f.run()

if __name__ == '__main__':
    main()

bug_report

SortedDict has an unexpected behaviour after interrupted for-loops

An interrupted for-loop results in an incorrect list representation of SortedDict and an incomplete for-loop following.

from order_book import OrderBook

ob = OrderBook()
ob.bids = {1: 0, 2: 0}

for ob_price in ob.bids:
    break

print(len(list(ob.bids)))  # 1 // expected 2
print(len(ob.bids))        # 2
from order_book import OrderBook

ob = OrderBook()
ob.bids = {1: 0, 2: 0}

for ob_price in ob.bids:
    break

print(list(ob.bids))         # [1] // expected [2, 1]
print(ob.bids.to_dict())     # {2: 0, 1: 0}
from order_book import OrderBook

ob = OrderBook()
ob.bids = {1: 0, 2: 0}

for ob_price in ob.bids:
    break

for ob_price in ob.bids:
    print("new loop:", ob_price)  # only one iteration // expected 2

Operating System:
macOS 10.15.7

Orderbook Version
order-book==0.3.0 installed with pip

Python Version
Python 3.9.5 (default, May 4 2021, 03:33:11)
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin

SortedDict iteration behavior

Description
The SortedDict object iteration behavior seems different than default python iteration behavior.
The __iter is not reset after break in the middle of the object

Minimal Example

from decimal import Decimal

import requests
from order_book import SortedDict

# get some orderbook data
data = requests.get("https://api.pro.coinbase.com/products/BTC-USD/book?level=2").json()
bids = SortedDict({Decimal(price): size for price, size, _ in data['bids']}, ordering='DESC')

first_loop_price = None
for i, price in enumerate(bids):
    if i > 5:
        first_loop_price = price
        break

second_loop_price = None
for i, price in enumerate(bids):
    if i > 5:
        second_loop_price = price
        break

print(f' {first_loop_price = }\n {second_loop_price = }\n {first_loop_price == second_loop_price = }')
 first_loop_price = Decimal('19825.46')
 second_loop_price = Decimal('19824.22')
 first_loop_price == second_loop_price = False

Package version

  • 0.5.0

Python version

  • 3.9

Checksum segmentation fault [kraken]

Describe the bug
Hi! The input below leads to a segmentation fault when computing the checksum for the kraken format. I checked the other formats and they work as expected for the same input.

Could you please confirm you can reproduce on your end?

To Reproduce

from order_book import OrderBook
from decimal import Decimal

ob = OrderBook(checksum_format="KRAKEN")
asks = {
    Decimal("55714.40000"): Decimal("0.03536863"),
    Decimal("55714.50000"): Decimal("0.00141863"),
    Decimal("55715.40000"): Decimal("0.01238736"),
    Decimal("55721.20000"): Decimal("0.03590825"),
    Decimal("55731.10000"): Decimal("0.00668963"),
    Decimal("55731.90000"): Decimal("0.00337518"),
    Decimal("55734.40000"): Decimal("0.15000000"),
    Decimal("55740.30000"): Decimal("0.49480000"),
    Decimal("55740.70000"): Decimal("0.03564719"),
}
bids = {
    Decimal("55710.10000"): Decimal("0.01541903"),
    Decimal("55697.50000"): Decimal("0.01058981"),
    Decimal("55697.40000"): Decimal("1.51620051"),
    Decimal("55695.80000"): Decimal("0.23647000"),
    Decimal("55692.50000"): Decimal("1.07793841"),
    Decimal("55690.00000"): Decimal("0.42416397"),
    Decimal("55689.80000"): Decimal("0.00280347"),
    Decimal("55686.70000"): Decimal("0.17044000"),
    Decimal("55683.50000"): Decimal("0.39335470"),
    Decimal("55679.70000"): Decimal("0.03576527"),
    Decimal("55678.50000"): Decimal("0.00359205"),
}
ob.asks = asks
ob.bids = bids
ob.checksum()

Operating System: Linux 64-bit
Orderbook Version Wheel built from HEAD
Python Version Python 3.8.8
Installation Method: pip install -e .

Can we inherit this OrderBook?

I want to inherit this OrderBook to add some custom functionality. Is that possible? How can it be done and which interfaces I need to implement?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.