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 exact constituents at each rebalance date.
from valuein_sdk import ValueinClient, ValueinError
# MCP: get the exact S&P 500 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 pd
from 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.