Skip to content

Language

Script kinds

A T0 file has the extension of what it is. The extension is not decoration: the compiler holds the file to it (E-KIND-EXT), the terminal's script browser filters and badges by it, and the host refuses to bind or start a file as something it is not.

Kind Extension What it is Entry point Who runs it
Indicator .tzi component indicator "name" version "x"; -- reads candles and the price, gives a line and signals fn compute(cfg, input) -> indicator.Output the terminal, about once a second, on the market in hand
Control .tzc a program for the terminal: charts, panels, alerts, other scripts fn main() a hook, a trigger line, the editor, another control script
Risk .tzr component risk -- authorizes and sizes orders authorize, optionally size the host, for every order that opens exposure
Portfolio .tzp component portfolio -- shares capital among strategies allocate the host, when it rebalances
Strategy .tzs a strategy block or component strategy -- decides entries and exits handlers and rules the backtest engine and the live engine
Manager .tzm component manager -- from the candles, says which strategies, risk script and portfolio controller of a playbook are on fn decide(cfg, input) -> manager.Decision the terminal (live) and the backtest engine, when a candle of the playbook's timeframe closes
Program .tz any program; the extension from before kinds existed fn main() as a control program
(module) .tz a library file for import; it declares no role none the importing program

The rule#

A file named for a kind must declare that kind:

  • guard.tzr must contain component risk …; -- a component portfolio in it is E-KIND-EXT, and the message says which extension it belongs to.
  • layout.tzc must have fn main() and no component header and no strategy.
  • trend.tzs must declare a strategy.
  • filter.tzm must contain component manager …;.
  • A plain .tz file, or a buffer with no name yet, is held to nothing: everything written before kinds keeps compiling and running.

A component is pure: the host calls its entry point with what it needs and it answers; it cannot read the market, touch a file or start anything. A control program is the opposite on purpose -- it exists to do things (with the permissions it was given), so it is a program, not a component.

Who can start whom#

A script is never started by a script on its own: something in the terminal sees the event and starts it, under limits.

From Starts How
Indicator a control script or a webhook each Emit it gives becomes the event indicator_signal; the hooks attached to it run
Strategy running live a control script or a webhook the strategy's signals become strategy_signal
Control script (or .tz) another control script script.run(path, args), permission script.run
Manager nothing -- it switches the host suspends or resumes the playbook's strategies and swaps its risk script and portfolio controller by what the manager decided; every change is the event playbook_changed
Risk, portfolio nothing they stay pure; what they decide already reaches the hooks (risk_declined, portfolio_rebalanced, …)

script.run is bounded: the target is a file of the scripts folder and a .tzc or .tz; it holds at most the permissions of the script that started it (and never more than a hook may hold); the chain is at most three deep; at most 16 arguments of 200 characters; and the terminal serves at most 30 starts a minute, 16 waiting. The started script reads the words it was given with event.current() (kind script_run, with caller and depth). Nothing waits for it and its end is not reported back.

Playbooks and managers#

A playbook is a named group of scripts -- strategies, risk scripts, a portfolio controller -- and one manager (.tzm) that says which of them are on. The manager is a pure calculation of the closed candles of the playbook's timeframe (a trend filter, a session window, a volatility switch, anything computable from candles and the price). It is called when a candle closes, never on a tick, in a backtest and live alike, with the decision it gave last time as its only memory, and it answers with the complete set that should be active: a strategy it does not name is suspended (no new entries; what it holds keeps its stops and targets), and risk / portfolio name the member in force (None leaves the account's own). The same script gives the same decisions in both places. It reads the market only -- not the account and not a member's profit -- which is what makes that true; the risk firewall already limits losses. The playbook (members, pins, timeframe, fault policy) is a JSON file the Playbooks window keeps.

Telling the world#

A hook on indicator_signal or strategy_signal can be a script or a built-in webhook (Discord, Telegram or JSON, with a message template and an optional filter by the source indicator or strategy). The webhook goes through the same guards as alert.webhook: https only, no private addresses, one message a second and 20 a minute per address, 1900 characters. No script needs to be written to be told when a strategy running live signals.

Parameters in a dialog: // @param#

An indicator's Config is what a person adjusts, and in the terminal they adjust it with controls, not by writing JSON: putting an indicator on opens its settings dialog (and each indicator keeps a Settings button), with one control per field of struct Config, the chart redrawing as it moves; Apply and Done keep it, Cancel and the window's [x] put back what it was. The control follows the field's type -- a switch for Bool, a box for Int and Float that takes only that, 5000 USDT for Money, 30s for Duration -- and the language has no attributes to say more, so a comment on the line above the field does:

struct Config {
    // @param "Window" min 1 max 200 step 1 default 9 hint "Bars averaged."
    window: Int,
    // @param "Source" options "close|hl2|ohlc4" default "close" group "Data"
    source: String,
}
Word Takes Does
the first quoted text a label the name beside the control (otherwise the field's name, max_gross as Max gross)
min, max a number with both, a slider; with one, the value is checked against it. Only on Int, Float, Decimal, Quantity, Ratio
step a number above 0 how far a slider moves per notch; it snaps to it
default a value in the form of the box (9, 0.85, true, 5000 USDT) what the dialog opens on for a new indicator, and what Defaults restores
options "a|b|c" a dropdown of those values; only on a String
group a name the card the field is in (fields with none share the first, Inputs)
hint a text a line of help under the control

The comment is read from the script's own text by the terminal; the compiler ignores it, so it changes nothing about what the script means and a script with none still has a form. A comment that cannot be honoured -- a range on a Bool, a default outside its range or not in options, a word it does not know, an unclosed quote, a comment with no field after it -- is shown in the dialog as a note and the rest of it still applies. When a script has // Default config: {..} in its header, that fills in the fields the comments give no default for. A Config can hold only scalars (and List or Option of them), so a dropdown is always a String with options; a List is typed as JSON.

The settings of any program: marked constants#

A program that is run (a strategy, a monitor) is asked for its settings the same way. A top-level const of type Int, Float, Decimal, Bool, String or Duration with a // @param comment on the line above is a setting; the terminal shows a dialog with a control for each before the program runs, and runs it with the values given in the place of the written ones -- the file is not changed, and the terminal remembers the values last used for it. The comment takes the same words as above (a label, min, max, step, default, options, group, hint); a Bool is a switch.

// @param "Deviations of the VWAP" min 0.5 max 5 step 0.5 default 2 group "Setup"
const SD_K: Decimal = 2.0;
// @param "Target at the nearest node" default true group "Exit"
const TP_AT_NODE: Bool = true;

A constant with no mark is not a setting. The values reach the program as text in the place of the literal on that line, so every diagnostic still points at the line the person sees. strategies/ideas/absorption_vwap.tzs is written this way.

Decorators on strategy parameters#

A strategy's parameters are what a sweep or an optimizer varies, and the source says which ones and how far, with a decorator written before the parameter (language 0.2):

strategy ema_cross(
    @param(label: "Fast EMA", min: 3, max: 30, step: 1, group: "Signal") fast: Int,
    @param(min: 20, max: 200, step: 5, above: fast) slow: Int,
    @param(min: 0.5, max: 4.0, step: 0.5) stop_atr: Decimal,
    size: Quantity,                       // no decorator: a fixed value, never optimized
) version "1.0.0" { … }
Key Takes Does
min, max a number written out (a whole number for an Int) required (unless the parameter is a switch); the range the optimizer searches (and the bounds the terminal's form checks a typed value against)
step a number above 0 the grid the value moves on; none means continuous (a Decimal)
log true / false spread the range evenly in its logarithm (needs min above 0)
label, group, hint a string the name, the card and the line of help in the terminal's form
above, below the name of another parameter of the same strategy an order the search keeps: slow with above: fast is always searched with fast < slow
switch true an Int that is 1 (on) or 0 (off), shown as a checkbox; the same as min: 0, max: 1, step: 1, and it takes no min, max, step, log or off
off a number (a whole number for an Int) the value that means "this filter is off", for a parameter that has a range when it is on (a ceiling that 0 removes): the terminal shows a checkbox beside the value and sends this one when it is unticked; it is not part of the range searched

The value the program passes (ema_cross(9, 21, …) in main) is where the form starts; the decorator only says how far it may move. The compiler checks the keys, the values, the order of the range and the references (E-DECO-UNKNOWN, E-DECO-VALUE, E-DECO-REF; a decorator on a function's parameter is E-DECO-PLACE; a program that says lang ">=0.1.0, <0.2.0" cannot use one: E-VER-FEATURE). It then puts the range on the signal graph (StrategyParam.tune, IR 0.1.6), which the terminal reads for the Backtests window: a Parameters panel per strategy and an Optimize tab. A decorator never changes what a strategy does; a strategy with decorators and the same strategy without them are the same strategy.

Examples#

  • 71_level_cross.tzi -- an indicator that gives cross-up / cross-down at a level.
  • 76_alma.tzi -- the Arnaud Legoux Moving Average as an indicator: its line on the chart, and cross-up / cross-down when the price goes through it (its strategy is 77_alma_cross.tzs).
  • 72_on_level_cross.tzc -- the control script for its signal: chimes and starts another with script.run.
  • 74_trend_filter.tzm -- a manager: trend or range from two averages, with hysteresis, each state naming its strategies and risk script.
  • 75_session_hours.tzm -- a manager that is not about candles: strategies and risk script by the hour of the UTC day.
  • 61–70 -- risk policies (.tzr) and portfolio controllers (.tzp).