thomasproject-stack/ksa-reits-tracker
GitHub: thomasproject-stack/ksa-reits-tracker
免费优先的数据管道,通过逆向 Yahoo Finance 认证自动发现并采集沙特所有 REIT 基金的价格与市值数据,生成公式驱动的双语 Excel 报告。
Stars: 0 | Forks: 0
# KSA REITs 追踪器
**一个免费优先的 pipeline,直接通过 Yahoo Finance 的公开 API 为 Tadawul 上的每一只沙特 REIT 拍摄快照,并生成一份样式精美、由公式驱动的 Excel 报告 —— 支持双语(EN/FR),并附带引用了所有数据来源的方法论工作表。** 无需付费数据源,无需 API key:整个项目只需三个小脚本即可运行。
## 为什么它很有趣
- **动态发现 Symbol,而非硬编码列表。** 沙特 REIT 位于一个连续的 ticker 区块(`43xx` 范围)内。`discover.py` 会探测每个代码的 chart endpoint,并保留名称中包含 "REIT" 的基金 —— 其数量(今天是 19 只)是直接从数据中得出的,而不是凭空断言的。
- **逆向工程的 Yahoo auth。** 实用的 `quoteSummary` endpoint(流通股数、实时市值)受到 "crumb" token 的访问限制,且该 token 只有在配合正确的 cookies 时才有效。本 pipeline 完整复现了这一握手过程 —— 许多教程都在这里出错。
- **时区精准的价格匹配。** 每个目标日期都会使用 **Asia/Riyadh** 日历匹配到 *当天或之前* 最近的交易日,因此 UTC 午夜的时间分界线绝不会在周末或节假日时悄无声息地将收盘价错移一天。
- **保持实时更新的交付物。** 只有原始输入数据(价格、单位数)被写为固定值;每一个派生指标 —— 变化值、变化百分比、市值、行业总计 —— 都是 **Excel 公式**。修改一个价格,整个工作表就会自动重新计算。
- **内建的信任机制。** 第二个工作表记录了计算方法,并将部分基金与 Argaam、Saudi Exchange 和 StockAnalysis 进行了交叉验证;任何缺失的数据都会被标记为 `n/a`,而不是捏造数据。
## 技术细节详述
**crumb/cookie 握手**(`extract.py`,在 `examples/yahoo_crumb_probe.py` 中作为独立脚本复现):
```
1. GET fc.yahoo.com ─┐ collect session cookies
2. GET finance.yahoo.com/quote/ ─┘
3. GET query2…/v1/test/getcrumb → crumb token (bound to those cookies)
4. GET query2…/v10/finance/quoteSummary/?modules=price,defaultKeyStatistics&crumb=
```
如果没有步骤 1–3,`quoteSummary` 调用将返回 `401/403`;加上这些步骤后,它将返回 `sharesOutstanding` 和实时市值。
**工作簿中的每一个数字及其生成方式** —— 原始输入 vs. 实时公式:
| 列名 | 来源 | 方式 |
|---|---|---|
| Price (start) / Price (end) | value | 来自 `/v8/finance/chart` 的调整后日 **close**,匹配至 ≤ 目标日期(Asia/Riyadh)的最近交易日 |
| Units outstanding | value | 来自通过 crumb 验证的 `quoteSummary` 的 `sharesOutstanding.raw` |
| Change (SAR) | **formula** | `=F{r}-E{r}` |
| Change (%) | **formula** | `=(F{r}-E{r})/E{r}` |
| Market cap (end) | **formula** | `=I{r}*F{r}` (units × 结束日期 close) |
| Sector totals | **formula** | `=SUM(I{first}:I{last})`, `=SUM(J{first}:J{last})` |
对于*过去*日期的市值,计算方式特意采用 `units × close`,而不是 Yahoo 的“实时”市值(后者反映的是你恰好查询当天的数值)。各行按市值从大到小排序;涨/跌变化以绿/红色带标出。
## 工作原理
```
REIT_CODE_MIN..MAX (default 4300..4360)
│
▼
discover.py ───────────────► reit_list.json [[code, name, last_price], …]
probe /v8/finance/chart per code,
keep names containing "REIT"
│
▼
extract.py ────────────────► reit_data.json {start_date, end_date, rows:[…]}
crumb/cookie auth → getcrumb
/v8/finance/chart (daily closes, closest Riyadh trading day)
/v10/…/quoteSummary (units outstanding, live mcap)
│
▼
build_xlsx.py ─────────────► KSA_REITs_price_and_market_cap.xlsx (EN)
one parameterised builder → REITs_Arabie_Saoudite_prix_capitalisation.xlsx (FR)
raw inputs as values; change / % / market cap / totals as live formulas
sheet 2: methodology + cited sources
```
- **`discover.py`** —— 通过扫描代码范围发现资产范围(可通过 `REIT_CODE_MIN` / `REIT_CODE_MAX` 覆盖)。
- **`extract.py`** —— crumb 交互过程、滑动窗口日收盘价抓取、最近交易日匹配,以及每只基金的市值/变化推导。快照日期可通过 `REIT_START_DATE` / `REIT_END_DATE` 覆盖。
- **`build_xlsx.py`** —— 一个单一且支持语言参数化的构建器,它通过一条代码路径生成英文和法文工作簿(标签位于 `LABELS` 表中),确保除语言外,这两份交付物在字节级保持完全一致。
- **`examples/yahoo_crumb_probe.py`** —— 针对单个 symbol 的 auth 握手的最小化独立演示。
## 技术栈
Python · httpx · openpyxl · Yahoo Finance 公开 API · `zoneinfo` (Asia/Riyadh)
## 在本地运行
```
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python3 discover.py # -> reit_list.json (find the REIT symbols)
python3 extract.py # -> reit_data.json (fetch prices + units)
python3 build_xlsx.py # -> the two .xlsx workbooks (EN + FR)
```
可选的覆盖配置(见 `.env.example`):`REIT_CODE_MIN`、`REIT_CODE_MAX`、`REIT_START_DATE`、`REIT_END_DATE`。在没有任何配置的情况下,它将为默认日期生成时间点快照。
## 数据与隐私
本仓库**仅包含代码**。不提供任何抓取出的输出 —— 没有 `reit_list.json`,没有 `reit_data.json`,也没有 `.xlsx`。工作簿中展示的所有内容均为运行时从 Yahoo Finance 提取的**公开沙特股票市场数据**,并与 Saudi Exchange / Argaam / StockAnalysis 进行了交叉验证。运行这三个脚本,交付物就会在本地重新构建。
## 项目布局
```
discover.py universe discovery (code-range scan)
extract.py crumb auth + price/units fetch → reit_data.json
build_xlsx.py bilingual, formula-driven workbook builder
examples/ standalone crumb-auth probe
```
## 许可证
MIT —— 详情见 [LICENSE](LICENSE)。
标签:运行时操纵, 逆向工具