Skip to content

Instantly share code, notes, and snippets.

@architectureman
Last active December 18, 2024 07:52
Show Gist options
  • Select an option

  • Save architectureman/0de2b6245d54e5dbd4b27ff2032fc468 to your computer and use it in GitHub Desktop.

Select an option

Save architectureman/0de2b6245d54e5dbd4b27ff2032fc468 to your computer and use it in GitHub Desktop.
Fix bug ticker smaller than risk-free rate
CONFIG = {
'MAX_PORTFOLIO_POINT_GENERATE_ATTEMPTS': 100,
'DEFAULT_RISK_FREE_RATE': 0.02,
'DEFAULT_LOOKBACK_DAYS': 365,
'MAX_TICKERS': 20
}
#------------------------------------------------------------------------------------------------------------------------
def safely_fetch_stock_data(
fetch_func: Callable[[List[str], Optional[datetime], Optional[datetime], Optional[str]], pd.DataFrame]
) -> Callable[[List[str], Optional[datetime], Optional[datetime], Optional[str]], pd.DataFrame]:
"""
Higher-order function for safe stock data retrieval.
Wraps the original fetch function with validation and error handling.
"""
def wrapper(
tickers: List[str],
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
exchange: Optional[str] = None
) -> pd.DataFrame:
if len(tickers) > CONFIG['MAX_TICKERS']:
raise StockDataFetchError(
f"Too many tickers. Maximum allowed: {CONFIG['MAX_TICKERS']}"
)
try:
return fetch_func(tickers, start_date, end_date, exchange)
except Exception as e:
logger.error(f"Stock data fetch error: {e}")
raise StockDataFetchError(f"Failed to fetch stock data: {e}")
return wrapper
#------------------------------------------------------------------------------------------------------------------------
def optimize_portfolio(
df: pd.DataFrame,
initial_weights: Dict[str, float],
constraints: Optional[Dict[str, Tuple[float, float]]] = None,
risk_free_rate: float = CONFIG['DEFAULT_RISK_FREE_RATE'] # Default to 2% risk-free rate
) -> Dict:
"""
Perform robust portfolio optimization with enhanced error handling.
Args:
df: Price data DataFrame
initial_weights: Initial portfolio weights
constraints: Optional allocation constraints
risk_free_rate: Risk-free rate for Sharpe ratio calculation
Returns:
Dictionary with optimized weights and metrics
"""
tickers = df.columns.tolist()
try:
# Validate input data
if df.empty:
logger.warning("Empty price data DataFrame")
return {
'optimized_weights': initial_weights,
'metrics': compute_portfolio_metrics(df, initial_weights)
}
# Calculate returns with annualized method
# Subtract risk-free rate to adjust expected returns
mu = expected_returns.mean_historical_return(df, frequency=252)
mu_adjusted = mu - risk_free_rate
# Handle cases with zero or negative returns
if (mu_adjusted <= 0).all():
logger.warning("All assets have returns below risk-free rate")
return {
'optimized_weights': initial_weights,
'metrics': compute_portfolio_metrics(df, initial_weights)
}
# Use sample covariance with annual frequency
S = risk_models.sample_cov(df, frequency=252)
# Set default weight bounds
weight_bounds = [(0, 1) for _ in range(len(tickers))]
# Apply custom constraints if provided
if constraints:
for ticker, (min_val, max_val) in constraints.items():
if ticker in tickers:
idx = tickers.index(ticker)
weight_bounds[idx] = (min_val, max_val)
# Create Efficient Frontier with adjusted returns
ef = EfficientFrontier(
mu_adjusted, # Use adjusted returns
S,
weight_bounds=weight_bounds
)
try:
# Attempt to maximize Sharpe ratio (now with adjusted returns)
weights = ef.max_sharpe()
except ValueError:
# Fallback to equal weight portfolio if optimization fails
logger.warning("Sharpe ratio optimization failed. Using equal weights.")
equal_weights = dict(zip(tickers, [1/len(tickers)]*len(tickers)))
return {
'optimized_weights': equal_weights,
'metrics': compute_portfolio_metrics(df, equal_weights)
}
# Clean and validate weights
optimized_weights = ef.clean_weights()
# Compute portfolio metrics
optimized_metrics = compute_portfolio_metrics(df, optimized_weights)
# Optional: Add risk-free rate to metrics if needed
if 'risk_free_rate' not in optimized_metrics:
optimized_metrics['risk_free_rate'] = risk_free_rate
return {
'optimized_weights': optimized_weights,
'metrics': optimized_metrics
}
except Exception as e:
logger.error(f"Portfolio optimization error: {e}")
return {
'optimized_weights': initial_weights,
'metrics': compute_portfolio_metrics(df, initial_weights)
}
#------------------------------------------------------------------------------------------------------------------------
def generate_efficient_frontier(
df: pd.DataFrame,
num_portfolios: int = CONFIG['MAX_PORTFOLIO_POINT_GENERATE_ATTEMPTS'],
risk_free_rate: float = CONFIG['DEFAULT_RISK_FREE_RATE'],
constraints: Optional[Dict[str, Tuple[float, float]]] = None
) -> Dict[str, Union[List[Dict[str, float]], Dict[str, float]]]:
try:
# Expected returns and sample covariance
mu = expected_returns.mean_historical_return(df)
S = risk_models.sample_cov(df)
# Set default weight bounds based on constraints
tickers = df.columns.tolist()
weight_bounds = [(0, 1) for _ in range(len(tickers))]
# Apply custom constraints if provided
if constraints:
for ticker, (min_val, max_val) in constraints.items():
if ticker in tickers:
idx = tickers.index(ticker)
weight_bounds[idx] = (min_val, max_val)
# Store frontier points
frontier_points = []
# Variables to track optimal portfolios
gmv_weights = None
gmv_return, gmv_volatility = None, None
# Calculate global minimum variance portfolio with constraints
ef_min = EfficientFrontier(mu, S, weight_bounds=weight_bounds)
ef_min.min_volatility()
gmv_weights = ef_min.clean_weights()
gmv_portfolio_performance = ef_min.portfolio_performance()
gmv_return, gmv_volatility = gmv_portfolio_performance[0], gmv_portfolio_performance[1]
# Calculate maximum Sharpe ratio portfolio with constraints
ef_msr = EfficientFrontier(mu, S, weight_bounds=weight_bounds)
ef_msr.max_sharpe(risk_free_rate=risk_free_rate)
msr_weights = ef_msr.clean_weights()
msr_portfolio_performance = ef_msr.portfolio_performance()
msr_return = msr_portfolio_performance[0]
msr_volatility = msr_portfolio_performance[1]
msr_sharpe = msr_portfolio_performance[2]
# Calculate the maximum return from individual assets
max_return = mu.max()
# Generate target returns within feasible range, extending beyond MSR
target_returns = np.linspace(gmv_return, max_return, num_portfolios + 1)
for target_return in target_returns:
try:
# Initialize EfficientFrontier object with constraints
ef = EfficientFrontier(mu, S, weight_bounds=weight_bounds)
# Optimize portfolio for target return
ef.efficient_return(target_return)
weights = ef.clean_weights()
# Calculate portfolio performance
portfolio_performance = ef.portfolio_performance()
portfolio_return = portfolio_performance[0]
portfolio_volatility = portfolio_performance[1]
# Calculate Sharpe Ratio
sharpe_ratio = (portfolio_return - risk_free_rate) / portfolio_volatility if portfolio_volatility > 0 else 0
# Add point to frontier
frontier_points.append({
'volatility': round(portfolio_volatility * 100, 4),
'return': round(portfolio_return * 100, 4),
'sharpe_ratio': round(sharpe_ratio, 4),
'weights': {ticker: round(float(weight), 4)
for ticker, weight in weights.items()}
})
except Exception as e:
logger.warning(f"Skipping target return {target_return}: {str(e)}")
continue
# Sort points by volatility for smooth plotting
# frontier_points.sort(key=lambda x: x['volatility'])
# Prepare outputs
return {
"frontier_points": frontier_points,
"global_minimum_variance": {
"return": round(gmv_return * 100, 4),
"volatility": round(gmv_volatility * 100, 4),
"weights": {ticker: round(float(weight), 4)
for ticker, weight in gmv_weights.items()}
},
"maximum_sharpe_ratio": {
"return": round(msr_return * 100, 4),
"volatility": round(msr_volatility * 100, 4),
"sharpe_ratio": round(msr_sharpe, 4),
"weights": {ticker: round(float(weight), 4)
for ticker, weight in msr_weights.items()}
},
"minimum_volatility": {
"return": round(gmv_return * 100, 4),
"volatility": round(gmv_volatility * 100, 4),
"sharpe_ratio": (gmv_return - risk_free_rate) / gmv_volatility if gmv_volatility > 0 else 0,
"weights": {ticker: round(float(weight), 4)
for ticker, weight in gmv_weights.items()}
},
"capital_market_line": {
"slope": round((msr_return * 100 - risk_free_rate * 100) / (msr_volatility * 100), 4),
"risk_free_rate": risk_free_rate * 100, # Convert to percentage
"start_point": {
"x": 0, # volatility
"y": risk_free_rate * 100 # return (converted to percentage)
},
"end_point": {
"x": round(msr_volatility * 100, 4), # volatility of maximum sharpe ratio portfolio
"y": round(msr_return * 100, 4) # return of maximum sharpe ratio portfolio
}
}
}
except Exception as e:
logger.error(f"Efficient Frontier generation error: {e}")
return {
"status": "error",
"message": str(e)
}
#------------------------------------------------------------------------------------------------------------------------
def calculate_asset_correlations(df: pd.DataFrame) -> Dict[str, Dict[str, float]]:
"""
Calculates the correlation matrix between assets.
Args:
df: DataFrame containing asset price data.
Returns:
A dictionary representing the correlation matrix.
"""
correlations = df.corr().to_dict()
# Convert to serializable format
serializable_correlations = {
outer_key: {inner_key: round(float(inner_val), 4) for inner_key, inner_val in outer_dict.items()}
for outer_key, outer_dict in correlations.items()
}
return serializable_correlations
#------------------------------------------------------------------------------------------------------------------------
def portfolio_manager(
tickers: List[str],
allocations: Optional[Dict[str, float]] = None,
constraints: Optional[Dict[str, Tuple[float, float]]] = None,
data_fetcher: Callable[[List[str], Optional[datetime], Optional[datetime]], pd.DataFrame] = default_yfinance_data_fetcher,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
exchange: Optional[str] = None
) -> Dict:
"""
Comprehensive portfolio management with JSON-serializable return.
"""
try:
# Use function composition for input resolution
resolved_allocations = (
lambda allocs: generate_weights(tickers, constraints) if allocs is None else allocs
)(allocations)
# Fetch data and compute metrics
df = data_fetcher(tickers, start_date, end_date, exchange)
# Compute initial metrics
initial_metrics = compute_portfolio_metrics(df, resolved_allocations)
# Optimize portfolio
optimized_results = optimize_portfolio(df, allocations, constraints)
# Generate Efficient Frontier data
efficient_frontier_data = generate_efficient_frontier(df, constraints=constraints)
# Optimization
ef = EfficientFrontier(
expected_returns.mean_historical_return(df),
risk_models.sample_cov(df)
)
ef.max_sharpe()
optimized_weights = ef.clean_weights()
optimized_metrics = compute_portfolio_metrics(df, optimized_weights)
# Prepare detailed asset allocation comparison
asset_allocation_changes = []
efficient_frontier_assets = []
for ticker in tickers:
initial_pct = allocations.get(ticker, 0) * 100
optimized_pct = optimized_results['optimized_weights'].get(ticker, 0) * 100
change_pct = optimized_pct - initial_pct
# Categorize change magnitude
if abs(change_pct) <= 5:
change_category = "minor"
elif 5 < abs(change_pct) <= 15:
change_category = "moderate"
else:
change_category = "significant"
asset_allocation_changes.append({
"ticker": ticker,
"initial_allocation": round(initial_pct, 2),
"optimized_allocation": round(optimized_pct, 2),
"allocation_change": round(change_pct, 2),
"change_category": change_category
})
# Expected return (annualized monthly arithmetic mean return)
expected_return = expected_returns.mean_historical_return(df[[ticker]], frequency=12)[ticker] * 100
# Standard deviation
standard_deviation = risk_models.sample_cov(df[[ticker]], frequency=12)[ticker][ticker]**0.5 * 100
# Sharpe ratio (using U.S. 3-Month Treasury Bill Rate as risk-free rate)
sharpe_ratio = (expected_return - CONFIG['DEFAULT_RISK_FREE_RATE'] * 100) / standard_deviation if standard_deviation > 0 else 0
efficient_frontier_assets.append({
"ticker": ticker,
"expected_return": round(expected_return, 2),
"standard_deviation": round(standard_deviation, 2),
"sharpe_ratio": round(sharpe_ratio, 3), # Adjusted to 3 decimal places
"min_weight": 0.0,
"max_weight": 100.0 # Assuming no constraints by default
})
# Apply constraints to weight ranges if provided
if constraints:
for asset_data in efficient_frontier_assets:
if asset_data["ticker"] in constraints:
min_weight, max_weight = constraints[asset_data["ticker"]]
asset_data["min_weight"] = min_weight * 100
asset_data["max_weight"] = max_weight * 100
# Prepare optimization insights
is_improved = bool(optimized_results['metrics']['sharpe_ratio'] > initial_metrics['sharpe_ratio'])
volatility_change = float(optimized_results['metrics']['volatility'] - initial_metrics['volatility'])
# Calculate asset correlations
asset_correlations = calculate_asset_correlations(df)
# Modify result to include efficient frontier data
result = {
"status": "success",
"portfolio_analysis": {
"performance_metrics": {
"initial": {
"return": round(float(initial_metrics['return'] * 100), 2),
"volatility": round(float(initial_metrics['volatility'] * 100), 2),
"sharpe_ratio": round(float(initial_metrics['sharpe_ratio']), 4)
},
"optimized": {
"return": round(float(optimized_results['metrics']['return'] * 100), 2),
"volatility": round(float(optimized_results['metrics']['volatility'] * 100), 2),
"sharpe_ratio": round(float(optimized_results['metrics']['sharpe_ratio']), 4)
},
"changes": {
"return_change": round(float((optimized_results['metrics']['return'] - initial_metrics['return']) * 100), 2),
"volatility_change": round(float((optimized_results['metrics']['volatility'] - initial_metrics['volatility']) * 100), 2),
"sharpe_ratio_change": round(float(optimized_results['metrics']['sharpe_ratio'] - initial_metrics['sharpe_ratio']), 4)
},
"asset_correlations": asset_correlations,
"efficient_frontier_assets": efficient_frontier_assets
},
"asset_allocation": {
"initial_weights": {ticker: round(float(weight), 4) for ticker, weight in allocations.items()},
"optimized_weights": {ticker: round(float(weight), 4) for ticker, weight in optimized_results['optimized_weights'].items()},
"allocation_changes": asset_allocation_changes
},
"optimization_insights": {
"performance_improved": is_improved,
"volatility_direction": "reduced" if volatility_change < 0 else "increased" if volatility_change > 0 else "stable",
"volatility_change_magnitude": round(float(volatility_change * 100), 2),
"recommendation": "Consider adopting optimized allocation" if is_improved else "Current allocation appears optimal"
},
"efficient_frontier": efficient_frontier_data
}
}
# Final conversion to ensure full JSON serializability
return convert_to_serializable(result)
except (StockDataFetchError, PortfolioOptimizationError) as e:
logger.error(f"Portfolio management error: {e}")
return {
"status": "error",
"message": str(e)
}
#------------------------------------------------------------------------------------------------------------------------
{
"status": "success",
"portfolio_analysis": {
"performance_metrics": {
"initial": {
"return": 45.33,
"volatility": 19.89,
"sharpe_ratio": 2.1778
},
"optimized": {
"return": 46.64,
"volatility": 21.16,
"sharpe_ratio": 2.1102
},
"changes": {
"return_change": 1.32,
"volatility_change": 1.26,
"sharpe_ratio_change": -0.0676
},
"asset_correlations": {
"AAPL": {
"AAPL": 1.0,
"GOOGL": 0.8048,
"MSFT": 0.7902,
"AMZN": 0.7236
},
"GOOGL": {
"AAPL": 0.8048,
"GOOGL": 1.0,
"MSFT": 0.9381,
"AMZN": 0.9481
},
"MSFT": {
"AAPL": 0.7902,
"GOOGL": 0.9381,
"MSFT": 1.0,
"AMZN": 0.9756
},
"AMZN": {
"AAPL": 0.7236,
"GOOGL": 0.9481,
"MSFT": 0.9756,
"AMZN": 1.0
}
},
"efficient_frontier_assets": [
{
"ticker": "AAPL",
"expected_return": 1.78,
"standard_deviation": 4.77,
"sharpe_ratio": -0.047,
"min_weight": 10.0,
"max_weight": 40.0
},
{
"ticker": "GOOGL",
"expected_return": 1.83,
"standard_deviation": 6.44,
"sharpe_ratio": -0.026,
"min_weight": 10.0,
"max_weight": 30.0
},
{
"ticker": "MSFT",
"expected_return": 1.64,
"standard_deviation": 5.06,
"sharpe_ratio": -0.071,
"min_weight": 0.0,
"max_weight": 100.0
},
{
"ticker": "AMZN",
"expected_return": 2.1,
"standard_deviation": 6.76,
"sharpe_ratio": 0.015,
"min_weight": 0.0,
"max_weight": 100.0
}
]
},
"asset_allocation": {
"initial_weights": {
"AAPL": 0.4444,
"GOOGL": 0.2222,
"MSFT": 0.2222,
"AMZN": 0.1111
},
"optimized_weights": {
"AAPL": 0.25,
"GOOGL": 0.25,
"MSFT": 0.25,
"AMZN": 0.25
},
"allocation_changes": [
{
"ticker": "AAPL",
"initial_allocation": 44.44,
"optimized_allocation": 25.0,
"allocation_change": -19.44,
"change_category": "significant"
},
{
"ticker": "GOOGL",
"initial_allocation": 22.22,
"optimized_allocation": 25.0,
"allocation_change": 2.78,
"change_category": "minor"
},
{
"ticker": "MSFT",
"initial_allocation": 22.22,
"optimized_allocation": 25.0,
"allocation_change": 2.78,
"change_category": "minor"
},
{
"ticker": "AMZN",
"initial_allocation": 11.11,
"optimized_allocation": 25.0,
"allocation_change": 13.89,
"change_category": "moderate"
}
]
},
"optimization_insights": {
"performance_improved": false,
"volatility_direction": "increased",
"volatility_change_magnitude": 1.26,
"recommendation": "Current allocation appears optimal"
},
"efficient_frontier": {
"frontier_points": [
{
"volatility": 19.517,
"return": 43.5759,
"sharpe_ratio": 2.1302,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1042,
"MSFT": 0.4496,
"AMZN": 0.0462
}
},
{
"volatility": 19.518,
"return": 43.6874,
"sharpe_ratio": 2.1358,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1041,
"MSFT": 0.4417,
"AMZN": 0.0542
}
},
{
"volatility": 19.5211,
"return": 43.799,
"sharpe_ratio": 2.1412,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1041,
"MSFT": 0.4338,
"AMZN": 0.0622
}
},
{
"volatility": 19.5262,
"return": 43.9105,
"sharpe_ratio": 2.1464,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.104,
"MSFT": 0.4259,
"AMZN": 0.0702
}
},
{
"volatility": 19.5333,
"return": 44.022,
"sharpe_ratio": 2.1513,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1039,
"MSFT": 0.4179,
"AMZN": 0.0782
}
},
{
"volatility": 19.5424,
"return": 44.1336,
"sharpe_ratio": 2.156,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1038,
"MSFT": 0.41,
"AMZN": 0.0862
}
},
{
"volatility": 19.5536,
"return": 44.2451,
"sharpe_ratio": 2.1605,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1037,
"MSFT": 0.4021,
"AMZN": 0.0942
}
},
{
"volatility": 19.5667,
"return": 44.3566,
"sharpe_ratio": 2.1647,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1036,
"MSFT": 0.3942,
"AMZN": 0.1022
}
},
{
"volatility": 19.5819,
"return": 44.4682,
"sharpe_ratio": 2.1687,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1035,
"MSFT": 0.3863,
"AMZN": 0.1102
}
},
{
"volatility": 19.5991,
"return": 44.5797,
"sharpe_ratio": 2.1725,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1034,
"MSFT": 0.3784,
"AMZN": 0.1182
}
},
{
"volatility": 19.6183,
"return": 44.6913,
"sharpe_ratio": 2.1761,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1033,
"MSFT": 0.3704,
"AMZN": 0.1262
}
},
{
"volatility": 19.6396,
"return": 44.8028,
"sharpe_ratio": 2.1794,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1032,
"MSFT": 0.3625,
"AMZN": 0.1342
}
},
{
"volatility": 19.6628,
"return": 44.9143,
"sharpe_ratio": 2.1825,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1032,
"MSFT": 0.3546,
"AMZN": 0.1422
}
},
{
"volatility": 19.688,
"return": 45.0259,
"sharpe_ratio": 2.1854,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1031,
"MSFT": 0.3467,
"AMZN": 0.1502
}
},
{
"volatility": 19.7151,
"return": 45.1374,
"sharpe_ratio": 2.188,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.103,
"MSFT": 0.3388,
"AMZN": 0.1582
}
},
{
"volatility": 19.7443,
"return": 45.2489,
"sharpe_ratio": 2.1905,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1029,
"MSFT": 0.3309,
"AMZN": 0.1663
}
},
{
"volatility": 19.7754,
"return": 45.3605,
"sharpe_ratio": 2.1926,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1028,
"MSFT": 0.3229,
"AMZN": 0.1743
}
},
{
"volatility": 19.8085,
"return": 45.472,
"sharpe_ratio": 2.1946,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1027,
"MSFT": 0.315,
"AMZN": 0.1823
}
},
{
"volatility": 19.8435,
"return": 45.5835,
"sharpe_ratio": 2.1964,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1026,
"MSFT": 0.3071,
"AMZN": 0.1903
}
},
{
"volatility": 19.8804,
"return": 45.6951,
"sharpe_ratio": 2.1979,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1025,
"MSFT": 0.2992,
"AMZN": 0.1983
}
},
{
"volatility": 19.9193,
"return": 45.8066,
"sharpe_ratio": 2.1992,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1024,
"MSFT": 0.2913,
"AMZN": 0.2063
}
},
{
"volatility": 19.96,
"return": 45.9181,
"sharpe_ratio": 2.2003,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1024,
"MSFT": 0.2833,
"AMZN": 0.2143
}
},
{
"volatility": 20.0027,
"return": 46.0297,
"sharpe_ratio": 2.2012,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1023,
"MSFT": 0.2754,
"AMZN": 0.2223
}
},
{
"volatility": 20.0473,
"return": 46.1412,
"sharpe_ratio": 2.2019,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1022,
"MSFT": 0.2675,
"AMZN": 0.2303
}
},
{
"volatility": 20.0937,
"return": 46.2527,
"sharpe_ratio": 2.2023,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1021,
"MSFT": 0.2596,
"AMZN": 0.2383
}
},
{
"volatility": 20.142,
"return": 46.3643,
"sharpe_ratio": 2.2026,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.102,
"MSFT": 0.2517,
"AMZN": 0.2463
}
},
{
"volatility": 20.1922,
"return": 46.4758,
"sharpe_ratio": 2.2026,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1019,
"MSFT": 0.2438,
"AMZN": 0.2543
}
},
{
"volatility": 20.2441,
"return": 46.5873,
"sharpe_ratio": 2.2025,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1018,
"MSFT": 0.2359,
"AMZN": 0.2623
}
},
{
"volatility": 20.2979,
"return": 46.6989,
"sharpe_ratio": 2.2021,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1017,
"MSFT": 0.2279,
"AMZN": 0.2703
}
},
{
"volatility": 20.3535,
"return": 46.8104,
"sharpe_ratio": 2.2016,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1017,
"MSFT": 0.22,
"AMZN": 0.2783
}
},
{
"volatility": 20.4109,
"return": 46.9219,
"sharpe_ratio": 2.2009,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1016,
"MSFT": 0.2121,
"AMZN": 0.2863
}
},
{
"volatility": 20.4701,
"return": 47.0335,
"sharpe_ratio": 2.2,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1015,
"MSFT": 0.2042,
"AMZN": 0.2943
}
},
{
"volatility": 20.5311,
"return": 47.145,
"sharpe_ratio": 2.1989,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1014,
"MSFT": 0.1963,
"AMZN": 0.3023
}
},
{
"volatility": 20.5937,
"return": 47.2565,
"sharpe_ratio": 2.1976,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1013,
"MSFT": 0.1883,
"AMZN": 0.3104
}
},
{
"volatility": 20.6582,
"return": 47.3681,
"sharpe_ratio": 2.1961,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1012,
"MSFT": 0.1804,
"AMZN": 0.3184
}
},
{
"volatility": 20.7243,
"return": 47.4796,
"sharpe_ratio": 2.1945,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1011,
"MSFT": 0.1725,
"AMZN": 0.3264
}
},
{
"volatility": 20.7921,
"return": 47.5911,
"sharpe_ratio": 2.1927,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.101,
"MSFT": 0.1646,
"AMZN": 0.3344
}
},
{
"volatility": 20.8616,
"return": 47.7027,
"sharpe_ratio": 2.1908,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1009,
"MSFT": 0.1567,
"AMZN": 0.3424
}
},
{
"volatility": 20.9328,
"return": 47.8142,
"sharpe_ratio": 2.1886,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1009,
"MSFT": 0.1488,
"AMZN": 0.3504
}
},
{
"volatility": 21.0056,
"return": 47.9258,
"sharpe_ratio": 2.1864,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1008,
"MSFT": 0.1409,
"AMZN": 0.3584
}
},
{
"volatility": 21.08,
"return": 48.0373,
"sharpe_ratio": 2.1839,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1007,
"MSFT": 0.1329,
"AMZN": 0.3664
}
},
{
"volatility": 21.1561,
"return": 48.1488,
"sharpe_ratio": 2.1813,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1006,
"MSFT": 0.125,
"AMZN": 0.3744
}
},
{
"volatility": 21.2338,
"return": 48.2604,
"sharpe_ratio": 2.1786,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1005,
"MSFT": 0.1171,
"AMZN": 0.3824
}
},
{
"volatility": 21.313,
"return": 48.3719,
"sharpe_ratio": 2.1758,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1004,
"MSFT": 0.1092,
"AMZN": 0.3904
}
},
{
"volatility": 21.3938,
"return": 48.4834,
"sharpe_ratio": 2.1728,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1003,
"MSFT": 0.1013,
"AMZN": 0.3984
}
},
{
"volatility": 21.4761,
"return": 48.595,
"sharpe_ratio": 2.1696,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1002,
"MSFT": 0.0933,
"AMZN": 0.4064
}
},
{
"volatility": 21.56,
"return": 48.7065,
"sharpe_ratio": 2.1664,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1002,
"MSFT": 0.0854,
"AMZN": 0.4144
}
},
{
"volatility": 21.6453,
"return": 48.818,
"sharpe_ratio": 2.163,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1001,
"MSFT": 0.0775,
"AMZN": 0.4224
}
},
{
"volatility": 21.7322,
"return": 48.9296,
"sharpe_ratio": 2.1594,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1,
"MSFT": 0.0696,
"AMZN": 0.4304
}
},
{
"volatility": 21.8205,
"return": 49.0411,
"sharpe_ratio": 2.1558,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1,
"MSFT": 0.0616,
"AMZN": 0.4384
}
},
{
"volatility": 21.9103,
"return": 49.1526,
"sharpe_ratio": 2.1521,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1,
"MSFT": 0.0536,
"AMZN": 0.4464
}
},
{
"volatility": 22.0015,
"return": 49.2642,
"sharpe_ratio": 2.1482,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1,
"MSFT": 0.0457,
"AMZN": 0.4543
}
},
{
"volatility": 22.0942,
"return": 49.3757,
"sharpe_ratio": 2.1443,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1,
"MSFT": 0.0377,
"AMZN": 0.4623
}
},
{
"volatility": 22.1882,
"return": 49.4872,
"sharpe_ratio": 2.1402,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1,
"MSFT": 0.0297,
"AMZN": 0.4703
}
},
{
"volatility": 22.2836,
"return": 49.5988,
"sharpe_ratio": 2.136,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1,
"MSFT": 0.0218,
"AMZN": 0.4782
}
},
{
"volatility": 22.3804,
"return": 49.7103,
"sharpe_ratio": 2.1318,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1,
"MSFT": 0.0138,
"AMZN": 0.4862
}
},
{
"volatility": 22.4785,
"return": 49.8218,
"sharpe_ratio": 2.1274,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1,
"MSFT": 0.0058,
"AMZN": 0.4942
}
},
{
"volatility": 22.5894,
"return": 49.9334,
"sharpe_ratio": 2.1219,
"weights": {
"AAPL": 0.397,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.503
}
},
{
"volatility": 22.734,
"return": 50.0449,
"sharpe_ratio": 2.1133,
"weights": {
"AAPL": 0.3858,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.5142
}
},
{
"volatility": 22.8827,
"return": 50.1564,
"sharpe_ratio": 2.1045,
"weights": {
"AAPL": 0.3746,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.5254
}
},
{
"volatility": 23.0354,
"return": 50.268,
"sharpe_ratio": 2.0954,
"weights": {
"AAPL": 0.3634,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.5366
}
},
{
"volatility": 23.1919,
"return": 50.3795,
"sharpe_ratio": 2.0861,
"weights": {
"AAPL": 0.3522,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.5478
}
},
{
"volatility": 23.3522,
"return": 50.491,
"sharpe_ratio": 2.0765,
"weights": {
"AAPL": 0.341,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.559
}
},
{
"volatility": 23.5163,
"return": 50.6026,
"sharpe_ratio": 2.0668,
"weights": {
"AAPL": 0.3299,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.5701
}
},
{
"volatility": 23.684,
"return": 50.7141,
"sharpe_ratio": 2.0568,
"weights": {
"AAPL": 0.3187,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.5813
}
},
{
"volatility": 23.8553,
"return": 50.8256,
"sharpe_ratio": 2.0467,
"weights": {
"AAPL": 0.3075,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.5925
}
},
{
"volatility": 24.0301,
"return": 50.9372,
"sharpe_ratio": 2.0365,
"weights": {
"AAPL": 0.2963,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.6037
}
},
{
"volatility": 24.2082,
"return": 51.0487,
"sharpe_ratio": 2.0261,
"weights": {
"AAPL": 0.2851,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.6149
}
},
{
"volatility": 24.3898,
"return": 51.1603,
"sharpe_ratio": 2.0156,
"weights": {
"AAPL": 0.2739,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.6261
}
},
{
"volatility": 24.5746,
"return": 51.2718,
"sharpe_ratio": 2.005,
"weights": {
"AAPL": 0.2627,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.6373
}
},
{
"volatility": 24.7626,
"return": 51.3833,
"sharpe_ratio": 1.9943,
"weights": {
"AAPL": 0.2515,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.6484
}
},
{
"volatility": 24.9537,
"return": 51.4949,
"sharpe_ratio": 1.9835,
"weights": {
"AAPL": 0.2404,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.6596
}
},
{
"volatility": 25.1479,
"return": 51.6064,
"sharpe_ratio": 1.9726,
"weights": {
"AAPL": 0.2292,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.6708
}
},
{
"volatility": 25.345,
"return": 51.7179,
"sharpe_ratio": 1.9616,
"weights": {
"AAPL": 0.218,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.682
}
},
{
"volatility": 25.5451,
"return": 51.8295,
"sharpe_ratio": 1.9506,
"weights": {
"AAPL": 0.2068,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.6932
}
},
{
"volatility": 25.748,
"return": 51.941,
"sharpe_ratio": 1.9396,
"weights": {
"AAPL": 0.1956,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.7044
}
},
{
"volatility": 25.9537,
"return": 52.0525,
"sharpe_ratio": 1.9285,
"weights": {
"AAPL": 0.1844,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.7156
}
},
{
"volatility": 26.1621,
"return": 52.1641,
"sharpe_ratio": 1.9174,
"weights": {
"AAPL": 0.1732,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.7268
}
},
{
"volatility": 26.3732,
"return": 52.2756,
"sharpe_ratio": 1.9063,
"weights": {
"AAPL": 0.162,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.7379
}
},
{
"volatility": 26.5868,
"return": 52.3871,
"sharpe_ratio": 1.8952,
"weights": {
"AAPL": 0.1509,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.7491
}
},
{
"volatility": 26.803,
"return": 52.4987,
"sharpe_ratio": 1.8841,
"weights": {
"AAPL": 0.1397,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.7603
}
},
{
"volatility": 27.0216,
"return": 52.6102,
"sharpe_ratio": 1.873,
"weights": {
"AAPL": 0.1285,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.7715
}
},
{
"volatility": 27.2426,
"return": 52.7217,
"sharpe_ratio": 1.8619,
"weights": {
"AAPL": 0.1173,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.7827
}
},
{
"volatility": 27.466,
"return": 52.8333,
"sharpe_ratio": 1.8508,
"weights": {
"AAPL": 0.1061,
"GOOGL": 0.1,
"MSFT": 0.0,
"AMZN": 0.7939
}
}
],
"global_minimum_variance": {
"return": 43.5759,
"volatility": 19.517,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1042,
"MSFT": 0.4496,
"AMZN": 0.0462
}
},
"maximum_sharpe_ratio": {
"return": 46.4497,
"volatility": 20.1803,
"sharpe_ratio": 2.2026,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1019,
"MSFT": 0.2456,
"AMZN": 0.2524
}
},
"minimum_volatility": {
"return": 43.5759,
"volatility": 19.517,
"sharpe_ratio": 2.1302396723830648,
"weights": {
"AAPL": 0.4,
"GOOGL": 0.1042,
"MSFT": 0.4496,
"AMZN": 0.0462
}
},
"capital_market_line": {
"slope": 2.2026,
"risk_free_rate": 2.0,
"start_point": {
"x": 0,
"y": 2.0
},
"end_point": {
"x": 20.1803,
"y": 46.4497
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment