Merge branch 'develop' into drylimit

This commit is contained in:
மனோஜ்குமார் பழனிச்சாமி
2022-04-03 17:52:11 +05:30
committed by GitHub
174 changed files with 33065 additions and 4522 deletions

View File

@@ -1,24 +1,29 @@
from datetime import datetime, timezone
from random import randint
from unittest.mock import MagicMock
from unittest.mock import MagicMock, PropertyMock
import ccxt
import pytest
from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
from tests.conftest import get_mock_coro, get_patched_exchange, log_has_re
from tests.exchange.test_exchange import ccxt_exceptionhandlers
@pytest.mark.parametrize('limitratio,expected', [
(None, 220 * 0.99),
(0.99, 220 * 0.99),
(0.98, 220 * 0.98),
@pytest.mark.parametrize('trademode', [TradingMode.FUTURES, TradingMode.SPOT])
@pytest.mark.parametrize('limitratio,expected,side', [
(None, 220 * 0.99, "sell"),
(0.99, 220 * 0.99, "sell"),
(0.98, 220 * 0.98, "sell"),
(None, 220 * 1.01, "buy"),
(0.99, 220 * 1.01, "buy"),
(0.98, 220 * 1.02, "buy"),
])
def test_stoploss_order_binance(default_conf, mocker, limitratio, expected):
def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side, trademode):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'stop_loss_limit'
order_type = 'stop_loss_limit' if trademode == TradingMode.SPOT else 'stop'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
@@ -27,45 +32,78 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected):
}
})
default_conf['dry_run'] = False
default_conf['margin_mode'] = MarginMode.ISOLATED
default_conf['trading_mode'] = trademode
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=190,
side=side,
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
leverage=1.0
)
api_mock.create_order.reset_mock()
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types)
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types=order_types,
side=side,
leverage=1.0
)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == order_type
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['side'] == side
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
# Price should be 1% below stopprice
assert api_mock.create_order.call_args_list[0][1]['price'] == expected
assert api_mock.create_order.call_args_list[0][1]['params'] == {'stopPrice': 220}
if trademode == TradingMode.SPOT:
params_dict = {'stopPrice': 220}
else:
params_dict = {'stopPrice': 220, 'reduceOnly': True}
assert api_mock.create_order.call_args_list[0][1]['params'] == params_dict
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0)
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
def test_stoploss_order_dry_run_binance(default_conf, mocker):
@@ -78,12 +116,25 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=190,
side="sell",
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
leverage=1.0
)
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side="sell",
leverage=1.0
)
assert 'id' in order
assert 'info' in order
@@ -94,22 +145,383 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker):
assert order['amount'] == 1
def test_stoploss_adjust_binance(mocker, default_conf):
@pytest.mark.parametrize('sl1,sl2,sl3,side', [
(1501, 1499, 1501, "sell"),
(1499, 1501, 1499, "buy")
])
def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id='binance')
order = {
'type': 'stop_loss_limit',
'price': 1500,
'info': {'stopPrice': 1500},
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
assert exchange.stoploss_adjust(sl1, order, side=side)
assert not exchange.stoploss_adjust(sl2, order, side=side)
# Test with invalid order case
order['type'] = 'stop_loss'
assert not exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(sl3, order, side=side)
def test_fill_leverage_tiers_binance(default_conf, mocker):
api_mock = MagicMock()
api_mock.fetch_leverage_tiers = MagicMock(return_value={
'ADA/BUSD': [
{
"tier": 1,
"notionalFloor": 0,
"notionalCap": 100000,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20,
"info": {
"bracket": "1",
"initialLeverage": "20",
"notionalCap": "100000",
"notionalFloor": "0",
"maintMarginRatio": "0.025",
"cum": "0.0"
}
},
{
"tier": 2,
"notionalFloor": 100000,
"notionalCap": 500000,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10,
"info": {
"bracket": "2",
"initialLeverage": "10",
"notionalCap": "500000",
"notionalFloor": "100000",
"maintMarginRatio": "0.05",
"cum": "2500.0"
}
},
{
"tier": 3,
"notionalFloor": 500000,
"notionalCap": 1000000,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5,
"info": {
"bracket": "3",
"initialLeverage": "5",
"notionalCap": "1000000",
"notionalFloor": "500000",
"maintMarginRatio": "0.1",
"cum": "27500.0"
}
},
{
"tier": 4,
"notionalFloor": 1000000,
"notionalCap": 2000000,
"maintenanceMarginRate": 0.15,
"maxLeverage": 3,
"info": {
"bracket": "4",
"initialLeverage": "3",
"notionalCap": "2000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.15",
"cum": "77500.0"
}
},
{
"tier": 5,
"notionalFloor": 2000000,
"notionalCap": 5000000,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2,
"info": {
"bracket": "5",
"initialLeverage": "2",
"notionalCap": "5000000",
"notionalFloor": "2000000",
"maintMarginRatio": "0.25",
"cum": "277500.0"
}
},
{
"tier": 6,
"notionalFloor": 5000000,
"notionalCap": 30000000,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1,
"info": {
"bracket": "6",
"initialLeverage": "1",
"notionalCap": "30000000",
"notionalFloor": "5000000",
"maintMarginRatio": "0.5",
"cum": "1527500.0"
}
}
],
"ZEC/USDT": [
{
"tier": 1,
"notionalFloor": 0,
"notionalCap": 50000,
"maintenanceMarginRate": 0.01,
"maxLeverage": 50,
"info": {
"bracket": "1",
"initialLeverage": "50",
"notionalCap": "50000",
"notionalFloor": "0",
"maintMarginRatio": "0.01",
"cum": "0.0"
}
},
{
"tier": 2,
"notionalFloor": 50000,
"notionalCap": 150000,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20,
"info": {
"bracket": "2",
"initialLeverage": "20",
"notionalCap": "150000",
"notionalFloor": "50000",
"maintMarginRatio": "0.025",
"cum": "750.0"
}
},
{
"tier": 3,
"notionalFloor": 150000,
"notionalCap": 250000,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10,
"info": {
"bracket": "3",
"initialLeverage": "10",
"notionalCap": "250000",
"notionalFloor": "150000",
"maintMarginRatio": "0.05",
"cum": "4500.0"
}
},
{
"tier": 4,
"notionalFloor": 250000,
"notionalCap": 500000,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5,
"info": {
"bracket": "4",
"initialLeverage": "5",
"notionalCap": "500000",
"notionalFloor": "250000",
"maintMarginRatio": "0.1",
"cum": "17000.0"
}
},
{
"tier": 5,
"notionalFloor": 500000,
"notionalCap": 1000000,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4,
"info": {
"bracket": "5",
"initialLeverage": "4",
"notionalCap": "1000000",
"notionalFloor": "500000",
"maintMarginRatio": "0.125",
"cum": "29500.0"
}
},
{
"tier": 6,
"notionalFloor": 1000000,
"notionalCap": 2000000,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2,
"info": {
"bracket": "6",
"initialLeverage": "2",
"notionalCap": "2000000",
"notionalFloor": "1000000",
"maintMarginRatio": "0.25",
"cum": "154500.0"
}
},
{
"tier": 7,
"notionalFloor": 2000000,
"notionalCap": 30000000,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1,
"info": {
"bracket": "7",
"initialLeverage": "1",
"notionalCap": "30000000",
"notionalFloor": "2000000",
"maintMarginRatio": "0.5",
"cum": "654500.0"
}
}
],
})
default_conf['dry_run'] = False
default_conf['trading_mode'] = TradingMode.FUTURES
default_conf['margin_mode'] = MarginMode.ISOLATED
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
exchange.fill_leverage_tiers()
assert exchange._leverage_tiers == {
'ADA/BUSD': [
{
"min": 0,
"max": 100000,
"mmr": 0.025,
"lev": 20,
"maintAmt": 0.0
},
{
"min": 100000,
"max": 500000,
"mmr": 0.05,
"lev": 10,
"maintAmt": 2500.0
},
{
"min": 500000,
"max": 1000000,
"mmr": 0.1,
"lev": 5,
"maintAmt": 27500.0
},
{
"min": 1000000,
"max": 2000000,
"mmr": 0.15,
"lev": 3,
"maintAmt": 77500.0
},
{
"min": 2000000,
"max": 5000000,
"mmr": 0.25,
"lev": 2,
"maintAmt": 277500.0
},
{
"min": 5000000,
"max": 30000000,
"mmr": 0.5,
"lev": 1,
"maintAmt": 1527500.0
}
],
"ZEC/USDT": [
{
'min': 0,
'max': 50000,
'mmr': 0.01,
'lev': 50,
'maintAmt': 0.0
},
{
'min': 50000,
'max': 150000,
'mmr': 0.025,
'lev': 20,
'maintAmt': 750.0
},
{
'min': 150000,
'max': 250000,
'mmr': 0.05,
'lev': 10,
'maintAmt': 4500.0
},
{
'min': 250000,
'max': 500000,
'mmr': 0.1,
'lev': 5,
'maintAmt': 17000.0
},
{
'min': 500000,
'max': 1000000,
'mmr': 0.125,
'lev': 4,
'maintAmt': 29500.0
},
{
'min': 1000000,
'max': 2000000,
'mmr': 0.25,
'lev': 2,
'maintAmt': 154500.0
},
{
'min': 2000000,
'max': 30000000,
'mmr': 0.5,
'lev': 1,
'maintAmt': 654500.0
},
]
}
api_mock = MagicMock()
api_mock.load_leverage_tiers = MagicMock()
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True})
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"binance",
"fill_leverage_tiers",
"fetch_leverage_tiers",
)
def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker, leverage_tiers):
api_mock = MagicMock()
default_conf['trading_mode'] = TradingMode.FUTURES
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
for key, value in leverage_tiers.items():
assert exchange._leverage_tiers[key] == value
def test__set_leverage_binance(mocker, default_conf):
api_mock = MagicMock()
api_mock.set_leverage = MagicMock()
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
default_conf['dry_run'] = False
exchange = get_patched_exchange(mocker, default_conf, id="binance")
exchange._set_leverage(3.0, trading_mode=TradingMode.MARGIN)
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"binance",
"_set_leverage",
"set_leverage",
pair="XRP/USDT",
leverage=5.0,
trading_mode=TradingMode.FUTURES
)
@pytest.mark.asyncio
async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog):
@pytest.mark.parametrize('candle_type', ['mark', ''])
async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog, candle_type):
ohlcv = [
[
int((datetime.now(timezone.utc).timestamp() - 1000) * 1000),
@@ -126,18 +538,55 @@ async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog):
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
pair = 'ETH/BTC'
respair, restf, res = await exchange._async_get_historic_ohlcv(
pair, "5m", 1500000000000, is_new_pair=False)
respair, restf, restype, res = await exchange._async_get_historic_ohlcv(
pair, "5m", 1500000000000, is_new_pair=False, candle_type=candle_type)
assert respair == pair
assert restf == '5m'
assert restype == candle_type
# Call with very old timestamp - causes tons of requests
assert exchange._api_async.fetch_ohlcv.call_count > 400
# assert res == ohlcv
exchange._api_async.fetch_ohlcv.reset_mock()
_, _, res = await exchange._async_get_historic_ohlcv(
pair, "5m", 1500000000000, is_new_pair=True)
_, _, _, res = await exchange._async_get_historic_ohlcv(
pair, "5m", 1500000000000, is_new_pair=True, candle_type=candle_type)
# Called twice - one "init" call - and one to get the actual data.
assert exchange._api_async.fetch_ohlcv.call_count == 2
assert res == ohlcv
assert log_has_re(r"Candle-data for ETH/BTC available starting with .*", caplog)
@pytest.mark.parametrize("trading_mode,margin_mode,config", [
("spot", "", {}),
("margin", "cross", {"options": {"defaultType": "margin"}}),
("futures", "isolated", {"options": {"defaultType": "future"}}),
])
def test__ccxt_config(default_conf, mocker, trading_mode, margin_mode, config):
default_conf['trading_mode'] = trading_mode
default_conf['margin_mode'] = margin_mode
exchange = get_patched_exchange(mocker, default_conf, id="binance")
assert exchange._ccxt_config == config
@pytest.mark.parametrize('pair,nominal_value,mm_ratio,amt', [
("BNB/BUSD", 0.0, 0.025, 0),
("BNB/USDT", 100.0, 0.0065, 0),
("BTC/USDT", 170.30, 0.004, 0),
("BNB/BUSD", 999999.9, 0.1, 27500.0),
("BNB/USDT", 5000000.0, 0.15, 233035.0),
("BTC/USDT", 600000000, 0.5, 1.997038E8),
])
def test_get_maintenance_ratio_and_amt_binance(
default_conf,
mocker,
leverage_tiers,
pair,
nominal_value,
mm_ratio,
amt,
):
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
exchange = get_patched_exchange(mocker, default_conf, id="binance")
exchange._leverage_tiers = leverage_tiers
(result_ratio, result_amt) = exchange.get_maintenance_ratio_and_amt(pair, nominal_value)
assert (round(result_ratio, 8), round(result_amt, 8)) == (mm_ratio, amt)

View File

@@ -5,14 +5,16 @@ 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
import pytest
from freqtrade.enums import CandleType
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
@@ -22,65 +24,91 @@ EXCHANGES = {
'stake_currency': 'USDT',
'hasQuoteVolume': False,
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
},
'binance': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures': True,
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
},
'kraken': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'leverage_tiers_public': False,
'leverage_in_spot_market': True,
},
'ftx': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'pair': 'BTC/USD',
'stake_currency': 'USD',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures_pair': 'BTC/USD:USD',
'futures': False,
'leverage_tiers_public': False, # TODO: Set to True once implemented on CCXT
'leverage_in_spot_market': True,
},
'kucoin': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'leverage_tiers_public': False,
'leverage_in_spot_market': True,
},
'gateio': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures': True,
'futures_pair': 'BTC/USDT:USDT',
'leverage_tiers_public': True,
'leverage_in_spot_market': True,
},
'okx': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures_pair': 'BTC/USDT:USDT',
'futures': True,
'leverage_tiers_public': True,
'leverage_in_spot_market': True,
},
'huobi': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures': False,
},
'bitvavo': {
'pair': 'BTC/EUR',
'stake_currency': 'EUR',
'hasQuoteVolume': True,
'timeframe': '5m',
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
},
}
@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'] = ''
config['dry_run'] = False
config['entry_pricing']['use_order_book'] = True
config['exit_pricing']['use_order_book'] = True
return config
@@ -93,6 +121,25 @@ def exchange(request, exchange_conf):
yield exchange, request.param
@pytest.fixture(params=EXCHANGES, scope="class")
def exchange_futures(request, exchange_conf, class_mocker):
if not EXCHANGES[request.param].get('futures') is True:
yield None, request.param
else:
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('freqtrade.exchange.exchange.Exchange.fetch_trading_fees')
exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True)
yield exchange, request.param
@pytest.mark.longrun
class TestCCXTExchange():
@@ -102,6 +149,20 @@ class TestCCXTExchange():
markets = exchange.markets
assert pair in markets
assert isinstance(markets[pair], dict)
assert exchange.market_is_spot(markets[pair])
def test_load_markets_futures(self, exchange_futures):
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
assert pair in markets
assert isinstance(markets[pair], dict)
assert exchange.market_is_future(markets[pair])
def test_ccxt_fetch_tickers(self, exchange):
exchange, exchangename = exchange
@@ -161,7 +222,9 @@ class TestCCXTExchange():
exchange, exchangename = exchange
pair = EXCHANGES[exchangename]['pair']
timeframe = EXCHANGES[exchangename]['timeframe']
pair_tf = (pair, timeframe)
pair_tf = (pair, timeframe, CandleType.SPOT)
ohlcv = exchange.refresh_latest_ohlcv([pair_tf])
assert isinstance(ohlcv, dict)
assert len(ohlcv[pair_tf]) == len(exchange.klines(pair_tf))
@@ -172,6 +235,82 @@ 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)
def test_ccxt_fetch_funding_rate_history(self, exchange_futures):
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)
timeframe_ff = exchange._ft_has.get('funding_fee_timeframe',
exchange._ft_has['mark_ohlcv_timeframe'])
pair_tf = (pair, timeframe_ff, CandleType.FUNDING_RATE)
funding_ohlcv = exchange.refresh_latest_ohlcv(
[pair_tf],
since_ms=since,
drop_incomplete=False)
assert isinstance(funding_ohlcv, dict)
rate = funding_ohlcv[pair_tf]
this_hour = timeframe_to_prev_date(timeframe_ff)
hour1 = timeframe_to_prev_date(timeframe_ff, this_hour - timedelta(minutes=1))
hour2 = timeframe_to_prev_date(timeframe_ff, hour1 - timedelta(minutes=1))
hour3 = timeframe_to_prev_date(timeframe_ff, hour2 - timedelta(minutes=1))
val0 = rate[rate['date'] == this_hour].iloc[0]['open']
val1 = rate[rate['date'] == hour1].iloc[0]['open']
val2 = rate[rate['date'] == hour2].iloc[0]['open']
val3 = rate[rate['date'] == hour3].iloc[0]['open']
# Test For last 4 hours
# Avoids random test-failure when funding-fees are 0 for a few hours.
assert val0 != 0.0 or val1 != 0.0 or val2 != 0.0 or val3 != 0.0
# We expect funding rates to be different from 0.0 - or moving around.
assert (
rate['open'].max() != 0.0 or rate['open'].min() != 0.0 or
(rate['open'].min() != rate['open'].max())
)
def test_ccxt_fetch_mark_price_history(self, exchange_futures):
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)
mark_ohlcv = exchange.refresh_latest_ohlcv(
[pair_tf],
since_ms=since,
drop_incomplete=False)
assert isinstance(mark_ohlcv, dict)
expected_tf = '1h'
mark_candles = mark_ohlcv[pair_tf]
this_hour = timeframe_to_prev_date(expected_tf)
prev_hour = timeframe_to_prev_date(expected_tf, this_hour - timedelta(minutes=1))
assert mark_candles[mark_candles['date'] == prev_hour].iloc[0]['open'] != 0.0
assert mark_candles[mark_candles['date'] == this_hour].iloc[0]['open'] != 0.0
def test_ccxt__calculate_funding_fees(self, exchange_futures):
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)
funding_fee = exchange._fetch_and_calculate_funding_fees(
pair, 20, is_short=False, open_date=since)
assert isinstance(funding_fee, float)
# assert funding_fee > 0
# TODO: tests fetch_trades (?)
def test_ccxt_get_fee(self, exchange):
@@ -182,3 +321,110 @@ class TestCCXTExchange():
assert 0 < exchange.get_fee(pair, 'limit', 'sell') < threshold
assert 0 < exchange.get_fee(pair, 'market', 'buy') < threshold
assert 0 < exchange.get_fee(pair, 'market', 'sell') < threshold
def test_ccxt_get_max_leverage_spot(self, exchange):
spot, spot_name = exchange
if spot:
leverage_in_market_spot = EXCHANGES[spot_name].get('leverage_in_spot_market')
if leverage_in_market_spot:
spot_pair = EXCHANGES[spot_name].get('pair', EXCHANGES[spot_name]['pair'])
spot_leverage = spot.get_max_leverage(spot_pair, 20)
assert (isinstance(spot_leverage, float) or isinstance(spot_leverage, int))
assert spot_leverage >= 1.0
def test_ccxt_get_max_leverage_futures(self, exchange_futures):
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):
futures, futures_name = exchange_futures
if 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):
futures, futures_name = exchange_futures
if futures and EXCHANGES[futures_name].get('leverage_tiers_public'):
leverage_tiers = futures.load_leverage_tiers()
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
EXCHANGES[futures_name]['pair']
)
assert (isinstance(leverage_tiers, dict))
assert futures_pair in leverage_tiers
pair_tiers = leverage_tiers[futures_pair]
assert len(pair_tiers) > 0
oldLeverage = float('inf')
oldMaintenanceMarginRate = oldNotionalFloor = oldNotionalCap = -1
for tier in pair_tiers:
for key in [
'maintenanceMarginRate',
'notionalFloor',
'notionalCap',
'maxLeverage'
]:
assert key in tier
assert tier[key] >= 0.0
assert tier['notionalCap'] > tier['notionalFloor']
assert tier['maxLeverage'] <= oldLeverage
assert tier['maintenanceMarginRate'] >= oldMaintenanceMarginRate
assert tier['notionalFloor'] > oldNotionalFloor
assert tier['notionalCap'] > oldNotionalCap
oldLeverage = tier['maxLeverage']
oldMaintenanceMarginRate = tier['maintenanceMarginRate']
oldNotionalFloor = tier['notionalFloor']
oldNotionalCap = tier['notionalCap']
def test_ccxt_dry_run_liquidation_price(self, exchange_futures):
futures, futures_name = exchange_futures
if futures and EXCHANGES[futures_name].get('leverage_tiers_public'):
futures_pair = EXCHANGES[futures_name].get(
'futures_pair',
EXCHANGES[futures_name]['pair']
)
liquidation_price = futures.dry_run_liquidation_price(
futures_pair,
40000,
False,
100,
100,
)
assert (isinstance(liquidation_price, float))
assert liquidation_price >= 0.0
liquidation_price = futures.dry_run_liquidation_price(
futures_pair,
40000,
False,
100,
100,
)
assert (isinstance(liquidation_price, float))
assert liquidation_price >= 0.0
def test_ccxt_get_max_pair_stake_amount(self, exchange_futures):
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

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,11 @@ from .test_exchange import ccxt_exceptionhandlers
STOPLOSS_ORDERTYPE = 'stop'
def test_stoploss_order_ftx(default_conf, mocker):
@pytest.mark.parametrize('order_price,exchangelimitratio,side', [
(217.8, 1.05, "sell"),
(222.2, 0.95, "buy"),
])
def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitratio, side):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
@@ -32,12 +36,18 @@ def test_stoploss_order_ftx(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
# stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=190,
side=side,
order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio},
leverage=1.0
)
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['side'] == side
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
assert 'stopPrice' in api_mock.create_order.call_args_list[0][1]['params']
@@ -47,51 +57,79 @@ def test_stoploss_order_ftx(default_conf, mocker):
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['side'] == side
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={'stoploss': 'limit'})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={'stoploss': 'limit'}, side=side,
leverage=1.0
)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['side'] == side
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert 'orderPrice' in api_mock.create_order.call_args_list[0][1]['params']
assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == 217.8
assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == order_price
assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
def test_stoploss_order_dry_run_ftx(default_conf, mocker):
@pytest.mark.parametrize('side', [("sell"), ("buy")])
def test_stoploss_order_dry_run_ftx(default_conf, mocker, side):
api_mock = MagicMock()
default_conf['dry_run'] = True
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
@@ -101,7 +139,14 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker):
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
assert 'id' in order
assert 'info' in order
@@ -112,20 +157,24 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker):
assert order['amount'] == 1
def test_stoploss_adjust_ftx(mocker, default_conf):
@pytest.mark.parametrize('sl1,sl2,sl3,side', [
(1501, 1499, 1501, "sell"),
(1499, 1501, 1499, "buy")
])
def test_stoploss_adjust_ftx(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id='ftx')
order = {
'type': STOPLOSS_ORDERTYPE,
'price': 1500,
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
assert exchange.stoploss_adjust(sl1, order, side=side)
assert not exchange.stoploss_adjust(sl2, order, side=side)
# Test with invalid order case ...
order['type'] = 'stop_loss_limit'
assert not exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(sl3, order, side=side)
def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order):
def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order, limit_buy_order):
default_conf['dry_run'] = True
order = MagicMock()
order.myid = 123

View File

@@ -1,7 +1,9 @@
from datetime import datetime, timezone
from unittest.mock import MagicMock
import pytest
from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import Gateio
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
@@ -15,13 +17,14 @@ def test_validate_order_types_gateio(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange.validate_pairs')
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
mocker.patch('freqtrade.exchange.Exchange.validate_pricing')
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
exch = ExchangeResolver.load_exchange('gateio', default_conf, True)
assert isinstance(exch, Gateio)
default_conf['order_types'] = {
'buy': 'market',
'sell': 'limit',
'entry': 'market',
'exit': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}
@@ -57,11 +60,59 @@ def test_cancel_stoploss_order_gateio(default_conf, mocker):
assert cancel_order_mock.call_args_list[0][1]['params'] == {'stop': True}
def test_stoploss_adjust_gateio(mocker, default_conf):
@pytest.mark.parametrize('sl1,sl2,sl3,side', [
(1501, 1499, 1501, "sell"),
(1499, 1501, 1499, "buy")
])
def test_stoploss_adjust_gateio(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id='gateio')
order = {
'price': 1500,
'stopPrice': 1500,
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
assert exchange.stoploss_adjust(sl1, order, side)
assert not exchange.stoploss_adjust(sl2, order, side)
@pytest.mark.parametrize('takerormaker,rate,cost', [
('taker', 0.0005, 0.0001554325),
('maker', 0.0, 0.0),
])
def test_fetch_my_trades_gateio(mocker, default_conf, takerormaker, rate, cost):
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
tick = {'ETH/USDT:USDT': {
'info': {'user_id': '',
'taker_fee': '0.0018',
'maker_fee': '0.0018',
'gt_discount': False,
'gt_taker_fee': '0',
'gt_maker_fee': '0',
'loan_fee': '0.18',
'point_type': '1',
'futures_taker_fee': '0.0005',
'futures_maker_fee': '0'},
'symbol': 'ETH/USDT:USDT',
'maker': 0.0,
'taker': 0.0005}
}
default_conf['dry_run'] = False
default_conf['trading_mode'] = TradingMode.FUTURES
default_conf['margin_mode'] = MarginMode.ISOLATED
api_mock = MagicMock()
api_mock.fetch_my_trades = MagicMock(return_value=[{
'fee': {'cost': None},
'price': 3108.65,
'cost': 0.310865,
'order': '22255',
'takerOrMaker': takerormaker,
'amount': 1, # 1 contract
}])
exchange = get_patched_exchange(mocker, default_conf, api_mock=api_mock, id='gateio')
exchange._trading_fees = tick
trades = exchange.get_trades_for_order('22255', 'ETH/USDT:USDT', datetime.now(timezone.utc))
trade = trades[0]
assert trade['fee']
assert trade['fee']['rate'] == rate
assert trade['fee']['currency'] == 'USDT'
assert trade['fee']['cost'] == cost

View File

@@ -9,12 +9,12 @@ from tests.conftest import get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers
@pytest.mark.parametrize('limitratio,expected', [
(None, 220 * 0.99),
(0.99, 220 * 0.99),
(0.98, 220 * 0.98),
@pytest.mark.parametrize('limitratio,expected,side', [
(None, 220 * 0.99, "sell"),
(0.99, 220 * 0.99, "sell"),
(0.98, 220 * 0.98, "sell"),
])
def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected):
def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'stop-limit'
@@ -33,11 +33,14 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected):
with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
side=side,
leverage=1.0)
api_mock.create_order.reset_mock()
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types)
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types,
side=side, leverage=1.0)
assert 'id' in order
assert 'info' in order
@@ -56,17 +59,20 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected):
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0)
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "huobi",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
def test_stoploss_order_dry_run_huobi(default_conf, mocker):
@@ -80,11 +86,13 @@ def test_stoploss_order_dry_run_huobi(default_conf, mocker):
with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
side='sell', leverage=1.0)
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side='sell', leverage=1.0)
assert 'id' in order
assert 'info' in order
@@ -102,8 +110,8 @@ def test_stoploss_adjust_huobi(mocker, default_conf):
'price': 1500,
'stopPrice': '1500',
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
assert exchange.stoploss_adjust(1501, order, 'sell')
assert not exchange.stoploss_adjust(1499, order, 'sell')
# Test with invalid order case
order['type'] = 'stop_loss'
assert not exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1501, order, 'sell')

View File

@@ -21,6 +21,7 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'symbol': 'ETH/BTC',
'info': {
'foo': 'bar'
}
@@ -31,8 +32,15 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
amount=1, rate=200, time_in_force=time_in_force)
order = exchange.create_order(
pair='ETH/BTC',
ordertype=order_type,
side="buy",
amount=1,
rate=200,
leverage=1.0,
time_in_force=time_in_force
)
assert 'id' in order
assert 'info' in order
@@ -53,6 +61,7 @@ def test_sell_kraken_trading_agreement(default_conf, mocker):
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'symbol': 'ETH/BTC',
'info': {
'foo': 'bar'
}
@@ -64,7 +73,7 @@ def test_sell_kraken_trading_agreement(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
side="sell", amount=1, rate=200)
side="sell", amount=1, rate=200, leverage=1.0)
assert 'id' in order
assert 'info' in order
@@ -166,7 +175,11 @@ def test_get_balances_prod(default_conf, mocker):
@pytest.mark.parametrize('ordertype', ['market', 'limit'])
def test_stoploss_order_kraken(default_conf, mocker, ordertype):
@pytest.mark.parametrize('side,adjustedprice', [
("sell", 217.8),
("buy", 222.2),
])
def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedprice):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
@@ -183,10 +196,17 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={'stoploss': ordertype,
'stoploss_on_exchange_limit_ratio': 0.99
})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
side=side,
order_types={
'stoploss': ordertype,
'stoploss_on_exchange_limit_ratio': 0.99
},
leverage=1.0
)
assert 'id' in order
assert 'info' in order
@@ -195,12 +215,14 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype):
if ordertype == 'limit':
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_LIMIT_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['params'] == {
'trading_agreement': 'agree', 'price2': 217.8}
'trading_agreement': 'agree',
'price2': adjustedprice
}
else:
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['params'] == {
'trading_agreement': 'agree'}
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['side'] == side
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
assert api_mock.create_order.call_args_list[0][1]['price'] == 220
@@ -208,20 +230,36 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype):
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
def test_stoploss_order_dry_run_kraken(default_conf, mocker):
@pytest.mark.parametrize('side', ['buy', 'sell'])
def test_stoploss_order_dry_run_kraken(default_conf, mocker, side):
api_mock = MagicMock()
default_conf['dry_run'] = True
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
@@ -231,7 +269,14 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker):
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
assert 'id' in order
assert 'info' in order
@@ -242,14 +287,18 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker):
assert order['amount'] == 1
def test_stoploss_adjust_kraken(mocker, default_conf):
@pytest.mark.parametrize('sl1,sl2,sl3,side', [
(1501, 1499, 1501, "sell"),
(1499, 1501, 1499, "buy")
])
def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id='kraken')
order = {
'type': STOPLOSS_ORDERTYPE,
'price': 1500,
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
assert exchange.stoploss_adjust(sl1, order, side=side)
assert not exchange.stoploss_adjust(sl2, order, side=side)
# Test with invalid order case ...
order['type'] = 'stop_loss_limit'
assert not exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(sl3, order, side=side)

View File

@@ -10,12 +10,12 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
@pytest.mark.parametrize('order_type', ['market', 'limit'])
@pytest.mark.parametrize('limitratio,expected', [
(None, 220 * 0.99),
(0.99, 220 * 0.99),
(0.98, 220 * 0.98),
@pytest.mark.parametrize('limitratio,expected,side', [
(None, 220 * 0.99, "sell"),
(0.99, 220 * 0.99, "sell"),
(0.98, 220 * 0.98, "sell"),
])
def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, order_type):
def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side, order_type):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
@@ -35,13 +35,15 @@ def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, order
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={
'stoploss': order_type,
'stoploss_on_exchange_limit_ratio': 1.05})
'stoploss_on_exchange_limit_ratio': 1.05},
side=side, leverage=1.0)
api_mock.create_order.reset_mock()
order_types = {'stoploss': order_type}
if limitratio is not None:
order_types.update({'stoploss_on_exchange_limit_ratio': limitratio})
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types)
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types=order_types, side=side, leverage=1.0)
assert 'id' in order
assert 'info' in order
@@ -65,17 +67,20 @@ def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, order
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0)
with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("kucoin Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kucoin",
"stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
@@ -90,11 +95,13 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss': 'limit',
'stoploss_on_exchange_limit_ratio': 1.05})
'stoploss_on_exchange_limit_ratio': 1.05},
side='sell', leverage=1.0)
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side='sell', leverage=1.0)
assert 'id' in order
assert 'info' in order
@@ -113,8 +120,8 @@ def test_stoploss_adjust_kucoin(mocker, default_conf):
'stopPrice': 1500,
'info': {'stopPrice': 1500, 'stop': "limit"},
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)
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)
assert not exchange.stoploss_adjust(1501, order, 'sell')

