[Python Stock Analysis] Calculating and Visualizing Ichimoku Cloud with Polars
Table of Contents
- # Overview
- # Motivation
- # Libraries Used
- # Notebook and Code
- # Explanation
- ## What is Ichimoku Cloud
- ## How to Calculate Ichimoku Cloud
- ## Basic Reading of Ichimoku Cloud
- ### The Cloud (Between Leading Span A and B)
- ### Cloud Bullish and Bearish
- ### Conversion and Base Line Cross
- ### Lagging Span
- ### Using the Future Cloud
- ## Visualization with Plotly
- ### Function to Generate Future Dates
- ### Function to Color-code Cloud by Bullish/Bearish
- ### Creating Cloud Segments Including Future Cloud
- ### Creating the Chart
- # Summary
Overview
In the previous article, we explained how to understand price volatility ranges using Bollinger Bands. This time, we’ll cover the Ichimoku Cloud, a technical indicator developed in Japan, including how to calculate it with Polars and visualize it with Plotly.
The Ichimoku Cloud consists of five components: the Conversion Line, Base Line, Leading Span A & B, and Lagging Span. It’s a powerful indicator that allows you to analyze multiple timeframes simultaneously.
Motivation
The Ichimoku Cloud (一目均衡表, Ichimoku Kinko Hyo) is a technical indicator developed by Goichi Hosoda in Japan in 1936. It’s also known internationally as “Ichimoku Cloud” and is used by traders outside Japan. The main features of the Ichimoku Cloud are:
- Simultaneous multi-timeframe analysis: Can grasp short-term (9 days), medium-term (26 days), and long-term (52 days) trends at once
- Visual support and resistance through the cloud: The “cloud” formed between Leading Span A and B functions as future support or resistance lines
- Understanding trend direction and strength: Can determine trend strength and turning points from the relationship between price and cloud, crosses of Conversion and Base lines, etc.
- Visually intuitive: The cloud is clearly displayed on the chart, making trend direction easy to see at a glance
As in previous articles, we’ll use modern Python libraries such as Polars, Plotly, marimo, and yfinance-pl for analysis.
Libraries Used
The libraries are as follows:
| Library | Related Library | Description |
|---|---|---|
| polars | pandas | A library for handling dataframes at high speed |
| numpy | - | Numerical computing library, used for array manipulation and differential calculation (np.diff()) to detect cloud bullish/bearish crossover points |
| plotly | matplotlib | Visualization library that allows users to interactively manipulate graphs |
| marimo | jupyter | A notebook that runs in py format and allows interactive code execution |
| yfinance-pl | yfinance | A Python library wrapping yfinance-rs that can handle Yahoo Finance information with Polars |
Notebook and Code
The HTML version of the marimo notebook is available here. We also link to the Python code on GitHub.
Explanation
What is Ichimoku Cloud
The Ichimoku Cloud is a technical indicator composed of the following five components:
- Conversion Line (Tenkan-sen): Average of the highest high and lowest low over the past 9 days
- Base Line (Kijun-sen): Average of the highest high and lowest low over the past 26 days
- Leading Span A (Senkou Span A): (Conversion Line + Base Line) / 2, shifted 26 days into the future
- Leading Span B (Senkou Span B): Average of the highest high and lowest low over the past 52 days, shifted 26 days into the future
- Lagging Span (Chikou Span): Today’s closing price, shifted 26 days into the past
The area between Leading Span A and Leading Span B is called the “cloud” (Kumo), which functions as support or resistance lines.
Future Cloud: Since the leading spans are shifted 26 days into the future, the last 26 days of data correspond to future dates. By displaying this future portion, you can anticipate future support and resistance. This is one of the greatest features of the Ichimoku Cloud.
How to Calculate Ichimoku Cloud
We’ll calculate each component of the Ichimoku Cloud using Polars. Use rolling_max() and rolling_min() to get the highest/lowest values within a period, and shift() to move the time axis.
import polars as pl
from typing import TypedDict
class IchimokuValues(TypedDict):
conversion_line: pl.Series
base_line: pl.Series
leading_span1: pl.Series
leading_span2: pl.Series
lagging_span: pl.Series
def get_ichimoku_values(df: pl.DataFrame) -> IchimokuValues:
# Decimal type doesn't support rolling operations, so convert to Float64 first
high = df["high.amount"].cast(pl.Float64)
low = df["low.amount"].cast(pl.Float64)
close = df["close.amount"].cast(pl.Float64)
# Conversion Line: (Max + Min) / 2 over past 9 days
conversion_line = (high.rolling_max(9) + low.rolling_min(9)) / 2
# Base Line: (Max + Min) / 2 over past 26 days
base_line = (high.rolling_max(26) + low.rolling_min(26)) / 2
# Leading Span A: (Conversion Line + Base Line) / 2, shifted 26 days into future
# Polars shift fills empty spaces with null by default
leading_span1 = ((conversion_line + base_line) / 2).shift(26)
# Leading Span B: (Max + Min) / 2 over past 52 days, shifted 26 days into future
leading_span2 = ((high.rolling_max(52) + low.rolling_min(52)) / 2).shift(26)
# Lagging Span: Today's closing price, shifted 26 days into past
lagging_span = close.shift(-26)
return {
"conversion_line": conversion_line,
"base_line": base_line,
"leading_span1": leading_span1,
"leading_span2": leading_span2,
"lagging_span": lagging_span,
}There are a few points to note:
- Decimal Type Conversion: Data retrieved with
yfinance-plis in Decimal type, but Polars’rolling_max()androlling_min()don’t support Decimal type, so convert withcast(pl.Float64) - shift() Direction:
shift()moves forward in time with positive values and backward with negative values. Leading spans useshift(26)to move 26 days forward, while lagging span usesshift(-26)to move 26 days backward - Period Meaning: The periods of 9 days, 26 days, and 52 days are the standard settings for Ichimoku Cloud. These are based on Japanese market business days
Basic Reading of Ichimoku Cloud
The Ichimoku Cloud is a visually intuitive indicator, but there are some basic ways to read it.
The Cloud (Between Leading Span A and B)
The cloud functions as future support or resistance lines.
- Price above the cloud: Uptrend. Cloud likely functions as support
- Price within the cloud: Range-bound market. Direction undetermined
- Price below the cloud: Downtrend. Cloud likely functions as resistance
The thickness of the cloud also indicates trend strength. Thicker clouds mean stronger support/resistance.
Cloud Bullish and Bearish
The color of the cloud changes based on the relationship between Leading Span A and Leading Span B.
- Bullish (bullish cloud): Leading Span A is above Leading Span B. Suggests uptrend
- Bearish (bearish cloud): Leading Span A is below Leading Span B. Suggests downtrend
The timing when the cloud changes from bearish to bullish, or vice versa, is an important signal of trend reversal.
Conversion and Base Line Cross
When the Conversion Line (short-term) crosses above the Base Line (medium-term), it’s a buy signal; when it crosses below, it’s a sell signal. This is similar to the golden cross/death cross concept of moving averages.
Lagging Span
The Lagging Span is an indicator for comparing the current price with the price 26 days ago. When the lagging span is above past prices, it’s considered bullish; when below, bearish.
Using the Future Cloud
The future cloud shows future support and resistance because the leading spans are shifted 26 days forward.
- Thick future cloud: Strong support/resistance expected. Difficult to break through
- Thin future cloud: Weak support/resistance. Price may break through easily
- Future cloud twist: Point where bearish⇔bullish switches. Suggests potential trend reversal
The future cloud is a prediction based on current data and changes according to actual price movements. It’s important to regularly update and check the chart.
Visualization with Plotly
We’ll use Plotly to display candlesticks and the five components of Ichimoku Cloud. In particular, we’ll fill the area between Leading Span A and B to visualize the “cloud” and change colors based on bullish/bearish conditions. We’ll also display the future cloud to visualize future support and resistance.
Function to Generate Future Dates
Since the leading spans of Ichimoku Cloud are shifted 26 days into the future, we need future dates to display 26 days of future cloud. We’ll generate future dates on a business day basis using Python’s datetime.
from datetime import datetime, timedelta
def generate_future_business_days(last_date: str, n_days: int = 26) -> list[str]:
"""
Generate future business days (weekdays) from the last date
Args:
last_date: Last data date (string format: "YYYY-MM-DD")
n_days: Number of business days to generate (default 26 days)
Returns:
List of future business days (string format: "YYYY-MM-DD")
"""
# Convert last date to date object
current = datetime.strptime(last_date, "%Y-%m-%d")
future_dates = []
# Advance one day at a time until required business days are collected
while len(future_dates) < n_days:
current += timedelta(days=1)
# Add only business days (Mon-Fri) (weekday(): Mon=0, Fri=4)
if current.weekday() < 5:
future_dates.append(current.strftime("%Y-%m-%d"))
return future_datesKey points of this function:
- Python’s
datetime: Convert string to date withdatetime.strptime(), advance one day at a time withtimedelta(days=1) - Business day filtering: Get weekday with
weekday()(Mon=0, Tue=1, …, Fri=4, Sat=5, Sun=6) and add only values less than 5 (Mon-Fri) - Loop until required days: Skip Sat/Sun and loop until 26 business days are collected
Function to Color-code Cloud by Bullish/Bearish
First, define a function that divides the cloud into segments and color-codes them.
import numpy as np
import plotly.graph_objects as go
def create_cloud_segments(dates, span1, span2):
"""
Divide Ichimoku Cloud into segments color-coded by bullish/bearish
"""
# Convert Polars Series to NumPy array
if hasattr(span1, "to_numpy"):
span1 = span1.to_numpy()
if hasattr(span2, "to_numpy"):
span2 = span2.to_numpy()
# Bullish judgment (Leading Span A > Leading Span B)
is_bullish = np.nan_to_num(span1 > span2, nan=False)
# Detect crossover points (where bearish⇔bullish switches)
crossovers = np.where(np.diff(is_bullish.astype(int)) != 0)[0] + 1
# Create segment boundaries
boundaries = np.concatenate([[0], crossovers, [len(dates)]])
traces = []
# Process each segment
for i in range(len(boundaries) - 1):
start_idx = boundaries[i]
end_idx = boundaries[i + 1]
segment_dates = dates[start_idx:end_idx]
segment_span1 = span1[start_idx:end_idx]
segment_span2 = span2[start_idx:end_idx]
# Skip segments with only NaN
if np.all(np.isnan(segment_span1)) or np.all(np.isnan(segment_span2)):
continue
# Determine bullish/bearish of segment and set color
segment_is_bullish = is_bullish[start_idx]
fillcolor = (
"rgba(135, 206, 250, 0.3)" # Bullish: Light blue
if segment_is_bullish
else "rgba(255, 165, 0, 0.3)" # Bearish: Orange
)
# Leading Span A line (no fill)
traces.append(
go.Scatter(
x=segment_dates,
y=segment_span1,
mode="lines",
line=dict(width=0.5, color="green"),
showlegend=False,
hoverinfo="skip",
)
)
# Leading Span B line (fill between Leading Span A)
traces.append(
go.Scatter(
x=segment_dates,
y=segment_span2,
mode="lines",
line=dict(width=0.5, color="orange"),
fill="tonexty", # Fill between previous trace (Leading Span A)
fillcolor=fillcolor,
showlegend=False,
hoverinfo="skip",
)
)
return tracesKey points of this function:
- Crossover detection with
np.diff(): Automatically detect points where bearish/bullish switches - Segment division: Divide cloud at crossover points and set color for each segment
- Color distinction: Light blue for bullish, orange for bearish for visual distinction
fill="tonexty": Fill between Leading Span A and B to express the cloud
Creating Cloud Segments Including Future Cloud
Extend the create_cloud_segments() function to display the future cloud.
def create_cloud_segments_with_future(dates, span1, span2, num_future_days=26):
"""
Divide Ichimoku Cloud (including future cloud) into segments color-coded by bullish/bearish
"""
# Generate future dates
last_date = dates[-1]
future_dates = generate_future_business_days(last_date, num_future_days)
# Combine dates (past + future)
all_dates = dates + future_dates
# Convert Polars Series to NumPy array
if hasattr(span1, "to_numpy"):
span1_values = span1.to_numpy()
else:
span1_values = span1
if hasattr(span2, "to_numpy"):
span2_values = span2.to_numpy()
else:
span2_values = span2
# Extend leading span arrays (fill first 26 with NaN)
span1_extended = np.concatenate([[np.nan] * num_future_days, span1_values])
span2_extended = np.concatenate([[np.nan] * num_future_days, span2_values])
# Separate past and future portions
past_dates = dates
future_span1 = span1_extended[-num_future_days:]
future_span2 = span2_extended[-num_future_days:]
past_span1 = span1_extended[:-num_future_days]
past_span2 = span2_extended[:-num_future_days]
# Bullish judgment for past portion
is_bullish = np.nan_to_num(past_span1 > past_span2, nan=False)
# Detect crossover points
crossovers = np.where(np.diff(is_bullish.astype(int)) != 0)[0] + 1
# Create segment boundaries
boundaries = np.concatenate([[0], crossovers, [len(past_dates)]])
traces = []
last_segment_color = None
# Process each segment of past portion
for i in range(len(boundaries) - 1):
start_idx = boundaries[i]
end_idx = boundaries[i + 1]
segment_dates = past_dates[start_idx:end_idx]
segment_span1 = past_span1[start_idx:end_idx]
segment_span2 = past_span2[start_idx:end_idx]
if np.all(np.isnan(segment_span1)) or np.all(np.isnan(segment_span2)):
continue
# Determine bullish/bearish of segment and set color
segment_is_bullish = is_bullish[start_idx]
fillcolor = (
"rgba(135, 206, 250, 0.3)" # Bullish: Light blue
if segment_is_bullish
else "rgba(255, 165, 0, 0.3)" # Bearish: Orange
)
last_segment_color = fillcolor
# Leading Span A line
traces.append(
go.Scatter(
x=segment_dates,
y=segment_span1,
mode="lines",
line=dict(width=0.5, color="green"),
showlegend=False,
hoverinfo="skip",
)
)
# Leading Span B line
traces.append(
go.Scatter(
x=segment_dates,
y=segment_span2,
mode="lines",
line=dict(width=0.5, color="orange"),
fill="tonexty",
fillcolor=fillcolor,
showlegend=False,
hoverinfo="skip",
)
)
# Add future cloud (continue last segment color)
if last_segment_color and not np.all(np.isnan(future_span1)):
traces.append(
go.Scatter(
x=future_dates,
y=future_span1,
mode="lines",
line=dict(width=0.5, color="green", dash="dot"), # Dotted line for future
showlegend=False,
hoverinfo="skip",
)
)
traces.append(
go.Scatter(
x=future_dates,
y=future_span2,
mode="lines",
line=dict(width=0.5, color="orange", dash="dot"), # Dotted line for future
fill="tonexty",
fillcolor=last_segment_color, # Continue last segment color
showlegend=False,
hoverinfo="skip",
)
)
return tracesKey points of this function:
- Future date generation: Generate 26 business days of future dates with
generate_future_business_days() - Array extension: Fill first 26 with
np.nanusingnp.concatenate([[np.nan] * 26, span1_values]), then concatenate actual leading span values - Separate past and future: Determine bullish/bearish with past portion (
[:-26]) of extended array, continue last segment color for future portion ([-26:]) - Dotted line for future: Display future cloud with dotted line using
dash="dot"to visually distinguish from past data
Creating the Chart
Next, retrieve data and create the Ichimoku Cloud chart.
import polars as pl
import yfinance_pl as yf
# Retrieve data
ticker = yf.Ticker("8381.T")
hist = ticker.history(period="6mo")
# Calculate Ichimoku Cloud
ichimoku = get_ichimoku_values(hist)
# Add to DataFrame
df_plot = hist.with_columns(
conversion_line=ichimoku["conversion_line"],
base_line=ichimoku["base_line"],
leading_span1=ichimoku["leading_span1"],
leading_span2=ichimoku["leading_span2"],
lagging_span=ichimoku["lagging_span"],
).with_columns(
[
pl.col("open.amount").cast(pl.Float64),
pl.col("high.amount").cast(pl.Float64),
pl.col("low.amount").cast(pl.Float64),
pl.col("close.amount").cast(pl.Float64),
]
)
dates = df_plot["date"].to_list()
# Create chart
data = []
# Candlestick
data.append(
go.Candlestick(
x=dates,
open=df_plot["open.amount"],
high=df_plot["high.amount"],
low=df_plot["low.amount"],
close=df_plot["close.amount"],
increasing_line_color="red",
decreasing_line_color="green",
name="Price",
)
)
# Conversion Line
data.append(
go.Scatter(
x=dates,
y=df_plot["conversion_line"],
mode="lines",
name="Conversion Line",
line=dict(color="blue", width=1),
)
)
# Base Line
data.append(
go.Scatter(
x=dates,
y=df_plot["base_line"],
mode="lines",
name="Base Line",
line=dict(color="red", width=1),
)
)
# Cloud (color-coded bearish/bullish + future cloud)
cloud_traces = create_cloud_segments_with_future(
dates,
df_plot["leading_span1"],
df_plot["leading_span2"],
num_future_days=26,
)
data.extend(cloud_traces)
# Lagging Span
data.append(
go.Scatter(
x=dates,
y=df_plot["lagging_span"],
mode="lines",
name="Lagging Span",
line=dict(color="purple", width=1, dash="dot"),
)
)
# Layout settings
layout = go.Layout(
title="Stock Price and Ichimoku Cloud",
xaxis_title="Date",
yaxis_title="Price",
xaxis_rangeslider_visible=False,
)
fig = go.Figure(data=data, layout=layout)
fig.show()In this code, the create_cloud_segments_with_future() function color-codes the cloud based on bullish/bearish conditions. It displays light blue for bullish and orange for bearish, making trend changes visually easy to understand.
Key points of this code:
- Automatic detection of cloud bearish/bullish: The
create_cloud_segments_with_future()function determines the magnitude relationship between Leading Span A and B to detect crossover points - Color-coding by segment: Fill cloud with light blue for bullish and orange for bearish to visually express trend changes
- Efficient rendering: Detect crossovers using NumPy’s
np.diff()and render cloud with minimum number of traces - Color transparency: Set 30% transparency in
rgba()format to make candlesticks and other lines easy to see - Future cloud display: Generate 26 business days of future dates with Python’s
datetimeand extend leading span arrays withnp.concatenate()to display future cloud - Array extension: Map actual leading span values to future dates by filling first 26 with
np.nan - Dotted line for future distinction: Display future cloud with dotted line using
dash="dot"to clearly distinguish from past data - Color continuation: Apply color of last segment (bearish/bullish) of past portion to future cloud to visualize trend continuity
Summary
This time, we explained the calculation method for the five components of Ichimoku Cloud (Conversion Line, Base Line, Leading Span A & B, Lagging Span) and how to visualize them with Plotly. Using Polars’ rolling_max(), rolling_min(), and shift(), we can concisely implement the complex calculations of Ichimoku Cloud. In particular, it’s convenient that the time axis shifts of leading and lagging spans can be easily achieved with shift(). While the relationship with the previous Bollinger Bands and moving averages is low, you can grasp rough trends with Ichimoku Cloud alone by combining with simple sub-charts like volume. Fundamental analysis is helpful for the beginning and end right after trend changes, but it’s difficult to grasp with Ichimoku alone. However, the ease of understanding and depth that Ichimoku Cloud provides as a complete system is attractive, so I think it’s worth learning Ichimoku Cloud.
Reference: 『Pythonで実践する株式投資分析』(片渕彼富 (著), 山田祥寛 (監修))
![[Stock Analysis with Python] Weekly and Monthly Charts with Bollinger Bands to Understand Price Volatility](https://b.rmc-8.com/img/2025/12/01/0218c7e535f81400a91a9ad80357a034.png)
![[Stock Analysis with Python] Detecting Moving Averages and Golden Cross Using Modern Libraries](https://b.rmc-8.com/img/2025/12/01/5383d87b9096bc828d358edd077fef67.png)

![[Python] Automatically Record Sleep Time to Toggl](https://pub-21c8df4785a6478092d6eb23a55a5c42.r2.dev/img/eyecatch/garmin_toggl.webp)


Loading comments...