Files
learn-trading/docs/test_mapping.py
T
tomatocream b5bf689e72 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>
2026-04-26 15:33:21 +08:00

229 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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")