Use order date to fetch trades

using the trade open-date may fail in case of several trade-entries spread over a longer timeperiod.

closes #6551
This commit is contained in:
Matthias 2022-03-20 09:42:34 +01:00
parent 8556e6a053
commit fcec071a08
2 changed files with 28 additions and 18 deletions

View File

@ -1428,14 +1428,14 @@ class FreqtradeBot(LoggingMixin):
def handle_order_fee(self, trade: Trade, order_obj: Order, order: Dict[str, Any]) -> None: def handle_order_fee(self, trade: Trade, order_obj: Order, order: Dict[str, Any]) -> None:
# Try update amount (binance-fix) # Try update amount (binance-fix)
try: try:
new_amount = self.get_real_amount(trade, order) new_amount = self.get_real_amount(trade, order, order_obj)
if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount, if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount,
abs_tol=constants.MATH_CLOSE_PREC): abs_tol=constants.MATH_CLOSE_PREC):
order_obj.ft_fee_base = trade.amount - new_amount order_obj.ft_fee_base = trade.amount - new_amount
except DependencyException as exception: except DependencyException as exception:
logger.warning("Could not update trade amount: %s", exception) logger.warning("Could not update trade amount: %s", exception)
def get_real_amount(self, trade: Trade, order: Dict) -> float: def get_real_amount(self, trade: Trade, order: Dict, order_obj: Order) -> float:
""" """
Detect and update trade fee. Detect and update trade fee.
Calls trade.update_fee() upon correct detection. Calls trade.update_fee() upon correct detection.
@ -1453,7 +1453,7 @@ class FreqtradeBot(LoggingMixin):
# 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)
logger.info(f"Fee for Trade {trade} [{order.get('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:
# Reject all fees that report as > 2%. # Reject all fees that report as > 2%.
@ -1465,17 +1465,18 @@ class FreqtradeBot(LoggingMixin):
return self.apply_fee_conditional(trade, trade_base_currency, return self.apply_fee_conditional(trade, trade_base_currency,
amount=order_amount, fee_abs=fee_cost) amount=order_amount, fee_abs=fee_cost)
return order_amount return order_amount
return self.fee_detection_from_trades(trade, order, order_amount, order.get('trades', [])) 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_amount: float, def fee_detection_from_trades(self, trade: Trade, order: Dict, order_obj: Order,
trades: List) -> float: order_amount: float, trades: List) -> float:
""" """
fee-detection fallback to Trades. fee-detection fallback to Trades.
Either uses provided trades list or the result of fetch_my_trades to get correct fee. Either uses provided trades list or the result of fetch_my_trades to get correct fee.
""" """
if not trades: if not trades:
trades = self.exchange.get_trades_for_order( trades = self.exchange.get_trades_for_order(
self.exchange.get_order_id_conditional(order), trade.pair, trade.open_date) self.exchange.get_order_id_conditional(order), trade.pair, order_obj.order_date)
if len(trades) == 0: if len(trades) == 0:
logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade) logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade)

View File

@ -3568,9 +3568,9 @@ def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fe
open_order_id="123456" open_order_id="123456"
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
# Amount is reduced by "fee" # Amount is reduced by "fee"
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001) assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount - (amount * 0.001)
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).', 'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).',
caplog) caplog)
@ -3594,8 +3594,9 @@ def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_ord
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
walletmock.reset_mock() walletmock.reset_mock()
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
# Amount is kept as is # Amount is kept as is
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount
assert walletmock.call_count == 1 assert walletmock.call_count == 1
assert log_has_re(r'Fee amount for Trade.* was in base currency ' assert log_has_re(r'Fee amount for Trade.* was in base currency '
'- Eating Fee 0.008 into dust', caplog) '- Eating Fee 0.008 into dust', caplog)
@ -3616,8 +3617,9 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
# Amount is reduced by "fee" # Amount is reduced by "fee"
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
'open_rate=0.24544100, open_since=closed) failed: myTrade-Dict empty found', 'open_rate=0.24544100, open_since=closed) failed: myTrade-Dict empty found',
caplog) caplog)
@ -3668,7 +3670,8 @@ def test_get_real_amount(
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', side_effect=ExchangeError) mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', side_effect=ExchangeError)
caplog.clear() caplog.clear()
assert freqtrade.get_real_amount(trade, buy_order) == 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, order_obj) == amount - fee_reduction_amount
if expected_log: if expected_log:
assert log_has(expected_log, caplog) assert log_has(expected_log, caplog)
@ -3715,7 +3718,8 @@ def test_get_real_amount_multi(
# Amount is reduced by "fee" # Amount is reduced by "fee"
expected_amount = amount - (amount * fee_reduction_amount) expected_amount = amount - (amount * fee_reduction_amount)
assert freqtrade.get_real_amount(trade, buy_order_fee) == expected_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( assert log_has(
( (
'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' 'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
@ -3750,8 +3754,9 @@ def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
# Amount does not change # Amount does not change
assert freqtrade.get_real_amount(trade, limit_buy_order_usdt) == amount assert freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj) == amount
def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_doublefee, def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_doublefee,
@ -3773,7 +3778,8 @@ def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_dou
# Amount does not change # Amount does not change
assert trade.fee_open == 0.0025 assert trade.fee_open == 0.0025
assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee) == 30.0 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 tfo_mock.call_count == 0 assert tfo_mock.call_count == 0
# Fetch fees from trades dict if available to get "proper" values # Fetch fees from trades dict if available to get "proper" values
assert round(trade.fee_open, 4) == 0.001 assert round(trade.fee_open, 4) == 0.001
@ -3797,9 +3803,10 @@ def test_get_real_amount_wrong_amount(default_conf_usdt, trades_for_order, buy_o
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
# Amount does not change # Amount does not change
with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"): with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"):
freqtrade.get_real_amount(trade, limit_buy_order_usdt) freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj)
def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_order, buy_order_fee, def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_order, buy_order_fee,
@ -3821,9 +3828,10 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
# Amount changes by fee amount. # Amount changes by fee amount.
assert isclose( assert isclose(
freqtrade.get_real_amount(trade, limit_buy_order_usdt), freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj),
amount - (amount * 0.001), amount - (amount * 0.001),
abs_tol=MATH_CLOSE_PREC, abs_tol=MATH_CLOSE_PREC,
) )
@ -3847,7 +3855,8 @@ def test_get_real_amount_open_trade(default_conf_usdt, fee, mocker):
'side': 'buy', 'side': 'buy',
} }
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
assert freqtrade.get_real_amount(trade, order) == amount order_obj = Order.parse_from_ccxt_object(order, 'LTC/ETH', 'buy')
assert freqtrade.get_real_amount(trade, order, order_obj) == amount
@pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [ @pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [