Fetch fund data from https://www.tefas.gov.tr
Project description
tefas-client
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.
Stable v1 API — Production-ready, fully tested, zero breaking changes.
Features
- 📊 Real-time data: Fund prices (NAV), market cap, investor counts
- 🔍 Portfolio breakdown: Asset allocation across thousands of securities
- ⏱️ 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
Installation
pip install tefas-client
Other package managers
# uv
uv pip install tefas-client
# Poetry
poetry add tefas-client
# Conda
conda install -c conda-forge tefas-client
Quick start
from 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 TRY
Usage Examples
Basic: Fetch latest price
from 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}")
Advanced: Date range with allocation
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}%")
Batch: Multiple funds at once
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}")
Integration: Export to Pandas
import 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())
Error handling
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")
API reference
Tefas(timeout: float = 30.0)
Context manager for managing HTTP connections and sessions.
with Tefas(timeout=15.0) 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 |
Tefas.fetch(fund_code: str = "", *, start_date: date | None = None, end_date: date | None = None, include_allocation: bool = False) -> dict[str, Fund]
Fetch fund data for a given date range.
with Tefas() as tefas:
# Single fund
funds = tefas.fetch("AAK", start_date=date(2024, 1, 1), end_date=date(2024, 3, 31))
# All funds
all_funds = tefas.fetch(start_date=date.today(), end_date=date.today())
| Parameter | Type | Default | Description |
|---|---|---|---|
fund_code |
str |
"" |
TEFAS code (e.g. "AAK"). Empty = all funds |
start_date |
date | None |
end_date |
Inclusive range start. Defaults to end_date |
end_date |
date | None |
today | Inclusive range end. Automatically adjusted to nearest Friday if weekend/holiday |
include_allocation |
bool |
False |
Include portfolio allocation breakdown |
Returns: dict[str, Fund] — mapping of fund code → fund data
Fund
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 |
History
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) |
Allocation
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} |
Exceptions
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") |
Known Constraints
| 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) |
Troubleshooting
"403 Forbidden" or "503 Service Unavailable"
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
"RateLimitError: Retry after X seconds"
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 here
"EmptyResponseError"
Cause: 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")
Slow performance
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))
Contributing
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 format
Development 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.
License
MIT — Free for personal and commercial use
Questions? Open an issue
Found a bug? Report it
Have a feature idea? Suggest it
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tefas_client-1.0.tar.gz.
File metadata
- Download URL: tefas_client-1.0.tar.gz
- Upload date:
- Size: 20.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e063295811e42e2eaadfc779e6b1c555fd97a0505dbfb0c61fd829217855e3d
|
|
| MD5 |
693385113609a5c301788b11cb733546
|
|
| BLAKE2b-256 |
4c1f7c8ad7a8913adfabeef4f17e3485bf25e8567e65b60dff1ea0ff91ff618d
|
Provenance
The following attestation bundles were made for tefas_client-1.0.tar.gz:
Publisher:
release.yml on semudu/tefas-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tefas_client-1.0.tar.gz -
Subject digest:
7e063295811e42e2eaadfc779e6b1c555fd97a0505dbfb0c61fd829217855e3d - Sigstore transparency entry: 1395058084
- Sigstore integration time:
-
Permalink:
semudu/tefas-client@a378888952e0ba54b53021d35e5a314f8ec0e3d4 -
Branch / Tag:
refs/tags/v1.0 - Owner: https://github.com/semudu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a378888952e0ba54b53021d35e5a314f8ec0e3d4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tefas_client-1.0-py3-none-any.whl.
File metadata
- Download URL: tefas_client-1.0-py3-none-any.whl
- Upload date:
- Size: 17.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06f2db6edae67cf65962f3306f4ea618bd62378ffc83a37b4b87138ea24c983d
|
|
| MD5 |
6fc141729380a8577337780be45373b4
|
|
| BLAKE2b-256 |
4f02ff5c9bbcaa774aa2531294f7a12f3e13a614d0a9f020a001144ae6c425eb
|
Provenance
The following attestation bundles were made for tefas_client-1.0-py3-none-any.whl:
Publisher:
release.yml on semudu/tefas-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tefas_client-1.0-py3-none-any.whl -
Subject digest:
06f2db6edae67cf65962f3306f4ea618bd62378ffc83a37b4b87138ea24c983d - Sigstore transparency entry: 1395058090
- Sigstore integration time:
-
Permalink:
semudu/tefas-client@a378888952e0ba54b53021d35e5a314f8ec0e3d4 -
Branch / Tag:
refs/tags/v1.0 - Owner: https://github.com/semudu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a378888952e0ba54b53021d35e5a314f8ec0e3d4 -
Trigger Event:
push
-
Statement type: