mirror of
https://github.com/lukewilson2002/autotrader.git
synced 2025-06-15 08:23:51 +00:00
369 lines
11 KiB
Go
369 lines
11 KiB
Go
package autotrader
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const testDataCSV = `date,open,high,low,close,volume
|
|
2022-01-01,1.1,1.2,1.0,1.15,100
|
|
2022-01-02,1.15,1.2,1.1,1.2,110
|
|
2022-01-03,1.2,1.3,1.15,1.25,120
|
|
2022-01-04,1.25,1.3,1.0,1.1,130
|
|
2022-01-05,1.1,1.2,1.0,1.15,110
|
|
2022-01-06,1.15,1.2,1.1,1.2,120
|
|
2022-01-07,1.2,1.3,1.15,1.25,140
|
|
2022-01-08,1.25,1.3,1.0,1.1,150
|
|
2022-01-09,1.1,1.4,1.0,1.3,220`
|
|
|
|
func newTestingDataframe() *Frame {
|
|
data, err := DataFrameFromCSVReaderLayout(strings.NewReader(testDataCSV), DataCSVLayout{
|
|
LatestFirst: false,
|
|
DateFormat: "2006-01-02",
|
|
Date: "date",
|
|
Open: "open",
|
|
High: "high",
|
|
Low: "low",
|
|
Close: "close",
|
|
Volume: "volume",
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return data
|
|
}
|
|
|
|
func TestBacktestingBrokerCandles(t *testing.T) {
|
|
data := newTestingDataframe()
|
|
broker := NewTestBroker(nil, data, 0, 0, 0, 0)
|
|
|
|
candles, err := broker.Candles("EUR_USD", "D", 3)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if candles.Len() != 1 {
|
|
t.Errorf("Expected 1 candle, got %d", candles.Len())
|
|
}
|
|
if candles.Date(0) != time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) {
|
|
t.Errorf("Expected first candle to be 2022-01-01, got %s", candles.Date(0))
|
|
}
|
|
|
|
broker.Advance()
|
|
candles, err = broker.Candles("EUR_USD", "D", 3)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if candles.Len() != 2 {
|
|
t.Errorf("Expected 2 candles, got %d", candles.Len())
|
|
}
|
|
if candles.Date(1) != time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC) {
|
|
t.Errorf("Expected second candle to be 2022-01-02, got %s", candles.Date(1))
|
|
}
|
|
|
|
for i := 0; i < 7; i++ { // 6 because we want to call broker.Candles 9 times total
|
|
broker.Advance()
|
|
candles, err = broker.Candles("EUR_USD", "D", 5)
|
|
if err != nil && err != ErrEOF && i != 6 { // Allow ErrEOF on last iteration.
|
|
t.Fatalf("Got an error on iteration %d: %v (called Advance() %d times)", i, err, broker.CandleIndex()+1)
|
|
}
|
|
if candles == nil {
|
|
t.Errorf("Candles is nil on iteration %d", i+1)
|
|
}
|
|
}
|
|
if candles.Len() != 5 {
|
|
t.Errorf("Expected 5 candles, got %d", candles.Len())
|
|
}
|
|
if candles.Close(4) != 1.3 {
|
|
t.Errorf("Expected the last closing price to be 1.3, got %f", candles.Close(4))
|
|
}
|
|
}
|
|
|
|
func TestBacktestingBrokerMarketOrders(t *testing.T) {
|
|
data := newTestingDataframe()
|
|
broker := NewTestBroker(nil, data, 100_000, 50, 0, 0)
|
|
broker.Slippage = 0
|
|
|
|
timeBeforeOrder := time.Now()
|
|
order, err := broker.Order(Market, "EUR_USD", 50_000, 0, 0, 0) // Buy 50,000 USD for 1000 EUR with no stop loss or take profit
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if order == nil {
|
|
t.Fatal("Order is nil")
|
|
}
|
|
|
|
if order.Symbol() != "EUR_USD" {
|
|
t.Errorf("Expected symbol to be EUR_USD, got %s", order.Symbol())
|
|
}
|
|
if order.Units() != 50_000 {
|
|
t.Errorf("Expected units to be 50_000, got %f", order.Units())
|
|
}
|
|
if order.Price() != 1.15 {
|
|
t.Errorf("Expected order price to be 1.15 (first close), got %f", order.Price())
|
|
}
|
|
if order.Fulfilled() != true {
|
|
t.Error("Expected order to be fulfilled")
|
|
}
|
|
if order.Time().Before(timeBeforeOrder) {
|
|
t.Error("Expected order time to be after timeBeforeOrder")
|
|
}
|
|
if order.Leverage() != 50 {
|
|
t.Errorf("Expected leverage to be 50, got %f", order.Leverage())
|
|
}
|
|
if order.StopLoss() != 0 {
|
|
t.Errorf("Expected stop loss to be 0, got %f", order.StopLoss())
|
|
}
|
|
if order.TakeProfit() != 0 {
|
|
t.Errorf("Expected take profit to be 0, got %f", order.TakeProfit())
|
|
}
|
|
if order.Type() != Market {
|
|
t.Errorf("Expected order type to be MarketOrder, got %s", order.Type())
|
|
}
|
|
|
|
position := order.Position()
|
|
if position == nil {
|
|
t.Fatal("Position is nil")
|
|
}
|
|
if position.Symbol() != "EUR_USD" {
|
|
t.Errorf("Expected symbol to be EUR_USD, got %s", position.Symbol())
|
|
}
|
|
if position.Units() != 50_000 {
|
|
t.Errorf("Expected units to be 50_000, got %f", position.Units())
|
|
}
|
|
if position.EntryPrice() != 1.15 {
|
|
t.Errorf("Expected entry price to be 1.15 (first close), got %f", position.EntryPrice())
|
|
}
|
|
if position.Time().Before(timeBeforeOrder) {
|
|
t.Error("Expected position time to be after timeBeforeOrder")
|
|
}
|
|
if position.Leverage() != 50 {
|
|
t.Errorf("Expected leverage to be 50, got %f", position.Leverage())
|
|
}
|
|
if position.StopLoss() != 0 {
|
|
t.Errorf("Expected stop loss to be 0, got %f", position.StopLoss())
|
|
}
|
|
if position.TakeProfit() != 0 {
|
|
t.Errorf("Expected take profit to be 0, got %f", position.TakeProfit())
|
|
}
|
|
|
|
if !EqualApprox(broker.NAV(), 100_000) { // NAV should not change until the next candle
|
|
t.Errorf("Expected NAV to be 100_000, got %f", broker.NAV())
|
|
}
|
|
|
|
broker.Advance() // Advance broker to the next candle
|
|
if !EqualApprox(position.PL(), 2500) { // (1.2-1.15) * 50_000 = 2500
|
|
t.Errorf("Expected position PL to be 2500, got %f", position.PL())
|
|
}
|
|
if !EqualApprox(broker.NAV(), 102_500) {
|
|
t.Errorf("Expected NAV to be 102_500, got %f", broker.NAV())
|
|
}
|
|
|
|
// Test closing positions.
|
|
position.Close()
|
|
if position.Closed() != true {
|
|
t.Error("Expected position to be closed")
|
|
}
|
|
broker.Advance()
|
|
if !EqualApprox(broker.NAV(), 102_500) {
|
|
t.Errorf("Expected NAV to still be 102_500, got %f", broker.NAV())
|
|
}
|
|
if !EqualApprox(broker.PL(), 2500) {
|
|
t.Errorf("Expected broker PL to be 2500, got %f", broker.PL())
|
|
}
|
|
}
|
|
|
|
func TestBacktestingBrokerLimitOrders(t *testing.T) {
|
|
data := newTestingDataframe()
|
|
broker := NewTestBroker(nil, data, 100_000, 50, 0, 0)
|
|
broker.Slippage = 0
|
|
|
|
order, err := broker.Order(Limit, "EUR_USD", -50_000, 1.3, 1.35, 1.1) // Sell limit 50,000 USD for 1000 EUR
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if order == nil {
|
|
t.Fatal("Order is nil")
|
|
}
|
|
if order.Price() != 1.3 {
|
|
t.Errorf("Expected order price to be 1.3, got %f", order.Price())
|
|
}
|
|
if order.Fulfilled() != false {
|
|
t.Error("Expected order to not be fulfilled")
|
|
}
|
|
|
|
broker.Advance()
|
|
broker.Advance() // Advance to the third candle where the order should be fulfilled
|
|
|
|
if order.Fulfilled() != true {
|
|
t.Error("Expected order to be fulfilled")
|
|
}
|
|
|
|
position := order.Position()
|
|
if position == nil {
|
|
t.Fatal("Position is nil")
|
|
}
|
|
if position.Closed() != false {
|
|
t.Fatal("Expected position to not be closed")
|
|
}
|
|
|
|
broker.Advance() // Advance to the fourth candle which should hit our take profit
|
|
|
|
if position.Closed() != true {
|
|
t.Fatal("Expected position to be closed")
|
|
}
|
|
if position.ClosePrice() != 1.1 {
|
|
t.Errorf("Expected position close price to be 1.1, got %f", position.ClosePrice())
|
|
}
|
|
if position.CloseType() != CloseTakeProfit {
|
|
t.Errorf("Expected position close type to be TP, got %s", position.CloseType())
|
|
}
|
|
if !EqualApprox(position.PL(), 10_000) { // abs(1.1-1.3) * 50_000 = 10,000
|
|
t.Errorf("Expected position PL to be 10000, got %f", position.PL())
|
|
}
|
|
}
|
|
|
|
func TestBacktestingBrokerStopOrders(t *testing.T) {
|
|
data := newTestingDataframe()
|
|
broker := NewTestBroker(nil, data, 100_000, 50, 0, 0)
|
|
broker.Slippage = 0
|
|
|
|
order, err := broker.Order(Stop, "EUR_USD", 50_000, 1.2, 1, 1.3) // Buy stop 50,000 EUR for 1000 USD
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if order == nil {
|
|
t.Fatal("Order is nil")
|
|
}
|
|
if order.Price() != 1.2 {
|
|
t.Errorf("Expected order price to be 1.2, got %f", order.Price())
|
|
}
|
|
if order.Fulfilled() != false {
|
|
t.Error("Expected order to not be fulfilled")
|
|
}
|
|
|
|
broker.Advance() // Advance to the second candle where the order should be fulfilled
|
|
|
|
if order.Fulfilled() != true {
|
|
t.Error("Expected order to be fulfilled")
|
|
}
|
|
|
|
position := order.Position()
|
|
if position == nil {
|
|
t.Fatal("Position is nil")
|
|
}
|
|
if position.Closed() != false {
|
|
t.Fatal("Expected position to not be closed")
|
|
}
|
|
|
|
broker.Advance() // Advance to the third candle which should hit our take profit
|
|
|
|
if position.Closed() != true {
|
|
t.Fatal("Expected position to be closed")
|
|
}
|
|
if position.ClosePrice() != 1.3 {
|
|
t.Errorf("Expected position close price to be 1.3, got %f", position.ClosePrice())
|
|
}
|
|
if position.CloseType() != CloseTakeProfit {
|
|
t.Errorf("Expected position close type to be TP, got %s", position.CloseType())
|
|
}
|
|
if !EqualApprox(position.PL(), 5000) { // (1.3-1.2) * 50_000 = 5000
|
|
t.Errorf("Expected position PL to be 5000, got %f", position.PL())
|
|
}
|
|
}
|
|
|
|
func TestBacktestingBrokerStopLossTakeProfit(t *testing.T) {
|
|
data := newTestingDataframe()
|
|
broker := NewTestBroker(nil, data, 100_000, 50, 0, 0)
|
|
broker.Slippage = 0
|
|
|
|
order, err := broker.Order(Market, "", 10_000, 0, 1.05, 1.25)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if order == nil {
|
|
t.Fatal("Order is nil")
|
|
}
|
|
if order.StopLoss() != 1.05 {
|
|
t.Errorf("Expected stop loss to be 1.1, got %f", order.StopLoss())
|
|
}
|
|
if order.TakeProfit() != 1.25 {
|
|
t.Errorf("Expected take profit to be 1.25, got %f", order.TakeProfit())
|
|
}
|
|
|
|
position := order.Position()
|
|
if position == nil {
|
|
t.Fatal("Position is nil")
|
|
}
|
|
if position.StopLoss() != 1.05 {
|
|
t.Errorf("Expected stop loss to be 1.1, got %f", position.StopLoss())
|
|
}
|
|
if position.TakeProfit() != 1.25 {
|
|
t.Errorf("Expected take profit to be 1.25, got %f", position.TakeProfit())
|
|
}
|
|
|
|
broker.Advance()
|
|
broker.Advance() // Now we're at the third candle which hits our take profit
|
|
|
|
if position.Closed() != true {
|
|
t.Error("Expected position to be closed")
|
|
}
|
|
if position.ClosePrice() != 1.25 {
|
|
t.Errorf("Expected close price to be 1.25, got %f", position.ClosePrice())
|
|
}
|
|
if !EqualApprox(position.PL(), 1000) { // (1.25-1.15) * 10_000 = 1000
|
|
t.Errorf("Expected position PL to be 1000, got %f", position.PL())
|
|
}
|
|
|
|
broker.Advance() // 4th candle
|
|
|
|
order, err = broker.Order(Market, "", 10_000, 0, -0.2, 1.4) // Long position with trailing stop loss of 0.2.
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if order == nil {
|
|
t.Fatal("Order is nil")
|
|
}
|
|
if order.StopLoss() != 0 {
|
|
t.Errorf("Expected stop loss to be 0, got %f", order.StopLoss())
|
|
}
|
|
if order.TakeProfit() != 1.4 {
|
|
t.Errorf("Expected take profit to be 1.4, got %f", order.TakeProfit())
|
|
}
|
|
if !EqualApprox(order.TrailingStop(), 0.2) { // Orders return the distance to the trailing stop loss.
|
|
t.Errorf("Expected trailing stop to be 0.2, got %f", order.TrailingStop())
|
|
}
|
|
|
|
broker.Advance() // Cause the position to get updated.
|
|
position = order.Position()
|
|
if position == nil {
|
|
t.Fatal("Position is nil")
|
|
}
|
|
if position.Closed() {
|
|
t.Error("Expected position to be open")
|
|
}
|
|
if position.StopLoss() != 0 {
|
|
t.Errorf("Expected stop loss to be 0, got %f", position.StopLoss())
|
|
}
|
|
if position.TakeProfit() != 1.4 {
|
|
t.Errorf("Expected take profit to be 1.4, got %f", position.TakeProfit())
|
|
}
|
|
if !EqualApprox(position.TrailingStop(), 0.95) { // Positions return the actual trailing stop loss price.
|
|
t.Errorf("Expected trailing stop to be 0.95, got %f", position.TrailingStop())
|
|
}
|
|
|
|
for !position.Closed() {
|
|
broker.Advance() // Advance until position is closed.
|
|
}
|
|
|
|
if !EqualApprox(position.ClosePrice(), 1.05) {
|
|
t.Errorf("Expected close price to be 1.05, got %f", position.ClosePrice())
|
|
}
|
|
if !EqualApprox(position.PL(), -500) { // (1.05-1.1) * 10_000 = -500
|
|
t.Errorf("Expected position PL to be 1000, got %f", position.PL())
|
|
}
|
|
if position.CloseType() != CloseTrailingStop {
|
|
t.Errorf("Expected close type to be %q, got %q", CloseTrailingStop, position.CloseType())
|
|
}
|
|
}
|