docs: add API references, mapping corrections, and verification script

- Add yfinance.org and defeatbeta-api.org reference docs
- Fix defeatbeta_mapping.org: deprecated yfinance property names
  (quarterly_financials→quarterly_income_stmt, financials→income_stmt),
  longName vs longBusinessSummary conceptual mismatch, cashflow note typo
- Add Mapping Limitations section with live verification results (AAPL):
  DuckDB 1.4.3 incompatibility, format differences, coverage gaps
- Add docs/test_mapping.py as runnable mapping verification script
- Add offline.py, persistent_cache.py, download_data.py, warmup_cache.py
  for offline/cached defeatbeta usage
- Add aapl_yfinance.py exploration script and quant.py scaffold
- Add .envrc (uv layout) and update pyproject.toml + uv.lock

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 15:33:21 +08:00
parent b71a8e77b0
commit b5bf689e72
16 changed files with 3650 additions and 141 deletions
+228
View File
@@ -0,0 +1,228 @@
"""
Mapping verification: yfinance vs defeatbeta-api for AAPL.
Prints type + 2 representative rows/values for each mapped pair.
Methods that fail are reported inline (not crashed).
"""
import warnings
warnings.filterwarnings("ignore")
import yfinance as yf
from defeatbeta_api.data.ticker import Ticker
import pandas as pd
SYMBOL = "AAPL"
yf_t = yf.Ticker(SYMBOL)
db_t = Ticker(SYMBOL)
PASS = ""
FAIL = ""
def try_db(fn, *args, **kwargs):
"""Call a defeatbeta method, return (result, None) or (None, error_str)."""
try:
return fn(*args, **kwargs), None
except Exception as e:
return None, str(e)[:80]
def show_df(label, df, n=2):
if df is None:
return
if isinstance(df, pd.DataFrame):
print(f" {label} ({df.shape[0]}r×{df.shape[1]}c), last {n} rows:")
cols = list(df.columns)[:6]
print(df[cols].tail(n).to_string(index=False))
elif isinstance(df, pd.Series):
print(f" {label} (Series len={len(df)}), last {n}:")
print(df.tail(n).to_string())
def row(label, yf_val, yf_type_label, db_result, db_err):
ok = PASS if db_result is not None else FAIL
print(f"\n{ok} {label}")
if db_err:
print(f" yfinance → {yf_type_label}: {yf_val!r}")
print(f" defeatbeta→ ERROR: {db_err}")
else:
print(f" yfinance type={yf_type_label} value={yf_val!r}")
print(f" defeatbeta type={type(db_result).__name__}", end="")
if isinstance(db_result, pd.DataFrame):
print(f" cols={list(db_result.columns)}")
show_df("defeatbeta", db_result)
else:
print(f" value={db_result!r}")
def section(title):
print(f"\n{'='*68}\n {title}\n{'='*68}")
# ── 1. PRICE DATA ─────────────────────────────────────────────────────────────
section("1. PRICE DATA — ticker.history() vs ticker.price()")
yf_price = yf_t.history(period="5d")[["Open", "Close", "High", "Low", "Volume"]]
db_price, db_price_err = try_db(db_t.price)
print(f"\n{PASS if not db_price_err else FAIL} ticker.history() vs ticker.price()")
print(f" yfinance type=DataFrame cols={list(yf_price.columns)}")
print(yf_price.tail(2).to_string())
if db_price_err:
print(f" defeatbeta→ ERROR: {db_price_err}")
else:
print(f" defeatbeta type=DataFrame cols={list(db_price.columns)}")
print(db_price.tail(2).to_string(index=False))
# ── 2. FINANCIAL STATEMENTS ───────────────────────────────────────────────────
section("2. FINANCIAL STATEMENTS")
# yfinance uses .quarterly_income_stmt (v1.3) — old .quarterly_financials is deprecated
yf_inc_q = yf_t.quarterly_income_stmt
db_inc_q, db_inc_q_err = try_db(db_t.quarterly_income_statement)
db_inc_q_df = db_inc_q.df() if db_inc_q else None
print(f"\n{PASS if not db_inc_q_err else FAIL} quarterly_income_stmt vs quarterly_income_statement()")
print(f" yfinance type={type(yf_inc_q).__name__} shape={yf_inc_q.shape} (rows=line items, cols=quarters)")
print(yf_inc_q.iloc[:2, :2].to_string())
if db_inc_q_err:
print(f" defeatbeta→ ERROR: {db_inc_q_err}")
else:
print(f" defeatbeta type={type(db_inc_q).__name__} → .df() shape={db_inc_q_df.shape}")
print(db_inc_q_df.iloc[:2, :5].to_string(index=False))
yf_bs = yf_t.balance_sheet
db_bs, db_bs_err = try_db(db_t.annual_balance_sheet)
db_bs_df = db_bs.df() if db_bs else None
print(f"\n{PASS if not db_bs_err else FAIL} balance_sheet vs annual_balance_sheet()")
print(f" yfinance type={type(yf_bs).__name__} shape={yf_bs.shape}")
print(yf_bs.iloc[:2, :2].to_string())
if db_bs_err:
print(f" defeatbeta→ ERROR: {db_bs_err}")
else:
print(f" defeatbeta type={type(db_bs).__name__} → .df() shape={db_bs_df.shape}")
print(db_bs_df.iloc[:2, :5].to_string(index=False))
yf_cf = yf_t.cashflow
db_cf, db_cf_err = try_db(db_t.annual_cash_flow)
db_cf_df = db_cf.df() if db_cf else None
print(f"\n{PASS if not db_cf_err else FAIL} cashflow vs annual_cash_flow()")
print(f" yfinance type={type(yf_cf).__name__} shape={yf_cf.shape}")
print(yf_cf.iloc[:2, :2].to_string())
if db_cf_err:
print(f" defeatbeta→ ERROR: {db_cf_err}")
else:
print(f" defeatbeta → .df() shape={db_cf_df.shape}")
print(db_cf_df.iloc[:2, :5].to_string(index=False))
# ── 3. VALUATION METRICS ──────────────────────────────────────────────────────
section("3. VALUATION METRICS")
db_pe, db_pe_err = try_db(db_t.ttm_pe)
db_eps, db_eps_err = try_db(db_t.ttm_eps)
db_mc, db_mc_err = try_db(db_t.market_capitalization)
db_pb, db_pb_err = try_db(db_t.pb_ratio)
db_ps, db_ps_err = try_db(db_t.ps_ratio)
row("trailingPE → ttm_pe()", yf_t.info.get("trailingPE"), "float", db_pe, db_pe_err)
row("trailingEps → ttm_eps()", yf_t.info.get("trailingEps"), "float", db_eps, db_eps_err)
row("marketCap → market_cap()", yf_t.info.get("marketCap"), "int", db_mc, db_mc_err)
row("priceToBook → pb_ratio()", yf_t.info.get("priceToBook"), "float", db_pb, db_pb_err)
row("priceToSales → ps_ratio()", yf_t.info.get("priceToSalesTrailing12Months"), "float", db_ps, db_ps_err)
# ── 4. FINANCIAL RATIOS ───────────────────────────────────────────────────────
section("4. FINANCIAL RATIOS")
db_roe, db_roe_err = try_db(db_t.roe)
db_roa, db_roa_err = try_db(db_t.roa)
db_beta, db_beta_err = try_db(db_t.beta)
db_wacc, db_wacc_err = try_db(db_t.wacc)
row("returnOnEquity → roe()", yf_t.info.get("returnOnEquity"), "float", db_roe, db_roe_err)
row("returnOnAssets → roa()", yf_t.info.get("returnOnAssets"), "float", db_roa, db_roa_err)
row("beta → beta()", yf_t.info.get("beta"), "float", db_beta, db_beta_err)
row("N/A → wacc()", None, "N/A", db_wacc, db_wacc_err)
# ── 5. GROWTH METRICS ─────────────────────────────────────────────────────────
section("5. GROWTH METRICS")
db_rg, db_rg_err = try_db(db_t.quarterly_revenue_yoy_growth)
db_eg, db_eg_err = try_db(db_t.quarterly_eps_yoy_growth)
db_nig, db_nig_err = try_db(db_t.quarterly_net_income_yoy_growth)
row("revenueGrowth → quarterly_revenue_yoy_growth()", yf_t.info.get("revenueGrowth"), "float", db_rg, db_rg_err)
row("earningsGrowth → quarterly_eps_yoy_growth()", yf_t.info.get("earningsGrowth"), "float", db_eg, db_eg_err)
row("N/A → quarterly_net_income_yoy_growth()", None, "N/A", db_nig, db_nig_err)
# ── 6. MARGIN METRICS ─────────────────────────────────────────────────────────
section("6. MARGIN METRICS")
db_nm, db_nm_err = try_db(db_t.quarterly_net_margin)
db_gm, db_gm_err = try_db(db_t.quarterly_gross_margin)
db_om, db_om_err = try_db(db_t.quarterly_operating_margin)
row("profitMargins → quarterly_net_margin()", yf_t.info.get("profitMargins"), "float", db_nm, db_nm_err)
row("grossMargins → quarterly_gross_margin()", yf_t.info.get("grossMargins"), "float", db_gm, db_gm_err)
row("operatingMargins → quarterly_op_margin()", yf_t.info.get("operatingMargins"), "float", db_om, db_om_err)
# ── 7. DIVIDENDS & SPLITS ─────────────────────────────────────────────────────
section("7. DIVIDENDS & SPLITS")
yf_div = yf_t.dividends
db_div, db_div_err = try_db(db_t.dividends)
print(f"\n{PASS if not db_div_err else FAIL} .dividends vs .dividends()")
print(f" yfinance type={type(yf_div).__name__} len={len(yf_div)}")
print(yf_div.tail(2).to_string())
if db_div_err:
print(f" defeatbeta→ ERROR: {db_div_err}")
else:
print(f" defeatbeta type={type(db_div).__name__} shape={db_div.shape}")
print(db_div.tail(2).to_string(index=False))
yf_sp = yf_t.splits
db_sp, db_sp_err = try_db(db_t.splits)
print(f"\n{PASS if not db_sp_err else FAIL} .splits vs .splits()")
print(f" yfinance type={type(yf_sp).__name__} len={len(yf_sp)}")
print(yf_sp.tail(2).to_string())
if db_sp_err:
print(f" defeatbeta→ ERROR: {db_sp_err}")
else:
print(f" defeatbeta type={type(db_sp).__name__} shape={db_sp.shape}")
print(db_sp.tail(2).to_string(index=False))
# ── 8. COMPANY INFO ───────────────────────────────────────────────────────────
section("8. COMPANY INFO — .info vs .info()")
yf_info = yf_t.info
db_info, db_info_err = try_db(db_t.info)
if db_info_err:
print(f" defeatbeta→ ERROR: {db_info_err}")
else:
fields = [
("sector", "sector", yf_info.get("sector")),
("industry", "industry", yf_info.get("industry")),
("employees", "full_time_employees", yf_info.get("fullTimeEmployees")),
("website", "web_site", yf_info.get("website")),
# Note: yf longName = company name; longBusinessSummary = description
("longName", None, yf_info.get("longName")),
("longBusinessSummary", "long_business_summary", yf_info.get("longBusinessSummary", "")[:60]),
]
print(f"\n {'Field':<30} {'yfinance':<35} {'defeatbeta'}")
print(f" {'-'*30} {'-'*35} {'-'*30}")
for label, db_col, yf_val in fields:
db_val = db_info[db_col].iloc[0] if db_col and db_col in db_info.columns else ""
if isinstance(db_val, str) and len(db_val) > 40:
db_val = db_val[:40] + ""
if isinstance(yf_val, str) and len(yf_val) > 34:
yf_val = yf_val[:34] + ""
print(f" {label:<30} {str(yf_val):<35} {str(db_val)}")
# ── SUMMARY ───────────────────────────────────────────────────────────────────
print(f"\n{'='*68}")
print(" ALL CHECKS COMPLETE")
print(f"{'='*68}\n")