diff --git a/model.py b/model.py index eb9cfe8..91a6f88 100644 --- a/model.py +++ b/model.py @@ -646,14 +646,20 @@ def create_plots(df, start=None, end=None, project_days=365): # Set up the style plt.style.use("seaborn-v0_8") - # Create figure with adjusted size for additional subplot - _ = plt.figure(figsize=(15, 15)) # Increased height to accommodate new subplot + # Create figure with adjusted size and spacing + fig = plt.figure(figsize=(15, 15)) + + # Use GridSpec for better control over subplot spacing + gs = plt.GridSpec(5, 1, height_ratios=[3, 1.5, 1.5, 1.5, 2], hspace=0.4) # 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')})" + # Calculate full date range including projections + full_date_range = pd.date_range(plot_df["Date"].min(), projections.index.max()) + # 1. Price history and projections (log scale) - ax1 = plt.subplot(4, 1, 1) + ax1 = fig.add_subplot(gs[0]) # Plot historical prices ax1.semilogy(plot_df["Date"], plot_df["Close"], "b-", label="Historical Price") @@ -703,22 +709,43 @@ def create_plots(df, start=None, end=None, project_days=365): ax1.set_title("Bitcoin Price History and Projections (Log Scale)" + hist_date_range) ax1.legend(fontsize=8) + # Set x-axis limits to full range + ax1.set_xlim(full_date_range[0], full_date_range[-1]) + ax1.tick_params(axis="x", rotation=45) + # 3. Rolling volatility - ax3 = plt.subplot(5, 1, 2) + ax3 = fig.add_subplot(gs[1]) ax3.plot( plot_df["Date"], plot_df["Rolling_Volatility_30d"], "r-", label="30-Day Rolling Volatility", ) + + # Add empty space to match price plot x-axis + ax3.set_xlim(full_date_range[0], full_date_range[-1]) + + # Add vertical line to mark start of projections + ax3.axvline(plot_df["Date"].max(), color="gray", linestyle="--", alpha=0.5) + ax3.text( + plot_df["Date"].max(), + ax3.get_ylim()[1], + "Projection Start", + rotation=90, + va="top", + ha="right", + alpha=0.7, + ) + 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() + ax3.tick_params(axis="x", rotation=45) # 4. Returns distribution - ax4 = plt.subplot(5, 1, 3) + ax4 = fig.add_subplot(gs[2]) returns_mean = plot_df["Daily_Return"].mean() returns_std = plot_df["Daily_Return"].std() filtered_returns = plot_df["Daily_Return"][ @@ -746,7 +773,7 @@ def create_plots(df, start=None, end=None, project_days=365): ) # 5. Projection ranges - ax5 = plt.subplot(5, 1, 4) + ax5 = fig.add_subplot(gs[3:]) # Use last two grid spaces for larger plot timepoints = np.array(range(30, project_days, 30)) timepoints = timepoints[timepoints <= project_days] @@ -786,13 +813,13 @@ def create_plots(df, start=None, end=None, project_days=365): ax5.grid(True, alpha=0.3) ax5.set_xticks(timepoints) - # Adjust 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") end_str = end if end else plot_df["Date"].max().strftime("%Y-%m-%d") filename = f"bitcoin_analysis_{start_str}_to_{end_str}_with_projections.png" + + # Use tight_layout with adjusted parameters + plt.tight_layout(pad=2.0) plt.savefig(filename, dpi=300, bbox_inches="tight") plt.close()