Merge branch 'feat/short' into futures_pairlist

This commit is contained in:
Matthias
2021-11-15 19:12:36 +01:00
25 changed files with 966 additions and 244 deletions

View File

@@ -2404,3 +2404,131 @@ def limit_order_open(limit_buy_order_usdt_open, limit_sell_order_usdt_open):
'buy': limit_buy_order_usdt_open,
'sell': limit_sell_order_usdt_open
}
@pytest.fixture(scope='function')
def mark_ohlcv():
return [
[1630454400000, 2.77, 2.77, 2.73, 2.73, 0],
[1630458000000, 2.73, 2.76, 2.72, 2.74, 0],
[1630461600000, 2.74, 2.76, 2.74, 2.76, 0],
[1630465200000, 2.76, 2.76, 2.74, 2.76, 0],
[1630468800000, 2.76, 2.77, 2.75, 2.77, 0],
[1630472400000, 2.77, 2.79, 2.75, 2.78, 0],
[1630476000000, 2.78, 2.80, 2.77, 2.77, 0],
[1630479600000, 2.78, 2.79, 2.77, 2.77, 0],
[1630483200000, 2.77, 2.79, 2.77, 2.78, 0],
[1630486800000, 2.77, 2.84, 2.77, 2.84, 0],
[1630490400000, 2.84, 2.85, 2.81, 2.81, 0],
[1630494000000, 2.81, 2.83, 2.81, 2.81, 0],
[1630497600000, 2.81, 2.84, 2.81, 2.82, 0],
[1630501200000, 2.82, 2.83, 2.81, 2.81, 0],
]
@pytest.fixture(scope='function')
def funding_rate_history_hourly():
return [
{
"symbol": "ADA/USDT",
"fundingRate": -0.000008,
"timestamp": 1630454400000,
"datetime": "2021-09-01T00:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": -0.000004,
"timestamp": 1630458000000,
"datetime": "2021-09-01T01:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": 0.000012,
"timestamp": 1630461600000,
"datetime": "2021-09-01T02:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": -0.000003,
"timestamp": 1630465200000,
"datetime": "2021-09-01T03:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": -0.000007,
"timestamp": 1630468800000,
"datetime": "2021-09-01T04:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": 0.000003,
"timestamp": 1630472400000,
"datetime": "2021-09-01T05:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": 0.000019,
"timestamp": 1630476000000,
"datetime": "2021-09-01T06:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": 0.000003,
"timestamp": 1630479600000,
"datetime": "2021-09-01T07:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": -0.000003,
"timestamp": 1630483200000,
"datetime": "2021-09-01T08:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": 0,
"timestamp": 1630486800000,
"datetime": "2021-09-01T09:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": 0.000013,
"timestamp": 1630490400000,
"datetime": "2021-09-01T10:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": 0.000077,
"timestamp": 1630494000000,
"datetime": "2021-09-01T11:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": 0.000072,
"timestamp": 1630497600000,
"datetime": "2021-09-01T12:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": 0.000097,
"timestamp": 1630501200000,
"datetime": "2021-09-01T13:00:00.000Z"
},
]
@pytest.fixture(scope='function')
def funding_rate_history_octohourly():
return [
{
"symbol": "ADA/USDT",
"fundingRate": -0.000008,
"timestamp": 1630454400000,
"datetime": "2021-09-01T00:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"fundingRate": -0.000003,
"timestamp": 1630483200000,
"datetime": "2021-09-01T08:00:00.000Z"
}
]

View File

@@ -488,7 +488,7 @@ def leverage_trade(fee):
open_order_id='dry_run_leverage_buy_12368',
strategy='DefaultStrategy',
timeframe=5,
sell_reason='sell_signal', # TODO-lev: Update to exit/close reason
sell_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=300),
close_date=datetime.now(tz=timezone.utc),
interest_rate=0.0005

View File

