<file_sep>"""
Basic order matching.
"""
from dataclasses import dataclass, field
from sortedcontainers import SortedList
import math
# from time import time


class Side:
    BUY = -1
    SELL = 1


@dataclass
class Order:
    goid: int
    side: Side
    instrument: int
    price: int
    quantity: int
    quant_remaining: int = field(init=False)

    def __post_init__(self):
        self.quant_remaining = self.quantity

    def __lt__(self, other):
        if self.price == other.price:
            return self.goid < other.goid
        return self.price * self.side < other.price * self.side

    def __le__(self, other):
        return (self == other) or (self < other)

    def __gt__(self, other):
        if self.price == other.price:
            return self.goid > other.goid
        return self.price * self.side > other.price * self.side

    def __ge__(self, other):
        return (self == other) or (self > other)

    def __eq__(self, other):
        return (self.goid == other.goid)\
               and (self.price == other.price)\
               and (self.side == other.side)\
               and (self.quantity == other.quantity)\
               and (self.instrument == other.instrument)


@dataclass(frozen=True)
class Trade:
    __slots__ = ['aggressor_goid', 'resting_goid', 'price', 'quantity']#, 'timestamp']
    aggressor_goid: int
    resting_goid: int
    price: int
    quantity: int
    # timestamp: float


class OrderBook:
    def __init__(self, algo) -> None:
        super().__init__()
        self.book: dict = {Side.BUY: SortedList(), Side.SELL: SortedList()}
        self.order_num = 0
        self.trades = []
        self.algo = algo
        self.dummy_order_cursor_min = Order(-math.inf, Side.BUY, 0, 0, 0)
        self.dummy_order_cursor_max = Order(math.inf, Side.BUY, 0, 0, 0)

    def execute_order(self, inc_order):
        is_buy = inc_order.side == Side.BUY
        side = inc_order.side
        cur_price = inc_order.price
        inc_remaining = inc_order.quant_remaining
        book = self.book[side]

        def _order_cond():
            if len(book) > 0:
                best_price = book[0].price
                return cur_price >= best_price if is_buy else cur_price <= best_price

        while (inc_remaining > 0) and _order_cond():
            self.trades += self.algo.execute(book, inc_order, self)
            inc_remaining = inc_order.quant_remaining

        (inc_remaining > 0) and self.book[-1 * side].add(inc_order)

    def add_order(self, side, instrument, price, quantity):
        self.order_num += 1
        self.execute_order(Order(self.order_num, side, instrument, price, quantity))<file_sep>from .order_matching_engine import Order, Side
from random import randint, random


def create_dummy_order(goid,
                       min_price=1,
                       max_price=4,
                       min_quantity=1,
                       max_quantity=200,
                       distribution=.5) -> Order:
    price = randint(min_price, max_price)
    quant = randint(min_quantity, max_quantity)
    if random() < distribution:
        return Order(goid, Side.BUY, 1, price, quant)
    else:
        return Order(goid, Side.SELL, 1, price, quant)
<file_sep>**goex** is an alternative exchange engine.

