RunState and Price Data

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

FunctionAliasReturns
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

FunctionReturns
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:

FunctionReturns
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

FunctionDescription
.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

FunctionDescription
.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

FunctionDescription
.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

FunctionDescription
.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)