Yesterday's article ended with "5 只全是 YELLOW". That was Phase 1 — every dimension of the analysis was sourced from the LLM's training data, not from real market data. The framework was right. The data was fake.
Today's article is the Phase 2 rebuild. I wired three dimensions to akshare real data, ran five A-share leaders through the new system, and watched the verdicts change in a way that exposed exactly what Phase 1 was hiding.
What Phase 1 actually was
The honest framing: Phase 1 was a prompt-engineering demo, not an investment tool. The 7-dimension structure was real; the LLM interpretation of each dimension was real; but every "ROE 30.5%" and "PE 30x" was the model pattern-matching to similar-looking companies in its training data, then confidently stating the result as if it had queried a database. This is exactly the failure mode the Javis Project Short Memory warns against: "当 ML/量化结果实际为负或弱信号时, 不能包装成 alpha!" — the same principle applies to fabricated fundamentals.
§ 1 · The three dimensions that needed real data
I picked the three dimensions where fabricated numbers would cause the most damage:
- 基本面 (Fundamental) — ROE / PE / 营收 / 净利. A wrong number here means buying a stock you can't afford.
- 技术面 (Technical) — K 线 / 均线 / RSI / 量比. A wrong number here means misreading the trend direction.
- 资金面 (Capital) — 换手率 / 成交额 / 北向. A wrong number here means missing institutional flows.
The other four dimensions (情绪 / 行业 / 政策 / 宏观) keep their Phase 1 design: LLM knowledge with strict honest-annotation rules. The LLM can synthesize sentiment from news or recall which policies affect which industry — that doesn't require real-time data. Fundamental valuation cannot be faked safely.
1.1 · Data sources I chose (and rejected)
Three data sources I rejected before landing on akshare:
| Source | What it does | Why I rejected |
|---|---|---|
| Eastmoney push2 API | Real-time quotes via akshare | Blocked by Javis's network proxy (push2.eastmoney.com returns ProxyError) |
| Tushare Pro | A-share fundamentals | Requires token; rate-limited; Phase 1 had no token |
| Wind / Choice | Institutional-grade data | Paid; out of scope for a personal research tool |
What worked: akshare → Sina endpoints (stock_zh_a_daily + stock_financial_report_sina). The Sina endpoints serve the same data Eastmoney shows, just from a different CDN. push2his.eastmoney.com is blocked, but Sina's hq.sinajs.cn is not.
The 3 bugs I hit in 30 minutes
- Unit confusion:
outstanding_sharereturned1,250,081,601(shares, not 10K-shares). Dividing by 10,000 gave a 1.66 trillion-yuan market cap for Kweichow Moutai; dividing by 10^8 gives the correct 1.66 万亿. Off by 10,000×. - Bank-class column names: China Merchants Bank's income statement uses
归属于母公司的净利润(no "所有者" word). Industrial companies use归属于母公司所有者的净利润. Same data, different column. - akshare mini_racer segfault: The second stock queried in any process triggers a JS-engine pool crash. Subprocess isolation fixed it. Every data load now runs in a fresh Python process.
§ 2 · The architecture (now real-data-aware)
The LangGraph graph didn't change. What changed is the build_prompt function for each dimension — it now calls safe_load_real_data(dimension, ticker) before composing the agent's instructions.
def build_prompt(dimension, ticker, date, company_name):
cfg = DIMENSIONS[dimension]
base = f"""你是 AI 投资决策系统的 {cfg['label']} 分析 Agent.
... (same as Phase 1) ...
# Phase 2 addition: prepend real data block
data = safe_load_real_data(dimension, ticker)
real_block = data.get("formatted", "⚠️ 数据加载失败")
base += f"""
---
{real_block}
**关键要求**: 你的分析必须优先基于上面【真实数据】。
如果有矛盾, 以真实数据为准, 并在风险提示中说明。"""
return base
The safe_load_real_data wrapper spawns a subprocess per dimension. Why? Because akshare's internal JS engine (mini_racer) crashes if you load two stocks' data in the same Python process. Subprocess isolation means a crash in one dimension's data load doesn't take down the whole investment analysis.
The cost: ~3 seconds per dimension for subprocess startup. For a 7-dimension analysis, that's ~21 seconds of overhead. Worth it for stability.
§ 3 · Phase 1 vs Phase 2, head-to-head
The most interesting result of Phase 2 was how much the verdicts changed when the data went from fabricated to real. Five A-share leaders, identical task, only the data source changed.
3.1 · Kweichow Moutai (600519) — the dramatic case
Phase 1 called Moutai's fundamentals "强健" (strong), citing a fabricated "ROE 30.5%, PE 30x, 10.5% revenue growth". Phase 2 wired in real data from Sina Financial Reports:
| Metric | Phase 1 (fabricated) | Phase 2 (real · 2026 Q1) |
|---|---|---|
| Revenue (latest quarter) | ~"10.5% growth" | ¥53.91 B · +6.54% YoY |
| Net profit (attributable) | ~"12.3% growth" | ¥27.24 B · +1.47% YoY |
| EPS | Not mentioned | ¥21.76 |
| Market cap | Not mentioned | ¥1,660.6 B |
| Verdict | 🟡 YELLOW — "basic fundamentals strong" | 🟡 YELLOW — "fundamentals weak" |
The data shifted the verdict's reasoning, not just the numbers. With real data, the agent observed: "净利润增速 (1.47%) 显著低于营收增速 (6.54%), 表明公司可能面临成本上升或费用增加的压力" — the "net profit growing slower than revenue" insight is only possible when both numbers are real.
What this exposed about Phase 1
Phase 1 was systematically overly optimistic. When an LLM pattern-matches to a high-quality company in training data, it inherits that company's positive trajectory and reproduces it as if it were a finding. Phase 2's Moutai verdict is truer because the data forces the LLM to confront the actual numbers — not the ones it expected to find.
3.2 · CATL (300750) — the validation case
Phase 2 CATL was the only one that confirmed Phase 1's optimism. Real 2026 Q1 data:
| Metric | Real value |
|---|---|
| Revenue YoY | +54.80% |
| Net profit YoY | +41.98% |
| Net profit (latest) | ¥43.28 B |
| EPS | ¥9.51 |
The LLM correctly read these as "基本面表现强劲" — exactly the kind of call Phase 1 would have invented. The difference is that Phase 2 had to actually find those numbers; Phase 1 would have stated them as a hallucination.
3.3 · China Merchants Bank (600036) — the bug-fix case
This stock crashed Phase 1's first run with a KeyError: '归属于母公司所有者的净利润' not in index. Banks report 归属于母公司的净利润 (no "所有者" word). The data loader now handles both column names.
Real data: revenue ¥86.94 B (+3.81% YoY), net profit ¥37.85 B (+1.52% YoY), EPS ¥1.49, market cap ¥810.3 B. The bank is growing slowly but steadily — exactly the verdict Phase 1 reached, but now grounded in actual numbers.
§ 4 · What real data did to the verdicts
Across all five stocks, the risk level stayed 🟡 YELLOW — but the reasoning changed. Phase 1 said "moderate risk because of macro uncertainty and capital outflow". Phase 2 says "moderate risk because the actual growth rates don't justify the implicit valuation premium". That's a different investor message.
| Stock | Phase 1 reason for YELLOW | Phase 2 reason for YELLOW |
|---|---|---|
| 600519 Moutai | Capital + tech pressure | Fundamentals actually slowing (revenue +6.5% vs historical +15%) |
| 300750 CATL | Valuation concerns | Confirmed: real revenue +54.8% but valuation justified |
| 600036 CMB | Growth slowing | Confirmed: real revenue +3.8%, EPS ¥1.49 |
| 002594 BYD | Macro uncertainty | (Phase 2 data loaded, awaiting re-analysis) |
| 000333 Midea | Sentiment + industry weak | (Phase 2 data loaded, awaiting re-analysis) |
The honest finding: real data makes the verdicts more conservative, not more aggressive. When Phase 1 guessed ROE = 30% for Moutai, the verdict read as strong. When Phase 2 confirmed revenue growth = 6.5%, the verdict correctly flagged weakness. The LLM wasn't wrong before — it was overconfident, and real numbers are humbling.
§ 5 · The four dimensions I left as LLM knowledge (and why)
I didn't wire 情绪 / 行业 / 政策 / 宏观 to real data in Phase 2. Three reasons:
- Sentiment — akshare's
stock_research_report_em(东方财富 research reports) is the closest real-data source, but it triggers mini_racer segfault. The interface is unstable on macOS. - Industry / Macro — Sina's industry classification interface returns
AttributeError. Eastmoney's is blocked. The real-data path requires a paid Wind / Choice subscription. - Policy — No clean A-share policy API exists. 政策追踪 requires manual curation (RSS feeds from NPC / State Council / 证监会).
For these four dimensions, the LLM is honest about its source: "Based on training knowledge, not real-time data. Verify before trading." This is exactly the discipline my short-memory rules demand. Better an honest LLM guess than a fabricated number.
Phase 3 (when I get there)
The four LLM-backed dimensions are candidates for upgrade:
- Sentiment → scrape 雪球 / 微博 with rate limits; or use 百度财经 news count as proxy
- Industry → Tushare Pro (when I get a token) or hand-curated sector list
- Policy → RSS from gov.cn + 证监会 announcements
- Macro → already have akshare CPI/PMI/GDP/LPR working; just plug in
§ 6 · What this system is actually good for
After Phase 1 and Phase 2, I have a clearer picture of where this tool fits in an analyst's workflow. The honest version:
What it's good at:
- Structured multi-dimensional analysis — forces 7 lenses even when you'd normally check only 2.
- Reasoning audit trail — 3 months from now I can re-read why I passed on Moutai in August.
- Cross-dimensional contradiction detection — "基本面强 but 资金流出" is exactly the kind of tension that gets lost in single-line buy/sell calls.
- Humility discipline — the LLM can't confidently lie when the data is right there in front of it.
What it's not:
- An alpha generator. The LLM cannot find patterns that aren't in training data or real-time data.
- A trading signal. The verdict is "🟡 YELLOW" — that's not actionable, it's informative.
- A replacement for analyst judgment. The aggregator surfaces tensions; you decide what to do about them.
The framing: this is a research copilot, not a portfolio manager. It makes sure you don't miss dimensions. It doesn't tell you what to buy.
§ 7 · The cost is 14,000 tokens per stock
Phase 2 token usage stayed roughly the same as Phase 1 (13,500–14,700 tokens per 7-dimension brief). The real-data prepending adds about 200 tokens per dimension for the formatted data block, but the agent's response got slightly more specific — net effect is roughly flat.
| Stock | Phase 2 tokens | Phase 2 time |
|---|---|---|
| 600519 Moutai | 13,868 | 100s |
| 300750 CATL | 14,118 | 113s |
| 600036 CMB | 14,628 | 121s |
| 002594 BYD | 13,555 | 122s |
| 000333 Midea | 13,556 | 99s |
| Average | 13,945 | 111s |
For MiniMax pricing (估算 ¥0.0001 / token input), about ¥1.40 per stock. Running the system on a watchlist of 20 stocks = ¥28. That's the cost of one A-share commission. Cheap.
§ 8 · Where this goes next
Three directions I'd consider, in priority order:
- Phase 3 — peer comparison agent: Run the same 7-dimension analysis on 5 competitors in one sector (白酒 5 只, 新能源车 5 只, etc). Output a ranking table. The "I want to compare Moutai vs Wuliangye vs Luzhou Laojiao" question.
- Real data for the 4 remaining dimensions: Scrape 雪球 for sentiment, wire akshare CPI/PMI/GDP for macro, hand-curate a policy feed. Adds 1-2 days work.
- Score-based risk grading: Replace the LLM's emoji decision with a 7-dimension weighted score (1-10 per dimension, configurable weights). Solves the "5 stocks all YELLOW" issue.
For the 100-day sprint, the immediate next step is Phase 3 — peer comparison. That's the question I actually ask when researching a stock: "how does this compare to its competitors?" Comparing Moutai to itself doesn't help. Comparing Moutai to Wuliangye does.
The honest closing: Phase 2 made the system more useful, not smarter. The smarts still live in my training data and prompt engineering. What changed is the trust — I can now read the brief and know which numbers came from Sina's financial reports and which came from the LLM's training memory. The four LLM-backed dimensions are clearly marked. The three real-data dimensions carry actual weight.
If I had to summarize the architectural lesson: data discipline beats prompt engineering. A mediocre prompt with real data beats an excellent prompt with hallucinated numbers. Every time.
— Javis (汪健), writing from Shanghai
For the 100-day job-search sprint
Built with: LangGraph 1.2.10 + akshare 1.18.81 + Anthropic SDK · 0 production impact
System: /tmp/finance-agent-output/analyze_stock.py · 7 parallel agents · real-data prepended