Example #1
0
def test_update_order_from_ccxt():
    # Most basic order return (only has orderid)
    o = Order.parse_from_ccxt_object({'id': '1234'}, 'ETH/BTC', 'buy')
    assert isinstance(o, Order)
    assert o.ft_pair == 'ETH/BTC'
    assert o.ft_order_side == 'buy'
    assert o.order_id == '1234'
    assert o.ft_is_open
    ccxt_order = {
        'id': '1234',
        'side': 'buy',
        'symbol': 'ETH/BTC',
        'type': 'limit',
        'price': 1234.5,
        'amount': 20.0,
        'filled': 9,
        'remaining': 11,
        'status': 'open',
        'timestamp': 1599394315123
    }
    o = Order.parse_from_ccxt_object(ccxt_order, 'ETH/BTC', 'buy')
    assert isinstance(o, Order)
    assert o.ft_pair == 'ETH/BTC'
    assert o.ft_order_side == 'buy'
    assert o.order_id == '1234'
    assert o.order_type == 'limit'
    assert o.price == 1234.5
    assert o.filled == 9
    assert o.remaining == 11
    assert o.order_date is not None
    assert o.ft_is_open
    assert o.order_filled_date is None

    # Order has been closed
    ccxt_order.update({'filled': 20.0, 'remaining': 0.0, 'status': 'closed'})
    o.update_from_ccxt_object(ccxt_order)

    assert o.filled == 20.0
    assert o.remaining == 0.0
    assert not o.ft_is_open
    assert o.order_filled_date is not None

    ccxt_order.update({'id': 'somethingelse'})
    with pytest.raises(DependencyException, match=r"Order-id's don't match"):
        o.update_from_ccxt_object(ccxt_order)
    def _enter_trade(
            self,
            pair: str,
            row: Tuple,
            stake_amount: Optional[float] = None,
            trade: Optional[LocalTrade] = None) -> Optional[LocalTrade]:

        # let's call the custom entry price, using the open price as default price
        propose_rate = strategy_safe_wrapper(
            self.strategy.custom_entry_price, default_retval=row[OPEN_IDX])(
                pair=pair,
                current_time=row[DATE_IDX].to_pydatetime(),
                proposed_rate=row[OPEN_IDX])  # default value is the open rate

        # Move rate to within the candle's low/high rate
        propose_rate = min(max(propose_rate, row[LOW_IDX]), row[HIGH_IDX])

        min_stake_amount = self.exchange.get_min_pair_stake_amount(
            pair, propose_rate, -0.05) or 0
        max_stake_amount = self.wallets.get_available_stake_amount()

        pos_adjust = trade is not None
        if not pos_adjust:
            try:
                stake_amount = self.wallets.get_trade_stake_amount(pair, None)
            except DependencyException:
                return trade

            stake_amount = strategy_safe_wrapper(
                self.strategy.custom_stake_amount,
                default_retval=stake_amount)(
                    pair=pair,
                    current_time=row[DATE_IDX].to_pydatetime(),
                    current_rate=propose_rate,
                    proposed_stake=stake_amount,
                    min_stake=min_stake_amount,
                    max_stake=max_stake_amount)

        stake_amount = self.wallets.validate_stake_amount(
            pair, stake_amount, min_stake_amount)

        if not stake_amount:
            # In case of pos adjust, still return the original trade
            # If not pos adjust, trade is None
            return trade

        order_type = self.strategy.order_types['buy']
        time_in_force = self.strategy.order_time_in_force['sell']
        # Confirm trade entry:
        if not pos_adjust:
            if not strategy_safe_wrapper(
                    self.strategy.confirm_trade_entry, default_retval=True)(
                        pair=pair,
                        order_type=order_type,
                        amount=stake_amount,
                        rate=propose_rate,
                        time_in_force=time_in_force,
                        current_time=row[DATE_IDX].to_pydatetime()):
                return None

        if stake_amount and (not min_stake_amount
                             or stake_amount > min_stake_amount):
            amount = round(stake_amount / propose_rate, 8)
            if trade is None:
                # Enter trade
                has_buy_tag = len(row) >= BUY_TAG_IDX + 1
                trade = LocalTrade(
                    pair=pair,
                    open_rate=propose_rate,
                    open_date=row[DATE_IDX].to_pydatetime(),
                    stake_amount=stake_amount,
                    amount=amount,
                    fee_open=self.fee,
                    fee_close=self.fee,
                    is_open=True,
                    buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None,
                    exchange='backtesting',
                    orders=[])

            order = Order(ft_is_open=False,
                          ft_pair=trade.pair,
                          symbol=trade.pair,
                          ft_order_side="buy",
                          side="buy",
                          order_type="market",
                          status="closed",
                          price=propose_rate,
                          average=propose_rate,
                          amount=amount,
                          filled=amount,
                          cost=stake_amount + trade.fee_open)
            trade.orders.append(order)
            if pos_adjust:
                trade.recalc_trade_from_orders()

        return trade
