skills/finnhub-client/SKILL.md
Shared Finnhub API client used by other skills. Provides rate-limited (60/min), cached, retry-aware access to 17 Finnhub endpoints covering quotes, OHLCV, fundamentals, earnings calendar, earnings surprises, insider transactions, recommendation history, price targets, upgrades/downgrades, dividends, splits, IPOs, and SEC filings. Also exports adapters that normalize Finnhub raw responses into FMP-compatible shapes so that downstream code can swap providers without changing call sites. Use when another skill needs Finnhub data or when building a unified provider layer.
npx skillsauth add kavi-lin/stock finnhub-clientInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Shared infrastructure module — not invoked directly by users. Imported by other skills.
Provide a single, consistent client for Finnhub free-tier endpoints with:
skills/fred-macro and skills/ftd-detector/scripts/fmp_client.py patterns).| Method | Finnhub Endpoint | Cache TTL | FMP equivalent |
|---|---|---|---|
| quote(ticker) | /quote | 5 min | /api/v3/quote |
| candle(ticker, days) | /stock/candle | 6 h | /historical-price-full |
| profile(ticker) | /stock/profile2 | 7 d | /profile |
| metric(ticker) | /stock/metric?metric=all | 1 d | /key-metrics |
| financials_reported(ticker) | /stock/financials-reported | 1 d | /income-statement |
| filings(ticker) | /stock/filings | 6 h | /sec-filings |
| company_news(ticker, days) | /company-news | 1 h | — |
| earnings_calendar(start, end) | /calendar/earnings | 6 h | — (FMP-only via econ calendar, not earnings) |
| earnings_surprise(ticker) | /stock/earnings | 1 d | — |
| insider_transactions(ticker) | /stock/insider-transactions | 1 d | — |
| insider_sentiment(ticker) | /stock/insider-sentiment | 1 d | — |
| recommendation(ticker) | /stock/recommendation | 1 d | — |
| price_target(ticker) | /stock/price-target | 1 d | — |
| upgrade_downgrade(ticker) | /stock/upgrade-downgrade | 6 h | — |
| dividends(ticker, start, end) | /stock/dividend2 | 1 d | — |
| splits(ticker, start, end) | /stock/splits | 7 d | — |
| ipo_calendar(start, end) | /calendar/ipo | 6 h | — |
Set FINNHUB_API_KEY in your environment. Free tier: 60 calls/minute, no documented daily cap.
export FINNHUB_API_KEY=...
from skills.finnhub_client.scripts.finnhub_client import FinnhubClient
client = FinnhubClient() # reads FINNHUB_API_KEY from env
q = client.quote("AAPL")
# → {"c": 178.32, "h": 179.01, "l": 177.50, "o": 178.00, "pc": 177.95, "t": 1714060800}
cal = client.earnings_calendar("2026-04-25", "2026-05-02")
# → {"earningsCalendar": [{"symbol": "MSFT", "date": "2026-04-29", "epsEstimate": 2.83, ...}]}
When a downstream skill expects FMP-shaped data, use the adapters to translate:
from skills.finnhub_client.scripts import adapters
raw = client.profile("AAPL")
fmp_like = adapters.profile_to_fmp(raw)
# → {"symbol": "AAPL", "companyName": "Apple Inc.", "sector": "Technology", ...}
Available adapters: quote_to_fmp, candle_to_fmp_historical, profile_to_fmp, metric_to_fmp_key_metrics.
To validate Finnhub data quality against FMP before flipping the primary provider, use:
bash skills/finnhub-client/scripts/run_diff.sh
# → outputs skills/finnhub-client/diff_reports/YYYYMMDD.md
The diff tool fetches the same fields from both providers across 10 default tickers, computes per-field percentage delta, and grades each (PASS <2%, WARN 2-5%, FAIL >5%). Use for one-off ad-hoc spot checks. For routine production ingest, use dual_fetch.py instead — see next section. Detailed usage in README.md.
dual_fetch.py is the production data-ingest entrypoint. It fetches canonical fields from both Finnhub and FMP and writes a single JSON per ticker with strict physical separation:
{
"scoring": { "_source": "finnhub", "price": ..., "peRatio": ..., ... },
"_audit": { "fmp": {...}, "diff": {...}, "fmp_status": "ok" }
}
The hard rule — _audit.* MUST NOT reach the LLM prompt.
Downstream code (LLM context construction, prompt assembly, scoring functions) MAY ONLY read scoring.*. The _audit underscore prefix is a python-style "private" flag enforced by code review and grep, not by runtime guards.
The diff tool revealed that several canonical fields have structural provider divergence — same stock, same moment, different number, both correct under their own definition:
| Field | Divergence | Cause |
|---|---|---|
| dividendYield | 5-7% | Finnhub: indicated annual (forward); FMP: TTM trailing (backward) |
| priceToBookRatio | 12-38% | Different book-value snapshot (MRQ vs TTM avg) and goodwill treatment |
If the LLM sees both values, it invents its own weighting and scoring becomes non-reproducible — the same stock can score differently across runs depending on which value the model latched onto. By contrast, fixing Finnhub as the only scoring source guarantees that the same stock at the same moment always produces the same scoring inputs, while the parallel FMP fetch retains observability for drift detection.
audit_drift_check.py scans _audit.diff from past N days and flags (ticker, field) pairs whose absolute divergence exceeded a threshold on >= MIN_HITS days. Run weekly. See README.md for cadence and interpretation.
python3 skills/finnhub-client/scripts/audit_drift_check.py --days 7 --threshold 5
fmp_status in _audit is one of: ok, quota_exceeded, unauthorized, http_<code>, network_error. Scoring is never affected by audit-side failures — when FMP is unreachable, the day loses drift detection but analysis continues unchanged.
Retry-After seconds (or 60s default), retries up to 3 times. After 3 failures, raises FinnhubRateLimit.FinnhubPremiumRequired immediately — caller should fall back to FMP or skip the feature.FinnhubError.None (matches FMP client convention).skills/finnhub-client/cache/
├── quote_AAPL.json # mtime-based TTL
├── candle_AAPL_365.json
├── profile_AAPL.json
├── earnings_calendar_2026-04-25_2026-05-02.json
└── ...
Cache dir is created on demand and gitignored (matches project-wide skills/*/cache/ rule).
/calendar/economic is premium-only — keep using FMP for economic events./stock/social-sentiment, /news-sentiment, /stock/institutional-ownership are premium-only — not exposed by this client.financials_reported returns raw SEC XBRL filings. Concept tagging, units, and GAAP/non-GAAP classification are inconsistent across companies. Do not use as a financial-statement primary — FMP /income-statement, /balance-sheet, /cash-flow-statement remain the canonical source. The included adapters.financials_to_fmp_income() is intentionally lossy (top-line only) and is for raw-filing reference, not for quality models.This client serves the market & events layer. Allocation:
| Layer | Provider | Why |
|---|---|---|
| Market data (quote / OHLCV / profile) | Finnhub primary | Low shape ambiguity, identical units |
| Events (earnings calendar / surprise / insider / upgrades) | Finnhub only | FMP doesn't cover these |
| Simple metrics (P/E, ROE, div yield, P/B) | Finnhub canonical, FMP audit (dual-fetch) | Resolved by dual_fetch.py: Finnhub feeds scoring; FMP fetched in parallel for drift monitoring only. See § Dual-Fetch Discipline. |
| Financial statements (income / balance / cash flow) | FMP primary | Normalized shape, consistent CFO/NI/share count — Finnhub raw XBRL would drift quality models |
| Economic calendar | FMP only | Finnhub free tier doesn't expose |
| Analyst estimates (forward EPS numerical) | FMP only | Finnhub gives buy/hold/sell counts, not numerical forecasts |
This is a 7-PR migration:
diff_tool.py): Side-by-side validation harness ✓data-client abstraction with per-resource provider routing + source tagging + conflict detectionftd-detector (lowest-risk pilot)market-top-detector, us-stock-analysisdevelopment
# earnings-analyst — 個股財報深度分析 > **Trigger**: `財報 [TICKER]` > **Version**: V1.0 > **Data Source**: FMP HTTP REST(`$FMP_API_KEY`) ## 目的 針對單一個股產出**深度財報分析報告**(逐季趨勢、品質指標、估值、分析師共識),涵蓋 sector V1.4 與 `分析 [TICKER]` 既有 protocol **沒有**的「財報層級」深潛內容。 ## 與既有 skill 的差異 | Skill | 重點 | 觸發 | |---|---|---| | `us-stock-analysis` | 估值/技術/情緒 snapshot(yfinance + FMP partial) | Phase 2 fundamentals lane | | `earnings-valuation-forecaster` | 12M 目標價 3×3 敏感度 | ad-hoc / earnings 前 14 天 | | `earnings-trade-analyzer` |
testing
Daily Top N hot themes × Top M short-term movers per theme. Combines theme-detector heat scoring (medium-term) with short-term-target predictions (1d/5d/15d) into a "Tactical Opportunity Radar" recommendation log. Tags concentration WARNING when ≥2 picks share theme. Records FRED + market regime snapshot at recommendation time for future backtest cross-tabs. Standalone — not auto-wired into investment_protocol. Use for daily watchlist refresh / Dashboard推薦面板feed / batch screening across hot themes.
testing
Short-term (1d / 5d / 15d) directional projection for a US stock — "Tactical Opportunity Radar". Outputs target range + confidence breakdown + benchmark-relative alpha + trading meta (stop / position size hint / exit trigger). Each horizon uses independent weights from config/weights.yaml. Refuses to project when source data is stale (returns insufficient_data with reasons). Hard-clamped to prevent cold-start absurd predictions. Use when caller wants short-term directional bias on a specific ticker, NOT for long-term valuation (use earnings-valuation-forecaster for 12-month). Standalone — not auto-wired into investment_protocol.
development
Analyze recent post-earnings stocks using a 5-factor scoring system (Gap Size, Pre-Earnings Trend, Volume Trend, MA200 Position, MA50 Position). Scores each stock 0-100 and assigns A/B/C/D grades. Use when user asks about earnings trade analysis, post-earnings momentum screening, earnings gap scoring, or finding best recent earnings reactions.