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.
- pypsx_toolkit/__init__.py +304 -0
- pypsx_toolkit/__init__.pyi +99 -0
- pypsx_toolkit/analysis/__init__.py +222 -0
- pypsx_toolkit/analysis/__init__.pyi +348 -0
- pypsx_toolkit/api.pyi +294 -0
- pypsx_toolkit/core/__init__.py +6 -0
- pypsx_toolkit/core/errors.pyi +38 -0
- pypsx_toolkit/core/stream.pyi +66 -0
- pypsx_toolkit/endpoints/__init__.py +1 -0
- pypsx_toolkit/endpoints/constants.py +121 -0
- pypsx_toolkit/format/__init__.py +1 -0
- pypsx_toolkit/market.pyi +158 -0
- pypsx_toolkit/models.py +224 -0
- pypsx_toolkit/models.pyi +199 -0
- pypsx_toolkit/py.typed +0 -0
- pypsx_toolkit/ticker.pyi +237 -0
- pypsx_toolkit-3.0.0.dist-info/METADATA +744 -0
- pypsx_toolkit-3.0.0.dist-info/RECORD +22 -0
- pypsx_toolkit-3.0.0.dist-info/WHEEL +5 -0
- pypsx_toolkit-3.0.0.dist-info/entry_points.txt +2 -0
- pypsx_toolkit-3.0.0.dist-info/licenses/LICENSE +17 -0
- pypsx_toolkit-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -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
|
pypsx_toolkit/market.pyi
ADDED
|
@@ -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
|
+
...
|
pypsx_toolkit/models.py
ADDED
|
@@ -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
|
+
|