Code a Heikin Ashi Strategy in Pine v6 Without Fake Fills

Code a Heikin Ashi Strategy in Pine v6 Without Fake Fills

By HorizonAI Team · 12 min read · Intermediate

How to Code a Heikin Ashi Strategy in Pine Script v6 (and Avoid the Backtest Trap)

A Heikin Ashi strategy can look almost flawless in TradingView, then fall apart the moment you ask how those trades were actually filled. The usual mistake is simple: using smoothed Heikin Ashi prices as though they were the market prices available to buy or sell.

Short answer: calculate Heikin Ashi candles from the chart’s standard OHLC data, use their color changes only to create signals, and let the strategy fill at real next-bar prices. Don’t run a strategy directly on a Heikin Ashi chart or set stops, targets, and fills from synthetic HA open, high, low, or close values.

The build below plots manual HA candles on a normal chart, enters after a confirmed bullish color flip above a 50-EMA, exits on a bearish flip or real-price risk level, and keeps TradingView’s default next-bar order processing. That separation is what makes the test useful instead of merely pretty.

What you need before writing the strategy

Open a regular candlestick chart in TradingView, not a chart with Heikin Ashi selected as its chart type. Add the script as a strategy so the Strategy Tester records orders. Start on a liquid market and a timeframe where spreads and overnight gaps matter to your intended trading style, such as a 15-minute index future, a 1-hour FX pair, or a daily stock chart.

Heikin Ashi is an averaging transformation, not a second market. For each bar, its close is the average of the real bar’s open, high, low, and close. Its open depends on the preceding Heikin Ashi bar:

  • HA close = (open + high + low + close) / 4
  • HA open = (previous HA open + previous HA close) / 2
  • HA high = max(high, HA open, HA close)
  • HA low = min(low, HA open, HA close)

That recursive open is why the candles smooth trends and why their prices can diverge from executable prices. TradingView classifies Heikin Ashi as non-standard chart data and cautions that it is calculated from standard-chart data rather than representing market prices. Read the Pine documentation on non-standard chart data.

Build the honest Heikin Ashi strategy first

Paste this complete script into the Pine Editor while your chart remains on standard candles. It calculates HA values locally, plots them for visual confirmation, and uses real OHLC for the EMA filter, stop, target, and the strategy engine’s fills.

//@version=6
strategy(
     "Honest Heikin Ashi Trend Strategy",
     overlay = true,
     initial_capital = 10000,
     pyramiding = 0,
     commission_type = strategy.commission.percent,
     commission_value = 0.05,
     slippage = 1,
     process_orders_on_close = false,
     calc_on_every_tick = false)

// --- Inputs
emaLength  = input.int(50, "Real-price EMA length", minval = 1)
stopPct    = input.float(1.5, "Stop loss (%)", minval = 0.1, step = 0.1) / 100.0
targetPct  = input.float(3.0, "Profit target (%)", minval = 0.1, step = 0.1) / 100.0
showHaBars = input.bool(true, "Plot Heikin Ashi candles")

// --- Manually calculated Heikin Ashi values from standard-chart OHLC
float haClose = ohlc4
var float haOpen = na
haOpen := na(haOpen[1]) ? (open + close) / 2.0 : (haOpen[1] + haClose[1]) / 2.0
float haHigh = math.max(high, math.max(haOpen, haClose))
float haLow = math.min(low, math.min(haOpen, haClose))

// --- Signal layer: HA trend state plus a filter from executable prices
bool haBullish = haClose > haOpen
bool bullishFlip = haBullish and not haBullish[1]
bool bearishFlip = not haBullish and haBullish[1]
float realEma = ta.ema(close, emaLength)

bool longSignal = barstate.isconfirmed and bullishFlip and close > realEma
bool closeSignal = barstate.isconfirmed and (bearishFlip or close < realEma)

// Orders submitted after a confirmed bar fill on the next bar by default.
if longSignal and strategy.position_size <= 0
    strategy.entry("Long", strategy.long, alert_message = "HA long signal confirmed")

if closeSignal and strategy.position_size > 0
    strategy.close("Long", alert_message = "HA long exit signal confirmed")

// Bracket levels derive from the actual recorded entry price, never HA prices.
float stopPrice = strategy.position_avg_price * (1.0 - stopPct)
float targetPrice = strategy.position_avg_price * (1.0 + targetPct)

if strategy.position_size > 0
    strategy.exit("Long bracket", from_entry = "Long", stop = stopPrice, limit = targetPrice,
         alert_message = "HA strategy stop or target filled")

// Visual layer only. These candles do not supply order-fill prices.
color haColor = haBullish ? color.new(color.lime, 20) : color.new(color.red, 20)
plotcandle(showHaBars ? haOpen : na, showHaBars ? haHigh : na, showHaBars ? haLow : na,
     showHaBars ? haClose : na, title = "Manual Heikin Ashi", color = haColor,
     wickcolor = haColor, bordercolor = haColor)
plot(realEma, "Real-price EMA", color = color.orange, linewidth = 2)
bgcolor(haBullish ? color.new(color.green, 92) : color.new(color.red, 94), title = "HA trend background")

The input defaults are deliberately restrained: a 50-EMA decides whether long signals are allowed, a 1.5% stop caps the initial loss, and a 3% target creates a 2:1 nominal reward-to-risk ratio. They are test inputs, not universal settings. On intraday futures or FX, replace percentage exits with tick or ATR-based exits once you have the base version behaving as expected.

The script starts with process_orders_on_close = false. That matters. Under TradingView’s standard broker-emulator behavior, an order created when a bar closes is normally filled at the next available tick, which is typically the next bar’s open in historical testing. TradingView documents this order-creation and fill sequence in its strategy concepts reference.

Why a strategy on a Heikin Ashi chart creates a backtest trap

A green HA candle does not mean the market traded at its displayed HA close. It means the transformed values produced a green result after averaging the current bar and carrying information from the prior transformed bar into the HA open.

Run a conventional strategy.entry() script directly on a Heikin Ashi chart and the tester can use synthetic OHLC values in its chart context. A fill may therefore be modeled at a price that did not print in the underlying instrument. That can improve apparent entries, hide gaps, soften adverse excursions, and make a stop appear farther away than it was.

This is not classic future-bar lookahead, where code accesses data that was unavailable at the time. The signal itself can be confirmed honestly at a bar close. The problem is synthetic execution: treating a derived display price as a tradeable price. The consequence looks similar, because the test benefits from information or pricing you could not have received as an actual fill.

The tempting version to avoid

The pattern below is not a good strategy design. It illustrates the shortcut that creates the problem: changing the entire chart to Heikin Ashi, then trusting the resulting Strategy Tester fills.

//@version=6
strategy("HA Chart Trap Example", overlay = true, process_orders_on_close = true)

bool greenBar = close > open
bool redBar = close < open

if greenBar and not greenBar[1]
    strategy.entry("Long", strategy.long)

if redBar
    strategy.close("Long")

The code compiles, but its meaning changes with the chart type. On a standard chart, open and close are real chart prices. On a Heikin Ashi chart, they are transformed values. process_orders_on_close = true also asks the tester to process the order on the signal bar’s close, which makes an already optimistic setup even easier to overstate.

Do this instead: keep the chart standard, calculate HA values inside the script, and leave order processing on the next bar unless your model has a defensible reason to simulate close execution.

Mistake: use haClose as the stop reference because it sits neatly beneath a green HA candle.

Do this instead: calculate every protective level from strategy.position_avg_price, close, high, low, ATR, or another series made from real OHLC.

Read the code as two separate systems

A reliable HA strategy has two jobs. The first identifies trend state. The second simulates an executable trade. Keep them separate in both code and thinking.

1. The signal system uses smoothing on purpose

haBullish is true when the manual HA close is higher than the HA open. bullishFlip catches only the first green HA bar after a red bar, so the strategy does not repeatedly enter during an established trend. The real 50-EMA filter then rejects bullish flips that happen while actual closing price is still below its trend baseline.

That filter is calculated with ta.ema(close, emaLength), not an EMA of HA close. You can test both approaches later, but starting with real close makes the rule easier to interpret: the transformed candle identifies a possible reversal while the market’s actual closing price confirms its trend location.

2. The execution system uses market-derived numbers only

strategy.entry() is triggered after barstate.isconfirmed is true. Historical bars are confirmed at their close, so the condition cannot flicker during a completed historical bar. The default next-bar processing gives you an entry that is closer to what a bar-close system could actually achieve.

The script then uses strategy.position_avg_price to calculate its stop and target. This is the strategy engine’s recorded entry price, including the model’s timing. It is the correct anchor for risk brackets. The Pine Script execution model is also worth reviewing before adding higher-timeframe filters, because timing and data requests are where many apparently stable tests become unreliable.

Test it with settings that expose bad assumptions

A clean equity curve is not validation. Make the model uncomfortable before you decide it has any value.

Start with the script’s 0.05% commission and one tick of slippage, then replace both with values that resemble the instrument and account you intend to use. A liquid large-cap stock, a crypto perpetual, and a micro futures contract have very different friction. If a strategy’s edge disappears after a modest change to cost assumptions, the edge was too thin for the original conclusion.

Use at least three timeframes. For a swing concept, try 1-hour, 4-hour, and daily. For an intraday concept, try 5-minute, 15-minute, and 1-hour. A HA color-flip rule often looks strongest where smoothing suppresses noise most aggressively, so compare trade count, average trade, maximum drawdown, and the distribution of losses rather than only net profit. The framework in these backtesting mistakes to avoid helps turn that review into a repeatable checklist.

Then split history into separate periods. Build parameters on an earlier segment, freeze them, and inspect a later segment without retuning. If a 50-EMA and 1.5% stop work only after repeated revisions on one symbol and one year, you have a fitted explanation, not a dependable rule.

A practical test matrix

TestKeep fixedChangeWhat it reveals
Chart integrityStandard candlesHA overlay on/offWhether the strategy accidentally depends on synthetic chart fills
Execution frictionEntry logicCommission, slippage, session gapsWhether small apparent edge survives trading costs
Timeframe stabilityRisk settings5m, 15m, 1h or 1h, 4h, 1DWhether smoothing only works on one bar size
Market stabilityScript inputsThree liquid symbols in the same asset classWhether one instrument drove the result
Out-of-sampleFrozen parametersA later date rangeWhether tuning merely matched past noise

Add alerts without pretending they automate execution

The alert_message parameters in the full script attach clear text to entry, exit, stop, and target orders. In TradingView, create a strategy alert and choose order-fill events to receive them. The alert confirms that the script created or filled an order in TradingView’s model. It does not place a trade at a broker.

Make each alert actionable. A useful message names the system, direction, and trigger, then you decide whether the live chart, bid-ask spread, and session conditions support taking the trade. If you later add external automation, keep the strategy test and its alert payload under version control so you know exactly which rule set produced each signal.

For a refresher on confirmation and conditions that can change as a live bar develops, read why Pine Script repaints and how to fix it. The HA calculation here is stable once a bar closes, but an unconfirmed current bar can still flip color before the close.

Common Heikin Ashi strategy mistakes

Mistake: enter every time HA is green. That turns a trend-state display into repeated signals and can reopen immediately after an exit.

Do this instead: use a state change such as haBullish and not haBullish[1], then add one real-price filter, such as close > ta.ema(close, 50).

Mistake: turn on process_orders_on_close to make the report match the signal candle.

Do this instead: test next-bar execution first. If your intended process really can transact at the closing auction or on an intrabar condition, model the exact rule, include realistic costs, and compare it with the next-bar baseline.

