mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 00:35:15 +00:00
Merge pull request #798 from c9s/feature/trailingstop
fix: fix trailingstop and add long position test case
This commit is contained in:
commit
f9f64f7eea
|
@ -26,6 +26,7 @@ type ExitMethod struct {
|
||||||
RoiTakeProfit *RoiTakeProfit `json:"roiTakeProfit"`
|
RoiTakeProfit *RoiTakeProfit `json:"roiTakeProfit"`
|
||||||
LowerShadowTakeProfit *LowerShadowTakeProfit `json:"lowerShadowTakeProfit"`
|
LowerShadowTakeProfit *LowerShadowTakeProfit `json:"lowerShadowTakeProfit"`
|
||||||
CumulatedVolumeTakeProfit *CumulatedVolumeTakeProfit `json:"cumulatedVolumeTakeProfit"`
|
CumulatedVolumeTakeProfit *CumulatedVolumeTakeProfit `json:"cumulatedVolumeTakeProfit"`
|
||||||
|
TrailingStop *TrailingStop2 `json:"trailingStop"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inherit is used for inheriting properties from the given strategy struct
|
// Inherit is used for inheriting properties from the given strategy struct
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
@ -47,10 +49,13 @@ func (s *TrailingStop2) Subscribe(session *ExchangeSession) {
|
||||||
func (s *TrailingStop2) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExecutor) {
|
func (s *TrailingStop2) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExecutor) {
|
||||||
s.session = session
|
s.session = session
|
||||||
s.orderExecutor = orderExecutor
|
s.orderExecutor = orderExecutor
|
||||||
|
s.latestHigh = fixedpoint.Zero
|
||||||
|
|
||||||
position := orderExecutor.Position()
|
position := orderExecutor.Position()
|
||||||
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
|
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
|
||||||
s.checkStopPrice(kline.Close, position)
|
if err := s.checkStopPrice(kline.Close, position); err != nil {
|
||||||
|
log.WithError(err).Errorf("error")
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if !IsBackTesting {
|
if !IsBackTesting {
|
||||||
|
@ -59,7 +64,9 @@ func (s *TrailingStop2) Bind(session *ExchangeSession, orderExecutor *GeneralOrd
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.checkStopPrice(trade.Price, position)
|
if err := s.checkStopPrice(trade.Price, position); err != nil {
|
||||||
|
log.WithError(err).Errorf("error")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +135,7 @@ func (s *TrailingStop2) checkStopPrice(price fixedpoint.Value, position *types.P
|
||||||
case types.SideTypeSell:
|
case types.SideTypeSell:
|
||||||
s.latestHigh = fixedpoint.Max(price, s.latestHigh)
|
s.latestHigh = fixedpoint.Max(price, s.latestHigh)
|
||||||
|
|
||||||
change := price.Sub(s.latestHigh).Div(s.latestHigh)
|
change := s.latestHigh.Sub(price).Div(price)
|
||||||
if change.Compare(s.CallbackRate) >= 0 {
|
if change.Compare(s.CallbackRate) >= 0 {
|
||||||
// submit order
|
// submit order
|
||||||
return s.triggerStop(price)
|
return s.triggerStop(price)
|
||||||
|
@ -139,6 +146,11 @@ func (s *TrailingStop2) checkStopPrice(price fixedpoint.Value, position *types.P
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TrailingStop2) triggerStop(price fixedpoint.Value) error {
|
func (s *TrailingStop2) triggerStop(price fixedpoint.Value) error {
|
||||||
|
// reset activated flag
|
||||||
|
defer func() {
|
||||||
|
s.activated = false
|
||||||
|
s.latestHigh = fixedpoint.Zero
|
||||||
|
}()
|
||||||
Notify("[TrailingStop] %s stop loss triggered. price: %f callback rate: %f", s.Symbol, price.Float64(), s.CallbackRate.Float64())
|
Notify("[TrailingStop] %s stop loss triggered. price: %f callback rate: %f", s.Symbol, price.Float64(), s.CallbackRate.Float64())
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
return s.orderExecutor.ClosePosition(ctx, fixedpoint.One, "trailingStop")
|
return s.orderExecutor.ClosePosition(ctx, fixedpoint.One, "trailingStop")
|
||||||
|
|
|
@ -27,7 +27,7 @@ func getTestMarket() types.Market {
|
||||||
return market
|
return market
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrailingStop(t *testing.T) {
|
func TestTrailingStop_ShortPosition(t *testing.T) {
|
||||||
market := getTestMarket()
|
market := getTestMarket()
|
||||||
|
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
|
@ -75,28 +75,108 @@ func TestTrailingStop(t *testing.T) {
|
||||||
|
|
||||||
// 20000 - 1% = 19800
|
// 20000 - 1% = 19800
|
||||||
currentPrice = currentPrice.Mul(one.Sub(activationRatio))
|
currentPrice = currentPrice.Mul(one.Sub(activationRatio))
|
||||||
|
assert.Equal(t, fixedpoint.NewFromFloat(19800.0), currentPrice)
|
||||||
|
|
||||||
err = stop.checkStopPrice(currentPrice, position)
|
err = stop.checkStopPrice(currentPrice, position)
|
||||||
if assert.NoError(t, err) {
|
if assert.NoError(t, err) {
|
||||||
assert.True(t, stop.activated)
|
assert.True(t, stop.activated)
|
||||||
assert.Equal(t, fixedpoint.NewFromFloat(19800.0), currentPrice)
|
|
||||||
assert.Equal(t, fixedpoint.NewFromFloat(19800.0), stop.latestHigh)
|
assert.Equal(t, fixedpoint.NewFromFloat(19800.0), stop.latestHigh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 19800 - 1% = 19602
|
// 19800 - 1% = 19602
|
||||||
currentPrice = currentPrice.Mul(one.Sub(callbackRatio))
|
currentPrice = currentPrice.Mul(one.Sub(callbackRatio))
|
||||||
|
assert.Equal(t, fixedpoint.NewFromFloat(19602.0), currentPrice)
|
||||||
|
|
||||||
err = stop.checkStopPrice(currentPrice, position)
|
err = stop.checkStopPrice(currentPrice, position)
|
||||||
if assert.NoError(t, err) {
|
if assert.NoError(t, err) {
|
||||||
assert.Equal(t, fixedpoint.NewFromFloat(19602.0), currentPrice)
|
|
||||||
assert.Equal(t, fixedpoint.NewFromFloat(19602.0), stop.latestHigh)
|
assert.Equal(t, fixedpoint.NewFromFloat(19602.0), stop.latestHigh)
|
||||||
assert.True(t, stop.activated)
|
assert.True(t, stop.activated)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 19602 + 1% = 19798.02
|
// 19602 + 1% = 19798.02
|
||||||
currentPrice = currentPrice.Mul(one.Add(callbackRatio))
|
currentPrice = currentPrice.Mul(one.Add(callbackRatio))
|
||||||
|
assert.Equal(t, fixedpoint.NewFromFloat(19798.02), currentPrice)
|
||||||
|
|
||||||
err = stop.checkStopPrice(currentPrice, position)
|
err = stop.checkStopPrice(currentPrice, position)
|
||||||
if assert.NoError(t, err) {
|
if assert.NoError(t, err) {
|
||||||
assert.Equal(t, fixedpoint.NewFromFloat(19798.02), currentPrice)
|
assert.Equal(t, fixedpoint.Zero, stop.latestHigh)
|
||||||
assert.Equal(t, fixedpoint.NewFromFloat(19602.0), stop.latestHigh)
|
assert.False(t, stop.activated)
|
||||||
assert.True(t, stop.activated)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrailingStop_LongPosition(t *testing.T) {
|
||||||
|
market := getTestMarket()
|
||||||
|
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockEx := mocks.NewMockExchange(mockCtrl)
|
||||||
|
mockEx.EXPECT().NewStream().Return(&types.StandardStream{}).Times(2)
|
||||||
|
mockEx.EXPECT().SubmitOrders(gomock.Any(), types.SubmitOrder{
|
||||||
|
Symbol: "BTCUSDT",
|
||||||
|
Side: types.SideTypeSell,
|
||||||
|
Type: types.OrderTypeMarket,
|
||||||
|
Market: market,
|
||||||
|
Quantity: fixedpoint.NewFromFloat(1.0),
|
||||||
|
Tag: "trailingStop",
|
||||||
|
})
|
||||||
|
|
||||||
|
session := NewExchangeSession("test", mockEx)
|
||||||
|
assert.NotNil(t, session)
|
||||||
|
|
||||||
|
session.markets[market.Symbol] = market
|
||||||
|
|
||||||
|
position := types.NewPositionFromMarket(market)
|
||||||
|
position.AverageCost = fixedpoint.NewFromFloat(20000.0)
|
||||||
|
position.Base = fixedpoint.NewFromFloat(1.0)
|
||||||
|
|
||||||
|
orderExecutor := NewGeneralOrderExecutor(session, "BTCUSDT", "test", "test-01", position)
|
||||||
|
|
||||||
|
activationRatio := fixedpoint.NewFromFloat(0.01)
|
||||||
|
callbackRatio := fixedpoint.NewFromFloat(0.01)
|
||||||
|
stop := &TrailingStop2{
|
||||||
|
Symbol: "BTCUSDT",
|
||||||
|
Interval: types.Interval1m,
|
||||||
|
Side: types.SideTypeSell,
|
||||||
|
CallbackRate: callbackRatio,
|
||||||
|
ActivationRatio: activationRatio,
|
||||||
|
}
|
||||||
|
stop.Bind(session, orderExecutor)
|
||||||
|
|
||||||
|
// the same price
|
||||||
|
currentPrice := fixedpoint.NewFromFloat(20000.0)
|
||||||
|
err := stop.checkStopPrice(currentPrice, position)
|
||||||
|
if assert.NoError(t, err) {
|
||||||
|
assert.False(t, stop.activated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 20000 + 1% = 20200
|
||||||
|
currentPrice = currentPrice.Mul(one.Add(activationRatio))
|
||||||
|
assert.Equal(t, fixedpoint.NewFromFloat(20200.0), currentPrice)
|
||||||
|
|
||||||
|
err = stop.checkStopPrice(currentPrice, position)
|
||||||
|
if assert.NoError(t, err) {
|
||||||
|
assert.True(t, stop.activated)
|
||||||
|
assert.Equal(t, fixedpoint.NewFromFloat(20200.0), stop.latestHigh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 20200 + 1% = 20402
|
||||||
|
currentPrice = currentPrice.Mul(one.Add(callbackRatio))
|
||||||
|
assert.Equal(t, fixedpoint.NewFromFloat(20402.0), currentPrice)
|
||||||
|
|
||||||
|
err = stop.checkStopPrice(currentPrice, position)
|
||||||
|
if assert.NoError(t, err) {
|
||||||
|
assert.Equal(t, fixedpoint.NewFromFloat(20402.0), stop.latestHigh)
|
||||||
|
assert.True(t, stop.activated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 20402 - 1%
|
||||||
|
currentPrice = currentPrice.Mul(one.Sub(callbackRatio))
|
||||||
|
assert.Equal(t, fixedpoint.NewFromFloat(20197.98), currentPrice)
|
||||||
|
|
||||||
|
err = stop.checkStopPrice(currentPrice, position)
|
||||||
|
if assert.NoError(t, err) {
|
||||||
|
assert.Equal(t, fixedpoint.Zero, stop.latestHigh)
|
||||||
|
assert.False(t, stop.activated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user