Lesson 1.3.8 · 50 min

How to choose a market data provider

A provider is not chosen by price or by how convenient its API is, but by the biases it introduces into your results. This lesson measures those biases on a fictional 40-stock index and ends with a weighted decision matrix and an audit you can run on any sample.

Fig. — The same equal-weighted index from 2015 to 2025 with three providers: complete, without delisted stocks and with current members only (annual return at the end of each line)
Level 1 lessons
  1. 1.1 · How markets work

    1. 1.1.1 Market participants and trading venues
    2. 1.1.2 Sessions, auctions, holidays and calendars
    3. 1.1.3 Order book: bid, ask, spread and depth
    4. 1.1.4 Order types
    5. 1.1.5 Costs, settlement, leverage and margin
  2. 1.2 · Asset classes

    1. 1.2.1 Stocks: splits, dividends and corporate actions
    2. 1.2.2 ETFs: replication, tracking error, leveraged ETFs and UCITS
    3. 1.2.3 Futures: specifications, margin, roll and contango
    4. 1.2.4 Forex: pairs, pips, lots, rollover and CFDs
    5. 1.2.5 Options: calls, puts, intrinsic value and time value
    6. 1.2.6 Crypto: spot, perpetuals, funding and custody
    7. 1.2.7 Fixed income and interest rates; CFDs and leveraged products
  3. 1.3 · Market data sources

    1. 1.3.1 Types of market data: EOD, bars, ticks, order book and reference
    2. 1.3.2 Free market data sources
    3. 1.3.3 Retail and prosumer market data APIs
    4. 1.3.4 Professional market data providers
    5. 1.3.5 Crypto and on-chain data
    6. 1.3.6 Brokers as market data sources
    7. 1.3.7 Market data licensing and terms of use
    8. 1.3.8 How to choose a market data provider
  4. 1.4 · Financial data engineering

    1. 1.4.1 Downloading over REST and websockets: pagination, rate limits and retries
    2. 1.4.2 Data cleaning: missing values, bad ticks, gaps and time zones
    3. 1.4.3 Adjusting for splits and dividends: adjusted and unadjusted prices
    4. 1.4.4 Universes without survivorship bias: delistings, mergers and symbol changes
    5. 1.4.5 Building bars: time, tick, volume, dollar and imbalance
    6. 1.4.6 Continuous futures series: roll rules, difference adjustment and ratio adjustment
    7. 1.4.7 Storage: CSV, Parquet, partitioning, DuckDB, TimescaleDB and ArcticDB
    8. 1.4.8 Reproducible pipeline: download, validation, incremental updates and quality tests
  5. 1.5 · Fundamental and macro data

    1. 1.5.1 Financial statements, ratios, XBRL and SEC EDGAR
    2. 1.5.2 Macro data, economic calendars, estimates and transcripts
  6. 1.6 · Financial exploratory analysis

    1. 1.6.1 Returns, volatility, rolling correlations and drawdown
    2. 1.6.2 Stylized facts: fat tails, clustering, autocorrelation and leverage
    3. 1.6.3 Seasonality: day of the week, month, turn of the month and intraday sessions
  7. 1.7 · Programmed technical analysis

    1. 1.7.1 Technical indicators in Python
    2. 1.7.2 Your own implementation versus TA-Lib: validating results
    3. 1.7.3 Candlestick patterns and support and resistance
    4. 1.7.4 Multi-timeframe analysis without look-ahead

Choosing a provider is choosing biases

The previous lessons in the module go through the sources: free (1.3.2), retail APIs (1.3.3), professional (1.3.4), crypto (1.3.5), brokers (1.3.6) and their licenses (1.3.7). All of them sell “market data”, but they do not sell the same thing. Two providers can give the same close for today and, at the same time, ten-year backtest results that differ widely in return, because one keeps the companies that went bankrupt and the other does not, or because one adjusts for splits and the other leaves a false 75% drop.

The useful question is not “which is the best provider?”, but “what does my system need and which defects can I not afford?”. The order of work in this lesson is the one in the figure:

noyesSystem use caseRequirementsKnockout criteria met?DiscardedWeighted matrixAudit sampleDocument
Mermaid source
flowchart TB
  U[System use case] --> R[Requirements]
  R --> E{Knockout criteria met?}
  E -- no --> X[Discarded]
  E -- yes --> P[Weighted matrix]
  P --> M[Audit sample]
  M --> D[Document]

