Plot Styles in Indie Language

Currently, Indie supports the following plot styles:

  • Line – simple lines

  • Marker – dots, crosses, etc. on the chart

  • Steps – a line connecting points with horizontal steps

  • Histogram – bar charts with absolute-width lines

  • Columns – bar charts with relative widths (0.0 to 1.0 relative to bar spacing)

To use these styles, you need two tools: a decorator for compile-time description and a class representing the plot value returned by the indicator:

@line() - Line()
@steps() - Steps()
@histogram() - Histogram()
@columns() - Columns()
@fill() - Fill()
@marker - Marker()

All these entities are in the indie.plot package. Using them is easy, let’s look at some examples:

# indie:lang_version = 5
from indie import indicator, plot, color

@indicator('My Indie 1', overlay_main_pane=True)
@plot.steps(color=color.RED)
def Main(self):
    return plot.Steps(self.close[0], offset=-4)

If you don’t need runtime parameter settings (like color or offset), you can return a simple float for most classes (Line, Steps, Histogram, Columns, Marker), and it will be cast automatically:

# indie:lang_version = 5
from indie import indicator, plot, color

@indicator('My Indie 1', overlay_main_pane=True)
@plot.steps(color=color.RED)
def Main(self):
    return self.close[0]

If your indicator only draws Line plots with default settings, you can skip the decorator for an even simpler code:

# indie:lang_version = 5
from indie import indicator

@indicator('My Indie 1', overlay_main_pane=True)
def Main(self):
    return self.close[0]

This example is a “Hello World” for indicators. If this is your first indicator, try running it on our platform before moving on.

Lines

Lines are the most common plot style and a great starting point. Let’s create a simple SMA Crossing indicator that draws multiple lines.

We’ll enhance our previous example by adding two SMA lines:

  • Add @indie.plot.line(...) in the Main function decorator to declare and configure the line.
  • Return two floats with current line values for each bar.

We’ll use the SMA algorithm from Indie’s standard library. More on series data and algorithms can be found in our documentation.

Here’s the code:

# indie:lang_version = 5
from indie import indicator, plot, color
from indie.algorithms import Sma

@indicator('SMA Crossing', overlay_main_pane=True)
@plot.line(color=color.AQUA)
@plot.line(color=color.OLIVE)
def Main(self):
    sma_slow = Sma.new(self.close, 200)
    sma_fast = Sma.new(self.close, 50)
    return sma_slow[0], sma_fast[0]

Let’s move hardcoded constants to indicator parameters for UI adjustments:

# indie:lang_version = 5
from indie import indicator, plot, color, param
from indie.algorithms import Sma

@indicator('SMA Crossing', overlay_main_pane=True)
@plot.line(color=color.AQUA)
@plot.line(color=color.OLIVE)
@param.int('sma_len_slow', default=200)
@param.int('sma_len_fast', default=50)
def Main(self, sma_len_slow, sma_len_fast):
    sma_slow = Sma.new(self.close, sma_len_slow)
    sma_fast = Sma.new(self.close, sma_len_fast)
    return sma_slow[0], sma_fast[0]

Great job! Let’s make the indicator even better.

Markers

Let’s add markers for SMA line crosses.

The algorithm is already known:

  • Add a @plot.marker decorator,
  • Return an indie.plot.Marker object from Main:
# indie:lang_version = 5
from indie import indicator, plot, color, param
from indie.algorithms import Sma

@indicator('SMA Crossing', overlay_main_pane=True)
@plot.line(id='sma_slow', color=color.AQUA)
@plot.line(id='sma_fast', color=color.OLIVE)
@plot.fill('sma_slow', 'sma_fast', color=color.NAVY(0.3))
@plot.marker(style=plot.marker_style.CIRCLE,
    position=plot.marker_position.CENTER, size=7,
    display_options=plot.MarkerDisplayOptions(
        pane=True, status_line=False, price_label=False,
    ),
)
@param.int('sma_len_slow', default=200)
@param.int('sma_len_fast', default=50)
def Main(self, sma_len_slow, sma_len_fast):
    sma_slow = Sma.new(self.close, sma_len_slow)
    sma_fast = Sma.new(self.close, sma_len_fast)

    fill_color = color.GREEN(0.3)
    if sma_fast[0] > sma_slow[0]:
        fill_color = color.RED(0.3)

    marker_color = color.rgba(0, 0, 0, 0)
    if sma_fast[0] >= sma_slow[0] and sma_fast[1] < sma_slow[1]:
        marker_color = color.RED
    if sma_fast[0] <= sma_slow[0] and sma_fast[1] > sma_slow[1]:
        marker_color = color.GREEN

    return (sma_slow[0], sma_fast[0],
           plot.Fill(color=fill_color),
           plot.Marker(value=sma_slow[0], color=marker_color))

