From 9fbe2e859e820e5ad5824cae32bc6ad78f7d3d40 Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 4 Jul 2022 11:58:11 +0800 Subject: [PATCH] update strategy dev doc --- doc/topics/developing-strategy.md | 153 +++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 1 deletion(-) diff --git a/doc/topics/developing-strategy.md b/doc/topics/developing-strategy.md index c28f590a1..a61696e23 100644 --- a/doc/topics/developing-strategy.md +++ b/doc/topics/developing-strategy.md @@ -12,6 +12,89 @@ it. In general, strategies are Go struct, placed in Go package. +## Quick Start + +To add your first strategy, the fastest way is to add the built-in strategy. + +Simply edit `pkg/cmd/builtin.go` and import your strategy package there. + +When BBGO starts, the strategy will be imported as a package, and register its struct to the engine. + +You can also create a new file called `pkg/cmd/builtin_short.go` and import your strategy package. + +``` +import ( + _ "github.com/c9s/bbgo/pkg/strategy/short" +) +``` + +Create a directory for your new strategy in the BBGO source code repository: + +```shell +mkdir -p pkg/strategy/short +``` + +Open a new file at `pkg/strategy/short/strategy.go` and paste the simplest strategy code: + +``` +package short + +import ( + "context" + "fmt" + + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/types" +) + +const ID = "short" + +func init() { + // Register our struct type to BBGO + // Note that you don't need to field the fields. + // BBGO uses reflect to parse your type information. + bbgo.RegisterStrategy(ID, &Strategy{}) +} + +type Strategy struct { + Symbol string `json:"symbol"` + Interval types.Interval `json:"interval"` +} + +func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) +} + +func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + session.MarketDataStream.OnKLineClosed(func(k types.KLine) { + fmt.Println(k) + }) + return nil +} +``` + +This is the most simple strategy with only ~30 lines code, it subscribes to the kline channel with the given symbol from the config, +And when the kline is closed, it prints the kline to the console. + +Note that, when Run() is executed, the user data stream is not connected to the exchange yet, but the history market data is already loaded, +so if you need to submit an order on start, be sure to write your order submit code inside the event closures like `OnKLineClosed` or `OnStart`. + +Now you can prepare your config file, create a file called `bbgo.yaml` with the following content: + +```yaml +exchangeStrategies: +- on: binance + short: + symbol: ETHUSDT + interval: 1m +``` + +And then, you should be able to run this strategy by running the following command: + +```shell +go run ./cmd/bbgo run +``` + ## The Strategy Struct BBGO loads the YAML config file and re-unmarshal the settings into your struct as JSON string, so you can define the @@ -68,8 +151,76 @@ Note that you don't need to fill the fields in the struct, BBGO just need to kno (BBGO use reflect to parse the fields from the given struct and allocate a new struct object from the given struct type internally) +## Market Data Stream and User Data Stream -## Built-in Strategy +When BBGO connects to the exchange, it allocates two stream objects for different purposes. + +They are: + +- MarketDataStream receives market data from the exchange, for example, KLine data (candlestick, or bars), market public trades. +- UserDataStream receives your personal trading data, for example, orders, executed trades, balance updates and other private information. + +To add your market data subscription to the `MarketDataStream`, you can register your subscription in the `Subscribe` of the strategy code, +for example: + +``` +func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"}) +} +``` + +Since the back-test engine is a kline-based engine, to subscribe market trades, you need to check if you're in the back-test environment: + +``` +func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { + if !bbgo.IsBackTesting { + session.Subscribe(types.MarketTradeChannel, s.Symbol, types.SubscribeOptions{}) + } +} +``` + +To receive the market data from the market data stream, you need to register the event callback: + +``` +func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { + // handle closed kline event here + }) + session.MarketDataStream.OnMarketTrade(func(trade types.Trade) { + // handle market trade event here + }) +} +``` + +In the above example, we register our event callback to the market data stream of the current exchange session, The +market data stream object here is a session-wide market data stream, so it's shared with other strategies that are also +using the same exchange session, you might receive kline with different symbol or interval. + +so it's better to add a condition to filter the kline events: + +``` +func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { + if kline.Symbol != s.Symbol || kline.Interval != s.Interval { + return + } + + // handle your kline here + }) +} +``` + +You can also use the KLineWith method to wrap your kline closure with the filter condition: + +``` +func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + session.MarketDataStream.OnKLineClosed(types.KLineWith("BTCUSDT", types.Interval1m, func(kline types.KLine) { + // handle your kline here + }) +} +``` + +Note that, when the Run() method is executed, the user data stream and market data stream are not connected yet.