Repository: freqtrade/freqtrade

Issue:
Order cancelled AND fullfilled
## Step 1: Have you search for this issue before posting it?
Yes

## Step 2: Describe your environment

  * Python Version: 3.6.7 (`python -V`)
  * CCXT version: 1.18.516 (`pip freeze | grep ccxt`)
  * Branch: Develop
  * Last Commit ID: 46b1ecc77d53054aed6835d69f9989316422aa78 (`git log --format="%H" -n 1`)
  
## Step 3: Describe the problem:

Exchange: Kraken
In configuration file: "stake_amount": "unlimited",

- Freqtrade makes a limit order


```
2019-05-16 10:00:05,129 - freqtrade.freqtradebot - INFO - Found no buy signals for whitelisted currencies. Trying again..
2019-05-16 10:00:16,095 - freqtrade.freqtradebot - INFO - Buy signal found: about create a new trade with stake_amount: 19.7381 ...
2019-05-16 10:00:16,096 - freqtrade.freqtradebot - INFO - Using Last Ask / Last Price
2019-05-16 10:00:19,401 - freqtrade.rpc.rpc_manager - INFO - Sending rpc message: {'type': buy, 'exchange': 'Kraken', 'pair': 'BTC/EUR', 'limit': 7150.3, 'stake_amount': 19.7381, 'stake_currency': 'EUR', 'fiat_currency': 'EUR'}
2019-05-16 10:00:22,382 - freqtrade.wallets - INFO - Wallets synced.
```

- Kraken cancels the order

```
2019-05-16 10:00:25,471 - freqtrade.rpc.rpc_manager - INFO - Sending rpc message: {'type': status, 'status': 'Unfilled buy order for BTC/EUR canceled on Exchange'}
```

ISSUE:

- Kraken tells FT that the order is canceled

- but Kraken filled the order.
Here is the explaination of the behavior of kraken:
https://support.kraken.com/hc/en-us/articles/235162907-Order-Canceled-due-to-Out-of-Funds-

```
Order Canceled due to "Out of Funds":
It is possible for an order to partially execute (or "fill") if there are insufficient balances in the account for the order to fully execute.
This most commonly occurs when an order is placed for maximum volume. 
[...]
This is because the trading fee was not taken into consideration when placing the order.
```

- But FT has no active trade (cf. telegram /status -> 'no active trade') so it does not seem to be aware that the order were filled

Is that a normal way to act for FT or an issue ?
If freqtrade has no active trade, will it be able to sell the previously bought cryptos ? (perhaps I did not correctly understood the "active trade" definition)

To bypass this issuer, I configured the "stake_amount" parameters a little bit lower than the amount of available currency (currently testing).

Thank you
Xavier
Order cancelled AND fullfilled
## Step 1: Have you search for this issue before posting it?
Yes

## Step 2: Describe your environment

  * Python Version: 3.6.7 (`python -V`)
  * CCXT version: 1.18.516 (`pip freeze | grep ccxt`)
  * Branch: Develop
  * Last Commit ID: 46b1ecc77d53054aed6835d69f9989316422aa78 (`git log --format="%H" -n 1`)
  
## Step 3: Describe the problem:

Exchange: Kraken
In configuration file: "stake_amount": "unlimited",

- Freqtrade makes a limit order


```
2019-05-16 10:00:05,129 - freqtrade.freqtradebot - INFO - Found no buy signals for whitelisted currencies. Trying again..
2019-05-16 10:00:16,095 - freqtrade.freqtradebot - INFO - Buy signal found: about create a new trade with stake_amount: 19.7381 ...
2019-05-16 10:00:16,096 - freqtrade.freqtradebot - INFO - Using Last Ask / Last Price
2019-05-16 10:00:19,401 - freqtrade.rpc.rpc_manager - INFO - Sending rpc message: {'type': buy, 'exchange': 'Kraken', 'pair': 'BTC/EUR', 'limit': 7150.3, 'stake_amount': 19.7381, 'stake_currency': 'EUR', 'fiat_currency': 'EUR'}
2019-05-16 10:00:22,382 - freqtrade.wallets - INFO - Wallets synced.
```

- Kraken cancels the order

```
2019-05-16 10:00:25,471 - freqtrade.rpc.rpc_manager - INFO - Sending rpc message: {'type': status, 'status': 'Unfilled buy order for BTC/EUR canceled on Exchange'}
```

ISSUE:

- Kraken tells FT that the order is canceled

- but Kraken filled the order.
Here is the explaination of the behavior of kraken:
https://support.kraken.com/hc/en-us/articles/235162907-Order-Canceled-due-to-Out-of-Funds-

```
Order Canceled due to "Out of Funds":
It is possible for an order to partially execute (or "fill") if there are insufficient balances in the account for the order to fully execute.
This most commonly occurs when an order is placed for maximum volume. 
[...]
This is because the trading fee was not taken into consideration when placing the order.
```

- But FT has no active trade (cf. telegram /status -> 'no active trade') so it does not seem to be aware that the order were filled

Is that a normal way to act for FT or an issue ?
If freqtrade has no active trade, will it be able to sell the previously bought cryptos ? (perhaps I did not correctly understood the "active trade" definition)

To bypass this issuer, I configured the "stake_amount" parameters a little bit lower than the amount of available currency (currently testing).

