feat: Manual trades wait for fresh 1-minute ATR datapoint
PHASE 2 ENHANCED: Manual trades now wait for next 1-minute datapoint instead of using cached/stale data. Guarantees fresh ATR (<60s old). User requirement: 'when i send a telegram message to enter the market, the bot will simply wait for the next 1 minute datapoint' Implementation: - Add wait_for_fresh_market_data() async helper function - Polls market data cache every 5 seconds (max 60s) - Detects fresh data by timestamp change - Extracts real ATR/ADX/RSI from 1-minute TradingView data - User sees waiting message + confirmation when fresh data arrives - Falls back to preset ATR 0.43 on timeout (fail-safe) Benefits: - Adaptive targets match CURRENT volatility (not historical) - No stale data risk (guaranteed <60s old) - Better than Phase 2 v1 (5-minute tolerance) - Consistent with automated trades (same 1-min data source) User Experience: 1. User: /long sol 2. Bot: ⏳ Waiting for next 1-minute datapoint... 3. [Wait 15-45 seconds typically] 4. Bot: ✅ Fresh ATR: 0.4523 | ADX: 34.2 | RSI: 56.8 5. Bot: ✅ Position opened with adaptive targets Changes: - Add asyncio import for async sleep - Add wait_for_fresh_market_data() before manual_trade_handler - Replace Phase 2 v1 (5min tolerance) with polling logic - Add 3 user messages (waiting, confirmation, timeout) - Extract ATR/ADX/RSI from fresh data or fallback Files: - telegram_command_bot.py: +70 lines polling logic
This commit is contained in:
@@ -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'],
|
||||
|
||||
Reference in New Issue
Block a user