Ejemplo n.º 1
0
    async def process_new_transactions(self, ws, transaction_pairs):
        latest_transactions = []

        for transaction_pair in transaction_pairs:
            maker_id, taker_id = transaction_pair.maker.tid, transaction_pair.taker.tid

            # Perform Accounting Operations
            # It makes sense to do them on the back end so we keep all the PnL Code for all traders in the one place
            # Otherwise we need the backend to request from all traders and then pass from backend to frontend
            # which is unecessary and more error prone and more prone to user meddling.

            # Note we make sure to update the pnls first and then notify the fills
            # otherwise when we assert an order has been executed there will be a network delay
            # in assessing risk which would lead to messy sleep statement workarounds
            # Note all traders will have their pnl marked to market by this function
            await self.update_pnls(transaction_pair)

            # Distribute the transaction confirmations to the 2 parties
            if self.trader_still_connected(maker_id):
                maker_ws = self._traders[maker_id]
                await maker_ws.send(
                    json.dumps({
                        'type':
                        'order_fill',
                        'data':
                        named_tuple_to_dict(transaction_pair.maker)
                    }))

            if self.trader_still_connected(taker_id):
                taker_ws = self._traders[taker_id]
                await taker_ws.send(
                    json.dumps({
                        'type':
                        'order_fill',
                        'data':
                        named_tuple_to_dict(transaction_pair.taker)
                    }))

            tape_transaction = TapeTransaction(transaction_pair.ticker,
                                               transaction_pair.action,
                                               transaction_pair.maker.qty,
                                               transaction_pair.maker.price,
                                               transaction_pair.timestamp)
            latest_transactions.append(tape_transaction)

        return latest_transactions
Ejemplo n.º 2
0
    async def init_trader_account(self, tid):
        """
        Initialises the trades pnl, risk and transaction records and transmits to the trader
        """
        print("Initialising Trader Account [%s]", tid)
        self._pnls[tid] = {}
        # self._risk[tid] = TraderRisk(0,0,0,0,0,[])
        self._risk[tid] = TraderRisk(0, 0, 0, 0, 0)
        self._transaction_records[tid] = {}
        self._suspect_trade_records[tid] = []

        trader = self._traders[tid]
        await trader.send(
            json.dumps({
                'type': 'risk',
                'data': named_tuple_to_dict(self._risk[tid])
            }))

        for ticker in self._books:
            self._pnls[tid][ticker] = TickerPnL(ticker, 0, 0, 0, 0)
            await trader.send(
                json.dumps({
                    'type': 'pnl',
                    'data': named_tuple_to_dict(self._pnls[tid][ticker])
                }))

            # NOTE Right now i can't see any reason to need this for client to need this
            # it already has this in its own form that it needs
            self._transaction_records[tid][ticker] = {
                'BUY': {
                    'sum_qty': 0,
                    'sum_qty_price': 0,
                    'transactions': []
                },
                'SELL': {
                    'sum_qty': 0,
                    'sum_qty_price': 0,
                    'transactions': []
                }
            }
Ejemplo n.º 3
0
    async def create_tender(self, ticker, action, trader):
        # Desired shape and expected value
        tender_qty = self.round_to_2_sf(self._expected_tender_qty * 3/2 * betavariate(3, 1.5))
        
        # Desired risk premium paid by the client
        improvement_on_best_quote = gammavariate(self._gamma_a, self._gamma_b)
        spread = self._book._asks.best_price - self._book._bids.best_price
        
        # If the action is BUY (we are buying from the client) so we
        # need to be able to sell at at a price >= midprice >= best bid

        if action == 'BUY':
            tender_price = (self._midprice - spread/2) - improvement_on_best_quote + self._gamma_shift
        else:
            tender_price = (self._midprice + spread/2) + improvement_on_best_quote - self._gamma_shift

        tender_price = round(tender_price, self._resolution)

        tender_order = TenderOrder(ticker, self.get_next_tender_id(), tender_qty, action, tender_price, self._time_fn() + self._expires_after)

        await trader.send(json.dumps({'type': 'tender', 'data': named_tuple_to_dict(tender_order)}))
        return tender_order
