diff --git a/README.md b/README.md
index 518d6ad9..5a4e1e28 100644
--- a/README.md
+++ b/README.md
@@ -145,10 +145,10 @@ Binaries will be published once the codebase reaches a stable condition.
|User|Contribution Amount|
|--|--|
-| [thrasher-](https://github.com/thrasher-) | 666 |
-| [shazbert](https://github.com/shazbert) | 258 |
-| [gloriousCode](https://github.com/gloriousCode) | 197 |
-| [dependabot[bot]](https://github.com/apps/dependabot) | 89 |
+| [thrasher-](https://github.com/thrasher-) | 667 |
+| [shazbert](https://github.com/shazbert) | 260 |
+| [gloriousCode](https://github.com/gloriousCode) | 199 |
+| [dependabot[bot]](https://github.com/apps/dependabot) | 99 |
| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 88 |
| [xtda](https://github.com/xtda) | 47 |
| [lrascao](https://github.com/lrascao) | 27 |
diff --git a/backtester/config/config_test.go b/backtester/config/config_test.go
index 31f33e95..5aa25d90 100644
--- a/backtester/config/config_test.go
+++ b/backtester/config/config_test.go
@@ -536,6 +536,67 @@ func TestGenerateConfigForDCAAPICandles(t *testing.T) {
}
}
+func TestGenerateConfigForPluginStrategy(t *testing.T) {
+ if !saveConfig {
+ t.Skip()
+ }
+ cfg := Config{
+ Nickname: "ExamplePluginStrategy",
+ Goal: "To demonstrate that custom strategies can be used",
+ StrategySettings: StrategySettings{
+ Name: "custom-strategy",
+ },
+ CurrencySettings: []CurrencySettings{
+ {
+ ExchangeName: testExchange,
+ Asset: asset.Spot,
+ Base: currency.BTC,
+ Quote: currency.USDT,
+ SpotDetails: &SpotDetails{
+ InitialQuoteFunds: initialFunds1000000,
+ },
+ BuySide: minMax,
+ SellSide: minMax,
+ MakerFee: &makerFee,
+ TakerFee: &takerFee,
+ },
+ },
+ DataSettings: DataSettings{
+ Interval: kline.OneDay,
+ DataType: common.CandleStr,
+ APIData: &APIData{
+ StartDate: startDate,
+ EndDate: endDate,
+ InclusiveEndDate: false,
+ },
+ },
+ PortfolioSettings: PortfolioSettings{
+ BuySide: minMax,
+ SellSide: minMax,
+ Leverage: Leverage{
+ CanUseLeverage: false,
+ },
+ },
+ StatisticSettings: StatisticSettings{
+ RiskFreeRate: decimal.NewFromFloat(0.03),
+ },
+ }
+ if saveConfig {
+ result, err := json.MarshalIndent(cfg, "", " ")
+ if err != nil {
+ t.Fatal(err)
+ }
+ p, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = os.WriteFile(filepath.Join(p, "examples", "custom-plugin-strategy.strat"), result, file.DefaultPermissionOctal)
+ if err != nil {
+ t.Error(err)
+ }
+ }
+}
+
func TestGenerateConfigForDCAAPICandlesExchangeLevelFunding(t *testing.T) {
if !saveConfig {
t.Skip()
diff --git a/backtester/config/examples/custom-plugin-strategy.strat b/backtester/config/examples/custom-plugin-strategy.strat
new file mode 100644
index 00000000..e8739742
--- /dev/null
+++ b/backtester/config/examples/custom-plugin-strategy.strat
@@ -0,0 +1,71 @@
+{
+ "nickname": "ExamplePluginStrategy",
+ "goal": "To demonstrate that custom strategies can be used",
+ "strategy-settings": {
+ "name": "custom-strategy",
+ "use-simultaneous-signal-processing": false,
+ "disable-usd-tracking": false
+ },
+ "funding-settings": {
+ "use-exchange-level-funding": false
+ },
+ "currency-settings": [
+ {
+ "exchange-name": "ftx",
+ "asset": "spot",
+ "base": "BTC",
+ "quote": "USDT",
+ "spot-details": {
+ "initial-quote-funds": "1000000"
+ },
+ "buy-side": {
+ "minimum-size": "0.005",
+ "maximum-size": "2",
+ "maximum-total": "40000"
+ },
+ "sell-side": {
+ "minimum-size": "0.005",
+ "maximum-size": "2",
+ "maximum-total": "40000"
+ },
+ "min-slippage-percent": "0",
+ "max-slippage-percent": "0",
+ "maker-fee-override": "0.0002",
+ "taker-fee-override": "0.0007",
+ "maximum-holdings-ratio": "0",
+ "skip-candle-volume-fitting": false,
+ "use-exchange-order-limits": false,
+ "use-exchange-pnl-calculation": false
+ }
+ ],
+ "data-settings": {
+ "interval": 86400000000000,
+ "data-type": "candle",
+ "api-data": {
+ "start-date": "2021-08-01T00:00:00+10:00",
+ "end-date": "2021-12-01T00:00:00+11:00",
+ "inclusive-end-date": false
+ }
+ },
+ "portfolio-settings": {
+ "leverage": {
+ "can-use-leverage": false,
+ "maximum-orders-with-leverage-ratio": "0",
+ "maximum-leverage-rate": "0",
+ "maximum-collateral-leverage-rate": "0"
+ },
+ "buy-side": {
+ "minimum-size": "0.005",
+ "maximum-size": "2",
+ "maximum-total": "40000"
+ },
+ "sell-side": {
+ "minimum-size": "0.005",
+ "maximum-size": "2",
+ "maximum-total": "40000"
+ }
+ },
+ "statistic-settings": {
+ "risk-free-rate": "0.03"
+ }
+}
\ No newline at end of file
diff --git a/backtester/eventhandlers/strategies/strategies.go b/backtester/eventhandlers/strategies/strategies.go
index e5e8d50c..c0575c31 100644
--- a/backtester/eventhandlers/strategies/strategies.go
+++ b/backtester/eventhandlers/strategies/strategies.go
@@ -3,42 +3,68 @@ package strategies
import (
"fmt"
"strings"
+ "sync"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/dollarcostaverage"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/ftxcashandcarry"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/rsi"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/top2bottom2"
+ "github.com/thrasher-corp/gocryptotrader/common"
)
// LoadStrategyByName returns the strategy by its name
func LoadStrategyByName(name string, useSimultaneousProcessing bool) (Handler, error) {
- strats := GetStrategies()
- for i := range strats {
- if !strings.EqualFold(name, strats[i].Name()) {
+ strategies := GetStrategies()
+ for i := range strategies {
+ if !strings.EqualFold(name, strategies[i].Name()) {
continue
}
if useSimultaneousProcessing {
- if !strats[i].SupportsSimultaneousProcessing() {
+ if !strategies[i].SupportsSimultaneousProcessing() {
return nil, fmt.Errorf(
"strategy '%v' %w",
name,
base.ErrSimultaneousProcessingNotSupported)
}
- strats[i].SetSimultaneousProcessing(useSimultaneousProcessing)
+ strategies[i].SetSimultaneousProcessing(useSimultaneousProcessing)
}
- return strats[i], nil
+ return strategies[i], nil
}
return nil, fmt.Errorf("strategy '%v' %w", name, base.ErrStrategyNotFound)
}
// GetStrategies returns a static list of set strategies
// they must be set in here for the backtester to recognise them
-func GetStrategies() []Handler {
- return []Handler{
+func GetStrategies() StrategyHolder {
+ m.Lock()
+ defer m.Unlock()
+ return strategyHolder
+}
+
+// AddStrategy will add a strategy to the list of strategies
+func AddStrategy(strategy Handler) error {
+ if strategy == nil {
+ return fmt.Errorf("%w strategy handler", common.ErrNilPointer)
+ }
+ m.Lock()
+ defer m.Unlock()
+ for i := range strategyHolder {
+ if strings.EqualFold(strategyHolder[i].Name(), strategy.Name()) {
+ return fmt.Errorf("'%v' %w", strategy.Name(), ErrStrategyAlreadyExists)
+ }
+ }
+ strategyHolder = append(strategyHolder, strategy)
+ return nil
+}
+
+var (
+ m sync.Mutex
+
+ strategyHolder = StrategyHolder{
new(dollarcostaverage.Strategy),
new(rsi.Strategy),
new(top2bottom2.Strategy),
new(ftxcashandcarry.Strategy),
}
-}
+)
diff --git a/backtester/eventhandlers/strategies/strategies_test.go b/backtester/eventhandlers/strategies/strategies_test.go
index 4fd37049..d3b0f6b5 100644
--- a/backtester/eventhandlers/strategies/strategies_test.go
+++ b/backtester/eventhandlers/strategies/strategies_test.go
@@ -4,9 +4,14 @@ import (
"errors"
"testing"
+ "github.com/thrasher-corp/gocryptotrader/backtester/data"
+ "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/dollarcostaverage"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/rsi"
+ "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
+ "github.com/thrasher-corp/gocryptotrader/backtester/funding"
+ "github.com/thrasher-corp/gocryptotrader/common"
)
func TestGetStrategies(t *testing.T) {
@@ -55,3 +60,49 @@ func TestLoadStrategyByName(t *testing.T) {
t.Errorf("received: %v, expected: %v", err, nil)
}
}
+
+type customStrategy struct {
+ base.Strategy
+}
+
+func (s *customStrategy) Name() string {
+ return "custom-strategy"
+}
+func (s *customStrategy) Description() string {
+ return "this is a demonstration of loading strategies via custom plugins"
+}
+func (s *customStrategy) SupportsSimultaneousProcessing() bool {
+ return true
+}
+func (s *customStrategy) OnSignal(d data.Handler, _ funding.IFundingTransferer, _ portfolio.Handler) (signal.Event, error) {
+ return s.createSignal(d)
+}
+func (s *customStrategy) OnSimultaneousSignals(d []data.Handler, f funding.IFundingTransferer, p portfolio.Handler) ([]signal.Event, error) {
+ return nil, nil
+}
+func (s *customStrategy) createSignal(d data.Handler) (*signal.Signal, error) {
+ return nil, nil
+}
+func (s *customStrategy) SetCustomSettings(map[string]interface{}) error {
+ return nil
+}
+
+// SetDefaults sets default values for overridable custom settings
+func (s *customStrategy) SetDefaults() {}
+
+func TestAddStrategy(t *testing.T) {
+ t.Parallel()
+ err := AddStrategy(nil)
+ if !errors.Is(err, common.ErrNilPointer) {
+ t.Errorf("received '%v' expected '%v'", err, common.ErrNilPointer)
+ }
+ err = AddStrategy(new(dollarcostaverage.Strategy))
+ if !errors.Is(err, ErrStrategyAlreadyExists) {
+ t.Errorf("received '%v' expected '%v'", err, ErrStrategyAlreadyExists)
+ }
+
+ err = AddStrategy(new(customStrategy))
+ if !errors.Is(err, nil) {
+ t.Errorf("received '%v' expected '%v'", err, nil)
+ }
+}
diff --git a/backtester/eventhandlers/strategies/strategies_types.go b/backtester/eventhandlers/strategies/strategies_types.go
index f7fdd4a5..792ba1d1 100644
--- a/backtester/eventhandlers/strategies/strategies_types.go
+++ b/backtester/eventhandlers/strategies/strategies_types.go
@@ -1,12 +1,20 @@
package strategies
import (
+ "errors"
+
"github.com/thrasher-corp/gocryptotrader/backtester/data"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio"
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
"github.com/thrasher-corp/gocryptotrader/backtester/funding"
)
+// ErrStrategyAlreadyExists returned when a strategy matches the same name
+var ErrStrategyAlreadyExists = errors.New("strategy already exists")
+
+// StrategyHolder holds strategies
+type StrategyHolder []Handler
+
// Handler defines all functions required to run strategies against data events
type Handler interface {
Name() string
diff --git a/backtester/eventtypes/event/event.go b/backtester/eventtypes/event/event.go
index b9611562..57014878 100644
--- a/backtester/eventtypes/event/event.go
+++ b/backtester/eventtypes/event/event.go
@@ -77,7 +77,7 @@ func (b *Base) GetReasons() []string {
return b.Reasons
}
-// GetBase returns an event base
+// GetBase returns the underlying base
func (b *Base) GetBase() *Base {
return b
}
diff --git a/backtester/main.go b/backtester/main.go
index ae1c9491..099905f2 100644
--- a/backtester/main.go
+++ b/backtester/main.go
@@ -9,19 +9,104 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/common"
"github.com/thrasher-corp/gocryptotrader/backtester/config"
backtest "github.com/thrasher-corp/gocryptotrader/backtester/engine"
+ "github.com/thrasher-corp/gocryptotrader/backtester/plugins/strategies"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/signaler"
)
+var configPath, templatePath, reportOutput, strategyPluginPath string
+var printLogo, generateReport, darkReport, verbose, colourOutput, logSubHeader bool
+
func main() {
- var configPath, templatePath, reportOutput string
- var printLogo, generateReport, darkReport, verbose, colourOutput, logSubHeader bool
wd, err := os.Getwd()
if err != nil {
fmt.Printf("Could not get working directory. Error: %v.\n", err)
os.Exit(1)
}
+ parseFlags(wd)
+ if !colourOutput {
+ common.PurgeColours()
+ }
+ var bt *backtest.BackTest
+ var cfg *config.Config
+ log.GlobalLogConfig = log.GenDefaultSettings()
+ log.GlobalLogConfig.AdvancedSettings.ShowLogSystemName = convert.BoolPtr(logSubHeader)
+ log.GlobalLogConfig.AdvancedSettings.Headers.Info = common.ColourInfo + "[INFO]" + common.ColourDefault
+ log.GlobalLogConfig.AdvancedSettings.Headers.Warn = common.ColourWarn + "[WARN]" + common.ColourDefault
+ log.GlobalLogConfig.AdvancedSettings.Headers.Debug = common.ColourDebug + "[DEBUG]" + common.ColourDefault
+ log.GlobalLogConfig.AdvancedSettings.Headers.Error = common.ColourError + "[ERROR]" + common.ColourDefault
+ err = log.SetupGlobalLogger()
+ if err != nil {
+ fmt.Printf("Could not setup global logger. Error: %v.\n", err)
+ os.Exit(1)
+ }
+
+ err = common.RegisterBacktesterSubLoggers()
+ if err != nil {
+ fmt.Printf("Could not register subloggers. Error: %v.\n", err)
+ os.Exit(1)
+ }
+
+ if strategyPluginPath != "" {
+ err = strategies.LoadCustomStrategies(strategyPluginPath)
+ if err != nil {
+ fmt.Printf("Could not load custom strategies. Error: %v.\n", err)
+ os.Exit(1)
+ }
+ }
+
+ cfg, err = config.ReadConfigFromFile(configPath)
+ if err != nil {
+ fmt.Printf("Could not read config. Error: %v.\n", err)
+ os.Exit(1)
+ }
+ if printLogo {
+ fmt.Println(common.Logo())
+ }
+
+ err = cfg.Validate()
+ if err != nil {
+ fmt.Printf("Could not read config. Error: %v.\n", err)
+ os.Exit(1)
+ }
+
+ bt, err = backtest.NewFromConfig(cfg, templatePath, reportOutput, verbose)
+ if err != nil {
+ fmt.Printf("Could not setup backtester from config. Error: %v.\n", err)
+ os.Exit(1)
+ }
+ if cfg.DataSettings.LiveData != nil {
+ go func() {
+ err = bt.RunLive()
+ if err != nil {
+ fmt.Printf("Could not complete live run. Error: %v.\n", err)
+ os.Exit(-1)
+ }
+ }()
+ interrupt := signaler.WaitForInterrupt()
+ log.Infof(log.Global, "Captured %v, shutdown requested.\n", interrupt)
+ bt.Stop()
+ } else {
+ bt.Run()
+ }
+
+ err = bt.Statistic.CalculateAllResults()
+ if err != nil {
+ log.Error(log.Global, err)
+ os.Exit(1)
+ }
+
+ if generateReport {
+ bt.Reports.UseDarkMode(darkReport)
+ err = bt.Reports.GenerateReport()
+ if err != nil {
+ log.Error(log.Global, err)
+ }
+ }
+}
+
+func parseFlags(wd string) {
flag.StringVar(
&configPath,
"configpath",
@@ -76,75 +161,10 @@ func main() {
"logsubheader",
true,
"displays logging subheader to track where activity originates")
+ flag.StringVar(
+ &strategyPluginPath,
+ "strategypluginpath",
+ "",
+ "example path: "+filepath.Join(wd, "plugins", "strategies", "example", "example.so"))
flag.Parse()
- if !colourOutput {
- common.PurgeColours()
- }
- var bt *backtest.BackTest
- var cfg *config.Config
- log.GlobalLogConfig = log.GenDefaultSettings()
- log.GlobalLogConfig.AdvancedSettings.ShowLogSystemName = convert.BoolPtr(logSubHeader)
- log.GlobalLogConfig.AdvancedSettings.Headers.Info = common.ColourInfo + "[INFO]" + common.ColourDefault
- log.GlobalLogConfig.AdvancedSettings.Headers.Warn = common.ColourWarn + "[WARN]" + common.ColourDefault
- log.GlobalLogConfig.AdvancedSettings.Headers.Debug = common.ColourDebug + "[DEBUG]" + common.ColourDefault
- log.GlobalLogConfig.AdvancedSettings.Headers.Error = common.ColourError + "[ERROR]" + common.ColourDefault
- err = log.SetupGlobalLogger()
- if err != nil {
- fmt.Printf("Could not setup global logger. Error: %v.\n", err)
- os.Exit(1)
- }
-
- err = common.RegisterBacktesterSubLoggers()
- if err != nil {
- fmt.Printf("Could not register subloggers. Error: %v.\n", err)
- os.Exit(1)
- }
-
- cfg, err = config.ReadConfigFromFile(configPath)
- if err != nil {
- fmt.Printf("Could not read config. Error: %v.\n", err)
- os.Exit(1)
- }
- if printLogo {
- fmt.Println(common.Logo())
- }
-
- err = cfg.Validate()
- if err != nil {
- fmt.Printf("Could not read config. Error: %v.\n", err)
- os.Exit(1)
- }
- bt, err = backtest.NewFromConfig(cfg, templatePath, reportOutput, verbose)
- if err != nil {
- fmt.Printf("Could not setup backtester from config. Error: %v.\n", err)
- os.Exit(1)
- }
- if cfg.DataSettings.LiveData != nil {
- go func() {
- err = bt.RunLive()
- if err != nil {
- fmt.Printf("Could not complete live run. Error: %v.\n", err)
- os.Exit(-1)
- }
- }()
- interrupt := signaler.WaitForInterrupt()
- log.Infof(log.Global, "Captured %v, shutdown requested.\n", interrupt)
- bt.Stop()
- } else {
- bt.Run()
- }
-
- err = bt.Statistic.CalculateAllResults()
- if err != nil {
- log.Error(log.Global, err)
- os.Exit(1)
- }
-
- if generateReport {
- bt.Reports.UseDarkMode(darkReport)
- err = bt.Reports.GenerateReport()
- if err != nil {
- log.Error(log.Global, err)
- }
- }
}
diff --git a/backtester/plugins/README.md b/backtester/plugins/README.md
new file mode 100644
index 00000000..17c0e81c
--- /dev/null
+++ b/backtester/plugins/README.md
@@ -0,0 +1,70 @@
+# GoCryptoTrader Backtester: Plugins package
+
+
+
+
+[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
+[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
+[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/backtester/plugins)
+[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
+[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
+
+
+This plugins package is part of the GoCryptoTrader codebase.
+
+## This is still in active development
+
+You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
+
+Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk)
+
+## Plugins package overview
+
+Golang Plugins are supported by the GoCryptoTrader Backtester. At present, only custom strategies are supported.
+
+Please read the Golang documentation on [plugins](https://golang.org/pkg/plugin/) for more information.
+
+## Building Golang Plugins
+
+### Windows
+Plugin support is not yet available for Windows. However, you can still build via WSL. See below for instructions on a basic setup for WSL. Once completed, follow the instructions for Linux.
+#### WSL Setup
+The following is a basic setup for WSL: [here](https://pureinfotech.com/install-wsl-windows-11/)
+
+### Linux, macOS & WSL
+A plugin is a Go main package with exported functions and variables that has been built with:
+
+```bash
+go build -buildmode=plugin
+```
+
+This outputs a file named `plugins.so` which can be loaded by the backtester. At present, only custom strategies can be loaded. See [here](/strategies/example/README.md) for more information on building custom strategies via plugins.
+
+You must ensure that the plugin is built with the same version of code as the GoCryptoTrader Backtester. Otherwise the plugin will refuse to load.
+
+
+
+#### Installing Golang in WSL
+See the following for instructions on installing Golang in WSL: [here](https://ao.ms/how-to-install-golang-on-wsl-wsl2/)
+
+
+### Please click GoDocs chevron above to view current GoDoc information for this package
+
+## Contribution
+
+Please feel free to submit any pull requests or suggest any desired features to be added.
+
+When submitting a PR, please abide by our coding guidelines:
+
++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
++ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
++ Pull requests need to be based on and opened against the `master` branch.
+
+## Donations
+
+
+
+If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
+
+***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
diff --git a/backtester/plugins/strategies/README.md b/backtester/plugins/strategies/README.md
new file mode 100644
index 00000000..8f2b5892
--- /dev/null
+++ b/backtester/plugins/strategies/README.md
@@ -0,0 +1,64 @@
+# GoCryptoTrader Backtester: Strategies package
+
+
+
+
+[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
+[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
+[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/backtester/plugins/strategies)
+[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
+[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
+
+
+This strategies package is part of the GoCryptoTrader codebase.
+
+## This is still in active development
+
+You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
+
+Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk)
+
+## Strategies package overview
+
+### Designing a strategy
+- File must contain `main` package
+- Custom strategy plugins must adhere to the strategy.Handler interface. See the [strategy.Handler interface documentation](./backtester/eventhandlers/strategies/README.md) for more information.
+- Must contain function `func GetStrategies() []strategy.Handler` to return a slice of implemented `strategy.Handler`.
+ - If only using one custom strategy, can simply `return []strategy.Handler{&customStrategy{}}`.
+
+
+### Building
+See [here](./backtester/plugins/README.md) for details on how to build the plugin file.
+
+### Running
+Plugins can only be loaded via Linux, macOS and WSL. Windows itself is not supported.
+
+To run a strategy you will need to use the following flags when running the GoCryptoTrader Backtester:
+
+```bash
+./backtester -strategypluginpath="path/to/strategy/example.so"
+```
+
+Upon startup, the GoCryptoTrader Backtester will load the strategy and run it for all events.
+
+
+### Please click GoDocs chevron above to view current GoDoc information for this package
+
+## Contribution
+
+Please feel free to submit any pull requests or suggest any desired features to be added.
+
+When submitting a PR, please abide by our coding guidelines:
+
++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
++ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
++ Pull requests need to be based on and opened against the `master` branch.
+
+## Donations
+
+
+
+If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
+
+***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
diff --git a/backtester/plugins/strategies/example/README.md b/backtester/plugins/strategies/example/README.md
new file mode 100644
index 00000000..5ae90d2c
--- /dev/null
+++ b/backtester/plugins/strategies/example/README.md
@@ -0,0 +1,71 @@
+# GoCryptoTrader Backtester: Example package
+
+
+
+
+[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
+[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
+[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/backtester/plugins/strategies/example)
+[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
+[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
+
+
+This example package is part of the GoCryptoTrader codebase.
+
+## This is still in active development
+
+You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
+
+Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk)
+
+## Example package overview
+
+This is a custom strategy for the GoCryptoTrader Backtester. It is a simple example of a strategy that trades a pair of assets and is used to highlight how strategies can be loaded from external sources.
+
+### Designing a strategy
+- File must contain `main` package.
+- Custom strategy plugins must adhere to the strategy.Handler interface. See the [strategy.Handler interface documentation](./backtester/eventhandlers/strategies/README.md) for more information.
+- Must contain function `func GetStrategies() []strategy.Handler` to return a slice of implemented `strategy.Handler`.
+ - If only using one custom strategy, can simply `return []strategy.Handler{&customStrategy{}}`.
+
+### Building
+See [here](./backtester/plugins/README.md) for details on how to build the plugin file.
+
+### Running
+Plugins can only be loaded via Linux, macOS and WSL. Windows itself is not supported.
+
+To run this strategy you will need to use the following flags when running the GoCryptoTrader Backtester:
+
+```bash
+./backtester -strategypluginpath="path/to/strategy/example.so"
+```
+
+To run this specific example strategy, use:
+
+```bash
+./backtester --strategypluginpath="./plugins/strategies/example/example.so"
+```
+
+Upon startup, the GoCryptoTrader Backtester will load the strategy and run it for all events.
+
+
+### Please click GoDocs chevron above to view current GoDoc information for this package
+
+## Contribution
+
+Please feel free to submit any pull requests or suggest any desired features to be added.
+
+When submitting a PR, please abide by our coding guidelines:
+
++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
++ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
++ Pull requests need to be based on and opened against the `master` branch.
+
+## Donations
+
+
+
+If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
+
+***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
diff --git a/backtester/plugins/strategies/example/example-strategy.go b/backtester/plugins/strategies/example/example-strategy.go
new file mode 100644
index 00000000..f5ad6b8f
--- /dev/null
+++ b/backtester/plugins/strategies/example/example-strategy.go
@@ -0,0 +1,89 @@
+package main
+
+import (
+ "github.com/thrasher-corp/gocryptotrader/backtester/data"
+ "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio"
+ "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies"
+ "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base"
+ "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
+ "github.com/thrasher-corp/gocryptotrader/backtester/funding"
+ gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order"
+)
+
+func main() {
+ // required for plugin system
+}
+
+// CustomStrategy the type used to define custom strategy functions
+type CustomStrategy struct {
+ base.Strategy
+}
+
+// GetStrategies is required to load the strategy or strategies into the GoCryptoTrader Backtester
+func GetStrategies() []strategies.Handler {
+ return []strategies.Handler{&CustomStrategy{}}
+}
+
+// Name returns the name of the strategy
+func (s *CustomStrategy) Name() string {
+ return "custom-strategy"
+}
+
+// Description describes the strategy
+func (s *CustomStrategy) Description() string {
+ return "this is a demonstration of loading strategies via custom plugins"
+}
+
+// SupportsSimultaneousProcessing this strategy only supports simultaneous signal processing
+func (s *CustomStrategy) SupportsSimultaneousProcessing() bool {
+ return true
+}
+
+// OnSignal handles a data event and returns what action the strategy believes should occur
+func (s *CustomStrategy) OnSignal(d data.Handler, _ funding.IFundingTransferer, _ portfolio.Handler) (signal.Event, error) {
+ return s.createSignal(d)
+}
+
+// OnSimultaneousSignals analyses multiple data points simultaneously, allowing flexibility
+// in allowing a strategy to only place an order for X currency if Y currency's price is Z
+func (s *CustomStrategy) OnSimultaneousSignals(d []data.Handler, f funding.IFundingTransferer, p portfolio.Handler) ([]signal.Event, error) {
+ response := make([]signal.Event, len(d))
+ for i := range d {
+ sig, err := s.createSignal(d[i])
+ if err != nil {
+ return nil, err
+ }
+ response[i] = sig
+ }
+ return response, nil
+}
+
+func (s *CustomStrategy) createSignal(d data.Handler) (*signal.Signal, error) {
+ es, err := s.GetBaseData(d)
+ if err != nil {
+ return nil, err
+ }
+
+ sig := &signal.Signal{
+ Base: es.Base,
+ OpenPrice: es.OpenPrice,
+ HighPrice: es.HighPrice,
+ LowPrice: es.LowPrice,
+ ClosePrice: es.ClosePrice,
+ Volume: es.Volume,
+ BuyLimit: es.BuyLimit,
+ SellLimit: es.SellLimit,
+ Amount: es.Amount,
+ Direction: gctorder.Buy,
+ }
+ sig.AppendReasonf("Signalling purchase of %s", es.Base.Pair())
+ return sig, nil
+}
+
+// SetCustomSettings can override default settings
+func (s *CustomStrategy) SetCustomSettings(map[string]interface{}) error {
+ return base.ErrCustomSettingsUnsupported
+}
+
+// SetDefaults sets default values for overridable custom settings
+func (s *CustomStrategy) SetDefaults() {}
diff --git a/backtester/plugins/strategies/loader.go b/backtester/plugins/strategies/loader.go
new file mode 100644
index 00000000..2696342f
--- /dev/null
+++ b/backtester/plugins/strategies/loader.go
@@ -0,0 +1,44 @@
+package strategies
+
+import (
+ "errors"
+ "fmt"
+ "plugin"
+
+ "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies"
+ gctcommon "github.com/thrasher-corp/gocryptotrader/common"
+)
+
+var errNoStrategies = errors.New("no strategies contained in plugin. please refer to docs")
+
+// LoadCustomStrategies utilises Go's plugin system to load
+// custom strategies into the backtester.
+func LoadCustomStrategies(strategyPluginPath string) error {
+ p, err := plugin.Open(strategyPluginPath)
+ if err != nil {
+ return fmt.Errorf("could not open plugin: %w", err)
+ }
+ v, err := p.Lookup("GetStrategies")
+ if err != nil {
+ return fmt.Errorf("could not lookup plugin. Plugin must have function `GetStrategy`. Error: %w", err)
+ }
+ customStrategies, ok := v.(func() []strategies.Handler)
+ if !ok {
+ return gctcommon.GetAssertError("[]strategies.Handler", customStrategies)
+ }
+ return addStrategies(customStrategies())
+}
+
+func addStrategies(s []strategies.Handler) error {
+ if len(s) == 0 {
+ return errNoStrategies
+ }
+ var err error
+ for i := range s {
+ err = strategies.AddStrategy(s[i])
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/backtester/plugins/strategies/loader_test.go b/backtester/plugins/strategies/loader_test.go
new file mode 100644
index 00000000..129dd96f
--- /dev/null
+++ b/backtester/plugins/strategies/loader_test.go
@@ -0,0 +1,65 @@
+package strategies
+
+import (
+ "errors"
+ "testing"
+
+ "github.com/thrasher-corp/gocryptotrader/backtester/data"
+ "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio"
+ "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies"
+ "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base"
+ "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/dollarcostaverage"
+ "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
+ "github.com/thrasher-corp/gocryptotrader/backtester/funding"
+)
+
+func TestAddStrategies(t *testing.T) {
+ t.Parallel()
+ err := addStrategies(nil)
+ if !errors.Is(err, errNoStrategies) {
+ t.Error(err)
+ }
+
+ err = addStrategies([]strategies.Handler{&dollarcostaverage.Strategy{}})
+ if !errors.Is(err, strategies.ErrStrategyAlreadyExists) {
+ t.Error(err)
+ }
+
+ err = addStrategies([]strategies.Handler{&CustomStrategy{}})
+ if !errors.Is(err, nil) {
+ t.Error(err)
+ }
+}
+
+type CustomStrategy struct {
+ base.Strategy
+}
+
+func (s *CustomStrategy) Name() string {
+ return "custom-strategy"
+}
+
+func (s *CustomStrategy) Description() string {
+ return "this is a demonstration of loading strategies via custom plugins"
+}
+
+func (s *CustomStrategy) SupportsSimultaneousProcessing() bool {
+ return true
+}
+
+func (s *CustomStrategy) OnSignal(d data.Handler, _ funding.IFundingTransferer, _ portfolio.Handler) (signal.Event, error) {
+ return s.createSignal(d)
+}
+func (s *CustomStrategy) OnSimultaneousSignals(d []data.Handler, f funding.IFundingTransferer, p portfolio.Handler) ([]signal.Event, error) {
+ return nil, nil
+}
+
+func (s *CustomStrategy) createSignal(d data.Handler) (*signal.Signal, error) {
+ return nil, nil
+}
+
+func (s *CustomStrategy) SetCustomSettings(map[string]interface{}) error {
+ return nil
+}
+
+func (s *CustomStrategy) SetDefaults() {}
diff --git a/cmd/documentation/backtester_templates/backtester_plugins_readme.tmpl b/cmd/documentation/backtester_templates/backtester_plugins_readme.tmpl
new file mode 100644
index 00000000..1ac4c758
--- /dev/null
+++ b/cmd/documentation/backtester_templates/backtester_plugins_readme.tmpl
@@ -0,0 +1,36 @@
+{{define "backtester plugins" -}}
+{{template "backtester-header" .}}
+## {{.CapitalName}} package overview
+
+Golang Plugins are supported by the GoCryptoTrader Backtester. At present, only custom strategies are supported.
+
+Please read the Golang documentation on [plugins](https://golang.org/pkg/plugin/) for more information.
+
+## Building Golang Plugins
+
+### Windows
+Plugin support is not yet available for Windows. However, you can still build via WSL. See below for instructions on a basic setup for WSL. Once completed, follow the instructions for Linux.
+#### WSL Setup
+The following is a basic setup for WSL: [here](https://pureinfotech.com/install-wsl-windows-11/)
+
+### Linux, macOS & WSL
+A plugin is a Go main package with exported functions and variables that has been built with:
+
+```bash
+go build -buildmode=plugin
+```
+
+This outputs a file named `{{.Name}}.so` which can be loaded by the backtester. At present, only custom strategies can be loaded. See [here](/strategies/example/README.md) for more information on building custom strategies via plugins.
+
+You must ensure that the plugin is built with the same version of code as the GoCryptoTrader Backtester. Otherwise the plugin will refuse to load.
+
+
+
+#### Installing Golang in WSL
+See the following for instructions on installing Golang in WSL: [here](https://ao.ms/how-to-install-golang-on-wsl-wsl2/)
+
+
+### Please click GoDocs chevron above to view current GoDoc information for this package
+{{template "contributions"}}
+{{template "donations" .}}
+{{end}}
\ No newline at end of file
diff --git a/cmd/documentation/backtester_templates/backtester_plugins_strategies_example_readme.tmpl b/cmd/documentation/backtester_templates/backtester_plugins_strategies_example_readme.tmpl
new file mode 100644
index 00000000..39c1138f
--- /dev/null
+++ b/cmd/documentation/backtester_templates/backtester_plugins_strategies_example_readme.tmpl
@@ -0,0 +1,37 @@
+{{define "backtester plugins strategies example" -}}
+{{template "backtester-header" .}}
+## {{.CapitalName}} package overview
+
+This is a custom strategy for the GoCryptoTrader Backtester. It is a simple example of a strategy that trades a pair of assets and is used to highlight how strategies can be loaded from external sources.
+
+### Designing a strategy
+- File must contain `main` package.
+- Custom strategy plugins must adhere to the strategy.Handler interface. See the [strategy.Handler interface documentation](./backtester/eventhandlers/strategies/README.md) for more information.
+- Must contain function `func GetStrategies() []strategy.Handler` to return a slice of implemented `strategy.Handler`.
+ - If only using one custom strategy, can simply `return []strategy.Handler{&customStrategy{}}`.
+
+### Building
+See [here](./backtester/plugins/README.md) for details on how to build the plugin file.
+
+### Running
+Plugins can only be loaded via Linux, macOS and WSL. Windows itself is not supported.
+
+To run this strategy you will need to use the following flags when running the GoCryptoTrader Backtester:
+
+```bash
+./backtester -strategypluginpath="path/to/strategy/example.so"
+```
+
+To run this specific example strategy, use:
+
+```bash
+./backtester --strategypluginpath="./plugins/strategies/example/example.so"
+```
+
+Upon startup, the GoCryptoTrader Backtester will load the strategy and run it for all events.
+
+
+### Please click GoDocs chevron above to view current GoDoc information for this package
+{{template "contributions"}}
+{{template "donations" .}}
+{{end}}
\ No newline at end of file
diff --git a/cmd/documentation/backtester_templates/backtester_plugins_strategies_readme.tmpl b/cmd/documentation/backtester_templates/backtester_plugins_strategies_readme.tmpl
new file mode 100644
index 00000000..9ec62634
--- /dev/null
+++ b/cmd/documentation/backtester_templates/backtester_plugins_strategies_readme.tmpl
@@ -0,0 +1,30 @@
+{{define "backtester plugins strategies" -}}
+{{template "backtester-header" .}}
+## {{.CapitalName}} package overview
+
+### Designing a strategy
+- File must contain `main` package
+- Custom strategy plugins must adhere to the strategy.Handler interface. See the [strategy.Handler interface documentation](./backtester/eventhandlers/strategies/README.md) for more information.
+- Must contain function `func GetStrategies() []strategy.Handler` to return a slice of implemented `strategy.Handler`.
+ - If only using one custom strategy, can simply `return []strategy.Handler{&customStrategy{}}`.
+
+
+### Building
+See [here](./backtester/plugins/README.md) for details on how to build the plugin file.
+
+### Running
+Plugins can only be loaded via Linux, macOS and WSL. Windows itself is not supported.
+
+To run a strategy you will need to use the following flags when running the GoCryptoTrader Backtester:
+
+```bash
+./backtester -strategypluginpath="path/to/strategy/example.so"
+```
+
+Upon startup, the GoCryptoTrader Backtester will load the strategy and run it for all events.
+
+
+### Please click GoDocs chevron above to view current GoDoc information for this package
+{{template "contributions"}}
+{{template "donations" .}}
+{{end}}
\ No newline at end of file