/force_trader_close

This commit is contained in:
Nicolás Sánchez 2025-09-02 16:04:52 -03:00
parent 694e5a95d1
commit bb3fb692df
4 changed files with 104 additions and 7 deletions

View File

@ -3,7 +3,7 @@ next:
. Now the trader supports multiple safety orders at the same time. . Now the trader supports multiple safety orders at the same time.
. Removed forcing orders when importing a trader. Maybe it will be reinstated at a later date. . Removed forcing orders when importing a trader. Maybe it will be reinstated at a later date.
. Removed endpoint /reload_safety_orders. . Removed endpoint /reload_safety_orders.
. New endpoints: /mod_concurrent_safety orders and /mod_boosted_concurrent_safety_orders. . New endpoints: /mod_concurrent_safety orders, /mod_boosted_concurrent_safety_orders and /force_trader_close.
2025.08.19: 2025.08.19:
. Improved log trimming. . Improved log trimming.

52
main.py
View File

@ -18,7 +18,7 @@ import exchange_wrapper
import trader import trader
version = "2025.09.01" version = "2025.09.02"
''' '''
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
@ -1135,6 +1135,30 @@ def toggle_autoswitch():
return jsonify({'Error': 'Halp'}) return jsonify({'Error': 'Halp'})
@base_api.route("/force_trader_close", methods=['POST'])
def force_trader_close():
'''
POST request
Parameters:
base: str
quote: str
'''
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:
if request.json is None:
return jsonify({'Error': 'request.json is None'})
data = request.json
base = data["base"]
quote = data["quote"]
return unwrapped_force_trader_close(base,quote)
except Exception as e:
print(e)
return jsonify({'Error': 'Halp'})
@base_api.route("/toggle_liquidate_after_switch", methods=['POST']) @base_api.route("/toggle_liquidate_after_switch", methods=['POST'])
def toggle_liquidate_after_switch(): def toggle_liquidate_after_switch():
''' '''
@ -2168,6 +2192,32 @@ def unwrapped_toggle_cleanup(base,quote):
return jsonify({"Error": "Task failed successfully"}) return jsonify({"Error": "Task failed successfully"})
def unwrapped_force_trader_close(base,quote):
'''
Forces a trader to close the position.
Parameters:
base (str): The base currency of the pair to close
quote (str): The quote currency of the pair to close
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
if symbol==instance.status.get_pair():
outcome = instance.force_close()
if outcome==0:
return jsonify({"Success": "Trader closed position successfully"})
return jsonify({"Error": "Error while forcing trader to close position"})
return jsonify({"Error": "Trader not found"})
except Exception as e:
broker.logger.log_this(f"Exception while forcing trader to close position: {e}",1,symbol)
return jsonify({"Error": "Halp"})
def unwrapped_toggle_autoswitch(base,quote): def unwrapped_toggle_autoswitch(base,quote):
''' '''
Signals a trader to enable or disable autoswitch. Signals a trader to enable or disable autoswitch.

View File

@ -702,6 +702,39 @@ class trader:
return 0 return 0
def force_close(self):
'''
This method forces the closing of a deal. It replaces the take profit order with a market order.
A simpler way of doing it would be to edit the price of the take profit order,
but KuCoin only supports order editing on high frequency orders.
'''
self.pause = True
self.status.set_pause_reason("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)
#Wait for it to be filled
tries = self.broker.get_retries()
while True:
order = self.broker.get_order(market_order["id"],self.status.get_pair())
if order["status"]=="closed":
break
tries-=1
if tries==0:
self.broker.logger.log_this("Forced market order not filling.",1,self.status.get_pair())
self.quit = True
return 1
#Call take profit routine
return self.take_profit_routine(order)
def take_profit_routine(self, filled_order: dict) -> int: def take_profit_routine(self, filled_order: dict) -> int:
''' '''
When profit is reached, this method is called to handle the profit calculations, the closing of orders, When profit is reached, this method is called to handle the profit calculations, the closing of orders,
@ -715,7 +748,6 @@ class trader:
self.deals_timestamps.append(time.time()) self.deals_timestamps.append(time.time())
self.deals_timestamps = self.deals_timestamps[-self.config.get_boosted_deals_range():] self.deals_timestamps = self.deals_timestamps[-self.config.get_boosted_deals_range():]
#Let's do some type checking first #Let's do some type checking first
if self.status.get_take_profit_order() is None: if self.status.get_take_profit_order() is None:
self.status.set_pause_reason(time.strftime(f"[%Y/%m/%d %H:%M:%S] | {self.status.get_pair()} | TP order is None")) self.status.set_pause_reason(time.strftime(f"[%Y/%m/%d %H:%M:%S] | {self.status.get_pair()} | TP order is None"))
@ -731,6 +763,7 @@ class trader:
#Cancel all the safety orders ASAP #Cancel all the safety orders ASAP
for order in self.status.get_safety_orders(): for order in self.status.get_safety_orders():
self.broker.cancel_order(order["id"],self.status.get_pair()) self.broker.cancel_order(order["id"],self.status.get_pair())
#Check if some safety orders were filled #Check if some safety orders were filled
for order in self.status.get_safety_orders(): for order in self.status.get_safety_orders():
closed_order = self.broker.get_order(order["id"],self.status.get_pair()) closed_order = self.broker.get_order(order["id"],self.status.get_pair())
@ -738,6 +771,7 @@ class trader:
#If this order wasn't filled, it is safe to assume that no order coming after this one was. #If this order wasn't filled, it is safe to assume that no order coming after this one was.
break break
#Sum the filled amounts #Sum the filled amounts
#Better than this, the total filled and total cost can be used to send a sell market order of the partial filled amount, and add that to the profit.
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()) 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())
self.status.set_base_bought(self.status.get_base_bought() + closed_order["filled"] - self.parse_fees(closed_order)[0]) self.status.set_base_bought(self.status.get_base_bought() + closed_order["filled"] - self.parse_fees(closed_order)[0])
self.status.set_quote_spent(self.status.get_quote_spent() + closed_order["cost"]) self.status.set_quote_spent(self.status.get_quote_spent() + closed_order["cost"])
@ -749,8 +783,7 @@ class trader:
break break
#Now we can clear the safety order list #Now we can clear the safety order list
self.status.set_safety_orders([]) self.status.set_safety_orders([])
if not self.broker.check_for_duplicate_profit_in_db(filled_order): if not self.broker.check_for_duplicate_profit_in_db(filled_order):
self.status.set_pause_reason("calculating profit") self.status.set_pause_reason("calculating profit")
# Calculate the profit # Calculate the profit

View File

@ -64,7 +64,7 @@ TRADERS
68) toggle_check_old_long_price 69) switch_quote_currency 68) toggle_check_old_long_price 69) switch_quote_currency
70) view_old_long 71) switch_price 72) reload_trader_config 70) view_old_long 71) switch_price 72) reload_trader_config
73) toggle_liquidate_after_switch 74) base_add_calculation 73) toggle_liquidate_after_switch 74) base_add_calculation
75) mod_concurrent_safety_orders 75) mod_concurrent_safety_orders 76) force_trader_close
98) Change broker 99) Exit 98) Change broker 99) Exit
''' '''
@ -880,4 +880,18 @@ if __name__=="__main__":
"quote": quote, "quote": quote,
"amount": new_amount} "amount": new_amount}
print(json.loads(requests.post(url, headers=headers, json=parameters).content)) print(json.loads(requests.post(url, headers=headers, json=parameters).content))
input("Press ENTER to continue ") input("Press ENTER to continue ")
elif command==76:
print("force_trader_close forces a trader to close the current position")
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
if not validate_pair(trading_pair):
print("The input is invalid")
break
if input("Proceed? (Y/n) ") in ["Y","y",""]:
url = f"{base_url}{port}/force_trader_close"
base,quote = trading_pair.split("/")
parameters = {"base": base,
"quote": quote}
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
input("Press ENTER to continue ")