Ejemplo n.º 4
0
    async def recompute_trader_pnl(self, tid, ticker):
        """
        Recomputes the PNL and Risk metrics and notifies the trader of the change
        :param tid: a trader id
        """
        trader_record = self._transaction_records[tid]
        ticker_record = trader_record[ticker]

        # Now let us recompute the pnl
        exchange_config = self._config['exchanges'][self._exchange_name]
        securities_config = exchange_config['securities']
        contract_config = securities_config[ticker]
        contract_point_value = contract_config['contract_point_value']
        contract_currency = contract_config['quote_currency']
        base_currency = exchange_config['base_currency']

        best_bid, best_ask = self._books[ticker]._bids.best_price, self._books[
            ticker]._asks.best_price,

        total_buy_qty = ticker_record['BUY']['sum_qty']
        total_sell_qty = ticker_record['SELL']['sum_qty']
        total_buy_qty_price = ticker_record['BUY']['sum_qty_price']
        total_sell_qty_price = ticker_record['SELL']['sum_qty_price']

        # This is fine as then there will be no realised
        if total_buy_qty > 0:
            # avg_buy_price = sum([transaction.price * transaction.qty for transaction in ticker_record['BUY']['transactions']]) / total_buy_qty
            avg_buy_price = total_buy_qty_price / total_buy_qty
        else:
            avg_buy_price = 0

        if total_sell_qty > 0:
            # avg_sell_price = sum([transaction.price * transaction.qty for transaction in ticker_record['SELL']['transactions']]) / total_sell_qty
            avg_sell_price = total_sell_qty_price / total_sell_qty
        else:
            avg_sell_price = 0

        if total_buy_qty == 0 or total_sell_qty == 0:
            realised_pnl_points = 0  # Nothing has yet been realised
            realised_pnl_contract_currency = 0
        else:
            realised_pnl_points = (avg_sell_price - avg_buy_price) * min(
                total_buy_qty, total_sell_qty)
            realised_pnl_contract_currency = round(
                realised_pnl_points * contract_point_value, 2)

        # We assume that trades may be settled at market close at the midprice to avoid any spurious
        # edge cases
        net_position = total_buy_qty - total_sell_qty
        avg_open_price = avg_buy_price * int(
            net_position > 0) + avg_sell_price * int(net_position < 0)
        theoretical_exit_price = (best_ask + best_bid) / 2

        unrealised_pnl_points = (theoretical_exit_price -
                                 avg_open_price) * net_position
        unrealised_pnl_contract_currency = round(
            unrealised_pnl_points * contract_point_value, 2)

        total_pnl_points = unrealised_pnl_points + realised_pnl_points
        total_pnl_contract_currency = round(total_pnl_points *
                                            contract_point_value,
                                            2)  # Pnl only stated to 2d.p

        # EX: PNL is in USD and Base is CAD
        # Direct Quote of USD => best ask = # CAD Required to buy 1 USD
        # Thus we are selling USD at market so we take the best bid
        conversion = 1 if base_currency == contract_currency else self._books[
            contract_currency]._bids.best_price

        unrealised_pnl_base_currency = unrealised_pnl_contract_currency * conversion
        realised_pnl_base_currency = realised_pnl_contract_currency * conversion
        pnl_total_base_currency = total_pnl_contract_currency * conversion

        # PnL history by ticker
        current_time = self.get_time()
        trader_pnls = self._pnls[tid]
        # ticker_pnl_history = trader_pnls[ticker].total_pnl_history
        # ticker_pnl_history.append({'time':current_time,'value':pnl_total_base_currency})

        # NOTE We remove history objects as it was becoming v expensive to keep encoding and sending
        # these to the clients many many times!

        # Update ticker pnl
        # trader_pnls[ticker] = TickerPnL(ticker, net_position, unrealised_pnl_base_currency, realised_pnl_base_currency, pnl_total_base_currency, ticker_pnl_history)

        trader_pnls[ticker] = TickerPnL(ticker, net_position,
                                        unrealised_pnl_base_currency,
                                        realised_pnl_base_currency,
                                        pnl_total_base_currency)

        # Recompute risk
        trader_risk = self._risk[tid]

        overall_net_position = sum(
            [pnl.net_position for ticker, pnl in trader_pnls.items()])
        overall_gross_position = sum(
            [abs(pnl.net_position) for ticker, pnl in trader_pnls.items()])
        overall_unrealised = sum(
            [pnl.unrealised for ticker, pnl in trader_pnls.items()])
        overall_realised = sum(
            [pnl.realised for ticker, pnl in trader_pnls.items()])
        overall_pnl = sum(
            [pnl.total_pnl for ticker, pnl in trader_pnls.items()])

        # pnl_history = trader_risk.pnl_history
        # # We avoid deeply nesting named tuples as this can be painful for data transfer
        # pnl_history.append({'time':current_time,'value':overall_pnl})

        # self._risk[tid] = TraderRisk(overall_net_position, overall_gross_position,overall_unrealised, overall_realised, overall_pnl, pnl_history)

        self._risk[tid] = TraderRisk(overall_net_position,
                                     overall_gross_position,
                                     overall_unrealised, overall_realised,
                                     overall_pnl)

        # TODO: Compute SHARPE, CALMAR, SORTINO, etc

        # send trader their updated pnl and risk
        trader = self._traders[tid]
        await trader.send(
            json.dumps({
                'type': 'pnl',
                'data': named_tuple_to_dict(trader_pnls[ticker])
            }))
        await trader.send(
            json.dumps({
                'type': 'risk',
                'data': named_tuple_to_dict(self._risk[tid])
            }))
Ejemplo n.º 5
0
    async def route_msg(self, ws, msg):
        order_msg = json.loads(msg)

        s_type, data = order_msg['type'], order_msg['data']

        if s_type == 'add_order':
            order_spec = to_named_tuple(data, OrderSpec)

            if self.order_conforms_to_trader_risk_limits(order_spec):

                book = self._books[order_spec.ticker]
                exchange_order = self.to_exchange_order(order_spec)

                if self._exchange_name != 'simulator':
                    book = self._books[order_spec.ticker]
                    order_id = book.new_order(order_spec)
                    self._orders[order_id] = order_spec
                    print(self._orders)

                    await ws.send(
                        json.dumps({
                            'type': 'order_opened',
                            'data': named_tuple_to_dict(exchange_order)
                        }))
                else:
                    book.add_order(exchange_order)

                    # Respond to user to confirm order dispatch
                    await ws.send(
                        json.dumps({
                            'type': 'order_opened',
                            'data': named_tuple_to_dict(exchange_order)
                        }))

                    # Recomputes the book / performs transactions where possible:
                    # Note this is necessary as it may provide additional liquidity to execute market orders
                    # which could not be previously executed and are queued.
                    transaction_pairs = book.auction()
                    latest_transactions = await self.process_new_transactions(
                        ws, transaction_pairs)

                    # TODO : (LUNA)
                    # WE NEED TO AGAIN ENSURE THAT THE PROCESSING OF NEW TRANSACTIONS
                    # IS HANDLED BY THE API

                    self._tape += latest_transactions

                    # Broadcast new limit order books
                    async def broadcast_to_trader(tid):
                        # Optimised to not send back everything
                        await self._traders[tid].send(
                            json.dumps({
                                'type':
                                'LOBS',
                                'data':
                                self.get_books(tickers=[order_spec.ticker])
                            }))
                        await self._traders[tid].send(
                            json.dumps({
                                'type':
                                'tape',
                                'data':
                                self.get_tape(transactions=latest_transactions)
                            }))

                    await asyncio.gather(
                        *[broadcast_to_trader(tid) for tid in self._traders])

                    # Broadcast new book and tape to observers
                    async def broadcast_to_observer(oid):
                        await self._observers[oid].send(
                            json.dumps({
                                'type':
                                'LOBS',
                                'data':
                                self.get_books(tickers=[order_spec.ticker],
                                               order_type='LMT')
                            }))
                        # Only observers may recieve the market book for debug purposes
                        await self._observers[oid].send(
                            json.dumps({
                                'type':
                                'MBS',
                                'data':
                                self.get_books(tickers=[order_spec.ticker],
                                               order_type='MKT')
                            }))
                        await self._observers[oid].send(
                            json.dumps({
                                'type':
                                'tape',
                                'data':
                                self.get_tape(transactions=latest_transactions)
                            }))

                    await asyncio.gather(*[
                        broadcast_to_observer(oid) for oid in self._observers
                    ])

                    # We call mark to market again at to make sure
                    # we have caught any no transacted market moving limits
                    await self.mark_traders_to_market(order_spec.ticker)
            else:
                # Breaches risk limits
                await ws.send(
                    json.dumps({
                        'type': 'order_failed',
                        'data': data
                    }))

        elif s_type == 'cancel_order':
            order_spec = to_named_tuple(data, OrderSpec)

            if self._exchange_name != 'simulator':
                book = self._books[order_spec.ticker]
                success = book.revoke_order(order_spec)

                # Send to confirmation back to user
                await ws.send(
                    json.dumps({
                        'type': 'order_cancelled',
                        'data': {
                            'order_spec': named_tuple_to_dict(order_spec),
                            'success': success
                        }
                    }))
            else:
                success = self._books[order_spec.ticker].cancel_order(
                    order_spec)

                # Send to confirmation back to user
                await ws.send(
                    json.dumps({
                        'type': 'order_cancelled',
                        'data': {
                            'order_spec': named_tuple_to_dict(order_spec),
                            'success': success
                        }
                    }))

                # TODO: Convert to efficient versions of below as above

                # Broadcast new limit order books
                for tid in self._traders:
                    await self._traders[tid].send(
                        json.dumps({
                            'type': 'LOBS',
                            'data': self.get_books()
                        }))

                # Broadcast new limit order books to observers
                for oid in self._observers:
                    await self._observers[oid].send(
                        json.dumps({
                            'type':
                            'LOBS',
                            'data':
                            self.get_books(tickers=[order_spec.ticker])
                        }))
                    await self._observers[oid].send(
                        json.dumps({
                            'type':
                            'MBS',
                            'data':
                            self.get_books(tickers=[order_spec.ticker],
                                           order_type='MKT')
                        }))

                # To catch any price movements due to cancellations
                await self.mark_traders_to_market(order_spec.ticker)

        elif s_type == 'accept_tender':
            tender_order = to_named_tuple(data, TenderOrder)

            if tender_order.expiration_time < self.get_time():
                # Tender Expired
                print("exchange - user out of sync delta : %s " %
                      (self.get_time() - order_msg['exchange_time']))
                print("Real Lag between user send and server processing: %s " %
                      (time.time() - order_msg['user_utc_time']))
                await ws.send(
                    json.dumps({
                        'type': 'tender_rejected',
                        'data': data
                    }))
            else:
                # Emulate the tender for acounting using a transaction for pnl purposes
                tid = self.get_trader_id_from_socket(ws)

                await self.update_pnls_after_tender_accept(tid, tender_order)

                # Tender Ok
                await ws.send(json.dumps({
                    'type': 'tender_fill',
                    'data': data
                }))