Lesson 1.3.2 · 50 min

Free market data sources

For learning and for research there is plenty of free data, but not all of it is alike: some sources are official and stable, others depend on a website that does not offer them as a service. This lesson goes through seven, with their access rules read in their own documentation, and processes the response format of each API.

Fig. — Which kind of data each free source in the lesson provides (Stooq is left out because its direct download did not work on 21/10/2026) (schematic)
02/08
Module
1.3 · Market data sources
Duration
50 min
Updated
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

Two kinds of “free”

There is a lot of free financial data, and almost everyone who learns starts with it. But under the same word there are two very different things:

  • Official services: a central bank, a regulator or a statistics agency publishes its data with a documented API, terms of use and an intention to maintain it. FRED (Federal Reserve Bank of St. Louis), EDGAR (SEC), the ECB, Banco de España (Spain's central bank) and the CNMV (Spain's securities regulator) are of this kind.
  • Unofficial tools: a library reads the data from a website that does not offer it as a service to third parties. yfinance is the best known. It works, and well, until the day the website changes.

The difference matters for a system. An official service can change version, but it announces and documents it. An unofficial tool can stop working without warning, change its format or return request limits that nobody has documented.

What do you need?PricesMacro and ratesCompaniesyfinanceFREDECBBanco de EspañaSEC EDGARCNMV
Mermaid source
flowchart LR
  Q{What do you need?} --> P[Prices]
  Q --> M[Macro and rates]
  Q --> E[Companies]
  P --> Y[yfinance]
  M --> F[FRED]
  M --> B[ECB]
  M --> BE[Banco de España]
  E --> S[SEC EDGAR]
  E --> C[CNMV]

The matrix in the header summarizes what type of data each one provides. The gaps matter as much as the squares: no free official source provides stock prices with long history and reliable corporate action adjustments. That is what the providers of lessons 1.3.3 and 1.3.4 are for.

yfinance: useful for learning, fragile for trading

yfinance is an open-source library (Apache license) that downloads prices, dividends, splits and some company data from Yahoo Finance. Its README is explicit1Note 1 · yfinance README and documentation, accessed on October 21, 2026; version 1.7.0 on PyPI.: it is not affiliated with, endorsed or vetted by Yahoo; it uses the APIs that Yahoo exposes publicly; it is intended for research and educational purposes; and you have to check Yahoo's terms of use to know what rights you have over the downloaded data, bearing in mind that the Yahoo Finance API is intended for personal use only. Lesson 1.3.7 explains what that means when publishing or sharing results.

IN [01]yfinance_descarga.pypython
import yfinance as yf                # uv add yfinance (needs network)

aapl = yf.Ticker("AAPL")
diario = aapl.history(period="1mo")
varios = yf.download(["MSFT", "AAPL"], start="2025-01-01",
                     end="2025-12-31", auto_adjust=True)

Three things worth knowing before using it:

  • It adjusts by default. download has auto_adjust=True: prices come adjusted for dividends and splits. If they are compared with traded prices from another source, they do not match (lessons 1.3.8 and 1.4.3).
  • Undocumented limits. The README says nothing about request limits, but the code defines a YFRateLimitError exception (“Too Many Requests”) raised when Yahoo responds with 429. A download of hundreds of tickers in a row can be cut off halfway.
  • Frequent releases. Version 1.7.0 is from August 26, 2026, after two others in July and August. When Yahoo changes something, the library has to adapt; pin the version in pyproject.toml (lesson 0.2.2).

FRED: macro and rates, with versions

FRED, from the Federal Reserve Bank of St. Louis, gathers economic series from many agencies: interest rates, inflation, employment, exchange rates. Its API requires a free key (a 32-character string) in every request and allows up to 120 requests per minute; above that it responds with 429, and not respecting the limit can end in a temporary block2Note 2 · FRED API: series/observations, API key and errors (“Up to 120 requests per minute”), accessed on October 21, 2026..

IN [02]fred_observaciones.pypython
import os
from urllib.parse import urlencode
from urllib.request import urlopen

params = {"series_id": "DGS10", "file_type": "json",
          "observation_start": "2025-01-01",
          "api_key": os.environ["FRED_API_KEY"]}
url = ("https://api.stlouisfed.org/fred/series/observations?"
       + urlencode(params))
with urlopen(url, timeout=10) as r:
    datos = r.read()

