From 5962742b43479aff27a3f0651bdda69f27f70c1f Mon Sep 17 00:00:00 2001 From: c9s Date: Sun, 9 Jul 2023 13:17:39 +0800 Subject: [PATCH] all: integrate google spread sheet service --- config/scmaker.yaml | 5 ++++ doc/topics/google-spreadsheet.md | 14 ++++++++++- pkg/bbgo/bootstrap.go | 12 +++++++++ pkg/bbgo/config.go | 15 +++++++++++- pkg/bbgo/environment.go | 13 ++++++++++ pkg/service/google/sheets.go | 42 +++++++++++++++++++++++++++----- 6 files changed, 93 insertions(+), 8 deletions(-) diff --git a/config/scmaker.yaml b/config/scmaker.yaml index b73515347..0f18409a8 100644 --- a/config/scmaker.yaml +++ b/config/scmaker.yaml @@ -5,6 +5,11 @@ sessions: makerFeeRate: 0% takerFeeRate: 0.025% +#services: +# googleSpreadSheet: +# jsonTokenFile: ".credentials/google-cloud/service-account-json-token.json" +# spreadSheetId: "YOUR_SPREADSHEET_ID" + exchangeStrategies: - on: max scmaker: diff --git a/doc/topics/google-spreadsheet.md b/doc/topics/google-spreadsheet.md index fbd34b998..246f592db 100644 --- a/doc/topics/google-spreadsheet.md +++ b/doc/topics/google-spreadsheet.md @@ -23,6 +23,18 @@ And - +Download the JSON token file and store it in a safe place. + +### Setting up service account permissions + +Go to Google Workspace and Add "Manage Domain Wide Delegation", add you client and with the following scopes: + +``` +https://www.googleapis.com/auth/drive +https://www.googleapis.com/auth/drive.file +https://www.googleapis.com/auth/drive.readonly +https://www.googleapis.com/auth/spreadsheets +https://www.googleapis.com/auth/spreadsheets.readonly +``` diff --git a/pkg/bbgo/bootstrap.go b/pkg/bbgo/bootstrap.go index 63b2dc452..bb297e2db 100644 --- a/pkg/bbgo/bootstrap.go +++ b/pkg/bbgo/bootstrap.go @@ -24,6 +24,12 @@ func BootstrapEnvironmentLightweight(ctx context.Context, environ *Environment, } } + if userConfig.Service != nil { + if err := environ.ConfigureService(ctx, userConfig.Service); err != nil { + return err + } + } + return nil } @@ -46,6 +52,12 @@ func BootstrapEnvironment(ctx context.Context, environ *Environment, userConfig } } + if userConfig.Service != nil { + if err := environ.ConfigureService(ctx, userConfig.Service); err != nil { + return err + } + } + if err := environ.ConfigureNotificationSystem(ctx, userConfig); err != nil { return errors.Wrap(err, "notification configure error") } diff --git a/pkg/bbgo/config.go b/pkg/bbgo/config.go index 2f13d20bb..c8d6e3120 100644 --- a/pkg/bbgo/config.go +++ b/pkg/bbgo/config.go @@ -21,7 +21,9 @@ import ( // DefaultFeeRate set the fee rate for most cases // BINANCE uses 0.1% for both maker and taker -// for BNB holders, it's 0.075% for both maker and taker +// +// for BNB holders, it's 0.075% for both maker and taker +// // MAX uses 0.050% for maker and 0.15% for taker var DefaultFeeRate = fixedpoint.NewFromFloat(0.075 * 0.01) @@ -312,6 +314,15 @@ type SyncConfig struct { } `json:"userDataStream,omitempty" yaml:"userDataStream,omitempty"` } +type GoogleSpreadSheetServiceConfig struct { + JsonTokenFile string `json:"jsonTokenFile" yaml:"jsonTokenFile"` + SpreadSheetID string `json:"spreadSheetId" yaml:"spreadSheetId"` +} + +type ServiceConfig struct { + GoogleSpreadSheetService *GoogleSpreadSheetServiceConfig `json:"googleSpreadSheet" yaml:"googleSpreadSheet"` +} + type Config struct { Build *BuildConfig `json:"build,omitempty" yaml:"build,omitempty"` @@ -327,6 +338,8 @@ type Config struct { Persistence *PersistenceConfig `json:"persistence,omitempty" yaml:"persistence,omitempty"` + Service *ServiceConfig `json:"services,omitempty" yaml:"services,omitempty"` + Sessions map[string]*ExchangeSession `json:"sessions,omitempty" yaml:"sessions,omitempty"` RiskControls *RiskControls `json:"riskControls,omitempty" yaml:"riskControls,omitempty"` diff --git a/pkg/bbgo/environment.go b/pkg/bbgo/environment.go index f89f71dbb..b4e1ac7cd 100644 --- a/pkg/bbgo/environment.go +++ b/pkg/bbgo/environment.go @@ -26,6 +26,7 @@ import ( "github.com/c9s/bbgo/pkg/notifier/slacknotifier" "github.com/c9s/bbgo/pkg/notifier/telegramnotifier" "github.com/c9s/bbgo/pkg/service" + googleservice "github.com/c9s/bbgo/pkg/service/google" "github.com/c9s/bbgo/pkg/slack/slacklog" "github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/util" @@ -78,6 +79,7 @@ const ( // Environment presents the real exchange data layer type Environment struct { + // built-in service DatabaseService *service.DatabaseService OrderService *service.OrderService TradeService *service.TradeService @@ -92,6 +94,9 @@ type Environment struct { DepositService *service.DepositService PersistentService *service.PersistenceServiceFacade + // external services + GoogleSpreadSheetService *googleservice.SpreadSheetService + // startTime is the time of start point (which is used in the backtest) startTime time.Time @@ -216,6 +221,14 @@ func (environ *Environment) AddExchange(name string, exchange types.Exchange) (s return environ.AddExchangeSession(name, session) } +func (environ *Environment) ConfigureService(ctx context.Context, srvConfig *ServiceConfig) error { + if srvConfig.GoogleSpreadSheetService != nil { + environ.GoogleSpreadSheetService = googleservice.NewSpreadSheetService(ctx, srvConfig.GoogleSpreadSheetService.JsonTokenFile, srvConfig.GoogleSpreadSheetService.SpreadSheetID) + } + + return nil +} + func (environ *Environment) ConfigureExchangeSessions(userConfig *Config) error { // if sessions are not defined, we detect the sessions automatically if len(userConfig.Sessions) == 0 { diff --git a/pkg/service/google/sheets.go b/pkg/service/google/sheets.go index 495100418..d77369e82 100644 --- a/pkg/service/google/sheets.go +++ b/pkg/service/google/sheets.go @@ -1,24 +1,54 @@ package google import ( + "context" "fmt" "reflect" "time" "github.com/sirupsen/logrus" + "google.golang.org/api/option" "google.golang.org/api/sheets/v4" "github.com/c9s/bbgo/pkg/fixedpoint" ) +var log = logrus.WithField("service", "google") + +type SpreadSheetService struct { + SpreadsheetID string + TokenFile string + + service *sheets.Service +} + +func NewSpreadSheetService(ctx context.Context, tokenFile string, spreadsheetID string) *SpreadSheetService { + if len(tokenFile) == 0 { + log.Panicf("google.SpreadSheetService: jsonTokenFile is not set") + } + + srv, err := sheets.NewService(ctx, + option.WithCredentialsFile(tokenFile), + ) + + if err != nil { + log.Panicf("google.SpreadSheetService: unable to initialize spreadsheet service: %v", err) + } + + return &SpreadSheetService{ + SpreadsheetID: spreadsheetID, + service: srv, + } +} + func ReadSheetValuesRange(srv *sheets.Service, spreadsheetId, readRange string) (*sheets.ValueRange, error) { - logrus.Infof("ReadSheetValuesRange: %s", readRange) + log.Infof("ReadSheetValuesRange: %s", readRange) resp, err := srv.Spreadsheets.Values.Get(spreadsheetId, readRange).Do() return resp, err } func AddNewSheet(srv *sheets.Service, spreadsheetId string, title string) (*sheets.BatchUpdateSpreadsheetResponse, error) { - logrus.Infof("AddNewSheet: %s", title) + log.Infof("AddNewSheet: %s", title) return srv.Spreadsheets.BatchUpdate(spreadsheetId, &sheets.BatchUpdateSpreadsheetRequest{ IncludeSpreadsheetInResponse: false, Requests: []*sheets.Request{ @@ -127,7 +157,7 @@ func AppendRow(srv *sheets.Service, spreadsheetId string, sheetId int64, values row := &sheets.RowData{} row.Values = ValuesToCellData(values) - logrus.Infof("AppendRow: %+v", row.Values) + log.Infof("AppendRow: %+v", row.Values) return srv.Spreadsheets.BatchUpdate(spreadsheetId, &sheets.BatchUpdateSpreadsheetRequest{ Requests: []*sheets.Request{ { @@ -142,7 +172,7 @@ func AppendRow(srv *sheets.Service, spreadsheetId string, sheetId int64, values } func DebugBatchUpdateSpreadsheetResponse(resp *sheets.BatchUpdateSpreadsheetResponse) { - logrus.Infof("BatchUpdateSpreadsheetResponse.SpreadsheetId: %+v", resp.SpreadsheetId) - logrus.Infof("BatchUpdateSpreadsheetResponse.UpdatedSpreadsheet: %+v", resp.UpdatedSpreadsheet) - logrus.Infof("BatchUpdateSpreadsheetResponse.Replies: %+v", resp.Replies) + log.Infof("BatchUpdateSpreadsheetResponse.SpreadsheetId: %+v", resp.SpreadsheetId) + log.Infof("BatchUpdateSpreadsheetResponse.UpdatedSpreadsheet: %+v", resp.UpdatedSpreadsheet) + log.Infof("BatchUpdateSpreadsheetResponse.Replies: %+v", resp.Replies) }