Skip to content

Commit

Permalink
Merge pull request #801 from c9s/feature/optimizer-metrics-tsv-format
Browse files Browse the repository at this point in the history
feature: optimizer: support --tsv option and render tsv output
  • Loading branch information
c9s committed Jul 6, 2022
2 parents c20b3e9 + 7b7d069 commit e778db1
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 11 deletions.
106 changes: 106 additions & 0 deletions pkg/cmd/optimize.go
Expand Up @@ -4,19 +4,24 @@ 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"
)

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)
}

Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
}
42 changes: 31 additions & 11 deletions pkg/optimizer/grid.go
Expand Up @@ -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 summaryReport.TotalProfit
}

var TotalVolume = func(summaryReport *backtest.SummaryReport) fixedpoint.Value {
if len(summaryReport.SymbolReports) == 0 {
return fixedpoint.Zero
}
return summaryReport.TotalProfit

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{} {
Expand Down Expand Up @@ -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{}

Expand Down Expand Up @@ -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,
})
}
Expand Down

0 comments on commit e778db1

Please sign in to comment.