From 733f6efbffc7e196f569568b864cbe932247b0e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20S=C3=A1nchez?= Date: Fri, 19 Sep 2025 10:29:32 -0300 Subject: [PATCH] 2025.09.18 --- changelog.txt | 5 +++ exchange_wrapper.py | 6 ++-- main.py | 2 +- trader.py | 85 +++++++++++++++++++-------------------------- 4 files changed, 45 insertions(+), 53 deletions(-) diff --git a/changelog.txt b/changelog.txt index d5af89c..99cceef 100755 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +2025.09.18: +. do_cleanup now uses get_min_quote_size. +. Added an extra price check to switch_to_long. +. Removed old check_old_long_price method. + 2025.09.14: . Refactored full order list fetching. . Minor refactor of restart_pair_no_json. diff --git a/exchange_wrapper.py b/exchange_wrapper.py index 03acced..33aa8a5 100755 --- a/exchange_wrapper.py +++ b/exchange_wrapper.py @@ -458,7 +458,7 @@ class Broker: try: return orderbook["bids"][0][0] except Exception as e: - self.logger.log_this(f"Exception getting top mid price: {e}",1,symbol) + self.logger.log_this(f"Exception getting top bid price: {e}",1,symbol) return self.get_ticker_price(symbol) @@ -537,7 +537,7 @@ class Broker: pairs = [] try: if self.get_exchange_name() in ["binance","kucoin"]: - if "unified_order_query" in self.broker_config and self.broker_config["unified_order_query"] is True: + if self.broker_config.get("unified_order_query"): return self.exchange.fetch_open_orders() result = [] for pair in pairs: @@ -642,7 +642,7 @@ class Broker: ''' try: - if "unified_order_query" in self.broker_config and self.broker_config["unified_order_query"] is True: + if self.broker_config.get("unified_order_query"): return self.exchange.fetch_closed_orders() result = [] for pair in pairs: diff --git a/main.py b/main.py index a256814..67d047e 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,7 @@ import exchange_wrapper import trader -version = "2025.09.14" +version = "2025.09.18" ''' Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors diff --git a/trader.py b/trader.py index 460bec4..e9c989f 100755 --- a/trader.py +++ b/trader.py @@ -11,14 +11,16 @@ class trader: self.pause = True self.quit = False self.restart = False - self.warnings = { - "short_price_exceeds_old_long": False, - "speol_notified": False - } self.trader_restart_errors = {1: "start_trader returned error #1. Trader will be restarted", 2: "start_trader returned error #2: Initial order never got filled. Trader will be restarted", 3: "start_trader returned error #3: Slippage threshold exceeded. Trader will be restarted"} + #Status string caches + self.low_price_cache = None + self.mid_price_cache = None + self.high_price_cache = None + self.concurrent_so_amount_cache = None + self.broker = broker self.config = ConfigHandler(pair,broker) base_quote = self.config.get_pair() @@ -29,12 +31,6 @@ class trader: self.market_reload_period = 86400 #Market reload period in seconds self.status.set_start_time(int(time.time())) self.last_time_seen = time.time() - - #Status string caches - self.low_price_cache = None - self.mid_price_cache = None - self.high_price_cache = None - self.concurrent_so_amount_cache = None if self.config.get_is_short(): #Check if there is an old_long file. If so, load it. @@ -442,7 +438,7 @@ class trader: return 1 balance_to_clean /= 2 #Maybe it's a good idea, sort of DCAing the dust. - if balance_to_clean >= self.broker.get_min_base_size(self.status.get_pair()): + if balance_to_clean*self.status.get_start_price()>self.broker.get_min_quote_size(self.status.get_pair()): self.broker.logger.log_this(f"Balance to clean: {balance_to_clean} {self.base}",2,self.status.get_pair()) self.broker.logger.log_this("Sending cleanup order...",2,self.status.get_pair()) cleanup_order = self.broker.new_limit_order(self.status.get_pair(),balance_to_clean,"sell",self.status.get_take_profit_price(),no_retries=True) @@ -585,7 +581,7 @@ class trader: return 0 - def switch_to_long(self, ignore_old_long: bool = False, already_received_quote: float = 0) -> int: + def switch_to_long(self, ignore_old_long: bool = False, already_received_quote: float = 0, double_check_price: bool = True) -> int: ''' Takes a short trader and changes the mode to long. Only does it if the current trader was previously a long one. @@ -595,7 +591,14 @@ class trader: self.broker.logger.log_this("Trader already in long mode, nothing to do",1,self.status.get_pair()) return 1 self.broker.logger.log_this("Attempting to switch to long trader",0,self.status.get_pair()) - + + if double_check_price: + #Waits a moment to see if the price has moved too much + time.sleep(self.broker.get_wait_time()*4) + if not self.check_old_long(True): + self.broker.logger.log_this("False positive. Nothing to do.",1,self.status.get_pair()) + return 2 + #Check old_long data if not ignore_old_long and self.status.get_old_long()=={}: self.broker.logger.log_this("Can't find old long info on status_dict, searching for oldlong file",1,self.status.get_pair()) @@ -1018,28 +1021,6 @@ class trader: #Done return 0 - - def check_old_long_price(self) -> int: - ''' - Checks if short price exceeds old long price. If so, send a Telegram message - ''' - - price_exceeds = False - if self.status.get_old_long()!={}: - price_exceeds = self.status.get_price()>float(self.status.get_old_long()["tp_price"]) - if price_exceeds: - self.warnings["short_price_exceeds_old_long"] = True - else: - self.warnings["short_price_exceeds_old_long"] = False - self.warnings["speol_notified"] = False - if not self.warnings["speol_notified"] and price_exceeds: - #Only notify one time AND if autoswitch is off - self.warnings["speol_notified"] = True - if not self.config.get_autoswitch(): - message = f"{self.base}@{self.status.get_price()} ({str(self.broker.exchange)}), exceeds old long price of {self.status.get_old_long()['tp_price']}" - self.broker.logger.log_this(message,0,self.status.get_pair()) - return 0 - def check_orderbook_depth(self, threshold: float, order_size: float, old_price: float = 0, size_in_quote = True) -> bool: ''' @@ -1084,6 +1065,21 @@ class trader: return False + def check_old_long(self, fetch_price=False): + ''' + Check if it is profitable to switch to back to long. + Returns True if it is profitable, False otherwise. + + If selling the base currency left at the current market price plus the quote already received turns out to be more than the old long deal target, + it means that we already are in profit territory, switch back to long. + A more conservative approach would be old_target = self.status.get_old_long()["quote_spent"], just breaking even. + ''' + price = self.status.get_price() if not fetch_price else self.broker.get_top_bid_price(self.status.get_pair()) + old_target = self.status.get_old_long()["tp_price"]*self.status.get_old_long()["tp_amount"] + base_left = self.status.get_old_long()["tp_amount"]-self.status.get_base_bought() + return (base_left*price)+self.status.get_quote_spent()>=old_target + + def check_status(self,open_orders: list) -> int: #Should I change the order? Check the SO first? ''' Main routine. It checks for closed orders and proceeds accordingly. @@ -1094,25 +1090,16 @@ class trader: self.update_status(False) return 0 - #Check if short price exceeds old long price. If so, send a Telegram message - if self.config.get_is_short() and self.status.get_old_long()!={} and self.config.get_check_old_long_price(): - self.check_old_long_price() - self.status.set_pause_reason("check for autoswitch") #If it's a short trader that used to be long AND autoswitch is enabled if self.config.get_is_short() and self.config.get_autoswitch() and self.status.get_old_long()!={}: - #If selling the base currency left at the current market price plus the quote already received turns out to be more than the old long deal target, - # it means that we already are in profit territory, switch back to long. - #A more conservative approach would be old_target = self.status.get_old_long()["quote_spent"], just breaking even. - old_target = self.status.get_old_long()["tp_price"]*self.status.get_old_long()["tp_amount"] - base_left = self.status.get_old_long()["tp_amount"]-self.status.get_base_bought() - if (base_left*self.status.get_price())+self.status.get_quote_spent()>=old_target: + if self.check_old_long(): #Sell all base (market), report the profits and restart the trader self.status.set_pause_reason("automatic_switch") - self.switch_to_long(already_received_quote=self.status.get_quote_spent()) - if not self.config.get_liquidate_after_switch(): - self.restart = True - return 1 + if self.switch_to_long(already_received_quote=self.status.get_quote_spent())!=2: + if not self.config.get_liquidate_after_switch(): + self.restart = True + return 1 #Check for autoswitch (long->short) if not self.config.get_is_short() and self.status.get_so_amount()==self.status.get_no_of_safety_orders() and self.config.get_autoswitch(): self.switch_to_short()