contract-sizes tests

This commit is contained in:
Sam Germain 2021-12-21 15:45:16 -06:00
parent d0a300a2e1
commit 78d1a267f0
3 changed files with 443 additions and 73 deletions

View File

@ -372,13 +372,17 @@ class Exchange:
def _get_contract_size(self, pair: str) -> int: def _get_contract_size(self, pair: str) -> int:
if self.trading_mode == TradingMode.FUTURES: if self.trading_mode == TradingMode.FUTURES:
return self.markets[pair]['contract_size'] market = self.markets[pair]
contract_size = 1
if 'contractSize' in market and market['contractSize'] is not None:
contract_size = market['contractSize']
return contract_size
else: else:
return 1 return 1
def _trades_contracts_to_amount(self, trades: List) -> List: def _trades_contracts_to_amount(self, trades: List) -> List:
if len(trades) > 0: if len(trades) > 0:
contract_size = self._get_contract_size(trades[0]['pair']) contract_size = self._get_contract_size(trades[0]['symbol'])
if contract_size != 1: if contract_size != 1:
for trade in trades: for trade in trades:
trade['amount'] = trade['amount'] * contract_size trade['amount'] = trade['amount'] * contract_size
@ -387,10 +391,10 @@ class Exchange:
return trades return trades
def _order_contracts_to_amount(self, order: Dict) -> Dict: def _order_contracts_to_amount(self, order: Dict) -> Dict:
contract_size = self._get_contract_size(order['pair']) contract_size = self._get_contract_size(order['symbol'])
if contract_size != 1: if contract_size != 1:
for prop in ['amount', 'cost', 'filled', 'remaining']: for prop in ['amount', 'cost', 'filled', 'remaining']:
if prop in order: if prop in order and order[prop] is not None:
order[prop] = order[prop] * contract_size order[prop] = order[prop] * contract_size
return order return order
@ -611,7 +615,7 @@ class Exchange:
Re-implementation of ccxt internal methods - ensuring we can test the result is correct Re-implementation of ccxt internal methods - ensuring we can test the result is correct
based on our definitions. based on our definitions.
""" """
amount = self._amount_to_contract_size(pair, amount) amount = self._amount_to_contracts(pair, amount)
if self.markets[pair]['precision']['amount']: if self.markets[pair]['precision']['amount']:
amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE, amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE,
precision=self.markets[pair]['precision']['amount'], precision=self.markets[pair]['precision']['amount'],
@ -670,12 +674,17 @@ class Exchange:
limits = market['limits'] limits = market['limits']
if ('cost' in limits and 'min' in limits['cost'] if ('cost' in limits and 'min' in limits['cost']
and limits['cost']['min'] is not None): and limits['cost']['min'] is not None):
min_stake_amounts.append(limits['cost']['min']) min_stake_amounts.append(
self._contracts_to_amount(
pair,
limits['cost']['min']
)
)
if ('amount' in limits and 'min' in limits['amount'] if ('amount' in limits and 'min' in limits['amount']
and limits['amount']['min'] is not None): and limits['amount']['min'] is not None):
min_stake_amounts.append( min_stake_amounts.append(
self._contract_size_to_amount( self._contracts_to_amount(
pair, pair,
limits['amount']['min'] * price limits['amount']['min'] * price
) )
@ -715,7 +724,7 @@ class Exchange:
def create_dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, def create_dry_run_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, leverage: float, params: Dict = {}) -> Dict[str, Any]: rate: float, leverage: float, params: Dict = {}) -> Dict[str, Any]:
order_id = f'dry_run_{side}_{datetime.now().timestamp()}' order_id = f'dry_run_{side}_{datetime.now().timestamp()}'
_amount = self._contract_size_to_amount(pair, self.amount_to_precision(pair, amount)) _amount = self._contracts_to_amount(pair, self.amount_to_precision(pair, amount))
dry_order: Dict[str, Any] = { dry_order: Dict[str, Any] = {
'id': order_id, 'id': order_id,
'symbol': pair, 'symbol': pair,
@ -865,19 +874,19 @@ class Exchange:
params.update({param: time_in_force}) params.update({param: time_in_force})
return params return params
def _amount_to_contract_size(self, pair: str, amount: float): def _amount_to_contracts(self, pair: str, amount: float):
if ('contractSize' in self.markets[pair]): if ('contractSize' in self.markets[pair]):
return amount / self.markets[pair]['contractSize'] return amount / self.markets[pair]['contractSize']
else: else:
return amount return amount
def _contract_size_to_amount(self, pair: str, amount: float): def _contracts_to_amount(self, pair: str, num_contracts: float):
if ('contractSize' in self.markets[pair]): if ('contractSize' in self.markets[pair]):
return amount * self.markets[pair]['contractSize'] return num_contracts * self.markets[pair]['contractSize']
else: else:
return amount return num_contracts
def create_order(self, pair: str, ordertype: str, side: str, amount: float, def create_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, leverage: float = 1.0, time_in_force: str = 'gtc') -> Dict: rate: float, leverage: float = 1.0, time_in_force: str = 'gtc') -> Dict:

View File

@ -914,6 +914,7 @@ def get_markets():
'active': True, 'active': True,
'spot': True, 'spot': True,
'type': 'spot', 'type': 'spot',
'contractSize': None,
'precision': { 'precision': {
'amount': 8, 'amount': 8,
'price': 8 'price': 8
@ -937,7 +938,8 @@ def get_markets():
'quote': 'USDT', 'quote': 'USDT',
'active': True, 'active': True,
'spot': False, 'spot': False,
'type': 'SomethingElse', 'type': 'swap',
'contractSize': 0.01,
'precision': { 'precision': {
'amount': 8, 'amount': 8,
'price': 8 'price': 8
@ -985,6 +987,59 @@ def get_markets():
'info': { 'info': {
} }
}, },
'ETH/USDT:USDT': {
'id': 'ETH_USDT',
'symbol': 'ETH/USDT:USDT',
'base': 'ETH',
'quote': 'USDT',
'settle': 'USDT',
'baseId': 'ETH',
'quoteId': 'USDT',
'settleId': 'USDT',
'type': 'swap',
'spot': False,
'margin': False,
'swap': True,
'futures': False,
'option': False,
'derivative': True,
'contract': True,
'linear': True,
'inverse': False,
'tierBased': False,
'percentage': True,
'taker': 0.0006,
'maker': 0.0002,
'contractSize': 10,
'active': True,
'expiry': None,
'expiryDatetime': None,
'strike': None,
'optionType': None,
'limits': {
'leverage': {
'min': 1,
'max': 100
},
'amount': {
'min': 1,
'max': 300000
},
'price': {
'min': None,
'max': None,
},
'cost': {
'min': None,
'max': None,
}
},
'precision': {
'price': 0.05,
'amount': 1
},
'info': {}
}
} }

View File

@ -20,6 +20,7 @@ from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_CO
from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes, timeframe_to_msecs, from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes, timeframe_to_msecs,
timeframe_to_next_date, timeframe_to_prev_date, timeframe_to_next_date, timeframe_to_prev_date,
timeframe_to_seconds) timeframe_to_seconds)
from freqtrade.persistence.models import Trade
from freqtrade.resolvers.exchange_resolver import ExchangeResolver from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has_re, num_log_has_re from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has_re, num_log_has_re
@ -224,28 +225,34 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog):
ex.validate_order_time_in_force(tif2) ex.validate_order_time_in_force(tif2)
@pytest.mark.parametrize("amount,precision_mode,precision,expected", [ @pytest.mark.parametrize("amount,precision_mode,precision,contract_size,expected", [
(2.34559, 2, 4, 2.3455), (2.34559, 2, 4, 1, 2.3455),
(2.34559, 2, 5, 2.34559), (2.34559, 2, 5, 1, 2.34559),
(2.34559, 2, 3, 2.345), (2.34559, 2, 3, 1, 2.345),
(2.9999, 2, 3, 2.999), (2.9999, 2, 3, 1, 2.999),
(2.9909, 2, 3, 2.990), (2.9909, 2, 3, 1, 2.990),
# Tests for Tick-size # Tests for Tick-size
(2.34559, 4, 0.0001, 2.3455), (2.34559, 4, 0.0001, 1, 2.3455),
(2.34559, 4, 0.00001, 2.34559), (2.34559, 4, 0.00001, 1, 2.34559),
(2.34559, 4, 0.001, 2.345), (2.34559, 4, 0.001, 1, 2.345),
(2.9999, 4, 0.001, 2.999), (2.9999, 4, 0.001, 1, 2.999),
(2.9909, 4, 0.001, 2.990), (2.9909, 4, 0.001, 1, 2.990),
(2.9909, 4, 0.005, 2.990), (2.9909, 4, 0.005, 0.01, 0.025),
(2.9999, 4, 0.005, 2.995), (2.9999, 4, 0.005, 10, 29.995),
]) ])
def test_amount_to_precision(default_conf, mocker, amount, precision_mode, precision, expected): def test_amount_to_precision(default_conf, mocker, amount, precision_mode, precision, contract_size, expected):
""" """
Test rounds down Test rounds down
""" """
# TODO-lev: Test for contract sizes of 0.01 and 10
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': precision}}}) markets = PropertyMock(return_value={
'ETH/BTC': {
'contractSize': contract_size,
'precision': {
'amount': precision
}
}
})
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, id="binance")
# digits counting mode # digits counting mode
@ -324,12 +331,11 @@ def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precisio
assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected
def test_get_min_pair_stake_amount(mocker, default_conf) -> None: def test_get_min_pair_stake_amount(mocker, default_conf, markets) -> None:
# TODO-lev: Test for contract sizes of 0.01 and 10
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, id="binance")
stoploss = -0.05 stoploss = -0.05
markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}, }
# no pair found # no pair found
mocker.patch( mocker.patch(
@ -467,6 +473,25 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0)
assert isclose(result, expected_result/12) assert isclose(result, expected_result/12)
markets["ETH/BTC"]["contractSize"] = 0.01
mocker.patch(
'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets)
)
# Contract size 0.01
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1)
assert isclose(result, expected_result * 0.01)
markets["ETH/BTC"]["contractSize"] = 10
mocker.patch(
'freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=markets)
)
# With Leverage, Contract size 10
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0)
assert isclose(result, (expected_result/12) * 10.0)
def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, id="binance")
@ -1006,7 +1031,6 @@ def test_exchange_has(default_conf, mocker):
]) ])
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_dry_run_order(default_conf, mocker, side, exchange_name): def test_create_dry_run_order(default_conf, mocker, side, exchange_name):
# TODO-lev: Test for contract sizes of 0.01 and 10
default_conf['dry_run'] = True default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
@ -1023,6 +1047,7 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name):
assert order["side"] == side assert order["side"] == side
assert order["type"] == "limit" assert order["type"] == "limit"
assert order["symbol"] == "ETH/BTC" assert order["symbol"] == "ETH/BTC"
assert order["amount"] == 1
@pytest.mark.parametrize("side,startprice,endprice", [ @pytest.mark.parametrize("side,startprice,endprice", [
@ -1123,7 +1148,6 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou
]) ])
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, exchange_name): def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, exchange_name):
# TODO-lev: Test for contract sizes of 0.01 and 10
api_mock = MagicMock() api_mock = MagicMock()
order_id = 'test_prod_{}_{}'.format(side, randint(0, 10 ** 6)) order_id = 'test_prod_{}_{}'.format(side, randint(0, 10 ** 6))
api_mock.options = {} if not marketprice else {"createMarketBuyOrderRequiresPrice": True} api_mock.options = {} if not marketprice else {"createMarketBuyOrderRequiresPrice": True}
@ -1131,9 +1155,12 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
'id': order_id, 'id': order_id,
'info': { 'info': {
'foo': 'bar' 'foo': 'bar'
} },
'symbol': 'XLTCUSDT',
'amount': 1
}) })
default_conf['dry_run'] = False default_conf['dry_run'] = False
default_conf['collateral'] = 'isolated'
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) 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) mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
@ -1141,7 +1168,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
exchange.set_margin_mode = MagicMock() exchange.set_margin_mode = MagicMock()
order = exchange.create_order( order = exchange.create_order(
pair='ETH/BTC', pair='XLTCUSDT',
ordertype=ordertype, ordertype=ordertype,
side=side, side=side,
amount=1, amount=1,
@ -1152,7 +1179,8 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
assert order['id'] == order_id assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' assert order['amount'] == 1
assert api_mock.create_order.call_args[0][0] == 'XLTCUSDT'
assert api_mock.create_order.call_args[0][1] == ordertype assert api_mock.create_order.call_args[0][1] == ordertype
assert api_mock.create_order.call_args[0][2] == side assert api_mock.create_order.call_args[0][2] == side
assert api_mock.create_order.call_args[0][3] == 1 assert api_mock.create_order.call_args[0][3] == 1
@ -1162,7 +1190,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
exchange.trading_mode = TradingMode.FUTURES exchange.trading_mode = TradingMode.FUTURES
order = exchange.create_order( order = exchange.create_order(
pair='ETH/BTC', pair='XLTCUSDT',
ordertype=ordertype, ordertype=ordertype,
side=side, side=side,
amount=1, amount=1,
@ -1172,6 +1200,8 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
assert exchange._set_leverage.call_count == 1 assert exchange._set_leverage.call_count == 1
assert exchange.set_margin_mode.call_count == 1 assert exchange.set_margin_mode.call_count == 1
# assert api_mock.create_order.call_args[0][3] == 100
assert order['amount'] == 0.01
def test_buy_dry_run(default_conf, mocker): def test_buy_dry_run(default_conf, mocker):
@ -2291,6 +2321,43 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000) await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000)
@pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_fetch_trades_contract_size(default_conf, mocker, caplog, exchange_name,
fetch_trades_result):
caplog.set_level(logging.DEBUG)
default_conf['collateral'] = 'isolated'
default_conf['trading_mode'] = 'futures'
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
# Monkey-patch async function
exchange._api_async.fetch_trades = get_mock_coro([
{'info': {'a': 126181333,
'p': '0.01952600',
'q': '0.01200000',
'f': 138604158,
'l': 138604158,
'T': 1565798399872,
'm': True,
'M': True},
'timestamp': 1565798399872,
'datetime': '2019-08-14T15:59:59.872Z',
'symbol': 'ETH/USDT:USDT',
'id': '126181383',
'order': None,
'type': None,
'takerOrMaker': None,
'side': 'sell',
'price': 2.0,
'amount': 30.0,
'cost': 60.0,
'fee': None}]
)
pair = 'ETH/USDT:USDT'
res = await exchange._async_fetch_trades(pair, since=None, params=None)
assert res[0][5] == 300
@pytest.mark.asyncio @pytest.mark.asyncio
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_get_trade_history_id(default_conf, mocker, exchange_name, async def test__async_get_trade_history_id(default_conf, mocker, exchange_name,
@ -2515,24 +2582,31 @@ def test_cancel_order_with_result_error(default_conf, mocker, exchange_name, cap
# Ensure that if not dry_run, we should call API # Ensure that if not dry_run, we should call API
@pytest.mark.parametrize("trading_mode,amount", [
('spot', 2),
('futures', 20),
])
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_cancel_order(default_conf, mocker, exchange_name): def test_cancel_order(default_conf, mocker, exchange_name, trading_mode, amount):
# TODO-lev: Test for contract sizes of 0.01 and 10
default_conf['dry_run'] = False default_conf['dry_run'] = False
default_conf['trading_mode'] = trading_mode
default_conf['collateral'] = 'isolated'
api_mock = MagicMock() api_mock = MagicMock()
api_mock.cancel_order = MagicMock(return_value={'id': '123'}) api_mock.cancel_order = MagicMock(
return_value={'id': '123', 'amount': 2, 'symbol': 'ETH/USDT:USDT'})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == {'id': '123'} assert exchange.cancel_order(
order_id='_', pair='ETH/USDT:USDT') == {'id': '123', 'amount': amount, 'symbol': 'ETH/USDT:USDT'}
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order")) api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.cancel_order(order_id='_', pair='TKN/BTC') exchange.cancel_order(order_id='_', pair='ETH/USDT:USDT')
assert api_mock.cancel_order.call_count == 1 assert api_mock.cancel_order.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
"cancel_order", "cancel_order", "cancel_order", "cancel_order",
order_id='_', pair='TKN/BTC') order_id='_', pair='ETH/USDT:USDT')
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
@ -2594,13 +2668,17 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123) exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123)
@pytest.mark.parametrize("trading_mode,amount", [
('spot', 2),
('futures', 20),
])
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_order(default_conf, mocker, exchange_name, caplog): def test_fetch_order(default_conf, mocker, exchange_name, caplog, trading_mode, amount):
# TODO-lev: Test for contract sizes of 0.01 and 10
default_conf['dry_run'] = True default_conf['dry_run'] = True
default_conf['exchange']['log_responses'] = True default_conf['exchange']['log_responses'] = True
order = MagicMock() order = MagicMock()
order.myid = 123 order.myid = 123
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
exchange._dry_run_open_orders['X'] = order exchange._dry_run_open_orders['X'] = order
assert exchange.fetch_order('X', 'TKN/BTC').myid == 123 assert exchange.fetch_order('X', 'TKN/BTC').myid == 123
@ -2609,11 +2687,20 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog):
exchange.fetch_order('Y', 'TKN/BTC') exchange.fetch_order('Y', 'TKN/BTC')
default_conf['dry_run'] = False default_conf['dry_run'] = False
default_conf['trading_mode'] = trading_mode
default_conf['collateral'] = 'isolated'
api_mock = MagicMock() api_mock = MagicMock()
api_mock.fetch_order = MagicMock(return_value=456) api_mock.fetch_order = MagicMock(
return_value={'id': '123', 'amount': 2, 'symbol': 'ETH/USDT:USDT'})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
assert exchange.fetch_order('X', 'TKN/BTC') == 456 assert exchange.fetch_order(
assert log_has("API fetch_order: 456", caplog) 'X', 'TKN/BTC') == {'id': '123', 'amount': amount, 'symbol': 'ETH/USDT:USDT'}
assert log_has(
("API fetch_order: {\'id\': \'123\', \'amount\': "
+ str(amount) + ", \'symbol\': \'ETH/USDT:USDT\'}"
),
caplog
)
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
@ -2706,12 +2793,17 @@ def test_name(default_conf, mocker, exchange_name):
assert exchange.id == exchange_name assert exchange.id == exchange_name
@pytest.mark.parametrize("trading_mode,amount", [
('spot', 0.2340606),
('futures', 2.340606),
])
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_trades_for_order(default_conf, mocker, exchange_name): def test_get_trades_for_order(default_conf, mocker, exchange_name, trading_mode, amount):
# TODO-lev: Test for contract sizes of 0.01 and 10
order_id = 'ABCD-ABCD' order_id = 'ABCD-ABCD'
since = datetime(2018, 5, 5, 0, 0, 0) since = datetime(2018, 5, 5, 0, 0, 0)
default_conf["dry_run"] = False default_conf["dry_run"] = False
default_conf["trading_mode"] = trading_mode
default_conf["collateral"] = 'isolated'
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
api_mock = MagicMock() api_mock = MagicMock()
@ -2728,22 +2820,24 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name):
'id': 'ABCD-ABCD'}, 'id': 'ABCD-ABCD'},
'timestamp': 1519860024438, 'timestamp': 1519860024438,
'datetime': '2018-02-28T23:20:24.438Z', 'datetime': '2018-02-28T23:20:24.438Z',
'symbol': 'LTC/BTC', 'symbol': 'ETH/USDT:USDT',
'type': 'limit', 'type': 'limit',
'side': 'buy', 'side': 'buy',
'price': 165.0, 'price': 165.0,
'amount': 0.2340606, 'amount': 0.2340606,
'fee': {'cost': 0.06179, 'currency': 'BTC'} 'fee': {'cost': 0.06179, 'currency': 'BTC'}
}]) }])
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
orders = exchange.get_trades_for_order(order_id, 'LTC/BTC', since) orders = exchange.get_trades_for_order(order_id, 'ETH/USDT:USDT', since)
assert len(orders) == 1 assert len(orders) == 1
assert orders[0]['price'] == 165 assert orders[0]['price'] == 165
assert orders[0]['amount'] == amount
assert api_mock.fetch_my_trades.call_count == 1 assert api_mock.fetch_my_trades.call_count == 1
# since argument should be # since argument should be
assert isinstance(api_mock.fetch_my_trades.call_args[0][1], int) assert isinstance(api_mock.fetch_my_trades.call_args[0][1], int)
assert api_mock.fetch_my_trades.call_args[0][0] == 'LTC/BTC' assert api_mock.fetch_my_trades.call_args[0][0] == 'ETH/USDT:USDT'
# Same test twice, hardcoded number and doing the same calculation # Same test twice, hardcoded number and doing the same calculation
assert api_mock.fetch_my_trades.call_args[0][1] == 1525478395000 assert api_mock.fetch_my_trades.call_args[0][1] == 1525478395000
assert api_mock.fetch_my_trades.call_args[0][1] == int(since.replace( assert api_mock.fetch_my_trades.call_args[0][1] == int(since.replace(
@ -2751,10 +2845,10 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name):
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
'get_trades_for_order', 'fetch_my_trades', 'get_trades_for_order', 'fetch_my_trades',
order_id=order_id, pair='LTC/BTC', since=since) order_id=order_id, pair='ETH/USDT:USDT', since=since)
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False)) mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False))
assert exchange.get_trades_for_order(order_id, 'LTC/BTC', since) == [] assert exchange.get_trades_for_order(order_id, 'ETH/USDT:USDT', since) == []
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
@ -3462,6 +3556,81 @@ def test__get_funding_fee(
assert kraken._get_funding_fee(size, funding_rate, mark_price, time_in_ratio) == kraken_fee assert kraken._get_funding_fee(size, funding_rate, mark_price, time_in_ratio) == kraken_fee
def test__get_mark_price_history(mocker, default_conf, mark_ohlcv):
api_mock = MagicMock()
api_mock.fetch_ohlcv = MagicMock(return_value=mark_ohlcv)
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
# mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
mark_prices = exchange._get_mark_price_history("ADA/USDT", 1630454400000)
assert mark_prices == {
1630454400000: 2.77,
1630458000000: 2.73,
1630461600000: 2.74,
1630465200000: 2.76,
1630468800000: 2.76,
1630472400000: 2.77,
1630476000000: 2.78,
1630479600000: 2.78,
1630483200000: 2.77,
1630486800000: 2.77,
1630490400000: 2.84,
1630494000000: 2.81,
1630497600000: 2.81,
1630501200000: 2.82,
}
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"binance",
"_get_mark_price_history",
"fetch_ohlcv",
pair="ADA/USDT",
since=1635580800001
)
def test_get_funding_rate_history(mocker, default_conf, funding_rate_history_hourly):
api_mock = MagicMock()
api_mock.fetch_funding_rate_history = MagicMock(return_value=funding_rate_history_hourly)
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
# mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
funding_rates = exchange.get_funding_rate_history('ADA/USDT', 1635580800001)
assert funding_rates == {
1630454400000: -0.000008,
1630458000000: -0.000004,
1630461600000: 0.000012,
1630465200000: -0.000003,
1630468800000: -0.000007,
1630472400000: 0.000003,
1630476000000: 0.000019,
1630479600000: 0.000003,
1630483200000: -0.000003,
1630486800000: 0,
1630490400000: 0.000013,
1630494000000: 0.000077,
1630497600000: 0.000072,
1630501200000: 0.000097,
}
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"binance",
"get_funding_rate_history",
"fetch_funding_rate_history",
pair="ADA/USDT",
since=1630454400000
)
@pytest.mark.parametrize('exchange,rate_start,rate_end,d1,d2,amount,expected_fees', [ @pytest.mark.parametrize('exchange,rate_start,rate_end,d1,d2,amount,expected_fees', [
('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999999999999), ('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0009140999999999999),
('binance', 0, 2, "2021-09-01 00:00:15", "2021-09-01 08:00:00", 30.0, -0.0009140999999999999), ('binance', 0, 2, "2021-09-01 00:00:15", "2021-09-01 08:00:00", 30.0, -0.0009140999999999999),
@ -3583,26 +3752,163 @@ def test__calculate_funding_fees_datetime_called(
assert funding_fees == expected_fees assert funding_fees == expected_fees
def test__get_contract_size(): @pytest.mark.parametrize('pair,expected_size,trading_mode', [
# TODO-lev ('XLTCUSDT', 1, 'spot'),
return ('LTC/USD', 1, 'futures'),
('XLTCUSDT', 0.01, 'futures'),
('LTC/ETH', 1, 'futures'),
('ETH/USDT:USDT', 10, 'futures')
])
def test__get_contract_size(mocker, default_conf, markets, pair, expected_size, trading_mode):
api_mock = MagicMock()
default_conf['trading_mode'] = trading_mode
default_conf['collateral'] = 'isolated'
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
size = exchange._get_contract_size(pair)
assert expected_size == size
def test__trades_contracts_to_amount(): @pytest.mark.parametrize('pair,contract_size,trading_mode', [
# TODO-lev ('XLTCUSDT', 1, 'spot'),
return ('LTC/USD', 1, 'futures'),
('XLTCUSDT', 0.01, 'futures'),
('LTC/ETH', 1, 'futures'),
('ETH/USDT:USDT', 10, 'futures'),
])
def test__order_contracts_to_amount(
mocker,
default_conf,
markets,
pair,
contract_size,
trading_mode,
):
api_mock = MagicMock()
default_conf['trading_mode'] = trading_mode
default_conf['collateral'] = 'isolated'
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
orders = [
{
'id': '123456320',
'clientOrderId': '12345632018',
'timestamp': 1640124992000,
'datetime': 'Tue 21 Dec 2021 22:16:32 UTC',
'lastTradeTimestamp': 1640124911000,
'status': 'active',
'symbol': pair,
'type': 'limit',
'timeInForce': 'gtc',
'postOnly': None,
'side': 'buy',
'price': 2.0,
'stopPrice': None,
'average': None,
'amount': 30.0,
'cost': 60.0,
'filled': None,
'remaining': 30.0,
'fee': 0.06,
'fees': [{
'currency': 'USDT',
'cost': 0.06,
}],
'trades': None,
'info': {},
},
{
'id': '123456380',
'clientOrderId': '12345638203',
'timestamp': 1640124992000,
'datetime': 'Tue 21 Dec 2021 22:16:32 UTC',
'lastTradeTimestamp': 1640124911000,
'status': 'active',
'symbol': pair,
'type': 'limit',
'timeInForce': 'gtc',
'postOnly': None,
'side': 'sell',
'price': 2.2,
'stopPrice': None,
'average': None,
'amount': 40.0,
'cost': 80.0,
'filled': None,
'remaining': 40.0,
'fee': 0.08,
'fees': [{
'currency': 'USDT',
'cost': 0.08,
}],
'trades': None,
'info': {},
},
]
order1 = exchange._order_contracts_to_amount(orders[0])
order2 = exchange._order_contracts_to_amount(orders[1])
assert order1['amount'] == 30.0 * contract_size
assert order2['amount'] == 40.0 * contract_size
def test__order_contracts_to_amount(): @pytest.mark.parametrize('pair,contract_size,trading_mode', [
# TODO-lev ('XLTCUSDT', 1, 'spot'),
return ('LTC/USD', 1, 'futures'),
('XLTCUSDT', 0.01, 'futures'),
('LTC/ETH', 1, 'futures'),
('ETH/USDT:USDT', 10, 'futures'),
])
def test__trades_contracts_to_amount(
mocker,
default_conf,
markets,
pair,
contract_size,
trading_mode,
):
api_mock = MagicMock()
default_conf['trading_mode'] = trading_mode
default_conf['collateral'] = 'isolated'
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
trades = [
{
'symbol': pair,
'amount': 30.0,
},
{
'symbol': pair,
'amount': 40.0,
}
]
new_amount_trades = exchange._trades_contracts_to_amount(trades)
assert new_amount_trades[0]['amount'] == 30.0 * contract_size
assert new_amount_trades[1]['amount'] == 40.0 * contract_size
def test__amount_to_contract_size(): @pytest.mark.parametrize('pair,param_amount,param_size', [
# TODO-lev ('XLTCUSDT', 40, 4000),
return ('LTC/ETH', 30, 30),
('ETH/USDT:USDT', 10, 1),
])
def test__contract_size_to_amount(): def test__amount_to_contracts(
# TODO-lev mocker,
return default_conf,
markets,
pair,
param_amount,
param_size
):
api_mock = MagicMock()
default_conf['trading_mode'] = 'futures'
default_conf['collateral'] = 'isolated'
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
result_size = exchange._amount_to_contracts(pair, param_amount)
assert result_size == param_size
result_amount = exchange._contracts_to_amount(pair, param_size)
assert result_amount == param_amount