Example #3
0
    def _enter_trade(
            self,
            pair: str,
            row: Tuple,
            stake_amount: Optional[float] = None,
            trade: Optional[LocalTrade] = None) -> Optional[LocalTrade]:

        current_time = row[DATE_IDX].to_pydatetime()
        entry_tag = row[BUY_TAG_IDX] if len(row) >= BUY_TAG_IDX + 1 else None
        # let's call the custom entry price, using the open price as default price
        order_type = self.strategy.order_types['buy']
        propose_rate = row[OPEN_IDX]
        if order_type == 'limit':
            propose_rate = strategy_safe_wrapper(
                self.strategy.custom_entry_price,
                default_retval=row[OPEN_IDX])(
                    pair=pair,
                    current_time=current_time,
                    proposed_rate=propose_rate,
                    entry_tag=entry_tag)  # default value is the open rate
            # We can't place orders higher than current high (otherwise it'd be a stop limit buy)
            # which freqtrade does not support in live.
            propose_rate = min(propose_rate, row[HIGH_IDX])

        min_stake_amount = self.exchange.get_min_pair_stake_amount(
            pair, propose_rate, -0.05) or 0
        max_stake_amount = self.wallets.get_available_stake_amount()

        pos_adjust = trade is not None
        if not pos_adjust:
            try:
                stake_amount = self.wallets.get_trade_stake_amount(
                    pair, None, update=False)
            except DependencyException:
                return None

            stake_amount = strategy_safe_wrapper(
                self.strategy.custom_stake_amount,
                default_retval=stake_amount)(pair=pair,
                                             current_time=current_time,
                                             current_rate=propose_rate,
                                             proposed_stake=stake_amount,
                                             min_stake=min_stake_amount,
                                             max_stake=max_stake_amount,
                                             entry_tag=entry_tag)

        stake_amount = self.wallets.validate_stake_amount(
            pair, stake_amount, min_stake_amount)

        if not stake_amount:
            # In case of pos adjust, still return the original trade
            # If not pos adjust, trade is None
            return trade

        time_in_force = self.strategy.order_time_in_force['buy']
        # Confirm trade entry:
        if not pos_adjust:
            if not strategy_safe_wrapper(self.strategy.confirm_trade_entry,
                                         default_retval=True)(
                                             pair=pair,
                                             order_type=order_type,
                                             amount=stake_amount,
                                             rate=propose_rate,
                                             time_in_force=time_in_force,
                                             current_time=current_time,
                                             entry_tag=entry_tag):
                return None

        if stake_amount and (not min_stake_amount
                             or stake_amount > min_stake_amount):
            self.order_id_counter += 1
            amount = round(stake_amount / propose_rate, 8)
            if trade is None:
                # Enter trade
                self.trade_id_counter += 1
                trade = LocalTrade(id=self.trade_id_counter,
                                   open_order_id=self.order_id_counter,
                                   pair=pair,
                                   open_rate=propose_rate,
                                   open_rate_requested=propose_rate,
                                   open_date=current_time,
                                   stake_amount=stake_amount,
                                   amount=amount,
                                   amount_requested=amount,
                                   fee_open=self.fee,
                                   fee_close=self.fee,
                                   is_open=True,
                                   buy_tag=entry_tag,
                                   exchange='backtesting',
                                   orders=[])

            trade.adjust_stop_loss(trade.open_rate,
                                   self.strategy.stoploss,
                                   initial=True)

            order = Order(
                id=self.order_id_counter,
                ft_trade_id=trade.id,
                ft_is_open=True,
                ft_pair=trade.pair,
                order_id=str(self.order_id_counter),
                symbol=trade.pair,
                ft_order_side="buy",
                side="buy",
                order_type=order_type,
                status="open",
                order_date=current_time,
                order_filled_date=current_time,
                order_update_date=current_time,
                price=propose_rate,
                average=propose_rate,
                amount=amount,
                filled=0,
                remaining=amount,
                cost=stake_amount + trade.fee_open,
            )
            if pos_adjust and self._get_order_filled(order.price, row):
                order.close_bt_order(current_time)
            else:
                trade.open_order_id = str(self.order_id_counter)
            trade.orders.append(order)
            trade.recalc_trade_from_orders()

        return trade
Example #4
0
def test_update_order_from_ccxt(caplog):
    # Most basic order return (only has orderid)
    o = Order.parse_from_ccxt_object({'id': '1234'}, 'ETH/BTC', 'buy')
    assert isinstance(o, Order)
    assert o.ft_pair == 'ETH/BTC'
    assert o.ft_order_side == 'buy'
    assert o.order_id == '1234'
    assert o.ft_is_open
    ccxt_order = {
        'id': '1234',
        'side': 'buy',
        'symbol': 'ETH/BTC',
        'type': 'limit',
        'price': 1234.5,
        'amount': 20.0,
        'filled': 9,
        'remaining': 11,
        'status': 'open',
        'timestamp': 1599394315123
    }
    o = Order.parse_from_ccxt_object(ccxt_order, 'ETH/BTC', 'buy')
    assert isinstance(o, Order)
    assert o.ft_pair == 'ETH/BTC'
    assert o.ft_order_side == 'buy'
    assert o.order_id == '1234'
    assert o.order_type == 'limit'
    assert o.price == 1234.5
    assert o.filled == 9
    assert o.remaining == 11
    assert o.order_date is not None
    assert o.ft_is_open
    assert o.order_filled_date is None

    # Order is unfilled, "filled" not set
    # https://github.com/freqtrade/freqtrade/issues/5404
    ccxt_order.update({
        'filled': None,
        'remaining': 20.0,
        'status': 'canceled'
    })
    o.update_from_ccxt_object(ccxt_order)

    # Order has been closed
    ccxt_order.update({'filled': 20.0, 'remaining': 0.0, 'status': 'closed'})
    o.update_from_ccxt_object(ccxt_order)

    assert o.filled == 20.0
    assert o.remaining == 0.0
    assert not o.ft_is_open
    assert o.order_filled_date is not None

    ccxt_order.update({'id': 'somethingelse'})
    with pytest.raises(DependencyException, match=r"Order-id's don't match"):
        o.update_from_ccxt_object(ccxt_order)

    message = "aaaa is not a valid response object."
    assert not log_has(message, caplog)
    Order.update_orders([o], 'aaaa')
    assert log_has(message, caplog)

    # Call regular update - shouldn't fail.
    Order.update_orders([o], {'id': '1234'})
Example #5
0
    def _get_sell_trade_entry_for_candle(
            self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]:

        # Check if we need to adjust our current positions
        if self.strategy.position_adjustment_enable:
            check_adjust_buy = True
            if self.strategy.max_entry_position_adjustment > -1:
                count_of_buys = trade.nr_of_successful_buys
                check_adjust_buy = (
                    count_of_buys <=
                    self.strategy.max_entry_position_adjustment)
            if check_adjust_buy:
                trade = self._get_adjust_trade_entry_for_candle(
                    trade, sell_row)

        sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
        sell = self.strategy.should_sell(
            trade,
            sell_row[OPEN_IDX],  # type: ignore
            sell_candle_time,
            sell_row[BUY_IDX],
            sell_row[SELL_IDX],
            low=sell_row[LOW_IDX],
            high=sell_row[HIGH_IDX])

        if sell.sell_flag:
            trade.close_date = sell_candle_time

            trade_dur = int(
                (trade.close_date_utc - trade.open_date_utc).total_seconds() //
                60)
            try:
                closerate = self._get_close_rate(sell_row, trade, sell,
                                                 trade_dur)
            except ValueError:
                return None
            # call the custom exit price,with default value as previous closerate
            current_profit = trade.calc_profit_ratio(closerate)
            order_type = self.strategy.order_types['sell']
            if sell.sell_type in (SellType.SELL_SIGNAL, SellType.CUSTOM_SELL):
                # Custom exit pricing only for sell-signals
                if order_type == 'limit':
                    closerate = strategy_safe_wrapper(
                        self.strategy.custom_exit_price,
                        default_retval=closerate)(
                            pair=trade.pair,
                            trade=trade,
                            current_time=sell_candle_time,
                            proposed_rate=closerate,
                            current_profit=current_profit)
                    # We can't place orders lower than current low.
                    # freqtrade does not support this in live, and the order would fill immediately
                    closerate = max(closerate, sell_row[LOW_IDX])
            # Confirm trade exit:
            time_in_force = self.strategy.order_time_in_force['sell']

            if not strategy_safe_wrapper(self.strategy.confirm_trade_exit,
                                         default_retval=True)(
                                             pair=trade.pair,
                                             trade=trade,
                                             order_type='limit',
                                             amount=trade.amount,
                                             rate=closerate,
                                             time_in_force=time_in_force,
                                             sell_reason=sell.sell_reason,
                                             current_time=sell_candle_time):
                return None

            trade.sell_reason = sell.sell_reason

            # Checks and adds an exit tag, after checking that the length of the
            # sell_row has the length for an exit tag column
            if (len(sell_row) > EXIT_TAG_IDX
                    and sell_row[EXIT_TAG_IDX] is not None
                    and len(sell_row[EXIT_TAG_IDX]) > 0):
                trade.sell_reason = sell_row[EXIT_TAG_IDX]

            self.order_id_counter += 1
            order = Order(
                id=self.order_id_counter,
                ft_trade_id=trade.id,
                order_date=sell_candle_time,
                order_update_date=sell_candle_time,
                ft_is_open=True,
                ft_pair=trade.pair,
                order_id=str(self.order_id_counter),
                symbol=trade.pair,
                ft_order_side="sell",
                side="sell",
                order_type=order_type,
                status="open",
                price=closerate,
                average=closerate,
                amount=trade.amount,
                filled=0,
                remaining=trade.amount,
                cost=trade.amount * closerate,
            )
            trade.orders.append(order)
            return trade

        return None