Skip to content

Commit 6abe9df

Browse files
feat(plotly): implement candlestick-volume (#3103)
## Implementation: `candlestick-volume` - plotly Implements the **plotly** version of `candlestick-volume`. **File:** `plots/candlestick-volume/implementations/plotly.py` **Parent Issue:** #3068 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20620310502)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 3e34794 commit 6abe9df

2 files changed

Lines changed: 177 additions & 0 deletions

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
""" pyplots.ai
2+
candlestick-volume: Stock Candlestick Chart with Volume
3+
Library: plotly 6.5.0 | Python 3.13.11
4+
Quality: 92/100 | Created: 2025-12-31
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
import plotly.graph_objects as go
10+
from plotly.subplots import make_subplots
11+
12+
13+
# Data - Generate 60 trading days of realistic OHLC + volume data
14+
np.random.seed(42)
15+
n_days = 60
16+
dates = pd.date_range("2024-01-02", periods=n_days, freq="B") # Business days
17+
18+
# Start price and generate random walk
19+
start_price = 150.0
20+
returns = np.random.normal(0.0005, 0.02, n_days)
21+
prices = start_price * np.cumprod(1 + returns)
22+
23+
# Generate OHLC from close prices
24+
close = prices
25+
open_prices = np.roll(close, 1)
26+
open_prices[0] = start_price
27+
28+
# Generate high/low with realistic intraday range
29+
daily_range = np.abs(np.random.normal(0.015, 0.008, n_days)) * close
30+
high = np.maximum(open_prices, close) + daily_range * np.random.uniform(0.3, 0.7, n_days)
31+
low = np.minimum(open_prices, close) - daily_range * np.random.uniform(0.3, 0.7, n_days)
32+
33+
# Volume with correlation to price movement (higher volume on bigger moves)
34+
base_volume = 5_000_000
35+
price_change = np.abs(close - open_prices) / open_prices
36+
volume = base_volume * (1 + 3 * price_change) * np.random.uniform(0.7, 1.3, n_days)
37+
volume = volume.astype(int)
38+
39+
# Determine up/down days for coloring
40+
is_up = close >= open_prices
41+
colors = ["#306998" if up else "#FFD43B" for up in is_up]
42+
43+
# Create subplot with shared x-axis
44+
# Price pane: 75% height, Volume pane: 25% height
45+
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.03, row_heights=[0.75, 0.25])
46+
47+
# Add candlestick chart
48+
fig.add_trace(
49+
go.Candlestick(
50+
x=dates,
51+
open=open_prices,
52+
high=high,
53+
low=low,
54+
close=close,
55+
increasing={"line": {"color": "#306998", "width": 2}, "fillcolor": "#306998"},
56+
decreasing={"line": {"color": "#FFD43B", "width": 2}, "fillcolor": "#FFD43B"},
57+
name="Price",
58+
showlegend=False,
59+
),
60+
row=1,
61+
col=1,
62+
)
63+
64+
# Add volume bars
65+
fig.add_trace(
66+
go.Bar(x=dates, y=volume, marker={"color": colors, "line": {"width": 0}}, name="Volume", showlegend=False),
67+
row=2,
68+
col=1,
69+
)
70+
71+
# Update layout for professional appearance
72+
fig.update_layout(
73+
title={"text": "candlestick-volume · plotly · pyplots.ai", "font": {"size": 32}, "x": 0.5, "xanchor": "center"},
74+
template="plotly_white",
75+
hovermode="x unified",
76+
# Add crosshair cursor
77+
xaxis={
78+
"showspikes": True,
79+
"spikemode": "across",
80+
"spikesnap": "cursor",
81+
"spikecolor": "#888888",
82+
"spikethickness": 1,
83+
"spikedash": "solid",
84+
},
85+
xaxis2={
86+
"showspikes": True,
87+
"spikemode": "across",
88+
"spikesnap": "cursor",
89+
"spikecolor": "#888888",
90+
"spikethickness": 1,
91+
"spikedash": "solid",
92+
},
93+
yaxis={
94+
"showspikes": True,
95+
"spikemode": "across",
96+
"spikesnap": "cursor",
97+
"spikecolor": "#888888",
98+
"spikethickness": 1,
99+
"spikedash": "solid",
100+
},
101+
yaxis2={
102+
"showspikes": True,
103+
"spikemode": "across",
104+
"spikesnap": "cursor",
105+
"spikecolor": "#888888",
106+
"spikethickness": 1,
107+
"spikedash": "solid",
108+
},
109+
# Disable range slider
110+
xaxis_rangeslider_visible=False,
111+
# Margins for proper spacing
112+
margin={"l": 80, "r": 40, "t": 100, "b": 60},
113+
)
114+
115+
# Update axes styling
116+
fig.update_xaxes(
117+
title={"text": "Date", "font": {"size": 22}},
118+
tickfont={"size": 18},
119+
gridcolor="rgba(128,128,128,0.2)",
120+
gridwidth=1,
121+
row=2,
122+
col=1,
123+
)
124+
125+
fig.update_yaxes(
126+
title={"text": "Price ($)", "font": {"size": 22}},
127+
tickfont={"size": 18},
128+
tickformat="$.0f",
129+
gridcolor="rgba(128,128,128,0.2)",
130+
gridwidth=1,
131+
row=1,
132+
col=1,
133+
)
134+
135+
fig.update_yaxes(
136+
title={"text": "Volume", "font": {"size": 22}},
137+
tickfont={"size": 18},
138+
tickformat=".2s",
139+
gridcolor="rgba(128,128,128,0.2)",
140+
gridwidth=1,
141+
row=2,
142+
col=1,
143+
)
144+
145+
# Hide x-axis title on price pane
146+
fig.update_xaxes(title=None, row=1, col=1)
147+
148+
# Save as PNG (4800 x 2700 pixels)
149+
fig.write_image("plot.png", width=1600, height=900, scale=3)
150+
151+
# Save as HTML for interactivity
152+
fig.write_html("plot.html", include_plotlyjs="cdn")
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
library: plotly
2+
specification_id: candlestick-volume
3+
created: '2025-12-31T13:54:24Z'
4+
updated: '2025-12-31T14:05:27Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20620310502
7+
issue: 3068
8+
python_version: 3.13.11
9+
library_version: 6.5.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/candlestick-volume/plotly/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/candlestick-volume/plotly/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/candlestick-volume/plotly/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent dual-pane layout with proper 75/25 vertical split as specified
17+
- 'Professional use of Plotly features: unified hover, crosshair spikes spanning
18+
both panes, shared x-axis'
19+
- Color scheme is consistent between candlesticks and volume bars for visual coherence
20+
- Realistic OHLC data generation with volume correlated to price movement magnitude
21+
- Both PNG and HTML outputs for static and interactive viewing
22+
- Clean, well-organized code following KISS principles
23+
weaknesses:
24+
- Volume axis label lacks units (should be "Volume (shares)" or similar)
25+
- No legend or annotation explaining that blue=up day, yellow=down day

0 commit comments

Comments
 (0)