diff --git a/model.py b/model.py index 1dd86a6..67c0880 100644 --- a/model.py +++ b/model.py @@ -105,7 +105,7 @@ def get_nice_price_points(min_price, max_price): def analyze_trends(df): """ - Analyze Bitcoin price trends using log returns. + Analyze Bitcoin price trends using log returns with simple moving average smoothing. """ df = df.copy() @@ -123,14 +123,18 @@ def analyze_trends(df): # Group by position in cycle position_returns = df.groupby("Cycle_Days")["Log_Return"].mean() - # Smooth the returns using Savitzky-Golay filter - window = 91 # About 3 months - if len(position_returns) > window: - position_returns = pd.Series( - savgol_filter(position_returns, window, 3), index=position_returns.index - ) + # Simple moving average smoothing + window = 60 + smoothed_returns = position_returns.rolling( + window=window, + center=True, # Center the window for better trend capture + min_periods=int(window / 2), # Allow partial windows to reduce edge effects + ).mean() - return position_returns + # Fill any NaN values at the edges + smoothed_returns = smoothed_returns.fillna(method="bfill").fillna(method="ffill") + + return smoothed_returns def calculate_volatility(df, short_window=30, medium_window=90, long_window=180): @@ -163,14 +167,14 @@ def project_prices( ): """ Project future Bitcoin prices using Monte Carlo simulation. - Now with enhanced volatility calculation. + Uses enhanced trend smoothing for better predictions. """ # Calculate log returns for volatility estimation df = df.copy() df["Log_Price"] = np.log(df["Close"]) df["Log_Return"] = df["Log_Price"].diff() - # Get cycle-based trends + # Get smoothed trends cycle_trends = analyze_trends(df) # Get current position in halving cycle @@ -188,24 +192,33 @@ def project_prices( start=last_date + timedelta(days=1), periods=days_forward, freq="D" ) - # Calculate expected returns for future dates based on cycle position + # Calculate expected returns with enhanced cycle boundary handling future_cycle_days = [ (current_cycle_days + i) % (4 * 365) for i in range(days_forward) ] - expected_returns = np.array( - [cycle_trends.get(day, cycle_trends.mean()) for day in future_cycle_days] - ) + expected_returns = [] - # Calculate enhanced volatility + for day in future_cycle_days: + # Get base trend value + base_trend = cycle_trends.get(day, cycle_trends.mean()) + + # Add slight mean reversion for extreme values + if abs(base_trend) > 2 * cycle_trends.std(): + base_trend *= 0.8 # Dampen extreme trends + + expected_returns.append(base_trend) + + expected_returns = np.array(expected_returns) + + # Calculate volatility volatility = calculate_volatility(df) - # Run Monte Carlo simulation with enhanced volatility + # Run Monte Carlo simulation np.random.seed(42) simulated_paths = np.zeros((days_forward, simulations)) for sim in range(simulations): - # Generate random returns with slight skew based on expected returns - skew = np.sign(expected_returns) * 0.1 # Small skew in direction of trend + skew = np.sign(expected_returns) * 0.087 # Small skew in direction of trend returns = np.random.normal( loc=expected_returns + skew * volatility, scale=volatility,