Skip to content

Guide

How to Write a Python Indicator on TradingLead

To write a Python indicator on TradingLead, open Indicator Studio on a chart, choose Python, define calculate(bars, params) that returns a title, overlay flag and a list of plots with one value per candle, then click Run on chart. Compatible code using the standard library, NumPy or pandas runs in an isolated browser sandbox; the TradingLead Desktop app is needed for the TradingLead SDK and other packages.

This guide walks you through writing a custom indicator in plain Python and plotting it on a TradingLead chart — from a simple moving average to a multi-timeframe pandas example. You don't need a special SDK: TradingLead calls one function and draws what it returns.

Step 1: Open Studio in Python mode

  1. Go to tradinglead.in/charts, sign in and load a symbol — for example a Bank Nifty constituent on a 5-minute chart.
  2. Select the Indicator Studio tab in the bottom panel and choose Python.
  3. Optionally click New for a guided empty draft that explains the calculate(bars, params) contract.

Step 2: Understand the contract

TradingLead calls:

def calculate(bars, params):
    ...
  • bars — chronological list of candles. Each is a dict with time (Unix milliseconds), open, high, low, close, volume (None if unavailable).
  • params — a dictionary of your settings.

You return a dictionary:

  • title — the indicator name.
  • overlay — True to draw on the price chart, False for a separate pane.
  • plots — a list; each plot has a stable id, a readable title, a color and values with exactly one value per candle.
  • drawings — optional list of text, lines, rectangles, rays or arrows.

Step 3: Write a simple moving average

# @input {"id":"length","type":"int","default":20,"min":1,"max":500,"step":1,"title":"SMA length","group":"Calculation"}
# @input {"id":"line_color","type":"color","default":"#2962ff","title":"Line color"}

def calculate(bars, params):
    length = int(params.get("length", 20))
    closes = [bar["close"] for bar in bars]
    values = []
    running = 0.0
    for i, price in enumerate(closes):
        running += price
        if i >= length:
            running -= closes[i - length]
        values.append(running / length if i >= length - 1 else None)
    return {
        "title": f"SMA ({length})",
        "overlay": True,
        "plots": [{"id": "sma", "title": f"SMA ({length})",
                   "color": params.get("line_color", "#2962ff"), "values": values}],
        "drawings": [],
    }

Notes:

  • The first length - 1 values are None — that is the warmup period, and it's required so every candle has a value.
  • The # @input comments create a settings form with a number field and a colour picker. Supported types are int, float, bool, string, enum (dropdown), color and null. Declaring an input does nothing until you read it from params.

Step 4: Run it on the chart

Click Run on chart. In the browser, the code runs in an isolated Pyodide sandbox: packages load, network access is switched off, your function runs, and the sandbox is discarded. If the TradingLead desktop app is installed, it runs there automatically.

Errors and print output appear as diagnostics in Studio. If your code needs something the browser can't run — for example the tradinglead SDK — Studio explains why and links to the download page.

Step 5: Use pandas for an EMA pair

# @input {"id":"fast","type":"int","default":20,"min":2,"max":400,"title":"Fast EMA"}
# @input {"id":"slow","type":"int","default":50,"min":2,"max":400,"title":"Slow EMA"}
import pandas as pd

def calculate(bars, params):
    df = pd.DataFrame(bars)
    fast, slow = int(params.get("fast", 20)), int(params.get("slow", 50))
    ema_fast = df["close"].ewm(span=fast, adjust=False, min_periods=fast).mean()
    ema_slow = df["close"].ewm(span=slow, adjust=False, min_periods=slow).mean()
    return {
        "title": "EMA pair",
        "overlay": True,
        "plots": [
            {"id": "fast", "title": f"Fast EMA ({fast})", "color": "#26a69a", "values": ema_fast},
            {"id": "slow", "title": f"Slow EMA ({slow})", "color": "#ef5350", "values": ema_slow},
        ],
        "drawings": [],
    }

pandas Series and NumPy arrays are accepted directly; NaN values are treated as gaps. Learn more about the maths in EMA.

Step 6: Add a higher timeframe (optional)

Declare extra data in comments and accept a third context argument:

# @data {"id":"daily","timeframe":"1D","bars":1000}
import pandas as pd

def calculate(bars, params, context):
    chart = pd.DataFrame(bars)
    daily = pd.DataFrame(context["datasets"]["daily"]["bars"])
    daily["ema"] = daily["close"].ewm(span=20, adjust=False, min_periods=20).mean()
    aligned = pd.merge_asof(chart, daily[["closeTime", "ema"]],
                            left_on="time", right_on="closeTime", direction="backward")
    return {"title": "Daily EMA 20", "overlay": True,
            "plots": [{"id": "d_ema", "title": "Daily EMA (20)", "color": "#ff9800",
                       "values": aligned["ema"]}], "drawings": []}

Secondary datasets contain closed candles only from the same provider as your chart, and each candle has a closeTime. Joining on closeTime (not the daily open time) avoids using a day's close before it happened. A provider may return fewer candles than requested — check len(dataset["bars"]).

Step 7: Style, save and reuse

  • Use On chart → Style, or right-click the study and choose Indicator settings (named plots), to change each plot's colour, width, style and visibility. Apply style doesn't rerun Python; Apply & recalculate on the Inputs tab does.
  • Click Save. The indicator appears in the chart's Indicators dialog under My saved indicators.
  • Use Export library backup to keep a copy or move scripts to another browser.

Getting AI help

Open the AI prompt panel, choose Indicator, describe what you want, and copy the generated prompt into the AI you use. Your current code is included only if you tick Include current code and parameters. Paste the returned Python into the editor, review it, then run it.

Limits to keep in mind

  • Browser runtime: standard library, NumPy and pandas; no internet access.
  • Desktop runtime: 100,000 combined candles, 64 MiB input/output, 20,000 visual objects, 15 seconds per calculation, 512 MiB memory.
  • Oversized results fail with an explicit error rather than being silently truncated.

For strategies with entries and exits, see Backtest a strategy. Backtests are simulations, not guarantees, and nothing here is investment advice.

Steps

  1. Open https://tradinglead.in/charts and load a symbol and timeframe.
  2. Open the Indicator Studio tab and choose Python.
  3. Write def calculate(bars, params) returning title, overlay, plots and drawings.
  4. Add '# @input' comments for settings you want to edit in a form.
  5. Click Run on chart and check the result and any diagnostics.
  6. Name your plots clearly, then Save so the indicator appears under My saved indicators.

Limitations

  • Browser runtime: standard library, NumPy and pandas only; network disabled during execution.
  • Secondary datasets are closed candles from the same provider; no fallback to another exchange.
  • Desktop runtime limits include 15 seconds per calculation and 512 MiB memory.

Frequently asked questions

What does the bars argument contain?
A chronological list of dictionaries with time (Unix milliseconds), open, high, low, close and volume. Volume is None when unavailable.
How many values must each plot return?
Exactly one per candle in bars, in the same order. Use None (or NaN) for warmup candles and gaps.
Can I use pandas and NumPy?
Yes. In the browser runtime, the standard library, NumPy and pandas are available. NumPy arrays and pandas Series are accepted as plot values.
Why does Studio say Desktop is required?
Your code imports something the browser sandbox can't run, such as the tradinglead SDK/Strategy helpers, a native or OS-level module, or a package not available in Pyodide. Install the TradingLead Desktop app or remove that dependency.
How do I use daily data on an intraday chart?
Declare it with a comment like # @data {"id":"daily","timeframe":"1D","bars":1000} and accept a third context argument. Read context['datasets']['daily']['bars'] and align on each daily candle's closeTime to avoid lookahead.

Last updated: