From fde67798730659ce3f022629853183a0023ddc79 Mon Sep 17 00:00:00 2001 From: Reigo Reinmets Date: Thu, 9 Dec 2021 14:47:44 +0200 Subject: [PATCH] Some code improvements. Still some bugs. --- freqtrade/freqtradebot.py | 104 ++++++++++---------------------- freqtrade/persistence/models.py | 20 ++++++ freqtrade/strategy/interface.py | 6 +- 3 files changed, 56 insertions(+), 74 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 7de0ce79b..b4503fe01 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -450,98 +450,84 @@ class FreqtradeBot(LoggingMixin): # # BUY / increase / decrease positions / DCA logic and methods # - def process_open_trade_positions(self) -> int: + def process_open_trade_positions(self): """ - Tries to execute additional buy orders for open trades (positions) + Tries to execute additional buy or sell orders for open trades (positions) """ - orders_created = 0 - - # Remove pairs for currently opened trades from the whitelist + # Walk through each pair and check if it needs changes for trade in Trade.get_open_trades(): try: - orders_created += self.adjust_trade_position(trade) + self.adjust_trade_position(trade) except DependencyException as exception: logger.warning('Unable to adjust position of trade for %s: %s', trade.pair, exception) - if not orders_created: - logger.debug("Found no trades to modify. Trying again...") - return orders_created - - - def adjust_trade_position(self, trade: Trade) -> int: + def adjust_trade_position(self, trade: Trade): """ Check the implemented trading strategy for adjustment command. - - If the pair triggers the adjustment a new buy-order gets issued towards the exchange. + If the strategy triggers the adjustment a new buy/sell-order gets issued. Once that completes, the existing trade is modified to match new data. - :return: True if a order has been created. """ logger.debug(f"adjust_trade_position for pair {trade.pair}") for order in trade.orders: if order.ft_is_open: - logger.debug(f"Order {order} is still open.") - return 0 - - analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, self.strategy.timeframe) + logger.debug(f"Order {order} is still open, skipping pair.") + return sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell") current_profit = trade.calc_profit_ratio(sell_rate) - amount_to_adjust = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=0.0)( + amount_to_adjust = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None)( pair=trade.pair, trade=trade, current_time=datetime.now(timezone.utc), current_rate=sell_rate, current_profit=current_profit) - if amount_to_adjust > 0.0: + if amount_to_adjust != None and amount_to_adjust > 0.0: # We should increase our position - is_order_success = self.execute_trade_position_change(trade.pair, amount_to_adjust, trade) - if is_order_success: - return 1 + self.execute_trade_position_change(trade.pair, amount_to_adjust, trade) - if amount_to_adjust < 0.0: + if amount_to_adjust != None and amount_to_adjust < 0.0: # We should decrease our position # TODO: Selling part of the trade not implemented yet. - return 0 + return - return 0 + return - def execute_trade_position_change(self, pair: str, amount: float, trade: Trade) -> bool: + def execute_trade_position_change(self, pair: str, amount: float, trade: Trade): """ - Executes a limit buy for the given pair - :param pair: pair for which we want to create a LIMIT_BUY - :param stake_amount: amount of stake-currency for the pair - :return: True if a buy order is created, false if it fails. + Executes a buy order for the given pair using specific amount + :param pair: pair for which we want to create a buy order + :param amount: amount of tradable pair to buy """ time_in_force = self.strategy.order_time_in_force['buy'] # Calculate price proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side="buy") - enter_limit_requested = self.get_valid_price(proposed_enter_rate, proposed_enter_rate) + custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, + default_retval=proposed_enter_rate)( + pair=pair, current_time=datetime.now(timezone.utc), + proposed_rate=proposed_enter_rate) + + enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate) + if not enter_limit_requested: raise PricingError('Could not determine buy price.') min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, enter_limit_requested, self.strategy.stoploss) - - stake_amount = amount * enter_limit_requested - - stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) - - logger.error(f'Executing DCA buy: amount={amount}, stake={stake_amount}') + stake_amount = self.wallets.validate_stake_amount(pair, (amount * enter_limit_requested), min_stake_amount) if not stake_amount: + logger.info(f'Additional order failed to get stake amount for pair {pair}, amount={amount}, price={enter_limit_requested}') return False + logger.debug(f'Executing additional order: amount={amount}, stake={stake_amount}, price={enter_limit_requested}') + amount = self.exchange.amount_to_precision(pair, amount) - order = self.exchange.create_order(pair=pair, ordertype='market', side="buy", + order = self.exchange.create_order(pair=pair, ordertype="market", side="buy", amount=amount, rate=enter_limit_requested, time_in_force=time_in_force) order_obj = Order.parse_from_ccxt_object(order, pair, 'buy') - order_id = order['id'] order_status = order.get('status', None) - # we assume the order is executed at the price requested - enter_limit_filled_price = enter_limit_requested - amount_requested = amount if order_status == 'expired' or order_status == 'rejected': order_tif = self.strategy.order_time_in_force['buy'] @@ -563,44 +549,19 @@ class FreqtradeBot(LoggingMixin): ) stake_amount = order['cost'] amount = safe_value_fallback(order, 'filled', 'amount') - enter_limit_filled_price = safe_value_fallback(order, 'average', 'price') # in case of FOK the order may be filled immediately and fully elif order_status == 'closed': stake_amount = order['cost'] amount = safe_value_fallback(order, 'filled', 'amount') - enter_limit_filled_price = safe_value_fallback(order, 'average', 'price') # Fee is applied only once because we make a LIMIT_BUY but the final trade will apply the sell fee. fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') - logger.info(f"Trade {pair} DCA order status {order_status}") - - total_amount = 0.0 - total_stake = 0.0 - for tempOrder in trade.orders: - if tempOrder.ft_is_open: - continue - if tempOrder.ft_order_side != 'buy': - # Skip not buy sides - continue - - if tempOrder.status == "closed": - tempOrderAmount = safe_value_fallback(order, 'filled', 'amount') - total_amount += tempOrderAmount - total_stake += tempOrder.average * tempOrderAmount - - total_amount += amount - total_stake += stake_amount - - trade.open_rate = total_stake / total_amount - - trade.fee_open += fee - trade.stake_amount = total_stake - trade.amount = total_amount + logger.info(f"Trade {pair} adjustment order status {order_status}") trade.orders.append(order_obj) - trade.recalc_open_trade_value() + trade.recalc_trade_from_orders() Trade.commit() # Updating wallets @@ -1459,6 +1420,7 @@ class FreqtradeBot(LoggingMixin): logger.warning("Could not update trade amount: %s", exception) trade.update(order) + trade.recalc_trade_from_orders() Trade.commit() # Updating wallets when order is closed diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 2fcdd58bb..2c0923078 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -568,6 +568,26 @@ class LocalTrade(): profit_ratio = (close_trade_value / self.open_trade_value) - 1 return float(f"{profit_ratio:.8f}") + + def recalc_trade_from_orders(self): + total_amount = 0.0 + total_stake = 0.0 + for temp_order in self.orders: + if temp_order.ft_is_open == False and temp_order.status == "closed" and temp_order.ft_order_side == 'buy': + tmp_amount = temp_order.amount + if temp_order.filled is not None: + tmp_amount = temp_order.filled + total_amount += tmp_amount + total_stake += temp_order.average * tmp_amount + + if total_amount > 0: + self.open_rate = total_stake / total_amount + self.stake_amount = total_stake + self.amount = total_amount + self.fee_open_cost = self.fee_open * self.stake_amount + self.recalc_open_trade_value() + + def select_order(self, order_side: str, is_open: Optional[bool]) -> Optional[Order]: """ Finds latest order for this orderside and status diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index e98cb5ff6..fead93190 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -383,8 +383,8 @@ class IStrategy(ABC, HyperStrategyMixin): def adjust_trade_position(self, pair: str, trade: Trade, - current_time: datetime, adjust_trade_position: float, - current_rate: float, current_profit: float, **kwargs) -> float: + current_time: datetime, current_rate: float, current_profit: float, + **kwargs) -> Optional[float]: """ Custom trade adjustment logic, returning the amount that a trade shold be either increased or decreased. @@ -400,7 +400,7 @@ class IStrategy(ABC, HyperStrategyMixin): :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: Amount to adjust your trade (buy more or sell some) """ - return 0.0 + return None def informative_pairs(self) -> ListPairsWithTimeframes: """