mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
strategy: add harmonic shark pattern recognition
strategy: add harmonic shark pattern recognition
This commit is contained in:
parent
04453c23ea
commit
f1ae7b5f30
33
config/harmonic.yaml
Normal file
33
config/harmonic.yaml
Normal file
|
@ -0,0 +1,33 @@
|
|||
persistence:
|
||||
json:
|
||||
directory: var/data
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
db: 0
|
||||
|
||||
sessions:
|
||||
binance:
|
||||
exchange: binance
|
||||
envVarPrefix: binance
|
||||
|
||||
exchangeStrategies:
|
||||
- on: binance
|
||||
harmonic:
|
||||
symbol: BTCBUSD
|
||||
interval: 1s
|
||||
window: 500
|
||||
quantity: 0.05
|
||||
|
||||
backtest:
|
||||
sessions:
|
||||
- binance
|
||||
startTime: "2022-09-30"
|
||||
endTime: "2022-10-01"
|
||||
symbols:
|
||||
- BTCBUSD
|
||||
accounts:
|
||||
binance:
|
||||
balances:
|
||||
BTC: 1.0
|
||||
BUSD: 40_000.0
|
|
@ -17,6 +17,7 @@ import (
|
|||
_ "github.com/c9s/bbgo/pkg/strategy/fmaker"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/funding"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/grid"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/harmonic"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/irr"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/kline"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/marketcap"
|
||||
|
|
201
pkg/strategy/harmonic/shark.go
Normal file
201
pkg/strategy/harmonic/shark.go
Normal file
|
@ -0,0 +1,201 @@
|
|||
package harmonic
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/datatype/floats"
|
||||
"github.com/c9s/bbgo/pkg/indicator"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
var zeroTime time.Time
|
||||
|
||||
//go:generate callbackgen -type SHARK
|
||||
type SHARK struct {
|
||||
types.IntervalWindow
|
||||
types.SeriesBase
|
||||
|
||||
Lows floats.Slice
|
||||
Highs floats.Slice
|
||||
LongScores floats.Slice
|
||||
ShortScores floats.Slice
|
||||
|
||||
Values floats.Slice
|
||||
|
||||
EndTime time.Time
|
||||
|
||||
updateCallbacks []func(value float64)
|
||||
}
|
||||
|
||||
var _ types.SeriesExtend = &SHARK{}
|
||||
|
||||
func (inc *SHARK) Update(high, low, price float64) {
|
||||
if inc.SeriesBase.Series == nil {
|
||||
inc.SeriesBase.Series = inc
|
||||
}
|
||||
inc.Highs.Update(high)
|
||||
inc.Lows.Update(low)
|
||||
|
||||
if inc.Highs.Length() < inc.Window || inc.Lows.Length() < inc.Window {
|
||||
return
|
||||
}
|
||||
|
||||
longScore := inc.SharkLong(inc.Highs, inc.Lows, price, inc.Window)
|
||||
shortScore := inc.SharkShort(inc.Highs, inc.Lows, price, inc.Window)
|
||||
|
||||
inc.LongScores.Push(longScore)
|
||||
inc.ShortScores.Push(shortScore)
|
||||
|
||||
inc.Values.Push(longScore - shortScore)
|
||||
|
||||
}
|
||||
|
||||
func (inc *SHARK) Last() float64 {
|
||||
if len(inc.Values) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return inc.Values[len(inc.Values)-1]
|
||||
}
|
||||
|
||||
func (inc *SHARK) Index(i int) float64 {
|
||||
if i >= len(inc.Values) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return inc.Values[len(inc.Values)-1-i]
|
||||
}
|
||||
|
||||
func (inc *SHARK) Length() int {
|
||||
return len(inc.Values)
|
||||
}
|
||||
|
||||
func (inc *SHARK) BindK(target indicator.KLineClosedEmitter, symbol string, interval types.Interval) {
|
||||
target.OnKLineClosed(types.KLineWith(symbol, interval, inc.PushK))
|
||||
}
|
||||
|
||||
func (inc *SHARK) PushK(k types.KLine) {
|
||||
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
|
||||
return
|
||||
}
|
||||
|
||||
inc.Update(indicator.KLineHighPriceMapper(k), indicator.KLineLowPriceMapper(k), indicator.KLineClosePriceMapper(k))
|
||||
inc.EndTime = k.EndTime.Time()
|
||||
inc.EmitUpdate(inc.Last())
|
||||
}
|
||||
|
||||
func (inc *SHARK) LoadK(allKLines []types.KLine) {
|
||||
for _, k := range allKLines {
|
||||
inc.PushK(k)
|
||||
}
|
||||
inc.EmitUpdate(inc.Last())
|
||||
}
|
||||
|
||||
func (inc SHARK) SharkLong(highs, lows floats.Slice, p float64, lookback int) float64 {
|
||||
score := 0.
|
||||
for x := 5; x < lookback; x++ {
|
||||
if lows.Index(x-1) > lows.Index(x) && lows.Index(x) < lows.Index(x+1) {
|
||||
X := lows.Index(x)
|
||||
for a := 4; a < x; a++ {
|
||||
if highs.Index(a-1) < highs.Index(a) && highs.Index(a) > highs.Index(a+1) {
|
||||
A := highs.Index(a)
|
||||
XA := math.Abs(X - A)
|
||||
hB := A - 0.382*XA
|
||||
lB := A - 0.618*XA
|
||||
for b := 3; b < a; b++ {
|
||||
if lows.Index(b-1) > lows.Index(b) && lows.Index(b) < lows.Index(b+1) {
|
||||
B := lows.Index(b)
|
||||
if hB > B && B > lB {
|
||||
//log.Infof("got point B:%f", B)
|
||||
AB := math.Abs(A - B)
|
||||
hC := B + 1.618*AB
|
||||
lC := B + 1.13*AB
|
||||
for c := 2; c < b; c++ {
|
||||
if highs.Index(c-1) < highs.Index(c) && highs.Index(c) > highs.Index(c+1) {
|
||||
C := highs.Index(c)
|
||||
if hC > C && C > lC {
|
||||
//log.Infof("got point C:%f", C)
|
||||
XC := math.Abs(X - C)
|
||||
hD := C - 0.886*XC
|
||||
lD := C - 1.13*XC
|
||||
//for d := 1; d < c; d++ {
|
||||
//if lows.Index(d-1) > lows.Index(d) && lows.Index(d) < lows.Index(d+1) {
|
||||
D := p //lows.Index(d)
|
||||
if hD > D && D > lD {
|
||||
BC := math.Abs(B - C)
|
||||
hD2 := C - 1.618*BC
|
||||
lD2 := C - 2.24*BC
|
||||
if hD2 > D && D > lD2 {
|
||||
//log.Infof("got point D:%f", D)
|
||||
score++
|
||||
}
|
||||
}
|
||||
//}
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
func (inc SHARK) SharkShort(highs, lows floats.Slice, p float64, lookback int) float64 {
|
||||
score := 0.
|
||||
for x := 5; x < lookback; x++ {
|
||||
if highs.Index(x-1) < highs.Index(x) && highs.Index(x) > highs.Index(x+1) {
|
||||
X := highs.Index(x)
|
||||
for a := 4; a < x; a++ {
|
||||
if lows.Index(a-1) > lows.Index(a) && lows.Index(a) < lows.Index(a+1) {
|
||||
A := lows.Index(a)
|
||||
XA := math.Abs(X - A)
|
||||
lB := A + 0.382*XA
|
||||
hB := A + 0.618*XA
|
||||
for b := 3; b < a; b++ {
|
||||
if highs.Index(b-1) > highs.Index(b) && highs.Index(b) < highs.Index(b+1) {
|
||||
B := highs.Index(b)
|
||||
if hB > B && B > lB {
|
||||
//log.Infof("got point B:%f", B)
|
||||
AB := math.Abs(A - B)
|
||||
lC := B - 1.618*AB
|
||||
hC := B - 1.13*AB
|
||||
for c := 2; c < b; c++ {
|
||||
if lows.Index(c-1) < lows.Index(c) && lows.Index(c) > lows.Index(c+1) {
|
||||
C := lows.Index(c)
|
||||
if hC > C && C > lC {
|
||||
//log.Infof("got point C:%f", C)
|
||||
XC := math.Abs(X - C)
|
||||
lD := C + 0.886*XC
|
||||
hD := C + 1.13*XC
|
||||
//for d := 1; d < c; d++ {
|
||||
//if lows.Index(d-1) > lows.Index(d) && lows.Index(d) < lows.Index(d+1) {
|
||||
D := p //lows.Index(d)
|
||||
if hD > D && D > lD {
|
||||
BC := math.Abs(B - C)
|
||||
lD2 := C + 1.618*BC
|
||||
hD2 := C + 2.24*BC
|
||||
if hD2 > D && D > lD2 {
|
||||
//log.Infof("got point D:%f", D)
|
||||
score++
|
||||
}
|
||||
}
|
||||
//}
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return score
|
||||
}
|
15
pkg/strategy/harmonic/shark_callbacks.go
Normal file
15
pkg/strategy/harmonic/shark_callbacks.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Code generated by "callbackgen -type SHARK"; DO NOT EDIT.
|
||||
|
||||
package harmonic
|
||||
|
||||
import ()
|
||||
|
||||
func (inc *SHARK) OnUpdate(cb func(value float64)) {
|
||||
inc.updateCallbacks = append(inc.updateCallbacks, cb)
|
||||
}
|
||||
|
||||
func (inc *SHARK) EmitUpdate(value float64) {
|
||||
for _, cb := range inc.updateCallbacks {
|
||||
cb(value)
|
||||
}
|
||||
}
|
369
pkg/strategy/harmonic/strategy.go
Normal file
369
pkg/strategy/harmonic/strategy.go
Normal file
|
@ -0,0 +1,369 @@
|
|||
package harmonic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/datatype/floats"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/interact"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
const ID = "harmonic"
|
||||
|
||||
var log = logrus.WithField("strategy", ID)
|
||||
|
||||
func init() {
|
||||
bbgo.RegisterStrategy(ID, &Strategy{})
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
Environment *bbgo.Environment
|
||||
Symbol string `json:"symbol"`
|
||||
Market types.Market
|
||||
|
||||
types.IntervalWindow
|
||||
//bbgo.OpenPositionOptions
|
||||
|
||||
// persistence fields
|
||||
Position *types.Position `persistence:"position"`
|
||||
ProfitStats *types.ProfitStats `persistence:"profit_stats"`
|
||||
TradeStats *types.TradeStats `persistence:"trade_stats"`
|
||||
|
||||
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
||||
|
||||
session *bbgo.ExchangeSession
|
||||
orderExecutor *bbgo.GeneralOrderExecutor
|
||||
|
||||
bbgo.QuantityOrAmount
|
||||
|
||||
// StrategyController
|
||||
bbgo.StrategyController
|
||||
|
||||
shark *SHARK
|
||||
|
||||
// plotting
|
||||
bbgo.SourceSelector
|
||||
priceLines *types.Queue
|
||||
midPrice fixedpoint.Value
|
||||
lock sync.RWMutex `ignore:"true"`
|
||||
positionLock sync.RWMutex `ignore:"true"`
|
||||
startTime time.Time
|
||||
minutesCounter int
|
||||
frameKLine *types.KLine
|
||||
kline1m *types.KLine
|
||||
CanvasPath string `json:"canvasPath"`
|
||||
HLRangeWindow int `json:"hlRangeWindow"`
|
||||
Window1m int `json:"window1m"`
|
||||
|
||||
// This is not related to trade but for statistics graph generation
|
||||
// Will deduct fee in percentage from every trade
|
||||
GraphPNLDeductFee bool `json:"graphPNLDeductFee"`
|
||||
GraphPNLPath string `json:"graphPNLPath"`
|
||||
GraphCumPNLPath string `json:"graphCumPNLPath"`
|
||||
// Whether to generate graph when shutdown
|
||||
GenerateGraph bool `json:"generateGraph"`
|
||||
}
|
||||
|
||||
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
|
||||
|
||||
if !bbgo.IsBackTesting {
|
||||
session.Subscribe(types.MarketTradeChannel, s.Symbol, types.SubscribeOptions{})
|
||||
}
|
||||
|
||||
s.ExitMethods.SetAndSubscribe(session, s)
|
||||
}
|
||||
|
||||
func (s *Strategy) ID() string {
|
||||
return ID
|
||||
}
|
||||
|
||||
func (s *Strategy) InstanceID() string {
|
||||
return fmt.Sprintf("%s:%s", ID, s.Symbol)
|
||||
}
|
||||
|
||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
var instanceID = s.InstanceID()
|
||||
|
||||
if s.Position == nil {
|
||||
s.Position = types.NewPositionFromMarket(s.Market)
|
||||
}
|
||||
|
||||
if s.ProfitStats == nil {
|
||||
s.ProfitStats = types.NewProfitStats(s.Market)
|
||||
}
|
||||
|
||||
if s.TradeStats == nil {
|
||||
s.TradeStats = types.NewTradeStats(s.Symbol)
|
||||
}
|
||||
|
||||
// StrategyController
|
||||
s.Status = types.StrategyStatusRunning
|
||||
|
||||
s.OnSuspend(func() {
|
||||
// Cancel active orders
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
})
|
||||
|
||||
s.OnEmergencyStop(func() {
|
||||
// Cancel active orders
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
// Close 100% position
|
||||
//_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||
})
|
||||
|
||||
s.session = session
|
||||
|
||||
s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
||||
s.orderExecutor.BindEnvironment(s.Environment)
|
||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||
s.orderExecutor.BindTradeStats(s.TradeStats)
|
||||
|
||||
profit := floats.Slice{1., 1.}
|
||||
price, _ := s.session.LastPrice(s.Symbol)
|
||||
initAsset := s.CalcAssetValue(price).Float64()
|
||||
cumProfit := floats.Slice{initAsset, initAsset}
|
||||
s.orderExecutor.TradeCollector().OnTrade(func(trade types.Trade, _profit, netProfit fixedpoint.Value) {
|
||||
profit.Update(netProfit.Float64())
|
||||
cumProfit.Update(s.CalcAssetValue(trade.Price).Float64())
|
||||
})
|
||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||
bbgo.Sync(s)
|
||||
})
|
||||
s.orderExecutor.Bind()
|
||||
|
||||
for _, method := range s.ExitMethods {
|
||||
method.Bind(session, s.orderExecutor)
|
||||
}
|
||||
|
||||
kLineStore, _ := s.session.MarketDataStore(s.Symbol)
|
||||
s.shark = &SHARK{IntervalWindow: types.IntervalWindow{Window: s.Window, Interval: s.Interval}}
|
||||
s.shark.BindK(s.session.MarketDataStream, s.Symbol, s.shark.Interval)
|
||||
if klines, ok := kLineStore.KLinesOfInterval(s.shark.Interval); ok {
|
||||
s.shark.LoadK((*klines)[0:])
|
||||
}
|
||||
s.session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
|
||||
|
||||
log.Infof("Shark Score: %f, Current Price: %f", s.shark.Last(), kline.Close.Float64())
|
||||
|
||||
//previousRegime := s.shark.Values.Tail(10).Mean()
|
||||
//zeroThreshold := 5.
|
||||
|
||||
if s.shark.Rank(s.Window).Last()/float64(s.Window) > 0.99 { // && ((previousRegime < zeroThreshold && previousRegime > -zeroThreshold) || s.shark.Index(1) < 0)
|
||||
if s.Position.IsShort() {
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
s.orderExecutor.ClosePosition(ctx, fixedpoint.One, "close short position")
|
||||
}
|
||||
_, err := s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: types.SideTypeBuy,
|
||||
Quantity: s.Quantity,
|
||||
Type: types.OrderTypeMarket,
|
||||
Tag: "shark long: buy in",
|
||||
})
|
||||
if err == nil {
|
||||
_, err = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: types.SideTypeSell,
|
||||
Quantity: s.Quantity,
|
||||
Price: fixedpoint.NewFromFloat(s.shark.Highs.Tail(100).Max()),
|
||||
Type: types.OrderTypeLimit,
|
||||
Tag: "shark long: sell back",
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
|
||||
} else if s.shark.Rank(s.Window).Last()/float64(s.Window) < 0.01 { // && ((previousRegime < zeroThreshold && previousRegime > -zeroThreshold) || s.shark.Index(1) > 0)
|
||||
if s.Position.IsLong() {
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
s.orderExecutor.ClosePosition(ctx, fixedpoint.One, "close long position")
|
||||
}
|
||||
_, err := s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: types.SideTypeSell,
|
||||
Quantity: s.Quantity,
|
||||
Type: types.OrderTypeMarket,
|
||||
Tag: "shark short: sell in",
|
||||
})
|
||||
if err == nil {
|
||||
_, err = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: types.SideTypeBuy,
|
||||
Quantity: s.Quantity,
|
||||
Price: fixedpoint.NewFromFloat(s.shark.Lows.Tail(100).Min()),
|
||||
Type: types.OrderTypeLimit,
|
||||
Tag: "shark short: buy back",
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
bbgo.RegisterCommand("/draw", "Draw Indicators", func(reply interact.Reply) {
|
||||
canvas := s.DrawIndicators(s.frameKLine.StartTime)
|
||||
var buffer bytes.Buffer
|
||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||
log.WithError(err).Errorf("cannot render indicators in oneliner")
|
||||
reply.Message(fmt.Sprintf("[error] cannot render indicators in harmonic: %v", err))
|
||||
return
|
||||
}
|
||||
bbgo.SendPhoto(&buffer)
|
||||
})
|
||||
|
||||
bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) {
|
||||
canvas := s.DrawPNL(&profit)
|
||||
var buffer bytes.Buffer
|
||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||
log.WithError(err).Errorf("cannot render pnl in oneliner")
|
||||
reply.Message(fmt.Sprintf("[error] cannot render pnl in harmonic: %v", err))
|
||||
return
|
||||
}
|
||||
bbgo.SendPhoto(&buffer)
|
||||
})
|
||||
|
||||
bbgo.RegisterCommand("/cumpnl", "Draw Cumulative PNL(Quote)", func(reply interact.Reply) {
|
||||
canvas := s.DrawCumPNL(&cumProfit)
|
||||
var buffer bytes.Buffer
|
||||
if err := canvas.Render(chart.PNG, &buffer); err != nil {
|
||||
log.WithError(err).Errorf("cannot render cumpnl in oneliner")
|
||||
reply.Message(fmt.Sprintf("[error] canot render cumpnl in harmonic: %v", err))
|
||||
return
|
||||
}
|
||||
bbgo.SendPhoto(&buffer)
|
||||
})
|
||||
|
||||
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
_, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String())
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) CalcAssetValue(price fixedpoint.Value) fixedpoint.Value {
|
||||
balances := s.session.GetAccount().Balances()
|
||||
return balances[s.Market.BaseCurrency].Total().Mul(price).Add(balances[s.Market.QuoteCurrency].Total())
|
||||
}
|
||||
|
||||
func (s *Strategy) DrawPNL(profit types.Series) *types.Canvas {
|
||||
canvas := types.NewCanvas(s.InstanceID())
|
||||
//log.Errorf("pnl Highest: %f, Lowest: %f", types.Highest(profit, profit.Length()), types.Lowest(profit, profit.Length()))
|
||||
length := profit.Length()
|
||||
if s.GraphPNLDeductFee {
|
||||
canvas.PlotRaw("pnl (with Fee Deducted)", profit, length)
|
||||
} else {
|
||||
canvas.PlotRaw("pnl", profit, length)
|
||||
}
|
||||
canvas.YAxis = chart.YAxis{
|
||||
ValueFormatter: func(v interface{}) string {
|
||||
if vf, isFloat := v.(float64); isFloat {
|
||||
return fmt.Sprintf("%.4f", vf)
|
||||
}
|
||||
return ""
|
||||
},
|
||||
}
|
||||
canvas.PlotRaw("1", types.NumberSeries(1), length)
|
||||
return canvas
|
||||
}
|
||||
|
||||
func (s *Strategy) DrawCumPNL(cumProfit types.Series) *types.Canvas {
|
||||
canvas := types.NewCanvas(s.InstanceID())
|
||||
canvas.PlotRaw("cumulative pnl", cumProfit, cumProfit.Length())
|
||||
canvas.YAxis = chart.YAxis{
|
||||
ValueFormatter: func(v interface{}) string {
|
||||
if vf, isFloat := v.(float64); isFloat {
|
||||
return fmt.Sprintf("%.4f", vf)
|
||||
}
|
||||
return ""
|
||||
},
|
||||
}
|
||||
return canvas
|
||||
}
|
||||
|
||||
func (s *Strategy) initIndicators(store *bbgo.SerialMarketDataStore) error {
|
||||
|
||||
klines, ok := store.KLinesOfInterval(s.Interval)
|
||||
klinesLength := len(*klines)
|
||||
if !ok || klinesLength == 0 {
|
||||
return errors.New("klines not exists")
|
||||
}
|
||||
if s.frameKLine != nil && klines != nil {
|
||||
s.frameKLine.Set(&(*klines)[len(*klines)-1])
|
||||
}
|
||||
klines, ok = store.KLinesOfInterval(types.Interval1m)
|
||||
klinesLength = len(*klines)
|
||||
if !ok || klinesLength == 0 {
|
||||
return errors.New("klines not exists")
|
||||
}
|
||||
if s.kline1m != nil && klines != nil {
|
||||
s.kline1m.Set(&(*klines)[len(*klines)-1])
|
||||
}
|
||||
s.startTime = s.kline1m.StartTime.Time().Add(s.kline1m.Interval.Duration())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) DrawIndicators(time types.Time) *types.Canvas {
|
||||
canvas := types.NewCanvas(s.InstanceID(), s.Interval)
|
||||
Length := s.priceLines.Length()
|
||||
if Length > 300 {
|
||||
Length = 300
|
||||
}
|
||||
log.Infof("draw indicators with %d data", Length)
|
||||
mean := s.priceLines.Mean(Length)
|
||||
|
||||
canvas.Plot("zero", types.NumberSeries(mean), time, Length)
|
||||
canvas.Plot("price", s.priceLines, time, Length)
|
||||
return canvas
|
||||
}
|
||||
|
||||
func (s *Strategy) Draw(time types.Time, profit types.Series, cumProfit types.Series) {
|
||||
canvas := s.DrawIndicators(time)
|
||||
f, err := os.Create(s.CanvasPath)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("cannot create on %s", s.CanvasPath)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
if err := canvas.Render(chart.PNG, f); err != nil {
|
||||
log.WithError(err).Errorf("cannot render in harmonic")
|
||||
}
|
||||
|
||||
canvas = s.DrawPNL(profit)
|
||||
f, err = os.Create(s.GraphPNLPath)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("open pnl")
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
if err := canvas.Render(chart.PNG, f); err != nil {
|
||||
log.WithError(err).Errorf("render pnl")
|
||||
}
|
||||
|
||||
canvas = s.DrawCumPNL(cumProfit)
|
||||
f, err = os.Create(s.GraphCumPNLPath)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("open cumpnl")
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
if err := canvas.Render(chart.PNG, f); err != nil {
|
||||
log.WithError(err).Errorf("render cumpnl")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user