# Golang : Markov chains to predict probability of next state example

Putting these two Golang codes here for my own future reference. The codes are adapted from the DataCamp's Markov Chains Python tutorial and writing the codes in Golang helps me to understand better about how Markov Chains works in predicting the next state or outcome. Useful in many situations and especially in trading. Can be used to predict the next trend of either bearish, bullish or stagnant.

If you've no idea about what these codes trying to do. Head over to DataCamp's Markov Chains Python tutorial to learn about Markov Chains first.

Here you go!

`Part 1 :`

`````` package main

import (
"fmt"
"strconv"
"strings"
"time"

"math/rand"

"github.com/gonum/matrix/mat64"
)

// statespace
var states = []string{"sleep", "icecream", "run"}

// possible sequences of events
var transitionFromSleep = []string{
"SS",
"SR",
"SI",
}

var transitionFromRun = []string{
"RS",
"RR",
"RI",
}

var transitionFromIceCream = []string{
"IS",
"IR",
"II",
}

var transitionMatrix mat64.Matrix

func main() {
// probabilities matrix(transition matrix)
// numbers are made up out of thin air...
transitionMatrixString := "[0.2 0.6 0.2;0.1 0.6 0.3; 0.2 0.7 0.1]"
transitionMatrix = matrix(transitionMatrixString)

// print all transitionMatrix elements
fmt.Printf("transitionMatrix :\n%v\n\n", mat64.Formatted(transitionMatrix, mat64.Prefix(""), mat64.Excerpt(0)))

// perform simple sanity check.
// all probabilities must sum up to 1, therefore we have 3 rows and the final
// result must be equal to 3
s := mat64.Sum(transitionMatrix)
s1 := fmt.Sprintf("%0.1f", s)

if s1 != "3.0" {
panic("Probabilites sum up must be equal to 3, each row sum up to 1. Check your transition matrix")
} else {
fmt.Println("Transition matrix checked out and proceeding to next stage!")
}

activityForecast(2)
}

func matrixByRow(a mat64.Matrix, rowNumber int) []float64 {
dst := []float64{0.0, 0.0, 0.0}
mat64.Row(dst, rowNumber, a)
return dst
}

func sampleSet(cdf []float64) int {
// https://stackoverflow.com/questions/50507513/golang-choice-number-from-slice-array-with-given-probability

rand.Seed(time.Now().UnixNano())
r := rand.Float64()

bucket := 0

for r > cdf[bucket] {
bucket++
if bucket == len(cdf) {
bucket--
break // need this to prevent runtime error: index out of range
}
}
return bucket
}

func activityForecast(days int) {
// default state
todayActivity := "sleep"
fmt.Println("Start state : ", todayActivity)

activityList := []string{}
activityList = append(activityList, todayActivity)

i := 0
// calculate the probability of the activityList
prob := 1.0

for i < days {
if todayActivity == "sleep" {
sleepNextProbabilityDistribution := matrixByRow(transitionMatrix, 0)

randomChoice := sampleSet(sleepNextProbabilityDistribution)
change := transitionFromSleep[randomChoice]

if change == "SS" {
prob = prob * 0.2
activityList = append(activityList, "sleep")
} else if change == "SR" {
prob = prob * 0.6
todayActivity = "run"
activityList = append(activityList, "run")
} else {
prob = prob * 0.2
todayActivity = "icecream"
activityList = append(activityList, "icecream")
}

} else if todayActivity == "run" {
runNextProbabilityDistribution := matrixByRow(transitionMatrix, 1)

randomChoice := sampleSet(runNextProbabilityDistribution)
change := transitionFromRun[randomChoice]

if change == "RR" {
prob = prob * 0.5
activityList = append(activityList, "run")
} else if change == "RS" {
prob = prob * 0.2
todayActivity = "sleep"
activityList = append(activityList, "sleep")
} else {
prob = prob * 0.3
todayActivity = "icecream"
activityList = append(activityList, "icecream")
}

} else {
sleepNextProbabilityDistribution := matrixByRow(transitionMatrix, 2)

randomChoice := sampleSet(sleepNextProbabilityDistribution)
change := transitionFromIceCream[randomChoice]

if change == "II" {
prob = prob * 0.1
activityList = append(activityList, "icecream")
} else if change == "IS" {
prob = prob * 0.2
todayActivity = "sleep"
activityList = append(activityList, "sleep")
} else {
prob = prob * 0.7
todayActivity = "run"
activityList = append(activityList, "run")
}

}

i = i + 1
}

fmt.Println("Possible states : ", activityList)
fmt.Println("End state after " + strconv.Itoa(days) + " days : " + todayActivity)
fmt.Println("Probability of the possible sequence of states : ", strconv.FormatFloat(prob, 'f', 6, 64))
}

