/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.
. Removed forcing orders when importing a trader. Maybe it will be reinstated at a later date.
. 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:
. Improved log trimming.

52
main.py
View File

@ -18,7 +18,7 @@ import exchange_wrapper
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
@ -1135,6 +1135,30 @@ def toggle_autoswitch():
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'])
def toggle_liquidate_after_switch():
'''
@ -2168,6 +2192,32 @@ def unwrapped_toggle_cleanup(base,quote):
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):
'''
Signals a trader to enable or disable autoswitch.

View File

@ -702,6 +702,39 @@ class trader:
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:
'''
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 = self.deals_timestamps[-self.config.get_boosted_deals_range():]
#Let's do some type checking first
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"))
@ -731,6 +763,7 @@ class trader:
#Cancel all the safety orders ASAP
for order in self.status.get_safety_orders():
self.broker.cancel_order(order["id"],self.status.get_pair())
#Check if some safety orders were filled
for order in self.status.get_safety_orders():
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.
break
#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.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"])
@ -750,7 +784,6 @@ class trader:
#Now we can clear the safety order list
self.status.set_safety_orders([])
if not self.broker.check_for_duplicate_profit_in_db(filled_order):
self.status.set_pause_reason("calculating profit")
# Calculate the profit

View File

@ -64,7 +64,7 @@ TRADERS
68) toggle_check_old_long_price 69) switch_quote_currency
70) view_old_long 71) switch_price 72) reload_trader_config
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
'''
@ -881,3 +881,17 @@ if __name__=="__main__":
"amount": new_amount}
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
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 ")