diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 385dac1d1..c27cd875c 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -153,7 +153,6 @@ CONF_SCHEMA = { 'max_open_trades', 'stake_currency', 'stake_amount', - 'fiat_display_currency', 'dry_run', 'bid_strategy', 'telegram' diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c0263c6fb..3527fba81 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -16,7 +16,6 @@ from cachetools import TTLCache, cached from freqtrade import (DependencyException, OperationalException, TemporaryError, __version__, constants, persistence) from freqtrade.exchange import Exchange -from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.persistence import Trade from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.state import State @@ -50,7 +49,6 @@ class FreqtradeBot(object): # Init objects self.config = config self.strategy: IStrategy = StrategyResolver(self.config).strategy - self.fiat_converter = CryptoToFiatConverter() self.rpc: RPCManager = RPCManager(self) self.persistence = None self.exchange = Exchange(self.config) @@ -346,7 +344,7 @@ class FreqtradeBot(object): pair_s = pair.replace('_', '/') pair_url = self.exchange.get_pair_detail_url(pair) stake_currency = self.config['stake_currency'] - fiat_currency = self.config['fiat_display_currency'] + fiat_currency = self.config.get('fiat_display_currency', None) # Calculate amount buy_limit = self.get_target_bid(self.exchange.get_ticker(pair)) @@ -363,12 +361,6 @@ class FreqtradeBot(object): order_id = self.exchange.buy(pair, buy_limit, amount)['id'] - stake_amount_fiat = self.fiat_converter.convert_amount( - stake_amount, - stake_currency, - fiat_currency - ) - self.rpc.send_msg({ 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': self.exchange.name.capitalize(), @@ -376,7 +368,6 @@ class FreqtradeBot(object): 'market_url': pair_url, 'limit': buy_limit, 'stake_amount': stake_amount, - 'stake_amount_fiat': stake_amount_fiat, 'stake_currency': stake_currency, 'fiat_currency': fiat_currency }) @@ -648,14 +639,7 @@ class FreqtradeBot(object): if 'stake_currency' in self.config and 'fiat_display_currency' in self.config: stake_currency = self.config['stake_currency'] fiat_currency = self.config['fiat_display_currency'] - fiat_converter = CryptoToFiatConverter() - profit_fiat = fiat_converter.convert_amount( - profit_trade, - stake_currency, - fiat_currency, - ) msg.update({ - 'profit_fiat': profit_fiat, 'stake_currency': stake_currency, 'fiat_currency': fiat_currency, }) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 27ec7ea7a..f58fbae9a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -6,13 +6,14 @@ from abc import abstractmethod from datetime import timedelta, datetime, date from decimal import Decimal from enum import Enum -from typing import Dict, Any, List +from typing import Dict, Any, List, Optional import arrow import sqlalchemy as sql from numpy import mean, nan_to_num from pandas import DataFrame +from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.misc import shorten_date from freqtrade.persistence import Trade from freqtrade.state import State @@ -49,6 +50,9 @@ class RPC(object): """ RPC class can be used to have extra feature, like bot data, and access to DB data """ + # Bind _fiat_converter if needed in each RPC handler + _fiat_converter: Optional[CryptoToFiatConverter] = None + def __init__(self, freqtrade) -> None: """ Initializes all enabled rpc modules @@ -142,7 +146,6 @@ class RPC(object): if not (isinstance(timescale, int) and timescale > 0): raise RPCException('timescale must be an integer greater than 0') - fiat = self._freqtrade.fiat_converter for day in range(0, timescale): profitday = today - timedelta(days=day) trades = Trade.query \ @@ -165,11 +168,11 @@ class RPC(object): symbol=stake_currency ), '{value:.3f} {symbol}'.format( - value=fiat.convert_amount( + value=self._fiat_converter.convert_amount( value['amount'], stake_currency, fiat_display_currency - ), + ) if self._fiat_converter else 0, symbol=fiat_display_currency ), '{value} trade{s}'.format( @@ -224,24 +227,23 @@ class RPC(object): bp_pair, bp_rate = best_pair - # FIX: we want to keep fiatconverter in a state/environment, - # doing this will utilize its caching functionallity, instead we reinitialize it here - fiat = self._freqtrade.fiat_converter # Prepare data to display profit_closed_coin_sum = round(sum(profit_closed_coin), 8) profit_closed_percent = round(nan_to_num(mean(profit_closed_percent)) * 100, 2) - profit_closed_fiat = fiat.convert_amount( + profit_closed_fiat = self._fiat_converter.convert_amount( profit_closed_coin_sum, stake_currency, fiat_display_currency - ) + ) if self._fiat_converter else 0 + profit_all_coin_sum = round(sum(profit_all_coin), 8) profit_all_percent = round(nan_to_num(mean(profit_all_percent)) * 100, 2) - profit_all_fiat = fiat.convert_amount( + profit_all_fiat = self._fiat_converter.convert_amount( profit_all_coin_sum, stake_currency, fiat_display_currency - ) + ) if self._fiat_converter else 0 + num = float(len(durations) or 1) return { 'profit_closed_coin': profit_closed_coin_sum, @@ -285,9 +287,9 @@ class RPC(object): if total == 0.0: raise RPCException('all balances are zero') - fiat = self._freqtrade.fiat_converter symbol = fiat_display_currency - value = fiat.convert_amount(total, 'BTC', symbol) + value = self._fiat_converter.convert_amount(total, 'BTC', + symbol) if self._fiat_converter else 0 return { 'currencies': output, 'total': total, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 02b74358e..3b5ce3f74 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -12,6 +12,7 @@ from telegram.error import NetworkError, TelegramError from telegram.ext import CommandHandler, Updater from freqtrade.__init__ import __version__ +from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.rpc import RPC, RPCException, RPCMessageType logger = logging.getLogger(__name__) @@ -66,6 +67,8 @@ class Telegram(RPC): self._updater: Updater = None self._config = freqtrade.config self._init() + if self._config.get('fiat_display_currency', None): + self._fiat_converter = CryptoToFiatConverter() def _init(self) -> None: """ @@ -114,11 +117,19 @@ class Telegram(RPC): """ Send a message to telegram channel """ if msg['type'] == RPCMessageType.BUY_NOTIFICATION: + if self._fiat_converter: + msg['stake_amount_fiat'] = self._fiat_converter.convert_amount( + msg['stake_amount'], msg['stake_currency'], msg['fiat_currency']) + else: + msg['stake_amount_fiat'] = 0 + message = "*{exchange}:* Buying [{pair}]({market_url})\n" \ "with limit `{limit:.8f}\n" \ - "({stake_amount:.6f} {stake_currency}," \ - "{stake_amount_fiat:.3f} {fiat_currency})`" \ - .format(**msg) + "({stake_amount:.6f} {stake_currency}".format(**msg) + + if msg.get('fiat_currency', None): + message += ",{stake_amount_fiat:.3f} {fiat_currency}".format(**msg) + message += ")`" elif msg['type'] == RPCMessageType.SELL_NOTIFICATION: msg['amount'] = round(msg['amount'], 8) @@ -133,8 +144,10 @@ class Telegram(RPC): # Check if all sell properties are available. # This might not be the case if the message origin is triggered by /forcesell - if all(prop in msg for prop in ['gain', 'profit_fiat', - 'fiat_currency', 'stake_currency']): + if (all(prop in msg for prop in ['gain', 'fiat_currency', 'stake_currency']) + and self._fiat_converter): + msg['profit_fiat'] = self._fiat_converter.convert_amount( + msg['profit_amount'], msg['stake_currency'], msg['fiat_currency']) message += '` ({gain}: {profit_amount:.8f} {stake_currency}`' \ '` / {profit_fiat:.3f} {fiat_currency})`'.format(**msg) @@ -213,7 +226,7 @@ class Telegram(RPC): :return: None """ stake_cur = self._config['stake_currency'] - fiat_disp_cur = self._config['fiat_display_currency'] + fiat_disp_cur = self._config.get('fiat_display_currency', '') try: timescale = int(update.message.text.replace('/daily', '').strip()) except (TypeError, ValueError): @@ -246,7 +259,7 @@ class Telegram(RPC): :return: None """ stake_cur = self._config['stake_currency'] - fiat_disp_cur = self._config['fiat_display_currency'] + fiat_disp_cur = self._config.get('fiat_display_currency', '') try: stats = self._rpc_trade_statistics( @@ -285,7 +298,7 @@ class Telegram(RPC): def _balance(self, bot: Bot, update: Update) -> None: """ Handler for /balance """ try: - result = self._rpc_balance(self._config['fiat_display_currency']) + result = self._rpc_balance(self._config.get('fiat_display_currency', '')) output = '' for currency in result['currencies']: output += "*{currency}:*\n" \ diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index e6cfceae7..e3530a149 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -9,6 +9,7 @@ from unittest.mock import MagicMock, ANY import pytest +from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.rpc import RPC, RPCException @@ -124,7 +125,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, fiat_display_currency = default_conf['fiat_display_currency'] rpc = RPC(freqtradebot) - + rpc._fiat_converter = CryptoToFiatConverter() # Create some test data freqtradebot.create_trade() trade = Trade.query.first() @@ -164,7 +165,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, 'freqtrade.fiat_convert.Market', ticker=MagicMock(return_value={'price_usd': 15000.0}), ) - mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=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', @@ -180,6 +181,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, 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) @@ -313,7 +315,7 @@ def test_rpc_balance_handle(default_conf, mocker): 'freqtrade.fiat_convert.Market', ticker=MagicMock(return_value={'price_usd': 15000.0}), ) - mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=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', @@ -324,6 +326,7 @@ def test_rpc_balance_handle(default_conf, mocker): freqtradebot = FreqtradeBot(default_conf) patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) + rpc._fiat_converter = CryptoToFiatConverter() result = rpc._rpc_balance(default_conf['fiat_display_currency']) assert prec_satoshi(result['total'], 12) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 3336810bd..b2cab6d37 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -362,7 +362,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, """ patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch( - 'freqtrade.fiat_convert.CryptoToFiatConverter._find_price', + 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0 ) mocker.patch.multiple( @@ -474,7 +474,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, Test _profit() method """ patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) - mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) + mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch.multiple( 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), @@ -742,7 +742,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee, Test _forcesell() method """ patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) - mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) + mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch.multiple( @@ -783,7 +783,6 @@ def test_forcesell_handle(default_conf, update, ticker, fee, 'current_rate': 1.172e-05, 'profit_amount': 6.126e-05, 'profit_percent': 0.06110514, - 'profit_fiat': 0.9189, 'stake_currency': 'BTC', 'fiat_currency': 'USD', } == last_msg @@ -841,7 +840,6 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, 'current_rate': 1.044e-05, 'profit_amount': -5.492e-05, 'profit_percent': -0.05478343, - 'profit_fiat': -0.8238000000000001, 'stake_currency': 'BTC', 'fiat_currency': 'USD', } == last_msg @@ -890,7 +888,6 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker 'current_rate': 1.098e-05, 'profit_amount': -5.91e-06, 'profit_percent': -0.00589292, - 'profit_fiat': -0.08865, 'stake_currency': 'BTC', 'fiat_currency': 'USD', } == msg @@ -1122,6 +1119,8 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) telegram = Telegram(freqtradebot) + old_convamount = telegram._fiat_converter.convert_amount + telegram._fiat_converter.convert_amount = lambda a, b, c: -24.812 telegram.send_msg({ 'type': RPCMessageType.SELL_NOTIFICATION, 'exchange': 'Binance', @@ -1134,7 +1133,6 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'current_rate': 3.201e-05, 'profit_amount': -0.05746268, 'profit_percent': -0.57405275, - 'profit_fiat': -24.81204044792, 'stake_currency': 'ETH', 'fiat_currency': 'USD' }) @@ -1170,6 +1168,8 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: '*Open Rate:* `0.00007500`\n' \ '*Current Rate:* `0.00003201`\n' \ '*Profit:* `-57.41%`' + # Reset singleton function to avoid random breaks + telegram._fiat_converter.convert_amount = old_convamount def test_send_msg_status_notification(default_conf, mocker) -> None: @@ -1203,6 +1203,68 @@ def test_send_msg_unknown_type(default_conf, mocker) -> None: }) +def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: + del default_conf['fiat_display_currency'] + msg_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.rpc.telegram.Telegram', + _init=MagicMock(), + _send_msg=msg_mock + ) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + telegram = Telegram(freqtradebot) + telegram.send_msg({ + 'type': RPCMessageType.BUY_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH', + 'limit': 1.099e-05, + 'stake_amount': 0.001, + 'stake_amount_fiat': 0.0, + 'stake_currency': 'BTC', + 'fiat_currency': None + }) + assert msg_mock.call_args[0][0] \ + == '*Bittrex:* Buying [ETH/BTC](https://bittrex.com/Market/Index?MarketName=BTC-ETH)\n' \ + 'with limit `0.00001099\n' \ + '(0.001000 BTC)`' + + +def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: + del default_conf['fiat_display_currency'] + msg_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.rpc.telegram.Telegram', + _init=MagicMock(), + _send_msg=msg_mock + ) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + telegram = Telegram(freqtradebot) + telegram.send_msg({ + 'type': RPCMessageType.SELL_NOTIFICATION, + 'exchange': 'Binance', + 'pair': 'KEY/ETH', + 'gain': 'loss', + 'market_url': 'https://www.binance.com/tradeDetail.html?symbol=KEY_ETH', + 'limit': 3.201e-05, + 'amount': 1333.3333333333335, + 'open_rate': 7.5e-05, + 'current_rate': 3.201e-05, + 'profit_amount': -0.05746268, + 'profit_percent': -0.57405275, + 'stake_currency': 'ETH', + 'fiat_currency': 'USD' + }) + assert msg_mock.call_args[0][0] \ + == '*Binance:* Selling [KEY/ETH]' \ + '(https://www.binance.com/tradeDetail.html?symbol=KEY_ETH)\n' \ + '*Limit:* `0.00003201`\n' \ + '*Amount:* `1333.33333333`\n' \ + '*Open Rate:* `0.00007500`\n' \ + '*Current Rate:* `0.00003201`\n' \ + '*Profit:* `-57.41%`' + + def test__send_msg(default_conf, mocker) -> None: """ Test send_msg() method diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 895603714..72f11abf9 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1353,7 +1353,6 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc get_fee=fee, get_markets=markets ) - mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -1386,7 +1385,6 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc 'current_rate': 1.172e-05, 'profit_amount': 6.126e-05, 'profit_percent': 0.06110514, - 'profit_fiat': 0.9189, 'stake_currency': 'BTC', 'fiat_currency': 'USD', } == last_msg @@ -1398,7 +1396,6 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, """ rpc_mock = patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch.multiple( 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), @@ -1439,7 +1436,6 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, 'current_rate': 1.044e-05, 'profit_amount': -5.492e-05, 'profit_percent': -0.05478343, - 'profit_fiat': -0.8238000000000001, 'stake_currency': 'BTC', 'fiat_currency': 'USD', } == last_msg diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 446945a07..80f5367b8 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -62,7 +62,6 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None: 'freqtrade.configuration.Configuration._load_config_file', lambda *args, **kwargs: default_conf ) - mocker.patch('freqtrade.freqtradebot.CryptoToFiatConverter', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) args = ['-c', 'config.json.example'] @@ -90,7 +89,6 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None: 'freqtrade.configuration.Configuration._load_config_file', lambda *args, **kwargs: default_conf ) - mocker.patch('freqtrade.freqtradebot.CryptoToFiatConverter', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) args = ['-c', 'config.json.example'] @@ -118,7 +116,6 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None: 'freqtrade.configuration.Configuration._load_config_file', lambda *args, **kwargs: default_conf ) - mocker.patch('freqtrade.freqtradebot.CryptoToFiatConverter', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) args = ['-c', 'config.json.example'] @@ -146,7 +143,6 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None: 'freqtrade.configuration.Configuration._load_config_file', lambda *args, **kwargs: default_conf ) - mocker.patch('freqtrade.freqtradebot.CryptoToFiatConverter', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) # Raise exception as side effect to avoid endless loop @@ -174,7 +170,6 @@ def test_reconfigure(mocker, default_conf) -> None: 'freqtrade.configuration.Configuration._load_config_file', lambda *args, **kwargs: default_conf ) - mocker.patch('freqtrade.freqtradebot.CryptoToFiatConverter', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) freqtrade = FreqtradeBot(default_conf)