freqtrade_origin/tests/test_integration.py

515 lines
18 KiB
Python
Raw Normal View History

from unittest.mock import MagicMock
import pytest
2022-03-25 05:55:37 +00:00
from freqtrade.enums import ExitCheckTuple, ExitType
from freqtrade.persistence import Trade
2022-02-20 13:27:26 +00:00
from freqtrade.persistence.models import Order
from freqtrade.rpc.rpc import RPC
from tests.conftest import get_patched_freqtradebot, log_has_re, patch_get_signal
def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
limit_buy_order, mocker) -> None:
"""
Tests workflow of selling stoploss_on_exchange.
Sells
* first trade as stoploss
* 2nd trade is kept
* 3rd trade is sold via sell-signal
"""
default_conf['max_open_trades'] = 3
default_conf['exchange']['name'] = 'binance'
stoploss = {
'id': 123,
'info': {}
}
stoploss_order_open = {
"id": "123",
"timestamp": 1542707426845,
"datetime": "2018-11-20T09:50:26.845Z",
"lastTradeTimestamp": None,
"symbol": "BTC/USDT",
"type": "stop_loss_limit",
"side": "sell",
"price": 1.08801,
"amount": 90.99181074,
"cost": 0.0,
"average": 0.0,
"filled": 0.0,
"remaining": 0.0,
"status": "open",
"fee": None,
"trades": None
}
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])
# Sell 3rd trade (not called for the first trade)
should_sell_mock = MagicMock(side_effect=[
2022-05-22 08:20:01 +00:00
[],
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]]
)
cancel_order_mock = MagicMock()
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
get_fee=fee,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
fetch_stoploss_order=stoploss_order_mock,
2021-05-16 12:15:24 +00:00
cancel_stoploss_order_with_result=cancel_order_mock,
)
mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot',
create_stoploss_order=MagicMock(return_value=True),
_notify_exit=MagicMock(),
)
2021-08-24 19:03:13 +00:00
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
2019-10-31 06:11:57 +00:00
wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
2019-12-18 19:16:53 +00:00
mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=1000))
2019-10-31 06:11:57 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# Switch ordertype to market to close trade immediately
2022-03-08 05:59:14 +00:00
freqtrade.strategy.order_types['exit'] = 'market'
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
patch_get_signal(freqtrade)
# Create some test data
freqtrade.enter_positions()
assert freqtrade.strategy.confirm_trade_entry.call_count == 3
freqtrade.strategy.confirm_trade_entry.reset_mock()
assert freqtrade.strategy.confirm_trade_exit.call_count == 0
wallets_mock.reset_mock()
trades = Trade.query.all()
# Make sure stoploss-order is open and trade is bought (since we mock update_trade_state)
for trade in trades:
2022-02-20 13:27:26 +00:00
stoploss_order_closed['id'] = '3'
oobj = Order.parse_from_ccxt_object(stoploss_order_closed, trade.pair, 'stoploss')
trade.orders.append(oobj)
trade.stoploss_order_id = '3'
trade.open_order_id = None
n = freqtrade.exit_positions(trades)
assert n == 2
assert should_sell_mock.call_count == 2
assert freqtrade.strategy.confirm_trade_entry.call_count == 0
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
freqtrade.strategy.confirm_trade_exit.reset_mock()
# Only order for 3rd trade needs to be cancelled
assert cancel_order_mock.call_count == 1
2020-05-01 13:35:57 +00:00
# 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]
2022-03-24 19:33:47 +00:00
assert trade.exit_reason == ExitType.STOPLOSS_ON_EXCHANGE.value
assert not trade.is_open
trade = trades[1]
2022-03-24 19:33:47 +00:00
assert not trade.exit_reason
assert trade.is_open
trade = trades[2]
2022-04-04 15:10:02 +00:00
assert trade.exit_reason == ExitType.EXIT_SIGNAL.value
assert not trade.is_open
2019-10-31 09:04:28 +00:00
@pytest.mark.parametrize("balance_ratio,result1", [
(1, 200),
(0.99, 198),
])
2022-01-02 19:20:56 +00:00
def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_ratio, result1) -> None:
2019-10-31 09:04:28 +00:00
"""
Tests workflow unlimited stake-amount
Buy 4 trades, forcebuy a 5th trade
Sell one trade, calculated stake amount should now be lower than before since
one trade was sold at a loss.
2019-10-31 09:04:28 +00:00
"""
default_conf['max_open_trades'] = 5
2022-04-08 11:39:41 +00:00
default_conf['force_entry_enable'] = True
2019-10-31 09:04:28 +00:00
default_conf['stake_amount'] = 'unlimited'
default_conf['tradable_balance_ratio'] = balance_ratio
default_conf['dry_run_wallet'] = 1000
2019-10-31 09:04:28 +00:00
default_conf['exchange']['name'] = 'binance'
default_conf['telegram']['enabled'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2019-10-31 09:04:28 +00:00
get_fee=fee,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
2019-10-31 09:04:28 +00:00
)
mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot',
create_stoploss_order=MagicMock(return_value=True),
_notify_exit=MagicMock(),
2019-10-31 09:04:28 +00:00
)
should_sell_mock = MagicMock(side_effect=[
2022-05-22 08:20:01 +00:00
[],
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)],
[],
[],
[]]
)
2021-08-24 19:03:13 +00:00
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
2019-10-31 09:04:28 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(freqtrade)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# Switch ordertype to market to close trade immediately
2022-03-08 05:59:14 +00:00
freqtrade.strategy.order_types['exit'] = 'market'
2019-10-31 09:04:28 +00:00
patch_get_signal(freqtrade)
# Create 4 trades
n = freqtrade.enter_positions()
assert n == 4
2019-10-31 09:04:28 +00:00
trades = Trade.query.all()
assert len(trades) == 4
assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') == result1
2022-01-26 18:08:37 +00:00
rpc._rpc_force_entry('TKN/BTC', None)
2019-10-31 09:04:28 +00:00
trades = Trade.query.all()
assert len(trades) == 5
for trade in trades:
assert trade.stake_amount == result1
# Reset trade open order id's
trade.open_order_id = None
trades = Trade.get_open_trades()
assert len(trades) == 5
bals = freqtrade.wallets.get_all_balances()
n = freqtrade.exit_positions(trades)
assert n == 1
trades = Trade.get_open_trades()
# One trade sold
assert len(trades) == 4
# stake-amount should now be reduced, since one trade was sold at a loss.
assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') < result1
# Validate that balance of sold trade is not in dry-run balances anymore.
bals2 = freqtrade.wallets.get_all_balances()
assert bals != bals2
assert len(bals) == 6
assert len(bals2) == 5
assert 'LTC' in bals
assert 'LTC' not in bals2
2022-01-02 19:20:56 +00:00
def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
default_conf_usdt['position_adjustment_enable'] = True
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
get_fee=fee,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
)
patch_get_signal(freqtrade)
freqtrade.enter_positions()
assert len(Trade.get_trades().all()) == 1
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
2022-02-13 15:01:44 +00:00
assert pytest.approx(trade.stake_amount) == 60
2022-01-02 19:20:56 +00:00
assert trade.open_rate == 2.0
# No adjustment
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
2022-02-13 15:01:44 +00:00
assert pytest.approx(trade.stake_amount) == 60
2022-01-02 19:20:56 +00:00
# Reduce bid amount
ticker_usdt_modif = ticker_usdt.return_value
ticker_usdt_modif['bid'] = ticker_usdt_modif['bid'] * 0.995
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value=ticker_usdt_modif)
# additional buy order
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
for o in trade.orders:
assert o.status == "closed"
2022-01-02 19:20:56 +00:00
assert trade.stake_amount == 120
# Open-rate averaged between 2.0 and 2.0 * 0.995
assert trade.open_rate < 2.0
assert trade.open_rate > 2.0 * 0.995
# No action - profit raised above 1% (the bar set in the strategy).
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert trade.stake_amount == 120
assert trade.orders[0].amount == 30
assert trade.orders[1].amount == 60 / ticker_usdt_modif['bid']
assert trade.amount == trade.orders[0].amount + trade.orders[1].amount
2022-01-13 18:31:03 +00:00
assert trade.nr_of_successful_buys == 2
2022-02-13 15:01:44 +00:00
assert trade.nr_of_successful_entries == 2
2022-01-02 19:20:56 +00:00
# Sell
2022-01-22 16:25:21 +00:00
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
2022-01-02 19:20:56 +00:00
freqtrade.process()
trade = Trade.get_trades().first()
assert trade.is_open is False
assert trade.orders[0].amount == 30
assert trade.orders[0].side == 'buy'
assert trade.orders[1].amount == 60 / ticker_usdt_modif['bid']
# Sold everything
assert trade.orders[-1].side == 'sell'
assert trade.orders[2].amount == trade.amount
2022-01-13 18:31:03 +00:00
assert trade.nr_of_successful_buys == 2
2022-02-13 15:01:44 +00:00
assert trade.nr_of_successful_entries == 2
def test_dca_short(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
default_conf_usdt['position_adjustment_enable'] = True
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
get_fee=fee,
2022-08-11 11:35:24 +00:00
amount_to_precision=lambda s, x, y: round(y, 4),
2022-02-13 15:01:44 +00:00
price_to_precision=lambda s, x, y: y,
)
patch_get_signal(freqtrade, enter_long=False, enter_short=True)
freqtrade.enter_positions()
assert len(Trade.get_trades().all()) == 1
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert pytest.approx(trade.stake_amount) == 60
assert trade.open_rate == 2.02
2022-08-11 11:35:24 +00:00
assert trade.orders[0].amount == trade.amount
2022-02-13 15:01:44 +00:00
# No adjustment
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert pytest.approx(trade.stake_amount) == 60
# Reduce bid amount
ticker_usdt_modif = ticker_usdt.return_value
2022-03-27 15:00:45 +00:00
ticker_usdt_modif['ask'] = ticker_usdt_modif['ask'] * 1.004
2022-02-13 15:01:44 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value=ticker_usdt_modif)
# additional buy order
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
for o in trade.orders:
assert o.status == "closed"
assert pytest.approx(trade.stake_amount) == 120
# Open-rate averaged between 2.0 and 2.0 * 1.015
assert trade.open_rate >= 2.02
assert trade.open_rate < 2.02 * 1.015
# No action - profit raised above 1% (the bar set in the strategy).
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert pytest.approx(trade.stake_amount) == 120
2022-08-11 11:35:24 +00:00
assert trade.orders[1].amount == round(60 / ticker_usdt_modif['ask'], 4)
2022-02-13 15:01:44 +00:00
assert trade.amount == trade.orders[0].amount + trade.orders[1].amount
assert trade.nr_of_successful_entries == 2
# Buy
patch_get_signal(freqtrade, enter_long=False, exit_short=True)
freqtrade.process()
trade = Trade.get_trades().first()
assert trade.is_open is False
# assert trade.orders[0].amount == 30
assert trade.orders[0].side == 'sell'
2022-08-11 11:35:24 +00:00
assert trade.orders[1].amount == round(60 / ticker_usdt_modif['ask'], 4)
2022-02-13 15:01:44 +00:00
# Sold everything
assert trade.orders[-1].side == 'buy'
assert trade.orders[2].amount == trade.amount
assert trade.nr_of_successful_entries == 2
assert trade.nr_of_successful_exits == 1
2022-05-06 17:49:39 +00:00
def test_dca_order_adjust(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
default_conf_usdt['position_adjustment_enable'] = True
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
get_fee=fee,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
)
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=False)
patch_get_signal(freqtrade)
freqtrade.strategy.custom_entry_price = lambda **kwargs: ticker_usdt['ask'] * 0.96
freqtrade.enter_positions()
assert len(Trade.get_trades().all()) == 1
trade: Trade = Trade.get_trades().first()
2022-05-06 17:49:39 +00:00
assert len(trade.orders) == 1
assert trade.open_order_id is not None
assert pytest.approx(trade.stake_amount) == 60
assert trade.open_rate == 1.96
assert trade.stop_loss_pct is None
assert trade.stop_loss == 0.0
assert trade.initial_stop_loss == 0.0
assert trade.initial_stop_loss_pct is None
2022-05-06 17:49:39 +00:00
# No adjustment
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert trade.open_order_id is not None
assert pytest.approx(trade.stake_amount) == 60
# Cancel order and place new one
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1.99)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert trade.open_order_id is not None
# Open rate is not adjusted yet
assert trade.open_rate == 1.96
assert trade.stop_loss_pct is None
assert trade.stop_loss == 0.0
assert trade.initial_stop_loss == 0.0
assert trade.initial_stop_loss_pct is None
2022-05-06 17:49:39 +00:00
# Fill order
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert trade.open_order_id is None
# Open rate is not adjusted yet
assert trade.open_rate == 1.99
assert trade.stop_loss_pct == -0.1
assert trade.stop_loss == 1.99 * 0.9
assert trade.initial_stop_loss == 1.99 * 0.9
assert trade.initial_stop_loss_pct == -0.1
2022-05-06 17:49:39 +00:00
# 2nd order - not filling
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=120)
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=False)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 3
assert trade.open_order_id is not None
assert trade.open_rate == 1.99
assert trade.orders[-1].price == 1.96
assert trade.orders[-1].cost == 120
# Replace new order with diff. order at a lower price
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1.95)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 4
assert trade.open_order_id is not None
assert trade.open_rate == 1.99
assert trade.orders[-1].price == 1.95
assert pytest.approx(trade.orders[-1].cost) == 120
# Fill DCA order
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=None)
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True)
freqtrade.strategy.adjust_entry_price = MagicMock(side_effect=ValueError)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 4
assert trade.open_order_id is None
assert pytest.approx(trade.open_rate) == 1.963153456
assert trade.orders[-1].price == 1.95
assert pytest.approx(trade.orders[-1].cost) == 120
assert trade.orders[-1].status == 'closed'
assert pytest.approx(trade.amount) == 91.689215
# Check the 2 filled orders equal the above amount
assert pytest.approx(trade.orders[1].amount) == 30.150753768
assert pytest.approx(trade.orders[-1].amount) == 61.538461232
def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog) -> None:
default_conf_usdt['position_adjustment_enable'] = True
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
get_fee=fee,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
get_min_pair_stake_amount=MagicMock(return_value=10),
)
patch_get_signal(freqtrade)
freqtrade.enter_positions()
assert len(Trade.get_trades().all()) == 1
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert pytest.approx(trade.stake_amount) == 60
assert pytest.approx(trade.amount) == 30.0
assert trade.open_rate == 2.0
# Too small size
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-59)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert pytest.approx(trade.stake_amount) == 60
assert pytest.approx(trade.amount) == 30.0
assert log_has_re("Remaining amount of 1.6.* would be too small.", caplog)
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-20)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert trade.orders[-1].ft_order_side == 'sell'
assert pytest.approx(trade.stake_amount) == 40.198
assert pytest.approx(trade.amount) == 20.099
assert trade.open_rate == 2.0
assert trade.is_open
caplog.clear()
# Sell more than what we got (we got ~20 coins left)
# First adjusts the amount to 20 - then rejects.
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-50)
freqtrade.process()
assert log_has_re("Adjusting amount to trade.amount as it is higher.*", caplog)
assert log_has_re("Remaining amount of 0.0 would be too small.", caplog)
trade = Trade.get_trades().first()
assert len(trade.orders) == 2
assert trade.orders[-1].ft_order_side == 'sell'
assert pytest.approx(trade.stake_amount) == 40.198
assert trade.is_open