mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
backtester: standalone application (#988)
* Ramshackle early leads to GRPC backtester * Adds GRPC server, default config generation * Partial support for GRPC backtester config * Update to use Buf, merge fixes * Full config for GRPC * Adds new commands, causes big panic * Fixes panics * Setup for the future * Docs update * test * grpc tests * Fix merge issues. Lint and test * minor fixes after rebase * Docs, formatting and main fixes * Change buf owner * shazNits * test-123 * rpc fixes * string fixes * Removes --singlerun flag and just relies on --singlerunstrategypath * fixes test * initial post merge compatability fixes * this actually all seems to work? unexpected * adds pluginpath to config * rm unused func. add gitignore * rm unused func. add gitignore * lintle * tITLE cASE lOG fIX,rm auth package, gitignore, tmpdir fix * buf updates + gen. go mod tidy * x2 * Update default port, update error text
This commit is contained in:
11
.github/workflows/proto-lint.yml
vendored
11
.github/workflows/proto-lint.yml
vendored
@@ -35,3 +35,14 @@ jobs:
|
||||
|
||||
- name: buf format
|
||||
run: buf format --diff --exit-code
|
||||
|
||||
- name: buf generate backtester
|
||||
working-directory: ./backtester/btrpc
|
||||
run: buf generate
|
||||
|
||||
- uses: bufbuild/buf-lint-action@v1
|
||||
with:
|
||||
input: ./backtester/btrpc
|
||||
|
||||
- name: buf format backtester
|
||||
run: buf format --diff --exit-code
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,6 +16,8 @@ vendor/
|
||||
# Binaries for programs and plugins
|
||||
gocryptotrader
|
||||
cmd/gctcli/gctcli
|
||||
backtester/backtester
|
||||
backtester/btcli/btcli
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
|
||||
@@ -45,13 +45,14 @@ An event-driven backtesting tool to test and iterate trading strategies using hi
|
||||
- Fund transfer. At a strategy level, transfer funds between exchanges to allow for complex strategy design
|
||||
- Backtesting support for futures asset types
|
||||
- Example cash and carry spot futures strategy
|
||||
- Long-running application
|
||||
- GRPC server implementation
|
||||
|
||||
## Planned Features
|
||||
We welcome pull requests on any feature for the Backtester! We will be especially appreciative of any contribution towards the following planned features:
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| Long-running application | Transform the Backtester to run a GRPC server, where commands can be sent to run Backtesting operations. Allowing for many strategies to be run, analysed and tweaked in a more efficient manner |
|
||||
| Leverage support | Leverage is a good way to enhance profit and loss and is important to include in strategies |
|
||||
| Enhance config-builder | Create an application that can create strategy configs in a more visual manner and execute them via GRPC to allow for faster customisation of strategies |
|
||||
| Save Backtester results to database | This will allow for easier comparison of results over time |
|
||||
|
||||
51
backtester/btcli/README.md
Normal file
51
backtester/btcli/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# GoCryptoTrader Backtester: Btcli package
|
||||
|
||||
<img src="/backtester/common/backtester.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](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/btcli)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This btcli 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)
|
||||
|
||||
## Btcli overview
|
||||
|
||||
This folder contains the GoCryptoTrader Backtester CMD CLI application. It can be used to interact
|
||||
with the GoCryptoTrader Backtester's GRPC server and send commands to be processed server-side.
|
||||
|
||||
For a list of commands, you can run the following
|
||||
|
||||
```
|
||||
go run .
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
|
||||
252
backtester/btcli/commands.go
Normal file
252
backtester/btcli/commands.go
Normal file
@@ -0,0 +1,252 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/btrpc"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/config"
|
||||
"github.com/urfave/cli/v2"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
var executeStrategyFromFileCommand = &cli.Command{
|
||||
Name: "executestrategyfromfile",
|
||||
Usage: "runs the strategy from a config file",
|
||||
ArgsUsage: "<path>",
|
||||
Action: executeStrategyFromFile,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "the filepath to a strategy to execute",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func executeStrategyFromFile(c *cli.Context) error {
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowCommandHelp(c, "executestrategyfromfile")
|
||||
}
|
||||
|
||||
var path string
|
||||
if c.IsSet("path") {
|
||||
path = c.String("path")
|
||||
} else {
|
||||
path = c.Args().First()
|
||||
}
|
||||
|
||||
client := btrpc.NewBacktesterServiceClient(conn)
|
||||
result, err := client.ExecuteStrategyFromFile(
|
||||
c.Context,
|
||||
&btrpc.ExecuteStrategyFromFileRequest{
|
||||
StrategyFilePath: path,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
var executeStrategyFromConfigCommand = &cli.Command{
|
||||
Name: "executestrategyfromconfig",
|
||||
Usage: "runs the default strategy config but via passing in as a struct instead of a filepath - this is a proof-of-concept implementation",
|
||||
Description: "the cli is not a good place to manage this type of command with n variables to pass in from a command line",
|
||||
Action: executeStrategyFromConfig,
|
||||
}
|
||||
|
||||
// executeStrategyFromConfig this is a proof of concept command
|
||||
// it demonstrates that a user can send complex strategies via GRPC
|
||||
// and have them execute. The ultimate goal is to allow a user to continuously
|
||||
// tweak values and send them via GRPC and determine the best returns then test them across
|
||||
// large ranges of data to avoid over fitting
|
||||
func executeStrategyFromConfig(c *cli.Context) error {
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
defaultPath := filepath.Join(
|
||||
"..",
|
||||
"config",
|
||||
"strategyexamples",
|
||||
"ftx-cash-carry.strat")
|
||||
defaultConfig, err := config.ReadStrategyConfigFromFile(defaultPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
customSettings := make([]*btrpc.CustomSettings, len(defaultConfig.StrategySettings.CustomSettings))
|
||||
x := 0
|
||||
for k, v := range defaultConfig.StrategySettings.CustomSettings {
|
||||
customSettings[x] = &btrpc.CustomSettings{
|
||||
KeyField: k,
|
||||
KeyValue: fmt.Sprintf("%v", v),
|
||||
}
|
||||
x++
|
||||
}
|
||||
|
||||
currencySettings := make([]*btrpc.CurrencySettings, len(defaultConfig.CurrencySettings))
|
||||
for i := range defaultConfig.CurrencySettings {
|
||||
var sd *btrpc.SpotDetails
|
||||
if defaultConfig.CurrencySettings[i].SpotDetails != nil {
|
||||
sd.InitialBaseFunds = defaultConfig.CurrencySettings[i].SpotDetails.InitialBaseFunds.String()
|
||||
sd.InitialQuoteFunds = defaultConfig.CurrencySettings[i].SpotDetails.InitialQuoteFunds.String()
|
||||
}
|
||||
var fd *btrpc.FuturesDetails
|
||||
if defaultConfig.CurrencySettings[i].FuturesDetails != nil {
|
||||
fd.Leverage = &btrpc.Leverage{
|
||||
CanUseLeverage: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.CanUseLeverage,
|
||||
MaximumOrdersWithLeverageRatio: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.MaximumOrdersWithLeverageRatio.String(),
|
||||
MaximumLeverageRate: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.MaximumOrderLeverageRate.String(),
|
||||
MaximumCollateralLeverageRate: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.MaximumCollateralLeverageRate.String(),
|
||||
}
|
||||
}
|
||||
currencySettings[i] = &btrpc.CurrencySettings{
|
||||
ExchangeName: defaultConfig.CurrencySettings[i].ExchangeName,
|
||||
Asset: defaultConfig.CurrencySettings[i].Asset.String(),
|
||||
Base: defaultConfig.CurrencySettings[i].Base.String(),
|
||||
Quote: defaultConfig.CurrencySettings[i].Quote.String(),
|
||||
BuySide: &btrpc.PurchaseSide{
|
||||
MinimumSize: defaultConfig.CurrencySettings[i].BuySide.MinimumSize.String(),
|
||||
MaximumSize: defaultConfig.CurrencySettings[i].BuySide.MaximumSize.String(),
|
||||
MaximumTotal: defaultConfig.CurrencySettings[i].BuySide.MaximumTotal.String(),
|
||||
},
|
||||
SellSide: &btrpc.PurchaseSide{
|
||||
MinimumSize: defaultConfig.CurrencySettings[i].SellSide.MinimumSize.String(),
|
||||
MaximumSize: defaultConfig.CurrencySettings[i].SellSide.MaximumSize.String(),
|
||||
MaximumTotal: defaultConfig.CurrencySettings[i].SellSide.MaximumTotal.String(),
|
||||
},
|
||||
MinSlippagePercent: defaultConfig.CurrencySettings[i].MinimumSlippagePercent.String(),
|
||||
MaxSlippagePercent: defaultConfig.CurrencySettings[i].MaximumSlippagePercent.String(),
|
||||
MakerFeeOverride: defaultConfig.CurrencySettings[i].MakerFee.String(),
|
||||
TakerFeeOverride: defaultConfig.CurrencySettings[i].TakerFee.String(),
|
||||
MaximumHoldingsRatio: defaultConfig.CurrencySettings[i].MaximumHoldingsRatio.String(),
|
||||
SkipCandleVolumeFitting: defaultConfig.CurrencySettings[i].SkipCandleVolumeFitting,
|
||||
UseExchangeOrderLimits: defaultConfig.CurrencySettings[i].CanUseExchangeLimits,
|
||||
UseExchangePnlCalculation: defaultConfig.CurrencySettings[i].UseExchangePNLCalculation,
|
||||
SpotDetails: sd,
|
||||
FuturesDetails: fd,
|
||||
}
|
||||
}
|
||||
|
||||
exchangeLevelFunding := make([]*btrpc.ExchangeLevelFunding, len(defaultConfig.FundingSettings.ExchangeLevelFunding))
|
||||
for i := range defaultConfig.FundingSettings.ExchangeLevelFunding {
|
||||
exchangeLevelFunding[i] = &btrpc.ExchangeLevelFunding{
|
||||
ExchangeName: defaultConfig.FundingSettings.ExchangeLevelFunding[i].ExchangeName,
|
||||
Asset: defaultConfig.FundingSettings.ExchangeLevelFunding[i].Asset.String(),
|
||||
Currency: defaultConfig.FundingSettings.ExchangeLevelFunding[i].Currency.String(),
|
||||
InitialFunds: defaultConfig.FundingSettings.ExchangeLevelFunding[i].InitialFunds.String(),
|
||||
TransferFee: defaultConfig.FundingSettings.ExchangeLevelFunding[i].TransferFee.String(),
|
||||
}
|
||||
}
|
||||
|
||||
dataSettings := &btrpc.DataSettings{
|
||||
Interval: uint64(defaultConfig.DataSettings.Interval.Duration().Nanoseconds()),
|
||||
Datatype: defaultConfig.DataSettings.DataType,
|
||||
}
|
||||
if defaultConfig.DataSettings.APIData != nil {
|
||||
dataSettings.ApiData = &btrpc.ApiData{
|
||||
StartDate: timestamppb.New(defaultConfig.DataSettings.APIData.StartDate),
|
||||
EndDate: timestamppb.New(defaultConfig.DataSettings.APIData.EndDate),
|
||||
InclusiveEndDate: defaultConfig.DataSettings.APIData.InclusiveEndDate,
|
||||
}
|
||||
}
|
||||
if defaultConfig.DataSettings.LiveData != nil {
|
||||
dataSettings.LiveData = &btrpc.LiveData{
|
||||
ApiKeyOverride: defaultConfig.DataSettings.LiveData.APIKeyOverride,
|
||||
ApiSecretOverride: defaultConfig.DataSettings.LiveData.APISecretOverride,
|
||||
ApiClientIdOverride: defaultConfig.DataSettings.LiveData.APIClientIDOverride,
|
||||
Api_2FaOverride: defaultConfig.DataSettings.LiveData.API2FAOverride,
|
||||
ApiSubAccountOverride: defaultConfig.DataSettings.LiveData.APISubAccountOverride,
|
||||
UseRealOrders: defaultConfig.DataSettings.LiveData.RealOrders,
|
||||
}
|
||||
}
|
||||
if defaultConfig.DataSettings.CSVData != nil {
|
||||
dataSettings.CsvData = &btrpc.CSVData{
|
||||
Path: defaultConfig.DataSettings.CSVData.FullPath,
|
||||
}
|
||||
}
|
||||
if defaultConfig.DataSettings.DatabaseData != nil {
|
||||
dbConnectionDetails := &btrpc.DatabaseConnectionDetails{
|
||||
Host: defaultConfig.DataSettings.DatabaseData.Config.Host,
|
||||
Port: uint32(defaultConfig.DataSettings.DatabaseData.Config.Port),
|
||||
Password: defaultConfig.DataSettings.DatabaseData.Config.Password,
|
||||
Database: defaultConfig.DataSettings.DatabaseData.Config.Database,
|
||||
SslMode: defaultConfig.DataSettings.DatabaseData.Config.SSLMode,
|
||||
UserName: defaultConfig.DataSettings.DatabaseData.Config.Username,
|
||||
}
|
||||
dbConfig := &btrpc.DatabaseConfig{
|
||||
Config: dbConnectionDetails,
|
||||
}
|
||||
dataSettings.DatabaseData = &btrpc.DatabaseData{
|
||||
StartDate: timestamppb.New(defaultConfig.DataSettings.DatabaseData.StartDate),
|
||||
EndDate: timestamppb.New(defaultConfig.DataSettings.DatabaseData.EndDate),
|
||||
Config: dbConfig,
|
||||
Path: defaultConfig.DataSettings.DatabaseData.Path,
|
||||
InclusiveEndDate: defaultConfig.DataSettings.DatabaseData.InclusiveEndDate,
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &btrpc.Config{
|
||||
Nickname: defaultConfig.Nickname,
|
||||
Goal: defaultConfig.Goal,
|
||||
StrategySettings: &btrpc.StrategySettings{
|
||||
Name: defaultConfig.StrategySettings.Name,
|
||||
UseSimultaneousSignalProcessing: defaultConfig.StrategySettings.SimultaneousSignalProcessing,
|
||||
DisableUsdTracking: defaultConfig.StrategySettings.DisableUSDTracking,
|
||||
CustomSettings: customSettings,
|
||||
},
|
||||
FundingSettings: &btrpc.FundingSettings{
|
||||
UseExchangeLevelFunding: defaultConfig.FundingSettings.UseExchangeLevelFunding,
|
||||
ExchangeLevelFunding: exchangeLevelFunding,
|
||||
},
|
||||
CurrencySettings: currencySettings,
|
||||
DataSettings: dataSettings,
|
||||
PortfolioSettings: &btrpc.PortfolioSettings{
|
||||
Leverage: &btrpc.Leverage{
|
||||
CanUseLeverage: defaultConfig.PortfolioSettings.Leverage.CanUseLeverage,
|
||||
MaximumOrdersWithLeverageRatio: defaultConfig.PortfolioSettings.Leverage.MaximumOrdersWithLeverageRatio.String(),
|
||||
MaximumLeverageRate: defaultConfig.PortfolioSettings.Leverage.MaximumOrderLeverageRate.String(),
|
||||
MaximumCollateralLeverageRate: defaultConfig.PortfolioSettings.Leverage.MaximumCollateralLeverageRate.String(),
|
||||
},
|
||||
BuySide: &btrpc.PurchaseSide{
|
||||
MinimumSize: defaultConfig.PortfolioSettings.BuySide.MinimumSize.String(),
|
||||
MaximumSize: defaultConfig.PortfolioSettings.BuySide.MaximumSize.String(),
|
||||
MaximumTotal: defaultConfig.PortfolioSettings.BuySide.MaximumTotal.String(),
|
||||
},
|
||||
SellSide: &btrpc.PurchaseSide{
|
||||
MinimumSize: defaultConfig.PortfolioSettings.SellSide.MinimumSize.String(),
|
||||
MaximumSize: defaultConfig.PortfolioSettings.SellSide.MaximumSize.String(),
|
||||
MaximumTotal: defaultConfig.PortfolioSettings.SellSide.MaximumTotal.String(),
|
||||
},
|
||||
},
|
||||
StatisticSettings: &btrpc.StatisticSettings{
|
||||
RiskFreeRate: defaultConfig.StatisticSettings.RiskFreeRate.String(),
|
||||
},
|
||||
}
|
||||
|
||||
client := btrpc.NewBacktesterServiceClient(conn)
|
||||
result, err := client.ExecuteStrategyFromConfig(
|
||||
c.Context,
|
||||
&btrpc.ExecuteStrategyFromConfigRequest{
|
||||
Config: cfg,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
17
backtester/btcli/helpers.go
Normal file
17
backtester/btcli/helpers.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func closeConn(conn *grpc.ClientConn, cancel context.CancelFunc) {
|
||||
if err := conn.Close(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
130
backtester/btcli/main.go
Normal file
130
backtester/btcli/main.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"github.com/thrasher-corp/gocryptotrader/gctrpc/auth"
|
||||
"github.com/thrasher-corp/gocryptotrader/signaler"
|
||||
"github.com/urfave/cli/v2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultUsername = "rpcuser"
|
||||
defaultPassword = "helloImTheDefaultPassword"
|
||||
)
|
||||
|
||||
var (
|
||||
host string
|
||||
username string
|
||||
password string
|
||||
pairDelimiter string
|
||||
certPath string
|
||||
timeout time.Duration
|
||||
)
|
||||
|
||||
const defaultTimeout = time.Second * 30
|
||||
|
||||
func jsonOutput(in interface{}) {
|
||||
j, err := json.MarshalIndent(in, "", " ")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Print(string(j))
|
||||
}
|
||||
|
||||
func setupClient(c *cli.Context) (*grpc.ClientConn, context.CancelFunc, error) {
|
||||
creds, err := credentials.NewClientTLSFromFile(certPath, "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
opts := []grpc.DialOption{grpc.WithTransportCredentials(creds),
|
||||
grpc.WithPerRPCCredentials(auth.BasicAuth{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}),
|
||||
}
|
||||
|
||||
var cancel context.CancelFunc
|
||||
c.Context, cancel = context.WithTimeout(c.Context, timeout)
|
||||
conn, err := grpc.DialContext(c.Context, host, opts...)
|
||||
return conn, cancel, err
|
||||
}
|
||||
|
||||
func main() {
|
||||
version := core.Version(true)
|
||||
version = strings.Replace(version, "GoCryptoTrader", "GoCryptoTrader Backtester", 1)
|
||||
app := cli.NewApp()
|
||||
app.Name = "btcli"
|
||||
app.Version = version
|
||||
app.EnableBashCompletion = true
|
||||
app.Usage = "command line interface for managing the backtester daemon"
|
||||
app.Flags = []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "rpchost",
|
||||
Value: "localhost:9054",
|
||||
Usage: "the gRPC host to connect to",
|
||||
Destination: &host,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "rpcuser",
|
||||
Value: defaultUsername,
|
||||
Usage: "the gRPC username",
|
||||
Destination: &username,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "rpcpassword",
|
||||
Value: defaultPassword,
|
||||
Usage: "the gRPC password",
|
||||
Destination: &password,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "delimiter",
|
||||
Value: "-",
|
||||
Usage: "the default currency pair delimiter used to standardise currency pair input",
|
||||
Destination: &pairDelimiter,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "cert",
|
||||
Value: filepath.Join(common.GetDefaultDataDir(runtime.GOOS), "backtester", "tls", "cert.pem"),
|
||||
Usage: "the path to TLS cert of the gRPC server",
|
||||
Destination: &certPath,
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Value: defaultTimeout,
|
||||
Usage: "the default context timeout value for requests",
|
||||
Destination: &timeout,
|
||||
},
|
||||
}
|
||||
app.Commands = []*cli.Command{
|
||||
executeStrategyFromFileCommand,
|
||||
executeStrategyFromConfigCommand,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
// Capture cancel for interrupt
|
||||
signaler.WaitForInterrupt()
|
||||
cancel()
|
||||
fmt.Println("rpc process interrupted")
|
||||
os.Exit(1)
|
||||
}()
|
||||
|
||||
err := app.RunContext(ctx, os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
95
backtester/btrpc/README.md
Normal file
95
backtester/btrpc/README.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# GoCryptoTrader Backtester: Btrpc package
|
||||
|
||||
<img src="/backtester/common/backtester.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](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/btrpc)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This btrpc 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)
|
||||
|
||||
## Btrpc overview
|
||||
|
||||
|
||||
The GoCryptoTrader Backtester utilises gRPC for client/server interaction. Authentication is done
|
||||
by a self signed TLS cert, which only supports connections from localhost and also
|
||||
through basic authorisation specified by the users config file.
|
||||
|
||||
The GoCryptoTrader Backtester also supports a gRPC JSON proxy service for applications which can
|
||||
be toggled on or off depending on the users preference. This can be found in your config file
|
||||
under `grpcProxyEnabled` `grpcProxyListenAddress`. See `btrpc.swagger.json` for endpoint definitions
|
||||
|
||||
## Installation
|
||||
|
||||
The GoCryptoTrader Backtester requires a local installation of the Google protocol buffers
|
||||
compiler `protoc` v3.0.0 or above. Please install this via your local package
|
||||
manager or by downloading one of the releases from the official repository:
|
||||
|
||||
[protoc releases](https://github.com/protocolbuffers/protobuf/releases)
|
||||
|
||||
Then use `go install` to download the following packages:
|
||||
|
||||
```bash
|
||||
go install \
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
|
||||
google.golang.org/protobuf/cmd/protoc-gen-go \
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc
|
||||
```
|
||||
|
||||
This will place the following binaries in your `$GOBIN`;
|
||||
|
||||
* `protoc-gen-grpc-gateway`
|
||||
* `protoc-gen-openapiv2`
|
||||
* `protoc-gen-go`
|
||||
* `protoc-gen-go-grpc`
|
||||
|
||||
Make sure that your `$GOBIN` is in your `$PATH`.
|
||||
|
||||
### Linux / macOS / Windows
|
||||
|
||||
The GoCryptoTrader Backtester requires a local installation of the `buf` cli tool that tries to make Protobuf handling more easier and reliable,
|
||||
after [installation](https://docs.buf.build/installation) you'll need to run:
|
||||
|
||||
```shell
|
||||
buf mod update
|
||||
```
|
||||
|
||||
After previous command, make necessary changes to the `rpc.proto` spec file and run the generation command:
|
||||
|
||||
```shell
|
||||
buf generate
|
||||
```
|
||||
|
||||
If any changes were made, ensure that the `rpc.proto` file is formatted correctly by using `buf format -w`
|
||||
|
||||
### 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
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
|
||||
2548
backtester/btrpc/btrpc.pb.go
Normal file
2548
backtester/btrpc/btrpc.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
256
backtester/btrpc/btrpc.pb.gw.go
Normal file
256
backtester/btrpc/btrpc.pb.gw.go
Normal file
@@ -0,0 +1,256 @@
|
||||
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
|
||||
// source: btrpc.proto
|
||||
|
||||
/*
|
||||
Package btrpc is a reverse proxy.
|
||||
|
||||
It translates gRPC into RESTful JSON APIs.
|
||||
*/
|
||||
package btrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Suppress "imported and not used" errors
|
||||
var _ codes.Code
|
||||
var _ io.Reader
|
||||
var _ status.Status
|
||||
var _ = runtime.String
|
||||
var _ = utilities.NewDoubleArray
|
||||
var _ = metadata.Join
|
||||
|
||||
var (
|
||||
filter_BacktesterService_ExecuteStrategyFromFile_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
|
||||
func request_BacktesterService_ExecuteStrategyFromFile_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ExecuteStrategyFromFileRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_ExecuteStrategyFromFile_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.ExecuteStrategyFromFile(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_BacktesterService_ExecuteStrategyFromFile_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ExecuteStrategyFromFileRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_ExecuteStrategyFromFile_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.ExecuteStrategyFromFile(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_BacktesterService_ExecuteStrategyFromConfig_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
|
||||
func request_BacktesterService_ExecuteStrategyFromConfig_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ExecuteStrategyFromConfigRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_ExecuteStrategyFromConfig_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.ExecuteStrategyFromConfig(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_BacktesterService_ExecuteStrategyFromConfig_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ExecuteStrategyFromConfigRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_ExecuteStrategyFromConfig_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.ExecuteStrategyFromConfig(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterBacktesterServiceHandlerServer registers the http handlers for service BacktesterService to "mux".
|
||||
// UnaryRPC :call BacktesterServiceServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterBacktesterServiceHandlerFromEndpoint instead.
|
||||
func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server BacktesterServiceServer) error {
|
||||
|
||||
mux.Handle("GET", pattern_BacktesterService_ExecuteStrategyFromFile_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/ExecuteStrategyFromFile", runtime.WithHTTPPathPattern("/v1/executestrategyfromfile"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_BacktesterService_ExecuteStrategyFromFile_0(ctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_BacktesterService_ExecuteStrategyFromFile_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_BacktesterService_ExecuteStrategyFromConfig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/ExecuteStrategyFromConfig", runtime.WithHTTPPathPattern("/v1/executestrategyfromconfig"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_BacktesterService_ExecuteStrategyFromConfig_0(ctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_BacktesterService_ExecuteStrategyFromConfig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterBacktesterServiceHandlerFromEndpoint is same as RegisterBacktesterServiceHandler but
|
||||
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
|
||||
func RegisterBacktesterServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
|
||||
conn, err := grpc.Dial(endpoint, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
return RegisterBacktesterServiceHandler(ctx, mux, conn)
|
||||
}
|
||||
|
||||
// RegisterBacktesterServiceHandler registers the http handlers for service BacktesterService to "mux".
|
||||
// The handlers forward requests to the grpc endpoint over "conn".
|
||||
func RegisterBacktesterServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
||||
return RegisterBacktesterServiceHandlerClient(ctx, mux, NewBacktesterServiceClient(conn))
|
||||
}
|
||||
|
||||
// RegisterBacktesterServiceHandlerClient registers the http handlers for service BacktesterService
|
||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "BacktesterServiceClient".
|
||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "BacktesterServiceClient"
|
||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||
// "BacktesterServiceClient" to call the correct interceptors.
|
||||
func RegisterBacktesterServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client BacktesterServiceClient) error {
|
||||
|
||||
mux.Handle("GET", pattern_BacktesterService_ExecuteStrategyFromFile_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
ctx, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/ExecuteStrategyFromFile", runtime.WithHTTPPathPattern("/v1/executestrategyfromfile"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_BacktesterService_ExecuteStrategyFromFile_0(ctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_BacktesterService_ExecuteStrategyFromFile_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_BacktesterService_ExecuteStrategyFromConfig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
ctx, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/ExecuteStrategyFromConfig", runtime.WithHTTPPathPattern("/v1/executestrategyfromconfig"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_BacktesterService_ExecuteStrategyFromConfig_0(ctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_BacktesterService_ExecuteStrategyFromConfig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pattern_BacktesterService_ExecuteStrategyFromFile_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "executestrategyfromfile"}, ""))
|
||||
|
||||
pattern_BacktesterService_ExecuteStrategyFromConfig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "executestrategyfromconfig"}, ""))
|
||||
)
|
||||
|
||||
var (
|
||||
forward_BacktesterService_ExecuteStrategyFromFile_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_BacktesterService_ExecuteStrategyFromConfig_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
199
backtester/btrpc/btrpc.proto
Normal file
199
backtester/btrpc/btrpc.proto
Normal file
@@ -0,0 +1,199 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package btrpc;
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
option go_package = "github.com/thrasher-corp/gocryptotrader/backtester/btrpc";
|
||||
|
||||
// struct definitions
|
||||
message StrategySettings {
|
||||
string name = 1;
|
||||
bool use_simultaneous_signal_processing = 2;
|
||||
bool disable_usd_tracking = 3;
|
||||
repeated CustomSettings custom_settings = 4;
|
||||
}
|
||||
|
||||
message CustomSettings {
|
||||
string key_field = 1;
|
||||
string key_value = 2;
|
||||
}
|
||||
|
||||
message ExchangeLevelFunding {
|
||||
string exchange_name = 1;
|
||||
string asset = 2;
|
||||
string currency = 3;
|
||||
string initial_funds = 4;
|
||||
string transfer_fee = 5;
|
||||
}
|
||||
|
||||
message FundingSettings {
|
||||
bool use_exchange_level_funding = 1;
|
||||
repeated ExchangeLevelFunding exchange_level_funding = 2;
|
||||
}
|
||||
|
||||
message PurchaseSide {
|
||||
string minimum_size = 1;
|
||||
string maximum_size = 2;
|
||||
string maximum_total = 3;
|
||||
}
|
||||
|
||||
message SpotDetails {
|
||||
string initial_base_funds = 1;
|
||||
string initial_quote_funds = 2;
|
||||
}
|
||||
|
||||
message FuturesDetails {
|
||||
Leverage leverage = 1;
|
||||
}
|
||||
|
||||
message CurrencySettings {
|
||||
string exchange_name = 1;
|
||||
string asset = 2;
|
||||
string base = 3;
|
||||
string quote = 4;
|
||||
PurchaseSide buy_side = 5;
|
||||
PurchaseSide sell_side = 6;
|
||||
string min_slippage_percent = 7;
|
||||
string max_slippage_percent = 8;
|
||||
string maker_fee_override = 9;
|
||||
string taker_fee_override = 10;
|
||||
string maximum_holdings_ratio = 11;
|
||||
bool skip_candle_volume_fitting = 12;
|
||||
bool use_exchange_order_limits = 13;
|
||||
bool use_exchange_pnl_calculation = 14;
|
||||
SpotDetails spot_details = 15;
|
||||
FuturesDetails futures_details = 16;
|
||||
}
|
||||
|
||||
message ApiData {
|
||||
google.protobuf.Timestamp start_date = 1;
|
||||
google.protobuf.Timestamp end_date = 2;
|
||||
bool inclusive_end_date = 3;
|
||||
}
|
||||
|
||||
message DbConfig {
|
||||
bool enabled = 1;
|
||||
bool verbose = 2;
|
||||
string driver = 3;
|
||||
string host = 4;
|
||||
uint32 port = 5;
|
||||
string username = 6;
|
||||
string password = 7;
|
||||
string database = 8;
|
||||
string ssl_mode = 9;
|
||||
}
|
||||
|
||||
message DbData {
|
||||
google.protobuf.Timestamp start_date = 1;
|
||||
google.protobuf.Timestamp end_date = 2;
|
||||
DbConfig config = 3;
|
||||
string path = 4;
|
||||
bool inclusive_end_date = 5;
|
||||
}
|
||||
|
||||
message CsvData {
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
message DatabaseConnectionDetails {
|
||||
string host = 1;
|
||||
uint32 port = 2;
|
||||
string user_name = 3;
|
||||
string password = 4;
|
||||
string database = 5;
|
||||
string ssl_mode = 6;
|
||||
}
|
||||
|
||||
message DatabaseConfig {
|
||||
bool enabled = 1;
|
||||
bool verbose = 2;
|
||||
string driver = 3;
|
||||
DatabaseConnectionDetails config = 4;
|
||||
}
|
||||
|
||||
message DatabaseData {
|
||||
google.protobuf.Timestamp start_date = 1;
|
||||
google.protobuf.Timestamp end_date = 2;
|
||||
DatabaseConfig config = 3;
|
||||
string path = 4;
|
||||
bool inclusive_end_date = 5;
|
||||
}
|
||||
|
||||
message CSVData {
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
message LiveData {
|
||||
string api_key_override = 1;
|
||||
string api_secret_override = 2;
|
||||
string api_client_id_override = 3;
|
||||
string api_2fa_override = 4;
|
||||
string api_sub_account_override = 5;
|
||||
bool use_real_orders = 6;
|
||||
}
|
||||
|
||||
message DataSettings {
|
||||
uint64 interval = 1;
|
||||
string datatype = 2;
|
||||
ApiData api_data = 3;
|
||||
DatabaseData database_data = 4;
|
||||
CSVData csv_data = 5;
|
||||
LiveData live_data = 6;
|
||||
}
|
||||
|
||||
message Leverage {
|
||||
bool can_use_leverage = 1;
|
||||
string maximum_orders_with_leverage_ratio = 2;
|
||||
string maximum_leverage_rate = 3;
|
||||
string maximum_collateral_leverage_rate = 4;
|
||||
}
|
||||
|
||||
message PortfolioSettings {
|
||||
Leverage leverage = 1;
|
||||
PurchaseSide buy_side = 2;
|
||||
PurchaseSide sell_side = 3;
|
||||
}
|
||||
|
||||
message StatisticSettings {
|
||||
string risk_free_rate = 1;
|
||||
}
|
||||
|
||||
message Config {
|
||||
string nickname = 1;
|
||||
string goal = 2;
|
||||
StrategySettings strategy_settings = 3;
|
||||
FundingSettings funding_settings = 4;
|
||||
repeated CurrencySettings currency_settings = 5;
|
||||
DataSettings data_settings = 6;
|
||||
PortfolioSettings portfolio_settings = 7;
|
||||
StatisticSettings statistic_settings = 8;
|
||||
}
|
||||
|
||||
// Requests and responses
|
||||
message ExecuteStrategyFromFileRequest {
|
||||
string strategy_file_path = 1;
|
||||
}
|
||||
|
||||
message ExecuteStrategyResponse {
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message ExecuteStrategyFromConfigRequest {
|
||||
btrpc.Config config = 1;
|
||||
}
|
||||
|
||||
service BacktesterService {
|
||||
rpc ExecuteStrategyFromFile(ExecuteStrategyFromFileRequest) returns (ExecuteStrategyResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/executestrategyfromfile"
|
||||
};
|
||||
}
|
||||
rpc ExecuteStrategyFromConfig(ExecuteStrategyFromConfigRequest) returns (ExecuteStrategyResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/executestrategyfromconfig"
|
||||
};
|
||||
}
|
||||
}
|
||||
729
backtester/btrpc/btrpc.swagger.json
Normal file
729
backtester/btrpc/btrpc.swagger.json
Normal file
@@ -0,0 +1,729 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "btrpc.proto",
|
||||
"version": "version not set"
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "BacktesterService"
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/executestrategyfromconfig": {
|
||||
"get": {
|
||||
"operationId": "BacktesterService_ExecuteStrategyFromConfig",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/btrpcExecuteStrategyResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "config.nickname",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.goal",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.strategySettings.name",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.strategySettings.useSimultaneousSignalProcessing",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "config.strategySettings.disableUsdTracking",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "config.fundingSettings.useExchangeLevelFunding",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.interval",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.datatype",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.apiData.startDate",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.apiData.endDate",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.apiData.inclusiveEndDate",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.startDate",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.endDate",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.config.enabled",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.config.verbose",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.config.driver",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.config.config.host",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.config.config.port",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.config.config.userName",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.config.config.password",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.config.config.database",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.config.config.sslMode",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.path",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.databaseData.inclusiveEndDate",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.csvData.path",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.liveData.apiKeyOverride",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.liveData.apiSecretOverride",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.liveData.apiClientIdOverride",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.liveData.api2faOverride",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.liveData.apiSubAccountOverride",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.dataSettings.liveData.useRealOrders",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "config.portfolioSettings.leverage.canUseLeverage",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "config.portfolioSettings.leverage.maximumOrdersWithLeverageRatio",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.portfolioSettings.leverage.maximumLeverageRate",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.portfolioSettings.leverage.maximumCollateralLeverageRate",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.portfolioSettings.buySide.minimumSize",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.portfolioSettings.buySide.maximumSize",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.portfolioSettings.buySide.maximumTotal",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.portfolioSettings.sellSide.minimumSize",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.portfolioSettings.sellSide.maximumSize",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.portfolioSettings.sellSide.maximumTotal",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "config.statisticSettings.riskFreeRate",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"BacktesterService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/executestrategyfromfile": {
|
||||
"get": {
|
||||
"operationId": "BacktesterService_ExecuteStrategyFromFile",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/btrpcExecuteStrategyResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "strategyFilePath",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"BacktesterService"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"btrpcApiData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"startDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"endDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"inclusiveEndDate": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcCSVData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nickname": {
|
||||
"type": "string"
|
||||
},
|
||||
"goal": {
|
||||
"type": "string"
|
||||
},
|
||||
"strategySettings": {
|
||||
"$ref": "#/definitions/btrpcStrategySettings"
|
||||
},
|
||||
"fundingSettings": {
|
||||
"$ref": "#/definitions/btrpcFundingSettings"
|
||||
},
|
||||
"currencySettings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/btrpcCurrencySettings"
|
||||
}
|
||||
},
|
||||
"dataSettings": {
|
||||
"$ref": "#/definitions/btrpcDataSettings"
|
||||
},
|
||||
"portfolioSettings": {
|
||||
"$ref": "#/definitions/btrpcPortfolioSettings"
|
||||
},
|
||||
"statisticSettings": {
|
||||
"$ref": "#/definitions/btrpcStatisticSettings"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcCurrencySettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exchangeName": {
|
||||
"type": "string"
|
||||
},
|
||||
"asset": {
|
||||
"type": "string"
|
||||
},
|
||||
"base": {
|
||||
"type": "string"
|
||||
},
|
||||
"quote": {
|
||||
"type": "string"
|
||||
},
|
||||
"buySide": {
|
||||
"$ref": "#/definitions/btrpcPurchaseSide"
|
||||
},
|
||||
"sellSide": {
|
||||
"$ref": "#/definitions/btrpcPurchaseSide"
|
||||
},
|
||||
"minSlippagePercent": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxSlippagePercent": {
|
||||
"type": "string"
|
||||
},
|
||||
"makerFeeOverride": {
|
||||
"type": "string"
|
||||
},
|
||||
"takerFeeOverride": {
|
||||
"type": "string"
|
||||
},
|
||||
"maximumHoldingsRatio": {
|
||||
"type": "string"
|
||||
},
|
||||
"skipCandleVolumeFitting": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"useExchangeOrderLimits": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"useExchangePnlCalculation": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"spotDetails": {
|
||||
"$ref": "#/definitions/btrpcSpotDetails"
|
||||
},
|
||||
"futuresDetails": {
|
||||
"$ref": "#/definitions/btrpcFuturesDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcCustomSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"keyField": {
|
||||
"type": "string"
|
||||
},
|
||||
"keyValue": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcDataSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"interval": {
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
},
|
||||
"datatype": {
|
||||
"type": "string"
|
||||
},
|
||||
"apiData": {
|
||||
"$ref": "#/definitions/btrpcApiData"
|
||||
},
|
||||
"databaseData": {
|
||||
"$ref": "#/definitions/btrpcDatabaseData"
|
||||
},
|
||||
"csvData": {
|
||||
"$ref": "#/definitions/btrpcCSVData"
|
||||
},
|
||||
"liveData": {
|
||||
"$ref": "#/definitions/btrpcLiveData"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcDatabaseConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"verbose": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"driver": {
|
||||
"type": "string"
|
||||
},
|
||||
"config": {
|
||||
"$ref": "#/definitions/btrpcDatabaseConnectionDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcDatabaseConnectionDetails": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"userName": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"sslMode": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcDatabaseData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"startDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"endDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"config": {
|
||||
"$ref": "#/definitions/btrpcDatabaseConfig"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"inclusiveEndDate": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcExchangeLevelFunding": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exchangeName": {
|
||||
"type": "string"
|
||||
},
|
||||
"asset": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"initialFunds": {
|
||||
"type": "string"
|
||||
},
|
||||
"transferFee": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcExecuteStrategyResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcFundingSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"useExchangeLevelFunding": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"exchangeLevelFunding": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/btrpcExchangeLevelFunding"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcFuturesDetails": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"leverage": {
|
||||
"$ref": "#/definitions/btrpcLeverage"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcLeverage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"canUseLeverage": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"maximumOrdersWithLeverageRatio": {
|
||||
"type": "string"
|
||||
},
|
||||
"maximumLeverageRate": {
|
||||
"type": "string"
|
||||
},
|
||||
"maximumCollateralLeverageRate": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcLiveData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"apiKeyOverride": {
|
||||
"type": "string"
|
||||
},
|
||||
"apiSecretOverride": {
|
||||
"type": "string"
|
||||
},
|
||||
"apiClientIdOverride": {
|
||||
"type": "string"
|
||||
},
|
||||
"api2faOverride": {
|
||||
"type": "string"
|
||||
},
|
||||
"apiSubAccountOverride": {
|
||||
"type": "string"
|
||||
},
|
||||
"useRealOrders": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcPortfolioSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"leverage": {
|
||||
"$ref": "#/definitions/btrpcLeverage"
|
||||
},
|
||||
"buySide": {
|
||||
"$ref": "#/definitions/btrpcPurchaseSide"
|
||||
},
|
||||
"sellSide": {
|
||||
"$ref": "#/definitions/btrpcPurchaseSide"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcPurchaseSide": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"minimumSize": {
|
||||
"type": "string"
|
||||
},
|
||||
"maximumSize": {
|
||||
"type": "string"
|
||||
},
|
||||
"maximumTotal": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcSpotDetails": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"initialBaseFunds": {
|
||||
"type": "string"
|
||||
},
|
||||
"initialQuoteFunds": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcStatisticSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"riskFreeRate": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"btrpcStrategySettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"useSimultaneousSignalProcessing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableUsdTracking": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"customSettings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/btrpcCustomSettings"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "struct definitions"
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@type": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {}
|
||||
},
|
||||
"rpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
141
backtester/btrpc/btrpc_grpc.pb.go
Normal file
141
backtester/btrpc/btrpc_grpc.pb.go
Normal file
@@ -0,0 +1,141 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc (unknown)
|
||||
// source: btrpc.proto
|
||||
|
||||
package btrpc
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// BacktesterServiceClient is the client API for BacktesterService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type BacktesterServiceClient interface {
|
||||
ExecuteStrategyFromFile(ctx context.Context, in *ExecuteStrategyFromFileRequest, opts ...grpc.CallOption) (*ExecuteStrategyResponse, error)
|
||||
ExecuteStrategyFromConfig(ctx context.Context, in *ExecuteStrategyFromConfigRequest, opts ...grpc.CallOption) (*ExecuteStrategyResponse, error)
|
||||
}
|
||||
|
||||
type backtesterServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewBacktesterServiceClient(cc grpc.ClientConnInterface) BacktesterServiceClient {
|
||||
return &backtesterServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *backtesterServiceClient) ExecuteStrategyFromFile(ctx context.Context, in *ExecuteStrategyFromFileRequest, opts ...grpc.CallOption) (*ExecuteStrategyResponse, error) {
|
||||
out := new(ExecuteStrategyResponse)
|
||||
err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/ExecuteStrategyFromFile", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *backtesterServiceClient) ExecuteStrategyFromConfig(ctx context.Context, in *ExecuteStrategyFromConfigRequest, opts ...grpc.CallOption) (*ExecuteStrategyResponse, error) {
|
||||
out := new(ExecuteStrategyResponse)
|
||||
err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/ExecuteStrategyFromConfig", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// BacktesterServiceServer is the server API for BacktesterService service.
|
||||
// All implementations must embed UnimplementedBacktesterServiceServer
|
||||
// for forward compatibility
|
||||
type BacktesterServiceServer interface {
|
||||
ExecuteStrategyFromFile(context.Context, *ExecuteStrategyFromFileRequest) (*ExecuteStrategyResponse, error)
|
||||
ExecuteStrategyFromConfig(context.Context, *ExecuteStrategyFromConfigRequest) (*ExecuteStrategyResponse, error)
|
||||
mustEmbedUnimplementedBacktesterServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedBacktesterServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedBacktesterServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedBacktesterServiceServer) ExecuteStrategyFromFile(context.Context, *ExecuteStrategyFromFileRequest) (*ExecuteStrategyResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ExecuteStrategyFromFile not implemented")
|
||||
}
|
||||
func (UnimplementedBacktesterServiceServer) ExecuteStrategyFromConfig(context.Context, *ExecuteStrategyFromConfigRequest) (*ExecuteStrategyResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ExecuteStrategyFromConfig not implemented")
|
||||
}
|
||||
func (UnimplementedBacktesterServiceServer) mustEmbedUnimplementedBacktesterServiceServer() {}
|
||||
|
||||
// UnsafeBacktesterServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to BacktesterServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeBacktesterServiceServer interface {
|
||||
mustEmbedUnimplementedBacktesterServiceServer()
|
||||
}
|
||||
|
||||
func RegisterBacktesterServiceServer(s grpc.ServiceRegistrar, srv BacktesterServiceServer) {
|
||||
s.RegisterService(&BacktesterService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _BacktesterService_ExecuteStrategyFromFile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ExecuteStrategyFromFileRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BacktesterServiceServer).ExecuteStrategyFromFile(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/btrpc.BacktesterService/ExecuteStrategyFromFile",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BacktesterServiceServer).ExecuteStrategyFromFile(ctx, req.(*ExecuteStrategyFromFileRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _BacktesterService_ExecuteStrategyFromConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ExecuteStrategyFromConfigRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BacktesterServiceServer).ExecuteStrategyFromConfig(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/btrpc.BacktesterService/ExecuteStrategyFromConfig",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BacktesterServiceServer).ExecuteStrategyFromConfig(ctx, req.(*ExecuteStrategyFromConfigRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// BacktesterService_ServiceDesc is the grpc.ServiceDesc for BacktesterService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var BacktesterService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "btrpc.BacktesterService",
|
||||
HandlerType: (*BacktesterServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "ExecuteStrategyFromFile",
|
||||
Handler: _BacktesterService_ExecuteStrategyFromFile_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ExecuteStrategyFromConfig",
|
||||
Handler: _BacktesterService_ExecuteStrategyFromConfig_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "btrpc.proto",
|
||||
}
|
||||
17
backtester/btrpc/buf.gen.yaml
Normal file
17
backtester/btrpc/buf.gen.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
version: v1
|
||||
plugins:
|
||||
- name: go
|
||||
out: ./
|
||||
opt:
|
||||
- paths=source_relative
|
||||
- name: go-grpc
|
||||
out: ./
|
||||
opt:
|
||||
- paths=source_relative
|
||||
- name: grpc-gateway
|
||||
out: ./
|
||||
opt:
|
||||
- paths=source_relative
|
||||
- generate_unbound_methods=true
|
||||
- name: openapiv2
|
||||
out: ./
|
||||
11
backtester/btrpc/buf.lock
Normal file
11
backtester/btrpc/buf.lock
Normal file
@@ -0,0 +1,11 @@
|
||||
# Generated by buf. DO NOT EDIT.
|
||||
version: v1
|
||||
deps:
|
||||
- remote: buf.build
|
||||
owner: googleapis
|
||||
repository: googleapis
|
||||
commit: 62f35d8aed1149c291d606d958a7ce32
|
||||
- remote: buf.build
|
||||
owner: grpc-ecosystem
|
||||
repository: grpc-gateway
|
||||
commit: bc28b723cd774c32b6fbc77621518765
|
||||
17
backtester/btrpc/buf.yaml
Normal file
17
backtester/btrpc/buf.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
version: v1
|
||||
name: buf.build/gocryptotrader/btrpc
|
||||
lint:
|
||||
use:
|
||||
- DEFAULT
|
||||
except:
|
||||
- RPC_REQUEST_RESPONSE_UNIQUE
|
||||
- PACKAGE_DIRECTORY_MATCH
|
||||
- PACKAGE_VERSION_SUFFIX
|
||||
- RPC_RESPONSE_STANDARD_NAME
|
||||
- RPC_REQUEST_STANDARD_NAME
|
||||
breaking:
|
||||
use:
|
||||
- FILE
|
||||
deps:
|
||||
- buf.build/googleapis/googleapis
|
||||
- buf.build/grpc-ecosystem/grpc-gateway
|
||||
@@ -134,43 +134,90 @@ func RegisterBacktesterSubLoggers() error {
|
||||
|
||||
// PurgeColours removes colour information
|
||||
func PurgeColours() {
|
||||
ColourGreen = ""
|
||||
ColourWhite = ""
|
||||
ColourGrey = ""
|
||||
ColourDefault = ""
|
||||
ColourH1 = ""
|
||||
ColourH2 = ""
|
||||
ColourH3 = ""
|
||||
ColourH4 = ""
|
||||
ColourSuccess = ""
|
||||
ColourInfo = ""
|
||||
ColourDebug = ""
|
||||
ColourWarn = ""
|
||||
ColourDarkGrey = ""
|
||||
ColourError = ""
|
||||
CMDColours.Green = ""
|
||||
CMDColours.White = ""
|
||||
CMDColours.Grey = ""
|
||||
CMDColours.Default = ""
|
||||
CMDColours.H1 = ""
|
||||
CMDColours.H2 = ""
|
||||
CMDColours.H3 = ""
|
||||
CMDColours.H4 = ""
|
||||
CMDColours.Success = ""
|
||||
CMDColours.Info = ""
|
||||
CMDColours.Debug = ""
|
||||
CMDColours.Warn = ""
|
||||
CMDColours.DarkGrey = ""
|
||||
CMDColours.Error = ""
|
||||
}
|
||||
|
||||
// SetColours sets cmd output colours at startup. Doing it at any other point
|
||||
// risks races and this really isn't worth adding a mutex for
|
||||
func SetColours(colours *Colours) {
|
||||
if colours.Default != "" && colours.Default != CMDColours.Default {
|
||||
CMDColours.Default = colours.Default
|
||||
}
|
||||
if colours.Green != "" && colours.Green != CMDColours.Green {
|
||||
CMDColours.Green = colours.Green
|
||||
}
|
||||
if colours.Error != "" && colours.Error != CMDColours.Error {
|
||||
CMDColours.Error = colours.Error
|
||||
}
|
||||
if colours.White != "" && colours.White != CMDColours.White {
|
||||
CMDColours.White = colours.White
|
||||
}
|
||||
if colours.Grey != "" && colours.Grey != CMDColours.Grey {
|
||||
CMDColours.Grey = colours.Grey
|
||||
}
|
||||
if colours.H1 != "" && colours.H1 != CMDColours.H1 {
|
||||
CMDColours.H1 = colours.H1
|
||||
}
|
||||
if colours.H2 != "" && colours.H2 != CMDColours.H2 {
|
||||
CMDColours.H2 = colours.H2
|
||||
}
|
||||
if colours.H3 != "" && colours.H3 != CMDColours.H3 {
|
||||
CMDColours.H3 = colours.H3
|
||||
}
|
||||
if colours.H4 != "" && colours.H4 != CMDColours.H4 {
|
||||
CMDColours.H4 = colours.H4
|
||||
}
|
||||
if colours.Success != "" && colours.Error != CMDColours.Success {
|
||||
CMDColours.Success = colours.Success
|
||||
}
|
||||
if colours.Info != "" && colours.Info != CMDColours.Info {
|
||||
CMDColours.Info = colours.Info
|
||||
}
|
||||
if colours.Debug != "" && colours.Debug != CMDColours.Debug {
|
||||
CMDColours.Debug = colours.Debug
|
||||
}
|
||||
if colours.Warn != "" && colours.Warn != CMDColours.Warn {
|
||||
CMDColours.Warn = colours.Warn
|
||||
}
|
||||
if colours.DarkGrey != "" && colours.DarkGrey != CMDColours.DarkGrey {
|
||||
CMDColours.DarkGrey = colours.DarkGrey
|
||||
}
|
||||
}
|
||||
|
||||
// Logo returns the logo
|
||||
func Logo() string {
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString(" \n")
|
||||
sb.WriteString(" " + ColourWhite + "@@@@@@@@@@@@@@@@@ \n")
|
||||
sb.WriteString(" " + ColourWhite + "@@@@@@@@@@@@@@@@@@@@@@@ " + ColourGrey + ",,,,,," + ColourWhite + " \n")
|
||||
sb.WriteString(" " + ColourWhite + "@@@@@@@@" + ColourGrey + ",,,,, " + ColourWhite + "@@@@@@@@@" + ColourGrey + ",,,,,,,," + ColourWhite + " \n")
|
||||
sb.WriteString(" " + ColourWhite + "@@@@@@@@" + ColourGrey + ",,,,,,, " + ColourWhite + "@@@@@@@" + ColourGrey + ",,,,,,," + ColourWhite + " \n")
|
||||
sb.WriteString(" " + ColourWhite + "@@@@@@" + ColourGrey + "(,,,,,,,, " + ColourGrey + ",," + ColourWhite + "@@@@@@@" + ColourGrey + ",,,,,," + ColourWhite + " \n")
|
||||
sb.WriteString(" " + ColourGrey + ",," + ColourWhite + "@@@@@@" + ColourGrey + ",,,,,,,,, #,,,,,,,,,,,,,,,,,," + ColourWhite + " \n")
|
||||
sb.WriteString(" " + ColourGrey + ",,,,*" + ColourWhite + "@@@@@@" + ColourGrey + ",,,,,,,,,,,,,,,,,,,,,,,,,," + ColourGreen + "%%%%%%%" + ColourWhite + " \n")
|
||||
sb.WriteString(" " + ColourGrey + ",,,,,,,*" + ColourWhite + "@@@@@@" + ColourGrey + ",,,,,,,,,,,,,," + ColourGreen + "%%%%%" + ColourGrey + " ,,,,,," + ColourGrey + "%" + ColourGreen + "%%%%%%" + ColourWhite + " \n")
|
||||
sb.WriteString(" " + ColourGrey + ",,,,,,,,*" + ColourWhite + "@@@@@@" + ColourGrey + ",,,,,,,,,,," + ColourGreen + "%%%%%%%%%%%%%%%%%%" + ColourGrey + "#" + ColourGreen + "%%" + ColourGrey + " \n")
|
||||
sb.WriteString(" " + ColourGrey + ",,,,,,*" + ColourWhite + "@@@@@@" + ColourGrey + ",,,,,,,,," + ColourGreen + "%%%" + ColourGrey + " ,,,,," + ColourGreen + "%%%%%%%%" + ColourGrey + ",,,,, \n")
|
||||
sb.WriteString(" " + ColourGrey + ",,,*" + ColourWhite + "@@@@@@" + ColourGrey + ",,,,,," + ColourGreen + "%%" + ColourGrey + ",, ,,,,,,," + ColourWhite + "@" + ColourGreen + "*%%," + ColourWhite + "@" + ColourGrey + ",,,,,, \n")
|
||||
sb.WriteString(" " + ColourGrey + "*" + ColourWhite + "@@@@@@" + ColourGrey + ",,,,,,,,, " + ColourGrey + ",,,,," + ColourWhite + "@@@@@@" + ColourGrey + ",,,,,," + ColourWhite + " \n")
|
||||
sb.WriteString(" " + ColourWhite + "@@@@@@" + ColourGrey + ",,,,,,,,, " + ColourWhite + "@@@@@@@" + ColourGrey + ",,,,,," + ColourWhite + " \n")
|
||||
sb.WriteString(" " + ColourWhite + "@@@@@@@@" + ColourGrey + ",,,,,,, " + ColourWhite + "@@@@@@@" + ColourGrey + ",,,,,,," + ColourWhite + " \n")
|
||||
sb.WriteString(" " + ColourWhite + "@@@@@@@@@" + ColourGrey + ",,,, " + ColourWhite + "@@@@@@@@@" + ColourGrey + "#,,,,,,," + ColourWhite + " \n")
|
||||
sb.WriteString(" " + ColourWhite + "@@@@@@@@@@@@@@@@@@@@@@@ " + ColourGrey + "*,,,," + ColourWhite + " \n")
|
||||
sb.WriteString(" " + ColourWhite + "@@@@@@@@@@@@@@@@" + ColourDefault + " \n")
|
||||
sb.WriteString(" " + CMDColours.White + "@@@@@@@@@@@@@@@@@ \n")
|
||||
sb.WriteString(" " + CMDColours.White + "@@@@@@@@@@@@@@@@@@@@@@@ " + CMDColours.Grey + ",,,,,," + CMDColours.White + " \n")
|
||||
sb.WriteString(" " + CMDColours.White + "@@@@@@@@" + CMDColours.Grey + ",,,,, " + CMDColours.White + "@@@@@@@@@" + CMDColours.Grey + ",,,,,,,," + CMDColours.White + " \n")
|
||||
sb.WriteString(" " + CMDColours.White + "@@@@@@@@" + CMDColours.Grey + ",,,,,,, " + CMDColours.White + "@@@@@@@" + CMDColours.Grey + ",,,,,,," + CMDColours.White + " \n")
|
||||
sb.WriteString(" " + CMDColours.White + "@@@@@@" + CMDColours.Grey + "(,,,,,,,, " + CMDColours.Grey + ",," + CMDColours.White + "@@@@@@@" + CMDColours.Grey + ",,,,,," + CMDColours.White + " \n")
|
||||
sb.WriteString(" " + CMDColours.Grey + ",," + CMDColours.White + "@@@@@@" + CMDColours.Grey + ",,,,,,,,, #,,,,,,,,,,,,,,,,,," + CMDColours.White + " \n")
|
||||
sb.WriteString(" " + CMDColours.Grey + ",,,,*" + CMDColours.White + "@@@@@@" + CMDColours.Grey + ",,,,,,,,,,,,,,,,,,,,,,,,,," + CMDColours.Green + "%%%%%%%" + CMDColours.White + " \n")
|
||||
sb.WriteString(" " + CMDColours.Grey + ",,,,,,,*" + CMDColours.White + "@@@@@@" + CMDColours.Grey + ",,,,,,,,,,,,,," + CMDColours.Green + "%%%%%" + CMDColours.Grey + " ,,,,,," + CMDColours.Grey + "%" + CMDColours.Green + "%%%%%%" + CMDColours.White + " \n")
|
||||
sb.WriteString(" " + CMDColours.Grey + ",,,,,,,,*" + CMDColours.White + "@@@@@@" + CMDColours.Grey + ",,,,,,,,,,," + CMDColours.Green + "%%%%%%%%%%%%%%%%%%" + CMDColours.Grey + "#" + CMDColours.Green + "%%" + CMDColours.Grey + " \n")
|
||||
sb.WriteString(" " + CMDColours.Grey + ",,,,,,*" + CMDColours.White + "@@@@@@" + CMDColours.Grey + ",,,,,,,,," + CMDColours.Green + "%%%" + CMDColours.Grey + " ,,,,," + CMDColours.Green + "%%%%%%%%" + CMDColours.Grey + ",,,,, \n")
|
||||
sb.WriteString(" " + CMDColours.Grey + ",,,*" + CMDColours.White + "@@@@@@" + CMDColours.Grey + ",,,,,," + CMDColours.Green + "%%" + CMDColours.Grey + ",, ,,,,,,," + CMDColours.White + "@" + CMDColours.Green + "*%%," + CMDColours.White + "@" + CMDColours.Grey + ",,,,,, \n")
|
||||
sb.WriteString(" " + CMDColours.Grey + "*" + CMDColours.White + "@@@@@@" + CMDColours.Grey + ",,,,,,,,, " + CMDColours.Grey + ",,,,," + CMDColours.White + "@@@@@@" + CMDColours.Grey + ",,,,,," + CMDColours.White + " \n")
|
||||
sb.WriteString(" " + CMDColours.White + "@@@@@@" + CMDColours.Grey + ",,,,,,,,, " + CMDColours.White + "@@@@@@@" + CMDColours.Grey + ",,,,,," + CMDColours.White + " \n")
|
||||
sb.WriteString(" " + CMDColours.White + "@@@@@@@@" + CMDColours.Grey + ",,,,,,, " + CMDColours.White + "@@@@@@@" + CMDColours.Grey + ",,,,,,," + CMDColours.White + " \n")
|
||||
sb.WriteString(" " + CMDColours.White + "@@@@@@@@@" + CMDColours.Grey + ",,,, " + CMDColours.White + "@@@@@@@@@" + CMDColours.Grey + "#,,,,,,," + CMDColours.White + " \n")
|
||||
sb.WriteString(" " + CMDColours.White + "@@@@@@@@@@@@@@@@@@@@@@@ " + CMDColours.Grey + "*,,,," + CMDColours.White + " \n")
|
||||
sb.WriteString(" " + CMDColours.White + "@@@@@@@@@@@@@@@@" + CMDColours.Default + " \n")
|
||||
sb.WriteString(ASCIILogo)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ func TestLogo(t *testing.T) {
|
||||
|
||||
func TestPurgeColours(t *testing.T) {
|
||||
PurgeColours()
|
||||
if ColourSuccess != "" {
|
||||
if CMDColours.Success != "" {
|
||||
t.Error("expected purged colour")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ var (
|
||||
ErrNilEvent = errors.New("nil event received")
|
||||
// ErrInvalidDataType occurs when an invalid data type is defined in the config
|
||||
ErrInvalidDataType = errors.New("invalid datatype received")
|
||||
// ErrFileNotFound returned when the file is not found
|
||||
ErrFileNotFound = errors.New("file not found")
|
||||
|
||||
errCannotGenerateFileName = errors.New("cannot generate filename")
|
||||
)
|
||||
@@ -89,23 +91,41 @@ type Directioner interface {
|
||||
GetDirection() order.Side
|
||||
}
|
||||
|
||||
// colours to display for the terminal output
|
||||
var (
|
||||
ColourDefault = "\u001b[0m"
|
||||
ColourGreen = "\033[38;5;157m"
|
||||
ColourWhite = "\033[38;5;255m"
|
||||
ColourGrey = "\033[38;5;240m"
|
||||
ColourDarkGrey = "\033[38;5;243m"
|
||||
ColourH1 = "\033[38;5;33m"
|
||||
ColourH2 = "\033[38;5;39m"
|
||||
ColourH3 = "\033[38;5;45m"
|
||||
ColourH4 = "\033[38;5;51m"
|
||||
ColourSuccess = "\033[38;5;40m"
|
||||
ColourInfo = "\u001B[32m"
|
||||
ColourDebug = "\u001B[34m"
|
||||
ColourWarn = "\u001B[33m"
|
||||
ColourError = "\033[38;5;196m"
|
||||
)
|
||||
// Colours defines colour types for CMD output
|
||||
type Colours struct {
|
||||
Default string
|
||||
Green string
|
||||
White string
|
||||
Grey string
|
||||
DarkGrey string
|
||||
H1 string
|
||||
H2 string
|
||||
H3 string
|
||||
H4 string
|
||||
Success string
|
||||
Info string
|
||||
Debug string
|
||||
Warn string
|
||||
Error string
|
||||
}
|
||||
|
||||
// CMDColours holds colour information for CMD output
|
||||
var CMDColours = Colours{
|
||||
Default: "\u001b[0m",
|
||||
Green: "\033[38;5;157m",
|
||||
White: "\033[38;5;255m",
|
||||
Grey: "\033[38;5;240m",
|
||||
DarkGrey: "\033[38;5;243m",
|
||||
H1: "\033[38;5;33m",
|
||||
H2: "\033[38;5;39m",
|
||||
H3: "\033[38;5;45m",
|
||||
H4: "\033[38;5;51m",
|
||||
Success: "\033[38;5;40m",
|
||||
Info: "\u001B[32m",
|
||||
Debug: "\u001B[34m",
|
||||
Warn: "\u001B[33m",
|
||||
Error: "\033[38;5;196m",
|
||||
}
|
||||
|
||||
// ASCIILogo is a sweet logo that is optionally printed to the command line window
|
||||
const ASCIILogo = `
|
||||
|
||||
@@ -20,6 +20,65 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
|
||||
## Config package overview
|
||||
|
||||
## Backtester Config overview
|
||||
Below are the details for the GoCryptoTrader Backtester _application_ config. Strategy config overview is below this section
|
||||
|
||||
| Key | Description | Example |
|
||||
|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------|
|
||||
| PrintLogo | Whether to print the GoCryptoTrader Backtester logo on startup. Recommended because it looks good | `true` |
|
||||
| Verbose | Whether to receive verbose output. If running a GRPC server, it outputs to the server, not to the client | `false` |
|
||||
| LogSubheaders | Whether log output contains a descriptor of what area the log is coming from, for example `STRATEGY`. Helpful for debugging | `true` |
|
||||
| SingleRun | Whether or not to run the GoCryptoTrader Backtester to read the `SingleRunStrategyConfig` strategy and exit afterwards. If false, will run a GRPC server | `false` |
|
||||
| SingleRunStrategyConfig | The path to the strategy to run when `SingleRun` is `true` | `path\to\strategy\example.strat` |
|
||||
| Report | Contains details on the output report after a successful backtesting run | See Report table below |
|
||||
| GRPC | Contains GRPC server details | See GRPC table below |
|
||||
| UseCMDColours | If enabled, will output pretty colours of your choosing when running the application | `true` |
|
||||
| Colours | Contains details on what the colour definitions are | See Colours table below |
|
||||
|
||||
### Backtester Config Report overview
|
||||
|
||||
| Key | Description | Example |
|
||||
|----------------|----------------------------------------------------------------------|---------------------------------|
|
||||
| GenerateReport | Whether or not to output a report after a successful backtesting run | `true` |
|
||||
| TemplatePath | The path for the template to use when generating a report | `/backtester/report/tpl.gohtml` |
|
||||
| OutputPath | The path where report output is saved | `/backtester/results` |
|
||||
| DarkMode | Whether or not the report defaults to using dark mode | `true` |
|
||||
|
||||
### Backtester Config GRPC overview
|
||||
|
||||
| Key | Description | Example |
|
||||
|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
|
||||
| Username | Your username to negotiate a successful connection with the server | `rpcuser` |
|
||||
| Password | Your password to negotiate a successful connection with the server | `helloImTheDefaultPassword` |
|
||||
| Enabled | Whether the server is enabled. Setting this to `false` and `SingleRun` to `false` would be inadvisable | `true` |
|
||||
| ListenAddress | The listen address for the GRPC server | `localhost:42069` |
|
||||
| GRPCProxyEnabled | If enabled, creates a proxy server to interact with the GRPC server via HTTP commands | `true` |
|
||||
| GRPCProxyListenAddress | The address for the proxy to listen on | `localhost:9053` |
|
||||
| TLSDir | The directory for holding your TLS certifications to make connections to the server. Will be generated by default on startup if not present | `/backtester/config/location/` |
|
||||
|
||||
|
||||
### Backtester Config Colours overview
|
||||
|
||||
| Key | Description | Example |
|
||||
|----------|---------------------------------------------------------------------|----------------|
|
||||
| Default | The colour definition for default text output |`[0m` |
|
||||
| Green | The colour definition for when green is warranted, such as the logo |`[38;5;157m` |
|
||||
| White | The colour definition for when white is warranted such as the logo |`[38;5;255m` |
|
||||
| Grey | The colour definition for grey | `[38;5;240m`|
|
||||
| DarkGrey | The colour definition for dark grey | `[38;5;243m`|
|
||||
| H1 | The colour definition for main headers | `[38;5;33m` |
|
||||
| H2 | The colour definition for sub headers | `[38;5;39m` |
|
||||
| H3 | The colour definition for sub sub headers | `[38;5;45m` |
|
||||
| H4 | The colour definition for sub sub sub headers | `[38;5;51m` |
|
||||
| Success | The colour definition for successful operations | `[38;5;40m` |
|
||||
| Info | The colour definition for when informing you of something | `[32m` |
|
||||
| Debug | The colour definition for debug output such as verbose | `[34m` |
|
||||
| Warn | The colour definition for when a warning occurs | `[33m` |
|
||||
| Error | The colour definition for when an error occurs | `[38;5;196m`|
|
||||
|
||||
|
||||
## Strategy Config overview
|
||||
|
||||
### What does the config package do?
|
||||
The config package contains a set of structs which allow for the customisation of the GoCryptoTrader Backtester when running.
|
||||
The GoCryptoTrader Backtester runs from reading config files (`.strat` files by default under `/examples`).
|
||||
@@ -38,172 +97,172 @@ See below for a set of tables and fields, expected values and what they can do
|
||||
|
||||
#### Config
|
||||
|
||||
| Key | Description |
|
||||
| --- | ------|
|
||||
| Nickname | A nickname for the specific config. When running multiple variants of the same strategy, use the nickname to help differentiate between runs |
|
||||
| Goal | A description of what you would hope the outcome to be. When verifying output, you can review and confirm whether the strategy met that goal |
|
||||
| CurrencySettings | Currency settings is an array of settings for each individual currency you wish to run the strategy against |
|
||||
| StrategySettings | Select which strategy to run, what custom settings to load and whether the strategy can assess multiple currencies at once to make more in-depth decisions |
|
||||
| FundingSettings | Defines whether individual funding settings can be used. Defines the funding exchange, asset, currencies at an individual level |
|
||||
| Key | Description |
|
||||
|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Nickname | A nickname for the specific config. When running multiple variants of the same strategy, use the nickname to help differentiate between runs |
|
||||
| Goal | A description of what you would hope the outcome to be. When verifying output, you can review and confirm whether the strategy met that goal |
|
||||
| CurrencySettings | Currency settings is an array of settings for each individual currency you wish to run the strategy against |
|
||||
| StrategySettings | Select which strategy to run, what custom settings to load and whether the strategy can assess multiple currencies at once to make more in-depth decisions |
|
||||
| FundingSettings | Defines whether individual funding settings can be used. Defines the funding exchange, asset, currencies at an individual level |
|
||||
| PortfolioSettings | Contains a list of global rules for the portfolio manager. CurrencySettings contain their own rules on things like how big a position is allowable, the portfolio manager rules are the same, but override any individual currency's settings |
|
||||
| StatisticSettings | Contains settings that impact statistics calculation. Such as the risk-free rate for the sharpe ratio |
|
||||
| StatisticSettings | Contains settings that impact statistics calculation. Such as the risk-free rate for the sharpe ratio |
|
||||
|
||||
|
||||
#### Strategy Settings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ------- | --- |
|
||||
| Name | The strategy to use | `rsi` |
|
||||
| UsesSimultaneousProcessing | This denotes whether multiple currencies are processed simultaneously with the strategy function `OnSimultaneousSignals`. Eg If you have multiple CurrencySettings and only wish to purchase BTC-USDT when XRP-DOGE is 1337, this setting is useful as you can analyse both signal events to output a purchase call for BTC | `true` |
|
||||
| CustomSettings | This is a map where you can enter custom settings for a strategy. The RSI strategy allows for customisation of the upper, lower and length variables to allow you to change them from 70, 30 and 14 respectively to 69, 36, 12 | `"custom-settings": { "rsi-high": 70, "rsi-low": 30, "rsi-period": 14 } ` |
|
||||
| DisableUSDTracking | If `false`, will track all currencies used in your strategy against USD equivalent candles. For example, if you are running a strategy for BTC/XRP, then the GoCryptoTrader Backtester will also retreive candles data for BTC/USD and XRP/USD to then track strategy performance against a single currency. This also tracks against USDT and other USD tracked stablecoins, so one exchange supporting USDT and another BUSD will still allow unified strategy performance analysis. If disabled, will not track against USD, this can be especially helpful when running strategies under live, database and CSV based data | `false` |
|
||||
| Key | Description | Example |
|
||||
|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
|
||||
| Name | The strategy to use | `rsi` |
|
||||
| UsesSimultaneousProcessing | This denotes whether multiple currencies are processed simultaneously with the strategy function `OnSimultaneousSignals`. Eg If you have multiple CurrencySettings and only wish to purchase BTC-USDT when XRP-DOGE is 1337, this setting is useful as you can analyse both signal events to output a purchase call for BTC | `true` |
|
||||
| CustomSettings | This is a map where you can enter custom settings for a strategy. The RSI strategy allows for customisation of the upper, lower and length variables to allow you to change them from 70, 30 and 14 respectively to 69, 36, 12 | `"custom-settings": { "rsi-high": 70, "rsi-low": 30, "rsi-period": 14 } ` |
|
||||
| DisableUSDTracking | If `false`, will track all currencies used in your strategy against USD equivalent candles. For example, if you are running a strategy for BTC/XRP, then the GoCryptoTrader Backtester will also retreive candles data for BTC/USD and XRP/USD to then track strategy performance against a single currency. This also tracks against USDT and other USD tracked stablecoins, so one exchange supporting USDT and another BUSD will still allow unified strategy performance analysis. If disabled, will not track against USD, this can be especially helpful when running strategies under live, database and CSV based data | `false` |
|
||||
|
||||
|
||||
#### Funding Config Settings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ------- | --- |
|
||||
| Key | Description | Example |
|
||||
|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
|
||||
| UseExchangeLevelFunding | Allows shared funding at an exchange asset level. You can set funding for `USDT` and all pairs that feature `USDT` will have access to those funds when making orders. See [this](/backtester/funding/README.md) for more information | `false` |
|
||||
| ExchangeLevelFunding | An array of exchange level funding settings. See below, or [this](/backtester/funding/README.md) for more information | `[]` |
|
||||
| ExchangeLevelFunding | An array of exchange level funding settings. See below, or [this](/backtester/funding/README.md) for more information | `[]` |
|
||||
|
||||
|
||||
##### Funding Item Config Settings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ------- | ----- |
|
||||
| ExchangeName | The exchange to set funds. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` |
|
||||
| Asset | The asset type to set funds. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports| `spot` |
|
||||
| Currency | The currency to set funds | `BTC` |
|
||||
| InitialFunds | The initial funding for the currency | `1337` |
|
||||
| TransferFee | If your strategy utilises transferring of funds via the Funding Manager, this is deducted upon doing so | `0.005` |
|
||||
| Key | Description | Example |
|
||||
|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|
|
||||
| ExchangeName | The exchange to set funds. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` |
|
||||
| Asset | The asset type to set funds. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` |
|
||||
| Currency | The currency to set funds | `BTC` |
|
||||
| InitialFunds | The initial funding for the currency | `1337` |
|
||||
| TransferFee | If your strategy utilises transferring of funds via the Funding Manager, this is deducted upon doing so | `0.005` |
|
||||
|
||||
|
||||
#### Currency Settings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ------- | ----- |
|
||||
| ExchangeName | The exchange to load. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` |
|
||||
| Asset | The asset type. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports| `spot` |
|
||||
| Base | The base of a currency | `BTC` |
|
||||
| Quote | The quote of a currency | `USDT` |
|
||||
| InitialFunds | A legacy field, will be temporarily migrated to `InitialQuoteFunds` if present in your strat config | `` |
|
||||
| BuySide | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount | - |
|
||||
| SellSide | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount | - |
|
||||
| MinimumSlippagePercent | Is the lower bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 90, then the most a price can be affected is 10% | `90` |
|
||||
| MaximumSlippagePercent | Is the upper bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 99, then the least a price can be affected is 1%. Set both upper and lower to 100 to have no randomness applied to purchase events | `100` |
|
||||
| MakerFee | The fee to use when sizing and purchasing currency. If `nil`, will lookup an exchange's fee details | `0.001` |
|
||||
| TakerFee | Unused fee for when an order is placed in the orderbook, rather than taken from the orderbook. If `nil`, will lookup an exchange's fee details | `0.002` |
|
||||
| MaximumHoldingsRatio | When multiple currency settings are used, you may set a maximum holdings ratio to prevent having too large a stake in a single currency | `0.5` |
|
||||
| CanUseExchangeLimits | Will lookup exchange rules around purchase sizing eg minimum order increments of 0.0005. Note: Will retrieve up-to-date rules which may not have existed for the data you are using. Best to use this when considering to use this strategy live | `false` |
|
||||
| SkipCandleVolumeFitting | When placing orders, by default the BackTester will shrink an order's size to fit the candle data's volume so as to not rewrite history. Set this to `true` to ignore this and to set order size at what the portfolio manager prescribes | `false` |
|
||||
| SpotSettings | An optional field which contains initial funding data for SPOT currency pairs | See SpotSettings table below |
|
||||
| FuturesSettings | An optional field which contains leverage data for FUTURES currency pairs | See FuturesSettings table below |
|
||||
| Key | Description | Example |
|
||||
|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------|
|
||||
| ExchangeName | The exchange to load. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` |
|
||||
| Asset | The asset type. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` |
|
||||
| Base | The base of a currency | `BTC` |
|
||||
| Quote | The quote of a currency | `USDT` |
|
||||
| InitialFunds | A legacy field, will be temporarily migrated to `InitialQuoteFunds` if present in your strat config | `` |
|
||||
| BuySide | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount | - |
|
||||
| SellSide | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount | - |
|
||||
| MinimumSlippagePercent | Is the lower bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 90, then the most a price can be affected is 10% | `90` |
|
||||
| MaximumSlippagePercent | Is the upper bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 99, then the least a price can be affected is 1%. Set both upper and lower to 100 to have no randomness applied to purchase events | `100` |
|
||||
| MakerFee | The fee to use when sizing and purchasing currency. If `nil`, will lookup an exchange's fee details | `0.001` |
|
||||
| TakerFee | Unused fee for when an order is placed in the orderbook, rather than taken from the orderbook. If `nil`, will lookup an exchange's fee details | `0.002` |
|
||||
| MaximumHoldingsRatio | When multiple currency settings are used, you may set a maximum holdings ratio to prevent having too large a stake in a single currency | `0.5` |
|
||||
| CanUseExchangeLimits | Will lookup exchange rules around purchase sizing eg minimum order increments of 0.0005. Note: Will retrieve up-to-date rules which may not have existed for the data you are using. Best to use this when considering to use this strategy live | `false` |
|
||||
| SkipCandleVolumeFitting | When placing orders, by default the BackTester will shrink an order's size to fit the candle data's volume so as to not rewrite history. Set this to `true` to ignore this and to set order size at what the portfolio manager prescribes | `false` |
|
||||
| SpotSettings | An optional field which contains initial funding data for SPOT currency pairs | See SpotSettings table below |
|
||||
| FuturesSettings | An optional field which contains leverage data for FUTURES currency pairs | See FuturesSettings table below |
|
||||
|
||||
##### SpotSettings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ------- | ----- |
|
||||
| InitialBaseFunds | The funds that the GoCryptoTraderBacktester has for the base currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `2` |
|
||||
| Key | Description | Example |
|
||||
|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
|
||||
| InitialBaseFunds | The funds that the GoCryptoTraderBacktester has for the base currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `2` |
|
||||
| InitialQuoteFunds | The funds that the GoCryptoTraderBacktester has for the quote currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `10000` |
|
||||
|
||||
##### FuturesSettings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ------- | ----- |
|
||||
| Leverage | This struct defines the leverage rules that this specific currency setting must abide by | `1` |
|
||||
| Key | Description | Example |
|
||||
|----------|------------------------------------------------------------------------------------------|---------|
|
||||
| Leverage | This struct defines the leverage rules that this specific currency setting must abide by | `1` |
|
||||
|
||||
#### PortfolioSettings
|
||||
|
||||
| Key | Description |
|
||||
| --- | ------- |
|
||||
| Leverage | This struct defines the leverage rules that this specific currency setting must abide by |
|
||||
| BuySide | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount |
|
||||
| Key | Description |
|
||||
|----------|------------------------------------------------------------------------------------------------------------------------|
|
||||
| Leverage | This struct defines the leverage rules that this specific currency setting must abide by |
|
||||
| BuySide | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount |
|
||||
| SellSide | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount |
|
||||
|
||||
#### StatisticsSettings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| RiskFreeRate | The risk free rate used in the calculation of sharpe and sortino ratios | `0.03` |
|
||||
| Key | Description | Example |
|
||||
|--------------|-------------------------------------------------------------------------|---------|
|
||||
| RiskFreeRate | The risk free rate used in the calculation of sharpe and sortino ratios | `0.03` |
|
||||
|
||||
#### APIData
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| StartDate | The start date to retrieve data | `2021-01-23T11:00:00+11:00` |
|
||||
| EndDate | The end date to retrieve data | `2021-01-24T11:00:00+11:00` |
|
||||
| InclusiveEndDate | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` |
|
||||
| Key | Description | Example |
|
||||
|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| StartDate | The start date to retrieve data | `2021-01-23T11:00:00+11:00` |
|
||||
| EndDate | The end date to retrieve data | `2021-01-24T11:00:00+11:00` |
|
||||
| InclusiveEndDate | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` |
|
||||
|
||||
#### CSVData
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `candle` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| FullPath | The file to load | `/data/exchangelist.csv` |
|
||||
| Key | Description | Example |
|
||||
|----------|--------------------------------------------------------------------------------------------------------|--------------------------|
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `candle` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| FullPath | The file to load | `/data/exchangelist.csv` |
|
||||
|
||||
#### DatabaseData
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| StartDate | The start date to retrieve data | `2021-01-23T11:00:00+11:00` |
|
||||
| EndDate | The end date to retrieve data | `2021-01-24T11:00:00+11:00` |
|
||||
| Config | This is the same struct used as your GoCryptoTrader database config. See below tables for breakdown | `see below` |
|
||||
| Path | If using SQLite, the path to the directory, not the file. Leaving blank will use GoCryptoTrader's default database path | `` |
|
||||
| InclusiveEndDate | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` |
|
||||
| Key | Description | Example |
|
||||
|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| StartDate | The start date to retrieve data | `2021-01-23T11:00:00+11:00` |
|
||||
| EndDate | The end date to retrieve data | `2021-01-24T11:00:00+11:00` |
|
||||
| Config | This is the same struct used as your GoCryptoTrader database config. See below tables for breakdown | `see below` |
|
||||
| Path | If using SQLite, the path to the directory, not the file. Leaving blank will use GoCryptoTrader's default database path | `` |
|
||||
| InclusiveEndDate | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` |
|
||||
|
||||
##### database
|
||||
|
||||
| Config | Description | Example |
|
||||
| ------ | ----------- | ------- |
|
||||
| enabled | Enabled or disables the database connection subsystem | `true` |
|
||||
| verbose | Displays more information to the logger which can be helpful for debugging | `false` |
|
||||
| driver | The SQL driver to use. Can be `postgres` or `sqlite` | `sqlite` |
|
||||
| connectionDetails | See below | |
|
||||
| Config | Description | Example |
|
||||
|-------------------|----------------------------------------------------------------------------|----------|
|
||||
| enabled | Enabled or disables the database connection subsystem | `true` |
|
||||
| verbose | Displays more information to the logger which can be helpful for debugging | `false` |
|
||||
| driver | The SQL driver to use. Can be `postgres` or `sqlite` | `sqlite` |
|
||||
| connectionDetails | See below | |
|
||||
|
||||
##### connectionDetails
|
||||
|
||||
| Config | Description | Example |
|
||||
| ------ | ----------- | ------- |
|
||||
| host | The host address of the database | `localhost` |
|
||||
| port | The port used to connect to the database | `5432` |
|
||||
| username | An optional username to connect to the database | `username` |
|
||||
| password | An optional password to connect to the database | `password` |
|
||||
| database | The name of the database | `database.db` |
|
||||
| sslmode | The connection type of the database for Postgres databases only | `disable` |
|
||||
| Config | Description | Example |
|
||||
|----------|-----------------------------------------------------------------|---------------|
|
||||
| host | The host address of the database | `localhost` |
|
||||
| port | The port used to connect to the database | `5432` |
|
||||
| username | An optional username to connect to the database | `username` |
|
||||
| password | An optional password to connect to the database | `password` |
|
||||
| database | The name of the database | `database.db` |
|
||||
| sslmode | The connection type of the database for Postgres databases only | `disable` |
|
||||
|
||||
#### LiveData
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `candle` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| APIKeyOverride | Will set the GoCryptoTrader exchange to use the following API Key | `1234` |
|
||||
| APISecretOverride | Will set the GoCryptoTrader exchange to use the following API Secret | `5678` |
|
||||
| APIClientIDOverride | Will set the GoCryptoTrader exchange to use the following API Client ID | `9012` |
|
||||
| API2FAOverride | Will set the GoCryptoTrader exchange to use the following 2FA seed | `hello-moto` |
|
||||
| APISubaccountOverride | Will set the GoCryptoTrader exchange to use the following subaccount on supported exchanges | `subzero` |
|
||||
| RealOrders | Whether to place real orders. You really should never consider using this. Ever ever | `true` |
|
||||
| Key | Description | Example |
|
||||
|-----------------------|--------------------------------------------------------------------------------------------------------|---------------|
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `candle` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| APIKeyOverride | Will set the GoCryptoTrader exchange to use the following API Key | `1234` |
|
||||
| APISecretOverride | Will set the GoCryptoTrader exchange to use the following API Secret | `5678` |
|
||||
| APIClientIDOverride | Will set the GoCryptoTrader exchange to use the following API Client ID | `9012` |
|
||||
| API2FAOverride | Will set the GoCryptoTrader exchange to use the following 2FA seed | `hello-moto` |
|
||||
| APISubaccountOverride | Will set the GoCryptoTrader exchange to use the following subaccount on supported exchanges | `subzero` |
|
||||
| RealOrders | Whether to place real orders. You really should never consider using this. Ever ever | `true` |
|
||||
|
||||
##### Leverage Settings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| CanUseLeverage | Allows the use of leverage | `false` |
|
||||
| MaximumOrdersWithLeverageRatio | If the ratio of leveraged orders for a currency exceeds this, the order cannot be placed | `0.5` |
|
||||
| MaximumLeverageRate | Orders cannot be placed with leverage over this amount | `100` |
|
||||
| Key | Description | Example |
|
||||
|--------------------------------|------------------------------------------------------------------------------------------|---------|
|
||||
| CanUseLeverage | Allows the use of leverage | `false` |
|
||||
| MaximumOrdersWithLeverageRatio | If the ratio of leveraged orders for a currency exceeds this, the order cannot be placed | `0.5` |
|
||||
| MaximumLeverageRate | Orders cannot be placed with leverage over this amount | `100` |
|
||||
|
||||
##### Buy/Sell Settings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| MinimumSize | If the order's quantity is below this, the order cannot be placed | `0.1` |
|
||||
| MaximumSize | If the order's quantity is over this amount, it cannot be placed and will be reduced to the maximum amount | `10` |
|
||||
| MaximumTotal | If the order's price * amount exceeds this number, the order cannot be placed and will be reduced to this figure | `1337` |
|
||||
| Key | Description | Example |
|
||||
|--------------|------------------------------------------------------------------------------------------------------------------|---------|
|
||||
| MinimumSize | If the order's quantity is below this, the order cannot be placed | `0.1` |
|
||||
| MaximumSize | If the order's quantity is over this amount, it cannot be placed and will be reduced to the maximum amount | `10` |
|
||||
| MaximumTotal | If the order's price * amount exceeds this number, the order cannot be placed and will be reduced to this figure | `1337` |
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
|
||||
|
||||
71
backtester/config/backtesterconfig.go
Normal file
71
backtester/config/backtesterconfig.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/file"
|
||||
gctconfig "github.com/thrasher-corp/gocryptotrader/config"
|
||||
)
|
||||
|
||||
// ReadBacktesterConfigFromPath will take a config from a path
|
||||
func ReadBacktesterConfigFromPath(path string) (*BacktesterConfig, error) {
|
||||
if !file.Exists(path) {
|
||||
return nil, fmt.Errorf("%w %v", common.ErrFileNotFound, path)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp *BacktesterConfig
|
||||
err = json.Unmarshal(data, &resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// GenerateDefaultConfig will return the default backtester config
|
||||
func GenerateDefaultConfig() (*BacktesterConfig, error) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BacktesterConfig{
|
||||
PrintLogo: true,
|
||||
LogSubheaders: true,
|
||||
Report: Report{
|
||||
GenerateReport: true,
|
||||
TemplatePath: filepath.Join(wd, "report", "tpl.gohtml"),
|
||||
OutputPath: filepath.Join(wd, "results"),
|
||||
},
|
||||
GRPC: GRPC{
|
||||
Username: "rpcuser",
|
||||
Password: "helloImTheDefaultPassword",
|
||||
GRPCConfig: gctconfig.GRPCConfig{
|
||||
Enabled: true,
|
||||
ListenAddress: "localhost:9054",
|
||||
},
|
||||
TLSDir: DefaultBTDir,
|
||||
},
|
||||
UseCMDColours: true,
|
||||
Colours: common.Colours{
|
||||
Default: common.CMDColours.Default,
|
||||
Green: common.CMDColours.Green,
|
||||
White: common.CMDColours.White,
|
||||
Grey: common.CMDColours.Grey,
|
||||
DarkGrey: common.CMDColours.DarkGrey,
|
||||
H1: common.CMDColours.H1,
|
||||
H2: common.CMDColours.H2,
|
||||
H3: common.CMDColours.H3,
|
||||
H4: common.CMDColours.H4,
|
||||
Success: common.CMDColours.Success,
|
||||
Info: common.CMDColours.Info,
|
||||
Debug: common.CMDColours.Debug,
|
||||
Warn: common.CMDColours.Warn,
|
||||
Error: common.CMDColours.Error,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
45
backtester/config/backtesterconfig_types.go
Normal file
45
backtester/config/backtesterconfig_types.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/common"
|
||||
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
|
||||
gctconfig "github.com/thrasher-corp/gocryptotrader/config"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultBTDir is the default backtester config directory
|
||||
DefaultBTDir = filepath.Join(gctcommon.GetDefaultDataDir(runtime.GOOS), "backtester")
|
||||
// DefaultBTConfigDir is the default backtester config file
|
||||
DefaultBTConfigDir = filepath.Join(DefaultBTDir, "config.json")
|
||||
)
|
||||
|
||||
// BacktesterConfig contains the configuration for the backtester
|
||||
type BacktesterConfig struct {
|
||||
PluginPath string `json:"plugin-path"`
|
||||
PrintLogo bool `json:"print-logo"`
|
||||
Verbose bool `json:"verbose"`
|
||||
LogSubheaders bool `json:"log-subheaders"`
|
||||
Report Report `json:"report"`
|
||||
GRPC GRPC `json:"grpc"`
|
||||
UseCMDColours bool `json:"use-cmd-colours"`
|
||||
Colours common.Colours `json:"cmd-colours"`
|
||||
}
|
||||
|
||||
// Report contains the report settings
|
||||
type Report struct {
|
||||
GenerateReport bool `json:"output-report"`
|
||||
TemplatePath string `json:"template-path"`
|
||||
OutputPath string `json:"output-path"`
|
||||
DarkMode bool `json:"dark-mode"`
|
||||
}
|
||||
|
||||
// GRPC holds the GRPC configuration
|
||||
type GRPC struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
gctconfig.GRPCConfig
|
||||
TLSDir string `json:"tls-dir"`
|
||||
}
|
||||
49
backtester/config/btcktesterconfig_test.go
Normal file
49
backtester/config/btcktesterconfig_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/file"
|
||||
)
|
||||
|
||||
func TestLoadBacktesterConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
cfg, err := GenerateDefaultConfig()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
testConfig, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
dir := t.TempDir()
|
||||
f := filepath.Join(dir, "test.config")
|
||||
err = file.Write(f, testConfig)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = ReadBacktesterConfigFromPath(f)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = ReadBacktesterConfigFromPath("test")
|
||||
if !errors.Is(err, common.ErrFileNotFound) {
|
||||
t.Errorf("received '%v' expected '%v'", err, common.ErrFileNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateDefaultConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
cfg, err := GenerateDefaultConfig()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !cfg.PrintLogo {
|
||||
t.Errorf("received '%v' expected '%v'", cfg.PrintLogo, true)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -17,28 +16,27 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
// ReadConfigFromFile will take a config from a path
|
||||
func ReadConfigFromFile(path string) (*Config, error) {
|
||||
// ReadStrategyConfigFromFile will take a config from a path
|
||||
func ReadStrategyConfigFromFile(path string) (*Config, error) {
|
||||
if !file.Exists(path) {
|
||||
return nil, errors.New("file not found")
|
||||
return nil, fmt.Errorf("%w %v", common.ErrFileNotFound, path)
|
||||
}
|
||||
|
||||
fileData, err := os.ReadFile(path)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return LoadConfig(fileData)
|
||||
}
|
||||
|
||||
// LoadConfig unmarshalls byte data into a config struct
|
||||
func LoadConfig(data []byte) (resp *Config, err error) {
|
||||
var resp *Config
|
||||
err = json.Unmarshal(data, &resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Validate checks all config settings
|
||||
func (c *Config) Validate() error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("%w nil config", common.ErrNilArguments)
|
||||
}
|
||||
err := c.validateDate()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -137,23 +135,13 @@ func (c *Config) validateStrategySettings() error {
|
||||
// validateDate checks whether someone has set a date poorly in their config
|
||||
func (c *Config) validateDate() error {
|
||||
if c.DataSettings.DatabaseData != nil {
|
||||
if c.DataSettings.DatabaseData.StartDate.IsZero() ||
|
||||
c.DataSettings.DatabaseData.EndDate.IsZero() {
|
||||
return errStartEndUnset
|
||||
}
|
||||
if c.DataSettings.DatabaseData.StartDate.After(c.DataSettings.DatabaseData.EndDate) ||
|
||||
c.DataSettings.DatabaseData.StartDate.Equal(c.DataSettings.DatabaseData.EndDate) {
|
||||
return errBadDate
|
||||
if err := gctcommon.StartEndTimeCheck(c.DataSettings.DatabaseData.StartDate, c.DataSettings.DatabaseData.EndDate); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.DataSettings.APIData != nil {
|
||||
if c.DataSettings.APIData.StartDate.IsZero() ||
|
||||
c.DataSettings.APIData.EndDate.IsZero() {
|
||||
return errStartEndUnset
|
||||
}
|
||||
if c.DataSettings.APIData.StartDate.After(c.DataSettings.APIData.EndDate) ||
|
||||
c.DataSettings.APIData.StartDate.Equal(c.DataSettings.APIData.EndDate) {
|
||||
return errBadDate
|
||||
if err := gctcommon.StartEndTimeCheck(c.DataSettings.APIData.StartDate, c.DataSettings.APIData.EndDate); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -234,8 +222,8 @@ func (c *Config) validateCurrencySettings() error {
|
||||
|
||||
// PrintSetting prints relevant settings to the console for easy reading
|
||||
func (c *Config) PrintSetting() {
|
||||
log.Info(common.Config, common.ColourH1+"------------------Backtester Settings------------------------"+common.ColourDefault)
|
||||
log.Info(common.Config, common.ColourH2+"------------------Strategy Settings--------------------------"+common.ColourDefault)
|
||||
log.Info(common.Config, common.CMDColours.H1+"------------------Backtester Settings------------------------"+common.CMDColours.Default)
|
||||
log.Info(common.Config, common.CMDColours.H2+"------------------Strategy Settings--------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.Config, "Strategy: %s", c.StrategySettings.Name)
|
||||
if len(c.StrategySettings.CustomSettings) > 0 {
|
||||
log.Info(common.Config, "Custom strategy variables:")
|
||||
@@ -249,7 +237,7 @@ func (c *Config) PrintSetting() {
|
||||
log.Infof(common.Config, "USD value tracking: %v", !c.StrategySettings.DisableUSDTracking)
|
||||
|
||||
if c.FundingSettings.UseExchangeLevelFunding && c.StrategySettings.SimultaneousSignalProcessing {
|
||||
log.Info(common.Config, common.ColourH2+"------------------Funding Settings---------------------------"+common.ColourDefault)
|
||||
log.Info(common.Config, common.CMDColours.H2+"------------------Funding Settings---------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.Config, "Use Exchange Level Funding: %v", c.FundingSettings.UseExchangeLevelFunding)
|
||||
for i := range c.FundingSettings.ExchangeLevelFunding {
|
||||
log.Infof(common.Config, "Initial funds for %v %v %v: %v",
|
||||
@@ -261,7 +249,7 @@ func (c *Config) PrintSetting() {
|
||||
}
|
||||
|
||||
for i := range c.CurrencySettings {
|
||||
currStr := fmt.Sprintf(common.ColourH2+"------------------%v %v-%v Currency Settings---------------------------------------------------------"+common.ColourDefault,
|
||||
currStr := fmt.Sprintf(common.CMDColours.H2+"------------------%v %v-%v Currency Settings---------------------------------------------------------"+common.CMDColours.Default,
|
||||
c.CurrencySettings[i].Asset,
|
||||
c.CurrencySettings[i].Base,
|
||||
c.CurrencySettings[i].Quote)
|
||||
@@ -303,32 +291,32 @@ func (c *Config) PrintSetting() {
|
||||
log.Infof(common.Config, "Can use exchange defined order execution limits: %+v", c.CurrencySettings[i].CanUseExchangeLimits)
|
||||
}
|
||||
|
||||
log.Info(common.Config, common.ColourH2+"------------------Portfolio Settings-------------------------"+common.ColourDefault)
|
||||
log.Info(common.Config, common.CMDColours.H2+"------------------Portfolio Settings-------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.Config, "Buy rules: %+v", c.PortfolioSettings.BuySide)
|
||||
log.Infof(common.Config, "Sell rules: %+v", c.PortfolioSettings.SellSide)
|
||||
log.Infof(common.Config, "Leverage rules: %+v", c.PortfolioSettings.Leverage)
|
||||
if c.DataSettings.LiveData != nil {
|
||||
log.Info(common.Config, common.ColourH2+"------------------Live Settings------------------------------"+common.ColourDefault)
|
||||
log.Info(common.Config, common.CMDColours.H2+"------------------Live Settings------------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.Config, "Data type: %v", c.DataSettings.DataType)
|
||||
log.Infof(common.Config, "Interval: %v", c.DataSettings.Interval)
|
||||
log.Infof(common.Config, "REAL ORDERS: %v", c.DataSettings.LiveData.RealOrders)
|
||||
log.Infof(common.Config, "Overriding GCT API settings: %v", c.DataSettings.LiveData.APIClientIDOverride != "")
|
||||
}
|
||||
if c.DataSettings.APIData != nil {
|
||||
log.Info(common.Config, common.ColourH2+"------------------API Settings-------------------------------"+common.ColourDefault)
|
||||
log.Info(common.Config, common.CMDColours.H2+"------------------API Settings-------------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.Config, "Data type: %v", c.DataSettings.DataType)
|
||||
log.Infof(common.Config, "Interval: %v", c.DataSettings.Interval)
|
||||
log.Infof(common.Config, "Start date: %v", c.DataSettings.APIData.StartDate.Format(gctcommon.SimpleTimeFormat))
|
||||
log.Infof(common.Config, "End date: %v", c.DataSettings.APIData.EndDate.Format(gctcommon.SimpleTimeFormat))
|
||||
}
|
||||
if c.DataSettings.CSVData != nil {
|
||||
log.Info(common.Config, common.ColourH2+"------------------CSV Settings-------------------------------"+common.ColourDefault)
|
||||
log.Info(common.Config, common.CMDColours.H2+"------------------CSV Settings-------------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.Config, "Data type: %v", c.DataSettings.DataType)
|
||||
log.Infof(common.Config, "Interval: %v", c.DataSettings.Interval)
|
||||
log.Infof(common.Config, "CSV file: %v", c.DataSettings.CSVData.FullPath)
|
||||
}
|
||||
if c.DataSettings.DatabaseData != nil {
|
||||
log.Info(common.Config, common.ColourH2+"------------------Database Settings--------------------------"+common.ColourDefault)
|
||||
log.Info(common.Config, common.CMDColours.H2+"------------------Database Settings--------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.Config, "Data type: %v", c.DataSettings.DataType)
|
||||
log.Infof(common.Config, "Interval: %v", c.DataSettings.Interval)
|
||||
log.Infof(common.Config, "Start date: %v", c.DataSettings.DatabaseData.StartDate.Format(gctcommon.SimpleTimeFormat))
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/top2bottom2"
|
||||
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/file"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/database"
|
||||
@@ -53,14 +54,6 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestLoadConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := LoadConfig([]byte(`{}`))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDate(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := Config{}
|
||||
@@ -72,14 +65,14 @@ func TestValidateDate(t *testing.T) {
|
||||
DatabaseData: &DatabaseData{},
|
||||
}
|
||||
err = c.validateDate()
|
||||
if !errors.Is(err, errStartEndUnset) {
|
||||
t.Errorf("received: %v, expected: %v", err, errStartEndUnset)
|
||||
if !errors.Is(err, gctcommon.ErrDateUnset) {
|
||||
t.Errorf("received: %v, expected: %v", err, gctcommon.ErrDateUnset)
|
||||
}
|
||||
c.DataSettings.DatabaseData.StartDate = time.Now()
|
||||
c.DataSettings.DatabaseData.EndDate = c.DataSettings.DatabaseData.StartDate
|
||||
err = c.validateDate()
|
||||
if !errors.Is(err, errBadDate) {
|
||||
t.Errorf("received: %v, expected: %v", err, errBadDate)
|
||||
if !errors.Is(err, gctcommon.ErrStartEqualsEnd) {
|
||||
t.Errorf("received: %v, expected: %v", err, gctcommon.ErrStartEqualsEnd)
|
||||
}
|
||||
c.DataSettings.DatabaseData.EndDate = c.DataSettings.DatabaseData.StartDate.Add(time.Minute)
|
||||
err = c.validateDate()
|
||||
@@ -88,14 +81,14 @@ func TestValidateDate(t *testing.T) {
|
||||
}
|
||||
c.DataSettings.APIData = &APIData{}
|
||||
err = c.validateDate()
|
||||
if !errors.Is(err, errStartEndUnset) {
|
||||
t.Errorf("received: %v, expected: %v", err, errStartEndUnset)
|
||||
if !errors.Is(err, gctcommon.ErrDateUnset) {
|
||||
t.Errorf("received: %v, expected: %v", err, gctcommon.ErrDateUnset)
|
||||
}
|
||||
c.DataSettings.APIData.StartDate = time.Now()
|
||||
c.DataSettings.APIData.EndDate = c.DataSettings.APIData.StartDate
|
||||
err = c.validateDate()
|
||||
if !errors.Is(err, errBadDate) {
|
||||
t.Errorf("received: %v, expected: %v", err, errBadDate)
|
||||
if !errors.Is(err, gctcommon.ErrStartEqualsEnd) {
|
||||
t.Errorf("received: %v, expected: %v", err, gctcommon.ErrStartEqualsEnd)
|
||||
}
|
||||
c.DataSettings.APIData.EndDate = c.DataSettings.APIData.StartDate.Add(time.Minute)
|
||||
err = c.validateDate()
|
||||
@@ -137,85 +130,6 @@ func TestValidateCurrencySettings(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
c.CurrencySettings[0].Asset = asset.PerpetualSwap
|
||||
err = c.validateCurrencySettings()
|
||||
if !errors.Is(err, errPerpetualsUnsupported) {
|
||||
t.Errorf("received: %v, expected: %v", err, errPerpetualsUnsupported)
|
||||
}
|
||||
|
||||
c.CurrencySettings[0].Asset = asset.Futures
|
||||
c.CurrencySettings[0].Quote = currency.NewCode("PERP")
|
||||
err = c.validateCurrencySettings()
|
||||
if !errors.Is(err, errPerpetualsUnsupported) {
|
||||
t.Errorf("received: %v, expected: %v", err, errPerpetualsUnsupported)
|
||||
}
|
||||
|
||||
c.CurrencySettings[0].MinimumSlippagePercent = decimal.NewFromInt(2)
|
||||
c.CurrencySettings[0].MaximumSlippagePercent = decimal.NewFromInt(3)
|
||||
c.CurrencySettings[0].Quote = currency.NewCode("USD")
|
||||
err = c.validateCurrencySettings()
|
||||
if !errors.Is(err, errFeatureIncompatible) {
|
||||
t.Errorf("received: %v, expected: %v", err, errFeatureIncompatible)
|
||||
}
|
||||
|
||||
c.CurrencySettings[0].Asset = asset.Spot
|
||||
c.CurrencySettings[0].MinimumSlippagePercent = decimal.NewFromInt(-1)
|
||||
err = c.validateCurrencySettings()
|
||||
if !errors.Is(err, errBadSlippageRates) {
|
||||
t.Errorf("received: %v, expected: %v", err, errBadSlippageRates)
|
||||
}
|
||||
c.CurrencySettings[0].MinimumSlippagePercent = decimal.NewFromInt(2)
|
||||
c.CurrencySettings[0].MaximumSlippagePercent = decimal.NewFromInt(-1)
|
||||
err = c.validateCurrencySettings()
|
||||
if !errors.Is(err, errBadSlippageRates) {
|
||||
t.Errorf("received: %v, expected: %v", err, errBadSlippageRates)
|
||||
}
|
||||
c.CurrencySettings[0].MinimumSlippagePercent = decimal.NewFromInt(2)
|
||||
c.CurrencySettings[0].MaximumSlippagePercent = decimal.NewFromInt(1)
|
||||
err = c.validateCurrencySettings()
|
||||
if !errors.Is(err, errBadSlippageRates) {
|
||||
t.Errorf("received: %v, expected: %v", err, errBadSlippageRates)
|
||||
}
|
||||
|
||||
c.CurrencySettings[0].SpotDetails = &SpotDetails{}
|
||||
err = c.validateCurrencySettings()
|
||||
if !errors.Is(err, errBadInitialFunds) {
|
||||
t.Errorf("received: %v, expected: %v", err, errBadInitialFunds)
|
||||
}
|
||||
|
||||
z := decimal.Zero
|
||||
c.CurrencySettings[0].SpotDetails.InitialQuoteFunds = &z
|
||||
c.CurrencySettings[0].SpotDetails.InitialBaseFunds = &z
|
||||
err = c.validateCurrencySettings()
|
||||
if !errors.Is(err, errBadInitialFunds) {
|
||||
t.Errorf("received: %v, expected: %v", err, errBadInitialFunds)
|
||||
}
|
||||
|
||||
c.CurrencySettings[0].SpotDetails.InitialQuoteFunds = &leet
|
||||
c.FundingSettings.UseExchangeLevelFunding = true
|
||||
err = c.validateCurrencySettings()
|
||||
if !errors.Is(err, errBadInitialFunds) {
|
||||
t.Errorf("received: %v, expected: %v", err, errBadInitialFunds)
|
||||
}
|
||||
|
||||
c.CurrencySettings[0].SpotDetails.InitialQuoteFunds = &z
|
||||
c.CurrencySettings[0].SpotDetails.InitialBaseFunds = &leet
|
||||
c.FundingSettings.UseExchangeLevelFunding = true
|
||||
err = c.validateCurrencySettings()
|
||||
if !errors.Is(err, errBadInitialFunds) {
|
||||
t.Errorf("received: %v, expected: %v", err, errBadInitialFunds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMinMaxes(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := &Config{}
|
||||
err := c.validateMinMaxes()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
c.CurrencySettings = []CurrencySettings{
|
||||
{
|
||||
SellSide: MinMax{
|
||||
@@ -450,12 +364,19 @@ func TestValidate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := c.Validate(); !errors.Is(err, nil) {
|
||||
err := c.Validate()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received %v expected %v", err, nil)
|
||||
}
|
||||
|
||||
c = nil
|
||||
err = c.Validate()
|
||||
if !errors.Is(err, common.ErrNilArguments) {
|
||||
t.Errorf("received %v expected %v", err, common.ErrNilArguments)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadConfigFromFile(t *testing.T) {
|
||||
func TestReadStrategyConfigFromFile(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
passFile, err := os.CreateTemp(tempDir, "*.start")
|
||||
if err != nil {
|
||||
@@ -469,10 +390,15 @@ func TestReadConfigFromFile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = ReadConfigFromFile(passFile.Name())
|
||||
_, err = ReadStrategyConfigFromFile(passFile.Name())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = ReadStrategyConfigFromFile("test")
|
||||
if !errors.Is(err, common.ErrFileNotFound) {
|
||||
t.Errorf("received '%v' expected '%v'", err, common.ErrFileNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateConfigForDCAAPICandles(t *testing.T) {
|
||||
@@ -11,15 +11,12 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
)
|
||||
|
||||
// Errors for config validation
|
||||
var (
|
||||
errBadDate = errors.New("start date >= end date, please check your config")
|
||||
errNoCurrencySettings = errors.New("no currency settings set in the config")
|
||||
errBadInitialFunds = errors.New("initial funds set with invalid data, please check your config")
|
||||
errUnsetExchange = errors.New("exchange name unset for currency settings, please check your config")
|
||||
errUnsetCurrency = errors.New("currency unset for currency settings, please check your config")
|
||||
errBadSlippageRates = errors.New("invalid slippage rates in currency settings, please check your config")
|
||||
errStartEndUnset = errors.New("data start and end dates are invalid, please check your config")
|
||||
errSimultaneousProcessingRequired = errors.New("exchange level funding requires simultaneous processing, please check your config and view funding readme for details")
|
||||
errExchangeLevelFundingRequired = errors.New("invalid config, funding details set while exchange level funding is disabled")
|
||||
errExchangeLevelFundingDataRequired = errors.New("invalid config, exchange level funding enabled with no funding data set")
|
||||
@@ -126,7 +126,7 @@ func main() {
|
||||
}
|
||||
fn, err = common.GenerateFileName(fn, extension)
|
||||
if err != nil {
|
||||
log.Printf("could not write file, please try again. err: %v", err)
|
||||
log.Printf("Could not write file, please try again. err: %v", err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Enter output file. If blank, will default to \"%v\"\n", fn)
|
||||
@@ -134,14 +134,14 @@ func main() {
|
||||
if parsedFileName != "" {
|
||||
fn, err = common.GenerateFileName(parsedFileName, extension)
|
||||
if err != nil {
|
||||
log.Printf("could not write file, please try again. err: %v", err)
|
||||
log.Printf("Could not write file, please try again. err: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
fp = filepath.Join(wd, fn)
|
||||
err = os.WriteFile(fp, resp, file.DefaultPermissionOctal)
|
||||
if err != nil {
|
||||
log.Printf("could not write file, please try again. err: %v", err)
|
||||
log.Printf("Could not write file, please try again. err: %v", err)
|
||||
continue
|
||||
}
|
||||
break
|
||||
@@ -38,7 +38,7 @@ func LoadData(startDate, endDate time.Time, interval time.Duration, exchangeName
|
||||
resp.Item = klineItem
|
||||
for i := range klineItem.Candles {
|
||||
if klineItem.Candles[i].ValidationIssues != "" {
|
||||
log.Warnf(common.Data, "candle validation issue for %v %v %v: %v", klineItem.Exchange, klineItem.Asset, klineItem.Pair, klineItem.Candles[i].ValidationIssues)
|
||||
log.Warnf(common.Data, "Candle validation issue for %v %v %v: %v", klineItem.Exchange, klineItem.Asset, klineItem.Pair, klineItem.Candles[i].ValidationIssues)
|
||||
}
|
||||
}
|
||||
case common.DataTrade:
|
||||
|
||||
@@ -95,7 +95,7 @@ func (d *DataFromKline) AppendResults(ki *gctkline.Item) {
|
||||
d.RangeHolder.Ranges[i].Intervals[j].HasData = true
|
||||
}
|
||||
}
|
||||
log.Debugf(common.Data, "appending %v candle intervals: %v", len(gctCandles), candleTimes)
|
||||
log.Debugf(common.Data, "Appending %v candle intervals: %v", len(gctCandles), candleTimes)
|
||||
d.AppendStream(klineData...)
|
||||
d.SortStream()
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func (d *DataFromKline) StreamOpen() []decimal.Decimal {
|
||||
if val, ok := s[x].(*kline.Kline); ok {
|
||||
ret[x] = val.Open
|
||||
} else {
|
||||
log.Errorf(common.Data, "incorrect data loaded into stream")
|
||||
log.Errorf(common.Data, "Incorrect data loaded into stream")
|
||||
}
|
||||
}
|
||||
return ret
|
||||
@@ -126,7 +126,7 @@ func (d *DataFromKline) StreamHigh() []decimal.Decimal {
|
||||
if val, ok := s[x].(*kline.Kline); ok {
|
||||
ret[x] = val.High
|
||||
} else {
|
||||
log.Errorf(common.Data, "incorrect data loaded into stream")
|
||||
log.Errorf(common.Data, "Incorrect data loaded into stream")
|
||||
}
|
||||
}
|
||||
return ret
|
||||
@@ -142,7 +142,7 @@ func (d *DataFromKline) StreamLow() []decimal.Decimal {
|
||||
if val, ok := s[x].(*kline.Kline); ok {
|
||||
ret[x] = val.Low
|
||||
} else {
|
||||
log.Errorf(common.Data, "incorrect data loaded into stream")
|
||||
log.Errorf(common.Data, "Incorrect data loaded into stream")
|
||||
}
|
||||
}
|
||||
return ret
|
||||
@@ -158,7 +158,7 @@ func (d *DataFromKline) StreamClose() []decimal.Decimal {
|
||||
if val, ok := s[x].(*kline.Kline); ok {
|
||||
ret[x] = val.Close
|
||||
} else {
|
||||
log.Errorf(common.Data, "incorrect data loaded into stream")
|
||||
log.Errorf(common.Data, "Incorrect data loaded into stream")
|
||||
}
|
||||
}
|
||||
return ret
|
||||
@@ -174,7 +174,7 @@ func (d *DataFromKline) StreamVol() []decimal.Decimal {
|
||||
if val, ok := s[x].(*kline.Kline); ok {
|
||||
ret[x] = val.Volume
|
||||
} else {
|
||||
log.Errorf(common.Data, "incorrect data loaded into stream")
|
||||
log.Errorf(common.Data, "Incorrect data loaded into stream")
|
||||
}
|
||||
}
|
||||
return ret
|
||||
|
||||
@@ -49,7 +49,7 @@ func (bt *BackTest) Reset() {
|
||||
// Run will iterate over loaded data events
|
||||
// save them and then handle the event based on its type
|
||||
func (bt *BackTest) Run() {
|
||||
log.Info(common.Backtester, "running backtester against pre-defined data")
|
||||
log.Info(common.Backtester, "Running backtester against pre-defined data")
|
||||
dataLoadingIssue:
|
||||
for ev := bt.EventQueue.NextEvent(); ; ev = bt.EventQueue.NextEvent() {
|
||||
if ev == nil {
|
||||
|
||||
50
backtester/engine/backtest.md
Normal file
50
backtester/engine/backtest.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# GoCryptoTrader Backtester: Backtest package
|
||||
|
||||
<img src="/backtester/common/backtester.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](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/engine/backtest)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This backtest 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)
|
||||
|
||||
## Backtest package overview
|
||||
|
||||
The backtest package is responsible for handling all events. It is the engine which combines all elements.
|
||||
Data is converted into candles which are then analysed via the strategyhandler. From there, events can be passed through to other handlers such as the portfolio handler to determine whether or not to place an order
|
||||
|
||||
|
||||
A flow of the application is as follows:
|
||||

|
||||
|
||||
|
||||
### 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
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
|
||||
@@ -2,13 +2,11 @@ package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/data"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/data/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/eventholder"
|
||||
@@ -18,7 +16,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/size"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics"
|
||||
"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/event"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/fill"
|
||||
@@ -27,13 +24,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/funding"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/report"
|
||||
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/database"
|
||||
"github.com/thrasher-corp/gocryptotrader/database/drivers"
|
||||
"github.com/thrasher-corp/gocryptotrader/engine"
|
||||
gctexchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ftx"
|
||||
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
@@ -62,410 +54,6 @@ func (p portfolioOverride) CreateLiquidationOrdersForExchange(ev common.DataEven
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestNewFromConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := NewFromConfig(nil, "", "", false)
|
||||
if !errors.Is(err, errNilConfig) {
|
||||
t.Errorf("received %v, expected %v", err, errNilConfig)
|
||||
}
|
||||
|
||||
cfg := &config.Config{}
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, base.ErrStrategyNotFound) {
|
||||
t.Errorf("received: %v, expected: %v", err, base.ErrStrategyNotFound)
|
||||
}
|
||||
|
||||
cfg.CurrencySettings = []config.CurrencySettings{
|
||||
{
|
||||
ExchangeName: "test",
|
||||
Base: currency.NewCode("test"),
|
||||
Quote: currency.NewCode("test"),
|
||||
},
|
||||
{
|
||||
ExchangeName: testExchange,
|
||||
Base: currency.BTC,
|
||||
Quote: currency.NewCode("0624"),
|
||||
Asset: asset.Futures,
|
||||
},
|
||||
}
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, engine.ErrExchangeNotFound) {
|
||||
t.Errorf("received: %v, expected: %v", err, engine.ErrExchangeNotFound)
|
||||
}
|
||||
cfg.CurrencySettings[0].ExchangeName = testExchange
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Errorf("received: %v, expected: %v", err, asset.ErrNotSupported)
|
||||
}
|
||||
cfg.CurrencySettings[0].Asset = asset.Spot
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, base.ErrStrategyNotFound) {
|
||||
t.Errorf("received: %v, expected: %v", err, base.ErrStrategyNotFound)
|
||||
}
|
||||
|
||||
cfg.StrategySettings = config.StrategySettings{
|
||||
Name: dollarcostaverage.Name,
|
||||
CustomSettings: map[string]interface{}{
|
||||
"hello": "moto",
|
||||
},
|
||||
}
|
||||
cfg.CurrencySettings[0].Base = currency.BTC
|
||||
cfg.CurrencySettings[0].Quote = currency.USD
|
||||
cfg.DataSettings.APIData = &config.APIData{
|
||||
StartDate: time.Time{},
|
||||
EndDate: time.Time{},
|
||||
}
|
||||
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if err != nil && !strings.Contains(err.Error(), "unrecognised dataType") {
|
||||
t.Error(err)
|
||||
}
|
||||
cfg.DataSettings.DataType = common.CandleStr
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, errIntervalUnset) {
|
||||
t.Errorf("received: %v, expected: %v", err, errIntervalUnset)
|
||||
}
|
||||
cfg.DataSettings.Interval = gctkline.OneMin
|
||||
cfg.CurrencySettings[0].MakerFee = &decimal.Zero
|
||||
cfg.CurrencySettings[0].TakerFee = &decimal.Zero
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, gctcommon.ErrDateUnset) {
|
||||
t.Errorf("received: %v, expected: %v", err, gctcommon.ErrDateUnset)
|
||||
}
|
||||
|
||||
cfg.DataSettings.APIData.StartDate = time.Now().Add(-time.Minute)
|
||||
cfg.DataSettings.APIData.EndDate = time.Now()
|
||||
cfg.DataSettings.APIData.InclusiveEndDate = true
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received: %v, expected: %v", err, nil)
|
||||
}
|
||||
|
||||
cfg.FundingSettings.UseExchangeLevelFunding = true
|
||||
cfg.FundingSettings.ExchangeLevelFunding = []config.ExchangeLevelFunding{
|
||||
{
|
||||
ExchangeName: testExchange,
|
||||
Asset: asset.Spot,
|
||||
Currency: currency.BTC,
|
||||
InitialFunds: leet,
|
||||
TransferFee: leet,
|
||||
},
|
||||
{
|
||||
ExchangeName: testExchange,
|
||||
Asset: asset.Futures,
|
||||
Currency: currency.BTC,
|
||||
InitialFunds: leet,
|
||||
TransferFee: leet,
|
||||
},
|
||||
}
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received: %v, expected: %v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDataAPI(t *testing.T) {
|
||||
t.Parallel()
|
||||
bt := BackTest{
|
||||
Reports: &report.Data{},
|
||||
}
|
||||
cp := currency.NewPair(currency.BTC, currency.USDT)
|
||||
cfg := &config.Config{
|
||||
CurrencySettings: []config.CurrencySettings{
|
||||
{
|
||||
ExchangeName: "Binance",
|
||||
Asset: asset.Spot,
|
||||
Base: cp.Base,
|
||||
Quote: cp.Quote,
|
||||
SpotDetails: &config.SpotDetails{
|
||||
InitialQuoteFunds: &leet,
|
||||
},
|
||||
BuySide: config.MinMax{},
|
||||
SellSide: config.MinMax{},
|
||||
MakerFee: &decimal.Zero,
|
||||
TakerFee: &decimal.Zero,
|
||||
},
|
||||
},
|
||||
DataSettings: config.DataSettings{
|
||||
DataType: common.CandleStr,
|
||||
Interval: gctkline.OneMin,
|
||||
APIData: &config.APIData{
|
||||
StartDate: time.Now().Add(-time.Minute),
|
||||
EndDate: time.Now(),
|
||||
}},
|
||||
StrategySettings: config.StrategySettings{
|
||||
Name: dollarcostaverage.Name,
|
||||
CustomSettings: map[string]interface{}{
|
||||
"hello": "moto",
|
||||
},
|
||||
},
|
||||
}
|
||||
em := engine.ExchangeManager{}
|
||||
exch, err := em.NewExchangeByName("Binance")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true}}
|
||||
|
||||
_, err = bt.loadData(cfg, exch, cp, asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDataDatabase(t *testing.T) {
|
||||
t.Parallel()
|
||||
bt := BackTest{
|
||||
Reports: &report.Data{},
|
||||
}
|
||||
cp := currency.NewPair(currency.BTC, currency.USDT)
|
||||
cfg := &config.Config{
|
||||
CurrencySettings: []config.CurrencySettings{
|
||||
{
|
||||
ExchangeName: "Binance",
|
||||
Asset: asset.Spot,
|
||||
Base: cp.Base,
|
||||
Quote: cp.Quote,
|
||||
SpotDetails: &config.SpotDetails{
|
||||
InitialQuoteFunds: &leet,
|
||||
},
|
||||
BuySide: config.MinMax{},
|
||||
SellSide: config.MinMax{},
|
||||
MakerFee: &decimal.Zero,
|
||||
TakerFee: &decimal.Zero,
|
||||
},
|
||||
},
|
||||
DataSettings: config.DataSettings{
|
||||
DataType: common.CandleStr,
|
||||
Interval: gctkline.OneMin,
|
||||
DatabaseData: &config.DatabaseData{
|
||||
Config: database.Config{
|
||||
Enabled: true,
|
||||
Driver: "sqlite3",
|
||||
ConnectionDetails: drivers.ConnectionDetails{
|
||||
Database: "gocryptotrader.db",
|
||||
},
|
||||
},
|
||||
StartDate: time.Now().Add(-time.Minute),
|
||||
EndDate: time.Now(),
|
||||
InclusiveEndDate: true,
|
||||
}},
|
||||
StrategySettings: config.StrategySettings{
|
||||
Name: dollarcostaverage.Name,
|
||||
CustomSettings: map[string]interface{}{
|
||||
"hello": "moto",
|
||||
},
|
||||
},
|
||||
}
|
||||
em := engine.ExchangeManager{}
|
||||
exch, err := em.NewExchangeByName("Binance")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true}}
|
||||
bt.databaseManager, err = engine.SetupDatabaseConnectionManager(&cfg.DataSettings.DatabaseData.Config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = bt.loadData(cfg, exch, cp, asset.Spot, false)
|
||||
if err != nil && !strings.Contains(err.Error(), "unable to retrieve data from GoCryptoTrader database") {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDataCSV(t *testing.T) {
|
||||
t.Parallel()
|
||||
bt := BackTest{
|
||||
Reports: &report.Data{},
|
||||
}
|
||||
cp := currency.NewPair(currency.BTC, currency.USDT)
|
||||
cfg := &config.Config{
|
||||
CurrencySettings: []config.CurrencySettings{
|
||||
{
|
||||
ExchangeName: "Binance",
|
||||
Asset: asset.Spot,
|
||||
Base: cp.Base,
|
||||
Quote: cp.Quote,
|
||||
SpotDetails: &config.SpotDetails{
|
||||
InitialQuoteFunds: &leet,
|
||||
},
|
||||
BuySide: config.MinMax{},
|
||||
SellSide: config.MinMax{},
|
||||
MakerFee: &decimal.Zero,
|
||||
TakerFee: &decimal.Zero,
|
||||
},
|
||||
},
|
||||
DataSettings: config.DataSettings{
|
||||
DataType: common.CandleStr,
|
||||
Interval: gctkline.OneMin,
|
||||
CSVData: &config.CSVData{
|
||||
FullPath: "test",
|
||||
}},
|
||||
StrategySettings: config.StrategySettings{
|
||||
Name: dollarcostaverage.Name,
|
||||
CustomSettings: map[string]interface{}{
|
||||
"hello": "moto",
|
||||
},
|
||||
},
|
||||
}
|
||||
em := engine.ExchangeManager{}
|
||||
exch, err := em.NewExchangeByName("Binance")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true}}
|
||||
_, err = bt.loadData(cfg, exch, cp, asset.Spot, false)
|
||||
if err != nil &&
|
||||
!strings.Contains(err.Error(), "The system cannot find the file specified.") &&
|
||||
!strings.Contains(err.Error(), "no such file or directory") {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDataLive(t *testing.T) {
|
||||
t.Parallel()
|
||||
bt := BackTest{
|
||||
Reports: &report.Data{},
|
||||
shutdown: make(chan struct{}),
|
||||
}
|
||||
cp := currency.NewPair(currency.BTC, currency.USDT)
|
||||
cfg := &config.Config{
|
||||
CurrencySettings: []config.CurrencySettings{
|
||||
{
|
||||
ExchangeName: "Binance",
|
||||
Asset: asset.Spot,
|
||||
Base: cp.Base,
|
||||
Quote: cp.Quote,
|
||||
SpotDetails: &config.SpotDetails{
|
||||
InitialQuoteFunds: &leet,
|
||||
},
|
||||
BuySide: config.MinMax{},
|
||||
SellSide: config.MinMax{},
|
||||
MakerFee: &decimal.Zero,
|
||||
TakerFee: &decimal.Zero,
|
||||
},
|
||||
},
|
||||
DataSettings: config.DataSettings{
|
||||
DataType: common.CandleStr,
|
||||
Interval: gctkline.OneMin,
|
||||
LiveData: &config.LiveData{
|
||||
APIKeyOverride: "test",
|
||||
APISecretOverride: "test",
|
||||
APIClientIDOverride: "test",
|
||||
API2FAOverride: "test",
|
||||
RealOrders: true,
|
||||
}},
|
||||
StrategySettings: config.StrategySettings{
|
||||
Name: dollarcostaverage.Name,
|
||||
CustomSettings: map[string]interface{}{
|
||||
"hello": "moto",
|
||||
},
|
||||
},
|
||||
}
|
||||
em := engine.ExchangeManager{}
|
||||
exch, err := em.NewExchangeByName("Binance")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true}}
|
||||
_, err = bt.loadData(cfg, exch, cp, asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
bt.Stop()
|
||||
}
|
||||
|
||||
func TestLoadLiveData(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := loadLiveData(nil, nil)
|
||||
if !errors.Is(err, common.ErrNilArguments) {
|
||||
t.Error(err)
|
||||
}
|
||||
cfg := &config.Config{}
|
||||
err = loadLiveData(cfg, nil)
|
||||
if !errors.Is(err, common.ErrNilArguments) {
|
||||
t.Error(err)
|
||||
}
|
||||
b := &gctexchange.Base{
|
||||
Name: testExchange,
|
||||
API: gctexchange.API{
|
||||
AuthenticatedSupport: false,
|
||||
AuthenticatedWebsocketSupport: false,
|
||||
PEMKeySupport: false,
|
||||
CredentialsValidator: struct {
|
||||
RequiresPEM bool
|
||||
RequiresKey bool
|
||||
RequiresSecret bool
|
||||
RequiresClientID bool
|
||||
RequiresBase64DecodeSecret bool
|
||||
}{
|
||||
RequiresPEM: true,
|
||||
RequiresKey: true,
|
||||
RequiresSecret: true,
|
||||
RequiresClientID: true,
|
||||
RequiresBase64DecodeSecret: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = loadLiveData(cfg, b)
|
||||
if !errors.Is(err, common.ErrNilArguments) {
|
||||
t.Error(err)
|
||||
}
|
||||
cfg.DataSettings.LiveData = &config.LiveData{
|
||||
|
||||
RealOrders: true,
|
||||
}
|
||||
cfg.DataSettings.Interval = gctkline.OneDay
|
||||
cfg.DataSettings.DataType = common.CandleStr
|
||||
err = loadLiveData(cfg, b)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cfg.DataSettings.LiveData.APIKeyOverride = "1234"
|
||||
cfg.DataSettings.LiveData.APISecretOverride = "1234"
|
||||
cfg.DataSettings.LiveData.APIClientIDOverride = "1234"
|
||||
cfg.DataSettings.LiveData.API2FAOverride = "1234"
|
||||
cfg.DataSettings.LiveData.APISubAccountOverride = "1234"
|
||||
err = loadLiveData(cfg, b)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReset(t *testing.T) {
|
||||
t.Parallel()
|
||||
f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, true, false)
|
||||
|
||||
505
backtester/engine/grpcserver.go
Normal file
505
backtester/engine/grpcserver.go
Normal file
@@ -0,0 +1,505 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/btrpc"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/database"
|
||||
"github.com/thrasher-corp/gocryptotrader/database/drivers"
|
||||
gctengine "github.com/thrasher-corp/gocryptotrader/engine"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/gctrpc/auth"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/utils"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
errBadPort = errors.New("received bad port")
|
||||
)
|
||||
|
||||
// GRPCServer struct
|
||||
type GRPCServer struct {
|
||||
btrpc.BacktesterServiceServer
|
||||
*config.BacktesterConfig
|
||||
}
|
||||
|
||||
// SetupRPCServer sets up the gRPC server
|
||||
func SetupRPCServer(cfg *config.BacktesterConfig) *GRPCServer {
|
||||
return &GRPCServer{
|
||||
BacktesterConfig: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// StartRPCServer starts a gRPC server with TLS auth
|
||||
func StartRPCServer(server *GRPCServer) error {
|
||||
targetDir := utils.GetTLSDir(server.GRPC.TLSDir)
|
||||
if err := gctengine.CheckCerts(targetDir); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf(log.GRPCSys, "Backtester GRPC server enabled. Starting GRPC server on https://%v.\n", server.GRPC.ListenAddress)
|
||||
lis, err := net.Listen("tcp", server.GRPC.ListenAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
creds, err := credentials.NewServerTLSFromFile(filepath.Join(targetDir, "cert.pem"), filepath.Join(targetDir, "key.pem"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := []grpc.ServerOption{
|
||||
grpc.Creds(creds),
|
||||
grpc.UnaryInterceptor(grpcauth.UnaryServerInterceptor(server.authenticateClient)),
|
||||
}
|
||||
s := grpc.NewServer(opts...)
|
||||
btrpc.RegisterBacktesterServiceServer(s, server)
|
||||
|
||||
go func() {
|
||||
if err = s.Serve(lis); err != nil {
|
||||
log.Error(log.GRPCSys, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debugln(log.GRPCSys, "GRPC server started!")
|
||||
|
||||
if server.GRPC.GRPCProxyEnabled {
|
||||
return server.StartRPCRESTProxy()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartRPCRESTProxy starts a gRPC proxy
|
||||
func (s *GRPCServer) StartRPCRESTProxy() error {
|
||||
log.Debugf(log.GRPCSys, "GRPC proxy server support enabled. Starting gRPC proxy server on http://%v.\n", s.GRPC.GRPCProxyListenAddress)
|
||||
targetDir := utils.GetTLSDir(s.GRPC.TLSDir)
|
||||
creds, err := credentials.NewClientTLSFromFile(filepath.Join(targetDir, "cert.pem"), "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("unabled to start gRPC proxy. Err: %w", err)
|
||||
}
|
||||
|
||||
mux := runtime.NewServeMux()
|
||||
opts := []grpc.DialOption{grpc.WithTransportCredentials(creds),
|
||||
grpc.WithPerRPCCredentials(auth.BasicAuth{
|
||||
Username: s.GRPC.Username,
|
||||
Password: s.GRPC.Password,
|
||||
}),
|
||||
}
|
||||
err = btrpc.RegisterBacktesterServiceHandlerFromEndpoint(context.Background(),
|
||||
mux, s.GRPC.ListenAddress, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register gRPC proxy. Err: %w", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err = http.ListenAndServe(s.GRPC.GRPCProxyListenAddress, mux); err != nil {
|
||||
log.Errorf(log.GRPCSys, "GRPC proxy failed to server: %s\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debug(log.GRPCSys, "GRPC proxy server started!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *GRPCServer) authenticateClient(ctx context.Context) (context.Context, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return ctx, fmt.Errorf("unable to extract metadata")
|
||||
}
|
||||
|
||||
authStr, ok := md["authorization"]
|
||||
if !ok {
|
||||
return ctx, fmt.Errorf("authorization header missing")
|
||||
}
|
||||
|
||||
if !strings.Contains(authStr[0], "Basic") {
|
||||
return ctx, fmt.Errorf("basic not found in authorization header")
|
||||
}
|
||||
|
||||
decoded, err := crypto.Base64Decode(strings.Split(authStr[0], " ")[1])
|
||||
if err != nil {
|
||||
return ctx, fmt.Errorf("unable to base64 decode authorization header")
|
||||
}
|
||||
|
||||
creds := strings.Split(string(decoded), ":")
|
||||
username := creds[0]
|
||||
password := creds[1]
|
||||
|
||||
if username != s.GRPC.Username ||
|
||||
password != s.GRPC.Password {
|
||||
return ctx, fmt.Errorf("username/password mismatch")
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// ExecuteStrategyFromFile will backtest a strategy from the filepath provided
|
||||
func (s *GRPCServer) ExecuteStrategyFromFile(_ context.Context, request *btrpc.ExecuteStrategyFromFileRequest) (*btrpc.ExecuteStrategyResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w nil request", common.ErrNilArguments)
|
||||
}
|
||||
dir := request.StrategyFilePath
|
||||
cfg, err := config.ReadStrategyConfigFromFile(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = ExecuteStrategy(cfg, s.BacktesterConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &btrpc.ExecuteStrategyResponse{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExecuteStrategyFromConfig will backtest a strategy config built from a GRPC command
|
||||
// this should be a preferred method of interacting with backtester, as it allows for very quick
|
||||
// minor tweaks to strategy to determine the best result - SO LONG AS YOU DONT OVERFIT
|
||||
func (s *GRPCServer) ExecuteStrategyFromConfig(_ context.Context, request *btrpc.ExecuteStrategyFromConfigRequest) (*btrpc.ExecuteStrategyResponse, error) {
|
||||
if request == nil || request.Config == nil {
|
||||
return nil, fmt.Errorf("%w nil request", common.ErrNilArguments)
|
||||
}
|
||||
|
||||
rfr, err := decimal.NewFromString(request.Config.StatisticSettings.RiskFreeRate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maximumOrdersWithLeverageRatio, err := decimal.NewFromString(request.Config.PortfolioSettings.Leverage.MaximumOrdersWithLeverageRatio)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maximumOrderLeverageRate, err := decimal.NewFromString(request.Config.PortfolioSettings.Leverage.MaximumLeverageRate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maximumCollateralLeverageRate, err := decimal.NewFromString(request.Config.PortfolioSettings.Leverage.MaximumCollateralLeverageRate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buySideMinimumSize, err := decimal.NewFromString(request.Config.PortfolioSettings.BuySide.MinimumSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buySideMaximumSize, err := decimal.NewFromString(request.Config.PortfolioSettings.BuySide.MaximumSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buySideMaximumTotal, err := decimal.NewFromString(request.Config.PortfolioSettings.BuySide.MaximumTotal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sellSideMinimumSize, err := decimal.NewFromString(request.Config.PortfolioSettings.SellSide.MinimumSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sellSideMaximumSize, err := decimal.NewFromString(request.Config.PortfolioSettings.SellSide.MaximumSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sellSideMaximumTotal, err := decimal.NewFromString(request.Config.PortfolioSettings.SellSide.MaximumTotal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fundingSettings := make([]config.ExchangeLevelFunding, len(request.Config.FundingSettings.ExchangeLevelFunding))
|
||||
for i := range request.Config.FundingSettings.ExchangeLevelFunding {
|
||||
var initialFunds, transferFee decimal.Decimal
|
||||
var a asset.Item
|
||||
initialFunds, err = decimal.NewFromString(request.Config.FundingSettings.ExchangeLevelFunding[i].InitialFunds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transferFee, err = decimal.NewFromString(request.Config.FundingSettings.ExchangeLevelFunding[i].TransferFee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, err = asset.New(request.Config.FundingSettings.ExchangeLevelFunding[i].Asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fundingSettings[i] = config.ExchangeLevelFunding{
|
||||
ExchangeName: request.Config.FundingSettings.ExchangeLevelFunding[i].ExchangeName,
|
||||
Asset: a,
|
||||
Currency: currency.NewCode(request.Config.FundingSettings.ExchangeLevelFunding[i].Currency),
|
||||
InitialFunds: initialFunds,
|
||||
TransferFee: transferFee,
|
||||
}
|
||||
}
|
||||
|
||||
customSettings := make(map[string]interface{}, len(request.Config.StrategySettings.CustomSettings))
|
||||
for i := range request.Config.StrategySettings.CustomSettings {
|
||||
customSettings[request.Config.StrategySettings.CustomSettings[i].KeyField] = request.Config.StrategySettings.CustomSettings[i].KeyValue
|
||||
}
|
||||
|
||||
configSettings := make([]config.CurrencySettings, len(request.Config.CurrencySettings))
|
||||
for i := range request.Config.CurrencySettings {
|
||||
var currencySettingBuySideMinimumSize, currencySettingBuySideMaximumSize,
|
||||
currencySettingBuySideMaximumTotal, currencySettingSellSideMinimumSize,
|
||||
currencySettingSellSideMaximumSize, currencySettingSellSideMaximumTotal,
|
||||
minimumSlippagePercent, maximumSlippagePercent, maximumHoldingsRatio decimal.Decimal
|
||||
var a asset.Item
|
||||
currencySettingBuySideMinimumSize, err = decimal.NewFromString(request.Config.CurrencySettings[i].BuySide.MinimumSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currencySettingBuySideMaximumSize, err = decimal.NewFromString(request.Config.CurrencySettings[i].BuySide.MaximumSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currencySettingBuySideMaximumTotal, err = decimal.NewFromString(request.Config.CurrencySettings[i].BuySide.MaximumTotal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currencySettingSellSideMinimumSize, err = decimal.NewFromString(request.Config.CurrencySettings[i].SellSide.MinimumSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currencySettingSellSideMaximumSize, err = decimal.NewFromString(request.Config.CurrencySettings[i].SellSide.MaximumSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currencySettingSellSideMaximumTotal, err = decimal.NewFromString(request.Config.CurrencySettings[i].SellSide.MaximumTotal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
minimumSlippagePercent, err = decimal.NewFromString(request.Config.CurrencySettings[i].MinSlippagePercent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maximumSlippagePercent, err = decimal.NewFromString(request.Config.CurrencySettings[i].MaxSlippagePercent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maximumHoldingsRatio, err = decimal.NewFromString(request.Config.CurrencySettings[i].MaximumHoldingsRatio)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, err = asset.New(request.Config.CurrencySettings[i].Asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var maker, taker *decimal.Decimal
|
||||
if request.Config.CurrencySettings[i].MakerFeeOverride != "" {
|
||||
// nil is a valid option
|
||||
var m decimal.Decimal
|
||||
m, err = decimal.NewFromString(request.Config.CurrencySettings[i].MakerFeeOverride)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v %v %v-%v maker fee %w", request.Config.CurrencySettings[i].ExchangeName, request.Config.CurrencySettings[i].Asset, request.Config.CurrencySettings[i].Base, request.Config.CurrencySettings[i].Quote, err)
|
||||
}
|
||||
maker = &m
|
||||
}
|
||||
if request.Config.CurrencySettings[i].TakerFeeOverride != "" {
|
||||
// nil is a valid option
|
||||
var t decimal.Decimal
|
||||
t, err = decimal.NewFromString(request.Config.CurrencySettings[i].MakerFeeOverride)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v %v %v-%v taker fee %w", request.Config.CurrencySettings[i].ExchangeName, request.Config.CurrencySettings[i].Asset, request.Config.CurrencySettings[i].Base, request.Config.CurrencySettings[i].Quote, err)
|
||||
}
|
||||
taker = &t
|
||||
}
|
||||
|
||||
var spotDetails *config.SpotDetails
|
||||
if request.Config.CurrencySettings[i].SpotDetails != nil {
|
||||
spotDetails = &config.SpotDetails{}
|
||||
if request.Config.CurrencySettings[i].SpotDetails.InitialBaseFunds != "" {
|
||||
var ibf decimal.Decimal
|
||||
ibf, err = decimal.NewFromString(request.Config.CurrencySettings[i].SpotDetails.InitialBaseFunds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spotDetails.InitialBaseFunds = &ibf
|
||||
}
|
||||
if request.Config.CurrencySettings[i].SpotDetails.InitialQuoteFunds != "" {
|
||||
var iqf decimal.Decimal
|
||||
iqf, err = decimal.NewFromString(request.Config.CurrencySettings[i].SpotDetails.InitialQuoteFunds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spotDetails.InitialQuoteFunds = &iqf
|
||||
}
|
||||
}
|
||||
|
||||
var futuresDetails *config.FuturesDetails
|
||||
if request.Config.CurrencySettings[i].FuturesDetails != nil &&
|
||||
request.Config.CurrencySettings[i].FuturesDetails.Leverage != nil {
|
||||
futuresDetails = &config.FuturesDetails{}
|
||||
var mowlr, mlr, mclr decimal.Decimal
|
||||
mowlr, err = decimal.NewFromString(request.Config.CurrencySettings[i].FuturesDetails.Leverage.MaximumOrdersWithLeverageRatio)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mlr, err = decimal.NewFromString(request.Config.CurrencySettings[i].FuturesDetails.Leverage.MaximumLeverageRate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mclr, err = decimal.NewFromString(request.Config.CurrencySettings[i].FuturesDetails.Leverage.MaximumCollateralLeverageRate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
futuresDetails.Leverage = config.Leverage{
|
||||
CanUseLeverage: request.Config.CurrencySettings[i].FuturesDetails.Leverage.CanUseLeverage,
|
||||
MaximumOrdersWithLeverageRatio: mowlr,
|
||||
MaximumOrderLeverageRate: mlr,
|
||||
MaximumCollateralLeverageRate: mclr,
|
||||
}
|
||||
}
|
||||
|
||||
configSettings[i] = config.CurrencySettings{
|
||||
ExchangeName: request.Config.CurrencySettings[i].ExchangeName,
|
||||
Asset: a,
|
||||
Base: currency.NewCode(request.Config.CurrencySettings[i].Base),
|
||||
Quote: currency.NewCode(request.Config.CurrencySettings[i].Quote),
|
||||
SpotDetails: spotDetails,
|
||||
FuturesDetails: futuresDetails,
|
||||
BuySide: config.MinMax{
|
||||
MinimumSize: currencySettingBuySideMinimumSize,
|
||||
MaximumSize: currencySettingBuySideMaximumSize,
|
||||
MaximumTotal: currencySettingBuySideMaximumTotal,
|
||||
},
|
||||
SellSide: config.MinMax{
|
||||
MinimumSize: currencySettingSellSideMinimumSize,
|
||||
MaximumSize: currencySettingSellSideMaximumSize,
|
||||
MaximumTotal: currencySettingSellSideMaximumTotal,
|
||||
},
|
||||
MinimumSlippagePercent: minimumSlippagePercent,
|
||||
MaximumSlippagePercent: maximumSlippagePercent,
|
||||
MakerFee: maker,
|
||||
TakerFee: taker,
|
||||
MaximumHoldingsRatio: maximumHoldingsRatio,
|
||||
SkipCandleVolumeFitting: request.Config.CurrencySettings[i].SkipCandleVolumeFitting,
|
||||
CanUseExchangeLimits: request.Config.CurrencySettings[i].UseExchangeOrderLimits,
|
||||
ShowExchangeOrderLimitWarning: request.Config.CurrencySettings[i].UseExchangeOrderLimits,
|
||||
UseExchangePNLCalculation: request.Config.CurrencySettings[i].UseExchangePnlCalculation,
|
||||
}
|
||||
}
|
||||
|
||||
var apiData *config.APIData
|
||||
if request.Config.DataSettings.ApiData != nil {
|
||||
apiData = &config.APIData{
|
||||
StartDate: request.Config.DataSettings.ApiData.StartDate.AsTime(),
|
||||
EndDate: request.Config.DataSettings.ApiData.EndDate.AsTime(),
|
||||
InclusiveEndDate: request.Config.DataSettings.ApiData.InclusiveEndDate,
|
||||
}
|
||||
}
|
||||
var dbData *config.DatabaseData
|
||||
if request.Config.DataSettings.DatabaseData != nil {
|
||||
if request.Config.DataSettings.DatabaseData.Config.Config.Port > math.MaxUint16 {
|
||||
return nil, fmt.Errorf("%w '%v' cannot exceed '%v'", errBadPort, request.Config.DataSettings.DatabaseData.Config.Config.Port, math.MaxUint16)
|
||||
}
|
||||
cfg := database.Config{
|
||||
Enabled: request.Config.DataSettings.DatabaseData.Config.Enabled,
|
||||
Verbose: request.Config.DataSettings.DatabaseData.Config.Verbose,
|
||||
Driver: request.Config.DataSettings.DatabaseData.Config.Driver,
|
||||
ConnectionDetails: drivers.ConnectionDetails{
|
||||
Host: request.Config.DataSettings.DatabaseData.Config.Config.Host,
|
||||
Port: uint16(request.Config.DataSettings.DatabaseData.Config.Config.Port),
|
||||
Username: request.Config.DataSettings.DatabaseData.Config.Config.UserName,
|
||||
Password: request.Config.DataSettings.DatabaseData.Config.Config.Password,
|
||||
Database: request.Config.DataSettings.DatabaseData.Config.Config.Database,
|
||||
SSLMode: request.Config.DataSettings.DatabaseData.Config.Config.SslMode,
|
||||
},
|
||||
}
|
||||
dbData = &config.DatabaseData{
|
||||
StartDate: request.Config.DataSettings.DatabaseData.StartDate.AsTime(),
|
||||
EndDate: request.Config.DataSettings.DatabaseData.EndDate.AsTime(),
|
||||
Path: request.Config.DataSettings.DatabaseData.Path,
|
||||
Config: cfg,
|
||||
InclusiveEndDate: request.Config.DataSettings.DatabaseData.InclusiveEndDate,
|
||||
}
|
||||
}
|
||||
var liveData *config.LiveData
|
||||
if request.Config.DataSettings.LiveData != nil {
|
||||
liveData = &config.LiveData{
|
||||
APIKeyOverride: request.Config.DataSettings.LiveData.ApiKeyOverride,
|
||||
APISecretOverride: request.Config.DataSettings.LiveData.ApiSecretOverride,
|
||||
APIClientIDOverride: request.Config.DataSettings.LiveData.ApiClientIdOverride,
|
||||
API2FAOverride: request.Config.DataSettings.LiveData.Api_2FaOverride,
|
||||
APISubAccountOverride: request.Config.DataSettings.LiveData.ApiSubAccountOverride,
|
||||
RealOrders: request.Config.DataSettings.LiveData.UseRealOrders,
|
||||
}
|
||||
}
|
||||
var csvData *config.CSVData
|
||||
if request.Config.DataSettings.CsvData != nil {
|
||||
csvData = &config.CSVData{
|
||||
FullPath: request.Config.DataSettings.CsvData.Path,
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &config.Config{
|
||||
Nickname: request.Config.Nickname,
|
||||
Goal: request.Config.Goal,
|
||||
StrategySettings: config.StrategySettings{
|
||||
Name: request.Config.StrategySettings.Name,
|
||||
SimultaneousSignalProcessing: request.Config.StrategySettings.UseSimultaneousSignalProcessing,
|
||||
DisableUSDTracking: request.Config.StrategySettings.DisableUsdTracking,
|
||||
CustomSettings: customSettings,
|
||||
},
|
||||
FundingSettings: config.FundingSettings{
|
||||
UseExchangeLevelFunding: request.Config.FundingSettings.UseExchangeLevelFunding,
|
||||
ExchangeLevelFunding: fundingSettings,
|
||||
},
|
||||
CurrencySettings: configSettings,
|
||||
DataSettings: config.DataSettings{
|
||||
Interval: gctkline.Interval(request.Config.DataSettings.Interval),
|
||||
DataType: request.Config.DataSettings.Datatype,
|
||||
APIData: apiData,
|
||||
DatabaseData: dbData,
|
||||
LiveData: liveData,
|
||||
CSVData: csvData,
|
||||
},
|
||||
PortfolioSettings: config.PortfolioSettings{
|
||||
Leverage: config.Leverage{
|
||||
CanUseLeverage: request.Config.PortfolioSettings.Leverage.CanUseLeverage,
|
||||
MaximumOrdersWithLeverageRatio: maximumOrdersWithLeverageRatio,
|
||||
MaximumOrderLeverageRate: maximumOrderLeverageRate,
|
||||
MaximumCollateralLeverageRate: maximumCollateralLeverageRate,
|
||||
},
|
||||
BuySide: config.MinMax{
|
||||
MinimumSize: buySideMinimumSize,
|
||||
MaximumSize: buySideMaximumSize,
|
||||
MaximumTotal: buySideMaximumTotal,
|
||||
},
|
||||
SellSide: config.MinMax{
|
||||
MinimumSize: sellSideMinimumSize,
|
||||
MaximumSize: sellSideMaximumSize,
|
||||
MaximumTotal: sellSideMaximumTotal,
|
||||
},
|
||||
},
|
||||
StatisticSettings: config.StatisticSettings{
|
||||
RiskFreeRate: rfr,
|
||||
},
|
||||
}
|
||||
|
||||
err = ExecuteStrategy(cfg, s.BacktesterConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &btrpc.ExecuteStrategyResponse{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
44
backtester/engine/grpcserver.md
Normal file
44
backtester/engine/grpcserver.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# GoCryptoTrader Backtester: Grpcserver package
|
||||
|
||||
<img src="/backtester/common/backtester.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](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/engine/grpcserver)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This grpcserver 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)
|
||||
|
||||
## Grpcserver package overview
|
||||
|
||||
The GRPC server is responsible for handling requests from the client. All GRPC functionality as defined in the proto file is implemented [here](/backtester/btrpc)
|
||||
|
||||
### 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
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
|
||||
285
backtester/engine/grpcserver_test.go
Normal file
285
backtester/engine/grpcserver_test.go
Normal file
@@ -0,0 +1,285 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/btrpc"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/config"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
var dcaConfigPath = filepath.Join("..", "config", "strategyexamples", "dca-api-candles.strat")
|
||||
|
||||
func TestExecuteStrategyFromFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := &GRPCServer{}
|
||||
_, err := s.ExecuteStrategyFromFile(context.Background(), nil)
|
||||
if !errors.Is(err, common.ErrNilArguments) {
|
||||
t.Errorf("received '%v' expecting '%v'", err, common.ErrNilArguments)
|
||||
}
|
||||
|
||||
_, err = s.ExecuteStrategyFromFile(context.Background(), &btrpc.ExecuteStrategyFromFileRequest{})
|
||||
if !errors.Is(err, common.ErrFileNotFound) {
|
||||
t.Errorf("received '%v' expecting '%v'", err, common.ErrFileNotFound)
|
||||
}
|
||||
|
||||
_, err = s.ExecuteStrategyFromFile(context.Background(), &btrpc.ExecuteStrategyFromFileRequest{
|
||||
StrategyFilePath: dcaConfigPath,
|
||||
})
|
||||
if !errors.Is(err, common.ErrNilArguments) {
|
||||
t.Errorf("received '%v' expecting '%v'", err, common.ErrNilArguments)
|
||||
}
|
||||
|
||||
s.BacktesterConfig = &config.BacktesterConfig{}
|
||||
_, err = s.ExecuteStrategyFromFile(context.Background(), &btrpc.ExecuteStrategyFromFileRequest{
|
||||
StrategyFilePath: dcaConfigPath,
|
||||
})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v' expecting '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteStrategyFromConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := &GRPCServer{}
|
||||
_, err := s.ExecuteStrategyFromConfig(context.Background(), nil)
|
||||
if !errors.Is(err, common.ErrNilArguments) {
|
||||
t.Errorf("received '%v' expecting '%v'", err, common.ErrNilArguments)
|
||||
}
|
||||
|
||||
s.BacktesterConfig = &config.BacktesterConfig{}
|
||||
_, err = s.ExecuteStrategyFromConfig(context.Background(), &btrpc.ExecuteStrategyFromConfigRequest{})
|
||||
if !errors.Is(err, common.ErrNilArguments) {
|
||||
t.Errorf("received '%v' expecting '%v'", err, common.ErrNilArguments)
|
||||
}
|
||||
|
||||
defaultConfig, err := config.ReadStrategyConfigFromFile(dcaConfigPath)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v' expecting '%v'", err, nil)
|
||||
}
|
||||
customSettings := make([]*btrpc.CustomSettings, len(defaultConfig.StrategySettings.CustomSettings))
|
||||
x := 0
|
||||
for k, v := range defaultConfig.StrategySettings.CustomSettings {
|
||||
customSettings[x] = &btrpc.CustomSettings{
|
||||
KeyField: k,
|
||||
KeyValue: fmt.Sprintf("%v", v),
|
||||
}
|
||||
x++
|
||||
}
|
||||
|
||||
currencySettings := make([]*btrpc.CurrencySettings, len(defaultConfig.CurrencySettings))
|
||||
for i := range defaultConfig.CurrencySettings {
|
||||
var sd *btrpc.SpotDetails
|
||||
if defaultConfig.CurrencySettings[i].SpotDetails != nil {
|
||||
if defaultConfig.CurrencySettings[i].SpotDetails.InitialBaseFunds != nil {
|
||||
sd = &btrpc.SpotDetails{
|
||||
InitialBaseFunds: defaultConfig.CurrencySettings[i].SpotDetails.InitialBaseFunds.String(),
|
||||
}
|
||||
}
|
||||
if defaultConfig.CurrencySettings[i].SpotDetails.InitialQuoteFunds != nil {
|
||||
if sd == nil {
|
||||
sd = &btrpc.SpotDetails{}
|
||||
}
|
||||
sd.InitialQuoteFunds = defaultConfig.CurrencySettings[i].SpotDetails.InitialQuoteFunds.String()
|
||||
}
|
||||
}
|
||||
var fd *btrpc.FuturesDetails
|
||||
if defaultConfig.CurrencySettings[i].FuturesDetails != nil {
|
||||
fd.Leverage = &btrpc.Leverage{
|
||||
CanUseLeverage: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.CanUseLeverage,
|
||||
MaximumOrdersWithLeverageRatio: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.MaximumOrdersWithLeverageRatio.String(),
|
||||
MaximumLeverageRate: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.MaximumOrderLeverageRate.String(),
|
||||
MaximumCollateralLeverageRate: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.MaximumCollateralLeverageRate.String(),
|
||||
}
|
||||
}
|
||||
var makerFee, takerFee string
|
||||
if defaultConfig.CurrencySettings[i].MakerFee != nil {
|
||||
makerFee = defaultConfig.CurrencySettings[i].MakerFee.String()
|
||||
}
|
||||
if defaultConfig.CurrencySettings[i].TakerFee != nil {
|
||||
takerFee = defaultConfig.CurrencySettings[i].TakerFee.String()
|
||||
}
|
||||
currencySettings[i] = &btrpc.CurrencySettings{
|
||||
ExchangeName: defaultConfig.CurrencySettings[i].ExchangeName,
|
||||
Asset: defaultConfig.CurrencySettings[i].Asset.String(),
|
||||
Base: defaultConfig.CurrencySettings[i].Base.String(),
|
||||
Quote: defaultConfig.CurrencySettings[i].Quote.String(),
|
||||
BuySide: &btrpc.PurchaseSide{
|
||||
MinimumSize: defaultConfig.CurrencySettings[i].BuySide.MinimumSize.String(),
|
||||
MaximumSize: defaultConfig.CurrencySettings[i].BuySide.MaximumSize.String(),
|
||||
MaximumTotal: defaultConfig.CurrencySettings[i].BuySide.MaximumTotal.String(),
|
||||
},
|
||||
SellSide: &btrpc.PurchaseSide{
|
||||
MinimumSize: defaultConfig.CurrencySettings[i].SellSide.MinimumSize.String(),
|
||||
MaximumSize: defaultConfig.CurrencySettings[i].SellSide.MaximumSize.String(),
|
||||
MaximumTotal: defaultConfig.CurrencySettings[i].SellSide.MaximumTotal.String(),
|
||||
},
|
||||
MinSlippagePercent: defaultConfig.CurrencySettings[i].MinimumSlippagePercent.String(),
|
||||
MaxSlippagePercent: defaultConfig.CurrencySettings[i].MaximumSlippagePercent.String(),
|
||||
MakerFeeOverride: makerFee,
|
||||
TakerFeeOverride: takerFee,
|
||||
MaximumHoldingsRatio: defaultConfig.CurrencySettings[i].MaximumHoldingsRatio.String(),
|
||||
SkipCandleVolumeFitting: defaultConfig.CurrencySettings[i].SkipCandleVolumeFitting,
|
||||
UseExchangeOrderLimits: defaultConfig.CurrencySettings[i].CanUseExchangeLimits,
|
||||
UseExchangePnlCalculation: defaultConfig.CurrencySettings[i].UseExchangePNLCalculation,
|
||||
SpotDetails: sd,
|
||||
FuturesDetails: fd,
|
||||
}
|
||||
}
|
||||
|
||||
exchangeLevelFunding := make([]*btrpc.ExchangeLevelFunding, len(defaultConfig.FundingSettings.ExchangeLevelFunding))
|
||||
for i := range defaultConfig.FundingSettings.ExchangeLevelFunding {
|
||||
exchangeLevelFunding[i] = &btrpc.ExchangeLevelFunding{
|
||||
ExchangeName: defaultConfig.FundingSettings.ExchangeLevelFunding[i].ExchangeName,
|
||||
Asset: defaultConfig.FundingSettings.ExchangeLevelFunding[i].Asset.String(),
|
||||
Currency: defaultConfig.FundingSettings.ExchangeLevelFunding[i].Currency.String(),
|
||||
InitialFunds: defaultConfig.FundingSettings.ExchangeLevelFunding[i].InitialFunds.String(),
|
||||
TransferFee: defaultConfig.FundingSettings.ExchangeLevelFunding[i].TransferFee.String(),
|
||||
}
|
||||
}
|
||||
|
||||
dataSettings := &btrpc.DataSettings{
|
||||
Interval: uint64(defaultConfig.DataSettings.Interval.Duration().Nanoseconds()),
|
||||
Datatype: defaultConfig.DataSettings.DataType,
|
||||
}
|
||||
if defaultConfig.DataSettings.APIData != nil {
|
||||
dataSettings.ApiData = &btrpc.ApiData{
|
||||
StartDate: timestamppb.New(defaultConfig.DataSettings.APIData.StartDate),
|
||||
EndDate: timestamppb.New(defaultConfig.DataSettings.APIData.EndDate),
|
||||
InclusiveEndDate: defaultConfig.DataSettings.APIData.InclusiveEndDate,
|
||||
}
|
||||
}
|
||||
if defaultConfig.DataSettings.LiveData != nil {
|
||||
dataSettings.LiveData = &btrpc.LiveData{
|
||||
ApiKeyOverride: defaultConfig.DataSettings.LiveData.APIKeyOverride,
|
||||
ApiSecretOverride: defaultConfig.DataSettings.LiveData.APISecretOverride,
|
||||
ApiClientIdOverride: defaultConfig.DataSettings.LiveData.APIClientIDOverride,
|
||||
Api_2FaOverride: defaultConfig.DataSettings.LiveData.API2FAOverride,
|
||||
ApiSubAccountOverride: defaultConfig.DataSettings.LiveData.APISubAccountOverride,
|
||||
UseRealOrders: defaultConfig.DataSettings.LiveData.RealOrders,
|
||||
}
|
||||
}
|
||||
if defaultConfig.DataSettings.CSVData != nil {
|
||||
dataSettings.CsvData = &btrpc.CSVData{
|
||||
Path: defaultConfig.DataSettings.CSVData.FullPath,
|
||||
}
|
||||
}
|
||||
if defaultConfig.DataSettings.DatabaseData != nil {
|
||||
dbConnectionDetails := &btrpc.DatabaseConnectionDetails{
|
||||
Host: defaultConfig.DataSettings.DatabaseData.Config.Host,
|
||||
Port: uint32(defaultConfig.DataSettings.DatabaseData.Config.Port),
|
||||
Password: defaultConfig.DataSettings.DatabaseData.Config.Password,
|
||||
Database: defaultConfig.DataSettings.DatabaseData.Config.Database,
|
||||
SslMode: defaultConfig.DataSettings.DatabaseData.Config.SSLMode,
|
||||
UserName: defaultConfig.DataSettings.DatabaseData.Config.Username,
|
||||
}
|
||||
dbConfig := &btrpc.DatabaseConfig{
|
||||
Enabled: false,
|
||||
Verbose: false,
|
||||
Driver: "",
|
||||
Config: dbConnectionDetails,
|
||||
}
|
||||
dataSettings.DatabaseData = &btrpc.DatabaseData{
|
||||
StartDate: timestamppb.New(defaultConfig.DataSettings.DatabaseData.StartDate),
|
||||
EndDate: timestamppb.New(defaultConfig.DataSettings.DatabaseData.EndDate),
|
||||
Config: dbConfig,
|
||||
Path: defaultConfig.DataSettings.DatabaseData.Path,
|
||||
InclusiveEndDate: defaultConfig.DataSettings.DatabaseData.InclusiveEndDate,
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &btrpc.Config{
|
||||
Nickname: defaultConfig.Nickname,
|
||||
Goal: defaultConfig.Goal,
|
||||
StrategySettings: &btrpc.StrategySettings{
|
||||
Name: defaultConfig.StrategySettings.Name,
|
||||
UseSimultaneousSignalProcessing: defaultConfig.StrategySettings.SimultaneousSignalProcessing,
|
||||
DisableUsdTracking: defaultConfig.StrategySettings.DisableUSDTracking,
|
||||
CustomSettings: customSettings,
|
||||
},
|
||||
FundingSettings: &btrpc.FundingSettings{
|
||||
UseExchangeLevelFunding: defaultConfig.FundingSettings.UseExchangeLevelFunding,
|
||||
ExchangeLevelFunding: exchangeLevelFunding,
|
||||
},
|
||||
CurrencySettings: currencySettings,
|
||||
DataSettings: dataSettings,
|
||||
PortfolioSettings: &btrpc.PortfolioSettings{
|
||||
Leverage: &btrpc.Leverage{
|
||||
CanUseLeverage: defaultConfig.PortfolioSettings.Leverage.CanUseLeverage,
|
||||
MaximumOrdersWithLeverageRatio: defaultConfig.PortfolioSettings.Leverage.MaximumOrdersWithLeverageRatio.String(),
|
||||
MaximumLeverageRate: defaultConfig.PortfolioSettings.Leverage.MaximumOrderLeverageRate.String(),
|
||||
MaximumCollateralLeverageRate: defaultConfig.PortfolioSettings.Leverage.MaximumCollateralLeverageRate.String(),
|
||||
},
|
||||
BuySide: &btrpc.PurchaseSide{
|
||||
MinimumSize: defaultConfig.PortfolioSettings.BuySide.MinimumSize.String(),
|
||||
MaximumSize: defaultConfig.PortfolioSettings.BuySide.MaximumSize.String(),
|
||||
MaximumTotal: defaultConfig.PortfolioSettings.BuySide.MaximumTotal.String(),
|
||||
},
|
||||
SellSide: &btrpc.PurchaseSide{
|
||||
MinimumSize: defaultConfig.PortfolioSettings.SellSide.MinimumSize.String(),
|
||||
MaximumSize: defaultConfig.PortfolioSettings.SellSide.MaximumSize.String(),
|
||||
MaximumTotal: defaultConfig.PortfolioSettings.SellSide.MaximumTotal.String(),
|
||||
},
|
||||
},
|
||||
StatisticSettings: &btrpc.StatisticSettings{
|
||||
RiskFreeRate: defaultConfig.StatisticSettings.RiskFreeRate.String(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err = s.ExecuteStrategyFromConfig(context.Background(), &btrpc.ExecuteStrategyFromConfigRequest{
|
||||
Config: cfg,
|
||||
})
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v' expecting '%v'", err, nil)
|
||||
}
|
||||
|
||||
// coverage test to ensure the rest of the config can successfully be converted
|
||||
// this will not have a successful response
|
||||
cfg.FundingSettings.UseExchangeLevelFunding = true
|
||||
cfg.StrategySettings.UseSimultaneousSignalProcessing = true
|
||||
cfg.FundingSettings.ExchangeLevelFunding = append(cfg.FundingSettings.ExchangeLevelFunding, &btrpc.ExchangeLevelFunding{
|
||||
ExchangeName: defaultConfig.CurrencySettings[0].ExchangeName,
|
||||
Asset: defaultConfig.CurrencySettings[0].Asset.String(),
|
||||
Currency: defaultConfig.CurrencySettings[0].Base.String(),
|
||||
InitialFunds: "1337",
|
||||
TransferFee: "1337",
|
||||
})
|
||||
cfg.CurrencySettings[0].FuturesDetails = &btrpc.FuturesDetails{Leverage: &btrpc.Leverage{
|
||||
CanUseLeverage: false,
|
||||
MaximumOrdersWithLeverageRatio: "1337",
|
||||
MaximumLeverageRate: "1337",
|
||||
MaximumCollateralLeverageRate: "1337",
|
||||
}}
|
||||
cfg.DataSettings.DatabaseData = &btrpc.DatabaseData{
|
||||
StartDate: timestamppb.New(time.Now()),
|
||||
EndDate: timestamppb.New(time.Now()),
|
||||
Config: &btrpc.DatabaseConfig{
|
||||
Enabled: false,
|
||||
Verbose: false,
|
||||
Driver: "",
|
||||
Config: &btrpc.DatabaseConnectionDetails{},
|
||||
},
|
||||
Path: "test",
|
||||
InclusiveEndDate: false,
|
||||
}
|
||||
cfg.DataSettings.LiveData = &btrpc.LiveData{}
|
||||
cfg.DataSettings.CsvData = &btrpc.CSVData{
|
||||
Path: "test",
|
||||
}
|
||||
for i := range cfg.CurrencySettings {
|
||||
cfg.CurrencySettings[i].SpotDetails.InitialQuoteFunds = ""
|
||||
cfg.CurrencySettings[i].SpotDetails.InitialBaseFunds = ""
|
||||
}
|
||||
_, err = s.ExecuteStrategyFromConfig(context.Background(), &btrpc.ExecuteStrategyFromConfigRequest{
|
||||
Config: cfg,
|
||||
})
|
||||
if err == nil {
|
||||
t.Error("expected an error from a bad setup")
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
// It runs by constantly checking for new live datas and running through the list of events
|
||||
// once new data is processed. It will run until application close event has been received
|
||||
func (bt *BackTest) RunLive() error {
|
||||
log.Info(common.Backtester, "running backtester against live data")
|
||||
log.Info(common.Backtester, "Running backtester against live data")
|
||||
timeoutTimer := time.NewTimer(time.Minute * 5)
|
||||
// a frequent timer so that when a new candle is released by an exchange
|
||||
// that it can be processed quickly
|
||||
@@ -99,7 +99,7 @@ func (bt *BackTest) loadLiveDataLoop(resp *kline.DataFromKline, cfg *config.Conf
|
||||
case <-bt.shutdown:
|
||||
return
|
||||
case <-loadNewDataTimer.C:
|
||||
log.Infof(common.Backtester, "fetching data for %v %v %v %v", exch.GetName(), a, fPair, cfg.DataSettings.Interval)
|
||||
log.Infof(common.Backtester, "Fetching data for %v %v %v %v", exch.GetName(), a, fPair, cfg.DataSettings.Interval)
|
||||
loadNewDataTimer.Reset(time.Second * 15)
|
||||
err = bt.loadLiveData(resp, cfg, exch, fPair, a, dataType)
|
||||
if err != nil {
|
||||
@@ -134,6 +134,6 @@ func (bt *BackTest) loadLiveData(resp *kline.DataFromKline, cfg *config.Config,
|
||||
}
|
||||
resp.AppendResults(candles)
|
||||
bt.Reports.UpdateItem(&resp.Item)
|
||||
log.Info(common.Backtester, "sleeping for 30 seconds before checking for new candle data")
|
||||
log.Info(common.Backtester, "Sleeping for 30 seconds before checking for new candle data")
|
||||
return nil
|
||||
}
|
||||
|
||||
51
backtester/engine/live.md
Normal file
51
backtester/engine/live.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# GoCryptoTrader Backtester: Live package
|
||||
|
||||
<img src="/backtester/common/backtester.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](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/engine/live)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This live 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)
|
||||
|
||||
## Live package overview
|
||||
|
||||
Live trading has specific requirements separate from backtesting. Handling the looping of candle data and managing real orders and orderbooks will be handled here
|
||||
|
||||
Live trading is only a proof of concept. Please do not risk your funds by using it with `realOrders` enabled
|
||||
|
||||
|
||||
A flow of the application is as follows:
|
||||

|
||||
|
||||
|
||||
### 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
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
|
||||
60
backtester/engine/live_test.go
Normal file
60
backtester/engine/live_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/config"
|
||||
gctexchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
)
|
||||
|
||||
func TestLoadLiveData(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := loadLiveData(nil, nil)
|
||||
if !errors.Is(err, common.ErrNilArguments) {
|
||||
t.Error(err)
|
||||
}
|
||||
cfg := &config.Config{}
|
||||
err = loadLiveData(cfg, nil)
|
||||
if !errors.Is(err, common.ErrNilArguments) {
|
||||
t.Error(err)
|
||||
}
|
||||
b := &gctexchange.Base{
|
||||
Name: testExchange,
|
||||
API: gctexchange.API{
|
||||
CredentialsValidator: gctexchange.CredentialsValidator{
|
||||
RequiresPEM: true,
|
||||
RequiresKey: true,
|
||||
RequiresSecret: true,
|
||||
RequiresClientID: true,
|
||||
RequiresBase64DecodeSecret: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = loadLiveData(cfg, b)
|
||||
if !errors.Is(err, common.ErrNilArguments) {
|
||||
t.Error(err)
|
||||
}
|
||||
cfg.DataSettings.LiveData = &config.LiveData{
|
||||
RealOrders: true,
|
||||
}
|
||||
cfg.DataSettings.Interval = gctkline.OneDay
|
||||
cfg.DataSettings.DataType = common.CandleStr
|
||||
err = loadLiveData(cfg, b)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cfg.DataSettings.LiveData.APIKeyOverride = "1234"
|
||||
cfg.DataSettings.LiveData.APISecretOverride = "1234"
|
||||
cfg.DataSettings.LiveData.APIClientIDOverride = "1234"
|
||||
cfg.DataSettings.LiveData.API2FAOverride = "1234"
|
||||
cfg.DataSettings.LiveData.APISubAccountOverride = "1234"
|
||||
err = loadLiveData(cfg, b)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange/slippage"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/risk"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/size"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics"
|
||||
@@ -39,11 +40,12 @@ import (
|
||||
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/signaler"
|
||||
)
|
||||
|
||||
// NewFromConfig takes a strategy config and configures a backtester variable to run
|
||||
func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool) (*BackTest, error) {
|
||||
log.Infoln(common.Setup, "loading config...")
|
||||
log.Infoln(common.Setup, "Loading config...")
|
||||
if cfg == nil {
|
||||
return nil, errNilConfig
|
||||
}
|
||||
@@ -232,7 +234,7 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool
|
||||
if cfg.CurrencySettings[i].MakerFee != nil &&
|
||||
cfg.CurrencySettings[i].TakerFee != nil &&
|
||||
cfg.CurrencySettings[i].MakerFee.GreaterThan(*cfg.CurrencySettings[i].TakerFee) {
|
||||
log.Warnf(common.Setup, "maker fee '%v' should not exceed taker fee '%v'. Please review config",
|
||||
log.Warnf(common.Setup, "Maker fee '%v' should not exceed taker fee '%v'. Please review config",
|
||||
cfg.CurrencySettings[i].MakerFee,
|
||||
cfg.CurrencySettings[i].TakerFee)
|
||||
}
|
||||
@@ -414,13 +416,25 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool
|
||||
}
|
||||
bt.Portfolio = p
|
||||
|
||||
hasFunding := false
|
||||
fundingItems := funds.GetAllFunding()
|
||||
for i := range fundingItems {
|
||||
if fundingItems[i].InitialFunds.IsPositive() {
|
||||
hasFunding = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasFunding {
|
||||
return nil, holdings.ErrInitialFundsZero
|
||||
}
|
||||
|
||||
cfg.PrintSetting()
|
||||
|
||||
return bt, nil
|
||||
}
|
||||
|
||||
func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (exchange.Exchange, error) {
|
||||
log.Infoln(common.Setup, "setting exchange settings...")
|
||||
log.Infoln(common.Setup, "Setting exchange settings...")
|
||||
resp := exchange.Exchange{}
|
||||
|
||||
for i := range cfg.CurrencySettings {
|
||||
@@ -476,7 +490,7 @@ func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (exchange.Exchange
|
||||
}
|
||||
|
||||
if cfg.CurrencySettings[i].MaximumSlippagePercent.LessThan(decimal.Zero) {
|
||||
log.Warnf(common.Setup, "invalid maximum slippage percent '%v'. Slippage percent is defined as a number, eg '100.00', defaulting to '%v'",
|
||||
log.Warnf(common.Setup, "Invalid maximum slippage percent '%v'. Slippage percent is defined as a number, eg '100.00', defaulting to '%v'",
|
||||
cfg.CurrencySettings[i].MaximumSlippagePercent,
|
||||
slippage.DefaultMaximumSlippagePercent)
|
||||
cfg.CurrencySettings[i].MaximumSlippagePercent = slippage.DefaultMaximumSlippagePercent
|
||||
@@ -485,7 +499,7 @@ func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (exchange.Exchange
|
||||
cfg.CurrencySettings[i].MaximumSlippagePercent = slippage.DefaultMaximumSlippagePercent
|
||||
}
|
||||
if cfg.CurrencySettings[i].MinimumSlippagePercent.LessThan(decimal.Zero) {
|
||||
log.Warnf(common.Setup, "invalid minimum slippage percent '%v'. Slippage percent is defined as a number, eg '80.00', defaulting to '%v'",
|
||||
log.Warnf(common.Setup, "Invalid minimum slippage percent '%v'. Slippage percent is defined as a number, eg '80.00', defaulting to '%v'",
|
||||
cfg.CurrencySettings[i].MinimumSlippagePercent,
|
||||
slippage.DefaultMinimumSlippagePercent)
|
||||
cfg.CurrencySettings[i].MinimumSlippagePercent = slippage.DefaultMinimumSlippagePercent
|
||||
@@ -520,7 +534,7 @@ func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (exchange.Exchange
|
||||
|
||||
if limits != (gctorder.MinMaxLevel{}) {
|
||||
if !cfg.CurrencySettings[i].CanUseExchangeLimits {
|
||||
log.Warnf(common.Setup, "exchange %s order execution limits supported but disabled for %s %s, live results may differ",
|
||||
log.Warnf(common.Setup, "Exchange %s order execution limits supported but disabled for %s %s, live results may differ",
|
||||
cfg.CurrencySettings[i].ExchangeName,
|
||||
pair,
|
||||
a)
|
||||
@@ -568,7 +582,7 @@ func (bt *BackTest) loadExchangePairAssetBase(exch string, base, quote currency.
|
||||
|
||||
exchangeBase := e.GetBase()
|
||||
if exchangeBase.ValidateAPICredentials(exchangeBase.GetDefaultCredentials()) != nil {
|
||||
log.Warnf(common.Setup, "no credentials set for %v, this is theoretical only", exchangeBase.Name)
|
||||
log.Warnf(common.Setup, "No credentials set for %v, this is theoretical only", exchangeBase.Name)
|
||||
}
|
||||
|
||||
fPair, err = exchangeBase.FormatExchangeCurrency(cp, ai)
|
||||
@@ -633,7 +647,7 @@ func (bt *BackTest) loadData(cfg *config.Config, exch gctexchange.IBotExchange,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof(common.Setup, "loading data for %v %v %v...\n", exch.GetName(), a, fPair)
|
||||
log.Infof(common.Setup, "Loading data for %v %v %v...\n", exch.GetName(), a, fPair)
|
||||
resp := &kline.DataFromKline{}
|
||||
switch {
|
||||
case cfg.DataSettings.CSVData != nil:
|
||||
@@ -860,8 +874,51 @@ func loadLiveData(cfg *config.Config, base *gctexchange.Base) error {
|
||||
validated := base.AreCredentialsValid(context.TODO())
|
||||
base.API.AuthenticatedSupport = validated
|
||||
if !validated && cfg.DataSettings.LiveData.RealOrders {
|
||||
log.Warn(common.Setup, "invalid API credentials set, real orders set to false")
|
||||
log.Warn(common.Setup, "Invalid API credentials set, real orders set to false")
|
||||
cfg.DataSettings.LiveData.RealOrders = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecuteStrategy executes the strategy using the provided configs
|
||||
func ExecuteStrategy(strategyCfg *config.Config, backtesterCfg *config.BacktesterConfig) error {
|
||||
if err := strategyCfg.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if backtesterCfg == nil {
|
||||
err := fmt.Errorf("%w backtester config", common.ErrNilArguments)
|
||||
return err
|
||||
}
|
||||
bt, err := NewFromConfig(strategyCfg, backtesterCfg.Report.TemplatePath, backtesterCfg.Report.OutputPath, backtesterCfg.Verbose)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strategyCfg.DataSettings.LiveData != nil {
|
||||
go func() {
|
||||
err = bt.RunLive()
|
||||
if err != nil {
|
||||
log.Error(log.Global, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
if backtesterCfg.Report.GenerateReport {
|
||||
bt.Reports.UseDarkMode(backtesterCfg.Report.DarkMode)
|
||||
err = bt.Reports.GenerateReport()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
369
backtester/engine/setup_test.go
Normal file
369
backtester/engine/setup_test.go
Normal file
@@ -0,0 +1,369 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/dollarcostaverage"
|
||||
"github.com/thrasher-corp/gocryptotrader/backtester/report"
|
||||
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/database"
|
||||
"github.com/thrasher-corp/gocryptotrader/database/drivers"
|
||||
"github.com/thrasher-corp/gocryptotrader/engine"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
)
|
||||
|
||||
func TestNewFromConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := NewFromConfig(nil, "", "", false)
|
||||
if !errors.Is(err, errNilConfig) {
|
||||
t.Errorf("received %v, expected %v", err, errNilConfig)
|
||||
}
|
||||
|
||||
cfg := &config.Config{}
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, base.ErrStrategyNotFound) {
|
||||
t.Errorf("received: %v, expected: %v", err, base.ErrStrategyNotFound)
|
||||
}
|
||||
|
||||
cfg.CurrencySettings = []config.CurrencySettings{
|
||||
{
|
||||
ExchangeName: "test",
|
||||
Base: currency.NewCode("test"),
|
||||
Quote: currency.NewCode("test"),
|
||||
},
|
||||
{
|
||||
ExchangeName: testExchange,
|
||||
Base: currency.BTC,
|
||||
Quote: currency.NewCode("0624"),
|
||||
Asset: asset.Futures,
|
||||
},
|
||||
}
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, engine.ErrExchangeNotFound) {
|
||||
t.Errorf("received: %v, expected: %v", err, engine.ErrExchangeNotFound)
|
||||
}
|
||||
cfg.CurrencySettings[0].ExchangeName = testExchange
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Errorf("received: %v, expected: %v", err, asset.ErrNotSupported)
|
||||
}
|
||||
cfg.CurrencySettings[0].Asset = asset.Spot
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, base.ErrStrategyNotFound) {
|
||||
t.Errorf("received: %v, expected: %v", err, base.ErrStrategyNotFound)
|
||||
}
|
||||
|
||||
cfg.StrategySettings = config.StrategySettings{
|
||||
Name: dollarcostaverage.Name,
|
||||
CustomSettings: map[string]interface{}{
|
||||
"hello": "moto",
|
||||
},
|
||||
}
|
||||
cfg.CurrencySettings[0].Base = currency.BTC
|
||||
cfg.CurrencySettings[0].Quote = currency.USD
|
||||
cfg.DataSettings.APIData = &config.APIData{
|
||||
StartDate: time.Time{},
|
||||
EndDate: time.Time{},
|
||||
}
|
||||
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if err != nil && !strings.Contains(err.Error(), "unrecognised dataType") {
|
||||
t.Error(err)
|
||||
}
|
||||
cfg.DataSettings.DataType = common.CandleStr
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, errIntervalUnset) {
|
||||
t.Errorf("received: %v, expected: %v", err, errIntervalUnset)
|
||||
}
|
||||
cfg.DataSettings.Interval = gctkline.OneMin
|
||||
cfg.CurrencySettings[0].MakerFee = &decimal.Zero
|
||||
cfg.CurrencySettings[0].TakerFee = &decimal.Zero
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, gctcommon.ErrDateUnset) {
|
||||
t.Errorf("received: %v, expected: %v", err, gctcommon.ErrDateUnset)
|
||||
}
|
||||
|
||||
cfg.DataSettings.APIData.StartDate = time.Now().Add(-time.Minute)
|
||||
cfg.DataSettings.APIData.EndDate = time.Now()
|
||||
cfg.DataSettings.APIData.InclusiveEndDate = true
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, holdings.ErrInitialFundsZero) {
|
||||
t.Errorf("received: %v, expected: %v", err, holdings.ErrInitialFundsZero)
|
||||
}
|
||||
|
||||
cfg.FundingSettings.UseExchangeLevelFunding = true
|
||||
cfg.FundingSettings.ExchangeLevelFunding = []config.ExchangeLevelFunding{
|
||||
{
|
||||
ExchangeName: testExchange,
|
||||
Asset: asset.Spot,
|
||||
Currency: currency.BTC,
|
||||
InitialFunds: leet,
|
||||
TransferFee: leet,
|
||||
},
|
||||
{
|
||||
ExchangeName: testExchange,
|
||||
Asset: asset.Futures,
|
||||
Currency: currency.BTC,
|
||||
InitialFunds: leet,
|
||||
TransferFee: leet,
|
||||
},
|
||||
}
|
||||
_, err = NewFromConfig(cfg, "", "", false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received: %v, expected: %v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDataAPI(t *testing.T) {
|
||||
t.Parallel()
|
||||
bt := BackTest{
|
||||
Reports: &report.Data{},
|
||||
}
|
||||
cp := currency.NewPair(currency.BTC, currency.USDT)
|
||||
cfg := &config.Config{
|
||||
CurrencySettings: []config.CurrencySettings{
|
||||
{
|
||||
ExchangeName: "Binance",
|
||||
Asset: asset.Spot,
|
||||
Base: cp.Base,
|
||||
Quote: cp.Quote,
|
||||
SpotDetails: &config.SpotDetails{
|
||||
InitialQuoteFunds: &leet,
|
||||
},
|
||||
BuySide: config.MinMax{},
|
||||
SellSide: config.MinMax{},
|
||||
MakerFee: &decimal.Zero,
|
||||
TakerFee: &decimal.Zero,
|
||||
},
|
||||
},
|
||||
DataSettings: config.DataSettings{
|
||||
DataType: common.CandleStr,
|
||||
Interval: gctkline.OneMin,
|
||||
APIData: &config.APIData{
|
||||
StartDate: time.Now().Add(-time.Minute * 5),
|
||||
EndDate: time.Now(),
|
||||
}},
|
||||
StrategySettings: config.StrategySettings{
|
||||
Name: dollarcostaverage.Name,
|
||||
CustomSettings: map[string]interface{}{
|
||||
"hello": "moto",
|
||||
},
|
||||
},
|
||||
}
|
||||
em := engine.ExchangeManager{}
|
||||
exch, err := em.NewExchangeByName("Binance")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true}}
|
||||
|
||||
_, err = bt.loadData(cfg, exch, cp, asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDataDatabase(t *testing.T) {
|
||||
t.Parallel()
|
||||
bt := BackTest{
|
||||
Reports: &report.Data{},
|
||||
}
|
||||
cp := currency.NewPair(currency.BTC, currency.USDT)
|
||||
cfg := &config.Config{
|
||||
CurrencySettings: []config.CurrencySettings{
|
||||
{
|
||||
ExchangeName: "Binance",
|
||||
Asset: asset.Spot,
|
||||
Base: cp.Base,
|
||||
Quote: cp.Quote,
|
||||
SpotDetails: &config.SpotDetails{
|
||||
InitialQuoteFunds: &leet,
|
||||
},
|
||||
BuySide: config.MinMax{},
|
||||
SellSide: config.MinMax{},
|
||||
MakerFee: &decimal.Zero,
|
||||
TakerFee: &decimal.Zero,
|
||||
},
|
||||
},
|
||||
DataSettings: config.DataSettings{
|
||||
DataType: common.CandleStr,
|
||||
Interval: gctkline.OneMin,
|
||||
DatabaseData: &config.DatabaseData{
|
||||
Config: database.Config{
|
||||
Enabled: true,
|
||||
Driver: "sqlite3",
|
||||
ConnectionDetails: drivers.ConnectionDetails{
|
||||
Database: t.TempDir() + "gocryptotrader.db",
|
||||
},
|
||||
},
|
||||
StartDate: time.Now().Add(-time.Minute),
|
||||
EndDate: time.Now(),
|
||||
InclusiveEndDate: true,
|
||||
}},
|
||||
StrategySettings: config.StrategySettings{
|
||||
Name: dollarcostaverage.Name,
|
||||
CustomSettings: map[string]interface{}{
|
||||
"hello": "moto",
|
||||
},
|
||||
},
|
||||
}
|
||||
em := engine.ExchangeManager{}
|
||||
exch, err := em.NewExchangeByName("Binance")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true}}
|
||||
bt.databaseManager, err = engine.SetupDatabaseConnectionManager(&cfg.DataSettings.DatabaseData.Config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = bt.loadData(cfg, exch, cp, asset.Spot, false)
|
||||
if err != nil && !strings.Contains(err.Error(), "unable to retrieve data from GoCryptoTrader database") {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDataCSV(t *testing.T) {
|
||||
t.Parallel()
|
||||
bt := BackTest{
|
||||
Reports: &report.Data{},
|
||||
}
|
||||
cp := currency.NewPair(currency.BTC, currency.USDT)
|
||||
cfg := &config.Config{
|
||||
CurrencySettings: []config.CurrencySettings{
|
||||
{
|
||||
ExchangeName: "Binance",
|
||||
Asset: asset.Spot,
|
||||
Base: cp.Base,
|
||||
Quote: cp.Quote,
|
||||
SpotDetails: &config.SpotDetails{
|
||||
InitialQuoteFunds: &leet,
|
||||
},
|
||||
BuySide: config.MinMax{},
|
||||
SellSide: config.MinMax{},
|
||||
MakerFee: &decimal.Zero,
|
||||
TakerFee: &decimal.Zero,
|
||||
},
|
||||
},
|
||||
DataSettings: config.DataSettings{
|
||||
DataType: common.CandleStr,
|
||||
Interval: gctkline.OneMin,
|
||||
CSVData: &config.CSVData{
|
||||
FullPath: "test",
|
||||
}},
|
||||
StrategySettings: config.StrategySettings{
|
||||
Name: dollarcostaverage.Name,
|
||||
CustomSettings: map[string]interface{}{
|
||||
"hello": "moto",
|
||||
},
|
||||
},
|
||||
}
|
||||
em := engine.ExchangeManager{}
|
||||
exch, err := em.NewExchangeByName("Binance")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true}}
|
||||
_, err = bt.loadData(cfg, exch, cp, asset.Spot, false)
|
||||
if err != nil &&
|
||||
!strings.Contains(err.Error(), "The system cannot find the file specified.") &&
|
||||
!strings.Contains(err.Error(), "no such file or directory") {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDataLive(t *testing.T) {
|
||||
t.Parallel()
|
||||
bt := BackTest{
|
||||
Reports: &report.Data{},
|
||||
shutdown: make(chan struct{}),
|
||||
}
|
||||
cp := currency.NewPair(currency.BTC, currency.USDT)
|
||||
cfg := &config.Config{
|
||||
CurrencySettings: []config.CurrencySettings{
|
||||
{
|
||||
ExchangeName: "Binance",
|
||||
Asset: asset.Spot,
|
||||
Base: cp.Base,
|
||||
Quote: cp.Quote,
|
||||
SpotDetails: &config.SpotDetails{
|
||||
InitialQuoteFunds: &leet,
|
||||
},
|
||||
BuySide: config.MinMax{},
|
||||
SellSide: config.MinMax{},
|
||||
MakerFee: &decimal.Zero,
|
||||
TakerFee: &decimal.Zero,
|
||||
},
|
||||
},
|
||||
DataSettings: config.DataSettings{
|
||||
DataType: common.CandleStr,
|
||||
Interval: gctkline.OneMin,
|
||||
LiveData: &config.LiveData{
|
||||
APIKeyOverride: "test",
|
||||
APISecretOverride: "test",
|
||||
APIClientIDOverride: "test",
|
||||
API2FAOverride: "test",
|
||||
RealOrders: true,
|
||||
}},
|
||||
StrategySettings: config.StrategySettings{
|
||||
Name: dollarcostaverage.Name,
|
||||
CustomSettings: map[string]interface{}{
|
||||
"hello": "moto",
|
||||
},
|
||||
},
|
||||
}
|
||||
em := engine.ExchangeManager{}
|
||||
exch, err := em.NewExchangeByName("Binance")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exch.SetDefaults()
|
||||
b := exch.GetBase()
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true},
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true}}
|
||||
_, err = bt.loadData(cfg, exch, cp, asset.Spot, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
bt.Stop()
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// ErrInitialFundsZero is an error when initial funds are zero or less
|
||||
var ErrInitialFundsZero = errors.New("initial funds < 0")
|
||||
var ErrInitialFundsZero = errors.New("initial funds <= 0")
|
||||
|
||||
// Holding contains pricing statistics for a given time
|
||||
// for a given exchange asset pair
|
||||
|
||||
@@ -31,13 +31,13 @@ func addReason(reason, msg string) string {
|
||||
|
||||
// PrintTotalResults outputs all results to the CMD
|
||||
func (s *Statistic) PrintTotalResults() {
|
||||
log.Info(common.Statistics, common.ColourH1+"------------------Strategy-----------------------------------"+common.ColourDefault)
|
||||
log.Info(common.Statistics, common.CMDColours.H1+"------------------Strategy-----------------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.Statistics, "Strategy Name: %v", s.StrategyName)
|
||||
log.Infof(common.Statistics, "Strategy Nickname: %v", s.StrategyNickname)
|
||||
log.Infof(common.Statistics, "Strategy Goal: %v\n\n", s.StrategyGoal)
|
||||
|
||||
log.Info(common.Statistics, common.ColourH2+"------------------Total Results------------------------------"+common.ColourDefault)
|
||||
log.Info(common.Statistics, common.ColourH3+"------------------Orders-------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.Statistics, common.CMDColours.H2+"------------------Total Results------------------------------"+common.CMDColours.Default)
|
||||
log.Info(common.Statistics, common.CMDColours.H3+"------------------Orders-------------------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.Statistics, "Total buy orders: %v", convert.IntToHumanFriendlyString(s.TotalBuyOrders, ","))
|
||||
log.Infof(common.Statistics, "Total sell orders: %v", convert.IntToHumanFriendlyString(s.TotalSellOrders, ","))
|
||||
log.Infof(common.Statistics, "Total long orders: %v", convert.IntToHumanFriendlyString(s.TotalLongOrders, ","))
|
||||
@@ -45,7 +45,7 @@ func (s *Statistic) PrintTotalResults() {
|
||||
log.Infof(common.Statistics, "Total orders: %v\n\n", convert.IntToHumanFriendlyString(s.TotalOrders, ","))
|
||||
|
||||
if s.BiggestDrawdown != nil {
|
||||
log.Info(common.Statistics, common.ColourH3+"------------------Biggest Drawdown-----------------------"+common.ColourDefault)
|
||||
log.Info(common.Statistics, common.CMDColours.H3+"------------------Biggest Drawdown-----------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.Statistics, "Exchange: %v Asset: %v Currency: %v", s.BiggestDrawdown.Exchange, s.BiggestDrawdown.Asset, s.BiggestDrawdown.Pair)
|
||||
log.Infof(common.Statistics, "Highest Price: %s", convert.DecimalToHumanFriendlyString(s.BiggestDrawdown.MaxDrawdown.Highest.Value, 8, ".", ","))
|
||||
log.Infof(common.Statistics, "Highest Price Time: %v", s.BiggestDrawdown.MaxDrawdown.Highest.Time)
|
||||
@@ -56,7 +56,7 @@ func (s *Statistic) PrintTotalResults() {
|
||||
log.Infof(common.Statistics, "Drawdown length: %v candles\n\n", convert.IntToHumanFriendlyString(s.BiggestDrawdown.MaxDrawdown.IntervalDuration, ","))
|
||||
}
|
||||
if s.BestMarketMovement != nil && s.BestStrategyResults != nil {
|
||||
log.Info(common.Statistics, common.ColourH4+"------------------Orders----------------------------------"+common.ColourDefault)
|
||||
log.Info(common.Statistics, common.CMDColours.H4+"------------------Orders----------------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.Statistics, "Best performing market movement: %v %v %v %v%%", s.BestMarketMovement.Exchange, s.BestMarketMovement.Asset, s.BestMarketMovement.Pair, convert.DecimalToHumanFriendlyString(s.BestMarketMovement.MarketMovement, 2, ".", ","))
|
||||
log.Infof(common.Statistics, "Best performing strategy movement: %v %v %v %v%%\n\n", s.BestStrategyResults.Exchange, s.BestStrategyResults.Asset, s.BestStrategyResults.Pair, convert.DecimalToHumanFriendlyString(s.BestStrategyResults.StrategyMovement, 2, ".", ","))
|
||||
}
|
||||
@@ -67,9 +67,9 @@ func (s *Statistic) PrintTotalResults() {
|
||||
// grouped by time to allow a clearer picture of events
|
||||
func (s *Statistic) PrintAllEventsChronologically() {
|
||||
var results []eventOutputHolder
|
||||
log.Info(common.Statistics, common.ColourH1+"------------------Events-------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.Statistics, common.CMDColours.H1+"------------------Events-------------------------------------"+common.CMDColours.Default)
|
||||
var errs gctcommon.Errors
|
||||
colour := common.ColourDefault
|
||||
colour := common.CMDColours.Default
|
||||
for exch, x := range s.ExchangeAssetPairStatistics {
|
||||
for a, y := range x {
|
||||
for pair, currencyStatistic := range y {
|
||||
@@ -84,7 +84,7 @@ func (s *Statistic) PrintAllEventsChronologically() {
|
||||
direction == order.TransferredFunds ||
|
||||
direction == order.UnknownSide {
|
||||
if direction == order.DoNothing {
|
||||
colour = common.ColourDarkGrey
|
||||
colour = common.CMDColours.DarkGrey
|
||||
}
|
||||
msg := fmt.Sprintf(colour+
|
||||
"%v %v%v%v| Price: %v\tDirection: %v",
|
||||
@@ -95,13 +95,13 @@ func (s *Statistic) PrintAllEventsChronologically() {
|
||||
currencyStatistic.Events[i].FillEvent.GetClosePrice().Round(8),
|
||||
currencyStatistic.Events[i].FillEvent.GetDirection())
|
||||
msg = addReason(currencyStatistic.Events[i].FillEvent.GetConcatReasons(), msg)
|
||||
msg += common.ColourDefault
|
||||
msg += common.CMDColours.Default
|
||||
results = addEventOutputToTime(results, currencyStatistic.Events[i].FillEvent.GetTime(), msg)
|
||||
} else {
|
||||
// successful order!
|
||||
colour = common.ColourSuccess
|
||||
colour = common.CMDColours.Success
|
||||
if currencyStatistic.Events[i].FillEvent.IsLiquidated() {
|
||||
colour = common.ColourError
|
||||
colour = common.CMDColours.Error
|
||||
}
|
||||
msg := fmt.Sprintf(colour+
|
||||
"%v %v%v%v| Price: %v\tDirection %v\tOrder placed: Amount: %v\tFee: %v\tTotal: %v",
|
||||
@@ -115,7 +115,7 @@ func (s *Statistic) PrintAllEventsChronologically() {
|
||||
currencyStatistic.Events[i].FillEvent.GetExchangeFee(),
|
||||
currencyStatistic.Events[i].FillEvent.GetTotal().Round(8))
|
||||
msg = addReason(currencyStatistic.Events[i].FillEvent.GetConcatReasons(), msg)
|
||||
msg += common.ColourDefault
|
||||
msg += common.CMDColours.Default
|
||||
results = addEventOutputToTime(results, currencyStatistic.Events[i].FillEvent.GetTime(), msg)
|
||||
}
|
||||
case currencyStatistic.Events[i].SignalEvent != nil:
|
||||
@@ -126,7 +126,7 @@ func (s *Statistic) PrintAllEventsChronologically() {
|
||||
fSIL(currencyStatistic.Events[i].SignalEvent.Pair().String(), limit14),
|
||||
currencyStatistic.Events[i].SignalEvent.GetClosePrice().Round(8))
|
||||
msg = addReason(currencyStatistic.Events[i].SignalEvent.GetConcatReasons(), msg)
|
||||
msg += common.ColourDefault
|
||||
msg += common.CMDColours.Default
|
||||
results = addEventOutputToTime(results, currencyStatistic.Events[i].SignalEvent.GetTime(), msg)
|
||||
case currencyStatistic.Events[i].DataEvent != nil:
|
||||
msg := fmt.Sprintf("%v %v%v%v| Price: $%v",
|
||||
@@ -136,10 +136,10 @@ func (s *Statistic) PrintAllEventsChronologically() {
|
||||
fSIL(currencyStatistic.Events[i].DataEvent.Pair().String(), limit14),
|
||||
currencyStatistic.Events[i].DataEvent.GetClosePrice().Round(8))
|
||||
msg = addReason(currencyStatistic.Events[i].DataEvent.GetConcatReasons(), msg)
|
||||
msg += common.ColourDefault
|
||||
msg += common.CMDColours.Default
|
||||
results = addEventOutputToTime(results, currencyStatistic.Events[i].DataEvent.GetTime(), msg)
|
||||
default:
|
||||
errs = append(errs, fmt.Errorf(common.ColourError+"%v%v%v unexpected data received %+v"+common.ColourDefault, exch, a, fSIL(pair.String(), limit14), currencyStatistic.Events[i]))
|
||||
errs = append(errs, fmt.Errorf(common.CMDColours.Error+"%v%v%v unexpected data received %+v"+common.CMDColours.Default, exch, a, fSIL(pair.String(), limit14), currencyStatistic.Events[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,7 +157,7 @@ func (s *Statistic) PrintAllEventsChronologically() {
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
log.Info(common.Statistics, common.ColourError+"------------------Errors-------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.Statistics, common.CMDColours.Error+"------------------Errors-------------------------------------"+common.CMDColours.Default)
|
||||
for i := range errs {
|
||||
log.Error(common.Statistics, errs[i].Error())
|
||||
}
|
||||
@@ -179,7 +179,7 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.
|
||||
c.TotalOrders = c.BuyOrders + c.SellOrders + c.ShortOrders + c.LongOrders
|
||||
last.Holdings.TotalValueLost = last.Holdings.TotalValueLostToSlippage.Add(last.Holdings.TotalValueLostToVolumeSizing)
|
||||
sep := fmt.Sprintf("%v %v %v |\t", fSIL(e, limit12), fSIL(a.String(), limit10), fSIL(p.String(), limit14))
|
||||
currStr := fmt.Sprintf(common.ColourH1+"------------------Stats for %v %v %v------------------------------------------------------"+common.ColourDefault, e, a, p)
|
||||
currStr := fmt.Sprintf(common.CMDColours.H1+"------------------Stats for %v %v %v------------------------------------------------------"+common.CMDColours.Default, e, a, p)
|
||||
log.Infof(common.CurrencyStatistics, currStr[:70])
|
||||
if a.IsFutures() {
|
||||
log.Infof(common.CurrencyStatistics, "%s Long orders: %s", sep, convert.IntToHumanFriendlyString(c.LongOrders, ","))
|
||||
@@ -199,17 +199,17 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.
|
||||
|
||||
log.Infof(common.CurrencyStatistics, "%s Total orders: %s", sep, convert.IntToHumanFriendlyString(c.TotalOrders, ","))
|
||||
|
||||
log.Info(common.CurrencyStatistics, common.ColourH2+"------------------Max Drawdown-------------------------------"+common.ColourDefault)
|
||||
log.Info(common.CurrencyStatistics, common.CMDColours.H2+"------------------Max Drawdown-------------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.CurrencyStatistics, "%s Highest Price of drawdown: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.MaxDrawdown.Highest.Value, 8, ".", ","), c.MaxDrawdown.Highest.Time)
|
||||
log.Infof(common.CurrencyStatistics, "%s Lowest Price of drawdown: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.MaxDrawdown.Lowest.Value, 8, ".", ","), c.MaxDrawdown.Lowest.Time)
|
||||
log.Infof(common.CurrencyStatistics, "%s Calculated Drawdown: %s%%", sep, convert.DecimalToHumanFriendlyString(c.MaxDrawdown.DrawdownPercent, 8, ".", ","))
|
||||
log.Infof(common.CurrencyStatistics, "%s Difference: %s", sep, convert.DecimalToHumanFriendlyString(c.MaxDrawdown.Highest.Value.Sub(c.MaxDrawdown.Lowest.Value), 2, ".", ","))
|
||||
log.Infof(common.CurrencyStatistics, "%s Drawdown length: %s", sep, convert.IntToHumanFriendlyString(c.MaxDrawdown.IntervalDuration, ","))
|
||||
if !usingExchangeLevelFunding {
|
||||
log.Info(common.CurrencyStatistics, common.ColourH2+"------------------Ratios------------------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.CurrencyStatistics, common.ColourH3+"------------------Rates-------------------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.CurrencyStatistics, common.CMDColours.H2+"------------------Ratios------------------------------------------------"+common.CMDColours.Default)
|
||||
log.Info(common.CurrencyStatistics, common.CMDColours.H3+"------------------Rates-------------------------------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.CurrencyStatistics, "%s Compound Annual Growth Rate: %s", sep, convert.DecimalToHumanFriendlyString(c.CompoundAnnualGrowthRate, 2, ".", ","))
|
||||
log.Info(common.CurrencyStatistics, common.ColourH4+"------------------Arithmetic--------------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.CurrencyStatistics, common.CMDColours.H4+"------------------Arithmetic--------------------------------------------"+common.CMDColours.Default)
|
||||
if c.ShowMissingDataWarning {
|
||||
log.Infoln(common.CurrencyStatistics, "Missing data was detected during this backtesting run")
|
||||
log.Infoln(common.CurrencyStatistics, "Ratio calculations will be skewed")
|
||||
@@ -219,7 +219,7 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.
|
||||
log.Infof(common.CurrencyStatistics, "%s Information ratio: %v", sep, c.ArithmeticRatios.InformationRatio.Round(4))
|
||||
log.Infof(common.CurrencyStatistics, "%s Calmar ratio: %v", sep, c.ArithmeticRatios.CalmarRatio.Round(4))
|
||||
|
||||
log.Info(common.CurrencyStatistics, common.ColourH4+"------------------Geometric--------------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.CurrencyStatistics, common.CMDColours.H4+"------------------Geometric--------------------------------------------"+common.CMDColours.Default)
|
||||
if c.ShowMissingDataWarning {
|
||||
log.Infoln(common.CurrencyStatistics, "Missing data was detected during this backtesting run")
|
||||
log.Infoln(common.CurrencyStatistics, "Ratio calculations will be skewed")
|
||||
@@ -230,7 +230,7 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.
|
||||
log.Infof(common.CurrencyStatistics, "%s Calmar ratio: %v", sep, c.GeometricRatios.CalmarRatio.Round(4))
|
||||
}
|
||||
|
||||
log.Info(common.CurrencyStatistics, common.ColourH2+"------------------Results------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.CurrencyStatistics, common.CMDColours.H2+"------------------Results------------------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.CurrencyStatistics, "%s Starting Close Price: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.StartingClosePrice.Value, 8, ".", ","), c.StartingClosePrice.Time)
|
||||
log.Infof(common.CurrencyStatistics, "%s Finishing Close Price: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.EndingClosePrice.Value, 8, ".", ","), c.EndingClosePrice.Time)
|
||||
log.Infof(common.CurrencyStatistics, "%s Lowest Close Price: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.LowestClosePrice.Value, 8, ".", ","), c.LowestClosePrice.Time)
|
||||
@@ -263,7 +263,7 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.
|
||||
log.Infof(common.CurrencyStatistics, "%s Final Realised PNL: %s", sep, convert.DecimalToHumanFriendlyString(realised.PNL, 8, ".", ","))
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
log.Info(common.CurrencyStatistics, common.ColourError+"------------------Errors-------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.CurrencyStatistics, common.CMDColours.Error+"------------------Errors-------------------------------------"+common.CMDColours.Default)
|
||||
for i := range errs {
|
||||
log.Error(common.CurrencyStatistics, errs[i].Error())
|
||||
}
|
||||
@@ -284,10 +284,10 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error {
|
||||
}
|
||||
}
|
||||
if len(spotResults) > 0 || len(futuresResults) > 0 {
|
||||
log.Info(common.FundingStatistics, common.ColourH1+"------------------Funding------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.FundingStatistics, common.CMDColours.H1+"------------------Funding------------------------------------"+common.CMDColours.Default)
|
||||
}
|
||||
if len(spotResults) > 0 {
|
||||
log.Info(common.FundingStatistics, common.ColourH2+"------------------Funding Spot Item Results------------------"+common.ColourDefault)
|
||||
log.Info(common.FundingStatistics, common.CMDColours.H2+"------------------Funding Spot Item Results------------------"+common.CMDColours.Default)
|
||||
for i := range spotResults {
|
||||
sep := fmt.Sprintf("%v%v%v| ", fSIL(spotResults[i].ReportItem.Exchange, limit12), fSIL(spotResults[i].ReportItem.Asset.String(), limit10), fSIL(spotResults[i].ReportItem.Currency.String(), limit14))
|
||||
if !spotResults[i].ReportItem.PairedWith.IsEmpty() {
|
||||
@@ -314,7 +314,7 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error {
|
||||
}
|
||||
}
|
||||
if len(futuresResults) > 0 {
|
||||
log.Info(common.FundingStatistics, common.ColourH2+"------------------Funding Futures Item Results---------------"+common.ColourDefault)
|
||||
log.Info(common.FundingStatistics, common.CMDColours.H2+"------------------Funding Futures Item Results---------------"+common.CMDColours.Default)
|
||||
for i := range futuresResults {
|
||||
sep := fmt.Sprintf("%v%v%v| ", fSIL(futuresResults[i].ReportItem.Exchange, limit12), fSIL(futuresResults[i].ReportItem.Asset.String(), limit10), fSIL(futuresResults[i].ReportItem.Currency.String(), limit14))
|
||||
log.Infof(common.FundingStatistics, "%s Is Collateral: %v", sep, futuresResults[i].IsCollateral)
|
||||
@@ -340,7 +340,7 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error {
|
||||
if f.Report.DisableUSDTracking {
|
||||
return nil
|
||||
}
|
||||
log.Info(common.FundingStatistics, common.ColourH2+"------------------USD Tracking Totals------------------------"+common.ColourDefault)
|
||||
log.Info(common.FundingStatistics, common.CMDColours.H2+"------------------USD Tracking Totals------------------------"+common.CMDColours.Default)
|
||||
sep := "USD Tracking Total |\t"
|
||||
|
||||
log.Infof(common.FundingStatistics, "%s Initial value: $%s", sep, convert.DecimalToHumanFriendlyString(f.Report.InitialFunds, 8, ".", ","))
|
||||
@@ -352,14 +352,14 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error {
|
||||
log.Infof(common.FundingStatistics, "%s Highest funds: $%s at %v", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.HighestHoldingValue.Value, 8, ".", ","), f.TotalUSDStatistics.HighestHoldingValue.Time)
|
||||
log.Infof(common.FundingStatistics, "%s Lowest funds: $%s at %v", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.LowestHoldingValue.Value, 8, ".", ","), f.TotalUSDStatistics.LowestHoldingValue.Time)
|
||||
|
||||
log.Info(common.FundingStatistics, common.ColourH3+"------------------Ratios------------------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.FundingStatistics, common.ColourH4+"------------------Rates-------------------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.FundingStatistics, common.CMDColours.H3+"------------------Ratios------------------------------------------------"+common.CMDColours.Default)
|
||||
log.Info(common.FundingStatistics, common.CMDColours.H4+"------------------Rates-------------------------------------------------"+common.CMDColours.Default)
|
||||
log.Infof(common.FundingStatistics, "%s Risk free rate: %s%%", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.RiskFreeRate.Mul(decimal.NewFromInt(100)), 2, ".", ","))
|
||||
log.Infof(common.FundingStatistics, "%s Compound Annual Growth Rate: %v%%", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.CompoundAnnualGrowthRate, 8, ".", ","))
|
||||
if f.TotalUSDStatistics.ArithmeticRatios == nil || f.TotalUSDStatistics.GeometricRatios == nil {
|
||||
return fmt.Errorf("%w missing ratio calculations", common.ErrNilArguments)
|
||||
}
|
||||
log.Info(common.FundingStatistics, common.ColourH4+"------------------Arithmetic--------------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.FundingStatistics, common.CMDColours.H4+"------------------Arithmetic--------------------------------------------"+common.CMDColours.Default)
|
||||
if wasAnyDataMissing {
|
||||
log.Infoln(common.FundingStatistics, "Missing data was detected during this backtesting run")
|
||||
log.Infoln(common.FundingStatistics, "Ratio calculations will be skewed")
|
||||
@@ -369,7 +369,7 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error {
|
||||
log.Infof(common.FundingStatistics, "%s Information ratio: %v", sep, f.TotalUSDStatistics.ArithmeticRatios.InformationRatio.Round(4))
|
||||
log.Infof(common.FundingStatistics, "%s Calmar ratio: %v", sep, f.TotalUSDStatistics.ArithmeticRatios.CalmarRatio.Round(4))
|
||||
|
||||
log.Info(common.FundingStatistics, common.ColourH4+"------------------Geometric--------------------------------------------"+common.ColourDefault)
|
||||
log.Info(common.FundingStatistics, common.CMDColours.H4+"------------------Geometric--------------------------------------------"+common.CMDColours.Default)
|
||||
if wasAnyDataMissing {
|
||||
log.Infoln(common.FundingStatistics, "Missing data was detected during this backtesting run")
|
||||
log.Infoln(common.FundingStatistics, "Ratio calculations will be skewed")
|
||||
|
||||
@@ -182,7 +182,7 @@ func (s *Statistic) AddComplianceSnapshotForTime(c compliance.Snapshot, e fill.E
|
||||
// CalculateAllResults calculates the statistics of all exchange asset pair holdings,
|
||||
// orders, ratios and drawdowns
|
||||
func (s *Statistic) CalculateAllResults() error {
|
||||
log.Info(common.Statistics, "calculating backtesting results")
|
||||
log.Info(common.Statistics, "Calculating backtesting results")
|
||||
s.PrintAllEventsChronologically()
|
||||
currCount := 0
|
||||
var finalResults []FinalResultsHolder
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -10,13 +11,14 @@ import (
|
||||
"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/common/file"
|
||||
"github.com/thrasher-corp/gocryptotrader/engine"
|
||||
"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
|
||||
var singleRunStrategyPath, templatePath, outputPath, btConfigDir, strategyPluginPath string
|
||||
var printLogo, generateReport, darkReport, colourOutput, logSubHeader bool
|
||||
|
||||
func main() {
|
||||
wd, err := os.Getwd()
|
||||
@@ -24,18 +26,96 @@ func main() {
|
||||
fmt.Printf("Could not get working directory. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
parseFlags(wd)
|
||||
if !colourOutput {
|
||||
|
||||
flags := parseFlags(wd)
|
||||
var btCfg *config.BacktesterConfig
|
||||
if btConfigDir == "" {
|
||||
btConfigDir = config.DefaultBTConfigDir
|
||||
log.Infof(log.Global, "Blank config received, using default path '%v'", btConfigDir)
|
||||
}
|
||||
fe := file.Exists(btConfigDir)
|
||||
switch {
|
||||
case fe:
|
||||
btCfg, err = config.ReadBacktesterConfigFromPath(btConfigDir)
|
||||
if err != nil {
|
||||
fmt.Printf("Could not read config. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
case !fe && btConfigDir == config.DefaultBTConfigDir:
|
||||
btCfg, err = config.GenerateDefaultConfig()
|
||||
if err != nil {
|
||||
fmt.Printf("Could not generate config. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var btCfgJSON []byte
|
||||
btCfgJSON, err = json.MarshalIndent(btCfg, "", " ")
|
||||
if err != nil {
|
||||
fmt.Printf("Could not generate config. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = os.MkdirAll(config.DefaultBTDir, file.DefaultPermissionOctal)
|
||||
if err != nil {
|
||||
fmt.Printf("Could not generate config. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = os.WriteFile(btConfigDir, btCfgJSON, file.DefaultPermissionOctal)
|
||||
if err != nil {
|
||||
fmt.Printf("Could not generate config. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
default:
|
||||
log.Errorf(log.Global, "Non-standard config '%v' does not exist. Exiting...", btConfigDir)
|
||||
return
|
||||
}
|
||||
|
||||
flagSet := engine.FlagSet(flags)
|
||||
flagSet.WithBool("printlogo", &printLogo, btCfg.PrintLogo)
|
||||
flagSet.WithBool("darkreport", &darkReport, btCfg.Report.DarkMode)
|
||||
flagSet.WithBool("generatereport", &generateReport, btCfg.Report.GenerateReport)
|
||||
flagSet.WithBool("logsubheaders", &logSubHeader, btCfg.LogSubheaders)
|
||||
flagSet.WithBool("colouroutput", &colourOutput, btCfg.UseCMDColours)
|
||||
|
||||
if singleRunStrategyPath != "" && !file.Exists(singleRunStrategyPath) {
|
||||
fmt.Printf("Strategy config path not found '%v'", singleRunStrategyPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
defaultTemplate := filepath.Join(
|
||||
wd,
|
||||
"report",
|
||||
"tpl.gohtml")
|
||||
defaultReportOutput := filepath.Join(
|
||||
wd,
|
||||
"results")
|
||||
|
||||
if templatePath != defaultTemplate {
|
||||
btCfg.Report.TemplatePath = templatePath
|
||||
}
|
||||
if !file.Exists(btCfg.Report.TemplatePath) {
|
||||
fmt.Printf("Report template path not found '%v'", btCfg.Report.TemplatePath)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if outputPath != defaultReportOutput {
|
||||
btCfg.Report.OutputPath = outputPath
|
||||
}
|
||||
if !file.Exists(btCfg.Report.OutputPath) {
|
||||
fmt.Printf("Report output path not found '%v'", btCfg.Report.OutputPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if colourOutput {
|
||||
common.SetColours(&btCfg.Colours)
|
||||
} else {
|
||||
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
|
||||
log.GlobalLogConfig.AdvancedSettings.ShowLogSystemName = &logSubHeader
|
||||
log.GlobalLogConfig.AdvancedSettings.Headers.Info = common.CMDColours.Info + "[INFO]" + common.CMDColours.Default
|
||||
log.GlobalLogConfig.AdvancedSettings.Headers.Warn = common.CMDColours.Warn + "[WARN]" + common.CMDColours.Default
|
||||
log.GlobalLogConfig.AdvancedSettings.Headers.Debug = common.CMDColours.Debug + "[DEBUG]" + common.CMDColours.Default
|
||||
log.GlobalLogConfig.AdvancedSettings.Headers.Error = common.CMDColours.Error + "[ERROR]" + common.CMDColours.Default
|
||||
err = log.SetupGlobalLogger()
|
||||
if err != nil {
|
||||
fmt.Printf("Could not setup global logger. Error: %v.\n", err)
|
||||
@@ -48,81 +128,91 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if printLogo {
|
||||
fmt.Println(common.Logo())
|
||||
}
|
||||
|
||||
if strategyPluginPath == "" && btCfg.PluginPath != "" {
|
||||
strategyPluginPath = btCfg.PluginPath
|
||||
}
|
||||
if strategyPluginPath != "" {
|
||||
err = strategies.LoadCustomStrategies(strategyPluginPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Could not load custom strategies. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Infof(common.Backtester, "Loaded plugin %v\n", strategyPluginPath)
|
||||
}
|
||||
|
||||
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 singleRunStrategyPath != "" {
|
||||
dir := singleRunStrategyPath
|
||||
var cfg *config.Config
|
||||
cfg, err = config.ReadStrategyConfigFromFile(dir)
|
||||
if err != nil {
|
||||
log.Error(log.Global, err)
|
||||
fmt.Printf("Could not read strategy config. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = backtest.ExecuteStrategy(cfg, &config.BacktesterConfig{
|
||||
Report: config.Report{
|
||||
GenerateReport: generateReport,
|
||||
TemplatePath: btCfg.Report.TemplatePath,
|
||||
OutputPath: btCfg.Report.OutputPath,
|
||||
DarkMode: darkReport,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Could not execute strategy. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
btCfg.Report.DarkMode = darkReport
|
||||
btCfg.Report.GenerateReport = generateReport
|
||||
|
||||
go func(c *config.BacktesterConfig) {
|
||||
log.Info(log.GRPCSys, "Starting RPC server")
|
||||
s := backtest.SetupRPCServer(c)
|
||||
err = backtest.StartRPCServer(s)
|
||||
if err != nil {
|
||||
fmt.Printf("Could not start RPC server. Error: %v.\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info(log.GRPCSys, "Ready to receive commands")
|
||||
}(btCfg)
|
||||
interrupt := signaler.WaitForInterrupt()
|
||||
log.Infof(log.Global, "Captured %v, shutdown requested.\n", interrupt)
|
||||
log.Infoln(log.Global, "Exiting.")
|
||||
}
|
||||
|
||||
func parseFlags(wd string) {
|
||||
func parseFlags(wd string) map[string]bool {
|
||||
defaultStrategy := filepath.Join(
|
||||
wd,
|
||||
"config",
|
||||
"strategyexamples",
|
||||
"dca-api-candles.strat")
|
||||
defaultTemplate := filepath.Join(
|
||||
wd,
|
||||
"report",
|
||||
"tpl.gohtml")
|
||||
|
||||
defaultReportOutput := filepath.Join(
|
||||
wd,
|
||||
"results")
|
||||
flag.StringVar(
|
||||
&configPath,
|
||||
"configpath",
|
||||
filepath.Join(
|
||||
wd,
|
||||
"config",
|
||||
"examples",
|
||||
"ftx-cash-carry.strat"),
|
||||
"the config containing strategy params")
|
||||
&singleRunStrategyPath,
|
||||
"singlerunstrategypath",
|
||||
"",
|
||||
fmt.Sprintf("path to a strategy file. Will execute strategy and exit, instead of creating a GRPC server. Example %v", defaultStrategy))
|
||||
flag.StringVar(
|
||||
&btConfigDir,
|
||||
"backtesterconfigpath",
|
||||
config.DefaultBTConfigDir,
|
||||
"the location of the backtester config")
|
||||
flag.StringVar(
|
||||
&templatePath,
|
||||
"templatepath",
|
||||
filepath.Join(
|
||||
wd,
|
||||
"report",
|
||||
"tpl.gohtml"),
|
||||
defaultTemplate,
|
||||
"the report template to use")
|
||||
flag.BoolVar(
|
||||
&generateReport,
|
||||
@@ -130,27 +220,15 @@ func parseFlags(wd string) {
|
||||
true,
|
||||
"whether to generate the report file")
|
||||
flag.StringVar(
|
||||
&reportOutput,
|
||||
&outputPath,
|
||||
"outputpath",
|
||||
filepath.Join(
|
||||
wd,
|
||||
"results"),
|
||||
defaultReportOutput,
|
||||
"the path where to output results")
|
||||
flag.BoolVar(
|
||||
&printLogo,
|
||||
"printlogo",
|
||||
true,
|
||||
"print out the logo to the command line, projected profits likely won't be affected if disabled")
|
||||
flag.BoolVar(
|
||||
&darkReport,
|
||||
"darkreport",
|
||||
false,
|
||||
"sets the output report to use a dark theme by default")
|
||||
flag.BoolVar(
|
||||
&verbose,
|
||||
"verbose",
|
||||
false,
|
||||
"if enabled, will set exchange requests to verbose for debugging purposes")
|
||||
flag.BoolVar(
|
||||
&colourOutput,
|
||||
"colouroutput",
|
||||
@@ -161,10 +239,20 @@ func parseFlags(wd string) {
|
||||
"logsubheader",
|
||||
true,
|
||||
"displays logging subheader to track where activity originates")
|
||||
flag.BoolVar(
|
||||
&printLogo,
|
||||
"printlogo",
|
||||
true,
|
||||
"shows the stunning, profit inducing logo on startup")
|
||||
flag.StringVar(
|
||||
&strategyPluginPath,
|
||||
"strategypluginpath",
|
||||
"",
|
||||
"example path: "+filepath.Join(wd, "plugins", "strategies", "example", "example.so"))
|
||||
flag.Parse()
|
||||
// collect flags
|
||||
flags := make(map[string]bool)
|
||||
// Stores the set flags
|
||||
flag.Visit(func(f *flag.Flag) { flags[f.Name] = true })
|
||||
return flags
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
// GenerateReport sends final data from statistics to a template
|
||||
// to create a lovely final report for someone to view
|
||||
func (d *Data) GenerateReport() error {
|
||||
log.Info(common.Report, "generating report")
|
||||
log.Info(common.Report, "Generating report")
|
||||
err := d.enhanceCandles()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -100,7 +100,7 @@ func (d *Data) GenerateReport() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof(common.Report, "successfully saved report to %v", filepath.Join(d.OutputPath, fileName))
|
||||
log.Infof(common.Report, "Successfully saved report to %v", filepath.Join(d.OutputPath, fileName))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{{define "backtester btcli" -}}
|
||||
{{template "backtester-header" .}}
|
||||
## {{.CapitalName}} overview
|
||||
|
||||
This folder contains the GoCryptoTrader Backtester CMD CLI application. It can be used to interact
|
||||
with the GoCryptoTrader Backtester's GRPC server and send commands to be processed server-side.
|
||||
|
||||
For a list of commands, you can run the following
|
||||
|
||||
```
|
||||
go run .
|
||||
```
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
{{template "contributions"}}
|
||||
{{template "donations" .}}
|
||||
{{end}}
|
||||
@@ -0,0 +1,61 @@
|
||||
{{define "backtester btrpc" -}}
|
||||
{{template "backtester-header" .}}
|
||||
## {{.CapitalName}} overview
|
||||
|
||||
|
||||
The GoCryptoTrader Backtester utilises gRPC for client/server interaction. Authentication is done
|
||||
by a self signed TLS cert, which only supports connections from localhost and also
|
||||
through basic authorisation specified by the users config file.
|
||||
|
||||
The GoCryptoTrader Backtester also supports a gRPC JSON proxy service for applications which can
|
||||
be toggled on or off depending on the users preference. This can be found in your config file
|
||||
under `grpcProxyEnabled` `grpcProxyListenAddress`. See `btrpc.swagger.json` for endpoint definitions
|
||||
|
||||
## Installation
|
||||
|
||||
The GoCryptoTrader Backtester requires a local installation of the Google protocol buffers
|
||||
compiler `protoc` v3.0.0 or above. Please install this via your local package
|
||||
manager or by downloading one of the releases from the official repository:
|
||||
|
||||
[protoc releases](https://github.com/protocolbuffers/protobuf/releases)
|
||||
|
||||
Then use `go install` to download the following packages:
|
||||
|
||||
```bash
|
||||
go install \
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
|
||||
google.golang.org/protobuf/cmd/protoc-gen-go \
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc
|
||||
```
|
||||
|
||||
This will place the following binaries in your `$GOBIN`;
|
||||
|
||||
* `protoc-gen-grpc-gateway`
|
||||
* `protoc-gen-openapiv2`
|
||||
* `protoc-gen-go`
|
||||
* `protoc-gen-go-grpc`
|
||||
|
||||
Make sure that your `$GOBIN` is in your `$PATH`.
|
||||
|
||||
### Linux / macOS / Windows
|
||||
|
||||
The GoCryptoTrader Backtester requires a local installation of the `buf` cli tool that tries to make Protobuf handling more easier and reliable,
|
||||
after [installation](https://docs.buf.build/installation) you'll need to run:
|
||||
|
||||
```shell
|
||||
buf mod update
|
||||
```
|
||||
|
||||
After previous command, make necessary changes to the `rpc.proto` spec file and run the generation command:
|
||||
|
||||
```shell
|
||||
buf generate
|
||||
```
|
||||
|
||||
If any changes were made, ensure that the `rpc.proto` file is formatted correctly by using `buf format -w`
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
{{template "contributions"}}
|
||||
{{template "donations" .}}
|
||||
{{end}}
|
||||
@@ -2,6 +2,65 @@
|
||||
{{template "backtester-header" .}}
|
||||
## {{.CapitalName}} package overview
|
||||
|
||||
## Backtester Config overview
|
||||
Below are the details for the GoCryptoTrader Backtester _application_ config. Strategy config overview is below this section
|
||||
|
||||
| Key | Description | Example |
|
||||
|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------|
|
||||
| PrintLogo | Whether to print the GoCryptoTrader Backtester logo on startup. Recommended because it looks good | `true` |
|
||||
| Verbose | Whether to receive verbose output. If running a GRPC server, it outputs to the server, not to the client | `false` |
|
||||
| LogSubheaders | Whether log output contains a descriptor of what area the log is coming from, for example `STRATEGY`. Helpful for debugging | `true` |
|
||||
| SingleRun | Whether or not to run the GoCryptoTrader Backtester to read the `SingleRunStrategyConfig` strategy and exit afterwards. If false, will run a GRPC server | `false` |
|
||||
| SingleRunStrategyConfig | The path to the strategy to run when `SingleRun` is `true` | `path\to\strategy\example.strat` |
|
||||
| Report | Contains details on the output report after a successful backtesting run | See Report table below |
|
||||
| GRPC | Contains GRPC server details | See GRPC table below |
|
||||
| UseCMDColours | If enabled, will output pretty colours of your choosing when running the application | `true` |
|
||||
| Colours | Contains details on what the colour definitions are | See Colours table below |
|
||||
|
||||
### Backtester Config Report overview
|
||||
|
||||
| Key | Description | Example |
|
||||
|----------------|----------------------------------------------------------------------|---------------------------------|
|
||||
| GenerateReport | Whether or not to output a report after a successful backtesting run | `true` |
|
||||
| TemplatePath | The path for the template to use when generating a report | `/backtester/report/tpl.gohtml` |
|
||||
| OutputPath | The path where report output is saved | `/backtester/results` |
|
||||
| DarkMode | Whether or not the report defaults to using dark mode | `true` |
|
||||
|
||||
### Backtester Config GRPC overview
|
||||
|
||||
| Key | Description | Example |
|
||||
|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
|
||||
| Username | Your username to negotiate a successful connection with the server | `rpcuser` |
|
||||
| Password | Your password to negotiate a successful connection with the server | `helloImTheDefaultPassword` |
|
||||
| Enabled | Whether the server is enabled. Setting this to `false` and `SingleRun` to `false` would be inadvisable | `true` |
|
||||
| ListenAddress | The listen address for the GRPC server | `localhost:42069` |
|
||||
| GRPCProxyEnabled | If enabled, creates a proxy server to interact with the GRPC server via HTTP commands | `true` |
|
||||
| GRPCProxyListenAddress | The address for the proxy to listen on | `localhost:9053` |
|
||||
| TLSDir | The directory for holding your TLS certifications to make connections to the server. Will be generated by default on startup if not present | `/backtester/config/location/` |
|
||||
|
||||
|
||||
### Backtester Config Colours overview
|
||||
|
||||
| Key | Description | Example |
|
||||
|----------|---------------------------------------------------------------------|----------------|
|
||||
| Default | The colour definition for default text output |`[0m` |
|
||||
| Green | The colour definition for when green is warranted, such as the logo |`[38;5;157m` |
|
||||
| White | The colour definition for when white is warranted such as the logo |`[38;5;255m` |
|
||||
| Grey | The colour definition for grey | `[38;5;240m`|
|
||||
| DarkGrey | The colour definition for dark grey | `[38;5;243m`|
|
||||
| H1 | The colour definition for main headers | `[38;5;33m` |
|
||||
| H2 | The colour definition for sub headers | `[38;5;39m` |
|
||||
| H3 | The colour definition for sub sub headers | `[38;5;45m` |
|
||||
| H4 | The colour definition for sub sub sub headers | `[38;5;51m` |
|
||||
| Success | The colour definition for successful operations | `[38;5;40m` |
|
||||
| Info | The colour definition for when informing you of something | `[32m` |
|
||||
| Debug | The colour definition for debug output such as verbose | `[34m` |
|
||||
| Warn | The colour definition for when a warning occurs | `[33m` |
|
||||
| Error | The colour definition for when an error occurs | `[38;5;196m`|
|
||||
|
||||
|
||||
## Strategy Config overview
|
||||
|
||||
### What does the config package do?
|
||||
The config package contains a set of structs which allow for the customisation of the GoCryptoTrader Backtester when running.
|
||||
The GoCryptoTrader Backtester runs from reading config files (`.strat` files by default under `/examples`).
|
||||
@@ -20,172 +79,172 @@ See below for a set of tables and fields, expected values and what they can do
|
||||
|
||||
#### Config
|
||||
|
||||
| Key | Description |
|
||||
| --- | ------|
|
||||
| Nickname | A nickname for the specific config. When running multiple variants of the same strategy, use the nickname to help differentiate between runs |
|
||||
| Goal | A description of what you would hope the outcome to be. When verifying output, you can review and confirm whether the strategy met that goal |
|
||||
| CurrencySettings | Currency settings is an array of settings for each individual currency you wish to run the strategy against |
|
||||
| StrategySettings | Select which strategy to run, what custom settings to load and whether the strategy can assess multiple currencies at once to make more in-depth decisions |
|
||||
| FundingSettings | Defines whether individual funding settings can be used. Defines the funding exchange, asset, currencies at an individual level |
|
||||
| Key | Description |
|
||||
|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Nickname | A nickname for the specific config. When running multiple variants of the same strategy, use the nickname to help differentiate between runs |
|
||||
| Goal | A description of what you would hope the outcome to be. When verifying output, you can review and confirm whether the strategy met that goal |
|
||||
| CurrencySettings | Currency settings is an array of settings for each individual currency you wish to run the strategy against |
|
||||
| StrategySettings | Select which strategy to run, what custom settings to load and whether the strategy can assess multiple currencies at once to make more in-depth decisions |
|
||||
| FundingSettings | Defines whether individual funding settings can be used. Defines the funding exchange, asset, currencies at an individual level |
|
||||
| PortfolioSettings | Contains a list of global rules for the portfolio manager. CurrencySettings contain their own rules on things like how big a position is allowable, the portfolio manager rules are the same, but override any individual currency's settings |
|
||||
| StatisticSettings | Contains settings that impact statistics calculation. Such as the risk-free rate for the sharpe ratio |
|
||||
| StatisticSettings | Contains settings that impact statistics calculation. Such as the risk-free rate for the sharpe ratio |
|
||||
|
||||
|
||||
#### Strategy Settings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ------- | --- |
|
||||
| Name | The strategy to use | `rsi` |
|
||||
| UsesSimultaneousProcessing | This denotes whether multiple currencies are processed simultaneously with the strategy function `OnSimultaneousSignals`. Eg If you have multiple CurrencySettings and only wish to purchase BTC-USDT when XRP-DOGE is 1337, this setting is useful as you can analyse both signal events to output a purchase call for BTC | `true` |
|
||||
| CustomSettings | This is a map where you can enter custom settings for a strategy. The RSI strategy allows for customisation of the upper, lower and length variables to allow you to change them from 70, 30 and 14 respectively to 69, 36, 12 | `"custom-settings": { "rsi-high": 70, "rsi-low": 30, "rsi-period": 14 } ` |
|
||||
| DisableUSDTracking | If `false`, will track all currencies used in your strategy against USD equivalent candles. For example, if you are running a strategy for BTC/XRP, then the GoCryptoTrader Backtester will also retreive candles data for BTC/USD and XRP/USD to then track strategy performance against a single currency. This also tracks against USDT and other USD tracked stablecoins, so one exchange supporting USDT and another BUSD will still allow unified strategy performance analysis. If disabled, will not track against USD, this can be especially helpful when running strategies under live, database and CSV based data | `false` |
|
||||
| Key | Description | Example |
|
||||
|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
|
||||
| Name | The strategy to use | `rsi` |
|
||||
| UsesSimultaneousProcessing | This denotes whether multiple currencies are processed simultaneously with the strategy function `OnSimultaneousSignals`. Eg If you have multiple CurrencySettings and only wish to purchase BTC-USDT when XRP-DOGE is 1337, this setting is useful as you can analyse both signal events to output a purchase call for BTC | `true` |
|
||||
| CustomSettings | This is a map where you can enter custom settings for a strategy. The RSI strategy allows for customisation of the upper, lower and length variables to allow you to change them from 70, 30 and 14 respectively to 69, 36, 12 | `"custom-settings": { "rsi-high": 70, "rsi-low": 30, "rsi-period": 14 } ` |
|
||||
| DisableUSDTracking | If `false`, will track all currencies used in your strategy against USD equivalent candles. For example, if you are running a strategy for BTC/XRP, then the GoCryptoTrader Backtester will also retreive candles data for BTC/USD and XRP/USD to then track strategy performance against a single currency. This also tracks against USDT and other USD tracked stablecoins, so one exchange supporting USDT and another BUSD will still allow unified strategy performance analysis. If disabled, will not track against USD, this can be especially helpful when running strategies under live, database and CSV based data | `false` |
|
||||
|
||||
|
||||
#### Funding Config Settings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ------- | --- |
|
||||
| Key | Description | Example |
|
||||
|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
|
||||
| UseExchangeLevelFunding | Allows shared funding at an exchange asset level. You can set funding for `USDT` and all pairs that feature `USDT` will have access to those funds when making orders. See [this](/backtester/funding/README.md) for more information | `false` |
|
||||
| ExchangeLevelFunding | An array of exchange level funding settings. See below, or [this](/backtester/funding/README.md) for more information | `[]` |
|
||||
| ExchangeLevelFunding | An array of exchange level funding settings. See below, or [this](/backtester/funding/README.md) for more information | `[]` |
|
||||
|
||||
|
||||
##### Funding Item Config Settings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ------- | ----- |
|
||||
| ExchangeName | The exchange to set funds. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` |
|
||||
| Asset | The asset type to set funds. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports| `spot` |
|
||||
| Currency | The currency to set funds | `BTC` |
|
||||
| InitialFunds | The initial funding for the currency | `1337` |
|
||||
| TransferFee | If your strategy utilises transferring of funds via the Funding Manager, this is deducted upon doing so | `0.005` |
|
||||
| Key | Description | Example |
|
||||
|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|
|
||||
| ExchangeName | The exchange to set funds. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` |
|
||||
| Asset | The asset type to set funds. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` |
|
||||
| Currency | The currency to set funds | `BTC` |
|
||||
| InitialFunds | The initial funding for the currency | `1337` |
|
||||
| TransferFee | If your strategy utilises transferring of funds via the Funding Manager, this is deducted upon doing so | `0.005` |
|
||||
|
||||
|
||||
#### Currency Settings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ------- | ----- |
|
||||
| ExchangeName | The exchange to load. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` |
|
||||
| Asset | The asset type. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports| `spot` |
|
||||
| Base | The base of a currency | `BTC` |
|
||||
| Quote | The quote of a currency | `USDT` |
|
||||
| InitialFunds | A legacy field, will be temporarily migrated to `InitialQuoteFunds` if present in your strat config | `` |
|
||||
| BuySide | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount | - |
|
||||
| SellSide | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount | - |
|
||||
| MinimumSlippagePercent | Is the lower bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 90, then the most a price can be affected is 10% | `90` |
|
||||
| MaximumSlippagePercent | Is the upper bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 99, then the least a price can be affected is 1%. Set both upper and lower to 100 to have no randomness applied to purchase events | `100` |
|
||||
| MakerFee | The fee to use when sizing and purchasing currency. If `nil`, will lookup an exchange's fee details | `0.001` |
|
||||
| TakerFee | Unused fee for when an order is placed in the orderbook, rather than taken from the orderbook. If `nil`, will lookup an exchange's fee details | `0.002` |
|
||||
| MaximumHoldingsRatio | When multiple currency settings are used, you may set a maximum holdings ratio to prevent having too large a stake in a single currency | `0.5` |
|
||||
| CanUseExchangeLimits | Will lookup exchange rules around purchase sizing eg minimum order increments of 0.0005. Note: Will retrieve up-to-date rules which may not have existed for the data you are using. Best to use this when considering to use this strategy live | `false` |
|
||||
| SkipCandleVolumeFitting | When placing orders, by default the BackTester will shrink an order's size to fit the candle data's volume so as to not rewrite history. Set this to `true` to ignore this and to set order size at what the portfolio manager prescribes | `false` |
|
||||
| SpotSettings | An optional field which contains initial funding data for SPOT currency pairs | See SpotSettings table below |
|
||||
| FuturesSettings | An optional field which contains leverage data for FUTURES currency pairs | See FuturesSettings table below |
|
||||
| Key | Description | Example |
|
||||
|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------|
|
||||
| ExchangeName | The exchange to load. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` |
|
||||
| Asset | The asset type. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` |
|
||||
| Base | The base of a currency | `BTC` |
|
||||
| Quote | The quote of a currency | `USDT` |
|
||||
| InitialFunds | A legacy field, will be temporarily migrated to `InitialQuoteFunds` if present in your strat config | `` |
|
||||
| BuySide | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount | - |
|
||||
| SellSide | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount | - |
|
||||
| MinimumSlippagePercent | Is the lower bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 90, then the most a price can be affected is 10% | `90` |
|
||||
| MaximumSlippagePercent | Is the upper bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 99, then the least a price can be affected is 1%. Set both upper and lower to 100 to have no randomness applied to purchase events | `100` |
|
||||
| MakerFee | The fee to use when sizing and purchasing currency. If `nil`, will lookup an exchange's fee details | `0.001` |
|
||||
| TakerFee | Unused fee for when an order is placed in the orderbook, rather than taken from the orderbook. If `nil`, will lookup an exchange's fee details | `0.002` |
|
||||
| MaximumHoldingsRatio | When multiple currency settings are used, you may set a maximum holdings ratio to prevent having too large a stake in a single currency | `0.5` |
|
||||
| CanUseExchangeLimits | Will lookup exchange rules around purchase sizing eg minimum order increments of 0.0005. Note: Will retrieve up-to-date rules which may not have existed for the data you are using. Best to use this when considering to use this strategy live | `false` |
|
||||
| SkipCandleVolumeFitting | When placing orders, by default the BackTester will shrink an order's size to fit the candle data's volume so as to not rewrite history. Set this to `true` to ignore this and to set order size at what the portfolio manager prescribes | `false` |
|
||||
| SpotSettings | An optional field which contains initial funding data for SPOT currency pairs | See SpotSettings table below |
|
||||
| FuturesSettings | An optional field which contains leverage data for FUTURES currency pairs | See FuturesSettings table below |
|
||||
|
||||
##### SpotSettings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ------- | ----- |
|
||||
| InitialBaseFunds | The funds that the GoCryptoTraderBacktester has for the base currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `2` |
|
||||
| Key | Description | Example |
|
||||
|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
|
||||
| InitialBaseFunds | The funds that the GoCryptoTraderBacktester has for the base currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `2` |
|
||||
| InitialQuoteFunds | The funds that the GoCryptoTraderBacktester has for the quote currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `10000` |
|
||||
|
||||
##### FuturesSettings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ------- | ----- |
|
||||
| Leverage | This struct defines the leverage rules that this specific currency setting must abide by | `1` |
|
||||
| Key | Description | Example |
|
||||
|----------|------------------------------------------------------------------------------------------|---------|
|
||||
| Leverage | This struct defines the leverage rules that this specific currency setting must abide by | `1` |
|
||||
|
||||
#### PortfolioSettings
|
||||
|
||||
| Key | Description |
|
||||
| --- | ------- |
|
||||
| Leverage | This struct defines the leverage rules that this specific currency setting must abide by |
|
||||
| BuySide | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount |
|
||||
| Key | Description |
|
||||
|----------|------------------------------------------------------------------------------------------------------------------------|
|
||||
| Leverage | This struct defines the leverage rules that this specific currency setting must abide by |
|
||||
| BuySide | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount |
|
||||
| SellSide | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount |
|
||||
|
||||
#### StatisticsSettings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| RiskFreeRate | The risk free rate used in the calculation of sharpe and sortino ratios | `0.03` |
|
||||
| Key | Description | Example |
|
||||
|--------------|-------------------------------------------------------------------------|---------|
|
||||
| RiskFreeRate | The risk free rate used in the calculation of sharpe and sortino ratios | `0.03` |
|
||||
|
||||
#### APIData
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| StartDate | The start date to retrieve data | `2021-01-23T11:00:00+11:00` |
|
||||
| EndDate | The end date to retrieve data | `2021-01-24T11:00:00+11:00` |
|
||||
| InclusiveEndDate | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` |
|
||||
| Key | Description | Example |
|
||||
|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| StartDate | The start date to retrieve data | `2021-01-23T11:00:00+11:00` |
|
||||
| EndDate | The end date to retrieve data | `2021-01-24T11:00:00+11:00` |
|
||||
| InclusiveEndDate | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` |
|
||||
|
||||
#### CSVData
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `candle` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| FullPath | The file to load | `/data/exchangelist.csv` |
|
||||
| Key | Description | Example |
|
||||
|----------|--------------------------------------------------------------------------------------------------------|--------------------------|
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `candle` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| FullPath | The file to load | `/data/exchangelist.csv` |
|
||||
|
||||
#### DatabaseData
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| StartDate | The start date to retrieve data | `2021-01-23T11:00:00+11:00` |
|
||||
| EndDate | The end date to retrieve data | `2021-01-24T11:00:00+11:00` |
|
||||
| Config | This is the same struct used as your GoCryptoTrader database config. See below tables for breakdown | `see below` |
|
||||
| Path | If using SQLite, the path to the directory, not the file. Leaving blank will use GoCryptoTrader's default database path | `` |
|
||||
| InclusiveEndDate | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` |
|
||||
| Key | Description | Example |
|
||||
|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| StartDate | The start date to retrieve data | `2021-01-23T11:00:00+11:00` |
|
||||
| EndDate | The end date to retrieve data | `2021-01-24T11:00:00+11:00` |
|
||||
| Config | This is the same struct used as your GoCryptoTrader database config. See below tables for breakdown | `see below` |
|
||||
| Path | If using SQLite, the path to the directory, not the file. Leaving blank will use GoCryptoTrader's default database path | `` |
|
||||
| InclusiveEndDate | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` |
|
||||
|
||||
##### database
|
||||
|
||||
| Config | Description | Example |
|
||||
| ------ | ----------- | ------- |
|
||||
| enabled | Enabled or disables the database connection subsystem | `true` |
|
||||
| verbose | Displays more information to the logger which can be helpful for debugging | `false` |
|
||||
| driver | The SQL driver to use. Can be `postgres` or `sqlite` | `sqlite` |
|
||||
| connectionDetails | See below | |
|
||||
| Config | Description | Example |
|
||||
|-------------------|----------------------------------------------------------------------------|----------|
|
||||
| enabled | Enabled or disables the database connection subsystem | `true` |
|
||||
| verbose | Displays more information to the logger which can be helpful for debugging | `false` |
|
||||
| driver | The SQL driver to use. Can be `postgres` or `sqlite` | `sqlite` |
|
||||
| connectionDetails | See below | |
|
||||
|
||||
##### connectionDetails
|
||||
|
||||
| Config | Description | Example |
|
||||
| ------ | ----------- | ------- |
|
||||
| host | The host address of the database | `localhost` |
|
||||
| port | The port used to connect to the database | `5432` |
|
||||
| username | An optional username to connect to the database | `username` |
|
||||
| password | An optional password to connect to the database | `password` |
|
||||
| database | The name of the database | `database.db` |
|
||||
| sslmode | The connection type of the database for Postgres databases only | `disable` |
|
||||
| Config | Description | Example |
|
||||
|----------|-----------------------------------------------------------------|---------------|
|
||||
| host | The host address of the database | `localhost` |
|
||||
| port | The port used to connect to the database | `5432` |
|
||||
| username | An optional username to connect to the database | `username` |
|
||||
| password | An optional password to connect to the database | `password` |
|
||||
| database | The name of the database | `database.db` |
|
||||
| sslmode | The connection type of the database for Postgres databases only | `disable` |
|
||||
|
||||
#### LiveData
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `candle` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| APIKeyOverride | Will set the GoCryptoTrader exchange to use the following API Key | `1234` |
|
||||
| APISecretOverride | Will set the GoCryptoTrader exchange to use the following API Secret | `5678` |
|
||||
| APIClientIDOverride | Will set the GoCryptoTrader exchange to use the following API Client ID | `9012` |
|
||||
| API2FAOverride | Will set the GoCryptoTrader exchange to use the following 2FA seed | `hello-moto` |
|
||||
| APISubaccountOverride | Will set the GoCryptoTrader exchange to use the following subaccount on supported exchanges | `subzero` |
|
||||
| RealOrders | Whether to place real orders. You really should never consider using this. Ever ever | `true` |
|
||||
| Key | Description | Example |
|
||||
|-----------------------|--------------------------------------------------------------------------------------------------------|---------------|
|
||||
| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `candle` |
|
||||
| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` |
|
||||
| APIKeyOverride | Will set the GoCryptoTrader exchange to use the following API Key | `1234` |
|
||||
| APISecretOverride | Will set the GoCryptoTrader exchange to use the following API Secret | `5678` |
|
||||
| APIClientIDOverride | Will set the GoCryptoTrader exchange to use the following API Client ID | `9012` |
|
||||
| API2FAOverride | Will set the GoCryptoTrader exchange to use the following 2FA seed | `hello-moto` |
|
||||
| APISubaccountOverride | Will set the GoCryptoTrader exchange to use the following subaccount on supported exchanges | `subzero` |
|
||||
| RealOrders | Whether to place real orders. You really should never consider using this. Ever ever | `true` |
|
||||
|
||||
##### Leverage Settings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| CanUseLeverage | Allows the use of leverage | `false` |
|
||||
| MaximumOrdersWithLeverageRatio | If the ratio of leveraged orders for a currency exceeds this, the order cannot be placed | `0.5` |
|
||||
| MaximumLeverageRate | Orders cannot be placed with leverage over this amount | `100` |
|
||||
| Key | Description | Example |
|
||||
|--------------------------------|------------------------------------------------------------------------------------------|---------|
|
||||
| CanUseLeverage | Allows the use of leverage | `false` |
|
||||
| MaximumOrdersWithLeverageRatio | If the ratio of leveraged orders for a currency exceeds this, the order cannot be placed | `0.5` |
|
||||
| MaximumLeverageRate | Orders cannot be placed with leverage over this amount | `100` |
|
||||
|
||||
##### Buy/Sell Settings
|
||||
|
||||
| Key | Description | Example |
|
||||
| --- | ----------- | ------- |
|
||||
| MinimumSize | If the order's quantity is below this, the order cannot be placed | `0.1` |
|
||||
| MaximumSize | If the order's quantity is over this amount, it cannot be placed and will be reduced to the maximum amount | `10` |
|
||||
| MaximumTotal | If the order's price * amount exceeds this number, the order cannot be placed and will be reduced to this figure | `1337` |
|
||||
| Key | Description | Example |
|
||||
|--------------|------------------------------------------------------------------------------------------------------------------|---------|
|
||||
| MinimumSize | If the order's quantity is below this, the order cannot be placed | `0.1` |
|
||||
| MaximumSize | If the order's quantity is over this amount, it cannot be placed and will be reduced to the maximum amount | `10` |
|
||||
| MaximumTotal | If the order's price * amount exceeds this number, the order cannot be placed and will be reduced to this figure | `1337` |
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
{{template "contributions"}}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{{define "engine backtest" -}}
|
||||
{{template "backtester-header" .}}
|
||||
## {{.CapitalName}} package overview
|
||||
|
||||
The backtest package is responsible for handling all events. It is the engine which combines all elements.
|
||||
Data is converted into candles which are then analysed via the strategyhandler. From there, events can be passed through to other handlers such as the portfolio handler to determine whether or not to place an order
|
||||
|
||||
|
||||
A flow of the application is as follows:
|
||||

|
||||
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
{{template "contributions"}}
|
||||
{{template "donations" .}}
|
||||
{{end}}
|
||||
@@ -0,0 +1,10 @@
|
||||
{{define "engine grpcserver" -}}
|
||||
{{template "backtester-header" .}}
|
||||
## {{.CapitalName}} package overview
|
||||
|
||||
The GRPC server is responsible for handling requests from the client. All GRPC functionality as defined in the proto file is implemented [here](/backtester/btrpc)
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
{{template "contributions"}}
|
||||
{{template "donations" .}}
|
||||
{{end}}
|
||||
@@ -0,0 +1,17 @@
|
||||
{{define "engine live" -}}
|
||||
{{template "backtester-header" .}}
|
||||
## {{.CapitalName}} package overview
|
||||
|
||||
Live trading has specific requirements separate from backtesting. Handling the looping of candle data and managing real orders and orderbooks will be handled here
|
||||
|
||||
Live trading is only a proof of concept. Please do not risk your funds by using it with `realOrders` enabled
|
||||
|
||||
|
||||
A flow of the application is as follows:
|
||||

|
||||
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
{{template "contributions"}}
|
||||
{{template "donations" .}}
|
||||
{{end}}
|
||||
@@ -1,22 +0,0 @@
|
||||
{{define "backtester engine" -}}
|
||||
{{template "backtester-header" .}}
|
||||
## {{.CapitalName}} package overview
|
||||
|
||||
The engine package is the most important package of the GoCryptoTrader backtester. It is the engine which combines all elements.
|
||||
It is responsible for the following functionality
|
||||
- Loading settings from a provided config file
|
||||
- Retrieving data
|
||||
- Loading the data into assessable chunks
|
||||
- Analysing the data via the `handleEvent` function
|
||||
- Looping through all data
|
||||
- Outputting results into a report
|
||||
|
||||
|
||||
A flow of the application is as follows:
|
||||

|
||||
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
{{template "contributions"}}
|
||||
{{template "donations" .}}
|
||||
{{end}}
|
||||
@@ -27,13 +27,14 @@ An event-driven backtesting tool to test and iterate trading strategies using hi
|
||||
- Fund transfer. At a strategy level, transfer funds between exchanges to allow for complex strategy design
|
||||
- Backtesting support for futures asset types
|
||||
- Example cash and carry spot futures strategy
|
||||
- Long-running application
|
||||
- GRPC server implementation
|
||||
|
||||
## Planned Features
|
||||
We welcome pull requests on any feature for the Backtester! We will be especially appreciative of any contribution towards the following planned features:
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| Long-running application | Transform the Backtester to run a GRPC server, where commands can be sent to run Backtesting operations. Allowing for many strategies to be run, analysed and tweaked in a more efficient manner |
|
||||
| Leverage support | Leverage is a good way to enhance profit and loss and is important to include in strategies |
|
||||
| Enhance config-builder | Create an application that can create strategy configs in a more visual manner and execute them via GRPC to allow for faster customisation of strategies |
|
||||
| Save Backtester results to database | This will allow for easier comparison of results over time |
|
||||
|
||||
@@ -540,7 +540,7 @@ func UpdateDocumentation(details DocumentationDetails) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if name == engineFolder {
|
||||
if strings.Contains(name, engineFolder) {
|
||||
d, err := os.ReadDir(details.Directories[i])
|
||||
if err != nil {
|
||||
fmt.Println("Excluding file:", err)
|
||||
|
||||
@@ -854,7 +854,8 @@ func verifyCert(pemData []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkCerts(certDir string) error {
|
||||
// CheckCerts checks and verifies RPC server certificates
|
||||
func CheckCerts(certDir string) error {
|
||||
certFile := filepath.Join(certDir, "cert.pem")
|
||||
keyFile := filepath.Join(certDir, "key.pem")
|
||||
|
||||
|
||||
@@ -1318,7 +1318,7 @@ func TestCheckAndGenCerts(t *testing.T) {
|
||||
}
|
||||
|
||||
defer cleanup()
|
||||
if err := checkCerts(tempDir); err != nil {
|
||||
if err := CheckCerts(tempDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -1327,11 +1327,11 @@ func TestCheckAndGenCerts(t *testing.T) {
|
||||
if err := os.Remove(certFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := checkCerts(tempDir); err != nil {
|
||||
if err := CheckCerts(tempDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Now call checkCerts to test an expired cert
|
||||
// Now call CheckCerts to test an expired cert
|
||||
certData, err := mockCert("", time.Now().Add(-time.Hour))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1340,7 +1340,7 @@ func TestCheckAndGenCerts(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = checkCerts(tempDir); err != nil {
|
||||
if err = CheckCerts(tempDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,10 +104,9 @@ func (s *RPCServer) authenticateClient(ctx context.Context) (context.Context, er
|
||||
return ctx, fmt.Errorf("unable to base64 decode authorization header")
|
||||
}
|
||||
|
||||
credentials := strings.Split(string(decoded), ":")
|
||||
|
||||
username := credentials[0]
|
||||
password := credentials[1]
|
||||
cred := strings.Split(string(decoded), ":")
|
||||
username := cred[0]
|
||||
password := cred[1]
|
||||
|
||||
if username != s.Config.RemoteControl.Username ||
|
||||
password != s.Config.RemoteControl.Password {
|
||||
@@ -127,8 +126,8 @@ func (s *RPCServer) authenticateClient(ctx context.Context) (context.Context, er
|
||||
// StartRPCServer starts a gRPC server with TLS auth
|
||||
func StartRPCServer(engine *Engine) {
|
||||
targetDir := utils.GetTLSDir(engine.Settings.DataDir)
|
||||
if err := checkCerts(targetDir); err != nil {
|
||||
log.Errorf(log.GRPCSys, "gRPC checkCerts failed. err: %s\n", err)
|
||||
if err := CheckCerts(targetDir); err != nil {
|
||||
log.Errorf(log.GRPCSys, "gRPC CheckCerts failed. err: %s\n", err)
|
||||
return
|
||||
}
|
||||
log.Debugf(log.GRPCSys, "gRPC server support enabled. Starting gRPC server on https://%v.\n", engine.Config.RemoteControl.GRPC.ListenAddress)
|
||||
|
||||
@@ -190,15 +190,17 @@ type API struct {
|
||||
credentials *account.Credentials
|
||||
credMu sync.RWMutex
|
||||
|
||||
CredentialsValidator struct {
|
||||
// For Huobi (optional)
|
||||
RequiresPEM bool
|
||||
CredentialsValidator CredentialsValidator
|
||||
}
|
||||
|
||||
RequiresKey bool
|
||||
RequiresSecret bool
|
||||
RequiresClientID bool
|
||||
RequiresBase64DecodeSecret bool
|
||||
}
|
||||
// CredentialsValidator determines what is required
|
||||
// to make authenticated requests for an exchange
|
||||
type CredentialsValidator struct {
|
||||
RequiresPEM bool
|
||||
RequiresKey bool
|
||||
RequiresSecret bool
|
||||
RequiresClientID bool
|
||||
RequiresBase64DecodeSecret bool
|
||||
}
|
||||
|
||||
// Base stores the individual exchange information
|
||||
|
||||
@@ -4,8 +4,8 @@ deps:
|
||||
- remote: buf.build
|
||||
owner: googleapis
|
||||
repository: googleapis
|
||||
commit: 80720a488c9a414bb8d4a9f811084989
|
||||
commit: 62f35d8aed1149c291d606d958a7ce32
|
||||
- remote: buf.build
|
||||
owner: grpc-ecosystem
|
||||
repository: grpc-gateway
|
||||
commit: 00116f302b12478b85deb33b734e026c
|
||||
commit: bc28b723cd774c32b6fbc77621518765
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc-gen-go v1.28.0
|
||||
// protoc (unknown)
|
||||
// source: rpc.proto
|
||||
|
||||
|
||||
2178
gctrpc/rpc.pb.gw.go
2178
gctrpc/rpc.pb.gw.go
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user