File size: 6,882 Bytes
18ee127
b02bd01
 
 
 
 
 
fb027d3
18ee127
b02bd01
 
18ee127
b02bd01
 
48663c9
 
 
 
 
 
 
 
 
 
 
 
 
b02bd01
 
 
48663c9
 
 
 
 
 
 
 
 
 
 
 
 
fb027d3
48663c9
 
 
 
b02bd01
48663c9
b02bd01
 
fb027d3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18ee127
b02bd01
 
 
 
 
 
18ee127
b02bd01
 
 
 
 
 
 
 
 
 
18ee127
b02bd01
 
 
 
 
 
 
 
 
 
48663c9
 
 
 
b02bd01
48663c9
b02bd01
48663c9
b02bd01
fb027d3
b02bd01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8901955
48663c9
 
b02bd01
 
 
 
 
48663c9
 
b02bd01
 
 
 
 
 
18ee127
b02bd01
 
 
8901955
b02bd01
 
 
 
 
 
 
 
 
 
 
 
48663c9
b02bd01
 
 
 
 
 
18ee127
 
b02bd01
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import gradio as gr
import yfinance as yf
import requests
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
import time

# Polymarket GraphQL API endpoint
POLYMARKET_API = "https://api.polymarket.com/graphql"

# Function to fetch Polymarket data
def fetch_polymarket_data(search_term="S&P"):
    try:
        query = """
        query {
          markets(first: 5, searchTerm: "%s") {
            edges {
              node {
                id
                question
                outcomes {
                  name
                  price
                }
              }
            }
          }
        }
        """ % search_term
        response = requests.post(POLYMARKET_API, json={"query": query}, timeout=10)
        if response.status_code != 200:
            return None
        data = response.json()
        markets = data["data"]["markets"]["edges"]
        if not markets:
            return None
        
        # Parse the first relevant market
        for market in markets:
            node = market["node"]
            outcomes = node["outcomes"]
            if len(outcomes) >= 2:
                return {
                    "question": node["question"],
                    "outcomes": {outcome["name"]: float(outcome["price"]) for outcome in outcomes}
                }
        return None
    except Exception as e:
        return None

# Function to fetch Yahoo Finance data with retry
def fetch_yahoo_data(ticker, retries=3, delay=2):
    for attempt in range(retries):
        try:
            stock = yf.download(ticker, period="1y", auto_adjust=False, progress=False)
            if stock.empty or len(stock) < 2:
                return None, None, None, f"No data returned for ticker '{ticker}'. It may be invalid or lack sufficient history."
            daily_returns = stock["Close"].pct_change().dropna()
            if daily_returns.empty:
                return None, None, None, f"No valid returns calculated for ticker '{ticker}'. Insufficient price data."
            mu = daily_returns.mean() * 252  # Annualized drift
            sigma = daily_returns.std() * np.sqrt(252)  # Annualized volatility
            last_price = stock["Close"][-1]  # Use most recent unadjusted Close
            return mu, sigma, last_price, None
        except Exception as e:
            error_msg = f"Attempt {attempt + 1}/{retries} failed for ticker '{ticker}': {str(e)}"
            if attempt < retries - 1:
                time.sleep(delay)  # Wait before retrying
            else:
                return None, None, None, error_msg
    return None, None, None, f"Failed to fetch data for '{ticker}' after {retries} attempts."

# Monte Carlo Simulation with GBM
def monte_carlo_simulation(S0, mu, sigma, T, N, sims, risk_factor, pm_data):
    dt = 1 / 252  # Daily time step
    steps = int(T * 252)
    sim_paths = np.zeros((sims, steps + 1))
    sim_paths[:, 0] = S0

    # Adjust drift based on Polymarket probabilities
    if pm_data and "outcomes" in pm_data:
        outcomes = pm_data["outcomes"]
        bullish_prob = outcomes.get("Yes", 0.5) if "Yes" in outcomes else 0.5
        bearish_prob = 1 - bullish_prob
        mu_bull = mu * 1.2 * risk_factor
        mu_bear = mu * -0.5 * risk_factor
        mu_adjusted = bullish_prob * mu_bull + bearish_prob * mu_bear
    else:
        mu_adjusted = mu * risk_factor

    for t in range(1, steps + 1):
        Z = np.random.standard_normal(sims)
        sim_paths[:, t] = sim_paths[:, t-1] * np.exp(
            (mu_adjusted - 0.5 * sigma**2) * dt + sigma * np.sqrt(dt) * Z
        )
    
    return sim_paths

