feature: SLTP from bookticker. fix: bookTicker typename, depth buffer error message

This commit is contained in:
zenix 2022-05-12 19:40:48 +09:00
parent 668328dd16
commit 71fe6c2d26
5 changed files with 222 additions and 133 deletions

View File

@ -582,7 +582,7 @@ func (session *ExchangeSession) MarketDataStore(symbol string) (s *MarketDataSto
return s, ok return s, ok
} }
// MarketDataStore returns the market data store of a symbol // OrderBook returns the personal orderbook of a symbol
func (session *ExchangeSession) OrderBook(symbol string) (s *types.StreamOrderBook, ok bool) { func (session *ExchangeSession) OrderBook(symbol string) (s *types.StreamOrderBook, ok bool) {
s, ok = session.orderBooks[symbol] s, ok = session.orderBooks[symbol]
return s, ok return s, ok
@ -935,9 +935,9 @@ func (session *ExchangeSession) SlackAttachment() slack.Attachment {
return slack.Attachment{ return slack.Attachment{
// Pretext: "", // Pretext: "",
// Text: text, // Text: text,
Title: session.Name, Title: session.Name,
Fields: fields, Fields: fields,
FooterIcon: footerIcon, FooterIcon: footerIcon,
Footer: util.Render("update time {{ . }}", time.Now().Format(time.RFC822)), Footer: util.Render("update time {{ . }}", time.Now().Format(time.RFC822)),
} }
} }

View File

@ -117,13 +117,14 @@ func (b *Buffer) AddUpdate(o types.SliceOrderBook, firstUpdateID int64, finalArg
if u.FirstUpdateID > b.finalUpdateID+1 { if u.FirstUpdateID > b.finalUpdateID+1 {
// emitReset will reset the once outside the mutex lock section // emitReset will reset the once outside the mutex lock section
b.buffer = []Update{u} b.buffer = []Update{u}
finalUpdateID = b.finalUpdateID
b.resetSnapshot() b.resetSnapshot()
b.emitReset() b.emitReset()
b.mu.Unlock() b.mu.Unlock()
return fmt.Errorf("found missing update between finalUpdateID %d and firstUpdateID %d, diff: %d", return fmt.Errorf("found missing update between finalUpdateID %d and firstUpdateID %d, diff: %d",
b.finalUpdateID+1, finalUpdateID+1,
u.FirstUpdateID, u.FirstUpdateID,
u.FirstUpdateID-b.finalUpdateID) u.FirstUpdateID-finalUpdateID)
} }
log.Debugf("depth update id %d -> %d", b.finalUpdateID, u.FinalUpdateID) log.Debugf("depth update id %d -> %d", b.finalUpdateID, u.FinalUpdateID)
@ -142,6 +143,7 @@ func (b *Buffer) fetchAndPush() error {
log.Debugf("fetched depth snapshot, final update id %d", finalUpdateID) log.Debugf("fetched depth snapshot, final update id %d", finalUpdateID)
b.mu.Lock() b.mu.Lock()
if len(b.buffer) > 0 { if len(b.buffer) > 0 {
// the snapshot is too early // the snapshot is too early
if finalUpdateID < b.buffer[0].FirstUpdateID { if finalUpdateID < b.buffer[0].FirstUpdateID {

View File

@ -1428,9 +1428,22 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
// QueryDepth query the order book depth of a symbol // QueryDepth query the order book depth of a symbol
func (e *Exchange) QueryDepth(ctx context.Context, symbol string) (snapshot types.SliceOrderBook, finalUpdateID int64, err error) { func (e *Exchange) QueryDepth(ctx context.Context, symbol string) (snapshot types.SliceOrderBook, finalUpdateID int64, err error) {
response, err := e.client.NewDepthService().Symbol(symbol).Do(ctx) var response *binance.DepthResponse
if err != nil { if e.IsFutures {
return snapshot, finalUpdateID, err res, err := e.futuresClient.NewDepthService().Symbol(symbol).Do(ctx)
if err != nil {
return snapshot, finalUpdateID, err
}
response = &binance.DepthResponse{
LastUpdateID: res.LastUpdateID,
Bids: res.Bids,
Asks: res.Asks,
}
} else {
response, err = e.client.NewDepthService().Symbol(symbol).Do(ctx)
if err != nil {
return snapshot, finalUpdateID, err
}
} }
snapshot.Symbol = symbol snapshot.Symbol = symbol

View File

@ -113,17 +113,17 @@ func (e *ExecutionReportEvent) Order() (*types.Order, error) {
orderCreationTime := time.Unix(0, e.OrderCreationTime*int64(time.Millisecond)) orderCreationTime := time.Unix(0, e.OrderCreationTime*int64(time.Millisecond))
return &types.Order{ return &types.Order{
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
ClientOrderID: e.ClientOrderID, ClientOrderID: e.ClientOrderID,
Symbol: e.Symbol, Symbol: e.Symbol,
Side: toGlobalSideType(binance.SideType(e.Side)), Side: toGlobalSideType(binance.SideType(e.Side)),
Type: toGlobalOrderType(binance.OrderType(e.OrderType)), Type: toGlobalOrderType(binance.OrderType(e.OrderType)),
Quantity: e.OrderQuantity, Quantity: e.OrderQuantity,
Price: e.OrderPrice, Price: e.OrderPrice,
StopPrice: e.StopPrice, StopPrice: e.StopPrice,
TimeInForce: types.TimeInForce(e.TimeInForce), TimeInForce: types.TimeInForce(e.TimeInForce),
IsFutures: false, IsFutures: false,
ReduceOnly: false, ReduceOnly: false,
ClosePosition: false, ClosePosition: false,
}, },
Exchange: types.ExchangeBinance, Exchange: types.ExchangeBinance,
IsWorking: e.IsOnBook, IsWorking: e.IsOnBook,
@ -276,7 +276,7 @@ func parseWebSocketEvent(message []byte) (interface{}, error) {
// fmt.Println(str) // fmt.Println(str)
eventType := string(val.GetStringBytes("e")) eventType := string(val.GetStringBytes("e"))
if eventType == "" && IsBookTicker(val) { if eventType == "" && IsBookTicker(val) {
eventType = "bookticker" eventType = "bookTicker"
} }
switch eventType { switch eventType {
@ -284,7 +284,7 @@ func parseWebSocketEvent(message []byte) (interface{}, error) {
var event KLineEvent var event KLineEvent
err := json.Unmarshal([]byte(message), &event) err := json.Unmarshal([]byte(message), &event)
return &event, err return &event, err
case "bookticker": case "bookTicker":
var event BookTickerEvent var event BookTickerEvent
err := json.Unmarshal([]byte(message), &event) err := json.Unmarshal([]byte(message), &event)
event.Event = eventType event.Event = eventType

View File

@ -23,6 +23,7 @@ func init() {
} }
type Strategy struct { type Strategy struct {
*bbgo.Environment
Market types.Market Market types.Market
Session *bbgo.ExchangeSession Session *bbgo.ExchangeSession
UseHeikinAshi bool `json:"useHeikinAshi"` // use heikinashi kline UseHeikinAshi bool `json:"useHeikinAshi"` // use heikinashi kline
@ -45,6 +46,8 @@ type Strategy struct {
heikinAshi *HeikinAshi heikinAshi *HeikinAshi
peakPrice fixedpoint.Value peakPrice fixedpoint.Value
bottomPrice fixedpoint.Value bottomPrice fixedpoint.Value
midPrice fixedpoint.Value
lock sync.RWMutex
} }
func (s *Strategy) ID() string { func (s *Strategy) ID() string {
@ -59,6 +62,10 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
log.Infof("subscribe %s", s.Symbol) log.Infof("subscribe %s", s.Symbol)
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m.String()}) session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m.String()})
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval.String()}) session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval.String()})
if s.Environment != nil && s.Environment.IsBackTesting() {
session.Subscribe(types.BookTickerChannel, s.Symbol, types.SubscribeOptions{})
}
s.SmartStops.Subscribe(session) s.SmartStops.Subscribe(session)
} }
@ -531,15 +538,176 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.SetupIndicators() s.SetupIndicators()
sellOrderTPSL := func(price fixedpoint.Value) {
balances := session.GetAccount().Balances()
quoteBalance := balances[s.Market.QuoteCurrency].Available
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
if !sellPrice.IsZero() {
if s.bottomPrice.IsZero() || s.bottomPrice.Compare(price) > 0 {
s.bottomPrice = price
}
}
takeProfit := false
bottomBack := s.bottomPrice
if !quoteBalance.IsZero() && !sellPrice.IsZero() && !s.DisableShortStop {
// TP
if !atrx2.IsZero() && s.bottomPrice.Add(atrx2).Compare(lastPrice) >= 0 &&
lastPrice.Compare(sellPrice) < 0 {
buyall = true
s.bottomPrice = fixedpoint.Zero
takeProfit = true
}
// SL
if (!atrx2.IsZero() && sellPrice.Add(atrx2).Compare(lastPrice) <= 0) ||
lastPrice.Sub(sellPrice).Div(sellPrice).Compare(s.Stoploss) > 0 {
buyall = true
s.bottomPrice = fixedpoint.Zero
}
}
if buyall {
totalQuantity := quoteBalance.Div(lastPrice)
order := types.SubmitOrder{
Symbol: s.Symbol,
Side: types.SideTypeBuy,
Type: types.OrderTypeMarket,
Quantity: totalQuantity,
Market: s.Market,
}
if s.validateOrder(&order) {
if takeProfit {
log.Errorf("takeprofit buy at %v, avg %v, l: %v, atrx2: %v", lastPrice, sellPrice, bottomBack, atrx2)
} else {
log.Errorf("stoploss buy at %v, avg %v, l: %v, atrx2: %v", lastPrice, sellPrice, bottomBack, atrx2)
}
createdOrders, err := orderExecutor.SubmitOrders(ctx, order)
if err != nil {
log.WithError(err).Errorf("cannot place order")
return
}
log.Infof("stoploss bought order %v", createdOrders)
s.tradeCollector.Process()
}
}
}
buyOrderTPSL := func(price fixedpoint.Value) {
balances := session.GetAccount().Balances()
baseBalance := balances[s.Market.BaseCurrency].Available
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
if !buyPrice.IsZero() {
if s.peakPrice.IsZero() || s.peakPrice.Compare(price) < 0 {
s.peakPrice = price
}
}
takeProfit := false
peakBack := s.peakPrice
if !baseBalance.IsZero() && !buyPrice.IsZero() {
// TP
if !atrx2.IsZero() && s.peakPrice.Sub(atrx2).Compare(lastPrice) >= 0 &&
lastPrice.Compare(buyPrice) > 0 {
sellall = true
s.peakPrice = fixedpoint.Zero
takeProfit = true
}
// SL
if buyPrice.Sub(lastPrice).Div(buyPrice).Compare(s.Stoploss) > 0 ||
(!atrx2.IsZero() && buyPrice.Sub(atrx2).Compare(lastPrice) >= 0) {
sellall = true
s.peakPrice = fixedpoint.Zero
}
}
if sellall {
order := types.SubmitOrder{
Symbol: s.Symbol,
Side: types.SideTypeSell,
Type: types.OrderTypeMarket,
Market: s.Market,
Quantity: baseBalance,
}
if s.validateOrder(&order) {
if takeProfit {
log.Errorf("takeprofit sell at %v, avg %v, h: %v, atrx2: %v", lastPrice, buyPrice, peakBack, atrx2)
} else {
log.Errorf("stoploss sell at %v, avg %v, h: %v, atrx2: %v", lastPrice, buyPrice, peakBack, atrx2)
}
createdOrders, err := orderExecutor.SubmitOrders(ctx, order)
if err != nil {
log.WithError(err).Errorf("cannot place order")
return
}
log.Infof("stoploss sold order %v", createdOrders)
s.tradeCollector.Process()
}
}
}
// set last price by realtime book ticker update
// to trigger TP/SL
session.MarketDataStream.OnBookTickerUpdate(func(ticker types.BookTicker) {
if s.Environment.IsBackTesting() {
return
}
bestBid := ticker.Buy
bestAsk := ticker.Sell
var midPrice fixedpoint.Value
if s.lock.TryLock() {
if !bestAsk.IsZero() && !bestBid.IsZero() {
s.midPrice = bestAsk.Add(bestBid).Div(types.Two)
} else if !bestAsk.IsZero() {
s.midPrice = bestAsk
} else {
s.midPrice = bestBid
}
midPrice = s.midPrice
s.lock.Unlock()
}
if !midPrice.IsZero() {
buyOrderTPSL(midPrice)
sellOrderTPSL(midPrice)
}
//log.Infof("best bid %v, best ask %v, mid %v", bestBid, bestAsk, midPrice)
})
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
if kline.Symbol != s.Symbol { if kline.Symbol != s.Symbol {
return return
} }
var lastPrice fixedpoint.Value
lastPrice, ok := session.LastPrice(s.Symbol) var ok bool
if !ok { if s.Environment.IsBackTesting() {
log.Errorf("cannot get last price") lastPrice, ok = session.LastPrice(s.Symbol)
return if !ok {
log.Errorf("cannot get last price")
return
}
} else {
s.lock.RLock()
lastPrice = s.midPrice
s.lock.RUnlock()
} }
// cancel non-traded orders // cancel non-traded orders
@ -558,12 +726,10 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.tradeCollector.Process() s.tradeCollector.Process()
} }
balances := session.GetAccount().Balances()
baseBalance := balances[s.Market.BaseCurrency].Available
quoteBalance := balances[s.Market.QuoteCurrency].Available
atrx2 := fixedpoint.NewFromFloat(s.atr.Last() * 2) atrx2 := fixedpoint.NewFromFloat(s.atr.Last() * 2)
log.Infof("Get last price: %v, kline: %v, balance[base]: %v balance[quote]: %v, atrx2: %v", if !s.Environment.IsBackTesting() {
lastPrice, kline, baseBalance, quoteBalance, atrx2) log.Infof("Get last price: %v, kline: %v, atrx2: %v", lastPrice, kline, atrx2)
}
// well, only track prices on 1m // well, only track prices on 1m
if kline.Interval == types.Interval1m { if kline.Interval == types.Interval1m {
@ -590,105 +756,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
log.Infof("repost order %v", createdOrders) log.Infof("repost order %v", createdOrders)
s.tradeCollector.Process() s.tradeCollector.Process()
} }
sellall := false
buyall := false
if !buyPrice.IsZero() {
if s.peakPrice.IsZero() || s.peakPrice.Compare(kline.High) < 0 {
s.peakPrice = kline.High
}
}
if !sellPrice.IsZero() { if s.Environment.IsBackTesting() {
if s.bottomPrice.IsZero() || s.bottomPrice.Compare(kline.Low) > 0 { buyOrderTPSL(kline.High)
s.bottomPrice = kline.Low sellOrderTPSL(kline.Low)
}
}
takeProfit := false
peakBack := s.peakPrice
bottomBack := s.bottomPrice
if !baseBalance.IsZero() && !buyPrice.IsZero() {
// TP
if !atrx2.IsZero() && s.peakPrice.Sub(atrx2).Compare(lastPrice) >= 0 &&
lastPrice.Compare(buyPrice) > 0 {
sellall = true
s.peakPrice = fixedpoint.Zero
takeProfit = true
}
// SL
if buyPrice.Sub(lastPrice).Div(buyPrice).Compare(s.Stoploss) > 0 ||
(!atrx2.IsZero() && buyPrice.Sub(atrx2).Compare(lastPrice) >= 0) {
sellall = true
s.peakPrice = fixedpoint.Zero
}
}
if !quoteBalance.IsZero() && !sellPrice.IsZero() && !s.DisableShortStop {
// TP
if !atrx2.IsZero() && s.bottomPrice.Add(atrx2).Compare(lastPrice) >= 0 &&
lastPrice.Compare(sellPrice) < 0 {
buyall = true
s.bottomPrice = fixedpoint.Zero
takeProfit = true
}
// SL
if (!atrx2.IsZero() && sellPrice.Add(atrx2).Compare(lastPrice) <= 0) ||
lastPrice.Sub(sellPrice).Div(sellPrice).Compare(s.Stoploss) > 0 {
buyall = true
s.bottomPrice = fixedpoint.Zero
}
}
if sellall {
order := types.SubmitOrder{
Symbol: s.Symbol,
Side: types.SideTypeSell,
Type: types.OrderTypeMarket,
Market: s.Market,
Quantity: baseBalance,
}
if s.validateOrder(&order) {
if takeProfit {
log.Errorf("takeprofit sell at %v, avg %v, h: %v, atrx2: %v, timestamp: %s", lastPrice, buyPrice, peakBack, atrx2, kline.StartTime)
} else {
log.Errorf("stoploss sell at %v, avg %v, h: %v, atrx2: %v, timestamp %s", lastPrice, buyPrice, peakBack, atrx2, kline.StartTime)
}
createdOrders, err := orderExecutor.SubmitOrders(ctx, order)
if err != nil {
log.WithError(err).Errorf("cannot place order")
return
}
log.Infof("stoploss sold order %v", createdOrders)
s.tradeCollector.Process()
}
}
if buyall {
totalQuantity := quoteBalance.Div(lastPrice)
order := types.SubmitOrder{
Symbol: kline.Symbol,
Side: types.SideTypeBuy,
Type: types.OrderTypeMarket,
Quantity: totalQuantity,
Market: s.Market,
}
if s.validateOrder(&order) {
if takeProfit {
log.Errorf("takeprofit buy at %v, avg %v, l: %v, atrx2: %v, timestamp: %s", lastPrice, sellPrice, bottomBack, atrx2, kline.StartTime)
} else {
log.Errorf("stoploss buy at %v, avg %v, l: %v, atrx2: %v, timestamp: %s", lastPrice, sellPrice, bottomBack, atrx2, kline.StartTime)
}
createdOrders, err := orderExecutor.SubmitOrders(ctx, order)
if err != nil {
log.WithError(err).Errorf("cannot place order")
return
}
log.Infof("stoploss bought order %v", createdOrders)
s.tradeCollector.Process()
}
} }
} }
@ -718,10 +790,12 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
// kline downthrough ma5, ma50 trend down, and ewo < threshold // kline downthrough ma5, ma50 trend down, and ewo < threshold
IsBear := !bull && breakDown && s.ewo.Last() <= mean-2*std IsBear := !bull && breakDown && s.ewo.Last() <= mean-2*std
log.Infof("IsBull: %v, bull: %v, longSignal[1]: %v, shortSignal: %v", if !s.Environment.IsBackTesting() {
IsBull, bull, longSignal.Index(1), shortSignal.Last()) log.Infof("IsBull: %v, bull: %v, longSignal[1]: %v, shortSignal: %v",
log.Infof("IsBear: %v, bear: %v, shortSignal[1]: %v, longSignal: %v", IsBull, bull, longSignal.Index(1), shortSignal.Last())
IsBear, !bull, shortSignal.Index(1), longSignal.Last()) log.Infof("IsBear: %v, bear: %v, shortSignal[1]: %v, longSignal: %v",
IsBear, !bull, shortSignal.Index(1), longSignal.Last())
}
var orders []types.SubmitOrder var orders []types.SubmitOrder
var price fixedpoint.Value var price fixedpoint.Value