Repository: freqtrade/freqtrade

Issue:
FTX Updated Fees for spot market 
Most FTX pairs spot markets are 

X/USD and in the past the fees were paid in USD but now with the latest update they are paid in X. 

This needs to be updated on freqtrade as right now Freqtrade thinks we bought Y amount of coins but we actually bought Y - Fee coins
activate "fee" parameter in config file
For exemple, on Binance, I use BNB to pay fees at 0.075% against 0.1%.
In documentation it is possible to set "fee" in config file but this parameter is only use with with backtesting.

### Steps to reproduce:

  1. Allow in Binance fees paiements with BNB
  2. Buy a pair in freqtrade
  3. Open database.sqlite
  4. In fee columns, you can see 0.1% and not 0.075%
  
### Observed Results:

Calculs of profits and loss are wrong

### Code to use to fix this problem
In "freqtrade/freqtradebot.py" file Line 424
  ```
        # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
        # fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
        if self.config.get('fee'):
            fee = self.config['fee']
        else:
            fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
  ```



Discussion:
Please include a bit more detail - it's not clear what the problem is from this description.

Also, please including the (api) output of an order (`fetch_order(order_id)`) (and if necessary the corresponding trades from `.fetch_my_trades(pair)` from ccxt) you're having the problem with, thanks.

Also - please include the CCXT version you're using while encountering this problem. the issue template is NOT there for fun ...
This is not related to freqtrade it self but FTX;

In the past any spot market trade paid fees in USD rather than BASE currency, FTX changed this and freqtrade still thinks he paid USD when in fact it paid base.


**If selling on a BTC/USD market, the proceeds currency would be USD. If buying, it would be BTC.**

**Our fills endpoint now has a feeCurrency field you can use to make sure you always interpret the fee currency correctly: https://docs.ftx.com/#fills. We strongly recommend updating your systems to account for it!**

I am aware of this - and freqtrade does handle this usually (binance does something similar) - therefore, letme ask again:
which version of ccxt are you using when you encountered this?
which error did you encounter (if it's with an assed bought, we'll need the logs from the section right after the trade was filled).

Also - since it'll then be related to a specific order / trade - Please provide us with the ccxt values for the corresponding order + trade.
Edit:

`2020-04-26 16:01:17,687 - freqtrade.rpc.rpc_manager - INFO - Sending rpc message: {'type': buy, 'exchange': 'Ftx', 'pair': 'BCH/USD', 'limit': 238.85, 'order_type': 'market', 'stake_amount': 214.0, 'stake_currency': 'USD', 'fiat_currency': 'USD', 'amount': 0.8959598074105087, 'open_date': datetime.datetime(2020, 4, 26, 16, 1, 17, 597740), 'current_rate': 238.85}`

Real amount of BCH bought: 0.89710321

I believe this could be because of the fees. FTX provides taker and maker fees over API, maybe we should let freqtrade know about this then update corresponding fees? My fees are: 

Maker fee: 0.004750%

Taker fee: 0.028500%

CCXT version 1.26.85; i need to wait for a fresh buy, current logs are not useful as I have like 10 files with 11074 lines each, couldnt find a buy instance.
while this may seem like a good idea, i'm not sure that the resulting calculations will be better (they will be in some circumstances, but not in all).

The idea of using `get_fee()` is that it provides the "most basic" fee.
Fees will only get lower after that by 
a) having a ton of volume so raising in tiers
b) using BNB

This means that while your calculations will be slightly off - profits cannot be lower than expected, but can only be higher.

The problem with setting a fixed lower amount is that you can't be sure this really applies for the below reasons:
* you ran out of BNB - so now 1% fees apply again
* you disabled BNB fees to test something and forgot to reenable it

Assuming your fees are set lower than what they really are - then the bot may sell with ROI = 1% - but really has a loss since your calculation is off.


Ideally, we could pull the fees from the exchange, however, unfortunately, i've not yet found how to retrieve that from the API reliably before trading - all "fee" endpoints only return the basic fees (or the tier-based list - but not what tier your'e in).

I think the best way would be to update the fee to the 0.075 after the buy-order did happen. This way, we use the "known" fee of 0.075 - but use the default fee on sell to calculate the profit - since due to the above reasons, this is a valid possibility.


@xmatthias what about adding custom slippage for dry-run? As a part of the fixed fee value set in the config, so same will be used for backtesting...
(but it will require more changes, not only this kind of simple fix)

that's not necessarily true.

slippage in simulations (backtesting, dry-run) is just a fixed (or dynamic) percentage you subtract from your profits ... so using 0.076 instead of 0.075 would be 0.2% slippage (fees are applied twice).
ideally it could be a random value between two limits to simulate a more realistic slippage - but that would give non-reproducible results, so i'd stay away from that if we can.

It's not very obvious - but even if a separate parameter is added for slippage for simulation modes, the would just be `fee + slippage / 2` - so the result is the same as you'd have by specifying it all in the fee parameter - so i'm not sure what other changes would be needed.

I wouldn't dive into slippage as part of this issue though - since that's not related to this.
@xmatthias Hi and thanks for all your work :)

In here, there is commission and commissionAsset in the response of the request. It is not what we are looking for ? (I imagine you saw it already ^^)
[Api Binance new_order-trade](https://binance-docs.github.io/apidocs/spot/en/#new-order-trade)
Yes ... but we need to be careful - the link points to "new_order" - but commission and commissionAsset (or fee / feeCurrency - as it's called after ccxt) is only available once the order is filled - not when creating a limit order.

I'll have a look at how to "properly" use the right fee structure in fees ... (or if that's done already). I know it's handled correctly if the fee is not in BNB (for binance) - not sure about BNB reduced fees.
Ok, thanks again for your work.

Resolving patch:
diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -22,7 +22,7 @@
 from freqtrade.exceptions import (DependencyException, InvalidOrderException,
                                   OperationalException, TemporaryError)
 from freqtrade.exchange.common import BAD_EXCHANGES, retrier, retrier_async
-from freqtrade.misc import deep_merge_dicts
+from freqtrade.misc import deep_merge_dicts, safe_value_fallback
 
 
 CcxtModuleType = Any
@@ -472,26 +472,31 @@ def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float,
             'pair': pair,
             'price': rate,
             'amount': _amount,
-            "cost": _amount * rate,
+            'cost': _amount * rate,
             'type': ordertype,
             'side': side,
             'remaining': _amount,
             'datetime': arrow.utcnow().isoformat(),
             'status': "closed" if ordertype == "market" else "open",
             'fee': None,
-            "info": {}
+            'info': {}
         }
