Refactor calculate_fee_rate to take separate parameters instead of an "Order"

we passed in a trade object anyway
This commit is contained in:
Matthias 2022-07-06 19:15:55 +02:00
parent e52f82b565
commit 2499276fca
3 changed files with 39 additions and 22 deletions

View File

@ -1631,27 +1631,30 @@ class Exchange:
and order['fee']['cost'] is not None and order['fee']['cost'] is not None
) )
def calculate_fee_rate(self, order: Dict) -> Optional[float]: def calculate_fee_rate(
self, fee: Dict, symbol: str, cost: float, amount: float) -> Optional[float]:
""" """
Calculate fee rate if it's not given by the exchange. Calculate fee rate if it's not given by the exchange.
:param order: Order or trade (one trade) dict :param fee: ccxt Fee dict - must contain cost / currency / rate
:param symbol: Symbol of the order
:param cost: Total cost of the order
:param amount: Amount of the order
""" """
if order['fee'].get('rate') is not None: if fee.get('rate') is not None:
return order['fee'].get('rate') return fee.get('rate')
fee_curr = order['fee']['currency'] fee_curr = fee['currency']
# Calculate fee based on order details # Calculate fee based on order details
if fee_curr in self.get_pair_base_currency(order['symbol']): if fee_curr in self.get_pair_base_currency(symbol):
# Base currency - divide by amount # Base currency - divide by amount
return round( return round(fee['cost'] / amount, 8)
order['fee']['cost'] / safe_value_fallback2(order, order, 'filled', 'amount'), 8) elif fee_curr in self.get_pair_quote_currency(symbol):
elif fee_curr in self.get_pair_quote_currency(order['symbol']):
# Quote currency - divide by cost # Quote currency - divide by cost
return round(self._contracts_to_amount( return round(self._contracts_to_amount(
order['symbol'], order['fee']['cost']) / order['cost'], symbol, fee['cost']) / cost,
8) if order['cost'] else None 8) if cost else None
else: else:
# If Fee currency is a different currency # If Fee currency is a different currency
if not order['cost']: if not cost:
# If cost is None or 0.0 -> falsy, return None # If cost is None or 0.0 -> falsy, return None
return None return None
try: try:
@ -1664,18 +1667,28 @@ class Exchange:
if not fee_to_quote_rate: if not fee_to_quote_rate:
return None return None
return round((self._contracts_to_amount( return round((self._contracts_to_amount(
order['symbol'], order['fee']['cost']) * fee_to_quote_rate) / order['cost'], 8) symbol, fee['cost']) * fee_to_quote_rate) / cost, 8)
def extract_cost_curr_rate(self, order: Dict) -> Tuple[float, str, Optional[float]]: def extract_cost_curr_rate(self, fee: Dict, symbol: str, cost: float,
amount: float) -> Tuple[float, str, Optional[float]]:
""" """
Extract tuple of cost, currency, rate. Extract tuple of cost, currency, rate.
Requires order_has_fee to run first! Requires order_has_fee to run first!
:param order: Order or trade (one trade) dict :param fee: ccxt Fee dict - must contain cost / currency / rate
:param symbol: Symbol of the order
:param cost: Total cost of the order
:param amount: Amount of the order
:return: Tuple with cost, currency, rate of the given fee dict :return: Tuple with cost, currency, rate of the given fee dict
""" """
return (order['fee']['cost'], return (fee['cost'],
order['fee']['currency'], fee['currency'],
self.calculate_fee_rate(order)) self.calculate_fee_rate(
fee,
symbol,
cost,
amount
)
)
# Historic data # Historic data

View File

@ -1742,7 +1742,8 @@ class FreqtradeBot(LoggingMixin):
trade_base_currency = self.exchange.get_pair_base_currency(trade.pair) trade_base_currency = self.exchange.get_pair_base_currency(trade.pair)
# use fee from order-dict if possible # use fee from order-dict if possible
if self.exchange.order_has_fee(order): if self.exchange.order_has_fee(order):
fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(order) fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(
order['fee'], order['symbol'], order['cost'], order_obj.safe_filled)
logger.info(f"Fee for Trade {trade} [{order_obj.ft_order_side}]: " logger.info(f"Fee for Trade {trade} [{order_obj.ft_order_side}]: "
f"{fee_cost:.8g} {fee_currency} - rate: {fee_rate}") f"{fee_cost:.8g} {fee_currency} - rate: {fee_rate}")
if fee_rate is None or fee_rate < 0.02: if fee_rate is None or fee_rate < 0.02:
@ -1780,7 +1781,9 @@ class FreqtradeBot(LoggingMixin):
for exectrade in trades: for exectrade in trades:
amount += exectrade['amount'] amount += exectrade['amount']
if self.exchange.order_has_fee(exectrade): if self.exchange.order_has_fee(exectrade):
fee_cost_, fee_currency, fee_rate_ = self.exchange.extract_cost_curr_rate(exectrade) fee_cost_, fee_currency, fee_rate_ = self.exchange.extract_cost_curr_rate(
exectrade['fee'], exectrade['symbol'], exectrade['cost'], exectrade['amount']
)
fee_cost += fee_cost_ fee_cost += fee_cost_
if fee_rate_ is not None: if fee_rate_ is not None:
fee_rate_array.append(fee_rate_) fee_rate_array.append(fee_rate_)

View File

@ -3544,7 +3544,7 @@ def test_order_has_fee(order, expected) -> None:
def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None: def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
mocker.patch('freqtrade.exchange.Exchange.calculate_fee_rate', MagicMock(return_value=0.01)) mocker.patch('freqtrade.exchange.Exchange.calculate_fee_rate', MagicMock(return_value=0.01))
ex = get_patched_exchange(mocker, default_conf) ex = get_patched_exchange(mocker, default_conf)
assert ex.extract_cost_curr_rate(order) == expected assert ex.extract_cost_curr_rate(order['fee'], order['symbol'], cost=20, amount=1) == expected
@pytest.mark.parametrize("order,unknown_fee_rate,expected", [ @pytest.mark.parametrize("order,unknown_fee_rate,expected", [
@ -3590,7 +3590,8 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_r
ex = get_patched_exchange(mocker, default_conf) ex = get_patched_exchange(mocker, default_conf)
assert ex.calculate_fee_rate(order) == expected assert ex.calculate_fee_rate(order['fee'], order['symbol'],
cost=order['cost'], amount=order['amount']) == expected
@pytest.mark.parametrize('retrycount,max_retries,expected', [ @pytest.mark.parametrize('retrycount,max_retries,expected', [