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:
@@ -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")
|
||||
Reference in New Issue
Block a user