mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
Merge pull request #1104 from c9s/narumi/rebalance/balance
fix: rebalance: adjust max amount by balance
This commit is contained in:
commit
cddf3570f2
|
@ -39,3 +39,4 @@ exchangeStrategies:
|
|||
maxAmount: 1_000 # max amount to buy or sell per order
|
||||
orderType: LIMIT_MAKER # LIMIT, LIMIT_MAKER or MARKET
|
||||
dryRun: false
|
||||
onStart: false
|
||||
|
|
|
@ -55,7 +55,7 @@ func (m GeneralOrderExecutorMap) SubmitOrders(ctx context.Context, submitOrders
|
|||
return nil, fmt.Errorf("order executor not found for symbol %s", submitOrder.Symbol)
|
||||
}
|
||||
|
||||
createdOrders, err := orderExecutor.SubmitOrders(ctx)
|
||||
createdOrders, err := orderExecutor.SubmitOrders(ctx, submitOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ type Strategy struct {
|
|||
MaxAmount fixedpoint.Value `json:"maxAmount"` // max amount to buy or sell per order
|
||||
OrderType types.OrderType `json:"orderType"`
|
||||
DryRun bool `json:"dryRun"`
|
||||
OnStart bool `json:"onStart"` // rebalance on start
|
||||
|
||||
PositionMap PositionMap `persistence:"positionMap"`
|
||||
ProfitStatsMap ProfitStatsMap `persistence:"profitStatsMap"`
|
||||
|
@ -114,6 +115,12 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
|
|||
s.activeOrderBook = bbgo.NewActiveOrderBook("")
|
||||
s.activeOrderBook.BindStream(s.session.UserDataStream)
|
||||
|
||||
session.UserDataStream.OnStart(func() {
|
||||
if s.OnStart {
|
||||
s.rebalance(ctx)
|
||||
}
|
||||
})
|
||||
|
||||
s.session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
s.rebalance(ctx)
|
||||
})
|
||||
|
@ -133,12 +140,17 @@ func (s *Strategy) rebalance(ctx context.Context) {
|
|||
log.WithError(err).Errorf("failed to cancel orders")
|
||||
}
|
||||
|
||||
submitOrders := s.generateSubmitOrders(ctx)
|
||||
submitOrders, err := s.generateSubmitOrders(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to generate submit orders")
|
||||
return
|
||||
}
|
||||
for _, order := range submitOrders {
|
||||
log.Infof("generated submit order: %s", order.String())
|
||||
}
|
||||
|
||||
if s.DryRun {
|
||||
log.Infof("dry run, not submitting orders")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -150,7 +162,7 @@ func (s *Strategy) rebalance(ctx context.Context) {
|
|||
s.activeOrderBook.Add(createdOrders...)
|
||||
}
|
||||
|
||||
func (s *Strategy) prices(ctx context.Context) types.ValueMap {
|
||||
func (s *Strategy) prices(ctx context.Context) (types.ValueMap, error) {
|
||||
m := make(types.ValueMap)
|
||||
for currency := range s.TargetWeights {
|
||||
if currency == s.QuoteCurrency {
|
||||
|
@ -160,29 +172,37 @@ func (s *Strategy) prices(ctx context.Context) types.ValueMap {
|
|||
|
||||
ticker, err := s.session.Exchange.QueryTicker(ctx, currency+s.QuoteCurrency)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to query tickers")
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m[currency] = ticker.Last
|
||||
m[currency] = ticker.Buy.Add(ticker.Sell).Div(fixedpoint.NewFromFloat(2.0))
|
||||
}
|
||||
return m
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (s *Strategy) quantities() types.ValueMap {
|
||||
m := make(types.ValueMap)
|
||||
|
||||
func (s *Strategy) balances() (types.BalanceMap, error) {
|
||||
m := make(types.BalanceMap)
|
||||
balances := s.session.GetAccount().Balances()
|
||||
for currency := range s.TargetWeights {
|
||||
m[currency] = balances[currency].Total()
|
||||
balance, ok := balances[currency]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no balance for %s", currency)
|
||||
}
|
||||
m[currency] = balance
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
return m
|
||||
func (s *Strategy) generateSubmitOrders(ctx context.Context) (submitOrders []types.SubmitOrder, err error) {
|
||||
prices, err := s.prices(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *Strategy) generateSubmitOrders(ctx context.Context) (submitOrders []types.SubmitOrder) {
|
||||
prices := s.prices(ctx)
|
||||
marketValues := prices.Mul(s.quantities())
|
||||
balances, err := s.balances()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
marketValues := prices.Mul(balanceToTotal(balances))
|
||||
currentWeights := marketValues.Normalize()
|
||||
|
||||
for currency, targetWeight := range s.TargetWeights {
|
||||
|
@ -221,8 +241,9 @@ func (s *Strategy) generateSubmitOrders(ctx context.Context) (submitOrders []typ
|
|||
quantity = quantity.Abs()
|
||||
}
|
||||
|
||||
if s.MaxAmount.Sign() > 0 {
|
||||
quantity = bbgo.AdjustQuantityByMaxAmount(quantity, currentPrice, s.MaxAmount)
|
||||
maxAmount := s.adjustMaxAmountByBalance(side, currency, currentPrice, balances)
|
||||
if maxAmount.Sign() > 0 {
|
||||
quantity = bbgo.AdjustQuantityByMaxAmount(quantity, currentPrice, maxAmount)
|
||||
log.Infof("adjust the quantity %v (%s %s @ %v) by max amount %v",
|
||||
quantity,
|
||||
symbol,
|
||||
|
@ -241,10 +262,12 @@ func (s *Strategy) generateSubmitOrders(ctx context.Context) (submitOrders []typ
|
|||
Price: currentPrice,
|
||||
}
|
||||
|
||||
if ok := s.checkMinimalOrderQuantity(order); ok {
|
||||
submitOrders = append(submitOrders, order)
|
||||
}
|
||||
}
|
||||
|
||||
return submitOrders
|
||||
return submitOrders, err
|
||||
}
|
||||
|
||||
func (s *Strategy) symbols() (symbols []string) {
|
||||
|
@ -268,3 +291,44 @@ func (s *Strategy) markets() ([]types.Market, error) {
|
|||
}
|
||||
return markets, nil
|
||||
}
|
||||
|
||||
func (s *Strategy) adjustMaxAmountByBalance(side types.SideType, currency string, currentPrice fixedpoint.Value, balances types.BalanceMap) fixedpoint.Value {
|
||||
var maxAmount fixedpoint.Value
|
||||
|
||||
switch side {
|
||||
case types.SideTypeBuy:
|
||||
maxAmount = balances[s.QuoteCurrency].Available
|
||||
case types.SideTypeSell:
|
||||
maxAmount = balances[currency].Available.Mul(currentPrice)
|
||||
default:
|
||||
log.Errorf("unknown side type: %s", side)
|
||||
return fixedpoint.Zero
|
||||
}
|
||||
|
||||
if s.MaxAmount.Sign() > 0 {
|
||||
maxAmount = fixedpoint.Min(s.MaxAmount, maxAmount)
|
||||
}
|
||||
|
||||
return maxAmount
|
||||
}
|
||||
|
||||
func (s *Strategy) checkMinimalOrderQuantity(order types.SubmitOrder) bool {
|
||||
if order.Quantity.Compare(order.Market.MinQuantity) < 0 {
|
||||
log.Infof("order quantity is too small: %f < %f", order.Quantity.Float64(), order.Market.MinQuantity.Float64())
|
||||
return false
|
||||
}
|
||||
|
||||
if order.Quantity.Mul(order.Price).Compare(order.Market.MinNotional) < 0 {
|
||||
log.Infof("order min notional is too small: %f < %f", order.Quantity.Mul(order.Price).Float64(), order.Market.MinNotional.Float64())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func balanceToTotal(balances types.BalanceMap) types.ValueMap {
|
||||
m := make(types.ValueMap)
|
||||
for _, b := range balances {
|
||||
m[b.Currency] = b.Total()
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user