mirror of
https://github.com/lukewilson2002/autotrader.git
synced 2025-06-14 07:53:51 +00:00
Add IndexedSeries
This commit is contained in:
parent
b0ab9543a4
commit
892d3d6965
@ -100,6 +100,13 @@ func (s *FloatSeries) Push(val float64) *FloatSeries {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *FloatSeries) Pop() float64 {
|
||||
if v := s.Series.Pop(); v != nil {
|
||||
return v.(float64)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Remove deletes the value at the given index and returns it. If the index is out of bounds, it returns 0.
|
||||
func (s *FloatSeries) Remove(i int) float64 {
|
||||
if v := s.Series.Remove(i); v != nil {
|
||||
|
293
series_indexed.go
Normal file
293
series_indexed.go
Normal file
@ -0,0 +1,293 @@
|
||||
package autotrader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
anymath "github.com/spatialcurrent/go-math/pkg/math"
|
||||
)
|
||||
|
||||
type ErrIndexExists struct {
|
||||
any
|
||||
}
|
||||
|
||||
func (e ErrIndexExists) Error() string {
|
||||
return fmt.Sprintf("index already exists: %v", e.any)
|
||||
}
|
||||
|
||||
// IndexedSeries is a Series with a custom index type.
|
||||
type IndexedSeries[I comparable] struct {
|
||||
*SignalManager
|
||||
series *Series
|
||||
index map[I]int
|
||||
}
|
||||
|
||||
func NewIndexedSeries[I comparable](name string, vals map[I]any) (*IndexedSeries[I], error) {
|
||||
out := &IndexedSeries[I]{
|
||||
&SignalManager{},
|
||||
NewSeries(name),
|
||||
make(map[I]int, len(vals)),
|
||||
}
|
||||
for key, val := range vals {
|
||||
// Check that the key is not already in the map.
|
||||
if _, ok := out.index[key]; ok {
|
||||
return nil, ErrIndexExists{key}
|
||||
}
|
||||
out.index[key] = out.series.Len()
|
||||
out.series.Push(val)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Add adds the values of the other series to the values of this series. The other series must have the same index type. The values are added by comparing their indexes. For example, adding two IndexedSeries that share no indexes will result in no change of values.
|
||||
func (s *IndexedSeries[I]) Add(other *IndexedSeries[I]) *IndexedSeries[I] {
|
||||
// For each index in self, add the corresponding value of the other series.
|
||||
for index, row := range s.index {
|
||||
if otherRow, ok := other.index[index]; ok {
|
||||
val, err := anymath.Add(s.series.Value(row), other.series.Value(otherRow))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error adding values at index %v: %w", index, err))
|
||||
}
|
||||
s.series.SetValue(row, val)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *IndexedSeries[I]) Copy() *IndexedSeries[I] {
|
||||
return s.CopyRange(0, -1)
|
||||
}
|
||||
|
||||
func (s *IndexedSeries[I]) CopyRange(start, count int) *IndexedSeries[I] {
|
||||
// Copy the index values over.
|
||||
index := make(map[I]int, len(s.index))
|
||||
for key, val := range s.index {
|
||||
index[key] = val
|
||||
}
|
||||
return &IndexedSeries[I]{
|
||||
&SignalManager{},
|
||||
s.series.CopyRange(start, count),
|
||||
index,
|
||||
}
|
||||
}
|
||||
|
||||
// Div divides this series values with the other series values. The other series must have the same index type. The values are divided by comparing their indexes. For example, dividing two IndexedSeries that share no indexes will result in no change of values.
|
||||
func (s *IndexedSeries[I]) Div(other *IndexedSeries[I]) *IndexedSeries[I] {
|
||||
for index, row := range s.index {
|
||||
if otherRow, ok := other.index[index]; ok {
|
||||
val, err := anymath.Divide(s.series.Value(row), other.series.Value(otherRow))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error dividing values at index %v: %w", index, err))
|
||||
}
|
||||
s.series.SetValue(row, val)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *IndexedSeries[I]) Filter(f func(i int, val any) bool) *IndexedSeries[I] {
|
||||
_ = s.series.Filter(f)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *IndexedSeries[I]) ForEach(f func(i int, val any)) *IndexedSeries[I] {
|
||||
_ = s.series.ForEach(f)
|
||||
return s
|
||||
}
|
||||
|
||||
// Index returns the index of the given row or nil if the row is out of bounds. row is an EasyIndex.
|
||||
func (s *IndexedSeries[I]) Index(row int) *I {
|
||||
row = EasyIndex(row, s.series.Len())
|
||||
for key, val := range s.index {
|
||||
if val == row {
|
||||
return &key
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Row returns the row of the given index or -1 if the index does not exist.
|
||||
func (s *IndexedSeries[I]) Row(index I) int {
|
||||
if i, ok := s.index[index]; ok {
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Len returns the number of rows in the series.
|
||||
func (s *IndexedSeries[I]) Len() int {
|
||||
return s.series.Len()
|
||||
}
|
||||
|
||||
func (s *IndexedSeries[I]) Map(f func(index I, row int, val any) any) *IndexedSeries[I] {
|
||||
_ = s.series.Map(func(i int, val any) any {
|
||||
index := s.Index(i)
|
||||
return f(*index, i, val)
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *IndexedSeries[I]) MapReverse(f func(index I, row int, val any) any) *IndexedSeries[I] {
|
||||
_ = s.series.MapReverse(func(i int, val any) any {
|
||||
index := s.Index(i)
|
||||
return f(*index, i, val)
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
// Mul multiplies this series values with the other series values. The other series must have the same index type. The values are multiplied by comparing their indexes. For example, multiplying two IndexedSeries that share no indexes will result in no change of values.
|
||||
func (s *IndexedSeries[I]) Mul(other *IndexedSeries[I]) *IndexedSeries[I] {
|
||||
for index, row := range s.index {
|
||||
if otherRow, ok := other.index[index]; ok {
|
||||
val, err := anymath.Multiply(s.series.Value(row), other.series.Value(otherRow))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error multiplying values at index %v: %w", index, err))
|
||||
}
|
||||
s.series.SetValue(row, val)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Push adds a value to the end of the series and returns the series or an error if the index already exists. The error is of type ErrIndexExists.
|
||||
func (s *IndexedSeries[I]) Push(index I, val any) (*IndexedSeries[I], error) {
|
||||
// Check that the key is not already in the map.
|
||||
if _, ok := s.index[index]; ok {
|
||||
return nil, ErrIndexExists{index}
|
||||
}
|
||||
s.series.Push(val)
|
||||
s.index[index] = s.series.Len() - 1
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *IndexedSeries[I]) Pop() any {
|
||||
return s.Remove(s.series.Len() - 1)
|
||||
}
|
||||
|
||||
// Remove deletes the row at the given index and returns it.
|
||||
func (s *IndexedSeries[I]) Remove(row int) any {
|
||||
// Remove the index from the map.
|
||||
for index, j := range s.index {
|
||||
if j == row {
|
||||
delete(s.index, index)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Shift each index after the removed index down by one.
|
||||
for key, j := range s.index {
|
||||
if j > row {
|
||||
s.index[key] = j - 1
|
||||
}
|
||||
}
|
||||
// Remove the value from the series.
|
||||
return s.series.Remove(row)
|
||||
}
|
||||
|
||||
// RemoveIndex deletes the row at the given index and returns it. If index does not exist, nil is returned.
|
||||
func (s *IndexedSeries[I]) RemoveIndex(index I) any {
|
||||
// Check that the key is in the map.
|
||||
if i, ok := s.index[index]; ok {
|
||||
return s.Remove(i)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveRange deletes the rows in the given range and returns the series.
|
||||
//
|
||||
// The operation is O(n) where n is the number of rows in the series.
|
||||
func (s *IndexedSeries[I]) RemoveRange(start, count int) *IndexedSeries[I] {
|
||||
start, end := s.series.Range(start, count)
|
||||
if start == end {
|
||||
return s
|
||||
}
|
||||
count = end - start
|
||||
// Remove the indexes from the map.
|
||||
for index, i := range s.index {
|
||||
if i >= start && i < end {
|
||||
delete(s.index, index)
|
||||
}
|
||||
}
|
||||
// Shift each index after the removed index down by count.
|
||||
for key, i := range s.index {
|
||||
if i >= end {
|
||||
s.index[key] = i - count
|
||||
}
|
||||
}
|
||||
// Remove the values from the series.
|
||||
_ = s.series.RemoveRange(start, count)
|
||||
return s
|
||||
}
|
||||
|
||||
// Reverse reverses the rows of the series.
|
||||
func (s *IndexedSeries[I]) Reverse() *IndexedSeries[I] {
|
||||
// Reverse the indexes.
|
||||
s.ReverseIndexes()
|
||||
// Reverse the values.
|
||||
_ = s.series.Reverse()
|
||||
return s
|
||||
}
|
||||
|
||||
// ReverseIndexes reverses the indexes of the series but not the rows.
|
||||
func (s *IndexedSeries[I]) ReverseIndexes() *IndexedSeries[I] {
|
||||
seriesLen := s.series.Len()
|
||||
for key, i := range s.index {
|
||||
s.index[key] = seriesLen - i - 1
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *IndexedSeries[I]) SetName(name string) *IndexedSeries[I] {
|
||||
_ = s.series.SetName(name)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *IndexedSeries[I]) SetValue(row int, val any) *IndexedSeries[I] {
|
||||
_ = s.series.SetValue(row, val)
|
||||
return s
|
||||
}
|
||||
|
||||
// SetValueIndex is like SetValue but uses the index instead of the row.
|
||||
func (s *IndexedSeries[I]) SetValueIndex(index I, val any) *IndexedSeries[I] {
|
||||
row := s.Row(index)
|
||||
if row < 0 {
|
||||
return s
|
||||
}
|
||||
return s.SetValue(row, val)
|
||||
}
|
||||
|
||||
// Sub subtracts the other series values from this series values. The other series must have the same index type. The values are subtracted by comparing their indexes. For example, subtracting two IndexedSeries that share no indexes will result in no change of values.
|
||||
func (s *IndexedSeries[I]) Sub(other *IndexedSeries[I]) *IndexedSeries[I] {
|
||||
for index, row := range s.index {
|
||||
if otherRow, ok := other.index[index]; ok {
|
||||
val, err := anymath.Divide(s.series.Value(row), other.series.Value(otherRow))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error subtracting values at index %v: %w", index, err))
|
||||
}
|
||||
s.series.SetValue(row, val)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Value returns the value at the given row.
|
||||
func (s *IndexedSeries[I]) Value(i int) any {
|
||||
return s.series.Value(i)
|
||||
}
|
||||
|
||||
// ValueIndex returns the value at the given index or nil if the index does not exist.
|
||||
func (s *IndexedSeries[I]) ValueIndex(index I) any {
|
||||
row := s.Row(index)
|
||||
if row < 0 {
|
||||
return nil
|
||||
}
|
||||
return s.Value(row)
|
||||
}
|
||||
|
||||
// Values returns a copy of the values in the series.
|
||||
func (s *IndexedSeries[I]) Values() []any {
|
||||
return s.series.ValueRange(0, -1)
|
||||
}
|
||||
|
||||
// ValueRange returns a copy of the values in the given range. start is an EasyIndex. count is the number of values to return. If count is -1, all values after start are returned. See Series.ValueRange() for more information.
|
||||
func (s *IndexedSeries[I]) ValueRange(start, count int) []any {
|
||||
return s.series.ValueRange(start, count)
|
||||
}
|
@ -3,10 +3,11 @@ package autotrader
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDataSeries(t *testing.T) {
|
||||
series := NewSeries("test", 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)
|
||||
func TestSeries(t *testing.T) {
|
||||
series := NewFloatSeries("test", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
if series.Len() != 10 {
|
||||
t.Fatalf("Expected 10 rows, got %d", series.Len())
|
||||
}
|
||||
@ -15,7 +16,7 @@ func TestDataSeries(t *testing.T) {
|
||||
t.Fatalf("Expected 10 rows, got %d", series.Len())
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
if val := series.Float(i); val != float64(10-i) {
|
||||
if val := series.Value(i); val != float64(10-i) {
|
||||
t.Errorf("(%d)\tExpected %f, got %v", i, float64(10-i), val)
|
||||
}
|
||||
}
|
||||
@ -25,12 +26,12 @@ func TestDataSeries(t *testing.T) {
|
||||
t.Fatalf("Expected 5 rows, got %d", last5.Len())
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
if val := last5.Float(i); val != float64(5-i) {
|
||||
if val := last5.Value(i); val != float64(5-i) {
|
||||
t.Errorf("(%d)\tExpected %f, got %v", i, float64(5-i), val)
|
||||
}
|
||||
}
|
||||
last5.SetValue(-1, 0.0)
|
||||
if series.Float(-1) == 0.0 {
|
||||
if series.Value(-1) == 0.0 {
|
||||
t.Errorf("Expected data to be copied, not referenced")
|
||||
}
|
||||
|
||||
@ -67,7 +68,7 @@ func TestDataSeries(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDataSeriesFunctional(t *testing.T) {
|
||||
func TestSeriesFunctional(t *testing.T) {
|
||||
series := NewSeries("test", 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)
|
||||
doubled := series.Copy().Map(func(_ int, val any) any {
|
||||
return val.(float64) * 2
|
||||
@ -120,7 +121,7 @@ func TestDataSeriesFunctional(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRollingAppliedSeries(t *testing.T) {
|
||||
func TestRollingSeries(t *testing.T) {
|
||||
// Test rolling average.
|
||||
series := NewSeries("test", 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)
|
||||
|
||||
@ -150,7 +151,70 @@ func TestRollingAppliedSeries(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDataSeriesEURUSD(t *testing.T) {
|
||||
func TestIndexedSeries(t *testing.T) {
|
||||
intIndexed, err := NewIndexedSeries("test", map[int]any{
|
||||
0: 1.0,
|
||||
2: 2.0,
|
||||
4: 3.0,
|
||||
6: 4.0,
|
||||
8: 5.0,
|
||||
10: 6.0,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
if intIndexed.Len() != 6 {
|
||||
t.Fatalf("Expected 6 rows, got %d", intIndexed.Len())
|
||||
}
|
||||
if intIndexed.ValueIndex(4).(float64) != 3.0 {
|
||||
t.Errorf("Expected value at index 4 to be 3.0, got %v", intIndexed.ValueIndex(4))
|
||||
}
|
||||
|
||||
floatIndexed, err := NewIndexedSeries[float64]("test", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
floatIndexed.Push(0.0, 1.0)
|
||||
floatIndexed.Push(2.0, 2.0)
|
||||
floatIndexed.Push(4.0, 3.0)
|
||||
if floatIndexed.Len() != 3 {
|
||||
t.Fatalf("Expected 3 rows, got %d", floatIndexed.Len())
|
||||
}
|
||||
if floatIndexed.ValueIndex(4.0).(float64) != 3.0 {
|
||||
t.Errorf("Expected value at index 4.0 to be 3.0, got %v", floatIndexed.ValueIndex(4.0))
|
||||
}
|
||||
|
||||
timeIndexed, err := NewIndexedSeries("test", map[time.Time]any{
|
||||
time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC): 1.0,
|
||||
time.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC): 2.0,
|
||||
time.Date(2018, 1, 3, 0, 0, 0, 0, time.UTC): 3.0,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
if timeIndexed.Len() != 3 {
|
||||
t.Fatalf("Expected 3 rows, got %d", timeIndexed.Len())
|
||||
}
|
||||
if timeIndexed.ValueIndex(time.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC)).(float64) != 2.0 {
|
||||
t.Errorf("Expected value at index 2018-01-02 to be 2.0, got %v", timeIndexed.ValueIndex(time.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC)))
|
||||
}
|
||||
|
||||
doubledTimeIndexed := timeIndexed.Copy().Add(timeIndexed)
|
||||
if doubledTimeIndexed.Len() != 3 {
|
||||
t.Fatalf("Expected 3 rows, got %d", doubledTimeIndexed.Len())
|
||||
}
|
||||
if doubledTimeIndexed.ValueIndex(time.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC)).(float64) != 4.0 {
|
||||
t.Errorf("Expected value at index 2018-01-02 to be 4.0, got %v", doubledTimeIndexed.ValueIndex(time.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC)))
|
||||
}
|
||||
|
||||
// Test that the Copy function works.
|
||||
doubledTimeIndexed.SetValueIndex(time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), 100.0)
|
||||
if timeIndexed.ValueIndex(time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC)).(float64) != 1.0 {
|
||||
t.Errorf("Expected value at index 2018-01-01 to be 1.0, got %v", timeIndexed.ValueIndex(time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeriesEURUSD(t *testing.T) {
|
||||
data, err := EURUSD()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
|
Loading…
x
Reference in New Issue
Block a user