ConfigHandler almost ready (without parameter validation)

This commit is contained in:
Nicolás Sánchez 2025-02-25 20:24:50 -03:00
parent 487115c15e
commit 72c8773263
4 changed files with 155 additions and 93 deletions

View File

@ -1,9 +1,12 @@
import json
class ConfigHandler: class ConfigHandler:
''' '''
Handles the configuration of the trader and the validation of the parameters Handles the configuration of the trader and the validation of the parameters
''' '''
def __init__(self, pair, config_dict = None): def __init__(self, pair, broker, config_dict = None):
self.broker = broker
self.default_config_dictionary = { self.default_config_dictionary = {
"pair": pair, "pair": pair,
"is_short": False, "is_short": False,
@ -19,6 +22,7 @@ class ConfigHandler:
"autoswitch": False, "autoswitch": False,
"attempt_restart": True, "attempt_restart": True,
"tp_mode": 3, "tp_mode": 3,
"tp_level": 1.02,
"tp_table": [], "tp_table": [],
"check_slippage": True, "check_slippage": True,
"programmed_stop": False, "programmed_stop": False,
@ -38,9 +42,6 @@ class ConfigHandler:
def get_config(self):
return self.config_dictionary
def get_pair(self): def get_pair(self):
return self.config_dictionary["pair"] return self.config_dictionary["pair"]
@ -83,6 +84,9 @@ class ConfigHandler:
def get_tp_mode(self): def get_tp_mode(self):
return self.config_dictionary["tp_mode"] return self.config_dictionary["tp_mode"]
def get_tp_level(self):
return self.config_dictionary["tp_level"]
def get_tp_table(self): def get_tp_table(self):
return self.config_dictionary["tp_table"] return self.config_dictionary["tp_table"]
@ -166,6 +170,10 @@ class ConfigHandler:
self.config_dictionary["tp_mode"] = tp_mode self.config_dictionary["tp_mode"] = tp_mode
return 0 return 0
def set_tp_level(self, tp_level: float):
self.config_dictionary["tp_level"] = tp_level
return 0
def set_tp_table(self, tp_table: list): def set_tp_table(self, tp_table: list):
self.config_dictionary["tp_table"] = tp_table self.config_dictionary["tp_table"] = tp_table
return 0 return 0
@ -203,3 +211,32 @@ class ConfigHandler:
def set_check_old_long_price(self, check_old_long_price: bool): def set_check_old_long_price(self, check_old_long_price: bool):
self.config_dictionary["check_old_long_price"] = check_old_long_price self.config_dictionary["check_old_long_price"] = check_old_long_price
return 0 return 0
def save_to_file(self, file_path: str):
try:
with open(file_path, "w") as f:
json.dump(self.config_dictionary, f)
return 0
except Exception as e:
self.broker.logger.log_this(f"Error saving config to file: {file_path}: {e}",1,self.get_pair())
return 1
def load_from_file(self, file_path: str):
try:
with open(file_path, "r") as f:
self.set_config(json.load(f))
return 0
except Exception as e:
self.broker.logger.log_this(f"Error loading config to file: {file_path}: {e}",1,self.get_pair())
return 1
def get_config(self):
return self.config_dictionary
def set_config(self, config_dictionary: dict):
'''
Validates every key in the config dictionary and sets the config dictionary
'''
self.config_dictionary = config_dictionary
return 0

View File

@ -25,6 +25,7 @@ class Broker:
self.logger = Logger(self.read_config) self.logger = Logger(self.read_config)
self.write_order_history = True #This should be a toggle in config_file self.write_order_history = True #This should be a toggle in config_file
#Initialize database
self.profits_database_filename = "profits/profits_database.db" self.profits_database_filename = "profits/profits_database.db"
self.database_connection = sqlite3.connect(self.profits_database_filename) self.database_connection = sqlite3.connect(self.profits_database_filename)
self.database_cursor = self.database_connection.cursor() self.database_cursor = self.database_connection.cursor()
@ -41,6 +42,7 @@ class Broker:
self.database_connection.commit() self.database_connection.commit()
self.database_connection.close() self.database_connection.close()
#Load markets
self.exchange.load_markets() self.exchange.load_markets()
#Populates deals cache #Populates deals cache

View File

@ -1,9 +1,12 @@
import json
class StatusHandler: class StatusHandler:
''' '''
Handles the status of the trader and the validation of the parameters Handles the status of the trader and the validation of the parameters
''' '''
def __init__(self, status_dict = None): def __init__(self, broker, status_dict = None):
self.broker = broker
self.default_status_dictionary = { self.default_status_dictionary = {
"tp_order_id": "", "tp_order_id": "",
"take_profit_order": {}, "take_profit_order": {},
@ -274,10 +277,38 @@ class StatusHandler:
self.status_dictionary["deal_order_history"] = deal_history self.status_dictionary["deal_order_history"] = deal_history
return 0 return 0
def update_deal_order_history(self, new_deal: dict): def update_deal_order_history(self, new_deal: dict):
self.status_dictionary["deal_order_history"].append(new_deal) self.status_dictionary["deal_order_history"].append(new_deal)
return 0 return 0
def save_to_file(self, file_path: str):
try:
with open(file_path, "w") as f:
json.dump(self.status_dictionary, f)
return 0
except Exception as e:
self.broker.logger.log_this(f"Error saving status to file: {file_path}: {e}",1)
return 1
def load_from_file(self, file_path: str):
try:
with open(file_path, "r") as f:
self.set_status(json.load(f))
return 0
except Exception as e:
self.broker.logger.log_this(f"Error loading status from file: {file_path}: {e}",1)
return 1
def get_status(self): def get_status(self):
return self.status_dictionary return self.status_dictionary
def set_status(self, dictionary: dict):
'''
Validates every key in the dictionary and then sets the status dictionary
'''
self.status_dictionary = dictionary
return 0

164
trader.py
View File

@ -2,6 +2,8 @@ import csv
import json import json
import time import time
import os import os
from config_handler import ConfigHandler
from status_handler import StatusHandler
class trader: class trader:
def __init__(self, broker, config_dict: dict, is_import: bool = False): def __init__(self, broker, config_dict: dict, is_import: bool = False):
@ -12,20 +14,16 @@ class trader:
self.broker = broker self.broker = broker
self.tp_order = self.broker.get_empty_order() self.tp_order = self.broker.get_empty_order()
self.so = self.broker.get_empty_order() self.so = self.broker.get_empty_order()
self.config_dict = config_dict self.config = ConfigHandler(config_dict["pair"],broker,config_dict)
self.pair = self.config_dict["pair"] self.pair = self.config.get_pair()
self.market = self.broker.fetch_market(self.pair) self.market = self.broker.fetch_market(self.pair)
self.market_load_time = int(time.time()) self.market_load_time = int(time.time())
self.market_reload_period = 86400 #Market reload period in seconds self.market_reload_period = 86400 #Market reload period in seconds
self.base,self.quote = self.pair.split("/") self.base,self.quote = self.pair.split("/")
self.is_short = self.config_dict["is_short"] self.is_short = self.config.get_is_short()
self.profit_table = self.config_dict["tp_table"] self.profit_table = self.config.get_tp_table()
self.max_short_safety_orders = 45 self.max_short_safety_orders = self.config.get_max_short_safety_orders()
if "max_short_safety_orders" in config_dict: self.check_slippage = self.config.get_check_slippage()
self.max_short_safety_orders = config_dict["max_short_safety_orders"]
self.check_slippage = True
if "check_slippage" in self.config_dict:
self.check_slippage = self.config_dict["check_slippage"]
self.is_boosted = False self.is_boosted = False
self.start_time = int(time.time()) self.start_time = int(time.time())
self.total_amount_of_quote=0 self.total_amount_of_quote=0
@ -42,10 +40,8 @@ class trader:
"quote_spent": 0, "quote_spent": 0,
"base_bought": 0, "base_bought": 0,
"so_amount": 0, "so_amount": 0,
#"max_so_amount": config_dict["no_of_safety_orders"],
"take_profit_price": 1, "take_profit_price": 1,
"next_so_price": 1, "next_so_price": 1,
#"acc_profit": 0,
"tp_order_id": "", "tp_order_id": "",
"take_profit_order": {}, "take_profit_order": {},
"so_order_id": "", "so_order_id": "",
@ -61,10 +57,12 @@ class trader:
"short_price_exceeds_old_long": False, "short_price_exceeds_old_long": False,
"speol_notified": False "speol_notified": False
} }
if "stop_time" in self.config_dict and int(self.config_dict["stop_time"])<int(time.time()): #What is this? Why?
if self.config.get_programmed_stop() and int(self.config.get_programmed_stop_time())<int(time.time()):
self.config_dict.pop("stop_time",None) self.config_dict.pop("stop_time",None)
#Write config file changes #Write config file changes
self.broker.rewrite_config_file() self.broker.rewrite_config_file()
if self.is_short: if self.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:
@ -75,15 +73,10 @@ class trader:
self.profit_filename = f"profits/{self.base}{self.quote}.profits" self.profit_filename = f"profits/{self.base}{self.quote}.profits"
self.log_filename = f"logs/{self.base}{self.quote}.log" self.log_filename = f"logs/{self.base}{self.quote}.log"
self.boosted_deals_range = 4 self.boosted_deals_range = self.config.get_boosted_deals_range()
self.boosted_time_range = 3600 self.boosted_time_range = self.config.get_boosted_time_range()
self.boosted_amount = .01 self.boosted_amount = self.config.get_boosted_amount()
if "boosted_deals_range" in self.config_dict:
self.boosted_deals_range = self.config_dict["boosted_deals_range"]
if "boosted_time_range" in self.config_dict:
self.boosted_time_range = self.config_dict["boosted_time_range"]
if "boosted_amount" in self.config_dict:
self.boosted_amount = self.config_dict["boosted_amount"]
self.deals_timestamps = self.broker.get_trades_timestamps(self.pair,self.boosted_time_range) self.deals_timestamps = self.broker.get_trades_timestamps(self.pair,self.boosted_time_range)
self.stop_when_profit = False self.stop_when_profit = False
@ -101,7 +94,7 @@ class trader:
elif start_result==1: #If initialization fails elif start_result==1: #If initialization fails
self.quit = True self.quit = True
elif start_result==2: #Retries exceeded elif start_result==2: #Retries exceeded
if "force_restart_if_retries_exhausted" in self.config_dict and self.config_dict["force_restart_if_retries_exhausted"]: if self.config.get_force_restart_if_retries_exhausted():
self.pause = False self.pause = False
self.restart = True self.restart = True
else: else:
@ -194,8 +187,8 @@ class trader:
if order_size is None or no_of_safety_orders is None: if order_size is None or no_of_safety_orders is None:
self.broker.logger.log_this("Can't calculate optimal size",1,self.pair) self.broker.logger.log_this("Can't calculate optimal size",1,self.pair)
return 1 return 1
self.config_dict["order_size"] = order_size self.config.set_order_size(order_size)
self.config_dict["no_of_safety_orders"] = no_of_safety_orders self.config.set_no_of_safety_orders(no_of_safety_orders)
self.broker.logger.log_this(f"Order size: {self.broker.amount_to_precision(self.pair,order_size)}. Amount of safety orders: {no_of_safety_orders}",2,self.pair) self.broker.logger.log_this(f"Order size: {self.broker.amount_to_precision(self.pair,order_size)}. Amount of safety orders: {no_of_safety_orders}",2,self.pair)
#Write the changes to the config file #Write the changes to the config file
@ -207,12 +200,12 @@ class trader:
self.status_dict["pause_reason"] = "start_trader - checking order size" self.status_dict["pause_reason"] = "start_trader - checking order size"
self.broker.logger.log_this("Checking for order size",2,self.pair) self.broker.logger.log_this("Checking for order size",2,self.pair)
minimum_order_size_allowed = self.broker.get_min_quote_size(self.pair) minimum_order_size_allowed = self.broker.get_min_quote_size(self.pair)
if minimum_order_size_allowed is not None and minimum_order_size_allowed>self.config_dict["order_size"]: if minimum_order_size_allowed is not None and minimum_order_size_allowed>self.config.get_order_size():
self.broker.logger.log_this(f"Order size too small. Minimum order size is {minimum_order_size_allowed} {self.quote}",1,self.pair) self.broker.logger.log_this(f"Order size too small. Minimum order size is {minimum_order_size_allowed} {self.quote}",1,self.pair)
if minimum_order_size_allowed<self.config_dict["order_size"]*2: if minimum_order_size_allowed<self.config.get_order_size()*2:
#int(n)+1 is treated here as a simplified ceil function, since minimum_order_size_allowed will always be positive. #int(n)+1 is treated here as a simplified ceil function, since minimum_order_size_allowed will always be positive.
self.broker.logger.log_this(f"Due to exchange limits, trader initial order size will be {float(int(minimum_order_size_allowed)+1)} {self.quote}",1,self.pair) self.broker.logger.log_this(f"Due to exchange limits, trader initial order size will be {float(int(minimum_order_size_allowed)+1)} {self.quote}",1,self.pair)
self.config_dict["order_size"] = float(int(minimum_order_size_allowed)+1) self.config.set_order_size(float(int(minimum_order_size_allowed)+1))
else: else:
self.broker.logger.log_this("Limit difference is more than 2x the configured order size. Please adjust the order size in the trader config file and restart the trader.",1,self.pair) self.broker.logger.log_this("Limit difference is more than 2x the configured order size. Please adjust the order size in the trader config file and restart the trader.",1,self.pair)
return 1 return 1
@ -221,19 +214,19 @@ class trader:
if self.check_slippage: if self.check_slippage:
self.broker.logger.log_this("Checking slippage...",2,self.pair) self.broker.logger.log_this("Checking slippage...",2,self.pair)
self.status_dict["pause_reason"] = "start_trader - checking slippage" self.status_dict["pause_reason"] = "start_trader - checking slippage"
if self.check_orderbook_depth(self.broker.get_slippage_default_threshold(),self.config_dict["order_size"]): if self.check_orderbook_depth(self.broker.get_slippage_default_threshold(),self.config.get_order_size()):
#Slippage threshold exceeded #Slippage threshold exceeded
self.broker.logger.log_this("Slippage threshold exceeded",1,self.pair) self.broker.logger.log_this("Slippage threshold exceeded",1,self.pair)
return 3 return 3
self.status_dict["pause_reason"] = "start_trader - after slippage" self.status_dict["pause_reason"] = "start_trader - after slippage"
self.status_dict["order_size"] = self.config_dict["order_size"] self.status_dict["order_size"] = self.config.get_order_size()
#Sending initial order #Sending initial order
self.status_dict["pause_reason"] = "start_trader - sending first order" self.status_dict["pause_reason"] = "start_trader - sending first order"
self.broker.logger.log_this("Sending first order...",2,self.pair) self.broker.logger.log_this("Sending first order...",2,self.pair)
action = "sell" if self.is_short else "buy" action = "sell" if self.is_short else "buy"
first_order = self.broker.new_market_order(self.pair,self.config_dict["order_size"],action) first_order = self.broker.new_market_order(self.pair,self.config.get_order_size(),action)
#self.broker.logger.log_this(f"First order id: {first_order}",1,self.pair) #self.broker.logger.log_this(f"First order id: {first_order}",1,self.pair)
if first_order in [None,self.broker.get_empty_order()]: if first_order in [None,self.broker.get_empty_order()]:
self.broker.logger.log_this(f"Error sending the first order. Market order returned {first_order}",1,self.pair) self.broker.logger.log_this(f"Error sending the first order. Market order returned {first_order}",1,self.pair)
@ -300,9 +293,8 @@ class trader:
return 1 return 1
# Generate the safety prices table # Generate the safety prices table
#self.start_price = returned_order["average"]
self.start_price = self.broker.price_to_precision(self.pair,self.total_amount_of_quote/self.total_amount_of_base) self.start_price = self.broker.price_to_precision(self.pair,self.total_amount_of_quote/self.total_amount_of_base)
self.safety_price_table = self.calculate_safety_prices(self.start_price,self.config_dict["no_of_safety_orders"],self.config_dict["safety_order_deviance"]) self.safety_price_table = self.calculate_safety_prices(self.start_price,self.config.get_no_of_safety_orders(),self.config.get_safety_order_deviance())
# Send the first safety order # Send the first safety order
self.status_dict["pause_reason"] = "start_trader - sending safety order" self.status_dict["pause_reason"] = "start_trader - sending safety order"
@ -316,7 +308,7 @@ class trader:
# Send cleanup order (if cleanup) # Send cleanup order (if cleanup)
self.status_dict["pause_reason"] = "start_trader - doing cleanup (if needed)" self.status_dict["pause_reason"] = "start_trader - doing cleanup (if needed)"
if self.config_dict["cleanup"] and not self.is_short: #Short traders do not need cleanup. if self.config.get_cleanup() and not self.is_short: #Short traders do not need cleanup.
self.do_cleanup() self.do_cleanup()
# Write variables to status_dict and reset deal_uptime # Write variables to status_dict and reset deal_uptime
@ -337,7 +329,7 @@ class trader:
config_filename = f"configs/{self.base}{self.quote}.json" config_filename = f"configs/{self.base}{self.quote}.json"
with open(config_filename,"r") as y: with open(config_filename,"r") as y:
config_dict = json.load(y) config_dict = json.load(y)
if self.config_dict["autoswitch"]: # In what context was this useful? if self.config.get_autoswitch(): # In what context was this useful?
config_dict["autoswitch"] = True # config_dict["autoswitch"] = True #
return config_dict return config_dict
@ -373,7 +365,7 @@ class trader:
self.status_dict["quote_spent"]=self.total_amount_of_quote self.status_dict["quote_spent"]=self.total_amount_of_quote
self.status_dict["base_bought"]=self.total_amount_of_base self.status_dict["base_bought"]=self.total_amount_of_base
self.status_dict["so_amount"]=self.safety_order_index self.status_dict["so_amount"]=self.safety_order_index
self.status_dict["no_of_safety_orders"]=self.config_dict["no_of_safety_orders"] self.status_dict["no_of_safety_orders"]=self.config.get_no_of_safety_orders()
self.status_dict["take_profit_price"]=self.take_profit_price self.status_dict["take_profit_price"]=self.take_profit_price
self.status_dict["safety_price_table"]=self.safety_price_table self.status_dict["safety_price_table"]=self.safety_price_table
self.status_dict["deal_uptime"]=int(time.time()) - self.deal_start_time self.status_dict["deal_uptime"]=int(time.time()) - self.deal_start_time
@ -381,14 +373,14 @@ class trader:
self.status_dict["fees_paid_in_base"]=self.fees_paid_in_base self.status_dict["fees_paid_in_base"]=self.fees_paid_in_base
self.status_dict["fees_paid_in_quote"]=self.fees_paid_in_quote self.status_dict["fees_paid_in_quote"]=self.fees_paid_in_quote
self.status_dict["start_price"]=self.start_price self.status_dict["start_price"]=self.start_price
self.status_dict["tp_mode"]=self.config_dict["tp_mode"] self.status_dict["tp_mode"]=self.config.get_tp_mode()
self.status_dict["profit_table"]=self.config_dict["tp_table"] self.status_dict["profit_table"]=self.config.get_tp_table()
self.status_dict["start_time"]=self.start_time self.status_dict["start_time"]=self.start_time
self.status_dict["deal_start_time"]=self.deal_start_time self.status_dict["deal_start_time"]=self.deal_start_time
self.status_dict["stop_when_profit"]=self.stop_when_profit self.status_dict["stop_when_profit"]=self.stop_when_profit
self.status_dict["autoswitch"]=False self.status_dict["autoswitch"]=False
if "autoswitch" in self.config_dict: if "autoswitch" in self.config_dict:
self.status_dict["autoswitch"]=self.config_dict["autoswitch"] self.status_dict["autoswitch"]=self.config.get_autoswitch()
except Exception as e: except Exception as e:
self.broker.logger.log_this(f"Can't update status dictionary. Exception: {e}",1,self.pair) self.broker.logger.log_this(f"Can't update status dictionary. Exception: {e}",1,self.pair)
@ -546,7 +538,7 @@ class trader:
optimal_order_size = 0 optimal_order_size = 0
minimum_amount_of_safety_orders = 1 #This variable could be a config knob minimum_amount_of_safety_orders = 1 #This variable could be a config knob
while amount_of_so>minimum_amount_of_safety_orders: while amount_of_so>minimum_amount_of_safety_orders:
optimal_order_size = self.return_optimal_order_size(free_base,min_base_size,amount_of_so,self.config_dict["safety_order_scale"]) #safety_order_scale: safety order growth factor optimal_order_size = self.return_optimal_order_size(free_base,min_base_size,amount_of_so,self.config.get_safety_order_scale()) #safety_order_scale: safety order growth factor
if optimal_order_size!=0: if optimal_order_size!=0:
self.broker.logger.log_this(f"Optimal order size is {optimal_order_size}",2,self.pair) self.broker.logger.log_this(f"Optimal order size is {optimal_order_size}",2,self.pair)
break break
@ -770,7 +762,7 @@ class trader:
self.safety_order_index = 0 self.safety_order_index = 0
#Disabling autoswitch #Disabling autoswitch
#self.config_dict["autoswitch"] = False #self.config.set_autoswitch(False)
#Done. Ready for start_trader #Done. Ready for start_trader
return 0 return 0
@ -894,7 +886,7 @@ class trader:
self.status_dict["pause_reason"] = "take_profit_routine - check time limit" self.status_dict["pause_reason"] = "take_profit_routine - check time limit"
#Checks if there is a time limit for the trader #Checks if there is a time limit for the trader
if "stop_time" in self.config_dict and time.time()>int(self.config_dict["stop_time"]): if self.config.get_programmed_stop() and time.time()>int(self.config.get_programmed_stop_time()):
self.stop_when_profit = True self.stop_when_profit = True
self.status_dict["pause_reason"] = "take_profit_routine - if stop_when_profit" self.status_dict["pause_reason"] = "take_profit_routine - if stop_when_profit"
@ -918,7 +910,7 @@ class trader:
self.pause = False self.pause = False
self.restart = True self.restart = True
return 1 return 1
elif self.check_orderbook_depth(self.broker.get_slippage_default_threshold(),self.config_dict["order_size"],filled_order["price"]): elif self.check_orderbook_depth(self.broker.get_slippage_default_threshold(),self.config.get_order_size(),filled_order["price"]):
self.broker.logger.log_this(f"Orderbook depth not sufficient, waiting for cooldown and restarting trader",1,self.pair) self.broker.logger.log_this(f"Orderbook depth not sufficient, waiting for cooldown and restarting trader",1,self.pair)
time.sleep(self.broker.get_wait_time()*self.broker.get_cooldown_multiplier()) time.sleep(self.broker.get_wait_time()*self.broker.get_cooldown_multiplier())
self.pause = False self.pause = False
@ -1046,7 +1038,7 @@ class trader:
if not self.warnings["speol_notified"] and price_exceeds: if not self.warnings["speol_notified"] and price_exceeds:
#Only notify one time AND if autoswitch is off #Only notify one time AND if autoswitch is off
self.warnings["speol_notified"] = True self.warnings["speol_notified"] = True
if not self.config_dict["autoswitch"]: if not self.config.get_autoswitch():
message = f"{self.base}@{self.status_dict['price']} ({str(self.broker.exchange)}), exceeds old long price of {self.status_dict['old_long']['tp_price']}" message = f"{self.base}@{self.status_dict['price']} ({str(self.broker.exchange)}), exceeds old long price of {self.status_dict['old_long']['tp_price']}"
self.broker.logger.log_this(message,0,self.pair) self.broker.logger.log_this(message,0,self.pair)
return 0 return 0
@ -1120,7 +1112,7 @@ class trader:
if self.tp_order["id"]=="": if self.tp_order["id"]=="":
self.broker.logger.log_this(f"Take profit order missing. Stopping bot. Order ID: {self.tp_order['id']}",1,self.pair) self.broker.logger.log_this(f"Take profit order missing. Stopping bot. Order ID: {self.tp_order['id']}",1,self.pair)
self.broker.cancel_order(self.so["id"],self.pair) self.broker.cancel_order(self.so["id"],self.pair)
if self.config_dict["attempt_restart"]: if self.config.get_attempt_restart():
self.write_status_file(is_backup=True) self.write_status_file(is_backup=True)
self.restart = True self.restart = True
self.broker.logger.log_this("Raising restart flag: take profit order missing, trader will be restarted",0,self.pair) self.broker.logger.log_this("Raising restart flag: take profit order missing, trader will be restarted",0,self.pair)
@ -1137,7 +1129,7 @@ class trader:
self.broker.logger.log_this(f"Take profit order closed but not filled, 0 filled. Stopping bot. Order ID: {self.tp_order['id']}",1,self.pair) self.broker.logger.log_this(f"Take profit order closed but not filled, 0 filled. Stopping bot. Order ID: {self.tp_order['id']}",1,self.pair)
#Cancelling safety order and stopping bot #Cancelling safety order and stopping bot
self.broker.cancel_order(self.so["id"],self.pair) self.broker.cancel_order(self.so["id"],self.pair)
if self.config_dict["attempt_restart"]: if self.config.get_attempt_restart():
self.write_status_file(is_backup=True) self.write_status_file(is_backup=True)
self.restart = True self.restart = True
self.broker.logger.log_this("Take profit order closed but not filled, trader will be restarted.",0,self.pair) self.broker.logger.log_this("Take profit order closed but not filled, trader will be restarted.",0,self.pair)
@ -1146,7 +1138,7 @@ class trader:
return 1 return 1
elif tp_status["status"]=="canceled": elif tp_status["status"]=="canceled":
#TODO: Here, if the safety order is still open, we could resend the tp order. #TODO: Here, if the safety order is still open, we could resend the tp order.
if self.config_dict["attempt_restart"]: if self.config.get_attempt_restart():
self.broker.logger.log_this("Take profit order canceled. Restarting the bot.",1,self.pair) self.broker.logger.log_this("Take profit order canceled. Restarting the bot.",1,self.pair)
self.write_status_file(is_backup=True) self.write_status_file(is_backup=True)
self.restart = True self.restart = True
@ -1158,7 +1150,7 @@ class trader:
return 1 return 1
# Check if safety order is filled # Check if safety order is filled
if self.so["id"] not in open_orders_ids and self.safety_order_index<=self.config_dict["no_of_safety_orders"]: if self.so["id"] not in open_orders_ids and self.safety_order_index<=self.config.get_no_of_safety_orders():
#so_status = self.so #so_status = self.so
#if self.so["id"]!="": #if self.so["id"]!="":
so_status = self.broker.get_order(self.so["id"],self.pair) so_status = self.broker.get_order(self.so["id"],self.pair)
@ -1170,42 +1162,42 @@ class trader:
if so_status["status"] in ["closed", "canceled", ""] and tp_order_status["status"]=="open": if so_status["status"] in ["closed", "canceled", ""] and tp_order_status["status"]=="open":
#Switch to short if all safety orders are sent and autoswitch is enabled. #Switch to short if all safety orders are sent and autoswitch is enabled.
#May get into trouble if the trader is short of funds #May get into trouble if the trader is short of funds
if not self.is_short and self.safety_order_index==self.config_dict["no_of_safety_orders"] and self.config_dict["autoswitch"]: if not self.is_short and self.safety_order_index==self.config.get_no_of_safety_orders() and self.config.get_autoswitch():
self.switch_to_short() self.switch_to_short()
self.write_status_file(is_backup=True) self.write_status_file(is_backup=True)
self.restart = True self.restart = True
return 0 return 0
a = self.new_so_routine(so_status,self.safety_order_index<self.config_dict["no_of_safety_orders"]) a = self.new_so_routine(so_status,self.safety_order_index<self.config.get_no_of_safety_orders())
#0 OK, 1 not enough funds, 2 can't cancel old TP, 3 can't send new TP #0 OK, 1 not enough funds, 2 can't cancel old TP, 3 can't send new TP
if a==1: if a==1:
self.broker.logger.log_this(f"Can't send new safety order. Not enough funds? new_so_routine returned {a}",1,self.pair) self.broker.logger.log_this(f"Can't send new safety order. Not enough funds? new_so_routine returned {a}",1,self.pair)
#If there are not enough funds do not even try to send more safety orders #If there are not enough funds do not even try to send more safety orders
#This way of doing it seems more practical than setting up yet another flag #This way of doing it seems more practical than setting up yet another flag
self.config_dict["no_of_safety_orders"] = self.safety_order_index self.config.set_no_of_safety_orders(self.safety_order_index)
return 1 return 1
elif a==2: elif a==2:
self.broker.logger.log_this(f"Can't cancel old take profit order. new_so_routine returned {a}",1,self.pair) self.broker.logger.log_this(f"Can't cancel old take profit order. new_so_routine returned {a}",1,self.pair)
self.pause = False self.pause = False
self.status_dict["pause_reason"] = "" self.status_dict["pause_reason"] = ""
if self.config_dict["attempt_restart"]: if self.config.get_attempt_restart():
self.write_status_file(is_backup=True) self.write_status_file(is_backup=True)
self.restart = True self.restart = True
return 1 return 1
elif a==3: elif a==3:
#self.pause = False #self.pause = False
self.broker.logger.log_this(f"Error in trader: Can't send new take profit order. Restart will be attempted. new_so_routine returned {a}",0,self.pair) self.broker.logger.log_this(f"Error in trader: Can't send new take profit order. Restart will be attempted. new_so_routine returned {a}",0,self.pair)
if self.config_dict["attempt_restart"]: if self.config.get_attempt_restart():
self.write_status_file(is_backup=True) self.write_status_file(is_backup=True)
self.restart = True self.restart = True
return 1 return 1
#Check if short price exceeds old long price. If so, send a Telegram message #Check if short price exceeds old long price. If so, send a Telegram message
if self.is_short and "old_long" in self.status_dict and self.config_dict["check_old_long_price"]: if self.is_short and "old_long" in self.status_dict and self.config.get_check_old_long_price():
self.check_old_long_price() self.check_old_long_price()
self.status_dict["pause_reason"] = "check for autoswitch" self.status_dict["pause_reason"] = "check for autoswitch"
#If it's a short bot that used to be long AND autoswitch is enabled #If it's a short bot that used to be long AND autoswitch is enabled
if self.is_short and "autoswitch" in self.config_dict and self.config_dict["autoswitch"] and "old_long" in self.status_dict: if self.is_short and "autoswitch" in self.config_dict and self.config.get_autoswitch() and "old_long" in self.status_dict:
#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, #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. # it means that we already are in profit territory, switch back to long.
#A more conservative approach would be old_target = self.status_dict["old_long"]["quote_spent"], just breaking even. #A more conservative approach would be old_target = self.status_dict["old_long"]["quote_spent"], just breaking even.
@ -1241,7 +1233,7 @@ class trader:
def get_tp_level(self, order_index: int = 0) -> float: def get_tp_level(self, order_index: int = 0) -> float:
''' '''
Returns the correct take profit percentage, according to the strategy (config_dict["tp_mode"]): Returns the correct take profit percentage, according to the strategy (config.get_tp_mode()):
0. Fixed percentage 0. Fixed percentage
1. Variable percentage (+0.5% to -0.5% of the fixed percentage) 1. Variable percentage (+0.5% to -0.5% of the fixed percentage)
2. Custom percentage table 2. Custom percentage table
@ -1257,26 +1249,26 @@ class trader:
self.is_boosted = True self.is_boosted = True
boost_percentage = self.boosted_amount boost_percentage = self.boosted_amount
if self.is_short or self.config_dict["tp_mode"]==0: #Fixed take profit percentage if self.is_short or self.config.get_tp_mode()==0: #Fixed take profit percentage
tp_level = self.config_dict["tp_level"] tp_level = self.config.get_tp_level()
elif self.config_dict["tp_mode"]==1: #Variable percentage elif self.config.get_tp_mode()==1: #Variable percentage
limit = self.config_dict["no_of_safety_orders"]/3 limit = self.config.get_no_of_safety_orders()/3
if order_index<=1: if order_index<=1:
tp_level = self.config_dict["tp_level"]+0.005 tp_level = self.config.get_tp_level()+0.005
elif order_index<=limit: elif order_index<=limit:
tp_level = self.config_dict["tp_level"] tp_level = self.config.get_tp_level()
elif limit<=order_index<=limit*2: elif limit<=order_index<=limit*2:
tp_level = self.config_dict["tp_level"]-0.0025 tp_level = self.config.get_tp_level()-0.0025
else: else:
tp_level = self.config_dict["tp_level"]-0.005 tp_level = self.config.get_tp_level()-0.005
elif self.config_dict["tp_mode"]==2: elif self.config.get_tp_mode()==2:
if ["tp_table"] in self.config_dict: if ["tp_table"] in self.config_dict:
if len(self.config_dict["tp_table"])>=order_index: if len(self.config.get_tp_table())>=order_index:
tp_level = self.config_dict["tp_table"][order_index] #Custom percentage table tp_level = self.config.get_tp_table()[order_index] #Custom percentage table
tp_level = self.config_dict["tp_table"][-1] tp_level = self.config.get_tp_table()[-1]
tp_level = self.config_dict["tp_level"] tp_level = self.config.get_tp_level()
elif self.config_dict["tp_mode"]==3: #Linear percentage table elif self.config.get_tp_mode()==3: #Linear percentage table
profit_table = self.linear_space(self.config_dict["tp_level"]+0.005,self.config_dict["tp_level"]-0.005,self.config_dict["no_of_safety_orders"]) profit_table = self.linear_space(self.config.get_tp_level()+0.005,self.config.get_tp_level()-0.005,self.config.get_no_of_safety_orders())
tp_level = profit_table[-1] tp_level = profit_table[-1]
if order_index<len(profit_table): #If more safety orders were added, instead of recalculating the whole table if order_index<len(profit_table): #If more safety orders were added, instead of recalculating the whole table
tp_level = profit_table[order_index] #it just returns the last value. Otherwise, the percentage gets very small. tp_level = profit_table[order_index] #it just returns the last value. Otherwise, the percentage gets very small.
@ -1366,7 +1358,7 @@ class trader:
''' '''
Sends a new safety order to the exchange Sends a new safety order to the exchange
''' '''
so_size = self.gib_so_size(size,self.safety_order_index+1,self.config_dict["safety_order_scale"]) #safety_order_scale: safety order growth factor so_size = self.gib_so_size(size,self.safety_order_index+1,self.config.get_safety_order_scale()) #safety_order_scale: safety order growth factor
if self.is_short: if self.is_short:
new_order = self.broker.new_limit_order(self.pair,so_size,"sell",self.safety_price_table[self.safety_order_index+1]) new_order = self.broker.new_limit_order(self.pair,so_size,"sell",self.safety_price_table[self.safety_order_index+1])
else: else:
@ -1428,16 +1420,16 @@ class trader:
Generates a table of safety order's prices Generates a table of safety order's prices
''' '''
safety_price_table = [start_price] safety_price_table = [start_price]
if self.config_dict["dynamic_so_deviance"]:# and no_of_safety_orders>=30: if self.config.get_dynamic_so_deviance():# and no_of_safety_orders>=30:
#if self.config_dict["dynamic_so_deviance"] and not self.is_short: #if self.config.get_dynamic_so_deviance() and not self.is_short:
#bias should be a real number between -1 and 1 (1>n>-1, NOT 1=>n>=-1) #bias should be a real number between -1 and 1 (1>n>-1, NOT 1=>n>=-1)
#If bias -> 1, more space between the first orders, if -> -1, more space between the last orders, if 0, no change.. #If bias -> 1, more space between the first orders, if -> -1, more space between the last orders, if 0, no change..
if "bias" in self.config_dict: if "bias" in self.config_dict:
deviance_factor = safety_order_deviance*self.clip_value(self.config_dict["bias"],-.99,.99) deviance_factor = safety_order_deviance*self.clip_value(self.config.get_bias(),-.99,.99)
so_deviance_table = self.linear_space(safety_order_deviance+deviance_factor,safety_order_deviance-deviance_factor,no_of_safety_orders) so_deviance_table = self.linear_space(safety_order_deviance+deviance_factor,safety_order_deviance-deviance_factor,no_of_safety_orders)
else: else:
#Old way of calculating deviance #Old way of calculating deviance
so_deviance_table = self.linear_space(safety_order_deviance-self.config_dict["dsd_range"],safety_order_deviance+self.config_dict["dsd_range"],no_of_safety_orders) so_deviance_table = self.linear_space(safety_order_deviance-self.config.get_dsd_range(),safety_order_deviance+self.config.get_dsd_range(),no_of_safety_orders)
so_deviance_table.extend([so_deviance_table[-1]]*2) #This extra entries are needed in the next for loop so_deviance_table.extend([so_deviance_table[-1]]*2) #This extra entries are needed in the next for loop
else: else:
so_deviance_table = [safety_order_deviance]*(no_of_safety_orders+2) so_deviance_table = [safety_order_deviance]*(no_of_safety_orders+2)
@ -1540,10 +1532,10 @@ class trader:
#Change pair-related variables #Change pair-related variables
old_quote = self.quote old_quote = self.quote
self.quote = new_quote self.quote = new_quote
self.config_dict["pair"] = f"{self.base}/{self.quote}" self.config.set_pair(f"{self.base}/{self.quote}")
self.profit_filename = f"profits/{self.base}{self.quote}.profits" self.profit_filename = f"profits/{self.base}{self.quote}.profits"
self.log_filename = f"logs/{self.base}{self.quote}.log" self.log_filename = f"logs/{self.base}{self.quote}.log"
self.pair = self.config_dict["pair"] self.pair = self.config.get_pair()
#If there is an old_long file, also copy it #If there is an old_long file, also copy it
if self.is_short and "old_long" in self.status_dict: if self.is_short and "old_long" in self.status_dict:
@ -1576,10 +1568,10 @@ class trader:
self.broker.remove_pair_from_config(f"{self.base}{new_quote}") self.broker.remove_pair_from_config(f"{self.base}{new_quote}")
self.broker.add_pair_to_config(f"{self.base}{self.quote}") self.broker.add_pair_to_config(f"{self.base}{self.quote}")
self.config_dict["pair"] = f"{self.base}/{self.quote}" self.config.set_pair(f"{self.base}/{self.quote}")
self.profit_filename = f"profits/{self.base}{self.quote}.profits" self.profit_filename = f"profits/{self.base}{self.quote}.profits"
self.log_filename = f"logs/{self.base}{self.quote}.log" self.log_filename = f"logs/{self.base}{self.quote}.log"
self.pair = self.config_dict["pair"] self.pair = self.config.get_pair()
#Writing config file #Writing config file
if write_broker_file and self.broker.rewrite_config_file()==1: if write_broker_file and self.broker.rewrite_config_file()==1:
@ -1626,7 +1618,7 @@ class trader:
mid_percentage = 10 mid_percentage = 10
high_percentage = 20 high_percentage = 20
safety_order_string = f"{self.status_dict['so_amount']-1}/{self.config_dict['no_of_safety_orders']}".rjust(5) safety_order_string = f"{self.status_dict['so_amount']-1}/{self.config.get_no_of_safety_orders()}".rjust(5)
#Check if necessary #Check if necessary
low_price = 0 low_price = 0
@ -1701,12 +1693,12 @@ class trader:
line1 = f"{p}{pair_color}{self.pair.center(13)}{white}| {safety_order_string} |{prices}| Uptime: {self.seconds_to_time(self.status_dict['deal_uptime'])}" line1 = f"{p}{pair_color}{self.pair.center(13)}{white}| {safety_order_string} |{prices}| Uptime: {self.seconds_to_time(self.status_dict['deal_uptime'])}"
if self.is_boosted: if self.is_boosted:
line1 = f"{line1} | BOOSTED" line1 = f"{line1} | BOOSTED"
if self.config_dict["autoswitch"]: if self.config.get_autoswitch():
line1 = f"{line1} | AUTO" line1 = f"{line1} | AUTO"
if multiplier>1: if multiplier>1:
#Only displays the multiplier if autoswitch is enabled. #Only displays the multiplier if autoswitch is enabled.
line1 = f"{line1}x{multiplier}" line1 = f"{line1}x{multiplier}"
if "stop_time" in self.config_dict and time.time()<=int(self.config_dict["stop_time"]): if self.config.get_programmed_stop() and time.time()<=self.config.get_programmed_stop_time():
line1 = f"{line1} | PROGRAMMED LAST DEAL" line1 = f"{line1} | PROGRAMMED LAST DEAL"
if self.stop_when_profit==True: if self.stop_when_profit==True:
line1 = f"{line1} | LAST DEAL" line1 = f"{line1} | LAST DEAL"
@ -1733,7 +1725,7 @@ class trader:
self.total_amount_of_quote = self.status_dict["quote_spent"] self.total_amount_of_quote = self.status_dict["quote_spent"]
self.total_amount_of_base = self.status_dict["base_bought"] self.total_amount_of_base = self.status_dict["base_bought"]
self.safety_order_index = self.status_dict["so_amount"] self.safety_order_index = self.status_dict["so_amount"]
self.config_dict["no_of_safety_orders"] = self.status_dict["no_of_safety_orders"] #If this is not loaded from status_dict, it will ignore if safety orders were added at runtime self.config.set_no_of_safety_orders(self.status_dict["no_of_safety_orders"]) #If this is not loaded from status_dict, it will ignore if safety orders were added at runtime
self.take_profit_price = self.status_dict["take_profit_price"] self.take_profit_price = self.status_dict["take_profit_price"]
self.safety_price_table = self.status_dict["safety_price_table"] self.safety_price_table = self.status_dict["safety_price_table"]
self.fees_paid_in_base = self.status_dict["fees_paid_in_base"] self.fees_paid_in_base = self.status_dict["fees_paid_in_base"]
@ -1756,9 +1748,9 @@ class trader:
#Load safety order #Load safety order
self.so = self.broker.get_order(self.status_dict["so_order_id"],self.pair) self.so = self.broker.get_order(self.status_dict["so_order_id"],self.pair)
if self.so==self.broker.get_empty_order() and self.safety_order_index<self.config_dict["no_of_safety_orders"]: if self.so==self.broker.get_empty_order() and self.safety_order_index<self.config.get_no_of_safety_orders():
#The second condition is important: it signals that the empty order returned was because of an error, not because the trader ran out of funds in the past. #The second condition is important: it signals that the empty order returned was because of an error, not because the trader ran out of funds in the past.
#When the trader runs out of funds, safety_order_index=config_dict["no_of_safety_orders"] #When the trader runs out of funds, safety_order_index=config.get_no_of_safety_orders()
self.broker.logger.log_this("Couldn't load safety order. Aborting.",2,self.pair) self.broker.logger.log_this("Couldn't load safety order. Aborting.",2,self.pair)
self.quit = True self.quit = True
return 1 return 1