Skip to content

Commit d907c19

Browse files
feat(highcharts): implement indicator-ema (#3690)
## Implementation: `indicator-ema` - highcharts Implements the **highcharts** version of `indicator-ema`. **File:** `plots/indicator-ema/implementations/highcharts.py` **Parent Issue:** #3652 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20886979938)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 8cccb9e commit d907c19

File tree

2 files changed

+411
-0
lines changed

2 files changed

+411
-0
lines changed
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
""" pyplots.ai
2+
indicator-ema: Exponential Moving Average (EMA) Indicator Chart
3+
Library: highcharts unknown | Python 3.13.11
4+
Quality: 91/100 | Created: 2026-01-11
5+
"""
6+
7+
import tempfile
8+
import time
9+
import urllib.request
10+
from pathlib import Path
11+
12+
import numpy as np
13+
import pandas as pd
14+
from highcharts_core.chart import Chart
15+
from highcharts_core.options import HighchartsOptions
16+
from highcharts_core.options.series.area import LineSeries
17+
from selenium import webdriver
18+
from selenium.webdriver.chrome.options import Options
19+
20+
21+
# Generate realistic stock price data
22+
np.random.seed(42)
23+
n_days = 120
24+
25+
# Start with a base price and generate realistic daily returns
26+
base_price = 150.0
27+
daily_returns = np.random.normal(0.001, 0.02, n_days) # Small positive drift with volatility
28+
29+
# Generate price series with cumulative returns
30+
prices = [base_price]
31+
for ret in daily_returns[1:]:
32+
prices.append(prices[-1] * (1 + ret))
33+
prices = np.array(prices)
34+
35+
# Create date range (trading days)
36+
dates = pd.date_range(start="2024-06-01", periods=n_days, freq="B")
37+
38+
39+
# Calculate EMAs
40+
def calc_ema(prices, span):
41+
"""Calculate exponential moving average."""
42+
ema = np.zeros(len(prices))
43+
multiplier = 2 / (span + 1)
44+
ema[0] = prices[0]
45+
for i in range(1, len(prices)):
46+
ema[i] = prices[i] * multiplier + ema[i - 1] * (1 - multiplier)
47+
return ema
48+
49+
50+
ema_12 = calc_ema(prices, 12)
51+
ema_26 = calc_ema(prices, 26)
52+
53+
# Convert dates to timestamps for Highcharts
54+
timestamps = [int(d.timestamp() * 1000) for d in dates]
55+
56+
# Create chart
57+
chart = Chart(container="container")
58+
chart.options = HighchartsOptions()
59+
60+
# Chart configuration
61+
chart.options.chart = {
62+
"type": "line",
63+
"width": 4800,
64+
"height": 2700,
65+
"backgroundColor": "#ffffff",
66+
"spacingTop": 60,
67+
"spacingBottom": 100,
68+
"spacingLeft": 80,
69+
"spacingRight": 80,
70+
}
71+
72+
# Title
73+
chart.options.title = {
74+
"text": "indicator-ema · highcharts · pyplots.ai",
75+
"style": {"fontSize": "56px", "fontWeight": "bold"},
76+
"margin": 40,
77+
}
78+
79+
# Subtitle
80+
chart.options.subtitle = {
81+
"text": "Stock Price with 12-day and 26-day EMAs",
82+
"style": {"fontSize": "36px", "color": "#666666"},
83+
}
84+
85+
# X-axis configuration (datetime)
86+
chart.options.x_axis = {
87+
"type": "datetime",
88+
"title": {"text": "Date", "style": {"fontSize": "36px"}, "margin": 25},
89+
"labels": {"style": {"fontSize": "28px"}},
90+
"gridLineWidth": 1,
91+
"gridLineColor": "rgba(0, 0, 0, 0.1)",
92+
"tickInterval": 30 * 24 * 3600 * 1000, # Monthly ticks
93+
"dateTimeLabelFormats": {"month": "%b %Y"},
94+
}
95+
96+
# Y-axis configuration
97+
chart.options.y_axis = {
98+
"title": {"text": "Price (USD)", "style": {"fontSize": "36px"}, "margin": 25},
99+
"labels": {"style": {"fontSize": "28px"}, "format": "${value:.0f}"},
100+
"gridLineWidth": 1,
101+
"gridLineColor": "rgba(0, 0, 0, 0.1)",
102+
}
103+
104+
# Legend
105+
chart.options.legend = {"enabled": True, "itemStyle": {"fontSize": "32px"}, "margin": 30}
106+
107+
# Tooltip
108+
chart.options.tooltip = {
109+
"shared": True,
110+
"crosshairs": True,
111+
"style": {"fontSize": "28px"},
112+
"valueDecimals": 2,
113+
"valuePrefix": "$",
114+
"xDateFormat": "%b %d, %Y",
115+
}
116+
117+
# Plot options
118+
chart.options.plot_options = {
119+
"line": {"lineWidth": 5, "marker": {"enabled": False}, "states": {"hover": {"lineWidth": 6}}}
120+
}
121+
122+
# Price series (prominent)
123+
price_series = LineSeries()
124+
price_series.name = "Close Price"
125+
price_series.data = [[t, round(p, 2)] for t, p in zip(timestamps, prices, strict=True)]
126+
price_series.color = "#306998" # Python Blue
127+
price_series.line_width = 5
128+
price_series.z_index = 3
129+
chart.add_series(price_series)
130+
131+
# EMA 12 series
132+
ema12_series = LineSeries()
133+
ema12_series.name = "EMA 12"
134+
ema12_series.data = [[t, round(e, 2)] for t, e in zip(timestamps, ema_12, strict=True)]
135+
ema12_series.color = "#FFD43B" # Python Yellow
136+
ema12_series.line_width = 3
137+
ema12_series.dash_style = "Solid"
138+
ema12_series.z_index = 2
139+
chart.add_series(ema12_series)
140+
141+
# EMA 26 series
142+
ema26_series = LineSeries()
143+
ema26_series.name = "EMA 26"
144+
ema26_series.data = [[t, round(e, 2)] for t, e in zip(timestamps, ema_26, strict=True)]
145+
ema26_series.color = "#9467BD" # Purple
146+
ema26_series.line_width = 3
147+
ema26_series.dash_style = "ShortDash"
148+
ema26_series.z_index = 1
149+
chart.add_series(ema26_series)
150+
151+
# Download Highcharts JS for inline embedding
152+
highcharts_url = "https://code.highcharts.com/highcharts.js"
153+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
154+
highcharts_js = response.read().decode("utf-8")
155+
156+
# Generate HTML with inline scripts
157+
html_str = chart.to_js_literal()
158+
html_content = f"""<!DOCTYPE html>
159+
<html>
160+
<head>
161+
<meta charset="utf-8">
162+
<script>{highcharts_js}</script>
163+
</head>
164+
<body style="margin:0;">
165+
<div id="container" style="width: 4800px; height: 2700px;"></div>
166+
<script>{html_str}</script>
167+
</body>
168+
</html>"""
169+
170+
# Write temp HTML and take screenshot
171+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
172+
f.write(html_content)
173+
temp_path = f.name
174+
175+
chrome_options = Options()
176+
chrome_options.add_argument("--headless")
177+
chrome_options.add_argument("--no-sandbox")
178+
chrome_options.add_argument("--disable-dev-shm-usage")
179+
chrome_options.add_argument("--disable-gpu")
180+
chrome_options.add_argument("--window-size=5000,3000")
181+
182+
driver = webdriver.Chrome(options=chrome_options)
183+
driver.get(f"file://{temp_path}")
184+
time.sleep(5)
185+
186+
# Screenshot the chart container element for exact dimensions
187+
container = driver.find_element("id", "container")
188+
container.screenshot("plot.png")
189+
driver.quit()
190+
191+
Path(temp_path).unlink()
192+
193+
# Save HTML for interactive version (using CDN for portability)
194+
interactive_html = f"""<!DOCTYPE html>
195+
<html>
196+
<head>
197+
<meta charset="utf-8">
198+
<title>indicator-ema · highcharts · pyplots.ai</title>
199+
<script src="https://code.highcharts.com/highcharts.js"></script>
200+
</head>
201+
<body style="margin:0;">
202+
<div id="container" style="width: 100%; height: 100vh;"></div>
203+
<script>{html_str}</script>
204+
</body>
205+
</html>"""
206+
with open("plot.html", "w", encoding="utf-8") as f:
207+
f.write(interactive_html)

0 commit comments

Comments
 (0)