The JSON response has query metadata (realtime_start, realtime_end, observation_start, count, offset, limit…) and an observations list with one dictionary per date: date, value and the real-time period during which that value was current. The detail that causes the most errors: value arrives as text. An illustrative sample with that structure:

IN [03]python
import json

import pandas as pd

fred_txt = """{
  "realtime_start": "2026-10-21", "realtime_end": "2026-10-21",
  "observation_start": "2025-01-01", "count": 3, "offset": 0,
  "limit": 100000,
  "observations": [
    {"realtime_start": "2026-10-21", "realtime_end": "2026-10-21",
     "date": "2025-01-02", "value": "4.57"},
    {"realtime_start": "2026-10-21", "realtime_end": "2026-10-21",
     "date": "2025-01-03", "value": "4.60"},
    {"realtime_start": "2026-10-21", "realtime_end": "2026-10-21",
     "date": "2025-01-06", "value": "4.62"}
  ]
}"""
fred = json.loads(fred_txt)
obs = pd.DataFrame(fred["observations"])
print(obs.dtypes.to_dict())
obs["date"] = pd.to_datetime(obs["date"])
obs["value"] = pd.to_numeric(obs["value"], errors="coerce")
obs[["date", "value"]]
OUT [03]FRED response: values as text
{'realtime_start': <StringDtype(na_value=nan)>, 'realtime_end': <StringDtype(na_value=nan)>, 'date': <StringDtype(na_value=nan)>, 'value': <StringDtype(na_value=nan)>}
        date  value
0 2025-01-02   4.57
1 2025-01-03   4.60
2 2025-01-06   4.62

Without to_numeric, obs["value"].mean() would fail and obs["value"] + obs["value"] would concatenate strings. With errors="coerce", any value that is not a number becomes NaN instead of breaking the conversion.

The two realtime_* fields are the door to one of FRED's most valuable features: vintages. Macro series get revised (GDP, employment); realtime_start, realtime_end and vintage_dates let you request the data as it existed on a past date, the basis of ALFRED, FRED's version archive. With an illustrative sample of a revised series:

IN [04]python
versiones = pd.DataFrame({
    "date": ["2025-03-01", "2025-03-01", "2025-03-01"],
    "value": [151.0, 228.0, 205.0],
    "realtime_start": ["2025-04-04", "2025-05-02", "2025-06-06"],
    "realtime_end": ["2025-05-01", "2025-06-05", "9999-12-31"],
})

def conocido_el(v, fecha):
    desde, hasta = v["realtime_start"], v["realtime_end"]
    vigente = (desde <= fecha) & (hasta >= fecha)
    return v.loc[vigente, ["date", "value"]]

for f in ["2025-04-10", "2025-05-20", "2026-01-01"]:
    print(f, conocido_el(versiones, f).to_dict("records"))
OUT [04]Which value was known on each date
2025-04-10 [{'date': '2025-03-01', 'value': 151.0}]
2025-05-20 [{'date': '2025-03-01', 'value': 228.0}]
2026-01-01 [{'date': '2025-03-01', 'value': 205.0}]

The same March figure was 151 for whoever looked at it in April, 228 in May and 205 from June on. A backtest that uses today's series is using in April a 205 that nobody knew. Dates are compared as text because they are in ISO format (YYYY-MM-DD), which sorts the same as dates; 9999-12-31 marks “still current”. Lesson 1.5.2 works through this with full macro data.

A license warning: FRED gathers series from many agencies, and those carrying a copyright notice belong to third parties with their own restrictions; to use them beyond personal use you have to ask their owner for permission3Note 3 · FRED: Legal Notices, Information and Disclaimers and FRED API Terms of Use..

SEC EDGAR: US fundamentals and filings

EDGAR is the SEC system where US listed companies file their reports (10-K, 10-Q, 8-K…). Its JSON APIs at data.sec.gov require no key or authentication4Note 4 · SEC: EDGAR Application Programming Interfaces.; the two most useful:

  • https://data.sec.gov/submissions/CIK##########.json: a company's filing history.
  • https://data.sec.gov/api/xbrl/companyfacts/CIK##########.json: all of the company's XBRL accounting data.

########## is the CIK (the SEC's Central Index Key) with ten digits, leading zeros included. In exchange for not requiring a key, the SEC sets fair access rules5Note 5 · SEC: Accessing EDGAR Data, accessed on October 21, 2026.: a current maximum of 10 requests per second and declaring a User-Agent with the requester's name and a contact email, with this sample format: User-Agent: Sample Company Name AdminContact@<sample company domain>.com. Requests without it can receive a 403.

IN [05]python
def url_submissions(cik):
    return f"https://data.sec.gov/submissions/CIK{int(cik):010d}.json"

CABECERAS = {
    "User-Agent": "My Research Project [email protected]",
    "Accept-Encoding": "gzip, deflate",
}
print(url_submissions(320193))
print(url_submissions("0000320193"))
OUT [05]Ten-digit CIK and declared headers
https://data.sec.gov/submissions/CIK0000320193.json
https://data.sec.gov/submissions/CIK0000320193.json
IN [06]edgar_submissions.pypython
import gzip
import json
from urllib.request import Request, urlopen

req = Request(url_submissions(320193), headers=CABECERAS)
with urlopen(req, timeout=10) as r:
    crudo = r.read()
    if r.headers.get("Content-Encoding") == "gzip":
        crudo = gzip.decompress(crudo)
sub = json.loads(crudo)

The submissions response has company data (cik, name, tickers, exchanges…) and, in filings.recent, the recent filings in columnar format: a dictionary of parallel lists (accessionNumber, filingDate, reportDate, form, primaryDocument…), not a list of records. That is exactly what pd.DataFrame expects. With an illustrative sample of that structure, for a fictitious company:

IN [07]python
sub = {
    "cik": "0000000042", "name": "NOVA CORP", "tickers": ["NOVA"],
    "filings": {"recent": {
        "accessionNumber": ["0000000042-25-000031",
                            "0000000042-25-000027",
                            "0000000042-25-000012"],
        "filingDate": ["2025-11-05", "2025-10-24", "2025-08-06"],
        "reportDate": ["2025-09-30", "", "2025-06-30"],
        "form": ["10-Q", "8-K", "10-Q"],
        "primaryDocument": ["nova-10q.htm", "nova-8k.htm",
                            "nova-10q.htm"],
    }},
}
pres = pd.DataFrame(sub["filings"]["recent"])
pres["filingDate"] = pd.to_datetime(pres["filingDate"])
pres[pres["form"].isin(["10-K", "10-Q"])][
    ["form", "reportDate", "filingDate"]]
OUT [07]A company's filings, from columns to rows
   form  reportDate filingDate
0  10-Q  2025-09-30 2025-11-05
2  10-Q  2025-06-30 2025-08-06

The two dates are the key to using fundamentals without look-ahead bias: reportDate is the end of the period (September 30) and filingDate, the day the report was made public (November 5). A backtest can only use the quarter's figures from the second one on (lesson 1.5.1).

Respecting EDGAR's rate

Ten requests per second seem like a lot until you download thousands of documents in a loop. The simplest loop fires requests as fast as the network responds and exceeds the limit in the first second. The solution is a rate limiter that waits as long as needed between requests. To run it without a network, with a virtual clock in integer milliseconds (so there are no rounding errors in the comparisons) in which each request takes 20 ms to respond:

yesnoRequest10 in 1 s?Waitdata.sec.gov
Mermaid source
flowchart LR
  P[Request] --> L{10 in 1 s?}
  L -- yes --> W[Wait]
  W --> L
  L -- no --> S[data.sec.gov]
IN [08]python
from collections import deque

def simular(n, por_segundo=None, latencia_ms=20):
    t, hechas, horas = 0, deque(), []      # integer milliseconds
    for _ in range(n):
        if por_segundo is not None:
            while hechas and t - hechas[0] >= 1000:
                hechas.popleft()
            if len(hechas) >= por_segundo:
                t = hechas[0] + 1000
                hechas.popleft()
        hechas.append(t)
        horas.append(t)
        t += latencia_ms
    return pd.Series(horas)

def peor_segundo(ms):
    return max(((ms >= h) & (ms < h + 1000)).sum() for h in ms)

sin_limite, con_limite = simular(100), simular(100, por_segundo=10)
print("without limiter:", f"{sin_limite.iloc[-1] / 1000:.2f} s,",
      "max in one second:", peor_segundo(sin_limite))
print("with limiter:", f"{con_limite.iloc[-1] / 1000:.2f} s,",
      "max in one second:", peor_segundo(con_limite))
OUT [08]Limiter of 10 requests per second, on a virtual clock
without limiter: 1.98 s, max in one second: 50
with limiter: 9.18 s, max in one second: 10
Fig. — One hundred EDGAR requests on a virtual clock, with and without a 10-requests-per-second limiter (20 ms response)

Without a limiter, the 100 requests go out in 2 seconds and the worst second has 50: five times the maximum. With the limiter they take 9.18 seconds and there are never more than 10 in any one-second window. It is slower, but it is the difference between a download that finishes and a blocked IP. Lesson 1.4.1 generalizes the limiter and adds retries.

ECB and Banco de España: European data without a key

The ECB Data Portal offers an SDMX 2.1 web service with its entry point at data-api.ecb.europa.eu/service/. Each series is identified by a key that joins the values of its dimensions with dots: the daily reference exchange rate of the dollar against the euro is EXR/D.USD.EUR.SP00.A (dataset EXR; frequency D, currency USD, reference currency EUR, type SP00, variant A). It accepts csvdata and jsondata, among other formats, and parameters such as startPeriod or lastNObservations6Note 6 · ECB Data Portal: API, Overview and Data.. It requires no key.

IN [09]bce_exr.pypython
url = ("https://data-api.ecb.europa.eu/service/data/EXR/"
       "D.USD.EUR.SP00.A?lastNObservations=3&format=csvdata")
bce = pd.read_csv(url)

The CSV response has one row per observation and more than thirty columns: the key's dimensions, the date (TIME_PERIOD), the value (OBS_VALUE) and many metadata fields (observation status, title, unit, decimals…). A sample with the main columns and the three values the previous query returned on October 21, 2026:

IN [10]python
from io import StringIO

bce_csv = """KEY,FREQ,CURRENCY,TIME_PERIOD,OBS_VALUE,OBS_STATUS,TITLE
EXR.D.USD.EUR.SP00.A,D,USD,2026-10-16,1.1253,A,US dollar/Euro
EXR.D.USD.EUR.SP00.A,D,USD,2026-10-19,1.1225,A,US dollar/Euro
EXR.D.USD.EUR.SP00.A,D,USD,2026-10-20,1.1215,A,US dollar/Euro
"""
bce = pd.read_csv(StringIO(bce_csv), parse_dates=["TIME_PERIOD"])
eurusd = bce.set_index("TIME_PERIOD")["OBS_VALUE"].rename("EURUSD")
print(eurusd)
print(f"Change: {eurusd.pct_change().iloc[-1]:.3%}")
OUT [10]ECB CSV: keeping what you need
TIME_PERIOD
2026-10-16    1.1253
2026-10-19    1.1225
2026-10-20    1.1215
Name: EURUSD, dtype: float64
Change: -0.089%

The ECB reference rate is a figure of the day (the series title itself places it at 14:15 CET), not a tradable price: it is useful for valuation, for converting currencies in a report or for macro, not for an intraday currency system (lesson 1.2.4). Its reuse is free under two conditions: citing the source and not modifying the statistics or their metadata; third-party data published by the ECB needs permission from whoever produces it7Note 7 · ECB: Policy regarding the reuse of ESCB statistics..

Banco de España has a JSON web service that gives access to all the information in its statistics section and in the HERMES application, with two types of request: the latest figure of one or several series (favoritas) and a listing of the series over a range (listaSeries). For bulk downloads it also offers all the time series CSV files in a ZIP. The series D_1NBAF472 is the one-year Euribor8Note 8 · Banco de España: Statistics web service (API) and time-series bulk download, accessed on October 21, 2026.:

IN [11]bde_ultimo_dato.pypython
url = ("https://app.bde.es/bierest/resources/srdatosapp/favoritas"
       "?idioma=es&series=D_1NBAF472")
IN [12]python
bde_txt = """[{"serie": "D_1NBAF472",
  "descripcionCorta": "Euribor a un año", "codFrecuencia": "M",
  "decimales": 3, "simbolo": "%", "tendencia": "+",
  "fechaValor": "2026-09-01T08:15:00Z", "valor": 3.247}]"""
bde = pd.DataFrame(json.loads(bde_txt))
bde["fechaValor"] = pd.to_datetime(bde["fechaValor"])
bde[["serie", "descripcionCorta", "fechaValor", "valor", "simbolo"]]
OUT [12]Latest figure from Banco de España
        serie  descripcionCorta                fechaValor  valor simbolo
0  D_1NBAF472  Euribor a un año 2026-09-01 08:15:00+00:00  3.247       %

This is the response the service returned on October 21, 2026: the monthly figure for September 2026, 3.247%. The fechaValor timestamp is in UTC (Z) and the frequency is monthly (M): it is worth keeping both, because a monthly series joined to daily prices needs an explicit alignment rule (lesson 1.4.2).

