Compare commits

..

No commits in common. "main" and "2025.10.10" have entirely different histories.

6 changed files with 37 additions and 65 deletions

View File

@ -1,27 +1,3 @@
2025.12.01:
. Modified log output of new_market_order.
. Modified Kucoin's case in min_amount_of_base.
2025.11.11:
. deals_cache and log_list cache are now 20 items long.
. Less log spam.
2025.11.08:
. broker.set_default_order_size() now saves the config file to disk after changing the value.
. Variable renaming and other small stuff.
2025.10.24:
. Toggling liquidate_after_switch now writes the config file to disk so the setting persists between trades.
. Manually switching to long now sets double_check_price to false.
. Added a few comments to switch_to_long.
2025.10.12:
. do_cleanup relocated after generating the safety orders' prices.
2025.10.11:
. Minor simplification in do_cleanup.
. Removed a couple of (no longer needed?) pauses.
2025.10.10: 2025.10.10:
. New endpoint: /refresh_log_cache. . New endpoint: /refresh_log_cache.
. Fixed an error in /add_so endpoint that incremented the config setting but not the status setting. . Fixed an error in /add_so endpoint that incremented the config setting but not the status setting.

View File

@ -252,7 +252,6 @@ class ConfigHandler:
# self.broker.logger.log_this(f"liquidate_after_switch must be a boolean",1,self.get_pair()) # self.broker.logger.log_this(f"liquidate_after_switch must be a boolean",1,self.get_pair())
# return 1 # return 1
self.config_dictionary["liquidate_after_switch"] = liquidate_after_switch self.config_dictionary["liquidate_after_switch"] = liquidate_after_switch
self.save_to_file()
return 0 return 0
def set_tp_mode(self, tp_mode: int): def set_tp_mode(self, tp_mode: int):

View File

@ -51,7 +51,7 @@ class Broker:
self.markets = self.exchange.load_markets() self.markets = self.exchange.load_markets()
#Populates deals cache #Populates deals cache
self.deals_cache_length = 20 self.deals_cache_length = 10
self.deals_list = self.preload_deals(amount_to_preload=self.deals_cache_length) self.deals_list = self.preload_deals(amount_to_preload=self.deals_cache_length)
@ -224,7 +224,6 @@ class Broker:
def set_default_order_size(self,size): def set_default_order_size(self,size):
try: try:
self.broker_config["default_order_size"] = float(size) self.broker_config["default_order_size"] = float(size)
self.rewrite_config_file()
except Exception as e: except Exception as e:
self.logger.log_this(f"Exception in set_default_order_size: {e}",1) self.logger.log_this(f"Exception in set_default_order_size: {e}",1)
return 1 return 1
@ -789,7 +788,7 @@ class Broker:
return self.get_order(order_to_send["id"],symbol) return self.get_order(order_to_send["id"],symbol)
except Exception as e: except Exception as e:
self.logger.log_this(f"Exception in new_market_order: {e} - Side: {side} - Size: {size}",1,symbol) self.logger.log_this(f"Exception in new_market_order: {e}",1,symbol)
if no_retries: if no_retries:
break break
time.sleep(self.wait_time) time.sleep(self.wait_time)
@ -988,7 +987,7 @@ class Broker:
if self.get_exchange_name() in ["okex","bybit"]: if self.get_exchange_name() in ["okex","bybit"]:
return float(market["limits"]["amount"]["min"]) return float(market["limits"]["amount"]["min"])
elif self.get_exchange_name() in ["kucoin"]: elif self.get_exchange_name() in ["kucoin"]:
return max(float(market["limits"]["amount"]["min"]),(float(market["limits"]["cost"]["min"])+.25)/self.get_ticker_price(pair)) return (float(market["limits"]["cost"]["min"])+.25)/self.get_ticker_price(pair)
elif self.get_exchange_name() in ["gateio"]: elif self.get_exchange_name() in ["gateio"]:
return (float(market["limits"]["cost"]["min"])+.1)/self.get_ticker_price(pair) return (float(market["limits"]["cost"]["min"])+.1)/self.get_ticker_price(pair)
elif self.get_exchange_name()=="binance": elif self.get_exchange_name()=="binance":
@ -1052,7 +1051,7 @@ class Logger:
self.broker_config = broker_config self.broker_config = broker_config
self.exchange_name = self.broker_config["exchange"] self.exchange_name = self.broker_config["exchange"]
self.tg_credentials = credentials.get_credentials("telegram") self.tg_credentials = credentials.get_credentials("telegram")
self.log_list_max_length = 20 # log cache self.log_list_max_length = 10
self.log_list = collections.deque(maxlen=self.log_list_max_length) self.log_list = collections.deque(maxlen=self.log_list_max_length)
self.preload_logs() self.preload_logs()

