AlgoPro UniversityCourse home
Part 3 · Lesson

Getting market data: OHLCV, sources & resolution

Understand what a bar actually contains, where to get it for stocks and crypto, how timeframe changes everything, and why every timestamp you store should be UTC.

Every strategy is downstream of its data. Before we manipulate a single price we need to know exactly what a "bar" is, where honest data comes from, and the two silent killers of beginner datasets: resolution and timezones.

What OHLCV actually is

Market data is almost always delivered as OHLCV bars — one row summarising a fixed slice of time. Each bar carries five numbers:

A candlestick draws all five at once: the body spans open-to-close (green if it closed up, red if down), and the thin wicks reach out to the high and low. One candle tells you not just where price ended but the *fight* that got it there.

Ten daily candles
Each candle is one OHLC bar. A long lower wick (bar 4) means sellers pushed price down but buyers reclaimed it before the close — a different story than a plain red body, even at the same close.

Where the data comes from

Different markets have different plumbing. Three sources cover almost everything you will do as a retail algo trader:

  1. Stocks / ETFs / indices / FXyfinance wraps Yahoo Finance for free daily and (limited) intraday history. Great for learning; for serious work you graduate to a paid vendor.
  2. Cryptoccxt speaks to Binance, Kraken, Coinbase and 100+ others with one interface. Crypto trades 24/7 and exchanges hand you clean OHLCV directly.
  3. Live / your broker — the account you actually trade through (Interactive Brokers, Alpaca, OANDA, an MT5 bridge) has its own API. This is your source of truth once you go live.
pythonPull daily stock bars with yfinance
import yfinance as yf

class="c"># Daily bars for class="n">2 years. auto_adjust=True gives split/dividend-
class="c"># adjusted prices — the correct default for backtesting (Lesson class="n">5).
spy = yf.download(
    class="s">"SPY",
    start=class="s">"class="n">2022-class="n">01-class="n">01",
    end=class="s">"class="n">2024-class="n">01-class="n">01",
    interval=class="s">"1d",
    auto_adjust=True,
    progress=False,
)
print(spy.shape)          class="c"># (rows, columns)
print(spy.columns.tolist())
print(spy.tail(class="n">3).round(class="n">2))
pythonPull crypto bars with ccxt
import ccxt
import pandas as pd

exchange = ccxt.binance()             class="c"># public data needs no API key
raw = exchange.fetch_ohlcv(
    class="s">"BTC/USDT",
    timeframe=class="s">"1h",                   class="c"># hourly bars
    limit=class="n">500,                        class="c"># last class="n">500 hours
)

class="c"># ccxt returns [timestamp_ms, open, high, low, close, volume]
df = pd.DataFrame(raw, columns=[class="s">"ts", class="s">"open", class="s">"high", class="s">"low", class="s">"close", class="s">"volume"])
class="c"># Timestamps are epoch milliseconds in UTC — convert and set as index.
df[class="s">"ts"] = pd.to_datetime(df[class="s">"ts"], unit=class="s">"ms", utc=True)
df = df.set_index(class="s">"ts")
print(df.tail(class="n">3))

Resolution changes the game

The timeframe (also called resolution or bar size) is not a cosmetic choice — it defines what strategy is even possible. A daily bar hides everything that happened inside the day; a 1-minute bar exposes noise that a swing strategy should ignore. As you go finer, you get more bars (more statistical power) but also more noise, more cost, and stricter data-quality demands.

1 day1 hour15 min5 min1 minRoughly how many bars per instrument per year
US equities, regular hours. A 1-minute strategy sees around 390x more bars than a daily one — and 390x more chances for a bad tick, a gap, or a slow backtest.

The UTC rule

The single most common data bug is a timezone mistake. Yahoo returns exchange-local time; ccxt returns UTC; your laptop is on its own clock; daylight-saving shifts move sessions twice a year. Mix these and your "9:30 open" bar lands an hour off — quietly corrupting every intraday signal.

pythonMake timestamps explicit and UTC
import pandas as pd

class="c"># A naive index (no timezone) — dangerous.
naive = pd.to_datetime([class="s">"class="n">2024-class="n">03-class="n">10 class="n">09:class="n">30", class="s">"class="n">2024-class="n">03-class="n">10 class="n">09:class="n">31"])

class="c"># Localize to the exchange zone, THEN convert to UTC for storage.
aware_utc = naive.tz_localize(class="s">"America/New_York").tz_convert(class="s">"UTC")
print(aware_utc)
class="c"># DatetimeIndex([class="s">'class="n">2024-class="n">03-class="n">10 class="n">13:class="n">30:class="n">00+class="n">00:class="n">00', ...], tz=class="s">'UTC')

class="c"># For display only, convert back at the edge:
print(aware_utc.tz_convert(class="s">"America/New_York"))

With clean OHLCV in hand and every timestamp anchored to UTC, we can start actually working with the data. That is pandas — the next lesson.