Add "Total Traded" statistic

This commit is contained in:
Luke I. Wilson
2023-05-24 16:50:27 -05:00
parent 74855ba4b1
commit 1339937d4a
3 changed files with 32 additions and 6 deletions

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"log"
"math"
"os"
"strconv"
"strings"
@@ -42,6 +43,23 @@ func Backtest(trader *Trader) {
stats := trader.Stats()
// log.Println(trader.Stats().Dated.String())
var totalTraded float64
stats.Dated.Series("Trades").ForEach(func(i int, val any) {
if val == nil {
return
}
switch typ := val.(type) {
case []TradeStat:
for _, trade := range typ {
if trade.Exit { // Only count entry trades.
continue
}
totalTraded += trade.Price * math.Abs(trade.Units)
}
default:
panic("unknown type when calculating totalTraded")
}
})
// Divide net profit by maximum drawdown to get the profit factor.
var maxDrawdown float64
stats.Dated.Series("Drawdown").ForEach(func(i int, val any) {
@@ -59,6 +77,7 @@ func Backtest(trader *Trader) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
fmt.Fprintln(w)
fmt.Fprintf(w, "Timespan:\t%s\t\n", stats.Dated.Date(-1).Sub(stats.Dated.Date(0)).Round(time.Second))
fmt.Fprintf(w, "Total Traded:\t$%.2f\t\n", totalTraded)
fmt.Fprintf(w, "Net Profit:\t$%.2f (%.2f%%)\t\n", profit, 100*profit/stats.Dated.Float("Equity", 0))
fmt.Fprintf(w, "Profit Factor:\t%.2f\t\n", profitFactor)
fmt.Fprintf(w, "Max Drawdown:\t$%.2f (%.2f%%)\t\n", maxDrawdown, maxDrawdownPct)
@@ -600,7 +619,7 @@ func (p *TestPosition) close(atPrice float64, closeType OrderCloseType) {
p.closePrice = atPrice
p.closeType = closeType
p.broker.Cash += p.Value() // Return the value of the position to the broker.
p.broker.spreadCollectedUSD += p.broker.Spread * p.units
p.broker.spreadCollectedUSD += p.broker.Spread * math.Abs(p.units) * p.closePrice
p.broker.SignalEmit("PositionClosed", p)
}

View File

@@ -23,7 +23,7 @@ func (s *IchimokuStrategy) Next(t *auto.Trader) {
laggingTime := data.Date(-s.leadingPeriods - 1)
// Extract ichimoku elements
ichimoku := auto.Ichimoku(data, s.convPeriod, s.basePeriod, s.leadingPeriods, time.Minute*15)
ichimoku := auto.Ichimoku(data, s.convPeriod, s.basePeriod, s.leadingPeriods, time.Minute*1)
conv := ichimoku.Series("Conversion")
base := ichimoku.Series("Base")
leadA := ichimoku.Series("LeadingA")
@@ -91,7 +91,7 @@ func main() {
Broker: auto.NewTestBroker(broker, nil, 10000, 50, 0.0002, 0),
Strategy: &IchimokuStrategy{convPeriod: 9, basePeriod: 26, leadingPeriods: 52},
Symbol: "EUR_USD",
Frequency: "M15",
Frequency: "M1", // If the frequency is changed, update the call to Ichimoku() above.
CandlesToKeep: 2500,
}))
}

View File

@@ -33,6 +33,7 @@ func (t *Trader) Data() *IndexedFrame[UnixTime] {
}
type TradeStat struct {
Price float64 // Price is the price at which the trade was executed. If Exit is true, this is the exit price. Otherwise, this is the entry price.
Units float64 // Units is the signed number of units bought or sold.
Exit bool // Exit is true if the trade was to exit a previous position.
}
@@ -165,14 +166,18 @@ func (t *Trader) Buy(units, stopLoss, takeProfit float64) {
t.CloseOrdersAndPositions()
t.Log.Printf("Buy %v units", units)
t.Broker.Order(Market, t.Symbol, units, 0, stopLoss, takeProfit)
t.stats.tradesThisCandle = append(t.stats.tradesThisCandle, TradeStat{units, false})
tradeStat := TradeStat{t.Broker.Ask(t.Symbol), units, false}
t.stats.tradesThisCandle = append(t.stats.tradesThisCandle, tradeStat)
}
func (t *Trader) Sell(units, stopLoss, takeProfit float64) {
t.CloseOrdersAndPositions()
t.Log.Printf("Sell %v units", units)
t.Broker.Order(Market, t.Symbol, -units, 0, stopLoss, takeProfit)
t.stats.tradesThisCandle = append(t.stats.tradesThisCandle, TradeStat{-units, false})
tradeStat := TradeStat{t.Broker.Bid(t.Symbol), units, false}
t.stats.tradesThisCandle = append(t.stats.tradesThisCandle, tradeStat)
}
func (t *Trader) CloseOrdersAndPositions() {
@@ -186,7 +191,9 @@ func (t *Trader) CloseOrdersAndPositions() {
if position.Symbol() == t.Symbol {
t.Log.Printf("Closing position: %v units, $%.2f PL", position.Units(), position.PL())
position.Close()
t.stats.tradesThisCandle = append(t.stats.tradesThisCandle, TradeStat{position.Units(), true})
tradeStat := TradeStat{t.Broker.Price(t.Symbol, position.Units() < 0), position.Units(), true}
t.stats.tradesThisCandle = append(t.stats.tradesThisCandle, tradeStat)
}
}
}