Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions frbnc/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func (a *Account) MarginRatio() (ratio, margin, totalPos float64) {
acct := a.Futures
margin = acct.TotalMarginBalance

// ETHBTC需要单独考虑 quote asset是USDC的无伤大雅
for _, pos := range acct.Positions {
totalPos += pos.PositionInitialMargin * pos.Leverage
}
Expand All @@ -92,6 +93,10 @@ func (a *Account) MarginRatio() (ratio, margin, totalPos float64) {
return
}

func (a *Account) FuturesMMR() (mmr float64) {
return a.Futures.TotalMaintMargin / a.Futures.TotalMarginBalance
}

func QueryAccount(user *bnc.User) (resp *resty.Response, acct *Account, err cex.RequestError) {
resp, spot, err := user.SpotAccount()
if err.IsNotNil() {
Expand Down
7 changes: 4 additions & 3 deletions frbnc/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package frbnc
import (
"errors"
"math"
"sort"
"slices"

"github.com/dwdwow/cex/bnc"
"github.com/dwdwow/mathy"
Expand Down Expand Up @@ -93,6 +93,7 @@ func AnalyzeFuturesUsdt(acct *Account) (analysis FuturesUsdtAnalysis) {
analysis.Err = errors.New("can not get usdt futures wallet balance")
return
}
// -5000 needs modification, it is a trigger value
if usdt.WalletBalance > -5000 {
return
}
Expand Down Expand Up @@ -129,8 +130,8 @@ func AnalyzeMarginableSpotBals(acct *Account) (bals []MarginableSpotBal) {
Err: err,
})
}
sort.Slice(bals, func(i, j int) bool {
return bals[i].MarginAvailable > bals[j].MarginAvailable
slices.SortFunc(bals, func(i, j MarginableSpotBal) int {
return int(math.Copysign(1, j.MarginAvailable-i.MarginAvailable))
})
return
}
Expand Down
29 changes: 13 additions & 16 deletions frbnc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"math"
"os"
"slices"
"sort"
"sync"
"time"

Expand Down Expand Up @@ -46,8 +45,7 @@ func NewMain(user *bnc.User, logger *slog.Logger) (*Main, error) {

func (m *Main) wait() {
suber := m.acctWatcher.Sub()
for {
acct := <-suber
for acct := range suber {
if acct.Err != nil {
m.logger.Error("Receive Account Error", "err", acct.Err)
continue
Expand All @@ -64,8 +62,7 @@ func (m *Main) handle(acct *Account) {

m.handleRedundant(acct)

analysis := AnalyzeAccount(acct)
m.handleAnalysis(acct, analysis)
m.handleAnalysis(acct, AnalyzeAccount(acct))
}

func (m *Main) handleRedundant(acct *Account) {
Expand Down Expand Up @@ -97,16 +94,15 @@ func (m *Main) handleLowLtvOrds(acct *Account) {
}

func (m *Main) adjustLowRiskFutureAccount(acct *Account) {
marginRatio, marginValue, totalPos := acct.MarginRatio()
_, marginValue, totalPos := acct.MarginRatio()

var marginGap float64

if totalPos <= 0 {
m.logger.Info("Future Total Position Is 0")
return
} else {
marginRatio = marginValue / totalPos
marginGap = totalPos * math.Abs(marginRatio-middleFuturesAccountMarginRatio)
marginGap = totalPos * math.Abs(marginValue/totalPos-middleFuturesAccountMarginRatio)
}

remainMarginGap := marginGap
Expand Down Expand Up @@ -222,12 +218,12 @@ func (m *Main) ClassifyLoanOrds(acct *Account) (lowLtvOrds, highLtvOrds []bnc.Cr
}
}

sort.Slice(lowLtvOrds, func(i, j int) bool {
return lowLtvOrds[i].CurrentLTV < lowLtvOrds[j].CurrentLTV
slices.SortFunc(lowLtvOrds, func(i, j bnc.CryptoLoanFlexibleOngoingOrder) int {
return int(math.Copysign(1, i.CurrentLTV-j.CurrentLTV))
})

sort.Slice(highLtvOrds, func(i, j int) bool {
return highLtvOrds[i].CurrentLTV > highLtvOrds[j].CurrentLTV
slices.SortFunc(highLtvOrds, func(i, j bnc.CryptoLoanFlexibleOngoingOrder) int {
return int(math.Copysign(1, j.CurrentLTV-i.CurrentLTV))
})

//var lowCollaterals, highCollaterals []string
Expand Down Expand Up @@ -794,12 +790,13 @@ func (m *Main) NewPos(spPair, fuPair cex.Pair, spSide cex.OrderSide, spQty, fuEx
}

var spTrader, fuTrader cex.MarketTraderFunc
if spSide == cex.OrderSideBuy {
switch spSide {
case cex.OrderSideBuy:
spTrader = m.user.NewSpotMarketBuyOrder
fuTrader = m.user.NewFuturesMarketSellOrder
} else if spSide == cex.OrderSideSell {
spTrader = m.user.NewSpotMarketSellOrder
fuTrader = m.user.NewFuturesMarketBuyOrder
case cex.OrderSideSell:
spTrader = m.user.NewSpotMarketSellOrder
fuTrader = m.user.NewFuturesMarketSellOrder
}

logger.Info("Placing Spot Market Order")
Expand Down
27 changes: 23 additions & 4 deletions frbnc/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ func slice2map[T any, S []T](s S, key func(T) string) map[string]T {
return m
}

func slice2mapkv[T1 any, T2 any, S []T1](s S, key func(T1) string, value func(T1) T2) map[string]T2 {
m := make(map[string]T2, len(s))
for _, t := range s {
m[key(t)] = value(t)
}
return m
}

func fuPrice(pos bnc.FuturesAccountPosition) (price float64, err error) {
posAmt := pos.AbsPositionAmt()
if posAmt > 0 {
Expand Down Expand Up @@ -70,10 +78,21 @@ func QueryFuPairs() (map[string]cex.Pair, error) {
return queryPairs(bnc.QueryFuturesPairs)
}

func mapGetter[U any](m map[string]U, key string) (U, bool) {
func mapGetter[U any](m map[string]U, key string) (v U, ok bool) {
if m == nil {
return *new(U), false
return
}
v, ok = m[key]
return
}

func delSpotAcctAssetZeroValue(acct *VIPPortmarAccount) []bnc.SpotBalance {
spotBals := make([]bnc.SpotBalance, 0, len(acct.spBals))
for _, bal := range acct.spBals {
if bal.Free == 0 && bal.Locked == 0 {
continue
}
spotBals = append(spotBals, bal)
}
v, ok := m[key]
return v, ok
return spotBals
}
119 changes: 93 additions & 26 deletions frbnc/vip_portmar_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ const (
)

type VIPPortmarAccountConfig struct {
MinUniMMR float64
BalancedUniMMR float64
MaxUniMMR float64
MinUniMMR float64
BalancedUniMMR float64
MaxUniMMR float64
MinVIPLoanLTV float64
BalancedVIPLoanLTV float64
MaxVIPLoanLTV float64
}

type VIPPortmarAccount struct {
Expand All @@ -26,16 +29,32 @@ type VIPPortmarAccount struct {

PortmarAccountDetail bnc.PortfolioMarginAccountDetail `json:"portmarAccountDetail"`
PortmarAccountInformation bnc.PortfolioMarginAccountInformation `json:"portmarAccountInformation"`

LoanOrders []bnc.VIPLoanOngoingOrder `json:"loanOrders"`
LoanStatusInfo []bnc.VIPLoanApplicationStatusInfo `json:"loanStatusInfo"`

CollateralRates []bnc.PortfolioMarginCollateralRate `json:"collateralRates"`

spBals map[string]bnc.SpotBalance
pmAssets map[string]bnc.PortfolioMarginAccountAsset
pmPoss map[string]bnc.PortfolioMarginAccountPosition
collRates map[string]bnc.PortfolioMarginCollateralRate
PortMarAccountBalances []bnc.PortfolioMarginBalance `json:"portMarAccountBalances"`
PortMarAccountUMPositions []bnc.PortfolioMarginUMPositionRisk `json:"portMarAccountUMPositions"`
PortMarAccountCMPositions []bnc.PortfolioMarginCMPositionRisk `json:"portMarAccountCMPositions"`
PortMarCollateralRates []bnc.PortfolioMarginCollateralRate `json:"portMarCollateralRates"`

LoanOrders []bnc.VIPLoanOngoingOrder `json:"loanOrders"`
LoanStatusInfo []bnc.VIPLoanApplicationStatusInfo `json:"loanStatusInfo"`
LoanCollateralAssets []bnc.VIPLoanCollateralAsset `json:"loanCollateralAssets"`

// spot account
spBals map[string]bnc.SpotBalance
// cross margin account
pmBals map[string]bnc.PortfolioMarginBalance
// um futures account
umPoss map[string]bnc.PortfolioMarginUMPositionRisk
// cm futures account
cmPoss map[string]bnc.PortfolioMarginCMPositionRisk

pmAssets map[string]bnc.PortfolioMarginAccountAsset
pmPoss map[string]bnc.PortfolioMarginAccountPosition
pmCollRateMap map[string]float64
loanCollAssets map[string]bnc.VIPLoanCollateralAsset

spPriceMap map[string]float64
umPriceMap map[string]float64
cmPriceMap map[string]float64
}

func (a VIPPortmarAccount) SpotBalance(asset string) (bnc.SpotBalance, bool) {
Expand All @@ -50,52 +69,100 @@ func (a VIPPortmarAccount) PortmarPosition(symbol string) (bnc.PortfolioMarginAc
return mapGetter(a.pmPoss, symbol)
}

func (a VIPPortmarAccount) PortmarCollateralRate(asset string) (bnc.PortfolioMarginCollateralRate, bool) {
return mapGetter(a.collRates, asset)
func (a VIPPortmarAccount) PortmarCollateralRate(asset string) (float64, bool) {
return mapGetter(a.pmCollRateMap, asset)
}

func QueryVIPPortmarAccount(user *bnc.User) (resp *resty.Response, acct *VIPPortmarAccount, reqErr cex.RequestError) {
resp, spot, reqErr := user.SpotAccount()
if reqErr.IsNotNil() {
return
}

resp, pmDetail, reqErr := user.PortfolioMarginAccountDetail()
if reqErr.IsNotNil() {
return
}

resp, pmInfo, reqErr := user.PortfolioMarginAccountInformation()
if reqErr.IsNotNil() {
return
}

_, pmBals, reqErr := user.PortfolioMarginBalances()
if reqErr.IsNotNil() {
return
}

_, pmPoss, reqErr := user.PortfolioMarginPositions("")
if reqErr.IsNotNil() {
return
}

resp, loanOrders, reqErr := user.VIPLoanOngoingOrders("", "", "", "")
if reqErr.IsNotNil() {
return
}

resp, loanStatusInfo, reqErr := user.VIPLoanApplicationStatus()
if reqErr.IsNotNil() {
return
}
collRates, err := bnc.QueryPortfolioMarginCollateralRates()

pmCollRates, err := bnc.QueryPortfolioMarginCollateralRates()
if err != nil {
reqErr = cex.RequestError{Err: err}
return
}

spPrices, err := bnc.QuerySpotPrices()
if err != nil {
reqErr = cex.RequestError{Err: err}
return
}

fuPrices, err := bnc.QueryFuturesPrices()
if err != nil {
reqErr = cex.RequestError{Err: err}
return
}

acct = &VIPPortmarAccount{
ApiKey: user.Api().ApiKey,
Time: time.Now().UnixMilli(),
Spot: spot,
ApiKey: user.Api().ApiKey,
Time: time.Now().UnixMilli(),
Spot: spot,

PortmarAccountDetail: pmDetail,
PortmarAccountInformation: pmInfo,
LoanOrders: loanOrders.Rows,
LoanStatusInfo: loanStatusInfo.Rows,
CollateralRates: collRates,
spBals: slice2map(spot.Balances, func(balance bnc.SpotBalance) string { return balance.Asset }),
pmAssets: slice2map(pmDetail.Assets, func(asset bnc.PortfolioMarginAccountAsset) string { return asset.Asset }),
pmPoss: slice2map(pmDetail.Positions, func(position bnc.PortfolioMarginAccountPosition) string { return position.Symbol }),
collRates: slice2map(collRates, func(rate bnc.PortfolioMarginCollateralRate) string {
PortMarAccountBalances: pmBals,
PortMarAccountUMPositions: pmPoss,
PortMarAccountCMPositions: []bnc.PortfolioMarginCMPositionRisk{},
PortMarCollateralRates: pmCollRates,

LoanOrders: loanOrders.Rows,
LoanStatusInfo: loanStatusInfo.Rows,
LoanCollateralAssets: []bnc.VIPLoanCollateralAsset{},

spBals: slice2map(spot.Balances, func(balance bnc.SpotBalance) string { return balance.Asset }),

pmBals: slice2map(pmBals, func(balance bnc.PortfolioMarginBalance) string { return balance.Asset }),

umPoss: slice2map(pmPoss, func(position bnc.PortfolioMarginUMPositionRisk) string { return position.Symbol }),

cmPoss: map[string]bnc.PortfolioMarginCMPositionRisk{},

pmAssets: slice2map(pmDetail.Assets, func(asset bnc.PortfolioMarginAccountAsset) string { return asset.Asset }),
pmPoss: slice2map(pmDetail.Positions, func(position bnc.PortfolioMarginAccountPosition) string { return position.Symbol }),
pmCollRateMap: slice2mapkv(pmCollRates, func(rate bnc.PortfolioMarginCollateralRate) string {
return rate.Asset
}, func(rate bnc.PortfolioMarginCollateralRate) float64 {
return rate.CollateralRate
}),
loanCollAssets: map[string]bnc.VIPLoanCollateralAsset{},

spPriceMap: slice2mapkv(spPrices, func(price bnc.SpotPriceTicker) string { return price.Symbol }, func(price bnc.SpotPriceTicker) float64 { return price.Price }),
umPriceMap: slice2mapkv(fuPrices, func(price bnc.FuturesPriceTicker) string { return price.Symbol }, func(price bnc.FuturesPriceTicker) float64 { return price.Price }),
cmPriceMap: map[string]float64{},
}

return
Expand Down
4 changes: 1 addition & 3 deletions frbnc/vip_portmar_acct_analy.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package frbnc

func AnalyseVIPLoan(acct *VIPPortmarAccount) {
//acct.LoanOrders
func AnalyseVIPLoan(acct *VIPPortmarAccount, cfg VIPPortmarAccountConfig) {
}

func AnalysePortmar(acct *VIPPortmarAccount, cfg VIPPortmarAccountConfig) {

}
Loading