File size: 2,176 Bytes
418f2f6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# src/forecast_utils.py
import numpy as np
import pandas as pd

TRADING_DAYS = 252

def forecast_to_annual_return(forecast_series: pd.Series,
                              last_price: float,
                              method: str = "avg_daily",
                              cap: float = 2.0) -> float:
    """
    Convert a forecasted price series into an expected annual return.
    
    Args:
        forecast_series (pd.Series): Forecasted prices with DatetimeIndex.
        last_price (float): Last observed price before forecast starts.
        method (str): 'avg_daily' (default, safer) or 'horizon'.
        cap (float): Maximum allowed annualized return (as fraction, default 2.0 = 200%).
    
    Returns:
        float: Annualized expected return (capped).
    """
    forecast_series = forecast_series.dropna()
    if forecast_series.empty:
        raise ValueError("Forecast series is empty")

    if method == "horizon":
        horizon_price = forecast_series.iloc[-1]
        horizon_days = len(forecast_series)
        horizon_return = horizon_price / last_price - 1
        annual_return = (1 + horizon_return) ** (TRADING_DAYS / horizon_days) - 1
    elif method == "avg_daily":
        daily_returns = forecast_series.pct_change().dropna()
        if daily_returns.empty:
            raise ValueError("Not enough forecast points to compute daily returns")
        avg_daily = daily_returns.mean()
        annual_return = (1 + avg_daily) ** TRADING_DAYS - 1
    else:
        raise ValueError("method must be 'avg_daily' or 'horizon'")
    
    # Cap extreme values for stability
    annual_return = np.clip(annual_return, -cap, cap)
    return annual_return

import matplotlib.pyplot as plt

def plot_forecast_vs_actual(actual, forecast, title="TSLA Forecast vs Actual"):
    """
    Plot actual vs forecasted stock prices.
    """
    fig, ax = plt.subplots(figsize=(10, 5))
    ax.plot(actual.index, actual.values, label="Actual", color="blue")
    ax.plot(forecast.index, forecast.values, label="Forecast", color="red")
    ax.set_title(title)
    ax.set_xlabel("Date")
    ax.set_ylabel("Price ($)")
    ax.legend()
    ax.grid(True)
    return fig