diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index fd7c8ce2d..454c62072 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -625,7 +625,7 @@ class DigDeeperStrategy(IStrategy): :param pair: Pair that's currently analyzed :param trade: trade object. :param current_time: datetime object, containing the current datetime - :param current_rate: Current buy rate. Use `exchange.get_rate` if you need sell rate. + :param current_rate: Current buy rate. :param current_profit: Current profit (as ratio), calculated based on current_rate. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: Stake amount to adjust your trade @@ -642,10 +642,7 @@ class DigDeeperStrategy(IStrategy): if last_candle['close'] < previous_candle['close']: return None - count_of_buys = 0 - for order in trade.orders: - if order.ft_order_side == 'buy' and order.status == "closed": - count_of_buys += 1 + count_of_buys = trade.nr_of_successful_buys() # Allow up to 3 additional increasingly larger buys (4 in total) # Initial buy is 1x diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0ef2d8fe1..508bf9b73 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -459,7 +459,7 @@ class FreqtradeBot(LoggingMixin): # Walk through each pair and check if it needs changes for trade in Trade.get_open_trades(): # If there is any open orders, wait for them to finish. - if len([o for o in trade.orders if o.ft_is_open]) == 0: + if trade.open_order_id is None: try: self.check_and_call_adjust_trade_position(trade) except DependencyException as exception: @@ -474,11 +474,15 @@ class FreqtradeBot(LoggingMixin): """ current_rate = self.exchange.get_rate(trade.pair, refresh=True, side="buy") current_profit = trade.calc_profit_ratio(current_rate) + min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair, + current_rate, + self.strategy.stoploss) + max_stake_amount = self.wallets.get_available_stake_amount() logger.debug(f"Calling adjust_trade_position for pair {trade.pair}") stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None)( - pair=trade.pair, trade=trade, current_time=datetime.now(timezone.utc), - current_rate=current_rate, current_profit=current_profit) + trade=trade, current_time=datetime.now(timezone.utc), current_rate=current_rate, + current_profit=current_profit, min_stake=min_stake_amount, max_stake=max_stake_amount) if stake_amount is not None and stake_amount > 0.0: # We should increase our position diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index ceb4c36dc..7d3869a2c 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -358,10 +358,12 @@ class Backtesting: ) -> LocalTrade: current_profit = trade.calc_profit_ratio(row[OPEN_IDX]) + min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, row[OPEN_IDX], -0.1) + max_stake = self.wallets.get_available_stake_amount() stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None)( - pair=trade.pair, trade=trade, current_time=row[DATE_IDX].to_pydatetime(), - current_rate=row[OPEN_IDX], current_profit=current_profit) + trade=trade, current_time=row[DATE_IDX].to_pydatetime(), current_rate=row[OPEN_IDX], + current_profit=current_profit, min_stake=min_stake, max_stake=max_stake) # Check if we should increase our position if stake_amount is not None and stake_amount > 0.0: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 335ffb3e5..36061dc20 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -384,9 +384,9 @@ class IStrategy(ABC, HyperStrategyMixin): """ return proposed_stake - def adjust_trade_position(self, pair: str, trade: Trade, current_time: datetime, - current_rate: float, current_profit: float, **kwargs - ) -> Optional[float]: + def adjust_trade_position(self, trade: Trade, current_time: datetime, + current_rate: float, current_profit: float, min_stake: float, + max_stake: float, **kwargs) -> Optional[float]: """ Custom trade adjustment logic, returning the stake amount that a trade should be increased. This means extra buy orders with additional fees. @@ -395,11 +395,12 @@ class IStrategy(ABC, HyperStrategyMixin): When not implemented by a strategy, returns None - :param pair: Pair that's currently analyzed :param trade: trade object. :param current_time: datetime object, containing the current datetime - :param current_rate: Current buy rate. Use `exchange.get_rate` if you need sell rate. + :param current_rate: Current buy rate. :param current_profit: Current profit (as ratio), calculated based on current_rate. + :param min_stake: Minimal stake size allowed by exchange. + :param max_stake: Balance available for trading. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: Stake amount to adjust your trade """ diff --git a/tests/strategy/strats/strategy_test_v2.py b/tests/strategy/strats/strategy_test_v2.py index 0b0e533c3..1aadd0d4e 100644 --- a/tests/strategy/strats/strategy_test_v2.py +++ b/tests/strategy/strats/strategy_test_v2.py @@ -162,12 +162,12 @@ class StrategyTestV2(IStrategy): 'sell'] = 1 return dataframe - def adjust_trade_position(self, pair: str, trade: Trade, current_time: datetime, - current_rate: float, current_profit: float, **kwargs): + def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, + current_profit: float, min_stake: float, max_stake: float, **kwargs): if current_profit < -0.0075: try: - return self.wallets.get_trade_stake_amount(pair, None) + return self.wallets.get_trade_stake_amount(trade.pair, None) except DependencyException: pass diff --git a/tests/test_integration.py b/tests/test_integration.py index 21b01126f..e1cbf3d2d 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -259,6 +259,8 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: assert trade.amount == trade.orders[0].amount + trade.orders[1].amount + assert trade.nr_of_successful_buys() == 2 + # Sell patch_get_signal(freqtrade, value=(False, True, None, None)) freqtrade.process() @@ -270,3 +272,5 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: # Sold everything assert trade.orders[-1].side == 'sell' assert trade.orders[2].amount == trade.amount + + assert trade.nr_of_successful_buys() == 2 diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 1a0c3c408..60d71d007 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1553,20 +1553,21 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee): assert trade.open_rate == o1_rate assert trade.fee_open_cost == o1_fee_cost assert trade.open_trade_value == o1_trade_val + assert trade.nr_of_successful_buys() == 1 order2 = Order( ft_order_side='buy', ft_pair=trade.pair, ft_is_open=True, - status="closed", + status="open", symbol=trade.pair, order_type="market", side="buy", - price=1, - average=2, - filled=0, - remaining=4, - cost=5, + price=o1_rate, + average=o1_rate, + filled=o1_amount, + remaining=0, + cost=o1_cost, order_date=arrow.utcnow().shift(hours=-1).datetime, order_filled_date=arrow.utcnow().shift(hours=-1).datetime, ) @@ -1579,8 +1580,9 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee): assert trade.open_rate == o1_rate assert trade.fee_open_cost == o1_fee_cost assert trade.open_trade_value == o1_trade_val + assert trade.nr_of_successful_buys() == 1 - # Let's try with some other orders +# Let's try with some other orders order3 = Order( ft_order_side='buy', ft_pair=trade.pair, @@ -1606,6 +1608,34 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee): assert trade.open_rate == o1_rate assert trade.fee_open_cost == o1_fee_cost assert trade.open_trade_value == o1_trade_val + assert trade.nr_of_successful_buys() == 1 + + order4 = Order( + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=False, + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=o1_rate, + average=o1_rate, + filled=o1_amount, + remaining=0, + cost=o1_cost, + order_date=arrow.utcnow().shift(hours=-1).datetime, + order_filled_date=arrow.utcnow().shift(hours=-1).datetime, + ) + trade.orders.append(order4) + trade.recalc_trade_from_orders() + + # Validate that the trade values have been changed + assert trade.amount == 2 * o1_amount + assert trade.stake_amount == 2 * o1_amount + assert trade.open_rate == o1_rate + assert trade.fee_open_cost == 2 * o1_fee_cost + assert trade.open_trade_value == 2 * o1_trade_val + assert trade.nr_of_successful_buys() == 2 # Just to make sure sell orders are ignored, let's calculate one more time. sell1 = Order( @@ -1627,8 +1657,10 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee): trade.orders.append(sell1) trade.recalc_trade_from_orders() - assert trade.amount == o1_amount - assert trade.stake_amount == o1_amount + assert trade.amount == 2 * o1_amount + assert trade.stake_amount == 2 * o1_amount assert trade.open_rate == o1_rate - assert trade.fee_open_cost == o1_fee_cost - assert trade.open_trade_value == o1_trade_val + assert trade.fee_open_cost == 2 * o1_fee_cost + assert trade.open_trade_value == 2 * o1_trade_val + assert trade.nr_of_successful_buys() == 2 +