Teoria de Portfólio de Markowitz para Cripto: Do Zero ao Herói
Construindo carteiras cripto ideais com Python - porque YOLO não é uma estratégia
Teoria de Portfólio de Markowitz: otimização matemática aplicada a ativos digitais para maximizar o retorno para um determinado nível de risco.
Introdução: por que sua carteira cripto precisa de matemática (não só de instinto)
E aí, crypto degens! 👋
Lembra daquela vez que você jogou todo o seu stack em DOGE porque o Elon tuitou algo? Ou quando você vendeu tudo em pânico durante o último crash? É, todos nós já passamos por isso. Hoje vamos falar sobre algo que pode salvar sua carteira (e sua sanidade): a Teoria de Portfólio de Markowitz.
Harry Markowitz literalmente ganhou um Prêmio Nobel por isso em 1990. A ideia básica? Você pode otimizar matematicamente sua carteira para obter o melhor retorno possível para qualquer nível de risco dado. É como ter um GPS para seus investimentos em vez de dirigir vendado.
O conceito central: risco vs. retorno (a dança eterna)
Antes de mergulharmos no código, vamos entender com o que estamos lidando:
- Retorno esperado: quanto dinheiro você espera ganhar
- Risco (volatilidade): o quanto o valor da sua carteira oscila
- Correlação: o quanto ativos diferentes se movem de forma semelhante entre si
A mágica acontece quando você combina ativos que não se movem em sincronia perfeita. Quando o Bitcoin despenca, talvez alguns tokens DeFi se sustentem melhor. Isso é a diversificação trabalhando a seu favor.
Risco vs. Retorno: equilibrando ativos voláteis de alto rendimento com uma base estável para alcançar o retorno médio geométrico ideal.
Configurando nosso ambiente Python
Primeiro as coisas mais importantes - vamos preparar nossas ferramentas:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.optimize import minimize
import yfinance as yf
import warnings
warnings.filterwarnings('ignore')
plt.style.use('dark_background')
sns.set_palette("husl")
Nível 1: Primeiros passos - Matemática básica de portfólio
Vamos começar com o básico. Vamos calcular o retorno e o risco de uma carteira simples com 2 ativos.
def get_crypto_data(symbols, period="1y"):
"""
Fetch crypto data from Yahoo Finance
symbols: list of crypto symbols (e.g., ['BTC-USD', 'ETH-USD'])
period: time period for data
"""
data = yf.download(symbols, period=period)['Adj Close']
return data
crypto_symbols = ['BTC-USD', 'ETH-USD']
prices = get_crypto_data(crypto_symbols)
returns = prices.pct_change().dropna()
print("Daily Returns Preview:")
print(returns.head())
Agora vamos calcular algumas métricas básicas de portfólio:
def portfolio_performance(weights, returns):
"""
Calculate portfolio return and volatility
weights: array of portfolio weights
returns: dataframe of asset returns
"""
portfolio_return = np.sum(returns.mean() * weights) * 252
portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))
return portfolio_return, portfolio_vol
weights_5050 = np.array([0.5, 0.5])
ret_5050, vol_5050 = portfolio_performance(weights_5050, returns)
print(f"50/50 Portfolio:")
print(f"Expected Annual Return: {ret_5050:.2%}")
print(f"Annual Volatility: {vol_5050:.2%}")
print(f"Sharpe Ratio: {ret_5050/vol_5050:.3f}")
Nível 2: Ficando sério - A fronteira eficiente
Agora vamos com tudo! A fronteira eficiente nos mostra todas as carteiras ótimas possíveis. Cada ponto representa o melhor retorno possível para um determinado nível de risco.
def generate_random_portfolios(returns, num_portfolios=10000):
"""
Generate random portfolio combinations
"""
num_assets = len(returns.columns)
results = np.zeros((4, num_portfolios))
for i in range(num_portfolios):
weights = np.random.random(num_assets)
weights /= np.sum(weights) # Normalize to sum to 1
portfolio_return, portfolio_vol = portfolio_performance(weights, returns)
sharpe_ratio = portfolio_return / portfolio_vol
results[0,i] = portfolio_return
results[1,i] = portfolio_vol
results[2,i] = sharpe_ratio
results[3,i:] = weights
return results
results = generate_random_portfolios(returns)
portfolio_results = pd.DataFrame({
'Returns': results[0],
'Volatility': results[1],
'Sharpe_Ratio': results[2]
})
plt.figure(figsize=(12, 8))
scatter = plt.scatter(portfolio_results['Volatility'],
portfolio_results['Returns'],
c=portfolio_results['Sharpe_Ratio'],
cmap='viridis', alpha=0.6)
plt.colorbar(scatter, label='Sharpe Ratio')
plt.xlabel('Volatility (Risk)')
plt.ylabel('Expected Return')
plt.title('Efficient Frontier - Random Portfolios')
plt.show()
A fronteira eficiente: a curva que representa o máximo retorno esperado possível para um determinado nível de risco.
Nível 3: Mestre da otimização - Encontrando a carteira perfeita
A amostragem aleatória é divertida, mas queremos a solução matematicamente ótima. Hora de trazer a artilharia pesada - otimização com scipy!
def negative_sharpe_ratio(weights, returns, risk_free_rate=0.02):
"""
Calculate negative Sharpe ratio (we minimize this)
"""
portfolio_return, portfolio_vol = portfolio_performance(weights, returns)
sharpe = (portfolio_return - risk_free_rate) / portfolio_vol
return -sharpe
def minimize_volatility(weights, returns):
"""
Calculate portfolio volatility (we minimize this)
"""
_, portfolio_vol = portfolio_performance(weights, returns)
return portfolio_vol
def portfolio_return_objective(weights, returns):
"""
Calculate portfolio return (we maximize this)
"""
portfolio_return, _ = portfolio_performance(weights, returns)
return -portfolio_return # Negative because we minimize
def optimize_portfolio(returns, objective='sharpe', target_return=None):
"""
Optimize portfolio based on different objectives
"""
num_assets = len(returns.columns)
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1}) # Weights sum to 1
bounds = tuple((0, 1) for _ in range(num_assets)) # No short selling
initial_guess = num_assets * [1. / num_assets]
if objective == 'sharpe':
result = minimize(negative_sharpe_ratio, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
elif objective == 'min_vol':
result = minimize(minimize_volatility, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
elif objective == 'target_return':
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1},
{'type': 'eq', 'fun': lambda x: portfolio_performance(x, returns)[0] - target_return})
result = minimize(minimize_volatility, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
return result
max_sharpe = optimize_portfolio(returns, 'sharpe')
min_vol = optimize_portfolio(returns, 'min_vol')
print("🎯 Maximum Sharpe Ratio Portfolio:")
for i, symbol in enumerate(crypto_symbols):
print(f"{symbol}: {max_sharpe.x[i]:.3f}")
ret_sharpe, vol_sharpe = portfolio_performance(max_sharpe.x, returns)
print(f"Return: {ret_sharpe:.2%}, Volatility: {vol_sharpe:.2%}")
print(f"Sharpe Ratio: {ret_sharpe/vol_sharpe:.3f}\n")
print("🛡️ Minimum Volatility Portfolio:")
for i, symbol in enumerate(crypto_symbols):
print(f"{symbol}: {min_vol.x[i]:.3f}")
ret_minvol, vol_minvol = portfolio_performance(min_vol.x, returns)
print(f"Return: {ret_minvol:.2%}, Volatility: {vol_minvol:.2%}")
Nível 4: Loucura multiativos - Uma carteira cripto de verdade
Vamos escalar isso para uma carteira cripto de verdade com múltiplos ativos:
crypto_portfolio = ['BTC-USD', 'ETH-USD', 'BNB-USD', 'ADA-USD', 'SOL-USD', 'DOT-USD']
prices_multi = get_crypto_data(crypto_portfolio, period="2y")
returns_multi = prices_multi.pct_change().dropna()
correlation_matrix = returns_multi.corr()
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='RdYlBu_r', center=0)
plt.title('Crypto Asset Correlation Matrix')
plt.show()
def efficient_frontier(returns, num_portfolios=50):
"""
Calculate the efficient frontier
"""
ret_range = np.linspace(returns.mean().min()*252, returns.mean().max()*252, num_portfolios)
efficient_portfolios = []
for target_ret in ret_range:
try:
result = optimize_portfolio(returns, 'target_return', target_ret)
if result.success:
ret, vol = portfolio_performance(result.x, returns)
efficient_portfolios.append([ret, vol, result.x])
except:
continue
return np.array(efficient_portfolios)
efficient_port = efficient_frontier(returns_multi)
plt.figure(figsize=(14, 10))
random_results = generate_random_portfolios(returns_multi, 5000)
plt.scatter(random_results[1], random_results[0],
c=random_results[2], cmap='viridis', alpha=0.3, s=10)
if len(efficient_port) > 0:
plt.plot(efficient_port[:,1], efficient_port[:,0], 'r-', linewidth=3, label='Efficient Frontier')
max_sharpe_multi = optimize_portfolio(returns_multi, 'sharpe')
min_vol_multi = optimize_portfolio(returns_multi, 'min_vol')
ret_sharpe_multi, vol_sharpe_multi = portfolio_performance(max_sharpe_multi.x, returns_multi)
ret_minvol_multi, vol_minvol_multi = portfolio_performance(min_vol_multi.x, returns_multi)
plt.scatter(vol_sharpe_multi, ret_sharpe_multi, marker='*', color='gold', s=500, label='Max Sharpe')
plt.scatter(vol_minvol_multi, ret_minvol_multi, marker='*', color='red', s=500, label='Min Volatility')
plt.colorbar(label='Sharpe Ratio')
plt.xlabel('Volatility (Risk)')
plt.ylabel('Expected Return')
plt.title('Multi-Asset Crypto Portfolio Optimization')
plt.legend()
plt.show()
print("🚀 Optimal Multi-Asset Allocations:")
print("\nMaximum Sharpe Ratio Portfolio:")
sharpe_weights = pd.Series(max_sharpe_multi.x, index=crypto_portfolio).sort_values(ascending=False)
for asset, weight in sharpe_weights.items():
if weight > 0.01: # Only show significant allocations
print(f"{asset}: {weight:.1%}")
print(f"\nPortfolio Metrics:")
print(f"Expected Return: {ret_sharpe_multi:.1%}")
print(f"Volatility: {vol_sharpe_multi:.1%}")
print(f"Sharpe Ratio: {ret_sharpe_multi/vol_sharpe_multi:.2f}")
Diversificação multiativos: construindo uma carteira robusta ao combinar ativos cripto não correlacionados em uma estrutura geométrica estável.
Nível 5: Técnicas avançadas - Black-Litterman e paridade de risco
Para os verdadeiros ninjas da otimização de carteiras, vamos implementar algumas técnicas avançadas:
def risk_parity_portfolio(returns):
"""
Risk Parity Portfolio - each asset contributes equally to portfolio risk
"""
def risk_contribution(weights, cov_matrix):
portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
marginal_contrib = np.dot(cov_matrix, weights) / portfolio_vol
contrib = weights * marginal_contrib
return contrib
def risk_parity_objective(weights, cov_matrix):
contrib = risk_contribution(weights, cov_matrix)
target_contrib = np.ones(len(weights)) / len(weights)
return np.sum((contrib - target_contrib)**2)
num_assets = len(returns.columns)
cov_matrix = returns.cov() * 252
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
bounds = tuple((0.001, 1) for _ in range(num_assets))
initial_guess = num_assets * [1. / num_assets]
result = minimize(risk_parity_objective, initial_guess,
args=(cov_matrix,), method='SLSQP',
bounds=bounds, constraints=constraints)
return result
risk_parity_result = risk_parity_portfolio(returns_multi)
print("⚖️ Risk Parity Portfolio:")
rp_weights = pd.Series(risk_parity_result.x, index=crypto_portfolio).sort_values(ascending=False)
for asset, weight in rp_weights.items():
print(f"{asset}: {weight:.1%}")
ret_rp, vol_rp = portfolio_performance(risk_parity_result.x, returns_multi)
print(f"\nRisk Parity Metrics:")
print(f"Expected Return: {ret_rp:.1%}")
print(f"Volatility: {vol_rp:.1%}")
print(f"Sharpe Ratio: {ret_rp/vol_rp:.2f}")
def backtest_portfolio(weights, prices):
"""
Simple backtest of portfolio performance
"""
returns = prices.pct_change().dropna()
portfolio_returns = (returns * weights).sum(axis=1)
cumulative_returns = (1 + portfolio_returns).cumprod()
total_return = cumulative_returns.iloc[-1] - 1
annualized_return = (1 + total_return) ** (252 / len(portfolio_returns)) - 1
annualized_vol = portfolio_returns.std() * np.sqrt(252)
sharpe_ratio = annualized_return / annualized_vol
max_dd = (cumulative_returns / cumulative_returns.expanding().max() - 1).min()
return {
'total_return': total_return,
'annualized_return': annualized_return,
'annualized_volatility': annualized_vol,
'sharpe_ratio': sharpe_ratio,
'max_drawdown': max_dd,
'cumulative_returns': cumulative_returns
}
strategies = {
'Max Sharpe': max_sharpe_multi.x,
'Min Volatility': min_vol_multi.x,
'Risk Parity': risk_parity_result.x,
'Equal Weight': np.ones(len(crypto_portfolio)) / len(crypto_portfolio)
}
plt.figure(figsize=(14, 8))
for name, weights in strategies.items():
backtest_results = backtest_portfolio(weights, prices_multi)
plt.plot(backtest_results['cumulative_returns'], label=f"{name} (Sharpe: {backtest_results['sharpe_ratio']:.2f})")
plt.title('Portfolio Strategy Backtests')
plt.xlabel('Date')
plt.ylabel('Cumulative Returns')
plt.legend()
plt.yscale('log')
plt.grid(True, alpha=0.3)
plt.show()