-        self._store_dry_order(dry_order)
+        self._store_dry_order(dry_order, pair)
         # Copy order and close it - so the returned order is open unless it's a market order
         return dry_order
 
-    def _store_dry_order(self, dry_order: Dict) -> None:
+    def _store_dry_order(self, dry_order: Dict, pair: str) -> None:
         closed_order = dry_order.copy()
-        if closed_order["type"] in ["market", "limit"]:
+        if closed_order['type'] in ["market", "limit"]:
             closed_order.update({
-                "status": "closed",
-                "filled": closed_order["amount"],
-                "remaining": 0
+                'status': 'closed',
+                'filled': closed_order['amount'],
+                'remaining': 0,
+                'fee': {
+                    'currency': self.get_pair_quote_currency(pair),
+                    'cost': dry_order['cost'] * self.get_fee(pair),
+                    'rate': self.get_fee(pair)
+                }
             })
         if closed_order["type"] in ["stop_loss_limit"]:
             closed_order["info"].update({"stopPrice": closed_order["price"]})
@@ -1063,6 +1068,61 @@ def get_fee(self, symbol: str, type: str = '', side: str = '', amount: float = 1
         except ccxt.BaseError as e:
             raise OperationalException(e) from e
 
+    @staticmethod
+    def order_has_fee(order: Dict) -> bool:
+        """
+        Verifies if the passed in order dict has the needed keys to extract fees,
+        and that these keys (currency, cost) are not empty.
+        :param order: Order or trade (one trade) dict
+        :return: True if the fee substructure contains currency and cost, false otherwise
+        """
+        if not isinstance(order, dict):
+            return False
+        return ('fee' in order and order['fee'] is not None
+                and (order['fee'].keys() >= {'currency', 'cost'})
+                and order['fee']['currency'] is not None
+                and order['fee']['cost'] is not None
+                )
+
+    def calculate_fee_rate(self, order: Dict) -> Optional[float]:
+        """
+        Calculate fee rate if it's not given by the exchange.
+        :param order: Order or trade (one trade) dict
+        """
+        if order['fee'].get('rate') is not None:
+            return order['fee'].get('rate')
+        fee_curr = order['fee']['currency']
+        # Calculate fee based on order details
+        if fee_curr in self.get_pair_base_currency(order['symbol']):
+            # Base currency - divide by amount
+            return round(
+                order['fee']['cost'] / safe_value_fallback(order, order, 'filled', 'amount'), 8)
+        elif fee_curr in self.get_pair_quote_currency(order['symbol']):
+            # Quote currency - divide by cost
+            return round(order['fee']['cost'] / order['cost'], 8)
+        else:
+            # If Fee currency is a different currency
+            try:
+                comb = self.get_valid_pair_combination(fee_curr, self._config['stake_currency'])
+                tick = self.fetch_ticker(comb)
+
+                fee_to_quote_rate = safe_value_fallback(tick, tick, 'last', 'ask')
+                return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8)
+            except DependencyException:
+                return None
+
+    def extract_cost_curr_rate(self, order: Dict) -> Tuple[float, str, Optional[float]]:
+        """
+        Extract tuple of cost, currency, rate.
+        Requires order_has_fee to run first!
+        :param order: Order or trade (one trade) dict
+        :return: Tuple with cost, currency, rate of the given fee dict
+        """
+        return (order['fee']['cost'],
+                order['fee']['currency'],
+                self.calculate_fee_rate(order))
+        # calculate rate ? (order['fee']['cost'] / (order['amount'] * order['price']))
+
 
 def is_exchange_bad(exchange_name: str) -> bool:
     return exchange_name in BAD_EXCHANGES
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -610,7 +610,7 @@ def exit_positions(self, trades: List[Any]) -> int:
                     trades_closed += 1
                     continue
                 # Check if we can sell our current pair
-                if trade.open_order_id is None and self.handle_trade(trade):
+                if trade.open_order_id is None and trade.is_open and self.handle_trade(trade):
                     trades_closed += 1
 
             except DependencyException as exception:
@@ -752,7 +752,7 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool:
         # We check if stoploss order is fulfilled
         if stoploss_order and stoploss_order['status'] == 'closed':
             trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
-            trade.update(stoploss_order)
+            self.update_trade_state(trade, stoploss_order, sl_order=True)
             # Lock pair for one candle to prevent immediate rebuys
             self.strategy.lock_pair(trade.pair,
                                     timeframe_to_next_date(self.config['ticker_interval']))
@@ -1032,7 +1032,7 @@ def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> boo
         trade.sell_reason = sell_reason.value
         # In case of market sell orders the order can be closed immediately
         if order.get('status', 'unknown') == 'closed':
-            trade.update(order)
+            self.update_trade_state(trade, order)
         Trade.session.flush()
 
         # Lock pair for one candle to prevent immediate rebuys
@@ -1123,7 +1123,7 @@ def _notify_sell_cancel(self, trade: Trade, order_type: str, reason: str) -> Non
 #
 
     def update_trade_state(self, trade: Trade, action_order: dict = None,
-                           order_amount: float = None) -> bool:
+                           order_amount: float = None, sl_order: bool = False) -> bool:
         """
         Checks trades with open orders and updates the amount if necessary
         Handles closing both buy and sell orders.
@@ -1131,84 +1131,125 @@ def update_trade_state(self, trade: Trade, action_order: dict = None,
         """
         # Get order details for actual price per unit
         if trade.open_order_id:
