sahmk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sahmk-0.1.0/LICENSE +21 -0
- sahmk-0.1.0/PKG-INFO +155 -0
- sahmk-0.1.0/README.md +126 -0
- sahmk-0.1.0/pyproject.toml +45 -0
- sahmk-0.1.0/sahmk/__init__.py +4 -0
- sahmk-0.1.0/sahmk/client.py +334 -0
- sahmk-0.1.0/sahmk.egg-info/PKG-INFO +155 -0
- sahmk-0.1.0/sahmk.egg-info/SOURCES.txt +10 -0
- sahmk-0.1.0/sahmk.egg-info/dependency_links.txt +1 -0
- sahmk-0.1.0/sahmk.egg-info/requires.txt +2 -0
- sahmk-0.1.0/sahmk.egg-info/top_level.txt +1 -0
- sahmk-0.1.0/setup.cfg +4 -0
sahmk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SAHMK
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sahmk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sahmk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight Python client for the SAHMK Developer API.
|
|
5
|
+
Author-email: Sahmk <developer@sahmk.sa>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sahmk-sa/sahmk-python
|
|
8
|
+
Project-URL: Documentation, https://sahmk.sa/developers/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/sahmk-sa/sahmk-python
|
|
10
|
+
Project-URL: Issues, https://github.com/sahmk-sa/sahmk-python/issues
|
|
11
|
+
Keywords: stocks,tadawul,market-data,websocket,api
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: requests>=2.28.0
|
|
27
|
+
Requires-Dist: websockets>=12.0
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# SAHMK Python SDK
|
|
31
|
+
|
|
32
|
+
A lightweight Python client for the [SAHMK Developer API](https://sahmk.sa/developers) — real-time and historical Saudi stock market (Tadawul) data.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- **Real-time quotes** — live prices for 350+ Tadawul stocks
|
|
37
|
+
- **Batch quotes** — up to 50 stocks in a single request
|
|
38
|
+
- **Historical data** — daily/weekly/monthly OHLCV with custom date ranges
|
|
39
|
+
- **Market overview** — TASI index, gainers, losers, volume/value leaders, sectors
|
|
40
|
+
- **Company info** — fundamentals, technicals, valuation, analyst consensus (by plan)
|
|
41
|
+
- **Financials** — income statements, balance sheets, cash flow
|
|
42
|
+
- **Dividends** — history, yield, upcoming payments
|
|
43
|
+
- **Events** — AI-generated stock event summaries
|
|
44
|
+
- **WebSocket streaming** — real-time price updates pushed to you (Pro+ plan)
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
After the first PyPI release:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install sahmk
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or clone this repo for local development:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/sahmk-sa/sahmk-python.git
|
|
58
|
+
cd sahmk-python
|
|
59
|
+
pip install -r requirements.txt
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from sahmk import SahmkClient
|
|
66
|
+
|
|
67
|
+
client = SahmkClient("your_api_key")
|
|
68
|
+
|
|
69
|
+
# Get a stock quote
|
|
70
|
+
quote = client.quote("2222")
|
|
71
|
+
print(f"{quote['name_en']}: {quote['price']} SAR ({quote['change_percent']}%)")
|
|
72
|
+
|
|
73
|
+
# Get market summary
|
|
74
|
+
market = client.market_summary()
|
|
75
|
+
print(f"TASI: {market['index_value']} ({market['index_change_percent']}%)")
|
|
76
|
+
|
|
77
|
+
# Batch quotes (Starter+ plan)
|
|
78
|
+
result = client.quotes(["2222", "1120", "4191"])
|
|
79
|
+
for q in result["quotes"]:
|
|
80
|
+
print(f"{q['symbol']}: {q['price']}")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Get Your API Key
|
|
84
|
+
|
|
85
|
+
1. Sign up at [sahmk.sa/developers](https://sahmk.sa/developers)
|
|
86
|
+
2. Verify your email
|
|
87
|
+
3. Go to Dashboard → API Keys → Create Key
|
|
88
|
+
4. Copy your key (starts with `shmk_live_` or `shmk_test_`)
|
|
89
|
+
|
|
90
|
+
## Plans
|
|
91
|
+
|
|
92
|
+
| Plan | Price | Requests/Day | WebSocket |
|
|
93
|
+
|------|-------|-------------|-----------|
|
|
94
|
+
| Free | 0 SAR | 100 | - |
|
|
95
|
+
| Starter | 149 SAR/mo | 5,000 | - |
|
|
96
|
+
| Pro | 499 SAR/mo | 50,000 | Yes |
|
|
97
|
+
| Enterprise | 1,499+ SAR/mo | Unlimited | Yes |
|
|
98
|
+
|
|
99
|
+
## Examples
|
|
100
|
+
|
|
101
|
+
Check the [`examples/`](examples/) directory:
|
|
102
|
+
|
|
103
|
+
| File | Description |
|
|
104
|
+
|------|-------------|
|
|
105
|
+
| [`quote.py`](examples/quote.py) | Get a single stock quote |
|
|
106
|
+
| [`batch_quotes.py`](examples/batch_quotes.py) | Fetch multiple stocks at once |
|
|
107
|
+
| [`historical.py`](examples/historical.py) | Historical price data |
|
|
108
|
+
| [`market_summary.py`](examples/market_summary.py) | Market overview and movers |
|
|
109
|
+
| [`websocket_stream.py`](examples/websocket_stream.py) | Real-time WebSocket streaming (Pro+) |
|
|
110
|
+
|
|
111
|
+
## API Reference
|
|
112
|
+
|
|
113
|
+
Base URL: `https://app.sahmk.sa/api/v1`
|
|
114
|
+
|
|
115
|
+
| Endpoint | Plan | Description |
|
|
116
|
+
|----------|------|-------------|
|
|
117
|
+
| `GET /quote/{symbol}/` | Free | Stock quote |
|
|
118
|
+
| `GET /quotes/?symbols=...` | Starter+ | Batch quotes (up to 50) |
|
|
119
|
+
| `GET /historical/{symbol}/` | Starter+ | Historical OHLCV data |
|
|
120
|
+
| `GET /market/summary/` | Free | Market overview & TASI index |
|
|
121
|
+
| `GET /market/gainers/` | Free | Top gainers |
|
|
122
|
+
| `GET /market/losers/` | Free | Top losers |
|
|
123
|
+
| `GET /market/volume/` | Free | Volume leaders |
|
|
124
|
+
| `GET /market/value/` | Free | Value leaders |
|
|
125
|
+
| `GET /market/sectors/` | Free | Sector performance |
|
|
126
|
+
| `GET /company/{symbol}/` | Free+ | Company info (tiered by plan) |
|
|
127
|
+
| `GET /financials/{symbol}/` | Starter+ | Financial statements |
|
|
128
|
+
| `GET /dividends/{symbol}/` | Starter+ | Dividend history & yield |
|
|
129
|
+
| `GET /events/` | Pro+ | AI-generated stock events |
|
|
130
|
+
|
|
131
|
+
All endpoints require the `X-API-Key` header.
|
|
132
|
+
|
|
133
|
+
Full docs: [sahmk.sa/developers/docs](https://sahmk.sa/developers/docs)
|
|
134
|
+
|
|
135
|
+
## WebSocket Streaming (Pro+)
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
import asyncio
|
|
139
|
+
from sahmk import SahmkClient
|
|
140
|
+
|
|
141
|
+
client = SahmkClient("your_api_key")
|
|
142
|
+
|
|
143
|
+
async def on_quote(msg):
|
|
144
|
+
symbol = msg["symbol"]
|
|
145
|
+
price = msg["data"]["price"]
|
|
146
|
+
print(f"{symbol}: {price}")
|
|
147
|
+
|
|
148
|
+
asyncio.run(client.stream(["2222", "1120"], on_quote=on_quote))
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Connection URL: `wss://app.sahmk.sa/ws/v1/stocks/?api_key=YOUR_KEY`
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT — see [LICENSE](LICENSE)
|
sahmk-0.1.0/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# SAHMK Python SDK
|
|
2
|
+
|
|
3
|
+
A lightweight Python client for the [SAHMK Developer API](https://sahmk.sa/developers) — real-time and historical Saudi stock market (Tadawul) data.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Real-time quotes** — live prices for 350+ Tadawul stocks
|
|
8
|
+
- **Batch quotes** — up to 50 stocks in a single request
|
|
9
|
+
- **Historical data** — daily/weekly/monthly OHLCV with custom date ranges
|
|
10
|
+
- **Market overview** — TASI index, gainers, losers, volume/value leaders, sectors
|
|
11
|
+
- **Company info** — fundamentals, technicals, valuation, analyst consensus (by plan)
|
|
12
|
+
- **Financials** — income statements, balance sheets, cash flow
|
|
13
|
+
- **Dividends** — history, yield, upcoming payments
|
|
14
|
+
- **Events** — AI-generated stock event summaries
|
|
15
|
+
- **WebSocket streaming** — real-time price updates pushed to you (Pro+ plan)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
After the first PyPI release:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install sahmk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or clone this repo for local development:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git clone https://github.com/sahmk-sa/sahmk-python.git
|
|
29
|
+
cd sahmk-python
|
|
30
|
+
pip install -r requirements.txt
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from sahmk import SahmkClient
|
|
37
|
+
|
|
38
|
+
client = SahmkClient("your_api_key")
|
|
39
|
+
|
|
40
|
+
# Get a stock quote
|
|
41
|
+
quote = client.quote("2222")
|
|
42
|
+
print(f"{quote['name_en']}: {quote['price']} SAR ({quote['change_percent']}%)")
|
|
43
|
+
|
|
44
|
+
# Get market summary
|
|
45
|
+
market = client.market_summary()
|
|
46
|
+
print(f"TASI: {market['index_value']} ({market['index_change_percent']}%)")
|
|
47
|
+
|
|
48
|
+
# Batch quotes (Starter+ plan)
|
|
49
|
+
result = client.quotes(["2222", "1120", "4191"])
|
|
50
|
+
for q in result["quotes"]:
|
|
51
|
+
print(f"{q['symbol']}: {q['price']}")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Get Your API Key
|
|
55
|
+
|
|
56
|
+
1. Sign up at [sahmk.sa/developers](https://sahmk.sa/developers)
|
|
57
|
+
2. Verify your email
|
|
58
|
+
3. Go to Dashboard → API Keys → Create Key
|
|
59
|
+
4. Copy your key (starts with `shmk_live_` or `shmk_test_`)
|
|
60
|
+
|
|
61
|
+
## Plans
|
|
62
|
+
|
|
63
|
+
| Plan | Price | Requests/Day | WebSocket |
|
|
64
|
+
|------|-------|-------------|-----------|
|
|
65
|
+
| Free | 0 SAR | 100 | - |
|
|
66
|
+
| Starter | 149 SAR/mo | 5,000 | - |
|
|
67
|
+
| Pro | 499 SAR/mo | 50,000 | Yes |
|
|
68
|
+
| Enterprise | 1,499+ SAR/mo | Unlimited | Yes |
|
|
69
|
+
|
|
70
|
+
## Examples
|
|
71
|
+
|
|
72
|
+
Check the [`examples/`](examples/) directory:
|
|
73
|
+
|
|
74
|
+
| File | Description |
|
|
75
|
+
|------|-------------|
|
|
76
|
+
| [`quote.py`](examples/quote.py) | Get a single stock quote |
|
|
77
|
+
| [`batch_quotes.py`](examples/batch_quotes.py) | Fetch multiple stocks at once |
|
|
78
|
+
| [`historical.py`](examples/historical.py) | Historical price data |
|
|
79
|
+
| [`market_summary.py`](examples/market_summary.py) | Market overview and movers |
|
|
80
|
+
| [`websocket_stream.py`](examples/websocket_stream.py) | Real-time WebSocket streaming (Pro+) |
|
|
81
|
+
|
|
82
|
+
## API Reference
|
|
83
|
+
|
|
84
|
+
Base URL: `https://app.sahmk.sa/api/v1`
|
|
85
|
+
|
|
86
|
+
| Endpoint | Plan | Description |
|
|
87
|
+
|----------|------|-------------|
|
|
88
|
+
| `GET /quote/{symbol}/` | Free | Stock quote |
|
|
89
|
+
| `GET /quotes/?symbols=...` | Starter+ | Batch quotes (up to 50) |
|
|
90
|
+
| `GET /historical/{symbol}/` | Starter+ | Historical OHLCV data |
|
|
91
|
+
| `GET /market/summary/` | Free | Market overview & TASI index |
|
|
92
|
+
| `GET /market/gainers/` | Free | Top gainers |
|
|
93
|
+
| `GET /market/losers/` | Free | Top losers |
|
|
94
|
+
| `GET /market/volume/` | Free | Volume leaders |
|
|
95
|
+
| `GET /market/value/` | Free | Value leaders |
|
|
96
|
+
| `GET /market/sectors/` | Free | Sector performance |
|
|
97
|
+
| `GET /company/{symbol}/` | Free+ | Company info (tiered by plan) |
|
|
98
|
+
| `GET /financials/{symbol}/` | Starter+ | Financial statements |
|
|
99
|
+
| `GET /dividends/{symbol}/` | Starter+ | Dividend history & yield |
|
|
100
|
+
| `GET /events/` | Pro+ | AI-generated stock events |
|
|
101
|
+
|
|
102
|
+
All endpoints require the `X-API-Key` header.
|
|
103
|
+
|
|
104
|
+
Full docs: [sahmk.sa/developers/docs](https://sahmk.sa/developers/docs)
|
|
105
|
+
|
|
106
|
+
## WebSocket Streaming (Pro+)
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import asyncio
|
|
110
|
+
from sahmk import SahmkClient
|
|
111
|
+
|
|
112
|
+
client = SahmkClient("your_api_key")
|
|
113
|
+
|
|
114
|
+
async def on_quote(msg):
|
|
115
|
+
symbol = msg["symbol"]
|
|
116
|
+
price = msg["data"]["price"]
|
|
117
|
+
print(f"{symbol}: {price}")
|
|
118
|
+
|
|
119
|
+
asyncio.run(client.stream(["2222", "1120"], on_quote=on_quote))
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Connection URL: `wss://app.sahmk.sa/ws/v1/stocks/?api_key=YOUR_KEY`
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT — see [LICENSE](LICENSE)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sahmk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Lightweight Python client for the SAHMK Developer API."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Sahmk", email = "developer@sahmk.sa" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["stocks", "tadawul", "market-data", "websocket", "api"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Topic :: Office/Business :: Financial",
|
|
27
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"requests>=2.28.0",
|
|
31
|
+
"websockets>=12.0"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/sahmk-sa/sahmk-python"
|
|
36
|
+
Documentation = "https://sahmk.sa/developers/docs"
|
|
37
|
+
Repository = "https://github.com/sahmk-sa/sahmk-python"
|
|
38
|
+
Issues = "https://github.com/sahmk-sa/sahmk-python/issues"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools]
|
|
41
|
+
include-package-data = true
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.packages.find]
|
|
44
|
+
where = ["."]
|
|
45
|
+
include = ["sahmk*"]
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SAHMK Python Client
|
|
3
|
+
|
|
4
|
+
A lightweight wrapper for the SAHMK Developer API.
|
|
5
|
+
https://sahmk.sa/developers/docs
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
BASE_URL = "https://app.sahmk.sa/api/v1"
|
|
13
|
+
WS_URL = "wss://app.sahmk.sa/ws/v1/stocks/"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SahmkError(Exception):
|
|
17
|
+
"""Base exception for SAHMK API errors."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, message, status_code=None, error_code=None, response=None):
|
|
20
|
+
super().__init__(message)
|
|
21
|
+
self.status_code = status_code
|
|
22
|
+
self.error_code = error_code
|
|
23
|
+
self.response = response
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SahmkClient:
|
|
27
|
+
"""
|
|
28
|
+
SAHMK Developer API client.
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
client = SahmkClient("your_api_key")
|
|
32
|
+
quote = client.quote("2222")
|
|
33
|
+
print(quote["price"])
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, api_key, base_url=None, timeout=30):
|
|
37
|
+
"""
|
|
38
|
+
Initialize the client.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
api_key: Your SAHMK API key (starts with shmk_live_ or shmk_test_)
|
|
42
|
+
base_url: Override the default API base URL
|
|
43
|
+
timeout: Request timeout in seconds (default: 30)
|
|
44
|
+
"""
|
|
45
|
+
self.api_key = api_key
|
|
46
|
+
self.base_url = (base_url or BASE_URL).rstrip("/")
|
|
47
|
+
self.timeout = timeout
|
|
48
|
+
self.session = requests.Session()
|
|
49
|
+
self.session.headers.update({"X-API-Key": api_key})
|
|
50
|
+
|
|
51
|
+
def _request(self, method, endpoint, params=None):
|
|
52
|
+
"""Make an API request."""
|
|
53
|
+
url = f"{self.base_url}{endpoint}"
|
|
54
|
+
try:
|
|
55
|
+
response = self.session.request(
|
|
56
|
+
method, url, params=params, timeout=self.timeout
|
|
57
|
+
)
|
|
58
|
+
except requests.RequestException as e:
|
|
59
|
+
raise SahmkError(f"Request failed: {e}")
|
|
60
|
+
|
|
61
|
+
if response.status_code == 429:
|
|
62
|
+
raise SahmkError(
|
|
63
|
+
"Rate limit exceeded. Check X-RateLimit-Remaining header.",
|
|
64
|
+
status_code=429,
|
|
65
|
+
error_code="RATE_LIMIT",
|
|
66
|
+
response=response,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if response.status_code != 200:
|
|
70
|
+
try:
|
|
71
|
+
body = response.json()
|
|
72
|
+
err = body.get("error", {})
|
|
73
|
+
code = err.get("code", "UNKNOWN")
|
|
74
|
+
message = err.get("message", response.text)
|
|
75
|
+
except (ValueError, KeyError):
|
|
76
|
+
code = "UNKNOWN"
|
|
77
|
+
message = response.text
|
|
78
|
+
raise SahmkError(
|
|
79
|
+
f"API error {response.status_code}: {message}",
|
|
80
|
+
status_code=response.status_code,
|
|
81
|
+
error_code=code,
|
|
82
|
+
response=response,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return response.json()
|
|
86
|
+
|
|
87
|
+
# -------------------------------------------------------------------------
|
|
88
|
+
# Quotes
|
|
89
|
+
# -------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
def quote(self, symbol):
|
|
92
|
+
"""
|
|
93
|
+
Get a stock quote.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
symbol: Stock symbol (e.g., "2222" for Aramco)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
dict with keys: symbol, name, name_en, price, change,
|
|
100
|
+
change_percent, volume, bid, ask, liquidity, etc.
|
|
101
|
+
"""
|
|
102
|
+
return self._request("GET", f"/quote/{symbol}/")
|
|
103
|
+
|
|
104
|
+
def quotes(self, symbols):
|
|
105
|
+
"""
|
|
106
|
+
Get batch quotes for multiple stocks (Starter+ plan).
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
symbols: List of stock symbols (up to 50)
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
dict with "quotes" list and "count"
|
|
113
|
+
"""
|
|
114
|
+
if len(symbols) > 50:
|
|
115
|
+
raise SahmkError("Maximum 50 symbols per batch request")
|
|
116
|
+
return self._request(
|
|
117
|
+
"GET", "/quotes/", params={"symbols": ",".join(symbols)}
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# -------------------------------------------------------------------------
|
|
121
|
+
# Historical
|
|
122
|
+
# -------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
def historical(self, symbol, from_date=None, to_date=None, interval=None):
|
|
125
|
+
"""
|
|
126
|
+
Get historical OHLCV data (Starter+ plan).
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
symbol: Stock symbol
|
|
130
|
+
from_date: Start date YYYY-MM-DD (default: 30 days ago)
|
|
131
|
+
to_date: End date YYYY-MM-DD (default: today)
|
|
132
|
+
interval: "1d", "1w", or "1m" (default: "1d")
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
dict with "symbol", "interval", "from", "to", "count", and "data" list
|
|
136
|
+
"""
|
|
137
|
+
params = {}
|
|
138
|
+
if from_date:
|
|
139
|
+
params["from"] = from_date
|
|
140
|
+
if to_date:
|
|
141
|
+
params["to"] = to_date
|
|
142
|
+
if interval:
|
|
143
|
+
params["interval"] = interval
|
|
144
|
+
return self._request("GET", f"/historical/{symbol}/", params=params)
|
|
145
|
+
|
|
146
|
+
# -------------------------------------------------------------------------
|
|
147
|
+
# Market
|
|
148
|
+
# -------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
def market_summary(self):
|
|
151
|
+
"""Get market overview (TASI index, change, volume, market_mood)."""
|
|
152
|
+
return self._request("GET", "/market/summary/")
|
|
153
|
+
|
|
154
|
+
def gainers(self, limit=None):
|
|
155
|
+
"""
|
|
156
|
+
Get top gaining stocks.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
limit: Number of results (default: 10, max: 50)
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
dict with "gainers" list and "count"
|
|
163
|
+
"""
|
|
164
|
+
params = {}
|
|
165
|
+
if limit is not None:
|
|
166
|
+
params["limit"] = limit
|
|
167
|
+
return self._request("GET", "/market/gainers/", params=params or None)
|
|
168
|
+
|
|
169
|
+
def losers(self, limit=None):
|
|
170
|
+
"""
|
|
171
|
+
Get top losing stocks.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
limit: Number of results (default: 10, max: 50)
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
dict with "losers" list and "count"
|
|
178
|
+
"""
|
|
179
|
+
params = {}
|
|
180
|
+
if limit is not None:
|
|
181
|
+
params["limit"] = limit
|
|
182
|
+
return self._request("GET", "/market/losers/", params=params or None)
|
|
183
|
+
|
|
184
|
+
def volume_leaders(self, limit=None):
|
|
185
|
+
"""
|
|
186
|
+
Get stocks with highest trading volume.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
limit: Number of results (default: 10, max: 50)
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
dict with "stocks" list and "count"
|
|
193
|
+
"""
|
|
194
|
+
params = {}
|
|
195
|
+
if limit is not None:
|
|
196
|
+
params["limit"] = limit
|
|
197
|
+
return self._request("GET", "/market/volume/", params=params or None)
|
|
198
|
+
|
|
199
|
+
def value_leaders(self, limit=None):
|
|
200
|
+
"""
|
|
201
|
+
Get stocks with highest trading value (SAR).
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
limit: Number of results (default: 10, max: 50)
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
dict with "stocks" list and "count"
|
|
208
|
+
"""
|
|
209
|
+
params = {}
|
|
210
|
+
if limit is not None:
|
|
211
|
+
params["limit"] = limit
|
|
212
|
+
return self._request("GET", "/market/value/", params=params or None)
|
|
213
|
+
|
|
214
|
+
def sectors(self):
|
|
215
|
+
"""
|
|
216
|
+
Get sector performance.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
dict with "sectors" list and "count"
|
|
220
|
+
"""
|
|
221
|
+
return self._request("GET", "/market/sectors/")
|
|
222
|
+
|
|
223
|
+
# -------------------------------------------------------------------------
|
|
224
|
+
# Company Data
|
|
225
|
+
# -------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
def company(self, symbol):
|
|
228
|
+
"""
|
|
229
|
+
Get company info. Response varies by plan:
|
|
230
|
+
Free (basic), Starter (fundamentals), Pro (technicals, valuation, analysts).
|
|
231
|
+
"""
|
|
232
|
+
return self._request("GET", f"/company/{symbol}/")
|
|
233
|
+
|
|
234
|
+
def financials(self, symbol):
|
|
235
|
+
"""Get financial statements (income, balance sheet, cash flow). Starter+ plan."""
|
|
236
|
+
return self._request("GET", f"/financials/{symbol}/")
|
|
237
|
+
|
|
238
|
+
def dividends(self, symbol):
|
|
239
|
+
"""Get dividend history and yield. Starter+ plan."""
|
|
240
|
+
return self._request("GET", f"/dividends/{symbol}/")
|
|
241
|
+
|
|
242
|
+
# -------------------------------------------------------------------------
|
|
243
|
+
# Events
|
|
244
|
+
# -------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
def events(self, symbol=None, limit=None):
|
|
247
|
+
"""
|
|
248
|
+
Get AI-generated stock event summaries (Pro+ plan).
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
symbol: Optional — filter events for a specific stock
|
|
252
|
+
limit: Number of results (default: 20)
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
dict with "events" list, "count", and "available_types"
|
|
256
|
+
"""
|
|
257
|
+
params = {}
|
|
258
|
+
if symbol:
|
|
259
|
+
params["symbol"] = symbol
|
|
260
|
+
if limit is not None:
|
|
261
|
+
params["limit"] = limit
|
|
262
|
+
return self._request("GET", "/events/", params=params or None)
|
|
263
|
+
|
|
264
|
+
# -------------------------------------------------------------------------
|
|
265
|
+
# WebSocket Streaming (Pro+ plan)
|
|
266
|
+
# -------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
async def stream(self, symbols, on_quote=None, on_error=None, ping_interval=30):
|
|
269
|
+
"""
|
|
270
|
+
Stream real-time quotes via WebSocket (Pro+ plan).
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
symbols: List of stock symbols to subscribe to (max 20 per call,
|
|
274
|
+
max 60 per connection on Pro, 200 on Enterprise)
|
|
275
|
+
on_quote: Async callback — on_quote(data) where data contains
|
|
276
|
+
symbol, timestamp, and quote fields (price, change, etc.)
|
|
277
|
+
on_error: Async callback — on_error(error_data)
|
|
278
|
+
ping_interval: Seconds between keep-alive pings (default: 30)
|
|
279
|
+
|
|
280
|
+
Usage:
|
|
281
|
+
async def handle_quote(data):
|
|
282
|
+
print(f"{data['symbol']}: {data['data']['price']}")
|
|
283
|
+
|
|
284
|
+
await client.stream(["2222", "1120"], on_quote=handle_quote)
|
|
285
|
+
"""
|
|
286
|
+
try:
|
|
287
|
+
import websockets
|
|
288
|
+
except ImportError:
|
|
289
|
+
raise SahmkError(
|
|
290
|
+
"websockets package required for streaming. "
|
|
291
|
+
"Install it with: pip install websockets"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
import asyncio
|
|
295
|
+
|
|
296
|
+
url = f"{WS_URL}?api_key={self.api_key}"
|
|
297
|
+
|
|
298
|
+
async with websockets.connect(url) as ws:
|
|
299
|
+
msg = json.loads(await ws.recv())
|
|
300
|
+
if msg.get("type") == "error":
|
|
301
|
+
raise SahmkError(f"WebSocket error: {msg.get('message')}")
|
|
302
|
+
|
|
303
|
+
for i in range(0, len(symbols), 20):
|
|
304
|
+
batch = symbols[i : i + 20]
|
|
305
|
+
await ws.send(
|
|
306
|
+
json.dumps({"action": "subscribe", "symbols": batch})
|
|
307
|
+
)
|
|
308
|
+
ack = json.loads(await ws.recv())
|
|
309
|
+
if ack.get("type") == "error":
|
|
310
|
+
raise SahmkError(
|
|
311
|
+
f"Subscribe error: {ack.get('message')}"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
async def _ping_loop():
|
|
315
|
+
while True:
|
|
316
|
+
await asyncio.sleep(ping_interval)
|
|
317
|
+
try:
|
|
318
|
+
await ws.send(json.dumps({"action": "ping"}))
|
|
319
|
+
except Exception:
|
|
320
|
+
break
|
|
321
|
+
|
|
322
|
+
ping_task = asyncio.create_task(_ping_loop())
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
async for message in ws:
|
|
326
|
+
data = json.loads(message)
|
|
327
|
+
msg_type = data.get("type")
|
|
328
|
+
|
|
329
|
+
if msg_type == "quote" and on_quote:
|
|
330
|
+
await on_quote(data)
|
|
331
|
+
elif msg_type == "error" and on_error:
|
|
332
|
+
await on_error(data)
|
|
333
|
+
finally:
|
|
334
|
+
ping_task.cancel()
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sahmk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight Python client for the SAHMK Developer API.
|
|
5
|
+
Author-email: Sahmk <developer@sahmk.sa>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sahmk-sa/sahmk-python
|
|
8
|
+
Project-URL: Documentation, https://sahmk.sa/developers/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/sahmk-sa/sahmk-python
|
|
10
|
+
Project-URL: Issues, https://github.com/sahmk-sa/sahmk-python/issues
|
|
11
|
+
Keywords: stocks,tadawul,market-data,websocket,api
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: requests>=2.28.0
|
|
27
|
+
Requires-Dist: websockets>=12.0
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# SAHMK Python SDK
|
|
31
|
+
|
|
32
|
+
A lightweight Python client for the [SAHMK Developer API](https://sahmk.sa/developers) — real-time and historical Saudi stock market (Tadawul) data.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- **Real-time quotes** — live prices for 350+ Tadawul stocks
|
|
37
|
+
- **Batch quotes** — up to 50 stocks in a single request
|
|
38
|
+
- **Historical data** — daily/weekly/monthly OHLCV with custom date ranges
|
|
39
|
+
- **Market overview** — TASI index, gainers, losers, volume/value leaders, sectors
|
|
40
|
+
- **Company info** — fundamentals, technicals, valuation, analyst consensus (by plan)
|
|
41
|
+
- **Financials** — income statements, balance sheets, cash flow
|
|
42
|
+
- **Dividends** — history, yield, upcoming payments
|
|
43
|
+
- **Events** — AI-generated stock event summaries
|
|
44
|
+
- **WebSocket streaming** — real-time price updates pushed to you (Pro+ plan)
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
After the first PyPI release:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install sahmk
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or clone this repo for local development:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/sahmk-sa/sahmk-python.git
|
|
58
|
+
cd sahmk-python
|
|
59
|
+
pip install -r requirements.txt
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from sahmk import SahmkClient
|
|
66
|
+
|
|
67
|
+
client = SahmkClient("your_api_key")
|
|
68
|
+
|
|
69
|
+
# Get a stock quote
|
|
70
|
+
quote = client.quote("2222")
|
|
71
|
+
print(f"{quote['name_en']}: {quote['price']} SAR ({quote['change_percent']}%)")
|
|
72
|
+
|
|
73
|
+
# Get market summary
|
|
74
|
+
market = client.market_summary()
|
|
75
|
+
print(f"TASI: {market['index_value']} ({market['index_change_percent']}%)")
|
|
76
|
+
|
|
77
|
+
# Batch quotes (Starter+ plan)
|
|
78
|
+
result = client.quotes(["2222", "1120", "4191"])
|
|
79
|
+
for q in result["quotes"]:
|
|
80
|
+
print(f"{q['symbol']}: {q['price']}")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Get Your API Key
|
|
84
|
+
|
|
85
|
+
1. Sign up at [sahmk.sa/developers](https://sahmk.sa/developers)
|
|
86
|
+
2. Verify your email
|
|
87
|
+
3. Go to Dashboard → API Keys → Create Key
|
|
88
|
+
4. Copy your key (starts with `shmk_live_` or `shmk_test_`)
|
|
89
|
+
|
|
90
|
+
## Plans
|
|
91
|
+
|
|
92
|
+
| Plan | Price | Requests/Day | WebSocket |
|
|
93
|
+
|------|-------|-------------|-----------|
|
|
94
|
+
| Free | 0 SAR | 100 | - |
|
|
95
|
+
| Starter | 149 SAR/mo | 5,000 | - |
|
|
96
|
+
| Pro | 499 SAR/mo | 50,000 | Yes |
|
|
97
|
+
| Enterprise | 1,499+ SAR/mo | Unlimited | Yes |
|
|
98
|
+
|
|
99
|
+
## Examples
|
|
100
|
+
|
|
101
|
+
Check the [`examples/`](examples/) directory:
|
|
102
|
+
|
|
103
|
+
| File | Description |
|
|
104
|
+
|------|-------------|
|
|
105
|
+
| [`quote.py`](examples/quote.py) | Get a single stock quote |
|
|
106
|
+
| [`batch_quotes.py`](examples/batch_quotes.py) | Fetch multiple stocks at once |
|
|
107
|
+
| [`historical.py`](examples/historical.py) | Historical price data |
|
|
108
|
+
| [`market_summary.py`](examples/market_summary.py) | Market overview and movers |
|
|
109
|
+
| [`websocket_stream.py`](examples/websocket_stream.py) | Real-time WebSocket streaming (Pro+) |
|
|
110
|
+
|
|
111
|
+
## API Reference
|
|
112
|
+
|
|
113
|
+
Base URL: `https://app.sahmk.sa/api/v1`
|
|
114
|
+
|
|
115
|
+
| Endpoint | Plan | Description |
|
|
116
|
+
|----------|------|-------------|
|
|
117
|
+
| `GET /quote/{symbol}/` | Free | Stock quote |
|
|
118
|
+
| `GET /quotes/?symbols=...` | Starter+ | Batch quotes (up to 50) |
|
|
119
|
+
| `GET /historical/{symbol}/` | Starter+ | Historical OHLCV data |
|
|
120
|
+
| `GET /market/summary/` | Free | Market overview & TASI index |
|
|
121
|
+
| `GET /market/gainers/` | Free | Top gainers |
|
|
122
|
+
| `GET /market/losers/` | Free | Top losers |
|
|
123
|
+
| `GET /market/volume/` | Free | Volume leaders |
|
|
124
|
+
| `GET /market/value/` | Free | Value leaders |
|
|
125
|
+
| `GET /market/sectors/` | Free | Sector performance |
|
|
126
|
+
| `GET /company/{symbol}/` | Free+ | Company info (tiered by plan) |
|
|
127
|
+
| `GET /financials/{symbol}/` | Starter+ | Financial statements |
|
|
128
|
+
| `GET /dividends/{symbol}/` | Starter+ | Dividend history & yield |
|
|
129
|
+
| `GET /events/` | Pro+ | AI-generated stock events |
|
|
130
|
+
|
|
131
|
+
All endpoints require the `X-API-Key` header.
|
|
132
|
+
|
|
133
|
+
Full docs: [sahmk.sa/developers/docs](https://sahmk.sa/developers/docs)
|
|
134
|
+
|
|
135
|
+
## WebSocket Streaming (Pro+)
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
import asyncio
|
|
139
|
+
from sahmk import SahmkClient
|
|
140
|
+
|
|
141
|
+
client = SahmkClient("your_api_key")
|
|
142
|
+
|
|
143
|
+
async def on_quote(msg):
|
|
144
|
+
symbol = msg["symbol"]
|
|
145
|
+
price = msg["data"]["price"]
|
|
146
|
+
print(f"{symbol}: {price}")
|
|
147
|
+
|
|
148
|
+
asyncio.run(client.stream(["2222", "1120"], on_quote=on_quote))
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Connection URL: `wss://app.sahmk.sa/ws/v1/stocks/?api_key=YOUR_KEY`
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT — see [LICENSE](LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sahmk
|
sahmk-0.1.0/setup.cfg
ADDED