Writing Your Own Strategy
This chapter walks through creating a new strategy from scratch.
Step 1: Create the strategy directory
mkdir strategies/mystrategyStep 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,graphUsing 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=trueSince 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.