Accumulation/Distribution

Accumulation/Distribution
# indie:lang_version = 4
from indie import indicator, format, plot, color
from indie.algorithms import CumSum, Mfv


@indicator('Accum/Dist', format=format.VOLUME)  # Accumulation/Distribution
@plot(color=color.OLIVE)
def Main(self):
    return CumSum.new(Mfv.new())[0]

Average Day Range

Average Day Range
# indie:lang_version = 4
from indie import indicator, param, plot, color
from indie.algorithms import Sma


@indicator('ADR')  # Average Day Range
@param.int('length', default=14, min=1)
@plot(color=color.BLUE)
def Main(self, length):
    sma_high = Sma.new(self.high, length)
    sma_low = Sma.new(self.low, length)
    return sma_high[0] - sma_low[0]

Advance/Decline Ratio (Bars)

Advance/Decline Ratio (Bars)
# indie:lang_version = 4
from math import isclose
from indie import indicator, param, level, color, plot, MutSeriesF
from indie.algorithms import Sum


@indicator('ADR_B')  # Advance/Decline Ratio (Bars)
@param.int('length', default=9, min=1)
@level(1, line_color=color.GRAY, title='Equality Line')
@plot(color=color.BLUE, title='ADR_B')
def Main(self, length):
    is_up = (self.close[0] - self.open[0]) >= 0.0

    is_down_ints = MutSeriesF.new(0 if is_up else 1)
    is_up_ints = MutSeriesF.new(1 if is_up else 0)

    down_bars = Sum.new(is_down_ints, length)
    up_bars = Sum.new(is_up_ints, length)

    res = up_bars[0]
    if not isclose(down_bars[0], 0):
        res = up_bars[0] / down_bars[0]
    return res

Average Directional Index

Average Directional Index
# indie:lang_version = 4
from indie import indicator, param, plot, color
from indie.algorithms import Adx


@indicator('ADX')  # Average Directional Index
@param.int('adx_len', default=14, min=1, title='ADX Smoothing')
@param.int('di_len', default=14, min=1, title='DI Length')
@plot(color=color.RED)
def Main(self, adx_len, di_len):
    _, adx, _ = Adx.new(adx_len, di_len)
    return adx[0]

Awesome Oscillator

Awesome Oscillator
# indie:lang_version = 4
from indie import indicator, plot, plot_style, color, Plot, MutSeriesF
from indie.algorithms import Sma


# TODO: Support palettes of colors in indicators
@indicator('AO')  # Awesome Oscillator
@plot(style=plot_style.COLUMNS)
def Main(self):
    ao = MutSeriesF.new(Sma.new(self.hl2, 5)[0] - Sma.new(self.hl2, 34)[0])
    d = ao[0] - ao[1]
    c = color.MAROON if d <= 0 else color.GREEN
    return Plot(ao[0], color=c)

Aroon

Aroon
# indie:lang_version = 4
from indie import indicator, param, plot, color
from indie.algorithms import SinceLowest, SinceHighest


@indicator('Aroon')
@param.int('length', default=14, min=1)
@plot(color=color.BLUE, title='Aroon Down')
@plot(color=color.YELLOW, title='Aroon Up')
def Main(self, length):
    lo_offset = SinceLowest.new(self.low, length)
    hi_offset = SinceHighest.new(self.high, length)

    down = 100 * (length - lo_offset[0]) / length
    up = 100 * (length - hi_offset[0]) / length

    return down, up

Arnaud Legoux Moving Average

Arnaud Legoux Moving Average
# indie:lang_version = 4
from math import exp, pow, nan, isclose
from indie import indicator, param, plot, color


@indicator('ALMA', overlay_main_pane=True)  # Arnaud Legoux Moving Average
@param.int('window_size', default=9, min=1, title='Window Size')
@param.float('offset', default=0.85)
@param.float('sigma', default=6.0, min=0.001)
@plot(color=color.BLUE)
def Main(self, window_size, offset, sigma):
    m = offset * (window_size - 1)
    s = window_size / sigma
    sum, norm = 0.0, 0.0
    for i in range(window_size):
        weight = exp(-1 * pow(i - m, 2) / (2 * pow(s, 2)))
        norm += weight
        sum += self.close[window_size - i - 1] * weight
    res = nan
    if not isclose(norm, 0):
        res = sum / norm
    return res

Average True Range

Average True Range
# indie:lang_version = 4
from indie import indicator, param, plot, color
from indie.algorithms import Atr


@indicator('ATR')  # Average True Range
@param.int('length', default=14, min=1)
@param.string('smoothing', default='RMA', options=['RMA', 'SMA', 'EMA', 'WMA'], title='Smoothing')
@plot(color=color.RED)
def Main(self, length, smoothing):
    return Atr.new(length, smoothing)[0]

Bollinger Bands

Bollinger Bands
# indie:lang_version = 4
from indie import indicator, param, source, plot, color, fill, Plot, Fill
from indie.algorithms import Bb