Thank you
Xavier
WARNING: Unable to sell trade: Insufficient funds to create limit sell order on market
## Describe your environment

  * Operating system: Ubuntu 18.04.3 LTS
  * Python Version: Python 3.6.8
  * CCXT version: ccxt==1.18.1208
  * Branch: Develop
  * Last Commit ID: 06ab51b53d9c7b5bc43e0cdd03d704376aa71c26
  
## Describe the problem:

Sometimes a buy fails for the total amount, due to a timeout, for example:

Binance: Buying OMG/BTC
at rate 0.00009590
(0.050000 BTC,362.484 EUR)

A bit later:
Status: Remaining buy order for OMG/BTC cancelled due to timeout

A portion is bought, but in the database (trades) the total amount is written.

Trade ID: 26 (since 35 minutes ago)
Current Pair: OMG/BTC
Amount: 235.17 (0.0225528 BTC)
Open Rate: 0.00009590
Current Rate: 0.00009590
Current Profit: -0.20%
Stoploss: 0.00007193 (-25.00%)

The real amount bought in Binance is 234.93

So when it want to sell the items, there areinsufficient funds on Binance... Resulting in these kind of errors:

Unable to sell trade: Insufficient funds to create limit sell order on market OMG/BTC.Tried to sell amount 235.17 at rate xxx.Message: binance Account has insufficient...

### Observed Results:

  * What happened?
See Above

  * What did you expect to happen?
I expect that the trades table contains the updated amount of bought coins after a timeout to reflect the real amount so a sell cannot fail. (resulting in losses)



Discussion:
Well, to be honest, this seems to be something specific to kraken (which not an officially supported exchange yet - however there is work to get that included).

I've not seen anything similar (that an order reports as cancelled, but is partially filled).

We'd need the output / return value from ccxt for that order  - feel free to mask order numbers / anything tied to your account.


``` python
import ccxt
ct = ccxt.Kraken({'apiKey': 'yourkey', 'secret': 'yoursecret'})
ct.fetch_order('order_id', 'pair')

```

you should be able to find both order_id and pair in the database ... (and you'll need to obviously supply your api keys to that script)...
> 
> 
> Well, to be honest, this seems to be something specific to kraken (which not an officially supported exchange yet - however there is work to get that included).
> 
> I've not seen anything similar (that an order reports as cancelled, but is partially filled).
> 
> We'd need the output / return value from ccxt for that order - feel free to mask order numbers / anything tied to your account.
> 
> ```python
> import ccxt
> ct = ccxt.Kraken({'apiKey': 'yourkey', 'secret': 'yoursecret'})
> ct.fetch_order('order_id', 'pair')
> ```
> 
> you should be able to find both order_id and pair in the database ... (and you'll need to obviously supply your api keys to that script)...

@xmatthias 
Thank you for giving me the script commands.
Here is the result:

```
{'info': {'id': 'XXXXXXX',
  'refid': None,
  'userref': 0,
  'status': 'canceled',
  'reason': 'Out of funds',
  'opentm': 1557993619.2635,
  'closetm': 1557993619.2742,
  'starttm': 0,
  'expiretm': 0,
  'descr': {'pair': 'XBTEUR',
   'type': 'buy',
   'ordertype': 'limit',
   'price': '7150.3',
   'price2': '0',
   'leverage': 'none',
   'order': 'buy 0.00276045 XBTEUR @ limit 7150.3',
   'close': ''},
  'vol': '0.00276045',
  'vol_exec': '0.00275330',
  'cost': '19.6',
  'fee': '0',
  'price': '7150.2',
  'stopprice': '0.00000',
  'limitprice': '0.00000',
  'misc': 'partial',
  'oflags': 'fciq',
  'trades': ['XXXXXXX']},
 'id': 'XXXXXX',
 'timestamp': 1557993619263,
 'datetime': '2019-05-16T08:00:19.263Z',
 'lastTradeTimestamp': None,
 'status': 'canceled',
 'symbol': 'BTC/EUR',
 'type': 'limit',
 'side': 'buy',
 'price': 7150.3,
 'cost': 19.6,
 'amount': 0.00276045,
 'filled': 0.0027533,
 'average': 7150.2,
 'remaining': 7.149999999999605e-06,
 'fee': {'cost': 0.0, 'rate': None, 'currency': 'EUR'}}
```


Well, to be honest, this seems to be something specific to kraken (which not an officially supported exchange yet - however there is work to get that included).

I've not seen anything similar (that an order reports as cancelled, but is partially filled).

We'd need the output / return value from ccxt for that order  - feel free to mask order numbers / anything tied to your account.


``` python
import ccxt
ct = ccxt.Kraken({'apiKey': 'yourkey', 'secret': 'yoursecret'})
ct.fetch_order('order_id', 'pair')

```

you should be able to find both order_id and pair in the database ... (and you'll need to obviously supply your api keys to that script)...
> 
> 
> Well, to be honest, this seems to be something specific to kraken (which not an officially supported exchange yet - however there is work to get that included).
> 
> I've not seen anything similar (that an order reports as cancelled, but is partially filled).
> 
> We'd need the output / return value from ccxt for that order - feel free to mask order numbers / anything tied to your account.
> 
> ```python
> import ccxt
> ct = ccxt.Kraken({'apiKey': 'yourkey', 'secret': 'yoursecret'})
> ct.fetch_order('order_id', 'pair')
> ```
> 
> you should be able to find both order_id and pair in the database ... (and you'll need to obviously supply your api keys to that script)...

@xmatthias 
Thank you for giving me the script commands.
Here is the result:

```
{'info': {'id': 'XXXXXXX',
  'refid': None,
  'userref': 0,
  'status': 'canceled',
  'reason': 'Out of funds',
  'opentm': 1557993619.2635,
  'closetm': 1557993619.2742,
  'starttm': 0,
  'expiretm': 0,
  'descr': {'pair': 'XBTEUR',
   'type': 'buy',
   'ordertype': 'limit',
   'price': '7150.3',
   'price2': '0',
   'leverage': 'none',
   'order': 'buy 0.00276045 XBTEUR @ limit 7150.3',
   'close': ''},
  'vol': '0.00276045',
  'vol_exec': '0.00275330',
  'cost': '19.6',
  'fee': '0',
  'price': '7150.2',
  'stopprice': '0.00000',
  'limitprice': '0.00000',
  'misc': 'partial',
  'oflags': 'fciq',
  'trades': ['XXXXXXX']},
 'id': 'XXXXXX',
 'timestamp': 1557993619263,
 'datetime': '2019-05-16T08:00:19.263Z',
 'lastTradeTimestamp': None,
 'status': 'canceled',
 'symbol': 'BTC/EUR',
 'type': 'limit',
 'side': 'buy',
 'price': 7150.3,
 'cost': 19.6,
 'amount': 0.00276045,
 'filled': 0.0027533,
 'average': 7150.2,
 'remaining': 7.149999999999605e-06,
 'fee': {'cost': 0.0, 'rate': None, 'currency': 'EUR'}}
```


unfilled / half-filled timeouts are a known problem (#1858) ... 

Would you have the logs from right after the buy / cancel?

I'm looking for messages like `Half bought? Amounts don't match` or `"Amount xxx does not match amount`  or `Partial buy order timeout for ...`  ... but every additional line will help identify the issue.

Thanks in advance
I looked for similar issues before opening issue, but didn't notice the one you mentioned, overlooked it..

Here are the log snippets related to my example:
```
Oct 17 05:32:02 server python3[13987]: 2019-10-17 05:32:02,674 - freqtrade.freqtradebot - INFO - Partial buy order timeout for Trade(id=26, pair=OMG/BTC, amount=235.17000000, open_rate=0.00009590, open_since=2019-10-17 03:21:59).
Oct 17 05:32:02 server python3[13987]: 2019-10-17 05:32:02,674 - freqtrade.rpc.rpc_manager - INFO - Sending rpc message: {'type': status, 'status': 'Remaining buy order for OMG/BTC cancelled due to timeout'}
Oct 17 05:32:03 server python3[13987]: 2019-10-17 05:32:03,177 - freqtrade.wallets - INFO - Wallets synced.
Oct 17 09:33:07 server python3[13987]: 2019-10-17 09:33:07,913 - freqtrade.freqtradebot - WARNING - Unable to sell trade: Insufficient funds to create limit sell order on market OMG/BTC.Tried to sell amount 235.16 at rate 9.61e-05.Message: binance Account has insufficient balance for requested action.
```
Last one repeats until I manually correct the trades table record with correct OMG/BTC amount
```
UPDATE trades
SET amount=234.93 WHERE id=26;
```
```
Oct 17 09:33:12 server python3[13987]: 2019-10-17 09:33:12,925 - freqtrade.persistence - INFO - Updating trade (id=26) ...
Oct 17 09:33:12 server python3[13987]: 2019-10-17 09:33:12,925 - freqtrade.persistence - INFO - Marking Trade(id=26, pair=OMG/BTC, amount=234.93000000, open_rate=0.00009590, open_since=closed) as closed as the trade is fulfilled and found no open orders for it.
Oct 17 09:33:12 server python3[13987]: 2019-10-17 09:33:12,925 - freqtrade.persistence - INFO - LIMIT_SELL has been fulfilled for Trade(id=26, pair=OMG/BTC, amount=234.93000000, open_rate=0.00009590, open_since=closed).
Oct 17 09:33:12 server python3[13987]: 2019-10-17 09:33:12,945 - freqtrade.exchange.exchange - INFO - returning cached ticker-data for OMG/BTC
```

I see no other related errors or warnings. As far as I can see the update of bought amount is not corrected after the timeout warning. 
A quick fix could be to check the amount in wallet after these errors/warnings occur and if it's lower than the db amount correct it. (maybe easier said then done)
This is strange - right before the output of `Partial buy order timeout for ...` the amount should be updated. 

https://github.com/freqtrade/freqtrade/blob/df62dd65d3ff3c04492f2fb91affa136f533d3eb/freqtrade/freqtradebot.py#L811-L816

Can you please provide the output of the below (either the commented line if you know the order id, otherwise the statement below).


```
import ccxt
from datetime import datetime
ct = ccxt.Binance({'apiKey': 'yourkey', 'secret': 'yoursecret'})
# ct.fetch_order('order_id', 'pair')
ct.fetch_orders(symbol='OMG/BTC', since=int(datetime(2019, 10, 17, 8, 0).timestamp()))
```
All OMG/BTC orders since 05:00 today below.
So the filled value is same as the one in the database!
But it's not the amount available in my Binance wallet after the buy (234,93)
Any idea, has to with fees, timing/latency, slippage?
I'm a cryptotrader novice, so don't shoot me ;-)
BTW I changed the Client and OrderID's not to give away sensitive data.

```
#
[{
    'info': {
        'symbol': 'OMGBTC',
        'orderId': 62125617,
        'orderListId': -1,
        'clientOrderId': 'iuqVeqm5s4NulGg0nMMW8B',
        'price': '0.00009590',
        'origQty': '521.37000000',
        'executedQty': '235.17000000',
        'cummulativeQuoteQty': '0.02255279',
        'status': 'CANCELED',
        'timeInForce': 'GTC',
        'type': 'LIMIT',
        'side': 'BUY',
        'stopPrice': '0.00000000',
        'icebergQty': '0.00000000',
        'time': 1571282519521,
        'updateTime': 1571283122559,
        'isWorking': True,
        },
    'id': '62125617',
    'timestamp': 1571282519520,
    'datetime': '2019-10-17T03:21:59.521Z',
    'lastTradeTimestamp': None,
    'symbol': 'OMG/BTC',
    'type': 'limit',
    'side': 'buy',
    'price': 9.59e-05,
    'amount': 521.37,
    'cost': 0.02255279,
    'average': 9.589994472084025e-05,
    'filled': 235.17,
    'remaining': 286.20000000000005,
    'status': 'canceled',
    'fee': None,
    'trades': None,
    }, {
    'info': {
        'symbol': 'OMGBTC',
        'orderId': 62131619,
        'orderListId': -1,
        'clientOrderId': 'DFVLrHoY8Z2xYichNKelAA',
        'price': '0.00009610',
        'origQty': '234.93000000',
        'executedQty': '234.93000000',
        'cummulativeQuoteQty': '0.02257677',
        'status': 'FILLED',
        'timeInForce': 'GTC',
        'type': 'LIMIT',
        'side': 'SELL',
        'stopPrice': '0.00000000',
        'icebergQty': '0.00000000',
        'time': 1571297592794,
        'updateTime': 1571297592794,
        'isWorking': True,
        },
    'id': '62131619',
    'timestamp': 1571297592794,
    'datetime': '2019-10-17T07:33:12.794Z',
    'lastTradeTimestamp': None,
    'symbol': 'OMG/BTC',
    'type': 'limit',
    'side': 'sell',
    'price': 9.61e-05,
    'amount': 234.93,
    'cost': 0.02257677,
    'average': 9.609998723023879e-05,
    'filled': 234.93,
    'remaining': 0.0,
    'status': 'closed',
    'fee': None,
    'trades': None,
    }]
```
Nobody will be shot here (not today, anyway ...)

It's also not a duplicate, since the logic to update the trade-amount did work (which is different for kraken, which cancels the order on the exchange somehow).

I think the relevant section is the following
```
  'amount': 521.37,
    'cost': 0.02255279,
    'average': 9.589994472084025e-05,
    'filled': 235.17,
    'remaining': 286.20000000000005,
```

the order was for 521.37 - it filled 235.17 (the entry in the database) - and had 286.2 remaining (the logic to update the amount did work).

Now you are left with 234.93 - which is 0.239 - or 0.1xx% ... so yes, i suspect that you're right, it's fees which are missing.


Can we please have the response for the question about the Trades too (it'll include how fees were calculated).

```
import ccxt
from datetime import datetime
ct = ccxt.Binance({'apiKey': 'yourkey', 'secret': 'yoursecret'})

ct.fetch_my_trades(symbol='OMG/BTC', since=int(datetime(2019, 10, 17, 5, 0).timestamp()))
```

This is the trades query output.

```
#!/usr/bin/python
#
[{
    'info': {
        'symbol': 'OMGBTC',
        'id': 10244928,
        'orderId': 62125617,
        'orderListId': -1,
        'price': '0.00009590',
        'qty': '132.53000000',
        'quoteQty': '0.01270962',
        'commission': '0.13253000',
        'commissionAsset': 'OMG',
        'time': 1571282519521,
        'isBuyer': True,
        'isMaker': False,
        'isBestMatch': True,
        },
    'timestamp': 1571282519521,
    'datetime': '2019-10-17T03:21:59.521Z',
    'symbol': 'OMG/BTC',
    'id': '10244928',
    'order': '62125617',
    'type': None,
    'takerOrMaker': 'taker',
    'side': 'buy',
    'price': 9.59e-05,
    'amount': 132.53,
    'cost': 0.012709627,
    'fee': {'cost': 0.13253, 'currency': 'OMG'},
    }, {
    'info': {
        'symbol': 'OMGBTC',
        'id': 10244931,
        'orderId': 62125617,
        'orderListId': -1,
        'price': '0.00009590',
        'qty': '100.00000000',
        'quoteQty': '0.00959000',
        'commission': '0.10000000',
        'commissionAsset': 'OMG',
        'time': 1571282519521,
        'isBuyer': True,
        'isMaker': False,
        'isBestMatch': True,
        },
    'timestamp': 1571282519521,
    'datetime': '2019-10-17T03:21:59.521Z',
    'symbol': 'OMG/BTC',
    'id': '10244931',
    'order': '62125617',
    'type': None,
    'takerOrMaker': 'taker',
    'side': 'buy',
    'price': 9.59e-05,
    'amount': 100.0,
    'cost': 0.00959,
    'fee': {'cost': 0.1, 'currency': 'OMG'},
    }, {
    'info': {
        'symbol': 'OMGBTC',
        'id': 10244930,
        'orderId': 62125617,
        'orderListId': -1,
        'price': '0.00009590',
        'qty': '2.64000000',
        'quoteQty': '0.00025317',
        'commission': '0.00264000',
        'commissionAsset': 'OMG',
        'time': 1571282609097,
        'isBuyer': True,
        'isMaker': True,
        'isBestMatch': True,
        },
    'timestamp': 1571282609097,
    'datetime': '2019-10-17T03:23:29.097Z',
    'symbol': 'OMG/BTC',
    'id': '10244930',
    'order': '62125617',
    'type': None,
    'takerOrMaker': 'maker',
    'side': 'buy',
    'price': 9.59e-05,
    'amount': 2.64,
    'cost': 0.00025317600000000003,
    'fee': {'cost': 0.00264, 'currency': 'OMG'},
    }, {
    'info': {
        'symbol': 'OMGBTC',
        'id': 10245304,
        'orderId': 62131619,
        'orderListId': -1,
        'price': '0.00009610',
        'qty': '234.93000000',
        'quoteQty': '0.02257677',
        'commission': '0.00002258',
        'commissionAsset': 'BTC',
        'time': 1571297592794,
        'isBuyer': False,
        'isMaker': False,
        'isBestMatch': True,
        },
    'timestamp': 1571297592794,
    'datetime': '2019-10-17T07:33:12.794Z',
    'symbol': 'OMG/BTC',
    'id': '10245304',
    'order': '62131619',
    'type': None,
    'takerOrMaker': 'taker',
    'side': 'sell',
    'price': 9.61e-05,
    'amount': 234.93,
    'cost': 0.022576773,
    'fee': {'cost': 2.258e-05, 'currency': 'BTC'},
    }]

```

Resolving patch:
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -266,6 +266,6 @@
         'stake_amount',
         'dry_run',
         'bid_strategy',
-        'telegram'
+        'unfilledtimeout',
     ]
 }
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -11,7 +11,7 @@
 import arrow
 from requests.exceptions import RequestException
 
-from freqtrade import (DependencyException, OperationalException, InvalidOrderException,
+from freqtrade import (DependencyException, InvalidOrderException,
                        __version__, constants, persistence)
 from freqtrade.data.converter import order_book_to_dataframe
 from freqtrade.data.dataprovider import DataProvider
@@ -466,12 +466,13 @@ def process_maybe_execute_sells(self, trades: List[Any]) -> None:
         if result:
             self.wallets.update()
 
-    def get_real_amount(self, trade: Trade, order: Dict) -> float:
+    def get_real_amount(self, trade: Trade, order: Dict, order_amount: float = None) -> float:
         """
         Get real amount for the trade
         Necessary for exchanges which charge fees in base currency (e.g. binance)
         """
-        order_amount = order['amount']
+        if order_amount is None:
+            order_amount = order['amount']
         # Only run for closed orders
         if trade.fee_open == 0 or order['status'] == 'open':
             return order_amount
@@ -508,7 +509,7 @@ def get_real_amount(self, trade: Trade, order: Dict) -> float:
 
         if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC):
             logger.warning(f"Amount {amount} does not match amount {trade.amount}")
-            raise OperationalException("Half bought? Amounts don't match")
+            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} "
@@ -536,7 +537,7 @@ def update_trade_state(self, trade, action_order: dict = None):
                     # Fee was applied, so set to 0
                     trade.fee_open = 0
 
-            except OperationalException as exception:
+            except DependencyException as exception:
                 logger.warning("Could not update trade amount: %s", exception)
 
             trade.update(order)
@@ -772,21 +773,19 @@ def check_handle_timedout(self) -> None:
                 self.wallets.update()
                 continue
 
-            # Handle cancelled on exchange
-            if order['status'] == 'canceled':
-                if order['side'] == 'buy':
-                    self.handle_buy_order_full_cancel(trade, "canceled on Exchange")
-                elif order['side'] == 'sell':
-                    self.handle_timedout_limit_sell(trade, order)
-                    self.wallets.update()
-            # Check if order is still actually open
-            elif order['status'] == 'open':
-                if order['side'] == 'buy' and ordertime < buy_timeoutthreashold:
-                    self.handle_timedout_limit_buy(trade, order)
-                    self.wallets.update()
-                elif order['side'] == 'sell' and ordertime < sell_timeoutthreashold:
-                    self.handle_timedout_limit_sell(trade, order)
-                    self.wallets.update()
+            if (order['side'] == 'buy'
+                and order['status'] == 'canceled'
+                or (order['status'] == 'open'
+                    and order['side'] == 'buy' and ordertime < buy_timeoutthreashold)):
+
+                self.handle_timedout_limit_buy(trade, order)
+                self.wallets.update()
+
+            elif (order['side'] == 'sell' and order['status'] == 'canceled'
+                  or (order['status'] == 'open'
+                      and order['side'] == 'sell' and ordertime < sell_timeoutthreashold)):
+                self.handle_timedout_limit_sell(trade, order)
+                self.wallets.update()
 
     def handle_buy_order_full_cancel(self, trade: Trade, reason: str) -> None:
         """Close trade in database and send message"""
@@ -802,16 +801,33 @@ def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool:
         """Buy timeout - cancel order
         :return: True if order was fully cancelled
         """
-        self.exchange.cancel_order(trade.open_order_id, trade.pair)
-        if order['remaining'] == order['amount']:
+        reason = "cancelled due to timeout"
+        if order['status'] != 'canceled':
+            corder = self.exchange.cancel_order(trade.open_order_id, trade.pair)
+        else:
+            # Order was cancelled already, so we can reuse the existing dict
+            corder = order
+            reason = "canceled on Exchange"
+
+        if corder['remaining'] == corder['amount']:
             # if trade is not partially completed, just delete the trade
-            self.handle_buy_order_full_cancel(trade, "cancelled due to timeout")
+            self.handle_buy_order_full_cancel(trade, reason)
             return True
 
         # if trade is partially complete, edit the stake details for the trade
         # and close the order
-        trade.amount = order['amount'] - order['remaining']
+        trade.amount = corder['amount'] - corder['remaining']
         trade.stake_amount = trade.amount * trade.open_rate
+        # verify if fees were taken from amount to avoid problems during selling
+        try:
+            new_amount = self.get_real_amount(trade, corder, trade.amount)
+            if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC):
+                trade.amount = new_amount
+                # Fee was applied, so set to 0
+                trade.fee_open = 0
+        except DependencyException as e:
+            logger.warning("Could not update trade amount: %s", e)
+
         trade.open_order_id = None
         logger.info('Partial buy order timeout for %s.', trade)
         self.rpc.send_msg({
diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py
--- a/freqtrade/rpc/rpc_manager.py
+++ b/freqtrade/rpc/rpc_manager.py
@@ -18,7 +18,7 @@ def __init__(self, freqtrade) -> None:
         self.registered_modules: List[RPC] = []
 
         # Enable telegram
-        if freqtrade.config['telegram'].get('enabled', False):
+        if freqtrade.config.get('telegram', {}).get('enabled', False):
             logger.info('Enabling rpc.telegram ...')
             from freqtrade.rpc.telegram import Telegram
             self.registered_modules.append(Telegram(freqtrade))


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -9,8 +9,8 @@
 from unittest.mock import MagicMock, PropertyMock
 
 import arrow
-import pytest
 import numpy as np
+import pytest
 from telegram import Chat, Message, Update
 
 from freqtrade import constants, persistence
@@ -19,10 +19,10 @@
 from freqtrade.edge import Edge, PairInfo
 from freqtrade.exchange import Exchange
 from freqtrade.freqtradebot import FreqtradeBot
+from freqtrade.persistence import Trade
 from freqtrade.resolvers import ExchangeResolver
 from freqtrade.worker import Worker
 
-
 logging.getLogger('').setLevel(logging.INFO)
 
 
@@ -608,6 +608,14 @@ def limit_buy_order_old_partial():
     }
 
 
+@pytest.fixture
+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'}
+    return res
+
+
 @pytest.fixture
 def limit_sell_order():
     return {
@@ -896,12 +904,6 @@ def result(testdatadir):
         return parse_ticker_dataframe(json.load(data_file), '1m', pair="UNITTEST/BTC",
                                       fill_missing=True)
 
-# FIX:
-# Create an fixture/function
-# that inserts a trade of some type and open-status
-# return the open-order-id
-# See tests in rpc/main that could use this
-
 
 @pytest.fixture(scope="function")
 def trades_for_order():
@@ -1075,3 +1077,19 @@ def mockedimport(name, *args, **kwargs):
 
     # restore previous importfunction
     builtins.__import__ = realimport
+
+
+@pytest.fixture(scope="function")
+def open_trade():
+    return Trade(
+        pair='ETH/BTC',
+        open_rate=0.00001099,
+        exchange='bittrex',
+        open_order_id='123456789',
+        amount=90.99181073,
+        fee_open=0.0,
+        fee_close=0.0,
+        stake_amount=1,
+        open_date=arrow.utcnow().shift(minutes=-601).datetime,
+        is_open=True
+    )
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -1678,7 +1678,7 @@ def test_update_trade_state_exception(mocker, default_conf,
     # Test raise of OperationalException exception
     mocker.patch(
         'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
-        side_effect=OperationalException()
+        side_effect=DependencyException()
     )
     freqtrade.update_trade_state(trade)
     assert log_has('Could not update trade amount: ', caplog)
@@ -1916,7 +1916,8 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order,
         freqtrade.handle_trade(trade)
 
 
-def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fee, mocker) -> None:
+def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, open_trade,
+                                   fee, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
     patch_exchange(mocker)
@@ -1929,31 +1930,18 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fe
     )
     freqtrade = FreqtradeBot(default_conf)
 
-    trade_buy = Trade(
-        pair='ETH/BTC',
-        open_rate=0.00001099,
-        exchange='bittrex',
-        open_order_id='123456789',
-        amount=90.99181073,
-        fee_open=0.0,
-        fee_close=0.0,
-        stake_amount=1,
-        open_date=arrow.utcnow().shift(minutes=-601).datetime,
-        is_open=True
-    )
-
-    Trade.session.add(trade_buy)
+    Trade.session.add(open_trade)
 
     # check it does cancel buy orders over the time limit
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 1
     assert rpc_mock.call_count == 1
-    trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
+    trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     nb_trades = len(trades)
     assert nb_trades == 0
 
 
-def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old,
+def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old, open_trade,
                                     fee, mocker, caplog) -> None:
     """ Handle Buy order cancelled on exchange"""
     rpc_mock = patch_RPCManager(mocker)
@@ -1969,32 +1957,19 @@ def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old,
     )
     freqtrade = FreqtradeBot(default_conf)
 
-    trade_buy = Trade(
-        pair='ETH/BTC',
-        open_rate=0.00001099,
-        exchange='bittrex',
-        open_order_id='123456789',
-        amount=90.99181073,
-        fee_open=0.0,
-        fee_close=0.0,
-        stake_amount=1,
-        open_date=arrow.utcnow().shift(minutes=-601).datetime,
-        is_open=True
-    )
-
-    Trade.session.add(trade_buy)
+    Trade.session.add(open_trade)
 
     # check it does cancel buy orders over the time limit
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     assert rpc_mock.call_count == 1
-    trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
+    trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     nb_trades = len(trades)
     assert nb_trades == 0
     assert log_has_re("Buy order canceled on Exchange for Trade.*", caplog)
 
 
-def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_order_old,
+def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_order_old, open_trade,
                                              fee, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
@@ -2009,31 +1984,19 @@ def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_ord
     )
     freqtrade = FreqtradeBot(default_conf)
 
-    trade_buy = Trade(
-        pair='ETH/BTC',
-        open_rate=0.00001099,
-        exchange='bittrex',
-        open_order_id='123456789',
-        amount=90.99181073,
-        fee_open=0.0,
-        fee_close=0.0,
-        stake_amount=1,
-        open_date=arrow.utcnow().shift(minutes=-601).datetime,
-        is_open=True
-    )
-
-    Trade.session.add(trade_buy)
+    Trade.session.add(open_trade)
 
     # check it does cancel buy orders over the time limit
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     assert rpc_mock.call_count == 0
-    trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
+    trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     nb_trades = len(trades)
     assert nb_trades == 1
 
 
