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: