mirror of
https://github.com/PR0M3TH3AN/python_charts.git
synced 2026-10-27 17:00:41 +00:00
Enhance Bitcoin vs M2 chart with improved plotting and indexing
Refactor `bitcoin_m2_chart.py` to plot Bitcoin price on a log scale against a rebased M2 money supply index (base 100). Update visual elements for clarity, including color adjustments and axis labels. Extend x-axis for future projections and improve x-tick formatting for better readability. Add new output images for the updated chart.
This commit is contained in:
1 parent
c9e2f285c3
commit
e32d52583c
5 files changed
+59
-25
No files matched your search
+59
-25
@@ -1,15 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
"""bitcoin_m2_chart.py
|
||||
----------------------
|
||||
Plot Bitcoin price versus global M2 money supply stored in `data/fred.db`.
|
||||
|
||||
The script reads two FRED series from the local SQLite database and
|
||||
plots them on dual axes. The M2 series can be shifted relative to the
|
||||
Bitcoin series by a configurable number of days.
|
||||
Plot Bitcoin price (log scale) versus global M2 money supply (as a 100-point index)
|
||||
stored in `data/fred.db`. Bitcoin is orange; M2 is green; white background; monthly x-ticks.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
@@ -23,7 +19,7 @@ import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
# IMPORTANT: use the package‐qualified import
|
||||
# IMPORTANT: use the package-qualified import
|
||||
from scripts.common import (
|
||||
fetch_series_db,
|
||||
save_figure,
|
||||
@@ -65,37 +61,76 @@ def plot_bitcoin_m2(
|
||||
height: float = 6,
|
||||
dpi: int | None = None,
|
||||
) -> plt.Figure:
|
||||
"""Plot Bitcoin price and shifted M2 on dual axes."""
|
||||
# Determine overlapping range
|
||||
"""
|
||||
Plot Bitcoin price (log scale) and a rebased (100-point) M2 index on dual axes:
|
||||
• Bitcoin is orange, on a log-scaled left y-axis
|
||||
• Global M2 is green, on a linear right y-axis
|
||||
• White background, monthly x-ticks, with an “empty buffer” past the last date.
|
||||
"""
|
||||
# 1) Determine overlapping date range
|
||||
common_start = max(btc.index.min(), m2.index.min())
|
||||
common_end = min(btc.index.max(), m2.index.max())
|
||||
|
||||
btc_common = btc.loc[common_start:common_end]
|
||||
m2_common = m2.loc[common_start:common_end]
|
||||
btc_common = btc.loc[common_start:common_end].dropna(subset=["value"])
|
||||
m2_common = m2.loc[common_start:common_end].dropna(subset=["value"])
|
||||
|
||||
# 2) Shift M2 by `offset_days`
|
||||
m2_shifted = m2_common.shift(offset_days)
|
||||
|
||||
# 3) Rebase M2 to a 100-point index
|
||||
# (so that at the first available date, M2_index = 100)
|
||||
m2_indexed = (m2_shifted["value"] / m2_shifted["value"].iloc[0]) * 100
|
||||
|
||||
# 4) Create figure + axes
|
||||
fig, ax1 = plt.subplots(figsize=(width, height), dpi=dpi)
|
||||
# (White background is default, so no style override is needed.)
|
||||
|
||||
# 5) Plot Bitcoin (orange) ON A LOGARITHMIC Y-AXIS
|
||||
ax1.plot(
|
||||
btc_common.index, btc_common["value"], label="Bitcoin (LHS)", color="#1f77b4"
|
||||
btc_common.index,
|
||||
btc_common["value"],
|
||||
label="Bitcoin (USD, log-scale)",
|
||||
color="#f7931a", # Bitcoin-orange
|
||||
linewidth=2.0,
|
||||
)
|
||||
ax1.set_ylabel("Price (USD)")
|
||||
ax1.set_yscale("log") # ← LOG SCALE
|
||||
ax1.set_ylabel("Bitcoin Price (USD, log)", color="#333333")
|
||||
ax1.tick_params(axis="y", colors="#333333")
|
||||
ax1.grid(axis="y", linestyle="--", alpha=0.3)
|
||||
|
||||
# 6) Plot M2 index (green) on a LINEAR right‐hand axis
|
||||
ax2 = ax1.twinx()
|
||||
ax2.plot(
|
||||
m2_shifted.index, m2_shifted["value"], label="Global M2 (RHS)", color="#ff7f0e"
|
||||
m2_indexed.index,
|
||||
m2_indexed,
|
||||
label="Global M2 Index (base 100)",
|
||||
color="#00aa00", # dark-green for contrast
|
||||
linewidth=2.0,
|
||||
)
|
||||
ax2.set_ylabel("Global M2")
|
||||
ax2.set_ylabel("Global M2 Index (linear)", color="#333333")
|
||||
ax2.tick_params(axis="y", colors="#333333")
|
||||
# (No grid on ax2 to keep it clean.)
|
||||
|
||||
ax1.set_xlim(common_start, common_end + relativedelta(years=extend_years))
|
||||
ax1.xaxis.set_major_locator(mdates.YearLocator(base=5))
|
||||
ax1.xaxis.set_minor_locator(mdates.YearLocator())
|
||||
ax1.xaxis.set_major_formatter(mdates.DateFormatter("%Y"))
|
||||
# 7) Extend x‐axis to give “future blank space”
|
||||
x_end_extended = common_end + relativedelta(years=extend_years)
|
||||
ax1.set_xlim(common_start, x_end_extended)
|
||||
|
||||
# 8) Monthly x-ticks (e.g., “Aug, Sep, Oct…”)
|
||||
ax1.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
|
||||
ax1.xaxis.set_major_formatter(mdates.DateFormatter("%b"))
|
||||
ax1.xaxis.set_minor_locator(mdates.MonthLocator(bymonthday=15))
|
||||
plt.setp(ax1.get_xticklabels(), rotation=0, ha="center")
|
||||
|
||||
# 9) Combine legends from both axes
|
||||
lines1, labels1 = ax1.get_legend_handles_labels()
|
||||
lines2, labels2 = ax2.get_legend_handles_labels()
|
||||
ax1.legend(lines1 + lines2, labels1 + labels2, loc="upper left", frameon=False)
|
||||
ax1.legend(
|
||||
lines1 + lines2,
|
||||
labels1 + labels2,
|
||||
loc="upper left",
|
||||
frameon=False,
|
||||
fontsize=10,
|
||||
)
|
||||
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
@@ -127,7 +162,7 @@ def main(argv: list[str] | None = None) -> plt.Figure:
|
||||
p.add_argument(
|
||||
"--offset",
|
||||
type=int,
|
||||
default=94,
|
||||
default=94, # M2 leads by 94 days
|
||||
help="shift M2 by this many days (positive = M2 leads)",
|
||||
)
|
||||
p.add_argument(
|
||||
@@ -162,17 +197,16 @@ def main(argv: list[str] | None = None) -> plt.Figure:
|
||||
|
||||
btc, m2 = fetch_series(start_dt, end_dt, args.btc_series, args.m2_series, args.db)
|
||||
|
||||
# ─── Drop any rows where the FRED tables had NULLs in "value" ──────────────────
|
||||
# Drop rows with null “value”
|
||||
btc = btc.dropna(subset=["value"])
|
||||
m2 = m2.dropna(subset=["value"])
|
||||
# ────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
validate_series(btc, m2, args.btc_series, args.m2_series)
|
||||
fig = plot_bitcoin_m2(
|
||||
btc,
|
||||
m2,
|
||||
args.offset,
|
||||
args.extend_years,
|
||||
offset_days=args.offset,
|
||||
extend_years=args.extend_years,
|
||||
width=args.width,
|
||||
height=args.height,
|
||||
dpi=args.dpi,
|
||||
|
||||
Reference in new issue
Block a user