diff --git a/plots/ohlc-bar/implementations/python/highcharts.py b/plots/ohlc-bar/implementations/python/highcharts.py
index 6210e0a9c8..7949d2dce8 100644
--- a/plots/ohlc-bar/implementations/python/highcharts.py
+++ b/plots/ohlc-bar/implementations/python/highcharts.py
@@ -1,10 +1,11 @@
-""" pyplots.ai
+""" anyplot.ai
ohlc-bar: OHLC Bar Chart
-Library: highcharts unknown | Python 3.13.11
-Quality: 91/100 | Created: 2026-01-09
+Library: highcharts unknown | Python 3.13.13
+Quality: 88/100 | Updated: 2026-05-23
"""
import json
+import os
import tempfile
import time
import urllib.request
@@ -12,18 +13,29 @@
from pathlib import Path
import numpy as np
+from PIL import Image
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
-# Data - 50 trading days of simulated stock prices
+# Theme tokens
+THEME = os.getenv("ANYPLOT_THEME", "light")
+PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
+ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
+INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
+INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
+GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
+
+# Semantic exception: finance — green=bullish/up, red=bearish/down (anyplot positions 1 and 3)
+UP_COLOR = "#009E73"
+DOWN_COLOR = "#B71D27"
+
+# Data — 50 trading days of simulated stock prices
np.random.seed(42)
-# Start price and generate OHLC data
start_price = 150.0
n_days = 50
-# Generate realistic stock movements
opens = [start_price]
highs = []
lows = []
@@ -34,9 +46,8 @@
if i > 0:
opens.append(open_price)
- # Daily volatility
daily_range = abs(np.random.randn() * 2) + 0.5
- direction = np.random.choice([-1, 1], p=[0.48, 0.52]) # Slight bullish bias
+ direction = np.random.choice([-1, 1], p=[0.48, 0.52])
close_price = open_price + direction * np.random.rand() * daily_range
high_price = max(open_price, close_price) + abs(np.random.randn() * 0.8)
@@ -48,110 +59,133 @@
opens = [round(o, 2) for o in opens]
-# Generate dates (trading days, skip weekends)
+# 5-day SMA for trend layer
+sma_period = 5
+sma_closes = []
+for i in range(sma_period - 1, n_days):
+ avg = sum(closes[i - sma_period + 1 : i + 1]) / sma_period
+ sma_closes.append(round(avg, 2))
+
+# Generate trading dates (skip weekends)
start_date = datetime(2024, 6, 1)
dates = []
current_date = start_date
while len(dates) < n_days:
- if current_date.weekday() < 5: # Monday to Friday
+ if current_date.weekday() < 5:
dates.append(current_date)
current_date += timedelta(days=1)
-# Format data for Highcharts: [timestamp, open, high, low, close]
+# Format as Highcharts timestamps: [timestamp_ms, open, high, low, close]
ohlc_data = []
for i in range(n_days):
- timestamp = int(dates[i].timestamp() * 1000) # JavaScript timestamp in ms
+ timestamp = int(dates[i].timestamp() * 1000)
ohlc_data.append([timestamp, opens[i], highs[i], lows[i], closes[i]])
-# Chart options for Highcharts Stock OHLC chart
-# Using colorblind-safe palette: Python Blue for down bars, Python Yellow for up bars
+sma_data = []
+for i in range(sma_period - 1, n_days):
+ timestamp = int(dates[i].timestamp() * 1000)
+ sma_data.append([timestamp, sma_closes[i - (sma_period - 1)]])
+
+# Net-change subtitle for data storytelling
+net_change = closes[-1] - opens[0]
+pct_change = net_change / opens[0] * 100
+sign = "+" if net_change >= 0 else ""
+subtitle_text = f"Jun–Aug 2024 · Net change: {sign}${net_change:.2f} / {sign}{pct_change:.1f}%"
+
+title = "ohlc-bar · python · highcharts · anyplot.ai"
+
chart_options = {
"chart": {
"type": "ohlc",
- "width": 4800,
- "height": 2700,
- "backgroundColor": "#ffffff",
- "marginBottom": 220,
- "marginLeft": 250,
+ "width": 3200,
+ "height": 1800,
+ "backgroundColor": PAGE_BG,
+ "plotBorderWidth": 0,
+ "marginBottom": 160,
+ "marginLeft": 200,
"marginRight": 80,
"marginTop": 150,
- "style": {"fontFamily": "Arial, sans-serif"},
- },
- "title": {
- "text": "ohlc-bar · highcharts · pyplots.ai",
- "style": {"fontSize": "72px", "fontWeight": "bold", "color": "#333333"},
- "y": 60,
+ "style": {"fontFamily": "Arial, sans-serif", "color": INK},
},
+ "title": {"text": title, "style": {"fontSize": "66px", "fontWeight": "600", "color": INK}, "y": 55},
+ "subtitle": {"text": subtitle_text, "style": {"fontSize": "36px", "color": INK_SOFT}, "y": 110},
"xAxis": {
"type": "datetime",
- "title": {"text": "Date", "style": {"fontSize": "52px", "color": "#333333"}, "margin": 30},
- "labels": {
- "style": {"fontSize": "36px", "color": "#333333"},
- "format": "{value:%b %d}",
- "y": 45,
- "step": 3, # Show every 3rd label to prevent overlap
- },
+ "title": {"text": "Date", "style": {"fontSize": "56px", "color": INK}, "margin": 25},
+ "labels": {"style": {"fontSize": "44px", "color": INK_SOFT}, "format": "{value:%b %d}", "y": 40, "step": 3},
"gridLineWidth": 1,
- "gridLineColor": "rgba(0, 0, 0, 0.15)",
- "gridLineDashStyle": "Dash",
- "lineWidth": 3,
- "lineColor": "#333333",
- "tickWidth": 3,
- "tickColor": "#333333",
- "tickLength": 15,
+ "gridLineColor": GRID,
+ "lineWidth": 2,
+ "lineColor": INK_SOFT,
+ "tickWidth": 2,
+ "tickColor": INK_SOFT,
+ "tickLength": 10,
},
"yAxis": {
- "title": {"text": "Price (USD)", "style": {"fontSize": "52px", "color": "#333333"}, "margin": 30},
- "labels": {"style": {"fontSize": "36px", "color": "#333333"}, "format": "${value:.0f}", "x": -15},
+ "title": {"text": "Price (USD)", "style": {"fontSize": "56px", "color": INK}, "margin": 25},
+ "labels": {"style": {"fontSize": "44px", "color": INK_SOFT}, "format": "${value:.0f}", "x": -15},
"gridLineWidth": 1,
- "gridLineColor": "rgba(0, 0, 0, 0.15)",
- "gridLineDashStyle": "Dash",
- "lineWidth": 3,
- "lineColor": "#333333",
- "opposite": False, # Keep Y-axis on left side only
+ "gridLineColor": GRID,
+ "lineWidth": 2,
+ "lineColor": INK_SOFT,
+ "opposite": False,
+ },
+ "legend": {
+ "enabled": True,
+ "itemStyle": {"color": INK_SOFT, "fontSize": "36px", "fontWeight": "normal"},
+ "backgroundColor": "transparent",
+ "borderWidth": 0,
+ "align": "right",
+ "verticalAlign": "top",
+ "layout": "vertical",
+ "x": -80,
+ "y": 160,
},
- "legend": {"enabled": False},
"tooltip": {
"split": False,
- "style": {"fontSize": "28px"},
+ "style": {"fontSize": "32px"},
"headerFormat": "{point.x:%b %d, %Y}
",
"pointFormat": "Open: ${point.open:.2f}
"
+ "High: ${point.high:.2f}
"
+ "Low: ${point.low:.2f}
"
+ "Close: ${point.close:.2f}",
},
- "plotOptions": {
- "ohlc": {
- # Colorblind-safe: Python Yellow for up bars, Python Blue for down bars
- "color": "#306998", # Python Blue for down bars (close < open)
- "upColor": "#FFD43B", # Python Yellow for up bars (close > open)
- "lineWidth": 5, # Bar line width - visible at large size
- }
- },
+ "plotOptions": {"ohlc": {"color": DOWN_COLOR, "upColor": UP_COLOR, "lineWidth": 4}},
"rangeSelector": {"enabled": False},
"navigator": {"enabled": False},
"scrollbar": {"enabled": False},
"credits": {"enabled": False},
- "series": [{"type": "ohlc", "name": "Stock Price", "data": ohlc_data}],
+ "series": [
+ {"type": "ohlc", "name": "Stock Price", "data": ohlc_data},
+ {
+ "type": "line",
+ "name": "5-day SMA",
+ "data": sma_data,
+ "color": INK_SOFT,
+ "lineWidth": 2,
+ "dashStyle": "Dash",
+ "enableMouseTracking": False,
+ "marker": {"enabled": False},
+ },
+ ],
}
-# Download Highstock JS (includes OHLC support)
-highstock_url = "https://code.highcharts.com/stock/highstock.js"
-with urllib.request.urlopen(highstock_url, timeout=30) as response:
+# Download Highstock JS (OHLC type lives in Highstock)
+highstock_url = "https://cdn.jsdelivr.net/npm/highcharts/highstock.js"
+req = urllib.request.Request(highstock_url, headers={"User-Agent": "Mozilla/5.0"})
+with urllib.request.urlopen(req, timeout=30) as response:
highstock_js = response.read().decode("utf-8")
-# Generate chart options JSON
chart_options_json = json.dumps(chart_options)
-# Generate HTML with inline scripts
html_content = f"""