diff --git a/asset/README.md b/asset/README.md
index 6607fe9..a890f19 100644
--- a/asset/README.md
+++ b/asset/README.md
@@ -27,12 +27,13 @@ The information provided on this project is strictly for informational purposes
- [type FileSystemRepository](<#FileSystemRepository>)
- [func NewFileSystemRepository\(base string\) \*FileSystemRepository](<#NewFileSystemRepository>)
- [func \(r \*FileSystemRepository\) Get\(name string\) \(\<\-chan \*Snapshot, error\)](<#FileSystemRepository.Get>)
+ - [func \(r \*FileSystemRepository\) LastDate\(name string\) \(time.Time, error\)](<#FileSystemRepository.LastDate>)
- [type Repository](<#Repository>)
- [type Snapshot](<#Snapshot>)
-## type [FileSystemRepository]()
+## type [FileSystemRepository]()
FileSystemRepository stores and retrieves asset snapshots using the local file system.
@@ -44,7 +45,7 @@ type FileSystemRepository struct {
```
-### func [NewFileSystemRepository]()
+### func [NewFileSystemRepository]()
```go
func NewFileSystemRepository(base string) *FileSystemRepository
@@ -53,7 +54,7 @@ func NewFileSystemRepository(base string) *FileSystemRepository
NewFileSystemRepository initializes a file system repository with the given base directory.
-### func \(\*FileSystemRepository\) [Get]()
+### func \(\*FileSystemRepository\) [Get]()
```go
func (r *FileSystemRepository) Get(name string) (<-chan *Snapshot, error)
@@ -61,8 +62,17 @@ func (r *FileSystemRepository) Get(name string) (<-chan *Snapshot, error)
Get attempts to return a channel of snapshots fo the asset with the given name.
+
+### func \(\*FileSystemRepository\) [LastDate]()
+
+```go
+func (r *FileSystemRepository) LastDate(name string) (time.Time, error)
+```
+
+LastDate returns the date of the last snapshot for the asset with the given name.
+
-## type [Repository]()
+## type [Repository]()
Repository serves as a centralized storage and retrieval location for asset snapshots.
@@ -71,6 +81,10 @@ type Repository interface {
// Get attempts to return a channel of snapshots for
// the asset with the given name.
Get(name string) (<-chan *Snapshot, error)
+
+ // LastDate returns the date of the last snapshot for
+ // the asset with the given name.
+ LastDate(name string) (time.Time, error)
}
```
diff --git a/asset/file_system_repository.go b/asset/file_system_repository.go
index af2689f..1f9c67e 100644
--- a/asset/file_system_repository.go
+++ b/asset/file_system_repository.go
@@ -5,8 +5,10 @@
package asset
import (
+ "errors"
"fmt"
"path/filepath"
+ "time"
"github.com/cinar/indicator/helper"
)
@@ -33,3 +35,21 @@ func (r *FileSystemRepository) Get(name string) (<-chan *Snapshot, error) {
file := filepath.Join(r.base, fmt.Sprintf("%s.csv", name))
return helper.ReadFromCsvFile[Snapshot](file, true)
}
+
+// LastDate returns the date of the last snapshot for the asset with the given name.
+func (r *FileSystemRepository) LastDate(name string) (time.Time, error) {
+ var last time.Time
+
+ snapshots, err := r.Get(name)
+ if err != nil {
+ return last, err
+ }
+
+ snapshot := helper.ChanToSlice(helper.Last(snapshots, 1))
+
+ if len(snapshot) != 1 {
+ return last, errors.New("empty asset")
+ }
+
+ return snapshot[0].Date, nil
+}
diff --git a/asset/file_system_repository_test.go b/asset/file_system_repository_test.go
index 2434924..d8793e6 100644
--- a/asset/file_system_repository_test.go
+++ b/asset/file_system_repository_test.go
@@ -6,6 +6,7 @@ package asset_test
import (
"testing"
+ "time"
"github.com/cinar/indicator/asset"
"github.com/cinar/indicator/helper"
@@ -30,3 +31,36 @@ func TestFileSystemRepositoryGetNonExisting(t *testing.T) {
t.Fatal("expected error")
}
}
+
+func TestFileSystemRepositoryLastDate(t *testing.T) {
+ expeted := time.Date(2022, 12, 30, 0, 0, 0, 0, time.UTC)
+
+ repository := asset.NewFileSystemRepository("testdata")
+
+ actual, err := repository.LastDate("brk-b")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if actual != expeted {
+ t.Fatalf("actual %v expected %v", actual, expeted)
+ }
+}
+
+func TestFileSystemRepositoryLastDateNonExisting(t *testing.T) {
+ repository := asset.NewFileSystemRepository("testdata")
+
+ _, err := repository.LastDate("brk")
+ if err == nil {
+ t.Fatal("expected error")
+ }
+}
+
+func TestFileSystemRepositoryLastDateEmpty(t *testing.T) {
+ repository := asset.NewFileSystemRepository("testdata")
+
+ _, err := repository.LastDate("empty")
+ if err == nil {
+ t.Fatal("expected error")
+ }
+}
diff --git a/asset/repository.go b/asset/repository.go
index 32128a5..0905b5c 100644
--- a/asset/repository.go
+++ b/asset/repository.go
@@ -14,7 +14,6 @@ type Repository interface {
Get(name string) (<-chan *Snapshot, error)
// LastDate returns the date of the last snapshot for
- // the asset with the given name, if any. Returns an
- // empty value if no snapshots exist.
- LastDate(name string) time.Time
+ // the asset with the given name.
+ LastDate(name string) (time.Time, error)
}
diff --git a/asset/testdata/empty.csv b/asset/testdata/empty.csv
new file mode 100644
index 0000000..0335cf0
--- /dev/null
+++ b/asset/testdata/empty.csv
@@ -0,0 +1 @@
+Date,Open,High,Low,Close,Adj Close,Volume
diff --git a/helper/README.md b/helper/README.md
index ed3683f..7c502a9 100644
--- a/helper/README.md
+++ b/helper/README.md
@@ -40,10 +40,12 @@ The information provided on this project is strictly for informational purposes
- [func Duplicate\[T any\]\(input \<\-chan T, count int\) \[\]\<\-chan T](<#Duplicate>)
- [func Field\[T, S any\]\(c \<\-chan \*S, name string\) \(\<\-chan T, error\)](<#Field>)
- [func Filter\[T Number\]\(c \<\-chan T, p func\(T\) bool\) \<\-chan T](<#Filter>)
+- [func First\[T any\]\(c \<\-chan T, count int\) \<\-chan T](<#First>)
- [func Head\[T Number\]\(c \<\-chan T, count int\) \<\-chan T](<#Head>)
- [func IncrementBy\[T Number\]\(c \<\-chan T, i T\) \<\-chan T](<#IncrementBy>)
- [func KeepNegatives\[T Number\]\(c \<\-chan T\) \<\-chan T](<#KeepNegatives>)
- [func KeepPositives\[T Number\]\(c \<\-chan T\) \<\-chan T](<#KeepPositives>)
+- [func Last\[T any\]\(c \<\-chan T, count int\) \<\-chan T](<#Last>)
- [func Map\[F, T any\]\(c \<\-chan F, f func\(F\) T\) \<\-chan T](<#Map>)
- [func Multiply\[T Number\]\(ac, bc \<\-chan T\) \<\-chan T](<#Multiply>)
- [func MultiplyBy\[T Number\]\(c \<\-chan T, m T\) \<\-chan T](<#MultiplyBy>)
@@ -88,7 +90,9 @@ The information provided on this project is strictly for informational purposes
- [func NewNumericReportColumn\[T Number\]\(name string, values \<\-chan T\) ReportColumn](<#NewNumericReportColumn>)
- [type Ring](<#Ring>)
- [func NewRing\[T any\]\(size int\) \*Ring\[T\]](<#NewRing>)
- - [func \(r \*Ring\[T\]\) Insert\(t T\) T](<#Ring[T].Insert>)
+ - [func \(r \*Ring\[T\]\) Get\(\) \(T, bool\)](<#Ring[T].Get>)
+ - [func \(r \*Ring\[T\]\) IsEmpty\(\) bool](<#Ring[T].IsEmpty>)
+ - [func \(r \*Ring\[T\]\) Put\(t T\) T](<#Ring[T].Put>)
## Constants
@@ -344,6 +348,15 @@ even := helper.Filter(c, func(n int) bool {
})
```
+
+## func [First]()
+
+```go
+func First[T any](c <-chan T, count int) <-chan T
+```
+
+First takes a channel of values and returns a new channel containing the first N values.
+
## func [Head]()
@@ -412,6 +425,15 @@ positives := helper.KeepPositives(c)
fmt.Println(helper.ChanToSlice(positives)) // [0, 20, 4, 0]
```
+
+## func [Last]()
+
+```go
+func Last[T any](c <-chan T, count int) <-chan T
+```
+
+Last takes a channel of values and returns a new channel containing the last N values.
+
## func [Map]()
@@ -950,7 +972,7 @@ func NewNumericReportColumn[T Number](name string, values <-chan T) ReportColumn
NewNumericReportColumn returns a new instance of a numeric data column for a report.
-## type [Ring]()
+## type [Ring]()
Ring represents a ring structure that can be instantiated using the NewRing function.
@@ -972,7 +994,7 @@ type Ring[T any] struct {
```
-### func [NewRing]()
+### func [NewRing]()
```go
func NewRing[T any](size int) *Ring[T]
@@ -980,13 +1002,31 @@ func NewRing[T any](size int) *Ring[T]
NewRing creates a new ring instance with the given size.
-
-### func \(\*Ring\[T\]\) [Insert]()
+
+### func \(\*Ring\[T\]\) [Get]()
+
+```go
+func (r *Ring[T]) Get() (T, bool)
+```
+
+Get retrieves the available value from the ring buffer. If empty, it returns the default value \(T\) and false.
+
+
+### func \(\*Ring\[T\]\) [IsEmpty]()
+
+```go
+func (r *Ring[T]) IsEmpty() bool
+```
+
+IsEmpty checks if the current ring buffer is empty.
+
+
+### func \(\*Ring\[T\]\) [Put]()
```go
-func (r *Ring[T]) Insert(t T) T
+func (r *Ring[T]) Put(t T) T
```
-Insert function inserts the specified value into the ring and returns the value that was previously stored at that index.
+Put inserts the specified value into the ring and returns the value that was previously stored at that index.
Generated by [gomarkdoc]()
diff --git a/helper/first.go b/helper/first.go
new file mode 100644
index 0000000..7798d8d
--- /dev/null
+++ b/helper/first.go
@@ -0,0 +1,26 @@
+// Copyright (c) 2023 Onur Cinar. All Rights Reserved.
+// The source code is provided under MIT License.
+// https://github.com/cinar/indicator
+
+package helper
+
+// First takes a channel of values and returns a new channel containing the first N values.
+func First[T any](c <-chan T, count int) <-chan T {
+ result := make(chan T, cap(c))
+
+ go func() {
+ defer close(result)
+ for i := 0; i < count; i++ {
+ n, ok := <-c
+ if !ok {
+ return
+ }
+
+ result <- n
+ }
+
+ Drain(c)
+ }()
+
+ return result
+}
diff --git a/helper/first_test.go b/helper/first_test.go
new file mode 100644
index 0000000..d698b39
--- /dev/null
+++ b/helper/first_test.go
@@ -0,0 +1,35 @@
+// Copyright (c) 2023 Onur Cinar. All Rights Reserved.
+// The source code is provided under MIT License.
+// https://github.com/cinar/indicator
+
+package helper_test
+
+import (
+ "testing"
+
+ "github.com/cinar/indicator/helper"
+)
+
+func TestFirst(t *testing.T) {
+ input := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
+ expected := helper.SliceToChan([]int{1, 2, 3, 4})
+
+ actual := helper.First(input, 4)
+
+ err := helper.CheckEquals(actual, expected)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestFirstLessValues(t *testing.T) {
+ input := helper.SliceToChan([]int{1, 2})
+ expected := helper.SliceToChan([]int{1, 2})
+
+ actual := helper.First(input, 4)
+
+ err := helper.CheckEquals(actual, expected)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/helper/last.go b/helper/last.go
index 6dc5002..2d6ec04 100644
--- a/helper/last.go
+++ b/helper/last.go
@@ -8,5 +8,20 @@ package helper
func Last[T any](c <-chan T, count int) <-chan T {
result := make(chan T, cap(c))
+ go func() {
+ defer close(result)
+
+ ring := NewRing[T](count)
+
+ for n := range c {
+ ring.Put(n)
+ }
+
+ for !ring.IsEmpty() {
+ n, _ := ring.Get()
+ result <- n
+ }
+ }()
+
return result
}
diff --git a/helper/ring.go b/helper/ring.go
index 3ce8c3f..12f0cd3 100644
--- a/helper/ring.go
+++ b/helper/ring.go
@@ -4,8 +4,6 @@
package helper
-import "fmt"
-
// Ring represents a ring structure that can be instantiated
// using the NewRing function.
//
@@ -21,6 +19,7 @@ type Ring[T any] struct {
buffer []T
begin int
end int
+ empty bool
}
// NewRing creates a new ring instance with the given size.
@@ -29,22 +28,22 @@ func NewRing[T any](size int) *Ring[T] {
buffer: make([]T, size),
begin: 0,
end: 0,
+ empty: true,
}
}
// Put inserts the specified value into the ring and returns the
// value that was previously stored at that index.
func (r *Ring[T]) Put(t T) T {
+ if !r.empty && (r.end == r.begin) {
+ r.begin = r.nextIndex(r.begin)
+ }
+
o := r.buffer[r.end]
r.buffer[r.end] = t
r.end = r.nextIndex(r.end)
-
- if r.end == r.begin {
- r.begin = r.nextIndex(r.begin)
- }
-
- fmt.Printf("b=%d e=%d buffer=%v\n", r.begin, r.end, r.buffer)
+ r.empty = false
return o
}
@@ -54,19 +53,23 @@ func (r *Ring[T]) Put(t T) T {
func (r *Ring[T]) Get() (T, bool) {
var t T
- if r.IsEmpty() {
+ if r.empty {
return t, false
}
t = r.buffer[r.begin]
r.begin = r.nextIndex(r.begin)
+ if r.begin == r.end {
+ r.empty = true
+ }
+
return t, true
}
// IsEmpty checks if the current ring buffer is empty.
func (r *Ring[T]) IsEmpty() bool {
- return r.end == r.begin
+ return r.empty
}
// nextIndex returns the next index in a ring buffer, wrapping
diff --git a/helper/ring_test.go b/helper/ring_test.go
index 3015438..2e54e9f 100644
--- a/helper/ring_test.go
+++ b/helper/ring_test.go
@@ -55,4 +55,9 @@ func TestRingEmpty(t *testing.T) {
if !ring.IsEmpty() {
t.Fatal("not empty")
}
+
+ _, ok := ring.Get()
+ if ok {
+ t.Fatal("not empty")
+ }
}