Notice the display_options parameter - this allows you to hide the marker values from the “pill” and from the ordinate axis:

    display_options=plot.MarkerDisplayOptions(
        pane=True, status_line=False, price_label=False,
    ),

Now it is much easier to detect line crosses, even if the crosses are very weak.

Columns

We created an SMA Crossing indicator. To highlight crossovers visually, let’s build a supporting indicator with columns in a separate pane.

An indicator by mustermann84 on our marketplace is free: Simple Crossover Signal Bars.

Here’s its code:

# indie:lang_version = 5
from indie import indicator, plot, color, MutSeriesF, level, line_style, param
from indie.algorithms import Sma

@indicator('Simple Crossover Signal Bars', overlay_main_pane=False)
@param.int('fast_ma_length', default=50, min=1, title='Fast MA Length')
@param.int('slow_ma_length', default=200, min=1, title='Slow MA Length')
@plot.columns(title='Crossover Signal')
@level(
    value=0, title='Zero', line_color=color.GRAY,
    line_style=line_style.DASHED, line_width=1,
)
def Main(self, fast_ma_length, slow_ma_length):
    # SMA calculating
    fast_ma = Sma.new(self.close, fast_ma_length)
    slow_ma = Sma.new(self.close, slow_ma_length)

    # Signal logic: 1 for bull сrossover, -1 for bear сrossover
    signal_value = MutSeriesF.new(init=0)

    if fast_ma[0] > slow_ma[0] and fast_ma[1] <= slow_ma[1]:
        signal_value[0] = 1  # Bull Signal
    elif fast_ma[0] < slow_ma[0] and fast_ma[1] >= slow_ma[1]:
        signal_value[0] = -1  # Bear Signal
    else:
        signal_value[0] = 0  # No signal

    # Different signals have different colors
    plot_color = (
        color.GREEN if signal_value[0] == 1
        else color.RED if signal_value[0] == -1
        else color.rgba(0, 0, 0, 0)  # Transparent, no signal
    )

    return plot.Columns(value=signal_value[0], color=plot_color)

It is much easier to search for line crosses with such indicator:

Histograms

Histograms in Indie are similar to columns but will gain more features in the future. Use histograms for data distributions or value differences, e.g., SMI Ergodic Oscillator shows the difference between TSI and its smoothed version:

# indie:lang_version = 5
from indie import indicator, format, param, plot, color
from indie.algorithms import Tsi, Ema

@indicator('SMIO', format=format.PRICE, precision=4)  # SMI Ergodic Oscillator
@param.int('long_len', default=20, min=1, title='Long Length')
@param.int('short_len', default=5, min=1, title='Short Length')
@param.int('signal_len', default=5, min=1, title='Signal Length')
@plot.histogram(color=color.RED)
def Main(self, long_len, short_len, signal_len):
    tsi = Tsi.new(self.close, long_len, short_len)
    return tsi[0] - Ema.new(tsi, signal_len)[0]

This built-in indicator is free for all users.

Steps

Steps displays data as step lines, ideal for discrete changes or support/resistance levels:

# indie:lang_version = 5
from indie import indicator, plot
from indie.algorithms import Lowest, Highest

@indicator('Support-Resistance', overlay_main_pane=True)
@plot.steps('support')
@plot.steps('resistance')
def Main(self):
    support_level = Lowest.new(self.low, 20)
    resistance_level = Highest.new(self.high, 20)
    return support_level[0], resistance_level[0]

We hope this guide helps you make the most of Indie’s plot styles when creating indicators. Experiment, build, and share your work with the community!