Merge branch 'develop' into drylimit
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
360
tests/exchange/test_okx.py
Normal 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
|
||||
},
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user