You paste a block of Ichimoku code into the Pine Editor, add it to the chart, and get five lines that do not look like the Ichimoku Cloud on any other chart. The cloud sits on top of price instead of ahead of it, or the lagging line is drawn at the right edge, or the two cloud colors are the same. Ichimoku is one of the few indicators where the arithmetic is simple and the plotting is the hard part, because three of the five lines are shifted in time and Pine Script has to be told exactly how.
This guide builds the indicator from the ground up in Pine Script v6. You will compute each line, plot the shifted ones with the right offsets, fill the cloud, and then turn the cloud into conditions you can test and alert on. Along the way you will see which parts are easy to get wrong and why the built-in version on TradingView uses an offset of 25, not 26. If you only want to see the numbers for one bar, the Ichimoku Cloud Calculator does the arithmetic without any code.
What the five lines actually are
Ichimoku Kinko Hyo was published in 1969 by the Japanese journalist Goichi Hosoda, who wrote under the name Ichimoku Sanjin. The standard settings of 9, 26, and 52 came from the Japanese trading week, which then ran six days: nine periods is a week and a half, twenty-six is a month of trading days, and fifty-two is two months. Those numbers are conventions, not constants of nature, and on a 24/7 crypto market they are only one reasonable choice among many.
Every line except one is the midpoint of a recent range. For a given length, take the highest high and the lowest low over that many bars, and average them. That is the same midpoint a Donchian channel draws as its center line, which is why the Donchian channel breakout post is a useful companion. The five lines are:
Tenkan-sen, the conversion line, is the midpoint over 9 bars. Kijun-sen, the base line, is the midpoint over 26 bars. Senkou Span A is the average of Tenkan and Kijun, plotted 26 bars into the future. Senkou Span B is the midpoint over 52 bars, also plotted 26 bars into the future. Chikou Span is the current close, plotted 26 bars into the past.
The area between Senkou Span A and Senkou Span B is the cloud, called the kumo. When Span A is above Span B the cloud is usually drawn in one color, and when it is below, in another. The cloud is the only part that is drawn ahead of the latest bar, which is what makes Ichimoku look different from a moving average ribbon.
Computing the midpoint lines
Pine Script has no single function that returns an Ichimoku line, but it has everything you need. ta.highest(high, length) returns the highest high over the last length bars, ta.lowest(low, length) returns the lowest low, and math.avg averages them. Wrapping that in a small function keeps the three midpoints readable and means you write the logic once.
//@version=6
indicator("Ichimoku Lines", overlay = true)
tenkanLen = input.int(9, "Tenkan length", minval = 1)
kijunLen = input.int(26, "Kijun length", minval = 1)
midpoint(int len) =>
math.avg(ta.highest(high, len), ta.lowest(low, len))
tenkan = midpoint(tenkanLen)
kijun = midpoint(kijunLen)
plot(tenkan, "Tenkan-sen", color = color.new(color.blue, 0))
plot(kijun, "Kijun-sen", color = color.new(color.red, 0))Two things are worth noticing. First, the function declares its parameter type, int len. That is optional, but it documents that the length must be a whole number and gives a clearer error if you pass a float. Second, ta.highest and ta.lowest are called inside a function, and TradingView asks that functions using ta.* series calls be evaluated on every bar. Calling midpoint unconditionally at the top level, as above, satisfies that. If you wrapped the call in an if block, the history inside the function would have gaps and the values would be wrong. The execution model post explains why in detail.
With only these two lines you already have something readable. The Tenkan line hugs price and turns quickly, and the Kijun line is slower and flatter. When Tenkan crosses above Kijun, price has recently been stronger than its longer-term midpoint. That is a description of the past 26 bars, not a forecast.
Shifting lines in time with offset
The next three lines need time shifts. In Pine Script, the offset argument of plot moves the drawn line along the time axis. A positive offset moves the plot to the right, into the future, and a negative offset moves it to the left, into the past. Nothing is recalculated. The same value is simply drawn at a different bar.
This is where the 25 comes from. TradingView's built-in Ichimoku Cloud uses a displacement setting of 26, but it applies an offset of displacement - 1, which is 25, to the spans, and -displacement + 1, which is -25, to the lagging line. The reason is that the current bar already counts as the first of the 26. If you use a plain 26 you get a cloud that is one bar off from the built-in indicator, which is enough to make crossovers and cloud touches disagree with the chart your friends are looking at. The script below follows the built-in convention and exposes the displacement as an input.
//@version=6
indicator("Ichimoku Cloud", overlay = true)
tenkanLen = input.int(9, "Tenkan length", minval = 1)
kijunLen = input.int(26, "Kijun length", minval = 1)
spanBLen = input.int(52, "Senkou B length", minval = 1)
disp = input.int(26, "Displacement", minval = 1)
midpoint(int len) =>
math.avg(ta.highest(high, len), ta.lowest(low, len))
tenkan = midpoint(tenkanLen)
kijun = midpoint(kijunLen)
spanA = math.avg(tenkan, kijun)
spanB = midpoint(spanBLen)
plot(tenkan, "Tenkan-sen", color = color.new(color.blue, 0))
plot(kijun, "Kijun-sen", color = color.new(color.red, 0))
plot(close, "Chikou Span", color = color.new(color.green, 0), offset = -disp + 1)
pA = plot(spanA, "Senkou Span A", color = color.new(color.teal, 0), offset = disp - 1)
pB = plot(spanB, "Senkou Span B", color = color.new(color.orange, 0), offset = disp - 1)
fill(pA, pB, color = spanA >= spanB ? color.new(color.teal, 80) : color.new(color.orange, 80), title = "Kumo")The fill call is the part people stumble on. It does not take two series. It takes two plot handles, which is why plot(spanA, ...) is assigned to pA and plot(spanB, ...) to pB. The color argument of fill can be a series, so the ternary picks one tint when Span A is above Span B and another when it is below. One subtlety: the comparison spanA >= spanB is evaluated on the bar where the value was calculated, not on the bar where it is drawn. Because both spans share the same offset, that is harmless here, and the color flips at the twist, where the two spans cross.
If the cloud appears but the colors never change, check that the fill color uses the ternary rather than a fixed color, and that you are looking at a long enough history. On a chart with fewer than about 52 bars the Span B midpoint cannot be calculated, so the cloud does not exist yet.
Reading the cloud without overreading it
Once the lines are on the chart, the common ways of reading Ichimoku are all comparisons you can express in code. Price above the cloud means the close is higher than both spans, price below means lower than both, and price inside the cloud means it sits between them. Cloud thickness is the gap between the spans, which is a rough visual measure of how far apart the 26-bar midpoint and the 52-bar midpoint are. A thin cloud, or a twist where Span A crosses Span B, marks a place where those two midpoints are close.
None of this predicts anything. The cloud is a smoothed picture of where the last 52 bars traded, shifted forward so you can see it beside future bars. It is useful as a structured description of recent range and as a set of precise conditions to test, and it is a poor fit if you treat a touch of the cloud as a promise. A chart where price repeatedly crosses a thin cloud will fire every condition you define, and the sideways market is exactly where the indicator is least informative.
The conditions are also where shifted plotting bites. The cloud you see ahead of the latest bar was calculated 25 bars ago. To ask whether the close is above the cloud on the current bar, you have to compare against the spans as they were drawn at this bar, which means reading the series from disp - 1 bars back. Comparing the close to today's spanA and spanB values would test it against a cloud that will not appear on the chart for another 25 bars.
Turning the cloud into conditions and alerts
The script below adds three conditions that match the usual vocabulary: a Tenkan and Kijun cross, price relative to the cloud as it is drawn today, and a lagging-line check that compares today's close with the close 25 bars back. The cross is marked with a shape, and an alertcondition lets you attach a TradingView alert to it.
//@version=6
indicator("Ichimoku Conditions", overlay = true)
tenkanLen = input.int(9, "Tenkan length", minval = 1)
kijunLen = input.int(26, "Kijun length", minval = 1)
spanBLen = input.int(52, "Senkou B length", minval = 1)
disp = input.int(26, "Displacement", minval = 1)
midpoint(int len) =>
math.avg(ta.highest(high, len), ta.lowest(low, len))
tenkan = midpoint(tenkanLen)
kijun = midpoint(kijunLen)
spanA = math.avg(tenkan, kijun)
spanB = midpoint(spanBLen)
// The cloud as drawn at the current bar was calculated disp - 1 bars ago.
cloudA = spanA[disp - 1]
cloudB = spanB[disp - 1]
cloudTop = math.max(cloudA, cloudB)
cloudBot = math.min(cloudA, cloudB)
aboveCloud = close > cloudTop
belowCloud = close < cloudBot
// The lagging line compares today's close with the close disp - 1 bars ago.
chikouAbove = close > close[disp - 1]
crossUp = ta.crossover(tenkan, kijun)
crossDown = ta.crossunder(tenkan, kijun)
plot(tenkan, "Tenkan-sen", color = color.blue)
plot(kijun, "Kijun-sen", color = color.red)
pA = plot(spanA, "Senkou Span A", color = color.teal, offset = disp - 1)
pB = plot(spanB, "Senkou Span B", color = color.orange, offset = disp - 1)
fill(pA, pB, color = spanA >= spanB ? color.new(color.teal, 80) : color.new(color.orange, 80))
plotshape(crossUp and aboveCloud and chikouAbove, "Cross up, above cloud",
style = shape.triangleup, location = location.belowbar, color = color.green, size = size.tiny)
plotshape(crossDown and belowCloud and not chikouAbove, "Cross down, below cloud",
style = shape.triangledown, location = location.abovebar, color = color.red, size = size.tiny)
alertcondition(crossUp and aboveCloud and chikouAbove, "Ichimoku cross up", "Tenkan crossed above Kijun with the close above the cloud")
alertcondition(crossDown and belowCloud and not chikouAbove, "Ichimoku cross down", "Tenkan crossed below Kijun with the close below the cloud")Reading spanA[disp - 1] is the key line. It asks for the value that was calculated 25 bars ago, which is exactly the value the plot draws at the current bar. Without it, the comparison would be against a cloud that has not been drawn yet.
This version uses ta.crossover and ta.crossunder, which return a value on the bar where the cross completes. Because the midpoints use the current bar's high and low, a cross can appear partway through a live bar and disappear before it closes. If you attach an alert to it, set the alert to fire once per bar close, or add barstate.isconfirmed to the condition, so the marker you see after the fact is the one that actually fired. The barstate.isconfirmed post covers that pattern, and why alerts fire on the wrong bar shows what goes wrong without it.
The mistakes that make a cloud look wrong
Most broken Ichimoku scripts fail in a small number of predictable ways.
The first is the off-by-one displacement already described. Use disp - 1 for the spans and -disp + 1 for the lagging line if you want to match the built-in indicator, and say so in your script's description if you chose differently.
The second is putting the offset on the calculation instead of the plot. Writing spanA = math.avg(tenkan, kijun)[26] shifts the history in the opposite direction from what you want and cannot reach into the future at all. The future part of the cloud only exists because plot is allowed to draw to the right of the latest bar. Keep the series unshifted and put the offset on plot.
The third is forgetting that the right side of the cloud has no price. The cloud extends 25 bars past the last candle, but there is nothing to compare it to yet. Any condition that reads spanA without the [disp - 1] shift is quietly comparing the close to a cloud that is not on the chart.
The fourth is the lagging line. The Chikou Span is the current close drawn in the past, so at the latest bar it is the close from 25 bars ago that appears under the candle. A check such as close > close[disp - 1] is the correct expression of "the lagging line is above price", because the plotted point at the old bar is today's close and the price at that old bar is the close from 25 bars back.
The fifth is the symbol and timeframe. Ichimoku settings of 9, 26, and 52 on a 5-minute chart describe 45 minutes, 130 minutes, and 260 minutes of trading. On a daily chart they describe about two weeks, a month, and two months. The same code means different things on different timeframes, and a handful of triggers in a short window says very little about how a setting behaves in general.
Checking the plot before you trust it
A script that compiles can still draw the wrong picture, and this indicator is a good example, because a one-bar offset error compiles perfectly. The quickest check is to put the built-in Ichimoku Cloud on the same chart and compare the lines to yours. If your Tenkan and Kijun overlap the built-in ones but the cloud sits one bar to the side, the displacement is the culprit.
A second check is to count. Click on the cloud's right edge and confirm that it ends 25 bars past the last candle, not 26 and not 0. A third is to scroll back and find a twist, then check that the fill color changes at the place where the two span lines cross.
If you would rather see the result before moving it into TradingView, PineScripter's chart runs a script on real market data inside the editor and draws its plots, shapes, and labels, so a misplaced cloud shows up immediately. It covers 308 crypto spot markets, so it will not show you a stock or a forex pair, and it is a check on what the script draws, not on whether the indicator is any good. For the question of what your cross markers did on past data, the same page covers indicator analysis, which measures price movement 1, 5, 10, and 20 bars after each trigger. It describes the past and nothing more.
Changing the cloud without rewriting the script
Ichimoku scripts tend to get customized. You might want a different cloud color for each regime, a thinner Kijun line, a version that uses only the cloud for a trend filter, or the whole thing running on a higher timeframe. Those are small edits to a script that already works, and they are the kind of change where a full rewrite introduces new bugs.
In the PineScripter AI editor, you describe the change in plain English, for example "make the cloud green when Span A is above Span B and gray when it is below, and drop the lagging line", and it edits only the lines that change. Every script is checked for compile errors the same way TradingView checks it, and the AI sees any error and fixes it itself. The Code Changes view then shows line by line what moved, so you can confirm the offsets were left alone. If you want to run the same cloud on a higher timeframe, the safe way to do that is covered in Multi-Timeframe Analysis in Pine Script Without Repainting. If you want to compare the cloud with a volatility envelope, the Bollinger Bands guide uses the same plotting patterns, and the Keltner Channels Calculator is a quick way to see the numbers.
PineScripter is a coding tool, so it writes and edits the script, and the Strategy Tester in TradingView is still where you would backtest a strategy built on it. You can see plans and pricing on the pricing page.
A short checklist
Before you rely on an Ichimoku script, confirm that the spans use an offset of disp - 1, that the lagging line uses -disp + 1, and that conditions read the spans with [disp - 1]. Confirm that the fill takes plot handles and a series color. Confirm that alerts run on bar close. And remember what the indicator is: a way of drawing recent range, shifted in time, that you can describe in precise conditions. It does not tell you what happens next.
Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.
Frequently asked questions
Why does the built-in Ichimoku use an offset of 25 and not 26?
The current bar counts as the first of the 26 displaced bars, so TradingView plots the spans with an offset of the displacement minus 1, which is 25, and the lagging line at minus 25. Matching that keeps your cloud aligned with the built-in indicator.
Can Pine Script plot the cloud into the future?
Yes. A positive offset on plot draws the series to the right of the latest bar. Keep the calculation unshifted and put the offset on the plot call.
How do I check if price is above the cloud in code?
Read the spans from displacement minus 1 bars back, for example spanA[disp - 1], then compare the close with the higher of the two. Comparing with the current spanA tests against a cloud that is not drawn yet.
Are 9, 26, and 52 the right Ichimoku settings?
They are the traditional conventions from a six-day trading week, not fixed rules. Inputs let you change them, and the same numbers cover very different spans of time on different timeframes.
Does the Ichimoku Cloud predict where price goes next?
No. It draws the midpoints of recent ranges, shifted in time. It describes past price and gives you precise conditions to test, not a forecast.
Describe your indicator in plain English and get Pine Script that compiles →
Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.