Writing Your Own Strategy

Writing Your Own Strategy

This chapter walks through creating a new strategy from scratch.

Step 1: Create the strategy directory

mkdir strategies/mystrategy

Step 2: Create the strategy file

Create strategies/mystrategy/mystrategy.go:

package mystrategy

import (
    "github.com/kmglennan/algolang/pkg/engine"
)

type Strategy struct {
    // Inputs with algolang tags
    RSIPeriod    int     `algolang:"default=14,values=[7,14,21]"`
    Overbought   float64 `algolang:"default=70,min=60,max=90,by=5"`
    Oversold     float64 `algolang:"default=30,min=10,max=40,by=5"`
    StopDollars  float64 `algolang:"default=500"`

    // Private state (no algolang tag)
    entryBar int
}

func (s *Strategy) Name() string {
    return "My RSI Strategy"
}

func (s *Strategy) Description() string {
    return "RSI-based mean reversion with stop loss."
}

func (s *Strategy) Begin(rs *engine.RunState) error {
    // Any initialisation
    return nil
}

func (s *Strategy) Strategy(rs *engine.RunState) error {
    rsi := rs.Close(0).RSI(s.RSIPeriod).Value()

    // Enter long when oversold
    if rs.MarketPosition() <= 0 && rsi < s.Oversold {
        rs.Buy()
        rs.SetStopLoss(s.StopDollars)
        s.entryBar = rs.BarNumber(0)
    }

    // Enter short when overbought
    if rs.MarketPosition() >= 0 && rsi > s.Overbought {
        rs.SellShort()
        rs.SetStopLoss(s.StopDollars)
        s.entryBar = rs.BarNumber(0)
    }

    // Time-based exit after 5 bars
    if rs.MarketPosition() != 0 && rs.BarsSinceEntry() >= 5 {
        if rs.MarketPosition() > 0 {
            rs.ExitLong(engine.AtMarket)
        } else {
            rs.ExitShort(engine.AtMarket)
        }
    }

    return nil
}

func (s *Strategy) End(rs *engine.RunState) error {
    return nil
}

Step 3: Build and run

make generate && make algo
algo run --strategy mystrategy --symbols @ES --output-mode summary,graph

Using callbacks

Order callbacks allow you to react when an order is filled. This is the mechanism behind linked orders (see the Linked orders section in the Trading Orders chapter):

func (s *Strategy) Strategy(rs *engine.RunState) error {
    if rs.MarketPosition() == 0 {
        rs.Buy(1, engine.AtOrLower, entryPrice, func(rs *engine.RunState, fill *engine.Order) {
            rs.ExitLong("Target", engine.AtOrHigher, fill.Price()+5.0)
            rs.SetStopLoss(500.00)
            rs.SetDollarTrailing(300.00)
        }, "Entry")
    }
    return nil
}

When the entry fills, the callback fires immediately during the same bar’s tick evaluation. Exits and stops placed inside the callback are active without the usual one-bar delay.

Debugging strategies

Use fmt.Printf or a debug input to add diagnostic output:

type Strategy struct {
    IsDebug bool `algolang:"default=false"`
}

func (s *Strategy) Strategy(rs *engine.RunState) error {
    if s.IsDebug {
        fmt.Printf("Bar %d: Close=%.2f RSI=%.2f Pos=%d\n",
            rs.BarNumber(0),
            rs.Close(0).Value(),
            rs.Close(0).RSI(14).Value(),
            rs.MarketPosition())
    }
    // ...
    return nil
}

Run with debug output:

algo run --strategy mystrategy --symbols @ES --inputs IsDebug=true

Since Algolang strategies are standard Go code, the built-in Go debugger (dlv) can be used to set breakpoints, inspect variables, and step through execution of both your strategy logic and the Algolang engine itself. This is particularly useful for diagnosing complex issues that are difficult to reproduce with print statements alone.