b5bf689e72
- 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>
229 lines
10 KiB
Python
229 lines
10 KiB
Python
"""
|
||
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")
|