-            # Update trade with order values
-            logger.info('Found open order for %s', trade)
-            try:
-                order = action_order or self.exchange.get_order(trade.open_order_id, trade.pair)
-            except InvalidOrderException as exception:
-                logger.warning('Unable to fetch order %s: %s', trade.open_order_id, exception)
-                return False
-            # Try update amount (binance-fix)
-            try:
-                new_amount = self.get_real_amount(trade, order, order_amount)
-                if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC):
-                    order['amount'] = new_amount
-                    order.pop('filled', None)
-                    # Fee was applied, so set to 0
-                    trade.fee_open = 0
-                    trade.recalc_open_trade_price()
-            except DependencyException as exception:
-                logger.warning("Could not update trade amount: %s", exception)
-
-            if self.exchange.check_order_canceled_empty(order):
-                # Trade has been cancelled on exchange
-                # Handling of this will happen in check_handle_timeout.
-                return True
-            trade.update(order)
-
-            # Updating wallets when order is closed
-            if not trade.is_open:
-                self.wallets.update()
+            order_id = trade.open_order_id
+        elif trade.stoploss_order_id and sl_order:
+            order_id = trade.stoploss_order_id
+        else:
+            return False
+        # Update trade with order values
+        logger.info('Found open order for %s', trade)
+        try:
+            order = action_order or self.exchange.get_order(order_id, trade.pair)
+        except InvalidOrderException as exception:
+            logger.warning('Unable to fetch order %s: %s', order_id, exception)
+            return False
+        # Try update amount (binance-fix)
+        try:
+            new_amount = self.get_real_amount(trade, order, order_amount)
+            if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC):
+                order['amount'] = new_amount
+                order.pop('filled', None)
+                trade.recalc_open_trade_price()
+        except DependencyException as exception:
+            logger.warning("Could not update trade amount: %s", exception)
+
+        if self.exchange.check_order_canceled_empty(order):
+            # Trade has been cancelled on exchange
+            # Handling of this will happen in check_handle_timeout.
+            return True
+        trade.update(order)
 
+        # Updating wallets when order is closed
+        if not trade.is_open:
+            self.wallets.update()
         return False
 
+    def apply_fee_conditional(self, trade: Trade, trade_base_currency: str,
+                              amount: float, fee_abs: float) -> float:
+        """
+        Applies the fee to amount (either from Order or from Trades).
+        Can eat into dust if more than the required asset is available.
+        """
+        self.wallets.update()
+        if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount:
+            # Eat into dust if we own more than base currency
+            logger.info(f"Fee amount for {trade} was in base currency - "
+                        f"Eating Fee {fee_abs} into dust.")
+        elif fee_abs != 0:
+            real_amount = self.exchange.amount_to_precision(trade.pair, amount - fee_abs)
+            logger.info(f"Applying fee on amount for {trade} "
+                        f"(from {amount} to {real_amount}).")
+            return real_amount
+        return amount
+
     def get_real_amount(self, trade: Trade, order: Dict, order_amount: float = None) -> float:
         """
-        Get real amount for the trade
+        Detect and update trade fee.
+        Calls trade.update_fee() uppon correct detection.
+        Returns modified amount if the fee was taken from the destination currency.
         Necessary for exchanges which charge fees in base currency (e.g. binance)
+        :return: identical (or new) amount for the trade
         """
+        # Init variables
         if order_amount is None:
             order_amount = order['amount']
         # Only run for closed orders
-        if trade.fee_open == 0 or order['status'] == 'open':
+        if trade.fee_updated(order.get('side', '')) or order['status'] == 'open':
             return order_amount
 
         trade_base_currency = self.exchange.get_pair_base_currency(trade.pair)
         # 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_base_currency == 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
+        if self.exchange.order_has_fee(order):
+            fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(order)
+            logger.info(f"Fee for Trade {trade} [{order.get('side')}]: "
+                        f"{fee_cost:.8g} {fee_currency} - rate: {fee_rate}")
+
+            trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', ''))
+            if trade_base_currency == fee_currency:
+                # Apply fee to amount
+                return self.apply_fee_conditional(trade, trade_base_currency,
+                                                  amount=order_amount, fee_abs=fee_cost)
+            return order_amount
+        return self.fee_detection_from_trades(trade, order, order_amount)
+
+    def fee_detection_from_trades(self, trade: Trade, order: Dict, order_amount: float) -> float:
+        """
+        fee-detection fallback to Trades. Parses result of fetch_my_trades to get correct fee.
+        """
         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
+        fee_currency = None
         amount = 0
-        fee_abs = 0
+        fee_abs = 0.0
+        fee_cost = 0.0
+        trade_base_currency = self.exchange.get_pair_base_currency(trade.pair)
+        fee_rate_array: List[float] = []
         for exectrade in trades:
             amount += exectrade['amount']
-            if ("fee" in exectrade and exectrade['fee'] is not None and
-                    (exectrade['fee'].keys() >= {'currency', 'cost'})):
+            if self.exchange.order_has_fee(exectrade):
+                fee_cost_, fee_currency, fee_rate_ = self.exchange.extract_cost_curr_rate(exectrade)
+                fee_cost += fee_cost_
+                if fee_rate_ is not None:
+                    fee_rate_array.append(fee_rate_)
                 # only applies if fee is in quote currency!
-                if (exectrade['fee']['currency'] is not None and
-                        exectrade['fee']['cost'] is not None and
-                        trade_base_currency == exectrade['fee']['currency']):
-                    fee_abs += exectrade['fee']['cost']
+                if trade_base_currency == fee_currency:
+                    fee_abs += fee_cost_
+        # Ensure at least one trade was found:
+        if fee_currency:
+            # fee_rate should use mean
+            fee_rate = sum(fee_rate_array) / float(len(fee_rate_array)) if fee_rate_array else None
+            trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', ''))
 
         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
+            return self.apply_fee_conditional(trade, trade_base_currency,
+                                              amount=amount, fee_abs=fee_abs)
+        else:
+            return amount
diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py
--- a/freqtrade/persistence.py
+++ b/freqtrade/persistence.py
@@ -86,11 +86,15 @@ def check_migrate(engine) -> None:
         logger.debug(f'trying {table_back_name}')
 
     # Check for latest column
