#!/usr/bin/env python3 """Enhanced GARP screener with comprehensive sector diversification and rebalancing""" import sys sys.path.append('/home/wdjones/.openclaw/workspace/projects/market-watch') import scanner import game_engine from collections import defaultdict import yfinance as yf # Sector diversification targets TARGET_SECTORS = { "Technology": 20, "Healthcare": 15, "Financial Services": 25, "Consumer Cyclical": 10, "Communication Services": 10, "Industrials": 10, "Consumer Defensive": 5, "Energy": 5 } MAX_SECTOR_PCT = 30 MIN_CASH_PCT = 15 def analyze_portfolio_risks(game_id, username): """Analyze current portfolio for sector and cash risks""" p = game_engine.get_portfolio(game_id, username) if not p: return None current_sectors = game_engine.get_sector_allocation(game_id, username) cash_pct = (p["cash"] / p["total_value"]) * 100 risks = { "sector_violations": [], "cash_risk": None, "recommendations": [] } # Check sector caps for sector, pct in current_sectors.items(): if pct > MAX_SECTOR_PCT: excess_pct = pct - MAX_SECTOR_PCT excess_value = (excess_pct / 100) * p["total_value"] risks["sector_violations"].append({ "sector": sector, "current_pct": pct, "excess_pct": excess_pct, "excess_value": excess_value }) # Check cash reserves if cash_pct < MIN_CASH_PCT: cash_deficit = (MIN_CASH_PCT - cash_pct) / 100 * p["total_value"] risks["cash_risk"] = { "current_pct": cash_pct, "deficit_pct": MIN_CASH_PCT - cash_pct, "deficit_value": cash_deficit } elif cash_pct > 70: # Too much cash sitting idle risks["cash_risk"] = { "current_pct": cash_pct, "excess_pct": cash_pct - 15, "deployable_value": ((cash_pct - 15) / 100) * p["total_value"] } return risks def get_rebalancing_recommendations(game_id, username): """Get specific rebalancing recommendations using game_engine functions""" risks = analyze_portfolio_risks(game_id, username) if not risks: return [] recommendations = [] # Use game_engine's rebalance function for sell recommendations if risks["sector_violations"] or risks["cash_risk"]: rebalance_result = game_engine.rebalance_portfolio(game_id, username, dry_run=True) if rebalance_result["success"] and rebalance_result["actions"]: recommendations.extend([{ "type": "SELL", "ticker": action["ticker"], "shares": action["shares"], "reason": action["reason"], "estimated_proceeds": action["value"] } for action in rebalance_result["actions"]]) # Add buy recommendations for underallocated sectors current_sectors = game_engine.get_sector_allocation(game_id, username) p = game_engine.get_portfolio(game_id, username) # Calculate deployable cash (current cash minus minimum reserve) min_cash_reserve = p["total_value"] * (MIN_CASH_PCT / 100) deployable_cash = max(0, p["cash"] - min_cash_reserve) if deployable_cash > 1000: # Only recommend if we have meaningful cash to deploy sector_priorities = get_sector_priority_scoring(game_id, username) underallocated = [(sector, shortage) for sector, shortage in sector_priorities.items() if shortage > 2] underallocated.sort(key=lambda x: x[1], reverse=True) if underallocated: recommendations.append({ "type": "DIVERSIFY", "deployable_cash": deployable_cash, "priority_sectors": underallocated[:3], "reason": "Deploy excess cash to underallocated sectors" }) return recommendations def get_sector_priority_scoring(game_id, username): """Calculate sector priority scoring based on current allocation vs targets""" current_sectors = game_engine.get_sector_allocation(game_id, username) priority_scores = {} for sector, target_pct in TARGET_SECTORS.items(): current_pct = current_sectors.get(sector, 0) # Higher score for underallocated sectors shortage = max(0, target_pct - current_pct) priority_scores[sector] = shortage return priority_scores def enhanced_garp_scan_with_diversification(game_id=None, username="case"): """Run GARP scan with sector diversification prioritization and sector data""" print("=== Enhanced GARP Scan with Sector Diversification ===") # Get current scan results latest_scan = scanner.load_latest_scan() if not latest_scan: print("No recent scan found, running new scan...") candidates = scanner.run_scan() latest_scan = scanner.load_latest_scan() else: candidates = latest_scan.get("candidates", []) if not candidates: print("No GARP candidates found") return [] print(f"Found {len(candidates)} GARP candidates") # Enhance candidates with sector information for candidate in candidates: if "sector" not in candidate: candidate["sector"] = game_engine.get_stock_sector(candidate["ticker"]) or "Unknown" # Group by sector for analysis by_sector = defaultdict(list) for candidate in candidates: sector = candidate.get("sector", "Unknown") by_sector[sector].append(candidate) print(f"\nšŸ“Š Sector Distribution in GARP Candidates:") for sector in sorted(by_sector.keys()): count = len(by_sector[sector]) print(f" {sector}: {count} candidates") # Portfolio context if available if game_id: print(f"\nšŸ’¼ Current Portfolio Analysis:") p = game_engine.get_portfolio(game_id, username) current_sectors = game_engine.get_sector_allocation(game_id, username) cash_pct = (p["cash"] / p["total_value"]) * 100 print(f" Total Value: ${p['total_value']:,.2f}") print(f" Cash: ${p['cash']:,.2f} ({cash_pct:.1f}%)") print(f" Positions: {p['num_positions']}") print(f"\nšŸ“ˆ Current Sector Allocation vs Targets:") all_sectors = set(TARGET_SECTORS.keys()) | set(current_sectors.keys()) for sector in sorted(all_sectors): current_pct = current_sectors.get(sector, 0) target_pct = TARGET_SECTORS.get(sector, 0) status = "" if current_pct > MAX_SECTOR_PCT: status = " āš ļø OVER CAP" elif current_pct > target_pct * 1.5: status = " šŸ”ø OVER TARGET" elif current_pct < target_pct * 0.5 and target_pct > 5: status = " šŸ”¹ UNDER TARGET" print(f" {sector:25s}: {current_pct:5.1f}% (target: {target_pct:2d}%){status}") # Analyze risks risks = analyze_portfolio_risks(game_id, username) if risks["sector_violations"]: print(f"\nāš ļø SECTOR VIOLATIONS:") for violation in risks["sector_violations"]: print(f" {violation['sector']}: {violation['current_pct']:.1f}% " + f"(excess: {violation['excess_pct']:.1f}%, ${violation['excess_value']:,.2f})") if risks["cash_risk"]: cash_risk = risks["cash_risk"] if "deficit_pct" in cash_risk: print(f"\nšŸ’ø CASH RESERVE RISK: {cash_risk['current_pct']:.1f}% " + f"(need ${cash_risk['deficit_value']:,.2f} more)") elif "excess_pct" in cash_risk: print(f"\nšŸ’° EXCESS CASH: {cash_risk['current_pct']:.1f}% " + f"(${cash_risk['deployable_value']:,.2f} available for deployment)") # Get sector priorities for candidate scoring sector_priorities = get_sector_priority_scoring(game_id, username) # Enhance candidates with sector priority scoring for candidate in candidates: sector = candidate.get("sector", "Unknown") # Base score (lower is better) base_score = candidate.get("score", 0) # Sector priority bonus (negative to improve score for high priority sectors) sector_bonus = 0 if game_id and sector in sector_priorities: sector_bonus = -sector_priorities[sector] * 0.5 candidate["enhanced_score"] = base_score + sector_bonus candidate["sector_priority"] = sector_priorities.get(sector, 0) if game_id else 0 # Sort by enhanced score enhanced_candidates = sorted(candidates, key=lambda x: x["enhanced_score"]) print(f"\nšŸŽÆ Top Diversified GARP Candidates:") print(f"{'#':>2} {'Ticker':>6} {'Sector':>20} {'Price':>8} {'PE':>6} {'FwdPE':>6} {'RevGr':>6} {'EPSGr':>6} {'RSI':>5} {'Priority'}") print("-" * 95) for i, c in enumerate(enhanced_candidates[:15], 1): priority_note = f"{c['sector_priority']:4.1f}" if c['sector_priority'] > 0 else " -" rsi = f"{c.get('rsi', 0):4.0f}" if c.get('rsi') else " N/A" print(f"{i:2d} {c['ticker']:>6s} {c['sector'][:20]:>20s} ${c['price']:7.2f} " + f"{c['trailing_pe']:5.1f} {c['forward_pe']:5.1f} {c['revenue_growth']:5.1f}% " + f"{c['earnings_growth']:5.1f}% {rsi} {priority_note}") return enhanced_candidates def suggest_specific_trades(game_id, username, max_suggestions=5): """Generate specific buy/sell recommendations with dollar amounts""" print(f"\nšŸŽÆ SPECIFIC TRADE RECOMMENDATIONS:") # Get rebalancing recommendations first rebalance_recs = get_rebalancing_recommendations(game_id, username) if rebalance_recs: # Show sell recommendations sells = [r for r in rebalance_recs if r["type"] == "SELL"] if sells: print(f"\nšŸ”“ SELL RECOMMENDATIONS (Risk Reduction):") for i, rec in enumerate(sells, 1): print(f" {i}. SELL {rec['shares']} shares of {rec['ticker']}") print(f" Proceeds: ${rec['estimated_proceeds']:,.2f}") print(f" Reason: {rec['reason']}") # Show diversification recommendations diversify_recs = [r for r in rebalance_recs if r["type"] == "DIVERSIFY"] if diversify_recs: rec = diversify_recs[0] print(f"\n🟢 BUY RECOMMENDATIONS (Diversification):") print(f"Available Cash for Deployment: ${rec['deployable_cash']:,.2f}") print(f"Priority Sectors (shortage from target):") # Get GARP candidates for priority sectors candidates = enhanced_garp_scan_with_diversification(game_id, username) p = game_engine.get_portfolio(game_id, username) existing_tickers = set(p["positions"].keys()) cash_per_sector = rec['deployable_cash'] / min(3, len(rec['priority_sectors'])) for sector, shortage in rec['priority_sectors']: print(f"\n šŸ“ {sector} (need {shortage:.1f}% more):") sector_candidates = [c for c in candidates[:20] if c.get('sector') == sector and c['ticker'] not in existing_tickers] if sector_candidates: best_candidate = sector_candidates[0] shares_to_buy = int(cash_per_sector / best_candidate['price']) investment = shares_to_buy * best_candidate['price'] print(f" → BUY {shares_to_buy} shares of {best_candidate['ticker']}") print(f" Price: ${best_candidate['price']:.2f} | Investment: ${investment:,.2f}") print(f" PE: {best_candidate['trailing_pe']:.1f} | FwdPE: {best_candidate['forward_pe']:.1f}") print(f" RevGr: {best_candidate['revenue_growth']:.1f}% | EPSGr: {best_candidate['earnings_growth']:.1f}%") else: print(f" āš ļø No suitable GARP candidates found in {sector}") else: print("āœ… Portfolio is well-balanced. No immediate rebalancing needed.") # Still show opportunities for excess cash deployment p = game_engine.get_portfolio(game_id, username) cash_pct = (p["cash"] / p["total_value"]) * 100 if cash_pct > 25: # Significant cash position print(f"\nšŸ’° Cash Deployment Opportunities:") print(f"Current cash: ${p['cash']:,.2f} ({cash_pct:.1f}% of portfolio)") min_reserve = p["total_value"] * (MIN_CASH_PCT / 100) deployable = p["cash"] - min_reserve if deployable > 1000: print(f"Deployable (above {MIN_CASH_PCT}% reserve): ${deployable:,.2f}") # Show top GARP picks regardless of sector candidates = enhanced_garp_scan_with_diversification(game_id, username) existing_tickers = set(p["positions"].keys()) available_candidates = [c for c in candidates[:10] if c['ticker'] not in existing_tickers] if available_candidates: print(f"\nTop New GARP Opportunities:") for i, c in enumerate(available_candidates[:3], 1): shares = int(deployable * 0.33 / c['price']) # 1/3 of deployable cash investment = shares * c['price'] print(f" {i}. {c['ticker']} ({c['sector']})") print(f" Buy {shares} shares @ ${c['price']:.2f} = ${investment:,.2f}") print(f" PE: {c['trailing_pe']:.1f} | FwdPE: {c['forward_pe']:.1f}") def suggest_sector_buy_candidates(game_id, username, max_suggestions=5): """Legacy function - maintained for compatibility""" return suggest_specific_trades(game_id, username, max_suggestions) def main(): """Main execution with comprehensive portfolio analysis""" gid = game_engine.get_default_game_id() if gid: # Run enhanced scan with portfolio context candidates = enhanced_garp_scan_with_diversification(gid, "case") # Generate specific trade recommendations suggest_specific_trades(gid, "case") print(f"\n" + "="*80) print(f"šŸ’” PORTFOLIO HEALTH SUMMARY:") # Quick portfolio health check risks = analyze_portfolio_risks(gid, "case") health_score = 100 if risks["sector_violations"]: health_score -= len(risks["sector_violations"]) * 20 print(f"āŒ Sector violations detected (-{len(risks['sector_violations']) * 20} points)") else: print(f"āœ… All sectors within limits (+0 points)") if risks["cash_risk"] and "deficit_pct" in risks["cash_risk"]: health_score -= 15 print(f"āŒ Insufficient cash reserves (-15 points)") elif risks["cash_risk"] and "excess_pct" in risks["cash_risk"]: if risks["cash_risk"]["excess_pct"] > 50: health_score -= 10 print(f"āš ļø Significant cash drag (-10 points)") else: print(f"āœ… Cash reserves adequate (+0 points)") p = game_engine.get_portfolio(gid, "case") current_sectors = game_engine.get_sector_allocation(gid, "case") diversification_score = len(current_sectors) # Simple metric if diversification_score >= 4: print(f"āœ… Good diversification across {diversification_score} sectors") elif diversification_score >= 2: health_score -= 10 print(f"āš ļø Limited diversification ({diversification_score} sectors) (-10 points)") else: health_score -= 20 print(f"āŒ Poor diversification ({diversification_score} sectors) (-20 points)") health_score = max(0, health_score) print(f"\nšŸ“Š OVERALL PORTFOLIO HEALTH: {health_score}/100") if health_score >= 90: print("🟢 Excellent portfolio management!") elif health_score >= 70: print("🟔 Good portfolio, minor optimizations possible") elif health_score >= 50: print("🟠 Portfolio needs attention") else: print("šŸ”“ Portfolio requires immediate rebalancing") else: print("āŒ No active game found. Cannot provide portfolio analysis.") # Still run basic GARP scan candidates = enhanced_garp_scan_with_diversification() if __name__ == "__main__": main()