add kline store

This commit is contained in:
c9s 2020-09-05 16:22:46 +08:00
parent dd55857423
commit a90184a464
7 changed files with 216 additions and 147 deletions

146
bbgo/kline_regression.go Normal file
View File

@ -0,0 +1,146 @@
package bbgo
import (
"context"
"fmt"
"time"
"github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo/types"
"github.com/c9s/bbgo/pkg/util"
)
type KLineRegressionTrader struct {
// Context is trading Context
Context *TradingContext
SourceKLines []types.KLine
ProfitAndLossCalculator *ProfitAndLossCalculator
doneOrders []*types.SubmitOrder
pendingOrders []*types.SubmitOrder
}
func (trader *KLineRegressionTrader) SubmitOrder(cxt context.Context, order *types.SubmitOrder) {
trader.pendingOrders = append(trader.pendingOrders, order)
}
func (trader *KLineRegressionTrader) RunStrategy(ctx context.Context, strategy Strategy) (chan struct{}, error) {
logrus.Infof("[regression] number of kline data: %d", len(trader.SourceKLines))
maxExposure := 0.4
trader.Context.Quota = make(map[string]types.Balance)
for currency, balance := range trader.Context.Balances {
quota := balance
quota.Available *= maxExposure
trader.Context.Quota[currency] = quota
}
done := make(chan struct{})
defer close(done)
if err := strategy.Init(trader.Context, trader); err != nil {
return nil, err
}
standardStream := types.StandardPrivateStream{}
if err := strategy.OnNewStream(&standardStream); err != nil {
return nil, err
}
var tradeID int64 = 0
for _, kline := range trader.SourceKLines {
logrus.Debugf("kline %+v", kline)
fmt.Print(".")
standardStream.EmitKLineClosed(&kline)
for _, order := range trader.pendingOrders {
switch order.Side {
case types.SideTypeBuy:
fmt.Print("B")
case types.SideTypeSell:
fmt.Print("S")
}
var price float64
if order.Type == types.OrderTypeLimit {
price = util.MustParseFloat(order.Price)
} else {
price = kline.GetClose()
}
volume := util.MustParseFloat(order.Quantity)
fee := 0.0
feeCurrency := ""
trader.Context.Lock()
if order.Side == types.SideTypeBuy {
fee = price * volume * 0.001
feeCurrency = "USDT"
quote := trader.Context.Balances[trader.Context.Market.QuoteCurrency]
if quote.Available < volume*price {
logrus.Fatalf("quote balance not enough: %+v", quote)
}
quote.Available -= volume * price
trader.Context.Balances[trader.Context.Market.QuoteCurrency] = quote
base := trader.Context.Balances[trader.Context.Market.BaseCurrency]
base.Available += volume
trader.Context.Balances[trader.Context.Market.BaseCurrency] = base
} else {
fee = volume * 0.001
feeCurrency = "BTC"
base := trader.Context.Balances[trader.Context.Market.BaseCurrency]
if base.Available < volume {
logrus.Fatalf("base balance not enough: %+v", base)
}
base.Available -= volume
trader.Context.Balances[trader.Context.Market.BaseCurrency] = base
quote := trader.Context.Balances[trader.Context.Market.QuoteCurrency]
quote.Available += volume * price
trader.Context.Balances[trader.Context.Market.QuoteCurrency] = quote
}
trader.Context.Unlock()
trade := types.Trade{
ID: tradeID,
Price: price,
Quantity: volume,
Side: string(order.Side),
IsBuyer: order.Side == types.SideTypeBuy,
IsMaker: false,
Time: time.Unix(0, kline.EndTime*int64(time.Millisecond)),
Symbol: trader.Context.Symbol,
Fee: fee,
FeeCurrency: feeCurrency,
}
tradeID++
trader.ProfitAndLossCalculator.AddTrade(trade)
trader.doneOrders = append(trader.doneOrders, order)
}
// clear pending orders
trader.pendingOrders = nil
}
fmt.Print("\n")
report := trader.ProfitAndLossCalculator.Calculate()
report.Print()
logrus.Infof("wallet balance:")
for _, balance := range trader.Context.Balances {
logrus.Infof(" %s: %f", balance.Currency, balance.Available)
}
return done, nil
}

View File

@ -1,13 +1,15 @@
package bbgo package bbgo
import ( import (
"github.com/c9s/bbgo/pkg/bbgo/types"
"github.com/c9s/bbgo/pkg/slack/slackstyle"
log "github.com/sirupsen/logrus"
"github.com/slack-go/slack"
"strconv" "strconv"
"strings" "strings"
"time" "time"
log "github.com/sirupsen/logrus"
"github.com/slack-go/slack"
"github.com/c9s/bbgo/pkg/bbgo/slack/slackstyle"
"github.com/c9s/bbgo/pkg/bbgo/types"
) )
type ProfitAndLossCalculator struct { type ProfitAndLossCalculator struct {

View File

@ -2,12 +2,14 @@ package service
import ( import (
"context" "context"
"github.com/c9s/bbgo/pkg/bbgo/exchange/binance" "time"
"github.com/c9s/bbgo/pkg/bbgo/types"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"time"
"github.com/c9s/bbgo/pkg/bbgo/exchange/binance"
"github.com/c9s/bbgo/pkg/bbgo/types"
) )
type TradeSync struct { type TradeSync struct {

View File

@ -3,9 +3,10 @@ package slack
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/slack-go/slack" "github.com/slack-go/slack"
"strings"
) )
type LogHook struct { type LogHook struct {

48
bbgo/store.go Normal file
View File

@ -0,0 +1,48 @@
package bbgo
import (
"github.com/c9s/bbgo/pkg/bbgo/types"
)
type Interval string
var Interval1m = Interval("1m")
var Interval5m = Interval("5m")
var Interval1h = Interval("1h")
var Interval1d = Interval("1d")
type KLineStore struct {
// MaxKLines stores the max change kline per interval
MaxKLines map[Interval]types.KLine `json:"-"`
// KLineWindows stores all loaded klines per interval
KLineWindows map[Interval]types.KLineWindow `json:"-"`
}
func NewKLineStore() *KLineStore {
return &KLineStore{
MaxKLines: make(map[Interval]types.KLine),
// KLineWindows stores all loaded klines per interval
KLineWindows: make(map[Interval]types.KLineWindow),
}
}
func (store *KLineStore) BindPrivateStream(stream *types.StandardPrivateStream) {
stream.OnKLineClosed(store.handleKLineClosed)
}
func (store *KLineStore) handleKLineClosed(kline *types.KLine) {
store.AddKLine(*kline)
}
func (store *KLineStore) AddKLine(kline types.KLine) {
var interval = Interval(kline.Interval)
var window = store.KLineWindows[interval]
window.Add(kline)
if kline.GetMaxChange() > store.MaxKLines[interval].GetMaxChange() {
store.MaxKLines[interval] = kline
}
}

View File

@ -2,11 +2,11 @@ package bbgo
import ( import (
"context" "context"
"fmt"
"github.com/c9s/bbgo/pkg/service"
"github.com/c9s/bbgo/pkg/util"
"time" "time"
"github.com/c9s/bbgo/pkg/bbgo/service"
"github.com/c9s/bbgo/pkg/util"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo/exchange/binance" "github.com/c9s/bbgo/pkg/bbgo/exchange/binance"
@ -18,140 +18,6 @@ type Strategy interface {
OnNewStream(stream *types.StandardPrivateStream) error OnNewStream(stream *types.StandardPrivateStream) error
} }
type KLineRegressionTrader struct {
// Context is trading Context
Context *TradingContext
SourceKLines []types.KLine
ProfitAndLossCalculator *ProfitAndLossCalculator
doneOrders []*types.SubmitOrder
pendingOrders []*types.SubmitOrder
}
func (trader *KLineRegressionTrader) SubmitOrder(cxt context.Context, order *types.SubmitOrder) {
trader.pendingOrders = append(trader.pendingOrders, order)
}
func (trader *KLineRegressionTrader) RunStrategy(ctx context.Context, strategy Strategy) (chan struct{}, error) {
log.Infof("[regression] number of kline data: %d", len(trader.SourceKLines))
maxExposure := 0.4
trader.Context.Quota = make(map[string]types.Balance)
for currency, balance := range trader.Context.Balances {
quota := balance
quota.Available *= maxExposure
trader.Context.Quota[ currency ] = quota
}
done := make(chan struct{})
defer close(done)
if err := strategy.Init(trader.Context, trader); err != nil {
return nil, err
}
standardStream := types.StandardPrivateStream{}
if err := strategy.OnNewStream(&standardStream); err != nil {
return nil, err
}
var tradeID int64 = 0
for _, kline := range trader.SourceKLines {
log.Debugf("kline %+v", kline)
fmt.Print(".")
standardStream.EmitKLineClosed(&kline)
for _, order := range trader.pendingOrders {
switch order.Side {
case types.SideTypeBuy:
fmt.Print("B")
case types.SideTypeSell:
fmt.Print("S")
}
var price float64
if order.Type == types.OrderTypeLimit {
price = util.MustParseFloat(order.Price)
} else {
price = kline.GetClose()
}
volume := util.MustParseFloat(order.Quantity)
fee := 0.0
feeCurrency := ""
trader.Context.Lock()
if order.Side == types.SideTypeBuy {
fee = price * volume * 0.001
feeCurrency = "USDT"
quote := trader.Context.Balances[trader.Context.Market.QuoteCurrency]
if quote.Available < volume*price {
log.Fatalf("quote balance not enough: %+v", quote)
}
quote.Available -= volume * price
trader.Context.Balances[trader.Context.Market.QuoteCurrency] = quote
base := trader.Context.Balances[trader.Context.Market.BaseCurrency]
base.Available += volume
trader.Context.Balances[trader.Context.Market.BaseCurrency] = base
} else {
fee = volume * 0.001
feeCurrency = "BTC"
base := trader.Context.Balances[trader.Context.Market.BaseCurrency]
if base.Available < volume {
log.Fatalf("base balance not enough: %+v", base)
}
base.Available -= volume
trader.Context.Balances[trader.Context.Market.BaseCurrency] = base
quote := trader.Context.Balances[trader.Context.Market.QuoteCurrency]
quote.Available += volume * price
trader.Context.Balances[trader.Context.Market.QuoteCurrency] = quote
}
trader.Context.Unlock()
trade := types.Trade{
ID: tradeID,
Price: price,
Quantity: volume,
Side: string(order.Side),
IsBuyer: order.Side == types.SideTypeBuy,
IsMaker: false,
Time: time.Unix(0, kline.EndTime*int64(time.Millisecond)),
Symbol: trader.Context.Symbol,
Fee: fee,
FeeCurrency: feeCurrency,
}
tradeID++
trader.ProfitAndLossCalculator.AddTrade(trade)
trader.doneOrders = append(trader.doneOrders, order)
}
// clear pending orders
trader.pendingOrders = nil
}
fmt.Print("\n")
report := trader.ProfitAndLossCalculator.Calculate()
report.Print()
log.Infof("wallet balance:")
for _, balance := range trader.Context.Balances {
log.Infof(" %s: %f", balance.Currency, balance.Available)
}
return done, nil
}
type Trader struct { type Trader struct {
Notifier *SlackNotifier Notifier *SlackNotifier
@ -191,6 +57,10 @@ func (trader *Trader) RunStrategy(ctx context.Context, strategy Strategy) (chan
return nil, err return nil, err
} }
// bind kline store to the stream
klineStore := NewKLineStore()
klineStore.BindPrivateStream(&stream.StandardPrivateStream)
if err := strategy.OnNewStream(&stream.StandardPrivateStream); err != nil { if err := strategy.OnNewStream(&stream.StandardPrivateStream); err != nil {
return nil, err return nil, err
} }
@ -204,13 +74,13 @@ func (trader *Trader) RunStrategy(ctx context.Context, strategy Strategy) (chan
return return
} }
if err := trader.TradeService.Insert(*trade) ; err != nil { if err := trader.TradeService.Insert(*trade); err != nil {
log.WithError(err).Error("trade insert error") log.WithError(err).Error("trade insert error")
} }
trader.ReportTrade(trade) trader.ReportTrade(trade)
trader.ProfitAndLossCalculator.AddTrade(*trade) trader.ProfitAndLossCalculator.AddTrade(*trade)
_ , err := trader.Context.StockManager.AddTrades([]types.Trade{*trade}) _, err := trader.Context.StockManager.AddTrades([]types.Trade{*trade})
if err != nil { if err != nil {
log.WithError(err).Error("stock manager load trades error") log.WithError(err).Error("stock manager load trades error")
} }