Spaces:
Running
Running
import pandas as pd | |
from openbb import obb | |
import riskfolio as rp | |
import os | |
import regex as re | |
from dotenv import load_dotenv | |
import matplotlib.pyplot as plt | |
import pandas as pd | |
import numpy as np | |
import plotly.graph_objs as go | |
import plotly.tools as tls | |
from plotly.subplots import make_subplots | |
import plotly.figure_factory as ff | |
import streamlit as st | |
import platform | |
import datetime | |
import asyncio | |
import nest_asyncio | |
import json | |
import requests | |
def open_nested_event_loop(): | |
# Check if there is an existing event loop, if not, create a new one | |
nest_asyncio.apply() | |
try: | |
loop = asyncio.get_event_loop() | |
st.write("event loop exists") | |
except RuntimeError as ex: | |
if "There is no current event loop in thread" in str(ex): | |
loop = asyncio.new_event_loop() | |
asyncio.set_event_loop(loop) | |
st.write("event loop created") | |
return | |
open_nested_event_loop() | |
def get_positions(account): | |
positions = ib.portfolio(account) | |
tickers = [] | |
for position in positions: | |
tickers.append([position.contract.symbol, position.position, position.marketPrice]) | |
return tickers | |
def get_finnhub_data(example: str) -> json: | |
""" | |
Pass in the "example" string from the API documentation. It changes for every endpoint. | |
:param1 example: '/company-news?symbol=AAPL&from=2023-08-15&to=2023-08-20' | |
""" | |
base_url = 'https://finnhub.io/api/v1//' | |
token = f"&token={os.environ['finnhub_token']}" | |
request = requests.get(f"{base_url}{example}{token}") | |
return request.json() | |
def get_list_of_tickers(): | |
comp_info = get_finnhub_data('/stock/symbol?exchange=US') | |
list_of_tickers = [] | |
for i in range(len(comp_info)-1): | |
for key in comp_info[i].keys(): | |
if key == 'symbol': | |
list_of_tickers.append(comp_info[i]['symbol']) | |
return list_of_tickers | |
load_dotenv() | |
obb.account.login(pat=os.environ['open_bb_pat']) | |
# -------------------------------- (Tickers) -------------------------------- # | |
if re.search('AuthenticAMD', platform.processor()) and 'tickers' not in st.session_state: # use live ibkr portfolio if ran from local machine | |
try: | |
# Initialize IB connection here | |
from ib_insync import * | |
with IB().connect('127.0.0.1', 7497, clientId=0, timeout=10) as ib: | |
# Assuming you're connecting to a local TWS instance with default settings | |
paper_acct = ib.managedAccounts()[0] | |
position_data = get_positions(paper_acct) | |
for pos in position_data: | |
tickers = [pos[0] for pos in position_data] | |
st.session_state.tickers = tickers | |
except Exception as e: | |
st.write('IB Workstation is not running. Please start the IB Workstation and try again.') | |
st.write('Error: ', e) | |
st.stop() | |
if 'tickers' not in st.session_state: | |
tickers = [ | |
"SPY", "QQQ","BND","GLD","VTI" | |
] | |
st.session_state['tickers'] = tickers | |
if not platform.processor(): # take inputs from the user for hosted application | |
tickers = st.session_state['tickers'] | |
list_of_tickers = get_list_of_tickers() | |
with st.form(key="selecting columns"): | |
tickers = st.multiselect(label='Enter Tickers Here. ', options=list_of_tickers, placeholder='...', default=st.session_state['tickers']) | |
submit_button = st.form_submit_button(label='Optimize Portfolio') | |
st.session_state['tickers'] = tickers | |
if submit_button: | |
# define range as today - 365 days to today | |
start_date = (datetime.datetime.now() - datetime.timedelta(days=365)).strftime('%Y-%m-%d') | |
end_date = datetime.datetime.now().strftime('%Y-%m-%d') | |
data = ( | |
obb | |
.equity | |
.price | |
.historical(tickers, start_date=start_date, end_date=end_date, provider="yfinance") | |
.to_df() | |
.pivot(columns="symbol", values="close") | |
) | |
returns = data.pct_change().dropna() | |
# -------------------------- (Efficient Frontier Calculation) -------------------------------- # | |
st.title('Efficient Frontier Portfolio') | |
st.write("The efficient frontier is a set of optimal portfolios that offer the highest expected return for a defined level of risk or the lowest risk for a given level of expected return. Portfolios that lie below the efficient frontier are sub-optimal because they do not provide enough return for the level of risk. Portfolios that cluster to the right of the efficient frontier are also sub-optimal because they have a higher level of risk for the defined rate of return.") | |
port = rp.Portfolio(returns=returns) | |
# Step 2: Set portfolio optimization model | |
port.assets_stats(model='hist') # Using historical data for estimation | |
# Step 3: Configure the optimization model and calculate the efficient frontier | |
ef = port.efficient_frontier(model='Classic', rm='MV', points=50, rf=0.0406, hist=True) | |
w1 = port.optimization(model='Classic', rm='MV', obj='Sharpe', rf=0.0, hist=True) | |
mu = port.mu # Expected returns | |
cov = port.cov # Covariance matrix | |
# ---------------------------- (Portfolio Statistics) -------------------------------- # | |
st.write('**Portfolio Statistics Optimized for Max Sharpe Ratio:**') | |
spy_prices = obb.equity.price.historical(symbol = "spy", provider="yfinance", start_date=start_date, end_date=end_date).to_df() | |
# Calculate daily returns | |
# Ensure you're using the adjusted close prices for accurate return calculation | |
benchmark_returns = spy_prices['close'].pct_change().dropna() | |
port.rf = 0.000406 # Risk-free rate | |
portfolio_return = np.dot(w1, mu) | |
# market return | |
spy_daily_return = benchmark_returns | |
spy_expected_return = spy_daily_return.mean() | |
# portfolio's beta | |
covariance = returns.apply(lambda x: x.cov(spy_daily_return)) | |
spy_variance = spy_daily_return.var() | |
beta_values = covariance / spy_variance | |
portfolio_beta = np.dot(w1['weights'], beta_values) | |
st.write('Portfolio Beta: ', np.round(portfolio_beta,3)) | |
# jensens alpha | |
expected_return = port.rf + portfolio_beta * (spy_daily_return - port.rf) | |
st.write('Jensen\'s Alpha: ', np.round(expected_return.iloc[-1],3)) | |
# treynor ratio | |
treynor_ratio = (expected_return - port.rf) / portfolio_beta | |
st.write('Treynor Ratio: ', np.round(treynor_ratio.iloc[-1],3)) | |
# Portfolio volatility | |
portfolio_stddev = np.sqrt(np.dot(pd.Series(w1['weights']).T, np.dot(covariance, w1['weights']))) | |
st.write('Portfolio Volatility: ', np.round(np.mean(portfolio_stddev), 3)) | |
# Sharpe Ratio, adjusted for the risk-free rate | |
sharpe_ratio = rp.RiskFunctions.Sharpe( | |
mu=mu, | |
cov=cov, | |
returns=returns, | |
rf=port.rf, | |
w=w1, | |
) | |
st.write('Sharpe Ratio: ', np.round(sharpe_ratio, 3)) | |
# -------------------------- (Plotting) -------------------------------- # | |
# Step 4: Plot the efficient frontier | |
fig_ef, ax_ef = plt.subplots() | |
ax_ef = rp.plot_frontier(mu=mu, cov=cov, returns=port.returns, w=w1, rm='MV', w_frontier=ef, marker='*', label='Optimal Portfolio - Max. Sharpe' ,s=16) | |
st.pyplot(fig_ef) | |
st.write('**Asset Mix of Optimized Portfolio:**') | |
st.dataframe(w1.T, use_container_width=True) | |
# corr matrix | |
fig, ax = plt.subplots() | |
corr = returns.corr() | |
# Create a heatmap | |
heatmap = go.Heatmap(z=corr.values, x=corr.columns, y=corr.index, colorscale='RdYlBu') | |
layout = go.Layout(title='Correlation Matrix', autosize=True) | |
fig = go.Figure(data=[heatmap], layout=layout) | |
st.plotly_chart(fig) | |
# -------------------------- (HRP Portfolio) -------------------------------- # | |
st.title('Hierarchical Risk Parity Portfolio') | |
st.write(""" | |
HRP is unlike traditional portfolio optimization methods. It can create an optimized portfolio when the covariance matrix is ill-degenerated or singular. This is impossible for quadratic optimizers. | |
Research has shown HRP to deliver lower out-of-sample variance than traditional optimization methods. | |
""") | |
fig1, ax1 = plt.subplots() | |
ax1 = rp.plot_clusters(returns=returns, | |
codependence='pearson', | |
linkage='single', | |
k=None, | |
max_k=10, | |
leaf_order=True, | |
dendrogram=True, | |
ax=None) | |
st.pyplot(fig1) | |
port = rp.HCPortfolio(returns=returns)#, w_max=0.3, w_min=0.05) | |
w = port.optimization( | |
model="HRP", | |
codependence="pearson", | |
obj='Sharpe', | |
rm='vol', # minimum variance | |
rf=0.000406, | |
linkage="single", | |
max_k=10, | |
leaf_order=True, | |
) | |
fig2, ax2 = plt.subplots() | |
ax2 = rp.plot_pie( | |
w=w, | |
title="HRP Naive Risk Parity", | |
others=0.05, | |
nrow=25, | |
cmap="tab20", | |
height=8, | |
width=10, | |
ax=None, | |
) | |
st.pyplot(fig2) | |
fig3, ax3 = plt.subplots() | |
ax3 = rp.plot_risk_con( | |
w=w, | |
cov=returns.cov(), | |
returns=returns, | |
rm="MV", | |
rf=0, | |
alpha=0.05, | |
color="tab:blue", | |
height=6, | |
width=10, | |
t_factor=252, | |
ax=None, | |
) | |
st.pyplot(fig3) | |
# ---------------------------- (Portfolio Statistics) -------------------------------- # | |
spy_prices = obb.equity.price.historical(symbol = "spy", provider="yfinance", start_date=start_date, end_date=end_date).to_df() | |
# Calculate daily returns | |
# Ensure you're using the adjusted close prices for accurate return calculation | |
benchmark_returns = spy_prices['close'].pct_change().dropna() | |
port.rf = 0.000406 # Risk-free rate | |
portfolio_return = np.dot(w, mu) | |
# market return | |
spy_daily_return = benchmark_returns | |
spy_expected_return = spy_daily_return.mean() | |
# portfolio's beta | |
covariance = returns.apply(lambda x: x.cov(spy_daily_return)) | |
spy_variance = spy_daily_return.var() | |
beta_values = covariance / spy_variance | |
portfolio_beta = np.dot(w['weights'], beta_values) | |
st.write('Portfolio Beta: ', np.round(portfolio_beta,3)) | |
# jensens alpha | |
expected_return = port.rf + portfolio_beta * (spy_daily_return - port.rf) | |
st.write('Jensen\'s Alpha: ', np.round(expected_return.iloc[-1],3)) | |
# treynor ratio | |
treynor_ratio = (expected_return - port.rf) / portfolio_beta | |
st.write('Treynor Ratio: ', np.round(treynor_ratio.iloc[-1],3)) | |
# Portfolio volatility | |
portfolio_stddev = np.sqrt(np.dot(pd.Series(w['weights']).T, np.dot(covariance, w['weights']))) | |
st.write('Portfolio Volatility: ', np.round(np.mean(portfolio_stddev), 3)) | |
# Sharpe Ratio, adjusted for the risk-free rate | |
sharpe_ratio = rp.RiskFunctions.Sharpe( | |
mu=mu, | |
cov=cov, | |
returns=returns, | |
rf=port.rf, | |
w=w, | |
) | |
st.write('Sharpe Ratio: ', np.round(sharpe_ratio, 3)) | |
# -------------------------- (Report) -------------------------------- # | |
# fig_report = rp.jupyter_report(returns, | |
# w=w1, | |
# rm='MV', | |
# rf=0.0406, | |
# nrow=25 | |
# ) | |
# st.pyplot(fig_report) | |