
Equities
- 393 installs
- 161 repo stars
- Updated July 18, 2026
- joellewis/finance_skills
equities is a finance research skill that evaluates stocks and equity sectors for developers who need valuation, earnings quality, dividend profile, and portfolio mandate fit analysis.
About
equities is a finance research skill that guides developers through evaluating individual stocks and equity sectors using valuation framing, earnings-quality checks, dividend profile analysis, and mandate-fit criteria. equities is useful when building portfolio analytics, advisor tooling, or research dashboards that must justify why a security belongs in an equity allocation and how it impacts risk and concentration. equities emphasizes research outputs that can be turned into product artifacts such as a stock memo, a sector snapshot, and an allocation-fit assessment that aligns with client constraints. equities is best used when a codebase already pulls fundamental and market data and the remaining gap is a consistent decision framework for screening, explaining, and comparing equities across a coverage universe.
- Fundamental valuation screening
- Sector and factor exposure mapping
- Earnings and dividend quality review
- Position sizing within equity sleeve
- Risk and volatility benchmarking
Equities by the numbers
- 393 all-time installs (skills.sh)
- +18 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #268 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/joellewis/finance_skills --skill equitiesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 393 |
|---|---|
| repo stars | ★ 161 |
| Last updated | July 18, 2026 |
| Repository | joellewis/finance_skills ↗ |
How do you structure equity research for valuation?
Research individual stocks and equity sectors for valuation, earnings quality, dividend profile, and fit within a client's equity allocation mandate.
Who is it for?
equities fits developers building equity research workflows inside portfolio or advisory software.
Skip if: equities is not for developers who need real-time order execution or brokerage connectivity.
When should I use this skill?
Invoke when a developer asks for a structured way to research stocks, evaluate valuation and earnings quality, or document equity allocation fit.
What you get
Stock research memo, sector research notes, valuation assumptions, dividend profile summary, allocation-fit rationale.
- equity research memo
- sector comparison notes
- allocation-fit summary
Files
Equities
This skill is a decision procedure: which valuation metric to use for which company, which index methodology fits which mandate, and the order of operations for analyzing a stock. It assumes the user can look up definitions; the value here is choosing the right tool.
Choosing the Valuation Metric
Match the metric to the sector and capital structure — using the wrong one is the most common equity-analysis error.
| Situation | Use | Avoid | Why |
|---|---|---|---|
| Financials (banks, insurers) | P/B, P/TBV, ROE vs P/B | EV/EBITDA | Debt is raw material, not financing — EV and EBITDA are meaningless; book value is marked closer to fair value |
| Capital-intensive (industrials, telecom, energy) | EV/EBITDA, EV/EBIT | P/E alone | Neutralizes depreciation policy and leverage differences across peers |
| Mature dividend payers (utilities, staples) | Dividend yield + payout sustainability, P/E | PEG | Growth is low and stable; income and coverage matter most |
| High-growth, low/no earnings | EV/Sales, PEG (if earnings exist), unit economics | P/E, P/B | Earnings are depressed by reinvestment; book value is mostly intangibles |
| Cyclicals (autos, semis, materials) | Mid-cycle or normalized P/E, P/B at trough | Spot P/E | P/E is lowest at the cycle peak and highest at the trough — spot P/E inverts the buy/sell signal |
| Negative earnings, positive cash flow | EV/EBITDA, P/FCF | P/E, earnings yield | Ratio is undefined or misleading with negative denominator |
| REITs and listed real estate | P/FFO, P/AFFO, NAV | P/E | GAAP depreciation distorts earnings for property — handled in detail by the real-assets skill |
| Cross-border / different leverage | EV-based multiples | Equity multiples | Enterprise value normalizes for capital structure |
Cross-checks that apply everywhere:
- Use forward (next-12-month) estimates for the numerator decision when the business is changing; trailing figures when estimate quality is poor.
- Compare against the company's own history and a true peer set, not the whole market.
- Translate any multiple into its implied assumptions (growth, margin, required return) before declaring cheap/expensive — a low multiple usually encodes a real problem.
Choosing the Index Methodology
| Mandate | Methodology | Trade-off to flag |
|---|---|---|
| Cheap, tax-efficient market exposure | Cap-weighted (S&P 500, total market) | Momentum-chasing by construction; concentration in mega-caps — a single sector can exceed 30% |
| Reduce concentration / small-cap tilt | Equal-weighted | Higher turnover and rebalancing cost; structural size and contrarian tilt |
| Break the price-weight link | Fundamental-weighted (revenue, earnings, book) | Effectively a value tilt with extra steps; compare cost vs an explicit value fund |
| Explicit factor exposure | Factor/style index (value, momentum, quality, low vol) | Verify the factor definition and rebalance rules; factor timing rarely works |
| Avoid | Price-weighted (DJIA-style) | Weight proportional to share price is economically arbitrary — legacy only |
Selection rules: default to cap-weighted for core beta; add equal- or fundamental-weighted only when the user explicitly wants the embedded tilt and accepts the turnover; treat any "smart beta" product as a factor portfolio and evaluate its factor loadings, not its marketing name.
Security Analysis Sequence
1. Classify the business — sector (GICS or equivalent), cyclical vs defensive, capital intensity, leverage. This determines the valuation toolkit (table above). 2. Quality screen — revenue trend, margin trend, ROIC vs cost of capital, balance-sheet risk (net debt/EBITDA, interest coverage), share count trajectory (dilution vs buybacks). 3. Earnings basis — pick trailing vs forward EPS, check for one-offs, use diluted share count. For cyclicals, normalize to mid-cycle. 4. Value with the matched metric — primary multiple from the table, one cross-check multiple, and where dividends are central a dividend-based check (Gordon growth: P = D1 / (r - g), valid only when g < r). 5. Factor and style context — regress (or eyeball) exposures to market beta, size, value, momentum, quality. Distinguish stock-specific thesis from a factor bet you could buy more cheaply via an index. 6. Portfolio fit — marginal effect on sector concentration and factor tilts; total return (price + dividends) is the comparison basis, never price return alone.
Key Formulas
| Formula | Expression | Use Case |
|---|---|---|
| EV/EBITDA | (Market Cap + Debt - Cash) / EBITDA | Capital-structure-neutral valuation |
| Earnings Yield | EPS / Price | Compare equity vs bond yields |
| PEG | (P/E) / Earnings Growth Rate (in %) | Growth-adjusted valuation |
| Gordon Growth | P = D1 / (r - g) | Dividend-based intrinsic value |
| CAPM | E(R) = R_f + beta × (E(R_m) - R_f) | Required return input for valuation |
| Total Return | Price Return + Dividend Return | Performance comparison basis |
Worked Example: Metric Selection and Valuation
Given: An industrial company with market cap $500M, total debt $100M, cash $50M, EBITDA $75M, EPS $7.50, price $150. Decide and calculate: 1. Capital-intensive industrial → primary metric is EV/EBITDA (table above), with P/E as cross-check. 2. EV = $500M + $100M - $50M = $550M. EV/EBITDA = $550M / $75M = 7.33x. 3. Cross-check: P/E = $150 / $7.50 = 20.0x; earnings yield = 7.50 / 150 = 5.0%. 4. Interpretation: 7.33x EV/EBITDA is modest for an industrial if margins are stable — compare against the peer set and the company's own 5-10 year range. The 20x P/E looks richer than the EV multiple because the company carries little net debt; the EV multiple is the better cross-peer comparison.
Common Pitfalls
- Applying EV/EBITDA to banks or P/E to REITs — metric/sector mismatch is the dominant error this skill exists to prevent
- Buying cyclicals on low trailing P/E at the cycle peak (the "value trap" inversion)
- Treating a fundamental-weighted or smart-beta index as alpha rather than a packaged factor tilt
- Confusing price return with total return — dividends compound to a large share of long-run equity returns
- Survivorship bias in backtested factor or screen results
Cross-References
- historical-risk: beta, volatility, and Sharpe ratio fundamentals
- fund-vehicles: equity fund selection (ETFs, mutual funds, SMAs)
- currencies-and-fx: international equity currency effects
- asset-allocation: equity allocation within multi-asset portfolios
- real-assets: REIT valuation (P/FFO, NAV) is owned by that skill
- qualitative-valuation and quantitative-valuation: deeper single-company valuation workflows
Running the Script
uv run scripts/equities.py # run the demo (uses PEP 723 inline deps)
uv run scripts/equities.py --verify # check demo outputs against the worked example (exit 1 on mismatch)
python3 scripts/equities.py # alternative (requires: pip install numpy)The demo prints valuation metrics (including the worked example's EV/EBITDA and earnings yield), a factor regression on synthetic data, and sector concentration analysis. Run --help for a list of the classes and functions. For programmatic use, import the module rather than running it — the demo only executes under python equities.py.
# /// script
# dependencies = ["numpy"]
# requires-python = ">=3.11"
# ///
"""
Equity Analysis
===============
CAPM expected return, dividend discount model (Gordon growth), valuation ratios
(PEG, earnings yield), factor exposure (beta), and sector allocation analysis.
Part of Layer 2 (Asset Classes) in the finance skills framework.
"""
import argparse
import sys
import numpy as np
class EquityValuation:
"""Equity valuation models and ratio calculations.
Provides static methods for single-stock and portfolio-level valuation
metrics including CAPM, Gordon Growth Model, and common valuation ratios.
"""
@staticmethod
def capm_expected_return(
risk_free_rate: float,
beta: float,
market_return: float,
) -> float:
"""Compute expected return using the Capital Asset Pricing Model.
E(R_i) = R_f + beta_i * (E(R_m) - R_f)
Parameters
----------
risk_free_rate : float
Risk-free rate (decimal, e.g., 0.04 for 4%).
beta : float
Asset's beta relative to the market portfolio.
market_return : float
Expected market return (decimal).
Returns
-------
float
CAPM expected return (decimal).
"""
equity_risk_premium = market_return - risk_free_rate
return risk_free_rate + beta * equity_risk_premium
@staticmethod
def gordon_growth_model(
dividend: float,
growth_rate: float,
required_return: float,
) -> float:
"""Compute intrinsic value using the Gordon Growth (constant dividend growth) model.
P = D_1 / (r - g)
where D_1 is the next period's expected dividend.
Parameters
----------
dividend : float
Next period's expected dividend (D_1). If you have the current
dividend D_0, pass D_0 * (1 + growth_rate).
growth_rate : float
Constant dividend growth rate (decimal). Must be less than
required_return.
required_return : float
Required rate of return (decimal). Must exceed growth_rate.
Returns
-------
float
Intrinsic stock price.
Raises
------
ValueError
If growth_rate >= required_return (model is undefined).
"""
if growth_rate >= required_return:
raise ValueError(
f"Growth rate ({growth_rate}) must be less than required return "
f"({required_return}) for the Gordon Growth Model to be valid."
)
return dividend / (required_return - growth_rate)
@staticmethod
def pe_ratio(price: float, eps: float) -> float:
"""Compute the Price-to-Earnings ratio.
Parameters
----------
price : float
Current share price.
eps : float
Earnings per share. Must be positive.
Returns
-------
float
P/E ratio.
Raises
------
ValueError
If eps <= 0.
"""
if eps <= 0:
raise ValueError(f"EPS must be positive for P/E calculation, got {eps}.")
return price / eps
@staticmethod
def earnings_yield(eps: float, price: float) -> float:
"""Compute the earnings yield (inverse of P/E).
Earnings Yield = EPS / Price
Parameters
----------
eps : float
Earnings per share.
price : float
Current share price. Must be positive.
Returns
-------
float
Earnings yield (decimal).
"""
if price <= 0:
raise ValueError(f"Price must be positive, got {price}.")
return eps / price
@staticmethod
def peg_ratio(pe: float, earnings_growth_rate: float) -> float:
"""Compute the PEG (Price/Earnings-to-Growth) ratio.
PEG = P/E / Earnings Growth Rate
Parameters
----------
pe : float
Price-to-earnings ratio.
earnings_growth_rate : float
Expected earnings growth rate as a percentage (e.g., 15 for 15%).
Must be positive.
Returns
-------
float
PEG ratio. Values below 1.0 are often considered attractive;
values above 2.0 may indicate overvaluation relative to growth.
Raises
------
ValueError
If earnings_growth_rate <= 0.
"""
if earnings_growth_rate <= 0:
raise ValueError(
f"Earnings growth rate must be positive, got {earnings_growth_rate}."
)
return pe / earnings_growth_rate
@staticmethod
def dividend_yield(annual_dividend: float, price: float) -> float:
"""Compute the dividend yield.
Dividend Yield = Annual Dividends Per Share / Price
Parameters
----------
annual_dividend : float
Annual dividend per share.
price : float
Current share price. Must be positive.
Returns
-------
float
Dividend yield (decimal).
"""
if price <= 0:
raise ValueError(f"Price must be positive, got {price}.")
return annual_dividend / price
@staticmethod
def ev_ebitda(
market_cap: float,
total_debt: float,
cash: float,
ebitda: float,
) -> float:
"""Compute the EV/EBITDA ratio.
EV/EBITDA = (Market Cap + Total Debt - Cash) / EBITDA
Parameters
----------
market_cap : float
Market capitalization.
total_debt : float
Total debt (short-term + long-term).
cash : float
Cash and cash equivalents.
ebitda : float
Earnings Before Interest, Taxes, Depreciation, and Amortization.
Must be positive.
Returns
-------
float
EV/EBITDA ratio.
Raises
------
ValueError
If ebitda <= 0.
"""
if ebitda <= 0:
raise ValueError(f"EBITDA must be positive, got {ebitda}.")
enterprise_value = market_cap + total_debt - cash
return enterprise_value / ebitda
@staticmethod
def total_return(
price_begin: float,
price_end: float,
dividends: float,
) -> float:
"""Compute total return (price return + dividend return).
Total Return = (P_end - P_begin + Dividends) / P_begin
Parameters
----------
price_begin : float
Price at start of period. Must be positive.
price_end : float
Price at end of period.
dividends : float
Total dividends received during the period.
Returns
-------
float
Total return (decimal).
"""
if price_begin <= 0:
raise ValueError(f"Beginning price must be positive, got {price_begin}.")
return (price_end - price_begin + dividends) / price_begin
class FactorAnalysis:
"""Factor exposure and style analysis for equity portfolios.
Parameters
----------
returns : np.ndarray
Array of periodic asset or portfolio excess returns (decimals).
factor_returns : np.ndarray
2D array of factor returns, shape (n_periods, n_factors).
Each column is a factor time series.
factor_names : list[str] or None, optional
Names for each factor. Default is None (uses F1, F2, ...).
"""
def __init__(
self,
returns: np.ndarray,
factor_returns: np.ndarray,
factor_names: list[str] | None = None,
):
self.returns = np.asarray(returns, dtype=np.float64)
self.factor_returns = np.atleast_2d(
np.asarray(factor_returns, dtype=np.float64)
)
if self.factor_returns.shape[0] != len(self.returns):
raise ValueError(
f"factor_returns rows ({self.factor_returns.shape[0]}) must match "
f"returns length ({len(self.returns)})."
)
n_factors = self.factor_returns.shape[1]
if factor_names is not None and len(factor_names) != n_factors:
raise ValueError(
f"factor_names length ({len(factor_names)}) must match number of "
f"factors ({n_factors})."
)
self.factor_names = factor_names or [
f"F{i+1}" for i in range(n_factors)
]
def beta(self) -> dict[str, float]:
"""Compute beta (factor exposure) for each factor via OLS regression.
For each factor k: beta_k = cov(R, F_k) / var(F_k)
This is the univariate beta; for multivariate exposures use
ols_regression.
Returns
-------
dict[str, float]
Mapping of factor name to univariate beta.
"""
betas = {}
for i, name in enumerate(self.factor_names):
factor = self.factor_returns[:, i]
cov = np.cov(self.returns, factor)
var_f = cov[1, 1]
if var_f == 0:
betas[name] = 0.0
else:
betas[name] = float(cov[0, 1] / var_f)
return betas
def ols_regression(self) -> dict:
"""Multivariate OLS regression of returns on all factors.
R = alpha + beta_1*F_1 + beta_2*F_2 + ... + epsilon
Returns
-------
dict
Keys: 'alpha' (float), 'betas' (dict[str, float]),
'r_squared' (float), 'residual_std' (float).
"""
n = len(self.returns)
# Add intercept column
x = np.column_stack([np.ones(n), self.factor_returns])
# OLS: (X'X)^-1 X'y
coeffs = np.linalg.lstsq(x, self.returns, rcond=None)[0]
alpha = float(coeffs[0])
betas = {
name: float(coeffs[i + 1]) for i, name in enumerate(self.factor_names)
}
# R-squared
fitted = x @ coeffs
residuals = self.returns - fitted
ss_res = np.sum(residuals ** 2)
ss_tot = np.sum((self.returns - np.mean(self.returns)) ** 2)
r_squared = float(1.0 - ss_res / ss_tot) if ss_tot > 0 else 0.0
return {
"alpha": alpha,
"betas": betas,
"r_squared": r_squared,
"residual_std": float(np.std(residuals, ddof=len(coeffs))),
}
class SectorAllocation:
"""Analyze sector allocation and concentration in equity portfolios.
Parameters
----------
sector_weights : dict[str, float]
Mapping of sector name to portfolio weight (decimals, should sum to 1).
benchmark_weights : dict[str, float] or None, optional
Mapping of sector name to benchmark weight. Default is None.
"""
def __init__(
self,
sector_weights: dict[str, float],
benchmark_weights: dict[str, float] | None = None,
):
self.sector_weights = sector_weights
self.benchmark_weights = benchmark_weights
def concentration(self) -> dict:
"""Compute portfolio concentration metrics.
Returns
-------
dict
Keys: 'top_sector' (str), 'top_weight' (float),
'herfindahl' (float — sum of squared weights, higher = more
concentrated), 'effective_sectors' (float — 1/HHI).
"""
weights = np.array(list(self.sector_weights.values()))
names = list(self.sector_weights.keys())
top_idx = int(np.argmax(weights))
hhi = float(np.sum(weights ** 2))
return {
"top_sector": names[top_idx],
"top_weight": float(weights[top_idx]),
"herfindahl": hhi,
"effective_sectors": float(1.0 / hhi) if hhi > 0 else 0.0,
}
def active_weights(self) -> dict[str, float]:
"""Compute active sector weights relative to the benchmark.
active_weight_k = portfolio_weight_k - benchmark_weight_k
Returns
-------
dict[str, float]
Mapping of sector name to active weight.
Raises
------
ValueError
If benchmark_weights is None.
"""
if self.benchmark_weights is None:
raise ValueError("Active weight calculation requires benchmark_weights.")
all_sectors = set(self.sector_weights) | set(self.benchmark_weights)
result = {}
for sector in sorted(all_sectors):
port_w = self.sector_weights.get(sector, 0.0)
bench_w = self.benchmark_weights.get(sector, 0.0)
result[sector] = port_w - bench_w
return result
def _demo() -> None:
# ----------------------------------------------------------------
# Demo: Equity analysis on synthetic data
# ----------------------------------------------------------------
np.random.seed(42)
print("=" * 60)
print("Equity Analysis - Demo")
print("=" * 60)
# --- Valuation ---
print("\n--- Valuation Metrics ---")
er = EquityValuation.capm_expected_return(
risk_free_rate=0.04, beta=1.2, market_return=0.10
)
print(f"CAPM Expected Return (beta=1.2): {er:.4f} ({er*100:.2f}%)")
ggm_price = EquityValuation.gordon_growth_model(
dividend=2.50, growth_rate=0.05, required_return=0.10
)
print(f"Gordon Growth Model price (D1=$2.50, g=5%, r=10%): ${ggm_price:.2f}")
pe = EquityValuation.pe_ratio(price=150.0, eps=7.50)
print(f"P/E Ratio ($150 / $7.50 EPS): {pe:.2f}")
ey = EquityValuation.earnings_yield(eps=7.50, price=150.0)
print(f"Earnings Yield: {ey:.4f} ({ey*100:.2f}%)")
peg = EquityValuation.peg_ratio(pe=20.0, earnings_growth_rate=15.0)
print(f"PEG Ratio (P/E=20, growth=15%): {peg:.2f}")
dy = EquityValuation.dividend_yield(annual_dividend=3.00, price=150.0)
print(f"Dividend Yield ($3.00 / $150): {dy:.4f} ({dy*100:.2f}%)")
ev_ebitda = EquityValuation.ev_ebitda(
market_cap=500e6, total_debt=100e6, cash=50e6, ebitda=75e6
)
print(f"EV/EBITDA: {ev_ebitda:.2f}")
tr = EquityValuation.total_return(
price_begin=100.0, price_end=110.0, dividends=3.0
)
print(f"Total Return ($100->$110, $3 div): {tr:.4f} ({tr*100:.2f}%)")
# --- Factor Analysis ---
print("\n--- Factor Analysis (CAPM + Fama-French) ---")
n_months = 60
market_excess = np.random.normal(0.005, 0.04, n_months)
smb = np.random.normal(0.002, 0.03, n_months)
hml = np.random.normal(0.001, 0.03, n_months)
# Synthetic fund returns with known exposures
alpha_true = 0.001 # 10bps monthly alpha
fund_excess = (
alpha_true
+ 1.1 * market_excess
+ 0.3 * smb
- 0.2 * hml
+ np.random.normal(0, 0.01, n_months)
)
factor_matrix = np.column_stack([market_excess, smb, hml])
fa = FactorAnalysis(
returns=fund_excess,
factor_returns=factor_matrix,
factor_names=["Market", "SMB", "HML"],
)
print("\nUnivariate betas:")
for name, b in fa.beta().items():
print(f" {name:10s}: {b:.4f}")
reg = fa.ols_regression()
print(f"\nMultivariate regression (OLS):")
print(f" Alpha (monthly): {reg['alpha']:.6f} ({reg['alpha']*12*100:.2f}% annualized)")
for name, b in reg["betas"].items():
print(f" Beta({name:6s}): {b:.4f}")
print(f" R-squared: {reg['r_squared']:.4f}")
# --- Sector Allocation ---
print("\n--- Sector Allocation ---")
portfolio_sectors = {
"Technology": 0.30,
"Healthcare": 0.15,
"Financials": 0.12,
"Consumer Disc.": 0.10,
"Industrials": 0.10,
"Energy": 0.08,
"Comm. Services": 0.06,
"Utilities": 0.04,
"Materials": 0.03,
"Real Estate": 0.02,
}
benchmark_sectors = {
"Technology": 0.28,
"Healthcare": 0.13,
"Financials": 0.13,
"Consumer Disc.": 0.11,
"Industrials": 0.09,
"Energy": 0.05,
"Comm. Services": 0.08,
"Utilities": 0.03,
"Materials": 0.05,
"Real Estate": 0.05,
}
sa = SectorAllocation(
sector_weights=portfolio_sectors,
benchmark_weights=benchmark_sectors,
)
conc = sa.concentration()
print(f"Top sector: {conc['top_sector']} ({conc['top_weight']*100:.1f}%)")
print(f"Herfindahl index: {conc['herfindahl']:.4f}")
print(f"Effective sectors: {conc['effective_sectors']:.1f}")
print("\nActive weights (portfolio - benchmark):")
for sector, aw in sa.active_weights().items():
direction = "OW" if aw > 0 else "UW" if aw < 0 else " "
print(f" {sector:18s}: {aw:+.2%} {direction}")
print("\n" + "=" * 60)
print("Demo complete.")
print("=" * 60)
def _check(failures: list, name: str, actual: float, expected: float, tol: float) -> None:
"""Record a verification check result."""
ok = abs(actual - expected) <= tol
status = "PASS" if ok else "FAIL"
print(f" [{status}] {name}: actual={actual:.6g}, expected={expected:.6g}, tol={tol:.2g}")
if not ok:
failures.append(name)
def _verify() -> None:
"""Verify key outputs against the SKILL.md worked example."""
failures: list = []
# SKILL.md worked example: industrial company metric selection
_check(failures, "EV/EBITDA ($550M EV / $75M EBITDA)",
EquityValuation.ev_ebitda(500e6, 100e6, 50e6, 75e6), 7.3333, 1e-4)
_check(failures, "P/E cross-check ($150 / $7.50)",
EquityValuation.pe_ratio(150.0, 7.50), 20.0, 1e-12)
_check(failures, "earnings yield (7.50 / 150)",
EquityValuation.earnings_yield(7.50, 150.0), 0.05, 1e-12)
# Key formulas table: CAPM and Gordon growth
_check(failures, "CAPM (rf=4%, beta=1.2, mkt=10%)",
EquityValuation.capm_expected_return(0.04, 1.2, 0.10), 0.112, 1e-12)
_check(failures, "Gordon growth (D1=2.50, g=5%, r=10%)",
EquityValuation.gordon_growth_model(2.50, 0.05, 0.10), 50.0, 1e-9)
if failures:
print(f"\n{len(failures)} check(s) FAILED: {', '.join(failures)}")
sys.exit(1)
print("\nAll checks passed.")
def main() -> None:
parser = argparse.ArgumentParser(
description=__doc__.strip().splitlines()[2] if __doc__ else "",
epilog=(
"Provides: EquityValuation, FactorAnalysis, SectorAllocation. "
"For programmatic use, import this module (equities) instead of running it. "
"Bare run executes a demo whose printed values match the SKILL.md worked examples; "
"--verify asserts those values and exits nonzero on mismatch."
),
)
parser.add_argument(
"--verify",
action="store_true",
help="run the verification checks against the SKILL.md worked-example values",
)
args = parser.parse_args()
if args.verify:
_verify()
else:
_demo()
if __name__ == "__main__":
main()
Related skills
How it compares
Pick this when you need research structure and decision criteria rather than API integration or execution plumbing.
FAQ
What does equities produce as output?
equities produces developer-friendly research artifacts such as a stock memo outline, a sector snapshot, valuation assumptions to record in code or docs, and an allocation-fit rationale that can be displayed in a portfolio UI or exported as a report.
When should a portfolio app use equities?
equities should be used when a portfolio or advisory app already has data ingestion in place and needs a consistent framework to evaluate valuation, earnings quality, and dividend profile so decisions can be explained and audited in product reports.