*Backtesting algorítmico: simulando o desempenho histórico para validar modelos teóricos de otimização.*
performance_summary = pd.DataFrame()
for name, weights in strategies.items():
results = backtest_portfolio(weights, prices_multi)
performance_summary[name] = [
f"{results['annualized_return']:.1%}",
f"{results['annualized_volatility']:.1%}",
f"{results['sharpe_ratio']:.2f}",
f"{results['max_drawdown']:.1%}"
]
performance_summary.index = ['Annual Return', 'Annual Volatility', 'Sharpe Ratio', 'Max Drawdown']
print("\n📊 Strategy Performance Summary:")
print(performance_summary)
O teste de realidade: o que Markowitz não te conta
Antes de você apostar tudo na otimização matemática, aqui vão algumas verdades duras sobre cripto:
1. Desempenho passado ≠ resultados futuros O mercado cripto é jovem e caótico. Aquelas correlações que você calculou? Elas podem se inverter da noite para o dia quando as regulamentações mudarem ou quando o próximo grande hack acontecer.
2. Custos de transação importam Reequilibrar sua carteira custa dinheiro. No DeFi, as taxas de gás podem comer seu lucro. Leve isso em conta na sua estratégia.
3. Problemas de liquidez Nem todas as criptomoedas são igualmente líquidas. Aquela altcoin de pequena capitalização pode parecer ótima na sua otimização, mas tente vendê-la durante um crash.
4. Mudanças de regime Os mercados cripto têm diferentes "regimes" - mercados em alta, em baixa, laterais. O que funciona em um pode não funcionar em outro.
Dicas práticas de implementação
def practical_portfolio_rebalancing(target_weights, current_weights, threshold=0.05):
"""
Only rebalance when weights drift beyond threshold
"""
weight_diff = np.abs(target_weights - current_weights)
needs_rebalancing = np.any(weight_diff > threshold)
if needs_rebalancing:
print("🔄 Rebalancing needed!")
for i, (target, current) in enumerate(zip(target_weights, current_weights)):
if abs(target - current) > threshold:
print(f"Asset {i}: {current:.1%} → {target:.1%}")
else:
print("✅ Portfolio within tolerance, no rebalancing needed")
return needs_rebalancing
current_allocation = np.array([0.35, 0.25, 0.15, 0.10, 0.10, 0.05])
target_allocation = max_sharpe_multi.x
practical_portfolio_rebalancing(target_allocation, current_allocation)
Conclusão: seu kit de ferramentas de otimização de portfólio
Agora você tem um kit de ferramentas completo para otimização de carteiras cripto:
- Cálculos básicos de risco e retorno
- Visualização da fronteira eficiente
- Otimização matemática para diferentes objetivos
- Estratégias avançadas como a paridade de risco
- Framework de backtesting para validar suas estratégias
- Considerações práticas para implementação no mundo real
Principais conclusões
- Diversificação é um almoço grátis - o único almoço grátis nos investimentos
- Otimize de acordo com sua tolerância a risco - o Sharpe máximo nem sempre é o melhor para você
- Reequilibre sistematicamente, mas sem operar em excesso
- Mantenha a humildade - modelos são ferramentas, não bolas de cristal
- Comece simples e adicione complexidade à medida que aprende
Lembre-se: em cripto, mesmo os melhores modelos matemáticos não conseguem prever quando o Elon vai tuitar sobre Dogecoin ou quando a próxima exchange vai ser hackeada. Use a teoria de portfólio como sua base, mas sempre mantenha pólvora seca e nunca invista mais do que você pode se dar ao luxo de perder.
Agora vá e otimize com responsabilidade! 🚀
Leitura adicional
Repositório de código
Todo o código deste tutorial está disponível no GitHub: https://github.com/suenot/markowitz
Boa otimização! 📈
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.