The general code structure of an indicator 'module' looks like this:
COMMENT_DIRECTIVES
IMPORTS
HELPER_FUNCTION_DEFINITIONS (optional)
DECORATED_HELPER_FUNCTION_DEFINITIONS (optional)
MAIN_FUNCTION_DEFINITION
The division into sections is schematic. In real indicator code, function definitions from any section can be mixed up relative to each other.
Syntactically, Indie code is a Python code. But, there are some differences that you may want to be aware about.
Comment directives are special comments that have some effect on how Indie's compiler and/or runtime work. At the moment there is only one comment directive which is:
# indie:lang_version = 2
and it declares the language version of the source code of the indicator.
The IMPORTS section consists of several import
statements. Every import
statement tells the runtime to import
(or connect) one or more symbols from libraries. After that those symbols may
be used in the indicator code.
from indie import indicator # This line imports the `indicator` decorator from `indie`
# library package
@indicator('Example') # Here we use the imported `indicator` decorator
def main(ctx):
# ...
Like in Python, there are several different forms of imports:
import indie.algorithm # Then use any symbols from that package with their full name,
# e.g. `indie.algorithm.sma`
import indie.algorithm as alg # Then use any symbols from that package but with a short
# alias, e.g. `alg.sma`
from indie.algorithm import sma # Then use only `sma` symbol from that package (without
# the package prefix)
from indie.algorithm import sma as my_foobar_sma # Then use the symbol with an alias
#`my_foobar_sma`
Also in every from
form you are allowed to use lists of symbols, for example:
from indie.algorithm import sma, wma, ema
from indie.algorithm import sma as s, wma as w, ema as e # You can do that, but this is not a
# good coding style! Because this makes
# your code harder to read and understand
One note about the aliases. Do not use them if you do not really need them. And you most certainly will need them in two cases:
from some.library import foobar_func
from another.library import foobar_func
Obviously, when you try to call the imported function foobar_func()
afterwards, it is unclear which one
of the two imported functions should be called. Such code cannot be run and produces an error.
Same example with a name clash resolved:
from some.library import foobar_func
from another.library import foobar_func as another_foobar_func
# Now you may call both `foobar_func()` and `another_foobar_func()` without any ambiguity
from some.library import very_useful_function_with_a_very_long_name
# Call the function later as `very_useful_function_with_a_very_long_name()`
Instead, it is better to use an alias:
from some.library import very_useful_function_with_a_very_long_name as useful_func
# Call the function later as `useful_func()`
Indicator algorithms could be quite complex, so it is a good code style to extract blocks of code into
helper functions with some meaningful names. The minimal function that accepts no arguments, does nothing
(Python keyword pass
is about that) and returns nothing (None
) looks like this:
def minimal_helper_func() -> None:
pass
Obviously there will be not much help of such a helper function like minimal_helper_func
, it's just an example.
Here is a more realistic example of a function that calculates a maximum of given two integers:
def max(a: int, b: int) -> int:
if a > b:
return a
else:
return b
At the moment, it is mandatory to declare type hints for every argument as well as for the return value of all
the functions (except for main
function and @ctx_func
-decorated functions). Read more about typing
in the Typing chapter.
There are two decorators that can be applied to helper functions: @func
and @ctx_func
. Both of them are syntactic sugar decorators which help a lot in making indicator code compact and clean.
The @func
decorator is a syntactic sugar for making your own series processors
like indie.algorithm.sma
.
@func
def some_func() -> Series:
# Some code...
The @ctx_func
decorator is a syntactic sugar for making an extra entry point function (like a secondary main
)
for additional instrument that indicator may request. This function should be used in combination with a
ctx.calc_on
function. Read more about it in Request additional instruments.
@ctx_func
def some_ctx_func():
# Some code, that processes the BINANCEUS:BTC/USD candles data...
@indicator('Example')
def main():
res = ctx.calc_on(exchange='BINANCEUS', ticker='BTC/USD', ctx_func=some_ctx_func)
# Some code...
@indicator('Example 1')
def main(ctx):
# Some code here...
The main
function, the entry point of the whole indicator, is mandatory. It is called on every receive of
a candle data update and it should return an indicator result for that piece of data.
It must have at least one parameter ctx
which is an object of Context
type.
Function main
must also be decorated with a @indicator
decorator. But also other decorators
could be added here, for example:
@plot
@fill
, @level
and @band
@param.*
decorators