Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.docker.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,10 @@ MONITOR_DB_PATH=/app/data/monitor.db
# 如需东方财富 suggest 补全,请在真实 .env 中配置,不要提交真实 token
EASTMONEY_TOKEN=

# ====== 美股数据源 ======
# 美股行情主源为 yfinance(无需 key);Finnhub 作为备选源及美股新闻源。
# 未配置时仅用 yfinance,美股个股新闻会跳过。https://finnhub.io 免费注册获取 key
FINNHUB_API_KEY=

# ====== 可选推送配置 ======
SERVERCHAN_KEY=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ logs/
# Local runtime data
data/*.db
data/agent_memory/
data/strategy_analysis_cache.json
data/user_settings.json

# Temporary files
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ OpenAshare 试图把这些碎片收进一个可控的工作台:
- 研究进度事件流,适合较长的分析任务
- 盘前、盘中、午间、盘后不同研究节奏提示

### 多市场支持

- **A股**:上交所 / 深交所 / 创业板 / 科创板(数据源 Ashare + akshare)
- **港股**:如 `00700.HK`(数据源腾讯)
- **美股**:如 `AAPL`、`NVDA`、`BRK.B`(主源 yfinance,备选 Finnhub)
- 三市场统一搜索:在同一界面里搜 `茅台`、`腾讯`、`苹果` / `AAPL` 均可
- 美股技术分析、指标、信号与 A股 完全对齐;个股新闻走 Finnhub(需配置 `FINNHUB_API_KEY`),A股 特有的资金流/龙虎榜对美股自动跳过

### 新闻与热点

- 个股新闻与全局市场消息浏览
Expand Down
46 changes: 45 additions & 1 deletion api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,44 @@ def _warm_read_caches() -> None:
hotspot_service.list_hotspots(limit=10)
except Exception:
logger.exception("Warmup failed for hotspots")
_warm_stock_pool()


def _warm_stock_pool() -> None:
"""后台预热股池中部分个股的行情分析(不含 AI),让用户首次点击命中缓存。

通过 WARMUP_STOCK_LIMIT 控制预热数量(默认 12,设为 0 可关闭)。
"""
try:
limit = int(os.getenv("WARMUP_STOCK_LIMIT", "12"))
except ValueError:
limit = 12
if limit <= 0:
return

try:
from ashare.stock_pool import load_stock_pool

pool = load_stock_pool()
except Exception:
logger.exception("Warmup failed to load stock pool")
return

# 多个别名可能指向同一代码,按出现顺序去重后截断。
seen: set[str] = set()
codes: list[str] = []
for code in pool.values():
if code and code not in seen:
seen.add(code)
codes.append(code)
if len(codes) >= limit:
break

for code in codes:
try:
stock_service.get_stock_analysis(code, include_ai=False)
except Exception:
logger.warning("Warmup failed for stock %s", code, exc_info=True)


@asynccontextmanager
Expand Down Expand Up @@ -847,7 +885,13 @@ def list_hotspots(request: Request, limit: int = Query(10, ge=1, le=20)) -> Resp
def list_global_news(request: Request, limit: int = Query(20, ge=1, le=50)) -> Response:
try:
payload = news_service.get_global_news(limit=limit)
return _cached_json_response(request, payload, max_age=60, stale_while_revalidate=180)
return _cached_json_response(
request,
payload,
max_age=60,
stale_while_revalidate=180,
no_store=not bool(payload),
)
except Exception as exc:
logger.exception("get_global_news failed")
return JSONResponse(content=[], headers={"Cache-Control": "no-store"})
Expand Down
12 changes: 12 additions & 0 deletions api/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ class HotspotRelatedStock(BaseModel):
reason: str


class HotspotHeatBreakdown(BaseModel):
trading_activity: float = 0
discussion_heat: float = 0
news_heat: float = 0
alert_count: int = 0
rank_hits: int = 0
attention_level: Literal["focus", "caution", "watch"] = "watch"
basis: List[str] = Field(default_factory=list)


class HotspotItem(BaseModel):
topic_name: str
heat_score: float
Expand All @@ -130,6 +140,7 @@ class HotspotItem(BaseModel):
trend_direction: Literal["up", "down", "flat"] = "flat"
ai_summary: Optional[str] = None
source: str = "derived"
heat_breakdown: HotspotHeatBreakdown = Field(default_factory=HotspotHeatBreakdown)


class HotspotHistoryPoint(BaseModel):
Expand Down Expand Up @@ -270,6 +281,7 @@ class StrategyHoldingAnalysis(BaseModel):
action_reason: str = ""
trigger_hits: List[str] = Field(default_factory=list)
alerts: List[str] = Field(default_factory=list)
analysis_status: Literal["live", "cached", "degraded", "local"] = "live"


class StrategyTodoItem(BaseModel):
Expand Down
Loading