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"`
Sharpe fixedpoint.Value `json:"sharpeRatio"`
Sortino fixedpoint.Value `json:"sortinoRatio"`
ProfitFactor fixedpoint.Value `json:"profitFactor"`
WinningRatio fixedpoint.Value `json:"winningRatio"`
}
func (r *SessionSymbolReport) InitialEquityValue() fixedpoint.Value {

View File

@ -4,11 +4,6 @@ import (
"bufio"
"context"
"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"
"path/filepath"
"sort"
@ -16,6 +11,12 @@ import (
"syscall"
"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"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@ -523,8 +524,11 @@ var BacktestCmd = &cobra.Command{
for _, session := range environ.Sessions() {
for symbol, trades := range session.Trades {
intervalProfits := sessionTradeStats[session.Name][symbol].IntervalProfits[types.Interval1d]
symbolReport, err := createSymbolReport(userConfig, session, symbol, trades.Trades, intervalProfits)
tradeState := sessionTradeStats[session.Name][symbol]
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 {
return err
}
@ -615,7 +619,8 @@ func collectSubscriptionIntervals(environ *bbgo.Environment) (allKLineIntervals
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,
error,
) {
@ -661,8 +666,10 @@ func createSymbolReport(userConfig *bbgo.Config, session *bbgo.ExchangeSession,
InitialBalances: initBalances,
FinalBalances: finalBalances,
// Manifests: manifests,
Sharpe: sharpeRatio,
Sortino: sortinoRatio,
Sharpe: sharpeRatio,
Sortino: sortinoRatio,
ProfitFactor: profitFactor,
WinningRatio: winningRatio,
}
for _, s := range session.Subscriptions {

View File

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

View File

@ -14,30 +14,43 @@ import (
"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 {
return summaryReport.TotalProfit
var TotalProfitMetricValueFunc = func(summaryReport *backtest.SummaryReport) float64 {
return summaryReport.TotalProfit.Float64()
}
var TotalVolume = func(summaryReport *backtest.SummaryReport) fixedpoint.Value {
var TotalVolume = func(summaryReport *backtest.SummaryReport) float64 {
if len(summaryReport.SymbolReports) == 0 {
return fixedpoint.Zero
return 0
}
buyVolume := summaryReport.SymbolReports[0].PnL.BuyVolume
sellVolume := summaryReport.SymbolReports[0].PnL.SellVolume
return buyVolume.Add(sellVolume)
buyVolume := summaryReport.SymbolReports[0].PnL.BuyVolume.Float64()
sellVolume := summaryReport.SymbolReports[0].PnL.SellVolume.Float64()
return buyVolume + sellVolume
}
var TotalEquityDiff = func(summaryReport *backtest.SummaryReport) fixedpoint.Value {
var TotalEquityDiff = func(summaryReport *backtest.SummaryReport) float64 {
if len(summaryReport.SymbolReports) == 0 {
return fixedpoint.Zero
return 0
}
initEquity := summaryReport.InitialEquityValue
finalEquity := summaryReport.FinalEquityValue
return finalEquity.Sub(initEquity)
initEquity := summaryReport.InitialEquityValue.Float64()
finalEquity := summaryReport.FinalEquityValue.Float64()
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 {
@ -51,7 +64,7 @@ type Metric struct {
Key string `json:"key"`
// Value is the metric value of the metric
Value fixedpoint.Value `json:"value,omitempty"`
Value float64 `json:"value,omitempty"`
}
func copyParams(params []interface{}) []interface{} {
@ -199,6 +212,7 @@ func (o *GridOptimizer) Run(executor Executor, configJson []byte) (map[string][]
"totalProfit": TotalProfitMetricValueFunc,
"totalVolume": TotalVolume,
"totalEquityDiff": TotalEquityDiff,
"profitFactor": ProfitFactorMetricValueFunc,
}
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 {
a := metrics[n][i].Value
b := metrics[n][j].Value
return a.Compare(b) > 0
return a > b
})
}

View File

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