2020-06-18 15:46:59 +00:00
package bbgo
import (
"fmt"
"github.com/adshao/go-binance"
"github.com/c9s/bbgo/pkg/bbgo/types"
2020-06-19 01:24:22 +00:00
"github.com/slack-go/slack"
2020-06-18 15:46:59 +00:00
"math"
2020-06-19 01:24:22 +00:00
"strconv"
2020-06-18 15:46:59 +00:00
)
2020-06-19 03:16:25 +00:00
const epsilon = 0.0000001
2020-06-18 15:46:59 +00:00
func NotZero ( v float64 ) bool {
2020-06-19 03:16:25 +00:00
return math . Abs ( v ) > epsilon
2020-06-18 15:46:59 +00:00
}
type KLineDetector struct {
2020-06-19 14:12:41 +00:00
Name string ` json:"name" `
Interval string ` json:"interval" `
2020-06-20 12:23:26 +00:00
// MinMaxPriceChange is the minimal max price change trigger
2020-06-28 05:45:55 +00:00
MinMaxPriceChange float64 ` json:"minMaxPriceChange" `
2020-06-19 14:12:41 +00:00
2020-06-20 12:23:26 +00:00
// MaxMaxPriceChange is the max - max price change trigger
2020-06-28 05:45:55 +00:00
MaxMaxPriceChange float64 ` json:"maxMaxPriceChange" `
2020-06-18 15:46:59 +00:00
EnableMinThickness bool ` json:"enableMinThickness" `
MinThickness float64 ` json:"minThickness" `
EnableMaxShadowRatio bool ` json:"enableMaxShadowRatio" `
MaxShadowRatio float64 ` json:"maxShadowRatio" `
EnableLookBack bool ` json:"enableLookBack" `
LookBackFrames int ` json:"lookBackFrames" `
2020-06-24 14:13:18 +00:00
MinProfitPriceTick float64 ` json:"minProfitPriceTick" `
2020-06-18 15:46:59 +00:00
DelayMilliseconds int ` json:"delayMsec" `
Stop bool ` json:"stop" `
}
2020-06-19 01:24:22 +00:00
func ( d * KLineDetector ) SlackAttachment ( ) slack . Attachment {
2020-06-19 03:21:17 +00:00
var name = "Detector "
if len ( d . Name ) > 0 {
name += " " + d . Name
}
name += fmt . Sprintf ( " %s" , d . Interval )
2020-06-19 01:24:22 +00:00
if d . EnableLookBack {
2020-06-19 03:21:17 +00:00
name += fmt . Sprintf ( " x %d" , d . LookBackFrames )
2020-06-19 01:24:22 +00:00
}
2020-06-20 12:23:26 +00:00
var maxPriceChangeRange = fmt . Sprintf ( "%.2f ~ NO LIMIT" , d . MinMaxPriceChange )
if NotZero ( d . MaxMaxPriceChange ) {
maxPriceChangeRange = fmt . Sprintf ( "%.2f ~ %.2f" , d . MinMaxPriceChange , d . MaxMaxPriceChange )
2020-06-19 01:24:22 +00:00
}
2020-06-20 12:23:26 +00:00
name += " MaxMaxPriceChange " + maxPriceChangeRange
2020-06-19 01:24:22 +00:00
2020-06-19 03:21:17 +00:00
var fields = [ ] slack . AttachmentField {
{
Title : "Interval" ,
Value : d . Interval ,
Short : true ,
} ,
}
2020-06-19 01:24:22 +00:00
2020-06-19 14:12:41 +00:00
if d . EnableMinThickness && NotZero ( d . MinThickness ) {
2020-06-19 01:24:22 +00:00
fields = append ( fields , slack . AttachmentField {
Title : "MinThickness" ,
Value : formatFloat ( d . MinThickness , 4 ) ,
Short : true ,
} )
}
2020-06-19 14:12:41 +00:00
if d . EnableMaxShadowRatio && NotZero ( d . MaxShadowRatio ) {
2020-06-19 01:24:22 +00:00
fields = append ( fields , slack . AttachmentField {
Title : "MaxShadowRatio" ,
Value : formatFloat ( d . MaxShadowRatio , 4 ) ,
Short : true ,
} )
}
if d . EnableLookBack {
fields = append ( fields , slack . AttachmentField {
Title : "LookBackFrames" ,
Value : strconv . Itoa ( d . LookBackFrames ) ,
Short : true ,
} )
}
return slack . Attachment {
Color : "" ,
Fallback : "" ,
ID : 0 ,
Title : name ,
Pretext : "" ,
Text : "" ,
Fields : fields ,
Footer : "" ,
FooterIcon : "" ,
Ts : "" ,
}
}
func ( d * KLineDetector ) String ( ) string {
2020-06-20 12:23:26 +00:00
var name = fmt . Sprintf ( "Detector %s (%f < x < %f)" , d . Interval , d . MinMaxPriceChange , d . MaxMaxPriceChange )
2020-06-18 15:46:59 +00:00
if d . EnableMinThickness {
name += fmt . Sprintf ( " [MinThickness: %f]" , d . MinThickness )
}
if d . EnableLookBack {
name += fmt . Sprintf ( " [LookBack: %d]" , d . LookBackFrames )
}
if d . EnableMaxShadowRatio {
name += fmt . Sprintf ( " [MaxShadowRatio: %f]" , d . MaxShadowRatio )
}
2020-06-24 14:13:18 +00:00
2020-06-18 15:46:59 +00:00
return name
}
2020-06-24 14:13:18 +00:00
func ( d * KLineDetector ) NewOrder ( e * KLineEvent , tradingCtx * TradingContext ) * Order {
2020-07-11 04:51:02 +00:00
var kline types . KLineOrWindow = e . KLine
2020-06-18 15:46:59 +00:00
if d . EnableLookBack {
klineWindow := tradingCtx . KLineWindows [ e . KLine . Interval ]
if len ( klineWindow ) >= d . LookBackFrames {
kline = klineWindow . Tail ( d . LookBackFrames )
}
}
var trend = kline . GetTrend ( )
var side binance . SideType
if trend < 0 {
side = binance . SideTypeBuy
} else if trend > 0 {
side = binance . SideTypeSell
}
2020-06-18 16:07:05 +00:00
var volume = tradingCtx . Market . FormatVolume ( VolumeByPriceChange ( tradingCtx . Market , kline . GetClose ( ) , kline . GetChange ( ) , side ) )
2020-06-24 14:13:18 +00:00
return & Order {
2020-06-18 15:46:59 +00:00
Symbol : e . KLine . Symbol ,
Type : binance . OrderTypeMarket ,
Side : side ,
VolumeStr : volume ,
}
}
2020-07-11 04:51:02 +00:00
func ( d * KLineDetector ) Detect ( e * KLineEvent , tradingCtx * TradingContext ) ( reason string , kline types . KLineOrWindow , ok bool ) {
2020-06-19 08:03:15 +00:00
kline = e . KLine
2020-06-18 15:46:59 +00:00
// if the 3m trend is drop, do not buy, let 5m window handle it.
if d . EnableLookBack {
klineWindow := tradingCtx . KLineWindows [ e . KLine . Interval ]
if len ( klineWindow ) >= d . LookBackFrames {
kline = klineWindow . Tail ( d . LookBackFrames )
}
/ *
if lookbackKline . AllDrop ( ) {
trader . Infof ( "1m window all drop down (%d frames), do not buy: %+v" , d . LookBackFrames , klineWindow )
} else if lookbackKline . AllRise ( ) {
trader . Infof ( "1m window all rise up (%d frames), do not sell: %+v" , d . LookBackFrames , klineWindow )
}
* /
}
var maxChange = math . Abs ( kline . GetMaxChange ( ) )
2020-06-20 12:23:26 +00:00
if maxChange < d . MinMaxPriceChange {
2020-06-19 08:03:15 +00:00
return "" , kline , false
2020-06-18 15:46:59 +00:00
}
2020-06-20 12:23:26 +00:00
if NotZero ( d . MaxMaxPriceChange ) && maxChange > d . MaxMaxPriceChange {
2020-06-24 14:25:06 +00:00
return fmt . Sprintf ( "exceeded max price change %.4f > %.4f" , maxChange , d . MaxMaxPriceChange ) , kline , false
2020-06-18 15:46:59 +00:00
}
if d . EnableMinThickness {
if kline . GetThickness ( ) < d . MinThickness {
2020-06-24 14:25:06 +00:00
return fmt . Sprintf ( "kline too thin. %.4f < min kline thickness %.4f" , kline . GetThickness ( ) , d . MinThickness ) , kline , false
2020-06-18 15:46:59 +00:00
}
}
var trend = kline . GetTrend ( )
if d . EnableMaxShadowRatio {
if trend > 0 {
if kline . GetUpperShadowRatio ( ) > d . MaxShadowRatio {
2020-06-24 14:25:06 +00:00
return fmt . Sprintf ( "kline upper shadow ratio too high. %.4f > %.4f (MaxShadowRatio)" , kline . GetUpperShadowRatio ( ) , d . MaxShadowRatio ) , kline , false
2020-06-18 15:46:59 +00:00
}
} else if trend < 0 {
if kline . GetLowerShadowRatio ( ) > d . MaxShadowRatio {
2020-06-24 14:25:06 +00:00
return fmt . Sprintf ( "kline lower shadow ratio too high. %.4f > %.4f (MaxShadowRatio)" , kline . GetLowerShadowRatio ( ) , d . MaxShadowRatio ) , kline , false
2020-06-18 15:46:59 +00:00
}
}
}
if trend > 0 && kline . BounceUp ( ) { // trend up, ignore bounce up
2020-06-24 14:25:06 +00:00
return fmt . Sprintf ( "bounce up, do not sell, kline mid: %.4f" , kline . Mid ( ) ) , kline , false
2020-06-18 15:46:59 +00:00
} else if trend < 0 && kline . BounceDown ( ) { // trend down, ignore bounce down
2020-06-24 14:25:06 +00:00
return fmt . Sprintf ( "bounce down, do not buy, kline mid: %.4f" , kline . Mid ( ) ) , kline , false
2020-06-18 15:46:59 +00:00
}
2020-06-24 14:13:18 +00:00
if NotZero ( d . MinProfitPriceTick ) {
// do not buy too early if it's greater than the average bid price + min profit tick
if trend < 0 && kline . GetClose ( ) > ( tradingCtx . AverageBidPrice - d . MinProfitPriceTick ) {
2020-06-24 14:25:06 +00:00
return fmt . Sprintf ( "price %f is greater than the average price + min profit tick %f" , kline . GetClose ( ) , tradingCtx . AverageBidPrice - d . MinProfitPriceTick ) , kline , false
2020-06-24 14:13:18 +00:00
}
// do not sell too early if it's less than the average bid price + min profit tick
if trend > 0 && kline . GetClose ( ) < ( tradingCtx . AverageBidPrice + d . MinProfitPriceTick ) {
2020-06-24 14:25:06 +00:00
return fmt . Sprintf ( "price %f is less than the average price + min profit tick %f" , kline . GetClose ( ) , tradingCtx . AverageBidPrice + d . MinProfitPriceTick ) , kline , false
2020-06-24 14:13:18 +00:00
}
}
2020-06-18 15:46:59 +00:00
/ *
if toPrice ( kline . GetClose ( ) ) == toPrice ( kline . GetLow ( ) ) {
return fmt . Sprintf ( "close near the lowest price, the price might continue to drop." ) , false
}
* /
2020-06-19 08:03:15 +00:00
return "" , kline , true
2020-06-18 15:46:59 +00:00
}