Compare commits
No commits in common. "main" and "2025.10.01" have entirely different histories.
main
...
2025.10.01
|
|
@ -1,45 +1,3 @@
|
|||
2025.12.01:
|
||||
. Modified log output of new_market_order.
|
||||
. Modified Kucoin's case in min_amount_of_base.
|
||||
|
||||
2025.11.11:
|
||||
. deals_cache and log_list cache are now 20 items long.
|
||||
. Less log spam.
|
||||
|
||||
2025.11.08:
|
||||
. broker.set_default_order_size() now saves the config file to disk after changing the value.
|
||||
. Variable renaming and other small stuff.
|
||||
|
||||
2025.10.24:
|
||||
. Toggling liquidate_after_switch now writes the config file to disk so the setting persists between trades.
|
||||
. Manually switching to long now sets double_check_price to false.
|
||||
. Added a few comments to switch_to_long.
|
||||
|
||||
2025.10.12:
|
||||
. do_cleanup relocated after generating the safety orders' prices.
|
||||
|
||||
2025.10.11:
|
||||
. Minor simplification in do_cleanup.
|
||||
. Removed a couple of (no longer needed?) pauses.
|
||||
|
||||
2025.10.10:
|
||||
. 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -252,7 +252,6 @@ 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):
|
||||
|
|
|
|||
|
|
@ -19,14 +19,13 @@ class Broker:
|
|||
#Default values
|
||||
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_after_initial_market_order = self.broker_config.get("wait_after_initial_market_order",2)
|
||||
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"
|
||||
|
|
@ -51,7 +50,7 @@ class Broker:
|
|||
self.markets = self.exchange.load_markets()
|
||||
|
||||
#Populates deals cache
|
||||
self.deals_cache_length = 20
|
||||
self.deals_cache_length = 10
|
||||
self.deals_list = self.preload_deals(amount_to_preload=self.deals_cache_length)
|
||||
|
||||
|
||||
|
|
@ -82,16 +81,6 @@ 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
|
||||
|
|
@ -224,7 +213,6 @@ 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
|
||||
|
|
@ -664,7 +652,7 @@ class Broker:
|
|||
return amount
|
||||
|
||||
|
||||
def new_simulated_market_order(self,symbol,size,side,amount_in_base=False,no_retries=False,log=""):
|
||||
def new_simulated_market_order(self,symbol,size,side,amount_in_base=False,no_retries=False):
|
||||
'''
|
||||
TODO: Emulating Market Orders With Limit Orders
|
||||
|
||||
|
|
@ -694,9 +682,7 @@ 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)
|
||||
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}")
|
||||
new_order = self.exchange.create_market_buy_order_with_cost(symbol, size)
|
||||
else:
|
||||
order_book = self.get_order_book(symbol)
|
||||
if order_book=={}:
|
||||
|
|
@ -710,8 +696,6 @@ 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:
|
||||
|
|
@ -758,7 +742,7 @@ class Broker:
|
|||
return None
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
'''
|
||||
Sends a new market order to the exchange.
|
||||
|
||||
|
|
@ -783,13 +767,11 @@ 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)
|
||||
|
||||
return self.get_order(order_to_send["id"],symbol)
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in new_market_order: {e} - Side: {side} - Size: {size}",1,symbol)
|
||||
self.logger.log_this(f"Exception in new_market_order: {e}",1,symbol)
|
||||
if no_retries:
|
||||
break
|
||||
time.sleep(self.wait_time)
|
||||
|
|
@ -871,7 +853,7 @@ class Broker:
|
|||
# return returned_orders
|
||||
|
||||
|
||||
def new_limit_order(self,symbol,size,side,price,no_retries=False,log=""):
|
||||
def new_limit_order(self,symbol,size,side,price,no_retries=False):
|
||||
'''
|
||||
Sends a new limit order.
|
||||
|
||||
|
|
@ -887,8 +869,6 @@ 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)
|
||||
|
|
@ -988,7 +968,7 @@ class Broker:
|
|||
if self.get_exchange_name() in ["okex","bybit"]:
|
||||
return float(market["limits"]["amount"]["min"])
|
||||
elif self.get_exchange_name() in ["kucoin"]:
|
||||
return max(float(market["limits"]["amount"]["min"]),(float(market["limits"]["cost"]["min"])+.25)/self.get_ticker_price(pair))
|
||||
return (float(market["limits"]["cost"]["min"])+.25)/self.get_ticker_price(pair)
|
||||
elif self.get_exchange_name() in ["gateio"]:
|
||||
return (float(market["limits"]["cost"]["min"])+.1)/self.get_ticker_price(pair)
|
||||
elif self.get_exchange_name()=="binance":
|
||||
|
|
@ -1052,7 +1032,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 = 20 # log cache
|
||||
self.log_list_max_length = 10
|
||||
self.log_list = collections.deque(maxlen=self.log_list_max_length)
|
||||
self.preload_logs()
|
||||
|
||||
|
|
@ -1068,16 +1048,6 @@ 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
|
||||
|
|
@ -1114,9 +1084,6 @@ 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):
|
||||
'''
|
||||
|
|
@ -1138,6 +1105,8 @@ 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:
|
||||
|
|
|
|||
67
main.py
67
main.py
|
|
@ -18,7 +18,7 @@ import exchange_wrapper
|
|||
import trader
|
||||
|
||||
|
||||
version = "2025.12.01"
|
||||
version = "2025.10.01"
|
||||
|
||||
'''
|
||||
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
||||
|
|
@ -1238,20 +1238,6 @@ 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():
|
||||
'''
|
||||
|
|
@ -1308,21 +1294,6 @@ 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():
|
||||
'''
|
||||
|
|
@ -1642,7 +1613,7 @@ def unwrapped_switch_to_long(base,quote,calculate_profits):
|
|||
for instance in running_traders:
|
||||
if f"{base}/{quote}"==instance.status.get_pair():
|
||||
instance.set_pause(True, "Switching to long mode")
|
||||
if instance.switch_to_long(ignore_old_long=ignore_old_long,double_check_price=False)==1:
|
||||
if instance.switch_to_long(ignore_old_long=ignore_old_long)==1:
|
||||
return jsonify({"Error": "Error in switch_to_long()"})
|
||||
if instance.start_trader()==1:
|
||||
instance.quit = True
|
||||
|
|
@ -1810,7 +1781,8 @@ def unwrapped_add_safety_orders(base,quote,amount):
|
|||
for instance in running_traders:
|
||||
if symbol==instance.status.get_pair():
|
||||
instance.set_pause(True, "Adding safety orders")
|
||||
instance.status.set_no_of_safety_orders(instance.status.get_no_of_safety_orders()+int(amount))
|
||||
#x.no_of_safety_orders += int(amount)
|
||||
instance.config.set_no_of_safety_orders(instance.config.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)
|
||||
|
|
@ -2280,7 +2252,6 @@ 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.
|
||||
|
|
@ -2389,20 +2360,6 @@ 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
|
||||
|
|
@ -2474,22 +2431,6 @@ 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.
|
||||
|
|
|
|||
105
trader.py
105
trader.py
|
|
@ -42,8 +42,8 @@ class trader:
|
|||
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 old_long_file_handler:
|
||||
self.status.set_old_long(load(old_long_file_handler))
|
||||
with open(f"status/{self.base}{self.quote}.oldlong") as ol:
|
||||
self.status.set_old_long(load(ol))
|
||||
except Exception as e:
|
||||
self.broker.logger.log_this(f"Exception: No old_long file. {e}",1,base_quote)
|
||||
|
||||
|
|
@ -155,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,log="start_trader-missing_base")
|
||||
self.broker.new_market_order(self.status.get_pair(),amount_to_buy,"buy",amount_in_base=True)
|
||||
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()
|
||||
|
|
@ -171,8 +171,6 @@ 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
|
||||
|
|
@ -204,6 +202,7 @@ class trader:
|
|||
|
||||
self.status.set_pause_reason("start_trader - after slippage")
|
||||
|
||||
|
||||
#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.
|
||||
|
|
@ -212,7 +211,7 @@ class trader:
|
|||
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,log="start_trader")
|
||||
first_order = self.broker.new_market_order(self.status.get_pair(),self.status.get_order_size(),action)
|
||||
#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())
|
||||
|
|
@ -281,11 +280,6 @@ 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())
|
||||
|
|
@ -299,6 +293,11 @@ 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.set_pause(False)
|
||||
|
|
@ -450,10 +449,14 @@ class trader:
|
|||
self.broker.logger.log_this("Can't fetch free base",1,self.status.get_pair())
|
||||
return 1
|
||||
|
||||
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())
|
||||
#If the balance is greater than the size of the first safety order, sell the difference.
|
||||
# Sometimes when an order is filled the balance is not updated immediately, so having a bit of a buffer irons out a couple of issues.
|
||||
min_size = self.status.get_safety_orders()[0]["amount"]
|
||||
|
||||
if (balance_in_account-min_size)*self.status.get_start_price()>self.broker.get_min_quote_size(self.status.get_pair()):
|
||||
self.broker.logger.log_this(f"Balance to clean: {balance_in_account-min_size} {self.base}",2,self.status.get_pair())
|
||||
self.broker.logger.log_this("Sending cleanup order...",2,self.status.get_pair())
|
||||
cleanup_order = self.broker.new_limit_order(self.status.get_pair(),balance_in_account,"sell",self.status.get_take_profit_price(),no_retries=True,log="cleanup")
|
||||
cleanup_order = self.broker.new_limit_order(self.status.get_pair(),balance_in_account-min_size,"sell",self.status.get_take_profit_price(),no_retries=True)
|
||||
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
|
||||
|
|
@ -572,8 +575,8 @@ class trader:
|
|||
"datetime": time.strftime("[%Y/%m/%d %H:%M:%S]")
|
||||
})
|
||||
try:
|
||||
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))
|
||||
with open(f"status/{self.base}{self.quote}.oldlong","w") as s:
|
||||
s.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())
|
||||
|
||||
|
|
@ -606,19 +609,17 @@ class trader:
|
|||
|
||||
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 old_long_file_handler:
|
||||
self.status.set_old_long(load(old_long_file_handler))
|
||||
with open(f"status/{self.base}{self.quote}.oldlong") as f:
|
||||
self.status.set_old_long(load(f))
|
||||
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())
|
||||
|
|
@ -626,7 +627,6 @@ 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:
|
||||
|
|
@ -635,21 +635,18 @@ 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 0
|
||||
return 1
|
||||
|
||||
#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"):
|
||||
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))
|
||||
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))
|
||||
if self.config.load_from_file()==1:
|
||||
self.config.reset_to_default()
|
||||
else:
|
||||
|
|
@ -671,7 +668,6 @@ 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
|
||||
|
||||
|
||||
|
|
@ -687,7 +683,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",log="liquidate_base")
|
||||
order = self.broker.new_market_order(self.status.get_pair(),free_base,"sell")
|
||||
time.sleep(self.broker.get_wait_time()*2)
|
||||
tries = self.broker.get_retries()
|
||||
while True:
|
||||
|
|
@ -727,7 +723,7 @@ class trader:
|
|||
|
||||
#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,log="force_close")
|
||||
market_order = self.broker.new_market_order(self.status.get_pair(),amount,"sell",amount_in_base=True)
|
||||
time.sleep(self.broker.get_wait_time()*2)
|
||||
|
||||
#Wait for it to be filled
|
||||
|
|
@ -798,7 +794,7 @@ class trader:
|
|||
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
|
||||
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")
|
||||
market_order = self.broker.new_market_order(self.status.get_pair(),partial_filled_amount,"sell",amount_in_base=True)
|
||||
time.sleep(self.broker.get_wait_time()*2)
|
||||
tries = self.broker.get_retries()
|
||||
while True:
|
||||
|
|
@ -808,11 +804,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())
|
||||
self.broker.logger.log_this("Waiting for partial fill sell order to fill.",1,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 filled.",1,self.status.get_pair())
|
||||
self.broker.logger.log_this("Partial fill sell order not filling.",1,self.status.get_pair())
|
||||
break
|
||||
|
||||
if not self.broker.check_for_duplicate_profit_in_db(filled_order):
|
||||
|
|
@ -929,9 +925,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], log="send_new_safety_order_batch")
|
||||
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])
|
||||
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],log="send_new_safety_order_batch")
|
||||
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])
|
||||
|
||||
if new_order==1:
|
||||
self.broker.logger.log_this("Not enough balance to send a new safety order",1,self.status.get_pair())
|
||||
|
|
@ -1021,23 +1017,24 @@ 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())
|
||||
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_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
|
||||
|
||||
#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()))
|
||||
return 4
|
||||
|
||||
#Update status dict
|
||||
self.update_status(True)
|
||||
|
|
@ -1199,9 +1196,9 @@ class trader:
|
|||
filled_orders = []
|
||||
for id in filled_ids:
|
||||
order = self.broker.get_order(id, self.status.get_pair())
|
||||
time.sleep(self.broker.get_wait_time())
|
||||
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)
|
||||
|
|
@ -1351,10 +1348,10 @@ 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(),log="new_tp_order"))
|
||||
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()))
|
||||
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(),log="new_tp_order"))
|
||||
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
|
||||
if self.config.get_is_short(): #If in short mode, we don't recalculate anything.
|
||||
return 1
|
||||
|
|
@ -1518,7 +1515,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"],log="quote_currency_replace_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:
|
||||
|
|
@ -1545,8 +1542,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 old_long_file_handler:
|
||||
old_long_file_handler.write(dumps(self.status.get_old_long(), indent=4))
|
||||
with open(f"status/{self.base}{self.quote}.oldlong","w") as c:
|
||||
c.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())
|
||||
|
||||
|
|
|
|||
|
|
@ -43,8 +43,7 @@ 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 19) toggle_log_orders
|
||||
20) refresh_log_cache
|
||||
18) mod_default_order_size
|
||||
|
||||
EARN
|
||||
31) toggle_pause 32) get_step_size 33) set_step_size
|
||||
|
|
@ -342,19 +341,6 @@ 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,8 +5,10 @@ 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
|
||||
from flask import Flask, jsonify, request, Response
|
||||
from waitress import serve
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue