From 00fc19f280d9b462d5f01f0fcb0221b5d74eb5cb Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Mon, 18 Dec 2023 07:25:02 -0800 Subject: [PATCH] Last added. --- asset/README.md | 49 ++++++++++++++++++++++---- asset/file_system_repository.go | 35 +++++++++++++++++++ asset/file_system_repository_test.go | 32 +++++++++++++++++ asset/repository.go | 20 +++++++++++ asset/snapshot.go | 8 ----- asset/testdata/brk-b.csv | 22 ++++++++++++ helper/last.go | 12 +++++++ helper/last_test.go | 35 +++++++++++++++++++ helper/ring.go | 52 +++++++++++++++++++++++----- helper/ring_test.go | 34 +++++++++++++++++- strategy/README.md | 4 +-- 11 files changed, 278 insertions(+), 25 deletions(-) create mode 100644 asset/file_system_repository.go create mode 100644 asset/file_system_repository_test.go create mode 100644 asset/repository.go create mode 100644 asset/testdata/brk-b.csv create mode 100644 helper/last.go create mode 100644 helper/last_test.go diff --git a/asset/README.md b/asset/README.md index 5ecb7e9..6607fe9 100644 --- a/asset/README.md +++ b/asset/README.md @@ -24,21 +24,58 @@ The information provided on this project is strictly for informational purposes ## Index -- [func CloseOnly\(s \<\-chan \*Snapshot\) \<\-chan float64](<#CloseOnly>) +- [type FileSystemRepository](<#FileSystemRepository>) + - [func NewFileSystemRepository\(base string\) \*FileSystemRepository](<#NewFileSystemRepository>) + - [func \(r \*FileSystemRepository\) Get\(name string\) \(\<\-chan \*Snapshot, error\)](<#FileSystemRepository.Get>) +- [type Repository](<#Repository>) - [type Snapshot](<#Snapshot>) - -## func [CloseOnly]() + +## type [FileSystemRepository]() + +FileSystemRepository stores and retrieves asset snapshots using the local file system. ```go -func CloseOnly(s <-chan *Snapshot) <-chan float64 +type FileSystemRepository struct { + Repository + // contains filtered or unexported fields +} ``` -CloseOnly filters the given snapshot channel and returns a new channel containing only the closing values. + +### func [NewFileSystemRepository]() + +```go +func NewFileSystemRepository(base string) *FileSystemRepository +``` + +NewFileSystemRepository initializes a file system repository with the given base directory. + + +### func \(\*FileSystemRepository\) [Get]() + +```go +func (r *FileSystemRepository) Get(name string) (<-chan *Snapshot, error) +``` + +Get attempts to return a channel of snapshots fo the asset with the given name. + + +## type [Repository]() + +Repository serves as a centralized storage and retrieval location for asset snapshots. + +```go +type Repository interface { + // Get attempts to return a channel of snapshots for + // the asset with the given name. + Get(name string) (<-chan *Snapshot, error) +} +``` -## type [Snapshot]() +## type [Snapshot]() Snapshot captures a single observation of an asset's price at a specific moment. diff --git a/asset/file_system_repository.go b/asset/file_system_repository.go new file mode 100644 index 0000000..af2689f --- /dev/null +++ b/asset/file_system_repository.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 asset + +import ( + "fmt" + "path/filepath" + + "github.com/cinar/indicator/helper" +) + +// FileSystemRepository stores and retrieves asset snapshots using +// the local file system. +type FileSystemRepository struct { + Repository + + // base is the root directory where asset snapshots are stored. + base string +} + +// NewFileSystemRepository initializes a file system repository with +// the given base directory. +func NewFileSystemRepository(base string) *FileSystemRepository { + return &FileSystemRepository{ + base: base, + } +} + +// Get attempts to return a channel of snapshots fo the asset with the given name. +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) +} diff --git a/asset/file_system_repository_test.go b/asset/file_system_repository_test.go new file mode 100644 index 0000000..2434924 --- /dev/null +++ b/asset/file_system_repository_test.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 Onur Cinar. All Rights Reserved. +// The source code is provided under MIT License. +// https://github.com/cinar/indicator + +package asset_test + +import ( + "testing" + + "github.com/cinar/indicator/asset" + "github.com/cinar/indicator/helper" +) + +func TestFileSystemRepositoryGet(t *testing.T) { + repository := asset.NewFileSystemRepository("testdata") + + snapshots, err := repository.Get("brk-b") + if err != nil { + t.Fatal(err) + } + + helper.Drain(snapshots) +} + +func TestFileSystemRepositoryGetNonExisting(t *testing.T) { + repository := asset.NewFileSystemRepository("testdata") + + _, err := repository.Get("brk") + if err == nil { + t.Fatal("expected error") + } +} diff --git a/asset/repository.go b/asset/repository.go new file mode 100644 index 0000000..32128a5 --- /dev/null +++ b/asset/repository.go @@ -0,0 +1,20 @@ +// Copyright (c) 2023 Onur Cinar. All Rights Reserved. +// The source code is provided under MIT License. +// https://github.com/cinar/indicator + +package asset + +import "time" + +// Repository serves as a centralized storage and retrieval +// location for asset snapshots. +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, if any. Returns an + // empty value if no snapshots exist. + LastDate(name string) time.Time +} diff --git a/asset/snapshot.go b/asset/snapshot.go index aa91506..4f90925 100644 --- a/asset/snapshot.go +++ b/asset/snapshot.go @@ -6,8 +6,6 @@ package asset import ( "time" - - "github.com/cinar/indicator/helper" ) // Snapshot captures a single observation of an asset's price @@ -36,9 +34,3 @@ type Snapshot struct { // the asset during the snapshot period. Volume float64 } - -// CloseOnly filters the given snapshot channel and returns a new -// channel containing only the closing values. -func CloseOnly(s <-chan *Snapshot) <-chan float64 { - return helper.Map(s, func(s *Snapshot) float64 { return s.Close }) -} diff --git a/asset/testdata/brk-b.csv b/asset/testdata/brk-b.csv new file mode 100644 index 0000000..610c962 --- /dev/null +++ b/asset/testdata/brk-b.csv @@ -0,0 +1,22 @@ +Date,Open,High,Low,Close,Adj Close,Volume +2022-12-01,319,319.559998,313.299988,315.839996,315.839996,4351600 +2022-12-02,313.48999,316.380005,312.75,316.149994,316.149994,3025700 +2022-12-05,315.220001,315.660004,308.730011,310.570007,310.570007,3835800 +2022-12-06,309.950012,310.290009,306.350006,307.779999,307.779999,3877400 +2022-12-07,307.070007,309.380005,304.920013,305.820007,305.820007,4130800 +2022-12-08,306,307.48999,305.089996,305.98999,305.98999,2351700 +2022-12-09,305.320007,308.339996,304.709991,306.390015,306.390015,3326000 +2022-12-12,307.549988,311.910004,305.459991,311.450012,311.450012,4366700 +2022-12-13,318.399994,318.910004,310.820007,312.329987,312.329987,5042800 +2022-12-14,312.73999,316.359985,308.399994,309.290009,309.290009,4056900 +2022-12-15,306.429993,306.959991,299.450012,301.910004,301.910004,5103900 +2022-12-16,299.049988,302.470001,297.76001,300,300,8305700 +2022-12-19,300.51001,301.480011,297.149994,300.029999,300.029999,3842200 +2022-12-20,300.089996,304.190002,297,302,302,3090700 +2022-12-21,304.380005,308.540009,304.160004,307.820007,307.820007,3264600 +2022-12-22,306.100006,306.5,297.640015,302.690002,302.690002,3560100 +2022-12-23,302.880005,306.570007,300.929993,306.48999,306.48999,2460400 +2022-12-27,306.450012,308.579987,304.649994,305.549988,305.549988,2730900 +2022-12-28,304.769989,307.459991,303.26001,303.429993,303.429993,2628200 +2022-12-29,305.940002,309.380005,305.23999,309.059998,309.059998,2846200 +2022-12-30,306.950012,309.040009,305.619995,308.899994,308.899994,3298300 diff --git a/helper/last.go b/helper/last.go new file mode 100644 index 0000000..6dc5002 --- /dev/null +++ b/helper/last.go @@ -0,0 +1,12 @@ +// Copyright (c) 2023 Onur Cinar. All Rights Reserved. +// The source code is provided under MIT License. +// https://github.com/cinar/indicator + +package helper + +// Last takes a channel of values and returns a new channel containing the last N values. +func Last[T any](c <-chan T, count int) <-chan T { + result := make(chan T, cap(c)) + + return result +} diff --git a/helper/last_test.go b/helper/last_test.go new file mode 100644 index 0000000..bc6f430 --- /dev/null +++ b/helper/last_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 TestLast(t *testing.T) { + input := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) + expected := helper.SliceToChan([]int{7, 8, 9, 10}) + + actual := helper.Last(input, 4) + + err := helper.CheckEquals(actual, expected) + if err != nil { + t.Fatal(err) + } +} + +func TestLastLessValues(t *testing.T) { + input := helper.SliceToChan([]int{1, 2}) + expected := helper.SliceToChan([]int{1, 2}) + + actual := helper.Last(input, 4) + + err := helper.CheckEquals(actual, expected) + if err != nil { + t.Fatal(err) + } +} diff --git a/helper/ring.go b/helper/ring.go index 7c3517b..3ce8c3f 100644 --- a/helper/ring.go +++ b/helper/ring.go @@ -4,6 +4,8 @@ package helper +import "fmt" + // Ring represents a ring structure that can be instantiated // using the NewRing function. // @@ -17,24 +19,58 @@ package helper // fmt.Println(ring.Insert(4)) // 2 type Ring[T any] struct { buffer []T - index int + begin int + end int } // NewRing creates a new ring instance with the given size. func NewRing[T any](size int) *Ring[T] { return &Ring[T]{ buffer: make([]T, size), - index: 0, + begin: 0, + end: 0, } } -// Insert function inserts the specified value into the ring and -// returns the value that was previously stored at that index. -func (r *Ring[T]) Insert(t T) T { - r.index = (r.index + 1) % len(r.buffer) +// 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 { + 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) + } - o := r.buffer[r.index] - r.buffer[r.index] = t + fmt.Printf("b=%d e=%d buffer=%v\n", r.begin, r.end, r.buffer) return o } + +// Get retrieves the available value from the ring buffer. If empty, +// it returns the default value (T) and false. +func (r *Ring[T]) Get() (T, bool) { + var t T + + if r.IsEmpty() { + return t, false + } + + t = r.buffer[r.begin] + r.begin = r.nextIndex(r.begin) + + return t, true +} + +// IsEmpty checks if the current ring buffer is empty. +func (r *Ring[T]) IsEmpty() bool { + return r.end == r.begin +} + +// nextIndex returns the next index in a ring buffer, wrapping +// around if it reaches the capacity. +func (r *Ring[T]) nextIndex(i int) int { + return (i + 1) % len(r.buffer) +} diff --git a/helper/ring_test.go b/helper/ring_test.go index 6ef0f23..3015438 100644 --- a/helper/ring_test.go +++ b/helper/ring_test.go @@ -5,6 +5,7 @@ package helper_test import ( + "fmt" "testing" "github.com/cinar/indicator/helper" @@ -17,10 +18,41 @@ func TestRing(t *testing.T) { ring := helper.NewRing[int](4) for i, n := range input { - actual := ring.Insert(n) + actual := ring.Put(n) if actual != expected[i] { t.Fatalf("actual %v expected %v", actual, expected[i]) } } } + +func TestRingEmpty(t *testing.T) { + input := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + size := 4 + + ring := helper.NewRing[int](size) + + if !ring.IsEmpty() { + t.Fatal("not empty") + } + + for _, n := range input { + ring.Put(n) + + if ring.IsEmpty() { + t.Fatal("is empty") + } + } + + for i := 0; i < size; i++ { + n, ok := ring.Get() + if !ok { + t.Fatal("is empty") + } + fmt.Printf("%d %d\n", i, n) + } + + if !ring.IsEmpty() { + t.Fatal("not empty") + } +} diff --git a/strategy/README.md b/strategy/README.md index 56cb014..80b9529 100644 --- a/strategy/README.md +++ b/strategy/README.md @@ -26,7 +26,7 @@ The information provided on this project is strictly for informational purposes - [func ActionsToAnnotations\(ac \<\-chan Action\) \<\-chan string](<#ActionsToAnnotations>) - [func NormalizeActions\(ac \<\-chan Action\) \<\-chan Action](<#NormalizeActions>) -- [func Outcome\(values \<\-chan float64, actions \<\-chan Action\) \<\-chan float64](<#Outcome>) +- [func Outcome\[T helper.Number\]\(values \<\-chan T, actions \<\-chan Action\) \<\-chan float64](<#Outcome>) - [func SpreadActions\(ac \<\-chan Action\) \<\-chan Action](<#SpreadActions>) - [type Action](<#Action>) - [func \(a Action\) Annotation\(\) string](<#Action.Annotation>) @@ -58,7 +58,7 @@ NormalizeActions transforms the given channel of actions to ensure a consistent ## func [Outcome]() ```go -func Outcome(values <-chan float64, actions <-chan Action) <-chan float64 +func Outcome[T helper.Number](values <-chan T, actions <-chan Action) <-chan float64 ``` Outcome simulates the potential result of executing the given actions based on the provided values.