feature: optimizer add profitFactor optimization. Optimization value use float64 instead to save memory and boost performance

This commit is contained in:
zenix 2022-11-10 18:17:35 +09:00
parent 7d03c69406
commit 7aaea257df
5 changed files with 58 additions and 29 deletions

View File

@ -81,6 +81,8 @@ type SessionSymbolReport struct {
Manifests Manifests `json:"manifests,omitempty"` Manifests Manifests `json:"manifests,omitempty"`
Sharpe fixedpoint.Value `json:"sharpeRatio"` Sharpe fixedpoint.Value `json:"sharpeRatio"`
Sortino fixedpoint.Value `json:"sortinoRatio"` Sortino fixedpoint.Value `json:"sortinoRatio"`
ProfitFactor fixedpoint.Value `json:"profitFactor"`
WinningRatio fixedpoint.Value `json:"winningRatio"`
} }
func (r *SessionSymbolReport) InitialEquityValue() fixedpoint.Value { func (r *SessionSymbolReport) InitialEquityValue() fixedpoint.Value {

View File

@ -4,11 +4,6 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"github.com/c9s/bbgo/pkg/cmd/cmdutil"
"github.com/c9s/bbgo/pkg/data/tsv"
"github.com/c9s/bbgo/pkg/util"
"github.com/fatih/color"
"github.com/google/uuid"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
@ -16,6 +11,12 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/c9s/bbgo/pkg/cmd/cmdutil"
"github.com/c9s/bbgo/pkg/data/tsv"
"github.com/c9s/bbgo/pkg/util"
"github.com/fatih/color"
"github.com/google/uuid"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -523,8 +524,11 @@ var BacktestCmd = &cobra.Command{
for _, session := range environ.Sessions() { for _, session := range environ.Sessions() {
for symbol, trades := range session.Trades { for symbol, trades := range session.Trades {
intervalProfits := sessionTradeStats[session.Name][symbol].IntervalProfits[types.Interval1d] tradeState := sessionTradeStats[session.Name][symbol]
symbolReport, err := createSymbolReport(userConfig, session, symbol, trades.Trades, intervalProfits) profitFactor := tradeState.ProfitFactor
winningRatio := tradeState.WinningRatio
intervalProfits := tradeState.IntervalProfits[types.Interval1d]
symbolReport, err := createSymbolReport(userConfig, session, symbol, trades.Trades, intervalProfits, profitFactor, winningRatio)
if err != nil { if err != nil {
return err return err
} }
@ -615,7 +619,8 @@ func collectSubscriptionIntervals(environ *bbgo.Environment) (allKLineIntervals
return allKLineIntervals, requiredInterval, backTestIntervals return allKLineIntervals, requiredInterval, backTestIntervals
} }
func createSymbolReport(userConfig *bbgo.Config, session *bbgo.ExchangeSession, symbol string, trades []types.Trade, intervalProfit *types.IntervalProfitCollector) ( func createSymbolReport(userConfig *bbgo.Config, session *bbgo.ExchangeSession, symbol string, trades []types.Trade, intervalProfit *types.IntervalProfitCollector,
profitFactor, winningRatio fixedpoint.Value) (
*backtest.SessionSymbolReport, *backtest.SessionSymbolReport,
error, error,
) { ) {
@ -661,8 +666,10 @@ func createSymbolReport(userConfig *bbgo.Config, session *bbgo.ExchangeSession,
InitialBalances: initBalances, InitialBalances: initBalances,
FinalBalances: finalBalances, FinalBalances: finalBalances,
// Manifests: manifests, // Manifests: manifests,
Sharpe: sharpeRatio, Sharpe: sharpeRatio,
Sortino: sortinoRatio, Sortino: sortinoRatio,
ProfitFactor: profitFactor,
WinningRatio: winningRatio,
} }
for _, s := range session.Subscriptions { for _, s := range session.Subscriptions {

View File

@ -79,7 +79,7 @@ func LoadConfig(yamlConfigFileName string) (*Config, error) {
switch objective := strings.ToLower(optConfig.Objective); objective { switch objective := strings.ToLower(optConfig.Objective); objective {
case "", "default": case "", "default":
optConfig.Objective = HpOptimizerObjectiveEquity optConfig.Objective = HpOptimizerObjectiveEquity
case HpOptimizerObjectiveEquity, HpOptimizerObjectiveProfit, HpOptimizerObjectiveVolume: case HpOptimizerObjectiveEquity, HpOptimizerObjectiveProfit, HpOptimizerObjectiveVolume, HpOptimizerObjectiveProfitFactor:
optConfig.Objective = objective optConfig.Objective = objective
default: default:
return nil, fmt.Errorf(`unknown objective "%s"`, optConfig.Objective) return nil, fmt.Errorf(`unknown objective "%s"`, optConfig.Objective)

View File

@ -14,30 +14,43 @@ import (
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
) )
type MetricValueFunc func(summaryReport *backtest.SummaryReport) fixedpoint.Value type MetricValueFunc func(summaryReport *backtest.SummaryReport) float64
var TotalProfitMetricValueFunc = func(summaryReport *backtest.SummaryReport) fixedpoint.Value { var TotalProfitMetricValueFunc = func(summaryReport *backtest.SummaryReport) float64 {
return summaryReport.TotalProfit return summaryReport.TotalProfit.Float64()
} }
var TotalVolume = func(summaryReport *backtest.SummaryReport) fixedpoint.Value { var TotalVolume = func(summaryReport *backtest.SummaryReport) float64 {
if len(summaryReport.SymbolReports) == 0 { if len(summaryReport.SymbolReports) == 0 {
return fixedpoint.Zero return 0
} }
buyVolume := summaryReport.SymbolReports[0].PnL.BuyVolume buyVolume := summaryReport.SymbolReports[0].PnL.BuyVolume.Float64()
sellVolume := summaryReport.SymbolReports[0].PnL.SellVolume sellVolume := summaryReport.SymbolReports[0].PnL.SellVolume.Float64()
return buyVolume.Add(sellVolume) return buyVolume + sellVolume
} }
var TotalEquityDiff = func(summaryReport *backtest.SummaryReport) fixedpoint.Value { var TotalEquityDiff = func(summaryReport *backtest.SummaryReport) float64 {
if len(summaryReport.SymbolReports) == 0 { if len(summaryReport.SymbolReports) == 0 {
return fixedpoint.Zero return 0
} }
initEquity := summaryReport.InitialEquityValue initEquity := summaryReport.InitialEquityValue.Float64()
finalEquity := summaryReport.FinalEquityValue finalEquity := summaryReport.FinalEquityValue.Float64()
return finalEquity.Sub(initEquity) return finalEquity - initEquity
}
var ProfitFactorMetricValueFunc = func(summaryReport *backtest.SummaryReport) float64 {
if len(summaryReport.SymbolReports) == 0 {
return 0
}
if len(summaryReport.SymbolReports) > 1 {
panic("multiple symbols' profitfactor optimization not supported")
}
report := summaryReport.SymbolReports[0]
pf := report.ProfitFactor.Float64()
win := report.WinningRatio.Float64()
return pf*0.9 + win*0.1
} }
type Metric struct { type Metric struct {
@ -51,7 +64,7 @@ type Metric struct {
Key string `json:"key"` Key string `json:"key"`
// Value is the metric value of the metric // Value is the metric value of the metric
Value fixedpoint.Value `json:"value,omitempty"` Value float64 `json:"value,omitempty"`
} }
func copyParams(params []interface{}) []interface{} { func copyParams(params []interface{}) []interface{} {
@ -199,6 +212,7 @@ func (o *GridOptimizer) Run(executor Executor, configJson []byte) (map[string][]
"totalProfit": TotalProfitMetricValueFunc, "totalProfit": TotalProfitMetricValueFunc,
"totalVolume": TotalVolume, "totalVolume": TotalVolume,
"totalEquityDiff": TotalEquityDiff, "totalEquityDiff": TotalEquityDiff,
"profitFactor": ProfitFactorMetricValueFunc,
} }
var metrics = map[string][]Metric{} var metrics = map[string][]Metric{}
@ -287,7 +301,7 @@ func (o *GridOptimizer) Run(executor Executor, configJson []byte) (map[string][]
sort.Slice(metrics[n], func(i, j int) bool { sort.Slice(metrics[n], func(i, j int) bool {
a := metrics[n][i].Value a := metrics[n][i].Value
b := metrics[n][j].Value b := metrics[n][j].Value
return a.Compare(b) > 0 return a > b
}) })
} }

View File

@ -3,6 +3,9 @@ package optimizer
import ( import (
"context" "context"
"fmt" "fmt"
"math"
"sync"
"github.com/c-bata/goptuna" "github.com/c-bata/goptuna"
goptunaCMAES "github.com/c-bata/goptuna/cmaes" goptunaCMAES "github.com/c-bata/goptuna/cmaes"
goptunaSOBOL "github.com/c-bata/goptuna/sobol" goptunaSOBOL "github.com/c-bata/goptuna/sobol"
@ -11,10 +14,9 @@ import (
"github.com/cheggaaa/pb/v3" "github.com/cheggaaa/pb/v3"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"math"
"sync"
) )
// WARNING: the text here could only be lower cases
const ( const (
// HpOptimizerObjectiveEquity optimize the parameters to maximize equity gain // HpOptimizerObjectiveEquity optimize the parameters to maximize equity gain
HpOptimizerObjectiveEquity = "equity" HpOptimizerObjectiveEquity = "equity"
@ -22,6 +24,8 @@ const (
HpOptimizerObjectiveProfit = "profit" HpOptimizerObjectiveProfit = "profit"
// HpOptimizerObjectiveVolume optimize the parameters to maximize trading volume // HpOptimizerObjectiveVolume optimize the parameters to maximize trading volume
HpOptimizerObjectiveVolume = "volume" HpOptimizerObjectiveVolume = "volume"
// HpOptimizerObjectiveProfitFactor optimize the parameters to maximize profit factor
HpOptimizerObjectiveProfitFactor = "profitfactor"
) )
const ( const (
@ -198,6 +202,8 @@ func (o *HyperparameterOptimizer) buildObjective(executor Executor, configJson [
metricValueFunc = TotalVolume metricValueFunc = TotalVolume
case HpOptimizerObjectiveEquity: case HpOptimizerObjectiveEquity:
metricValueFunc = TotalEquityDiff metricValueFunc = TotalEquityDiff
case HpOptimizerObjectiveProfitFactor:
metricValueFunc = ProfitFactorMetricValueFunc
} }
return func(trial goptuna.Trial) (float64, error) { return func(trial goptuna.Trial) (float64, error) {
@ -225,7 +231,7 @@ func (o *HyperparameterOptimizer) buildObjective(executor Executor, configJson [
return 0.0, err return 0.0, err
} }
// By config, the Goptuna optimize the parameters by maximize the objective output. // By config, the Goptuna optimize the parameters by maximize the objective output.
return metricValueFunc(summary).Float64(), nil return metricValueFunc(summary), nil
} }
} }