@@ -12,7 +12,7 @@ import pytest
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import get_default_conf
from tests.conftest import get_default_conf_usdt
# Exchanges that should be tested
@@ -33,9 +33,11 @@ EXCHANGES = {
'timeframe': '5m',
},
'ftx': {
'pair': 'BTC/USDT',
'pair': 'BTC/USD',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures_pair': 'BTC-PERP',
'futures': True,
},
'kucoin': {
'pair': 'BTC/USDT',
@@ -46,18 +48,24 @@ EXCHANGES = {
'pair': 'BTC/USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures': True,
'futures_fundingrate_tf': '8h',
'futures_pair': 'BTC/USDT:USDT',
},
'okex': {
'pair': 'BTC/USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures_fundingrate_tf': '8h',
'futures_pair': 'BTC/USDT:USDT',
'futures': True,
},
}
@pytest.fixture(scope="class")
def exchange_conf():
config = get_default_conf((Path(__file__).parent / "testdata").resolve())
config = get_default_conf_usdt((Path(__file__).parent / "testdata").resolve())
config['exchange']['pair_whitelist'] = []
config['exchange']['key'] = ''
config['exchange']['secret'] = ''
@@ -73,6 +81,19 @@ def exchange(request, exchange_conf):
yield exchange, request.param
@pytest.fixture(params=EXCHANGES, scope="class")
def exchange_futures(request, exchange_conf):
if not EXCHANGES[request.param].get('futures') is True:
yield None, request.param
else:
exchange_conf['exchange']['name'] = request.param
exchange_conf['trading_mode'] = 'futures'
exchange_conf['collateral'] = 'cross'
exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True)
yield exchange, request.param
@pytest.mark.longrun
class TestCCXTExchange():
@@ -149,6 +170,25 @@ class TestCCXTExchange():
now = datetime.now(timezone.utc) - timedelta(minutes=(timeframe_to_minutes(timeframe) * 2))
assert exchange.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now)
@pytest.mark.skip("No futures support yet")
def test_ccxt_fetch_funding_rate_history(self, exchange_futures):
# TODO-lev: enable this test once Futures mode is enabled.
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)
rate = exchange.get_funding_rate_history(pair, since)
assert isinstance(rate, dict)
expected_tf = EXCHANGES[exchangename].get('futures_fundingrate_tf', '1h')
this_hour = timeframe_to_prev_date(expected_tf)
prev_tick = timeframe_to_prev_date(expected_tf, this_hour - timedelta(minutes=1))
assert rate[int(this_hour.timestamp() * 1000)] != 0.0
assert rate[int(prev_tick.timestamp() * 1000)] != 0.0
# TODO: tests fetch_trades (?)
def test_ccxt_get_fee(self, exchange):

View File

