Point-in-Time Fundamentals and Macro Data for Bias-Free Backtesting
Using restated fundamentals or revised macro data in backtests introduces look-ahead bias, inflating performance. FinzData provides as-of fundamentals and macro vintages to access data as it was first released. This guide shows how to retrieve point-in-time fundamentals with as_of and compare vintage='first' versus vintage='all' for macro series.
Updated 2026-10-11. Code on this page was run against live FinzData data before publishing.
Why Look-Ahead Bias Occurs in Fundamental Data
Fundamental data is often restated after initial filing due to accounting changes, errors, or new guidance. Macro data is revised as more complete information becomes available. Using the latest revised values in a backtest assumes knowledge that was not available on the backtest date, creating look-ahead bias.
For example, a company's revenue for Q1 2023 might be revised upward in Q3 2023. If a backtest uses the revised Q3 value to make a decision in Q1 2023, it unfairly benefits from future information. The same applies to macro indicators like GDP or CPI, which undergo multiple revisions.
To avoid this, backtests must use only data that was publicly available at the time of the decision. FinzData's point-in-time fundamentals and macro vintages enable this by providing data as it was first released or as of a specific date.
Retrieving Point-in-Time Fundamentals with as_of
The fundamentals() method accepts an as_of parameter to return only XBRL facts that had been filed by that date. This prevents access to future restatements. The data includes the filing date (filed) and period end (end) to confirm timing.
For example, to get Apple's revenue as known on June 30, 2025, use as_of='2025-06-30'. This returns facts filed on or before that date, excluding any later amendments. The concept parameter specifies the XBRL tag, such as 'Revenues'.
Without as_of, fundamentals() returns all available facts, including restatements, which should not be used in backtests as it introduces look-ahead bias.
import finzdata as yf
# Needs a look-back bundle or the Institutional plan (free keys get HTTP 403)
c = yf.Client()
df = c.fundamentals(ticker='AAPL', concept='Revenues', as_of='2025-06-30')
print(df[['filed', 'end', 'value']].head())Comparing First-Released and Revised Macro Data
Macro data is released in vintages: 'first' shows the value as initially published, 'latest' shows the most recent revision, and 'all' returns every vintage. Using 'latest' in a backtest risks look-ahead bias if the revision occurred after the backtest date.
To compare, retrieve the same series with vintage='first' and vintage='all'. The 'first' vintage reflects what market participants saw at release. The 'all' vintage includes realtime_start and realtime_end, showing when each value was in effect.
For example, CPIAUCSL (CPI) is revised monthly. Using vintage='first' ensures the backtest uses only the number available at the time, while vintage='all' allows studying revision patterns but must be filtered by as_of to avoid bias.
import finzdata as yf
# Needs a look-back bundle or the Institutional plan (free keys get HTTP 403)
c = yf.Client()
first = c.macro('CPIAUCSL', start='2023-01-01', vintage='first')
all_vintages = c.macro('CPIAUCSL', start='2023-01-01', vintage='all')
print('First release:', first[['date', 'value']].head())
print('All vintages:', all_vintages[['date', 'value', 'realtime_start', 'realtime_end']].head())Using as_of with Macro Data for Point-in-Time Accuracy
The as_of parameter in macro() returns only what was known on a specific date, showing the data available at that time. This is essential for backtests requiring macro indicators as they were first released or as of a decision date.
For example, to get the CPI value known on June 30, 2022, use as_of='2022-06-30'. This returns the latest vintage available by that date, which may be a prior revision if the June data was not yet published.
Note: as_of and vintage='all' require a look-back bundle or Institutional plan, as they access historical revisions not available on the free tier.
import finzdata as yf
# Needs a look-back bundle or the Institutional plan (free keys get HTTP 403)
c = yf.Client()
df = c.macro('CPIAUCSL', as_of='2022-06-30')
print(df[['date', 'value', 'realtime_start']].tail())Practical Example: Backtesting a Fundamental Strategy
Suppose you want to backtest a strategy that buys stocks with year-over-year revenue growth > 10%. Using the latest fundamentals would include restatements, making past growth look stronger than it was known at the time.
Instead, for each rebalance date, retrieve fundamentals with as_of set to that date. This ensures the revenue figure used was actually available when the decision was made. Repeat for macro filters like GDP growth or CPI.
This approach eliminates look-ahead bias from both corporate restatements and macro revisions, yielding more realistic performance estimates.
import finzdata as yf
import pandas as pd
# Needs a look-back bundle or the Institutional plan (free keys get HTTP 403)
c = yf.Client()
def get_revenue_as_of(ticker, as_of_date):
df = c.fundamentals(ticker=ticker, concept='Revenues', as_of=as_of_date)
if df.empty:
return None
# Get annual revenue (assuming 10-K, fp='FY')
annual = df[df['fp'] == 'FY']
if annual.empty:
return None
return annual.iloc[0]['value']
# Example usage for AAPL as of 2023-12-31
revenue = get_revenue_as_of('AAPL', '2023-12-31')
print(f'AAPL revenue as of 2023-12-31: {revenue}')Checking for Look-Ahead Bias in Your Backtest
To verify your backtest avoids look-ahead bias, audit each data source: confirm fundamentals use as_of, macro uses vintage='first' or as_of.
A useful check is to compare results using vintage='first' versus vintage='latest' for macro data. If performance significantly improves with 'latest', look-ahead bias is likely present.
Similarly, compare fundamentals with and without as_of. If the strategy performs better without as_of, it is likely benefiting from future restatements.
Limitations and Data Availability
Point-in-time fundamentals (as_of) and macro vintages (vintage='all', as_of) require a look-back bundle or Institutional plan. The free tier provides only latest fundamentals and latest macro values, which are unsuitable for backtesting due to look-ahead bias risk.
Hourly intraday bars are available with any look-back bundle, while 1-minute and 5-minute bars require 25 years of history or an Institutional plan. Daily bars for the last 12 months are free.
Always verify the filing date (filed) in fundamentals and realtime_start in macro to ensure data was available at your backtest timestamp. Never assume the latest value was known in the past.
Questions
What is the difference between vintage='first' and vintage='all' in macro data?
vintage='first' returns the value as initially released, with no revisions. vintage='all' returns every vintage ever published for that series, including realtime_start and realtime_end to show when each value was in effect.
Can I use the free plan for backtesting with point-in-time fundamentals?
No. The free plan only provides latest fundamentals and latest macro values. as_of and vintage='all' require a look-back bundle or Institutional plan to access historical revisions and filing timestamps.
How do I know if a fundamental fact was available on a specific backtest date?
Check the 'filed' column in the fundamentals output. If filed <= backtest date, the fact was publicly available by that date. The 'end' column shows the period the fact refers to (e.g., quarter end).
Keep going
- Macro series with vintages
- 13F Holdings API Python Guide
- Accessing CFTC Commitments of Traders Data with FinzData Python
- Migrating from yfinance to FinzData
- Survivorship Bias Free Stock Data Download
- Download 1-minute, 5-minute and Hourly Intraday Stock Data
Get a free API key See pricing
Data for research, not investment advice.