mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 00:35:15 +00:00
skeleton: add detailed comment to the skeleton
This commit is contained in:
parent
7398afbde7
commit
b9cbb9d478
|
@ -75,7 +75,7 @@ func (inc *ATR) Length() int {
|
||||||
|
|
||||||
var _ types.Series = &ATR{}
|
var _ types.Series = &ATR{}
|
||||||
|
|
||||||
func (inc *ATR) calculateAndUpdate(kLines []types.KLine) {
|
func (inc *ATR) CalculateAndUpdate(kLines []types.KLine) {
|
||||||
for _, k := range kLines {
|
for _, k := range kLines {
|
||||||
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
|
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
|
||||||
continue
|
continue
|
||||||
|
@ -92,7 +92,7 @@ func (inc *ATR) handleKLineWindowUpdate(interval types.Interval, window types.KL
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
inc.calculateAndUpdate(window)
|
inc.CalculateAndUpdate(window)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inc *ATR) Bind(updater KLineWindowUpdater) {
|
func (inc *ATR) Bind(updater KLineWindowUpdater) {
|
||||||
|
|
|
@ -61,7 +61,7 @@ func Test_calculateATR(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
atr := &ATR{IntervalWindow: types.IntervalWindow{Window: tt.window}}
|
atr := &ATR{IntervalWindow: types.IntervalWindow{Window: tt.window}}
|
||||||
atr.calculateAndUpdate(tt.kLines)
|
atr.CalculateAndUpdate(tt.kLines)
|
||||||
got := atr.Last()
|
got := atr.Last()
|
||||||
diff := math.Trunc((got-tt.want)*100) / 100
|
diff := math.Trunc((got-tt.want)*100) / 100
|
||||||
if diff != 0 {
|
if diff != 0 {
|
||||||
|
|
|
@ -2,11 +2,13 @@ package skeleton
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/bbgo"
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/indicator"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,42 +16,129 @@ const ID = "skeleton"
|
||||||
|
|
||||||
var log = logrus.WithField("strategy", ID)
|
var log = logrus.WithField("strategy", ID)
|
||||||
|
|
||||||
|
var Ten = fixedpoint.NewFromInt(10)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
bbgo.RegisterStrategy(ID, &Strategy{})
|
bbgo.RegisterStrategy(ID, &Strategy{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type Strategy struct {
|
// State is a struct contains the information that we want to keep in the persistence layer,
|
||||||
Symbol string `json:"symbol"`
|
// for example, redis or json file.
|
||||||
|
type State struct {
|
||||||
|
Counter int `json:"counter,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strategy is a struct that contains the settings of your strategy.
|
||||||
|
// These settings will be loaded from the BBGO YAML config file "bbgo.yaml" automatically.
|
||||||
|
type Strategy struct {
|
||||||
|
Symbol string `json:"symbol"`
|
||||||
|
|
||||||
|
// State is a state of your strategy
|
||||||
|
// When BBGO shuts down, everything in the memory will be dropped
|
||||||
|
// If you need to store something and restore this information back,
|
||||||
|
// Simply define the "persistence" tag
|
||||||
|
State *State `persistence:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID should return the identity of this strategy
|
||||||
func (s *Strategy) ID() string {
|
func (s *Strategy) ID() string {
|
||||||
return ID
|
return ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InstanceID returns the identity of the current instance of this strategy.
|
||||||
|
// You may have multiple instance of a strategy, with different symbols and settings.
|
||||||
|
// This value will be used for persistence layer to separate the storage.
|
||||||
|
//
|
||||||
|
// Run:
|
||||||
|
// redis-cli KEYS "*"
|
||||||
|
//
|
||||||
|
// And you will see how this instance ID is used in redis.
|
||||||
|
func (s *Strategy) InstanceID() string {
|
||||||
|
return ID + ":" + s.Symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe method subscribes specific market data from the given session.
|
||||||
|
// Before BBGO is connected to the exchange, we need to collect what we want to subscribe.
|
||||||
|
// Here the strategy needs kline data, so it adds the kline subscription.
|
||||||
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
log.Infof("subscribe %s", s.Symbol)
|
// We want 1m kline data of the symbol
|
||||||
|
// It will be BTCUSDT 1m if our s.Symbol is BTCUSDT
|
||||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"})
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"})
|
||||||
}
|
}
|
||||||
|
|
||||||
var Ten = fixedpoint.NewFromInt(10)
|
|
||||||
|
|
||||||
// This strategy simply spent all available quote currency to buy the symbol whenever kline gets closed
|
// This strategy simply spent all available quote currency to buy the symbol whenever kline gets closed
|
||||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||||
|
// Initialize the default value for state
|
||||||
|
if s.State == nil {
|
||||||
|
s.State = &State{Counter: 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: You can get the market data store from session
|
||||||
|
store, ok := session.MarketDataStore(s.Symbol)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("market data store %s not found", s.Symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a custom indicator
|
||||||
|
atr := &indicator.ATR{
|
||||||
|
IntervalWindow: types.IntervalWindow{
|
||||||
|
Interval: types.Interval1m,
|
||||||
|
Window: 14,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind the indicator to the market data store, so that when a new kline is received,
|
||||||
|
// the indicator will be updated.
|
||||||
|
atr.Bind(store)
|
||||||
|
|
||||||
|
// To get the past kline history, call KLinesOfInterval from the market data store
|
||||||
|
klines, ok := store.KLinesOfInterval(types.Interval1m)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("market data store %s lkline not found", s.Symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the history data to initialize the indicator
|
||||||
|
atr.CalculateAndUpdate(*klines)
|
||||||
|
|
||||||
|
// To get the market information from the current session
|
||||||
|
// The market object provides the precision, MoQ (minimal of quantity) information
|
||||||
market, ok := session.Market(s.Symbol)
|
market, ok := session.Market(s.Symbol)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Warnf("fetch market fail %s", s.Symbol)
|
return fmt.Errorf("market %s not found", s.Symbol)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// here we define a kline callback
|
||||||
|
// when a kline is closed, we will do something
|
||||||
callback := func(kline types.KLine) {
|
callback := func(kline types.KLine) {
|
||||||
|
// get the latest ATR value from the indicator object that we just defined.
|
||||||
|
atrValue := atr.Last()
|
||||||
|
log.Infof("atr %f", atrValue)
|
||||||
|
|
||||||
|
// Update our counter and sync the changes to the persistence layer on time
|
||||||
|
// If you don't do this, BBGO will sync it automatically when BBGO shuts down.
|
||||||
|
s.State.Counter++
|
||||||
|
bbgo.Sync(s)
|
||||||
|
|
||||||
|
// To check if we have the quote balance
|
||||||
|
// When symbol = "BTCUSDT", the quote currency is USDT
|
||||||
|
// We can get this information from the market object
|
||||||
quoteBalance, ok := session.GetAccount().Balance(market.QuoteCurrency)
|
quoteBalance, ok := session.GetAccount().Balance(market.QuoteCurrency)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
// if not ok, it means we don't have this currency in the account
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For each balance, we have Available and Locked balance.
|
||||||
|
// balance.Available is the balance you can use to place an order.
|
||||||
|
// Note that the available balance is a fixed-point object, so you can not compare it with integer directly.
|
||||||
|
// Instead, you should call valueA.Compare(valueB)
|
||||||
quantityAmount := quoteBalance.Available
|
quantityAmount := quoteBalance.Available
|
||||||
if quantityAmount.Sign() <= 0 || quantityAmount.Compare(Ten) < 0 {
|
if quantityAmount.Sign() <= 0 || quantityAmount.Compare(Ten) < 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call LastPrice(symbol) If you need to get the latest price
|
||||||
|
// Note this last price is updated by the closed kline
|
||||||
currentPrice, ok := session.LastPrice(s.Symbol)
|
currentPrice, ok := session.LastPrice(s.Symbol)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
|
@ -57,7 +146,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
|
|
||||||
totalQuantity := quantityAmount.Div(currentPrice)
|
totalQuantity := quantityAmount.Div(currentPrice)
|
||||||
|
|
||||||
_, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
// Place a market order to the exchange
|
||||||
|
createdOrders, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||||
Symbol: kline.Symbol,
|
Symbol: kline.Symbol,
|
||||||
Side: types.SideTypeBuy,
|
Side: types.SideTypeBuy,
|
||||||
Type: types.OrderTypeMarket,
|
Type: types.OrderTypeMarket,
|
||||||
|
@ -68,12 +158,18 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("submit order error")
|
log.WithError(err).Error("submit order error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("createdOrders: %+v", createdOrders)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// register our kline event handler
|
||||||
|
session.MarketDataStream.OnKLineClosed(callback)
|
||||||
|
|
||||||
|
// if you need to do something when the user data stream is ready
|
||||||
|
// note that you only receive order update, trade update, balance update when the user data stream is connect.
|
||||||
session.UserDataStream.OnStart(func() {
|
session.UserDataStream.OnStart(func() {
|
||||||
log.Infof("connected")
|
log.Infof("connected")
|
||||||
})
|
})
|
||||||
|
|
||||||
session.MarketDataStream.OnKLineClosed(callback)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user