-    if not has_column(cols, 'close_profit_abs'):
+    if not has_column(cols, 'fee_close_cost'):
         logger.info(f'Running database migration - backup available as {table_back_name}')
 
         fee_open = get_column_def(cols, 'fee_open', 'fee')
+        fee_open_cost = get_column_def(cols, 'fee_open_cost', 'null')
+        fee_open_currency = get_column_def(cols, 'fee_open_currency', 'null')
         fee_close = get_column_def(cols, 'fee_close', 'fee')
+        fee_close_cost = get_column_def(cols, 'fee_close_cost', 'null')
+        fee_close_currency = get_column_def(cols, 'fee_close_currency', 'null')
         open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null')
         close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null')
         stop_loss = get_column_def(cols, 'stop_loss', '0.0')
@@ -120,7 +124,9 @@ def check_migrate(engine) -> None:
 
         # Copy data back - following the correct schema
         engine.execute(f"""insert into trades
-                (id, exchange, pair, is_open, fee_open, fee_close, open_rate,
+                (id, exchange, pair, is_open,
+                fee_open, fee_open_cost, fee_open_currency,
+                fee_close, fee_close_cost, fee_open_currency, open_rate,
                 open_rate_requested, close_rate, close_rate_requested, close_profit,
                 stake_amount, amount, open_date, close_date, open_order_id,
                 stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
@@ -136,7 +142,9 @@ def check_migrate(engine) -> None:
                     else pair
                     end
                 pair,
-                is_open, {fee_open} fee_open, {fee_close} fee_close,
+                is_open, {fee_open} fee_open, {fee_open_cost} fee_open_cost,
+                {fee_open_currency} fee_open_currency, {fee_close} fee_close,
+                {fee_close_cost} fee_close_cost, {fee_close_currency} fee_close_currency,
                 open_rate, {open_rate_requested} open_rate_requested, close_rate,
                 {close_rate_requested} close_rate_requested, close_profit,
                 stake_amount, amount, open_date, close_date, open_order_id,
@@ -185,7 +193,11 @@ class Trade(_DECL_BASE):
     pair = Column(String, nullable=False, index=True)
     is_open = Column(Boolean, nullable=False, default=True, index=True)
     fee_open = Column(Float, nullable=False, default=0.0)
+    fee_open_cost = Column(Float, nullable=True)
+    fee_open_currency = Column(String, nullable=True)
     fee_close = Column(Float, nullable=False, default=0.0)
+    fee_close_cost = Column(Float, nullable=True)
+    fee_close_currency = Column(String, nullable=True)
     open_rate = Column(Float)
     open_rate_requested = Column(Float)
     # open_trade_price - calculated via _calc_open_trade_price
@@ -235,7 +247,11 @@ def to_json(self) -> Dict[str, Any]:
             'pair': self.pair,
             'is_open': self.is_open,
             'fee_open': self.fee_open,
+            'fee_open_cost': self.fee_open_cost,
+            'fee_open_currency': self.fee_open_currency,
             'fee_close': self.fee_close,
+            'fee_close_cost': self.fee_close_cost,
+            'fee_close_currency': self.fee_close_currency,
             'open_date_hum': arrow.get(self.open_date).humanize(),
             'open_date': self.open_date.strftime("%Y-%m-%d %H:%M:%S"),
             'close_date_hum': (arrow.get(self.close_date).humanize()
@@ -360,6 +376,35 @@ def close(self, rate: float) -> None:
             self
         )
 
+    def update_fee(self, fee_cost: float, fee_currency: Optional[str], fee_rate: Optional[float],
+                   side: str) -> None:
+        """
+        Update Fee parameters. Only acts once per side
+        """
+        if side == 'buy' and self.fee_open_currency is None:
+            self.fee_open_cost = fee_cost
+            self.fee_open_currency = fee_currency
+            if fee_rate is not None:
+                self.fee_open = fee_rate
+                # Assume close-fee will fall into the same fee category and take an educated guess
+                self.fee_close = fee_rate
+        elif side == 'sell' and self.fee_close_currency is None:
+            self.fee_close_cost = fee_cost
+            self.fee_close_currency = fee_currency
+            if fee_rate is not None:
+                self.fee_close = fee_rate
+
+    def fee_updated(self, side: str) -> bool:
+        """
+        Verify if this side (buy / sell) has already been updated
+        """
+        if side == 'buy':
+            return self.fee_open_currency is not None
+        elif side == 'sell':
+            return self.fee_close_currency is not None
+        else:
+            return False
+
     def _calc_open_trade_price(self) -> float:
         """
         Calculate the open_rate including open_fee.


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -779,7 +779,7 @@ def limit_buy_order():
         'id': 'mocked_limit_buy',
         'type': 'limit',
         'side': 'buy',
-        'pair': 'mocked',
+        'symbol': 'mocked',
         'datetime': arrow.utcnow().isoformat(),
         'price': 0.00001099,
         'amount': 90.99181073,
@@ -795,7 +795,7 @@ def market_buy_order():
         'id': 'mocked_market_buy',
         'type': 'market',
         'side': 'buy',
-        'pair': 'mocked',
+        'symbol': 'mocked',
         'datetime': arrow.utcnow().isoformat(),
         'price': 0.00004099,
         'amount': 91.99181073,
@@ -811,7 +811,7 @@ def market_sell_order():
         'id': 'mocked_limit_sell',
         'type': 'market',
         'side': 'sell',
-        'pair': 'mocked',
+        'symbol': 'mocked',
         'datetime': arrow.utcnow().isoformat(),
         'price': 0.00004173,
         'amount': 91.99181073,
@@ -827,7 +827,7 @@ def limit_buy_order_old():
         'id': 'mocked_limit_buy_old',
         'type': 'limit',
         'side': 'buy',
-        'pair': 'mocked',
+        'symbol': 'mocked',
         'datetime': str(arrow.utcnow().shift(minutes=-601).datetime),
         'price': 0.00001099,
         'amount': 90.99181073,
@@ -843,7 +843,7 @@ def limit_sell_order_old():
         'id': 'mocked_limit_sell_old',
         'type': 'limit',
         'side': 'sell',
-        'pair': 'ETH/BTC',
+        'symbol': 'ETH/BTC',
         'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
         'price': 0.00001099,
         'amount': 90.99181073,
@@ -859,7 +859,7 @@ def limit_buy_order_old_partial():
         'id': 'mocked_limit_buy_old_partial',
         'type': 'limit',
         'side': 'buy',
-        'pair': 'ETH/BTC',
+        'symbol': 'ETH/BTC',
         'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
         'price': 0.00001099,
         'amount': 90.99181073,
@@ -873,7 +873,7 @@ def limit_buy_order_old_partial():
 def limit_buy_order_old_partial_canceled(limit_buy_order_old_partial):
     res = deepcopy(limit_buy_order_old_partial)
     res['status'] = 'canceled'
-    res['fee'] = {'cost': 0.0001, 'currency': 'ETH'}
+    res['fee'] = {'cost': 0.023, 'currency': 'ETH'}
     return res
 
 
@@ -1482,7 +1482,7 @@ def buy_order_fee():
         'id': 'mocked_limit_buy_old',
         'type': 'limit',
         'side': 'buy',
-        'pair': 'mocked',
+        'symbol': 'mocked',
         'datetime': str(arrow.utcnow().shift(minutes=-601).datetime),
         'price': 0.245441,
         'amount': 8.0,
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -2145,3 +2145,58 @@ def test_symbol_is_pair(market_symbol, base_currency, quote_currency, expected_r
 ])
 def test_market_is_active(market, expected_result) -> None:
     assert market_is_active(market) == expected_result
