all: integrate google spread sheet service

This commit is contained in:
c9s 2023-07-09 13:17:39 +08:00
parent e41d720867
commit 5962742b43
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
6 changed files with 93 additions and 8 deletions

View File

@ -5,6 +5,11 @@ sessions:
makerFeeRate: 0% makerFeeRate: 0%
takerFeeRate: 0.025% takerFeeRate: 0.025%
#services:
# googleSpreadSheet:
# jsonTokenFile: ".credentials/google-cloud/service-account-json-token.json"
# spreadSheetId: "YOUR_SPREADSHEET_ID"
exchangeStrategies: exchangeStrategies:
- on: max - on: max
scmaker: scmaker:

View File

@ -23,6 +23,18 @@ And
<https://developers.google.com/workspace/guides/create-credentials> <https://developers.google.com/workspace/guides/create-credentials>
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
```

View File

@ -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 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 { if err := environ.ConfigureNotificationSystem(ctx, userConfig); err != nil {
return errors.Wrap(err, "notification configure error") return errors.Wrap(err, "notification configure error")
} }

View File

@ -21,7 +21,9 @@ import (
// DefaultFeeRate set the fee rate for most cases // DefaultFeeRate set the fee rate for most cases
// BINANCE uses 0.1% for both maker and taker // 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 // MAX uses 0.050% for maker and 0.15% for taker
var DefaultFeeRate = fixedpoint.NewFromFloat(0.075 * 0.01) var DefaultFeeRate = fixedpoint.NewFromFloat(0.075 * 0.01)
@ -312,6 +314,15 @@ type SyncConfig struct {
} `json:"userDataStream,omitempty" yaml:"userDataStream,omitempty"` } `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 { type Config struct {
Build *BuildConfig `json:"build,omitempty" yaml:"build,omitempty"` Build *BuildConfig `json:"build,omitempty" yaml:"build,omitempty"`
@ -327,6 +338,8 @@ type Config struct {
Persistence *PersistenceConfig `json:"persistence,omitempty" yaml:"persistence,omitempty"` 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"` Sessions map[string]*ExchangeSession `json:"sessions,omitempty" yaml:"sessions,omitempty"`
RiskControls *RiskControls `json:"riskControls,omitempty" yaml:"riskControls,omitempty"` RiskControls *RiskControls `json:"riskControls,omitempty" yaml:"riskControls,omitempty"`

View File

@ -26,6 +26,7 @@ import (
"github.com/c9s/bbgo/pkg/notifier/slacknotifier" "github.com/c9s/bbgo/pkg/notifier/slacknotifier"
"github.com/c9s/bbgo/pkg/notifier/telegramnotifier" "github.com/c9s/bbgo/pkg/notifier/telegramnotifier"
"github.com/c9s/bbgo/pkg/service" "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/slack/slacklog"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util" "github.com/c9s/bbgo/pkg/util"
@ -78,6 +79,7 @@ const (
// Environment presents the real exchange data layer // Environment presents the real exchange data layer
type Environment struct { type Environment struct {
// built-in service
DatabaseService *service.DatabaseService DatabaseService *service.DatabaseService
OrderService *service.OrderService OrderService *service.OrderService
TradeService *service.TradeService TradeService *service.TradeService
@ -92,6 +94,9 @@ type Environment struct {
DepositService *service.DepositService DepositService *service.DepositService
PersistentService *service.PersistenceServiceFacade PersistentService *service.PersistenceServiceFacade
// external services
GoogleSpreadSheetService *googleservice.SpreadSheetService
// startTime is the time of start point (which is used in the backtest) // startTime is the time of start point (which is used in the backtest)
startTime time.Time startTime time.Time
@ -216,6 +221,14 @@ func (environ *Environment) AddExchange(name string, exchange types.Exchange) (s
return environ.AddExchangeSession(name, session) 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 { func (environ *Environment) ConfigureExchangeSessions(userConfig *Config) error {
// if sessions are not defined, we detect the sessions automatically // if sessions are not defined, we detect the sessions automatically
if len(userConfig.Sessions) == 0 { if len(userConfig.Sessions) == 0 {

View File

@ -1,24 +1,54 @@
package google package google
import ( import (
"context"
"fmt" "fmt"
"reflect" "reflect"
"time" "time"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"google.golang.org/api/option"
"google.golang.org/api/sheets/v4" "google.golang.org/api/sheets/v4"
"github.com/c9s/bbgo/pkg/fixedpoint" "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) { 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() resp, err := srv.Spreadsheets.Values.Get(spreadsheetId, readRange).Do()
return resp, err return resp, err
} }
func AddNewSheet(srv *sheets.Service, spreadsheetId string, title string) (*sheets.BatchUpdateSpreadsheetResponse, error) { 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{ return srv.Spreadsheets.BatchUpdate(spreadsheetId, &sheets.BatchUpdateSpreadsheetRequest{
IncludeSpreadsheetInResponse: false, IncludeSpreadsheetInResponse: false,
Requests: []*sheets.Request{ Requests: []*sheets.Request{
@ -127,7 +157,7 @@ func AppendRow(srv *sheets.Service, spreadsheetId string, sheetId int64, values
row := &sheets.RowData{} row := &sheets.RowData{}
row.Values = ValuesToCellData(values) row.Values = ValuesToCellData(values)
logrus.Infof("AppendRow: %+v", row.Values) log.Infof("AppendRow: %+v", row.Values)
return srv.Spreadsheets.BatchUpdate(spreadsheetId, &sheets.BatchUpdateSpreadsheetRequest{ return srv.Spreadsheets.BatchUpdate(spreadsheetId, &sheets.BatchUpdateSpreadsheetRequest{
Requests: []*sheets.Request{ Requests: []*sheets.Request{
{ {
@ -142,7 +172,7 @@ func AppendRow(srv *sheets.Service, spreadsheetId string, sheetId int64, values
} }
func DebugBatchUpdateSpreadsheetResponse(resp *sheets.BatchUpdateSpreadsheetResponse) { func DebugBatchUpdateSpreadsheetResponse(resp *sheets.BatchUpdateSpreadsheetResponse) {
logrus.Infof("BatchUpdateSpreadsheetResponse.SpreadsheetId: %+v", resp.SpreadsheetId) log.Infof("BatchUpdateSpreadsheetResponse.SpreadsheetId: %+v", resp.SpreadsheetId)
logrus.Infof("BatchUpdateSpreadsheetResponse.UpdatedSpreadsheet: %+v", resp.UpdatedSpreadsheet) log.Infof("BatchUpdateSpreadsheetResponse.UpdatedSpreadsheet: %+v", resp.UpdatedSpreadsheet)
logrus.Infof("BatchUpdateSpreadsheetResponse.Replies: %+v", resp.Replies) log.Infof("BatchUpdateSpreadsheetResponse.Replies: %+v", resp.Replies)
} }