|
| 1 | +"""pyplots.ai |
| 2 | +indicator-bollinger: Bollinger Bands Indicator Chart |
| 3 | +Library: altair | Python 3.13 |
| 4 | +Quality: pending | Created: 2025-01-07 |
| 5 | +""" |
| 6 | + |
| 7 | +import altair as alt |
| 8 | +import numpy as np |
| 9 | +import pandas as pd |
| 10 | + |
| 11 | + |
| 12 | +# Data - Generate realistic stock price data with Bollinger Bands |
| 13 | +np.random.seed(42) |
| 14 | + |
| 15 | +# Generate 120 trading days of price data |
| 16 | +n_periods = 120 |
| 17 | +dates = pd.date_range("2024-01-02", periods=n_periods, freq="B") # Business days |
| 18 | + |
| 19 | +# Generate realistic price movement (random walk with drift) |
| 20 | +returns = np.random.normal(0.001, 0.02, n_periods) # Small positive drift, 2% daily volatility |
| 21 | +price_base = 150.0 |
| 22 | +close_prices = price_base * np.cumprod(1 + returns) |
| 23 | + |
| 24 | +# Calculate Bollinger Bands (20-period SMA, 2 standard deviations) |
| 25 | +window = 20 |
| 26 | +df = pd.DataFrame({"date": dates, "close": close_prices}) |
| 27 | +df["sma"] = df["close"].rolling(window=window).mean() |
| 28 | +df["std"] = df["close"].rolling(window=window).std() |
| 29 | +df["upper_band"] = df["sma"] + 2 * df["std"] |
| 30 | +df["lower_band"] = df["sma"] - 2 * df["std"] |
| 31 | + |
| 32 | +# Drop NaN values from rolling calculation |
| 33 | +df = df.dropna().reset_index(drop=True) |
| 34 | + |
| 35 | +# Create base chart |
| 36 | +base = alt.Chart(df).encode(x=alt.X("date:T", title="Date", axis=alt.Axis(format="%b %Y", labelAngle=-45))) |
| 37 | + |
| 38 | +# Upper band line |
| 39 | +upper_line = base.mark_line(strokeWidth=2, color="#306998", opacity=0.7).encode( |
| 40 | + y=alt.Y("upper_band:Q", title="Price ($)", scale=alt.Scale(zero=False)) |
| 41 | +) |
| 42 | + |
| 43 | +# Lower band line |
| 44 | +lower_line = base.mark_line(strokeWidth=2, color="#306998", opacity=0.7).encode( |
| 45 | + y=alt.Y("lower_band:Q", scale=alt.Scale(zero=False)) |
| 46 | +) |
| 47 | + |
| 48 | +# Band fill area (between upper and lower bands) |
| 49 | +band_area = ( |
| 50 | + alt.Chart(df) |
| 51 | + .mark_area(opacity=0.15, color="#306998") |
| 52 | + .encode(x=alt.X("date:T"), y=alt.Y("upper_band:Q", scale=alt.Scale(zero=False)), y2="lower_band:Q") |
| 53 | +) |
| 54 | + |
| 55 | +# Middle band (SMA) - dashed line |
| 56 | +sma_line = base.mark_line(strokeWidth=2.5, strokeDash=[8, 4], color="#306998", opacity=0.9).encode( |
| 57 | + y=alt.Y("sma:Q", scale=alt.Scale(zero=False)) |
| 58 | +) |
| 59 | + |
| 60 | +# Close price line - prominent |
| 61 | +price_line = base.mark_line(strokeWidth=3, color="#FFD43B").encode(y=alt.Y("close:Q", scale=alt.Scale(zero=False))) |
| 62 | + |
| 63 | +# Combine all layers |
| 64 | +chart = ( |
| 65 | + alt.layer(band_area, upper_line, lower_line, sma_line, price_line) |
| 66 | + .properties( |
| 67 | + width=1600, |
| 68 | + height=900, |
| 69 | + title=alt.Title(text="indicator-bollinger · altair · pyplots.ai", fontSize=28, anchor="middle"), |
| 70 | + ) |
| 71 | + .configure_axis(labelFontSize=18, titleFontSize=22, gridOpacity=0.3) |
| 72 | + .configure_view(strokeWidth=0) |
| 73 | + .configure_legend(titleFontSize=18, labelFontSize=16) |
| 74 | +) |
| 75 | + |
| 76 | +# Save as PNG and HTML |
| 77 | +chart.save("plot.png", scale_factor=3.0) |
| 78 | +chart.save("plot.html") |
0 commit comments