diff --git a/src/pages/analysis/monte_carlo_page.py b/src/pages/analysis/monte_carlo_page.py new file mode 100644 index 0000000..b4a97c6 --- /dev/null +++ b/src/pages/analysis/monte_carlo_page.py @@ -0,0 +1,227 @@ +import streamlit as st +import pandas as pd +import numpy as np +import yfinance as yf +from datetime import datetime, timedelta +from utils.common_utils import get_stock_data +import plotly.graph_objects as go +from plotly.subplots import make_subplots +from typing import Tuple, List, Dict +from scipy import stats + +class MonteCarloSimulator: + def __init__(self, data: pd.DataFrame, num_simulations: int, time_horizon: int): + """ + Initialize Monte Carlo simulator + + Args: + data (pd.DataFrame): Historical price data + num_simulations (int): Number of simulation paths + time_horizon (int): Number of days to simulate + """ + self.data = data + self.num_simulations = num_simulations + self.time_horizon = time_horizon + self.returns = np.log(data['Close'] / data['Close'].shift(1)).dropna() + self.last_price = data['Close'].iloc[-1] + self.drift = self.returns.mean() + self.volatility = self.returns.std() + + def run_simulation(self) -> np.ndarray: + """Run Monte Carlo simulation and return paths""" + # Generate random walks + daily_returns = np.random.normal( + (self.drift + (self.volatility ** 2) / 2), + self.volatility, + size=(self.time_horizon, self.num_simulations) + ) + + # Calculate price paths + price_paths = np.zeros_like(daily_returns) + price_paths[0] = self.last_price + for t in range(1, self.time_horizon): + price_paths[t] = price_paths[t-1] * np.exp(daily_returns[t]) + + return price_paths + + def calculate_metrics(self, paths: np.ndarray) -> Dict: + """Calculate key metrics from simulation results""" + final_prices = paths[-1] + returns = (final_prices - self.last_price) / self.last_price + + metrics = { + 'Expected Price': np.mean(final_prices), + 'Median Price': np.median(final_prices), + 'Std Dev': np.std(final_prices), + 'Skewness': stats.skew(final_prices), + 'Kurtosis': stats.kurtosis(final_prices), + '95% CI Lower': np.percentile(final_prices, 2.5), + '95% CI Upper': np.percentile(final_prices, 97.5), + 'Probability Above Current': np.mean(final_prices > self.last_price) * 100, + 'Expected Return': np.mean(returns) * 100, + 'VaR (95%)': np.percentile(returns, 5) * 100, + 'CVaR (95%)': np.mean(returns[returns <= np.percentile(returns, 5)]) * 100 + } + return metrics + +def create_simulation_plot(paths: np.ndarray, dates: pd.DatetimeIndex, + ticker: str, last_price: float) -> go.Figure: + """Create an interactive plot of simulation results""" + fig = make_subplots( + rows=2, cols=1, + subplot_titles=('Price Paths', 'Price Distribution at End Date'), + vertical_spacing=0.15, + row_heights=[0.7, 0.3] + ) + + # Plot confidence intervals + percentiles = np.percentile(paths, [5, 25, 50, 75, 95], axis=1) + + # Add price paths + fig.add_trace( + go.Scatter(x=dates, y=percentiles[2], name='Median Path', + line=dict(color='blue', width=2)), row=1, col=1) + + # Add confidence intervals + fig.add_trace( + go.Scatter(x=dates, y=percentiles[4], name='95% Confidence', + line=dict(color='rgba(0,100,80,0.2)', width=0)), row=1, col=1) + fig.add_trace( + go.Scatter(x=dates, y=percentiles[0], name='95% Confidence', + fill='tonexty', line=dict(color='rgba(0,100,80,0.2)', width=0)), + row=1, col=1) + + # Add starting price line + fig.add_trace( + go.Scatter(x=dates, y=[last_price] * len(dates), name='Current Price', + line=dict(color='red', dash='dash')), row=1, col=1) + + # Add histogram of final prices + fig.add_trace( + go.Histogram(x=paths[-1], name='Final Price Distribution', + nbinsx=50), row=2, col=1) + + fig.update_layout( + title=f'Monte Carlo Simulation - {ticker}', + showlegend=True, + height=800 + ) + + return fig + +def monte_carlo_page(): + st.title("Monte Carlo Price Simulation") + + # Input parameters + col1, col2 = st.columns(2) + + with col1: + ticker = st.text_input("Enter Ticker Symbol", value="AAPL").upper() + start_date = st.date_input( + "Start Date (for historical data)", + value=datetime.now() - timedelta(days=365) + ) + end_date = st.date_input("End Date", value=datetime.now()) + + with col2: + num_simulations = st.number_input( + "Number of Simulations", + min_value=100, + max_value=10000, + value=1000, + step=100 + ) + time_horizon = st.number_input( + "Time Horizon (Days)", + min_value=5, + max_value=365, + value=30 + ) + confidence_level = st.slider( + "Confidence Level (%)", + min_value=80, + max_value=99, + value=95 + ) + + if st.button("Run Simulation"): + with st.spinner('Running Monte Carlo simulation...'): + try: + # Get historical data + df = get_stock_data( + ticker, + datetime.combine(start_date, datetime.min.time()), + datetime.combine(end_date, datetime.min.time()), + 'daily' + ) + + if df.empty: + st.error("No data available for the selected period") + return + + # Initialize simulator + simulator = MonteCarloSimulator(df, num_simulations, time_horizon) + + # Run simulation + paths = simulator.run_simulation() + + # Calculate metrics + metrics = simulator.calculate_metrics(paths) + + # Generate future dates for plotting + future_dates = pd.date_range( + start=end_date, + periods=time_horizon, + freq='B' # Business days + ) + + # Display results + col1, col2 = st.columns([3, 1]) + + with col1: + # Plot results + fig = create_simulation_plot( + paths, future_dates, ticker, simulator.last_price + ) + st.plotly_chart(fig, use_container_width=True) + + with col2: + st.subheader("Simulation Metrics") + + # Price metrics + st.write("##### Price Projections") + st.metric("Expected Price", f"${metrics['Expected Price']:.2f}") + st.metric("95% CI", f"${metrics['95% CI Lower']:.2f} - ${metrics['95% CI Upper']:.2f}") + + # Risk metrics + st.write("##### Risk Metrics") + st.metric("Expected Return", f"{metrics['Expected Return']:.1f}%") + st.metric("Value at Risk (95%)", f"{abs(metrics['VaR (95%)']):.1f}%") + st.metric("Conditional VaR", f"{abs(metrics['CVaR (95%)']):.1f}%") + + # Distribution metrics + st.write("##### Distribution Metrics") + st.metric("Standard Deviation", f"${metrics['Std Dev']:.2f}") + st.metric("Skewness", f"{metrics['Skewness']:.2f}") + st.metric("Kurtosis", f"{metrics['Kurtosis']:.2f}") + + # Add download button for simulation results + final_prices_df = pd.DataFrame({ + 'Simulation': range(1, num_simulations + 1), + 'Final_Price': paths[-1], + 'Return': (paths[-1] - simulator.last_price) / simulator.last_price * 100 + }) + + csv = final_prices_df.to_csv(index=False) + st.download_button( + label="Download Simulation Results", + data=csv, + file_name=f'monte_carlo_{ticker}_{datetime.now().strftime("%Y%m%d")}.csv', + mime='text/csv' + ) + + except Exception as e: + st.error(f"Error during simulation: {str(e)}") + +if __name__ == "__main__": + monte_carlo_page()