Simplify base fee handling
This commit is contained in:
parent
50e2808667
commit
c9aa09ec89
@ -1778,7 +1778,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
self.rpc.send_msg(msg)
|
||||
|
||||
def apply_fee_conditional(self, trade: Trade, trade_base_currency: str,
|
||||
amount: float, fee_abs: float) -> float:
|
||||
amount: float, fee_abs: float) -> Optional[float]:
|
||||
"""
|
||||
Applies the fee to amount (either from Order or from Trades).
|
||||
Can eat into dust if more than the required asset is available.
|
||||
@ -1791,35 +1791,32 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.info(f"Fee amount for {trade} was in base currency - "
|
||||
f"Eating Fee {fee_abs} into dust.")
|
||||
elif fee_abs != 0:
|
||||
real_amount = self.exchange.amount_to_precision(trade.pair, amount - fee_abs)
|
||||
logger.info(f"Applying fee on amount for {trade} "
|
||||
f"(from {amount} to {real_amount}).")
|
||||
return real_amount
|
||||
return amount
|
||||
logger.info(f"Applying fee on amount for {trade}, fee={fee_abs}.")
|
||||
return fee_abs
|
||||
return None
|
||||
|
||||
def handle_order_fee(self, trade: Trade, order_obj: Order, order: Dict[str, Any]) -> None:
|
||||
# Try update amount (binance-fix)
|
||||
try:
|
||||
new_amount = self.get_real_amount(trade, order, order_obj)
|
||||
if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount,
|
||||
abs_tol=constants.MATH_CLOSE_PREC):
|
||||
order_obj.ft_fee_base = trade.amount - new_amount
|
||||
fee_abs = self.get_real_amount(trade, order, order_obj)
|
||||
if fee_abs is not None:
|
||||
order_obj.ft_fee_base = fee_abs
|
||||
except DependencyException as exception:
|
||||
logger.warning("Could not update trade amount: %s", exception)
|
||||
|
||||
def get_real_amount(self, trade: Trade, order: Dict, order_obj: Order) -> float:
|
||||
def get_real_amount(self, trade: Trade, order: Dict, order_obj: Order) -> Optional[float]:
|
||||
"""
|
||||
Detect and update trade fee.
|
||||
Calls trade.update_fee() upon correct detection.
|
||||
Returns modified amount if the fee was taken from the destination currency.
|
||||
Necessary for exchanges which charge fees in base currency (e.g. binance)
|
||||
:return: identical (or new) amount for the trade
|
||||
:return: Absolute fee to apply for this order or None
|
||||
"""
|
||||
# Init variables
|
||||
order_amount = safe_value_fallback(order, 'filled', 'amount')
|
||||
# Only run for closed orders
|
||||
if trade.fee_updated(order.get('side', '')) or order['status'] == 'open':
|
||||
return order_amount
|
||||
return None
|
||||
|
||||
trade_base_currency = self.exchange.get_pair_base_currency(trade.pair)
|
||||
# use fee from order-dict if possible
|
||||
@ -1837,12 +1834,12 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Apply fee to amount
|
||||
return self.apply_fee_conditional(trade, trade_base_currency,
|
||||
amount=order_amount, fee_abs=fee_cost)
|
||||
return order_amount
|
||||
return None
|
||||
return self.fee_detection_from_trades(
|
||||
trade, order, order_obj, order_amount, order.get('trades', []))
|
||||
|
||||
def fee_detection_from_trades(self, trade: Trade, order: Dict, order_obj: Order,
|
||||
order_amount: float, trades: List) -> float:
|
||||
order_amount: float, trades: List) -> Optional[float]:
|
||||
"""
|
||||
fee-detection fallback to Trades.
|
||||
Either uses provided trades list or the result of fetch_my_trades to get correct fee.
|
||||
@ -1853,7 +1850,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
if len(trades) == 0:
|
||||
logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade)
|
||||
return order_amount
|
||||
return None
|
||||
fee_currency = None
|
||||
amount = 0
|
||||
fee_abs = 0.0
|
||||
@ -1897,8 +1894,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
if fee_abs != 0:
|
||||
return self.apply_fee_conditional(trade, trade_base_currency,
|
||||
amount=amount, fee_abs=fee_abs)
|
||||
else:
|
||||
return amount
|
||||
return None
|
||||
|
||||
def get_valid_price(self, custom_price: float, proposed_price: float) -> float:
|
||||
"""
|
||||
|
@ -1927,8 +1927,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=order)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
||||
return_value=order['amount'])
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=0.0)
|
||||
order_id = order['id']
|
||||
|
||||
trade = Trade(
|
||||
@ -1960,11 +1959,11 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca
|
||||
assert trade.amount == order['amount']
|
||||
|
||||
trade.open_order_id = order_id
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
|
||||
assert trade.amount != 90.81
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=0.01)
|
||||
assert trade.amount == 30.0
|
||||
# test amount modified by fee-logic
|
||||
freqtrade.update_trade_state(trade, order_id)
|
||||
assert trade.amount == 90.81
|
||||
assert trade.amount == 29.99
|
||||
assert trade.open_order_id is None
|
||||
|
||||
trade.is_open = True
|
||||
@ -4268,10 +4267,10 @@ def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fe
|
||||
caplog.clear()
|
||||
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
|
||||
# Amount is reduced by "fee"
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount - (amount * 0.001)
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == (amount * 0.001)
|
||||
assert log_has(
|
||||
'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, is_short=False,'
|
||||
' leverage=1.0, open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).',
|
||||
' leverage=1.0, open_rate=0.24544100, open_since=closed), fee=0.008.',
|
||||
caplog
|
||||
)
|
||||
|
||||
@ -4296,7 +4295,7 @@ def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_ord
|
||||
walletmock.reset_mock()
|
||||
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
|
||||
# Amount is kept as is
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) is None
|
||||
assert walletmock.call_count == 1
|
||||
assert log_has_re(r'Fee amount for Trade.* was in base currency '
|
||||
'- Eating Fee 0.008 into dust', caplog)
|
||||
@ -4319,7 +4318,7 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock
|
||||
|
||||
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
|
||||
# Amount is reduced by "fee"
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) is None
|
||||
assert log_has(
|
||||
'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
|
||||
'is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed) failed: '
|
||||
@ -4343,8 +4342,7 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock
|
||||
# from order
|
||||
({'cost': 0.004, 'currency': 'LTC'}, 0.004, False, (
|
||||
'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
|
||||
'is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed) (from'
|
||||
' 8.0 to 7.996).'
|
||||
'is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed), fee=0.004.'
|
||||
)),
|
||||
# invalid, no currency in from fee dict
|
||||
({'cost': 0.008, 'currency': None}, 0, True, None),
|
||||
@ -4376,7 +4374,11 @@ def test_get_real_amount(
|
||||
|
||||
caplog.clear()
|
||||
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
|
||||
assert freqtrade.get_real_amount(trade, buy_order, order_obj) == amount - fee_reduction_amount
|
||||
res = freqtrade.get_real_amount(trade, buy_order, order_obj)
|
||||
if fee_reduction_amount == 0:
|
||||
assert res is None
|
||||
else:
|
||||
assert res == fee_reduction_amount
|
||||
|
||||
if expected_log:
|
||||
assert log_has(expected_log, caplog)
|
||||
@ -4422,14 +4424,14 @@ def test_get_real_amount_multi(
|
||||
return_value={'ask': 0.19, 'last': 0.2})
|
||||
|
||||
# Amount is reduced by "fee"
|
||||
expected_amount = amount - (amount * fee_reduction_amount)
|
||||
expected_amount = amount * fee_reduction_amount
|
||||
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
|
||||
assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == expected_amount
|
||||
assert log_has(
|
||||
(
|
||||
'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
|
||||
'is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed) '
|
||||
f'(from 8.0 to {expected_log_amount}).'
|
||||
'is_short=False, leverage=1.0, open_rate=0.24544100, open_since=closed), '
|
||||
f'fee={expected_amount}.'
|
||||
),
|
||||
caplog
|
||||
)
|
||||
@ -4462,7 +4464,7 @@ def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_
|
||||
|
||||
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
|
||||
# Amount does not change
|
||||
assert freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj) == amount
|
||||
assert freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj) is None
|
||||
|
||||
|
||||
def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_doublefee,
|
||||
@ -4485,7 +4487,7 @@ def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_dou
|
||||
# Amount does not change
|
||||
assert trade.fee_open == 0.0025
|
||||
order_obj = Order.parse_from_ccxt_object(market_buy_order_usdt_doublefee, 'LTC/ETH', 'buy')
|
||||
assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee, order_obj) == 30.0
|
||||
assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee, order_obj) is None
|
||||
assert tfo_mock.call_count == 0
|
||||
# Fetch fees from trades dict if available to get "proper" values
|
||||
assert round(trade.fee_open, 4) == 0.001
|
||||
@ -4537,7 +4539,7 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord
|
||||
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
|
||||
# Amount changes by fee amount.
|
||||
assert pytest.approx(freqtrade.get_real_amount(
|
||||
trade, limit_buy_order_usdt, order_obj)) == amount - (amount * 0.001)
|
||||
trade, limit_buy_order_usdt, order_obj)) == (amount * 0.001)
|
||||
|
||||
|
||||
def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker):
|
||||
@ -4559,7 +4561,7 @@ def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker):
|
||||
}
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||
order_obj = Order.parse_from_ccxt_object(order, 'LTC/ETH', 'buy')
|
||||
assert freqtrade.get_real_amount(trade, order, order_obj) == amount
|
||||
assert freqtrade.get_real_amount(trade, order, order_obj) is None
|
||||
|
||||
|
||||
def test_get_real_amount_in_point(default_conf_usdt, buy_order_fee, fee, mocker, caplog):
|
||||
@ -4616,7 +4618,7 @@ def test_get_real_amount_in_point(default_conf_usdt, buy_order_fee, fee, mocker,
|
||||
|
||||
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
|
||||
res = freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj)
|
||||
assert res == amount
|
||||
assert res is None
|
||||
assert trade.fee_open_currency is None
|
||||
assert trade.fee_open_cost is None
|
||||
message = "Not updating buy-fee - rate: None, POINT."
|
||||
@ -4624,7 +4626,7 @@ def test_get_real_amount_in_point(default_conf_usdt, buy_order_fee, fee, mocker,
|
||||
caplog.clear()
|
||||
freqtrade.config['exchange']['unknown_fee_rate'] = 1
|
||||
res = freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj)
|
||||
assert res == amount
|
||||
assert res is None
|
||||
assert trade.fee_open_currency == 'POINT'
|
||||
assert pytest.approx(trade.fee_open_cost) == 0.3046651026
|
||||
assert trade.fee_open == 0.002
|
||||
@ -4633,12 +4635,12 @@ def test_get_real_amount_in_point(default_conf_usdt, buy_order_fee, fee, mocker,
|
||||
|
||||
|
||||
@pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [
|
||||
(8.0, 0.0, 10, 8),
|
||||
(8.0, 0.0, 0, 8),
|
||||
(8.0, 0.1, 0, 7.9),
|
||||
(8.0, 0.1, 10, 8),
|
||||
(8.0, 0.1, 8.0, 8.0),
|
||||
(8.0, 0.1, 7.9, 7.9),
|
||||
(8.0, 0.0, 10, None),
|
||||
(8.0, 0.0, 0, None),
|
||||
(8.0, 0.1, 0, 0.1),
|
||||
(8.0, 0.1, 10, None),
|
||||
(8.0, 0.1, 8.0, None),
|
||||
(8.0, 0.1, 7.9, 0.1),
|
||||
])
|
||||
def test_apply_fee_conditional(default_conf_usdt, fee, mocker,
|
||||
amount, fee_abs, wallet, amount_exp):
|
||||
|
Loading…
Reference in New Issue
Block a user