Compare commits
20 Commits
2025.09.12
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
4a1f1c844d | |
|
|
536866364c | |
|
|
7c33dd231d | |
|
|
96d1cf6d78 | |
|
|
b69b0d2f15 | |
|
|
18506dbaf3 | |
|
|
ca43b3dad5 | |
|
|
d06bfd9d10 | |
|
|
e354ea4d55 | |
|
|
8f3b0eb186 | |
|
|
2823dff56a | |
|
|
c42a505e49 | |
|
|
0576f93477 | |
|
|
65c406a03d | |
|
|
9e2a1dc7a1 | |
|
|
171738fa4d | |
|
|
09f9aa313c | |
|
|
451e1a63aa | |
|
|
733f6efbff | |
|
|
0d753aa3cd |
|
|
@ -1,3 +1,88 @@
|
|||
2025.12.01:
|
||||
. Modified log output of new_market_order.
|
||||
. Modified Kucoin's case in min_amount_of_base.
|
||||
|
||||
2025.11.11:
|
||||
. deals_cache and log_list cache are now 20 items long.
|
||||
. Less log spam.
|
||||
|
||||
2025.11.08:
|
||||
. broker.set_default_order_size() now saves the config file to disk after changing the value.
|
||||
. Variable renaming and other small stuff.
|
||||
|
||||
2025.10.24:
|
||||
. Toggling liquidate_after_switch now writes the config file to disk so the setting persists between trades.
|
||||
. Manually switching to long now sets double_check_price to false.
|
||||
. Added a few comments to switch_to_long.
|
||||
|
||||
2025.10.12:
|
||||
. do_cleanup relocated after generating the safety orders' prices.
|
||||
|
||||
2025.10.11:
|
||||
. Minor simplification in do_cleanup.
|
||||
. Removed a couple of (no longer needed?) pauses.
|
||||
|
||||
2025.10.10:
|
||||
. New endpoint: /refresh_log_cache.
|
||||
. Fixed an error in /add_so endpoint that incremented the config setting but not the status setting.
|
||||
|
||||
2025.10.09:
|
||||
. Cleanup is done as soon as the trader starts, rather than after sending the take profit and safety orders.
|
||||
|
||||
2025.10.07:
|
||||
. In short traders, if there are too few safety orders (less than 67% of the max amount), safety_order_deviance is increased from 2% to 3%.
|
||||
|
||||
2025.10.04:
|
||||
. Fixed error while logging orders in new_simulated_market_order.
|
||||
. renew_tp_and_so_routine now send the take profit order first, and then the safety orders.
|
||||
|
||||
2025.10.03:
|
||||
. New broker config option: log_orders. If set to True, the orders will be logged in orders.log under logs directory.
|
||||
. New API endpoint: /toggle_log_orders.
|
||||
|
||||
2025.10.01:
|
||||
. Fixed base fees not being taken into account.
|
||||
|
||||
2025.09.27:
|
||||
. Added notes in every entry of deal_order_history.
|
||||
. Minor refactor in renew_tp_and_so_routine.
|
||||
. Added another cooldown before sending a take profit order (To give the exchange a bit more time to reflect correctly the amount of base present in the account)
|
||||
. Updated cleanup routine to leave some change in the account.
|
||||
|
||||
2025.09.25:
|
||||
. Added a pause after getting filled orders in check_status.
|
||||
. Added an extra logging line in take_profit_routine.
|
||||
|
||||
2025.09.24:
|
||||
. Added a new config option: wait_after_initial_market_order. If specifies in seconds the amount of wait time after sending the initial market order.
|
||||
It should help the exchanges to report correctly the recently filled market order.
|
||||
. Removed the "PAUSED" notice in the screen output that was unused.
|
||||
|
||||
2025.09.21:
|
||||
. Fixed a bug that caused short traders to have an incorrect order size.
|
||||
|
||||
2025.09.20:
|
||||
. Fixed bug that caused short traders to initialize using the same workflow as a long one.
|
||||
|
||||
2025.09.19:
|
||||
. Added pageSize parameter to the open order requests when querying Kucoin.
|
||||
|
||||
2025.09.18:
|
||||
. do_cleanup now uses get_min_quote_size.
|
||||
. Added an extra price check to switch_to_long.
|
||||
. Removed old check_old_long_price method.
|
||||
|
||||
2025.09.14:
|
||||
. Refactored full order list fetching.
|
||||
. Minor refactor of restart_pair_no_json.
|
||||
. Pausing the trader is now done via set_pause() method.
|
||||
. Reverted modification of wait time after initial market order.
|
||||
. wait_time now present in broker config file.
|
||||
. Minor refactorings.
|
||||
|
||||
2025.09.13:
|
||||
. Increased wait time after initial market order.
|
||||
|
||||
2025.09.12:
|
||||
. No retries when sending a cleanup order.
|
||||
. Removed redundant try...except blocks in switch_to_long.
|
||||
|
|
|
|||
|
|
@ -252,6 +252,7 @@ class ConfigHandler:
|
|||
# self.broker.logger.log_this(f"liquidate_after_switch must be a boolean",1,self.get_pair())
|
||||
# return 1
|
||||
self.config_dictionary["liquidate_after_switch"] = liquidate_after_switch
|
||||
self.save_to_file()
|
||||
return 0
|
||||
|
||||
def set_tp_mode(self, tp_mode: int):
|
||||
|
|
|
|||
|
|
@ -14,17 +14,19 @@ class Broker:
|
|||
self.broker_config = broker_config
|
||||
self.exchange = exchange
|
||||
self.last_price = 0
|
||||
self.wait_time = .5 #Default wait time for API breathing room
|
||||
self.empty_order = {"id": "", "status": "", "filled": 0, "remaining": 0, "price": 0, "cost": 0, "fees": [], "symbol": ""}
|
||||
|
||||
|
||||
#Default values
|
||||
self.cooldown_multiplier = self.broker_config["cooldown_multiplier"] if "cooldown_multiplier" in self.broker_config else 2
|
||||
self.wait_before_new_safety_order = self.broker_config["wait_before_new_safety_order"] if "wait_before_new_safety_order" in self.broker_config else 1
|
||||
self.retries = self.broker_config["retries"] if "retries" in self.broker_config else 5
|
||||
self.slippage_default_threshold = self.broker_config["slippage_default_threshold"] if "slippage_default_threshold" in self.broker_config else .03
|
||||
self.follow_order_history = self.broker_config["follow_order_history"] if "follow_order_history" in self.broker_config else False
|
||||
self.write_order_history = self.broker_config["write_order_history"] if "write_order_history" in self.broker_config else False
|
||||
self.wait_time = self.broker_config.get("wait_time",.5)
|
||||
self.cooldown_multiplier = self.broker_config.get("cooldown_multiplier",2)
|
||||
self.wait_after_initial_market_order = self.broker_config.get("wait_after_initial_market_order",1)
|
||||
self.wait_before_new_safety_order = self.broker_config.get("wait_before_new_safety_order",1)
|
||||
self.retries = self.broker_config.get("retries",5)
|
||||
self.slippage_default_threshold = self.broker_config.get("slippage_default_threshold",.03)
|
||||
self.follow_order_history = self.broker_config.get("follow_order_history",False)
|
||||
self.write_order_history = self.broker_config.get("write_order_history", False)
|
||||
self.logger = Logger(self.broker_config)
|
||||
self.log_orders = self.broker_config.get("log_orders",False)
|
||||
|
||||
#Initialize database
|
||||
self.profits_database_filename = "profits/profits_database.db"
|
||||
|
|
@ -49,7 +51,7 @@ class Broker:
|
|||
self.markets = self.exchange.load_markets()
|
||||
|
||||
#Populates deals cache
|
||||
self.deals_cache_length = 10
|
||||
self.deals_cache_length = 20
|
||||
self.deals_list = self.preload_deals(amount_to_preload=self.deals_cache_length)
|
||||
|
||||
|
||||
|
|
@ -80,6 +82,16 @@ class Broker:
|
|||
def get_deals_cache(self):
|
||||
return self.deals_list
|
||||
|
||||
|
||||
def get_log_orders(self):
|
||||
return self.log_orders
|
||||
|
||||
|
||||
def set_log_orders(self,log_orders:bool):
|
||||
self.log_orders = log_orders
|
||||
return 0
|
||||
|
||||
|
||||
def get_symbol(self,pair):
|
||||
if "/" in pair:
|
||||
return pair
|
||||
|
|
@ -192,6 +204,13 @@ class Broker:
|
|||
self.cooldown_multiplier = value
|
||||
return 0
|
||||
|
||||
def get_wait_after_initial_market_order(self):
|
||||
return self.wait_after_initial_market_order
|
||||
|
||||
def set_wait_after_initial_market_order(self, value:float):
|
||||
self.wait_after_initial_market_order = value
|
||||
return 0
|
||||
|
||||
def get_wait_before_new_safety_order(self):
|
||||
return self.wait_before_new_safety_order
|
||||
|
||||
|
|
@ -205,6 +224,7 @@ class Broker:
|
|||
def set_default_order_size(self,size):
|
||||
try:
|
||||
self.broker_config["default_order_size"] = float(size)
|
||||
self.rewrite_config_file()
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in set_default_order_size: {e}",1)
|
||||
return 1
|
||||
|
|
@ -458,7 +478,7 @@ class Broker:
|
|||
try:
|
||||
return orderbook["bids"][0][0]
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception getting top mid price: {e}",1,symbol)
|
||||
self.logger.log_this(f"Exception getting top bid price: {e}",1,symbol)
|
||||
return self.get_ticker_price(symbol)
|
||||
|
||||
|
||||
|
|
@ -503,28 +523,6 @@ class Broker:
|
|||
return []
|
||||
|
||||
|
||||
def fetch_full_orders(self,pairs=None) -> list:
|
||||
'''
|
||||
Returns a list of all orders on the exchange
|
||||
|
||||
:param pairs: list of pairs to get orders for
|
||||
:return: list of orders
|
||||
'''
|
||||
|
||||
if pairs is None:
|
||||
pairs = []
|
||||
try:
|
||||
orders = []
|
||||
if self.get_exchange_name()=="binance":
|
||||
orders = self.get_opened_orders_binance(pairs)
|
||||
else:
|
||||
orders = self.get_opened_orders()
|
||||
return [] if orders is None else orders
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in fetch_full_orders: {e}",2)
|
||||
return []
|
||||
|
||||
|
||||
def fetch_open_orders(self,pairs=None) -> list:
|
||||
'''
|
||||
Returns a list of all open orders on the exchange
|
||||
|
|
@ -536,9 +534,18 @@ class Broker:
|
|||
if pairs is None:
|
||||
pairs = []
|
||||
try:
|
||||
if self.get_exchange_name()in ["binance","kucoin"]:
|
||||
return self.get_opened_orders_binance(pairs)
|
||||
return self.get_opened_orders()
|
||||
if self.get_exchange_name()=="binance":
|
||||
if self.broker_config.get("unified_order_query"):
|
||||
return self.exchange.fetch_open_orders()
|
||||
result = []
|
||||
for pair in pairs:
|
||||
a = self.exchange.fetch_open_orders(pair)
|
||||
result.extend(iter(a))
|
||||
return result
|
||||
elif self.get_exchange_name()=="kucoin":
|
||||
return self.exchange.fetch_open_orders(params={"pageSize": "500"})
|
||||
else:
|
||||
return self.exchange.fetch_open_orders()
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in fetch_open_orders: {e}",2)
|
||||
return []
|
||||
|
|
@ -559,31 +566,10 @@ class Broker:
|
|||
return self.get_closed_orders_binance(pairs)
|
||||
return self.get_closed_orders()
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in fetch_open_orders: {e}",2)
|
||||
self.logger.log_this(f"Exception in fetch_closed_orders: {e}",2)
|
||||
return []
|
||||
|
||||
|
||||
def get_opened_orders(self,no_retries=False): #It should return a list of all opened orders
|
||||
'''
|
||||
Returns a list of all the open orders on the exchange
|
||||
|
||||
:param pairs: list of pairs
|
||||
:return: list of all the open orders on the exchange
|
||||
'''
|
||||
|
||||
retries = self.retries
|
||||
while retries>0:
|
||||
try:
|
||||
return self.exchange.fetch_open_orders()
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in get_opened_orders: {e}",1)
|
||||
if no_retries:
|
||||
break
|
||||
time.sleep(self.wait_time)
|
||||
retries-=1
|
||||
return []
|
||||
|
||||
|
||||
def get_closed_orders(self,pair=None,no_retries=False): #It should return a list of all opened orders
|
||||
'''
|
||||
Returns a list of all the open orders on the exchange
|
||||
|
|
@ -605,27 +591,6 @@ class Broker:
|
|||
return []
|
||||
|
||||
|
||||
def get_opened_orders_binance(self,pairs):
|
||||
'''
|
||||
Returns a list of all the open orders on the exchange
|
||||
|
||||
:param pairs: list of pairs
|
||||
:return: list of all the open orders on the exchange
|
||||
'''
|
||||
|
||||
try:
|
||||
if "unified_order_query" in self.broker_config and self.broker_config["unified_order_query"] is True:
|
||||
return self.exchange.fetch_open_orders()
|
||||
result = []
|
||||
for pair in pairs:
|
||||
a = self.exchange.fetch_open_orders(pair)
|
||||
result.extend(iter(a))
|
||||
return result
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in get_opened_orders_binance: {e}",1)
|
||||
return []
|
||||
|
||||
|
||||
def get_closed_orders_binance(self,pairs):
|
||||
'''
|
||||
Returns a list of all the closed orders on the exchange
|
||||
|
|
@ -635,7 +600,7 @@ class Broker:
|
|||
'''
|
||||
|
||||
try:
|
||||
if "unified_order_query" in self.broker_config and self.broker_config["unified_order_query"] is True:
|
||||
if self.broker_config.get("unified_order_query"):
|
||||
return self.exchange.fetch_closed_orders()
|
||||
result = []
|
||||
for pair in pairs:
|
||||
|
|
@ -699,7 +664,7 @@ class Broker:
|
|||
return amount
|
||||
|
||||
|
||||
def new_simulated_market_order(self,symbol,size,side,amount_in_base=False,no_retries=False):
|
||||
def new_simulated_market_order(self,symbol,size,side,amount_in_base=False,no_retries=False,log=""):
|
||||
'''
|
||||
TODO: Emulating Market Orders With Limit Orders
|
||||
|
||||
|
|
@ -729,7 +694,9 @@ class Broker:
|
|||
while retries>0:
|
||||
try:
|
||||
if self.get_exchange_name()=="gateio" and side=="buy" and not amount_in_base:
|
||||
new_order = self.exchange.create_market_buy_order_with_cost(symbol, size)
|
||||
new_order = self.exchange.create_market_buy_order_with_cost(symbol, size)
|
||||
if self.log_orders:
|
||||
self.logger.log_order(f"New simulated market order: Symbol: {symbol} - Side: {side} - Size: {size} - ID: {new_order['id']} - Origin: {log}")
|
||||
else:
|
||||
order_book = self.get_order_book(symbol)
|
||||
if order_book=={}:
|
||||
|
|
@ -743,6 +710,8 @@ class Broker:
|
|||
price = self.find_minimum_viable_price(order_book,base_amount,side)
|
||||
#Maybe check for slippage here instead of within the trader itself? idk
|
||||
new_order = self.exchange.create_order(symbol,"limit",side,base_amount,price)
|
||||
if self.log_orders:
|
||||
self.logger.log_order(f"New simulated market order: Symbol: {symbol} - Side: {side} - Size: {size} - Price: {price} - ID: {new_order['id']} - Origin: {log}")
|
||||
time.sleep(self.wait_time)
|
||||
return self.get_order(new_order["id"],symbol)
|
||||
except Exception as e:
|
||||
|
|
@ -789,7 +758,7 @@ class Broker:
|
|||
return None
|
||||
|
||||
|
||||
def new_market_order(self,symbol,size,side,amount_in_base=False,no_retries=False): #It should send a new market order to the exchange
|
||||
def new_market_order(self,symbol,size,side,amount_in_base=False,no_retries=False, log=""): #It should send a new market order to the exchange
|
||||
'''
|
||||
Sends a new market order to the exchange.
|
||||
|
||||
|
|
@ -814,12 +783,13 @@ class Broker:
|
|||
amount = self.amount_to_precision(symbol,size) #Market sell orders are always nominated in base currency
|
||||
|
||||
order_to_send = self.exchange.create_order(symbol,"market",side,amount)
|
||||
if self.log_orders:
|
||||
self.logger.log_order(f"New market order: Symbol: {symbol} - Side: {side} - Size: {size} - ID: {order_to_send['id']} - Origin: {log}")
|
||||
time.sleep(self.wait_time)
|
||||
# Wait a bit more when dealing with Kucoin
|
||||
|
||||
return self.get_order(order_to_send["id"],symbol)
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in new_market_order: {e}",1,symbol)
|
||||
self.logger.log_this(f"Exception in new_market_order: {e} - Side: {side} - Size: {size}",1,symbol)
|
||||
if no_retries:
|
||||
break
|
||||
time.sleep(self.wait_time)
|
||||
|
|
@ -901,7 +871,7 @@ class Broker:
|
|||
# return returned_orders
|
||||
|
||||
|
||||
def new_limit_order(self,symbol,size,side,price,no_retries=False):
|
||||
def new_limit_order(self,symbol,size,side,price,no_retries=False,log=""):
|
||||
'''
|
||||
Sends a new limit order.
|
||||
|
||||
|
|
@ -917,6 +887,8 @@ class Broker:
|
|||
try:
|
||||
order_to_send = self.exchange.create_order(symbol,"limit",side,self.amount_to_precision(symbol,size),price)
|
||||
time.sleep(self.wait_time)
|
||||
if self.log_orders:
|
||||
self.logger.log_order(f"New limit order: Symbol: {symbol} - Side: {side} - Size: {size} - Price: {price} - ID: {order_to_send['id']} - Notes: {log}")
|
||||
return self.get_order(order_to_send["id"],symbol)
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in new_limit_order - Side: {side} - Size: {size} - {self.amount_to_precision(symbol,size)} - Exception: {e}",1,symbol)
|
||||
|
|
@ -1016,9 +988,9 @@ class Broker:
|
|||
if self.get_exchange_name() in ["okex","bybit"]:
|
||||
return float(market["limits"]["amount"]["min"])
|
||||
elif self.get_exchange_name() in ["kucoin"]:
|
||||
return (float(market["limits"]["cost"]["min"])+.1)/self.get_ticker_price(pair)
|
||||
return max(float(market["limits"]["amount"]["min"]),(float(market["limits"]["cost"]["min"])+.25)/self.get_ticker_price(pair))
|
||||
elif self.get_exchange_name() in ["gateio"]:
|
||||
return (float(market["limits"]["cost"]["min"])+.25)/self.get_ticker_price(pair)
|
||||
return (float(market["limits"]["cost"]["min"])+.1)/self.get_ticker_price(pair)
|
||||
elif self.get_exchange_name()=="binance":
|
||||
for line in market["info"]["filters"]:
|
||||
if line["filterType"] == "NOTIONAL":
|
||||
|
|
@ -1080,7 +1052,7 @@ class Logger:
|
|||
self.broker_config = broker_config
|
||||
self.exchange_name = self.broker_config["exchange"]
|
||||
self.tg_credentials = credentials.get_credentials("telegram")
|
||||
self.log_list_max_length = 10
|
||||
self.log_list_max_length = 20 # log cache
|
||||
self.log_list = collections.deque(maxlen=self.log_list_max_length)
|
||||
self.preload_logs()
|
||||
|
||||
|
|
@ -1096,6 +1068,16 @@ class Logger:
|
|||
return 1
|
||||
|
||||
|
||||
def refresh_logs(self):
|
||||
try:
|
||||
self.log_list.clear()
|
||||
self.preload_logs()
|
||||
return 0
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return 1
|
||||
|
||||
|
||||
def set_log_list_max_length(self, amount):
|
||||
self.log_list_max_length = amount
|
||||
return self.log_list_max_length
|
||||
|
|
@ -1132,6 +1114,9 @@ class Logger:
|
|||
self.log_this(f"Error in send_tg_message: {e}",1)
|
||||
return 1
|
||||
|
||||
def log_order(self,message):
|
||||
with open(f"logs/orders.log","a") as log_file:
|
||||
log_file.write(time.strftime(f"[%Y/%m/%d %H:%M:%S] | {message}\n"))
|
||||
|
||||
def log_this(self,message,level=2,pair=None):
|
||||
'''
|
||||
|
|
@ -1153,8 +1138,6 @@ class Logger:
|
|||
#Write to log file
|
||||
with open(f"logs/{self.exchange_name}.log","a") as log_file:
|
||||
log_file.write(text+"\n")
|
||||
log_file.close()
|
||||
|
||||
#Append to log list
|
||||
self.log_list.append(text)
|
||||
except Exception as e:
|
||||
|
|
|
|||
117
main.py
117
main.py
|
|
@ -18,7 +18,7 @@ import exchange_wrapper
|
|||
import trader
|
||||
|
||||
|
||||
version = "2025.09.12"
|
||||
version = "2025.12.01"
|
||||
|
||||
'''
|
||||
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
||||
|
|
@ -41,7 +41,7 @@ executor = None
|
|||
|
||||
#Shutdown handler
|
||||
def shutdown_handler(signum, _):
|
||||
broker.logger.log_this(f"Received signal {signum}, shutting down as gracefully as possible...", 2)
|
||||
broker.logger.log_this(f"Received signal {signum}, shutting down.", 2)
|
||||
if executor:
|
||||
executor.shutdown(wait=True, timeout=5)
|
||||
os_exit(0)
|
||||
|
|
@ -246,20 +246,26 @@ def restart_pair_no_json(base: str, quote: str) -> int:
|
|||
|
||||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
order_list = broker.fetch_full_orders(tickers)
|
||||
for instance in running_traders:
|
||||
if symbol==instance.status.get_pair():
|
||||
instance.pause = True
|
||||
instance.set_pause(True, "Restarting trader")
|
||||
#Backing up old status file
|
||||
instance.status.save_to_file(is_backup=True)
|
||||
#Here, we could open a duster (if needed)
|
||||
for order in order_list:
|
||||
if order["symbol"]==symbol and instance.config.get_is_short() and order["side"]=="sell":
|
||||
broker.logger.log_this(f"Cancelling old sell orders",2,symbol)
|
||||
broker.cancel_order(order["id"],order["symbol"])
|
||||
elif order["symbol"]==symbol and not instance.config.get_is_short() and order["side"]=="buy":
|
||||
broker.logger.log_this(f"Cancelling old buy orders",2,symbol)
|
||||
broker.cancel_order(order["id"],order["symbol"])
|
||||
|
||||
broker.logger.log_this(f"Cancelling old take profit order",2,symbol)
|
||||
try:
|
||||
old_tp_order = instance.status.get_take_profit_order()
|
||||
broker.cancel_order(old_tp_order["id"],old_tp_order["symbol"])
|
||||
except Exception as e:
|
||||
broker.logger.log_this(f"Error canceling old take profit order: {e}",2,symbol)
|
||||
|
||||
broker.logger.log_this(f"Cancelling old take safety orders",2,symbol)
|
||||
for item in instance.status.get_safety_orders():
|
||||
try:
|
||||
broker.cancel_order(item["id"],item["symbol"])
|
||||
except Exception as e:
|
||||
broker.logger.log_this(f"Error canceling old safety order: {e}",2,symbol)
|
||||
|
||||
try:
|
||||
running_traders.remove(instance)
|
||||
except ValueError:
|
||||
|
|
@ -1232,6 +1238,20 @@ def switch_quote_currency():
|
|||
return jsonify({'Error': 'Halp'})
|
||||
|
||||
|
||||
@base_api.route("/toggle_log_orders", methods=['POST'])
|
||||
def toggle_log_orders():
|
||||
'''
|
||||
POST request
|
||||
|
||||
Parameters:
|
||||
None
|
||||
'''
|
||||
|
||||
if not "X-API-KEY" in request.headers or not request.headers.get("X-API-KEY") in valid_keys:
|
||||
return jsonify({'Error': 'API key invalid'}), 401
|
||||
return unwrapped_toggle_log_orders()
|
||||
|
||||
|
||||
@base_api.route("/toggle_restart", methods=['POST'])
|
||||
def toggle_restart():
|
||||
'''
|
||||
|
|
@ -1288,6 +1308,21 @@ def get_log_list():
|
|||
return unwrapped_get_log_list()
|
||||
|
||||
|
||||
@base_api.route("/refresh_log_cache", methods=['POST'])
|
||||
def refresh_log_cache():
|
||||
'''
|
||||
POST request
|
||||
'''
|
||||
|
||||
if not "X-API-KEY" in request.headers or not request.headers.get("X-API-KEY") in valid_keys:
|
||||
return jsonify({'Error': 'API key invalid'}), 401
|
||||
try:
|
||||
return unwrapped_refresh_log_cache()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return jsonify({'Error': 'Halp'})
|
||||
|
||||
|
||||
@base_api.route("/get_balance", methods=['GET'])
|
||||
def get_balance():
|
||||
'''
|
||||
|
|
@ -1606,8 +1641,8 @@ def unwrapped_switch_to_long(base,quote,calculate_profits):
|
|||
return jsonify({"Error": "Pair not running"})
|
||||
for instance in running_traders:
|
||||
if f"{base}/{quote}"==instance.status.get_pair():
|
||||
instance.pause = True
|
||||
if instance.switch_to_long(ignore_old_long=ignore_old_long)==1:
|
||||
instance.set_pause(True, "Switching to long mode")
|
||||
if instance.switch_to_long(ignore_old_long=ignore_old_long,double_check_price=False)==1:
|
||||
return jsonify({"Error": "Error in switch_to_long()"})
|
||||
if instance.start_trader()==1:
|
||||
instance.quit = True
|
||||
|
|
@ -1774,14 +1809,13 @@ def unwrapped_add_safety_orders(base,quote,amount):
|
|||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.status.get_pair():
|
||||
instance.pause = True
|
||||
#x.no_of_safety_orders += int(amount)
|
||||
instance.config.set_no_of_safety_orders(instance.config.get_no_of_safety_orders()+int(amount))
|
||||
instance.set_pause(True, "Adding safety orders")
|
||||
instance.status.set_no_of_safety_orders(instance.status.get_no_of_safety_orders()+int(amount))
|
||||
broker.logger.log_this("Recalculating safety price table...",1,symbol)
|
||||
instance.status.set_safety_price_table(instance.calculate_safety_prices(instance.status.get_start_price(),instance.config.get_no_of_safety_orders(),instance.config.get_safety_order_deviance()))
|
||||
broker.logger.log_this(f"Done. Added {amount} safety orders",1,symbol)
|
||||
instance.update_status(True)
|
||||
instance.pause = False
|
||||
instance.set_pause(False)
|
||||
return jsonify({"Success": f"Done. Added {amount} safety orders"})
|
||||
return jsonify({"Error": "Pair not found"})
|
||||
except Exception as e:
|
||||
|
|
@ -2097,24 +2131,24 @@ def unwrapped_add_quote(base,quote,amount):
|
|||
if f"{base}/{quote}"==instance.status.get_pair():
|
||||
if instance.config.get_is_short():
|
||||
return jsonify({"Error": "Quote can't be added to short traders"})
|
||||
instance.pause = True
|
||||
instance.set_pause(True, "Adding quote")
|
||||
new_average_price = (instance.status.get_quote_spent()+float(amount))/(instance.status.get_base_bought()+(float(amount)/instance.status.get_price()))
|
||||
broker.logger.log_this(f"Your new average buy price will be {new_average_price} {quote}",2,instance.status.get_pair())
|
||||
broker.logger.log_this(f"Your new take profit price price will be {new_average_price*instance.get_tp_level()} {quote}",2,instance.status.get_pair())
|
||||
new_order = broker.new_market_order(instance.status.get_pair(),float(amount),"buy")
|
||||
if new_order is None:
|
||||
broker.logger.log_this("Error: Market order returned None",2,instance.status.get_pair())
|
||||
instance.pause = False
|
||||
instance.set_pause(False)
|
||||
return jsonify({"Error": "Market order returned None"})
|
||||
while True:
|
||||
time.sleep(broker.get_wait_time())
|
||||
returned_order = broker.get_order(new_order["id"],instance.status.get_pair())
|
||||
if returned_order==broker.empty_order:
|
||||
broker.logger.log_this("Problems sending the order",2,instance.status.get_pair())
|
||||
instance.pause = False
|
||||
instance.set_pause(False)
|
||||
return jsonify({"Error": "Problems sending the order"})
|
||||
elif returned_order["status"]=="expired":
|
||||
instance.pause = False
|
||||
instance.set_pause(False)
|
||||
return jsonify({"Error": "New order expired"})
|
||||
elif returned_order["status"]=="closed":
|
||||
broker.logger.log_this("Order sent",2,instance.status.get_pair())
|
||||
|
|
@ -2131,7 +2165,7 @@ def unwrapped_add_quote(base,quote,amount):
|
|||
attempts-=1
|
||||
if attempts==0:
|
||||
broker.logger.log_this("Can't cancel old take profit order, cancelling...",2,instance.status.get_pair())
|
||||
instance.pause = False
|
||||
instance.set_pause(False)
|
||||
return jsonify({"Error": "Can't cancel old take profit order."})
|
||||
instance.status.set_take_profit_price(instance.status.get_quote_spent()/instance.status.get_base_bought()*instance.get_tp_level())
|
||||
instance.status.set_take_profit_order(broker.new_limit_order(instance.status.get_pair(),instance.status.get_base_bought(),"sell",instance.status.get_take_profit_price()))
|
||||
|
|
@ -2141,7 +2175,7 @@ def unwrapped_add_quote(base,quote,amount):
|
|||
broker.logger.log_this("Waiting for initial order to get filled",2,instance.status.get_pair())
|
||||
broker.logger.log_this(f"{returned_order}",2,instance.status.get_pair())
|
||||
time.sleep(broker.get_wait_time())
|
||||
instance.pause = False
|
||||
instance.set_pause(False)
|
||||
broker.logger.log_this("Done",2,instance.status.get_pair())
|
||||
return jsonify({"Success": "Quote added successfully"})
|
||||
return jsonify({"Error": "Something horrible happened :S"})
|
||||
|
|
@ -2246,6 +2280,7 @@ def unwrapped_toggle_autoswitch(base,quote):
|
|||
broker.logger.log_this(f"Exception while toggling autoswitch: {e}",1,symbol)
|
||||
return jsonify({"Error": "Halp"})
|
||||
|
||||
|
||||
def unwrapped_toggle_liquidate_after_switch(base,quote):
|
||||
'''
|
||||
Signals a trader to enable or disable quitting after switching from short to long.
|
||||
|
|
@ -2324,14 +2359,14 @@ def unwrapped_switch_quote_currency(base,quote,new_quote):
|
|||
for trader in running_traders:
|
||||
if symbol==trader.status.get_pair():
|
||||
#Pause the trader
|
||||
trader.pause = True
|
||||
trader.set_pause(True, "Switching quote currency")
|
||||
|
||||
#Call x.switch_quote_currency
|
||||
if trader.switch_quote_currency(new_quote)==1:
|
||||
return jsonify({"Error": "Swap failed. Check log files for details."})
|
||||
|
||||
#Resume the trader
|
||||
trader.pause = False
|
||||
trader.set_pause(False)
|
||||
return jsonify({"Success": "Mission successful"})
|
||||
return jsonify({"Error": "Trader not found"})
|
||||
except Exception as e:
|
||||
|
|
@ -2354,6 +2389,20 @@ def unwrapped_toggle_restart():
|
|||
return jsonify({"Success": "attempt_to_restart disabled"})
|
||||
|
||||
|
||||
def unwrapped_toggle_log_orders():
|
||||
'''
|
||||
Toggles on or off the logging of orders.
|
||||
|
||||
Returns:
|
||||
jsonify: A jsonified dictionary detailing the outcome of the operation.
|
||||
'''
|
||||
|
||||
broker.set_log_orders(not broker.get_log_orders())
|
||||
if broker.get_log_orders():
|
||||
return jsonify({"Success": "log_orders enabled"})
|
||||
return jsonify({"Success": "log_orders disabled"})
|
||||
|
||||
|
||||
def unwrapped_toggle_telegram():
|
||||
'''
|
||||
Switches on or off the Telegram notifications
|
||||
|
|
@ -2425,6 +2474,22 @@ def unwrapped_get_log_list():
|
|||
return jsonify({"Logs": broker.logger.get_log_list()})
|
||||
|
||||
|
||||
def unwrapped_refresh_log_cache():
|
||||
'''
|
||||
Reloads the log file cache.
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns:
|
||||
jsonify: A jsonified dictionary containing the last n entries from the log file.
|
||||
'''
|
||||
if broker.logger.refresh_logs()==0:
|
||||
return jsonify({"Success": "Logs refreshed"})
|
||||
else:
|
||||
return jsonify({"Error": "Error while refreshing logs"})
|
||||
|
||||
|
||||
def unwrapped_get_deals_cache():
|
||||
'''
|
||||
Retrieves the last n entries from the broker's logger.
|
||||
|
|
|
|||
|
|
@ -418,11 +418,11 @@ class StatusHandler:
|
|||
self.status_dictionary["deal_order_history"] = []
|
||||
return 0
|
||||
|
||||
def update_deal_order_history(self, new_deal: dict):
|
||||
def update_deal_order_history(self, new_deal: dict, note: str = ""):
|
||||
# if not isinstance(new_deal, dict):
|
||||
# self.broker.logger.log_this(f"value provided is not a dict",1,self.get_pair())
|
||||
id = new_deal["id"] if "id" in new_deal else None
|
||||
self.status_dictionary["deal_order_history"].append(id)
|
||||
self.status_dictionary["deal_order_history"].append(f"{note} - {id}")
|
||||
return 0
|
||||
|
||||
def save_to_file(self, file_path = None, is_backup = False):
|
||||
|
|
|
|||
338
trader.py
338
trader.py
|
|
@ -11,14 +11,16 @@ class trader:
|
|||
self.pause = True
|
||||
self.quit = False
|
||||
self.restart = False
|
||||
self.warnings = {
|
||||
"short_price_exceeds_old_long": False,
|
||||
"speol_notified": False
|
||||
}
|
||||
self.trader_restart_errors = {1: "start_trader returned error #1. Trader will be restarted",
|
||||
2: "start_trader returned error #2: Initial order never got filled. Trader will be restarted",
|
||||
3: "start_trader returned error #3: Slippage threshold exceeded. Trader will be restarted"}
|
||||
|
||||
#Status string caches
|
||||
self.low_price_cache = None
|
||||
self.mid_price_cache = None
|
||||
self.high_price_cache = None
|
||||
self.concurrent_so_amount_cache = None
|
||||
|
||||
self.broker = broker
|
||||
self.config = ConfigHandler(pair,broker)
|
||||
base_quote = self.config.get_pair()
|
||||
|
|
@ -30,17 +32,18 @@ class trader:
|
|||
self.status.set_start_time(int(time.time()))
|
||||
self.last_time_seen = time.time()
|
||||
|
||||
#Status string caches
|
||||
self.low_price_cache = None
|
||||
self.mid_price_cache = None
|
||||
self.high_price_cache = None
|
||||
self.concurrent_so_amount_cache = None
|
||||
self.base_diff_check = False
|
||||
self.min_base_difference = .1 #Percentage difference between base in the tp order and its adjusted amount that triggers a recheck.
|
||||
#The exchanges sometimes take a few seconds to update the balance after an order is closed.
|
||||
self.base_check_interval = 60 #In seconds
|
||||
self.base_check_time = 0
|
||||
self.base_amount_missing = 0
|
||||
|
||||
if self.config.get_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.set_old_long(load(ol))
|
||||
with open(f"status/{self.base}{self.quote}.oldlong") as old_long_file_handler:
|
||||
self.status.set_old_long(load(old_long_file_handler))
|
||||
except Exception as e:
|
||||
self.broker.logger.log_this(f"Exception: No old_long file. {e}",1,base_quote)
|
||||
|
||||
|
|
@ -67,12 +70,12 @@ class trader:
|
|||
self.quit = True
|
||||
elif start_result==2: #Retries exceeded
|
||||
if self.config.get_force_restart_if_retries_exhausted():
|
||||
self.pause = False
|
||||
self.set_pause(False)
|
||||
self.restart = True
|
||||
else:
|
||||
self.quit = True
|
||||
elif start_result==3: #Not enough liquidity
|
||||
self.pause = False
|
||||
self.set_pause(False)
|
||||
self.restart = True
|
||||
|
||||
|
||||
|
|
@ -127,10 +130,9 @@ class trader:
|
|||
if new_market_data is not None:
|
||||
self.market = new_market_data
|
||||
|
||||
self.pause = True
|
||||
self.status.set_pause_reason("start_trader")
|
||||
self.set_pause(True, "start_trader")
|
||||
|
||||
if self.status.get_is_short():
|
||||
if self.config.get_is_short():
|
||||
self.broker.logger.log_this("Calculating optimal order size...",2,self.status.get_pair())
|
||||
|
||||
#Get minimum order size from exchange
|
||||
|
|
@ -153,7 +155,7 @@ class trader:
|
|||
if self.status.get_old_long()["tp_amount"]-free_base>min_base_size:
|
||||
amount_to_buy = self.broker.amount_to_precision(self.status.get_pair(),self.status.get_old_long()["tp_amount"]-free_base)
|
||||
self.broker.logger.log_this(f"Buying missing {amount_to_buy} {self.base}",1,self.status.get_pair())
|
||||
self.broker.new_market_order(self.status.get_pair(),amount_to_buy,"buy",amount_in_base=True)
|
||||
self.broker.new_market_order(self.status.get_pair(),amount_to_buy,"buy",amount_in_base=True,log="start_trader-missing_base")
|
||||
time.sleep(self.broker.get_wait_time()*2)
|
||||
#Re-querying for the amount of base currency on the exchange
|
||||
free_base = self.fetch_free_base()
|
||||
|
|
@ -169,6 +171,8 @@ class trader:
|
|||
return 1
|
||||
self.status.set_order_size(order_size)
|
||||
self.status.set_no_of_safety_orders(no_of_safety_orders)
|
||||
if no_of_safety_orders<self.config.get_max_short_safety_orders()*.67:
|
||||
self.config.set_safety_order_deviance(3)
|
||||
self.broker.logger.log_this(f"Order size: {self.broker.amount_to_precision(self.status.get_pair(),order_size)}. Amount of safety orders: {no_of_safety_orders}",2,self.status.get_pair())
|
||||
|
||||
#Write the changes to the config file
|
||||
|
|
@ -196,15 +200,19 @@ class trader:
|
|||
#Slippage threshold exceeded
|
||||
self.broker.logger.log_this("Slippage threshold exceeded",1,self.status.get_pair())
|
||||
return 3
|
||||
|
||||
self.status.set_order_size(self.config.get_order_size())
|
||||
|
||||
self.status.set_pause_reason("start_trader - after slippage")
|
||||
self.status.set_order_size(self.config.get_order_size())
|
||||
|
||||
|
||||
#Sending initial order
|
||||
#
|
||||
# Here, if the amount of the initial order is already available in the account, don't send a market order; just pull the current price and simulate that the order was sent and filled.
|
||||
# Cleanup probably would have to be disabled for this to make sense.
|
||||
#
|
||||
self.status.set_pause_reason("start_trader - sending first order")
|
||||
self.broker.logger.log_this("Sending first order...",2,self.status.get_pair())
|
||||
action = "sell" if self.config.get_is_short() else "buy"
|
||||
first_order = self.broker.new_market_order(self.status.get_pair(),self.status.get_order_size(),action)
|
||||
first_order = self.broker.new_market_order(self.status.get_pair(),self.status.get_order_size(),action,log="start_trader")
|
||||
#self.broker.logger.log_this(f"First order id: {first_order}",1,self.status.get_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.status.get_pair())
|
||||
|
|
@ -217,7 +225,7 @@ class trader:
|
|||
#Wait a bit longer, sometimes a recently filled market order is not updated quickly enough.
|
||||
# When that happens, 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.
|
||||
time.sleep(self.broker.get_wait_time()*2)
|
||||
time.sleep(self.broker.get_wait_after_initial_market_order())
|
||||
returned_order = self.broker.get_order(first_order["id"],self.status.get_pair())
|
||||
if returned_order==self.broker.get_empty_order():
|
||||
self.broker.logger.log_this("Problems with the initial order",1,self.status.get_pair())
|
||||
|
|
@ -242,7 +250,7 @@ class trader:
|
|||
#Save the order
|
||||
if self.broker.follow_order_history:
|
||||
self.status.set_pause_reason("start_trader - saving the order in deal_order_history")
|
||||
self.status.update_deal_order_history(returned_order)
|
||||
self.status.update_deal_order_history(returned_order, "init")
|
||||
|
||||
# Reset the fee count and sum fees from the first order
|
||||
self.status.set_fees_paid_in_base(self.parse_fees(returned_order)[0])
|
||||
|
|
@ -273,6 +281,11 @@ class trader:
|
|||
self.status.set_start_price(self.broker.price_to_precision(self.status.get_pair(),self.status.get_quote_spent()/self.status.get_base_bought()))
|
||||
self.status.set_safety_price_table(self.calculate_safety_prices(self.status.get_start_price(),self.status.get_no_of_safety_orders(),self.config.get_safety_order_deviance()))
|
||||
|
||||
# Send cleanup order (if cleanup)
|
||||
self.status.set_pause_reason("start_trader - doing cleanup (if needed)")
|
||||
if self.config.get_cleanup() and not self.config.get_is_short(): #Short traders do not need cleanup.
|
||||
self.do_cleanup()
|
||||
|
||||
# Send the initial batch of safety orders
|
||||
self.status.set_pause_reason("start_trader - sending safety orders")
|
||||
self.broker.logger.log_this("Sending safety orders...",2,self.status.get_pair())
|
||||
|
|
@ -286,15 +299,9 @@ class trader:
|
|||
self.broker.cancel_order(self.status.get_take_profit_order()["id"],self.status.get_pair())
|
||||
return 1
|
||||
|
||||
# Send cleanup order (if cleanup)
|
||||
self.status.set_pause_reason("start_trader - doing cleanup (if needed)")
|
||||
if self.config.get_cleanup() and not self.config.get_is_short(): #Short traders do not need cleanup.
|
||||
self.do_cleanup()
|
||||
|
||||
self.status.set_deal_start_time(int(time.time()))
|
||||
self.update_status(True)
|
||||
self.pause = False
|
||||
self.status.set_pause_reason("")
|
||||
self.set_pause(False)
|
||||
return 0
|
||||
|
||||
|
||||
|
|
@ -438,18 +445,15 @@ class trader:
|
|||
if self.config.get_is_short(): #Short traders do not need cleanup
|
||||
return 0
|
||||
|
||||
balance_to_clean = self.fetch_free_base()
|
||||
if balance_to_clean is None:
|
||||
balance_in_account = self.fetch_free_base()
|
||||
if balance_in_account is None:
|
||||
self.broker.logger.log_this("Can't fetch free base",1,self.status.get_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.status.get_pair())
|
||||
#minimum_cleanup_size = self.status.get_safety_orders()[0]["amount"]*2
|
||||
|
||||
if balance_to_clean >= min_base_size:
|
||||
self.broker.logger.log_this(f"Balance to clean: {balance_to_clean} {self.base}",2,self.status.get_pair())
|
||||
|
||||
if balance_in_account*self.status.get_start_price()>self.broker.get_min_quote_size(self.status.get_pair()):
|
||||
self.broker.logger.log_this(f"Balance to clean: {balance_in_account} {self.base}",2,self.status.get_pair())
|
||||
self.broker.logger.log_this("Sending cleanup order...",2,self.status.get_pair())
|
||||
cleanup_order = self.broker.new_limit_order(self.status.get_pair(),balance_to_clean,"sell",self.status.get_take_profit_price(),no_retries=True)
|
||||
cleanup_order = self.broker.new_limit_order(self.status.get_pair(),balance_in_account,"sell",self.status.get_take_profit_price(),no_retries=True,log="cleanup")
|
||||
if cleanup_order is None:
|
||||
self.broker.logger.log_this("Problems with the cleanup order, new_limit_order returned None",1,self.status.get_pair())
|
||||
return 1
|
||||
|
|
@ -478,13 +482,11 @@ class trader:
|
|||
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.status.get_pair())
|
||||
self.pause = False
|
||||
self.status.set_pause_reason("")
|
||||
self.set_pause(False)
|
||||
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.status.get_pair())
|
||||
self.pause = False
|
||||
self.status.set_pause_reason("")
|
||||
self.set_pause(False)
|
||||
return None,None
|
||||
return optimal_order_size,amount_of_so
|
||||
|
||||
|
|
@ -522,8 +524,7 @@ class trader:
|
|||
return 1
|
||||
|
||||
#Pauses trader
|
||||
self.pause = True
|
||||
self.status.set_pause_reason("switch_to_short")
|
||||
self.set_pause(True,"switch_to_short")
|
||||
|
||||
#Fetch the real amount of available base
|
||||
self.broker.logger.log_this(f"Fetching available {self.base}",2,self.status.get_pair())
|
||||
|
|
@ -535,15 +536,13 @@ class trader:
|
|||
min_base_size = self.broker.get_min_base_size(self.status.get_pair())
|
||||
if min_base_size is None:
|
||||
self.broker.logger.log_this("Error. Can't fetch market info from the exchange",1,self.status.get_pair())
|
||||
self.pause = False
|
||||
self.status.set_pause_reason("")
|
||||
self.set_pause(False)
|
||||
return 1
|
||||
|
||||
#Check if there is enough base
|
||||
if self.broker.amount_to_precision(self.status.get_pair(),free_base+self.status.get_take_profit_order()["amount"])<=min_base_size:
|
||||
self.broker.logger.log_this("Error. Not enough base currency",1,self.status.get_pair())
|
||||
self.pause = False
|
||||
self.status.set_pause_reason("")
|
||||
self.set_pause(False)
|
||||
return 1
|
||||
|
||||
#Calculate order size
|
||||
|
|
@ -559,8 +558,7 @@ class trader:
|
|||
self.broker.logger.log_this("Closing orders...",2,self.status.get_pair())
|
||||
if self.broker.cancel_order(self.status.get_take_profit_order()["id"],self.status.get_pair())==1:
|
||||
self.broker.logger.log_this("Can't cancel the take profit order. Can't switch mode",1,self.status.get_pair())
|
||||
self.pause = False
|
||||
self.status.set_pause_reason("")
|
||||
self.set_pause(False)
|
||||
return 1
|
||||
if self.status.get_take_profit_order()["id"]!="":
|
||||
self.broker.cancel_order(self.status.get_take_profit_order()["id"],self.status.get_pair())
|
||||
|
|
@ -574,8 +572,8 @@ class trader:
|
|||
"datetime": time.strftime("[%Y/%m/%d %H:%M:%S]")
|
||||
})
|
||||
try:
|
||||
with open(f"status/{self.base}{self.quote}.oldlong","w") as s:
|
||||
s.write(dumps(self.status.get_old_long(),indent=4))
|
||||
with open(f"status/{self.base}{self.quote}.oldlong","w") as old_long_file_handler:
|
||||
old_long_file_handler.write(dumps(self.status.get_old_long(),indent=4))
|
||||
except Exception as e:
|
||||
self.broker.logger.log_this(f"Exception while saving old_long file: {e}",1,self.status.get_pair())
|
||||
|
||||
|
|
@ -588,7 +586,6 @@ class trader:
|
|||
self.broker.logger.log_this("Config file updated",2,self.status.get_pair())
|
||||
except Exception as e:
|
||||
self.broker.logger.log_this(f"Error. Can't write the config file. Exception: {e}",1,self.status.get_pair())
|
||||
#self.pause = False
|
||||
return 1
|
||||
self.status.set_stop_when_profit(False)
|
||||
#self.config.set_is_short(True)
|
||||
|
|
@ -596,7 +593,7 @@ class trader:
|
|||
return 0
|
||||
|
||||
|
||||
def switch_to_long(self, ignore_old_long: bool = False, already_received_quote: float = 0) -> int:
|
||||
def switch_to_long(self, ignore_old_long: bool = False, already_received_quote: float = 0, double_check_price: bool = True) -> int:
|
||||
'''
|
||||
Takes a short trader and changes the mode to long.
|
||||
Only does it if the current trader was previously a long one.
|
||||
|
|
@ -606,13 +603,22 @@ class trader:
|
|||
self.broker.logger.log_this("Trader already in long mode, nothing to do",1,self.status.get_pair())
|
||||
return 1
|
||||
self.broker.logger.log_this("Attempting to switch to long trader",0,self.status.get_pair())
|
||||
|
||||
|
||||
if double_check_price:
|
||||
#Waits a moment to see if the price has moved too much
|
||||
self.broker.logger.log_this("Confirming price...",2,self.status.get_pair())
|
||||
time.sleep(self.broker.get_wait_time()*4)
|
||||
if not self.check_old_long(True):
|
||||
self.broker.logger.log_this("False positive. Nothing to do.",1,self.status.get_pair())
|
||||
return 2
|
||||
|
||||
#Check old_long data
|
||||
self.broker.logger.log_this("Checking if old long data is valid.",2,self.status.get_pair())
|
||||
if not ignore_old_long and self.status.get_old_long()=={}:
|
||||
self.broker.logger.log_this("Can't find old long info on status_dict, searching for oldlong file",1,self.status.get_pair())
|
||||
try:
|
||||
with open(f"status/{self.base}{self.quote}.oldlong") as f:
|
||||
self.status.set_old_long(load(f))
|
||||
with open(f"status/{self.base}{self.quote}.oldlong") as old_long_file_handler:
|
||||
self.status.set_old_long(load(old_long_file_handler))
|
||||
except Exception as e:
|
||||
#self.write_to_log(time.strftime(f"[%Y/%m/%d %H:%M:%S] | {self.status.get_pair()} | Can't find old long file"))
|
||||
self.broker.logger.log_this(f"Can't file oldlong file. Exception: {e}",1,self.status.get_pair())
|
||||
|
|
@ -620,6 +626,7 @@ class trader:
|
|||
return 1
|
||||
|
||||
#Cancel open orders
|
||||
self.broker.logger.log_this("Cancelling open orders",2,self.status.get_pair())
|
||||
for order in self.status.get_safety_orders():
|
||||
self.broker.cancel_order(order["id"],self.status.get_pair())
|
||||
if self.status.get_take_profit_order() is not None:
|
||||
|
|
@ -628,18 +635,21 @@ class trader:
|
|||
self.broker.logger.log_this("Safety order is None",1,self.status.get_pair())
|
||||
|
||||
#Sell all base currency
|
||||
self.broker.logger.log_this(f"Selling {self.status.get_pair().split('/')[0]}",2,self.status.get_pair())
|
||||
self.liquidate_base(ignore_profits=ignore_old_long, already_received_quote=already_received_quote)
|
||||
|
||||
if self.config.get_liquidate_after_switch():
|
||||
self.broker.logger.log_this("Liquidate after switch active. Raising quit flag.",1,self.status.get_pair())
|
||||
self.quit = True
|
||||
return 1
|
||||
return 0
|
||||
|
||||
#Rewrite config file (if it exists)
|
||||
if path.isfile(f"configs/{self.base}{self.quote}.bak") and path.isfile(f"configs/{self.base}{self.quote}.json"):
|
||||
with open(f"configs/{self.base}{self.quote}.bak") as c:
|
||||
old_config = load(c)
|
||||
with open(f"configs/{self.base}{self.quote}.json","w") as c:
|
||||
c.write(dumps(old_config, indent=4))
|
||||
self.broker.logger.log_this("Restoring config file from backup",2,self.status.get_pair())
|
||||
with open(f"configs/{self.base}{self.quote}.bak") as backup_config_file_handler:
|
||||
old_config = load(backup_config_file_handler)
|
||||
with open(f"configs/{self.base}{self.quote}.json","w") as config_file_handler:
|
||||
config_file_handler.write(dumps(old_config, indent=4))
|
||||
if self.config.load_from_file()==1:
|
||||
self.config.reset_to_default()
|
||||
else:
|
||||
|
|
@ -661,6 +671,7 @@ class trader:
|
|||
self.status.set_so_amount(0)
|
||||
|
||||
#Done. Ready for start_trader
|
||||
self.broker.logger.log_this("Finished setting up the switch to long.",2,self.status.get_pair())
|
||||
return 0
|
||||
|
||||
|
||||
|
|
@ -676,7 +687,7 @@ class trader:
|
|||
self.broker.logger.log_this("Can't fetch free base",1,self.status.get_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.status.get_pair(),free_base,"sell")
|
||||
order = self.broker.new_market_order(self.status.get_pair(),free_base,"sell",log="liquidate_base")
|
||||
time.sleep(self.broker.get_wait_time()*2)
|
||||
tries = self.broker.get_retries()
|
||||
while True:
|
||||
|
|
@ -709,15 +720,14 @@ class trader:
|
|||
but KuCoin only supports order editing on high frequency orders.
|
||||
'''
|
||||
|
||||
self.pause = True
|
||||
self.status.set_pause_reason("force_close - order handling")
|
||||
self.set_pause(True,"force_close - order handling")
|
||||
|
||||
#Close the take profit order
|
||||
self.broker.cancel_order(self.status.get_take_profit_order()["id"],self.status.get_pair())
|
||||
|
||||
#Send the market order
|
||||
amount = self.status.get_take_profit_order()["amount"]
|
||||
market_order = self.broker.new_market_order(self.status.get_pair(),amount,"sell",amount_in_base=True)
|
||||
market_order = self.broker.new_market_order(self.status.get_pair(),amount,"sell",amount_in_base=True,log="force_close")
|
||||
time.sleep(self.broker.get_wait_time()*2)
|
||||
|
||||
#Wait for it to be filled
|
||||
|
|
@ -743,8 +753,7 @@ class trader:
|
|||
the reporting and the restart of the trader.
|
||||
'''
|
||||
|
||||
self.pause = True #To stop the main thread to iterate through this trader's orders (just in case)
|
||||
self.status.set_pause_reason("take_profit_routine - order handling") #start_trader will set this flag to False again once it starts
|
||||
self.set_pause(True,"take_profit_routine - order handling") #To stop the main thread to iterate through this trader's orders (just in case)
|
||||
|
||||
#Add the timestamp to the deals cache and trims it
|
||||
self.deals_timestamps.append(time.time())
|
||||
|
|
@ -760,7 +769,7 @@ class trader:
|
|||
|
||||
#Save the order in history.
|
||||
if self.broker.get_follow_order_history():
|
||||
self.status.update_deal_order_history(filled_order)
|
||||
self.status.update_deal_order_history(filled_order, "tp")
|
||||
|
||||
#Cancel all the safety orders ASAP
|
||||
for order in self.status.get_safety_orders():
|
||||
|
|
@ -777,18 +786,19 @@ class trader:
|
|||
partial_filled_price.append(closed_order["average"])
|
||||
self.broker.logger.log_this(f"Old safety order is partially filled, ID: {closed_order['id']}, {closed_order['filled']}/{closed_order['amount']} {self.base} filled",1,self.status.get_pair())
|
||||
if self.broker.get_follow_order_history():
|
||||
self.status.update_deal_order_history(closed_order)
|
||||
self.status.update_deal_order_history(closed_order, "partial_fill")
|
||||
if closed_order["remaining"]!=0: #If this order is not completely filled, it is safe to assume that no order coming after this one was partially filled.
|
||||
break
|
||||
#Now we can clear the safety order list
|
||||
self.status.set_safety_orders([])
|
||||
|
||||
#Handle the partial fills
|
||||
if not self.status.get_is_short():
|
||||
if not self.config.get_is_short():
|
||||
#With short traders is just an accounting issue, since when the trader restarts it will be buying cheaper what it sold more expensive in the partially filled safety order(s)
|
||||
if partial_filled_amount!=0 and len(partial_filled_price)>0 and partial_filled_amount>self.broker.get_min_base_size(self.status.get_pair()):
|
||||
#send a market order and sum the profits and wait for it to be filled
|
||||
market_order = self.broker.new_market_order(self.status.get_pair(),partial_filled_amount,"sell",amount_in_base=True)
|
||||
self.broker.logger.log_this("Sending partial fill sell order...",1,self.status.get_pair())
|
||||
market_order = self.broker.new_market_order(self.status.get_pair(),partial_filled_amount,"sell",amount_in_base=True,log="take_profit_routine-partial_fill")
|
||||
time.sleep(self.broker.get_wait_time()*2)
|
||||
tries = self.broker.get_retries()
|
||||
while True:
|
||||
|
|
@ -798,10 +808,11 @@ class trader:
|
|||
partial_profit = market_order["cost"]-(avg_buy_price*partial_filled_amount)-self.parse_fees(market_order)[1]
|
||||
self.status.set_partial_profit(self.status.get_partial_profit()+partial_profit)
|
||||
break
|
||||
self.broker.logger.log_this("Waiting for partial fill sell order to fill.",2,self.status.get_pair())
|
||||
tries-=1
|
||||
time.sleep(self.broker.get_wait_time())
|
||||
if tries==0:
|
||||
self.broker.logger.log_this("Partial fill sell order not filling.",1,self.status.get_pair())
|
||||
self.broker.logger.log_this("Partial fill sell order not filled.",1,self.status.get_pair())
|
||||
break
|
||||
|
||||
if not self.broker.check_for_duplicate_profit_in_db(filled_order):
|
||||
|
|
@ -853,13 +864,13 @@ class trader:
|
|||
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.status.get_pair())
|
||||
time.sleep(self.broker.get_wait_time()*self.broker.get_cooldown_multiplier())
|
||||
self.pause = False
|
||||
self.set_pause(False)
|
||||
self.restart = True
|
||||
return 1
|
||||
elif self.check_orderbook_depth(self.broker.get_slippage_default_threshold(),self.status.get_order_size(),filled_order["price"]):
|
||||
self.broker.logger.log_this(f"Orderbook depth not sufficient, waiting for cooldown and restarting trader",1,self.status.get_pair())
|
||||
time.sleep(self.broker.get_wait_time()*self.broker.get_cooldown_multiplier())
|
||||
self.pause = False
|
||||
self.set_pause(False)
|
||||
self.restart = True
|
||||
return 1
|
||||
|
||||
|
|
@ -868,7 +879,7 @@ class trader:
|
|||
restart_trader = self.start_trader()
|
||||
self.status.set_pause_reason("take_profit_routine - restart_trader call - start_trader() called")
|
||||
if restart_trader in self.trader_restart_errors.keys():
|
||||
self.pause = False
|
||||
self.set_pause(False)
|
||||
self.restart = True
|
||||
self.status.save_to_file(is_backup=True)
|
||||
self.broker.logger.log_this(self.trader_restart_errors[restart_trader],1,self.status.get_pair())
|
||||
|
|
@ -918,9 +929,9 @@ class trader:
|
|||
self.broker.logger.log_this(f"Sending a new safety order ({i+1}/{orders_to_place})",2,self.status.get_pair())
|
||||
so_size = self.gib_so_size(self.status.get_order_size(),self.status.get_so_amount()+1,self.config.get_safety_order_scale())
|
||||
if self.config.get_is_short():
|
||||
new_order = self.broker.new_limit_order(self.status.get_pair(),so_size,"sell",self.status.get_safety_price_table()[self.status.get_so_amount()+1])
|
||||
new_order = self.broker.new_limit_order(self.status.get_pair(),so_size,"sell",self.status.get_safety_price_table()[self.status.get_so_amount()+1], log="send_new_safety_order_batch")
|
||||
else:
|
||||
new_order = self.broker.new_limit_order(self.status.get_pair(),so_size/self.status.get_safety_price_table()[self.status.get_so_amount()+1],"buy",self.status.get_safety_price_table()[self.status.get_so_amount()+1])
|
||||
new_order = self.broker.new_limit_order(self.status.get_pair(),so_size/self.status.get_safety_price_table()[self.status.get_so_amount()+1],"buy",self.status.get_safety_price_table()[self.status.get_so_amount()+1],log="send_new_safety_order_batch")
|
||||
|
||||
if new_order==1:
|
||||
self.broker.logger.log_this("Not enough balance to send a new safety order",1,self.status.get_pair())
|
||||
|
|
@ -955,21 +966,26 @@ class trader:
|
|||
return 1
|
||||
|
||||
#Pause the trader
|
||||
self.pause = True
|
||||
self.status.set_pause_reason("renew_tp_and_so_routine")
|
||||
self.set_pause(True, "renew_tp_and_so_routine")
|
||||
|
||||
#Save the order
|
||||
if self.broker.get_follow_order_history():
|
||||
for item in filled_safety_orders:
|
||||
self.status.update_deal_order_history(item)
|
||||
self.status.update_deal_order_history(item, "so")
|
||||
|
||||
#Add the amount filled in the last safety orders to the totals
|
||||
previous_base = self.status.get_base_bought()
|
||||
previous_quote = self.status.get_quote_spent()
|
||||
previous_fees_paid_in_quote = self.status.get_fees_paid_in_quote()
|
||||
for order in filled_safety_orders:
|
||||
safety_orders_to_remove_by_id.append(order["id"])
|
||||
new_fees_base,new_fees_quote = self.parse_fees(order)
|
||||
self.status.set_fees_paid_in_quote(self.status.get_fees_paid_in_quote() + new_fees_quote)
|
||||
self.status.set_base_bought(self.status.get_base_bought() + order["filled"] - new_fees_base)
|
||||
self.status.set_quote_spent(self.status.get_quote_spent() + order["cost"])
|
||||
new_fees_base, new_fees_quote = self.parse_fees(order)
|
||||
previous_fees_paid_in_quote += new_fees_quote
|
||||
previous_base = previous_base + order["filled"] - new_fees_base
|
||||
previous_quote += order["cost"]
|
||||
self.status.set_base_bought(previous_base)
|
||||
self.status.set_quote_spent(previous_quote)
|
||||
self.status.set_fees_paid_in_quote(previous_fees_paid_in_quote)
|
||||
|
||||
#Remove the filled safety orders from the list
|
||||
if safety_orders_to_remove_by_id!=[]:
|
||||
|
|
@ -992,7 +1008,7 @@ class trader:
|
|||
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.status.get_pair())
|
||||
if self.broker.get_follow_order_history():
|
||||
self.status.update_deal_order_history(old_tp_order)
|
||||
self.status.update_deal_order_history(old_tp_order, "old_tp")
|
||||
#self.status.set_base_bought(old_tp_order["remaining"])
|
||||
# Partial profit calculation
|
||||
if not self.config.get_is_short():
|
||||
|
|
@ -1005,56 +1021,33 @@ class trader:
|
|||
self.status.set_fees_paid_in_base(self.status.get_fees_paid_in_base() + self.parse_fees(old_tp_order)[0])
|
||||
|
||||
#Cooldown
|
||||
time.sleep(self.broker.get_wait_before_new_safety_order())
|
||||
|
||||
#Send new SO(s)
|
||||
#Do not send new orders if the max amount is reached or surpassed.
|
||||
#It can happen if the max amount of concurrent orders is modified through an API call.
|
||||
max_orders = self.config.get_concurrent_safety_orders() if not self.status.get_is_boosted() else self.config.get_boosted_concurrent_safety_orders()
|
||||
if len(self.status.get_safety_orders())<max_orders:
|
||||
self.send_new_safety_order_batch(max_orders-len(self.status.get_safety_orders()))
|
||||
#Cooldown
|
||||
time.sleep(self.broker.get_wait_time())
|
||||
#time.sleep(self.broker.get_wait_before_new_safety_order())
|
||||
|
||||
#Send new TP 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.status.get_pair())
|
||||
self.status.set_pause_reason(error_string)
|
||||
return 4
|
||||
return 4
|
||||
|
||||
#Cooldown
|
||||
#time.sleep(self.broker.get_wait_before_new_safety_order())
|
||||
|
||||
#Send new safety order(s)
|
||||
#Do not send new orders if the max amount is reached or surpassed.
|
||||
max_orders = self.config.get_concurrent_safety_orders() if not self.status.get_is_boosted() else self.config.get_boosted_concurrent_safety_orders()
|
||||
if len(self.status.get_safety_orders())<max_orders:
|
||||
self.send_new_safety_order_batch(max_orders-len(self.status.get_safety_orders()))
|
||||
|
||||
#Update status dict
|
||||
self.update_status(True)
|
||||
|
||||
#Toggle the pause flag
|
||||
self.pause = False
|
||||
self.status.set_pause_reason("")
|
||||
self.set_pause(False)
|
||||
|
||||
#Done
|
||||
return 0
|
||||
|
||||
|
||||
def check_old_long_price(self) -> int:
|
||||
'''
|
||||
Checks if short price exceeds old long price. If so, send a Telegram message
|
||||
'''
|
||||
|
||||
price_exceeds = False
|
||||
if self.status.get_old_long()!={}:
|
||||
price_exceeds = self.status.get_price()>float(self.status.get_old_long()["tp_price"])
|
||||
if price_exceeds:
|
||||
self.warnings["short_price_exceeds_old_long"] = True
|
||||
else:
|
||||
self.warnings["short_price_exceeds_old_long"] = False
|
||||
self.warnings["speol_notified"] = False
|
||||
if not self.warnings["speol_notified"] and price_exceeds:
|
||||
#Only notify one time AND if autoswitch is off
|
||||
self.warnings["speol_notified"] = True
|
||||
if not self.config.get_autoswitch():
|
||||
message = f"{self.base}@{self.status.get_price()} ({str(self.broker.exchange)}), exceeds old long price of {self.status.get_old_long()['tp_price']}"
|
||||
self.broker.logger.log_this(message,0,self.status.get_pair())
|
||||
return 0
|
||||
|
||||
|
||||
def check_orderbook_depth(self, threshold: float, order_size: float, old_price: float = 0, size_in_quote = True) -> bool:
|
||||
'''
|
||||
|
|
@ -1099,6 +1092,21 @@ class trader:
|
|||
return False
|
||||
|
||||
|
||||
def check_old_long(self, fetch_price=False):
|
||||
'''
|
||||
Check if it is profitable to switch to back to long.
|
||||
Returns True if it is profitable, False otherwise.
|
||||
|
||||
If selling the base currency left at the current market price plus the quote already received turns out to be more than the old long deal target,
|
||||
it means that we already are in profit territory, switch back to long.
|
||||
A more conservative approach would be old_target = self.status.get_old_long()["quote_spent"], just breaking even.
|
||||
'''
|
||||
price = self.status.get_price() if not fetch_price else self.broker.get_top_bid_price(self.status.get_pair())
|
||||
old_target = self.status.get_old_long()["tp_price"]*self.status.get_old_long()["tp_amount"]
|
||||
base_left = self.status.get_old_long()["tp_amount"]-self.status.get_base_bought()
|
||||
return (base_left*price)+self.status.get_quote_spent()>=old_target
|
||||
|
||||
|
||||
def check_status(self,open_orders: list) -> int: #Should I change the order? Check the SO first?
|
||||
'''
|
||||
Main routine. It checks for closed orders and proceeds accordingly.
|
||||
|
|
@ -1109,25 +1117,16 @@ class trader:
|
|||
self.update_status(False)
|
||||
return 0
|
||||
|
||||
#Check if short price exceeds old long price. If so, send a Telegram message
|
||||
if self.config.get_is_short() and self.status.get_old_long()!={} and self.config.get_check_old_long_price():
|
||||
self.check_old_long_price()
|
||||
|
||||
self.status.set_pause_reason("check for autoswitch")
|
||||
#If it's a short trader that used to be long AND autoswitch is enabled
|
||||
if self.config.get_is_short() and self.config.get_autoswitch() and self.status.get_old_long()!={}:
|
||||
#If selling the base currency left at the current market price plus the quote already received turns out to be more than the old long deal target,
|
||||
# it means that we already are in profit territory, switch back to long.
|
||||
#A more conservative approach would be old_target = self.status.get_old_long()["quote_spent"], just breaking even.
|
||||
old_target = self.status.get_old_long()["tp_price"]*self.status.get_old_long()["tp_amount"]
|
||||
base_left = self.status.get_old_long()["tp_amount"]-self.status.get_base_bought()
|
||||
if (base_left*self.status.get_price())+self.status.get_quote_spent()>=old_target:
|
||||
if self.check_old_long():
|
||||
#Sell all base (market), report the profits and restart the trader
|
||||
self.status.set_pause_reason("automatic_switch")
|
||||
self.switch_to_long(already_received_quote=self.status.get_quote_spent())
|
||||
if not self.config.get_liquidate_after_switch():
|
||||
self.restart = True
|
||||
return 1
|
||||
if self.switch_to_long(already_received_quote=self.status.get_quote_spent())!=2:
|
||||
if not self.config.get_liquidate_after_switch():
|
||||
self.restart = True
|
||||
return 1
|
||||
#Check for autoswitch (long->short)
|
||||
if not self.config.get_is_short() and self.status.get_so_amount()==self.status.get_no_of_safety_orders() and self.config.get_autoswitch():
|
||||
self.switch_to_short()
|
||||
|
|
@ -1161,17 +1160,6 @@ class trader:
|
|||
self.status.set_pause_reason("check if tp_order is filled")
|
||||
#Checks if the take profit order is filled
|
||||
if self.status.get_take_profit_order()["id"] not in open_orders_ids:
|
||||
# Check if the order has a wrong id. If so, update the order.
|
||||
# To cover a very rare case that happens if the trader sends a new take profit order but is interrupted before saving the status.
|
||||
# Not sure if it is worth to keep this code.
|
||||
for order in open_orders_list:
|
||||
if order["amount"]==self.status.get_take_profit_order()["amount"] and order["price"]==self.status.get_take_profit_order()["price"] and order["side"]==self.status.get_take_profit_order()["side"]:
|
||||
#Right order, wrong id. Update order
|
||||
self.broker.logger.log_this(f"Updating take profit order for {self.status.get_pair()}",1,self.status.get_pair())
|
||||
self.status.set_take_profit_order(order)
|
||||
self.update_status(True)
|
||||
return 0
|
||||
|
||||
tp_status = self.broker.get_order(self.status.get_take_profit_order()["id"],self.status.get_pair())
|
||||
if tp_status["status"]=="closed":
|
||||
if tp_status["filled"]>0:
|
||||
|
|
@ -1208,13 +1196,12 @@ class trader:
|
|||
filled_ids.append(order["id"])
|
||||
|
||||
if filled_ids!=[]:
|
||||
#closed_orders = self.broker.get_closed_orders(self.status.get_pair())
|
||||
#filled_orders = [item for item in closed_orders if item["id"] in filled_ids and item["status"]=="closed"]
|
||||
filled_orders = []
|
||||
for id in filled_ids:
|
||||
order = self.broker.get_order(id, self.status.get_pair())
|
||||
if order["status"]=="closed":
|
||||
filled_orders.append(order)
|
||||
time.sleep(self.broker.get_wait_time())
|
||||
if len(filled_orders)>0: #To make sure that the safety orders are actually filled (Kucoin sometimes sends incomplete order lists)
|
||||
self.status.set_safety_orders_filled(self.status.get_safety_orders_filled()+len(filled_orders))
|
||||
renew_outcome = self.renew_tp_and_so_routine(filled_orders)
|
||||
|
|
@ -1234,8 +1221,7 @@ class trader:
|
|||
return 1
|
||||
elif renew_outcome==3:
|
||||
self.broker.logger.log_this(f"Can't cancel old take profit order. renew_tp_and_so_routine returned 3",1,self.status.get_pair())
|
||||
self.pause = False
|
||||
self.status.set_pause_reason("")
|
||||
self.set_pause(False)
|
||||
if self.config.get_attempt_restart():
|
||||
self.status.save_to_file(is_backup=True)
|
||||
self.restart = True
|
||||
|
|
@ -1255,11 +1241,15 @@ class trader:
|
|||
|
||||
if condition_a and condition_b:
|
||||
amount_to_send = max_concurrent_safety_orders-len(self.status.get_safety_orders())
|
||||
self.pause = True
|
||||
self.set_pause(True, "sending safety order batch")
|
||||
self.send_new_safety_order_batch(amount_to_send)
|
||||
self.pause = False
|
||||
self.set_pause(False)
|
||||
self.update_status(True)
|
||||
|
||||
#Base check
|
||||
if self.base_diff_check and time.time()>self.base_check_time+self.base_check_interval:
|
||||
self.base_check()
|
||||
|
||||
#Render status line(s)
|
||||
self.status.set_status_string(self.generate_status_strings())
|
||||
|
||||
|
|
@ -1272,6 +1262,14 @@ class trader:
|
|||
return 0
|
||||
|
||||
|
||||
def base_check(self):
|
||||
self.base_check_time = time.time()
|
||||
current_base_balance = self.fetch_free_base()
|
||||
#3. If self.base_amount_missing==current_base_balance: replace take profit order
|
||||
#4. Set self.base_diff_check to False
|
||||
|
||||
|
||||
|
||||
def check_boosted(self):
|
||||
'''
|
||||
Checks if the trader qualifies for boost:
|
||||
|
|
@ -1353,15 +1351,21 @@ class trader:
|
|||
return 1
|
||||
if self.config.get_is_short():
|
||||
self.status.set_take_profit_price(self.status.get_quote_spent()/self.status.get_base_bought()*(1-(self.get_tp_level(self.status.get_so_amount())-1)))
|
||||
self.status.set_take_profit_order(self.broker.new_limit_order(self.status.get_pair(),self.status.get_base_bought(),"buy",self.status.get_take_profit_price()))
|
||||
self.status.set_take_profit_order(self.broker.new_limit_order(self.status.get_pair(),self.status.get_base_bought(),"buy",self.status.get_take_profit_price(),log="new_tp_order"))
|
||||
else:
|
||||
self.status.set_take_profit_price(self.status.get_quote_spent()/self.status.get_base_bought()*self.get_tp_level(self.status.get_so_amount()))
|
||||
self.status.set_take_profit_order(self.broker.new_limit_order(self.status.get_pair(),self.status.get_base_bought(),"sell",self.status.get_take_profit_price()))
|
||||
if self.status.get_take_profit_order()==1: #This means that there was a miscalculation of base currency amount, let's correct it.
|
||||
self.status.set_take_profit_order(self.broker.new_limit_order(self.status.get_pair(),self.status.get_base_bought(),"sell",self.status.get_take_profit_price(),log="new_tp_order"))
|
||||
if self.status.get_take_profit_order()==1: #This means that there was a miscalculation of base currency amount
|
||||
if self.config.get_is_short(): #If in short mode, we don't recalculate anything.
|
||||
return 1
|
||||
adjusted = self.adjust_base()
|
||||
if adjusted is not None:
|
||||
# if self.status.get_base_bought()-adjusted>=adjusted*self.min_base_difference:
|
||||
# #Enabling base check
|
||||
# self.broker.logger.log_this("Enabling base check",1,self.status.get_pair())
|
||||
# self.base_diff_check = True
|
||||
# self.base_check_time = time.time()
|
||||
# self.base_amount_missing = self.status.get_base_bought()-adjusted
|
||||
self.status.set_base_bought(adjusted)
|
||||
self.status.set_take_profit_order(None) #Just to be able to iterate
|
||||
if self.status.get_take_profit_order() not in [None,self.broker.get_empty_order()]:
|
||||
|
|
@ -1514,7 +1518,7 @@ class trader:
|
|||
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"])
|
||||
return self.broker.new_limit_order(f"{self.base}/{new_quote}",old_order["amount"],old_order["side"],old_order["price"],log="quote_currency_replace_order")
|
||||
|
||||
|
||||
def quote_currency_switch_configs(self, new_quote: str) -> int:
|
||||
|
|
@ -1541,8 +1545,8 @@ class trader:
|
|||
#If there is an old_long file, also copy it
|
||||
if self.config.get_is_short() and self.status.get_old_long()!={}:
|
||||
try:
|
||||
with open(f"status/{self.base}{self.quote}.oldlong","w") as c:
|
||||
c.write(dumps(self.status.get_old_long(), indent=4))
|
||||
with open(f"status/{self.base}{self.quote}.oldlong","w") as old_long_file_handler:
|
||||
old_long_file_handler.write(dumps(self.status.get_old_long(), indent=4))
|
||||
except Exception as e:
|
||||
self.broker.logger.log_this(f"Exception while writing new old_long file: {e}",1,self.status.get_pair())
|
||||
|
||||
|
|
@ -1647,7 +1651,6 @@ class trader:
|
|||
line3 = ""
|
||||
if self.status.get_base_bought()!=0:
|
||||
line3 = draw_line(mid_price,low_price,high_price,self.status.get_quote_spent()/self.status.get_base_bought())
|
||||
p = "*PAUSED*" if self.pause==True else ""
|
||||
low_boundary_color = self.get_color("red")
|
||||
price_color = self.get_color("white")
|
||||
target_price_color = self.get_color("green")
|
||||
|
|
@ -1687,7 +1690,7 @@ class trader:
|
|||
|
||||
safety_order_string = f"{self.status.get_safety_orders_filled()}/{self.get_color('cyan')}{concurrent_so_amount}{self.get_color('white')}/{self.status.get_no_of_safety_orders()}".rjust(27)
|
||||
prices = f"{low_boundary_color}{low_boundary}{self.get_color('white')}|{price_color}{mid_boundary}{self.get_color('white')}|{target_price_color}{high_boundary}{self.get_color('white')}|{pct_color}{pct_to_profit_str}%{self.get_color('white')}"
|
||||
line1 = f"{p}{pair_color}{self.status.get_pair().center(13)}{self.get_color('white')}| {safety_order_string} |{prices}| Uptime: {self.seconds_to_time(self.status.get_deal_uptime())}"
|
||||
line1 = f"{pair_color}{self.status.get_pair().center(13)}{self.get_color('white')}| {safety_order_string} |{prices}| Uptime: {self.seconds_to_time(self.status.get_deal_uptime())}"
|
||||
if self.status.get_is_boosted():
|
||||
line1 = f"{line1} | BOOSTED"
|
||||
if self.config.get_autoswitch():
|
||||
|
|
@ -1708,6 +1711,16 @@ class trader:
|
|||
return status_string
|
||||
|
||||
|
||||
def set_pause(self, pause: bool, msg: str = "") -> None:
|
||||
'''
|
||||
Sets the pause state and reason
|
||||
'''
|
||||
|
||||
self.pause = pause
|
||||
self.status.set_pause_reason(msg)
|
||||
return
|
||||
|
||||
|
||||
def load_imported_trader(self) -> int:
|
||||
'''
|
||||
Loads status dictionary, orders and sets up variables
|
||||
|
|
@ -1729,8 +1742,7 @@ class trader:
|
|||
return 1
|
||||
|
||||
#Done
|
||||
self.pause = False
|
||||
self.status.set_pause_reason("")
|
||||
self.set_pause(False)
|
||||
self.update_status(True)
|
||||
return 0
|
||||
|
||||
|
|
@ -43,7 +43,8 @@ INSTANCE
|
|||
10) edit_call_wait_time 11) reload_markets 12) fetch_full_log
|
||||
13) paused_traders 14) fetch_log 15) edit_cooldown_multiplier
|
||||
16) get_balance 17) cancel_global_last_call
|
||||
18) mod_default_order_size
|
||||
18) mod_default_order_size 19) toggle_log_orders
|
||||
20) refresh_log_cache
|
||||
|
||||
EARN
|
||||
31) toggle_pause 32) get_step_size 33) set_step_size
|
||||
|
|
@ -341,6 +342,19 @@ if __name__=="__main__":
|
|||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||
input("Press ENTER to continue ")
|
||||
|
||||
elif command==19:
|
||||
print("toggle_log_orders turns on or off the logging of orders")
|
||||
if input("Proceed? (Y/n) ") in ["Y","y",""]:
|
||||
url = f"{base_url}{port}/toggle_log_orders"
|
||||
print(json.loads(requests.post(url, headers=headers).content))
|
||||
input("Press ENTER to continue ")
|
||||
|
||||
elif command==20:
|
||||
print("refresh_log_cache refreshes the log cache")
|
||||
if input("Proceed? (Y/n) ") in ["Y","y",""]:
|
||||
url = f"{base_url}{port}/refresh_log_cache"
|
||||
print(json.loads(requests.post(url, headers=headers).content))
|
||||
input("Press ENTER to continue ")
|
||||
|
||||
######################
|
||||
######## EARN ########
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ import calendar
|
|||
import logging
|
||||
import threading
|
||||
import os
|
||||
from collections import deque
|
||||
from typing import Iterable, List, Tuple
|
||||
from contextlib import contextmanager
|
||||
from flask import Flask, jsonify, request, Response
|
||||
from flask import Flask, jsonify, request
|
||||
from waitress import serve
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue