backtest: calculate realized Sharpe & Sortino ratios

This commit is contained in:
Raphanus Lo 2022-08-24 14:13:28 +08:00
parent e2774ed2b5
commit ad1b9a53a1
2 changed files with 58 additions and 2 deletions

View File

@ -79,6 +79,8 @@ type SessionSymbolReport struct {
InitialBalances types.BalanceMap `json:"initialBalances,omitempty"`
FinalBalances types.BalanceMap `json:"finalBalances,omitempty"`
Manifests Manifests `json:"manifests,omitempty"`
Sharpe float64 `json:"sharpeRatio"`
Sortino float64 `json:"sortinoRatio"`
}
func (r *SessionSymbolReport) InitialEquityValue() fixedpoint.Value {
@ -117,6 +119,18 @@ func (r *SessionSymbolReport) Print(wantBaseAssetBaseline bool) {
color.Red("ASSET DECREASED: %v %s (%s)", finalQuoteAsset.Sub(initQuoteAsset), r.Market.QuoteCurrency, finalQuoteAsset.Sub(initQuoteAsset).Div(initQuoteAsset).FormatPercentage(2))
}
if r.Sharpe > 0.0 {
color.Green("REALIZED SHARPE RATIO: +%v", r.Sharpe)
} else {
color.Red("REALIZED SHARPE RATIO: +%v", r.Sharpe)
}
if r.Sortino > 0.0 {
color.Green("REALIZED SORTINO RATIO: %v", r.Sortino)
} else {
color.Red("REALIZED SORTINO RATIO: %v", r.Sortino)
}
if wantBaseAssetBaseline {
if r.LastPrice.Compare(r.StartPrice) > 0 {
color.Green("%s BASE ASSET PERFORMANCE: +%s (= (%s - %s) / %s)",

View File

@ -297,6 +297,42 @@ var BacktestCmd = &cobra.Command{
var manifests backtest.Manifests
var runID = userConfig.GetSignature() + "_" + uuid.NewString()
var reportDir = outputDirectory
var sessionTradeStats = make(map[string]map[string]*types.TradeStats)
var tradeCollectorList []*bbgo.TradeCollector
for _, exSource := range exchangeSources {
sessionName := exSource.Session.Name
tradeStatsMap := make(map[string]*types.TradeStats)
for usedSymbol := range exSource.Session.Positions() {
market, _ := exSource.Session.Market(usedSymbol)
position := types.NewPositionFromMarket(market)
orderStore := bbgo.NewOrderStore(usedSymbol)
orderStore.AddOrderUpdate = true
tradeCollector := bbgo.NewTradeCollector(usedSymbol, position, orderStore)
tradeStats := types.NewTradeStats(usedSymbol)
tradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1d, startTime))
tradeCollector.OnProfit(func(trade types.Trade, profit *types.Profit) {
if profit == nil {
return
}
tradeStats.Add(profit)
})
tradeStatsMap[usedSymbol] = tradeStats
orderStore.BindStream(exSource.Session.UserDataStream)
tradeCollector.BindStream(exSource.Session.UserDataStream)
tradeCollectorList = append(tradeCollectorList, tradeCollector)
}
sessionTradeStats[sessionName] = tradeStatsMap
}
kLineHandlers = append(kLineHandlers, func(k types.KLine, _ *backtest.ExchangeDataSource) {
if k.Interval == types.Interval1d && k.Closed {
for _, collector := range tradeCollectorList {
collector.Process()
}
}
})
if generatingReport {
if reportFileInSubDir {
@ -494,7 +530,8 @@ var BacktestCmd = &cobra.Command{
for _, session := range environ.Sessions() {
for symbol, trades := range session.Trades {
symbolReport, err := createSymbolReport(userConfig, session, symbol, trades.Trades)
intervalProfits := sessionTradeStats[session.Name][symbol].IntervalProfits[types.Interval1d]
symbolReport, err := createSymbolReport(userConfig, session, symbol, trades.Trades, intervalProfits)
if err != nil {
return err
}
@ -555,7 +592,10 @@ var BacktestCmd = &cobra.Command{
},
}
func createSymbolReport(userConfig *bbgo.Config, session *bbgo.ExchangeSession, symbol string, trades []types.Trade) (*backtest.SessionSymbolReport, error) {
func createSymbolReport(userConfig *bbgo.Config, session *bbgo.ExchangeSession, symbol string, trades []types.Trade, intervalProfit *types.IntervalProfitCollector) (
*backtest.SessionSymbolReport,
error,
) {
backtestExchange, ok := session.Exchange.(*backtest.Exchange)
if !ok {
return nil, fmt.Errorf("unexpected error, exchange instance is not a backtest exchange")
@ -595,6 +635,8 @@ func createSymbolReport(userConfig *bbgo.Config, session *bbgo.ExchangeSession,
InitialBalances: initBalances,
FinalBalances: finalBalances,
// Manifests: manifests,
Sharpe: intervalProfit.GetSharpe(),
Sortino: intervalProfit.GetSortino(),
}
for _, s := range session.Subscriptions {