-def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, mocker) -> None:
+def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, mocker,
+                                    open_trade) -> None:
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
     patch_exchange(mocker)
@@ -2045,30 +2008,20 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
     )
     freqtrade = FreqtradeBot(default_conf)
 
-    trade_sell = Trade(
-        pair='ETH/BTC',
-        open_rate=0.00001099,
-        exchange='bittrex',
-        open_order_id='123456789',
-        amount=90.99181073,
-        fee_open=0.0,
-        fee_close=0.0,
-        stake_amount=1,
-        open_date=arrow.utcnow().shift(hours=-5).datetime,
-        close_date=arrow.utcnow().shift(minutes=-601).datetime,
-        is_open=False
-    )
+    open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
+    open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
+    open_trade.is_open = False
 
-    Trade.session.add(trade_sell)
+    Trade.session.add(open_trade)
 
     # check it does cancel sell orders over the time limit
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 1
     assert rpc_mock.call_count == 1
-    assert trade_sell.is_open is True
+    assert open_trade.is_open is True
 
 
-def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old,
+def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old, open_trade,
                                      mocker, caplog) -> None:
     """ Handle sell order cancelled on exchange"""
     rpc_mock = patch_RPCManager(mocker)
@@ -2083,34 +2036,24 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old,
     )
     freqtrade = FreqtradeBot(default_conf)
 
