mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
Merge pull request #44 from c9s/indicator/boll
feature: add bollinger band indicator
This commit is contained in:
commit
8025e7003b
1
go.mod
1
go.mod
|
@ -41,6 +41,7 @@ require (
|
|||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
||||
gonum.org/v1/gonum v0.8.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c
|
||||
)
|
||||
|
|
16
go.sum
16
go.sum
|
@ -21,6 +21,7 @@ github.com/adshao/go-binance v0.0.0-20200604145522-bf563a35f17f h1:lVxx5HSt/impr
|
|||
github.com/adshao/go-binance v0.0.0-20200604145522-bf563a35f17f/go.mod h1:XlIpE7brbCEQxp6VRouG/ZgjLjygQWE1xnc1DtQNp6I=
|
||||
github.com/adshao/go-binance v0.0.0-20201015231210-37cee298310e h1:+fOwsQnvjCIVXuiVbyfuzNbubHvUrx1saeRa9pd7Df8=
|
||||
github.com/adshao/go-binance v0.0.0-20201015231210-37cee298310e/go.mod h1:XlIpE7brbCEQxp6VRouG/ZgjLjygQWE1xnc1DtQNp6I=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
|
@ -60,6 +61,7 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
|
|||
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU=
|
||||
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
|
@ -79,6 +81,7 @@ github.com/go-test/deep v1.0.6/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V
|
|||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
|
@ -147,6 +150,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
|||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
|
@ -301,11 +305,15 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -375,8 +383,10 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb
|
|||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
|
@ -393,6 +403,11 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.1 h1:wGtP3yGpc5mCLOLeTeBdjeui9oZSz5De0eOjMLC/QuQ=
|
||||
gonum.org/v1/gonum v0.8.1/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
|
@ -444,3 +459,4 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
|
|
@ -11,6 +11,7 @@ type StandardIndicatorSet struct {
|
|||
// interval -> window
|
||||
SMA map[types.IntervalWindow]*indicator.SMA
|
||||
EWMA map[types.IntervalWindow]*indicator.EWMA
|
||||
BOLL map[types.IntervalWindow]*indicator.BOLL
|
||||
|
||||
store *MarketDataStore
|
||||
}
|
||||
|
@ -33,6 +34,12 @@ func NewStandardIndicatorSet(symbol string, store *MarketDataStore) *StandardInd
|
|||
set.EWMA[iw] = &indicator.EWMA{IntervalWindow: iw}
|
||||
set.EWMA[iw].Bind(store)
|
||||
}
|
||||
|
||||
// setup BOLL indicator, we may refactor BOLL indicator by subscribing SMA indicator,
|
||||
// however, since general used BOLLINGER band use window 21, which is not in the existing SMA indicator sets.
|
||||
iw := types.IntervalWindow{Interval: interval, Window: 21}
|
||||
set.BOLL[iw] = &indicator.BOLL{IntervalWindow: iw, K: 2.0}
|
||||
set.BOLL[iw].Bind(store)
|
||||
}
|
||||
|
||||
return set
|
||||
|
|
|
@ -46,8 +46,6 @@ type Trader struct {
|
|||
exchangeStrategies map[string][]SingleExchangeStrategy
|
||||
|
||||
tradeReporter *TradeReporter
|
||||
// reportTimer *time.Timer
|
||||
// ProfitAndLossCalculator *accounting.ProfitAndLossCalculator
|
||||
}
|
||||
|
||||
func NewTrader(environ *Environment) *Trader {
|
||||
|
|
89
pkg/indicator/boll.go
Normal file
89
pkg/indicator/boll.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package indicator
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gonum.org/v1/gonum/stat"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
/*
|
||||
BOLL implements the bollinger indicator:
|
||||
|
||||
The Basics of Bollinger Bands
|
||||
- https://www.investopedia.com/articles/technical/102201.asp
|
||||
|
||||
Bollinger Bands
|
||||
- https://www.investopedia.com/terms/b/bollingerbands.asp
|
||||
|
||||
Bollinger Bands Technical indicator guide:
|
||||
- https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/bollinger-bands
|
||||
*/
|
||||
type BOLL struct {
|
||||
types.IntervalWindow
|
||||
|
||||
// times of Std, generally it's 2
|
||||
K float64
|
||||
|
||||
SMA Float64Slice
|
||||
StdDev Float64Slice
|
||||
UpBand Float64Slice
|
||||
DownBand Float64Slice
|
||||
|
||||
EndTime time.Time
|
||||
}
|
||||
|
||||
func (inc *BOLL) LastSMA() float64 {
|
||||
return inc.SMA[len(inc.SMA)-1]
|
||||
}
|
||||
|
||||
func (inc *BOLL) calculateAndUpdate(kLines []types.KLine) {
|
||||
if len(kLines) < inc.Window {
|
||||
return
|
||||
}
|
||||
|
||||
var index = len(kLines) - 1
|
||||
var kline = kLines[index]
|
||||
|
||||
if inc.EndTime != zeroTime && kline.EndTime.Before(inc.EndTime) {
|
||||
return
|
||||
}
|
||||
|
||||
var recentK = kLines[index-(inc.Window-1) : index+1]
|
||||
var sma = calculateSMA(recentK)
|
||||
inc.SMA.Push(sma)
|
||||
|
||||
var prices []float64
|
||||
for _, k := range recentK {
|
||||
prices = append(prices, k.Close)
|
||||
}
|
||||
|
||||
var std = stat.StdDev(prices, nil)
|
||||
inc.StdDev.Push(std)
|
||||
|
||||
var upBand = sma + inc.K*std
|
||||
inc.UpBand.Push(upBand)
|
||||
|
||||
var downBand = sma - inc.K*std
|
||||
inc.DownBand.Push(downBand)
|
||||
|
||||
// update end time
|
||||
inc.EndTime = kLines[index].EndTime
|
||||
}
|
||||
|
||||
func (inc *BOLL) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
|
||||
if inc.Interval != interval {
|
||||
return
|
||||
}
|
||||
|
||||
if inc.EndTime != zeroTime && inc.EndTime.Before(inc.EndTime) {
|
||||
return
|
||||
}
|
||||
|
||||
inc.calculateAndUpdate(window)
|
||||
}
|
||||
|
||||
func (inc *BOLL) Bind(updater KLineWindowUpdater) {
|
||||
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
|
||||
}
|
|
@ -16,8 +16,8 @@ var zeroTime time.Time
|
|||
|
||||
type SMA struct {
|
||||
types.IntervalWindow
|
||||
Values Float64Slice
|
||||
EndTime time.Time
|
||||
Values Float64Slice
|
||||
EndTime time.Time
|
||||
}
|
||||
|
||||
func (inc *SMA) Last() float64 {
|
||||
|
|
|
@ -15,17 +15,17 @@ func init() {
|
|||
|
||||
type Strategy struct {
|
||||
Symbol string `json:"symbol"`
|
||||
|
||||
types.Market
|
||||
}
|
||||
|
||||
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"})
|
||||
}
|
||||
|
||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"})
|
||||
session.Stream.OnKLineClosed(func(kline types.KLine) {
|
||||
market, ok := session.Market(s.Symbol)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
quoteBalance, ok := session.Account.Balance(market.QuoteCurrency)
|
||||
quoteBalance, ok := session.Account.Balance(s.Market.QuoteCurrency)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
|
||||
trend := kline.GetTrend()
|
||||
switch trend {
|
||||
case 1:
|
||||
case types.TrendUp:
|
||||
// if it goes up and it's above the moving average price, then we sell
|
||||
if closePrice > movingAveragePrice {
|
||||
s.notify(":chart_with_upwards_trend: closePrice %f is above movingAveragePrice %f, submitting SELL order", closePrice, movingAveragePrice)
|
||||
|
@ -137,7 +137,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
log.WithError(err).Error("submit order error")
|
||||
}
|
||||
}
|
||||
case -1:
|
||||
case types.TrendDown:
|
||||
// if it goes down and it's below the moving average price, then we buy
|
||||
if closePrice < movingAveragePrice {
|
||||
s.notify(":chart_with_downwards_trend: closePrice %f is below movingAveragePrice %f, submitting BUY order", closePrice, movingAveragePrice)
|
||||
|
|
|
@ -10,6 +10,12 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/util"
|
||||
)
|
||||
|
||||
type Trend int
|
||||
|
||||
const TrendUp = 1
|
||||
const TrendFlat = 0
|
||||
const TrendDown = -1
|
||||
|
||||
type KLineOrWindow interface {
|
||||
GetInterval() string
|
||||
GetTrend() int
|
||||
|
@ -87,16 +93,16 @@ func (k KLine) BounceDown() bool {
|
|||
return trend > 0 && k.Open < mid && k.Close < mid
|
||||
}
|
||||
|
||||
func (k KLine) GetTrend() int {
|
||||
func (k KLine) GetTrend() Trend {
|
||||
o := k.GetOpen()
|
||||
c := k.GetClose()
|
||||
|
||||
if c > o {
|
||||
return 1
|
||||
return TrendUp
|
||||
} else if c < o {
|
||||
return -1
|
||||
return TrendDown
|
||||
}
|
||||
return 0
|
||||
return TrendFlat
|
||||
}
|
||||
|
||||
func (k KLine) GetHigh() float64 {
|
||||
|
@ -442,4 +448,3 @@ func (k KLineWindow) SlackAttachment() slack.Attachment {
|
|||
}
|
||||
|
||||
type KLineCallback func(kline KLine)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user