diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 59879902b..517b2f8dc 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -17,7 +17,7 @@ jobs: env: MYSQL_DATABASE: bbgo MYSQL_USER: "root" - MYSQL_PASSWORD: "root" + MYSQL_PASSWORD: "root" # pragma: allowlist secret steps: @@ -48,6 +48,15 @@ jobs: with: go-version: 1.18 + - name: Run pre-commit + run: | + pip install pre-commit + go get -v -u github.com/go-critic/go-critic/cmd/gocritic + pre-commit install-hooks + pre-commit run markdownlint --files=README.md --verbose + pre-commit run go-fmt --all-files --verbose + pre-commit run go-critic --all-files --verbose + - name: Install Migration Tool run: go install github.com/c9s/rockhopper/cmd/rockhopper@v1.2.1 diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 000000000..cd599ae32 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,5 @@ +default: true +extends: null +MD033: false +MD010: false +MD013: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..2a4c2857e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +--- +repos: + # Secret Detection + - repo: https://github.com/Yelp/detect-secrets + rev: v1.2.0 + hooks: + - id: detect-secrets + args: ['--exclude-secrets', '3899a918953e01bfe218116cdfeccbed579e26275c4a89abcbc70d2cb9e9bbb8'] + exclude: pacakge.lock.json + # Markdown + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.31.1 + hooks: + - id: markdownlint + # Go + - repo: https://github.com/dnephin/pre-commit-golang + rev: v0.5.0 + hooks: + - id: go-fmt + args: ['-l'] + - id: go-critic diff --git a/README.md b/README.md index 6c425bd52..2e896b34f 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ A trading bot framework written in Go. The name bbgo comes from the BB8 bot in t ## Current Status [![Go](https://github.com/c9s/bbgo/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/c9s/bbgo/actions/workflows/go.yml) +[![GoDoc](https://godoc.org/github.com/c9s/bbgo?status.svg)](https://pkg.go.dev/github.com/c9s/bbgo) +[![Go Report Card](https://goreportcard.com/badge/github.com/c9s/bbgo)](https://goreportcard.com/report/github.com/c9s/bbgo) [![DockerHub](https://img.shields.io/docker/pulls/yoanlin/bbgo.svg)](https://hub.docker.com/r/yoanlin/bbgo) [![Coverage Status](http://codecov.io/github/c9s/bbgo/coverage.svg?branch=main)](http://codecov.io/github/c9s/bbgo?branch=main) open collective badge @@ -42,7 +44,38 @@ You can use BBGO's underlying common exchange API, currently it supports 4+ majo - Built-in parameter optimization tool. - Built-in Grid strategy and many other built-in strategies. - Multi-exchange session support: you can connect to more than 2 exchanges with different accounts or subaccounts. -- Standard indicators, e.g., SMA, EMA, BOLL, VMA, MACD... +- Indicators with interface similar to `pandas.Series`([series](https://github.com/c9s/bbgo/blob/main/doc/development/series.md))([usage](https://github.com/c9s/bbgo/blob/main/doc/development/indicator.md)): + - [Accumulation/Distribution Indicator](./pkg/indicator/ad.go) + - [Arnaud Legoux Moving Average](./pkg/indicator/alma.go) + - [Average True Range](./pkg/indicator/atr.go) + - [Bollinger Bands](./pkg/indicator/boll.go) + - [Commodity Channel Index](./pkg/indicator/cci.go) + - [Cumulative Moving Average](./pkg/indicator/cma.go) + - [Double Exponential Moving Average](./pkg/indicator/dema.go) + - [Directional Movement Index](./pkg/indicator/dmi.go) + - [Brownian Motion's Drift Factor](./pkg/indicator/drift.go) + - [Ease of Movement](./pkg/indicator/emv.go) + - [Exponentially Weighted Moving Average](./pkg/indicator/ewma.go) + - [Hull Moving Average](./pkg/indicator/hull.go) + - [Trend Line (Tool)](./pkg/indicator/line.go) + - [Moving Average Convergence Divergence Indicator](./pkg/indicator/macd.go) + - [On-Balance Volume](./pkg/indicator/obv.go) + - [Pivot](./pkg/indicator/pivot.go) + - [Running Moving Average](./pkg/indicator/rma.go) + - [Relative Strength Index](./pkg/indicator/rsi.go) + - [Simple Moving Average](./pkg/indicator/sma.go) + - [Ehler's Super Smoother Filter](./pkg/indicator/ssf.go) + - [Stochastic Oscillator](./pkg/indicator/stoch.go) + - [SuperTrend](./pkg/indicator/supertrend.go) + - [Triple Exponential Moving Average](./pkg/indicator/tema.go) + - [Tillson T3 Moving Average](./pkg/indicator/till.go) + - [Triangular Moving Average](./pkg/indicator/tma.go) + - [Variable Index Dynamic Average](./pkg/indicator/vidya.go) + - [Volatility Indicator](./pkg/indicator/volatility.go) + - [Volume Weighted Average Price](./pkg/indicator/vwap.go) + - [Zero Lag Exponential Moving Average](./pkg/indicator/zlema.go) + - And more... +- HeikinAshi OHLC / Normal OHLC (check [this config](https://github.com/c9s/bbgo/blob/main/config/skeleton.yaml#L5)) - React-powered Web Dashboard. - Docker image ready. - Kubernetes support. @@ -51,7 +84,7 @@ You can use BBGO's underlying common exchange API, currently it supports 4+ majo ## Screenshots -![bbgo dashboard](assets/screenshots/dashboard.jpeg) +![bbgo dashboard](assets/screenshots/dashboard.jpeg) ![bbgo backtest report](assets/screenshots/backtest-report.jpg) @@ -63,8 +96,8 @@ You can use BBGO's underlying common exchange API, currently it supports 4+ majo - Kucoin Spot Exchange - MAX Spot Exchange (located in Taiwan) - ## Documentation and General Topics + - Check the [documentation index](doc/README.md) ## BBGO Tokenomics @@ -105,6 +138,7 @@ bash <(curl -s https://raw.githubusercontent.com/c9s/bbgo/main/scripts/setup-bol ``` If you already have configuration somewhere, a download-only script might be suitable for you: + ```sh bash <(curl -s https://raw.githubusercontent.com/c9s/bbgo/main/scripts/download.sh) ``` @@ -114,7 +148,7 @@ Or refer to the [Release Page](https://github.com/c9s/bbgo/releases) and downloa Since v2, we've added new float point implementation from dnum to support decimals with higher precision. To download & setup, please refer to [Dnum Installation](doc/topics/dnum-binary.md) -### One-click Linode StackScript: +### One-click Linode StackScript - BBGO Grid Trading on Binance - BBGO USDT/TWD Grid Trading on MAX @@ -165,14 +199,12 @@ Prepare your dotenv file `.env.local` and BBGO yaml config file `bbgo.yaml`. To check the available environment variables, please see [Environment Variables](./doc/configuration/envvars.md) - The minimal bbgo.yaml could be generated by: ```sh curl -o bbgo.yaml https://raw.githubusercontent.com/c9s/bbgo/main/config/minimal.yaml ``` - To run strategy: ```sh @@ -185,14 +217,12 @@ To start bbgo with the frontend dashboard: bbgo run --enable-webserver ``` - If you want to switch to other dotenv file, you can add an `--dotenv` option or `--config`: ```sh bbgo sync --dotenv .env.dev --config config/grid.yaml --session binance ``` - To query transfer history: ```sh @@ -207,13 +237,13 @@ bbgo pnl --exchange binance --asset BTC --since "2019-01-01" ``` ---> - ## Advanced Configuration ### Testnet (Paper Trading) Currently only supports binance testnet. To run bbgo in testnet, apply new API keys from [Binance Test Network](https://testnet.binance.vision), and set the following env before you start bbgo: + ```bash export PAPER_TRADE=1 export DISABLE_MARKET_CACHE=1 # the symbols supported in testnet is far less than the mainnet diff --git a/pkg/bbgo/environment.go b/pkg/bbgo/environment.go index bace19b25..b1f11da2f 100644 --- a/pkg/bbgo/environment.go +++ b/pkg/bbgo/environment.go @@ -928,10 +928,11 @@ func (environ *Environment) setupInteraction(persistence service.PersistenceServ } interact.AddCustomInteraction(&interact.AuthInteract{ - Strict: authStrict, - Mode: authMode, - Token: authToken, // can be empty string here - OneTimePasswordKey: key, // can be nil here + Strict: authStrict, + Mode: authMode, + Token: authToken, // can be empty string here + // pragma: allowlist nextline secret + OneTimePasswordKey: key, // can be nil here }) return nil } diff --git a/pkg/bbgo/strategycontroller_callbacks.go b/pkg/bbgo/strategycontroller_callbacks.go index caa3e68fb..dd432fcb5 100644 --- a/pkg/bbgo/strategycontroller_callbacks.go +++ b/pkg/bbgo/strategycontroller_callbacks.go @@ -1,4 +1,4 @@ -// Code generated by "callbackgen -type StrategyController strategy_controller.go"; DO NOT EDIT. +// Code generated by "callbackgen -type StrategyController -interface"; DO NOT EDIT. package bbgo @@ -33,3 +33,11 @@ func (s *StrategyController) EmitEmergencyStop() { cb() } } + +type StrategyControllerEventHub interface { + OnSuspend(cb func()) + + OnResume(cb func()) + + OnEmergencyStop(cb func()) +} diff --git a/pkg/datasource/coinmarketcap/v1/client.go b/pkg/datasource/coinmarketcap/v1/client.go index d1e68da60..be4f3d181 100644 --- a/pkg/datasource/coinmarketcap/v1/client.go +++ b/pkg/datasource/coinmarketcap/v1/client.go @@ -35,6 +35,7 @@ func New() *RestClient { } func (c *RestClient) Auth(apiKey string) { + // pragma: allowlist nextline secret c.apiKey = apiKey } diff --git a/pkg/datasource/glassnode/glassnodeapi/client.go b/pkg/datasource/glassnode/glassnodeapi/client.go index c1631a015..fcc71d0b1 100644 --- a/pkg/datasource/glassnode/glassnodeapi/client.go +++ b/pkg/datasource/glassnode/glassnodeapi/client.go @@ -35,6 +35,7 @@ func NewRestClient() *RestClient { } func (c *RestClient) Auth(apiKey string) { + // pragma: allowlist nextline secret c.apiKey = apiKey } diff --git a/pkg/exchange/binance/binanceapi/client.go b/pkg/exchange/binance/binanceapi/client.go index 45c39e6f1..0fa05db42 100644 --- a/pkg/exchange/binance/binanceapi/client.go +++ b/pkg/exchange/binance/binanceapi/client.go @@ -60,6 +60,7 @@ func NewClient(baseURL string) *RestClient { func (c *RestClient) Auth(key, secret string) { c.Key = key + // pragma: allowlist nextline secret c.Secret = secret } diff --git a/pkg/exchange/binance/exchange.go b/pkg/exchange/binance/exchange.go index f8a5fc023..9dc464e65 100644 --- a/pkg/exchange/binance/exchange.go +++ b/pkg/exchange/binance/exchange.go @@ -122,7 +122,8 @@ func New(key, secret string) *Exchange { } return &Exchange{ - key: key, + key: key, + // pragma: allowlist nextline secret secret: secret, client: client, futuresClient: futuresClient, diff --git a/pkg/exchange/ftx/exchange.go b/pkg/exchange/ftx/exchange.go index a941caa0c..b947dc5c0 100644 --- a/pkg/exchange/ftx/exchange.go +++ b/pkg/exchange/ftx/exchange.go @@ -90,8 +90,9 @@ func NewExchange(key, secret string, subAccount string) *Exchange { client: client, restEndpoint: u, key: key, - secret: secret, - subAccount: subAccount, + // pragma: allowlist nextline secret + secret: secret, + subAccount: subAccount, } } diff --git a/pkg/exchange/ftx/ftxapi/client.go b/pkg/exchange/ftx/ftxapi/client.go index 58b2ddde0..2437bd48f 100644 --- a/pkg/exchange/ftx/ftxapi/client.go +++ b/pkg/exchange/ftx/ftxapi/client.go @@ -68,6 +68,7 @@ func NewClient() *RestClient { func (c *RestClient) Auth(key, secret, subAccount string) { c.Key = key + // pragma: allowlist nextline secret c.Secret = secret c.subAccount = subAccount } diff --git a/pkg/exchange/ftx/rest.go b/pkg/exchange/ftx/rest.go index 9d9cc96b1..18282551c 100644 --- a/pkg/exchange/ftx/rest.go +++ b/pkg/exchange/ftx/rest.go @@ -92,6 +92,7 @@ func newRestRequest(c *http.Client, baseURL *url.URL) *restRequest { func (r *restRequest) Auth(key, secret string) *restRequest { r.key = key + // pragma: allowlist nextline secret r.secret = secret return r } diff --git a/pkg/exchange/ftx/stream.go b/pkg/exchange/ftx/stream.go index 4e6650c5d..6a70a249d 100644 --- a/pkg/exchange/ftx/stream.go +++ b/pkg/exchange/ftx/stream.go @@ -37,8 +37,9 @@ type klineSubscription struct { func NewStream(key, secret string, subAccount string, e *Exchange) *Stream { s := &Stream{ - exchange: e, - key: key, + exchange: e, + key: key, + // pragma: allowlist nextline secret secret: secret, subAccount: subAccount, StandardStream: &types.StandardStream{}, diff --git a/pkg/exchange/ftx/websocket_messages_test.go b/pkg/exchange/ftx/websocket_messages_test.go index bbe0f6e6f..5a9635e5a 100644 --- a/pkg/exchange/ftx/websocket_messages_test.go +++ b/pkg/exchange/ftx/websocket_messages_test.go @@ -182,6 +182,7 @@ func Test_insertAt(t *testing.T) { func Test_newLoginRequest(t *testing.T) { // From API doc: https://docs.ftx.com/?javascript#authentication-2 r := newLoginRequest("", "Y2QTHI23f23f23jfjas23f23To0RfUwX3H42fvN-", time.Unix(0, 1557246346499*int64(time.Millisecond)), "") + // pragma: allowlist nextline secret expectedSignature := "d10b5a67a1a941ae9463a60b285ae845cdeac1b11edc7da9977bef0228b96de9" assert.Equal(t, expectedSignature, r.Login.Signature) jsonStr, err := json.Marshal(r) diff --git a/pkg/exchange/kucoin/exchange.go b/pkg/exchange/kucoin/exchange.go index 84e8a8d1b..28da3a04e 100644 --- a/pkg/exchange/kucoin/exchange.go +++ b/pkg/exchange/kucoin/exchange.go @@ -44,7 +44,8 @@ func New(key, secret, passphrase string) *Exchange { } return &Exchange{ - key: key, + key: key, + // pragma: allowlist nextline secret secret: secret, passphrase: passphrase, client: client, diff --git a/pkg/exchange/kucoin/kucoinapi/client.go b/pkg/exchange/kucoin/kucoinapi/client.go index 0e084310d..6df47191c 100644 --- a/pkg/exchange/kucoin/kucoinapi/client.go +++ b/pkg/exchange/kucoin/kucoinapi/client.go @@ -58,6 +58,7 @@ func NewClient() *RestClient { func (c *RestClient) Auth(key, secret, passphrase string) { c.Key = key + // pragma: allowlist nextline secret c.Secret = secret c.Passphrase = passphrase } diff --git a/pkg/exchange/max/exchange.go b/pkg/exchange/max/exchange.go index 2972719ff..f7493975e 100644 --- a/pkg/exchange/max/exchange.go +++ b/pkg/exchange/max/exchange.go @@ -47,8 +47,9 @@ func New(key, secret string) *Exchange { client := maxapi.NewRestClient(baseURL) client.Auth(key, secret) return &Exchange{ - client: client, - key: key, + client: client, + key: key, + // pragma: allowlist nextline secret secret: secret, v3order: &v3.OrderService{Client: client}, v3margin: &v3.MarginService{Client: client}, diff --git a/pkg/exchange/max/maxapi/restapi.go b/pkg/exchange/max/maxapi/restapi.go index cd3f1743a..c9f1a0a3e 100644 --- a/pkg/exchange/max/maxapi/restapi.go +++ b/pkg/exchange/max/maxapi/restapi.go @@ -130,7 +130,9 @@ func NewRestClient(baseURL string) *RestClient { // Auth sets api key and secret for usage is requests that requires authentication. func (c *RestClient) Auth(key string, secret string) *RestClient { + // pragma: allowlist nextline secret c.APIKey = key + // pragma: allowlist nextline secret c.APISecret = secret return c } diff --git a/pkg/exchange/max/stream.go b/pkg/exchange/max/stream.go index b5a96212f..8a80bc7ad 100644 --- a/pkg/exchange/max/stream.go +++ b/pkg/exchange/max/stream.go @@ -44,7 +44,8 @@ func NewStream(key, secret string) *Stream { stream := &Stream{ StandardStream: types.NewStandardStream(), key: key, - secret: secret, + // pragma: allowlist nextline secret + secret: secret, } stream.SetEndpointCreator(stream.getEndpoint) stream.SetParser(max.ParseMessage) @@ -116,7 +117,9 @@ func (s *Stream) handleConnect() { nonce := time.Now().UnixNano() / int64(time.Millisecond) auth := &max.AuthMessage{ - Action: "auth", + // pragma: allowlist nextline secret + Action: "auth", + // pragma: allowlist nextline secret APIKey: s.key, Nonce: nonce, Signature: signPayload(fmt.Sprintf("%d", nonce), s.secret), diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index fe1b4deba..ab8dcddd2 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -38,7 +38,8 @@ func New(key, secret, passphrase string) *Exchange { } return &Exchange{ - key: key, + key: key, + // pragma: allowlist nextline secret secret: secret, passphrase: passphrase, client: client, diff --git a/pkg/exchange/okex/okexapi/client.go b/pkg/exchange/okex/okexapi/client.go index 5f23a0094..626e84160 100644 --- a/pkg/exchange/okex/okexapi/client.go +++ b/pkg/exchange/okex/okexapi/client.go @@ -91,6 +91,7 @@ func NewClient() *RestClient { func (c *RestClient) Auth(key, secret, passphrase string) { c.Key = key + // pragma: allowlist nextline secret c.Secret = secret c.Passphrase = passphrase } diff --git a/pkg/fixedpoint/convert.go b/pkg/fixedpoint/convert.go index f70f25885..9d4d83950 100644 --- a/pkg/fixedpoint/convert.go +++ b/pkg/fixedpoint/convert.go @@ -349,11 +349,11 @@ func NewFromString(input string) (Value, error) { } } if hasDecimal { - after := input[dotIndex+1 : len(input)] + after := input[dotIndex+1:] if decimalCount >= 8 { - after = after[0:8] + "." + after[8:len(after)] + after = after[0:8] + "." + after[8:] } else { - after = after[0:decimalCount] + strings.Repeat("0", 8-decimalCount) + after[decimalCount:len(after)] + after = after[0:decimalCount] + strings.Repeat("0", 8-decimalCount) + after[decimalCount:] } input = input[0:dotIndex] + after v, err := strconv.ParseFloat(input, 64) @@ -368,7 +368,7 @@ func NewFromString(input string) (Value, error) { return Value(int64(math.Trunc(v))), nil } else if hasScientificNotion { - exp, err := strconv.ParseInt(input[scIndex+1:len(input)], 10, 32) + exp, err := strconv.ParseInt(input[scIndex+1:], 10, 32) if err != nil { return 0, err } diff --git a/pkg/interact/auth.go b/pkg/interact/auth.go index 71212df76..343ff382a 100644 --- a/pkg/interact/auth.go +++ b/pkg/interact/auth.go @@ -37,6 +37,7 @@ type AuthInteract struct { func (it *AuthInteract) Commands(interact *Interact) { if it.Strict { // generate a one-time-use otp + // pragma: allowlist nextline secret if it.OneTimePasswordKey == nil { opts := totp.GenerateOpts{ Issuer: "interact", @@ -48,7 +49,7 @@ func (it *AuthInteract) Commands(interact *Interact) { if err != nil { panic(err) } - + // pragma: allowlist nextline secret it.OneTimePasswordKey = key } interact.Command("/auth", "authorize", func(reply Reply, session Session) error { diff --git a/pkg/migrations/mysql/migration_api_test.go b/pkg/migrations/mysql/migration_api_test.go index 2684076d3..9864095ce 100644 --- a/pkg/migrations/mysql/migration_api_test.go +++ b/pkg/migrations/mysql/migration_api_test.go @@ -14,7 +14,7 @@ func TestGetMigrationsMap(t *testing.T) { func TestMergeMigrationsMap(t *testing.T) { MergeMigrationsMap(map[int64]*rockhopper.Migration{ - 2: &rockhopper.Migration{}, - 3: &rockhopper.Migration{}, + 2: {}, + 3: {}, }) } diff --git a/pkg/migrations/sqlite3/migration_api_test.go b/pkg/migrations/sqlite3/migration_api_test.go index d1f4fe1ab..d7f77c875 100644 --- a/pkg/migrations/sqlite3/migration_api_test.go +++ b/pkg/migrations/sqlite3/migration_api_test.go @@ -14,7 +14,7 @@ func TestGetMigrationsMap(t *testing.T) { func TestMergeMigrationsMap(t *testing.T) { MergeMigrationsMap(map[int64]*rockhopper.Migration{ - 2: &rockhopper.Migration{}, - 3: &rockhopper.Migration{}, + 2: {}, + 3: {}, }) } diff --git a/pkg/server/envvars.go b/pkg/server/envvars.go index 82f4fbbf9..f11a418f9 100644 --- a/pkg/server/envvars.go +++ b/pkg/server/envvars.go @@ -17,11 +17,15 @@ func collectSessionEnvVars(sessions map[string]*bbgo.ExchangeSession) (envVars m } if len(session.EnvVarPrefix) > 0 { + // pragma: allowlist nextline secret envVars[session.EnvVarPrefix+"_API_KEY"] = session.Key + // pragma: allowlist nextline secret envVars[session.EnvVarPrefix+"_API_SECRET"] = session.Secret } else if len(session.Name) > 0 { sn := strings.ToUpper(session.Name) + // pragma: allowlist nextline secret envVars[sn+"_API_KEY"] = session.Key + // pragma: allowlist nextline secret envVars[sn+"_API_SECRET"] = session.Secret } else { err = fmt.Errorf("session %s name or env var prefix is not defined", session.Name) diff --git a/pkg/service/persistence_redis.go b/pkg/service/persistence_redis.go index e71629138..6b91d0583 100644 --- a/pkg/service/persistence_redis.go +++ b/pkg/service/persistence_redis.go @@ -19,6 +19,7 @@ func NewRedisPersistenceService(config *RedisPersistenceConfig) *RedisPersistenc client := redis.NewClient(&redis.Options{ Addr: net.JoinHostPort(config.Host, config.Port), // Username: "", // username is only for redis 6.0 + // pragma: allowlist nextline secret Password: config.Password, // no password set DB: config.DB, // use default DB })