2020-10-21 07:57:14 +00:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-10-24 07:38:13 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
2021-05-18 07:38:22 +00:00
|
|
|
"runtime/pprof"
|
2020-10-21 07:57:14 +00:00
|
|
|
"syscall"
|
2020-11-12 06:50:08 +00:00
|
|
|
"time"
|
2020-10-21 07:57:14 +00:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2020-10-24 07:43:55 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2020-10-21 07:57:14 +00:00
|
|
|
"github.com/spf13/cobra"
|
2020-10-24 07:38:13 +00:00
|
|
|
flag "github.com/spf13/pflag"
|
2020-10-21 07:57:14 +00:00
|
|
|
|
|
|
|
"github.com/c9s/bbgo/pkg/bbgo"
|
2020-10-21 07:58:58 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/cmd/cmdutil"
|
2022-04-08 11:21:57 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/grpc"
|
2021-02-02 18:26:41 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/server"
|
2020-10-21 07:57:14 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2020-10-24 07:38:13 +00:00
|
|
|
RunCmd.Flags().Bool("no-compile", false, "do not compile wrapper binary")
|
2022-01-27 00:30:31 +00:00
|
|
|
RunCmd.Flags().Bool("no-sync", false, "do not sync on startup")
|
2020-12-11 09:12:16 +00:00
|
|
|
RunCmd.Flags().String("totp-key-url", "", "time-based one-time password key URL, if defined, it will be used for restoring the otp key")
|
2020-12-11 06:40:04 +00:00
|
|
|
RunCmd.Flags().String("totp-issuer", "", "")
|
|
|
|
RunCmd.Flags().String("totp-account-name", "", "")
|
2021-05-03 09:24:10 +00:00
|
|
|
RunCmd.Flags().Bool("enable-webserver", false, "enable webserver")
|
|
|
|
RunCmd.Flags().Bool("enable-web-server", false, "legacy option, this is renamed to --enable-webserver")
|
|
|
|
RunCmd.Flags().String("webserver-bind", ":8080", "webserver binding")
|
2022-04-08 11:21:57 +00:00
|
|
|
|
|
|
|
RunCmd.Flags().Bool("enable-grpc", false, "enable grpc server")
|
2022-04-13 03:52:51 +00:00
|
|
|
RunCmd.Flags().String("grpc-bind", ":50051", "grpc server binding")
|
2022-04-08 11:21:57 +00:00
|
|
|
|
2021-02-01 09:20:01 +00:00
|
|
|
RunCmd.Flags().Bool("setup", false, "use setup mode")
|
2020-10-23 06:28:07 +00:00
|
|
|
RootCmd.AddCommand(RunCmd)
|
2020-10-21 07:57:14 +00:00
|
|
|
}
|
|
|
|
|
2020-12-31 09:07:12 +00:00
|
|
|
var RunCmd = &cobra.Command{
|
|
|
|
Use: "run",
|
|
|
|
Short: "run strategies from config file",
|
|
|
|
|
|
|
|
// SilenceUsage is an option to silence usage when an error occurs.
|
|
|
|
SilenceUsage: true,
|
2022-04-08 11:21:57 +00:00
|
|
|
RunE: run,
|
2020-12-31 09:07:12 +00:00
|
|
|
}
|
|
|
|
|
2021-02-03 10:22:16 +00:00
|
|
|
func runSetup(baseCtx context.Context, userConfig *bbgo.Config, enableApiServer bool) error {
|
|
|
|
ctx, cancelTrading := context.WithCancel(baseCtx)
|
2021-02-02 03:44:07 +00:00
|
|
|
defer cancelTrading()
|
|
|
|
|
|
|
|
environ := bbgo.NewEnvironment()
|
|
|
|
|
|
|
|
trader := bbgo.NewTrader(environ)
|
|
|
|
|
|
|
|
if enableApiServer {
|
|
|
|
go func() {
|
2021-02-03 10:09:33 +00:00
|
|
|
s := &server.Server{
|
2021-02-05 05:01:07 +00:00
|
|
|
Config: userConfig,
|
|
|
|
Environ: environ,
|
|
|
|
Trader: trader,
|
2021-02-04 05:48:21 +00:00
|
|
|
OpenInBrowser: true,
|
2021-02-04 05:29:43 +00:00
|
|
|
Setup: &server.Setup{
|
|
|
|
Context: ctx,
|
|
|
|
Cancel: cancelTrading,
|
|
|
|
Token: "",
|
|
|
|
},
|
2021-02-03 10:09:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.Run(ctx); err != nil {
|
2021-02-02 03:44:07 +00:00
|
|
|
log.WithError(err).Errorf("server error")
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
cmdutil.WaitForSignal(ctx, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
cancelTrading()
|
|
|
|
|
2021-05-22 09:44:20 +00:00
|
|
|
// graceful period = 15 second
|
|
|
|
shutdownCtx, cancelShutdown := context.WithDeadline(ctx, time.Now().Add(15*time.Second))
|
2021-02-02 03:44:07 +00:00
|
|
|
|
|
|
|
log.Infof("shutting down...")
|
|
|
|
trader.Graceful.Shutdown(shutdownCtx)
|
|
|
|
cancelShutdown()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-05-07 16:14:25 +00:00
|
|
|
func BootstrapBacktestEnvironment(ctx context.Context, environ *bbgo.Environment, userConfig *bbgo.Config) error {
|
|
|
|
if err := environ.ConfigureDatabase(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
environ.Notifiability = bbgo.Notifiability{
|
|
|
|
SymbolChannelRouter: bbgo.NewPatternChannelRouter(nil),
|
|
|
|
SessionChannelRouter: bbgo.NewPatternChannelRouter(nil),
|
|
|
|
ObjectChannelRouter: bbgo.NewObjectChannelRouter(),
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-02-20 16:45:56 +00:00
|
|
|
func BootstrapEnvironment(ctx context.Context, environ *bbgo.Environment, userConfig *bbgo.Config) error {
|
2021-02-21 08:52:47 +00:00
|
|
|
if err := environ.ConfigureDatabase(ctx); err != nil {
|
2021-02-20 16:45:56 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-02-21 08:52:47 +00:00
|
|
|
if err := environ.ConfigureExchangeSessions(userConfig); err != nil {
|
2021-02-21 09:48:03 +00:00
|
|
|
return errors.Wrap(err, "exchange session configure error")
|
2021-02-20 16:45:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if userConfig.Persistence != nil {
|
|
|
|
if err := environ.ConfigurePersistence(userConfig.Persistence); err != nil {
|
2021-02-21 09:48:03 +00:00
|
|
|
return errors.Wrap(err, "persistence configure error")
|
2021-02-20 16:45:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-16 10:39:18 +00:00
|
|
|
if err := environ.ConfigureNotificationSystem(userConfig); err != nil {
|
|
|
|
return errors.Wrap(err, "notification configure error")
|
2020-10-27 01:38:29 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 10:22:47 +00:00
|
|
|
return nil
|
|
|
|
}
|
2020-10-30 21:21:17 +00:00
|
|
|
|
2022-01-27 00:30:31 +00:00
|
|
|
func runConfig(basectx context.Context, cmd *cobra.Command, userConfig *bbgo.Config) error {
|
|
|
|
noSync, err := cmd.Flags().GetBool("no-sync")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
enableWebServer, err := cmd.Flags().GetBool("enable-webserver")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
webServerBind, err := cmd.Flags().GetString("webserver-bind")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
enableWebServerLegacy, err := cmd.Flags().GetBool("enable-web-server")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if enableWebServerLegacy {
|
|
|
|
log.Warn("command option --enable-web-server is renamed to --enable-webserver")
|
|
|
|
enableWebServer = true
|
|
|
|
}
|
|
|
|
|
2022-04-08 11:21:57 +00:00
|
|
|
enableGrpc, err := cmd.Flags().GetBool("enable-grpc")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
grpcBind, err := cmd.Flags().GetString("grpc-bind")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_ = grpcBind
|
|
|
|
_ = enableGrpc
|
|
|
|
|
2021-02-04 10:22:47 +00:00
|
|
|
ctx, cancelTrading := context.WithCancel(basectx)
|
|
|
|
defer cancelTrading()
|
|
|
|
|
|
|
|
environ := bbgo.NewEnvironment()
|
2021-02-05 05:01:07 +00:00
|
|
|
if err := BootstrapEnvironment(ctx, environ, userConfig); err != nil {
|
2021-02-04 10:22:47 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-05-18 07:38:22 +00:00
|
|
|
if err := environ.Init(ctx); err != nil {
|
2021-05-07 16:44:43 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-01-27 00:30:31 +00:00
|
|
|
if !noSync {
|
|
|
|
if err := environ.Sync(ctx, userConfig); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-01-27 10:19:25 +00:00
|
|
|
|
2022-03-11 08:58:45 +00:00
|
|
|
if userConfig.Sync != nil {
|
|
|
|
environ.BindSync(userConfig.Sync)
|
|
|
|
}
|
2021-02-22 08:54:08 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 10:22:47 +00:00
|
|
|
trader := bbgo.NewTrader(environ)
|
2021-02-20 04:23:31 +00:00
|
|
|
if err := trader.Configure(userConfig); err != nil {
|
2021-02-04 10:22:47 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-05-05 06:28:42 +00:00
|
|
|
if err := trader.LoadState(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-12 06:50:08 +00:00
|
|
|
if err := trader.Run(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-05-03 09:24:10 +00:00
|
|
|
if enableWebServer {
|
2021-01-24 11:07:56 +00:00
|
|
|
go func() {
|
2021-02-03 10:09:33 +00:00
|
|
|
s := &server.Server{
|
2021-02-04 05:29:43 +00:00
|
|
|
Config: userConfig,
|
2021-02-03 10:09:33 +00:00
|
|
|
Environ: environ,
|
2021-02-04 05:29:43 +00:00
|
|
|
Trader: trader,
|
2021-02-03 10:09:33 +00:00
|
|
|
}
|
|
|
|
|
2021-05-03 09:24:10 +00:00
|
|
|
if err := s.Run(ctx, webServerBind); err != nil {
|
2022-04-08 11:21:57 +00:00
|
|
|
log.WithError(err).Errorf("http server bind error")
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
if enableGrpc {
|
|
|
|
go func() {
|
|
|
|
s := &grpc.Server{
|
|
|
|
Config: userConfig,
|
|
|
|
Environ: environ,
|
|
|
|
Trader: trader,
|
|
|
|
}
|
|
|
|
if err := s.ListenAndServe(grpcBind); err != nil {
|
|
|
|
log.WithError(err).Errorf("grpc server bind error")
|
2021-01-24 11:07:56 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
2021-01-24 10:42:36 +00:00
|
|
|
|
2020-11-12 06:50:08 +00:00
|
|
|
cmdutil.WaitForSignal(ctx, syscall.SIGINT, syscall.SIGTERM)
|
2022-01-14 16:49:27 +00:00
|
|
|
cancelTrading()
|
2020-11-12 06:50:08 +00:00
|
|
|
|
2022-01-14 16:49:27 +00:00
|
|
|
log.Infof("shutting down...")
|
2020-11-17 00:19:22 +00:00
|
|
|
shutdownCtx, cancelShutdown := context.WithDeadline(ctx, time.Now().Add(30*time.Second))
|
2020-11-12 06:50:08 +00:00
|
|
|
trader.Graceful.Shutdown(shutdownCtx)
|
2020-11-17 00:19:22 +00:00
|
|
|
cancelShutdown()
|
2022-01-14 16:49:27 +00:00
|
|
|
|
2022-05-05 06:28:42 +00:00
|
|
|
if err := trader.SaveState(); err != nil {
|
|
|
|
log.WithError(err).Errorf("can not save strategy states")
|
|
|
|
}
|
|
|
|
|
2022-01-14 16:49:27 +00:00
|
|
|
for _, session := range environ.Sessions() {
|
|
|
|
if err := session.MarketDataStream.Close(); err != nil {
|
|
|
|
log.WithError(err).Errorf("[%s] market data stream close error", session.Name)
|
|
|
|
}
|
|
|
|
if err := session.UserDataStream.Close(); err != nil {
|
|
|
|
log.WithError(err).Errorf("[%s] user data stream close error", session.Name)
|
|
|
|
}
|
|
|
|
}
|
2021-05-09 18:17:19 +00:00
|
|
|
|
2020-11-12 06:50:08 +00:00
|
|
|
return nil
|
2020-10-23 06:49:54 +00:00
|
|
|
}
|
|
|
|
|
2020-12-31 09:07:12 +00:00
|
|
|
func run(cmd *cobra.Command, args []string) error {
|
2021-02-02 03:44:07 +00:00
|
|
|
setup, err := cmd.Flags().GetBool("setup")
|
2020-12-31 09:07:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-10-21 07:57:14 +00:00
|
|
|
|
2021-02-02 03:44:07 +00:00
|
|
|
noCompile, err := cmd.Flags().GetBool("no-compile")
|
2020-12-31 09:07:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-10-24 07:38:13 +00:00
|
|
|
|
2021-02-02 03:44:07 +00:00
|
|
|
configFile, err := cmd.Flags().GetString("config")
|
2021-01-24 11:07:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-05-18 07:38:22 +00:00
|
|
|
cpuProfile, err := cmd.Flags().GetString("cpu-profile")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-02-05 05:04:52 +00:00
|
|
|
if !setup {
|
2021-02-05 05:01:07 +00:00
|
|
|
// if it's not setup, then the config file option is required.
|
2021-02-02 03:44:07 +00:00
|
|
|
if len(configFile) == 0 {
|
|
|
|
return errors.New("--config option is required")
|
|
|
|
}
|
|
|
|
|
2021-02-05 05:01:07 +00:00
|
|
|
if _, err := os.Stat(configFile); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-02-02 03:44:07 +00:00
|
|
|
}
|
2020-10-21 07:57:14 +00:00
|
|
|
|
2021-02-02 03:44:07 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
// for wrapper binary, we can just run the strategies
|
|
|
|
if bbgo.IsWrapperBinary || (userConfig.Build != nil && len(userConfig.Build.Imports) == 0) || noCompile {
|
2021-01-21 04:27:21 +00:00
|
|
|
if bbgo.IsWrapperBinary {
|
|
|
|
log.Infof("running wrapper binary...")
|
|
|
|
}
|
|
|
|
|
2021-02-02 03:44:07 +00:00
|
|
|
if setup {
|
2021-02-05 05:04:52 +00:00
|
|
|
return runSetup(ctx, userConfig, true)
|
2021-02-02 03:44:07 +00:00
|
|
|
}
|
|
|
|
|
2022-03-03 08:34:32 +00:00
|
|
|
// default setting is false, here load as true
|
2021-02-02 03:44:07 +00:00
|
|
|
userConfig, err = bbgo.Load(configFile, true)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-05-18 07:38:22 +00:00
|
|
|
if cpuProfile != "" {
|
|
|
|
f, err := os.Create(cpuProfile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("could not create CPU profile: ", err)
|
|
|
|
}
|
|
|
|
defer f.Close() // error handling omitted for example
|
|
|
|
|
|
|
|
if err := pprof.StartCPUProfile(f); err != nil {
|
|
|
|
log.Fatal("could not start CPU profile: ", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer pprof.StopCPUProfile()
|
|
|
|
}
|
|
|
|
|
2022-01-27 00:30:31 +00:00
|
|
|
return runConfig(ctx, cmd, userConfig)
|
2021-02-01 09:31:54 +00:00
|
|
|
}
|
2020-12-09 08:13:20 +00:00
|
|
|
|
2022-01-27 00:30:31 +00:00
|
|
|
return runWrapperBinary(ctx, cmd, userConfig, args)
|
2021-02-01 09:31:54 +00:00
|
|
|
}
|
2020-10-24 07:38:13 +00:00
|
|
|
|
2022-01-27 00:30:31 +00:00
|
|
|
func runWrapperBinary(ctx context.Context, cmd *cobra.Command, userConfig *bbgo.Config, args []string) error {
|
2021-02-01 09:31:54 +00:00
|
|
|
var runArgs = []string{"run"}
|
|
|
|
cmd.Flags().Visit(func(flag *flag.Flag) {
|
|
|
|
runArgs = append(runArgs, "--"+flag.Name, flag.Value.String())
|
|
|
|
})
|
|
|
|
runArgs = append(runArgs, args...)
|
|
|
|
|
|
|
|
runCmd, err := buildAndRun(ctx, userConfig, runArgs...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if sig := cmdutil.WaitForSignal(ctx, syscall.SIGTERM, syscall.SIGINT); sig != nil {
|
|
|
|
log.Infof("sending signal to the child process...")
|
|
|
|
if err := runCmd.Process.Signal(sig); err != nil {
|
2020-10-24 08:28:42 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-10-24 07:38:13 +00:00
|
|
|
|
2021-02-01 09:31:54 +00:00
|
|
|
if err := runCmd.Wait(); err != nil {
|
|
|
|
return err
|
2020-11-23 08:36:03 +00:00
|
|
|
}
|
2020-12-31 09:07:12 +00:00
|
|
|
}
|
2020-11-23 08:36:03 +00:00
|
|
|
|
2020-12-31 09:07:12 +00:00
|
|
|
return nil
|
2020-10-24 08:28:42 +00:00
|
|
|
}
|
2020-10-24 07:38:13 +00:00
|
|
|
|
2021-01-21 04:06:03 +00:00
|
|
|
// buildAndRun builds the package natively and run the binary with the given args
|
|
|
|
func buildAndRun(ctx context.Context, userConfig *bbgo.Config, args ...string) (*exec.Cmd, error) {
|
|
|
|
packageDir, err := ioutil.TempDir("build", "bbgow")
|
2020-10-24 08:28:42 +00:00
|
|
|
if err != nil {
|
2021-01-21 04:06:03 +00:00
|
|
|
return nil, err
|
2020-10-26 05:27:07 +00:00
|
|
|
}
|
|
|
|
|
2021-01-21 04:06:03 +00:00
|
|
|
defer os.RemoveAll(packageDir)
|
2020-10-26 05:27:07 +00:00
|
|
|
|
2021-01-21 04:06:03 +00:00
|
|
|
targetConfig := bbgo.GetNativeBuildTargetConfig()
|
|
|
|
binary, err := bbgo.Build(ctx, userConfig, targetConfig)
|
2020-10-26 05:27:07 +00:00
|
|
|
if err != nil {
|
2020-11-23 08:36:03 +00:00
|
|
|
return nil, err
|
2020-10-26 05:27:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
2020-11-23 08:36:03 +00:00
|
|
|
return nil, err
|
2020-10-24 08:28:42 +00:00
|
|
|
}
|
|
|
|
|
2020-10-26 03:41:21 +00:00
|
|
|
executePath := filepath.Join(cwd, binary)
|
2020-11-23 08:36:03 +00:00
|
|
|
runCmd := exec.Command(executePath, args...)
|
2020-10-24 08:28:42 +00:00
|
|
|
runCmd.Stdout = os.Stdout
|
|
|
|
runCmd.Stderr = os.Stderr
|
2020-11-23 08:36:03 +00:00
|
|
|
return runCmd, runCmd.Start()
|
2020-10-21 07:57:14 +00:00
|
|
|
}
|