diff --git a/telegram_command_bot.py b/telegram_command_bot.py index f65c9f8..aac2afe 100644 --- a/telegram_command_bot.py +++ b/telegram_command_bot.py @@ -5,6 +5,7 @@ Only responds to YOUR commands in YOUR chat """ import os import time +import asyncio import requests from telegram import Update from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters @@ -556,6 +557,71 @@ async def trade_command(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text(f"❌ Error: {str(e)}") +async def wait_for_fresh_market_data(symbol: str, max_wait: int = 60): + """ + Poll market data cache until fresh data arrives (new timestamp detected). + + Args: + symbol: Drift symbol (e.g., 'SOL-PERP', 'ETH-PERP') + max_wait: Maximum seconds to wait (default 60) + + Returns: + dict: Fresh market data with atr/adx/rsi/timestamp + None: Timeout or error + """ + start_time = time.time() + last_timestamp = None + poll_count = 0 + + print(f"⏳ Waiting for fresh 1-minute data: {symbol} (max {max_wait}s)", flush=True) + + while (time.time() - start_time) < max_wait: + try: + response = requests.get( + f"{TRADING_BOT_URL}/api/trading/market-data", + timeout=5 + ) + + if response.ok: + data = response.json() + + if data.get('success') and data.get('cache'): + symbol_data = data['cache'].get(symbol) + + if symbol_data: + current_timestamp = symbol_data.get('timestamp') + data_age = symbol_data.get('ageSeconds', 999) + poll_count += 1 + + print(f"🔍 Poll #{poll_count}: timestamp={current_timestamp}, age={data_age}s", flush=True) + + # Fresh data detected (timestamp changed from last poll) + if last_timestamp and current_timestamp != last_timestamp: + print(f"✅ Fresh data detected after {poll_count} polls ({time.time() - start_time:.1f}s)", flush=True) + return symbol_data + + last_timestamp = current_timestamp + else: + print(f"⚠️ No data for {symbol} in cache (poll #{poll_count + 1})", flush=True) + poll_count += 1 + else: + print(f"⚠️ No cache data in response (poll #{poll_count + 1})", flush=True) + poll_count += 1 + else: + print(f"⚠️ Market data fetch failed: {response.status_code} (poll #{poll_count + 1})", flush=True) + poll_count += 1 + + except Exception as e: + print(f"⚠️ Market data poll error: {e} (poll #{poll_count + 1})", flush=True) + poll_count += 1 + + # Wait 5 seconds before next poll + await asyncio.sleep(5) + + print(f"❌ Timeout after {poll_count} polls ({max_wait}s) - no fresh data received", flush=True) + return None + + async def manual_trade_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): """Execute manual long/short commands sent as plain text with analytics validation.""" @@ -655,15 +721,74 @@ async def manual_trade_handler(update: Update, context: ContextTypes.DEFAULT_TYP # Analytics check error - proceed with trade (fail-open) print(f"⚠️ Analytics error: {analytics_error} - proceeding anyway", flush=True) - # Execute the trade - metrics = MANUAL_METRICS[direction] + # 🆕 PHASE 2 ENHANCED: Wait for fresh 1-minute datapoint (Dec 2, 2025) + # User requirement: "when i send a telegram message to enter the market, + # the bot will simply wait for the next 1 minute datapoint" + + # Send waiting message to user + await update.message.reply_text( + f"⏳ *Waiting for next 1-minute datapoint...*\n" + f"Will execute with fresh ATR (max 60s)", + parse_mode='Markdown' + ) + + # Poll for fresh data (new timestamp = new datapoint arrived) + fresh_data = await wait_for_fresh_market_data(drift_symbol, max_wait=60) + + # Extract metrics from fresh data or fallback to preset + metrics = MANUAL_METRICS[direction] # Start with preset defaults + + if fresh_data: + # Use real-time metrics from fresh 1-minute data + fresh_atr = fresh_data.get('atr') + fresh_adx = fresh_data.get('adx') + fresh_rsi = fresh_data.get('rsi') + data_age = fresh_data.get('ageSeconds', 0) + + if fresh_atr and fresh_atr > 0: + metrics = { + 'atr': fresh_atr, + 'adx': fresh_adx if fresh_adx else metrics['adx'], # Fallback if missing + 'rsi': fresh_rsi if fresh_rsi else metrics['rsi'], # Fallback if missing + 'volumeRatio': metrics['volumeRatio'], # Keep preset (not in 1-min data) + 'pricePosition': metrics['pricePosition'], # Keep preset (not in 1-min data) + } + + print(f"✅ Using fresh metrics: ATR={metrics['atr']:.4f}, ADX={metrics['adx']:.1f}, RSI={metrics['rsi']:.1f} ({data_age}s old)", flush=True) + + await update.message.reply_text( + f"✅ *Fresh data received*\n" + f"ATR: {metrics['atr']:.4f} | ADX: {metrics['adx']:.1f} | RSI: {metrics['rsi']:.1f}\n" + f"Executing {direction.upper()} {symbol_info['label']}...", + parse_mode='Markdown' + ) + else: + print(f"⚠️ Fresh data invalid (ATR={fresh_atr}), using preset metrics", flush=True) + + await update.message.reply_text( + f"⚠️ *Fresh data invalid*\n" + f"Using preset ATR: {metrics['atr']}\n" + f"Executing {direction.upper()} {symbol_info['label']}...", + parse_mode='Markdown' + ) + else: + # Timeout - fallback to preset with warning + print(f"⚠️ Timeout waiting for fresh data - using preset metrics: ATR={metrics['atr']}", flush=True) + + await update.message.reply_text( + f"⚠️ *Timeout waiting for fresh data*\n" + f"Using preset ATR: {metrics['atr']}\n" + f"Executing {direction.upper()} {symbol_info['label']}...", + parse_mode='Markdown' + ) + # Execute the trade with fresh or fallback metrics payload = { 'symbol': symbol_info['tradingview'], 'direction': direction, 'timeframe': 'manual', 'signalStrength': 'manual', - 'atr': metrics['atr'], + 'atr': metrics['atr'], # 🆕 Fresh ATR from 1-minute data or preset fallback 'adx': metrics['adx'], 'rsi': metrics['rsi'], 'volumeRatio': metrics['volumeRatio'],