pypsx-toolkit 3.0.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.
@@ -0,0 +1,66 @@
1
+ """Type stubs for pypsx.core.stream — real-time PSX data streaming."""
2
+
3
+ from typing import Any, Callable, Dict, List, Union
4
+
5
+
6
+ class PSXStream:
7
+ """
8
+ Real-time PSX market data stream using polling (15-second interval by default).
9
+
10
+ Fetches live price ticks for one or more symbols and delivers them via a
11
+ callback function.
12
+
13
+ Example:
14
+ >>> import pypsx
15
+ >>>
16
+ >>> def on_tick(data: dict):
17
+ ... print(f"{data['symbol']}: {data['price']} ({data['change_pct']:+.2f}%)")
18
+ >>>
19
+ >>> stream = pypsx.PSXStream(["ENGRO", "HBL", "SYS"])
20
+ >>> stream.subscribe(on_tick)
21
+ >>> # Stream runs until stream.stop() is called or KeyboardInterrupt
22
+ """
23
+
24
+ symbols: List[str]
25
+ interval: int
26
+
27
+ def __init__(
28
+ self,
29
+ symbols: Union[str, List[str]],
30
+ interval: int = 15,
31
+ ) -> None:
32
+ """
33
+ Create a real-time stream for one or more PSX symbols.
34
+
35
+ Args:
36
+ symbols: Single symbol string or list of symbols to monitor.
37
+ interval: Polling interval in seconds (minimum 15 to respect PSX rate limits).
38
+ """
39
+ ...
40
+
41
+ def subscribe(self, callback: Callable[[Dict[str, Any]], None]) -> None:
42
+ """
43
+ Start the stream and deliver ticks to the callback function.
44
+
45
+ The callback receives a dict with keys:
46
+ symbol (str), price (float), change (float), change_pct (float),
47
+ volume (int), high (float), low (float), timestamp (str).
48
+
49
+ This method blocks until stop() is called or an exception occurs.
50
+
51
+ Args:
52
+ callback: Function called with each price tick dict.
53
+
54
+ Example:
55
+ >>> stream = pypsx.PSXStream("OGDC", interval=30)
56
+ >>> stream.subscribe(lambda d: print(d["price"]))
57
+ """
58
+ ...
59
+
60
+ def stop(self) -> None:
61
+ """
62
+ Stop the stream gracefully.
63
+
64
+ Safe to call from within the callback or from another thread.
65
+ """
66
+ ...
@@ -0,0 +1 @@
1
+ # Endpoints module for PyPSX library
@@ -0,0 +1,121 @@
1
+ """
2
+ PSX endpoint URL constants and builders.
3
+
4
+ Centralizes all PSX data endpoint URLs for consistency and maintainability.
5
+ """
6
+
7
+ # Base URL for all PSX endpoints
8
+ PSX_BASE_URL = "https://dps.psx.com.pk"
9
+
10
+ # Main endpoints
11
+ MARKET_WATCH_URL = f"{PSX_BASE_URL}/market-watch"
12
+ SECTOR_SUMMARY_URL = f"{PSX_BASE_URL}/sector-summary/sectorwise"
13
+ PERFORMERS_URL = f"{PSX_BASE_URL}/performers"
14
+ TRADING_BOARD_URL = f"{PSX_BASE_URL}/trading-board/REG/main"
15
+ INDICES_OVERVIEW_URL = f"{PSX_BASE_URL}/indices"
16
+ HISTORICAL_URL = f"{PSX_BASE_URL}/historical"
17
+ PSX_MAIN_URL = f"{PSX_BASE_URL}"
18
+
19
+ # URL builders for dynamic endpoints
20
+ def get_index_url(index_code: str) -> str:
21
+ """
22
+ Get URL for index constituents.
23
+
24
+ Args:
25
+ index_code: Index code (e.g., 'KSE100', 'KMI30', 'KSE30')
26
+
27
+ Returns:
28
+ Full URL for the index endpoint
29
+ """
30
+ return f"{PSX_BASE_URL}/indices/{index_code}"
31
+
32
+
33
+ def get_timeseries_intraday_url(symbol: str) -> str:
34
+ """
35
+ Get URL for intraday timeseries data.
36
+
37
+ Args:
38
+ symbol: Stock symbol (e.g., 'OGDC', 'HBL')
39
+
40
+ Returns:
41
+ Full URL for intraday timeseries endpoint
42
+ """
43
+ return f"{PSX_BASE_URL}/timeseries/int/{symbol.upper()}"
44
+
45
+
46
+ def get_timeseries_eod_url(symbol: str) -> str:
47
+ """
48
+ Get URL for end-of-day timeseries data.
49
+
50
+ Args:
51
+ symbol: Stock symbol (e.g., 'OGDC', 'HBL')
52
+
53
+ Returns:
54
+ Full URL for EOD timeseries endpoint
55
+ """
56
+ return f"{PSX_BASE_URL}/timeseries/eod/{symbol.upper()}"
57
+
58
+
59
+ def get_company_url(symbol: str) -> str:
60
+ """
61
+ Get URL for company information.
62
+
63
+ Args:
64
+ symbol: Stock symbol (e.g., 'OGDC', 'HBL')
65
+
66
+ Returns:
67
+ Full URL for company endpoint
68
+ """
69
+ return f"{PSX_BASE_URL}/company/{symbol.upper()}"
70
+
71
+
72
+ def get_company_reports_url(symbol: str) -> str:
73
+ """
74
+ Get URL for company financial reports.
75
+
76
+ Args:
77
+ symbol: Stock symbol (e.g., 'OGDC', 'HBL')
78
+
79
+ Returns:
80
+ Full URL for company reports endpoint
81
+ """
82
+ return f"{PSX_BASE_URL}/company/reports/{symbol.upper()}"
83
+
84
+
85
+ def get_listings_url(listing_type: str = "nc") -> str:
86
+ """
87
+ Get URL for listings table.
88
+
89
+ Args:
90
+ listing_type: 'nc' for normal counter or 'dc' for defaulters counter
91
+
92
+ Returns:
93
+ Full URL for listings table endpoint
94
+ """
95
+ if listing_type not in ("nc", "dc"):
96
+ raise ValueError("listing_type must be 'nc' or 'dc'")
97
+ return f"{PSX_BASE_URL}/listings-table/main/{listing_type}"
98
+
99
+
100
+ def get_download_image_url(image_file: str) -> str:
101
+ """
102
+ Get URL for downloading images from PSX.
103
+
104
+ Args:
105
+ image_file: Image filename
106
+
107
+ Returns:
108
+ Full URL for image download endpoint
109
+ """
110
+ return f"{PSX_BASE_URL}/download/image/{image_file}"
111
+
112
+
113
+ # Common index codes
114
+ COMMON_INDICES = [
115
+ "KSE100",
116
+ "KMI30",
117
+ "KSE30",
118
+ "KSEALLSHR",
119
+ "KMIALLSHR",
120
+ ]
121
+
@@ -0,0 +1 @@
1
+ # Format module for PyPSX library
@@ -0,0 +1,158 @@
1
+ """Type stubs for pypsx.market — market-level functions."""
2
+
3
+ from typing import Any, Dict, List
4
+ import pandas as pd
5
+
6
+
7
+ def market_watch() -> pd.DataFrame:
8
+ """
9
+ Full live market watch — all PSX-listed securities with current prices.
10
+
11
+ Returns:
12
+ DataFrame with columns [Symbol, Company, Sector, LDCP, Open, High, Low,
13
+ Current, Change, Change%, Volume, Value, Trades].
14
+
15
+ Example:
16
+ >>> import pypsx
17
+ >>> mw = pypsx.market_watch()
18
+ >>> gainers = mw[mw["Change%"] > 5]
19
+ """
20
+ ...
21
+
22
+
23
+ def performers() -> Dict[str, pd.DataFrame]:
24
+ """
25
+ Top gainers, top decliners, and most-active stocks for today.
26
+
27
+ Returns:
28
+ Dict with keys "top_gainers", "top_decliners", "top_actives".
29
+ Each value is a DataFrame with [Symbol, Company, Price, Change%].
30
+
31
+ Example:
32
+ >>> result = pypsx.performers()
33
+ >>> print(result["top_gainers"].head())
34
+ """
35
+ ...
36
+
37
+
38
+ def sectors() -> pd.DataFrame:
39
+ """
40
+ List all PSX sectors with aggregate statistics.
41
+
42
+ Returns:
43
+ DataFrame with columns [Sector_Code, Sector_Name, Companies_Count,
44
+ Market_Cap, Change%, Volume].
45
+
46
+ Example:
47
+ >>> df = pypsx.sectors()
48
+ """
49
+ ...
50
+
51
+
52
+ def trading_board() -> pd.DataFrame:
53
+ """
54
+ Live PSX trading board with bid/ask spreads and market depth summary.
55
+
56
+ Returns:
57
+ DataFrame with columns [Symbol, Bid, Ask, LTP, Volume, Trades,
58
+ High, Low, LDCP, Change%].
59
+
60
+ Example:
61
+ >>> board = pypsx.trading_board()
62
+ """
63
+ ...
64
+
65
+
66
+ def get_symbols() -> List[str]:
67
+ """
68
+ Return a list of all currently listed PSX equity symbols.
69
+
70
+ Returns:
71
+ Sorted list of ticker strings, e.g. ["ABOT", "ABL", "ACPL", ...].
72
+
73
+ Example:
74
+ >>> symbols = pypsx.get_symbols()
75
+ >>> print(len(symbols), "listed symbols")
76
+ """
77
+ ...
78
+
79
+
80
+ def listings_nc() -> List[str]:
81
+ """
82
+ Symbols of Non-Compliant (NC) listed companies on PSX.
83
+
84
+ Returns:
85
+ List of ticker strings.
86
+ """
87
+ ...
88
+
89
+
90
+ def listings_dc() -> List[str]:
91
+ """
92
+ Symbols of companies on the Defaulters Counter (DC) on PSX.
93
+
94
+ Returns:
95
+ List of ticker strings.
96
+ """
97
+ ...
98
+
99
+
100
+ def get_indices() -> pd.DataFrame:
101
+ """
102
+ List all PSX market indices with current values and changes.
103
+
104
+ Returns:
105
+ DataFrame with columns [Index, Current, Change, Change%, High, Low].
106
+
107
+ Example:
108
+ >>> indices = pypsx.get_indices()
109
+ """
110
+ ...
111
+
112
+
113
+ def get_indices_breakdown() -> Dict[str, Any]:
114
+ """
115
+ Detailed breakdown of all PSX indices including sub-indices and sectors.
116
+
117
+ Returns:
118
+ Nested dict keyed by index code, with sector weights and constituent counts.
119
+ """
120
+ ...
121
+
122
+
123
+ def get_sector_breakdown() -> Dict[str, Any]:
124
+ """
125
+ Market-wide sector breakdown with aggregate market cap and performance.
126
+
127
+ Returns:
128
+ Dict with sector codes as keys and aggregate stats as values.
129
+ """
130
+ ...
131
+
132
+
133
+ def get_homepage_indices() -> pd.DataFrame:
134
+ """
135
+ Abbreviated index snapshot used on the PSX homepage.
136
+
137
+ Returns:
138
+ DataFrame with KSE-100, KSE-30, KMI-30, and All-Share index data.
139
+ """
140
+ ...
141
+
142
+
143
+ def index_constituents(index_code: str) -> pd.DataFrame:
144
+ """
145
+ Get all companies that make up a specific PSX index.
146
+
147
+ Args:
148
+ index_code: Index identifier, e.g. "KSE100", "KMI30", "KSE30", "ALLSHR".
149
+
150
+ Returns:
151
+ DataFrame with columns [Symbol, Company, Sector, Weight, Market_Cap,
152
+ Shares, Price, Change%].
153
+
154
+ Example:
155
+ >>> df = pypsx.index_constituents("KMI30") # KMI-30 index
156
+ >>> df = pypsx.index_constituents("KSE100") # KSE-100 index
157
+ """
158
+ ...
@@ -0,0 +1,224 @@
1
+ """
2
+ Data models for PyPSX library.
3
+
4
+ Provides typed data structures for all PSX endpoints following the specifications
5
+ in CHANGE.md. All models use normalized field names and ensure no nulls where
6
+ data is guaranteed to exist.
7
+ """
8
+
9
+ from dataclasses import dataclass, field
10
+ from typing import List, Optional, Dict, Any
11
+ from datetime import datetime
12
+
13
+
14
+ @dataclass
15
+ class SymbolInfo:
16
+ """Symbol with optional status tags."""
17
+ symbol: str
18
+ tags: List[str] = field(default_factory=list)
19
+
20
+
21
+ @dataclass
22
+ class SectorSummary:
23
+ """Sector summary data from sector-summary endpoint."""
24
+ sector_code: str
25
+ sector_name: str
26
+ advance: int = 0
27
+ decline: int = 0
28
+ unchange: int = 0
29
+ turnover: float = 0.0
30
+ market_cap_b: float = 0.0 # Market cap in billions
31
+
32
+
33
+ @dataclass
34
+ class SectorCompany:
35
+ """Company within a sector."""
36
+ symbol: str
37
+ name: str
38
+ sector_code: str
39
+ last_close: float = 0.0
40
+ open: float = 0.0
41
+ high: float = 0.0
42
+ low: float = 0.0
43
+ current_price: float = 0.0
44
+ change: float = 0.0
45
+ change_pct: float = 0.0
46
+ volume: int = 0
47
+ tags: List[str] = field(default_factory=list)
48
+
49
+
50
+ @dataclass
51
+ class CompanyMarketWatch:
52
+ """Market watch entry for a company."""
53
+ symbol: str
54
+ sector: str
55
+ listed_in: List[str] = field(default_factory=list)
56
+ last_close: float = 0.0
57
+ open: float = 0.0
58
+ high: float = 0.0
59
+ low: float = 0.0
60
+ current_price: float = 0.0
61
+ change: float = 0.0
62
+ change_pct: float = 0.0
63
+ volume: int = 0
64
+ tags: List[str] = field(default_factory=list)
65
+
66
+
67
+ @dataclass
68
+ class IndexConstituent:
69
+ """Index constituent data."""
70
+ symbol: str
71
+ name: str
72
+ last_close: float = 0.0
73
+ current_price: float = 0.0
74
+ change: float = 0.0
75
+ change_pct: float = 0.0
76
+ index_weight_pct: float = 0.0
77
+ index_point: float = 0.0
78
+ volume: int = 0
79
+ freefloat_m: float = 0.0 # Free float in millions
80
+ market_cap_m: float = 0.0 # Market cap in millions
81
+ tags: List[str] = field(default_factory=list)
82
+
83
+
84
+ @dataclass
85
+ class IndexMeta:
86
+ """Index metadata."""
87
+ index_code: str
88
+ index_name: str
89
+ current_value: float = 0.0
90
+ change: float = 0.0
91
+ change_pct: float = 0.0
92
+ high: float = 0.0
93
+ low: float = 0.0
94
+ volume: int = 0
95
+
96
+
97
+ @dataclass
98
+ class TradingBoardRow:
99
+ """Trading board entry."""
100
+ symbol: str
101
+ name: str
102
+ bid_volume: int = 0
103
+ bid_price: float = 0.0
104
+ offer_volume: int = 0
105
+ offer_price: float = 0.0
106
+ last_close: float = 0.0
107
+ change: float = 0.0
108
+ volume: int = 0
109
+ tags: List[str] = field(default_factory=list)
110
+
111
+
112
+ @dataclass
113
+ class TopActiveStock:
114
+ """Top active stock entry."""
115
+ symbol: str
116
+ price: float = 0.0
117
+ change: float = 0.0
118
+ change_pct: float = 0.0
119
+ volume: int = 0
120
+ tags: List[str] = field(default_factory=list)
121
+
122
+
123
+ @dataclass
124
+ class TopAdvancer:
125
+ """Top advancer entry."""
126
+ symbol: str
127
+ price: float = 0.0
128
+ change: float = 0.0
129
+ change_pct: float = 0.0
130
+ volume: int = 0
131
+ tags: List[str] = field(default_factory=list)
132
+
133
+
134
+ @dataclass
135
+ class TopDecliner:
136
+ """Top decliner entry."""
137
+ symbol: str
138
+ price: float = 0.0
139
+ change: float = 0.0
140
+ change_pct: float = 0.0
141
+ volume: int = 0
142
+ tags: List[str] = field(default_factory=list)
143
+
144
+
145
+ @dataclass
146
+ class IntradayBar:
147
+ """Intraday data point."""
148
+ timestamp: datetime
149
+ price: float = 0.0
150
+ volume: int = 0
151
+
152
+
153
+ @dataclass
154
+ class EODBar:
155
+ """End-of-day data point."""
156
+ timestamp: datetime
157
+ close: float = 0.0
158
+ volume: int = 0
159
+ weighted_avg: float = 0.0
160
+ open: Optional[float] = None
161
+ high: Optional[float] = None
162
+ low: Optional[float] = None
163
+
164
+
165
+ @dataclass
166
+ class ListingEntry:
167
+ """Listing entry from listings table."""
168
+ symbol: str
169
+ name: str
170
+ sector: str
171
+ clearing_type: str = ""
172
+ shares: int = 0
173
+ free_float: int = 0
174
+ listed_in: List[str] = field(default_factory=list)
175
+ non_compliance: str = "" # For DC listings
176
+ tags: List[str] = field(default_factory=list)
177
+
178
+
179
+ @dataclass
180
+ class CompanyFundamentals:
181
+ """Company fundamentals data."""
182
+ symbol: str
183
+ business_description: str = ""
184
+ address: str = ""
185
+ website: str = ""
186
+ key_people: Dict[str, str] = field(default_factory=dict) # Name -> Title
187
+ equity_profile: Dict[str, Any] = field(default_factory=dict)
188
+ financials_annual: Dict[str, List[float]] = field(default_factory=dict)
189
+ financials_quarterly: Dict[str, List[float]] = field(default_factory=dict)
190
+ ratios: Dict[str, List[float]] = field(default_factory=dict)
191
+
192
+
193
+ @dataclass
194
+ class Announcement:
195
+ """Company announcement."""
196
+ symbol: str
197
+ date: str
198
+ title: str
199
+ section: str # "Financial Results", "Board Meetings", "Others"
200
+ image_link: Optional[str] = None
201
+ pdf_link: Optional[str] = None
202
+
203
+
204
+ @dataclass
205
+ class DividendInfo:
206
+ """Dividend information."""
207
+ symbol: str
208
+ dividend_yield: Optional[float] = None
209
+ annual_dividend: Optional[float] = None
210
+ ex_dividend_date: Optional[str] = None
211
+ payout_frequency: Optional[str] = None
212
+ payout_ratio: Optional[float] = None
213
+ dividend_growth: Optional[str] = None
214
+
215
+
216
+ @dataclass
217
+ class DividendHistory:
218
+ """Dividend history entry."""
219
+ symbol: str
220
+ ex_dividend_date: str
221
+ cash_amount: str
222
+ record_date: Optional[str] = None
223
+ pay_date: Optional[str] = None
224
+