backtester: run manager (#1040)

* begins defining run management options

* fleshes out concept

* completes fund manager and RPC commands

* coverage and improvements

* adds coverage, and bad log concept

* simplifies output at expense of races

* removes run logging for now. tightens races. adds cov

* Lints thine splints

* Fixes stopping and clearing bugs

* some niteroos

* fix races
This commit is contained in:
Scott
2022-09-30 14:15:10 +10:00
committed by GitHub
parent 625020c6e8
commit 2232478415
25 changed files with 4560 additions and 258 deletions

View File

@@ -10,6 +10,19 @@ import (
"google.golang.org/protobuf/types/known/timestamppb"
)
var (
doNotRunFlag = &cli.BoolFlag{
Name: "donotrunimmediately",
Aliases: []string{"dnr"},
Usage: "if true, will load the strategy, but will not execute until another command is sent",
}
doNotStoreFlag = &cli.BoolFlag{
Name: "donotstore",
Aliases: []string{"dns"},
Usage: "if true, will not store the run internally - cannot be run in conjunction with dnr",
}
)
var executeStrategyFromFileCommand = &cli.Command{
Name: "executestrategyfromfile",
Usage: "runs the strategy from a config file",
@@ -21,6 +34,8 @@ var executeStrategyFromFileCommand = &cli.Command{
Aliases: []string{"p"},
Usage: "the filepath to a strategy to execute",
},
doNotRunFlag,
doNotStoreFlag,
},
}
@@ -32,7 +47,7 @@ func executeStrategyFromFile(c *cli.Context) error {
defer closeConn(conn, cancel)
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowCommandHelp(c, "executestrategyfromfile")
return cli.ShowCommandHelp(c, c.Command.Name)
}
var path string
@@ -42,11 +57,22 @@ func executeStrategyFromFile(c *cli.Context) error {
path = c.Args().First()
}
var dnr bool
if c.IsSet("donotrunimmediately") {
dnr = c.Bool("donotrunimmediately")
}
var dns bool
if c.IsSet("donotstore") {
dns = c.Bool("donotstore")
}
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.ExecuteStrategyFromFile(
c.Context,
&btrpc.ExecuteStrategyFromFileRequest{
StrategyFilePath: path,
StrategyFilePath: path,
DoNotRunImmediately: dnr,
DoNotStore: dns,
},
)
@@ -58,11 +84,264 @@ func executeStrategyFromFile(c *cli.Context) error {
return nil
}
var listAllRunsCommand = &cli.Command{
Name: "listallruns",
Usage: "returns a list of all loaded backtest/livestrategy runs",
Action: listAllRuns,
}
func listAllRuns(c *cli.Context) error {
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.ListAllRuns(
c.Context,
&btrpc.ListAllRunsRequest{},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var startRunCommand = &cli.Command{
Name: "startrun",
Usage: "executes a strategy loaded into the server",
ArgsUsage: "<id>",
Action: startRun,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "id",
Usage: "the id of the backtest/livestrategy run",
},
},
}
func startRun(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, c.Command.Name)
}
var id string
if c.IsSet("id") {
id = c.String("id")
} else {
id = c.Args().First()
}
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.StartRun(
c.Context,
&btrpc.StartRunRequest{
Id: id,
},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var startAllRunsCommand = &cli.Command{
Name: "startallruns",
Usage: "executes all strategies loaded into the server that have not been run",
Action: startAllRuns,
}
func startAllRuns(c *cli.Context) error {
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.StartAllRuns(
c.Context,
&btrpc.StartAllRunsRequest{},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var stopRunCommand = &cli.Command{
Name: "stoprun",
Usage: "stops a strategy loaded into the server",
ArgsUsage: "<id>",
Action: stopRun,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "id",
Usage: "the id of the backtest/livestrategy run",
},
},
}
func stopRun(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, c.Command.Name)
}
var id string
if c.IsSet("id") {
id = c.String("id")
} else {
id = c.Args().First()
}
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.StopRun(
c.Context,
&btrpc.StopRunRequest{
Id: id,
},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var stopAllRunsCommand = &cli.Command{
Name: "stopallruns",
Usage: "stops all strategies loaded into the server",
Action: stopAllRuns,
}
func stopAllRuns(c *cli.Context) error {
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.StopAllRuns(
c.Context,
&btrpc.StopAllRunsRequest{},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var clearRunCommand = &cli.Command{
Name: "clearrun",
Usage: "clears/deletes a strategy loaded into the server - if it is not running",
ArgsUsage: "<id>",
Action: clearRun,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "id",
Usage: "the id of the backtest/livestrategy run",
},
},
}
func clearRun(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, c.Command.Name)
}
var id string
if c.IsSet("id") {
id = c.String("id")
} else {
id = c.Args().First()
}
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.ClearRun(
c.Context,
&btrpc.ClearRunRequest{
Id: id,
},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var clearAllRunsCommand = &cli.Command{
Name: "clearallruns",
Usage: "clears all strategies loaded into the server. Only runs not actively running will be cleared",
Action: clearAllRuns,
}
func clearAllRuns(c *cli.Context) error {
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.ClearAllRuns(
c.Context,
&btrpc.ClearAllRunsRequest{},
)
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,
Flags: []cli.Flag{
doNotRunFlag,
doNotStoreFlag,
},
}
// executeStrategyFromConfig this is a proof of concept command
@@ -235,11 +514,22 @@ func executeStrategyFromConfig(c *cli.Context) error {
},
}
var dnr bool
if c.IsSet("donotrunimmediately") {
dnr = c.Bool("donotrunimmediately")
}
var dns bool
if c.IsSet("donotstore") {
dns = c.Bool("donotstore")
}
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.ExecuteStrategyFromConfig(
c.Context,
&btrpc.ExecuteStrategyFromConfigRequest{
Config: cfg,
Config: cfg,
DoNotRunImmediately: dnr,
DoNotStore: dns,
},
)

View File

@@ -112,6 +112,13 @@ func main() {
app.Commands = []*cli.Command{
executeStrategyFromFileCommand,
executeStrategyFromConfigCommand,
listAllRunsCommand,
startRunCommand,
startAllRunsCommand,
stopRunCommand,
stopAllRunsCommand,
clearRunCommand,
clearAllRunsCommand,
}
ctx, cancel := context.WithCancel(context.Background())

File diff suppressed because it is too large Load Diff

View File

@@ -103,57 +103,414 @@ func local_request_BacktesterService_ExecuteStrategyFromConfig_0(ctx context.Con
}
func request_BacktesterService_ListAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListAllRunsRequest
var metadata runtime.ServerMetadata
msg, err := client.ListAllRuns(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_BacktesterService_ListAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListAllRunsRequest
var metadata runtime.ServerMetadata
msg, err := server.ListAllRuns(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_BacktesterService_StartRun_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_BacktesterService_StartRun_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StartRunRequest
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_StartRun_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.StartRun(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_BacktesterService_StartRun_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StartRunRequest
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_StartRun_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.StartRun(ctx, &protoReq)
return msg, metadata, err
}
func request_BacktesterService_StartAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StartAllRunsRequest
var metadata runtime.ServerMetadata
msg, err := client.StartAllRuns(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_BacktesterService_StartAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StartAllRunsRequest
var metadata runtime.ServerMetadata
msg, err := server.StartAllRuns(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_BacktesterService_StopRun_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_BacktesterService_StopRun_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StopRunRequest
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_StopRun_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.StopRun(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_BacktesterService_StopRun_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StopRunRequest
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_StopRun_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.StopRun(ctx, &protoReq)
return msg, metadata, err
}
func request_BacktesterService_StopAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StopAllRunsRequest
var metadata runtime.ServerMetadata
msg, err := client.StopAllRuns(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_BacktesterService_StopAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StopAllRunsRequest
var metadata runtime.ServerMetadata
msg, err := server.StopAllRuns(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_BacktesterService_ClearRun_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_BacktesterService_ClearRun_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ClearRunRequest
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_ClearRun_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ClearRun(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_BacktesterService_ClearRun_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ClearRunRequest
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_ClearRun_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ClearRun(ctx, &protoReq)
return msg, metadata, err
}
func request_BacktesterService_ClearAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ClearAllRunsRequest
var metadata runtime.ServerMetadata
msg, err := client.ClearAllRuns(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_BacktesterService_ClearAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ClearAllRunsRequest
var metadata runtime.ServerMetadata
msg, err := server.ClearAllRuns(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) {
mux.Handle("POST", 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"))
var annotatedContext context.Context
annotatedContext, 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)
resp, md, err := local_request_BacktesterService_ExecuteStrategyFromFile_0(annotatedContext, 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)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_ExecuteStrategyFromFile_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_BacktesterService_ExecuteStrategyFromFile_0(annotatedContext, 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) {
mux.Handle("POST", 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"))
var annotatedContext context.Context
annotatedContext, 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)
resp, md, err := local_request_BacktesterService_ExecuteStrategyFromConfig_0(annotatedContext, 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)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_ExecuteStrategyFromConfig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_BacktesterService_ExecuteStrategyFromConfig_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_BacktesterService_ListAllRuns_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/ListAllRuns", runtime.WithHTTPPathPattern("/v1/listallruns"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_BacktesterService_ListAllRuns_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_ListAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_BacktesterService_StartRun_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/StartRun", runtime.WithHTTPPathPattern("/v1/startrun"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_BacktesterService_StartRun_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_StartRun_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_BacktesterService_StartAllRuns_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/StartAllRuns", runtime.WithHTTPPathPattern("/v1/startallruns"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_BacktesterService_StartAllRuns_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_StartAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_BacktesterService_StopRun_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/StopRun", runtime.WithHTTPPathPattern("/v1/stoprun"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_BacktesterService_StopRun_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_StopRun_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_BacktesterService_StopAllRuns_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/StopAllRuns", runtime.WithHTTPPathPattern("/v1/stopallruns"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_BacktesterService_StopAllRuns_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_StopAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_BacktesterService_ClearRun_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/ClearRun", runtime.WithHTTPPathPattern("/v1/clearrun"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_BacktesterService_ClearRun_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_ClearRun_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_BacktesterService_ClearAllRuns_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/ClearAllRuns", runtime.WithHTTPPathPattern("/v1/clearallruns"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_BacktesterService_ClearAllRuns_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_ClearAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -198,45 +555,201 @@ func RegisterBacktesterServiceHandler(ctx context.Context, mux *runtime.ServeMux
// "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) {
mux.Handle("POST", 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"))
var annotatedContext context.Context
annotatedContext, 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)
resp, md, err := request_BacktesterService_ExecuteStrategyFromFile_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_ExecuteStrategyFromFile_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_BacktesterService_ExecuteStrategyFromFile_0(annotatedContext, 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) {
mux.Handle("POST", 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"))
var annotatedContext context.Context
annotatedContext, 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)
resp, md, err := request_BacktesterService_ExecuteStrategyFromConfig_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_ExecuteStrategyFromConfig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_BacktesterService_ExecuteStrategyFromConfig_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_BacktesterService_ListAllRuns_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/ListAllRuns", runtime.WithHTTPPathPattern("/v1/listallruns"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_BacktesterService_ListAllRuns_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_ListAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_BacktesterService_StartRun_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/StartRun", runtime.WithHTTPPathPattern("/v1/startrun"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_BacktesterService_StartRun_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_StartRun_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_BacktesterService_StartAllRuns_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/StartAllRuns", runtime.WithHTTPPathPattern("/v1/startallruns"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_BacktesterService_StartAllRuns_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_StartAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_BacktesterService_StopRun_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/StopRun", runtime.WithHTTPPathPattern("/v1/stoprun"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_BacktesterService_StopRun_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_StopRun_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_BacktesterService_StopAllRuns_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/StopAllRuns", runtime.WithHTTPPathPattern("/v1/stopallruns"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_BacktesterService_StopAllRuns_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_StopAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_BacktesterService_ClearRun_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/ClearRun", runtime.WithHTTPPathPattern("/v1/clearrun"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_BacktesterService_ClearRun_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_ClearRun_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_BacktesterService_ClearAllRuns_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
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/ClearAllRuns", runtime.WithHTTPPathPattern("/v1/clearallruns"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_BacktesterService_ClearAllRuns_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_BacktesterService_ClearAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -247,10 +760,38 @@ 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"}, ""))
pattern_BacktesterService_ListAllRuns_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "listallruns"}, ""))
pattern_BacktesterService_StartRun_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "startrun"}, ""))
pattern_BacktesterService_StartAllRuns_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "startallruns"}, ""))
pattern_BacktesterService_StopRun_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "stoprun"}, ""))
pattern_BacktesterService_StopAllRuns_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "stopallruns"}, ""))
pattern_BacktesterService_ClearRun_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "clearrun"}, ""))
pattern_BacktesterService_ClearAllRuns_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "clearallruns"}, ""))
)
var (
forward_BacktesterService_ExecuteStrategyFromFile_0 = runtime.ForwardResponseMessage
forward_BacktesterService_ExecuteStrategyFromConfig_0 = runtime.ForwardResponseMessage
forward_BacktesterService_ListAllRuns_0 = runtime.ForwardResponseMessage
forward_BacktesterService_StartRun_0 = runtime.ForwardResponseMessage
forward_BacktesterService_StartAllRuns_0 = runtime.ForwardResponseMessage
forward_BacktesterService_StopRun_0 = runtime.ForwardResponseMessage
forward_BacktesterService_StopAllRuns_0 = runtime.ForwardResponseMessage
forward_BacktesterService_ClearRun_0 = runtime.ForwardResponseMessage
forward_BacktesterService_ClearAllRuns_0 = runtime.ForwardResponseMessage
)