+
+
+@pytest.mark.parametrize("order,expected", [
+    ([{'fee'}], False),
+    ({'fee': None}, False),
+    ({'fee': {'currency': 'ETH/BTC'}}, False),
+    ({'fee': {'currency': 'ETH/BTC', 'cost': None}}, False),
+    ({'fee': {'currency': 'ETH/BTC', 'cost': 0.01}}, True),
+])
+def test_order_has_fee(order, expected) -> None:
+    assert Exchange.order_has_fee(order) == expected
+
+
+@pytest.mark.parametrize("order,expected", [
+    ({'symbol': 'ETH/BTC', 'fee': {'currency': 'ETH', 'cost': 0.43}},
+        (0.43, 'ETH', 0.01)),
+    ({'symbol': 'ETH/USDT', 'fee': {'currency': 'USDT', 'cost': 0.01}},
+        (0.01, 'USDT', 0.01)),
+    ({'symbol': 'BTC/USDT', 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}},
+        (0.34, 'USDT', 0.01)),
+])
+def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
+    mocker.patch('freqtrade.exchange.Exchange.calculate_fee_rate', MagicMock(return_value=0.01))
+    ex = get_patched_exchange(mocker, default_conf)
+    assert ex.extract_cost_curr_rate(order) == expected
+
+
+@pytest.mark.parametrize("order,expected", [
+    # Using base-currency
+    ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
+        'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.1),
+    ({'symbol': 'ETH/BTC', 'amount': 0.05, 'cost': 0.05,
+        'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.08),
+    # Using quote currency
+    ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
+        'fee': {'currency': 'BTC', 'cost': 0.005}}, 0.1),
+    ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
+        'fee': {'currency': 'BTC', 'cost': 0.002, 'rate': None}}, 0.04),
+    # Using foreign currency
+    ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
+        'fee': {'currency': 'NEO', 'cost': 0.0012}}, 0.001944),
+    ({'symbol': 'ETH/BTC', 'amount': 2.21, 'cost': 0.02992561,
+        'fee': {'currency': 'NEO', 'cost': 0.00027452}}, 0.00074305),
+    # TODO: More tests here!
+    # Rate included in return - return as is
+    ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
+        'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}}, 0.01),
+    ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
+        'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.005}}, 0.005),
+])
+def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None:
+    mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081})
+
+    ex = get_patched_exchange(mocker, default_conf)
+    assert ex.calculate_fee_rate(order) == expected
diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -51,7 +51,11 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
         'open_date_hum': ANY,
         'is_open': ANY,
         'fee_open': ANY,
+        'fee_open_cost': ANY,
+        'fee_open_currency': ANY,
         'fee_close': ANY,
+        'fee_close_cost': ANY,
+        'fee_close_currency': ANY,
         'open_rate_requested': ANY,
         'open_trade_price': ANY,
         'close_rate_requested': ANY,
@@ -90,7 +94,11 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
         'open_date_hum': ANY,
         'is_open': ANY,
         'fee_open': ANY,
+        'fee_open_cost': ANY,
+        'fee_open_currency': ANY,
         'fee_close': ANY,
+        'fee_close_cost': ANY,
+        'fee_close_currency': ANY,
         'open_rate_requested': ANY,
         'open_trade_price': ANY,
         'close_rate_requested': ANY,
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -472,7 +472,11 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
                         'close_rate_requested': None,
                         'current_rate': 1.099e-05,
                         'fee_close': 0.0025,
+                        'fee_close_cost': None,
+                        'fee_close_currency': None,
                         'fee_open': 0.0025,
+                        'fee_open_cost': None,
+                        'fee_open_currency': None,
                         'open_date': ANY,
                         'is_open': True,
                         'max_rate': 0.0,
@@ -575,7 +579,11 @@ def test_api_forcebuy(botclient, mocker, fee):
                        'close_profit': None,
                        'close_rate_requested': None,
                        'fee_close': 0.0025,
+                       'fee_close_cost': None,
+                       'fee_close_currency': None,
                        'fee_open': 0.0025,
+                       'fee_open_cost': None,
+                       'fee_open_currency': None,
                        'is_open': False,
                        'max_rate': None,
                        'min_rate': None,
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -1140,7 +1140,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
         'status': 'closed',
         'type': 'stop_loss_limit',
         'price': 3,
-        'average': 2
+        'average': 2,
+        'amount': limit_buy_order['amount'],
     })
     mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit)
     assert freqtrade.handle_stoploss_on_exchange(trade) is True
