在单台 VPS 上搭建日本股票分析服务:EDINET DB + Yahoo Finance + LLM
需求
手头有一台 VPS,已经跑了视频生成、语音转写等服务。现在想加一个股票分析功能:
- 输入日本股票代码(如 4167)
- 自动获取公司财务数据、股价
- 输出结构化的分析报告(基本面、估值、风险、关注动态)
数据源选型
日本股票分析需要两类数据:财务数据和股价数据。
财务数据:EDINET DB
EDINET 是日本金融厅的电子披露系统,所有上市公司的有价证券报告书、决算短信都在这里。EDINET DB 是它的第三方 API 封装,提供 REST 接口。
| 端点 | 用途 |
|---|---|
/v1/search?q={code} | 用证券代码搜索,获取 EDINET 代码 |
/v1/companies/{edinet_code}/earnings | 获取决算短信历史(实绩 + 通期预想) |
/v1/companies/{edinet_code}/financials | 获取 FY2011-2025 的长期财务数据 |
免费版限制:100 请求/天,超限返回 429。对于分析和测试来说够用。
股价数据:Yahoo Finance
日本股票的股价可以用 yfinance 库获取,代码格式是 4167.T(加 .T 后缀)。
能拿到的字段包括:当前价、涨跌幅、市值、PER、PBR、股息率、52周高低。
注意:Yahoo Finance 的数据仅供个人使用,如果要做商用服务,需要换回 JPX 官方的 J-Quants API。
关键陷阱:代码格式不同
EDINET DB 和 Yahoo Finance 使用不同的公司标识符:
| 数据源 | 格式 | 例子 |
|---|---|---|
| EDINET DB(search 输入) | 4 位证券代码 | 4167 |
| EDINET DB(返回) | E + 5 位 | E36086 |
| Yahoo Finance | 4 位 + .T | 4167.T |
脚本里先用 4167 搜索,拿到 E36086,再用它调 /earnings。股价则用 4167.T。
完整脚本
核心逻辑分四步:
- 用证券代码搜索 EDINET 代码和公司信息
- 用 EDINET 代码获取最新决算短信
- 用 Yahoo Finance 获取股价
- 把数据组装成 prompt,交给 LLM 分析
python
import json
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
EDINET_KEY = "edb_你的Key"
LLM_URL = "http://172.17.0.1:3000/v1/chat/completions"
LLM_TOKEN = "sk-你的OneAPI令牌"
LLM_MODEL = "nvidia/nemotron-3-ultra-550b-a55b"
EDINET_BASE = "https://edinetdb.jp/v1"
def search_company(sec_code):
r = requests.get(
f"{EDINET_BASE}/search",
params={"q": sec_code},
headers={"X-API-Key": EDINET_KEY},
timeout=30,
)
r.raise_for_status()
data = r.json().get("data", [])
return data[0] if data else None
def get_earnings(edinet_code):
r = requests.get(
f"{EDINET_BASE}/companies/{edinet_code}/earnings",
headers={"X-API-Key": EDINET_KEY},
timeout=30,
)
r.raise_for_status()
return r.json().get("data", {}).get("earnings", [])
def get_stock_price(sec_code):
import yfinance as yf
ticker = yf.Ticker(f"{sec_code}.T")
info = ticker.info
return {
"price": info.get("regularMarketPrice"),
"market_cap": info.get("marketCap"),
"trailing_pe": info.get("trailingPE"),
"price_to_book": info.get("priceToBook"),
}
@app.route("/analyze", methods=["POST"])
def analyze():
data = request.get_json() or {}
sec_code = str(data.get("code", "")).strip()
# 1. 搜索公司
company = search_company(sec_code)
# 2. 决算短信
earnings = get_earnings(company["edinet_code"])
latest = earnings[0]
# 3. 股价
price = get_stock_price(sec_code)
# 4. 计算进度率
forecast_oi = latest.get("forecast_ordinary_income", 0)
actual_oi = latest.get("ordinary_income", 0)
progress = (actual_oi / forecast_oi * 100) if forecast_oi else 0
# 5. LLM 分析
prompt = f"""你是一位专业的日本股票分析师。
请基于以下数据,对 {company['name_ja']} 进行分析。
【最新季度实绩({latest['fiscal_year_end']} Q{latest['quarter']})】
- 売上高:{latest['revenue']} 百万円
- 営業利益:{latest.get('operating_income', 'N/A')} 百万円
- 経常利益:{latest.get('ordinary_income', 'N/A')} 百万円
【通期予想】
- 売上高:{latest.get('forecast_revenue', 'N/A')} 百万円
- 経常利益:{latest.get('forecast_ordinary_income', 'N/A')} 百万円
【進捗率】
- 経常利益進捗率:{progress:.1f}%
【財務状態】
- 自己資本比率:{latest.get('equity_ratio', 'N/A')}%
【最新股价】
{json.dumps(price, ensure_ascii=False, indent=2)}
请从以下四个维度输出中文分析报告:
1. 公司基本面分析
2. 通期予想达成可能性
3. 主要风险
4. 关注动态"""
body = {
"model": LLM_MODEL,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 4096,
"temperature": 0.3,
"stream": False,
}
r = requests.post(
LLM_URL,
headers={"Authorization": f"Bearer {LLM_TOKEN}"},
json=body,
timeout=600,
)
analysis = r.json()["choices"][0]["message"]["content"]
return jsonify({
"code": sec_code,
"company": company["name_ja"],
"latest_quarter": f"{latest['fiscal_year_end']} Q{latest['quarter']}",
"revenue": latest.get("revenue"),
"ordinary_income": actual_oi,
"forecast_ordinary_income": forecast_oi,
"progress_percent": round(progress, 2),
"stock_price": price.get("price"),
"analysis": analysis,
})
运行
bash
python stock_service.py
服务监听在 5002 端口。
测试
bash
curl -X POST http://localhost:5002/analyze \
-H "Content-Type: application/json" \
-d '{"code": "4167"}'
返回完整的 JSON 报告,耗时 2-3 分钟(主要是 LLM 推理)。
实测:4167 的分析输出
输入 4167(株式会社ココペリ),LLM 输出的报告要点:
基本面:Q1 营收 4.82 亿日元(+9.3%),但营业利润率只有 1.87%,经常利润率 1.45%——处于”微利/投资期”,规模效应还没显现。
通期预想达成难度:全年经常利润目标 8,200 万,Q1 只完成 700 万,进度率 8.5%,远低于均匀分布的 25%。剩余三季度需要单季平均 2,500 万,是 Q1 的 3.57 倍。
核心风险:
- 盈利兑现不确定性(5 星)
- 微盘股流动性陷阱(4 星)
- 行业竞争与模式风险(3 星)
下一节点:2026 年 11 月的 Q2 财报,关键验证指标是上半期经常利润进度率是否 >35%。
缓存优化
LLM 分析需要 2-3 分钟,而且每次调用消耗 2 次 EDINET 配额(search + earnings)。免费版每天只有 100 次请求,重复分析同一只股票很浪费。
缓存设计
按日期缓存,同一天内重复请求直接返回已生成的结果:
python
from datetime import datetime
from pathlib import Path
CACHE_DIR = Path("/opt/rag-nvidia/cache")
CACHE_DIR.mkdir(exist_ok=True)
def get_cache_path(sec_code):
today = datetime.now().strftime("%Y-%m-%d")
return CACHE_DIR / f"{sec_code}_{today}.json"
def load_cache(sec_code):
path = get_cache_path(sec_code)
if path.exists():
return json.loads(path.read_text(encoding="utf-8"))
return None
def save_cache(sec_code, data):
path = get_cache_path(sec_code)
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
在 /analyze 端点开头加缓存检查:
python
if not force_refresh:
cached = load_cache(sec_code)
if cached:
cached["cached"] = True
return jsonify(cached)
缓存效果
| 调用 | cached | 耗时 |
|---|---|---|
| 第一次 | False | 2-3 分钟 |
| 第二次 | True | 0.041 秒 |
第二次直接返回缓存,不消耗 EDINET 配额,也不调用 LLM。
强制刷新
如果需要重新分析(比如股价变动),传 force_refresh:
bash
curl -X POST http://localhost:5002/analyze \
-H "Content-Type: application/json" \
-d '{"code": "4167", "force_refresh": true}'
踩坑记录
1. EDINET DB 和 Yahoo Finance 的代码格式不同
EDINET 用 E36086,Yahoo 用 4167.T。脚本里需要做转换。
2. LLM 输出是日文
即使 prompt 要求”中文输出”,nemotron-3-ultra 有时会跟随数据的语言输出日文。需要在 prompt 末尾强调”请用中文输出”。
3. 推理模型响应慢nemotron-3-ultra 是推理模型,会先生成”思考过程”,再输出正式回答。2-3 分钟的等待是正常的。如果想加速,可以换非推理模型。
4. EDINET 免费版配额有限
100 请求/天,每次分析消耗 2 次(search + earnings)。每天约能分析 50 只股票。缓存能大幅减少配额消耗。
5. 部分数据缺失
EDINET 的 1Q 记录里可能没有 net_income 字段(公司只披露 EPS)。LLM 会正确标注”数据缺失”,不会编造。
当前状态
| 组件 | 状态 |
|---|---|
| EDINET DB 搜索 | ✅ |
| EDINET DB 决算短信 | ✅ |
| Yahoo Finance 股价 | ✅ |
| 进度率自动计算 | ✅ |
| LLM 分析 | ✅ |
| 报告缓存 | ✅ 按日期缓存 |
| Flask 服务 | ✅ 运行在 5002 |
下一步
- 接入 n8n:定时批量分析多只关注的股票,结果发 Telegram
- 加历史趋势:调用 EDINET DB 的
/financials端点,获取 FY2011-2025 的长期数据 - 存入 WordPress:把分析报告自动发布到站点
- 多股票对比:同时分析多只股票,生成行业对比报告
追记:
如何调查其他公司的业绩
服务搭好后,调查任意日本上市公司的业绩只需要把股票代码换掉。
最简单的方式:curl 调用
bash
curl -s -X POST http://localhost:5002/analyze \
-H "Content-Type: application/json" \
-d '{"code": "7203"}' | python3 -c "
import sys, json
d = json.load(sys.stdin)
if 'error' in d:
print('错误:', d['error'])
else:
print(f\"{d['company']} ({d['code']})\")
print(f\"最新季度: {d['latest_quarter']}\")
print(f\"股价: {d['stock_price']} 円\")
print(f\"营收: {d['revenue']} 百万円\")
print(f\"经常利润: {d['ordinary_income']} 百万円\")
print(f\"通期预想: {d['forecast_ordinary_income']} 百万円\")
print(f\"进度率: {d['progress_percent']}%\")
print(f\"缓存: {d.get('cached', False)}\")
print()
print(d['analysis'])
"
把 7203 换成目标公司的 4 位证券代码。首次调用约 2-3 分钟(LLM 分析),同一天内再次查同一只股票秒回(走缓存)。
常见日本公司代码
| 代码 | 公司 |
|---|---|
7203 | トヨタ自動車(丰田) |
6758 | ソニーグループ(索尼) |
9984 | ソフトバンクグループ |
6861 | キーエンス |
8306 | 三菱UFJフィナンシャル・グループ |
9432 | 日本電信電話(NTT) |
4063 | 信越化学工業 |
6501 | 日立製作所 |
不知道代码怎么办
方法一:用 EDINET DB 搜索公司名
bash
curl -s -H "X-API-Key: edb_你的Key" \ "https://edinetdb.jp/v1/search?q=トヨタ" | python3 -m json.tool --no-ensure-ascii
搜索支持日文和英文公司名。返回结果里的 sec_code 字段就是证券代码。
方法二:在 Yahoo Finance 上搜
访问 finance.yahoo.com 搜索公司名,URL 里会显示代码(如 7203.T),去掉 .T 就是 4 位代码。
批量调查多只股票
如果要一次查多只,写一个循环脚本:
bash
cat > /opt/rag-nvidia/batch_analyze.sh << 'ENDOFFILE'
#!/bin/bash
CODES=("7203" "6758" "9984" "6861")
for code in "${CODES[@]}"; do
echo "================================"
echo "分析 $code ..."
curl -s -X POST http://localhost:5002/analyze \
-H "Content-Type: application/json" \
-d "{\"code\": \"$code\"}" | python3 -c "
import sys, json
d = json.load(sys.stdin)
if 'error' in d:
print(' 错误:', d['error'])
else:
print(f\" {d['company']} ({d['code']})\")
print(f\" 股价: {d['stock_price']} 円 | 进度率: {d['progress_percent']}%\")
print(f\" 报告已保存: {d['report_path']}\")
"
sleep 2
done
ENDOFFILE
chmod +x /opt/rag-nvidia/batch_analyze.sh
/opt/rag-nvidia/batch_analyze.sh
配额注意:每只股票消耗 2 次 EDINET 请求(search + earnings)。4 只就是 8 次。免费版每天 100 次,约能分析 50 只。
配额与缓存
| 项目 | 说明 |
|---|---|
| EDINET DB 免费版 | 100 请求/天 |
| 每次分析消耗 | 2 次(search + earnings) |
| 缓存效果 | 同一天内重复查同一只股票不消耗配额 |
| 超限返回 | HTTP 429,等第二天重置 |
报告保存位置
每次分析的报告都保存到:
text
/opt/rag-nvidia/reports/{代码}_analysis.md
比如查了 7203,报告就在 /opt/rag-nvidia/reports/7203_analysis.md。直接 cat 就能看:
bash
cat /opt/rag-nvidia/reports/7203_analysis.md
建议的用法
先查一只你熟悉的公司,验证分析质量。比如已经查过 4167(ココペリ),可以再查 7203(丰田),对比 LLM 对大公司和小公司的分析差异。
bash
curl -s -X POST http://localhost:5002/analyze \
-H "Content-Type: application/json" \
-d '{"code": "7203"}' | python3 -c "
import sys, json
print(json.load(sys.stdin)['analysis'])
"
查完几家公司后,可以在 /opt/rag-nvidia/reports/ 目录下对比不同公司的报告,快速了解同行业或不同行业的业绩特征。
