256 lines
7.8 KiB
Go
256 lines
7.8 KiB
Go
package types
|
||
|
||
import (
|
||
"fmt"
|
||
"sync"
|
||
|
||
"github.com/sirupsen/logrus"
|
||
"github.com/spf13/viper"
|
||
|
||
"git.qtrade.icu/lychiyu/bbgo/pkg/fixedpoint"
|
||
)
|
||
|
||
var debugBalance = false
|
||
|
||
func init() {
|
||
debugBalance = viper.GetBool("debug-balance")
|
||
}
|
||
|
||
type PositionMap map[string]Position
|
||
type IsolatedMarginAssetMap map[string]IsolatedMarginAsset
|
||
type MarginAssetMap map[string]MarginUserAsset
|
||
type FuturesAssetMap map[string]FuturesUserAsset
|
||
type FuturesPositionMap map[string]FuturesPosition
|
||
|
||
type AccountType string
|
||
|
||
const (
|
||
AccountTypeFutures = AccountType("futures")
|
||
AccountTypeMargin = AccountType("margin")
|
||
AccountTypeIsolatedMargin = AccountType("isolated_margin")
|
||
AccountTypeSpot = AccountType("spot")
|
||
)
|
||
|
||
type Account struct {
|
||
sync.Mutex `json:"-"`
|
||
|
||
AccountType AccountType `json:"accountType,omitempty"`
|
||
FuturesInfo *FuturesAccountInfo
|
||
MarginInfo *MarginAccountInfo
|
||
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"`
|
||
TakerFeeRate fixedpoint.Value `json:"takerFeeRate,omitempty"`
|
||
|
||
TotalAccountValue fixedpoint.Value `json:"totalAccountValue,omitempty"`
|
||
|
||
CanDeposit bool `json:"canDeposit"`
|
||
CanTrade bool `json:"canTrade"`
|
||
CanWithdraw bool `json:"canWithdraw"`
|
||
|
||
balances BalanceMap
|
||
}
|
||
|
||
type FuturesAccountInfo struct {
|
||
// Futures fields
|
||
Assets FuturesAssetMap `json:"assets"`
|
||
Positions FuturesPositionMap `json:"positions"`
|
||
TotalInitialMargin fixedpoint.Value `json:"totalInitialMargin"`
|
||
TotalMaintMargin fixedpoint.Value `json:"totalMaintMargin"`
|
||
TotalMarginBalance fixedpoint.Value `json:"totalMarginBalance"`
|
||
TotalOpenOrderInitialMargin fixedpoint.Value `json:"totalOpenOrderInitialMargin"`
|
||
TotalPositionInitialMargin fixedpoint.Value `json:"totalPositionInitialMargin"`
|
||
TotalUnrealizedProfit fixedpoint.Value `json:"totalUnrealizedProfit"`
|
||
TotalWalletBalance fixedpoint.Value `json:"totalWalletBalance"`
|
||
UpdateTime int64 `json:"updateTime"`
|
||
}
|
||
|
||
type MarginAccountInfo struct {
|
||
// Margin fields
|
||
BorrowEnabled bool `json:"borrowEnabled"`
|
||
MarginLevel fixedpoint.Value `json:"marginLevel"`
|
||
TotalAssetOfBTC fixedpoint.Value `json:"totalAssetOfBtc"`
|
||
TotalLiabilityOfBTC fixedpoint.Value `json:"totalLiabilityOfBtc"`
|
||
TotalNetAssetOfBTC fixedpoint.Value `json:"totalNetAssetOfBtc"`
|
||
TradeEnabled bool `json:"tradeEnabled"`
|
||
TransferEnabled bool `json:"transferEnabled"`
|
||
Assets MarginAssetMap `json:"userAssets"`
|
||
}
|
||
|
||
type IsolatedMarginAccountInfo struct {
|
||
TotalAssetOfBTC fixedpoint.Value `json:"totalAssetOfBtc"`
|
||
TotalLiabilityOfBTC fixedpoint.Value `json:"totalLiabilityOfBtc"`
|
||
TotalNetAssetOfBTC fixedpoint.Value `json:"totalNetAssetOfBtc"`
|
||
Assets IsolatedMarginAssetMap `json:"userAssets"`
|
||
}
|
||
|
||
func NewAccount() *Account {
|
||
return &Account{
|
||
AccountType: "spot",
|
||
FuturesInfo: nil,
|
||
MarginInfo: nil,
|
||
IsolatedMarginInfo: nil,
|
||
MarginLevel: fixedpoint.Zero,
|
||
MarginTolerance: fixedpoint.Zero,
|
||
BorrowEnabled: false,
|
||
TransferEnabled: false,
|
||
MarginRatio: fixedpoint.Zero,
|
||
LiquidationPrice: fixedpoint.Zero,
|
||
LiquidationRate: fixedpoint.Zero,
|
||
MakerFeeRate: fixedpoint.Zero,
|
||
TakerFeeRate: fixedpoint.Zero,
|
||
TotalAccountValue: fixedpoint.Zero,
|
||
CanDeposit: false,
|
||
CanTrade: false,
|
||
CanWithdraw: false,
|
||
balances: make(BalanceMap),
|
||
}
|
||
|
||
}
|
||
|
||
// Balances lock the balances and returned the copied balances
|
||
func (a *Account) Balances() (d BalanceMap) {
|
||
a.Lock()
|
||
d = a.balances.Copy()
|
||
a.Unlock()
|
||
return d
|
||
}
|
||
|
||
func (a *Account) Balance(currency string) (balance Balance, ok bool) {
|
||
a.Lock()
|
||
balance, ok = a.balances[currency]
|
||
a.Unlock()
|
||
return balance, ok
|
||
}
|
||
|
||
func (a *Account) AddBalance(currency string, fund fixedpoint.Value) {
|
||
a.Lock()
|
||
defer a.Unlock()
|
||
|
||
balance, ok := a.balances[currency]
|
||
if ok {
|
||
balance.Available = balance.Available.Add(fund)
|
||
a.balances[currency] = balance
|
||
return
|
||
}
|
||
|
||
a.balances[currency] = Balance{
|
||
Currency: currency,
|
||
Available: fund,
|
||
Locked: fixedpoint.Zero,
|
||
}
|
||
}
|
||
|
||
func (a *Account) UseLockedBalance(currency string, fund fixedpoint.Value) error {
|
||
a.Lock()
|
||
defer a.Unlock()
|
||
|
||
balance, ok := a.balances[currency]
|
||
if !ok {
|
||
return fmt.Errorf("account balance %s does not exist", currency)
|
||
}
|
||
|
||
// simple case, using fund less than locked
|
||
if balance.Locked.Compare(fund) >= 0 {
|
||
balance.Locked = balance.Locked.Sub(fund)
|
||
a.balances[currency] = balance
|
||
return nil
|
||
}
|
||
|
||
return fmt.Errorf("trying to use more than locked: locked %v < want to use %v diff %v", balance.Locked, fund, balance.Locked.Sub(fund))
|
||
}
|
||
|
||
var QuantityDelta = fixedpoint.MustNewFromString("0.00000000001")
|
||
|
||
func (a *Account) UnlockBalance(currency string, unlocked fixedpoint.Value) error {
|
||
a.Lock()
|
||
defer a.Unlock()
|
||
|
||
balance, ok := a.balances[currency]
|
||
if !ok {
|
||
return fmt.Errorf("trying to unlocked inexisted balance: %s", currency)
|
||
}
|
||
|
||
// Instead of showing error in UnlockBalance,
|
||
// since this function is only called when cancel orders,
|
||
// there might be inequivalence in the last order quantity
|
||
if unlocked.Compare(balance.Locked) > 0 {
|
||
// check if diff is within delta
|
||
if unlocked.Sub(balance.Locked).Compare(QuantityDelta) <= 0 {
|
||
balance.Available = balance.Available.Add(balance.Locked)
|
||
balance.Locked = fixedpoint.Zero
|
||
a.balances[currency] = balance
|
||
return nil
|
||
}
|
||
return fmt.Errorf("trying to unlocked more than locked %s: locked %v < want to unlock %v", currency, balance.Locked, unlocked)
|
||
}
|
||
|
||
balance.Locked = balance.Locked.Sub(unlocked)
|
||
balance.Available = balance.Available.Add(unlocked)
|
||
a.balances[currency] = balance
|
||
return nil
|
||
}
|
||
|
||
func (a *Account) LockBalance(currency string, locked fixedpoint.Value) error {
|
||
a.Lock()
|
||
defer a.Unlock()
|
||
|
||
balance, ok := a.balances[currency]
|
||
if ok && balance.Available.Compare(locked) >= 0 {
|
||
balance.Locked = balance.Locked.Add(locked)
|
||
balance.Available = balance.Available.Sub(locked)
|
||
a.balances[currency] = balance
|
||
return nil
|
||
}
|
||
|
||
return fmt.Errorf("insufficient available balance %s for lock: want to lock %v, available %v", currency, locked, balance.Available)
|
||
}
|
||
|
||
func (a *Account) UpdateBalances(balances BalanceMap) {
|
||
a.Lock()
|
||
defer a.Unlock()
|
||
|
||
if a.balances == nil {
|
||
a.balances = make(BalanceMap)
|
||
}
|
||
|
||
for _, balance := range balances {
|
||
a.balances[balance.Currency] = balance
|
||
}
|
||
}
|
||
|
||
func (a *Account) Print() {
|
||
a.Lock()
|
||
defer a.Unlock()
|
||
|
||
if a.AccountType != "" {
|
||
logrus.Infof("account type: %s", a.AccountType)
|
||
}
|
||
|
||
if a.MakerFeeRate.Sign() > 0 {
|
||
logrus.Infof("maker fee rate: %v", a.MakerFeeRate)
|
||
}
|
||
if a.TakerFeeRate.Sign() > 0 {
|
||
logrus.Infof("taker fee rate: %v", a.TakerFeeRate)
|
||
}
|
||
|
||
a.balances.Print()
|
||
}
|