bbgo_origin/bbgo/kline_detector.go

233 lines
6.4 KiB
Go
Raw Normal View History

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
}