First you write down what the system does (asset, frequency, horizon, whether it trades live). The requirements follow from that. Some are knockout criteria: if the provider does not meet them, the price does not matter. The rest are weighted. And before paying, you test a sample, because a provider's website describes what it sells, not the defects it has.

The criteria, one by one

CriterionWhat to askTypical error if it fails
CoverageWhich markets, assets and venues? Consolidated tape or only the primary exchange?Volumes that do not add up; missing assets (lesson 1.1.1)
Historical depthSince when, and with what granularity in each period?Backtest over a single market regime
Delisted securitiesDoes it include bankruptcies, mergers and removals, with their last price?Survivorship bias (lesson 1.4.4)
Index membersDoes it provide historical composition, with addition and removal dates?Today's universe applied to the past
Corporate adjustmentsDoes it adjust for splits and dividends? By which method? Can you request unadjusted data?False jumps; returns without dividends (lesson 1.4.3)
IdentifiersIs there a permanent identifier besides the ticker?Histories cut by a ticker change
Point-in-timeDoes it keep the versions of each data point with their publication date?Look-ahead bias with fundamentals and macro (lessons 1.5.1 and 1.5.2)
Time zone and sessionsIn which time zone do the timestamps come? Does it include extended hours?Bars shifted by one hour twice a year
Latency and reliabilityReal time, delayed, end of day? What happens if the API goes down?Signals with yesterday's data in a live system
LicensePersonal or professional use? Can you publish results or derived data?Breaching the contract (lesson 1.3.7)
Total costSubscription fee, usage cost, request limits and hours of cleaning“Free” data that costs weeks of work

Not all of them weigh the same in every system. For a daily equity study, delisted securities and adjustments are knockout criteria and latency hardly matters. For an intraday futures system, dividend adjustments do not exist, but the continuous series (lesson 1.4.6), latency and exact timestamps do.

The course universe as a test bench

To measure biases you need to know the truth. The course universe is a fictional 40-stock index from 2015 to 2025 with everything a cheap provider usually lacks: the prices of the 57 tickers that have been in the index (including the bankrupt, the acquired and the removed ones) and the addition and removal dates of each member.

IN [01]python
import pandas as pd

precios = pd.read_parquet("universo_precios.parquet")
miembros = pd.read_csv("universo_miembros.csv",
                       parse_dates=["alta", "baja"])
eventos = pd.read_csv("universo_eventos.csv")

ancho = precios.pivot(index="fecha", columns="ticker",
                      values="cierre")
print(ancho.shape[1], "tickers,", len(ancho), "sessions")
eventos["evento"].value_counts()
OUT [01]Universe prices and members
57 tickers, 2766 sessions
evento
exclusion             7
deslistado_quiebra    4
fusion                4
cambio_simbolo        2
Name: count, dtype: int64

With the addition and removal dates you build the membership mask: True if the security was a member on that day. The portfolio for each session is decided with the members of the previous session (shift(1)), which is what was known at the open.

IN [02]python
fechas = ancho.index
es_miembro = pd.DataFrame(False, index=fechas,
                          columns=ancho.columns)
for t, alta, baja in miembros.itertuples(index=False):
    fin = baja if pd.notna(baja) else pd.Timestamp.max
    es_miembro.loc[(fechas >= alta) & (fechas < fin), t] = True

peso = es_miembro.shift(1, fill_value=False)
rent = ancho.pct_change(fill_method=None)
es_miembro.sum(axis=1).unique()
OUT [02]Point-in-time membership mask
array([40])

Exactly 40 members every day, as the index rule says.

Three providers, three returns

With this data you can simulate three providers that sell the “same” history:

  • Complete (completo). It has the 57 tickers and the membership history. It is the truth of the universe.
  • Without delisted securities (sin_deslistadas). It has the membership history, but only the tickers that trade today: the bankrupt, the acquired and the old tickers are missing. It is the most frequent defect in free sources.
  • Current members only (miembros_actuales). Neither delisted securities nor membership history: the user takes today's 40 members and applies the whole history to them. It is what you do without noticing when you download “the index” from a current list.
IN [03]python
cotiza_hoy = ancho.columns[ancho.iloc[-1].notna()]
actuales = miembros.loc[miembros["baja"].isna(), "ticker"]

proveedores = {
    "completo": rent.where(peso),
    "sin_deslistadas": rent[cotiza_hoy].where(peso[cotiza_hoy]),
    "miembros_actuales": rent[actuales],
}
{k: v.shape[1] for k, v in proveedores.items()}
OUT [03]What each provider has
{'completo': 57, 'sin_deslistadas': 47, 'miembros_actuales': 40}

