Skip to main content

Fetch fund data from https://www.tefas.gov.tr

Project description

tefas-client

PyPI - Version PyPI - Python Version PyPI - License CI Docs

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

tefas_client-1.0.tar.gz (20.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tefas_client-1.0-py3-none-any.whl (17.4 kB view details)

Uploaded Python 3

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

Hashes for tefas_client-1.0.tar.gz
Algorithm Hash digest
SHA256 7e063295811e42e2eaadfc779e6b1c555fd97a0505dbfb0c61fd829217855e3d
MD5 693385113609a5c301788b11cb733546
BLAKE2b-256 4c1f7c8ad7a8913adfabeef4f17e3485bf25e8567e65b60dff1ea0ff91ff618d

See more details on using hashes here.

Provenance

The following attestation bundles were made for tefas_client-1.0.tar.gz:

Publisher: release.yml on semudu/tefas-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for tefas_client-1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 06f2db6edae67cf65962f3306f4ea618bd62378ffc83a37b4b87138ea24c983d
MD5 6fc141729380a8577337780be45373b4
BLAKE2b-256 4f02ff5c9bbcaa774aa2531294f7a12f3e13a614d0a9f020a001144ae6c425eb

See more details on using hashes here.

Provenance

The following attestation bundles were made for tefas_client-1.0-py3-none-any.whl:

Publisher: release.yml on semudu/tefas-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page