// from https://www.socketloop.com/tutorials/golang-emulate-numpy-way-of-creating-matrix-example

// WARNING: this function does not perform error checking on input string
// You probably need to modify this function to perform sanity check on stuff such as:

// such as only 1 pair of [ ]
// all columns and rows have number or 0
// all rows are same size [pynum will throw "ValueError: Rows not the same size."]

func matrix(str string) *mat64.Dense {

// remove [ and ]
str = strings.Replace(str, "[", "", -1)
str = strings.Replace(str, "]", "", -1)

// calculate total number of rows
parts := strings.SplitN(str, ";", -1)
rows := len(parts)

// calculate total number of columns
colSlice := strings.Fields(parts)
columns := len(colSlice)

// replace all ; with space
str = strings.Replace(str, ";", " ", -1)

// convert str to slice
// taken from https://www.socketloop.com/tutorials/golang-convert-string-to-array-slice
elements := strings.Fields(str)

// populate data for the new matrix(Dense type)
data := make([]float64, rows*columns)
for i := range data {
floatValue, _ := strconv.ParseFloat(elements[i], 64)
data[i] = floatValue
}

M := mat64.NewDense(rows, columns, data)
return M
}
``````

Sample output:

transitionMatrix :

⎡0.2 0.6 0.2⎤

⎢0.1 0.6 0.3⎥

⎣0.2 0.7 0.1⎦

Transition matrix checked out and proceeding to next stage!

Start state : sleep

Possible states : [sleep run run]

End state after 2 days : run

Probability of the possible sequence of states : 0.300000

`Part 2 :`

`````` package main

import (
"fmt"
"strconv"
"strings"
"time"

"math/rand"

"github.com/gonum/matrix/mat64"
)

// statespace
var states = []string{"sleep", "icecream", "run"}

// possible sequences of events
var transitionFromSleep = []string{
"SS",
"SR",
"SI",
}

var transitionFromRun = []string{
"RS",
"RR",
"RI",
}

var transitionFromIceCream = []string{
"IS",
"IR",
"II",
}

var transitionMatrix mat64.Matrix

func main() {
// probabilities matrix(transition matrix)
// numbers are made up out of thin air...
transitionMatrixString := "[0.2 0.6 0.2;0.1 0.6 0.3; 0.2 0.7 0.1]"
transitionMatrix = matrix(transitionMatrixString)

// print all transitionMatrix elements
fmt.Printf("transitionMatrix :\n%v\n\n", mat64.Formatted(transitionMatrix, mat64.Prefix(""), mat64.Excerpt(0)))

// perform simple sanity check.
// all probabilities must sum up to 1, therefore we have 3 rows and the final
// result must be equal to 3
s := mat64.Sum(transitionMatrix)
s1 := fmt.Sprintf("%0.1f", s)

if s1 != "3.0" {
panic("Probabilites sum up must be equal to 3, each row sum up to 1. Check your transition matrix")
} else {
fmt.Println("Transition matrix checked out and proceeding to next stage!")
}

// to save every activityList
activityListSlice := [][]string{}
count := 0
numberOfIterations := 10000

// iterations
for i := 1; i < numberOfIterations; i++ {
activityListSlice = append(activityListSlice, activityForecast(2))
}

// check out all the 'activityList' we collected
//fmt.Println(activityListSlice)

// iterate through the list to get a count of all activities ending in the state : "Run"
for _, v := range activityListSlice {
if v == "run" {
count++

}
}

// calculate the probability of starting from state: "sleep" and ending at state "run"
inner := float64(count) / float64(numberOfIterations)
percentage := inner * 100.00
fmt.Printf("Probability of starting at state: 'sleep' and ending at state 'run' : %0.2f%%\n", percentage)
}

