mirror of
https://github.com/lukewilson2002/autotrader.git
synced 2025-08-02 13:19:32 +00:00
Add "Total Traded" statistic
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -42,6 +43,23 @@ func Backtest(trader *Trader) {
|
|||||||
stats := trader.Stats()
|
stats := trader.Stats()
|
||||||
// log.Println(trader.Stats().Dated.String())
|
// 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.
|
// Divide net profit by maximum drawdown to get the profit factor.
|
||||||
var maxDrawdown float64
|
var maxDrawdown float64
|
||||||
stats.Dated.Series("Drawdown").ForEach(func(i int, val any) {
|
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)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||||
fmt.Fprintln(w)
|
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, "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, "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, "Profit Factor:\t%.2f\t\n", profitFactor)
|
||||||
fmt.Fprintf(w, "Max Drawdown:\t$%.2f (%.2f%%)\t\n", maxDrawdown, maxDrawdownPct)
|
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.closePrice = atPrice
|
||||||
p.closeType = closeType
|
p.closeType = closeType
|
||||||
p.broker.Cash += p.Value() // Return the value of the position to the broker.
|
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)
|
p.broker.SignalEmit("PositionClosed", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,7 +23,7 @@ func (s *IchimokuStrategy) Next(t *auto.Trader) {
|
|||||||
laggingTime := data.Date(-s.leadingPeriods - 1)
|
laggingTime := data.Date(-s.leadingPeriods - 1)
|
||||||
|
|
||||||
// Extract ichimoku elements
|
// 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")
|
conv := ichimoku.Series("Conversion")
|
||||||
base := ichimoku.Series("Base")
|
base := ichimoku.Series("Base")
|
||||||
leadA := ichimoku.Series("LeadingA")
|
leadA := ichimoku.Series("LeadingA")
|
||||||
@@ -91,7 +91,7 @@ func main() {
|
|||||||
Broker: auto.NewTestBroker(broker, nil, 10000, 50, 0.0002, 0),
|
Broker: auto.NewTestBroker(broker, nil, 10000, 50, 0.0002, 0),
|
||||||
Strategy: &IchimokuStrategy{convPeriod: 9, basePeriod: 26, leadingPeriods: 52},
|
Strategy: &IchimokuStrategy{convPeriod: 9, basePeriod: 26, leadingPeriods: 52},
|
||||||
Symbol: "EUR_USD",
|
Symbol: "EUR_USD",
|
||||||
Frequency: "M15",
|
Frequency: "M1", // If the frequency is changed, update the call to Ichimoku() above.
|
||||||
CandlesToKeep: 2500,
|
CandlesToKeep: 2500,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
13
trader.go
13
trader.go
@@ -33,6 +33,7 @@ func (t *Trader) Data() *IndexedFrame[UnixTime] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TradeStat struct {
|
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.
|
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.
|
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.CloseOrdersAndPositions()
|
||||||
t.Log.Printf("Buy %v units", units)
|
t.Log.Printf("Buy %v units", units)
|
||||||
t.Broker.Order(Market, t.Symbol, units, 0, stopLoss, takeProfit)
|
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) {
|
func (t *Trader) Sell(units, stopLoss, takeProfit float64) {
|
||||||
t.CloseOrdersAndPositions()
|
t.CloseOrdersAndPositions()
|
||||||
t.Log.Printf("Sell %v units", units)
|
t.Log.Printf("Sell %v units", units)
|
||||||
t.Broker.Order(Market, t.Symbol, -units, 0, stopLoss, takeProfit)
|
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() {
|
func (t *Trader) CloseOrdersAndPositions() {
|
||||||
@@ -186,7 +191,9 @@ func (t *Trader) CloseOrdersAndPositions() {
|
|||||||
if position.Symbol() == t.Symbol {
|
if position.Symbol() == t.Symbol {
|
||||||
t.Log.Printf("Closing position: %v units, $%.2f PL", position.Units(), position.PL())
|
t.Log.Printf("Closing position: %v units, $%.2f PL", position.Units(), position.PL())
|
||||||
position.Close()
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user