Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

166 lines
6.6 KiB
Python

"""
akshare news adapter for TradingAgents.
Replaces yfinance_news for company and global news sourcing.
Uses:
- stock_news_em → individual stock news (East Money)
- news_cctv → CCTV daily news (macro/global)
- news_economic_baidu → economic calendar/events
"""
from datetime import datetime, timedelta
from typing import Optional
import os
import pandas as pd
from dateutil.relativedelta import relativedelta
# Suppress akshare's internal tqdm progress bars
os.environ.setdefault('TQDM_DISABLE', '1')
import akshare as ak
# ═══════════════════════════════════════════════════════════════
# Symbol detection
# ═══════════════════════════════════════════════════════════════
def _normalize_ticker(ticker: str) -> str:
"""Normalize ticker for akshare news API.
stock_news_em accepts: A-share 6-digit codes, or US tickers as-is.
"""
sym = ticker.strip().upper()
if sym.isdigit() and len(sym) == 6:
return sym
return sym
# ═══════════════════════════════════════════════════════════════
# Company news
# ═══════════════════════════════════════════════════════════════
def get_news(
ticker: str,
start_date: str,
end_date: str,
) -> str:
"""Get company-specific news from East Money.
Args:
ticker: Stock symbol (A-share code or US ticker).
start_date: YYYY-MM-DD format.
end_date: YYYY-MM-DD format.
Returns:
Markdown string containing news items.
"""
try:
sym = _normalize_ticker(ticker)
df = ak.stock_news_em(symbol=sym)
# Filter by date range
df["发布时间"] = pd.to_datetime(df["发布时间"])
mask = (df["发布时间"] >= pd.Timestamp(start_date)) & (
df["发布时间"] <= pd.Timestamp(end_date) + pd.Timedelta(days=1)
)
df = df[mask]
if df.empty:
return f"No news found for {ticker} between {start_date} and {end_date}."
# Select and format key columns
cols = [c for c in ["发布时间", "新闻标题", "新闻内容", "文章来源"] if c in df.columns]
result = df[cols].head(30) # Limit to 30 most recent
result["发布时间"] = result["发布时间"].astype(str)
return result.to_markdown(index=False)
except Exception as e:
return f"[akshare] Error fetching news for {ticker}: {e}"
# ═══════════════════════════════════════════════════════════════
# Global / macro news
# ═══════════════════════════════════════════════════════════════
def get_global_news(
curr_date: str,
look_back_days: int = 7,
limit: int = 20,
) -> str:
"""Get global macroeconomic news from multiple Chinese news sources.
Aggregates:
- CCTV daily news (political/economic headlines)
- Baidu economic calendar (economic events/data releases)
Args:
curr_date: Current reference date (YYYY-MM-DD).
look_back_days: Days to look back.
limit: Maximum total news items to return.
Returns:
Markdown string with global news and economic events.
"""
curr_dt = datetime.strptime(curr_date, "%Y-%m-%d")
sections = []
# — CCTV news —
try:
cctv_parts = []
for i in range(look_back_days + 1):
day = curr_dt - relativedelta(days=i)
day_str = day.strftime("%Y%m%d")
try:
df_day = ak.news_cctv(date=day_str)
if df_day is not None and not df_day.empty:
for _, row in df_day.head(10).iterrows():
if len(cctv_parts) >= limit:
break
title = str(row.get("title", "")) if "title" in df_day.columns else ""
content = str(row.get("content", "")) if "content" in df_day.columns else ""
brief = content[:120] if content else ""
cctv_parts.append(f"- [{day_str}] **{title}** — {brief}")
if len(cctv_parts) >= limit:
break
except Exception:
continue # skip days with no data
if cctv_parts:
sections.append("## CCTV News (央视新闻)\n\n" + "\n".join(cctv_parts[:limit]))
except Exception as e:
sections.append(f"## CCTV News\n\n*Error fetching CCTV news: {e}*")
# — Economic calendar —
try:
econ_parts = []
for i in range(look_back_days + 1):
day = curr_dt - relativedelta(days=i)
day_str = day.strftime("%Y%m%d")
try:
df_econ = ak.news_economic_baidu(date=day_str)
if df_econ is not None and not df_econ.empty:
for _, row in df_econ.head(10).iterrows():
if len(econ_parts) >= limit:
break
time_val = str(row.get("time", ""))
country = str(row.get("country", "")) if "country" in df_econ.columns else ""
event = str(row.get("event", "")) if "event" in df_econ.columns else ""
importance = str(row.get("importance", "")) if "importance" in df_econ.columns else ""
line = f"- **{country}** {importance} — {event} ({time_val})"
econ_parts.append(line)
if len(econ_parts) >= limit:
break
except Exception:
continue
if econ_parts:
sections.append("## Economic Calendar (经济日历)\n\n" + "\n".join(econ_parts[:limit]))
except Exception as e:
sections.append(f"## Economic Calendar\n\n*Error fetching economic calendar: {e}*")
if not sections:
return f"No global news found for the period ending {curr_date}."
header = f"**Global news and economic events from {curr_dt - relativedelta(days=look_back_days):%Y-%m-%d} to {curr_date}**\n\n"
return header + "\n\n".join(sections)