sahmk-sa
sahmk-python
Python

Official Python SDK for SAHMK — Saudi market data, financials, and real-time market APIs.

Last updated Aug 3, 2026
24
Stars
3
Forks
0
Issues
0
Stars/day
Attention Score
63
Language breakdown
Python 100.0%
Files click to expand
README

SAHMK Python SDK

Official Source

Official distribution: GitHub (sahmk-sa) and PyPI only. Do not download binaries from third-party forks.

Official Python SDK for Sahmk — Saudi market data and richer market workflows for developers.

Use one client for live Tadawul quotes, market-level insights, company/fundamental data, financials, events, and historical series.

Features

  • Real-time quotes for 350+ Tadawul stocks
  • Batch quotes for up to 50 symbols per request
  • Historical OHLCV data with date-range support (1d, 1w, 1m, 30m, 60m)
  • Market overview with index scoping (TASI/NOMU)
  • Market depth order book snapshots (entitlement-gated)
  • Live trades recent prints and real-time tape (Pro+)
  • Company directory endpoint for symbol discovery
  • Company/fundamental data (plan-dependent fields)
  • Financials, dividends, and events endpoints (by plan)
  • WebSocket streaming for quotes, depth, and trades (Pro+/entitled)

Installation

pip install sahmk

For local development:

git clone https://github.com/sahmk-sa/sahmk-python.git
cd sahmk-python
pip install -r requirements.txt

Security

  • Use environment variables for API keys (recommended: SAHMKAPIKEY).
  • Never commit API keys to source control, notebooks, or logs.
  • If a key is exposed, rotate it immediately from your Sahmk dashboard.

Quick Start

import os
from sahmk import SahmkClient

client = SahmkClient(os.environ["SAHMKAPIKEY"])

quote = client.quote("2222") print(f"{quote['nameen']}: {quote['price']} SAR ({quote['changepercent']}%)")

market = client.market_summary(index="TASI") print(f"TASI: {market['indexvalue']} ({market['indexchange_percent']}%)")

Batch quotes are Starter+ plan.

for q in client.quotes(["2222", "1120", "7010"])["quotes"]: print(f"{q['symbol']}: {q['price']}")

Identifier Resolution (Quotes)

quote() and quotes() accept either traditional symbols or resolvable identifiers:

  • Symbol: "2222"
  • Arabic company name: "أرامكو السعودية"
  • English company name/alias: "Aramco"
Symbol input always works. Name/alias input requires backend identifier-resolution support. For batch quotes, the SDK first tries identifiers=..., then automatically falls back to legacy symbols=... when connected to older backends.
q1 = client.quote("2222")              # classic symbol usage
q2 = client.quote("أرامكو السعودية")   # Arabic identifier
q3 = client.quote("Aramco")            # English alias

batch = client.quotes(["2222", "الراجحي", "SABIC"]) for q in batch.quotes: print(q.requested_identifier, "=>", q.symbol)

if batch.ambiguous: print("Ambiguous:", batch.ambiguous) if batch.unknown: print("Unknown:", batch.unknown)

When the backend returns resolution metadata, it is exposed on typed objects:

quote = client.quote("Aramco")
print(quote.requested_identifier)      # Aramco
print(quote.resolved_symbol)           # 2222
print(quote.resolution.matched_by)     # alias (if provided by API)

Company Directory / Symbol Discovery

Use companies() as the canonical symbol-discovery path before calling quote() or company().

# Search by symbol or name
directory = client.companies(search="aram")
for row in directory["results"]:
    print(row["symbol"], row.get("name_en") or row.get("name"))
# Filter by market (TASI / NOMU, NOMUC alias is accepted)
nomu_companies = client.companies(market="NOMUC", limit=20)
print(nomu_companies["count"])
# Pagination loop with offset
offset = 0
page_size = 100

while True: page = client.companies(limit=page_size, offset=offset) for company in page["results"]: print(company["symbol"])

offset += page_size if offset >= page["total"]: break

Recommended flow:

  • Discover valid symbols with companies().
  • Call quote(symbol) / company(symbol) with a validated symbol.

Production Reliability

  • The client retries transient failures: HTTP 429 and 5xx errors.
  • Defaults: retries=3, backoff_factor=0.5 (0.5s, 1s, 2s).
  • Invalid symbols, authentication failures, and plan-access errors are not retryable.
from sahmk import SahmkClient

client = SahmkClient("yourapikey", retries=3, backoff_factor=0.5)

Plan Behavior

Some methods are plan-gated (for example quotes, historical, financials, dividends, events). When your plan does not include an endpoint, the API returns an error response (not retried automatically).

Historical data availability is plan-based:

  • Free: no historical access
  • Starter: 1d, 1w, 1m
  • Pro: Starter intervals + 60m up to 90 days
  • Business: Starter intervals + 30m up to 6 months + 60m up to 1 year
  • Enterprise: Business defaults plus custom retention/interval/delivery by agreement
Out-of-plan interval/range requests are returned by the API as 403 PLAN_LIMIT. The SDK does not hard-block these requests client-side; it passes the API response through.

CLI Quick Start

export SAHMKAPIKEY="yourapikey"
sahmk quote 2222
sahmk market summary --index NOMU
sahmk market gainers --limit 5 --index NOMUC
sahmk historical 2222 --from 2026-01-01 --to 2026-01-28
sahmk historical 2222 --from 2026-01-01 --to 2026-01-03 --interval 60m
sahmk company 2222
sahmk financials 2222
sahmk ratios 2222 --history latest --period annual --metrics core
sahmk compare 2222,1120 --metrics extended
sahmk dividends 2222
sahmk events --symbol 2222 --limit 5
sahmk depth 2222 --levels 5
sahmk trades 2222 --limit 20
sahmk stream 2222,1120
sahmk stream-depth 2222,1120 --levels 5
sahmk stream-trades 2222,1120

