Merge pull request #5888 from samgermain/contract-sizes
Convert contract size to underlying asset size
This commit is contained in:
commit
e57c2d64a5
@ -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(
|
||||
|
@ -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': {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
@ -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
|
||||
|
@ -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'
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user