pypsx 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.
Files changed (56) hide show
  1. pypsx/__init__.py +305 -0
  2. pypsx/analysis/__init__.py +222 -0
  3. pypsx/analysis/indicators.py +471 -0
  4. pypsx/analysis/insights.py +615 -0
  5. pypsx/analysis/performance.py +547 -0
  6. pypsx/analysis/stats.py +343 -0
  7. pypsx/analysis.py +115 -0
  8. pypsx/api.py +256 -0
  9. pypsx/core/__init__.py +6 -0
  10. pypsx/core/cache.py +166 -0
  11. pypsx/core/clean.py +50 -0
  12. pypsx/core/errors.py +20 -0
  13. pypsx/core/export.py +55 -0
  14. pypsx/core/fetch.py +51 -0
  15. pypsx/core/fetchers.py +228 -0
  16. pypsx/core/http.py +128 -0
  17. pypsx/core/normalize.py +168 -0
  18. pypsx/core/parse.py +230 -0
  19. pypsx/core/parsers.py +694 -0
  20. pypsx/core/stream.py +212 -0
  21. pypsx/core/symbol_parser.py +151 -0
  22. pypsx/core/utils.py +587 -0
  23. pypsx/endpoints/__init__.py +1 -0
  24. pypsx/endpoints/announcements.py +188 -0
  25. pypsx/endpoints/charts.py +907 -0
  26. pypsx/endpoints/company.py +264 -0
  27. pypsx/endpoints/company_fundamentals.py +258 -0
  28. pypsx/endpoints/compliant_listings.py +493 -0
  29. pypsx/endpoints/constants.py +121 -0
  30. pypsx/endpoints/dividends.py +139 -0
  31. pypsx/endpoints/historical.py +295 -0
  32. pypsx/endpoints/index_constituents.py +76 -0
  33. pypsx/endpoints/index_snapshot_detailed.py +418 -0
  34. pypsx/endpoints/indices.py +238 -0
  35. pypsx/endpoints/indices_snapshot.py +300 -0
  36. pypsx/endpoints/market_watch.py +135 -0
  37. pypsx/endpoints/non_compliant_listings.py +481 -0
  38. pypsx/endpoints/performers.py +382 -0
  39. pypsx/endpoints/reports.py +168 -0
  40. pypsx/endpoints/sectors.py +474 -0
  41. pypsx/endpoints/snapshot.py +207 -0
  42. pypsx/endpoints/timeseries.py +311 -0
  43. pypsx/endpoints/trading_board.py +173 -0
  44. pypsx/format/__init__.py +1 -0
  45. pypsx/format/json_utils.py +130 -0
  46. pypsx/format/normalize.py +39 -0
  47. pypsx/format/tables.py +179 -0
  48. pypsx/market.py +273 -0
  49. pypsx/models.py +224 -0
  50. pypsx/py.typed +0 -0
  51. pypsx/ticker.py +438 -0
  52. pypsx-3.0.0.dist-info/METADATA +712 -0
  53. pypsx-3.0.0.dist-info/RECORD +56 -0
  54. pypsx-3.0.0.dist-info/WHEEL +5 -0
  55. pypsx-3.0.0.dist-info/entry_points.txt +2 -0
  56. pypsx-3.0.0.dist-info/top_level.txt +1 -0
