Main
entry point
@algorithm
Algorithm.new()
method
MutSeries[T].new()
method
@sec_context
Indie programming language has several syntactic sugar constructs which simplify a lot writing code of technical analysis indicators for TakeProfit platform.
Main
entry pointEvery indicator in Indie has a Main
entry point. It could be written in one of two forms: as a Main
function
definition which is a form of syntactic sugar or as a Main
class definition (which is not).
Main
function definitionHere is an example of minimal indicator written in a form of a Main
as a function definition:
# indie:lang_version = 4
from indie import indicator
@indicator('Example')
def Main(self):
return self.close[0]
This form is very compact therefore it is recommended for simple indicators.
When Indie compiler sees a combination of a function definition with name Main
decorated with @indicator
it transforms it into a Main
class definition (i.e. to the second form) according to a few simple rules:
Main
class definition which is inherited from indie.MainContext
base class;Main
function becomes Main.calc()
method;Main
class may optionally have __init__
constructor (only if it is needed).This transformation is automatic and hidden from user, that is why the first form of Main
entry point is considered to
be a syntactic sugar. In some cases when indicator needs greater control over what is happening in the code the second form of writing the indicator's Main
entry point with classes syntax can be used explicitly.
Main
written as a class definitionIndicator could be written using the second form of a Main
entry point which uses class definition syntax:
# indie:lang_version = 4
from indie import indicator, MainContext
@indicator('Example')
class Main(MainContext):
def __init__(self):
pass
def calc(self):
return self.close[0]
Writing indicator in the second form using class syntax is less compact but it has it's advantages, for example user is
able to declare __init__
constructor method and place some one-time initialization code there. Therefore for
indicators with complex logic you probably would like to use the second form. Constructor __init__
is optional though.
@algorithm
Functions decorated with @algorithm
are automatically transformed by Indie compiler into classes inherited from
indie.Algorithm
. For example:
# indie:lang_version = 4
from indie import algorithm, SeriesF
from indie.algorithms import Ema
@algorithm
def DoubleEma(self, src: SeriesF, length: int) -> SeriesF:
ema1 = Ema.new(src, length)
ema2 = Ema.new(ema1, length)
return ema2
Will be transformed into a class form:
# indie:lang_version = 4
from indie import Algorithm, SeriesF, Context
from indie.algorithms import Ema
class DoubleEma(Algorithm):
def __init__(self, ctx: Context):
super().__init__(ctx)
def calc(self, src: SeriesF, length: int) -> SeriesF:
ema1 = Ema.new(src, length)
ema2 = Ema.new(ema1, length)
return ema2
Users are able to choose which form they want to use in their indicators' code. Of course, as with Main
entry point,
the decorator-based form is preferred in most cases. Class form should be used only in more complex cases, where a
more fine-grained control is needed.
Decorator @algorithm
is also a syntactic sugar in Indie language. Here are the simple rules that Indie compiler uses
to process it:
@algorithm
is replaced with a class with the same name inherited from
indie.Algorithm
base class;calc
method of this class, function body does not change;__init__
constructor is added to the generated class which accepts ctx: indie.Context
parameter and forwards it to
the parent constructor;NOTE: to get OHLCV values of the current instrument from the algorithm methods there is a self.ctx
property of
indie.Context
type. For example to get access to close prices use self.ctx.close
expression.
The DoubleEma
algorithm from the example above could be created and used in two ways:
DoubleEma.new()
static method (new
is a method of parent class indie.Algorithm
, well... not exactly but
kind of). This is a syntactic sugar that allows to use any algorithm right in the place where it is needed in the
indicator (e.g. in def Main
function, or in the function body of some other algorithm).DoubleEma()
constructor in __init__
method of a corresponding MainContext
, SecContext
or other
Algorithm
. Then, in the calc
method of a corresponding MainContext
, SecContext
or other
Algorithm
you should explicitly call the DoubleEma
's calc()
method.Algorithm.new()
methodMethod Algorithm.new()
is used to create and use algorithm objects in Indie. It looks like a static method, but
it behaves very differently because it is also a syntactic sugar of the language. When Indie compiler sees a call, e.g.
res = Ema.new(src, length)
, it uses the following rules to transform such a piece of code:
__init__
constructor of the enclosing MainContext
, SecContext
or other
Algorithm
. In our example it would be: self._ema1 = Ema(ctx)
(in case self
is a Context
) or
self._ema1 = Ema(self.ctx)
(in case self
is an Algorithm
).Ema.new
is replaced with self._ema1.calc
call. In our example it would be
res = self._ema1.calc(src, length)
.Here is a full example of such transformation:
# indie:lang_version = 4
from indie import Algorithm, SeriesF, Context
from indie.algorithms import Ema
class DoubleEma(Algorithm):
def __init__(self, ctx: Context):
super().__init__(ctx)
def calc(self, src: SeriesF, length: int) -> SeriesF:
ema1 = Ema.new(src, length)
ema2 = Ema.new(ema1, length)
return ema2
Will be transformed into:
# indie:lang_version = 4
from indie import Algorithm, SeriesF, Context
from indie.algorithms import Ema
class DoubleEma(Algorithm):
def __init__(self, ctx: Context):
super().__init__(ctx)
self._ema1 = Ema(self.ctx)
self._ema2 = Ema(self.ctx)
def calc(self, src: SeriesF, length: int) -> SeriesF:
ema1 = self._ema1.calc(src, length)
ema2 = self._ema2.calc(ema1, length)
return ema2
Of course, syntactic sugar of Algorithm.new()
works only inside methods of descendants of MainContext
, SecContext
or other Algorithm
classes (except the __init__
constructors). You cannot use this functionality in plain
Python-like functions.
Syntactic sugar of Algorithm.new()
method can be used with any Indie algorithm regardless of the form in which the
algorithm was written (the decorator-based form or the class-based form).
MutSeries[T].new()
methodMethod MutSeries[T].new()
(or MutSeriesF.new()
where MutSeriesF
is an alias for MutSeries[float]
) is used to create and use mutable series objects in Indie. It looks like a static method, but
it behaves very differently because it is also a syntactic sugar of the language. When Indie compiler sees a call, e.g.
s = MutSeriesF.new((a + b) / 2)
, it uses the following rules to transform such a piece of code:
__init__
constructor of the enclosing MainContext
, SecContext
or
Algorithm
. In our example it would be: self._ms1 = self.new_mut_series_f()
(in case self
is a Context
) or self.ctx.new_mut_series_f()
(in case self
is an Algorithm
).MutSeriesF.new
is replaced with self._ms1.calc
call. In our example it would be
s = self._ms1.calc((a + b) / 2)
. Method MutSeriesF.calc
writes given expression (a + b) / 2
into the last
element of the mutable series object and returns a reference to the mutable series itself.For example:
# indie:lang_version = 4
from indie import algorithm, SeriesF, MutSeriesF
@algorithm
def Hl2(self) -> SeriesF:
h = self.ctx.high
l = self.ctx.low
return MutSeriesF.new((h[0] + l[0]) / 2)
transforms into:
# indie:lang_version = 4
from indie import Algorithm, SeriesF, MutSeriesF, Context
class Hl2(Algorithm):
def __init__(self, ctx: Context):
super().__init__(ctx)
self._ms1 = self.ctx.new_mut_series_f()
def calc(self) -> SeriesF:
h = self.ctx.high
l = self.ctx.low
return self._ms1.calc((h[0] + l[0]) / 2)
In this example two transformations took place at once: syntactic sugar of @algorithm
and MutSeriesF.new()
call.
@sec_context
Functions decorated with @sec_context
are used to create secondary entry-points (besides Main
) which are needed when
indicator requests data of additional instruments. And decorator @sec_context
is syntactic sugar, because every time
Indie compiler sees a function definition decorated with @sec_context
it transforms it into a class, inherited from
indie.SecContext
according to a few simple rules:
@sec_context
is replaced with a class with the same name inherited from
indie.SecContext
base class;calc()
method of the generated class;__init__
constructor (only if it is needed).Let us see how it works with an example, this piece of code:
# indie:lang_version = 4
from indie import sec_context
@sec_context
def SecHighLow(self):
return self.high[0], self.low[0]
transforms into this:
# indie:lang_version = 4
from indie import SecContext
class SecHighLow(SecContext):
def calc(self):
return self.high[0], self.low[0]
The code that actually uses the SecHighLow
looks like this:
# indie:lang_version = 4
from indie import indicator, tf
# >>> definition of SecHighLow is here <<<
@indicator('MyIndie')
class Main(MainContext):
def __init__(self):
self._h, self._l = self.calc_on(SecHighLow, time_frame=tf('1D'))
def calc(self):
return self._h[0], self._l[0]
NOTE that in the Indie (since version 4) it is allowed to call Context.calc_on
only from __init__
constructors.