Skip to content
← All examples

Example 77

Alma cross

a trend strategy on the Arnaud Legoux Moving Average (Arnaud Legoux and Dimitrios Kouzis-Loukas, 2009): it buys when a bar closes above the ALMA and sells when a bar closes below it,

Category
Host: needs the terminal's data and engine
Permissions
orders.submit
Before you run it
a chart in hand on the live market or a replay whose instrument the live feed serves (closed bars), nine closed bars of history to start the line
What it does
asks the terminal to run the strategy on that market; once running it places orders on the account the person chose.
How to run it
Add script on the Market Replay window or the title bar's script icon (replay or real time); Insert strategy in the Datasets window (backtest).
Language
>=0.2.0, <0.3.0
Host
>=0.1.17, <0.2.0
Needs
features >=0.1.8, <0.2.0trade >=0.1.0, <0.2.0

Source

// Purpose: a trend strategy on the Arnaud Legoux Moving Average (Arnaud Legoux and Dimitrios Kouzis-Loukas, 2009): it buys when a bar closes above the ALMA and sells when a bar closes below it,
// so it is always on the side the last close chose -- long above the line, short under it. The line is the same one the indicator draws (76_alma.tzi) with the authors' values: 9 bars, offset 0.85,
// sigma 6, i.e. a Gaussian over the nine last closes whose peak sits on the second newest. A strategy's signals can only be sums and products of what the engine gives it, so the line is written out:
// each close (`features.close_ago(k)`, 0 the last closed bar) times its weight, the nine weights summing to 1. They are `exp(-(i - m)^2 / (2 s^2))` for i = 0..8 (0 the oldest) with
// m = offset * (window - 1) = 6.8 and s = window / sigma = 1.5, divided by their sum; to try another window, offset or sigma, compute the weights again the same way and write one `close_ago` per
// weight (76_alma.tzi takes those three as its configuration).
//
//   LONG    a bar closes above the ALMA (the close goes up through it): a market buy, with a target 0.3 % above the entry and a stop 0.3 % below it -- one risk for one reward, 1:1.
//   SHORT   a bar closes under the ALMA: a market sell, target 0.3 % below, stop 0.3 % above.
//   EXIT    the target or the stop, whichever the price reaches first (they are orders the host leaves on the position, not conditions the strategy watches); or a bar closing on the other side of the
//           line, which closes the position at the market and, the book being flat, takes the other side at once -- stop and reverse, with its own 0.3 % pair. A position closed by its target or stop waits for the
//           next cross: it does not buy again in the same trend.
//   `distance_pct` is that 0.3 (a percentage of the entry price, the same distance for the target and the stop); change it in `main` to size the pair.
//
// WHY THE CLOSE OF A BAR AND NOT THE PRICE NOW: the price crosses a moving line many times inside one bar when it is close to it, and each crossing would be a trade; a bar's close is one reading, never
// repainted, and a backtest, a replay and the live market read it the same way. The cost is that the signal comes when the bar closes, not when the price touched the line.
// HONEST NOTE: written to the specification and checked that it compiles and hands over a strategy; its edge has not been measured. A moving-average cross loses in ranges (it buys the top of a swing
// and sells the bottom) and pays in trends, and the fees of a reversal each time are not small on a short timeframe: backtest it on the instrument and timeframe before running it with money.
// Category: host
// Permissions: orders.submit
// Preconditions: a chart in hand on the live market or a replay whose instrument the live feed serves (closed bars), nine closed bars of history to start the line
// Side effects: asks the terminal to run the strategy on that market; once running it places orders on the account the person chose.
// Run: Add script on the Market Replay window or the title bar's script icon (replay or real time); Insert strategy in the Datasets window (backtest).

lang ">=0.2.0, <0.3.0";
host "ttt" ">=0.1.17, <0.2.0";
requires "features" ">=0.1.8, <0.2.0";
requires "trade" ">=0.1.0, <0.2.0";

strategy alma_cross(distance_pct: Decimal, size: Quantity) version "1.1.0" {
    // The last closed bar, and the ALMA(9, 0.85, 6) of the closes up to it. Unknown until nine bars are known, and a rule that reads an unknown does not fire.
    let bar = features.bar_close();
    let alma =
        0.0000104644 * features.close_ago(8) +
        0.0001720830 * features.close_ago(7) +
        0.0018144406 * features.close_ago(6) +
        0.0122667003 * features.close_ago(5) +
        0.0531732242 * features.close_ago(4) +
        0.1477877594 * features.close_ago(3) +
        0.2633687034 * features.close_ago(2) +
        0.3009331953 * features.close_ago(1) +
        0.2204734295 * features.close_ago(0);

    let px = features.price(features.Source.AggTrades);
    let held = features.position_sign();
    // The target and the stop, the same distance either side of the price the entry is decided at.
    let above = px * (1 + distance_pct / 100);
    let below = px * (1 - distance_pct / 100);

    // Which side to take once flat after a position was closed by the line: 1 long, -1 short, 0 none. Set when a bar closes on the other side of the line, cleared when the entry is made.
    var side = 0;
    let to_long = 1;
    let to_short = 0 - 1;
    let none = 0;

    // A bar closed on the other side of the line: the position is closed, and the other side is the next entry.
    rule exit_long when exposure_is(Exposure.Open) && held > 0 && cross_below(bar, alma)
        => [order(Side.Sell, Intent.Close, OrderType.Market, Sizing.Fixed(size)), set(side, to_short)];
    rule exit_short when exposure_is(Exposure.Open) && held < 0 && cross_above(bar, alma)
        => [order(Side.Buy, Intent.Close, OrderType.Market, Sizing.Fixed(size)), set(side, to_long)];

    // Flat on a cross: take that side.
    rule enter_long when exposure_is(Exposure.Flat) && cross_above(bar, alma)
        => order(Side.Buy, Intent.Open, OrderType.Market, Sizing.Fixed(size), Protect.Bracket(above, below));
    rule enter_short when exposure_is(Exposure.Flat) && cross_below(bar, alma)
        => order(Side.Sell, Intent.Open, OrderType.Market, Sizing.Fixed(size), Protect.Bracket(below, above));

    // Flat after the line closed a position: the side the cross chose, while the last bar is still on it.
    rule reverse_long when exposure_is(Exposure.Flat) && side > 0 && bar > alma
        => [order(Side.Buy, Intent.Open, OrderType.Market, Sizing.Fixed(size), Protect.Bracket(above, below)), set(side, none)];
    rule reverse_short when exposure_is(Exposure.Flat) && side < 0 && bar < alma
        => [order(Side.Sell, Intent.Open, OrderType.Market, Sizing.Fixed(size), Protect.Bracket(below, above)), set(side, none)];
    flatten_on_stop;
}

fn main() -> Result<Unit, Error> {
    // A target and a stop 0.3 % from the entry; a hundredth of a coin.
    let run = trade.run(alma_cross(0.3, qty(0.01)))?;
    print(run.name + " on " + run.target + ": " + run.state);
    return Ok(());
}