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:
mindesbunister
2025-12-02 19:35:24 +01:00
parent 702ef7953b
commit 23277b7c87

View File

@@ -5,6 +5,7 @@ Only responds to YOUR commands in YOUR chat
""" """
import os import os
import time import time
import asyncio
import requests import requests
from telegram import Update from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters 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)}") 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): async def manual_trade_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Execute manual long/short commands sent as plain text with analytics validation.""" """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) # Analytics check error - proceed with trade (fail-open)
print(f"⚠️ Analytics error: {analytics_error} - proceeding anyway", flush=True) print(f"⚠️ Analytics error: {analytics_error} - proceeding anyway", flush=True)
# Execute the trade # 🆕 PHASE 2 ENHANCED: Wait for fresh 1-minute datapoint (Dec 2, 2025)
metrics = MANUAL_METRICS[direction] # 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 = { payload = {
'symbol': symbol_info['tradingview'], 'symbol': symbol_info['tradingview'],
'direction': direction, 'direction': direction,
'timeframe': 'manual', 'timeframe': 'manual',
'signalStrength': 'manual', 'signalStrength': 'manual',
'atr': metrics['atr'], 'atr': metrics['atr'], # 🆕 Fresh ATR from 1-minute data or preset fallback
'adx': metrics['adx'], 'adx': metrics['adx'],
'rsi': metrics['rsi'], 'rsi': metrics['rsi'],
'volumeRatio': metrics['volumeRatio'], 'volumeRatio': metrics['volumeRatio'],