mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
feature: add heikinashi
This commit is contained in:
parent
2f51441256
commit
4ee73149c1
|
@ -9,16 +9,19 @@ exchangeStrategies:
|
|||
|
||||
- on: binance
|
||||
ewo_dgtrd:
|
||||
symbol: MATICUSDT
|
||||
interval: 5m
|
||||
useEma: true
|
||||
sigWin: 5
|
||||
symbol: NEARUSDT
|
||||
interval: 15m
|
||||
useEma: false
|
||||
useSma: false
|
||||
sigWin: 3
|
||||
stoploss: 2%
|
||||
callback: 1%
|
||||
useHeikinAshi: true
|
||||
#stops:
|
||||
#- trailingStop:
|
||||
# callbackRate: 5.1%
|
||||
# closePosition: 20%
|
||||
# minProfit: 2%
|
||||
# minProfit: 1%
|
||||
# interval: 1m
|
||||
# virtual: true
|
||||
|
||||
|
@ -30,16 +33,16 @@ sync:
|
|||
sessions:
|
||||
- binance
|
||||
symbols:
|
||||
- MATICUSDT
|
||||
- NEARUSDT
|
||||
|
||||
backtest:
|
||||
startTime: "2021-04-13"
|
||||
endTime: "2022-04-13"
|
||||
startTime: "2022-03-03"
|
||||
endTime: "2022-04-14"
|
||||
symbols:
|
||||
- MATICUSDT
|
||||
- NEARUSDT
|
||||
sessions: [binance]
|
||||
account:
|
||||
binance:
|
||||
balances:
|
||||
MATIC: 0
|
||||
NEAR: 0
|
||||
USDT: 10000
|
||||
|
|
|
@ -2,6 +2,8 @@ package ewo_dgtrd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -15,23 +17,34 @@ import (
|
|||
const ID = "ewo_dgtrd"
|
||||
|
||||
var log = logrus.WithField("strategy", ID)
|
||||
var modifier = fixedpoint.NewFromFloat(0.99)
|
||||
var modifier = fixedpoint.NewFromFloat(0.995)
|
||||
|
||||
func init() {
|
||||
bbgo.RegisterStrategy(ID, &Strategy{})
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
Market types.Market
|
||||
Session *bbgo.ExchangeSession
|
||||
UseHeikinAshi bool `json:"useHeikinAshi"` // use heikinashi kline
|
||||
Stoploss fixedpoint.Value `json:"stoploss"`
|
||||
Callback fixedpoint.Value `json:"callback"`
|
||||
Symbol string `json:"symbol"`
|
||||
Interval types.Interval `json:"interval"`
|
||||
UseEma bool `json:"useEma"` // use exponential ma or not
|
||||
UseSma bool `json:"useSma"` // if UseEma == false, use simple ma or not
|
||||
SignalWindow int `json:"sigWin"` // signal window
|
||||
|
||||
*bbgo.Graceful
|
||||
//bbgo.SmartStops
|
||||
market types.Market
|
||||
session *bbgo.ExchangeSession
|
||||
bbgo.SmartStops
|
||||
tradeCollector *bbgo.TradeCollector
|
||||
Stoploss fixedpoint.Value `json:"stoploss"`
|
||||
Symbol string `json:"symbol"`
|
||||
Interval types.Interval `json:"interval"`
|
||||
UseEma bool `json:"useEma"` // use exponential ma or simple ma
|
||||
SignalWindow int `json:"sigWin"` // signal window
|
||||
ma5 types.Series
|
||||
ma34 types.Series
|
||||
ewo types.Series
|
||||
ewoSignal types.Series
|
||||
heikinAshi *HeikinAshi
|
||||
peakPrice fixedpoint.Value
|
||||
bottomPrice fixedpoint.Value
|
||||
}
|
||||
|
||||
func (s *Strategy) ID() string {
|
||||
|
@ -39,27 +52,349 @@ func (s *Strategy) ID() string {
|
|||
}
|
||||
|
||||
func (s *Strategy) Initialize() error {
|
||||
//return s.SmartStops.InitializeStopControllers(s.Symbol)
|
||||
return nil
|
||||
return s.SmartStops.InitializeStopControllers(s.Symbol)
|
||||
}
|
||||
|
||||
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||
log.Infof("subscribe %s", s.Symbol)
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval.String()})
|
||||
//s.SmartStops.Subscribe(session)
|
||||
s.SmartStops.Subscribe(session)
|
||||
}
|
||||
|
||||
type EwoSignal interface {
|
||||
type UpdatableSeries interface {
|
||||
types.Series
|
||||
Update(value float64)
|
||||
}
|
||||
|
||||
type EVWMA struct {
|
||||
PV UpdatableSeries
|
||||
V UpdatableSeries
|
||||
}
|
||||
|
||||
func (inc *EVWMA) Last() float64 {
|
||||
return inc.PV.Last() / inc.V.Last()
|
||||
}
|
||||
|
||||
func (inc *EVWMA) Index(i int) float64 {
|
||||
if i >= inc.PV.Length() {
|
||||
return 0
|
||||
}
|
||||
vi := inc.V.Index(i)
|
||||
if vi == 0 {
|
||||
return 0
|
||||
}
|
||||
return inc.PV.Index(i) / vi
|
||||
}
|
||||
|
||||
func (inc *EVWMA) Length() int {
|
||||
pvl := inc.PV.Length()
|
||||
vl := inc.V.Length()
|
||||
if pvl < vl {
|
||||
return pvl
|
||||
}
|
||||
return vl
|
||||
}
|
||||
|
||||
func (inc *EVWMA) Update(kline types.KLine) {
|
||||
inc.PV.Update(kline.Close.Mul(kline.Volume).Float64())
|
||||
inc.V.Update(kline.Volume.Float64())
|
||||
}
|
||||
|
||||
func (inc *EVWMA) UpdateVal(price float64, vol float64) {
|
||||
inc.PV.Update(price * vol)
|
||||
inc.V.Update(vol)
|
||||
}
|
||||
|
||||
type Queue struct {
|
||||
arr []float64
|
||||
size int
|
||||
}
|
||||
|
||||
func NewQueue(size int) *Queue {
|
||||
return &Queue{
|
||||
arr: make([]float64, 0, size),
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
func (inc *Queue) Last() float64 {
|
||||
if len(inc.arr) == 0 {
|
||||
return 0
|
||||
}
|
||||
return inc.arr[len(inc.arr)-1]
|
||||
}
|
||||
|
||||
func (inc *Queue) Index(i int) float64 {
|
||||
if len(inc.arr)-i-1 < 0 {
|
||||
return 0
|
||||
}
|
||||
return inc.arr[len(inc.arr)-i-1]
|
||||
}
|
||||
|
||||
func (inc *Queue) Length() int {
|
||||
return len(inc.arr)
|
||||
}
|
||||
|
||||
func (inc *Queue) Update(v float64) {
|
||||
inc.arr = append(inc.arr, v)
|
||||
if len(inc.arr) > inc.size {
|
||||
inc.arr = inc.arr[len(inc.arr)-inc.size:]
|
||||
}
|
||||
}
|
||||
|
||||
type HeikinAshi struct {
|
||||
Close *Queue
|
||||
Open *Queue
|
||||
High *Queue
|
||||
Low *Queue
|
||||
Volume *Queue
|
||||
}
|
||||
|
||||
func NewHeikinAshi(size int) *HeikinAshi {
|
||||
return &HeikinAshi{
|
||||
Close: NewQueue(size),
|
||||
Open: NewQueue(size),
|
||||
High: NewQueue(size),
|
||||
Low: NewQueue(size),
|
||||
Volume: NewQueue(size),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HeikinAshi) Print() string {
|
||||
return fmt.Sprintf("Heikin c: %.3f, o: %.3f, h: %.3f, l: %.3f, v: %.3f",
|
||||
s.Close.Last(),
|
||||
s.Open.Last(),
|
||||
s.High.Last(),
|
||||
s.Low.Last(),
|
||||
s.Volume.Last())
|
||||
}
|
||||
|
||||
func (inc *HeikinAshi) Update(kline types.KLine) {
|
||||
open := kline.Open.Float64()
|
||||
cloze := kline.Close.Float64()
|
||||
high := kline.High.Float64()
|
||||
low := kline.Low.Float64()
|
||||
newClose := (open + high + low + cloze) / 4.
|
||||
newOpen := (inc.Open.Last() + inc.Close.Last()) / 2.
|
||||
inc.Close.Update(newClose)
|
||||
inc.Open.Update(newOpen)
|
||||
inc.High.Update(math.Max(math.Max(high, newOpen), newClose))
|
||||
inc.Low.Update(math.Min(math.Min(low, newOpen), newClose))
|
||||
inc.Volume.Update(kline.Volume.Float64())
|
||||
}
|
||||
|
||||
func (s *Strategy) SetupIndicators() {
|
||||
store, ok := s.Session.MarketDataStore(s.Symbol)
|
||||
if !ok {
|
||||
log.Errorf("cannot get marketdatastore of %s", s.Symbol)
|
||||
return
|
||||
}
|
||||
|
||||
if s.UseHeikinAshi {
|
||||
s.heikinAshi = NewHeikinAshi(50)
|
||||
store.OnKLineWindowUpdate(func(interval types.Interval, window types.KLineWindow) {
|
||||
if s.Interval != interval {
|
||||
return
|
||||
}
|
||||
if s.heikinAshi.Close.Length() == 0 {
|
||||
for _, kline := range window {
|
||||
s.heikinAshi.Update(kline)
|
||||
}
|
||||
} else {
|
||||
s.heikinAshi.Update(window[len(window)-1])
|
||||
}
|
||||
})
|
||||
if s.UseEma {
|
||||
ema5 := &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}}
|
||||
ema34 := &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}}
|
||||
store.OnKLineWindowUpdate(func(interval types.Interval, _ types.KLineWindow) {
|
||||
if s.Interval != interval {
|
||||
return
|
||||
}
|
||||
if ema5.Length() == 0 {
|
||||
closes := types.ToReverseArray(s.heikinAshi.Close)
|
||||
for _, cloze := range closes {
|
||||
ema5.Update(cloze)
|
||||
ema34.Update(cloze)
|
||||
}
|
||||
} else {
|
||||
cloze := s.heikinAshi.Close.Last()
|
||||
ema5.Update(cloze)
|
||||
ema34.Update(cloze)
|
||||
}
|
||||
})
|
||||
s.ma5 = ema5
|
||||
s.ma34 = ema34
|
||||
} else if s.UseSma {
|
||||
sma5 := &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}}
|
||||
sma34 := &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}}
|
||||
store.OnKLineWindowUpdate(func(interval types.Interval, _ types.KLineWindow) {
|
||||
if s.Interval != interval {
|
||||
return
|
||||
}
|
||||
if sma5.Length() == 0 {
|
||||
closes := types.ToReverseArray(s.heikinAshi.Close)
|
||||
for _, cloze := range closes {
|
||||
sma5.Update(cloze)
|
||||
sma34.Update(cloze)
|
||||
}
|
||||
} else {
|
||||
cloze := s.heikinAshi.Close.Last()
|
||||
sma5.Update(cloze)
|
||||
sma34.Update(cloze)
|
||||
}
|
||||
})
|
||||
s.ma5 = sma5
|
||||
s.ma34 = sma34
|
||||
} else {
|
||||
evwma5 := &EVWMA{
|
||||
PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}},
|
||||
V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}},
|
||||
}
|
||||
evwma34 := &EVWMA{
|
||||
PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}},
|
||||
V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}},
|
||||
}
|
||||
store.OnKLineWindowUpdate(func(interval types.Interval, _ types.KLineWindow) {
|
||||
if s.Interval != interval {
|
||||
return
|
||||
}
|
||||
if evwma5.PV.Length() == 0 {
|
||||
for i := s.heikinAshi.Close.Length() - 1; i >= 0; i-- {
|
||||
price := s.heikinAshi.Close.Index(i)
|
||||
vol := s.heikinAshi.Volume.Index(i)
|
||||
evwma5.UpdateVal(price, vol)
|
||||
evwma34.UpdateVal(price, vol)
|
||||
}
|
||||
} else {
|
||||
price := s.heikinAshi.Close.Last()
|
||||
vol := s.heikinAshi.Volume.Last()
|
||||
evwma5.UpdateVal(price, vol)
|
||||
evwma34.UpdateVal(price, vol)
|
||||
}
|
||||
})
|
||||
s.ma5 = evwma5
|
||||
s.ma34 = evwma34
|
||||
}
|
||||
} else {
|
||||
indicatorSet, ok := s.Session.StandardIndicatorSet(s.Symbol)
|
||||
if !ok {
|
||||
log.Errorf("cannot get indicator set of %s", s.Symbol)
|
||||
return
|
||||
}
|
||||
if s.UseEma {
|
||||
s.ma5 = indicatorSet.EWMA(types.IntervalWindow{s.Interval, 5})
|
||||
s.ma34 = indicatorSet.EWMA(types.IntervalWindow{s.Interval, 34})
|
||||
} else if s.UseSma {
|
||||
s.ma5 = indicatorSet.SMA(types.IntervalWindow{s.Interval, 5})
|
||||
s.ma34 = indicatorSet.SMA(types.IntervalWindow{s.Interval, 34})
|
||||
} else {
|
||||
evwma5 := &EVWMA{
|
||||
PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}},
|
||||
V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 5}},
|
||||
}
|
||||
evwma34 := &EVWMA{
|
||||
PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}},
|
||||
V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, 34}},
|
||||
}
|
||||
store.OnKLineWindowUpdate(func(interval types.Interval, window types.KLineWindow) {
|
||||
if s.Interval != interval {
|
||||
return
|
||||
}
|
||||
if evwma5.PV.Length() == 0 {
|
||||
for _, kline := range window {
|
||||
evwma5.Update(kline)
|
||||
evwma34.Update(kline)
|
||||
}
|
||||
} else {
|
||||
evwma5.Update(window[len(window)-1])
|
||||
evwma34.Update(window[len(window)-1])
|
||||
}
|
||||
})
|
||||
s.ma5 = evwma5
|
||||
s.ma34 = evwma34
|
||||
}
|
||||
}
|
||||
|
||||
s.ewo = types.Mul(types.Minus(types.Div(s.ma5, s.ma34), 1.0), 100.)
|
||||
if s.UseEma {
|
||||
sig := &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}}
|
||||
store.OnKLineWindowUpdate(func(interval types.Interval, _ types.KLineWindow) {
|
||||
if interval != s.Interval {
|
||||
return
|
||||
}
|
||||
|
||||
if sig.Length() == 0 {
|
||||
// lazy init
|
||||
ewoVals := types.ToReverseArray(s.ewo)
|
||||
for _, ewoValue := range ewoVals {
|
||||
sig.Update(ewoValue)
|
||||
}
|
||||
} else {
|
||||
sig.Update(s.ewo.Last())
|
||||
}
|
||||
})
|
||||
s.ewoSignal = sig
|
||||
} else if s.UseSma {
|
||||
sig := &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}}
|
||||
store.OnKLineWindowUpdate(func(interval types.Interval, _ types.KLineWindow) {
|
||||
if interval != s.Interval {
|
||||
return
|
||||
}
|
||||
|
||||
if sig.Length() == 0 {
|
||||
// lazy init
|
||||
ewoVals := types.ToReverseArray(s.ewo)
|
||||
for _, ewoValue := range ewoVals {
|
||||
sig.Update(ewoValue)
|
||||
}
|
||||
} else {
|
||||
sig.Update(s.ewo.Last())
|
||||
}
|
||||
})
|
||||
s.ewoSignal = sig
|
||||
} else {
|
||||
sig := &EVWMA{
|
||||
PV: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}},
|
||||
V: &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}},
|
||||
}
|
||||
store.OnKLineWindowUpdate(func(interval types.Interval, window types.KLineWindow) {
|
||||
if interval != s.Interval {
|
||||
return
|
||||
}
|
||||
var vol float64
|
||||
if sig.Length() == 0 {
|
||||
// lazy init
|
||||
ewoVals := types.ToReverseArray(s.ewo)
|
||||
for i, ewoValue := range ewoVals {
|
||||
if s.UseHeikinAshi {
|
||||
vol = s.heikinAshi.Volume.Index(len(ewoVals) - 1 - i)
|
||||
} else {
|
||||
vol = window[len(ewoVals)-1-i].Volume.Float64()
|
||||
}
|
||||
sig.PV.Update(ewoValue * vol)
|
||||
sig.V.Update(vol)
|
||||
}
|
||||
} else {
|
||||
if s.UseHeikinAshi {
|
||||
vol = s.heikinAshi.Volume.Last()
|
||||
} else {
|
||||
vol = window[len(window)-1].Volume.Float64()
|
||||
}
|
||||
sig.PV.Update(s.ewo.Last() * vol)
|
||||
sig.V.Update(vol)
|
||||
}
|
||||
})
|
||||
s.ewoSignal = sig
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) validateOrder(order *types.SubmitOrder) bool {
|
||||
if order.Type == types.OrderTypeMarket && order.TimeInForce != "" {
|
||||
return false
|
||||
}
|
||||
if order.Side == types.SideTypeSell {
|
||||
baseBalance, ok := s.session.Account.Balance(s.market.BaseCurrency)
|
||||
baseBalance, ok := s.Session.Account.Balance(s.Market.BaseCurrency)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
@ -68,26 +403,26 @@ func (s *Strategy) validateOrder(order *types.SubmitOrder) bool {
|
|||
}
|
||||
price := order.Price
|
||||
if price.IsZero() {
|
||||
price, ok = s.session.LastPrice(s.Symbol)
|
||||
price, ok = s.Session.LastPrice(s.Symbol)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
orderAmount := order.Quantity.Mul(price)
|
||||
if order.Quantity.Sign() <= 0 ||
|
||||
order.Quantity.Compare(s.market.MinQuantity) < 0 ||
|
||||
orderAmount.Compare(s.market.MinNotional) < 0 {
|
||||
order.Quantity.Compare(s.Market.MinQuantity) < 0 ||
|
||||
orderAmount.Compare(s.Market.MinNotional) < 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else if order.Side == types.SideTypeBuy {
|
||||
quoteBalance, ok := s.session.Account.Balance(s.market.QuoteCurrency)
|
||||
quoteBalance, ok := s.Session.Account.Balance(s.Market.QuoteCurrency)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
price := order.Price
|
||||
if price.IsZero() {
|
||||
price, ok = s.session.LastPrice(s.Symbol)
|
||||
price, ok = s.Session.LastPrice(s.Symbol)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
@ -98,8 +433,8 @@ func (s *Strategy) validateOrder(order *types.SubmitOrder) bool {
|
|||
}
|
||||
orderAmount := order.Quantity.Mul(price)
|
||||
if order.Quantity.Sign() <= 0 ||
|
||||
orderAmount.Compare(s.market.MinNotional) < 0 ||
|
||||
order.Quantity.Compare(s.market.MinQuantity) < 0 {
|
||||
orderAmount.Compare(s.Market.MinNotional) < 0 ||
|
||||
order.Quantity.Compare(s.Market.MinQuantity) < 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -109,23 +444,11 @@ func (s *Strategy) validateOrder(order *types.SubmitOrder) bool {
|
|||
}
|
||||
|
||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
s.session = session
|
||||
buyPrice := fixedpoint.Zero
|
||||
sellPrice := fixedpoint.Zero
|
||||
prevPrice := fixedpoint.Zero
|
||||
tp := types.PositionClosed
|
||||
market, ok := session.Market(s.Symbol)
|
||||
if !ok {
|
||||
log.Errorf("fetch market fail %s", s.Symbol)
|
||||
return nil
|
||||
}
|
||||
s.market = market
|
||||
s.peakPrice = fixedpoint.Zero
|
||||
s.bottomPrice = fixedpoint.Zero
|
||||
|
||||
indicatorSet, ok := session.StandardIndicatorSet(s.Symbol)
|
||||
if !ok {
|
||||
log.Errorf("cannot get indicatorSet of %s", s.Symbol)
|
||||
return nil
|
||||
}
|
||||
orderbook, ok := session.OrderStore(s.Symbol)
|
||||
if !ok {
|
||||
log.Errorf("cannot get orderbook of %s", s.Symbol)
|
||||
|
@ -141,51 +464,24 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
if !profit.IsZero() {
|
||||
log.Warnf("generate profit: %v, netprofit: %v, trade: %v", profit, netprofit, trade)
|
||||
}
|
||||
if trade.Side == types.SideTypeBuy {
|
||||
buyPrice = trade.Price
|
||||
s.peakPrice = buyPrice.Mul(fixedpoint.One.Add(s.Callback))
|
||||
} else if trade.Side == types.SideTypeSell {
|
||||
sellPrice = trade.Price
|
||||
s.bottomPrice = sellPrice.Mul(fixedpoint.One.Sub(s.Callback))
|
||||
}
|
||||
})
|
||||
|
||||
s.tradeCollector.OnPositionUpdate(func(position *types.Position) {
|
||||
log.Infof("position changed: %s", position)
|
||||
tp = position.Type()
|
||||
if tp == types.PositionLong {
|
||||
buyPrice = position.AverageCost
|
||||
} else if tp == types.PositionShort {
|
||||
sellPrice = position.AverageCost
|
||||
} else {
|
||||
buyPrice = fixedpoint.Zero
|
||||
sellPrice = fixedpoint.Zero
|
||||
}
|
||||
})
|
||||
s.tradeCollector.BindStream(session.UserDataStream)
|
||||
|
||||
//s.SmartStops.RunStopControllers(ctx, session, s.tradeCollector)
|
||||
s.SmartStops.RunStopControllers(ctx, session, s.tradeCollector)
|
||||
|
||||
/*store, ok := session.MarketDataStore(s.Symbol)
|
||||
if !ok {
|
||||
log.Errorf("cannot get marketdatastore of %s", s.Symbol)
|
||||
return nil
|
||||
}*/
|
||||
s.SetupIndicators()
|
||||
|
||||
/*window, ok := store.KLinesOfInterval(s.Interval)
|
||||
if !ok {
|
||||
log.Errorf("cannot get klinewindow of %s", s.Interval)
|
||||
}*/
|
||||
var ma5, ma34, ma50, ewo types.Series
|
||||
if s.UseEma {
|
||||
ma5 = indicatorSet.EWMA(types.IntervalWindow{s.Interval, 5})
|
||||
ma34 = indicatorSet.EWMA(types.IntervalWindow{s.Interval, 34})
|
||||
ma50 = indicatorSet.EWMA(types.IntervalWindow{s.Interval, 50})
|
||||
} else {
|
||||
ma5 = indicatorSet.SMA(types.IntervalWindow{s.Interval, 5})
|
||||
ma34 = indicatorSet.SMA(types.IntervalWindow{s.Interval, 34})
|
||||
ma50 = indicatorSet.SMA(types.IntervalWindow{s.Interval, 50})
|
||||
}
|
||||
ewo = types.Mul(types.Minus(types.Div(ma5, ma34), 1.0), 100.)
|
||||
var ewoSignal EwoSignal
|
||||
if s.UseEma {
|
||||
ewoSignal = &indicator.EWMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}}
|
||||
} else {
|
||||
ewoSignal = &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, s.SignalWindow}}
|
||||
}
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
if kline.Symbol != s.Symbol {
|
||||
return
|
||||
|
@ -196,6 +492,19 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
log.Errorf("cannot get last price")
|
||||
return
|
||||
}
|
||||
balances := session.Account.Balances()
|
||||
baseBalance := balances[s.Market.BaseCurrency].Available
|
||||
quoteBalance := balances[s.Market.QuoteCurrency].Available
|
||||
/*if buyPrice.IsZero() {
|
||||
if !baseBalance.IsZero() {
|
||||
buyPrice = lastPrice
|
||||
}
|
||||
}
|
||||
if sellPrice.IsZero() {
|
||||
if !quoteBalance.IsZero() {
|
||||
sellPrice = lastPrice
|
||||
}
|
||||
}*/
|
||||
|
||||
// cancel non-traded orders
|
||||
var toCancel []types.Order
|
||||
|
@ -239,33 +548,45 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
sellall := false
|
||||
buyall := false
|
||||
if !prevPrice.IsZero() {
|
||||
change := lastPrice.Sub(prevPrice).Div(prevPrice)
|
||||
if change.Compare(s.Stoploss) > 0 && tp == types.PositionShort {
|
||||
buyall = true
|
||||
} else if change.Compare(s.Stoploss.Neg()) < 0 && tp == types.PositionLong {
|
||||
sellall = true
|
||||
if !s.peakPrice.IsZero() {
|
||||
change := s.peakPrice.Sub(lastPrice).Div(s.peakPrice)
|
||||
if change.Compare(s.Callback) > 0 {
|
||||
if !baseBalance.IsZero() {
|
||||
sellall = true
|
||||
}
|
||||
s.peakPrice = fixedpoint.Zero
|
||||
} else {
|
||||
s.peakPrice = kline.High
|
||||
}
|
||||
}
|
||||
if !s.bottomPrice.IsZero() {
|
||||
change := lastPrice.Sub(s.bottomPrice).Div(s.bottomPrice)
|
||||
if change.Compare(s.Callback) > 0 {
|
||||
if !quoteBalance.IsZero() {
|
||||
buyall = true
|
||||
}
|
||||
s.bottomPrice = fixedpoint.Zero
|
||||
} else {
|
||||
s.bottomPrice = kline.Low
|
||||
}
|
||||
}
|
||||
|
||||
if !buyPrice.IsZero() &&
|
||||
buyPrice.Sub(lastPrice).Div(buyPrice).Compare(s.Stoploss) > 0 { // stoploss 2%
|
||||
// sell all
|
||||
sellall = true
|
||||
}
|
||||
if !sellPrice.IsZero() &&
|
||||
lastPrice.Sub(sellPrice).Div(sellPrice).Compare(s.Stoploss) > 0 { // stoploss 2%
|
||||
// buy back
|
||||
buyall = true
|
||||
}
|
||||
if sellall {
|
||||
balances := session.Account.Balances()
|
||||
baseBalance := balances[market.BaseCurrency].Available.Mul(modifier)
|
||||
baseBalance := balances[s.Market.BaseCurrency].Available.Mul(modifier)
|
||||
order := types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: types.SideTypeSell,
|
||||
Type: types.OrderTypeMarket,
|
||||
Market: market,
|
||||
Market: s.Market,
|
||||
Quantity: baseBalance,
|
||||
}
|
||||
if s.validateOrder(&order) {
|
||||
|
@ -281,7 +602,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
|
||||
if buyall {
|
||||
quoteBalance, ok := session.Account.Balance(market.QuoteCurrency)
|
||||
quoteBalance, ok := session.Account.Balance(s.Market.QuoteCurrency)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -292,7 +613,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
Side: types.SideTypeBuy,
|
||||
Type: types.OrderTypeMarket,
|
||||
Quantity: totalQuantity,
|
||||
Market: market,
|
||||
Market: s.Market,
|
||||
}
|
||||
if s.validateOrder(&order) {
|
||||
log.Infof("stoploss long at %v, avg %v, timestamp: %s", lastPrice, sellPrice, kline.StartTime)
|
||||
|
@ -308,39 +629,42 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
}
|
||||
|
||||
prevPrice = lastPrice
|
||||
|
||||
if kline.Interval != s.Interval {
|
||||
return
|
||||
}
|
||||
|
||||
if ewoSignal.Length() == 0 {
|
||||
// lazy init
|
||||
ewoVals := types.ToReverseArray(ewo)
|
||||
for _, ewoValue := range ewoVals {
|
||||
ewoSignal.Update(ewoValue)
|
||||
}
|
||||
} else {
|
||||
ewoSignal.Update(ewo.Last())
|
||||
}
|
||||
|
||||
// To get the threshold for ewo
|
||||
mean := types.Mean(types.Abs(ewo), 7)
|
||||
mean := types.Mean(types.Abs(s.ewo), 5)
|
||||
|
||||
longSignal := types.CrossOver(ewo, ewoSignal)
|
||||
shortSignal := types.CrossUnder(ewo, ewoSignal)
|
||||
// get
|
||||
bull := types.Predict(ma50, 50, 2) > ma50.Last()
|
||||
longSignal := types.CrossOver(s.ewo, s.ewoSignal)
|
||||
shortSignal := types.CrossUnder(s.ewo, s.ewoSignal)
|
||||
// get trend flags
|
||||
var bull, breakThrough, breakDown bool
|
||||
if s.UseHeikinAshi {
|
||||
// heikinashi itself contains the concept of trend, so no need breakthrough/down
|
||||
bull = s.heikinAshi.Close.Last() > s.heikinAshi.Open.Last()
|
||||
breakThrough = true
|
||||
breakDown = true
|
||||
} else {
|
||||
bull = types.Predict(s.ma34, 5, 2) > s.ma34.Last()
|
||||
breakThrough = kline.Low.Float64() > s.ma5.Last()
|
||||
breakDown = kline.High.Float64() < s.ma5.Last()
|
||||
}
|
||||
// kline breakthrough ma5, ma50 trend up, and ewo > threshold
|
||||
IsBull := bull && kline.High.Float64() > ma5.Last() && ewo.Last() > mean
|
||||
IsBull := bull && breakThrough && s.ewo.Last() >= mean
|
||||
// kline downthrough ma5, ma50 trend down, and ewo < threshold
|
||||
IsBear := !bull && kline.Low.Float64() < ma5.Last() && ewo.Last() < -mean
|
||||
IsBear := !bull && breakDown && s.ewo.Last() <= -mean
|
||||
|
||||
var orders []types.SubmitOrder
|
||||
var price fixedpoint.Value
|
||||
|
||||
if longSignal.Index(1) && !shortSignal.Last() && IsBull {
|
||||
price := kline.Low
|
||||
quoteBalance, ok := session.Account.Balance(market.QuoteCurrency)
|
||||
if s.UseHeikinAshi {
|
||||
price = fixedpoint.NewFromFloat(s.heikinAshi.Close.Last())
|
||||
} else {
|
||||
price = kline.Low
|
||||
}
|
||||
quoteBalance, ok := session.Account.Balance(s.Market.QuoteCurrency)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -352,30 +676,34 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
Type: types.OrderTypeLimit,
|
||||
Price: price,
|
||||
Quantity: totalQuantity,
|
||||
Market: market,
|
||||
Market: s.Market,
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
}
|
||||
if s.validateOrder(&order) {
|
||||
// strong long
|
||||
log.Infof("long at %v, timestamp: %s", price, kline.StartTime)
|
||||
log.Warnf("long at %v, timestamp: %s", price, kline.StartTime)
|
||||
|
||||
orders = append(orders, order)
|
||||
}
|
||||
} else if shortSignal.Index(1) && !longSignal.Last() && IsBear {
|
||||
price := kline.High
|
||||
if s.UseHeikinAshi {
|
||||
price = fixedpoint.NewFromFloat(s.heikinAshi.Close.Last())
|
||||
} else {
|
||||
price = kline.High
|
||||
}
|
||||
balances := session.Account.Balances()
|
||||
baseBalance := balances[market.BaseCurrency].Available.Mul(modifier)
|
||||
baseBalance := balances[s.Market.BaseCurrency].Available.Mul(modifier)
|
||||
order := types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: types.SideTypeSell,
|
||||
Type: types.OrderTypeLimit,
|
||||
Market: market,
|
||||
Market: s.Market,
|
||||
Quantity: baseBalance,
|
||||
Price: price,
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
}
|
||||
if s.validateOrder(&order) {
|
||||
log.Infof("short at %v, timestamp: %s", price, kline.StartTime)
|
||||
log.Warnf("short at %v, timestamp: %s", price, kline.StartTime)
|
||||
orders = append(orders, order)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user