diff --git a/config.yml b/config.yml index 955b49d..cfb9a17 100644 --- a/config.yml +++ b/config.yml @@ -2,9 +2,9 @@ #バックテストの基本設定 -assetName: "AVAXUSDT" -duration: "1h" -start: "20220101" -end: "20230101" +assetName: "SOLUSDT" +duration: "15m" +start: "" +end: "" simpleInterest: false positionPersentage: 0.9 \ No newline at end of file diff --git a/go.mod b/go.mod index defa48a..b91c445 100755 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/ramya-rao-a/go-outline v0.0.0-20210608161538-9736a4bde949 // indirect github.com/rivo/uniseg v0.4.6 // indirect github.com/rocketlaunchr/dataframe-go v0.0.0-20211025052708-a1030444159b // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/crypto v0.18.0 // indirect diff --git a/go.sum b/go.sum index 203d5e6..9adcec7 100755 --- a/go.sum +++ b/go.sum @@ -266,6 +266,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/sandertv/go-formula/v2 v2.0.0-alpha.7/go.mod h1:Ag4V2fiOHWXct3SraXNN3dFzFtyu9vqBfrjfYWMGLhE= github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= diff --git a/pkg/analytics/profit_and_loss.go b/pkg/analytics/profit_and_loss.go index 0193722..78c8166 100644 --- a/pkg/analytics/profit_and_loss.go +++ b/pkg/analytics/profit_and_loss.go @@ -381,7 +381,7 @@ func PLSlice(s *execute.SignalEvents) []float64 { } for _, signal := range s.Signals { - if signal.Side != "BUY" && signal.Side != "SELL" { + if signal.Side != "BUY" && signal.Side != "SELL" && signal.Side != "CLOSE" { return nil } if signal.Side == "BUY" { @@ -390,7 +390,7 @@ func PLSlice(s *execute.SignalEvents) []float64 { if sellPrice != 0 { pl = append(pl, (sellPrice-buyPrice)*signal.Size) // reset the sell price - sellPrice = 0 + buyPrice = 0 } } if signal.Side == "SELL" { @@ -399,11 +399,10 @@ func PLSlice(s *execute.SignalEvents) []float64 { if buyPrice != 0 { pl = append(pl, (sellPrice-buyPrice)*signal.Size) // reset the buy price - buyPrice = 0 + sellPrice = 0 } } } - return pl } @@ -422,7 +421,7 @@ func TotalProfitSlice(s *execute.SignalEvents) []float64 { } for _, signal := range s.Signals { - if signal.Side != "BUY" && signal.Side != "SELL" { + if signal.Side != "BUY" && signal.Side != "SELL" && signal.Side != "CLOSE" { return nil } if signal.Side == "BUY" { diff --git a/pkg/analytics/sharp_ratio.go b/pkg/analytics/sharp_ratio.go index 4650756..e874bdc 100755 --- a/pkg/analytics/sharp_ratio.go +++ b/pkg/analytics/sharp_ratio.go @@ -23,23 +23,6 @@ func stdDev(data []float64) float64 { variance := sqDiffSum / float64(len(data)-1) return math.Sqrt(variance) } - -// func calculateReturns(s *execute.SignalEvents) []float64 { -// var returns []float64 -// var buyPrice float64 - -// for _, signal := range s.Signals { -// if signal.Side == "BUY" { -// buyPrice = signal.Price -// } else if signal.Side == "SELL" && buyPrice != 0 { -// returns = append(returns, (signal.Price-buyPrice)/buyPrice) -// buyPrice = 0 // Reset buy price after a sell -// } -// } - -// return returns -// } - func SharpeRatio(s *execute.SignalEvents, riskFreeRate float64) float64 { if s == nil { diff --git a/pkg/analytics/total_trade_counts.go b/pkg/analytics/total_trade_counts.go index b1753dc..c0d514c 100644 --- a/pkg/analytics/total_trade_counts.go +++ b/pkg/analytics/total_trade_counts.go @@ -70,7 +70,7 @@ func LongWinningTrades(s *execute.SignalEvents) int { for _, signal := range s.Signals { if signal.Side == "BUY" { buyPrice = signal.Price - } else if signal.Side == "SELL" && buyPrice != 0 { + } else if signal.Side == "CLOSE" && buyPrice != 0 { if signal.Price > buyPrice { winningTrades++ } @@ -92,7 +92,7 @@ func LongLosingTrades(s *execute.SignalEvents) int { for _, signal := range s.Signals { if signal.Side == "BUY" { buyPrice = signal.Price - } else if signal.Side == "SELL" && buyPrice != 0 { + } else if signal.Side == "CLOSE" && buyPrice != 0 { if signal.Price < buyPrice { losingTrades++ } @@ -113,7 +113,7 @@ func ShortWinningTrades(s *execute.SignalEvents) int { for _, signal := range s.Signals { if signal.Side == "SELL" { sellPrice = signal.Price - } else if signal.Side == "BUY" && sellPrice != 0 { + } else if signal.Side == "CLOSE" && sellPrice != 0 { if signal.Price < sellPrice { winningTrades++ } @@ -134,7 +134,7 @@ func ShortLosingTrades(s *execute.SignalEvents) int { for _, signal := range s.Signals { if signal.Side == "SELL" { sellPrice = signal.Price - } else if signal.Side == "BUY" && sellPrice != 0 { + } else if signal.Side == "CLOSE" && sellPrice != 0 { if signal.Price > sellPrice { losingTrades++ } @@ -235,11 +235,12 @@ func AverageWinningHoldingBars(s *execute.SignalEvents) float64 { var totalBars int var winningTrades int var buyPrice float64 + var sellPrice float64 for i, signal := range s.Signals { if signal.Side == "BUY" { buyPrice = signal.Price - } else if signal.Side == "SELL" && buyPrice != 0 { + } else if signal.Side == "CLOSE" && buyPrice != 0 { if signal.Price > buyPrice { // Find the corresponding buy signal for j := i - 1; j >= 0; j-- { @@ -256,6 +257,25 @@ func AverageWinningHoldingBars(s *execute.SignalEvents) float64 { } buyPrice = 0 // Reset buy price after a sell } + if signal.Side == "SELL" { // Assign sellPrice when signal is SELL + sellPrice = signal.Price + } else if signal.Side == "CLOSE" && sellPrice != 0 { + if signal.Price < sellPrice { // Count as a winning trade if signal price is lower than sell price + // Find the corresponding sell signal + for j := i - 1; j >= 0; j-- { + if s.Signals[j].Side == "SELL" { // Change the condition to SELL + // Calculate the number of bars for this trade + // Use the ConvertDuration function to get the bar period in minutes + barPeriod := ConvertDuration(signal.Duration) + bars := int(signal.Time.Sub(s.Signals[j].Time).Minutes() / barPeriod) + totalBars += bars + winningTrades++ + break + } + } + } + sellPrice = 0 // Reset sell price after a close + } } if winningTrades == 0 { @@ -273,11 +293,12 @@ func AverageLosingHoldingBars(s *execute.SignalEvents) float64 { var totalBars int var losingTrades int var buyPrice float64 + var sellPrice float64 for i, signal := range s.Signals { if signal.Side == "BUY" { buyPrice = signal.Price - } else if signal.Side == "SELL" && buyPrice != 0 { + } else if signal.Side == "CLOSE" && buyPrice != 0 { if signal.Price < buyPrice { // Find the corresponding buy signal for j := i - 1; j >= 0; j-- { @@ -294,6 +315,25 @@ func AverageLosingHoldingBars(s *execute.SignalEvents) float64 { } buyPrice = 0 // Reset buy price after a sell } + if signal.Side == "SELL" { // Assign sellPrice when signal is SELL + sellPrice = signal.Price + } else if signal.Side == "CLOSE" && sellPrice != 0 { + if signal.Price > sellPrice { // Count as a losing trade if signal price is higher than sell price + // Find the corresponding sell signal + for j := i - 1; j >= 0; j-- { + if s.Signals[j].Side == "SELL" { // Change the condition to SELL + // Calculate the number of bars for this trade + // Use the ConvertDuration function to get the bar period in minutes + barPeriod := ConvertDuration(signal.Duration) + bars := int(signal.Time.Sub(s.Signals[j].Time).Minutes() / barPeriod) + totalBars += bars + losingTrades++ + break + } + } + } + sellPrice = 0 // Reset sell price after a close + } } if losingTrades == 0 { @@ -308,11 +348,12 @@ func MaxWinCount(s *execute.SignalEvents) int { } var maxWinStreak, winStreak int var buyPrice float64 + var sellPrice float64 for _, signal := range s.Signals { if signal.Side == "BUY" { buyPrice = signal.Price - } else if signal.Side == "SELL" && buyPrice != 0 { + } else if signal.Side == "CLOSE" && buyPrice != 0 { if signal.Price > buyPrice { winStreak++ if winStreak > maxWinStreak { @@ -321,7 +362,20 @@ func MaxWinCount(s *execute.SignalEvents) int { } else { winStreak = 0 } - buyPrice = 0 // Reset buy price after a sell + buyPrice = 0 + } + if signal.Side == "SELl" { + sellPrice = signal.Price + } else if signal.Side == "CLOSE" && sellPrice != 0 { + if signal.Price < sellPrice { + winStreak++ + if winStreak > maxWinStreak { + maxWinStreak = winStreak + } + } else { + winStreak = 0 + } + sellPrice = 0 // Reset buy price after a sell } } @@ -334,11 +388,12 @@ func MaxLoseCount(s *execute.SignalEvents) int { } var maxLoseStreak, loseStreak int var buyPrice float64 + var sellPrice float64 for _, signal := range s.Signals { if signal.Side == "BUY" { buyPrice = signal.Price - } else if signal.Side == "SELL" && buyPrice != 0 { + } else if signal.Side == "CLOSE" && buyPrice != 0 { if signal.Price < buyPrice { loseStreak++ if loseStreak > maxLoseStreak { @@ -347,7 +402,20 @@ func MaxLoseCount(s *execute.SignalEvents) int { } else { loseStreak = 0 } - buyPrice = 0 // Reset buy price after a sell + buyPrice = 0 + } + if signal.Side == "SELL" { + sellPrice = signal.Price + } else if signal.Side == "CLOSE" && sellPrice != 0 { + if signal.Price > sellPrice { + loseStreak++ + if loseStreak > maxLoseStreak { + maxLoseStreak = loseStreak + } + } else { + loseStreak = 0 + } + sellPrice = 0 } } diff --git a/pkg/analytics/winrate.go b/pkg/analytics/winrate.go index 7044737..66e0000 100755 --- a/pkg/analytics/winrate.go +++ b/pkg/analytics/winrate.go @@ -70,7 +70,7 @@ func WinRate(s *execute.SignalEvents) float64 { currentSignal := s.Signals[i] nextSignal := s.Signals[i+1] - if currentSignal.Side == "BUY" && nextSignal.Side == "SELL" { + if currentSignal.Side == "BUY" && nextSignal.Side == "CLOSE" { if nextSignal.Price > currentSignal.Price { profitCount++ } else if nextSignal.Price < currentSignal.Price { @@ -98,7 +98,7 @@ func ShortWinRate(s *execute.SignalEvents) float64 { currentSignal := s.Signals[i] nextSignal := s.Signals[i+1] - if currentSignal.Side == "SELL" && nextSignal.Side == "BUY" { + if currentSignal.Side == "SELL" && nextSignal.Side == "CLOSE" { if nextSignal.Price < currentSignal.Price { profitCount++ } else if nextSignal.Price > currentSignal.Price { diff --git a/pkg/execute/signal.go b/pkg/execute/signal.go index 341eb6b..d4183bd 100755 --- a/pkg/execute/signal.go +++ b/pkg/execute/signal.go @@ -141,24 +141,15 @@ func (s *SignalEvents) CanSell(t time.Time) bool { } return false } -func (s *SignalEvents) CanLongClose(t time.Time) bool { - lenSignals := len(s.Signals) - - lastSignal := s.Signals[lenSignals-1] - if lastSignal.Side == "BUY" && t.After(lastSignal.Time) { - return true - } - return false -} -func (s *SignalEvents) CanShortClose(t time.Time) bool { +func (s *SignalEvents) CanClose(t time.Time) bool { lenSignals := len(s.Signals) if lenSignals == 0 { return false } lastSignal := s.Signals[lenSignals-1] - if lastSignal.Side == "SELL" && t.After(lastSignal.Time) { + if lastSignal.Side == "SELL" || lastSignal.Side == "BUY" && t.After(lastSignal.Time) { return true } return false @@ -222,7 +213,7 @@ func (s *SignalEvents) Sell(signalId uuid.UUID, strategyName string, assetName s func (s *SignalEvents) Close(signalId uuid.UUID, strategyName string, assetName string, duration string, date time.Time, price, size float64, accountBalance float64, save bool) bool { - if s.CanLongClose(date) || s.CanShortClose(date) { + if s.CanClose(date) { signalEvent := SignalEvent{ SignalId: signalId, diff --git a/pkg/strategey/buy&hold.go b/pkg/strategey/buy&hold.go index f42218d..5b9460a 100644 --- a/pkg/strategey/buy&hold.go +++ b/pkg/strategey/buy&hold.go @@ -32,10 +32,10 @@ func BuyAndHoldingStrategy(account *trader.Account) (profit float64, multiple fl } // account := trader.NewAccount(1000) - buySize := account.TradeSize(1) / close[0] + buySize := account.TradeSize(1) account.HolderBuy(close[0], buySize) - account.Exit(close[len(close)-1]) + account.Exit(close[lenCandles-1]) profit = account.Balance - initialBalance multiple = account.Balance / initialBalance diff --git a/pkg/strategey/ema_rsi.go b/pkg/strategey/ema_rsi.go index 4ce732f..d3b26b6 100644 --- a/pkg/strategey/ema_rsi.go +++ b/pkg/strategey/ema_rsi.go @@ -65,11 +65,12 @@ func (df *DataFrameCandle) EmaRsiStrategy(period1 int, account *trader.Account, isBuyHolding = true } else { - buySize = account.TradeSize(riskSize) / df.Candles[i].Close + buyPrice = c[i] + buySize = account.TradeSize(riskSize) / c[i] buySignalId = uuid.New() accountBalance := account.GetBalance() - if account.Entry(df.Candles[i].Close, buySize) { + if account.Entry(buyPrice, buySize) { signalEvents.Buy(buySignalId, StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, buySize, accountBalance, false) isSellHolding = false isBuyHolding = true @@ -97,17 +98,16 @@ func (df *DataFrameCandle) EmaRsiStrategy(period1 int, account *trader.Account, sellSize = account.SimpleTradeSize(1) sellPrice = c[i] sellSignalId = uuid.New() - accountBalance := account.GetBalance() - signalEvents.Sell(sellSignalId, StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, sellSize, accountBalance, false) + signalEvents.Sell(sellSignalId, StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, sellSize, account.GetBalance(), false) isBuyHolding = false isSellHolding = true } else { - sellSize = account.TradeSize(riskSize) / df.Candles[i].Close sellPrice = c[i] + sellSize = account.TradeSize(riskSize) / c[i] + sellSignalId = uuid.New() - accountBalance := account.GetBalance() - if account.Entry(df.Candles[i].Close, sellSize) { - signalEvents.Sell(sellSignalId, StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, sellSize, accountBalance, false) + if account.Entry(sellPrice, sellSize) { + signalEvents.Sell(sellSignalId, StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, sellSize, account.GetBalance(), false) isBuyHolding = false isSellHolding = true @@ -118,13 +118,11 @@ func (df *DataFrameCandle) EmaRsiStrategy(period1 int, account *trader.Account, if (ema1[i-1] > c[i-1] && ema1[i] <= c[i] || (c[i] <= buyPrice*longSlRatio)) && isSellHolding { if simple { - accountBalance := account.GetBalance() - signalEvents.Close(sellSignalId, StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, sellSize, accountBalance, false) + signalEvents.Close(sellSignalId, StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, sellSize, account.GetBalance(), false) isSellHolding = false } else { if account.Exit(c[i]) { - accountBalance := account.GetBalance() - signalEvents.Close(sellSignalId, StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, sellSize, accountBalance, false) + signalEvents.Close(sellSignalId, StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, sellSize, account.GetBalance(), false) isSellHolding = false sellSize = 0 } diff --git a/pkg/strategey/strategy.go b/pkg/strategey/strategy.go index 968f149..3e72645 100755 --- a/pkg/strategey/strategy.go +++ b/pkg/strategey/strategy.go @@ -441,7 +441,7 @@ func Result(s *execute.SignalEvents) { fmt.Println("1トレードの最大損失と日時", ml, mt) // fmt.Println("バルサラの破産確率", analytics.BalsaraAxum(s)) - fmt.Println(s) + // fmt.Println(s) fmt.Println("--------------------------------------------") fmt.Println("") diff --git a/pkg/trader/acount.go b/pkg/trader/acount.go index c46e48c..c50f121 100755 --- a/pkg/trader/acount.go +++ b/pkg/trader/acount.go @@ -33,11 +33,13 @@ func (a *Account) Entry(price, size float64) bool { a.PositionSize = size return true } + +// このコードに問題あり func (a *Account) Exit(price float64) bool { if a.PositionSize <= 0 { return false } - a.Balance += price * a.PositionSize + a.Balance += a.PositionSize * price a.PositionSize = 0.0 return true }