Tasilab
Home Pricing Docs EN · عربي Sign in Start free →
Home / Guides / TASI historical data

How to get TASI historical data.

Where to find Saudi Exchange (Tadawul) historical prices in Python — an honest map of the options, then clean OHLCV bars in three lines with Tasilab.

Any backtest, indicator, or model needs history. For the Saudi market there are a few real ways to get it, and the right one depends on what you're doing. US-market tooling doesn't map cleanly here — Tadawul-listed names use a 4-digit code, most of the free ecosystem built around yfinance/pandas-datareader was written with NYSE/NASDAQ tickers in mind, and licensed Saudi market-data vendors are a much smaller field than their US counterparts. Here's the honest version of what's actually available — no single source wins for every job.

Your options

SourceBest forWatch out for
Licensed data API (e.g. SAHMK)Production apps needing raw, redistributable, comprehensive TASI data + fundamentalsPaid tiers; you build the storage/backtest layer yourself
yfinanceQuick, free exploration in a notebook (2222.SR)Delayed, coverage/quality gaps on some TASI names, unofficial
Kaggle datasetsOne-off study on a fixed windowStatic snapshots that go stale; no live updates
R tasi packageR users wanting prices + financial statementsR ecosystem, not Python
TasilabBacktesting & paper trading in one sandbox — bars + orders + experiments together15-min delayed on free tier; a sandbox, not a raw-data reseller

If you need raw data to redistribute inside your own product, a licensed provider is the right tool. If you want to test strategies on that market — bars, simulated orders, and logged results in one place — that's what Tasilab is for.

Licensed data APIs

If you're building something that redistributes Saudi market data — a research product, an internal dashboard for a fund, a public data feed — a licensed vendor (SAHMK is the one most Saudi-market developers run into first) is the only option that's actually meant for that. You get comprehensive coverage, fundamentals alongside prices, and a commercial license that covers redistribution. What you don't get is a backtest engine or an order book — a licensed feed gives you rows of data; you still have to build the layer that turns those rows into a strategy result.

yfinance in practice

For quick, free exploration, yfinance is genuinely useful — Tadawul symbols are addressable by appending .SR to the 4-digit code (Saudi Aramco is 2222.SR, SABIC is 2010.SR, Al Rajhi Bank is 1120.SR):

Python
import yfinance as yf

df = yf.download("2222.SR", start="2024-01-01", end="2025-12-31")
print(df[["Open", "High", "Low", "Close", "Volume"]].tail())

Three things to know before you trust the output. First, it's unofficial — Yahoo Finance doesn't publish an SLA for Tadawul coverage, so depth and history length vary by symbol, and thinly-traded names are more likely to have gaps than the large caps. Second, quotes are delayed, which is fine for a backtest on daily bars but wrong for anything that needs a live price. Third, corporate actions (splits, stock dividends) are applied inconsistently across smaller listings — always sanity-check a few known price levels against a second source before trusting a multi-year series for anything that matters.

Static datasets (Kaggle and similar)

A downloaded CSV is the fastest way to get started on a one-off study, and there are Tadawul snapshots on Kaggle and similar sites. The tradeoff is exactly what you'd expect from a static file: it's frozen at whatever date it was uploaded, it won't pick up new listings or delistings, and — the subtler problem — a dataset assembled from today's constituent list has survivorship bias baked in. Companies that were delisted, merged, or suspended during the window aren't in the file, which quietly inflates any backtest that treats "today's TASI 30" as if it were the actual investable universe on every past date.

Historical bars with Tasilab

Get a free key, then pull a date range and drop it straight into pandas:

Python
import os
import pandas as pd
from tasilab import Tasilab

tasi = Tasilab(api_key=os.environ["TASILAB_API_KEY"])

data = tasi.get_historical("2222", "2024-01-01", "2025-12-31")   # Saudi Aramco
df = pd.DataFrame(data["bars"])
df["date"] = pd.to_datetime(df["date"])
df = df.set_index("date")

print(df[["open", "high", "low", "close", "volume"]].tail())

Each bar carries symbol, date, open, high, low, close, adj_close, and volume — standard OHLCV, ready for indicators, resampling, or a chart. The same data is one GET away over HTTP:

curl
curl "https://api.tasilab.com/v1/historical/2222?start=2024-01-01&end=2025-12-31" \
  -H "X-API-Key: $TASILAB_API_KEY"

Under the hood, Tasilab pulls from yfinance and caches the result in its own database, so the first request for a symbol/range combination you haven't asked for before can take a few seconds; every request after that is served from cache. History depth is tier-gated — free accounts get 1 year, Starter and Growth get 3 years, and Pro is unlimited up to a 50-year absolute ceiling (see pricing). Asking for more than your plan allows returns a normal plan_limit_exceeded error rather than silently truncating the range, so a backtest never runs on a shorter window than you think it did.

Use adj_close when you need returns that account for splits and dividends; use close for the raw traded price. Mixing the two is a classic source of wrong backtest numbers.

What "clean" OHLCV actually means

"Clean" isn't just "no missing rows." Two things matter more, and both are easy to get wrong on TASI specifically.

Adjustment. Several TASI blue chips — Al Rajhi Bank, STC, SABIC among them — pay meaningful regular dividends. On the ex-dividend date, the raw traded price drops by roughly the dividend amount even though nothing happened to the company's value; a return series built from unadjusted close records that drop as a loss. Run enough of those through a Sharpe ratio calculation and the strategy looks worse than it is — sometimes badly enough to flip a real edge into an apparent loser. adj_close backs that drop out, so period-over-period returns reflect what a holder actually earned, dividends included.

Gaps. Any pipeline that resamples through yfinance — Tasilab's included — occasionally hits a holiday-adjacent or thinly-traded day where the upstream feed returns a non-numeric OHLC value instead of a normal bar. Tasilab's historical endpoint filters those rows out before they ever reach your client, specifically because an unfiltered NaN silently breaks any pipeline that assumes every bar is a real number — a moving average or RSI calculation will propagate a single NaN through every value downstream of it. If you're pulling from yfinance directly instead, you don't get that filtering for free — check for non-finite values before you feed a series into an indicator.

Data quality gotchas specific to TASI

  • Trading week and holidays. Tadawul trades Sunday–Thursday, not the Monday–Friday week most US-market tooling assumes by default. Ramadan brings shortened trading hours for the whole exchange, and Saudi public holidays close the market entirely — any calendar logic ported from a US-market library needs the week and holiday set corrected, or your bar-counting and "days since last trade" math will be quietly off.
  • Symbol format. Tadawul-listed companies use a 4-digit numeric code (2222 for Saudi Aramco), not a ticker of letters. Most non-Saudi-specific tooling expects letters, so double-check any library's symbol validation doesn't reject a purely numeric code before you conclude the data itself is missing.
  • Corporate actions. Stock splits and bonus-share issuances happen on TASI the same as any exchange. A price series that isn't split-adjusted will show a sudden, spurious drop on the effective date — if a chart shows a stock "losing half its value" overnight with no news to match, a stock split is the first thing to check, not a data bug.
  • Survivorship bias. Covered above for static datasets, but it applies to any workflow that starts from "today's constituent list" and pulls history for those symbols only — delisted and merged companies are invisible by construction, which flatters any backtest that implicitly assumes the investable universe never changes.

Cross-checking a source before you trust it

Whichever source you pick, the fastest way to catch a coverage or adjustment problem is to compare a handful of known prices against a second source before running a real backtest. If you're using Tasilab and want a second opinion, pulling the same symbol from yfinance alongside it takes a few lines:

Python
import pandas as pd
import yfinance as yf
from tasilab import Tasilab

tasi = Tasilab(api_key="YOUR_API_KEY")

data = tasi.get_historical("2222", "2025-01-01", "2025-06-30")
tasilab_df = pd.DataFrame(data["bars"]).set_index("date")["close"]

yf_df = yf.download("2222.SR", start="2025-01-01", end="2025-06-30")["Close"]
yf_df.index = yf_df.index.strftime("%Y-%m-%d")

compare = pd.DataFrame({"tasilab": tasilab_df, "yfinance": yf_df}).dropna()
compare["diff_pct"] = (compare["tasilab"] - compare["yfinance"]) / compare["yfinance"] * 100
print(compare[compare["diff_pct"].abs() > 0.5])   # rows that disagree by more than 0.5%

An empty result means the two sources agree closely enough to trust either one for that window. A handful of disagreeing rows around a known dividend or split date is expected — that's the adjustment difference described above, not a bug. Disagreement scattered across ordinary trading days with no corporate action nearby is the signal to dig further before building anything on top of the data.

FAQ

Is TASI historical data free?

yfinance and Kaggle snapshots are free with the tradeoffs above. Tasilab's free tier includes 1 year of historical bars at no cost; paid tiers extend that to 3 years or unlimited. Fully licensed, redistributable data from a vendor like SAHMK is paid.

What's the difference between close and adj_close?

close is the actual traded price on that date. adj_close retroactively adjusts every prior price for splits and dividends so that period-over-period percentage changes reflect real investment return. Use adj_close for any return, drawdown, or Sharpe calculation; use close only when you specifically need the price someone actually paid on that day.

Why does my TASI backtest look different from a US-market tutorial's numbers?

Usually one of the gotchas above: a Sunday–Thursday week instead of Monday–Friday, an unadjusted close on a dividend-heavy name, or a static dataset with survivorship bias baked in. Rule those out first before assuming the strategy logic itself is wrong.

Put the data to work

You've got the bars — now run a strategy against them. The backtesting guide takes it from here.

Backtesting guide →
Tasilab

An API-native paper-trading sandbox for the Saudi Exchange. Trade with discipline.

Product
  • Paper trading
  • Tadawul API
  • Python SDK
  • Documentation
Guides
  • Backtest in Python
  • Historical data
  • First trading bot
Legal
  • Terms of use
  • Privacy
  • Disclaimer

Tasilab is a simulation-only paper-trading environment. No real orders are placed on the Saudi Exchange. Tasilab is not licensed by the Capital Market Authority and does not provide investment advice.

© 2026 Tasilab