i have been trying to create a bot that i forward a certain message to it and it will start trading when the requirements are met
bot in the trading process it wont handle any incoming keyboard markup or update when i tell it to stop trading or even cancel trading before the trading even began
when the bot receives and matches the signals the conversation state changes to SIGNAL_RECIVED and it runs an asyncio task and starts a function called Update_Price_And_Place_order
the said function contains a while loop that updates the market prices
now my problem is that the keyboard buttons for cancel trading does not work in mentioned function
# Import necessary libraries and modulesimport loggingimport nest_asyncioimport asyncioimport reimport MetaTrader5 as mt5import threadingfrom threading import Threadfrom multiprocessing import Processfrom datetime import datetimeimport telegramfrom telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, Userfrom telegram.ext import ( Application, CommandHandler, MessageHandler, ConversationHandler, CallbackContext, filters,)nest_asyncio.apply()# Enable logginglogging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO)logger = logging.getLogger(__name__)# Bot tokenTOKEN = # Define states for the conversationLOGIN, LOGGED_OUT, LOGGED_IN, WAITING_FOR_SIGNAL, SIGNAL_RECIVED, ORDER_PLACED = range( 6)# last ask and bid pricesMatching_Symbol = ""Last_signal_time = 0trading_active = Falseget_tick = FalseBuy_placed_at_top = FalseBuy_placed_at_buttom = FalseBuy_placed_at_instatly = Falsesell_placed_at_top = Falsesell_placed_at_buttom = Falsesell_placed_instatly = FalseAsk = 0Bid = 0last_Ask = 0Last_Bid = 0lot_size = 0.01# define the signal dictionary to store the signal datasignal = {}# Define the regex pattern to match the format of the messageSignal_pattern = re.compile( r"([A-Z]+)\s+([A-Z]+)\s+(\d+_\d+)\s*" r"TP1:\s*(\d+)\s*" r"TP2:\s*(\d+)\s*" r"TP3:\s*(\d+)\s*" r"TP4:\s*(\d+)\s*" r"SL:\s*(\d+)")# Define the start command that shows the Start buttonasync def start(update: Update, context: CallbackContext) -> int: await update.message.reply_text( "Welcome to the bot! Please click 'login' to begin.", reply_markup=ReplyKeyboardMarkup( [["Login"]], one_time_keyboard=True, resize_keyboard=True ), ) return LOGGED_OUT# Define the login functionasync def login_to_mt5(update: Update, context: CallbackContext) -> int: # Initialize MetaTrader 5 if "mt5_login" not in context.user_data: # Ask for MetaTrader 5 login number await update.message.reply_text( "Please enter your MetaTrader 5 login number:", reply_markup=ReplyKeyboardMarkup( [["Cancel"]], one_time_keyboard=True, resize_keyboard=True ), ) context.user_data["state"] = "ask_mt5_login" return LOGIN elif "mt5_password" not in context.user_data: # Ask for MetaTrader 5 password # Store login number context.user_data["mt5_login"] = update.message.text await update.message.reply_text( "Please enter your MetaTrader 5 password:", reply_markup=ReplyKeyboardMarkup( [["Cancel"]], one_time_keyboard=True, resize_keyboard=True ), ) context.user_data["state"] = "ask_mt5_password" return LOGIN elif "mt5_server" not in context.user_data: # Ask for MetaTrader 5 server # Store password context.user_data["mt5_password"] = update.message.text await update.message.reply_text( "Please enter your MetaTrader 5 server:", reply_markup=ReplyKeyboardMarkup( [["Cancel"]], one_time_keyboard=True, resize_keyboard=True ), ) context.user_data["state"] = "ask_mt5_server" return LOGIN else: if "mt5_server" not in context.user_data: # Store server context.user_data["mt5_server"] = update.message.text mt5_login = int(context.user_data["mt5_login"]) mt5_password = context.user_data["mt5_password"] mt5_server = context.user_data["mt5_server"] # Initialize MetaTrader 5 if not mt5.initialize(): await update.message.reply_text("Failed to initialize MetaTrader 5.") logger.error("MT5 initialization failed") return LOGGED_OUT # Attempt to login if not mt5.login(login=mt5_login, password=mt5_password, server=mt5_server): await update.message.reply_text( "MetaTrader 5 login failed. Please check your credentials." ) logger.error( "MT5 login failed", reply_markup=ReplyKeyboardMarkup( [["Login"]], one_time_keyboard=True, resize_keyboard=True ), ) # Clear saved credentials to ask again context.user_data.pop("mt5_login", None) context.user_data.pop("mt5_password", None) context.user_data.pop("mt5_server", None) return LOGGED_OUT # Get and send account information account_info = mt5.account_info() if account_info is None: await update.message.reply_text( "Failed to retrieve MetaTrader 5 account information.", reply_markup=ReplyKeyboardMarkup( [["login"]], one_time_keyboard=True, resize_keyboard=True ), ) logger.error("Failed to retrieve account information") return LOGGED_OUT if account_info is not None: await update.message.reply_text( "successfully retrieved MetaTrader 5 account information", reply_markup=ReplyKeyboardMarkup( [ [ "Start Trading", "get account info", "Get XAUUSD market info", "Change Lot Size", "Logout", ] ], ), ) return LOGGED_IN# get account informationasync def get_account_info(update: Update, context: CallbackContext): """Get and send account information.""" account_info = mt5.account_info() account_info_message = ( f"Account Information:\n" f"name: {account_info.name}\n" f"Balance: {account_info.balance}\n" f"Equity: {account_info.equity}\n" f"Profit: {account_info.profit}\n" f"Currency: {account_info.currency}\n" f"Leverage: {account_info.leverage}\n" ) await update.message.reply_text( account_info_message, reply_markup=ReplyKeyboardMarkup( [ [ "Start Trading", "get account info", "Get XAUUSD market info", "Change Lot Size", "Logout", ] ], ), ) context.user_data["account_info"] = account_info_message # Successful login logger.info("MT5 login and account info retrieval successful") return LOGGED_IN# Modify the start_trading function to wait for the message, check the format, and store it in the signal dictionaryasync def start_trading(update: Update, context: CallbackContext) -> int: global trading_active, get_tick trading_active = True get_tick = True await update.message.reply_text( "Trading started. Please send your signal message in the correct format.", reply_markup=ReplyKeyboardMarkup( [ [ "stop Trading", "Logout", ] ] ), ) return WAITING_FOR_SIGNAL# Handle the Stop Trading buttonasync def stop_trading(update: Update, context: CallbackContext) -> int: global trading_active, get_tick, monitor_task trading_active = False get_tick = False if monitor_task: monitor_task.cancel() # Gracefully stop the monitoring task await update.message.reply_text( "Trading stopped.", reply_markup=ReplyKeyboardMarkup( [ [ "Start Trading", "get account info", "Get XAUUSD market info", "Change Lot Size", "Logout", ] ] ), ) return LOGGED_IN# get current gold market priceasync def Get_XAUUSD_market_info(update: Update, context: CallbackContext) -> int: global Matching_Symbol Get_All_Symbols = mt5.symbols_get() for symbol in Get_All_Symbols: if "XAUUSD" in symbol.name: Matching_Symbol = symbol.name symbol_info = mt5.symbol_info(Matching_Symbol) break symbol_info_massage = ( f"Gold market info:\n" f"Symbol: {Matching_Symbol}\n" f"Ask Price: {symbol_info.ask}\n" f"Bid Price: {symbol_info.bid}\n" f"currency: {symbol_info.currency_base}\n" f"description: {symbol_info.description}\n" ) await update.message.reply_text( symbol_info_massage, reply_markup=ReplyKeyboardMarkup( [ [ "Start Trading", "get account info", "Get XAUUSD market info", "Change Lot Size", "Logout", ] ] ), ) return LOGGED_IN# Change the lot sizeasync def change_lot_size(update: Update, context: CallbackContext): global Lot_Size await update.message.reply_text("Enter the desired Lot Size") Lot_Size = int(update.message.text) await update.message.reply_text( f"Lot Size changed to {Lot_Size}.", reply_markup=ReplyKeyboardMarkup( [ [ "Start Trading", "get account info", "Get XAUUSD market info", "Change Lot Size", "Logout", ] ] ), ) return LOGGED_IN# Handle the incoming message in TRADING stateasync def handle_signal_message(update: Update, context: CallbackContext) -> int: global Signal_pattern, Last_signal_time, signal # Check if 'signal' already exists in context.user_data, otherwise initialize it as a dictionary if "signal" not in context.user_data: context.user_data["signal"] = {} # Get the user's message user_message = update.message.text user_message = user_message.upper() user_message = user_message.encode("ascii", "ignore").decode() user_message = re.sub(r"\s+\s+", "\n", user_message) print(f"Received message: \n {user_message}") # Try to match the message format match = Signal_pattern.match(user_message) print(f"Match: {match}") if match: # Extract the values from the message symbol = match.group(1) Order_type = match.group(2) price_range = match.group(3) tp1 = int(match.group(4)) tp2 = int(match.group(5)) tp3 = int(match.group(6)) tp4 = int(match.group(7)) sl = int(match.group(8)) # Store the extracted information in the signal dictionary signal_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") Last_signal_time = signal_time signal = { signal_time: { "symbol": symbol, "Order_Type": Order_type, "Price_range": price_range, "Take_Profit": tp1, "Stop_Loss": sl, "Lot_Size": lot_size, } } # Save it to the user's context data context.user_data["signal"].update(signal) if "BUY" in Order_type.upper(): order_massage = "📈Order Type: Buy" if "SELL" in Order_type.upper(): order_massage = "📈Order Type: Sell" # Respond with confirmation (fixed string concatenation) response_message = ( f"Signal received and stored waiting for the conditions:\n" f"🕕Time: {signal_time}\n" f"💰Symbol: {symbol}\n" f"{order_massage}\n" f"💵Price Range: {price_range}\n" f"✅Take Profit: {tp1}\n" f"🛑Stop Loss: {sl}\n" f"🧮Lot Size: {lot_size}" ) await update.message.reply_text( response_message, reply_markup=ReplyKeyboardMarkup( [ [ "cancel trade", "Get XAUUSD market info", ] ] ), ) global monitor_task monitor_task = asyncio.create_task( Update_Price_And_Place_order( update, context, Order_type, price_range, tp1, sl, lot_size) ) if monitor_task: result = ( await monitor_task ) # This will wait for the task to complete and get the result if result == ORDER_PLACED: return ORDER_PLACED # Pass it to the conversation handler if result == LOGGED_IN: stop_trading(update, context) return LOGGED_IN # Pass it to the conversation return SIGNAL_RECIVED else: # If the message format is incorrect, notify the user await update.message.reply_text( "Invalid format. Please send the message in the correct format.", reply_markup=ReplyKeyboardMarkup( [ [ "stop Trading", "Logout", ] ] ), ) return WAITING_FOR_SIGNAL# Define the logout functionasync def logout(update: Update, context: CallbackContext) -> int: """Logout from MetaTrader 5.""" global trading_active, get_tick trading_active = False get_tick = False # Check if user is logged in if "mt5_login" in context.user_data: # Terminate MetaTrader 5 session mt5.shutdown() context.user_data.pop("mt5_login", None) context.user_data.pop("mt5_password", None) context.user_data.pop("mt5_server", None) await update.message.reply_text( "You have successfully logged out from MetaTrader 5.", reply_markup=ReplyKeyboardMarkup( [["Login"]], one_time_keyboard=True, resize_keyboard=True ), ) logger.info("MT5 logout successful") else: context.user_data.pop("mt5_login", None) context.user_data.pop("mt5_password", None) context.user_data.pop("mt5_server", None) await update.message.reply_text( "You are not logged in to MetaTrader 5.", reply_markup=ReplyKeyboardMarkup( [["Login"]], one_time_keyboard=True, resize_keyboard=True ), ) logger.warning("MT5 logout attempted without being logged in") return LOGGED_OUT# Cancel command to exit the conversationasync def cancel_login(update: Update, context: CallbackContext) -> int: await update.message.reply_text( "Login process has been canceled.", reply_markup=ReplyKeyboardMarkup( [["Login"]], one_time_keyboard=True, resize_keyboard=True ), ) context.user_data.pop("mt5_login", None) context.user_data.pop("mt5_password", None) context.user_data.pop("mt5_server", None) return LOGGED_OUT# setup the connection to metatrader 5 and update the pricesasync def Update_Price_And_Place_order(update: Update, context: CallbackContext, Order_type, price_range, TP, SL, lot_size): global Ask, Bid, Matching_Symbol, get_tick, last_Ask, Last_Bid # Get market information Get_All_Symbols = mt5.symbols_get() for symbol in Get_All_Symbols: if "XAUUSD" in symbol.name: Matching_Symbol = symbol.name mt5.symbol_select(symbol.name, True) break # Continuously update prices while get_tick: await asyncio.sleep(0.01) tick = mt5.symbol_info_tick(Matching_Symbol) if tick: if Ask != tick.ask or Bid != tick.bid: Ask = tick.ask Bid = tick.bid logger.info(f"prices updated Bid: {Bid}, Ask: {Ask}") await update.message.reply_text( f"prices updated Bid: {Bid}, Ask: {Ask}", reply_markup=ReplyKeyboardMarkup( [ [ "cancel trade", ] ] ), ) logger.info(conv_handler.check_update(update)) if last_Ask == 0 or Last_Bid == 0: last_Ask = Ask Last_Bid = Bid Price1, Price2 = int(price_range.split("_")[0]), int( price_range.split("_")[1] ) Price_Handled = await Handle_Price_Update( update, context, last_Ask, Last_Bid, Order_type, Ask, Bid, TP, SL, lot_size, Price1, Price2, ) if Price_Handled == ORDER_PLACED: return ORDER_PLACED # Propagate ORDER_PLACEDasync def Handle_Price_Update( update: Update, context: CallbackContext, last_Ask, Last_Bid, Order_type, Ask, Bid, TP, SL, lot_size, Price1, Price2,): global Buy_placed_at_top, Buy_placed_at_buttom, Buy_placed_at_instatly, sell_placed_at_top, sell_placed_at_buttom, sell_placed_instatly if "BUY" in Order_type.upper(): if last_Ask < min( Price1, Price2 ): # ask price is under the price range == buy at min price if Buy_placed_at_buttom == False and ( max(Price1, Price2) >= Ask >= min(Price1, Price2) ): # ask price has hit the price range from below Buy_placed_at_buttom = await Buy(int(TP), int(SL), int(lot_size), Ask) logger.info("trying to place a buy at min price") if Buy_placed_at_buttom == ORDER_PLACED: return ORDER_PLACED # Propagate ORDER_PLACED last_Ask = Ask elif last_Ask > max( Price1, Price2 ): # ask price is above the price range == buy at max price and buy at min price if (max(Price1, Price2) >= Ask >= min(Price1, Price2)) and ( Buy_placed_at_top == False ): # ask price has hit the price range from above Buy_placed_at_top = await Buy(int(TP), int(SL), int(lot_size), Ask) logger.info("trying to place a buy at max price") if Buy_placed_at_top == ORDER_PLACED: return ORDER_PLACED # Propagate ORDER_PLACED if (min(Price1, Price2) >= Ask) and ( Buy_placed_at_buttom == False ): # ask price has hit the minimum price Buy_placed_at_buttom = await Buy(int(TP), int(SL), int(lot_size), Ask) logger.info("trying to place a buy at min price") if Buy_placed_at_buttom == ORDER_PLACED: return ORDER_PLACED # Propagate ORDER_PLACED last_Ask = Ask elif ( min(Price1, Price2) <= last_Ask <= max(Price1, Price2) ): # ask price is between the price range == instant buy and buy at min price if Buy_placed_at_instatly == False: Buy_placed_at_instatly = await Buy(int(TP), int(SL), int(lot_size), Ask) logger.info("trying to place a buy instantly") if Buy_placed_at_instatly == ORDER_PLACED: return ORDER_PLACED # Propagate ORDER_PLACED if min(Price1, Price2) >= Ask and Buy_placed_at_buttom == False: Buy_placed_at_buttom = await Buy(int(TP), int(SL), int(lot_size), Ask) logger.info("trying to place a buy at low price") if Buy_placed_at_buttom == ORDER_PLACED: return ORDER_PLACED # Propagate ORDER_PLACED last_Ask = Ask elif "SELL" in Order_type.upper(): if Last_Bid < min( Price1, Price2 ): # bid price is under the price range == sell at min price and sell at max price if sell_placed_at_buttom == False and ( max(Price1, Price2) >= Bid >= min(Price1, Price2) ): sell_placed_at_buttom = await Sell(int(TP), int(SL), int(lot_size), Bid) logger.info("Trying to place a sell at min price") if sell_placed_at_buttom == ORDER_PLACED: return ORDER_PLACED # Propagate ORDER_PLACED if sell_placed_at_top == False and ( max(Price1, Price2) <= Bid ): sell_placed_at_top = await Sell(int(TP), int(SL), int(lot_size), Bid) logger.info("Trying to place a sell at min price") if sell_placed_at_top == ORDER_PLACED: return ORDER_PLACED # Propagate ORDER_PLACED Last_Bid = Bid # Update the last bid price elif Last_Bid > max( Price1, Price2 ): # bid price is above the price range == sell at max price if (max(Price1, Price2) >= Bid >= min(Price1, Price2)) and ( sell_placed_at_top == False ): sell_placed_at_top = await Sell(int(TP), int(SL), int(lot_size), Bid) logger.info("Trying to place a sell at peak price") if sell_placed_at_top == ORDER_PLACED: return ORDER_PLACED # Propagate ORDER_PLACED Last_Bid = Bid elif ( min(Price1, Price2) <= Last_Bid <= max(Price1, Price2) ): # bid price is between the price range == instant sell and sell at peak price if sell_placed_instatly == False: sell_placed_instatly = await Sell(int(TP), int(SL), int(lot_size), Bid) if sell_placed_instatly == ORDER_PLACED: return ORDER_PLACED # Propagate ORDER_PLACED logger.info("Trying to place a sell instantly") if max(Price1, Price2) <= Bid and sell_placed_at_top == False: sell_placed_at_top = await Sell(int(TP), int(SL), int(lot_size), Bid) logger.info("Trying to place a sell instantly") if sell_placed_at_top == ORDER_PLACED: return ORDER_PLACED # Propagate ORDER_PLACED Last_Bid = Bidasync def Buy(update: Update, context: CallbackContext, TP, SL, symbol): request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": int(lot_size), "type": mt5.ORDER_TYPE_BUY, "price": Ask, "sl": int(SL), "tp": int(TP), "deviation": 20, "magic": 234000, "comment": "python script open", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_RETURN, } try: result = mt5.order_send(request) logger.info(result) if result.retcode != mt5.TRADE_RETCODE_DONE: await update.message.reply_text( f"Failed to place buy order for { symbol}. Error: {result.retcode}" ) return False else: await update.message.reply_text( f"Buy order placed successfully for { symbol} at price {context.user_data['Ask']}!" ) return ORDER_PLACED except Exception as e: await update.message.reply_text( f"An error occurred while placing the buy order: {str(e)}" ) logger.error(f"Error in Buy function: {e}") return Falseasync def Sell(update: Update, context: CallbackContext, TP, SL, symbol): request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": int(lot_size), "type": mt5.ORDER_TYPE_SELL, "price": Bid, "sl": int(SL), "tp": int(TP), "deviation": 20, "magic": 234000, "comment": "python script open", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_RETURN, } try: result = mt5.order_send(request) logger.info(result) if result.retcode != mt5.TRADE_RETCODE_DONE: await update.message.reply_text( f"Failed to place sell order for { symbol}. Error: {result.retcode}" ) return False else: await update.message.reply_text( f"Sell order placed successfully for { symbol} at price {context.user_data['Bid']}!" ) return ORDER_PLACED except Exception as e: await update.message.reply_text( f"An error occurred while placing the sell order: {str(e)}" ) logger.error(f"Error in Sell function: {e}") return Falseasync def close_all_trades(update: Update, context: CallbackContext) -> int: global trading_active, get_tick, Buy_placed_at_top, Buy_placed_at_buttom global Buy_placed_at_instatly, sell_placed_at_top, sell_placed_at_buttom global sell_placed_instatly, Ask, Bid, last_Ask, Last_Bid, lot_size global monitor_task # Stop price monitoring if monitor_task: monitor_task.cancel() monitor_task = None # Close all open positions open_positions = mt5.positions_get() if open_positions is None or len(open_positions) == 0: await update.message.reply_text("No trades to close.") else: for position in open_positions: close_request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": position.symbol, "volume": position.volume, "type": ( mt5.ORDER_TYPE_SELL if position.type == mt5.ORDER_TYPE_BUY else mt5.ORDER_TYPE_BUY ), "position": position.ticket, "price": ( mt5.symbol_info_tick(position.symbol).bid if position.type == mt5.ORDER_TYPE_BUY else mt5.symbol_info_tick(position.symbol).ask ), "deviation": 20, "magic": 234000, "comment": "Close all trades", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_RETURN, } result = mt5.order_send(close_request) if result.retcode != mt5.TRADE_RETCODE_DONE: await update.message.reply_text( f"Failed to close trade for { position.symbol}. Error: {result.retcode}" ) else: await update.message.reply_text(f"Closed trade for {position.symbol}.") # Reset global variables trading_active = False get_tick = False Buy_placed_at_top = False Buy_placed_at_buttom = False Buy_placed_at_instatly = False sell_placed_at_top = False sell_placed_at_buttom = False sell_placed_instatly = False Ask = 0 Bid = 0 last_Ask = 0 Last_Bid = 0 lot_size = 0.01 # Respond with confirmation await update.message.reply_text( "All trades closed and system reset.", reply_markup=ReplyKeyboardMarkup( [ [ "Start Trading", "get account info", "Get XAUUSD market info", "Change Lot Size", "Logout", ] ], resize_keyboard=True, ), ) return LOGGED_IN# Set up the conversation handler with states and transitionsdef main(): # Replace 'YOUR_BOT_TOKEN' with the token you got from the BotFather global conv_handler, application application = Application.builder().token(TOKEN).build() # Conversation handler to manage the login flow conv_handler = ConversationHandler( entry_points=[ CommandHandler("start", login_to_mt5), MessageHandler(filters.Regex("(?i)^Start$"), start), ], states={ LOGIN: [ MessageHandler(filters.Regex("(?i)^Login$"), login_to_mt5), MessageHandler(filters.Regex("(?i)^Cancel$"), cancel_login), ], LOGGED_IN: [ MessageHandler(filters.Regex("(?i)^Logout$"), logout), MessageHandler(filters.Regex( "(?i)^Start Trading$"), start_trading), MessageHandler( filters.Regex("(?i)^Get XAUUSD market info$"), Get_XAUUSD_market_info, ), MessageHandler( filters.Regex("(?i)^get account info$"), get_account_info ), MessageHandler(filters.Regex( "(?i)^Change Lot Size$"), change_lot_size), ], WAITING_FOR_SIGNAL: [ MessageHandler(filters.Regex( "(?i)^stop Trading$"), stop_trading), MessageHandler(filters.Regex("(?i)^Logout$"), logout), MessageHandler(filters.TEXT & ~filters.COMMAND, handle_signal_message), ], SIGNAL_RECIVED: [ MessageHandler(filters.Regex( "(?i)^cancel trade$"), stop_trading), MessageHandler( filters.Regex("(?i)^Get XAUUSD market info$"), Get_XAUUSD_market_info, ), ], ORDER_PLACED: [ MessageHandler( filters.Regex("(?i)^Close All Trades$"), close_all_trades ), MessageHandler( filters.Regex("(?i)^\\+\\d+pip"), close_all_trades ), # Pip message handler ], LOGGED_OUT: [ MessageHandler(filters.Regex("(?i)^Login$"), login_to_mt5), ], }, fallbacks=[CommandHandler("cancel", cancel_login)], ) # Add the conversation handler to the application application.add_handler(conv_handler) # Start the bot application.run_polling()if __name__ == "__main__": asyncio.run(main())