commit
59b0fd1166
@ -77,6 +77,7 @@ class Exchange:
|
|||||||
"mark_ohlcv_price": "mark",
|
"mark_ohlcv_price": "mark",
|
||||||
"mark_ohlcv_timeframe": "8h",
|
"mark_ohlcv_timeframe": "8h",
|
||||||
"ccxt_futures_name": "swap",
|
"ccxt_futures_name": "swap",
|
||||||
|
"fee_cost_in_contracts": False, # Fee cost needs contract conversion
|
||||||
"needs_trading_fees": False, # use fetch_trading_fees to cache fees
|
"needs_trading_fees": False, # use fetch_trading_fees to cache fees
|
||||||
}
|
}
|
||||||
_ft_has: Dict = {}
|
_ft_has: Dict = {}
|
||||||
@ -1631,27 +1632,35 @@ 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.get('currency')
|
||||||
|
if fee_curr is None:
|
||||||
|
return None
|
||||||
|
fee_cost = fee['cost']
|
||||||
|
if self._ft_has['fee_cost_in_contracts']:
|
||||||
|
# Convert cost via "contracts" conversion
|
||||||
|
fee_cost = self._contracts_to_amount(symbol, fee['cost'])
|
||||||
|
|
||||||
# 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 == 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 == 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(fee_cost / cost, 8) if cost else None
|
||||||
order['symbol'], order['fee']['cost']) / order['cost'],
|
|
||||||
8) if order['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:
|
||||||
@ -1663,19 +1672,28 @@ class Exchange:
|
|||||||
fee_to_quote_rate = self._config['exchange'].get('unknown_fee_rate', None)
|
fee_to_quote_rate = self._config['exchange'].get('unknown_fee_rate', None)
|
||||||
if not fee_to_quote_rate:
|
if not fee_to_quote_rate:
|
||||||
return None
|
return None
|
||||||
return round((self._contracts_to_amount(
|
return round((fee_cost * fee_to_quote_rate) / cost, 8)
|
||||||
order['symbol'], order['fee']['cost']) * fee_to_quote_rate) / order['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
|
||||||
|
|
||||||
|
@ -32,7 +32,8 @@ class Gateio(Exchange):
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ft_has_futures: Dict = {
|
_ft_has_futures: Dict = {
|
||||||
"needs_trading_fees": True
|
"needs_trading_fees": True,
|
||||||
|
"fee_cost_in_contracts": False, # Set explicitly to false for clarity
|
||||||
}
|
}
|
||||||
|
|
||||||
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
||||||
|
@ -28,6 +28,7 @@ class Okx(Exchange):
|
|||||||
}
|
}
|
||||||
_ft_has_futures: Dict = {
|
_ft_has_futures: Dict = {
|
||||||
"tickers_have_quoteVolume": False,
|
"tickers_have_quoteVolume": False,
|
||||||
|
"fee_cost_in_contracts": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
||||||
|
@ -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,15 @@ 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)
|
# Prefer singular fee
|
||||||
|
fees = [exectrade['fee']]
|
||||||
|
else:
|
||||||
|
fees = exectrade.get('fees', [])
|
||||||
|
for fee in fees:
|
||||||
|
|
||||||
|
fee_cost_, fee_currency, fee_rate_ = self.exchange.extract_cost_curr_rate(
|
||||||
|
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_)
|
||||||
|
@ -821,7 +821,7 @@ class LocalTrade():
|
|||||||
self.open_rate = total_stake / total_amount
|
self.open_rate = total_stake / total_amount
|
||||||
self.stake_amount = total_stake / (self.leverage or 1.0)
|
self.stake_amount = total_stake / (self.leverage or 1.0)
|
||||||
self.amount = total_amount
|
self.amount = total_amount
|
||||||
self.fee_open_cost = self.fee_open * self.stake_amount
|
self.fee_open_cost = self.fee_open * total_stake
|
||||||
self.recalc_open_trade_value()
|
self.recalc_open_trade_value()
|
||||||
if self.stop_loss_pct is not None and self.open_rate is not None:
|
if self.stop_loss_pct is not None and self.open_rate is not None:
|
||||||
self.adjust_stop_loss(self.open_rate, self.stop_loss_pct)
|
self.adjust_stop_loss(self.open_rate, self.stop_loss_pct)
|
||||||
|
@ -2,7 +2,7 @@ numpy==1.23.0
|
|||||||
pandas==1.4.3
|
pandas==1.4.3
|
||||||
pandas-ta==0.3.14b
|
pandas-ta==0.3.14b
|
||||||
|
|
||||||
ccxt==1.89.96
|
ccxt==1.90.40
|
||||||
# Pin cryptography for now due to rust build errors with piwheels
|
# Pin cryptography for now due to rust build errors with piwheels
|
||||||
cryptography==37.0.2
|
cryptography==37.0.2
|
||||||
aiohttp==3.8.1
|
aiohttp==3.8.1
|
||||||
|
@ -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", [
|
||||||
@ -3582,6 +3582,9 @@ def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
|
|||||||
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 1, 4.0),
|
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 1, 4.0),
|
||||||
({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5,
|
({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5,
|
||||||
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 2, 8.0),
|
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 2, 8.0),
|
||||||
|
# Missing currency
|
||||||
|
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
|
||||||
|
'fee': {'currency': None, 'cost': 0.005}}, None, None),
|
||||||
])
|
])
|
||||||
def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_rate) -> None:
|
def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_rate) -> None:
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081})
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081})
|
||||||
@ -3590,7 +3593,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', [
|
||||||
|
Loading…
Reference in New Issue
Block a user