2025-12-26 19:50:12 +01:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
"""
|
2025-12-26 20:09:41 +01:00
|
|
|
|
🤖 TELEGRAM BOT COMMANDS V2 - Interactive Bot Control
|
|
|
|
|
|
Updated for python-telegram-bot 22.x (latest version)
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
COMMANDS:
|
|
|
|
|
|
/status - Bot Status, offene Positionen, Balance
|
|
|
|
|
|
/pause - Trading pausieren (keine neuen Trades)
|
|
|
|
|
|
/resume - Trading fortsetzen
|
|
|
|
|
|
/close - ALLE offenen Positionen schließen (Emergency)
|
|
|
|
|
|
/stats - Performance Statistiken (heute, Woche, gesamt)
|
|
|
|
|
|
/balance - Aktueller Kontostand + Equity
|
|
|
|
|
|
/help - Liste aller Commands
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
import asyncio
|
2025-12-26 19:50:12 +01:00
|
|
|
|
import threading
|
|
|
|
|
|
import json
|
|
|
|
|
|
import MetaTrader5 as mt5
|
|
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
|
|
from typing import Optional, Dict
|
|
|
|
|
|
import sqlite3
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
# New API imports for python-telegram-bot 22.x
|
|
|
|
|
|
from telegram import Update
|
|
|
|
|
|
from telegram.ext import Application, CommandHandler, ContextTypes
|
|
|
|
|
|
|
2025-12-26 19:50:12 +01:00
|
|
|
|
# Import existing modules
|
2025-12-26 20:09:41 +01:00
|
|
|
|
from telegram_notifier import load_telegram_config
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TradingBotController:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Controller für Trading Bot - ermöglicht Pause/Resume/Close
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
self.is_paused = False
|
|
|
|
|
|
self.pause_reason = ""
|
|
|
|
|
|
self.pause_timestamp = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def pause_trading(self, reason: str = "Manual pause via Telegram"):
|
|
|
|
|
|
"""Pausiert Trading (keine neuen Trades)"""
|
|
|
|
|
|
self.is_paused = True
|
|
|
|
|
|
self.pause_reason = reason
|
|
|
|
|
|
self.pause_timestamp = datetime.now()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
'success': True,
|
|
|
|
|
|
'message': f"✅ Trading PAUSED\nReason: {reason}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def resume_trading(self):
|
|
|
|
|
|
"""Aktiviert Trading wieder"""
|
|
|
|
|
|
if not self.is_paused:
|
|
|
|
|
|
return {
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'message': "⚠️ Trading is not paused"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pause_duration = datetime.now() - self.pause_timestamp
|
|
|
|
|
|
self.is_paused = False
|
|
|
|
|
|
self.pause_reason = ""
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
'success': True,
|
|
|
|
|
|
'message': f"✅ Trading RESUMED\nWas paused for: {pause_duration}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def close_all_positions(self) -> Dict:
|
2025-12-26 20:09:41 +01:00
|
|
|
|
"""Schließt ALLE offenen Positionen (Emergency Exit)"""
|
2025-12-26 19:50:12 +01:00
|
|
|
|
if not mt5.initialize():
|
|
|
|
|
|
return {
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'message': "❌ MT5 connection failed"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
positions = mt5.positions_get()
|
|
|
|
|
|
|
|
|
|
|
|
if not positions:
|
|
|
|
|
|
return {
|
|
|
|
|
|
'success': True,
|
|
|
|
|
|
'message': "ℹ️ No open positions to close"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
closed_count = 0
|
|
|
|
|
|
failed_count = 0
|
|
|
|
|
|
total_profit = 0
|
|
|
|
|
|
|
|
|
|
|
|
for position in positions:
|
|
|
|
|
|
# Prepare close request
|
|
|
|
|
|
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": "Emergency close via Telegram",
|
|
|
|
|
|
"type_time": mt5.ORDER_TIME_GTC,
|
|
|
|
|
|
"type_filling": mt5.ORDER_FILLING_IOC,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Execute close
|
|
|
|
|
|
result = mt5.order_send(close_request)
|
|
|
|
|
|
|
|
|
|
|
|
if result.retcode == mt5.TRADE_RETCODE_DONE:
|
|
|
|
|
|
closed_count += 1
|
|
|
|
|
|
total_profit += position.profit
|
|
|
|
|
|
else:
|
|
|
|
|
|
failed_count += 1
|
|
|
|
|
|
|
|
|
|
|
|
message = f"🚨 *EMERGENCY CLOSE EXECUTED*\n\n"
|
|
|
|
|
|
message += f"✅ Closed: {closed_count} positions\n"
|
|
|
|
|
|
if failed_count > 0:
|
|
|
|
|
|
message += f"❌ Failed: {failed_count} positions\n"
|
|
|
|
|
|
message += f"💰 Total P&L: ${total_profit:.2f}"
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
'success': True,
|
|
|
|
|
|
'message': message,
|
|
|
|
|
|
'closed': closed_count,
|
|
|
|
|
|
'failed': failed_count,
|
|
|
|
|
|
'profit': total_profit
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_status(self) -> Dict:
|
2025-12-26 20:09:41 +01:00
|
|
|
|
"""Holt aktuellen Bot Status"""
|
2025-12-26 19:50:12 +01:00
|
|
|
|
if not mt5.initialize():
|
|
|
|
|
|
return {
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'message': "❌ MT5 connection failed"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Account Info
|
|
|
|
|
|
account_info = mt5.account_info()
|
|
|
|
|
|
|
|
|
|
|
|
# Open Positions
|
|
|
|
|
|
positions = mt5.positions_get()
|
|
|
|
|
|
|
|
|
|
|
|
# Calculate floating P&L
|
|
|
|
|
|
floating_pl = sum(p.profit for p in positions) if positions else 0
|
|
|
|
|
|
|
|
|
|
|
|
# Build status message
|
|
|
|
|
|
status_msg = "📊 *BOT STATUS*\n\n"
|
|
|
|
|
|
|
|
|
|
|
|
# Trading Status
|
|
|
|
|
|
if self.is_paused:
|
|
|
|
|
|
status_msg += "⏸️ *Status:* PAUSED\n"
|
|
|
|
|
|
status_msg += f"*Reason:* {self.pause_reason}\n"
|
|
|
|
|
|
duration = datetime.now() - self.pause_timestamp
|
|
|
|
|
|
status_msg += f"*Duration:* {duration}\n\n"
|
|
|
|
|
|
else:
|
|
|
|
|
|
status_msg += "✅ *Status:* ACTIVE\n\n"
|
|
|
|
|
|
|
|
|
|
|
|
# Account Info
|
|
|
|
|
|
status_msg += "💰 *ACCOUNT*\n"
|
|
|
|
|
|
status_msg += f"Balance: ${account_info.balance:.2f}\n"
|
|
|
|
|
|
status_msg += f"Equity: ${account_info.equity:.2f}\n"
|
|
|
|
|
|
status_msg += f"Margin: ${account_info.margin:.2f}\n"
|
|
|
|
|
|
status_msg += f"Free Margin: ${account_info.margin_free:.2f}\n\n"
|
|
|
|
|
|
|
|
|
|
|
|
# Open Positions
|
|
|
|
|
|
status_msg += f"📈 *POSITIONS*\n"
|
|
|
|
|
|
status_msg += f"Open: {len(positions) if positions else 0}\n"
|
|
|
|
|
|
status_msg += f"Floating P&L: ${floating_pl:.2f}\n\n"
|
|
|
|
|
|
|
|
|
|
|
|
# Position Details
|
|
|
|
|
|
if positions:
|
|
|
|
|
|
status_msg += "*Open Trades:*\n"
|
|
|
|
|
|
for i, p in enumerate(positions[:5], 1): # Max 5 positions
|
|
|
|
|
|
type_emoji = "🟢" if p.type == mt5.ORDER_TYPE_BUY else "🔴"
|
|
|
|
|
|
status_msg += f"{i}. {type_emoji} {p.symbol} | {p.volume} lots | ${p.profit:.2f}\n"
|
|
|
|
|
|
|
|
|
|
|
|
if len(positions) > 5:
|
|
|
|
|
|
status_msg += f"... and {len(positions) - 5} more\n"
|
|
|
|
|
|
|
|
|
|
|
|
status_msg += f"\n⏰ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} UTC"
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
'success': True,
|
|
|
|
|
|
'message': status_msg,
|
|
|
|
|
|
'is_paused': self.is_paused,
|
|
|
|
|
|
'positions': len(positions) if positions else 0,
|
|
|
|
|
|
'balance': account_info.balance,
|
|
|
|
|
|
'equity': account_info.equity
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_balance(self) -> Dict:
|
|
|
|
|
|
"""Holt Balance + Equity Info"""
|
|
|
|
|
|
if not mt5.initialize():
|
|
|
|
|
|
return {
|
|
|
|
|
|
'success': False,
|
|
|
|
|
|
'message': "❌ MT5 connection failed"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
account_info = mt5.account_info()
|
|
|
|
|
|
positions = mt5.positions_get()
|
|
|
|
|
|
floating_pl = sum(p.profit for p in positions) if positions else 0
|
|
|
|
|
|
|
|
|
|
|
|
message = "💰 *ACCOUNT BALANCE*\n\n"
|
|
|
|
|
|
message += f"Balance: ${account_info.balance:.2f}\n"
|
|
|
|
|
|
message += f"Equity: ${account_info.equity:.2f}\n"
|
|
|
|
|
|
message += f"Floating P&L: ${floating_pl:.2f}\n\n"
|
|
|
|
|
|
message += f"Margin Used: ${account_info.margin:.2f}\n"
|
|
|
|
|
|
message += f"Free Margin: ${account_info.margin_free:.2f}\n"
|
|
|
|
|
|
message += f"Margin Level: {account_info.margin_level:.2f}%\n\n"
|
|
|
|
|
|
message += f"⏰ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} UTC"
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
'success': True,
|
|
|
|
|
|
'message': message
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TelegramBotCommander:
|
|
|
|
|
|
"""
|
2025-12-26 20:09:41 +01:00
|
|
|
|
Telegram Bot Command Handler (python-telegram-bot 22.x)
|
2025-12-26 19:50:12 +01:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, config_file: str = "telegram_config.json"):
|
2025-12-26 20:09:41 +01:00
|
|
|
|
"""Initialize Telegram Bot Commander"""
|
2025-12-26 19:50:12 +01:00
|
|
|
|
# Load config
|
|
|
|
|
|
self.config = load_telegram_config()
|
|
|
|
|
|
|
|
|
|
|
|
if not self.config or 'bot_token' not in self.config:
|
|
|
|
|
|
raise ValueError("❌ Telegram config not found or invalid")
|
|
|
|
|
|
|
|
|
|
|
|
self.bot_token = self.config['bot_token']
|
|
|
|
|
|
self.chat_id = self.config['chat_id']
|
|
|
|
|
|
|
|
|
|
|
|
# Controller
|
|
|
|
|
|
self.controller = TradingBotController()
|
|
|
|
|
|
|
|
|
|
|
|
# Database path
|
|
|
|
|
|
self.db_path = "trading_bot.db"
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
# Application (new API)
|
|
|
|
|
|
self.application = None
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
print("✅ Telegram Bot Commander initialized")
|
|
|
|
|
|
print(f"📱 Bot Token: {self.bot_token[:20]}...")
|
|
|
|
|
|
print(f"👤 Chat ID: {self.chat_id}")
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
async def start_async(self):
|
|
|
|
|
|
"""Start bot asynchronously (new API)"""
|
2025-12-26 21:38:17 +01:00
|
|
|
|
# Create application (disable job queue to avoid timezone issues)
|
|
|
|
|
|
self.application = (
|
|
|
|
|
|
Application.builder()
|
|
|
|
|
|
.token(self.bot_token)
|
|
|
|
|
|
.job_queue(None) # Disable job queue - fixes timezone error
|
|
|
|
|
|
.build()
|
|
|
|
|
|
)
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
# Add command handlers
|
|
|
|
|
|
self.application.add_handler(CommandHandler("start", self.cmd_start))
|
|
|
|
|
|
self.application.add_handler(CommandHandler("help", self.cmd_help))
|
|
|
|
|
|
self.application.add_handler(CommandHandler("status", self.cmd_status))
|
|
|
|
|
|
self.application.add_handler(CommandHandler("pause", self.cmd_pause))
|
|
|
|
|
|
self.application.add_handler(CommandHandler("resume", self.cmd_resume))
|
|
|
|
|
|
self.application.add_handler(CommandHandler("close", self.cmd_close))
|
|
|
|
|
|
self.application.add_handler(CommandHandler("balance", self.cmd_balance))
|
|
|
|
|
|
self.application.add_handler(CommandHandler("stats", self.cmd_stats))
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
print("✅ Command handlers registered")
|
|
|
|
|
|
print("🚀 Starting Telegram Bot...")
|
|
|
|
|
|
print("📱 Send /help to see available commands")
|
|
|
|
|
|
|
|
|
|
|
|
# Send startup message
|
2025-12-26 20:09:41 +01:00
|
|
|
|
await self.application.bot.send_message(
|
|
|
|
|
|
chat_id=self.chat_id,
|
|
|
|
|
|
text="🤖 *Telegram Bot Commander STARTED*\n\n"
|
|
|
|
|
|
"✅ Bot is now listening for commands\n"
|
|
|
|
|
|
"Send /help for available commands",
|
|
|
|
|
|
parse_mode='Markdown'
|
2025-12-26 19:50:12 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
# Initialize and start polling
|
|
|
|
|
|
await self.application.initialize()
|
|
|
|
|
|
await self.application.start()
|
|
|
|
|
|
await self.application.updater.start_polling()
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
print("✅ Bot is running")
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def start_background(self):
|
|
|
|
|
|
"""Start bot in background thread"""
|
2025-12-26 20:09:41 +01:00
|
|
|
|
def run_async_loop():
|
2025-12-26 21:38:17 +01:00
|
|
|
|
try:
|
|
|
|
|
|
# Create new event loop for this thread
|
|
|
|
|
|
loop = asyncio.new_event_loop()
|
|
|
|
|
|
asyncio.set_event_loop(loop)
|
|
|
|
|
|
|
|
|
|
|
|
# Run bot
|
|
|
|
|
|
loop.run_until_complete(self.start_async())
|
|
|
|
|
|
|
|
|
|
|
|
# Keep loop running
|
|
|
|
|
|
loop.run_forever()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"❌ Bot thread error: {e}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
2025-12-26 20:09:41 +01:00
|
|
|
|
|
|
|
|
|
|
thread = threading.Thread(target=run_async_loop, daemon=True)
|
2025-12-26 19:50:12 +01:00
|
|
|
|
thread.start()
|
|
|
|
|
|
print("✅ Telegram Bot running in background")
|
|
|
|
|
|
return thread
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================
|
2025-12-26 20:09:41 +01:00
|
|
|
|
# COMMAND HANDLERS (async in new API)
|
2025-12-26 19:50:12 +01:00
|
|
|
|
# ==========================================
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
async def cmd_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
2025-12-26 19:50:12 +01:00
|
|
|
|
"""Handle /start command"""
|
|
|
|
|
|
message = (
|
|
|
|
|
|
"🤖 *Trading Bot Commander*\n\n"
|
|
|
|
|
|
"Welcome! I can help you control your trading bot remotely.\n\n"
|
|
|
|
|
|
"Send /help to see available commands."
|
|
|
|
|
|
)
|
2025-12-26 20:09:41 +01:00
|
|
|
|
await update.message.reply_text(message, parse_mode='Markdown')
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
async def cmd_help(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
2025-12-26 19:50:12 +01:00
|
|
|
|
"""Handle /help command"""
|
|
|
|
|
|
message = """
|
|
|
|
|
|
📚 *Available Commands*
|
|
|
|
|
|
|
|
|
|
|
|
*Bot Control:*
|
|
|
|
|
|
/status - Bot status, positions, balance
|
|
|
|
|
|
/pause - Pause trading (no new trades)
|
|
|
|
|
|
/resume - Resume trading
|
|
|
|
|
|
/close - Close ALL positions (emergency)
|
|
|
|
|
|
|
|
|
|
|
|
*Information:*
|
|
|
|
|
|
/balance - Account balance & equity
|
|
|
|
|
|
/stats - Performance statistics
|
|
|
|
|
|
/help - Show this help
|
|
|
|
|
|
|
|
|
|
|
|
*Safety Features:*
|
|
|
|
|
|
⚠️ /close requires confirmation
|
|
|
|
|
|
✅ /pause is instant
|
|
|
|
|
|
🔒 Only authorized user can use commands
|
|
|
|
|
|
"""
|
2025-12-26 20:09:41 +01:00
|
|
|
|
await update.message.reply_text(message, parse_mode='Markdown')
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
async def cmd_status(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
2025-12-26 19:50:12 +01:00
|
|
|
|
"""Handle /status command"""
|
|
|
|
|
|
result = self.controller.get_status()
|
2025-12-26 20:09:41 +01:00
|
|
|
|
await update.message.reply_text(result['message'], parse_mode='Markdown')
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
async def cmd_pause(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
2025-12-26 19:50:12 +01:00
|
|
|
|
"""Handle /pause command"""
|
|
|
|
|
|
result = self.controller.pause_trading("Manual pause via Telegram")
|
2025-12-26 20:09:41 +01:00
|
|
|
|
await update.message.reply_text(result['message'], parse_mode='Markdown')
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
async def cmd_resume(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
2025-12-26 19:50:12 +01:00
|
|
|
|
"""Handle /resume command"""
|
|
|
|
|
|
result = self.controller.resume_trading()
|
2025-12-26 20:09:41 +01:00
|
|
|
|
await update.message.reply_text(result['message'], parse_mode='Markdown')
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
async def cmd_close(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
2025-12-26 19:50:12 +01:00
|
|
|
|
"""Handle /close command"""
|
|
|
|
|
|
# Safety check - require confirmation
|
|
|
|
|
|
args = context.args
|
|
|
|
|
|
|
|
|
|
|
|
if not args or args[0].lower() != 'confirm':
|
|
|
|
|
|
message = (
|
|
|
|
|
|
"⚠️ *EMERGENCY CLOSE*\n\n"
|
|
|
|
|
|
"This will close ALL open positions!\n\n"
|
|
|
|
|
|
"To confirm, send:\n"
|
|
|
|
|
|
"`/close confirm`"
|
|
|
|
|
|
)
|
2025-12-26 20:09:41 +01:00
|
|
|
|
await update.message.reply_text(message, parse_mode='Markdown')
|
2025-12-26 19:50:12 +01:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Execute close
|
2025-12-26 20:09:41 +01:00
|
|
|
|
await update.message.reply_text("🚨 Closing all positions...", parse_mode='Markdown')
|
2025-12-26 19:50:12 +01:00
|
|
|
|
result = self.controller.close_all_positions()
|
2025-12-26 20:09:41 +01:00
|
|
|
|
await update.message.reply_text(result['message'], parse_mode='Markdown')
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
async def cmd_balance(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
2025-12-26 19:50:12 +01:00
|
|
|
|
"""Handle /balance command"""
|
|
|
|
|
|
result = self.controller.get_balance()
|
2025-12-26 20:09:41 +01:00
|
|
|
|
await update.message.reply_text(result['message'], parse_mode='Markdown')
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
async def cmd_stats(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
2025-12-26 19:50:12 +01:00
|
|
|
|
"""Handle /stats command"""
|
|
|
|
|
|
stats = self._get_performance_stats()
|
2025-12-26 20:09:41 +01:00
|
|
|
|
await update.message.reply_text(stats, parse_mode='Markdown')
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================
|
|
|
|
|
|
# HELPER FUNCTIONS
|
|
|
|
|
|
# ==========================================
|
|
|
|
|
|
|
|
|
|
|
|
def _get_performance_stats(self) -> str:
|
|
|
|
|
|
"""Get performance statistics from database"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
conn = sqlite3.connect(self.db_path)
|
|
|
|
|
|
cursor = conn.cursor()
|
|
|
|
|
|
|
|
|
|
|
|
# Today's stats
|
|
|
|
|
|
today_stats = cursor.execute("""
|
|
|
|
|
|
SELECT
|
|
|
|
|
|
COUNT(*) as trades,
|
|
|
|
|
|
SUM(CASE WHEN net_profit > 0 THEN 1 ELSE 0 END) as wins,
|
|
|
|
|
|
SUM(CASE WHEN net_profit < 0 THEN 1 ELSE 0 END) as losses,
|
|
|
|
|
|
ROUND(SUM(net_profit), 2) as profit
|
|
|
|
|
|
FROM trades
|
|
|
|
|
|
WHERE DATE(entry_time) = DATE('now')
|
|
|
|
|
|
""").fetchone()
|
|
|
|
|
|
|
|
|
|
|
|
# This week's stats
|
|
|
|
|
|
week_stats = cursor.execute("""
|
|
|
|
|
|
SELECT
|
|
|
|
|
|
COUNT(*) as trades,
|
|
|
|
|
|
SUM(CASE WHEN net_profit > 0 THEN 1 ELSE 0 END) as wins,
|
|
|
|
|
|
SUM(CASE WHEN net_profit < 0 THEN 1 ELSE 0 END) as losses,
|
|
|
|
|
|
ROUND(SUM(net_profit), 2) as profit
|
|
|
|
|
|
FROM trades
|
|
|
|
|
|
WHERE entry_time >= datetime('now', '-7 days')
|
|
|
|
|
|
""").fetchone()
|
|
|
|
|
|
|
|
|
|
|
|
# Overall stats
|
|
|
|
|
|
overall_stats = cursor.execute("""
|
|
|
|
|
|
SELECT
|
|
|
|
|
|
COUNT(*) as trades,
|
|
|
|
|
|
SUM(CASE WHEN net_profit > 0 THEN 1 ELSE 0 END) as wins,
|
|
|
|
|
|
SUM(CASE WHEN net_profit < 0 THEN 1 ELSE 0 END) as losses,
|
|
|
|
|
|
ROUND(SUM(net_profit), 2) as profit,
|
|
|
|
|
|
ROUND(AVG(net_profit), 2) as avg_profit
|
|
|
|
|
|
FROM trades
|
|
|
|
|
|
WHERE net_profit IS NOT NULL
|
|
|
|
|
|
""").fetchone()
|
|
|
|
|
|
|
|
|
|
|
|
conn.close()
|
|
|
|
|
|
|
|
|
|
|
|
# Build message
|
|
|
|
|
|
message = "📊 *PERFORMANCE STATISTICS*\n\n"
|
|
|
|
|
|
|
|
|
|
|
|
# Today
|
|
|
|
|
|
message += "📅 *TODAY*\n"
|
|
|
|
|
|
if today_stats[0] > 0:
|
|
|
|
|
|
today_wr = (today_stats[1] / today_stats[0] * 100) if today_stats[0] > 0 else 0
|
|
|
|
|
|
message += f"Trades: {today_stats[0]} | WR: {today_wr:.1f}%\n"
|
|
|
|
|
|
message += f"Profit: ${today_stats[3]:.2f}\n\n"
|
|
|
|
|
|
else:
|
|
|
|
|
|
message += "No trades today\n\n"
|
|
|
|
|
|
|
|
|
|
|
|
# This Week
|
|
|
|
|
|
message += "📈 *THIS WEEK (7 days)*\n"
|
|
|
|
|
|
if week_stats[0] > 0:
|
|
|
|
|
|
week_wr = (week_stats[1] / week_stats[0] * 100) if week_stats[0] > 0 else 0
|
|
|
|
|
|
message += f"Trades: {week_stats[0]} | WR: {week_wr:.1f}%\n"
|
|
|
|
|
|
message += f"Wins: {week_stats[1]} | Losses: {week_stats[2]}\n"
|
|
|
|
|
|
message += f"Profit: ${week_stats[3]:.2f}\n\n"
|
|
|
|
|
|
else:
|
|
|
|
|
|
message += "No trades this week\n\n"
|
|
|
|
|
|
|
|
|
|
|
|
# Overall
|
|
|
|
|
|
message += "🎯 *OVERALL*\n"
|
|
|
|
|
|
if overall_stats[0] > 0:
|
|
|
|
|
|
overall_wr = (overall_stats[1] / overall_stats[0] * 100) if overall_stats[0] > 0 else 0
|
|
|
|
|
|
message += f"Total Trades: {overall_stats[0]}\n"
|
|
|
|
|
|
message += f"Win Rate: {overall_wr:.1f}%\n"
|
|
|
|
|
|
message += f"Total Profit: ${overall_stats[3]:.2f}\n"
|
|
|
|
|
|
message += f"Avg/Trade: ${overall_stats[4]:.2f}\n"
|
|
|
|
|
|
else:
|
|
|
|
|
|
message += "No trade history\n"
|
|
|
|
|
|
|
|
|
|
|
|
message += f"\n⏰ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} UTC"
|
|
|
|
|
|
|
|
|
|
|
|
return message
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
return f"❌ Error fetching stats: {e}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================
|
|
|
|
|
|
# GLOBAL CONTROLLER INSTANCE
|
|
|
|
|
|
# ==========================================
|
|
|
|
|
|
|
|
|
|
|
|
_global_controller = None
|
|
|
|
|
|
|
|
|
|
|
|
def get_bot_controller() -> TradingBotController:
|
|
|
|
|
|
"""Get global bot controller instance"""
|
|
|
|
|
|
global _global_controller
|
|
|
|
|
|
if _global_controller is None:
|
|
|
|
|
|
_global_controller = TradingBotController()
|
|
|
|
|
|
return _global_controller
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================
|
2025-12-26 20:09:41 +01:00
|
|
|
|
# MAIN
|
2025-12-26 19:50:12 +01:00
|
|
|
|
# ==========================================
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
print("="*70)
|
2025-12-26 20:09:41 +01:00
|
|
|
|
print("🤖 TELEGRAM BOT COMMANDER V2 - Starting")
|
2025-12-26 19:50:12 +01:00
|
|
|
|
print("="*70)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
commander = TelegramBotCommander()
|
2025-12-26 20:09:41 +01:00
|
|
|
|
asyncio.run(commander.start_async())
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
2025-12-26 20:09:41 +01:00
|
|
|
|
# Keep running
|
|
|
|
|
|
asyncio.get_event_loop().run_forever()
|
2025-12-26 19:50:12 +01:00
|
|
|
|
|
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
|
print("\n⏸️ Bot stopped by user")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"❌ Error: {e}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|