Add yfinance fallback for gold/silver + coarse Telegram progress

- yfinance>=0.2.40 added as a hard dependency
- _fetch_yfinance_fallback(): pulls OHLCV from Yahoo for symbols not on
  the Twelve Data free tier (XAUUSD → GC=F, XAGUSD → SI=F)
- Hooked into both batch and single-symbol fetch paths; logs as [yf]
  to distinguish from Twelve Data [live]
- Fail-loud: if both providers return nothing, the pair is skipped with
  the existing "data unavailable" message
- _analyze_all_tfs accepts an async progress callback; both /signal and
  /scan now show coarse milestones via message edits:
    "📡 Fetching market data..."  →  "🧠 Analyzing pairs..."  →  result
- prefetch_batch is now run in a thread so it doesn't block the event loop

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Harwinder
2026-05-05 14:40:19 -04:00
parent 492b39313a
commit 6a75ed1ffb
3 changed files with 117 additions and 4 deletions
+25 -4
View File
@@ -276,7 +276,7 @@ class ForexTelegramBot:
return SignalAggregator(self._build_config(tf, pairs), data_provider=provider).analyze_all_pairs()
async def _analyze_all_tfs(
self, pairs: List[str]
self, pairs: List[str], progress=None,
) -> Dict[str, List[SignalReport]]:
"""Single combined prefetch, then run all SCAN_TIMEFRAMES concurrently off the warm cache."""
# Collect all unique timeframes needed across every TF scan
@@ -286,9 +286,16 @@ class ForexTelegramBot:
for tf in [_TF_MAP[tf_name]] + _TF_CONFIRMATIONS.get(tf_name, [])
})
provider = ForexDataProvider(PlatformConfig())
provider.prefetch_batch(list(pairs), all_tfs)
loop = asyncio.get_running_loop()
if progress:
await progress(f"📡 Fetching market data ({len(all_tfs)} timeframes × {len(pairs)} pairs)…")
# Run blocking prefetch off the event loop so we don't freeze the bot
await loop.run_in_executor(None, provider.prefetch_batch, list(pairs), all_tfs)
if progress:
await progress("🧠 Analyzing pairs across timeframes…")
tasks = {
tf: loop.run_in_executor(self._executor, self._run_scan, tf, list(pairs), provider)
for tf in SCAN_TIMEFRAMES
@@ -368,8 +375,15 @@ class ForexTelegramBot:
msg = await update.message.reply_text(
f"🔄 Analyzing {_h(display)} across {_h(tfs_str)}…"
)
async def progress(text: str):
try:
await msg.edit_text(text)
except Exception:
pass # ignore edit-rate errors / no-change errors
try:
results_by_tf = await self._analyze_all_tfs([raw])
results_by_tf = await self._analyze_all_tfs([raw], progress=progress)
now_str = _now_est()
# Check we got at least one result
@@ -396,8 +410,15 @@ class ForexTelegramBot:
msg = await update.message.reply_text(
f"🔄 Scanning all pairs across {_h(tfs_str)}…"
)
async def progress(text: str):
try:
await msg.edit_text(text)
except Exception:
pass
try:
results_by_tf = await self._analyze_all_tfs(list(MAJOR_PAIRS))
results_by_tf = await self._analyze_all_tfs(list(MAJOR_PAIRS), progress=progress)
now_str = _now_est()
# Best grade per pair across all TFs