2022-06-28 17:58:15 +00:00
package bbgo
2022-06-26 08:31:48 +00:00
import (
"context"
2022-06-29 10:39:16 +00:00
log "github.com/sirupsen/logrus"
2022-06-26 08:31:48 +00:00
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
2022-06-28 17:39:33 +00:00
// CumulatedVolumeTakeProfit
// This exit method cumulate the volume by N bars, if the cumulated volume exceeded a threshold, then we take profit.
//
// To query the historical quote volume, use the following query:
//
// > SELECT start_time, `interval`, quote_volume, open, close FROM binance_klines WHERE symbol = 'ETHUSDT' AND `interval` = '5m' ORDER BY quote_volume DESC LIMIT 20;
2022-06-26 08:31:48 +00:00
type CumulatedVolumeTakeProfit struct {
2022-06-29 10:39:16 +00:00
Symbol string ` json:"symbol" `
2022-06-26 08:31:48 +00:00
types . IntervalWindow
2022-06-29 10:39:16 +00:00
2022-06-26 08:31:48 +00:00
Ratio fixedpoint . Value ` json:"ratio" `
MinQuoteVolume fixedpoint . Value ` json:"minQuoteVolume" `
2022-06-28 17:58:15 +00:00
session * ExchangeSession
orderExecutor * GeneralOrderExecutor
2022-06-26 08:31:48 +00:00
}
2022-06-28 17:58:15 +00:00
func ( s * CumulatedVolumeTakeProfit ) Bind ( session * ExchangeSession , orderExecutor * GeneralOrderExecutor ) {
2022-06-26 08:31:48 +00:00
s . session = session
s . orderExecutor = orderExecutor
position := orderExecutor . Position ( )
store , _ := session . MarketDataStore ( position . Symbol )
2022-07-05 04:15:31 +00:00
session . MarketDataStream . OnKLineClosed ( types . KLineWith ( s . Symbol , s . Interval , func ( kline types . KLine ) {
2022-06-26 08:31:48 +00:00
closePrice := kline . Close
2022-09-14 04:04:12 +00:00
openPrice := kline . Open
2023-11-29 10:26:01 +00:00
if position . IsClosed ( ) || position . IsDust ( closePrice ) || position . IsClosing ( ) {
2022-06-26 08:31:48 +00:00
return
}
roi := position . ROI ( closePrice )
if roi . Sign ( ) < 0 {
return
}
2022-06-29 10:39:16 +00:00
klines , ok := store . KLinesOfInterval ( s . Interval )
if ! ok {
log . Warnf ( "history kline not found" )
return
}
if len ( * klines ) < s . Window {
return
}
var cbv = fixedpoint . Zero
var cqv = fixedpoint . Zero
for i := 0 ; i < s . Window ; i ++ {
last := ( * klines ) [ len ( * klines ) - 1 - i ]
cqv = cqv . Add ( last . QuoteVolume )
cbv = cbv . Add ( last . Volume )
}
2022-09-14 04:04:12 +00:00
if cqv . Compare ( s . MinQuoteVolume ) < 0 {
return
}
2022-06-29 10:39:16 +00:00
2022-09-14 04:04:12 +00:00
// If the closed price is below the open price, it means the sell taker is still strong.
if closePrice . Compare ( openPrice ) < 0 {
log . Infof ( "[CumulatedVolumeTakeProfit] closePrice %f is below openPrice %f, skip taking profit" , closePrice . Float64 ( ) , openPrice . Float64 ( ) )
2022-06-29 10:39:16 +00:00
return
2022-06-26 08:31:48 +00:00
}
2022-09-14 04:04:12 +00:00
2022-09-14 04:32:36 +00:00
upperShadow := kline . GetUpperShadowHeight ( )
lowerShadow := kline . GetLowerShadowHeight ( )
if upperShadow . Compare ( lowerShadow ) > 0 {
log . Infof ( "[CumulatedVolumeTakeProfit] upper shadow is longer than the lower shadow, skip taking profit" )
return
2022-09-14 04:04:12 +00:00
}
Notify ( "[CumulatedVolumeTakeProfit] %s TakeProfit triggered by cumulated volume (window: %d) %f > %f, price = %f" ,
position . Symbol ,
s . Window ,
cqv . Float64 ( ) ,
s . MinQuoteVolume . Float64 ( ) , kline . Close . Float64 ( ) )
if err := orderExecutor . ClosePosition ( context . Background ( ) , fixedpoint . One , "cumulatedVolumeTakeProfit" ) ; err != nil {
log . WithError ( err ) . Errorf ( "close position error" )
}
2022-07-05 04:15:31 +00:00
} ) )
2022-06-26 08:31:48 +00:00
}