1. Compound Annual Growth Rate (CAGR)
CAGR measures the mean annual growth rate of an investment over a specified time period longer than one year.
where n = number of years
Implementation
def compute_cagr(series):
if len(series) < 2:
return None
first_val = series[0]
last_val = series[-1]
years = last_year - first_year
if first_val <= 0:
return None
return (last_val / first_val) ** (1 / years) - 1
Use Cases
- Revenue CAGR (3Y, 5Y): Measures consistent revenue growth over multiple years
- Net Profit CAGR: Evaluates profitability growth trajectory
- Price CAGR: Calculates stock price appreciation over 1, 3, and 5 years
2. Robust Scaling (Percentile-Based Normalization)
Robust scaling uses percentiles to reduce the impact of outliers, making the scoring system more stable.
P5 = 5th percentile, P95 = 95th percentile
Implementation
def robust_scale(series, weight, higher_is_better=True):
# Clip values to 5th-95th percentile range
low = np.nanpercentile(series, 5)
high = np.nanpercentile(series, 95)
clipped = series.clip(lower=low, upper=high)
# Normalize to 0-1 range
if higher_is_better:
normalized = (clipped - low) / (high - low)
else:
normalized = (high - clipped) / (high - low)
return normalized * weight
Advantages
- Reduces impact of extreme outliers
- More stable scoring across different market conditions
- Handles skewed distributions better than min-max scaling
3. Financial Health Metrics
3.1 Debt-to-Equity Ratio
Lower values indicate less financial risk. Equity = Equity Capital + Reserves
3.2 Interest Coverage Ratio
Measures ability to pay interest. Higher values (>3) indicate better financial health.
3.3 Cash Flow Coverage
Ratio > 1 indicates strong cash generation relative to reported profits.
4. Profitability Metrics
4.1 Net Profit Margin
4.2 Operating Margin
4.3 Return on Equity (ROE)
Measures return generated on shareholders' investment. Higher is better (typically >15%).
4.4 Return on Capital Employed (ROCE)
Capital Employed = Equity + Long-term Debt. Measures efficiency of capital usage.
5. Volatility & Risk Metrics
5.1 Profit Volatility
Standard deviation of sequential growth rates in profit over the last 4 quarters.
def compute_volatility(values):
# Calculate growth rates
growth_rates = []
for i in range(1, len(values)):
if values[i-1] != 0:
growth = (values[i] - values[i-1]) / values[i-1]
growth_rates.append(growth)
# Return standard deviation of recent growth rates
window = growth_rates[-4:] if len(growth_rates) >= 4 else growth_rates
return np.std(window)
Lower volatility indicates more consistent profitability.
5.2 Price Volatility (1Y)
Annualized volatility using 252 trading days per year.
5.3 Sharpe Ratio
Measures risk-adjusted returns. Higher values indicate better risk-adjusted performance.
5.4 Maximum Drawdown
Largest peak-to-trough decline in stock price over the period.
def calculate_max_drawdown(prices):
peak = prices.expanding().max()
drawdown = (prices - peak) / peak
return drawdown.min()
6. Technical Indicators
6.1 Moving Averages
We use MA50 (50-day) and MA200 (200-day) to identify trends.
6.2 Relative Strength Index (RSI)
RSI measures the speed and magnitude of price changes.
def calculate_rsi(prices, period=14):
delta = prices.diff()
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
return rsi
RSI > 70 indicates overbought, RSI < 30 indicates oversold.
6.3 MACD (Moving Average Convergence Divergence)
Signal Line = EMA(9) of MACD. Histogram = MACD - Signal Line.
7. Trend Analysis
7.1 Linear Regression Trend (90-day)
Fits a linear trend to the last 90 days of price data.
from sklearn.linear_model import LinearRegression
def calculate_trend_slope(prices, days=90):
recent_prices = prices.tail(days)
X = np.arange(len(recent_prices)).reshape(-1, 1)
y = recent_prices.values
model = LinearRegression()
model.fit(X, y)
slope = model.coef_[0]
r_squared = model.score(X, y)
# Convert to percentage
slope_pct = (slope / recent_prices.iloc[0]) * 100
return slope_pct, r_squared
Positive slope indicates upward trend. R² measures trend strength (0-1).
8. Composite Scoring System
8.1 Financial Score Components (100 points)
- Growth Score (0-25): Revenue CAGR, Profit CAGR, YoY Growth
- Profitability Score (0-25): ROE, ROCE, OPM, Net Profit Margin
- Financial Health Score (0-20): Debt-to-Equity, Interest Coverage, Cash Flow
- Consistency Score (0-15): Profit Volatility, Years Positive Profit
- Valuation Score (0-15): P/E Ratio, Market Cap
8.2 Price Score Components (40 points)
- Price Momentum Score (0-35): 1M/3M/6M changes, CAGRs, Trend Slope
- Volume/Liquidity Score (0-20): Average Volume, Volume Trends
- Technical Score (0-25): Price vs MA50/MA200, RSI, MACD, 52W High/Low
- Risk-Adjusted Score (0-20): Volatility, Max Drawdown, Sharpe Ratio
8.3 Overall Composite Score
Weighted combination with 60% weight on financials and 40% on price momentum.
9. Year-to-Date (YTD) Calculations
For quarterly periods, we calculate cumulative results within the fiscal year:
- Jun 2024: Jun 2024 only
- Sep 2024: Jun 2024 + Sep 2024
- Dec 2024: Jun 2024 + Sep 2024 + Dec 2024
- Mar 2025: Jun 2024 + Sep 2024 + Dec 2024 + Mar 2025
This provides a "Financial_result_so_far" view for incomplete fiscal years.
10. Fiscal Year Logic
Indian companies typically follow April-March fiscal year. For quarterly periods:
- Jun, Sep, Dec quarters: Use previous year's annual data (e.g., Jun 2024 → Mar 2024 annual)
- Mar quarter: Use current year's annual data (e.g., Mar 2025 → Mar 2025 annual)
This ensures we're not using future data for current quarter analysis.
Conclusion
Our comprehensive scoring system combines multiple financial and technical metrics using robust statistical methods. The percentile-based scaling ensures fair comparison across different market sectors, while the weighted composite score provides a balanced view of both fundamental strength and price momentum.
All calculations are performed using Python with NumPy and Pandas for accuracy and efficiency, processing thousands of stocks in real-time.