Создание продвинутого инструмента анализа портфеля и рыночной разведки на OpenBB
'Практическое руководство по использованию OpenBB для построения портфеля, расчета технических и риск-метрик, анализа секторов и интеграции новостей для принятия инвестиционных решений.'
Подготовка окружения
Перед началом установите OpenBB и импортируйте стандартные Python-библиотеки для анализа данных и визуализации. Ниже код из руководства, который настраивает окружение и подавляет предупреждения:
!pip install openbb[all] --quiet
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime, timedelta
import openbb
from openbb import obb
pd.set_option('display.max_columns', None)
pd.set_option('display.width', 1000)
print(" Advanced OpenBB Financial Analysis Tutorial")
print("=" * 60)Построение и анализ тех-портфеля
В примере показывают, как собрать портфель из технологических акций, скачать годовую историю цен, посчитать доходности, нормализовать веса для успешно загруженных тикеров и подготовить данные для анализа:
print("\n 1. BUILDING AND ANALYZING A TECH PORTFOLIO")
print("-" * 50)
tech_stocks = ['AAPL', 'GOOGL', 'MSFT', 'TSLA', 'NVDA']
initial_weights = [0.25, 0.20, 0.25, 0.15, 0.15]
end_date = datetime.now().strftime('%Y-%m-%d')
start_date = (datetime.now() - timedelta(days=365)).strftime('%Y-%m-%d')
portfolio_data = {}
portfolio_returns = pd.DataFrame()
successful_stocks = []
print(f"Fetching data from {start_date} to {end_date}...")
for i, symbol in enumerate(tech_stocks):
try:
data = obb.equity.price.historical(symbol=symbol, start_date=start_date, end_date=end_date)
df = data.to_df()
if df.index.duplicated().any():
df = df[~df.index.duplicated(keep='first')]
portfolio_data[symbol] = df
returns = df['close'].pct_change().dropna()
portfolio_returns[symbol] = returns
successful_stocks.append(symbol)
print(f" {symbol}: {len(df)} days of data")
except Exception as e:
print(f" Error fetching {symbol}: {str(e)}")
if successful_stocks:
successful_indices = [tech_stocks.index(stock) for stock in successful_stocks]
portfolio_weights = [initial_weights[i] for i in successful_indices]
total_weight = sum(portfolio_weights)
portfolio_weights = [w/total_weight for w in portfolio_weights]
print(f"\n Portfolio composition (normalized weights):")
for stock, weight in zip(successful_stocks, portfolio_weights):
print(f" {stock}: {weight:.1%}")
else:
portfolio_weights = []Анализ эффективности портфеля
Когда доходности и веса готовы, вычисляются взвешенные доходности, годовая доходность и волатильность, коэффициент Шарпа, максимальная просадка и годовые показатели по каждой акции:
print("\n 2. PORTFOLIO PERFORMANCE ANALYSIS")
print("-" * 50)
if not portfolio_returns.empty and portfolio_weights:
weighted_returns = (portfolio_returns * portfolio_weights).sum(axis=1)
annual_return = weighted_returns.mean() * 252
annual_volatility = weighted_returns.std() * np.sqrt(252)
sharpe_ratio = annual_return / annual_volatility if annual_volatility > 0 else 0
max_drawdown = (weighted_returns.cumsum().expanding().max() - weighted_returns.cumsum()).max()
print(f"Portfolio Annual Return: {annual_return:.2%}")
print(f"Portfolio Volatility: {annual_volatility:.2%}")
print(f"Sharpe Ratio: {sharpe_ratio:.3f}")
print(f"Max Drawdown: {max_drawdown:.2%}")
print("\n Individual Stock Performance:")
for stock in successful_stocks:
stock_return = portfolio_returns[stock].mean() * 252
stock_vol = portfolio_returns[stock].std() * np.sqrt(252)
print(f"{stock}: Return {stock_return:.2%}, Volatility {stock_vol:.2%}")
else:
print(" No valid portfolio data available for analysis")Продвинутые технические индикаторы
В примере рассчитываются SMA, EMA, MACD (и сигнал), RSI и полосы Боллинджера для тикера NVDA, после чего получают текущие значения и простую MACD-метрику BUY/SELL:
print("\n 3. ADVANCED TECHNICAL ANALYSIS")
print("-" * 50)
symbol = 'NVDA'
try:
price_data = obb.equity.price.historical(symbol=symbol, start_date=start_date, end_date=end_date)
df = price_data.to_df()
df['SMA_20'] = df['close'].rolling(window=20).mean()
df['SMA_50'] = df['close'].rolling(window=50).mean()
df['EMA_12'] = df['close'].ewm(span=12).mean()
df['EMA_26'] = df['close'].ewm(span=26).mean()
df['MACD'] = df['EMA_12'] - df['EMA_26']
df['MACD_signal'] = df['MACD'].ewm(span=9).mean()
delta = df['close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
df['RSI'] = 100 - (100 / (1 + rs))
df['BB_middle'] = df['close'].rolling(window=20).mean()
bb_std = df['close'].rolling(window=20).std()
df['BB_upper'] = df['BB_middle'] + (bb_std * 2)
df['BB_lower'] = df['BB_middle'] - (bb_std * 2)
current_price = df['close'].iloc[-1]
current_rsi = df['RSI'].iloc[-1]
macd_signal = "BUY" if df['MACD'].iloc[-1] > df['MACD_signal'].iloc[-1] else "SELL"
price_vs_sma20 = "Above" if current_price > df['SMA_20'].iloc[-1] else "Below"
print(f"\n{symbol} Technical Analysis:")
print(f"Current Price: ${current_price:.2f}")
print(f"RSI (14): {current_rsi:.2f} ({'Overbought' if current_rsi > 70 else 'Oversold' if current_rsi < 30 else 'Neutral'})")
print(f"MACD Signal: {macd_signal}")
print(f"Price vs SMA(20): {price_vs_sma20}")
except Exception as e:
print(f"Error in technical analysis: {str(e)}")Анализ секторов и скрининг
Дальше демонстрируется, как сгруппировать акции по секторам, посчитать среднюю годовую доходность по каждому сектору и быстро оценить лидерство сектора:
print("\n 4. SECTOR ANALYSIS & STOCK SCREENING")
print("-" * 50)
sectors = {
'Technology': ['AAPL', 'GOOGL', 'MSFT'],
'Electric Vehicles': ['TSLA', 'RIVN', 'LCID'],
'Semiconductors': ['NVDA', 'AMD', 'INTC']
}
sector_performance = {}
for sector_name, stocks in sectors.items():
sector_returns = []
for stock in stocks:
try:
data = obb.equity.price.historical(symbol=stock, start_date=start_date, end_date=end_date)
df = data.to_df()
if df.index.duplicated().any():
df = df[~df.index.duplicated(keep='first')]
returns = df['close'].pct_change().dropna()
sector_returns.append(returns.mean() * 252)
except Exception as e:
print(f" Failed to fetch {stock}: {str(e)}")
continue
if sector_returns:
avg_return = np.mean(sector_returns)
sector_performance[sector_name] = avg_return
print(f"{sector_name}: {avg_return:.2%} average annual return")Анализ настроений рынка
В примере также запрашиваются последние новости по некоторым тикерам, чтобы добавить качественный слой сентимента к количественному анализу:
print("\n 5. MARKET SENTIMENT ANALYSIS")
print("-" * 50)
for symbol in successful_stocks[:2]:
try:
news = obb.news.company(symbol=symbol, limit=3)
news_df = news.to_df()
print(f"\n{symbol} Recent News Headlines:")
for idx, row in news_df.iterrows():
print(f"• {row.get('title', 'N/A')[:80]}...")
break
except Exception as e:
print(f"News not available for {symbol}: {str(e)}")Оценка риска
Рассчитывается матрица корреляций по доходностям, затем оценивается дисперсия портфеля и годовая волатильность на основе корреляций и стандартных отклонений активов:
print("\n 6. RISK ANALYSIS")
print("-" * 50)
if not portfolio_returns.empty and len(portfolio_returns.columns) > 1:
correlation_matrix = portfolio_returns.corr()
print("\nPortfolio Correlation Matrix:")
print(correlation_matrix.round(3))
portfolio_var = np.dot(portfolio_weights, np.dot(correlation_matrix *
(portfolio_returns.std().values.reshape(-1,1) *
portfolio_returns.std().values.reshape(1,-1)),
portfolio_weights))
portfolio_risk = np.sqrt(portfolio_var) * np.sqrt(252)
print(f"\nPortfolio Risk (Volatility): {portfolio_risk:.2%}")Визуализации и дашборд
Для наглядности создаются графики кумулятивной доходности, скользящей волатильности, распределения доходностей и тепловая карта корреляций:
print("\n 7. CREATING PERFORMANCE VISUALIZATIONS")
print("-" * 50)
if not portfolio_returns.empty:
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
fig.suptitle('Portfolio Analysis Dashboard', fontsize=16)
cumulative_returns = (1 + portfolio_returns).cumprod()
cumulative_returns.plot(ax=axes[0,0], title='Cumulative Returns', alpha=0.7)
axes[0,0].legend(bbox_to_anchor=(1.05, 1), loc='upper left')
rolling_vol = portfolio_returns.rolling(window=30).std() * np.sqrt(252)
rolling_vol.plot(ax=axes[0,1], title='30-Day Rolling Volatility', alpha=0.7)
axes[0,1].legend(bbox_to_anchor=(1.05, 1), loc='upper left')
weighted_returns.hist(bins=50, ax=axes[1,0], alpha=0.7)
axes[1,0].set_title('Portfolio Returns Distribution')
axes[1,0].axvline(weighted_returns.mean(), color='red', linestyle='--', label='Mean')
axes[1,0].legend()
if len(correlation_matrix) > 1:
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0, ax=axes[1,1])
axes[1,1].set_title('Correlation Matrix')
plt.tight_layout()
plt.show()Итоги и рекомендации
В конце выводятся лучшие и худшие тикеры, количество проанализированных акций и секций, сгенерированные индикаторы и метрики риска. Рекомендуются шаги вперед: бэктестинг, добавление фундаментальных метрик, автоматизация оповещений и исследование ESG/факторного скрининга:
print("\n 8. INVESTMENT SUMMARY & RECOMMENDATIONS")
print("-" * 50)
print("Portfolio Analysis Complete!")
print(f" Analyzed {len(successful_stocks)} stocks")
print(f" Calculated {len(sector_performance)} sector performances")
print(f" Generated technical indicators and risk metrics")
if not portfolio_returns.empty and len(successful_stocks) > 0:
best_performer = portfolio_returns.mean().idxmax()
worst_performer = portfolio_returns.mean().idxmin()
print(f" Best Performer: {best_performer}")
print(f" Worst Performer: {worst_performer}")
print("\n Key Insights:")
print("• Diversification across tech sectors reduces portfolio risk")
print("• Technical indicators help identify entry/exit points")
print("• Regular rebalancing maintains target allocations")
print("• Monitor correlations to avoid concentration risk")
print("\n Next Steps:")
print("• Backtest different allocation strategies")
print("• Add fundamental analysis metrics")
print("• Implement automated alerts for technical signals")
print("• Explore ESG and factor-based screening")
print("\n" + "="*60)
print("OpenBB Advanced Tutorial Complete! ")
print("Visit https://openbb.co for more features and documentation")Этот подход демонстрирует, как сочетание статистики производительности, технических сигналов и сентимента позволяет принимать более взвешенные инвестиционные решения и непрерывно улучшать стратегию.
Switch Language
Read this article in English