Mistake: optimize the EMA, stop, and target until the equity curve becomes smooth.

Do this instead: define a small test grid before looking at results, for example EMA lengths of 34, 50, and 100; stops of 1%, 1.5%, and 2%; targets at 1.5R and 2R. Freeze the selection before the out-of-sample run.

Mistake: use HA low as proof that a real stop would not have been hit.

Do this instead: inspect the standard-chart low and your assumed fill rules. A synthetic HA low can obscure the path the actual market took inside the bar.

Pro tips for making the base rule more useful

First, treat the HA overlay as a visual signal diagnostic. If an entry makes no sense on the standard candles with the HA overlay visible, it is unlikely to become sensible because the equity curve is attractive. Mark the entry and inspect the real opening price on the following bar.

Second, test a short side independently. Don’t mirror the long code and assume symmetry. A bearish flip below the 50-EMA is the logical counterpart, but assets with long upward drift, particular funding mechanics, or short-sale constraints can produce a very different result. Add it only after the long-only version has been tested across markets.

Third, consider replacing percentage risk with ATR after you validate the execution model. A starting variant could place the stop 1.5 ATR below the actual entry and target 3 ATR above it. That adapts to volatility, but it also adds another parameter, so change one component at a time. If you want a broader trend template to compare against, see this Supertrend strategy with Pine alerts.

Generating this without writing the code yourself

You can generate the same build in HorizonAI by describing the signal and execution separation precisely. HorizonAI can create Pine Script v6 strategies from plain-English chat, compile-check the code, and let you refine it in the browser editor. It writes the script; you still add it to TradingView and decide how to act on any alerts.

Build a Pine Script v6 long-only strategy for a standard candlestick chart. Calculate Heikin Ashi values manually from real OHLC, plot them with plotcandle, and never use HA OHLC as order-fill or risk prices. Enter only when HA flips bullish on a confirmed bar and real close is above a 50 EMA. Submit orders for next-bar processing, use a 1.5% stop and 3% target calculated from strategy.position_avg_price, include 0.05% commission and 1 tick slippage, and add descriptive order-fill alert messages.

If you want to test an alternate risk model, follow with:

Keep the same Heikin Ashi signals and real-price execution. Replace the percentage bracket with a stop 1.5 ATR below the actual entry and a target 3 ATR above it. Add inputs for ATR length and multipliers, then explain every changed line.

You’ll get working Pine code you can edit in chat, including fixes if a compiler error appears. Try it free →

FAQs

Is a Heikin Ashi close a real tradeable price?

No. A Heikin Ashi close is an average of the standard bar’s OHLC values. Use it to define a visual trend or signal, but calculate order fills and risk levels from standard-market price series.

Does manually calculating Heikin Ashi prevent repainting?

It prevents dependence on a synthetic chart context, but it does not make an unconfirmed live bar permanent. Wait for barstate.isconfirmed when your rule requires bar-close confirmation.

Should a Heikin Ashi strategy use next-bar fills?

Use next-bar fills as the honest baseline for a bar-close signal. Then compare any faster execution assumption against that baseline with realistic commissions, slippage, and session behavior.

Can I use Heikin Ashi for short entries too?

Yes. A basic short rule is a confirmed bearish flip while real close is below the 50-EMA, with exits calculated from the actual position price. Test the short side separately because its behavior can differ sharply from the long side.

Final thoughts

Heikin Ashi is useful because it reduces visual noise, not because it creates better fill prices. Calculate the candles yourself, keep the chart on regular OHLC, and make the strategy engine trade only assumptions you could plausibly execute.

One practical habit pays for itself: whenever the HA version looks exceptional, rerun the identical entry logic with next-bar fills, higher friction, and standard-price stops. If it still holds up, you have a result worth investigating.

Related articles

Questions about Heikin Ashi strategies? Join our Discord to discuss with other traders!