CNMV: regulatory information

The CNMV has no documented public API like the previous ones, but it publishes useful information on its website, in searches and downloadable files9Note 9 · CNMV: short positions, other relevant information, official registers and statistics.:

  • Short positions: notifications of net short positions in Spanish stocks, with a downloadable file of the published and historical ones. Useful for studying bearish pressure or short squeeze risk.
  • Other relevant information: issuers' announcements (formerly “hechos relevantes”, material events, until February 2020), notifications from executives and significant shareholdings.
  • Official registers of issuers and listed companies, and statistics on issuers, funds and investment firms.

Since there is no API, access is by downloading files or reading pages, and any automation has to respect the website's terms of use. It is information for research and context, not a feed.

A common format for different sources

Each source returns a different format. Before storing, it is best to bring everything to a common shape, with the source and the series as columns, so that the rest of the code does not depend on who provided each figure:

IN [13]python
largo = pd.concat([
    pd.DataFrame({"fuente": "FRED", "serie": "DGS10",
                  "fecha": obs["date"], "valor": obs["value"]}),
    pd.DataFrame({"fuente": "ECB", "serie": "EXR.D.USD.EUR",
                  "fecha": eurusd.index, "valor": eurusd.values}),
    pd.DataFrame({"fuente": "BdE", "serie": bde["serie"],
                  "fecha": bde["fechaValor"].dt.tz_localize(None)
                  .dt.normalize(), "valor": bde["valor"]}),
], ignore_index=True)
print(largo.dtypes.to_dict())
largo.groupby(["fuente", "serie"])["fecha"].agg(["min", "max", "size"])
OUT [13]The three series in a common long format
{'fuente': <StringDtype(na_value=nan)>, 'serie': <StringDtype(na_value=nan)>, 'fecha': dtype('<M8[us]'), 'valor': dtype('float64')}
                            min        max  size
fuente serie
BdE    D_1NBAF472    2026-09-01 2026-09-01     1
ECB    EXR.D.USD.EUR 2026-10-16 2026-10-20     3
FRED   DGS10         2025-01-02 2025-01-06     3

The long format (one row per source, series and date) is easy to store in Parquet, to filter and to pivot when needed (lesson 1.4.7). What is lost when normalizing (the units, the frequency, the status of each observation) is stored separately, in a metadata table per series: without it, a year from now nobody will know whether 3.247 was percentage points or a fraction.

Exercises

  1. Add to versiones a fourth release of the March figure, 199.0, current from 2025-09-05. Adjust the realtime_end of the previous one and check which value was known on 2025-07-01 and on 2025-10-01.
Solution
IN [14]python
v2 = versiones.copy()
v2.loc[2, "realtime_end"] = "2025-09-04"
v2.loc[3] = ["2025-03-01", 199.0, "2025-09-05", "9999-12-31"]
for f in ["2025-07-01", "2025-10-01"]:
    print(f, conocido_el(v2, f)["value"].tolist())
OUT [14]One more revision
2025-07-01 [205.0]
2025-10-01 [199.0]

On July 1, 205 was still current; from September 5, 199. Each revision closes the period of the previous one: if you forget to adjust realtime_end, two versions appear to be current at the same time and conocido_el returns two values.

  1. With simular, what is the maximum number of requests per second that would go out if each EDGAR response took 150 ms and there were no limiter? Is the limiter needed then?
Solution
IN [15]python
lentas = simular(100, latencia_ms=150)
print("max in one second:", peor_segundo(lentas))
OUT [15]With slower responses
max in one second: 7

With 150 ms per response, a sequential loop does not exceed 7 requests per second: below the limit. But the limiter is still needed: latency changes (a cache, another network) and, as soon as the code is parallelized with threads, the count multiplies. The limit is enforced in the code, not entrusted to the slowness of the network.

  1. You are going to study whether the spread between the one-year Euribor and the US 10-year bond anticipates anything in EURUSD. Say which sources you would take each series from, at what frequency they come and what you would have to resolve to join them.
Solution

One-year Euribor: Banco de España (or the ECB), monthly. US 10-year bond yield: FRED, daily. EURUSD: the ECB's daily reference rate (EXR), a closing figure from mid-afternoon in Frankfurt. To join them you have to decide the common frequency (monthly, if the Euribor is monthly), which daily value represents each month (the last one known at month end, not the average that is only known afterwards), the times of each figure (the ECB rate is set before the New York close) and, if revised data is used, the version that was known on each date (FRED vintages). In addition, cite the sources and check the terms of use of each series (lesson 1.3.7).