pypsx/__init__.py ADDED
@@ -0,0 +1,305 @@
1
+ """
2
+ PSX yfinance-style API
3
+ """
4
+ from __future__ import annotations
5
+
6
+ __version__ = "3.0.0"
7
+ __author__ = "PyPSX Team"
8
+ __email__ = "pypsx@example.com"
9
+
10
+ from pypsx.ticker import PSXTicker, Ticker
11
+
12
+ # Backward compatibility alias
13
+ class PSXSymbol(Ticker):
14
+ pass
15
+
16
+ from pypsx.api import (
17
+ download,
18
+ sectors,
19
+ performers,
20
+ market_watch,
21
+ listings_nc,
22
+ listings_dc,
23
+ trading_board,
24
+ index_constituents,
25
+ get_indices,
26
+ get_intraday_multiple,
27
+ get_historical,
28
+ symbols_nc,
29
+ symbols_dc,
30
+ get_symbols,
31
+ )
32
+
33
+ from pypsx.endpoints.company import get_quote, get_quote_batch
34
+ from pypsx.endpoints.company_fundamentals import get_company_fundamentals
35
+ from pypsx.endpoints.announcements import get_announcements as _get_announcements
36
+ from pypsx.endpoints.dividends import get_dividend_info as _get_dividend_info, get_dividend_history as _get_dividend_history
37
+ from pypsx.endpoints.sectors import get_sector_constituents as _get_sector_constituents
38
+ from pypsx.endpoints.compliant_listings import get_symbols_by_sector as _get_symbols_by_sector
39
+ from pypsx.endpoints.snapshot import get_snapshot
40
+
41
+ from pypsx.market import (
42
+ top_performers,
43
+ sector_summary,
44
+ market_watch as market_watch_func,
45
+ get_indices as get_indices_func,
46
+ get_indices_breakdown,
47
+ get_sector_breakdown,
48
+ get_homepage_indices,
49
+ )
50
+
51
+ from pypsx.core.stream import PSXStream
52
+
53
+ # Models
54
+ from pypsx.models import (
55
+ SymbolInfo,
56
+ SectorSummary,
57
+ SectorCompany,
58
+ CompanyMarketWatch,
59
+ IndexConstituent,
60
+ IndexMeta,
61
+ TradingBoardRow,
62
+ TopActiveStock,
63
+ TopAdvancer,
64
+ TopDecliner,
65
+ IntradayBar,
66
+ EODBar,
67
+ ListingEntry,
68
+ CompanyFundamentals,
69
+ Announcement,
70
+ DividendInfo,
71
+ DividendHistory,
72
+ )
73
+
74
+ # Analysis module (optional - requires analysis package)
75
+ try:
76
+ from pypsx.analysis import (
77
+ interpret_stock,
78
+ sharpe_ratio,
79
+ rsi,
80
+ macd,
81
+ bollinger_bands,
82
+ )
83
+ except ImportError:
84
+ # Analysis module not available
85
+ pass
86
+
87
+ __all__ = [
88
+ "__version__",
89
+ "PSXTicker",
90
+ "Ticker",
91
+ "PSXSymbol",
92
+ "PSXStream",
93
+ "download",
94
+ "sectors",
95
+ "performers",
96
+ "market_watch",
97
+ "top_performers",
98
+ "sector_summary",
99
+ "get_indices",
100
+ "get_indices_breakdown",
101
+ "get_sector_breakdown",
102
+ "get_homepage_indices",
103
+ "listings_nc",
104
+ "listings_dc",
105
+ "trading_board",
106
+ "index_constituents",
107
+ "get_intraday_multiple",
108
+ "get_historical",
109
+ "symbols_nc",
110
+ "symbols_dc",
111
+ "get_symbols",
112
+ # Convenience wrappers
113
+ "get_market_watch",
114
+ "get_most_active",
115
+ "get_top_gainers",
116
+ "get_top_losers",
117
+ "get_orderbook",
118
+ "get_intraday",
119
+ "get_history",
120
+ "get_index",
121
+ "get_symbols_nc",
122
+ "get_symbols_dc",
123
+ "get_quote",
124
+ "get_quote_batch",
125
+ "get_company_fundamentals",
126
+ "get_announcements",
127
+ "get_dividend_info",
128
+ "get_dividend_history",
129
+ "get_business_description",
130
+ "get_sector_constituents",
131
+ "get_symbols_by_sector",
132
+ "get_snapshot",
133
+ # Models
134
+ "SymbolInfo",
135
+ "SectorSummary",
136
+ "SectorCompany",
137
+ "CompanyMarketWatch",
138
+ "IndexConstituent",
139
+ "IndexMeta",
140
+ "TradingBoardRow",
141
+ "TopActiveStock",
142
+ "TopAdvancer",
143
+ "TopDecliner",
144
+ "IntradayBar",
145
+ "EODBar",
146
+ "ListingEntry",
147
+ "CompanyFundamentals",
148
+ "Announcement",
149
+ "DividendInfo",
150
+ "DividendHistory",
151
+ ]
152
+
153
+
154
+ # Legacy compatibility wrappers used by audit script (clean outputs only)
155
+
156
+ def get_market_watch():
157
+ return market_watch()
158
+
159
+
160
+ def get_most_active():
161
+ return performers().get("top_actives")
162
+
163
+
164
+ def get_top_gainers():
165
+ return performers().get("top_gainers")
166
+
167
+
168
+ def get_top_losers():
169
+ return performers().get("top_decliners")
170
+
171
+
172
+ def get_orderbook(symbol: str | None = None):
173
+ if symbol:
174
+ return Ticker(symbol).orderbook()
175
+ return trading_board()
176
+
177
+
178
+ def get_intraday(symbol: str):
179
+ return Ticker(symbol).history(period="1d", interval="1m")
180
+
181
+
182
+ def get_history(symbol: str, period: str = "1y"):
183
+ """Get historical data for a symbol (convenience function)."""
184
+ return PSXTicker(symbol).history(period=period, interval="1d")
185
+
186
+
187
+ def get_index(index_name: str, format: str = 'dataframe'):
188
+ """Get index constituents data (convenience function)."""
189
+ return index_constituents(index_name)
190
+
191
+
192
+
193
+
194
+ def get_symbols_nc():
195
+ import pandas as _pd
196
+ return _pd.DataFrame({"Symbol": symbols_nc()})
197
+
198
+
199
+ def get_symbols_dc():
200
+ import pandas as _pd
201
+ return _pd.DataFrame({"Symbol": symbols_dc()})
202
+
203
+
204
+ def get_announcements(symbol: str, format: str = 'dataframe'):
205
+ """Get company announcements (convenience function)."""
206
+ return _get_announcements(symbol, format)
207
+
208
+
209
+ def get_dividend_info(symbol: str, format: str = 'dataframe'):
210
+ """Get dividend information (convenience function)."""
211
+ return _get_dividend_info(symbol, format)
212
+
213
+
214
+ def get_dividend_history(symbol: str, format: str = 'dataframe'):
215
+ """Get dividend history (convenience function)."""
216
+ return _get_dividend_history(symbol, format)
217
+
218
+
219
+ def get_sector_constituents(sector_code: str, format: str = 'dataframe'):
220
+ """
221
+ Get all companies in a specific sector.
222
+
223
+ Args:
224
+ sector_code: Sector code (e.g., '0801' for AUTOMOBILE ASSEMBLER) or sector name
225
+ format: Output format - 'dataframe' or 'json'
226
+
227
+ Returns:
228
+ DataFrame with sector constituents or JSON dict
229
+
230
+ Example:
231
+ >>> import pypsx
232
+ >>> df = pypsx.get_sector_constituents('0801') # Automobile Assembler
233
+ >>> print(df.head())
234
+ """
235
+ return _get_sector_constituents(sector_code, format)
236
+
237
+
238
+ def get_symbols_by_sector(sector_name: str, format: str = 'dataframe'):
239
+ """
240
+ Get all symbols in a specific sector by sector name (supports partial matching).
241
+
242
+ Args:
243
+ sector_name: Sector name (e.g., 'automobile', 'AUTOMOBILE ASSEMBLER', 'Automobile Assembler')
244
+ format: Output format - 'dataframe' or 'json'
245
+
246
+ Returns:
247
+ DataFrame with symbols in the sector or JSON dict
248
+
249
+ Example:
250
+ >>> import pypsx
251
+ >>> df = pypsx.get_symbols_by_sector('automobile')
252
+ >>> print(df.head())
253
+ """
254
+ return _get_symbols_by_sector(sector_name, format)
255
+
256
+
257
+ def get_business_description(symbol: str) -> str:
258
+ """
259
+ Get business description for a company.
260
+
261
+ Args:
262
+ symbol: Stock symbol
263
+
264
+ Returns:
265
+ Business description string, or empty string if not found
266
+ """
267
+ import pandas as pd
268
+ try:
269
+ df = get_company_fundamentals(symbol, format='dataframe')
270
+ if df is None or df.empty: # type: ignore[union-attr]
271
+ return ""
272
+
273
+ # Look for Business Description in the fundamentals DataFrame
274
+ if isinstance(df, pd.DataFrame):
275
+ # Try resetting index first (handles both regular and MultiIndex)
276
+ df_reset = df.reset_index()
277
+
278
+ # Check if we have METRIC and VALUE columns
279
+ if 'METRIC' in df_reset.columns and 'VALUE' in df_reset.columns:
280
+ desc_row = df_reset[df_reset['METRIC'] == 'Business Description']
281
+ if not desc_row.empty:
282
+ value = desc_row['VALUE'].iloc[0]
283
+ # Return clean string, not DataFrame representation
284
+ if pd.notna(value):
285
+ return str(value).strip()
286
+
287
+ # Alternative: check if it's indexed by METRIC (MultiIndex) - try direct access
288
+ if hasattr(df.index, 'get_level_values') and isinstance(df.index, pd.MultiIndex):
289
+ if 'Business Description' in df.index.get_level_values('METRIC').tolist():
290
+ try:
291
+ # Sort index first to avoid PerformanceWarning
292
+ df_sorted = df.sort_index()
293
+ value = df_sorted.loc[(symbol.upper(), 'Profile', 'Business Description'), 'VALUE']
294
+ if pd.notna(value):
295
+ return str(value).strip()
296
+ except (KeyError, IndexError):
297
+ pass
298
+
299
+ return ""
300
+ except Exception:
301
+ return ""
302
+
303
+
304
+
305
+
@@ -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
15
+ from pypsx.analysis import interpret_stock, sharpe_ratio, bollinger_bands
16
+
17
+ # Get stock data
18
+ ticker = pypsx.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
90
+ >>> ticker = pypsx.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.PSXTicker("OGDC").history(period="1y"),
162
+ ... "PPL": pypsx.PSXTicker("PPL").history(period="1y"),
163
+ ... "KEL": pypsx.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