View File

@ -18,7 +18,7 @@ import exchange_wrapper
import trader import trader
version = "2025.12.01" version = "2025.10.10"
''' '''
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
@ -1642,7 +1642,7 @@ def unwrapped_switch_to_long(base,quote,calculate_profits):
for instance in running_traders: for instance in running_traders:
if f"{base}/{quote}"==instance.status.get_pair(): if f"{base}/{quote}"==instance.status.get_pair():
instance.set_pause(True, "Switching to long mode") instance.set_pause(True, "Switching to long mode")
if instance.switch_to_long(ignore_old_long=ignore_old_long,double_check_price=False)==1: if instance.switch_to_long(ignore_old_long=ignore_old_long)==1:
return jsonify({"Error": "Error in switch_to_long()"}) return jsonify({"Error": "Error in switch_to_long()"})
if instance.start_trader()==1: if instance.start_trader()==1:
instance.quit = True instance.quit = True
@ -2280,7 +2280,6 @@ def unwrapped_toggle_autoswitch(base,quote):
broker.logger.log_this(f"Exception while toggling autoswitch: {e}",1,symbol) broker.logger.log_this(f"Exception while toggling autoswitch: {e}",1,symbol)
return jsonify({"Error": "Halp"}) return jsonify({"Error": "Halp"})
def unwrapped_toggle_liquidate_after_switch(base,quote): def unwrapped_toggle_liquidate_after_switch(base,quote):
''' '''
Signals a trader to enable or disable quitting after switching from short to long. Signals a trader to enable or disable quitting after switching from short to long.

View File

