mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 06:53:52 +00:00
feature: add multiple exchange support in backtest
fix: change doc, since --exchange removed from backtest fix: test for config changes
This commit is contained in:
parent
d01fcb304d
commit
25b5eddc03
|
@ -52,11 +52,12 @@ backtest:
|
|||
symbols:
|
||||
- BTCUSDT
|
||||
account:
|
||||
makerCommission: 15
|
||||
takerCommission: 15
|
||||
balances:
|
||||
BTC: 0.0
|
||||
USDT: 10000.0
|
||||
max:
|
||||
makerCommission: 15
|
||||
takerCommission: 15
|
||||
balances:
|
||||
BTC: 0.0
|
||||
USDT: 10000.0
|
||||
|
||||
exchangeStrategies:
|
||||
- on: max
|
||||
|
|
|
@ -5,8 +5,13 @@ persistence:
|
|||
port: 6379
|
||||
db: 0
|
||||
|
||||
sessions:
|
||||
binance:
|
||||
exchange: binance
|
||||
envVarPrefix: BINANCE
|
||||
|
||||
# example command:
|
||||
# godotenv -f .env.local -- go run ./cmd/bbgo backtest --exchange max --sync-from 2020-11-01 --config config/grid.yaml --base-asset-baseline
|
||||
# godotenv -f .env.local -- go run ./cmd/bbgo backtest --sync-from 2020-11-01 --config config/grid.yaml --base-asset-baseline
|
||||
backtest:
|
||||
# for testing max draw down (MDD) at 03-12
|
||||
# see here for more details
|
||||
|
@ -16,9 +21,10 @@ backtest:
|
|||
symbols:
|
||||
- ETHUSDT
|
||||
account:
|
||||
balances:
|
||||
ETH: 1.0
|
||||
USDT: 20_000.0
|
||||
binance:
|
||||
balances:
|
||||
ETH: 1.0
|
||||
USDT: 20_000.0
|
||||
|
||||
exchangeStrategies:
|
||||
|
||||
|
|
|
@ -30,12 +30,13 @@ backtest:
|
|||
symbols:
|
||||
- USDTTWD
|
||||
account:
|
||||
makerCommission: 15
|
||||
takerCommission: 15
|
||||
balances:
|
||||
BTC: 0.0
|
||||
USDT: 10_000.0
|
||||
TWD: 100_000.0
|
||||
max:
|
||||
makerCommission: 15
|
||||
takerCommission: 15
|
||||
balances:
|
||||
BTC: 0.0
|
||||
USDT: 10_000.0
|
||||
TWD: 100_000.0
|
||||
|
||||
exchangeStrategies:
|
||||
- on: max
|
||||
|
|
|
@ -4,9 +4,9 @@ sessions:
|
|||
exchange: binance
|
||||
envVarPrefix: binance
|
||||
|
||||
max:
|
||||
exchange: max
|
||||
envVarPrefix: max
|
||||
#max:
|
||||
# exchange: max
|
||||
# envVarPrefix: max
|
||||
|
||||
riskControls:
|
||||
# This is the session-based risk controller, which let you configure different risk controller by session.
|
||||
|
@ -26,23 +26,24 @@ riskControls:
|
|||
maxOrderAmount: 1000.0
|
||||
|
||||
# example command:
|
||||
# godotenv -f .env.local -- go run ./cmd/bbgo backtest --exchange max --sync-from 2020-11-01 --config config/grid.yaml --base-asset-baseline
|
||||
# godotenv -f .env.local -- go run ./cmd/bbgo backtest --sync-from 2020-11-01 --config config/grid.yaml --base-asset-baseline
|
||||
backtest:
|
||||
# for testing max draw down (MDD) at 03-12
|
||||
# see here for more details
|
||||
# https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
|
||||
startTime: "2021-01-10"
|
||||
endTime: "2021-01-21"
|
||||
startTime: "2022-01-10"
|
||||
endTime: "2022-01-11"
|
||||
symbols:
|
||||
- BTCUSDT
|
||||
- BTCUSDT
|
||||
account:
|
||||
balances:
|
||||
BTC: 0.0
|
||||
USDT: 10000.0
|
||||
binance:
|
||||
balances:
|
||||
BTC: 0.0
|
||||
USDT: 10000.0
|
||||
|
||||
exchangeStrategies:
|
||||
|
||||
- on: max
|
||||
- on: binance
|
||||
grid:
|
||||
symbol: BTCUSDT
|
||||
quantity: 0.001
|
||||
|
|
|
@ -40,18 +40,19 @@ backtest:
|
|||
# for testing max draw down (MDD) at 03-12
|
||||
# see here for more details
|
||||
# https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
|
||||
startTime: "2020-01-01"
|
||||
endTime: "2020-01-15"
|
||||
startTime: "2022-01-01"
|
||||
endTime: "2022-01-15"
|
||||
symbols:
|
||||
- BTCUSDT
|
||||
account:
|
||||
makerCommission: 15
|
||||
takerCommission: 15
|
||||
buyerCommission: 0
|
||||
sellerCommission: 0
|
||||
balances:
|
||||
BTC: 0.1
|
||||
USDT: 10000.0
|
||||
binance:
|
||||
makerCommission: 15
|
||||
takerCommission: 15
|
||||
buyerCommission: 0
|
||||
sellerCommission: 0
|
||||
balances:
|
||||
BTC: 0.1
|
||||
USDT: 10000.0
|
||||
|
||||
exchangeStrategies:
|
||||
- on: binance
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
---
|
||||
# time godotenv -f .env.local -- go run ./cmd/bbgo backtest --exchange binance --base-asset-baseline --config config/schedule-ethusdt.yaml -v
|
||||
|
||||
sessions:
|
||||
binance:
|
||||
exchange: binance
|
||||
envVarPrefix: binance
|
||||
|
||||
# time godotenv -f .env.local -- go run ./cmd/bbgo backtest --base-asset-baseline --config config/schedule-ethusdt.yaml -v
|
||||
backtest:
|
||||
startTime: "2021-08-01"
|
||||
endTime: "2021-08-07"
|
||||
symbols:
|
||||
- ETHUSDT
|
||||
account:
|
||||
balances:
|
||||
ETH: 1.0
|
||||
USDT: 20_000.0
|
||||
binance:
|
||||
balances:
|
||||
ETH: 1.0
|
||||
USDT: 20_000.0
|
||||
|
||||
riskControls:
|
||||
# This is the session-based risk controller, which let you configure different risk controller by session.
|
||||
|
|
|
@ -16,6 +16,7 @@ backtest:
|
|||
symbols:
|
||||
- BNBBUSD
|
||||
account:
|
||||
balances:
|
||||
BNB: 0
|
||||
BUSD: 10000
|
||||
binance:
|
||||
balances:
|
||||
BNB: 0
|
||||
BUSD: 10000
|
||||
|
|
|
@ -44,14 +44,14 @@ backtest:
|
|||
startTime: "2020-09-04"
|
||||
endTime: "2020-09-14"
|
||||
symbols:
|
||||
- BTCUSDT
|
||||
- ETHUSDT
|
||||
- LINKUSDT
|
||||
account:
|
||||
makerCommission: 15
|
||||
takerCommission: 15
|
||||
balances:
|
||||
BTC: 0.0
|
||||
USDT: 10000.0
|
||||
binance:
|
||||
makerCommission: 15
|
||||
takerCommission: 15
|
||||
balances:
|
||||
LINK: 0.0
|
||||
USDT: 10000.0
|
||||
|
||||
exchangeStrategies:
|
||||
|
||||
|
@ -59,14 +59,14 @@ exchangeStrategies:
|
|||
support:
|
||||
symbol: LINKUSDT
|
||||
interval: 1m
|
||||
minVolume: 1_000
|
||||
minVolume: 2_000
|
||||
marginOrderSideEffect: borrow
|
||||
|
||||
scaleQuantity:
|
||||
byVolume:
|
||||
exp:
|
||||
domain: [ 1_000, 200_000 ]
|
||||
range: [ 0.5, 1.0 ]
|
||||
range: [ 3.0, 5.0 ]
|
||||
|
||||
maxBaseAssetBalance: 1000.0
|
||||
minQuoteAssetBalance: 2000.0
|
||||
|
|
|
@ -44,9 +44,10 @@ backtest:
|
|||
- BTCUSDT
|
||||
- ETHUSDT
|
||||
account:
|
||||
balances:
|
||||
BTC: 0.0
|
||||
USDT: 10000.0
|
||||
binance:
|
||||
balances:
|
||||
BTC: 0.0
|
||||
USDT: 10000.0
|
||||
|
||||
exchangeStrategies:
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ bbgo [flags]
|
|||
* [bbgo orderupdate](bbgo_orderupdate.md) - Listen to order update events
|
||||
* [bbgo pnl](bbgo_pnl.md) - pnl calculator
|
||||
* [bbgo run](bbgo_run.md) - run strategies from config file
|
||||
* [bbgo submit-order](bbgo_submit-order.md) - place limit order to the exchange
|
||||
* [bbgo submit-order](bbgo_submit-order.md) - place order to the exchange
|
||||
* [bbgo sync](bbgo_sync.md) - sync trades and orders history
|
||||
* [bbgo trades](bbgo_trades.md) - Query trading history
|
||||
* [bbgo tradeupdate](bbgo_tradeupdate.md) - Listen to trade update events
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
show user account details (ex: balance)
|
||||
|
||||
```
|
||||
bbgo account [--session=[exchange_name]] [flags]
|
||||
bbgo account [--session SESSION] [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
|
|
@ -10,7 +10,6 @@ bbgo backtest [flags]
|
|||
|
||||
```
|
||||
--base-asset-baseline use base asset performance as the competitive baseline performance
|
||||
--exchange string target exchange
|
||||
--force force execution without confirm
|
||||
-h, --help help for backtest
|
||||
--output string the report output directory
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Show user account balances
|
||||
|
||||
```
|
||||
bbgo balances --session SESSION [flags]
|
||||
bbgo balances [--session SESSION] [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
## bbgo submit-order
|
||||
|
||||
place limit order to the exchange
|
||||
place order to the exchange
|
||||
|
||||
```
|
||||
bbgo submit-order --session SESSION --symbol SYMBOL --side SIDE --quantity QUANTITY [--price PRICE] [flags]
|
||||
|
|
|
@ -20,9 +20,10 @@ backtest:
|
|||
|
||||
account:
|
||||
# the initial account balance you want to start with
|
||||
balances:
|
||||
BTC: 0.0
|
||||
USDT: 10000.0
|
||||
binance: # exchange name
|
||||
balances:
|
||||
BTC: 0.0
|
||||
USDT: 10000.0
|
||||
```
|
||||
|
||||
Note on date formats, the following date formats are supported:
|
||||
|
@ -33,7 +34,7 @@ Note on date formats, the following date formats are supported:
|
|||
And then, you can sync remote exchange k-lines (candle bars) data for back-testing:
|
||||
|
||||
```sh
|
||||
bbgo backtest --exchange binance -v --sync --sync-only --sync-from 2020-11-01 --config config/grid.yaml
|
||||
bbgo backtest -v --sync --sync-only --sync-from 2020-11-01 --config config/grid.yaml
|
||||
```
|
||||
|
||||
Note that, you should sync from an earlier date before your startTime because some indicator like EMA needs more data to calculate the current EMA value.
|
||||
|
@ -48,13 +49,13 @@ Here we sync one month before `2021-01-10`.
|
|||
Run back-test:
|
||||
|
||||
```sh
|
||||
bbgo backtest --exchange binance --base-asset-baseline --config config/grid.yaml
|
||||
bbgo backtest --base-asset-baseline --config config/grid.yaml
|
||||
```
|
||||
|
||||
If you're developing a strategy, you might want to start with a command like this:
|
||||
|
||||
```shell
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo backtest --exchange binance --config config/grid.yaml --base-asset-baseline
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo backtest --config config/grid.yaml --base-asset-baseline
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
|
|
@ -83,13 +83,15 @@ func NewExchange(sourceName types.ExchangeName, sourceExchange types.Exchange, s
|
|||
endTime = time.Now()
|
||||
}
|
||||
|
||||
configAccount := config.Account[sourceName.String()]
|
||||
|
||||
account := &types.Account{
|
||||
MakerFeeRate: config.Account.MakerFeeRate,
|
||||
TakerFeeRate: config.Account.TakerFeeRate,
|
||||
MakerFeeRate: configAccount.MakerFeeRate,
|
||||
TakerFeeRate: configAccount.TakerFeeRate,
|
||||
AccountType: types.AccountTypeSpot,
|
||||
}
|
||||
|
||||
balances := config.Account.Balances.BalanceMap()
|
||||
balances := configAccount.Balances.BalanceMap()
|
||||
account.UpdateBalances(balances)
|
||||
|
||||
e := &Exchange{
|
||||
|
@ -288,7 +290,7 @@ func (e *Exchange) matchingBook(symbol string) (*SimplePriceMatching, bool) {
|
|||
return m, ok
|
||||
}
|
||||
|
||||
func (e *Exchange) FeedMarketData() error {
|
||||
func (e *Exchange) InitMarketData() {
|
||||
e.userDataStream.OnTradeUpdate(func(trade types.Trade) {
|
||||
e.addTrade(trade)
|
||||
})
|
||||
|
@ -301,7 +303,9 @@ func (e *Exchange) FeedMarketData() error {
|
|||
}
|
||||
e.matchingBooksMutex.Unlock()
|
||||
|
||||
marketDataStream := e.marketDataStream
|
||||
}
|
||||
|
||||
func (e *Exchange) GetMarketData() (chan types.KLine, error) {
|
||||
log.Infof("collecting backtest configurations...")
|
||||
|
||||
loadedSymbols := map[string]struct{}{}
|
||||
|
@ -310,8 +314,7 @@ func (e *Exchange) FeedMarketData() error {
|
|||
types.Interval1m: {},
|
||||
types.Interval1d: {},
|
||||
}
|
||||
|
||||
for _, sub := range marketDataStream.Subscriptions {
|
||||
for _, sub := range e.marketDataStream.Subscriptions {
|
||||
loadedSymbols[sub.Symbol] = struct{}{}
|
||||
|
||||
switch sub.Channel {
|
||||
|
@ -319,7 +322,7 @@ func (e *Exchange) FeedMarketData() error {
|
|||
loadedIntervals[types.Interval(sub.Options.Interval)] = struct{}{}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("stream channel %s is not supported in backtest", sub.Channel)
|
||||
return nil, fmt.Errorf("stream channel %s is not supported in backtest", sub.Channel)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -336,35 +339,33 @@ func (e *Exchange) FeedMarketData() error {
|
|||
log.Infof("using symbols: %v and intervals: %v for back-testing", symbols, intervals)
|
||||
log.Infof("querying klines from database...")
|
||||
klineC, errC := e.srv.QueryKLinesCh(e.startTime, e.endTime, e, symbols, intervals)
|
||||
numKlines := 0
|
||||
for k := range klineC {
|
||||
if k.Interval == types.Interval1m {
|
||||
matching, ok := e.matchingBook(k.Symbol)
|
||||
if !ok {
|
||||
log.Errorf("matching book of %s is not initialized", k.Symbol)
|
||||
continue
|
||||
}
|
||||
go func() {
|
||||
if err := <-errC; err != nil {
|
||||
log.WithError(err).Error("backtest data feed error")
|
||||
}
|
||||
}()
|
||||
return klineC, nil
|
||||
}
|
||||
|
||||
// here we generate trades and order updates
|
||||
matching.processKLine(k)
|
||||
numKlines++
|
||||
func (e *Exchange) ConsumeKLine(k types.KLine) {
|
||||
if k.Interval == types.Interval1m {
|
||||
matching, ok := e.matchingBook(k.Symbol)
|
||||
if !ok {
|
||||
log.Errorf("matching book of %s is not initialized", k.Symbol)
|
||||
return
|
||||
}
|
||||
|
||||
marketDataStream.EmitKLineClosed(k)
|
||||
// here we generate trades and order updates
|
||||
matching.processKLine(k)
|
||||
}
|
||||
|
||||
if err := <-errC; err != nil {
|
||||
log.WithError(err).Error("backtest data feed error")
|
||||
}
|
||||
e.marketDataStream.EmitKLineClosed(k)
|
||||
}
|
||||
|
||||
if numKlines == 0 {
|
||||
log.Error("kline data is empty, make sure you have sync the exchange market data")
|
||||
}
|
||||
|
||||
if err := marketDataStream.Close(); err != nil {
|
||||
func (e *Exchange) CloseMarketData() error {
|
||||
if err := e.marketDataStream.Close(); err != nil {
|
||||
log.WithError(err).Error("stream close error")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -98,10 +98,10 @@ type Backtest struct {
|
|||
EndTime *types.LooseFormatTime `json:"endTime,omitempty" yaml:"endTime,omitempty"`
|
||||
|
||||
// RecordTrades is an option, if set to true, back-testing should record the trades into database
|
||||
RecordTrades bool `json:"recordTrades,omitempty" yaml:"recordTrades,omitempty"`
|
||||
Account BacktestAccount `json:"account" yaml:"account"`
|
||||
Symbols []string `json:"symbols" yaml:"symbols"`
|
||||
Session string `json:"session" yaml:"session"`
|
||||
RecordTrades bool `json:"recordTrades,omitempty" yaml:"recordTrades,omitempty"`
|
||||
Account map[string]BacktestAccount `json:"account" yaml:"account"`
|
||||
Symbols []string `json:"symbols" yaml:"symbols"`
|
||||
Sessions []string `json:"sessions" yaml:"sessions"`
|
||||
}
|
||||
|
||||
type BacktestAccount struct {
|
||||
|
|
|
@ -188,8 +188,8 @@ func TestLoadConfig(t *testing.T) {
|
|||
assert.Len(t, config.ExchangeStrategies, 1)
|
||||
assert.NotNil(t, config.Backtest)
|
||||
assert.NotNil(t, config.Backtest.Account)
|
||||
assert.NotNil(t, config.Backtest.Account.Balances)
|
||||
assert.Len(t, config.Backtest.Account.Balances, 2)
|
||||
assert.NotNil(t, config.Backtest.Account["binance"].Balances)
|
||||
assert.Len(t, config.Backtest.Account["binance"].Balances, 2)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
15
pkg/bbgo/testdata/backtest.yaml
vendored
15
pkg/bbgo/testdata/backtest.yaml
vendored
|
@ -14,13 +14,14 @@ backtest:
|
|||
# https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
|
||||
startTime: "2020-01-01"
|
||||
account:
|
||||
makerCommission: 15
|
||||
takerCommission: 15
|
||||
buyerCommission: 0
|
||||
sellerCommission: 0
|
||||
balances:
|
||||
BTC: 1.0
|
||||
USDT: 5000.0
|
||||
binance:
|
||||
makerCommission: 15
|
||||
takerCommission: 15
|
||||
buyerCommission: 0
|
||||
sellerCommission: 0
|
||||
balances:
|
||||
BTC: 1.0
|
||||
USDT: 5000.0
|
||||
|
||||
|
||||
exchangeStrategies:
|
||||
|
|
|
@ -36,7 +36,6 @@ type BackTestReport struct {
|
|||
}
|
||||
|
||||
func init() {
|
||||
BacktestCmd.Flags().String("exchange", "", "target exchange")
|
||||
BacktestCmd.Flags().Bool("sync", false, "sync backtest data")
|
||||
BacktestCmd.Flags().Bool("sync-only", false, "sync backtest data only, do not run backtest")
|
||||
BacktestCmd.Flags().String("sync-from", "", "sync backtest data from the given time, which will override the time range in the backtest config")
|
||||
|
@ -54,6 +53,9 @@ var BacktestCmd = &cobra.Command{
|
|||
Use: "backtest",
|
||||
Short: "backtest your strategies",
|
||||
SilenceUsage: true,
|
||||
PreRunE: cobraInitRequired([]string{
|
||||
"config",
|
||||
}),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
verboseCnt, err := cmd.Flags().GetCount("verbose")
|
||||
if err != nil {
|
||||
|
@ -110,11 +112,6 @@ var BacktestCmd = &cobra.Command{
|
|||
return err
|
||||
}
|
||||
|
||||
exchangeNameStr, err := cmd.Flags().GetString("exchange")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userConfig, err := bbgo.Load(configFile, true)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -129,45 +126,44 @@ var BacktestCmd = &cobra.Command{
|
|||
log.SetLevel(log.ErrorLevel)
|
||||
}
|
||||
|
||||
// if it's declared in the cmd , use the cmd one first
|
||||
if exchangeNameStr == "" {
|
||||
exchangeNameStr = userConfig.Backtest.Session
|
||||
if userConfig.Backtest == nil {
|
||||
return errors.New("backtest config is not defined")
|
||||
}
|
||||
|
||||
var sourceExchange types.Exchange
|
||||
var exchangeName types.ExchangeName
|
||||
acceptAllSessions := false
|
||||
var whitelistedSessions map[string]struct{}
|
||||
if len(userConfig.Backtest.Sessions) == 0 {
|
||||
acceptAllSessions = true
|
||||
} else {
|
||||
for _, name := range userConfig.Backtest.Sessions {
|
||||
_, err := types.ValidExchangeName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
whitelistedSessions[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
sourceExchanges := make(map[string]types.Exchange)
|
||||
|
||||
for key, session := range userConfig.Sessions {
|
||||
if exchangeNameStr == key {
|
||||
ok := acceptAllSessions
|
||||
if !ok {
|
||||
_, ok = whitelistedSessions[key]
|
||||
}
|
||||
if ok {
|
||||
publicExchange, err := cmdutil.NewExchangePublic(session.ExchangeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sourceExchange = publicExchange
|
||||
exchangeName = session.ExchangeName
|
||||
}
|
||||
}
|
||||
|
||||
if sourceExchange == nil {
|
||||
exchangeName, err = types.ValidExchangeName(exchangeNameStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sourceExchange, err = cmdutil.NewExchangePublic(exchangeName)
|
||||
if err != nil {
|
||||
return err
|
||||
sourceExchanges[key] = publicExchange
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
if userConfig.Backtest == nil {
|
||||
return errors.New("backtest config is not defined")
|
||||
}
|
||||
|
||||
var now = time.Now()
|
||||
var startTime, endTime time.Time
|
||||
|
||||
|
@ -183,10 +179,6 @@ var BacktestCmd = &cobra.Command{
|
|||
}
|
||||
_ = endTime
|
||||
|
||||
if len(userConfig.CrossExchangeStrategies) > 0 {
|
||||
log.Warnf("backtest does not support CrossExchangeStrategy, strategies won't be added.")
|
||||
}
|
||||
|
||||
log.Infof("starting backtest with startTime %s", startTime.Format(time.ANSIC))
|
||||
|
||||
environ := bbgo.NewEnvironment()
|
||||
|
@ -223,43 +215,47 @@ var BacktestCmd = &cobra.Command{
|
|||
log.Info("starting synchronization...")
|
||||
for _, symbol := range userConfig.Backtest.Symbols {
|
||||
|
||||
exCustom, ok := sourceExchange.(types.CustomIntervalProvider)
|
||||
for _, sourceExchange := range sourceExchanges {
|
||||
exCustom, ok := sourceExchange.(types.CustomIntervalProvider)
|
||||
|
||||
var supportIntervals map[types.Interval]int
|
||||
if ok {
|
||||
supportIntervals = exCustom.SupportedInterval()
|
||||
} else {
|
||||
supportIntervals = types.SupportedIntervals
|
||||
}
|
||||
|
||||
for interval := range supportIntervals {
|
||||
// if err := s.SyncKLineByInterval(ctx, exchange, symbol, interval, startTime, endTime); err != nil {
|
||||
// return err
|
||||
// }
|
||||
firstKLine, err := backtestService.QueryFirstKLine(sourceExchange.Name(), symbol, interval)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to query backtest kline")
|
||||
var supportIntervals map[types.Interval]int
|
||||
if ok {
|
||||
supportIntervals = exCustom.SupportedInterval()
|
||||
} else {
|
||||
supportIntervals = types.SupportedIntervals
|
||||
}
|
||||
|
||||
// if we don't have klines before the start time endpoint, the back-test will fail.
|
||||
// because the last price will be missing.
|
||||
if firstKLine != nil {
|
||||
if err := backtestService.SyncExist(ctx, sourceExchange, symbol, syncFromTime, time.Now(), interval); err != nil {
|
||||
return err
|
||||
for interval := range supportIntervals {
|
||||
// if err := s.SyncKLineByInterval(ctx, exchange, symbol, interval, startTime, endTime); err != nil {
|
||||
// return err
|
||||
// }
|
||||
firstKLine, err := backtestService.QueryFirstKLine(sourceExchange.Name(), symbol, interval)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to query backtest kline")
|
||||
}
|
||||
} else {
|
||||
if err := backtestService.Sync(ctx, sourceExchange, symbol, syncFromTime, time.Now(), interval); err != nil {
|
||||
return err
|
||||
|
||||
// if we don't have klines before the start time endpoint, the back-test will fail.
|
||||
// because the last price will be missing.
|
||||
if firstKLine != nil {
|
||||
if err := backtestService.SyncExist(ctx, sourceExchange, symbol, syncFromTime, time.Now(), interval); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := backtestService.Sync(ctx, sourceExchange, symbol, syncFromTime, time.Now(), interval); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Info("synchronization done")
|
||||
|
||||
if shouldVerify {
|
||||
err2, done := backtestService.Verify(userConfig.Backtest.Symbols, startTime, time.Now(), sourceExchange, verboseCnt)
|
||||
if done {
|
||||
return err2
|
||||
for _, sourceExchange := range sourceExchanges {
|
||||
if shouldVerify {
|
||||
err2, done := backtestService.Verify(userConfig.Backtest.Symbols, startTime, time.Now(), sourceExchange, verboseCnt)
|
||||
if done {
|
||||
return err2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,19 +279,17 @@ var BacktestCmd = &cobra.Command{
|
|||
}
|
||||
}
|
||||
|
||||
if userConfig.Backtest == nil {
|
||||
return errors.New("backtest config can not be nil")
|
||||
}
|
||||
|
||||
backtestExchange, err := backtest.NewExchange(exchangeName, sourceExchange, backtestService, userConfig.Backtest)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create backtest exchange")
|
||||
}
|
||||
|
||||
environ.SetStartTime(startTime)
|
||||
|
||||
// exchangeNameStr is the session name.
|
||||
environ.AddExchange(exchangeNameStr, backtestExchange)
|
||||
for name, sourceExchange := range sourceExchanges {
|
||||
|
||||
backtestExchange, err := backtest.NewExchange(sourceExchange.Name(), sourceExchange, backtestService, userConfig.Backtest)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create backtest exchange")
|
||||
}
|
||||
environ.AddExchange(name, backtestExchange)
|
||||
}
|
||||
|
||||
if err := environ.Init(ctx); err != nil {
|
||||
return err
|
||||
|
@ -314,7 +308,42 @@ var BacktestCmd = &cobra.Command{
|
|||
return err
|
||||
}
|
||||
|
||||
backtestExchange.FeedMarketData()
|
||||
type KChanEx struct {
|
||||
KChan chan types.KLine
|
||||
Exchange *backtest.Exchange
|
||||
}
|
||||
for _, session := range environ.Sessions() {
|
||||
backtestExchange := session.Exchange.(*backtest.Exchange)
|
||||
backtestExchange.InitMarketData()
|
||||
}
|
||||
|
||||
var klineChans []KChanEx
|
||||
for _, session := range environ.Sessions() {
|
||||
exchange := session.Exchange.(*backtest.Exchange)
|
||||
c, err := exchange.GetMarketData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
klineChans = append(klineChans, KChanEx{KChan: c, Exchange: exchange})
|
||||
}
|
||||
|
||||
for {
|
||||
count := len(klineChans)
|
||||
for _, kchanex := range klineChans {
|
||||
kLine, more := <-kchanex.KChan
|
||||
if more {
|
||||
kchanex.Exchange.ConsumeKLine(kLine)
|
||||
} else {
|
||||
if err := kchanex.Exchange.CloseMarketData(); err != nil {
|
||||
return err
|
||||
}
|
||||
count--
|
||||
}
|
||||
}
|
||||
if count == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("shutting down trader...")
|
||||
shutdownCtx, cancel := context.WithDeadline(ctx, time.Now().Add(10*time.Second))
|
||||
|
@ -324,10 +353,12 @@ var BacktestCmd = &cobra.Command{
|
|||
// put the logger back to print the pnl
|
||||
log.SetLevel(log.InfoLevel)
|
||||
for _, session := range environ.Sessions() {
|
||||
backtestExchange := session.Exchange.(*backtest.Exchange)
|
||||
exchangeName := session.Exchange.Name().String()
|
||||
for symbol, trades := range session.Trades {
|
||||
market, ok := session.Market(symbol)
|
||||
if !ok {
|
||||
return fmt.Errorf("market not found: %s", symbol)
|
||||
return fmt.Errorf("market not found: %s, %s", symbol, exchangeName)
|
||||
}
|
||||
|
||||
calculator := &pnl.AverageCostCalculator{
|
||||
|
@ -337,21 +368,21 @@ var BacktestCmd = &cobra.Command{
|
|||
|
||||
startPrice, ok := session.StartPrice(symbol)
|
||||
if !ok {
|
||||
return fmt.Errorf("start price not found: %s", symbol)
|
||||
return fmt.Errorf("start price not found: %s, %s", symbol, exchangeName)
|
||||
}
|
||||
|
||||
lastPrice, ok := session.LastPrice(symbol)
|
||||
if !ok {
|
||||
return fmt.Errorf("last price not found: %s", symbol)
|
||||
return fmt.Errorf("last price not found: %s, %s", symbol, exchangeName)
|
||||
}
|
||||
|
||||
log.Infof("%s PROFIT AND LOSS REPORT", symbol)
|
||||
log.Infof("===============================================")
|
||||
color.Green("%s %s PROFIT AND LOSS REPORT", strings.ToUpper(exchangeName), symbol)
|
||||
color.Green("===============================================")
|
||||
|
||||
report := calculator.Calculate(symbol, trades.Trades, lastPrice)
|
||||
report.Print()
|
||||
|
||||
initBalances := userConfig.Backtest.Account.Balances.BalanceMap()
|
||||
initBalances := userConfig.Backtest.Account[exchangeName].Balances.BalanceMap()
|
||||
finalBalances := session.Account.Balances()
|
||||
|
||||
log.Infof("INITIAL BALANCES:")
|
||||
|
|
Loading…
Reference in New Issue
Block a user