View File

@@ -171,29 +171,127 @@ message Config {
StatisticSettings statistic_settings = 8;
}
message RunSummary {
string id = 1;
string strategy_name = 2;
string date_loaded = 3;
string date_started = 4;
string date_ended = 5;
bool closed = 6;
bool live_testing = 7;
bool real_orders = 8;
}
// Requests and responses
message ExecuteStrategyFromFileRequest {
string strategy_file_path = 1;
bool do_not_run_immediately = 2;
bool do_not_store = 3;
}
message ExecuteStrategyResponse {
bool success = 1;
string message = 2;
RunSummary run = 1;
}
message ExecuteStrategyFromConfigRequest {
btrpc.Config config = 1;
bool do_not_run_immediately = 1;
bool do_not_store = 2;
btrpc.Config config = 3;
}
message ListAllRunsRequest {}
message ListAllRunsResponse {
repeated RunSummary runs = 1;
}
message StopRunRequest {
string id = 1;
}
message StopRunResponse {
RunSummary stopped_run = 1;
}
message StartRunRequest {
string id = 1;
}
message StartRunResponse {
bool started = 1;
}
message StartAllRunsRequest {}
message StartAllRunsResponse {
repeated string runs_started = 1;
}
message StopAllRunsRequest {}
message StopAllRunsResponse {
repeated RunSummary runs_stopped = 1;
}
message ClearRunRequest {
string id = 1;
}
message ClearRunResponse {
RunSummary cleared_run = 1;
}
message ClearAllRunsRequest {}
message ClearAllRunsResponse {
repeated RunSummary cleared_runs = 1;
repeated RunSummary remaining_runs = 2;
}
service BacktesterService {
rpc ExecuteStrategyFromFile(ExecuteStrategyFromFileRequest) returns (ExecuteStrategyResponse) {
option (google.api.http) = {
get: "/v1/executestrategyfromfile"
post: "/v1/executestrategyfromfile"
};
}
rpc ExecuteStrategyFromConfig(ExecuteStrategyFromConfigRequest) returns (ExecuteStrategyResponse) {
option (google.api.http) = {
get: "/v1/executestrategyfromconfig"
post: "/v1/executestrategyfromconfig"
};
}
rpc ListAllRuns(ListAllRunsRequest) returns (ListAllRunsResponse) {
option (google.api.http) = {
get: "/v1/listallruns"
};
}
rpc StartRun(StartRunRequest) returns (StartRunResponse) {
option (google.api.http) = {
post: "/v1/startrun"
};
}
rpc StartAllRuns(StartAllRunsRequest) returns (StartAllRunsResponse) {
option (google.api.http) = {
post: "/v1/startallruns"
};
}
rpc StopRun(StopRunRequest) returns (StopRunResponse) {
option (google.api.http) = {
post: "/v1/stoprun"
};
}
rpc StopAllRuns(StopAllRunsRequest) returns (StopAllRunsResponse) {
option (google.api.http) = {
post: "/v1/stopallruns"
};
}
rpc ClearRun(ClearRunRequest) returns (ClearRunResponse) {
option (google.api.http) = {
delete: "/v1/clearrun"
};
}
rpc ClearAllRuns(ClearAllRunsRequest) returns (ClearAllRunsResponse) {
option (google.api.http) = {
delete: "/v1/clearallruns"
};
}
}

View File