Footnotes

  1. yfinance README and documentation, accessed on October 21, 2026; version 1.7.0 on PyPI. ↩

  2. FRED API: series/observations, API key and errors (“Up to 120 requests per minute”), accessed on October 21, 2026. ↩

  3. FRED: Legal Notices, Information and Disclaimers and FRED API Terms of Use. ↩

  4. SEC: EDGAR Application Programming Interfaces. ↩

  5. SEC: Accessing EDGAR Data, accessed on October 21, 2026. ↩

  6. ECB Data Portal: API, Overview and Data. ↩

  7. ECB: Policy regarding the reuse of ESCB statistics. ↩

  8. Banco de España: Statistics web service (API) and time-series bulk download, accessed on October 21, 2026. ↩

  9. CNMV: short positions, other relevant information, official registers and statistics. ↩

Each answer is folded under its question

Self-assessment

  1. What does the yfinance README itself say about its relationship with Yahoo?

    • AThat it is Yahoo Finance's official API
    • BThat the data is in the public domain
    • CThat it has a commercial agreement with Yahoo
    • DThat it is not affiliated with, endorsed or vetted by Yahoo, that it is meant for research and education and that you must check Yahoo's terms on the use of the data
    Show answer

    Answer D. That it is not affiliated with, endorsed or vetted by Yahoo, that it is meant for research and education and that you must check Yahoo's terms on the use of the data

    yfinance is an open-source tool that uses Yahoo's publicly available APIs. The README refers to Yahoo's terms of use and reminds you that its API is intended for personal use.

  2. What does the SEC require to download from EDGAR in an automated way?

    • ADeclaring a User-Agent with a name and contact email and not exceeding 10 requests per second
    • BA paid API key
    • CRegistering as a professional
    • DNothing
    Show answer

    Answer A. Declaring a User-Agent with a name and contact email and not exceeding 10 requests per second

    The “Accessing EDGAR Data” page sets a current maximum of 10 requests per second and asks you to declare the User-Agent. The data.sec.gov APIs need no key.

  3. In the FRED API, the value field of each observation arrives as "4.33". What do you have to do?

    • ANothing, it is already a number
    • BConvert it with pd.to_numeric(..., errors="coerce"), because it is text and there may be non-numeric values
    • CMultiply it by 100
    • DRead it as a date
    Show answer

    Answer B. Convert it with pd.to_numeric(..., errors="coerce"), because it is text and there may be non-numeric values

    FRED's JSON brings the values in quotes. If they are not converted, arithmetic operations fail or concatenate text.

  4. What does FRED's realtime_start / realtime_end parameter allow?

    • ARequesting real-time data
    • BChanging the time zone
    • CRequesting the data as it was known on a past date, with later revisions excluded (ALFRED)
    • DLimiting the number of observations
    Show answer

    Answer C. Requesting the data as it was known on a past date, with later revisions excluded (ALFRED)

    Macro series get revised. With the real-time period (or vintage_dates) you get the versions that existed on each date, which is what a backtest without look-ahead bias needs (lesson 1.5.2).

  5. What condition does the ECB set for reusing its public statistics?

    • ACiting the source and not modifying the statistics (or their metadata); third-party data needs its author's permission
    • BPaying for a license
    • CNon-commercial use only
    • DNone
    Show answer

    Answer A. Citing the source and not modifying the statistics (or their metadata); third-party data needs its author's permission

    The ESCB statistics reuse policy allows free reuse citing the source (“Source: ECB statistics.”) and without modifying the data.

Key points

  • “Free” covers two different things: official services with a documented API (FRED, EDGAR, ECB, Banco de España, CNMV) and unofficial tools that read a website (yfinance).
  • yfinance is not affiliated with Yahoo, adjusts prices by default (auto_adjust=True) and can return rate-limit errors (YFRateLimitError): it is fine for learning, not as the foundation of a system.
  • Each official source has its rules: FRED requires a key and allows up to 120 requests per minute; EDGAR, a declared User-Agent and 10 per second; the ECB, citing the source.
  • Responses have to be typed: values as text in FRED, lists in columns in EDGAR, dozens of metadata columns in the ECB's CSV.
  • Sources change: Stooq's direct CSV download, widely used for years, returned a browser check instead of the file on October 21, 2026.

Resources