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.
- pypsx/__init__.py +305 -0
- pypsx/analysis/__init__.py +222 -0
- pypsx/analysis/indicators.py +471 -0
- pypsx/analysis/insights.py +615 -0
- pypsx/analysis/performance.py +547 -0
- pypsx/analysis/stats.py +343 -0
- pypsx/analysis.py +115 -0
- pypsx/api.py +256 -0
- pypsx/core/__init__.py +6 -0
- pypsx/core/cache.py +166 -0
- pypsx/core/clean.py +50 -0
- pypsx/core/errors.py +20 -0
- pypsx/core/export.py +55 -0
- pypsx/core/fetch.py +51 -0
- pypsx/core/fetchers.py +228 -0
- pypsx/core/http.py +128 -0
- pypsx/core/normalize.py +168 -0
- pypsx/core/parse.py +230 -0
- pypsx/core/parsers.py +694 -0
- pypsx/core/stream.py +212 -0
- pypsx/core/symbol_parser.py +151 -0
- pypsx/core/utils.py +587 -0
- pypsx/endpoints/__init__.py +1 -0
- pypsx/endpoints/announcements.py +188 -0
- pypsx/endpoints/charts.py +907 -0
- pypsx/endpoints/company.py +264 -0
- pypsx/endpoints/company_fundamentals.py +258 -0
- pypsx/endpoints/compliant_listings.py +493 -0
- pypsx/endpoints/constants.py +121 -0
- pypsx/endpoints/dividends.py +139 -0
- pypsx/endpoints/historical.py +295 -0
- pypsx/endpoints/index_constituents.py +76 -0
- pypsx/endpoints/index_snapshot_detailed.py +418 -0
- pypsx/endpoints/indices.py +238 -0
- pypsx/endpoints/indices_snapshot.py +300 -0
- pypsx/endpoints/market_watch.py +135 -0
- pypsx/endpoints/non_compliant_listings.py +481 -0
- pypsx/endpoints/performers.py +382 -0
- pypsx/endpoints/reports.py +168 -0
- pypsx/endpoints/sectors.py +474 -0
- pypsx/endpoints/snapshot.py +207 -0
- pypsx/endpoints/timeseries.py +311 -0
- pypsx/endpoints/trading_board.py +173 -0
- pypsx/format/__init__.py +1 -0
- pypsx/format/json_utils.py +130 -0
- pypsx/format/normalize.py +39 -0
- pypsx/format/tables.py +179 -0
- pypsx/market.py +273 -0
- pypsx/models.py +224 -0
- pypsx/py.typed +0 -0
- pypsx/ticker.py +438 -0
- pypsx-3.0.0.dist-info/METADATA +712 -0
- pypsx-3.0.0.dist-info/RECORD +56 -0
- pypsx-3.0.0.dist-info/WHEEL +5 -0
- pypsx-3.0.0.dist-info/entry_points.txt +2 -0
- 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
|