@indicator('BB', overlay_main_pane=True)  # Bollinger Bands
@param.int('length', default=20, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@param.float('mult', default=2.0, min=0.001, max=50.0, title='StdDev')
@param.int('offset', default=0, min=-500, max=500)
@plot('lower', color=color.BLUE, title='Lower')
@plot(color=color.RED, title='Basis')
@plot('upper', color=color.BLUE, title='Upper')
@fill('lower', 'upper', color=color.AQUA(0.05), title='Background')
def Main(self, length, src, mult, offset):
    (lower, middle, upper) = Bb.new(src, length, mult)
    return (
        Plot(lower[0], offset=offset),
        Plot(middle[0], offset=offset),
        Plot(upper[0], offset=offset),
        Fill(),
    )

Bull Bear Power

Bull Bear Power
# indie:lang_version = 4
from indie import indicator, param, plot, color
from indie.algorithms import Ema


@indicator('BBP')  # Bull Bear Power
@param.int('length', default=13, min=1)
@plot(color=color.BLUE)
def Main(self, length):
    bear_power = self.low[0] - Ema.new(self.close, length)[0]
    bull_power = self.high[0] - Ema.new(self.close, length)[0]
    return bull_power + bear_power

Bollinger Bands %B

Bollinger Bands %B
# indie:lang_version = 4
from math import nan, isclose
from indie import indicator, param, source, band, color, level, plot
from indie.algorithms import Bb


@indicator('BB %B')  # Bollinger Bands %B
@param.int('length', default=20, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@param.float('mult', default=2.0, min=0.001, max=50.0, title='StdDev')
@band(0, 1, line_color=color.GRAY, fill_color=color.AQUA(0.1), title='Background')
@level(0.5, line_color=color.GRAY, title='Middle Band')
@plot(color=color.AQUA, title='Bollinger Bands %B')
def Main(self, length, src, mult):
    (lower, _, upper) = Bb.new(src, length, mult)
    bbr = nan
    if not isclose(upper[0] - lower[0], 0):
        bbr = (src[0] - lower[0]) / (upper[0] - lower[0])
    return bbr

Bollinger Bands Width

Bollinger Bands Width
# indie:lang_version = 4
from math import nan, isclose
from indie import indicator, param, source, plot, color
from indie.algorithms import Bb


@indicator('BBW')  # Bollinger Bands Width
@param.int('length', default=20, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@param.float('mult', default=2.0, min=0.001, max=50.0, title='StdDev')
@plot(color=color.TEAL)
def Main(self, length, src, mult):
    (lower, middle, upper) = Bb.new(src, length, mult)
    bww = nan
    if not isclose(middle[0], 0):
        bww = (upper[0] - lower[0]) / middle[0]
    return bww

Balance of Power

Balance of Power
# indie:lang_version = 4
from math import nan, isclose
from indie import indicator, plot, color


@indicator('BoP')  # Balance of Power
@plot(color=color.RED)
def Main(self):
    res = nan
    if not isclose(self.high[0] - self.low[0], 0):
        res = (self.close[0] - self.open[0]) / (self.high[0] - self.low[0])
    return res

Commodity Channel Index

Commodity Channel Index
# indie:lang_version = 4
from indie import indicator, param, source, band, color, level, plot
from indie.algorithms import Cci


@indicator('CCI')  # Commodity Channel Index
@param.int('length', default=20, min=1)
@param.source('src', default=source.HLC3, title='Source')
@band(-100, 100, line_color=color.GRAY, fill_color=color.AQUA(0.1), title='Background')
@level(0, line_color=color.GRAY(0.5), title='Middle Band')
@plot(color=color.BLUE, title='CCI')
def Main(self, length, src):
    return Cci.new(src, length)[0]
    # TODO: implement smoothing when display.none is supported

Chaikin Oscillator

Chaikin Oscillator
# indie:lang_version = 4
from indie import indicator, format, param, level, color, plot
from indie.algorithms import CumSum, Mfv, Ema


@indicator('Chaikin Osc', format=format.VOLUME)  # Chaikin Oscillator
@param.int('fast_length', default=3, min=1, title='Fast Length')
@param.int('slow_length', default=10, min=1, title='Slow Length')
@level(0, line_color=color.GRAY, title='Zero')
@plot(color=color.RED, title='Chaikin Oscillator')
def Main(self, fast_length, slow_length):
    ad = CumSum.new(Mfv.new())
    ch_osc = Ema.new(ad, fast_length)[0] - Ema.new(ad, slow_length)[0]
    return ch_osc

Chande Momentum Oscillator

Chande Momentum Oscillator
# indie:lang_version = 4
from math import nan, isclose
from indie import indicator, param, source, level, color, plot, MutSeriesF
from indie.algorithms import Change, Sum


@indicator('Chande MO')  # Chande Momentum Oscillator
@param.int('length', default=9, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@level(0, line_color=color.GRAY, title='Zero')
@plot(color=color.BLUE, title='Chande MO')
def Main(self, length, src):
    momm = Change.new(src)[0]

    m1 = momm if momm >= 0 else 0
    m2 = 0.0 if momm >= 0 else -momm

    sm1 = Sum.new(MutSeriesF.new(m1), length)[0]
    sm2 = Sum.new(MutSeriesF.new(m2), length)[0]

    res = nan
    if not isclose(sm1 + sm2, 0):
        res = 100 * (sm1 - sm2) / (sm1 + sm2)
    return res

Choppiness Index

Choppiness Index
# indie:lang_version = 4
from math import nan, isclose, log10
from indie import indicator, param, band, color, level, plot, Plot
from indie.algorithms import Highest, Lowest, Sum, Atr


@indicator('CHOP')  # Choppiness Index
@param.int('length', default=14, min=1)
@param.int('offset', default=0, min=-500, max=500)
@band(38.2, 61.8, line_color=color.GRAY, fill_color=color.AQUA(0.1), title='Background')
@level(50, line_color=color.GRAY(0.5), title='Middle Band')
@plot(color=color.BLUE, title='CHOP')
def Main(self, length, offset):
    hl = Highest.new(self.high, length)[0] - Lowest.new(self.low, length)[0]
    res = nan
    if not isclose(hl, 0) and not isclose(log10(length), 0):
        res = 100 * log10(Sum.new(Atr.new(1), length)[0] / hl) / log10(length)
    return Plot(res, offset=offset)

Chop Zone

Chop Zone
# indie:lang_version = 4
from math import nan, isclose, sqrt, isnan, acos, pi
from indie import indicator, plot, plot_style, color, Color, Plot
from indie.algorithms import Highest, Lowest, Ema


@indicator('Chop Zone')
@plot(style=plot_style.COLUMNS)
def Main(self):
    color_turquoise = color.rgba(38, 198, 218)
    color_dark_green = color.rgba(67, 160, 71)
    color_pale_green = color.rgba(165, 214, 167)
    color_lime = color.rgba(0, 150, 136)
    color_dark_red = color.rgba(213, 0, 0)
    color_red = color.rgba(233, 30, 99)
    color_orange = color.rgba(255, 109, 0)
    color_light_orange = color.rgba(255, 183, 77)
    color_yellow = color.rgba(253, 216, 53)

    periods = 30
    highest_high = Highest.new(self.high, periods)[0]
    lowest_low = Lowest.new(self.low, periods)[0]
    span = nan
    if not isclose(highest_high - lowest_low, 0):
        span = 25 / (highest_high - lowest_low) * lowest_low

    ema34 = Ema.new(self.close, 34)
    y2_ema34 = nan
    if not isclose(self.hlc3[0], 0):
        y2_ema34 = (ema34[1] - ema34[0]) / self.hlc3[0] * span

    c_ema34 = sqrt(1 + y2_ema34 ** 2)
    ema_angle_1 = nan
    if not isnan(c_ema34):
        ema_angle_1 = round(180 * acos(1 / c_ema34) / pi)  # or ema_angle_1 = round(180 * atan(y2_ema34) / pi)
    ema_angle = -ema_angle_1 if y2_ema34 > 0 else ema_angle_1

    # TODO: Find out why comparison is in radians
    chop_zone_color: Color
    if ema_angle >= 5:
        chop_zone_color = color_turquoise
    elif ema_angle >= 3.57:
        chop_zone_color = color_dark_green
    elif ema_angle >= 2.14:
        chop_zone_color = color_pale_green
    elif ema_angle >= 0.71:
        chop_zone_color = color_lime
    elif ema_angle <= -5:
        chop_zone_color = color_dark_red
    elif ema_angle <= -3.57:
        chop_zone_color = color_red
    elif ema_angle <= -2.14:
        chop_zone_color = color_orange
    elif ema_angle <= -0.71:
        chop_zone_color = color_light_orange
    else:  # ema_angle > -0.71 and ema_angle < 0.71
        chop_zone_color = color_yellow
    return Plot(1, color=chop_zone_color)

Chande Kroll Stop

Chande Kroll Stop
# indie:lang_version = 4
from indie import indicator, param, plot, color, MutSeriesF
from indie.algorithms import Atr, Lowest, Highest


@indicator('Chande KS', overlay_main_pane=True)  # Chande Kroll Stop
@param.int('p', default=10, min=1)
@param.int('x', default=1, min=1)
@param.int('q', default=9, min=1)
@plot(color=color.BLUE, title='Stop Long')
@plot(color=color.MAROON, title='Stop Short')
def Main(self, p, x, q):
    atr = Atr.new(p)[0]
    first_low_stop = MutSeriesF.new(Lowest.new(self.low, p)[0] + x * atr)
    first_high_stop = MutSeriesF.new(Highest.new(self.high, p)[0] - x * atr)
    stop_long = Lowest.new(first_low_stop, q)
    stop_short = Highest.new(first_high_stop, q)
    return stop_long[0], stop_short[0]

Chaikin Money Flow

Chaikin Money Flow
# indie:lang_version = 4
from math import isclose, nan
from indie import indicator, param, level, color, plot
from indie.algorithms import Sum, Mfv


@indicator('CMF')  # Chaikin Money Flow
@param.int('length', default=20, min=1, title='Length')
@level(0, line_color=color.GRAY, title='Zero')
@plot(color=color.GREEN, title='MF')
def Main(self, length):
    mfv_sum = Sum.new(Mfv.new(), length)[0]
    vol_sum = Sum.new(self.volume, length)[0]

    res = nan
    if not isclose(vol_sum, 0):
        res = mfv_sum / vol_sum
    return res

Coppock Curve

Coppock Curve
# indie:lang_version = 4
from indie import indicator, param, plot, color, MutSeriesF
from indie.algorithms import Roc, Wma


@indicator('Coppock')  # Coppock Curve
@param.int('wma_length', default=10, min=1, title='WMA Length')
@param.int('long_roc_length', default=14, min=1, title='Long RoC Length')
@param.int('short_roc_length', default=11, min=1, title='Short RoC Length')
@plot(color=color.BLUE)
def Main(self, wma_length, long_roc_length, short_roc_length):
    l_roc = Roc.new(self.close, long_roc_length)[0]
    s_roc = Roc.new(self.close, short_roc_length)[0]
    curve = Wma.new(MutSeriesF.new(l_roc + s_roc), wma_length)[0]
    return curve

Connors RSI

Connors RSI
# indie:lang_version = 4
from math import isnan
from indie import indicator, param, band, color, level, plot, MutSeriesF
from indie.algorithms import Rsi, PercentRank, Roc


def nan_to_zero(val: float) -> float:
    return 0 if isnan(val) else val


@indicator('CRSI')  # Connors RSI
@param.int('len_rsi', default=3, min=1, title='RSI Length')
@param.int('len_up_down', default=2, min=1, title='UpDown Length')
@param.int('len_roc', default=100, min=1, title='ROC Length')
@band(30, 70, line_color=color.GRAY, fill_color=color.AQUA(0.1), title='Background')
@level(50, line_color=color.GRAY(0.5), title='Middle Band')
@plot(color=color.BLUE, title='CRSI')
def Main(self, len_rsi, len_up_down, len_roc):
    is_equal = self.close[0] == self.close[1]
    is_growing = self.close[0] > self.close[1]
    ud = MutSeriesF.new(0)  # why not mut_series(init=0)
    if is_growing:
        if nan_to_zero(ud[1]) <= 0:  # why not equal nan_to_zero(ud)[1]
            ud[0] = 1
        else:
            ud[0] = nan_to_zero(ud[1]) + 1
    elif not is_equal:
        if nan_to_zero(ud[1]) >= 0:
            ud[0] = -1
        else:
            ud[0] = nan_to_zero(ud[1]) - 1

    rsi = Rsi.new(self.close, len_rsi)[0]
    ud_rsi = Rsi.new(ud, len_up_down)[0]
    pr = PercentRank.new(Roc.new(self.close, 1), len_roc)[0]
    return (rsi + ud_rsi + pr) / 3

Donchian Channels

Donchian Channels
# indie:lang_version = 4
from indie import indicator, param, plot, color, fill, Fill
from indie.algorithms import Lowest, Highest, Donchian


@indicator('DC', overlay_main_pane=True)  # Donchian Channels
@param.int('length', default=20, min=1)
@plot('lower', color=color.BLUE, title='Lower')
@plot(color=color.RED, title='Basis')
@plot('upper', color=color.BLUE, title='Upper')
@fill('lower', 'upper', color=color.AQUA(0.05), title='Background')
def Main(self, length):
    lower = Lowest.new(self.low, length)
    upper = Highest.new(self.high, length)
    basis = Donchian.new(length)
    return lower[0], basis[0], upper[0], Fill()

Double EMA

Double EMA
# indie:lang_version = 4
from indie import indicator, param, source, plot, color
from indie.algorithms import Ema


@indicator('DEMA', overlay_main_pane=True)  # Double EMA
@param.int('length', default=9, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@plot(color=color.GREEN)
def Main(self, length, src):
    ema1 = Ema.new(src, length)
    ema2 = Ema.new(ema1, length)
    return 2 * ema1[0] - ema2[0]

Directional Movement Index

Directional Movement Index
# indie:lang_version = 4
from indie import indicator, format, param, plot, color
from indie.algorithms import Adx


@indicator('DMI', format=format.PRICE, precision=4)  # Directional Movement Index
@param.int('adx_len', default=14, min=1, title='ADX Smoothing')
@param.int('di_len', default=14, min=1, title='DI Length')
@plot(color=color.MAROON, title='-DI')
@plot(color=color.RED, title='ADX')
@plot(color=color.BLUE, title='+DI')
def Main(self, adx_len, di_len):
    minus, adx, plus = Adx.new(adx_len, di_len)
    return minus[0], adx[0], plus[0]

Detrended Price Oscillator

Detrended Price Oscillator
# indie:lang_version = 4
from indie import indicator, param, level, color, plot, Plot
from indie.algorithms import Sma


@indicator('DPO')  # Detrended Price Oscillator
@param.int('length', default=21, min=1)
@param.bool('centered', default=False, title='Centered')
@level(0, line_color=color.GRAY, title='Zero')
@plot(color=color.GREEN, title='Detrended Price Oscillator')
def Main(self, length, centered):
    bars_back = length // 2 + 1
    ma = Sma.new(self.close, length)
    dpo = self.close[bars_back] - ma[0] if centered else self.close[0] - ma[bars_back]
    return Plot(dpo, offset=-bars_back if centered else 0)

Elders Force Index

Elders Force Index
# indie:lang_version = 4
from indie import indicator, format, param, level, color, plot, MutSeriesF
from indie.algorithms import Change, Ema


@indicator('EFI', format=format.VOLUME)  # Elders Force Index
@param.int('length', default=13, min=1)
@level(0, line_color=color.GRAY, title='Zero')
@plot(color=color.RED, title='Elders Force Index')
def Main(self, length):
    efi = Change.new(self.close)[0] * self.volume[0]
    return Ema.new(MutSeriesF.new(efi), length)[0]

Exponential Moving Average

Exponential Moving Average
# indie:lang_version = 4
from indie import indicator, param, source, plot, color, Plot
from indie.algorithms import Ema


@indicator('EMA', overlay_main_pane=True)  # Moving Average Exponential
@param.int('length', default=9, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@param.int('offset', default=0, min=-500, max=500, title='Offset')
@plot(color=color.BLUE)
def Main(self, length, src, offset):
    ema = Ema.new(src, length)
    return Plot(ema[0], offset=offset)
    # TODO: implement smoothing when display.none is supported

Envelope

Envelope
# indie:lang_version = 4
from indie import indicator, param, source, plot, color, fill, Fill
from indie.algorithms import Ema, Sma


@indicator('Env', overlay_main_pane=True)  # Envelope
@param.int('length', default=20, min=1)
@param.float('percent', default=10.0)
@param.source('src', default=source.CLOSE, title='Source')
@param.bool('exponential', default=False)
@plot('lower', color=color.BLUE, title='Lower')
@plot('basis', color=color.RED, title='Basis')
@plot('upper', color=color.BLUE, title='Upper')
@fill('lower', 'upper', color=color.AQUA(0.05), title='Background')
def Main(self, length, percent, src, exponential):
    basis = 0.0
    if exponential:
        basis = Ema.new(src, length)[0]
    else:
        basis = Sma.new(src, length)[0]
    k = percent / 100.0
    upper = basis * (1 + k)
    lower = basis * (1 - k)
    return lower, basis, upper, Fill()

Ease of Movement

Ease of Movement
# indie:lang_version = 4
from math import nan, isclose
from indie import indicator, format, param, plot, color, MutSeriesF
from indie.algorithms import Change, Sma


@indicator('EOM', format=format.VOLUME)  # Ease of Movement
@param.int('length', default=14, min=1)
@param.int('divisor', default=10000, min=1)
@plot(color=color.GREEN)
def Main(self, length, divisor):
    eom = nan
    if not isclose(self.volume[0], 0):
        eom = divisor * Change.new(self.hl2)[0] * (self.high[0] - self.low[0]) / self.volume[0]
    return Sma.new(MutSeriesF.new(eom), length)[0]

Fisher Transform

Fisher Transform
# indie:lang_version = 4
from math import isnan, isclose, nan, log
from indie import indicator, param, level, color, plot, MutSeriesF
from indie.algorithms import Highest, Lowest


def nan_to_zero(val: float) -> float:
    return 0 if isnan(val) else val


def round(val: float) -> float:
    if val > 0.99:
        val = 0.999
    elif val < -0.99:
        val = -0.999
    return val


@indicator('Fisher')  # Fisher Transform
@param.int('length', default=9, min=1)
@level(-1.5, line_color=color.RED, title='-1.5')
@level(-0.75, line_color=color.GRAY, title='-0.75')
@level(0, line_color=color.RED, title='0')
@level(0.75, line_color=color.GRAY, title='0.75')
@level(1.5, line_color=color.RED, title='1.5')
@plot(color=color.BLUE, title='Fisher')
@plot(color=color.MAROON, title='Trigger')
def Main(self, length):
    high = Highest.new(self.hl2, length)[0]
    low = Lowest.new(self.hl2, length)[0]

    value = MutSeriesF.new(init=0.0)
    if not isclose(high - low, 0):
        value[0] = round(0.66 * ((self.hl2[0] - low) / (high - low) - 0.5) + \
                                 0.67 * nan_to_zero(value[1]))
    fish = MutSeriesF.new(init=nan)
    if not isclose(1 - value[0], 0):
        fish[0] = 0.5 * log((1 + value[0]) / (1 - value[0])) + 0.5 * nan_to_zero(fish[1])
    return fish[0], fish[1]

Hull Moving Average

Hull Moving Average
# indie:lang_version = 4
from math import floor, sqrt
from indie import indicator, param, source, plot, color, MutSeriesF
from indie.algorithms import Wma


@indicator('HMA', overlay_main_pane=True)  # Hull Moving Average
@param.int('length', default=9, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@plot(color=color.BLUE)
def Main(self, length, src):
    wma_l2 = Wma.new(src, length // 2)[0]
    wma_l = Wma.new(src, length)[0]
    return Wma.new(MutSeriesF.new(2 * wma_l2 - wma_l), floor(sqrt(length)))[0]

Ichimoku Cloud

Ichimoku Cloud
# indie:lang_version = 4
from indie import SeriesF, MutSeriesF, indicator, param, plot, color, fill, Plot, Fill
from indie.algorithms import Donchian


def mean(price1: SeriesF, price2: SeriesF) -> float:
    # TODO: implement @algorithm avg with variable number of arguments in indie.algorithms
    return (price1[0] + price2[0]) / 2


# TODO: Add palette of colors
@indicator('Ichimoku', overlay_main_pane=True)  # Ichimoku Cloud
@param.int('conversion_periods', default=9, min=1, title='Conversion Line Length')
@param.int('base_periods', default=26, min=1, title='Base Line Length')
@param.int('lagging_span_2_periods', default=52, min=1, title='Leading Span B Length')
@param.int('displacement', default=26, min=1, title='Lagging Span')
@plot(color=color.BLUE, title='Conversion Line')
@plot(color=color.RED, title='Base Line')
@plot(color=color.GREEN, title='Lagging Span')
@plot('ls_a', color=color.GREEN(0.6), title='Leading Span A')
@plot('ls_b', color=color.RED(0.6), title='Leading Span B')
@fill('ls_a', 'ls_b', title='Background')
def Main(self, conversion_periods, base_periods, lagging_span_2_periods, displacement):
    conversion_line = Donchian.new(conversion_periods)
    base_line = Donchian.new(base_periods)
    lead_line1 = mean(conversion_line, base_line)
    lead_line2 = Donchian.new(lagging_span_2_periods)[0]
    fill_color = color.GREEN(0.1) if lead_line1 > lead_line2 else color.RED(0.1)

    return (
        conversion_line[0],
        base_line[0],
        Plot(self.close[0], offset=-displacement + 1),
        Plot(lead_line1, offset=displacement - 1),
        Plot(lead_line2, offset=displacement - 1),
        Fill(offset=displacement - 1, color=fill_color),
    )

Keltner Channels

Keltner Channels
# indie:lang_version = 4
from indie import indicator, param, source, plot, color, fill, MutSeriesF, Fill
from indie.algorithms import Ma, Tr, Atr, Rma


@indicator('KC', overlay_main_pane=True)  # Keltner Channels
@param.int('length', default=20, min=1)
@param.float('mult', default=2.0, title='Multiplier')
@param.source('src', default=source.CLOSE, title='Source')
@param.bool('exp', default=True, title='Use Exponential MA')
@param.string('bands_style', default='Average True Range', title='Bands Style',
              options=['Average True Range', 'True Range', 'Range'])
@param.int('atr_length', default=10, min=1, title='ATR Length')
@plot('lower', color=color.BLUE, title='Lower')
@plot(color=color.BLUE, title='Basis')
@plot('upper', color=color.BLUE, title='Upper')
@fill('lower', 'upper', color=color.AQUA(0.05), title='Background')
def Main(self, length, mult, src, exp, bands_style, atr_length):
    ma = Ma.new(src, length, 'EMA' if exp else 'SMA')[0]
    range_ma = 0.0
    if bands_style == 'True Range':
        range_ma = Tr.new(True)[0]
    elif bands_style == 'Average True Range':
        range_ma = Atr.new(atr_length)[0]
    else:  # bands_style == 'Range'
        range_ma = Rma.new(MutSeriesF.new(self.high[0] - self.low[0]), length)[0]
    upper = ma + range_ma * mult
    lower = ma - range_ma * mult
    return lower, ma, upper, Fill()

Klinger Oscillator

Klinger Oscillator
# indie:lang_version = 4
from indie import indicator, format, plot, color, MutSeriesF
from indie.algorithms import Change, Ema


@indicator('Klinger Osc', format=format.VOLUME)  # Klinger Oscillator
@plot(color=color.BLUE, title='KO')
@plot(color=color.GREEN, title='Signal')
def Main(self):
    sv = MutSeriesF.new(self.volume[0] if Change.new(self.hlc3)[0] >= 0 else -self.volume[0])
    kvo = MutSeriesF.new(Ema.new(sv, 34)[0] - Ema.new(sv, 55)[0])
    sig = Ema.new(kvo, 13)
    return kvo[0], sig[0]

Know Sure Thing

Know Sure Thing
# indie:lang_version = 4
from indie import algorithm, MutSeriesF, SeriesF, indicator, format, param, level, color, plot
from indie.algorithms import Sma, Roc


@algorithm
def SmaRoc(self, roc_len: int, sma_len: int) -> SeriesF:
    return Sma.new(Roc.new(self.ctx.close, roc_len), sma_len)


@indicator('KST', format=format.PRICE, precision=4)  # Know Sure Thing
@param.int('roc_len1', default=10, min=1, title='ROC Length #1')
@param.int('roc_len2', default=15, min=1, title='ROC Length #2')
@param.int('roc_len3', default=20, min=1, title='ROC Length #3')
@param.int('roc_len4', default=30, min=1, title='ROC Length #4')
@param.int('sma_len1', default=10, min=1, title='SMA Length #1')
@param.int('sma_len2', default=10, min=1, title='SMA Length #2')
@param.int('sma_len3', default=10, min=1, title='SMA Length #3')
@param.int('sma_len4', default=15, min=1, title='SMA Length #4')
@param.int('sig_len', default=9, min=1, title='Signal Line Length')
@level(0, line_color=color.GRAY, title='Zero')
@plot(color=color.GREEN, title='KST')
@plot(color=color.RED, title='Signal')
def Main(self, roc_len1, roc_len2, roc_len3, roc_len4, sma_len1, sma_len2, sma_len3, sma_len4, sig_len):
    kst = 1 * SmaRoc.new(roc_len1, sma_len1)[0] + \
          2 * SmaRoc.new(roc_len2, sma_len2)[0] + \
          3 * SmaRoc.new(roc_len3, sma_len3)[0] + \
          4 * SmaRoc.new(roc_len4, sma_len4)[0]
    sig = Sma.new(MutSeriesF.new(kst), sig_len)[0]
    return kst, sig

Moving Average Ribbon

Moving Average Ribbon
# indie:lang_version = 4
from math import nan
from indie import indicator, param, source, plot, color
from indie.algorithms import Ma


@indicator('MA Ribbon', overlay_main_pane=True)  # Moving Average Ribbon
@param.bool('ma1', default=True, title='Show MA №1')
@param.string('ma1_type', default='SMA', title='MA №1 Type', options=['SMA', 'EMA', 'SMMA (RMA)', 'WMA', 'VWMA'])
@param.source('ma1_source', default=source.CLOSE, title='MA №1 Source')
@param.int('ma1_length', default=20, min=1, title='MA №1 Length')
@param.bool('ma2', default=True, title='Show MA №2')
@param.string('ma2_type', default='SMA', title='MA №2 Type', options=['SMA', 'EMA', 'SMMA (RMA)', 'WMA', 'VWMA'])
@param.source('ma2_source', default=source.CLOSE, title='MA №2 Source')
@param.int('ma2_length', default=50, min=1, title='MA №2 Length')
@param.bool('ma3', default=True, title='Show MA №3')
@param.string('ma3_type', default='SMA', title='MA №3 Type', options=['SMA', 'EMA', 'SMMA (RMA)', 'WMA', 'VWMA'])
@param.source('ma3_source', default=source.CLOSE, title='MA №3 Source')
@param.int('ma3_length', default=100, min=1, title='MA №3 Length')
@param.bool('ma4', default=True, title='Show MA №4')
@param.string('ma4_type', default='SMA', title='MA №4 Type', options=['SMA', 'EMA', 'SMMA (RMA)', 'WMA', 'VWMA'])
@param.source('ma4_source', default=source.CLOSE, title='MA №4 Source')
@param.int('ma4_length', default=200, min=1, title='MA №4 Length')
@plot(color=color.rgba(246, 195, 9), title='MA №1')
@plot(color=color.rgba(251, 152, 0), title='MA №2')
@plot(color=color.rgba(251, 101, 0), title='MA №3')
@plot(color=color.rgba(246, 12, 12), title='MA №4')
def Main(self, ma1, ma1_type, ma1_source, ma1_length, \
         ma2, ma2_type, ma2_source, ma2_length, \
         ma3, ma3_type, ma3_source, ma3_length, \
         ma4, ma4_type, ma4_source, ma4_length):
    ma1_val = Ma.new(ma1_source, ma1_length, ma1_type)[0]
    ma2_val = Ma.new(ma2_source, ma2_length, ma2_type)[0]
    ma3_val = Ma.new(ma3_source, ma3_length, ma3_type)[0]
    ma4_val = Ma.new(ma4_source, ma4_length, ma4_type)[0]
    return ma1_val if ma1 else nan, \
           ma2_val if ma2 else nan, \
           ma3_val if ma3 else nan, \
           ma4_val if ma4 else nan

Moving Average Convergence Divergence

Moving Average Convergence Divergence
# indie:lang_version = 4
from indie import indicator, param, source, level, color, plot, plot_style, MutSeriesF, Color, Plot
from indie.algorithms import Ma


@indicator('MACD')  # Moving Average Convergence Divergence
@param.int('fast_length', default=12, min=1, title='Fast Length')
@param.int('slow_length', default=26, min=1, title='Slow Length')
@param.source('src', default=source.CLOSE, title='Source')
@param.int('signal_length', default=9, min=1, max=50, title='Signal Smoothing')
@param.string('sma_source', default='EMA', title='Oscillator MA Type', options=['SMA', 'EMA'])
@param.string('sma_signal', default='EMA', title='Signal Line MA Type', options=['SMA', 'EMA'])
@level(0, line_color=color.GRAY(0.5))
@plot(style=plot_style.COLUMNS, title='Histogram')
@plot(color=color.BLUE, title='MACD')
@plot(color=color.MAROON, title='Signal')
def Main(self, fast_length, slow_length, src, signal_length, sma_source, sma_signal):
    fast_ma = Ma.new(src, fast_length, sma_source)
    slow_ma = Ma.new(src, slow_length, sma_source)
    macd = MutSeriesF.new(fast_ma[0] - slow_ma[0])
    signal = Ma.new(macd, signal_length, sma_signal)
    hist = MutSeriesF.new(macd[0] - signal[0])

    hist_color: Color
    if hist[0] >= 0:
        if hist[1] < hist[0]:
            hist_color = color.GREEN
        else:
            hist_color = color.LIME
    else:
        if hist[1] < hist[0]:
            hist_color = color.RED
        else:
            hist_color = color.MAROON

    return (
        Plot(hist[0], color=hist_color),
        macd[0],
        signal[0],
    )

Mass Index

Mass Index
# indie:lang_version = 4
from math import nan, isclose
from indie import indicator, param, plot, color, MutSeriesF
from indie.algorithms import Ema, Sum


@indicator('Mass Index')
@param.int('length', default=10, min=1)
@plot(color=color.BLUE)
def Main(self, length):
    span = MutSeriesF.new(self.high[0] - self.low[0])
    ema1 = Ema.new(span, 9)
    ema2 = Ema.new(ema1, 9)
    res = nan
    if not isclose(ema2[0], 0):
        res = Sum.new(MutSeriesF.new(ema1[0] / ema2[0]), length)[0]
    return res

McGinley Dynamic

McGinley Dynamic
# indie:lang_version = 4
from math import isnan, nan, isclose, pow
from indie import indicator, param, plot, color, MutSeriesF
from indie.algorithms import Ema


@indicator('McGinley', overlay_main_pane=True)  # McGinley Dynamic
@param.int('length', default=14, min=1)
@plot(color=color.BLUE)
def Main(self, length):
    mg = MutSeriesF.new(init=0)
    if isnan(mg[1]):
        mg[0] = Ema.new(self.close, length)[0]
    else:
        mg[0] = nan
        if not isclose(mg[1], 0) and not isclose(pow(self.close[0] / mg[1], 4), 0):
            mg[0] = mg[1] + (self.close[0] - mg[1]) / (length * pow(self.close[0] / mg[1], 4))
    return mg[0]

Median

Median
# indie:lang_version = 4
from indie import indicator, param, source, plot, color, fill, MutSeriesF, Fill
from indie.algorithms import Median, Atr, Ema


@indicator('Median', overlay_main_pane=True)
@param.source('src', default=source.HL2, title='Source')
@param.int('length', default=3, min=1, title='Median Length')
@param.int('atr_length', default=14, min=1, title='ATR Length')
@param.int('atr_mult', default=2, min=1, title='ATR Multiplier')
@plot('ema', color=color.BLUE, title='Median EMA')
@plot(color=color.FUCHSIA, title='Lower Band')
@plot('median', color=color.RED, line_width=3, title='Median')
@plot(color=color.LIME, title='Upper Band')
@fill('median', 'ema', title='Background')
def Main(self, src, length, atr_length, atr_mult):
    median = Median.new(src, length)[0]

    atr = atr_mult * Atr.new(atr_length)[0]

    median_ema = Ema.new(MutSeriesF.new(median), length)[0]

    fill_color = color.LIME(0.9) if median > median_ema else color.FUCHSIA(0.9)
    return (
        median_ema,
        median - atr,
        median,
        median + atr,
        Fill(color=fill_color),
    )

Money Flow Index

Money Flow Index
# indie:lang_version = 4
from math import isclose
from indie import indicator, param, source, band, color, level, plot, MutSeriesF
from indie.algorithms import NanToZero, Change, Sum


@indicator('MFI')  # Money Flow Index
@param.int('length', default=14, min=1)
@param.source('src', default=source.HLC3, title='Source')
@band(20, 80, line_color=color.GRAY, fill_color=color.PURPLE(0.1), title='Background')
@level(50, line_color=color.GRAY, title='Middle Band')
@plot(color=color.PURPLE, title='MF')
def Main(self, length, src):
    upper_raw = MutSeriesF.new(self.volume[0] * (src[0] if NanToZero.new(Change.new(src))[0] > 0 else 0.0))
    upper = Sum.new(upper_raw, length)

    lower_raw = MutSeriesF.new(self.volume[0] * (src[0] if NanToZero.new(Change.new(src))[0] < 0 else 0.0))
    lower = Sum.new(lower_raw, length)

    res = 100.0
    if not isclose(lower[0], 0):
        res = 100.0 - (100.0 / (1.0 + upper[0] / lower[0]))
    return res

Momentum

Momentum
# indie:lang_version = 4
from indie import indicator, param, source, plot, color
from indie.algorithms import Change


@indicator('Mom')  # Momentum
@param.int('length', default=10, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@plot(color=color.BLUE)
def Main(self, length, src):
    return Change.new(src, length)[0]

Net Volume

Net Volume
# indie:lang_version = 4
from indie import indicator, format, plot, color
from indie.algorithms import NetVolume


@indicator('Net Volume', format=format.VOLUME)
@plot(color=color.BLUE)
def Main(self):
    return NetVolume.new(self.close)[0]

On Balance Volume

On Balance Volume
# indie:lang_version = 4
from indie import indicator, format, plot, color
from indie.algorithms import CumSum, NanToZero, NetVolume


@indicator('OBV', format=format.VOLUME)  # On Balance Volume
@plot(color=color.BLUE)
def Main(self):
    return CumSum.new(NanToZero.new(NetVolume.new(self.close)))[0]
    # TODO: implement smoothing when display.none is supported

Price Oscillator

Price Oscillator
# indie:lang_version = 4
from math import nan, isclose
from indie import indicator, param, source, level, color, plot
from indie.algorithms import Ema, Sma


@indicator('PPO')  # Price Oscillator
@param.int('short_len', default=10, min=1, title='Short Length')
@param.int('long_len', default=21, min=1, title='Long Length')
@param.source('src', default=source.CLOSE, title='Source')
@param.bool('exponential', default=False)
@level(0, line_color=color.GRAY, title='Zero')
@plot(color=color.GREEN, title='OSC')
def Main(self, short_len, long_len, src, exponential):
    short, long = 0.0, 0.0
    if exponential:
        short = Ema.new(src, short_len)[0]
        long = Ema.new(src, long_len)[0]
    else:
        short = Sma.new(src, short_len)[0]
        long = Sma.new(src, long_len)[0]
    po = nan
    if not isclose(long, 0):
        po = 100 * (short - long) / long
    return po

Price Volume Trend

Price Volume Trend
# indie:lang_version = 4
from math import nan, isclose
from indie import indicator, format, plot, color, MutSeriesF
from indie.algorithms import CumSum, NanToZero, Change


@indicator('PVT', format=format.VOLUME)  # Price Volume Trend
@plot(color=color.BLUE)
def Main(self):
    res = nan
    if not isclose(self.close[1], 0):
        res = CumSum.new(NanToZero.new(MutSeriesF.new(Change.new(self.close)[0] / self.close[1] * self.volume[0])))[0]
    return res

Rate Of Change

Rate Of Change
# indie:lang_version = 4
from indie import indicator, param, source, level, color, plot
from indie.algorithms import Roc


@indicator('ROC')  # Rate Of Change
@param.int('length', default=9, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@level(0, line_color=color.GRAY, title='Zero')
@plot(color=color.BLUE, title='ROC')
def Main(self, length, src):
    return Roc.new(src, length)[0]

Relative Strength Index

Relative Strength Index
# indie:lang_version = 4
from math import nan
from indie import indicator, param, source, band, color, level, plot, fill, Fill
from indie.algorithms import Rsi, Ma, StdDev


@indicator('RSI')  # Relative Strength Index
@param.int('rsi_length', default=14, title='RSI Length', min=1)
@param.source('src', default=source.CLOSE, title='Source')
@param.string('ma_type', default='SMA', title='MA Type',
              options=['SMA', 'Bollinger Bands', 'EMA', 'SMMA (RMA)', 'WMA', 'VWMA'])
@param.int('ma_length', default=14, title='MA Length', min=1)
@param.float('bb_mult', default=2.0, title='BB StdDev', min=0.001, max=50)
@band(30, 70, line_color=color.GRAY, fill_color=color.PURPLE(0.1))
@level(50, line_color=color.GRAY(0.5))
@plot('bb_lower', color=color.GREEN, title='RSI Lower Band')
@plot(color=color.YELLOW, title='RSI-based MA')
@plot('bb_upper', color=color.GREEN, title='RSI Upper Band')
@plot(color=color.PURPLE, title='RSI')
@fill('bb_lower', 'bb_upper', color=color.GREEN(0.1), title='Bollinger Bands Background Fill')
def Main(self, rsi_length, src, ma_type, ma_length, bb_mult):
    rsi = Rsi.new(src, rsi_length)

    is_bb = ma_type == 'Bollinger Bands'

    ma_algorithm = ma_type if not is_bb else 'SMA'
    rsi_ma = Ma.new(rsi, ma_length, ma_algorithm)

    std_dev = StdDev.new(rsi, ma_length)
    bb_lower = rsi_ma[0] - std_dev[0] * bb_mult if is_bb else nan
    bb_upper = rsi_ma[0] + std_dev[0] * bb_mult if is_bb else nan
    return bb_lower, rsi_ma[0], bb_upper, rsi[0], Fill()

Relative Vigor Index

Relative Vigor Index
# indie:lang_version = 4
from math import nan, isclose
from indie import algorithm, SeriesF, MutSeriesF, indicator, format, param, plot, color, Plot
from indie.algorithms import Sum


@algorithm  # Symmetrically Weighted Moving Average
def Swma(self, src: SeriesF) -> SeriesF:
    return MutSeriesF.new(src[3] / 6 + src[2] / 3 + src[1] / 3 + src[0] / 6)


@indicator('RVGI', format=format.PRICE, precision=4)  # Relative Vigor Index
@param.int('length', default=10, min=1)
@param.int('offset', default=0, min=-500, max=500)
@plot(color=color.GREEN, title='RVGI')
@plot(color=color.RED, title='Signal')
def Main(self, length, offset):
    cl_op_sum = Sum.new(Swma.new(MutSeriesF.new(self.close[0] - self.open[0])), length)[0]
    hi_lo_sum = Sum.new(Swma.new(MutSeriesF.new(self.high[0] - self.low[0])), length)[0]
    rvi = nan
    if not isclose(hi_lo_sum, 0):
        rvi = cl_op_sum / hi_lo_sum
    sig = Swma.new(MutSeriesF.new(rvi))[0]
    return Plot(rvi, offset=offset), Plot(sig, offset=offset)

Relative Volatility Index

Relative Volatility Index
# indie:lang_version = 4
from math import nan, isclose
from indie import indicator, param, band, color, level, plot, fill, MutSeriesF, Plot, Fill
from indie.algorithms import StdDev, Ema, Change, Bb, Ma


@indicator('RVI')  # Relative Volatility Index
@param.int('length', default=10, min=1)
@param.int('offset', default=0, min=-500, max=500)
@param.string('ma_type', default='SMA', title='MA Type', options=['SMA', 'Bollinger Bands', 'EMA', 'SMMA (RMA)', 'WMA', 'VWMA'])
@param.int('ma_length', default=14, title='MA Length', min=1)
@param.float('bb_mult', default=2.0, title='BB StdDev', min=0.001, max=50)
@band(20, 80, line_color=color.GRAY, fill_color=color.PURPLE(0.1))
@level(50, line_color=color.GRAY(0.5))
@plot('bb_lower', color=color.GREEN, title='Lower Bollinger Band')
@plot(color=color.YELLOW, title='RVI-based MA')
@plot('bb_upper', color=color.GREEN, title='Upper Bollinger Band')
@plot(color=color.PURPLE, title='RVI')
@fill('bb_lower', 'bb_upper', color=color.GREEN(0.1), title='Bollinger Bands Background Fill')
def Main(self, length, offset, ma_type, ma_length, bb_mult):
    dev = StdDev.new(self.close, length)[0]
    upper = Ema.new(MutSeriesF.new(0 if Change.new(self.close)[0] <= 0 else dev), 14)[0]
    lower = Ema.new(MutSeriesF.new(0 if Change.new(self.close)[0] > 0 else dev), 14)[0]

    rvi = MutSeriesF.new(init=nan)
    if not isclose(upper + lower, 0):
        rvi[0] = 100 * upper / (upper + lower)

    low_value, rvi_ma, high_value = nan, nan, nan
    if ma_type == 'Bollinger Bands':
        (bb_lower, bb_middle, bb_upper) = Bb.new(rvi, ma_length, bb_mult)
        low_value = bb_lower[0]
        rvi_ma = bb_middle[0]
        high_value = bb_upper[0]
    else:
        rvi_ma = Ma.new(rvi, ma_length, ma_type)[0]

    return (
        low_value,
        Plot(rvi_ma, offset=offset),
        high_value,
        Plot(rvi[0], offset=offset),
        Fill(),
    )

Simple Moving Average

Simple Moving Average
# indie:lang_version = 4
from indie import indicator, param, source, plot, color, Plot
from indie.algorithms import Sma


@indicator('SMA', overlay_main_pane=True)  # Simple Moving Average
@param.int('length', default=9, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@param.int('offset', default=0, min=-500, max=500, title='Offset')
@plot(color=color.BLUE)
def Main(self, length, src, offset):
    sma = Sma.new(src, length)
    return Plot(sma[0], offset=offset)
    # TODO: implement smoothing when display.none is supported

SMI Ergodic Indicator

SMI Ergodic Indicator
# indie:lang_version = 4
from indie import indicator, format, param, plot, color
from indie.algorithms import Tsi, Ema


@indicator('SMII', format=format.PRICE, precision=4)  # SMI Ergodic Indicator
@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(color=color.BLUE, title='SMI')
@plot(color=color.MAROON, title='Signal')
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]

SMI Ergodic Oscillator

SMI Ergodic Oscillator
# indie:lang_version = 4
from indie import indicator, format, param, plot, color, plot_style
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(color=color.RED, style=plot_style.HISTOGRAM)
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]

Smoothed Moving Average

Smoothed Moving Average
# indie:lang_version = 4
from indie import indicator, param, source, plot, color
from indie.algorithms import Rma


@indicator('SMMA', overlay_main_pane=True)  # Smoothed Moving Average
@param.int('length', default=7, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@plot(color=color.PURPLE)
def Main(self, length, src):
    return Rma.new(src, length)[0]

Stochastic Oscillator

Stochastic Oscillator
# indie:lang_version = 4
from indie import indicator, param, band, color, level, plot
from indie.algorithms import Sma, Stoch


@indicator('Stoch')  # Stochastic
@param.int('k_length', default=14, min=1, title='Fast K stochastic length')
@param.int('k_smoothing', default=1, min=1, title='Fast K stochastic smoothing')
@param.int('d_smoothing', default=3, min=1, title='Slow D stochastic smoothing')
@band(20, 80, fill_color=color.AQUA(0.1), line_color=color.GRAY, title='Background')
@level(50, line_color=color.GRAY(0.5), title='Middle Band')
@plot(color=color.BLUE, title='%K')
@plot(color=color.RED, title='%D')
def Main(self, k_length, k_smoothing, d_smoothing):
    k = Sma.new(Stoch.new(self.close, self.low, self.high, k_length), k_smoothing)
    d = Sma.new(k, d_smoothing)
    return k[0], d[0]

Stochastic RSI

Stochastic RSI
# indie:lang_version = 4
from indie import indicator, param, source, band, color, level, plot
from indie.algorithms import Rsi, Stoch, Sma


@indicator('Stoch RSI')  # Stochastic RSI
@param.int('k_smoothing', default=3, min=1, title='Fast K stochastic smoothing')
@param.int('d_smoothing', default=3, min=1, title='Slow D stochastic smoothing')
@param.int('rsi_length', default=14, min=1, title='RSI length')
@param.int('k_length', default=14, min=1, title='Stochastic Length')
@param.source('src', default=source.CLOSE, title='Source')
@band(20, 80, fill_color=color.AQUA(0.1), line_color=color.GRAY, title='Background')
@level(50, line_color=color.GRAY(0.5), title='Middle Band')
@plot(color=color.BLUE, title='K')
@plot(color=color.RED, title='D')
def Main(self, k_smoothing, d_smoothing, rsi_length, k_length, src):
    rsi = Rsi.new(src, rsi_length)
    k = Sma.new(Stoch.new(rsi, rsi, rsi, k_length), k_smoothing)
    d = Sma.new(k, d_smoothing)
    return k[0], d[0]

Supertrend

Supertrend
# indie:lang_version = 4
from math import nan
from indie import indicator, param, plot, color, fill, Fill
from indie.algorithms import Supertrend


@indicator('Supertrend', overlay_main_pane=True)
@param.int('atr_period', default=10, min=1)
@param.float('factor', default=3.0)
@param.string('ma_algorithm', default='RMA', options=['RMA', 'SMA', 'EMA', 'WMA'])
@plot('down', color=color.RED, title='Down Trend')
@plot('middle', color=color.GRAY(0.5), title='Body Middle')  # TODO: support display.none
@plot('up', color=color.GREEN, title='Up Trend')
@fill('down', 'middle', color=color.RED(0.1), title='Down-Middle Fill')
@fill('middle', 'up', color=color.GREEN(0.1), title='Middle-Up Fill')
def Main(self, factor, atr_period, ma_algorithm):
    st, direction = Supertrend.new(factor, atr_period, ma_algorithm)
    st_down = st[0] if direction[0] > 0 else nan
    st_up = st[0] if direction[0] < 0 else nan
    middle = (self.open[0] + self.close[0]) / 2
    return st_down, middle, st_up, Fill(), Fill()

Triple EMA

Triple EMA
# indie:lang_version = 4
from indie import indicator, param, source, plot, color
from indie.algorithms import Ema


@indicator('TEMA', overlay_main_pane=True)  # Triple EMA
@param.int('length', default=9, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@plot(color=color.BLUE)
def Main(self, length, src):
    ema1 = Ema.new(src, length)
    ema2 = Ema.new(ema1, length)
    ema3 = Ema.new(ema2, length)
    return 3 * (ema1[0] - ema2[0]) + ema3[0]

TRIX

TRIX
# indie:lang_version = 4
from math import log
from indie import indicator, param, level, color, plot, MutSeriesF
from indie.algorithms import Change, Ema


@indicator('TRIX')
@param.int('length', default=18, min=1)
@level(0, line_color=color.GRAY, title='Zero')
@plot(color=color.RED, title='TRIX')
def Main(self, length):
    return 10000 * Change.new(Ema.new(Ema.new(Ema.new(MutSeriesF.new(log(self.close[0])), length), length), length))[0]

True Strength Indicator

True Strength Indicator
# indie:lang_version = 4
from indie import indicator, format, param, level, color, plot
from indie.algorithms import Tsi, Ema


@indicator('TSI', format=format.PRICE, precision=4)  # True Strength Indicator
@param.int('long_len', default=25, min=1, title='Long Length')
@param.int('short_len', default=13, min=1, title='Short Length')
@param.int('signal_len', default=13, min=1, title='Signal Length')
@level(0, line_color=color.GRAY, title='Zero')
@plot(color=color.BLUE, title='TSI')
@plot(color=color.RED, title='Signal')
def Main(self, long_len, short_len, signal_len):
    tsi = Tsi.new(self.close, long_len, short_len)
    return 100 * tsi[0], 100 * Ema.new(tsi, signal_len)[0]

Ultimate Oscillator

Ultimate Oscillator
# indie:lang_version = 4
from math import nan, isclose
from indie import algorithm, SeriesF, indicator, param, plot, color, MutSeriesF
from indie.algorithms import Sum


@algorithm
def Average(self, bp: SeriesF, tr: SeriesF, length: int) -> float:
    bp_sum = Sum.new(bp, length)[0]
    tr_sum = Sum.new(tr, length)[0]
    res = nan
    if not isclose(tr_sum, 0):
        res = bp_sum / tr_sum
    return res


@indicator('UO')  # Ultimate Oscillator
@param.int('fast_len', default=7, min=1, title='Fast Length')
@param.int('middle_len', default=14, min=1, title='Middle Length')
@param.int('slow_len', default=28, min=1, title='Slow Length')
@plot(color=color.RED)
def Main(self, fast_len, middle_len, slow_len):
    high = max(self.high[0], self.close[1])
    low = min(self.low[0], self.close[1])
    bp = MutSeriesF.new(self.close[0] - low)
    tr = MutSeriesF.new(high - low)
    avg7 = Average.new(bp, tr, fast_len)
    avg14 = Average.new(bp, tr, middle_len)
    avg28 = Average.new(bp, tr, slow_len)
    return 100 * (4 * avg7 + 2 * avg14 + avg28) / 7

Volume Oscillator

Volume Oscillator
# indie:lang_version = 4
from math import nan, isclose
from indie import indicator, param, level, color, plot
from indie.algorithms import Ema


@indicator('Volume Osc')  # Volume Oscillator
@param.int('short_len', default=5, min=1, title='Short Length')
@param.int('long_len', default=10, min=1, title='Long Length')
@level(0, line_color=color.GRAY, title='Zero')
@plot(color=color.BLUE, title='VO')
def Main(self, short_len, long_len):
    short = Ema.new(self.volume, short_len)[0]
    long = Ema.new(self.volume, long_len)[0]
    res = nan
    if not isclose(long, 0):
        res = 100 * (short - long) / long
    return res

Vortex Indicator

Vortex Indicator
# indie:lang_version = 4
from math import nan, isclose
from indie import indicator, format, param, plot, color, MutSeriesF
from indie.algorithms import Sum, Atr


@indicator('VI', format=format.PRICE, precision=4)  # Vortex Indicator
@param.int('length', default=14, min=2)
@plot(color=color.RED, title='VI -')
@plot(color=color.BLUE, title='VI +')
def Main(self, length):
    vmm = Sum.new(MutSeriesF.new(abs(self.low[0] - self.high[1])), length)[0]
    vmp = Sum.new(MutSeriesF.new(abs(self.high[0] - self.low[1])), length)[0]
    satr = Sum.new(Atr.new(1), length)[0]
    vim, vip = nan, nan
    if not isclose(satr, 0):
        vim = vmm / satr
        vip = vmp / satr
    return vim, vip

Volume Weighted Average Price

Volume Weighted Average Price
# indie:lang_version = 4
from indie import indicator, param, source, plot, fill, color, Plot, Fill
from indie.algorithms import Vwap


@indicator('VWAP', overlay_main_pane=True)  # Volume Weighted Average Price
@param.source('src', default=source.HLC3, title='Source')
# TODO: add Earnings, Dividends, Splits
@param.string('anchor', default='Session', options=['Session', 'Week', 'Month', 'Year'], title='Anchor')
@param.int('offset', default=0, min=-500, max=500, title='Offset')
@plot('vwap', title='VWAP', color=color.BLUE)
@plot('upper', title='Upper band', color=color.GREEN)
@plot('lower', title='Lower band', color=color.GREEN)
@fill('upper', 'lower', color=color.GREEN(0.1), title='Background')
def Main(self, src, anchor, offset):
    std_dev_mult = 1.0
    main_line, upper, lower = Vwap.new(src, anchor, std_dev_mult)
    return (
        Plot(main_line[0], offset=offset),
        Plot(upper[0], offset=offset),
        Plot(lower[0], offset=offset),
        Fill(),
    )

Volume Moving Average Weighted

Volume Moving Average Weighted
# indie:lang_version = 4
from indie import indicator, param, source, plot, color, Plot
from indie.algorithms import Vwma


@indicator('VWMA', overlay_main_pane=True)  # Volume Weighted Moving Average
@param.int('length', default=20, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@param.int('offset', default=0, min=-500, max=500, title='Offset')
@plot(color=color.BLUE)
def Main(self, length, src, offset):
    result = Vwma.new(src, length)
    return Plot(result[0], offset=offset)

Williams Alligator

Williams Alligator
# indie:lang_version = 4
from indie import indicator, param, plot, color, Plot
from indie.algorithms import Rma


@indicator('Alligator', overlay_main_pane=True)  # Williams Alligator
@param.int('jaw_period', default=13, min=1, title='Jaw Period')
@param.int('teeth_period', default=8, min=1, title='Teeth Period')
@param.int('lips_period', default=5, min=1, title='Lips Period')
@param.int('jaw_offset', default=8, min=1, title='Jaw Offset')
@param.int('teeth_offset', default=5, min=1, title='Teeth Offset')
@param.int('lips_offset', default=3, min=1, title='Lips Offset')
@plot(color=color.BLUE, title='Jaw')
@plot(color=color.RED, title='Teeth')
@plot(color=color.GREEN, title='Lips')
def Main(self, jaw_period, teeth_period, lips_period, jaw_offset, teeth_offset, lips_offset):
    jaw = Rma.new(self.hl2, jaw_period)
    teeth = Rma.new(self.hl2, teeth_period)
    lips = Rma.new(self.hl2, lips_period)

    return (
        Plot(jaw[0], offset=jaw_offset),
        Plot(teeth[0], offset=teeth_offset),
        Plot(lips[0], offset=lips_offset),
    )

Williams Percent Range

Williams Percent Range
# indie:lang_version = 4
from math import nan, isclose
from indie import indicator, param, source, band, color, level, line_style, plot
from indie.algorithms import Highest, Lowest


@indicator('Williams %R')  # Williams Percent Range
@param.int('length', default=14, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@band(-20, -80, line_color=color.GRAY, fill_color=color.PURPLE(0.1), title='Background')
@level(-50, line_color=color.GRAY, line_style=line_style.DOTTED, title='Middle Level')
@plot(color=color.PURPLE, title='%R')
def Main(self, length, src):
    max = Highest.new(self.high, length)[0]
    min = Lowest.new(self.low, length)[0]
    res = nan
    if not isclose(max - min, 0):
        res = 100 * (src[0] - max) / (max - min)
    return res

Moving Average Weighted

Moving Average Weighted
# indie:lang_version = 4
from indie import indicator, param, source, plot, color, Plot
from indie.algorithms import Wma


@indicator('WMA', overlay_main_pane=True)  # Weighted Moving Average
@param.int('length', default=9, min=1)
@param.source('src', default=source.CLOSE, title='Source')
@param.int('offset', default=0, min=-500, max=500, title='Offset')
@plot(color=color.BLUE)
def Main(self, length, src, offset):
    result = Wma.new(src, length)
    return Plot(result[0], offset=offset)

Woodies CCI

Woodies CCI
# indie:lang_version = 4
from indie import indicator, param, level, color, line_style, plot, plot_style, Color, Plot
from indie.algorithms import Cci


@indicator('Woodies CCI')
@param.int('cci_turbo_len', default=6, min=3, max=14, title='CCI Turbo Length')
@param.int('cci_14_len', default=14, min=7, max=20, title='CCI 14 Length')
@level(-100, line_color=color.GRAY, line_style=line_style.DOTTED, title='Minus Line')
@level(0, line_color=color.GRAY, line_style=line_style.SOLID, title='Zero Line')
@level(100, line_color=color.GRAY, line_style=line_style.DOTTED, title='Hundred Line')
@plot(style=plot_style.HISTOGRAM, title='CCI Turbo Histogram')
@plot(color=color.GREEN, title='CCI Turbo')
@plot(color=color.RED, title='CCI 14')
def Main(self, cci_turbo_len, cci_14_len):
    cci_turbo = Cci.new(self.close, cci_turbo_len)
    cci_14 = Cci.new(self.close, cci_14_len)
    last_5_down = cci_14[5] < 0 and cci_14[4] < 0 and cci_14[3] < 0 and cci_14[2] < 0 and cci_14[1] < 0
    last_5_up = cci_14[5] > 0 and cci_14[4] > 0 and cci_14[3] > 0 and cci_14[2] > 0 and cci_14[1] > 0

    hist_color: Color
    if last_5_up:
        hist_color = color.GREEN
    elif last_5_down:
        hist_color = color.RED
    elif cci_14[0] < 0:
        hist_color = color.GREEN
    else:
        hist_color = color.RED
    return Plot(cci_14[0], color=hist_color), cci_turbo[0], cci_14[0]