File size: 4,156 Bytes
b651d9b
 
538f8e7
86ea38c
b651d9b
538f8e7
 
 
b651d9b
538f8e7
 
b651d9b
 
538f8e7
 
b651d9b
538f8e7
 
b651d9b
 
 
538f8e7
b651d9b
 
 
538f8e7
 
b651d9b
 
 
538f8e7
 
b651d9b
b8afc46
538f8e7
 
 
b651d9b
 
538f8e7
b651d9b
 
538f8e7
 
b651d9b
538f8e7
b651d9b
538f8e7
 
b651d9b
 
 
538f8e7
b651d9b
538f8e7
b651d9b
 
b8afc46
538f8e7
 
b651d9b
 
 
 
 
 
 
 
538f8e7
 
b651d9b
 
 
 
86ea38c
 
 
 
 
b8afc46
 
 
 
 
 
86ea38c
 
 
b651d9b
 
 
 
538f8e7
b8afc46
e127f01
b651d9b
86ea38c
b651d9b
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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()