|
1 | 1 | """ pyplots.ai |
2 | 2 | candlestick-basic: Basic Candlestick Chart |
3 | | -Library: letsplot 4.8.2 | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: letsplot 4.8.2 | Python 3.14.3 |
| 4 | +Quality: /100 | Updated: 2026-02-24 |
5 | 5 | """ |
6 | 6 |
|
7 | | -import cairosvg |
8 | 7 | import numpy as np |
9 | 8 | import pandas as pd |
10 | | -from lets_plot import ( |
11 | | - LetsPlot, |
12 | | - aes, |
13 | | - element_blank, |
14 | | - element_text, |
15 | | - geom_rect, |
16 | | - geom_segment, |
17 | | - ggplot, |
18 | | - ggsize, |
19 | | - labs, |
20 | | - scale_x_continuous, |
21 | | - theme, |
22 | | - theme_minimal, |
23 | | -) |
24 | | -from lets_plot.export import ggsave |
| 9 | +from lets_plot import * # noqa: F403 |
25 | 10 |
|
26 | 11 |
|
27 | | -LetsPlot.setup_html() |
| 12 | +LetsPlot.setup_html() # noqa: F405 |
28 | 13 |
|
29 | 14 | # Data - simulated 30 trading days of stock OHLC data |
30 | 15 | np.random.seed(42) |
31 | 16 | n_days = 30 |
32 | 17 |
|
33 | | -dates = pd.date_range(start="2024-01-02", periods=n_days, freq="B") # Business days |
| 18 | +dates = pd.date_range(start="2024-01-02", periods=n_days, freq="B") |
34 | 19 |
|
35 | 20 | # Generate realistic OHLC data with random walk |
36 | 21 | price = 100.0 |
|
52 | 37 |
|
53 | 38 | df = pd.DataFrame({"date": dates, "open": opens, "high": highs, "low": lows, "close": closes}) |
54 | 39 |
|
55 | | -# Calculate candlestick properties |
| 40 | +# Candlestick properties |
56 | 41 | df["bullish"] = df["close"] >= df["open"] |
57 | | -df["color"] = df["bullish"].map({True: "#22C55E", False: "#EF4444"}) # Green/Red |
58 | 42 | df["body_low"] = df[["open", "close"]].min(axis=1) |
59 | 43 | df["body_high"] = df[["open", "close"]].max(axis=1) |
60 | | -df["x"] = range(len(df)) # Numeric x for positioning |
| 44 | +df["x"] = range(len(df)) |
61 | 45 | df["xmin"] = df["x"] - 0.35 |
62 | 46 | df["xmax"] = df["x"] + 0.35 |
| 47 | +df["date_str"] = df["date"].dt.strftime("%b %d") |
| 48 | + |
| 49 | +bull_color = "#2271B5" # Blue for bullish (up) |
| 50 | +bear_color = "#D55E00" # Orange for bearish (down) — colorblind-safe |
| 51 | + |
| 52 | +bull_df = df[df["bullish"]].copy() |
| 53 | +bear_df = df[~df["bullish"]].copy() |
| 54 | + |
| 55 | +# Tick label positions: every 5th trading day |
| 56 | +tick_pos = list(range(0, n_days, 5)) |
| 57 | +tick_labels = [dates[i].strftime("%b %d") for i in tick_pos] |
| 58 | + |
| 59 | +# Tooltip template for interactive HTML export |
| 60 | +tip_fmt = ( |
| 61 | + layer_tooltips() # noqa: F405 |
| 62 | + .line("@date_str") |
| 63 | + .line("Open|$@open") |
| 64 | + .line("High|$@high") |
| 65 | + .line("Low|$@low") |
| 66 | + .line("Close|$@close") |
| 67 | +) |
63 | 68 |
|
64 | 69 | # Plot |
65 | 70 | plot = ( |
66 | | - ggplot() |
67 | | - # Wicks (high-low lines) |
68 | | - + geom_segment(aes(x="x", xend="x", y="low", yend="high"), data=df, color="#666666", size=1) |
69 | | - # Bullish candle bodies (green) |
70 | | - + geom_rect( |
71 | | - aes(xmin="xmin", xmax="xmax", ymin="body_low", ymax="body_high"), |
72 | | - data=df[df["bullish"]], |
73 | | - fill="#22C55E", |
74 | | - color="#22C55E", |
| 71 | + ggplot() # noqa: F405 |
| 72 | + # Wicks (high-low lines) colored per direction |
| 73 | + + geom_segment( # noqa: F405 |
| 74 | + aes(x="x", xend="x", y="low", yend="high"), # noqa: F405 |
| 75 | + data=bull_df, |
| 76 | + color=bull_color, |
| 77 | + size=0.9, |
| 78 | + tooltips=tip_fmt, |
| 79 | + ) |
| 80 | + + geom_segment( # noqa: F405 |
| 81 | + aes(x="x", xend="x", y="low", yend="high"), # noqa: F405 |
| 82 | + data=bear_df, |
| 83 | + color=bear_color, |
| 84 | + size=0.9, |
| 85 | + tooltips=tip_fmt, |
| 86 | + ) |
| 87 | + # Bullish candle bodies (blue) |
| 88 | + + geom_rect( # noqa: F405 |
| 89 | + aes(xmin="xmin", xmax="xmax", ymin="body_low", ymax="body_high"), # noqa: F405 |
| 90 | + data=bull_df, |
| 91 | + fill=bull_color, |
| 92 | + color=bull_color, |
75 | 93 | size=0.5, |
| 94 | + tooltips=tip_fmt, |
76 | 95 | ) |
77 | | - # Bearish candle bodies (red) |
78 | | - + geom_rect( |
79 | | - aes(xmin="xmin", xmax="xmax", ymin="body_low", ymax="body_high"), |
80 | | - data=df[~df["bullish"]], |
81 | | - fill="#EF4444", |
82 | | - color="#EF4444", |
| 96 | + # Bearish candle bodies (orange) |
| 97 | + + geom_rect( # noqa: F405 |
| 98 | + aes(xmin="xmin", xmax="xmax", ymin="body_low", ymax="body_high"), # noqa: F405 |
| 99 | + data=bear_df, |
| 100 | + fill=bear_color, |
| 101 | + color=bear_color, |
83 | 102 | size=0.5, |
| 103 | + tooltips=tip_fmt, |
84 | 104 | ) |
85 | | - # Labels and theme |
86 | | - + scale_x_continuous( |
87 | | - breaks=list(range(0, n_days, 5)), labels=[df["date"].iloc[i].strftime("%b %d") for i in range(0, n_days, 5)] |
| 105 | + + scale_x_continuous(breaks=tick_pos, labels=tick_labels, expand=[0.02, 0]) # noqa: F405 |
| 106 | + + labs( # noqa: F405 |
| 107 | + x="Trading Day (Jan\u2013Feb 2024)", y="Price ($)", title="candlestick-basic \u00b7 letsplot \u00b7 pyplots.ai" |
88 | 108 | ) |
89 | | - + labs(x="Date", y="Price ($)", title="candlestick-basic · letsplot · pyplots.ai") |
90 | | - + theme_minimal() |
91 | | - + theme( |
92 | | - axis_title=element_text(size=20), |
93 | | - axis_text=element_text(size=16), |
94 | | - plot_title=element_text(size=24), |
95 | | - panel_grid_major_x=element_blank(), |
96 | | - panel_grid_minor=element_blank(), |
| 109 | + + theme_minimal() # noqa: F405 |
| 110 | + + theme( # noqa: F405 |
| 111 | + axis_title=element_text(size=20, color="#333333"), # noqa: F405 |
| 112 | + axis_text=element_text(size=16, color="#555555"), # noqa: F405 |
| 113 | + plot_title=element_text(size=24, color="#222222"), # noqa: F405 |
| 114 | + panel_grid_major_x=element_line(color="#ececec", size=0.3), # noqa: F405 |
| 115 | + panel_grid_major_y=element_line(color="#e0e0e0", size=0.4), # noqa: F405 |
| 116 | + panel_grid_minor=element_blank(), # noqa: F405 |
| 117 | + axis_ticks=element_blank(), # noqa: F405 |
| 118 | + plot_background=element_rect(fill="white", color="white"), # noqa: F405 |
97 | 119 | ) |
98 | | - + ggsize(1600, 900) |
| 120 | + + ggsize(1600, 900) # noqa: F405 |
99 | 121 | ) |
100 | 122 |
|
101 | | -# Save HTML for interactive version |
102 | | -ggsave(plot, "plot.html", path=".") |
103 | | - |
104 | | -# Save SVG first, then convert to PNG |
105 | | -ggsave(plot, "plot.svg", path=".", w=1600, h=900, unit="px") |
106 | | - |
107 | | -# Convert SVG to PNG using cairosvg |
108 | | -with open("plot.svg", "r") as f: |
109 | | - svg_content = f.read() |
110 | | - |
111 | | -cairosvg.svg2png(bytestring=svg_content.encode("utf-8"), write_to="plot.png", output_width=4800, output_height=2700) |
| 123 | +# Save |
| 124 | +ggsave(plot, "plot.png", path=".", scale=3) # noqa: F405 |
| 125 | +ggsave(plot, "plot.html", path=".") # noqa: F405 |
0 commit comments