360
tests/exchange/test_okx.py Normal file
View File

@@ -0,0 +1,360 @@
from unittest.mock import MagicMock, PropertyMock
from freqtrade.enums import MarginMode, TradingMode
from tests.conftest import get_patched_exchange
def test_get_maintenance_ratio_and_amt_okx(
default_conf,
mocker,
):
api_mock = MagicMock()
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
default_conf['dry_run'] = False
mocker.patch.multiple(
'freqtrade.exchange.Okx',
exchange_has=MagicMock(return_value=True),
load_leverage_tiers=MagicMock(return_value={
'ETH/USDT:USDT': [
{
'tier': 1,
'notionalFloor': 0,
'notionalCap': 2000,
'maintenanceMarginRate': 0.01,
'maxLeverage': 75,
'info': {
'baseMaxLoan': '',
'imr': '0.013',
'instId': '',
'maxLever': '75',
'maxSz': '2000',
'minSz': '0',
'mmr': '0.01',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '1',
'uly': 'ETH-USDT'
}
},
{
'tier': 2,
'notionalFloor': 2001,
'notionalCap': 4000,
'maintenanceMarginRate': 0.015,
'maxLeverage': 50,
'info': {
'baseMaxLoan': '',
'imr': '0.02',
'instId': '',
'maxLever': '50',
'maxSz': '4000',
'minSz': '2001',
'mmr': '0.015',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '2',
'uly': 'ETH-USDT'
}
},
{
'tier': 3,
'notionalFloor': 4001,
'notionalCap': 8000,
'maintenanceMarginRate': 0.02,
'maxLeverage': 20,
'info': {
'baseMaxLoan': '',
'imr': '0.05',
'instId': '',
'maxLever': '20',
'maxSz': '8000',
'minSz': '4001',
'mmr': '0.02',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '3',
'uly': 'ETH-USDT'
}
},
],
'ADA/USDT:USDT': [
{
'tier': 1,
'notionalFloor': 0,
'notionalCap': 500,
'maintenanceMarginRate': 0.02,
'maxLeverage': 75,
'info': {
'baseMaxLoan': '',
'imr': '0.013',
'instId': '',
'maxLever': '75',
'maxSz': '500',
'minSz': '0',
'mmr': '0.01',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '1',
'uly': 'ADA-USDT'
}
},
{
'tier': 2,
'notionalFloor': 501,
'notionalCap': 1000,
'maintenanceMarginRate': 0.025,
'maxLeverage': 50,
'info': {
'baseMaxLoan': '',
'imr': '0.02',
'instId': '',
'maxLever': '50',
'maxSz': '1000',
'minSz': '501',
'mmr': '0.015',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '2',
'uly': 'ADA-USDT'
}
},
{
'tier': 3,
'notionalFloor': 1001,
'notionalCap': 2000,
'maintenanceMarginRate': 0.03,
'maxLeverage': 20,
'info': {
'baseMaxLoan': '',
'imr': '0.05',
'instId': '',
'maxLever': '20',
'maxSz': '2000',
'minSz': '1001',
'mmr': '0.02',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '3',
'uly': 'ADA-USDT'
}
},
]
})
)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx")
assert exchange.get_maintenance_ratio_and_amt('ETH/USDT:USDT', 2000) == (0.01, None)
assert exchange.get_maintenance_ratio_and_amt('ETH/USDT:USDT', 2001) == (0.015, None)
assert exchange.get_maintenance_ratio_and_amt('ETH/USDT:USDT', 4001) == (0.02, None)
assert exchange.get_maintenance_ratio_and_amt('ETH/USDT:USDT', 8000) == (0.02, None)
assert exchange.get_maintenance_ratio_and_amt('ADA/USDT:USDT', 1) == (0.02, None)
assert exchange.get_maintenance_ratio_and_amt('ADA/USDT:USDT', 2000) == (0.03, None)
def test_get_max_pair_stake_amount_okx(default_conf, mocker, leverage_tiers):
exchange = get_patched_exchange(mocker, default_conf, id="okx")
assert exchange.get_max_pair_stake_amount('BNB/BUSD', 1.0) == float('inf')
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
exchange = get_patched_exchange(mocker, default_conf, id="okx")
exchange._leverage_tiers = leverage_tiers
assert exchange.get_max_pair_stake_amount('BNB/BUSD', 1.0) == 30000000
assert exchange.get_max_pair_stake_amount('BNB/USDT', 1.0) == 50000000
assert exchange.get_max_pair_stake_amount('BTC/USDT', 1.0) == 1000000000
assert exchange.get_max_pair_stake_amount('BTC/USDT', 1.0, 10.0) == 100000000
assert exchange.get_max_pair_stake_amount('TTT/USDT', 1.0) == float('inf') # Not in tiers
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=[
[
{
'tier': 1,
'notionalFloor': 0,
'notionalCap': 500,
'maintenanceMarginRate': 0.02,
'maxLeverage': 75,
'info': {
'baseMaxLoan': '',
'imr': '0.013',
'instId': '',
'maxLever': '75',
'maxSz': '500',
'minSz': '0',
'mmr': '0.01',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '1',
'uly': 'ADA-USDT'
}
},
{
'tier': 2,
'notionalFloor': 501,
'notionalCap': 1000,
'maintenanceMarginRate': 0.025,
'maxLeverage': 50,
'info': {
'baseMaxLoan': '',
'imr': '0.02',
'instId': '',
'maxLever': '50',
'maxSz': '1000',
'minSz': '501',
'mmr': '0.015',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '2',
'uly': 'ADA-USDT'
}
},
{
'tier': 3,
'notionalFloor': 1001,
'notionalCap': 2000,
'maintenanceMarginRate': 0.03,
'maxLeverage': 20,
'info': {
'baseMaxLoan': '',
'imr': '0.05',
'instId': '',
'maxLever': '20',
'maxSz': '2000',
'minSz': '1001',
'mmr': '0.02',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '3',
'uly': 'ADA-USDT'
}
},
],
[
{
'tier': 1,
'notionalFloor': 0,
'notionalCap': 2000,
'maintenanceMarginRate': 0.01,
'maxLeverage': 75,
'info': {
'baseMaxLoan': '',
'imr': '0.013',
'instId': '',
'maxLever': '75',
'maxSz': '2000',
'minSz': '0',
'mmr': '0.01',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '1',
'uly': 'ETH-USDT'
}
},
{
'tier': 2,
'notionalFloor': 2001,
'notionalCap': 4000,
'maintenanceMarginRate': 0.015,
'maxLeverage': 50,
'info': {
'baseMaxLoan': '',
'imr': '0.02',
'instId': '',
'maxLever': '50',
'maxSz': '4000',
'minSz': '2001',
'mmr': '0.015',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '2',
'uly': 'ETH-USDT'
}
},
{
'tier': 3,
'notionalFloor': 4001,
'notionalCap': 8000,
'maintenanceMarginRate': 0.02,
'maxLeverage': 20,
'info': {
'baseMaxLoan': '',
'imr': '0.05',
'instId': '',
'maxLever': '20',
'maxSz': '8000',
'minSz': '4001',
'mmr': '0.02',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '3',
'uly': 'ETH-USDT'
}
},
]
])
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
default_conf['stake_currency'] = 'USDT'
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx")
exchange.trading_mode = TradingMode.FUTURES
exchange.margin_mode = MarginMode.ISOLATED
exchange.markets = markets
# Initialization of load_leverage_tiers happens as part of exchange init.
assert exchange._leverage_tiers == {
'ADA/USDT:USDT': [
{
'min': 0,
'max': 500,
'mmr': 0.02,
'lev': 75,
'maintAmt': None
},
{
'min': 501,
'max': 1000,
'mmr': 0.025,
'lev': 50,
'maintAmt': None
},
{
'min': 1001,
'max': 2000,
'mmr': 0.03,
'lev': 20,
'maintAmt': None
},
],
'ETH/USDT:USDT': [
{
'min': 0,
'max': 2000,
'mmr': 0.01,
'lev': 75,
'maintAmt': None
},
{
'min': 2001,
'max': 4000,
'mmr': 0.015,
'lev': 50,
'maintAmt': None
},
{
'min': 4001,
'max': 8000,
'mmr': 0.02,
'lev': 20,
'maintAmt': None
},
],
}