Plotting close and an SMA
Lesson 3 · about 11 min
plot() is the function you will call more than any other. It takes a series and draws one value per bar. Everything else about it is styling: title, colour, width, line style, and where the plot lives. This lesson builds one script step by step so the options land in context rather than as a list.
Step one: the raw close and a moving average
//@version=6
indicator("Close and SMA", shorttitle="C+SMA", overlay=true)
length = 20
sma = ta.sma(close, length)
plot(close, "Close", color=color.gray, linewidth=1)
plot(sma, "SMA 20", color=color.blue, linewidth=2)
ta.sma(close, length) returns a series: on each bar, the average of the last twenty closes. On the first nineteen bars there are not enough values, so the result is na ("not available") and nothing is drawn. That gap at the left edge of every moving average is normal, not a bug.
The title string ("SMA 20") matters more than it looks. It appears in the data window, in the style settings dialog, and, importantly, it is how alert message placeholders like {{plot("SMA 20")}} refer to the plot later.
Step two: colours
Pine colours come three ways:
- Named constants:
color.blue,color.red,color.gray,color.orange,color.purple,color.teal,color.white,color.blackand a few more. - RGB:
color.rgb(33, 150, 243), with an optional fourth argument for transparency. - A base colour with transparency:
color.new(color.blue, 70), where the second number is 0 (opaque) to 100 (invisible).
Colour can vary per bar because a colour is just another series. Replace the SMA plot with:
smaColor = close > sma ? color.green : color.red
plot(sma, "SMA 20", color=smaColor, linewidth=2)
Now the average is green while price is above it and red below. The ? : is a ternary: condition, value if true, value if false. It is the most common way to pick a colour, and we return to it in module 2.
Step three: fills and styles
plot() returns an ID. Two IDs can be handed to fill() to shade the space between them:
pClose = plot(close, "Close", color=color.gray)
pSma = plot(sma, "SMA 20", color=smaColor, linewidth=2)
fill(pClose, pSma, color=color.new(smaColor, 85))
Line style is set with the style argument. The useful ones:
| Style | Looks like |
|---|---|
plot.style_line |
default connected line |
plot.style_linebr |
line that breaks where the series is na |
plot.style_stepline |
horizontal steps |
plot.style_histogram |
bars from zero |
plot.style_columns |
wider bars from zero |
plot.style_circles |
dots |
plot.style_cross |
small crosses |
plot.style_linebr is the one to remember. When you plot a stop level that only exists while you are in a trade, a normal line draws a diagonal from the last stop to the next one; linebr leaves the gap.
Overlay versus pane
overlay=true draws on the price pane using the price scale. overlay=false gives the script its own pane with its own scale. The choice is about units: a moving average of price belongs on price; an RSI, a volume ratio or a count of bars belongs in a pane, because plotting a 0 to 100 oscillator on a chart of a $4,000 index squashes it into a flat line.
A single script can put some plots in the other place using force_overlay=true on the individual plot() call, which is handy for an oscillator script that also wants to mark bars on price. You cannot go the other way and push a plot from an overlay script into a new pane.
Key idea:
plot()draws one value per bar; colour, width and style can all be series, so anything you can compute you can display.
Putting it together
//@version=6
indicator("Close and SMA", shorttitle="C+SMA", overlay=true)
length = 20
sma = ta.sma(close, length)
smaColor = close > sma ? color.green : color.red
pClose = plot(close, "Close", color=color.gray, linewidth=1)
pSma = plot(sma, "SMA 20", color=smaColor, linewidth=2)
fill(pClose, pSma, color=color.new(smaColor, 85))
plot(ta.sma(close, 50), "SMA 50", color=color.new(color.orange, 30), style=plot.style_circles)
Notice the last line calls ta.sma directly inside plot(). That is fine; a function call is an expression and can go anywhere a series is expected. As scripts grow, giving intermediate results a name makes them easier to read and lets you reuse them, so prefer sma50 = ta.sma(close, 50) and then plot(sma50, ...).
Limits worth knowing now
A script can have at most 64 plot-type outputs (plot, plotshape, plotchar, bgcolor, fill all count). You will not hit that soon, but it explains why heavy community indicators use labels and lines instead of plots. Also, plot() must be called in the global scope: you cannot put it inside an if. To plot conditionally, plot condition ? value : na instead.
Try it: Change
lengthto 50, then 200, and look at the left edge of the chart each time. Then change the second plot tostyle=plot.style_histogramand setoverlay=false. Finally, put theplot()inside anif close > smablock and read the error; then fix it with a ternary.
Recap
plot(series, title, color, linewidth, style)draws one value per bar; title is used by alerts later.- Colours are named constants,
color.rgb()orcolor.new(base, transparency), and can change per bar. fill()shades between two plot IDs;plot.style_linebrbreaks lines atna.overlaydecides price pane versus own pane; match it to the units of what you plot.- Plots must be in the global scope; use
cond ? value : nato plot conditionally.
See it drawn
Original diagrams for the ideas on this page. Illustrative, not real market data.