|
1 | 1 | """ pyplots.ai |
2 | 2 | candlestick-basic: Basic Candlestick Chart |
3 | | -Library: highcharts unknown | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: highcharts 1.10.3 | Python 3.14.3 |
| 4 | +Quality: /100 | Updated: 2026-02-24 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import json |
|
19 | 19 | # Data - 30 trading days of simulated stock prices |
20 | 20 | np.random.seed(42) |
21 | 21 |
|
22 | | -# Start price and generate OHLC data |
23 | 22 | start_price = 150.0 |
24 | 23 | n_days = 30 |
25 | 24 |
|
26 | | -# Generate realistic stock movements |
27 | 25 | opens = [start_price] |
28 | 26 | highs = [] |
29 | 27 | lows = [] |
|
34 | 32 | if i > 0: |
35 | 33 | opens.append(open_price) |
36 | 34 |
|
37 | | - # Daily volatility |
38 | 35 | daily_range = abs(np.random.randn() * 2) + 1 |
39 | | - direction = np.random.choice([-1, 1], p=[0.45, 0.55]) # Slight bullish bias |
| 36 | + direction = np.random.choice([-1, 1], p=[0.45, 0.55]) |
40 | 37 |
|
41 | 38 | close_price = open_price + direction * np.random.rand() * daily_range |
42 | 39 | high_price = max(open_price, close_price) + abs(np.random.randn() * 0.5) |
|
53 | 50 | dates = [] |
54 | 51 | current_date = start_date |
55 | 52 | while len(dates) < n_days: |
56 | | - if current_date.weekday() < 5: # Monday to Friday |
| 53 | + if current_date.weekday() < 5: |
57 | 54 | dates.append(current_date) |
58 | 55 | current_date += timedelta(days=1) |
59 | 56 |
|
60 | 57 | # Format data for Highcharts: [timestamp, open, high, low, close] |
61 | 58 | ohlc_data = [] |
62 | 59 | for i in range(n_days): |
63 | | - timestamp = int(dates[i].timestamp() * 1000) # JavaScript timestamp in ms |
| 60 | + timestamp = int(dates[i].timestamp() * 1000) |
64 | 61 | ohlc_data.append([timestamp, opens[i], highs[i], lows[i], closes[i]]) |
65 | 62 |
|
66 | | -# Chart options for Highcharts Stock candlestick |
67 | | -# Using colorblind-safe palette: Python Blue for bullish, warm amber for bearish |
| 63 | +# Chart options |
68 | 64 | chart_options = { |
69 | 65 | "chart": { |
70 | 66 | "type": "candlestick", |
71 | 67 | "width": 4800, |
72 | 68 | "height": 2700, |
73 | 69 | "backgroundColor": "#ffffff", |
74 | | - "marginBottom": 220, |
75 | | - "marginLeft": 250, |
76 | | - "marginRight": 80, |
77 | | - "marginTop": 150, |
| 70 | + "marginBottom": 200, |
| 71 | + "marginLeft": 240, |
| 72 | + "marginRight": 100, |
| 73 | + "marginTop": 140, |
78 | 74 | "style": {"fontFamily": "Arial, sans-serif"}, |
79 | 75 | }, |
80 | 76 | "title": { |
81 | | - "text": "Stock Price Movement · candlestick-basic · highcharts · pyplots.ai", |
82 | | - "style": {"fontSize": "72px", "fontWeight": "bold", "color": "#333333"}, |
83 | | - "y": 60, |
| 77 | + "text": "Stock Price Movement \u00b7 candlestick-basic \u00b7 highcharts \u00b7 pyplots.ai", |
| 78 | + "style": {"fontSize": "68px", "fontWeight": "bold", "color": "#2c2c2c"}, |
| 79 | + "y": 55, |
84 | 80 | }, |
85 | 81 | "xAxis": { |
86 | 82 | "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": 2, # Show every 2nd label to prevent overlap |
93 | | - }, |
94 | | - "gridLineWidth": 1, |
95 | | - "gridLineColor": "rgba(0, 0, 0, 0.15)", |
96 | | - "gridLineDashStyle": "Dash", |
97 | | - "lineWidth": 3, |
| 83 | + "title": {"text": "Date", "style": {"fontSize": "48px", "color": "#333333"}, "margin": 25}, |
| 84 | + "labels": {"style": {"fontSize": "36px", "color": "#555555"}, "format": "{value:%b %d}", "y": 40, "step": 2}, |
| 85 | + "gridLineWidth": 0, |
| 86 | + "lineWidth": 2, |
98 | 87 | "lineColor": "#333333", |
99 | | - "tickWidth": 3, |
100 | | - "tickColor": "#333333", |
101 | | - "tickLength": 15, |
| 88 | + "tickWidth": 0, |
102 | 89 | }, |
103 | 90 | "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}, |
| 91 | + "title": {"text": "Price (USD)", "style": {"fontSize": "48px", "color": "#333333"}, "margin": 25}, |
| 92 | + "labels": {"style": {"fontSize": "36px", "color": "#555555"}, "format": "${value:.0f}", "x": -15}, |
106 | 93 | "gridLineWidth": 1, |
107 | | - "gridLineColor": "rgba(0, 0, 0, 0.15)", |
108 | | - "gridLineDashStyle": "Dash", |
109 | | - "lineWidth": 3, |
| 94 | + "gridLineColor": "rgba(0, 0, 0, 0.20)", |
| 95 | + "lineWidth": 2, |
110 | 96 | "lineColor": "#333333", |
111 | | - "opposite": False, # Keep Y-axis on left side only |
112 | | - "tickInterval": 1, # Show labels at $1 intervals |
| 97 | + "opposite": False, |
| 98 | + "tickWidth": 0, |
| 99 | + "tickInterval": 2, |
113 | 100 | }, |
114 | 101 | "legend": {"enabled": False}, |
115 | 102 | "tooltip": { |
|
123 | 110 | }, |
124 | 111 | "plotOptions": { |
125 | 112 | "candlestick": { |
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 | | - "lineColor": "#C0392B", # Darker line for bearish wicks |
130 | | - "upLineColor": "#1A3A5C", # Darker blue for bullish wicks |
131 | | - "lineWidth": 4, # Wick line width - visible at large size |
132 | | - "pointWidth": 70, # Candle body width |
| 113 | + "color": "#E67E22", |
| 114 | + "upColor": "#306998", |
| 115 | + "lineColor": "#C0392B", |
| 116 | + "upLineColor": "#1A3A5C", |
| 117 | + "lineWidth": 4, |
| 118 | + "pointWidth": 70, |
133 | 119 | } |
134 | 120 | }, |
135 | 121 | "rangeSelector": {"enabled": False}, |
|
144 | 130 | with urllib.request.urlopen(highstock_url, timeout=30) as response: |
145 | 131 | highstock_js = response.read().decode("utf-8") |
146 | 132 |
|
147 | | -# Generate chart options JSON |
148 | 133 | chart_options_json = json.dumps(chart_options) |
149 | 134 |
|
150 | | -# Generate HTML with inline scripts |
| 135 | +# Render |
151 | 136 | html_content = f"""<!DOCTYPE html> |
152 | 137 | <html> |
153 | 138 | <head> |
|
164 | 149 | </body> |
165 | 150 | </html>""" |
166 | 151 |
|
167 | | -# Write temp HTML file |
168 | 152 | with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: |
169 | 153 | f.write(html_content) |
170 | 154 | temp_path = f.name |
171 | 155 |
|
172 | | -# Also save the HTML for interactive viewing |
173 | 156 | with open("plot.html", "w", encoding="utf-8") as f: |
174 | 157 | f.write(html_content) |
175 | 158 |
|
176 | | -# Take screenshot with headless Chrome |
| 159 | +# Screenshot |
177 | 160 | chrome_options = Options() |
178 | 161 | chrome_options.add_argument("--headless") |
179 | 162 | chrome_options.add_argument("--no-sandbox") |
|
187 | 170 | driver.save_screenshot("plot.png") |
188 | 171 | driver.quit() |
189 | 172 |
|
190 | | -# Clean up temp file |
191 | 173 | Path(temp_path).unlink() |
0 commit comments