You can also pass the key directly:

sahmk quote 2222 --api-key yourapikey

Typed Responses

Most methods return typed objects with IDE autocomplete while preserving dict-style access. Analytics methods (ratios, compare) return raw API dict responses to match the production contract exactly.

quote = client.quote("2222")
print(quote.price)
print(quote.liquidity.net_value)

Backwards-compatible dict access

print(quote["price"]) print(quote.get("volume")) print(quote.raw)

Financials & Analytics

client.financials("1120", history="3y", result="latest")
client.ratios("1120")
client.ratios("1120", history="5y", period="quarterly", metrics="extended")
client.compare(["1120", "1180", "1010"])
client.compare(["1120", "1180", "1010", "2222"], metrics="extended")

Financials responses no longer include meta. Existing financial statement sections (incomestatements, balancesheets, cash_flows) are unchanged.

Analytics meta remains minimal and includes only:

  • period
  • metrics
  • warnings

Market Index Scoping

Supported values:

  • TASI
  • NOMU
  • NOMUC alias (normalized to NOMU)
summary = client.market_summary(index="NOMUC")
print(summary.index)       # NOMU
print(summary.is_delayed)  # True/False by entitlement

API Reference

Base URL: https://api.sahmk.sa/api/v1

| Endpoint | Plan | Description | |----------|------|-------------| | GET /quote/{symbol}/ | Free | Stock quote | | GET /quotes/?symbols=... | Starter+ | Batch quotes (up to 50) | | GET /historical/{symbol}/ | Starter+ | Historical OHLCV data (1d, 1w, 1m, 30m, 60m, plan-limited) | | GET /market/summary/ | Free | Market overview | | GET /market/gainers/ | Free | Top gainers | | GET /market/losers/ | Free | Top losers | | GET /market/volume/ | Free | Volume leaders | | GET /market/value/ | Free | Value leaders | | GET /market/sectors/ | Free | Sector performance | | GET /market/depth/{symbol}/ | Entitled | Market depth / order book (levels 1-20). Request access | | GET /market/trades/{symbol}/ | Pro+ | Recent live trade prints (limit 1-200) | | GET /companies/ | Free | Company directory and symbol discovery | | GET /company/{symbol}/ | Free+ | Company info (tiered by plan) | | GET /financials/{symbol}/ | Starter+ | Financial statements | | GET /analytics/ratios/{symbol}/ | Starter+ | Analytics ratios for one company | | GET /analytics/compare/ | Starter+ | Analytics comparison across companies | | GET /dividends/{symbol}/ | Starter+ | Dividend history and yield | | GET /events/ | Pro+ | AI-generated stock events |

All endpoints require X-API-Key.

Full docs: sahmk.sa/developers/docs

Examples

Example scripts:

Market Depth

Market depth REST and WebSocket access are entitlement-gated. Request access from the developer realtime-access dashboard.

depth = client.depth("2222", levels=5)
print(depth.bestbid, depth.bestask, depth.spread)
for level in depth.bids:
    print(level.level, level.price, level.quantity)

CLI:

sahmk depth 2222 --levels 5
sahmk stream-depth 2222,1120 --levels 5

Live Trades (Pro+)

Recent trade prints (REST) and a real-time tape (WebSocket). Same Pro+ plan bar as Best Price–style realtime — no separate trades product request.

trades = client.trades("2222", limit=20)
print(trades.count, trades.summary.trade_value)
for event in trades.events:
    print(event.event_time, event.price, event.quantity)

CLI:

sahmk trades 2222 --limit 20
sahmk stream-trades 2222,1120

WebSocket Streaming (Pro+)

import asyncio
from sahmk import SahmkClient

client = SahmkClient("yourapikey")

async def on_quote(msg): print(f"{msg['symbol']}: {msg['data']['price']}")

asyncio.run(client.stream(["2222", "1120"], onquote=onquote))

Depth streaming uses a dedicated channel:

async def on_depth(msg):
    print(f"{msg['symbol']}: {msg['bestbid']} / {msg['bestask']}")

asyncio.run(client.streamdepth(["2222"], ondepth=on_depth, levels=5))

Trades streaming also uses a dedicated channel:

async def on_trade(msg):
    print(f"{msg['symbol']}: {msg['price']} x {msg['quantity']}")

async def on_snapshot(msg): print(f"snapshot {msg['symbol']}: {msg['count']} events")

asyncio.run( client.streamtrades(["2222"], ontrade=ontrade, onsnapshot=on_snapshot) )

The streaming client auto-reconnects with exponential backoff + jitter and resubscribes symbols after reconnect.

Runtime behavior (verified with backend contract):

  • Authentication close code: 4401 (non-retryable)
  • Entitlement / plan / inactive / unverified close code: 4403 (non-retryable)
  • Invalid JSON / unknown action returns type="error" while socket stays open
  • Active subscriptions are per-connection; the SDK automatically resubscribes after reconnect
  • Symbol chunking uses backend connected.limits.maxsymbolsper_call when available
For a production-style long-running process (logging, graceful shutdown, reconnect visibility, and automatic resubscribe behavior), see: Changelog: CHANGELOG.md Roadmap: ROADMAP.md

License

MIT — see LICENSE

🔗 More in this category

© 2026 GitRepoTrend · sahmk-sa/sahmk-python · Updated daily from GitHub