diff --git a/pkg/strategy/bollmaker/strategy.go b/pkg/strategy/bollmaker/strategy.go index cbf313d94..da5054e32 100644 --- a/pkg/strategy/bollmaker/strategy.go +++ b/pkg/strategy/bollmaker/strategy.go @@ -3,12 +3,11 @@ package bollmaker import ( "context" "fmt" + "github.com/c9s/bbgo/pkg/indicator" "math" "sync" "time" - "github.com/c9s/bbgo/pkg/indicator" - "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -37,8 +36,36 @@ func init() { bbgo.RegisterStrategy(ID, &Strategy{}) } +// NewStack returns a new position stack. +func NewStack() *PositionStack { + return &PositionStack{} +} + +// Stack is a basic LIFO stack that resizes as needed. +type PositionStack struct { + positions []*types.Position +} + +// Push adds a node to the stack. +func (s *PositionStack) Length() int { + return len(s.positions) +} + +// Push adds a node to the stack. +func (s *PositionStack) Push(p *types.Position) { + s.positions = append(s.positions, p) +} + +// Pop removes and returns a node from the stack in last to first order. +func (s *PositionStack) Pop() *types.Position { + if len(s.positions) == 0 { + return nil + } + return s.positions[len(s.positions)-1] +} + type State struct { - Position *types.Position `json:"position,omitempty"` + Position types.Position `json:"position,omitempty"` ProfitStats bbgo.ProfitStats `json:"profitStats,omitempty"` } @@ -198,7 +225,7 @@ func (s *Strategy) Validate() error { } func (s *Strategy) CurrentPosition() *types.Position { - return s.state.Position + return &s.state.Position } func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error { @@ -263,9 +290,9 @@ func (s *Strategy) LoadState() error { } // if position is nil, we need to allocate a new position for calculation - if s.state.Position == nil { - s.state.Position = types.NewPositionFromMarket(s.market) - } + //if s.state.Position == nil { + s.state.Position = *types.NewPositionFromMarket(s.market) + //} // init profit states s.state.ProfitStats.Symbol = s.market.Symbol @@ -518,6 +545,8 @@ func (s *Strategy) adjustOrderQuantity(submitOrder types.SubmitOrder) types.Subm } func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + PositionStack := NewStack() + if s.DisableShort { s.Long = &[]bool{true}[0] } @@ -567,8 +596,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.orderStore = bbgo.NewOrderStore(s.Symbol) s.orderStore.BindStream(session.UserDataStream) - - s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, s.state.Position, s.orderStore) + s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, &s.state.Position, s.orderStore) s.tradeCollector.OnProfit(func(trade types.Trade, profit fixedpoint.Value, netProfit fixedpoint.Value) { log.Infof("generated profit: %v", profit) p := bbgo.Profit{ @@ -626,6 +654,29 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se log.WithError(err).Errorf("graceful cancel order error") } + log.Infof("trade collector position: %v", s.tradeCollector.Position()) + + if kline.Close.Float64() < s.state.Position.AverageCost.Float64()*0.8 { + p := s.state.Position + PositionStack.Push(&p) + s.state.Position.Reset() + //s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, s.state.Position, s.orderStore) + } + + if kline.Close.Float64() > s.state.Position.AverageCost.Float64()*1.2 { + if s.state.Position.GetBase().Float64() > 0 && s.state.Position.GetBase().Float64() < s.Quantity.Float64() { + if PositionStack.Length() > 1 { + s.ClosePosition(ctx, 100.0) + s.state.Position.Reset() + p := PositionStack.Pop() + s.state.Position = *p + } + } + } + if PositionStack.Length() > 1 { + log.Infof("position stack: %v, length: %d, current position: %v", PositionStack.positions, PositionStack.Length(), s.state.Position) + } + // check if there is a canceled order had partially filled. s.tradeCollector.Process()