#!/usr/bin/env python3 """Enhanced portfolio management with sector diversification and position cleanup""" import sys sys.path.append('/home/wdjones/.openclaw/workspace/projects/market-watch') import game_engine import yfinance as yf from datetime import datetime MIN_POSITION_SIZE = 1000 # Minimum position size in dollars MAX_SECTOR_PCT = 30 # Maximum sector allocation percentage MIN_CASH_PCT = 15 # Minimum cash percentage def cleanup_small_positions(game_id, username, min_size=MIN_POSITION_SIZE, dry_run=True): """Sell positions below minimum size threshold""" p = game_engine.get_portfolio(game_id, username) if not p: return {"error": "Portfolio not found"} small_positions = [] actions = [] for ticker, pos in p["positions"].items(): if pos["market_value"] < min_size: small_positions.append({ "ticker": ticker, "value": pos["market_value"], "shares": pos["shares"], "price": pos["current_price"] }) if not small_positions: return {"message": "No small positions found", "actions": []} print(f"Found {len(small_positions)} positions under ${min_size:,}:") total_cleanup_value = 0 for pos in small_positions: reason = f"Position cleanup: ${pos['value']:.0f} < ${min_size:,} minimum" action = { "action": "SELL", "ticker": pos["ticker"], "shares": pos["shares"], "price": pos["price"], "value": pos["value"], "reason": reason } actions.append(action) total_cleanup_value += pos["value"] print(f" - SELL {pos['ticker']}: {pos['shares']} @ ${pos['price']:.2f} = ${pos['value']:.0f}") if not dry_run: result = game_engine.sell(game_id, username, pos["ticker"], pos["shares"], pos["price"], reason) action["result"] = result return { "message": f"Cleanup would free ${total_cleanup_value:.0f} from {len(small_positions)} small positions", "actions": actions, "total_value": total_cleanup_value } def analyze_sector_opportunities(game_id, username): """Analyze sector allocation and identify opportunities for better diversification""" p = game_engine.get_portfolio(game_id, username) if not p: return {"error": "Portfolio not found"} sectors = game_engine.get_sector_allocation(game_id, username) # Calculate current allocations invested_value = p["total_value"] - p["cash"] cash_pct = (p["cash"] / p["total_value"]) * 100 print(f"\nSector Analysis:") print(f"Portfolio Value: ${p['total_value']:,.2f}") print(f"Invested: ${invested_value:,.2f}") print(f"Cash: ${p['cash']:,.2f} ({cash_pct:.1f}%)") print(f"Min Cash Required: ${p['total_value'] * MIN_CASH_PCT / 100:,.2f} ({MIN_CASH_PCT}%)") print(f"\nSector Allocation:") recommendations = [] for sector, pct in sorted(sectors.items(), key=lambda x: x[1], reverse=True): status = "" if pct > MAX_SECTOR_PCT: excess = pct - MAX_SECTOR_PCT excess_value = (excess / 100) * p["total_value"] status = f" *** OVERWEIGHT by {excess:.1f}% (${excess_value:,.0f}) ***" recommendations.append({ "type": "REDUCE", "sector": sector, "current_pct": pct, "target_pct": MAX_SECTOR_PCT, "excess_value": excess_value }) elif pct < 5: status = " (Underweight - potential opportunity)" print(f" {sector}: {pct:.1f}%{status}") # Identify underrepresented sectors major_sectors = [ "Technology", "Healthcare", "Financial Services", "Consumer Cyclical", "Communication Services", "Industrials", "Consumer Defensive", "Energy" ] missing_sectors = [s for s in major_sectors if s not in sectors or sectors[s] < 5] if missing_sectors: print(f"\nUnderrepresented sectors (< 5%): {', '.join(missing_sectors)}") recommendations.extend([ {"type": "ADD", "sector": sector, "current_pct": sectors.get(sector, 0)} for sector in missing_sectors ]) return { "sectors": sectors, "cash_pct": cash_pct, "recommendations": recommendations } def suggest_rebalance_actions(game_id, username): """Suggest comprehensive rebalance actions""" print("=== ENHANCED PORTFOLIO REBALANCE ANALYSIS ===") # 1. Clean up small positions print("\n1. Position Cleanup Analysis:") cleanup_result = cleanup_small_positions(game_id, username, dry_run=True) if cleanup_result.get("actions"): print(cleanup_result["message"]) else: print("No small positions to cleanup") # 2. Analyze sectors print("\n2. Sector Diversification Analysis:") sector_analysis = analyze_sector_opportunities(game_id, username) # 3. Check existing violations print("\n3. Current Violations Check:") violation_check = game_engine.rebalance_portfolio(game_id, username, dry_run=True) if violation_check["violations"]: print("Violations found:") for v in violation_check["violations"]: print(f" - {v}") else: print("No current violations") # 4. Generate action plan print("\n4. Recommended Action Plan:") action_plan = [] # Add cleanup actions if needed if cleanup_result.get("actions"): action_plan.extend(cleanup_result["actions"]) # Add violation fixes if needed if violation_check.get("actions"): action_plan.extend(violation_check["actions"]) if action_plan: print("Recommended actions:") for i, action in enumerate(action_plan, 1): print(f" {i}. {action['action']} {action['shares']} {action['ticker']} @ ${action['price']:.2f} - {action['reason']}") else: print("No immediate actions required") return { "cleanup": cleanup_result, "sectors": sector_analysis, "violations": violation_check, "action_plan": action_plan } def execute_rebalance(game_id, username): """Execute the rebalance plan""" print("=== EXECUTING PORTFOLIO REBALANCE ===") # 1. Execute cleanup print("\n1. Executing position cleanup...") cleanup_result = cleanup_small_positions(game_id, username, dry_run=False) # 2. Execute violation fixes print("\n2. Executing violation fixes...") violation_result = game_engine.rebalance_portfolio(game_id, username, dry_run=False) print("\nRebalance completed!") return { "cleanup": cleanup_result, "violations": violation_result } def main(): gid = game_engine.get_default_game_id() if not gid: print("No game found") return # Run analysis analysis = suggest_rebalance_actions(gid, "case") # Ask if user wants to execute print("\n" + "="*50) response = input("Execute rebalance actions? (y/N): ").strip().lower() if response in ['y', 'yes']: execute_rebalance(gid, "case") # Show updated status print("\n" + "="*50) print("UPDATED PORTFOLIO STATUS:") import subprocess subprocess.run(["python3", "check_portfolio.py"], cwd="/home/wdjones/.openclaw/workspace-glitch") else: print("Rebalance cancelled") if __name__ == "__main__": main()