Tutorials

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.

Tom Hartman

Marketing

13 Min Read Reviewed by Tom Hartman Fact-checked by Tom Hartman
BluSky — The Future of Trading. Prop firm futures trading. Sign up at BluSky.pro.

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 / 2 and square-root WMA length is converted with int(), math.floor() or math.round().
  • No series length is passed to ta.ema(), ta.hma() or ta.atr().
  • History-dependent ta.* calls sit in global scope, not inside conditionals, ternaries or and/or expressions.
  • The signal itself is gated with barstate.isconfirmed or 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.isconfirmed and 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

All In Tutorials
  • Tutorials Oct 9, 2026

    Using AI to Write a Pine Script Futures Strategy for Webhooks

    Learn to prompt AI for a Pine Script v6 futures strategy with webhook-ready alerts, and catch the alert_message, session, and contract mistakes AI makes.

  • Tutorials Oct 9, 2026

    AI Pine Script DCA Strategy: Prompts That Work

    Learn to prompt AI for working Pine Script v6 DCA, grid, and scale-in code, plus the pyramiding, exit, and trimming mistakes to catch before you deploy it.

  • Tutorials Oct 9, 2026

    How to Build an AI Pine Script Swing Trading Strategy

    Prompt AI for a Pine Script v6 swing trading strategy, catch repainting and warm-up errors in the output, then route a confirmed signal to a broker.

  • Tutorials Oct 9, 2026

    Coding an AI Pine Script Scalping Strategy

    Prompt an AI for a Pine Script v6 scalping strategy, then audit the backtest for the fill, cost, and bar-detail mistakes AI assistants make most.

Start trading at scale today. Sign up for free.

Free 7-day trial

Set-up in 3 minutes

Paper account for testing

By continuing, you agree to the TradersPost Terms of Service and Privacy Policy. This site is protected by reCAPTCHA.

TradersPost operates as a non-custodial automated trading platform, enabling users to connect alerts from their preferred trading platforms to their selected brokerage or exchange accounts. It abstains from the transmission, custody, or management of customer funds, covering both traditional and cryptocurrency assets. Typically, registration requirements set by regulatory entities such as the SEC, FINRA, or FinCEN apply to entities that hold or transmit customer funds. To ensure ongoing compliance, TradersPost regularly engages with regulatory authorities to confirm its adherence to all relevant local and federal laws.

TradersPost does not provide alerts, signals, research, analysis, or trading advice of any kind. It is designed to assist traders and investors in making their own trading decisions based on their alerts. The platform does not offer recommendations regarding securities to buy or sell, nor does it provide trading or investing advice. The platform and its features, capabilities, and tools are provided 'as-is' without any warranty.

Risk Disclosure: The use of automated trading systems involves inherent risks, including the potential for significant financial loss. These systems operate based on predetermined algorithms that may not fully adapt to changing market conditions, possibly making them unsuitable for some investors. Individuals are advised to thoroughly assess their financial situation and risk tolerance before using this platform.

Testimonials appearing on this website may not be representative of other clients or customers and is not a guarantee of future performance or success.

Broker, exchange, trading platform, company names, product names, service marks, trademarks, and logos appearing on this website are the property of their respective owners and are used solely to identify supported connections and technical compatibility. TradersPost is an independent third-party platform and is not affiliated with, endorsed by, sponsored by, or authorized by any of these organizations unless expressly stated. Technical compatibility does not imply a commercial partnership, sponsorship, endorsement, or authorization. See Important Disclosures for trademark information.

© 2026 TradersPost, Inc. All rights reserved.