diff --git a/config.yml b/config.yml index 572919d..b7a5459 100644 --- a/config.yml +++ b/config.yml @@ -2,9 +2,9 @@ #バックテストの基本設定 -assetName: "SOLUSDT" -duration: "30m" -start: "20200801" -end: "20230930" -simpleInterest: false +assetName: "AVAXUSDT" +duration: "15m" +start: "20221201" +end: "20231230" +simpleInterest: true positionPersentage: 0.9 \ No newline at end of file diff --git a/main.go b/main.go index 6b96d41..ab39f39 100644 --- a/main.go +++ b/main.go @@ -15,9 +15,9 @@ func main() { // strategey.RunDonchainOptimize() // strategey.RunBetterRsiOptimize() - // strategey.DonchainBacktest() - // strategey.EmaBacktest() - // strategey.SuperTrendBacktest() + strategey.DonchainBacktest() + strategey.EmaBacktest() + strategey.SuperTrendBacktest() strategey.RSIBetterBacktest() // strategey.EmaBacktest() diff --git a/pkg/analytics/drawdown.go b/pkg/analytics/drawdown.go index 14e17fa..63ae20d 100755 --- a/pkg/analytics/drawdown.go +++ b/pkg/analytics/drawdown.go @@ -1,7 +1,6 @@ package analytics import ( - "fmt" "math" "sort" dbquery "v1/pkg/data/query" @@ -92,7 +91,7 @@ func MaxDrawdownPercent(s *execute.SignalEvents) float64 { // TODO: Handle other sides if necessary continue } - fmt.Println(signal.AccountBalance) + if signal.AccountBalance > peak { // Update peak value if account balance is higher peak = signal.AccountBalance } diff --git a/pkg/analytics/profit_and_loss.go b/pkg/analytics/profit_and_loss.go index b1d339a..d53d2a7 100644 --- a/pkg/analytics/profit_and_loss.go +++ b/pkg/analytics/profit_and_loss.go @@ -181,7 +181,7 @@ func FinalBalance(s *execute.SignalEvents) (float64, float64) { return 0, 0 } - finalBlanceValue := accountBalance + LongNetProfit(s) + finalBlanceValue := accountBalance + TotalNetProfit(s) finalBlanceRatio := finalBlanceValue / accountBalance return finalBlanceValue, finalBlanceRatio @@ -272,12 +272,13 @@ func GainPainRatio(s *execute.SignalEvents) float64 { } -func ReturnProfitLoss(s *execute.SignalEvents) []float64 { +func PLSlice(s *execute.SignalEvents) []float64 { if s == nil { return nil } var pl []float64 // profit or loss slice var buyPrice float64 + var sellPrice float64 if s.Signals == nil || len(s.Signals) == 0 { return nil @@ -289,9 +290,21 @@ func ReturnProfitLoss(s *execute.SignalEvents) []float64 { } if signal.Side == "BUY" { buyPrice = signal.Price - } else if signal.Side == "SELL" && buyPrice != 0 { - pl = append(pl, (signal.Price-buyPrice)*signal.Size) // append the profit or loss of the trade to the slice - buyPrice = 0 // Reset buy price after a sell + // if there is a previous sell price, calculate the profit or loss + if sellPrice != 0 { + pl = append(pl, (sellPrice-buyPrice)*signal.Size) + // reset the sell price + sellPrice = 0 + } + } + if signal.Side == "SELL" { + sellPrice = signal.Price + // if there is a previous buy price, calculate the profit or loss + if buyPrice != 0 { + pl = append(pl, (sellPrice-buyPrice)*signal.Size) + // reset the buy price + buyPrice = 0 + } } } diff --git a/pkg/analytics/winrate.go b/pkg/analytics/winrate.go index 5532612..6d23c6d 100755 --- a/pkg/analytics/winrate.go +++ b/pkg/analytics/winrate.go @@ -7,6 +7,58 @@ type Winrate_arg struct { Totall_trade int } +// func WinRate(s *execute.SignalEvents) float64 { + +// if s == nil { +// return 0.0 +// } +// var profitCount, lossCount float64 + +// for i := 0; i < len(s.Signals)-1; i += 2 { +// buySignal := s.Signals[i] +// sellSignal := s.Signals[i+1] + +// if sellSignal.Price > buySignal.Price { +// profitCount++ +// } else if sellSignal.Price < buySignal.Price { +// lossCount++ +// } +// } + +// totalCount := profitCount + lossCount +// if totalCount == 0 { +// return 0 +// } + +// return profitCount / totalCount +// } + +// func ShortWinRate(s *execute.SignalEvents) float64 { + +// if s == nil { +// return 0.0 +// } +// var profitCount, lossCount float64 + +// for i := 0; i < len(s.Signals)-1; i += 2 { +// sellSignal := s.Signals[i] +// buySignal := s.Signals[i+1] + +// if buySignal.Price < sellSignal.Price { +// profitCount++ +// } else if buySignal.Price > sellSignal.Price { +// lossCount++ +// } +// } + +// totalCount := profitCount + lossCount +// if totalCount == 0 { +// return 0 +// } + +// return profitCount / totalCount +// } + func WinRate(s *execute.SignalEvents) float64 { if s == nil { @@ -14,23 +66,25 @@ func WinRate(s *execute.SignalEvents) float64 { } var profitCount, lossCount float64 - for i := 0; i < len(s.Signals)-1; i += 2 { - buySignal := s.Signals[i] - sellSignal := s.Signals[i+1] + for i := 0; i < len(s.Signals)-1; i++ { + currentSignal := s.Signals[i] + nextSignal := s.Signals[i+1] - if sellSignal.Price > buySignal.Price { - profitCount++ - } else if sellSignal.Price < buySignal.Price { - lossCount++ + if currentSignal.Side == "BUY" && nextSignal.Side == "SELL" { + if nextSignal.Price > currentSignal.Price { + profitCount++ + } else if nextSignal.Price < currentSignal.Price { + lossCount++ + } } } - totalCount := profitCount + lossCount - if totalCount == 0 { + longCount := profitCount + lossCount + if longCount == 0 { return 0 } - return profitCount / totalCount + return profitCount / longCount } func ShortWinRate(s *execute.SignalEvents) float64 { @@ -40,23 +94,25 @@ func ShortWinRate(s *execute.SignalEvents) float64 { } var profitCount, lossCount float64 - for i := 0; i < len(s.Signals)-1; i += 2 { - sellSignal := s.Signals[i] - buySignal := s.Signals[i+1] + for i := 0; i < len(s.Signals)-1; i++ { + currentSignal := s.Signals[i] + nextSignal := s.Signals[i+1] - if buySignal.Price < sellSignal.Price { - profitCount++ - } else if buySignal.Price > sellSignal.Price { - lossCount++ + if currentSignal.Side == "SELL" && nextSignal.Side == "BUY" { + if nextSignal.Price < currentSignal.Price { + profitCount++ + } else if nextSignal.Price > currentSignal.Price { + lossCount++ + } } } - totalCount := profitCount + lossCount - if totalCount == 0 { + shortCount := profitCount + lossCount + if shortCount == 0 { return 0 } - return profitCount / totalCount + return profitCount / shortCount } func TotalWinRate(s *execute.SignalEvents) float64 { diff --git a/pkg/execute/signal.go b/pkg/execute/signal.go index b8ec282..c514220 100755 --- a/pkg/execute/signal.go +++ b/pkg/execute/signal.go @@ -125,7 +125,7 @@ func (s *SignalEvents) CanBuy(t time.Time) bool { return false } -func (s *SignalEvents) CanClose(t time.Time) bool { +func (s *SignalEvents) CanLongClose(t time.Time) bool { lenSignals := len(s.Signals) if lenSignals == 0 { return false @@ -141,7 +141,7 @@ func (s *SignalEvents) CanClose(t time.Time) bool { func (s *SignalEvents) CanSell(t time.Time) bool { lenSignals := len(s.Signals) if lenSignals == 0 { - return false + return true } lastSignal := s.Signals[lenSignals-1] @@ -151,10 +151,23 @@ func (s *SignalEvents) CanSell(t time.Time) bool { return false } -func (s *SignalEvents) Buy(strategyName string, assetName string, duration string, date time.Time, price, size float64, accountBalance float64, save bool) bool { +func (s *SignalEvents) CanShortClose(t time.Time) bool { + lenSignals := len(s.Signals) + if lenSignals == 0 { + return false + } + + lastSignal := s.Signals[lenSignals-1] + if lastSignal.Side == "SELL" && lastSignal.Time.Before(t) { + return true + } + return false +} + +func (s *SignalEvents) Buy(strategyName string, assetName string, duration string, date time.Time, price, size float64, accountBalance float64, save bool) (bool, uuid.UUID) { if !s.CanBuy(date) { - return false + return false, uuid.UUID{} } signalId := uuid.New() @@ -178,14 +191,13 @@ func (s *SignalEvents) Buy(strategyName string, assetName string, duration strin // } s.Signals = append(s.Signals, signalEvent) - return true + return true, signalId } -func (s *SignalEvents) Sell(strategyName string, assetName string, duration string, date time.Time, price, size float64, accountBalance float64, save bool) bool { +func (s *SignalEvents) Sell(strategyName string, assetName string, duration string, date time.Time, price, size float64, accountBalance float64, save bool) (bool, uuid.UUID) { if !s.CanSell(date) { - - return false + return false, uuid.UUID{} } signalId := uuid.New() signalEvent := SignalEvent{ @@ -206,23 +218,23 @@ func (s *SignalEvents) Sell(strategyName string, assetName string, duration stri // } s.Signals = append(s.Signals, signalEvent) - return true + return true, signalId } -func (s *SignalEvents) Close(strategyName string, assetName string, duration string, date time.Time, price, size float64, accountBalance float64, save bool) bool { +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.CanClose(date) { + if !s.CanLongClose(date) || !s.CanShortClose(date) { return false } - signalId := uuid.New() + signalEvent := SignalEvent{ SignalId: signalId, Time: date, StrategyName: strategyName, AssetName: assetName, Duration: duration, - Side: "SELL", + Side: "CLOSE", Price: price, Size: size, AccountBalance: accountBalance, diff --git a/pkg/management/risk/optimal_f.go b/pkg/management/risk/optimal_f.go index 61541c5..7dfb99b 100644 --- a/pkg/management/risk/optimal_f.go +++ b/pkg/management/risk/optimal_f.go @@ -6,82 +6,6 @@ import ( "v1/pkg/execute" ) -// func CalculateHPR(f float64, pl []float64) float64 { -// if len(pl) == 0 { -// return 0.0 -// } - -// maxLoss := pl[0] // 最初の損益を仮の最大損失とする -// for _, p := range pl { -// maxLoss = math.Min(maxLoss, p) // plの中で一番小さい値を最大損失とする -// } - -// if maxLoss == 0 { -// return 0.0 // 資産が0になった場合はHPRも0になる -// } - -// return 1 + f*(-pl[0]/maxLoss) // pl[0]は最初の損益 -// } - -// func CalculateTWR(f float64, pl []float64) float64 { -// if len(pl) == 0 { -// return 0.0 -// } - -// maxLoss := pl[0] // 最初の損益を仮の最大損失とする -// for _, p := range pl { -// maxLoss = math.Min(maxLoss, p) // plの中で一番小さい値を最大損失とする -// } - -// twr := 1.0 -// for _, p := range pl { -// if maxLoss != 0 { -// twr *= (1 + f*(-p/maxLoss)) // maxLossは最大損失 -// } else { -// twr = 0.0 // 資産が0になった場合はTWRも0になる -// } - -// } - -// return twr -// } - -// func OptimalF(s *execute.SignalEvents) float64 { -// maxF := 0.0 -// maxGeomMean := 0.0 - -// plSlice := analytics.ReturnProfitLoss(s) -// // maxLossTrade, _ := analytics.MaxLossTrade(s) - -// low := 0.000 -// high := 1.000 -// epsilon := 0.0001 - -// for high-low > epsilon { -// f := (low + high) / 2 -// hpr := CalculateHPR(f, plSlice) -// twr := CalculateTWR(f, plSlice) - -// geomMean := math.Pow(twr, 1/float64(len(plSlice))) - -// if geomMean > maxGeomMean { -// maxGeomMean = geomMean -// maxF = f -// } - -// // fの値を更新する -// // HPRとTWRの関数はfに対して凸関数なので、 -// // 幾何平均が最大になるfは、HPRとTWRが等しくなるfに近い -// if hpr > twr { -// low = f -// } else { -// high = f -// } -// } - -// return maxF -// } - func CalculateHPR(f float64, pl []float64) float64 { if len(pl) == 0 { return 0.0 @@ -130,7 +54,8 @@ func OptimalF(s *execute.SignalEvents) float64 { maxF := 0.0 maxGeomMean := 0.0 - plSlice := analytics.ReturnProfitLoss(s) + plSlice := analytics.PLSlice(s) + // fmt.Println(plSlice) // maxLossTrade, _ := analytics.MaxLossTrade(s) for f := 0.01; f < 1; f += 0.01 { diff --git a/pkg/strategey/strategy.go b/pkg/strategey/strategy.go index 0b4f4d9..da7fdce 100755 --- a/pkg/strategey/strategy.go +++ b/pkg/strategey/strategy.go @@ -377,10 +377,20 @@ func Result(s *execute.SignalEvents) { name := n.StrategyName + "_" + n.AssetName + "_" + n.Duration fmt.Println("🌟", name, "🌟") + fmt.Println("") + fmt.Println("") + fmt.Println("🔮コア指標🔮") + fmt.Println("") fmt.Println("初期残高", initialBalance) fmt.Println("最終残高", l, "USD", lr, "倍") fmt.Println("オプティマルF", risk.OptimalF(s)) - + fmt.Println("ソルティノレシオ", analytics.SortinoRatio(s, 0.02)) + fmt.Println("SQN", analytics.SQN(s)) + fmt.Println("期待値", analytics.ExpectedValue(s), "USD") + fmt.Println("") + fmt.Println("") + fmt.Println("🕵方向別指標🕵") + fmt.Println("") fmt.Println("ロング利益", analytics.LongProfit(s)) fmt.Println("ロング損失", analytics.Loss(s)) fmt.Println("ショート利益", analytics.ShortProfit(s)) @@ -389,13 +399,17 @@ func Result(s *execute.SignalEvents) { fmt.Println("ショート勝率", analytics.ShortWinRate(s)*100, "%") fmt.Println("ロング純利益", analytics.LongNetProfit(s)) fmt.Println("ショート純利益", analytics.ShortNetProfit(s)) + fmt.Println("") + fmt.Println("") + + fmt.Println("📊トータル指標📊") + fmt.Println("") fmt.Println("トータル純利益", analytics.TotalNetProfit(s)) - fmt.Println("トータル勝率", analytics.TotalWinRate(s)) + fmt.Println("トータル勝率", analytics.TotalWinRate(s)*100, "%") fmt.Println("プロフィットファクター", analytics.ProfitFactor(s)) fmt.Println("最大ドローダウン金額", analytics.MaxDrawdownUSD(s), "USD ") fmt.Println("最大ドローダウン", dd*100, "% ") fmt.Println("シャープレシオ", analytics.SharpeRatio(s, 0.02)) - fmt.Println("ソルティノレシオ", analytics.SortinoRatio(s, 0.02)) fmt.Println("トータルトレード回数", analytics.TotalTrades(s)) fmt.Println("勝ちトレード回数", analytics.WinningTrades(s)) fmt.Println("負けトレード回数", analytics.LosingTrades(s)) @@ -404,8 +418,6 @@ func Result(s *execute.SignalEvents) { fmt.Println("ペイオフレシオ", analytics.PayOffRatio(s)) fmt.Println("ゲインペインレシオ", analytics.GainPainRatio(s)) fmt.Println("リターンドローダウンレシオ", analytics.ReturnDDRattio(s)) - fmt.Println("SQN", analytics.SQN(s)) - fmt.Println("期待値", analytics.ExpectedValue(s), "USD") fmt.Println("最大連勝数", analytics.MaxWinCount(s)) fmt.Println("最大連敗数", analytics.MaxLoseCount(s)) fmt.Println("勝ちトレードの平均バー数", analytics.AverageWinningHoldingBars(s)) @@ -417,6 +429,9 @@ func Result(s *execute.SignalEvents) { // fmt.Println(s) fmt.Println("--------------------------------------------") + fmt.Println("") + fmt.Println("") + } func GetCandleData_old(assetName string, duration string) (*DataFrameCandle, error) {