# Main simulation function
def run_simulation(investment, ticker, horizon, num_sims, risk_factor):
    # Validate inputs
    if not ticker or investment <= 0 or horizon <= 0 or num_sims <= 0:
        return None, "Please provide valid inputs: positive investment, horizon, and simulations, and a ticker."

    # Fetch data
    mu, sigma, S0, error_msg = fetch_yahoo_data(ticker)
    if mu is None:
        return None, error_msg
    
    pm_data = fetch_polymarket_data("S&P")
    
    # Run Monte Carlo simulation
    sim_paths = monte_carlo_simulation(S0, mu, sigma, horizon, num_sims, num_sims, risk_factor, pm_data)
    final_values = sim_paths[:, -1] * (investment / S0)  # Scale to investment amount
    
    # Create histogram
    fig, ax = plt.subplots(figsize=(8, 6))
    ax.hist(final_values, bins=50, color="skyblue", edgecolor="black")
    ax.set_title(f"Distribution of Final Investment Value ({ticker})")
    ax.set_xlabel("Final Value ($)")
    ax.set_ylabel("Frequency")
    plt.tight_layout()
    
    # Calculate stats
    mean_val = np.mean(final_values)
    min_val = np.min(final_values)
    max_val = np.max(final_values)
    std_val = np.std(final_values)
    
    # Prepare summary text
    summary = f"Market Data (Yahoo Finance):\n"
    summary += f"- Drift (μ): {mu:.4f} (based on unadjusted Close)\n"
    summary += f"- Volatility (σ): {sigma:.4f}\n"
    summary += f"- Last Close Price: ${S0:.2f}\n\n"
    if pm_data:
        summary += f"Polymarket Data:\n- Question: {pm_data['question']}\n"
        for outcome, prob in pm_data["outcomes"].items():
            summary += f"- {outcome}: {prob*100:.1f}%\n"
    else:
        summary += "Polymarket Data: No relevant market found or API unavailable.\n"
    summary += f"\nSimulation Results ({num_sims} runs):\n"
    summary += f"- Mean Final Value: ${mean_val:.2f}\n"
    summary += f"- Min Final Value: ${min_val:.2f}\n"
    summary += f"- Max Final Value: ${max_val:.2f}\n"
    summary += f"- Std Dev: ${std_val:.2f}"
    
    return fig, summary

# Gradio UI
with gr.Blocks(title="Investment Simulation Platform") as demo:
    gr.Markdown("# Investment Decision Simulation Platform")
    gr.Markdown("Simulate investment outcomes using Yahoo Finance data (unadjusted) and Polymarket probabilities.")
    
    with gr.Row():
        with gr.Column():
            investment = gr.Number(label="Investment Amount ($)", value=10000)
            ticker = gr.Textbox(label="Ticker Symbol (e.g., AAPL, ^GSPC)", value="AAPL")
            horizon = gr.Number(label="Investment Horizon (Years)", value=1)
            num_sims = gr.Number(label="Number of Simulations", value=1000)
            risk_factor = gr.Slider(0.5, 2.0, value=1.0, label="Risk Factor")
            submit_btn = gr.Button("Run Simulation")
        
        with gr.Column():
            plot_output = gr.Plot(label="Final Value Distribution")
            text_output = gr.Textbox(label="Simulation Summary", lines=12)
    
    submit_btn.click(
        fn=run_simulation,
        inputs=[investment, ticker, horizon, num_sims, risk_factor],
        outputs=[plot_output, text_output]
    )

if __name__ == "__main__":
    demo.launch()