@@ -2916,6 +2916,8 @@ def test_timeframe_to_prev_date():
# Does not round
time = datetime(2019, 8, 12, 13, 20, 0, tzinfo=timezone.utc)
assert timeframe_to_prev_date('5m', time) == time
time = datetime(2019, 8, 12, 13, 0, 0, tzinfo=timezone.utc)
assert timeframe_to_prev_date('1h', time) == time
def test_timeframe_to_next_date():
@@ -3097,7 +3099,7 @@ def test_calculate_backoff(retrycount, max_retries, expected):
@pytest.mark.parametrize("exchange_name", ['binance', 'ftx'])
def test_get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
api_mock = MagicMock()
api_mock.fetch_funding_history = MagicMock(return_value=[
{
@@ -3140,11 +3142,11 @@ def test_get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
date_time = datetime.strptime("2021-09-01T00:00:01.000Z", '%Y-%m-%dT%H:%M:%S.%fZ')
unix_time = int(date_time.timestamp())
expected_fees = -0.001 # 0.14542341 + -0.14642341
fees_from_datetime = exchange.get_funding_fees_from_exchange(
fees_from_datetime = exchange._get_funding_fees_from_exchange(
pair='XRP/USDT',
since=date_time
)
fees_from_unix_time = exchange.get_funding_fees_from_exchange(
fees_from_unix_time = exchange._get_funding_fees_from_exchange(
pair='XRP/USDT',
since=unix_time
)
@@ -3157,7 +3159,7 @@ def test_get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
default_conf,
api_mock,
exchange_name,
"get_funding_fees_from_exchange",
"_get_funding_fees_from_exchange",
"fetch_funding_history",
pair="XRP/USDT",
since=unix_time
@@ -3292,16 +3294,16 @@ def test_validate_trading_mode_and_collateral(
("binance", "spot", {}),
("binance", "margin", {"options": {"defaultType": "margin"}}),
("binance", "futures", {"options": {"defaultType": "future"}}),
("kraken", "spot", {}),
("kraken", "margin", {}),
("kraken", "futures", {}),
("ftx", "spot", {}),
("ftx", "margin", {}),
("ftx", "futures", {}),
("bittrex", "spot", {}),
("gateio", "spot", {}),
("gateio", "margin", {"options": {"defaultType": "margin"}}),
("bibox", "spot", {"has": {"fetchCurrencies": False}}),
("bibox", "margin", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "margin"}}),
("bibox", "futures", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "swap"}}),
("bybit", "futures", {"options": {"defaultType": "linear"}}),
("ftx", "futures", {"options": {"defaultType": "swap"}}),
("gateio", "futures", {"options": {"defaultType": "swap"}}),
("hitbtc", "futures", {"options": {"defaultType": "swap"}}),
("kraken", "futures", {"options": {"defaultType": "swap"}}),
("kucoin", "futures", {"options": {"defaultType": "swap"}}),
("okex", "futures", {"options": {"defaultType": "swap"}}),
])
def test__ccxt_config(
default_conf,
@@ -3327,3 +3329,226 @@ def test_get_max_leverage(default_conf, mocker, pair, nominal_value, max_lev):
# Binance has a different method of getting the max leverage
exchange = get_patched_exchange(mocker, default_conf, id="kraken")
assert exchange.get_max_leverage(pair, nominal_value) == max_lev
@pytest.mark.parametrize(
'size,funding_rate,mark_price,time_in_ratio,funding_fee,kraken_fee', [
(10, 0.0001, 2.0, 1.0, 0.002, 0.002),
(10, 0.0002, 2.0, 0.01, 0.004, 0.00004),
(10, 0.0002, 2.5, None, 0.005, None),
])
def test__get_funding_fee(
default_conf,
mocker,
size,
funding_rate,
mark_price,
funding_fee,
kraken_fee,
time_in_ratio
):
exchange = get_patched_exchange(mocker, default_conf)
kraken = get_patched_exchange(mocker, default_conf, id="kraken")
assert exchange._get_funding_fee(size, funding_rate, mark_price, time_in_ratio) == funding_fee
if (kraken_fee is None):
with pytest.raises(OperationalException):
kraken._get_funding_fee(size, funding_rate, mark_price, time_in_ratio)
else:
assert kraken._get_funding_fee(size, funding_rate, mark_price, time_in_ratio) == kraken_fee
def test__get_mark_price_history(mocker, default_conf, mark_ohlcv):
api_mock = MagicMock()
api_mock.fetch_ohlcv = MagicMock(return_value=mark_ohlcv)
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
# mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
mark_prices = exchange._get_mark_price_history("ADA/USDT", 1630454400000)
assert mark_prices == {
1630454400000: 2.77,
1630458000000: 2.73,
1630461600000: 2.74,
1630465200000: 2.76,
1630468800000: 2.76,
1630472400000: 2.77,
1630476000000: 2.78,
1630479600000: 2.78,
1630483200000: 2.77,
1630486800000: 2.77,
1630490400000: 2.84,
1630494000000: 2.81,
1630497600000: 2.81,
1630501200000: 2.82,
}
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"binance",
"_get_mark_price_history",
"fetch_ohlcv",
pair="ADA/USDT",
since=1635580800001
)
def test_get_funding_rate_history(mocker, default_conf, funding_rate_history_hourly):
api_mock = MagicMock()
api_mock.fetch_funding_rate_history = MagicMock(return_value=funding_rate_history_hourly)
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
# mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
funding_rates = exchange.get_funding_rate_history('ADA/USDT', 1635580800001)
assert funding_rates == {
1630454400000: -0.000008,
1630458000000: -0.000004,
1630461600000: 0.000012,
1630465200000: -0.000003,
1630468800000: -0.000007,
1630472400000: 0.000003,
1630476000000: 0.000019,
1630479600000: 0.000003,
1630483200000: -0.000003,
1630486800000: 0,
1630490400000: 0.000013,
1630494000000: 0.000077,
1630497600000: 0.000072,
1630501200000: 0.000097,
}
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"binance",
"get_funding_rate_history",
"fetch_funding_rate_history",
pair="ADA/USDT",
since=1630454400000
)
@pytest.mark.parametrize('exchange,rate_start,rate_end,d1,d2,amount,expected_fees', [
('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999999999999),
('binance', 0, 2, "2021-09-01 00:00:15", "2021-09-01 08:00:00", 30.0, -0.0009140999999999999),
('binance', 1, 2, "2021-09-01 01:00:14", "2021-09-01 08:00:00", 30.0, -0.0002493),
('binance', 1, 2, "2021-09-01 00:00:16", "2021-09-01 08:00:00", 30.0, -0.0002493),
('binance', 0, 1, "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.0006647999999999999),
('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.0009140999999999999),
('binance', 0, 2, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0009140999999999999),
# TODO: Uncoment once _calculate_funding_fees can pas time_in_ratio to exchange._get_funding_fee
# ('kraken', "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0014937),
# ('kraken', "2021-09-01 00:00:15", "2021-09-01 08:00:00", 30.0, -0.0008289),
# ('kraken', "2021-09-01 01:00:14", "2021-09-01 08:00:00", 30.0, -0.0008289),
# ('kraken', "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.0012443999999999999),
# ('kraken', "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, 0.0045759),
# ('kraken', "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0008289),
('ftx', 0, 9, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, 0.0010008000000000003),
('ftx', 0, 13, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, 0.0146691),
('ftx', 1, 9, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, 0.0016656000000000002),
('gateio', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999999999999),
('gateio', 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.0009140999999999999),
('gateio', 1, 2, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0002493),
('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, -0.0015235000000000001),
# TODO: Uncoment once _calculate_funding_fees can pas time_in_ratio to exchange._get_funding_fee
# ('kraken', "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, -0.0024895),
('ftx', 0, 9, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 50.0, 0.0016680000000000002),
])
def test__calculate_funding_fees(
mocker,
default_conf,
funding_rate_history_hourly,
funding_rate_history_octohourly,
rate_start,
rate_end,
mark_ohlcv,
exchange,
d1,
d2,
amount,
expected_fees
):
'''
nominal_value = mark_price * size
funding_fee = nominal_value * funding_rate
size: 30
time: 0, mark: 2.77, nominal_value: 83.1, fundRate: -0.000008, fundFee: -0.0006648
time: 1, mark: 2.73, nominal_value: 81.9, fundRate: -0.000004, fundFee: -0.0003276
time: 2, mark: 2.74, nominal_value: 82.2, fundRate: 0.000012, fundFee: 0.0009864
time: 3, mark: 2.76, nominal_value: 82.8, fundRate: -0.000003, fundFee: -0.0002484
time: 4, mark: 2.76, nominal_value: 82.8, fundRate: -0.000007, fundFee: -0.0005796
time: 5, mark: 2.77, nominal_value: 83.1, fundRate: 0.000003, fundFee: 0.0002493
time: 6, mark: 2.78, nominal_value: 83.4, fundRate: 0.000019, fundFee: 0.0015846
time: 7, mark: 2.78, nominal_value: 83.4, fundRate: 0.000003, fundFee: 0.0002502
time: 8, mark: 2.77, nominal_value: 83.1, fundRate: -0.000003, fundFee: -0.0002493
time: 9, mark: 2.77, nominal_value: 83.1, fundRate: 0, fundFee: 0.0
time: 10, mark: 2.84, nominal_value: 85.2, fundRate: 0.000013, fundFee: 0.0011076
time: 11, mark: 2.81, nominal_value: 84.3, fundRate: 0.000077, fundFee: 0.0064911
time: 12, mark: 2.81, nominal_value: 84.3, fundRate: 0.000072, fundFee: 0.0060696
time: 13, mark: 2.82, nominal_value: 84.6, fundRate: 0.000097, fundFee: 0.0082062
size: 50
time: 0, mark: 2.77, nominal_value: 138.5, fundRate: -0.000008, fundFee: -0.001108
time: 1, mark: 2.73, nominal_value: 136.5, fundRate: -0.000004, fundFee: -0.000546
time: 2, mark: 2.74, nominal_value: 137.0, fundRate: 0.000012, fundFee: 0.001644
time: 3, mark: 2.76, nominal_value: 138.0, fundRate: -0.000003, fundFee: -0.000414
time: 4, mark: 2.76, nominal_value: 138.0, fundRate: -0.000007, fundFee: -0.000966
time: 5, mark: 2.77, nominal_value: 138.5, fundRate: 0.000003, fundFee: 0.0004155
time: 6, mark: 2.78, nominal_value: 139.0, fundRate: 0.000019, fundFee: 0.002641
time: 7, mark: 2.78, nominal_value: 139.0, fundRate: 0.000003, fundFee: 0.000417
time: 8, mark: 2.77, nominal_value: 138.5, fundRate: -0.000003, fundFee: -0.0004155
time: 9, mark: 2.77, nominal_value: 138.5, fundRate: 0, fundFee: 0.0
time: 10, mark: 2.84, nominal_value: 142.0, fundRate: 0.000013, fundFee: 0.001846
time: 11, mark: 2.81, nominal_value: 140.5, fundRate: 0.000077, fundFee: 0.0108185
time: 12, mark: 2.81, nominal_value: 140.5, fundRate: 0.000072, fundFee: 0.010116
time: 13, mark: 2.82, nominal_value: 141.0, fundRate: 0.000097, fundFee: 0.013677
'''
d1 = datetime.strptime(f"{d1} +0000", '%Y-%m-%d %H:%M:%S %z')
d2 = datetime.strptime(f"{d2} +0000", '%Y-%m-%d %H:%M:%S %z')
funding_rate_history = {
'binance': funding_rate_history_octohourly,
'ftx': funding_rate_history_hourly,
'gateio': funding_rate_history_octohourly,
}[exchange][rate_start:rate_end]
api_mock = MagicMock()
api_mock.fetch_funding_rate_history = MagicMock(return_value=funding_rate_history)
api_mock.fetch_ohlcv = MagicMock(return_value=mark_ohlcv)
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
funding_fees = exchange._calculate_funding_fees('ADA/USDT', amount, d1, d2)
assert funding_fees == expected_fees
@ pytest.mark.parametrize('exchange,expected_fees', [
('binance', -0.0009140999999999999),
('gateio', -0.0009140999999999999),
])
def test__calculate_funding_fees_datetime_called(
mocker,
default_conf,
funding_rate_history_octohourly,
mark_ohlcv,
exchange,
time_machine,
expected_fees
):
api_mock = MagicMock()
api_mock.fetch_ohlcv = MagicMock(return_value=mark_ohlcv)
api_mock.fetch_funding_rate_history = MagicMock(return_value=funding_rate_history_octohourly)
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
d1 = datetime.strptime("2021-09-01 00:00:00 +0000", '%Y-%m-%d %H:%M:%S %z')
time_machine.move_to("2021-09-01 08:00:00 +00:00")
funding_fees = exchange._calculate_funding_fees('ADA/USDT', 30.0, d1)
assert funding_fees == expected_fees

View File

@@ -586,10 +586,10 @@ def test_api_trades(botclient, mocker, fee, markets, is_short):
assert rc.json()['total_trades'] == 2
# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
def test_api_trade_single(botclient, mocker, fee, ticker, markets):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets),
@@ -599,7 +599,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets):
assert_response(rc, 404)
assert rc.json()['detail'] == 'Trade not found.'
create_mock_trades(fee, False)
create_mock_trades(fee, is_short=is_short)
Trade.query.session.flush()
rc = client_get(client, f"{BASE_URI}/trade/3")
@@ -607,10 +607,10 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets):
assert rc.json()['trade_id'] == 3
# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
def test_api_delete_trade(botclient, mocker, fee, markets):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
mocker.patch.multiple(
@@ -749,10 +749,10 @@ def test_api_profit(botclient, mocker, ticker, fee, markets):
}
# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
def test_api_stats(botclient, mocker, ticker, fee, markets,):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_stats(botclient, mocker, ticker, fee, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
@@ -766,7 +766,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,):
assert 'durations' in rc.json()
assert 'sell_reasons' in rc.json()
create_mock_trades(fee, False)
create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/stats")
assert_response(rc, 200)

View File

@@ -1294,8 +1294,8 @@ def test_telegram_trades(mocker, update, default_conf, fee):
msg_mock.call_args_list[0][0][0]))
# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
def test_telegram_delete_trade(mocker, update, default_conf, fee):
@pytest.mark.parametrize('is_short', [True, False])
def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short):
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
context = MagicMock()
@@ -1305,7 +1305,7 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee):
assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
create_mock_trades(fee, False)
create_mock_trades(fee, is_short)
context = MagicMock()
context.args = [1]

