Skip to content

Commit

Permalink
binance: convert loans and repays to global types
Browse files Browse the repository at this point in the history
  • Loading branch information
c9s committed May 29, 2022
1 parent 409ad9b commit 70f0dcc
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 52 deletions.
12 changes: 12 additions & 0 deletions config/binance-margin.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
---
sessions:
# cross margin
binance_margin:
exchange: binance
margin: true

# isolated margin
binance_margin_linkusdt:
exchange: binance
margin: true
isolatedMargin: true
isolatedMarginSymbol: LINKUSDT

binance_margin_dotusdt:
exchange: binance
margin: true
isolatedMargin: true
isolatedMarginSymbol: DOTUSDT

exchangeStrategies:

- on: binance_margin_linkusdt
Expand Down
103 changes: 103 additions & 0 deletions pkg/cmd/margin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package cmd

import (
"context"
"errors"
"fmt"
"time"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/types"
)

var selectedSession *bbgo.ExchangeSession

func init() {
marginLoansCmd.Flags().String("asset", "", "asset")
marginLoansCmd.Flags().String("session", "", "exchange session name")
marginCmd.AddCommand(marginLoansCmd)

RootCmd.AddCommand(marginCmd)
}

// go run ./cmd/bbgo margin --session=binance
var marginCmd = &cobra.Command{
Use: "margin",
Short: "margin related history",
SilenceUsage: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if err := cobraLoadDotenv(cmd, args); err != nil {
return err
}

if err := cobraLoadConfig(cmd, args); err != nil {
return err
}

// ctx := context.Background()
environ := bbgo.NewEnvironment()

if userConfig == nil {
return errors.New("user config is not loaded")
}

if err := environ.ConfigureExchangeSessions(userConfig); err != nil {
return err
}

sessionName, err := cmd.Flags().GetString("session")
if err != nil {
return err
}

session, ok := environ.Session(sessionName)
if !ok {
return fmt.Errorf("session %s not found", sessionName)
}

selectedSession = session
return nil
},
}

// go run ./cmd/bbgo margin loans --session=binance
var marginLoansCmd = &cobra.Command{
Use: "loans --session=SESSION_NAME --asset=ASSET",
Short: "query loans history",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()

asset, err := cmd.Flags().GetString("asset")
if err != nil {
return fmt.Errorf("can't get the symbol from flags: %w", err)
}

if selectedSession == nil {
return errors.New("session is not set")
}

marginHistoryService, ok := selectedSession.Exchange.(types.MarginHistory)
if !ok {
return fmt.Errorf("exchange %s does not support MarginHistory service", selectedSession.ExchangeName)
}

now := time.Now()
startTime := now.AddDate(0, -5, 0)
endTime := now
loans, err := marginHistoryService.QueryLoanHistory(ctx, asset, &startTime, &endTime)
if err != nil {
return err
}

log.Infof("%d loans", len(loans))
for _, loan := range loans {
log.Infof("LOAN %+v", loan)
}

return nil
},
}
88 changes: 49 additions & 39 deletions pkg/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,10 @@ var RootCmd = &cobra.Command{
SilenceUsage: true,

PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
disableDotEnv, err := cmd.Flags().GetBool("no-dotenv")
if err != nil {
if err := cobraLoadDotenv(cmd, args) ; err != nil {
return err
}

if !disableDotEnv {
dotenvFile, err := cmd.Flags().GetString("dotenv")
if err != nil {
return err
}

if _, err := os.Stat(dotenvFile); err == nil {
if err := godotenv.Load(dotenvFile); err != nil {
return errors.Wrap(err, "error loading dotenv file")
}
}
}

if viper.GetBool("debug") {
log.Infof("debug mode is enabled")
log.SetLevel(log.DebugLevel)
Expand All @@ -67,37 +53,61 @@ var RootCmd = &cobra.Command{
}()
}

configFile, err := cmd.Flags().GetString("config")
return cobraLoadConfig(cmd, args)
},
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}

func cobraLoadDotenv(cmd *cobra.Command, args []string) error {
disableDotEnv, err := cmd.Flags().GetBool("no-dotenv")
if err != nil {
return err
}

if !disableDotEnv {
dotenvFile, err := cmd.Flags().GetString("dotenv")
if err != nil {
return errors.Wrapf(err, "failed to get the config flag")
return err
}

// load config file nicely
if len(configFile) > 0 {
// if config file exists, use the config loaded from the config file.
// otherwise, use a empty config object
if _, err := os.Stat(configFile); err == nil {
// load successfully
userConfig, err = bbgo.Load(configFile, false)
if err != nil {
return errors.Wrapf(err, "can not load config file: %s", configFile)
}

} else if os.IsNotExist(err) {
// config file doesn't exist, we should use the empty config
userConfig = &bbgo.Config{}
} else {
// other error
return errors.Wrapf(err, "config file load error: %s", configFile)
if _, err := os.Stat(dotenvFile); err == nil {
if err := godotenv.Load(dotenvFile); err != nil {
return errors.Wrap(err, "error loading dotenv file")
}
}
}
return nil
}

return nil
},
func cobraLoadConfig(cmd *cobra.Command, args []string) error {
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return errors.Wrapf(err, "failed to get the config flag")
}

RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
// load config file nicely
if len(configFile) > 0 {
// if config file exists, use the config loaded from the config file.
// otherwise, use an empty config object
if _, err := os.Stat(configFile); err == nil {
// load successfully
userConfig, err = bbgo.Load(configFile, false)
if err != nil {
return errors.Wrapf(err, "can not load config file: %s", configFile)
}

} else if os.IsNotExist(err) {
// config file doesn't exist, we should use the empty config
userConfig = &bbgo.Config{}
} else {
// other error
return errors.Wrapf(err, "config file load error: %s", configFile)
}
}

return nil
}

