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 +10 -0
- equityiq/data/__init__.py +9 -0
- equityiq/data/fetcher.py +287 -0
- equityiq/fundamental.py +467 -0
- equityiq/health.py +75 -0
- equityiq/multiples_valuation.py +361 -0
- equityiq/projection.py +314 -0
- equityiq/report.py +189 -0
- equityiq/signals.py +183 -0
- equityiq/valuation.py +110 -0
- equityiq-0.1.0.dist-info/METADATA +238 -0
- equityiq-0.1.0.dist-info/RECORD +15 -0
- equityiq-0.1.0.dist-info/WHEEL +5 -0
- equityiq-0.1.0.dist-info/licenses/LICENSE +21 -0
- equityiq-0.1.0.dist-info/top_level.txt +1 -0
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"
|
equityiq/data/fetcher.py
ADDED
|
@@ -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
|
+
}
|