From 7b7d0690c745caa798b395df63e121b341a133e6 Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 7 Jul 2022 02:11:52 +0800 Subject: [PATCH] optimizer: support --tsv option and render tsv output --- pkg/cmd/optimize.go | 106 ++++++++++++++++++++++++++++++++++++++++++ pkg/optimizer/grid.go | 44 +++++++++++++----- 2 files changed, 138 insertions(+), 12 deletions(-) diff --git a/pkg/cmd/optimize.go b/pkg/cmd/optimize.go index 27ed89f55..32bb2faab 100644 --- a/pkg/cmd/optimize.go +++ b/pkg/cmd/optimize.go @@ -4,12 +4,16 @@ import ( "context" "encoding/json" "fmt" + "io" "io/ioutil" "os" + "strconv" "github.com/spf13/cobra" "gopkg.in/yaml.v3" + "github.com/c9s/bbgo/pkg/data/tsv" + "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/optimizer" ) @@ -17,6 +21,7 @@ func init() { optimizeCmd.Flags().String("optimizer-config", "optimizer.yaml", "config file") optimizeCmd.Flags().String("output", "output", "backtest report output directory") optimizeCmd.Flags().Bool("json", false, "print optimizer metrics in json format") + optimizeCmd.Flags().Bool("tsv", false, "print optimizer metrics in csv format") RootCmd.AddCommand(optimizeCmd) } @@ -43,6 +48,11 @@ var optimizeCmd = &cobra.Command{ return err } + printTsvFormat, err := cmd.Flags().GetBool("tsv") + if err != nil { + return err + } + outputDirectory, err := cmd.Flags().GetString("output") if err != nil { return err @@ -104,6 +114,10 @@ var optimizeCmd = &cobra.Command{ // print metrics JSON to stdout fmt.Println(string(out)) + } else if printTsvFormat { + if err := formatMetricsTsv(metrics, os.Stdout); err != nil { + return err + } } else { for n, values := range metrics { if len(values) == 0 { @@ -120,3 +134,95 @@ var optimizeCmd = &cobra.Command{ return nil }, } + +func transformMetricsToRows(metrics map[string][]optimizer.Metric) (headers []string, rows [][]interface{}) { + var metricsKeys []string + for k := range metrics { + metricsKeys = append(metricsKeys, k) + } + + var numEntries int + var paramLabels []string + for _, ms := range metrics { + for _, m := range ms { + paramLabels = m.Labels + break + } + + numEntries = len(ms) + break + } + + headers = append(paramLabels, metricsKeys...) + rows = make([][]interface{}, numEntries) + + var metricsRows = make([][]interface{}, numEntries) + + // build params into the rows + for i, m := range metrics[metricsKeys[0]] { + rows[i] = m.Params + } + + for _, metricKey := range metricsKeys { + for i, ms := range metrics[metricKey] { + if len(metricsRows[i]) == 0 { + metricsRows[i] = make([]interface{}, 0, len(metricsKeys)) + } + metricsRows[i] = append(metricsRows[i], ms.Value) + } + } + + // merge rows + for i := range rows { + rows[i] = append(rows[i], metricsRows[i]...) + } + + return headers, rows +} + +func formatMetricsTsv(metrics map[string][]optimizer.Metric, writer io.WriteCloser) error { + headers, rows := transformMetricsToRows(metrics) + w := tsv.NewWriter(writer) + if err := w.Write(headers); err != nil { + return err + } + + for _, row := range rows { + var cells []string + for _, o := range row { + cell, err := castCellValue(o) + if err != nil { + return err + } + cells = append(cells, cell) + } + + if err := w.Write(cells); err != nil { + return err + } + } + return w.Close() +} + +func castCellValue(a interface{}) (string, error) { + switch tv := a.(type) { + case fixedpoint.Value: + return tv.String(), nil + case float64: + return strconv.FormatFloat(tv, 'f', -1, 64), nil + case int64: + return strconv.FormatInt(tv, 10), nil + case int32: + return strconv.FormatInt(int64(tv), 10), nil + case int: + return strconv.Itoa(tv), nil + case bool: + return strconv.FormatBool(tv), nil + case string: + return tv, nil + case []byte: + return string(tv), nil + default: + return "", fmt.Errorf("unsupported object type: %T value: %v", tv, tv) + } +} diff --git a/pkg/optimizer/grid.go b/pkg/optimizer/grid.go index 952e6e06a..0b76ca886 100644 --- a/pkg/optimizer/grid.go +++ b/pkg/optimizer/grid.go @@ -17,16 +17,31 @@ import ( type MetricValueFunc func(summaryReport *backtest.SummaryReport) fixedpoint.Value var TotalProfitMetricValueFunc = func(summaryReport *backtest.SummaryReport) fixedpoint.Value { - if summaryReport == nil { - return fixedpoint.Zero - } return summaryReport.TotalProfit } +var TotalVolume = func(summaryReport *backtest.SummaryReport) fixedpoint.Value { + if len(summaryReport.SymbolReports) == 0 { + return fixedpoint.Zero + } + + buyVolume := summaryReport.SymbolReports[0].PnL.BuyVolume + sellVolume := summaryReport.SymbolReports[0].PnL.SellVolume + return buyVolume.Add(sellVolume) +} + type Metric struct { - Labels []string `json:"labels,omitempty"` - Params []interface{} `json:"params,omitempty"` - Value fixedpoint.Value `json:"value,omitempty"` + // Labels is the labels of the given parameters + Labels []string `json:"labels,omitempty"` + + // Params is the parameters used to output the metrics result + Params []interface{} `json:"params,omitempty"` + + // Key is the metric name + Key string `json:"key"` + + // Value is the metric value of the metric + Value fixedpoint.Value `json:"value,omitempty"` } func copyParams(params []interface{}) []interface{} { @@ -172,6 +187,7 @@ func (o *GridOptimizer) Run(executor Executor, configJson []byte) (map[string][] var valueFunctions = map[string]MetricValueFunc{ "totalProfit": TotalProfitMetricValueFunc, + "totalVolume": TotalVolume, } var metrics = map[string][]Metric{} @@ -220,16 +236,20 @@ func (o *GridOptimizer) Run(executor Executor, configJson []byte) (map[string][] close(taskC) // this will shut down the executor for result := range resultsC { - for metricName, metricFunc := range valueFunctions { - if result.Report == nil { - log.Errorf("no summaryReport found for params: %+v", result.Params) - } + if result.Report == nil { + log.Errorf("no summaryReport found for params: %+v", result.Params) + continue + } + + for metricKey, metricFunc := range valueFunctions { var metricValue = metricFunc(result.Report) - bar.Set("log", fmt.Sprintf("params: %+v => %s %+v", result.Params, metricName, metricValue)) + bar.Set("log", fmt.Sprintf("params: %+v => %s %+v", result.Params, metricKey, metricValue)) bar.Increment() - metrics[metricName] = append(metrics[metricName], Metric{ + + metrics[metricKey] = append(metrics[metricKey], Metric{ Params: result.Params, Labels: result.Labels, + Key: metricKey, Value: metricValue, }) }