AI Pine Script Moving Average Indicators
Learn to prompt AI for working Pine Script v6 moving average, Hull MA, and Supertrend code, catch its common bugs, and turn the result into an alert.
Marketing
An AI Pine Script moving average indicator can compile and fire clean alerts, but only when your prompt pins Pine Script v6 and names the exact built-ins, input types and confirmed-bar behavior. Leave those out and AI tools often get Hull Moving Average lengths, Pine's type qualifiers and history-dependent functions wrong.
The workflow below is simple: prompt for one narrow indicator, compile it, audit the length and confirmation logic, then test the alert before you automate anything. It uses a Hull MA turn signal as the worked example because it exposes the same v6 mistakes that show up in SMA, EMA, Supertrend and ribbon code. It is part of our Best AI for Pine Script series; for a hand-coded starting point, see how to code a moving average crossover in Pine Script.
What AI-Generated MA Indicators Cover
SMA, EMA and WMA Basics
A simple moving average (SMA) averages prices over a fixed window and gives every bar in it equal weight. An exponential moving average (EMA) gives more weight to recent prices; a 10-period EMA, for example, puts an 18.18% weight on the latest price. Because each day's EMA depends on the EMA for every day before it, you need far more than 10 bars of data for an accurate 10-period EMA.1
All moving averages lag because they are built from past prices, and the longer the average, the more it lags. An EMA lags less than an SMA of the same length because it weights recent data more heavily.1 A weighted moving average (WMA) also puts extra weight on recent values, which is why the Hull MA is built from WMAs.2
Hull MA and Supertrend
The Hull Moving Average takes a WMA of half the period and a WMA of the full period, combines them, and smooths the result with a third WMA whose length is the square root of the period. The goal is less lag while keeping the line smooth.2
Supertrend draws a trailing band from the average true range that flips sides when the trend changes. Pine's ta.supertrend() returns two values, the line and a direction, and the direction convention trips up AI-written code: a negative direction means an uptrend and a positive one means a downtrend. A model that reads "positive" as "up" will invert every signal. This version plots each side and marks confirmed flips:
//@version=6
indicator("Supertrend Direction Check", overlay = true)
float factorInput = input.float(3.0, "Factor", minval = 0.1, step = 0.1)
int atrLengthInput = input.int(10, "ATR Length", minval = 1)
[supertrendLine, direction] = ta.supertrend(factorInput, atrLengthInput)
// In ta.supertrend(), a negative direction is the uptrend
bool isUptrend = direction < 0
bool flippedUp = barstate.isconfirmed and direction < 0 and direction[1] > 0
bool flippedDown = barstate.isconfirmed and direction > 0 and direction[1] < 0
plot(isUptrend ? supertrendLine : na, "Up trend", color = color.green, style = plot.style_linebr)
plot(not isUptrend ? supertrendLine : na, "Down trend", color = color.red, style = plot.style_linebr)
plotshape(flippedUp, title = "Flip up", style = shape.triangleup, location = location.belowbar, color = color.green, size = size.tiny)
plotshape(flippedDown, title = "Flip down", style = shape.triangledown, location = location.abovebar, color = color.red, size = size.tiny)
MA Ribbons for Alignment
A moving average ribbon plots several averages of increasing length on one chart, so you can see whether short and long trends agree. Each line is still an SMA, EMA, WMA or HMA, so the same version, qualifier and confirmed-bar rules apply. The same review process works for AI-generated momentum indicators and AI-generated volatility indicators.
Writing a Precise Pine Prompt
Pin the Pine Version
Ask for //@version=6 explicitly. Version 6 changed how integer division works, made and and or evaluate lazily, and stopped treating some mutable variables as constants.3 A model trained on older code will mix those behaviors unless you tell it which version you want.
Name Inputs and Qualifiers
Ask for input.int() for the length and input.source() for the price series, and name the built-in you want, such as ta.sma(), ta.ema(), ta.hma() or ta.supertrend(), instead of asking for "a moving average."
Pine's qualifier hierarchy runs const < input < simple < series. A parameter accepts a value with its required qualifier or a weaker one, never a stronger one.4 That matters here because ta.ema(), ta.hma() and ta.atr() need a simple integer length, while ta.sma() and ta.wma() accept a series integer length.
Require Confirmed-Bar Logic
Tell the model to gate the signal itself with barstate.isconfirmed, or to use the previous bar's values on purpose. Ask for an alertcondition(), not just a plot, so the response has to define exactly when a signal happens.
- Specify Pine Script v6.
- Name every input and built-in function.
- Define the signal as a one-time event.
- Require confirmed-bar gating.
- Ask for plots and alert conditions.
Worked Example: Prompting a Hull MA
The Prompt
Keep the prompt narrow enough that you can check every requirement:
Write a Pine Script v6 overlay indicator.
Use input.int() for an HMA length with a default of 20 and a minimum of 2.
Use input.source() for the source with close as the default.
Calculate the HMA with ta.hma().
Plot the HMA and color it by its current slope.
Create turn-up and turn-down conditions based on the HMA changing direction.
Each condition must fire once per confirmed turn, not on an HMA crossover.
Gate the actual signals with barstate.isconfirmed.
Add plotshape() markers and alertcondition() calls.
Keep all history-dependent ta.* calls in global scope.
Return only code that compiles in Pine Script v6.
What First Drafts Get Wrong
If the model hand-builds the HMA instead of calling ta.hma(), it often passes length / 2 straight to ta.wma(). That value is a float, and ta.wma() needs an integer length, so the script won't compile until the length is converted.
Another common mistake is making the length adjustable through a variable that is reassigned with := and then passing it to ta.ema(). In v6 a reassigned variable is series, but ta.ema() needs a simple length, so it fails to compile. When you report the error, the model may "fix" it by switching to ta.sma() or ta.wma(), which accept series lengths. That compiles, but it is a different indicator. Keep the average you asked for and give it a compatible length instead.
The Corrected v6 Code
The built-in ta.hma() handles the length math for you. This version detects turns, not crossovers, which matches Hull's advice to trade the HMA's turning points and not to use it for crossover signals.5
//@version=6
indicator("Confirmed HMA Turns", overlay = true)
int hmaLength = input.int(20, "HMA Length", minval = 2)
float sourceInput = input.source(close, "Source")
float hmaValue = ta.hma(sourceInput, hmaLength)
bool turnUp = hmaValue > hmaValue[1] and hmaValue[1] <= hmaValue[2]
bool turnDown = hmaValue < hmaValue[1] and hmaValue[1] >= hmaValue[2]
bool confirmedTurnUp = barstate.isconfirmed and turnUp
bool confirmedTurnDown = barstate.isconfirmed and turnDown
color hmaColor = hmaValue > hmaValue[1] ? color.lime : color.red
plot(hmaValue, "HMA", color = hmaColor, linewidth = 2)
plotshape(confirmedTurnUp, title = "HMA Turn Up", style = shape.triangleup, location = location.belowbar, color = color.lime, size = size.tiny)
plotshape(confirmedTurnDown, title = "HMA Turn Down", style = shape.triangledown, location = location.abovebar, color = color.red, size = size.tiny)
alertcondition(confirmedTurnUp, "HMA Turn Up", "{\"ticker\":\"{{ticker}}\",\"action\":\"buy\",\"quantity\":1,\"price\":{{close}}}")
alertcondition(confirmedTurnDown, "HMA Turn Down", "{\"ticker\":\"{{ticker}}\",\"action\":\"sell\",\"quantity\":1,\"price\":{{close}}}")
Three Pine v6 Bugs
Integer Division Breaks HMA Length
In Pine v6, dividing two integers that don't divide evenly returns a fraction, even when both are constants.3 With a length from input.int() the result is a float either way, so a hand-coded HMA has to convert length / 2 and the square-root length to integers before ta.wma() can use them.
Hull's own formula truncates: Integer(Period/2) and Integer(SquareRoot(Period)).5 StockCharts rounds to the nearest whole number instead, so an 11-period HMA uses 6 for the half-length WMA where Hull's formula uses 5.2 Both are reasonable, and they draw slightly different lines. If you hand-code it, int() or math.floor() matches Hull and math.round() matches StockCharts:
//@version=6
indicator("Manual HMA with Whole-Number Lengths", overlay = true)
int hmaLength = input.int(21, "HMA Length", minval = 2)
// hmaLength / 2 is a float (10.5 for 21), so convert it before ta.wma() uses it
int halfLength = int(hmaLength / 2)
int sqrtLength = int(math.sqrt(hmaLength))
float rawHma = 2 * ta.wma(close, halfLength) - ta.wma(close, hmaLength)
float manualHma = ta.wma(rawHma, sqrtLength)
plot(manualHma, "Manual HMA", color = color.purple, linewidth = 2)
plot(ta.hma(close, hmaLength), "Built-in ta.hma()", color = color.gray)
If you don't need a specific rounding method, use ta.hma() and skip the choice.
EMA Length Qualifier Errors
The question isn't whether a variable holds an integer; it's whether that integer is const, input, simple or series. Once an expression produces a series value, a script can't lower its qualifier to fit a parameter that needs a simple one.4
So check the exact function before you accept an AI's fix. A series length works with ta.sma() and ta.wma(), but not with ta.ema(), ta.hma() or ta.atr(), which need a simple length.
Lazy Logic Skips Calculations
Pine v6 evaluates and and or lazily: if the left side already decides the result, the right side never runs.3 A history-dependent ta.* call on the right side can therefore skip bars and build an inconsistent history.
TradingView's compiler warns about this with "should be called on each calculation for consistency," and the fix is to move the call out of the conditional expression into the global scope.6 Calculate first, then combine:
//@version=6
indicator("Keep ta.* Calls in Global Scope", overlay = true)
// Risky: on bars where close > open is false, ta.sma() never runs,
// so its history skips those bars
// bool signal = close > open and close > ta.sma(close, 10)
// Safe: calculate on every bar, then combine
float fastAverage = ta.sma(close, 10)
bool signal = barstate.isconfirmed and close > open and close > fastAverage
plot(fastAverage, "SMA 10", color = color.orange)
plotshape(signal, title = "Close above SMA", style = shape.circle, location = location.belowbar, color = color.teal, size = size.tiny)
Confirmed Bars and Repainting
Why Prices Still Move
On a realtime bar, high, low and close can change many times before the bar closes; only the open stays fixed.7 A condition built on the current close can appear during the bar and vanish before it closes.
Using barstate.isconfirmed
barstate.isconfirmed is true on all historical bars and on the last, closing update of a realtime bar. Gating a condition with it stops that condition from firing before the bar is complete, but it does not work inside a request.security() call.8
Waiting for confirmation always gives a later signal than reacting to an open bar. TradingView notes that every method of preventing repainting triggers signals later than a repainting script would.7 That delay is the price of a signal that doesn't change after the fact.
Audit the Confirmation Logic
Check that barstate.isconfirmed is attached to the alert condition, not just to a plot or background color. Confirming the visuals doesn't protect an alert built from a separate, unconfirmed condition.
Review every higher-timeframe request.security() call on its own. TradingView's documented non-repainting pattern offsets the requested expression by at least one bar with [1] and sets the lookahead argument to barmerge.lookahead_on.7 Lookahead without the offset leaks future data into historical bars.
Reviewing AI-Generated Indicator Code
Compile Before Trusting It
Paste the code into the Pine Editor and fix every error and warning. Check that the //@version=6 header matches the behavior the script relies on. A clean compile only proves Pine accepts the code, not that the signal does what you asked.
Moving Average Review Checklist
- Every hand-coded
length / 2and square-root WMA length is converted withint(),math.floor()ormath.round(). - No series length is passed to
ta.ema(),ta.hma()orta.atr(). - History-dependent
ta.*calls sit in global scope, not inside conditionals, ternaries orand/orexpressions. - The signal itself is gated with
barstate.isconfirmedor a deliberate previous-bar offset. - Higher-timeframe requests use both an offset and the intended lookahead setting.
- Each turn or crossover fires once, rather than staying true for several bars.
- Supertrend logic treats a negative direction as the uptrend.
From Indicator to Automated Alert
Wrap the Signal in Alerts
A confirmed condition can feed alertcondition() in an indicator, or entry and exit calls in a strategy. When you create the TradingView alert, choose Once Per Bar Close so the alert's frequency matches the script's confirmation logic.
Keep the JSON message in the script where you can. A message with ticker, action, quantity and price lets you review the order instructions next to the signal logic.
Send Alerts to a Broker
A TradingView alert can send that JSON to a webhook URL. TradersPost receives it, applies your strategy and subscription settings, and sends the trade to your connected broker. Subscriptions can inherit values from the signal or override them, for example the quantity or order type.
Subscribe a paper account first, such as the built-in paper broker. Confirm that turn-up and turn-down events produce the actions, quantities and order types you expect before connecting the strategy to a live account.
Test Before Going Live
Add "test": true to the payload to have the signal processed as a test without sending an order to your broker. Remove it once the payload checks out.
Then compare TradingView's Alert Log with the strategy's Signals page in TradersPost, which lists every signal the strategy received with its ticker, action and timestamp. Matching the two separates Pine timing problems from payload or execution settings.
Bottom Line
- Prompt for Pine Script v6, exact inputs, named built-ins and confirmed-bar alerts.
- Use
ta.hma()unless you need a specific hand-coded rounding method. - Check length qualifiers, keep history-dependent calls in global scope, and remember that a negative Supertrend direction means up.
- Treat
barstate.isconfirmedand higher-timeframe repainting as separate problems. - Compile, test the webhook payload and paper trade before automating with real money.
For choosing a model and the wider prompting and review process, continue with Best AI for Pine Script.
Frequently Asked Questions
Can AI Write a Working Indicator?
Yes, if the prompt pins Pine Script v6 and specifies the inputs, built-ins, signal definition and confirmed-bar logic. Compile the result before trusting it. The common failures are fractional HMA lengths, series lengths passed to ta.ema() and history-dependent calls skipped by lazy and/or.
Why Does My Hull MA Fail?
Usually a fractional length / 2 or square-root length is being passed to ta.wma() without converting it to an integer. The other common cause is a series length passed to a function that needs a simple one. Switching to ta.hma() avoids the manual length math.
Does isconfirmed Stop All Repainting?
No. It stops the condition it gates from firing before the chart bar closes. It doesn't work inside request.security(), so higher-timeframe data still needs a properly offset request. It also delays the signal until the bar's closing update.
Should I Hand-Code the HMA?
Only if you need a specific rounding convention or want to see every step. Hull's formula truncates the derived lengths, while StockCharts rounds them. Use ta.hma() when that difference doesn't matter to you.
How Do Alerts Become Trades?
Wrap the confirmed condition in alertcondition() or strategy order calls, create a TradingView alert, and send its JSON message to your strategy's webhook URL. Send test signals first, subscribe a paper account, and compare the Alert Log with the signals received before going live.
References
1 StockCharts ChartSchool: Moving Averages, Simple and Exponential
2 StockCharts ChartSchool: Hull Moving Average (HMA)
3 TradingView: To Pine Script Version 6
4 TradingView: Pine Script Type System
5 Alan Hull: The Hull Moving Average
6 TradingView: CW10003, function should be called on each calculation for consistency
7 TradingView: Repainting
8 TradingView: Bar States