The portfolio is equal-weighted and rebalanced every day: the index return is the average of the returns of the members the provider has. If a member has no price, it simply does not enter the average, which is what any code that does not check coverage would do.

IN [04]python
def anual(r_dia):
    curva = (1 + r_dia.iloc[1:].fillna(0)).cumprod()
    anos = (curva.index[-1] - curva.index[0]).days / 365.25
    return curva, curva.iloc[-1] ** (1 / anos) - 1

curvas, tabla = {}, {}
for nombre, r in proveedores.items():
    curvas[nombre], cagr = anual(r.mean(axis=1))
    tabla[nombre] = {"final": curvas[nombre].iloc[-1],
                     "anual_pct": 100 * cagr}
pd.DataFrame(tabla).T.round(2)
OUT [04]Annual return by provider
                   final  anual_pct
completo            1.74       5.18
sin_deslistadas     2.21       7.49
miembros_actuales   3.13      10.94

The figure at the top compares the three providers. The same index, the same rule, the same eleven years. With the complete provider, 1 dollar becomes 1.74 (5.18% per year). Without the delisted securities, 2.21 (7.49%). With the current members, 3.13 (10.94%): more than twice the annual return. None of that is an edge of the system: it is the provider.

The reason is the one explained in lesson 1.4.4. Securities leave the index after falling (because of market capitalization, because of bankruptcy) and enter it after rising. A provider without delisted securities erases the losers of the past; using the current members also puts into the past the winners that were not there yet.

It is not enough for the provider to say it has delisted securities. You have to check that it has the ones that matter for your universe: the members of the index at the start of the history.

IN [05]python
ini = miembros.loc[miembros["alta"] == "2015-01-02", "ticker"]
for nombre, r in proveedores.items():
    tiene = ini.isin(r.columns).sum()
    print(f"{nombre:18} {tiene} of {len(ini)}")
OUT [05]2015 members each provider has
completo           40 of 40
sin_deslistadas    30 of 40
miembros_actuales  23 of 40

A current-members-only provider has 23 of the 40 securities the index started with: 42% of the 2015 universe has disappeared from its data or was never there. The provider without delisted securities has 30: it is missing the ones that went bankrupt, the acquired ones and the two old tickers.

Identifiers: the ticker is not the company

Those two old tickers are another criterion. There are two ticker changes in the universe: ZXUV is renamed ZXMA in 2018 and ZXLU becomes ZXBI in 2022. They are the same company, but the data stores them as two series:

IN [06]python
cambios = eventos[eventos["evento"] == "cambio_simbolo"]
for _, ev in cambios.iterrows():
    for t in (ev["ticker"], ev["ticker_nuevo"]):
        s = ancho[t].dropna()
        print(f"{t}: {s.index[0]:%Y-%m-%d} to "
              f"{s.index[-1]:%Y-%m-%d}")
OUT [06]A ticker change splits the history
ZXUV: 2015-01-02 to 2018-06-01
ZXMA: 2018-06-04 to 2025-12-31
ZXLU: 2015-01-02 to 2022-01-07
ZXBI: 2022-01-10 to 2025-12-31

A provider that indexes by ticker and does not link the two stretches gives ZXMA a history that starts in 2018: a company that seems to have gone public that year. Serious providers maintain a permanent identifier (their own or a public standard) that does not change with the ticker, and a ticker table with validity dates. When evaluating them, ask for an example of a ticker change and see how they return it.

Corporate adjustments: know what you are given

The second knockout criterion for equities is adjustments. A provider can give raw prices (the ones that were traded), prices adjusted for splits, adjusted for splits and dividends, or all three. None of them is dangerous: what is dangerous is not knowing which one it is. NOVA's daily series is unadjusted; if it is taken as if it were adjusted:

IN [07]python
nova = pd.read_csv("nova_diario_sin_ajustar.csv",
                   parse_dates=["fecha"], index_col="fecha")
r_nova = nova["cierre"].pct_change()
(100 * r_nova).nsmallest(3).round(2)
OUT [07]NOVA's largest unadjusted moves
fecha
2021-08-23   -75.19
2021-06-02   -12.21
2025-03-26   -12.10
Name: cierre, dtype: float64

A 75.19% drop in one day, with no bankruptcy or news. The audit consists of cross-checking the large jumps against the corporate events:

IN [08]python
ev_nova = pd.read_csv("nova_eventos.csv", parse_dates=["fecha_ex"])

def saltos_con_evento(r, eventos, umbral=0.20):
    grandes = r[r.abs() > umbral]
    return pd.DataFrame({
        "rent_pct": (100 * grandes).round(2),
        "evento": grandes.index.isin(eventos["fecha_ex"]),
    })

saltos_con_evento(r_nova, ev_nova)
OUT [08]Jumps that match a corporate event
            rent_pct  evento
fecha
2021-08-02     23.94   False
2021-08-23    -75.19    True

Of the two moves of more than 20%, only the one on 2021-08-23 falls on an event date: the 4-for-1 split. The other, a 23.94% rise on 2021-08-02, does not match any event: it is a market move and has to stay. A filter that deleted every large jump would also remove that one, which is real. If the previous close is divided by the split ratio, the jump disappears:

IN [09]python
dia = pd.Timestamp("2021-08-23")
previo = nova["cierre"].shift(1).loc[dia]
print(f"Previous close {previo:.2f}, close {nova.loc[dia, 'cierre']}")
print(f"Actual return: "
      f"{nova.loc[dia, 'cierre'] / (previo / 4) - 1:.2%}")
OUT [09]The jump, corrected by the ratio
Previous close 240.00, close 59.55
Actual return: -0.75%

The full adjustment (splits and dividends, multiplicative method) is lesson 1.4.3. To choose a provider it is enough to know three things: whether it adjusts, by which method, and whether it also provides the raw prices and the event table so you can redo the adjustment. A provider that only gives adjusted prices forces you to trust its adjustment; one that gives raw prices and events lets you check it.

Depth: regimes, not rows

Many free or cheap plans limit history to a few years. Two years of daily data are about 500 sessions, which seems like a lot. The problem is that they may be a single type of market:

IN [10]python
pit = proveedores["completo"].mean(axis=1).iloc[1:]
por_ano = (1 + pit).groupby(pit.index.year).prod() - 1
(100 * por_ano).round(1).to_frame("rent_pct").T
OUT [10]Index return year by year
fecha     2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  2025
rent_pct -24.6  18.7  10.5  -9.7  30.1  -9.7  49.3 -29.9  14.2  34.7   3.2
Fig. — Annual return of the course index with the complete provider: a plan with two years of history only sees 2024 and 2025

With only 2024 and 2025 you would see two positive years (+34.7% and +3.2%) and no bear year. The full history has four negative years, among them 2015 (−24.6%) and 2022 (−29.9%). A system that has only been seen in two bull years has not been tested in a downturn. For a daily system, useful depth is the one that covers several regimes: bull, bear, sideways, low and high interest rates. For an intraday system, the number of sessions matters less than the variety of conditions (news days, expirations, half-day sessions).

The decision matrix

With clear criteria, the choice can be written in pandas. Three generic profiles, inspired by the categories in the module (they are not specific providers and the scores are illustrative):

  • A: unofficial free source, end of day, no delisted securities, personal license.
  • B: paid retail API, delisted securities and adjustments, monthly fee.
  • C: professional provider, broad coverage and tick data, high usage-based price.

Scores from 1 to 5 per criterion and two different use cases, each with its weights. First the knockout criteria, then the weighted sum:

IN [11]python
notas = pd.DataFrame({
    "cobertura":    [3, 4, 5],
    "profundidad":  [3, 4, 5],
    "deslistadas":  [1, 4, 5],
    "ajustes":      [3, 4, 4],
    "intradia":     [1, 3, 5],
    "licencia":     [1, 3, 4],
    "coste":        [5, 4, 1],
}, index=["A", "B", "C"])

usos = pd.DataFrame({
    "diario_acciones":  [2, 3, 3, 3, 0, 1, 4],
    "intradia_futuros": [2, 2, 0, 0, 4, 1, 1],
}, index=notas.columns)
pesos = usos / usos.sum()

eliminatorio = {"diario_acciones": ("deslistadas", 3),
                "intradia_futuros": ("intradia", 4)}

def puntuar(uso):
    criterio, minimo = eliminatorio[uso]
    validos = notas[notas[criterio] >= minimo]
    return (validos @ pesos[uso]).round(2)

pd.DataFrame({u: puntuar(u) for u in usos})
OUT [11]Weighted decision matrix
   diario_acciones  intradia_futuros
B             3.94               NaN
C             3.75               4.5
Fig. — Decision matrix: weighted score of three provider profiles for two uses, after applying the knock-out criteria (schematic)

