Merge branch 'develop' into pr/jpribyl/3210-1
This commit is contained in:
@@ -10,11 +10,13 @@ from freqtrade.commands import (start_convert_data, start_create_userdir,
|
||||
start_list_hyperopts, start_list_markets,
|
||||
start_list_strategies, start_list_timeframes,
|
||||
start_new_hyperopt, start_new_strategy,
|
||||
start_test_pairlist, start_trading)
|
||||
start_show_trades, start_test_pairlist,
|
||||
start_trading)
|
||||
from freqtrade.configuration import setup_utils_configuration
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.state import RunMode
|
||||
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||
from tests.conftest import (create_mock_trades, get_args, log_has, log_has_re,
|
||||
patch_exchange,
|
||||
patched_configuration_load_config_file)
|
||||
|
||||
|
||||
@@ -30,7 +32,7 @@ def test_setup_utils_configuration():
|
||||
assert config['exchange']['secret'] == ''
|
||||
|
||||
|
||||
def test_start_trading_fail(mocker):
|
||||
def test_start_trading_fail(mocker, caplog):
|
||||
|
||||
mocker.patch("freqtrade.worker.Worker.run", MagicMock(side_effect=OperationalException))
|
||||
|
||||
@@ -41,16 +43,15 @@ def test_start_trading_fail(mocker):
|
||||
'trade',
|
||||
'-c', 'config.json.example'
|
||||
]
|
||||
with pytest.raises(OperationalException):
|
||||
start_trading(get_args(args))
|
||||
start_trading(get_args(args))
|
||||
assert exitmock.call_count == 1
|
||||
|
||||
exitmock.reset_mock()
|
||||
|
||||
caplog.clear()
|
||||
mocker.patch("freqtrade.worker.Worker.__init__", MagicMock(side_effect=OperationalException))
|
||||
with pytest.raises(OperationalException):
|
||||
start_trading(get_args(args))
|
||||
start_trading(get_args(args))
|
||||
assert exitmock.call_count == 0
|
||||
assert log_has('Fatal exception!', caplog)
|
||||
|
||||
|
||||
def test_list_exchanges(capsys):
|
||||
@@ -1040,3 +1041,46 @@ def test_convert_data_trades(mocker, testdatadir):
|
||||
assert trades_mock.call_args[1]['convert_from'] == 'jsongz'
|
||||
assert trades_mock.call_args[1]['convert_to'] == 'json'
|
||||
assert trades_mock.call_args[1]['erase'] is False
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_show_trades(mocker, fee, capsys, caplog):
|
||||
mocker.patch("freqtrade.persistence.init")
|
||||
create_mock_trades(fee)
|
||||
args = [
|
||||
"show-trades",
|
||||
"--db-url",
|
||||
"sqlite:///"
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_show_trades(pargs)
|
||||
assert log_has("Printing 3 Trades: ", caplog)
|
||||
captured = capsys.readouterr()
|
||||
assert "Trade(id=1" in captured.out
|
||||
assert "Trade(id=2" in captured.out
|
||||
assert "Trade(id=3" in captured.out
|
||||
args = [
|
||||
"show-trades",
|
||||
"--db-url",
|
||||
"sqlite:///",
|
||||
"--print-json",
|
||||
"--trade-ids", "1", "2"
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
start_show_trades(pargs)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert log_has("Printing 2 Trades: ", caplog)
|
||||
assert '"trade_id": 1' in captured.out
|
||||
assert '"trade_id": 2' in captured.out
|
||||
assert '"trade_id": 3' not in captured.out
|
||||
args = [
|
||||
"show-trades",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
|
||||
with pytest.raises(OperationalException, match=r"--db-url is required for this command."):
|
||||
start_show_trades(pargs)
|
||||
|
@@ -305,7 +305,8 @@ def default_conf(testdatadir):
|
||||
"user_data_dir": Path("user_data"),
|
||||
"verbosity": 3,
|
||||
"strategy_path": str(Path(__file__).parent / "strategy" / "strats"),
|
||||
"strategy": "DefaultStrategy"
|
||||
"strategy": "DefaultStrategy",
|
||||
"internals": {},
|
||||
}
|
||||
return configuration
|
||||
|
||||
@@ -780,7 +781,7 @@ def limit_buy_order():
|
||||
'id': 'mocked_limit_buy',
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'pair': 'mocked',
|
||||
'symbol': 'mocked',
|
||||
'datetime': arrow.utcnow().isoformat(),
|
||||
'price': 0.00001099,
|
||||
'amount': 90.99181073,
|
||||
@@ -796,7 +797,7 @@ def market_buy_order():
|
||||
'id': 'mocked_market_buy',
|
||||
'type': 'market',
|
||||
'side': 'buy',
|
||||
'pair': 'mocked',
|
||||
'symbol': 'mocked',
|
||||
'datetime': arrow.utcnow().isoformat(),
|
||||
'price': 0.00004099,
|
||||
'amount': 91.99181073,
|
||||
@@ -812,7 +813,7 @@ def market_sell_order():
|
||||
'id': 'mocked_limit_sell',
|
||||
'type': 'market',
|
||||
'side': 'sell',
|
||||
'pair': 'mocked',
|
||||
'symbol': 'mocked',
|
||||
'datetime': arrow.utcnow().isoformat(),
|
||||
'price': 0.00004173,
|
||||
'amount': 91.99181073,
|
||||
@@ -828,7 +829,7 @@ def limit_buy_order_old():
|
||||
'id': 'mocked_limit_buy_old',
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'pair': 'mocked',
|
||||
'symbol': 'mocked',
|
||||
'datetime': str(arrow.utcnow().shift(minutes=-601).datetime),
|
||||
'price': 0.00001099,
|
||||
'amount': 90.99181073,
|
||||
@@ -844,7 +845,7 @@ def limit_sell_order_old():
|
||||
'id': 'mocked_limit_sell_old',
|
||||
'type': 'limit',
|
||||
'side': 'sell',
|
||||
'pair': 'ETH/BTC',
|
||||
'symbol': 'ETH/BTC',
|
||||
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
|
||||
'price': 0.00001099,
|
||||
'amount': 90.99181073,
|
||||
@@ -860,7 +861,7 @@ def limit_buy_order_old_partial():
|
||||
'id': 'mocked_limit_buy_old_partial',
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'pair': 'ETH/BTC',
|
||||
'symbol': 'ETH/BTC',
|
||||
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
|
||||
'price': 0.00001099,
|
||||
'amount': 90.99181073,
|
||||
@@ -874,10 +875,103 @@ def limit_buy_order_old_partial():
|
||||
def limit_buy_order_old_partial_canceled(limit_buy_order_old_partial):
|
||||
res = deepcopy(limit_buy_order_old_partial)
|
||||
res['status'] = 'canceled'
|
||||
res['fee'] = {'cost': 0.0001, 'currency': 'ETH'}
|
||||
res['fee'] = {'cost': 0.023, 'currency': 'ETH'}
|
||||
return res
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def limit_buy_order_canceled_empty(request):
|
||||
# Indirect fixture
|
||||
# Documentation:
|
||||
# https://docs.pytest.org/en/latest/example/parametrize.html#apply-indirect-on-particular-arguments
|
||||
|
||||
exchange_name = request.param
|
||||
if exchange_name == 'ftx':
|
||||
return {
|
||||
'info': {},
|
||||
'id': '1234512345',
|
||||
'clientOrderId': None,
|
||||
'timestamp': arrow.utcnow().shift(minutes=-601).timestamp,
|
||||
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
|
||||
'lastTradeTimestamp': None,
|
||||
'symbol': 'LTC/USDT',
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'price': 34.3225,
|
||||
'amount': 0.55,
|
||||
'cost': 0.0,
|
||||
'average': None,
|
||||
'filled': 0.0,
|
||||
'remaining': 0.0,
|
||||
'status': 'closed',
|
||||
'fee': None,
|
||||
'trades': None
|
||||
}
|
||||
elif exchange_name == 'kraken':
|
||||
return {
|
||||
'info': {},
|
||||
'id': 'AZNPFF-4AC4N-7MKTAT',
|
||||
'clientOrderId': None,
|
||||
'timestamp': arrow.utcnow().shift(minutes=-601).timestamp,
|
||||
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
|
||||
'lastTradeTimestamp': None,
|
||||
'status': 'canceled',
|
||||
'symbol': 'LTC/USDT',
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'price': 34.3225,
|
||||
'cost': 0.0,
|
||||
'amount': 0.55,
|
||||
'filled': 0.0,
|
||||
'average': 0.0,
|
||||
'remaining': 0.55,
|
||||
'fee': {'cost': 0.0, 'rate': None, 'currency': 'USDT'},
|
||||
'trades': []
|
||||
}
|
||||
elif exchange_name == 'binance':
|
||||
return {
|
||||
'info': {},
|
||||
'id': '1234512345',
|
||||
'clientOrderId': 'alb1234123',
|
||||
'timestamp': arrow.utcnow().shift(minutes=-601).timestamp,
|
||||
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
|
||||
'lastTradeTimestamp': None,
|
||||
'symbol': 'LTC/USDT',
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'price': 0.016804,
|
||||
'amount': 0.55,
|
||||
'cost': 0.0,
|
||||
'average': None,
|
||||
'filled': 0.0,
|
||||
'remaining': 0.55,
|
||||
'status': 'canceled',
|
||||
'fee': None,
|
||||
'trades': None
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'info': {},
|
||||
'id': '1234512345',
|
||||
'clientOrderId': 'alb1234123',
|
||||
'timestamp': arrow.utcnow().shift(minutes=-601).timestamp,
|
||||
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
|
||||
'lastTradeTimestamp': None,
|
||||
'symbol': 'LTC/USDT',
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'price': 0.016804,
|
||||
'amount': 0.55,
|
||||
'cost': 0.0,
|
||||
'average': None,
|
||||
'filled': 0.0,
|
||||
'remaining': 0.55,
|
||||
'status': 'canceled',
|
||||
'fee': None,
|
||||
'trades': None
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def limit_sell_order():
|
||||
return {
|
||||
@@ -1329,6 +1423,15 @@ def trades_for_order():
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def trades_history():
|
||||
return [[1565798399463, '126181329', None, 'buy', 0.019627, 0.04, 0.00078508],
|
||||
[1565798399629, '126181330', None, 'buy', 0.019627, 0.244, 0.004788987999999999],
|
||||
[1565798399752, '126181331', None, 'sell', 0.019626, 0.011, 0.00021588599999999999],
|
||||
[1565798399862, '126181332', None, 'sell', 0.019626, 0.011, 0.00021588599999999999],
|
||||
[1565798399872, '126181333', None, 'sell', 0.019626, 0.011, 0.00021588599999999999]]
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def fetch_trades_result():
|
||||
return [{'info': {'a': 126181329,
|
||||
'p': '0.01962700',
|
||||
'q': '0.04000000',
|
||||
@@ -1483,7 +1586,7 @@ def buy_order_fee():
|
||||
'id': 'mocked_limit_buy_old',
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'pair': 'mocked',
|
||||
'symbol': 'mocked',
|
||||
'datetime': str(arrow.utcnow().shift(minutes=-601).datetime),
|
||||
'price': 0.245441,
|
||||
'amount': 8.0,
|
||||
|
@@ -5,12 +5,10 @@ from freqtrade.configuration.timerange import TimeRange
|
||||
from freqtrade.data.converter import (convert_ohlcv_format,
|
||||
convert_trades_format,
|
||||
ohlcv_fill_up_missing_data,
|
||||
ohlcv_to_dataframe,
|
||||
trim_dataframe)
|
||||
from freqtrade.data.history import (get_timerange,
|
||||
load_data,
|
||||
load_pair_history,
|
||||
validate_backtest_data)
|
||||
ohlcv_to_dataframe, trades_dict_to_list,
|
||||
trades_remove_duplicates, trim_dataframe)
|
||||
from freqtrade.data.history import (get_timerange, load_data,
|
||||
load_pair_history, validate_backtest_data)
|
||||
from tests.conftest import log_has
|
||||
from tests.data.test_history import _backup_file, _clean_test_file
|
||||
|
||||
@@ -197,32 +195,60 @@ def test_trim_dataframe(testdatadir) -> None:
|
||||
assert all(data_modify.iloc[0] == data.iloc[25])
|
||||
|
||||
|
||||
def test_convert_trades_format(mocker, default_conf, testdatadir):
|
||||
file = testdatadir / "XRP_ETH-trades.json.gz"
|
||||
file_new = testdatadir / "XRP_ETH-trades.json"
|
||||
_backup_file(file, copy_file=True)
|
||||
default_conf['datadir'] = testdatadir
|
||||
def test_trades_remove_duplicates(trades_history):
|
||||
trades_history1 = trades_history * 3
|
||||
assert len(trades_history1) == len(trades_history) * 3
|
||||
res = trades_remove_duplicates(trades_history1)
|
||||
assert len(res) == len(trades_history)
|
||||
for i, t in enumerate(res):
|
||||
assert t == trades_history[i]
|
||||
|
||||
assert not file_new.exists()
|
||||
|
||||
def test_trades_dict_to_list(fetch_trades_result):
|
||||
res = trades_dict_to_list(fetch_trades_result)
|
||||
assert isinstance(res, list)
|
||||
assert isinstance(res[0], list)
|
||||
for i, t in enumerate(res):
|
||||
assert t[0] == fetch_trades_result[i]['timestamp']
|
||||
assert t[1] == fetch_trades_result[i]['id']
|
||||
assert t[2] == fetch_trades_result[i]['type']
|
||||
assert t[3] == fetch_trades_result[i]['side']
|
||||
assert t[4] == fetch_trades_result[i]['price']
|
||||
assert t[5] == fetch_trades_result[i]['amount']
|
||||
assert t[6] == fetch_trades_result[i]['cost']
|
||||
|
||||
|
||||
def test_convert_trades_format(mocker, default_conf, testdatadir):
|
||||
files = [{'old': testdatadir / "XRP_ETH-trades.json.gz",
|
||||
'new': testdatadir / "XRP_ETH-trades.json"},
|
||||
{'old': testdatadir / "XRP_OLD-trades.json.gz",
|
||||
'new': testdatadir / "XRP_OLD-trades.json"},
|
||||
]
|
||||
for file in files:
|
||||
_backup_file(file['old'], copy_file=True)
|
||||
assert not file['new'].exists()
|
||||
|
||||
default_conf['datadir'] = testdatadir
|
||||
|
||||
convert_trades_format(default_conf, convert_from='jsongz',
|
||||
convert_to='json', erase=False)
|
||||
|
||||
assert file_new.exists()
|
||||
assert file.exists()
|
||||
for file in files:
|
||||
assert file['new'].exists()
|
||||
assert file['old'].exists()
|
||||
|
||||
# Remove original file
|
||||
file.unlink()
|
||||
# Remove original file
|
||||
file['old'].unlink()
|
||||
# Convert back
|
||||
convert_trades_format(default_conf, convert_from='json',
|
||||
convert_to='jsongz', erase=True)
|
||||
for file in files:
|
||||
assert file['old'].exists()
|
||||
assert not file['new'].exists()
|
||||
|
||||
assert file.exists()
|
||||
assert not file_new.exists()
|
||||
|
||||
_clean_test_file(file)
|
||||
if file_new.exists():
|
||||
file_new.unlink()
|
||||
_clean_test_file(file['old'])
|
||||
if file['new'].exists():
|
||||
file['new'].unlink()
|
||||
|
||||
|
||||
def test_convert_ohlcv_format(mocker, default_conf, testdatadir):
|
||||
|
@@ -1,8 +1,11 @@
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from pandas import DataFrame
|
||||
import pytest
|
||||
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.pairlist.pairlistmanager import PairListManager
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.state import RunMode
|
||||
from tests.conftest import get_patched_exchange
|
||||
|
||||
@@ -64,8 +67,8 @@ def test_get_pair_dataframe(mocker, default_conf, ohlcv_history):
|
||||
assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty
|
||||
|
||||
# Test with and without parameter
|
||||
assert dp.get_pair_dataframe("UNITTEST/BTC",
|
||||
ticker_interval).equals(dp.get_pair_dataframe("UNITTEST/BTC"))
|
||||
assert dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval)\
|
||||
.equals(dp.get_pair_dataframe("UNITTEST/BTC"))
|
||||
|
||||
default_conf["runmode"] = RunMode.LIVE
|
||||
dp = DataProvider(default_conf, exchange)
|
||||
@@ -90,10 +93,7 @@ def test_available_pairs(mocker, default_conf, ohlcv_history):
|
||||
|
||||
dp = DataProvider(default_conf, exchange)
|
||||
assert len(dp.available_pairs) == 2
|
||||
assert dp.available_pairs == [
|
||||
("XRP/BTC", ticker_interval),
|
||||
("UNITTEST/BTC", ticker_interval),
|
||||
]
|
||||
assert dp.available_pairs == [("XRP/BTC", ticker_interval), ("UNITTEST/BTC", ticker_interval), ]
|
||||
|
||||
|
||||
def test_refresh(mocker, default_conf, ohlcv_history):
|
||||
@@ -152,3 +152,45 @@ def test_market(mocker, default_conf, markets):
|
||||
|
||||
res = dp.market('UNITTEST/BTC')
|
||||
assert res is None
|
||||
|
||||
|
||||
def test_ticker(mocker, default_conf, tickers):
|
||||
ticker_mock = MagicMock(return_value=tickers()['ETH/BTC'])
|
||||
mocker.patch("freqtrade.exchange.Exchange.fetch_ticker", ticker_mock)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
dp = DataProvider(default_conf, exchange)
|
||||
res = dp.ticker('ETH/BTC')
|
||||
assert type(res) is dict
|
||||
assert 'symbol' in res
|
||||
assert res['symbol'] == 'ETH/BTC'
|
||||
|
||||
ticker_mock = MagicMock(side_effect=DependencyException('Pair not found'))
|
||||
mocker.patch("freqtrade.exchange.Exchange.fetch_ticker", ticker_mock)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
dp = DataProvider(default_conf, exchange)
|
||||
res = dp.ticker('UNITTEST/BTC')
|
||||
assert res == {}
|
||||
|
||||
|
||||
def test_current_whitelist(mocker, default_conf, tickers):
|
||||
# patch default conf to volumepairlist
|
||||
default_conf['pairlists'][0] = {'method': 'VolumePairList', "number_assets": 5}
|
||||
|
||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
get_tickers=tickers)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
|
||||
pairlist = PairListManager(exchange, default_conf)
|
||||
dp = DataProvider(default_conf, exchange, pairlist)
|
||||
|
||||
# Simulate volumepairs from exchange.
|
||||
pairlist.refresh_pairlist()
|
||||
|
||||
assert dp.current_whitelist() == pairlist._whitelist
|
||||
# The identity of the 2 lists should be identical
|
||||
assert dp.current_whitelist() is pairlist._whitelist
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
dp = DataProvider(default_conf, exchange)
|
||||
dp.current_whitelist()
|
||||
|
@@ -547,6 +547,17 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad
|
||||
assert log_has("New Amount of trades: 5", caplog)
|
||||
assert file1.is_file()
|
||||
|
||||
ght_mock.reset_mock()
|
||||
since_time = int(trades_history[-3][0] // 1000)
|
||||
since_time2 = int(trades_history[-1][0] // 1000)
|
||||
timerange = TimeRange('date', None, since_time, 0)
|
||||
assert _download_trades_history(data_handler=data_handler, exchange=exchange,
|
||||
pair='ETH/BTC', timerange=timerange)
|
||||
|
||||
assert ght_mock.call_count == 1
|
||||
# Check this in seconds - since we had to convert to seconds above too.
|
||||
assert int(ght_mock.call_args_list[0][1]['since'] // 1000) == since_time2 - 5
|
||||
|
||||
# clean files freshly downloaded
|
||||
_clean_test_file(file1)
|
||||
|
||||
@@ -601,7 +612,7 @@ def test_jsondatahandler_ohlcv_get_pairs(testdatadir):
|
||||
def test_jsondatahandler_trades_get_pairs(testdatadir):
|
||||
pairs = JsonGzDataHandler.trades_get_pairs(testdatadir)
|
||||
# Convert to set to avoid failures due to sorting
|
||||
assert set(pairs) == {'XRP/ETH'}
|
||||
assert set(pairs) == {'XRP/ETH', 'XRP/OLD'}
|
||||
|
||||
|
||||
def test_jsondatahandler_ohlcv_purge(mocker, testdatadir):
|
||||
@@ -614,6 +625,17 @@ def test_jsondatahandler_ohlcv_purge(mocker, testdatadir):
|
||||
assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m')
|
||||
|
||||
|
||||
def test_jsondatahandler_trades_load(mocker, testdatadir, caplog):
|
||||
dh = JsonGzDataHandler(testdatadir)
|
||||
logmsg = "Old trades format detected - converting"
|
||||
dh.trades_load('XRP/ETH')
|
||||
assert not log_has(logmsg, caplog)
|
||||
|
||||
# Test conversation is happening
|
||||
dh.trades_load('XRP/OLD')
|
||||
assert log_has(logmsg, caplog)
|
||||
|
||||
|
||||
def test_jsondatahandler_trades_purge(mocker, testdatadir):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
mocker.patch.object(Path, "unlink", MagicMock())
|
||||
|
@@ -335,12 +335,16 @@ def test_edge_init_error(mocker, edge_conf,):
|
||||
get_patched_freqtradebot(mocker, edge_conf)
|
||||
|
||||
|
||||
def test_process_expectancy(mocker, edge_conf):
|
||||
@pytest.mark.parametrize("fee,risk_reward_ratio,expectancy", [
|
||||
(0.0005, 306.5384615384, 101.5128205128),
|
||||
(0.001, 152.6923076923, 50.2307692308),
|
||||
])
|
||||
def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectancy):
|
||||
edge_conf['edge']['min_trade_number'] = 2
|
||||
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||
|
||||
def get_fee(*args, **kwargs):
|
||||
return 0.001
|
||||
return fee
|
||||
|
||||
freqtrade.exchange.get_fee = get_fee
|
||||
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||
@@ -394,9 +398,9 @@ def test_process_expectancy(mocker, edge_conf):
|
||||
assert 'TEST/BTC' in final
|
||||
assert final['TEST/BTC'].stoploss == -0.9
|
||||
assert round(final['TEST/BTC'].winrate, 10) == 0.3333333333
|
||||
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == 306.5384615384
|
||||
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == risk_reward_ratio
|
||||
assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0
|
||||
assert round(final['TEST/BTC'].expectancy, 10) == 101.5128205128
|
||||
assert round(final['TEST/BTC'].expectancy, 10) == expectancy
|
||||
|
||||
# Pop last item so no trade is profitable
|
||||
trades.pop()
|
||||
|
@@ -9,7 +9,12 @@ from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
||||
from tests.conftest import get_patched_exchange
|
||||
|
||||
|
||||
def test_stoploss_order_binance(default_conf, mocker):
|
||||
@pytest.mark.parametrize('limitratio,expected', [
|
||||
(None, 220 * 0.99),
|
||||
(0.99, 220 * 0.99),
|
||||
(0.98, 220 * 0.98),
|
||||
])
|
||||
def test_stoploss_order_binance(default_conf, mocker, limitratio, expected):
|
||||
api_mock = MagicMock()
|
||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||
order_type = 'stop_loss_limit'
|
||||
@@ -20,7 +25,6 @@ def test_stoploss_order_binance(default_conf, mocker):
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
|
||||
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
|
||||
@@ -32,8 +36,8 @@ def test_stoploss_order_binance(default_conf, mocker):
|
||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
||||
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
|
||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
@@ -42,7 +46,8 @@ def test_stoploss_order_binance(default_conf, mocker):
|
||||
assert api_mock.create_order.call_args_list[0][1]['type'] == order_type
|
||||
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
|
||||
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
|
||||
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
|
||||
# Price should be 1% below stopprice
|
||||
assert api_mock.create_order.call_args_list[0][1]['price'] == expected
|
||||
assert api_mock.create_order.call_args_list[0][1]['params'] == {'stopPrice': 220}
|
||||
|
||||
# test exception handling
|
||||
|
@@ -1537,18 +1537,18 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
|
||||
trades_history):
|
||||
fetch_trades_result):
|
||||
|
||||
caplog.set_level(logging.DEBUG)
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
# Monkey-patch async function
|
||||
exchange._api_async.fetch_trades = get_mock_coro(trades_history)
|
||||
exchange._api_async.fetch_trades = get_mock_coro(fetch_trades_result)
|
||||
|
||||
pair = 'ETH/BTC'
|
||||
res = await exchange._async_fetch_trades(pair, since=None, params=None)
|
||||
assert type(res) is list
|
||||
assert isinstance(res[0], dict)
|
||||
assert isinstance(res[1], dict)
|
||||
assert isinstance(res[0], list)
|
||||
assert isinstance(res[1], list)
|
||||
|
||||
assert exchange._api_async.fetch_trades.call_count == 1
|
||||
assert exchange._api_async.fetch_trades.call_args[0][0] == pair
|
||||
@@ -1594,7 +1594,7 @@ async def test__async_get_trade_history_id(default_conf, mocker, caplog, exchang
|
||||
if 'since' in kwargs:
|
||||
# Return first 3
|
||||
return trades_history[:-2]
|
||||
elif kwargs.get('params', {}).get(pagination_arg) == trades_history[-3]['id']:
|
||||
elif kwargs.get('params', {}).get(pagination_arg) == trades_history[-3][1]:
|
||||
# Return 2
|
||||
return trades_history[-3:-1]
|
||||
else:
|
||||
@@ -1604,8 +1604,8 @@ async def test__async_get_trade_history_id(default_conf, mocker, caplog, exchang
|
||||
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
|
||||
|
||||
pair = 'ETH/BTC'
|
||||
ret = await exchange._async_get_trade_history_id(pair, since=trades_history[0]["timestamp"],
|
||||
until=trades_history[-1]["timestamp"]-1)
|
||||
ret = await exchange._async_get_trade_history_id(pair, since=trades_history[0][0],
|
||||
until=trades_history[-1][0]-1)
|
||||
assert type(ret) is tuple
|
||||
assert ret[0] == pair
|
||||
assert type(ret[1]) is list
|
||||
@@ -1614,7 +1614,7 @@ async def test__async_get_trade_history_id(default_conf, mocker, caplog, exchang
|
||||
fetch_trades_cal = exchange._async_fetch_trades.call_args_list
|
||||
# first call (using since, not fromId)
|
||||
assert fetch_trades_cal[0][0][0] == pair
|
||||
assert fetch_trades_cal[0][1]['since'] == trades_history[0]["timestamp"]
|
||||
assert fetch_trades_cal[0][1]['since'] == trades_history[0][0]
|
||||
|
||||
# 2nd call
|
||||
assert fetch_trades_cal[1][0][0] == pair
|
||||
@@ -1630,7 +1630,7 @@ async def test__async_get_trade_history_time(default_conf, mocker, caplog, excha
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
async def mock_get_trade_hist(pair, *args, **kwargs):
|
||||
if kwargs['since'] == trades_history[0]["timestamp"]:
|
||||
if kwargs['since'] == trades_history[0][0]:
|
||||
return trades_history[:-1]
|
||||
else:
|
||||
return trades_history[-1:]
|
||||
@@ -1640,8 +1640,8 @@ async def test__async_get_trade_history_time(default_conf, mocker, caplog, excha
|
||||
# Monkey-patch async function
|
||||
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
|
||||
pair = 'ETH/BTC'
|
||||
ret = await exchange._async_get_trade_history_time(pair, since=trades_history[0]["timestamp"],
|
||||
until=trades_history[-1]["timestamp"]-1)
|
||||
ret = await exchange._async_get_trade_history_time(pair, since=trades_history[0][0],
|
||||
until=trades_history[-1][0]-1)
|
||||
assert type(ret) is tuple
|
||||
assert ret[0] == pair
|
||||
assert type(ret[1]) is list
|
||||
@@ -1650,11 +1650,11 @@ async def test__async_get_trade_history_time(default_conf, mocker, caplog, excha
|
||||
fetch_trades_cal = exchange._async_fetch_trades.call_args_list
|
||||
# first call (using since, not fromId)
|
||||
assert fetch_trades_cal[0][0][0] == pair
|
||||
assert fetch_trades_cal[0][1]['since'] == trades_history[0]["timestamp"]
|
||||
assert fetch_trades_cal[0][1]['since'] == trades_history[0][0]
|
||||
|
||||
# 2nd call
|
||||
assert fetch_trades_cal[1][0][0] == pair
|
||||
assert fetch_trades_cal[0][1]['since'] == trades_history[0]["timestamp"]
|
||||
assert fetch_trades_cal[0][1]['since'] == trades_history[0][0]
|
||||
assert log_has_re(r"Stopping because until was reached.*", caplog)
|
||||
|
||||
|
||||
@@ -1666,7 +1666,7 @@ async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog,
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
async def mock_get_trade_hist(pair, *args, **kwargs):
|
||||
if kwargs['since'] == trades_history[0]["timestamp"]:
|
||||
if kwargs['since'] == trades_history[0][0]:
|
||||
return trades_history[:-1]
|
||||
else:
|
||||
return []
|
||||
@@ -1676,8 +1676,8 @@ async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog,
|
||||
# Monkey-patch async function
|
||||
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
|
||||
pair = 'ETH/BTC'
|
||||
ret = await exchange._async_get_trade_history_time(pair, since=trades_history[0]["timestamp"],
|
||||
until=trades_history[-1]["timestamp"]-1)
|
||||
ret = await exchange._async_get_trade_history_time(pair, since=trades_history[0][0],
|
||||
until=trades_history[-1][0]-1)
|
||||
assert type(ret) is tuple
|
||||
assert ret[0] == pair
|
||||
assert type(ret[1]) is list
|
||||
@@ -1686,7 +1686,7 @@ async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog,
|
||||
fetch_trades_cal = exchange._async_fetch_trades.call_args_list
|
||||
# first call (using since, not fromId)
|
||||
assert fetch_trades_cal[0][0][0] == pair
|
||||
assert fetch_trades_cal[0][1]['since'] == trades_history[0]["timestamp"]
|
||||
assert fetch_trades_cal[0][1]['since'] == trades_history[0][0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
@@ -1698,8 +1698,8 @@ def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades
|
||||
|
||||
exchange._async_get_trade_history_id = get_mock_coro((pair, trades_history))
|
||||
exchange._async_get_trade_history_time = get_mock_coro((pair, trades_history))
|
||||
ret = exchange.get_historic_trades(pair, since=trades_history[0]["timestamp"],
|
||||
until=trades_history[-1]["timestamp"])
|
||||
ret = exchange.get_historic_trades(pair, since=trades_history[0][0],
|
||||
until=trades_history[-1][0])
|
||||
|
||||
# Depending on the exchange, one or the other method should be called
|
||||
assert sum([exchange._async_get_trade_history_id.call_count,
|
||||
@@ -1720,8 +1720,8 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match="This exchange does not suport downloading Trades."):
|
||||
exchange.get_historic_trades(pair, since=trades_history[0]["timestamp"],
|
||||
until=trades_history[-1]["timestamp"])
|
||||
exchange.get_historic_trades(pair, since=trades_history[0][0],
|
||||
until=trades_history[-1][0])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
@@ -2145,3 +2145,58 @@ def test_symbol_is_pair(market_symbol, base_currency, quote_currency, expected_r
|
||||
])
|
||||
def test_market_is_active(market, expected_result) -> None:
|
||||
assert market_is_active(market) == expected_result
|
||||
|
||||
|
||||
@pytest.mark.parametrize("order,expected", [
|
||||
([{'fee'}], False),
|
||||
({'fee': None}, False),
|
||||
({'fee': {'currency': 'ETH/BTC'}}, False),
|
||||
({'fee': {'currency': 'ETH/BTC', 'cost': None}}, False),
|
||||
({'fee': {'currency': 'ETH/BTC', 'cost': 0.01}}, True),
|
||||
])
|
||||
def test_order_has_fee(order, expected) -> None:
|
||||
assert Exchange.order_has_fee(order) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("order,expected", [
|
||||
({'symbol': 'ETH/BTC', 'fee': {'currency': 'ETH', 'cost': 0.43}},
|
||||
(0.43, 'ETH', 0.01)),
|
||||
({'symbol': 'ETH/USDT', 'fee': {'currency': 'USDT', 'cost': 0.01}},
|
||||
(0.01, 'USDT', 0.01)),
|
||||
({'symbol': 'BTC/USDT', 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}},
|
||||
(0.34, 'USDT', 0.01)),
|
||||
])
|
||||
def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.calculate_fee_rate', MagicMock(return_value=0.01))
|
||||
ex = get_patched_exchange(mocker, default_conf)
|
||||
assert ex.extract_cost_curr_rate(order) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("order,expected", [
|
||||
# Using base-currency
|
||||
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
|
||||
'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.1),
|
||||
({'symbol': 'ETH/BTC', 'amount': 0.05, 'cost': 0.05,
|
||||
'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.08),
|
||||
# Using quote currency
|
||||
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
|
||||
'fee': {'currency': 'BTC', 'cost': 0.005}}, 0.1),
|
||||
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
|
||||
'fee': {'currency': 'BTC', 'cost': 0.002, 'rate': None}}, 0.04),
|
||||
# Using foreign currency
|
||||
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
|
||||
'fee': {'currency': 'NEO', 'cost': 0.0012}}, 0.001944),
|
||||
({'symbol': 'ETH/BTC', 'amount': 2.21, 'cost': 0.02992561,
|
||||
'fee': {'currency': 'NEO', 'cost': 0.00027452}}, 0.00074305),
|
||||
# TODO: More tests here!
|
||||
# Rate included in return - return as is
|
||||
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
|
||||
'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}}, 0.01),
|
||||
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
|
||||
'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.005}}, 0.005),
|
||||
])
|
||||
def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081})
|
||||
|
||||
ex = get_patched_exchange(mocker, default_conf)
|
||||
assert ex.calculate_fee_rate(order) == expected
|
||||
|
@@ -649,6 +649,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||
assert log_has(line, caplog)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
|
||||
patch_exchange(mocker)
|
||||
|
@@ -51,7 +51,11 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'open_date_hum': ANY,
|
||||
'is_open': ANY,
|
||||
'fee_open': ANY,
|
||||
'fee_open_cost': ANY,
|
||||
'fee_open_currency': ANY,
|
||||
'fee_close': ANY,
|
||||
'fee_close_cost': ANY,
|
||||
'fee_close_currency': ANY,
|
||||
'open_rate_requested': ANY,
|
||||
'open_trade_price': ANY,
|
||||
'close_rate_requested': ANY,
|
||||
@@ -90,7 +94,11 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'open_date_hum': ANY,
|
||||
'is_open': ANY,
|
||||
'fee_open': ANY,
|
||||
'fee_open_cost': ANY,
|
||||
'fee_open_currency': ANY,
|
||||
'fee_close': ANY,
|
||||
'fee_close_cost': ANY,
|
||||
'fee_close_currency': ANY,
|
||||
'open_rate_requested': ANY,
|
||||
'open_trade_price': ANY,
|
||||
'close_rate_requested': ANY,
|
||||
|
@@ -49,6 +49,7 @@ def client_get(client, url):
|
||||
def assert_response(response, expected_code=200):
|
||||
assert response.status_code == expected_code
|
||||
assert response.content_type == "application/json"
|
||||
assert ('Access-Control-Allow-Origin', '*') in response.headers._list
|
||||
|
||||
|
||||
def test_api_not_found(botclient):
|
||||
@@ -94,6 +95,33 @@ def test_api_unauthorized(botclient):
|
||||
assert rc.json == {'error': 'Unauthorized'}
|
||||
|
||||
|
||||
def test_api_token_login(botclient):
|
||||
ftbot, client = botclient
|
||||
rc = client_post(client, f"{BASE_URI}/token/login")
|
||||
assert_response(rc)
|
||||
assert 'access_token' in rc.json
|
||||
assert 'refresh_token' in rc.json
|
||||
|
||||
# test Authentication is working with JWT tokens too
|
||||
rc = client.get(f"{BASE_URI}/count",
|
||||
content_type="application/json",
|
||||
headers={'Authorization': f'Bearer {rc.json["access_token"]}'})
|
||||
assert_response(rc)
|
||||
|
||||
|
||||
def test_api_token_refresh(botclient):
|
||||
ftbot, client = botclient
|
||||
rc = client_post(client, f"{BASE_URI}/token/login")
|
||||
assert_response(rc)
|
||||
rc = client.post(f"{BASE_URI}/token/refresh",
|
||||
content_type="application/json",
|
||||
data=None,
|
||||
headers={'Authorization': f'Bearer {rc.json["refresh_token"]}'})
|
||||
assert_response(rc)
|
||||
assert 'access_token' in rc.json
|
||||
assert 'refresh_token' not in rc.json
|
||||
|
||||
|
||||
def test_api_stop_workflow(botclient):
|
||||
ftbot, client = botclient
|
||||
assert ftbot.state == State.RUNNING
|
||||
@@ -123,6 +151,12 @@ def test_api__init__(default_conf, mocker):
|
||||
"""
|
||||
Test __init__() method
|
||||
"""
|
||||
default_conf.update({"api_server": {"enabled": True,
|
||||
"listen_ip_address": "127.0.0.1",
|
||||
"listen_port": 8080,
|
||||
"username": "TestUser",
|
||||
"password": "testPass",
|
||||
}})
|
||||
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock())
|
||||
|
||||
@@ -283,6 +317,7 @@ def test_api_show_config(botclient, mocker):
|
||||
assert 'dry_run' in rc.json
|
||||
assert rc.json['exchange'] == 'bittrex'
|
||||
assert rc.json['ticker_interval'] == '5m'
|
||||
assert rc.json['state'] == 'running'
|
||||
assert not rc.json['trailing_stop']
|
||||
|
||||
|
||||
@@ -472,7 +507,11 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
'close_rate_requested': None,
|
||||
'current_rate': 1.099e-05,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'open_date': ANY,
|
||||
'is_open': True,
|
||||
'max_rate': 0.0,
|
||||
@@ -575,7 +614,11 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
'close_profit': None,
|
||||
'close_rate_requested': None,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'is_open': False,
|
||||
'max_rate': None,
|
||||
'min_rate': None,
|
||||
|
@@ -1158,7 +1158,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
||||
'status': 'closed',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 3,
|
||||
'average': 2
|
||||
'average': 2,
|
||||
'amount': limit_buy_order['amount'],
|
||||
})
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit)
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is True
|
||||
@@ -2220,6 +2221,7 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap
|
||||
limit_buy_order_old_partial_canceled, mocker) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
|
||||
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=0))
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
@@ -2239,7 +2241,7 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap
|
||||
# and apply fees if necessary.
|
||||
freqtrade.check_handle_timedout()
|
||||
|
||||
assert log_has_re(r"Applying fee on amount for Trade.* Order", caplog)
|
||||
assert log_has_re(r"Applying fee on amount for Trade.*", caplog)
|
||||
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 2
|
||||
@@ -2247,9 +2249,10 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap
|
||||
assert len(trades) == 1
|
||||
# Verify that trade has been updated
|
||||
assert trades[0].amount == (limit_buy_order_old_partial['amount'] -
|
||||
limit_buy_order_old_partial['remaining']) - 0.0001
|
||||
limit_buy_order_old_partial['remaining']) - 0.023
|
||||
assert trades[0].open_order_id is None
|
||||
assert trades[0].fee_open == 0
|
||||
assert trades[0].fee_updated('buy')
|
||||
assert pytest.approx(trades[0].fee_open) == 0.001
|
||||
|
||||
|
||||
def test_check_handle_timedout_partial_except(default_conf, ticker, open_trade, caplog, fee,
|
||||
@@ -2320,7 +2323,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
|
||||
caplog)
|
||||
|
||||
|
||||
def test_handle_timedout_limit_buy(mocker, caplog, default_conf, limit_buy_order) -> None:
|
||||
def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_order)
|
||||
@@ -2332,28 +2335,52 @@ def test_handle_timedout_limit_buy(mocker, caplog, default_conf, limit_buy_order
|
||||
Trade.session = MagicMock()
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/ETH'
|
||||
limit_buy_order['remaining'] = limit_buy_order['amount']
|
||||
limit_buy_order['filled'] = 0.0
|
||||
limit_buy_order['status'] = 'open'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
limit_buy_order['amount'] = 2
|
||||
limit_buy_order['filled'] = 2
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
limit_buy_order['filled'] = 2
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException)
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
|
||||
indirect=['limit_buy_order_canceled_empty'])
|
||||
def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf,
|
||||
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_buy_cancel')
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
Trade.session = MagicMock()
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/ETH'
|
||||
assert freqtrade.handle_cancel_buy(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('cancelorder', [
|
||||
{},
|
||||
{'remaining': None},
|
||||
'String Return value',
|
||||
123
|
||||
])
|
||||
def test_handle_timedout_limit_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
cancelorder) -> None:
|
||||
def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
cancelorder) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock(return_value=cancelorder)
|
||||
@@ -2368,18 +2395,19 @@ def test_handle_timedout_limit_buy_corder_empty(mocker, default_conf, limit_buy_
|
||||
Trade.session = MagicMock()
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/ETH'
|
||||
limit_buy_order['remaining'] = limit_buy_order['amount']
|
||||
limit_buy_order['filled'] = 0.0
|
||||
limit_buy_order['status'] = 'open'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
limit_buy_order['amount'] = 2
|
||||
limit_buy_order['filled'] = 1.0
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
|
||||
def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
|
||||
def test_handle_cancel_sell_limit(mocker, default_conf) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
@@ -2405,6 +2433,22 @@ def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
|
||||
def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException())
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
trade = MagicMock()
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
order = {'remaining': 1,
|
||||
'amount': 1,
|
||||
'status': "open"}
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order'
|
||||
|
||||
|
||||
def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
@@ -3309,8 +3353,6 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order,
|
||||
|
||||
def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, fee, caplog, mocker):
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
amount = sum(x['amount'] for x in trades_for_order)
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
@@ -3321,21 +3363,43 @@ def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, fe
|
||||
fee_close=fee.return_value,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
# Amount is reduced by "fee"
|
||||
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, '
|
||||
'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992) from Trades',
|
||||
'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).',
|
||||
caplog)
|
||||
|
||||
|
||||
def test_get_real_amount_quote_dust(default_conf, trades_for_order, buy_order_fee, fee,
|
||||
caplog, mocker):
|
||||
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)
|
||||
|
||||
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 '
|
||||
'- Eating Fee 0.008 into dust', caplog)
|
||||
|
||||
|
||||
def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker, fee):
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
amount = buy_order_fee['amount']
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
@@ -3346,8 +3410,7 @@ def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker, f
|
||||
fee_close=fee.return_value,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
# Amount is reduced by "fee"
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
|
||||
@@ -3359,8 +3422,6 @@ def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker, f
|
||||
def test_get_real_amount_stake(default_conf, trades_for_order, buy_order_fee, fee, mocker):
|
||||
trades_for_order[0]['fee']['currency'] = 'ETH'
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
||||
amount = sum(x['amount'] for x in trades_for_order)
|
||||
trade = Trade(
|
||||
@@ -3372,8 +3433,7 @@ def test_get_real_amount_stake(default_conf, trades_for_order, buy_order_fee, fe
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
# Amount does not change
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
|
||||
@@ -3386,8 +3446,6 @@ def test_get_real_amount_no_currency_in_fee(default_conf, trades_for_order, buy_
|
||||
limit_buy_order['fee'] = {'cost': 0.004, 'currency': None}
|
||||
trades_for_order[0]['fee']['currency'] = None
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
||||
amount = sum(x['amount'] for x in trades_for_order)
|
||||
trade = Trade(
|
||||
@@ -3399,8 +3457,7 @@ def test_get_real_amount_no_currency_in_fee(default_conf, trades_for_order, buy_
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
# Amount does not change
|
||||
assert freqtrade.get_real_amount(trade, limit_buy_order) == amount
|
||||
@@ -3410,8 +3467,6 @@ def test_get_real_amount_BNB(default_conf, trades_for_order, buy_order_fee, fee,
|
||||
trades_for_order[0]['fee']['currency'] = 'BNB'
|
||||
trades_for_order[0]['fee']['cost'] = 0.00094518
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
||||
amount = sum(x['amount'] for x in trades_for_order)
|
||||
trade = Trade(
|
||||
@@ -3423,16 +3478,13 @@ def test_get_real_amount_BNB(default_conf, trades_for_order, buy_order_fee, fee,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
# Amount does not change
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
|
||||
|
||||
|
||||
def test_get_real_amount_multi(default_conf, trades_for_order2, buy_order_fee, caplog, fee, mocker):
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order2)
|
||||
amount = float(sum(x['amount'] for x in trades_for_order2))
|
||||
trade = Trade(
|
||||
@@ -3444,13 +3496,12 @@ def test_get_real_amount_multi(default_conf, trades_for_order2, buy_order_fee, c
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
# Amount is reduced by "fee"
|
||||
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, '
|
||||
'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992) from Trades',
|
||||
'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).',
|
||||
caplog)
|
||||
|
||||
|
||||
@@ -3459,8 +3510,6 @@ def test_get_real_amount_fromorder(default_conf, trades_for_order, buy_order_fee
|
||||
limit_buy_order = deepcopy(buy_order_fee)
|
||||
limit_buy_order['fee'] = {'cost': 0.004, 'currency': 'LTC'}
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
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))
|
||||
@@ -3473,13 +3522,12 @@ def test_get_real_amount_fromorder(default_conf, trades_for_order, buy_order_fee
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
# Amount is reduced by "fee"
|
||||
assert freqtrade.get_real_amount(trade, limit_buy_order) == amount - 0.004
|
||||
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
|
||||
'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.996) from Order',
|
||||
'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.996).',
|
||||
caplog)
|
||||
|
||||
|
||||
@@ -3487,8 +3535,6 @@ def test_get_real_amount_invalid_order(default_conf, trades_for_order, buy_order
|
||||
limit_buy_order = deepcopy(buy_order_fee)
|
||||
limit_buy_order['fee'] = {'cost': 0.004}
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
||||
amount = float(sum(x['amount'] for x in trades_for_order))
|
||||
trade = Trade(
|
||||
@@ -3500,8 +3546,7 @@ def test_get_real_amount_invalid_order(default_conf, trades_for_order, buy_order
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
# Amount does not change
|
||||
assert freqtrade.get_real_amount(trade, limit_buy_order) == amount
|
||||
@@ -3511,8 +3556,6 @@ def test_get_real_amount_wrong_amount(default_conf, trades_for_order, buy_order_
|
||||
limit_buy_order = deepcopy(buy_order_fee)
|
||||
limit_buy_order['amount'] = limit_buy_order['amount'] - 0.001
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
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(
|
||||
@@ -3524,8 +3567,7 @@ def test_get_real_amount_wrong_amount(default_conf, trades_for_order, buy_order_
|
||||
fee_close=fee.return_value,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
# Amount does not change
|
||||
with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"):
|
||||
@@ -3538,8 +3580,6 @@ def test_get_real_amount_wrong_amount_rounding(default_conf, trades_for_order, b
|
||||
limit_buy_order = deepcopy(buy_order_fee)
|
||||
trades_for_order[0]['amount'] = trades_for_order[0]['amount'] + 1e-15
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
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(
|
||||
@@ -3551,8 +3591,7 @@ def test_get_real_amount_wrong_amount_rounding(default_conf, trades_for_order, b
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
# Amount changes by fee amount.
|
||||
assert isclose(freqtrade.get_real_amount(trade, limit_buy_order), amount - (amount * 0.001),
|
||||
@@ -3563,8 +3602,6 @@ def test_get_real_amount_invalid(default_conf, trades_for_order, buy_order_fee,
|
||||
# Remove "Currency" from fee dict
|
||||
trades_for_order[0]['fee'] = {'cost': 0.008}
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
||||
amount = sum(x['amount'] for x in trades_for_order)
|
||||
trade = Trade(
|
||||
@@ -3577,15 +3614,12 @@ def test_get_real_amount_invalid(default_conf, trades_for_order, buy_order_fee,
|
||||
|
||||
open_order_id="123456"
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
# Amount does not change
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
|
||||
|
||||
|
||||
def test_get_real_amount_open_trade(default_conf, fee, mocker):
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
amount = 12345
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
@@ -3600,12 +3634,41 @@ def test_get_real_amount_open_trade(default_conf, fee, mocker):
|
||||
'id': 'mocked_order',
|
||||
'amount': amount,
|
||||
'status': 'open',
|
||||
'side': 'buy',
|
||||
}
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
assert freqtrade.get_real_amount(trade, order) == amount
|
||||
|
||||
|
||||
@pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [
|
||||
(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),
|
||||
])
|
||||
def test_apply_fee_conditional(default_conf, fee, caplog, mocker,
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
|
||||
def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, mocker,
|
||||
order_book_l2):
|
||||
default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True
|
||||
@@ -3899,4 +3962,3 @@ def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limi
|
||||
freqtrade.cancel_all_open_orders()
|
||||
assert buy_mock.call_count == 1
|
||||
assert sell_mock.call_count == 1
|
||||
|
||||
|
@@ -44,6 +44,8 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
}
|
||||
stoploss_order_closed = stoploss_order_open.copy()
|
||||
stoploss_order_closed['status'] = 'closed'
|
||||
stoploss_order_closed['filled'] = stoploss_order_closed['amount']
|
||||
|
||||
# Sell first trade based on stoploss, keep 2nd and 3rd trade open
|
||||
stoploss_order_mock = MagicMock(
|
||||
side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
|
||||
@@ -67,7 +69,6 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
create_stoploss_order=MagicMock(return_value=True),
|
||||
update_trade_state=MagicMock(),
|
||||
_notify_sell=MagicMock(),
|
||||
)
|
||||
mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
|
||||
@@ -97,8 +98,9 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
|
||||
# Only order for 3rd trade needs to be cancelled
|
||||
assert cancel_order_mock.call_count == 1
|
||||
# Wallets must be updated between stoploss cancellation and selling.
|
||||
assert wallets_mock.call_count == 2
|
||||
# Wallets must be updated between stoploss cancellation and selling, and will be updated again
|
||||
# during update_trade_state
|
||||
assert wallets_mock.call_count == 4
|
||||
|
||||
trade = trades[0]
|
||||
assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
|
||||
@@ -144,7 +146,6 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
create_stoploss_order=MagicMock(return_value=True),
|
||||
update_trade_state=MagicMock(),
|
||||
_notify_sell=MagicMock(),
|
||||
)
|
||||
should_sell_mock = MagicMock(side_effect=[
|
||||
|
@@ -115,6 +115,32 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None:
|
||||
assert log_has('Oh snap!', caplog)
|
||||
|
||||
|
||||
def test_main_operational_exception1(mocker, default_conf, caplog) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch(
|
||||
'freqtrade.commands.list_commands.available_exchanges',
|
||||
MagicMock(side_effect=ValueError('Oh snap!'))
|
||||
)
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
args = ['list-exchanges']
|
||||
|
||||
# Test Main + the KeyboardInterrupt exception
|
||||
with pytest.raises(SystemExit):
|
||||
main(args)
|
||||
|
||||
assert log_has('Fatal exception!', caplog)
|
||||
assert not log_has_re(r'SIGINT.*', caplog)
|
||||
mocker.patch(
|
||||
'freqtrade.commands.list_commands.available_exchanges',
|
||||
MagicMock(side_effect=KeyboardInterrupt)
|
||||
)
|
||||
with pytest.raises(SystemExit):
|
||||
main(args)
|
||||
|
||||
assert log_has_re(r'SIGINT.*', caplog)
|
||||
|
||||
|
||||
def test_main_reload_conf(mocker, default_conf, caplog) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cleanup', MagicMock())
|
||||
|
@@ -465,6 +465,10 @@ def test_migrate_old(mocker, default_conf, fee):
|
||||
assert trade.initial_stop_loss == 0.0
|
||||
assert trade.open_trade_price == trade._calc_open_trade_price()
|
||||
assert trade.close_profit_abs is None
|
||||
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
|
||||
|
||||
trade = Trade.query.filter(Trade.id == 2).first()
|
||||
assert trade.close_rate is not None
|
||||
@@ -741,7 +745,11 @@ def test_to_json(default_conf, fee):
|
||||
'open_rate_requested': None,
|
||||
'open_trade_price': 15.1668225,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'close_rate': None,
|
||||
'close_rate_requested': None,
|
||||
'amount': 123.0,
|
||||
@@ -790,7 +798,11 @@ def test_to_json(default_conf, fee):
|
||||
'close_profit': None,
|
||||
'close_rate_requested': None,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'is_open': None,
|
||||
'max_rate': None,
|
||||
'min_rate': None,
|
||||
@@ -862,6 +874,75 @@ def test_stoploss_reinitialization(default_conf, fee):
|
||||
assert trade_adj.initial_stop_loss_pct == -0.04
|
||||
|
||||
|
||||
def test_update_fee(fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee_open=fee.return_value,
|
||||
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||
amount=10,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
open_rate=1,
|
||||
max_rate=1,
|
||||
)
|
||||
fee_cost = 0.15
|
||||
fee_currency = 'BTC'
|
||||
fee_rate = 0.0075
|
||||
assert trade.fee_open_currency is None
|
||||
assert not trade.fee_updated('buy')
|
||||
assert not trade.fee_updated('sell')
|
||||
|
||||
trade.update_fee(fee_cost, fee_currency, fee_rate, 'buy')
|
||||
assert trade.fee_updated('buy')
|
||||
assert not trade.fee_updated('sell')
|
||||
assert trade.fee_open_currency == fee_currency
|
||||
assert trade.fee_open_cost == fee_cost
|
||||
assert trade.fee_open == fee_rate
|
||||
# Setting buy rate should "guess" close rate
|
||||
assert trade.fee_close == fee_rate
|
||||
assert trade.fee_close_currency is None
|
||||
assert trade.fee_close_cost is None
|
||||
|
||||
fee_rate = 0.0076
|
||||
trade.update_fee(fee_cost, fee_currency, fee_rate, 'sell')
|
||||
assert trade.fee_updated('buy')
|
||||
assert trade.fee_updated('sell')
|
||||
assert trade.fee_close == 0.0076
|
||||
assert trade.fee_close_cost == fee_cost
|
||||
assert trade.fee_close == fee_rate
|
||||
|
||||
|
||||
def test_fee_updated(fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee_open=fee.return_value,
|
||||
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||
amount=10,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
open_rate=1,
|
||||
max_rate=1,
|
||||
)
|
||||
|
||||
assert trade.fee_open_currency is None
|
||||
assert not trade.fee_updated('buy')
|
||||
assert not trade.fee_updated('sell')
|
||||
assert not trade.fee_updated('asdf')
|
||||
|
||||
trade.update_fee(0.15, 'BTC', 0.0075, 'buy')
|
||||
assert trade.fee_updated('buy')
|
||||
assert not trade.fee_updated('sell')
|
||||
assert trade.fee_open_currency is not None
|
||||
assert trade.fee_close_currency is None
|
||||
|
||||
trade.update_fee(0.15, 'ABC', 0.0075, 'sell')
|
||||
assert trade.fee_updated('buy')
|
||||
assert trade.fee_updated('sell')
|
||||
assert not trade.fee_updated('asfd')
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_total_open_trades_stakes(fee):
|
||||
|
||||
|
BIN
tests/testdata/XRP_ETH-trades.json.gz
vendored
BIN
tests/testdata/XRP_ETH-trades.json.gz
vendored
Binary file not shown.
BIN
tests/testdata/XRP_OLD-trades.json.gz
vendored
Normal file
BIN
tests/testdata/XRP_OLD-trades.json.gz
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user