|
|
"""
|
|
|
Demo Data for Portfolio Optimization
|
|
|
|
|
|
This module provides sample stock data for the portfolio optimization quickstart.
|
|
|
The data includes 20 stocks across 4 sectors with ML-predicted returns.
|
|
|
|
|
|
In a real application, these predictions would come from an ML model trained
|
|
|
on historical stock data. For this quickstart, we use hardcoded realistic values.
|
|
|
|
|
|
FINANCE CONCEPTS:
|
|
|
- predicted_return: Expected percentage gain (0.12 = 12% expected return)
|
|
|
- sector: Industry classification for diversification
|
|
|
- Equal weight: Each selected stock gets 100%/20 = 5% of the portfolio
|
|
|
"""
|
|
|
from enum import Enum
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
from .domain import StockSelection, PortfolioOptimizationPlan, PortfolioConfig
|
|
|
|
|
|
|
|
|
class DemoData(Enum):
|
|
|
"""Available demo datasets."""
|
|
|
SMALL = 'SMALL'
|
|
|
LARGE = 'LARGE'
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
class DemoDataConfig:
|
|
|
"""Configuration for demo data generation."""
|
|
|
target_position_count: int
|
|
|
max_sector_percentage: float
|
|
|
|
|
|
|
|
|
demo_data_configs = {
|
|
|
DemoData.SMALL: DemoDataConfig(
|
|
|
target_position_count=20,
|
|
|
max_sector_percentage=0.25,
|
|
|
),
|
|
|
DemoData.LARGE: DemoDataConfig(
|
|
|
target_position_count=20,
|
|
|
max_sector_percentage=0.25,
|
|
|
),
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SMALL_DATASET_STOCKS = [
|
|
|
|
|
|
|
|
|
("AAPL", "Apple Inc.", "Technology", 0.12),
|
|
|
("GOOGL", "Alphabet (Google)", "Technology", 0.15),
|
|
|
("MSFT", "Microsoft Corp.", "Technology", 0.10),
|
|
|
("NVDA", "NVIDIA Corp.", "Technology", 0.18),
|
|
|
("META", "Meta Platforms", "Technology", 0.08),
|
|
|
("TSLA", "Tesla Inc.", "Technology", 0.20),
|
|
|
("AMD", "AMD Inc.", "Technology", 0.14),
|
|
|
|
|
|
|
|
|
|
|
|
("JNJ", "Johnson & Johnson", "Healthcare", 0.09),
|
|
|
("UNH", "UnitedHealth Group", "Healthcare", 0.11),
|
|
|
("PFE", "Pfizer Inc.", "Healthcare", 0.07),
|
|
|
("ABBV", "AbbVie Inc.", "Healthcare", 0.10),
|
|
|
("TMO", "Thermo Fisher", "Healthcare", 0.13),
|
|
|
("DHR", "Danaher Corp.", "Healthcare", 0.12),
|
|
|
|
|
|
|
|
|
|
|
|
("JPM", "JPMorgan Chase", "Finance", 0.08),
|
|
|
("BAC", "Bank of America", "Finance", 0.06),
|
|
|
("WFC", "Wells Fargo", "Finance", 0.07),
|
|
|
("GS", "Goldman Sachs", "Finance", 0.09),
|
|
|
("MS", "Morgan Stanley", "Finance", 0.08),
|
|
|
("C", "Citigroup", "Finance", 0.05),
|
|
|
|
|
|
|
|
|
|
|
|
("XOM", "Exxon Mobil", "Energy", 0.04),
|
|
|
("CVX", "Chevron Corp.", "Energy", 0.05),
|
|
|
("COP", "ConocoPhillips", "Energy", 0.06),
|
|
|
("SLB", "Schlumberger", "Energy", 0.03),
|
|
|
("EOG", "EOG Resources", "Energy", 0.07),
|
|
|
("PXD", "Pioneer Natural", "Energy", 0.08),
|
|
|
]
|
|
|
|
|
|
LARGE_DATASET_STOCKS = SMALL_DATASET_STOCKS + [
|
|
|
|
|
|
("CRM", "Salesforce", "Technology", 0.11),
|
|
|
("ADBE", "Adobe Inc.", "Technology", 0.09),
|
|
|
("ORCL", "Oracle Corp.", "Technology", 0.07),
|
|
|
("CSCO", "Cisco Systems", "Technology", 0.06),
|
|
|
("IBM", "IBM Corp.", "Technology", 0.04),
|
|
|
("QCOM", "Qualcomm", "Technology", 0.13),
|
|
|
|
|
|
|
|
|
("MRK", "Merck & Co.", "Healthcare", 0.08),
|
|
|
("LLY", "Eli Lilly", "Healthcare", 0.16),
|
|
|
("BMY", "Bristol-Myers", "Healthcare", 0.06),
|
|
|
("AMGN", "Amgen Inc.", "Healthcare", 0.09),
|
|
|
("GILD", "Gilead Sciences", "Healthcare", 0.05),
|
|
|
("ISRG", "Intuitive Surgical", "Healthcare", 0.14),
|
|
|
|
|
|
|
|
|
("AXP", "American Express", "Finance", 0.10),
|
|
|
("BLK", "BlackRock", "Finance", 0.11),
|
|
|
("SCHW", "Charles Schwab", "Finance", 0.07),
|
|
|
("USB", "U.S. Bancorp", "Finance", 0.04),
|
|
|
|
|
|
|
|
|
("OXY", "Occidental Petroleum", "Energy", 0.06),
|
|
|
("HAL", "Halliburton", "Energy", 0.05),
|
|
|
|
|
|
|
|
|
("AMZN", "Amazon.com", "Consumer", 0.14),
|
|
|
("WMT", "Walmart", "Consumer", 0.06),
|
|
|
("HD", "Home Depot", "Consumer", 0.08),
|
|
|
("MCD", "McDonald's", "Consumer", 0.07),
|
|
|
("NKE", "Nike Inc.", "Consumer", 0.09),
|
|
|
("SBUX", "Starbucks", "Consumer", 0.05),
|
|
|
("PG", "Procter & Gamble", "Consumer", 0.04),
|
|
|
("KO", "Coca-Cola", "Consumer", 0.05),
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def generate_demo_data(demo_data: DemoData) -> PortfolioOptimizationPlan:
|
|
|
"""
|
|
|
Generate demo data for portfolio optimization.
|
|
|
|
|
|
Args:
|
|
|
demo_data: Which demo dataset to generate (SMALL or LARGE)
|
|
|
|
|
|
Returns:
|
|
|
PortfolioOptimizationPlan with candidate stocks (all unselected initially)
|
|
|
|
|
|
Example:
|
|
|
>>> plan = generate_demo_data(DemoData.SMALL)
|
|
|
>>> len(plan.stocks)
|
|
|
20
|
|
|
>>> plan.stocks[0].stock_id
|
|
|
'AAPL'
|
|
|
"""
|
|
|
config = demo_data_configs[demo_data]
|
|
|
stock_data = SMALL_DATASET_STOCKS if demo_data == DemoData.SMALL else LARGE_DATASET_STOCKS
|
|
|
|
|
|
stocks = [
|
|
|
StockSelection(
|
|
|
stock_id=ticker,
|
|
|
stock_name=name,
|
|
|
sector=sector,
|
|
|
predicted_return=predicted_return,
|
|
|
selection=None,
|
|
|
)
|
|
|
for ticker, name, sector, predicted_return in stock_data
|
|
|
]
|
|
|
|
|
|
|
|
|
target_count = config.target_position_count
|
|
|
max_per_sector = max(1, int(config.max_sector_percentage * target_count))
|
|
|
|
|
|
|
|
|
portfolio_config = PortfolioConfig(
|
|
|
target_count=target_count,
|
|
|
max_per_sector=max_per_sector,
|
|
|
unselected_penalty=10000,
|
|
|
)
|
|
|
|
|
|
return PortfolioOptimizationPlan(
|
|
|
stocks=stocks,
|
|
|
target_position_count=config.target_position_count,
|
|
|
max_sector_percentage=config.max_sector_percentage,
|
|
|
portfolio_config=portfolio_config,
|
|
|
)
|
|
|
|
|
|
|
|
|
def get_stock_summary(plan: PortfolioOptimizationPlan) -> str:
|
|
|
"""
|
|
|
Generate a human-readable summary of the portfolio.
|
|
|
|
|
|
Useful for debugging and understanding the solution.
|
|
|
"""
|
|
|
lines = [
|
|
|
"=" * 60,
|
|
|
"PORTFOLIO SUMMARY",
|
|
|
"=" * 60,
|
|
|
]
|
|
|
|
|
|
selected = plan.get_selected_stocks()
|
|
|
if not selected:
|
|
|
lines.append("No stocks selected yet.")
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
weight = plan.get_weight_per_stock()
|
|
|
expected_return = plan.get_expected_return()
|
|
|
|
|
|
lines.append(f"Selected: {len(selected)} stocks @ {weight*100:.1f}% each")
|
|
|
lines.append(f"Expected Return: {expected_return*100:.2f}%")
|
|
|
lines.append("")
|
|
|
|
|
|
|
|
|
sector_stocks: dict[str, list[StockSelection]] = {}
|
|
|
for stock in selected:
|
|
|
if stock.sector not in sector_stocks:
|
|
|
sector_stocks[stock.sector] = []
|
|
|
sector_stocks[stock.sector].append(stock)
|
|
|
|
|
|
lines.append("BY SECTOR:")
|
|
|
for sector, stocks in sorted(sector_stocks.items()):
|
|
|
sector_weight = len(stocks) * weight * 100
|
|
|
lines.append(f" {sector}: {len(stocks)} stocks = {sector_weight:.1f}%")
|
|
|
for stock in sorted(stocks, key=lambda s: -s.predicted_return):
|
|
|
lines.append(f" - {stock.stock_id}: {stock.stock_name} ({stock.predicted_return*100:.1f}% pred)")
|
|
|
|
|
|
lines.append("")
|
|
|
lines.append(f"Score: {plan.score}")
|
|
|
lines.append("=" * 60)
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|