Align with Twelve Data API docs and add Telegram command menu

data_provider:
- Switch to Authorization header (recommended over apikey query param)
- Enforce 800 credits/day daily limit with warning at 80%
- Fall back to synthetic data when daily limit is reached
- Fix null OHLC field handling (API can return null for any field)

telegram_bot:
- Register commands with set_my_commands so Telegram shows / menu
- Add /credits command to show daily API credit usage and limits

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Harwinder
2026-05-04 15:33:32 -04:00
parent 71c3fb6d82
commit 0e95a34d82
2 changed files with 98 additions and 28 deletions
+41 -1
View File
@@ -27,7 +27,7 @@ from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timezone
from typing import Dict, Optional
from telegram import Update
from telegram import BotCommand, Update
from telegram.constants import ParseMode
from telegram.ext import Application, CallbackContext, CommandHandler, ContextTypes
@@ -514,6 +514,29 @@ class ForexTelegramBot:
job.schedule_removal()
await update.message.reply_text("❌ Watch alerts disabled.")
async def cmd_credits(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Show API credit usage against the free-tier daily limit."""
from data_provider import ForexDataProvider
config = self._build_config(self._state(update.effective_chat.id))
provider = ForexDataProvider(config)
used = provider._daily_credits
limit = provider.DAILY_LIMIT
remaining = limit - used
pct = (used / limit) * 100 if limit else 0
bar_filled = int(pct / 5)
bar = "█" * bar_filled + "░" * (20 - bar_filled)
status = "🟢" if pct < 60 else ("🟡" if pct < 80 else "🔴")
text = (
"<b>📊 API Credit Usage</b>\n\n"
f"<code>{bar}</code> {status}\n"
f"<b>Used:</b> {used} / {limit} credits\n"
f"<b>Remaining:</b> {remaining} credits\n\n"
"<i>Free tier: 8 credits/min, 800 credits/day\n"
"Each full scan uses ~21 credits (7 pairs × 3 timeframes)\n"
"≈ 38 scans available per day</i>"
)
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
# ── Watch job ──────────────────────────────────────────────────
async def _watch_job(self, context: CallbackContext):
@@ -548,9 +571,25 @@ class ForexTelegramBot:
" export TELEGRAM_BOT_TOKEN='your_token_here'"
)
async def _register_commands(app: Application) -> None:
"""Register commands with Telegram so they appear in the / menu."""
await app.bot.set_my_commands([
BotCommand("scan", "Scan all major pairs"),
BotCommand("signals", "Actionable signals only (grade B+)"),
BotCommand("pair", "Detailed pair analysis — /pair EURUSD"),
BotCommand("all", "Scan major + cross pairs"),
BotCommand("tf", "Set timeframe — /tf H4"),
BotCommand("watch", "Enable periodic alerts"),
BotCommand("unwatch", "Disable alerts"),
BotCommand("credits", "View API credit usage"),
BotCommand("status", "View current settings"),
BotCommand("help", "Full command reference"),
])
app = (
Application.builder()
.token(token)
.post_init(_register_commands)
.build()
)
@@ -564,6 +603,7 @@ class ForexTelegramBot:
app.add_handler(CommandHandler("all", self.cmd_all))
app.add_handler(CommandHandler("watch", self.cmd_watch))
app.add_handler(CommandHandler("unwatch", self.cmd_unwatch))
app.add_handler(CommandHandler("credits", self.cmd_credits))
return app