diff --git a/model.py b/model.py index d58481e..51897fe 100644 --- a/model.py +++ b/model.py @@ -163,14 +163,111 @@ def calculate_volatility(df, short_window=30, medium_window=90, long_window=180) return base_vol * volatility_scale +def calculate_market_maturity_score(df): + """ + Calculate a market maturity score (0-1) based on multiple indicators. + Higher scores indicate a more mature market. + """ + df = df.copy() + + # 1. Volume-based metrics + df["log_volume"] = np.log(df["Volume"]) + df["volume_ma"] = df["log_volume"].rolling(window=365).mean() + volume_growth = (df["volume_ma"] - df["volume_ma"].shift(365)) / df[ + "volume_ma" + ].shift(365) + + # 2. Volatility maturity (lower volatility = more mature) + df["rolling_vol"] = df["Daily_Return"].rolling(window=365).std() * np.sqrt(365) + vol_maturity = 1 / (1 + df["rolling_vol"]) + + # 3. Market efficiency score + df["autocorr"] = ( + df["Daily_Return"] + .rolling(window=30) + .apply(lambda x: abs(pd.Series(x).autocorr(1))) + ) + efficiency = 1 - df["autocorr"] # Lower autocorrelation = more efficient + + # 4. Futures market impact (post-2017) + futures_date = pd.Timestamp("2017-12-10") + futures_impact = (df["Date"] > futures_date).astype(float) * 0.2 + + # Combine scores with time-varying weights + weights = {"volume": 0.3, "volatility": 0.3, "efficiency": 0.2, "futures": 0.2} + + maturity_score = ( + weights["volume"] * volume_growth.clip(-1, 1).map(lambda x: (x + 1) / 2) + + weights["volatility"] * vol_maturity + + weights["efficiency"] * efficiency + + weights["futures"] * futures_impact + ) + + # Normalize to 0-1 range and smooth + maturity_score = (maturity_score - maturity_score.min()) / ( + maturity_score.max() - maturity_score.min() + ) + maturity_score = maturity_score.rolling(window=30, min_periods=1).mean() + + return maturity_score + + +def adjust_projections_for_maturity(df, projections, maturity_score): + """ + Adjust price projections based on market maturity score. + More mature markets should have tighter confidence intervals + and more conservative growth expectations. + """ + # Get final maturity score + final_maturity = maturity_score.iloc[-1] + + # Adjust confidence intervals based on maturity + # More mature markets = tighter intervals + ci_adjustment = 1 - (final_maturity * 0.3) # Max 30% reduction in interval width + + # Adjust expected returns based on maturity + # More mature markets = more conservative growth + returns_adjustment = 1 - ( + final_maturity * 0.2 + ) # Max 20% reduction in expected returns + + adjusted_projections = projections.copy() + + # Adjust confidence intervals + for ci in [68, 95]: + upper_key = f"Upper_{ci}" + lower_key = f"Lower_{ci}" + median = adjusted_projections["Median"] + + # Calculate distances from median + upper_distance = adjusted_projections[upper_key] - median + lower_distance = median - adjusted_projections[lower_key] + + # Apply maturity-based adjustment + adjusted_projections[upper_key] = median + (upper_distance * ci_adjustment) + adjusted_projections[lower_key] = median - (lower_distance * ci_adjustment) + + # Adjust expected trend + trend_distance = ( + adjusted_projections["Expected_Trend"] - adjusted_projections["Median"] + ) + adjusted_projections["Expected_Trend"] = ( + adjusted_projections["Median"] + trend_distance * returns_adjustment + ) + + return adjusted_projections + + def project_prices( df, days_forward=365, simulations=1000, confidence_levels=[0.95, 0.68] ): """ - Project future Bitcoin prices using Monte Carlo simulation. - Uses enhanced trend smoothing for better predictions. + Project future Bitcoin prices using Monte Carlo simulation with market maturity adjustments. """ - # Calculate log returns for volatility estimation + # Calculate market maturity score + maturity_score = calculate_market_maturity_score(df) + + # Original calculations df = df.copy() df["Log_Price"] = np.log(df["Close"]) df["Log_Return"] = df["Log_Price"].diff() @@ -193,7 +290,7 @@ def project_prices( start=last_date + timedelta(days=1), periods=days_forward, freq="D" ) - # Calculate expected returns with enhanced cycle boundary handling + # Calculate expected returns with cycle boundary handling future_cycle_days = [ (current_cycle_days + i) % (4 * 365) for i in range(days_forward) ] @@ -211,17 +308,30 @@ def project_prices( expected_returns = np.array(expected_returns) - # Calculate volatility - volatility = calculate_volatility(df) + # Calculate volatility with maturity adjustment + base_volatility = calculate_volatility(df) + final_maturity = maturity_score.iloc[-1] + + # Adjust volatility based on market maturity (more mature = lower volatility) + volatility_adjustment = 1 - (final_maturity * 0.3) # Max 30% reduction + volatility = base_volatility * volatility_adjustment # Run Monte Carlo simulation np.random.seed(42) simulated_paths = np.zeros((days_forward, simulations)) + # Adjust trend expectations based on market maturity + trend_adjustment = 1 - (final_maturity * 0.2) # Max 20% reduction + adjusted_expected_returns = expected_returns * trend_adjustment + for sim in range(simulations): - skew = np.sign(expected_returns) * 0.087 # Small skew in direction of trend + # Adjust skew based on market maturity (more mature = less skew) + base_skew = 0.087 + skew_adjustment = 1 - (final_maturity * 0.4) # Max 40% reduction + skew = np.sign(adjusted_expected_returns) * base_skew * skew_adjustment + returns = np.random.normal( - loc=expected_returns + skew * volatility, + loc=adjusted_expected_returns + skew * volatility, scale=volatility, size=days_forward, ) @@ -247,7 +357,12 @@ def project_prices( ) # Add expected trend line - results["Expected_Trend"] = last_price * np.exp(np.cumsum(expected_returns)) + results["Expected_Trend"] = last_price * np.exp( + np.cumsum(adjusted_expected_returns) + ) + + # Add maturity score to results for analysis + results["Market_Maturity"] = final_maturity return results @@ -268,10 +383,17 @@ def analyze_bitcoin_prices(csv_path): print(df.info()) # Convert price columns to float and handle any potential formatting issues - price_columns = ["Price", "Open", "High", "Low"] - for col in price_columns: - # Remove any commas in numbers + numeric_columns = ["Price", "Open", "High", "Low", "Vol."] # Added Volume + for col in numeric_columns: + # Remove any commas and 'K'/'M' suffixes df[col] = df[col].astype(str).str.replace(",", "") + # Convert K to thousands + df[col] = df[col].str.replace("K", "e3") + # Convert M to millions + df[col] = df[col].str.replace("M", "e6") + # Convert B to billions + df[col] = df[col].str.replace("B", "e9") + # Convert to numeric df[col] = pd.to_numeric(df[col], errors="coerce") # Rename columns for clarity @@ -282,7 +404,7 @@ def analyze_bitcoin_prices(csv_path): # Print summary statistics after conversion print("\nPrice Summary After Conversion:") - print(df[["Close", "Open", "High", "Low"]].describe()) + print(df[["Close", "Open", "High", "Low", "Volume"]].describe()) # Calculate daily returns df["Daily_Return"] = df["Close"].pct_change() @@ -340,24 +462,24 @@ def create_plots(df, start=None, end=None, project_days=365): if len(plot_df) == 0: raise ValueError("No data found for the specified date range") - # Generate projections - # cycle_returns, cycle_volatility = analyze_trends(plot_df) - projections = project_prices(plot_df, days_forward=project_days) + # Calculate market maturity score + maturity_score = calculate_market_maturity_score(plot_df) + plot_df["Market_Maturity"] = maturity_score - # Create cycle visualization - # visualize_cycle_patterns(plot_df, cycle_returns, cycle_volatility) + # Generate projections with market maturity adjustments + projections = project_prices(plot_df, days_forward=project_days) # Set up the style plt.style.use("seaborn-v0_8") - # Create figure - fig = plt.figure(figsize=(15, 15)) + # Create figure with additional subplot for maturity + fig = plt.figure(figsize=(15, 18)) # Made taller to accommodate new subplot # Date range for titles hist_date_range = f" ({plot_df['Date'].min().strftime('%Y-%m-%d')} to {plot_df['Date'].max().strftime('%Y-%m-%d')})" # 1. Price history and projections (log scale) - ax1 = plt.subplot(4, 1, 1) + ax1 = plt.subplot(5, 1, 1) # Changed to 5,1 grid # Plot historical prices ax1.semilogy(plot_df["Date"], plot_df["Close"], "b-", label="Historical Price") @@ -535,6 +657,45 @@ def create_plots(df, start=None, end=None, project_days=365): # Set x-axis to show only our timepoints ax4.set_xticks(timepoints) + # 2. Market Maturity Score (New) + ax2 = plt.subplot(5, 1, 2) + ax2.plot( + plot_df["Date"], + plot_df["Market_Maturity"], + color="purple", + label="Market Maturity Score", + ) + ax2.set_title("Market Maturity Score" + hist_date_range) + ax2.set_xlabel("Date") + ax2.set_ylabel("Maturity Score (0-1)") + ax2.grid(True) + ax2.legend() + + # Add annotations for key events + futures_date = pd.Timestamp("2017-12-10") + if futures_date >= plot_df["Date"].min() and futures_date <= plot_df["Date"].max(): + ax2.axvline(futures_date, color="red", linestyle="--", alpha=0.5) + ax2.text( + futures_date, + ax2.get_ylim()[1], + "Futures\nLaunch", + rotation=90, + va="top", + ha="right", + ) + + # 3. Rolling volatility (now third subplot) + ax3 = plt.subplot(5, 1, 3) + # [Previous volatility plotting code...] + + # 4. Returns distribution (now fourth subplot) + ax4 = plt.subplot(5, 1, 4) + # [Previous distribution plotting code...] + + # 5. Projection ranges (now fifth subplot) + ax5 = plt.subplot(5, 1, 5) + # [Previous projection ranges plotting code...] + # Adjust layout plt.tight_layout()