Merge pull request #5888 from samgermain/contract-sizes

Convert contract size to underlying asset size
This commit is contained in:
Matthias 2022-01-03 21:55:19 +01:00 committed by GitHub
commit e57c2d64a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 459 additions and 37 deletions

View File

@ -370,6 +370,49 @@ class Exchange:
else:
return DataFrame()
def _get_contract_size(self, pair: str) -> int:
if self.trading_mode == TradingMode.FUTURES:
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:
return 1
def _trades_contracts_to_amount(self, trades: List) -> List:
if len(trades) > 0 and 'symbol' in trades[0]:
contract_size = self._get_contract_size(trades[0]['symbol'])
if contract_size != 1:
for trade in trades:
trade['amount'] = trade['amount'] * contract_size
return trades
def _order_contracts_to_amount(self, order: Dict) -> Dict:
if 'symbol' in order:
contract_size = self._get_contract_size(order['symbol'])
if contract_size != 1:
for prop in ['amount', 'cost', 'filled', 'remaining']:
if prop in order and order[prop] is not None:
order[prop] = order[prop] * contract_size
return order
def _amount_to_contracts(self, pair: str, amount: float):
contract_size = self._get_contract_size(pair)
if contract_size and contract_size != 1:
return amount / contract_size
else:
return amount
def _contracts_to_amount(self, pair: str, num_contracts: float):
contract_size = self._get_contract_size(pair)
if contract_size and contract_size != 1:
return num_contracts * contract_size
else:
return num_contracts
def set_sandbox(self, api: ccxt.Exchange, exchange_config: dict, name: str) -> None:
if exchange_config.get('sandbox'):
if api.urls.get('test'):
@ -587,6 +630,7 @@ class Exchange:
Re-implementation of ccxt internal methods - ensuring we can test the result is correct
based on our definitions.
"""
amount = self._amount_to_contracts(pair, amount)
if self.markets[pair]['precision']['amount']:
amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE,
precision=self.markets[pair]['precision']['amount'],
@ -645,11 +689,21 @@ class Exchange:
limits = market['limits']
if ('cost' in limits and 'min' in limits['cost']
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']
and limits['amount']['min'] is not None):
min_stake_amounts.append(limits['amount']['min'] * price)
min_stake_amounts.append(
self._contracts_to_amount(
pair,
limits['amount']['min'] * price
)
)
if not min_stake_amounts:
return None
@ -685,7 +739,7 @@ class Exchange:
def create_dry_run_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, leverage: float, params: Dict = {}) -> Dict[str, Any]:
order_id = f'dry_run_{side}_{datetime.now().timestamp()}'
_amount = self.amount_to_precision(pair, amount)
_amount = self._contracts_to_amount(pair, self.amount_to_precision(pair, amount))
dry_order: Dict[str, Any] = {
'id': order_id,
'symbol': pair,
@ -861,6 +915,7 @@ class Exchange:
params
)
self._log_exchange_response('create_order', order)
order = self._order_contracts_to_amount(order)
return order
except ccxt.InsufficientFunds as e:
@ -909,6 +964,7 @@ class Exchange:
try:
order = self._api.fetch_order(order_id, pair)
self._log_exchange_response('fetch_order', order)
order = self._order_contracts_to_amount(order)
return order
except ccxt.OrderNotFound as e:
raise RetryableOrderError(
@ -963,6 +1019,7 @@ class Exchange:
try:
order = self._api.cancel_order(order_id, pair)
self._log_exchange_response('cancel_order', order)
order = self._order_contracts_to_amount(order)
return order
except ccxt.InvalidOrder as e:
raise InvalidOrderException(
@ -1230,6 +1287,9 @@ class Exchange:
matched_trades = [trade for trade in my_trades if trade['order'] == order_id]
self._log_exchange_response('get_trades_for_order', matched_trades)
matched_trades = self._trades_contracts_to_amount(matched_trades)
return matched_trades
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
@ -1572,6 +1632,7 @@ class Exchange:
'(' + arrow.get(since // 1000).isoformat() + ') ' if since is not None else ''
)
trades = await self._api_async.fetch_trades(pair, since=since, limit=1000)
trades = self._trades_contracts_to_amount(trades)
return trades_dict_to_list(trades)
except ccxt.NotSupported as e:
raise OperationalException(

View File

@ -914,6 +914,7 @@ def get_markets():
'active': True,
'spot': True,
'type': 'spot',
'contractSize': None,
'precision': {
'amount': 8,
'price': 8
@ -937,7 +938,8 @@ def get_markets():
'quote': 'USDT',
'active': True,
'spot': False,
'type': 'SomethingElse',
'type': 'swap',
'contractSize': 0.01,
'precision': {
'amount': 8,
'price': 8
@ -985,6 +987,59 @@ def get_markets():
'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

@ -224,27 +224,46 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog):
ex.validate_order_time_in_force(tif2)
@pytest.mark.parametrize("amount,precision_mode,precision,expected", [
(2.34559, 2, 4, 2.3455),
(2.34559, 2, 5, 2.34559),
(2.34559, 2, 3, 2.345),
(2.9999, 2, 3, 2.999),
(2.9909, 2, 3, 2.990),
@pytest.mark.parametrize("amount,precision_mode,precision,contract_size,expected,trading_mode", [
(2.34559, 2, 4, 1, 2.3455, 'spot'),
(2.34559, 2, 5, 1, 2.34559, 'spot'),
(2.34559, 2, 3, 1, 2.345, 'spot'),
(2.9999, 2, 3, 1, 2.999, 'spot'),
(2.9909, 2, 3, 1, 2.990, 'spot'),
# Tests for Tick-size
(2.34559, 4, 0.0001, 2.3455),
(2.34559, 4, 0.00001, 2.34559),
(2.34559, 4, 0.001, 2.345),
(2.9999, 4, 0.001, 2.999),
(2.9909, 4, 0.001, 2.990),
(2.9909, 4, 0.005, 2.990),
(2.9999, 4, 0.005, 2.995),
(2.34559, 4, 0.0001, 1, 2.3455, 'spot'),
(2.34559, 4, 0.00001, 1, 2.34559, 'spot'),
(2.34559, 4, 0.001, 1, 2.345, 'spot'),
(2.9999, 4, 0.001, 1, 2.999, 'spot'),
(2.9909, 4, 0.001, 1, 2.990, 'spot'),
(2.9909, 4, 0.005, 0.01, 299.09, 'futures'),
(2.9999, 4, 0.005, 10, 0.295, 'futures'),
])
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,
trading_mode
):
"""
Test rounds down
"""
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': precision}}})
markets = PropertyMock(return_value={
'ETH/BTC': {
'contractSize': contract_size,
'precision': {
'amount': precision
}
}
})
default_conf['trading_mode'] = trading_mode
default_conf['collateral'] = 'isolated'
exchange = get_patched_exchange(mocker, default_conf, id="binance")
# digits counting mode
@ -465,6 +484,28 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0)
assert isclose(result, expected_result/12)
markets["ETH/BTC"]["contractSize"] = 0.01
default_conf['trading_mode'] = 'futures'
default_conf['collateral'] = 'isolated'
exchange = get_patched_exchange(mocker, default_conf, id="binance")
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:
exchange = get_patched_exchange(mocker, default_conf, id="binance")
@ -1020,6 +1061,7 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name):
assert order["side"] == side
assert order["type"] == "limit"
assert order["symbol"] == "ETH/BTC"
assert order["amount"] == 1
@pytest.mark.parametrize("side,startprice,endprice", [
@ -1127,9 +1169,12 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
'id': order_id,
'info': {
'foo': 'bar'
}
},
'symbol': 'XLTCUSDT',
'amount': 1
})
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.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
@ -1137,7 +1182,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
exchange.set_margin_mode = MagicMock()
order = exchange.create_order(
pair='ETH/BTC',
pair='XLTCUSDT',
ordertype=ordertype,
side=side,
amount=1,
@ -1148,7 +1193,8 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
assert 'id' in order
assert 'info' in order
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][2] == side
assert api_mock.create_order.call_args[0][3] == 1
@ -1158,7 +1204,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
exchange.trading_mode = TradingMode.FUTURES
order = exchange.create_order(
pair='ETH/BTC',
pair='XLTCUSDT',
ordertype=ordertype,
side=side,
amount=1,
@ -1168,6 +1214,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
assert exchange._set_leverage.call_count == 1
assert exchange.set_margin_mode.call_count == 1
assert order['amount'] == 0.01
def test_buy_dry_run(default_conf, mocker):
@ -1189,6 +1236,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'symbol': 'ETH/BTC',
'info': {
'foo': 'bar'
}
@ -1264,6 +1312,7 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'symbol': 'ETH/BTC',
'info': {
'foo': 'bar'
}
@ -1326,6 +1375,7 @@ def test_sell_prod(default_conf, mocker, exchange_name):
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'symbol': 'ETH/BTC',
'info': {
'foo': 'bar'
}
@ -1392,6 +1442,7 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'symbol': 'ETH/BTC',
'info': {
'foo': 'bar'
}
@ -2243,7 +2294,7 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
@pytest.mark.parametrize("exchange_name", EXCHANGES)
async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
fetch_trades_result):
# TODO-lev: Test for contract sizes of 0.01 and 10
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
# Monkey-patch async function
@ -2287,6 +2338,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)
@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.parametrize("exchange_name", EXCHANGES)
async def test__async_get_trade_history_id(default_conf, mocker, exchange_name,
@ -2595,6 +2683,8 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog):
default_conf['exchange']['log_responses'] = True
order = MagicMock()
order.myid = 123
order.symbol = 'TKN/BTC'
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
exchange._dry_run_open_orders['X'] = order
assert exchange.fetch_order('X', 'TKN/BTC').myid == 123
@ -2604,10 +2694,15 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog):
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.fetch_order = MagicMock(return_value=456)
api_mock.fetch_order = MagicMock(return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
assert exchange.fetch_order('X', 'TKN/BTC') == 456
assert log_has("API fetch_order: 456", caplog)
assert exchange.fetch_order(
'X', 'TKN/BTC') == {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}
assert log_has(
("API fetch_order: {\'id\': \'123\', \'amount\': 2, \'symbol\': \'TKN/BTC\'}"
),
caplog
)
with pytest.raises(InvalidOrderException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
@ -2651,9 +2746,9 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
default_conf['dry_run'] = False
api_mock = MagicMock()
api_mock.fetch_order = MagicMock(return_value=456)
api_mock.fetch_order = MagicMock(return_value={'id': '123', 'symbol': 'TKN/BTC'})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
assert exchange.fetch_stoploss_order('X', 'TKN/BTC') == 456
assert exchange.fetch_stoploss_order('X', 'TKN/BTC') == {'id': '123', 'symbol': 'TKN/BTC'}
with pytest.raises(InvalidOrderException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
@ -2700,12 +2795,17 @@ def test_name(default_conf, mocker, 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)
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):
order_id = 'ABCD-ABCD'
since = datetime(2018, 5, 5, 0, 0, 0)
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)
api_mock = MagicMock()
@ -2722,22 +2822,24 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name):
'id': 'ABCD-ABCD'},
'timestamp': 1519860024438,
'datetime': '2018-02-28T23:20:24.438Z',
'symbol': 'LTC/BTC',
'symbol': 'ETH/USDT:USDT',
'type': 'limit',
'side': 'buy',
'price': 165.0,
'amount': 0.2340606,
'fee': {'cost': 0.06179, 'currency': 'BTC'}
}])
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 orders[0]['price'] == 165
assert isclose(orders[0]['amount'], amount)
assert api_mock.fetch_my_trades.call_count == 1
# since argument should be
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
assert api_mock.fetch_my_trades.call_args[0][1] == 1525478395000
assert api_mock.fetch_my_trades.call_args[0][1] == int(since.replace(
@ -2745,10 +2847,10 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name):
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
'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))
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)
@ -3549,7 +3651,7 @@ def test__calculate_funding_fees(
assert pytest.approx(funding_fees) == expected_fees
@ pytest.mark.parametrize('exchange,expected_fees', [
@pytest.mark.parametrize('exchange,expected_fees', [
('binance', -0.0009140999999999999),
('gateio', -0.0009140999999999999),
])
@ -3575,3 +3677,205 @@ def test__calculate_funding_fees_datetime_called(
time_machine.move_to("2021-09-01 08:00:00 +00:00")
funding_fees = exchange._calculate_funding_fees('ADA/USDT', 30.0, d1)
assert funding_fees == expected_fees
@pytest.mark.parametrize('pair,expected_size,trading_mode', [
('XLTCUSDT', 1, 'spot'),
('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, pair, expected_size, trading_mode):
api_mock = MagicMock()
default_conf['trading_mode'] = trading_mode
default_conf['collateral'] = 'isolated'
mocker.patch('freqtrade.exchange.Exchange.markets', {
'LTC/USD': {
'symbol': 'LTC/USD',
'contractSize': None,
},
'XLTCUSDT': {
'symbol': 'XLTCUSDT',
'contractSize': 0.01,
},
'LTC/ETH': {
'symbol': 'LTC/ETH',
},
'ETH/USDT:USDT': {
'symbol': 'ETH/USDT:USDT',
'contractSize': 10,
}
})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
size = exchange._get_contract_size(pair)
assert expected_size == size
@pytest.mark.parametrize('pair,contract_size,trading_mode', [
('XLTCUSDT', 1, 'spot'),
('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
@pytest.mark.parametrize('pair,contract_size,trading_mode', [
('XLTCUSDT', 1, 'spot'),
('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
@pytest.mark.parametrize('pair,param_amount,param_size', [
('XLTCUSDT', 40, 4000),
('LTC/ETH', 30, 30),
('LTC/USD', 30, 30),
('ETH/USDT:USDT', 10, 1),
])
def test__amount_to_contracts(
mocker,
default_conf,
markets,
pair,
param_amount,
param_size
):
api_mock = MagicMock()
default_conf['trading_mode'] = 'spot'
default_conf['collateral'] = 'isolated'
mocker.patch('freqtrade.exchange.Exchange.markets', {
'LTC/USD': {
'symbol': 'LTC/USD',
'contractSize': None,
},
'XLTCUSDT': {
'symbol': 'XLTCUSDT',
'contractSize': 0.01,
},
'LTC/ETH': {
'symbol': 'LTC/ETH',
},
'ETH/USDT:USDT': {
'symbol': 'ETH/USDT:USDT',
'contractSize': 10,
}
})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
result_size = exchange._amount_to_contracts(pair, param_amount)
assert result_size == param_amount
result_amount = exchange._contracts_to_amount(pair, param_size)
assert result_amount == param_size
default_conf['trading_mode'] = 'futures'
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

View File

@ -21,6 +21,7 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
api_mock.options = {}
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'symbol': 'ETH/BTC',
'info': {
'foo': 'bar'
}
@ -53,6 +54,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'
}