Skip to content

Commit

Permalink
common: update Errors type (thrasher-corp#1129)
Browse files Browse the repository at this point in the history
* common: adjust common error slice to allow multi errors.Is matching and conform to interface better

* zb: forgot to save?

* linties: fixies

* linties: word change as well.

* nitters: glorious

* buts

* nitters: fix glorious bug

* Update common/common.go

Co-authored-by: Scott <[email protected]>

* nitters: shifty

---------

Co-authored-by: Ryan O'Hara-Reid <[email protected]>
Co-authored-by: Scott <[email protected]>
  • Loading branch information
3 people authored Feb 19, 2023
1 parent ffea386 commit d256140
Show file tree
Hide file tree
Showing 28 changed files with 322 additions and 267 deletions.
14 changes: 5 additions & 9 deletions backtester/eventhandlers/statistics/currencystatistics.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import (

// CalculateResults calculates all statistics for the exchange, asset, currency pair
func (c *CurrencyPairStatistic) CalculateResults(riskFreeRate decimal.Decimal) error {
var errs gctcommon.Errors
var err error
first := c.Events[0]
sep := fmt.Sprintf("%v %v %v |\t", first.DataEvent.GetExchange(), first.DataEvent.GetAssetType(), first.DataEvent.Pair())

Expand Down Expand Up @@ -54,7 +52,7 @@ func (c *CurrencyPairStatistic) CalculateResults(riskFreeRate decimal.Decimal) e
c.StrategyMovement = last.Holdings.TotalValue.Sub(first.Holdings.TotalValue).Div(first.Holdings.TotalValue).Mul(oneHundred)
}
c.analysePNLGrowth()
err = c.calculateHighestCommittedFunds()
err := c.calculateHighestCommittedFunds()
if err != nil {
return err
}
Expand Down Expand Up @@ -87,9 +85,10 @@ func (c *CurrencyPairStatistic) CalculateResults(riskFreeRate decimal.Decimal) e
// ratio calculations as no movement has been made
benchmarkRates = benchmarkRates[1:]
returnsPerCandle = returnsPerCandle[1:]
var errs error
c.MaxDrawdown, err = CalculateBiggestEventDrawdown(allDataEvents)
if err != nil {
errs = append(errs, err)
errs = gctcommon.AppendError(errs, err)
}

interval := first.DataEvent.GetInterval()
Expand All @@ -109,7 +108,7 @@ func (c *CurrencyPairStatistic) CalculateResults(riskFreeRate decimal.Decimal) e
decimal.NewFromInt(int64(len(c.Events))),
)
if err != nil && !errors.Is(err, gctmath.ErrPowerDifferenceTooSmall) {
errs = append(errs, err)
errs = gctcommon.AppendError(errs, err)
}
c.CompoundAnnualGrowthRate = cagr
}
Expand All @@ -124,10 +123,7 @@ func (c *CurrencyPairStatistic) CalculateResults(riskFreeRate decimal.Decimal) e
c.UnrealisedPNL = last.PNL.GetUnrealisedPNL().PNL
c.RealisedPNL = last.PNL.GetRealisedPNL().PNL
}
if len(errs) > 0 {
return errs
}
return nil
return errs
}

