#!/usr/bin/env python3
"""
Trading Commands - Handles all trading-related Telegram commands.
"""
import logging
from typing import Optional
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import ContextTypes
from src.config.config import Config
logger = logging.getLogger(__name__)
class TradingCommands:
"""Handles all trading-related Telegram commands."""
def __init__(self, trading_engine, notification_manager, info_commands_handler=None, management_commands_handler=None):
"""Initialize with trading engine, notification manager, and other command handlers."""
self.trading_engine = trading_engine
self.notification_manager = notification_manager
self.info_commands_handler = info_commands_handler
self.management_commands_handler = management_commands_handler
def _is_authorized(self, chat_id: str) -> bool:
"""Check if the chat ID is authorized."""
return str(chat_id) == str(Config.TELEGRAM_CHAT_ID)
async def long_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle the /long command for opening long positions."""
chat_id = update.effective_chat.id
if not self._is_authorized(chat_id):
await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
return
try:
if not context.args or len(context.args) < 2:
await context.bot.send_message(chat_id=chat_id, text=(
"❌ Usage: /long [token] [USDC amount] [price (optional)] [sl:price (optional)]\n"
"Examples:\n"
"• /long BTC 100 - Market order\n"
"• /long BTC 100 45000 - Limit order at $45,000\n"
"• /long BTC 100 sl:44000 - Market order with stop loss at $44,000\n"
"• /long BTC 100 45000 sl:44000 - Limit order at $45,000 with stop loss at $44,000"
))
return
token = context.args[0].upper()
usdc_amount = float(context.args[1])
# Parse arguments for price and stop loss
limit_price = None
stop_loss_price = None
# Parse remaining arguments
for i, arg in enumerate(context.args[2:], 2):
if arg.startswith('sl:'):
# Stop loss parameter
try:
stop_loss_price = float(arg[3:]) # Remove 'sl:' prefix
except ValueError:
await context.bot.send_message(chat_id=chat_id, text="❌ Invalid stop loss price format. Use sl:price (e.g., sl:44000)")
return
elif limit_price is None:
# First non-sl parameter is the limit price
try:
limit_price = float(arg)
except ValueError:
await context.bot.send_message(chat_id=chat_id, text="❌ Invalid limit price format. Please use numbers only.")
return
# Get current market price
market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
if not market_data:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch market data for {token}")
return
current_price = float(market_data['ticker'].get('last', 0))
if current_price <= 0:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid current price for {token}")
return
# Determine order type and price
if limit_price:
order_type = "Limit"
price = limit_price
token_amount = usdc_amount / price
else:
order_type = "Market"
price = current_price
token_amount = usdc_amount / current_price
# Validate stop loss for long positions
if stop_loss_price and stop_loss_price >= price:
await context.bot.send_message(chat_id=chat_id, text=(
f"⚠️ Stop loss price should be BELOW entry price for long positions\n\n"
f"📊 Your order:\n"
f"• Entry Price: ${price:,.2f}\n"
f"• Stop Loss: ${stop_loss_price:,.2f} ❌\n\n"
f"💡 Try a lower stop loss like: sl:{price * 0.95:.0f}"
))
return
# Create confirmation message
confirmation_text = f"""
🟢 Long Order Confirmation
📊 Order Details:
• Token: {token}
• USDC Amount: ${usdc_amount:,.2f}
• Token Amount: {token_amount:.6f} {token}
• Order Type: {order_type}
• Price: ${price:,.2f}
• Current Price: ${current_price:,.2f}
• Est. Value: ${token_amount * price:,.2f}"""
if stop_loss_price:
confirmation_text += f"""
• 🛑 Stop Loss: ${stop_loss_price:,.2f}"""
confirmation_text += f"""
⚠️ Are you sure you want to open this LONG position?
This will {"place a limit buy order" if limit_price else "execute a market buy order"} for {token}."""
if stop_loss_price:
confirmation_text += f"\nStop loss will be set automatically when order fills."
# Create callback data for confirmation
callback_data = f"confirm_long_{token}_{usdc_amount}_{price if limit_price else 'market'}"
if stop_loss_price:
callback_data += f"_sl_{stop_loss_price}"
keyboard = [
[
InlineKeyboardButton("✅ Execute Long", callback_data=callback_data),
InlineKeyboardButton("❌ Cancel", callback_data="cancel_order")
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
except ValueError as e:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid input format: {e}")
except Exception as e:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing long command: {e}")
logger.error(f"Error in long command: {e}")
async def short_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle the /short command for opening short positions."""
chat_id = update.effective_chat.id
if not self._is_authorized(chat_id):
await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
return
try:
if not context.args or len(context.args) < 2:
await context.bot.send_message(chat_id=chat_id, text=(
"❌ Usage: /short [token] [USDC amount] [price (optional)] [sl:price (optional)]\n"
"Examples:\n"
"• /short BTC 100 - Market order\n"
"• /short BTC 100 45000 - Limit order at $45,000\n"
"• /short BTC 100 sl:46000 - Market order with stop loss at $46,000\n"
"• /short BTC 100 45000 sl:46000 - Limit order at $45,000 with stop loss at $46,000"
))
return
token = context.args[0].upper()
usdc_amount = float(context.args[1])
# Parse arguments (similar to long_command)
limit_price = None
stop_loss_price = None
for i, arg in enumerate(context.args[2:], 2):
if arg.startswith('sl:'):
try:
stop_loss_price = float(arg[3:])
except ValueError:
await context.bot.send_message(chat_id=chat_id, text="❌ Invalid stop loss price format. Use sl:price (e.g., sl:46000)")
return
elif limit_price is None:
try:
limit_price = float(arg)
except ValueError:
await context.bot.send_message(chat_id=chat_id, text="❌ Invalid limit price format. Please use numbers only.")
return
# Get current market price
market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
if not market_data:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch market data for {token}")
return
current_price = float(market_data['ticker'].get('last', 0))
if current_price <= 0:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid current price for {token}")
return
# Determine order type and price
if limit_price:
order_type = "Limit"
price = limit_price
token_amount = usdc_amount / price
else:
order_type = "Market"
price = current_price
token_amount = usdc_amount / current_price
# Validate stop loss for short positions
if stop_loss_price and stop_loss_price <= price:
await context.bot.send_message(chat_id=chat_id, text=(
f"⚠️ Stop loss price should be ABOVE entry price for short positions\n\n"
f"📊 Your order:\n"
f"• Entry Price: ${price:,.2f}\n"
f"• Stop Loss: ${stop_loss_price:,.2f} ❌\n\n"
f"💡 Try a higher stop loss like: sl:{price * 1.05:.0f}"
))
return
# Create confirmation message
confirmation_text = f"""
🔴 Short Order Confirmation
📊 Order Details:
• Token: {token}
• USDC Amount: ${usdc_amount:,.2f}
• Token Amount: {token_amount:.6f} {token}
• Order Type: {order_type}
• Price: ${price:,.2f}
• Current Price: ${current_price:,.2f}
• Est. Value: ${token_amount * price:,.2f}"""
if stop_loss_price:
confirmation_text += f"""
• 🛑 Stop Loss: ${stop_loss_price:,.2f}"""
confirmation_text += f"""
⚠️ Are you sure you want to open this SHORT position?
This will {"place a limit sell order" if limit_price else "execute a market sell order"} for {token}."""
if stop_loss_price:
confirmation_text += f"\nStop loss will be set automatically when order fills."
# Create callback data for confirmation
callback_data = f"confirm_short_{token}_{usdc_amount}_{price if limit_price else 'market'}"
if stop_loss_price:
callback_data += f"_sl_{stop_loss_price}"
keyboard = [
[
InlineKeyboardButton("✅ Execute Short", callback_data=callback_data),
InlineKeyboardButton("❌ Cancel", callback_data="cancel_order")
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
except ValueError as e:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid input format: {e}")
except Exception as e:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing short command: {e}")
logger.error(f"Error in short command: {e}")
async def exit_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle the /exit command for closing positions."""
chat_id = update.effective_chat.id
if not self._is_authorized(chat_id):
await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
return
try:
if not context.args or len(context.args) < 1:
await context.bot.send_message(chat_id=chat_id, text=(
"❌ Usage: /exit [token]\n"
"Example: /exit BTC\n\n"
"This closes your entire position for the specified token."
))
return
token = context.args[0].upper()
# Find the position
position = self.trading_engine.find_position(token)
if not position:
await context.bot.send_message(chat_id=chat_id, text=f"📭 No open position found for {token}")
return
# Get position details
position_type, exit_side, contracts = self.trading_engine.get_position_direction(position)
entry_price = float(position.get('entryPx', 0))
unrealized_pnl = float(position.get('unrealizedPnl', 0))
# Get current market price
market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
if not market_data:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch current price for {token}")
return
current_price = float(market_data['ticker'].get('last', 0))
exit_value = contracts * current_price
# Create confirmation message
pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
exit_emoji = "🔴" if position_type == "LONG" else "🟢"
confirmation_text = f"""
{exit_emoji} Exit Position Confirmation
📊 Position Details:
• Token: {token}
• Position: {position_type}
• Size: {contracts:.6f} contracts
• Entry Price: ${entry_price:,.2f}
• Current Price: ${current_price:,.2f}
• {pnl_emoji} Unrealized P&L: ${unrealized_pnl:,.2f}
🎯 Exit Order:
• Action: {exit_side.upper()} (Close {position_type})
• Amount: {contracts:.6f} {token}
• Est. Value: ~${exit_value:,.2f}
• Order Type: Market Order
⚠️ Are you sure you want to close this {position_type} position?
"""
keyboard = [
[
InlineKeyboardButton(f"✅ Close {position_type}", callback_data=f"confirm_exit_{token}"),
InlineKeyboardButton("❌ Cancel", callback_data="cancel_order")
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
except Exception as e:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing exit command: {e}")
logger.error(f"Error in exit command: {e}")
async def sl_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle the /sl (stop loss) command."""
chat_id = update.effective_chat.id
if not self._is_authorized(chat_id):
await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
return
try:
if not context.args or len(context.args) < 2:
await context.bot.send_message(chat_id=chat_id, text=(
"❌ Usage: /sl [token] [price]\n"
"Example: /sl BTC 44000\n\n"
"This sets a stop loss order for your existing position."
))
return
token = context.args[0].upper()
stop_price = float(context.args[1])
# Find the position
position = self.trading_engine.find_position(token)
if not position:
await context.bot.send_message(chat_id=chat_id, text=f"📭 No open position found for {token}")
return
# Get position details
position_type, exit_side, contracts = self.trading_engine.get_position_direction(position)
entry_price = float(position.get('entryPx', 0))
# Validate stop loss price based on position direction
if position_type == "LONG" and stop_price >= entry_price:
await context.bot.send_message(chat_id=chat_id, text=(
f"⚠️ Stop loss price should be BELOW entry price for long positions\n\n"
f"📊 Your {token} LONG position:\n"
f"• Entry Price: ${entry_price:,.2f}\n"
f"• Stop Price: ${stop_price:,.2f} ❌\n\n"
f"💡 Try a lower price like: /sl {token} {entry_price * 0.95:.0f}"
))
return
elif position_type == "SHORT" and stop_price <= entry_price:
await context.bot.send_message(chat_id=chat_id, text=(
f"⚠️ Stop loss price should be ABOVE entry price for short positions\n\n"
f"📊 Your {token} SHORT position:\n"
f"• Entry Price: ${entry_price:,.2f}\n"
f"• Stop Price: ${stop_price:,.2f} ❌\n\n"
f"💡 Try a higher price like: /sl {token} {entry_price * 1.05:.0f}"
))
return
# Get current market price
market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
current_price = 0
if market_data:
current_price = float(market_data['ticker'].get('last', 0))
# Calculate estimated P&L at stop loss
if position_type == "LONG":
pnl_at_stop = (stop_price - entry_price) * contracts
else: # SHORT
pnl_at_stop = (entry_price - stop_price) * contracts
pnl_emoji = "🟢" if pnl_at_stop >= 0 else "🔴"
confirmation_text = f"""
🛑 Stop Loss Order Confirmation
📊 Position Details:
• Token: {token}
• Position: {position_type}
• Size: {contracts:.6f} contracts
• Entry Price: ${entry_price:,.2f}
• Current Price: ${current_price:,.2f}
🎯 Stop Loss Order:
• Stop Price: ${stop_price:,.2f}
• Action: {exit_side.upper()} (Close {position_type})
• Amount: {contracts:.6f} {token}
• Order Type: Limit Order
• {pnl_emoji} Est. P&L: ${pnl_at_stop:,.2f}
⚠️ Are you sure you want to set this stop loss?
This will place a limit {exit_side} order at ${stop_price:,.2f} to protect your {position_type} position.
"""
keyboard = [
[
InlineKeyboardButton("✅ Set Stop Loss", callback_data=f"confirm_sl_{token}_{stop_price}"),
InlineKeyboardButton("❌ Cancel", callback_data="cancel_order")
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
except ValueError:
await context.bot.send_message(chat_id=chat_id, text="❌ Invalid price format. Please use numbers only.")
except Exception as e:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing stop loss command: {e}")
logger.error(f"Error in sl command: {e}")
async def tp_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle the /tp (take profit) command."""
chat_id = update.effective_chat.id
if not self._is_authorized(chat_id):
await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
return
try:
if not context.args or len(context.args) < 2:
await context.bot.send_message(chat_id=chat_id, text=(
"❌ Usage: /tp [token] [price]\n"
"Example: /tp BTC 50000\n\n"
"This sets a take profit order for your existing position."
))
return
token = context.args[0].upper()
tp_price = float(context.args[1])
# Find the position
position = self.trading_engine.find_position(token)
if not position:
await context.bot.send_message(chat_id=chat_id, text=f"📭 No open position found for {token}")
return
# Get position details
position_type, exit_side, contracts = self.trading_engine.get_position_direction(position)
entry_price = float(position.get('entryPx', 0))
# Validate take profit price based on position direction
if position_type == "LONG" and tp_price <= entry_price:
await context.bot.send_message(chat_id=chat_id, text=(
f"⚠️ Take profit price should be ABOVE entry price for long positions\n\n"
f"📊 Your {token} LONG position:\n"
f"• Entry Price: ${entry_price:,.2f}\n"
f"• Take Profit: ${tp_price:,.2f} ❌\n\n"
f"💡 Try a higher price like: /tp {token} {entry_price * 1.05:.0f}"
))
return
elif position_type == "SHORT" and tp_price >= entry_price:
await context.bot.send_message(chat_id=chat_id, text=(
f"⚠️ Take profit price should be BELOW entry price for short positions\n\n"
f"📊 Your {token} SHORT position:\n"
f"• Entry Price: ${entry_price:,.2f}\n"
f"• Take Profit: ${tp_price:,.2f} ❌\n\n"
f"💡 Try a lower price like: /tp {token} {entry_price * 0.95:.0f}"
))
return
# Get current market price
market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
current_price = 0
if market_data:
current_price = float(market_data['ticker'].get('last', 0))
# Calculate estimated P&L at take profit
if position_type == "LONG":
pnl_at_tp = (tp_price - entry_price) * contracts
else: # SHORT
pnl_at_tp = (entry_price - tp_price) * contracts
pnl_emoji = "🟢" if pnl_at_tp >= 0 else "🔴"
confirmation_text = f"""
🎯 Take Profit Order Confirmation
📊 Position Details:
• Token: {token}
• Position: {position_type}
• Size: {contracts:.6f} contracts
• Entry Price: ${entry_price:,.2f}
• Current Price: ${current_price:,.2f}
🎯 Take Profit Order:
• Take Profit Price: ${tp_price:,.2f}
• Action: {exit_side.upper()} (Close {position_type})
• Amount: {contracts:.6f} {token}
• Order Type: Limit Order
• {pnl_emoji} Est. P&L: ${pnl_at_tp:,.2f}
⚠️ Are you sure you want to set this take profit?
This will place a limit {exit_side} order at ${tp_price:,.2f} to secure profits from your {position_type} position.
"""
keyboard = [
[
InlineKeyboardButton("✅ Set Take Profit", callback_data=f"confirm_tp_{token}_{tp_price}"),
InlineKeyboardButton("❌ Cancel", callback_data="cancel_order")
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
except ValueError:
await context.bot.send_message(chat_id=chat_id, text="❌ Invalid price format. Please use numbers only.")
except Exception as e:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing take profit command: {e}")
logger.error(f"Error in tp command: {e}")
async def coo_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle the /coo (cancel all orders) command."""
chat_id = update.effective_chat.id
if not self._is_authorized(chat_id):
await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
return
try:
if not context.args or len(context.args) < 1:
await context.bot.send_message(chat_id=chat_id, text=(
"❌ Usage: /coo [token]\n"
"Example: /coo BTC\n\n"
"This cancels all open orders for the specified token."
))
return
token = context.args[0].upper()
confirmation_text = f"""
🚫 Cancel All Orders Confirmation
📊 Action: Cancel all open orders for {token}
⚠️ Are you sure you want to cancel all {token} orders?
This will cancel ALL pending orders for {token}, including:
• Limit orders
• Stop loss orders
• Take profit orders
This action cannot be undone.
"""
keyboard = [
[
InlineKeyboardButton(f"✅ Cancel All {token} Orders", callback_data=f"confirm_coo_{token}"),
InlineKeyboardButton("❌ Keep Orders", callback_data="cancel_order")
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
except Exception as e:
await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing cancel orders command: {e}")
logger.error(f"Error in coo command: {e}")
async def button_callback(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle button callbacks for trading commands."""
query = update.callback_query
await query.answer()
if not self._is_authorized(query.message.chat_id):
await query.edit_message_text("❌ Unauthorized access.")
return
callback_data = query.data
logger.info(f"Button callback triggered with data: {callback_data}")
# Define a map for informational and management command callbacks
# These commands expect `update` and `context` as if called by a CommandHandler
command_action_map = {}
if self.info_commands_handler:
command_action_map.update({
"balance": self.info_commands_handler.balance_command,
"positions": self.info_commands_handler.positions_command,
"orders": self.info_commands_handler.orders_command,
"stats": self.info_commands_handler.stats_command,
"price": self.info_commands_handler.price_command,
"market": self.info_commands_handler.market_command,
"performance": self.info_commands_handler.performance_command,
"daily": self.info_commands_handler.daily_command,
"weekly": self.info_commands_handler.weekly_command,
"monthly": self.info_commands_handler.monthly_command,
"trades": self.info_commands_handler.trades_command,
# Note: 'help' is handled separately below as its main handler is in TelegramTradingBot core
})
if self.management_commands_handler:
command_action_map.update({
"alarm": self.management_commands_handler.alarm_command,
"monitoring": self.management_commands_handler.monitoring_command,
"logs": self.management_commands_handler.logs_command,
# Add other management commands here if they have quick action buttons
})
# Prepare key for map lookup, stripping leading '/' if present for general commands
mapped_command_key = callback_data
if callback_data.startswith('/') and not callback_data.startswith('/confirm_'): # Avoid stripping for confirm actions
mapped_command_key = callback_data[1:]
# Check if the callback_data matches a mapped informational/management command
if mapped_command_key in command_action_map:
command_method = command_action_map[mapped_command_key]
try:
logger.info(f"Executing {mapped_command_key} command (from callback: {callback_data}) via button callback.")
# Edit the original message to indicate the action is being processed
# await query.edit_message_text(text=f"🔄 Processing {mapped_command_key.capitalize()}...", parse_mode='HTML') # Optional
await command_method(update, context) # Call the actual command method
# After the command sends its own message(s), we might want to remove or clean up the original message with buttons.
# For now, let the command method handle all responses.
# Optionally, delete the message that had the buttons:
# await query.message.delete()
except Exception as e:
logger.error(f"Error executing command '{mapped_command_key}' from button: {e}", exc_info=True)
try:
await query.message.reply_text(f"❌ Error processing {mapped_command_key.capitalize()}: {e}")
except Exception as reply_e:
logger.error(f"Failed to send error reply for {mapped_command_key} button: {reply_e}")
return # Handled
# Special handling for 'help' callback from InfoCommands quick menu
# This check should use the modified key as well if we want /help to work via this mechanism
# However, the 'help' key in command_action_map is 'help', not '/help'.
# The current 'help' handling is separate and specific. Let's adjust it for consistency if needed or verify.
# The previous change to info_commands.py made help callback_data='/help'.
if callback_data == "/help": # Check for the actual callback_data value
logger.info("Handling '/help' button callback. Guiding user to /help command.")
try:
# Remove the inline keyboard from the original message and provide guidance.
await query.edit_message_text(
text="📖 To view all commands and their descriptions, please type the /help command.",
reply_markup=None, # Remove buttons
parse_mode='HTML'
)
except Exception as e:
logger.error(f"Error editing message for 'help' callback: {e}")
# Fallback if edit fails (e.g., message too old)
await query.message.reply_text("📖 Please type /help for command details.", parse_mode='HTML')
return # Handled
# Existing trading confirmation logic
if callback_data.startswith("confirm_long_"):
await self._execute_long_callback(query, callback_data)
elif callback_data.startswith("confirm_short_"):
await self._execute_short_callback(query, callback_data)
elif callback_data.startswith("confirm_exit_"):
await self._execute_exit_callback(query, callback_data)
elif callback_data.startswith("confirm_sl_"):
await self._execute_sl_callback(query, callback_data)
elif callback_data.startswith("confirm_tp_"):
await self._execute_tp_callback(query, callback_data)
elif callback_data.startswith("confirm_coo_"):
await self._execute_coo_callback(query, callback_data)
elif callback_data == 'cancel_order':
await query.edit_message_text("❌ Order cancelled.")
async def _execute_long_callback(self, query, callback_data):
"""Execute long order from callback."""
parts = callback_data.split('_')
token = parts[2]
usdc_amount = float(parts[3])
price = None if parts[4] == 'market' else float(parts[4])
stop_loss_price = None
# Check for stop loss
if len(parts) > 5 and parts[5] == 'sl':
stop_loss_price = float(parts[6])
await query.edit_message_text("⏳ Executing long order...")
result = await self.trading_engine.execute_long_order(token, usdc_amount, price, stop_loss_price)
if result["success"]:
await self.notification_manager.send_long_success_notification(
query, token, result["token_amount"], result["actual_price"], result["order"], stop_loss_price
)
else:
await query.edit_message_text(f"❌ Long order failed: {result['error']}")
async def _execute_short_callback(self, query, callback_data):
"""Execute short order from callback."""
parts = callback_data.split('_')
token = parts[2]
usdc_amount = float(parts[3])
price = None if parts[4] == 'market' else float(parts[4])
stop_loss_price = None
# Check for stop loss
if len(parts) > 5 and parts[5] == 'sl':
stop_loss_price = float(parts[6])
await query.edit_message_text("⏳ Executing short order...")
result = await self.trading_engine.execute_short_order(token, usdc_amount, price, stop_loss_price)
if result["success"]:
await self.notification_manager.send_short_success_notification(
query, token, result["token_amount"], result["actual_price"], result["order"], stop_loss_price
)
else:
await query.edit_message_text(f"❌ Short order failed: {result['error']}")
async def _execute_exit_callback(self, query, callback_data):
"""Execute exit order from callback."""
parts = callback_data.split('_')
token = parts[2]
await query.edit_message_text("⏳ Closing position...")
result = await self.trading_engine.execute_exit_order(token)
if result["success"]:
await self.notification_manager.send_exit_success_notification(
query, token, result["position_type_closed"], result["contracts_intended_to_close"],
result.get("actual_price", 0), result.get("pnl", 0),
{**result.get("order_placed_details", {}), "cancelled_stop_losses": result.get("cancelled_stop_losses", 0)}
)
else:
await query.edit_message_text(f"❌ Exit order failed: {result['error']}")
async def _execute_sl_callback(self, query, callback_data):
"""Execute stop loss order from callback."""
parts = callback_data.split('_')
token = parts[2]
stop_price = float(parts[3])
await query.edit_message_text("⏳ Setting stop loss...")
result = await self.trading_engine.execute_sl_order(token, stop_price)
if result["success"]:
await self.notification_manager.send_sl_success_notification(
query, token, result["position_type_for_sl"], result["contracts_for_sl"],
stop_price, result.get("order_placed_details", {})
)
else:
await query.edit_message_text(f"❌ Stop loss failed: {result['error']}")
async def _execute_tp_callback(self, query, callback_data):
"""Execute take profit order from callback."""
parts = callback_data.split('_')
token = parts[2]
tp_price = float(parts[3])
await query.edit_message_text("⏳ Setting take profit...")
result = await self.trading_engine.execute_tp_order(token, tp_price)
if result["success"]:
await self.notification_manager.send_tp_success_notification(
query, token, result["position_type_for_tp"], result["contracts_for_tp"],
tp_price, result.get("order_placed_details", {})
)
else:
await query.edit_message_text(f"❌ Take profit failed: {result['error']}")
async def _execute_coo_callback(self, query, callback_data):
"""Execute cancel all orders from callback."""
parts = callback_data.split('_')
token = parts[2]
await query.edit_message_text("⏳ Cancelling orders...")
result = await self.trading_engine.execute_coo_order(token)
if result["success"]:
cancelled_count = result.get("cancelled_count", 0)
failed_count = result.get("failed_count", 0)
cancelled_linked_sls = result.get("cancelled_linked_stop_losses", 0)
cancelled_orders = result.get("cancelled_orders", [])
await self.notification_manager.send_coo_success_notification(
query, token, cancelled_count, failed_count, cancelled_linked_sls, cancelled_orders
)
else:
await query.edit_message_text(f"❌ Cancel orders failed: {result['error']}")