Skip to content

Commit 8cccb9e

Browse files
feat(plotnine): implement indicator-sma (#3687)
## Implementation: `indicator-sma` - plotnine Implements the **plotnine** version of `indicator-sma`. **File:** `plots/indicator-sma/implementations/plotnine.py` **Parent Issue:** #3651 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20886981190)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 3c1e329 commit 8cccb9e

File tree

2 files changed

+299
-0
lines changed

2 files changed

+299
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
""" pyplots.ai
2+
indicator-sma: Simple Moving Average (SMA) Indicator Chart
3+
Library: plotnine 0.15.2 | Python 3.13.11
4+
Quality: 91/100 | Created: 2026-01-11
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
from plotnine import (
10+
aes,
11+
element_line,
12+
element_text,
13+
geom_line,
14+
ggplot,
15+
labs,
16+
scale_color_manual,
17+
scale_x_datetime,
18+
theme,
19+
theme_minimal,
20+
)
21+
22+
23+
# Data - Generate realistic stock price data with trend and volatility
24+
np.random.seed(42)
25+
n_days = 300
26+
dates = pd.date_range("2024-01-01", periods=n_days, freq="B") # Business days
27+
28+
# Create a price series with trends and mean reversion
29+
base_price = 150
30+
returns = np.random.normal(0.0003, 0.015, n_days) # Daily returns
31+
# Add some trending behavior
32+
trend = np.sin(np.linspace(0, 3 * np.pi, n_days)) * 0.001
33+
returns = returns + trend
34+
close = base_price * np.cumprod(1 + returns)
35+
36+
# Calculate SMAs
37+
df = pd.DataFrame({"date": dates, "close": close})
38+
df["sma_20"] = df["close"].rolling(window=20).mean()
39+
df["sma_50"] = df["close"].rolling(window=50).mean()
40+
df["sma_200"] = df["close"].rolling(window=200).mean()
41+
42+
# Reshape data for plotnine (long format for multiple lines with legend)
43+
df_long = pd.melt(
44+
df, id_vars=["date"], value_vars=["close", "sma_20", "sma_50", "sma_200"], var_name="series", value_name="price"
45+
)
46+
47+
# Rename series for legend
48+
series_labels = {"close": "Price", "sma_20": "SMA 20", "sma_50": "SMA 50", "sma_200": "SMA 200"}
49+
df_long["series"] = df_long["series"].map(series_labels)
50+
51+
# Set order for legend
52+
series_order = ["Price", "SMA 20", "SMA 50", "SMA 200"]
53+
df_long["series"] = pd.Categorical(df_long["series"], categories=series_order, ordered=True)
54+
55+
# Define colors matching the other implementations
56+
colors = {"Price": "#306998", "SMA 20": "#FFD43B", "SMA 50": "#E74C3C", "SMA 200": "#2ECC71"}
57+
58+
# Plot
59+
plot = (
60+
ggplot(df_long, aes(x="date", y="price", color="series"))
61+
+ geom_line(size=1.5, alpha=0.9)
62+
+ scale_color_manual(values=colors)
63+
+ scale_x_datetime(date_breaks="2 months", date_labels="%b %Y")
64+
+ labs(x="Date", y="Price ($)", title="indicator-sma · plotnine · pyplots.ai", color="")
65+
+ theme_minimal()
66+
+ theme(
67+
figure_size=(16, 9),
68+
plot_title=element_text(size=24, weight="bold"),
69+
axis_title=element_text(size=20),
70+
axis_text=element_text(size=16),
71+
axis_text_x=element_text(rotation=30, ha="right"),
72+
legend_text=element_text(size=16),
73+
legend_position=(0.02, 0.98),
74+
legend_direction="vertical",
75+
panel_grid_major_y=element_line(color="#cccccc", size=0.5, alpha=0.3),
76+
panel_grid_major_x=element_line(alpha=0),
77+
panel_grid_minor=element_line(alpha=0),
78+
)
79+
)
80+
81+
plot.save("plot.png", dpi=300)
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
library: plotnine
2+
specification_id: indicator-sma
3+
created: '2026-01-11T00:53:45Z'
4+
updated: '2026-01-11T00:56:27Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20886981190
7+
issue: 3651
8+
python_version: 3.13.11
9+
library_version: 0.15.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/indicator-sma/plotnine/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/indicator-sma/plotnine/plot_thumb.png
12+
preview_html: null
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent use of plotnine grammar of graphics with proper long-format data transformation
17+
- Clean color scheme that distinguishes all four series effectively
18+
- Proper title format following the spec-id · library · pyplots.ai convention
19+
- Good handling of date axis with appropriate rotation to prevent overlap
20+
- Realistic synthetic stock price data with visible trend behavior
21+
- All three SMA periods (20, 50, 200) are correctly calculated and displayed
22+
weaknesses:
23+
- Legend could benefit from a background/box for better readability against data
24+
- The SMA 200 line only appears in the final third of the chart due to the 200-day
25+
window requirement
26+
image_description: 'The plot displays a Simple Moving Average (SMA) indicator chart
27+
with a dark blue price line showing daily stock prices over approximately one
28+
year (Jan 2024 - Feb 2025). Three SMA overlay lines are visible: a yellow SMA
29+
20 (short-term), a red SMA 50 (medium-term), and a green SMA 200 (long-term).
30+
The price fluctuates between roughly $130-$170, showing a downward trend from
31+
Jan-Aug 2024 followed by an upward trend from Oct 2024 onwards. The legend is
32+
positioned in the upper left corner with entries for Price, SMA 20, SMA 50, and
33+
SMA 200. The title follows the correct format "indicator-sma · plotnine · pyplots.ai"
34+
displayed in bold at the top. X-axis shows dates in "Mon YYYY" format with labels
35+
rotated 30 degrees to prevent overlap. Y-axis shows "Price ($)" with values ranging
36+
from 130 to 170. Grid lines are subtle, appearing only on the y-axis with low
37+
opacity. The minimal theme provides a clean background. All four lines are clearly
38+
distinguishable with consistent 1.5 line width and 0.9 alpha transparency.'
39+
criteria_checklist:
40+
visual_quality:
41+
score: 36
42+
max: 40
43+
items:
44+
- id: VQ-01
45+
name: Text Legibility
46+
score: 10
47+
max: 10
48+
passed: true
49+
comment: Title is bold (24pt), axis labels (20pt) and tick labels (16pt) are
50+
clearly readable at full resolution
51+
- id: VQ-02
52+
name: No Overlap
53+
score: 8
54+
max: 8
55+
passed: true
56+
comment: No overlapping text elements, date labels rotated 30 degrees to prevent
57+
overlap
58+
- id: VQ-03
59+
name: Element Visibility
60+
score: 7
61+
max: 8
62+
passed: true
63+
comment: Line width of 1.5 and alpha of 0.9 provide good visibility, all four
64+
series clearly distinguishable
65+
- id: VQ-04
66+
name: Color Accessibility
67+
score: 5
68+
max: 5
69+
passed: true
70+
comment: Blue, yellow, red, green color scheme is colorblind-friendly and
71+
provides good contrast
72+
- id: VQ-05
73+
name: Layout Balance
74+
score: 4
75+
max: 5
76+
passed: true
77+
comment: Good canvas utilization, legend well positioned in upper-left, minimal
78+
wasted space
79+
- id: VQ-06
80+
name: Axis Labels
81+
score: 2
82+
max: 2
83+
passed: true
84+
comment: Y-axis has units 'Price ($)', X-axis labeled 'Date'
85+
- id: VQ-07
86+
name: Grid & Legend
87+
score: 0
88+
max: 2
89+
passed: false
90+
comment: Grid is appropriately subtle but legend lacks background frame for
91+
readability against data
92+
spec_compliance:
93+
score: 25
94+
max: 25
95+
items:
96+
- id: SC-01
97+
name: Plot Type
98+
score: 8
99+
max: 8
100+
passed: true
101+
comment: Correct line chart with price and three SMA overlays as specified
102+
- id: SC-02
103+
name: Data Mapping
104+
score: 5
105+
max: 5
106+
passed: true
107+
comment: Date on X-axis, price values on Y-axis, all correctly mapped
108+
- id: SC-03
109+
name: Required Features
110+
score: 5
111+
max: 5
112+
passed: true
113+
comment: 'All required features present: price line, 20/50/200-day SMAs, legend
114+
with periods'
115+
- id: SC-04
116+
name: Data Range
117+
score: 3
118+
max: 3
119+
passed: true
120+
comment: All data visible, axes accommodate full range from ~130 to ~170
121+
- id: SC-05
122+
name: Legend Accuracy
123+
score: 2
124+
max: 2
125+
passed: true
126+
comment: Legend correctly shows 'Price', 'SMA 20', 'SMA 50', 'SMA 200'
127+
- id: SC-06
128+
name: Title Format
129+
score: 2
130+
max: 2
131+
passed: true
132+
comment: Correct format 'indicator-sma · plotnine · pyplots.ai'
133+
data_quality:
134+
score: 18
135+
max: 20
136+
items:
137+
- id: DQ-01
138+
name: Feature Coverage
139+
score: 7
140+
max: 8
141+
passed: true
142+
comment: Shows uptrend and downtrend, SMAs crossing, different lag behavior
143+
of each SMA period
144+
- id: DQ-02
145+
name: Realistic Context
146+
score: 7
147+
max: 7
148+
passed: true
149+
comment: Realistic stock price scenario with plausible daily volatility and
150+
price range (~$130-$170)
151+
- id: DQ-03
152+
name: Appropriate Scale
153+
score: 4
154+
max: 5
155+
passed: true
156+
comment: 300 business days is appropriate for showing 200-day SMA; price range
157+
realistic
158+
code_quality:
159+
score: 9
160+
max: 10
161+
items:
162+
- id: CQ-01
163+
name: KISS Structure
164+
score: 3
165+
max: 3
166+
passed: true
167+
comment: 'Clean linear structure: imports → data generation → SMA calculation
168+
→ reshape → plot → save'
169+
- id: CQ-02
170+
name: Reproducibility
171+
score: 3
172+
max: 3
173+
passed: true
174+
comment: np.random.seed(42) set at the beginning for reproducible data
175+
- id: CQ-03
176+
name: Clean Imports
177+
score: 2
178+
max: 2
179+
passed: true
180+
comment: 'All imports are used: numpy, pandas, and plotnine components'
181+
- id: CQ-04
182+
name: No Deprecated API
183+
score: 1
184+
max: 1
185+
passed: true
186+
comment: Uses current plotnine API
187+
- id: CQ-05
188+
name: Output Correct
189+
score: 0
190+
max: 1
191+
passed: false
192+
comment: Saves as plot.png correctly but minor formatting
193+
library_features:
194+
score: 3
195+
max: 5
196+
items:
197+
- id: LF-01
198+
name: Distinctive Features
199+
score: 3
200+
max: 5
201+
passed: true
202+
comment: Uses plotnine's ggplot grammar, theme_minimal, scale_color_manual,
203+
element_text, and proper long-format data transformation with pd.melt which
204+
is idiomatic for plotnine
205+
verdict: APPROVED
206+
impl_tags:
207+
dependencies: []
208+
techniques:
209+
- layer-composition
210+
patterns:
211+
- data-generation
212+
- wide-to-long
213+
dataprep:
214+
- rolling-window
215+
- time-series
216+
styling:
217+
- alpha-blending
218+
- grid-styling

0 commit comments

Comments
 (0)