fix: statistics on entry/exit on signal changes, fix position check

This commit is contained in:
zenix 2022-05-30 12:45:52 +09:00
parent e81216e678
commit e3a8ef4e69
5 changed files with 329 additions and 158 deletions

View File

@ -31,6 +31,8 @@ exchangeStrategies:
ccistochFilterHigh: 80 ccistochFilterHigh: 80
# CCI Stochastic Indicator low filter # CCI Stochastic Indicator low filter
ccistochFilterLow: 20 ccistochFilterLow: 20
# print record exit point in log messages
record: false
sync: sync:
userDataStream: userDataStream:
@ -43,7 +45,7 @@ sync:
backtest: backtest:
startTime: "2022-05-01" startTime: "2022-05-01"
endTime: "2022-05-25" endTime: "2022-05-27"
symbols: symbols:
- MATICUSDT - MATICUSDT
sessions: [binance] sessions: [binance]
@ -52,5 +54,5 @@ backtest:
#makerFeeRate: 0 #makerFeeRate: 0
#takerFeeRate: 15 #takerFeeRate: 15
balances: balances:
MATIC: 00.0 MATIC: 000.0
USDT: 15000.0 USDT: 15000.0

View File

@ -17,6 +17,8 @@ import (
const ID = "ewo_dgtrd" const ID = "ewo_dgtrd"
const record = false
var log = logrus.WithField("strategy", ID) var log = logrus.WithField("strategy", ID)
func init() { func init() {
@ -41,6 +43,8 @@ type Strategy struct {
FilterHigh float64 `json:"ccistochFilterHigh"` // high filter for CCI Stochastic indicator FilterHigh float64 `json:"ccistochFilterHigh"` // high filter for CCI Stochastic indicator
FilterLow float64 `json:"ccistochFilterLow"` // low filter for CCI Stochastic indicator FilterLow float64 `json:"ccistochFilterLow"` // low filter for CCI Stochastic indicator
Record bool `json:"record"` // print record messages on position exit point
KLineStartTime types.Time KLineStartTime types.Time
KLineEndTime types.Time KLineEndTime types.Time
@ -53,6 +57,8 @@ type Strategy struct {
activeMakerOrders *bbgo.LocalActiveOrderBook activeMakerOrders *bbgo.LocalActiveOrderBook
orderStore *bbgo.OrderStore orderStore *bbgo.OrderStore
tradeCollector *bbgo.TradeCollector tradeCollector *bbgo.TradeCollector
entryPrice fixedpoint.Value
waitForTrade bool
atr *indicator.ATR atr *indicator.ATR
ccis *CCISTOCH ccis *CCISTOCH
@ -194,6 +200,7 @@ func (inc *VWEMA) UpdateVal(price float64, vol float64) {
inc.V.Update(vol) inc.V.Update(vol)
} }
// Setup the Indicators going to be used
func (s *Strategy) SetupIndicators() { func (s *Strategy) SetupIndicators() {
store, ok := s.Session.MarketDataStore(s.Symbol) store, ok := s.Session.MarketDataStore(s.Symbol)
if !ok { if !ok {
@ -440,6 +447,7 @@ func (s *Strategy) SetupIndicators() {
} }
} }
// Utility to evaluate if the order is valid or not to send to the exchange
func (s *Strategy) validateOrder(order *types.SubmitOrder) error { func (s *Strategy) validateOrder(order *types.SubmitOrder) error {
if order.Type == types.OrderTypeMarket && order.TimeInForce != "" { if order.Type == types.OrderTypeMarket && order.TimeInForce != "" {
return errors.New("wrong field: market vs TimeInForce") return errors.New("wrong field: market vs TimeInForce")
@ -451,7 +459,8 @@ func (s *Strategy) validateOrder(order *types.SubmitOrder) error {
return errors.New("cannot get account") return errors.New("cannot get account")
} }
if order.Quantity.Compare(baseBalance.Available) > 0 { if order.Quantity.Compare(baseBalance.Available) > 0 {
order.Quantity = baseBalance.Available log.Errorf("qty %v > avail %v", order.Quantity, baseBalance.Available)
return errors.New("qty > avail")
} }
price := order.Price price := order.Price
if price.IsZero() { if price.IsZero() {
@ -466,7 +475,7 @@ func (s *Strategy) validateOrder(order *types.SubmitOrder) error {
order.Quantity.Compare(s.Market.MinQuantity) < 0 || order.Quantity.Compare(s.Market.MinQuantity) < 0 ||
orderAmount.Compare(s.Market.MinNotional) < 0 { orderAmount.Compare(s.Market.MinNotional) < 0 {
log.Debug("amount fail") log.Debug("amount fail")
return errors.New("amount fail") return errors.New(fmt.Sprintf("amount fail: quantity: %v, amount: %v", order.Quantity, orderAmount))
} }
return nil return nil
} else if order.Side == types.SideTypeBuy { } else if order.Side == types.SideTypeBuy {
@ -485,7 +494,7 @@ func (s *Strategy) validateOrder(order *types.SubmitOrder) error {
} }
totalQuantity := quoteBalance.Available.Div(price) totalQuantity := quoteBalance.Available.Div(price)
if order.Quantity.Compare(totalQuantity) > 0 { if order.Quantity.Compare(totalQuantity) > 0 {
log.Error("qty > avail") log.Errorf("qty %v > avail %v", order.Quantity, totalQuantity)
return errors.New("qty > avail") return errors.New("qty > avail")
} }
orderAmount := order.Quantity.Mul(price) orderAmount := order.Quantity.Mul(price)
@ -493,7 +502,7 @@ func (s *Strategy) validateOrder(order *types.SubmitOrder) error {
orderAmount.Compare(s.Market.MinNotional) < 0 || orderAmount.Compare(s.Market.MinNotional) < 0 ||
order.Quantity.Compare(s.Market.MinQuantity) < 0 { order.Quantity.Compare(s.Market.MinQuantity) < 0 {
log.Debug("amount fail") log.Debug("amount fail")
return errors.New("amount fail") return errors.New(fmt.Sprintf("amount fail: quantity: %v, amount: %v", order.Quantity, orderAmount))
} }
return nil return nil
} }
@ -502,15 +511,27 @@ func (s *Strategy) validateOrder(order *types.SubmitOrder) error {
} }
func (s *Strategy) PlaceBuyOrder(ctx context.Context, price fixedpoint.Value) { func (s *Strategy) PlaceBuyOrder(ctx context.Context, price fixedpoint.Value) (*types.Order, *types.Order) {
if s.Position.GetBase().Add(s.Market.MinQuantity).Sign() < 0 && !s.ClosePosition(ctx) { var closeOrder *types.Order
log.Errorf("sell position %v remained not closed, skip placing order", s.Position.GetBase()) var ok bool
return waitForTrade := false
base := s.Position.GetBase()
if base.Abs().Compare(s.Market.MinQuantity) >= 0 && base.Mul(s.GetLastPrice()).Abs().Compare(s.Market.MinNotional) >= 0 && base.Sign() < 0 {
if closeOrder, ok = s.ClosePosition(ctx); !ok {
log.Errorf("sell position %v remained not closed, skip placing order", base)
return closeOrder, nil
}
}
if s.Position.GetBase().Sign() < 0 {
// we are not able to make close trade at this moment,
// will close the rest of the position by normal limit order
// s.entryPrice is set in the last trade
waitForTrade = true
} }
quoteBalance, ok := s.Session.GetAccount().Balance(s.Market.QuoteCurrency) quoteBalance, ok := s.Session.GetAccount().Balance(s.Market.QuoteCurrency)
if !ok { if !ok {
log.Infof("buy order at price %v failed", price) log.Infof("buy order at price %v failed", price)
return return closeOrder, nil
} }
quantityAmount := quoteBalance.Available quantityAmount := quoteBalance.Available
totalQuantity := quantityAmount.Div(price) totalQuantity := quantityAmount.Div(price)
@ -525,84 +546,126 @@ func (s *Strategy) PlaceBuyOrder(ctx context.Context, price fixedpoint.Value) {
} }
if err := s.validateOrder(&order); err != nil { if err := s.validateOrder(&order); err != nil {
log.Infof("validation failed %v: %v", order, err) log.Infof("validation failed %v: %v", order, err)
return return closeOrder, nil
} }
// strong long log.Warnf("long at %v, position %v, closeOrder %v, timestamp: %s", price, s.Position.GetBase(), closeOrder, s.KLineStartTime)
log.Warnf("long at %v, timestamp: %s", price, s.KLineStartTime)
createdOrders, err := s.Session.Exchange.SubmitOrders(ctx, order) createdOrders, err := s.Session.Exchange.SubmitOrders(ctx, order)
if err != nil { if err != nil {
log.WithError(err).Errorf("cannot place order") log.WithError(err).Errorf("cannot place order")
return return closeOrder, nil
} }
log.Infof("post order %v", createdOrders)
log.Infof("post order c: %v, entryPrice: %v o: %v", waitForTrade, s.entryPrice, createdOrders)
s.waitForTrade = waitForTrade
s.orderStore.Add(createdOrders...) s.orderStore.Add(createdOrders...)
s.activeMakerOrders.Add(createdOrders...) s.activeMakerOrders.Add(createdOrders...)
s.tradeCollector.Process() s.tradeCollector.Process()
return closeOrder, &createdOrders[0]
} }
func (s *Strategy) PlaceSellOrder(ctx context.Context, price fixedpoint.Value) { func (s *Strategy) PlaceSellOrder(ctx context.Context, price fixedpoint.Value) (*types.Order, *types.Order) {
if s.Position.GetBase().Compare(s.Market.MinQuantity) > 0 && !s.ClosePosition(ctx) { var closeOrder *types.Order
log.Errorf("buy position %v remained not closed, skip placing order", s.Position.GetBase()) var ok bool
return waitForTrade := false
base := s.Position.GetBase()
if base.Abs().Compare(s.Market.MinQuantity) >= 0 && base.Abs().Mul(s.GetLastPrice()).Compare(s.Market.MinNotional) >= 0 && base.Sign() > 0 {
if closeOrder, ok = s.ClosePosition(ctx); !ok {
log.Errorf("buy position %v remained not closed, skip placing order", base)
return closeOrder, nil
}
}
if s.Position.GetBase().Sign() > 0 {
// we are not able to make close trade at this moment,
// will close the rest of the position by normal limit order
// s.entryPrice is set in the last trade
waitForTrade = true
}
baseBalance, ok := s.Session.GetAccount().Balance(s.Market.BaseCurrency)
if !ok {
return closeOrder, nil
} }
balances := s.Session.GetAccount().Balances()
baseBalance := balances[s.Market.BaseCurrency].Available
order := types.SubmitOrder{ order := types.SubmitOrder{
Symbol: s.Symbol, Symbol: s.Symbol,
Side: types.SideTypeSell, Side: types.SideTypeSell,
Type: types.OrderTypeLimit, Type: types.OrderTypeLimit,
Market: s.Market, Market: s.Market,
Quantity: baseBalance, Quantity: baseBalance.Available,
Price: price, Price: price,
TimeInForce: types.TimeInForceGTC, TimeInForce: types.TimeInForceGTC,
} }
if err := s.validateOrder(&order); err != nil { if err := s.validateOrder(&order); err != nil {
log.Infof("validation failed %v: %v", order, err) log.Infof("validation failed %v: %v", order, err)
return return closeOrder, nil
} }
log.Warnf("short at %v, timestamp: %s", price, s.KLineStartTime) log.Warnf("short at %v, position %v closeOrder %v, timestamp: %s", price, s.Position.GetBase(), closeOrder, s.KLineStartTime)
createdOrders, err := s.Session.Exchange.SubmitOrders(ctx, order) createdOrders, err := s.Session.Exchange.SubmitOrders(ctx, order)
if err != nil { if err != nil {
log.WithError(err).Errorf("cannot place order") log.WithError(err).Errorf("cannot place order")
return return closeOrder, nil
} }
log.Infof("post order %v", createdOrders) log.Infof("post order, c: %v, entryPrice: %v o: %v", waitForTrade, s.entryPrice, createdOrders)
s.waitForTrade = waitForTrade
s.orderStore.Add(createdOrders...) s.orderStore.Add(createdOrders...)
s.activeMakerOrders.Add(createdOrders...) s.activeMakerOrders.Add(createdOrders...)
s.tradeCollector.Process() s.tradeCollector.Process()
return closeOrder, &createdOrders[0]
} }
func (s *Strategy) ClosePosition(ctx context.Context) bool { // ClosePosition(context.Context) -> (closeOrder *types.Order, ok bool)
// this will decorate the generated order from NewClosePositionOrder
// add do necessary checks
// if available quantity is zero, will return (nil, true)
// if any of the checks failed, will return (nil, false)
// otherwise, return the created close order and true
func (s *Strategy) ClosePosition(ctx context.Context) (*types.Order, bool) {
order := s.Position.NewClosePositionOrder(fixedpoint.One) order := s.Position.NewClosePositionOrder(fixedpoint.One)
// no position exists
if order == nil { if order == nil {
// no base // no base
s.sellPrice = fixedpoint.Zero s.sellPrice = fixedpoint.Zero
s.buyPrice = fixedpoint.Zero s.buyPrice = fixedpoint.Zero
return true return nil, true
} }
order.TimeInForce = "" order.TimeInForce = ""
// If there's any order not yet been traded in the orderbook,
// we need this additional check to make sure we have enough balance to post a close order
balances := s.Session.GetAccount().Balances()
baseBalance := balances[s.Market.BaseCurrency].Available
if order.Side == types.SideTypeBuy {
price := s.GetLastPrice()
quoteAmount := balances[s.Market.QuoteCurrency].Available.Div(price)
if order.Quantity.Compare(quoteAmount) > 0 {
order.Quantity = quoteAmount
}
} else if order.Side == types.SideTypeSell && order.Quantity.Compare(baseBalance) > 0 {
order.Quantity = baseBalance
}
// if no available balance...
if order.Quantity.IsZero() {
return nil, true
}
if err := s.validateOrder(order); err != nil { if err := s.validateOrder(order); err != nil {
log.Errorf("cannot place close order %v: %v", order, err) log.Errorf("cannot place close order %v: %v", order, err)
return false return nil, false
} }
createdOrders, err := s.Session.Exchange.SubmitOrders(ctx, *order) createdOrders, err := s.Session.Exchange.SubmitOrders(ctx, *order)
if err != nil { if err != nil {
log.WithError(err).Errorf("cannot place close order") log.WithError(err).Errorf("cannot place close order")
return false return nil, false
} }
log.Infof("close order %v", createdOrders) log.Infof("close order %v", createdOrders)
s.orderStore.Add(createdOrders...) s.orderStore.Add(createdOrders...)
s.activeMakerOrders.Add(createdOrders...) s.activeMakerOrders.Add(createdOrders...)
s.tradeCollector.Process() s.tradeCollector.Process()
return true return &createdOrders[0], true
} }
func (s *Strategy) CancelAll(ctx context.Context, side types.SideType) { func (s *Strategy) CancelAll(ctx context.Context) {
var toCancel []types.Order var toCancel []types.Order
for _, order := range s.orderStore.Orders() { for _, order := range s.orderStore.Orders() {
if order.Status == types.OrderStatusNew || order.Status == types.OrderStatusPartiallyFilled && order.Side == side { if order.Status == types.OrderStatusNew || order.Status == types.OrderStatusPartiallyFilled {
toCancel = append(toCancel, order) toCancel = append(toCancel, order)
} }
} }
@ -610,11 +673,35 @@ func (s *Strategy) CancelAll(ctx context.Context, side types.SideType) {
if err := s.Session.Exchange.CancelOrders(ctx, toCancel...); err != nil { if err := s.Session.Exchange.CancelOrders(ctx, toCancel...); err != nil {
log.WithError(err).Errorf("cancel order error") log.WithError(err).Errorf("cancel order error")
} }
s.waitForTrade = false
s.tradeCollector.Process()
} }
} }
func (s *Strategy) GetLastPrice() fixedpoint.Value {
var lastPrice fixedpoint.Value
var ok bool
if s.Environment.IsBackTesting() {
lastPrice, ok = s.Session.LastPrice(s.Symbol)
if !ok {
log.Errorf("cannot get last price")
return lastPrice
}
} else {
s.lock.RLock()
if s.midPrice.IsZero() {
lastPrice, ok = s.Session.LastPrice(s.Symbol)
if !ok {
log.Errorf("cannot get last price")
return lastPrice
}
} else {
lastPrice = s.midPrice
}
s.lock.RUnlock()
}
return lastPrice
}
// Trading Rules: // Trading Rules:
// - buy / sell the whole asset // - buy / sell the whole asset
// - SL by atr (lastprice < buyprice - atr * 2) || (lastprice > sellprice + atr * 2) // - SL by atr (lastprice < buyprice - atr * 2) || (lastprice > sellprice + atr * 2)
@ -684,7 +771,42 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} else { } else {
s.Environment.RecordPosition(s.Position, trade, nil) s.Environment.RecordPosition(s.Position, trade, nil)
} }
if s.Position.GetBase().Abs().Compare(s.Market.MinQuantity) > 0 { // calculate report for the position that cannot be closed by close order (amount too small)
if s.waitForTrade {
price := s.entryPrice
if price.IsZero() {
panic("no price found")
}
pnlRate := trade.Price.Sub(price).Abs().Div(trade.Price).Float64()
if s.Record {
log.Errorf("record avg %v trade %v", price, trade)
}
if trade.Side == types.SideTypeBuy {
if trade.Price.Compare(price) < 0 {
percentAvgTPfromOrder = percentAvgTPfromOrder*float64(counterTPfromOrder) + pnlRate
counterTPfromOrder += 1
percentAvgTPfromOrder /= float64(counterTPfromOrder)
} else {
percentAvgSLfromOrder = percentAvgSLfromOrder*float64(counterSLfromOrder) + pnlRate
counterSLfromOrder += 1
percentAvgSLfromOrder /= float64(counterSLfromOrder)
}
} else if trade.Side == types.SideTypeSell {
if trade.Price.Compare(price) > 0 {
percentAvgTPfromOrder = percentAvgTPfromOrder*float64(counterTPfromOrder) + pnlRate
counterTPfromOrder += 1
percentAvgTPfromOrder /= float64(counterTPfromOrder)
} else {
percentAvgSLfromOrder = percentAvgSLfromOrder*float64(counterSLfromOrder) + pnlRate
counterSLfromOrder += 1
percentAvgSLfromOrder /= float64(counterSLfromOrder)
}
} else {
panic(fmt.Sprintf("no sell(%v) or buy price(%v), %v", s.sellPrice, s.buyPrice, trade))
}
s.waitForTrade = false
}
if s.Position.GetBase().Abs().Compare(s.Market.MinQuantity) >= 0 && s.Position.GetBase().Abs().Mul(trade.Price).Compare(s.Market.MinNotional) >= 0 {
sign := s.Position.GetBase().Sign() sign := s.Position.GetBase().Sign()
if sign > 0 { if sign > 0 {
log.Infof("base become positive, %v", trade) log.Infof("base become positive, %v", trade)
@ -692,19 +814,23 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.sellPrice = fixedpoint.Zero s.sellPrice = fixedpoint.Zero
s.peakPrice = s.Position.AverageCost s.peakPrice = s.Position.AverageCost
} else if sign == 0 { } else if sign == 0 {
log.Infof("base become zero") panic("not going to happen")
s.buyPrice = fixedpoint.Zero
s.sellPrice = fixedpoint.Zero
} else { } else {
log.Infof("base become negative, %v", trade) log.Infof("base become negative, %v", trade)
s.buyPrice = fixedpoint.Zero s.buyPrice = fixedpoint.Zero
s.sellPrice = s.Position.AverageCost s.sellPrice = s.Position.AverageCost
s.bottomPrice = s.Position.AverageCost s.bottomPrice = s.Position.AverageCost
} }
s.entryPrice = trade.Price
} else { } else {
log.Infof("base become zero") log.Infof("base become zero, rest of base: %v", s.Position.GetBase())
if s.Position.GetBase().IsZero() {
s.entryPrice = fixedpoint.Zero
}
s.buyPrice = fixedpoint.Zero s.buyPrice = fixedpoint.Zero
s.sellPrice = fixedpoint.Zero s.sellPrice = fixedpoint.Zero
s.peakPrice = fixedpoint.Zero
s.bottomPrice = fixedpoint.Zero
} }
}) })
@ -717,7 +843,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.SetupIndicators() s.SetupIndicators()
sellOrderTPSL := func(price fixedpoint.Value) { sellOrderTPSL := func(price fixedpoint.Value) {
if s.Position.GetBase().IsZero() { lastPrice := s.GetLastPrice()
base := s.Position.GetBase().Abs()
if base.Mul(lastPrice).Compare(s.Market.MinNotional) < 0 || base.Compare(s.Market.MinQuantity) < 0 {
return return
} }
if s.sellPrice.IsZero() { if s.sellPrice.IsZero() {
@ -727,15 +855,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
quoteBalance := balances[s.Market.QuoteCurrency].Available quoteBalance := balances[s.Market.QuoteCurrency].Available
atr := fixedpoint.NewFromFloat(s.atr.Last()) atr := fixedpoint.NewFromFloat(s.atr.Last())
atrx2 := fixedpoint.NewFromFloat(s.atr.Last() * 2) atrx2 := fixedpoint.NewFromFloat(s.atr.Last() * 2)
lastPrice := price
var ok bool
if s.Environment.IsBackTesting() {
lastPrice, ok = session.LastPrice(s.Symbol)
if !ok {
log.Errorf("cannot get last price")
return
}
}
buyall := false buyall := false
if s.bottomPrice.IsZero() || s.bottomPrice.Compare(price) > 0 { if s.bottomPrice.IsZero() || s.bottomPrice.Compare(price) > 0 {
s.bottomPrice = price s.bottomPrice = price
@ -743,44 +862,30 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
takeProfit := false takeProfit := false
bottomBack := s.bottomPrice bottomBack := s.bottomPrice
spBack := s.sellPrice spBack := s.sellPrice
if !quoteBalance.IsZero() { reason := -1
if quoteBalance.Div(lastPrice).Compare(s.Market.MinQuantity) >= 0 && quoteBalance.Compare(s.Market.MinNotional) >= 0 {
longSignal := types.CrossOver(s.ewo, s.ewoSignal) longSignal := types.CrossOver(s.ewo, s.ewoSignal)
base := fixedpoint.NewFromFloat(s.ma34.Last()) base := fixedpoint.NewFromFloat(s.ma34.Last())
// TP // TP
if lastPrice.Compare(s.sellPrice) < 0 && (s.ccis.BuySignal() || longSignal.Last() || (!atrx2.IsZero() && base.Sub(atrx2).Compare(lastPrice) >= 0)) { if lastPrice.Compare(s.sellPrice) < 0 && (s.ccis.BuySignal() || longSignal.Last() || (!atrx2.IsZero() && base.Sub(atrx2).Compare(lastPrice) >= 0)) {
buyall = true buyall = true
s.bottomPrice = fixedpoint.Zero
takeProfit = true takeProfit = true
// calculate report // calculate report
pnlRate := s.sellPrice.Sub(lastPrice).Div(lastPrice).Float64()
if s.ccis.BuySignal() { if s.ccis.BuySignal() {
percentAvgTPfromCCI = percentAvgTPfromCCI*float64(counterTPfromCCI) + pnlRate reason = 0
counterTPfromCCI += 1
percentAvgTPfromCCI /= float64(counterTPfromCCI)
} else if longSignal.Last() { } else if longSignal.Last() {
percentAvgTPfromLongShort = percentAvgTPfromLongShort*float64(counterTPfromLongShort) + pnlRate reason = 1
counterTPfromLongShort += 1
percentAvgTPfromLongShort /= float64(counterTPfromLongShort)
} else { } else {
percentAvgTPfromAtr = percentAvgTPfromAtr*float64(counterTPfromAtr) + pnlRate reason = 2
counterTPfromAtr += 1
percentAvgTPfromAtr /= float64(counterTPfromAtr)
} }
} }
if !atr.IsZero() && s.bottomPrice.Add(atr).Compare(lastPrice) <= 0 && if !atr.IsZero() && s.bottomPrice.Add(atr).Compare(lastPrice) <= 0 &&
lastPrice.Compare(s.sellPrice) < 0 { lastPrice.Compare(s.sellPrice) < 0 {
buyall = true buyall = true
s.bottomPrice = fixedpoint.Zero
takeProfit = true takeProfit = true
reason = 3
// calculate report
pnlRate := s.sellPrice.Sub(lastPrice).Div(lastPrice).Float64()
percentAvgTPfromPeak = percentAvgTPfromPeak*float64(counterTPfromPeak) + pnlRate
counterTPfromPeak += 1
percentAvgTPfromPeak /= float64(counterTPfromPeak)
} }
// SL // SL
@ -795,28 +900,64 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
if !s.DisableShortStop && ((!atr.IsZero() && s.sellPrice.Sub(atr).Compare(lastPrice) >= 0) || if !s.DisableShortStop && ((!atr.IsZero() && s.sellPrice.Sub(atr).Compare(lastPrice) >= 0) ||
lastPrice.Sub(s.sellPrice).Div(s.sellPrice).Compare(s.Stoploss) > 0) { lastPrice.Sub(s.sellPrice).Div(s.sellPrice).Compare(s.Stoploss) > 0) {
buyall = true buyall = true
s.bottomPrice = fixedpoint.Zero reason = 4
// calculate report
pnlRate := lastPrice.Sub(s.sellPrice).Div(lastPrice).Float64()
percentAvgSLfromSL = percentAvgSLfromSL*float64(counterSLfromSL) + pnlRate
counterSLfromSL += 1
percentAvgSLfromSL /= float64(counterSLfromSL)
} }
} }
if buyall { if buyall {
log.Warnf("buyall TPSL %v %v", s.Position.GetBase(), quoteBalance) log.Warnf("buyall TPSL %v %v", s.Position.GetBase(), quoteBalance)
if s.ClosePosition(ctx) { p := s.sellPrice
if order, ok := s.ClosePosition(ctx); order != nil && ok {
if takeProfit { if takeProfit {
log.Errorf("takeprofit buy at %v, avg %v, l: %v, atrx2: %v", lastPrice, spBack, bottomBack, atrx2) log.Errorf("takeprofit buy at %v, avg %v, l: %v, atrx2: %v", lastPrice, spBack, bottomBack, atrx2)
} else { } else {
log.Errorf("stoploss buy at %v, avg %v, l: %v, atrx2: %v", lastPrice, spBack, bottomBack, atrx2) log.Errorf("stoploss buy at %v, avg %v, l: %v, atrx2: %v", lastPrice, spBack, bottomBack, atrx2)
} }
// calculate report
if s.Record {
log.Error("record ba")
}
var pnlRate float64
if takeProfit {
pnlRate = p.Sub(lastPrice).Div(lastPrice).Float64()
} else {
pnlRate = lastPrice.Sub(p).Div(lastPrice).Float64()
}
switch reason {
case 0:
percentAvgTPfromCCI = percentAvgTPfromCCI*float64(counterTPfromCCI) + pnlRate
counterTPfromCCI += 1
percentAvgTPfromCCI /= float64(counterTPfromCCI)
break
case 1:
percentAvgTPfromLongShort = percentAvgTPfromLongShort*float64(counterTPfromLongShort) + pnlRate
counterTPfromLongShort += 1
percentAvgTPfromLongShort /= float64(counterTPfromLongShort)
break
case 2:
percentAvgTPfromAtr = percentAvgTPfromAtr*float64(counterTPfromAtr) + pnlRate
counterTPfromAtr += 1
percentAvgTPfromAtr /= float64(counterTPfromAtr)
break
case 3:
percentAvgTPfromPeak = percentAvgTPfromPeak*float64(counterTPfromPeak) + pnlRate
counterTPfromPeak += 1
percentAvgTPfromPeak /= float64(counterTPfromPeak)
break
case 4:
percentAvgSLfromSL = percentAvgSLfromSL*float64(counterSLfromSL) + pnlRate
counterSLfromSL += 1
percentAvgSLfromSL /= float64(counterSLfromSL)
break
}
} }
} }
} }
buyOrderTPSL := func(price fixedpoint.Value) { buyOrderTPSL := func(price fixedpoint.Value) {
if s.Position.GetBase().IsZero() { lastPrice := s.GetLastPrice()
base := s.Position.GetBase().Abs()
if base.Mul(lastPrice).Compare(s.Market.MinNotional) < 0 || base.Compare(s.Market.MinQuantity) < 0 {
return return
} }
if s.buyPrice.IsZero() { if s.buyPrice.IsZero() {
@ -826,15 +967,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
baseBalance := balances[s.Market.BaseCurrency].Available baseBalance := balances[s.Market.BaseCurrency].Available
atr := fixedpoint.NewFromFloat(s.atr.Last()) atr := fixedpoint.NewFromFloat(s.atr.Last())
atrx2 := fixedpoint.NewFromFloat(s.atr.Last() * 2) atrx2 := fixedpoint.NewFromFloat(s.atr.Last() * 2)
lastPrice := price
var ok bool
if s.Environment.IsBackTesting() {
lastPrice, ok = session.LastPrice(s.Symbol)
if !ok {
log.Errorf("cannot get last price")
return
}
}
sellall := false sellall := false
if s.peakPrice.IsZero() || s.peakPrice.Compare(price) < 0 { if s.peakPrice.IsZero() || s.peakPrice.Compare(price) < 0 {
s.peakPrice = price s.peakPrice = price
@ -842,43 +974,30 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
takeProfit := false takeProfit := false
peakBack := s.peakPrice peakBack := s.peakPrice
bpBack := s.buyPrice bpBack := s.buyPrice
if !baseBalance.IsZero() { reason := -1
if baseBalance.Compare(s.Market.MinQuantity) >= 0 && baseBalance.Mul(lastPrice).Compare(s.Market.MinNotional) >= 0 {
shortSignal := types.CrossUnder(s.ewo, s.ewoSignal) shortSignal := types.CrossUnder(s.ewo, s.ewoSignal)
// TP // TP
if !atr.IsZero() && s.peakPrice.Sub(atr).Compare(lastPrice) >= 0 &&
lastPrice.Compare(s.buyPrice) > 0 {
sellall = true
s.peakPrice = fixedpoint.Zero
takeProfit = true
// calculate report
pnlRate := lastPrice.Sub(s.buyPrice).Div(s.buyPrice).Float64()
percentAvgTPfromPeak = percentAvgTPfromPeak*float64(counterTPfromPeak) + pnlRate
counterTPfromPeak += 1
percentAvgTPfromPeak /= float64(counterTPfromPeak)
}
base := fixedpoint.NewFromFloat(s.ma34.Last()) base := fixedpoint.NewFromFloat(s.ma34.Last())
if lastPrice.Compare(s.buyPrice) > 0 && (s.ccis.SellSignal() || shortSignal.Last() || (!atrx2.IsZero() && base.Add(atrx2).Compare(lastPrice) <= 0)) { if lastPrice.Compare(s.buyPrice) > 0 && (s.ccis.SellSignal() || shortSignal.Last() || (!atrx2.IsZero() && base.Add(atrx2).Compare(lastPrice) <= 0)) {
sellall = true sellall = true
s.peakPrice = fixedpoint.Zero
takeProfit = true takeProfit = true
// calculate report // calculate report
pnlRate := lastPrice.Sub(s.buyPrice).Div(s.buyPrice).Float64()
if s.ccis.SellSignal() { if s.ccis.SellSignal() {
percentAvgTPfromCCI = percentAvgTPfromCCI*float64(counterTPfromCCI) + pnlRate reason = 0
counterTPfromCCI += 1
percentAvgTPfromCCI /= float64(counterTPfromCCI)
} else if shortSignal.Last() { } else if shortSignal.Last() {
percentAvgTPfromLongShort = percentAvgTPfromLongShort*float64(counterTPfromLongShort) + pnlRate reason = 1
counterTPfromLongShort += 1
percentAvgTPfromLongShort /= float64(counterTPfromLongShort)
} else { } else {
percentAvgTPfromAtr = percentAvgTPfromAtr*float64(counterTPfromAtr) + pnlRate reason = 2
counterTPfromAtr += 1
percentAvgTPfromAtr /= float64(counterTPfromAtr)
} }
} }
if !atr.IsZero() && s.peakPrice.Sub(atr).Compare(lastPrice) >= 0 &&
lastPrice.Compare(s.buyPrice) > 0 {
sellall = true
takeProfit = true
reason = 3
}
// SL // SL
/*if s.peakPrice.Sub(lastPrice).Div(s.peakPrice).Compare(s.Stoploss) > 0 || /*if s.peakPrice.Sub(lastPrice).Div(s.peakPrice).Compare(s.Stoploss) > 0 ||
@ -892,24 +1011,56 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
if !s.DisableLongStop && (s.buyPrice.Sub(lastPrice).Div(s.buyPrice).Compare(s.Stoploss) > 0 || if !s.DisableLongStop && (s.buyPrice.Sub(lastPrice).Div(s.buyPrice).Compare(s.Stoploss) > 0 ||
(!atr.IsZero() && s.buyPrice.Sub(atr).Compare(lastPrice) >= 0)) { (!atr.IsZero() && s.buyPrice.Sub(atr).Compare(lastPrice) >= 0)) {
sellall = true sellall = true
s.peakPrice = fixedpoint.Zero reason = 4
// calculate report
pnlRate := s.buyPrice.Sub(lastPrice).Div(s.buyPrice).Float64()
percentAvgSLfromSL = percentAvgSLfromSL*float64(counterSLfromSL) + pnlRate
counterSLfromSL += 1
percentAvgSLfromSL /= float64(counterSLfromSL)
} }
} }
if sellall { if sellall {
log.Warnf("sellall TPSL %v", s.Position.GetBase()) log.Warnf("sellall TPSL %v", s.Position.GetBase())
if s.ClosePosition(ctx) { p := s.buyPrice
if order, ok := s.ClosePosition(ctx); order != nil && ok {
if takeProfit { if takeProfit {
log.Errorf("takeprofit sell at %v, avg %v, h: %v, atrx2: %v", lastPrice, bpBack, peakBack, atrx2) log.Errorf("takeprofit sell at %v, avg %v, h: %v, atrx2: %v", lastPrice, bpBack, peakBack, atrx2)
} else { } else {
log.Errorf("stoploss sell at %v, avg %v, h: %v, atrx2: %v", lastPrice, bpBack, peakBack, atrx2) log.Errorf("stoploss sell at %v, avg %v, h: %v, atrx2: %v", lastPrice, bpBack, peakBack, atrx2)
} }
// calculate report
if s.Record {
log.Error("record sa")
}
var pnlRate float64
if takeProfit {
pnlRate = lastPrice.Sub(p).Div(p).Float64()
} else {
pnlRate = p.Sub(lastPrice).Div(p).Float64()
}
switch reason {
case 0:
percentAvgTPfromCCI = percentAvgTPfromCCI*float64(counterTPfromCCI) + pnlRate
counterTPfromCCI += 1
percentAvgTPfromCCI /= float64(counterTPfromCCI)
break
case 1:
percentAvgTPfromLongShort = percentAvgTPfromLongShort*float64(counterTPfromLongShort) + pnlRate
counterTPfromLongShort += 1
percentAvgTPfromLongShort /= float64(counterTPfromLongShort)
break
case 2:
percentAvgTPfromAtr = percentAvgTPfromAtr*float64(counterTPfromAtr) + pnlRate
counterTPfromAtr += 1
percentAvgTPfromAtr /= float64(counterTPfromAtr)
break
case 3:
percentAvgTPfromPeak = percentAvgTPfromPeak*float64(counterTPfromPeak) + pnlRate
counterTPfromPeak += 1
percentAvgTPfromPeak /= float64(counterTPfromPeak)
break
case 4:
percentAvgSLfromSL = percentAvgSLfromSL*float64(counterSLfromSL) + pnlRate
counterSLfromSL += 1
percentAvgSLfromSL /= float64(counterSLfromSL)
break
}
} }
} }
} }
@ -1028,65 +1179,74 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
price := kline.Close.Sub(atr.Div(types.Two)) price := kline.Close.Sub(atr.Div(types.Two))
// if total asset (including locked) could be used to buy // if total asset (including locked) could be used to buy
if quoteBalance.Div(price).Compare(s.Market.MinQuantity) >= 0 && quoteBalance.Compare(s.Market.MinNotional) >= 0 { if quoteBalance.Div(price).Compare(s.Market.MinQuantity) >= 0 && quoteBalance.Compare(s.Market.MinNotional) >= 0 {
// cancel all buy orders to release lock // cancel all orders to release lock
s.CancelAll(ctx, types.SideTypeBuy) s.CancelAll(ctx)
// backup, since the s.sellPrice will be cleared when doing ClosePosition
sellPrice := s.sellPrice
// calculate report // calculate report
if !s.sellPrice.IsZero() { if closeOrder, _ := s.PlaceBuyOrder(ctx, price); closeOrder != nil {
if price.Compare(s.sellPrice) > 0 { if s.Record {
pnlRate := price.Sub(s.sellPrice).Div(price).Float64() log.Error("record l")
percentAvgTPfromOrder = percentAvgTPfromOrder*float64(counterTPfromOrder) + pnlRate }
counterTPfromOrder += 1 if !sellPrice.IsZero() {
percentAvgTPfromOrder /= float64(counterTPfromOrder) if lastPrice.Compare(sellPrice) > 0 {
pnlRate := lastPrice.Sub(sellPrice).Div(lastPrice).Float64()
percentAvgTPfromOrder = percentAvgTPfromOrder*float64(counterTPfromOrder) + pnlRate
counterTPfromOrder += 1
percentAvgTPfromOrder /= float64(counterTPfromOrder)
} else {
pnlRate := sellPrice.Sub(lastPrice).Div(lastPrice).Float64()
percentAvgSLfromOrder = percentAvgSLfromOrder*float64(counterSLfromOrder) + pnlRate
counterSLfromOrder += 1
percentAvgSLfromOrder /= float64(counterSLfromOrder)
}
} else { } else {
pnlRate := s.sellPrice.Sub(price).Div(price).Float64() panic("no sell price")
percentAvgSLfromOrder = percentAvgSLfromOrder*float64(counterSLfromOrder) + pnlRate
counterSLfromOrder += 1
percentAvgSLfromOrder /= float64(counterSLfromOrder)
} }
} }
s.PlaceBuyOrder(ctx, price)
} }
} }
if shortSignal.Index(1) && !longSignal.Last() && IsBear { if shortSignal.Index(1) && !longSignal.Last() && IsBear {
price := kline.Close.Add(atr.Div(types.Two)) price := kline.Close.Add(atr.Div(types.Two))
// if total asset (including locked) could be used to sell // if total asset (including locked) could be used to sell
if baseBalance.Mul(price).Compare(s.Market.MinNotional) >= 0 && baseBalance.Compare(s.Market.MinQuantity) >= 0 { if baseBalance.Mul(price).Compare(s.Market.MinNotional) >= 0 && baseBalance.Compare(s.Market.MinQuantity) >= 0 {
// cancel all sell orders to release lock // cancel all orders to release lock
s.CancelAll(ctx, types.SideTypeSell) s.CancelAll(ctx)
// backup, since the s.buyPrice will be cleared when doing ClosePosition
buyPrice := s.buyPrice
// calculate report // calculate report
if !s.buyPrice.IsZero() { if closeOrder, _ := s.PlaceSellOrder(ctx, price); closeOrder != nil {
if price.Compare(s.buyPrice) > 0 { if s.Record {
pnlRate := price.Sub(s.buyPrice).Div(s.buyPrice).Float64() log.Error("record s")
percentAvgTPfromOrder = percentAvgTPfromOrder*float64(counterTPfromOrder) + pnlRate }
counterTPfromOrder += 1 if !buyPrice.IsZero() {
percentAvgTPfromOrder /= float64(counterTPfromOrder) if lastPrice.Compare(buyPrice) > 0 {
pnlRate := lastPrice.Sub(buyPrice).Div(buyPrice).Float64()
percentAvgTPfromOrder = percentAvgTPfromOrder*float64(counterTPfromOrder) + pnlRate
counterTPfromOrder += 1
percentAvgTPfromOrder /= float64(counterTPfromOrder)
} else {
pnlRate := buyPrice.Sub(lastPrice).Div(buyPrice).Float64()
percentAvgSLfromOrder = percentAvgSLfromOrder*float64(counterSLfromOrder) + pnlRate
counterSLfromOrder += 1
percentAvgSLfromOrder /= float64(counterSLfromOrder)
}
} else { } else {
pnlRate := s.buyPrice.Sub(price).Div(s.buyPrice).Float64() panic("no buy price")
percentAvgSLfromOrder = percentAvgSLfromOrder*float64(counterSLfromOrder) + pnlRate
counterSLfromOrder += 1
percentAvgSLfromOrder /= float64(counterSLfromOrder)
} }
} }
s.PlaceSellOrder(ctx, price)
} }
} }
}) })
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) { s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
log.Infof("canceling active orders...") log.Infof("canceling active orders...")
s.CancelAll(ctx)
var toCancel []types.Order
for _, order := range s.orderStore.Orders() {
if order.Status == types.OrderStatusNew || order.Status == types.OrderStatusPartiallyFilled {
toCancel = append(toCancel, order)
}
}
if err := orderExecutor.CancelOrders(ctx, toCancel...); err != nil {
log.WithError(err).Errorf("cancel order error")
}
s.tradeCollector.Process() s.tradeCollector.Process()
color.HiBlue("---- Trade Report (Without Fee) ----") color.HiBlue("---- Trade Report (Without Fee) ----")
color.HiBlue("TP:") color.HiBlue("TP:")

View File

@ -9,3 +9,8 @@ func tryLock(lock *sync.RWMutex) bool {
lock.Lock() lock.Lock()
return true return true
} }
func tryRLock(lock *sync.RWMutex) bool {
lock.RLock()
return true
}

View File

@ -8,3 +8,7 @@ import "sync"
func tryLock(lock *sync.RWMutex) bool { func tryLock(lock *sync.RWMutex) bool {
return lock.TryLock() return lock.TryLock()
} }
func tryRLock(lock *sync.RWMutex) bool {
return lock.TryRLock()
}

View File

@ -122,7 +122,7 @@ func (p *Position) NewProfit(trade Trade, profit, netProfit fixedpoint.Value) Pr
func (p *Position) NewClosePositionOrder(percentage fixedpoint.Value) *SubmitOrder { func (p *Position) NewClosePositionOrder(percentage fixedpoint.Value) *SubmitOrder {
base := p.GetBase() base := p.GetBase()
quantity := base.Mul(percentage) quantity := base.Mul(percentage).Abs()
if quantity.Compare(p.Market.MinQuantity) < 0 { if quantity.Compare(p.Market.MinQuantity) < 0 {
return nil return nil
} }