tefas-client is a type-safe Python client for real-time fund data from TEFAS (Türkiye Elektronik Fon Alım Satım Platformu).
Fetch fund prices, allocations, and metrics with a simple, synchronous API. Perfect for building financial dashboards, analysis tools, and investment tracking apps.
v2 API — Production-ready, fully tested, zero breaking changes.
- 📊 Real-time data: Fund prices (NAV), market cap, investor counts
- 🔍 Portfolio breakdown: Asset allocation across thousands of securities
- 🗂️ Fund discovery: List umbrella fund types and founder institutions, with built-in caching
- 📋 Fund snapshots: Instant overview (price, daily return, category rank, market share)
- 🌍 Language support: Turkish (
TR) and English (EN) responses - 🔎 Advanced filters: Filter by umbrella type code or founder institution
- ⏱️ Smart chunking: Automatic handling of TEFAS's 28-day query limits
- 🛡️ Type-safe: Full Pydantic validation, mypy compatible
- ⚡ Resilient: Exponential backoff, automatic weekend date handling
- 🐍 Modern Python: 3.10+ with context manager support
pip install tefas-client# uv
uv pip install tefas-client
# Poetry
poetry add tefas-client
# Conda
conda install -c conda-forge tefas-clientfrom datetime import date
from tefas_client import Tefas
# Basic usage – single fund
with Tefas() as tefas:
funds = tefas.fetch("AAK", start_date=date(2024, 2, 1), end_date=date(2024, 2, 1))
fund = funds["AAK"]
print(f"{fund.title}: {fund.latest().price} TRY")
# Output: Ak Portfoy Amerikan Dolar Yabanci BYF: 23.456789 TRYfrom datetime import date, timedelta
from tefas_client import Tefas
with Tefas(timeout=15.0) as tefas:
today = date.today()
funds = tefas.fetch("AAK", start_date=today, end_date=today)
latest = funds["AAK"].latest()
print(f"Price: {latest.price}")
print(f"Market Cap: {latest.market_cap} TRY")
print(f"Investors: {latest.number_of_investors}")from datetime import date
from tefas_client import Tefas
with Tefas() as tefas:
# Fetch 3-month history with portfolio breakdown
funds = tefas.fetch(
"AAK",
start_date=date(2024, 1, 1),
end_date=date(2024, 3, 31),
include_allocation=True,
)
for history in funds["AAK"].history:
print(f"{history.date}: {history.price} TRY")
if history.allocation:
print(" Top holdings:")
for code in list(history.allocation.assets.keys())[:3]:
pct = history.allocation.assets[code]
name = history.allocation.asset_names[code]
print(f" {name}: {pct:.2f}%")from datetime import date
from tefas_client import Tefas
fund_codes = ["AAK", "AAFTIYTF", "AKBLV"]
with Tefas() as tefas:
# Fetch all funds at once (empty code = all available funds)
all_funds = tefas.fetch(start_date=date(2024, 2, 1), end_date=date(2024, 2, 28))
# Filter to codes of interest
for code in fund_codes:
if code in all_funds:
fund = all_funds[code]
price = fund.latest().price
print(f"{code}: {price}")from tefas_client import Tefas
with Tefas() as tefas:
ov = tefas.fetch_overview("IPB")
print(f"{ov.title}")
print(f" Price : {ov.price} TRY")
print(f" Daily ret : {ov.daily_return}%")
print(f" Rank : {ov.category_rank}/{ov.category_fund_count} in {ov.category}")
print(f" Investors : {ov.number_of_investors:,}")
print(f" Mkt share : {ov.market_share}%")from tefas_client import Tefas
with Tefas() as tefas:
# Umbrella fund type codes — use as umbrella_type= in fetch()
types = tefas.fetch_fund_types()
for t in types:
print(t.code, t.name)
# Founder codes — use as founder_code= in fetch()
founders = tefas.fetch_founders()
for f in founders:
print(f.code, f.name)from datetime import date
from tefas_client import Tefas
with Tefas() as tefas:
# Only equity umbrella funds (code 104) by a specific founder
funds = tefas.fetch(
fund_type="YAT",
umbrella_type=104,
founder_code="IPO",
start_date=date(2024, 2, 1),
end_date=date(2024, 2, 29),
)
for code, fund in funds.items():
print(code, fund.latest().price)from tefas_client import Tefas
with Tefas(lang="EN") as tefas:
ov = tefas.fetch_overview("IPB")
print(ov.category) # "Money Market Fund" instead of "Para Piyasası Fonu"
types = tefas.fetch_fund_types()
print(types[0].name) # English umbrella type namesimport pandas as pd
from datetime import date
from tefas_client import Tefas
with Tefas() as tefas:
funds = tefas.fetch("AAK", start_date=date(2024, 1, 1), end_date=date(2024, 3, 31))
# Convert to DataFrame
df = pd.DataFrame([
{
"date": h.date,
"price": h.price,
"market_cap": h.market_cap,
"investors": h.number_of_investors,
}
for h in funds["AAK"].history
])
print(df.describe())from tefas_client import Tefas, RateLimitError, EmptyResponseError
try:
with Tefas() as tefas:
funds = tefas.fetch("INVALID")
except EmptyResponseError:
print("No data for this date range")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds")Context manager for managing HTTP connections and sessions.
with Tefas(timeout=15.0, lang="EN") as tefas:
funds = tefas.fetch("AAK", start_date=date.today(), end_date=date.today())| Parameter | Type | Default | Description |
|---|---|---|---|
timeout |
float |
30.0 |
Per-request HTTP timeout in seconds |
lang |
str |
"TR" |
Response language: "TR" (Turkish) or "EN" (English) |
Fetch fund price history for a given date range. Large ranges are automatically split into ≤28-day chunks.
with Tefas() as tefas:
# Single fund — type auto-detected (YAT → EMK → BYF)
funds = tefas.fetch("AAK", start_date=date(2024, 1, 1), end_date=date(2024, 3, 31))
# All investment funds
all_funds = tefas.fetch(start_date=date.today(), end_date=date.today())
# Pension (BES) funds filtered by founder
funds = tefas.fetch(fund_type="EMK", founder_code="IPO", start_date=date(2024, 2, 1))
# Filter by umbrella type (e.g. equity funds = 104)
funds = tefas.fetch(fund_type="YAT", umbrella_type=104, start_date=date(2024, 2, 1))| Parameter | Type | Default | Description |
|---|---|---|---|
fund_code |
str | list[str] |
"" |
TEFAS code(s), e.g. "AAK" or ["AAK", "TLY"]. Empty = all funds |
start_date |
date | None |
end_date |
Inclusive range start. Defaults to end_date |
end_date |
date | None |
today | Inclusive range end. Auto-adjusted to nearest Friday if weekend |
include_allocation |
bool |
False |
Include portfolio allocation breakdown |
fund_type |
"YAT" | "EMK" | "BYF" | None |
None |
Fund type. When None and a specific code is given, auto-detected via YAT → EMK → BYF |
umbrella_type |
int | None |
None |
Umbrella fund type code (e.g. 104). Use fetch_fund_types() to list valid codes |
founder_code |
str | None |
None |
Founder institution code (e.g. "IPO"). Use fetch_founders() to list valid codes |
Returns: dict[str, Fund] — mapping of fund code → fund data
Fetch an instant snapshot of a single fund — current price, daily return, category ranking, and market share. Not a time-series; reflects the state at call time.
from tefas_client import Tefas, FundOverview
with Tefas() as tefas:
ov = tefas.fetch_overview("IPB")
print(f"{ov.title}: {ov.price} TRY, rank {ov.category_rank}/{ov.category_fund_count}")| Field | Type | Description |
|---|---|---|
code |
str |
TEFAS fund code |
title |
str |
Full fund name |
price |
float | None |
Latest NAV in TRY |
daily_return |
float | None |
Daily return percentage |
shares |
float | None |
Total circulating shares |
market_cap |
float | None |
Portfolio size in TRY |
category |
str | None |
Fund category name |
category_rank |
int | None |
Rank within category |
category_fund_count |
int | None |
Total funds in category |
number_of_investors |
int | None |
Active investor count |
market_share |
float | None |
Market share percentage |
Tefas.fetch_fund_types(fund_type: "YAT" | "EMK" = "YAT", *, refresh: bool = False) -> list[UmbrellaFundType]
List umbrella fund type codes and names. Pass the returned code values as umbrella_type in fetch().
Results are cached per instance — the second call returns the same list without hitting the API. Pass refresh=True to force a fresh request.
from tefas_client import Tefas
with Tefas() as tefas:
types = tefas.fetch_fund_types() # fetches from API
types = tefas.fetch_fund_types() # returns cached result
types = tefas.fetch_fund_types(refresh=True) # forces fresh fetch
for t in types:
print(t.code, t.name) # e.g. 104 "Hisse Senedi Şemsiye Fonu"| Parameter | Type | Default | Description |
|---|---|---|---|
fund_type |
"YAT" | "EMK" |
"YAT" |
Fund type to list categories for |
refresh |
bool |
False |
When True, bypass cache and fetch fresh data |
| Field | Type | Description |
|---|---|---|
code |
int |
Numeric umbrella type code |
name |
str |
Umbrella type name |
List founder institution codes and names. Pass the returned code values as founder_code in fetch().
Results are cached per instance — the second call returns the same list without hitting the API. Pass refresh=True to force a fresh request.
from tefas_client import Tefas
with Tefas() as tefas:
founders = tefas.fetch_founders() # fetches from API
founders = tefas.fetch_founders() # returns cached result
founders = tefas.fetch_founders(refresh=True) # forces fresh fetch
for f in founders:
print(f.code, f.name) # e.g. "IPO" "İŞ PORTFÖY YÖNETİMİ A.Ş."| Parameter | Type | Default | Description |
|---|---|---|---|
fund_type |
"YAT" | "EMK" |
"YAT" |
Fund type to list founders for |
refresh |
bool |
False |
When True, bypass cache and fetch fresh data |
| Field | Type | Description |
|---|---|---|
code |
str |
Founder institution code |
name |
str |
Founder institution name |
fund_type |
str | None |
Fund type indicator |
Represents a single fund.
fund: Fund
fund.code # "AAK"
fund.title # "Ak Portfoy Amerikan Dolar Yabanci BYF"
fund.latest() # History (most recent entry)
fund.history # list[History] (all historical entries, oldest first)| Attribute | Type | Description |
|---|---|---|
code |
str |
TEFAS fund code (e.g. "AAK") |
title |
str |
Full fund name in Turkish |
history |
list[History] |
Chronological price/metric history, oldest first |
Single fund snapshot for a trading date.
h: History
h.price # Fund unit price (NAV) in TRY
h.market_cap # Portfolio value in TRY
h.number_of_investors # Active investor count
h.allocation # Allocation data (if requested)| Field | Type | Description |
|---|---|---|
date |
date |
Trading date (never a weekend/holiday) |
price |
float | None |
Fund unit price (NAV) in TRY |
market_cap |
float | None |
Portfolio size (TRY) |
number_of_shares |
float | None |
Total circulating shares |
number_of_investors |
int | None |
Active investor count |
exchange_bulletin_price |
float | None |
Exchange bulletin price (BYF only) |
allocation |
Allocation | None |
Portfolio breakdown (if include_allocation=True) |
Portfolio composition snapshot.
a: Allocation
a.assets # {"US0378331005": 45.5, "IE00B4L5Y983": 30.2, ...}
a.asset_names # {"US0378331005": "APPLE INC.", ...}| Field | Type | Description |
|---|---|---|
date |
date |
Allocation date |
assets |
dict[str, float] |
{ISIN: percentage_allocation} |
asset_names |
dict[str, str] |
{ISIN: security_name} |
All exceptions inherit from TefasError and can be caught with:
from tefas_client import Tefas, TefasError
try:
with Tefas() as tefas:
funds = tefas.fetch("AAK", start_date=date(2024, 1, 1))
except TefasError as e:
print(f"TEFAS error: {e}")| Exception | When | Example |
|---|---|---|
TefasError |
Base class for all library errors | except TefasError: pass |
RateLimitError |
HTTP 429 after retries (has retry_after attribute) |
except RateLimitError as e: time.sleep(e.retry_after) |
EmptyResponseError |
API returned 200 but no rows for query | except EmptyResponseError: print("No data") |
| Constraint | Impact | Workaround |
|---|---|---|
| Rate limit | ~6 req/min, retries with backoff, raises after 3 failures | Space requests ≥10s apart, use batch queries |
| 28-day window | Max query is ~28 calendar days | Automatic; fetch() chunks larger ranges |
| No weekend data | TEFAS closed weekends/holidays | Dates auto-adjust to nearest Friday |
| WAF/IP blocks | Datacenter IPs may get 403/503 | Use residential IP or VPN |
| Synchronous only | No built-in async/await | Use asyncio.to_thread() if needed (advanced) |
Cause: TEFAS WAF blocks datacenter IP ranges (AWS, Azure, etc.)
Solution:
- Use a residential VPN or local ISP connection
- For production: Use a proxy service with residential IPs
- The library auto-retries; wait a few seconds before trying again
Cause: Too many requests to TEFAS API (>6/min)
Solution:
import time
from tefas_client import Tefas, RateLimitError
with Tefas() as tefas:
for fund_code in ["AAK", "AAFTIYTF", "AKBLV"]:
try:
funds = tefas.fetch(fund_code, start_date=date.today(), end_date=date.today())
except RateLimitError as e:
print(f"Rate limited. Waiting {e.retry_after}s...")
time.sleep(e.retry_after + 1) # +1 for safety margin
# Retry logic hereCause: No trading data for that date (e.g., weekend, holiday, fund didn't exist)
Solution:
from tefas_client import Tefas, EmptyResponseError
try:
with Tefas() as tefas:
# Try a date range instead of single day
funds = tefas.fetch("AAK", start_date=date(2024, 1, 1), end_date=date(2024, 1, 10))
except EmptyResponseError:
print("No data available; fund may not have existed or dates were all holidays")Tip 1: Use context manager (reuses connection across multiple fetch() calls)
# ✅ Good: single connection for 3 calls
with Tefas() as tefas:
funds1 = tefas.fetch("AAK", ...)
funds2 = tefas.fetch("AAFTIYTF", ...)
funds3 = tefas.fetch("AKBLV", ...)Tip 2: Fetch all funds in one call instead of per-code
# ✅ Good: 1 request
with Tefas() as tefas:
all_funds = tefas.fetch(start_date=date.today(), end_date=date.today())
tefas_funds = {k: v for k, v in all_funds.items() if k.startswith("AAFTI")}
# ❌ Avoid: 3 requests
with Tefas() as tefas:
funds1 = tefas.fetch("AAK", ...)
funds2 = tefas.fetch("AAFTIYTF", ...)
funds3 = tefas.fetch("AKBLV", ...)Tip 3: Use shorter date ranges
# ✅ Good: smaller response, faster
with Tefas() as tefas:
funds = tefas.fetch("AAK", start_date=date(2024, 1, 1), end_date=date(2024, 1, 31))
# ❌ Slow: year of data
with Tefas() as tefas:
funds = tefas.fetch("AAK", start_date=date(2023, 1, 1), end_date=date(2024, 12, 31))We welcome contributions! To get started:
# Clone and install
git clone https://github.com/semudu/tefas-client
cd tefas-client
make install
# Run tests
make test
# Lint and format
make lint
make formatDevelopment workflow:
- Fork the repository on GitHub
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes and ensure tests pass (
make test) - Commit:
git commit -am "Add my feature" - Push and open a pull request
Code style:
- Python 3.10+ with type hints
- Ruff for formatting and linting
- Mypy for static type checking
- Pytest for testing
See CONTRIBUTING.md (if available) or open an issue to discuss ideas.
MIT — Free for personal and commercial use
Questions? Open an issue
Found a bug? Report it
Have a feature idea? Suggest it