diff --git a/README.md b/README.md index be6bf0975..286591064 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,12 @@ use the `last` price and values between those interpolate between ask and last price. Using `ask` price will guarantee quick success in bid, but bot will also end up paying more then would probably have been necessary. +`fiat_currency` set the fiat to use for the conversion form coin to +fiat in Telegram. The valid value are: "AUD", "BRL", "CAD", "CHF", +"CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", +"INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", +"RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD". + The other values should be self-explanatory, if not feel free to raise a github issue. diff --git a/config.json.example b/config.json.example index cae64aab5..f94e423eb 100644 --- a/config.json.example +++ b/config.json.example @@ -2,6 +2,7 @@ "max_open_trades": 3, "stake_currency": "BTC", "stake_amount": 0.05, + "fiat_display_currency": "USD", "dry_run": false, "minimal_roi": { "40": 0.0, diff --git a/freqtrade/fiat_convert.py b/freqtrade/fiat_convert.py new file mode 100644 index 000000000..567835eed --- /dev/null +++ b/freqtrade/fiat_convert.py @@ -0,0 +1,156 @@ +import logging +import time +from pymarketcap import Pymarketcap + +logger = logging.getLogger(__name__) + + +class CryptoFiat(): + # Constants + CACHE_DURATION = 6 * 60 * 60 # 6 hours + + def __init__(self, crypto_symbol: str, fiat_symbol: str, price: float) -> None: + """ + Create an object that will contains the price for a crypto-currency in fiat + :param crypto_symbol: Crypto-currency you want to convert (e.g BTC) + :param fiat_symbol: FIAT currency you want to convert to (e.g USD) + :param price: Price in FIAT + """ + + # Public attributes + self.crypto_symbol = None + self.fiat_symbol = None + self.price = 0.0 + + # Private attributes + self._expiration = 0 + + self.crypto_symbol = crypto_symbol.upper() + self.fiat_symbol = fiat_symbol.upper() + self.set_price(price=price) + + def set_price(self, price: float) -> None: + """ + Set the price of the Crypto-currency in FIAT and set the expiration time + :param price: Price of the current Crypto currency in the fiat + :return: None + """ + self.price = price + self._expiration = time.time() + self.CACHE_DURATION + + def is_expired(self) -> bool: + """ + Return if the current price is still valid or needs to be refreshed + :return: bool, true the price is expired and needs to be refreshed, false the price is + still valid + """ + return self._expiration - time.time() <= 0 + + +class CryptoToFiatConverter(): + # Constants + SUPPORTED_FIAT = [ + "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", + "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", + "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", + "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD" + ] + + def __init__(self) -> None: + self._coinmarketcap = Pymarketcap() + self._pairs = [] + + def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float: + """ + Convert an amount of crypto-currency to fiat + :param crypto_amount: amount of crypto-currency to convert + :param crypto_symbol: crypto-currency used + :param fiat_symbol: fiat to convert to + :return: float, value in fiat of the crypto-currency amount + """ + price = self.get_price(crypto_symbol=crypto_symbol, fiat_symbol=fiat_symbol) + return float(crypto_amount) * float(price) + + def get_price(self, crypto_symbol: str, fiat_symbol: str) -> float: + """ + Return the price of the Crypto-currency in Fiat + :param crypto_symbol: Crypto-currency you want to convert (e.g BTC) + :param fiat_symbol: FIAT currency you want to convert to (e.g USD) + :return: Price in FIAT + """ + crypto_symbol = crypto_symbol.upper() + fiat_symbol = fiat_symbol.upper() + + # Check if the fiat convertion you want is supported + if not self._is_supported_fiat(fiat=fiat_symbol): + raise ValueError('The fiat {} is not supported.'.format(fiat_symbol)) + + # Get the pair that interest us and return the price in fiat + for pair in self._pairs: + if pair.crypto_symbol == crypto_symbol and pair.fiat_symbol == fiat_symbol: + # If the price is expired we refresh it, avoid to call the API all the time + if pair.is_expired(): + pair.set_price( + price=self._find_price( + crypto_symbol=pair.crypto_symbol, + fiat_symbol=pair.fiat_symbol + ) + ) + + # return the last price we have for this pair + return pair.price + + # The pair does not exist, so we create it and return the price + return self._add_pair( + crypto_symbol=crypto_symbol, + fiat_symbol=fiat_symbol, + price=self._find_price( + crypto_symbol=crypto_symbol, + fiat_symbol=fiat_symbol + ) + ) + + def _add_pair(self, crypto_symbol: str, fiat_symbol: str, price: float) -> float: + """ + :param crypto_symbol: Crypto-currency you want to convert (e.g BTC) + :param fiat_symbol: FIAT currency you want to convert to (e.g USD) + :return: price in FIAT + """ + self._pairs.append( + CryptoFiat( + crypto_symbol=crypto_symbol, + fiat_symbol=fiat_symbol, + price=price + ) + ) + + return price + + def _is_supported_fiat(self, fiat: str) -> bool: + """ + Check if the FIAT your want to convert to is supported + :param fiat: FIAT to check (e.g USD) + :return: bool, True supported, False not supported + """ + + fiat = fiat.upper() + + return fiat in self.SUPPORTED_FIAT + + def _find_price(self, crypto_symbol: str, fiat_symbol: str) -> float: + """ + Call CoinMarketCap API to retrieve the price in the FIAT + :param crypto_symbol: Crypto-currency you want to convert (e.g BTC) + :param fiat_symbol: FIAT currency you want to convert to (e.g USD) + :return: float, price of the crypto-currency in Fiat + """ + # Check if the fiat convertion you want is supported + if not self._is_supported_fiat(fiat=fiat_symbol): + raise ValueError('The fiat {} is not supported.'.format(fiat_symbol)) + + return float( + self._coinmarketcap.ticker( + currency=crypto_symbol, + convert=fiat_symbol + )['price_' + fiat_symbol.lower()] + ) diff --git a/freqtrade/main.py b/freqtrade/main.py index 212e9f0e9..47ff599b2 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -17,6 +17,7 @@ from freqtrade.analyze import get_signal, SignalType from freqtrade.misc import State, get_state, update_state, parse_args, throttle, \ load_config from freqtrade.persistence import Trade +from freqtrade.fiat_convert import CryptoToFiatConverter logger = logging.getLogger('freqtrade') @@ -119,14 +120,28 @@ def execute_sell(trade: Trade, limit: float) -> None: trade.open_order_id = order_id fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2) - rpc.send_msg('*{}:* Selling [{}]({}) with limit `{:.8f} (profit: ~{:.2f}%, {:.8f})`'.format( - trade.exchange, - trade.pair.replace('_', '/'), - exchange.get_pair_detail_url(trade.pair), - limit, - fmt_exp_profit, - trade.calc_profit(rate=limit), - )) + profit_trade = trade.calc_profit(rate=limit) + + fiat_converter = CryptoToFiatConverter() + profit_fiat = fiat_converter.convert_amount( + profit_trade, + _CONF['stake_currency'], + _CONF['fiat_display_currency'] + ) + + rpc.send_msg('*{exchange}:* Selling [{pair}]({pair_url}) with limit `{limit:.8f}`' + '` (profit: ~{profit_percent:.2f}%, {profit_coin:.8f} {coin}`' + '` / {profit_fiat:.3f} {fiat})`'.format( + exchange=trade.exchange, + pair=trade.pair.replace('_', '/'), + pair_url=exchange.get_pair_detail_url(trade.pair), + limit=limit, + profit_percent=fmt_exp_profit, + profit_coin=profit_trade, + coin=_CONF['stake_currency'], + profit_fiat=profit_fiat, + fiat=_CONF['fiat_display_currency'], + )) Trade.session.flush() diff --git a/freqtrade/misc.py b/freqtrade/misc.py index b01fd9fe9..57e7c6735 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -208,6 +208,7 @@ CONF_SCHEMA = { 'max_open_trades': {'type': 'integer', 'minimum': 1}, 'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT']}, 'stake_amount': {'type': 'number', 'minimum': 0.0005}, + 'fiat_display_currency': {'type': 'string', 'enum': ['USD', 'EUR', 'CAD', 'SGD']}, 'dry_run': {'type': 'boolean'}, 'minimal_roi': { 'type': 'object', diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index d62d491e1..79290d159 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -15,6 +15,7 @@ from telegram.ext import CommandHandler, Updater from freqtrade import exchange, __version__ from freqtrade.misc import get_state, State, update_state from freqtrade.persistence import Trade +from freqtrade.fiat_convert import CryptoToFiatConverter # Remove noisy log messages logging.getLogger('requests.packages.urllib3').setLevel(logging.INFO) @@ -23,6 +24,7 @@ logger = logging.getLogger(__name__) _UPDATER: Updater = None _CONF = {} +_FIAT_CONVERT = CryptoToFiatConverter() def init(config: dict) -> None: @@ -242,8 +244,28 @@ def _daily(bot: Bot, update: Update) -> None: curdayprofit = sum(trade.calc_profit() for trade in trades) profit_days[date.fromordinal(today - day)] = format(curdayprofit, '.8f') - stats = [[key, str(value) + ' BTC'] for key, value in profit_days.items()] - stats = tabulate(stats, headers=['Day', 'Profit'], tablefmt='simple') + stats = [ + [ + key, + '{value:.8f} {symbol}'.format(value=float(value), symbol=_CONF['stake_currency']), + '{value:.3f} {symbol}'.format( + value=_FIAT_CONVERT.convert_amount( + value, + _CONF['stake_currency'], + _CONF['fiat_display_currency'] + ), + symbol=_CONF['fiat_display_currency'] + ) + ] + for key, value in profit_days.items() + ] + stats = tabulate(stats, + headers=[ + 'Day', + 'Profit {}'.format(_CONF['stake_currency']), + 'Profit {}'.format(_CONF['fiat_display_currency']) + ], + tablefmt='simple') message = 'Daily Profit over the last {} days:\n
{}
'.format(timescale, stats) send_msg(message, bot=bot, parse_mode=ParseMode.HTML) @@ -260,9 +282,9 @@ def _profit(bot: Bot, update: Update) -> None: """ trades = Trade.query.order_by(Trade.id).all() - profit_all_btc = [] + profit_all_coin = [] profit_all_percent = [] - profit_btc_closed = [] + profit_closed_coin = [] profit_closed_percent = [] durations = [] @@ -276,14 +298,14 @@ def _profit(bot: Bot, update: Update) -> None: if not trade.is_open: profit_percent = trade.calc_profit_percent() - profit_btc_closed.append(trade.calc_profit()) + profit_closed_coin.append(trade.calc_profit()) profit_closed_percent.append(profit_percent) else: # Get current rate current_rate = exchange.get_ticker(trade.pair)['bid'] profit_percent = trade.calc_profit_percent(rate=current_rate) - profit_all_btc.append(trade.calc_profit(rate=Decimal(trade.close_rate or current_rate))) + profit_all_coin.append(trade.calc_profit(rate=Decimal(trade.close_rate or current_rate))) profit_all_percent.append(profit_percent) best_pair = Trade.session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ @@ -297,19 +319,46 @@ def _profit(bot: Bot, update: Update) -> None: return bp_pair, bp_rate = best_pair + + # Prepare data to display + profit_closed_coin = round(sum(profit_closed_coin), 8) + profit_closed_percent = round(sum(profit_closed_percent) * 100, 2) + profit_closed_fiat = _FIAT_CONVERT.convert_amount( + profit_closed_coin, + _CONF['stake_currency'], + _CONF['fiat_display_currency'] + ) + profit_all_coin = round(sum(profit_all_coin), 8) + profit_all_percent = round(sum(profit_all_percent) * 100, 2) + profit_all_fiat = _FIAT_CONVERT.convert_amount( + profit_all_coin, + _CONF['stake_currency'], + _CONF['fiat_display_currency'] + ) + + # Message to display markdown_msg = """ -*ROI Trade closed:* `{profit_closed_btc:.8f} BTC ({profit_closed_percent:.2f}%)` -*ROI All trades:* `{profit_all_btc:.8f} BTC ({profit_all_percent:.2f}%)` +*ROI:* Close trades + ∙ `{profit_closed_coin:.8f} {coin} ({profit_closed_percent:.2f}%)` + ∙ `{profit_closed_fiat:.3f} {fiat}` +*ROI:* All trades + ∙ `{profit_all_coin:.8f} {coin} ({profit_all_percent:.2f}%)` + ∙ `{profit_all_fiat:.3f} {fiat}` + *Total Trade Count:* `{trade_count}` *First Trade opened:* `{first_trade_date}` *Latest Trade opened:* `{latest_trade_date}` *Avg. Duration:* `{avg_duration}` *Best Performing:* `{best_pair}: {best_rate:.2f}%` """.format( - profit_closed_btc=round(sum(profit_btc_closed), 8), - profit_closed_percent=round(sum(profit_closed_percent) * 100, 2), - profit_all_btc=round(sum(profit_all_btc), 8), - profit_all_percent=round(sum(profit_all_percent) * 100, 2), + coin=_CONF['stake_currency'], + fiat=_CONF['fiat_display_currency'], + profit_closed_coin=profit_closed_coin, + profit_closed_percent=profit_closed_percent, + profit_closed_fiat=profit_closed_fiat, + profit_all_coin=profit_all_coin, + profit_all_percent=profit_all_percent, + profit_all_fiat=profit_all_fiat, trade_count=len(trades), first_trade_date=arrow.get(trades[0].open_date).humanize(), latest_trade_date=arrow.get(trades[-1].open_date).humanize(), diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index b034b8c9f..c8ecd39c7 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -16,6 +16,7 @@ def default_conf(): "max_open_trades": 1, "stake_currency": "BTC", "stake_amount": 0.001, + "fiat_display_currency": "USD", "dry_run": True, "minimal_roi": { "40": 0.0, diff --git a/freqtrade/tests/test_fiat_convert.py b/freqtrade/tests/test_fiat_convert.py new file mode 100644 index 000000000..13597f3a3 --- /dev/null +++ b/freqtrade/tests/test_fiat_convert.py @@ -0,0 +1,111 @@ +# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors, C0103 + +import time +import pytest +from unittest.mock import MagicMock + +from freqtrade.fiat_convert import CryptoToFiatConverter, CryptoFiat + + +def test_pair_convertion_object(): + pair_convertion = CryptoFiat( + crypto_symbol='btc', + fiat_symbol='usd', + price=12345.0 + ) + + # Check the cache duration is 6 hours + assert pair_convertion.CACHE_DURATION == 6 * 60 * 60 + + # Check a regular usage + assert pair_convertion.crypto_symbol == 'BTC' + assert pair_convertion.fiat_symbol == 'USD' + assert pair_convertion.price == 12345.0 + assert pair_convertion.is_expired() is False + + # Update the expiration time (- 2 hours) and check the behavior + pair_convertion._expiration = time.time() - 2 * 60 * 60 + assert pair_convertion.is_expired() is True + + # Check set price behaviour + time_reference = time.time() + pair_convertion.CACHE_DURATION + pair_convertion.set_price(price=30000.123) + assert pair_convertion.is_expired() is False + assert pair_convertion._expiration >= time_reference + assert pair_convertion.price == 30000.123 + + +def test_fiat_convert_is_supported(): + fiat_convert = CryptoToFiatConverter() + assert fiat_convert._is_supported_fiat(fiat='USD') is True + assert fiat_convert._is_supported_fiat(fiat='usd') is True + assert fiat_convert._is_supported_fiat(fiat='abc') is False + assert fiat_convert._is_supported_fiat(fiat='ABC') is False + + +def test_fiat_convert_add_pair(): + fiat_convert = CryptoToFiatConverter() + + assert len(fiat_convert._pairs) == 0 + + fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='usd', price=12345.0) + assert len(fiat_convert._pairs) == 1 + assert fiat_convert._pairs[0].crypto_symbol == 'BTC' + assert fiat_convert._pairs[0].fiat_symbol == 'USD' + assert fiat_convert._pairs[0].price == 12345.0 + + fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='Eur', price=13000.2) + assert len(fiat_convert._pairs) == 2 + assert fiat_convert._pairs[1].crypto_symbol == 'BTC' + assert fiat_convert._pairs[1].fiat_symbol == 'EUR' + assert fiat_convert._pairs[1].price == 13000.2 + + +def test_fiat_convert_find_price(mocker): + api_mock = MagicMock(return_value={ + 'price_usd': 12345.0, + 'price_eur': 13000.2 + }) + mocker.patch('freqtrade.fiat_convert.Pymarketcap.ticker', api_mock) + fiat_convert = CryptoToFiatConverter() + + with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'): + fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='ABC') + + assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 12345.0 + assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 12345.0 + assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 13000.2 + + +def test_fiat_convert_get_price(mocker): + api_mock = MagicMock(return_value={ + 'price_usd': 28000.0, + 'price_eur': 15000.0 + }) + mocker.patch('freqtrade.fiat_convert.Pymarketcap.ticker', api_mock) + + fiat_convert = CryptoToFiatConverter() + + with pytest.raises(ValueError, match=r'The fiat US DOLLAR is not supported.'): + fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='US Dollar') + + # Check the value return by the method + assert len(fiat_convert._pairs) == 0 + assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 28000.0 + assert fiat_convert._pairs[0].crypto_symbol == 'BTC' + assert fiat_convert._pairs[0].fiat_symbol == 'USD' + assert fiat_convert._pairs[0].price == 28000.0 + assert fiat_convert._pairs[0]._expiration is not 0 + assert len(fiat_convert._pairs) == 1 + + # Verify the cached is used + fiat_convert._pairs[0].price = 9867.543 + expiration = fiat_convert._pairs[0]._expiration + assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 9867.543 + assert fiat_convert._pairs[0]._expiration == expiration + + # Verify the cache expiration + expiration = time.time() - 2 * 60 * 60 + fiat_convert._pairs[0]._expiration = expiration + assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 28000.0 + assert fiat_convert._pairs[0]._expiration is not expiration diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index d9b774a9d..7c06936c0 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -192,6 +192,9 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): }), buy=MagicMock(return_value='mocked_limit_buy'), sell=MagicMock(return_value='mocked_limit_sell')) + mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', + ticker=MagicMock(return_value={'price_usd': 15000.0}), + _cache_symbols=MagicMock(return_value={'BTC': 1})) init(default_conf, create_engine('sqlite://')) create_trade(0.001) diff --git a/freqtrade/tests/test_rpc_telegram.py b/freqtrade/tests/test_rpc_telegram.py index 3b6197f23..5a5765ba2 100644 --- a/freqtrade/tests/test_rpc_telegram.py +++ b/freqtrade/tests/test_rpc_telegram.py @@ -164,6 +164,9 @@ def test_profit_handle( mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) + mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', + ticker=MagicMock(return_value={'price_usd': 15000.0}), + _cache_symbols=MagicMock(return_value={'BTC': 1})) init(default_conf, create_engine('sqlite://')) _profit(bot=MagicMock(), update=update) @@ -194,9 +197,14 @@ def test_profit_handle( _profit(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 - assert '*ROI Trade closed:* `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] - assert '*ROI All trades:* `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] - assert 'Best Performing:* `BTC_ETH: 6.20%`' in msg_mock.call_args_list[-1][0][0] + assert '*ROI:* Close trades' in msg_mock.call_args_list[-1][0][0] + assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] + assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0] + assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] + assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] + assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0] + + assert '*Best Performing:* `BTC_ETH: 6.20%`' in msg_mock.call_args_list[-1][0][0] def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker): @@ -210,6 +218,9 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker): mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) + mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', + ticker=MagicMock(return_value={'price_usd': 15000.0}), + _cache_symbols=MagicMock(return_value={'BTC': 1})) init(default_conf, create_engine('sqlite://')) # Create some test data @@ -228,7 +239,9 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker): assert rpc_mock.call_count == 2 assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] - assert '0.00001172 (profit: ~6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0] + assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] + assert 'profit: ~6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0] + assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0] def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, mocker): @@ -242,6 +255,9 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) + mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', + ticker=MagicMock(return_value={'price_usd': 15000.0}), + _cache_symbols=MagicMock(return_value={'BTC': 1})) init(default_conf, create_engine('sqlite://')) # Create some test data @@ -260,7 +276,9 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m assert rpc_mock.call_count == 2 assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] - assert '0.00001044 (profit: ~-5.48%, -0.00005492)' in rpc_mock.call_args_list[-1][0][0] + assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] + assert 'profit: ~-5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] + assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0] def test_exec_forcesell_open_orders(default_conf, ticker, mocker): @@ -298,6 +316,9 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker): mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) + mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', + ticker=MagicMock(return_value={'price_usd': 15000.0}), + _cache_symbols=MagicMock(return_value={'BTC': 1})) init(default_conf, create_engine('sqlite://')) # Create some test data @@ -310,7 +331,9 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker): assert rpc_mock.call_count == 4 for args in rpc_mock.call_args_list: - assert '0.00001098 (profit: ~-0.59%, -0.00000591)' in args[0][0] + assert '0.00001098' in args[0][0] + assert 'profit: ~-0.59%, -0.00000591 BTC' in args[0][0] + assert '-0.089 USD' in args[0][0] def test_forcesell_handle_invalid(default_conf, update, mocker): @@ -397,6 +420,9 @@ def test_daily_handle( mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) + mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', + ticker=MagicMock(return_value={'price_usd': 15000.0}), + _cache_symbols=MagicMock(return_value={'BTC': 1})) init(default_conf, create_engine('sqlite://')) # Create some test data @@ -418,7 +444,9 @@ def test_daily_handle( _daily(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'Daily' in msg_mock.call_args_list[0][0][0] - assert str(datetime.utcnow().date()) + ' 0.00006217 BTC' in msg_mock.call_args_list[0][0][0] + assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0] + assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0] + assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0] # Try invalid data msg_mock.reset_mock() diff --git a/requirements.txt b/requirements.txt index 712533cb5..b37a15c79 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ hyperopt==0.1 # do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325 networkx==1.11 tabulate==0.8.2 +pymarketcap==3.3.139 # Required for plotting data #matplotlib==2.1.0 diff --git a/setup.py b/setup.py index 1514f6405..e53606dea 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ setup(name='freqtrade', 'TA-Lib', 'tabulate', 'cachetools', + 'pymarketcap', ], include_package_data=True, zip_safe=False,