Merge pull request #3239 from freqtrade/feat/fee_handling
Improve fee handling
This commit is contained in:
@@ -22,7 +22,7 @@ from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
|
||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
||||
OperationalException, TemporaryError)
|
||||
from freqtrade.exchange.common import BAD_EXCHANGES, retrier, retrier_async
|
||||
from freqtrade.misc import deep_merge_dicts
|
||||
from freqtrade.misc import deep_merge_dicts, safe_value_fallback
|
||||
|
||||
CcxtModuleType = Any
|
||||
|
||||
@@ -471,26 +471,31 @@ class Exchange:
|
||||
'pair': pair,
|
||||
'price': rate,
|
||||
'amount': _amount,
|
||||
"cost": _amount * rate,
|
||||
'cost': _amount * rate,
|
||||
'type': ordertype,
|
||||
'side': side,
|
||||
'remaining': _amount,
|
||||
'datetime': arrow.utcnow().isoformat(),
|
||||
'status': "closed" if ordertype == "market" else "open",
|
||||
'fee': None,
|
||||
"info": {}
|
||||
'info': {}
|
||||
}
|
||||
self._store_dry_order(dry_order)
|
||||
self._store_dry_order(dry_order, pair)
|
||||
# Copy order and close it - so the returned order is open unless it's a market order
|
||||
return dry_order
|
||||
|
||||
def _store_dry_order(self, dry_order: Dict) -> None:
|
||||
def _store_dry_order(self, dry_order: Dict, pair: str) -> None:
|
||||
closed_order = dry_order.copy()
|
||||
if closed_order["type"] in ["market", "limit"]:
|
||||
if closed_order['type'] in ["market", "limit"]:
|
||||
closed_order.update({
|
||||
"status": "closed",
|
||||
"filled": closed_order["amount"],
|
||||
"remaining": 0
|
||||
'status': 'closed',
|
||||
'filled': closed_order['amount'],
|
||||
'remaining': 0,
|
||||
'fee': {
|
||||
'currency': self.get_pair_quote_currency(pair),
|
||||
'cost': dry_order['cost'] * self.get_fee(pair),
|
||||
'rate': self.get_fee(pair)
|
||||
}
|
||||
})
|
||||
if closed_order["type"] in ["stop_loss_limit"]:
|
||||
closed_order["info"].update({"stopPrice": closed_order["price"]})
|
||||
@@ -1066,6 +1071,61 @@ class Exchange:
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
@staticmethod
|
||||
def order_has_fee(order: Dict) -> bool:
|
||||
"""
|
||||
Verifies if the passed in order dict has the needed keys to extract fees,
|
||||
and that these keys (currency, cost) are not empty.
|
||||
:param order: Order or trade (one trade) dict
|
||||
:return: True if the fee substructure contains currency and cost, false otherwise
|
||||
"""
|
||||
if not isinstance(order, dict):
|
||||
return False
|
||||
return ('fee' in order and order['fee'] is not None
|
||||
and (order['fee'].keys() >= {'currency', 'cost'})
|
||||
and order['fee']['currency'] is not None
|
||||
and order['fee']['cost'] is not None
|
||||
)
|
||||
|
||||
def calculate_fee_rate(self, order: Dict) -> Optional[float]:
|
||||
"""
|
||||
Calculate fee rate if it's not given by the exchange.
|
||||
:param order: Order or trade (one trade) dict
|
||||
"""
|
||||
if order['fee'].get('rate') is not None:
|
||||
return order['fee'].get('rate')
|
||||
fee_curr = order['fee']['currency']
|
||||
# Calculate fee based on order details
|
||||
if fee_curr in self.get_pair_base_currency(order['symbol']):
|
||||
# Base currency - divide by amount
|
||||
return round(
|
||||
order['fee']['cost'] / safe_value_fallback(order, order, 'filled', 'amount'), 8)
|
||||
elif fee_curr in self.get_pair_quote_currency(order['symbol']):
|
||||
# Quote currency - divide by cost
|
||||
return round(order['fee']['cost'] / order['cost'], 8)
|
||||
else:
|
||||
# If Fee currency is a different currency
|
||||
try:
|
||||
comb = self.get_valid_pair_combination(fee_curr, self._config['stake_currency'])
|
||||
tick = self.fetch_ticker(comb)
|
||||
|
||||
fee_to_quote_rate = safe_value_fallback(tick, tick, 'last', 'ask')
|
||||
return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8)
|
||||
except DependencyException:
|
||||
return None
|
||||
|
||||
def extract_cost_curr_rate(self, order: Dict) -> Tuple[float, str, Optional[float]]:
|
||||
"""
|
||||
Extract tuple of cost, currency, rate.
|
||||
Requires order_has_fee to run first!
|
||||
:param order: Order or trade (one trade) dict
|
||||
:return: Tuple with cost, currency, rate of the given fee dict
|
||||
"""
|
||||
return (order['fee']['cost'],
|
||||
order['fee']['currency'],
|
||||
self.calculate_fee_rate(order))
|
||||
# calculate rate ? (order['fee']['cost'] / (order['amount'] * order['price']))
|
||||
|
||||
|
||||
def is_exchange_bad(exchange_name: str) -> bool:
|
||||
return exchange_name in BAD_EXCHANGES
|
||||
|
Reference in New Issue
Block a user