View File

@@ -1,5 +1,6 @@
from datetime import datetime
import pytest
from pandas import DataFrame
from freqtrade.persistence.models import Trade
@@ -7,7 +8,7 @@ from freqtrade.persistence.models import Trade
from .strats.strategy_test_v3 import StrategyTestV3
def test_strategy_test_v2_structure():
def test_strategy_test_v3_structure():
assert hasattr(StrategyTestV3, 'minimal_roi')
assert hasattr(StrategyTestV3, 'stoploss')
assert hasattr(StrategyTestV3, 'timeframe')
@@ -16,7 +17,11 @@ def test_strategy_test_v2_structure():
assert hasattr(StrategyTestV3, 'populate_sell_trend')
def test_strategy_test_v2(result, fee):
@pytest.mark.parametrize('is_short,side', [
(True, 'short'),
(False, 'long'),
])
def test_strategy_test_v3(result, fee, is_short, side):
strategy = StrategyTestV3({})
metadata = {'pair': 'ETH/BTC'}
@@ -32,16 +37,18 @@ def test_strategy_test_v2(result, fee):
open_rate=19_000,
amount=0.1,
pair='ETH/BTC',
fee_open=fee.return_value
fee_open=fee.return_value,
is_short=is_short
)
assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc',
current_time=datetime.utcnow(), side='long') is True
current_time=datetime.utcnow(),
side=side) is True
assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc', sell_reason='roi',
current_time=datetime.utcnow()) is True
current_time=datetime.utcnow(),
side=side) is True
# TODO-lev: Test for shorts?
assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(),
current_rate=20_000, current_profit=0.05) == strategy.stoploss