For the daily equity system, A is out before anything is added up: without delisted securities there is no valid study, however cheap it is. Between B and C, B wins (3.94 versus 3.75): C is better at almost everything, but cost weighs a lot and intraday does not count. For the intraday futures system, A and B are out because of the intraday data criterion (NaN in the table) and C wins alone, with 4.5. With the same providers, the choice changes with the use case; that is why the weights are written down before looking at the prices, not after.

The matrix is not an algorithm for deciding but a way of making priorities explicit. If, after seeing the result, you feel like changing the weights, it is worth asking whether the weight was wrong or whether you simply do not like the result.

Audit a sample before paying

Most providers let you try them: a free plan, a trial period or a sample. That sample goes through an automatic audit. With the course universe as an example:

IN [12]python
def auditar(r_dia, universo_inicial, cotiza_hoy):
    tiene = r_dia.columns
    perdidos = sorted(set(universo_inicial) - set(tiene))
    muertos = set(tiene) - set(cotiza_hoy)
    return {
        "tickers": len(tiene),
        "faltan_del_inicio": len(perdidos),
        "deslistados_incluidos": len(muertos),
        "primer_dia": f"{r_dia.index[0]:%Y-%m-%d}",
        "saltos_75pct": int((r_dia.abs() > 0.70).sum().sum()),
    }

pd.DataFrame({k: auditar(v, ini, cotiza_hoy)
              for k, v in proveedores.items()})
OUT [12]Audit of a provider sample
                         completo sin_deslistadas miembros_actuales
tickers                        57              47                40
faltan_del_inicio               0              10                17
deslistados_incluidos          10               0                 0
primer_dia             2015-01-02      2015-01-02        2015-01-02
saltos_75pct                    0               0                 0

Each row is a question asked of the sample: how many securities from the initial universe are missing, whether there is any that no longer trades (if there is none, the database has no delisted securities; in the complete one there are 10, the 8 bankrupt or acquired ones and the 2 old tickers), when it starts, and whether there are impossible jumps. Here no provider has 70% jumps because universo_precios already comes adjusted; the bankrupt ones fall to near zero over several sessions, not all at once. The full list for any provider:

CheckHow
Delisted securitiesAsk for 3 or 4 companies that went bankrupt or were acquired in your market and see whether they are there, up to their last day
Historical membersAsk for an index's composition on an old date and count it
Ticker changesAsk for a security that changed its ticker and see whether the history is joined
AdjustmentsLook for a known split in the prices and see whether there is a jump; ask for raw prices and events
GapsCompare the dates with the market calendar (lesson 1.1.2)
Time zoneLook at the time of the first intraday bar in winter and in summer
VolumeCompare one day's volume with another source: consolidated or only the primary exchange?
LicenseRead what can be published or shared before building anything on top (lesson 1.3.7)

And one last recommendation: store, next to each dataset, which provider it comes from, with which parameters it was requested and when. If a year from now the provider corrects its history, you will know why your backtest has changed (lesson 1.4.8).

Exercises

  1. Repeat the annual return of the “without delisted securities” provider, also removing the securities that enter the index after 2015 (the IPOs and the replacements). Does it go up or down relative to 7.49%? Why?
Solution
IN [13]python
viejos = [t for t in cotiza_hoy if t in set(ini)]
r_viejos = rent[viejos].where(peso[viejos])
_, cagr = anual(r_viejos.mean(axis=1))
print(len(viejos), "tickers;", f"{cagr:.2%} per year")
OUT [13]Without delisted securities and without later additions
30 tickers; 6.05% per year

Without the later additions, only the initial members that still trade remain. The return falls relative to the provider without delisted securities, because the additions entered after rising strongly and kept contributing; but it stays above the complete index, because the losers are missing. Every thing missing from the data moves the result, and not always in the same direction.

  1. In the decision matrix, raise the weight of cost in the diario_acciones use case from 4 to 8. Does the winner change? What happens if you also remove the knockout criterion?
Solution
IN [14]python
usos.loc["coste", "diario_acciones"] = 8
pesos = usos / usos.sum()
print(puntuar("diario_acciones"))
print((notas @ pesos["diario_acciones"]).round(2))
OUT [14]Sensitivity to the weights
B    3.95
C    3.20
dtype: float64
A    3.40
B    3.95
C    3.20
dtype: float64

