stable/tests/test_freqtradebot.py

5403 lines
194 KiB
Python
Raw Normal View History

# pragma pylint: disable=missing-docstring, C0103
# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
import logging
import time
from copy import deepcopy
from math import isclose
2021-12-21 20:23:01 +00:00
from typing import List
from unittest.mock import ANY, MagicMock, PropertyMock
2018-03-17 21:44:47 +00:00
import arrow
import pytest
2021-12-11 08:49:48 +00:00
from pandas import DataFrame
2020-09-28 17:43:15 +00:00
from freqtrade.constants import CANCEL_REASON, MATH_CLOSE_PREC, UNLIMITED_STAKE_AMOUNT
2021-12-03 13:27:04 +00:00
from freqtrade.enums import CandleType, RPCMessageType, RunMode, SellType, SignalDirection, State
2020-09-28 17:43:15 +00:00
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
InvalidOrderException, OperationalException, PricingError,
TemporaryError)
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Order, PairLocks, Trade
2020-10-25 09:54:30 +00:00
from freqtrade.persistence.models import PairLock
2021-06-08 19:06:47 +00:00
from freqtrade.strategy.interface import SellCheckTuple
from freqtrade.worker import Worker
2020-09-28 17:43:15 +00:00
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, get_patched_worker,
log_has, log_has_re, patch_edge, patch_exchange, patch_get_signal,
patch_wallet, patch_whitelist)
from tests.conftest_trades import (MOCK_TRADE_COUNT, enter_side, exit_side, mock_order_1,
mock_order_2, mock_order_2_sell, mock_order_3, mock_order_3_sell,
mock_order_4, mock_order_5_stoploss, mock_order_6_sell)
2021-09-15 03:08:15 +00:00
def patch_RPCManager(mocker) -> MagicMock:
"""
This function mock RPC manager to avoid repeating this code in almost every tests
:param mocker: mocker to patch RPCManager class
:return: RPCManager.send_msg MagicMock to track if this method is called
"""
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
rpc_mock = mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock())
return rpc_mock
# Unit tests
2021-09-14 21:38:26 +00:00
def test_freqtradebot_state(mocker, default_conf_usdt, markets) -> None:
2019-03-05 18:46:03 +00:00
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
assert freqtrade.state is State.RUNNING
default_conf_usdt.pop('initial_state')
freqtrade = FreqtradeBot(default_conf_usdt)
assert freqtrade.state is State.STOPPED
def test_process_stopped(mocker, default_conf_usdt) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
coo_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cancel_all_open_orders')
freqtrade.process_stopped()
assert coo_mock.call_count == 0
default_conf_usdt['cancel_open_orders_on_exit'] = True
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
freqtrade.process_stopped()
assert coo_mock.call_count == 1
def test_bot_cleanup(mocker, default_conf_usdt, caplog) -> None:
2020-10-16 05:39:12 +00:00
mock_cleanup = mocker.patch('freqtrade.freqtradebot.cleanup_db')
coo_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cancel_all_open_orders')
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
freqtrade.cleanup()
2019-08-11 18:17:39 +00:00
assert log_has('Cleaning up modules ...', caplog)
2018-03-05 08:11:13 +00:00
assert mock_cleanup.call_count == 1
assert coo_mock.call_count == 0
freqtrade.config['cancel_open_orders_on_exit'] = True
freqtrade.cleanup()
assert coo_mock.call_count == 1
2018-03-05 08:11:13 +00:00
2021-09-19 23:44:12 +00:00
@pytest.mark.parametrize('runmode', [
RunMode.DRY_RUN,
RunMode.LIVE
])
def test_order_dict(default_conf_usdt, mocker, runmode, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
conf = default_conf_usdt.copy()
2021-09-19 23:44:12 +00:00
conf['runmode'] = runmode
conf['order_types'] = {
'buy': 'market',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True,
}
2021-03-20 12:08:02 +00:00
conf['bid_strategy']['price_side'] = 'ask'
freqtrade = FreqtradeBot(conf)
2021-09-19 23:44:12 +00:00
if runmode == RunMode.LIVE:
assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog)
assert freqtrade.strategy.order_types['stoploss_on_exchange']
caplog.clear()
# is left untouched
conf = default_conf_usdt.copy()
2021-09-19 23:44:12 +00:00
conf['runmode'] = runmode
conf['order_types'] = {
'buy': 'market',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False,
}
freqtrade = FreqtradeBot(conf)
assert not freqtrade.strategy.order_types['stoploss_on_exchange']
assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog)
2021-10-02 06:58:33 +00:00
def test_get_trade_stake_amount(default_conf_usdt, mocker) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
freqtrade = FreqtradeBot(default_conf_usdt)
result = freqtrade.wallets.get_trade_stake_amount('ETH/USDT')
assert result == default_conf_usdt['stake_amount']
2020-01-10 05:36:28 +00:00
@pytest.mark.parametrize("amend_last,wallet,max_open,lsamr,expected", [
2021-10-03 07:22:50 +00:00
(False, 120, 2, 0.5, [60, None]),
(True, 120, 2, 0.5, [60, 58.8]),
(False, 180, 3, 0.5, [60, 60, None]),
(True, 180, 3, 0.5, [60, 60, 58.2]),
(False, 122, 3, 0.5, [60, 60, None]),
(True, 122, 3, 0.5, [60, 60, 0.0]),
(True, 167, 3, 0.5, [60, 60, 45.33]),
(True, 122, 3, 1, [60, 60, 0.0]),
2021-07-18 03:58:54 +00:00
])
2021-09-30 09:19:28 +00:00
def test_check_available_stake_amount(
default_conf_usdt, ticker_usdt, mocker, fee, limit_buy_order_usdt_open,
amend_last, wallet, max_open, lsamr, expected
) -> None:
2020-01-05 12:25:21 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
2020-01-05 12:25:21 +00:00
get_fee=fee
)
default_conf_usdt['dry_run_wallet'] = wallet
2020-01-05 12:25:21 +00:00
default_conf_usdt['amend_last_stake_amount'] = amend_last
default_conf_usdt['last_stake_amount_min_ratio'] = lsamr
2020-01-10 05:36:28 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
2020-01-05 12:25:21 +00:00
for i in range(0, max_open):
if expected[i] is not None:
2021-09-17 08:25:58 +00:00
limit_buy_order_usdt_open['id'] = str(i)
result = freqtrade.wallets.get_trade_stake_amount('ETH/USDT')
2020-01-05 12:25:21 +00:00
assert pytest.approx(result) == expected[i]
freqtrade.execute_entry('ETH/USDT', result)
2020-01-05 12:25:21 +00:00
else:
with pytest.raises(DependencyException):
freqtrade.wallets.get_trade_stake_amount('ETH/USDT')
2020-01-05 12:25:21 +00:00
2018-11-10 17:03:46 +00:00
def test_edge_called_in_process(mocker, edge_conf) -> None:
patch_RPCManager(mocker)
patch_edge(mocker)
2018-11-10 17:09:32 +00:00
2018-11-10 17:03:46 +00:00
patch_exchange(mocker)
freqtrade = FreqtradeBot(edge_conf)
patch_get_signal(freqtrade)
2019-03-26 08:07:24 +00:00
freqtrade.process()
2018-11-10 17:03:46 +00:00
assert freqtrade.active_pair_whitelist == ['NEO/BTC', 'LTC/BTC']
2018-11-10 16:20:11 +00:00
def test_edge_overrides_stake_amount(mocker, edge_conf) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_edge(mocker)
edge_conf['dry_run_wallet'] = 999.9
2018-11-10 16:20:11 +00:00
freqtrade = FreqtradeBot(edge_conf)
assert freqtrade.wallets.get_trade_stake_amount(
'NEO/BTC', freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.20
assert freqtrade.wallets.get_trade_stake_amount(
'LTC/BTC', freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.21
2021-10-11 14:26:15 +00:00
@pytest.mark.parametrize('buy_price_mult,ignore_strat_sl', [
(0.79, False), # Override stoploss
(0.85, True), # Override strategy stoploss
2021-09-19 23:44:12 +00:00
])
2021-10-11 14:26:15 +00:00
def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker,
2021-09-19 23:44:12 +00:00
buy_price_mult, ignore_strat_sl, edge_conf) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_edge(mocker)
2019-08-13 08:12:12 +00:00
edge_conf['max_open_trades'] = float('inf')
# Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2
# Thus, if price falls 21%, stoploss should be triggered
#
2021-10-03 23:41:01 +00:00
# mocking the ticker: price is falling ...
2021-10-11 14:26:15 +00:00
enter_price = limit_order['buy']['price']
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
'bid': enter_price * buy_price_mult,
'ask': enter_price * buy_price_mult,
'last': enter_price * buy_price_mult,
}),
get_fee=fee,
)
#############################################
2021-09-17 08:25:58 +00:00
# Create a trade with "limit_buy_order_usdt" price
2018-11-10 16:20:11 +00:00
freqtrade = FreqtradeBot(edge_conf)
freqtrade.active_pair_whitelist = ['NEO/BTC']
2021-10-11 14:26:15 +00:00
patch_get_signal(freqtrade)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
trade = Trade.query.first()
2021-10-03 23:41:01 +00:00
caplog.clear()
2021-10-11 14:26:15 +00:00
trade.update(limit_order['buy'])
#############################################
# stoploss shoud be hit
2021-09-19 23:44:12 +00:00
assert freqtrade.handle_trade(trade) is not ignore_strat_sl
if not ignore_strat_sl:
2021-11-06 14:24:52 +00:00
assert log_has_re('Exit for NEO/BTC detected. Reason: stop_loss.*', caplog)
2021-09-19 23:44:12 +00:00
assert trade.sell_reason == SellType.STOP_LOSS.value
2018-10-04 16:07:47 +00:00
def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
default_conf_usdt['max_open_trades'] = 2
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
get_fee=fee,
2021-06-05 13:22:52 +00:00
_is_dry_limit_order_filled=MagicMock(return_value=False),
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
freqtrade.enter_positions()
trade = Trade.query.first()
assert trade is not None
2021-10-03 07:22:50 +00:00
assert trade.stake_amount == 60.0
assert trade.is_open
assert trade.open_date is not None
freqtrade.enter_positions()
trade = Trade.query.order_by(Trade.id.desc()).first()
assert trade is not None
2021-10-03 07:22:50 +00:00
assert trade.stake_amount == 60.0
assert trade.is_open
assert trade.open_date is not None
2021-10-03 07:22:50 +00:00
assert Trade.total_open_trades_stakes() == 120.0
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short,open_rate", [
(False, 2.0),
(True, 2.2)
2021-09-14 21:38:26 +00:00
])
def test_create_trade(default_conf_usdt, ticker_usdt, limit_order,
2021-09-14 21:38:26 +00:00
fee, mocker, is_short, open_rate) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2018-04-21 17:39:18 +00:00
get_fee=fee,
2021-06-05 13:22:52 +00:00
_is_dry_limit_order_filled=MagicMock(return_value=False),
)
# Save state of current whitelist
whitelist = deepcopy(default_conf_usdt['exchange']['pair_whitelist'])
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.create_trade('ETH/USDT')
trade = Trade.query.first()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
assert trade is not None
2021-10-03 07:22:50 +00:00
assert trade.stake_amount == 60.0
assert trade.is_open
assert trade.open_date is not None
2021-04-20 10:54:22 +00:00
assert trade.exchange == 'binance'
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_order[enter_side(is_short)])
2021-09-14 21:38:26 +00:00
assert trade.open_rate == open_rate
2021-09-17 08:25:58 +00:00
assert trade.amount == 30.0
assert whitelist == default_conf_usdt['exchange']['pair_whitelist']
2021-10-02 06:58:33 +00:00
def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
2018-06-16 23:23:12 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
patch_wallet(mocker, free=default_conf_usdt['stake_amount'] * 0.5)
2018-06-16 23:23:12 +00:00
mocker.patch.multiple(
2018-06-23 13:50:27 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2018-06-16 23:23:12 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
2018-06-16 23:23:12 +00:00
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
freqtrade.create_trade('ETH/USDT')
2018-06-16 23:23:12 +00:00
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2021-09-19 23:44:12 +00:00
@pytest.mark.parametrize('stake_amount,create,amount_enough,max_open_trades', [
(5.0, True, True, 99),
2021-11-18 19:20:01 +00:00
(0.049, True, False, 99), # Amount will be adjusted to min - which is 0.051
2021-09-19 23:44:12 +00:00
(0, False, True, 99),
(UNLIMITED_STAKE_AMOUNT, False, True, 0),
])
def test_create_trade_minimal_amount(
default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker,
2021-09-20 01:06:43 +00:00
stake_amount, create, amount_enough, max_open_trades, caplog, is_short
2021-09-19 23:44:12 +00:00
) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
enter_mock = MagicMock(return_value=limit_order_open[enter_side(is_short)])
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
create_order=enter_mock,
get_fee=fee,
)
default_conf_usdt['max_open_trades'] = max_open_trades
freqtrade = FreqtradeBot(default_conf_usdt)
2021-09-19 23:44:12 +00:00
freqtrade.config['stake_amount'] = stake_amount
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2021-09-19 23:44:12 +00:00
if create:
assert freqtrade.create_trade('ETH/USDT')
2021-09-19 23:44:12 +00:00
if amount_enough:
rate, amount = enter_mock.call_args[1]['rate'], enter_mock.call_args[1]['amount']
assert rate * amount <= default_conf_usdt['stake_amount']
2021-09-19 23:44:12 +00:00
else:
assert log_has_re(
r"Stake amount for pair .* is too small.*",
caplog
)
else:
assert not freqtrade.create_trade('ETH/USDT')
2021-09-19 23:44:12 +00:00
if not max_open_trades:
assert freqtrade.wallets.get_trade_stake_amount('ETH/USDT', freqtrade.edge) == 0
2021-09-19 23:44:12 +00:00
@pytest.mark.parametrize('whitelist,positions', [
(["ETH/USDT"], 1), # No pairs left
2021-09-19 23:44:12 +00:00
([], 0), # No pairs in whitelist
])
2021-09-30 09:19:28 +00:00
def test_enter_positions_no_pairs_left(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open,
fee, whitelist, positions, mocker, caplog) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
default_conf_usdt['exchange']['pair_whitelist'] = whitelist
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
n = freqtrade.enter_positions()
2021-09-19 23:44:12 +00:00
assert n == positions
if positions:
assert not log_has_re(r"No currency pair in active pair whitelist.*", caplog)
n = freqtrade.enter_positions()
assert n == 0
assert log_has_re(r"No currency pair in active pair whitelist.*", caplog)
else:
assert n == 0
assert log_has("Active pair whitelist is empty.", caplog)
@pytest.mark.usefixtures("init_persistence")
def test_enter_positions_global_pairlock(default_conf_usdt, ticker_usdt, limit_buy_order_usdt, fee,
mocker, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value={'id': limit_buy_order_usdt['id']}),
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
n = freqtrade.enter_positions()
2020-11-25 10:54:11 +00:00
message = r"Global pairlock active until.* Not creating new trades."
n = freqtrade.enter_positions()
# 0 trades, but it's not because of pairlock.
assert n == 0
2020-11-25 10:54:11 +00:00
assert not log_has_re(message, caplog)
caplog.clear()
PairLocks.lock_pair('*', arrow.utcnow().shift(minutes=20).datetime, 'Just because')
n = freqtrade.enter_positions()
assert n == 0
2020-11-25 10:54:11 +00:00
assert log_has_re(message, caplog)
2021-10-02 09:58:02 +00:00
@pytest.mark.parametrize('is_short', [False, True])
def test_handle_protections(mocker, default_conf_usdt, fee, is_short):
default_conf_usdt['protections'] = [
2021-09-20 17:49:18 +00:00
{"method": "CooldownPeriod", "stop_duration": 60},
{
"method": "StoplossGuard",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 4,
"only_per_pair": False
}
]
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-09-20 17:49:18 +00:00
freqtrade.protections._protection_handlers[1].global_stop = MagicMock(
return_value=(True, arrow.utcnow().shift(hours=1).datetime, "asdf"))
2021-10-02 09:58:02 +00:00
create_mock_trades(fee, is_short)
2021-09-20 17:49:18 +00:00
freqtrade.handle_protections('ETC/BTC')
send_msg_mock = freqtrade.rpc.send_msg
assert send_msg_mock.call_count == 2
assert send_msg_mock.call_args_list[0][0][0]['type'] == RPCMessageType.PROTECTION_TRIGGER
assert send_msg_mock.call_args_list[1][0][0]['type'] == RPCMessageType.PROTECTION_TRIGGER_GLOBAL
def test_create_trade_no_signal(default_conf_usdt, fee, mocker) -> None:
default_conf_usdt['dry_run'] = True
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
default_conf_usdt['stake_amount'] = 10
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
Trade.query = MagicMock()
Trade.query.filter = MagicMock()
assert not freqtrade.create_trade('ETH/USDT')
2019-08-13 08:34:27 +00:00
@pytest.mark.parametrize("max_open", range(0, 5))
@pytest.mark.parametrize("tradable_balance_ratio,modifier", [(1.0, 1), (0.99, 0.8), (0.5, 0.5)])
2021-09-30 09:19:28 +00:00
def test_create_trades_multiple_trades(
default_conf_usdt, ticker_usdt, fee, mocker, limit_buy_order_usdt_open,
max_open, tradable_balance_ratio, modifier
) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
default_conf_usdt['max_open_trades'] = max_open
default_conf_usdt['tradable_balance_ratio'] = tradable_balance_ratio
2021-10-03 07:22:50 +00:00
default_conf_usdt['dry_run_wallet'] = 60.0 * max_open
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
n = freqtrade.enter_positions()
trades = Trade.get_open_trades()
# Expected trades should be max_open * a modified value
# depending on the configured tradable_balance
assert n == max(int(max_open * modifier), 0)
assert len(trades) == max(int(max_open * modifier), 0)
2021-09-30 09:19:28 +00:00
def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker,
limit_buy_order_usdt_open) -> None:
2019-08-14 04:21:15 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
default_conf_usdt['max_open_trades'] = 4
2019-08-14 04:21:15 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
2019-08-14 04:21:15 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2019-08-14 04:21:15 +00:00
patch_get_signal(freqtrade)
# Create 2 existing trades
freqtrade.execute_entry('ETH/USDT', default_conf_usdt['stake_amount'])
freqtrade.execute_entry('NEO/BTC', default_conf_usdt['stake_amount'])
2019-08-14 04:21:15 +00:00
assert len(Trade.get_open_trades()) == 2
# Change order_id for new orders
2021-09-17 08:25:58 +00:00
limit_buy_order_usdt_open['id'] = '123444'
2019-08-14 04:21:15 +00:00
# Create 2 new trades using create_trades
assert freqtrade.create_trade('ETH/USDT')
2019-12-29 01:38:28 +00:00
assert freqtrade.create_trade('NEO/BTC')
2019-08-14 04:21:15 +00:00
trades = Trade.get_open_trades()
assert len(trades) == 4
@pytest.mark.parametrize('is_short, open_rate', [
(False, 2.0),
(True, 2.02)
])
def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_order, limit_order_open,
is_short, open_rate, fee, mocker, caplog
) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
create_order=MagicMock(return_value=limit_order_open[enter_side(is_short)]),
fetch_order=MagicMock(return_value=limit_order[enter_side(is_short)]),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert not trades
2019-08-13 07:38:21 +00:00
freqtrade.process()
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert len(trades) == 1
trade = trades[0]
assert trade is not None
assert trade.stake_amount == default_conf_usdt['stake_amount']
assert trade.is_open
assert trade.open_date is not None
2021-04-20 10:54:22 +00:00
assert trade.exchange == 'binance'
assert trade.open_rate == open_rate # TODO-lev: I think? That's what the ticker ask price is
assert isclose(trade.amount, 60 / open_rate)
assert log_has(
f'{"Short" if is_short else "Long"} signal found: about create a new trade for ETH/USDT '
'with stake_amount: 60.0 ...',
2021-04-10 12:52:34 +00:00
caplog
)
def test_process_exchange_failures(default_conf_usdt, ticker_usdt, mocker) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
create_order=MagicMock(side_effect=TemporaryError)
)
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
worker = Worker(args=None, config=default_conf_usdt)
2019-03-26 08:07:24 +00:00
patch_get_signal(worker.freqtrade)
2020-02-22 22:45:15 +00:00
worker._process_running()
assert sleep_mock.has_calls()
def test_process_operational_exception(default_conf_usdt, ticker_usdt, mocker) -> None:
msg_mock = patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
create_order=MagicMock(side_effect=OperationalException)
)
worker = Worker(args=None, config=default_conf_usdt)
2019-03-26 08:07:24 +00:00
patch_get_signal(worker.freqtrade)
2020-01-29 14:08:36 +00:00
assert worker.freqtrade.state == State.RUNNING
2020-02-22 22:45:15 +00:00
worker._process_running()
2020-01-29 14:08:36 +00:00
assert worker.freqtrade.state == State.STOPPED
2018-06-24 22:04:27 +00:00
assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]['status']
2021-09-30 09:19:28 +00:00
def test_process_trade_handling(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open, fee,
mocker) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
fetch_order=MagicMock(return_value=limit_buy_order_usdt_open),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert not trades
2019-08-13 07:36:52 +00:00
freqtrade.process()
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert len(trades) == 1
2019-08-13 07:36:52 +00:00
# Nothing happened ...
freqtrade.process()
assert len(trades) == 1
def test_process_trade_no_whitelist_pair(default_conf_usdt, ticker_usdt, limit_buy_order_usdt,
fee, mocker) -> None:
2019-03-26 08:07:24 +00:00
""" Test process with trade not in pair list """
2018-10-29 18:23:56 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value={'id': limit_buy_order_usdt['id']}),
fetch_order=MagicMock(return_value=limit_buy_order_usdt),
2018-10-29 18:23:56 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2018-10-29 18:23:56 +00:00
patch_get_signal(freqtrade)
pair = 'BLK/BTC'
# Ensure the pair is not in the whitelist!
assert pair not in default_conf_usdt['exchange']['pair_whitelist']
2018-10-29 18:23:56 +00:00
# create open trade not in whitelist
Trade.query.session.add(Trade(
2018-10-29 18:23:56 +00:00
pair=pair,
stake_amount=0.001,
fee_open=fee.return_value,
fee_close=fee.return_value,
is_open=True,
amount=20,
open_rate=0.01,
2021-04-20 10:54:22 +00:00
exchange='binance',
2018-10-29 18:23:56 +00:00
))
Trade.query.session.add(Trade(
pair='ETH/USDT',
2018-10-29 18:23:56 +00:00
stake_amount=0.001,
fee_open=fee.return_value,
fee_close=fee.return_value,
is_open=True,
amount=12,
open_rate=0.001,
2021-04-20 10:54:22 +00:00
exchange='binance',
2018-10-29 18:23:56 +00:00
))
assert pair not in freqtrade.active_pair_whitelist
2019-08-13 07:38:21 +00:00
freqtrade.process()
2018-10-29 18:23:56 +00:00
assert pair in freqtrade.active_pair_whitelist
# Make sure each pair is only in the list once
assert len(freqtrade.active_pair_whitelist) == len(set(freqtrade.active_pair_whitelist))
def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker) -> None:
2019-01-26 19:05:49 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
refresh_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
create_order=MagicMock(side_effect=TemporaryError),
2019-01-26 19:05:49 +00:00
refresh_latest_ohlcv=refresh_mock,
)
2021-12-08 13:35:15 +00:00
inf_pairs = MagicMock(return_value=[
("BTC/ETH", '1m', CandleType.SPOT),
("ETH/USDT", "1h", CandleType.SPOT)
2021-12-31 12:49:21 +00:00
])
mocker.patch.multiple(
'freqtrade.strategy.interface.IStrategy',
get_exit_signal=MagicMock(return_value=(False, False)),
get_entry_signal=MagicMock(return_value=(None, None))
2021-07-20 16:56:03 +00:00
)
2019-01-26 19:05:49 +00:00
mocker.patch('time.sleep', return_value=None)
freqtrade = FreqtradeBot(default_conf_usdt)
2019-01-26 19:05:49 +00:00
freqtrade.strategy.informative_pairs = inf_pairs
# patch_get_signal(freqtrade)
2019-03-26 08:07:24 +00:00
freqtrade.process()
2019-01-26 19:05:49 +00:00
assert inf_pairs.call_count == 1
assert refresh_mock.call_count == 1
2021-12-08 13:35:15 +00:00
assert ("BTC/ETH", "1m", CandleType.SPOT) in refresh_mock.call_args[0][0]
assert ("ETH/USDT", "1h", CandleType.SPOT) in refresh_mock.call_args[0][0]
assert ("ETH/USDT", default_conf_usdt["timeframe"],
CandleType.SPOT) in refresh_mock.call_args[0][0]
2019-01-26 19:05:49 +00:00
@pytest.mark.parametrize("is_short,trading_mode,exchange_name,margin_mode,liq_buffer,liq_price", [
(False, 'spot', 'binance', None, 0.0, None),
(True, 'spot', 'binance', None, 0.0, None),
(False, 'spot', 'gateio', None, 0.0, None),
(True, 'spot', 'gateio', None, 0.0, None),
(False, 'spot', 'okx', None, 0.0, None),
(True, 'spot', 'okx', None, 0.0, None),
(True, 'futures', 'binance', 'isolated', 0.0, 11.89108910891089),
(False, 'futures', 'binance', 'isolated', 0.0, 8.070707070707071),
(True, 'futures', 'gateio', 'isolated', 0.0, 11.87413417771621),
(False, 'futures', 'gateio', 'isolated', 0.0, 8.085708510208207),
(True, 'futures', 'binance', 'isolated', 0.05, 11.796534653465345),
(False, 'futures', 'binance', 'isolated', 0.05, 8.167171717171717),
(True, 'futures', 'gateio', 'isolated', 0.05, 11.7804274688304),
(False, 'futures', 'gateio', 'isolated', 0.05, 8.181423084697796),
(True, 'futures', 'okex', 'isolated', 11.87413417771621),
(False, 'futures', 'okex', 'isolated', 8.085708510208207),
])
def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
limit_order_open, is_short, trading_mode,
exchange_name, margin_mode, liq_buffer, liq_price) -> None:
"""
exchange_name = binance, is_short = true
2022-01-15 16:23:20 +00:00
leverage = 5
position = 0.2 * 5
((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
((2 + 0.01) - ((-1) * 1 * 10)) / ((1 * 0.01) - ((-1) * 1)) = 11.89108910891089
exchange_name = binance, is_short = false
2022-01-15 16:23:20 +00:00
((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
((2 + 0.01) - (1 * 1 * 10)) / ((1 * 0.01) - (1 * 1)) = 8.070707070707071
2022-02-11 16:02:04 +00:00
exchange_name = gateio/okx, is_short = true
(open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate))
2022-01-15 16:23:20 +00:00
(10 + (2 / 1)) / (1 + (0.01 + 0.0006)) = 11.87413417771621
2022-02-11 16:02:04 +00:00
exchange_name = gateio/okx, is_short = false
(open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate))
2022-01-15 16:23:20 +00:00
(10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207
"""
2022-02-04 06:20:27 +00:00
# TODO: Split this test into multiple tests to improve readability
open_order = limit_order_open[enter_side(is_short)]
order = limit_order[enter_side(is_short)]
2021-11-19 18:34:59 +00:00
default_conf_usdt['trading_mode'] = trading_mode
default_conf_usdt['liquidation_buffer'] = liq_buffer
leverage = 1.0 if trading_mode == 'spot' else 5.0
default_conf_usdt['exchange']['name'] = exchange_name
if margin_mode:
default_conf_usdt['margin_mode'] = margin_mode
mocker.patch('freqtrade.exchange.Gateio.validate_ordertypes')
2018-10-09 05:06:11 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker, id=exchange_name)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
2021-11-19 18:34:59 +00:00
freqtrade.strategy.leverage = MagicMock(return_value=leverage)
2018-10-09 05:06:11 +00:00
stake_amount = 2
bid = 0.11
2021-09-14 21:38:26 +00:00
enter_rate_mock = MagicMock(return_value=bid)
enter_mm = MagicMock(return_value=open_order)
2018-10-09 05:06:11 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2021-09-14 21:38:26 +00:00
get_rate=enter_rate_mock,
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
2021-09-17 08:25:58 +00:00
'bid': 1.9,
'ask': 2.2,
'last': 1.9
2018-10-09 05:06:11 +00:00
}),
2021-09-14 21:38:26 +00:00
create_order=enter_mm,
get_min_pair_stake_amount=MagicMock(return_value=1),
2018-10-09 05:06:11 +00:00
get_fee=fee,
2021-11-19 18:34:59 +00:00
get_funding_fees=MagicMock(return_value=0),
name=exchange_name,
get_maintenance_ratio_and_amt=MagicMock(return_value=(0.01, 0.01)),
2018-10-09 05:06:11 +00:00
)
pair = 'ETH/USDT'
2018-10-09 05:06:11 +00:00
2021-09-14 21:38:26 +00:00
assert not freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
assert enter_rate_mock.call_count == 1
assert enter_mm.call_count == 0
assert freqtrade.strategy.confirm_trade_entry.call_count == 1
2021-09-14 21:38:26 +00:00
enter_rate_mock.reset_mock()
2021-09-14 21:38:26 +00:00
open_order['id'] = '22'
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
2021-08-26 04:48:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount)
2021-09-14 21:38:26 +00:00
assert enter_rate_mock.call_count == 1
assert enter_mm.call_count == 1
call_args = enter_mm.call_args_list[0][1]
2018-11-15 18:31:24 +00:00
assert call_args['pair'] == pair
assert call_args['rate'] == bid
2022-02-11 16:02:04 +00:00
assert pytest.approx(call_args['amount']) == round(stake_amount / bid * leverage, 8)
2021-09-14 21:38:26 +00:00
enter_rate_mock.reset_mock()
2018-10-09 05:06:11 +00:00
2018-12-12 12:05:55 +00:00
# Should create an open trade with an open order id
# As the order is not fulfilled yet
trade = Trade.query.first()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2018-12-12 12:05:55 +00:00
assert trade
assert trade.is_open is True
assert trade.open_order_id == '22'
2018-12-12 12:05:55 +00:00
2018-10-09 05:06:11 +00:00
# Test calling with price
2021-09-14 21:38:26 +00:00
open_order['id'] = '33'
2018-10-09 05:06:11 +00:00
fix_price = 0.06
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, fix_price, is_short=is_short)
2021-07-18 03:58:54 +00:00
# Make sure get_rate wasn't called again
2021-09-14 21:38:26 +00:00
assert enter_rate_mock.call_count == 0
2018-10-09 05:06:11 +00:00
2021-09-14 21:38:26 +00:00
assert enter_mm.call_count == 2
call_args = enter_mm.call_args_list[1][1]
2018-11-15 18:31:24 +00:00
assert call_args['pair'] == pair
assert call_args['rate'] == fix_price
2022-02-11 16:02:04 +00:00
assert pytest.approx(call_args['amount']) == round(stake_amount / fix_price * leverage, 8)
2018-10-09 05:06:11 +00:00
2018-12-12 12:05:55 +00:00
# In case of closed order
2021-09-14 21:38:26 +00:00
order['status'] = 'closed'
order['price'] = 10
order['cost'] = 100
order['id'] = '444'
mocker.patch('freqtrade.exchange.Exchange.create_order',
2021-09-14 21:38:26 +00:00
MagicMock(return_value=order))
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
2018-12-12 12:05:55 +00:00
trade = Trade.query.all()[2]
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2018-12-12 12:05:55 +00:00
assert trade
assert trade.open_order_id is None
assert trade.open_rate == 10
assert trade.stake_amount == 100
# In case of rejected or expired order and partially filled
2021-09-14 21:38:26 +00:00
order['status'] = 'expired'
order['amount'] = 30.0
order['filled'] = 20.0
2021-09-14 21:38:26 +00:00
order['remaining'] = 10.00
order['price'] = 0.5
order['cost'] = 15.0
2021-09-14 21:38:26 +00:00
order['id'] = '555'
mocker.patch('freqtrade.exchange.Exchange.create_order',
2021-09-14 21:38:26 +00:00
MagicMock(return_value=order))
2021-08-26 04:48:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount)
2018-12-12 12:05:55 +00:00
trade = Trade.query.all()[3]
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2018-12-12 12:05:55 +00:00
assert trade
assert trade.open_order_id == '555'
2018-12-12 12:05:55 +00:00
assert trade.open_rate == 0.5
assert trade.stake_amount == 15.0
2018-12-12 12:05:55 +00:00
2021-07-11 12:10:41 +00:00
# Test with custom stake
2021-09-14 21:38:26 +00:00
order['status'] = 'open'
order['id'] = '556'
2021-07-11 12:10:41 +00:00
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
2021-07-11 12:10:41 +00:00
trade = Trade.query.all()[4]
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2021-07-11 12:10:41 +00:00
assert trade
assert trade.stake_amount == 150
# Exception case
2021-09-14 21:38:26 +00:00
order['id'] = '557'
2021-07-11 12:10:41 +00:00
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
2021-07-11 12:10:41 +00:00
trade = Trade.query.all()[5]
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2021-07-11 12:10:41 +00:00
assert trade
assert trade.stake_amount == 2.0
2018-12-12 12:05:55 +00:00
# In case of the order is rejected and not filled at all
2021-09-14 21:38:26 +00:00
order['status'] = 'rejected'
2021-11-19 18:34:59 +00:00
order['amount'] = 30.0 * leverage
2021-09-14 21:38:26 +00:00
order['filled'] = 0.0
order['remaining'] = 30.0
2021-09-14 21:38:26 +00:00
order['price'] = 0.5
order['cost'] = 0.0
order['id'] = '66'
mocker.patch('freqtrade.exchange.Exchange.create_order',
2021-09-14 21:38:26 +00:00
MagicMock(return_value=order))
2021-08-26 04:48:26 +00:00
assert not freqtrade.execute_entry(pair, stake_amount)
2021-11-19 18:34:59 +00:00
assert freqtrade.strategy.leverage.call_count == 0 if trading_mode == 'spot' else 2
2018-12-12 12:05:55 +00:00
# Fail to get price...
2021-07-18 03:58:54 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))
2021-09-20 01:06:43 +00:00
with pytest.raises(PricingError, match=f"Could not determine {enter_side(is_short)} price."):
2021-09-14 21:38:26 +00:00
freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
# In case of custom entry price
mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50)
2021-09-14 21:38:26 +00:00
order['status'] = 'open'
order['id'] = '5566'
freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[6]
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
assert trade
assert trade.open_rate_requested == 0.508
# In case of custom entry price set to None
2021-09-14 21:38:26 +00:00
order['status'] = 'open'
order['id'] = '5567'
freqtrade.strategy.custom_entry_price = lambda **kwargs: None
2018-10-09 05:06:11 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_rate=MagicMock(return_value=10),
)
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[7]
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
assert trade
assert trade.open_rate_requested == 10
# In case of custom entry price not float type
2022-01-15 16:23:20 +00:00
freqtrade.exchange.name = exchange_name
2021-09-14 21:38:26 +00:00
order['status'] = 'open'
order['id'] = '5568'
freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price"
2021-09-14 21:38:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[8]
# Trade(id=9, pair=ETH/USDT, amount=0.20000000, is_short=False,
# leverage=1.0, open_rate=10.00000000, open_since=...)
# Trade(id=9, pair=ETH/USDT, amount=0.60000000, is_short=True,
# leverage=3.0, open_rate=10.00000000, open_since=...)
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
assert trade
assert trade.open_rate_requested == 10
assert trade.isolated_liq == liq_price
# In case of too high stake amount
order['status'] = 'open'
order['id'] = '55672'
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_max_pair_stake_amount=MagicMock(return_value=500),
)
freqtrade.exchange.get_max_pair_stake_amount = MagicMock(return_value=500)
assert freqtrade.execute_entry(pair, 2000, is_short=is_short)
trade = Trade.query.all()[9]
trade.is_short = is_short
assert trade.stake_amount == 500
2018-10-09 05:06:11 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order, is_short) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-06-14 08:49:15 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
2021-09-17 08:25:58 +00:00
'bid': 1.9,
'ask': 2.2,
'last': 1.9
2020-06-14 08:49:15 +00:00
}),
create_order=MagicMock(return_value=limit_order[enter_side(is_short)]),
2021-07-18 03:58:54 +00:00
get_rate=MagicMock(return_value=0.11),
get_min_pair_stake_amount=MagicMock(return_value=1),
2020-06-14 08:49:15 +00:00
get_fee=fee,
)
stake_amount = 2
pair = 'ETH/USDT'
2020-06-14 08:49:15 +00:00
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError)
2021-08-26 04:48:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount)
2020-06-14 08:49:15 +00:00
limit_order[enter_side(is_short)]['id'] = '222'
2020-06-14 08:49:15 +00:00
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception)
2021-08-26 04:48:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount)
2020-06-14 08:49:15 +00:00
limit_order[enter_side(is_short)]['id'] = '2223'
2020-06-14 08:49:15 +00:00
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
2021-08-26 04:48:26 +00:00
assert freqtrade.execute_entry(pair, stake_amount)
2020-06-14 08:49:15 +00:00
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
2021-08-26 04:48:26 +00:00
assert not freqtrade.execute_entry(pair, stake_amount)
2020-06-14 08:49:15 +00:00
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
order = limit_order[enter_side(is_short)]
2018-11-23 19:28:01 +00:00
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
2021-09-14 21:38:26 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
2018-11-23 19:28:01 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
return_value=order['amount'])
2018-11-23 19:28:01 +00:00
stoploss = MagicMock(return_value={'id': 13434334})
2021-04-20 10:54:22 +00:00
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
2018-11-23 19:28:01 +00:00
trade = MagicMock()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2018-11-23 19:28:01 +00:00
trade.open_order_id = None
trade.stoploss_order_id = None
trade.is_open = True
2019-10-02 00:27:42 +00:00
trades = [trade]
freqtrade.exit_positions(trades)
2018-11-23 14:17:36 +00:00
assert trade.stoploss_order_id == '13434334'
assert stoploss.call_count == 1
2018-11-23 19:28:01 +00:00
assert trade.is_open is True
2018-10-09 05:06:11 +00:00
2018-11-22 18:27:32 +00:00
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_short,
limit_order) -> None:
stoploss = MagicMock(return_value={'id': 13434334})
enter_order = limit_order[enter_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
2018-11-24 18:12:00 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
2021-09-17 08:25:58 +00:00
'bid': 1.9,
'ask': 2.2,
'last': 1.9
2018-11-24 18:12:00 +00:00
}),
create_order=MagicMock(side_effect=[
2021-09-14 21:38:26 +00:00
{'id': enter_order['id']},
{'id': exit_order['id']},
]),
2018-11-24 18:12:00 +00:00
get_fee=fee,
2021-04-20 10:54:22 +00:00
)
mocker.patch.multiple(
'freqtrade.exchange.Binance',
stoploss=stoploss
2018-11-24 18:12:00 +00:00
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# First case: when stoploss is not yet set but the order is open
# should get the stoploss order id immediately
# and should return false as no trade actually happened
trade = MagicMock()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = None
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 1
assert trade.stoploss_order_id == "13434334"
# Second case: when stoploss is set but it is not yet hit
# should do nothing and return false
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
2021-04-20 10:54:22 +00:00
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', hanging_stoploss_order)
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert trade.stoploss_order_id == 100
2019-04-04 15:23:21 +00:00
# Third case: when stoploss was set but it was canceled for some reason
# should set a stoploss immediately and return False
caplog.clear()
2019-04-04 15:23:21 +00:00
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'})
2021-04-20 10:54:22 +00:00
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', canceled_stoploss_order)
stoploss.reset_mock()
2019-04-04 15:23:21 +00:00
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 1
2019-04-04 15:23:21 +00:00
assert trade.stoploss_order_id == "13434334"
# Fourth case: when stoploss is set and it is hit
# should unset stoploss_order_id and return true
# as a trade actually happened
caplog.clear()
freqtrade.enter_positions()
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
2018-11-24 18:12:00 +00:00
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
assert trade
2018-11-24 18:12:00 +00:00
stoploss_order_hit = MagicMock(return_value={
2020-08-13 13:39:29 +00:00
'id': 100,
2018-11-24 18:12:00 +00:00
'status': 'closed',
'type': 'stop_loss_limit',
'price': 3,
'average': 2,
2021-09-14 21:38:26 +00:00
'amount': enter_order['amount'],
2018-11-24 18:12:00 +00:00
})
2021-04-20 10:54:22 +00:00
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hit)
2018-11-24 18:12:00 +00:00
assert freqtrade.handle_stoploss_on_exchange(trade) is True
2020-08-21 18:13:06 +00:00
assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog)
2019-01-09 15:23:13 +00:00
assert trade.stoploss_order_id is None
assert trade.is_open is False
caplog.clear()
2019-01-09 15:23:13 +00:00
mocker.patch(
2021-04-20 10:54:22 +00:00
'freqtrade.exchange.Binance.stoploss',
side_effect=ExchangeError()
)
trade.is_open = True
freqtrade.handle_stoploss_on_exchange(trade)
assert log_has('Unable to place a stoploss order on exchange.', caplog)
assert trade.stoploss_order_id is None
# Fifth case: fetch_order returns InvalidOrder
2019-04-05 18:20:16 +00:00
# It should try to add stoploss order
trade.stoploss_order_id = 100
stoploss.reset_mock()
2021-04-20 10:54:22 +00:00
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order',
2020-03-25 16:02:47 +00:00
side_effect=InvalidOrderException())
2021-04-20 10:54:22 +00:00
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
2019-04-05 18:20:16 +00:00
freqtrade.handle_stoploss_on_exchange(trade)
assert stoploss.call_count == 1
2019-04-05 18:20:16 +00:00
2020-01-23 20:07:11 +00:00
# Sixth case: Closed Trade
# Should not create new order
trade.stoploss_order_id = None
trade.is_open = False
2020-02-02 19:02:38 +00:00
stoploss.reset_mock()
mocker.patch('freqtrade.exchange.Exchange.fetch_order')
2021-04-20 10:54:22 +00:00
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
2020-01-23 20:07:11 +00:00
assert freqtrade.handle_stoploss_on_exchange(trade) is False
2020-02-02 19:02:38 +00:00
assert stoploss.call_count == 0
2020-01-23 20:07:11 +00:00
2019-01-09 15:23:13 +00:00
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, is_short,
limit_order) -> None:
# Sixth case: stoploss order was cancelled but couldn't create new one
enter_order = limit_order[enter_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
2021-09-17 08:25:58 +00:00
'bid': 1.9,
'ask': 2.2,
'last': 1.9
}),
create_order=MagicMock(side_effect=[
2021-09-14 21:38:26 +00:00
{'id': enter_order['id']},
{'id': exit_order['id']},
]),
get_fee=fee,
2021-04-20 10:54:22 +00:00
)
mocker.patch.multiple(
'freqtrade.exchange.Binance',
2020-08-13 13:54:36 +00:00
fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}),
stoploss=MagicMock(side_effect=ExchangeError()),
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.query.first()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
assert trade
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert log_has_re(r'Stoploss order was cancelled, but unable to recreate one.*', caplog)
assert trade.stoploss_order_id is None
assert trade.is_open is True
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_create_stoploss_order_invalid_order(
mocker, default_conf_usdt, caplog, fee, is_short, limit_order, limit_order_open
):
open_order = limit_order_open[enter_side(is_short)]
order = limit_order[exit_side(is_short)]
rpc_mock = patch_RPCManager(mocker)
patch_exchange(mocker)
create_order_mock = MagicMock(side_effect=[
2021-09-14 21:38:26 +00:00
open_order,
{'id': order['id']}
])
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
2021-09-17 08:25:58 +00:00
'bid': 1.9,
'ask': 2.2,
'last': 1.9
}),
create_order=create_order_mock,
get_fee=fee,
2021-04-20 10:54:22 +00:00
)
mocker.patch.multiple(
'freqtrade.exchange.Binance',
fetch_order=MagicMock(return_value={'status': 'canceled'}),
stoploss=MagicMock(side_effect=InvalidOrderException()),
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
freqtrade.enter_positions()
trade = Trade.query.first()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
caplog.clear()
freqtrade.create_stoploss_order(trade, 200)
assert trade.stoploss_order_id is None
assert trade.sell_reason == SellType.EMERGENCY_SELL.value
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
2021-09-08 07:48:22 +00:00
assert log_has("Exiting the trade forcefully", caplog)
# Should call a market sell
assert create_order_mock.call_count == 2
assert create_order_mock.call_args[1]['ordertype'] == 'market'
assert create_order_mock.call_args[1]['pair'] == trade.pair
assert create_order_mock.call_args[1]['amount'] == trade.amount
# Rpc is sending first buy, then sell
assert rpc_mock.call_count == 2
assert rpc_mock.call_args_list[1][0][0]['sell_reason'] == SellType.EMERGENCY_SELL.value
assert rpc_mock.call_args_list[1][0][0]['order_type'] == 'market'
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_create_stoploss_order_insufficient_funds(
mocker, default_conf_usdt, caplog, fee, limit_order_open,
limit_order, is_short
2021-09-14 21:38:26 +00:00
):
exit_order = limit_order[exit_side(is_short)]['id']
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-09-14 15:34:13 +00:00
mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
2021-09-17 08:25:58 +00:00
'bid': 1.9,
'ask': 2.2,
'last': 1.9
2020-09-14 15:34:13 +00:00
}),
create_order=MagicMock(side_effect=[
limit_order[enter_side(is_short)],
2021-09-14 21:38:26 +00:00
exit_order,
]),
2020-09-14 15:34:13 +00:00
get_fee=fee,
fetch_order=MagicMock(return_value={'status': 'canceled'}),
2021-04-20 10:54:22 +00:00
)
mocker.patch.multiple(
'freqtrade.exchange.Binance',
2020-09-14 15:34:13 +00:00
stoploss=MagicMock(side_effect=InsufficientFundsError()),
)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2020-09-14 15:34:13 +00:00
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
freqtrade.enter_positions()
trade = Trade.query.first()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2020-09-14 15:34:13 +00:00
caplog.clear()
freqtrade.create_stoploss_order(trade, 200)
# stoploss_orderid was empty before
assert trade.stoploss_order_id is None
assert mock_insuf.call_count == 1
mock_insuf.reset_mock()
trade.stoploss_order_id = 'stoploss_orderid'
freqtrade.create_stoploss_order(trade, 200)
# No change to stoploss-orderid
assert trade.stoploss_order_id == 'stoploss_orderid'
assert mock_insuf.call_count == 1
2021-10-03 23:41:01 +00:00
@pytest.mark.parametrize("is_short,bid,ask,stop_price,amt,hang_price", [
(False, [4.38, 4.16], [4.4, 4.17], ['2.0805', 4.4 * 0.95], 27.39726027, 3),
(True, [1.09, 1.21], [1.1, 1.22], ['2.321', 1.09 * 1.05], 27.27272727, 1.5),
2021-10-03 23:41:01 +00:00
])
@pytest.mark.usefixtures("init_persistence")
2021-10-03 23:41:01 +00:00
def test_handle_stoploss_on_exchange_trailing(
mocker, default_conf_usdt, fee, is_short, bid, ask, limit_order, stop_price, amt, hang_price
) -> None:
2019-01-09 15:23:13 +00:00
# When trailing stoploss is set
enter_order = limit_order[enter_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
stoploss = MagicMock(return_value={'id': 13434334})
2019-01-09 15:23:13 +00:00
patch_RPCManager(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
2021-09-17 08:25:58 +00:00
'bid': 2.19,
'ask': 2.2,
2021-10-03 23:41:01 +00:00
'last': 2.19,
2019-01-09 15:23:13 +00:00
}),
create_order=MagicMock(side_effect=[
2021-09-14 21:38:26 +00:00
{'id': enter_order['id']},
{'id': exit_order['id']},
]),
2019-01-09 15:23:13 +00:00
get_fee=fee,
2021-04-20 10:54:22 +00:00
)
mocker.patch.multiple(
'freqtrade.exchange.Binance',
2020-01-19 19:06:04 +00:00
stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True),
2019-01-09 15:23:13 +00:00
)
2019-01-15 10:04:32 +00:00
# enabling TSL
default_conf_usdt['trailing_stop'] = True
2019-01-15 10:04:32 +00:00
# disabling ROI
default_conf_usdt['minimal_roi']['0'] = 999999999
2019-01-15 10:04:32 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2019-01-15 10:04:32 +00:00
# enabling stoploss on exchange
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
2019-01-15 10:04:32 +00:00
# setting stoploss
2021-10-03 23:41:01 +00:00
freqtrade.strategy.stoploss = 0.05 if is_short else -0.05
2019-01-15 10:04:32 +00:00
2019-01-16 10:51:54 +00:00
# setting stoploss_on_exchange_interval to 60 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
2019-01-15 10:04:32 +00:00
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2019-01-09 15:23:13 +00:00
freqtrade.enter_positions()
2019-01-09 15:23:13 +00:00
trade = Trade.query.first()
2021-10-03 23:41:01 +00:00
trade.is_short = is_short
2019-01-09 15:23:13 +00:00
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
stoploss_order_hanging = MagicMock(return_value={
'id': 100,
'status': 'open',
'type': 'stop_loss_limit',
2021-10-03 23:41:01 +00:00
'price': hang_price,
2019-01-09 15:23:13 +00:00
'average': 2,
2019-01-15 10:10:28 +00:00
'info': {
2021-10-03 23:41:01 +00:00
'stopPrice': stop_price[0]
2019-01-09 15:23:13 +00:00
}
})
2021-04-20 10:54:22 +00:00
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hanging)
2019-01-09 15:23:13 +00:00
# stoploss initially at 5%
assert freqtrade.handle_trade(trade) is False
2019-01-16 10:22:25 +00:00
assert freqtrade.handle_stoploss_on_exchange(trade) is False
2019-01-09 15:23:13 +00:00
# price jumped 2x
2021-09-17 08:25:58 +00:00
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
2021-10-03 23:41:01 +00:00
'bid': bid[0],
'ask': ask[0],
'last': bid[0],
2021-09-17 08:25:58 +00:00
})
)
2019-01-15 10:04:32 +00:00
cancel_order_mock = MagicMock()
2020-08-13 14:18:03 +00:00
stoploss_order_mock = MagicMock(return_value={'id': 13434334})
2021-04-20 10:54:22 +00:00
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock)
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock)
2019-01-09 15:23:13 +00:00
2019-01-16 10:51:54 +00:00
# stoploss should not be updated as the interval is 60 seconds
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False
cancel_order_mock.assert_not_called()
stoploss_order_mock.assert_not_called()
assert freqtrade.handle_trade(trade) is False
2021-10-03 23:41:01 +00:00
assert trade.stop_loss == stop_price[1]
2019-01-16 10:51:54 +00:00
# setting stoploss_on_exchange_interval to 0 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
2019-01-09 15:23:13 +00:00
assert freqtrade.handle_stoploss_on_exchange(trade) is False
cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
2021-07-26 06:01:57 +00:00
stoploss_order_mock.assert_called_once_with(
amount=amt,
pair='ETH/USDT',
2021-07-26 06:01:57 +00:00
order_types=freqtrade.strategy.order_types,
2021-10-03 23:41:01 +00:00
stop_price=stop_price[1],
side=exit_side(is_short),
2021-09-19 23:44:12 +00:00
leverage=1.0
2021-07-26 06:01:57 +00:00
)
# price fell below stoploss, so dry-run sells trade.
2021-09-18 04:18:14 +00:00
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
2021-10-03 23:41:01 +00:00
'bid': bid[1],
'ask': ask[1],
'last': bid[1],
2021-09-18 04:18:14 +00:00
})
)
assert freqtrade.handle_trade(trade) is True
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2021-09-30 09:19:28 +00:00
def test_handle_stoploss_on_exchange_trailing_error(
mocker, default_conf_usdt, fee, caplog, limit_order, is_short
2021-09-30 09:19:28 +00:00
) -> None:
enter_order = limit_order[enter_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
# When trailing stoploss is set
stoploss = MagicMock(return_value={'id': 13434334})
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
2021-09-17 08:25:58 +00:00
'bid': 1.9,
'ask': 2.2,
'last': 1.9
}),
create_order=MagicMock(side_effect=[
2021-09-14 21:38:26 +00:00
{'id': enter_order['id']},
{'id': exit_order['id']},
]),
get_fee=fee,
2021-04-20 10:54:22 +00:00
)
mocker.patch.multiple(
'freqtrade.exchange.Binance',
2020-01-19 19:06:04 +00:00
stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True),
)
# enabling TSL
default_conf_usdt['trailing_stop'] = True
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
# enabling stoploss on exchange
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# setting stoploss
2021-10-03 23:41:01 +00:00
freqtrade.strategy.stoploss = 0.05 if is_short else -0.05
# setting stoploss_on_exchange_interval to 60 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = "abcd"
trade.stop_loss = 0.2
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime.replace(tzinfo=None)
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
stoploss_order_hanging = {
'id': "abcd",
'status': 'open',
'type': 'stop_loss_limit',
'price': 3,
'average': 2,
'info': {
'stopPrice': '0.1'
}
}
2021-04-20 10:54:22 +00:00
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order',
2020-03-25 16:02:47 +00:00
side_effect=InvalidOrderException())
2021-05-16 12:15:24 +00:00
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order',
return_value=stoploss_order_hanging)
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog)
# Still try to create order
assert stoploss.call_count == 1
# Fail creating stoploss order
caplog.clear()
2021-04-20 10:54:22 +00:00
cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock())
mocker.patch("freqtrade.exchange.Binance.stoploss", side_effect=ExchangeError())
2021-10-02 09:58:02 +00:00
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert cancel_mock.call_count == 1
assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog)
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_stoploss_on_exchange_custom_stop(
mocker, default_conf_usdt, fee, is_short, limit_order
) -> None:
enter_order = limit_order[enter_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
# When trailing stoploss is set
stoploss = MagicMock(return_value={'id': 13434334})
patch_RPCManager(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
2021-09-17 08:25:58 +00:00
'bid': 1.9,
'ask': 2.2,
'last': 1.9
}),
create_order=MagicMock(side_effect=[
2021-09-14 21:38:26 +00:00
{'id': enter_order['id']},
{'id': exit_order['id']},
]),
get_fee=fee,
2021-04-20 10:54:22 +00:00
)
mocker.patch.multiple(
'freqtrade.exchange.Binance',
stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True),
)
# enabling TSL
default_conf_usdt['use_custom_stoploss'] = True
# disabling ROI
default_conf_usdt['minimal_roi']['0'] = 999999999
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
# enabling stoploss on exchange
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# setting stoploss
freqtrade.strategy.custom_stoploss = lambda *args, **kwargs: -0.04
# setting stoploss_on_exchange_interval to 60 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.query.first()
2021-10-03 23:41:01 +00:00
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
stoploss_order_hanging = MagicMock(return_value={
'id': 100,
'status': 'open',
'type': 'stop_loss_limit',
'price': 3,
'average': 2,
'info': {
2021-09-18 04:18:14 +00:00
'stopPrice': '2.0805'
}
})
2021-04-20 10:54:22 +00:00
mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hanging)
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False
# price jumped 2x
2021-09-18 04:18:14 +00:00
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
2021-10-17 07:00:10 +00:00
'bid': 4.38 if not is_short else 1.9 / 2,
'ask': 4.4 if not is_short else 2.2 / 2,
'last': 4.38 if not is_short else 1.9 / 2,
2021-09-18 04:18:14 +00:00
})
)
cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock(return_value={'id': 13434334})
2021-04-20 10:54:22 +00:00
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock)
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock)
# stoploss should not be updated as the interval is 60 seconds
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False
cancel_order_mock.assert_not_called()
stoploss_order_mock.assert_not_called()
assert freqtrade.handle_trade(trade) is False
2021-10-17 07:00:10 +00:00
assert trade.stop_loss == 4.4 * 0.96 if not is_short else 1.1
assert trade.stop_loss_pct == -0.04 if not is_short else 0.04
# setting stoploss_on_exchange_interval to 0 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
assert freqtrade.handle_stoploss_on_exchange(trade) is False
cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
2021-10-17 07:00:10 +00:00
# Long uses modified ask - offset, short modified bid + offset
2021-07-26 06:01:57 +00:00
stoploss_order_mock.assert_called_once_with(
2021-10-17 07:00:10 +00:00
amount=trade.amount,
pair='ETH/USDT',
2021-07-26 06:01:57 +00:00
order_types=freqtrade.strategy.order_types,
2021-10-17 07:00:10 +00:00
stop_price=4.4 * 0.96 if not is_short else 0.95 * 1.04,
side=exit_side(is_short),
2021-09-19 23:44:12 +00:00
leverage=1.0
2021-07-26 06:01:57 +00:00
)
# price fell below stoploss, so dry-run sells trade.
2021-09-18 04:18:14 +00:00
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
'bid': 4.17,
'ask': 4.19,
'last': 4.17
2021-09-18 04:18:14 +00:00
})
)
assert freqtrade.handle_trade(trade) is True
2021-10-11 14:26:15 +00:00
def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
limit_order) -> None:
2021-09-14 21:38:26 +00:00
2021-10-11 14:26:15 +00:00
enter_order = limit_order['buy']
exit_order = limit_order['sell']
2021-09-14 21:38:26 +00:00
2019-01-16 17:38:20 +00:00
# When trailing stoploss is set
stoploss = MagicMock(return_value={'id': 13434334})
2019-01-16 17:38:20 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_edge(mocker)
2019-08-13 08:12:12 +00:00
edge_conf['max_open_trades'] = float('inf')
edge_conf['dry_run_wallet'] = 999.9
2020-01-19 19:06:04 +00:00
edge_conf['exchange']['name'] = 'binance'
2019-01-16 17:38:20 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
'bid': 2.19,
2021-09-17 08:25:58 +00:00
'ask': 2.2,
'last': 2.19
2019-01-16 17:38:20 +00:00
}),
create_order=MagicMock(side_effect=[
2021-09-14 21:38:26 +00:00
{'id': enter_order['id']},
{'id': exit_order['id']},
]),
2019-01-16 17:38:20 +00:00
get_fee=fee,
2020-01-19 19:06:04 +00:00
stoploss=stoploss,
2019-01-16 17:38:20 +00:00
)
# enabling TSL
edge_conf['trailing_stop'] = True
edge_conf['trailing_stop_positive'] = 0.01
edge_conf['trailing_stop_positive_offset'] = 0.011
# disabling ROI
edge_conf['minimal_roi']['0'] = 999999999
freqtrade = FreqtradeBot(edge_conf)
# enabling stoploss on exchange
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# setting stoploss
freqtrade.strategy.stoploss = -0.02
2019-10-18 17:36:04 +00:00
# setting stoploss_on_exchange_interval to 0 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
2019-01-16 17:38:20 +00:00
2021-10-11 14:26:15 +00:00
patch_get_signal(freqtrade)
2019-01-16 17:38:20 +00:00
freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist)
freqtrade.enter_positions()
2019-01-16 17:38:20 +00:00
trade = Trade.query.first()
trade.is_open = True
trade.open_order_id = None
trade.stoploss_order_id = 100
stoploss_order_hanging = MagicMock(return_value={
'id': 100,
'status': 'open',
'type': 'stop_loss_limit',
'price': 3,
'average': 2,
'info': {
2021-09-17 08:25:58 +00:00
'stopPrice': '2.178'
2019-01-16 17:38:20 +00:00
}
})
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging)
2019-01-16 17:38:20 +00:00
# stoploss initially at 20% as edge dictated it.
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert isclose(trade.stop_loss, 1.76)
2019-01-16 17:38:20 +00:00
cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock()
2020-03-25 16:02:47 +00:00
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock)
2020-01-19 19:06:04 +00:00
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock)
2019-01-16 17:38:20 +00:00
# price goes down 5%
2019-12-18 15:34:30 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
'bid': 2.19 * 0.95,
2021-09-17 08:25:58 +00:00
'ask': 2.2 * 0.95,
'last': 2.19 * 0.95
2019-01-16 17:38:20 +00:00
}))
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False
# stoploss should remain the same
assert isclose(trade.stop_loss, 1.76)
2019-01-16 17:38:20 +00:00
# stoploss on exchange should not be canceled
cancel_order_mock.assert_not_called()
# price jumped 2x
2019-12-18 15:34:30 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
'bid': 4.38,
'ask': 4.4,
'last': 4.38
2019-01-16 17:38:20 +00:00
}))
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False
# stoploss should be set to 1% as trailing is on
assert trade.stop_loss == 4.4 * 0.99
2019-01-16 17:38:20 +00:00
cancel_order_mock.assert_called_once_with(100, 'NEO/BTC')
2021-07-26 06:01:57 +00:00
stoploss_order_mock.assert_called_once_with(
amount=11.41438356,
2021-07-26 06:01:57 +00:00
pair='NEO/BTC',
order_types=freqtrade.strategy.order_types,
stop_price=4.4 * 0.99,
2021-10-11 14:26:15 +00:00
side='sell',
2021-09-19 23:44:12 +00:00
leverage=1.0
2021-07-26 06:01:57 +00:00
)
2019-01-16 17:38:20 +00:00
2021-09-19 23:44:12 +00:00
@pytest.mark.parametrize('return_value,side_effect,log_message', [
(False, None, 'Found no enter signals for whitelisted currencies. Trying again...'),
(None, DependencyException, 'Unable to create trade for ETH/USDT: ')
2021-09-19 23:44:12 +00:00
])
def test_enter_positions(mocker, default_conf_usdt, return_value, side_effect,
2021-09-19 23:44:12 +00:00
log_message, caplog) -> None:
caplog.set_level(logging.DEBUG)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2019-12-31 06:01:58 +00:00
mock_ct = mocker.patch(
2019-12-29 01:38:28 +00:00
'freqtrade.freqtradebot.FreqtradeBot.create_trade',
2021-09-19 23:44:12 +00:00
MagicMock(
return_value=return_value,
side_effect=side_effect
)
)
n = freqtrade.enter_positions()
assert n == 0
2021-09-19 23:44:12 +00:00
assert log_has(log_message, caplog)
# create_trade should be called once for every pair in the whitelist.
assert mock_ct.call_count == len(default_conf_usdt['exchange']['pair_whitelist'])
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_exit_positions(
mocker, default_conf_usdt, limit_order, is_short, caplog
) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
return_value=limit_order[enter_side(is_short)])
2018-06-17 19:11:10 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
2018-04-22 09:05:23 +00:00
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
return_value=limit_order[enter_side(is_short)]['amount'])
trade = MagicMock()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
trade.open_order_id = '123'
2018-04-22 09:05:23 +00:00
trade.open_fee = 0.001
2019-10-02 00:27:42 +00:00
trades = [trade]
n = freqtrade.exit_positions(trades)
assert n == 0
2018-04-22 09:05:23 +00:00
# Test amount not modified by fee-logic
assert not log_has(
2021-09-17 08:25:58 +00:00
'Applying fee to amount for Trade {} from 30.0 to 90.81'.format(trade), caplog
)
2018-04-22 09:05:23 +00:00
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
# test amount modified by fee-logic
n = freqtrade.exit_positions(trades)
assert n == 0
2018-04-22 09:05:23 +00:00
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_exit_positions_exception(
mocker, default_conf_usdt, limit_order, caplog, is_short
) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order = limit_order[enter_side(is_short)]
mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
trade = MagicMock()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
2020-03-24 16:20:16 +00:00
trade.open_order_id = None
trade.pair = 'ETH/USDT'
2019-10-02 00:27:42 +00:00
trades = [trade]
2019-03-31 13:51:45 +00:00
# Test raise of DependencyException exception
mocker.patch(
2020-03-24 16:20:16 +00:00
'freqtrade.freqtradebot.FreqtradeBot.handle_trade',
2019-03-31 13:51:45 +00:00
side_effect=DependencyException()
)
caplog.clear()
n = freqtrade.exit_positions(trades)
assert n == 0
assert log_has('Unable to exit trade ETH/USDT: ', caplog)
2019-03-31 13:51:45 +00:00
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_update_trade_state(
mocker, default_conf_usdt, limit_order, is_short, caplog
) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order = limit_order[enter_side(is_short)]
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
2021-09-14 21:38:26 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
2021-09-14 21:38:26 +00:00
return_value=order['amount'])
2019-12-17 05:58:10 +00:00
trade = Trade(
open_order_id=123,
fee_open=0.001,
fee_close=0.001,
open_rate=0.01,
open_date=arrow.utcnow().datetime,
amount=11,
exchange="binance",
2021-09-14 21:38:26 +00:00
is_short=is_short
2019-12-17 05:58:10 +00:00
)
2020-09-06 13:05:47 +00:00
assert not freqtrade.update_trade_state(trade, None)
assert log_has_re(r'Orderid for trade .* is empty.', caplog)
caplog.clear()
2019-09-14 08:07:23 +00:00
# Add datetime explicitly since sqlalchemy defaults apply only once written to database
freqtrade.update_trade_state(trade, '123')
# Test amount not modified by fee-logic
2019-08-11 18:17:39 +00:00
assert not log_has_re(r'Applying fee to .*', caplog)
caplog.clear()
assert trade.open_order_id is None
2021-09-14 21:38:26 +00:00
assert trade.amount == order['amount']
trade.open_order_id = '123'
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
assert trade.amount != 90.81
# test amount modified by fee-logic
freqtrade.update_trade_state(trade, '123')
assert trade.amount == 90.81
assert trade.open_order_id is None
trade.is_open = True
trade.open_order_id = None
# Assert we call handle_trade() if trade is feasible for execution
freqtrade.update_trade_state(trade, '123')
2019-08-11 18:17:39 +00:00
assert log_has_re('Found open order for.*', caplog)
2021-11-06 14:24:52 +00:00
limit_buy_order_usdt_new = deepcopy(limit_order)
limit_buy_order_usdt_new['filled'] = 0.0
2021-11-06 14:24:52 +00:00
limit_buy_order_usdt_new['status'] = 'canceled'
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', side_effect=ValueError)
mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt_new)
res = freqtrade.update_trade_state(trade, '123')
# Cancelled empty
assert res is True
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2021-09-19 23:44:12 +00:00
@pytest.mark.parametrize('initial_amount,has_rounding_fee', [
2021-09-17 08:25:58 +00:00
(30.0 + 1e-14, True),
2021-09-19 23:44:12 +00:00
(8.0, False)
])
def test_update_trade_state_withorderdict(
default_conf_usdt, trades_for_order, limit_order, fee, mocker, initial_amount,
has_rounding_fee, is_short, caplog
):
order = limit_order[enter_side(is_short)]
2021-09-19 23:44:12 +00:00
trades_for_order[0]['amount'] = initial_amount
2019-03-31 17:56:01 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
# fetch_order should not be called!!
mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
2019-03-31 17:56:01 +00:00
patch_exchange(mocker)
amount = sum(x['amount'] for x in trades_for_order)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
caplog.clear()
2019-03-31 17:56:01 +00:00
trade = Trade(
pair='LTC/USDT',
2019-03-31 17:56:01 +00:00
amount=amount,
exchange='binance',
open_rate=2.0,
open_date=arrow.utcnow().datetime,
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
open_order_id="123456",
is_open=True,
2021-09-14 21:38:26 +00:00
is_short=is_short
2019-03-31 17:56:01 +00:00
)
2021-09-14 21:38:26 +00:00
freqtrade.update_trade_state(trade, '123456', order)
2019-03-31 17:56:01 +00:00
assert trade.amount != amount
assert trade.amount == order['amount']
2021-09-19 23:44:12 +00:00
if has_rounding_fee:
assert log_has_re(r'Applying fee on amount for .*', caplog)
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_update_trade_state_exception(mocker, default_conf_usdt, is_short, limit_order,
caplog) -> None:
order = limit_order[enter_side(is_short)]
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-09-14 21:38:26 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
2019-03-31 13:51:45 +00:00
trade = MagicMock()
trade.open_order_id = '123'
# Test raise of OperationalException exception
mocker.patch(
'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
2019-10-17 17:33:21 +00:00
side_effect=DependencyException()
)
freqtrade.update_trade_state(trade, trade.open_order_id)
2019-08-11 18:17:39 +00:00
assert log_has('Could not update trade amount: ', caplog)
def test_update_trade_state_orderexception(mocker, default_conf_usdt, caplog) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
MagicMock(side_effect=InvalidOrderException))
trade = MagicMock()
trade.open_order_id = '123'
# Test raise of OperationalException exception
grm_mock = mocker.patch("freqtrade.freqtradebot.FreqtradeBot.get_real_amount", MagicMock())
freqtrade.update_trade_state(trade, trade.open_order_id)
assert grm_mock.call_count == 0
2019-08-11 18:17:39 +00:00
assert log_has(f'Unable to fetch order {trade.open_order_id}: ', caplog)
2021-09-14 21:38:26 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_update_trade_state_sell(
default_conf_usdt, trades_for_order, limit_order_open, limit_order, is_short, mocker,
):
open_order = limit_order_open[exit_side(is_short)]
l_order = limit_order[exit_side(is_short)]
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
# fetch_order should not be called!!
mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
wallet_mock = MagicMock()
mocker.patch('freqtrade.wallets.Wallets.update', wallet_mock)
patch_exchange(mocker)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
amount = l_order["amount"]
wallet_mock.reset_mock()
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
open_rate=0.245441,
fee_open=0.0025,
fee_close=0.0025,
open_date=arrow.utcnow().datetime,
open_order_id="123456",
is_open=True,
2021-10-04 05:13:34 +00:00
interest_rate=0.0005,
2021-09-14 21:38:26 +00:00
is_short=is_short
)
order = Order.parse_from_ccxt_object(open_order, 'LTC/ETH', (enter_side(is_short)))
2020-08-13 13:54:36 +00:00
trade.orders.append(order)
assert order.status == 'open'
freqtrade.update_trade_state(trade, trade.open_order_id, l_order)
assert trade.amount == l_order['amount']
# Wallet needs to be updated after closing a limit-sell order to reenable buying
assert wallet_mock.call_count == 1
assert not trade.is_open
2020-08-13 13:54:36 +00:00
# Order is updated by update_trade_state
assert order.status == 'closed'
2021-10-14 10:05:50 +00:00
@pytest.mark.parametrize('is_short,close_profit', [
(False, 0.09451372),
(True, 0.08635224),
])
def test_handle_trade(
2021-10-14 10:05:50 +00:00
default_conf_usdt, limit_order_open, limit_order, fee, mocker, is_short, close_profit
) -> None:
open_order = limit_order_open[exit_side(is_short)]
enter_order = limit_order[enter_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
'bid': 2.19,
2021-09-17 08:25:58 +00:00
'ask': 2.2,
'last': 2.19
}),
create_order=MagicMock(side_effect=[
2021-09-14 21:38:26 +00:00
enter_order,
open_order,
]),
2018-06-16 23:23:12 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
assert trade
time.sleep(0.01) # Race condition fix
2021-09-14 21:38:26 +00:00
trade.update(enter_order)
assert trade.is_open is True
2019-12-18 19:16:53 +00:00
freqtrade.wallets.update()
2021-11-06 14:24:52 +00:00
patch_get_signal(freqtrade, enter_long=False, exit_short=is_short,
exit_long=not is_short, exit_tag='sell_signal1')
assert freqtrade.handle_trade(trade) is True
2021-09-14 21:38:26 +00:00
assert trade.open_order_id == exit_order['id']
2021-09-14 21:38:26 +00:00
# Simulate fulfilled LIMIT order for trade
trade.update(exit_order)
assert trade.close_rate == 2.0 if is_short else 2.2
2021-10-11 14:26:15 +00:00
assert trade.close_profit == close_profit
2021-10-14 10:05:50 +00:00
assert trade.calc_profit() == 5.685
assert trade.close_date is not None
2021-10-31 09:51:56 +00:00
assert trade.sell_reason == 'sell_signal1'
@pytest.mark.parametrize("is_short", [False, True])
2021-09-14 21:38:26 +00:00
def test_handle_overlapping_signals(
default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, is_short
2021-09-14 21:38:26 +00:00
) -> None:
open_order = limit_order_open[exit_side(is_short)]
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
create_order=MagicMock(side_effect=[
2021-09-14 21:38:26 +00:00
open_order,
{'id': 1234553382},
]),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
if is_short:
patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
else:
patch_get_signal(freqtrade, enter_long=True, exit_long=True)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
# Buy and Sell triggering, so doing nothing ...
trades = Trade.query.all()
2021-09-14 21:38:26 +00:00
nb_trades = len(trades)
assert nb_trades == 0
# Buy is triggering, so buying ...
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
trades = Trade.query.all()
2021-09-14 21:38:26 +00:00
for trade in trades:
trade.is_short = is_short
nb_trades = len(trades)
assert nb_trades == 1
assert trades[0].is_open is True
# Buy and Sell are not triggering, so doing nothing ...
patch_get_signal(freqtrade, enter_long=False)
assert freqtrade.handle_trade(trades[0]) is False
trades = Trade.query.all()
2021-09-14 21:38:26 +00:00
for trade in trades:
trade.is_short = is_short
nb_trades = len(trades)
assert nb_trades == 1
assert trades[0].is_open is True
# Buy and Sell are triggering, so doing nothing ...
if is_short:
patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
else:
patch_get_signal(freqtrade, enter_long=True, exit_long=True)
assert freqtrade.handle_trade(trades[0]) is False
trades = Trade.query.all()
2021-09-14 21:38:26 +00:00
for trade in trades:
trade.is_short = is_short
nb_trades = len(trades)
assert nb_trades == 1
assert trades[0].is_open is True
# Sell is triggering, guess what : we are Selling!
if is_short:
patch_get_signal(freqtrade, enter_long=False, exit_short=True)
else:
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
trades = Trade.query.all()
2021-09-14 21:38:26 +00:00
for trade in trades:
trade.is_short = is_short
assert freqtrade.handle_trade(trades[0]) is True
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog,
is_short) -> None:
2021-09-14 21:38:26 +00:00
open_order = limit_order_open[enter_side(is_short)]
2021-09-14 21:38:26 +00:00
caplog.set_level(logging.DEBUG)
patch_RPCManager(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
create_order=MagicMock(side_effect=[
2021-09-14 21:38:26 +00:00
open_order,
{'id': 1234553382},
]),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.enter_positions()
trade = Trade.query.first()
2021-09-14 21:38:26 +00:00
trade.is_short = is_short
trade.is_open = True
# FIX: sniffing logs, suggest handle_trade should not execute_trade_exit
# instead that responsibility should be moved out of handle_trade(),
# we might just want to check if we are in a sell condition without
# executing
# if ROI is reached we must sell
caplog.clear()
2022-01-01 18:16:24 +00:00
patch_get_signal(freqtrade)
assert freqtrade.handle_trade(trade)
assert log_has("ETH/USDT - Required profit reached. sell_type=SellType.ROI",
2019-09-10 09:37:15 +00:00
caplog)
@pytest.mark.parametrize("is_short", [False, True])
2021-09-14 21:38:26 +00:00
def test_handle_trade_use_sell_signal(
default_conf_usdt, ticker_usdt, limit_order_open, fee, mocker, caplog, is_short
2021-09-14 21:38:26 +00:00
) -> None:
enter_open_order = limit_order_open[exit_side(is_short)]
exit_open_order = limit_order_open[enter_side(is_short)]
2021-09-14 21:38:26 +00:00
# use_sell_signal is True buy default
caplog.set_level(logging.DEBUG)
patch_RPCManager(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
create_order=MagicMock(side_effect=[
2021-09-14 21:38:26 +00:00
enter_open_order,
exit_open_order,
]),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
trade.is_open = True
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
assert not freqtrade.handle_trade(trade)
if is_short:
patch_get_signal(freqtrade, enter_long=False, exit_short=True)
else:
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
assert freqtrade.handle_trade(trade)
assert log_has("ETH/USDT - Sell signal received. sell_type=SellType.SELL_SIGNAL",
2019-09-10 09:37:15 +00:00
caplog)
@pytest.mark.parametrize("is_short", [False, True])
def test_close_trade(
default_conf_usdt, ticker_usdt, limit_order_open,
limit_order, fee, mocker, is_short
) -> None:
open_order = limit_order_open[exit_side(is_short)]
enter_order = limit_order[exit_side(is_short)]
exit_order = limit_order[enter_side(is_short)]
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-09-14 21:38:26 +00:00
create_order=MagicMock(return_value=open_order),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# Create trade and sell it
freqtrade.enter_positions()
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
assert trade
2021-09-14 21:38:26 +00:00
trade.update(enter_order)
trade.update(exit_order)
assert trade.is_open is False
with pytest.raises(DependencyException, match=r'.*closed trade.*'):
freqtrade.handle_trade(trade)
def test_bot_loop_start_called_once(mocker, default_conf_usdt, caplog):
ftbot = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-01-28 18:40:10 +00:00
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade')
patch_get_signal(ftbot)
ftbot.strategy.bot_loop_start = MagicMock(side_effect=ValueError)
ftbot.strategy.analyze = MagicMock()
ftbot.process()
assert log_has_re(r'Strategy caused the following exception.*', caplog)
assert ftbot.strategy.bot_loop_start.call_count == 1
assert ftbot.strategy.analyze.call_count == 1
@pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_buy_usercustom(
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
limit_sell_order_old, fee, mocker, is_short
) -> None:
2021-09-14 21:38:26 +00:00
old_order = limit_sell_order_old if is_short else limit_buy_order_old
default_conf_usdt["unfilledtimeout"] = {"buy": 30,
"sell": 1400} if is_short else {"buy": 1400, "sell": 30}
rpc_mock = patch_RPCManager(mocker)
2021-09-14 21:38:26 +00:00
cancel_order_mock = MagicMock(return_value=old_order)
cancel_enter_order = deepcopy(old_order)
cancel_enter_order['status'] = 'canceled'
cancel_order_wr_mock = MagicMock(return_value=cancel_enter_order)
2020-08-01 13:59:50 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-09-14 21:38:26 +00:00
fetch_order=MagicMock(return_value=old_order),
2020-08-01 13:59:50 +00:00
cancel_order_with_result=cancel_order_wr_mock,
cancel_order=cancel_order_mock,
get_fee=fee
)
freqtrade = FreqtradeBot(default_conf_usdt)
open_trade.is_short = is_short
2022-02-11 16:02:04 +00:00
open_trade.orders[0].side = 'sell' if is_short else 'buy'
Trade.query.session.add(open_trade)
# Ensure default is to return empty (so not mocked yet)
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
# Return false - trade remains open
if is_short:
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
else:
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
nb_trades = len(trades)
assert nb_trades == 1
if is_short:
assert freqtrade.strategy.check_sell_timeout.call_count == 1
# Raise Keyerror ... (no impact on trade)
freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError)
else:
assert freqtrade.strategy.check_buy_timeout.call_count == 1
freqtrade.strategy.check_buy_timeout = MagicMock(side_effect=KeyError)
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
nb_trades = len(trades)
assert nb_trades == 1
if is_short:
assert freqtrade.strategy.check_sell_timeout.call_count == 1
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=True)
else:
assert freqtrade.strategy.check_buy_timeout.call_count == 1
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True)
# Trade should be closed since the function returns true
freqtrade.check_handle_timedout()
2020-08-01 13:59:50 +00:00
assert cancel_order_wr_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()
nb_trades = len(trades)
assert nb_trades == 0
if is_short:
assert freqtrade.strategy.check_sell_timeout.call_count == 1
else:
assert freqtrade.strategy.check_buy_timeout.call_count == 1
@pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_buy(
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
limit_sell_order_old, fee, mocker, is_short
) -> None:
2021-09-14 21:38:26 +00:00
old_order = limit_sell_order_old if is_short else limit_buy_order_old
rpc_mock = patch_RPCManager(mocker)
2022-02-11 16:02:04 +00:00
old_order['id'] = open_trade.open_order_id
2021-09-14 21:38:26 +00:00
limit_buy_cancel = deepcopy(old_order)
2020-08-01 13:59:50 +00:00
limit_buy_cancel['status'] = 'canceled'
cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-09-14 21:38:26 +00:00
fetch_order=MagicMock(return_value=old_order),
cancel_order_with_result=cancel_order_mock,
get_fee=fee
)
freqtrade = FreqtradeBot(default_conf_usdt)
open_trade.is_short = is_short
Trade.query.session.add(open_trade)
if is_short:
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
else:
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
# 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_(open_trade.open_order_id)).all()
nb_trades = len(trades)
assert nb_trades == 0
2020-02-23 12:11:33 +00:00
# Custom user buy-timeout is never called
if is_short:
assert freqtrade.strategy.check_sell_timeout.call_count == 0
else:
assert freqtrade.strategy.check_buy_timeout.call_count == 0
@pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_cancelled_buy(
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
limit_sell_order_old, fee, mocker, caplog, is_short
) -> None:
2019-02-03 12:39:19 +00:00
""" Handle Buy order cancelled on exchange"""
2021-09-14 21:38:26 +00:00
old_order = limit_sell_order_old if is_short else limit_buy_order_old
2019-02-03 12:39:19 +00:00
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
2021-09-14 21:38:26 +00:00
old_order.update({"status": "canceled", 'filled': 0.0})
2019-02-03 12:39:19 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-09-14 21:38:26 +00:00
fetch_order=MagicMock(return_value=old_order),
2019-02-03 12:39:19 +00:00
cancel_order=cancel_order_mock,
get_fee=fee
)
freqtrade = FreqtradeBot(default_conf_usdt)
open_trade.is_short = is_short
Trade.query.session.add(open_trade)
2019-02-03 12:39:19 +00:00
# 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_(open_trade.open_order_id)).all()
2019-02-03 12:39:19 +00:00
nb_trades = len(trades)
assert nb_trades == 0
2021-09-14 21:38:26 +00:00
assert log_has_re(
f"{'Sell' if is_short else 'Buy'} order cancelled on exchange for Trade.*", caplog)
2019-02-03 12:39:19 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_buy_exception(
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
is_short, fee, mocker
) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
fetch_ticker=ticker_usdt,
2020-06-28 17:45:42 +00:00
fetch_order=MagicMock(side_effect=ExchangeError),
cancel_order=cancel_order_mock,
get_fee=fee
)
freqtrade = FreqtradeBot(default_conf_usdt)
Trade.query.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_(open_trade.open_order_id)).all()
nb_trades = len(trades)
assert nb_trades == 1
@pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_sell_usercustom(
default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
2021-11-18 19:20:01 +00:00
is_short, open_trade_usdt, caplog
) -> None:
2021-11-06 12:10:41 +00:00
default_conf_usdt["unfilledtimeout"] = {"buy": 1440, "sell": 1440, "exit_timeout_count": 1}
2022-02-11 16:02:04 +00:00
limit_sell_order_old['id'] = open_trade_usdt.open_order_id
2022-01-30 18:25:09 +00:00
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
2021-11-06 12:10:41 +00:00
et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit')
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=limit_sell_order_old),
cancel_order=cancel_order_mock
)
freqtrade = FreqtradeBot(default_conf_usdt)
open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime
open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
open_trade_usdt.close_profit_abs = 0.001
open_trade_usdt.is_open = False
Trade.query.session.add(open_trade_usdt)
# Ensure default is false
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
# Return false - No impact
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
assert rpc_mock.call_count == 0
assert open_trade_usdt.is_open is False
assert freqtrade.strategy.check_sell_timeout.call_count == 1
freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError)
# Return Error - No impact
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
assert rpc_mock.call_count == 0
assert open_trade_usdt.is_open is False
assert freqtrade.strategy.check_sell_timeout.call_count == 1
# Return True - sells!
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=True)
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 1
assert open_trade_usdt.is_open is True
assert freqtrade.strategy.check_sell_timeout.call_count == 1
# 2nd canceled trade - Fail execute sell
2021-11-06 12:10:41 +00:00
caplog.clear()
2022-02-11 16:02:04 +00:00
open_trade_usdt.open_order_id = limit_sell_order_old['id']
2021-11-06 12:10:41 +00:00
mocker.patch('freqtrade.persistence.Trade.get_exit_order_count', return_value=1)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit',
side_effect=DependencyException)
freqtrade.check_handle_timedout()
assert log_has_re('Unable to emergency sell .*', caplog)
et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit')
caplog.clear()
# 2nd canceled trade ...
2022-02-11 16:02:04 +00:00
open_trade_usdt.open_order_id = limit_sell_order_old['id']
2021-11-06 12:10:41 +00:00
freqtrade.check_handle_timedout()
assert log_has_re('Emergencyselling trade.*', caplog)
assert et_mock.call_count == 1
@pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_sell(
default_conf_usdt, ticker_usdt, limit_sell_order_old,
mocker, is_short, open_trade_usdt
) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
2022-02-11 16:02:04 +00:00
limit_sell_order_old['id'] = open_trade_usdt.open_order_id
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=limit_sell_order_old),
cancel_order=cancel_order_mock
)
freqtrade = FreqtradeBot(default_conf_usdt)
open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime
open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
open_trade_usdt.close_profit_abs = 0.001
open_trade_usdt.is_open = False
Trade.query.session.add(open_trade_usdt)
2020-02-23 12:11:33 +00:00
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
# check it does cancel sell orders over the time limit
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 1
2021-04-20 05:57:34 +00:00
assert rpc_mock.call_count == 1
assert open_trade_usdt.is_open is True
2020-02-23 12:11:33 +00:00
# Custom user sell-timeout is never called
assert freqtrade.strategy.check_sell_timeout.call_count == 0
@pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_cancelled_sell(
default_conf_usdt, ticker_usdt, limit_sell_order_old, open_trade_usdt,
is_short, mocker, caplog
) -> None:
2019-02-03 12:39:19 +00:00
""" Handle sell order cancelled on exchange"""
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
2020-03-24 16:20:16 +00:00
limit_sell_order_old.update({"status": "canceled", 'filled': 0.0})
2019-02-03 12:39:19 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=limit_sell_order_old),
cancel_order_with_result=cancel_order_mock
2019-02-03 12:39:19 +00:00
)
freqtrade = FreqtradeBot(default_conf_usdt)
2019-02-03 12:39:19 +00:00
open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime
open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
open_trade_usdt.is_open = False
2019-02-03 12:39:19 +00:00
Trade.query.session.add(open_trade_usdt)
2019-02-03 12:39:19 +00:00
# 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 open_trade_usdt.is_open is True
2020-02-08 20:02:52 +00:00
assert log_has_re("Sell order cancelled on exchange for Trade.*", caplog)
2019-02-03 12:39:19 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_partial(
default_conf_usdt, ticker_usdt, limit_buy_order_old_partial, is_short,
open_trade, mocker
) -> None:
rpc_mock = patch_RPCManager(mocker)
2022-01-30 18:25:09 +00:00
limit_buy_order_old_partial['id'] = open_trade.open_order_id
2020-08-01 13:59:50 +00:00
limit_buy_canceled = deepcopy(limit_buy_order_old_partial)
limit_buy_canceled['status'] = 'canceled'
cancel_order_mock = MagicMock(return_value=limit_buy_canceled)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=limit_buy_order_old_partial),
cancel_order_with_result=cancel_order_mock
)
freqtrade = FreqtradeBot(default_conf_usdt)
Trade.query.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 == 2
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 == open_trade.open_rate * trades[0].amount
@pytest.mark.parametrize("is_short", [False, True])
def test_check_handle_timedout_partial_fee(
default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short,
limit_buy_order_old_partial, trades_for_order,
limit_buy_order_old_partial_canceled, mocker
) -> None:
2019-10-18 04:38:07 +00:00
rpc_mock = patch_RPCManager(mocker)
2022-01-30 18:25:09 +00:00
limit_buy_order_old_partial['id'] = open_trade.open_order_id
2019-10-18 04:38:07 +00:00
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
2020-05-03 13:25:54 +00:00
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=0))
2019-10-18 04:38:07 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=limit_buy_order_old_partial),
cancel_order_with_result=cancel_order_mock,
2019-10-18 04:38:07 +00:00
get_trades_for_order=MagicMock(return_value=trades_for_order),
)
freqtrade = FreqtradeBot(default_conf_usdt)
2019-10-18 04:38:07 +00:00
assert open_trade.amount == limit_buy_order_old_partial['amount']
open_trade.fee_open = fee()
open_trade.fee_close = fee()
Trade.query.session.add(open_trade)
2019-10-18 04:38:07 +00:00
# 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.*", caplog)
2019-10-18 04:38:07 +00:00
assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 2
2019-10-18 04:38:07 +00:00
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
assert len(trades) == 1
2020-03-24 15:16:10 +00:00
# Verify that trade has been updated
assert trades[0].amount == (limit_buy_order_old_partial['amount'] -
limit_buy_order_old_partial['remaining']) - 0.023
2019-10-18 04:38:07 +00:00
assert trades[0].open_order_id is None
assert trades[0].fee_updated('buy')
assert pytest.approx(trades[0].fee_open) == 0.001
2019-10-18 04:38:07 +00:00
@pytest.mark.parametrize("is_short", [False, True])
2021-09-15 03:08:15 +00:00
def test_check_handle_timedout_partial_except(
default_conf_usdt, ticker_usdt, open_trade, caplog, fee, is_short,
limit_buy_order_old_partial, trades_for_order,
limit_buy_order_old_partial_canceled, mocker
2021-09-15 03:08:15 +00:00
) -> None:
rpc_mock = patch_RPCManager(mocker)
2022-01-30 18:25:09 +00:00
limit_buy_order_old_partial_canceled['id'] = open_trade.open_order_id
limit_buy_order_old_partial['id'] = open_trade.open_order_id
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(return_value=limit_buy_order_old_partial),
cancel_order_with_result=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_usdt)
assert open_trade.amount == limit_buy_order_old_partial['amount']
open_trade.fee_open = fee()
open_trade.fee_close = fee()
Trade.query.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 == 2
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
assert len(trades) == 1
2020-03-24 15:16:10 +00:00
# Verify that trade has 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()
2021-09-30 09:19:28 +00:00
def test_check_handle_timedout_exception(default_conf_usdt, ticker_usdt, open_trade_usdt, mocker,
caplog) -> None:
2018-03-05 08:11:13 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2018-03-05 08:11:13 +00:00
cancel_order_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot',
handle_cancel_enter=MagicMock(),
handle_cancel_exit=MagicMock(),
2018-03-05 08:11:13 +00:00
)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
fetch_order=MagicMock(side_effect=ExchangeError('Oh snap')),
2018-03-05 08:11:13 +00:00
cancel_order=cancel_order_mock
)
freqtrade = FreqtradeBot(default_conf_usdt)
2018-03-05 08:11:13 +00:00
2021-09-17 08:25:58 +00:00
Trade.query.session.add(open_trade_usdt)
2018-03-05 08:11:13 +00:00
caplog.clear()
freqtrade.check_handle_timedout()
2021-09-17 08:25:58 +00:00
assert log_has_re(r"Cannot query order for Trade\(id=1, pair=ADA/USDT, amount=30.00000000, "
r"is_short=False, leverage=1.0, "
2021-09-17 08:25:58 +00:00
r"open_rate=2.00000000, open_since="
f"{open_trade_usdt.open_date.strftime('%Y-%m-%d %H:%M:%S')}"
2019-09-12 19:53:54 +00:00
r"\) due to Traceback \(most recent call last\):\n*",
2019-09-10 09:37:15 +00:00
caplog)
2018-03-05 08:11:13 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order,
is_short) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
l_order = limit_order[enter_side(is_short)]
cancel_buy_order = deepcopy(limit_order[enter_side(is_short)])
2020-08-01 13:59:50 +00:00
cancel_buy_order['status'] = 'canceled'
del cancel_buy_order['filled']
cancel_order_mock = MagicMock(return_value=cancel_buy_order)
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade._notify_enter_cancel = MagicMock()
trade = MagicMock()
2021-05-21 17:32:26 +00:00
trade.pair = 'LTC/USDT'
trade.open_rate = 200
2021-09-10 17:34:57 +00:00
trade.is_short = False
trade.enter_side = "buy"
l_order['filled'] = 0.0
l_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT']
assert freqtrade.handle_cancel_enter(trade, l_order, reason)
assert cancel_order_mock.call_count == 1
2021-05-21 17:32:26 +00:00
cancel_order_mock.reset_mock()
caplog.clear()
l_order['filled'] = 0.01
assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
2021-05-21 17:32:26 +00:00
assert cancel_order_mock.call_count == 0
2021-09-10 17:34:57 +00:00
assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unexitable.*", caplog)
2021-05-21 17:32:26 +00:00
caplog.clear()
cancel_order_mock.reset_mock()
l_order['filled'] = 2
assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
assert cancel_order_mock.call_count == 1
2020-08-01 13:59:50 +00:00
# Order remained open for some reason (cancel failed)
cancel_buy_order['status'] = 'open'
cancel_order_mock = MagicMock(return_value=cancel_buy_order)
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
2020-08-01 13:59:50 +00:00
assert log_has_re(r"Order .* for .* not cancelled.", caplog)
@pytest.mark.parametrize("is_short", [False, True])
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
indirect=['limit_buy_order_canceled_empty'])
def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_short,
limit_buy_order_canceled_empty) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
cancel_order_mock = mocker.patch(
'freqtrade.exchange.Exchange.cancel_order_with_result',
return_value=limit_buy_order_canceled_empty)
nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter_cancel')
freqtrade = FreqtradeBot(default_conf_usdt)
reason = CANCEL_REASON['TIMEOUT']
trade = MagicMock()
trade.pair = 'LTC/ETH'
2021-09-10 17:34:57 +00:00
trade.enter_side = "buy"
assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
assert cancel_order_mock.call_count == 0
assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog)
assert nofiy_mock.call_count == 1
@pytest.mark.parametrize("is_short", [False, True])
@pytest.mark.parametrize('cancelorder', [
{},
{'remaining': None},
'String Return value',
123
])
def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order, is_short,
cancelorder) -> None:
2019-11-20 19:37:46 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
l_order = limit_order[enter_side(is_short)]
cancel_order_mock = MagicMock(return_value=cancelorder)
2019-11-20 19:37:46 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
cancel_order=cancel_order_mock
)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade._notify_enter_cancel = MagicMock()
2019-11-20 19:37:46 +00:00
trade = MagicMock()
2021-05-21 17:32:26 +00:00
trade.pair = 'LTC/USDT'
2021-09-10 17:34:57 +00:00
trade.enter_side = "buy"
2021-05-21 17:32:26 +00:00
trade.open_rate = 200
2021-09-10 17:34:57 +00:00
trade.enter_side = "buy"
l_order['filled'] = 0.0
l_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT']
assert freqtrade.handle_cancel_enter(trade, l_order, reason)
2019-11-20 19:37:46 +00:00
assert cancel_order_mock.call_count == 1
cancel_order_mock.reset_mock()
l_order['filled'] = 1.0
assert not freqtrade.handle_cancel_enter(trade, l_order, reason)
2019-11-20 19:37:46 +00:00
assert cancel_order_mock.call_count == 1
def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None:
send_msg_mock = patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
cancel_order_mock = MagicMock()
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
cancel_order=cancel_order_mock,
)
2021-07-18 03:58:54 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.245441)
freqtrade = FreqtradeBot(default_conf_usdt)
trade = Trade(
pair='LTC/ETH',
amount=2,
exchange='binance',
open_rate=0.245441,
open_order_id="123456",
open_date=arrow.utcnow().datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
)
order = {'remaining': 1,
2019-02-03 12:39:19 +00:00
'amount': 1,
'status': "open"}
reason = CANCEL_REASON['TIMEOUT']
assert freqtrade.handle_cancel_exit(trade, order, reason)
assert cancel_order_mock.call_count == 1
assert send_msg_mock.call_count == 1
send_msg_mock.reset_mock()
order['amount'] = 2
assert freqtrade.handle_cancel_exit(trade, order, reason
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
# Assert cancel_order was not called (callcount remains unchanged)
assert cancel_order_mock.call_count == 1
assert send_msg_mock.call_count == 1
assert freqtrade.handle_cancel_exit(trade, order, reason
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
# Message should not be iterated again
assert trade.sell_order_status == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
assert send_msg_mock.call_count == 1
def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
2020-05-10 14:24:00 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch(
'freqtrade.exchange.Exchange.cancel_order_with_result', side_effect=InvalidOrderException())
2020-05-10 14:24:00 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
2020-05-10 14:24:00 +00:00
trade = MagicMock()
reason = CANCEL_REASON['TIMEOUT']
2020-05-10 14:24:00 +00:00
order = {'remaining': 1,
'amount': 1,
'status': "open"}
assert freqtrade.handle_cancel_exit(trade, order, reason) == 'error cancelling order'
2020-05-10 14:24:00 +00:00
@pytest.mark.parametrize("is_short, open_rate, amt", [
(False, 2.0, 30.0),
2021-10-17 08:55:20 +00:00
(True, 2.02, 29.70297029),
])
def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, mocker,
2021-10-17 08:55:20 +00:00
ticker_usdt_sell_down, is_short, open_rate, amt) -> None:
rpc_mock = patch_RPCManager(mocker)
2019-09-10 21:18:07 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2021-06-05 13:22:52 +00:00
_is_dry_limit_order_filled=MagicMock(return_value=False),
)
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=False)
# Create some test data
freqtrade.enter_positions()
rpc_mock.reset_mock()
trade = Trade.query.first()
2021-10-17 08:55:20 +00:00
assert trade.is_short == is_short
assert trade
assert freqtrade.strategy.confirm_trade_exit.call_count == 0
# Increase the price and sell it
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
2021-10-17 08:55:20 +00:00
fetch_ticker=ticker_usdt_sell_down if is_short else ticker_usdt_sell_up
)
# Prevented sell ...
freqtrade.execute_trade_exit(
trade=trade,
2021-10-17 08:55:20 +00:00
limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
sell_reason=SellCheckTuple(sell_type=SellType.ROI)
)
assert rpc_mock.call_count == 0
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
assert id(freqtrade.strategy.confirm_trade_exit.call_args_list[0][1]['trade']) != id(trade)
assert freqtrade.strategy.confirm_trade_exit.call_args_list[0][1]['trade'].id == trade.id
# Repatch with true
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
freqtrade.execute_trade_exit(
trade=trade,
2021-10-17 08:55:20 +00:00
limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
sell_reason=SellCheckTuple(sell_type=SellType.ROI)
)
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
assert rpc_mock.call_count == 1
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
'trade_id': 1,
2021-04-20 04:41:58 +00:00
'type': RPCMessageType.SELL,
2021-04-20 10:54:22 +00:00
'exchange': 'Binance',
'pair': 'ETH/USDT',
'gain': 'profit',
2021-10-17 08:55:20 +00:00
'limit': 2.0 if is_short else 2.2,
'amount': amt,
2019-06-17 04:55:54 +00:00
'order_type': 'limit',
2021-10-20 17:13:34 +00:00
'buy_tag': None,
2021-12-29 13:24:12 +00:00
'direction': 'Short' if trade.is_short else 'Long',
'leverage': 1.0,
2021-11-21 08:51:16 +00:00
'enter_tag': None,
'open_rate': open_rate,
2021-10-17 08:55:20 +00:00
'current_rate': 2.01 if is_short else 2.3,
'profit_amount': 0.29554455 if is_short else 5.685,
'profit_ratio': 0.00493809 if is_short else 0.09451372,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'sell_reason': SellType.ROI.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
} == last_msg
@pytest.mark.parametrize("is_short", [False, True])
2021-09-30 09:19:28 +00:00
def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down,
2021-10-17 08:55:20 +00:00
ticker_usdt_sell_up, mocker, is_short) -> None:
rpc_mock = patch_RPCManager(mocker)
2019-09-10 21:18:07 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2021-06-05 13:22:52 +00:00
_is_dry_limit_order_filled=MagicMock(return_value=False),
)
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# Create some test data
freqtrade.enter_positions()
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
assert trade
# Decrease the price and sell it
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
2021-10-17 08:55:20 +00:00
fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down
)
2021-10-17 08:55:20 +00:00
freqtrade.execute_trade_exit(
trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down)()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
2021-04-20 04:41:58 +00:00
'type': RPCMessageType.SELL,
'trade_id': 1,
2021-04-20 10:54:22 +00:00
'exchange': 'Binance',
'pair': 'ETH/USDT',
2021-12-29 13:24:12 +00:00
'direction': 'Short' if trade.is_short else 'Long',
'leverage': 1.0,
'gain': 'loss',
2021-10-17 08:55:20 +00:00
'limit': 2.2 if is_short else 2.01,
'amount': 29.70297029 if is_short else 30.0,
2019-06-17 04:55:54 +00:00
'order_type': 'limit',
2021-10-20 17:13:34 +00:00
'buy_tag': None,
2021-11-21 08:51:16 +00:00
'enter_tag': None,
2021-10-17 08:55:20 +00:00
'open_rate': 2.02 if is_short else 2.0,
'current_rate': 2.2 if is_short else 2.0,
'profit_amount': -5.65990099 if is_short else -0.00075,
'profit_ratio': -0.0945681 if is_short else -1.247e-05,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
'sell_reason': SellType.STOP_LOSS.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
} == last_msg
@pytest.mark.parametrize(
"is_short,amount,open_rate,current_rate,limit,profit_amount,profit_ratio,profit_or_loss", [
(False, 30, 2.0, 2.3, 2.25, 7.18125, 0.11938903, 'profit'),
2022-01-01 16:34:33 +00:00
(True, 29.70297029, 2.02, 2.2, 2.25, -7.14876237, -0.11944465, 'loss'),
])
def test_execute_trade_exit_custom_exit_price(
default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, amount, open_rate,
current_rate, limit, profit_amount, profit_ratio, profit_or_loss, mocker) -> None:
2021-08-05 21:57:45 +00:00
rpc_mock = patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-08-05 21:57:45 +00:00
get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=False),
)
config = deepcopy(default_conf_usdt)
config['custom_price_max_distance_ratio'] = 0.1
patch_whitelist(mocker, config)
freqtrade = FreqtradeBot(config)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2021-08-05 21:57:45 +00:00
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=False)
# Create some test data
freqtrade.enter_positions()
rpc_mock.reset_mock()
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
2021-08-05 21:57:45 +00:00
assert trade
assert freqtrade.strategy.confirm_trade_exit.call_count == 0
# Increase the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt_sell_up
2021-08-05 21:57:45 +00:00
)
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
# Set a custom exit price
freqtrade.strategy.custom_exit_price = lambda **kwargs: 2.25
2021-11-10 06:43:55 +00:00
freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)
)
2021-08-05 21:57:45 +00:00
# Sell price must be different to default bid price
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
assert rpc_mock.call_count == 1
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
'trade_id': 1,
'type': RPCMessageType.SELL,
'exchange': 'Binance',
'pair': 'ETH/USDT',
2021-12-29 13:24:12 +00:00
'direction': 'Short' if trade.is_short else 'Long',
'leverage': 1.0,
'gain': profit_or_loss,
'limit': limit,
'amount': amount,
2021-08-05 21:57:45 +00:00
'order_type': 'limit',
2021-10-20 17:13:34 +00:00
'buy_tag': None,
2021-11-21 08:51:16 +00:00
'enter_tag': None,
'open_rate': open_rate,
'current_rate': current_rate,
'profit_amount': profit_amount,
'profit_ratio': profit_ratio,
'stake_currency': 'USDT',
2021-08-05 21:57:45 +00:00
'fiat_currency': 'USD',
'sell_reason': SellType.SELL_SIGNAL.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
} == last_msg
@pytest.mark.parametrize("is_short", [False, True])
2021-09-30 09:19:28 +00:00
def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
2021-10-17 08:55:20 +00:00
default_conf_usdt, ticker_usdt, fee, is_short, ticker_usdt_sell_down,
ticker_usdt_sell_up, mocker) -> None:
2018-11-25 19:16:53 +00:00
rpc_mock = patch_RPCManager(mocker)
2019-09-10 21:18:07 +00:00
patch_exchange(mocker)
2018-11-25 19:16:53 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2018-11-25 19:16:53 +00:00
get_fee=fee,
2021-06-05 13:22:52 +00:00
_is_dry_limit_order_filled=MagicMock(return_value=False),
2018-11-25 19:16:53 +00:00
)
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 19:16:53 +00:00
# Create some test data
freqtrade.enter_positions()
2018-11-25 19:16:53 +00:00
trade = Trade.query.first()
2021-10-17 08:55:20 +00:00
assert trade.is_short == is_short
2018-11-25 19:16:53 +00:00
assert trade
# Decrease the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2021-10-17 08:55:20 +00:00
fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down
2018-11-25 19:16:53 +00:00
)
default_conf_usdt['dry_run'] = True
2018-12-01 09:43:26 +00:00
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
2018-11-25 19:16:53 +00:00
# Setting trade stoploss to 0.01
2021-10-17 08:55:20 +00:00
trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99
freqtrade.execute_trade_exit(
trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down())['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
2018-11-25 19:16:53 +00:00
assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0]
2018-12-01 09:43:26 +00:00
2018-11-25 19:16:53 +00:00
assert {
2021-04-20 04:41:58 +00:00
'type': RPCMessageType.SELL,
'trade_id': 1,
2021-04-20 10:54:22 +00:00
'exchange': 'Binance',
'pair': 'ETH/USDT',
2021-12-29 13:24:12 +00:00
'direction': 'Short' if trade.is_short else 'Long',
'leverage': 1.0,
2018-11-25 19:16:53 +00:00
'gain': 'loss',
2021-10-17 08:55:20 +00:00
'limit': 2.02 if is_short else 1.98,
'amount': 29.70297029 if is_short else 30.0,
2019-06-17 04:55:54 +00:00
'order_type': 'limit',
2021-10-20 17:13:34 +00:00
'buy_tag': None,
2021-11-21 08:51:16 +00:00
'enter_tag': None,
2021-10-17 08:55:20 +00:00
'open_rate': 2.02 if is_short else 2.0,
'current_rate': 2.2 if is_short else 2.0,
'profit_amount': -0.3 if is_short else -0.8985,
'profit_ratio': -0.00501253 if is_short else -0.01493766,
'stake_currency': 'USDT',
2018-11-25 19:16:53 +00:00
'fiat_currency': 'USD',
'sell_reason': SellType.STOP_LOSS.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
2018-11-25 19:16:53 +00:00
} == last_msg
def test_execute_trade_exit_sloe_cancel_exception(
mocker, default_conf_usdt, ticker_usdt, fee, caplog) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-03-25 16:02:47 +00:00
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
side_effect=InvalidOrderException())
2019-12-13 06:06:54 +00:00
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=300))
create_order_mock = MagicMock(side_effect=[
{'id': '12345554'},
{'id': '12345555'},
])
2019-09-10 21:18:07 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
get_fee=fee,
create_order=create_order_mock,
)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
patch_get_signal(freqtrade)
freqtrade.enter_positions()
trade = Trade.query.first()
2020-10-17 09:28:34 +00:00
PairLock.session = MagicMock()
freqtrade.config['dry_run'] = False
trade.stoploss_order_id = "abcd"
2021-10-04 05:22:51 +00:00
freqtrade.execute_trade_exit(trade=trade, limit=1234,
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert create_order_mock.call_count == 2
2019-08-11 18:17:39 +00:00
assert log_has('Could not cancel stoploss order abcd', caplog)
@pytest.mark.parametrize("is_short", [False, True])
def test_execute_trade_exit_with_stoploss_on_exchange(
default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, mocker) -> None:
default_conf_usdt['exchange']['name'] = 'binance'
2018-11-22 20:12:49 +00:00
rpc_mock = patch_RPCManager(mocker)
2019-09-10 21:18:07 +00:00
patch_exchange(mocker)
stoploss = MagicMock(return_value={
'id': 123,
'info': {
'foo': 'bar'
}
})
cancel_order = MagicMock(return_value=True)
2019-10-03 04:23:58 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2019-10-03 04:23:58 +00:00
get_fee=fee,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
stoploss=stoploss,
2020-03-25 16:02:47 +00:00
cancel_stoploss_order=cancel_order,
2021-06-05 13:22:52 +00:00
_is_dry_limit_order_filled=MagicMock(side_effect=[True, False]),
2019-10-03 04:23:58 +00:00
)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
# Create some test data
freqtrade.enter_positions()
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
assert trade
2019-10-02 00:27:42 +00:00
trades = [trade]
2020-03-24 18:54:13 +00:00
freqtrade.check_handle_timedout()
freqtrade.exit_positions(trades)
2018-11-23 19:28:01 +00:00
# Increase the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt_sell_up
)
2021-11-10 06:43:55 +00:00
freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)
)
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
assert trade
assert cancel_order.call_count == 1
assert rpc_mock.call_count == 3
2021-12-29 13:24:12 +00:00
# TODO-lev: add short, RPC short, short fill
def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt, ticker_usdt, fee,
mocker) -> None:
default_conf_usdt['exchange']['name'] = 'binance'
2018-11-23 14:17:36 +00:00
rpc_mock = patch_RPCManager(mocker)
2019-09-10 21:18:07 +00:00
patch_exchange(mocker)
2018-11-23 14:17:36 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2018-11-23 14:17:36 +00:00
get_fee=fee,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
2021-06-05 13:22:52 +00:00
_is_dry_limit_order_filled=MagicMock(side_effect=[False, True]),
2018-11-23 14:17:36 +00:00
)
stoploss = MagicMock(return_value={
2018-11-23 14:17:36 +00:00
'id': 123,
'info': {
'foo': 'bar'
}
})
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
2018-11-23 14:17:36 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
2018-11-23 14:17:36 +00:00
patch_get_signal(freqtrade)
# Create some test data
freqtrade.enter_positions()
2020-03-24 16:20:16 +00:00
freqtrade.check_handle_timedout()
2018-11-23 14:17:36 +00:00
trade = Trade.query.first()
2019-10-02 00:27:42 +00:00
trades = [trade]
2020-03-24 16:20:16 +00:00
assert trade.stoploss_order_id is None
freqtrade.exit_positions(trades)
2018-11-23 14:17:36 +00:00
assert trade
assert trade.stoploss_order_id == '123'
2018-11-23 19:28:01 +00:00
assert trade.open_order_id is None
2018-11-23 14:17:36 +00:00
# Assuming stoploss on exchnage is hit
# stoploss_order_id should become None
# and trade should be sold at the price of stoploss
stoploss_executed = MagicMock(return_value={
2018-11-23 14:17:36 +00:00
"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": 99.0000000032274,
"average": 1.08801,
"filled": 90.99181074,
"remaining": 0.0,
"status": "closed",
"fee": None,
"trades": None
})
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_executed)
2018-11-23 14:17:36 +00:00
freqtrade.exit_positions(trades)
2018-11-23 14:17:36 +00:00
assert trade.stoploss_order_id is None
assert trade.is_open is False
2018-11-26 17:28:13 +00:00
assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
assert rpc_mock.call_count == 3
2021-04-20 04:41:58 +00:00
assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY
assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.BUY_FILL
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
2018-11-23 14:17:36 +00:00
@pytest.mark.parametrize(
"is_short,amount,open_rate,current_rate,limit,profit_amount,profit_ratio,profit_or_loss", [
(False, 30, 2.0, 2.3, 2.2, 5.685, 0.09451372, 'profit'),
# TODO-lev: Should the current rate be 2.2 for shorts?
(True, 29.70297029, 2.02, 2.2, 2.3, -8.63762376, -0.1443212, 'loss'),
])
def test_execute_trade_exit_market_order(
default_conf_usdt, ticker_usdt, fee, is_short, current_rate, amount, open_rate,
limit, profit_amount, profit_ratio, profit_or_loss, ticker_usdt_sell_up, mocker
) -> None:
"""
amount
long: 60 / 2.0 = 30
short: 60 / 2.02 = 29.70297029
open_value
long: (30 * 2.0) + (30 * 2.0 * 0.0025) = 60.15
short: (29.702970297029704 * 2.02) - (29.702970297029704 * 2.02 * 0.0025) = 59.85
close_value
long: (30 * 2.2) - (30 * 2.2 * 0.0025) = 65.835
short: (29.702970297029704 * 2.3) + (29.702970297029704 * 2.3 * 0.0025) = 68.48762376237624
profit
long: 65.835 - 60.15 = 5.684999999999995
short: 59.85 - 68.48762376237624 = -8.637623762376244
profit_ratio
long: (65.835/60.15) - 1 = 0.0945137157107232
short: 1 - (68.48762376237624/59.85) = -0.1443211990371971
"""
2019-08-12 14:47:00 +00:00
rpc_mock = patch_RPCManager(mocker)
2019-09-10 21:18:07 +00:00
patch_exchange(mocker)
2019-08-12 14:47:00 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2019-08-12 14:47:00 +00:00
get_fee=fee,
2021-06-05 13:22:52 +00:00
_is_dry_limit_order_filled=MagicMock(return_value=False),
2019-08-12 14:47:00 +00:00
)
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2019-08-12 14:47:00 +00:00
# Create some test data
freqtrade.enter_positions()
2019-08-12 14:47:00 +00:00
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
2019-08-12 14:47:00 +00:00
assert trade
# Increase the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt_sell_up
2019-08-12 14:47:00 +00:00
)
freqtrade.config['order_types']['sell'] = 'market'
freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.ROI)
)
2019-08-12 14:47:00 +00:00
assert not trade.is_open
assert trade.close_profit == profit_ratio
2019-08-12 14:47:00 +00:00
assert rpc_mock.call_count == 3
last_msg = rpc_mock.call_args_list[-2][0][0]
2019-08-12 14:47:00 +00:00
assert {
2021-04-20 04:41:58 +00:00
'type': RPCMessageType.SELL,
'trade_id': 1,
2021-04-20 10:54:22 +00:00
'exchange': 'Binance',
'pair': 'ETH/USDT',
2021-12-29 13:24:12 +00:00
'direction': 'Short' if trade.is_short else 'Long',
'leverage': 1.0,
'gain': profit_or_loss,
'limit': limit,
'amount': round(amount, 9),
2019-08-12 14:47:00 +00:00
'order_type': 'market',
2021-10-20 17:13:34 +00:00
'buy_tag': None,
2021-11-21 08:51:16 +00:00
'enter_tag': None,
'open_rate': open_rate,
'current_rate': current_rate,
'profit_amount': profit_amount,
'profit_ratio': profit_ratio,
'stake_currency': 'USDT',
2019-08-12 14:47:00 +00:00
'fiat_currency': 'USD',
'sell_reason': SellType.ROI.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
2019-08-12 14:47:00 +00:00
} == last_msg
@pytest.mark.parametrize("is_short", [False, True])
def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_usdt, fee, is_short,
ticker_usdt_sell_up, mocker) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-09-06 12:59:30 +00:00
mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2020-09-06 12:59:30 +00:00
get_fee=fee,
create_order=MagicMock(side_effect=[
{'id': 1234553382},
InsufficientFundsError(),
]),
2020-09-06 12:59:30 +00:00
)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2020-09-06 12:59:30 +00:00
# Create some test data
freqtrade.enter_positions()
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
2020-09-06 12:59:30 +00:00
assert trade
# Increase the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt_sell_up
2020-09-06 12:59:30 +00:00
)
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
2021-11-10 06:43:55 +00:00
assert not freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=sell_reason
)
2020-09-06 12:59:30 +00:00
assert mock_insuf.call_count == 1
@pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type,is_short', [
2021-09-19 23:44:12 +00:00
# Enable profit
2021-10-14 09:52:29 +00:00
(True, 2.18, 2.2, False, True, SellType.SELL_SIGNAL.value, False),
(True, 2.18, 2.2, False, True, SellType.SELL_SIGNAL.value, True),
# # Disable profit
(False, 3.19, 3.2, True, False, SellType.SELL_SIGNAL.value, False),
(False, 3.19, 3.2, True, False, SellType.SELL_SIGNAL.value, True),
# # Enable loss
# # * Shouldn't this be SellType.STOP_LOSS.value
(True, 0.21, 0.22, False, False, None, False),
(True, 2.41, 2.42, False, False, None, True),
2021-09-19 23:44:12 +00:00
# Disable loss
2021-10-14 09:52:29 +00:00
(False, 0.10, 0.22, True, False, SellType.SELL_SIGNAL.value, False),
(False, 0.10, 0.22, True, False, SellType.SELL_SIGNAL.value, True),
2021-09-19 23:44:12 +00:00
])
def test_sell_profit_only(
default_conf_usdt, limit_order, limit_order_open, is_short,
2021-09-19 23:44:12 +00:00
fee, mocker, profit_only, bid, ask, handle_first, handle_second, sell_type) -> None:
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
2018-06-17 19:11:10 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
2021-09-19 23:44:12 +00:00
'bid': bid,
'ask': ask,
'last': bid
}),
create_order=MagicMock(side_effect=[
limit_order_open[enter_side(is_short)],
{'id': 1234553382},
]),
2018-04-21 17:39:18 +00:00
get_fee=fee,
)
default_conf_usdt.update({
'use_sell_signal': True,
2021-09-19 23:44:12 +00:00
'sell_profit_only': profit_only,
2021-01-11 18:42:44 +00:00
'sell_profit_offset': 0.1,
2021-06-26 14:39:01 +00:00
})
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2021-09-19 23:44:12 +00:00
if sell_type == SellType.SELL_SIGNAL.value:
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
else:
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple(
sell_type=SellType.NONE))
freqtrade.enter_positions()
trade = Trade.query.first()
2021-10-05 08:13:29 +00:00
trade.is_short = is_short
trade.update(limit_order[enter_side(is_short)])
2019-12-18 19:16:53 +00:00
freqtrade.wallets.update()
2021-10-11 14:26:15 +00:00
patch_get_signal(freqtrade, enter_long=False, exit_short=is_short, exit_long=not is_short)
2021-09-19 23:44:12 +00:00
assert freqtrade.handle_trade(trade) is handle_first
2021-09-19 23:44:12 +00:00
if handle_second:
freqtrade.strategy.sell_profit_offset = 0.0
assert freqtrade.handle_trade(trade) is True
2018-04-15 17:38:58 +00:00
def test_sell_not_enough_balance(default_conf_usdt, limit_order, limit_order_open,
2021-10-11 14:26:15 +00:00
fee, mocker, caplog) -> None:
2019-12-13 06:06:54 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=MagicMock(return_value={
2019-12-13 06:06:54 +00:00
'bid': 0.00002172,
'ask': 0.00002173,
'last': 0.00002172
}),
create_order=MagicMock(side_effect=[
2021-10-11 14:26:15 +00:00
limit_order_open['buy'],
{'id': 1234553382},
]),
2019-12-13 06:06:54 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-11 14:26:15 +00:00
patch_get_signal(freqtrade)
2019-12-13 06:06:54 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
2019-12-13 06:06:54 +00:00
trade = Trade.query.first()
amnt = trade.amount
2021-10-11 14:26:15 +00:00
trade.update(limit_order['buy'])
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
2019-12-13 06:06:54 +00:00
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985))
assert freqtrade.handle_trade(trade) is True
assert log_has_re(r'.*Falling back to wallet-amount.', caplog)
assert trade.amount != amnt
@pytest.mark.parametrize('amount_wallet,has_err', [
2021-09-19 23:44:12 +00:00
(95.29, False),
(91.29, True)
])
def test__safe_exit_amount(default_conf_usdt, fee, caplog, mocker, amount_wallet, has_err):
2019-12-13 06:06:54 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
amount = 95.33
2021-09-19 23:44:12 +00:00
amount_wallet = amount_wallet
2019-12-13 06:06:54 +00:00
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=amount_wallet))
wallet_update = mocker.patch('freqtrade.wallets.Wallets.update')
2019-12-13 06:06:54 +00:00
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
open_rate=0.245441,
2019-12-18 19:16:53 +00:00
open_order_id="123456",
fee_open=fee.return_value,
fee_close=fee.return_value,
2019-12-13 06:06:54 +00:00
)
freqtrade = FreqtradeBot(default_conf_usdt)
2019-12-13 06:06:54 +00:00
patch_get_signal(freqtrade)
2021-09-19 23:44:12 +00:00
if has_err:
with pytest.raises(DependencyException, match=r"Not enough amount to exit trade."):
assert freqtrade._safe_exit_amount(trade.pair, trade.amount)
else:
wallet_update.reset_mock()
assert freqtrade._safe_exit_amount(trade.pair, trade.amount) == amount_wallet
assert log_has_re(r'.*Falling back to wallet-amount.', caplog)
assert wallet_update.call_count == 1
caplog.clear()
wallet_update.reset_mock()
assert freqtrade._safe_exit_amount(trade.pair, amount_wallet) == amount_wallet
assert not log_has_re(r'.*Falling back to wallet-amount.', caplog)
assert wallet_update.call_count == 1
2019-12-13 06:06:54 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
ticker_usdt_sell_down, mocker, caplog, is_short) -> None:
2019-08-12 18:48:21 +00:00
patch_RPCManager(mocker)
2019-09-10 21:18:07 +00:00
patch_exchange(mocker)
2019-08-12 18:48:21 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2019-08-12 18:48:21 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2019-08-12 18:48:21 +00:00
# Create some test data
freqtrade.enter_positions()
2019-08-12 18:48:21 +00:00
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
2019-08-12 18:48:21 +00:00
assert trade
# Decrease the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt_sell_down
2019-08-12 18:48:21 +00:00
)
2021-11-10 06:43:55 +00:00
freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_down()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)
)
trade.close(ticker_usdt_sell_down()['bid'])
2019-08-12 18:48:21 +00:00
assert freqtrade.strategy.is_pair_locked(trade.pair)
# reinit - should buy other pair.
caplog.clear()
freqtrade.enter_positions()
2019-08-12 18:48:21 +00:00
2020-11-25 10:54:11 +00:00
assert log_has_re(f"Pair {trade.pair} is still locked.*", caplog)
2019-08-12 18:48:21 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open, is_short,
fee, mocker) -> None:
2018-06-22 18:10:05 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2018-06-22 18:10:05 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
2021-09-18 04:18:14 +00:00
'bid': 2.19,
'ask': 2.2,
'last': 2.19
2018-06-22 18:10:05 +00:00
}),
create_order=MagicMock(side_effect=[
limit_order_open[enter_side(is_short)],
{'id': 1234553382},
]),
2018-06-22 18:10:05 +00:00
get_fee=fee,
)
default_conf_usdt['ignore_roi_if_buy_signal'] = True
2021-06-26 14:39:01 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.enter_positions()
2018-06-22 18:10:05 +00:00
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
trade.update(limit_order[enter_side(is_short)])
2019-12-18 19:16:53 +00:00
freqtrade.wallets.update()
if is_short:
patch_get_signal(freqtrade, enter_long=False, enter_short=True, exit_short=True)
else:
patch_get_signal(freqtrade, enter_long=True, exit_long=True)
2018-06-22 18:10:05 +00:00
assert freqtrade.handle_trade(trade) is False
# Test if buy-signal is absent (should sell due to roi = true)
if is_short:
2022-01-01 18:16:24 +00:00
patch_get_signal(freqtrade, enter_long=False, exit_short=False)
else:
2022-01-01 18:16:24 +00:00
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
2018-06-22 18:10:05 +00:00
assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.ROI.value
2018-06-22 18:10:05 +00:00
@pytest.mark.parametrize("is_short,val1,val2", [
2021-10-17 08:55:20 +00:00
(False, 1.5, 1.1),
(True, 0.5, 0.9)
])
def test_trailing_stop_loss(default_conf_usdt, limit_order_open,
2021-10-17 08:55:20 +00:00
is_short, val1, val2, fee, caplog, mocker) -> None:
2018-06-26 21:40:36 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2018-06-26 21:40:36 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
2021-09-17 08:25:58 +00:00
'bid': 2.0,
'ask': 2.0,
'last': 2.0
2018-06-26 21:40:36 +00:00
}),
create_order=MagicMock(side_effect=[
limit_order_open[enter_side(is_short)],
{'id': 1234553382},
]),
2018-06-26 21:40:36 +00:00
get_fee=fee,
)
default_conf_usdt['trailing_stop'] = True
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
2018-06-26 21:40:36 +00:00
trade = Trade.query.first()
2021-10-17 08:55:20 +00:00
assert trade.is_short == is_short
2019-06-02 11:27:31 +00:00
assert freqtrade.handle_trade(trade) is False
2021-10-17 08:55:20 +00:00
# Raise praise into profits
2019-12-18 15:34:30 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
2019-06-02 11:27:31 +00:00
MagicMock(return_value={
2021-10-17 08:55:20 +00:00
'bid': 2.0 * val1,
'ask': 2.0 * val1,
'last': 2.0 * val1
2019-06-02 11:27:31 +00:00
}))
# Stoploss should be adjusted
assert freqtrade.handle_trade(trade) is False
2021-09-18 04:18:14 +00:00
caplog.clear()
2019-06-02 11:27:31 +00:00
# Price fell
2019-12-18 15:34:30 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
2019-06-02 11:27:31 +00:00
MagicMock(return_value={
2021-10-17 08:55:20 +00:00
'bid': 2.0 * val2,
'ask': 2.0 * val2,
'last': 2.0 * val2
2019-06-02 11:27:31 +00:00
}))
2018-06-26 21:40:36 +00:00
caplog.set_level(logging.DEBUG)
2018-06-26 22:16:19 +00:00
# Sell as trailing-stop is reached
2018-06-26 21:40:36 +00:00
assert freqtrade.handle_trade(trade) is True
2021-10-17 08:55:20 +00:00
stop_multi = 1.1 if is_short else 0.9
assert log_has(f"ETH/USDT - HIT STOP: current price at {(2.0 * val2):6f}, "
f"stoploss is {(2.0 * val1 * stop_multi):6f}, "
f"initial stoploss was at {(2.0 * stop_multi):6f}, trade opened at 2.000000",
caplog)
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
2018-06-26 21:40:36 +00:00
@pytest.mark.parametrize('offset,trail_if_reached,second_sl,is_short', [
(0, False, 2.0394, False),
(0.011, False, 2.0394, False),
(0.055, True, 1.8, False),
2021-10-17 07:54:38 +00:00
(0, False, 2.1614, True),
(0.011, False, 2.1614, True),
(0.055, True, 2.42, True),
2021-09-18 04:18:14 +00:00
])
2021-09-30 09:19:28 +00:00
def test_trailing_stop_loss_positive(
default_conf_usdt, limit_order, limit_order_open,
offset, fee, caplog, mocker, trail_if_reached, second_sl, is_short
2021-09-30 09:19:28 +00:00
) -> None:
enter_price = limit_order[enter_side(is_short)]['price']
2018-06-26 22:16:19 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2018-06-26 22:16:19 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
2021-10-07 11:03:38 +00:00
'bid': enter_price - (-0.01 if is_short else 0.01),
'ask': enter_price - (-0.01 if is_short else 0.01),
'last': enter_price - (-0.01 if is_short else 0.01),
2018-06-26 22:16:19 +00:00
}),
create_order=MagicMock(side_effect=[
limit_order_open[enter_side(is_short)],
{'id': 1234553382},
]),
2018-06-26 22:16:19 +00:00
get_fee=fee,
)
default_conf_usdt['trailing_stop'] = True
default_conf_usdt['trailing_stop_positive'] = 0.01
2021-09-18 04:18:14 +00:00
if offset:
default_conf_usdt['trailing_stop_positive_offset'] = offset
default_conf_usdt['trailing_only_offset_is_reached'] = trail_if_reached
patch_whitelist(mocker, default_conf_usdt)
2019-10-26 11:28:04 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
2018-06-26 22:16:19 +00:00
trade = Trade.query.first()
2021-10-05 08:13:29 +00:00
trade.is_short = is_short
trade.update(limit_order[enter_side(is_short)])
2018-06-26 22:16:19 +00:00
caplog.set_level(logging.DEBUG)
# stop-loss not reached
assert freqtrade.handle_trade(trade) is False
2018-06-27 04:51:48 +00:00
# Raise ticker_usdt above buy price
2021-09-18 04:18:14 +00:00
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
2021-10-07 11:03:38 +00:00
'bid': enter_price + (-0.06 if is_short else 0.06),
'ask': enter_price + (-0.06 if is_short else 0.06),
'last': enter_price + (-0.06 if is_short else 0.06),
2021-09-18 04:18:14 +00:00
})
)
2018-06-27 04:51:48 +00:00
# stop-loss not reached, adjusted stoploss
assert freqtrade.handle_trade(trade) is False
2021-10-17 07:54:38 +00:00
caplog_text = (f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
2021-11-18 19:20:01 +00:00
f"{'2.49' if not is_short else '2.24'}%")
2021-09-18 04:18:14 +00:00
if trail_if_reached:
assert not log_has(caplog_text, caplog)
assert not log_has("ETH/USDT - Adjusting stoploss...", caplog)
2021-09-18 04:18:14 +00:00
else:
assert log_has(caplog_text, caplog)
assert log_has("ETH/USDT - Adjusting stoploss...", caplog)
2021-10-17 07:54:38 +00:00
assert pytest.approx(trade.stop_loss) == second_sl
caplog.clear()
2018-07-16 19:23:35 +00:00
2021-09-18 04:18:14 +00:00
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
2021-10-17 07:54:38 +00:00
'bid': enter_price + (-0.135 if is_short else 0.125),
'ask': enter_price + (-0.135 if is_short else 0.125),
'last': enter_price + (-0.135 if is_short else 0.125),
2021-09-18 04:18:14 +00:00
})
2018-07-16 19:23:35 +00:00
)
assert freqtrade.handle_trade(trade) is False
2021-09-18 04:18:14 +00:00
assert log_has(
2021-10-17 07:54:38 +00:00
f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
2021-11-18 19:20:01 +00:00
f"{'5.72' if not is_short else '5.67'}%",
2021-09-18 04:18:14 +00:00
caplog
)
assert log_has("ETH/USDT - Adjusting stoploss...", caplog)
2018-07-16 19:23:35 +00:00
2021-09-18 04:18:14 +00:00
mocker.patch(
'freqtrade.exchange.Exchange.fetch_ticker',
MagicMock(return_value={
2021-10-07 11:03:38 +00:00
'bid': enter_price + (-0.02 if is_short else 0.02),
'ask': enter_price + (-0.02 if is_short else 0.02),
'last': enter_price + (-0.02 if is_short else 0.02),
2021-09-18 04:18:14 +00:00
})
)
2018-06-27 04:51:48 +00:00
# Lower price again (but still positive)
2018-06-26 22:16:19 +00:00
assert freqtrade.handle_trade(trade) is True
assert log_has(
2021-10-07 11:03:38 +00:00
f"ETH/USDT - HIT STOP: current price at {enter_price + (-0.02 if is_short else 0.02):.6f}, "
2019-09-10 07:43:15 +00:00
f"stoploss is {trade.stop_loss:.6f}, "
2021-10-17 07:54:38 +00:00
f"initial stoploss was at {'2.42' if is_short else '1.80'}0000, "
f"trade opened at {2.2 if is_short else 2.0}00000",
2021-10-11 14:26:15 +00:00
caplog)
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
2018-06-26 21:40:36 +00:00
2018-06-27 04:51:48 +00:00
@pytest.mark.parametrize("is_short", [False, True])
def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_open,
is_short, fee, mocker) -> None:
2018-06-22 18:10:05 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2018-06-22 18:10:05 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
2021-10-17 08:39:53 +00:00
'bid': 2.0,
'ask': 2.0,
'last': 2.0
}),
create_order=MagicMock(side_effect=[
limit_order_open[enter_side(is_short)],
{'id': 1234553382},
{'id': 1234553383}
]),
2018-04-21 17:39:18 +00:00
get_fee=fee,
2021-06-05 13:22:52 +00:00
_is_dry_limit_order_filled=MagicMock(return_value=False),
)
default_conf_usdt['ask_strategy'] = {
2018-06-22 18:10:05 +00:00
'ignore_roi_if_buy_signal': False
}
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
2018-11-25 13:31:46 +00:00
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.enter_positions()
trade = Trade.query.first()
2021-10-05 08:13:29 +00:00
trade.is_short = is_short
trade.update(limit_order[enter_side(is_short)])
2018-06-22 18:10:05 +00:00
# Sell due to min_roi_reached
2022-01-01 18:16:24 +00:00
patch_get_signal(freqtrade, enter_long=not is_short, enter_short=is_short, exit_short=is_short)
2018-06-22 18:10:05 +00:00
assert freqtrade.handle_trade(trade) is True
# Test if buy-signal is absent
2022-01-01 18:16:24 +00:00
patch_get_signal(freqtrade)
assert freqtrade.handle_trade(trade) is True
2021-10-17 08:39:53 +00:00
assert trade.sell_reason == SellType.ROI.value
2018-04-15 17:38:58 +00:00
2021-09-30 09:19:28 +00:00
def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fee, fee, caplog,
mocker):
2018-06-17 19:11:10 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
2018-04-15 17:56:33 +00:00
amount = sum(x['amount'] for x in trades_for_order)
2018-04-15 17:38:58 +00:00
trade = Trade(
pair='LTC/ETH',
2018-04-15 17:56:33 +00:00
amount=amount,
2018-04-15 17:38:58 +00:00
exchange='binance',
2018-04-25 06:52:08 +00:00
open_rate=0.245441,
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
2018-04-15 17:38:58 +00:00
open_order_id="123456"
2018-10-04 16:07:47 +00:00
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-10-02 09:15:12 +00:00
caplog.clear()
2018-04-15 17:56:33 +00:00
# Amount is reduced by "fee"
2018-04-25 06:52:08 +00:00
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001)
assert log_has(
'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, is_short=False,'
' leverage=1.0, open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).',
caplog
)
2018-04-15 17:56:33 +00:00
def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_order_fee, fee,
2020-05-03 09:28:29 +00:00
caplog, mocker):
2020-05-03 09:13:59 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
walletmock = mocker.patch('freqtrade.wallets.Wallets.update')
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=8.1122)
amount = sum(x['amount'] for x in trades_for_order)
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
open_rate=0.245441,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_order_id="123456"
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-05-03 09:13:59 +00:00
walletmock.reset_mock()
# Amount is kept as is
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
assert walletmock.call_count == 1
assert log_has_re(r'Fee amount for Trade.* was in base currency '
2020-05-03 09:28:29 +00:00
'- Eating Fee 0.008 into dust', caplog)
2020-05-03 09:13:59 +00:00
def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mocker, fee):
2018-06-17 19:11:10 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
2018-04-25 07:08:02 +00:00
amount = buy_order_fee['amount']
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
open_rate=0.245441,
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
2018-04-25 07:08:02 +00:00
open_order_id="123456"
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2018-04-25 07:08:02 +00:00
# Amount is reduced by "fee"
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
assert log_has(
'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
'is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed) failed: '
'myTrade-Dict empty found',
caplog
)
2018-04-25 07:08:02 +00:00
2018-04-25 07:13:56 +00:00
@pytest.mark.parametrize(
'fee_par,fee_reduction_amount,use_ticker_usdt_rate,expected_log', [
2021-09-22 22:11:27 +00:00
# basic, amount does not change
2021-09-24 18:09:24 +00:00
({'cost': 0.008, 'currency': 'ETH'}, 0, False, None),
2021-09-22 22:11:27 +00:00
# no currency in fee
2021-09-24 18:09:24 +00:00
({'cost': 0.004, 'currency': None}, 0, True, None),
2021-09-22 22:11:27 +00:00
# BNB no rate
2021-09-24 18:09:24 +00:00
({'cost': 0.00094518, 'currency': 'BNB'}, 0, True, (
2021-10-02 09:15:12 +00:00
'Fee for Trade Trade(id=None, pair=LTC/ETH, amount=8.00000000, is_short=False, '
'leverage=1.0, open_rate=0.24544100, open_since=closed) [buy]: 0.00094518 BNB -'
' rate: None'
)),
2021-09-22 22:11:27 +00:00
# from order
2021-09-24 18:09:24 +00:00
({'cost': 0.004, 'currency': 'LTC'}, 0.004, False, (
'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
2021-10-02 09:15:12 +00:00
'is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed) (from'
' 8.0 to 7.996).'
)),
2021-09-22 22:11:27 +00:00
# invalid, no currency in from fee dict
2021-09-24 18:09:24 +00:00
({'cost': 0.008, 'currency': None}, 0, True, None),
])
2021-09-21 13:56:16 +00:00
def test_get_real_amount(
default_conf_usdt, trades_for_order, buy_order_fee, fee, mocker, caplog,
fee_par, fee_reduction_amount, use_ticker_usdt_rate, expected_log
2021-09-21 13:56:16 +00:00
):
buy_order = deepcopy(buy_order_fee)
2021-09-24 18:09:24 +00:00
buy_order['fee'] = fee_par
trades_for_order[0]['fee'] = fee_par
2018-04-15 17:38:58 +00:00
2018-06-17 19:11:10 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
2018-04-15 17:56:33 +00:00
amount = sum(x['amount'] for x in trades_for_order)
2018-04-15 17:38:58 +00:00
trade = Trade(
2018-04-15 17:56:33 +00:00
pair='LTC/ETH',
amount=amount,
2018-04-15 17:38:58 +00:00
exchange='binance',
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
2018-04-25 06:52:08 +00:00
open_rate=0.245441,
2018-04-15 17:38:58 +00:00
open_order_id="123456"
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
if not use_ticker_usdt_rate:
2021-09-21 13:56:16 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', side_effect=ExchangeError)
2018-04-15 17:56:33 +00:00
caplog.clear()
2021-09-21 13:56:16 +00:00
assert freqtrade.get_real_amount(trade, buy_order) == amount - fee_reduction_amount
2021-09-21 13:56:16 +00:00
if expected_log:
assert log_has(expected_log, caplog)
@pytest.mark.parametrize(
2021-09-22 17:28:42 +00:00
'fee_cost, fee_currency, fee_reduction_amount, expected_fee, expected_log_amount', [
2021-09-22 22:11:27 +00:00
# basic, amount is reduced by fee
2021-09-22 17:28:42 +00:00
(None, None, 0.001, 0.001, 7.992),
2021-09-22 22:11:27 +00:00
# different fee currency on both trades, fee is average of both trade's fee
2021-09-22 17:28:42 +00:00
(0.02, 'BNB', 0.0005, 0.001518575, 7.996),
])
2021-09-21 13:56:16 +00:00
def test_get_real_amount_multi(
default_conf_usdt, trades_for_order2, buy_order_fee, caplog, fee, mocker, markets,
2021-09-22 17:28:42 +00:00
fee_cost, fee_currency, fee_reduction_amount, expected_fee, expected_log_amount,
2021-09-21 13:56:16 +00:00
):
2021-09-21 13:56:16 +00:00
trades_for_order = deepcopy(trades_for_order2)
if fee_cost:
trades_for_order[0]['fee']['cost'] = fee_cost
if fee_currency:
trades_for_order[0]['fee']['currency'] = fee_currency
2018-04-15 17:38:58 +00:00
2018-06-17 19:11:10 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
2021-09-21 13:56:16 +00:00
amount = float(sum(x['amount'] for x in trades_for_order))
default_conf_usdt['stake_currency'] = "ETH"
2018-04-15 17:56:33 +00:00
2018-04-15 17:38:58 +00:00
trade = Trade(
pair='LTC/ETH',
2018-04-15 17:56:33 +00:00
amount=amount,
2018-04-15 17:38:58 +00:00
exchange='binance',
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
2018-04-25 06:52:08 +00:00
open_rate=0.245441,
2018-04-15 17:38:58 +00:00
open_order_id="123456"
)
2020-12-12 10:43:47 +00:00
# Fake markets entry to enable fee parsing
markets['BNB/ETH'] = markets['ETH/USDT']
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-12-12 10:43:47 +00:00
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
return_value={'ask': 0.19, 'last': 0.2})
# Amount is reduced by "fee"
2021-09-21 13:56:16 +00:00
expected_amount = amount - (amount * fee_reduction_amount)
assert freqtrade.get_real_amount(trade, buy_order_fee) == expected_amount
assert log_has(
2021-09-22 16:32:30 +00:00
(
'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
2021-10-02 09:15:12 +00:00
'is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed) '
f'(from 8.0 to {expected_log_amount}).'
2021-09-22 16:32:30 +00:00
),
caplog
)
2021-09-21 13:56:16 +00:00
assert trade.fee_open == expected_fee
assert trade.fee_close == expected_fee
2020-12-12 10:43:47 +00:00
assert trade.fee_open_cost is not None
assert trade.fee_open_currency is not None
assert trade.fee_close_cost is None
assert trade.fee_close_currency is None
2018-04-25 06:52:08 +00:00
2021-09-30 09:19:28 +00:00
def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_order_fee, fee,
mocker):
2021-09-17 08:25:58 +00:00
limit_buy_order_usdt = deepcopy(buy_order_fee)
limit_buy_order_usdt['fee'] = {'cost': 0.004}
2018-05-15 18:13:43 +00:00
2018-06-17 19:11:10 +00:00
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
2018-05-15 18:13:43 +00:00
amount = float(sum(x['amount'] for x in trades_for_order))
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
2018-05-15 18:13:43 +00:00
open_rate=0.245441,
open_order_id="123456"
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2018-05-15 18:13:43 +00:00
# Amount does not change
2021-09-17 08:25:58 +00:00
assert freqtrade.get_real_amount(trade, limit_buy_order_usdt) == amount
2018-05-15 18:13:43 +00:00
def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_doublefee,
fee, mocker):
tfo_mock = mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', return_value='BNB/USDT')
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 200})
trade = Trade(
pair='LTC/USDT',
amount=30.0,
exchange='binance',
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.245441,
open_order_id="123456"
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
# Amount does not change
assert trade.fee_open == 0.0025
assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee) == 30.0
assert tfo_mock.call_count == 0
# Fetch fees from trades dict if available to get "proper" values
assert round(trade.fee_open, 4) == 0.001
2021-09-30 09:19:28 +00:00
def test_get_real_amount_wrong_amount(default_conf_usdt, trades_for_order, buy_order_fee, fee,
mocker):
2021-09-17 08:25:58 +00:00
limit_buy_order_usdt = deepcopy(buy_order_fee)
limit_buy_order_usdt['amount'] = limit_buy_order_usdt['amount'] - 0.001
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
amount = float(sum(x['amount'] for x in trades_for_order))
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
open_rate=0.245441,
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
open_order_id="123456"
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
# Amount does not change
2019-10-17 17:33:21 +00:00
with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"):
2021-09-17 08:25:58 +00:00
freqtrade.get_real_amount(trade, limit_buy_order_usdt)
2021-09-30 09:19:28 +00:00
def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_order, buy_order_fee,
fee, mocker):
# Floats should not be compared directly.
2021-09-17 08:25:58 +00:00
limit_buy_order_usdt = deepcopy(buy_order_fee)
trades_for_order[0]['amount'] = trades_for_order[0]['amount'] + 1e-15
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
amount = float(sum(x['amount'] for x in trades_for_order))
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.245441,
open_order_id="123456"
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
# Amount changes by fee amount.
2021-09-30 09:19:28 +00:00
assert isclose(
freqtrade.get_real_amount(trade, limit_buy_order_usdt),
amount - (amount * 0.001),
abs_tol=MATH_CLOSE_PREC,
)
def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker):
amount = 12345
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
open_rate=0.245441,
2019-12-17 05:58:10 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
open_order_id="123456"
)
order = {
'id': 'mocked_order',
'amount': amount,
'status': 'open',
2020-05-01 18:03:06 +00:00
'side': 'buy',
}
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
assert freqtrade.get_real_amount(trade, order) == amount
2018-08-15 04:05:56 +00:00
@pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [
2020-05-03 09:28:29 +00:00
(8.0, 0.0, 10, 8),
(8.0, 0.0, 0, 8),
(8.0, 0.1, 0, 7.9),
(8.0, 0.1, 10, 8),
(8.0, 0.1, 8.0, 8.0),
(8.0, 0.1, 7.9, 7.9),
])
2021-10-02 06:58:33 +00:00
def test_apply_fee_conditional(default_conf_usdt, fee, mocker,
2020-05-03 09:28:29 +00:00
amount, fee_abs, wallet, amount_exp):
walletmock = mocker.patch('freqtrade.wallets.Wallets.update')
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=wallet)
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
open_rate=0.245441,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_order_id="123456"
)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-05-03 09:28:29 +00:00
walletmock.reset_mock()
# Amount is kept as is
assert freqtrade.apply_fee_conditional(trade, 'LTC', amount, fee_abs) == amount_exp
assert walletmock.call_count == 1
@pytest.mark.parametrize("delta, is_high_delta", [
(0.1, False),
(100, True),
])
@pytest.mark.parametrize('is_short, open_rate', [
(False, 2.0),
(True, 2.02),
])
2021-09-30 09:19:28 +00:00
def test_order_book_depth_of_market(
default_conf_usdt, ticker_usdt, limit_order, limit_order_open,
fee, mocker, order_book_l2, delta, is_high_delta, is_short, open_rate
2021-09-30 09:19:28 +00:00
):
default_conf_usdt['bid_strategy']['check_depth_of_market']['enabled'] = True
default_conf_usdt['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = delta
2018-08-05 04:41:06 +00:00
patch_RPCManager(mocker)
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
2018-08-05 04:41:06 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
create_order=MagicMock(return_value=limit_order_open[enter_side(is_short)]),
2018-08-05 04:41:06 +00:00
get_fee=fee,
)
# Save state of current whitelist
whitelist = deepcopy(default_conf_usdt['exchange']['pair_whitelist'])
freqtrade = FreqtradeBot(default_conf_usdt)
2021-10-03 23:41:01 +00:00
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions()
2018-08-05 04:41:06 +00:00
trade = Trade.query.first()
if is_high_delta:
assert trade is None
else:
trade.is_short = is_short
assert trade is not None
2021-10-03 07:22:50 +00:00
assert trade.stake_amount == 60.0
assert trade.is_open
assert trade.open_date is not None
assert trade.exchange == 'binance'
2018-08-05 14:41:58 +00:00
assert len(Trade.query.all()) == 1
2018-08-05 14:41:58 +00:00
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_order_open[enter_side(is_short)])
2018-08-05 14:41:58 +00:00
assert trade.open_rate == open_rate # TODO-lev: double check
assert whitelist == default_conf_usdt['exchange']['pair_whitelist']
2018-08-05 14:41:58 +00:00
@pytest.mark.parametrize('exception_thrown,ask,last,order_book_top,order_book', [
2021-09-19 23:44:12 +00:00
(False, 0.045, 0.046, 2, None),
(True, 0.042, 0.046, 1, {'bids': [[]], 'asks': [[]]})
])
def test_order_book_bid_strategy1(mocker, default_conf_usdt, order_book_l2, exception_thrown,
2021-09-19 23:44:12 +00:00
ask, last, order_book_top, order_book, caplog) -> None:
2018-08-07 10:29:37 +00:00
"""
2021-09-19 23:44:12 +00:00
test if function get_rate will return the order book price instead of the ask rate
2018-08-07 10:29:37 +00:00
"""
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
ticker_usdt_mock = MagicMock(return_value={'ask': ask, 'last': last})
2018-08-25 11:21:10 +00:00
mocker.patch.multiple(
2018-10-04 16:07:47 +00:00
'freqtrade.exchange.Exchange',
2021-09-19 23:44:12 +00:00
fetch_l2_order_book=MagicMock(return_value=order_book) if order_book else order_book_l2,
fetch_ticker=ticker_usdt_mock,
2018-08-25 11:21:10 +00:00
)
default_conf_usdt['exchange']['name'] = 'binance'
default_conf_usdt['bid_strategy']['use_order_book'] = True
default_conf_usdt['bid_strategy']['order_book_top'] = order_book_top
default_conf_usdt['bid_strategy']['ask_last_balance'] = 0
default_conf_usdt['telegram']['enabled'] = False
2018-08-07 10:29:37 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
2021-09-19 23:44:12 +00:00
if exception_thrown:
with pytest.raises(PricingError):
freqtrade.exchange.get_rate('ETH/USDT', refresh=True, side="buy")
2021-09-19 23:44:12 +00:00
assert log_has_re(
r'Buy Price at location 1 from orderbook could not be determined.', caplog)
else:
assert freqtrade.exchange.get_rate('ETH/USDT', refresh=True, side="buy") == 0.043935
assert ticker_usdt_mock.call_count == 0
2018-08-05 14:41:58 +00:00
def test_check_depth_of_market(default_conf_usdt, mocker, order_book_l2) -> None:
2018-08-14 10:12:44 +00:00
"""
test check depth of market
"""
2018-09-10 18:19:28 +00:00
patch_exchange(mocker)
2018-08-25 11:21:10 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_l2_order_book=order_book_l2
2018-08-25 11:21:10 +00:00
)
default_conf_usdt['telegram']['enabled'] = False
default_conf_usdt['exchange']['name'] = 'binance'
default_conf_usdt['bid_strategy']['check_depth_of_market']['enabled'] = True
2018-08-07 10:29:37 +00:00
# delta is 100 which is impossible to reach. hence function will return false
default_conf_usdt['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 100
freqtrade = FreqtradeBot(default_conf_usdt)
2018-08-05 14:41:58 +00:00
conf = default_conf_usdt['bid_strategy']['check_depth_of_market']
2021-09-10 17:34:57 +00:00
assert freqtrade._check_depth_of_market('ETH/BTC', conf, side=SignalDirection.LONG) is False
2018-08-05 14:41:58 +00:00
@pytest.mark.parametrize('is_short', [False, True])
2021-09-30 09:19:28 +00:00
def test_order_book_ask_strategy(
default_conf_usdt, limit_buy_order_usdt_open, limit_buy_order_usdt, fee, is_short,
2021-09-30 09:19:28 +00:00
limit_sell_order_usdt_open, mocker, order_book_l2, caplog) -> None:
2018-08-14 10:12:44 +00:00
"""
test order book ask strategy
"""
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
default_conf_usdt['exchange']['name'] = 'binance'
default_conf_usdt['ask_strategy']['use_order_book'] = True
default_conf_usdt['ask_strategy']['order_book_top'] = 1
default_conf_usdt['telegram']['enabled'] = False
2018-08-05 14:41:58 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
2018-08-05 14:41:58 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock(return_value={
2021-09-17 08:25:58 +00:00
'bid': 1.9,
'ask': 2.2,
'last': 1.9
2018-08-05 14:41:58 +00:00
}),
create_order=MagicMock(side_effect=[
2021-09-17 08:25:58 +00:00
limit_buy_order_usdt_open,
limit_sell_order_usdt_open,
]),
2018-08-05 14:41:58 +00:00
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf_usdt)
2018-08-05 14:41:58 +00:00
patch_get_signal(freqtrade)
freqtrade.enter_positions()
2018-08-05 14:41:58 +00:00
trade = Trade.query.first()
assert trade
2018-08-05 14:56:14 +00:00
time.sleep(0.01) # Race condition fix
2021-09-17 08:25:58 +00:00
trade.update(limit_buy_order_usdt)
2019-12-18 19:16:53 +00:00
freqtrade.wallets.update()
2018-08-05 14:56:14 +00:00
assert trade.is_open is True
if is_short:
patch_get_signal(freqtrade, enter_long=False, exit_short=True)
else:
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
2018-08-05 14:56:14 +00:00
assert freqtrade.handle_trade(trade) is True
2020-05-26 18:24:44 +00:00
assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0]
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book',
2020-05-26 18:24:44 +00:00
return_value={'bids': [[]], 'asks': [[]]})
with pytest.raises(PricingError):
2020-05-26 18:24:44 +00:00
freqtrade.handle_trade(trade)
2021-06-25 18:51:45 +00:00
assert log_has_re(r'Sell Price at location 1 from orderbook could not be determined\..*',
caplog)
2018-08-29 17:32:44 +00:00
def test_startup_state(default_conf_usdt, mocker):
default_conf_usdt['pairlist'] = {'method': 'VolumePairList',
'config': {'number_assets': 20}
}
2018-12-02 21:12:12 +00:00
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
worker = get_patched_worker(mocker, default_conf_usdt)
2020-01-29 14:08:36 +00:00
assert worker.freqtrade.state is State.RUNNING
def test_startup_trade_reinit(default_conf_usdt, edge_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
reinit_mock = MagicMock()
mocker.patch('freqtrade.persistence.Trade.stoploss_reinitialization', reinit_mock)
ftbot = get_patched_freqtradebot(mocker, default_conf_usdt)
ftbot.startup()
assert reinit_mock.call_count == 1
reinit_mock.reset_mock()
ftbot = get_patched_freqtradebot(mocker, edge_conf)
ftbot.startup()
assert reinit_mock.call_count == 0
2019-10-25 13:00:16 +00:00
@pytest.mark.usefixtures("init_persistence")
2021-09-30 09:19:28 +00:00
def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_buy_order_usdt_open,
caplog):
default_conf_usdt['dry_run'] = True
2019-12-15 09:26:56 +00:00
# Initialize to 2 times stake amount
2021-10-03 07:22:50 +00:00
default_conf_usdt['dry_run_wallet'] = 120.0
default_conf_usdt['max_open_trades'] = 2
default_conf_usdt['tradable_balance_ratio'] = 1.0
2019-12-15 09:26:56 +00:00
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt,
2021-09-17 08:25:58 +00:00
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
2019-12-15 09:26:56 +00:00
get_fee=fee,
)
bot = get_patched_freqtradebot(mocker, default_conf_usdt)
2019-12-15 09:26:56 +00:00
patch_get_signal(bot)
2021-10-03 07:22:50 +00:00
assert bot.wallets.get_free('USDT') == 120.0
2019-12-15 09:26:56 +00:00
n = bot.enter_positions()
assert n == 2
2019-12-15 09:26:56 +00:00
trades = Trade.query.all()
assert len(trades) == 2
bot.config['max_open_trades'] = 3
n = bot.enter_positions()
assert n == 0
assert log_has_re(r"Unable to create trade for XRP/USDT: "
2021-10-03 07:22:50 +00:00
r"Available balance \(0.0 USDT\) is lower than stake amount \(60.0 USDT\)",
2019-12-29 01:38:28 +00:00
caplog)
2020-05-16 10:40:25 +00:00
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short,buy_calls,sell_calls", [
(False, 1, 2),
(True, 1, 2),
])
def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_order, limit_order_open,
is_short, buy_calls, sell_calls):
default_conf_usdt['cancel_open_orders_on_exit'] = True
mocker.patch(
'freqtrade.exchange.Exchange.fetch_order',
side_effect=[
ExchangeError(),
limit_order[exit_side(is_short)],
limit_order_open[enter_side(is_short)],
limit_order_open[exit_side(is_short)],
]
)
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter')
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit')
2020-05-16 10:40:25 +00:00
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-09-15 03:08:15 +00:00
create_mock_trades(fee, is_short=is_short)
2020-05-16 10:40:25 +00:00
trades = Trade.query.all()
2020-09-10 05:40:19 +00:00
assert len(trades) == MOCK_TRADE_COUNT
2020-05-16 10:40:25 +00:00
freqtrade.cancel_all_open_orders()
assert buy_mock.call_count == buy_calls
assert sell_mock.call_count == sell_calls
2020-06-30 05:16:08 +00:00
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_check_for_open_trades(mocker, default_conf_usdt, fee, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-06-30 05:16:08 +00:00
freqtrade.check_for_open_trades()
assert freqtrade.rpc.send_msg.call_count == 0
2021-09-15 03:08:15 +00:00
create_mock_trades(fee, is_short)
2020-06-30 05:16:08 +00:00
trade = Trade.query.first()
2021-10-05 08:16:17 +00:00
trade.is_short = is_short
2020-06-30 05:16:08 +00:00
trade.is_open = True
freqtrade.check_for_open_trades()
assert freqtrade.rpc.send_msg.call_count == 1
assert 'Handle these trades manually' in freqtrade.rpc.send_msg.call_args[0][0]['status']
2020-09-06 17:32:00 +00:00
@pytest.mark.parametrize("is_short", [False, True])
@pytest.mark.usefixtures("init_persistence")
def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2021-09-15 03:08:15 +00:00
create_mock_trades(fee, is_short=is_short)
2020-09-06 17:32:00 +00:00
freqtrade.startup_update_open_orders()
assert not log_has_re(r"Error updating Order .*", caplog)
caplog.clear()
freqtrade.config['dry_run'] = False
freqtrade.startup_update_open_orders()
2020-09-06 17:32:00 +00:00
assert log_has_re(r"Error updating Order .*", caplog)
caplog.clear()
2020-09-10 05:40:19 +00:00
assert len(Order.get_open_orders()) == 3
2021-09-15 03:08:15 +00:00
matching_buy_order = mock_order_4(is_short=is_short)
matching_buy_order.update({
2020-09-06 17:32:00 +00:00
'status': 'closed',
})
2020-09-06 17:32:00 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=matching_buy_order)
freqtrade.startup_update_open_orders()
2020-09-10 05:40:19 +00:00
# Only stoploss and sell orders are kept open
assert len(Order.get_open_orders()) == 2
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
def patch_with_fee(order):
2020-12-12 10:43:47 +00:00
order.update({'fee': {'cost': 0.1, 'rate': 0.01,
'currency': order['symbol'].split('/')[0]}})
return order
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
side_effect=[
2021-09-15 03:08:15 +00:00
patch_with_fee(mock_order_2_sell(is_short=is_short)),
patch_with_fee(mock_order_3_sell(is_short=is_short)),
patch_with_fee(mock_order_1(is_short=is_short)),
patch_with_fee(mock_order_2(is_short=is_short)),
patch_with_fee(mock_order_3(is_short=is_short)),
patch_with_fee(mock_order_4(is_short=is_short)),
]
)
2021-09-15 03:08:15 +00:00
create_mock_trades(fee, is_short=is_short)
trades = Trade.get_trades().all()
2020-09-10 05:40:19 +00:00
assert len(trades) == MOCK_TRADE_COUNT
for trade in trades:
2021-09-15 03:08:15 +00:00
trade.is_short = is_short
assert trade.fee_open_cost is None
assert trade.fee_open_currency is None
assert trade.fee_close_cost is None
assert trade.fee_close_currency is None
freqtrade.update_closed_trades_without_assigned_fees()
# Does nothing for dry-run
trades = Trade.get_trades().all()
assert len(trades) == MOCK_TRADE_COUNT
for trade in trades:
assert trade.fee_open_cost is None
assert trade.fee_open_currency is None
assert trade.fee_close_cost is None
assert trade.fee_close_currency is None
freqtrade.config['dry_run'] = False
freqtrade.update_closed_trades_without_assigned_fees()
2020-09-09 05:01:43 +00:00
trades = Trade.get_trades().all()
2020-09-10 05:40:19 +00:00
assert len(trades) == MOCK_TRADE_COUNT
for trade in trades:
if trade.is_open:
# Exclude Trade 4 - as the order is still open.
2021-09-15 03:08:15 +00:00
if trade.select_order(enter_side(is_short), False):
assert trade.fee_open_cost is not None
assert trade.fee_open_currency is not None
else:
assert trade.fee_open_cost is None
assert trade.fee_open_currency is None
else:
assert trade.fee_close_cost is not None
assert trade.fee_close_currency is not None
2020-09-09 04:40:40 +00:00
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-09-09 04:40:40 +00:00
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
2021-09-15 03:08:15 +00:00
create_mock_trades(fee, is_short=is_short)
2020-09-09 04:40:40 +00:00
trades = Trade.get_trades().all()
freqtrade.reupdate_enter_order_fees(trades[0])
2021-09-15 03:08:15 +00:00
assert log_has_re(
f"Trying to reupdate {enter_side(is_short)} "
r"fees for .*",
caplog
)
2020-09-09 04:40:40 +00:00
assert mock_uts.call_count == 1
assert mock_uts.call_args_list[0][0][0] == trades[0]
2021-09-15 03:08:15 +00:00
assert mock_uts.call_args_list[0][0][1] == mock_order_1(is_short=is_short)['id']
assert log_has_re(
f"Updating {enter_side(is_short)}-fee on trade "
r".* for order .*\.",
caplog
)
2020-09-09 04:40:40 +00:00
mock_uts.reset_mock()
caplog.clear()
# Test with trade without orders
trade = Trade(
pair='XRP/ETH',
stake_amount=60.0,
2020-09-09 04:40:40 +00:00
fee_open=fee.return_value,
fee_close=fee.return_value,
open_date=arrow.utcnow().datetime,
is_open=True,
amount=30,
open_rate=2.0,
2021-04-20 10:54:22 +00:00
exchange='binance',
2021-09-15 03:08:15 +00:00
is_short=is_short
2020-09-09 04:40:40 +00:00
)
Trade.query.session.add(trade)
2020-09-09 04:40:40 +00:00
freqtrade.reupdate_enter_order_fees(trade)
2021-09-15 03:08:15 +00:00
assert log_has_re(f"Trying to reupdate {enter_side(is_short)} fees for "
r".*", caplog)
2020-09-09 04:40:40 +00:00
assert mock_uts.call_count == 0
2021-09-15 03:08:15 +00:00
assert not log_has_re(f"Updating {enter_side(is_short)}-fee on trade "
r".* for order .*\.", caplog)
2020-09-09 04:46:38 +00:00
@pytest.mark.usefixtures("init_persistence")
def test_handle_insufficient_funds(mocker, default_conf_usdt, fee):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-09-09 04:46:38 +00:00
mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order')
mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_enter_order_fees')
2021-09-15 03:08:15 +00:00
create_mock_trades(fee, is_short=False)
2020-09-09 04:46:38 +00:00
trades = Trade.get_trades().all()
# Trade 0 has only a open buy order, no closed order
freqtrade.handle_insufficient_funds(trades[0])
assert mock_rlo.call_count == 0
assert mock_bof.call_count == 1
mock_rlo.reset_mock()
mock_bof.reset_mock()
# Trade 1 has closed buy and sell orders
freqtrade.handle_insufficient_funds(trades[1])
assert mock_rlo.call_count == 1
assert mock_bof.call_count == 0
mock_rlo.reset_mock()
mock_bof.reset_mock()
# Trade 2 has closed buy and sell orders
freqtrade.handle_insufficient_funds(trades[2])
assert mock_rlo.call_count == 1
assert mock_bof.call_count == 0
mock_rlo.reset_mock()
mock_bof.reset_mock()
# Trade 3 has an opne buy order
freqtrade.handle_insufficient_funds(trades[3])
assert mock_rlo.call_count == 0
assert mock_bof.call_count == 1
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog, is_short):
2020-09-19 07:46:32 +00:00
caplog.set_level(logging.DEBUG)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
2020-09-09 05:50:52 +00:00
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
2020-09-09 04:46:38 +00:00
mock_fo = mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
return_value={'status': 'open'})
def reset_open_orders(trade):
trade.open_order_id = None
trade.stoploss_order_id = None
2021-09-15 03:08:15 +00:00
trade.is_short = is_short
2020-09-09 04:46:38 +00:00
2021-09-15 03:08:15 +00:00
create_mock_trades(fee, is_short=is_short)
2020-09-09 04:46:38 +00:00
trades = Trade.get_trades().all()
2020-09-09 05:50:52 +00:00
caplog.clear()
# No open order
trade = trades[0]
reset_open_orders(trade)
assert trade.open_order_id is None
assert trade.stoploss_order_id is None
freqtrade.refind_lost_order(trade)
2021-09-15 03:08:15 +00:00
order = mock_order_1(is_short=is_short)
2020-09-09 05:50:52 +00:00
assert log_has_re(r"Order Order(.*order_id=" + order['id'] + ".*) is no longer open.", caplog)
assert mock_fo.call_count == 0
assert mock_uts.call_count == 0
# No change to orderid - as update_trade_state is mocked
assert trade.open_order_id is None
assert trade.stoploss_order_id is None
2020-09-09 04:46:38 +00:00
2020-09-09 05:50:52 +00:00
caplog.clear()
mock_fo.reset_mock()
# Open buy order
trade = trades[3]
reset_open_orders(trade)
assert trade.open_order_id is None
assert trade.stoploss_order_id is None
freqtrade.refind_lost_order(trade)
2021-09-15 03:08:15 +00:00
order = mock_order_4(is_short=is_short)
2020-09-09 05:50:52 +00:00
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 0
assert mock_uts.call_count == 0
# No change to orderid - as update_trade_state is mocked
assert trade.open_order_id is None
assert trade.stoploss_order_id is None
2020-09-09 05:50:52 +00:00
caplog.clear()
mock_fo.reset_mock()
# Open stoploss order
trade = trades[4]
reset_open_orders(trade)
assert trade.open_order_id is None
assert trade.stoploss_order_id is None
freqtrade.refind_lost_order(trade)
2021-09-15 03:08:15 +00:00
order = mock_order_5_stoploss(is_short=is_short)
2020-09-09 05:50:52 +00:00
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 1
assert mock_uts.call_count == 1
# stoploss_order_id is "refound" and added to the trade
assert trade.open_order_id is None
assert trade.stoploss_order_id is not None
caplog.clear()
mock_fo.reset_mock()
mock_uts.reset_mock()
# Open sell order
trade = trades[5]
reset_open_orders(trade)
assert trade.open_order_id is None
assert trade.stoploss_order_id is None
freqtrade.refind_lost_order(trade)
2021-09-15 03:08:15 +00:00
order = mock_order_6_sell(is_short=is_short)
assert log_has_re(r"Trying to refind Order\(.*", caplog)
assert mock_fo.call_count == 1
assert mock_uts.call_count == 1
# sell-orderid is "refound" and added to the trade
assert trade.open_order_id == order['id']
assert trade.stoploss_order_id is None
2020-09-09 05:50:52 +00:00
2020-09-09 05:57:02 +00:00
caplog.clear()
2020-09-09 05:50:52 +00:00
# Test error case
mock_fo = mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
side_effect=ExchangeError())
2021-09-15 03:08:15 +00:00
order = mock_order_5_stoploss(is_short=is_short)
2020-09-09 05:50:52 +00:00
freqtrade.refind_lost_order(trades[4])
assert log_has(f"Error updating {order['id']}.", caplog)
def test_get_valid_price(mocker, default_conf_usdt) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.config['custom_price_max_distance_ratio'] = 0.02
custom_price_string = "10"
custom_price_badstring = "10abc"
custom_price_float = 10.0
custom_price_int = 10
custom_price_over_max_alwd = 11.0
custom_price_under_min_alwd = 9.0
proposed_price = 10.1
valid_price_from_string = freqtrade.get_valid_price(custom_price_string, proposed_price)
valid_price_from_badstring = freqtrade.get_valid_price(custom_price_badstring, proposed_price)
valid_price_from_int = freqtrade.get_valid_price(custom_price_int, proposed_price)
valid_price_from_float = freqtrade.get_valid_price(custom_price_float, proposed_price)
valid_price_at_max_alwd = freqtrade.get_valid_price(custom_price_over_max_alwd, proposed_price)
valid_price_at_min_alwd = freqtrade.get_valid_price(custom_price_under_min_alwd, proposed_price)
assert isinstance(valid_price_from_string, float)
assert isinstance(valid_price_from_badstring, float)
assert isinstance(valid_price_from_int, float)
assert isinstance(valid_price_from_float, float)
assert valid_price_from_string == custom_price_float
assert valid_price_from_badstring == proposed_price
assert valid_price_from_int == custom_price_int
assert valid_price_from_float == custom_price_float
assert valid_price_at_max_alwd < custom_price_over_max_alwd
assert valid_price_at_max_alwd > proposed_price
assert valid_price_at_min_alwd > custom_price_under_min_alwd
assert valid_price_at_min_alwd < proposed_price
2021-09-14 21:38:26 +00:00
2022-02-11 09:48:09 +00:00
@pytest.mark.parametrize('liquidation_buffer', [0.0, 0.05])
2022-02-02 20:09:49 +00:00
@pytest.mark.parametrize(
"is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", [
(False, 'spot', 'binance', '', 5.0, 10.0, 1.0, None),
(True, 'spot', 'binance', '', 5.0, 10.0, 1.0, None),
(False, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None),
(True, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None),
2022-02-11 16:02:04 +00:00
(False, 'spot', 'okx', '', 5.0, 10.0, 1.0, None),
(True, 'spot', 'okx', '', 5.0, 10.0, 1.0, None),
2022-02-02 20:09:49 +00:00
# Binance, short
(True, 'futures', 'binance', 'isolated', 5.0, 10.0, 1.0, 11.89108910891089),
(True, 'futures', 'binance', 'isolated', 3.0, 10.0, 1.0, 13.211221122079207),
(True, 'futures', 'binance', 'isolated', 5.0, 8.0, 1.0, 9.514851485148514),
(True, 'futures', 'binance', 'isolated', 5.0, 10.0, 0.6, 12.557755775577558),
# Binance, long
(False, 'futures', 'binance', 'isolated', 5, 10, 1.0, 8.070707070707071),
(False, 'futures', 'binance', 'isolated', 5, 8, 1.0, 6.454545454545454),
(False, 'futures', 'binance', 'isolated', 3, 10, 1.0, 6.717171717171718),
(False, 'futures', 'binance', 'isolated', 5, 10, 0.6, 7.39057239057239),
2022-02-11 16:02:04 +00:00
# Gateio/okx, short
2022-02-02 20:09:49 +00:00
(True, 'futures', 'gateio', 'isolated', 5, 10, 1.0, 11.87413417771621),
(True, 'futures', 'gateio', 'isolated', 5, 10, 2.0, 11.87413417771621),
(True, 'futures', 'gateio', 'isolated', 3, 10, 1.0, 13.476180850346978),
(True, 'futures', 'gateio', 'isolated', 5, 8, 1.0, 9.499307342172967),
2022-02-11 16:02:04 +00:00
# Gateio/okx, long
2022-02-02 20:09:49 +00:00
(False, 'futures', 'gateio', 'isolated', 5.0, 10.0, 1.0, 8.085708510208207),
(False, 'futures', 'gateio', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506),
2022-02-11 16:02:04 +00:00
# (True, 'futures', 'okx', 'isolated', 11.87413417771621),
# (False, 'futures', 'okx', 'isolated', 8.085708510208207),
2022-02-02 20:09:49 +00:00
]
)
def test_leverage_prep(
mocker,
default_conf_usdt,
is_short,
trading_mode,
exchange_name,
margin_mode,
leverage,
open_rate,
amount,
expected_liq,
2022-02-11 09:48:09 +00:00
liquidation_buffer,
2022-02-02 20:09:49 +00:00
):
"""
position = 0.2 * 5
wb: wallet balance (stake_amount if isolated)
cum_b: maintenance amount
side_1: -1 if is_short else 1
ep1: entry price
mmr_b: maintenance margin ratio
Binance, Short
leverage = 5, open_rate = 10, amount = 1.0
((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
((2 + 0.01) - ((-1) * 1 * 10)) / ((1 * 0.01) - ((-1) * 1)) = 11.89108910891089
leverage = 3, open_rate = 10, amount = 1.0
((3.3333333333 + 0.01) - ((-1) * 1.0 * 10)) / ((1.0 * 0.01) - ((-1) * 1.0)) = 13.2112211220
leverage = 5, open_rate = 8, amount = 1.0
((1.6 + 0.01) - ((-1) * 1 * 8)) / ((1 * 0.01) - ((-1) * 1)) = 9.514851485148514
leverage = 5, open_rate = 10, amount = 0.6
((1.6 + 0.01) - ((-1) * 0.6 * 10)) / ((0.6 * 0.01) - ((-1) * 0.6)) = 12.557755775577558
Binance, Long
leverage = 5, open_rate = 10, amount = 1.0
((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position))
((2 + 0.01) - (1 * 1 * 10)) / ((1 * 0.01) - (1 * 1)) = 8.070707070707071
leverage = 5, open_rate = 8, amount = 1.0
((1.6 + 0.01) - (1 * 1 * 8)) / ((1 * 0.01) - (1 * 1)) = 6.454545454545454
leverage = 3, open_rate = 10, amount = 1.0
((2 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 6.717171717171718
leverage = 5, open_rate = 10, amount = 0.6
((1.6 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 7.39057239057239
2022-02-11 16:02:04 +00:00
Gateio/Okx, Short
2022-02-02 20:09:49 +00:00
leverage = 5, open_rate = 10, amount = 1.0
(open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate))
(10 + (2 / 1.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621
leverage = 5, open_rate = 10, amount = 2.0
(10 + (4 / 2.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621
leverage = 3, open_rate = 10, amount = 1.0
(10 + (3.3333333333333 / 1.0)) / (1 - (0.01 + 0.0006)) = 13.476180850346978
leverage = 5, open_rate = 8, amount = 1.0
(8 + (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 9.499307342172967
2022-02-11 16:02:04 +00:00
Gateio/Okx, Long
2022-02-02 20:09:49 +00:00
leverage = 5, open_rate = 10, amount = 1.0
(open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate))
(10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207
leverage = 5, open_rate = 10, amount = 2.0
(10 - (4 / 2.0)) / (1 + (0.01 + 0.0006)) = 7.916089451810806
leverage = 3, open_rate = 10, amount = 1.0
(10 - (3.333333333333333333 / 1.0)) / (1 - (0.01 + 0.0006)) = 6.738090425173506
leverage = 5, open_rate = 8, amount = 1.0
(8 - (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 6.332871561448645
"""
2022-02-11 09:48:09 +00:00
default_conf_usdt['liquidation_buffer'] = liquidation_buffer
2022-02-02 20:09:49 +00:00
default_conf_usdt['trading_mode'] = trading_mode
default_conf_usdt['exchange']['name'] = exchange_name
default_conf_usdt['margin_mode'] = margin_mode
mocker.patch('freqtrade.exchange.Gateio.validate_ordertypes')
patch_RPCManager(mocker)
patch_exchange(mocker, id=exchange_name)
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01))
freqtrade.exchange.name = exchange_name
# default_conf_usdt.update({
# "dry_run": False,
# })
(interest, liq) = freqtrade.leverage_prep(
pair='ETH/USDT:USDT',
open_rate=open_rate,
amount=amount,
leverage=leverage,
is_short=is_short,
)
assert interest == 0.0
if expected_liq is None:
assert liq is None
else:
2022-02-11 09:48:09 +00:00
buffer_amount = liquidation_buffer * abs(open_rate - expected_liq)
expected_liq = expected_liq - buffer_amount if is_short else expected_liq + buffer_amount
2022-02-02 20:09:49 +00:00
isclose(expected_liq, liq)
@pytest.mark.parametrize('trading_mode,calls,t1,t2', [
('spot', 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
('margin', 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
('futures', 31, "2021-09-01 00:00:02", "2021-09-01 08:00:01"),
('futures', 32, "2021-08-31 23:59:59", "2021-09-01 08:00:01"),
('futures', 32, "2021-09-01 00:00:02", "2021-09-01 08:00:02"),
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:02"),
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:03"),
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:04"),
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:05"),
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:06"),
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:07"),
('futures', 33, "2021-08-31 23:59:58", "2021-09-01 08:00:07"),
2021-09-30 05:11:01 +00:00
])
def test_update_funding_fees_schedule(mocker, default_conf, trading_mode, calls, time_machine,
t1, t2):
time_machine.move_to(f"{t1} +00:00")
2021-09-30 05:11:01 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
2021-10-04 04:59:08 +00:00
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_funding_fees', return_value=True)
default_conf['trading_mode'] = trading_mode
default_conf['margin_mode'] = 'isolated'
freqtrade = get_patched_freqtradebot(mocker, default_conf)
2021-09-30 05:11:01 +00:00
time_machine.move_to(f"{t2} +00:00")
2021-10-11 18:35:18 +00:00
# Check schedule jobs in debugging with freqtrade._schedule.jobs
freqtrade._schedule.run_pending()
2021-10-06 05:05:34 +00:00
assert freqtrade.update_funding_fees.call_count == calls
@pytest.mark.parametrize('schedule_off', [False, True])
@pytest.mark.parametrize('is_short', [True, False])
def test_update_funding_fees(
mocker,
default_conf,
time_machine,
fee,
ticker_usdt_sell_up,
is_short,
limit_order_open,
schedule_off
):
"""
2021-11-09 07:00:57 +00:00
nominal_value = mark_price * size
funding_fee = nominal_value * funding_rate
size = 123
"LTC/USDT"
2021-11-09 07:00:57 +00:00
time: 0, mark: 3.3, fundRate: 0.00032583, nominal_value: 405.9, fundFee: 0.132254397
time: 8, mark: 3.2, fundRate: 0.00024472, nominal_value: 393.6, fundFee: 0.096321792
"ETH/USDT"
2021-11-09 07:00:57 +00:00
time: 0, mark: 2.4, fundRate: 0.0001, nominal_value: 295.2, fundFee: 0.02952
time: 8, mark: 2.5, fundRate: 0.0001, nominal_value: 307.5, fundFee: 0.03075
"ETC/USDT"
2021-11-09 07:00:57 +00:00
time: 0, mark: 4.3, fundRate: 0.00031077, nominal_value: 528.9, fundFee: 0.164366253
time: 8, mark: 4.1, fundRate: 0.00022655, nominal_value: 504.3, fundFee: 0.114249165
"XRP/USDT"
2021-11-09 07:00:57 +00:00
time: 0, mark: 1.2, fundRate: 0.00049426, nominal_value: 147.6, fundFee: 0.072952776
time: 8, mark: 1.2, fundRate: 0.00032715, nominal_value: 147.6, fundFee: 0.04828734
"""
# SETUP
time_machine.move_to("2021-09-01 00:00:00 +00:00")
open_order = limit_order_open[enter_side(is_short)]
2021-11-11 19:07:56 +00:00
open_exit_order = limit_order_open[exit_side(is_short)]
bid = 0.11
enter_rate_mock = MagicMock(return_value=bid)
enter_mm = MagicMock(return_value=open_order)
patch_RPCManager(mocker)
patch_exchange(mocker)
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
2021-12-11 08:49:48 +00:00
date_midnight = arrow.get('2021-09-01 00:00:00')
date_eight = arrow.get('2021-09-01 08:00:00')
date_sixteen = arrow.get('2021-09-01 16:00:00')
columns = ['date', 'open', 'high', 'low', 'close', 'volume']
# 16:00 entry is actually never used
# But should be kept in the test to ensure we're filtering correctly.
funding_rates = {
"LTC/USDT":
2021-12-11 08:49:48 +00:00
DataFrame([
[date_midnight, 0.00032583, 0, 0, 0, 0],
[date_eight, 0.00024472, 0, 0, 0, 0],
[date_sixteen, 0.00024472, 0, 0, 0, 0],
], columns=columns),
"ETH/USDT":
2021-12-11 08:49:48 +00:00
DataFrame([
[date_midnight, 0.0001, 0, 0, 0, 0],
[date_eight, 0.0001, 0, 0, 0, 0],
[date_sixteen, 0.0001, 0, 0, 0, 0],
], columns=columns),
"XRP/USDT":
2021-12-11 08:49:48 +00:00
DataFrame([
[date_midnight, 0.00049426, 0, 0, 0, 0],
[date_eight, 0.00032715, 0, 0, 0, 0],
[date_sixteen, 0.00032715, 0, 0, 0, 0],
], columns=columns)
}
mark_prices = {
"LTC/USDT":
2021-12-11 08:49:48 +00:00
DataFrame([
[date_midnight, 3.3, 0, 0, 0, 0],
[date_eight, 3.2, 0, 0, 0, 0],
[date_sixteen, 3.2, 0, 0, 0, 0],
], columns=columns),
"ETH/USDT":
2021-12-11 08:49:48 +00:00
DataFrame([
[date_midnight, 2.4, 0, 0, 0, 0],
[date_eight, 2.5, 0, 0, 0, 0],
[date_sixteen, 2.5, 0, 0, 0, 0],
], columns=columns),
"XRP/USDT":
2021-12-11 08:49:48 +00:00
DataFrame([
[date_midnight, 1.2, 0, 0, 0, 0],
[date_eight, 1.2, 0, 0, 0, 0],
[date_sixteen, 1.2, 0, 0, 0, 0],
], columns=columns)
}
2021-12-11 08:49:48 +00:00
def refresh_latest_ohlcv_mock(pairlist, **kwargs):
ret = {}
for p, tf, ct in pairlist:
if ct == CandleType.MARK:
ret[(p, tf, ct)] = mark_prices[p]
else:
ret[(p, tf, ct)] = funding_rates[p]
2021-12-11 08:49:48 +00:00
return ret
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv',
side_effect=refresh_latest_ohlcv_mock)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_rate=enter_rate_mock,
fetch_ticker=MagicMock(return_value={
'bid': 1.9,
'ask': 2.2,
'last': 1.9
}),
create_order=enter_mm,
get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
# initial funding fees,
freqtrade.execute_entry('ETH/USDT', 123, is_short=is_short)
freqtrade.execute_entry('LTC/USDT', 2.0, is_short=is_short)
freqtrade.execute_entry('XRP/USDT', 123, is_short=is_short)
2022-01-18 06:40:09 +00:00
multipl = 1 if is_short else -1
trades = Trade.get_open_trades()
assert len(trades) == 3
for trade in trades:
2021-12-11 08:49:48 +00:00
assert pytest.approx(trade.funding_fees) == (
trade.amount *
2021-12-11 08:49:48 +00:00
mark_prices[trade.pair].iloc[0]['open'] *
2022-01-18 06:40:09 +00:00
funding_rates[trade.pair].iloc[0]['open'] * multipl
)
2021-11-11 19:07:56 +00:00
mocker.patch('freqtrade.exchange.Exchange.create_order', return_value=open_exit_order)
time_machine.move_to("2021-09-01 08:00:00 +00:00")
if schedule_off:
for trade in trades:
freqtrade.execute_trade_exit(
trade=trade,
# The values of the next 2 params are irrelevant for this test
limit=ticker_usdt_sell_up()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.ROI)
)
2021-12-11 08:49:48 +00:00
assert trade.funding_fees == pytest.approx(sum(
trade.amount *
mark_prices[trade.pair].iloc[0:2]['open'] *
2022-01-18 06:40:09 +00:00
funding_rates[trade.pair].iloc[0:2]['open'] * multipl
2021-12-11 08:49:48 +00:00
))
else:
freqtrade._schedule.run_pending()
# Funding fees for 00:00 and 08:00
for trade in trades:
2021-12-11 08:49:48 +00:00
assert trade.funding_fees == pytest.approx(sum(
trade.amount *
2022-01-18 06:40:09 +00:00
mark_prices[trade.pair].iloc[0:2]['open'] *
funding_rates[trade.pair].iloc[0:2]['open'] *
multipl
2021-12-31 12:49:21 +00:00
))
2022-01-22 16:25:21 +00:00
def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
2021-12-19 10:27:17 +00:00
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_wallet(mocker, free=10000)
default_conf_usdt.update({
"position_adjustment_enable": True,
"dry_run": False,
"stake_amount": 10.0,
"dry_run_wallet": 1000.0,
})
2021-12-19 10:27:17 +00:00
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
bid = 11
stake_amount = 10
buy_rate_mock = MagicMock(return_value=bid)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_rate=buy_rate_mock,
fetch_ticker=MagicMock(return_value={
'bid': 10,
'ask': 12,
'last': 11
}),
get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee,
)
pair = 'ETH/USDT'
# Initial buy
closed_successful_buy_order = {
'pair': pair,
'ft_pair': pair,
'ft_order_side': 'buy',
'side': 'buy',
'type': 'limit',
'status': 'closed',
'price': bid,
'average': bid,
'cost': bid * stake_amount,
'amount': stake_amount,
'filled': stake_amount,
'ft_is_open': False,
'id': '650',
'order_id': '650'
}
2021-12-19 10:27:17 +00:00
mocker.patch('freqtrade.exchange.Exchange.create_order',
MagicMock(return_value=closed_successful_buy_order))
2021-12-19 10:27:17 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
MagicMock(return_value=closed_successful_buy_order))
2021-12-19 10:27:17 +00:00
assert freqtrade.execute_entry(pair, stake_amount)
# Should create an closed trade with an no open order id
# Order is filled and trade is open
orders = Order.query.all()
assert orders
assert len(orders) == 1
trade = Trade.query.first()
assert trade
assert trade.is_open is True
assert trade.open_order_id is None
assert trade.open_rate == 11
assert trade.stake_amount == 110
# Assume it does nothing since order is closed and trade is open
2021-12-19 10:27:17 +00:00
freqtrade.update_closed_trades_without_assigned_fees()
trade = Trade.query.first()
assert trade
assert trade.is_open is True
assert trade.open_order_id is None
assert trade.open_rate == 11
assert trade.stake_amount == 110
assert not trade.fee_updated('buy')
freqtrade.check_handle_timedout()
trade = Trade.query.first()
assert trade
assert trade.is_open is True
assert trade.open_order_id is None
assert trade.open_rate == 11
assert trade.stake_amount == 110
assert not trade.fee_updated('buy')
2021-12-19 10:27:17 +00:00
# First position adjustment buy.
open_dca_order_1 = {
'ft_pair': pair,
'ft_order_side': 'buy',
'side': 'buy',
'type': 'limit',
'status': None,
'price': 9,
'amount': 12,
'cost': 100,
'ft_is_open': True,
'id': '651',
'order_id': '651'
}
2021-12-19 10:27:17 +00:00
mocker.patch('freqtrade.exchange.Exchange.create_order',
MagicMock(return_value=open_dca_order_1))
2021-12-19 10:27:17 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
MagicMock(return_value=open_dca_order_1))
2021-12-19 10:27:17 +00:00
assert freqtrade.execute_entry(pair, stake_amount, trade=trade)
orders = Order.query.all()
assert orders
assert len(orders) == 2
trade = Trade.query.first()
assert trade
assert trade.open_order_id == '651'
assert trade.open_rate == 11
assert trade.amount == 10
assert trade.stake_amount == 110
assert not trade.fee_updated('buy')
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
assert len(trades) == 1
assert trade.is_open
assert not trade.fee_updated('buy')
order = trade.select_order('buy', False)
assert order
assert order.order_id == '650'
2021-12-19 10:27:17 +00:00
2021-12-26 14:29:10 +00:00
def make_sure_its_651(*args, **kwargs):
2021-12-26 14:29:10 +00:00
if args[0] == '650':
return closed_successful_buy_order
2021-12-26 14:29:10 +00:00
if args[0] == '651':
return open_dca_order_1
return None
# Assume it does nothing since order is still open
fetch_order_mm = MagicMock(side_effect=make_sure_its_651)
mocker.patch('freqtrade.exchange.Exchange.create_order', fetch_order_mm)
mocker.patch('freqtrade.exchange.Exchange.fetch_order', fetch_order_mm)
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', fetch_order_mm)
2021-12-19 10:27:17 +00:00
freqtrade.update_closed_trades_without_assigned_fees()
orders = Order.query.all()
assert orders
assert len(orders) == 2
# Assert that the trade is found as open and without fees
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
assert len(trades) == 1
# Assert trade is as expected
trade = Trade.query.first()
assert trade
assert trade.open_order_id == '651'
assert trade.open_rate == 11
assert trade.amount == 10
assert trade.stake_amount == 110
assert not trade.fee_updated('buy')
2021-12-19 10:27:17 +00:00
# Make sure the closed order is found as the first order.
order = trade.select_order('buy', False)
assert order.order_id == '650'
# Now close the order so it should update.
closed_dca_order_1 = {
'ft_pair': pair,
'ft_order_side': 'buy',
'side': 'buy',
'type': 'limit',
'status': 'closed',
'price': 9,
'average': 9,
'amount': 12,
'filled': 12,
'cost': 108,
'ft_is_open': False,
'id': '651',
2022-01-22 16:25:21 +00:00
'order_id': '651',
'datetime': arrow.utcnow().isoformat(),
}
mocker.patch('freqtrade.exchange.Exchange.create_order',
MagicMock(return_value=closed_dca_order_1))
2021-12-19 10:27:17 +00:00
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
MagicMock(return_value=closed_dca_order_1))
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
MagicMock(return_value=closed_dca_order_1))
2021-12-19 10:27:17 +00:00
freqtrade.check_handle_timedout()
# Assert trade is as expected (averaged dca)
trade = Trade.query.first()
assert trade
assert trade.open_order_id is None
assert pytest.approx(trade.open_rate) == 9.90909090909
assert trade.amount == 22
assert trade.stake_amount == 218
orders = Order.query.all()
assert orders
assert len(orders) == 2
# Make sure the closed order is found as the second order.
order = trade.select_order('buy', False)
assert order.order_id == '651'
# Assert that the trade is not found as open and without fees
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
assert len(trades) == 1
# Add a second DCA
closed_dca_order_2 = {
'ft_pair': pair,
'status': 'closed',
'ft_order_side': 'buy',
'side': 'buy',
'type': 'limit',
'price': 7,
'average': 7,
'amount': 15,
'filled': 15,
'cost': 105,
'ft_is_open': False,
'id': '652',
'order_id': '652'
}
mocker.patch('freqtrade.exchange.Exchange.create_order',
MagicMock(return_value=closed_dca_order_2))
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
MagicMock(return_value=closed_dca_order_2))
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
MagicMock(return_value=closed_dca_order_2))
assert freqtrade.execute_entry(pair, stake_amount, trade=trade)
# Assert trade is as expected (averaged dca)
trade = Trade.query.first()
assert trade
assert trade.open_order_id is None
assert pytest.approx(trade.open_rate) == 8.729729729729
assert trade.amount == 37
assert trade.stake_amount == 323
orders = Order.query.all()
assert orders
assert len(orders) == 3
# Make sure the closed order is found as the second order.
order = trade.select_order('buy', False)
assert order.order_id == '652'
def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, caplog) -> None:
default_conf_usdt.update({
"position_adjustment_enable": True,
})
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.check_and_call_adjust_trade_position',
side_effect=DependencyException())
create_mock_trades(fee)
freqtrade.process_open_trade_positions()
assert log_has_re(r"Unable to adjust position of trade for .*", caplog)
def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, caplog) -> None:
default_conf_usdt.update({
"position_adjustment_enable": True,
"max_entry_position_adjustment": 0,
})
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
create_mock_trades(fee)
caplog.set_level(logging.DEBUG)
freqtrade.process_open_trade_positions()
assert log_has_re(r"Max adjustment entries for .* has been reached\.", caplog)