Esempio n. 1
0
    def remove(self, netting_order: MarketOrder or LimitOrder) -> float:
        """
        Remove position from inventory and return position PnL.

        :param netting_order: order object used to net position
        :return: (bool) TRUE if position removed successfully
        """
        pnl = 0.
        if self.position_count < 1:
            LOGGER.info('Error. No {} positions to remove.'.format(self.side))
            return pnl

        order = self.positions.popleft()

        # Calculate PnL
        if self.side == 'long':
            pnl = (netting_order.price / order.average_execution_price) - 1.
        elif self.side == 'short':
            pnl = (order.average_execution_price / netting_order.price) - 1.

        # Add Profit and Loss to realized gains/losses
        self.realized_pnl += pnl

        # Update positions attributes
        self.total_exposure -= order.average_execution_price
        self.average_price = self.total_exposure / self.position_count if \
            self.position_count > 0 else 0.
        self.full_inventory = self.position_count >= self.max_position_count

        LOGGER.debug(
            'remove-> Netted {} position #{} with {} trade #{} PnL = {:.4f}'.
            format(self.side, order.id, netting_order.side, netting_order.id,
                   pnl))

        return pnl
Esempio n. 2
0
    def flatten_inventory(self, price: float) -> float:
        """
        Flatten all positions held in inventory.

        :param price: (float) current bid or ask price
        :return: (float) PnL from flattening inventory
        """
        LOGGER.debug('{} is flattening inventory of {}'.format(
            self.side, self.position_count))

        if self.position_count < 1:
            return -ENCOURAGEMENT

        pnl = 0.
        # Need to reverse the side to reflect the correct direction of
        # the flatten_order()
        side = 'long' if self.side == 'short' else 'short'

        while self.position_count > 0:
            order = MarketOrder(ccy=None, side=side, price=price)
            pnl += self.remove(netting_order=order)
            self.total_trade_count += 1

            # Deduct transaction fee based on order type
            if self.transaction_fee:
                pnl -= MARKET_ORDER_FEE

            # Update statistics
            self.statistics.market_orders += 1

        return pnl
Esempio n. 3
0
    def _add_market_order(self, order: MarketOrder) -> bool:
        """
        Add a MARKET order.

        :param order: (Order) New order to be used for updating existing order or
                        placing a new order
        """
        if self.full_inventory:
            LOGGER.debug('  %s inventory max' % order.side)
            return False

        # Create a hypothetical average execution price incorporating a fixed slippage
        order.average_execution_price = order.price
        order.executed = order.DEFAULT_SIZE

        # Update position inventory attributes
        self.cancel_limit_order()  # remove any unfilled limit orders
        self.positions.append(order)  # execute and save the market order
        self.total_exposure += order.average_execution_price
        self.average_price = self.total_exposure / self.position_count
        self.full_inventory = self.position_count >= self.max_position_count
        self.total_trade_count += 1

        # deduct transaction fees whenever an order gets filled
        if self.transaction_fee:
            self.realized_pnl -= MARKET_ORDER_FEE

        # update statistics
        self.statistics.market_orders += 1

        LOGGER.debug('  %s @ %.2f | step %i' %
                     (order.side, order.average_execution_price, order.step))
        return True
Esempio n. 4
0
    def cancel_limit_order(self) -> bool:
        """
        Cancel a limit order.

        :return: (bool) TRUE if cancel was successful
        """
        if self.order is None:
            LOGGER.debug('No {} open orders to cancel.'.format(self.side))
            return False

        LOGGER.debug('Cancelling order ({})'.format(self.order))
        self.order = None
        return True
Esempio n. 5
0
    def flatten_inventory(self, bid_price: float, ask_price: float) -> float:
        """
        Flatten all positions held in inventory.

        :param bid_price: (float) current bid price
        :param ask_price: (float) current ask price
        :return: (float) PnL from flattening inventory
        """
        LOGGER.debug('Flattening inventory. {} longs / {} shorts'.format(
            self.long_inventory_count, self.short_inventory_count))
        long_pnl = self.long_inventory.flatten_inventory(price=bid_price)
        short_pnl = self.short_inventory.flatten_inventory(price=ask_price)
        return long_pnl + short_pnl
Esempio n. 6
0
    def _add_limit_order(self, order: LimitOrder) -> bool:
        """
        Add / update a LIMIT order.

        :param order: (Order) New order to be used for updating existing order or
                        placing a new order
        """
        if self.order is None:
            if self.full_inventory:
                LOGGER.debug(
                    "{} order rejected. Already at max position limit ({})".
                    format(self.side, self.max_position_count))
                return False
            self.order = order
            # update statistics
            self.statistics.orders_placed += 1
            LOGGER.debug('\nOpened new order={}'.format(order))

        elif self.order.price != order.price:
            self.order.price = order.price
            self.order.queue_ahead = order.queue_ahead
            self.order.id = order.id
            self.order.step = order.step
            # update statistics
            self.statistics.orders_updated += 1
            LOGGER.debug('\nUpdating order{} --> \n{}'.format(
                order, self.order))

        else:
            LOGGER.debug("\nNothing to update about the order {}".format(
                self.order))

        return True
Esempio n. 7
0
    def _step_limit_order(self, bid_price: float, ask_price: float,
                          buy_volume: float, sell_volume: float,
                          step: int) -> bool:
        """
        Step in environment and update LIMIT order inventories.

        :param bid_price: best bid price
        :param ask_price: best ask price
        :param buy_volume: executions initiated by buyers (in notional terms)
        :param sell_volume: executions initiated by sellers (in notional terms)
        :param step: current time step
        :return: (bool) TRUE if a limit order was filled, otherwise FALSE
        """
        if self.order is None:
            return False

        if self.order.side == 'long':
            if bid_price <= self.order.price:
                self._process_transaction_volume(volume=sell_volume)

        elif self.order.side == 'short':
            if ask_price >= self.order.price:
                self._process_transaction_volume(volume=buy_volume)

        if self.order.is_filled:
            avg_execution_px = self.order.get_average_execution_price()
            self.positions.append(self.order)
            self.total_exposure += avg_execution_px
            self.average_price = self.total_exposure / self.position_count
            self.full_inventory = self.position_count >= self.max_position_count
            self.total_trade_count += 1

            LOGGER.debug(
                'FILLED {} order #{} at {:.3f} after {} steps on {}.'.format(
                    self.order.side, self.order.id, avg_execution_px,
                    self.order.metrics.steps_in_position, step))

            self.order = None  # set the slot back to no open orders
            self.statistics.orders_executed += 1

            # deduct transaction fees when the LIMIT order gets filled
            if self.transaction_fee:
                self.realized_pnl -= LIMIT_ORDER_FEE

            return True

        return False
Esempio n. 8
0
    def pop_position(self) -> LimitOrder:
        """
        Remove LIMIT order position from inventory when netted out.

        :return: (LimitOrder) position being netted out
        """
        if self.position_count > 0:
            position = self.positions.popleft()

            # update positions attributes
            self.total_exposure -= position.average_execution_price
            if self.position_count > 0:
                self.average_price = self.total_exposure / self.position_count
            else:
                self.average_price = 0

            self.full_inventory = self.position_count >= self.max_position_count
            LOGGER.debug(
                'pop_position-> %s position #%i @ %.4f has been netted out.' %
                (self.side, position.id, position.price))
            return position
        else:
            raise ValueError('Error. No {} pop_position to remove.'.format(
                self.side))