SPB Git forge

spb/hfmarketdata

Public

Open high-frequency market data platform — FirstRate full-history downloader, DuckDB/Parquet lake, open REST API and React docs platform (www.hfmarketdata.io)

127commits 1branches 0releases
24.7 MBsize
maindefault branch
11 days agolast push
JavaScript 53.7% Python 38.3% CSS 4.6% TypeScript 3.1%
15.9 KB · 271 lines python
Raw Blame History
1"""Integration tests of /v1/fundamentals/* (TestClient over the ingested prototype companies)."""2from __future__ import annotations34import io56import pandas as pd7import pytest89pytestmark = pytest.mark.usefixtures("fundamentals_data")101112def _rows(body):13    return body["data"]141516def _find(rows, fy, fq, statement=None):17    return next(r for r in rows if r["fiscal_year"] == fy and r["fiscal_quarter"] == fq and (statement is None or r["statement"] == statement))181920def test_universe_and_share_classes(fundamentals_data):21    from fundamentals.service import resolve_company22    u = fundamentals_data["universe"]23    assert u.companies == 4 and u.added == 4                     # AAPL, MSFT, SHAK, Alphabet (GOOG+GOOGL)24    goog, googl = resolve_company("GOOG"), resolve_company("GOOGL")25    assert goog.cik == googl.cik == 1652044 and sorted(goog.tickers) == ["GOOG", "GOOGL"]26    assert resolve_company("aapl").cik == 320193272829def test_statements_quarterly_json(client):30    r = client.get("/v1/fundamentals/AAPL/statements?statement=income&period=quarterly&limit=200")31    assert r.status_code == 200 and r.headers["X-Row-Count"] == str(r.json()["meta"]["count"])32    body = r.json()33    assert body["meta"]["ticker"] == "AAPL" and body["meta"]["statement"] == "income"34    row = _find(_rows(body), 2024, 2)35    assert row["period_end"] == "2024-03-30" and row["revenue"] == 90_753_000_000 and row["net_income"] == 23_636_000_00036    assert row["eps_diluted"] == 1.53 and row["form"] == "10-Q" and row["filed_date"] == "2024-05-03"37    assert row["coverage"]["interest_expense"] == {"reason": "no_mapped_tag"}38    assert "shares_outstanding" not in row and "depreciation_amortization" not in row       # auxiliary accounts hidden39    q4 = _find(_rows(body), 2024, 4)40    assert q4["derived"] is True and q4["coverage"]["revenue"]["derived"] in ("FY-YTD9", "FY-(Q1+Q2+Q3)")41    # rows are newest first and only quarters42    assert all(r["fiscal_quarter"] in (1, 2, 3, 4) for r in _rows(body))43    assert _rows(body)[0]["period_end"] >= _rows(body)[-1]["period_end"]444546def test_statements_all_statements_and_pagination(client):47    r = client.get("/v1/fundamentals/AAPL/statements?statement=all&period=quarterly&limit=5")48    body = r.json()49    assert body["meta"]["count"] == 5 and body["meta"]["next_cursor"]50    r2 = client.get(f"/v1/fundamentals/AAPL/statements?statement=all&period=quarterly&limit=5&cursor={body['meta']['next_cursor']}")51    assert r2.status_code == 200 and r2.json()["data"][0] != body["data"][0]52    assert {r["statement"] for r in body["data"]} <= {"income", "balance", "cashflow"}53    assert "total_assets" in body["data"][0] and "operating_cash_flow" in body["data"][0]545556def test_statements_annual_ttm_and_balance(client):57    r = client.get("/v1/fundamentals/AAPL/statements?statement=income&period=annual")58    fy = _find(_rows(r.json()), 2024, 0)59    assert fy["revenue"] == 391_035_000_000 and fy["period_start"] == "2023-10-01" and fy["period_end"] == "2024-09-28"60    r = client.get("/v1/fundamentals/AAPL/statements?statement=income&period=ttm&to=2024-03-30&limit=1")61    row = _rows(r.json())[0]62    assert row["ttm"] is True and row["fiscal_year"] == 2024 and row["fiscal_quarter"] == 2 and row["revenue"] == 381_623_000_00063    r = client.get("/v1/fundamentals/AAPL/statements?statement=balance&period=quarterly&from=2024-03-30&to=2024-03-30")64    bal = _rows(r.json())[0]65    assert bal["total_assets"] == 337_411_000_000 and bal["period_start"] is None66    assert bal["total_debt"] == bal["short_term_debt"] + bal["long_term_debt"]67    r = client.get("/v1/fundamentals/AAPL/statements?statement=cashflow&period=quarterly&from=2024-03-30&to=2024-03-30")68    cf = _rows(r.json())[0]69    assert cf["operating_cash_flow"] == 22_690_000_000 and cf["derived"] is True70    assert cf["free_cash_flow"] == cf["operating_cash_flow"] - cf["capex"]717273def test_statements_as_of_point_in_time(client):74    r = client.get("/v1/fundamentals/AAPL/statements?statement=income&period=quarterly&as_of=2024-05-02&limit=1")75    assert r.status_code == 20076    top = _rows(r.json())[0]77    assert (top["fiscal_year"], top["fiscal_quarter"]) == (2024, 1) and r.json()["meta"]["as_of"] == "2024-05-02"78    r = client.get("/v1/fundamentals/AAPL/statements?statement=income&period=quarterly&as_of=2024-05-03&limit=1")79    top = _rows(r.json())[0]80    assert (top["fiscal_year"], top["fiscal_quarter"]) == (2024, 2)81    r = client.get("/v1/fundamentals/AAPL/statements?as_of=2000-01-01")82    assert r.status_code == 404 and r.json()["error"]["code"] == "FUNDAMENTALS_NOT_AVAILABLE"838485def test_statements_formats(client):86    r = client.get("/v1/fundamentals/AAPL/statements?statement=income&period=annual&format=csv")87    assert r.status_code == 200 and r.headers["content-type"].startswith("text/csv")88    df = pd.read_csv(io.StringIO(r.text))89    assert "revenue" in df.columns and len(df) == int(r.headers["X-Row-Count"])90    r = client.get("/v1/fundamentals/AAPL/statements?statement=income&period=annual&format=parquet")91    assert r.status_code == 200 and r.headers["content-type"] == "application/vnd.apache.parquet"92    df = pd.read_parquet(io.BytesIO(r.content))93    assert df["revenue"].max() > 3e1194    r = client.get("/v1/fundamentals/AAPL/statements?format=xml")95    assert r.status_code == 400 and r.json()["error"]["code"] == "INVALID_PARAMETER"969798def test_statements_as_reported_view(client):99    r = client.get("/v1/fundamentals/AAPL/statements?statement=income&period=quarterly&view=as_reported&from=2024-03-30&to=2024-03-30")100    assert r.status_code == 200101    rows = _rows(r.json())102    rev = next(x for x in rows if x["account"] == "revenue")103    assert rev["tag"] == "RevenueFromContractWithCustomerExcludingAssessedTax" and rev["val"] == 90_753_000_000104    assert rev["accn"] == "0000320193-24-000069" and rev["unit"] == "USD"105106107def test_errors_envelope(client):108    r = client.get("/v1/fundamentals/NOPE/statements")109    assert r.status_code == 404110    body = r.json()111    assert body["error"]["code"] == "FUNDAMENTALS_NOT_AVAILABLE" and body["error"]["docs"].endswith("#fundamentals_not_available")112    assert body["detail"] and body["error"]["details"]["ticker"] == "NOPE"113    r = client.get("/v1/fundamentals/SPY/statements")           # in the price lake but not an SEC filer114    assert r.status_code == 404 and r.json()["error"]["code"] == "FUNDAMENTALS_NOT_AVAILABLE"115    r = client.get("/v1/fundamentals/AAPL/statements?statement=equity")116    assert r.status_code == 400 and r.json()["error"]["code"] == "INVALID_PARAMETER"117    r = client.get("/v1/fundamentals/AAPL/statements?as_of=yesterday")118    assert r.status_code == 400 and r.json()["error"]["code"] == "INVALID_PARAMETER"119    r = client.get("/v1/fundamentals/AAPL/statements?limit=0")120    assert r.status_code == 422 and r.json()["error"]["code"] == "VALIDATION_ERROR"121122123def test_facts_standardized_and_raw(client):124    r = client.get("/v1/fundamentals/AAPL/facts/revenue?from=2024-03-30&to=2024-03-30")125    assert r.status_code == 200126    rows = _rows(r.json())127    q2 = next(x for x in rows if x["fiscal_quarter"] == 2)128    assert q2["value"] == 90_753_000_000 and q2["coverage"]["tag"].endswith("RevenueFromContractWithCustomerExcludingAssessedTax")129    assert r.json()["meta"]["kind"] == "standardized" and r.json()["meta"]["tags"][0] == "us-gaap:Revenues"130    r = client.get("/v1/fundamentals/AAPL/facts/us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax?from=2024-03-30&to=2024-03-30")131    assert r.status_code == 200132    raw = _rows(r.json())133    assert any(x["val"] == 90_753_000_000 and x["accn"] == "0000320193-24-000069" for x in raw)134    assert any(x["frame"] == "CY2024Q1" for x in raw) and r.json()["meta"]["kind"] == "xbrl_fact"135    r = client.get("/v1/fundamentals/AAPL/facts/us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax?as_of=2024-05-02&from=2024-03-30&to=2024-03-30")136    assert all(x["end"] != "2024-03-30" or x["start"] != "2023-12-31" for x in _rows(r.json()))137    r = client.get("/v1/fundamentals/AAPL/facts/us-gaap:NoSuchConcept")138    assert r.status_code == 404 and r.json()["error"]["code"] == "CONCEPT_NOT_FOUND"139    r = client.get("/v1/fundamentals/AAPL/facts/dei:EntityCommonStockSharesOutstanding?format=csv")140    assert r.status_code == 200 and "EntityCommonStockSharesOutstanding" in r.text141142143def test_ratios(client):144    r = client.get("/v1/fundamentals/AAPL/ratios?as_of=2024-05-03")145    assert r.status_code == 200146    d, m = r.json()["data"], r.json()["meta"]147    assert d["fundamentals_period_end"] == "2024-03-30" and d["price"] is not None and d["price_date"] <= "2024-05-03"148    assert d["valuation"]["market_cap"] == pytest.approx(d["price"] * 15_334_082_000)149    assert d["valuation"]["pe"] == pytest.approx(d["price"] / 6.43, rel=1e-3)150    assert d["valuation"]["forward_pe"] is None and m["reasons"]["forward_pe"] == "no_estimates"151    assert d["profitability"]["gross_margin"] == pytest.approx(173_966 / 381_623, rel=1e-6)152    assert set(d) >= {"valuation", "profitability", "liquidity", "solvency", "efficiency", "growth", "per_share", "inputs"}153    assert m["shares_source"] == "dei:EntityCommonStockSharesOutstanding" and m["price_source"].startswith("stock/1day/")154    r = client.get("/v1/fundamentals/AAPL/ratios?period=annual")155    assert r.status_code == 200 and r.json()["meta"]["period"] == "annual"156    r = client.get("/v1/fundamentals/AAPL/ratios?period=weekly")157    assert r.status_code == 400158    r = client.get("/v1/fundamentals/SHAK/ratios")159    d = r.json()["data"]160    assert d["profitability"]["gross_margin"] is None and r.json()["meta"]["reasons"]["gross_margin"].startswith("missing:")161162163def test_ratios_daily_point_in_time(client):164    r = client.get("/v1/fundamentals/AAPL/ratios/daily?from=2024-04-25&to=2024-05-10&fields=pe,pb,market_cap")165    assert r.status_code == 200166    rows = _rows(r.json())167    assert rows and list(rows[0]) == ["date", "close", "fundamentals_as_of", "fundamentals_period_end", "pe", "pb", "market_cap"]168    before = [x for x in rows if x["date"] < "2024-05-03"]169    after = [x for x in rows if x["date"] >= "2024-05-03"]170    assert before and after171    assert all(x["fundamentals_period_end"] == "2023-12-30" for x in before)          # Q1 FY2024 was the latest public data172    assert all(x["fundamentals_period_end"] == "2024-03-30" for x in after)           # the 10-Q filed May 3 applies from May 3173    assert all(x["fundamentals_as_of"] <= x["date"] for x in rows)174    assert r.json()["meta"]["point_in_time"] is True175    r = client.get("/v1/fundamentals/AAPL/ratios/daily?from=2024-04-25&to=2024-05-10&fields=nope")176    assert r.status_code == 400 and r.json()["error"]["code"] == "INVALID_PARAMETER"177    r = client.get("/v1/fundamentals/AAPL/ratios/daily?from=2024-04-25&to=2024-05-10&format=parquet")178    assert r.status_code == 200 and len(pd.read_parquet(io.BytesIO(r.content))) == int(r.headers["X-Row-Count"])179180181def test_screener(client_hu):182    r = client_hu.get("/v1/fundamentals/screener?filters=market_cap>1b&sort=market_cap:desc")183    assert r.status_code == 200184    body = r.json()185    tickers = [x["ticker"] for x in body["data"]]186    assert tickers[:2] == ["AAPL", "MSFT"] and body["meta"]["total"] >= 2187    r = client_hu.get("/v1/fundamentals/screener?filters=ticker=SHAK&columns=ticker,revenue,gross_margin,net_margin")188    row = r.json()["data"][0]189    assert row["ticker"] == "SHAK" and row["gross_margin"] is None and row["net_margin"] is not None190    r = client_hu.get("/v1/fundamentals/screener?filters=revenue=1..2,pe>0")191    assert r.status_code == 200 and r.json()["meta"]["count"] == 0192    r = client_hu.get("/v1/fundamentals/screener?filters=roe>10000%25")193    assert r.status_code == 200 and r.json()["meta"]["count"] == 0194    r = client_hu.get("/v1/fundamentals/screener?filters=pe<<15")195    assert r.status_code == 400 and r.json()["error"]["code"] == "INVALID_FILTER"196    r = client_hu.get("/v1/fundamentals/screener?filters=unknown>1")197    assert r.status_code == 400 and "numeric_fields" in r.json()["error"]["details"]198    r = client_hu.get("/v1/fundamentals/screener?limit=1&format=csv")199    assert r.status_code == 200 and r.headers["X-Row-Count"] == "1"200    r = client_hu.get("/v1/fundamentals/screener?limit=1")201    nxt = r.json()["meta"]["next_cursor"]202    assert nxt and client_hu.get(f"/v1/fundamentals/screener?limit=1&cursor={nxt}").json()["data"][0]["ticker"] != r.json()["data"][0]["ticker"]203204205def test_frames(client):206    r = client.get("/v1/fundamentals/frames/revenue?calendar_quarter=2024Q1")207    assert r.status_code == 200208    rows = _rows(r.json())209    by = {x["ticker"]: x for x in rows}210    assert by["AAPL"]["value"] == 90_753_000_000 and by["AAPL"]["fiscal_quarter"] == 2       # Apple's fiscal Q2211    assert by["MSFT"]["fiscal_quarter"] == 3 and by["SHAK"]["fiscal_quarter"] == 1212    assert [x["value"] for x in rows] == sorted([x["value"] for x in rows], reverse=True)213    r = client.get("/v1/fundamentals/frames/revenue?fiscal_year=2024&fiscal_quarter=0&format=csv")214    assert r.status_code == 200 and "AAPL" in r.text215    r = client.get("/v1/fundamentals/frames/revenue?calendar_quarter=2024Q1&as_of=2024-05-02")216    assert "AAPL" not in {x["ticker"] for x in _rows(r.json())}217    r = client.get("/v1/fundamentals/frames/nope?calendar_quarter=2024Q1")218    assert r.status_code == 404 and r.json()["error"]["code"] == "CONCEPT_NOT_FOUND"219    r = client.get("/v1/fundamentals/frames/revenue")220    assert r.status_code == 400221    r = client.get("/v1/fundamentals/frames/revenue?calendar_quarter=2024-Q1x")222    assert r.status_code == 400223224225def test_filings(client):226    r = client.get("/v1/fundamentals/AAPL/filings?form=10-Q&from=2024-01-01&to=2024-12-31")227    assert r.status_code == 200228    rows = _rows(r.json())229    assert rows and all(x["form"] == "10-Q" for x in rows)230    q2 = next(x for x in rows if x["accn"] == "0000320193-24-000069")231    assert q2["filed_date"] == "2024-05-03" and q2["period_of_report"] == "2024-03-30" and q2["is_xbrl"] is True232    assert q2["primary_doc_url"] == "https://www.sec.gov/Archives/edgar/data/320193/000032019324000069/aapl-20240330.htm"233    assert q2["index_url"].endswith("/0000320193-24-000069-index.htm")234    r = client.get("/v1/fundamentals/AAPL/filings?format=parquet&limit=3")235    assert r.status_code == 200 and r.headers["X-Row-Count"] == "3"236237238def test_coverage_and_custom_extensions(client):239    r = client.get("/v1/fundamentals/SHAK/coverage")240    assert r.status_code == 200241    d = r.json()["data"]242    assert d["cik"] == 1620533 and d["quarters"] > 8 and d["completeness"] is not None243    assert d["missing_accounts"]["cost_of_revenue"]["reason"] == "no_mapped_tag"244    ext = {e["tag"] for e in d["custom_extensions"]}245    assert "shak:OperatingMaterialsExpense" in ext246    hint = next(e for e in d["custom_extensions"] if e["tag"] == "shak:OperatingMaterialsExpense")["hint_account"]247    assert hint == "cost_of_revenue"248    r = client.get("/v1/fundamentals/AAPL/coverage")249    d = r.json()["data"]250    assert d["fiscal_year_end"] and d["completeness_by_statement"]["income"] > 80 and d["derived_quarters"] > 0251    assert d["gaps"] == []252253254def test_mapping_and_health(client):255    r = client.get("/v1/fundamentals/_mapping")256    assert r.status_code == 200257    rev = next(a for a in r.json()["data"] if a["account"] == "revenue")258    assert rev["tags"][0]["tag"] == "Revenues" and rev["tags"][1]["priority"] == 2259    r = client.get("/v1/fundamentals/_health")260    d = r.json()["data"]261    assert d["companies"] == 4 and d["statement_versions"] > 200 and d["screener_rows"] == 3262    assert d["jobs"]["backfill"]["companies_done"] == 3 and d["jobs"]["backfill"]["mapping_failure_rate"] is not None263264265def test_openapi_declares_fundamentals(client):266    spec = client.get("/openapi.json").json()267    op = spec["paths"]["/v1/fundamentals/{ticker}/statements"]["get"]268    assert op["tags"] == ["fundamentals"] and "404" in op["responses"] and "FUNDAMENTALS_NOT_AVAILABLE" in op["responses"]["404"]["description"]269    assert "/v1/fundamentals/screener" in spec["paths"] and "/v1/bulk/fundamentals/{year}.parquet" in spec["paths"]270    assert "/v1/fundamentals/_health" not in spec["paths"]271