@@ -2202,6 +2203,7 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap
                                            limit_buy_order_old_partial_canceled, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
+    mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=0))
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -2221,7 +2223,7 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap
     # and apply fees if necessary.
     freqtrade.check_handle_timedout()
 
-    assert log_has_re(r"Applying fee on amount for Trade.* Order", caplog)
+    assert log_has_re(r"Applying fee on amount for Trade.*", caplog)
 
     assert cancel_order_mock.call_count == 1
     assert rpc_mock.call_count == 2
@@ -2229,9 +2231,10 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap
     assert len(trades) == 1
     # Verify that trade has been updated
     assert trades[0].amount == (limit_buy_order_old_partial['amount'] -
-                                limit_buy_order_old_partial['remaining']) - 0.0001
+                                limit_buy_order_old_partial['remaining']) - 0.023
     assert trades[0].open_order_id is None
-    assert trades[0].fee_open == 0
+    assert trades[0].fee_updated('buy')
+    assert pytest.approx(trades[0].fee_open) == 0.001
 
 
 def test_check_handle_timedout_partial_except(default_conf, ticker, open_trade, caplog, fee,
@@ -3285,8 +3288,6 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order,
 
 def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, fee, caplog, mocker):
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
-    patch_RPCManager(mocker)
-    patch_exchange(mocker)
     amount = sum(x['amount'] for x in trades_for_order)
     trade = Trade(
         pair='LTC/ETH',
@@ -3297,21 +3298,43 @@ def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, fe
         fee_close=fee.return_value,
         open_order_id="123456"
     )
-    freqtrade = FreqtradeBot(default_conf)
-    patch_get_signal(freqtrade)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     # Amount is reduced by "fee"
     assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001)
     assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
-                   'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992) from Trades',
+                   'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).',
                    caplog)
 
 
+def test_get_real_amount_quote_dust(default_conf, trades_for_order, buy_order_fee, fee,
+                                    caplog, mocker):
+    mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
+    walletmock = mocker.patch('freqtrade.wallets.Wallets.update')
+    mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=8.1122)
+    amount = sum(x['amount'] for x in trades_for_order)
+    trade = Trade(
+        pair='LTC/ETH',
+        amount=amount,
+        exchange='binance',
+        open_rate=0.245441,
+        fee_open=fee.return_value,
+        fee_close=fee.return_value,
+        open_order_id="123456"
+    )
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
+
+    walletmock.reset_mock()
+    # Amount is kept as is
+    assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
+    assert walletmock.call_count == 1
+    assert log_has_re(r'Fee amount for Trade.* was in base currency '
+                      '- Eating Fee 0.008 into dust', caplog)
+
+
 def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker, fee):
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
 
-    patch_RPCManager(mocker)
-    patch_exchange(mocker)
     amount = buy_order_fee['amount']
     trade = Trade(
         pair='LTC/ETH',
@@ -3322,8 +3345,7 @@ def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker, f
         fee_close=fee.return_value,
         open_order_id="123456"
     )
-    freqtrade = FreqtradeBot(default_conf)
-    patch_get_signal(freqtrade)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     # Amount is reduced by "fee"
     assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
@@ -3335,8 +3357,6 @@ def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker, f
 def test_get_real_amount_stake(default_conf, trades_for_order, buy_order_fee, fee, mocker):
     trades_for_order[0]['fee']['currency'] = 'ETH'
 
