Example #1
0
    def reduce_position_at(self, qty, price, role=None) -> Order:
        self._validate_qty(qty)

        qty = abs(qty)

        # validation
        if price < 0:
            raise ValueError('price cannot be negative.')

        # validation
        if self.position.is_close:
            raise OrderNotAllowed(
                'Cannot submit a reduce_position order when there is not open position'
            )

        side = jh.opposite_side(jh.type_to_side(self.position.type))

        # validation
        if side == 'buy' and price >= self.position.current_price:
            raise OrderNotAllowed(
                'Cannot reduce (via LIMIT) buy at ${} when current_price is ${}'
                .format(price, self.position.current_price))
        # validation
        if side == 'sell' and price <= self.position.current_price:
            raise OrderNotAllowed(
                'Cannot reduce (via LIMIT) sell at ${} when current_price is ${}'
                .format(price, self.position.current_price))

        return self.api.limit_order(self.exchange, self.symbol, qty, price,
                                    side, role, [order_flags.REDUCE_ONLY])
Example #2
0
    def stop_loss_at(self, qty, price, role=None) -> Order:
        self._validate_qty(qty)

        # validation
        if self.position.is_close:
            raise OrderNotAllowed(
                'Cannot submit a (reduce_only) stop_loss order when there is not open position'
            )

        side = jh.opposite_side(jh.type_to_side(self.position.type))

        if price < 0:
            raise ValueError('price cannot be negative.')

        if side == 'buy' and price < self.position.current_price:
            raise OrderNotAllowed(
                'Cannot submit a buy stop at {} when current price is {}'.
                format(price, self.position.current_price))
        if side == 'sell' and price > self.position.current_price:
            raise OrderNotAllowed(
                'Cannot submit a sell stop at {} when current price is {}.'.
                format(price, self.position.current_price))

        return self.api.stop_order(self.exchange, self.symbol, abs(qty), price,
                                   side, role, [order_flags.REDUCE_ONLY])
Example #3
0
    def reduce_position_at(self,
                           qty: float,
                           price: float,
                           role: str = None) -> Union[Order, None]:
        self._validate_qty(qty)

        qty = abs(qty)

        # validation
        if price < 0:
            raise ValueError('price cannot be negative.')

        # validation
        if self.position.is_close:
            raise OrderNotAllowed(
                'Cannot submit a reduce_position order when there is no open position'
            )

        side = jh.opposite_side(jh.type_to_side(self.position.type))

        if abs(price - self.position.current_price) < 0.0001:
            return self.api.market_order(self.exchange, self.symbol, qty,
                                         price, side, role,
                                         [order_flags.REDUCE_ONLY])

        elif (side == 'sell' and self.position.type == 'long'
              and price > self.position.current_price) or (
                  side == 'buy' and self.position.type == 'short'
                  and price < self.position.current_price):
            return self.api.limit_order(self.exchange, self.symbol, qty, price,
                                        side, role, [order_flags.REDUCE_ONLY])
        elif (side == 'sell' and self.position.type == 'long'
              and price < self.position.current_price) or (
                  side == 'buy' and self.position.type == 'short'
                  and price > self.position.current_price):
            return self.api.stop_order(self.exchange, self.symbol, abs(qty),
                                       price, side, role,
                                       [order_flags.REDUCE_ONLY])
        else:
            raise OrderNotAllowed(
                "This order doesn't seem to be for reducing the position.")
Example #4
0
def test_type_to_side():
    assert jh.type_to_side('long') == 'buy'
    assert jh.type_to_side('short') == 'sell'
Example #5
0
    def _log_position_update(self, order: Order, role: str) -> None:
        """
        A log can be either about opening, adding, reducing, or closing the position.

        Arguments:
            order {order} -- the order object
        """
        # set the trade_id for the order if we're in the middle of a trade. Otherwise, it
        # is done at order_roles.OPEN_POSITION
        if self.trade:
            order.trade_id = self.trade.id

        if role == order_roles.OPEN_POSITION:
            self.trade = CompletedTrade()
            self.trade.leverage = self.leverage
            self.trade.orders = [order]
            self.trade.timeframe = self.timeframe
            self.trade.id = jh.generate_unique_id()
            order.trade_id = self.trade.id
            self.trade.strategy_name = self.name
            self.trade.exchange = order.exchange
            self.trade.symbol = order.symbol
            self.trade.type = trade_types.LONG if order.side == sides.BUY else trade_types.SHORT
            self.trade.qty = order.qty
            self.trade.opened_at = jh.now_to_timestamp()
            self.trade.entry_candle_timestamp = self.current_candle[0]
        elif role == order_roles.INCREASE_POSITION:
            self.trade.orders.append(order)
            self.trade.qty += order.qty
        elif role == order_roles.REDUCE_POSITION:
            self.trade.orders.append(order)
            self.trade.qty += order.qty
        elif role == order_roles.CLOSE_POSITION:
            self.trade.exit_candle_timestamp = self.current_candle[0]
            self.trade.orders.append(order)

            # calculate average stop-loss price
            sum_price = 0
            sum_qty = 0
            if self._log_stop_loss is not None:
                for l in self._log_stop_loss:
                    sum_qty += abs(l[0])
                    sum_price += abs(l[0]) * l[1]
                self.trade.stop_loss_at = sum_price / sum_qty
            else:
                self.trade.stop_loss_at = np.nan

            # calculate average take-profit price
            sum_price = 0
            sum_qty = 0
            if self._log_take_profit is not None:
                for l in self._log_take_profit:
                    sum_qty += abs(l[0])
                    sum_price += abs(l[0]) * l[1]
                self.trade.take_profit_at = sum_price / sum_qty
            else:
                self.trade.take_profit_at = np.nan

            # calculate average entry_price price
            sum_price = 0
            sum_qty = 0
            for l in self.trade.orders:
                if not l.is_executed:
                    continue

                if jh.side_to_type(l.side) != self.trade.type:
                    continue

                sum_qty += abs(l.qty)
                sum_price += abs(l.qty) * l.price
            self.trade.entry_price = sum_price / sum_qty

            # calculate average exit_price
            sum_price = 0
            sum_qty = 0
            for l in self.trade.orders:
                if not l.is_executed:
                    continue

                if jh.side_to_type(l.side) == self.trade.type:
                    continue

                sum_qty += abs(l.qty)
                sum_price += abs(l.qty) * l.price
            self.trade.exit_price = sum_price / sum_qty

            self.trade.closed_at = jh.now_to_timestamp()
            self.trade.qty = pydash.sum_by(
                filter(lambda o: o.side == jh.type_to_side(self.trade.type),
                       self.trade.orders), lambda o: abs(o.qty))

            store.completed_trades.add_trade(self.trade)
            if jh.is_livetrading():
                store_completed_trade_into_db(self.trade)
            self.trade = None
            self.trades_count += 1

        if jh.is_livetrading():
            store_order_into_db(order)