reportify-sdk 0.1.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.
reportify_sdk/stock.py ADDED
@@ -0,0 +1,430 @@
1
+ """
2
+ Stock Module
3
+
4
+ Provides access to stock market data including prices, financial statements,
5
+ and company information. Time series data is returned as pandas DataFrame.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Any, Literal
9
+
10
+ import pandas as pd
11
+
12
+ if TYPE_CHECKING:
13
+ from reportify_sdk.client import Reportify
14
+
15
+
16
+ class StockModule:
17
+ """
18
+ Stock data module for financial data and market information
19
+
20
+ Access through the main client:
21
+ >>> client = Reportify(api_key="xxx")
22
+ >>> income = client.stock.income_statement("US:AAPL")
23
+ """
24
+
25
+ def __init__(self, client: "Reportify"):
26
+ self._client = client
27
+
28
+ def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
29
+ return self._client._post(path, json=json)
30
+
31
+ # -------------------------------------------------------------------------
32
+ # Company Information
33
+ # -------------------------------------------------------------------------
34
+
35
+ def overview(self, symbol: str) -> dict[str, Any]:
36
+ """
37
+ Get company overview including business description, sector, and key metrics
38
+
39
+ Args:
40
+ symbol: Stock symbol (e.g., "US:AAPL", "HK:0700", "CN:600519")
41
+
42
+ Returns:
43
+ Company information dictionary
44
+
45
+ Example:
46
+ >>> info = client.stock.overview("US:AAPL")
47
+ >>> print(info["name"], info["sector"])
48
+ """
49
+ response = self._post("/v1/stock/company-overview", json={"symbol": symbol})
50
+ return response
51
+
52
+ def shareholders(self, symbol: str) -> list[dict[str, Any]]:
53
+ """
54
+ Get list of major shareholders
55
+
56
+ Args:
57
+ symbol: Stock symbol
58
+
59
+ Returns:
60
+ List of shareholders with ownership details
61
+ """
62
+ response = self._post("/v1/stock/company-shareholders", json={"symbol": symbol})
63
+ return response if isinstance(response, list) else response.get("shareholders", [])
64
+
65
+ # -------------------------------------------------------------------------
66
+ # Financial Statements (returns DataFrame)
67
+ # -------------------------------------------------------------------------
68
+
69
+ def income_statement(
70
+ self,
71
+ symbol: str,
72
+ *,
73
+ period: Literal["annual", "quarterly"] = "annual",
74
+ limit: int = 10,
75
+ ) -> pd.DataFrame:
76
+ """
77
+ Get income statement data
78
+
79
+ Args:
80
+ symbol: Stock symbol (e.g., "US:AAPL")
81
+ period: "annual" or "quarterly"
82
+ limit: Number of periods to return
83
+
84
+ Returns:
85
+ DataFrame with income statement data, indexed by date
86
+
87
+ Example:
88
+ >>> income = client.stock.income_statement("US:AAPL", period="quarterly")
89
+ >>> print(income[["revenue", "net_income"]].head())
90
+ """
91
+ response = self._post(
92
+ "/v1/stock/company-income-statement",
93
+ json={"symbol": symbol, "period": period, "limit": limit},
94
+ )
95
+ return self._to_dataframe(response)
96
+
97
+ def balance_sheet(
98
+ self,
99
+ symbol: str,
100
+ *,
101
+ period: Literal["annual", "quarterly"] = "annual",
102
+ limit: int = 10,
103
+ ) -> pd.DataFrame:
104
+ """
105
+ Get balance sheet data
106
+
107
+ Args:
108
+ symbol: Stock symbol
109
+ period: "annual" or "quarterly"
110
+ limit: Number of periods to return
111
+
112
+ Returns:
113
+ DataFrame with balance sheet data, indexed by date
114
+
115
+ Example:
116
+ >>> balance = client.stock.balance_sheet("US:AAPL")
117
+ >>> print(balance[["total_assets", "total_liabilities"]].head())
118
+ """
119
+ response = self._post(
120
+ "/v1/stock/company-balance-sheet",
121
+ json={"symbol": symbol, "period": period, "limit": limit},
122
+ )
123
+ return self._to_dataframe(response)
124
+
125
+ def cashflow_statement(
126
+ self,
127
+ symbol: str,
128
+ *,
129
+ period: Literal["annual", "quarterly"] = "annual",
130
+ limit: int = 10,
131
+ ) -> pd.DataFrame:
132
+ """
133
+ Get cash flow statement data
134
+
135
+ Args:
136
+ symbol: Stock symbol
137
+ period: "annual" or "quarterly"
138
+ limit: Number of periods to return
139
+
140
+ Returns:
141
+ DataFrame with cash flow data, indexed by date
142
+
143
+ Example:
144
+ >>> cashflow = client.stock.cashflow_statement("US:AAPL")
145
+ >>> print(cashflow[["operating_cashflow", "free_cashflow"]].head())
146
+ """
147
+ response = self._post(
148
+ "/v1/stock/company-cashflow-statement",
149
+ json={"symbol": symbol, "period": period, "limit": limit},
150
+ )
151
+ return self._to_dataframe(response)
152
+
153
+ def revenue_breakdown(
154
+ self,
155
+ symbol: str,
156
+ *,
157
+ breakdown_type: Literal["segment", "product", "region"] = "segment",
158
+ ) -> pd.DataFrame:
159
+ """
160
+ Get revenue breakdown by segment, product, or region
161
+
162
+ Args:
163
+ symbol: Stock symbol
164
+ breakdown_type: Type of breakdown ("segment", "product", or "region")
165
+
166
+ Returns:
167
+ DataFrame with revenue breakdown data
168
+ """
169
+ response = self._post(
170
+ "/v1/stock/company-revenue-breakdown",
171
+ json={"symbol": symbol, "breakdown_type": breakdown_type},
172
+ )
173
+ return self._to_dataframe(response)
174
+
175
+ # -------------------------------------------------------------------------
176
+ # Price Data (returns DataFrame)
177
+ # -------------------------------------------------------------------------
178
+
179
+ def prices(
180
+ self,
181
+ symbol: str,
182
+ *,
183
+ start_date: str | None = None,
184
+ end_date: str | None = None,
185
+ limit: int = 100,
186
+ ) -> pd.DataFrame:
187
+ """
188
+ Get historical stock prices with market data
189
+
190
+ Args:
191
+ symbol: Stock symbol
192
+ start_date: Start date (YYYY-MM-DD)
193
+ end_date: End date (YYYY-MM-DD)
194
+ limit: Maximum number of records
195
+
196
+ Returns:
197
+ DataFrame with price data (date, close, volume, market_cap, pe, ps)
198
+
199
+ Example:
200
+ >>> prices = client.stock.prices("US:AAPL", limit=30)
201
+ >>> print(prices[["close", "volume"]].tail())
202
+ """
203
+ data: dict[str, Any] = {"symbol": symbol, "limit": limit}
204
+ if start_date:
205
+ data["start_date"] = start_date
206
+ if end_date:
207
+ data["end_date"] = end_date
208
+
209
+ response = self._post("/v1/stock/company-prices", json=data)
210
+ return self._to_dataframe(response)
211
+
212
+ def kline(
213
+ self,
214
+ symbol: str,
215
+ *,
216
+ interval: Literal["1d", "1w", "1m"] = "1d",
217
+ adjust: Literal["forward", "backward", "none"] = "forward",
218
+ start_date: str | None = None,
219
+ end_date: str | None = None,
220
+ limit: int = 100,
221
+ ) -> pd.DataFrame:
222
+ """
223
+ Get K-line (candlestick) data
224
+
225
+ Args:
226
+ symbol: Stock symbol
227
+ interval: Time interval ("1d" daily, "1w" weekly, "1m" monthly)
228
+ adjust: Price adjustment type
229
+ - "forward": Forward adjusted (前复权)
230
+ - "backward": Backward adjusted (后复权)
231
+ - "none": Unadjusted (不复权)
232
+ start_date: Start date (YYYY-MM-DD)
233
+ end_date: End date (YYYY-MM-DD)
234
+ limit: Maximum number of records
235
+
236
+ Returns:
237
+ DataFrame with OHLCV data (date, open, high, low, close, volume)
238
+
239
+ Example:
240
+ >>> kline = client.stock.kline("US:TSLA", interval="1d", limit=100)
241
+ >>> print(kline.tail())
242
+ """
243
+ data: dict[str, Any] = {
244
+ "symbol": symbol,
245
+ "interval": interval,
246
+ "adjust": adjust,
247
+ "limit": limit,
248
+ }
249
+ if start_date:
250
+ data["start_date"] = start_date
251
+ if end_date:
252
+ data["end_date"] = end_date
253
+
254
+ response = self._post("/v1/stock/kline", json=data)
255
+ return self._to_dataframe(response)
256
+
257
+ def quote(self, symbols: str | list[str]) -> pd.DataFrame:
258
+ """
259
+ Get real-time stock quotes
260
+
261
+ Args:
262
+ symbols: Single symbol or list of symbols
263
+
264
+ Returns:
265
+ DataFrame with real-time quote data
266
+
267
+ Example:
268
+ >>> quotes = client.stock.quote(["US:AAPL", "US:MSFT"])
269
+ >>> print(quotes[["symbol", "price", "change_percent"]])
270
+ """
271
+ if isinstance(symbols, str):
272
+ symbols = [symbols]
273
+
274
+ response = self._post("/v1/stock/quote-realtime", json={"symbols": symbols})
275
+ return self._to_dataframe(response)
276
+
277
+ def index_prices(
278
+ self,
279
+ symbol: str,
280
+ *,
281
+ start_date: str | None = None,
282
+ end_date: str | None = None,
283
+ limit: int = 100,
284
+ ) -> pd.DataFrame:
285
+ """
286
+ Get stock index prices (e.g., S&P 500, NASDAQ)
287
+
288
+ Args:
289
+ symbol: Index symbol
290
+ start_date: Start date (YYYY-MM-DD)
291
+ end_date: End date (YYYY-MM-DD)
292
+ limit: Maximum number of records
293
+
294
+ Returns:
295
+ DataFrame with index price data
296
+ """
297
+ data: dict[str, Any] = {"symbol": symbol, "limit": limit}
298
+ if start_date:
299
+ data["start_date"] = start_date
300
+ if end_date:
301
+ data["end_date"] = end_date
302
+
303
+ response = self._post("/v1/stock/index-prices", json=data)
304
+ return self._to_dataframe(response)
305
+
306
+ # -------------------------------------------------------------------------
307
+ # Screening and Calendar
308
+ # -------------------------------------------------------------------------
309
+
310
+ def screener(
311
+ self,
312
+ *,
313
+ market: str | None = None,
314
+ sector: str | None = None,
315
+ min_market_cap: float | None = None,
316
+ max_market_cap: float | None = None,
317
+ min_pe: float | None = None,
318
+ max_pe: float | None = None,
319
+ limit: int = 50,
320
+ ) -> pd.DataFrame:
321
+ """
322
+ Screen stocks based on various criteria
323
+
324
+ Args:
325
+ market: Filter by market (US, HK, CN)
326
+ sector: Filter by sector
327
+ min_market_cap: Minimum market cap
328
+ max_market_cap: Maximum market cap
329
+ min_pe: Minimum P/E ratio
330
+ max_pe: Maximum P/E ratio
331
+ limit: Maximum number of results
332
+
333
+ Returns:
334
+ DataFrame with screened stocks
335
+ """
336
+ data: dict[str, Any] = {"limit": limit}
337
+ if market:
338
+ data["market"] = market
339
+ if sector:
340
+ data["sector"] = sector
341
+ if min_market_cap is not None:
342
+ data["min_market_cap"] = min_market_cap
343
+ if max_market_cap is not None:
344
+ data["max_market_cap"] = max_market_cap
345
+ if min_pe is not None:
346
+ data["min_pe"] = min_pe
347
+ if max_pe is not None:
348
+ data["max_pe"] = max_pe
349
+
350
+ response = self._post("/v1/stock/screener", json=data)
351
+ return self._to_dataframe(response)
352
+
353
+ def earnings_calendar(
354
+ self,
355
+ *,
356
+ area: Literal["us", "hk", "cn"] = "us",
357
+ start_date: str | None = None,
358
+ end_date: str | None = None,
359
+ symbol: str | None = None,
360
+ ) -> pd.DataFrame:
361
+ """
362
+ Get earnings announcement calendar
363
+
364
+ Args:
365
+ area: Market area ("us", "hk", "cn")
366
+ start_date: Start date (YYYY-MM-DD)
367
+ end_date: End date (YYYY-MM-DD)
368
+ symbol: Filter by specific symbol
369
+
370
+ Returns:
371
+ DataFrame with earnings calendar data
372
+ """
373
+ data: dict[str, Any] = {"area": area}
374
+ if start_date:
375
+ data["start_date"] = start_date
376
+ if end_date:
377
+ data["end_date"] = end_date
378
+ if symbol:
379
+ data["symbol"] = symbol
380
+
381
+ response = self._post("/v1/stock/earnings-calendar", json=data)
382
+ return self._to_dataframe(response)
383
+
384
+ def ipo_calendar_hk(
385
+ self,
386
+ status: Literal["Filing", "Hearing", "Priced"] = "Filing",
387
+ ) -> pd.DataFrame:
388
+ """
389
+ Get Hong Kong IPO calendar
390
+
391
+ Args:
392
+ status: IPO status filter ("Filing", "Hearing", "Priced")
393
+
394
+ Returns:
395
+ DataFrame with IPO calendar data
396
+ """
397
+ response = self._post("/v1/stock/ipo-calendar-hk", json={"status": status})
398
+ return self._to_dataframe(response)
399
+
400
+ # -------------------------------------------------------------------------
401
+ # Helper Methods
402
+ # -------------------------------------------------------------------------
403
+
404
+ def _to_dataframe(self, data: Any) -> pd.DataFrame:
405
+ """Convert API response to DataFrame"""
406
+ if isinstance(data, list):
407
+ df = pd.DataFrame(data)
408
+ elif isinstance(data, dict):
409
+ # Handle nested data structures
410
+ if "data" in data:
411
+ df = pd.DataFrame(data["data"])
412
+ elif "items" in data:
413
+ df = pd.DataFrame(data["items"])
414
+ elif "records" in data:
415
+ df = pd.DataFrame(data["records"])
416
+ else:
417
+ # Try to create DataFrame from dict values
418
+ df = pd.DataFrame([data])
419
+ else:
420
+ df = pd.DataFrame()
421
+
422
+ # Set date column as index if present
423
+ date_columns = ["date", "period", "fiscal_date", "report_date"]
424
+ for col in date_columns:
425
+ if col in df.columns:
426
+ df[col] = pd.to_datetime(df[col])
427
+ df = df.set_index(col)
428
+ break
429
+
430
+ return df
@@ -0,0 +1,127 @@
1
+ """
2
+ Timeline Module
3
+
4
+ Provides access to timeline feeds based on user's followed entities
5
+ (companies, topics, institutes, media).
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ if TYPE_CHECKING:
11
+ from reportify_sdk.client import Reportify
12
+
13
+
14
+ class TimelineModule:
15
+ """
16
+ Timeline module for following-based content feeds
17
+
18
+ Access through the main client:
19
+ >>> client = Reportify(api_key="xxx")
20
+ >>> timeline = client.timeline.companies(num=20)
21
+ """
22
+
23
+ def __init__(self, client: "Reportify"):
24
+ self._client = client
25
+
26
+ def _post(self, path: str, json: dict[str, Any] | None = None) -> dict[str, Any]:
27
+ return self._client._post(path, json=json)
28
+
29
+ def companies(self, *, num: int = 10) -> list[dict[str, Any]]:
30
+ """
31
+ Get timeline for followed companies
32
+
33
+ Returns recent content related to companies the user is following,
34
+ including financial reports, news, research reports, and announcements.
35
+
36
+ Args:
37
+ num: Number of items to return (default: 10, max: 100)
38
+
39
+ Returns:
40
+ List of timeline items (documents) from followed companies
41
+
42
+ Example:
43
+ >>> timeline = client.timeline.companies(num=20)
44
+ >>> for item in timeline:
45
+ ... print(item["title"], item["published_at"])
46
+ """
47
+ response = self._post("/v1/tools/timeline/companies", json={"num": num})
48
+ return response.get("docs", [])
49
+
50
+ def topics(self, *, num: int = 10) -> list[dict[str, Any]]:
51
+ """
52
+ Get timeline for followed topics
53
+
54
+ Returns recent content related to custom topics the user is following.
55
+
56
+ Args:
57
+ num: Number of items to return (default: 10, max: 100)
58
+
59
+ Returns:
60
+ List of timeline items related to followed topics
61
+
62
+ Example:
63
+ >>> timeline = client.timeline.topics(num=20)
64
+ >>> for item in timeline:
65
+ ... print(item["title"])
66
+ """
67
+ response = self._post("/v1/tools/timeline/topics", json={"num": num})
68
+ return response.get("docs", [])
69
+
70
+ def institutes(self, *, num: int = 10) -> list[dict[str, Any]]:
71
+ """
72
+ Get timeline for followed professional institutes
73
+
74
+ Returns recent content from research institutions, banks,
75
+ and other professional organizations the user is following.
76
+
77
+ Args:
78
+ num: Number of items to return (default: 10, max: 100)
79
+
80
+ Returns:
81
+ List of timeline items from followed institutes
82
+
83
+ Example:
84
+ >>> timeline = client.timeline.institutes(num=20)
85
+ >>> for item in timeline:
86
+ ... print(item["channel_name"], item["title"])
87
+ """
88
+ response = self._post("/v1/tools/timeline/institutes", json={"num": num})
89
+ return response.get("docs", [])
90
+
91
+ def public_media(self, *, num: int = 10) -> list[dict[str, Any]]:
92
+ """
93
+ Get timeline for followed public media
94
+
95
+ Returns recent content from public media accounts (WeChat, etc.)
96
+ the user is following.
97
+
98
+ Args:
99
+ num: Number of items to return (default: 10, max: 100)
100
+
101
+ Returns:
102
+ List of timeline items from followed public media
103
+
104
+ Example:
105
+ >>> timeline = client.timeline.public_media(num=20)
106
+ """
107
+ response = self._post("/v1/tools/timeline/public-media", json={"num": num})
108
+ return response.get("docs", [])
109
+
110
+ def social_media(self, *, num: int = 10) -> list[dict[str, Any]]:
111
+ """
112
+ Get timeline for followed social media
113
+
114
+ Returns recent content from social media accounts (Twitter, etc.)
115
+ the user is following.
116
+
117
+ Args:
118
+ num: Number of items to return (default: 10, max: 100)
119
+
120
+ Returns:
121
+ List of timeline items from followed social media
122
+
123
+ Example:
124
+ >>> timeline = client.timeline.social_media(num=20)
125
+ """
126
+ response = self._post("/v1/tools/timeline/social-media", json={"num": num})
127
+ return response.get("docs", [])