func matrixByRow(a mat64.Matrix, rowNumber int) []float64 {
dst := []float64{0.0, 0.0, 0.0}
mat64.Row(dst, rowNumber, a)
return dst
}

func sampleSet(cdf []float64) int {
// https://stackoverflow.com/questions/50507513/golang-choice-number-from-slice-array-with-given-probability

rand.Seed(time.Now().UnixNano())
r := rand.Float64()

bucket := 0

for r > cdf[bucket] {
bucket++
if bucket == len(cdf) {
bucket--
break // need this to prevent runtime error: index out of range
}
}
return bucket
}

func activityForecast(days int) []string {
// default state
todayActivity := "sleep"
activityList := []string{}
activityList = append(activityList, todayActivity)

i := 0
// calculate the probability of the activityList
prob := 1.0

for i < days {
if todayActivity == "sleep" {
sleepNextProbabilityDistribution := matrixByRow(transitionMatrix, 0)

randomChoice := sampleSet(sleepNextProbabilityDistribution)
change := transitionFromSleep[randomChoice]

if change == "SS" {
prob = prob * 0.2
activityList = append(activityList, "sleep")
} else if change == "SR" {
prob = prob * 0.6
todayActivity = "run"
activityList = append(activityList, "run")
} else {
prob = prob * 0.2
todayActivity = "icecream"
activityList = append(activityList, "icecream")
}

} else if todayActivity == "run" {
runNextProbabilityDistribution := matrixByRow(transitionMatrix, 1)

randomChoice := sampleSet(runNextProbabilityDistribution)
change := transitionFromRun[randomChoice]

if change == "RR" {
prob = prob * 0.5
activityList = append(activityList, "run")
} else if change == "RS" {
prob = prob * 0.2
todayActivity = "sleep"
activityList = append(activityList, "sleep")
} else {
prob = prob * 0.3
todayActivity = "icecream"
activityList = append(activityList, "icecream")
}

} else {
sleepNextProbabilityDistribution := matrixByRow(transitionMatrix, 2)

randomChoice := sampleSet(sleepNextProbabilityDistribution)
change := transitionFromIceCream[randomChoice]

if change == "II" {
prob = prob * 0.1
activityList = append(activityList, "icecream")
} else if change == "IS" {
prob = prob * 0.2
todayActivity = "sleep"
activityList = append(activityList, "sleep")
} else {
prob = prob * 0.7
todayActivity = "run"
activityList = append(activityList, "run")
}

}

i++

}
return activityList
}

// from https://www.socketloop.com/tutorials/golang-emulate-numpy-way-of-creating-matrix-example

// WARNING: this function does not perform error checking on input string
// You probably need to modify this function to perform sanity check on stuff such as:

// such as only 1 pair of [ ]
// all columns and rows have number or 0
// all rows are same size [pynum will throw "ValueError: Rows not the same size."]

func matrix(str string) *mat64.Dense {

// remove [ and ]
str = strings.Replace(str, "[", "", -1)
str = strings.Replace(str, "]", "", -1)

// calculate total number of rows
parts := strings.SplitN(str, ";", -1)
rows := len(parts)

// calculate total number of columns
colSlice := strings.Fields(parts)
columns := len(colSlice)

// replace all ; with space
str = strings.Replace(str, ";", " ", -1)

// convert str to slice
// taken from https://www.socketloop.com/tutorials/golang-convert-string-to-array-slice
elements := strings.Fields(str)

// populate data for the new matrix(Dense type)
data := make([]float64, rows*columns)
for i := range data {
floatValue, _ := strconv.ParseFloat(elements[i], 64)
data[i] = floatValue
}

M := mat64.NewDense(rows, columns, data)
return M
}
``````

Sample output:

transitionMatrix :

⎡0.2 0.6 0.2⎤

⎢0.1 0.6 0.3⎥

⎣0.2 0.7 0.1⎦

Transition matrix checked out and proceeding to next stage!

Probability of starting at state: 'sleep' and ending at state 'run' : 47.54%

References :

https://www.socketloop.com/tutorials/golang-linear-algebra-and-matrix-calculation-example

https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.choice.html

https://stackoverflow.com/questions/50507513/golang-choice-number-from-slice-array-with-given-probability

https://www.datacamp.com/community/tutorials/markov-chains-python-tutorial