mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 14:55:16 +00:00
pivotshort: refactor take profit and stop loss methods
Signed-off-by: c9s <yoanlin93@gmail.com>
This commit is contained in:
parent
4c02d8f729
commit
47677e303f
|
@ -56,7 +56,7 @@ type SimplePriceMatching struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
bidOrders []types.Order
|
bidOrders []types.Order
|
||||||
askOrders []types.Order
|
askOrders []types.Order
|
||||||
closedOrders []types.Order
|
closedOrders map[uint64]types.Order
|
||||||
|
|
||||||
LastPrice fixedpoint.Value
|
LastPrice fixedpoint.Value
|
||||||
LastKLine types.KLine
|
LastKLine types.KLine
|
||||||
|
@ -375,8 +375,9 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
|
||||||
trades = append(trades, trade)
|
trades = append(trades, trade)
|
||||||
|
|
||||||
m.EmitOrderUpdate(o)
|
m.EmitOrderUpdate(o)
|
||||||
|
|
||||||
|
m.closedOrders[o.OrderID] = o
|
||||||
}
|
}
|
||||||
m.closedOrders = append(m.closedOrders, closedOrders...)
|
|
||||||
|
|
||||||
return closedOrders, trades
|
return closedOrders, trades
|
||||||
}
|
}
|
||||||
|
@ -445,12 +446,33 @@ func (m *SimplePriceMatching) SellToPrice(price fixedpoint.Value) (closedOrders
|
||||||
trades = append(trades, trade)
|
trades = append(trades, trade)
|
||||||
|
|
||||||
m.EmitOrderUpdate(o)
|
m.EmitOrderUpdate(o)
|
||||||
|
|
||||||
|
m.closedOrders[o.OrderID] = o
|
||||||
}
|
}
|
||||||
m.closedOrders = append(m.closedOrders, closedOrders...)
|
|
||||||
|
|
||||||
return closedOrders, trades
|
return closedOrders, trades
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *SimplePriceMatching) getOrder(orderID uint64) (types.Order, bool) {
|
||||||
|
if o, ok := m.closedOrders[orderID]; ok {
|
||||||
|
return o, true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range m.bidOrders {
|
||||||
|
if o.OrderID == orderID {
|
||||||
|
return o, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range m.askOrders {
|
||||||
|
if o.OrderID == orderID {
|
||||||
|
return o, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.Order{}, false
|
||||||
|
}
|
||||||
|
|
||||||
func (m *SimplePriceMatching) processKLine(kline types.KLine) {
|
func (m *SimplePriceMatching) processKLine(kline types.KLine) {
|
||||||
m.CurrentTime = kline.EndTime.Time()
|
m.CurrentTime = kline.EndTime.Time()
|
||||||
m.LastKLine = kline
|
m.LastKLine = kline
|
||||||
|
|
|
@ -14,11 +14,6 @@ import (
|
||||||
type OrderExecutor interface {
|
type OrderExecutor interface {
|
||||||
SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders types.OrderSlice, err error)
|
SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders types.OrderSlice, err error)
|
||||||
CancelOrders(ctx context.Context, orders ...types.Order) error
|
CancelOrders(ctx context.Context, orders ...types.Order) error
|
||||||
|
|
||||||
OnTradeUpdate(cb func(trade types.Trade))
|
|
||||||
OnOrderUpdate(cb func(order types.Order))
|
|
||||||
EmitTradeUpdate(trade types.Trade)
|
|
||||||
EmitOrderUpdate(order types.Order)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderExecutionRouter interface {
|
type OrderExecutionRouter interface {
|
||||||
|
|
|
@ -85,6 +85,11 @@ func (e *GeneralOrderExecutor) Bind() {
|
||||||
e.tradeCollector.BindStream(e.session.UserDataStream)
|
e.tradeCollector.BindStream(e.session.UserDataStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *GeneralOrderExecutor) CancelOrders(ctx context.Context, orders ...types.Order) error {
|
||||||
|
err := e.session.Exchange.CancelOrders(ctx, orders...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (e *GeneralOrderExecutor) SubmitOrders(ctx context.Context, submitOrders ...types.SubmitOrder) (types.OrderSlice, error) {
|
func (e *GeneralOrderExecutor) SubmitOrders(ctx context.Context, submitOrders ...types.SubmitOrder) (types.OrderSlice, error) {
|
||||||
formattedOrders, err := e.session.FormatOrders(submitOrders)
|
formattedOrders, err := e.session.FormatOrders(submitOrders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -125,3 +130,11 @@ func (e *GeneralOrderExecutor) ClosePosition(ctx context.Context, percentage fix
|
||||||
func (e *GeneralOrderExecutor) TradeCollector() *TradeCollector {
|
func (e *GeneralOrderExecutor) TradeCollector() *TradeCollector {
|
||||||
return e.tradeCollector
|
return e.tradeCollector
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *GeneralOrderExecutor) Session() *ExchangeSession {
|
||||||
|
return e.session
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *GeneralOrderExecutor) Position() *types.Position {
|
||||||
|
return e.position
|
||||||
|
}
|
||||||
|
|
155
pkg/strategy/pivotshort/protection_stop.go
Normal file
155
pkg/strategy/pivotshort/protection_stop.go
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
package pivotshort
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProtectionStopLoss struct {
|
||||||
|
// ActivationRatio is the trigger condition of this ROI protection stop loss
|
||||||
|
// When the price goes lower (for short position) with the ratio, the protection stop will be activated.
|
||||||
|
// This number should be positive to protect the profit
|
||||||
|
ActivationRatio fixedpoint.Value `json:"activationRatio"`
|
||||||
|
|
||||||
|
// StopLossRatio is the ratio for stop loss. This number should be positive to protect the profit.
|
||||||
|
// negative ratio will cause loss.
|
||||||
|
StopLossRatio fixedpoint.Value `json:"stopLossRatio"`
|
||||||
|
|
||||||
|
// PlaceStopOrder places the stop order on exchange and lock the balance
|
||||||
|
PlaceStopOrder bool `json:"placeStopOrder"`
|
||||||
|
|
||||||
|
session *bbgo.ExchangeSession
|
||||||
|
orderExecutor *bbgo.GeneralOrderExecutor
|
||||||
|
stopLossPrice fixedpoint.Value
|
||||||
|
stopLossOrder *types.Order
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProtectionStopLoss) shouldActivate(position *types.Position, closePrice fixedpoint.Value) bool {
|
||||||
|
if position.IsLong() {
|
||||||
|
r := one.Add(s.ActivationRatio)
|
||||||
|
activationPrice := position.AverageCost.Mul(r)
|
||||||
|
return closePrice.Compare(activationPrice) > 0
|
||||||
|
} else if position.IsShort() {
|
||||||
|
r := one.Sub(s.ActivationRatio)
|
||||||
|
activationPrice := position.AverageCost.Mul(r)
|
||||||
|
// for short position, if the close price is less than the activation price then this is a profit position.
|
||||||
|
return closePrice.Compare(activationPrice) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProtectionStopLoss) placeStopOrder(ctx context.Context, position *types.Position, orderExecutor bbgo.OrderExecutor) error {
|
||||||
|
if s.stopLossOrder != nil {
|
||||||
|
if err := orderExecutor.CancelOrders(ctx, *s.stopLossOrder); err != nil {
|
||||||
|
log.WithError(err).Errorf("failed to cancel stop limit order: %+v", s.stopLossOrder)
|
||||||
|
}
|
||||||
|
s.stopLossOrder = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
createdOrders, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||||
|
Symbol: position.Symbol,
|
||||||
|
Side: types.SideTypeBuy,
|
||||||
|
Type: types.OrderTypeStopLimit,
|
||||||
|
Quantity: position.GetQuantity(),
|
||||||
|
Price: s.stopLossPrice.Mul(one.Add(fixedpoint.NewFromFloat(0.005))), // +0.5% from the trigger price, slippage protection
|
||||||
|
StopPrice: s.stopLossPrice,
|
||||||
|
Market: position.Market,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(createdOrders) > 0 {
|
||||||
|
s.stopLossOrder = &createdOrders[0]
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProtectionStopLoss) shouldStop(closePrice fixedpoint.Value) bool {
|
||||||
|
if s.stopLossPrice.IsZero() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return closePrice.Compare(s.stopLossPrice) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProtectionStopLoss) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
|
||||||
|
s.session = session
|
||||||
|
s.orderExecutor = orderExecutor
|
||||||
|
|
||||||
|
orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
|
if position.IsClosed() {
|
||||||
|
s.stopLossOrder = nil
|
||||||
|
s.stopLossPrice = zero
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
session.UserDataStream.OnOrderUpdate(func(order types.Order) {
|
||||||
|
if s.stopLossOrder == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.OrderID == s.stopLossOrder.OrderID {
|
||||||
|
switch order.Status {
|
||||||
|
case types.OrderStatusFilled, types.OrderStatusCanceled:
|
||||||
|
s.stopLossOrder = nil
|
||||||
|
s.stopLossPrice = zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
position := orderExecutor.Position()
|
||||||
|
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||||
|
if kline.Symbol != position.Symbol || kline.Interval != types.Interval1m {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isPositionOpened := !position.IsClosed() && !position.IsDust(kline.Close)
|
||||||
|
if isPositionOpened && position.IsShort() {
|
||||||
|
s.handleChange(context.Background(), position, kline.Close, s.orderExecutor)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProtectionStopLoss) handleChange(ctx context.Context, position *types.Position, closePrice fixedpoint.Value, orderExecutor *bbgo.GeneralOrderExecutor) {
|
||||||
|
if s.stopLossOrder != nil {
|
||||||
|
// use RESTful to query the order status
|
||||||
|
// orderQuery := orderExecutor.Session().Exchange.(types.ExchangeOrderQueryService)
|
||||||
|
// order, err := orderQuery.QueryOrder(ctx, types.OrderQuery{
|
||||||
|
// Symbol: s.stopLossOrder.Symbol,
|
||||||
|
// OrderID: strconv.FormatUint(s.stopLossOrder.OrderID, 10),
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// log.WithError(err).Errorf("query order failed")
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.stopLossPrice.IsZero() {
|
||||||
|
if s.shouldActivate(position, closePrice) {
|
||||||
|
// calculate stop loss price
|
||||||
|
if position.IsShort() {
|
||||||
|
s.stopLossPrice = position.AverageCost.Mul(one.Sub(s.StopLossRatio))
|
||||||
|
} else if position.IsLong() {
|
||||||
|
s.stopLossPrice = position.AverageCost.Mul(one.Add(s.StopLossRatio))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("[ProtectionStopLoss] %s protection stop loss activated, current price = %f, average cost = %f, stop loss price = %f",
|
||||||
|
position.Symbol, closePrice.Float64(), position.AverageCost.Float64(), s.stopLossPrice.Float64())
|
||||||
|
} else {
|
||||||
|
// not activated, skip setup stop order
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.PlaceStopOrder {
|
||||||
|
if err := s.placeStopOrder(ctx, position, orderExecutor); err != nil {
|
||||||
|
log.WithError(err).Errorf("failed to place stop limit order")
|
||||||
|
}
|
||||||
|
} else if s.shouldStop(closePrice) {
|
||||||
|
log.Infof("[ProtectionStopLoss] protection stop order is triggered at price %f, position = %+v", closePrice.Float64(), position)
|
||||||
|
if err := orderExecutor.ClosePosition(ctx, one); err != nil {
|
||||||
|
log.WithError(err).Errorf("failed to close position")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
pkg/strategy/pivotshort/roi_stop.go
Normal file
41
pkg/strategy/pivotshort/roi_stop.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package pivotshort
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RoiStopLoss struct {
|
||||||
|
Percentage fixedpoint.Value `json:"percentage"`
|
||||||
|
|
||||||
|
session *bbgo.ExchangeSession
|
||||||
|
orderExecutor *bbgo.GeneralOrderExecutor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RoiStopLoss) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
|
||||||
|
s.session = session
|
||||||
|
s.orderExecutor = orderExecutor
|
||||||
|
|
||||||
|
position := orderExecutor.Position()
|
||||||
|
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||||
|
if kline.Symbol != position.Symbol || kline.Interval != types.Interval1m {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
closePrice := kline.Close
|
||||||
|
if position.IsClosed() || position.IsDust(closePrice) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
roi := position.ROI(closePrice)
|
||||||
|
if roi.Compare(s.Percentage.Neg()) < 0 {
|
||||||
|
// stop loss
|
||||||
|
bbgo.Notify("[RoiStopLoss] %s stop loss triggered by ROI %s/%s, price: %f", position.Symbol, roi.Percentage(), s.Percentage.Neg().Percentage(), kline.Close.Float64())
|
||||||
|
_ = orderExecutor.ClosePosition(context.Background(), fixedpoint.One)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
41
pkg/strategy/pivotshort/roi_take_profit.go
Normal file
41
pkg/strategy/pivotshort/roi_take_profit.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package pivotshort
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RoiTakeProfit struct {
|
||||||
|
Percentage fixedpoint.Value `json:"percentage"`
|
||||||
|
|
||||||
|
session *bbgo.ExchangeSession
|
||||||
|
orderExecutor *bbgo.GeneralOrderExecutor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RoiTakeProfit) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
|
||||||
|
s.session = session
|
||||||
|
s.orderExecutor = orderExecutor
|
||||||
|
|
||||||
|
position := orderExecutor.Position()
|
||||||
|
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||||
|
if kline.Symbol != position.Symbol || kline.Interval != types.Interval1m {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
closePrice := kline.Close
|
||||||
|
if position.IsClosed() || position.IsDust(closePrice) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
roi := position.ROI(closePrice)
|
||||||
|
if roi.Compare(s.Percentage) > 0 {
|
||||||
|
// stop loss
|
||||||
|
bbgo.Notify("[RoiTakeProfit] %s take profit is triggered by ROI %s/%s, price: %f", position.Symbol, roi.Percentage(), s.Percentage.Percentage(), kline.Close.Float64())
|
||||||
|
_ = orderExecutor.ClosePosition(context.Background(), fixedpoint.One)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -17,6 +17,9 @@ import (
|
||||||
|
|
||||||
const ID = "pivotshort"
|
const ID = "pivotshort"
|
||||||
|
|
||||||
|
var one = fixedpoint.One
|
||||||
|
var zero = fixedpoint.Zero
|
||||||
|
|
||||||
var log = logrus.WithField("strategy", ID)
|
var log = logrus.WithField("strategy", ID)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -72,10 +75,12 @@ type CumulatedVolume struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Exit struct {
|
type Exit struct {
|
||||||
RoiStopLossPercentage fixedpoint.Value `json:"roiStopLossPercentage"`
|
|
||||||
RoiTakeProfitPercentage fixedpoint.Value `json:"roiTakeProfitPercentage"`
|
|
||||||
RoiMinTakeProfitPercentage fixedpoint.Value `json:"roiMinTakeProfitPercentage"`
|
RoiMinTakeProfitPercentage fixedpoint.Value `json:"roiMinTakeProfitPercentage"`
|
||||||
|
|
||||||
|
RoiTakeProfit *RoiTakeProfit `json:"roiTakeProfit"`
|
||||||
|
RoiStopLoss *RoiStopLoss `json:"roiStopLoss"`
|
||||||
|
ProtectionStopLoss *ProtectionStopLoss `json:"protectionStopLoss"`
|
||||||
|
|
||||||
LowerShadowRatio fixedpoint.Value `json:"lowerShadowRatio"`
|
LowerShadowRatio fixedpoint.Value `json:"lowerShadowRatio"`
|
||||||
|
|
||||||
CumulatedVolume *CumulatedVolume `json:"cumulatedVolume"`
|
CumulatedVolume *CumulatedVolume `json:"cumulatedVolume"`
|
||||||
|
@ -108,6 +113,7 @@ type Strategy struct {
|
||||||
session *bbgo.ExchangeSession
|
session *bbgo.ExchangeSession
|
||||||
orderExecutor *bbgo.GeneralOrderExecutor
|
orderExecutor *bbgo.GeneralOrderExecutor
|
||||||
|
|
||||||
|
stopLossPrice fixedpoint.Value
|
||||||
lastLow fixedpoint.Value
|
lastLow fixedpoint.Value
|
||||||
pivot *indicator.Pivot
|
pivot *indicator.Pivot
|
||||||
resistancePivot *indicator.Pivot
|
resistancePivot *indicator.Pivot
|
||||||
|
@ -178,6 +184,7 @@ func (s *Strategy) CurrentPosition() *types.Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error {
|
func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error {
|
||||||
|
bbgo.Notify("Closing position", s.Position)
|
||||||
return s.orderExecutor.ClosePosition(ctx, percentage)
|
return s.orderExecutor.ClosePosition(ctx, percentage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,9 +278,20 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if s.Exit.ProtectionStopLoss != nil {
|
||||||
|
s.Exit.ProtectionStopLoss.Bind(session, s.orderExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Exit.RoiStopLoss != nil {
|
||||||
|
s.Exit.RoiStopLoss.Bind(session, s.orderExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Exit.RoiTakeProfit != nil {
|
||||||
|
s.Exit.RoiTakeProfit.Bind(session, s.orderExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
// Always check whether you can open a short position or not
|
// Always check whether you can open a short position or not
|
||||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||||
// StrategyController
|
|
||||||
if s.Status != types.StrategyStatusRunning {
|
if s.Status != types.StrategyStatusRunning {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -285,21 +303,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
isPositionOpened := !s.Position.IsClosed() && !s.Position.IsDust(kline.Close)
|
isPositionOpened := !s.Position.IsClosed() && !s.Position.IsDust(kline.Close)
|
||||||
|
|
||||||
if isPositionOpened && s.Position.IsShort() {
|
if isPositionOpened && s.Position.IsShort() {
|
||||||
// calculate return rate
|
roi := s.Position.ROI(kline.Close)
|
||||||
// TODO: apply quantity to this formula
|
if !s.Exit.RoiMinTakeProfitPercentage.IsZero() {
|
||||||
roi := s.Position.AverageCost.Sub(kline.Close).Div(s.Position.AverageCost)
|
if roi.Compare(s.Exit.RoiMinTakeProfitPercentage) > 0 {
|
||||||
if roi.Compare(s.Exit.RoiStopLossPercentage.Neg()) < 0 {
|
|
||||||
// stop loss
|
|
||||||
bbgo.Notify("%s ROI StopLoss triggered at price %f: Loss %s", s.Symbol, kline.Close.Float64(), roi.Percentage())
|
|
||||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
// take profit
|
|
||||||
if roi.Compare(s.Exit.RoiTakeProfitPercentage) > 0 { // force take profit
|
|
||||||
bbgo.Notify("%s TakeProfit triggered at price %f: by ROI percentage %s", s.Symbol, kline.Close.Float64(), roi.Percentage(), kline)
|
|
||||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
|
||||||
return
|
|
||||||
} else if !s.Exit.RoiMinTakeProfitPercentage.IsZero() && roi.Compare(s.Exit.RoiMinTakeProfitPercentage) > 0 {
|
|
||||||
if !s.Exit.LowerShadowRatio.IsZero() && kline.GetLowerShadowHeight().Div(kline.Close).Compare(s.Exit.LowerShadowRatio) > 0 {
|
if !s.Exit.LowerShadowRatio.IsZero() && kline.GetLowerShadowHeight().Div(kline.Close).Compare(s.Exit.LowerShadowRatio) > 0 {
|
||||||
bbgo.Notify("%s TakeProfit triggered at price %f: by shadow ratio %f",
|
bbgo.Notify("%s TakeProfit triggered at price %f: by shadow ratio %f",
|
||||||
s.Symbol,
|
s.Symbol,
|
||||||
|
@ -334,6 +340,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s.pivotLowPrices) == 0 {
|
if len(s.pivotLowPrices) == 0 {
|
||||||
|
log.Infof("currently there is no pivot low prices, skip placing orders...")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -174,6 +174,11 @@ func (p *Position) GetBase() (base fixedpoint.Value) {
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Position) GetQuantity() fixedpoint.Value {
|
||||||
|
base := p.GetBase()
|
||||||
|
return base.Abs()
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Position) UnrealizedProfit(price fixedpoint.Value) fixedpoint.Value {
|
func (p *Position) UnrealizedProfit(price fixedpoint.Value) fixedpoint.Value {
|
||||||
quantity := p.GetBase().Abs()
|
quantity := p.GetBase().Abs()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user