diff --git a/.gitignore b/.gitignore
index 3b735ec..ee09abe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,8 @@
*.dll
*.so
*.dylib
+indicator-backtest
+indicator-sync
# Test binary, built with `go test -c`
*.test
diff --git a/README.md b/README.md
index 3f9b7b7..034382a 100644
--- a/README.md
+++ b/README.md
@@ -177,7 +177,12 @@ The [Sync function]() facilitates the synchronization of assets between designat
The `indicator-sync` command line tool also offers the capability of synchronizing data between the Tiingo Repository and the File System Repository. To illustrate its usage, consider the following example command:
```bash
-$ indicator-sync -key $TIINGO_KEY -target /home/user/assets -days 30
+$ indicator-sync \
+ -source-name tiingo \
+ -source-config $TIINGO_KEY \
+ -target-name filesystem \
+ -target-config /home/user/assets \
+ -days 30
```
This command effectively retrieves the most recent snapshots for assets residing within the `/home/user/assets` directory from the Tiingo Repository. In the event that the local asset file is devoid of content, it automatically extends its reach to synchronize 30 days' worth of snapshots, ensuring a comprehensive and up-to-date repository.
@@ -201,7 +206,11 @@ if err != nil {
The `indicator-backtest` command line tool empowers users to conduct comprehensive backtesting of assets residing within a specified repository. This capability encompasses the application of all currently recognized strategies, culminating in the generation of detailed reports within a designated output directory.
```bash
-$ indicator-backtest -repository /home/user/assets -output /home/user/reports -workers 1
+$ indicator-backtest \
+ -source-name filesystem \
+ -source-config /home/user/assets \
+ -output /home/user/reports \
+ -workers 1
```
Usage
diff --git a/asset/README.md b/asset/README.md
index 0c18aab..07e8c9b 100644
--- a/asset/README.md
+++ b/asset/README.md
@@ -26,6 +26,7 @@ The information provided on this project is strictly for informational purposes
- [Constants](<#constants>)
- [Variables](<#variables>)
+- [func RegisterRepositoryBuilder\(name string, builder RepositoryBuilderFunc\)](<#RegisterRepositoryBuilder>)
- [func SnapshotsAsClosings\(snapshots \<\-chan \*Snapshot\) \<\-chan float64](<#SnapshotsAsClosings>)
- [func SnapshotsAsDates\(snapshots \<\-chan \*Snapshot\) \<\-chan time.Time](<#SnapshotsAsDates>)
- [func SnapshotsAsHighs\(snapshots \<\-chan \*Snapshot\) \<\-chan float64](<#SnapshotsAsHighs>)
@@ -47,6 +48,8 @@ The information provided on this project is strictly for informational purposes
- [func \(r \*InMemoryRepository\) GetSince\(name string, date time.Time\) \(\<\-chan \*Snapshot, error\)](<#InMemoryRepository.GetSince>)
- [func \(r \*InMemoryRepository\) LastDate\(name string\) \(time.Time, error\)](<#InMemoryRepository.LastDate>)
- [type Repository](<#Repository>)
+ - [func NewRepository\(name, config string\) \(Repository, error\)](<#NewRepository>)
+- [type RepositoryBuilderFunc](<#RepositoryBuilderFunc>)
- [type Snapshot](<#Snapshot>)
- [type Sync](<#Sync>)
- [func NewSync\(\) \*Sync](<#NewSync>)
@@ -65,6 +68,21 @@ The information provided on this project is strictly for informational purposes
## Constants
+
+
+```go
+const (
+ // InMemoryRepositoryBuilderName is the name for the in memory repository builder.
+ InMemoryRepositoryBuilderName = "memory"
+
+ // FileSystemRepositoryBuilderName is the name for the file system repository builder.
+ FileSystemRepositoryBuilderName = "filesystem"
+
+ // TiingoRepositoryBuilderName is the name of the Tiingo repository builder.
+ TiingoRepositoryBuilderName = "tiingo"
+)
+```
+
```go
@@ -91,6 +109,15 @@ var ErrRepositoryAssetEmpty = errors.New("asset empty")
var ErrRepositoryAssetNotFound = errors.New("asset is not found")
```
+
+## func [RegisterRepositoryBuilder]()
+
+```go
+func RegisterRepositoryBuilder(name string, builder RepositoryBuilderFunc)
+```
+
+RegisterRepositoryBuilder registers the given builder.
+
## func [SnapshotsAsClosings]()
@@ -303,6 +330,24 @@ type Repository interface {
}
```
+
+### func [NewRepository]()
+
+```go
+func NewRepository(name, config string) (Repository, error)
+```
+
+NewRepository builds a new repository by the given name type and the configuration.
+
+
+## type [RepositoryBuilderFunc]()
+
+RepositoryBuilderFunc defines a function to build a new repository using the given configuration parameter.
+
+```go
+type RepositoryBuilderFunc func(config string) (Repository, error)
+```
+
## type [Snapshot]()
diff --git a/asset/repository_factory.go b/asset/repository_factory.go
new file mode 100644
index 0000000..0dc9d67
--- /dev/null
+++ b/asset/repository_factory.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2021-2024 Onur Cinar.
+// The source code is provided under GNU AGPLv3 License.
+// https://github.com/cinar/indicator
+
+package asset
+
+import (
+ "fmt"
+)
+
+const (
+ // InMemoryRepositoryBuilderName is the name for the in memory repository builder.
+ InMemoryRepositoryBuilderName = "memory"
+
+ // FileSystemRepositoryBuilderName is the name for the file system repository builder.
+ FileSystemRepositoryBuilderName = "filesystem"
+
+ // TiingoRepositoryBuilderName is the name of the Tiingo repository builder.
+ TiingoRepositoryBuilderName = "tiingo"
+)
+
+// RepositoryBuilderFunc defines a function to build a new repository using the given configuration parameter.
+type RepositoryBuilderFunc func(config string) (Repository, error)
+
+// repositoryBuilders provides mapping for the repository builders.
+var repositoryBuilders = map[string]RepositoryBuilderFunc{
+ InMemoryRepositoryBuilderName: inMemoryRepositoryBuilder,
+ FileSystemRepositoryBuilderName: fileSystemRepositoryBuilder,
+ TiingoRepositoryBuilderName: tiingoRepositoryBuilder,
+}
+
+// RegisterRepositoryBuilder registers the given builder.
+func RegisterRepositoryBuilder(name string, builder RepositoryBuilderFunc) {
+ repositoryBuilders[name] = builder
+}
+
+// NewRepository builds a new repository by the given name type and the configuration.
+func NewRepository(name, config string) (Repository, error) {
+ builder, ok := repositoryBuilders[name]
+ if !ok {
+ return nil, fmt.Errorf("unknown repository: %s", name)
+ }
+
+ return builder(config)
+}
+
+// inMemoryRepositoryBuilder builds a new in memory repository instance.
+func inMemoryRepositoryBuilder(_ string) (Repository, error) {
+ return NewInMemoryRepository(), nil
+}
+
+// fileSystemRepositoryBuilder builds a new file system repository instance.
+func fileSystemRepositoryBuilder(config string) (Repository, error) {
+ return NewFileSystemRepository(config), nil
+}
+
+// tiingoRepositoryBuilder builds a new Tiingo repository instance.
+func tiingoRepositoryBuilder(config string) (Repository, error) {
+ return NewTiingoRepository(config), nil
+}
diff --git a/asset/repository_factory_test.go b/asset/repository_factory_test.go
new file mode 100644
index 0000000..4625a02
--- /dev/null
+++ b/asset/repository_factory_test.go
@@ -0,0 +1,77 @@
+// Copyright (c) 2021-2024 Onur Cinar.
+// The source code is provided under GNU AGPLv3 License.
+// https://github.com/cinar/indicator
+
+package asset_test
+
+import (
+ "testing"
+
+ "github.com/cinar/indicator/v2/asset"
+)
+
+func TestNewRepositoryUnknown(t *testing.T) {
+ repository, err := asset.NewRepository("unknown", "")
+ if err == nil {
+ t.Fatalf("unknown repository: %T", repository)
+ }
+}
+
+func TestRegisterRepositoryBuilder(t *testing.T) {
+ builderName := "testbuilder"
+
+ repository, err := asset.NewRepository(builderName, "")
+ if err == nil {
+ t.Fatalf("testbuilder is: %T", repository)
+ }
+
+ asset.RegisterRepositoryBuilder(builderName, func(_ string) (asset.Repository, error) {
+ return asset.NewInMemoryRepository(), nil
+ })
+
+ repository, err = asset.NewRepository(builderName, "")
+ if err != nil {
+ t.Fatalf("testbuilder is not found: %v", err)
+ }
+
+ _, ok := repository.(*asset.InMemoryRepository)
+ if !ok {
+ t.Fatalf("testbuilder is: %T", repository)
+ }
+}
+
+func TestNewRepositoryMemory(t *testing.T) {
+ repository, err := asset.NewRepository(asset.InMemoryRepositoryBuilderName, "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, ok := repository.(*asset.InMemoryRepository)
+ if !ok {
+ t.Fatalf("repository not correct type: %T", repository)
+ }
+}
+
+func TestNewRepositoryFileSystem(t *testing.T) {
+ repository, err := asset.NewRepository(asset.FileSystemRepositoryBuilderName, "testdata")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, ok := repository.(*asset.FileSystemRepository)
+ if !ok {
+ t.Fatalf("repository not correct type: %T", repository)
+ }
+}
+
+func TestNewTiingoRepository(t *testing.T) {
+ repository, err := asset.NewRepository(asset.TiingoRepositoryBuilderName, "1234")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, ok := repository.(*asset.TiingoRepository)
+ if !ok {
+ t.Fatalf("repository not correct type: %T", repository)
+ }
+}
diff --git a/cmd/indicator-backtest/main.go b/cmd/indicator-backtest/main.go
index 9837c7e..e8ae154 100644
--- a/cmd/indicator-backtest/main.go
+++ b/cmd/indicator-backtest/main.go
@@ -21,7 +21,8 @@ import (
)
func main() {
- var repositoryDir string
+ var sourceName string
+ var sourceConfig string
var outputDir string
var workers int
var lastDays int
@@ -36,7 +37,8 @@ func main() {
fmt.Fprintln(os.Stderr, "https://github.com/cinar/indicator")
fmt.Fprintln(os.Stderr)
- flag.StringVar(&repositoryDir, "repository", ".", "file system repository directory")
+ flag.StringVar(&sourceName, "source-name", "filesystem", "source repository type")
+ flag.StringVar(&sourceConfig, "source-config", "", "source repository config")
flag.StringVar(&outputDir, "output", ".", "output directory")
flag.IntVar(&workers, "workers", strategy.DefaultBacktestWorkers, "number of concurrent workers")
flag.IntVar(&lastDays, "last", strategy.DefaultLastDays, "number of days to do backtest")
@@ -46,11 +48,12 @@ func main() {
flag.StringVar(&dateFormat, "date-format", helper.DefaultReportDateFormat, "date format to use")
flag.Parse()
- flag.Parse()
-
- repository := asset.NewFileSystemRepository(repositoryDir)
+ source, err := asset.NewRepository(sourceName, sourceConfig)
+ if err != nil {
+ log.Fatalf("unable to initialize source: %v", err)
+ }
- backtest := strategy.NewBacktest(repository, outputDir)
+ backtest := strategy.NewBacktest(source, outputDir)
backtest.Workers = workers
backtest.LastDays = lastDays
backtest.WriteStrategyReports = writeStrategyRerpots
@@ -70,8 +73,8 @@ func main() {
backtest.Strategies = append(backtest.Strategies, strategy.AllAndStrategies(backtest.Strategies)...)
}
- err := backtest.Run()
+ err = backtest.Run()
if err != nil {
- log.Fatal(err)
+ log.Fatalf("unable to run backtest: %v", err)
}
}
diff --git a/cmd/indicator-sync/main.go b/cmd/indicator-sync/main.go
index 95da46f..d57a8d4 100644
--- a/cmd/indicator-sync/main.go
+++ b/cmd/indicator-sync/main.go
@@ -16,8 +16,10 @@ import (
)
func main() {
- var tiingoKey string
- var targetBase string
+ var sourceName string
+ var sourceConfig string
+ var targetName string
+ var targetConfig string
var minusDays int
var workers int
var delay int
@@ -28,29 +30,42 @@ func main() {
fmt.Fprintln(os.Stderr, "https://github.com/cinar/indicator")
fmt.Fprintln(os.Stderr)
- flag.StringVar(&tiingoKey, "key", "", "tiingo service api key")
- flag.StringVar(&targetBase, "target", ".", "target repository base directory")
+ flag.StringVar(&sourceName, "source-name", "tiingo", "source repository type")
+ flag.StringVar(&sourceConfig, "source-config", "", "source repository config")
+ flag.StringVar(&targetName, "target-name", "filesystem", "target repository type")
+ flag.StringVar(&targetConfig, "target-config", "", "target repository config")
flag.IntVar(&minusDays, "days", 0, "lookback period in days for the new assets")
flag.IntVar(&workers, "workers", asset.DefaultSyncWorkers, "number of concurrent workers")
flag.IntVar(&delay, "delay", asset.DefaultSyncDelay, "delay between each get")
flag.Parse()
- if tiingoKey == "" {
- log.Fatal("Tiingo API key required")
+ source, err := asset.NewRepository(sourceName, sourceConfig)
+ if err != nil {
+ log.Fatalf("unable to initialize source: %v", err)
+ }
+
+ target, err := asset.NewRepository(targetName, targetConfig)
+ if err != nil {
+ log.Fatalf("unable to initialize target: %v", err)
}
defaultStartDate := time.Now().AddDate(0, 0, -minusDays)
- source := asset.NewTiingoRepository(tiingoKey)
- target := asset.NewFileSystemRepository(targetBase)
+ assets := flag.Args()
+ if len(assets) == 0 {
+ assets, err = source.Assets()
+ if err != nil {
+ log.Fatalf("unable to get assets: %v", err)
+ }
+ }
sync := asset.NewSync()
sync.Workers = workers
sync.Delay = delay
- sync.Assets = flag.Args()
+ sync.Assets = assets
- err := sync.Run(source, target, defaultStartDate)
+ err = sync.Run(source, target, defaultStartDate)
if err != nil {
- log.Fatal(err)
+ log.Fatalf("unable to sync repositories: %v", err)
}
}
diff --git a/helper/skip.go b/helper/skip.go
index 69dcbd5..bec5f90 100644
--- a/helper/skip.go
+++ b/helper/skip.go
@@ -17,7 +17,10 @@ func Skip[T any](c <-chan T, count int) <-chan T {
go func() {
for i := 0; i < count; i++ {
- <-c
+ _, ok := <-c
+ if !ok {
+ break
+ }
}
Pipe(c, result)
diff --git a/strategy/README.md b/strategy/README.md
index b0a9ef0..c4038bd 100644
--- a/strategy/README.md
+++ b/strategy/README.md
@@ -89,7 +89,7 @@ const (
```
-## func [ActionSources]()
+## func [ActionSources]()
```go
func ActionSources(strategies []Strategy, snapshots <-chan *asset.Snapshot) []<-chan Action
@@ -555,7 +555,7 @@ func AllSplitStrategies(strategies []Strategy) []Strategy
AllSplitStrategies performs a cartesian product operation on the given strategies, resulting in a collection containing all split strategies formed by combining individual buy and sell strategies.
-### func [AllStrategies]()
+### func [AllStrategies]()
```go
func AllStrategies() []Strategy
diff --git a/strategy/strategy.go b/strategy/strategy.go
index cb26887..d4257b5 100644
--- a/strategy/strategy.go
+++ b/strategy/strategy.go
@@ -43,16 +43,11 @@ func ComputeWithOutcome(s Strategy, c <-chan *asset.Snapshot) (<-chan Action, <-
snapshots := helper.Duplicate(c, 2)
actions := helper.Duplicate(s.Compute(snapshots[0]), 2)
+ closings := asset.SnapshotsAsClosings(snapshots[1])
- openings := helper.Skip(asset.SnapshotsAsOpenings(snapshots[1]), 1)
+ outcomes := Outcome(closings, actions[1])
- outcomes := helper.Echo(
- Outcome(openings, actions[0]),
- 1,
- 1,
- )
-
- return actions[1], outcomes
+ return actions[0], outcomes
}
// AllStrategies returns a slice containing references to all available base strategies.
diff --git a/taskfile.yml b/taskfile.yml
index 9ace79e..ae2fad0 100644
--- a/taskfile.yml
+++ b/taskfile.yml
@@ -30,3 +30,8 @@ tasks:
docs:
cmds:
- go run github.com/princjef/gomarkdoc/cmd/gomarkdoc@v1.1.0 ./...
+
+ build-tools:
+ cmds:
+ - go build -o indicator-backtest cmd/indicator-backtest/main.go
+ - go build -o indicator-sync cmd/indicator-sync/main.go