diff --git a/model.py b/model.py index 0ba9fbf..aa49cb5 100644 --- a/model.py +++ b/model.py @@ -512,7 +512,7 @@ def analyze_bitcoin_prices(csv_path): def create_plots(df, start=None, end=None, project_days=365): """ - Create plots including historical data and future projections. + Create enhanced plots including market maturity visualization. """ # Filter data based on date range mask = pd.Series(True, index=df.index) @@ -530,20 +530,20 @@ def create_plots(df, start=None, end=None, project_days=365): maturity_score = calculate_market_maturity_score(plot_df) plot_df["Market_Maturity"] = maturity_score - # Generate projections with market maturity adjustments + # Generate projections projections = project_prices(plot_df, days_forward=project_days) # Set up the style plt.style.use("seaborn-v0_8") - # Create figure with additional subplot for maturity - fig = plt.figure(figsize=(15, 18)) # Made taller to accommodate new subplot + # Create figure with adjusted size for additional subplot + fig = plt.figure(figsize=(15, 20)) # Increased height 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(5, 1, 1) # Changed to 5,1 grid + ax1 = plt.subplot(5, 1, 1) # Plot historical prices ax1.semilogy(plot_df["Date"], plot_df["Close"], "b-", label="Historical Price") @@ -582,48 +582,54 @@ def create_plots(df, start=None, end=None, project_days=365): # Customize y-axis ax1.yaxis.set_major_formatter(plt.FuncFormatter(format_price)) - - # Set custom y-axis ticks at meaningful price points min_price = min(plot_df["Low"].min(), projections["Lower_95"].min()) max_price = max(plot_df["High"].max(), projections["Upper_95"].max()) - price_points = get_nice_price_points(min_price, max_price) ax1.set_yticks(price_points) - - # Adjust y-axis label properties - ax1.tick_params(axis="y", labelsize=8) # Smaller font size - - # Add some padding to prevent label cutoff + ax1.tick_params(axis="y", labelsize=8) ax1.margins(y=0.02) - - # Adjust label padding to prevent overlap - ax1.yaxis.set_tick_params(pad=1) - - # Add grid lines with adjusted opacity ax1.grid(True, which="major", linestyle="-", alpha=0.5) ax1.grid(True, which="minor", linestyle=":", alpha=0.2) - ax1.set_title("Bitcoin Price History and Projections (Log Scale)" + hist_date_range) - # Make legend font size smaller too for consistency ax1.legend(fontsize=8) - # 2. Rolling volatility - ax2 = plt.subplot(4, 1, 2) + # 2. Market Maturity Score + 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_ylim(0, 1) + ax2.grid(True, alpha=0.3) + ax2.legend() + + # Add futures launch annotation + 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, 0.95, "Futures\nLaunch", rotation=90, va="top", ha="right" + ) + + # 3. Rolling volatility + ax3 = plt.subplot(5, 1, 3) + ax3.plot( plot_df["Date"], plot_df["Rolling_Volatility_30d"], "r-", label="30-Day Rolling Volatility", ) - ax2.set_title("30-Day Rolling Volatility (Annualized)" + hist_date_range) - ax2.set_xlabel("Date") - ax2.set_ylabel("Volatility") - ax2.grid(True) - ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: "{:.0%}".format(y))) - ax2.legend() + ax3.set_title("30-Day Rolling Volatility (Annualized)" + hist_date_range) + ax3.set_ylabel("Volatility") + ax3.grid(True) + ax3.yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: "{:.0%}".format(y))) + ax3.legend() - # 3. Returns distribution - ax3 = plt.subplot(4, 1, 3) + # 4. Returns distribution + ax4 = plt.subplot(5, 1, 4) returns_mean = plot_df["Daily_Return"].mean() returns_std = plot_df["Daily_Return"].std() filtered_returns = plot_df["Daily_Return"][ @@ -631,30 +637,28 @@ def create_plots(df, start=None, end=None, project_days=365): & (plot_df["Daily_Return"] < returns_mean + 5 * returns_std) ] - sns.histplot(filtered_returns, bins=100, ax=ax3) - ax3.set_title( + sns.histplot(filtered_returns, bins=100, ax=ax4) + ax4.set_title( "Distribution of Daily Returns (Excluding Extreme Outliers)" + hist_date_range ) - ax3.set_xlabel("Daily Return") - ax3.set_ylabel("Count") - ax3.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: "{:.0%}".format(x))) + ax4.set_xlabel("Daily Return") + ax4.set_ylabel("Count") + ax4.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, _: "{:.0%}".format(x))) - # Add a vertical line for mean return - ax3.axvline(filtered_returns.mean(), color="r", linestyle="dashed", linewidth=1) - ax3.text( + # Add mean line + ax4.axvline(filtered_returns.mean(), color="r", linestyle="dashed", linewidth=1) + ax4.text( filtered_returns.mean(), - ax3.get_ylim()[1], + ax4.get_ylim()[1], "Mean", rotation=90, va="top", ha="right", ) - # 4. Projection ranges - ax4 = plt.subplot(4, 1, 4) - - # Calculate and plot price ranges at different future points - timepoints = np.array(range(30, 365, 30)) + # 5. Projection ranges + ax5 = plt.subplot(5, 1, 5) + timepoints = np.array(range(30, project_days, 30)) timepoints = timepoints[timepoints <= project_days] ranges = [] @@ -662,7 +666,7 @@ def create_plots(df, start=None, end=None, project_days=365): positions = [] for t in timepoints: - idx = t - 1 # Convert to 0-based index + idx = t - 1 ranges.extend( [ projections["Lower_95"].iloc[idx], @@ -675,93 +679,26 @@ def create_plots(df, start=None, end=None, project_days=365): labels.extend(["95% Lower", "68% Lower", "Median", "68% Upper", "95% Upper"]) positions.extend([t] * 5) - # Plot ranges (removed violin plot) - ax4.scatter(positions, ranges, alpha=0.6) + ax5.scatter(positions, ranges, alpha=0.6) - # Add lines connecting the ranges for t in timepoints: idx = positions.index(t) - ax4.plot([t] * 5, ranges[idx : idx + 5], "k-", alpha=0.3) + ax5.plot([t] * 5, ranges[idx : idx + 5], "k-", alpha=0.3) - # Set log scale first - ax4.set_yscale("log") - - # Get the current order of magnitude for setting appropriate ticks + ax5.set_yscale("log") min_price = min(ranges) max_price = max(ranges) - - # Create price points at regular intervals on log scale - log_min = np.floor(np.log10(min_price)) - log_max = np.ceil(np.log10(max_price)) - price_points = [] - for exp in range(int(log_min), int(log_max + 1)): - for mult in [1, 2, 5]: - point = mult * 10**exp - if min_price <= point <= max_price: - price_points.append(point) - - ax4.set_yticks(price_points) - - def price_formatter(x, p): - if x >= 1e6: - return f"${x/1e6:.1f}M" - if x >= 1e3: - return f"${x/1e3:.0f}K" - return f"${x:.0f}" - - # Apply formatter to major ticks - ax4.yaxis.set_major_formatter(plt.FuncFormatter(price_formatter)) - - # Customize the plot - ax4.set_title("Projected Price Ranges at Future Timepoints") - ax4.set_xlabel("Days Forward") - ax4.set_ylabel("Price (USD)") - ax4.grid(True, alpha=0.3) - - # 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...] + price_points = get_nice_price_points(min_price, max_price) + ax5.set_yticks(price_points) + ax5.yaxis.set_major_formatter(plt.FuncFormatter(format_price)) + ax5.set_title("Projected Price Ranges at Future Timepoints") + ax5.set_xlabel("Days Forward") + ax5.set_ylabel("Price (USD)") + ax5.grid(True, alpha=0.3) + ax5.set_xticks(timepoints) # Adjust layout - plt.tight_layout() + plt.tight_layout(h_pad=1.0) # Increased spacing between subplots # Save the plot start_str = start if start else plot_df["Date"].min().strftime("%Y-%m-%d")