{"spec_id":"indicator-macd","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nindicator-macd: MACD Technical Indicator Chart\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-16\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Legend, Span\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Okabe-Ito palette for MACD components\nHIST_POSITIVE = \"#009E73\"  # Position 1 - brand green\nHIST_NEGATIVE = \"#AE3030\"  # imprint red — bars below zero\nMACD_LINE_COLOR = \"#4467A3\"  # Position 3 - blue\nSIGNAL_LINE_COLOR = \"#BD8233\"  # imprint ochre — distinct from histogram bars\n\n# Data - Generate synthetic stock price data and calculate MACD\nnp.random.seed(42)\nn_days = 150\n\n# Generate realistic price movement with trend and volatility\nreturns = np.random.normal(0.001, 0.02, n_days)\nprice = 100 * np.cumprod(1 + returns)\n\n# Calculate EMAs\ndf = pd.DataFrame({\"date\": pd.date_range(\"2025-06-01\", periods=n_days, freq=\"D\"), \"close\": price})\n\n# Calculate 12-day and 26-day EMA\ndf[\"ema12\"] = df[\"close\"].ewm(span=12, adjust=False).mean()\ndf[\"ema26\"] = df[\"close\"].ewm(span=26, adjust=False).mean()\n\n# Calculate MACD line (12-day EMA - 26-day EMA)\ndf[\"macd\"] = df[\"ema12\"] - df[\"ema26\"]\n\n# Calculate signal line (9-day EMA of MACD)\ndf[\"signal\"] = df[\"macd\"].ewm(span=9, adjust=False).mean()\n\n# Calculate histogram (MACD - Signal)\ndf[\"histogram\"] = df[\"macd\"] - df[\"signal\"]\n\n# Use data from day 35 onwards for meaningful MACD values\ndf = df.iloc[35:].reset_index(drop=True)\n\n# Separate positive and negative histogram values for coloring\ndf[\"hist_positive\"] = df[\"histogram\"].where(df[\"histogram\"] >= 0, 0)\ndf[\"hist_negative\"] = df[\"histogram\"].where(df[\"histogram\"] < 0, 0)\n\n# Format date for display\ndf[\"date_str\"] = df[\"date\"].dt.strftime(\"%Y-%m-%d\")\n\n# Create ColumnDataSource\nsource = ColumnDataSource(\n    data={\n        \"date\": df[\"date\"],\n        \"date_str\": df[\"date_str\"],\n        \"macd\": df[\"macd\"],\n        \"signal\": df[\"signal\"],\n        \"histogram\": df[\"histogram\"],\n        \"hist_positive\": df[\"hist_positive\"],\n        \"hist_negative\": df[\"hist_negative\"],\n    }\n)\n\n# Plot\np = figure(\n    width=4800,\n    height=2700,\n    x_axis_type=\"datetime\",\n    title=\"indicator-macd · bokeh · anyplot.ai\",\n    x_axis_label=\"Date\",\n    y_axis_label=\"MACD Value\",\n)\n\n# Calculate bar width (1 day in milliseconds, slightly narrower for gaps)\nbar_width = 0.8 * 24 * 60 * 60 * 1000\n\n# Plot histogram bars - positive (Okabe-Ito green)\nhist_pos = p.vbar(\n    x=\"date\",\n    top=\"hist_positive\",\n    width=bar_width,\n    source=source,\n    fill_color=HIST_POSITIVE,\n    line_color=HIST_POSITIVE,\n    line_width=1,\n    alpha=0.8,\n)\n\n# Plot histogram bars - negative (Okabe-Ito orange)\nhist_neg = p.vbar(\n    x=\"date\",\n    top=\"hist_negative\",\n    width=bar_width,\n    source=source,\n    fill_color=HIST_NEGATIVE,\n    line_color=HIST_NEGATIVE,\n    line_width=1,\n    alpha=0.8,\n)\n\n# Plot MACD line (Okabe-Ito blue)\nmacd_line = p.line(x=\"date\", y=\"macd\", source=source, line_color=MACD_LINE_COLOR, line_width=4, alpha=0.9)\n\n# Plot signal line (Okabe-Ito orange)\nsignal_line = p.line(x=\"date\", y=\"signal\", source=source, line_color=SIGNAL_LINE_COLOR, line_width=4, alpha=0.9)\n\n# Add zero reference line\nzero_line = Span(location=0, dimension=\"width\", line_color=INK_SOFT, line_dash=\"dashed\", line_width=2, line_alpha=0.5)\np.add_layout(zero_line)\n\n# Create legend\nlegend = Legend(\n    items=[\n        (\"MACD Line (12-26)\", [macd_line]),\n        (\"Signal Line (9)\", [signal_line]),\n        (\"Histogram (+)\", [hist_pos]),\n        (\"Histogram (-)\", [hist_neg]),\n    ],\n    location=\"top_left\",\n)\nlegend.label_text_font_size = \"22pt\"\nlegend.spacing = 10\nlegend.background_fill_color = ELEVATED_BG\nlegend.background_fill_alpha = 0.9\nlegend.border_line_color = INK_SOFT\nlegend.label_text_color = INK_SOFT\np.add_layout(legend)\n\n# Add HoverTool for interactivity\nhover = HoverTool(\n    tooltips=[\n        (\"Date\", \"@date_str\"),\n        (\"MACD\", \"@macd{0.000}\"),\n        (\"Signal\", \"@signal{0.000}\"),\n        (\"Histogram\", \"@histogram{0.000}\"),\n    ]\n)\np.add_tools(hover)\n\n# Style\np.title.text_font_size = \"28pt\"\np.title.text_color = INK\n\np.xaxis.axis_label_text_font_size = \"22pt\"\np.yaxis.axis_label_text_font_size = \"22pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\n\np.xaxis.major_label_text_font_size = \"18pt\"\np.yaxis.major_label_text_font_size = \"18pt\"\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\n\n# Grid styling - subtle\np.xgrid.grid_line_alpha = 0.10\np.ygrid.grid_line_alpha = 0.10\np.xgrid.grid_line_color = INK\np.ygrid.grid_line_color = INK\n\n# Backgrounds and borders (theme-adaptive)\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\np.outline_line_width = 1\n\n# Axis styling\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.axis_line_width = 1\np.yaxis.axis_line_width = 1\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\n\n# Hide toolbar\np.toolbar_location = None\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome\nW, H = 4800, 2700\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)  # let bokeh's JS render the canvas\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}