From a39d51d7d02bf685387275357ca9f8aaef08ce11 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Oct 2019 19:33:21 +0200 Subject: [PATCH 1/7] Update test to use limit_buy_order --- tests/conftest.py | 6 ------ tests/test_freqtradebot.py | 15 +++++++-------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 6a0a74b5b..b0c68c805 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -896,12 +896,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(): diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 7fb84f078..28ba57c1d 100644 --- 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) @@ -2188,7 +2188,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) - 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() @@ -2201,12 +2201,11 @@ 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 - order['amount'] = 2 - assert not freqtrade.handle_timedout_limit_buy(trade, order) + limit_buy_order['amount'] = 2 + assert not freqtrade.handle_timedout_limit_buy(trade, limit_buy_order) assert cancel_order_mock.call_count == 2 @@ -3361,7 +3360,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) From c735d352656f05619a6134554ee5f8c1bf21ee41 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Oct 2019 19:53:01 +0200 Subject: [PATCH 2/7] Extract open_trade generation from freqtradebot --- freqtrade/freqtradebot.py | 6 +- tests/conftest.py | 20 ++++- tests/test_freqtradebot.py | 148 +++++++++---------------------------- 3 files changed, 54 insertions(+), 120 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d62c6a912..46ff09173 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -11,7 +11,7 @@ from typing import Any, Dict, List, Optional, Tuple 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 @@ -508,7 +508,7 @@ class FreqtradeBot: 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 +536,7 @@ class FreqtradeBot: # 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) diff --git a/tests/conftest.py b/tests/conftest.py index b0c68c805..8871c01b4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,8 +9,8 @@ from pathlib import Path 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.data.converter import parse_ticker_dataframe 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) @@ -1069,3 +1069,19 @@ def import_fails() -> None: # 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 index 28ba57c1d..3072a4661 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -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,32 +2036,22 @@ 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() patch_exchange(mocker) @@ -2120,33 +2063,20 @@ 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_exception(default_conf, ticker, mocker, caplog) -> None: +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,26 +2094,12 @@ 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) @@ -2204,9 +2120,11 @@ def test_handle_timedout_limit_buy(mocker, default_conf, limit_buy_order) -> Non 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 == 2 + assert cancel_order_mock.call_count == 1 def test_handle_timedout_limit_sell(mocker, default_conf) -> None: From 0ac46eddca5a5277148cc824c46d05873586b094 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 18 Oct 2019 06:38:07 +0200 Subject: [PATCH 3/7] Add tests for new scenario --- tests/conftest.py | 8 ++++++++ tests/test_freqtradebot.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 8871c01b4..7d3f1c7ab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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 { diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 3072a4661..89e4a40da 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2076,6 +2076,42 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old 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, + 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 = 0.0025 + open_trade.fee_close = 0.0025 + 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"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'] - 0.0001 + assert trades[0].open_order_id is None + assert trades[0].fee_open == 0 + + def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocker, caplog) -> None: patch_RPCManager(mocker) patch_exchange(mocker) From c181fac6c706fb334ca6f5e8b9200faf62afce7d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 18 Oct 2019 06:46:48 +0200 Subject: [PATCH 4/7] fix #2383 --- freqtrade/freqtradebot.py | 16 +++++++++++++--- tests/test_freqtradebot.py | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 46ff09173..9b9fd3531 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -802,16 +802,26 @@ class FreqtradeBot: """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']: + corder = self.exchange.cancel_order(trade.open_order_id, trade.pair) + 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") 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) + 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/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 89e4a40da..b5d36f994 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2053,7 +2053,7 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old, def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial, 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', @@ -2143,7 +2143,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke 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 From 271846dfb6058afe80f376d1ab6bd36d331aaf9e Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 18 Oct 2019 07:01:05 +0200 Subject: [PATCH 5/7] Simplify cancel timedout --- freqtrade/freqtradebot.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 9b9fd3531..4b4904d24 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -772,21 +772,19 @@ class FreqtradeBot: 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,10 +800,17 @@ class FreqtradeBot: """Buy timeout - cancel order :return: True if order was fully cancelled """ - corder = self.exchange.cancel_order(trade.open_order_id, trade.pair) + 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 From 2588990f4b1ec521accab7cff7e59fdcb142373f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 18 Oct 2019 07:10:02 +0200 Subject: [PATCH 6/7] Require unfilledtimeout - don't require telegram in config --- freqtrade/constants.py | 2 +- freqtrade/rpc/rpc_manager.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 697bc280f..a310b4896 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -266,6 +266,6 @@ CONF_SCHEMA = { 'stake_amount', 'dry_run', 'bid_strategy', - 'telegram' + 'unfilledtimeout', ] } diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index 802550b94..cb9e697e9 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -18,7 +18,7 @@ class RPCManager: 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)) From 9d739f98ac6fd9026b419feeed1d462036c1ef35 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 18 Oct 2019 09:04:05 +0200 Subject: [PATCH 7/7] use requested - remaining amount - not the requested amount! --- freqtrade/freqtradebot.py | 7 +++--- tests/test_freqtradebot.py | 49 ++++++++++++++++++++++++++++++++++---- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4b4904d24..e3e2ed24b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -466,12 +466,13 @@ class FreqtradeBot: 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 @@ -819,7 +820,7 @@ class FreqtradeBot: 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) + 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 diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index b5d36f994..503c0e78c 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2076,7 +2076,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old 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, +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) @@ -2093,8 +2093,8 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap assert open_trade.amount == limit_buy_order_old_partial['amount'] - open_trade.fee_open = 0.0025 - open_trade.fee_close = 0.0025 + 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. @@ -2107,11 +2107,52 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap 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'] - 0.0001 + 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)