diff --git a/Makefile b/Makefile index e619807aa..712494585 100644 --- a/Makefile +++ b/Makefile @@ -37,16 +37,22 @@ docker: docker build --build-arg GO_MOD_CACHE=_mod --tag yoanlin/bbgo . bash -c "[[ -n $(DOCKER_TAG) ]] && docker tag yoanlin/bbgo yoanlin/bbgo:$(DOCKER_TAG)" + docker-push: docker push yoanlin/bbgo bash -c "[[ -n $(DOCKER_TAG) ]] && docker push yoanlin/bbgo:$(DOCKER_TAG)" -static: +static: pkged.go (cd frontend && yarn export) pkger git commit pkged.go -m "update pkged static files" +desktop: build/BBGO.app/Contents/MacOS/bbgo-desktop static + mkdir -p $(dir $<) + go build -o $< ./cmd/bbgo-desktop + # bash desktop/build-darwin.sh + tools: GO111MODULES=off go get github.com/markbates/pkger/cmd/pkger -.PHONY: dist migrations +.PHONY: dist migrations desktop diff --git a/cmd/bbgo-desktop/main.go b/cmd/bbgo-desktop/main.go new file mode 100644 index 000000000..2dcee0258 --- /dev/null +++ b/cmd/bbgo-desktop/main.go @@ -0,0 +1,141 @@ +package main + +import ( + "context" + "net" + "os" + "os/signal" + "runtime" + + "github.com/zserge/lorca" + + log "github.com/sirupsen/logrus" + + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/cmd" + "github.com/c9s/bbgo/pkg/server" +) + +func main() { + var args []string + if runtime.GOOS == "linux" { + args = append(args, "--class=bbgo") + } + args = append(args, "--class=bbgo") + + // here allocate a chrome window with a blank page. + ui, err := lorca.New("", "", 800, 640, args...) + if err != nil { + log.WithError(err).Error("failed to initialize the window") + return + } + + defer ui.Close() + + // A simple way to know when UI is ready (uses body.onload event in JS) + ui.Bind("start", func() { + log.Println("lorca is ready") + }) + + // Create and bind Go object to the UI + // ui.Bind("counterAdd", c.Add) + + // Load HTML. + // You may also use `data:text/html,` approach to load initial HTML, + // e.g: ui.Load("data:text/html," + url.PathEscape(html)) + // TODO: load the loading page html + + // find a free port for binding the server + ln, err := net.Listen("tcp", "127.0.0.1:9999") + if err != nil { + log.WithError(err).Error("can not bind listener") + return + } + + defer ln.Close() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + configFile := "bbgo.yaml" + var setup *server.Setup + var userConfig *bbgo.Config + + _, err = os.Stat(configFile) + if os.IsNotExist(err) { + setup = &server.Setup{ + Context: ctx, + Cancel: cancel, + Token: "", + } + userConfig = &bbgo.Config{ + Notifications: nil, + Persistence: nil, + Sessions: nil, + ExchangeStrategies: nil, + } + } else { + userConfig, err = bbgo.Load(configFile, true) + if err != nil { + log.WithError(err).Error("can not load config file") + return + } + } + + environ := bbgo.NewEnvironment() + trader := bbgo.NewTrader(environ) + + // we could initialize the environment from the settings + if setup == nil { + if err := cmd.BootstrapEnvironment(ctx, environ, userConfig) ; err != nil { + log.WithError(err).Error("failed to bootstrap environment") + return + } + + if err := cmd.ConfigureTrader(trader, userConfig) ; err != nil { + log.WithError(err).Error("failed to configure trader") + return + } + + // for setup mode, we don't start the trader + trader.Subscribe() + if err := trader.Run(ctx); err != nil { + log.WithError(err).Error("failed to start trader") + return + } + } + + go func() { + srv := &server.Server{ + Config: userConfig, + Environ: environ, + Trader: trader, + OpenInBrowser: false, + Setup: setup, + } + + if err := srv.RunWithListener(ctx, ln); err != nil { + log.WithError(err).Errorf("server error") + } + }() + + baseURL := "http://" + ln.Addr().String() + + go server.PingUntil(ctx, baseURL, func() { + if err := ui.Load(baseURL) ; err != nil { + log.WithError(err).Error("failed to load page") + } + }) + + + // Wait until the interrupt signal arrives or browser window is closed + sigc := make(chan os.Signal) + signal.Notify(sigc, os.Interrupt) + + select { + case <-sigc: + case <-ui.Done(): + } + + log.Println("exiting...") +} diff --git a/desktop/build-darwin.sh b/desktop/build-darwin.sh index 392456520..d57f67ac9 100644 --- a/desktop/build-darwin.sh +++ b/desktop/build-darwin.sh @@ -1,17 +1,17 @@ #!/bin/sh - APP="BBGO.app" +APP_DIR=build/$APP -mkdir -p $APP/Contents/{MacOS,Resources} -go build -o $APP/Contents/MacOS/bbgo +mkdir -p $APP_DIR/Contents/{MacOS,Resources} +go build -o $APP_DIR/Contents/MacOS/bbgo-desktop ./cmd/bbgo-desktop -cat > $APP/Contents/Info.plist << EOF +cat > $APP_DIR/Contents/Info.plist << EOF CFBundleExecutable - bbgo + bbgo-desktop CFBundleIconFile icon.icns CFBundleIdentifier @@ -20,5 +20,5 @@ cat > $APP/Contents/Info.plist << EOF EOF -cp icons/icon.icns $APP/Contents/Resources/icon.icns -find $APP +cp -v desktop/icons/icon.icns $APP_DIR/Contents/Resources/icon.icns +find $APP_DIR diff --git a/frontend/api/bbgo.js b/frontend/api/bbgo.js index 9818291eb..44a9739b3 100644 --- a/frontend/api/bbgo.js +++ b/frontend/api/bbgo.js @@ -48,7 +48,7 @@ export function queryStrategies(cb) { export function querySessions(cb) { return axios.get(baseURL + '/api/sessions', {}) .then(response => { - cb(response.data.sessions) + cb(response.data.sessions || []) }); } diff --git a/frontend/pages/index.tsx b/frontend/pages/index.tsx index f2099cfc8..1b9e186f9 100644 --- a/frontend/pages/index.tsx +++ b/frontend/pages/index.tsx @@ -44,10 +44,10 @@ export default function Home() { React.useEffect(() => { querySessions((sessions) => { - if (sessions.length == 0) { - push("/setup"); - } else { + if (sessions && sessions.length > 0) { setSessions(sessions) + } else { + push("/setup"); } }).catch((err) => { console.error(err); diff --git a/go.mod b/go.mod index 11d7955a7..4a4033a8a 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( github.com/ugorji/go v1.2.3 // indirect github.com/valyala/fastjson v1.5.1 github.com/x-cray/logrus-prefixed-formatter v0.5.2 + github.com/zserge/lorca v0.1.9 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect golang.org/x/text v0.3.5 // indirect diff --git a/go.sum b/go.sum index 36670f7f2..982d91dba 100644 --- a/go.sum +++ b/go.sum @@ -370,6 +370,8 @@ github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7V github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +github.com/zserge/lorca v0.1.9 h1:vbDdkqdp2/rmeg8GlyCewY2X8Z+b0s7BqWyIQL/gakc= +github.com/zserge/lorca v0.1.9/go.mod h1:bVmnIbIRlOcoV285KIRSe4bUABKi7R7384Ycuum6e4A= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -426,6 +428,7 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go index 89cbe8649..b0233a3dc 100644 --- a/pkg/cmd/run.go +++ b/pkg/cmd/run.go @@ -95,14 +95,9 @@ func runSetup(baseCtx context.Context, userConfig *bbgo.Config, enableApiServer return nil } -func runConfig(basectx context.Context, userConfig *bbgo.Config, enableApiServer bool) error { - ctx, cancelTrading := context.WithCancel(basectx) - defer cancelTrading() - environ := bbgo.NewEnvironment() - - if viper.IsSet("mysql-url") { - dsn := viper.GetString("mysql-url") +func BootstrapEnvironment(ctx context.Context, environ *bbgo.Environment, userConfig *bbgo.Config) error { + if dsn, ok := os.LookupEnv("MYSQL_URL") ; ok { if err := environ.ConfigureDatabase(ctx, dsn); err != nil { return err } @@ -230,8 +225,10 @@ func runConfig(basectx context.Context, userConfig *bbgo.Config, enableApiServer } } - trader := bbgo.NewTrader(environ) + return nil +} +func ConfigureTrader(trader *bbgo.Trader, userConfig *bbgo.Config) error { if userConfig.RiskControls != nil { trader.SetRiskControls(userConfig.RiskControls) } @@ -262,6 +259,23 @@ func runConfig(basectx context.Context, userConfig *bbgo.Config, enableApiServer } } + return nil +} + +func runConfig(basectx context.Context, userConfig *bbgo.Config, enableApiServer bool) error { + ctx, cancelTrading := context.WithCancel(basectx) + defer cancelTrading() + + environ := bbgo.NewEnvironment() + if err := BootstrapEnvironment(ctx, environ, userConfig) ; err != nil { + return err + } + + trader := bbgo.NewTrader(environ) + if err := ConfigureTrader(trader, userConfig) ; err != nil { + return err + } + trader.Subscribe() if err := trader.Run(ctx); err != nil { diff --git a/pkg/server/ping.go b/pkg/server/ping.go index 5aa30d248..44490ab85 100644 --- a/pkg/server/ping.go +++ b/pkg/server/ping.go @@ -7,7 +7,7 @@ import ( "github.com/sirupsen/logrus" ) -func pingUntil(ctx context.Context, baseURL string, callback func()) { +func PingUntil(ctx context.Context, baseURL string, callback func()) { pingURL := baseURL + "/api/ping" timeout := time.NewTimer(time.Minute) @@ -37,7 +37,7 @@ func pingUntil(ctx context.Context, baseURL string, callback func()) { func pingAndOpenURL(ctx context.Context, baseURL string) { setupURL := baseURL + "/setup" - go pingUntil(ctx, baseURL, func() { + go PingUntil(ctx, baseURL, func() { if err := openURL(setupURL); err != nil { logrus.WithError(err).Errorf("can not call open command to open the web page") }