Educational indicators
SMA algorithm that accepts ‘series’ length
Indie’s standard indie.algorithms.Sma
algorithm has a length parameter which cannot be dynamically changed. Its common usage looks like this: Sma.new(self.close, length=12)
, where the length
parameter is typically a literal constant. While it is possible for the length
to be a variable calculated at the time of indicator initialization or even during calculation over bars/candles, the general rule that should not be broken is as follows: once set, the value of the length
parameter should not change. Otherwise, the algorithm breaks internally and starts giving inaccurate results*.
Sometimes, greater flexibility is needed. Fortunately, there is a way to achieve this, and this indicator with MySma
algorithm demonstrates it. The algorithm is based on properties derived from prefix sums. Let’s look at an example to see how it works. Suppose we have a source series of values, src, like this:
src = …, 2, 7, 5, 4, 3, 9, 6
Think of this series as a series of close prices of some instrument on a chart. The last (i.e., current) value of this series is 6
, which is accessed in Indie with the expression src[0]
.
To calculate the SMA with a length
period, we simply divide the sum of length
elements by the length
value. The sum calculation seems straightforward, but it can be inefficient. For example, sum(src, length=3)
is calculated as 3 + 9 + 6 = 18
. This calculation is CPU-intensive, and the larger the length
value, the more computationally expensive it becomes (the time complexity of this calculation is linear). This isn’t a big issue if the length
is constant. However, consider a use case where length
may vary from 2 to 300, and the size of the src series is around 20,000 bars, requiring SMA (and thus sum) calculations at every bar of this dataset. That would involve a lot of sum operations.
If we use prefix sums, which in Indie can be calculated with a cs = CumSum.new(src, length)
statement, we can calculate sums in constant time (which is faster):
cs = …, 2, 9, 14, 18, 21, 30, 36
Calculation of any sum with any length using the cs series is as simple as subtracting two numbers. For example, sum(src, length=3) = cs[0] - cs[3] = 36 - 18 = 18. Let’s calculate sums with a few different lengths:
sum(src, length=4) = cs[0] - cs[4] = 36 - 14 = 22
, which is the same as4 + 3 + 9 + 6 = 22
sum(src, length=5) = cs[0] - cs[5] = 36 - 9 = 27
, which is the same as5 + 4 + 3 + 9 + 6 = 27
and so on. The general formula here is:
sum(src, length) = cs[0] - cs[length]
In this indicator, there are three SMA plot lines:
- The thin green and thin red lines both use the
indie.algorithm.Sma
from the standard library. They use different but constantlength
parameters:short_len
andlong_len
. These two plots serve as references with which we compare the third plot. - The thick light blue line uses the
MySma
algorithm described above, which is based on the prefix sums approach. MySma can accept a ‘series’ length, and depending on runtime conditions, eithershort_len
orlong_len
is passed to it.
We see that the blue line is perfectly aligned with one of the two fixed-length SMA plots, proving its correctness.
This approach can be effectively used not only in the SMA algorithm but in any similar algorithm that can be calculated with the help of prefix sums or derivatives of SMA (like double-SMA and so on).
P. S. This is true as of Indie v4.
Green/Red Bar Counts - usage example of indie.MutSeries[T] generic class
Indie includes generic series types: indie.Series[T]
and indie.MutSeries[T]
. The float-types indie.SeriesF
and indie.MutSeriesF
are still available as aliases for indie.Series[float]
and indie.MutSeries[float]
, respectively.
The “Green/Red Bar Counts” indicator demonstrates the usage of the new generic class indie.MutSeries[T]
, which is instantiated with T
as the int
type. The generic type parameter T
can be any (well, almost any) Indie type, such as bool
, str
, etc.
Sma-Ema Crossover - usage example of indie.Var[T]
The indie.Var[T]
container functions similarly to indie.MutSeries[T]
, but it has a single-value history depth, making it ideal for storing and updating the current state without accumulating historical values. Although indie.Var[T]
does not store historical values, it differs significantly from a primitive field of type T
of an Algorithm/Context or a global variable. The key difference lies in its behavior during real-time calculations. indie.Var[T]
retains only the values set on closing bar updates, reverting to the previous value before each intrabar update.
The “SMA-EMA Crossover” indicator demonstrates the use of indie.Var[T]
through the algorithm CrossoverWithVar, which detects and retains values on specific crossover events. It also includes the algorithm CrossoverWithSeries to illustrate how to achieve the same result without using indie.Var[T]
.
Green/Red Bar Count - usage example of indie.Var[T]
This update of “Green/Red Bar Counts” indicator showcases the indie.Var[T]
container usage.
In this implementation:
- State Tracking: The
indie.Var[T]
container counts consecutive green (up) or red (down) bars without retaining any history.indie.Var[T]
is ideal here since it efficiently holds only the most recent value, unlike indie.MutSeries[T], which maintains a historical sequence of values.
Why Use indie.Var Instead of a Simple Float or Integer?
Unlike using a single float
or int
, indie.Var
is fully compatible with Indie’s series-based environment, where values update bar by bar. Plain variables would reset on each new bar calculation, causing them to lose their previous state. Moreover, indie.Var
ensures that values persist correctly in both historical and real-time data. A simple local number variable would reset or lack the ability to update accurately in real-time, making it unsuitable for cumulative calculations. In contrast, indie.Var provides stable, consistent updates across all bars.
Key Differences with the Previous Version: General Transformation from MutSeries
to Var
In cases where only the latest value is required without retaining historical data, you can convert code from indie.MutSeries to indie.Var by following these general rules:
- Replace
MutSeries.new()
withVar.new()
:
my_var = Var[float].new(0) # instead of my_series = MutSeries[float].new(0)
- Use
.get()
and.set()
instead of indexing:
var_value = my_var.get() # instead of series_value = my_series[0]
my_var.set(new_value) # instead of my_series[0] = new_value
Median Algorithm (written with Indie v4 classes)
This is an example of an Indie algorithm that uses class syntax, introduced in the language since version 4.
The Median class of this indicator inherits from the indie.Algorithm
base class. It has two methods: the __init__
constructor and calc
. It is essential that the __init__
constructor is executed only once during the creation of the indicator. This makes it a good location to create all objects that hold the algorithm’s state, ensuring these objects do not lose their data when a candle update arrives (triggering the calc method). The stateful objects (fields) are:
self._sorted_vals: list[float]
- A list of the last length values of the input series src. The algorithm keeps those values ordered.self._prev_src_val: float
- The previous value of the last element in the src series.self._bar_count: int
- A counter for bars that we use to detect when a new bar arrives.
The main idea of this Median algorithm is to maintain a sliding window of the last length
values of the src series and keep these values in sorted order. With such an array, the median is easily calculated as the value in the middle of it.
Custom VWAP - usage example of indie.Schedule class
The “Custom VWAP” indicator demonstrates the usage of the Schedule
class. If we don’t want to reset VWAP at the start of each new default trading day (using the ‘Session’ option of the anchor parameter), we can create a custom schedule to define the reset time.
For example, by default, the VWAP reset time for ‘BTC/USDT’ is at 00:00 UTC. However, we may want to change this reset time to 00:00 in our chosen timezone (e.g., ‘America/New_York’ for this indicator).
Key Points:
- The custom schedule is defined in the
__init__
function because creating it on each bar in theContext.calc
function is resource-inefficient. Alternatively, theContext.pre_calc
function can be used for schedule definition if we need to access data fromself.info
orself.trading_session
. - The is_same_period method (for both
Schedule
andSession
options) is used to detect whether two timestamps belong to the same schedule period or fall on different days.
Was this page helpful?