File size: 4,206 Bytes
d79bff2
 
 
 
 
 
 
60b5e0e
d79bff2
60b5e0e
d79bff2
6993a9d
c3b67d5
d79bff2
6993a9d
c3b67d5
60b5e0e
c3b67d5
d79bff2
c3b67d5
 
 
fa28d11
6993a9d
 
 
d79bff2
 
 
 
 
 
fa28d11
d79bff2
60b5e0e
fa28d11
60b5e0e
 
 
 
 
 
 
 
 
 
 
d79bff2
60b5e0e
 
 
 
 
 
 
 
 
 
 
 
 
 
d79bff2
 
60b5e0e
d79bff2
60b5e0e
 
 
 
 
6993a9d
 
60b5e0e
 
6993a9d
 
60b5e0e
 
 
 
 
 
 
58ddaed
60b5e0e
d79bff2
 
60b5e0e
 
d79bff2
 
 
 
 
 
60b5e0e
d79bff2
60b5e0e
d79bff2
60b5e0e
d79bff2
 
6993a9d
 
60b5e0e
d79bff2
 
60b5e0e
6993a9d
 
60b5e0e
d79bff2
41d6b54
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
103
104
105
106
107
108
109
110
111
112
113
114
import gradio as gr
import pandas as pd
import numpy as np
import plotly.graph_objects as go

CSV_URL = "https://gardenstatemls.stats.showingtime.com/infoserv/s-v1/kpou-Asg"

def monte_carlo_combined_plot(T=1.0, steps=120, n_paths=500):
    try:
        # Load and clean data
        df = pd.read_csv(CSV_URL, skiprows=9)
        df = df.dropna(axis=1, how='all')
        df = df.iloc[:, :2]
        df.columns = ['Date', 'Median Sales Price']
        df['Median Sales Price'] = pd.to_numeric(df['Median Sales Price'].replace('[\$,]', '', regex=True), errors='coerce')
        df['Date'] = pd.to_datetime(df['Date'], errors='coerce')
        df = df.dropna(subset=['Date', 'Median Sales Price']).sort_values(by='Date')

        prices = df['Median Sales Price'].values
        if len(prices) < 2:
            raise ValueError("Not enough valid price data after cleaning.")
        returns = np.diff(np.log(prices))

        mu = np.mean(returns) * 12
        sigma = np.std(returns) * np.sqrt(12)
        S0 = prices[-1]

        dt = T / steps
        paths = np.zeros((steps + 1, n_paths))
        paths[0] = S0
        for t in range(1, steps + 1):
            rand = np.random.normal(0, 1, n_paths)
            paths[t] = paths[t - 1] * np.exp((mu - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * rand)

        forecast_dates = pd.date_range(start=df['Date'].iloc[-1], periods=steps+1, freq='MS')[1:]

        # Combine history and forecast for unified plot
        fig = go.Figure()

        # Plot historical
        fig.add_trace(go.Scatter(
            x=df['Date'],
            y=df['Median Sales Price'],
            mode='lines+markers',
            name='Historical',
            line=dict(width=3)
        ))

        # Plot Monte Carlo paths
        for i in range(n_paths):
            full_x = pd.concat([pd.Series([df['Date'].iloc[-1]]), pd.Series(forecast_dates)])
            full_y = np.concatenate([[S0], paths[1:, i]])
            fig.add_trace(go.Scatter(
                x=full_x,
                y=full_y,
                mode='lines',
                line=dict(width=1),
                showlegend=False,
                opacity=0.3
            ))

        # Plot mean and median path
        mean_path = paths.mean(axis=1)
        median_path = np.median(paths, axis=1)
        future_dates_full = pd.concat([pd.Series([df['Date'].iloc[-1]]), pd.Series(forecast_dates)])

        fig.add_trace(go.Scatter(x=future_dates_full, y=mean_path, name='Mean Forecast', line=dict(width=3, dash='dash')))
        fig.add_trace(go.Scatter(x=future_dates_full, y=median_path, name='Median Forecast', line=dict(width=3, dash='dot')))

        fig.update_layout(
            title="🏑 Garden State MLS: Historical + Monte Carlo Forecast",
            xaxis_title="Date",
            yaxis_title="Median Sales Price ($)",
            height=700,
            template='plotly_white'
        )

        summary = f"""
        **Simulation Summary**
        - Starting Price: ${S0:,.2f}
        - Simulated {n_paths} paths over {T:.1f} years
        - Annual Drift (ΞΌ): {mu * 100:.2f}%
        - Annual Volatility (Οƒ): {sigma * 100:.2f}%
        """

        return fig, summary

    except Exception as e:
        fig = go.Figure()
        fig.update_layout(title="Error", annotations=[{
            "text": str(e),
            "xref": "paper",
            "yref": "paper",
            "showarrow": False,
            "font": {"size": 16}
        }])
        return fig, f"Error: {e}"

# Gradio Interface
with gr.Blocks() as demo:
    gr.Markdown("## πŸ“ˆ Garden State MLS Forecast: Historical + Monte Carlo Simulation")

    with gr.Row():
        years = gr.Slider(0.1, 10.0, value=1.0, step=0.1, label="Forecast Horizon (Years)")
        steps = gr.Slider(12, 240, value=120, step=12, label="Time Steps")
        paths = gr.Slider(10, 2000, value=500, step=10, label="Number of Simulated Paths")

    run_button = gr.Button("Run Simulation")
    plot = gr.Plot(label="πŸ“‰ Combined Historical + Forecast Plot")
    summary = gr.Markdown(label="πŸ“ Simulation Summary")

    run_button.click(monte_carlo_combined_plot, inputs=[years, steps, paths], outputs=[plot, summary])

demo.launch()