2021-02-14 17:26:46 +00:00
package support
2021-02-10 16:21:06 +00:00
import (
"context"
"fmt"
"github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)
2021-02-14 17:26:46 +00:00
const ID = "support"
2021-02-10 16:21:06 +00:00
var log = logrus . WithField ( "strategy" , ID )
func init ( ) {
bbgo . RegisterStrategy ( ID , & Strategy { } )
}
type Target struct {
2021-02-14 17:26:46 +00:00
ProfitPercentage float64 ` json:"profitPercentage" `
QuantityPercentage float64 ` json:"quantityPercentage" `
MarginOrderSideEffect types . MarginOrderSideEffectType ` json:"marginOrderSideEffect" `
2021-02-10 16:21:06 +00:00
}
type Strategy struct {
2021-02-20 02:50:57 +00:00
* bbgo . Notifiability
2021-02-14 17:26:46 +00:00
Symbol string ` json:"symbol" `
Interval types . Interval ` json:"interval" `
MovingAverageWindow int ` json:"movingAverageWindow" `
Quantity fixedpoint . Value ` json:"quantity" `
MinVolume fixedpoint . Value ` json:"minVolume" `
MarginOrderSideEffect types . MarginOrderSideEffectType ` json:"marginOrderSideEffect" `
Targets [ ] Target ` json:"targets" `
2021-02-28 03:57:25 +00:00
2021-02-28 06:51:24 +00:00
ScaleQuantity * bbgo . PriceVolumeScale ` json:"scaleQuantity" `
2021-02-10 16:21:06 +00:00
}
func ( s * Strategy ) ID ( ) string {
return ID
}
2021-04-02 02:32:24 +00:00
func ( s * Strategy ) Validate ( ) error {
if s . Quantity == 0 && s . ScaleQuantity == nil {
return fmt . Errorf ( "quantity or scaleQuantity can not be zero" )
}
if s . MinVolume == 0 {
return fmt . Errorf ( "minVolume can not be zero" )
}
return nil
}
2021-02-10 16:21:06 +00:00
func ( s * Strategy ) Subscribe ( session * bbgo . ExchangeSession ) {
session . Subscribe ( types . KLineChannel , s . Symbol , types . SubscribeOptions { Interval : string ( s . Interval ) } )
}
func ( s * Strategy ) Run ( ctx context . Context , orderExecutor bbgo . OrderExecutor , session * bbgo . ExchangeSession ) error {
// set default values
if s . Interval == "" {
s . Interval = types . Interval5m
}
if s . MovingAverageWindow == 0 {
s . MovingAverageWindow = 99
}
// buy when price drops -8%
market , ok := session . Market ( s . Symbol )
if ! ok {
return fmt . Errorf ( "market %s is not defined" , s . Symbol )
}
standardIndicatorSet , ok := session . StandardIndicatorSet ( s . Symbol )
if ! ok {
return fmt . Errorf ( "standardIndicatorSet is nil, symbol %s" , s . Symbol )
}
var iw = types . IntervalWindow { Interval : s . Interval , Window : s . MovingAverageWindow }
var ema = standardIndicatorSet . EWMA ( iw )
2021-05-27 19:15:29 +00:00
session . MarketDataStream . OnKLineClosed ( func ( kline types . KLine ) {
2021-02-10 16:21:06 +00:00
// skip k-lines from other symbols
if kline . Symbol != s . Symbol {
return
}
closePrice := kline . GetClose ( )
if closePrice > ema . Last ( ) {
return
}
if kline . Volume < s . MinVolume . Float64 ( ) {
return
}
2021-05-15 17:07:53 +00:00
s . Notify ( "Found %s support: the close price %f is under EMA %f and volume %f > minimum volume %f" , s . Symbol , closePrice , ema . Last ( ) , kline . Volume , s . MinVolume . Float64 ( ) )
2021-02-10 16:21:06 +00:00
2021-02-28 03:57:25 +00:00
var quantity float64
if s . Quantity > 0 {
quantity = s . Quantity . Float64 ( )
} else if s . ScaleQuantity != nil {
var err error
quantity , err = s . ScaleQuantity . Scale ( closePrice , kline . Volume )
if err != nil {
log . WithError ( err ) . Error ( err . Error ( ) )
return
}
}
2021-02-14 17:26:46 +00:00
2021-05-11 05:25:29 +00:00
// for spot, we need to modify the quantity
if ! session . Margin {
minNotional := closePrice * 1.003 * quantity
b , ok := session . Account . Balance ( market . QuoteCurrency )
if ! ok {
log . Errorf ( "balance %s not found" , market . QuoteCurrency )
return
}
if minNotional > b . Available . Float64 ( ) {
log . Warnf ( "modifying quantity %f according to the min quote balance %f %s" , quantity , b . Available . Float64 ( ) , market . QuoteCurrency )
2021-05-14 06:53:18 +00:00
quantity = bbgo . AdjustFloatQuantityByMaxAmount ( quantity , closePrice , b . Available . Float64 ( ) )
2021-05-11 05:25:29 +00:00
}
}
2021-05-15 17:07:53 +00:00
s . Notify ( "Submitting %s market order buy with quantity %f according to the support volume %f" , s . Symbol , quantity , kline . Volume )
2021-02-14 17:26:46 +00:00
orderForm := types . SubmitOrder {
Symbol : s . Symbol ,
Market : market ,
Side : types . SideTypeBuy ,
Type : types . OrderTypeMarket ,
Quantity : quantity ,
MarginSideEffect : s . MarginOrderSideEffect ,
}
_ , err := orderExecutor . SubmitOrders ( ctx , orderForm )
2021-02-10 16:21:06 +00:00
if err != nil {
log . WithError ( err ) . Error ( "submit order error" )
return
}
// submit target orders
var targetOrders [ ] types . SubmitOrder
for _ , target := range s . Targets {
targetPrice := closePrice * ( 1.0 + target . ProfitPercentage )
targetQuantity := quantity * target . QuantityPercentage
2021-05-15 17:04:46 +00:00
targetQuoteQuantity := targetPrice * targetQuantity
if targetQuoteQuantity <= market . MinNotional {
continue
}
if targetQuantity <= market . MinQuantity {
continue
}
2021-02-10 16:21:06 +00:00
targetOrders = append ( targetOrders , types . SubmitOrder {
Symbol : kline . Symbol ,
Market : market ,
Type : types . OrderTypeLimit ,
Side : types . SideTypeSell ,
Price : targetPrice ,
Quantity : targetQuantity ,
2021-02-14 17:26:46 +00:00
MarginSideEffect : target . MarginOrderSideEffect ,
2021-02-10 16:21:06 +00:00
TimeInForce : "GTC" ,
} )
}
_ , err = orderExecutor . SubmitOrders ( ctx , targetOrders ... )
if err != nil {
log . WithError ( err ) . Error ( "submit profit target order error" )
}
} )
return nil
}