mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 16:55:15 +00:00
Merge pull request #555 from c9s/feature/binance-margin-api
feature: binance margin api integration
This commit is contained in:
commit
c3cc34d770
4
go.mod
4
go.mod
|
@ -6,7 +6,7 @@ go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||||
github.com/adshao/go-binance/v2 v2.3.3
|
github.com/adshao/go-binance/v2 v2.3.5
|
||||||
github.com/c9s/requestgen v1.1.1-0.20211230171502-c042072e23cd
|
github.com/c9s/requestgen v1.1.1-0.20211230171502-c042072e23cd
|
||||||
github.com/c9s/rockhopper v1.2.1-0.20210217093258-2661955904a9
|
github.com/c9s/rockhopper v1.2.1-0.20210217093258-2661955904a9
|
||||||
github.com/codingconcepts/env v0.0.0-20200821220118-a8fbf8d84482
|
github.com/codingconcepts/env v0.0.0-20200821220118-a8fbf8d84482
|
||||||
|
@ -16,7 +16,7 @@ require (
|
||||||
github.com/go-redis/redis/v8 v8.8.0
|
github.com/go-redis/redis/v8 v8.8.0
|
||||||
github.com/go-sql-driver/mysql v1.5.0
|
github.com/go-sql-driver/mysql v1.5.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/jmoiron/sqlx v1.3.4
|
github.com/jmoiron/sqlx v1.3.4
|
||||||
github.com/joho/godotenv v1.3.0
|
github.com/joho/godotenv v1.3.0
|
||||||
github.com/leekchan/accounting v0.0.0-20191218023648-17a4ce5f94d4
|
github.com/leekchan/accounting v0.0.0-20191218023648-17a4ce5f94d4
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -39,6 +39,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/adshao/go-binance/v2 v2.3.3 h1:ts46Mq4n4Ji9pkbXhcuOZ2T2Zy1x3i5SboC6EKKqzyg=
|
github.com/adshao/go-binance/v2 v2.3.3 h1:ts46Mq4n4Ji9pkbXhcuOZ2T2Zy1x3i5SboC6EKKqzyg=
|
||||||
github.com/adshao/go-binance/v2 v2.3.3/go.mod h1:TfcBwfGtmRibSljDDR0XCaPkfBt1kc2N9lnNMYC3dCQ=
|
github.com/adshao/go-binance/v2 v2.3.3/go.mod h1:TfcBwfGtmRibSljDDR0XCaPkfBt1kc2N9lnNMYC3dCQ=
|
||||||
|
github.com/adshao/go-binance/v2 v2.3.5 h1:WVYZecm0w8l14YoWlnKZj6xxZT2AKMTHpMQSqIX1xxA=
|
||||||
|
github.com/adshao/go-binance/v2 v2.3.5/go.mod h1:8Pg/FGTLyAhq8QXA0IkoReKyRpoxJcK3LVujKDAZV/c=
|
||||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
@ -229,6 +231,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/cache"
|
"github.com/c9s/bbgo/pkg/cache"
|
||||||
|
@ -190,7 +191,8 @@ type ExchangeSession struct {
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
|
|
||||||
// The exchange account states
|
// The exchange account states
|
||||||
Account *types.Account `json:"-" yaml:"-"`
|
Account *types.Account `json:"-" yaml:"-"`
|
||||||
|
accountMutex sync.Mutex
|
||||||
|
|
||||||
IsInitialized bool `json:"-" yaml:"-"`
|
IsInitialized bool `json:"-" yaml:"-"`
|
||||||
|
|
||||||
|
@ -280,6 +282,18 @@ func NewExchangeSession(name string, exchange types.Exchange) *ExchangeSession {
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateAccount locks the account mutex and update the account object
|
||||||
|
func (session *ExchangeSession) UpdateAccount(ctx context.Context) error {
|
||||||
|
account, err := session.Exchange.QueryAccount(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
session.accountMutex.Lock()
|
||||||
|
session.Account = account
|
||||||
|
session.accountMutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Init initializes the basic data structure and market information by its exchange.
|
// Init initializes the basic data structure and market information by its exchange.
|
||||||
// Note that the subscribed symbols are not loaded in this stage.
|
// Note that the subscribed symbols are not loaded in this stage.
|
||||||
func (session *ExchangeSession) Init(ctx context.Context, environ *Environment) error {
|
func (session *ExchangeSession) Init(ctx context.Context, environ *Environment) error {
|
||||||
|
@ -311,26 +325,39 @@ func (session *ExchangeSession) Init(ctx context.Context, environ *Environment)
|
||||||
|
|
||||||
// query and initialize the balances
|
// query and initialize the balances
|
||||||
if !session.PublicOnly {
|
if !session.PublicOnly {
|
||||||
log.Infof("querying balances from session %s...", session.Name)
|
account, err := session.Exchange.QueryAccount(ctx)
|
||||||
balances, err := session.Exchange.QueryAccountBalances(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.accountMutex.Lock()
|
||||||
|
session.Account = account
|
||||||
|
session.accountMutex.Unlock()
|
||||||
|
|
||||||
log.Infof("%s account", session.Name)
|
log.Infof("%s account", session.Name)
|
||||||
balances.Print()
|
account.Balances().Print()
|
||||||
session.Account.UpdateBalances(balances)
|
|
||||||
|
|
||||||
// forward trade updates and order updates to the order executor
|
// forward trade updates and order updates to the order executor
|
||||||
session.UserDataStream.OnTradeUpdate(session.OrderExecutor.EmitTradeUpdate)
|
session.UserDataStream.OnTradeUpdate(session.OrderExecutor.EmitTradeUpdate)
|
||||||
session.UserDataStream.OnOrderUpdate(session.OrderExecutor.EmitOrderUpdate)
|
session.UserDataStream.OnOrderUpdate(session.OrderExecutor.EmitOrderUpdate)
|
||||||
session.Account.BindStream(session.UserDataStream)
|
|
||||||
|
session.UserDataStream.OnBalanceSnapshot(func(balances types.BalanceMap) {
|
||||||
|
session.accountMutex.Lock()
|
||||||
|
session.Account.UpdateBalances(balances)
|
||||||
|
session.accountMutex.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
|
session.UserDataStream.OnBalanceUpdate(func(balances types.BalanceMap) {
|
||||||
|
session.accountMutex.Lock()
|
||||||
|
session.Account.UpdateBalances(balances)
|
||||||
|
session.accountMutex.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
session.bindConnectionStatusNotification(session.UserDataStream, "user data")
|
session.bindConnectionStatusNotification(session.UserDataStream, "user data")
|
||||||
|
|
||||||
// if metrics mode is enabled, we bind the callbacks to update metrics
|
// if metrics mode is enabled, we bind the callbacks to update metrics
|
||||||
if viper.GetBool("metrics") {
|
if viper.GetBool("metrics") {
|
||||||
session.metricsBalancesUpdater(balances)
|
session.metricsBalancesUpdater(account.Balances())
|
||||||
session.bindUserDataStreamMetrics(session.UserDataStream)
|
session.bindUserDataStreamMetrics(session.UserDataStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,62 +227,144 @@ func (e *Exchange) NewStream() types.Stream {
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exchange) QueryMarginAccount(ctx context.Context) (*types.Account, error) {
|
func (e *Exchange) QueryMarginAssetMaxBorrowable(ctx context.Context, asset string) (amount fixedpoint.Value, err error) {
|
||||||
account, err := e.Client.NewGetMarginAccountService().Do(ctx)
|
req := e.Client.NewGetMaxBorrowableService()
|
||||||
|
req.Asset(asset)
|
||||||
|
if e.IsIsolatedMargin {
|
||||||
|
req.IsolatedSymbol(e.IsolatedMarginSymbol)
|
||||||
|
}
|
||||||
|
resp, err := req.Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fixedpoint.Zero, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fixedpoint.NewFromString(resp.Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exchange) RepayMarginAsset(ctx context.Context, asset string, amount fixedpoint.Value) error {
|
||||||
|
req := e.Client.NewMarginRepayService()
|
||||||
|
req.Asset(asset)
|
||||||
|
req.Amount(amount.String())
|
||||||
|
if e.IsIsolatedMargin {
|
||||||
|
req.IsolatedSymbol(e.IsolatedMarginSymbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := req.Do(ctx)
|
||||||
|
log.Debugf("margin repayed %f %s, transaction id = %d", amount.Float64(), asset, resp.TranID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exchange) BorrowMarginAsset(ctx context.Context, asset string, amount fixedpoint.Value) error {
|
||||||
|
req := e.Client.NewMarginLoanService()
|
||||||
|
req.Asset(asset)
|
||||||
|
req.Amount(amount.String())
|
||||||
|
if e.IsIsolatedMargin {
|
||||||
|
req.IsolatedSymbol(e.IsolatedMarginSymbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := req.Do(ctx)
|
||||||
|
log.Debugf("margin borrowed %f %s, transaction id = %d", amount.Float64(), asset, resp.TranID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// transferCrossMarginAccountAsset transfer asset to the cross margin account or to the main account
|
||||||
|
func (e *Exchange) transferCrossMarginAccountAsset(ctx context.Context, asset string, amount fixedpoint.Value, io int) error {
|
||||||
|
req := e.Client.NewMarginTransferService()
|
||||||
|
req.Asset(asset)
|
||||||
|
req.Amount(amount.String())
|
||||||
|
|
||||||
|
if io > 0 { // in
|
||||||
|
req.Type(binance.MarginTransferTypeToMargin)
|
||||||
|
} else if io < 0 { // out
|
||||||
|
req.Type(binance.MarginTransferTypeToMain)
|
||||||
|
}
|
||||||
|
resp, err := req.Do(ctx)
|
||||||
|
|
||||||
|
log.Debugf("cross margin transfer %f %s, transaction id = %d", amount.Float64(), asset, resp.TranID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exchange) queryCrossMarginAccount(ctx context.Context) (*types.Account, error) {
|
||||||
|
marginAccount, err := e.Client.NewGetMarginAccountService().Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
marginLevel := fixedpoint.MustNewFromString(marginAccount.MarginLevel)
|
||||||
a := &types.Account{
|
a := &types.Account{
|
||||||
AccountType: types.AccountTypeMargin,
|
AccountType: types.AccountTypeMargin,
|
||||||
MarginInfo: toGlobalMarginAccountInfo(account), // In binance GO api, Account define account info which mantain []*AccountAsset and []*AccountPosition.
|
MarginInfo: toGlobalMarginAccountInfo(marginAccount), // In binance GO api, Account define marginAccount info which mantain []*AccountAsset and []*AccountPosition.
|
||||||
|
MarginLevel: marginLevel,
|
||||||
|
MarginTolerance: calculateMarginTolerance(marginLevel),
|
||||||
|
BorrowEnabled: marginAccount.BorrowEnabled,
|
||||||
|
TransferEnabled: marginAccount.TransferEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert cross margin user assets into balances
|
||||||
balances := types.BalanceMap{}
|
balances := types.BalanceMap{}
|
||||||
for _, userAsset := range account.UserAssets {
|
for _, userAsset := range marginAccount.UserAssets {
|
||||||
balances[userAsset.Asset] = types.Balance{
|
balances[userAsset.Asset] = types.Balance{
|
||||||
Currency: userAsset.Asset,
|
Currency: userAsset.Asset,
|
||||||
Available: fixedpoint.MustNewFromString(userAsset.Free),
|
Available: fixedpoint.MustNewFromString(userAsset.Free),
|
||||||
Locked: fixedpoint.MustNewFromString(userAsset.Locked),
|
Locked: fixedpoint.MustNewFromString(userAsset.Locked),
|
||||||
|
Interest: fixedpoint.MustNewFromString(userAsset.Interest),
|
||||||
|
Borrowed: fixedpoint.MustNewFromString(userAsset.Borrowed),
|
||||||
|
NetAsset: fixedpoint.MustNewFromString(userAsset.NetAsset),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a.UpdateBalances(balances)
|
a.UpdateBalances(balances)
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exchange) QueryIsolatedMarginAccount(ctx context.Context, symbols ...string) (*types.Account, error) {
|
func (e *Exchange) queryIsolatedMarginAccount(ctx context.Context) (*types.Account, error) {
|
||||||
req := e.Client.NewGetIsolatedMarginAccountService()
|
req := e.Client.NewGetIsolatedMarginAccountService()
|
||||||
if len(symbols) > 0 {
|
req.Symbols(e.IsolatedMarginSymbol)
|
||||||
req.Symbols(symbols...)
|
|
||||||
}
|
|
||||||
|
|
||||||
account, err := req.Do(ctx)
|
marginAccount, err := req.Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
a := &types.Account{
|
a := &types.Account{
|
||||||
AccountType: types.AccountTypeMargin,
|
AccountType: types.AccountTypeIsolatedMargin,
|
||||||
IsolatedMarginInfo: toGlobalIsolatedMarginAccountInfo(account), // In binance GO api, Account define account info which mantain []*AccountAsset and []*AccountPosition.
|
IsolatedMarginInfo: toGlobalIsolatedMarginAccountInfo(marginAccount), // In binance GO api, Account define marginAccount info which mantain []*AccountAsset and []*AccountPosition.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for isolated margin account, we will only have one asset in the Assets array.
|
||||||
|
if len(marginAccount.Assets) > 1 {
|
||||||
|
return nil, fmt.Errorf("unexpected number of user assets returned, got %d user assets", len(marginAccount.Assets))
|
||||||
|
}
|
||||||
|
|
||||||
|
userAsset := marginAccount.Assets[0]
|
||||||
|
marginLevel := fixedpoint.MustNewFromString(userAsset.MarginLevel)
|
||||||
|
a.MarginLevel = marginLevel
|
||||||
|
a.MarginTolerance = calculateMarginTolerance(marginLevel)
|
||||||
|
a.MarginRatio = fixedpoint.MustNewFromString(userAsset.MarginRatio)
|
||||||
|
a.BorrowEnabled = userAsset.BaseAsset.BorrowEnabled || userAsset.QuoteAsset.BorrowEnabled
|
||||||
|
a.LiquidationPrice = fixedpoint.MustNewFromString(userAsset.LiquidatePrice)
|
||||||
|
a.LiquidationRate = fixedpoint.MustNewFromString(userAsset.LiquidateRate)
|
||||||
|
|
||||||
|
// Convert user assets into balances
|
||||||
balances := types.BalanceMap{}
|
balances := types.BalanceMap{}
|
||||||
for _, userAsset := range account.Assets {
|
balances[userAsset.BaseAsset.Asset] = types.Balance{
|
||||||
balances[userAsset.BaseAsset.Asset] = types.Balance{
|
Currency: userAsset.BaseAsset.Asset,
|
||||||
Currency: userAsset.BaseAsset.Asset,
|
Available: fixedpoint.MustNewFromString(userAsset.BaseAsset.Free),
|
||||||
Available: fixedpoint.MustNewFromString(userAsset.BaseAsset.Free),
|
Locked: fixedpoint.MustNewFromString(userAsset.BaseAsset.Locked),
|
||||||
Locked: fixedpoint.MustNewFromString(userAsset.BaseAsset.Locked),
|
Interest: fixedpoint.MustNewFromString(userAsset.BaseAsset.Interest),
|
||||||
}
|
Borrowed: fixedpoint.MustNewFromString(userAsset.BaseAsset.Borrowed),
|
||||||
|
NetAsset: fixedpoint.MustNewFromString(userAsset.BaseAsset.NetAsset),
|
||||||
balances[userAsset.QuoteAsset.Asset] = types.Balance{
|
|
||||||
Currency: userAsset.QuoteAsset.Asset,
|
|
||||||
Available: fixedpoint.MustNewFromString(userAsset.QuoteAsset.Free),
|
|
||||||
Locked: fixedpoint.MustNewFromString(userAsset.QuoteAsset.Locked),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
balances[userAsset.QuoteAsset.Asset] = types.Balance{
|
||||||
|
Currency: userAsset.QuoteAsset.Asset,
|
||||||
|
Available: fixedpoint.MustNewFromString(userAsset.QuoteAsset.Free),
|
||||||
|
Locked: fixedpoint.MustNewFromString(userAsset.QuoteAsset.Locked),
|
||||||
|
Interest: fixedpoint.MustNewFromString(userAsset.QuoteAsset.Interest),
|
||||||
|
Borrowed: fixedpoint.MustNewFromString(userAsset.QuoteAsset.Borrowed),
|
||||||
|
NetAsset: fixedpoint.MustNewFromString(userAsset.QuoteAsset.NetAsset),
|
||||||
|
}
|
||||||
|
|
||||||
a.UpdateBalances(balances)
|
a.UpdateBalances(balances)
|
||||||
|
|
||||||
|
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,9 +623,9 @@ func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) {
|
||||||
if e.IsFutures {
|
if e.IsFutures {
|
||||||
account, err = e.QueryFuturesAccount(ctx)
|
account, err = e.QueryFuturesAccount(ctx)
|
||||||
} else if e.IsIsolatedMargin {
|
} else if e.IsIsolatedMargin {
|
||||||
account, err = e.QueryIsolatedMarginAccount(ctx)
|
account, err = e.queryIsolatedMarginAccount(ctx)
|
||||||
} else if e.IsMargin {
|
} else if e.IsMargin {
|
||||||
account, err = e.QueryMarginAccount(ctx)
|
account, err = e.queryCrossMarginAccount(ctx)
|
||||||
} else {
|
} else {
|
||||||
account, err = e.QuerySpotAccount(ctx)
|
account, err = e.QuerySpotAccount(ctx)
|
||||||
}
|
}
|
||||||
|
@ -1177,7 +1259,6 @@ func (e *Exchange) QueryKLines(ctx context.Context, symbol string, interval type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) (trades []types.Trade, err error) {
|
func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) (trades []types.Trade, err error) {
|
||||||
|
|
||||||
if e.IsMargin {
|
if e.IsMargin {
|
||||||
var remoteTrades []*binance.TradeV3
|
var remoteTrades []*binance.TradeV3
|
||||||
req := e.Client.NewListMarginTradesService().
|
req := e.Client.NewListMarginTradesService().
|
||||||
|
@ -1217,6 +1298,8 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
||||||
trades = append(trades, *localTrade)
|
trades = append(trades, *localTrade)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trades = types.SortTradesAscending(trades)
|
||||||
|
|
||||||
return trades, nil
|
return trades, nil
|
||||||
} else if e.IsFutures {
|
} else if e.IsFutures {
|
||||||
var remoteTrades []*futures.AccountTrade
|
var remoteTrades []*futures.AccountTrade
|
||||||
|
@ -1247,6 +1330,7 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
||||||
trades = append(trades, *localTrade)
|
trades = append(trades, *localTrade)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trades = types.SortTradesAscending(trades)
|
||||||
return trades, nil
|
return trades, nil
|
||||||
} else {
|
} else {
|
||||||
var remoteTrades []*binance.TradeV3
|
var remoteTrades []*binance.TradeV3
|
||||||
|
@ -1285,10 +1369,12 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
||||||
trades = append(trades, *localTrade)
|
trades = append(trades, *localTrade)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trades = types.SortTradesAscending(trades)
|
||||||
return trades, nil
|
return trades, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryDepth query the order book depth of a symbol
|
||||||
func (e *Exchange) QueryDepth(ctx context.Context, symbol string) (snapshot types.SliceOrderBook, finalUpdateID int64, err error) {
|
func (e *Exchange) QueryDepth(ctx context.Context, symbol string) (snapshot types.SliceOrderBook, finalUpdateID int64, err error) {
|
||||||
response, err := e.Client.NewDepthService().Symbol(symbol).Do(ctx)
|
response, err := e.Client.NewDepthService().Symbol(symbol).Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1415,3 +1501,17 @@ func getLaunchDate() (time.Time, error) {
|
||||||
|
|
||||||
return time.Date(2017, time.July, 14, 0, 0, 0, 0, loc), nil
|
return time.Date(2017, time.July, 14, 0, 0, 0, 0, loc), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Margin tolerance ranges from 0.0 (liquidation) to 1.0 (safest level of margin).
|
||||||
|
func calculateMarginTolerance(marginLevel fixedpoint.Value) fixedpoint.Value {
|
||||||
|
if marginLevel.IsZero() {
|
||||||
|
// Although margin level shouldn't be zero, that would indicate a significant problem.
|
||||||
|
// In that case, margin tolerance should return 0.0 to also reflect that problem.
|
||||||
|
return fixedpoint.Zero
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formula created by operations team for our binance code. Liquidation occurs at 1.1,
|
||||||
|
// so when marginLevel equals 1.1, the formula becomes 1.0 - 1.0, or zero.
|
||||||
|
// = 1.0 - (1.1 / marginLevel)
|
||||||
|
return fixedpoint.One.Sub(fixedpoint.NewFromFloat(1.1).Div(marginLevel))
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,13 @@ type Balance struct {
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
Available fixedpoint.Value `json:"available"`
|
Available fixedpoint.Value `json:"available"`
|
||||||
Locked fixedpoint.Value `json:"locked,omitempty"`
|
Locked fixedpoint.Value `json:"locked,omitempty"`
|
||||||
|
|
||||||
|
// margin related fields
|
||||||
|
Borrowed fixedpoint.Value `json:"borrowed,omitempty"`
|
||||||
|
Interest fixedpoint.Value `json:"interest,omitempty"`
|
||||||
|
|
||||||
|
// NetAsset = (Available + Locked) - Borrowed - Interest
|
||||||
|
NetAsset fixedpoint.Value `json:"net,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Balance) Total() fixedpoint.Value {
|
func (b Balance) Total() fixedpoint.Value {
|
||||||
|
@ -223,9 +230,10 @@ func (m BalanceMap) Print() {
|
||||||
type AccountType string
|
type AccountType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AccountTypeFutures = AccountType("futures")
|
AccountTypeFutures = AccountType("futures")
|
||||||
AccountTypeMargin = AccountType("margin")
|
AccountTypeMargin = AccountType("margin")
|
||||||
AccountTypeSpot = AccountType("spot")
|
AccountTypeIsolatedMargin = AccountType("isolated_margin")
|
||||||
|
AccountTypeSpot = AccountType("spot")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
|
@ -236,6 +244,23 @@ type Account struct {
|
||||||
MarginInfo *MarginAccountInfo
|
MarginInfo *MarginAccountInfo
|
||||||
IsolatedMarginInfo *IsolatedMarginAccountInfo
|
IsolatedMarginInfo *IsolatedMarginAccountInfo
|
||||||
|
|
||||||
|
// Margin related common field
|
||||||
|
// From binance:
|
||||||
|
// Margin Level = Total Asset Value / (Total Borrowed + Total Accrued Interest)
|
||||||
|
// If your margin level drops to 1.3, you will receive a Margin Call, which is a reminder that you should either increase your collateral (by depositing more funds) or reduce your loan (by repaying what you’ve borrowed).
|
||||||
|
// If your margin level drops to 1.1, your assets will be automatically liquidated, meaning that Binance will sell your funds at market price to repay the loan.
|
||||||
|
MarginLevel fixedpoint.Value `json:"marginLevel,omitempty"`
|
||||||
|
MarginTolerance fixedpoint.Value `json:"marginTolerance,omitempty"`
|
||||||
|
|
||||||
|
BorrowEnabled bool `json:"borrowEnabled,omitempty"`
|
||||||
|
TransferEnabled bool `json:"transferEnabled,omitempty"`
|
||||||
|
|
||||||
|
// isolated margin related fields
|
||||||
|
// LiquidationPrice is only used when account is in the isolated margin mode
|
||||||
|
MarginRatio fixedpoint.Value `json:"marginRatio,omitempty"`
|
||||||
|
LiquidationPrice fixedpoint.Value `json:"liquidationPrice,omitempty"`
|
||||||
|
LiquidationRate fixedpoint.Value `json:"liquidationRate,omitempty"`
|
||||||
|
|
||||||
MakerFeeRate fixedpoint.Value `json:"makerFeeRate,omitempty"`
|
MakerFeeRate fixedpoint.Value `json:"makerFeeRate,omitempty"`
|
||||||
TakerFeeRate fixedpoint.Value `json:"takerFeeRate,omitempty"`
|
TakerFeeRate fixedpoint.Value `json:"takerFeeRate,omitempty"`
|
||||||
|
|
||||||
|
@ -393,18 +418,6 @@ func (a *Account) UpdateBalances(balances BalanceMap) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printBalanceUpdate(balances BalanceMap) {
|
|
||||||
logrus.Infof("balance update: %+v", balances)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Account) BindStream(stream Stream) {
|
|
||||||
stream.OnBalanceUpdate(a.UpdateBalances)
|
|
||||||
stream.OnBalanceSnapshot(a.UpdateBalances)
|
|
||||||
if debugBalance {
|
|
||||||
stream.OnBalanceUpdate(printBalanceUpdate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Account) Print() {
|
func (a *Account) Print() {
|
||||||
a.Lock()
|
a.Lock()
|
||||||
defer a.Unlock()
|
defer a.Unlock()
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import "github.com/c9s/bbgo/pkg/fixedpoint"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
)
|
||||||
|
|
||||||
type FuturesExchange interface {
|
type FuturesExchange interface {
|
||||||
UseFutures()
|
UseFutures()
|
||||||
|
@ -48,6 +52,11 @@ type MarginExchange interface {
|
||||||
// QueryMarginAccount(ctx context.Context) (*binance.MarginAccount, error)
|
// QueryMarginAccount(ctx context.Context) (*binance.MarginAccount, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MarginBorrowRepay interface {
|
||||||
|
RepayMarginAsset(ctx context.Context, asset string, amount fixedpoint.Value) error
|
||||||
|
BorrowMarginAsset(ctx context.Context, asset string, amount fixedpoint.Value) error
|
||||||
|
}
|
||||||
|
|
||||||
type MarginSettings struct {
|
type MarginSettings struct {
|
||||||
IsMargin bool
|
IsMargin bool
|
||||||
IsIsolatedMargin bool
|
IsIsolatedMargin bool
|
||||||
|
|
Loading…
Reference in New Issue
Block a user