-    trade_sell = Trade(
-        pair='ETH/BTC',
-        open_rate=0.00001099,
-        exchange='bittrex',
-        open_order_id='123456789',
-        amount=90.99181073,
-        fee_open=0.0,
-        fee_close=0.0,
-        stake_amount=1,
-        open_date=arrow.utcnow().shift(hours=-5).datetime,
-        close_date=arrow.utcnow().shift(minutes=-601).datetime,
-        is_open=False
-    )
+    open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
+    open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
+    open_trade.is_open = False
 
-    Trade.session.add(trade_sell)
+    Trade.session.add(open_trade)
 
     # check it does cancel sell orders over the time limit
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 0
     assert rpc_mock.call_count == 1
-    assert trade_sell.is_open is True
+    assert open_trade.is_open is True
     assert log_has_re("Sell order canceled on exchange for Trade.*", caplog)
 
 
 def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial,
-                                       mocker) -> None:
+                                       open_trade, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
-    cancel_order_mock = MagicMock()
+    cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial)
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -2120,33 +2063,97 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
     )
     freqtrade = FreqtradeBot(default_conf)
 
-    trade_buy = Trade(
-        pair='ETH/BTC',
-        open_rate=0.00001099,
-        exchange='bittrex',
-        open_order_id='123456789',
-        amount=90.99181073,
-        fee_open=0.0,
-        fee_close=0.0,
-        stake_amount=1,
-        open_date=arrow.utcnow().shift(minutes=-601).datetime,
-        is_open=True
-    )
-
-    Trade.session.add(trade_buy)
+    Trade.session.add(open_trade)
 
     # check it does cancel buy orders over the time limit
     # note this is for a partially-complete buy order
     freqtrade.check_handle_timedout()
     assert cancel_order_mock.call_count == 1
     assert rpc_mock.call_count == 1
