mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-21 22:43:52 +00:00
add basic desktop app
This commit is contained in:
parent
0cb2a3c452
commit
621321f5db
10
Makefile
10
Makefile
|
@ -37,16 +37,22 @@ docker:
|
||||||
docker build --build-arg GO_MOD_CACHE=_mod --tag yoanlin/bbgo .
|
docker build --build-arg GO_MOD_CACHE=_mod --tag yoanlin/bbgo .
|
||||||
bash -c "[[ -n $(DOCKER_TAG) ]] && docker tag yoanlin/bbgo yoanlin/bbgo:$(DOCKER_TAG)"
|
bash -c "[[ -n $(DOCKER_TAG) ]] && docker tag yoanlin/bbgo yoanlin/bbgo:$(DOCKER_TAG)"
|
||||||
|
|
||||||
|
|
||||||
docker-push:
|
docker-push:
|
||||||
docker push yoanlin/bbgo
|
docker push yoanlin/bbgo
|
||||||
bash -c "[[ -n $(DOCKER_TAG) ]] && docker push yoanlin/bbgo:$(DOCKER_TAG)"
|
bash -c "[[ -n $(DOCKER_TAG) ]] && docker push yoanlin/bbgo:$(DOCKER_TAG)"
|
||||||
|
|
||||||
static:
|
static: pkged.go
|
||||||
(cd frontend && yarn export)
|
(cd frontend && yarn export)
|
||||||
pkger
|
pkger
|
||||||
git commit pkged.go -m "update pkged static files"
|
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:
|
tools:
|
||||||
GO111MODULES=off go get github.com/markbates/pkger/cmd/pkger
|
GO111MODULES=off go get github.com/markbates/pkger/cmd/pkger
|
||||||
|
|
||||||
.PHONY: dist migrations
|
.PHONY: dist migrations desktop
|
||||||
|
|
141
cmd/bbgo-desktop/main.go
Normal file
141
cmd/bbgo-desktop/main.go
Normal file
|
@ -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,<base64>` 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...")
|
||||||
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
APP="BBGO.app"
|
APP="BBGO.app"
|
||||||
|
APP_DIR=build/$APP
|
||||||
|
|
||||||
mkdir -p $APP/Contents/{MacOS,Resources}
|
mkdir -p $APP_DIR/Contents/{MacOS,Resources}
|
||||||
go build -o $APP/Contents/MacOS/bbgo
|
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
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>bbgo</string>
|
<string>bbgo-desktop</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>icon.icns</string>
|
<string>icon.icns</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
|
@ -20,5 +20,5 @@ cat > $APP/Contents/Info.plist << EOF
|
||||||
</plist>
|
</plist>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cp icons/icon.icns $APP/Contents/Resources/icon.icns
|
cp -v desktop/icons/icon.icns $APP_DIR/Contents/Resources/icon.icns
|
||||||
find $APP
|
find $APP_DIR
|
||||||
|
|
|
@ -48,7 +48,7 @@ export function queryStrategies(cb) {
|
||||||
export function querySessions(cb) {
|
export function querySessions(cb) {
|
||||||
return axios.get(baseURL + '/api/sessions', {})
|
return axios.get(baseURL + '/api/sessions', {})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
cb(response.data.sessions)
|
cb(response.data.sessions || [])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,10 +44,10 @@ export default function Home() {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
querySessions((sessions) => {
|
querySessions((sessions) => {
|
||||||
if (sessions.length == 0) {
|
if (sessions && sessions.length > 0) {
|
||||||
push("/setup");
|
|
||||||
} else {
|
|
||||||
setSessions(sessions)
|
setSessions(sessions)
|
||||||
|
} else {
|
||||||
|
push("/setup");
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -51,6 +51,7 @@ require (
|
||||||
github.com/ugorji/go v1.2.3 // indirect
|
github.com/ugorji/go v1.2.3 // indirect
|
||||||
github.com/valyala/fastjson v1.5.1
|
github.com/valyala/fastjson v1.5.1
|
||||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
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/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect
|
||||||
golang.org/x/text v0.3.5 // indirect
|
golang.org/x/text v0.3.5 // indirect
|
||||||
|
|
3
go.sum
3
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/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 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
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.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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
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-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-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-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-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 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
|
||||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
|
|
@ -95,14 +95,9 @@ func runSetup(baseCtx context.Context, userConfig *bbgo.Config, enableApiServer
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runConfig(basectx context.Context, userConfig *bbgo.Config, enableApiServer bool) error {
|
|
||||||
ctx, cancelTrading := context.WithCancel(basectx)
|
|
||||||
defer cancelTrading()
|
|
||||||
|
|
||||||
environ := bbgo.NewEnvironment()
|
func BootstrapEnvironment(ctx context.Context, environ *bbgo.Environment, userConfig *bbgo.Config) error {
|
||||||
|
if dsn, ok := os.LookupEnv("MYSQL_URL") ; ok {
|
||||||
if viper.IsSet("mysql-url") {
|
|
||||||
dsn := viper.GetString("mysql-url")
|
|
||||||
if err := environ.ConfigureDatabase(ctx, dsn); err != nil {
|
if err := environ.ConfigureDatabase(ctx, dsn); err != nil {
|
||||||
return err
|
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 {
|
if userConfig.RiskControls != nil {
|
||||||
trader.SetRiskControls(userConfig.RiskControls)
|
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()
|
trader.Subscribe()
|
||||||
|
|
||||||
if err := trader.Run(ctx); err != nil {
|
if err := trader.Run(ctx); err != nil {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"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"
|
pingURL := baseURL + "/api/ping"
|
||||||
timeout := time.NewTimer(time.Minute)
|
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) {
|
func pingAndOpenURL(ctx context.Context, baseURL string) {
|
||||||
setupURL := baseURL + "/setup"
|
setupURL := baseURL + "/setup"
|
||||||
go pingUntil(ctx, baseURL, func() {
|
go PingUntil(ctx, baseURL, func() {
|
||||||
if err := openURL(setupURL); err != nil {
|
if err := openURL(setupURL); err != nil {
|
||||||
logrus.WithError(err).Errorf("can not call open command to open the web page")
|
logrus.WithError(err).Errorf("can not call open command to open the web page")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user