trading_commands.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. #!/usr/bin/env python3
  2. """
  3. Trading Commands - Handles all trading-related Telegram commands.
  4. """
  5. import logging
  6. from typing import Optional
  7. from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
  8. from telegram.ext import ContextTypes
  9. from src.config.config import Config
  10. logger = logging.getLogger(__name__)
  11. class TradingCommands:
  12. """Handles all trading-related Telegram commands."""
  13. def __init__(self, trading_engine, notification_manager):
  14. """Initialize with trading engine and notification manager."""
  15. self.trading_engine = trading_engine
  16. self.notification_manager = notification_manager
  17. def _is_authorized(self, chat_id: str) -> bool:
  18. """Check if the chat ID is authorized."""
  19. return str(chat_id) == str(Config.TELEGRAM_CHAT_ID)
  20. async def long_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  21. """Handle the /long command for opening long positions."""
  22. chat_id = update.effective_chat.id
  23. if not self._is_authorized(chat_id):
  24. await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
  25. return
  26. try:
  27. if not context.args or len(context.args) < 2:
  28. await context.bot.send_message(chat_id=chat_id, text=(
  29. "❌ Usage: /long [token] [USDC amount] [price (optional)] [sl:price (optional)]\n"
  30. "Examples:\n"
  31. "• /long BTC 100 - Market order\n"
  32. "• /long BTC 100 45000 - Limit order at $45,000\n"
  33. "• /long BTC 100 sl:44000 - Market order with stop loss at $44,000\n"
  34. "• /long BTC 100 45000 sl:44000 - Limit order at $45,000 with stop loss at $44,000"
  35. ))
  36. return
  37. token = context.args[0].upper()
  38. usdc_amount = float(context.args[1])
  39. # Parse arguments for price and stop loss
  40. limit_price = None
  41. stop_loss_price = None
  42. # Parse remaining arguments
  43. for i, arg in enumerate(context.args[2:], 2):
  44. if arg.startswith('sl:'):
  45. # Stop loss parameter
  46. try:
  47. stop_loss_price = float(arg[3:]) # Remove 'sl:' prefix
  48. except ValueError:
  49. await context.bot.send_message(chat_id=chat_id, text="❌ Invalid stop loss price format. Use sl:price (e.g., sl:44000)")
  50. return
  51. elif limit_price is None:
  52. # First non-sl parameter is the limit price
  53. try:
  54. limit_price = float(arg)
  55. except ValueError:
  56. await context.bot.send_message(chat_id=chat_id, text="❌ Invalid limit price format. Please use numbers only.")
  57. return
  58. # Get current market price
  59. market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
  60. if not market_data:
  61. await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch market data for {token}")
  62. return
  63. current_price = float(market_data['ticker'].get('last', 0))
  64. if current_price <= 0:
  65. await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid current price for {token}")
  66. return
  67. # Determine order type and price
  68. if limit_price:
  69. order_type = "Limit"
  70. price = limit_price
  71. token_amount = usdc_amount / price
  72. else:
  73. order_type = "Market"
  74. price = current_price
  75. token_amount = usdc_amount / current_price
  76. # Validate stop loss for long positions
  77. if stop_loss_price and stop_loss_price >= price:
  78. await context.bot.send_message(chat_id=chat_id, text=(
  79. f"⚠️ Stop loss price should be BELOW entry price for long positions\n\n"
  80. f"📊 Your order:\n"
  81. f"• Entry Price: ${price:,.2f}\n"
  82. f"• Stop Loss: ${stop_loss_price:,.2f} ❌\n\n"
  83. f"💡 Try a lower stop loss like: sl:{price * 0.95:.0f}"
  84. ))
  85. return
  86. # Create confirmation message
  87. confirmation_text = f"""
  88. 🟢 <b>Long Order Confirmation</b>
  89. 📊 <b>Order Details:</b>
  90. • Token: {token}
  91. • USDC Amount: ${usdc_amount:,.2f}
  92. • Token Amount: {token_amount:.6f} {token}
  93. • Order Type: {order_type}
  94. • Price: ${price:,.2f}
  95. • Current Price: ${current_price:,.2f}
  96. • Est. Value: ${token_amount * price:,.2f}
  97. {f"🛑 Stop Loss: ${stop_loss_price:,.2f}" if stop_loss_price else ""}
  98. ⚠️ <b>Are you sure you want to open this LONG position?</b>
  99. This will {"place a limit buy order" if limit_price else "execute a market buy order"} for {token}.
  100. """
  101. # Create callback data for confirmation
  102. callback_data = f"confirm_long_{token}_{usdc_amount}_{price if limit_price else 'market'}"
  103. if stop_loss_price:
  104. callback_data += f"_sl_{stop_loss_price}"
  105. keyboard = [
  106. [
  107. InlineKeyboardButton("✅ Execute Long", callback_data=callback_data),
  108. InlineKeyboardButton("❌ Cancel", callback_data="cancel_order")
  109. ]
  110. ]
  111. reply_markup = InlineKeyboardMarkup(keyboard)
  112. await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
  113. except ValueError as e:
  114. await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid input format: {e}")
  115. except Exception as e:
  116. await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing long command: {e}")
  117. logger.error(f"Error in long command: {e}")
  118. async def short_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  119. """Handle the /short command for opening short positions."""
  120. chat_id = update.effective_chat.id
  121. if not self._is_authorized(chat_id):
  122. await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
  123. return
  124. try:
  125. if not context.args or len(context.args) < 2:
  126. await context.bot.send_message(chat_id=chat_id, text=(
  127. "❌ Usage: /short [token] [USDC amount] [price (optional)] [sl:price (optional)]\n"
  128. "Examples:\n"
  129. "• /short BTC 100 - Market order\n"
  130. "• /short BTC 100 45000 - Limit order at $45,000\n"
  131. "• /short BTC 100 sl:46000 - Market order with stop loss at $46,000\n"
  132. "• /short BTC 100 45000 sl:46000 - Limit order at $45,000 with stop loss at $46,000"
  133. ))
  134. return
  135. token = context.args[0].upper()
  136. usdc_amount = float(context.args[1])
  137. # Parse arguments (similar to long_command)
  138. limit_price = None
  139. stop_loss_price = None
  140. for i, arg in enumerate(context.args[2:], 2):
  141. if arg.startswith('sl:'):
  142. try:
  143. stop_loss_price = float(arg[3:])
  144. except ValueError:
  145. await context.bot.send_message(chat_id=chat_id, text="❌ Invalid stop loss price format. Use sl:price (e.g., sl:46000)")
  146. return
  147. elif limit_price is None:
  148. try:
  149. limit_price = float(arg)
  150. except ValueError:
  151. await context.bot.send_message(chat_id=chat_id, text="❌ Invalid limit price format. Please use numbers only.")
  152. return
  153. # Get current market price
  154. market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
  155. if not market_data:
  156. await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch market data for {token}")
  157. return
  158. current_price = float(market_data['ticker'].get('last', 0))
  159. if current_price <= 0:
  160. await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid current price for {token}")
  161. return
  162. # Determine order type and price
  163. if limit_price:
  164. order_type = "Limit"
  165. price = limit_price
  166. token_amount = usdc_amount / price
  167. else:
  168. order_type = "Market"
  169. price = current_price
  170. token_amount = usdc_amount / current_price
  171. # Validate stop loss for short positions
  172. if stop_loss_price and stop_loss_price <= price:
  173. await context.bot.send_message(chat_id=chat_id, text=(
  174. f"⚠️ Stop loss price should be ABOVE entry price for short positions\n\n"
  175. f"📊 Your order:\n"
  176. f"• Entry Price: ${price:,.2f}\n"
  177. f"• Stop Loss: ${stop_loss_price:,.2f} ❌\n\n"
  178. f"💡 Try a higher stop loss like: sl:{price * 1.05:.0f}"
  179. ))
  180. return
  181. # Create confirmation message
  182. confirmation_text = f"""
  183. 🔴 <b>Short Order Confirmation</b>
  184. 📊 <b>Order Details:</b>
  185. • Token: {token}
  186. • USDC Amount: ${usdc_amount:,.2f}
  187. • Token Amount: {token_amount:.6f} {token}
  188. • Order Type: {order_type}
  189. • Price: ${price:,.2f}
  190. • Current Price: ${current_price:,.2f}
  191. • Est. Value: ${token_amount * price:,.2f}
  192. {f"🛑 Stop Loss: ${stop_loss_price:,.2f}" if stop_loss_price else ""}
  193. ⚠️ <b>Are you sure you want to open this SHORT position?</b>
  194. This will {"place a limit sell order" if limit_price else "execute a market sell order"} for {token}.
  195. """
  196. # Create callback data for confirmation
  197. callback_data = f"confirm_short_{token}_{usdc_amount}_{price if limit_price else 'market'}"
  198. if stop_loss_price:
  199. callback_data += f"_sl_{stop_loss_price}"
  200. keyboard = [
  201. [
  202. InlineKeyboardButton("✅ Execute Short", callback_data=callback_data),
  203. InlineKeyboardButton("❌ Cancel", callback_data="cancel_order")
  204. ]
  205. ]
  206. reply_markup = InlineKeyboardMarkup(keyboard)
  207. await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
  208. except ValueError as e:
  209. await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid input format: {e}")
  210. except Exception as e:
  211. await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing short command: {e}")
  212. logger.error(f"Error in short command: {e}")
  213. async def exit_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  214. """Handle the /exit command for closing positions."""
  215. chat_id = update.effective_chat.id
  216. if not self._is_authorized(chat_id):
  217. await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
  218. return
  219. try:
  220. if not context.args or len(context.args) < 1:
  221. await context.bot.send_message(chat_id=chat_id, text=(
  222. "❌ Usage: /exit [token]\n"
  223. "Example: /exit BTC\n\n"
  224. "This closes your entire position for the specified token."
  225. ))
  226. return
  227. token = context.args[0].upper()
  228. # Find the position
  229. position = self.trading_engine.find_position(token)
  230. if not position:
  231. await context.bot.send_message(chat_id=chat_id, text=f"📭 No open position found for {token}")
  232. return
  233. # Get position details
  234. position_type, exit_side, contracts = self.trading_engine.get_position_direction(position)
  235. entry_price = float(position.get('entryPx', 0))
  236. unrealized_pnl = float(position.get('unrealizedPnl', 0))
  237. # Get current market price
  238. market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
  239. if not market_data:
  240. await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch current price for {token}")
  241. return
  242. current_price = float(market_data['ticker'].get('last', 0))
  243. exit_value = contracts * current_price
  244. # Create confirmation message
  245. pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
  246. exit_emoji = "🔴" if position_type == "LONG" else "🟢"
  247. confirmation_text = f"""
  248. {exit_emoji} <b>Exit Position Confirmation</b>
  249. 📊 <b>Position Details:</b>
  250. • Token: {token}
  251. • Position: {position_type}
  252. • Size: {contracts:.6f} contracts
  253. • Entry Price: ${entry_price:,.2f}
  254. • Current Price: ${current_price:,.2f}
  255. • {pnl_emoji} Unrealized P&L: ${unrealized_pnl:,.2f}
  256. 🎯 <b>Exit Order:</b>
  257. • Action: {exit_side.upper()} (Close {position_type})
  258. • Amount: {contracts:.6f} {token}
  259. • Est. Value: ~${exit_value:,.2f}
  260. • Order Type: Market Order
  261. ⚠️ <b>Are you sure you want to close this {position_type} position?</b>
  262. """
  263. keyboard = [
  264. [
  265. InlineKeyboardButton(f"✅ Close {position_type}", callback_data=f"confirm_exit_{token}"),
  266. InlineKeyboardButton("❌ Cancel", callback_data="cancel_order")
  267. ]
  268. ]
  269. reply_markup = InlineKeyboardMarkup(keyboard)
  270. await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
  271. except Exception as e:
  272. await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing exit command: {e}")
  273. logger.error(f"Error in exit command: {e}")
  274. async def sl_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  275. """Handle the /sl (stop loss) command."""
  276. chat_id = update.effective_chat.id
  277. if not self._is_authorized(chat_id):
  278. await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
  279. return
  280. try:
  281. if not context.args or len(context.args) < 2:
  282. await context.bot.send_message(chat_id=chat_id, text=(
  283. "❌ Usage: /sl [token] [price]\n"
  284. "Example: /sl BTC 44000\n\n"
  285. "This sets a stop loss order for your existing position."
  286. ))
  287. return
  288. token = context.args[0].upper()
  289. stop_price = float(context.args[1])
  290. # Find the position
  291. position = self.trading_engine.find_position(token)
  292. if not position:
  293. await context.bot.send_message(chat_id=chat_id, text=f"📭 No open position found for {token}")
  294. return
  295. # Get position details
  296. position_type, exit_side, contracts = self.trading_engine.get_position_direction(position)
  297. entry_price = float(position.get('entryPx', 0))
  298. # Validate stop loss price based on position direction
  299. if position_type == "LONG" and stop_price >= entry_price:
  300. await context.bot.send_message(chat_id=chat_id, text=(
  301. f"⚠️ Stop loss price should be BELOW entry price for long positions\n\n"
  302. f"📊 Your {token} LONG position:\n"
  303. f"• Entry Price: ${entry_price:,.2f}\n"
  304. f"• Stop Price: ${stop_price:,.2f} ❌\n\n"
  305. f"💡 Try a lower price like: /sl {token} {entry_price * 0.95:.0f}"
  306. ))
  307. return
  308. elif position_type == "SHORT" and stop_price <= entry_price:
  309. await context.bot.send_message(chat_id=chat_id, text=(
  310. f"⚠️ Stop loss price should be ABOVE entry price for short positions\n\n"
  311. f"📊 Your {token} SHORT position:\n"
  312. f"• Entry Price: ${entry_price:,.2f}\n"
  313. f"• Stop Price: ${stop_price:,.2f} ❌\n\n"
  314. f"💡 Try a higher price like: /sl {token} {entry_price * 1.05:.0f}"
  315. ))
  316. return
  317. # Get current market price
  318. market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
  319. current_price = 0
  320. if market_data:
  321. current_price = float(market_data['ticker'].get('last', 0))
  322. # Calculate estimated P&L at stop loss
  323. if position_type == "LONG":
  324. pnl_at_stop = (stop_price - entry_price) * contracts
  325. else: # SHORT
  326. pnl_at_stop = (entry_price - stop_price) * contracts
  327. pnl_emoji = "🟢" if pnl_at_stop >= 0 else "🔴"
  328. confirmation_text = f"""
  329. 🛑 <b>Stop Loss Order Confirmation</b>
  330. 📊 <b>Position Details:</b>
  331. • Token: {token}
  332. • Position: {position_type}
  333. • Size: {contracts:.6f} contracts
  334. • Entry Price: ${entry_price:,.2f}
  335. • Current Price: ${current_price:,.2f}
  336. 🎯 <b>Stop Loss Order:</b>
  337. • Stop Price: ${stop_price:,.2f}
  338. • Action: {exit_side.upper()} (Close {position_type})
  339. • Amount: {contracts:.6f} {token}
  340. • Order Type: Limit Order
  341. • {pnl_emoji} Est. P&L: ${pnl_at_stop:,.2f}
  342. ⚠️ <b>Are you sure you want to set this stop loss?</b>
  343. This will place a limit {exit_side} order at ${stop_price:,.2f} to protect your {position_type} position.
  344. """
  345. keyboard = [
  346. [
  347. InlineKeyboardButton("✅ Set Stop Loss", callback_data=f"confirm_sl_{token}_{stop_price}"),
  348. InlineKeyboardButton("❌ Cancel", callback_data="cancel_order")
  349. ]
  350. ]
  351. reply_markup = InlineKeyboardMarkup(keyboard)
  352. await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
  353. except ValueError:
  354. await context.bot.send_message(chat_id=chat_id, text="❌ Invalid price format. Please use numbers only.")
  355. except Exception as e:
  356. await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing stop loss command: {e}")
  357. logger.error(f"Error in sl command: {e}")
  358. async def tp_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  359. """Handle the /tp (take profit) command."""
  360. chat_id = update.effective_chat.id
  361. if not self._is_authorized(chat_id):
  362. await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
  363. return
  364. try:
  365. if not context.args or len(context.args) < 2:
  366. await context.bot.send_message(chat_id=chat_id, text=(
  367. "❌ Usage: /tp [token] [price]\n"
  368. "Example: /tp BTC 50000\n\n"
  369. "This sets a take profit order for your existing position."
  370. ))
  371. return
  372. token = context.args[0].upper()
  373. tp_price = float(context.args[1])
  374. # Find the position
  375. position = self.trading_engine.find_position(token)
  376. if not position:
  377. await context.bot.send_message(chat_id=chat_id, text=f"📭 No open position found for {token}")
  378. return
  379. # Get position details
  380. position_type, exit_side, contracts = self.trading_engine.get_position_direction(position)
  381. entry_price = float(position.get('entryPx', 0))
  382. # Validate take profit price based on position direction
  383. if position_type == "LONG" and tp_price <= entry_price:
  384. await context.bot.send_message(chat_id=chat_id, text=(
  385. f"⚠️ Take profit price should be ABOVE entry price for long positions\n\n"
  386. f"📊 Your {token} LONG position:\n"
  387. f"• Entry Price: ${entry_price:,.2f}\n"
  388. f"• Take Profit: ${tp_price:,.2f} ❌\n\n"
  389. f"💡 Try a higher price like: /tp {token} {entry_price * 1.05:.0f}"
  390. ))
  391. return
  392. elif position_type == "SHORT" and tp_price >= entry_price:
  393. await context.bot.send_message(chat_id=chat_id, text=(
  394. f"⚠️ Take profit price should be BELOW entry price for short positions\n\n"
  395. f"�� Your {token} SHORT position:\n"
  396. f"• Entry Price: ${entry_price:,.2f}\n"
  397. f"• Take Profit: ${tp_price:,.2f} ❌\n\n"
  398. f"💡 Try a lower price like: /tp {token} {entry_price * 0.95:.0f}"
  399. ))
  400. return
  401. # Get current market price
  402. market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
  403. current_price = 0
  404. if market_data:
  405. current_price = float(market_data['ticker'].get('last', 0))
  406. # Calculate estimated P&L at take profit
  407. if position_type == "LONG":
  408. pnl_at_tp = (tp_price - entry_price) * contracts
  409. else: # SHORT
  410. pnl_at_tp = (entry_price - tp_price) * contracts
  411. pnl_emoji = "🟢" if pnl_at_tp >= 0 else "🔴"
  412. confirmation_text = f"""
  413. 🎯 <b>Take Profit Order Confirmation</b>
  414. 📊 <b>Position Details:</b>
  415. • Token: {token}
  416. • Position: {position_type}
  417. • Size: {contracts:.6f} contracts
  418. • Entry Price: ${entry_price:,.2f}
  419. • Current Price: ${current_price:,.2f}
  420. 🎯 <b>Take Profit Order:</b>
  421. • Take Profit Price: ${tp_price:,.2f}
  422. • Action: {exit_side.upper()} (Close {position_type})
  423. • Amount: {contracts:.6f} {token}
  424. • Order Type: Limit Order
  425. • {pnl_emoji} Est. P&L: ${pnl_at_tp:,.2f}
  426. ⚠️ <b>Are you sure you want to set this take profit?</b>
  427. This will place a limit {exit_side} order at ${tp_price:,.2f} to secure profits from your {position_type} position.
  428. """
  429. keyboard = [
  430. [
  431. InlineKeyboardButton("✅ Set Take Profit", callback_data=f"confirm_tp_{token}_{tp_price}"),
  432. InlineKeyboardButton("❌ Cancel", callback_data="cancel_order")
  433. ]
  434. ]
  435. reply_markup = InlineKeyboardMarkup(keyboard)
  436. await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
  437. except ValueError:
  438. await context.bot.send_message(chat_id=chat_id, text="❌ Invalid price format. Please use numbers only.")
  439. except Exception as e:
  440. await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing take profit command: {e}")
  441. logger.error(f"Error in tp command: {e}")
  442. async def coo_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  443. """Handle the /coo (cancel all orders) command."""
  444. chat_id = update.effective_chat.id
  445. if not self._is_authorized(chat_id):
  446. await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
  447. return
  448. try:
  449. if not context.args or len(context.args) < 1:
  450. await context.bot.send_message(chat_id=chat_id, text=(
  451. "❌ Usage: /coo [token]\n"
  452. "Example: /coo BTC\n\n"
  453. "This cancels all open orders for the specified token."
  454. ))
  455. return
  456. token = context.args[0].upper()
  457. confirmation_text = f"""
  458. 🚫 <b>Cancel All Orders Confirmation</b>
  459. 📊 <b>Action:</b> Cancel all open orders for {token}
  460. ⚠️ <b>Are you sure you want to cancel all {token} orders?</b>
  461. This will cancel ALL pending orders for {token}, including:
  462. • Limit orders
  463. • Stop loss orders
  464. • Take profit orders
  465. This action cannot be undone.
  466. """
  467. keyboard = [
  468. [
  469. InlineKeyboardButton(f"✅ Cancel All {token} Orders", callback_data=f"confirm_coo_{token}"),
  470. InlineKeyboardButton("❌ Keep Orders", callback_data="cancel_order")
  471. ]
  472. ]
  473. reply_markup = InlineKeyboardMarkup(keyboard)
  474. await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
  475. except Exception as e:
  476. await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing cancel orders command: {e}")
  477. logger.error(f"Error in coo command: {e}")
  478. async def button_callback(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  479. """Handle button callbacks for trading commands."""
  480. query = update.callback_query
  481. await query.answer()
  482. if not self._is_authorized(query.message.chat_id):
  483. await query.edit_message_text("❌ Unauthorized access.")
  484. return
  485. callback_data = query.data
  486. try:
  487. if callback_data.startswith('confirm_long_'):
  488. await self._execute_long_callback(query, callback_data)
  489. elif callback_data.startswith('confirm_short_'):
  490. await self._execute_short_callback(query, callback_data)
  491. elif callback_data.startswith('confirm_exit_'):
  492. await self._execute_exit_callback(query, callback_data)
  493. elif callback_data.startswith('confirm_sl_'):
  494. await self._execute_sl_callback(query, callback_data)
  495. elif callback_data.startswith('confirm_tp_'):
  496. await self._execute_tp_callback(query, callback_data)
  497. elif callback_data.startswith('confirm_coo_'):
  498. await self._execute_coo_callback(query, callback_data)
  499. elif callback_data == 'cancel_order':
  500. await query.edit_message_text("❌ Order cancelled.")
  501. # Handle info command button callbacks
  502. elif callback_data in ['balance', 'positions', 'orders', 'stats', 'trades', 'market', 'price',
  503. 'performance', 'daily', 'weekly', 'monthly', 'alarm', 'monitoring', 'logs', 'help']:
  504. await query.edit_message_text(f"✅ Please use /{callback_data} command to get the latest data.")
  505. except Exception as e:
  506. await query.edit_message_text(f"❌ Error processing order: {e}")
  507. logger.error(f"Error in button callback: {e}")
  508. async def _execute_long_callback(self, query, callback_data):
  509. """Execute long order from callback."""
  510. parts = callback_data.split('_')
  511. token = parts[2]
  512. usdc_amount = float(parts[3])
  513. price = None if parts[4] == 'market' else float(parts[4])
  514. stop_loss_price = None
  515. # Check for stop loss
  516. if len(parts) > 5 and parts[5] == 'sl':
  517. stop_loss_price = float(parts[6])
  518. await query.edit_message_text("⏳ Executing long order...")
  519. result = await self.trading_engine.execute_long_order(token, usdc_amount, price, stop_loss_price)
  520. if result["success"]:
  521. await self.notification_manager.send_long_success_notification(
  522. query, token, result["token_amount"], result["actual_price"], result["order"], stop_loss_price
  523. )
  524. else:
  525. await query.edit_message_text(f"❌ Long order failed: {result['error']}")
  526. async def _execute_short_callback(self, query, callback_data):
  527. """Execute short order from callback."""
  528. parts = callback_data.split('_')
  529. token = parts[2]
  530. usdc_amount = float(parts[3])
  531. price = None if parts[4] == 'market' else float(parts[4])
  532. stop_loss_price = None
  533. # Check for stop loss
  534. if len(parts) > 5 and parts[5] == 'sl':
  535. stop_loss_price = float(parts[6])
  536. await query.edit_message_text("⏳ Executing short order...")
  537. result = await self.trading_engine.execute_short_order(token, usdc_amount, price, stop_loss_price)
  538. if result["success"]:
  539. await self.notification_manager.send_short_success_notification(
  540. query, token, result["token_amount"], result["actual_price"], result["order"], stop_loss_price
  541. )
  542. else:
  543. await query.edit_message_text(f"❌ Short order failed: {result['error']}")
  544. async def _execute_exit_callback(self, query, callback_data):
  545. """Execute exit order from callback."""
  546. parts = callback_data.split('_')
  547. token = parts[2]
  548. await query.edit_message_text("⏳ Closing position...")
  549. result = await self.trading_engine.execute_exit_order(token)
  550. if result["success"]:
  551. await self.notification_manager.send_exit_success_notification(
  552. query, token, result["position_type"], result["contracts"],
  553. result["actual_price"], result["pnl"], result["order"]
  554. )
  555. else:
  556. await query.edit_message_text(f"❌ Exit order failed: {result['error']}")
  557. async def _execute_sl_callback(self, query, callback_data):
  558. """Execute stop loss order from callback."""
  559. parts = callback_data.split('_')
  560. token = parts[2]
  561. stop_price = float(parts[3])
  562. await query.edit_message_text("⏳ Setting stop loss...")
  563. result = await self.trading_engine.execute_sl_order(token, stop_price)
  564. if result["success"]:
  565. await self.notification_manager.send_sl_success_notification(
  566. query, token, result["position_type"], result["contracts"],
  567. stop_price, result["order"]
  568. )
  569. else:
  570. await query.edit_message_text(f"❌ Stop loss failed: {result['error']}")
  571. async def _execute_tp_callback(self, query, callback_data):
  572. """Execute take profit order from callback."""
  573. parts = callback_data.split('_')
  574. token = parts[2]
  575. tp_price = float(parts[3])
  576. await query.edit_message_text("⏳ Setting take profit...")
  577. result = await self.trading_engine.execute_tp_order(token, tp_price)
  578. if result["success"]:
  579. await self.notification_manager.send_tp_success_notification(
  580. query, token, result["position_type"], result["contracts"],
  581. tp_price, result["order"]
  582. )
  583. else:
  584. await query.edit_message_text(f"❌ Take profit failed: {result['error']}")
  585. async def _execute_coo_callback(self, query, callback_data):
  586. """Execute cancel all orders from callback."""
  587. parts = callback_data.split('_')
  588. token = parts[2]
  589. await query.edit_message_text("⏳ Cancelling orders...")
  590. result = await self.trading_engine.execute_coo_order(token)
  591. if result["success"]:
  592. await self.notification_manager.send_coo_success_notification(
  593. query, token, result["cancelled_count"], result["failed_count"]
  594. )
  595. else:
  596. await query.edit_message_text(f"❌ Cancel orders failed: {result['error']}")