Skip to content

Commit 705d549

Browse files
heikohwde
authored andcommitted
Fix Aroon Indicator
add highest and lowest add window, use it from highest and lowest add Max/MinSince with helper Window and SlicesReverse change aroon indicator as suggested by #124 add a another test for Aroon applied some ai suggesttions update aroon strategy test data to match new actions add another Aroon Test inspired by an R implementation Update testdata to fix depending test of no_loss_strategy fix depending testdata
1 parent 8252ac7 commit 705d549

18 files changed

+747
-303
lines changed

helper/Lowest.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2021-2024 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package helper
6+
7+
import "slices"
8+
9+
// Lowest returns a channel that emits the lowest value
10+
// within a sliding window of size w from the input channel c.
11+
func Lowest[T Number](c <-chan T, w int) <-chan T {
12+
return Window(c, func(s []T, i int) T {
13+
return slices.Min(s)
14+
}, w)
15+
}

helper/highest.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2021-2024 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package helper
6+
7+
import "slices"
8+
9+
// Highest returns a channel that emits the highest value
10+
// within a sliding window of size w from the input channel c.
11+
func Highest[T Number](c <-chan T, w int) <-chan T {
12+
return Window(c, func(s []T, i int) T {
13+
return slices.Max(s)
14+
}, w)
15+
}

helper/highest_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) 2021-2024 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package helper
6+
7+
import (
8+
"testing"
9+
)
10+
11+
func TestHighest(t *testing.T) {
12+
input := SliceToChan([]int{48, 52, 50, 49, 10})
13+
expected := SliceToChan([]int{48, 52, 52, 52, 50})
14+
window := 3
15+
actual := Highest(input, window)
16+
17+
err := CheckEquals(actual, expected)
18+
if err != nil {
19+
t.Fatal(err)
20+
}
21+
}

helper/lowest_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) 2021-2024 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package helper
6+
7+
import (
8+
"testing"
9+
)
10+
11+
func TestLowest(t *testing.T) {
12+
input := SliceToChan([]int{48, 52, 50, 49, 10})
13+
expected := SliceToChan([]int{48, 48, 48, 49, 10})
14+
window := 3
15+
actual := Lowest(input, window)
16+
17+
err := CheckEquals(actual, expected)
18+
if err != nil {
19+
t.Fatal(err)
20+
}
21+
}

helper/max_since.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2021-2024 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package helper
6+
7+
import (
8+
"slices"
9+
)
10+
11+
// MaxSince returns a channel of T indicating since when
12+
// (number of previous values) the respective value was the maximum
13+
// within the window of size w.
14+
func MaxSince[T Number](c <-chan T, w int) <-chan T {
15+
return Window(c, func(w []T, i int) T {
16+
since := 0
17+
found := false
18+
m := slices.Max(w)
19+
SlicesReverse(w, i, func(n T) bool {
20+
if found && n < m {
21+
return false
22+
}
23+
since++
24+
if n == m {
25+
found = true
26+
}
27+
return true
28+
})
29+
return T(since - 1)
30+
}, w)
31+
}

helper/max_since_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package helper
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestMaxSince(t *testing.T) {
8+
input := SliceToChan([]int{48, 49, 47, 52, 52, 52, 53, 50, 55})
9+
expected := SliceToChan([]int{0, 0, 1, 0, 1, 2, 0, 1, 0})
10+
actual := MaxSince(input, 3)
11+
12+
err := CheckEquals(actual, expected)
13+
if err != nil {
14+
t.Fatal(err)
15+
}
16+
}
17+
18+
func TestMaxSinceAtEnd(t *testing.T) {
19+
input := SliceToChan([]int{48, 49, 47, 52, 52, 52, 52})
20+
expected := SliceToChan([]int{0, 0, 1, 0, 1, 2, 2})
21+
actual := MaxSince(input, 3)
22+
23+
err := CheckEquals(actual, expected)
24+
if err != nil {
25+
t.Fatal(err)
26+
}
27+
}

helper/min_since.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) 2021-2024 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package helper
6+
7+
import (
8+
"slices"
9+
)
10+
11+
// MinSince returns a channel of T indicating since when
12+
// (number of previous values) the respective value was the minimum.
13+
func MinSince[T Number](c <-chan T, w int) <-chan T {
14+
return Window(c, func(w []T, i int) T {
15+
since := 0
16+
found := false
17+
m := slices.Min(w)
18+
SlicesReverse(w, i, func(n T) bool {
19+
if found && n > m {
20+
return false
21+
}
22+
since++
23+
if n == m {
24+
found = true
25+
}
26+
return true
27+
})
28+
return T(since - 1)
29+
}, w)
30+
}

helper/min_since_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package helper
2+
3+
import "testing"
4+
5+
func TestMinSince(t *testing.T) {
6+
input := SliceToChan([]int{48, 50, 50, 50, 49, 49, 51})
7+
expected := SliceToChan([]int{0, 1, 2, 2, 0, 1, 2})
8+
actual := MinSince(input, 3)
9+
10+
err := CheckEquals(actual, expected)
11+
if err != nil {
12+
t.Fatal(err)
13+
}
14+
}
15+
16+
func TestMinSinceAtEnd(t *testing.T) {
17+
input := SliceToChan([]int{48, 50, 50, 50, 49, 49, 49})
18+
expected := SliceToChan([]int{0, 1, 2, 2, 0, 1, 2})
19+
actual := MinSince(input, 3)
20+
21+
err := CheckEquals(actual, expected)
22+
if err != nil {
23+
t.Fatal(err)
24+
}
25+
}
26+
27+
func TestMinSinceFromStart(t *testing.T) {
28+
input := SliceToChan([]int{1, 1, 3})
29+
expected := SliceToChan([]int{0, 1, 2})
30+
actual := MinSince(input, 3)
31+
32+
err := CheckEquals(actual, expected)
33+
if err != nil {
34+
t.Fatal(err)
35+
}
36+
}

helper/slices_reverse.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package helper
2+
3+
// SlicesReverse loops through a slice in reverse order starting from the given index.
4+
// The given function is called for each element in the slice. If the function returns false,
5+
// the loop is terminated.
6+
func SlicesReverse[T any](r []T, i int, f func(T) bool) {
7+
l := len(r)
8+
if l == 0 || i < 0 || i >= l {
9+
return
10+
}
11+
for m := i - 1; m >= 0; m-- {
12+
if !f(r[m]) {
13+
return
14+
}
15+
}
16+
for m := l - 1; m >= i; m-- {
17+
if !f(r[m]) {
18+
return
19+
}
20+
}
21+
}

helper/slices_reverse_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package helper
2+
3+
import (
4+
"slices"
5+
"testing"
6+
)
7+
8+
func TestSlicesReverseSimple(t *testing.T) {
9+
input := []int{1, 2, 3, 4}
10+
expected := []int{4, 3, 2, 1}
11+
actual := make([]int, 0, len(input))
12+
SlicesReverse(input, 0, func(i int) bool {
13+
actual = append(actual, i)
14+
return true
15+
})
16+
if !slices.Equal(actual, expected) {
17+
t.Fatal("not equal")
18+
}
19+
}
20+
21+
func TestSlicesReverseMiddle(t *testing.T) {
22+
input := []int{1, 2, 3, 4}
23+
expected := []int{2, 1, 4, 3}
24+
actual := make([]int, 0, len(input))
25+
SlicesReverse(input, 2, func(i int) bool {
26+
actual = append(actual, i)
27+
return true
28+
})
29+
if !slices.Equal(actual, expected) {
30+
t.Fatal("not equal")
31+
}
32+
}

0 commit comments

Comments
 (0)