Skip to content

Commit

Permalink
xmaker: add account updater and handle margin account to add more fle…
Browse files Browse the repository at this point in the history
…xibility
  • Loading branch information
c9s committed Aug 28, 2024
1 parent 108fb61 commit 1d6282a
Showing 1 changed file with 112 additions and 39 deletions.
151 changes: 112 additions & 39 deletions pkg/strategy/xmaker/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ type Strategy struct {

askPriceHeartBeat, bidPriceHeartBeat *types.PriceHeartBeat

accountValueCalculator *bbgo.AccountValueCalculator

lastPrice fixedpoint.Value
groupID uint32

Expand Down Expand Up @@ -193,7 +195,6 @@ func aggregatePrice(pvs types.PriceVolumeSlice, requiredQuantity fixedpoint.Valu
func (s *Strategy) Initialize() error {
s.bidPriceHeartBeat = types.NewPriceHeartBeat(priceUpdateTimeout)
s.askPriceHeartBeat = types.NewPriceHeartBeat(priceUpdateTimeout)

s.logger = logrus.WithFields(logrus.Fields{
"symbol": s.Symbol,
"strategy": ID,
Expand Down Expand Up @@ -272,7 +273,7 @@ func (s *Strategy) applyBollingerMargin(
// so that the original bid margin can be multiplied by 1.x
bollMargin := s.BollBandMargin.Mul(ratio).Mul(factor)

s.logger.Infof("%s bollband uptrend adjusting bid margin %f (askMargin) + %f (bollMargin) = %f (finalAskMargin)",
s.logger.Infof("%s bollband uptrend adjusting ask margin %f (askMargin) + %f (bollMargin) = %f (finalAskMargin)",
s.Symbol,
quote.AskMargin.Float64(),
bollMargin.Float64(),
Expand Down Expand Up @@ -369,44 +370,90 @@ func (s *Strategy) updateQuote(ctx context.Context) {
}
}

hedgeBalances := s.sourceSession.GetAccount().Balances()
// if
// 1) the source session is a margin session
// 2) the min margin level is configured
// 3) the hedge account's margin level is lower than the min margin level
hedgeAccount := s.sourceSession.GetAccount()
hedgeBalances := hedgeAccount.Balances()
hedgeQuota := &bbgo.QuotaTransaction{}
if b, ok := hedgeBalances[s.sourceMarket.BaseCurrency]; ok {
// to make bid orders, we need enough base asset in the foreign exchange,
// if the base asset balance is not enough for selling
if s.StopHedgeBaseBalance.Sign() > 0 {
minAvailable := s.StopHedgeBaseBalance.Add(s.sourceMarket.MinQuantity)
if b.Available.Compare(minAvailable) > 0 {
hedgeQuota.BaseAsset.Add(b.Available.Sub(minAvailable))

if s.sourceSession.Margin &&
!s.MinMarginLevel.IsZero() &&
!hedgeAccount.MarginLevel.IsZero() {

if hedgeAccount.MarginLevel.Compare(s.MinMarginLevel) < 0 {
if quote, ok := hedgeAccount.Balance(s.sourceMarket.QuoteCurrency); ok {
quoteDebt := quote.Debt()
if quoteDebt.Sign() > 0 {
hedgeQuota.BaseAsset.Add(quoteDebt.Div(bestBid.Price))
}
}

if base, ok := hedgeAccount.Balance(s.sourceMarket.BaseCurrency); ok {
baseDebt := base.Debt()
if baseDebt.Sign() > 0 {
hedgeQuota.QuoteAsset.Add(baseDebt.Mul(bestAsk.Price))
}
}
} else {
// credit buffer
creditBufferRatio := fixedpoint.NewFromFloat(1.2)
if quote, ok := hedgeAccount.Balance(s.sourceMarket.QuoteCurrency); ok {
netQuote := quote.Net()
if netQuote.Sign() > 0 {
hedgeQuota.BaseAsset.Add(netQuote.Mul(creditBufferRatio).Div(bestBid.Price))
}
}

if base, ok := hedgeAccount.Balance(s.sourceMarket.BaseCurrency); ok {
netBase := base.Net()
if netBase.Sign() > 0 {
hedgeQuota.QuoteAsset.Add(netBase.Mul(creditBufferRatio).Mul(bestAsk.Price))
}
}
// netValueInUsd, err := s.accountValueCalculator.NetValue(ctx)
}

} else {
if b, ok := hedgeBalances[s.sourceMarket.BaseCurrency]; ok {
// to make bid orders, we need enough base asset in the foreign exchange,
// if the base asset balance is not enough for selling
if s.StopHedgeBaseBalance.Sign() > 0 {
minAvailable := s.StopHedgeBaseBalance.Add(s.sourceMarket.MinQuantity)
if b.Available.Compare(minAvailable) > 0 {
hedgeQuota.BaseAsset.Add(b.Available.Sub(minAvailable))
} else {
s.logger.Warnf("%s maker bid disabled: insufficient base balance %s", s.Symbol, b.String())
disableMakerBid = true
}
} else if b.Available.Compare(s.sourceMarket.MinQuantity) > 0 {
hedgeQuota.BaseAsset.Add(b.Available)
} else {
s.logger.Warnf("%s maker bid disabled: insufficient base balance %s", s.Symbol, b.String())
disableMakerBid = true
}
} else if b.Available.Compare(s.sourceMarket.MinQuantity) > 0 {
hedgeQuota.BaseAsset.Add(b.Available)
} else {
s.logger.Warnf("%s maker bid disabled: insufficient base balance %s", s.Symbol, b.String())
disableMakerBid = true
}
}

if b, ok := hedgeBalances[s.sourceMarket.QuoteCurrency]; ok {
// to make ask orders, we need enough quote asset in the foreign exchange,
// if the quote asset balance is not enough for buying
if s.StopHedgeQuoteBalance.Sign() > 0 {
minAvailable := s.StopHedgeQuoteBalance.Add(s.sourceMarket.MinNotional)
if b.Available.Compare(minAvailable) > 0 {
hedgeQuota.QuoteAsset.Add(b.Available.Sub(minAvailable))
if b, ok := hedgeBalances[s.sourceMarket.QuoteCurrency]; ok {
// to make ask orders, we need enough quote asset in the foreign exchange,
// if the quote asset balance is not enough for buying
if s.StopHedgeQuoteBalance.Sign() > 0 {
minAvailable := s.StopHedgeQuoteBalance.Add(s.sourceMarket.MinNotional)
if b.Available.Compare(minAvailable) > 0 {
hedgeQuota.QuoteAsset.Add(b.Available.Sub(minAvailable))
} else {
s.logger.Warnf("%s maker ask disabled: insufficient quote balance %s", s.Symbol, b.String())
disableMakerAsk = true
}
} else if b.Available.Compare(s.sourceMarket.MinNotional) > 0 {
hedgeQuota.QuoteAsset.Add(b.Available)
} else {
s.logger.Warnf("%s maker ask disabled: insufficient quote balance %s", s.Symbol, b.String())
disableMakerAsk = true
}
} else if b.Available.Compare(s.sourceMarket.MinNotional) > 0 {
hedgeQuota.QuoteAsset.Add(b.Available)
} else {
s.logger.Warnf("%s maker ask disabled: insufficient quote balance %s", s.Symbol, b.String())
disableMakerAsk = true
}

}

// if max exposure position is configured, we should not:
Expand Down Expand Up @@ -653,10 +700,9 @@ func (s *Strategy) updateQuote(ctx context.Context) {
_ = createdOrders
}

func (s *Strategy) adjustHedgeQuantityWithAvailableBalance(side types.SideType, quantity, lastPrice fixedpoint.Value) fixedpoint.Value {
// adjust quantity according to the balances
account := s.sourceSession.GetAccount()

func (s *Strategy) adjustHedgeQuantityWithAvailableBalance(
account *types.Account, side types.SideType, quantity, lastPrice fixedpoint.Value,
) fixedpoint.Value {
switch side {

case types.SideTypeBuy:
Expand Down Expand Up @@ -710,20 +756,15 @@ func (s *Strategy) Hedge(ctx context.Context, pos fixedpoint.Value) {
}
}

account := s.sourceSession.GetAccount()
if s.sourceSession.Margin {
// check the margin level
account, err := s.sourceSession.UpdateAccount(ctx)
if err != nil {
log.WithError(err).Errorf("unable to update account")
return
}

if !s.MinMarginLevel.IsZero() && !account.MarginLevel.IsZero() && account.MarginLevel.Compare(s.MinMarginLevel) < 0 {
log.Errorf("margin level %f is too low (< %f), skip hedge", account.MarginLevel.Float64(), s.MinMarginLevel.Float64())
return
}
} else {
quantity = s.adjustHedgeQuantityWithAvailableBalance(side, quantity, lastPrice)
quantity = s.adjustHedgeQuantityWithAvailableBalance(account, side, quantity, lastPrice)
}

// truncate quantity for the supported precision
Expand Down Expand Up @@ -920,6 +961,35 @@ func (s *Strategy) quoteWorker(ctx context.Context) {
}
}

func (s *Strategy) accountUpdater(ctx context.Context) {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return

case <-ticker.C:
if _, err := s.sourceSession.UpdateAccount(ctx); err != nil {
log.WithError(err).Errorf("unable to update account")
}

if err := s.accountValueCalculator.UpdatePrices(ctx); err != nil {
log.WithError(err).Errorf("unable to update account value with prices")
return
}

netValue, err := s.accountValueCalculator.NetValue(ctx)
if err != nil {
log.WithError(err).Errorf("unable to update account")
return
}

s.logger.Infof("hedge session net value ~= %f USD", netValue.Float64())
}
}
}

func (s *Strategy) hedgeWorker(ctx context.Context) {
ticker := time.NewTicker(util.MillisecondsJitter(s.HedgeInterval.Duration(), 200))
defer ticker.Stop()
Expand Down Expand Up @@ -1008,6 +1078,8 @@ func (s *Strategy) CrossRun(
return fmt.Errorf("maker session market %s is not defined", s.Symbol)
}

s.accountValueCalculator = bbgo.NewAccountValueCalculator(s.sourceSession, s.sourceMarket.QuoteCurrency)

indicators := s.sourceSession.Indicators(s.Symbol)

s.boll = indicators.BOLL(types.IntervalWindow{
Expand Down Expand Up @@ -1164,6 +1236,7 @@ func (s *Strategy) CrossRun(
go s.tradeRecover(ctx)
}

go s.accountUpdater(ctx)
go s.hedgeWorker(ctx)
go s.quoteWorker(ctx)

Expand Down

0 comments on commit 1d6282a

Please sign in to comment.