rebalance / app.py
Baisub Lee
added rebalance moments
b8afc46
import yfinance as yf
import pandas as pd
import gradio as gr
import plotly.graph_objs as go
def fetch_and_rebalance(etfs_str, period, rebalance_threshold_percentage):
etfs = [etf.strip() for etf in etfs_str.split(",")]
# Fetch historical data for the given period
etf_data = {etf: yf.Ticker(etf).history(period=period) for etf in etfs}
# Ensure the index is a DatetimeIndex
for etf in etfs:
etf_data[etf].index = pd.to_datetime(etf_data[etf].index)
# Fetch dividend data and ensure the index is a DatetimeIndex
etf_dividends = {etf: yf.Ticker(etf).dividends for etf in etfs}
# Ensure both data series have the same dates
common_dates = etf_data[etfs[0]].index
for etf in etfs[1:]:
common_dates = common_dates.intersection(etf_data[etf].index)
for etf in etfs:
etf_data[etf] = etf_data[etf].reindex(common_dates)
etf_dividends[etf] = etf_dividends[etf].reindex(common_dates, fill_value=0)
# Initial investment
initial_investment = 10000
investments = {etf: initial_investment / len(etfs) for etf in etfs}
shares = {etf: investments[etf] / etf_data[etf]['Close'].iloc[0] for etf in etfs}
# DataFrame to track the investments
df_rebalance = pd.DataFrame(columns=["Date"] + [f"{etf} Value" for etf in etfs] + ["Total Value"])
rebalance_dates = []
# Perform the rebalancing based on the percentage difference condition
for date in common_dates[1:]: # Skip the first date for the initial investment
total_value = 0
for etf in etfs:
# Update the values based on daily returns
previous_date = etf_data[etf].index[etf_data[etf].index.get_loc(date) - 1]
investments[etf] *= (etf_data[etf]['Close'].loc[date] / etf_data[etf]['Close'].loc[previous_date])
# Add dividends
investments[etf] += etf_dividends[etf].loc[date] * shares[etf]
total_value += investments[etf]
# Check if rebalancing is needed
value_difference = abs(investments[etfs[0]] - investments[etfs[1]])
percentage_difference = value_difference / total_value
rebalance_needed = percentage_difference > rebalance_threshold_percentage
if rebalance_needed:
# Rebalance to equal weight
investments = {etf: total_value / len(etfs) for etf in etfs}
shares = {etf: investments[etf] / etf_data[etf]['Close'].loc[date] for etf in etfs}
rebalance_dates.append(date)
# Append to DataFrame using pd.concat
df_rebalance = pd.concat([
df_rebalance,
pd.DataFrame({
"Date": [date],
**{f"{etf} Value": [investments[etf]] for etf in etfs},
"Total Value": [total_value]
})
], ignore_index=True)
# Check if df_rebalance is empty
if df_rebalance.empty:
return "No data was appended."
final_value = df_rebalance["Total Value"].iloc[-1]
total_return = (final_value - initial_investment) / initial_investment * 100
# Create the plot
fig = go.Figure()
for etf in etfs:
fig.add_trace(go.Scatter(x=df_rebalance["Date"], y=df_rebalance[f"{etf} Value"], mode='lines', name=etf))
# Add rebalancing markers
for date in rebalance_dates:
fig.add_trace(go.Scatter(x=[date], y=[df_rebalance.loc[df_rebalance['Date'] == date, f"{etf} Value"].values[0]],
mode='markers', name='Rebalance', marker=dict(size=10, color='red')))
fig.update_layout(title='ETF Valuations Over Time', xaxis_title='Date', yaxis_title='Value')
return f"Final Value: {final_value}, Total Return: {total_return}%", fig
interface = gr.Interface(
fn=fetch_and_rebalance,
inputs=[
gr.Textbox(lines=1, placeholder="ETFs (comma separated)", value="TQQQ,JEPI"),
gr.Textbox(lines=1, placeholder="Period", value="ytd"),
gr.Slider(minimum=0.0, maximum=1.0, value=0.05, label="Rebalance Threshold Percentage")
],
outputs=["text", "plot"]
)
interface.launch()