RunState and Price Data
The RunState object is the primary interface between a strategy and the Algolang engine. It is passed to Begin(), Strategy(), and End() and provides access to all price data, position state, order placement, and instrument information.
Accessing price data
Price data is accessed through series functions on RunState. Each function takes a dataN parameter indicating which data series to use (0 for the primary series, 1 for the second, and so on).
Basic price series
| Function | Alias | Returns |
|---|---|---|
Open(dataN) | O(dataN) | Series of opening prices |
High(dataN) | H(dataN) | Series of high prices |
Low(dataN) | L(dataN) | Series of low prices |
Close(dataN) | C(dataN) | Series of closing prices |
Volume(dataN) | V(dataN) | Series of volume values |
Composite price series
| Function | Returns |
|---|---|
HLAvg(dataN) | Average of High and Low |
HLCAvg(dataN) | Average of High, Low, and Close |
OHLCAvg(dataN) | Average of Open, High, Low, and Close |
OCAvg(dataN) | Average of Open and Close |
Daily/Weekly/Monthly aggregates
Price data can also be accessed at daily, weekly, monthly, and session boundaries:
| Function | Returns |
|---|---|
CloseD(dataN, loc) | Series of daily closing prices |
HighD(dataN, loc) | Series of daily highs |
LowD(dataN, loc) | Series of daily lows |
OpenD(dataN, loc) | Series of daily opens |
CloseW(dataN, loc) | Weekly closing price |
CloseM(dataN, loc) | Monthly closing price |
SessionOpen(dataN) | Opening price of the current session |
SessionHigh(dataN) | Highest price of the current session |
SessionLow(dataN) | Lowest price of the current session |
SessionClose(dataN) | Closing price of the current session |
The loc parameter is a *time.Location for timezone-aware boundary calculations.
Series functions
Price functions return an nseries.Series, which supports a rich set of chained operations:
Statistical functions
| Function | Description |
|---|---|
.Average(length) | Simple moving average |
.StdDev(length) | Standard deviation |
.Variance(length) | Variance |
.RSI(length) | Relative Strength Index |
.Bollinger(length, nStdDevs) | Bollinger Band (positive for upper, negative for lower) |
Extreme values
| Function | Description |
|---|---|
.Highest(length) | Highest value over N bars |
.Lowest(length) | Lowest value over N bars |
.HighestBar(length) | Bars since highest value |
.LowestBar(length) | Bars since lowest value |
Comparison functions
| Function | Description |
|---|---|
.CrossesAbove(other) | True when series crosses above another |
.CrossesBelow(other) | True when series crosses below another |
.GreaterThan(other) | True when series is greater than another |
.LessThan(other) | True when series is less than another |
Arithmetic functions
| Function | Description |
|---|---|
.Add(value) | Add a constant or series |
.Sub(value) | Subtract a constant or series |
.Mul(value) | Multiply by a constant or series |
.Div(value) | Divide by a constant or series |
How series functions compose
To appreciate the power of the series system, consider the implementation of the Bollinger function:
func (s Series) Bollinger(length int, nbrStdDevs float64) Series {
return s.Average(length).Add(s.StdDev(length).Mul(nbrStdDevs))
}Note that this function applies the calculation to whatever series is used as the receiver. A strategy can calculate the upper and lower Bollinger bands on the OHLC average price as follows:
upper := rs.OHLCAvg(0).Bollinger(20, 2.0)
lower := rs.OHLCAvg(0).Bollinger(20, -2.0)Some series functions require that data series be passed as parameters. Consider the implementation of Williams %R:
func (s Series) WilliamsR(h, l, c Series, n int) Series {
return h.Highest(n).Sub(c).Div(h.Highest(n).Sub(l.Lowest(n))).Mul(-100).SetN(n-1, 0.00)
}Since the calculation requires High, Low, and Close prices, they are passed as explicit parameters. In a strategy:
willPctR := rs.Any().WilliamsR(rs.High(0), rs.Low(0), rs.Close(0), 14)Note the use of Any(), which returns an empty series for use as a receiver when the operation needs multiple data series as parameters (see The Any() function below).
Chaining example
Series functions can be chained to express complex calculations concisely:
// Fast MA crosses above slow MA
signal := rs.Close(0).Average(10).CrossesAbove(rs.Close(0).Average(50))
// Arithmetic chain
adjusted := rs.Close(0).Mul(2).Add(math.Pi).Div(math.Sqrt2)
// High divided by Low series
highDivLow := rs.High(0).Div(rs.Low(0))The Value() function
To extract a scalar value from a series, use Value():
c := rs.Close(0)
currentClose := c.Value() // Current bar's closing price
previousClose := c.Value(1) // Previous bar's closing price
twoBack := c.Value(2) // Two bars ago
Value() with no argument returns the most recent value. Value(n) looks back n bars.
The LookBack() function
LookBack(n) returns a new series shifted by n bars. This is useful when comparing current values to historical ones:
// Close 1 bar ago
prevClose := rs.Close(0).LookBack(1)
// Lowest close over 20 bars, as of 1 bar ago
prevLowest := rs.Close(0).Lowest(20).LookBack(1)The Func() function
The Func() function allows arbitrary transformations to be applied to a series. It takes a function that receives a series and returns a new series:
// Add a constant to every value
addN := func(n float64) func(nseries.Series) nseries.Series {
return func(s nseries.Series) nseries.Series {
result := make(nseries.Series, len(s))
for i := range s {
result[i] = s[i] + n
}
return result
}
}
adjusted := rs.Close(0).Func(addN(5.0)).Average(20).Value()Closures can be used to create more sophisticated custom functions. The following addFibonacci() function adds the repeating sequence of the first 20 Fibonacci numbers to the values in a series:
func addFibonacci() func(nseries.Series) nseries.Series {
return func(s nseries.Series) nseries.Series {
var fib func(i int) float64
fib = func(i int) float64 {
switch i % 20 {
case 0, 1:
return float64(i)
default:
return fib(i-1) + fib(i-2)
}
}
result := make(nseries.Series, 0)
for i := 0; i < len(s); i++ {
result = append(result, s[i]+fib(int(s[i])))
}
return result
}
}This can be inserted into any chain of series functions:
closePlusFibAvg20 := rs.Close(0).Func(addFibonacci()).Average(20).Value()Note, however, that the Fibonacci sequence above is generated on each call, which adds unnecessary performance overhead. The closure can be used to generate the sequence once and reuse it:
func addFibonacci(n int) func(nseries.Series) nseries.Series {
var fib func(i int) float64
var fibs []float64
fib = func(i int) float64 {
switch i {
case 0, 1:
return float64(i)
default:
return fib(i-1) + fib(i-2)
}
}
for i := 0; i < n; i++ {
fibs = append(fibs, fib(i))
}
return func(s nseries.Series) nseries.Series {
result := make(nseries.Series, 0)
for i := 0; i < len(s); i++ {
result = append(result, s[i]+fibs[i%n])
}
return result
}
}The closure function can take any arbitrary parameters, including other functions (and even other closure functions).
While the average Algolang user will probably never use Func(), it serves as a powerful example of just how unconstrained Algolang strategies can be.
The Any() function
Any() returns an empty series, useful as a receiver for operations that require multiple data series as parameters (e.g., Williams %R, which needs High, Low, and Close):
willR := rs.Any().WilliamsR(rs.High(0), rs.Low(0), rs.Close(0), 14)