-    trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
+    trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
     assert len(trades) == 1
     assert trades[0].amount == 23.0
-    assert trades[0].stake_amount == trade_buy.open_rate * trades[0].amount
+    assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount
+
+
+def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, caplog, fee,
+                                           limit_buy_order_old_partial, trades_for_order,
+                                           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)
+    patch_exchange(mocker)
+    mocker.patch.multiple(
+        'freqtrade.exchange.Exchange',
+        get_ticker=ticker,
+        get_order=MagicMock(return_value=limit_buy_order_old_partial),
+        cancel_order=cancel_order_mock,
+        get_trades_for_order=MagicMock(return_value=trades_for_order),
+    )
+    freqtrade = FreqtradeBot(default_conf)
 
+    assert open_trade.amount == limit_buy_order_old_partial['amount']
+
+    open_trade.fee_open = fee()
+    open_trade.fee_close = fee()
+    Trade.session.add(open_trade)
+    # cancelling a half-filled order should update the amount to the bought amount
+    # and apply fees if necessary.
+    freqtrade.check_handle_timedout()
 
-def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -> None:
+    assert log_has_re(r"Applying fee on amount for Trade.* Order", caplog)
+
+    assert cancel_order_mock.call_count == 1
+    assert rpc_mock.call_count == 1
+    trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
+    assert len(trades) == 1
+    # Verify that tradehas been updated
+    assert trades[0].amount == (limit_buy_order_old_partial['amount'] -
+                                limit_buy_order_old_partial['remaining']) - 0.0001
+    assert trades[0].open_order_id is None
+    assert trades[0].fee_open == 0
+
+
+def test_check_handle_timedout_partial_except(default_conf, ticker, open_trade, caplog, fee,
+                                              limit_buy_order_old_partial, trades_for_order,
+                                              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)
+    patch_exchange(mocker)
+    mocker.patch.multiple(
+        'freqtrade.exchange.Exchange',
+        get_ticker=ticker,
+        get_order=MagicMock(return_value=limit_buy_order_old_partial),
+        cancel_order=cancel_order_mock,
+        get_trades_for_order=MagicMock(return_value=trades_for_order),
+    )
+    mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
+                 MagicMock(side_effect=DependencyException))
+    freqtrade = FreqtradeBot(default_conf)
+
+    assert open_trade.amount == limit_buy_order_old_partial['amount']
+
+    open_trade.fee_open = fee()
+    open_trade.fee_close = fee()
+    Trade.session.add(open_trade)
+    # cancelling a half-filled order should update the amount to the bought amount
+    # and apply fees if necessary.
+    freqtrade.check_handle_timedout()
+
+    assert log_has_re(r"Could not update trade amount: .*", caplog)
+
+    assert cancel_order_mock.call_count == 1
+    assert rpc_mock.call_count == 1
+    trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
+    assert len(trades) == 1
+    # Verify that tradehas been updated
+
+    assert trades[0].amount == (limit_buy_order_old_partial['amount'] -
+                                limit_buy_order_old_partial['remaining'])
+    assert trades[0].open_order_id is None
+    assert trades[0].fee_open == fee()
+
+
+def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocker, caplog) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
     cancel_order_mock = MagicMock()
