support json output for backtesting

This commit is contained in:
c9s 2021-12-06 01:05:33 +08:00
parent 1e151a170a
commit 474be4e815
3 changed files with 66 additions and 30 deletions

View File

@ -21,12 +21,13 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
if len(trades) == 0 {
return &AverageCostPnlReport{
Symbol: symbol,
CurrentPrice: currentPrice,
NumTrades: 0,
BuyVolume: bidVolume,
SellVolume: askVolume,
FeeInUSD: feeUSD,
Symbol: symbol,
Market: c.Market,
LastPrice: currentPrice,
NumTrades: 0,
BuyVolume: bidVolume,
SellVolume: askVolume,
FeeInUSD: feeUSD,
}
}
@ -68,10 +69,11 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
unrealizedProfit := (fixedpoint.NewFromFloat(currentPrice) - position.AverageCost).Mul(position.Base)
return &AverageCostPnlReport{
Symbol: symbol,
CurrentPrice: currentPrice,
NumTrades: len(trades),
StartTime: time.Time(trades[0].Time),
Symbol: symbol,
Market: c.Market,
LastPrice: currentPrice,
NumTrades: len(trades),
StartTime: time.Time(trades[0].Time),
BuyVolume: bidVolume,
SellVolume: askVolume,
@ -80,7 +82,7 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
Profit: totalProfit,
NetProfit: totalNetProfit,
UnrealizedProfit: unrealizedProfit,
AverageBidCost: position.AverageCost.Float64(),
AverageCost: position.AverageCost.Float64(),
FeeInUSD: (totalProfit - totalNetProfit).Float64(),
CurrencyFees: currencyFees,
}

View File

@ -3,8 +3,11 @@ package cmd
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
@ -28,6 +31,7 @@ func init() {
BacktestCmd.Flags().CountP("verbose", "v", "verbose level")
BacktestCmd.Flags().String("config", "config/bbgo.yaml", "strategy config file")
BacktestCmd.Flags().Bool("force", false, "force execution without confirm")
BacktestCmd.Flags().String("output", "", "the report output directory")
RootCmd.AddCommand(BacktestCmd)
}
@ -65,6 +69,12 @@ var BacktestCmd = &cobra.Command{
return err
}
outputDirectory, err := cmd.Flags().GetString("output")
if err != nil {
return err
}
jsonOutputEnabled := len(outputDirectory) > 0
syncOnly, err := cmd.Flags().GetBool("sync-only")
if err != nil {
return err
@ -270,7 +280,7 @@ var BacktestCmd = &cobra.Command{
calculator := &pnl.AverageCostCalculator{
TradingFeeCurrency: backtestExchange.PlatformFeeCurrency(),
Market: market,
Market: market,
}
startPrice, ok := session.StartPrice(symbol)
@ -278,14 +288,14 @@ var BacktestCmd = &cobra.Command{
return fmt.Errorf("start price not found: %s", symbol)
}
log.Infof("%s PROFIT AND LOSS REPORT", symbol)
log.Infof("===============================================")
lastPrice, ok := session.LastPrice(symbol)
if !ok {
return fmt.Errorf("last price not found: %s", symbol)
}
log.Infof("%s PROFIT AND LOSS REPORT", symbol)
log.Infof("===============================================")
report := calculator.Calculate(symbol, trades.Trades, lastPrice)
report.Print()
@ -298,6 +308,29 @@ var BacktestCmd = &cobra.Command{
log.Infof("FINAL BALANCES:")
finalBalances.Print()
if jsonOutputEnabled {
result := struct {
Symbol string `json:"symbol,omitempty"`
PnLReport *pnl.AverageCostPnlReport `json:"pnlReport,omitempty"`
InitialBalances types.BalanceMap `json:"initialBalances,omitempty"`
FinalBalances types.BalanceMap `json:"finalBalances,omitempty"`
}{
Symbol: symbol,
PnLReport: report,
InitialBalances: initBalances,
FinalBalances: finalBalances,
}
jsonOutput, err := json.MarshalIndent(&result,"", " ")
if err != nil {
return err
}
if err := ioutil.WriteFile(filepath.Join(outputDirectory, symbol + ".json"), jsonOutput, 0644) ; err != nil {
return err
}
}
if wantBaseAssetBaseline {
initBaseAsset := inBaseAsset(initBalances, market, startPrice)
finalBaseAsset := inBaseAsset(finalBalances, market, lastPrice)

View File

@ -3,10 +3,11 @@ package types
import (
"encoding/json"
"fmt"
"github.com/leekchan/accounting"
"math"
"strconv"
"time"
"github.com/leekchan/accounting"
)
type Duration time.Duration
@ -48,27 +49,27 @@ func (d *Duration) UnmarshalJSON(data []byte) error {
}
type Market struct {
Symbol string
LocalSymbol string // LocalSymbol is used for exchange's API
Symbol string `json:"symbol"`
LocalSymbol string `json:"localSymbol,omitempty" `// LocalSymbol is used for exchange's API
PricePrecision int
VolumePrecision int
QuoteCurrency string
BaseCurrency string
PricePrecision int `json:"pricePrecision,omitempty"`
VolumePrecision int `json:"volumePrecision,omitempty"`
QuoteCurrency string `json:"quoteCurrency,omitempty"`
BaseCurrency string `json:"baseCurrency,omitempty"`
// The MIN_NOTIONAL filter defines the minimum notional value allowed for an order on a symbol.
// An order's notional value is the price * quantity
MinNotional float64
MinAmount float64
MinNotional float64 `json:"minNotional,omitempty"`
MinAmount float64 `json:"minAmount,omitempty"`
// The LOT_SIZE filter defines the quantity
MinQuantity float64
MaxQuantity float64
StepSize float64
MinQuantity float64 `json:"minQuantity,omitempty"`
MaxQuantity float64 `json:"maxQuantity,omitempty"`
StepSize float64 `json:"stepSize,omitempty"`
MinPrice float64
MaxPrice float64
TickSize float64
MinPrice float64 `json:"minPrice,omitempty"`
MaxPrice float64 `json:"maxPrice,omitempty"`
TickSize float64 `json:"tickSize,omitempty"`
}
func (m Market) BaseCurrencyFormatter() *accounting.Accounting {