Files
workspace/projects/market-watch/enhanced_portfolio_manager.py

218 lines
7.5 KiB
Python

#!/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()