qbtrade/pkg/report/profit_report.go

178 lines
4.9 KiB
Go
Raw Normal View History

2024-06-27 14:42:38 +00:00
package report
import (
"fmt"
"git.qtrade.icu/lychiyu/qbtrade/pkg/data/tsv"
"git.qtrade.icu/lychiyu/qbtrade/pkg/datatype/floats"
"git.qtrade.icu/lychiyu/qbtrade/pkg/fixedpoint"
indicatorv2 "git.qtrade.icu/lychiyu/qbtrade/pkg/indicator/v2"
"git.qtrade.icu/lychiyu/qbtrade/pkg/types"
"strconv"
)
// AccumulatedProfitReport For accumulated profit report output
type AccumulatedProfitReport struct {
// ProfitMAWindow Accumulated profit SMA window
ProfitMAWindow int `json:"profitMAWindow"`
// ShortTermProfitWindow The window to sum up the short-term profit
ShortTermProfitWindow int `json:"shortTermProfitWindow"`
// TsvReportPath The path to output report to
TsvReportPath string `json:"tsvReportPath"`
symbol string
types.IntervalWindow
// ProfitMAWindow Accumulated profit SMA window
AccumulateTradeWindow int `json:"accumulateTradeWindow"`
// Accumulated profit
accumulatedProfit fixedpoint.Value
accumulatedProfitPerInterval *types.Float64Series
// Accumulated profit MA
profitMA *indicatorv2.SMAStream
profitMAPerInterval floats.Slice
// Profit of each interval
ProfitPerInterval floats.Slice
// Accumulated fee
accumulatedFee fixedpoint.Value
accumulatedFeePerInterval floats.Slice
// Win ratio
winRatioPerInterval floats.Slice
// Profit factor
profitFactorPerInterval floats.Slice
// Trade number
accumulatedTrades int
accumulatedTradesPerInterval floats.Slice
// Extra values
strategyParameters [][2]string
}
func (r *AccumulatedProfitReport) Initialize(symbol string, interval types.Interval, window int) {
r.symbol = symbol
r.Interval = interval
r.Window = window
if r.ProfitMAWindow <= 0 {
r.ProfitMAWindow = 60
}
if r.Window <= 0 {
r.Window = 7
}
if r.ShortTermProfitWindow <= 0 {
r.ShortTermProfitWindow = 7
}
r.accumulatedProfitPerInterval = types.NewFloat64Series()
r.profitMA = indicatorv2.SMA(r.accumulatedProfitPerInterval, r.ProfitMAWindow)
}
func (r *AccumulatedProfitReport) AddStrategyParameter(title string, value string) {
r.strategyParameters = append(r.strategyParameters, [2]string{title, value})
}
func (r *AccumulatedProfitReport) AddTrade(trade types.Trade) {
r.accumulatedFee = r.accumulatedFee.Add(trade.Fee)
r.accumulatedTrades += 1
}
func (r *AccumulatedProfitReport) Rotate(ps *types.ProfitStats, ts *types.TradeStats) {
// Accumulated profit
r.accumulatedProfit = r.accumulatedProfit.Add(ps.AccumulatedNetProfit)
r.accumulatedProfitPerInterval.PushAndEmit(r.accumulatedProfit.Float64())
// Profit of each interval
r.ProfitPerInterval.Update(ps.AccumulatedNetProfit.Float64())
// Profit MA
r.profitMAPerInterval.Update(r.profitMA.Last(0))
// Accumulated Fee
r.accumulatedFeePerInterval.Update(r.accumulatedFee.Float64())
// Trades
r.accumulatedTradesPerInterval.Update(float64(r.accumulatedTrades))
// Win ratio
r.winRatioPerInterval.Update(ts.WinningRatio.Float64())
// Profit factor
r.profitFactorPerInterval.Update(ts.ProfitFactor.Float64())
}
// CsvHeader returns a header slice
func (r *AccumulatedProfitReport) CsvHeader() []string {
titles := []string{
"#",
"Symbol",
"Total Net Profit",
fmt.Sprintf("Total Net Profit %sMA%d", r.Interval, r.ProfitMAWindow),
fmt.Sprintf("%s%d Net Profit", r.Interval, r.ShortTermProfitWindow),
"accumulatedFee",
"winRatio",
"profitFactor",
fmt.Sprintf("%s%d Trades", r.Interval, r.AccumulateTradeWindow),
}
for i := 0; i < len(r.strategyParameters); i++ {
titles = append(titles, r.strategyParameters[i][0])
}
return titles
}
// CsvRecords returns a data slice
func (r *AccumulatedProfitReport) CsvRecords() [][]string {
var data [][]string
for i := 0; i <= r.Window-1; i++ {
values := []string{
strconv.Itoa(i + 1),
r.symbol,
strconv.FormatFloat(r.accumulatedProfitPerInterval.Last(i), 'f', 4, 64),
strconv.FormatFloat(r.profitMAPerInterval.Last(i), 'f', 4, 64),
strconv.FormatFloat(r.accumulatedProfitPerInterval.Last(i)-r.accumulatedProfitPerInterval.Last(i+r.ShortTermProfitWindow), 'f', 4, 64),
strconv.FormatFloat(r.accumulatedFeePerInterval.Last(i), 'f', 4, 64),
strconv.FormatFloat(r.winRatioPerInterval.Last(i), 'f', 4, 64),
strconv.FormatFloat(r.profitFactorPerInterval.Last(i), 'f', 4, 64),
strconv.FormatFloat(r.accumulatedTradesPerInterval.Last(i)-r.accumulatedTradesPerInterval.Last(i+r.AccumulateTradeWindow), 'f', 4, 64),
}
for j := 0; j < len(r.strategyParameters); j++ {
values = append(values, r.strategyParameters[j][1])
}
data = append(data, values)
}
return data
}
// Output Accumulated profit report to a TSV file
func (r *AccumulatedProfitReport) Output() {
if r.TsvReportPath != "" {
// Open specified file for appending
tsvwiter, err := tsv.AppendWriterFile(r.TsvReportPath)
if err != nil {
panic(err)
}
defer tsvwiter.Close()
// Column Title
_ = tsvwiter.Write(r.CsvHeader())
// Output data rows
_ = tsvwiter.WriteAll(r.CsvRecords())
}
}