func init() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"time"

"github.com/c9s/requestgen"

"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)

// RepayStatus one of PENDING (pending execution), CONFIRMED (successfully loaned), FAILED (execution failed, nothing happened to your account);
Expand All @@ -16,14 +19,14 @@ const (
)

type MarginRepayRecord struct {
IsolatedSymbol string `json:"isolatedSymbol"`
Amount string `json:"amount"`
Asset string `json:"asset"`
Interest string `json:"interest"`
Principal string `json:"principal"`
Status string `json:"status"`
Timestamp int64 `json:"timestamp"`
TxId int64 `json:"txId"`
IsolatedSymbol string `json:"isolatedSymbol"`
Amount fixedpoint.Value `json:"amount"`
Asset string `json:"asset"`
Interest fixedpoint.Value `json:"interest"`
Principal fixedpoint.Value `json:"principal"`
Status string `json:"status"`
Timestamp types.MillisecondTimestamp `json:"timestamp"`
TxId uint64 `json:"txId"`
}

//go:generate requestgen -method GET -url "/sapi/v1/margin/repay" -type GetMarginRepayHistoryRequest -responseType .RowsResponse -responseDataField Rows -responseDataType []MarginRepayRecord
Expand Down
2 changes: 2 additions & 0 deletions pkg/exchange/binance/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ func New(key, secret string) *Exchange {

var err error
if len(key) > 0 && len(secret) > 0 {
client2.Auth(key, secret)

timeSetter.Do(func() {
_, err = client.NewSetServerTimeService().Do(context.Background())
if err != nil {
Expand Down
75 changes: 70 additions & 5 deletions pkg/exchange/binance/margin_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,32 @@ import (
"context"
"time"

"github.com/c9s/bbgo/pkg/exchange/binance/binanceapi"
"github.com/c9s/bbgo/pkg/types"
)

func (e *Exchange) QueryLoanHistory(ctx context.Context, asset string, startTime, endTime *time.Time) ([]types.MarginLoanRecord, error) {
req := e.client2.NewGetMarginLoanHistoryRequest()
req.Asset(asset)
req.Size(100)

if startTime != nil {
req.StartTime(*startTime)

// 6 months
if time.Since(*startTime) > time.Hour*24*30*6 {
req.Archived(true)
}
}

if startTime != nil && endTime != nil {
duration := endTime.Sub(*startTime)
if duration > time.Hour*24*30 {
t := startTime.Add(time.Hour * 24 * 30)
endTime = &t
}
}

if endTime != nil {
req.EndTime(*endTime)
}
Expand All @@ -22,18 +38,51 @@ func (e *Exchange) QueryLoanHistory(ctx context.Context, asset string, startTime
req.IsolatedSymbol(e.MarginSettings.IsolatedMarginSymbol)
}

loans, err := req.Do(ctx)
_ = loans
return nil, err
records, err := req.Do(ctx)
if err != nil {
return nil, err
}

var loans []types.MarginLoanRecord
for _, record := range records {
loans = append(loans, toGlobalLoan(record))
}

return loans, err
}

func toGlobalLoan(record binanceapi.MarginLoanRecord) types.MarginLoanRecord {
return types.MarginLoanRecord{
TransactionID: uint64(record.TxId),
Asset: record.Asset,
Principle: record.Principal,
Time: types.Time(record.Timestamp),
IsolatedSymbol: record.IsolatedSymbol,
}
}

func (e *Exchange) QueryRepayHistory(ctx context.Context, asset string, startTime, endTime *time.Time) ([]types.MarginRepayRecord, error) {
req := e.client2.NewGetMarginRepayHistoryRequest()
req.Asset(asset)
req.Size(100)

if startTime != nil {
req.StartTime(*startTime)

// 6 months
if time.Since(*startTime) > time.Hour*24*30*6 {
req.Archived(true)
}
}

if startTime != nil && endTime != nil {
duration := endTime.Sub(*startTime)
if duration > time.Hour*24*30 {
t := startTime.Add(time.Hour * 24 * 30)
endTime = &t
}
}

if endTime != nil {
req.EndTime(*endTime)
}
Expand All @@ -42,8 +91,24 @@ func (e *Exchange) QueryRepayHistory(ctx context.Context, asset string, startTim
req.IsolatedSymbol(e.MarginSettings.IsolatedMarginSymbol)
}

_, err := req.Do(ctx)
return nil, err
records, err := req.Do(ctx)

var repays []types.MarginRepayRecord
for _, record := range records {
repays = append(repays, toGlobalRepay(record))
}

return repays, err
}

func toGlobalRepay(record binanceapi.MarginRepayRecord) types.MarginRepayRecord {
return types.MarginRepayRecord{
TransactionID: record.TxId,
Asset: record.Asset,
Principle: record.Principal,
Time: types.Time(record.Timestamp),
IsolatedSymbol: record.IsolatedSymbol,
}
}

func (e *Exchange) QueryLiquidationHistory(ctx context.Context, startTime, endTime *time.Time) ([]types.MarginLiquidationRecord, error) {
Expand Down

0 comments on commit 70f0dcc

Please sign in to comment.