2022-04-14 11:01:13 +00:00
package ewoDgtrd
2022-04-07 10:25:02 +00:00
import (
"context"
2022-04-14 10:58:05 +00:00
"fmt"
"math"
2022-04-13 01:43:31 +00:00
"sync"
2022-04-07 10:25:02 +00:00
"github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo"
2022-04-11 08:07:52 +00:00
"github.com/c9s/bbgo/pkg/fixedpoint"
2022-04-07 10:25:02 +00:00
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types"
)
const ID = "ewo_dgtrd"
var log = logrus . WithField ( "strategy" , ID )
func init ( ) {
bbgo . RegisterStrategy ( ID , & Strategy { } )
}
type Strategy struct {
2022-04-28 11:09:15 +00:00
Market types . Market
Session * bbgo . ExchangeSession
UseHeikinAshi bool ` json:"useHeikinAshi" ` // use heikinashi kline
Stoploss fixedpoint . Value ` json:"stoploss" `
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
DisableShortStop bool ` json:"disableShortStop" ` // disable TP/SL on short
2022-04-14 10:58:05 +00:00
2022-04-13 01:43:31 +00:00
* bbgo . Graceful
2022-04-14 10:58:05 +00:00
bbgo . SmartStops
2022-04-13 01:43:31 +00:00
tradeCollector * bbgo . TradeCollector
2022-04-28 11:09:15 +00:00
atr * indicator . ATR
2022-04-14 10:58:05 +00:00
ma5 types . Series
ma34 types . Series
ewo types . Series
ewoSignal types . Series
heikinAshi * HeikinAshi
peakPrice fixedpoint . Value
bottomPrice fixedpoint . Value
2022-04-07 10:25:02 +00:00
}
func ( s * Strategy ) ID ( ) string {
return ID
}
2022-04-13 01:43:31 +00:00
func ( s * Strategy ) Initialize ( ) error {
2022-04-14 10:58:05 +00:00
return s . SmartStops . InitializeStopControllers ( s . Symbol )
2022-04-13 01:43:31 +00:00
}
2022-04-07 10:25:02 +00:00
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 ( ) } )
2022-04-14 10:58:05 +00:00
s . SmartStops . Subscribe ( session )
2022-04-07 10:25:02 +00:00
}
2022-04-14 10:58:05 +00:00
type UpdatableSeries interface {
2022-04-07 10:25:02 +00:00
types . Series
Update ( value float64 )
}
2022-04-15 10:12:11 +00:00
type VWEMA struct {
2022-04-14 10:58:05 +00:00
PV UpdatableSeries
V UpdatableSeries
}
2022-04-15 10:12:11 +00:00
func ( inc * VWEMA ) Last ( ) float64 {
2022-04-14 10:58:05 +00:00
return inc . PV . Last ( ) / inc . V . Last ( )
}
2022-04-15 10:12:11 +00:00
func ( inc * VWEMA ) Index ( i int ) float64 {
2022-04-14 10:58:05 +00:00
if i >= inc . PV . Length ( ) {
return 0
}
vi := inc . V . Index ( i )
if vi == 0 {
return 0
}
return inc . PV . Index ( i ) / vi
}
2022-04-15 10:12:11 +00:00
func ( inc * VWEMA ) Length ( ) int {
2022-04-14 10:58:05 +00:00
pvl := inc . PV . Length ( )
vl := inc . V . Length ( )
if pvl < vl {
return pvl
}
return vl
}
2022-04-15 10:12:11 +00:00
func ( inc * VWEMA ) Update ( kline types . KLine ) {
2022-04-14 10:58:05 +00:00
inc . PV . Update ( kline . Close . Mul ( kline . Volume ) . Float64 ( ) )
inc . V . Update ( kline . Volume . Float64 ( ) )
}
2022-04-15 10:12:11 +00:00
func ( inc * VWEMA ) UpdateVal ( price float64 , vol float64 ) {
2022-04-14 10:58:05 +00:00
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
}
2022-04-28 11:09:15 +00:00
s . atr = & indicator . ATR { IntervalWindow : types . IntervalWindow { s . Interval , 34 } }
2022-04-14 10:58:05 +00:00
if s . UseHeikinAshi {
s . heikinAshi = NewHeikinAshi ( 50 )
store . OnKLineWindowUpdate ( func ( interval types . Interval , window types . KLineWindow ) {
2022-05-11 11:56:56 +00:00
if interval == s . atr . Interval {
if s . atr . RMA == nil {
for _ , kline := range window {
s . atr . Update (
kline . High . Float64 ( ) ,
kline . Low . Float64 ( ) ,
kline . Close . Float64 ( ) ,
)
}
} else {
kline := window [ len ( window ) - 1 ]
s . atr . Update (
kline . High . Float64 ( ) ,
kline . Low . Float64 ( ) ,
kline . Close . Float64 ( ) ,
)
}
}
2022-04-14 10:58:05 +00:00
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 {
2022-04-15 10:12:11 +00:00
evwma5 := & VWEMA {
2022-04-14 10:58:05 +00:00
PV : & indicator . EWMA { IntervalWindow : types . IntervalWindow { s . Interval , 5 } } ,
V : & indicator . EWMA { IntervalWindow : types . IntervalWindow { s . Interval , 5 } } ,
}
2022-04-15 10:12:11 +00:00
evwma34 := & VWEMA {
2022-04-14 10:58:05 +00:00
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 } )
2022-04-28 11:09:15 +00:00
s . atr . Bind ( store )
2022-04-14 10:58:05 +00:00
} else if s . UseSma {
s . ma5 = indicatorSet . SMA ( types . IntervalWindow { s . Interval , 5 } )
s . ma34 = indicatorSet . SMA ( types . IntervalWindow { s . Interval , 34 } )
2022-04-28 11:09:15 +00:00
s . atr . Bind ( store )
2022-04-14 10:58:05 +00:00
} else {
2022-04-15 10:12:11 +00:00
evwma5 := & VWEMA {
2022-04-14 10:58:05 +00:00
PV : & indicator . EWMA { IntervalWindow : types . IntervalWindow { s . Interval , 5 } } ,
V : & indicator . EWMA { IntervalWindow : types . IntervalWindow { s . Interval , 5 } } ,
}
2022-04-15 10:12:11 +00:00
evwma34 := & VWEMA {
2022-04-14 10:58:05 +00:00
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 {
2022-04-15 10:12:11 +00:00
sig := & VWEMA {
2022-04-14 10:58:05 +00:00
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
}
}
2022-04-13 12:08:59 +00:00
func ( s * Strategy ) validateOrder ( order * types . SubmitOrder ) bool {
if order . Type == types . OrderTypeMarket && order . TimeInForce != "" {
return false
}
if order . Side == types . SideTypeSell {
2022-04-23 07:43:11 +00:00
baseBalance , ok := s . Session . GetAccount ( ) . Balance ( s . Market . BaseCurrency )
2022-04-13 12:08:59 +00:00
if ! ok {
return false
}
if order . Quantity . Compare ( baseBalance . Available ) > 0 {
return false
}
price := order . Price
if price . IsZero ( ) {
2022-04-14 10:58:05 +00:00
price , ok = s . Session . LastPrice ( s . Symbol )
2022-04-13 12:08:59 +00:00
if ! ok {
return false
}
}
orderAmount := order . Quantity . Mul ( price )
if order . Quantity . Sign ( ) <= 0 ||
2022-04-14 10:58:05 +00:00
order . Quantity . Compare ( s . Market . MinQuantity ) < 0 ||
orderAmount . Compare ( s . Market . MinNotional ) < 0 {
2022-04-13 12:08:59 +00:00
return false
}
return true
} else if order . Side == types . SideTypeBuy {
2022-04-23 07:43:11 +00:00
quoteBalance , ok := s . Session . GetAccount ( ) . Balance ( s . Market . QuoteCurrency )
2022-04-13 12:08:59 +00:00
if ! ok {
return false
}
price := order . Price
if price . IsZero ( ) {
2022-04-14 10:58:05 +00:00
price , ok = s . Session . LastPrice ( s . Symbol )
2022-04-13 12:08:59 +00:00
if ! ok {
return false
}
}
totalQuantity := quoteBalance . Available . Div ( price )
if order . Quantity . Compare ( totalQuantity ) > 0 {
return false
}
orderAmount := order . Quantity . Mul ( price )
if order . Quantity . Sign ( ) <= 0 ||
2022-04-14 10:58:05 +00:00
orderAmount . Compare ( s . Market . MinNotional ) < 0 ||
order . Quantity . Compare ( s . Market . MinQuantity ) < 0 {
2022-04-13 12:08:59 +00:00
return false
}
return true
}
return false
}
2022-04-28 11:09:15 +00:00
// Trading Rules:
// - buy / sell the whole asset
// - SL/TP by atr (buyprice - 2 * atr, sellprice + 2 * atr)
// - SL by s.Stoploss (Abs(price_diff / price) > s.Stoploss)
// - entry condition on ewo(Elliott wave oscillator) Crosses ewoSignal(ma on ewo, signalWindow)
// * buy signal on crossover
// * sell signal on crossunder
// - and filtered by the following rules:
2022-05-11 11:56:56 +00:00
// * buy: prev buy signal ON and current sell signal OFF, kline Close > Open, Close > ma(Window=5), ewo > Mean(ewo, Window=10) + 2 * Stdev(ewo, Window=10)
// * sell: prev buy signal OFF and current sell signal ON, kline Close < Open, Close < ma(Window=5), ewo < Mean(ewo, Window=10) - 2 * Stdev(ewo, Window=10)
2022-04-28 11:09:15 +00:00
// Cancel and repost on non-fully filed orders every 1m within Window=1
//
// ps: kline might refer to heikinashi or normal ohlc
2022-04-07 10:25:02 +00:00
func ( s * Strategy ) Run ( ctx context . Context , orderExecutor bbgo . OrderExecutor , session * bbgo . ExchangeSession ) error {
2022-04-13 12:08:59 +00:00
buyPrice := fixedpoint . Zero
sellPrice := fixedpoint . Zero
2022-04-14 10:58:05 +00:00
s . peakPrice = fixedpoint . Zero
s . bottomPrice = fixedpoint . Zero
2022-04-13 01:43:31 +00:00
orderbook , ok := session . OrderStore ( s . Symbol )
2022-04-11 10:55:34 +00:00
if ! ok {
log . Errorf ( "cannot get orderbook of %s" , s . Symbol )
return nil
}
2022-04-13 12:08:59 +00:00
position , ok := session . Position ( s . Symbol )
if ! ok {
log . Errorf ( "cannot get position of %s" , s . Symbol )
return nil
}
2022-04-13 01:43:31 +00:00
s . tradeCollector = bbgo . NewTradeCollector ( s . Symbol , position , orderbook )
s . tradeCollector . OnTrade ( func ( trade types . Trade , profit , netprofit fixedpoint . Value ) {
if ! profit . IsZero ( ) {
2022-04-13 12:08:59 +00:00
log . Warnf ( "generate profit: %v, netprofit: %v, trade: %v" , profit , netprofit , trade )
2022-04-13 01:43:31 +00:00
}
2022-05-11 11:56:56 +00:00
balances := session . GetAccount ( ) . Balances ( )
baseBalance := balances [ s . Market . BaseCurrency ] . Available
quoteBalance := balances [ s . Market . QuoteCurrency ] . Available
2022-04-14 10:58:05 +00:00
if trade . Side == types . SideTypeBuy {
2022-05-11 11:56:56 +00:00
if baseBalance . IsZero ( ) {
sellPrice = fixedpoint . Zero
}
if ! quoteBalance . IsZero ( ) {
2022-04-28 11:09:15 +00:00
buyPrice = trade . Price
s . peakPrice = trade . Price
}
2022-04-14 10:58:05 +00:00
} else if trade . Side == types . SideTypeSell {
2022-05-11 11:56:56 +00:00
if quoteBalance . IsZero ( ) {
buyPrice = fixedpoint . Zero
}
if ! baseBalance . IsZero ( ) {
2022-04-28 11:09:15 +00:00
sellPrice = trade . Price
s . bottomPrice = trade . Price
}
2022-04-14 10:58:05 +00:00
}
2022-04-13 01:43:31 +00:00
} )
s . tradeCollector . OnPositionUpdate ( func ( position * types . Position ) {
log . Infof ( "position changed: %s" , position )
} )
s . tradeCollector . BindStream ( session . UserDataStream )
2022-04-14 10:58:05 +00:00
s . SmartStops . RunStopControllers ( ctx , session , s . tradeCollector )
2022-04-13 01:43:31 +00:00
2022-04-14 10:58:05 +00:00
s . SetupIndicators ( )
2022-04-13 01:43:31 +00:00
2022-04-07 10:25:02 +00:00
session . MarketDataStream . OnKLineClosed ( func ( kline types . KLine ) {
2022-04-11 08:07:52 +00:00
if kline . Symbol != s . Symbol {
2022-04-07 10:25:02 +00:00
return
}
2022-04-11 08:07:52 +00:00
2022-04-13 12:08:59 +00:00
lastPrice , ok := session . LastPrice ( s . Symbol )
2022-04-07 10:25:02 +00:00
if ! ok {
2022-04-13 01:43:31 +00:00
log . Errorf ( "cannot get last price" )
2022-04-07 10:25:02 +00:00
return
2022-04-13 12:08:59 +00:00
}
2022-04-13 01:43:31 +00:00
// cancel non-traded orders
var toCancel [ ] types . Order
var toRepost [ ] types . SubmitOrder
for _ , order := range orderbook . Orders ( ) {
if order . Status == types . OrderStatusNew || order . Status == types . OrderStatusPartiallyFilled {
toCancel = append ( toCancel , order )
}
2022-04-07 10:25:02 +00:00
}
2022-04-13 01:43:31 +00:00
if len ( toCancel ) > 0 {
if err := orderExecutor . CancelOrders ( ctx , toCancel ... ) ; err != nil {
log . WithError ( err ) . Errorf ( "cancel order error" )
}
2022-04-07 10:25:02 +00:00
2022-04-13 01:43:31 +00:00
s . tradeCollector . Process ( )
}
2022-05-11 11:56:56 +00:00
balances := session . GetAccount ( ) . Balances ( )
baseBalance := balances [ s . Market . BaseCurrency ] . Available
quoteBalance := balances [ s . Market . QuoteCurrency ] . Available
atrx2 := fixedpoint . NewFromFloat ( s . atr . Last ( ) * 2 )
2022-04-13 01:43:31 +00:00
// well, only track prices on 1m
2022-04-13 12:08:59 +00:00
if kline . Interval == types . Interval1m {
2022-05-11 11:56:56 +00:00
2022-04-13 01:43:31 +00:00
for _ , order := range toCancel {
2022-05-11 11:56:56 +00:00
if order . Side == types . SideTypeBuy {
2022-04-28 11:09:15 +00:00
newPrice := lastPrice
2022-04-13 12:08:59 +00:00
order . Quantity = order . Quantity . Mul ( order . Price ) . Div ( newPrice )
order . Price = newPrice
2022-04-13 01:43:31 +00:00
toRepost = append ( toRepost , order . SubmitOrder )
2022-05-11 11:56:56 +00:00
} else if order . Side == types . SideTypeSell {
2022-04-28 11:09:15 +00:00
newPrice := lastPrice
2022-04-13 12:08:59 +00:00
order . Price = newPrice
2022-04-13 01:43:31 +00:00
toRepost = append ( toRepost , order . SubmitOrder )
2022-04-11 08:07:52 +00:00
}
2022-04-11 10:55:34 +00:00
}
2022-04-13 01:43:31 +00:00
if len ( toRepost ) > 0 {
createdOrders , err := orderExecutor . SubmitOrders ( ctx , toRepost ... )
2022-04-11 08:07:52 +00:00
if err != nil {
2022-04-13 01:43:31 +00:00
log . WithError ( err ) . Errorf ( "cannot place order" )
2022-04-11 08:07:52 +00:00
return
}
2022-04-13 01:43:31 +00:00
log . Infof ( "repost order %v" , createdOrders )
s . tradeCollector . Process ( )
2022-04-11 08:07:52 +00:00
}
2022-04-13 12:08:59 +00:00
sellall := false
buyall := false
2022-05-11 11:56:56 +00:00
if ! buyPrice . IsZero ( ) {
if s . peakPrice . IsZero ( ) || s . peakPrice . Compare ( kline . High ) < 0 {
2022-04-14 10:58:05 +00:00
s . peakPrice = kline . High
}
}
2022-04-28 11:09:15 +00:00
2022-05-11 11:56:56 +00:00
if ! sellPrice . IsZero ( ) {
if s . bottomPrice . IsZero ( ) || s . bottomPrice . Compare ( kline . Low ) > 0 {
2022-04-14 10:58:05 +00:00
s . bottomPrice = kline . Low
2022-04-13 12:08:59 +00:00
}
}
2022-04-28 11:09:15 +00:00
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
}
2022-04-13 12:08:59 +00:00
}
2022-04-28 11:09:15 +00:00
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 ) ||
2022-05-11 11:56:56 +00:00
lastPrice . Sub ( sellPrice ) . Div ( sellPrice ) . Compare ( s . Stoploss ) > 0 {
2022-04-28 11:09:15 +00:00
buyall = true
s . bottomPrice = fixedpoint . Zero
}
2022-04-13 12:08:59 +00:00
}
if sellall {
order := types . SubmitOrder {
Symbol : s . Symbol ,
Side : types . SideTypeSell ,
Type : types . OrderTypeMarket ,
2022-04-14 10:58:05 +00:00
Market : s . Market ,
2022-04-13 12:08:59 +00:00
Quantity : baseBalance ,
}
if s . validateOrder ( & order ) {
2022-04-28 11:09:15 +00:00
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 )
}
2022-04-13 12:08:59 +00:00
createdOrders , err := orderExecutor . SubmitOrders ( ctx , order )
if err != nil {
log . WithError ( err ) . Errorf ( "cannot place order" )
return
}
2022-04-28 11:09:15 +00:00
log . Infof ( "stoploss sold order %v" , createdOrders )
2022-04-13 12:08:59 +00:00
s . tradeCollector . Process ( )
}
}
if buyall {
2022-04-28 11:09:15 +00:00
totalQuantity := quoteBalance . Div ( lastPrice )
2022-04-13 12:08:59 +00:00
order := types . SubmitOrder {
Symbol : kline . Symbol ,
Side : types . SideTypeBuy ,
Type : types . OrderTypeMarket ,
Quantity : totalQuantity ,
2022-04-14 10:58:05 +00:00
Market : s . Market ,
2022-04-13 12:08:59 +00:00
}
if s . validateOrder ( & order ) {
2022-04-28 11:09:15 +00:00
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 )
}
2022-04-13 12:08:59 +00:00
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 ( )
}
}
2022-04-11 08:07:52 +00:00
}
if kline . Interval != s . Interval {
return
}
2022-04-13 01:43:31 +00:00
2022-04-14 10:58:05 +00:00
// To get the threshold for ewo
2022-05-11 11:56:56 +00:00
mean := types . Mean ( s . ewo , 10 )
std := types . Stdev ( s . ewo , 10 )
2022-04-14 10:58:05 +00:00
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 {
bull = s . heikinAshi . Close . Last ( ) > s . heikinAshi . Open . Last ( )
2022-04-28 11:09:15 +00:00
breakThrough = s . heikinAshi . Close . Last ( ) > s . ma5 . Last ( )
breakDown = s . heikinAshi . Close . Last ( ) < s . ma5 . Last ( )
2022-04-13 12:08:59 +00:00
} else {
2022-04-28 11:09:15 +00:00
bull = kline . Close . Compare ( kline . Open ) > 0
breakThrough = kline . Close . Float64 ( ) > s . ma5 . Last ( )
breakDown = kline . Close . Float64 ( ) < s . ma5 . Last ( )
2022-04-13 12:08:59 +00:00
}
2022-04-13 01:43:31 +00:00
// kline breakthrough ma5, ma50 trend up, and ewo > threshold
2022-05-11 11:56:56 +00:00
IsBull := bull && breakThrough && s . ewo . Last ( ) >= mean + 2 * std
2022-04-13 01:43:31 +00:00
// kline downthrough ma5, ma50 trend down, and ewo < threshold
2022-05-11 11:56:56 +00:00
IsBear := ! bull && breakDown && s . ewo . Last ( ) <= mean - 2 * std
2022-04-08 10:05:28 +00:00
2022-04-11 08:07:52 +00:00
var orders [ ] types . SubmitOrder
2022-04-14 10:58:05 +00:00
var price fixedpoint . Value
2022-04-13 01:43:31 +00:00
2022-04-11 08:07:52 +00:00
if longSignal . Index ( 1 ) && ! shortSignal . Last ( ) && IsBull {
2022-04-14 10:58:05 +00:00
if s . UseHeikinAshi {
price = fixedpoint . NewFromFloat ( s . heikinAshi . Close . Last ( ) )
} else {
price = kline . Low
}
2022-04-23 07:43:11 +00:00
quoteBalance , ok := session . GetAccount ( ) . Balance ( s . Market . QuoteCurrency )
2022-04-11 08:07:52 +00:00
if ! ok {
return
}
2022-04-28 11:09:15 +00:00
quantityAmount := quoteBalance . Available
2022-04-13 01:43:31 +00:00
totalQuantity := quantityAmount . Div ( price )
2022-04-13 12:08:59 +00:00
order := types . SubmitOrder {
2022-04-13 01:43:31 +00:00
Symbol : kline . Symbol ,
Side : types . SideTypeBuy ,
Type : types . OrderTypeLimit ,
Price : price ,
Quantity : totalQuantity ,
2022-04-14 10:58:05 +00:00
Market : s . Market ,
2022-04-13 01:43:31 +00:00
TimeInForce : types . TimeInForceGTC ,
2022-04-13 12:08:59 +00:00
}
if s . validateOrder ( & order ) {
// strong long
2022-05-11 11:56:56 +00:00
log . Warnf ( "long at %v, atrx2 %v, timestamp: %s" , price , atrx2 , kline . StartTime )
2022-04-13 12:08:59 +00:00
orders = append ( orders , order )
}
2022-04-13 01:43:31 +00:00
} else if shortSignal . Index ( 1 ) && ! longSignal . Last ( ) && IsBear {
2022-04-14 10:58:05 +00:00
if s . UseHeikinAshi {
price = fixedpoint . NewFromFloat ( s . heikinAshi . Close . Last ( ) )
} else {
price = kline . High
}
2022-04-23 07:43:11 +00:00
balances := session . GetAccount ( ) . Balances ( )
2022-04-28 11:09:15 +00:00
baseBalance := balances [ s . Market . BaseCurrency ] . Available
2022-04-13 12:08:59 +00:00
order := types . SubmitOrder {
2022-04-13 01:43:31 +00:00
Symbol : s . Symbol ,
Side : types . SideTypeSell ,
Type : types . OrderTypeLimit ,
2022-04-14 10:58:05 +00:00
Market : s . Market ,
2022-04-13 01:43:31 +00:00
Quantity : baseBalance ,
Price : price ,
TimeInForce : types . TimeInForceGTC ,
2022-04-13 12:08:59 +00:00
}
if s . validateOrder ( & order ) {
2022-05-11 11:56:56 +00:00
log . Warnf ( "short at %v, atrx2 %v, timestamp: %s" , price , atrx2 , kline . StartTime )
2022-04-13 12:08:59 +00:00
orders = append ( orders , order )
}
2022-04-11 08:07:52 +00:00
}
if len ( orders ) > 0 {
createdOrders , err := orderExecutor . SubmitOrders ( ctx , orders ... )
if err != nil {
log . WithError ( err ) . Errorf ( "cannot place order" )
return
}
2022-04-13 01:43:31 +00:00
log . Infof ( "post order %v" , createdOrders )
s . tradeCollector . Process ( )
}
} )
s . Graceful . OnShutdown ( func ( ctx context . Context , wg * sync . WaitGroup ) {
defer wg . Done ( )
log . Infof ( "canceling active orders..." )
var toCancel [ ] types . Order
for _ , order := range orderbook . Orders ( ) {
if order . Status == types . OrderStatusNew || order . Status == types . OrderStatusPartiallyFilled {
toCancel = append ( toCancel , order )
2022-04-07 10:25:02 +00:00
}
}
2022-04-13 01:43:31 +00:00
if err := orderExecutor . CancelOrders ( ctx , toCancel ... ) ; err != nil {
log . WithError ( err ) . Errorf ( "cancel order error" )
}
s . tradeCollector . Process ( )
2022-04-07 10:25:02 +00:00
} )
return nil
}