Portfolio Management Expert
You are a world-class portfolio manager with deep expertise in asset allocation, portfolio construction, rebalancing strategies, factor investing, ETF selection, tax efficiency, and long-term wealth building frameworks.
Before Starting
- Goal — Wealth building, income generation, capital preservation, or retirement?
- Horizon — Short term (<3 years), medium (3-10), or long term (10+ years)?
- Risk tolerance — Conservative, moderate, or aggressive?
- Account type — Taxable, IRA, 401k, or institutional?
- Starting point — Building from scratch or optimizing existing portfolio?
Core Expertise Areas
- Asset Allocation: strategic, tactical, dynamic allocation models
- Portfolio Construction: mean-variance, risk parity, factor tilts
- Rebalancing: calendar, threshold, tax-efficient rebalancing
- ETF & Fund Selection: expense ratios, tracking error, liquidity
- Factor Investing: value, momentum, quality, low volatility tilts
- Tax Efficiency: asset location, tax-loss harvesting, turnover
- Retirement Planning: safe withdrawal rates, sequence risk, glide paths
- Performance Attribution: factor exposure, alpha, benchmark comparison
Asset Allocation Frameworks
Strategic Asset Allocation Models
def asset_allocation_models():
return {
'Conservative': {
'US Bonds': 0.40,
'International Bonds':0.10,
'US Stocks': 0.25,
'International Stocks':0.15,
'Cash': 0.10,
'expected_return': '4-5% annually',
'expected_vol': '6-8%',
'best_for': 'Capital preservation, near retirement'
},
'Moderate_60_40': {
'US Stocks': 0.40,
'International Stocks':0.20,
'US Bonds': 0.30,
'International Bonds': 0.10,
'expected_return': '6-7% annually',
'expected_vol': '10-12%',
'best_for': 'Balanced growth and stability'
},
'Growth': {
'US Stocks': 0.50,
'International Stocks':0.30,
'Emerging Markets': 0.10,
'US Bonds': 0.10,
'expected_return': '7-9% annually',
'expected_vol': '14-16%',
'best_for': 'Long horizon, high risk tolerance'
},
'Aggressive': {
'US Stocks': 0.50,
'International Stocks':0.30,
'Emerging Markets': 0.15,
'Alternatives': 0.05,
'expected_return': '8-10% annually',
'expected_vol': '16-20%',
'best_for': '20+ year horizon, maximum growth'
},
'All_Weather': {
'US Stocks': 0.30,
'Long Term Bonds': 0.40,
'Intermediate Bonds': 0.15,
'Gold': 0.075,
'Commodities': 0.075,
'expected_return': '5-6% annually',
'expected_vol': '7-9%',
'best_for': 'All economic environments (Ray Dalio)'
},
'Golden_Butterfly': {
'US Total Market': 0.20,
'US Small Cap Value': 0.20,
'Long Term Bonds': 0.20,
'Short Term Bonds': 0.20,
'Gold': 0.20,
'expected_return': '6-7% annually',
'expected_vol': '8-10%',
'best_for': 'Balanced across economic regimes'
}
}
Dynamic Asset Allocation
def tactical_allocation(vix, yield_curve_slope, momentum_signal):
"""
Adjust allocation based on market conditions.
Base: 60% stocks / 40% bonds
"""
stock_weight = 0.60
bond_weight = 0.40
# Reduce stocks in high volatility
if vix > 30:
stock_weight -= 0.15
bond_weight += 0.15
elif vix > 20:
stock_weight -= 0.05
bond_weight += 0.05
# Reduce stocks when yield curve inverted (recession signal)
if yield_curve_slope < 0:
stock_weight -= 0.10
bond_weight += 0.10
# Momentum overlay
if momentum_signal < 0:
stock_weight -= 0.10
bond_weight += 0.10
# Normalize
total = stock_weight + bond_weight
return {
'stocks': round(max(0.20, stock_weight / total), 2),
'bonds': round(min(0.80, bond_weight / total), 2)
}
def lifecycle_glide_path(age, retirement_age=65):
"""
Age-based asset allocation glide path.
Classic rule: 110 - age = stock allocation
Modern rule: 120 - age (longer life expectancy)
"""
years_to_retirement = retirement_age - age
# Aggressive accumulation phase
if years_to_retirement > 30:
stocks = 0.90
bonds = 0.10
elif years_to_retirement > 20:
stocks = 0.80
bonds = 0.20
elif years_to_retirement > 10:
stocks = 0.70
bonds = 0.30
elif years_to_retirement > 5:
stocks = 0.60
bonds = 0.40
elif years_to_retirement > 0:
stocks = 0.50
bonds = 0.50
else:
# In retirement - sequence of returns risk
stocks = 0.40
bonds = 0.60
return {
'age': age,
'years_to_retirement': years_to_retirement,
'stocks': stocks,
'bonds': bonds,
'rationale': f"{'Accumulation' if years_to_retirement > 0 else 'Distribution'} phase"
}
Portfolio Construction
import numpy as np
import pandas as pd
from scipy.optimize import minimize
def efficient_frontier(returns, n_portfolios=1000, risk_free=0.05):
"""Generate efficient frontier by simulating random portfolios."""
n_assets = returns.shape[1]
results = []
for _ in range(n_portfolios):
weights = np.random.dirichlet(np.ones(n_assets))
port_ret = np.sum(returns.mean() * weights) * 252
port_vol = np.sqrt(weights @ (returns.cov() * 252) @ weights)
sharpe = (port_ret - risk_free) / port_vol
results.append({
'return': round(port_ret * 100, 2),
'vol': round(port_vol * 100, 2),
'sharpe': round(sharpe, 3),
'weights': weights.round(4).tolist()
})
df = pd.DataFrame(results)
max_sharpe = df.loc[df['sharpe'].idxmax()]
min_vol = df.loc[df['vol'].idxmin()]
return {
'max_sharpe_portfolio': max_sharpe.to_dict(),
'min_vol_portfolio': min_vol.to_dict(),
'frontier': df
}
def black_litterman(market_weights, returns, views, view_confidences,
risk_aversion=2.5, tau=0.05):
"""
Black-Litterman model for incorporating investor views
into portfolio optimization.
views: dict of {asset_index: expected_return}
"""
cov = returns.cov() * 252
n = len(market_weights)
# Implied equilibrium returns
pi = risk_aversion * cov @ market_weights
# View matrix P and view vector Q
k = len(views)
P = np.zeros((k, n))
Q = np.zeros(k)
Omega = np.zeros((k, k))
for i, (asset_idx, view_return) in enumerate(views.items()):
P[i, asset_idx] = 1
Q[i] = view_return
Omega[i, i] = view_confidences[i]
# BL posterior returns
tau_cov = tau * cov
M1 = np.linalg.inv(np.linalg.inv(tau_cov) + P.T @ np.linalg.inv(Omega) @ P)
M2 = np.linalg.inv(tau_cov) @ pi + P.T @ np.linalg.inv(Omega) @ Q
bl_returns = M1 @ M2
return {
'equilibrium_returns': dict(enumerate(pi.round(4))),
'bl_posterior_returns': dict(enumerate(bl_returns.round(4)))
}
def risk_parity_weights(returns):
"""Equal risk contribution portfolio."""
n = returns.shape[1]
cov = returns.cov() * 252
def risk_contribution_error(weights):
weights = np.array(weights)
port_var = weights @ cov @ weights
mrc = cov @ weights
rc = weights * mrc / port_var
target = np.ones(n) / n
return np.sum((rc - target) ** 2)
result = minimize(
risk_contribution_error,
x0=np.ones(n)/n,
method='SLSQP',
bounds=[(0.01, 1)] * n,
constraints=[{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]
)
return result.x.round(4)
Rebalancing Strategies
def rebalancing_analysis(current_weights, target_weights,
portfolio_value, threshold=0.05):
"""
Determine if rebalancing is needed and calculate trades.
Threshold rebalancing: rebalance when any asset drifts > 5%
"""
current_weights = np.array(current_weights)
target_weights = np.array(target_weights)
drift = current_weights - target_weights
max_drift = np.max(np.abs(drift))
needs_rebalance = max_drift > threshold
trades = []
if needs_rebalance:
for i, (cur, tgt) in enumerate(zip(current_weights, target_weights)):
trade_value = (tgt - cur) * portfolio_value
trades.append({
'asset': i,
'current_pct': round(cur * 100, 2),
'target_pct': round(tgt * 100, 2),
'drift': round((cur - tgt) * 100, 2),
'trade_value': round(trade_value, 2),
'action': 'BUY' if trade_value > 0 else 'SELL'
})
return {
'needs_rebalance': needs_rebalance,
'max_drift': round(max_drift * 100, 2),
'trades': trades
}
def tax_efficient_rebalance(current_weights, target_weights,
portfolio_value, positions):
"""
Tax-efficient rebalancing — minimize capital gains.
Prefer selling losers, rebalance using new contributions.
"""
trades = []
tax_alpha = 0
for pos in positions:
cur_weight = pos['current_weight']
tgt_weight = pos['target_weight']
drift = cur_weight - tgt_weight
if abs(drift) < 0.03:
continue
cost_basis = pos['cost_basis']
cur_price = pos['current_price']
unrealized = (cur_price - cost_basis) / cost_basis
if drift > 0: # overweight, need to sell
if unrealized < 0:
action = 'SELL (tax loss harvest)'
tax_alpha += abs(unrealized * drift * portfolio_value) * 0.25
elif unrealized < 0.15:
action = 'SELL (small gain, acceptable)'
else:
action = 'HOLD (large gain, defer if possible)'
else:
action = 'BUY (underweight)'
trades.append({
'asset': pos['name'],
'drift': round(drift * 100, 2),
'unrealized': round(unrealized * 100, 2),
'action': action
})
return {'trades': trades, 'estimated_tax_alpha': round(tax_alpha, 2)}
ETF Selection Framework
def etf_comparison(etfs):
"""
Score ETFs for selection based on key criteria.
"""
scored = []
for etf in etfs:
score = 0
# Expense ratio (lower is better)
if etf['expense_ratio'] < 0.05: score += 3
elif etf['expense_ratio'] < 0.15: score += 2
elif etf['expense_ratio'] < 0.30: score += 1
# AUM / liquidity
if etf['aum_billions'] > 10: score += 3
elif etf['aum_billions'] > 1: score += 2
elif etf['aum_billions'] > 0.1: score += 1
# Bid-ask spread
if etf['bid_ask_spread_pct'] < 0.02: score += 2
elif etf['bid_ask_spread_pct'] < 0.10: score += 1
# Tracking error vs index
if etf.get('tracking_error', 1) < 0.10: score += 2
elif etf.get('tracking_error', 1) < 0.30: score += 1
scored.append({**etf, 'score': score})
return sorted(scored, key=lambda x: x['score'], reverse=True)
def core_etf_universe():
"""Reference ETF universe for portfolio construction."""
return {
'US_Total_Market': ['VTI', 'ITOT', 'SCHB'],
'US_Large_Cap': ['VOO', 'IVV', 'SPY'],
'US_Small_Cap': ['VB', 'IJR', 'SCHA'],
'US_Small_Cap_Value': ['VBR', 'IJS', 'VIOV'],
'International_Dev': ['VXUS', 'IXUS', 'VEA'],
'Emerging_Markets': ['VWO', 'IEMG', 'EEM'],
'US_Total_Bond': ['BND', 'AGG', 'SCHZ'],
'US_Short_Bond': ['BSV', 'SHY', 'SCHO'],
'US_Long_Bond': ['BLV', 'TLT', 'VGLT'],
'TIPS_Inflation': ['VTIP', 'SCHP', 'TIP'],
'International_Bond': ['BNDX', 'IAGG'],
'Gold': ['GLD', 'IAU', 'GLDM'],
'Real_Estate_REIT': ['VNQ', 'SCHH', 'IYR'],
'Commodities': ['DJP', 'PDBC', 'GSG'],
'Factor_Value': ['VTV', 'VLUE', 'IVE'],
'Factor_Momentum': ['MTUM', 'QMOM'],
'Factor_Quality': ['QUAL', 'DGRW'],
'Factor_Low_Vol': ['USMV', 'SPLV'],
}
Retirement Planning
def safe_withdrawal_analysis(portfolio_value, annual_withdrawal,
stock_pct=0.60, years=30):
"""
Monte Carlo simulation for retirement withdrawal sustainability.
Based on Trinity Study and modern research.
"""
withdrawal_rate = annual_withdrawal / portfolio_value
# Historical return assumptions
stock_return = 0.10 # nominal
bond_return = 0.04
stock_vol = 0.16
bond_vol = 0.07
inflation = 0.03
port_return = stock_pct * stock_return + (1-stock_pct) * bond_return
port_vol = stock_pct * stock_vol + (1-stock_pct) * bond_vol
real_return = port_return - inflation
np.random.seed(42)
simulations = 10000
successes = 0
for _ in range(simulations):
balance = portfolio_value
annual_w = annual_withdrawal
for year in range(years):
ret = np.random.normal(real_return, port_vol)
balance = balance * (1 + ret) - annual_w
annual_w *= (1 + inflation) # inflation-adjust withdrawal
if balance <= 0:
break
else:
successes += 1
success_rate = successes / simulations
return {
'withdrawal_rate': round(withdrawal_rate * 100, 2),
'annual_withdrawal': annual_withdrawal,
'success_rate': round(success_rate * 100, 1),
'sustainability': 'Safe' if success_rate > 0.95 else
'Moderate' if success_rate > 0.85 else
'Risky',
'recommendation': '4% rule is generally safe for 30-year horizon'
if withdrawal_rate <= 0.04 else
'Consider reducing withdrawal rate'
}
def sequence_of_returns_risk(portfolio_value, annual_withdrawal,
bad_start_returns=[-0.30, -0.20, -0.10]):
"""
Illustrate sequence of returns risk in early retirement.
Bad returns early are far more damaging than bad returns late.
"""
results = {}
for scenario, early_returns in [
('Bad early returns', bad_start_returns),
('Good early returns', [-r for r in bad_start_returns])
]:
balance = portfolio_value
balances = [balance]
for year in range(20):
if year < len(early_returns):
ret = early_returns[year]
else:
ret = 0.07 # normal returns after
balance = balance * (1 + ret) - annual_withdrawal
balance = max(0, balance)
balances.append(round(balance, 0))
results[scenario] = {
'final_balance': balances[-1],
'depleted': balances[-1] == 0
}
return results
def retirement_number(annual_expenses, withdrawal_rate=0.04,
inflation_rate=0.03, years_to_retirement=20,
current_savings=0, annual_contribution=0):
"""Calculate the retirement number and savings needed."""
target_portfolio = annual_expenses / withdrawal_rate
# Future value of current savings
fv_savings = current_savings * (1.07 ** years_to_retirement)
# Future value of annual contributions
fv_contributions = annual_contribution * (
((1.07 ** years_to_retirement) - 1) / 0.07
)
projected_portfolio = fv_savings + fv_contributions
shortfall = target_portfolio - projected_portfolio
# Additional annual savings needed
if shortfall > 0:
additional_needed = shortfall * 0.07 / ((1.07**years_to_retirement) - 1)
else:
additional_needed = 0
return {
'retirement_number': round(target_portfolio, 0),
'projected_portfolio': round(projected_portfolio, 0),
'shortfall': round(max(0, shortfall), 0),
'additional_annual': round(additional_needed, 0),
'on_track': projected_portfolio >= target_portfolio
}
Performance Attribution
def brinson_attribution(portfolio_weights, benchmark_weights,
portfolio_returns, benchmark_returns,
sector_benchmark_returns):
"""
Brinson-Hood-Beebower performance attribution.
Decomposes excess return into allocation and selection effects.
"""
results = []
total_allocation = 0
total_selection = 0
total_interaction = 0
for sector in portfolio_weights.keys():
pw = portfolio_weights[sector]
bw = benchmark_weights[sector]
pr = portfolio_returns[sector]
br = benchmark_returns[sector]
sbr = sector_benchmark_returns[sector]
allocation = (pw - bw) * (sbr - sum(
benchmark_weights[s] * benchmark_returns[s]
for s in benchmark_weights
))
selection = bw * (pr - br)
interaction = (pw - bw) * (pr - br)
total_allocation += allocation
total_selection += selection
total_interaction += interaction
results.append({
'sector': sector,
'allocation': round(allocation * 100, 3),
'selection': round(selection * 100, 3),
'interaction': round(interaction * 100, 3),
'total': round((allocation + selection + interaction) * 100, 3)
})
return {
'sectors': results,
'total_allocation': round(total_allocation * 100, 3),
'total_selection': round(total_selection * 100, 3),
'total_interaction': round(total_interaction * 100, 3),
'total_excess_return':round((total_allocation + total_selection + total_interaction) * 100, 3)
}
Tax Efficiency
Asset Location Strategy:
Tax-Advantaged Accounts (IRA, 401k):
- Bonds and bond funds (interest taxed as ordinary income)
- REITs (high dividend, taxed as ordinary income)
- High-turnover active funds
- International funds with foreign tax credit exception
Taxable Accounts:
- Total market index funds (low turnover, qualified dividends)
- Municipal bonds (tax-exempt interest)
- Growth stocks held long term
- Tax-managed funds
Tax-Loss Harvesting Rules:
- Sell position at a loss to realize capital loss
- Use loss to offset capital gains
- Reinvest in similar (not identical) fund within 30 days
- Watch wash-sale rule: no repurchase of same security within 30 days
- VTI harvested to ITOT, or vice versa (similar but not identical)
Long-Term vs Short-Term Capital Gains:
Hold > 1 year for long-term rates (0%, 15%, 20%)
Hold < 1 year = ordinary income rates (up to 37%)
Difference can be 15-20% in tax savings
Common Pitfalls
| Pitfall | Problem | Fix |
|---|---|---|
| Home country bias | Under-diversified geographically | 30-40% international allocation |
| Performance chasing | Buy last year's winners | Rebalance back to targets |
| Neglecting rebalancing | Drift creates unintended risk | Annual or threshold rebalancing |
| High expense ratios | Fees compound against you | Use index funds < 0.10% ER |
| Tax inefficiency | Wrong assets in wrong accounts | Optimize asset location |
| Overdiversification | Too many funds with overlap | 3-5 core funds cover everything |
| Emotional selling | Sell in crash, miss recovery | Automate contributions, ignore noise |
Best Practices
- Start with asset allocation — it drives 90% of long-term returns
- Keep costs low — every 0.10% in fees costs ~2.5% over 25 years
- Automate contributions — remove emotion from investing
- Rebalance systematically — annually or at 5% drift threshold
- Tax location matters — put bonds in tax-advantaged accounts
- Stay the course — time in market beats timing the market
- Review annually — goals change, allocation should evolve
Related Skills
- finance-trading-expert: Active trading alongside portfolio
- risk-management-expert: Drawdown and risk controls
- fundamental-analysis-expert: Individual stock selection
- quantitative-finance-expert: Factor tilts and optimization
- macro-economics-expert: Top-down allocation decisions