Skip to content
← All examples

Example 29

Custom indicator

a custom indicator written in T0: an EMA of the chart's closes drawn as a line on the chart, and a mark where a

Category
Workspace: reads or changes charts and panels
Permissions
market.readworkspace.readworkspace.write
Before you run it
a terminal window with a chart on a timeframe the backend keeps candles for (1m, 5m, 15m, 30m, 1h, 4h, 1D)
What it does
DRAWS on the active chart: one line and the crossing marks, named "ema_cross". Delete them with plot.clear.
How to run it
open the T0 Editor, grant market.read, workspace.read and workspace.write, Run. Attach it to a "Timeframe changes"
Language
>=0.1.1, <0.2.0
Host
>=0.1.6, <0.2.0
Needs
market >=0.1.2, <0.2.0workspace >=0.1.0, <0.2.0plot >=0.1.0, <0.2.0

Source

// Purpose: a custom indicator written in T0: an EMA of the chart's closes drawn as a line on the chart, and a mark where a
// fast EMA crosses a slow one. Run it again and it replaces its own drawing; nothing a person drew is touched.
// Category: workspace
// Permissions: market.read, workspace.read, workspace.write
// Preconditions: a terminal window with a chart on a timeframe the backend keeps candles for (1m, 5m, 15m, 30m, 1h, 4h, 1D)
// Side effects: DRAWS on the active chart: one line and the crossing marks, named "ema_cross". Delete them with plot.clear.
// Run: open the T0 Editor, grant market.read, workspace.read and workspace.write, Run. Attach it to a "Timeframe changes"
// hook (Script Hooks) to keep it on every chart you switch.

lang ">=0.1.1, <0.2.0";
host "ttt" ">=0.1.6, <0.2.0";
requires "market" ">=0.1.2, <0.2.0";
requires "workspace" ">=0.1.0, <0.2.0";
requires "plot" ">=0.1.0, <0.2.0";
import std.ind;

const OWNER: String = "ema_cross";

// The value at an index, whether the index is past the series or the indicator has not warmed up yet.
fn at(xs: List<Option<Float>>, i: Int) -> Option<Float> {
    let found = match xs.get(i) {
        Some(value) => value,
        None => None,
    };
    return found;
}

// How far the fast line is above the slow one at an index (None until both have a value).
fn gap(fast: List<Option<Float>>, slow: List<Option<Float>>, i: Int) -> Option<Float> {
    let found = match at(fast, i) {
        Some(f) => match at(slow, i) {
            Some(s) => Some(f - s),
            None => None,
        },
        None => None,
    };
    return found;
}

fn main() -> Result<Unit, Error> {
    let chart = workspace.active_chart().expect("no chart is open");
    // Only closed candles: a bar that can still change is never given to a program.
    let candles = market.chart_candles(chart.symbol, chart.timeframe, 500)?;
    let mut closes: List<Float> = [];
    for c in candles {
        closes.push(to_float(c.close.amount()));
    }
    let fast = ind.ema(closes, 9);
    let slow = ind.ema(closes, 21);
    plot.clear(chart.chart, OWNER)?;

    let mut points: List<plot.Point> = [];
    let mut crossings = 0;
    let mut i = 0;
    while i < candles.len() {
        let candle = candles.get(i).expect("a candle");
        match at(slow, i) {
            Some(value) => {
                points.push(plot.Point { time: candle.open_time, price: price(decimal_from_float(value, 2), candle.close.currency()) });
            }
            None => {}
        }
        // Crossed: the fast line was at or below the slow one on the previous bar and is above now, or the reverse.
        if i > 0 {
            match gap(fast, slow, i - 1) {
                Some(before) => {
                    match gap(fast, slow, i) {
                        Some(now) => {
                            if before <= 0.0 && now > 0.0 {
                                plot.marker(chart.chart, OWNER, candle.open_time, candle.low, "▲ cross up", "green")?;
                                crossings = crossings + 1;
                            }
                            if before >= 0.0 && now < 0.0 {
                                plot.marker(chart.chart, OWNER, candle.open_time, candle.high, "▼ cross down", "red")?;
                                crossings = crossings + 1;
                            }
                        }
                        None => {}
                    }
                }
                None => {}
            }
        }
        i = i + 1;
    }
    plot.line(chart.chart, OWNER, points, "EMA 21", "#ff8000")?;
    print(chart.symbol + " " + chart.timeframe + ": " + to_string(candles.len()) + " candles, EMA 21 drawn, " + to_string(crossings) + " crossings marked");
    return Ok(());
}