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 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. // 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(). avgLoss := &FloatSeries{delta.Copy().
Map(func(i int, val float64) float64 { return math.Abs(math.Min(val, 0)) }). Map(func(i int, val float64) float64 { return math.Abs(math.Min(val, 0)) }).
Rolling(periods).Average()} Rolling(periods).Average()}
// Calculate the RSI. // Calculate the RSI.
return avgGain.Map(func(i int, val float64) float64 { return avgGain.Map(func(i int, val float64) float64 {
loss := avgLoss.Float(i) loss := avgLoss.Float(i)
@ -29,7 +33,7 @@ func RSI(series *FloatSeries, periods int) *FloatSeries {
return float64(100) return float64(100)
} }
return float64(100 - 100/(1+val/loss)) 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. // 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 // - LeadingA
// - LeadingB // - LeadingB
// - Lagging // - 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. // Calculate the Conversion Line.
conv := series.Copy().Rolling(convPeriod).Max().Add(series.Copy().Rolling(convPeriod).Min()). 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) return val.(float64) / float64(2)
}) })
// Calculate the Base Line. // Calculate the Base Line.
base := series.Copy().Rolling(basePeriod).Max().Add(series.Copy().Rolling(basePeriod).Min()). 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) return val.(float64) / float64(2)
}) })
// Calculate the Leading Span A. // Calculate the Leading Span A.
leadingA := conv.Copy().Rolling(leadingPeriods).Max().Add(base.Copy().Rolling(leadingPeriods).Max()). 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) return val.(float64) / float64(2)
}) })
// Calculate the Leading Span B. // Calculate the Leading Span B.
leadingB := series.Copy().Rolling(leadingPeriods).Max().Add(series.Copy().Rolling(leadingPeriods).Min()). 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) return val.(float64) / float64(2)
}) })
// Calculate the Lagging Span. // Calculate the Lagging Span.
// lagging := series.Shift(-leadingPeriods) lagging := series.Copy().ShiftIndex(-leadingPeriods, UnixTimeStep(time.Hour))
// Return a DataFrame of the results. // 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} 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. // 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(i int) []any { func (s *RollingSeries) Period(row int) []any {
items := make([]any, 0, s.period) items := make([]any, 0, s.period)
i = EasyIndex(i, s.series.Len()) row = EasyIndex(row, s.series.Len())
if i < 0 || i >= s.series.Len() { if row < 0 || row >= s.series.Len() {
return items 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)) items = slices.Insert(items, 0, s.series.Value(j))
} }
return items return items

View File

@ -89,6 +89,18 @@ func (s *IndexedSeries[I]) Filter(f func(i int, val any) bool) *IndexedSeries[I]
return s 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] { func (s *IndexedSeries[I]) ForEach(f func(i int, val any)) *IndexedSeries[I] {
_ = s.series.ForEach(f) _ = s.series.ForEach(f)
return s return s
@ -240,6 +252,10 @@ func (s *IndexedSeries[I]) ReverseIndexes() *IndexedSeries[I] {
return s return s
} }
func (s *IndexedSeries[I]) Rolling(period int) *IndexedRollingSeries[I] {
return NewIndexedRollingSeries(s, period)
}
func (s *IndexedSeries[I]) SetName(name string) *IndexedSeries[I] { func (s *IndexedSeries[I]) SetName(name string) *IndexedSeries[I] {
_ = s.series.SetName(name) _ = s.series.SetName(name)
return s return s
@ -314,3 +330,51 @@ func (s *IndexedSeries[I]) Values() []any {
func (s *IndexedSeries[I]) ValueRange(start, count int) []any { func (s *IndexedSeries[I]) ValueRange(start, count int) []any {
return s.series.ValueRange(start, count) 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") 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 // 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) 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. // 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 { func EasyIndex(i, n int) int {
if i < 0 { if i < 0 {