borsapy 0.4.0__py3-none-any.whl
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.
- borsapy/__init__.py +134 -0
- borsapy/_models/__init__.py +1 -0
- borsapy/_providers/__init__.py +5 -0
- borsapy/_providers/base.py +94 -0
- borsapy/_providers/bist_index.py +150 -0
- borsapy/_providers/btcturk.py +230 -0
- borsapy/_providers/canlidoviz.py +773 -0
- borsapy/_providers/dovizcom.py +869 -0
- borsapy/_providers/dovizcom_calendar.py +276 -0
- borsapy/_providers/dovizcom_tahvil.py +172 -0
- borsapy/_providers/hedeffiyat.py +376 -0
- borsapy/_providers/isin.py +247 -0
- borsapy/_providers/isyatirim.py +943 -0
- borsapy/_providers/isyatirim_screener.py +468 -0
- borsapy/_providers/kap.py +534 -0
- borsapy/_providers/paratic.py +278 -0
- borsapy/_providers/tcmb.py +317 -0
- borsapy/_providers/tefas.py +802 -0
- borsapy/_providers/viop.py +204 -0
- borsapy/bond.py +162 -0
- borsapy/cache.py +86 -0
- borsapy/calendar.py +272 -0
- borsapy/crypto.py +153 -0
- borsapy/exceptions.py +64 -0
- borsapy/fund.py +471 -0
- borsapy/fx.py +388 -0
- borsapy/index.py +285 -0
- borsapy/inflation.py +166 -0
- borsapy/market.py +53 -0
- borsapy/multi.py +227 -0
- borsapy/screener.py +365 -0
- borsapy/ticker.py +1196 -0
- borsapy/viop.py +162 -0
- borsapy-0.4.0.dist-info/METADATA +969 -0
- borsapy-0.4.0.dist-info/RECORD +37 -0
- borsapy-0.4.0.dist-info/WHEEL +4 -0
- borsapy-0.4.0.dist-info/licenses/LICENSE +190 -0
borsapy/fx.py
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""FX class for forex and commodity data - yfinance-like API."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from borsapy._providers.canlidoviz import get_canlidoviz_provider
|
|
9
|
+
from borsapy._providers.dovizcom import get_dovizcom_provider
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def banks() -> list[str]:
|
|
13
|
+
"""
|
|
14
|
+
Get list of supported banks for exchange rates.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
List of bank codes.
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
>>> import borsapy as bp
|
|
21
|
+
>>> bp.banks()
|
|
22
|
+
['akbank', 'albaraka', 'alternatifbank', 'anadolubank', ...]
|
|
23
|
+
"""
|
|
24
|
+
return get_dovizcom_provider().get_banks()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def metal_institutions() -> list[str]:
|
|
28
|
+
"""
|
|
29
|
+
Get list of supported precious metal assets for institution rates.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of asset codes that support institution_rates.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
>>> import borsapy as bp
|
|
36
|
+
>>> bp.metal_institutions()
|
|
37
|
+
['gram-altin', 'gram-gumus', 'gram-platin', 'ons-altin']
|
|
38
|
+
"""
|
|
39
|
+
return get_dovizcom_provider().get_metal_institutions()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class FX:
|
|
43
|
+
"""
|
|
44
|
+
A yfinance-like interface for forex and commodity data.
|
|
45
|
+
|
|
46
|
+
Supported assets:
|
|
47
|
+
- Currencies: USD, EUR, GBP, JPY, CHF, CAD, AUD (+ 58 more via canlidoviz)
|
|
48
|
+
- Precious Metals: gram-altin, gumus, ons-altin, gram-platin, XAG-USD, XPT-USD, XPD-USD
|
|
49
|
+
- Energy: BRENT
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
>>> import borsapy as bp
|
|
53
|
+
>>> usd = bp.FX("USD")
|
|
54
|
+
>>> usd.current
|
|
55
|
+
{'symbol': 'USD', 'last': 34.85, ...}
|
|
56
|
+
>>> usd.history(period="1mo")
|
|
57
|
+
Open High Low Close
|
|
58
|
+
Date
|
|
59
|
+
2024-12-01 34.50 34.80 34.40 34.75
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
>>> gold = bp.FX("gram-altin")
|
|
63
|
+
>>> gold.current
|
|
64
|
+
{'symbol': 'gram-altin', 'last': 2850.50, ...}
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, asset: str):
|
|
68
|
+
"""
|
|
69
|
+
Initialize an FX object.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
asset: Asset code (USD, EUR, gram-altin, BRENT, etc.)
|
|
73
|
+
"""
|
|
74
|
+
self._asset = asset
|
|
75
|
+
self._canlidoviz = get_canlidoviz_provider()
|
|
76
|
+
self._dovizcom = get_dovizcom_provider()
|
|
77
|
+
self._current_cache: dict[str, Any] | None = None
|
|
78
|
+
|
|
79
|
+
def _use_canlidoviz(self) -> bool:
|
|
80
|
+
"""Check if canlidoviz should be used for this asset."""
|
|
81
|
+
asset_upper = self._asset.upper()
|
|
82
|
+
# Currencies
|
|
83
|
+
if asset_upper in self._canlidoviz.CURRENCY_IDS:
|
|
84
|
+
return True
|
|
85
|
+
# Metals supported by canlidoviz (TRY prices)
|
|
86
|
+
if self._asset in self._canlidoviz.METAL_IDS:
|
|
87
|
+
return True
|
|
88
|
+
# Energy supported by canlidoviz (USD prices)
|
|
89
|
+
if asset_upper in self._canlidoviz.ENERGY_IDS:
|
|
90
|
+
return True
|
|
91
|
+
# Commodities supported by canlidoviz (USD prices)
|
|
92
|
+
if asset_upper in self._canlidoviz.COMMODITY_IDS:
|
|
93
|
+
return True
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def asset(self) -> str:
|
|
98
|
+
"""Return the asset code."""
|
|
99
|
+
return self._asset
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def symbol(self) -> str:
|
|
103
|
+
"""Return the asset code (alias for asset)."""
|
|
104
|
+
return self._asset
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def current(self) -> dict[str, Any]:
|
|
108
|
+
"""
|
|
109
|
+
Get current price information.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Dictionary with current market data:
|
|
113
|
+
- symbol: Asset code
|
|
114
|
+
- last: Last price
|
|
115
|
+
- open: Opening price
|
|
116
|
+
- high: Day high
|
|
117
|
+
- low: Day low
|
|
118
|
+
- update_time: Last update timestamp
|
|
119
|
+
"""
|
|
120
|
+
if self._current_cache is None:
|
|
121
|
+
if self._use_canlidoviz():
|
|
122
|
+
self._current_cache = self._canlidoviz.get_current(self._asset)
|
|
123
|
+
else:
|
|
124
|
+
try:
|
|
125
|
+
self._current_cache = self._dovizcom.get_current(self._asset)
|
|
126
|
+
except Exception:
|
|
127
|
+
# Fallback to bank_rates for currencies not supported by APIs
|
|
128
|
+
self._current_cache = self._current_from_bank_rates()
|
|
129
|
+
return self._current_cache
|
|
130
|
+
|
|
131
|
+
def _current_from_bank_rates(self) -> dict[str, Any]:
|
|
132
|
+
"""Calculate current price from bank rates as fallback."""
|
|
133
|
+
from datetime import datetime
|
|
134
|
+
|
|
135
|
+
rates = self._dovizcom.get_bank_rates(self._asset)
|
|
136
|
+
if rates.empty:
|
|
137
|
+
raise ValueError(f"No data available for {self._asset}")
|
|
138
|
+
|
|
139
|
+
# Calculate average mid price from all banks
|
|
140
|
+
mids = (rates["buy"] + rates["sell"]) / 2
|
|
141
|
+
avg_mid = float(mids.mean())
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
"symbol": self._asset,
|
|
145
|
+
"last": avg_mid,
|
|
146
|
+
"open": avg_mid,
|
|
147
|
+
"high": float(rates["sell"].max()),
|
|
148
|
+
"low": float(rates["buy"].min()),
|
|
149
|
+
"update_time": datetime.now(),
|
|
150
|
+
"source": "bank_rates_avg",
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def info(self) -> dict[str, Any]:
|
|
155
|
+
"""Alias for current property (yfinance compatibility)."""
|
|
156
|
+
return self.current
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def bank_rates(self) -> pd.DataFrame:
|
|
160
|
+
"""
|
|
161
|
+
Get exchange rates from all banks.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
DataFrame with columns: bank, bank_name, currency, buy, sell, spread
|
|
165
|
+
|
|
166
|
+
Examples:
|
|
167
|
+
>>> usd = FX("USD")
|
|
168
|
+
>>> usd.bank_rates
|
|
169
|
+
bank bank_name currency buy sell spread
|
|
170
|
+
0 akbank Akbank USD 41.6610 44.1610 5.99
|
|
171
|
+
1 garanti Garanti BBVA USD 41.7000 44.2000 5.99
|
|
172
|
+
...
|
|
173
|
+
"""
|
|
174
|
+
return self._dovizcom.get_bank_rates(self._asset)
|
|
175
|
+
|
|
176
|
+
def bank_rate(self, bank: str) -> dict[str, Any]:
|
|
177
|
+
"""
|
|
178
|
+
Get exchange rate from a specific bank.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
bank: Bank code (akbank, garanti, isbank, ziraat, etc.)
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Dictionary with keys: bank, currency, buy, sell, spread
|
|
185
|
+
|
|
186
|
+
Examples:
|
|
187
|
+
>>> usd = FX("USD")
|
|
188
|
+
>>> usd.bank_rate("akbank")
|
|
189
|
+
{'bank': 'akbank', 'currency': 'USD', 'buy': 41.6610, 'sell': 44.1610, 'spread': 5.99}
|
|
190
|
+
"""
|
|
191
|
+
return self._dovizcom.get_bank_rates(self._asset, bank=bank)
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def banks() -> list[str]:
|
|
195
|
+
"""
|
|
196
|
+
Get list of supported banks.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List of bank codes.
|
|
200
|
+
|
|
201
|
+
Examples:
|
|
202
|
+
>>> FX.banks()
|
|
203
|
+
['akbank', 'albaraka', 'alternatifbank', 'anadolubank', ...]
|
|
204
|
+
"""
|
|
205
|
+
from borsapy._providers.dovizcom import get_dovizcom_provider
|
|
206
|
+
|
|
207
|
+
return get_dovizcom_provider().get_banks()
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def institution_rates(self) -> pd.DataFrame:
|
|
211
|
+
"""
|
|
212
|
+
Get precious metal rates from all institutions (kuyumcular, bankalar).
|
|
213
|
+
|
|
214
|
+
Only available for precious metals: gram-altin, gram-gumus, ons-altin,
|
|
215
|
+
gram-platin
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
DataFrame with columns: institution, institution_name, asset, buy, sell, spread
|
|
219
|
+
|
|
220
|
+
Examples:
|
|
221
|
+
>>> gold = FX("gram-altin")
|
|
222
|
+
>>> gold.institution_rates
|
|
223
|
+
institution institution_name asset buy sell spread
|
|
224
|
+
0 altinkaynak Altınkaynak gram-altin 6315.00 6340.00 0.40
|
|
225
|
+
1 akbank Akbank gram-altin 6310.00 6330.00 0.32
|
|
226
|
+
...
|
|
227
|
+
"""
|
|
228
|
+
return self._dovizcom.get_metal_institution_rates(self._asset)
|
|
229
|
+
|
|
230
|
+
def institution_rate(self, institution: str) -> dict[str, Any]:
|
|
231
|
+
"""
|
|
232
|
+
Get precious metal rate from a specific institution.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
institution: Institution slug (kapalicarsi, altinkaynak, akbank, etc.)
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Dictionary with keys: institution, institution_name, asset, buy, sell, spread
|
|
239
|
+
|
|
240
|
+
Examples:
|
|
241
|
+
>>> gold = FX("gram-altin")
|
|
242
|
+
>>> gold.institution_rate("akbank")
|
|
243
|
+
{'institution': 'akbank', 'institution_name': 'Akbank', 'asset': 'gram-altin',
|
|
244
|
+
'buy': 6310.00, 'sell': 6330.00, 'spread': 0.32}
|
|
245
|
+
"""
|
|
246
|
+
return self._dovizcom.get_metal_institution_rates(self._asset, institution=institution)
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def metal_institutions() -> list[str]:
|
|
250
|
+
"""
|
|
251
|
+
Get list of supported precious metal assets for institution rates.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
List of asset codes that support institution_rates.
|
|
255
|
+
|
|
256
|
+
Examples:
|
|
257
|
+
>>> FX.metal_institutions()
|
|
258
|
+
['gram-altin', 'gram-gumus', 'gram-platin', 'ons-altin']
|
|
259
|
+
"""
|
|
260
|
+
from borsapy._providers.dovizcom import get_dovizcom_provider
|
|
261
|
+
|
|
262
|
+
return get_dovizcom_provider().get_metal_institutions()
|
|
263
|
+
|
|
264
|
+
def history(
|
|
265
|
+
self,
|
|
266
|
+
period: str = "1mo",
|
|
267
|
+
start: datetime | str | None = None,
|
|
268
|
+
end: datetime | str | None = None,
|
|
269
|
+
) -> pd.DataFrame:
|
|
270
|
+
"""
|
|
271
|
+
Get historical OHLC data.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
period: How much data to fetch. Valid periods:
|
|
275
|
+
1d, 5d, 1mo, 3mo, 6mo, 1y.
|
|
276
|
+
Ignored if start is provided.
|
|
277
|
+
start: Start date (string or datetime).
|
|
278
|
+
end: End date (string or datetime). Defaults to today.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
DataFrame with columns: Open, High, Low, Close.
|
|
282
|
+
Index is the Date.
|
|
283
|
+
|
|
284
|
+
Examples:
|
|
285
|
+
>>> fx = FX("USD")
|
|
286
|
+
>>> fx.history(period="1mo") # Last month
|
|
287
|
+
>>> fx.history(start="2024-01-01", end="2024-06-30") # Date range
|
|
288
|
+
"""
|
|
289
|
+
start_dt = self._parse_date(start) if start else None
|
|
290
|
+
end_dt = self._parse_date(end) if end else None
|
|
291
|
+
|
|
292
|
+
if self._use_canlidoviz():
|
|
293
|
+
return self._canlidoviz.get_history(
|
|
294
|
+
asset=self._asset,
|
|
295
|
+
period=period,
|
|
296
|
+
start=start_dt,
|
|
297
|
+
end=end_dt,
|
|
298
|
+
)
|
|
299
|
+
else:
|
|
300
|
+
return self._dovizcom.get_history(
|
|
301
|
+
asset=self._asset,
|
|
302
|
+
period=period,
|
|
303
|
+
start=start_dt,
|
|
304
|
+
end=end_dt,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
def institution_history(
|
|
308
|
+
self,
|
|
309
|
+
institution: str,
|
|
310
|
+
period: str = "1mo",
|
|
311
|
+
start: datetime | str | None = None,
|
|
312
|
+
end: datetime | str | None = None,
|
|
313
|
+
) -> pd.DataFrame:
|
|
314
|
+
"""
|
|
315
|
+
Get historical OHLC data from a specific institution.
|
|
316
|
+
|
|
317
|
+
Supports both precious metals and currencies.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
institution: Institution slug (akbank, kapalicarsi, harem, etc.)
|
|
321
|
+
period: How much data to fetch. Valid periods:
|
|
322
|
+
1d, 5d, 1mo, 3mo, 6mo, 1y.
|
|
323
|
+
Ignored if start is provided.
|
|
324
|
+
start: Start date (string or datetime).
|
|
325
|
+
end: End date (string or datetime). Defaults to today.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
DataFrame with columns: Open, High, Low, Close.
|
|
329
|
+
Index is the Date.
|
|
330
|
+
Note: Banks typically return only Close values (Open/High/Low = 0).
|
|
331
|
+
|
|
332
|
+
Examples:
|
|
333
|
+
>>> # Metal history
|
|
334
|
+
>>> gold = FX("gram-altin")
|
|
335
|
+
>>> gold.institution_history("akbank", period="1mo")
|
|
336
|
+
>>> gold.institution_history("kapalicarsi", start="2024-01-01")
|
|
337
|
+
|
|
338
|
+
>>> # Currency history
|
|
339
|
+
>>> usd = FX("USD")
|
|
340
|
+
>>> usd.institution_history("akbank", period="1mo")
|
|
341
|
+
>>> usd.institution_history("garanti-bbva", period="5d")
|
|
342
|
+
"""
|
|
343
|
+
start_dt = self._parse_date(start) if start else None
|
|
344
|
+
end_dt = self._parse_date(end) if end else None
|
|
345
|
+
|
|
346
|
+
# Use canlidoviz for currencies and precious metals (bank-specific rates)
|
|
347
|
+
asset_upper = self._asset.upper()
|
|
348
|
+
use_canlidoviz = (
|
|
349
|
+
asset_upper in self._canlidoviz.CURRENCY_IDS
|
|
350
|
+
or self._asset in ("gram-altin", "gumus", "gram-platin")
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
if use_canlidoviz:
|
|
354
|
+
# Check if canlidoviz has bank ID for this asset
|
|
355
|
+
try:
|
|
356
|
+
return self._canlidoviz.get_history(
|
|
357
|
+
asset=self._asset,
|
|
358
|
+
period=period,
|
|
359
|
+
start=start_dt,
|
|
360
|
+
end=end_dt,
|
|
361
|
+
institution=institution,
|
|
362
|
+
)
|
|
363
|
+
except Exception:
|
|
364
|
+
# Fall back to dovizcom if canlidoviz doesn't support this bank
|
|
365
|
+
pass
|
|
366
|
+
|
|
367
|
+
# Use dovizcom for other metals and unsupported banks
|
|
368
|
+
return self._dovizcom.get_institution_history(
|
|
369
|
+
asset=self._asset,
|
|
370
|
+
institution=institution,
|
|
371
|
+
period=period,
|
|
372
|
+
start=start_dt,
|
|
373
|
+
end=end_dt,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
def _parse_date(self, date: str | datetime) -> datetime:
|
|
377
|
+
"""Parse a date string to datetime."""
|
|
378
|
+
if isinstance(date, datetime):
|
|
379
|
+
return date
|
|
380
|
+
for fmt in ["%Y-%m-%d", "%Y/%m/%d", "%d-%m-%Y", "%d/%m/%Y"]:
|
|
381
|
+
try:
|
|
382
|
+
return datetime.strptime(date, fmt)
|
|
383
|
+
except ValueError:
|
|
384
|
+
continue
|
|
385
|
+
raise ValueError(f"Could not parse date: {date}")
|
|
386
|
+
|
|
387
|
+
def __repr__(self) -> str:
|
|
388
|
+
return f"FX('{self._asset}')"
|
borsapy/index.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Index class for market index data - yfinance-like API."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from borsapy._providers.bist_index import get_bist_index_provider
|
|
9
|
+
from borsapy._providers.paratic import get_paratic_provider
|
|
10
|
+
|
|
11
|
+
# Known market indices with their names
|
|
12
|
+
INDICES = {
|
|
13
|
+
# Main indices
|
|
14
|
+
"XU100": "BIST 100",
|
|
15
|
+
"XU050": "BIST 50",
|
|
16
|
+
"XU030": "BIST 30",
|
|
17
|
+
"XUTUM": "BIST Tüm",
|
|
18
|
+
# Participation (Katılım) indices
|
|
19
|
+
"XKTUM": "BIST Katılım Tüm",
|
|
20
|
+
"XK100": "BIST Katılım 100",
|
|
21
|
+
"XK050": "BIST Katılım 50",
|
|
22
|
+
"XK030": "BIST Katılım 30",
|
|
23
|
+
"XKTMT": "BIST Katılım Model Portföy",
|
|
24
|
+
# Sector indices
|
|
25
|
+
"XBANK": "BIST Banka",
|
|
26
|
+
"XUSIN": "BIST Sınai",
|
|
27
|
+
"XUMAL": "BIST Mali",
|
|
28
|
+
"XHOLD": "BIST Holding ve Yatırım",
|
|
29
|
+
"XUTEK": "BIST Teknoloji",
|
|
30
|
+
"XGIDA": "BIST Gıda",
|
|
31
|
+
"XTRZM": "BIST Turizm",
|
|
32
|
+
"XULAS": "BIST Ulaştırma",
|
|
33
|
+
"XSGRT": "BIST Sigorta",
|
|
34
|
+
"XMANA": "BIST Metal Ana",
|
|
35
|
+
"XKMYA": "BIST Kimya",
|
|
36
|
+
"XMADN": "BIST Maden",
|
|
37
|
+
"XELKT": "BIST Elektrik",
|
|
38
|
+
"XTEKS": "BIST Tekstil",
|
|
39
|
+
"XILTM": "BIST İletişim",
|
|
40
|
+
# Thematic indices
|
|
41
|
+
"XSRDK": "BIST Sürdürülebilirlik",
|
|
42
|
+
"XKURY": "BIST Kurumsal Yönetim",
|
|
43
|
+
"XYLDZ": "BIST Yıldız",
|
|
44
|
+
"XBANA": "BIST Banka Dışı Likit 10",
|
|
45
|
+
"XSPOR": "BIST Spor",
|
|
46
|
+
"XGMYO": "BIST GYO",
|
|
47
|
+
"XTUMY": "BIST Tüm-100",
|
|
48
|
+
"XYORT": "BIST Yatırım Ortaklıkları",
|
|
49
|
+
"XSDNZ": "BIST Seçme Divident",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Index:
|
|
54
|
+
"""
|
|
55
|
+
A yfinance-like interface for Turkish market indices.
|
|
56
|
+
|
|
57
|
+
Examples:
|
|
58
|
+
>>> import borsapy as bp
|
|
59
|
+
>>> xu100 = bp.Index("XU100")
|
|
60
|
+
>>> xu100.info
|
|
61
|
+
{'symbol': 'XU100', 'name': 'BIST 100', 'last': 9500.5, ...}
|
|
62
|
+
>>> xu100.history(period="1mo")
|
|
63
|
+
Open High Low Close Volume
|
|
64
|
+
Date
|
|
65
|
+
2024-12-01 9400.00 9550.00 9380.00 9500.50 1234567890
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
# Available indices
|
|
69
|
+
>>> bp.indices()
|
|
70
|
+
['XU100', 'XU050', 'XU030', 'XBANK', ...]
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, symbol: str):
|
|
74
|
+
"""
|
|
75
|
+
Initialize an Index object.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
symbol: Index symbol (e.g., "XU100", "XU030", "XBANK").
|
|
79
|
+
"""
|
|
80
|
+
self._symbol = symbol.upper()
|
|
81
|
+
self._paratic = get_paratic_provider()
|
|
82
|
+
self._bist_index = get_bist_index_provider()
|
|
83
|
+
self._info_cache: dict[str, Any] | None = None
|
|
84
|
+
self._components_cache: list[dict[str, Any]] | None = None
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def symbol(self) -> str:
|
|
88
|
+
"""Return the index symbol."""
|
|
89
|
+
return self._symbol
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def info(self) -> dict[str, Any]:
|
|
93
|
+
"""
|
|
94
|
+
Get current index information.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Dictionary with index data:
|
|
98
|
+
- symbol: Index symbol
|
|
99
|
+
- name: Index full name
|
|
100
|
+
- last: Current value
|
|
101
|
+
- open: Opening value
|
|
102
|
+
- high: Day high
|
|
103
|
+
- low: Day low
|
|
104
|
+
- close: Previous close
|
|
105
|
+
- change: Value change
|
|
106
|
+
- change_percent: Percent change
|
|
107
|
+
- update_time: Last update timestamp
|
|
108
|
+
"""
|
|
109
|
+
if self._info_cache is None:
|
|
110
|
+
# Use Paratic API to get quote (same endpoint works for indices)
|
|
111
|
+
quote = self._paratic.get_quote(self._symbol)
|
|
112
|
+
quote["name"] = INDICES.get(self._symbol, self._symbol)
|
|
113
|
+
quote["type"] = "index"
|
|
114
|
+
self._info_cache = quote
|
|
115
|
+
return self._info_cache
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def components(self) -> list[dict[str, Any]]:
|
|
119
|
+
"""
|
|
120
|
+
Get constituent stocks of this index.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of component dicts with 'symbol' and 'name' keys.
|
|
124
|
+
Empty list if index components are not available.
|
|
125
|
+
|
|
126
|
+
Examples:
|
|
127
|
+
>>> import borsapy as bp
|
|
128
|
+
>>> xu030 = bp.Index("XU030")
|
|
129
|
+
>>> xu030.components
|
|
130
|
+
[{'symbol': 'AKBNK', 'name': 'AKBANK'}, ...]
|
|
131
|
+
>>> len(xu030.components)
|
|
132
|
+
30
|
|
133
|
+
"""
|
|
134
|
+
if self._components_cache is None:
|
|
135
|
+
self._components_cache = self._bist_index.get_components(self._symbol)
|
|
136
|
+
return self._components_cache
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def component_symbols(self) -> list[str]:
|
|
140
|
+
"""
|
|
141
|
+
Get just the ticker symbols of constituent stocks.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
List of stock symbols.
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
>>> import borsapy as bp
|
|
148
|
+
>>> xu030 = bp.Index("XU030")
|
|
149
|
+
>>> xu030.component_symbols
|
|
150
|
+
['AKBNK', 'AKSA', 'AKSEN', ...]
|
|
151
|
+
"""
|
|
152
|
+
return [c["symbol"] for c in self.components]
|
|
153
|
+
|
|
154
|
+
def history(
|
|
155
|
+
self,
|
|
156
|
+
period: str = "1mo",
|
|
157
|
+
interval: str = "1d",
|
|
158
|
+
start: datetime | str | None = None,
|
|
159
|
+
end: datetime | str | None = None,
|
|
160
|
+
) -> pd.DataFrame:
|
|
161
|
+
"""
|
|
162
|
+
Get historical index data.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
period: How much data to fetch. Valid periods:
|
|
166
|
+
1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, ytd, max.
|
|
167
|
+
Ignored if start is provided.
|
|
168
|
+
interval: Data interval. Valid intervals:
|
|
169
|
+
1m, 5m, 15m, 30m, 1h, 1d, 1wk, 1mo.
|
|
170
|
+
start: Start date (string or datetime).
|
|
171
|
+
end: End date (string or datetime). Defaults to today.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
DataFrame with columns: Open, High, Low, Close, Volume.
|
|
175
|
+
Index is the Date.
|
|
176
|
+
|
|
177
|
+
Examples:
|
|
178
|
+
>>> idx = Index("XU100")
|
|
179
|
+
>>> idx.history(period="1mo") # Last month
|
|
180
|
+
>>> idx.history(period="1y") # Last year
|
|
181
|
+
>>> idx.history(start="2024-01-01", end="2024-06-30")
|
|
182
|
+
"""
|
|
183
|
+
# Parse dates
|
|
184
|
+
start_dt = self._parse_date(start) if start else None
|
|
185
|
+
end_dt = self._parse_date(end) if end else None
|
|
186
|
+
|
|
187
|
+
# Use Paratic provider (same API works for indices)
|
|
188
|
+
return self._paratic.get_history(
|
|
189
|
+
symbol=self._symbol,
|
|
190
|
+
period=period,
|
|
191
|
+
interval=interval,
|
|
192
|
+
start=start_dt,
|
|
193
|
+
end=end_dt,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def _parse_date(self, date: str | datetime) -> datetime:
|
|
197
|
+
"""Parse a date string to datetime."""
|
|
198
|
+
if isinstance(date, datetime):
|
|
199
|
+
return date
|
|
200
|
+
for fmt in ["%Y-%m-%d", "%Y/%m/%d", "%d-%m-%Y", "%d/%m/%Y"]:
|
|
201
|
+
try:
|
|
202
|
+
return datetime.strptime(date, fmt)
|
|
203
|
+
except ValueError:
|
|
204
|
+
continue
|
|
205
|
+
raise ValueError(f"Could not parse date: {date}")
|
|
206
|
+
|
|
207
|
+
def __repr__(self) -> str:
|
|
208
|
+
return f"Index('{self._symbol}')"
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def indices(detailed: bool = False) -> list[str] | list[dict[str, Any]]:
|
|
212
|
+
"""
|
|
213
|
+
Get list of available market indices.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
detailed: If True, return list of dicts with symbol, name, and count.
|
|
217
|
+
If False (default), return just the symbol list.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
List of index symbols, or list of dicts if detailed=True.
|
|
221
|
+
|
|
222
|
+
Examples:
|
|
223
|
+
>>> import borsapy as bp
|
|
224
|
+
>>> bp.indices()
|
|
225
|
+
['XU100', 'XU050', 'XU030', 'XBANK', 'XUSIN', ...]
|
|
226
|
+
>>> bp.indices(detailed=True)
|
|
227
|
+
[{'symbol': 'XU100', 'name': 'BIST 100', 'count': 100}, ...]
|
|
228
|
+
"""
|
|
229
|
+
if not detailed:
|
|
230
|
+
return list(INDICES.keys())
|
|
231
|
+
|
|
232
|
+
# Get component counts from provider
|
|
233
|
+
provider = get_bist_index_provider()
|
|
234
|
+
available = provider.get_available_indices()
|
|
235
|
+
|
|
236
|
+
# Create lookup for counts
|
|
237
|
+
count_map = {item["symbol"]: item["count"] for item in available}
|
|
238
|
+
|
|
239
|
+
result = []
|
|
240
|
+
for symbol, name in INDICES.items():
|
|
241
|
+
result.append({
|
|
242
|
+
"symbol": symbol,
|
|
243
|
+
"name": name,
|
|
244
|
+
"count": count_map.get(symbol, 0),
|
|
245
|
+
})
|
|
246
|
+
return result
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def all_indices() -> list[dict[str, Any]]:
|
|
250
|
+
"""
|
|
251
|
+
Get all indices from BIST with component counts.
|
|
252
|
+
|
|
253
|
+
This returns all 79 indices available in the BIST data,
|
|
254
|
+
not just the commonly used ones in indices().
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
List of dicts with 'symbol', 'name', and 'count' keys.
|
|
258
|
+
|
|
259
|
+
Examples:
|
|
260
|
+
>>> import borsapy as bp
|
|
261
|
+
>>> bp.all_indices()
|
|
262
|
+
[{'symbol': 'X030C', 'name': 'BIST 30 Capped', 'count': 30}, ...]
|
|
263
|
+
"""
|
|
264
|
+
provider = get_bist_index_provider()
|
|
265
|
+
return provider.get_available_indices()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def index(symbol: str) -> Index:
|
|
269
|
+
"""
|
|
270
|
+
Get an Index object for the given symbol.
|
|
271
|
+
|
|
272
|
+
This is a convenience function that creates an Index object.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
symbol: Index symbol (e.g., "XU100", "XBANK").
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Index object.
|
|
279
|
+
|
|
280
|
+
Examples:
|
|
281
|
+
>>> import borsapy as bp
|
|
282
|
+
>>> xu100 = bp.index("XU100")
|
|
283
|
+
>>> xu100.history(period="1mo")
|
|
284
|
+
"""
|
|
285
|
+
return Index(symbol)
|