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
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
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'})
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