📄 daily_briefing.py 5,759 bytes May 01, 2026 📋 Raw

!/usr/bin/env python3

"""
Daily market briefing job (PM).
Runs at 4:00 PM CT after market close.
"""
import sys
import os
from datetime import datetime

Add workspace to path

sys.path.insert(0, "/home/hoffmann_admin/.openclaw/workspace")

from core.market import (
get_watchlist_with_metadata,
get_top_stories,
get_insider_sentiment_summary,
get_earnings_this_week,
format_news_headline,
check_staleness,
should_skip_briefing,
load_user_watchlist
)
from core.market.news import fetch_ticker_news, extract_top_headlines

def format_price(price: float, change: float, change_pct: float) -> str:
"""Format price with change indicator."""
sign = "+" if change >= 0 else ""
emoji = "🟢" if change >= 0 else "🔴"
return f"${price:.2f} ({sign}{change:.2f} / {sign}{change_pct:.2f}%) {emoji}"

def generate_daily_briefing() -> str:
"""Generate the daily post-market briefing."""
today = datetime.now().strftime("%A, %B %d, %Y")

# Check for stale data
stale = check_staleness()
stale_warning = ""
if any(stale.values()):
    stale_warning = "⚠️ Some data may be stale\n"

# Check if we should skip (critical data too old)
if should_skip_briefing():
    return f"⚠️ Market Briefing Skipped\n\nData is too stale to provide reliable briefing.\nLast successful fetch: check logs."

# Get watchlist data
watchlist_data = get_watchlist_with_metadata()
user_tickers = watchlist_data["user"]
dynamic_tickers = watchlist_data["dynamic"]
all_symbols = watchlist_data["all_symbols"]

# Calculate daily stats
gainers = [t for t in user_tickers + dynamic_tickers if t["change"] > 0]
losers = [t for t in user_tickers + dynamic_tickers if t["change"] < 0]

# Best and worst performers
best = max(gainers, key=lambda x: x["change_pct"]) if gainers else None
worst = min(losers, key=lambda x: x["change_pct"]) if losers else None

# Build watchlist section
watchlist_lines = []

# User tickers
for ticker in user_tickers:
    symbol = ticker["symbol"]
    price_str = format_price(
        ticker["price"],
        ticker["change"],
        ticker["change_pct"]
    )

    # Get news for this ticker
    news = fetch_ticker_news(symbol)
    top_news = extract_top_headlines(news, 1)

    news_str = ""
    if top_news:
        news_str = f"\n    {format_news_headline(symbol, top_news[0])}"

    watchlist_lines.append(f"• {symbol}: {price_str}{news_str}")

# Dynamic tickers with reasons
for ticker in dynamic_tickers:
    symbol = ticker["symbol"]
    price_str = format_price(
        ticker["price"],
        ticker["change"],
        ticker["change_pct"]
    )
    reason = ticker.get("reason", "Trending")

    # Get news for this ticker
    news = fetch_ticker_news(symbol)
    top_news = extract_top_headlines(news, 1)

    news_str = ""
    if top_news:
        news_str = f"\n    {format_news_headline(symbol, top_news[0])}"

    watchlist_lines.append(f"• {symbol}: {price_str} {reason}{news_str}")

# Get top stories across watchlist
top_stories = get_top_stories(all_symbols, count=5)
stories_lines = []
for story in top_stories[:3]:  # Top 3
    ticker = story.get("_ticker", "")
    headline = story.get("headline", "")
    source = story.get("source", "Unknown")
    if len(headline) > 90:
        headline = headline[:87] + "..."
    stories_lines.append(f"• [{ticker}] {headline} ({source})")

if not stories_lines:
    stories_lines.append("• No major market-moving news today")

# Get insider sentiment
insider = get_insider_sentiment_summary(all_symbols)

# Get earnings
earnings = get_earnings_this_week(all_symbols)

# Performance summary
performance_lines = []
if best:
    performance_lines.append(f"🏆 Best: {best['symbol']} (+{best['change_pct']:.2f}%)")
if worst:
    performance_lines.append(f"📉 Worst: {worst['symbol']} ({worst['change_pct']:.2f}%)")

# Build the briefing
briefing = f"""📈 Market Close Brief — {today}

{stale_warning}
📊 Watchlist Performance:
{chr(10).join(watchlist_lines)}

{chr(10).join(performance_lines)}

📰 Today's Top Stories:
{chr(10).join(stories_lines)}

🎯 Insider Sentiment:
{chr(10).join(insider)}

📅 This Week:
{chr(10).join(earnings)}
"""

return briefing

def main():
"""Run the daily briefing."""
try:
briefing = generate_daily_briefing()
print(briefing)

    # Send to Telegram if configured
    chat_id = os.environ.get("MARKET_BRIEFING_CHAT_ID", "8386527252")
    if chat_id:
        try:
            # Try to use Telegram API if available
            import requests
            bot_token = os.environ.get("TELEGRAM_BOT_TOKEN")
            if bot_token and len(briefing) < 4000:
                url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
                payload = {
                    "chat_id": chat_id,
                    "text": briefing,
                    "parse_mode": "HTML"
                }
                requests.post(url, json=payload, timeout=30)
        except Exception as e:
            print(f"Failed to send Telegram message: {e}")

    return 0
except Exception as e:
    print(f"Error generating briefing: {e}")
    import traceback
    traceback.print_exc()
    return 1

if name == "main":
sys.exit(main())