@ -42,8 +42,8 @@ class trader:
if self.config.get_is_short(): if self.config.get_is_short():
#Check if there is an old_long file. If so, load it. #Check if there is an old_long file. If so, load it.
try: try:
with open(f"status/{self.base}{self.quote}.oldlong") as old_long_file_handler: with open(f"status/{self.base}{self.quote}.oldlong") as ol:
self.status.set_old_long(load(old_long_file_handler)) self.status.set_old_long(load(ol))
except Exception as e: except Exception as e:
self.broker.logger.log_this(f"Exception: No old_long file. {e}",1,base_quote) self.broker.logger.log_this(f"Exception: No old_long file. {e}",1,base_quote)
@ -277,15 +277,15 @@ class trader:
self.broker.logger.log_this("Error sending take profit order. Aborting.",1,self.status.get_pair()) self.broker.logger.log_this("Error sending take profit order. Aborting.",1,self.status.get_pair())
return 1 return 1
# Generate the safety prices table
self.status.set_start_price(self.broker.price_to_precision(self.status.get_pair(),self.status.get_quote_spent()/self.status.get_base_bought()))
self.status.set_safety_price_table(self.calculate_safety_prices(self.status.get_start_price(),self.status.get_no_of_safety_orders(),self.config.get_safety_order_deviance()))
# Send cleanup order (if cleanup) # Send cleanup order (if cleanup)
self.status.set_pause_reason("start_trader - doing cleanup (if needed)") self.status.set_pause_reason("start_trader - doing cleanup (if needed)")
if self.config.get_cleanup() and not self.config.get_is_short(): #Short traders do not need cleanup. if self.config.get_cleanup() and not self.config.get_is_short(): #Short traders do not need cleanup.
self.do_cleanup() self.do_cleanup()
# Generate the safety prices table
self.status.set_start_price(self.broker.price_to_precision(self.status.get_pair(),self.status.get_quote_spent()/self.status.get_base_bought()))
self.status.set_safety_price_table(self.calculate_safety_prices(self.status.get_start_price(),self.status.get_no_of_safety_orders(),self.config.get_safety_order_deviance()))
# Send the initial batch of safety orders # Send the initial batch of safety orders
self.status.set_pause_reason("start_trader - sending safety orders") self.status.set_pause_reason("start_trader - sending safety orders")
self.broker.logger.log_this("Sending safety orders...",2,self.status.get_pair()) self.broker.logger.log_this("Sending safety orders...",2,self.status.get_pair())
@ -450,10 +450,14 @@ class trader:
self.broker.logger.log_this("Can't fetch free base",1,self.status.get_pair()) self.broker.logger.log_this("Can't fetch free base",1,self.status.get_pair())
return 1 return 1
if balance_in_account*self.status.get_start_price()>self.broker.get_min_quote_size(self.status.get_pair()): #If the balance is greater than the size of the first safety order, sell the difference.
self.broker.logger.log_this(f"Balance to clean: {balance_in_account} {self.base}",2,self.status.get_pair()) # Sometimes when an order is filled the balance is not updated immediately, so having a bit of a buffer irons out a couple of issues.
min_size = self.config.get_order_size()/self.status.get_take_profit_price()
if (balance_in_account-min_size)*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_in_account-min_size} {self.base}",2,self.status.get_pair())
self.broker.logger.log_this("Sending cleanup order...",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_in_account,"sell",self.status.get_take_profit_price(),no_retries=True,log="cleanup") cleanup_order = self.broker.new_limit_order(self.status.get_pair(),balance_in_account-min_size,"sell",self.status.get_take_profit_price(),no_retries=True,log="cleanup")
if cleanup_order is None: if cleanup_order is None:
self.broker.logger.log_this("Problems with the cleanup order, new_limit_order returned None",1,self.status.get_pair()) self.broker.logger.log_this("Problems with the cleanup order, new_limit_order returned None",1,self.status.get_pair())
return 1 return 1
@ -572,8 +576,8 @@ class trader:
"datetime": time.strftime("[%Y/%m/%d %H:%M:%S]") "datetime": time.strftime("[%Y/%m/%d %H:%M:%S]")
}) })
try: try:
with open(f"status/{self.base}{self.quote}.oldlong","w") as old_long_file_handler: with open(f"status/{self.base}{self.quote}.oldlong","w") as s:
old_long_file_handler.write(dumps(self.status.get_old_long(),indent=4)) s.write(dumps(self.status.get_old_long(),indent=4))
except Exception as e: except Exception as e:
self.broker.logger.log_this(f"Exception while saving old_long file: {e}",1,self.status.get_pair()) self.broker.logger.log_this(f"Exception while saving old_long file: {e}",1,self.status.get_pair())
@ -606,19 +610,17 @@ class trader:
if double_check_price: if double_check_price:
#Waits a moment to see if the price has moved too much #Waits a moment to see if the price has moved too much
self.broker.logger.log_this("Confirming price...",2,self.status.get_pair())
time.sleep(self.broker.get_wait_time()*4) time.sleep(self.broker.get_wait_time()*4)
if not self.check_old_long(True): if not self.check_old_long(True):
self.broker.logger.log_this("False positive. Nothing to do.",1,self.status.get_pair()) self.broker.logger.log_this("False positive. Nothing to do.",1,self.status.get_pair())
return 2 return 2
#Check old_long data #Check old_long data
self.broker.logger.log_this("Checking if old long data is valid.",2,self.status.get_pair())
if not ignore_old_long and self.status.get_old_long()=={}: 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()) self.broker.logger.log_this("Can't find old long info on status_dict, searching for oldlong file",1,self.status.get_pair())
try: try:
with open(f"status/{self.base}{self.quote}.oldlong") as old_long_file_handler: with open(f"status/{self.base}{self.quote}.oldlong") as f:
self.status.set_old_long(load(old_long_file_handler)) self.status.set_old_long(load(f))
except Exception as e: except Exception as e:
#self.write_to_log(time.strftime(f"[%Y/%m/%d %H:%M:%S] | {self.status.get_pair()} | Can't find old long file")) #self.write_to_log(time.strftime(f"[%Y/%m/%d %H:%M:%S] | {self.status.get_pair()} | Can't find old long file"))
self.broker.logger.log_this(f"Can't file oldlong file. Exception: {e}",1,self.status.get_pair()) self.broker.logger.log_this(f"Can't file oldlong file. Exception: {e}",1,self.status.get_pair())
@ -626,7 +628,6 @@ class trader:
return 1 return 1
#Cancel open orders #Cancel open orders
self.broker.logger.log_this("Cancelling open orders",2,self.status.get_pair())
for order in self.status.get_safety_orders(): for order in self.status.get_safety_orders():
self.broker.cancel_order(order["id"],self.status.get_pair()) self.broker.cancel_order(order["id"],self.status.get_pair())
if self.status.get_take_profit_order() is not None: if self.status.get_take_profit_order() is not None:
@ -635,21 +636,18 @@ class trader:
self.broker.logger.log_this("Safety order is None",1,self.status.get_pair()) self.broker.logger.log_this("Safety order is None",1,self.status.get_pair())
#Sell all base currency #Sell all base currency
self.broker.logger.log_this(f"Selling {self.status.get_pair().split('/')[0]}",2,self.status.get_pair())
self.liquidate_base(ignore_profits=ignore_old_long, already_received_quote=already_received_quote) self.liquidate_base(ignore_profits=ignore_old_long, already_received_quote=already_received_quote)
if self.config.get_liquidate_after_switch(): if self.config.get_liquidate_after_switch():
self.broker.logger.log_this("Liquidate after switch active. Raising quit flag.",1,self.status.get_pair())
self.quit = True self.quit = True
return 0 return 1
#Rewrite config file (if it exists) #Rewrite config file (if it exists)
if path.isfile(f"configs/{self.base}{self.quote}.bak") and path.isfile(f"configs/{self.base}{self.quote}.json"): if path.isfile(f"configs/{self.base}{self.quote}.bak") and path.isfile(f"configs/{self.base}{self.quote}.json"):
self.broker.logger.log_this("Restoring config file from backup",2,self.status.get_pair()) with open(f"configs/{self.base}{self.quote}.bak") as c:
with open(f"configs/{self.base}{self.quote}.bak") as backup_config_file_handler: old_config = load(c)
old_config = load(backup_config_file_handler) with open(f"configs/{self.base}{self.quote}.json","w") as c:
with open(f"configs/{self.base}{self.quote}.json","w") as config_file_handler: c.write(dumps(old_config, indent=4))
config_file_handler.write(dumps(old_config, indent=4))
if self.config.load_from_file()==1: if self.config.load_from_file()==1:
self.config.reset_to_default() self.config.reset_to_default()
else: else:
@ -671,7 +669,6 @@ class trader:
self.status.set_so_amount(0) self.status.set_so_amount(0)
#Done. Ready for start_trader #Done. Ready for start_trader
self.broker.logger.log_this("Finished setting up the switch to long.",2,self.status.get_pair())
return 0 return 0
@ -808,11 +805,11 @@ class trader:
partial_profit = market_order["cost"]-(avg_buy_price*partial_filled_amount)-self.parse_fees(market_order)[1] partial_profit = market_order["cost"]-(avg_buy_price*partial_filled_amount)-self.parse_fees(market_order)[1]
self.status.set_partial_profit(self.status.get_partial_profit()+partial_profit) self.status.set_partial_profit(self.status.get_partial_profit()+partial_profit)
break break
self.broker.logger.log_this("Waiting for partial fill sell order to fill.",2,self.status.get_pair()) self.broker.logger.log_this("Waiting for partial fill sell order to fill.",1,self.status.get_pair())
tries-=1 tries-=1
time.sleep(self.broker.get_wait_time()) time.sleep(self.broker.get_wait_time())
if tries==0: if tries==0:
self.broker.logger.log_this("Partial fill sell order not filled.",1,self.status.get_pair()) self.broker.logger.log_this("Partial fill sell order not filling.",1,self.status.get_pair())
break break
if not self.broker.check_for_duplicate_profit_in_db(filled_order): if not self.broker.check_for_duplicate_profit_in_db(filled_order):
@ -1021,7 +1018,7 @@ class trader:
self.status.set_fees_paid_in_base(self.status.get_fees_paid_in_base() + self.parse_fees(old_tp_order)[0]) self.status.set_fees_paid_in_base(self.status.get_fees_paid_in_base() + self.parse_fees(old_tp_order)[0])
#Cooldown #Cooldown
#time.sleep(self.broker.get_wait_before_new_safety_order()) time.sleep(self.broker.get_wait_before_new_safety_order())
#Send new TP order #Send new TP order
if self.send_new_tp_order()==1: if self.send_new_tp_order()==1:
@ -1031,7 +1028,7 @@ class trader:
return 4 return 4
#Cooldown #Cooldown
#time.sleep(self.broker.get_wait_before_new_safety_order()) time.sleep(self.broker.get_wait_before_new_safety_order())
#Send new safety order(s) #Send new safety order(s)
#Do not send new orders if the max amount is reached or surpassed. #Do not send new orders if the max amount is reached or surpassed.
@ -1545,8 +1542,8 @@ class trader:
#If there is an old_long file, also copy it #If there is an old_long file, also copy it
if self.config.get_is_short() and self.status.get_old_long()!={}: if self.config.get_is_short() and self.status.get_old_long()!={}:
try: try:
with open(f"status/{self.base}{self.quote}.oldlong","w") as old_long_file_handler: with open(f"status/{self.base}{self.quote}.oldlong","w") as c:
old_long_file_handler.write(dumps(self.status.get_old_long(), indent=4)) c.write(dumps(self.status.get_old_long(), indent=4))
except Exception as e: except Exception as e:
self.broker.logger.log_this(f"Exception while writing new old_long file: {e}",1,self.status.get_pair()) self.broker.logger.log_this(f"Exception while writing new old_long file: {e}",1,self.status.get_pair())

View File

@ -5,8 +5,10 @@ import calendar
import logging import logging
import threading import threading
import os import os
from collections import deque
from typing import Iterable, List, Tuple
from contextlib import contextmanager from contextlib import contextmanager
from flask import Flask, jsonify, request from flask import Flask, jsonify, request, Response
from waitress import serve from waitress import serve