equityiq 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.
equityiq/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ from .health import HealthAnalyzer
2
+ from .valuation import ValuationAnalyzer
3
+ from .signals import SignalAnalyzer
4
+ from .report import Report
5
+ from .fundamental import FundamentalAnalyzer
6
+ from .projection import Projector
7
+ from .multiples_valuation import MultiplesValuation
8
+
9
+ __version__ = "0.1.0"
10
+ __author__ = "Marcos Salguero"
@@ -0,0 +1,9 @@
1
+ from .fetcher import (
2
+ get_stock_info,
3
+ get_price_history,
4
+ get_income_statement,
5
+ get_balance_sheet,
6
+ get_cash_flow,
7
+ get_financials,
8
+ get_full_data,
9
+ )
@@ -0,0 +1,287 @@
1
+ import yfinance as yf
2
+ import pandas as pd
3
+ from typing import Optional
4
+
5
+
6
+ def get_stock_info(ticker: str) -> dict:
7
+ """
8
+ Returns general information and fundamentals of a stock.
9
+
10
+ Args:
11
+ ticker: Stock ticker symbol (e.g. 'AAPL', 'MSFT')
12
+
13
+ Returns:
14
+ Dictionary with company info and key metrics
15
+ """
16
+ stock = yf.Ticker(ticker)
17
+ info = stock.info
18
+
19
+ if not info or "symbol" not in info:
20
+ raise ValueError(
21
+ f"Could not retrieve data for ticker '{ticker}'. "
22
+ f"Please check the symbol is correct."
23
+ )
24
+ return info
25
+
26
+
27
+ def get_price_history(
28
+ ticker: str,
29
+ period: str = "1y",
30
+ interval: str = "1d"
31
+ ) -> pd.DataFrame:
32
+ """
33
+ Returns historical price data for a stock.
34
+
35
+ Args:
36
+ ticker: Stock ticker symbol
37
+ period: Time period ('1mo', '3mo', '6mo', '1y', '2y', '5y')
38
+ interval: Data interval ('1d', '1wk', '1mo')
39
+
40
+ Returns:
41
+ DataFrame with OHLCV data
42
+ """
43
+ stock = yf.Ticker(ticker)
44
+ df = stock.history(period=period, interval=interval)
45
+
46
+ if df.empty:
47
+ raise ValueError(f"No price history found for ticker '{ticker}'.")
48
+
49
+ df.index = pd.to_datetime(df.index)
50
+ return df
51
+
52
+
53
+ def get_income_statement(ticker: str, annual: bool = True) -> pd.DataFrame:
54
+ """
55
+ Returns a cleaned annual income statement with standardized field names.
56
+
57
+ Fields returned (in millions):
58
+ - revenue, gross_profit, ebit, ebitda, net_income,
59
+ interest_expense, interest_income, tax_expense,
60
+ da (depreciation & amortization), eps_diluted, shares_diluted
61
+
62
+ Args:
63
+ ticker: Stock ticker symbol
64
+ annual: If True returns annual data, else quarterly
65
+
66
+ Returns:
67
+ DataFrame with years as columns and metrics as rows
68
+ """
69
+ stock = yf.Ticker(ticker)
70
+ is_raw = stock.financials if annual else stock.quarterly_financials
71
+ cf_raw = stock.cashflow if annual else stock.quarterly_cashflow
72
+
73
+ if is_raw is None or is_raw.empty:
74
+ raise ValueError(f"No income statement data available for '{ticker}'.")
75
+
76
+ def safe_row(df, *keys):
77
+ for k in keys:
78
+ if k in df.index:
79
+ return df.loc[k]
80
+ return pd.Series(dtype=float)
81
+
82
+ revenue = safe_row(is_raw, "Total Revenue")
83
+ gross_profit = safe_row(is_raw, "Gross Profit")
84
+ ebit = safe_row(is_raw, "EBIT", "Operating Income")
85
+ net_income = safe_row(is_raw, "Net Income")
86
+ interest_expense = safe_row(is_raw, "Interest Expense")
87
+ interest_income = safe_row(
88
+ is_raw, "Interest Income", "Interest And Investment Income"
89
+ )
90
+ tax_expense = safe_row(is_raw, "Tax Provision", "Income Tax Expense")
91
+ da = safe_row(
92
+ cf_raw,
93
+ "Depreciation And Amortization",
94
+ "Depreciation Amortization Depletion",
95
+ )
96
+ eps_diluted = safe_row(is_raw, "Diluted EPS")
97
+ shares_diluted = safe_row(is_raw, "Diluted Average Shares")
98
+
99
+ ebitda = ebit + da.reindex(ebit.index, fill_value=0)
100
+
101
+ result = pd.DataFrame({
102
+ "revenue": revenue,
103
+ "gross_profit": gross_profit,
104
+ "ebit": ebit,
105
+ "ebitda": ebitda,
106
+ "da": da,
107
+ "net_income": net_income,
108
+ "interest_expense": interest_expense,
109
+ "interest_income": interest_income,
110
+ "tax_expense": tax_expense,
111
+ "eps_diluted": eps_diluted,
112
+ "shares_diluted": shares_diluted,
113
+ }).T
114
+
115
+ result.columns = [str(c.year) for c in result.columns]
116
+ result = result.sort_index(axis=1)
117
+ result = result / 1e6 # Convert to millions
118
+ result.loc["eps_diluted"] = result.loc["eps_diluted"] * 1e6 # EPS stays as-is
119
+ result.loc["shares_diluted"] = (
120
+ result.loc["shares_diluted"] * 1e6
121
+ ) # shares back to units
122
+
123
+ return result
124
+
125
+
126
+ def get_balance_sheet(ticker: str, annual: bool = True) -> pd.DataFrame:
127
+ """
128
+ Returns a cleaned annual balance sheet with standardized field names.
129
+
130
+ Fields returned (in millions):
131
+ - cash, marketable_securities, accounts_receivable, inventories,
132
+ total_current_assets, total_assets, accounts_payable,
133
+ short_term_debt, long_term_debt, operating_lease_current,
134
+ operating_lease_non_current, total_equity, unearned_revenue
135
+
136
+ Args:
137
+ ticker: Stock ticker symbol
138
+ annual: If True returns annual data, else quarterly
139
+
140
+ Returns:
141
+ DataFrame with years as columns and metrics as rows
142
+ """
143
+ stock = yf.Ticker(ticker)
144
+ bs_raw = stock.balance_sheet if annual else stock.quarterly_balance_sheet
145
+
146
+ if bs_raw is None or bs_raw.empty:
147
+ raise ValueError(f"No balance sheet data available for '{ticker}'.")
148
+
149
+ def safe_row(df, *keys):
150
+ for k in keys:
151
+ if k in df.index:
152
+ return df.loc[k]
153
+ return pd.Series(dtype=float)
154
+
155
+ result = pd.DataFrame({
156
+ "cash": safe_row(bs_raw, "Cash And Cash Equivalents"),
157
+ "marketable_securities": safe_row(
158
+ bs_raw, "Available For Sale Securities",
159
+ "Other Short Term Investments"
160
+ ),
161
+ "accounts_receivable": safe_row(
162
+ bs_raw, "Accounts Receivable", "Net Receivables"
163
+ ),
164
+ "inventories": safe_row(bs_raw, "Inventory"),
165
+ "total_current_assets": safe_row(bs_raw, "Current Assets"),
166
+ "total_assets": safe_row(bs_raw, "Total Assets"),
167
+ "accounts_payable": safe_row(bs_raw, "Accounts Payable"),
168
+ "unearned_revenue": safe_row(
169
+ bs_raw, "Deferred Revenue", "Contract Liabilities"
170
+ ),
171
+ "short_term_debt": safe_row(
172
+ bs_raw, "Current Debt", "Short Term Debt And Current Portion Of Long Term Debt"
173
+ ),
174
+ "long_term_debt": safe_row(bs_raw, "Long Term Debt"),
175
+ "operating_lease_current": safe_row(
176
+ bs_raw, "Current Deferred Liabilities", "Finance Lease Liability Current"
177
+ ),
178
+ "operating_lease_non_current": safe_row(
179
+ bs_raw, "Non Current Deferred Liabilities",
180
+ "Finance Lease Liability Non Current"
181
+ ),
182
+ "total_equity": safe_row(
183
+ bs_raw, "Stockholders Equity", "Common Stock Equity"
184
+ ),
185
+ }).T
186
+
187
+ result.columns = [str(c.year) for c in result.columns]
188
+ result = result.sort_index(axis=1)
189
+ result = result / 1e6
190
+ return result
191
+
192
+
193
+ def get_cash_flow(ticker: str, annual: bool = True) -> pd.DataFrame:
194
+ """
195
+ Returns a cleaned annual cash flow statement with standardized field names.
196
+
197
+ Fields returned (in millions):
198
+ - cfo (cash from operations), capex, capex_maintenance,
199
+ capex_expansion, acquisitions, repurchases, dividends,
200
+ net_change_in_cash, stock_based_compensation
201
+
202
+ Args:
203
+ ticker: Stock ticker symbol
204
+ annual: If True returns annual data, else quarterly
205
+
206
+ Returns:
207
+ DataFrame with years as columns and metrics as rows
208
+ """
209
+ stock = yf.Ticker(ticker)
210
+ cf_raw = stock.cashflow if annual else stock.quarterly_cashflow
211
+
212
+ if cf_raw is None or cf_raw.empty:
213
+ raise ValueError(f"No cash flow data available for '{ticker}'.")
214
+
215
+ def safe_row(df, *keys):
216
+ for k in keys:
217
+ if k in df.index:
218
+ return df.loc[k]
219
+ return pd.Series(dtype=float)
220
+
221
+ da = safe_row(
222
+ cf_raw,
223
+ "Depreciation And Amortization",
224
+ "Depreciation Amortization Depletion",
225
+ )
226
+ capex = safe_row(cf_raw, "Capital Expenditure")
227
+ capex_maintenance = -da.abs() # Use D&A as proxy for maintenance capex
228
+ capex_expansion = capex - capex_maintenance
229
+
230
+ result = pd.DataFrame({
231
+ "cfo": safe_row(cf_raw, "Operating Cash Flow"),
232
+ "capex": capex,
233
+ "capex_maintenance": capex_maintenance,
234
+ "capex_expansion": capex_expansion,
235
+ "da": da,
236
+ "acquisitions": safe_row(cf_raw, "Acquisitions Net"),
237
+ "repurchases": safe_row(
238
+ cf_raw, "Repurchase Of Capital Stock", "Common Stock Repurchased"
239
+ ),
240
+ "dividends": safe_row(
241
+ cf_raw, "Common Stock Dividend Paid", "Payment Of Dividends"
242
+ ),
243
+ "stock_based_compensation": safe_row(cf_raw, "Stock Based Compensation"),
244
+ "net_change_in_cash": safe_row(cf_raw, "Changes In Cash"),
245
+ }).T
246
+
247
+ result.columns = [str(c.year) for c in result.columns]
248
+ result = result.sort_index(axis=1)
249
+ result = result / 1e6
250
+ return result
251
+
252
+
253
+ def get_financials(ticker: str) -> dict:
254
+ """
255
+ Returns the main financial statements of a company (legacy format).
256
+
257
+ Args:
258
+ ticker: Stock ticker symbol
259
+
260
+ Returns:
261
+ Dictionary with income statement, balance sheet and cash flow
262
+ """
263
+ stock = yf.Ticker(ticker)
264
+ return {
265
+ "income_statement": stock.financials,
266
+ "balance_sheet": stock.balance_sheet,
267
+ "cash_flow": stock.cashflow,
268
+ }
269
+
270
+
271
+ def get_full_data(ticker: str) -> dict:
272
+ """
273
+ Returns all available data for a stock in a single call.
274
+
275
+ Args:
276
+ ticker: Stock ticker symbol
277
+
278
+ Returns:
279
+ Dictionary with info, price history and all financial statements
280
+ """
281
+ return {
282
+ "info": get_stock_info(ticker),
283
+ "price_history": get_price_history(ticker),
284
+ "income_statement": get_income_statement(ticker),
285
+ "balance_sheet": get_balance_sheet(ticker),
286
+ "cash_flow": get_cash_flow(ticker),
287
+ }