diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c4991a31c..d5d918585 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -27,6 +27,7 @@ from freqtrade.state import State from freqtrade.strategy.interface import IStrategy, SellType from freqtrade.wallets import Wallets + logger = logging.getLogger(__name__) @@ -181,6 +182,43 @@ class FreqtradeBot: open_trades = len(Trade.get_open_trades()) return max(0, self.config['max_open_trades'] - open_trades) +# +# BUY / enter positions / open trades logic and methods +# + + def enter_positions(self) -> int: + """ + Tries to execute buy orders for new trades (positions) + """ + trades_created = 0 + + whitelist = copy.deepcopy(self.active_pair_whitelist) + if not whitelist: + logger.info("Active pair whitelist is empty.") + else: + # 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) + + if not whitelist: + logger.info("No currency pair in active pair whitelist, " + "but checking to sell open trades.") + else: + # Create entity and execute trade for each pair from whitelist + for pair in whitelist: + try: + trades_created += self.create_trade(pair) + except DependencyException as exception: + logger.warning('Unable to create trade for %s: %s', pair, exception) + + if not trades_created: + logger.debug("Found no buy signals for whitelisted currencies. " + "Trying again...") + + return trades_created + def get_target_bid(self, pair: str, tick: Dict = None) -> float: """ Calculates bid target between current ask price and last price @@ -369,7 +407,7 @@ class FreqtradeBot: if price: buy_limit_requested = price else: - # Calculate amount + # Calculate price buy_limit_requested = self.get_target_bid(pair) min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested) @@ -460,38 +498,9 @@ class FreqtradeBot: return True - def enter_positions(self) -> int: - """ - Tries to execute buy orders for new trades (positions) - """ - trades_created = 0 - - whitelist = copy.deepcopy(self.active_pair_whitelist) - if not whitelist: - logger.info("Active pair whitelist is empty.") - else: - # 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) - - if not whitelist: - logger.info("No currency pair in active pair whitelist, " - "but checking to sell open trades.") - else: - # Create entity and execute trade for each pair from whitelist - for pair in whitelist: - try: - trades_created += self.create_trade(pair) - except DependencyException as exception: - logger.warning('Unable to create trade for %s: %s', pair, exception) - - if not trades_created: - logger.debug("Found no buy signals for whitelisted currencies. " - "Trying again...") - - return trades_created +# +# SELL / exit positions / close trades logic and methods +# def exit_positions(self, trades: List[Any]) -> int: """ @@ -519,87 +528,6 @@ class FreqtradeBot: return trades_closed - def get_real_amount(self, trade: Trade, order: Dict, order_amount: float = None) -> float: - """ - Get real amount for the trade - Necessary for exchanges which charge fees in base currency (e.g. binance) - """ - if order_amount is None: - order_amount = order['amount'] - # Only run for closed orders - if trade.fee_open == 0 or order['status'] == 'open': - return order_amount - - # use fee from order-dict if possible - if ('fee' in order and order['fee'] is not None and - (order['fee'].keys() >= {'currency', 'cost'})): - if (order['fee']['currency'] is not None and - order['fee']['cost'] is not None and - trade.pair.startswith(order['fee']['currency'])): - new_amount = order_amount - order['fee']['cost'] - logger.info("Applying fee on amount for %s (from %s to %s) from Order", - trade, order['amount'], new_amount) - return new_amount - - # Fallback to Trades - trades = self.exchange.get_trades_for_order(trade.open_order_id, trade.pair, - trade.open_date) - - if len(trades) == 0: - logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade) - return order_amount - amount = 0 - fee_abs = 0 - for exectrade in trades: - amount += exectrade['amount'] - if ("fee" in exectrade and exectrade['fee'] is not None and - (exectrade['fee'].keys() >= {'currency', 'cost'})): - # only applies if fee is in quote currency! - if (exectrade['fee']['currency'] is not None and - exectrade['fee']['cost'] is not None and - trade.pair.startswith(exectrade['fee']['currency'])): - fee_abs += exectrade['fee']['cost'] - - if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC): - logger.warning(f"Amount {amount} does not match amount {trade.amount}") - raise DependencyException("Half bought? Amounts don't match") - real_amount = amount - fee_abs - if fee_abs != 0: - logger.info(f"Applying fee on amount for {trade} " - f"(from {order_amount} to {real_amount}) from Trades") - return real_amount - - def update_trade_state(self, trade, action_order: dict = None): - """ - Checks trades with open orders and updates the amount if necessary - """ - # Get order details for actual price per unit - if trade.open_order_id: - # Update trade with order values - logger.info('Found open order for %s', trade) - try: - order = action_order or self.exchange.get_order(trade.open_order_id, trade.pair) - except InvalidOrderException as exception: - logger.warning('Unable to fetch order %s: %s', trade.open_order_id, exception) - return - # Try update amount (binance-fix) - try: - new_amount = self.get_real_amount(trade, order) - if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC): - order['amount'] = new_amount - # Fee was applied, so set to 0 - trade.fee_open = 0 - trade.recalc_open_trade_price() - - except DependencyException as exception: - logger.warning("Could not update trade amount: %s", exception) - - trade.update(order) - - # Updating wallets when order is closed - if not trade.is_open: - self.wallets.update() - def get_sell_rate(self, pair: str, refresh: bool) -> float: """ Get sell rate - either using get-ticker bid or first bid based on orderbook @@ -1038,3 +966,88 @@ class FreqtradeBot: # Send the message self.rpc.send_msg(msg) + +# +# Common update trade state methods +# + + def update_trade_state(self, trade, action_order: dict = None): + """ + Checks trades with open orders and updates the amount if necessary + """ + # Get order details for actual price per unit + if trade.open_order_id: + # Update trade with order values + logger.info('Found open order for %s', trade) + try: + order = action_order or self.exchange.get_order(trade.open_order_id, trade.pair) + except InvalidOrderException as exception: + logger.warning('Unable to fetch order %s: %s', trade.open_order_id, exception) + return + # Try update amount (binance-fix) + try: + new_amount = self.get_real_amount(trade, order) + if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC): + order['amount'] = new_amount + # Fee was applied, so set to 0 + trade.fee_open = 0 + trade.recalc_open_trade_price() + + except DependencyException as exception: + logger.warning("Could not update trade amount: %s", exception) + + trade.update(order) + + # Updating wallets when order is closed + if not trade.is_open: + self.wallets.update() + + def get_real_amount(self, trade: Trade, order: Dict, order_amount: float = None) -> float: + """ + Get real amount for the trade + Necessary for exchanges which charge fees in base currency (e.g. binance) + """ + if order_amount is None: + order_amount = order['amount'] + # Only run for closed orders + if trade.fee_open == 0 or order['status'] == 'open': + return order_amount + + # use fee from order-dict if possible + if ('fee' in order and order['fee'] is not None and + (order['fee'].keys() >= {'currency', 'cost'})): + if (order['fee']['currency'] is not None and + order['fee']['cost'] is not None and + trade.pair.startswith(order['fee']['currency'])): + new_amount = order_amount - order['fee']['cost'] + logger.info("Applying fee on amount for %s (from %s to %s) from Order", + trade, order['amount'], new_amount) + return new_amount + + # Fallback to Trades + trades = self.exchange.get_trades_for_order(trade.open_order_id, trade.pair, + trade.open_date) + + if len(trades) == 0: + logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade) + return order_amount + amount = 0 + fee_abs = 0 + for exectrade in trades: + amount += exectrade['amount'] + if ("fee" in exectrade and exectrade['fee'] is not None and + (exectrade['fee'].keys() >= {'currency', 'cost'})): + # only applies if fee is in quote currency! + if (exectrade['fee']['currency'] is not None and + exectrade['fee']['cost'] is not None and + trade.pair.startswith(exectrade['fee']['currency'])): + fee_abs += exectrade['fee']['cost'] + + if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC): + logger.warning(f"Amount {amount} does not match amount {trade.amount}") + raise DependencyException("Half bought? Amounts don't match") + real_amount = amount - fee_abs + if fee_abs != 0: + logger.info(f"Applying fee on amount for {trade} " + f"(from {order_amount} to {real_amount}) from Trades") + return real_amount