第 7 章 · 03 polygonapi 真相澄清 本节摘要:本节直面 AutoHedge 一个尴尬的真相—— 文件名叫 polygon,但代码里实际指向的却是 ,而且路径用的还是 polygon 风格的( 、 )。这种「名实不符」是项目未完成的典型缩影:作者大概率起手想用 polygon.io(知名股票数据 API),写了一半换了数据源(massive.com),却没改文件名、没改路径风格、也没改错误日志里的「Polygon API」。
本节摘要:本节直面 AutoHedge 一个尴尬的真相——
autohedge/tools/polygon_api.py文件名叫 polygon,但代码里实际指向的却是api.massive.com,而且路径用的还是 polygon 风格的(/v3/reference/tickers/...、/v1/open-close/...)。这种「名实不符」是项目未完成的典型缩影:作者大概率起手想用 polygon.io(知名股票数据 API),写了一半换了数据源(massive.com),却没改文件名、没改路径风格、也没改错误日志里的「Polygon API」。本节逐段读这个文件,讲清三个对外函数(get_ticker_overview/get_balance_sheets/get_daily_ticker_summary)各自做什么,并把它和 yahoo_api 对照,说明为什么这个工具实际上没被任何 Agent 用上、为什么是「半成品占位」。读完本节,你会更清醒地看待项目的成熟度。
内容来源:原项目源码
autohedge/tools/polygon_api.py,逐行精读并套用体系化模板。
⚠️ 现实澄清:这个文件是「名实不符」的活样本。文件名 polygon、域名 massive.com、路径 polygon 风格、日志写 Polygon——四处不一致。它没被任何 Agent 绑定,在项目里实际不起作用。本节的价值在「识别半成品」,不在「学怎么用」。
阅读完本节,你应当能够:
polygon_api.py 的三个对外函数。先看文件最顶部的模块 docstring 和常量(第 1-14 行):
""" Stocks API client for ticker overview, balance sheets, and daily OHLC. Uses Massive API (https://massive.com/docs). Set MASSIVE_API_KEY and optionally POLYGON_BASE_URL in .env. """ import json import os from typing import Any, Optional import httpx from loguru import logger DEFAULT_BASE_URL = "https://api.massive.com"
逐行细看,矛盾立刻浮现:
Stocks API client(股票 API 客户端)——但文件名是 polygon_api,polygon.io 确实是股票数据商,这点对得上。DEFAULT_BASE_URL = "https://api.massive.com"——实际请求走的是 massive.com。这就是第一处名实不符:文件叫 polygon,实际打 massive.com。而且 docstring 自己还残留着「POLYGON_BASE_URL」这个根本没在代码里用到的环境变量名——这是个没清理干净的痕迹。
看 _get_headers(第 17-22 行):
def _get_headers() -> dict[str, str]: headers: dict[str, str] = {} key = os.getenv("MASSIVE_API_KEY") if key: headers["Authorization"] = f"Bearer {key}" return headers
观察:
MASSIVE_API_KEY(又一个项目里没在 .env.example 出现的 key,和 EXA_API_KEY 一样是「代码读、配置漏写」)。Authorization: Bearer <key>——这是 massive.com 的鉴权风格;polygon.io 用的是 apiKey 查询参数或 header。又一个「实指向 massive」的证据。.env.example 里既没有 MASSIVE_API_KEY,也没有 POLYGON_API_KEY,只有 JUPITER/OPENAI/WALLET 三个。所以这个工具即便被调用,也永远拿不到 key,header 永远是空 dict。看内部 _get 函数(第 25-42 行):
def _get(path: str, *, params: Optional[dict[str, Any]] = None) -> str: url = f"{DEFAULT_BASE_URL.rstrip('/')}{path}" try: with httpx.Client(timeout=15) as client: resp = client.get( url, params=params, headers=_get_headers() or None, ) resp.raise_for_status() return json.dumps(resp.json()) except httpx.HTTPError as e: logger.error(f"Polygon API request failed: {e}") # ← 日志写 Polygon! raise
注意第 41 行的错误日志:"Polygon API request failed"——但实际请求的是 massive.com。这是第三处名实不符:日志里的名字还是 polygon,会误导排查问题的人。
💡 半成品的指纹:这种「日志名/变量名/文件名和实际行为不一致」,是代码改了一半没改完的典型指纹。生产代码 review 时,看到这种不一致就该追问:「这个文件到底想用哪个服务?为什么没收尾?」
虽然 BASE 是 massive.com,但路径风格还是 polygon.io 的。逐个看。
def get_ticker_overview(ticker: str, date: Optional[str] = None) -> str: ... path = f"/v3/reference/tickers/{ticker.strip()}" params: dict[str, Any] = {} if date: params["date"] = date return _get(path, params=params if params else None)
/v3/reference/tickers/{ticker} 是 polygon.io 的标准路径(polygon 的 ticker reference 端点)。api.massive.com 后面,大概率 massive.com 根本没有这个端点——请求会 404。date(point-in-time,按某日查),这也是 polygon 的特性。这是文件里最长的函数,但逻辑很简单——把一堆可选筛选条件塞进 params:
def get_balance_sheets( *, cik: Optional[str] = None, tickers: Optional[str] = None, tickers_any_of: Optional[str] = None, period_end: Optional[str] = None, period_end_gte: Optional[str] = None, period_end_lte: Optional[str] = None, filing_date: Optional[str] = None, fiscal_year: Optional[float] = None, fiscal_quarter: Optional[float] = None, timeframe: Optional[str] = None, limit: Optional[int] = None, sort: Optional[str] = None, ) -> str: params: dict[str, Any] = {} if cik is not None: params["cik"] = cik ... return _get("/stocks/financials/v1/balance-sheets", params=params or None)
tickers_any_of 映射成查询参数 tickers.any_of(带点号),这是 polygon 的命名约定。/stocks/financials/v1/balance-sheets ——注意这个路径和上面的 /v3/reference/ 风格又不一样,像是 massive.com 的路径风格。所以同一个文件里路径风格都可能是混的。def get_daily_ticker_summary( stocks_ticker: str, date: str, adjusted: Optional[bool] = None, ) -> str: ... path = f"/v1/open-close/{stocks_ticker.strip()}/{date.strip()}" params: dict[str, Any] = {} if adjusted is not None: params["adjusted"] = "true" if adjusted else "false" return _get(path, params=params if params else None)
/v1/open-close/{ticker}/{date} 是 polygon.io 的日开高低收端点(Daily Open/Close API)。adjusted(复权)也是 polygon 的标准参数。把上面看到的矛盾集中成一张表:
| 位置 | 内容 | 指向 |
|---|---|---|
| 文件名 | polygon_api.py |
polygon |
| docstring | Uses Massive API |
massive |
| docstring env | POLYGON_BASE_URL(代码未用) |
polygon(残留) |
| docstring env | MASSIVE_API_KEY |
massive |
| 常量 | DEFAULT_BASE_URL = "https://api.massive.com" |
massive |
| 鉴权 | MASSIVE_API_KEY + Bearer |
massive |
| 错误日志 | Polygon API request failed |
polygon(残留) |
路径 /v3/reference/tickers/ |
polygon 风格 | polygon |
路径 /v1/open-close/ |
polygon 风格 | polygon |
路径 /stocks/financials/v1/balance-sheets |
massive 风格 | massive |
四处名字打架:文件名、docstring、域名、日志各说各话。这是项目「写到一半换方向、没回头清理」的铁证。
从这些蛛丝马迹,可以合理推断这个文件的演化轨迹:
.env.example 没加 MASSIVE_API_KEY,甚至连 docstring 里的 POLYGON_BASE_URL 都没删。换完核心逻辑就去做别的了,留下一个半成品。workers.py 四个专家没一个 import 它,get_tools() 也没收它。它纯粹躺在 tools 目录里。同样是股票数据工具,为什么 yahoo_api 写得那么认真(限流容错、部分返回),polygon_api 却是个半成品?
| 维度 | yahoo_api | polygon_api |
|---|---|---|
| 数据源 | yfinance(免费、非官方) | 起手 polygon、实际 massive(付费) |
| 完成度 | 完整可用,容错细致 | 半成品,名实不符 |
| 是否被 Agent 用 | (workers.py 里也没显式绑) | 完全没接 |
| 教学价值 | 限流容错典范 | 半成品识别样本 |
合理推断:作者发现免费数据源(yfinance)够用之后,就放弃了付费数据源(polygon/massive)的接入,yahoo_api 留下打磨,polygon_api 扔在那。这符合「最小成本跑通 demo」的常见心态。
本节最大的价值,不是「学怎么用 polygon_api」(它根本没被用),而是学会从代码痕迹识别半成品。归纳几个指纹:
/v3/reference/、有的 /stocks/...)。⚠️ 现实澄清:看到这种文件,别被「文件名叫 polygon」骗去配 polygon API key——配了也没用,代码根本不打 polygon。也别花时间想「怎么把它接给 Agent」,作者自己都没接。它的正确处理方式是:要么补完(确定用哪个服务,改对名字,接入 Agent),要么删掉(承认这是 abandoned code)。现状是「既没补完也没删」,纯粹占地方。
api.massive.com、日志写 Polygon——四处打架。/v3/reference/tickers/)、get_balance_sheets(11 个筛选参数)、get_daily_ticker_summary(/v1/open-close/)——路径都是 polygon 风格,拼到 massive 域名下大概率 404。workers.py 四个专家没一个用它,get_tools() 也没收它——纯占位。至此第 7 章结束,你看清了 AutoHedge 的数据工具层:yahoo 限流容错认真做、Jupiter 官方 API 对称设计、polygon 名实不符的占位。下一章我们进入全教程技术密度最高的部分——Solana 链上交易实战,从账户与密钥模型开始。