feature: post orders for ewo

This commit is contained in:
zenix 2022-04-11 17:07:52 +09:00
parent 42a3737f2e
commit 0fe14c5fe5

View File

@ -6,6 +6,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/indicator" "github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
) )
@ -19,11 +20,12 @@ func init() {
} }
type Strategy struct { type Strategy struct {
Symbol string `json:"symbol"` Symbol string `json:"symbol"`
Interval types.Interval `json:"interval"` Interval types.Interval `json:"interval"`
Threshold float64 `json:"threshold"` // strength threshold Threshold float64 `json:"threshold"` // strength threshold
UseEma bool `json:"useEma"` // use exponential ma or simple ma UseEma bool `json:"useEma"` // use exponential ma or simple ma
SignalWindow int `json:"sigWin"` // signal window SignalWindow int `json:"sigWin"` // signal window
StopLoss fixedpoint.Value `json:"stoploss"` // stop price = latest price * (1 - stoploss)
} }
func (s *Strategy) ID() string { func (s *Strategy) ID() string {
@ -41,21 +43,27 @@ type EwoSignal interface {
} }
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
log.Infof("stoploss: %v", s.StopLoss)
indicatorSet, ok := session.StandardIndicatorSet(s.Symbol) indicatorSet, ok := session.StandardIndicatorSet(s.Symbol)
if !ok { if !ok {
log.Errorf("cannot get indicatorSet of %s", s.Symbol) log.Errorf("cannot get indicatorSet of %s", s.Symbol)
return nil return nil
} }
store, ok := session.MarketDataStore(s.Symbol) //store, ok := session.MarketDataStore(s.Symbol)
//if !ok {
// log.Errorf("cannot get marketdatastore of %s", s.Symbol)
// return nil
//}
market, ok := session.Market(s.Symbol)
if !ok { if !ok {
log.Errorf("cannot get marketdatastore of %s", s.Symbol) log.Errorf("fetch market fail %s", s.Symbol)
return nil return nil
} }
window, ok := store.KLinesOfInterval(s.Interval) //window, ok := store.KLinesOfInterval(s.Interval)
if !ok { //if !ok {
log.Errorf("cannot get klinewindow of %s", s.Interval) // log.Errorf("cannot get klinewindow of %s", s.Interval)
} //}
mid := types.Div(types.Add(window.Open(), window.Close()), 2) //mid := types.Div(types.Add(window.Open(), window.Close()), 2)
var ma5, ma34, ewo types.Series var ma5, ma34, ewo types.Series
if s.UseEma { if s.UseEma {
ma5 = indicatorSet.EWMA(types.IntervalWindow{s.Interval, 5}) ma5 = indicatorSet.EWMA(types.IntervalWindow{s.Interval, 5})
@ -71,18 +79,25 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} else { } else {
ewoSignal = &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}} ewoSignal = &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}}
} }
entryPrice := fixedpoint.Zero
stopPrice := fixedpoint.Zero
inTrade := false
tradeDirectionLong := true
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
if kline.Symbol != s.Symbol || kline.Interval != s.Interval { if kline.Symbol != s.Symbol {
return return
} }
if ewoSignal.Length() == 0 {
// lazy init if kline.Interval == s.Interval {
ewoVals := types.ToReverseArray(ewo) if ewoSignal.Length() == 0 {
for _, ewoValue := range ewoVals { // lazy init
ewoSignal.Update(ewoValue) ewoVals := types.ToReverseArray(ewo)
for _, ewoValue := range ewoVals {
ewoSignal.Update(ewoValue)
}
} else {
ewoSignal.Update(ewo.Last())
} }
} else {
ewoSignal.Update(ewo.Last())
} }
lastPrice, ok := session.LastPrice(s.Symbol) lastPrice, ok := session.LastPrice(s.Symbol)
@ -90,29 +105,179 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
return return
} }
// stoploss
if inTrade {
if tradeDirectionLong && kline.Low.Compare(stopPrice) <= 0 {
balances := session.Account.Balances()
baseBalance := balances[market.BaseCurrency].Available
baseAmount := baseBalance.Mul(lastPrice)
if baseBalance.Sign() <= 0 ||
baseBalance.Compare(market.MinQuantity) < 0 ||
baseAmount.Compare(market.MinNotional) < 0 {
//log.Infof("base balance %v is not enough. stop generating sell orders", baseBalance)
return
}
_, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
Symbol: kline.Symbol,
Side: types.SideTypeSell,
Type: types.OrderTypeMarket,
Quantity: baseBalance,
Market: market,
TimeInForce: types.TimeInForceGTC,
})
if err != nil {
log.WithError(err).Errorf("cannot place order for stoploss")
return
}
log.Warnf("StopLoss Long at %v", lastPrice)
inTrade = false
entryPrice = fixedpoint.Zero
stopPrice = fixedpoint.Zero
return
} else if !tradeDirectionLong && kline.High.Compare(stopPrice) >= 0 {
quoteBalance, ok := session.Account.Balance(market.QuoteCurrency)
if !ok {
return
}
quantityAmount := quoteBalance.Available
totalQuantity := quantityAmount.Div(lastPrice)
if quantityAmount.Sign() <= 0 ||
quantityAmount.Compare(market.MinNotional) < 0 ||
totalQuantity.Compare(market.MinQuantity) < 0 {
//log.Infof("quote balance %v is not enough. stop generating buy orders", quoteBalance)
return
}
_, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
Symbol: kline.Symbol,
Side: types.SideTypeBuy,
Type: types.OrderTypeMarket,
Quantity: totalQuantity,
Market: market,
TimeInForce: types.TimeInForceGTC,
})
if err != nil {
log.WithError(err).Errorf("cannot place order for stoploss")
return
}
log.Warnf("StopLoss Short at %v", lastPrice)
inTrade = false
entryPrice = fixedpoint.Zero
stopPrice = fixedpoint.Zero
return
}
}
if kline.Interval != s.Interval {
return
}
longSignal := types.CrossOver(ewo, ewoSignal) longSignal := types.CrossOver(ewo, ewoSignal)
shortSignal := types.CrossUnder(ewo, ewoSignal) shortSignal := types.CrossUnder(ewo, ewoSignal)
nextVal := types.Predict(mid, 5, 1) IsBull := kline.Close.Compare(kline.Open) > 0
IsBull := mid.Last() < nextVal //nextVal := types.Predict(mid, 3, 1)
log.Warnf("mid %v, last %v, next %v", kline.Mid(), mid.Last(), nextVal) //IsBull := lastPrice.Float64() < nextVal
//log.Warnf("mid %v, last %v, next %v", kline.Mid(), mid.Last(), nextVal)
//log.Warnf("long %v %v, short %v %v", longSignal.Index(1), longSignal.Last(), shortSignal.Index(1), shortSignal.Last())
//log.Infof("(%f, %f, %f) (%f, %f, %f)", ewo.Last(), ewo.Index(1), ewo.Index(2), ewoSignal.Last(), ewoSignal.Index(1), ewoSignal.Index(2))
//log.Infof("long %v %v %v", longSignal.Last(), longSignal.Index(1), ewo.Last() >= ewoSignal.Last() && ewoSignal.Index(1) >= ewo.Index(1))
//log.Infof("short %v %v", shortSignal.Last(), shortSignal.Index(1))
if longSignal.Last() && IsBull { var orders []types.SubmitOrder
if longSignal.Index(1) && !shortSignal.Last() && IsBull {
quoteBalance, ok := session.Account.Balance(market.QuoteCurrency)
if !ok {
return
}
quantityAmount := quoteBalance.Available
totalQuantity := quantityAmount.Div(lastPrice)
if quantityAmount.Sign() <= 0 ||
quantityAmount.Compare(market.MinNotional) < 0 ||
totalQuantity.Compare(market.MinQuantity) < 0 {
log.Infof("quote balance %v is not enough. stop generating buy orders", quoteBalance)
return
}
if ewo.Last() < -s.Threshold { if ewo.Last() < -s.Threshold {
// strong long // strong long
log.Infof("strong long at %v, timestamp: %s", lastPrice, kline.StartTime) log.Infof("strong long at %v, timestamp: %s", lastPrice, kline.StartTime)
} else {
log.Infof("long at %v, timestamp: %s", lastPrice, kline.StartTime) orders = append(orders, types.SubmitOrder{
Symbol: kline.Symbol,
Side: types.SideTypeBuy,
Type: types.OrderTypeLimit,
Price: lastPrice,
Quantity: totalQuantity,
Market: market,
TimeInForce: types.TimeInForceGTC,
})
} else if ewo.Last() < 0 {
log.Infof("long at %v, amount: %v, timestamp: %s", lastPrice, lastPrice.Mul(totalQuantity), kline.StartTime)
// Long // Long
// TODO: smaller quantity?
orders = append(orders, types.SubmitOrder{
Symbol: s.Symbol,
Side: types.SideTypeBuy,
Type: types.OrderTypeLimit,
Price: lastPrice,
Quantity: totalQuantity,
Market: market,
TimeInForce: types.TimeInForceGTC,
})
}
} else if shortSignal.Index(1) && !longSignal.Last() && !IsBull {
balances := session.Account.Balances()
baseBalance := balances[market.BaseCurrency].Available
baseAmount := baseBalance.Mul(lastPrice)
if baseBalance.Sign() <= 0 ||
baseBalance.Compare(market.MinQuantity) < 0 ||
baseAmount.Compare(market.MinNotional) < 0 {
log.Infof("base balance %v is not enough. stop generating sell orders", baseBalance)
return
} }
} else if shortSignal.Last() && !IsBull {
if ewo.Last() > s.Threshold { if ewo.Last() > s.Threshold {
// Strong short // Strong short
log.Infof("strong short at %v, timestamp: %s", lastPrice, kline.StartTime) log.Infof("strong short at %v, timestamp: %s", lastPrice, kline.StartTime)
} else { orders = append(orders, types.SubmitOrder{
Symbol: s.Symbol,
Side: types.SideTypeSell,
Type: types.OrderTypeLimit,
Market: market,
Quantity: baseBalance,
Price: lastPrice,
TimeInForce: types.TimeInForceGTC,
})
} else if ewo.Last() > 0 {
// short // short
log.Infof("short at %v, timestamp: %s", lastPrice, kline.StartTime) log.Infof("short at %v, timestamp: %s", lastPrice, kline.StartTime)
// TODO: smaller quantity?
orders = append(orders, types.SubmitOrder{
Symbol: s.Symbol,
Side: types.SideTypeSell,
Type: types.OrderTypeLimit,
Market: market,
Quantity: baseBalance,
Price: lastPrice,
TimeInForce: types.TimeInForceGTC,
})
} }
} }
if len(orders) > 0 {
createdOrders, err := orderExecutor.SubmitOrders(ctx, orders...)
if err != nil {
log.WithError(err).Errorf("cannot place order")
return
}
entryPrice = lastPrice
inTrade = true
tradeDirectionLong = IsBull
if tradeDirectionLong {
stopPrice = entryPrice.Mul(fixedpoint.One.Sub(s.StopLoss))
} else {
stopPrice = entryPrice.Mul(fixedpoint.One.Add(s.StopLoss))
}
log.Infof("Place orders %v", createdOrders)
}
}) })
return nil return nil
} }