@@ -16,8 +16,60 @@
"application/json"
],
"paths": {
"/v1/clearallruns": {
"delete": {
"operationId": "BacktesterService_ClearAllRuns",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/btrpcClearAllRunsResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"BacktesterService"
]
}
},
"/v1/clearrun": {
"delete": {
"operationId": "BacktesterService_ClearRun",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/btrpcClearRunResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "id",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"BacktesterService"
]
}
},
"/v1/executestrategyfromconfig": {
"get": {
"post": {
"operationId": "BacktesterService_ExecuteStrategyFromConfig",
"responses": {
"200": {
@@ -34,6 +86,18 @@
}
},
"parameters": [
{
"name": "doNotRunImmediately",
"in": "query",
"required": false,
"type": "boolean"
},
{
"name": "doNotStore",
"in": "query",
"required": false,
"type": "boolean"
},
{
"name": "config.nickname",
"in": "query",
@@ -299,7 +363,7 @@
}
},
"/v1/executestrategyfromfile": {
"get": {
"post": {
"operationId": "BacktesterService_ExecuteStrategyFromFile",
"responses": {
"200": {
@@ -321,6 +385,144 @@
"in": "query",
"required": false,
"type": "string"
},
{
"name": "doNotRunImmediately",
"in": "query",
"required": false,
"type": "boolean"
},
{
"name": "doNotStore",
"in": "query",
"required": false,
"type": "boolean"
}
],
"tags": [
"BacktesterService"
]
}
},
"/v1/listallruns": {
"get": {
"operationId": "BacktesterService_ListAllRuns",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/btrpcListAllRunsResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"BacktesterService"
]
}
},
"/v1/startallruns": {
"post": {
"operationId": "BacktesterService_StartAllRuns",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/btrpcStartAllRunsResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"BacktesterService"
]
}
},
"/v1/startrun": {
"post": {
"operationId": "BacktesterService_StartRun",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/btrpcStartRunResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "id",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"BacktesterService"
]
}
},
"/v1/stopallruns": {
"post": {
"operationId": "BacktesterService_StopAllRuns",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/btrpcStopAllRunsResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"BacktesterService"
]
}
},
"/v1/stoprun": {
"post": {
"operationId": "BacktesterService_StopRun",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/btrpcStopRunResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "id",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
@@ -354,6 +556,31 @@
}
}
},
"btrpcClearAllRunsResponse": {
"type": "object",
"properties": {
"clearedRuns": {
"type": "array",
"items": {
"$ref": "#/definitions/btrpcRunSummary"
}
},
"remainingRuns": {
"type": "array",
"items": {
"$ref": "#/definitions/btrpcRunSummary"
}
}
}
},
"btrpcClearRunResponse": {
"type": "object",
"properties": {
"clearedRun": {
"$ref": "#/definitions/btrpcRunSummary"
}
}
},
"btrpcConfig": {
"type": "object",
"properties": {
@@ -560,11 +787,8 @@
"btrpcExecuteStrategyResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"message": {
"type": "string"
"run": {
"$ref": "#/definitions/btrpcRunSummary"
}
}
},
@@ -607,6 +831,17 @@
}
}
},
"btrpcListAllRunsResponse": {
"type": "object",
"properties": {
"runs": {
"type": "array",
"items": {
"$ref": "#/definitions/btrpcRunSummary"
}
}
}
},
"btrpcLiveData": {
"type": "object",
"properties": {
@@ -658,6 +893,35 @@
}
}
},
"btrpcRunSummary": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"strategyName": {
"type": "string"
},
"dateLoaded": {
"type": "string"
},
"dateStarted": {
"type": "string"
},
"dateEnded": {
"type": "string"
},
"closed": {
"type": "boolean"
},
"liveTesting": {
"type": "boolean"
},
"realOrders": {
"type": "boolean"
}
}
},
"btrpcSpotDetails": {
"type": "object",
"properties": {
@@ -669,6 +933,25 @@
}
}
},
"btrpcStartAllRunsResponse": {
"type": "object",
"properties": {
"runsStarted": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"btrpcStartRunResponse": {
"type": "object",
"properties": {
"started": {
"type": "boolean"
}
}
},
"btrpcStatisticSettings": {
"type": "object",
"properties": {
@@ -677,6 +960,25 @@
}
}
},
"btrpcStopAllRunsResponse": {
"type": "object",
"properties": {
"runsStopped": {
"type": "array",
"items": {
"$ref": "#/definitions/btrpcRunSummary"
}
}
}
},
"btrpcStopRunResponse": {
"type": "object",
"properties": {
"stoppedRun": {
"$ref": "#/definitions/btrpcRunSummary"
}
}
},
"btrpcStrategySettings": {
"type": "object",
"properties": {

View File

@@ -24,6 +24,13 @@ const _ = grpc.SupportPackageIsVersion7
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)
ListAllRuns(ctx context.Context, in *ListAllRunsRequest, opts ...grpc.CallOption) (*ListAllRunsResponse, error)
StartRun(ctx context.Context, in *StartRunRequest, opts ...grpc.CallOption) (*StartRunResponse, error)
StartAllRuns(ctx context.Context, in *StartAllRunsRequest, opts ...grpc.CallOption) (*StartAllRunsResponse, error)
StopRun(ctx context.Context, in *StopRunRequest, opts ...grpc.CallOption) (*StopRunResponse, error)
StopAllRuns(ctx context.Context, in *StopAllRunsRequest, opts ...grpc.CallOption) (*StopAllRunsResponse, error)
ClearRun(ctx context.Context, in *ClearRunRequest, opts ...grpc.CallOption) (*ClearRunResponse, error)
ClearAllRuns(ctx context.Context, in *ClearAllRunsRequest, opts ...grpc.CallOption) (*ClearAllRunsResponse, error)
}
type backtesterServiceClient struct {
@@ -52,12 +59,82 @@ func (c *backtesterServiceClient) ExecuteStrategyFromConfig(ctx context.Context,
return out, nil
}
func (c *backtesterServiceClient) ListAllRuns(ctx context.Context, in *ListAllRunsRequest, opts ...grpc.CallOption) (*ListAllRunsResponse, error) {
out := new(ListAllRunsResponse)
err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/ListAllRuns", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *backtesterServiceClient) StartRun(ctx context.Context, in *StartRunRequest, opts ...grpc.CallOption) (*StartRunResponse, error) {
out := new(StartRunResponse)
err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/StartRun", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *backtesterServiceClient) StartAllRuns(ctx context.Context, in *StartAllRunsRequest, opts ...grpc.CallOption) (*StartAllRunsResponse, error) {
out := new(StartAllRunsResponse)
err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/StartAllRuns", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *backtesterServiceClient) StopRun(ctx context.Context, in *StopRunRequest, opts ...grpc.CallOption) (*StopRunResponse, error) {
out := new(StopRunResponse)
err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/StopRun", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *backtesterServiceClient) StopAllRuns(ctx context.Context, in *StopAllRunsRequest, opts ...grpc.CallOption) (*StopAllRunsResponse, error) {
out := new(StopAllRunsResponse)
err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/StopAllRuns", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *backtesterServiceClient) ClearRun(ctx context.Context, in *ClearRunRequest, opts ...grpc.CallOption) (*ClearRunResponse, error) {
out := new(ClearRunResponse)
err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/ClearRun", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *backtesterServiceClient) ClearAllRuns(ctx context.Context, in *ClearAllRunsRequest, opts ...grpc.CallOption) (*ClearAllRunsResponse, error) {
out := new(ClearAllRunsResponse)
err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/ClearAllRuns", 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)
ListAllRuns(context.Context, *ListAllRunsRequest) (*ListAllRunsResponse, error)
StartRun(context.Context, *StartRunRequest) (*StartRunResponse, error)
StartAllRuns(context.Context, *StartAllRunsRequest) (*StartAllRunsResponse, error)
StopRun(context.Context, *StopRunRequest) (*StopRunResponse, error)
StopAllRuns(context.Context, *StopAllRunsRequest) (*StopAllRunsResponse, error)
ClearRun(context.Context, *ClearRunRequest) (*ClearRunResponse, error)
ClearAllRuns(context.Context, *ClearAllRunsRequest) (*ClearAllRunsResponse, error)
mustEmbedUnimplementedBacktesterServiceServer()
}
@@ -71,6 +148,27 @@ func (UnimplementedBacktesterServiceServer) ExecuteStrategyFromFile(context.Cont
func (UnimplementedBacktesterServiceServer) ExecuteStrategyFromConfig(context.Context, *ExecuteStrategyFromConfigRequest) (*ExecuteStrategyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExecuteStrategyFromConfig not implemented")
}
func (UnimplementedBacktesterServiceServer) ListAllRuns(context.Context, *ListAllRunsRequest) (*ListAllRunsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAllRuns not implemented")
}
func (UnimplementedBacktesterServiceServer) StartRun(context.Context, *StartRunRequest) (*StartRunResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method StartRun not implemented")
}
func (UnimplementedBacktesterServiceServer) StartAllRuns(context.Context, *StartAllRunsRequest) (*StartAllRunsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method StartAllRuns not implemented")
}
func (UnimplementedBacktesterServiceServer) StopRun(context.Context, *StopRunRequest) (*StopRunResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method StopRun not implemented")
}
func (UnimplementedBacktesterServiceServer) StopAllRuns(context.Context, *StopAllRunsRequest) (*StopAllRunsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method StopAllRuns not implemented")
}
func (UnimplementedBacktesterServiceServer) ClearRun(context.Context, *ClearRunRequest) (*ClearRunResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ClearRun not implemented")
}
func (UnimplementedBacktesterServiceServer) ClearAllRuns(context.Context, *ClearAllRunsRequest) (*ClearAllRunsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ClearAllRuns not implemented")
}
func (UnimplementedBacktesterServiceServer) mustEmbedUnimplementedBacktesterServiceServer() {}
// UnsafeBacktesterServiceServer may be embedded to opt out of forward compatibility for this service.
@@ -120,6 +218,132 @@ func _BacktesterService_ExecuteStrategyFromConfig_Handler(srv interface{}, ctx c
return interceptor(ctx, in, info, handler)
}
func _BacktesterService_ListAllRuns_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListAllRunsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BacktesterServiceServer).ListAllRuns(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/btrpc.BacktesterService/ListAllRuns",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BacktesterServiceServer).ListAllRuns(ctx, req.(*ListAllRunsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _BacktesterService_StartRun_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StartRunRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BacktesterServiceServer).StartRun(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/btrpc.BacktesterService/StartRun",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BacktesterServiceServer).StartRun(ctx, req.(*StartRunRequest))
}
return interceptor(ctx, in, info, handler)
}
func _BacktesterService_StartAllRuns_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StartAllRunsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BacktesterServiceServer).StartAllRuns(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/btrpc.BacktesterService/StartAllRuns",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BacktesterServiceServer).StartAllRuns(ctx, req.(*StartAllRunsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _BacktesterService_StopRun_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StopRunRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BacktesterServiceServer).StopRun(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/btrpc.BacktesterService/StopRun",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BacktesterServiceServer).StopRun(ctx, req.(*StopRunRequest))
}
return interceptor(ctx, in, info, handler)
}
func _BacktesterService_StopAllRuns_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StopAllRunsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BacktesterServiceServer).StopAllRuns(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/btrpc.BacktesterService/StopAllRuns",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BacktesterServiceServer).StopAllRuns(ctx, req.(*StopAllRunsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _BacktesterService_ClearRun_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ClearRunRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BacktesterServiceServer).ClearRun(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/btrpc.BacktesterService/ClearRun",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BacktesterServiceServer).ClearRun(ctx, req.(*ClearRunRequest))
}
return interceptor(ctx, in, info, handler)
}
func _BacktesterService_ClearAllRuns_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ClearAllRunsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BacktesterServiceServer).ClearAllRuns(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/btrpc.BacktesterService/ClearAllRuns",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BacktesterServiceServer).ClearAllRuns(ctx, req.(*ClearAllRunsRequest))
}
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)
@@ -135,6 +359,34 @@ var BacktesterService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ExecuteStrategyFromConfig",
Handler: _BacktesterService_ExecuteStrategyFromConfig_Handler,
},
{
MethodName: "ListAllRuns",
Handler: _BacktesterService_ListAllRuns_Handler,
},
{
MethodName: "StartRun",
Handler: _BacktesterService_StartRun_Handler,
},
{
MethodName: "StartAllRuns",
Handler: _BacktesterService_StartAllRuns_Handler,
},
{
MethodName: "StopRun",
Handler: _BacktesterService_StopRun_Handler,
},
{
MethodName: "StopAllRuns",
Handler: _BacktesterService_StopAllRuns_Handler,
},
{
MethodName: "ClearRun",
Handler: _BacktesterService_ClearRun_Handler,
},
{
MethodName: "ClearAllRuns",
Handler: _BacktesterService_ClearAllRuns_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "btrpc.proto",

View File

@@ -3,7 +3,10 @@ package engine
import (
"errors"
"fmt"
"sync"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/backtester/common"
"github.com/thrasher-corp/gocryptotrader/backtester/data"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/eventholder"
@@ -17,6 +20,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order"
"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
"github.com/thrasher-corp/gocryptotrader/backtester/funding"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
gctexchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -25,12 +29,17 @@ import (
)
// New returns a new BackTest instance
func New() *BackTest {
return &BackTest{
func New() (*BackTest, error) {
bt := &BackTest{
shutdown: make(chan struct{}),
Datas: &data.HandlerPerCurrency{},
EventQueue: &eventholder.Holder{},
}
err := bt.SetupMetaData()
if err != nil {
return nil, err
}
return bt, nil
}
// Reset BackTest values to default
@@ -46,9 +55,73 @@ func (bt *BackTest) Reset() {
bt.databaseManager = nil
}
// ExecuteStrategy executes the strategy using the provided configs
func (bt *BackTest) ExecuteStrategy(waitForOfflineCompletion bool) error {
if bt == nil {
return gctcommon.ErrNilPointer
}
bt.m.Lock()
if bt.MetaData.DateLoaded.IsZero() {
bt.m.Unlock()
return errNotSetup
}
if !bt.MetaData.Closed && !bt.MetaData.DateStarted.IsZero() {
bt.m.Unlock()
return fmt.Errorf("%w %v %v", errRunIsRunning, bt.MetaData.ID, bt.MetaData.Strategy)
}
if bt.MetaData.Closed {
bt.m.Unlock()
return fmt.Errorf("%w %v %v", errAlreadyRan, bt.MetaData.ID, bt.MetaData.Strategy)
}
bt.MetaData.DateStarted = time.Now()
liveTesting := bt.MetaData.LiveTesting
bt.m.Unlock()
var wg sync.WaitGroup
if waitForOfflineCompletion {
wg.Add(1)
}
go func() {
if waitForOfflineCompletion {
defer wg.Done()
}
if liveTesting {
if waitForOfflineCompletion {
log.Errorf(common.Backtester, "%v cannot wait for completion of a live test", errCannotHandleRequest)
return
}
err := bt.RunLive()
if err != nil {
log.Error(log.Global, err)
}
} else {
bt.Run()
close(bt.shutdown)
bt.m.Lock()
bt.MetaData.Closed = true
bt.MetaData.DateEnded = time.Now()
bt.m.Unlock()
err := bt.Statistic.CalculateAllResults()
if err != nil {
log.Error(log.Global, err)
return
}
err = bt.Reports.GenerateReport()
if err != nil {
log.Error(log.Global, err)
}
}
}()
wg.Wait()
return nil
}
// Run will iterate over loaded data events
// save them and then handle the event based on its type
func (bt *BackTest) Run() {
if bt.MetaData.DateLoaded.IsZero() {
return
}
log.Info(common.Backtester, "Running backtester against pre-defined data")
dataLoadingIssue:
for ev := bt.EventQueue.NextEvent(); ; ev = bt.EventQueue.NextEvent() {
@@ -469,5 +542,109 @@ func (bt *BackTest) processFuturesFillEvent(ev fill.Event, funds funding.IFundRe
// Stop shuts down the live data loop
func (bt *BackTest) Stop() {
if bt == nil {
return
}
bt.m.Lock()
defer bt.m.Unlock()
if bt.MetaData.Closed {
return
}
close(bt.shutdown)
bt.MetaData.Closed = true
bt.MetaData.DateEnded = time.Now()
err := bt.Statistic.CalculateAllResults()
if err != nil {
log.Error(log.Global, err)
return
}
err = bt.Reports.GenerateReport()
if err != nil {
log.Error(log.Global, err)
}
}
// GenerateSummary creates a summary of a backtesting/livestrategy run
// this summary contains many details of a run
func (bt *BackTest) GenerateSummary() (*RunSummary, error) {
if bt == nil {
return nil, gctcommon.ErrNilPointer
}
bt.m.Lock()
defer bt.m.Unlock()
return &RunSummary{
MetaData: bt.MetaData,
}, nil
}
// SetupMetaData will populate metadata fields
func (bt *BackTest) SetupMetaData() error {
if bt == nil {
return gctcommon.ErrNilPointer
}
bt.m.Lock()
defer bt.m.Unlock()
if !bt.MetaData.ID.IsNil() && !bt.MetaData.DateLoaded.IsZero() {
// already setup
return nil
}
id, err := uuid.NewV4()
if err != nil {
return err
}
bt.MetaData.ID = id
bt.MetaData.DateLoaded = time.Now()
return nil
}
// IsRunning checks if the run is running
func (bt *BackTest) IsRunning() bool {
if bt == nil {
return false
}
bt.m.Lock()
defer bt.m.Unlock()
return !bt.MetaData.DateStarted.IsZero() && !bt.MetaData.Closed
}
// HasRan checks if the run has been ran
func (bt *BackTest) HasRan() bool {
if bt == nil {
return false
}
bt.m.Lock()
defer bt.m.Unlock()
return bt.MetaData.Closed
}
// Equal checks if the incoming run matches
func (bt *BackTest) Equal(bt2 *BackTest) bool {
if bt == nil || bt2 == nil {
return false
}
bt.m.Lock()
btM := bt.MetaData
bt.m.Unlock()
// if they are actually the same pointer
// locks must be handled separately
bt2.m.Lock()
btM2 := bt2.MetaData
bt2.m.Unlock()
return btM == btM2
}
// MatchesID checks if the backtesting run's ID matches the supplied
func (bt *BackTest) MatchesID(id uuid.UUID) bool {
if bt == nil {
return false
}
if id.IsNil() {
return false
}
bt.m.Lock()
defer bt.m.Unlock()
if bt.MetaData.ID.IsNil() {
return false
}
return bt.MetaData.ID == id
}

View File

@@ -2,11 +2,14 @@ package engine
import (
"errors"
"path/filepath"
"testing"
"time"
"github.com/gofrs/uuid"
"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"
@@ -24,6 +27,7 @@ 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/currency"
"github.com/thrasher-corp/gocryptotrader/engine"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -180,7 +184,19 @@ func TestFullCycle(t *testing.T) {
func TestStop(t *testing.T) {
t.Parallel()
bt := BackTest{shutdown: make(chan struct{})}
bt := &BackTest{
shutdown: make(chan struct{}),
Statistic: &statistics.Statistic{},
}
bt.Stop()
tt := bt.MetaData.DateEnded
bt.Stop()
if !tt.Equal(bt.MetaData.DateEnded) {
t.Errorf("received '%v' expected '%v'", bt.MetaData.DateEnded, tt)
}
bt = nil
bt.Stop()
}
@@ -965,3 +981,232 @@ func TestProcessFuturesFillEvent(t *testing.T) {
t.Errorf("received '%v' expected '%v'", err, expectedError)
}
}
func TestGenerateSummary(t *testing.T) {
t.Parallel()
bt := &BackTest{}
sum, err := bt.GenerateSummary()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if !sum.MetaData.ID.IsNil() {
t.Errorf("received '%v' expected '%v'", sum.MetaData.ID, "")
}
id, err := uuid.NewV4()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
bt.MetaData.ID = id
sum, err = bt.GenerateSummary()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if sum.MetaData.ID != id {
t.Errorf("received '%v' expected '%v'", sum.MetaData.ID, id)
}
bt = nil
_, err = bt.GenerateSummary()
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
}
func TestSetupMetaData(t *testing.T) {
t.Parallel()
bt := &BackTest{}
err := bt.SetupMetaData()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if bt.MetaData.ID.IsNil() {
t.Errorf("received '%v' expected '%v'", bt.MetaData.ID, "an ID")
}
firstID := bt.MetaData.ID
err = bt.SetupMetaData()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if bt.MetaData.ID != firstID {
t.Errorf("received '%v' expected '%v'", bt.MetaData.ID, firstID)
}
bt = nil
err = bt.SetupMetaData()
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
}
func TestIsRunning(t *testing.T) {
t.Parallel()
bt := &BackTest{}
if bt.IsRunning() {
t.Errorf("received '%v' expected '%v'", true, false)
}
bt.MetaData.DateStarted = time.Now()
if !bt.IsRunning() {
t.Errorf("received '%v' expected '%v'", false, true)
}
bt.MetaData.Closed = true
if bt.IsRunning() {
t.Errorf("received '%v' expected '%v'", true, false)
}
bt = nil
if bt.IsRunning() {
t.Errorf("received '%v' expected '%v'", true, false)
}
}
func TestHasRan(t *testing.T) {
t.Parallel()
bt := &BackTest{}
if bt.HasRan() {
t.Errorf("received '%v' expected '%v'", true, false)
}
bt.MetaData.DateStarted = time.Now()
if bt.HasRan() {
t.Errorf("received '%v' expected '%v'", false, true)
}
bt.MetaData.Closed = true
if !bt.HasRan() {
t.Errorf("received '%v' expected '%v'", true, false)
}
bt = nil
if bt.HasRan() {
t.Errorf("received '%v' expected '%v'", true, false)
}
}
func TestEqual(t *testing.T) {
t.Parallel()
bt := &BackTest{}
bt2 := &BackTest{}
bt3 := &BackTest{}
if !bt.Equal(bt2) {
t.Errorf("received '%v' expected '%v'", false, true)
}
err := bt.SetupMetaData()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
bt2.MetaData = bt.MetaData
if !bt.Equal(bt2) {
t.Errorf("received '%v' expected '%v'", false, true)
}
if bt.Equal(nil) {
t.Errorf("received '%v' expected '%v'", true, false)
}
err = bt3.SetupMetaData()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if bt.Equal(bt3) {
t.Errorf("received '%v' expected '%v'", true, false)
}
bt = nil
if bt.Equal(bt2) {
t.Errorf("received '%v' expected '%v'", true, false)
}
}
func TestMatchesID(t *testing.T) {
t.Parallel()
bt := &BackTest{}
if bt.MatchesID(uuid.Nil) {
t.Errorf("received '%v' expected '%v'", true, false)
}
err := bt.SetupMetaData()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if bt.MatchesID(uuid.Nil) {
t.Errorf("received '%v' expected '%v'", true, false)
}
if !bt.MatchesID(bt.MetaData.ID) {
t.Errorf("received '%v' expected '%v'", false, true)
}
id := bt.MetaData.ID
bt.MetaData.ID = uuid.Nil
if bt.MatchesID(id) {
t.Errorf("received '%v' expected '%v'", true, false)
}
bt = nil
if bt.MatchesID(id) {
t.Errorf("received '%v' expected '%v'", true, false)
}
}
func TestExecuteStrategy(t *testing.T) {
t.Parallel()
bt := &BackTest{}
err := bt.ExecuteStrategy(false)
if !errors.Is(err, errNotSetup) {
t.Errorf("received '%v' expected '%v'", err, errNotSetup)
}
bt.MetaData.DateLoaded = time.Now()
bt.MetaData.DateStarted = time.Now()
err = bt.ExecuteStrategy(false)
if !errors.Is(err, errRunIsRunning) {
t.Errorf("received '%v' expected '%v'", err, errRunIsRunning)
}
strat1 := filepath.Join("..", "config", "strategyexamples", "dca-api-candles.strat")
cfg, err := config.ReadStrategyConfigFromFile(strat1)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
bt, err = NewFromConfig(cfg, "", "", false)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
bt.Stop()
err = bt.ExecuteStrategy(true)
if !errors.Is(err, errAlreadyRan) {
t.Errorf("received '%v' expected '%v'", err, errAlreadyRan)
}
strat2 := filepath.Join("..", "config", "strategyexamples", "dca-candles-live.strat")
cfg, err = config.ReadStrategyConfigFromFile(strat2)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
bt, err = NewFromConfig(cfg, "", "", false)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
err = bt.ExecuteStrategy(true)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
bt.MetaData.DateStarted = time.Time{}
err = bt.ExecuteStrategy(false)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
bt.Stop()
bt = nil
err = bt.ExecuteStrategy(false)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
}

View File

@@ -2,7 +2,10 @@ package engine
import (
"errors"
"sync"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/backtester/data"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/eventholder"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange"
@@ -24,11 +27,14 @@ var (
errNilData = errors.New("nil data received")
errNilExchange = errors.New("nil exchange received")
errLiveUSDTrackingNotSupported = errors.New("USD tracking not supported for live data")
errNotSetup = errors.New("backtesting run not setup")
)
// BackTest is the main holder of all backtesting functionality
type BackTest struct {
m sync.Mutex
hasHandledEvent bool
MetaData RunMetaData
shutdown chan struct{}
Datas data.Holder
Strategy strategies.Handler
@@ -42,3 +48,27 @@ type BackTest struct {
orderManager *engine.OrderManager
databaseManager *engine.DatabaseConnectionManager
}
// RunSummary holds details of a BackTest
// rather than passing entire contents around
type RunSummary struct {
MetaData RunMetaData
}
// RunMetaData contains details about a run such as when it was loaded
type RunMetaData struct {
ID uuid.UUID
Strategy string
DateLoaded time.Time
DateStarted time.Time
DateEnded time.Time
Closed bool
LiveTesting bool
RealOrders bool
}
// RunManager contains all backtesting/livestrategy runs
type RunManager struct {
m sync.Mutex
runs []*BackTest
}

View File

@@ -10,12 +10,14 @@ import (
"path/filepath"
"strings"
"github.com/gofrs/uuid"
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"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/database"
@@ -32,30 +34,39 @@ import (
)
var (
errBadPort = errors.New("received bad port")
errBadPort = errors.New("received bad port")
errCannotHandleRequest = errors.New("cannot handle request")
)
// GRPCServer struct
type GRPCServer struct {
btrpc.BacktesterServiceServer
*config.BacktesterConfig
config *config.BacktesterConfig
manager *RunManager
}
// SetupRPCServer sets up the gRPC server
func SetupRPCServer(cfg *config.BacktesterConfig) *GRPCServer {
return &GRPCServer{
BacktesterConfig: cfg,
func SetupRPCServer(cfg *config.BacktesterConfig, manager *RunManager) (*GRPCServer, error) {
if cfg == nil {
return nil, fmt.Errorf("%w backtester config", common.ErrNilArguments)
}
if manager == nil {
return nil, fmt.Errorf("%w run manager", common.ErrNilArguments)
}
return &GRPCServer{
config: cfg,
manager: manager,
}, nil
}
// StartRPCServer starts a gRPC server with TLS auth
func StartRPCServer(server *GRPCServer) error {
targetDir := utils.GetTLSDir(server.GRPC.TLSDir)
targetDir := utils.GetTLSDir(server.config.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)
log.Debugf(log.GRPCSys, "Backtester GRPC server enabled. Starting GRPC server on https://%v.\n", server.config.GRPC.ListenAddress)
lis, err := net.Listen("tcp", server.config.GRPC.ListenAddress)
if err != nil {
return err
}
@@ -81,7 +92,7 @@ func StartRPCServer(server *GRPCServer) error {
log.Debugln(log.GRPCSys, "GRPC server started!")
if server.GRPC.GRPCProxyEnabled {
if server.config.GRPC.GRPCProxyEnabled {
return server.StartRPCRESTProxy()
}
return nil
@@ -89,8 +100,8 @@ func StartRPCServer(server *GRPCServer) error {
// 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)
log.Debugf(log.GRPCSys, "GRPC proxy server support enabled. Starting gRPC proxy server on http://%v.\n", s.config.GRPC.GRPCProxyListenAddress)
targetDir := utils.GetTLSDir(s.config.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)
@@ -99,18 +110,18 @@ func (s *GRPCServer) StartRPCRESTProxy() error {
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithTransportCredentials(creds),
grpc.WithPerRPCCredentials(auth.BasicAuth{
Username: s.GRPC.Username,
Password: s.GRPC.Password,
Username: s.config.GRPC.Username,
Password: s.config.GRPC.Password,
}),
}
err = btrpc.RegisterBacktesterServiceHandlerFromEndpoint(context.Background(),
mux, s.GRPC.ListenAddress, opts)
mux, s.config.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 {
if err = http.ListenAndServe(s.config.GRPC.GRPCProxyListenAddress, mux); err != nil {
log.Errorf(log.GRPCSys, "GRPC proxy failed to server: %s\n", err)
}
}()
@@ -143,29 +154,93 @@ func (s *GRPCServer) authenticateClient(ctx context.Context) (context.Context, e
username := creds[0]
password := creds[1]
if username != s.GRPC.Username ||
password != s.GRPC.Password {
if username != s.config.GRPC.Username ||
password != s.config.GRPC.Password {
return ctx, fmt.Errorf("username/password mismatch")
}
return ctx, nil
}
// convertSummary converts a run summary into a RPC format
func convertSummary(run *RunSummary) *btrpc.RunSummary {
runSummary := &btrpc.RunSummary{
Id: run.MetaData.ID.String(),
StrategyName: run.MetaData.Strategy,
Closed: run.MetaData.Closed,
LiveTesting: run.MetaData.LiveTesting,
RealOrders: run.MetaData.RealOrders,
}
if !run.MetaData.DateStarted.IsZero() {
runSummary.DateStarted = run.MetaData.DateStarted.Format(gctcommon.SimpleTimeFormatWithTimezone)
}
if !run.MetaData.DateLoaded.IsZero() {
runSummary.DateLoaded = run.MetaData.DateLoaded.Format(gctcommon.SimpleTimeFormatWithTimezone)
}
if !run.MetaData.DateEnded.IsZero() {
runSummary.DateEnded = run.MetaData.DateEnded.Format(gctcommon.SimpleTimeFormatWithTimezone)
}
return runSummary
}
// ExecuteStrategyFromFile will backtest a strategy from the filepath provided
func (s *GRPCServer) ExecuteStrategyFromFile(_ context.Context, request *btrpc.ExecuteStrategyFromFileRequest) (*btrpc.ExecuteStrategyResponse, error) {
if s.config == nil {
return nil, fmt.Errorf("%w server config", gctcommon.ErrNilPointer)
}
if s.manager == nil {
return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer)
}
if request == nil {
return nil, fmt.Errorf("%w nil request", common.ErrNilArguments)
}
if request.DoNotRunImmediately && request.DoNotStore {
return nil, fmt.Errorf("%w cannot manage a run with both dnr and dns", errCannotHandleRequest)
}
dir := request.StrategyFilePath
cfg, err := config.ReadStrategyConfigFromFile(dir)
if err != nil {
return nil, err
}
err = ExecuteStrategy(cfg, s.BacktesterConfig)
err = cfg.Validate()
if err != nil {
return nil, err
}
if cfg == nil {
err = fmt.Errorf("%w backtester config", common.ErrNilArguments)
return nil, err
}
if !s.config.Report.GenerateReport {
s.config.Report.OutputPath = ""
s.config.Report.TemplatePath = ""
}
bt, err := NewFromConfig(cfg, s.config.Report.TemplatePath, s.config.Report.OutputPath, s.config.Verbose)
if err != nil {
return nil, err
}
if !request.DoNotStore {
err = s.manager.AddRun(bt)
if err != nil {
return nil, err
}
}
if !request.DoNotRunImmediately {
err = bt.ExecuteStrategy(false)
if err != nil {
return nil, err
}
}
btSum, err := bt.GenerateSummary()
if err != nil {
return nil, err
}
return &btrpc.ExecuteStrategyResponse{
Success: true,
Run: convertSummary(btSum),
}, nil
}
@@ -173,9 +248,18 @@ func (s *GRPCServer) ExecuteStrategyFromFile(_ context.Context, request *btrpc.E
// 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 s.config == nil {
return nil, fmt.Errorf("%w server config", gctcommon.ErrNilPointer)
}
if s.manager == nil {
return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer)
}
if request == nil || request.Config == nil {
return nil, fmt.Errorf("%w nil request", common.ErrNilArguments)
}
if request.DoNotRunImmediately && request.DoNotStore {
return nil, fmt.Errorf("%w cannot manage a run with both dnr and dns", errCannotHandleRequest)
}
rfr, err := decimal.NewFromString(request.Config.StatisticSettings.RiskFreeRate)
if err != nil {
@@ -495,11 +579,185 @@ func (s *GRPCServer) ExecuteStrategyFromConfig(_ context.Context, request *btrpc
},
}
err = ExecuteStrategy(cfg, s.BacktesterConfig)
if !s.config.Report.GenerateReport {
s.config.Report.OutputPath = ""
s.config.Report.TemplatePath = ""
}
bt, err := NewFromConfig(cfg, s.config.Report.TemplatePath, s.config.Report.OutputPath, s.config.Verbose)
if err != nil {
return nil, err
}
if !request.DoNotStore {
err = s.manager.AddRun(bt)
if err != nil {
return nil, err
}
}
if !request.DoNotRunImmediately {
err = bt.ExecuteStrategy(false)
if err != nil {
return nil, err
}
}
btSum, err := bt.GenerateSummary()
if err != nil {
return nil, err
}
return &btrpc.ExecuteStrategyResponse{
Success: true,
Run: convertSummary(btSum),
}, nil
}
// ListAllRuns returns all backtesting/livestrategy runs managed by the server
func (s *GRPCServer) ListAllRuns(_ context.Context, _ *btrpc.ListAllRunsRequest) (*btrpc.ListAllRunsResponse, error) {
if s.manager == nil {
return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer)
}
list, err := s.manager.List()
if err != nil {
return nil, err
}
response := make([]*btrpc.RunSummary, len(list))
for i := range list {
response[i] = convertSummary(list[i])
}
return &btrpc.ListAllRunsResponse{
Runs: response,
}, nil
}
// StopRun stops a backtest/livestrategy run in its tracks
func (s *GRPCServer) StopRun(_ context.Context, req *btrpc.StopRunRequest) (*btrpc.StopRunResponse, error) {
if s.manager == nil {
return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer)
}
if req == nil {
return nil, fmt.Errorf("%w StopRunRequest", gctcommon.ErrNilPointer)
}
id, err := uuid.FromString(req.Id)
if err != nil {
return nil, err
}
run, err := s.manager.GetSummary(id)
if err != nil {
return nil, err
}
err = s.manager.StopRun(id)
if err != nil {
return nil, err
}
return &btrpc.StopRunResponse{
StoppedRun: convertSummary(run),
}, nil
}
// StopAllRuns stops all backtest/livestrategy runs in its tracks
func (s *GRPCServer) StopAllRuns(_ context.Context, _ *btrpc.StopAllRunsRequest) (*btrpc.StopAllRunsResponse, error) {
if s.manager == nil {
return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer)
}
stopped, err := s.manager.StopAllRuns()
if err != nil {
return nil, err
}
stoppedRuns := make([]*btrpc.RunSummary, len(stopped))
for i := range stopped {
stoppedRuns[i] = convertSummary(stopped[i])
}
return &btrpc.StopAllRunsResponse{
RunsStopped: stoppedRuns,
}, nil
}
// StartRun starts a backtest/livestrategy that was set to not start automatically
func (s *GRPCServer) StartRun(_ context.Context, req *btrpc.StartRunRequest) (*btrpc.StartRunResponse, error) {
if s.manager == nil {
return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer)
}
if req == nil {
return nil, fmt.Errorf("%w StartRunRequest", gctcommon.ErrNilPointer)
}
id, err := uuid.FromString(req.Id)
if err != nil {
return nil, err
}
err = s.manager.StartRun(id)
if err != nil {
return nil, err
}
return &btrpc.StartRunResponse{
Started: true,
}, nil
}
// StartAllRuns starts all backtest/livestrategy runs
func (s *GRPCServer) StartAllRuns(_ context.Context, _ *btrpc.StartAllRunsRequest) (*btrpc.StartAllRunsResponse, error) {
if s.manager == nil {
return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer)
}
started, err := s.manager.StartAllRuns()
if err != nil {
return nil, err
}
startedRuns := make([]string, len(started))
for i := range started {
startedRuns[i] = started[i].String()
}
return &btrpc.StartAllRunsResponse{
RunsStarted: startedRuns,
}, nil
}
// ClearRun removes a run from memory, but only if it is not running
func (s *GRPCServer) ClearRun(_ context.Context, req *btrpc.ClearRunRequest) (*btrpc.ClearRunResponse, error) {
if s.manager == nil {
return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer)
}
if req == nil {
return nil, fmt.Errorf("%w ClearRunRequest", gctcommon.ErrNilPointer)
}
id, err := uuid.FromString(req.Id)
if err != nil {
return nil, err
}
run, err := s.manager.GetSummary(id)
if err != nil {
return nil, err
}
err = s.manager.ClearRun(id)
if err != nil {
return nil, err
}
return &btrpc.ClearRunResponse{
ClearedRun: convertSummary(run),
}, nil
}
// ClearAllRuns removes all runs from memory, but only if they are not running
func (s *GRPCServer) ClearAllRuns(_ context.Context, _ *btrpc.ClearAllRunsRequest) (*btrpc.ClearAllRunsResponse, error) {
if s.manager == nil {
return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer)
}
clearedRuns, remainingRuns, err := s.manager.ClearAllRuns()
if err != nil {
return nil, err
}
clearedResponse := make([]*btrpc.RunSummary, len(clearedRuns))
for i := range clearedRuns {
clearedResponse[i] = convertSummary(clearedRuns[i])
}
remainingResponse := make([]*btrpc.RunSummary, len(remainingRuns))
for i := range remainingRuns {
remainingResponse[i] = convertSummary(remainingRuns[i])
}
return &btrpc.ClearAllRunsResponse{
ClearedRuns: clearedResponse,
RemainingRuns: remainingResponse,
}, nil
}

View File

@@ -11,6 +11,11 @@ import (
"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/backtester/data"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/eventholder"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/ftxcashandcarry"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"google.golang.org/protobuf/types/known/timestamppb"
)
@@ -20,6 +25,22 @@ func TestExecuteStrategyFromFile(t *testing.T) {
t.Parallel()
s := &GRPCServer{}
_, err := s.ExecuteStrategyFromFile(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
s.config, err = config.GenerateDefaultConfig()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
s.config.Report.GenerateReport = false
_, err = s.ExecuteStrategyFromFile(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
s.manager = SetupRunManager()
_, err = s.ExecuteStrategyFromFile(context.Background(), nil)
if !errors.Is(err, common.ErrNilArguments) {
t.Errorf("received '%v' expecting '%v'", err, common.ErrNilArguments)
}
@@ -29,32 +50,43 @@ func TestExecuteStrategyFromFile(t *testing.T) {
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)
}
_, err = s.ExecuteStrategyFromFile(context.Background(), &btrpc.ExecuteStrategyFromFileRequest{
StrategyFilePath: dcaConfigPath,
DoNotRunImmediately: true,
DoNotStore: true,
})
if !errors.Is(err, errCannotHandleRequest) {
t.Errorf("received '%v' expecting '%v'", err, errCannotHandleRequest)
}
}
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)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
s.BacktesterConfig = &config.BacktesterConfig{}
_, err = s.ExecuteStrategyFromConfig(context.Background(), &btrpc.ExecuteStrategyFromConfigRequest{})
s.config, err = config.GenerateDefaultConfig()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
_, err = s.ExecuteStrategyFromConfig(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
s.config.Report.GenerateReport = false
s.manager = SetupRunManager()
_, err = s.ExecuteStrategyFromConfig(context.Background(), nil)
if !errors.Is(err, common.ErrNilArguments) {
t.Errorf("received '%v' expecting '%v'", err, common.ErrNilArguments)
}
@@ -239,6 +271,15 @@ func TestExecuteStrategyFromConfig(t *testing.T) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
_, err = s.ExecuteStrategyFromConfig(context.Background(), &btrpc.ExecuteStrategyFromConfigRequest{
DoNotRunImmediately: true,
DoNotStore: true,
Config: cfg,
})
if !errors.Is(err, errCannotHandleRequest) {
t.Errorf("received '%v' expecting '%v'", err, errCannotHandleRequest)
}
// coverage test to ensure the rest of the config can successfully be converted
// this will not have a successful response
cfg.FundingSettings.UseExchangeLevelFunding = true
@@ -283,3 +324,279 @@ func TestExecuteStrategyFromConfig(t *testing.T) {
t.Error("expected an error from a bad setup")
}
}
func TestListAllRuns(t *testing.T) {
t.Parallel()
s := &GRPCServer{}
_, err := s.ListAllRuns(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
s.manager = SetupRunManager()
_, err = s.ListAllRuns(context.Background(), nil)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
EventQueue: &eventholder.Holder{},
Datas: &data.HandlerPerCurrency{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = s.manager.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
resp, err := s.ListAllRuns(context.Background(), &btrpc.ListAllRunsRequest{})
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
if len(resp.Runs) != 1 {
t.Errorf("received '%v' expecting '%v'", len(resp.Runs), 1)
}
}
func TestGRPCStopRun(t *testing.T) {
t.Parallel()
s := &GRPCServer{}
_, err := s.StopRun(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
s.manager = SetupRunManager()
_, err = s.StopRun(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
EventQueue: &eventholder.Holder{},
Datas: &data.HandlerPerCurrency{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = s.manager.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = s.StopRun(context.Background(), &btrpc.StopRunRequest{
Id: bt.MetaData.ID.String(),
})
if !errors.Is(err, errRunHasNotRan) {
t.Errorf("received '%v' expecting '%v'", err, errRunHasNotRan)
}
if len(s.manager.runs) != 1 {
t.Fatalf("received '%v' expecting '%v'", len(s.manager.runs), 1)
}
s.manager.runs[0].MetaData.DateStarted = time.Now()
_, err = s.StopRun(context.Background(), &btrpc.StopRunRequest{
Id: bt.MetaData.ID.String(),
})
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
if s.manager.runs[0].MetaData.DateEnded.IsZero() {
t.Errorf("received '%v' expecting '%v'", s.manager.runs[0].MetaData.DateEnded, "a date")
}
}
func TestGRPCStopAllRuns(t *testing.T) {
t.Parallel()
s := &GRPCServer{}
_, err := s.StopAllRuns(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
s.manager = SetupRunManager()
_, err = s.StopAllRuns(context.Background(), nil)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
EventQueue: &eventholder.Holder{},
Datas: &data.HandlerPerCurrency{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = s.manager.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
resp, err := s.StopAllRuns(context.Background(), &btrpc.StopAllRunsRequest{})
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
if len(s.manager.runs) != 1 {
t.Fatalf("received '%v' expecting '%v'", len(s.manager.runs), 1)
}
if len(resp.RunsStopped) != 0 {
t.Errorf("received '%v' expecting '%v'", len(resp.RunsStopped), 0)
}
s.manager.runs[0].MetaData.DateStarted = time.Now()
resp, err = s.StopAllRuns(context.Background(), &btrpc.StopAllRunsRequest{})
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
if s.manager.runs[0].MetaData.DateEnded.IsZero() {
t.Errorf("received '%v' expecting '%v'", s.manager.runs[0].MetaData.DateEnded, "a date")
}
if len(resp.RunsStopped) != 1 {
t.Errorf("received '%v' expecting '%v'", len(resp.RunsStopped), 1)
}
}
func TestGRPCStartRun(t *testing.T) {
t.Parallel()
s := &GRPCServer{}
_, err := s.StartRun(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
s.manager = SetupRunManager()
_, err = s.StartRun(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
EventQueue: &eventholder.Holder{},
Datas: &data.HandlerPerCurrency{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = s.manager.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = s.StartRun(context.Background(), &btrpc.StartRunRequest{
Id: bt.MetaData.ID.String(),
})
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
if len(s.manager.runs) != 1 {
t.Fatalf("received '%v' expecting '%v'", len(s.manager.runs), 1)
}
if s.manager.runs[0].MetaData.DateStarted.IsZero() {
t.Errorf("received '%v' expecting '%v'", s.manager.runs[0].MetaData.DateStarted, "a date")
}
}
func TestGRPCStartAllRuns(t *testing.T) {
t.Parallel()
s := &GRPCServer{}
_, err := s.StartAllRuns(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
s.manager = SetupRunManager()
_, err = s.StartAllRuns(context.Background(), nil)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
EventQueue: &eventholder.Holder{},
Datas: &data.HandlerPerCurrency{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = s.manager.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = s.StartAllRuns(context.Background(), &btrpc.StartAllRunsRequest{})
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
if len(s.manager.runs) != 1 {
t.Fatalf("received '%v' expecting '%v'", len(s.manager.runs), 1)
}
if s.manager.runs[0].MetaData.DateStarted.IsZero() {
t.Errorf("received '%v' expecting '%v'", s.manager.runs[0].MetaData.DateStarted, "a date")
}
}
func TestGRPCClearRun(t *testing.T) {
t.Parallel()
s := &GRPCServer{}
_, err := s.ClearRun(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
s.manager = SetupRunManager()
_, err = s.ClearRun(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
EventQueue: &eventholder.Holder{},
Datas: &data.HandlerPerCurrency{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = s.manager.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = s.ClearRun(context.Background(), &btrpc.ClearRunRequest{
Id: bt.MetaData.ID.String(),
})
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
if len(s.manager.runs) != 0 {
t.Fatalf("received '%v' expecting '%v'", len(s.manager.runs), 0)
}
}
func TestGRPCClearAllRuns(t *testing.T) {
t.Parallel()
s := &GRPCServer{}
_, err := s.ClearAllRuns(context.Background(), nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer)
}
s.manager = SetupRunManager()
_, err = s.ClearAllRuns(context.Background(), nil)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
EventQueue: &eventholder.Holder{},
Datas: &data.HandlerPerCurrency{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = s.manager.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = s.ClearAllRuns(context.Background(), &btrpc.ClearAllRunsRequest{})
if !errors.Is(err, nil) {
t.Errorf("received '%v' expecting '%v'", err, nil)
}
if len(s.manager.runs) != 0 {
t.Fatalf("received '%v' expecting '%v'", len(s.manager.runs), 0)
}
}

View File

@@ -0,0 +1,214 @@
package engine
import (
"errors"
"fmt"
"github.com/gofrs/uuid"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
)
var (
errRunNotFound = errors.New("run not found")
errRunAlreadyMonitored = errors.New("run already monitored")
errAlreadyRan = errors.New("run already ran")
errRunHasNotRan = errors.New("run hasn't ran yet")
errRunIsRunning = errors.New("run is already running")
errCannotClear = errors.New("cannot clear run")
)
// SetupRunManager creates a run manager to allow the backtester to manage multiple strategies
func SetupRunManager() *RunManager {
return &RunManager{}
}
// AddRun adds a run to the manager
func (r *RunManager) AddRun(b *BackTest) error {
if r == nil {
return fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer)
}
if b == nil {
return fmt.Errorf("%w BackTest", gctcommon.ErrNilPointer)
}
r.m.Lock()
defer r.m.Unlock()
for i := range r.runs {
if r.runs[i].Equal(b) {
return fmt.Errorf("%w %s %s", errRunAlreadyMonitored, b.MetaData.ID, b.MetaData.Strategy)
}
}
err := b.SetupMetaData()
if err != nil {
return err
}
r.runs = append(r.runs, b)
return nil
}
// List details all backtesting/livestrategy runs
func (r *RunManager) List() ([]*RunSummary, error) {
if r == nil {
return nil, fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer)
}
r.m.Lock()
defer r.m.Unlock()
resp := make([]*RunSummary, len(r.runs))
for i := range r.runs {
sum, err := r.runs[i].GenerateSummary()
if err != nil {
return nil, err
}
resp[i] = sum
}
return resp, nil
}
// GetSummary returns details about a completed backtesting/livestrategy run
func (r *RunManager) GetSummary(id uuid.UUID) (*RunSummary, error) {
if r == nil {
return nil, fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer)
}
r.m.Lock()
defer r.m.Unlock()
for i := range r.runs {
if !r.runs[i].MatchesID(id) {
continue
}
return r.runs[i].GenerateSummary()
}
return nil, fmt.Errorf("%s %w", id, errRunNotFound)
}
// StopRun stops a backtesting/livestrategy run if enabled, this will run CloseAllPositions
func (r *RunManager) StopRun(id uuid.UUID) error {
if r == nil {
return fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer)
}
r.m.Lock()
defer r.m.Unlock()
for i := range r.runs {
switch {
case !r.runs[i].MatchesID(id):
continue
case r.runs[i].IsRunning():
r.runs[i].Stop()
return nil
case r.runs[i].HasRan():
return fmt.Errorf("%w %v", errAlreadyRan, id)
default:
return fmt.Errorf("%w %v", errRunHasNotRan, id)
}
}
return fmt.Errorf("%s %w", id, errRunNotFound)
}
// StopAllRuns stops all running strategies
func (r *RunManager) StopAllRuns() ([]*RunSummary, error) {
if r == nil {
return nil, fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer)
}
r.m.Lock()
defer r.m.Unlock()
resp := make([]*RunSummary, 0, len(r.runs))
for i := range r.runs {
if !r.runs[i].IsRunning() {
continue
}
r.runs[i].Stop()
sum, err := r.runs[i].GenerateSummary()
if err != nil {
return nil, err
}
resp = append(resp, sum)
}
return resp, nil
}
// StartRun executes a strategy if found
func (r *RunManager) StartRun(id uuid.UUID) error {
if r == nil {
return fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer)
}
r.m.Lock()
defer r.m.Unlock()
for i := range r.runs {
switch {
case !r.runs[i].MatchesID(id):
continue
case r.runs[i].IsRunning():
return fmt.Errorf("%w %v", errRunIsRunning, id)
case r.runs[i].HasRan():
return fmt.Errorf("%w %v", errAlreadyRan, id)
default:
return r.runs[i].ExecuteStrategy(false)
}
}
return fmt.Errorf("%s %w", id, errRunNotFound)
}
// StartAllRuns executes all strategies
func (r *RunManager) StartAllRuns() ([]uuid.UUID, error) {
if r == nil {
return nil, fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer)
}
r.m.Lock()
defer r.m.Unlock()
executedRuns := make([]uuid.UUID, 0, len(r.runs))
for i := range r.runs {
if r.runs[i].HasRan() {
continue
}
executedRuns = append(executedRuns, r.runs[i].MetaData.ID)
err := r.runs[i].ExecuteStrategy(false)
if err != nil {
return nil, err
}
}
return executedRuns, nil
}
// ClearRun removes a run from memory
func (r *RunManager) ClearRun(id uuid.UUID) error {
if r == nil {
return fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer)
}
r.m.Lock()
defer r.m.Unlock()
for i := range r.runs {
if !r.runs[i].MatchesID(id) {
continue
}
if r.runs[i].IsRunning() {
return fmt.Errorf("%w %v, currently running. Stop it first", errCannotClear, r.runs[i].MetaData.ID)
}
r.runs = append(r.runs[:i], r.runs[i+1:]...)
return nil
}
return fmt.Errorf("%s %w", id, errRunNotFound)
}
// ClearAllRuns removes all runs from memory
func (r *RunManager) ClearAllRuns() (clearedRuns, remainingRuns []*RunSummary, err error) {
if r == nil {
return nil, nil, fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer)
}
r.m.Lock()
defer r.m.Unlock()
for i := 0; i < len(r.runs); i++ {
var run *RunSummary
run, err = r.runs[i].GenerateSummary()
if err != nil {
return nil, nil, err
}
if r.runs[i].IsRunning() {
remainingRuns = append(remainingRuns, run)
} else {
clearedRuns = append(clearedRuns, run)
r.runs = append(r.runs[:i], r.runs[i+1:]...)
i--
}
}
return clearedRuns, remainingRuns, nil
}

View File

@@ -0,0 +1,425 @@
package engine
import (
"errors"
"testing"
"time"
"github.com/gofrs/uuid"
"github.com/thrasher-corp/gocryptotrader/backtester/data"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/eventholder"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/ftxcashandcarry"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
)
func TestSetupRunManager(t *testing.T) {
t.Parallel()
rm := SetupRunManager()
if rm == nil {
t.Errorf("received '%v' expected '%v'", rm, "&RunManager{}")
}
}
func TestAddRun(t *testing.T) {
t.Parallel()
rm := SetupRunManager()
err := rm.AddRun(nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
bt := &BackTest{}
err = rm.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if bt.MetaData.ID.IsNil() {
t.Errorf("received '%v' expected '%v'", bt.MetaData.ID, "a random ID")
}
if len(rm.runs) != 1 {
t.Errorf("received '%v' expected '%v'", len(rm.runs), 1)
}
err = rm.AddRun(bt)
if !errors.Is(err, errRunAlreadyMonitored) {
t.Errorf("received '%v' expected '%v'", err, errRunAlreadyMonitored)
}
if len(rm.runs) != 1 {
t.Errorf("received '%v' expected '%v'", len(rm.runs), 1)
}
rm = nil
err = rm.AddRun(bt)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
}
func TestGetSummary(t *testing.T) {
t.Parallel()
rm := SetupRunManager()
id, err := uuid.NewV4()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = rm.GetSummary(id)
if !errors.Is(err, errRunNotFound) {
t.Errorf("received '%v' expected '%v'", err, errRunNotFound)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
Statistic: &statistics.Statistic{},
}
err = rm.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
sum, err := rm.GetSummary(bt.MetaData.ID)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if sum.MetaData.ID != bt.MetaData.ID {
t.Errorf("received '%v' expected '%v'", sum.MetaData.ID, bt.MetaData.ID)
}
rm = nil
_, err = rm.GetSummary(id)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
}
func TestList(t *testing.T) {
t.Parallel()
rm := SetupRunManager()
list, err := rm.List()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(list) != 0 {
t.Errorf("received '%v' expected '%v'", len(list), 0)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
Statistic: &statistics.Statistic{},
}
err = rm.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
list, err = rm.List()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(list) != 1 {
t.Errorf("received '%v' expected '%v'", len(list), 1)
}
rm = nil
_, err = rm.List()
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
}
func TestStopRun(t *testing.T) {
t.Parallel()
rm := SetupRunManager()
list, err := rm.List()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(list) != 0 {
t.Errorf("received '%v' expected '%v'", len(list), 0)
}
id, err := uuid.NewV4()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
err = rm.StopRun(id)
if !errors.Is(err, errRunNotFound) {
t.Errorf("received '%v' expected '%v'", err, errRunNotFound)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = rm.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
err = rm.StopRun(bt.MetaData.ID)
if !errors.Is(err, errRunHasNotRan) {
t.Errorf("received '%v' expected '%v'", err, errRunHasNotRan)
}
bt.MetaData.DateStarted = time.Now()
err = rm.StopRun(bt.MetaData.ID)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
err = rm.StopRun(bt.MetaData.ID)
if !errors.Is(err, errAlreadyRan) {
t.Errorf("received '%v' expected '%v'", err, errAlreadyRan)
}
rm = nil
err = rm.StopRun(id)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
}
func TestStopAllRuns(t *testing.T) {
t.Parallel()
rm := SetupRunManager()
stoppedRuns, err := rm.StopAllRuns()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(stoppedRuns) != 0 {
t.Errorf("received '%v' expected '%v'", len(stoppedRuns), 0)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = rm.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
bt.MetaData.DateStarted = time.Now()
stoppedRuns, err = rm.StopAllRuns()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(stoppedRuns) != 1 {
t.Errorf("received '%v' expected '%v'", len(stoppedRuns), 1)
}
rm = nil
_, err = rm.StopAllRuns()
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
}
func TestStartRun(t *testing.T) {
t.Parallel()
rm := SetupRunManager()
list, err := rm.List()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(list) != 0 {
t.Errorf("received '%v' expected '%v'", len(list), 0)
}
id, err := uuid.NewV4()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
err = rm.StartRun(id)
if !errors.Is(err, errRunNotFound) {
t.Errorf("received '%v' expected '%v'", err, errRunNotFound)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
EventQueue: &eventholder.Holder{},
Datas: &data.HandlerPerCurrency{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = rm.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
err = rm.StartRun(bt.MetaData.ID)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
err = rm.StartRun(bt.MetaData.ID)
if !errors.Is(err, errRunIsRunning) {
t.Errorf("received '%v' expected '%v'", err, errRunIsRunning)
}
bt.MetaData.DateEnded = time.Now()
bt.MetaData.Closed = true
err = rm.StartRun(bt.MetaData.ID)
if !errors.Is(err, errAlreadyRan) {
t.Errorf("received '%v' expected '%v'", err, errAlreadyRan)
}
rm = nil
err = rm.StartRun(id)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
}
func TestStartAllRuns(t *testing.T) {
t.Parallel()
rm := SetupRunManager()
startedRuns, err := rm.StartAllRuns()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(startedRuns) != 0 {
t.Errorf("received '%v' expected '%v'", len(startedRuns), 0)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
EventQueue: &eventholder.Holder{},
Datas: &data.HandlerPerCurrency{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = rm.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
startedRuns, err = rm.StartAllRuns()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(startedRuns) != 1 {
t.Errorf("received '%v' expected '%v'", len(startedRuns), 1)
}
rm = nil
_, err = rm.StartAllRuns()
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
}
func TestClearRun(t *testing.T) {
t.Parallel()
rm := SetupRunManager()
id, err := uuid.NewV4()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
err = rm.ClearRun(id)
if !errors.Is(err, errRunNotFound) {
t.Errorf("received '%v' expected '%v'", err, errRunNotFound)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
EventQueue: &eventholder.Holder{},
Datas: &data.HandlerPerCurrency{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = rm.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
bt.MetaData.DateStarted = time.Now()
err = rm.ClearRun(bt.MetaData.ID)
if !errors.Is(err, errCannotClear) {
t.Errorf("received '%v' expected '%v'", err, errCannotClear)
}
bt.MetaData.DateStarted = time.Time{}
err = rm.ClearRun(bt.MetaData.ID)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
list, err := rm.List()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(list) != 0 {
t.Errorf("received '%v' expected '%v'", len(list), 0)
}
rm = nil
err = rm.ClearRun(id)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
}
func TestClearAllRuns(t *testing.T) {
t.Parallel()
rm := SetupRunManager()
clearedRuns, remainingRuns, err := rm.ClearAllRuns()
if len(clearedRuns) != 0 {
t.Errorf("received '%v' expected '%v'", len(clearedRuns), 0)
}
if len(remainingRuns) != 0 {
t.Errorf("received '%v' expected '%v'", len(remainingRuns), 0)
}
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
bt := &BackTest{
Strategy: &ftxcashandcarry.Strategy{},
EventQueue: &eventholder.Holder{},
Datas: &data.HandlerPerCurrency{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
err = rm.AddRun(bt)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
bt.MetaData.DateStarted = time.Now()
clearedRuns, remainingRuns, err = rm.ClearAllRuns()
if len(clearedRuns) != 0 {
t.Errorf("received '%v' expected '%v'", len(clearedRuns), 0)
}
if len(remainingRuns) != 1 {
t.Errorf("received '%v' expected '%v'", len(remainingRuns), 1)
}
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
bt.MetaData.DateStarted = time.Time{}
clearedRuns, remainingRuns, err = rm.ClearAllRuns()
if len(clearedRuns) != 1 {
t.Errorf("received '%v' expected '%v'", len(clearedRuns), 1)
}
if len(remainingRuns) != 0 {
t.Errorf("received '%v' expected '%v'", len(remainingRuns), 0)
}
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
list, err := rm.List()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if len(list) != 0 {
t.Errorf("received '%v' expected '%v'", len(list), 0)
}
rm = nil
_, _, err = rm.ClearAllRuns()
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
}

View File

@@ -40,7 +40,6 @@ 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
@@ -49,13 +48,16 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool
if cfg == nil {
return nil, errNilConfig
}
var err error
bt := New()
bt, err := New()
if err != nil {
return nil, err
}
bt.exchangeManager = engine.SetupExchangeManager()
bt.orderManager, err = engine.SetupOrderManager(bt.exchangeManager, &engine.CommunicationManager{}, &sync.WaitGroup{}, false, false, 0)
if err != nil {
return nil, err
}
err = bt.orderManager.Start()
if err != nil {
return nil, err
@@ -348,7 +350,9 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool
if err != nil {
return nil, err
}
bt.MetaData.Strategy = bt.Strategy.Name()
bt.Strategy.SetDefaults()
if cfg.StrategySettings.CustomSettings != nil {
err = bt.Strategy.SetCustomSettings(cfg.StrategySettings.CustomSettings)
if err != nil && !errors.Is(err, base.ErrCustomSettingsUnsupported) {
@@ -514,6 +518,8 @@ func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (exchange.Exchange
realOrders := false
if cfg.DataSettings.LiveData != nil {
realOrders = cfg.DataSettings.LiveData.RealOrders
bt.MetaData.LiveTesting = true
bt.MetaData.RealOrders = realOrders
}
buyRule := exchange.MinMax{
@@ -880,45 +886,24 @@ func loadLiveData(cfg *config.Config, base *gctexchange.Base) error {
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
// NewBacktesterFromConfigs creates a new backtester based on config settings
func NewBacktesterFromConfigs(strategyCfg *config.Config, backtesterCfg *config.BacktesterConfig) (*BackTest, error) {
if strategyCfg == nil {
return nil, fmt.Errorf("%w strategy config", gctcommon.ErrNilPointer)
}
if backtesterCfg == nil {
err := fmt.Errorf("%w backtester config", common.ErrNilArguments)
return err
return nil, fmt.Errorf("%w backtester config", gctcommon.ErrNilPointer)
}
if err := strategyCfg.Validate(); err != nil {
return nil, err
}
bt, err := NewFromConfig(strategyCfg, backtesterCfg.Report.TemplatePath, backtesterCfg.Report.OutputPath, backtesterCfg.Verbose)
if err != nil {
return err
return nil, 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()
err = bt.SetupMetaData()
if err != nil {
return err
return nil, err
}
if backtesterCfg.Report.GenerateReport {
bt.Reports.UseDarkMode(backtesterCfg.Report.DarkMode)
err = bt.Reports.GenerateReport()
if err != nil {
return err
}
}
return nil
return bt, nil
}

View File

@@ -2,6 +2,7 @@ package engine
import (
"errors"
"path/filepath"
"strings"
"testing"
"time"
@@ -10,6 +11,7 @@ import (
"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/statistics"
"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"
@@ -128,7 +130,8 @@ func TestNewFromConfig(t *testing.T) {
func TestLoadDataAPI(t *testing.T) {
t.Parallel()
bt := BackTest{
Reports: &report.Data{},
Reports: &report.Data{},
Statistic: &statistics.Statistic{},
}
cp := currency.NewPair(currency.BTC, currency.USDT)
cfg := &config.Config{
@@ -185,7 +188,8 @@ func TestLoadDataAPI(t *testing.T) {
func TestLoadDataDatabase(t *testing.T) {
t.Parallel()
bt := BackTest{
Reports: &report.Data{},
Reports: &report.Data{},
Statistic: &statistics.Statistic{},
}
cp := currency.NewPair(currency.BTC, currency.USDT)
cfg := &config.Config{
@@ -253,7 +257,8 @@ func TestLoadDataDatabase(t *testing.T) {
func TestLoadDataCSV(t *testing.T) {
t.Parallel()
bt := BackTest{
Reports: &report.Data{},
Reports: &report.Data{},
Statistic: &statistics.Statistic{},
}
cp := currency.NewPair(currency.BTC, currency.USDT)
cfg := &config.Config{
@@ -310,8 +315,9 @@ func TestLoadDataCSV(t *testing.T) {
func TestLoadDataLive(t *testing.T) {
t.Parallel()
bt := BackTest{
Reports: &report.Data{},
shutdown: make(chan struct{}),
Reports: &report.Data{},
Statistic: &statistics.Statistic{},
shutdown: make(chan struct{}),
}
cp := currency.NewPair(currency.BTC, currency.USDT)
cfg := &config.Config{
@@ -367,3 +373,39 @@ func TestLoadDataLive(t *testing.T) {
}
bt.Stop()
}
func TestNewBacktesterFromConfigs(t *testing.T) {
t.Parallel()
_, err := NewBacktesterFromConfigs(nil, nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
strat1 := filepath.Join("..", "config", "strategyexamples", "dca-api-candles.strat")
cfg, err := config.ReadStrategyConfigFromFile(strat1)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
dc, err := config.GenerateDefaultConfig()
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
_, err = NewBacktesterFromConfigs(cfg, nil)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
_, err = NewBacktesterFromConfigs(nil, dc)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
bt, err := NewBacktesterFromConfigs(cfg, dc)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
}
if bt.MetaData.DateLoaded.IsZero() {
t.Errorf("received '%v' expected '%v'", bt.MetaData.DateLoaded, "a date")
}
}

View File

@@ -297,6 +297,15 @@ func (s *Statistic) SetStrategyName(name string) {
// Serialise outputs the Statistic struct in json
func (s *Statistic) Serialise() (string, error) {
s.CurrencyStatistics = nil
for _, exchangeMap := range s.ExchangeAssetPairStatistics {
for _, assetMap := range exchangeMap {
for _, stats := range assetMap {
s.CurrencyStatistics = append(s.CurrencyStatistics, stats)
}
}
}
resp, err := json.MarshalIndent(s, "", " ")
if err != nil {
return "", err

View File

@@ -41,7 +41,8 @@ type Statistic struct {
EndDate time.Time `json:"end-date"`
CandleInterval gctkline.Interval `json:"candle-interval"`
RiskFreeRate decimal.Decimal `json:"risk-free-rate"`
ExchangeAssetPairStatistics map[string]map[asset.Item]map[currency.Pair]*CurrencyPairStatistic `json:"exchange-asset-pair-statistics"`
ExchangeAssetPairStatistics map[string]map[asset.Item]map[currency.Pair]*CurrencyPairStatistic `json:"-"`
CurrencyStatistics []*CurrencyPairStatistic `json:"currency-statistics"`
TotalBuyOrders int64 `json:"total-buy-orders"`
TotalLongOrders int64 `json:"total-long-orders"`
TotalShortOrders int64 `json:"total-short-orders"`
@@ -164,11 +165,11 @@ type CurrencyPairStatistic struct {
UnrealisedPNL decimal.Decimal `json:"unrealised-pnl"`
RealisedPNL decimal.Decimal `json:"realised-pnl"`
CompoundAnnualGrowthRate decimal.Decimal `json:"compound-annual-growth-rate"`
TotalAssetValue decimal.Decimal
TotalFees decimal.Decimal
TotalValueLostToVolumeSizing decimal.Decimal
TotalValueLostToSlippage decimal.Decimal
TotalValueLost decimal.Decimal
TotalAssetValue decimal.Decimal `json:"total-asset-value"`
TotalFees decimal.Decimal `json:"total-fees"`
TotalValueLostToVolumeSizing decimal.Decimal `json:"total-value-lost-to-volume-sizing"`
TotalValueLostToSlippage decimal.Decimal `json:"total-value-lost-to-slippage"`
TotalValueLost decimal.Decimal `json:"total-value-lost"`
Events []DataAtOffset `json:"-"`
@@ -194,7 +195,7 @@ type Swing struct {
Highest ValueAtTime `json:"highest"`
Lowest ValueAtTime `json:"lowest"`
DrawdownPercent decimal.Decimal `json:"drawdown"`
IntervalDuration int64
IntervalDuration int64 `json:"interval-duration"`
}
// ValueAtTime is an individual iteration of price at a time
@@ -211,55 +212,55 @@ type relatedCurrencyPairStatistics struct {
// FundingStatistics stores all funding related statistics
type FundingStatistics struct {
Report *funding.Report
Items []FundingItemStatistics
TotalUSDStatistics *TotalFundingStatistics
Report *funding.Report `json:"-"`
Items []FundingItemStatistics `json:"funding-item-statistics"`
TotalUSDStatistics *TotalFundingStatistics `json:"total-usd-statistics"`
}
// FundingItemStatistics holds statistics for funding items
type FundingItemStatistics struct {
ReportItem *funding.ReportItem
ReportItem *funding.ReportItem `json:"-"`
// USD stats
StartingClosePrice ValueAtTime
EndingClosePrice ValueAtTime
LowestClosePrice ValueAtTime
HighestClosePrice ValueAtTime
MarketMovement decimal.Decimal
StrategyMovement decimal.Decimal
DidStrategyBeatTheMarket bool
RiskFreeRate decimal.Decimal
CompoundAnnualGrowthRate decimal.Decimal
BuyOrders int64
SellOrders int64
TotalOrders int64
MaxDrawdown Swing
HighestCommittedFunds ValueAtTime
StartingClosePrice ValueAtTime `json:"starting-close-price"`
EndingClosePrice ValueAtTime `json:"ending-close-price"`
LowestClosePrice ValueAtTime `json:"lowest-close-price"`
HighestClosePrice ValueAtTime `json:"highest-close-price"`
MarketMovement decimal.Decimal `json:"market-movement"`
StrategyMovement decimal.Decimal `json:"strategy-movement"`
DidStrategyBeatTheMarket bool `json:"did-strategy-beat-the-market"`
RiskFreeRate decimal.Decimal `json:"risk-free-rate"`
CompoundAnnualGrowthRate decimal.Decimal `json:"compound-annual-growth-rate"`
BuyOrders int64 `json:"buy-orders"`
SellOrders int64 `json:"sell-orders"`
TotalOrders int64 `json:"total-orders"`
MaxDrawdown Swing `json:"max-drawdown"`
HighestCommittedFunds ValueAtTime `json:"highest-committed-funds"`
// CollateralPair stats
IsCollateral bool
InitialCollateral ValueAtTime
FinalCollateral ValueAtTime
HighestCollateral ValueAtTime
LowestCollateral ValueAtTime
IsCollateral bool `json:"is-collateral"`
InitialCollateral ValueAtTime `json:"initial-collateral"`
FinalCollateral ValueAtTime `json:"final-collateral"`
HighestCollateral ValueAtTime `json:"highest-collateral"`
LowestCollateral ValueAtTime `json:"lowest-collateral"`
// Contracts
LowestHoldings ValueAtTime
HighestHoldings ValueAtTime
InitialHoldings ValueAtTime
FinalHoldings ValueAtTime
LowestHoldings ValueAtTime `json:"lowest-holdings"`
HighestHoldings ValueAtTime `json:"highest-holdings"`
InitialHoldings ValueAtTime `json:"initial-holdings"`
FinalHoldings ValueAtTime `json:"final-holdings"`
}
// TotalFundingStatistics holds values for overall statistics for funding items
type TotalFundingStatistics struct {
HoldingValues []ValueAtTime
HighestHoldingValue ValueAtTime
LowestHoldingValue ValueAtTime
BenchmarkMarketMovement decimal.Decimal
StrategyMovement decimal.Decimal
RiskFreeRate decimal.Decimal
CompoundAnnualGrowthRate decimal.Decimal
MaxDrawdown Swing
GeometricRatios *Ratios
ArithmeticRatios *Ratios
DidStrategyBeatTheMarket bool
DidStrategyMakeProfit bool
HoldingValueDifference decimal.Decimal
HoldingValues []ValueAtTime `json:"-"`
HighestHoldingValue ValueAtTime `json:"highest-holding-value"`
LowestHoldingValue ValueAtTime `json:"lowest-holding-value"`
BenchmarkMarketMovement decimal.Decimal `json:"benchmark-market-movement"`
StrategyMovement decimal.Decimal `json:"strategy-movement"`
RiskFreeRate decimal.Decimal `json:"risk-free-rate"`
CompoundAnnualGrowthRate decimal.Decimal `json:"compound-annual-growth-rate"`
MaxDrawdown Swing `json:"max-drawdown"`
GeometricRatios *Ratios `json:"geometric-ratios"`
ArithmeticRatios *Ratios `json:"arithmetic-ratios"`
DidStrategyBeatTheMarket bool `json:"did-strategy-beat-the-market"`
DidStrategyMakeProfit bool `json:"did-strategy-make-profit"`
HoldingValueDifference decimal.Decimal `json:"holding-value-difference"`
}

View File

@@ -152,7 +152,8 @@ func main() {
fmt.Printf("Could not read strategy config. Error: %v.\n", err)
os.Exit(1)
}
err = backtest.ExecuteStrategy(cfg, &config.BacktesterConfig{
var bt *backtest.BackTest
bt, err = backtest.NewBacktesterFromConfigs(cfg, &config.BacktesterConfig{
Report: config.Report{
GenerateReport: generateReport,
TemplatePath: btCfg.Report.TemplatePath,
@@ -164,15 +165,36 @@ func main() {
fmt.Printf("Could not execute strategy. Error: %v.\n", err)
os.Exit(1)
}
if bt.MetaData.LiveTesting {
err = bt.ExecuteStrategy(false)
if err != nil {
fmt.Printf("Could execute strategy. Error: %v.\n", err)
os.Exit(1)
}
interrupt := signaler.WaitForInterrupt()
log.Infof(log.Global, "Captured %v, shutdown requested.\n", interrupt)
log.Infoln(log.Global, "Exiting.")
bt.Stop()
} else {
err = bt.ExecuteStrategy(true)
if err != nil {
fmt.Printf("Could execute strategy. Error: %v.\n", err)
os.Exit(1)
}
}
return
}
// grpc server mode
btCfg.Report.DarkMode = darkReport
btCfg.Report.GenerateReport = generateReport
runManager := backtest.SetupRunManager()
go func(c *config.BacktesterConfig) {
log.Info(log.GRPCSys, "Starting RPC server")
s := backtest.SetupRPCServer(c)
var s *backtest.GRPCServer
s, err = backtest.SetupRPCServer(c, runManager)
err = backtest.StartRPCServer(s)
if err != nil {
fmt.Printf("Could not start RPC server. Error: %v.\n", err)

View File

@@ -18,6 +18,9 @@ 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 {
if d.TemplatePath == "" || d.OutputPath == "" {
return nil
}
log.Info(common.Report, "Generating report")
err := d.enhanceCandles()
if err != nil {

View File

@@ -111,7 +111,7 @@ func (t *TimePeriodCalculator) calculatePeriods() {
return
}
iterateDateMate := t.start
for !iterateDateMate.Equal(t.end) && !iterateDateMate.After(t.end) {
for !iterateDateMate.Equal(t.end) && iterateDateMate.Before(t.end) {
tp := TimePeriod{
Time: iterateDateMate,
dataInRange: false,

View File

@@ -286,7 +286,7 @@ func (b *Binance) batchAggregateTrades(ctx context.Context, arg *AggregatedTrade
// cutting off trades for high activity pairs
increment := time.Second * 10
for start := arg.StartTime; len(resp) == 0; start = start.Add(increment) {
if !arg.EndTime.IsZero() && !start.Before(arg.EndTime) {
if !arg.EndTime.IsZero() && start.After(arg.EndTime) {
// All requests returned empty
return nil, nil
}

View File

@@ -288,7 +288,7 @@ func (bi *Binanceus) batchAggregateTrades(ctx context.Context, arg *AggregatedTr
increment := time.Second * 10
for len(resp) == 0 {
startTime = startTime.Add(increment)
if !endTime.IsZero() && !startTime.Before(endTime) {
if !endTime.IsZero() && startTime.After(endTime) {
// All requests returned empty
return nil, nil
}
@@ -915,10 +915,10 @@ func (bi *Binanceus) GetSubaccountTransferHistory(ctx context.Context,
hundredDayBefore := time.Now()
hundredDayBefore.Sub(time.UnixMilli(int64((time.Hour * 24 * 10) / time.Millisecond)))
if !(startTimeT.Before(hundredDayBefore)) || !startTimeT.After(time.Now()) {
if !(startTimeT.Before(hundredDayBefore)) || startTimeT.Before(time.Now()) {
params.Set("startTime", strconv.Itoa(int(startTime)))
}
if !(endTimeT.Before(hundredDayBefore)) || !endTimeT.After(time.Now()) {
if !(endTimeT.Before(hundredDayBefore)) || endTimeT.Before(time.Now()) {
params.Set("startTime", strconv.Itoa(int(endTime)))
}
return resp.Transfers, bi.SendAuthHTTPRequest(ctx,

View File

@@ -415,7 +415,7 @@ func CalculateCandleDateRanges(start, end time.Time, interval Interval, limit ui
End: CreateIntervalTime(end),
}
var intervalsInWholePeriod []IntervalData
for i := start; !i.After(end) && !i.Equal(end); i = i.Add(interval.Duration()) {
for i := start; i.Before(end) && !i.Equal(end); i = i.Add(interval.Duration()) {
intervalsInWholePeriod = append(intervalsInWholePeriod, IntervalData{
Start: CreateIntervalTime(i.Round(interval.Duration())),
End: CreateIntervalTime(i.Round(interval.Duration()).Add(interval.Duration())),

View File

@@ -7,8 +7,8 @@ import (
)
var (
errWriterAlreadyLoaded = errors.New("io.Writer already loaded")
errWriterNotFound = errors.New("io.Writer not found")
errWriterAlreadyLoaded = errors.New("io.Writer already loaded")
)
// Add appends a new writer to the multiwriter slice