mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
Compare commits
11 Commits
cff7103ece
...
f12ba1adb9
Author | SHA1 | Date | |
---|---|---|---|
|
f12ba1adb9 | ||
|
294e529a98 | ||
|
f30aca1b5a | ||
|
f9b9832fff | ||
|
2bf1072977 | ||
|
4d1c357c3d | ||
|
a4833524cf | ||
|
ed073264f1 | ||
|
ad6056834e | ||
|
8b1306a6a6 | ||
|
d85da78e17 |
|
@ -12,12 +12,16 @@ type Quota struct {
|
|||
Locked fixedpoint.Value
|
||||
}
|
||||
|
||||
// Add adds the fund to the available quota
|
||||
func (q *Quota) Add(fund fixedpoint.Value) {
|
||||
q.mu.Lock()
|
||||
q.Available = q.Available.Add(fund)
|
||||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
// Lock locks the fund from the available quota
|
||||
// returns true if the fund is locked successfully
|
||||
// returns false if the fund is not enough
|
||||
func (q *Quota) Lock(fund fixedpoint.Value) bool {
|
||||
if fund.Compare(q.Available) > 0 {
|
||||
return false
|
||||
|
@ -31,12 +35,15 @@ func (q *Quota) Lock(fund fixedpoint.Value) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// Commit commits the locked fund
|
||||
func (q *Quota) Commit() {
|
||||
q.mu.Lock()
|
||||
q.Locked = fixedpoint.Zero
|
||||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
// Rollback rolls back the locked fund
|
||||
// this will move the locked fund to the available quota
|
||||
func (q *Quota) Rollback() {
|
||||
q.mu.Lock()
|
||||
q.Available = q.Available.Add(q.Locked)
|
||||
|
@ -44,12 +51,14 @@ func (q *Quota) Rollback() {
|
|||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
// QuotaTransaction is a transactional quota manager
|
||||
type QuotaTransaction struct {
|
||||
mu sync.Mutex
|
||||
BaseAsset Quota
|
||||
QuoteAsset Quota
|
||||
}
|
||||
|
||||
// Commit commits the transaction
|
||||
func (m *QuotaTransaction) Commit() bool {
|
||||
m.mu.Lock()
|
||||
m.BaseAsset.Commit()
|
||||
|
@ -58,6 +67,7 @@ func (m *QuotaTransaction) Commit() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// Rollback rolls back the transaction
|
||||
func (m *QuotaTransaction) Rollback() bool {
|
||||
m.mu.Lock()
|
||||
m.BaseAsset.Rollback()
|
||||
|
|
|
@ -119,6 +119,8 @@ type Strategy struct {
|
|||
// MaxExposurePosition defines the unhedged quantity of stop
|
||||
MaxExposurePosition fixedpoint.Value `json:"maxExposurePosition"`
|
||||
|
||||
MaxHedgeAccountLeverage fixedpoint.Value `json:"maxHedgeAccountLeverage"`
|
||||
|
||||
DisableHedge bool `json:"disableHedge"`
|
||||
|
||||
NotifyTrade bool `json:"notifyTrade"`
|
||||
|
@ -424,10 +426,10 @@ func (s *Strategy) updateQuote(ctx context.Context) {
|
|||
if s.CircuitBreaker != nil {
|
||||
now := time.Now()
|
||||
if reason, halted := s.CircuitBreaker.IsHalted(now); halted {
|
||||
s.logger.Warnf("[arbWorker] strategy is halted, reason: %s", reason)
|
||||
s.logger.Warnf("strategy %s is halted, reason: %s", ID, reason)
|
||||
|
||||
if s.circuitBreakerAlertLimiter.AllowN(now, 1) {
|
||||
bbgo.Notify("Strategy is halted, reason: %s", reason)
|
||||
bbgo.Notify("Strategy %s is halted, reason: %s", ID, reason)
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -436,6 +438,7 @@ func (s *Strategy) updateQuote(ctx context.Context) {
|
|||
|
||||
bestBid, bestAsk, hasPrice := s.book.BestBidAndAsk()
|
||||
if !hasPrice {
|
||||
s.logger.Warnf("no valid price, skip quoting")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -472,14 +475,21 @@ func (s *Strategy) updateQuote(ctx context.Context) {
|
|||
// check maker's balance quota
|
||||
// we load the balances from the account while we're generating the orders,
|
||||
// the balance may have a chance to be deducted by other strategies or manual orders submitted by the user
|
||||
makerBalances := s.makerSession.GetAccount().Balances()
|
||||
makerBalances := s.makerSession.GetAccount().Balances().NotZero()
|
||||
|
||||
s.logger.Infof("maker balances: %+v", makerBalances)
|
||||
|
||||
makerQuota := &bbgo.QuotaTransaction{}
|
||||
if b, ok := makerBalances[s.makerMarket.BaseCurrency]; ok {
|
||||
if b.Available.Compare(s.makerMarket.MinQuantity) > 0 {
|
||||
makerQuota.BaseAsset.Add(b.Available)
|
||||
} else {
|
||||
if s.makerMarket.IsDustQuantity(b.Available, s.lastPrice) {
|
||||
disableMakerAsk = true
|
||||
s.logger.Infof("%s maker ask disabled: insufficient base balance %s", s.Symbol, b.String())
|
||||
} else {
|
||||
makerQuota.BaseAsset.Add(b.Available)
|
||||
}
|
||||
} else {
|
||||
disableMakerAsk = true
|
||||
s.logger.Infof("%s maker ask disabled: base balance %s not found", s.Symbol, b.String())
|
||||
}
|
||||
|
||||
if b, ok := makerBalances[s.makerMarket.QuoteCurrency]; ok {
|
||||
|
@ -487,9 +497,15 @@ func (s *Strategy) updateQuote(ctx context.Context) {
|
|||
makerQuota.QuoteAsset.Add(b.Available)
|
||||
} else {
|
||||
disableMakerBid = true
|
||||
s.logger.Infof("%s maker bid disabled: insufficient quote balance %s", s.Symbol, b.String())
|
||||
}
|
||||
} else {
|
||||
disableMakerBid = true
|
||||
s.logger.Infof("%s maker bid disabled: quote balance %s not found", s.Symbol, b.String())
|
||||
}
|
||||
|
||||
s.logger.Infof("maker quota: %+v", makerQuota)
|
||||
|
||||
// if
|
||||
// 1) the source session is a margin session
|
||||
// 2) the min margin level is configured
|
||||
|
@ -503,6 +519,11 @@ func (s *Strategy) updateQuote(ctx context.Context) {
|
|||
!hedgeAccount.MarginLevel.IsZero() {
|
||||
|
||||
if hedgeAccount.MarginLevel.Compare(s.MinMarginLevel) < 0 {
|
||||
s.logger.Infof("hedge account margin level %s is less then the min margin level %s, calculating the borrowed positions",
|
||||
hedgeAccount.MarginLevel.String(),
|
||||
s.MinMarginLevel.String())
|
||||
|
||||
// TODO: should consider base asset debt as well.
|
||||
if quote, ok := hedgeAccount.Balance(s.sourceMarket.QuoteCurrency); ok {
|
||||
quoteDebt := quote.Debt()
|
||||
if quoteDebt.Sign() > 0 {
|
||||
|
@ -517,24 +538,46 @@ func (s *Strategy) updateQuote(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
} 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.QuoteAsset.Add(netQuote.Mul(creditBufferRatio))
|
||||
}
|
||||
}
|
||||
s.logger.Infof("hedge account margin level %s is greater than the min margin level %s, calculating the net value",
|
||||
hedgeAccount.MarginLevel.String(),
|
||||
s.MinMarginLevel.String())
|
||||
|
||||
if base, ok := hedgeAccount.Balance(s.sourceMarket.BaseCurrency); ok {
|
||||
netBase := base.Net()
|
||||
if netBase.Sign() > 0 {
|
||||
hedgeQuota.BaseAsset.Add(netBase.Mul(creditBufferRatio))
|
||||
netValueInUsd, calcErr := s.accountValueCalculator.NetValue(ctx)
|
||||
if calcErr != nil {
|
||||
s.logger.WithError(calcErr).Errorf("unable to calculate the net value")
|
||||
} else {
|
||||
// calculate credit buffer
|
||||
s.logger.Infof("hedge account net value in usd: %f", netValueInUsd.Float64())
|
||||
|
||||
maximumValueInUsd := netValueInUsd.Mul(s.MaxHedgeAccountLeverage)
|
||||
|
||||
s.logger.Infof("hedge account maximum leveraged value in usd: %f (%f x)", maximumValueInUsd.Float64(), s.MaxHedgeAccountLeverage.Float64())
|
||||
|
||||
if quote, ok := hedgeAccount.Balance(s.sourceMarket.QuoteCurrency); ok {
|
||||
debt := quote.Debt()
|
||||
quota := maximumValueInUsd.Sub(debt)
|
||||
|
||||
s.logger.Infof("hedge account quote balance: %s, debt: %s, quota: %s",
|
||||
quote.String(),
|
||||
debt.String(),
|
||||
quota.String())
|
||||
|
||||
hedgeQuota.QuoteAsset.Add(quota)
|
||||
}
|
||||
|
||||
if base, ok := hedgeAccount.Balance(s.sourceMarket.BaseCurrency); ok {
|
||||
debt := base.Debt()
|
||||
quota := maximumValueInUsd.Div(bestAsk.Price).Sub(debt)
|
||||
|
||||
s.logger.Infof("hedge account base balance: %s, debt: %s, quota: %s",
|
||||
base.String(),
|
||||
debt.String(),
|
||||
quota.String())
|
||||
|
||||
hedgeQuota.BaseAsset.Add(quota)
|
||||
}
|
||||
}
|
||||
// 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,
|
||||
|
@ -544,13 +587,13 @@ func (s *Strategy) updateQuote(ctx context.Context) {
|
|||
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())
|
||||
s.logger.Warnf("%s maker bid disabled: insufficient hedge 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())
|
||||
s.logger.Warnf("%s maker bid disabled: insufficient hedge base balance %s", s.Symbol, b.String())
|
||||
disableMakerBid = true
|
||||
}
|
||||
}
|
||||
|
@ -563,17 +606,16 @@ func (s *Strategy) updateQuote(ctx context.Context) {
|
|||
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())
|
||||
s.logger.Warnf("%s maker ask disabled: insufficient hedge 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())
|
||||
s.logger.Warnf("%s maker ask disabled: insufficient hedge quote balance %s", s.Symbol, b.String())
|
||||
disableMakerAsk = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if max exposure position is configured, we should not:
|
||||
|
@ -811,6 +853,7 @@ func (s *Strategy) updateQuote(ctx context.Context) {
|
|||
createdOrders, errIdx, err := bbgo.BatchPlaceOrder(ctx, s.makerSession.Exchange, orderCreateCallback, formattedOrders...)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("unable to place maker orders: %+v", formattedOrders)
|
||||
return
|
||||
}
|
||||
|
||||
openOrderBidExposureInUsdMetrics.With(s.metricsLabels).Set(bidExposureInUsd.Float64())
|
||||
|
@ -1009,6 +1052,10 @@ func (s *Strategy) Defaults() error {
|
|||
s.MinMarginLevel = fixedpoint.NewFromFloat(3.0)
|
||||
}
|
||||
|
||||
if s.MaxHedgeAccountLeverage.IsZero() {
|
||||
s.MaxHedgeAccountLeverage = fixedpoint.NewFromFloat(1.2)
|
||||
}
|
||||
|
||||
if s.BidMargin.IsZero() {
|
||||
if !s.Margin.IsZero() {
|
||||
s.BidMargin = s.Margin
|
||||
|
@ -1207,7 +1254,7 @@ func (s *Strategy) CrossRun(
|
|||
|
||||
// restore state
|
||||
s.groupID = util.FNV32(instanceID)
|
||||
log.Infof("using group id %d from fnv(%s)", s.groupID, instanceID)
|
||||
s.logger.Infof("using group id %d from fnv(%s)", s.groupID, instanceID)
|
||||
|
||||
configLabels := prometheus.Labels{"strategy_id": s.InstanceID(), "strategy_type": ID, "symbol": s.Symbol}
|
||||
configNumOfLayersMetrics.With(configLabels).Set(float64(s.NumLayers))
|
||||
|
@ -1219,8 +1266,12 @@ func (s *Strategy) CrossRun(
|
|||
s.Position = types.NewPositionFromMarket(s.makerMarket)
|
||||
s.Position.Strategy = ID
|
||||
s.Position.StrategyInstanceID = instanceID
|
||||
} else {
|
||||
s.Position.Strategy = ID
|
||||
s.Position.StrategyInstanceID = instanceID
|
||||
}
|
||||
|
||||
s.Position.UpdateMetrics()
|
||||
bbgo.Notify("xmaker: %s position is restored", s.Symbol, s.Position)
|
||||
|
||||
if s.ProfitStats == nil {
|
||||
|
@ -1267,9 +1318,8 @@ func (s *Strategy) CrossRun(
|
|||
return errors.New("tradesSince time can not be zero")
|
||||
}
|
||||
|
||||
makerMarket, _ := makerSession.Market(s.Symbol)
|
||||
position := types.NewPositionFromMarket(makerMarket)
|
||||
profitStats := types.NewProfitStats(makerMarket)
|
||||
position := types.NewPositionFromMarket(s.makerMarket)
|
||||
profitStats := types.NewProfitStats(s.makerMarket)
|
||||
|
||||
fixer := common.NewProfitFixer()
|
||||
// fixer.ConverterManager = s.ConverterManager
|
||||
|
@ -1284,7 +1334,7 @@ func (s *Strategy) CrossRun(
|
|||
fixer.AddExchange(sourceSession.Name, ss)
|
||||
}
|
||||
|
||||
if err2 := fixer.Fix(ctx, makerMarket.Symbol,
|
||||
if err2 := fixer.Fix(ctx, s.makerMarket.Symbol,
|
||||
s.ProfitFixerConfig.TradesSince.Time(),
|
||||
time.Now(),
|
||||
profitStats,
|
||||
|
|
|
@ -656,6 +656,12 @@ func (p *Position) AddTrade(td Trade) (profit fixedpoint.Value, netProfit fixedp
|
|||
return fixedpoint.Zero, fixedpoint.Zero, false
|
||||
}
|
||||
|
||||
func (p *Position) UpdateMetrics() {
|
||||
p.Lock()
|
||||
p.updateMetrics()
|
||||
p.Unlock()
|
||||
}
|
||||
|
||||
func (p *Position) updateMetrics() {
|
||||
// update the position metrics only if the position defines the strategy ID
|
||||
if p.StrategyInstanceID == "" || p.Strategy == "" {
|
||||
|
|
Loading…
Reference in New Issue
Block a user