|
| 1 | +"""pyplots.ai |
| 2 | +ohlc-bar: OHLC Bar Chart |
| 3 | +Library: highcharts | Python 3.13 |
| 4 | +Quality: pending | Created: 2026-01-08 |
| 5 | +""" |
| 6 | + |
| 7 | +import json |
| 8 | +import tempfile |
| 9 | +import time |
| 10 | +import urllib.request |
| 11 | +from datetime import datetime, timedelta |
| 12 | +from pathlib import Path |
| 13 | + |
| 14 | +import numpy as np |
| 15 | +from selenium import webdriver |
| 16 | +from selenium.webdriver.chrome.options import Options |
| 17 | + |
| 18 | + |
| 19 | +# Data - 45 trading days of simulated stock prices |
| 20 | +np.random.seed(42) |
| 21 | + |
| 22 | +# Start price and generate OHLC data |
| 23 | +start_price = 125.0 |
| 24 | +n_days = 45 |
| 25 | + |
| 26 | +# Generate realistic stock movements |
| 27 | +opens = [start_price] |
| 28 | +highs = [] |
| 29 | +lows = [] |
| 30 | +closes = [] |
| 31 | + |
| 32 | +for i in range(n_days): |
| 33 | + open_price = opens[i] if i == 0 else closes[i - 1] + np.random.randn() * 0.3 |
| 34 | + if i > 0: |
| 35 | + opens.append(open_price) |
| 36 | + |
| 37 | + # Daily volatility |
| 38 | + daily_range = abs(np.random.randn() * 1.5) + 0.5 |
| 39 | + direction = np.random.choice([-1, 1], p=[0.48, 0.52]) # Slight bullish bias |
| 40 | + |
| 41 | + close_price = open_price + direction * np.random.rand() * daily_range |
| 42 | + high_price = max(open_price, close_price) + abs(np.random.randn() * 0.4) |
| 43 | + low_price = min(open_price, close_price) - abs(np.random.randn() * 0.4) |
| 44 | + |
| 45 | + highs.append(round(high_price, 2)) |
| 46 | + lows.append(round(low_price, 2)) |
| 47 | + closes.append(round(close_price, 2)) |
| 48 | + |
| 49 | +opens = [round(o, 2) for o in opens] |
| 50 | + |
| 51 | +# Generate dates (trading days, skip weekends) |
| 52 | +start_date = datetime(2024, 9, 1) |
| 53 | +dates = [] |
| 54 | +current_date = start_date |
| 55 | +while len(dates) < n_days: |
| 56 | + if current_date.weekday() < 5: # Monday to Friday |
| 57 | + dates.append(current_date) |
| 58 | + current_date += timedelta(days=1) |
| 59 | + |
| 60 | +# Format data for Highcharts: [timestamp, open, high, low, close] |
| 61 | +ohlc_data = [] |
| 62 | +for i in range(n_days): |
| 63 | + timestamp = int(dates[i].timestamp() * 1000) # JavaScript timestamp in ms |
| 64 | + ohlc_data.append([timestamp, opens[i], highs[i], lows[i], closes[i]]) |
| 65 | + |
| 66 | +# Chart options for Highcharts Stock OHLC chart |
| 67 | +# Using colorblind-safe palette: Python Blue for bullish, warm amber for bearish |
| 68 | +chart_options = { |
| 69 | + "chart": { |
| 70 | + "type": "ohlc", |
| 71 | + "width": 4800, |
| 72 | + "height": 2700, |
| 73 | + "backgroundColor": "#ffffff", |
| 74 | + "marginBottom": 220, |
| 75 | + "marginLeft": 250, |
| 76 | + "marginRight": 80, |
| 77 | + "marginTop": 150, |
| 78 | + "style": {"fontFamily": "Arial, sans-serif"}, |
| 79 | + }, |
| 80 | + "title": { |
| 81 | + "text": "Stock Price Analysis · ohlc-bar · highcharts · pyplots.ai", |
| 82 | + "style": {"fontSize": "72px", "fontWeight": "bold", "color": "#333333"}, |
| 83 | + "y": 60, |
| 84 | + }, |
| 85 | + "xAxis": { |
| 86 | + "type": "datetime", |
| 87 | + "title": {"text": "Date", "style": {"fontSize": "52px", "color": "#333333"}, "margin": 30}, |
| 88 | + "labels": { |
| 89 | + "style": {"fontSize": "36px", "color": "#333333"}, |
| 90 | + "format": "{value:%b %d}", |
| 91 | + "y": 45, |
| 92 | + "step": 3, # Show every 3rd label to prevent overlap with more data points |
| 93 | + }, |
| 94 | + "gridLineWidth": 1, |
| 95 | + "gridLineColor": "rgba(0, 0, 0, 0.15)", |
| 96 | + "gridLineDashStyle": "Dash", |
| 97 | + "lineWidth": 3, |
| 98 | + "lineColor": "#333333", |
| 99 | + "tickWidth": 3, |
| 100 | + "tickColor": "#333333", |
| 101 | + "tickLength": 15, |
| 102 | + }, |
| 103 | + "yAxis": { |
| 104 | + "title": {"text": "Price (USD)", "style": {"fontSize": "52px", "color": "#333333"}, "margin": 30}, |
| 105 | + "labels": {"style": {"fontSize": "36px", "color": "#333333"}, "format": "${value:.0f}", "x": -15}, |
| 106 | + "gridLineWidth": 1, |
| 107 | + "gridLineColor": "rgba(0, 0, 0, 0.15)", |
| 108 | + "gridLineDashStyle": "Dash", |
| 109 | + "lineWidth": 3, |
| 110 | + "lineColor": "#333333", |
| 111 | + "opposite": False, # Keep Y-axis on left side only |
| 112 | + "tickInterval": 2, # Show labels at $2 intervals to avoid duplicates |
| 113 | + }, |
| 114 | + "legend": {"enabled": False}, |
| 115 | + "tooltip": { |
| 116 | + "split": False, |
| 117 | + "style": {"fontSize": "28px"}, |
| 118 | + "headerFormat": "<b>{point.x:%b %d, %Y}</b><br/>", |
| 119 | + "pointFormat": "Open: ${point.open:.2f}<br/>" |
| 120 | + + "High: ${point.high:.2f}<br/>" |
| 121 | + + "Low: ${point.low:.2f}<br/>" |
| 122 | + + "Close: ${point.close:.2f}", |
| 123 | + }, |
| 124 | + "plotOptions": { |
| 125 | + "ohlc": { |
| 126 | + # Colorblind-safe: Python Blue for bullish, warm amber for bearish |
| 127 | + "color": "#E67E22", # Warm amber for bearish (close < open) |
| 128 | + "upColor": "#306998", # Python Blue for bullish (close > open) |
| 129 | + "lineWidth": 5, # Bar line width - visible at large size |
| 130 | + } |
| 131 | + }, |
| 132 | + "rangeSelector": {"enabled": False}, |
| 133 | + "navigator": {"enabled": False}, |
| 134 | + "scrollbar": {"enabled": False}, |
| 135 | + "credits": {"enabled": False}, |
| 136 | + "series": [{"type": "ohlc", "name": "Stock Price", "data": ohlc_data}], |
| 137 | +} |
| 138 | + |
| 139 | +# Download Highstock JS (includes OHLC support) |
| 140 | +highstock_url = "https://code.highcharts.com/stock/highstock.js" |
| 141 | +with urllib.request.urlopen(highstock_url, timeout=30) as response: |
| 142 | + highstock_js = response.read().decode("utf-8") |
| 143 | + |
| 144 | +# Generate chart options JSON |
| 145 | +chart_options_json = json.dumps(chart_options) |
| 146 | + |
| 147 | +# Generate HTML with inline scripts |
| 148 | +html_content = f"""<!DOCTYPE html> |
| 149 | +<html> |
| 150 | +<head> |
| 151 | + <meta charset="utf-8"> |
| 152 | + <script>{highstock_js}</script> |
| 153 | +</head> |
| 154 | +<body style="margin:0; background-color: #ffffff;"> |
| 155 | + <div id="container" style="width: 4800px; height: 2700px;"></div> |
| 156 | + <script> |
| 157 | + document.addEventListener('DOMContentLoaded', function() {{ |
| 158 | + Highcharts.stockChart('container', {chart_options_json}); |
| 159 | + }}); |
| 160 | + </script> |
| 161 | +</body> |
| 162 | +</html>""" |
| 163 | + |
| 164 | +# Write temp HTML file |
| 165 | +with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: |
| 166 | + f.write(html_content) |
| 167 | + temp_path = f.name |
| 168 | + |
| 169 | +# Also save the HTML for interactive viewing |
| 170 | +with open("plot.html", "w", encoding="utf-8") as f: |
| 171 | + f.write(html_content) |
| 172 | + |
| 173 | +# Take screenshot with headless Chrome |
| 174 | +chrome_options = Options() |
| 175 | +chrome_options.add_argument("--headless") |
| 176 | +chrome_options.add_argument("--no-sandbox") |
| 177 | +chrome_options.add_argument("--disable-dev-shm-usage") |
| 178 | +chrome_options.add_argument("--disable-gpu") |
| 179 | +chrome_options.add_argument("--window-size=4800,2700") |
| 180 | + |
| 181 | +driver = webdriver.Chrome(options=chrome_options) |
| 182 | +driver.get(f"file://{temp_path}") |
| 183 | +time.sleep(5) |
| 184 | +driver.save_screenshot("plot.png") |
| 185 | +driver.quit() |
| 186 | + |
| 187 | +# Clean up temp file |
| 188 | +Path(temp_path).unlink() |
0 commit comments