View File

@@ -906,7 +906,6 @@ def test_execute_entry_confirm_error(mocker, default_conf_usdt, fee, limit_order
pair = 'ETH/USDT'
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError)
# TODO-lev: KeyError happens on short, why?
assert freqtrade.execute_entry(pair, stake_amount)
limit_order[enter_side(is_short)]['id'] = '222'
@@ -1228,7 +1227,6 @@ def test_create_stoploss_order_insufficient_funds(
def test_handle_stoploss_on_exchange_trailing(
mocker, default_conf_usdt, fee, is_short, bid, ask, limit_order, stop_price, amt, hang_price
) -> None:
# TODO-lev: test for short
# When trailing stoploss is set
enter_order = limit_order[enter_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
@@ -1435,7 +1433,6 @@ def test_handle_stoploss_on_exchange_custom_stop(
enter_order = limit_order[enter_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
# When trailing stoploss is set
# TODO-lev: test for short
stoploss = MagicMock(return_value={'id': 13434334})
patch_RPCManager(mocker)
mocker.patch.multiple(
@@ -2973,9 +2970,11 @@ def test_execute_trade_exit_custom_exit_price(
# Set a custom exit price
freqtrade.strategy.custom_exit_price = lambda **kwargs: 2.25
# TODO-lev: side="buy"
freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL))
freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)
)
# Sell price must be different to default bid price
@@ -3098,7 +3097,6 @@ def test_execute_trade_exit_sloe_cancel_exception(
freqtrade.config['dry_run'] = False
trade.stoploss_order_id = "abcd"
# TODO-lev: side="buy"
freqtrade.execute_trade_exit(trade=trade, limit=1234,
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert create_order_mock.call_count == 2
@@ -3152,9 +3150,11 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
fetch_ticker=ticker_usdt_sell_up
)
# TODO-lev: side="buy"
freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)
)
trade = Trade.query.first()
trade.is_short = is_short
@@ -3288,7 +3288,6 @@ def test_execute_trade_exit_market_order(
)
freqtrade.config['order_types']['sell'] = 'market'
# TODO-lev: side="buy"
freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
@@ -3354,9 +3353,11 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
)
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
# TODO-lev: side="buy"
assert not freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_up()['bid'],
sell_reason=sell_reason)
assert not freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=sell_reason
)
assert mock_insuf.call_count == 1
@@ -3517,9 +3518,11 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
fetch_ticker=ticker_usdt_sell_down
)
# TODO-lev: side="buy"
freqtrade.execute_trade_exit(trade=trade, limit=ticker_usdt_sell_down()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
freqtrade.execute_trade_exit(
trade=trade,
limit=ticker_usdt_sell_down()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)
)
trade.close(ticker_usdt_sell_down()['bid'])
assert freqtrade.strategy.is_pair_locked(trade.pair)
@@ -4697,8 +4700,8 @@ def test_leverage_prep():
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:07"),
('futures', 33, "2021-08-31 23:59:58", "2021-09-01 08:00:07"),
])
def test_update_funding_fees(mocker, default_conf, trading_mode, calls, time_machine,
t1, t2):
def test_update_funding_fees_schedule(mocker, default_conf, trading_mode, calls, time_machine,
t1, t2):
time_machine.move_to(f"{t1} +00:00")
patch_RPCManager(mocker)
@@ -4713,3 +4716,159 @@ def test_update_funding_fees(mocker, default_conf, trading_mode, calls, time_mac
freqtrade._schedule.run_pending()
assert freqtrade.update_funding_fees.call_count == calls
@pytest.mark.parametrize('schedule_off', [False, True])
@pytest.mark.parametrize('is_short', [True, False])
def test_update_funding_fees(
mocker,
default_conf,
time_machine,
fee,
ticker_usdt_sell_up,
is_short,
limit_order_open,
schedule_off
):
'''
nominal_value = mark_price * size
funding_fee = nominal_value * funding_rate
size = 123
"LTC/BTC"
time: 0, mark: 3.3, fundRate: 0.00032583, nominal_value: 405.9, fundFee: 0.132254397
time: 8, mark: 3.2, fundRate: 0.00024472, nominal_value: 393.6, fundFee: 0.096321792
"ETH/BTC"
time: 0, mark: 2.4, fundRate: 0.0001, nominal_value: 295.2, fundFee: 0.02952
time: 8, mark: 2.5, fundRate: 0.0001, nominal_value: 307.5, fundFee: 0.03075
"ETC/BTC"
time: 0, mark: 4.3, fundRate: 0.00031077, nominal_value: 528.9, fundFee: 0.164366253
time: 8, mark: 4.1, fundRate: 0.00022655, nominal_value: 504.3, fundFee: 0.114249165
"XRP/BTC"
time: 0, mark: 1.2, fundRate: 0.00049426, nominal_value: 147.6, fundFee: 0.072952776
time: 8, mark: 1.2, fundRate: 0.00032715, nominal_value: 147.6, fundFee: 0.04828734
'''
# SETUP
time_machine.move_to("2021-09-01 00:00:00 +00:00")
open_order = limit_order_open[enter_side(is_short)]
open_exit_order = limit_order_open[exit_side(is_short)]
bid = 0.11
enter_rate_mock = MagicMock(return_value=bid)
enter_mm = MagicMock(return_value=open_order)
patch_RPCManager(mocker)
patch_exchange(mocker)
default_conf['trading_mode'] = 'futures'
default_conf['collateral'] = 'isolated'
default_conf['dry_run'] = True
timestamp_midnight = 1630454400000
timestamp_eight = 1630483200000
funding_rates_midnight = {
"LTC/BTC": {
timestamp_midnight: 0.00032583,
},
"ETH/BTC": {
timestamp_midnight: 0.0001,
},
"XRP/BTC": {
timestamp_midnight: 0.00049426,
}
}
funding_rates_eight = {
"LTC/BTC": {
timestamp_midnight: 0.00032583,
timestamp_eight: 0.00024472,
},
"ETH/BTC": {
timestamp_midnight: 0.0001,
timestamp_eight: 0.0001,
},
"XRP/BTC": {
timestamp_midnight: 0.00049426,
timestamp_eight: 0.00032715,
}
}
mark_prices = {
"LTC/BTC": {
timestamp_midnight: 3.3,
timestamp_eight: 3.2,
},
"ETH/BTC": {
timestamp_midnight: 2.4,
timestamp_eight: 2.5,
},
"XRP/BTC": {
timestamp_midnight: 1.2,
timestamp_eight: 1.2,
}
}
mocker.patch(
'freqtrade.exchange.Exchange._get_mark_price_history',
side_effect=lambda pair, since: mark_prices[pair]
)
mocker.patch(
'freqtrade.exchange.Exchange.get_funding_rate_history',
side_effect=lambda pair, since: funding_rates_midnight[pair]
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_rate=enter_rate_mock,
fetch_ticker=MagicMock(return_value={
'bid': 1.9,
'ask': 2.2,
'last': 1.9
}),
create_order=enter_mm,
get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee,
)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
# initial funding fees,
freqtrade.execute_entry('ETH/BTC', 123)
freqtrade.execute_entry('LTC/BTC', 2.0)
freqtrade.execute_entry('XRP/BTC', 123)
trades = Trade.get_open_trades()
assert len(trades) == 3
for trade in trades:
assert trade.funding_fees == (
trade.amount *
mark_prices[trade.pair][timestamp_midnight] *
funding_rates_midnight[trade.pair][timestamp_midnight]
)
mocker.patch('freqtrade.exchange.Exchange.create_order', return_value=open_exit_order)
# create_mock_trades(fee, False)
time_machine.move_to("2021-09-01 08:00:00 +00:00")
mocker.patch(
'freqtrade.exchange.Exchange.get_funding_rate_history',
side_effect=lambda pair, since: funding_rates_eight[pair]
)
if schedule_off:
for trade in trades:
assert trade.funding_fees == (
trade.amount *
mark_prices[trade.pair][timestamp_midnight] *
funding_rates_eight[trade.pair][timestamp_midnight]
)
freqtrade.execute_trade_exit(
trade=trade,
# The values of the next 2 params are irrelevant for this test
limit=ticker_usdt_sell_up()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.ROI)
)
else:
freqtrade._schedule.run_pending()
# Funding fees for 00:00 and 08:00
for trade in trades:
assert trade.funding_fees == sum([
trade.amount *
mark_prices[trade.pair][time] *
funding_rates_eight[trade.pair][time] for time in mark_prices[trade.pair].keys()
])

View File

@@ -1907,12 +1907,12 @@ def test_get_total_closed_profit(fee, use_db):
@pytest.mark.usefixtures("init_persistence")
# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
@pytest.mark.parametrize('is_short', [True, False])
@pytest.mark.parametrize('use_db', [True, False])
def test_get_trades_proxy(fee, use_db):
def test_get_trades_proxy(fee, use_db, is_short):
Trade.use_db = use_db
Trade.reset_trades()
create_mock_trades(fee, False, use_db)
create_mock_trades(fee, is_short, use_db)
trades = Trade.get_trades_proxy()
assert len(trades) == 6
@@ -2042,48 +2042,48 @@ def test_update_order_from_ccxt(caplog):
@pytest.mark.usefixtures("init_persistence")
# TODO-lev: @pytest.mark.parametrize('is_short', [True, False])
def test_select_order(fee):
create_mock_trades(fee, False)
@pytest.mark.parametrize('is_short', [True, False])
def test_select_order(fee, is_short):
create_mock_trades(fee, is_short)
trades = Trade.get_trades().all()
# Open buy order, no sell order
order = trades[0].select_order('buy', True)
order = trades[0].select_order(trades[0].enter_side, True)
assert order is None
order = trades[0].select_order('buy', False)
order = trades[0].select_order(trades[0].enter_side, False)
assert order is not None
order = trades[0].select_order('sell', None)
order = trades[0].select_order(trades[0].exit_side, None)
assert order is None
# closed buy order, and open sell order
order = trades[1].select_order('buy', True)
order = trades[1].select_order(trades[1].enter_side, True)
assert order is None
order = trades[1].select_order('buy', False)
order = trades[1].select_order(trades[1].enter_side, False)
assert order is not None
order = trades[1].select_order('buy', None)
order = trades[1].select_order(trades[1].enter_side, None)
assert order is not None
order = trades[1].select_order('sell', True)
order = trades[1].select_order(trades[1].exit_side, True)
assert order is None
order = trades[1].select_order('sell', False)
order = trades[1].select_order(trades[1].exit_side, False)
assert order is not None
# Has open buy order
order = trades[3].select_order('buy', True)
order = trades[3].select_order(trades[3].enter_side, True)
assert order is not None
order = trades[3].select_order('buy', False)
order = trades[3].select_order(trades[3].enter_side, False)
assert order is None
# Open sell order
order = trades[4].select_order('buy', True)
order = trades[4].select_order(trades[4].enter_side, True)
assert order is None
order = trades[4].select_order('buy', False)
order = trades[4].select_order(trades[4].enter_side, False)
assert order is not None
order = trades[4].select_order('sell', True)
order = trades[4].select_order(trades[4].exit_side, True)
assert order is not None
assert order.ft_order_side == 'stoploss'
order = trades[4].select_order('sell', False)
order = trades[4].select_order(trades[4].exit_side, False)
assert order is None