Anatomy of a Strategy

Anatomy of a Strategy

An Algolang strategy is a Go package that implements a specific interface. This chapter describes each component.

Mandatory components

Every strategy must contain:

  1. A package declaration
  2. An import declaration (at minimum, the engine package)
  3. A Strategy struct declaration
  4. A Strategy function

Optional components

Strategies may also include:

  • A Begin function (called before the first bar)
  • An End function (called after the last bar)
  • A Name function (returns the strategy’s display name)
  • A Description function (returns a description)

The package declaration

The package declaration is the first line of the file:

package mystrategy

The package name serves as the default strategy name if no Name() function is provided.

The import declaration

At minimum, the engine package must be imported:

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

Additional packages from the Go standard library or any external source can be imported as needed.

The Strategy struct

The Strategy struct defines the strategy’s inputs and any state that must persist between bars:

type Strategy struct {
    LenFast int `algolang:"default=10,values=[5,10,15,20,25,30,40,50]"`
    LenSlow int `algolang:"default=30,values=[20,25,30,40,50]"`
}

The algolang: struct tag

Struct fields with the algolang: tag are treated as strategy inputs. The tag supports:

TagDescriptionExample
default=NDefault valuedefault=10
values=[...]Discrete values for optimisationvalues=[5,10,15,20]
min=NMinimum value for optimisationmin=5
max=NMaximum value for optimisationmax=100
by=NStep size for optimisationby=5

Examples:

// Discrete values
Length int `algolang:"default=20,values=[10,20,50,100,200]"`

// Range with step
Mult float64 `algolang:"default=1.5,min=0.5,max=3.0,by=0.5"`

// Boolean input
IsDebug bool `algolang:"default=false"`

// String input
SwingMode string `algolang:"default=SSC,values=[SSC,NSC]"`

Fields without the algolang: tag are private to the strategy and can be used to store any state between bars.

The Strategy function

The Strategy function is called once for every bar in the primary data series. It receives a *engine.RunState parameter that provides access to all price data, position state, and order placement:

func (s *Strategy) Strategy(rs *engine.RunState) error {
    // Trading logic here
    return nil
}

Returning a non-nil error halts the strategy immediately.

The Begin function

Called once before the first bar is processed. Use it for initialisation, such as loading additional data series:

func (s *Strategy) Begin(rs *engine.RunState) error {
    // Load weekly data for the primary symbol
    _, err := rs.LoadSeries(engine.SeriesOptions{
        BarInterval: "weekly",
    })
    return err
}

The End function

Called once after the last bar is processed. Use it for any cleanup or final calculations:

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

The Name and Description functions

These return the display name and description shown in the algo strategies -l listing:

func (s *Strategy) Name() string {
    return "Moving Average Crossover"
}

func (s *Strategy) Description() string {
    return "Simple moving-average crossover strategy."
}

If not provided, the package name is used for both.

Complete example

Here is a complete moving average crossover strategy:

package macrossover

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

type Strategy struct {
    LenFast int `algolang:"default=10,values=[5,10,15,20]"`
    LenSlow int `algolang:"default=30,values=[20,30,40,50,75,100]"`
}

func (s *Strategy) Strategy(rs *engine.RunState) error {
    if rs.MarketPosition() <= 0 {
        if rs.Close(0).Average(s.LenFast).CrossesAbove(rs.Close(0).Average(s.LenSlow)) {
            rs.Buy()
        }
    }

    if rs.MarketPosition() >= 0 {
        if rs.Close(0).Average(s.LenFast).CrossesBelow(rs.Close(0).Average(s.LenSlow)) {
            rs.SellShort()
        }
    }

    return nil
}

func (s *Strategy) Name() string {
    return "Moving Average Crossover"
}

func (s *Strategy) Description() string {
    return "Simple moving-average crossover strategy."
}

Registering a strategy

To make a strategy available as a built-in:

  1. Create a directory under strategies/ (e.g., strategies/mystrategy/)
  2. Place the strategy .go file inside it
  3. Run make generate && make algo to rebuild

The code generator scans strategies/ and registers all packages that contain a Strategy struct.