def create_order(self, pair: str, ordertype: str, side: str, amount: float, rate: float, params: Dict = {}) -> Dict: try: # Set the precision for amount and price(rate) as accepted by the exchange amount = self.amount_to_precision(pair, amount) needs_price = (ordertype != 'market' or self._api.options.get( "createMarketBuyOrderRequiresPrice", False)) rate_for_order = self.price_to_precision( pair, rate) if needs_price else None return self._api.create_order(pair, ordertype, side, amount, rate_for_order, params) except ccxt.InsufficientFunds as e: raise DependencyException( f'Insufficient funds to create {ordertype} {side} order on market {pair}.' f'Tried to {side} amount {amount} at rate {rate}.' f'Message: {e}') from e except ccxt.InvalidOrder as e: raise DependencyException( f'Could not create {ordertype} {side} order on market {pair}.' f'Tried to {side} amount {amount} at rate {rate}.' f'Message: {e}') from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}' ) from e except ccxt.BaseError as e: raise OperationalException(e) from e
def update_from_ccxt_object(self, order): """ Update Order from ccxt response Only updates if fields are available from ccxt - """ if self.order_id != str(order['id']): raise DependencyException("Order-id's don't match") self.status = order.get('status', self.status) self.symbol = order.get('symbol', self.symbol) self.order_type = order.get('type', self.order_type) self.side = order.get('side', self.side) self.price = order.get('price', self.price) self.amount = order.get('amount', self.amount) self.filled = order.get('filled', self.filled) self.remaining = order.get('remaining', self.remaining) self.cost = order.get('cost', self.cost) if 'timestamp' in order and order['timestamp'] is not None: self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc) self.ft_is_open = True if self.status in ('closed', 'canceled', 'cancelled'): self.ft_is_open = False if order.get('filled', 0) > 0: self.order_filled_date = arrow.utcnow().datetime self.order_update_date = arrow.utcnow().datetime
def _safe_sell_amount(self, pair: str, amount: float) -> float: """ Get sellable amount. Should be trade.amount - but will fall back to the available amount if necessary. This should cover cases where get_real_amount() was not able to update the amount for whatever reason. :param pair: Pair we're trying to sell :param amount: amount we expect to be available :return: amount to sell :raise: DependencyException: if available balance is not within 2% of the available amount. """ # Update wallets to ensure amounts tied up in a stoploss is now free! self.wallets.update() wallet_amount = self.wallets.get_free(pair.split('/')[0]) logger.debug( f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}") if wallet_amount >= amount: return amount elif wallet_amount > amount * 0.98: logger.info(f"{pair} - Falling back to wallet-amount.") return wallet_amount else: raise DependencyException( f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}" )
def _check_available_stake_amount(self, stake_amount: float) -> float: """ Check if stake amount can be fulfilled with the available balance for the stake currency :return: float: Stake amount :raise: DependencyException if balance is lower than stake-amount """ available_amount = self._get_available_stake_amount() if self._config['amend_last_stake_amount']: # Remaining amount needs to be at least stake_amount * last_stake_amount_min_ratio # Otherwise the remaining amount is too low to trade. if available_amount > ( stake_amount * self._config['last_stake_amount_min_ratio']): stake_amount = min(stake_amount, available_amount) else: stake_amount = 0 if available_amount < stake_amount: raise DependencyException( f"Available balance ({available_amount} {self._config['stake_currency']}) is " f"lower than stake amount ({stake_amount} {self._config['stake_currency']})" ) return stake_amount
def check_abort(self): """ Check if abort was requested, raise DependencyException if that's the case Only applies to Interactive backtest mode (webserver mode) """ if self.abort: self.abort = False raise DependencyException("Stop requested")
def get_valid_pair_combination(self, curr_1: str, curr_2: str) -> str: """ Get valid pair combination of curr_1 and curr_2 by trying both combinations. """ for pair in [f"{curr_1}/{curr_2}", f"{curr_2}/{curr_1}"]: if pair in self.markets and self.markets[pair].get('active'): return pair raise DependencyException(f"Could not combine {curr_1} and {curr_2} to get a valid pair.")
def get_real_amount(self, trade: Trade, order: Dict, order_amount: float = None) -> float: """ Get real amount for the trade Necessary for exchanges which charge fees in base currency (e.g. binance) """ if order_amount is None: order_amount = order['amount'] # Only run for closed orders if trade.fee_open == 0 or order['status'] == 'open': return order_amount # use fee from order-dict if possible if ('fee' in order and order['fee'] is not None and (order['fee'].keys() >= {'currency', 'cost'})): if (order['fee']['currency'] is not None and order['fee']['cost'] is not None and trade.pair.startswith(order['fee']['currency'])): new_amount = order_amount - order['fee']['cost'] logger.info( "Applying fee on amount for %s (from %s to %s) from Order", trade, order['amount'], new_amount) return new_amount # Fallback to Trades trades = self.exchange.get_trades_for_order(trade.open_order_id, trade.pair, trade.open_date) if len(trades) == 0: logger.info( "Applying fee on amount for %s failed: myTrade-Dict empty found", trade) return order_amount amount = 0 fee_abs = 0 for exectrade in trades: amount += exectrade['amount'] if ("fee" in exectrade and exectrade['fee'] is not None and (exectrade['fee'].keys() >= {'currency', 'cost'})): # only applies if fee is in quote currency! if (exectrade['fee']['currency'] is not None and exectrade['fee']['cost'] is not None and trade.pair.startswith(exectrade['fee']['currency'])): fee_abs += exectrade['fee']['cost'] if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC): logger.warning( f"Amount {amount} does not match amount {trade.amount}") raise DependencyException("Half bought? Amounts don't match") real_amount = amount - fee_abs if fee_abs != 0: logger.info(f"Applying fee on amount for {trade} " f"(from {order_amount} to {real_amount}) from Trades") return real_amount
def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict: """ creates a stoploss limit order. this stoploss-limit is binance-specific. It may work with a limited number of other exchanges, but this has not been tested yet. """ ordertype = "stop_loss_limit" stop_price = self.price_to_precision(pair, stop_price) # Ensure rate is less than stop price if stop_price <= rate: raise OperationalException( 'In stoploss limit order, stop price should be more than limit price' ) if self._config['dry_run']: dry_order = self.dry_run_order(pair, ordertype, "sell", amount, stop_price) return dry_order try: params = self._params.copy() params.update({'stopPrice': stop_price}) amount = self.amount_to_precision(pair, amount) rate = self.price_to_precision(pair, rate) order = self._api.create_order(pair, ordertype, 'sell', amount, rate, params) logger.info( 'stoploss limit order added for %s. ' 'stop price: %s. limit: %s', pair, stop_price, rate) return order except ccxt.InsufficientFunds as e: raise DependencyException( f'Insufficient funds to create {ordertype} sell order on market {pair}.' f'Tried to sell amount {amount} at rate {rate}. ' f'Message: {e}') from e except ccxt.InvalidOrder as e: # Errors: # `binance Order would trigger immediately.` raise InvalidOrderException( f'Could not create {ordertype} sell order on market {pair}. ' f'Tried to sell amount {amount} at rate {rate}. ' f'Message: {e}') from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not place sell order due to {e.__class__.__name__}. Message: {e}' ) from e except ccxt.BaseError as e: raise OperationalException(e) from e
def fetch_ticker(self, pair: str) -> dict: try: if pair not in self._api.markets or not self._api.markets[pair].get('active'): raise DependencyException(f"Pair {pair} not available") data = self._api.fetch_ticker(pair) return data except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not load ticker due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: """ Creates a stoploss order. depending on order_types.stoploss configuration, uses 'market' or limit order. Limit orders are defined by having orderPrice set, otherwise a market order is used. """ limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) limit_rate = stop_price * limit_price_pct ordertype = "stop" stop_price = self.price_to_precision(pair, stop_price) if self._config['dry_run']: dry_order = self.dry_run_order(pair, ordertype, "sell", amount, stop_price) return dry_order try: params = self._params.copy() if order_types.get('stoploss', 'market') == 'limit': # set orderPrice to place limit order, otherwise it's a market order params['orderPrice'] = limit_rate amount = self.amount_to_precision(pair, amount) order = self._api.create_order(symbol=pair, type=ordertype, side='sell', amount=amount, price=stop_price, params=params) logger.info('stoploss order added for %s. ' 'stop price: %s.', pair, stop_price) return order except ccxt.InsufficientFunds as e: raise DependencyException( f'Insufficient funds to create {ordertype} sell order on market {pair}. ' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except ccxt.InvalidOrder as e: raise InvalidOrderException( f'Could not create {ordertype} sell order on market {pair}. ' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not place sell order due to {e.__class__.__name__}. Message: {e}' ) from e except ccxt.BaseError as e: raise OperationalException(e) from e
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', get_price=MagicMock(return_value={'bitcoin': { 'usd': 15000.0 }}), ) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING with pytest.raises(RPCException, match=r'.*no active trade*'): rpc._rpc_status_table(default_conf['stake_currency'], 'USD') freqtradebot.enter_positions() result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') assert "Since" in headers assert "Pair" in headers assert 'instantly' == result[0][2] assert 'ETH/BTC' in result[0][1] assert '-0.41%' == result[0][3] # Test with fiatconvert rpc._fiat_converter = CryptoToFiatConverter() result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') assert "Since" in headers assert "Pair" in headers assert 'instantly' == result[0][2] assert 'ETH/BTC' in result[0][1] assert '-0.41% (-0.06)' == result[0][3] mocker.patch( 'freqtrade.freqtradebot.FreqtradeBot.get_sell_rate', MagicMock( side_effect=DependencyException("Pair 'ETH/BTC' not available"))) result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') assert 'instantly' == result[0][2] assert 'ETH/BTC' in result[0][1] assert 'nan%' == result[0][3]
def handle_trade(self, trade: Trade) -> bool: """ Sells the current pair if the threshold is reached and updates the trade record. :return: True if trade has been sold, False otherwise """ if not trade.is_open: raise DependencyException( f'Attempt to handle closed trade: {trade}') logger.debug('Handling %s ...', trade) (buy, sell) = (False, False) config_ask_strategy = self.config.get('ask_strategy', {}) if (config_ask_strategy.get('use_sell_signal', True) or config_ask_strategy.get('ignore_roi_if_buy_signal')): (buy, sell) = self.strategy.get_signal( trade.pair, self.strategy.ticker_interval, self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval)) if config_ask_strategy.get('use_order_book', False): logger.info('Using order book for selling...') # logger.debug('Order book %s',orderBook) order_book_min = config_ask_strategy.get('order_book_min', 1) order_book_max = config_ask_strategy.get('order_book_max', 1) order_book = self.exchange.get_order_book(trade.pair, order_book_max) for i in range(order_book_min, order_book_max + 1): order_book_rate = order_book['asks'][i - 1][0] logger.info(' order book asks top %s: %0.8f', i, order_book_rate) sell_rate = order_book_rate if self._check_and_execute_sell(trade, sell_rate, buy, sell): return True else: logger.debug('checking sell') sell_rate = self.get_sell_rate(trade.pair, True) if self._check_and_execute_sell(trade, sell_rate, buy, sell): return True logger.debug('Found no sell signal for %s.', trade) return False
def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str, Any]: """ Prepare the configuration for the Hyperopt module :param args: Cli args from Arguments() :return: Configuration """ config = setup_utils_configuration(args, method) if method == RunMode.BACKTEST: if config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT: raise DependencyException( 'stake amount could not be "%s" for backtesting' % constants.UNLIMITED_STAKE_AMOUNT) return config
def test_ticker(mocker, default_conf, tickers): ticker_mock = MagicMock(return_value=tickers()['ETH/BTC']) mocker.patch("freqtrade.exchange.Exchange.fetch_ticker", ticker_mock) exchange = get_patched_exchange(mocker, default_conf) dp = DataProvider(default_conf, exchange) res = dp.ticker('ETH/BTC') assert type(res) is dict assert 'symbol' in res assert res['symbol'] == 'ETH/BTC' ticker_mock = MagicMock(side_effect=DependencyException('Pair not found')) mocker.patch("freqtrade.exchange.Exchange.fetch_ticker", ticker_mock) exchange = get_patched_exchange(mocker, default_conf) dp = DataProvider(default_conf, exchange) res = dp.ticker('UNITTEST/BTC') assert res == {}
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: """ Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by kraken. """ ordertype = "stop-loss" stop_price = self.price_to_precision(pair, stop_price) if self._config['dry_run']: dry_order = self.dry_run_order(pair, ordertype, "sell", amount, stop_price) return dry_order try: params = self._params.copy() amount = self.amount_to_precision(pair, amount) order = self._api.create_order(symbol=pair, type=ordertype, side='sell', amount=amount, price=stop_price, params=params) logger.info('stoploss order added for %s. ' 'stop price: %s.', pair, stop_price) return order except ccxt.InsufficientFunds as e: raise DependencyException( f'Insufficient funds to create {ordertype} sell order on market {pair}.' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except ccxt.InvalidOrder as e: raise InvalidOrderException( f'Could not create {ordertype} sell order on market {pair}. ' f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. ' f'Message: {e}') from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not place sell order due to {e.__class__.__name__}. Message: {e}' ) from e except ccxt.BaseError as e: raise OperationalException(e) from e
def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str, Any]: """ Prepare the configuration for the Hyperopt module :param args: Cli args from Arguments() :return: Configuration """ config = setup_utils_configuration(args, method) no_unlimited_runmodes = { RunMode.BACKTEST: 'backtesting', RunMode.HYPEROPT: 'hyperoptimization', } if (method in no_unlimited_runmodes.keys() and config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT): raise DependencyException( f'The value of `stake_amount` cannot be set as "{constants.UNLIMITED_STAKE_AMOUNT}" ' f'for {no_unlimited_runmodes[method]}') return config
def fetch_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: if refresh or pair not in self._cached_ticker.keys(): try: if pair not in self._api.markets or not self._api.markets[ pair].get('active'): raise DependencyException(f"Pair {pair} not available") data = self._api.fetch_ticker(pair) try: self._cached_ticker[pair] = { 'bid': float(data['bid']), 'ask': float(data['ask']), } except KeyError: logger.debug("Could not cache ticker data for %s", pair) return data except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not load ticker due to {e.__class__.__name__}. Message: {e}' ) from e except ccxt.BaseError as e: raise OperationalException(e) from e else: logger.info("returning cached ticker-data for %s", pair) return self._cached_ticker[pair]
def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING with pytest.raises(RPCException, match=r'.*no active trade*'): rpc._rpc_trade_status() freqtradebot.enter_positions() results = rpc._rpc_trade_status() assert { 'trade_id': 1, 'pair': 'ETH/BTC', 'base_currency': 'BTC', 'open_date': ANY, 'open_date_hum': ANY, 'is_open': ANY, 'fee_open': ANY, 'fee_close': ANY, 'open_rate_requested': ANY, 'open_trade_price': ANY, 'close_rate_requested': ANY, 'sell_reason': ANY, 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, 'ticker_interval': ANY, 'open_order_id': ANY, 'close_date': None, 'close_date_hum': None, 'open_rate': 1.098e-05, 'close_rate': None, 'current_rate': 1.099e-05, 'amount': 91.07468124, 'stake_amount': 0.001, 'close_profit': None, 'current_profit': -0.41, 'stop_loss': 0.0, 'initial_stop_loss': 0.0, 'initial_stop_loss_pct': None, 'stop_loss_pct': None, 'open_order': '(limit buy rem=0.00000000)' } == results[0] mocker.patch( 'freqtrade.freqtradebot.FreqtradeBot.get_sell_rate', MagicMock( side_effect=DependencyException(f"Pair 'ETH/BTC' not available"))) results = rpc._rpc_trade_status() assert isnan(results[0]['current_profit']) assert isnan(results[0]['current_rate']) assert { 'trade_id': 1, 'pair': 'ETH/BTC', 'base_currency': 'BTC', 'open_date': ANY, 'open_date_hum': ANY, 'is_open': ANY, 'fee_open': ANY, 'fee_close': ANY, 'open_rate_requested': ANY, 'open_trade_price': ANY, 'close_rate_requested': ANY, 'sell_reason': ANY, 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, 'ticker_interval': ANY, 'open_order_id': ANY, 'close_date': None, 'close_date_hum': None, 'open_rate': 1.098e-05, 'close_rate': None, 'current_rate': ANY, 'amount': 91.07468124, 'stake_amount': 0.001, 'close_profit': None, 'current_profit': ANY, 'stop_loss': 0.0, 'initial_stop_loss': 0.0, 'initial_stop_loss_pct': None, 'stop_loss_pct': None, 'open_order': '(limit buy rem=0.00000000)' } == results[0]
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, limit_buy_order, limit_sell_order, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', get_price=MagicMock(return_value={'bitcoin': { 'usd': 15000.0 }}), ) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot, (True, False)) stake_currency = default_conf['stake_currency'] fiat_display_currency = default_conf['fiat_display_currency'] rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() res = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) assert res['trade_count'] == 0 assert res['first_trade_date'] == '' assert res['first_trade_timestamp'] == 0 assert res['latest_trade_date'] == '' assert res['latest_trade_timestamp'] == 0 # Create some test data freqtradebot.enter_positions() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) # Update the ticker with a market going up mocker.patch.multiple('freqtrade.exchange.Exchange', fetch_ticker=ticker_sell_up) trade.update(limit_sell_order) trade.close_date = datetime.utcnow() trade.is_open = False freqtradebot.enter_positions() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) # Update the ticker with a market going up mocker.patch.multiple('freqtrade.exchange.Exchange', fetch_ticker=ticker_sell_up) trade.update(limit_sell_order) trade.close_date = datetime.utcnow() trade.is_open = False stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) assert prec_satoshi(stats['profit_closed_coin'], 6.217e-05) assert prec_satoshi(stats['profit_closed_percent'], 6.2) assert prec_satoshi(stats['profit_closed_fiat'], 0.93255) assert prec_satoshi(stats['profit_all_coin'], 5.802e-05) assert prec_satoshi(stats['profit_all_percent'], 2.89) assert prec_satoshi(stats['profit_all_fiat'], 0.8703) assert stats['trade_count'] == 2 assert stats['first_trade_date'] == 'just now' assert stats['latest_trade_date'] == 'just now' assert stats['avg_duration'] == '0:00:00' assert stats['best_pair'] == 'ETH/BTC' assert prec_satoshi(stats['best_rate'], 6.2) # Test non-available pair mocker.patch( 'freqtrade.freqtradebot.FreqtradeBot.get_sell_rate', MagicMock( side_effect=DependencyException("Pair 'ETH/BTC' not available"))) stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) assert stats['trade_count'] == 2 assert stats['first_trade_date'] == 'just now' assert stats['latest_trade_date'] == 'just now' assert stats['avg_duration'] == '0:00:00' assert stats['best_pair'] == 'ETH/BTC' assert prec_satoshi(stats['best_rate'], 6.2) assert isnan(stats['profit_all_coin'])
def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING with pytest.raises(RPCException, match=r'.*no active trade*'): rpc._rpc_trade_status() freqtradebot.enter_positions() trades = Trade.get_open_trades() trades[0].open_order_id = None freqtradebot.exit_positions(trades) results = rpc._rpc_trade_status() assert results[0] == { 'trade_id': 1, 'pair': 'ETH/BTC', 'base_currency': 'BTC', 'open_date': ANY, 'open_date_hum': ANY, 'open_timestamp': ANY, 'is_open': ANY, 'fee_open': ANY, 'fee_open_cost': ANY, 'fee_open_currency': ANY, 'fee_close': fee.return_value, 'fee_close_cost': ANY, 'fee_close_currency': ANY, 'open_rate_requested': ANY, 'open_trade_price': 0.0010025, 'close_rate_requested': ANY, 'sell_reason': ANY, 'sell_order_status': ANY, 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, 'ticker_interval': ANY, 'timeframe': ANY, 'open_order_id': ANY, 'close_date': None, 'close_date_hum': None, 'close_timestamp': None, 'open_rate': 1.098e-05, 'close_rate': None, 'current_rate': 1.099e-05, 'amount': 91.07468124, 'stake_amount': 0.001, 'close_profit': None, 'close_profit_pct': None, 'close_profit_abs': None, 'current_profit': -0.00408133, 'current_profit_pct': -0.41, 'current_profit_abs': -4.09e-06, 'stop_loss': 9.882e-06, 'stop_loss_abs': 9.882e-06, 'stop_loss_pct': -10.0, 'stop_loss_ratio': -0.1, 'stoploss_order_id': None, 'stoploss_last_update': ANY, 'stoploss_last_update_timestamp': ANY, 'initial_stop_loss': 9.882e-06, 'initial_stop_loss_abs': 9.882e-06, 'initial_stop_loss_pct': -10.0, 'initial_stop_loss_ratio': -0.1, 'stoploss_current_dist': -1.1080000000000002e-06, 'stoploss_current_dist_ratio': -0.10081893, 'stoploss_entry_dist': -0.00010475, 'stoploss_entry_dist_ratio': -0.10448878, 'open_order': None, 'exchange': 'bittrex', } mocker.patch( 'freqtrade.freqtradebot.FreqtradeBot.get_sell_rate', MagicMock( side_effect=DependencyException("Pair 'ETH/BTC' not available"))) results = rpc._rpc_trade_status() assert isnan(results[0]['current_profit']) assert isnan(results[0]['current_rate']) assert results[0] == { 'trade_id': 1, 'pair': 'ETH/BTC', 'base_currency': 'BTC', 'open_date': ANY, 'open_date_hum': ANY, 'open_timestamp': ANY, 'is_open': ANY, 'fee_open': ANY, 'fee_open_cost': ANY, 'fee_open_currency': ANY, 'fee_close': fee.return_value, 'fee_close_cost': ANY, 'fee_close_currency': ANY, 'open_rate_requested': ANY, 'open_trade_price': ANY, 'close_rate_requested': ANY, 'sell_reason': ANY, 'sell_order_status': ANY, 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, 'ticker_interval': ANY, 'timeframe': ANY, 'open_order_id': ANY, 'close_date': None, 'close_date_hum': None, 'close_timestamp': None, 'open_rate': 1.098e-05, 'close_rate': None, 'current_rate': ANY, 'amount': 91.07468124, 'stake_amount': 0.001, 'close_profit': None, 'close_profit_pct': None, 'close_profit_abs': None, 'current_profit': ANY, 'current_profit_pct': ANY, 'current_profit_abs': ANY, 'stop_loss': 9.882e-06, 'stop_loss_abs': 9.882e-06, 'stop_loss_pct': -10.0, 'stop_loss_ratio': -0.1, 'stoploss_order_id': None, 'stoploss_last_update': ANY, 'stoploss_last_update_timestamp': ANY, 'initial_stop_loss': 9.882e-06, 'initial_stop_loss_abs': 9.882e-06, 'initial_stop_loss_pct': -10.0, 'initial_stop_loss_ratio': -0.1, 'stoploss_current_dist': ANY, 'stoploss_current_dist_ratio': ANY, 'stoploss_entry_dist': -0.00010475, 'stoploss_entry_dist_ratio': -0.10448878, 'open_order': None, 'exchange': 'bittrex', }
def test_api_backtesting(botclient, mocker, fee, caplog): ftbot, client = botclient mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) # Backtesting not started yet rc = client_get(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() assert result['status'] == 'not_started' assert not result['running'] assert result['status_msg'] == 'Backtest not yet executed' assert result['progress'] == 0 # Reset backtesting rc = client_delete(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() assert result['status'] == 'reset' assert not result['running'] assert result['status_msg'] == 'Backtest reset' # start backtesting data = { "strategy": "StrategyTestV2", "timeframe": "5m", "timerange": "20180110-20180111", "max_open_trades": 3, "stake_amount": 100, "dry_run_wallet": 1000, "enable_protections": False } rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data)) assert_response(rc) result = rc.json() assert result['status'] == 'running' assert result['progress'] == 0 assert result['running'] assert result['status_msg'] == 'Backtest started' rc = client_get(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() assert result['status'] == 'ended' assert not result['running'] assert result['status_msg'] == 'Backtest ended' assert result['progress'] == 1 assert result['backtest_result'] rc = client_get(client, f"{BASE_URI}/backtest/abort") assert_response(rc) result = rc.json() assert result['status'] == 'not_running' assert not result['running'] assert result['status_msg'] == 'Backtest ended' # Simulate running backtest ApiServer._bgtask_running = True rc = client_get(client, f"{BASE_URI}/backtest/abort") assert_response(rc) result = rc.json() assert result['status'] == 'stopping' assert not result['running'] assert result['status_msg'] == 'Backtest ended' # Get running backtest... rc = client_get(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() assert result['status'] == 'running' assert result['running'] assert result['step'] == "backtest" assert result['status_msg'] == "Backtest running" # Try delete with task still running rc = client_delete(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() assert result['status'] == 'running' # Post to backtest that's still running rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data)) assert_response(rc, 502) result = rc.json() assert 'Bot Background task already running' in result['error'] ApiServer._bgtask_running = False mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy', side_effect=DependencyException()) rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data)) assert log_has("Backtesting caused an error: ", caplog) # Delete backtesting to avoid leakage since the backtest-object may stick around. rc = client_delete(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() assert result['status'] == 'reset' assert not result['running'] assert result['status_msg'] == 'Backtest reset'