intermediate12 min read14 of 24

    AI-Assisted Coding — Pine Script and Python You Can Actually Audit

    Generated code that looks right and repaints is worse than no code. How to specify, test and read what the model hands you.

    Rohit Singh

    Mr. Chartist · SEBI RA INH000015297

    Module

    Ask a model for a Pine Script indicator and you get one in about ten seconds. It compiles. It plots. Arrows appear on your chart in places that look sensible. This is the most dangerous moment in the whole exercise, because everything you can see is working, and the failure modes in trading code are specifically the ones you cannot see by looking at a finished chart.

    The problem is not that AI writes bad code. It often writes clean, readable code. The problem is that trading code has a category of bug that no other software has: a program that produces a different answer about the past than it produced at the time. An indicator that quietly redraws its own history will show you a beautiful chart full of arrows at exactly the right places, because it moved them. Generated code that looks correct and repaints is worse than no code at all, because it produces a backtest you believe.

    So the skill here is not prompting. It is specification and audit. You need to describe what you want precisely enough that the result is checkable — exact data series, exact bar reference, exact condition, and what happens on the first bars before enough history exists. Then you need to read what you were handed, knowing the four specific bugs to look for, and test it in a way that would actually expose them. That is the whole article: specify, generate, audit, test.

    The one thing to remember

    AI writes trading code faster than you can read it — so the value comes entirely from a specification tight enough to check against and an audit that assumes repainting and look-ahead until proven otherwise.

    Why Generated Trading Code Is a Special Risk

    Most software fails loudly. It throws an error, returns nothing, or produces output that is obviously wrong. Trading code has a quieter failure available to it. It can produce output that is wrong only in the sense that it could not have been known at the time — and that kind of wrong looks like accuracy. It looks like edge. It looks like the thing you were hoping to find.

    Two words cover most of it. Repainting: a signal that changes after the bar it appeared on has closed, so the historical chart no longer shows what the indicator actually said in real time. Look-ahead: the code reads a value from a bar that had not completed at the moment the decision is claimed. Both produce a historical record that is better than reality, and neither raises an error.

    AI introduces these bugs at a higher rate than a careful human would, for a structural reason. A language model produces code that resembles code it has seen. An enormous amount of publicly posted indicator code contains exactly these defects, because they are the mistakes beginners make and beginners post the most code. The model is not being careless. It is faithfully reproducing the distribution it learned from.

    There is also a subtler asymmetry. When AI writes a web page, you see the result and judge it. When AI writes an indicator, you see a chart — and the chart is generated by the same code you are trying to evaluate. You are checking the work using the work. That circularity is why the audit has to be procedural and specific, rather than a glance at whether the arrows look right.

    You cannot validate an indicator by looking at the chart it drew. The chart is the output of the code you are trying to check.

    Watch out — A repainting indicator does not produce a slightly optimistic backtest. It produces a backtest of a strategy that could never have been traded, at any size, by anyone. Treat any unexplained cluster of near-perfect entries as a bug report about your code, not as a discovery.

    The Loop: Specify, Generate, Audit, Test

    Work in a fixed loop and most of the risk drains out of the exercise. Write the specification before you open the model. Generate against it. Audit the code against the specification line by line. Then test in a way designed to expose timing bugs rather than to confirm that the thing plots.

    The order is not decorative. Writing the specification first means you have a document to check against that was not produced by the model — which is the only way the audit is independent. If you describe what you want loosely and then read the code to find out what you asked for, the code becomes the specification, and there is nothing left to audit it against.

    The loop is also where you enforce a rule that saves an enormous amount of time: reject the first answer that skipped a line of your specification. Not fix it, not patch it — say which line was ignored and ask again. Code that ignored one instruction has usually ignored the reasoning behind it too, and patching that by hand leaves you owning a file you did not write and do not fully understand.

    Expect to run the loop more than once for anything non-trivial. The first pass typically gets the arithmetic right and the timing wrong. The second pass, after you have quoted the exact bar-reference problem back at it, is usually much closer. Three passes on a small indicator is normal and still far faster than writing it yourself.

    1. 1

      Specify, in writing, before you open the model

      Name the exact series, the exact bar references, the exact condition, and the warm-up behaviour on the first bars. Save the text — it is the only independent document in this process.

    2. 2

      Generate against the specification

      Paste the specification as the whole instruction. Ask the model to raise anything ambiguous rather than guess. Reject and re-ask if it silently skipped a line.

    3. 3

      Audit the code against the specification

      Go line by line. For every historical reference, name the bar it resolves to and confirm that bar had closed when the signal is claimed. Delete anything you did not ask for.

    4. 4

      Plot the raw condition and replay it

      Plot the underlying boolean as its own series, then step through bar by bar in replay and watch when it turns on. A repaint is visible here and almost nowhere else.

    5. 5

      Hand-check a handful of signals against the candles

      Pick several signals across different market conditions and verify by eye that both the bar and the level match what the specification said. Only then does the code get to influence a decision.

    Specifying an Indicator So the Output Is Checkable

    A vague request produces code that cannot be wrong, because there is nothing to be wrong against. “Give me a breakout indicator” has no failure condition. “The close of the current bar is greater than the highest high of the previous twenty completed bars, where the current bar’s own high is excluded from that window” has exactly one meaning, and the code either implements it or does not.

    Four things have to be pinned down every time. The series: which price, which timeframe, regular session or extended. The bar reference: which bars are inside the window and whether the current bar is one of them. The condition: the comparison itself, stated as a sentence that has a true or false answer. And the warm-up: what the indicator outputs on the first bars, before enough history exists to evaluate the condition at all.

    That last one is quietly important and almost always omitted. On the opening bars of a chart there is no twenty-bar window. The code has to do something. If it silently returns an undefined value that later gets treated as a signal, you have a defect that only shows up on the left edge of the chart — which is the part everybody scrolls past. Specify it: on the first bars, the condition is false, not undefined.

    Being concrete costs one extra sentence per item. In Pine, “the highest high of the previous twenty completed bars” is not the same expression as “the highest high of the last twenty bars” — the second includes the bar you are standing on, and a close cannot exceed a maximum it is part of. That single distinction is the difference between an indicator that fires and one that never fires, and it is exactly the kind of thing a loose prompt leaves to chance.

    Name the series precisely: which price field, which timeframe, and whether the session is regular or extended.
    State whether the current bar is inside or outside every historical window you reference.
    Write the condition as a sentence with a true-or-false answer, not as a description of an intention.
    Define the warm-up explicitly — what the output is on the first bars before enough history exists.
    Say whether the signal is evaluated on every tick or only on confirmed bars, and require the code to show you where that is enforced.
    Ask the model to raise ambiguities instead of resolving them silently; the resolutions are where assumptions hide.
    Indicator specification prompt
    Write a TradingView Pine Script v6 indicator to the specification below.
    Follow it literally. If any part is ambiguous, ask me instead of guessing.
    Do not add features I did not ask for.
    
    SPECIFICATION
    Series: daily candles, regular session only.
    Condition: the close of the current bar is greater than the highest HIGH
      of the previous 20 COMPLETED bars. The current bar's own high must NOT
      be inside that 20-bar window.
    Output: a single boolean series named breakoutRaw, plotted in a separate
      pane as 1 when the condition is true and 0 when it is false. No shapes,
      no labels, no alerts yet.
    Warm-up: on any bar where 20 completed prior bars do not exist,
      breakoutRaw must be 0 - not na, not true.
    Timing: the condition must be evaluated only on confirmed bars. Add a
      comment naming the line that enforces this.
    
    AFTER THE CODE, answer these separately:
    1. List every line that references a bar other than the current one, and
       state the bar index each one resolves to.
    2. State which line prevents the value from changing after the bar closes.
    3. State what breakoutRaw equals on bar 1 of the chart, and why.

    When to use — Every time you ask for a new indicator or scan. Rewrite the SPECIFICATION block for your own condition and leave the three trailing questions exactly as they are.

    A good answer — Short code with no extra features, an explicit offset on the historical window so the current bar is excluded, a named line that handles the confirmed-bar rule, and three answers that match the specification rather than describing what the code happens to do.

    The Four Bugs AI Reliably Introduces

    Repainting is the first and worst. A signal repaints when it can change after the bar it appeared on has closed. The classic cause is evaluating a condition using values that are still moving — an unconfirmed bar’s close, or a higher-timeframe value that has not finished forming. On the historical part of the chart everything looks settled, because history is made of closed bars. Live, the arrow appears, vanishes, and reappears somewhere else.

    Look-ahead is the second. Here the code reads a value from a bar that had not completed at the moment the decision is claimed. In Pine this shows up most often in higher-timeframe requests, where a lookahead setting can hand you the weekly value before the week has finished. In Python it is usually a rolling window that includes the current row — a twenty-row maximum computed with no shift is a maximum the current row is inside, so a comparison against it is partly a comparison against itself.

    Off-by-one bar indexing is the third, and it is the most common of all because it is so easy to write and so hard to see. A reference to the previous bar’s value versus the current bar’s value differs by one character. Both compile. Both plot. One of them is the indicator you specified and the other is an indicator that is systematically one candle early or one candle late — which is more than enough to turn a useless idea into a beautiful one.

    The fourth is behaviour that differs on the live bar. The last bar on a chart updates continuously while it is forming; every other bar is frozen. Code that does not distinguish between the two runs one way over history and another way in real time. That is the bug that makes people say the indicator “worked in backtest” — it did, because in backtest every bar was already closed.

    A repainting signal moves its own history

    Nothing errors. The code compiles, plots, and lies about where the signal was.

    Live — the last candle is still formingsignal here — you act on itthe condition was checked againsta close that had not happened yetformingTwo candles later — the same chart, redrawnthe marker moved back two candlesthe dotted outline is where you tradedthat entry is no longer on the chartHistory is made of closed candles, so on the historical part of the chart a repaintingindicator looks flawless. That is the symptom, not the reassurance.
    The same indicator, live and after the fact. A repainting signal moves its own history, so the chart you review is not the chart you traded.
    BugWhat it isWhat you seeWhen it bites
    RepaintingA signal that changes after its bar has closedHistory looks near-perfect; live signals flickerThe first time you trade it live
    Look-aheadReads a bar that had not completed at decision timeA backtest with implausibly good entriesImmediately, and only if you check
    Off-by-one indexingWindow or reference shifted by a single barSignals consistently one candle early or lateQuietly, across every trade
    Live-bar divergenceDifferent behaviour on the forming bar than on historyBacktest and live results that will not reconcileAfter you have already sized up
    Four timing bugs, what each one looks like, and the moment you find out. None of them raises an error.

    Pro tip — When you suspect a timing bug, ask the model to state the bar index that every historical reference resolves to. Forcing it to name the index turns a hidden assumption into a sentence you can disagree with in two seconds.

    Auditing the Code You Were Handed

    The audit is a reading exercise with a fixed set of questions, not a general review. You are not asking whether the code is elegant. You are asking, for every line that touches a bar other than the current one, which bar it resolves to and whether that bar had closed at the moment the signal is claimed. Everything else in the file is secondary.

    Use the model to audit its own output, but constrain it hard. An unconstrained “review this code” request produces a rewrite, and a rewrite means you now have a second version you have not read either. Tell it explicitly not to improve anything, and require it to quote the exact line it is talking about in each answer. Quotation is what stops the audit from becoming a summary of what the code was supposed to do.

    Then read the answers sceptically, because this is still the same machine. The audit prompt is not a proof. What it does is force the timing assumptions out of the code and onto the page, where you can check them yourself against your specification. If an answer says a value is final on bar close and you cannot find the line that makes that true, the answer is wrong, not the question.

    Delete everything you did not ask for. Generated code arrives with helpful extras — an extra filter, a smoothing parameter, an alert condition, a second confirmation rule. Each one is a piece of logic you did not specify and cannot audit against anything. They also quietly do the thing you are trying to avoid: adding parameters until the historical chart looks good.

    Code audit prompt
    Here is a trading indicator. Do NOT improve it. Do NOT rewrite it. Do
    NOT add features. Your only job is to answer questions about it.
    
    <paste the code>
    
    Answer each question separately, quoting the exact line you are talking
    about:
    
    1. Which lines read a value that is not final until the bar has closed?
    2. For every reference to a bar other than the current one, state the bar
       index it resolves to, and whether that bar had completed at the moment
       the signal is claimed.
    3. Where does this behave differently on the live forming bar than on
       historical bars? If nowhere, quote the line that guarantees that.
    4. What is the output on the first bars of the chart, before there is
       enough history for the condition? Quote the line that handles it.
    5. Name every function or setting here whose value can change after the
       bar on which it was computed.
    6. List anything in this code that my specification did not ask for.
    
    If a question cannot be answered from the code alone, write "cannot
    determine from this code" instead of assuming. Do not tell me whether the
    strategy is good.

    When to use — Immediately after generation, before the code goes anywhere near a chart you intend to make decisions from — and again after any edit, however small.

    A good answer — Direct quotes of real lines, a named bar index for every historical reference, at least one honest “cannot determine from this code”, and a list of extras you can then delete.

    The code audit checklist

    Every item is a yes-or-no you can answer by pointing at a line. If you cannot point at the line, the answer is no.

    • Every bar reference is explicit, and you can name the bar index each one resolves to.
    • No condition is evaluated on a value that is still changing while the bar is open.
    • The confirmed-bar rule is enforced by a specific line you can point at, not assumed.
    • The first bars, before enough history exists, produce a defined value rather than a silent gap.
    • Any higher-timeframe data is requested without look-ahead, and you have checked the setting rather than trusted the default.
    • The raw condition is plotted as its own visible series, not hidden behind shapes or arrows.
    • Every parameter in the file appeared in your written specification; anything else has been deleted.
    • You have re-run this checklist after the most recent edit, not only after the first generation.

    Testing: Plot It, Replay It, Hand-Check It

    Testing generated trading code means something narrower than testing software. You are not checking that it runs. You are checking that what it says today about a past bar is identical to what it said on that bar at the time. Three techniques cover almost all of it, and none of them requires any tooling beyond the chart you already use.

    First, plot the raw condition as its own series. Not the arrows, not the shapes, not the entries — the underlying boolean, in a separate pane, as a line that is either up or down. Arrows hide things; a plotted condition does not. You will immediately see whether the condition is on far more often than it should be, or whether it turns on mid-bar and off again, which no arrow-based display will ever show you.

    Second, replay it bar by bar. Every serious charting platform has a bar-replay mode. Step forward one candle at a time through a stretch that contains several signals and watch when the plot changes. A repaint is obvious here and effectively invisible everywhere else: the condition turns on inside a forming bar and then turns off when the bar closes, or a past signal moves as you advance.

    Third, hand-check a handful of signals against the candles. Pick five or six across different conditions — a trending stretch, a sideways stretch, a gap, and the left edge of the chart. For each one, count back the bars yourself and verify the level. This is tedious and it is the step that catches off-by-one indexing, because an indicator that is systematically one candle early looks perfectly reasonable until you count.

    Plot the raw boolean condition in its own pane; arrows conceal exactly the behaviour you are hunting.
    Step through bar replay and watch whether the condition changes while a bar is still forming.
    Hand-count the bars for several signals — this, and only this, catches off-by-one indexing.
    Include the left edge of the chart in your sample, where warm-up bugs live.
    Test across different conditions — trending, sideways, and around a gap — not just where the indicator looks good.
    Any discrepancy between the historical plot and what replay shows is a bug, not a quirk to work around.

    Pro tip — Keep the specification document open next to the chart while you hand-check. You are comparing the chart against the specification, not against your memory of what you wanted — and memory is exactly what an attractive-looking indicator edits.

    Where AI-Assisted Coding Stops Being Useful

    There is a clean line between a tool that shows you something and a system that acts. Generated code that highlights bars matching a condition you specified is a labour saving device; you still read the chart and make the decision. Generated code that decides and executes is a system, and a system built from output you cannot fully audit is a system nobody is accountable for. That is a bad place to arrive at by accident, and it is very easy to arrive at by accident, one convenient addition at a time.

    Be equally sceptical of the second-order use: asking the model to improve a condition until the historical chart looks better. This is curve-fitting with a conversational interface. Every parameter tweaked in response to how history looks is a parameter fitted to that history, and the process is so frictionless that you can do in an afternoon what used to take weeks of self-deception.

    There is a language trap worth naming too. Ask a model whether your indicator is good and it will tell you it looks reasonable, because agreement is its default posture. Ask it which bar index a reference resolves to and it has to produce something specific and checkable. Keep every question about generated code in the second form, and the whole exercise stays honest.

    Used inside those limits, this is genuinely one of the strongest applications of AI for a trader. Writing a clean scan by hand takes hours if you code occasionally, and the language barrier stops a lot of people from testing ideas they could otherwise examine. Getting to a checkable draft in minutes is real. It is just that the minutes you save on writing get spent on auditing, and that trade is the point, not a tax on it.

    Keeping generated code honest

    Do

    • Write the specification down before you generate, and keep it as the reference for every audit.
    • Ask questions with checkable answers — bar indexes, line numbers, defined outputs.
    • Delete every feature the model added that you did not request.
    • Re-run the audit after every edit, including edits you made yourself.
    • Keep the output at the level of highlighting conditions, so a human still makes the decision.

    Don't

    • Ask whether the strategy is good; you will get agreement, not analysis.
    • Judge an indicator by whether its arrows look well placed on history.
    • Tune parameters until the historical chart improves — that is curve-fitting with extra steps.
    • Paste code you have not audited into anything that places, modifies or cancels an order.
    • Assume a second generation fixed the timing bug because the model said it did.

    Watch out — Code you cannot audit must not be connected to execution. The gap between an indicator that draws an arrow and a script that sends an order is a few lines and a great deal of accountability — and once orders are being placed by logic you have not read, there is no version of “I did not realise it repainted” that gets the money back.

    Common questions

    It can write Pine Script that compiles and plots, usually on the first attempt. Whether it works is a different question. The arithmetic is generally right; the timing frequently is not. Treat the first output as a draft that has repainting and look-ahead until you have checked each one against a written specification.

    Knowledge Check

    Question 1 of 3Score: 0

    Why is a repainting indicator worse than having no indicator at all?

    Rohit Singh — Mr. Chartist

    Written By

    Rohit Singh

    Mr. Chartist

    With 14+ years of experience in Indian financial markets, Rohit Singh (Mr. Chartist) is a SEBI Registered Research Analyst, Amazon #1 bestselling author, and the founder of Investology — a premium trading ecosystem trusted by a 1.5 Lakh+ strong community across India.

    INH000015297Full Bio

    Keep reading