-    patch_RPCManager(mocker)
-    patch_exchange(mocker)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
     amount = sum(x['amount'] for x in trades_for_order)
     trade = Trade(
@@ -3348,8 +3368,7 @@ def test_get_real_amount_stake(default_conf, trades_for_order, buy_order_fee, fe
         open_rate=0.245441,
         open_order_id="123456"
     )
-    freqtrade = FreqtradeBot(default_conf)
-    patch_get_signal(freqtrade)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     # Amount does not change
     assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
@@ -3362,8 +3381,6 @@ def test_get_real_amount_no_currency_in_fee(default_conf, trades_for_order, buy_
     limit_buy_order['fee'] = {'cost': 0.004, 'currency': None}
     trades_for_order[0]['fee']['currency'] = None
 
-    patch_RPCManager(mocker)
-    patch_exchange(mocker)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
     amount = sum(x['amount'] for x in trades_for_order)
     trade = Trade(
@@ -3375,8 +3392,7 @@ def test_get_real_amount_no_currency_in_fee(default_conf, trades_for_order, buy_
         open_rate=0.245441,
         open_order_id="123456"
     )
-    freqtrade = FreqtradeBot(default_conf)
-    patch_get_signal(freqtrade)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     # Amount does not change
     assert freqtrade.get_real_amount(trade, limit_buy_order) == amount
@@ -3386,8 +3402,6 @@ def test_get_real_amount_BNB(default_conf, trades_for_order, buy_order_fee, fee,
     trades_for_order[0]['fee']['currency'] = 'BNB'
     trades_for_order[0]['fee']['cost'] = 0.00094518
 
-    patch_RPCManager(mocker)
-    patch_exchange(mocker)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
     amount = sum(x['amount'] for x in trades_for_order)
     trade = Trade(
@@ -3399,16 +3413,13 @@ def test_get_real_amount_BNB(default_conf, trades_for_order, buy_order_fee, fee,
         open_rate=0.245441,
         open_order_id="123456"
     )
-    freqtrade = FreqtradeBot(default_conf)
-    patch_get_signal(freqtrade)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     # Amount does not change
     assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
 
 
 def test_get_real_amount_multi(default_conf, trades_for_order2, buy_order_fee, caplog, fee, mocker):
-    patch_RPCManager(mocker)
-    patch_exchange(mocker)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order2)
     amount = float(sum(x['amount'] for x in trades_for_order2))
     trade = Trade(
@@ -3420,13 +3431,12 @@ def test_get_real_amount_multi(default_conf, trades_for_order2, buy_order_fee, c
         open_rate=0.245441,
         open_order_id="123456"
     )
-    freqtrade = FreqtradeBot(default_conf)
-    patch_get_signal(freqtrade)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     # Amount is reduced by "fee"
     assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001)
     assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
-                   'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992) from Trades',
+                   'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).',
                    caplog)
 
 
@@ -3435,8 +3445,6 @@ def test_get_real_amount_fromorder(default_conf, trades_for_order, buy_order_fee
     limit_buy_order = deepcopy(buy_order_fee)
     limit_buy_order['fee'] = {'cost': 0.004, 'currency': 'LTC'}
 
-    patch_RPCManager(mocker)
-    patch_exchange(mocker)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order',
                  return_value=[trades_for_order])
     amount = float(sum(x['amount'] for x in trades_for_order))
@@ -3449,13 +3457,12 @@ def test_get_real_amount_fromorder(default_conf, trades_for_order, buy_order_fee
         open_rate=0.245441,
         open_order_id="123456"
     )
-    freqtrade = FreqtradeBot(default_conf)
-    patch_get_signal(freqtrade)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     # Amount is reduced by "fee"
     assert freqtrade.get_real_amount(trade, limit_buy_order) == amount - 0.004
     assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
-                   'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.996) from Order',
+                   'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.996).',
                    caplog)
 
 
@@ -3463,8 +3470,6 @@ def test_get_real_amount_invalid_order(default_conf, trades_for_order, buy_order
     limit_buy_order = deepcopy(buy_order_fee)
     limit_buy_order['fee'] = {'cost': 0.004}
 
-    patch_RPCManager(mocker)
-    patch_exchange(mocker)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
     amount = float(sum(x['amount'] for x in trades_for_order))
     trade = Trade(
@@ -3476,8 +3481,7 @@ def test_get_real_amount_invalid_order(default_conf, trades_for_order, buy_order
         open_rate=0.245441,
         open_order_id="123456"
     )
-    freqtrade = FreqtradeBot(default_conf)
-    patch_get_signal(freqtrade)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     # Amount does not change
     assert freqtrade.get_real_amount(trade, limit_buy_order) == amount
@@ -3487,8 +3491,6 @@ def test_get_real_amount_wrong_amount(default_conf, trades_for_order, buy_order_
     limit_buy_order = deepcopy(buy_order_fee)
     limit_buy_order['amount'] = limit_buy_order['amount'] - 0.001
 
-    patch_RPCManager(mocker)
-    patch_exchange(mocker)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
     amount = float(sum(x['amount'] for x in trades_for_order))
     trade = Trade(
@@ -3500,8 +3502,7 @@ def test_get_real_amount_wrong_amount(default_conf, trades_for_order, buy_order_
         fee_close=fee.return_value,
         open_order_id="123456"
     )
-    freqtrade = FreqtradeBot(default_conf)
-    patch_get_signal(freqtrade)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     # Amount does not change
     with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"):
@@ -3514,8 +3515,6 @@ def test_get_real_amount_wrong_amount_rounding(default_conf, trades_for_order, b
     limit_buy_order = deepcopy(buy_order_fee)
     trades_for_order[0]['amount'] = trades_for_order[0]['amount'] + 1e-15
 
-    patch_RPCManager(mocker)
-    patch_exchange(mocker)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
     amount = float(sum(x['amount'] for x in trades_for_order))
     trade = Trade(
@@ -3527,8 +3526,7 @@ def test_get_real_amount_wrong_amount_rounding(default_conf, trades_for_order, b
         open_rate=0.245441,
         open_order_id="123456"
     )
-    freqtrade = FreqtradeBot(default_conf)
-    patch_get_signal(freqtrade)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
 
     # Amount changes by fee amount.
     assert isclose(freqtrade.get_real_amount(trade, limit_buy_order), amount - (amount * 0.001),
@@ -3539,8 +3537,6 @@ def test_get_real_amount_invalid(default_conf, trades_for_order, buy_order_fee,
     # Remove "Currency" from fee dict
     trades_for_order[0]['fee'] = {'cost': 0.008}
 
-    patch_RPCManager(mocker)
-    patch_exchange(mocker)
     mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
     amount = sum(x['amount'] for x in trades_for_order)
     trade = Trade(
@@ -3553,15 +3549,12 @@ def test_get_real_amount_invalid(default_conf, trades_for_order, buy_order_fee,
 
         open_order_id="123456"
     )
-    freqtrade = FreqtradeBot(default_conf)
-    patch_get_signal(freqtrade)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
     # Amount does not change
     assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
 
 
 def test_get_real_amount_open_trade(default_conf, fee, mocker):
-    patch_RPCManager(mocker)
-    patch_exchange(mocker)
     amount = 12345
     trade = Trade(
         pair='LTC/ETH',
@@ -3576,12 +3569,41 @@ def test_get_real_amount_open_trade(default_conf, fee, mocker):
         'id': 'mocked_order',
         'amount': amount,
         'status': 'open',
+        'side': 'buy',
     }
-    freqtrade = FreqtradeBot(default_conf)
-    patch_get_signal(freqtrade)
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
     assert freqtrade.get_real_amount(trade, order) == amount
 
 
+@pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [
+    (8.0, 0.0, 10, 8),
+    (8.0, 0.0, 0, 8),
+    (8.0, 0.1, 0, 7.9),
+    (8.0, 0.1, 10, 8),
+    (8.0, 0.1, 8.0, 8.0),
+    (8.0, 0.1, 7.9, 7.9),
+])
+def test_apply_fee_conditional(default_conf, fee, caplog, mocker,
+                               amount, fee_abs, wallet, amount_exp):
+    walletmock = mocker.patch('freqtrade.wallets.Wallets.update')
+    mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=wallet)
+    trade = Trade(
+        pair='LTC/ETH',
+        amount=amount,
+        exchange='binance',
+        open_rate=0.245441,
+        fee_open=fee.return_value,
+        fee_close=fee.return_value,
+        open_order_id="123456"
+    )
+    freqtrade = get_patched_freqtradebot(mocker, default_conf)
+
+    walletmock.reset_mock()
+    # Amount is kept as is
+    assert freqtrade.apply_fee_conditional(trade, 'LTC', amount, fee_abs) == amount_exp
+    assert walletmock.call_count == 1
+
+
 def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, mocker,
                                     order_book_l2):
     default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True
diff --git a/tests/test_integration.py b/tests/test_integration.py
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -44,6 +44,8 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
     }
     stoploss_order_closed = stoploss_order_open.copy()
     stoploss_order_closed['status'] = 'closed'
+    stoploss_order_closed['filled'] = stoploss_order_closed['amount']
+
     # Sell first trade based on stoploss, keep 2nd and 3rd trade open
     stoploss_order_mock = MagicMock(
         side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
@@ -67,7 +69,6 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
     mocker.patch.multiple(
         'freqtrade.freqtradebot.FreqtradeBot',
         create_stoploss_order=MagicMock(return_value=True),
-        update_trade_state=MagicMock(),
         _notify_sell=MagicMock(),
     )
     mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
@@ -97,8 +98,9 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
 
     # Only order for 3rd trade needs to be cancelled
     assert cancel_order_mock.call_count == 1
-    # Wallets must be updated between stoploss cancellation and selling.
-    assert wallets_mock.call_count == 2
+    # Wallets must be updated between stoploss cancellation and selling, and will be updated again
+    # during update_trade_state
+    assert wallets_mock.call_count == 4
 
     trade = trades[0]
     assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
@@ -144,7 +146,6 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
     mocker.patch.multiple(
         'freqtrade.freqtradebot.FreqtradeBot',
         create_stoploss_order=MagicMock(return_value=True),
-        update_trade_state=MagicMock(),
         _notify_sell=MagicMock(),
     )
     should_sell_mock = MagicMock(side_effect=[
diff --git a/tests/test_persistence.py b/tests/test_persistence.py
--- a/tests/test_persistence.py
+++ b/tests/test_persistence.py
@@ -465,6 +465,10 @@ def test_migrate_old(mocker, default_conf, fee):
     assert trade.initial_stop_loss == 0.0
     assert trade.open_trade_price == trade._calc_open_trade_price()
     assert trade.close_profit_abs is None
+    assert trade.fee_open_cost is None
+    assert trade.fee_open_currency is None
+    assert trade.fee_close_cost is None
+    assert trade.fee_close_currency is None
 
     trade = Trade.query.filter(Trade.id == 2).first()
     assert trade.close_rate is not None
@@ -741,7 +745,11 @@ def test_to_json(default_conf, fee):
                       'open_rate_requested': None,
                       'open_trade_price': 15.1668225,
                       'fee_close': 0.0025,
+                      'fee_close_cost': None,
+                      'fee_close_currency': None,
                       'fee_open': 0.0025,
+                      'fee_open_cost': None,
+                      'fee_open_currency': None,
                       'close_rate': None,
                       'close_rate_requested': None,
                       'amount': 123.0,
@@ -790,7 +798,11 @@ def test_to_json(default_conf, fee):
                       'close_profit': None,
                       'close_rate_requested': None,
                       'fee_close': 0.0025,
+                      'fee_close_cost': None,
+                      'fee_close_currency': None,
                       'fee_open': 0.0025,
+                      'fee_open_cost': None,
+                      'fee_open_currency': None,
                       'is_open': None,
                       'max_rate': None,
                       'min_rate': None,
@@ -862,6 +874,75 @@ def test_stoploss_reinitialization(default_conf, fee):
     assert trade_adj.initial_stop_loss_pct == -0.04
 
 
+def test_update_fee(fee):
+    trade = Trade(
+        pair='ETH/BTC',
+        stake_amount=0.001,
+        fee_open=fee.return_value,
+        open_date=arrow.utcnow().shift(hours=-2).datetime,
+        amount=10,
+        fee_close=fee.return_value,
+        exchange='bittrex',
+        open_rate=1,
+        max_rate=1,
+    )
+    fee_cost = 0.15
+    fee_currency = 'BTC'
+    fee_rate = 0.0075
+    assert trade.fee_open_currency is None
+    assert not trade.fee_updated('buy')
+    assert not trade.fee_updated('sell')
+
+    trade.update_fee(fee_cost, fee_currency, fee_rate, 'buy')
+    assert trade.fee_updated('buy')
+    assert not trade.fee_updated('sell')
+    assert trade.fee_open_currency == fee_currency
+    assert trade.fee_open_cost == fee_cost
+    assert trade.fee_open == fee_rate
+    # Setting buy rate should "guess" close rate
+    assert trade.fee_close == fee_rate
+    assert trade.fee_close_currency is None
+    assert trade.fee_close_cost is None
+
+    fee_rate = 0.0076
+    trade.update_fee(fee_cost, fee_currency, fee_rate, 'sell')
+    assert trade.fee_updated('buy')
+    assert trade.fee_updated('sell')
+    assert trade.fee_close == 0.0076
+    assert trade.fee_close_cost == fee_cost
+    assert trade.fee_close == fee_rate
+
+
+def test_fee_updated(fee):
+    trade = Trade(
+        pair='ETH/BTC',
+        stake_amount=0.001,
+        fee_open=fee.return_value,
+        open_date=arrow.utcnow().shift(hours=-2).datetime,
+        amount=10,
+        fee_close=fee.return_value,
+        exchange='bittrex',
+        open_rate=1,
+        max_rate=1,
+    )
+
+    assert trade.fee_open_currency is None
+    assert not trade.fee_updated('buy')
+    assert not trade.fee_updated('sell')
+    assert not trade.fee_updated('asdf')
+
+    trade.update_fee(0.15, 'BTC', 0.0075, 'buy')
+    assert trade.fee_updated('buy')
+    assert not trade.fee_updated('sell')
+    assert trade.fee_open_currency is not None
+    assert trade.fee_close_currency is None
+
+    trade.update_fee(0.15, 'ABC', 0.0075, 'sell')
+    assert trade.fee_updated('buy')
+    assert trade.fee_updated('sell')
+    assert not trade.fee_updated('asfd')
+
+
 @pytest.mark.usefixtures("init_persistence")
 def test_total_open_trades_stakes(fee):