mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 23:05:15 +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
|
maxAmount: 1_000 # max amount to buy or sell per order
|
||||||
orderType: LIMIT_MAKER # LIMIT, LIMIT_MAKER or MARKET
|
orderType: LIMIT_MAKER # LIMIT, LIMIT_MAKER or MARKET
|
||||||
dryRun: false
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ type Strategy struct {
|
||||||
MaxAmount fixedpoint.Value `json:"maxAmount"` // max amount to buy or sell per order
|
MaxAmount fixedpoint.Value `json:"maxAmount"` // max amount to buy or sell per order
|
||||||
OrderType types.OrderType `json:"orderType"`
|
OrderType types.OrderType `json:"orderType"`
|
||||||
DryRun bool `json:"dryRun"`
|
DryRun bool `json:"dryRun"`
|
||||||
|
OnStart bool `json:"onStart"` // rebalance on start
|
||||||
|
|
||||||
PositionMap PositionMap `persistence:"positionMap"`
|
PositionMap PositionMap `persistence:"positionMap"`
|
||||||
ProfitStatsMap ProfitStatsMap `persistence:"profitStatsMap"`
|
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 = bbgo.NewActiveOrderBook("")
|
||||||
s.activeOrderBook.BindStream(s.session.UserDataStream)
|
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.session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||||
s.rebalance(ctx)
|
s.rebalance(ctx)
|
||||||
})
|
})
|
||||||
|
@ -133,12 +140,17 @@ func (s *Strategy) rebalance(ctx context.Context) {
|
||||||
log.WithError(err).Errorf("failed to cancel orders")
|
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 {
|
for _, order := range submitOrders {
|
||||||
log.Infof("generated submit order: %s", order.String())
|
log.Infof("generated submit order: %s", order.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.DryRun {
|
if s.DryRun {
|
||||||
|
log.Infof("dry run, not submitting orders")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +162,7 @@ func (s *Strategy) rebalance(ctx context.Context) {
|
||||||
s.activeOrderBook.Add(createdOrders...)
|
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)
|
m := make(types.ValueMap)
|
||||||
for currency := range s.TargetWeights {
|
for currency := range s.TargetWeights {
|
||||||
if currency == s.QuoteCurrency {
|
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)
|
ticker, err := s.session.Exchange.QueryTicker(ctx, currency+s.QuoteCurrency)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("failed to query tickers")
|
return nil, err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
func (s *Strategy) balances() (types.BalanceMap, error) {
|
||||||
m := make(types.ValueMap)
|
m := make(types.BalanceMap)
|
||||||
|
|
||||||
balances := s.session.GetAccount().Balances()
|
balances := s.session.GetAccount().Balances()
|
||||||
for currency := range s.TargetWeights {
|
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
|
}
|
||||||
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) generateSubmitOrders(ctx context.Context) (submitOrders []types.SubmitOrder) {
|
func (s *Strategy) generateSubmitOrders(ctx context.Context) (submitOrders []types.SubmitOrder, err error) {
|
||||||
prices := s.prices(ctx)
|
prices, err := s.prices(ctx)
|
||||||
marketValues := prices.Mul(s.quantities())
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
balances, err := s.balances()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
marketValues := prices.Mul(balanceToTotal(balances))
|
||||||
currentWeights := marketValues.Normalize()
|
currentWeights := marketValues.Normalize()
|
||||||
|
|
||||||
for currency, targetWeight := range s.TargetWeights {
|
for currency, targetWeight := range s.TargetWeights {
|
||||||
|
@ -221,8 +241,9 @@ func (s *Strategy) generateSubmitOrders(ctx context.Context) (submitOrders []typ
|
||||||
quantity = quantity.Abs()
|
quantity = quantity.Abs()
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.MaxAmount.Sign() > 0 {
|
maxAmount := s.adjustMaxAmountByBalance(side, currency, currentPrice, balances)
|
||||||
quantity = bbgo.AdjustQuantityByMaxAmount(quantity, currentPrice, s.MaxAmount)
|
if maxAmount.Sign() > 0 {
|
||||||
|
quantity = bbgo.AdjustQuantityByMaxAmount(quantity, currentPrice, maxAmount)
|
||||||
log.Infof("adjust the quantity %v (%s %s @ %v) by max amount %v",
|
log.Infof("adjust the quantity %v (%s %s @ %v) by max amount %v",
|
||||||
quantity,
|
quantity,
|
||||||
symbol,
|
symbol,
|
||||||
|
@ -241,10 +262,12 @@ func (s *Strategy) generateSubmitOrders(ctx context.Context) (submitOrders []typ
|
||||||
Price: currentPrice,
|
Price: currentPrice,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ok := s.checkMinimalOrderQuantity(order); ok {
|
||||||
submitOrders = append(submitOrders, order)
|
submitOrders = append(submitOrders, order)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return submitOrders
|
return submitOrders, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) symbols() (symbols []string) {
|
func (s *Strategy) symbols() (symbols []string) {
|
||||||
|
@ -268,3 +291,44 @@ func (s *Strategy) markets() ([]types.Market, error) {
|
||||||
}
|
}
|
||||||
return markets, nil
|
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