From ff6b0fc1c9266f2177d8d59cab9c00dc2fcd03a0 Mon Sep 17 00:00:00 2001
From: Gerald Lonlas <g.lonlas@gmail.com>
Date: Sun, 24 Dec 2017 23:51:41 -0800
Subject: [PATCH] Display profits in fiat

---
 README.md                            |   6 ++
 config.json.example                  |   1 +
 freqtrade/fiat_convert.py            | 156 +++++++++++++++++++++++++++
 freqtrade/main.py                    |  31 ++++--
 freqtrade/misc.py                    |   1 +
 freqtrade/rpc/telegram.py            |  73 ++++++++++---
 freqtrade/tests/conftest.py          |   1 +
 freqtrade/tests/test_fiat_convert.py | 111 +++++++++++++++++++
 freqtrade/tests/test_main.py         |   3 +
 freqtrade/tests/test_rpc_telegram.py |  42 ++++++--
 requirements.txt                     |   1 +
 setup.py                             |   1 +
 12 files changed, 400 insertions(+), 27 deletions(-)
 create mode 100644 freqtrade/fiat_convert.py
 create mode 100644 freqtrade/tests/test_fiat_convert.py

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 = '<b>Daily Profit over the last {} days</b>:\n<pre>{}</pre>'.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,