Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: improve: bollmaker: position stack #641

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 32 additions & 25 deletions config/bollmaker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ backtest:
# for testing max draw down (MDD) at 03-12
# see here for more details
# https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
startTime: "2022-01-01"
endTime: "2022-05-12"
startTime: "2022-05-01"
endTime: "2022-05-31"
sessions:
- binance
symbols:
Expand All @@ -26,7 +26,7 @@ backtest:
binance:
balances:
ETH: 0.0
USDT: 10_000.0
USDT: 100_000.0

exchangeStrategies:

Expand All @@ -38,7 +38,14 @@ exchangeStrategies:
interval: 1m

# quantity is the base order quantity for your buy/sell order.
quantity: 0.05
# quantity: 0.05
amount: 20

# Position Stack, with longer stack length, may need more capital.
# Push position in stack is initiating a position to calculate base, average cost, etc.
# Pop position in stack is loading a previous position back.
pushThreshold: 10%
# popThreshold : 1%
c9s marked this conversation as resolved.
Show resolved Hide resolved

# useTickerPrice use the ticker api to get the mid price instead of the closed kline price.
# The back-test engine is kline-based, so the ticker price api is not supported.
Expand Down Expand Up @@ -103,7 +110,7 @@ exchangeStrategies:
domain: [ -1, 1 ]
# when in down band, holds 1.0 by maximum
# when in up band, holds 0.05 by maximum
range: [ 10.0, 1.0 ]
range: [10.0, 1.0 ]

# DisableShort means you can don't want short position during the market making
# THe short here means you might sell some of your existing inventory.
Expand Down Expand Up @@ -136,25 +143,25 @@ exchangeStrategies:
# Set up your stop order, this is optional
# sometimes the stop order might decrease your total profit.
# you can setup multiple stop,
stops:
# stops:
# use trailing stop order
- trailingStop:
# callbackRate: when the price reaches -1% from the previous highest, we trigger the stop
callbackRate: 5.1%

# closePosition is how much position do you want to close
closePosition: 20%

# minProfit is how much profit you want to take.
# if you set this option, your stop will only be triggered above the average cost.
minProfit: 5%

# interval is the time interval for checking your stop
interval: 1m

# virtual means we don't place a a REAL stop order
# when virtual is on
# the strategy won't place a REAL stop order, instead if watches the close price,
# and if the condition matches, it submits a market order to close your position.
virtual: true
# - trailingStop:
# # callbackRate: when the price reaches -1% from the previous highest, we trigger the stop
# callbackRate: 5.1%
#
# # closePosition is how much position do you want to close
# closePosition: 20%
#
# # minProfit is how much profit you want to take.
# # if you set this option, your stop will only be triggered above the average cost.
# minProfit: 5%
#
# # interval is the time interval for checking your stop
# interval: 1m
#
# # virtual means we don't place a a REAL stop order
# # when virtual is on
# # the strategy won't place a REAL stop order, instead if watches the close price,
# # and if the condition matches, it submits a market order to close your position.
# virtual: true

6 changes: 3 additions & 3 deletions pkg/bbgo/smart_stops.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ func (c *TrailingStopController) Subscribe(session *ExchangeSession) {

func (c *TrailingStopController) Run(ctx context.Context, session *ExchangeSession, tradeCollector *TradeCollector) {
// store the position
c.position = tradeCollector.Position()
c.position = tradeCollector.Position().(*types.Position)
c.averageCost = c.position.AverageCost

// Use trade collector to get the position update event
tradeCollector.OnPositionUpdate(func(position *types.Position) {
tradeCollector.OnPositionUpdate(func(position types.PositionInterface) {
// update average cost if we have it.
c.averageCost = position.AverageCost
c.averageCost = position.(*types.Position).AverageCost
})

session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
Expand Down
8 changes: 4 additions & 4 deletions pkg/bbgo/tradecollector.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ type TradeCollector struct {

tradeStore *TradeStore
tradeC chan types.Trade
position *types.Position
position types.PositionInterface
orderStore *OrderStore
doneTrades map[types.TradeKey]struct{}

recoverCallbacks []func(trade types.Trade)
tradeCallbacks []func(trade types.Trade, profit, netProfit fixedpoint.Value)
positionUpdateCallbacks []func(position *types.Position)
positionUpdateCallbacks []func(position types.PositionInterface)
profitCallbacks []func(trade types.Trade, profit, netProfit fixedpoint.Value)
}

func NewTradeCollector(symbol string, position *types.Position, orderStore *OrderStore) *TradeCollector {
func NewTradeCollector(symbol string, position types.PositionInterface, orderStore *OrderStore) *TradeCollector {
return &TradeCollector{
Symbol: symbol,
orderSig: sigchan.New(1),
Expand All @@ -47,7 +47,7 @@ func (c *TradeCollector) OrderStore() *OrderStore {
}

// Position returns the position used by the trade collector
func (c *TradeCollector) Position() *types.Position {
func (c *TradeCollector) Position() types.PositionInterface {
return c.position
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/bbgo/tradecollector_callbacks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 29 additions & 8 deletions pkg/strategy/bollmaker/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,11 @@ type Strategy struct {
state *State

// persistence fields
Position *types.Position `json:"position,omitempty" persistence:"position"`
ProfitStats *types.ProfitStats `json:"profitStats,omitempty" persistence:"profit_stats"`
Position *types.PositionStack `json:"position,omitempty" persistence:"position"`
ProfitStats *types.ProfitStats `json:"profitStats,omitempty" persistence:"profit_stats"`

PushThreshold fixedpoint.Value `json:"pushThreshold,omitempty"`
PopThreshold fixedpoint.Value `json:"popThreshold,omitempty"`

activeMakerOrders *bbgo.LocalActiveOrderBook
orderStore *bbgo.OrderStore
Expand Down Expand Up @@ -289,7 +292,7 @@ func (s *Strategy) Validate() error {
return nil
}

func (s *Strategy) CurrentPosition() *types.Position {
func (s *Strategy) CurrentPosition() *types.PositionStack {
return s.Position
}

Expand Down Expand Up @@ -652,9 +655,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
if s.Position == nil {
// fallback to the legacy position struct in the state
if s.state != nil && s.state.Position != nil {
s.Position = s.state.Position
s.Position.Position = s.state.Position
} else {
s.Position = types.NewPositionFromMarket(s.Market)
s.Position = types.NewPositionStackFromMarket(s.Market)
}
}

Expand Down Expand Up @@ -699,7 +702,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.ProfitStats.AddTrade(trade)

if profit.Compare(fixedpoint.Zero) == 0 {
s.Environment.RecordPosition(s.Position, trade, nil)
s.Environment.RecordPosition(s.Position.Position, trade, nil)
} else {
log.Infof("%s generated profit: %v", s.Symbol, profit)
p := s.Position.NewProfit(trade, profit, netProfit)
Expand All @@ -710,11 +713,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.ProfitStats.AddProfit(p)
s.Notify(&s.ProfitStats)

s.Environment.RecordPosition(s.Position, trade, &p)
s.Environment.RecordPosition(s.Position.Position, trade, &p)
}
})

s.tradeCollector.OnPositionUpdate(func(position *types.Position) {
s.tradeCollector.OnPositionUpdate(func(position types.PositionInterface) {
log.Infof("position changed: %s", s.Position)
s.Notify(s.Position)
})
Expand Down Expand Up @@ -772,7 +775,25 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
if err := s.activeMakerOrders.GracefulCancel(ctx, s.session.Exchange); err != nil {
log.WithError(err).Errorf("graceful cancel order error")
}
//log.Error(len(s.Position.Stack), s.Position.AverageCost, kline.Close)

if s.Position.Position.AverageCost.Div(kline.Close).Compare(fixedpoint.One.Add(s.PushThreshold)) > 0 {
log.Errorf("push")
log.Error(s.Position)
s.Position = s.Position.Push(types.NewPositionFromMarket(s.Market))
}
// &&
if len(s.Position.Stack) > 1 && s.Position.Stack[len(s.Position.Stack)-2].AverageCost.Compare(kline.Close) < 0 && s.Market.IsDustQuantity(s.Position.Position.GetBase(), kline.Close) {
log.Errorf("pop")
log.Error(s.Position)
s.Position = s.Position.Pop()
}
//if s.Position.AverageCost.Div(kline.Close).Compare(fixedpoint.One.Sub(s.PopThreshold)) < 0 && && !s.Position.AverageCost.IsZero() {
// //log.Error(len(s.Position.Stack), s.Position.AverageCost, kline.Close)
// log.Errorf("pop")
// s.ClosePosition(ctx, fixedpoint.One)
// s.Position = s.Position.Pop()
//}
// check if there is a canceled order had partially filled.
s.tradeCollector.Process()

Expand Down
2 changes: 1 addition & 1 deletion pkg/strategy/ewoDgtrd/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
})

s.tradeCollector.OnPositionUpdate(func(position *types.Position) {
s.tradeCollector.OnPositionUpdate(func(position types.PositionInterface) {
log.Infof("position changed: %s", position)
s.Notify(s.Position)
})
Expand Down
2 changes: 1 addition & 1 deletion pkg/strategy/grid/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
*/

s.tradeCollector.OnPositionUpdate(func(position *types.Position) {
s.tradeCollector.OnPositionUpdate(func(position types.PositionInterface) {
s.Notifiability.Notify(position)
})
s.tradeCollector.BindStream(session.UserDataStream)
Expand Down
2 changes: 1 addition & 1 deletion pkg/strategy/pivotshort/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
})

s.tradeCollector.OnPositionUpdate(func(position *types.Position) {
s.tradeCollector.OnPositionUpdate(func(position types.PositionInterface) {
log.Infof("position changed: %s", s.Position)
s.Notify(s.Position)
})
Expand Down
8 changes: 4 additions & 4 deletions pkg/strategy/support/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -499,13 +499,13 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se

if !s.TrailingStopTarget.TrailingStopCallbackRatio.IsZero() {
// Update trailing stop when the position changes
s.tradeCollector.OnPositionUpdate(func(position *types.Position) {
s.tradeCollector.OnPositionUpdate(func(position types.PositionInterface) {
// StrategyController
if s.Status != types.StrategyStatusRunning {
return
}

if position.Base.Compare(s.Market.MinQuantity) > 0 { // Update order if we have a position
if position.(*types.Position).Base.Compare(s.Market.MinQuantity) > 0 { // Update order if we have a position
// Cancel the original order
if err := s.cancelOrder(s.trailingStopControl.OrderID, ctx, orderExecutor); err != nil {
log.WithError(err).Errorf("Can not cancel the original trailing stop order!")
Expand All @@ -515,12 +515,12 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
// Calculate minimum target price
var minTargetPrice = fixedpoint.Zero
if s.trailingStopControl.minimumProfitPercentage.Sign() > 0 {
minTargetPrice = position.AverageCost.Mul(fixedpoint.One.Add(s.trailingStopControl.minimumProfitPercentage))
minTargetPrice = position.(*types.Position).AverageCost.Mul(fixedpoint.One.Add(s.trailingStopControl.minimumProfitPercentage))
}

// Place new order if the target price is higher than the minimum target price
if s.trailingStopControl.IsHigherThanMin(minTargetPrice) {
orderForm := s.trailingStopControl.GenerateStopOrder(position.Base)
orderForm := s.trailingStopControl.GenerateStopOrder(position.(*types.Position).Base)
orders, err := s.submitOrders(ctx, orderExecutor, orderForm)
if err != nil {
log.WithError(err).Error("submit profit trailing stop order error")
Expand Down
3 changes: 1 addition & 2 deletions pkg/strategy/wall/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
})

s.tradeCollector.OnPositionUpdate(func(position *types.Position) {
s.tradeCollector.OnPositionUpdate(func(position types.PositionInterface) {
log.Infof("position changed: %s", s.Position)
s.Notify(s.Position)
})
Expand Down Expand Up @@ -340,7 +340,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
log.WithError(err).Errorf("can not place order")
}


if err := s.activeAdjustmentOrders.GracefulCancel(ctx, s.session.Exchange); err != nil {
log.WithError(err).Errorf("graceful cancel order error")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/strategy/xmaker/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ func (s *Strategy) CrossRun(ctx context.Context, orderExecutionRouter bbgo.Order
}
})

s.tradeCollector.OnPositionUpdate(func(position *types.Position) {
s.tradeCollector.OnPositionUpdate(func(position types.PositionInterface) {
s.Notifiability.Notify(position)
})
s.tradeCollector.OnRecover(func(trade types.Trade) {
Expand Down
Loading