Works on Python 3.6+, but for best performance, run with [Python 3.6 compatible PyPy v7.3.1](https://www.pypy.org/download.html#python-3-6-compatible-pypy3-6-v7-3-1). 

<file_sep>certifi==2020.4.5.1
dataclasses==0.7
sortedcontainers==2.1.0
tqdm==4.45.0
<file_sep>"""
Implementations of matching algorithms.

Any time an aggressing order is received, matching takes the following steps:
1. Check if aggressing quantity
Each algorithm is composed of steps.
"""
from .order_matching_engine import Order, OrderBook, Trade
from sortedcontainers import SortedList
from typing import List
import numpy as np


class OrderMatchingAlgorithm:
    # def __init__(self, order_book):
    #     self.order_book = order_book
    #
    #

    @staticmethod
    def execute(book: SortedList, order: Order, ob: OrderBook) -> List[Trade]:
        raise NotImplementedError()


class FIFO(OrderMatchingAlgorithm):

    @staticmethod
    def order_key(order):
        return order.price, order.goid

    @staticmethod
    def execute(book: SortedList, order: Order, ob: OrderBook) -> List[Trade]:
        resting_order = book.pop(0)
        filled_quantity = min(order.quant_remaining, resting_order.quant_remaining)
        order.quant_remaining -= filled_quantity
        resting_order.quant_remaining -= filled_quantity
        (resting_order.quant_remaining > 0) and book.add(resting_order)
        # TODO: Global TradeLog object?
        return [Trade(order.goid, resting_order.goid, resting_order.price, filled_quantity)]
    #
    # def execute_order(self, inc_order):
    #     is_buy = inc_order.side == Side.BUY
    #     side = inc_order.side
    #     cur_price = inc_order.price
    #     inc_remaining = inc_order.quant_remaining
    #     book = self.book[side]
    #
    #     def _order_cond():
    #         if len(book) > 0:
    #             best_price = book[0].price
    #             return cur_price >= best_price if is_buy else cur_price <= best_price
    #
    #     while (inc_remaining > 0) and _order_cond():
    #         self.trades += self.algo.execute(book, inc_order, self)
    #         inc_remaining = inc_order.quant_remaining
    #
    #     (inc_remaining > 0) and self.book[-1 * side].add(inc_order)


# class ProRata(OrderMatchingAlgorithm):
#     def __init__(self, thresh=2):
#         self.thresh = thresh
#     @staticmethod
#     def execute(book: SortedList, order: Order, ob: OrderBook) -> List[Trade]:
#         trades = []
#         # Fill the TOP order
#         trades += FIFO.execute(book, order, ob)
#         if order.quant_remaining < 1:
#             # Order filled entirely by TOP
#             return trades
#         ob.dummy_order_cursor_max.price = order.price
#         grp_max = book.bisect_right(ob.dummy_order_cursor_max)
#         if not grp_max:
#             # No more orders
#             return trades
#         grp = [book.pop(0) for _ in range(grp_max)]
#         arr = np.array([o.quant_remaining for o in grp])
#         den = arr.sum()
#         arr = np.floor(order.quant_remaining * arr / den)
#         qfills = [q if q > 2 else 0 for q in arr]
#         trades = []
#         for ix, q in enumerate(qfills):
#             resting_order = grp[ix]
#             order.quant_remaining -= q
#             resting_order.quant_remaining -= q
#             (resting_order.quant_remaining > 0) and book.add(resting_order)
#             trades.append(Trade(order.goid, resting_order.goid, resting_order.price, q))
#
#         def _fifo_rest():
#             t = FIFO.execute(book, order, ob)
#             t and trades.extend(t)
#
#         while order.quant_remaining > 0:
#             _fifo_rest()
#
#         return trades
<file_sep>## Structure of order matching algorithms

**goex** will aim to support most of the order matching algorithms supported on 
 the [CME Globex platform.](https://www.cmegroup.com/confluence/display/EPICSANDBOX/Supported+Matching+Algorithms#space-menu-link-content)
 
The CME group currently lists 8 supported matching algorithms:
* Allocation
* FIFO
* FIFO w/ LMM
* FIFO w/ Top Order and LMM
* Pro Rata
* Configurable
* Threshold Pro-Rata
* Threshold Pro-Rata w/ LMM

In practice, each of these algorithms are composed of steps. Each step can be
 one of the following algorithms:
 * FIFO
 * Pro Rata
 * LMM
 * Split
 * Leveling
 * TOP
 
 In order to differentiate between the steps of an algorithm and the total
  algorithm, we will refer to the steps as **allocation steps** and the
   algorithm composed of allocation steps as the **matching algorithm**.
   
####[Algorithm Matrix](https://www.cmegroup.com/confluence/display/EPICSANDBOX/CME+Globex+Matching+Algorithm+Steps#CMEGlobexMatchingAlgorithmSteps-TOP)
| Algorithm | Operational Name                            | Step     |          |          |       |           |           |      |
|-----------|---------------------------------------------|----------|----------|----------|-------|-----------|-----------|------|
|           |                                             | 1        | 2        | 3        | 4     | 5         | 6         | 7    |
| A         | Allocation Algorithm<br>(Pro Rata w/ Top)   | TOP      | Pro Rata | FIFO     |       |           |           |      |
| C         | Currency Roll<br>(Pro Rata w/o Top)         | Pro Rata | FIFO     |          |       |           |           |      |
| F         | FIFO                                        | FIFO     |          |          |       |           |           |      |
| K         | Configurable (Split FIFO/Pro Rata with LMM) | TOP      | LMM      | Split    | FIFO\* | Pro Rata\* | Leveling\* | FIFO |
| O         | Pro Rata w/ Top and parameters              | TOP      | Pro Rata | FIFO     |       |           |           |      |
| Q         | Pro Rata w/ Top, Parameters, and LMM        | TOP      | LMM      | Pro Rata | FIFO  |           |           |      |
| S         | LMM w/ Top                                  | TOP      | LMM      | FIFO     |       |           |           |      |
| T         | LMM w/o Top                                 | LMM      | FIFO     |          |       |           |           |      |
 \*FIFO and Pro Rata percentages are determined by CMEG and calculated during
  Split. Leveling can be optionally on or off for K.


<file_sep>const airbnb = require('@neutrinojs/airbnb');
const react = require('@neutrinojs/react');
const jest = require('@neutrinojs/jest');
const svgr = require('@svgr/webpack')


module.exports = {
  options: {
    root: __dirname,
  },
  use: [
    airbnb(),
    react({
      html: {
        title: 'app'
      }
    }),
    // (neutrino) => {
    //   neutrino.config.module
    //   .rule('svgr')
    //   .test(/\.svg$/)
    //   // .use(svgr)
    // },
    jest(),
  ],
};
<file_sep>## goex

**goex** is an open-source exchange that supports trading of nearly any
commodity or financial instrument.
<file_sep>from goex.engine import Order, OrderBook, Side, create_dummy_order, FIFO
from random import randint, random
from tqdm import tqdm
import argparse


def printl(s1, s2, p=20):
    print(f"{f'{s1:<{p}} {s2:<{int(p / 2)}}':^80}")


class Benchmarker:
    @staticmethod
    def run(num_orders=10 ** 7,
            min_price=1, max_price=4,
            min_quantity=1, max_quantity=200,
            distribution=.5,
            verbose=1,
            algo='FIFO'):
        # Benchmark
        twidth = 80
        OB = OrderBook(FIFO())
        orders = []
        if verbose >= 1:
            print(f"{'-' * twidth}\n")
            print(f"{'Running benchmark for goex v0.1':^{twidth}}\n")
        if verbose >= 2:
            print(f"{'-' * twidth}\n")
            print(f"{'Settings':^{twidth}}\n")
            # printl('Algorithm', f'{algo}')
            printl('Algorithm', f'{algo}')
            printl('Orders', f'{num_orders}')
            printl(f"Price", f"[{min_price}, {max_price}]")
            printl(f"Quantity", f"[{min_quantity}, {max_quantity}]")
            printl(f"Distribution", f"[{distribution}, {1 - distribution}]")
            printl(f"Verbosity", f"{verbose}")
            print()
        if verbose >= 2:
            print(f"{'-' * twidth}\n")
            print(f"{'Benchmark':^{twidth}}\n")
            print(f"{'Stage':<22}{'Rate':<10}{'Elapsed':<10}{'Progress':<10}")
        # for n in tqdm(range(num_orders),
        #               disable=verbose < 1,
        #               unit='',
        #               smoothing=0,
        #               ncols=twidth,
        #               ascii=True,
        #               unit_scale=True,
        #               desc=f"{'Building orders...':<22}",
        #               bar_format="{desc}{rate_fmt:<10}{elapsed:<10}{bar} {percentage:3.0f}%"):
        orders = [create_dummy_order(n, min_price, max_price, min_quantity, max_quantity,
                                     distribution) for n in tqdm(range(num_orders),
                                                                 disable=verbose < 1,
                                                                 unit='',
                                                                 smoothing=0,
                                                                 ncols=twidth,
                                                                 ascii=True,
                                                                 unit_scale=True,
                                                                 desc=f"{'Building orders...':<22}",
                                                                 bar_format="{desc}{rate_fmt:<10}{elapsed:<10}{bar} {percentage:3.0f}%")]
        from time import time
        start = time()
        for order in tqdm(orders,
                          disable=verbose < 1,
                          smoothing=0,
                          desc=f"{'Simulating orders...':<22}",
                          unit='',
                          ascii=True,
                          ncols=twidth,
                          unit_scale=True,
                          bar_format="{desc}{rate_fmt:<10}{elapsed:<10}{bar} {percentage:3.0f}%"):
            OB.execute_order(order)
            # time()
        end = time()
        (verbose >= 2) and print()
        print(f"{'-' * twidth}\n")
        print(f"{'Results':^80}\n")
        total_time = (end - start)
        print(f"{f'Processed {num_orders} orders and filled {len(OB.trades)} trades.':^80}\n")
        printl(f"Total time elapsed (s):", f"{total_time:{6}.{5}f}", p=30)
        printl(f"Average time per order (us):", f"{(1000000 * total_time / num_orders):{6}.{5}f}",
               p=30)
        printl(f"Average orders per second:", f"{(num_orders / total_time):{6}.{2}f}", p=30)
        print()
        print(f"{'-' * twidth}")


class NumRange:
    """
    Adapted from https://stackoverflow.com/a/61411431/9893711
    """

    def __init__(self, ntype, imin=None, imax=None):
        self.ntype = ntype
        self.imin = imin
        self.imax = imax

    def __call__(self, arg):
        try:
            value = self.ntype(arg)
        except ValueError:
            raise self.exception()
        if (self.imin is not None and value < self.imin) or (
                self.imax is not None and value > self.imax):
            raise self.exception()
        return value

    def type_str(self):
        raise NotImplementedError("You should extend NumRange and implement type_str().")

    def exception(self):
        if self.imin is not None and self.imax is not None:
            return argparse.ArgumentTypeError(
                f"Must be an {self.type_str()} in the range [{self.imin}, {self.imax}]")
        elif self.imin is not None:
            return argparse.ArgumentTypeError(f"Must be an {self.type_str()}  >= {self.imin}")
        elif self.imax is not None:
            return argparse.ArgumentTypeError(f"Must be an {self.type_str()}  <= {self.imax}")
        else:
            return argparse.ArgumentTypeError(f"Must be an {self.type_str()}")


class IntRange(NumRange):
    def __init__(self, imin=None, imax=None):
        super().__init__(int, imin, imax)

    def type_str(self):
        return 'integer'


class FloatRange(NumRange):
    def __init__(self, imin=None, imax=None):
        super().__init__(float, imin, imax)

    def type_str(self):
        return 'float'


parser = argparse.ArgumentParser(description='Benchmark order_matching_engine.py.',
                                 formatter_class=argparse.ArgumentDefaultsHelpFormatter)

parser.add_argument('-n', '--num-orders',
                    type=IntRange(1),
                    default=10000000,
                    help='number of orders to simulate')

parser.add_argument('-p', '--min-price',
                    type=IntRange(0),
                    default=1,
                    help='minimum price per order')

parser.add_argument('-P', '--max-price',
                    type=IntRange(0),
                    default=4,
                    help='maximum price per order')

parser.add_argument('-q', '--min-quantity',
                    type=IntRange(0),
                    default=1,
                    help='minimum quantity per order')

parser.add_argument('-Q', '--max-quantity',
                    type=IntRange(0),
                    default=200,
                    help='maximum quantity per order')

parser.add_argument('-d', '--distribution',
                    type=FloatRange(0, 1),
                    default=.5,
                    help='probability of generating a buy order')

parser.add_argument('-v', '--verbose',
                    type=int,
                    choices=range(4),
                    default=1,
                    help='verbose output')

parser.add_argument('--version',
                    action='version',
                    version='goex v0.1')

if __name__ == '__main__':
    args = parser.parse_args()
    Benchmarker.run(args.num_orders,
                    args.min_price,
                    args.max_price,
                    args.min_quantity,
                    args.max_quantity,
                    args.distribution,
                    args.verbose)
<file_sep>from .order_matching_engine import Order, OrderBook, Side, Trade
from .algos import OrderMatchingAlgorithm, FIFO, ProRata
from .utils import create_dummy_order