@@ -2164,34 +2171,20 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
     )
     freqtrade = FreqtradeBot(default_conf)
 
-    open_date = arrow.utcnow().shift(minutes=-601)
-    trade_buy = Trade(
-        pair='ETH/BTC',
-        open_rate=0.00001099,
-        exchange='bittrex',
-        open_order_id='123456789',
-        amount=90.99181073,
-        fee_open=0.0,
-        fee_close=0.0,
-        stake_amount=1,
-        open_date=open_date.datetime,
-        is_open=True
-    )
-
-    Trade.session.add(trade_buy)
+    Trade.session.add(open_trade)
 
     freqtrade.check_handle_timedout()
     assert log_has_re(r"Cannot query order for Trade\(id=1, pair=ETH/BTC, amount=90.99181073, "
                       r"open_rate=0.00001099, open_since="
-                      f"{open_date.strftime('%Y-%m-%d %H:%M:%S')}"
+                      f"{open_trade.open_date.strftime('%Y-%m-%d %H:%M:%S')}"
                       r"\) due to Traceback \(most recent call last\):\n*",
                       caplog)
 
 
-def test_handle_timedout_limit_buy(mocker, default_conf) -> None:
+def test_handle_timedout_limit_buy(mocker, default_conf, limit_buy_order) -> None:
     patch_RPCManager(mocker)
     patch_exchange(mocker)
-    cancel_order_mock = MagicMock()
+    cancel_order_mock = MagicMock(return_value=limit_buy_order)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
         cancel_order=cancel_order_mock
@@ -2201,13 +2194,14 @@ def test_handle_timedout_limit_buy(mocker, default_conf) -> None:
 
     Trade.session = MagicMock()
     trade = MagicMock()
-    order = {'remaining': 1,
-             'amount': 1}
-    assert freqtrade.handle_timedout_limit_buy(trade, order)
+    limit_buy_order['remaining'] = limit_buy_order['amount']
+    assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
+    assert cancel_order_mock.call_count == 1
+
+    cancel_order_mock.reset_mock()
+    limit_buy_order['amount'] = 2
+    assert not freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
     assert cancel_order_mock.call_count == 1
-    order['amount'] = 2
-    assert not freqtrade.handle_timedout_limit_buy(trade, order)
-    assert cancel_order_mock.call_count == 2
 
 
 def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
@@ -3361,7 +3355,7 @@ def test_get_real_amount_wrong_amount(default_conf, trades_for_order, buy_order_
     patch_get_signal(freqtrade)
 
     # Amount does not change
-    with pytest.raises(OperationalException, match=r"Half bought\? Amounts don't match"):
+    with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"):
         freqtrade.get_real_amount(trade, limit_buy_order)