Point-in-Time Backtesting
Construct survivorship-bias-free factor portfolios using accepted_at timestamps and PIT universe snapshots.
The Look-Ahead Bias Problem
Most financial datasets don't tell you WHEN a data point became available. If you use a company's 2023 annual results (filed March 2024) in a January 2024 backtest, you've cheated — that information wasn't available yet. Valuein's accepted_at field records the exact SEC acceptance timestamp for every fact, enabling rigorous PIT backtesting.
Filtering by accepted_at
Always filter by accepted_at <= rebalance_date when selecting signals for a historical portfolio.
from valuein_sdk import ValueinClient, ValueinError rebalance_date = "2022-01-01" try: with ValueinClient() as client: # PIT-safe: each company's latest annual revenue the SEC had # accepted by the rebalance date revenue_pit = client.run_query(f""" SELECT entity_id, period_end, numeric_value AS revenue, accepted_at FROM fact WHERE standard_concept = 'TotalRevenue' AND fiscal_period = 'FY' AND accepted_at <= TIMESTAMP '{rebalance_date}' QUALIFY ROW_NUMBER() OVER ( PARTITION BY entity_id ORDER BY period_end DESC, accepted_at DESC ) = 1 """) print(revenue_pit.head()) except ValueinError as e: print(f"Error: {e}")Survivorship-Bias-Free Universe
Never use today's S&P 500 list for historical backtests. Use `client.pit_universe()` in the SDK (or the MCP `get_pit_universe` tool) to get the constituents as of each rebalance date.
from valuein_sdk import ValueinClient, ValueinError # MCP: get the S&P 500 members on Jan 1, 2020# get_pit_universe(as_of_date="2020-01-01", index="SP500") # Python SDK: pit_universe() reads index_membership (keyed on cik) with the# half-open effective_date / removal_date window, delisted members included.try: with ValueinClient() as client: universe_2020 = client.pit_universe("2020-01-01", index="SP500") print(f"S&P 500 universe on 2020-01-01: {len(universe_2020)} companies") print(universe_2020[["cik", "company_name", "ticker_at_date"]].head()) except ValueinError as e: print(f"Error: {e}")Full Monthly Rebalance Loop
Combine PIT universe + PIT signals to build a proper factor backtest.
import pandas as pdfrom valuein_sdk import ValueinClient, ValueinError rebalance_dates = pd.date_range("2018-01-01", "2023-12-01", freq="QS")portfolio_returns = [] try: with ValueinClient() as client: for date in rebalance_dates: date_str = date.strftime("%Y-%m-%d") # Step 1: PIT universe on this date (delisted members included) universe = client.pit_universe(date_str) if universe.empty: continue # Step 2: latest annual revenue known on this date — fact.entity_id == cik ids = ",".join(f"'{cik}'" for cik in universe["cik"]) signals = client.run_query(f""" SELECT entity_id, numeric_value AS revenue, accepted_at FROM fact WHERE standard_concept = 'TotalRevenue' AND fiscal_period = 'FY' AND accepted_at <= TIMESTAMP '{date_str}' AND entity_id IN ({ids}) QUALIFY ROW_NUMBER() OVER ( PARTITION BY entity_id ORDER BY period_end DESC, accepted_at DESC ) = 1 """) # Step 3: rank by revenue (simplified) signals["rank"] = signals["revenue"].rank(pct=True) top_quintile = signals[signals["rank"] > 0.8] portfolio_returns.append({"date": date, "n": len(top_quintile)}) print(pd.DataFrame(portfolio_returns).tail()) except ValueinError as e: print(f"Error: {e}")Up next
Building a Financial Agent with MCP
Configure Claude or Cursor to query SEC data via the Valuein MCP Server. Write prompts that generate investment research, screen for opportunities, and analyze risk.