Hopefully finish Ichimoku()

This commit is contained in:
Luke I. Wilson 2023-05-22 09:49:55 -05:00
parent 30b6482fbf
commit c0de28664e
4 changed files with 102 additions and 15 deletions

View File

@ -1,6 +1,9 @@
package autotrader
import "math"
import (
"math"
"time"
)
// RSI calculates the Relative Strength Index for a given Series. Typically, the input series is the Close column of a DataFrame. Returns a Series of RSI values of the same length as the input.
//
@ -22,6 +25,7 @@ func RSI(series *FloatSeries, periods int) *FloatSeries {
avgLoss := &FloatSeries{delta.Copy().
Map(func(i int, val float64) float64 { return math.Abs(math.Min(val, 0)) }).
Rolling(periods).Average()}
// Calculate the RSI.
return avgGain.Map(func(i int, val float64) float64 {
loss := avgLoss.Float(i)
@ -29,7 +33,7 @@ func RSI(series *FloatSeries, periods int) *FloatSeries {
return float64(100)
}
return float64(100 - 100/(1+val/loss))
})
}).SetName("RSI")
}
// Ichimoku calculates the Ichimoku Cloud for a given Series. Returns a DataFrame of the same length as the input with float64 values. The series input must contain only float64 values, which are traditionally the close prices.
@ -45,29 +49,40 @@ func RSI(series *FloatSeries, periods int) *FloatSeries {
// - LeadingA
// - LeadingB
// - Lagging
func Ichimoku(series *FloatSeries, convPeriod, basePeriod, leadingPeriods int) *Frame {
func Ichimoku(series *IndexedSeries[UnixTime], convPeriod, basePeriod, leadingPeriods int) *IndexedFrame[UnixTime] {
// TODO: make this run concurrently.
// Calculate the Conversion Line.
conv := series.Copy().Rolling(convPeriod).Max().Add(series.Copy().Rolling(convPeriod).Min()).
Map(func(i int, val any) any {
Map(func(_ UnixTime, _ int, val any) any {
return val.(float64) / float64(2)
})
// Calculate the Base Line.
base := series.Copy().Rolling(basePeriod).Max().Add(series.Copy().Rolling(basePeriod).Min()).
Map(func(i int, val any) any {
Map(func(_ UnixTime, _ int, val any) any {
return val.(float64) / float64(2)
})
// Calculate the Leading Span A.
leadingA := conv.Copy().Rolling(leadingPeriods).Max().Add(base.Copy().Rolling(leadingPeriods).Max()).
Map(func(i int, val any) any {
Map(func(_ UnixTime, _ int, val any) any {
return val.(float64) / float64(2)
})
// Calculate the Leading Span B.
leadingB := series.Copy().Rolling(leadingPeriods).Max().Add(series.Copy().Rolling(leadingPeriods).Min()).
Map(func(i int, val any) any {
Map(func(_ UnixTime, _ int, val any) any {
return val.(float64) / float64(2)
})
// Calculate the Lagging Span.
// lagging := series.Shift(-leadingPeriods)
lagging := series.Copy().ShiftIndex(-leadingPeriods, UnixTimeStep(time.Hour))
// Return a DataFrame of the results.
return NewFrame(conv, base, leadingA, leadingB)
return NewIndexedFrame(
conv.SetName("Conversion"),
base.SetName("Base"),
leadingA.SetName("LeadingA"),
leadingB.SetName("LeadingB"),
lagging.SetName("Lagging"),
)
}

View File

@ -435,14 +435,14 @@ func NewRollingSeries(series *Series, period int) *RollingSeries {
return &RollingSeries{series, period}
}
// Period returns a slice of 'any' values with a length up to the period of the RollingSeries. The last item in the slice is the item at i. If i is out of bounds, nil is returned.
func (s *RollingSeries) Period(i int) []any {
// Period returns a slice of 'any' values with a length up to the period of the RollingSeries. The last item in the slice is the item at row. If row is out of bounds, nil is returned.
func (s *RollingSeries) Period(row int) []any {
items := make([]any, 0, s.period)
i = EasyIndex(i, s.series.Len())
if i < 0 || i >= s.series.Len() {
row = EasyIndex(row, s.series.Len())
if row < 0 || row >= s.series.Len() {
return items
}
for j := i; j > i-s.period && j >= 0; j-- {
for j := row; j > row-s.period && j >= 0; j-- {
items = slices.Insert(items, 0, s.series.Value(j))
}
return items

View File

@ -89,6 +89,18 @@ func (s *IndexedSeries[I]) Filter(f func(i int, val any) bool) *IndexedSeries[I]
return s
}
func (s *IndexedSeries[I]) Float(i int) float64 {
return s.series.Float(i)
}
func (s *IndexedSeries[I]) FloatIndex(index I) float64 {
row := s.Row(index)
if row < 0 {
return 0.0
}
return s.series.Float(row)
}
func (s *IndexedSeries[I]) ForEach(f func(i int, val any)) *IndexedSeries[I] {
_ = s.series.ForEach(f)
return s
@ -240,6 +252,10 @@ func (s *IndexedSeries[I]) ReverseIndexes() *IndexedSeries[I] {
return s
}
func (s *IndexedSeries[I]) Rolling(period int) *IndexedRollingSeries[I] {
return NewIndexedRollingSeries(s, period)
}
func (s *IndexedSeries[I]) SetName(name string) *IndexedSeries[I] {
_ = s.series.SetName(name)
return s
@ -314,3 +330,51 @@ func (s *IndexedSeries[I]) Values() []any {
func (s *IndexedSeries[I]) ValueRange(start, count int) []any {
return s.series.ValueRange(start, count)
}
type IndexedRollingSeries[I comparable] struct {
rolling *RollingSeries
series *IndexedSeries[I]
}
func NewIndexedRollingSeries[I comparable](series *IndexedSeries[I], period int) *IndexedRollingSeries[I] {
return &IndexedRollingSeries[I]{NewRollingSeries(series.series, period), series}
}
func (s *IndexedRollingSeries[I]) Period(row int) []any {
return s.rolling.Period(row)
}
func (s *IndexedRollingSeries[I]) Max() *IndexedSeries[I] {
_ = s.rolling.Max() // Mutate the underlying series.
return s.series
}
func (s *IndexedRollingSeries[I]) Min() *IndexedSeries[I] {
_ = s.rolling.Min() // Mutate the underlying series.
return s.series
}
func (s *IndexedRollingSeries[I]) Average() *IndexedSeries[I] {
_ = s.rolling.Average() // Mutate the underlying series.
return s.series
}
func (s *IndexedRollingSeries[I]) Mean() *IndexedSeries[I] {
_ = s.rolling.Mean() // Mutate the underlying series.
return s.series
}
func (s *IndexedRollingSeries[I]) Median() *IndexedSeries[I] {
_ = s.rolling.Median() // Mutate the underlying series.
return s.series
}
func (s *IndexedRollingSeries[I]) EMA() *IndexedSeries[I] {
_ = s.rolling.EMA() // Mutate the underlying series.
return s.series
}
func (s *IndexedRollingSeries[I]) StdDev() *IndexedSeries[I] {
_ = s.rolling.StdDev() // Mutate the underlying series.
return s.series
}

View File

@ -14,10 +14,18 @@ const float64Tolerance = float64(1e-6)
var ErrNotASignedNumber = errors.New("not a signed number")
// Crossover returns true if the latest a value crosses above the latest b value, but only if it just happened. For example, if a series is [1, 2, 3, 4, 5] and b series is [1, 2, 3, 4, 3], then Crossover(a, b) returns false because the latest a value is 5 and the latest b value is 3. However, if a series is [1, 2, 3, 4, 5] and b series is [1, 2, 3, 4, 6], then Crossover(a, b) returns true because the latest a value is 5 and the latest b value is 6
func Crossover(a, b Series) bool {
func Crossover(a, b *Series) bool {
return a.Float(-1) > b.Float(-1) && a.Float(-2) <= b.Float(-2)
}
func CrossoverIndex[I comparable](index I, a, b *IndexedSeries[I]) bool {
aRow, bRow := a.Row(index), b.Row(index)
if aRow < 1 || bRow < 1 {
return false
}
return a.Float(aRow) > b.Float(bRow) && a.Float(aRow-1) <= b.Float(bRow-1)
}
// EasyIndex returns an index to the `n` -length object that allows for negative indexing. For example, EasyIndex(-1, 5) returns 4. This is similar to Python's negative indexing. The return value may be less than zero if (-i) > n.
func EasyIndex(i, n int) int {
if i < 0 {