DCAv2/trader.py

1837 lines
94 KiB
Python
Executable File

import csv
import json
import time
import os
class trader:
def __init__(self, broker, config_dict: dict, is_import: bool = False):
self.pause = True #Signals the trader to not process order info when an API call manhandles the trader
#True by default, once the trader is started the start_trader method toggles it
self.quit = False #If true, it doesn't restart the bot when profit is reached.
self.restart = False
self.broker = broker
self.tp_order = self.broker.get_empty_order()
self.so = self.broker.get_empty_order()
self.config_dict = config_dict
self.pair = self.config_dict["pair"]
self.market = self.broker.fetch_market(self.pair)
self.market_load_time = int(time.time())
self.market_reload_period = 86400 #Market reload period in seconds
self.base,self.quote = self.pair.split("/")
self.is_short = self.config_dict["is_short"]
self.profit_table = self.config_dict["tp_table"]
self.max_short_safety_orders = 45
if "max_short_safety_orders" in config_dict:
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.start_time = int(time.time())
self.total_amount_of_quote=0
self.total_amount_of_base=1
self.take_profit_price=0
self.safety_price_table=[0]
self.fees_paid_in_base=0
self.fees_paid_in_quote=0
self.deal_start_time = 0
self.last_time_seen = time.time()
self.start_price = 0
self.safety_order_index = 0
self.status_dict = {
"quote_spent": 0,
"base_bought": 0,
"so_amount": 0,
#"max_so_amount": config_dict["no_of_safety_orders"],
"take_profit_price": 1,
"next_so_price": 1,
#"acc_profit": 0,
"tp_order_id": "",
"take_profit_order": {},
"so_order_id": "",
"safety_order": {},
"safety_price_table": [],
"pause_reason": "",
"deal_uptime": 0, #In seconds
"total_uptime": 0, #In seconds
"price": 0,
"deal_order_history": []
}
self.warnings = {
"short_price_exceeds_old_long": False,
"speol_notified": False
}
if "stop_time" in self.config_dict and int(self.config_dict["stop_time"])<int(time.time()):
self.config_dict.pop("stop_time",None)
#Write config file changes
self.broker.rewrite_config_file()
if self.is_short:
#Check if there is an old_long file. If so, load it.
try:
with open(f"status/{self.base}{self.quote}.oldlong") as ol:
self.status_dict["old_long"] = json.load(ol)
except Exception as e:
self.broker.logger.log_this(f"Exception: No old_long file. {e}",1,self.pair)
self.profit_filename = f"profits/{self.base}{self.quote}.profits"
self.log_filename = f"logs/{self.base}{self.quote}.log"
self.stop_when_profit = False
self.status_dict["pause_reason"] = "Initialization"
if is_import:
self.load_imported_trader()
return None
# An alternative would be to set up a variable like self.is_initalized to false and finish the initialization here.
# Then, in the main loop, check if self.is_initalized is false. If it is, run start_trader.
start_result = self.start_trader()
if start_result==0:
return None #Everything is OK
elif start_result==1: #If initialization fails
self.quit = True
elif start_result==2: #Retries exceeded
self.quit = True
elif start_result==3: #Not enough liquidity
self.pause = False
self.restart = True
def set_market_load_time(self, period: float) -> int:
self.market_load_time = period
return 0
def get_market_reload_period(self) -> float:
return self.market_reload_period
def reload_safety_order(self) -> int:
'''
Reloads the safety order.
'''
self.so = self.broker.get_order(self.status_dict["so_order_id"],self.pair)
return 0
def start_trader(self) -> int:
'''
Initializes the trader.
'''
#Perhaps we should search for open buy orders from a crashed trader and cancel them?
#Reset some variables
self.safety_order_index = 0
self.status_dict["deal_order_history"].clear()
self.tp_order = self.broker.get_empty_order()
self.so = self.broker.get_empty_order()
#Reloads the market
new_market_data = self.broker.fetch_market(self.pair)
if new_market_data is not None:
self.market = new_market_data
self.pause = True
self.status_dict["pause_reason"] = "start_trader"
if self.is_short:
self.broker.logger.log_this("Calculating optimal order size...",2,self.pair)
#Get minimum order size from exchange
self.broker.logger.log_this("Fetching minimum order size...",2,self.pair)
min_base_size = self.broker.get_min_base_size(self.pair)
if min_base_size is None:
self.broker.logger.log_this("Can't fetch the minimum order size",1,self.pair)
return 1
#Fetch the amount of free base available on the exchange
self.broker.logger.log_this("Fetching free base currency on the exchange...",2,self.pair)
free_base = self.fetch_free_base()
if free_base is None:
self.broker.logger.log_this("Can't fetch the amount of base at the exchange",1,self.pair)
return 1
#Buy missing base sold because of rounding errors (rare)
if "old_long" in self.status_dict:
diff = self.status_dict["old_long"]["tp_amount"] - free_base
if diff>min_base_size:
diff = self.broker.amount_to_precision(self.pair,diff)
self.broker.logger.log_this(f"Buying missing {diff} {self.base}",1,self.pair)
self.broker.new_market_order(self.pair,diff,"buy",amount_in_base=True)
time.sleep(self.broker.get_wait_time()*2)
#Re-quering for the amount of base currency on the exchange
free_base = self.fetch_free_base()
if free_base is None:
self.broker.logger.log_this("Can't fetch the amount of base at the exchange",1,self.pair)
return 1
#Calculate order size and amount of safety orders
self.broker.logger.log_this("Calculating the order size...",2,self.pair)
order_size,no_of_safety_orders = self.calculate_order_size(free_base,min_base_size,self.max_short_safety_orders)
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)
return 1
self.config_dict["order_size"] = order_size
self.config_dict["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)
#Write the changes to the config file
with open(f"configs/{self.base}{self.quote}.json","w") as g:
g.write(json.dumps(self.config_dict, indent=4))
else:
#Check order size
self.status_dict["pause_reason"] = "start_trader - checking order size"
self.broker.logger.log_this("Checking for order size",2,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"]:
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:
#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.config_dict["order_size"] = float(int(minimum_order_size_allowed)+1)
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)
return 1
#check slippage
if self.check_slippage:
self.broker.logger.log_this("Checking slippage...",2,self.pair)
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"]):
#Slippage threshold exceeded
self.broker.logger.log_this("Slippage threshold exceeded",1,self.pair)
return 3
self.status_dict["pause_reason"] = "start_trader - after slippage"
self.status_dict["order_size"] = self.config_dict["order_size"]
#Sending initial order
self.status_dict["pause_reason"] = "start_trader - sending first order"
self.broker.logger.log_this("Sending first order...",2,self.pair)
action = "sell" if self.is_short else "buy"
first_order = self.broker.new_market_order(self.pair,self.config_dict["order_size"],action)
#self.broker.logger.log_this(f"First order id: {first_order}",1,self.pair)
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)
return 1
tries = self.broker.get_retries()*2 #This is really necessary, don't change it. Don't. DON'T.
#Wait until the first order gets filled
self.status_dict["pause_reason"] = "start_trader - waiting for the first order to get filled"
while True:
#Wait a bit longer, to catch a bug:
#Sometimes the amount of base taken into account by the trader is lower than the amount bought,
# which ends up misrepresenting the trade cost per unit of base, which causes the take profit price to skyrocket.
# Maybe is the first market order getting "closed" before is fully filled?
# Or is there an error later in the trader?
time.sleep(self.broker.get_wait_time())
returned_order = self.broker.get_order(first_order["id"],self.pair)
if returned_order==self.broker.get_empty_order():
self.broker.logger.log_this("Problems with the initial order",1,self.pair)
return 1
elif returned_order["status"]=="closed":
break
elif returned_order["status"]=="expired":
self.broker.logger.log_this(f"First order expired. Id: {returned_order['id']}",1,self.pair)
return 1
else:
tries-=1
self.broker.logger.log_this("Waiting for initial order to get filled...",2,self.pair)
self.broker.logger.log_this(f"Order ID: {returned_order['id']}",2,self.pair)
if tries==0:
self.broker.logger.log_this("Restart retries exhausted.",0,self.pair)
self.broker.cancel_order(returned_order["id"],self.pair)
#self.restart = True #This restart is tricky, it can end up in an endless loop of retries
#By this point, both the take_profit_routine initialization AND the subsequent restart attempt failed.
#Since it only reaches this point very unfrequently, let we'll the trader get stuck in a pause state.
return 2
#Save the order
self.status_dict["pause_reason"] = "start_trader - saving the order in deal_order_history"
self.status_dict["deal_order_history"].append(returned_order)
# Reset the fee count and sum fees from the first order
self.fees_paid_in_base, self.fees_paid_in_quote = self.parse_fees(returned_order)
self.broker.logger.log_this(f"Fees paid: {self.fees_paid_in_base} {self.base}, {self.fees_paid_in_quote} {self.quote}",2,self.pair)
self.broker.logger.log_this(f"Take profit order ID: {returned_order['id']}",2,self.pair)
# Sum total amount of quote and base
if returned_order["filled"]!=None:
self.total_amount_of_base = returned_order["filled"]
if not self.is_short:
#self.total_amount_of_base -= self.parse_fees(returned_order)[0] #The substraction is because some exchanges charges some fees in base
self.total_amount_of_base -= self.fees_paid_in_base
self.total_amount_of_quote = returned_order["cost"]
else:
self.broker.logger.log_this("Error starting bot. Aborting.",1,self.pair)
return 1
# Send the take profit order
self.status_dict["pause_reason"] = "start_trader - sending tp order"
self.broker.logger.log_this("Sending take profit order...",2,self.pair)
if self.send_new_tp_order()==0:
self.broker.logger.log_this("Take profit order sent",2,self.pair)
else:
self.broker.logger.log_this("Error sending take profit order. Aborting.",1,self.pair)
return 1
# 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.safety_price_table = self.calculate_safety_prices(self.start_price,self.config_dict["no_of_safety_orders"],self.config_dict["safety_order_deviance"])
# Send the first safety order
self.status_dict["pause_reason"] = "start_trader - sending safety order"
self.broker.logger.log_this("Sending safety order...",2,self.pair)
if self.send_new_safety_order(self.status_dict["order_size"])==0:
self.broker.logger.log_this("Safety order sent",2,self.pair)
else:
self.broker.logger.log_this("Error sending safety order. Cancelling take profit order and aborting",1,self.pair)
self.broker.cancel_order(self.tp_order["id"],self.pair)
return 1
# Send cleanup order (if cleanup)
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.
self.do_cleanup()
# Write variables to status_dict and reset deal_uptime
self.deal_start_time = int(time.time())
self.update_status(True)
self.pause = False
self.status_dict["pause_reason"] = ""
return 0
def reload_config_dict(self) -> dict:
'''
Reloads the config dictionary from disk
:return: dict
'''
config_filename = f"configs/{self.base}{self.quote}.json"
with open(config_filename,"r") as y:
config_dict = json.load(y)
if self.config_dict["autoswitch"]:
config_dict["autoswitch"] = True
return config_dict
def update_status(self, write_to_disk: bool) -> int:
'''
Updates the status dictionary
:param write_to_disk: bool - If True, writes the status file to disk.
:return: int
'''
try:
if self.tp_order is not None: #Type checking
self.status_dict["tp_order_id"]=self.tp_order["id"]
self.status_dict["take_profit_order"]=self.tp_order
if self.so is not None: #Type checking
self.status_dict["so_order_id"]=self.so["id"]
self.status_dict["safety_order"]=self.so
if self.tp_order is not None and self.tp_order["price"] is not None:
self.status_dict["take_profit_price"]=self.tp_order["price"]
try:
self.status_dict["next_so_price"]=self.safety_price_table[self.safety_order_index] #List index out of range bug
except Exception as e:
self.broker.logger.log_this(f"Is safety_price_table populated? Exception: {e} | Safety price table: {self.safety_price_table} | Safety order index: {self.safety_order_index}",1,self.pair)
if self.so is not None and self.so["price"] is not None and self.so!=self.broker.get_empty_order():
self.status_dict["next_so_price"]=self.so["price"]
self.status_dict["is_boosted"]=self.is_boosted
self.status_dict["is_short"]=self.is_short
self.status_dict["quote_spent"]=self.total_amount_of_quote
self.status_dict["base_bought"]=self.total_amount_of_base
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["take_profit_price"]=self.take_profit_price
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["total_uptime"]=int(time.time()) - self.start_time
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["start_price"]=self.start_price
self.status_dict["tp_mode"]=self.config_dict["tp_mode"]
self.status_dict["profit_table"]=self.config_dict["tp_table"]
self.status_dict["start_time"]=self.start_time
self.status_dict["deal_start_time"]=self.deal_start_time
self.status_dict["stop_when_profit"]=self.stop_when_profit
except Exception as e:
self.broker.logger.log_this(f"Can't update status dictionary. Exception: {e}",1,self.pair)
if write_to_disk:
self.write_status_file()
#try:
# if write_to_disk:
# json_object = json.dumps(self.status_dict, indent=4)
# with open(f"status/{self.base}{self.quote}.status", "w") as c:
# c.write(json_object)
#except Exception as e:
# self.broker.logger.log_this(f"Can't write status file to disk. Exception: {e}",1,self.pair)
return 0
def write_status_file(self,is_backup:bool=False):
try:
json_object = json.dumps(self.status_dict, indent=4)
file_name = f"{self.base}{self.quote}.status"
if is_backup:
self.broker.logger.log_this("Backing up status file...",2,self.pair)
file_name = time.strftime(f"{self.base}{self.quote}_%Y-%m-%d_%H:%M:%S.backup_status")
with open(f"status/{file_name}", "w") as c:
c.write(json_object)
except Exception as e:
self.broker.logger.log_this(f"Can't write status file to disk. Exception: {e}",1,self.pair)
def dca_cost_calculator(self, order_size: float, amount_of_so: int, scalar: float) -> float:
'''
Returns the maximum amount of currency that can be used by a trader, given the initial order size
:param order_size: float
:param amount_of_so: int
:param scalar: float
:return: float
'''
total = order_size
for i in range(1,amount_of_so+1):
total+=self.gib_so_size(order_size,i,scalar)
return total
def return_optimal_order_size(self, amount: float, min_size: float, amount_of_safety_orders: int, scalar: float) -> float:
'''
Calculates the optimal order size for a short bot, according to the amount passed as a parameter.
Due to performance issues, the step size that is used is 1/10th of the minimum order size.
:param amount: float
:param min_size: float
:param amount_of_safety_orders: int
:param scalar: float
:return: float
'''
total_size = float(min_size)
#Calculate optimal step size
self.broker.logger.log_this("Calculating optimal step size...",2,self.pair)
#step = self.get_step_size()
#if step is None:
# step = min_size
#if step==0:
# step = min_size
#self.broker.logger.log_this(f"Step size is {step}",2,self.pair)
divisor = 10
while divisor>0:
#step = self.broker.amount_to_precision(self.pair,min_size/divisor)
step = min_size/divisor
if step!=0: #When using amount_to_precision, this comes handy.
break
divisor-=1
#if step==0:
# step = self.broker.amount_to_precision(self.pair,min_size)
previous_size = 0
while True: #This loop should have a safeguard
self.broker.logger.log_this(f"Calculating optimal order size ...",2,self.pair)
total_cost = self.dca_cost_calculator(total_size,amount_of_safety_orders,scalar)
if total_cost>=amount:
return previous_size
previous_size = total_size
total_size+=step
def parse_fees(self, order: dict) -> tuple:
'''
Returns the fees paid ordered in "base,quote"
Note: CCXT does not detail the fees paid if the exchange is Binance.
'''
basefee = 0
quotefee = 0
#Uncomment if you want to guesstimate Binance's fees (Should this be a flag?).
#if self.broker.get_exchange_name()=="binance":
# #Fees of buy orders are charged in base currency, fees of sell orders are charged in quote currency.
# try:
# fee_rate = self.market["maker"] if order["type"]=="limit" else self.market["taker"]
# except Exception as e:
# self.broker.logger.log_this(f"Exception fetching market information: {e}. Using default fee rate of 0.1%",1,self.pair)
# fee_rate = 0.001
#
# if order["side"]=="buy":
# basefee = order["filled"]*float(fee_rate)
# elif order["side"]=="sell":
# quotefee = order["cost"]*float(fee_rate)
# return basefee,quotefee
for x in order["fees"]:
if x["currency"]==self.base:
basefee+=float(x["cost"])
if x["currency"]==self.quote:
quotefee+=float(x["cost"])
return basefee,quotefee
def do_cleanup(self) -> int:
'''
Checks for any remaining base currency balance on the exchange
If it finds some and that amount is enough, it sends a sell order at the take profit price
It was implemented because some deals close with a little amount of base remaining
and it tends to pile up overtime
A more elegant solution would be to take note of the amount and the price at the moment of the deal closing that lead to
that small amount of change to appear, to make possible to calculate an optimal sell price of the remaining assets
instead of brute forcing it this way.
For smaller bots that might be overengineering it a bit anyway
'''
if self.is_short: #Short bots do not need cleanup
return 0
balance_to_clean = self.fetch_free_base()
if balance_to_clean is None:
self.broker.logger.log_this("Can't fetch free base",1,self.pair)
return 1
#balance_to_clean /= 2 #Maybe it's a good idea, sort of DCAing the dust.
min_base_size = self.broker.get_min_base_size(self.pair)
minimum_cleanup_size = self.so["amount"]*2 # type: ignore
if balance_to_clean-minimum_cleanup_size >= min_base_size:
self.broker.logger.log_this(f"Balance to clean: {balance_to_clean-minimum_cleanup_size} {self.base}",2,self.pair)
self.broker.logger.log_this("Sending cleanup order...",2,self.pair)
cleanup_order = self.broker.new_limit_order(self.pair,balance_to_clean-minimum_cleanup_size,"sell",self.take_profit_price)
if cleanup_order not in [None,self.broker.get_empty_order()]:
self.broker.logger.log_this("Cleanup successful",2,self.pair)
return 0
self.broker.logger.log_this("Problems with the cleanup order",1,self.pair)
return 1
self.broker.logger.log_this("No cleanup needed",2,self.pair)
return 0
def calculate_order_size(self, free_base: float, min_base_size: float, amount_of_so: int = 30) -> tuple:
'''
Calculates the optimal order size and the amount of safety orders from the free_base and minimum base size
'''
optimal_order_size = 0
minimum_amount_of_safety_orders = 1 #This variable could be a config knob
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
if optimal_order_size!=0:
self.broker.logger.log_this(f"Optimal order size is {optimal_order_size}",2,self.pair)
break
amount_of_so-=1
if optimal_order_size==0:
self.broker.logger.log_this("Not enough base to switch. Order size would be too small",1,self.pair)
self.pause = False
self.status_dict["pause_reason"] = ""
return None,None
if optimal_order_size<min_base_size: #Sometimes amount_to_precision rounds to a value less than the minimum
self.broker.logger.log_this("Optimal order size is smaller than the minimum order size",1,self.pair)
self.pause = False
self.status_dict["pause_reason"] = ""
return None,None
return optimal_order_size,amount_of_so
def fetch_free_base(self,currency: str = ""):
'''
Returns the amount of free currency on the exchange
'''
if currency=="":
currency = self.base
balance = self.broker.get_coins_balance()
if balance==[]:
self.broker.logger.log_this("Can't fetch free base from the exchange",1,self.pair)
return None
if currency in balance["free"]:
return float(balance["free"][currency])
self.broker.logger.log_this("Currency not present in balance",1,self.pair)
return 0
def switch_to_short(self) -> int:
'''
This method modifies the config file of a pair to convert it to short.
It automagically sets the order size according to the funds available.
If not enough funds, it returns 1
'''
if self.is_short: #Check if bot is already a short bot
return 1
#Let's do some type checking first
if self.tp_order is None:
self.broker.logger.log_this("Take profit order is None, can't switch to short",1,self.pair)
return 1
if self.so is None:
self.broker.logger.log_this("Safety order is None, can't switch to short",1,self.pair)
return 1
#Pauses bot
self.pause = True
self.status_dict["pause_reason"] = "switch_to_short"
#Read the config file
self.broker.logger.log_this("Reading config file",2,self.pair)
try:
with open(f"configs/{self.base}{self.quote}.json","r") as f:
old_config = json.load(f)
except Exception as e:
self.broker.logger.log_this(f"Error. Can't read the config file. Can't switch mode. Exception: {e}",1,self.pair)
self.pause = False
self.status_dict["pause_reason"] = ""
return 1
#Calculate if there is enough base and, if so, calculate the optimal order size
#Fetch the real amount of available base
self.broker.logger.log_this(f"Fetching available {self.base}",2,self.pair)
free_base = self.fetch_free_base()
if free_base is None:
return 1
#Fetch the minimal order size
min_base_size = self.broker.get_min_base_size(self.pair)
if min_base_size is None:
self.broker.logger.log_this("Error. Can't fetch market info from the exchange",1,self.pair)
self.pause = False
self.status_dict["pause_reason"] = ""
return 1
#Check if there is enough base
if self.broker.amount_to_precision(self.pair,free_base+self.tp_order["amount"])<=min_base_size:
self.broker.logger.log_this("Error. Not enough base currency",1,self.pair)
self.pause = False
self.status_dict["pause_reason"] = ""
return 1
#Calculate order size
self.broker.logger.log_this("Calculating optimal order size",2,self.pair)
optimal_order_size,amount_of_so = self.calculate_order_size(free_base+self.tp_order["amount"],min_base_size,amount_of_so=self.max_short_safety_orders)
if optimal_order_size is None or amount_of_so is None:
return 1
self.broker.logger.log_this(f"New order size: {optimal_order_size}",2,self.pair)
self.broker.logger.log_this(f"Amount of safety orders: {amount_of_so}",2,self.pair)
#Close old orders
self.broker.logger.log_this("Switching trader mode to short",2,self.pair)
self.broker.logger.log_this("Closing orders...",2,self.pair)
if self.broker.cancel_order(self.tp_order["id"],self.pair)==1:
self.broker.logger.log_this("Can't cancel the take profit order. Can't switch mode",1,self.pair)
self.pause = False
self.status_dict["pause_reason"] = ""
return 1
if self.so["id"]!="":
self.broker.cancel_order(self.so["id"],self.pair)
#Save the old take profit order info for later and saves it to a different file in case of trader crash
self.broker.logger.log_this("Saving state in status_dict",2,self.pair)
self.status_dict["old_long"] = {"tp_price": self.tp_order["price"],
"tp_amount": self.tp_order["amount"],
"quote_spent": self.total_amount_of_quote,
"fees_paid_in_quote": self.fees_paid_in_quote,
"datetime": time.strftime("[%Y/%m/%d %H:%M:%S]")
}
try:
with open(f"status/{self.base}{self.quote}.oldlong","w") as s:
s.write(json.dumps(self.status_dict["old_long"],indent=4))
except Exception as e:
self.broker.logger.log_this(f"Exception while saving old_long file: {e}",1,self.pair)
#Modify config file accordingly
self.broker.logger.log_this("Modifying config file and saving a backup",2,self.pair)
try:
with open(f"configs/{self.base}{self.quote}.bak","w") as c:
c.write(json.dumps(old_config, indent=4))
old_config["is_short"] = True
old_config["order_size"] = optimal_order_size #Now calculated on-the-fly at the start of every deal
old_config["no_of_safety_orders"] = amount_of_so
#4. Write the config file
with open(f"configs/{self.base}{self.quote}.json","w") as c:
c.write(json.dumps(old_config, indent=4))
self.broker.logger.log_this("Config file updated",2,self.pair)
except Exception as e:
self.broker.logger.log_this(f"Error. Can't write the config file. Exception: {e}",1,self.pair)
#self.pause = False
return 1
self.stop_when_profit = False
self.is_short = True
self.broker.logger.log_this("Done configuring. Starting bot...",2,self.pair)
return 0
def switch_to_long(self, ignore_old_long: bool = False, already_received_quote: float = 0) -> int:
'''
Takes a short bot and changes the mode to long.
Only does it if the current bot was previously a long one.
'''
#Check if it's not already long
if not self.is_short:
self.broker.logger.log_this("Can't switch a long trader to long, there's nothing to do",1,self.pair)
return 1
#Check if the orders are OK
if self.tp_order is None:
self.broker.logger.log_this("Take profit order is None, can't switch to short",1,self.pair)
return 1
if self.so is None:
self.broker.logger.log_this("Safety order is None, can't switch to short",1,self.pair)
return 1
#Send Telegram message
self.broker.logger.log_this("Attempting to switch to long bot",0,self.pair)
if not ignore_old_long and "old_long" not in self.status_dict:
self.broker.logger.log_this("Can't find old long info on status_dict, searching for oldlong file",1,self.pair)
try:
with open(f"status/{self.base}{self.quote}.oldlong") as f:
self.status_dict["old_long"] = json.load(f)
except Exception as e:
#self.write_to_log(time.strftime(f"[%Y/%m/%d %H:%M:%S] | {self.pair} | Can't find old long file"))
self.broker.logger.log_this(f"Can't file oldlong file. Exception: {e}",1,self.pair)
return 1
#Cancel open orders
try:
self.broker.cancel_order(self.so["id"],self.pair)
except Exception as e:
self.broker.logger.log_this(f"Error in cancel_order while cancelling safety order. Exception: {e}",1,self.pair)
try:
self.broker.cancel_order(self.tp_order["id"],self.pair)
except Exception as e:
self.broker.logger.log_this(f"Error in cancel_order while cancelling take profit order. Exception: {e}",1,self.pair)
#Liquidate base
self.liquidate_base(ignore_profits=ignore_old_long, already_received_quote=already_received_quote)
#switch config files and set self.is_short to False
try:
with open(f"configs/{self.base}{self.quote}.bak") as c:
old_config = json.load(c)
with open(f"configs/{self.base}{self.quote}.json","w") as c:
c.write(json.dumps(old_config, indent=4))
self.is_short = False
except Exception as e:
self.broker.logger.log_this(f"Exception in switch_to_long while switching config files: {e}",1,self.pair)
return 1
#Remove old_long file (if it exists)
if os.path.isfile(f"status/{self.base}{self.quote}.oldlong"):
self.broker.logger.log_this("Removing old_long file...",2,self.pair)
os.remove(f"status/{self.base}{self.quote}.oldlong")
#Set up a few variables
self.fees_paid_in_quote = 0
self.fees_paid_in_base = 0
self.tp_order = self.broker.get_empty_order()
self.so = self.broker.get_empty_order()
self.safety_price_table = [0]
self.safety_order_index = 0
#Disabling autoswitch
#self.config_dict["autoswitch"] = False
#Done. Ready for start_trader
return 0
def liquidate_base(self, ignore_profits: bool = True, already_received_quote: float = 0) -> int:
'''
Fetches the amount of free base on the exchange, sells the entirety of the amount and calculates profits
Sends the Telegram message and writes the profits to disk
'''
#Find out the amount of free base
free_base = self.fetch_free_base()
if free_base is None:
self.broker.logger.log_this("Can't fetch free base",1,self.pair)
return 1
#send market order selling the total amount of base in the last take profit short order
order = self.broker.new_market_order(self.pair,free_base,"sell")
tries = self.broker.get_retries()
while True:
time.sleep(self.broker.get_wait_time())
market_tp_order = self.broker.get_order(order["id"],self.pair)
if market_tp_order["status"]=="closed":
_, fees_paid = self.parse_fees(market_tp_order)
break
tries-=1
if tries==0:
self.broker.logger.log_this("Liquidation order not filling. Skipping base liquidation",1,self.pair)
return 1
#calculate profits
if not ignore_profits:
profit = already_received_quote + market_tp_order["cost"] - self.status_dict["old_long"]["quote_spent"] - self.status_dict["old_long"]["fees_paid_in_quote"] - fees_paid
#self.status_dict["acc_profit"] += profit
#Add profits to file and send telegram notifying profits
self.profit_to_file(profit,market_tp_order["id"]) #This is not used anymore, but it's still here since it does not take almost any disk space and/or CPU time.
self.profit_to_db(profit,market_tp_order["id"],self.broker.get_write_order_history())
self.broker.logger.log_this(f"Switch successful. Profit: {round(profit,2)} {self.quote}",0,self.pair)
self.broker.logger.log_this(f"Sell price: {market_tp_order['price']} {self.quote}",0,self.pair)
self.broker.logger.log_this(f"Order ID: {market_tp_order['id']}",0,self.pair)
return 0
def take_profit_routine(self, filled_order: dict) -> int:
'''
When profit is reached, this method is called to handle the profit calculations, the closing of orders,
the reporting and the restart of the trader.
'''
self.pause = True #To stop the main thread to iterate through this bot's orders (just in case)
self.status_dict["pause_reason"] = "take_profit_routine - order handling" #start_trader will set this flag to False again once it starts
#Let's do some type checking first
if self.tp_order is None:
self.status_dict["pause_reason"] = time.strftime(f"[%Y/%m/%d %H:%M:%S] | {self.pair} | TP order is None")
self.broker.logger.log_this("Error. Take profit order is None, pair will be restarted",0,self.pair)
self.write_status_file(is_backup=True)
self.restart = True
return 1
if self.so is None:
self.status_dict["pause_reason"] = time.strftime(f"[%Y/%m/%d %H:%M:%S] | {self.pair} | Safety order is None")
self.broker.logger.log_this("Error. Safety order is None",1,self.pair)
self.so = self.broker.get_empty_order()
#Save the order
self.status_dict["deal_order_history"].append(filled_order)
# Cancel the current safety order (first check if there is something to cancel)
already_counted = False
if self.so["id"]=="":
self.broker.logger.log_this("There is no safety order to cancel",2,self.pair)
elif self.broker.cancel_order(self.so["id"],self.pair)==1:
self.broker.logger.log_this("Old safety order probably filled. Can't cancel.",1,self.pair)
closed_order = self.broker.get_order(self.so["id"],self.pair)
if closed_order!=self.broker.get_empty_order() and closed_order["status"]=="closed":
self.total_amount_of_base = self.total_amount_of_base + closed_order["filled"]
self.total_amount_of_quote = self.total_amount_of_quote + closed_order["cost"]
#Save the order
self.status_dict["deal_order_history"].append(closed_order)
already_counted = True
#IF NOT SHORT - Check if the SO was partially filled. If so, add the amounts to total_amount_of_base and total_amount_of_quote
#Suggestion: Would it be feasible to send a market sell order for the amount of base bought on the old safety order?
#Or is it better to leave this amount of change to the cleanup routine?
if not self.is_short and self.so["id"]!="" and not already_counted:
old_so_order = self.broker.get_order(self.so["id"],self.pair)
if old_so_order["filled"]>0:
self.broker.logger.log_this(f"Old safety order is partially filled, ID: {old_so_order['id']}",1,self.pair)
self.status_dict["deal_order_history"].append(old_so_order)
#Uncomment the next two lines if you do not want to ignore the partial fill.
#Keep them commented out if you want to send a market sell order.
#self.total_amount_of_base = self.total_amount_of_base + old_so_order["filled"]
#self.total_amount_of_quote = self.total_amount_of_quote + old_so_order["cost"]
#Hypothetical market order code below:
#If there is not enough base for an order, we'll accumulate it and let the cleanup routine deal with that.
# floor_amount = self.broker.get_min_base_size(self.pair)
# if floor_amount is not None and old_so_order["filled"]>floor_amount:
# self.broker.logger.log_this(f"Sending sell order for partially filled funds: {old_so_order["filled"]} {self.base}",1,self.pair)
# loose_change_order = broker.new_market_order(self.pair,old_so_order["filled"],"sell",amount_in_base=True)
# retries = self.broker.get_retries()
# while retries>0:
# time.sleep(self.broker.get_wait_time())
# order_to_fill = self.broker.get_order(loose_change_order["id"],self.pair)
# if order_to_fill["status"]=="closed" and order_to_fill["filled"]>0:
# profit_with_fees = order_to_fill["filled"]-old_so_order["cost"]-self.parse_fees(order_to_fill)[1]
# self.broker.logger.log_this(f"Trader closed a loose change deal. Profit: {profit_with_fees} {self.quote}",0,self.pair)
# if profit_with_fees>0:
# self.profit_to_file(profit_with_fees,order_to_fill["id"])
# retries-=1
if not self.broker.check_for_duplicate_profit_in_db(filled_order):
self.status_dict["pause_reason"] = "calculating profit"
# Calculate the profit
if self.is_short:
profit = self.total_amount_of_quote-filled_order["cost"]-self.fees_paid_in_quote-self.parse_fees(filled_order)[1]
else:
profit = filled_order["cost"]-self.total_amount_of_quote-self.fees_paid_in_quote-self.parse_fees(filled_order)[1]
if "partial_profit" in self.status_dict:
profit+=self.status_dict["partial_profit"]
#Checks if some base was left over.
base_profit = max(self.total_amount_of_base-filled_order["filled"],0) #To avoid negative numbers in base_profit
# Write the profit to file and send telegram message
if profit>0: #Negative profits are not saved because the cleanup takes care of the unsold base currency (the notorious small change issue that plagues some exchanges)
self.profit_to_file(profit,filled_order["id"])
self.profit_to_db(profit,filled_order["id"],self.broker.get_write_order_history())
else: #For logging purposes
self.broker.logger.log_this(f"NEGATIVE PROFIT - Total amount of base: {self.total_amount_of_base}, base in the order: {filled_order['amount']}, base filled: {filled_order['filled']}, base 'profit': {base_profit}",1,self.pair)
self.telegram_bot_sendprofit(profit,filled_order,base_profit=base_profit)
# Print profit message on screen
extra = ' and {:.4f}'.format(base_profit) + f" {self.base}" if base_profit>0 else ""
self.broker.logger.log_this(f"Trader closed a deal. Profit: {'{:.4f}'.format(profit)} {self.quote}{extra}",2,self.pair)
self.broker.logger.log_this(f"Fill price: {filled_order['price']} {self.quote}",2,self.pair)
self.broker.logger.log_this(f"Safety orders triggered: {self.safety_order_index-1}",2,self.pair)
self.status_dict["pause_reason"] = "take_profit_routine - check time limit"
#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"]):
self.stop_when_profit = True
self.status_dict["pause_reason"] = "take_profit_routine - if stop_when_profit"
if self.stop_when_profit: #Signal to stop when trade is closed
self.broker.logger.log_this("Pair shutting down. So long and thanks for all the fish",0,self.pair)
self.quit = True
return 1
# Clear variables and reload config_dict (Only reloading the config file is needed.)
self.status_dict["pause_reason"] = "take_profit_routine - var cleanup"
self.config_dict = self.reload_config_dict()
if self.check_slippage:
self.broker.logger.log_this("Checking slippage...",2,self.pair)
price_to_compare = self.broker.get_top_bid_price(self.pair) if self.is_short else self.broker.get_top_ask_price(self.pair)
if abs(filled_order["price"]-price_to_compare)/filled_order["price"]>self.broker.get_slippage_default_threshold():
self.broker.logger.log_this(f"Slippage threshold exceeded, waiting for cooldown and restarting trader",1,self.pair)
time.sleep(self.broker.get_wait_time()*self.broker.get_cooldown_multiplier())
#The trader is restarted by the instance instead of by itself to allow a couple of more seconds for the price to return to normal.
#This could also be the default behavior.
self.pause = False
self.restart = True
return 1
elif self.check_orderbook_depth(self.broker.get_slippage_default_threshold(),self.config_dict["order_size"],filled_order["price"]):
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())
self.pause = False
self.restart = True
return 1
#Possible restart errors
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"}
#Restarting the trader
self.status_dict["pause_reason"] = "take_profit_routine - restart_trader call"
restart_trader = self.start_trader()
self.status_dict["pause_reason"] = "take_profit_routine - restart_trader call - start_trader() called"
#retries = self.broker.get_retries()
if restart_trader in restart_errors.keys():
self.pause = False
self.restart = True
self.write_status_file(is_backup=True)
self.broker.logger.log_this(restart_errors[restart_trader],1,self.pair)
return restart_trader
def sum_filled_amounts(self, order: dict) -> int:
'''
Adds the amount filled and the cost of an order to the totals.
'''
# Add up the last order fees
new_fees_base = 0 #For type checking compliance
new_fees_quote = 0
# Update the total_amount_of_quote and total_amount_of_base variables
if order["id"]!="": #Necessary check when adding SOs
new_fees_base,new_fees_quote = self.parse_fees(order)
self.fees_paid_in_quote += new_fees_quote
self.total_amount_of_base = self.total_amount_of_base + order["filled"] - new_fees_base
self.total_amount_of_quote = self.total_amount_of_quote + order["cost"]
# Done
return 0
def new_so_routine(self, filled_order: dict, send_new_so: bool) -> int:
'''
Handles all the bureaucracy prior and after sending a new safety order
:param filled_order: dict
:param send_new_so: bool
:return: 0 OK, 1 not enough funds, if can't cancel old TP, can't send new TP
'''
#Let's do some type checking first
if self.tp_order is None:
self.broker.logger.log_this("Take profit order is None, can't send a new safety order",1,self.pair)
return 1
if self.so is None:
self.broker.logger.log_this("Safety order is None, can't send a new safety order",1,self.pair)
return 1
self.pause = True
self.status_dict["pause_reason"] = "new_so_routine"
# Save the order
self.status_dict["deal_order_history"].append(filled_order)
# Cancel the tp order
if self.broker.cancel_order(self.tp_order["id"],self.pair)==1:
error_string = f"{self.pair} | {self.tp_order['id']} | Old TP order probably filled. Can't cancel. This trader should be restarted"
self.broker.logger.log_this(f"Old take profit order is probably filled, can't cancel. This trader should be restarted. Order ID: {self.tp_order['id']}",1,self.pair)
self.status_dict["pause_reason"] = error_string
return 2
# Add the amount filled in the last safety order to the totals
self.sum_filled_amounts(filled_order)
#Cooldown
time.sleep(self.broker.get_wait_before_new_safety_order())
# Send the new safety order. If all expected safety orders are filled, it assigns an empty order to self.so
if send_new_so:
self.broker.logger.log_this("Sending a new safety order",2,self.pair)
if self.send_new_safety_order(self.status_dict["order_size"])==1:
error_string = "Problems sending the new safety order. Maybe not enough funds?"
self.broker.logger.log_this(error_string,1,self.pair)
self.status_dict["pause_reason"] = error_string
self.sum_filled_amounts(filled_order)
#self.so = self.broker.get_empty_order()
return 1
else:
self.so = self.broker.get_empty_order()
self.safety_order_index+=1
# Check if the old tp order was partially filled. If so, update the previous two variables accordingly
# TODO: This should also be taken into account for the profit calculation
# Do the partial profit calculation and save it for later
old_tp_order = self.broker.get_order(self.tp_order["id"],self.pair)
if old_tp_order["filled"]>0:
self.broker.logger.log_this(f"Old take profit order is partially filled, id {old_tp_order['id']}",1,self.pair)
self.status_dict["deal_order_history"].append(old_tp_order)
#self.total_amount_of_base = old_tp_order["remaining"]
# Partial profit calculation
#if not self.is_short:
# current_deal_price = self.total_amount_of_base/self.total_amount_of_quote
# self.status_dict["partial_profit"] = old_tp_order["cost"]-(old_tp_order["filled"]*current_deal_price)-self.parse.fees(old_tp_order)[1]
# self.update_status(True)
#
# Maybe here we shouldn't substract fees yet, but add them up to the check.
#
self.total_amount_of_base = self.total_amount_of_base - old_tp_order["filled"] - self.parse_fees(old_tp_order)[0]
self.total_amount_of_quote = self.total_amount_of_quote - old_tp_order["cost"]# + self.parse_fees(old_tp_order)[1]
self.fees_paid_in_quote += self.parse_fees(old_tp_order)[1]
#self.fees_paid_in_base += self.parse_fees(old_tp_order)[0]
#Cooldown
time.sleep(self.broker.get_wait_time())
# Send the new take profit order
if self.send_new_tp_order()==1:
error_string = "Problems sending the new take profit order"
self.broker.logger.log_this("Problems sending the new take profit order",1,self.pair)
self.status_dict["pause_reason"] = error_string
return 3
# Update the status_dict and that's it
self.update_status(True)
self.pause = False
self.status_dict["pause_reason"] = ""
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 "old_long" in self.status_dict:
price_exceeds = self.status_dict["price"]>float(self.status_dict["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_dict["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']}"
self.broker.logger.log_this(message,0,self.pair)
return 0
def check_orderbook_depth(self, threshold: float, order_size: float, old_price: float = 0, size_in_quote = True) -> bool:
'''
Checks if the orderbook depth exceed the slippage threshold. Returns True if threshold is exceeded, False otherwise.
Parameters:
threshold (float): The threshold for the orderbook depth.
order_size (float): The size of the order to check..
old_price (float): The old price of the order.
size_in_quote (bool): If True, the order size is in quote currency. If False, the order size is in base currency.
Returns:
bool: True if the orderbook depth exceeds the threshold, False otherwise.
'''
if self.is_short: #Do not check for slippage in short traders (Pending to be implemented)
return False
order_book = self.broker.get_order_book(self.pair,no_retries=True)
if order_book=={}:
self.broker.logger.log_this("Can't fetch orderbook",1,self.pair)
return False
suma = 0
try:
mid_price = old_price if old_price!=0 else (order_book["asks"][0][0]+order_book["bids"][0][0])/2
if size_in_quote:
amount = (order_size/mid_price)*2 #Extra margin to avoid surprises.
else:
amount = order_size*2
first_price = order_book["asks"][0][0]
for x in order_book["asks"]:
suma += x[1]
if suma>=amount:
last_price = x[0]
break
if last_price-first_price>first_price*threshold:
#Threshold exceeded
return True
return False
except Exception as e:
self.broker.logger.log_this(f"Exception in check_orderbook_depth: {e}",1,self.pair)
return False
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.
'''
#It does not even try if it receives an empty list or the worker is paused
if open_orders==[] or self.pause:
self.update_status(False)
return 0
#Checks if the orders are valid
if self.tp_order is None:
self.broker.logger.log_this("Take profit order is None",1,self.pair)
return 1
if self.so is None:
#Attempt to reload the safety order from the status dict?
self.broker.logger.log_this("Safety order is None",1,self.pair)
self.so = self.broker.get_empty_order()
return 1
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.cancel_order(self.so["id"],self.pair)
if self.config_dict["attempt_restart"]:
self.write_status_file(is_backup=True)
self.restart = True
self.broker.logger.log_this("Raising restart flag: take profit order missing, trader will be restarted",0,self.pair)
else:
self.broker.logger.log_this("Take profit order missing. Trader restart disabled.",2,self.pair)
return 1
#Checks if the take profit order is filled
if self.tp_order["id"] not in open_orders:
tp_status = self.broker.get_order(self.tp_order["id"],self.pair)
if tp_status["status"]=="closed":
if tp_status["filled"]>0:
return self.take_profit_routine(tp_status)
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
self.broker.cancel_order(self.so["id"],self.pair)
if self.config_dict["attempt_restart"]:
self.write_status_file(is_backup=True)
self.restart = True
self.broker.logger.log_this("Take profit order closed but not filled, trader will be restarted.",0,self.pair)
else:
self.broker.logger.log_this("Take profit order closed but not filled, trader restart disabled.",1,self.pair)
return 1
elif tp_status["status"]=="canceled":
#TODO: Here, if the safety order is still open, we could resend the tp order.
if self.config_dict["attempt_restart"]:
self.broker.logger.log_this("Take profit order canceled. Restarting the bot.",1,self.pair)
self.write_status_file(is_backup=True)
self.restart = True
else:
self.broker.logger.log_this("Take profit order canceled. Trader restart disabled.",1,self.pair)
return 1
elif tp_status["status"]=="":
self.broker.logger.log_this(f"Take profit order search returned empty order. Order ID: {tp_status['id']}",1,self.pair)
return 1
# Check if safety order is filled
if self.so["id"] not in open_orders and self.safety_order_index<=self.config_dict["no_of_safety_orders"]:
#so_status = self.so
#if self.so["id"]!="":
so_status = self.broker.get_order(self.so["id"],self.pair)
tp_order_status = self.broker.get_order(self.tp_order["id"],self.pair)
#Now we check 2 things:
#1. That the prior safety order status is indeed closed (or canceled)
#2. That the take profit order is still opened (if not, the deal must have closed, both orders closing is quite common in high variance scenarios)
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.
#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"]:
self.switch_to_short()
self.write_status_file(is_backup=True)
self.restart = True
return 0
a = self.new_so_routine(so_status,self.safety_order_index<self.config_dict["no_of_safety_orders"])
#0 OK, 1 not enough funds, 2 can't cancel old TP, 3 can't send new TP
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)
#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
self.config_dict["no_of_safety_orders"] = self.safety_order_index
return 1
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.pause = False
self.status_dict["pause_reason"] = ""
if self.config_dict["attempt_restart"]:
self.write_status_file(is_backup=True)
self.restart = True
return 1
elif a==3:
#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)
if self.config_dict["attempt_restart"]:
self.write_status_file(is_backup=True)
self.restart = True
return 1
#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"]:
self.check_old_long_price()
self.status_dict["pause_reason"] = "check for autoswitch"
#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 selling the base currency left at the current market price plus the quote already received turns out to be more than the amount of the old_long,
# it means that we already are in profit territory, switch back to long.
old_target = self.status_dict["old_long"]["tp_price"]*self.status_dict["old_long"]["tp_amount"]
base_left = self.status_dict["old_long"]["tp_amount"]-self.status_dict["base_bought"]
if (base_left*self.status_dict["price"])+self.status_dict["quote_spent"]>=old_target:
#Sell all base (market), report the profits and restart the trader
self.status_dict["pause_reason"] = "automatic_switch"
self.switch_to_long(already_received_quote=self.status_dict["quote_spent"])
self.restart = True
return 1
#Render status line(s)
self.status_dict["status_string"] = self.generate_status_strings()
#Wrap up
self.status_dict["deal_uptime"]=int(time.time()) - self.deal_start_time
self.status_dict["total_uptime"]=int(time.time()) - self.start_time
self.update_status(False)
self.last_time_seen = int(time.time())
return 0
def get_tp_level(self, order_index: int = 0) -> float:
'''
Returns the correct take profit percentage, according to the strategy (config_dict["tp_mode"]):
0. Fixed percentage
1. Variable percentage (+0.5% to -0.5% of the fixed percentage)
2. Custom percentage table
3. Linear percentage table
'''
tp_level = 1
boost_percentage = 0
#BOOST ROUTINE: If the trader closed certain amount of deals within the last t timespan, raise the take profit level by x%
#Default values
boosted_deals_range = 4
boosted_time_range = 3600
boosted_amount = .01
#Load config values (if present)
if "boosted_deals_range" in self.config_dict:
boosted_deals_range = self.config_dict["boosted_deals_range"]
if "boosted_time_range" in self.config_dict:
boosted_time_range = self.config_dict["boosted_time_range"]
if "boosted_amount" in self.config_dict:
boosted_amount = self.config_dict["boosted_amount"]
self.is_boosted = False
if not self.is_short:
last_deals_timestamps = self.broker.return_last_n_deals_timestamps(self.pair,boosted_deals_range)
if len(last_deals_timestamps)==boosted_deals_range and all(item >= time.time()-boosted_time_range for item in last_deals_timestamps):
self.is_boosted = True
boost_percentage = boosted_amount
if self.is_short or self.config_dict["tp_mode"]==0: #Fixed take profit percentage
tp_level = self.config_dict["tp_level"]
elif self.config_dict["tp_mode"]==1: #Variable percentage
limit = self.config_dict["no_of_safety_orders"]/3
if order_index<=1:
tp_level = self.config_dict["tp_level"]+0.005
elif order_index<=limit:
tp_level = self.config_dict["tp_level"]
elif limit<=order_index<=limit*2:
tp_level = self.config_dict["tp_level"]-0.0025
else:
tp_level = self.config_dict["tp_level"]-0.005
elif self.config_dict["tp_mode"]==2:
if ["tp_table"] in self.config_dict:
if len(self.config_dict["tp_table"])>=order_index:
tp_level = self.config_dict["tp_table"][order_index] #Custom percentage table
tp_level = self.config_dict["tp_table"][-1]
tp_level = self.config_dict["tp_level"]
elif self.config_dict["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"])
tp_level = profit_table[-1]
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.
return tp_level+boost_percentage
def seconds_to_time(self, total_seconds: float) -> str:
'''
Returns a D:HH:MM:SS representation of total_seconds
'''
return f"{int(total_seconds / 86400)}:" + '%02d:%02d:%02d' % (int(total_seconds % 86400 / 3600), int(total_seconds % 3600 / 60), int(total_seconds % 60))
def adjust_base(self):
time.sleep(self.broker.get_wait_time())
new_balance = self.broker.get_coins_balance()
if bool(new_balance):
self.broker.logger.log_this(f"Adjusting base amount to {new_balance['free'][self.base]}, total balance: {new_balance['total'][self.base]}",1,self.pair)
return new_balance["free"][self.base]
return None
def send_new_tp_order(self) -> int:
'''
Calculates the correct take profit price and sends the order to the exchange
'''
tries = self.broker.get_retries()
while tries>0:
if self.total_amount_of_base==0:
self.broker.logger.log_this("Amount of base equals 0, can't send take profit order",1,self.pair)
return 1
if self.is_short:
self.take_profit_price = self.total_amount_of_quote/self.total_amount_of_base*(1-(self.get_tp_level(self.safety_order_index)-1))
self.tp_order = self.broker.new_limit_order(self.pair,self.total_amount_of_base,"buy",self.take_profit_price)
else:
self.take_profit_price = self.total_amount_of_quote/self.total_amount_of_base*self.get_tp_level(self.safety_order_index)
self.tp_order = self.broker.new_limit_order(self.pair,self.total_amount_of_base,"sell",self.take_profit_price)
if self.tp_order==1: #This means that there was a miscalculation of base currency amount, let's correct it.
if self.is_short: #If in short mode, we don't recalculate anything.
return 1
adjusted = self.adjust_base()
if adjusted is not None:
self.total_amount_of_base = adjusted
self.tp_order = None #Just to be able to iterate
if self.tp_order not in [None,self.broker.get_empty_order()]:
return 0
tries-=1
time.sleep(self.broker.get_wait_time())
self.broker.logger.log_this("Problems sending take profit order",1,self.pair)
return 1
def profit_to_file(self, amount: float, orderid: str) -> int:
'''
Saves the profit to the corresponding profit file
DEPRECATED. Use profit_to_db instead.
'''
try:
with open(self.profit_filename,"a") as profit_file:
profit_writer = csv.writer(profit_file, delimiter=",")
profit_writer.writerow([time.strftime("%Y-%m-%d"), amount, orderid])
except Exception as e:
self.broker.logger.log_this(f"Exception in profit_to_file: {e}",1,self.pair)
return 0
def profit_to_db(self, amount: float, orderid: str, write_deal_order_history: bool = False) -> int:
'''
Saves the profit to the db in the format (pair,timestamp,profit,exchange_name,order_id,order_history)
'''
retries = self.broker.get_retries()
while retries>0:
try:
order_history = json.dumps(self.status_dict["deal_order_history"]) if write_deal_order_history else ""
dataset = (time.time(),self.pair,amount,self.broker.get_exchange_name(),str(orderid),order_history)
return self.broker.write_profit_to_db(dataset)
except Exception as e:
self.broker.logger.log_this(f"Exception while writing profit: {e}",1,self.pair)
retries-=1
time.sleep(self.broker.get_wait_time())
return 1
def send_new_safety_order(self, size: float) -> int:
'''
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
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])
else:
new_order = self.broker.new_limit_order(self.pair,so_size/self.safety_price_table[self.safety_order_index+1],"buy",self.safety_price_table[self.safety_order_index+1])
if new_order==1:
self.so = self.broker.get_empty_order()
self.broker.logger.log_this("Not enough balance to send a new safety order",1,self.pair)
#elif new_order in [None,self.broker.get_empty_order()] #MAYUBE THIS CONDITIONAL IS BETTER
elif new_order is None:
self.so = None
return 1
else:
self.so = new_order
self.safety_order_index+=1
return 0
def telegram_bot_sendprofit(self,profit,order,base_profit=0) -> int:
'''
Sends the Telegram notification when profit is met
'''
try:
extra = f" and {round(base_profit,6)} {self.base}" if base_profit>0 else ""
message = f"{self.pair} closed a {'short' if self.is_short else 'long'} trade.\nProfit: {round(profit,6)} {self.quote}{extra}\nSafety orders triggered: {self.safety_order_index-1}\nTake profit price: {order['price']} {self.quote}\nTrade size: {round(order['cost'],2)} {self.quote}\nDeal uptime: {self.seconds_to_time(self.status_dict['deal_uptime'])}\nOrder ID: {order['id']}\nExchange: {self.broker.get_exchange_name().capitalize()}\n"
self.broker.logger.send_tg_message(message)
return 0
except Exception as e:
self.broker.logger.log_this(f"Exception in telegram_bot_sendprofit: {e}",1,self.pair)
return 1
def gib_so_size(self, starting_order_size: float, so_number: int, scaling_factor: float) -> float:
'''
Returns the correct safety order size depending on the number
Scaling factor example: 5% = 0.0105
'''
order_size = starting_order_size
for _ in range(so_number):
order_size = order_size*scaling_factor*100
return order_size
def clip_value(self,value,lower_limit,upper_limit):
'''
Clips a value to a given range
'''
if value<lower_limit:
return lower_limit
if value>upper_limit:
return upper_limit
return value
def calculate_safety_prices(self, start_price: float, no_of_safety_orders: int, safety_order_deviance: float) -> list:
'''
Generates a table of safety order's prices
'''
safety_price_table = [start_price]
if self.config_dict["dynamic_so_deviance"]:# and no_of_safety_orders>=30:
#if self.config_dict["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)
#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:
deviance_factor = safety_order_deviance*self.clip_value(self.config_dict["bias"],-.99,.99)
so_deviance_table = self.linear_space(safety_order_deviance+deviance_factor,safety_order_deviance-deviance_factor,no_of_safety_orders)
else:
#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.extend([so_deviance_table[-1]]*2) #This extra entries are needed in the next for loop
else:
so_deviance_table = [safety_order_deviance]*(no_of_safety_orders+2)
multiplier = -1 if self.is_short else 1
for y in range(1, no_of_safety_orders+2): #+2 instead of the expected +1 because of a bug when updating the status dict. It could be any value, if we add SOs the table is recalculated anyway
safety_price_table.append(safety_price_table[-1]-multiplier*(safety_price_table[0]*so_deviance_table[y-1]/100))
return safety_price_table
def linear_space(self, start: float, stop: float, amount: int) -> list:
'''
Numpy's linspace local implementation.
Implemented here because:
- This is the only piece of code needed from Numpy
- Only executed when calculating the safety order table, so there's no need for outstanding performance.
'''
result = [start]
if amount in [0,1]:
return result
step = (start-stop)/(amount-1)
for _ in range(1,amount):
result.append(result[-1]-step)
return result
def switch_quote_currency(self, new_quote: str) -> int:
'''
Replaces the open orders with new updated orders, updates the config file and the status.
'''
#First let's check if the market exists
market = self.broker.fetch_market(f"{self.base}/{new_quote}")
if market is None:
self.broker.logger.log_this("Market might not exist",1,self.pair)
return 1
if "active" in market and not market["active"]:
self.broker.logger.log_this("Market is closed",1,self.pair)
return 1
if self.tp_order is None:
self.broker.logger.log_this("Take profit order is None",1,self.pair)
return 1
if self.so is None:
self.broker.logger.log_this("Safety order is None",1,self.pair)
return 1
#Replace the current take profit order with a new one with new quote currency
self.broker.logger.log_this("Replacing take profit order",2,self.pair)
self.tp_order = self.quote_currency_replace_order(self.tp_order,new_quote)
if self.tp_order==self.broker.get_empty_order():
return 1
#Replace the current safety order (if any) with a new one with the new quote currency
if self.so!=self.broker.get_empty_order():
self.broker.logger.log_this("Replacing safety order",2,self.pair)
self.so = self.quote_currency_replace_order(self.so,new_quote)
if self.so==self.broker.get_empty_order():
return 1
#Calls switch_quote_currency_config
self.broker.logger.log_this("Modifying config file",2,self.pair)
self.quote_currency_switch_configs(new_quote)
#Updates status_dict
self.broker.logger.log_this("Updating status file",2,self.pair)
self.update_status(True)
#Done
self.broker.logger.log_this("Quote swap successful",2,self.pair)
return 0
def quote_currency_replace_order(self, old_order: dict, new_quote: str) -> dict:
'''
Cancels the order and returns the new updated order
'''
#Cancels the old order
if self.broker.cancel_order(old_order["id"],self.pair)==1:
self.broker.logger.log_this(f"Can't cancel old order {old_order['id']}",1,self.pair)
return self.broker.get_empty_order()
#Sends the new order
return self.broker.new_limit_order(f"{self.base}/{new_quote}",old_order["amount"],old_order["side"],old_order["price"])
def quote_currency_switch_configs(self, new_quote: str) -> int:
'''
Updates the broker config file, changes all the variables and writes the new pair config file
'''
#Change broker config file
self.broker.remove_pair_from_config(f"{self.base}{self.quote}")
self.broker.add_pair_to_config(f"{self.base}{new_quote}")
if self.broker.rewrite_config_file()==1:
#Error writing broker config file, undoing changes
self.broker.logger.log_this("Error writing new broker config file",1,self.pair)
self.quote_currency_undo_changes(new_quote,self.quote,False)
return 1
#Change pair-related variables
old_quote = self.quote
self.quote = new_quote
self.config_dict["pair"] = f"{self.base}/{self.quote}"
self.profit_filename = f"profits/{self.base}{self.quote}.profits"
self.log_filename = f"logs/{self.base}{self.quote}.log"
self.pair = self.config_dict["pair"]
#If there is an old_long file, also copy it
if self.is_short and "old_long" in self.status_dict:
try:
with open(f"status/{self.base}{self.quote}.oldlong","w") as c:
c.write(json.dumps(self.status_dict["old_long"], indent=4))
except Exception as e:
self.broker.logger.log_this(f"Exception while writing new old_long file: {e}",1,self.pair)
#Write the new config file
try:
with open(f"configs/{self.base}{self.quote}.json","w") as c:
c.write(json.dumps(self.config_dict, indent=4))
except Exception as e:
self.broker.logger.log_this(f"Exception while writing new trader config file: {e}",1,self.pair)
#Undoing changes
self.quote_currency_undo_changes(new_quote,old_quote,True)
return 1
#Done
return 0
def quote_currency_undo_changes(self, new_quote: str, old_quote: str, write_broker_file: bool = False) -> int:
'''
Revert changes made by switch_quote_currency()
'''
#Switching variables
self.quote = old_quote
self.broker.remove_pair_from_config(f"{self.base}{new_quote}")
self.broker.add_pair_to_config(f"{self.base}{self.quote}")
self.config_dict["pair"] = f"{self.base}/{self.quote}"
self.profit_filename = f"profits/{self.base}{self.quote}.profits"
self.log_filename = f"logs/{self.base}{self.quote}.log"
self.pair = self.config_dict["pair"]
#Writing config file
if write_broker_file and self.broker.rewrite_config_file()==1:
self.broker.logger.log_this("Error in quote_currency_undo_changed: error writing new broker config file",1,self.pair)
#Done
return 0
def generate_status_strings(self) -> str:
'''
Returns the status string properly formatted for screen output
'''
yellow = "\033[0;33;40m"
green = "\033[0;32;40m"
red = "\033[0;31;40m"
blue = "\033[0;34;40m"
cyan = "\033[0;36;40m"
bright_white = "\033[0;97;40m"
bright_green = "\033[0;92;40m"
white = "\033[0;37;40m"
def draw_line(price,min_value,max_value,break_even):
'''
It draws the progress bar according to the inputs:
* If the price is bigger or equal to the break even price, the line's color is green
* If it's lower, the line's color is red
* All the way to the left, new safety order
* All the way to the right, profit!
'''
try:
value = int(((price-min_value)/(max_value-min_value))*80)
except Exception as e:
self.broker.logger.log_this(f"{e}")
value = 1
if min_value<max_value:
color = green if price>=break_even else red
else:
color = red if price>=break_even else green
return f"{color}{'='*value}{white}{'='*max(0,(80-value))}"[:100]
decimals = 11
low_percentage = 1
mid_percentage = 10
high_percentage = 20
safety_order_string = f"{self.status_dict['so_amount']-1}/{self.config_dict['no_of_safety_orders']}".rjust(5)
#Check if necessary
low_price = 0
mid_price = 0
high_price = 0
if self.status_dict["next_so_price"] is not None:
low_price = self.status_dict["next_so_price"]
if self.status_dict["price"] is not None:
mid_price = self.status_dict["price"]
if self.status_dict["take_profit_price"] is not None:
high_price = self.status_dict["take_profit_price"]
low_boundary = '{:.20f}'.format(low_price)[:decimals].center(decimals)
mid_boundary = '{:.20f}'.format(mid_price)[:decimals].center(decimals)
high_boundary = '{:.20f}'.format(high_price)[:decimals].center(decimals)
percentage_to_profit = 100
pct_to_profit_str = "XX.XX"
if self.status_dict not in [0,None] and self.status_dict["price"]!=0:
diff = abs(self.status_dict["take_profit_price"]-self.status_dict["price"])
percentage_to_profit = diff/self.status_dict["price"]*100
#Formatting (on-screen percentage not longer than 4 digits)
pct_to_profit_str = "{:.2f}".format(percentage_to_profit)
if len(pct_to_profit_str)==4:
pct_to_profit_str = f" {pct_to_profit_str}"
elif len(pct_to_profit_str)==6:
pct_to_profit_str = pct_to_profit_str[:5]
line3 = ""
if self.total_amount_of_base!=0:
line3 = draw_line(self.status_dict["price"],self.status_dict["next_so_price"],self.status_dict["take_profit_price"],self.total_amount_of_quote/self.total_amount_of_base)
p = "*PAUSED*" if self.pause==True else ""
price_color = white
target_price_color = green
pair_color = cyan
if self.is_short:
price_color = white
pair_color = yellow
if "old_long" in self.status_dict:
if self.status_dict["price"]>self.status_dict["old_long"]["tp_price"]:
price_color = bright_green
if self.status_dict["take_profit_price"]>self.status_dict["old_long"]["tp_price"]:
target_price_color = bright_green
#Set percentage's color
pct_color = white
if percentage_to_profit<low_percentage:
pct_color = green
if percentage_to_profit>mid_percentage:
pct_color = yellow
if percentage_to_profit>high_percentage:
pct_color = red
prices = f"{red}{low_boundary}{white}|{price_color}{mid_boundary}{white}|{target_price_color}{high_boundary}{white}|{pct_color}{pct_to_profit_str}%{white}"
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:
line1 = f"{line1} | BOOSTED"
if self.config_dict["autoswitch"]:
line1 = f"{line1} | AUTO"
if self.is_short and "old_long" in self.status_dict:
try:
#When adding a trader, this line always throws an exception since status_dict["price"] is not yet populated
percentage_to_switch = (self.status_dict["old_long"]["tp_price"]-self.status_dict["price"])*100/self.status_dict["price"]
#line1 = f"{line1} {round(percentage_to_switch,2)}%"
multiplier = int(percentage_to_switch/100)+1
if multiplier>1:
line1 = f"{line1}x{multiplier}"
except ZeroDivisionError as e:
print(e)
if "stop_time" in self.config_dict and time.time()<=int(self.config_dict["stop_time"]):
line1 = f"{line1} | PROGRAMMED LAST DEAL"
if self.stop_when_profit==True:
line1 = f"{line1} | LAST DEAL"
return f"{white}{line1}\n{line3}{white}"
def load_imported_trader(self) -> int:
'''
Loads status dictionary, orders and sets up variables
'''
#Load status dict
try:
with open(f"status/{self.base}{self.quote}.status") as sd:
self.status_dict = json.load(sd)
except Exception as e:
self.broker.logger.log_this(f"Exception: Couldn't load status dict. Aborting {e}",1,self.pair)
self.quit = True
return 1
self.status_dict["pause_reason"] = "Importing trader"
#Load variables
self.total_amount_of_quote = self.status_dict["quote_spent"]
self.total_amount_of_base = self.status_dict["base_bought"]
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.take_profit_price = self.status_dict["take_profit_price"]
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_quote = self.status_dict["fees_paid_in_quote"]
self.start_price = self.status_dict["start_price"]
self.start_time = self.status_dict["start_time"]
self.deal_start_time = self.status_dict["deal_start_time"]
self.stop_when_profit = self.status_dict["stop_when_profit"]
if "is_boosted" in self.status_dict:
self.is_boosted = self.status_dict["is_boosted"]
if "deal_order_history" not in self.status_dict: #No longer needed?
self.status_dict["deal_order_history"] = []
#Load take profit order
self.tp_order = self.broker.get_order(self.status_dict["tp_order_id"],self.pair)
if self.tp_order==self.broker.get_empty_order():
self.broker.logger.log_this("Couldn't load take profit order (broker returned empty order). Aborting.",1,self.pair)
self.quit = True
return 1
#Load safety order
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"]:
#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"]
self.broker.logger.log_this("Couldn't load safety order. Aborting.",2,self.pair)
self.quit = True
return 1
#Done
self.pause = False
self.status_dict["pause_reason"] = ""
self.update_status(True)
return 0
'''
class imported_trader(trader):
def __init__(self,broker,config_file,status_file):
self.broker = broker
self.config_dict = config_file
self.status_dict = status_file
self.restart = False
self.last_time_seen = time.time()
self.pair = self.config_dict["pair"]
self.market = self.broker.fetch_market(self.pair)
self.market_load_time = int(time.time())
self.market_reload_period = 86400
if self.load_values()==1:
self.broker.logger.log_this("Error loading values",2,self.pair)
self.quit = True
self.warnings = {
"short_price_exceeds_old_long": False,
"speol_notified": False
}
self.update_status(True)
def load_values(self):
self.is_short = self.config_dict["is_short"]
self.base,self.quote = self.pair.split("/")
self.tp_order = self.broker.get_order(self.status_dict["tp_order_id"],self.pair)
if self.status_dict["so_order_id"]=="":
self.so = self.broker.get_empty_order()
else:
self.so = self.broker.get_order(self.status_dict["so_order_id"],self.pair)
self.total_amount_of_quote = self.status_dict["quote_spent"]
self.total_amount_of_base = self.status_dict["base_bought"]
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.take_profit_price = self.status_dict["take_profit_price"]
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_quote = self.status_dict["fees_paid_in_quote"]
self.start_price = self.status_dict["start_price"]
self.start_time = self.status_dict["start_time"]
self.deal_start_time = self.status_dict["deal_start_time"]
self.stop_when_profit = self.status_dict["stop_when_profit"]
if "deal_order_history" not in self.status_dict:
self.status_dict["deal_order_history"] = []
#I need to revise this two conditionals
if self.so==self.broker.get_empty_order() and self.safety_order_index<self.config_dict["no_of_safety_orders"]:
self.broker.logger.log_this("Couldn't load safety order. Aborting.",2,self.pair)
self.quit = True
return 1
if self.tp_order==self.broker.get_empty_order():
self.broker.logger.log_this("Couldn't load take profit order. Aborting.",2,self.pair)
self.quit = True
return 1
self.profit_filename = f"profits/{self.base}{self.quote}.profits"
self.log_filename = f"logs/{self.base}{self.quote}.log"
if self.is_short and "old_long" not in self.status_dict:
#Check if there is an old_long file. If so, load it.
try:
with open(f"status/{self.base}{self.quote}.oldlong") as ol:
self.status_dict["old_long"] = json.load(ol)
except Exception as e:
self.broker.logger.log_this(f"Exception in load_values while trying to load the old_long file: {e}",1,self.pair)
self.quit = False
self.pause = False
self.status_dict["pause_reason"] = ""
return 0
'''