Spaces:
Running
Running
| import pandas as pd | |
| from typing import Dict, Any | |
| def safe_float(val): | |
| if val is None or val == '': | |
| return 0.0 | |
| try: | |
| return float(val) | |
| except: | |
| return 0.0 | |
| def normalize_linear(value: float, min_val: float, max_val: float) -> float: | |
| """ | |
| Linear normalization to 0-100 scale | |
| """ | |
| if value is None or pd.isna(value): | |
| return 50.0 # Neutral if missing | |
| if value <= min_val: | |
| return 0.0 | |
| elif value >= max_val: | |
| return 100.0 | |
| else: | |
| return ((value - min_val) / (max_val - min_val)) * 100.0 | |
| def calculate_normalized_scores_v2(raw_data: Dict[str, Any]) -> Dict[str, float]: | |
| """ | |
| Enhanced normalization with Dividend Yield | |
| """ | |
| # 1. GROWTH (5Y Returns) | |
| # Scale: -20% = 0, 0% = 40, 100% = 100 | |
| returns_5y = raw_data.get('returns_5y') | |
| if returns_5y is not None: | |
| growth_score = normalize_linear(returns_5y, min_val=-20, max_val=100) | |
| else: | |
| growth_score = 50.0 | |
| # 2. PROFITABILITY (ROE) | |
| # Scale: 0% = 0, 15% = 50, 30%+ = 100 | |
| roe = raw_data.get('roe') | |
| if roe is not None: | |
| profitability_score = normalize_linear(roe, min_val=0, max_val=30) | |
| else: | |
| profitability_score = 50.0 | |
| # 3. EFFICIENCY (ROCE) | |
| # Scale: 0% = 0, 15% = 50, 30%+ = 100 | |
| roce = raw_data.get('roce') | |
| if roce is not None: | |
| efficiency_score = normalize_linear(roce, min_val=0, max_val=30) | |
| else: | |
| efficiency_score = 50.0 | |
| # 4. VALUATION (P/E vs Industry P/E) | |
| # Closer to industry P/E = better | |
| pe_ratio = raw_data.get('pe_ratio') | |
| industry_pe = raw_data.get('industry_pe') | |
| if pe_ratio and industry_pe and industry_pe > 0: | |
| # Calculate deviation percentage | |
| deviation = abs(pe_ratio - industry_pe) / industry_pe * 100 | |
| # 0% deviation = 100 score | |
| # 50%+ deviation = 0 score | |
| valuation_score = max(0, 100 - (deviation * 2)) | |
| # Bonus: Slight preference for undervalued (P/E < Industry) | |
| if pe_ratio < industry_pe: | |
| valuation_score = min(100, valuation_score * 1.1) | |
| else: | |
| # If P/E is valid but industry PE is missing, maybe assume fair? | |
| # Or if PE is 0 (loss making), low score? | |
| if pe_ratio and pe_ratio < 0: valuation_score = 20 # Loss making | |
| else: valuation_score = 50.0 | |
| # 5. DIVIDEND YIELD (NEW) | |
| # Use pre-calculated yield if present, else calculate | |
| dividend_yield = raw_data.get('dividend_yield') | |
| if dividend_yield is None: | |
| dividend = raw_data.get('dividend', 0.0) | |
| current_price = raw_data.get('current_price', 0.0) | |
| if dividend and current_price and current_price > 0: | |
| dividend_yield = (dividend / current_price) * 100 | |
| else: | |
| dividend_yield = 0.0 | |
| # Scale: 0% = 0, 2% = 50, 5%+ = 100 | |
| dividend_score = normalize_linear(dividend_yield, min_val=0, max_val=5) | |
| # 6. MOMENTUM (1Y Returns) | |
| # Scale: -50% = 0, 0% = 50, 100% = 100 | |
| returns_1y = raw_data.get('returns_1y') | |
| if returns_1y is not None: | |
| momentum_score = normalize_linear(returns_1y, min_val=-50, max_val=100) | |
| else: | |
| momentum_score = 50.0 | |
| return { | |
| "Growth": round(growth_score, 1), | |
| "Profitability": round(profitability_score, 1), | |
| "Efficiency": round(efficiency_score, 1), | |
| "Valuation": round(valuation_score, 1), | |
| "Dividend Yield": round(dividend_score, 1), | |
| "Momentum": round(momentum_score, 1) | |
| } | |