With cost at 8, B still wins (3.95) and C drops to 3.20. Without the knockout criterion, A, the provider without delisted securities, scores 3.40 thanks to its price and ends up ahead of the professional one. That is exactly what the prior filter prevents: a defect that invalidates the study is not offset by a discount.

  1. Write the requirements and the knockout criteria of a system that trades a European ETF daily with closing data, and say which three audit checks you would run first.
Solution

Requirements: daily closes from the European exchange where the ETF is listed, in its currency, with its holiday calendar; enough history for several regimes; price adjusted for distributions if the ETF is a distributing one (or the net asset value, lesson 1.2.2). Knockout criteria: that it has that ETF on its exchange (not only the US version), the distributions, and a license that allows the intended use. Delisted securities weigh less than in an equity study because the universe is a single product; they matter again if the system chooses among many ETFs, because ETFs also get closed. The first three checks: dates against that exchange's calendar (lesson 1.1.2), distributions against the fund manager's documents, and the time and time zone of the timestamps.

Each answer is folded under its question

Self-assessment

  1. A provider only has the securities that trade today. With it you rebuild an equal-weighted index with its members on each day. What error do you make?

    • AYou overestimate the return: exactly the securities that went bankrupt or were acquired are missing, and they usually leave after falling
    • BNone, because you use the members of each day
    • CYou underestimate the return, because the IPOs are missing
    • DIt only affects volatility, not return
    Show answer

    Answer A. You overestimate the return: exactly the securities that went bankrupt or were acquired are missing, and they usually leave after falling

    The member list is correct, but the ones that disappeared have no prices and the average is computed without them. In the course index, the annual return goes from 5.18% to 7.49%.

  2. A daily series shows a 75.2% drop in one day, with no news. What is the most likely explanation?

    • AA bankruptcy
    • BA 4-for-1 split in unadjusted data
    • CA rounding error
    • DA special dividend
    Show answer

    Answer B. A 4-for-1 split in unadjusted data

    A drop of almost exactly 1 − 1/4 matches a 4-for-1 split. If the series is not adjusted and the backtest takes it as a return, a short system would “earn” 75% that day. That is why jumps have to be cross-checked against corporate events.

  3. Why does the decision matrix start with knockout criteria and not with the weighted sum?

    • ABecause it is faster
    • BBecause the weights are always equal
    • CBecause a high score on price or convenience does not make up for a defect that invalidates the result, such as having no delisted securities for an equity study
    • DBecause the weighted sum does not work with pandas
    Show answer

    Answer C. Because a high score on price or convenience does not make up for a defect that invalidates the result, such as having no delisted securities for an equity study

    The weighted sum allows compensation: a lot of one criterion covers up too little of another. Some defects cannot be compensated; they are filtered out first.

  4. A provider offers two years of history for free. What problem does it have for validating a daily system?

    • ANone, two years are 500 sessions
    • BThat free data is always wrong
    • CThat the format is different
    • DThat two years are usually a single market regime: in the course index, 2024 and 2025 give +34.7% and +3.2%, and there is no bear year like 2022 (−29.9%)
    Show answer

    Answer D. That two years are usually a single market regime: in the course index, 2024 and 2025 give +34.7% and +3.2%, and there is no bear year like 2022 (−29.9%)

    Depth is not measured in rows but in regimes. With two bull years you cannot know how the system behaves in a downturn.

  5. A security changes its ticker in 2018. The provider indexes prices by the current ticker. What happens to its earlier history?

    • ANothing, the ticker does not matter
    • BIt becomes a delisted security
    • CIt depends: if the provider does not link the two tickers with a permanent identifier, the old history stays under the old ticker or is lost
    • DIt is adjusted like a split
    Show answer

    Answer C. It depends: if the provider does not link the two tickers with a permanent identifier, the old history stays under the old ticker or is lost

    In the course universe, ZXUV becomes ZXMA on 2018-06-04: they are two separate series. A provider with a permanent identifier joins them; one that only knows the current ticker gives ZXMA a history that starts in 2018.

Key points

  • Choosing a provider is choosing which biases enter your results: backtest figures are only as good as the data.
  • For equities, the knockout criteria are having delisted securities and membership history. In the course index, without them the annual return goes from 5.18% to 7.49% or to 10.94%.
  • You need to know whether prices come adjusted, by which method, and whether they can be requested unadjusted. An unadjusted split is a false 75% drop.
  • Useful depth is measured in market regimes, not in rows.
  • A decision matrix starts with the knockout criteria and only then weights; the weights depend on the use case.
  • Before paying, audit a sample: delisted securities, jumps against events, gaps, time zone and license.

Resources