Data Series and Multi-Timeframe Strategies
Algolang strategies can operate on one or more data series simultaneously. This enables multi-timeframe analysis, inter-market strategies, and spread/pairs trading.
The primary series
The primary data series is always index 0. All trading functions (Buy, SellShort, etc.) operate on the primary series. The Strategy() function is called once per bar of the primary series.
Specifying multiple series on the command line
Use the slash separator to specify multiple series:
# Two symbols in one run (e.g., for spread or pairs strategies)
algo run --strategy pairs --symbols @ES/@NQIn the strategy, access each series by index:
esClose := rs.Close(0) // Primary series (@ES)
nqClose := rs.Close(1) // Second series (@NQ)
Dynamic series loading
Strategies can load additional data series programmatically in the Begin() function using LoadSeries():
func (s *Strategy) Begin(rs *engine.RunState) error {
// Load weekly data of the primary symbol
weeklyIdx, err := rs.LoadSeries(engine.SeriesOptions{
BarInterval: "weekly",
})
if err != nil {
return err
}
s.weeklyIndex = weeklyIdx // Store for use in Strategy()
return nil
}SeriesOptions
| Field | Type | Default | Description |
|---|---|---|---|
Symbol | string | Primary symbol | Symbol to load (with prefix) |
BarInterval | string | "daily" | Bar interval |
BarBuildingMode | string | "natural" | "natural" or "session" |
SessionHours | string | Symbol default | Trading session hours |
SessionDays | string | Symbol default | Trading session days |
Timezone | string | Exchange TZ | IANA timezone |
Detrend | bool | false | Apply logarithmic detrending |
DetrendMode | string | "simple" | "simple" or "regression" |
SeriesIndex | int | Auto-assign | Explicit series index; values < 1 mean auto-assign next available |
If Symbol is omitted, the primary symbol is used. This makes it easy to load additional timeframes of the same instrument.
If SeriesIndex is not specified (or is less than 1), the series is automatically assigned the next available index. The returned index should be stored for later use when accessing the series data.
Key constraints
LoadSeries()can only be called fromBegin(). Calling it fromStrategy()orEnd()returns an error.- Dynamically loaded series are automatically synchronised with the primary series.
Data series synchronisation
The engine iterates over bars in the primary series. For each primary bar, additional series are synchronised so that the strategy sees the most recent bar of each series that is not later than the current primary bar.
This means a strategy running on 60-minute bars with a daily secondary series will see the daily bar update once per day, while the primary series updates every hour.
Example: daily + weekly strategy
package lookaway
import "github.com/kmglennan/algolang/pkg/engine"
type Strategy struct {
Len int `algolang:"default=10,min=5,max=50,by=1"`
weeklyIndex int
}
func (s *Strategy) Begin(rs *engine.RunState) error {
idx, err := rs.LoadSeries(engine.SeriesOptions{
BarInterval: "weekly",
})
if err != nil {
return err
}
s.weeklyIndex = idx
return nil
}
func (s *Strategy) Strategy(rs *engine.RunState) error {
weeklyClose := rs.Close(s.weeklyIndex)
if weeklyClose.Value() >= weeklyClose.Lowest(s.Len).LookBack(1).Value() {
rs.Buy()
} else {
rs.SellShort()
}
return nil
}