Merge branch 'develop' into pr/Axel-CH/8779

This commit is contained in:
Matthias
2023-08-23 07:20:34 +02:00
107 changed files with 1931 additions and 1491 deletions
+10 -2
View File
@@ -14,7 +14,7 @@ import pytest
from freqtrade import constants
from freqtrade.commands import Arguments
from freqtrade.data.converter import ohlcv_to_dataframe
from freqtrade.data.converter import ohlcv_to_dataframe, trades_list_to_df
from freqtrade.edge import PairInfo
from freqtrade.enums import CandleType, MarginMode, RunMode, SignalDirection, TradingMode
from freqtrade.exchange import Exchange
@@ -2346,7 +2346,15 @@ def trades_history():
[1565798399629, '1261813bb30', None, 'buy', 0.019627, 0.244, 0.004788987999999999],
[1565798399752, '1261813cc31', None, 'sell', 0.019626, 0.011, 0.00021588599999999999],
[1565798399862, '126181cc332', None, 'sell', 0.019626, 0.011, 0.00021588599999999999],
[1565798399872, '1261aa81333', None, 'sell', 0.019626, 0.011, 0.00021588599999999999]]
[1565798399862, '126181cc333', None, 'sell', 0.019626, 0.012, 0.00021588599999999999],
[1565798399872, '1261aa81334', None, 'sell', 0.019626, 0.011, 0.00021588599999999999]]
@pytest.fixture(scope="function")
def trades_history_df(trades_history):
trades = trades_list_to_df(trades_history)
trades['date'] = pd.to_datetime(trades['timestamp'], unit='ms', utc=True)
return trades
@pytest.fixture(scope="function")
+15 -19
View File
@@ -4,13 +4,14 @@ from pathlib import Path
from shutil import copyfile
import numpy as np
import pandas as pd
import pytest
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,
reduce_dataframe_footprint, trades_dict_to_list,
trades_remove_duplicates, trades_to_ohlcv, trim_dataframe)
reduce_dataframe_footprint, trades_df_remove_duplicates,
trades_dict_to_list, trades_to_ohlcv, trim_dataframe)
from freqtrade.data.history import (get_timerange, load_data, load_pair_history,
validate_backtest_data)
from freqtrade.data.history.idatahandler import IDataHandler
@@ -34,26 +35,21 @@ def test_ohlcv_to_dataframe(ohlcv_history_list, caplog):
assert log_has('Converting candle (OHLCV) data to dataframe for pair UNITTEST/BTC.', caplog)
def test_trades_to_ohlcv(ohlcv_history_list, caplog):
def test_trades_to_ohlcv(trades_history_df, caplog):
caplog.set_level(logging.DEBUG)
with pytest.raises(ValueError, match="Trade-list empty."):
trades_to_ohlcv([], '1m')
trades_to_ohlcv(pd.DataFrame(columns=trades_history_df.columns), '1m')
trades = [
[1570752011620, "13519807", None, "sell", 0.00141342, 23.0, 0.03250866],
[1570752011620, "13519808", None, "sell", 0.00141266, 54.0, 0.07628364],
[1570752017964, "13519809", None, "sell", 0.00141266, 8.0, 0.01130128]]
df = trades_to_ohlcv(trades, '1m')
df = trades_to_ohlcv(trades_history_df, '1m')
assert not df.empty
assert len(df) == 1
assert 'open' in df.columns
assert 'high' in df.columns
assert 'low' in df.columns
assert 'close' in df.columns
assert df.loc[:, 'high'][0] == 0.00141342
assert df.loc[:, 'low'][0] == 0.00141266
assert df.loc[:, 'high'][0] == 0.019627
assert df.loc[:, 'low'][0] == 0.019626
def test_ohlcv_fill_up_missing_data(testdatadir, caplog):
@@ -302,13 +298,13 @@ def test_trim_dataframe(testdatadir) -> None:
assert all(data_modify.iloc[0] == data.iloc[25])
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]
def test_trades_df_remove_duplicates(trades_history_df):
trades_history1 = pd.concat([trades_history_df, trades_history_df, trades_history_df]
).reset_index(drop=True)
assert len(trades_history1) == len(trades_history_df) * 3
res = trades_df_remove_duplicates(trades_history1)
assert len(res) == len(trades_history_df)
assert res.equals(trades_history_df)
def test_trades_dict_to_list(fetch_trades_result):
+45 -99
View File
@@ -6,7 +6,8 @@ from pathlib import Path
from unittest.mock import MagicMock
import pytest
from pandas import DataFrame
from pandas import DataFrame, Timestamp
from pandas.testing import assert_frame_equal
from freqtrade.configuration import TimeRange
from freqtrade.constants import AVAILABLE_DATAHANDLERS
@@ -117,12 +118,6 @@ def test_datahandler_ohlcv_get_available_data(testdatadir):
assert set(paircombs) == {('UNITTEST/BTC', '5m', CandleType.SPOT)}
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', 'XRP/OLD'}
def test_jsondatahandler_ohlcv_purge(mocker, testdatadir):
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
@@ -246,8 +241,10 @@ def test_datahandler__check_empty_df(testdatadir, caplog):
assert log_has_re(expected_text, caplog)
@pytest.mark.parametrize('datahandler', ['parquet'])
# @pytest.mark.parametrize('datahandler', [])
@pytest.mark.skip("All datahandlers currently support trades data.")
def test_datahandler_trades_not_supported(datahandler, testdatadir, ):
# Currently disabled. Reenable should a new provider not support trades data.
dh = get_datahandler(testdatadir, datahandler)
with pytest.raises(NotImplementedError):
dh.trades_load('UNITTEST/ETH')
@@ -266,18 +263,6 @@ def test_jsondatahandler_trades_load(testdatadir, caplog):
assert log_has(logmsg, caplog)
def test_jsondatahandler_trades_purge(mocker, testdatadir):
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
dh = JsonGzDataHandler(testdatadir)
assert not dh.trades_purge('UNITTEST/NONEXIST')
assert unlinkmock.call_count == 0
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
assert dh.trades_purge('UNITTEST/NONEXIST')
assert unlinkmock.call_count == 1
@pytest.mark.parametrize('datahandler', AVAILABLE_DATAHANDLERS)
def test_datahandler_ohlcv_append(datahandler, testdatadir, ):
dh = get_datahandler(testdatadir, datahandler)
@@ -291,79 +276,48 @@ def test_datahandler_ohlcv_append(datahandler, testdatadir, ):
def test_datahandler_trades_append(datahandler, testdatadir):
dh = get_datahandler(testdatadir, datahandler)
with pytest.raises(NotImplementedError):
dh.trades_append('UNITTEST/ETH', [])
dh.trades_append('UNITTEST/ETH', DataFrame())
def test_hdf5datahandler_trades_get_pairs(testdatadir):
pairs = HDF5DataHandler.trades_get_pairs(testdatadir)
@pytest.mark.parametrize('datahandler,expected', [
('jsongz', {'XRP/ETH', 'XRP/OLD'}),
('hdf5', {'XRP/ETH'}),
('feather', {'XRP/ETH'}),
('parquet', {'XRP/ETH'}),
])
def test_datahandler_trades_get_pairs(testdatadir, datahandler, expected):
pairs = get_datahandlerclass(datahandler).trades_get_pairs(testdatadir)
# Convert to set to avoid failures due to sorting
assert set(pairs) == {'XRP/ETH'}
assert set(pairs) == expected
def test_hdf5datahandler_trades_load(testdatadir):
dh = get_datahandler(testdatadir, 'hdf5')
trades = dh.trades_load('XRP/ETH')
assert isinstance(trades, list)
assert isinstance(trades, DataFrame)
trades1 = dh.trades_load('UNITTEST/NONEXIST')
assert trades1 == []
assert isinstance(trades1, DataFrame)
assert trades1.empty
# data goes from 2019-10-11 - 2019-10-13
timerange = TimeRange.parse_timerange('20191011-20191012')
trades2 = dh._trades_load('XRP/ETH', timerange)
assert len(trades) > len(trades2)
# Check that ID is None (If it's nan, it's wrong)
assert trades2[0][2] is None
assert trades2.iloc[0]['type'] is None
# unfiltered load has trades before starttime
assert len([t for t in trades if t[0] < timerange.startts * 1000]) >= 0
assert len(trades.loc[trades['timestamp'] < timerange.startts * 1000]) >= 0
# filtered list does not have trades before starttime
assert len([t for t in trades2 if t[0] < timerange.startts * 1000]) == 0
assert len(trades2.loc[trades2['timestamp'] < timerange.startts * 1000]) == 0
# unfiltered load has trades after endtime
assert len([t for t in trades if t[0] > timerange.stopts * 1000]) > 0
assert len(trades.loc[trades['timestamp'] > timerange.stopts * 1000]) >= 0
# filtered list does not have trades after endtime
assert len([t for t in trades2 if t[0] > timerange.stopts * 1000]) == 0
def test_hdf5datahandler_trades_store(testdatadir, tmpdir):
tmpdir1 = Path(tmpdir)
dh = get_datahandler(testdatadir, 'hdf5')
trades = dh.trades_load('XRP/ETH')
dh1 = get_datahandler(tmpdir1, 'hdf5')
dh1.trades_store('XRP/NEW', trades)
file = tmpdir1 / 'XRP_NEW-trades.h5'
assert file.is_file()
# Load trades back
trades_new = dh1.trades_load('XRP/NEW')
assert len(trades_new) == len(trades)
assert trades[0][0] == trades_new[0][0]
assert trades[0][1] == trades_new[0][1]
# assert trades[0][2] == trades_new[0][2] # This is nan - so comparison does not make sense
assert trades[0][3] == trades_new[0][3]
assert trades[0][4] == trades_new[0][4]
assert trades[0][5] == trades_new[0][5]
assert trades[0][6] == trades_new[0][6]
assert trades[-1][0] == trades_new[-1][0]
assert trades[-1][1] == trades_new[-1][1]
# assert trades[-1][2] == trades_new[-1][2] # This is nan - so comparison does not make sense
assert trades[-1][3] == trades_new[-1][3]
assert trades[-1][4] == trades_new[-1][4]
assert trades[-1][5] == trades_new[-1][5]
assert trades[-1][6] == trades_new[-1][6]
def test_hdf5datahandler_trades_purge(mocker, testdatadir):
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
dh = get_datahandler(testdatadir, 'hdf5')
assert not dh.trades_purge('UNITTEST/NONEXIST')
assert unlinkmock.call_count == 0
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
assert dh.trades_purge('UNITTEST/NONEXIST')
assert unlinkmock.call_count == 1
assert len(trades2.loc[trades2['timestamp'] > timerange.stopts * 1000]) == 0
# assert len([t for t in trades2 if t[0] > timerange.stopts * 1000]) == 0
@pytest.mark.parametrize('pair,timeframe,candle_type,candle_append,startdt,enddt', [
@@ -490,50 +444,42 @@ def test_hdf5datahandler_ohlcv_purge(mocker, testdatadir):
assert unlinkmock.call_count == 2
def test_featherdatahandler_trades_load(testdatadir):
dh = get_datahandler(testdatadir, 'feather')
@pytest.mark.parametrize('datahandler', ['jsongz', 'hdf5', 'feather', 'parquet'])
def test_datahandler_trades_load(testdatadir, datahandler):
dh = get_datahandler(testdatadir, datahandler)
trades = dh.trades_load('XRP/ETH')
assert isinstance(trades, list)
assert trades[0][0] == 1570752011620
assert trades[-1][-1] == 0.1986231
assert isinstance(trades, DataFrame)
assert trades.iloc[0]['timestamp'] == 1570752011620
assert trades.iloc[0]['date'] == Timestamp('2019-10-11 00:00:11.620000+0000')
assert trades.iloc[-1]['cost'] == 0.1986231
trades1 = dh.trades_load('UNITTEST/NONEXIST')
assert trades1 == []
assert isinstance(trades, DataFrame)
assert trades1.empty
def test_featherdatahandler_trades_store(testdatadir, tmpdir):
@pytest.mark.parametrize('datahandler', ['jsongz', 'hdf5', 'feather', 'parquet'])
def test_datahandler_trades_store(testdatadir, tmpdir, datahandler):
tmpdir1 = Path(tmpdir)
dh = get_datahandler(testdatadir, 'feather')
dh = get_datahandler(testdatadir, datahandler)
trades = dh.trades_load('XRP/ETH')
dh1 = get_datahandler(tmpdir1, 'feather')
dh1 = get_datahandler(tmpdir1, datahandler)
dh1.trades_store('XRP/NEW', trades)
file = tmpdir1 / 'XRP_NEW-trades.feather'
file = tmpdir1 / f'XRP_NEW-trades.{dh1._get_file_extension()}'
assert file.is_file()
# Load trades back
trades_new = dh1.trades_load('XRP/NEW')
assert_frame_equal(trades, trades_new, check_exact=True)
assert len(trades_new) == len(trades)
assert trades[0][0] == trades_new[0][0]
assert trades[0][1] == trades_new[0][1]
# assert trades[0][2] == trades_new[0][2] # This is nan - so comparison does not make sense
assert trades[0][3] == trades_new[0][3]
assert trades[0][4] == trades_new[0][4]
assert trades[0][5] == trades_new[0][5]
assert trades[0][6] == trades_new[0][6]
assert trades[-1][0] == trades_new[-1][0]
assert trades[-1][1] == trades_new[-1][1]
# assert trades[-1][2] == trades_new[-1][2] # This is nan - so comparison does not make sense
assert trades[-1][3] == trades_new[-1][3]
assert trades[-1][4] == trades_new[-1][4]
assert trades[-1][5] == trades_new[-1][5]
assert trades[-1][6] == trades_new[-1][6]
def test_featherdatahandler_trades_purge(mocker, testdatadir):
@pytest.mark.parametrize('datahandler', ['jsongz', 'hdf5', 'feather', 'parquet'])
def test_datahandler_trades_purge(mocker, testdatadir, datahandler):
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
dh = get_datahandler(testdatadir, 'feather')
dh = get_datahandler(testdatadir, datahandler)
assert not dh.trades_purge('UNITTEST/NONEXIST')
assert unlinkmock.call_count == 0
+11 -6
View File
@@ -129,9 +129,14 @@ def test_get_pair_dataframe(mocker, default_conf, ohlcv_history, candle_type):
default_conf["runmode"] = RunMode.BACKTEST
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.BACKTEST
assert isinstance(dp.get_pair_dataframe(
"UNITTEST/BTC", timeframe, candle_type=candle_type), DataFrame)
# assert dp.get_pair_dataframe("NONESENSE/AAA", timeframe).empty
df = dp.get_pair_dataframe("UNITTEST/BTC", timeframe, candle_type=candle_type)
assert isinstance(df, DataFrame)
assert len(df) == 3 # ohlcv_history mock has just 3 rows
dp._set_dataframe_max_date(ohlcv_history.iloc[-1]['date'])
df = dp.get_pair_dataframe("UNITTEST/BTC", timeframe, candle_type=candle_type)
assert isinstance(df, DataFrame)
assert len(df) == 2 # ohlcv_history is limited to 2 rows now
def test_available_pairs(mocker, default_conf, ohlcv_history):
@@ -259,7 +264,7 @@ def test_orderbook(mocker, default_conf, order_book_l2):
assert order_book_l2.call_args_list[0][0][0] == 'ETH/BTC'
assert order_book_l2.call_args_list[0][0][1] >= 5
assert type(res) is dict
assert isinstance(res, dict)
assert 'bids' in res
assert 'asks' in res
@@ -272,7 +277,7 @@ def test_market(mocker, default_conf, markets):
dp = DataProvider(default_conf, exchange)
res = dp.market('ETH/BTC')
assert type(res) is dict
assert isinstance(res, dict)
assert 'symbol' in res
assert res['symbol'] == 'ETH/BTC'
@@ -286,7 +291,7 @@ def test_ticker(mocker, default_conf, tickers):
exchange = get_patched_exchange(mocker, default_conf)
dp = DataProvider(default_conf, exchange)
res = dp.ticker('ETH/BTC')
assert type(res) is dict
assert isinstance(res, dict)
assert 'symbol' in res
assert res['symbol'] == 'ETH/BTC'
+17 -7
View File
@@ -3,6 +3,7 @@
import json
import logging
import uuid
from datetime import timedelta
from pathlib import Path
from shutil import copyfile
from unittest.mock import MagicMock, PropertyMock
@@ -26,7 +27,7 @@ from freqtrade.enums import CandleType
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import file_dump_json
from freqtrade.resolvers import StrategyResolver
from freqtrade.util import dt_utc
from freqtrade.util import dt_ts, dt_utc
from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_patched_exchange, log_has, log_has_re,
patch_exchange)
@@ -569,7 +570,10 @@ def test_refresh_backtest_trades_data(mocker, default_conf, markets, caplog, tes
def test_download_trades_history(trades_history, mocker, default_conf, testdatadir, caplog,
tmpdir) -> None:
tmpdir, time_machine) -> None:
start_dt = dt_utc(2023, 1, 1)
time_machine.move_to(start_dt, tick=False)
tmpdir1 = Path(tmpdir)
ght_mock = MagicMock(side_effect=lambda pair, *args, **kwargs: (pair, trades_history))
mocker.patch(f'{EXMS}.get_historic_trades', ght_mock)
@@ -581,8 +585,13 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad
assert _download_trades_history(data_handler=data_handler, exchange=exchange,
pair='ETH/BTC')
assert log_has("New Amount of trades: 5", caplog)
assert log_has("Current Amount of trades: 0", caplog)
assert log_has("New Amount of trades: 6", caplog)
assert ght_mock.call_count == 1
# Default "since" - 30 days before current day.
assert ght_mock.call_args_list[0][1]['since'] == dt_ts(start_dt - timedelta(days=30))
assert file1.is_file()
caplog.clear()
ght_mock.reset_mock()
since_time = int(trades_history[-3][0] // 1000)
@@ -599,6 +608,7 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad
file1.unlink()
mocker.patch(f'{EXMS}.get_historic_trades', MagicMock(side_effect=ValueError))
caplog.clear()
assert not _download_trades_history(data_handler=data_handler, exchange=exchange,
pair='ETH/BTC')
@@ -620,7 +630,7 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad
assert int(ght_mock.call_args_list[0][1]['since'] // 1000) == since_time
assert ght_mock.call_args_list[0][1]['from_id'] is None
assert log_has_re(r'Start earlier than available data. Redownloading trades for.*', caplog)
assert log_has_re(r'Start .* earlier than available data. Redownloading trades for.*', caplog)
_clean_test_file(file2)
@@ -651,10 +661,10 @@ def test_convert_trades_to_ohlcv(testdatadir, tmpdir, caplog):
assert_frame_equal(dfbak_1m, df_1m, check_exact=True)
assert_frame_equal(dfbak_5m, df_5m, check_exact=True)
assert not log_has('Could not convert NoDatapair to OHLCV.', caplog)
msg = 'Could not convert NoDatapair to OHLCV.'
assert not log_has(msg, caplog)
convert_trades_to_ohlcv(['NoDatapair'], timeframes=['1m', '5m'],
data_format_trades='jsongz',
datadir=tmpdir1, timerange=tr, erase=True)
assert log_has('Could not convert NoDatapair to OHLCV.', caplog)
assert log_has(msg, caplog)
+1 -1
View File
@@ -35,7 +35,7 @@ def test__get_params_binance(default_conf, mocker, side, type, time_in_force, ex
])
def test_create_stoploss_order_binance(default_conf, mocker, limitratio, expected, side, trademode):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_id = f'test_prod_buy_{randint(0, 10 ** 6)}'
order_type = 'stop_loss_limit' if trademode == TradingMode.SPOT else 'stop'
api_mock.create_order = MagicMock(return_value={
+15 -50
View File
@@ -556,41 +556,6 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
assert result == 4000
def test_set_sandbox(default_conf, mocker):
"""
Test working scenario
"""
api_mock = MagicMock()
api_mock.load_markets = MagicMock(return_value={
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
})
url_mock = PropertyMock(return_value={'test': "api-public.sandbox.gdax.com",
'api': 'https://api.gdax.com'})
type(api_mock).urls = url_mock
exchange = get_patched_exchange(mocker, default_conf, api_mock)
liveurl = exchange._api.urls['api']
default_conf['exchange']['sandbox'] = True
exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname')
assert exchange._api.urls['api'] != liveurl
def test_set_sandbox_exception(default_conf, mocker):
"""
Test Fail scenario
"""
api_mock = MagicMock()
api_mock.load_markets = MagicMock(return_value={
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
})
url_mock = PropertyMock(return_value={'api': 'https://api.gdax.com'})
type(api_mock).urls = url_mock
with pytest.raises(OperationalException, match=r'does not provide a sandbox api'):
exchange = get_patched_exchange(mocker, default_conf, api_mock)
default_conf['exchange']['sandbox'] = True
exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname')
def test__load_async_markets(default_conf, mocker, caplog):
mocker.patch(f'{EXMS}._init_ccxt')
mocker.patch(f'{EXMS}.validate_pairs')
@@ -1372,7 +1337,7 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, exchange_name):
api_mock = MagicMock()
order_id = 'test_prod_{}_{}'.format(side, randint(0, 10 ** 6))
order_id = f'test_prod_{side}_{randint(0, 10 ** 6)}'
api_mock.options = {} if not marketprice else {"createMarketBuyOrderRequiresPrice": True}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
@@ -1452,7 +1417,7 @@ def test_buy_dry_run(default_conf, mocker, exchange_name):
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_buy_prod(default_conf, mocker, exchange_name):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_id = f'test_prod_buy_{randint(0, 10 ** 6)}'
order_type = 'market'
time_in_force = 'gtc'
api_mock.options = {}
@@ -1541,7 +1506,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_id = f'test_prod_buy_{randint(0, 10 ** 6)}'
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
@@ -1608,7 +1573,7 @@ def test_sell_dry_run(default_conf, mocker):
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_sell_prod(default_conf, mocker, exchange_name):
api_mock = MagicMock()
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
order_id = f'test_prod_sell_{randint(0, 10 ** 6)}'
order_type = 'market'
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
@@ -1686,7 +1651,7 @@ def test_sell_prod(default_conf, mocker, exchange_name):
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
api_mock = MagicMock()
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
order_id = f'test_prod_sell_{randint(0, 10 ** 6)}'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'symbol': 'ETH/BTC',
@@ -2505,7 +2470,7 @@ def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog):
assert exchange._klines
assert exchange._api_async.fetch_ohlcv.call_count == 2
assert type(res) is dict
assert isinstance(res, dict)
assert len(res) == 1
# Test that each is in list at least once as order is not guaranteed
assert log_has("Error loading ETH/BTC. Result was [[]].", caplog)
@@ -2889,7 +2854,7 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
pair = 'ETH/BTC'
res = await exchange._async_fetch_trades(pair, since=None, params=None)
assert type(res) is list
assert isinstance(res, list)
assert isinstance(res[0], list)
assert isinstance(res[1], list)
@@ -2989,9 +2954,9 @@ async def test__async_get_trade_history_id(default_conf, mocker, exchange_name,
ret = await exchange._async_get_trade_history_id(pair,
since=fetch_trades_result[0]['timestamp'],
until=fetch_trades_result[-1]['timestamp'] - 1)
assert type(ret) is tuple
assert isinstance(ret, tuple)
assert ret[0] == pair
assert type(ret[1]) is list
assert isinstance(ret[1], list)
assert len(ret[1]) == len(fetch_trades_result)
assert exchange._api_async.fetch_trades.call_count == 3
fetch_trades_cal = exchange._api_async.fetch_trades.call_args_list
@@ -3027,9 +2992,9 @@ async def test__async_get_trade_history_time(default_conf, mocker, caplog, excha
pair,
since=fetch_trades_result[0]['timestamp'],
until=fetch_trades_result[-1]['timestamp'] - 1)
assert type(ret) is tuple
assert isinstance(ret, tuple)
assert ret[0] == pair
assert type(ret[1]) is list
assert isinstance(ret[1], list)
assert len(ret[1]) == len(fetch_trades_result)
assert exchange._api_async.fetch_trades.call_count == 2
fetch_trades_cal = exchange._api_async.fetch_trades.call_args_list
@@ -3063,9 +3028,9 @@ async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog,
pair = 'ETH/BTC'
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 isinstance(ret, tuple)
assert ret[0] == pair
assert type(ret[1]) is list
assert isinstance(ret[1], list)
assert len(ret[1]) == len(trades_history) - 1
assert exchange._async_fetch_trades.call_count == 2
fetch_trades_cal = exchange._async_fetch_trades.call_args_list
@@ -3557,7 +3522,7 @@ def test_get_valid_pair_combination(default_conf, mocker, markets):
assert ex.get_valid_pair_combination("ETH", "BTC") == "ETH/BTC"
assert ex.get_valid_pair_combination("BTC", "ETH") == "ETH/BTC"
with pytest.raises(DependencyException, match=r"Could not combine.* to get a valid pair."):
with pytest.raises(ValueError, match=r"Could not combine.* to get a valid pair."):
ex.get_valid_pair_combination("NOPAIR", "ETH")
@@ -5392,7 +5357,7 @@ def test_get_liquidation_price(
])
def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amount):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_id = f'test_prod_buy_{randint(0, 10 ** 6)}'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
+1 -1
View File
@@ -16,7 +16,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
])
def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_id = f'test_prod_buy_{randint(0, 10 ** 6)}'
order_type = 'stop-limit'
api_mock.create_order = MagicMock(return_value={
+3 -3
View File
@@ -15,7 +15,7 @@ STOPLOSS_LIMIT_ORDERTYPE = 'stop-loss-limit'
def test_buy_kraken_trading_agreement(default_conf, mocker):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_id = f'test_prod_buy_{randint(0, 10 ** 6)}'
order_type = 'limit'
time_in_force = 'ioc'
api_mock.options = {}
@@ -56,7 +56,7 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
def test_sell_kraken_trading_agreement(default_conf, mocker):
api_mock = MagicMock()
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
order_id = f'test_prod_sell_{randint(0, 10 ** 6)}'
order_type = 'market'
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
@@ -181,7 +181,7 @@ def test_get_balances_prod(default_conf, mocker):
])
def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedprice):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_id = f'test_prod_buy_{randint(0, 10 ** 6)}'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
+2 -2
View File
@@ -17,7 +17,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
])
def test_create_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side, order_type):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_id = f'test_prod_buy_{randint(0, 10 ** 6)}'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
@@ -136,7 +136,7 @@ def test_stoploss_adjust_kucoin(mocker, default_conf):
])
def test_kucoin_create_order(default_conf, mocker, side, ordertype, rate):
api_mock = MagicMock()
order_id = 'test_prod_{}_{}'.format(side, randint(0, 10 ** 6))
order_id = f'test_prod_{side}_{randint(0, 10 ** 6)}'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
View File
+334
View File
@@ -0,0 +1,334 @@
from copy import deepcopy
from pathlib import Path
from typing import Tuple
import pytest
from freqtrade.constants import Config
from freqtrade.exchange.exchange import Exchange
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import EXMS, get_default_conf_usdt
EXCHANGE_FIXTURE_TYPE = Tuple[Exchange, str]
# Exchanges that should be tested online
EXCHANGES = {
'bittrex': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': False,
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
},
'binance': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'use_ci_proxy': True,
'hasQuoteVolume': True,
'timeframe': '1h',
'futures': True,
'futures_pair': 'BTC/USDT:USDT',
'hasQuoteVolumeFutures': True,
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
'trades_lookback_hours': 4,
'private_methods': [
'fapiPrivateGetPositionSideDual',
'fapiPrivateGetMultiAssetsMargin'
],
'sample_order': [{
"symbol": "SOLUSDT",
"orderId": 3551312894,
"orderListId": -1,
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
"transactTime": 1674493798550,
"price": "15.50000000",
"origQty": "1.10000000",
"executedQty": "0.00000000",
"cummulativeQuoteQty": "0.00000000",
"status": "NEW",
"timeInForce": "GTC",
"type": "LIMIT",
"side": "BUY",
"workingTime": 1674493798550,
"fills": [],
"selfTradePreventionMode": "NONE",
}]
},
'binanceus': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '1h',
'futures': False,
'sample_order': [{
"symbol": "SOLUSDT",
"orderId": 3551312894,
"orderListId": -1,
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
"transactTime": 1674493798550,
"price": "15.50000000",
"origQty": "1.10000000",
"executedQty": "0.00000000",
"cummulativeQuoteQty": "0.00000000",
"status": "NEW",
"timeInForce": "GTC",
"type": "LIMIT",
"side": "BUY",
"workingTime": 1674493798550,
"fills": [],
"selfTradePreventionMode": "NONE",
}]
},
'kraken': {
'pair': 'BTC/USD',
'stake_currency': 'USD',
'hasQuoteVolume': True,
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': True,
'trades_lookback_hours': 12,
},
'kucoin': {
'pair': 'XRP/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': True,
'sample_order': [
{'id': '63d6742d0adc5570001d2bbf7'}, # create order
{
'id': '63d6742d0adc5570001d2bbf7',
'symbol': 'SOL-USDT',
'opType': 'DEAL',
'type': 'limit',
'side': 'buy',
'price': '15.5',
'size': '1.1',
'funds': '0',
'dealFunds': '17.05',
'dealSize': '1.1',
'fee': '0.000065252',
'feeCurrency': 'USDT',
'stp': '',
'stop': '',
'stopTriggered': False,
'stopPrice': '0',
'timeInForce': 'GTC',
'postOnly': False,
'hidden': False,
'iceberg': False,
'visibleSize': '0',
'cancelAfter': 0,
'channel': 'API',
'clientOid': '0a053870-11bf-41e5-be61-b272a4cb62e1',
'remark': None,
'tags': 'partner:ccxt',
'isActive': False,
'cancelExist': False,
'createdAt': 1674493798550,
'tradeType': 'TRADE'
}],
},
'gate': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '1h',
'futures': True,
'futures_pair': 'BTC/USDT:USDT',
'hasQuoteVolumeFutures': True,
'leverage_tiers_public': True,
'leverage_in_spot_market': True,
'sample_order': [
{
"id": "276266139423",
"text": "apiv4",
"create_time": "1674493798",
"update_time": "1674493798",
"create_time_ms": "1674493798550",
"update_time_ms": "1674493798550",
"status": "closed",
"currency_pair": "SOL_USDT",
"type": "limit",
"account": "spot",
"side": "buy",
"amount": "1.1",
"price": "15.5",
"time_in_force": "gtc",
"iceberg": "0",
"left": "0",
"fill_price": "17.05",
"filled_total": "17.05",
"avg_deal_price": "15.5",
"fee": "0.0000018",
"fee_currency": "SOL",
"point_fee": "0",
"gt_fee": "0",
"gt_maker_fee": "0",
"gt_taker_fee": "0.0015",
"gt_discount": True,
"rebated_fee": "0",
"rebated_fee_currency": "USDT"
},
{
# market order
'id': '276401180529',
'text': 'apiv4',
'create_time': '1674493798',
'update_time': '1674493798',
'create_time_ms': '1674493798550',
'update_time_ms': '1674493798550',
'status': 'cancelled',
'currency_pair': 'SOL_USDT',
'type': 'market',
'account': 'spot',
'side': 'buy',
'amount': '17.05',
'price': '0',
'time_in_force': 'ioc',
'iceberg': '0',
'left': '0.0000000016228',
'fill_price': '17.05',
'filled_total': '17.05',
'avg_deal_price': '15.5',
'fee': '0',
'fee_currency': 'SOL',
'point_fee': '0.0199999999967544',
'gt_fee': '0',
'gt_maker_fee': '0',
'gt_taker_fee': '0',
'gt_discount': False,
'rebated_fee': '0',
'rebated_fee_currency': 'USDT'
}
],
},
'okx': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '1h',
'futures': True,
'futures_pair': 'BTC/USDT:USDT',
'hasQuoteVolumeFutures': False,
'leverage_tiers_public': True,
'leverage_in_spot_market': True,
'private_methods': ['fetch_accounts'],
},
'bybit': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'use_ci_proxy': True,
'timeframe': '1h',
'futures_pair': 'BTC/USDT:USDT',
'futures': True,
'leverage_tiers_public': True,
'leverage_in_spot_market': True,
'sample_order': [
{
"orderId": "1274754916287346280",
"orderLinkId": "1666798627015730",
"symbol": "SOLUSDT",
"createTime": "1674493798550",
"orderPrice": "15.5",
"orderQty": "1.1",
"orderType": "LIMIT",
"side": "BUY",
"status": "NEW",
"timeInForce": "GTC",
"accountId": "5555555",
"execQty": "0",
"orderCategory": "0"
}
]
},
'huobi': {
'pair': 'ETH/BTC',
'stake_currency': 'BTC',
'hasQuoteVolume': True,
'timeframe': '1h',
'futures': False,
},
'bitvavo': {
'pair': 'BTC/EUR',
'stake_currency': 'EUR',
'hasQuoteVolume': True,
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
},
}
@pytest.fixture(scope="class")
def exchange_conf():
config = get_default_conf_usdt((Path(__file__).parent / "testdata").resolve())
config['exchange']['pair_whitelist'] = []
config['exchange']['key'] = ''
config['exchange']['secret'] = ''
config['dry_run'] = False
config['entry_pricing']['use_order_book'] = True
config['exit_pricing']['use_order_book'] = True
return config
def set_test_proxy(config: Config, use_proxy: bool) -> Config:
# Set proxy to test in CI.
import os
if use_proxy and (proxy := os.environ.get('CI_WEB_PROXY')):
config1 = deepcopy(config)
config1['exchange']['ccxt_config'] = {
"httpsProxy": proxy,
}
return config1
return config
def get_exchange(exchange_name, exchange_conf):
exchange_conf = set_test_proxy(
exchange_conf, EXCHANGES[exchange_name].get('use_ci_proxy', False))
exchange_conf['exchange']['name'] = exchange_name
exchange_conf['stake_currency'] = EXCHANGES[exchange_name]['stake_currency']
exchange = ExchangeResolver.load_exchange(exchange_conf, validate=True,
load_leverage_tiers=True)
yield exchange, exchange_name
def get_futures_exchange(exchange_name, exchange_conf, class_mocker):
if EXCHANGES[exchange_name].get('futures') is not True:
pytest.skip(f"Exchange {exchange_name} does not support futures.")
else:
exchange_conf = deepcopy(exchange_conf)
exchange_conf = set_test_proxy(
exchange_conf, EXCHANGES[exchange_name].get('use_ci_proxy', False))
exchange_conf['trading_mode'] = 'futures'
exchange_conf['margin_mode'] = 'isolated'
class_mocker.patch(
'freqtrade.exchange.binance.Binance.fill_leverage_tiers')
class_mocker.patch(f'{EXMS}.fetch_trading_fees')
class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init')
class_mocker.patch('freqtrade.exchange.binance.Binance.additional_exchange_init')
class_mocker.patch('freqtrade.exchange.bybit.Bybit.additional_exchange_init')
class_mocker.patch(f'{EXMS}.load_cached_leverage_tiers', return_value=None)
class_mocker.patch(f'{EXMS}.cache_leverage_tiers')
yield from get_exchange(exchange_name, exchange_conf)
@pytest.fixture(params=EXCHANGES, scope="class")
def exchange(request, exchange_conf):
yield from get_exchange(request.param, exchange_conf)
@pytest.fixture(params=EXCHANGES, scope="class")
def exchange_futures(request, exchange_conf, class_mocker):
yield from get_futures_exchange(request.param, exchange_conf, class_mocker)
@@ -5,338 +5,14 @@ However, these tests should give a good idea to determine if a new exchange is
suitable to run with freqtrade.
"""
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Tuple
import pytest
from freqtrade.constants import Config
from freqtrade.enums import CandleType
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
from freqtrade.exchange.exchange import Exchange, timeframe_to_msecs
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import EXMS, get_default_conf_usdt
EXCHANGE_FIXTURE_TYPE = Tuple[Exchange, str]
# Exchanges that should be tested
EXCHANGES = {
'bittrex': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': False,
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
},
'binance': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'use_ci_proxy': True,
'hasQuoteVolume': True,
'timeframe': '1h',
'futures': True,
'futures_pair': 'BTC/USDT:USDT',
'hasQuoteVolumeFutures': True,
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
'trades_lookback_hours': 4,
'private_methods': [
'fapiPrivateGetPositionSideDual',
'fapiPrivateGetMultiAssetsMargin'
],
'sample_order': [{
"symbol": "SOLUSDT",
"orderId": 3551312894,
"orderListId": -1,
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
"transactTime": 1674493798550,
"price": "15.50000000",
"origQty": "1.10000000",
"executedQty": "0.00000000",
"cummulativeQuoteQty": "0.00000000",
"status": "NEW",
"timeInForce": "GTC",
"type": "LIMIT",
"side": "BUY",
"workingTime": 1674493798550,
"fills": [],
"selfTradePreventionMode": "NONE",
}]
},
'binanceus': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '1h',
'futures': False,
'sample_order': [{
"symbol": "SOLUSDT",
"orderId": 3551312894,
"orderListId": -1,
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
"transactTime": 1674493798550,
"price": "15.50000000",
"origQty": "1.10000000",
"executedQty": "0.00000000",
"cummulativeQuoteQty": "0.00000000",
"status": "NEW",
"timeInForce": "GTC",
"type": "LIMIT",
"side": "BUY",
"workingTime": 1674493798550,
"fills": [],
"selfTradePreventionMode": "NONE",
}]
},
'kraken': {
'pair': 'BTC/USD',
'stake_currency': 'USD',
'hasQuoteVolume': True,
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': True,
'trades_lookback_hours': 12,
},
'kucoin': {
'pair': 'XRP/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': True,
'sample_order': [
{'id': '63d6742d0adc5570001d2bbf7'}, # create order
{
'id': '63d6742d0adc5570001d2bbf7',
'symbol': 'SOL-USDT',
'opType': 'DEAL',
'type': 'limit',
'side': 'buy',
'price': '15.5',
'size': '1.1',
'funds': '0',
'dealFunds': '17.05',
'dealSize': '1.1',
'fee': '0.000065252',
'feeCurrency': 'USDT',
'stp': '',
'stop': '',
'stopTriggered': False,
'stopPrice': '0',
'timeInForce': 'GTC',
'postOnly': False,
'hidden': False,
'iceberg': False,
'visibleSize': '0',
'cancelAfter': 0,
'channel': 'API',
'clientOid': '0a053870-11bf-41e5-be61-b272a4cb62e1',
'remark': None,
'tags': 'partner:ccxt',
'isActive': False,
'cancelExist': False,
'createdAt': 1674493798550,
'tradeType': 'TRADE'
}],
},
'gate': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '1h',
'futures': True,
'futures_pair': 'BTC/USDT:USDT',
'hasQuoteVolumeFutures': True,
'leverage_tiers_public': True,
'leverage_in_spot_market': True,
'sample_order': [
{
"id": "276266139423",
"text": "apiv4",
"create_time": "1674493798",
"update_time": "1674493798",
"create_time_ms": "1674493798550",
"update_time_ms": "1674493798550",
"status": "closed",
"currency_pair": "SOL_USDT",
"type": "limit",
"account": "spot",
"side": "buy",
"amount": "1.1",
"price": "15.5",
"time_in_force": "gtc",
"iceberg": "0",
"left": "0",
"fill_price": "17.05",
"filled_total": "17.05",
"avg_deal_price": "15.5",
"fee": "0.0000018",
"fee_currency": "SOL",
"point_fee": "0",
"gt_fee": "0",
"gt_maker_fee": "0",
"gt_taker_fee": "0.0015",
"gt_discount": True,
"rebated_fee": "0",
"rebated_fee_currency": "USDT"
},
{
# market order
'id': '276401180529',
'text': 'apiv4',
'create_time': '1674493798',
'update_time': '1674493798',
'create_time_ms': '1674493798550',
'update_time_ms': '1674493798550',
'status': 'cancelled',
'currency_pair': 'SOL_USDT',
'type': 'market',
'account': 'spot',
'side': 'buy',
'amount': '17.05',
'price': '0',
'time_in_force': 'ioc',
'iceberg': '0',
'left': '0.0000000016228',
'fill_price': '17.05',
'filled_total': '17.05',
'avg_deal_price': '15.5',
'fee': '0',
'fee_currency': 'SOL',
'point_fee': '0.0199999999967544',
'gt_fee': '0',
'gt_maker_fee': '0',
'gt_taker_fee': '0',
'gt_discount': False,
'rebated_fee': '0',
'rebated_fee_currency': 'USDT'
}
],
},
'okx': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '1h',
'futures': True,
'futures_pair': 'BTC/USDT:USDT',
'hasQuoteVolumeFutures': False,
'leverage_tiers_public': True,
'leverage_in_spot_market': True,
'private_methods': ['fetch_accounts'],
},
'bybit': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'use_ci_proxy': True,
'timeframe': '1h',
'futures_pair': 'BTC/USDT:USDT',
'futures': True,
'leverage_tiers_public': True,
'leverage_in_spot_market': True,
'sample_order': [
{
"orderId": "1274754916287346280",
"orderLinkId": "1666798627015730",
"symbol": "SOLUSDT",
"createTime": "1674493798550",
"orderPrice": "15.5",
"orderQty": "1.1",
"orderType": "LIMIT",
"side": "BUY",
"status": "NEW",
"timeInForce": "GTC",
"accountId": "5555555",
"execQty": "0",
"orderCategory": "0"
}
]
},
'huobi': {
'pair': 'ETH/BTC',
'stake_currency': 'BTC',
'hasQuoteVolume': True,
'timeframe': '1h',
'futures': False,
},
'bitvavo': {
'pair': 'BTC/EUR',
'stake_currency': 'EUR',
'hasQuoteVolume': True,
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
},
}
@pytest.fixture(scope="class")
def exchange_conf():
config = get_default_conf_usdt((Path(__file__).parent / "testdata").resolve())
config['exchange']['pair_whitelist'] = []
config['exchange']['key'] = ''
config['exchange']['secret'] = ''
config['dry_run'] = False
config['entry_pricing']['use_order_book'] = True
config['exit_pricing']['use_order_book'] = True
return config
def set_test_proxy(config: Config, use_proxy: bool) -> Config:
# Set proxy to test in CI.
import os
if use_proxy and (proxy := os.environ.get('CI_WEB_PROXY')):
config1 = deepcopy(config)
config1['exchange']['ccxt_config'] = {
"httpsProxy": proxy,
}
return config1
return config
@pytest.fixture(params=EXCHANGES, scope="class")
def exchange(request, exchange_conf):
exchange_conf = set_test_proxy(
exchange_conf, EXCHANGES[request.param].get('use_ci_proxy', False))
exchange_conf['exchange']['name'] = request.param
exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency']
exchange = ExchangeResolver.load_exchange(exchange_conf, validate=True)
yield exchange, request.param
@pytest.fixture(params=EXCHANGES, scope="class")
def exchange_futures(request, exchange_conf, class_mocker):
if EXCHANGES[request.param].get('futures') is not True:
yield None, request.param
else:
exchange_conf = set_test_proxy(
exchange_conf, EXCHANGES[request.param].get('use_ci_proxy', False))
exchange_conf = deepcopy(exchange_conf)
exchange_conf['exchange']['name'] = request.param
exchange_conf['trading_mode'] = 'futures'
exchange_conf['margin_mode'] = 'isolated'
exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency']
class_mocker.patch(
'freqtrade.exchange.binance.Binance.fill_leverage_tiers')
class_mocker.patch(f'{EXMS}.fetch_trading_fees')
class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init')
class_mocker.patch('freqtrade.exchange.binance.Binance.additional_exchange_init')
class_mocker.patch('freqtrade.exchange.bybit.Bybit.additional_exchange_init')
class_mocker.patch(f'{EXMS}.load_cached_leverage_tiers', return_value=None)
class_mocker.patch(f'{EXMS}.cache_leverage_tiers')
exchange = ExchangeResolver.load_exchange(
exchange_conf, validate=True, load_leverage_tiers=True)
yield exchange, request.param
from freqtrade.exchange.exchange import timeframe_to_msecs
from tests.exchange_online.conftest import EXCHANGE_FIXTURE_TYPE, EXCHANGES
@pytest.mark.longrun
@@ -371,9 +47,6 @@ class TestCCXTExchange:
def test_load_markets_futures(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
exchange, exchangename = exchange_futures
if not exchange:
# exchange_futures only returns values for supported exchanges
return
pair = EXCHANGES[exchangename]['pair']
pair = EXCHANGES[exchangename].get('futures_pair', pair)
markets = exchange.markets
@@ -561,9 +234,6 @@ class TestCCXTExchange:
def test_ccxt__async_get_candle_history_futures(
self, exchange_futures: EXCHANGE_FIXTURE_TYPE, candle_type):
exchange, exchangename = exchange_futures
if not exchange:
# exchange_futures only returns values for supported exchanges
return
pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair'])
timeframe = EXCHANGES[exchangename]['timeframe']
if candle_type == CandleType.FUNDING_RATE:
@@ -579,9 +249,6 @@ class TestCCXTExchange:
def test_ccxt_fetch_funding_rate_history(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
exchange, exchangename = exchange_futures
if not exchange:
# exchange_futures only returns values for supported exchanges
return
pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair'])
since = int((datetime.now(timezone.utc) - timedelta(days=5)).timestamp() * 1000)
@@ -617,9 +284,6 @@ class TestCCXTExchange:
def test_ccxt_fetch_mark_price_history(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
exchange, exchangename = exchange_futures
if not exchange:
# exchange_futures only returns values for supported exchanges
return
pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair'])
since = int((datetime.now(timezone.utc) - timedelta(days=5)).timestamp() * 1000)
pair_tf = (pair, '1h', CandleType.MARK)
@@ -641,9 +305,6 @@ class TestCCXTExchange:
def test_ccxt__calculate_funding_fees(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
exchange, exchangename = exchange_futures
if not exchange:
# exchange_futures only returns values for supported exchanges
return
pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair'])
since = datetime.now(timezone.utc) - timedelta(days=5)
@@ -690,31 +351,29 @@ class TestCCXTExchange:
def test_ccxt_get_max_leverage_futures(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
futures, futures_name = exchange_futures
if futures:
leverage_tiers_public = EXCHANGES[futures_name].get('leverage_tiers_public')
if leverage_tiers_public:
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
EXCHANGES[futures_name]['pair']
)
futures_leverage = futures.get_max_leverage(futures_pair, 20)
assert (isinstance(futures_leverage, float) or isinstance(futures_leverage, int))
assert futures_leverage >= 1.0
def test_ccxt_get_contract_size(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
futures, futures_name = exchange_futures
if futures:
leverage_tiers_public = EXCHANGES[futures_name].get('leverage_tiers_public')
if leverage_tiers_public:
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
EXCHANGES[futures_name]['pair']
)
contract_size = futures.get_contract_size(futures_pair)
assert (isinstance(contract_size, float) or isinstance(contract_size, int))
assert contract_size >= 0.0
futures_leverage = futures.get_max_leverage(futures_pair, 20)
assert (isinstance(futures_leverage, float) or isinstance(futures_leverage, int))
assert futures_leverage >= 1.0
def test_ccxt_get_contract_size(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
futures, futures_name = exchange_futures
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
EXCHANGES[futures_name]['pair']
)
contract_size = futures.get_contract_size(futures_pair)
assert (isinstance(contract_size, float) or isinstance(contract_size, int))
assert contract_size >= 0.0
def test_ccxt_load_leverage_tiers(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
futures, futures_name = exchange_futures
if futures and EXCHANGES[futures_name].get('leverage_tiers_public'):
if EXCHANGES[futures_name].get('leverage_tiers_public'):
leverage_tiers = futures.load_leverage_tiers()
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
@@ -747,7 +406,7 @@ class TestCCXTExchange:
def test_ccxt_dry_run_liquidation_price(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
futures, futures_name = exchange_futures
if futures and EXCHANGES[futures_name].get('leverage_tiers_public'):
if EXCHANGES[futures_name].get('leverage_tiers_public'):
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
@@ -780,14 +439,13 @@ class TestCCXTExchange:
def test_ccxt_get_max_pair_stake_amount(self, exchange_futures: EXCHANGE_FIXTURE_TYPE):
futures, futures_name = exchange_futures
if futures:
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
EXCHANGES[futures_name]['pair']
)
max_stake_amount = futures.get_max_pair_stake_amount(futures_pair, 40000)
assert (isinstance(max_stake_amount, float))
assert max_stake_amount >= 0.0
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
EXCHANGES[futures_name]['pair']
)
max_stake_amount = futures.get_max_pair_stake_amount(futures_pair, 40000)
assert (isinstance(max_stake_amount, float))
assert max_stake_amount >= 0.0
def test_private_method_presence(self, exchange: EXCHANGE_FIXTURE_TYPE):
exch, exchangename = exchange
+2 -2
View File
@@ -97,9 +97,9 @@ def mock_pytorch_mlp_model_training_parameters() -> Dict[str, Any]:
return {
"learning_rate": 3e-4,
"trainer_kwargs": {
"max_iters": 1,
"n_steps": None,
"batch_size": 64,
"max_n_eval_batches": 1,
"n_epochs": 1,
},
"model_kwargs": {
"hidden_dim": 32,
+11 -6
View File
@@ -20,7 +20,7 @@ from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import get_timerange
from freqtrade.enums import CandleType, ExitType, RunMode
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.exchange.exchange import timeframe_to_next_date
from freqtrade.exchange import timeframe_to_next_date, timeframe_to_prev_date
from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename, get_strategy_run_id
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.persistence import LocalTrade, Trade
@@ -1122,10 +1122,10 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
global count
count = 0
def tmp_confirm_entry(pair, current_time, **kwargs):
nonlocal count
dp = backtesting.strategy.dp
df, _ = dp.get_analyzed_dataframe(pair, backtesting.strategy.timeframe)
current_candle = df.iloc[-1].squeeze()
@@ -1135,8 +1135,13 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi
assert candle_date == current_time
# These asserts don't properly raise as they are nested,
# therefore we increment count and assert for that.
global count
count = count + 1
df = dp.get_pair_dataframe(pair, backtesting.strategy.timeframe)
prior_time = timeframe_to_prev_date(backtesting.strategy.timeframe,
candle_date - timedelta(seconds=1))
assert prior_time == df.iloc[-1].squeeze()['date']
assert df.iloc[-1].squeeze()['date'] < current_time
count += 1
backtesting.strategy.confirm_trade_entry = tmp_confirm_entry
backtesting.backtest(
@@ -1354,11 +1359,11 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
# Cached data correctly removed amounts
offset = 1 if tres == 0 else 0
removed_candles = len(data[pair]) - offset - backtesting.strategy.startup_candle_count
removed_candles = len(data[pair]) - offset
assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, '5m')[0]) == removed_candles
assert len(
backtesting.dataprovider.get_analyzed_dataframe('NXT/BTC', '5m')[0]
) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count
) == len(data['NXT/BTC']) - 1
backtesting.strategy.max_open_trades = 1
backtesting.config.update({'max_open_trades': 1})
+23 -1
View File
@@ -17,6 +17,8 @@ from tests.conftest import EXMS, get_args, log_has_re, patch_exchange
def lookahead_conf(default_conf_usdt):
default_conf_usdt['minimum_trade_amount'] = 10
default_conf_usdt['targeted_trade_amount'] = 20
default_conf_usdt['timerange'] = '20220101-20220501'
default_conf_usdt['strategy_path'] = str(
Path(__file__).parent.parent / "strategy/strats/lookahead_bias")
default_conf_usdt['strategy'] = 'strategy_test_v3_with_lookahead_bias'
@@ -43,7 +45,9 @@ def test_start_lookahead_analysis(mocker):
"--pairs",
"UNITTEST/BTC",
"--max-open-trades",
"1"
"1",
"--timerange",
"20220101-20220201"
]
pargs = get_args(args)
pargs['config'] = None
@@ -72,6 +76,24 @@ def test_start_lookahead_analysis(mocker):
match=r"Targeted trade amount can't be smaller than minimum trade amount.*"):
start_lookahead_analysis(pargs)
# Missing timerange
args = [
"lookahead-analysis",
"--strategy",
"strategy_test_v3_with_lookahead_bias",
"--strategy-path",
str(Path(__file__).parent.parent / "strategy/strats/lookahead_bias"),
"--pairs",
"UNITTEST/BTC",
"--max-open-trades",
"1",
]
pargs = get_args(args)
pargs['config'] = None
with pytest.raises(OperationalException,
match=r"Please set a timerange\..*"):
start_lookahead_analysis(pargs)
def test_lookahead_helper_invalid_config(lookahead_conf) -> None:
conf = deepcopy(lookahead_conf)
+1 -1
View File
@@ -553,7 +553,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
assert isinstance(whitelist, list)
# Verify length of pairlist matches (used for ShuffleFilter without seed)
if type(whitelist_result) is list:
if isinstance(whitelist_result, list):
assert whitelist == whitelist_result
else:
len(whitelist) == whitelist_result
+9 -9
View File
@@ -1424,12 +1424,12 @@ def test_api_pair_candles(botclient, ohlcv_history):
assert len(rc.json()['data']) == amount
assert (rc.json()['data'] ==
[['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
[['2017-11-26T08:50:00Z', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
None, 0, 0, 0, 0, 1511686200000, None, None, None, None],
['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05,
['2017-11-26T08:55:00Z', 8.88e-05, 8.942e-05, 8.88e-05,
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0, 0, 0, 1511686500000, 8.893e-05,
None, None, None],
['2017-11-26 09:00:00', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05,
['2017-11-26T09:00:00Z', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05,
0.7039405, 8.885e-05, 0, 0, 0, 0, 1511686800000, None, None, None, None]
])
@@ -1443,13 +1443,13 @@ def test_api_pair_candles(botclient, ohlcv_history):
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
assert_response(rc)
assert (rc.json()['data'] ==
[['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
[['2017-11-26T08:50:00Z', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
None, 0, None, 0, 0, None, 1511686200000, None, None, None, None],
['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05,
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0.0, 0, 0, '2017-11-26 08:55:00',
['2017-11-26T08:55:00Z', 8.88e-05, 8.942e-05, 8.88e-05,
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0.0, 0, 0, '2017-11-26T08:55:00Z',
1511686500000, 8.893e-05, None, None, None],
['2017-11-26 09:00:00', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05,
0.7039405, 8.885e-05, 0, 0.0, 0, 0, '2017-11-26 09:00:00', 1511686800000,
['2017-11-26T09:00:00Z', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05,
0.7039405, 8.885e-05, 0, 0.0, 0, 0, '2017-11-26T09:00:00Z', 1511686800000,
None, None, None, None]
])
@@ -1506,7 +1506,7 @@ def test_api_pair_history(botclient, mocker):
date_col_idx = [idx for idx, c in enumerate(result['columns']) if c == 'date'][0]
rsi_col_idx = [idx for idx, c in enumerate(result['columns']) if c == 'rsi'][0]
assert data[0][date_col_idx] == '2018-01-11 00:00:00'
assert data[0][date_col_idx] == '2018-01-11T00:00:00Z'
assert data[0][rsi_col_idx] is not None
assert data[0][rsi_col_idx] > 0
assert lfm.call_count == 1
+6 -6
View File
@@ -25,13 +25,13 @@ def test_strategy_test_v3(dataframe_1m, fee, is_short, side):
strategy = StrategyTestV3({})
metadata = {'pair': 'ETH/BTC'}
assert type(strategy.minimal_roi) is dict
assert type(strategy.stoploss) is float
assert type(strategy.timeframe) is str
assert isinstance(strategy.minimal_roi, dict)
assert isinstance(strategy.stoploss, float)
assert isinstance(strategy.timeframe, str)
indicators = strategy.populate_indicators(dataframe_1m, metadata)
assert type(indicators) is DataFrame
assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame
assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame
assert isinstance(indicators, DataFrame)
assert isinstance(strategy.populate_buy_trend(indicators, metadata), DataFrame)
assert isinstance(strategy.populate_sell_trend(indicators, metadata), DataFrame)
trade = Trade(
open_rate=19_000,
+1 -1
View File
@@ -133,7 +133,7 @@ def test_parse_args_backtesting_custom() -> None:
assert call_args['command'] == 'backtesting'
assert call_args['func'] is not None
assert call_args['timeframe'] == '1m'
assert type(call_args['strategy_list']) is list
assert isinstance(call_args['strategy_list'], list)
assert len(call_args['strategy_list']) == 2
+1 -2
View File
@@ -1038,8 +1038,7 @@ def test_load_config_stoploss_exchange_limit_ratio(all_conf) -> None:
validate_config_schema(all_conf)
@pytest.mark.parametrize("keys", [("exchange", "sandbox", False),
("exchange", "key", ""),
@pytest.mark.parametrize("keys", [("exchange", "key", ""),
("exchange", "secret", ""),
("exchange", "password", ""),
])
+1 -16
View File
@@ -9,8 +9,7 @@ import pytest
from freqtrade.misc import (dataframe_to_json, decimals_per_coin, deep_merge_dicts, file_dump_json,
file_load_json, is_file_in_dir, json_to_dataframe, pair_to_filename,
parse_db_uri_for_logging, plural, render_template,
render_template_with_fallback, round_coin_value, safe_value_fallback,
parse_db_uri_for_logging, plural, round_coin_value, safe_value_fallback,
safe_value_fallback2)
@@ -177,20 +176,6 @@ def test_plural() -> None:
assert plural(-1.5, "ox", "oxen") == "oxen"
def test_render_template_fallback(mocker):
from jinja2.exceptions import TemplateNotFound
with pytest.raises(TemplateNotFound):
val = render_template(
templatefile='subtemplates/indicators_does-not-exist.j2',)
val = render_template_with_fallback(
templatefile='strategy_subtemplates/indicators_does-not-exist.j2',
templatefallbackfile='strategy_subtemplates/indicators_minimal.j2',
)
assert isinstance(val, str)
assert 'if self.dp' in val
@pytest.mark.parametrize('conn_url,expected', [
("postgresql+psycopg2://scott123:scott123@host:1245/dbname",
"postgresql+psycopg2://scott123:*****@host:1245/dbname"),
Binary file not shown.
+1 -1
View File
@@ -63,7 +63,7 @@ def test_format_ms_time() -> None:
# Date 2018-04-10 18:02:01
date_in_epoch_ms = 1523383321000
date = format_ms_time(date_in_epoch_ms)
assert type(date) is str
assert isinstance(date, str)
res = datetime(2018, 4, 10, 18, 2, 1, tzinfo=timezone.utc)
assert date == res.astimezone(None).strftime('%Y-%m-%dT%H:%M:%S')
res = datetime(2017, 12, 13, 8, 2, 1, tzinfo=timezone.utc)
+17
View File
@@ -0,0 +1,17 @@
import pytest
from freqtrade.util import render_template, render_template_with_fallback
def test_render_template_fallback():
from jinja2.exceptions import TemplateNotFound
with pytest.raises(TemplateNotFound):
val = render_template(
templatefile='subtemplates/indicators_does-not-exist.j2',)
val = render_template_with_fallback(
templatefile='strategy_subtemplates/indicators_does-not-exist.j2',
templatefallbackfile='strategy_subtemplates/indicators_minimal.j2',
)
assert isinstance(val, str)
assert 'if self.dp' in val