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,304 @@
1
+ """
2
+ PSX yfinance-style API
3
+ """
4
+
5
+ __version__ = "3.0.0"
6
+ __author__ = "PyPSX Team"
7
+ __email__ = "pypsx@example.com"
8
+
9
+ from pypsx_toolkit.ticker import PSXTicker, Ticker
10
+
11
+ # Backward compatibility alias
12
+ class PSXSymbol(Ticker):
13
+ pass
14
+
15
+ from pypsx_toolkit.api import (
16
+ download,
17
+ sectors,
18
+ performers,
19
+ market_watch,
20
+ listings_nc,
21
+ listings_dc,
22
+ trading_board,
23
+ index_constituents,
24
+ get_indices,
25
+ get_intraday_multiple,
26
+ get_historical,
27
+ symbols_nc,
28
+ symbols_dc,
29
+ get_symbols,
30
+ )
31
+
32
+ from pypsx_toolkit.endpoints.company import get_quote, get_quote_batch
33
+ from pypsx_toolkit.endpoints.company_fundamentals import get_company_fundamentals
34
+ from pypsx_toolkit.endpoints.announcements import get_announcements as _get_announcements
35
+ from pypsx_toolkit.endpoints.dividends import get_dividend_info as _get_dividend_info, get_dividend_history as _get_dividend_history
36
+ from pypsx_toolkit.endpoints.sectors import get_sector_constituents as _get_sector_constituents
37
+ from pypsx_toolkit.endpoints.compliant_listings import get_symbols_by_sector as _get_symbols_by_sector
38
+ from pypsx_toolkit.endpoints.snapshot import get_snapshot
39
+
40
+ from pypsx_toolkit.market import (
41
+ top_performers,
42
+ sector_summary,
43
+ market_watch as market_watch_func,
44
+ get_indices as get_indices_func,
45
+ get_indices_breakdown,
46
+ get_sector_breakdown,
47
+ get_homepage_indices,
48
+ )
49
+
50
+ from pypsx_toolkit.core.stream import PSXStream
51
+
52
+ # Models
53
+ from pypsx_toolkit.models import (
54
+ SymbolInfo,
55
+ SectorSummary,
56
+ SectorCompany,
57
+ CompanyMarketWatch,
58
+ IndexConstituent,
59
+ IndexMeta,
60
+ TradingBoardRow,
61
+ TopActiveStock,
62
+ TopAdvancer,
63
+ TopDecliner,
64
+ IntradayBar,
65
+ EODBar,
66
+ ListingEntry,
67
+ CompanyFundamentals,
68
+ Announcement,
69
+ DividendInfo,
70
+ DividendHistory,
71
+ )
72
+
73
+ # Analysis module (optional - requires analysis package)
74
+ try:
75
+ from pypsx_toolkit.analysis import (
76
+ interpret_stock,
77
+ sharpe_ratio,
78
+ rsi,
79
+ macd,
80
+ bollinger_bands,
81
+ )
82
+ except ImportError:
83
+ # Analysis module not available
84
+ pass
85
+
86
+ __all__ = [
87
+ "__version__",
88
+ "PSXTicker",
89
+ "Ticker",
90
+ "PSXSymbol",
91
+ "PSXStream",
92
+ "download",
93
+ "sectors",
94
+ "performers",
95
+ "market_watch",
96
+ "top_performers",
97
+ "sector_summary",
98
+ "get_indices",
99
+ "get_indices_breakdown",
100
+ "get_sector_breakdown",
101
+ "get_homepage_indices",
102
+ "listings_nc",
103
+ "listings_dc",
104
+ "trading_board",
105
+ "index_constituents",
106
+ "get_intraday_multiple",
107
+ "get_historical",
108
+ "symbols_nc",
109
+ "symbols_dc",
110
+ "get_symbols",
111
+ # Convenience wrappers
112
+ "get_market_watch",
113
+ "get_most_active",
114
+ "get_top_gainers",
115
+ "get_top_losers",
116
+ "get_orderbook",
117
+ "get_intraday",
118
+ "get_history",
119
+ "get_index",
120
+ "get_symbols_nc",
121
+ "get_symbols_dc",
122
+ "get_quote",
123
+ "get_quote_batch",
124
+ "get_company_fundamentals",
125
+ "get_announcements",
126
+ "get_dividend_info",
127
+ "get_dividend_history",
128
+ "get_business_description",
129
+ "get_sector_constituents",
130
+ "get_symbols_by_sector",
131
+ "get_snapshot",
132
+ # Models
133
+ "SymbolInfo",
134
+ "SectorSummary",
135
+ "SectorCompany",
136
+ "CompanyMarketWatch",
137
+ "IndexConstituent",
138
+ "IndexMeta",
139
+ "TradingBoardRow",
140
+ "TopActiveStock",
141
+ "TopAdvancer",
142
+ "TopDecliner",
143
+ "IntradayBar",
144
+ "EODBar",
145
+ "ListingEntry",
146
+ "CompanyFundamentals",
147
+ "Announcement",
148
+ "DividendInfo",
149
+ "DividendHistory",
150
+ ]
151
+
152
+
153
+ # Legacy compatibility wrappers used by audit script (clean outputs only)
154
+
155
+ def get_market_watch():
156
+ return market_watch()
157
+
158
+
159
+ def get_most_active():
160
+ return performers().get("top_actives")
161
+
162
+
163
+ def get_top_gainers():
164
+ return performers().get("top_gainers")
165
+
166
+
167
+ def get_top_losers():
168
+ return performers().get("top_decliners")
169
+
170
+
171
+ def get_orderbook(symbol: str | None = None):
172
+ if symbol:
173
+ return Ticker(symbol).orderbook()
174
+ return trading_board()
175
+
176
+
177
+ def get_intraday(symbol: str):
178
+ return Ticker(symbol).history(period="1d", interval="1m")
179
+
180
+
181
+ def get_history(symbol: str, period: str = "1y"):
182
+ """Get historical data for a symbol (convenience function)."""
183
+ return PSXTicker(symbol).history(period=period, interval="1d")
184
+
185
+
186
+ def get_index(index_name: str, format: str = 'dataframe'):
187
+ """Get index constituents data (convenience function)."""
188
+ return index_constituents(index_name)
189
+
190
+
191
+
192
+
193
+ def get_symbols_nc():
194
+ import pandas as _pd
195
+ return _pd.DataFrame({"Symbol": symbols_nc()})
196
+
197
+
198
+ def get_symbols_dc():
199
+ import pandas as _pd
200
+ return _pd.DataFrame({"Symbol": symbols_dc()})
201
+
202
+
203
+ def get_announcements(symbol: str, format: str = 'dataframe'):
204
+ """Get company announcements (convenience function)."""
205
+ return _get_announcements(symbol, format)
206
+
207
+
208
+ def get_dividend_info(symbol: str, format: str = 'dataframe'):
209
+ """Get dividend information (convenience function)."""
210
+ return _get_dividend_info(symbol, format)
211
+
212
+
213
+ def get_dividend_history(symbol: str, format: str = 'dataframe'):
214
+ """Get dividend history (convenience function)."""
215
+ return _get_dividend_history(symbol, format)
216
+
217
+
218
+ def get_sector_constituents(sector_code: str, format: str = 'dataframe'):
219
+ """
220
+ Get all companies in a specific sector.
221
+
222
+ Args:
223
+ sector_code: Sector code (e.g., '0801' for AUTOMOBILE ASSEMBLER) or sector name
224
+ format: Output format - 'dataframe' or 'json'
225
+
226
+ Returns:
227
+ DataFrame with sector constituents or JSON dict
228
+
229
+ Example:
230
+ >>> import pypsx_toolkit
231
+ >>> df = pypsx_toolkit.get_sector_constituents('0801') # Automobile Assembler
232
+ >>> print(df.head())
233
+ """
234
+ return _get_sector_constituents(sector_code, format)
235
+
236
+
237
+ def get_symbols_by_sector(sector_name: str, format: str = 'dataframe'):
238
+ """
239
+ Get all symbols in a specific sector by sector name (supports partial matching).
240
+
241
+ Args:
242
+ sector_name: Sector name (e.g., 'automobile', 'AUTOMOBILE ASSEMBLER', 'Automobile Assembler')
243
+ format: Output format - 'dataframe' or 'json'
244
+
245
+ Returns:
246
+ DataFrame with symbols in the sector or JSON dict
247
+
248
+ Example:
249
+ >>> import pypsx_toolkit
250
+ >>> df = pypsx_toolkit.get_symbols_by_sector('automobile')
251
+ >>> print(df.head())
252
+ """
253
+ return _get_symbols_by_sector(sector_name, format)
254
+
255
+
256
+ def get_business_description(symbol: str) -> str:
257
+ """
258
+ Get business description for a company.
259
+
260
+ Args:
261
+ symbol: Stock symbol
262
+
263
+ Returns:
264
+ Business description string, or empty string if not found
265
+ """
266
+ import pandas as pd
267
+ try:
268
+ df = get_company_fundamentals(symbol, format='dataframe')
269
+ if df is None or df.empty: # type: ignore[union-attr]
270
+ return ""
271
+
272
+ # Look for Business Description in the fundamentals DataFrame
273
+ if isinstance(df, pd.DataFrame):
274
+ # Try resetting index first (handles both regular and MultiIndex)
275
+ df_reset = df.reset_index()
276
+
277
+ # Check if we have METRIC and VALUE columns
278
+ if 'METRIC' in df_reset.columns and 'VALUE' in df_reset.columns:
279
+ desc_row = df_reset[df_reset['METRIC'] == 'Business Description']
280
+ if not desc_row.empty:
281
+ value = desc_row['VALUE'].iloc[0]
282
+ # Return clean string, not DataFrame representation
283
+ if pd.notna(value):
284
+ return str(value).strip()
285
+
286
+ # Alternative: check if it's indexed by METRIC (MultiIndex) - try direct access
287
+ if hasattr(df.index, 'get_level_values') and isinstance(df.index, pd.MultiIndex):
288
+ if 'Business Description' in df.index.get_level_values('METRIC').tolist():
289
+ try:
290
+ # Sort index first to avoid PerformanceWarning
291
+ df_sorted = df.sort_index()
292
+ value = df_sorted.loc[(symbol.upper(), 'Profile', 'Business Description'), 'VALUE']
293
+ if pd.notna(value):
294
+ return str(value).strip()
295
+ except (KeyError, IndexError):
296
+ pass
297
+
298
+ return ""
299
+ except Exception:
300
+ return ""
301
+
302
+
303
+
304
+
@@ -0,0 +1,99 @@
1
+ """
2
+ pypsx-toolkit — Pakistan Stock Exchange data library (yfinance-style API).
3
+
4
+ Optional, supplementary to the pypsx trading SDK - not required for trading,
5
+ backtesting, or standard data fetching (see the pypsx package for that).
6
+
7
+ Install:
8
+ pip install pypsx-toolkit
9
+
10
+ Quick start:
11
+ import pypsx_toolkit
12
+
13
+ # Single ticker
14
+ t = pypsx_toolkit.Ticker("ENGRO")
15
+ df = t.history(period="1y")
16
+
17
+ # Batch download
18
+ df = pypsx_toolkit.download(["ENGRO", "HBL", "OGDC"], period="6mo")
19
+
20
+ # Market overview
21
+ mw = pypsx_toolkit.market_watch()
22
+ """
23
+
24
+ from typing import List, Optional, Union
25
+ import pandas as pd
26
+
27
+ from .ticker import PSXTicker as PSXTicker, PSXTicker as Ticker
28
+ from .models import (
29
+ SymbolInfo as SymbolInfo,
30
+ SectorSummary as SectorSummary,
31
+ SectorCompany as SectorCompany,
32
+ CompanyMarketWatch as CompanyMarketWatch,
33
+ IndexConstituent as IndexConstituent,
34
+ IndexMeta as IndexMeta,
35
+ TradingBoardRow as TradingBoardRow,
36
+ TopActiveStock as TopActiveStock,
37
+ TopAdvancer as TopAdvancer,
38
+ TopDecliner as TopDecliner,
39
+ IntradayBar as IntradayBar,
40
+ EODBar as EODBar,
41
+ ListingEntry as ListingEntry,
42
+ CompanyFundamentals as CompanyFundamentals,
43
+ Announcement as Announcement,
44
+ DividendInfo as DividendInfo,
45
+ DividendHistory as DividendHistory,
46
+ )
47
+ from .core.errors import (
48
+ PSXError as PSXError,
49
+ PSXHTTPError as PSXHTTPError,
50
+ PSXTimeoutError as PSXTimeoutError,
51
+ PSXNotFoundError as PSXNotFoundError,
52
+ PSXScopeError as PSXScopeError,
53
+ )
54
+ from .core.stream import PSXStream as PSXStream
55
+ from .api import (
56
+ download as download,
57
+ get_historical as get_historical,
58
+ get_intraday as get_intraday,
59
+ get_intraday_multiple as get_intraday_multiple,
60
+ get_quote as get_quote,
61
+ get_quote_batch as get_quote_batch,
62
+ get_company_fundamentals as get_company_fundamentals,
63
+ get_announcements as get_announcements,
64
+ get_dividend_info as get_dividend_info,
65
+ get_dividend_history as get_dividend_history,
66
+ get_snapshot as get_snapshot,
67
+ get_sector_constituents as get_sector_constituents,
68
+ get_symbols_by_sector as get_symbols_by_sector,
69
+ get_business_description as get_business_description,
70
+ )
71
+ from .market import (
72
+ market_watch as market_watch,
73
+ performers as performers,
74
+ sectors as sectors,
75
+ trading_board as trading_board,
76
+ get_symbols as get_symbols,
77
+ listings_nc as listings_nc,
78
+ listings_dc as listings_dc,
79
+ get_indices as get_indices,
80
+ get_indices_breakdown as get_indices_breakdown,
81
+ get_sector_breakdown as get_sector_breakdown,
82
+ get_homepage_indices as get_homepage_indices,
83
+ index_constituents as index_constituents,
84
+ )
85
+ from .analysis import (
86
+ moving_average as moving_average,
87
+ exponential_moving_average as exponential_moving_average,
88
+ bollinger_bands as bollinger_bands,
89
+ rsi as rsi,
90
+ macd as macd,
91
+ sharpe_ratio as sharpe_ratio,
92
+ sortino_ratio as sortino_ratio,
93
+ performance_summary as performance_summary,
94
+ interpret_stock as interpret_stock,
95
+ portfolio_analysis as portfolio_analysis,
96
+ )
97
+
98
+ __version__: str
99
+ __all__: List[str]
@@ -0,0 +1,222 @@
1
+ """
2
+ PyPSX Analysis Module
3
+
4
+ This module provides comprehensive financial analysis tools for Pakistan Stock Exchange data,
5
+ including statistical functions, technical indicators, performance metrics, and automated insights.
6
+
7
+ Main Components:
8
+ - stats: Core statistical functions (returns, volatility, correlation, etc.)
9
+ - indicators: Technical analysis indicators (RSI, MACD, Bollinger Bands, etc.)
10
+ - performance: Financial performance metrics (Sharpe ratio, drawdown, etc.)
11
+ - insights: Automated insight generation and pattern detection
12
+
13
+ Usage:
14
+ import pypsx_toolkit
15
+ from pypsx_toolkit.analysis import interpret_stock, sharpe_ratio, bollinger_bands
16
+
17
+ # Get stock data
18
+ ticker = pypsx_toolkit.PSXTicker("OGDC")
19
+ df = ticker.history(period="1y")
20
+
21
+ # Generate insights
22
+ insights = interpret_stock(df, "OGDC")
23
+ print(insights['insights'])
24
+
25
+ # Calculate metrics
26
+ sharpe = sharpe_ratio(df)
27
+ ma, upper, lower = bollinger_bands(df)
28
+ """
29
+
30
+ # Core statistical functions
31
+ from .stats import (
32
+ returns, volatility, correlation, correlation_matrix, beta,
33
+ skewness, kurtosis, var, cvar, autocorrelation
34
+ )
35
+
36
+ # Technical indicators
37
+ from .indicators import (
38
+ moving_average, exponential_moving_average, bollinger_bands,
39
+ rsi, macd, stochastic, williams_r, atr, adx, cci, obv, vwap
40
+ )
41
+
42
+ # Performance metrics
43
+ from .performance import (
44
+ sharpe_ratio, sortino_ratio, calmar_ratio, cumulative_returns,
45
+ annualized_return, annualized_volatility, drawdown, max_drawdown,
46
+ drawdown_duration, information_ratio, treynor_ratio, jensen_alpha,
47
+ win_rate, profit_loss_ratio, recovery_factor, performance_summary
48
+ )
49
+
50
+ # Insight engine
51
+ from .insights import (
52
+ interpret_stock, interpret_portfolio, detect_patterns,
53
+ generate_trading_signals, market_sentiment_analysis
54
+ )
55
+
56
+ __all__ = [
57
+ # Statistical functions
58
+ 'returns', 'volatility', 'correlation', 'correlation_matrix', 'beta',
59
+ 'skewness', 'kurtosis', 'var', 'cvar', 'autocorrelation',
60
+
61
+ # Technical indicators
62
+ 'moving_average', 'exponential_moving_average', 'bollinger_bands',
63
+ 'rsi', 'macd', 'stochastic', 'williams_r', 'atr', 'adx', 'cci', 'obv', 'vwap',
64
+
65
+ # Performance metrics
66
+ 'sharpe_ratio', 'sortino_ratio', 'calmar_ratio', 'cumulative_returns',
67
+ 'annualized_return', 'annualized_volatility', 'drawdown', 'max_drawdown',
68
+ 'drawdown_duration', 'information_ratio', 'treynor_ratio', 'jensen_alpha',
69
+ 'win_rate', 'profit_loss_ratio', 'recovery_factor', 'performance_summary',
70
+
71
+ # Insight engine
72
+ 'interpret_stock', 'interpret_portfolio', 'detect_patterns',
73
+ 'generate_trading_signals', 'market_sentiment_analysis'
74
+ ]
75
+
76
+
77
+ def quick_analysis(df, symbol=None):
78
+ """
79
+ Quick analysis function that provides a comprehensive overview of stock performance.
80
+
81
+ Args:
82
+ df: Stock DataFrame with OHLCV data
83
+ symbol: Stock symbol (optional)
84
+
85
+ Returns:
86
+ Dictionary containing key metrics and insights
87
+
88
+ Example:
89
+ >>> import pypsx_toolkit
90
+ >>> ticker = pypsx_toolkit.PSXTicker("OGDC")
91
+ >>> df = ticker.history(period="1y")
92
+ >>> analysis = quick_analysis(df, "OGDC")
93
+ >>> print(f"Sharpe Ratio: {analysis['sharpe_ratio']:.3f}")
94
+ >>> print(f"Total Return: {analysis['total_return']:.2%}")
95
+ """
96
+ if df is None or df.empty:
97
+ return {"error": "No data available for analysis"}
98
+
99
+ try:
100
+ # Get comprehensive insights
101
+ insights = interpret_stock(df, symbol)
102
+
103
+ # Get performance summary
104
+ perf_summary = performance_summary(df)
105
+
106
+ # Get technical patterns
107
+ patterns = detect_patterns(df, symbol)
108
+
109
+ # Get trading signals
110
+ signals = generate_trading_signals(df, symbol)
111
+
112
+ # Merge performance summary metrics at top level for easy access
113
+ result = {
114
+ "symbol": symbol,
115
+ "performance": perf_summary,
116
+ "insights": insights.get('insights', []),
117
+ "patterns": patterns.get('patterns', []),
118
+ "trading_signal": signals.get('primary_signal', 'HOLD'),
119
+ "signal_confidence": signals.get('confidence', 0),
120
+ "key_metrics": {
121
+ "total_return": insights.get('total_return', 0),
122
+ "volatility": insights.get('volatility', 0),
123
+ "sharpe_ratio": insights.get('sharpe_ratio', 0),
124
+ "max_drawdown": insights.get('max_drawdown', 0),
125
+ "rsi": insights.get('rsi', 50)
126
+ }
127
+ }
128
+
129
+ # Also add top-level access for backward compatibility
130
+ # These match the keys that users are accessing directly
131
+ if perf_summary:
132
+ result["sharpe_ratio"] = perf_summary.get('sharpe_ratio', insights.get('sharpe_ratio', 0))
133
+ result["max_drawdown"] = perf_summary.get('max_drawdown', insights.get('max_drawdown', 0))
134
+ result["total_return"] = perf_summary.get('total_return', insights.get('total_return', 0))
135
+ result["annualized_return"] = perf_summary.get('annualized_return', 0)
136
+ result["annualized_volatility"] = perf_summary.get('annualized_volatility', 0)
137
+
138
+ return result
139
+
140
+ except Exception as e:
141
+ return {
142
+ "symbol": symbol,
143
+ "error": str(e),
144
+ "insights": ["Error in analysis"]
145
+ }
146
+
147
+
148
+ def portfolio_analysis(portfolio_data, risk_free_rate=0.08):
149
+ """
150
+ Comprehensive portfolio analysis function.
151
+
152
+ Args:
153
+ portfolio_data: Dictionary of symbol -> DataFrame
154
+ risk_free_rate: Risk-free rate for calculations (default: 0.08)
155
+
156
+ Returns:
157
+ Dictionary containing portfolio analysis results
158
+
159
+ Example:
160
+ >>> portfolio = {
161
+ ... "OGDC": pypsx_toolkit.PSXTicker("OGDC").history(period="1y"),
162
+ ... "PPL": pypsx_toolkit.PSXTicker("PPL").history(period="1y"),
163
+ ... "KEL": pypsx_toolkit.PSXTicker("KEL").history(period="1y")
164
+ ... }
165
+ >>> analysis = portfolio_analysis(portfolio)
166
+ >>> print(f"Portfolio insights: {analysis['portfolio_insights']}")
167
+ """
168
+ if not portfolio_data:
169
+ return {"error": "No portfolio data provided"}
170
+
171
+ try:
172
+ # Get portfolio insights
173
+ portfolio_insights = interpret_portfolio(portfolio_data, risk_free_rate)
174
+
175
+ # Get market sentiment
176
+ sentiment = market_sentiment_analysis(portfolio_data)
177
+
178
+ # Individual stock analysis
179
+ individual_analyses = {}
180
+ for symbol, df in portfolio_data.items():
181
+ if df is not None and not df.empty:
182
+ individual_analyses[symbol] = quick_analysis(df, symbol)
183
+
184
+ return {
185
+ "portfolio_insights": portfolio_insights.get('portfolio_insights', []),
186
+ "portfolio_metrics": portfolio_insights.get('portfolio_metrics', {}),
187
+ "market_sentiment": sentiment.get('overall_sentiment', 'NEUTRAL'),
188
+ "sentiment_strength": sentiment.get('sentiment_strength', 'Mixed'),
189
+ "individual_analyses": individual_analyses,
190
+ "summary": {
191
+ "total_stocks": len(portfolio_data),
192
+ "analyzed_stocks": len(individual_analyses),
193
+ "bullish_stocks": sentiment.get('bullish_stocks', 0),
194
+ "bearish_stocks": sentiment.get('bearish_stocks', 0),
195
+ "neutral_stocks": sentiment.get('neutral_stocks', 0)
196
+ }
197
+ }
198
+
199
+ except Exception as e:
200
+ return {
201
+ "error": str(e),
202
+ "portfolio_insights": ["Error in portfolio analysis"]
203
+ }
204
+
205
+
206
+ # Add convenience aliases
207
+ ma = moving_average
208
+ ema = exponential_moving_average
209
+ bb = bollinger_bands
210
+ sharpe = sharpe_ratio
211
+ sortino = sortino_ratio
212
+ calmar = calmar_ratio
213
+ cum_returns = cumulative_returns
214
+ max_dd = max_drawdown
215
+ win_rate_pct = win_rate
216
+ pl_ratio = profit_loss_ratio
217
+ recovery = recovery_factor
218
+ perf_summary = performance_summary
219
+ interpret = interpret_stock
220
+ patterns = detect_patterns
221
+ signals = generate_trading_signals
222
+ sentiment = market_sentiment_analysis