func (c *CurrencyPairStatistic) calculateHighestCommittedFunds() error {
Expand Down
24 changes: 9 additions & 15 deletions backtester/eventhandlers/statistics/printresults.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package statistics

import (
"errors"
"fmt"
"sort"
"time"
Expand Down Expand Up @@ -71,8 +72,7 @@ func (s *Statistic) PrintTotalResults() {
// grouped by time to allow a clearer picture of events
func (s *Statistic) PrintAllEventsChronologically() {
log.Info(common.Statistics, common.CMDColours.H1+"------------------Events-------------------------------------"+common.CMDColours.Default)
var errs gctcommon.Errors
var err error
var errs error
var results []eventOutputHolder
for _, exchangeMap := range s.ExchangeAssetPairStatistics {
for _, assetMap := range exchangeMap {
Expand All @@ -81,25 +81,26 @@ func (s *Statistic) PrintAllEventsChronologically() {
for i := range currencyStatistic.Events {
var result string
var tt time.Time
var err error
switch {
case currencyStatistic.Events[i].FillEvent != nil:
result, err = s.CreateLog(currencyStatistic.Events[i].FillEvent)
if err != nil {
errs = append(errs, err)
errs = gctcommon.AppendError(errs, err)
continue
}
tt = currencyStatistic.Events[i].FillEvent.GetTime()
case currencyStatistic.Events[i].SignalEvent != nil:
result, err = s.CreateLog(currencyStatistic.Events[i].SignalEvent)
if err != nil {
errs = append(errs, err)
errs = gctcommon.AppendError(errs, err)
continue
}
tt = currencyStatistic.Events[i].SignalEvent.GetTime()
case currencyStatistic.Events[i].DataEvent != nil:
result, err = s.CreateLog(currencyStatistic.Events[i].DataEvent)
if err != nil {
errs = append(errs, err)
errs = gctcommon.AppendError(errs, err)
continue
}
tt = currencyStatistic.Events[i].DataEvent.GetTime()
Expand All @@ -121,10 +122,10 @@ func (s *Statistic) PrintAllEventsChronologically() {
log.Info(common.Statistics, results[i].Events[j])
}
}
if len(errs) > 0 {
if errs != nil {
log.Info(common.Statistics, common.CMDColours.Error+"------------------Errors-------------------------------------"+common.CMDColours.Default)
for i := range errs {
log.Error(common.Statistics, errs[i].Error())
for err := errors.Unwrap(errs); err != nil; err = errors.Unwrap(errs) {
log.Error(common.Statistics, err.Error())
}
}
}
Expand Down Expand Up @@ -206,7 +207,6 @@ func (s *Statistic) CreateLog(data common.Event) (string, error) {

// PrintResults outputs all calculated statistics to the command line
func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.Pair, usingExchangeLevelFunding bool) {
var errs gctcommon.Errors
sort.Slice(c.Events, func(i, j int) bool {
return c.Events[i].Time.Before(c.Events[j].Time)
})
Expand Down Expand Up @@ -302,12 +302,6 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.
log.Infof(common.CurrencyStatistics, "%s Final Unrealised PNL: %s", sep, convert.DecimalToHumanFriendlyString(unrealised.PNL, 8, ".", ","))
log.Infof(common.CurrencyStatistics, "%s Final Realised PNL: %s", sep, convert.DecimalToHumanFriendlyString(realised.PNL, 8, ".", ","))
}
if len(errs) > 0 {
log.Info(common.CurrencyStatistics, common.CMDColours.Error+"------------------Errors-------------------------------------"+common.CMDColours.Default)
for i := range errs {
log.Error(common.CurrencyStatistics, errs[i].Error())
}
}
}

// PrintResults outputs all calculated funding statistics to the command line
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,16 @@ func (s *Strategy) SupportsSimultaneousProcessing() bool {
// For dollarcostaverage, the strategy is always "buy", so it uses the OnSignal function
func (s *Strategy) OnSimultaneousSignals(d []data.Handler, _ funding.IFundingTransferer, _ portfolio.Handler) ([]signal.Event, error) {
var resp []signal.Event
var errs gctcommon.Errors
var errs error
for i := range d {
sigEvent, err := s.OnSignal(d[i], nil, nil)
if err != nil {
errs = append(errs, err)
errs = gctcommon.AppendError(errs, err)
} else {
resp = append(resp, sigEvent)
}
}

if len(errs) > 0 {
return nil, errs
}
return resp, nil
return resp, errs
}

// SetCustomSettings not required for DCA
Expand Down
14 changes: 7 additions & 7 deletions backtester/eventhandlers/strategies/rsi/rsi.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,24 +114,24 @@ func (s *Strategy) SupportsSimultaneousProcessing() bool {
// in allowing a strategy to only place an order for X currency if Y currency's price is Z
func (s *Strategy) OnSimultaneousSignals(d []data.Handler, _ funding.IFundingTransferer, _ portfolio.Handler) ([]signal.Event, error) {
var resp []signal.Event
var errs gctcommon.Errors
var errs error
for i := range d {
latest, err := d[i].Latest()
if err != nil {
return nil, err
}
sigEvent, err := s.OnSignal(d[i], nil, nil)
if err != nil {
errs = append(errs, fmt.Errorf("%v %v %v %w", latest.GetExchange(), latest.GetAssetType(), latest.Pair(), err))
errs = gctcommon.AppendError(errs, fmt.Errorf("%v %v %v %w",
latest.GetExchange(),
latest.GetAssetType(),
latest.Pair(),
err))
} else {
resp = append(resp, sigEvent)
}
}

if len(errs) > 0 {
return nil, errs
}
return resp, nil
return resp, errs
}

// SetCustomSettings allows a user to modify the RSI limits in their config
Expand Down
82 changes: 66 additions & 16 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,28 +433,78 @@ func InArray(val, array interface{}) (exists bool, index int) {
return
}

// Errors defines multiple errors
type Errors []error
// multiError holds all the errors as a slice, this is unexported, so it forces
// inbuilt error handling.
type multiError struct {
loadedErrors []error
offset *int
}

// Error implements error interface
func (e Errors) Error() string {
if len(e) == 0 {
return ""
// AppendError appends error in a more idiomatic way. This can start out as a
// standard error e.g. err := errors.New("random error")
// err = AppendError(err, errors.New("another random error"))
func AppendError(original, incoming error) error {
errSliceP, ok := original.(*multiError)
if ok {
errSliceP.offset = nil
}
if incoming == nil {
return original // Skip append - continue as normal.
}
if !ok {
// This assumes that a standard error is passed in and we can want to
// track it and add additional errors.
errSliceP = &multiError{}
if original != nil {
errSliceP.loadedErrors = append(errSliceP.loadedErrors, original)
}
}
var r string
for i := range e {
r += e[i].Error() + ", "
if incomingSlice, ok := incoming.(*multiError); ok {
// Join slices if needed.
errSliceP.loadedErrors = append(errSliceP.loadedErrors, incomingSlice.loadedErrors...)
} else {
errSliceP.loadedErrors = append(errSliceP.loadedErrors, incoming)
}
return r[:len(r)-2]
return errSliceP
}

// Unwrap implements interface behaviour for errors.Is() matching NOTE: only
// returns first element.
func (e Errors) Unwrap() error {
if len(e) == 0 {
return nil
// Error displays all errors comma separated, if unwrapped has been called and
// has not been reset will display the individual error
func (e *multiError) Error() string {
if e.offset != nil {
return e.loadedErrors[*e.offset].Error()
}
allErrors := make([]string, len(e.loadedErrors))
for x := range e.loadedErrors {
allErrors[x] = e.loadedErrors[x].Error()
}
return strings.Join(allErrors, ", ")
}

// Unwrap increments the offset so errors.Is() can be called to its individual
// error for correct matching.
func (e *multiError) Unwrap() error {
if e.offset == nil {
e.offset = new(int)
} else {
*e.offset++
}
if *e.offset == len(e.loadedErrors) {
e.offset = nil
return nil // Force errors.Is package to return false.
}
return e[0]
return e
}

// Is checks to see if the errors match. It calls package errors.Is() so that
// we can keep fmt.Errorf() trimmings. This is called in errors package at
// interface assertion err.(interface{ Is(error) bool }).
func (e *multiError) Is(incoming error) bool {
if e.offset != nil && errors.Is(e.loadedErrors[*e.offset], incoming) {
e.offset = nil
return true
}
return false
}

// StartEndTimeCheck provides some basic checks which occur
Expand Down
Loading

0 comments on commit d256140

Please sign in to comment.