From 17832025769ee84f29a41808aedd6c4fae1f867f Mon Sep 17 00:00:00 2001 From: esfem Date: Thu, 29 Oct 2020 20:39:35 -0400 Subject: [PATCH] Modified files for partial trades operation Signed-off-by: Es Fem --- freqtrade/freqtradebot.py | 241 +++++++++++++++++++++++++++++--- freqtrade/persistence/models.py | 90 +++++++++++- freqtrade/strategy/interface.py | 43 ++++-- freqtrade/wallets.py | 12 +- 4 files changed, 352 insertions(+), 34 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index eec09a17c..322b2d37e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -26,7 +26,7 @@ from freqtrade.persistence import Order, Trade from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.state import State -from freqtrade.strategy.interface import IStrategy, SellType +from freqtrade.strategy.interface import IStrategy, SellType, PartialTradeTuple from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.wallets import Wallets @@ -345,11 +345,11 @@ class FreqtradeBot: if not whitelist: logger.info("Active pair whitelist is empty.") else: - # Remove pairs for currently opened trades from the whitelist + '''# Remove pairs for currently opened trades from the whitelist for trade in Trade.get_open_trades(): if trade.pair in whitelist: whitelist.remove(trade.pair) - logger.debug('Ignoring %s in pair whitelist', trade.pair) + logger.debug('Ignoring %s in pair whitelist', trade.pair)''' if not whitelist: logger.info("No currency pair in active pair whitelist, " @@ -550,9 +550,9 @@ class FreqtradeBot: return False # running get_signal on historical data fetched - (buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df) + (buy, sell, partial_buy, partial_sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df) - if buy and not sell: + if buy or partial_buy.flag and not sell and not partial_sell.flag: stake_amount = self.get_trade_stake_amount(pair) if not stake_amount: logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.") @@ -565,13 +565,21 @@ class FreqtradeBot: if ((bid_check_dom.get('enabled', False)) and (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): if self._check_depth_of_market_buy(pair, bid_check_dom): - logger.info(f'Executing Buy for {pair}.') - return self.execute_buy(pair, stake_amount) + if partial_buy.flag: + logger.info(f'Executing Partial Buy for {pair}.') + return self.execute_partial_buy(pair, partial_buy.amount) + else: + logger.info(f'Executing Buy for {pair}.') + return self.execute_buy(pair, stake_amount) else: return False - logger.info(f'Executing Buy for {pair}') - return self.execute_buy(pair, stake_amount) + if partial_buy.flag: + logger.info(f'Executing Partial Buy for {pair}') + return self.execute_partial_buy(pair, partial_buy.amount) + else: + logger.info(f'Executing Buy for {pair}') + return self.execute_buy(pair, stake_amount) else: return False @@ -701,6 +709,122 @@ class FreqtradeBot: return True + def execute_partial_buy(self, pair: str, stake_amount: float, price: Optional[float] = None) -> bool: + """ + Executes a limit buy for the given pair + :param pair: pair for which we want to create a LIMIT_BUY + :return: True if a buy order is created, false if it fails. + """ + time_in_force = self.strategy.order_time_in_force['buy'] + + if price: + buy_limit_requested = price + else: + # Calculate price + buy_limit_requested = self.get_buy_rate(pair, True) + + # in case you try to buy more than available + #stake_amount = self._safe_buy_amount(pair, stake_amount) + min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested) + if min_stake_amount is not None and min_stake_amount > stake_amount: + logger.warning( + f"Can't open a new trade for {pair}: stake amount " + f"is too small ({stake_amount} < {min_stake_amount})" + ) + return False + + amount = stake_amount / buy_limit_requested + order_type = self.strategy.order_types['buy'] + + if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( + pair=pair, order_type=order_type, amount=amount, rate=buy_limit_requested, + time_in_force=time_in_force): + logger.info(f"User requested abortion of buying {pair}") + return False + + amount = self.exchange.amount_to_precision(pair, amount) + order = self.exchange.buy(pair=pair, ordertype=order_type, + amount=amount, rate=buy_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 + buy_limit_filled_price = buy_limit_requested + amount_requested = amount + + if order_status == 'expired' or order_status == 'rejected': + order_tif = self.strategy.order_time_in_force['buy'] + + # return false if the order is not filled + if float(order['filled']) == 0: + logger.warning('Buy %s order with time in force %s for %s is %s by %s.' + ' zero amount is fulfilled.', + order_tif, order_type, pair, order_status, self.exchange.name) + return False + else: + # the order is partially fulfilled + # in case of IOC orders we can check immediately + # if the order is fulfilled fully or partially + logger.warning('Buy %s order with time in force %s for %s is %s by %s.' + ' %s amount fulfilled out of %s (%s remaining which is canceled).', + order_tif, order_type, pair, order_status, self.exchange.name, + order['filled'], order['amount'], order['remaining'] + ) + stake_amount = order['cost'] + amount = safe_value_fallback(order, 'filled', 'amount') + buy_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') + buy_limit_filled_price = safe_value_fallback(order, 'average', 'price') + + # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL + fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') + order['fee'] = fee + + + for trade in Trade.get_open_trades(): + if trade.pair == pair: + break + if len(Trade.get_open_trades()) == 0: + trade = Trade( + pair=pair, + stake_amount=stake_amount, + amount=amount, + amount_requested=amount_requested, + fee_open=fee, + fee_close=fee, + open_rate=buy_limit_filled_price, + open_rate_requested=buy_limit_requested, + open_date=datetime.utcnow(), + exchange=_bot.exchange.id, + open_order_id=order_id, + strategy=_bot.strategy.get_strategy_name(), + timeframe=timeframe_to_minutes(_bot.config['timeframe']) + ) + trade.orders.append(order_obj) + # Update fees if order is closed + if order_status == 'closed': + self.update_trade_state(trade, order_id, order) + Trade.session.add(trade) + else: + trade.open_order_id = order_id + trade.orders.append(order_obj) + self.update_trade_state(trade, order_id, order) + + Trade.session.flush() + + # Updating wallets + self.wallets.update() + + self._notify_buy(trade, order_type) + + return True + def _notify_buy(self, trade: Trade, order_type: str) -> None: """ Sends rpc notification when a buy occured. @@ -831,7 +955,8 @@ class FreqtradeBot: logger.debug('Handling %s ...', trade) - (buy, sell) = (False, False) + (buy, sell, partial_buy, partial_sell) = (False, False, PartialTradeTuple(flag=False, amount=0), + PartialTradeTuple(flag=False, amount=0)) config_ask_strategy = self.config.get('ask_strategy', {}) @@ -840,7 +965,8 @@ class FreqtradeBot: analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, self.strategy.timeframe) - (buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.timeframe, analyzed_df) + (buy, sell, partial_buy, partial_sell) = self.strategy.get_signal(trade.pair, + self.strategy.timeframe, analyzed_df) if config_ask_strategy.get('use_order_book', False): order_book_min = config_ask_strategy.get('order_book_min', 1) @@ -865,13 +991,13 @@ class FreqtradeBot: # resulting in outdated RPC messages self._sell_rate_cache[trade.pair] = sell_rate - if self._check_and_execute_sell(trade, sell_rate, buy, sell): + if self._check_and_execute_sell(trade, sell_rate, buy, sell, partial_buy, partial_sell): return True else: logger.debug('checking sell') sell_rate = self.get_sell_rate(trade.pair, True) - if self._check_and_execute_sell(trade, sell_rate, buy, sell): + if self._check_and_execute_sell(trade, sell_rate, buy, sell, partial_buy, partial_sell): return True logger.debug('Found no sell signal for %s.', trade) @@ -1001,18 +1127,23 @@ class FreqtradeBot: f"for pair {trade.pair}.") def _check_and_execute_sell(self, trade: Trade, sell_rate: float, - buy: bool, sell: bool) -> bool: + buy: bool, sell: bool, partial_buy: PartialTradeTuple, + partial_sell: PartialTradeTuple) -> bool: """ Check and execute sell """ should_sell = self.strategy.should_sell( - trade, sell_rate, datetime.utcnow(), buy, sell, + trade, sell_rate, datetime.utcnow(), buy, sell, partial_buy, partial_sell, force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 ) if should_sell.sell_flag: - logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}') - self.execute_sell(trade, sell_rate, should_sell.sell_type) + if should_sell.sell_type == SellType.PARTIAL_SELL_SIGNAL: + logger.info(f'Executing Partial Sell for {trade.pair}. Reason: {should_sell.sell_type}') + self.execute_partial_sell(trade, partial_sell.amount, sell_rate, should_sell.sell_type) + else: + logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}') + self.execute_sell(trade, sell_rate, should_sell.sell_type) return True return False @@ -1269,6 +1400,78 @@ class FreqtradeBot: return True + def execute_partial_sell(self, trade: Trade, amount: float, limit: float, sell_reason: SellType) -> bool: + """ + Executes a limit sell for the given trade and limit + :param trade: Trade instance + :param limit: limit rate for the sell order + :param sellreason: Reason the sell was triggered + :return: True if it succeeds (supported) False (not supported) + :OSM metodo modificado para aceptar ventas parciales + """ + #TODO: Add a custom partial sell notification call + sell_type = 'sell' + + # First cancelling stoploss on exchange ... + if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id: + try: + self.exchange.cancel_stoploss_order(trade.stoploss_order_id, trade.pair) + except InvalidOrderException: + logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") + + order_type = self.strategy.order_types[sell_type] + if sell_reason == SellType.EMERGENCY_SELL: + # Emergency sells (default to market!) + order_type = self.strategy.order_types.get("emergencysell", "market") + + if amount > trade.amount: + amount = trade.amount + amount = self._safe_sell_amount(trade.pair, amount) + time_in_force = self.strategy.order_time_in_force['sell'] + + if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( + pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit, + time_in_force=time_in_force, + sell_reason=sell_reason.value): + logger.info(f"User requested abortion of selling {trade.pair}") + return False + + try: + # Execute sell and update trade record + order = self.exchange.sell(pair=trade.pair, + ordertype=order_type, + amount=amount, rate=limit, + time_in_force=time_in_force + ) + except InsufficientFundsError as e: + logger.warning(f"Unable to place order {e}.") + # Try to figure out what went wrong + self.handle_insufficient_funds(trade) + return False + + order_obj = Order.parse_from_ccxt_object(order, trade.pair, 'sell') + + trade.orders.append(order_obj) + + trade.open_order_id = order['id'] + trade.close_rate_requested = limit + trade.sell_reason = sell_reason.value + # In case of market sell orders the order can be closed immediately + if order.get('status', 'unknown') == 'closed': + self.update_trade_state(trade, trade.open_order_id, order) + # force trade to contine opened, if amount >= trade amount close trade + if amount < trade.amount: + trade.is_open = True + + Trade.session.flush() + + #Lock pair for one candle to prevent immediate rebuys + self.strategy.lock_pair(trade.pair, timeframe_to_next_date(self.config['timeframe'])) + + self._notify_sell(trade, order_type) + + return True + def _notify_sell(self, trade: Trade, order_type: str) -> None: """ Sends rpc notification when a sell occured. @@ -1389,7 +1592,7 @@ class FreqtradeBot: abs_tol=constants.MATH_CLOSE_PREC): order['amount'] = new_amount order.pop('filled', None) - trade.recalc_open_trade_price() + except DependencyException as exception: logger.warning("Could not update trade amount: %s", exception) @@ -1400,7 +1603,7 @@ class FreqtradeBot: trade.update(order) # Updating wallets when order is closed - if not trade.is_open: + if order['status'] == 'closed': self.wallets.update() return False diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 816e23fd3..a4ecefb0a 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -125,6 +125,8 @@ class Order(_DECL_BASE): filled = Column(Float, nullable=True) remaining = Column(Float, nullable=True) cost = Column(Float, nullable=True) + fee = Column(Float, nullable=True) # OSM + fee_cost = Column(Float, nullable=True) # OSM order_date = Column(DateTime, nullable=True, default=datetime.utcnow) order_filled_date = Column(DateTime, nullable=True) order_update_date = Column(DateTime, nullable=True) @@ -151,6 +153,7 @@ class Order(_DECL_BASE): self.filled = order.get('filled', self.filled) self.remaining = order.get('remaining', self.remaining) self.cost = order.get('cost', self.cost) + self.fee = order.get('fee', self.fee) if 'timestamp' in order and order['timestamp'] is not None: self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc) @@ -377,6 +380,10 @@ class Trade(_DECL_BASE): return logger.info('Updating trade (id=%s) ...', self.id) + # to be able to partially buy or sell + if order['amount'] < self.amount: + self.partial_update(order) + return if order_type in ('market', 'limit') and order['side'] == 'buy': # Update open rate and actual amount @@ -400,6 +407,44 @@ class Trade(_DECL_BASE): raise ValueError(f'Unknown order type: {order_type}') cleanup() + def partial_update(self, order: Dict) -> None: + """ + Updates this entity with amount and actual open/close rates, + modified to support multiple orders keeping the trade opened + :param order: order retrieved by exchange.fetch_order() + :return: None + + """ + order_type = order['type'] + + if order_type in ('market', 'limit') and order['side'] == 'buy': + # Update open rate and actual amount + self.open_rate = self.average_open_rate(order['filled'], + safe_value_fallback(order, 'average', 'price'), + self.amount, self.open_rate) + self.amount = Decimal(self.amount or 0) + Decimal(order['filled']) + self.decrease_wallet(self, Decimal(order['filled']), self.open_rate) + if self.is_open and order['filled'] != 0: + logger.info(f'{order_type.upper()}_Partial BUY has been fulfilled for {self}.') + self.open_order_id = None + + elif order_type in ('market', 'limit') and order['side'] == 'sell': + self.amount = (Decimal(self.amount or 0) - Decimal(order['filled'])) + if self.is_open and order['filled'] != 0: + logger.info(f'{order_type.upper()}_Partial SELL has been fulfilled for {self}.') + self.partial_close(self, safe_value_fallback(order, 'average', 'price')) + self.increase_wallet(self, Decimal(order['filled']), order['price']) + + elif order_type in ('stop_loss_limit', 'stop-loss', 'stop'): + self.stoploss_order_id = None + self.close_rate_requested = self.stop_loss + if self.is_open: + logger.info(f'{order_type.upper()} is hit for {self}.') + self.close(order['average']) + else: + raise ValueError(f'Unknown order type: {order_type}') + cleanup() + def close(self, rate: float) -> None: """ Sets close_rate to the given rate, calculates total profit @@ -417,6 +462,17 @@ class Trade(_DECL_BASE): self ) + def partial_close(self, rate: float) -> None: + """ modified close() to keep trade opened + """ + self.is_open = True + self.sell_order_status = 'closed' + self.open_order_id = None + logger.info( + 'Updated position %s,', + self + ) + def update_fee(self, fee_cost: float, fee_currency: Optional[str], fee_rate: Optional[float], side: str) -> None: """ @@ -494,6 +550,8 @@ class Trade(_DECL_BASE): fee: Optional[float] = None) -> float: """ Calculate the absolute profit in stake currency between Close and Open trade + Modified to stop using open_tride_price and use open_rate instead, + which be actualized if partial buys. :param fee: fee to use on the close rate (optional). If rate is not set self.fee will be used :param rate: close rate to compare with (optional). @@ -504,13 +562,15 @@ class Trade(_DECL_BASE): rate=(rate or self.close_rate), fee=(fee or self.fee_close) ) - profit = close_trade_price - self.open_trade_price + profit = close_trade_price - self.open_rate * self.amount return float(f"{profit:.8f}") def calc_profit_ratio(self, rate: Optional[float] = None, fee: Optional[float] = None) -> float: """ Calculates the profit as ratio (including fee). + Modified to stop using open_tride_price and use open_rate instead, + which be actualized if partial buys. :param rate: rate to compare with (optional). If rate is not set self.close_rate will be used :param fee: fee to use on the close rate (optional). @@ -520,7 +580,7 @@ class Trade(_DECL_BASE): rate=(rate or self.close_rate), fee=(fee or self.fee_close) ) - profit_ratio = (close_trade_price / self.open_trade_price) - 1 + profit_ratio = (close_trade_price / (self.open_rate * self.amount)) - 1 return float(f"{profit_ratio:.8f}") def select_order(self, order_side: str, is_open: Optional[bool]) -> Optional[Order]: @@ -538,6 +598,32 @@ class Trade(_DECL_BASE): else: return None + def average_open_rate(self, order_amount, order_price, trade_amount, trade_open_rate): + """ + Calculates average entry price when increase an open position with a partial buy + :param order_amount: Amount in base coin to buy + :param order_price: rate at the time of order buy + :trade_amount: Actual amount of the open position. (befor adding new order amount) + :trade_open_rate: Actual open price of position. + :return: New open rate modified with the order data. + """ + #((order['amount'] * order['price']) + (self.amount * trade.open_rate)) / (order['amount'] + trade.amount) + return ((order_amount * order_price) + (trade_amount * trade_open_rate)) / (order_amount + trade_amount) + + def increase_wallet(self, amount: float, rate: float) -> None: + + sell_trade = Decimal(amount) * Decimal(rate) + fees = sell_trade * Decimal(self.fee_close) + close_trade_price = float(sell_trade - fees) + self.stake_amount = Decimal(self.stake_amount or 0) + Decimal(close_trade_price) + + def decrease_wallet(self, amount: float, rate: float) -> None: + + buy_trade = Decimal(amount * rate) + fees = buy_trade * Decimal(self.fee_open) + open_trade_price = float(buy_trade + fees) + self.stake_amount = Decimal(self.stake_amount or 0) - Decimal(open_trade_price) + @staticmethod def get_trades(trade_filter=None) -> Query: """ diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 92d9f6c48..a1a4be2c3 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -30,6 +30,8 @@ class SignalType(Enum): """ BUY = "buy" SELL = "sell" + PARTIAL_BUY = "partial_buy" + PARTIAL_SELL = "partial_sell" class SellType(Enum): @@ -41,6 +43,7 @@ class SellType(Enum): STOPLOSS_ON_EXCHANGE = "stoploss_on_exchange" TRAILING_STOP_LOSS = "trailing_stop_loss" SELL_SIGNAL = "sell_signal" + PARTIAL_SELL_SIGNAL = "partial_sell_signal" FORCE_SELL = "force_sell" EMERGENCY_SELL = "emergency_sell" NONE = "" @@ -57,6 +60,13 @@ class SellCheckTuple(NamedTuple): sell_flag: bool sell_type: SellType +class PartialTradeTuple(NamedTuple): + """ + NamedTuple for partial trade + amount + """ + flag: bool + amount: float + class IStrategy(ABC): """ @@ -100,6 +110,8 @@ class IStrategy(ABC): 'stoploss': 'limit', 'stoploss_on_exchange': False, 'stoploss_on_exchange_interval': 60, + 'partial_buy': 'limit', + 'partial_sell': 'limit', } # Optional time in force @@ -423,18 +435,19 @@ class IStrategy(ABC): else: raise StrategyError(f"Dataframe returned from strategy has mismatching {message}.") - def get_signal(self, pair: str, timeframe: str, dataframe: DataFrame) -> Tuple[bool, bool]: + def get_signal(self, pair: str, timeframe: str, dataframe: DataFrame) \ + -> Tuple[bool, bool, PartialTradeTuple, PartialTradeTuple]: """ - Calculates current signal based based on the buy / sell columns of the dataframe. + Calculates current signal based based on the buy / sell / partial_buy / partial_sell columns of the dataframe. Used by Bot to get the signal to buy or sell :param pair: pair in format ANT/BTC :param timeframe: timeframe to use :param dataframe: Analyzed dataframe to get signal from. - :return: (Buy, Sell) A bool-tuple indicating buy/sell signal + :return: (Buy, Sell,partial_buy, partial_sell) A bool-tuple indicating buy/sell signal """ if not isinstance(dataframe, DataFrame) or dataframe.empty: logger.warning(f'Empty candle (OHLCV) data for pair {pair}') - return False, False + return False, False, PartialTradeTuple(False,0), PartialTradeTuple(False,0) latest_date = dataframe['date'].max() latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1] @@ -449,19 +462,22 @@ class IStrategy(ABC): 'Outdated history for pair %s. Last tick is %s minutes old', pair, int((arrow.utcnow() - latest_date).total_seconds() // 60) ) - return False, False + return False, False, PartialTradeTuple(False,0), PartialTradeTuple(False,0) - (buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1 - logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', - latest['date'], pair, str(buy), str(sell)) - return buy, sell + (buy, sell, partial_buy, partial_sell) = \ + latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1,\ + latest[SignalType.PARTIAL_BUY] == 1, latest[SignalType.PARTIAL_SELL == 1] + logger.debug('trigger: %s (pair=%s) buy=%s sell=%s partial_buy = %s partial_sell = %s', + latest['date'], pair, str(buy), str(sell), str(partial_buy), str(partial_sell)) + return buy, sell, partial_buy, partial_sell def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, - sell: bool, low: float = None, high: float = None, - force_stoploss: float = 0) -> SellCheckTuple: + sell: bool, partial_buy: PartialTradeTuple, partial_sell: PartialTradeTuple, + low: float = None, high: float = None, force_stoploss: float = 0) -> SellCheckTuple: """ This function evaluates if one of the conditions required to trigger a sell has been reached, which can either be a stop-loss, ROI or sell-signal. + Modified to support partial trades :param low: Only used during backtesting to simulate stoploss :param high: Only used during backtesting, to simulate ROI :param force_stoploss: Externally provided stoploss @@ -511,6 +527,11 @@ class IStrategy(ABC): f"sell_type=SellType.SELL_SIGNAL") return SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL) + if partial_sell.flag and not partial_buy.flag and not buy and config_ask_strategy.get('use_sell_signal', True): + logger.debug(f"{trade.pair} - Partial Sell signal received. partial_sell_flag=True, " + f"sell_type=SellType.PARTIAL_SELL_SIGNAL") + return SellCheckTuple(sell_flag=True, sell_type=SellType.PARTIAL_SELL_SIGNAL) + # This one is noisy, commented out... # logger.debug(f"{trade.pair} - No sell signal. sell_flag=False") return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index ac08f337c..799f82b4d 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -55,6 +55,7 @@ class Wallets: def _update_dry(self) -> None: """ Update from database in dry-run mode + - Modified to support partial trades considering trade.stake_amount as wallet value (Beta) - Apply apply profits of closed trades on top of stake amount - Subtract currently tied up stake_amount in open trades - update balances for currencies currently in trades @@ -64,9 +65,9 @@ class Wallets: closed_trades = Trade.get_trades(Trade.is_open.is_(False)).all() open_trades = Trade.get_trades(Trade.is_open.is_(True)).all() tot_profit = sum([trade.calc_profit() for trade in closed_trades]) - tot_in_trades = sum([trade.stake_amount for trade in open_trades]) - current_stake = self.start_cap + tot_profit - tot_in_trades + + current_stake = self.start_cap + tot_profit _wallets[self._config['stake_currency']] = Wallet( self._config['stake_currency'], current_stake, @@ -82,6 +83,13 @@ class Wallets: 0, trade.amount ) + current_stake += trade.stake_amount + _wallets[self._config['stake_currency']] = Wallet( + self._config['stake_currency'], + current_stake, + 0, + current_stake + ) self._wallets = _wallets def _update_live(self) -> None: