Merge branch 'develop' into pr/samgermain/6780
This commit is contained in:
@@ -154,6 +154,7 @@ def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side):
|
||||
order = {
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 1500,
|
||||
'stopPrice': 1500,
|
||||
'info': {'stopPrice': 1500},
|
||||
}
|
||||
assert exchange.stoploss_adjust(sl1, order, side=side)
|
||||
@@ -490,11 +491,11 @@ def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker, leverage_tiers
|
||||
default_conf['margin_mode'] = MarginMode.ISOLATED
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
|
||||
exchange.fill_leverage_tiers()
|
||||
|
||||
leverage_tiers = leverage_tiers
|
||||
|
||||
assert len(exchange._leverage_tiers.keys()) > 100
|
||||
for key, value in leverage_tiers.items():
|
||||
assert exchange._leverage_tiers[key] == value
|
||||
v = exchange._leverage_tiers[key]
|
||||
assert isinstance(v, list)
|
||||
assert len(v) == len(value)
|
||||
|
||||
|
||||
def test__set_leverage_binance(mocker, default_conf):
|
||||
|
@@ -13,6 +13,7 @@ import pytest
|
||||
|
||||
from freqtrade.enums import CandleType
|
||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
|
||||
from freqtrade.exchange.exchange import timeframe_to_msecs
|
||||
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
||||
from tests.conftest import get_default_conf_usdt
|
||||
|
||||
@@ -135,6 +136,7 @@ def exchange_futures(request, exchange_conf, class_mocker):
|
||||
class_mocker.patch(
|
||||
'freqtrade.exchange.binance.Binance.fill_leverage_tiers')
|
||||
class_mocker.patch('freqtrade.exchange.exchange.Exchange.fetch_trading_fees')
|
||||
class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init')
|
||||
exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True)
|
||||
|
||||
yield exchange, request.param
|
||||
@@ -151,6 +153,25 @@ class TestCCXTExchange():
|
||||
assert isinstance(markets[pair], dict)
|
||||
assert exchange.market_is_spot(markets[pair])
|
||||
|
||||
def test_has_validations(self, exchange):
|
||||
|
||||
exchange, exchangename = exchange
|
||||
|
||||
exchange.validate_ordertypes({
|
||||
'entry': 'limit',
|
||||
'exit': 'limit',
|
||||
'stoploss': 'limit',
|
||||
})
|
||||
|
||||
if exchangename == 'gateio':
|
||||
# gateio doesn't have market orders on spot
|
||||
return
|
||||
exchange.validate_ordertypes({
|
||||
'entry': 'market',
|
||||
'exit': 'market',
|
||||
'stoploss': 'market',
|
||||
})
|
||||
|
||||
def test_load_markets_futures(self, exchange_futures):
|
||||
exchange, exchangename = exchange_futures
|
||||
if not exchange:
|
||||
@@ -197,8 +218,13 @@ class TestCCXTExchange():
|
||||
l2 = exchange.fetch_l2_order_book(pair)
|
||||
assert 'asks' in l2
|
||||
assert 'bids' in l2
|
||||
assert len(l2['asks']) >= 1
|
||||
assert len(l2['bids']) >= 1
|
||||
l2_limit_range = exchange._ft_has['l2_limit_range']
|
||||
l2_limit_range_required = exchange._ft_has['l2_limit_range_required']
|
||||
if exchangename == 'gateio':
|
||||
# TODO: Gateio is unstable here at the moment, ignoring the limit partially.
|
||||
return
|
||||
for val in [1, 2, 5, 25, 100]:
|
||||
l2 = exchange.fetch_l2_order_book(pair, val)
|
||||
if not l2_limit_range or val in l2_limit_range:
|
||||
@@ -218,7 +244,7 @@ class TestCCXTExchange():
|
||||
assert len(l2['asks']) == next_limit
|
||||
assert len(l2['asks']) == next_limit
|
||||
|
||||
def test_fetch_ohlcv(self, exchange):
|
||||
def test_ccxt_fetch_ohlcv(self, exchange):
|
||||
exchange, exchangename = exchange
|
||||
pair = EXCHANGES[exchangename]['pair']
|
||||
timeframe = EXCHANGES[exchangename]['timeframe']
|
||||
@@ -230,11 +256,44 @@ class TestCCXTExchange():
|
||||
assert len(ohlcv[pair_tf]) == len(exchange.klines(pair_tf))
|
||||
# assert len(exchange.klines(pair_tf)) > 200
|
||||
# Assume 90% uptime ...
|
||||
assert len(exchange.klines(pair_tf)) > exchange.ohlcv_candle_limit(timeframe) * 0.90
|
||||
assert len(exchange.klines(pair_tf)) > exchange.ohlcv_candle_limit(
|
||||
timeframe, CandleType.SPOT) * 0.90
|
||||
# Check if last-timeframe is within the last 2 intervals
|
||||
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)
|
||||
|
||||
def test_ccxt__async_get_candle_history(self, exchange):
|
||||
exchange, exchangename = exchange
|
||||
# For some weired reason, this test returns random lengths for bittrex.
|
||||
if not exchange._ft_has['ohlcv_has_history'] or exchangename == 'bittrex':
|
||||
return
|
||||
pair = EXCHANGES[exchangename]['pair']
|
||||
timeframe = EXCHANGES[exchangename]['timeframe']
|
||||
candle_type = CandleType.SPOT
|
||||
timeframe_ms = timeframe_to_msecs(timeframe)
|
||||
now = timeframe_to_prev_date(
|
||||
timeframe, datetime.now(timezone.utc))
|
||||
for offset in (360, 120, 30, 10, 5, 2):
|
||||
since = now - timedelta(days=offset)
|
||||
since_ms = int(since.timestamp() * 1000)
|
||||
|
||||
res = exchange.loop.run_until_complete(exchange._async_get_candle_history(
|
||||
pair=pair,
|
||||
timeframe=timeframe,
|
||||
since_ms=since_ms,
|
||||
candle_type=candle_type
|
||||
)
|
||||
)
|
||||
assert res
|
||||
assert res[0] == pair
|
||||
assert res[1] == timeframe
|
||||
assert res[2] == candle_type
|
||||
candles = res[3]
|
||||
candle_count = exchange.ohlcv_candle_limit(timeframe, candle_type, since_ms) * 0.9
|
||||
candle_count1 = (now.timestamp() * 1000 - since_ms) // timeframe_ms
|
||||
assert len(candles) >= min(candle_count, candle_count1)
|
||||
assert candles[0][0] == since_ms or (since_ms + timeframe_ms)
|
||||
|
||||
def test_ccxt_fetch_funding_rate_history(self, exchange_futures):
|
||||
exchange, exchangename = exchange_futures
|
||||
if not exchange:
|
||||
|
75
tests/exchange/test_ccxt_precise.py
Normal file
75
tests/exchange/test_ccxt_precise.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from ccxt import Precise
|
||||
|
||||
|
||||
ws = Precise('-1.123e-6')
|
||||
ws = Precise('-1.123e-6')
|
||||
xs = Precise('0.00000002')
|
||||
ys = Precise('69696900000')
|
||||
zs = Precise('0')
|
||||
|
||||
|
||||
def test_precise():
|
||||
assert ys * xs == '1393.938'
|
||||
assert xs * ys == '1393.938'
|
||||
|
||||
assert ys + xs == '69696900000.00000002'
|
||||
assert xs + ys == '69696900000.00000002'
|
||||
assert xs - ys == '-69696899999.99999998'
|
||||
assert ys - xs == '69696899999.99999998'
|
||||
assert xs / ys == '0'
|
||||
assert ys / xs == '3484845000000000000'
|
||||
|
||||
assert ws * xs == '-0.00000000000002246'
|
||||
assert xs * ws == '-0.00000000000002246'
|
||||
|
||||
assert ws + xs == '-0.000001103'
|
||||
assert xs + ws == '-0.000001103'
|
||||
|
||||
assert xs - ws == '0.000001143'
|
||||
assert ws - xs == '-0.000001143'
|
||||
|
||||
assert xs / ws == '-0.017809439002671415'
|
||||
assert ws / xs == '-56.15'
|
||||
|
||||
assert zs * ws == '0'
|
||||
assert zs * xs == '0'
|
||||
assert zs * ys == '0'
|
||||
assert ws * zs == '0'
|
||||
assert xs * zs == '0'
|
||||
assert ys * zs == '0'
|
||||
|
||||
assert zs + ws == '-0.000001123'
|
||||
assert zs + xs == '0.00000002'
|
||||
assert zs + ys == '69696900000'
|
||||
assert ws + zs == '-0.000001123'
|
||||
assert xs + zs == '0.00000002'
|
||||
assert ys + zs == '69696900000'
|
||||
|
||||
assert abs(Precise('-500.1')) == '500.1'
|
||||
assert abs(Precise('213')) == '213'
|
||||
|
||||
assert abs(Precise('-500.1')) == '500.1'
|
||||
assert -Precise('213') == '-213'
|
||||
|
||||
assert Precise('10.1') % Precise('0.5') == '0.1'
|
||||
assert Precise('5550') % Precise('120') == '30'
|
||||
|
||||
assert Precise('-0.0') == Precise('0')
|
||||
assert Precise('5.534000') == Precise('5.5340')
|
||||
|
||||
assert min(Precise('-3.1415'), Precise('-2')) == '-3.1415'
|
||||
|
||||
assert max(Precise('3.1415'), Precise('-2')) == '3.1415'
|
||||
|
||||
assert Precise('2') > Precise('1.2345')
|
||||
assert not Precise('-3.1415') > Precise('-2')
|
||||
assert not Precise('3.1415') > Precise('3.1415')
|
||||
assert Precise.string_gt('3.14150000000000000000001', '3.1415')
|
||||
|
||||
assert Precise('3.1415') >= Precise('3.1415')
|
||||
assert Precise('3.14150000000000000000001') >= Precise('3.1415')
|
||||
|
||||
assert not Precise('3.1415') < Precise('3.1415')
|
||||
|
||||
assert Precise('3.1415') <= Precise('3.1415')
|
||||
assert Precise('3.1415') <= Precise('3.14150000000000000000001')
|
@@ -17,9 +17,9 @@ from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOr
|
||||
from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken
|
||||
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
|
||||
calculate_backoff, remove_credentials)
|
||||
from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes, timeframe_to_msecs,
|
||||
timeframe_to_next_date, timeframe_to_prev_date,
|
||||
timeframe_to_seconds)
|
||||
from freqtrade.exchange.exchange import (date_minus_candles, market_is_active, timeframe_to_minutes,
|
||||
timeframe_to_msecs, timeframe_to_next_date,
|
||||
timeframe_to_prev_date, timeframe_to_seconds)
|
||||
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
||||
from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has_re, num_log_has_re
|
||||
|
||||
@@ -99,6 +99,8 @@ def test_remove_credentials(default_conf, caplog) -> None:
|
||||
def test_init_ccxt_kwargs(default_conf, mocker, caplog):
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||
aei_mock = mocker.patch('freqtrade.exchange.Exchange.additional_exchange_init')
|
||||
|
||||
caplog.set_level(logging.INFO)
|
||||
conf = copy.deepcopy(default_conf)
|
||||
conf['exchange']['ccxt_async_config'] = {'aiohttp_trust_env': True, 'asyncio_loop': True}
|
||||
@@ -108,6 +110,7 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog):
|
||||
caplog)
|
||||
assert ex._api_async.aiohttp_trust_env
|
||||
assert not ex._api.aiohttp_trust_env
|
||||
assert aei_mock.call_count == 1
|
||||
|
||||
# Reset logging and config
|
||||
caplog.clear()
|
||||
@@ -302,6 +305,7 @@ def test_amount_to_precision(
|
||||
(234.53, 4, 0.5, 235.0),
|
||||
(0.891534, 4, 0.0001, 0.8916),
|
||||
(64968.89, 4, 0.01, 64968.89),
|
||||
(0.000000003483, 4, 1e-12, 0.000000003483),
|
||||
|
||||
])
|
||||
def test_price_to_precision(default_conf, mocker, price, precision_mode, precision, expected):
|
||||
@@ -936,6 +940,7 @@ def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker):
|
||||
|
||||
|
||||
def test_validate_timeframes_not_in_config(default_conf, mocker):
|
||||
# TODO: this test does not assert ...
|
||||
del default_conf["timeframe"]
|
||||
api_mock = MagicMock()
|
||||
id_mock = PropertyMock(return_value='test_exchange')
|
||||
@@ -951,6 +956,7 @@ def test_validate_timeframes_not_in_config(default_conf, mocker):
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_required_startup_candles')
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
@@ -1081,6 +1087,13 @@ def test_validate_required_startup_candles(default_conf, mocker, caplog):
|
||||
with pytest.raises(OperationalException, match=r'This strategy requires 6000.*'):
|
||||
Exchange(default_conf)
|
||||
|
||||
# Emulate kraken mode
|
||||
ex._ft_has['ohlcv_has_history'] = False
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'This strategy requires 2500.*, '
|
||||
r'which is more than the amount.*'):
|
||||
ex.validate_required_startup_candles(2500, '5m')
|
||||
|
||||
|
||||
def test_exchange_has(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
@@ -1122,7 +1135,7 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverag
|
||||
assert order["symbol"] == "ETH/BTC"
|
||||
assert order["amount"] == 1
|
||||
assert order["leverage"] == leverage
|
||||
assert order["cost"] == 1 * 200 / leverage
|
||||
assert order["cost"] == 1 * 200
|
||||
|
||||
|
||||
@pytest.mark.parametrize('side,is_short,order_reason', [
|
||||
@@ -1913,7 +1926,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_
|
||||
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
|
||||
# one_call calculation * 1.8 should do 2 calls
|
||||
|
||||
since = 5 * 60 * exchange.ohlcv_candle_limit('5m') * 1.8
|
||||
since = 5 * 60 * exchange.ohlcv_candle_limit('5m', CandleType.SPOT) * 1.8
|
||||
ret = exchange.get_historic_ohlcv(
|
||||
pair,
|
||||
"5m",
|
||||
@@ -1979,7 +1992,7 @@ def test_get_historic_ohlcv_as_df(default_conf, mocker, exchange_name, candle_ty
|
||||
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
|
||||
# one_call calculation * 1.8 should do 2 calls
|
||||
|
||||
since = 5 * 60 * exchange.ohlcv_candle_limit('5m') * 1.8
|
||||
since = 5 * 60 * exchange.ohlcv_candle_limit('5m', CandleType.SPOT) * 1.8
|
||||
ret = exchange.get_historic_ohlcv_as_df(
|
||||
pair,
|
||||
"5m",
|
||||
@@ -2033,7 +2046,7 @@ async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_
|
||||
)
|
||||
# Required candles
|
||||
candles = (end_ts - start_ts) / 300_000
|
||||
exp = candles // exchange.ohlcv_candle_limit('5m') + 1
|
||||
exp = candles // exchange.ohlcv_candle_limit('5m', CandleType.SPOT) + 1
|
||||
|
||||
# Depending on the exchange, this should be called between 1 and 6 times.
|
||||
assert exchange._api_async.fetch_ohlcv.call_count == exp
|
||||
@@ -2183,6 +2196,8 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog):
|
||||
from freqtrade.exchange.common import _reset_logging_mixin
|
||||
_reset_logging_mixin()
|
||||
caplog.set_level(logging.INFO)
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.DDoSProtection(
|
||||
@@ -2836,6 +2851,7 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange
|
||||
until=trades_history[-1][0])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
|
||||
default_conf['dry_run'] = True
|
||||
@@ -3001,6 +3017,7 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
|
||||
exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_fetch_order(default_conf, mocker, exchange_name, caplog):
|
||||
default_conf['dry_run'] = True
|
||||
@@ -3053,6 +3070,7 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog):
|
||||
order_id='_', pair='TKN/BTC')
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
|
||||
# Don't test FTX here - that needs a separate test
|
||||
@@ -3380,7 +3398,7 @@ def test_ohlcv_candle_limit(default_conf, mocker, exchange_name):
|
||||
expected = exchange._ft_has['ohlcv_candle_limit_per_timeframe'][timeframe]
|
||||
# This should only run for bittrex
|
||||
assert exchange_name == 'bittrex'
|
||||
assert exchange.ohlcv_candle_limit(timeframe) == expected
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.SPOT) == expected
|
||||
|
||||
|
||||
def test_timeframe_to_minutes():
|
||||
@@ -3462,6 +3480,17 @@ def test_timeframe_to_next_date():
|
||||
assert timeframe_to_next_date("5m", date) == date + timedelta(minutes=5)
|
||||
|
||||
|
||||
def test_date_minus_candles():
|
||||
|
||||
date = datetime(2019, 8, 12, 13, 25, 0, tzinfo=timezone.utc)
|
||||
|
||||
assert date_minus_candles("5m", 3, date) == date - timedelta(minutes=15)
|
||||
assert date_minus_candles("5m", 5, date) == date - timedelta(minutes=25)
|
||||
assert date_minus_candles("1m", 6, date) == date - timedelta(minutes=6)
|
||||
assert date_minus_candles("1h", 3, date) == date - timedelta(hours=3, minutes=25)
|
||||
assert date_minus_candles("1h", 3) == timeframe_to_prev_date('1h') - timedelta(hours=3)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"market_symbol,base,quote,exchange,spot,margin,futures,trademode,add_dict,expected_result",
|
||||
[
|
||||
@@ -3556,7 +3585,7 @@ def test_order_has_fee(order, expected) -> None:
|
||||
def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.calculate_fee_rate', MagicMock(return_value=0.01))
|
||||
ex = get_patched_exchange(mocker, default_conf)
|
||||
assert ex.extract_cost_curr_rate(order) == expected
|
||||
assert ex.extract_cost_curr_rate(order['fee'], order['symbol'], cost=20, amount=1) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("order,unknown_fee_rate,expected", [
|
||||
@@ -3594,6 +3623,9 @@ def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
|
||||
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 1, 4.0),
|
||||
({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5,
|
||||
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 2, 8.0),
|
||||
# Missing currency
|
||||
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
|
||||
'fee': {'currency': None, 'cost': 0.005}}, None, None),
|
||||
])
|
||||
def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_rate) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081})
|
||||
@@ -3602,7 +3634,8 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_r
|
||||
|
||||
ex = get_patched_exchange(mocker, default_conf)
|
||||
|
||||
assert ex.calculate_fee_rate(order) == expected
|
||||
assert ex.calculate_fee_rate(order['fee'], order['symbol'],
|
||||
cost=order['cost'], amount=order['amount']) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('retrycount,max_retries,expected', [
|
||||
@@ -3831,6 +3864,7 @@ def test_validate_trading_mode_and_margin_mode(
|
||||
("bibox", "spot", {"has": {"fetchCurrencies": False}}),
|
||||
("bibox", "margin", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "margin"}}),
|
||||
("bibox", "futures", {"has": {"fetchCurrencies": False}, "options": {"defaultType": "swap"}}),
|
||||
("bybit", "spot", {"options": {"defaultType": "spot"}}),
|
||||
("bybit", "futures", {"options": {"defaultType": "linear"}}),
|
||||
("ftx", "futures", {"options": {"defaultType": "swap"}}),
|
||||
("gateio", "futures", {"options": {"defaultType": "swap"}}),
|
||||
@@ -3929,6 +3963,70 @@ def test_calculate_funding_fees(
|
||||
) == kraken_fee
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'mark_price,funding_rate,futures_funding_rate', [
|
||||
(1000, 0.001, None),
|
||||
(1000, 0.001, 0.01),
|
||||
(1000, 0.001, 0.0),
|
||||
(1000, 0.001, -0.01),
|
||||
])
|
||||
def test_combine_funding_and_mark(
|
||||
default_conf,
|
||||
mocker,
|
||||
funding_rate,
|
||||
mark_price,
|
||||
futures_funding_rate,
|
||||
):
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
prior2_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc) - timedelta(hours=2))
|
||||
prior_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc) - timedelta(hours=1))
|
||||
trade_date = timeframe_to_prev_date('1h', datetime.now(timezone.utc))
|
||||
funding_rates = DataFrame([
|
||||
{'date': prior2_date, 'open': funding_rate},
|
||||
{'date': prior_date, 'open': funding_rate},
|
||||
{'date': trade_date, 'open': funding_rate},
|
||||
])
|
||||
mark_rates = DataFrame([
|
||||
{'date': prior2_date, 'open': mark_price},
|
||||
{'date': prior_date, 'open': mark_price},
|
||||
{'date': trade_date, 'open': mark_price},
|
||||
])
|
||||
|
||||
df = exchange.combine_funding_and_mark(funding_rates, mark_rates, futures_funding_rate)
|
||||
assert 'open_mark' in df.columns
|
||||
assert 'open_fund' in df.columns
|
||||
assert len(df) == 3
|
||||
|
||||
funding_rates = DataFrame([
|
||||
{'date': trade_date, 'open': funding_rate},
|
||||
])
|
||||
mark_rates = DataFrame([
|
||||
{'date': prior2_date, 'open': mark_price},
|
||||
{'date': prior_date, 'open': mark_price},
|
||||
{'date': trade_date, 'open': mark_price},
|
||||
])
|
||||
df = exchange.combine_funding_and_mark(funding_rates, mark_rates, futures_funding_rate)
|
||||
|
||||
if futures_funding_rate is not None:
|
||||
assert len(df) == 3
|
||||
assert df.iloc[0]['open_fund'] == futures_funding_rate
|
||||
assert df.iloc[1]['open_fund'] == futures_funding_rate
|
||||
assert df.iloc[2]['open_fund'] == funding_rate
|
||||
else:
|
||||
assert len(df) == 1
|
||||
|
||||
# Empty funding rates
|
||||
funding_rates = DataFrame([], columns=['date', 'open'])
|
||||
df = exchange.combine_funding_and_mark(funding_rates, mark_rates, futures_funding_rate)
|
||||
if futures_funding_rate is not None:
|
||||
assert len(df) == 3
|
||||
assert df.iloc[0]['open_fund'] == futures_funding_rate
|
||||
assert df.iloc[1]['open_fund'] == futures_funding_rate
|
||||
assert df.iloc[2]['open_fund'] == futures_funding_rate
|
||||
else:
|
||||
assert len(df) == 0
|
||||
|
||||
|
||||
def test_get_or_calculate_liquidation_price(mocker, default_conf):
|
||||
|
||||
api_mock = MagicMock()
|
||||
@@ -4799,8 +4897,10 @@ def test__get_params(mocker, default_conf, exchange_name):
|
||||
|
||||
if exchange_name == 'okx':
|
||||
params2['tdMode'] = 'isolated'
|
||||
params2['posSide'] = 'net'
|
||||
|
||||
assert exchange._get_params(
|
||||
side="buy",
|
||||
ordertype='market',
|
||||
reduceOnly=False,
|
||||
time_in_force='gtc',
|
||||
@@ -4808,6 +4908,7 @@ def test__get_params(mocker, default_conf, exchange_name):
|
||||
) == params1
|
||||
|
||||
assert exchange._get_params(
|
||||
side="buy",
|
||||
ordertype='market',
|
||||
reduceOnly=False,
|
||||
time_in_force='ioc',
|
||||
@@ -4815,6 +4916,7 @@ def test__get_params(mocker, default_conf, exchange_name):
|
||||
) == params1
|
||||
|
||||
assert exchange._get_params(
|
||||
side="buy",
|
||||
ordertype='limit',
|
||||
reduceOnly=False,
|
||||
time_in_force='gtc',
|
||||
@@ -4827,6 +4929,7 @@ def test__get_params(mocker, default_conf, exchange_name):
|
||||
exchange._params = {'test': True}
|
||||
|
||||
assert exchange._get_params(
|
||||
side="buy",
|
||||
ordertype='limit',
|
||||
reduceOnly=True,
|
||||
time_in_force='ioc',
|
||||
|
@@ -174,6 +174,7 @@ def test_stoploss_adjust_ftx(mocker, default_conf, sl1, sl2, sl3, side):
|
||||
assert not exchange.stoploss_adjust(sl3, order, side=side)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order, limit_buy_order):
|
||||
default_conf['dry_run'] = True
|
||||
order = MagicMock()
|
||||
|
@@ -33,7 +33,14 @@ def test_validate_order_types_gateio(default_conf, mocker):
|
||||
match=r'Exchange .* does not support market orders.'):
|
||||
ExchangeResolver.load_exchange('gateio', default_conf, True)
|
||||
|
||||
# market-orders supported on futures markets.
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
default_conf['margin_mode'] = 'isolated'
|
||||
ex = ExchangeResolver.load_exchange('gateio', default_conf, True)
|
||||
assert ex
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_fetch_stoploss_order_gateio(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='gateio')
|
||||
|
||||
@@ -46,6 +53,25 @@ def test_fetch_stoploss_order_gateio(default_conf, mocker):
|
||||
assert fetch_order_mock.call_args_list[0][1]['pair'] == 'ETH/BTC'
|
||||
assert fetch_order_mock.call_args_list[0][1]['params'] == {'stop': True}
|
||||
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
default_conf['margin_mode'] = 'isolated'
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='gateio')
|
||||
|
||||
exchange.fetch_order = MagicMock(return_value={
|
||||
'status': 'closed',
|
||||
'id': '1234',
|
||||
'stopPrice': 5.62,
|
||||
'info': {
|
||||
'trade_id': '222555'
|
||||
}
|
||||
})
|
||||
|
||||
exchange.fetch_stoploss_order('1234', 'ETH/BTC')
|
||||
assert exchange.fetch_order.call_count == 2
|
||||
assert exchange.fetch_order.call_args_list[0][1]['order_id'] == '1234'
|
||||
assert exchange.fetch_order.call_args_list[1][1]['order_id'] == '222555'
|
||||
|
||||
|
||||
def test_cancel_stoploss_order_gateio(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='gateio')
|
||||
|
@@ -123,5 +123,5 @@ def test_stoploss_adjust_kucoin(mocker, default_conf):
|
||||
assert exchange.stoploss_adjust(1501, order, 'sell')
|
||||
assert not exchange.stoploss_adjust(1499, order, 'sell')
|
||||
# Test with invalid order case
|
||||
order['info']['stop'] = None
|
||||
assert not exchange.stoploss_adjust(1501, order, 'sell')
|
||||
order['stopPrice'] = None
|
||||
assert exchange.stoploss_adjust(1501, order, 'sell')
|
||||
|
@@ -1,7 +1,40 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.enums import MarginMode, TradingMode
|
||||
from tests.conftest import get_patched_exchange
|
||||
from freqtrade.enums.candletype import CandleType
|
||||
from freqtrade.exchange.exchange import timeframe_to_minutes
|
||||
from tests.conftest import get_mock_coro, get_patched_exchange
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
|
||||
def test_okx_ohlcv_candle_limit(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='okx')
|
||||
timeframes = ('1m', '5m', '1h')
|
||||
start_time = int(datetime(2021, 1, 1, tzinfo=timezone.utc).timestamp() * 1000)
|
||||
|
||||
for timeframe in timeframes:
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.SPOT) == 300
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.FUTURES) == 300
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.MARK) == 100
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.FUNDING_RATE) == 100
|
||||
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.SPOT, start_time) == 100
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.FUTURES, start_time) == 100
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.MARK, start_time) == 100
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.FUNDING_RATE, start_time) == 100
|
||||
one_call = int((datetime.now(timezone.utc) - timedelta(
|
||||
minutes=290 * timeframe_to_minutes(timeframe))).timestamp() * 1000)
|
||||
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.SPOT, one_call) == 300
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.FUTURES, one_call) == 300
|
||||
|
||||
one_call = int((datetime.now(timezone.utc) - timedelta(
|
||||
minutes=320 * timeframe_to_minutes(timeframe))).timestamp() * 1000)
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.SPOT, one_call) == 100
|
||||
assert exchange.ohlcv_candle_limit(timeframe, CandleType.FUTURES, one_call) == 100
|
||||
|
||||
|
||||
def test_get_maintenance_ratio_and_amt_okx(
|
||||
@@ -170,13 +203,77 @@ def test_get_max_pair_stake_amount_okx(default_conf, mocker, leverage_tiers):
|
||||
assert exchange.get_max_pair_stake_amount('TTT/USDT', 1.0) == float('inf') # Not in tiers
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mode,side,reduceonly,result', [
|
||||
('net', 'buy', False, 'net'),
|
||||
('net', 'sell', True, 'net'),
|
||||
('net', 'sell', False, 'net'),
|
||||
('net', 'buy', True, 'net'),
|
||||
('longshort', 'buy', False, 'long'),
|
||||
('longshort', 'sell', True, 'long'),
|
||||
('longshort', 'sell', False, 'short'),
|
||||
('longshort', 'buy', True, 'short'),
|
||||
])
|
||||
def test__get_posSide(default_conf, mocker, mode, side, reduceonly, result):
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="okx")
|
||||
exchange.net_only = mode == 'net'
|
||||
assert exchange._get_posSide(side, reduceonly) == result
|
||||
|
||||
|
||||
def test_additional_exchange_init_okx(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_accounts = MagicMock(return_value=[
|
||||
{'id': '2555',
|
||||
'type': '2',
|
||||
'currency': None,
|
||||
'info': {'acctLv': '2',
|
||||
'autoLoan': False,
|
||||
'ctIsoMode': 'automatic',
|
||||
'greeksType': 'PA',
|
||||
'level': 'Lv1',
|
||||
'levelTmp': '',
|
||||
'mgnIsoMode': 'automatic',
|
||||
'posMode': 'long_short_mode',
|
||||
'uid': '2555'}}])
|
||||
default_conf['dry_run'] = False
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="okx", api_mock=api_mock)
|
||||
assert api_mock.fetch_accounts.call_count == 0
|
||||
exchange.trading_mode = TradingMode.FUTURES
|
||||
# Default to netOnly
|
||||
assert exchange.net_only
|
||||
exchange.additional_exchange_init()
|
||||
assert api_mock.fetch_accounts.call_count == 1
|
||||
assert not exchange.net_only
|
||||
|
||||
api_mock.fetch_accounts = MagicMock(return_value=[
|
||||
{'id': '2555',
|
||||
'type': '2',
|
||||
'currency': None,
|
||||
'info': {'acctLv': '2',
|
||||
'autoLoan': False,
|
||||
'ctIsoMode': 'automatic',
|
||||
'greeksType': 'PA',
|
||||
'level': 'Lv1',
|
||||
'levelTmp': '',
|
||||
'mgnIsoMode': 'automatic',
|
||||
'posMode': 'net_mode',
|
||||
'uid': '2555'}}])
|
||||
exchange.additional_exchange_init()
|
||||
assert api_mock.fetch_accounts.call_count == 1
|
||||
assert exchange.net_only
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
default_conf['margin_mode'] = 'isolated'
|
||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'okx',
|
||||
"additional_exchange_init", "fetch_accounts")
|
||||
|
||||
|
||||
def test_load_leverage_tiers_okx(default_conf, mocker, markets):
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).has = PropertyMock(return_value={
|
||||
'fetchLeverageTiers': False,
|
||||
'fetchMarketLeverageTiers': True,
|
||||
})
|
||||
api_mock.fetch_market_leverage_tiers = MagicMock(side_effect=[
|
||||
api_mock.fetch_market_leverage_tiers = get_mock_coro(side_effect=[
|
||||
[
|
||||
{
|
||||
'tier': 1,
|
||||
|
Reference in New Issue
Block a user