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 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,4 @@
1
+ from .client import SahmkClient
2
+
3
+ __version__ = "0.1.0"
4
+ __all__ = ["SahmkClient"]
@@ -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,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ sahmk/__init__.py
5
+ sahmk/client.py
6
+ sahmk.egg-info/PKG-INFO
7
+ sahmk.egg-info/SOURCES.txt
8
+ sahmk.egg-info/dependency_links.txt
9
+ sahmk.egg-info/requires.txt
10
+ sahmk.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ requests>=2.28.0
2
+ websockets>=12.0
@@ -0,0 +1 @@
1
+ sahmk
sahmk-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+