Merge branch 'develop' into pr/Axel-CH/8779
This commit is contained in:
+10
-2
@@ -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")
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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': {
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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"),
|
||||
|
||||
BIN
Binary file not shown.
@@ -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)
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user