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.symbol_amount_prec(pair, amount) needs_price = (ordertype != 'market' or self._api.options.get( "createMarketBuyOrderRequiresPrice", False)) rate = self.symbol_price_prec(pair, rate) if needs_price else None return self._api.create_order(pair, ordertype, side, amount, rate, 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} (total {rate * amount}).' 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} (total {rate * amount}).' 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 setup_configuration(args: Namespace, 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) if method == RunMode.HYPEROPT: # Special cases for Hyperopt if config.get( 'strategy') and config.get('strategy') != 'DefaultStrategy': logger.error("Please don't use --strategy for hyperopt.") logger.error( "Read the documentation at " "https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md " "to understand how to configure hyperopt.") raise DependencyException( "--strategy configured but not supported for hyperopt") return config
def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict: """ creates a stoploss limit order. NOTICE: it is not supported by all exchanges. only binance is tested for now. """ # Set the precision for amount and price(rate) as accepted by the exchange amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) stop_price = self.symbol_price_prec(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._conf['dry_run']: order_id = f'dry_run_buy_{randint(0, 10**6)}' self._dry_run_open_orders[order_id] = { 'info': {}, 'id': order_id, 'pair': pair, 'price': stop_price, 'amount': amount, 'type': 'stop_loss_limit', 'side': 'sell', 'remaining': amount, 'datetime': arrow.utcnow().isoformat(), 'status': 'open', 'fee': None } return self._dry_run_open_orders[order_id] try: return self._api.create_order(pair, 'stop_loss_limit', 'sell', amount, rate, {'stopPrice': stop_price}) except ccxt.InsufficientFunds as e: raise DependencyException( f'Insufficient funds to place stoploss limit order on market {pair}. ' f'Tried to put a stoploss amount {amount} with ' f'stop {stop_price} and limit {rate} (total {rate*amount}).' f'Message: {e}') except ccxt.InvalidOrder as e: raise DependencyException( f'Could not place stoploss limit order on market {pair}.' f'Tried to place stoploss amount {amount} with ' f'stop {stop_price} and limit {rate} (total {rate*amount}).' f'Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not place stoploss limit order due to {e.__class__.__name__}. Message: {e}' ) except ccxt.BaseError as e: raise OperationalException(e)
def buy(self, pair: str, rate: float, amount: float) -> Dict: if self._conf['dry_run']: order_id = f'dry_run_buy_{randint(0, 10**6)}' self._dry_run_open_orders[order_id] = { 'pair': pair, 'price': rate, 'amount': amount, 'type': 'limit', 'side': 'buy', 'remaining': 0.0, 'datetime': arrow.utcnow().isoformat(), 'status': 'closed', 'fee': None } return {'id': order_id} try: # Set the precision for amount and price(rate) as accepted by the exchange symbol = pair.replace('/', '') amount, quaselltrade = self.symbol_amount_prec_str(pair, amount) if (float(amount) > 0): rate = self.symbol_price_prec_str(pair, rate) print(amount, rate) return self.client.order_limit_buy(symbol=symbol, quantity=amount, price=rate) else: return {'orderId': None} print('Error - Buy') #return self._api.create_limit_buy_order(pair, amount, rate) except ccxt.InsufficientFunds as e: raise DependencyException( f'Insufficient funds to create limit buy order on market {pair}.' f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).' f'Message: {e}') except ccxt.InvalidOrder as e: raise DependencyException( f'Could not create limit buy order on market {pair}.' f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).' f'Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not place buy order due to {e.__class__.__name__}. Message: {e}' ) except ccxt.BaseError as e: raise OperationalException(e) except OSError as e: return {'orderId': None} print(e) pass
def sell(self, pair: str, ordertype: str, amount: float, rate: float, time_in_force='gtc') -> Dict: if self._conf['dry_run']: order_id = f'dry_run_sell_{randint(0, 10**6)}' self._dry_run_open_orders[order_id] = { 'pair': pair, 'price': rate, 'amount': amount, 'type': ordertype, 'side': 'sell', 'remaining': 0.0, 'datetime': arrow.utcnow().isoformat(), 'status': 'closed' } return {'id': order_id} try: # Set the precision for amount and price(rate) as accepted by the exchange amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec( pair, rate) if ordertype != 'market' else None if time_in_force == 'gtc': return self._api.create_order(pair, ordertype, 'sell', amount, rate) else: return self._api.create_order(pair, ordertype, 'sell', amount, rate, {'timeInForce': time_in_force}) except ccxt.InsufficientFunds as e: raise DependencyException( f'Insufficient funds to create limit sell order on market {pair}.' f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).' f'Message: {e}') except ccxt.InvalidOrder as e: raise DependencyException( f'Could not create limit sell order on market {pair}.' f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).' f'Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not place sell order due to {e.__class__.__name__}. Message: {e}' ) except ccxt.BaseError as e: raise OperationalException(e)
def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None: patch_exchange(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, markets=PropertyMock(return_value=markets) ) freqtradebot = FreqtradeBot(default_conf) patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING with pytest.raises(RPCException, match=r'.*no active order*'): rpc._rpc_status_table() freqtradebot.create_trades() result = rpc._rpc_status_table() assert 'instantly' in result['Since'].all() assert 'ETH/BTC' in result['Pair'].all() assert '-0.59%' in result['Profit'].all() mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available"))) # invalidate ticker cache rpc._freqtrade.exchange._cached_ticker = {} result = rpc._rpc_status_table() assert 'instantly' in result['Since'].all() assert 'ETH/BTC' in result['Pair'].all() assert 'nan%' in result['Profit'].all()
def _get_trade_stake_amount(self) -> Optional[float]: """ Check if stake amount can be fulfilled with the available balance for the stake currency :return: float: Stake Amount """ stake_amount = self.config['stake_amount'] avaliable_amount = self.exchange.get_balance( self.config['stake_currency']) if stake_amount == constants.UNLIMITED_STAKE_AMOUNT: open_trades = len( Trade.query.filter(Trade.is_open.is_(True)).all()) if open_trades >= self.config['max_open_trades']: logger.warning( 'Can\'t open a new trade: max number of trades is reached') return None return avaliable_amount / (self.config['max_open_trades'] - open_trades) # Check if stake_amount is fulfilled if avaliable_amount < stake_amount: raise DependencyException( 'Available balance(%f %s) is lower than stake amount(%f %s)' % (avaliable_amount, self.config['stake_currency'], stake_amount, self.config['stake_currency'])) return stake_amount
def create_trade(self) -> bool: """ Checks the implemented trading indicator(s) for a randomly picked pair, if one pair triggers the buy_signal a new trade record gets created :return: True if a trade object has been created and persisted, False otherwise """ interval = self.strategy.ticker_interval stake_amount = self._get_trade_stake_amount() if not stake_amount: return False logger.info( 'Checking buy signals to create a new trade with stake_amount: %f ...', stake_amount) whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist']) # Remove currently opened and latest pairs from whitelist for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): if trade.pair in whitelist: whitelist.remove(trade.pair) logger.debug('Ignoring %s in pair whitelist', trade.pair) if not whitelist: raise DependencyException('No currency pairs in whitelist') # Pick pair based on buy signals for _pair in whitelist: thistory = self.exchange.get_candle_history(_pair, interval) (buy, sell) = self.strategy.get_signal(_pair, interval, thistory) if buy and not sell: return self.execute_buy(_pair, stake_amount) return False
def _get_trade_stake_amount(self, pair) -> Optional[float]: """ Check if stake amount can be fulfilled with the available balance for the stake currency :return: float: Stake Amount """ if self.edge: return self.edge.stake_amount( pair, self.wallets.get_free(self.config['stake_currency']), self.wallets.get_total(self.config['stake_currency']), Trade.total_open_trades_stakes() ) else: stake_amount = self.config['stake_amount'] available_amount = self.wallets.get_free(self.config['stake_currency']) if stake_amount == constants.UNLIMITED_STAKE_AMOUNT: open_trades = len(Trade.get_open_trades()) if open_trades >= self.config['max_open_trades']: logger.warning('Can\'t open a new trade: max number of trades is reached') return None return available_amount / (self.config['max_open_trades'] - open_trades) # Check if stake_amount is fulfilled 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 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 get_valid_pair_combination(self, curr_1, curr_2) -> 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 test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None: patch_coinmarketcap(mocker) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple('freqtrade.exchange.Exchange', _load_markets=MagicMock(return_value={}), get_ticker=ticker, get_fee=fee, get_markets=markets) freqtradebot = FreqtradeBot(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.create_trade() results = rpc._rpc_trade_status() assert { 'trade_id': 1, 'pair': 'ETH/BTC', 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'date': ANY, 'open_rate': 1.099e-05, 'close_rate': None, 'current_rate': 1.098e-05, 'amount': 90.99181074, 'close_profit': None, 'current_profit': -0.59, 'open_order': '(limit buy rem=0.00000000)' } == results[0] mocker.patch( 'freqtrade.exchange.Exchange.get_ticker', MagicMock( side_effect=DependencyException(f"Pair 'ETH/BTC' not available"))) # invalidate ticker cache rpc._freqtrade.exchange._cached_ticker = {} results = rpc._rpc_trade_status() assert isnan(results[0]['current_profit']) assert isnan(results[0]['current_rate']) assert { 'trade_id': 1, 'pair': 'ETH/BTC', 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', 'date': ANY, 'open_rate': 1.099e-05, 'close_rate': None, 'current_rate': ANY, 'amount': 90.99181074, 'close_profit': None, 'current_profit': ANY, 'open_order': '(limit buy rem=0.00000000)' } == results[0]
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.symbol_price_prec(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.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(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 test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.fiat_convert.Market', ticker=MagicMock(return_value={'price_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', get_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 order*'): rpc._rpc_status_table(default_conf['stake_currency'], 'USD') freqtradebot.create_trades() 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' == result[0][1] assert '-0.59%' == 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' == result[0][1] assert '-0.59% (-0.09)' == result[0][3] mocker.patch( 'freqtrade.exchange.Exchange.get_ticker', MagicMock( side_effect=DependencyException(f"Pair 'ETH/BTC' not available"))) # invalidate ticker cache rpc._freqtrade.exchange._cached_ticker = {} result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') assert 'instantly' == result[0][2] assert 'ETH/BTC' == result[0][1] assert 'nan%' == result[0][3]
def cancel_order(self, order_id: str, pair: str) -> None: if self._conf['dry_run']: return try: return self._api.cancel_order(order_id, pair) except ccxt.InvalidOrder as e: raise DependencyException(f'Could not cancel order. Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not cancel order due to {e.__class__.__name__}. Message: {e}' ) except ccxt.BaseError as e: raise OperationalException(e)
def get_order(self, order_id: str, pair: str) -> Dict: if self._conf['dry_run']: order = self._dry_run_open_orders[order_id] order.update({'id': order_id}) return order try: return self._api.fetch_order(order_id, pair) except ccxt.InvalidOrder as e: raise DependencyException(f'Could not get order. Message: {e}') except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( f'Could not get order due to {e.__class__.__name__}. Message: {e}' ) except ccxt.BaseError as e: raise OperationalException(e)
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_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 create_trade(self) -> bool: """ Checks the implemented trading indicator(s) for a randomly picked pair, if one pair triggers the buy_signal a new trade record gets created :return: True if a trade object has been created and persisted, False otherwise """ interval = self.strategy.ticker_interval whitelist = copy.deepcopy(self.active_pair_whitelist) # Remove currently opened and latest pairs from whitelist for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): if trade.pair in whitelist: whitelist.remove(trade.pair) logger.debug('Ignoring %s in pair whitelist', trade.pair) if not whitelist: raise DependencyException('No currency pairs in whitelist') # running get_signal on historical data fetched for _pair in whitelist: (buy, sell) = self.strategy.get_signal( _pair, interval, self.dataprovider.ohlcv(_pair, self.strategy.ticker_interval)) if buy and not sell: stake_amount = self._get_trade_stake_amount(_pair) if not stake_amount: return False logger.info( f"Buy signal found: about create a new trade with stake_amount: " f"{stake_amount} ...") bidstrat_check_depth_of_market = self.config.get('bid_strategy', {}).\ get('check_depth_of_market', {}) if (bidstrat_check_depth_of_market.get('enabled', False)) and\ (bidstrat_check_depth_of_market.get('bids_to_ask_delta', 0) > 0): if self._check_depth_of_market_buy( _pair, bidstrat_check_depth_of_market): return self.execute_buy(_pair, stake_amount) else: return False return self.execute_buy(_pair, stake_amount) return False
def setup_configuration(args: Namespace) -> Dict[str, Any]: """ Prepare the configuration for the backtesting :param args: Cli args from Arguments() :return: Configuration """ configuration = Configuration(args) config = configuration.get_config() # Ensure we do not use Exchange credentials config['exchange']['key'] = '' config['exchange']['secret'] = '' 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 get_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: 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}' ) except ccxt.BaseError as e: raise OperationalException(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, markets, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, markets=PropertyMock(return_value=markets) ) freqtradebot = FreqtradeBot(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.create_trades() results = rpc._rpc_trade_status() assert { 'trade_id': 1, 'pair': 'ETH/BTC', 'base_currency': 'BTC', 'open_date': ANY, 'open_date_hum': ANY, 'close_date': None, 'close_date_hum': None, 'open_rate': 1.099e-05, 'close_rate': None, 'current_rate': 1.098e-05, 'amount': 90.99181074, 'stake_amount': 0.001, 'close_profit': None, 'current_profit': -0.59, '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.exchange.Exchange.get_ticker', MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available"))) # invalidate ticker cache rpc._freqtrade.exchange._cached_ticker = {} 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, 'close_date': None, 'close_date_hum': None, 'open_rate': 1.099e-05, 'close_rate': None, 'current_rate': ANY, 'amount': 90.99181074, '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 create_trade(self) -> bool: """ Checks the implemented trading indicator(s) for a randomly picked pair, if one pair triggers the buy_signal a new trade record gets created :return: True if a trade object has been created and persisted, False otherwise """ interval = self.analyze.get_ticker_interval() stake_amount = self._get_trade_stake_amount() if not stake_amount: return False stake_currency = self.config['stake_currency'] fiat_currency = self.config['fiat_display_currency'] exc_name = self.exchange.name logger.info( 'Checking buy signals to create a new trade with stake_amount: %f ...', stake_amount) whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist']) # Remove currently opened and latest pairs from whitelist for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): if trade.pair in whitelist: whitelist.remove(trade.pair) logger.debug('Ignoring %s in pair whitelist', trade.pair) if not whitelist: raise DependencyException('No currency pairs in whitelist') # Pick pair based on buy signals for _pair in whitelist: (buy, sell) = self.analyze.get_signal(self.exchange, _pair, interval) if buy and not sell: pair = _pair break else: return False pair_s = pair.replace('_', '/') pair_url = self.exchange.get_pair_detail_url(pair) # Calculate amount buy_limit = self.get_target_bid(self.exchange.get_ticker(pair)) min_stake_amount = self._get_min_pair_stake_amount(pair_s, buy_limit) if min_stake_amount is not None and min_stake_amount > stake_amount: logger.warning( f'Can\'t open a new trade for {pair_s}: stake amount' f' is too small ({stake_amount} < {min_stake_amount})') return False amount = stake_amount / buy_limit order_id = self.exchange.buy(pair, buy_limit, amount)['id'] stake_amount_fiat = self.fiat_converter.convert_amount( stake_amount, stake_currency, fiat_currency) # Create trade entity and return self.rpc.send_msg(f"""*{exc_name}:* Buying [{pair_s}]({pair_url}) \ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ {stake_currency}, {stake_amount_fiat:.3f} {fiat_currency})`""") # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') trade = Trade(pair=pair, stake_amount=stake_amount, amount=amount, fee_open=fee, fee_close=fee, open_rate=buy_limit, open_rate_requested=buy_limit, open_date=datetime.utcnow(), exchange=self.exchange.id, open_order_id=order_id) Trade.session.add(trade) Trade.session.flush() return True
def create_trade(self) -> bool: """ Checks the implemented trading indicator(s) for a randomly picked pair, if one pair triggers the buy_signal a new trade record gets created :param stake_amount: amount of btc to spend :param interval: Ticker interval used for Analyze :return: True if a trade object has been created and persisted, False otherwise """ stake_amount = self.config['stake_amount'] interval = self.analyze.get_ticker_interval() logger.info( 'Checking buy signals to create a new trade with stake_amount: %f ...', stake_amount) whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist']) # Check if stake_amount is fulfilled if exchange.get_balance(self.config['stake_currency']) < stake_amount: raise DependencyException( 'stake amount is not fulfilled (currency={})'.format( self.config['stake_currency'])) # Remove currently opened and latest pairs from whitelist for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): if trade.pair in whitelist: whitelist.remove(trade.pair) logger.debug('Ignoring %s in pair whitelist', trade.pair) if not whitelist: raise DependencyException('No currency pairs in whitelist') # Pick pair based on StochRSI buy signals for _pair in whitelist: (buy, sell) = self.analyze.get_signal(_pair, interval) if buy and not sell: pair = _pair break else: return False # Calculate amount buy_limit = self.get_target_bid(exchange.get_ticker(pair)) amount = stake_amount / buy_limit order_id = exchange.buy(pair, buy_limit, amount) stake_amount_fiat = self.fiat_converter.convert_amount( stake_amount, self.config['stake_currency'], self.config['fiat_display_currency']) # Create trade entity and return self.rpc.send_msg( '*{}:* Buying [{}]({}) with limit `{:.8f} ({:.6f} {}, {:.3f} {})` ' .format(exchange.get_name().upper(), pair.replace('_', '/'), exchange.get_pair_detail_url(pair), buy_limit, stake_amount, self.config['stake_currency'], stake_amount_fiat, self.config['fiat_display_currency'])) # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL trade = Trade(pair=pair, stake_amount=stake_amount, amount=amount, fee=exchange.get_fee(), open_rate=buy_limit, open_date=datetime.utcnow(), exchange=exchange.get_name().upper(), open_order_id=order_id) Trade.session.add(trade) Trade.session.flush() return True
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, limit_buy_order, limit_sell_order, markets, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.fiat_convert.Market', ticker=MagicMock(return_value={'price_usd': 15000.0}), ) patch_exchange(mocker) 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', get_ticker=ticker, get_fee=fee, markets=PropertyMock(return_value=markets) ) freqtradebot = FreqtradeBot(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() with pytest.raises(RPCException, match=r'.*no closed trade*'): rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) # Create some test data freqtradebot.create_trades() 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', get_ticker=ticker_sell_up ) trade.update(limit_sell_order) trade.close_date = datetime.utcnow() trade.is_open = False freqtradebot.create_trades() 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', get_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.632e-05) assert prec_satoshi(stats['profit_all_percent'], 2.81) assert prec_satoshi(stats['profit_all_fiat'], 0.8448) 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.exchange.Exchange.get_ticker', MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available"))) # invalidate ticker cache rpc._freqtrade.exchange._cached_ticker = {} 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'])