From 09b67186966262d48f698cda05ec38edc2b671c7 Mon Sep 17 00:00:00 2001 From: "T.K" Date: Fri, 9 Feb 2024 04:10:00 +0900 Subject: [PATCH] =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=BC=E3=83=88=E3=83=88?= =?UTF-8?q?=E3=83=AC=E3=83=BC=E3=83=89=E3=81=AE=E3=82=A2=E3=83=8A=E3=83=AA?= =?UTF-8?q?=E3=83=86=E3=82=A3=E3=82=AF=E3=82=B9=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.yml | 9 +- main.go | 7 +- pkg/analytics/calc_winrate.go | 57 --------- pkg/analytics/drawdown.go | 108 +++++++++++++----- pkg/analytics/pay_off_ratio.go | 6 +- pkg/analytics/profit_and_loss.go | 87 +++++++++++++- .../{calc_profit_test.go => profit_test.go} | 4 +- pkg/analytics/return_dorawdown_ratio.go | 2 +- ...c_total_trade.go => total_trade_counts.go} | 0 pkg/analytics/winrate.go | 94 +++++++++++++++ pkg/config/yaml.go | 11 +- pkg/strategey/positionsize.go | 4 +- pkg/strategey/rsi_basic.go | 4 +- pkg/strategey/rsi_donchain.go | 4 +- pkg/strategey/strategy.go | 21 ++-- pkg/strategey/supertrend.go | 2 +- 16 files changed, 296 insertions(+), 124 deletions(-) delete mode 100755 pkg/analytics/calc_winrate.go rename pkg/analytics/{calc_profit_test.go => profit_test.go} (95%) rename pkg/analytics/{calc_total_trade.go => total_trade_counts.go} (100%) create mode 100755 pkg/analytics/winrate.go diff --git a/config.yml b/config.yml index 91ddada..b7fb47a 100644 --- a/config.yml +++ b/config.yml @@ -2,8 +2,9 @@ #バックテストの基本設定 -assetName: "ATOMUSDT" -duration: "5m" +assetName: "SOLUSDT" +duration: "30m" start: "20200801" -end: "20230730" -simpleInterest: true \ No newline at end of file +end: "20210930" +simpleInterest: true +positionPersentage: 0.3 \ No newline at end of file diff --git a/main.go b/main.go index 4fe1f2f..7471f1d 100644 --- a/main.go +++ b/main.go @@ -13,12 +13,11 @@ func main() { // strategey.RunEmaOptimize() // strategey.RunSTOptimize() // strategey.RunDonchainOptimize() - // strategey.RunBetterRsiOptimize() - strategey.DonchainBacktest() - strategey.EmaBacktest() - strategey.SuperTrendBacktest() + // strategey.DonchainBacktest() + // strategey.EmaBacktest() + // strategey.SuperTrendBacktest() strategey.RSIBryyrtBacktest() // strategey.EmaBacktest() diff --git a/pkg/analytics/calc_winrate.go b/pkg/analytics/calc_winrate.go deleted file mode 100755 index 69c0bba..0000000 --- a/pkg/analytics/calc_winrate.go +++ /dev/null @@ -1,57 +0,0 @@ -package analytics - -import "v1/pkg/execute" - -type Winrate_arg struct { - Totall_wintrade int - Totall_trade int -} - -// func WinRate(s *execute.SignalEvents) float64 { -// var winCount, totalCount float64 -// var buyPrice float64 - -// for _, signal := range s.Signals { -// if signal.Side == "BUY" { -// buyPrice = signal.Price -// } else if signal.Side == "SELL" { -// totalCount++ -// if signal.Price > buyPrice { -// winCount++ -// } -// buyPrice = 0 // Reset buy price after a sell -// } -// } - -// if totalCount == 0 { -// return 0 -// } - -// return winCount / totalCount -// } - -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 -} diff --git a/pkg/analytics/drawdown.go b/pkg/analytics/drawdown.go index cb35000..8df6cf1 100755 --- a/pkg/analytics/drawdown.go +++ b/pkg/analytics/drawdown.go @@ -73,8 +73,7 @@ func MaxDrawdownRatio(s *execute.SignalEvents) float64 { } return lossDrawdown } - -func MaxDrawdownUSD(s *execute.SignalEvents) float64 { +func MaxDrawdownPercent(s *execute.SignalEvents) float64 { if s == nil || s.Signals == nil || len(s.Signals) == 0 { return 0.0 } @@ -82,45 +81,98 @@ func MaxDrawdownUSD(s *execute.SignalEvents) float64 { sort.Slice(s.Signals, func(i, j int) bool { return s.Signals[i].Time.Before(s.Signals[j].Time) }) - var lossDrawdown float64 = 0.0 - var buyPrice float64 = 0.0 // Initialize buyPrice as zero - var loss float64 = 0.0 - var maxEquity float64 = 0.0 // Initialize maxEquity as zero + var peak float64 = 0.0 // Initialize peak value + var maxDrawdown float64 = 0.0 // Initialize max drawdown + var drawdown float64 = 0.0 // Initialize drawdown for _, signal := range s.Signals { if signal.Side != "BUY" && signal.Side != "SELL" { + // TODO: Handle other sides if necessary continue } - if signal.Side == "BUY" { - if signal.Price > buyPrice || buyPrice == 0 { // Update buy price only when it is higher than the previous one or zero - buyPrice = signal.Price - } - if signal.AccountBalance > maxEquity { // Update maxEquity only when it is higher than the previous one - maxEquity = signal.AccountBalance - } - loss = 0.0 // Reset loss when buy signal occurs - } else if signal.Side == "SELL" && buyPrice != 0 { - if signal.Price < buyPrice && loss == 0 { // Calculate loss only when the price is lower than the buy price and loss is zero + if signal.AccountBalance > peak { // Update peak value if account balance is higher + peak = signal.AccountBalance + } + drawdown = (peak - signal.AccountBalance) / peak // Calculate drawdown for each signal + if drawdown > maxDrawdown { + maxDrawdown = drawdown // Update max drawdown if drawdown is higher + } + } + return maxDrawdown // Return max drawdown in percentage +} - loss = (buyPrice - signal.Price) * signal.Size // Calculate loss +func MaxDrawdownUSD(s *execute.SignalEvents) float64 { + if s == nil || s.Signals == nil || len(s.Signals) == 0 { + return 0.0 + } + // Sort the signals by time + sort.Slice(s.Signals, func(i, j int) bool { + return s.Signals[i].Time.Before(s.Signals[j].Time) + }) + var peak float64 = 0.0 // Initialize peak value + var maxDrawdown float64 = 0.0 // Initialize max drawdown + var drawdown float64 = 0.0 // Initialize drawdown - } + for _, signal := range s.Signals { - // drawdown := loss / maxEquity // Calculate drawdown using maxEquity - // if drawdown > lossDrawdown { - // lossDrawdown = drawdown // Update loss drawdown - // } - // Replace the above lines with the following lines - if loss > lossDrawdown { - lossDrawdown = loss // Update loss drawdown in USD - } - buyPrice = 0.0 // Reset buyPrice when sell signal occurs + if signal.Side != "BUY" && signal.Side != "SELL" { + // TODO: Handle other sides if necessary + continue + } + if signal.AccountBalance > peak { // Update peak value if account balance is higher + peak = signal.AccountBalance + } + drawdown = (peak - signal.AccountBalance) / peak // Calculate drawdown for each signal + if drawdown > maxDrawdown { + maxDrawdown = drawdown // Update max drawdown if drawdown is higher } } - return lossDrawdown + return maxDrawdown * peak // Return max drawdown in USD } +// func MaxDrawdownUSD(s *execute.SignalEvents) float64 { +// if s == nil || s.Signals == nil || len(s.Signals) == 0 { +// return 0.0 +// } +// // Sort the signals by time +// sort.Slice(s.Signals, func(i, j int) bool { +// return s.Signals[i].Time.Before(s.Signals[j].Time) +// }) +// var lossDrawdown float64 = 0.0 +// var buyPrice float64 = 0.0 +// var loss float64 = 0.0 +// var maxEquity float64 = 0.0 + +// for _, signal := range s.Signals { + +// if signal.Side != "BUY" && signal.Side != "SELL" { +// continue +// } +// if signal.Side == "BUY" { +// if signal.Price > buyPrice || buyPrice == 0 { // Update buy price only when it is higher than the previous one or zero +// buyPrice = signal.Price +// } +// if signal.AccountBalance > maxEquity { // Update maxEquity only when it is higher than the previous one +// maxEquity = signal.AccountBalance +// } +// loss = 0.0 // Reset loss when buy signal occurs +// } else if signal.Side == "SELL" && buyPrice != 0 { +// if signal.Price < buyPrice && loss == 0 { // Calculate loss only when the price is lower than the buy price and loss is zero + +// loss = (buyPrice - signal.Price) * signal.Size // Calculate loss + +// } + +// if loss > lossDrawdown { +// lossDrawdown = loss // Update loss drawdown in USD +// } +// buyPrice = 0.0 // Reset buyPrice when sell signal occurs +// } +// } +// return lossDrawdown +// } + // MaxDrawdown returns the maximum drawdown and its duration of a strategy // based on the signal events and the initial capital func MaxDrawdown(s *execute.SignalEvents) (float64, int) { diff --git a/pkg/analytics/pay_off_ratio.go b/pkg/analytics/pay_off_ratio.go index b46a75a..1a52d43 100644 --- a/pkg/analytics/pay_off_ratio.go +++ b/pkg/analytics/pay_off_ratio.go @@ -9,7 +9,7 @@ func AveregeProfit(s *execute.SignalEvents) float64 { } winningTrades := WinningTrades(s) - totalProfit := Profit(s) + totalProfit := LongProfit(s) averegeProfit := totalProfit / float64(winningTrades) @@ -21,7 +21,7 @@ func AveregeProfitRatio(s *execute.SignalEvents) float64 { if s == nil { return 0.0 } - totalProfit := Profit(s) + totalProfit := LongProfit(s) // USDの金額ベースから%表記に変換 averageProfitPercentage := (totalProfit / s.Signals[0].AccountBalance) @@ -51,7 +51,7 @@ func AveregeTradeProfit(s *execute.SignalEvents) float64 { } totalTrade := TotalTrades(s) - netProfit := NetProfit(s) + netProfit := LongNetProfit(s) averegeTradeProfit := netProfit / float64(totalTrade) diff --git a/pkg/analytics/profit_and_loss.go b/pkg/analytics/profit_and_loss.go index 57c1471..b1d339a 100644 --- a/pkg/analytics/profit_and_loss.go +++ b/pkg/analytics/profit_and_loss.go @@ -69,7 +69,7 @@ func Profi2(s *execute.SignalEvents) float64 { return profit } -func Profit(s *execute.SignalEvents) float64 { +func LongProfit(s *execute.SignalEvents) float64 { if s == nil { return 0.0 } @@ -126,21 +126,41 @@ func Loss(s *execute.SignalEvents) float64 { return loss } -func NetProfit(s *execute.SignalEvents) float64 { +func LongNetProfit(s *execute.SignalEvents) float64 { if s == nil { return 0.0 } - totalProfit := Profit(s) + totalProfit := LongProfit(s) totalLoss := Loss(s) return totalProfit - totalLoss } +func ShortNetProfit(s *execute.SignalEvents) float64 { + if s == nil { + return 0.0 + } + totalProfit := ShortProfit(s) + totalLoss := ShortLoss(s) + + return totalProfit - totalLoss +} + +func TotalNetProfit(s *execute.SignalEvents) float64 { + if s == nil { + return 0.0 + } + longProfit := LongNetProfit(s) + shortProfit := ShortNetProfit(s) + + return longProfit + shortProfit +} + func ProfitFactor(s *execute.SignalEvents) float64 { if s == nil { return 0.0 } - totalProfit := Profit(s) + totalProfit := LongProfit(s) totalLoss := Loss(s) // if totalLoss == 0 { @@ -161,7 +181,7 @@ func FinalBalance(s *execute.SignalEvents) (float64, float64) { return 0, 0 } - finalBlanceValue := accountBalance + NetProfit(s) + finalBlanceValue := accountBalance + LongNetProfit(s) finalBlanceRatio := finalBlanceValue / accountBalance return finalBlanceValue, finalBlanceRatio @@ -277,3 +297,60 @@ func ReturnProfitLoss(s *execute.SignalEvents) []float64 { return pl } + +func ShortProfit(s *execute.SignalEvents) float64 { + if s == nil { + return 0.0 + } + var profit float64 = 0.0 + var sellPrice float64 + + if s.Signals == nil || len(s.Signals) == 0 { + return 0.0 + } + for _, signal := range s.Signals { + + if signal.Side != "BUY" && signal.Side != "SELL" { + return 0.0 + } + if signal.Side == "SELL" { + sellPrice = signal.Price + } else if signal.Side == "BUY" && sellPrice != 0 { + if signal.Price < sellPrice { + profit += (sellPrice - signal.Price) * signal.Size + } + sellPrice = 0 // Reset sell price after a buy + } + } + + return profit +} + +func ShortLoss(s *execute.SignalEvents) float64 { + + if s == nil { + return 0.0 + } + var loss float64 = 0.0 + var sellPrice float64 + + if s.Signals == nil || len(s.Signals) == 0 { + return 0.0 + } + for _, signal := range s.Signals { + + if signal.Side != "BUY" && signal.Side != "SELL" { + return 0.0 + } + if signal.Side == "SELL" { + sellPrice = signal.Price + } else if signal.Side == "BUY" && sellPrice != 0 { + if signal.Price > sellPrice { + loss += (signal.Price - sellPrice) * signal.Size + } + sellPrice = 0 // Reset sell price after a buy + } + } + + return loss +} diff --git a/pkg/analytics/calc_profit_test.go b/pkg/analytics/profit_test.go similarity index 95% rename from pkg/analytics/calc_profit_test.go rename to pkg/analytics/profit_test.go index 62bcf05..777dc9e 100644 --- a/pkg/analytics/calc_profit_test.go +++ b/pkg/analytics/profit_test.go @@ -57,7 +57,7 @@ func TestProfit(t *testing.T) { } // Call the Profit function and store the result - profit := analytics.Profit(s) + profit := analytics.LongProfit(s) // Define the expected value want := 150.0 @@ -65,6 +65,6 @@ func TestProfit(t *testing.T) { // Check if the result is equal to the expected value if profit != want { // If not, report an error to the testing framework - t.Errorf("Profit(s) = %v, want %v", profit, want) + t.Errorf("LongProfit(s) = %v, want %v", profit, want) } } diff --git a/pkg/analytics/return_dorawdown_ratio.go b/pkg/analytics/return_dorawdown_ratio.go index 38c7945..cbd45d7 100644 --- a/pkg/analytics/return_dorawdown_ratio.go +++ b/pkg/analytics/return_dorawdown_ratio.go @@ -8,7 +8,7 @@ func ReturnDDRattio(s *execute.SignalEvents) float64 { return 0.0 } - np := NetProfit(s) + np := LongNetProfit(s) dd := MaxDrawdownUSD(s) rdr := np / dd diff --git a/pkg/analytics/calc_total_trade.go b/pkg/analytics/total_trade_counts.go similarity index 100% rename from pkg/analytics/calc_total_trade.go rename to pkg/analytics/total_trade_counts.go diff --git a/pkg/analytics/winrate.go b/pkg/analytics/winrate.go new file mode 100755 index 0000000..5532612 --- /dev/null +++ b/pkg/analytics/winrate.go @@ -0,0 +1,94 @@ +package analytics + +import "v1/pkg/execute" + +type Winrate_arg struct { + Totall_wintrade int + 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 TotalWinRate(s *execute.SignalEvents) float64 { + + if s == nil { + return 0.0 + } + var profitCount, lossCount float64 + + for i := 0; i < len(s.Signals)-1; i++ { + currentSignal := s.Signals[i] + nextSignal := s.Signals[i+1] + + if currentSignal.Side == "BUY" && nextSignal.Side == "SELL" { + if nextSignal.Price > currentSignal.Price { + profitCount++ + } else if nextSignal.Price < currentSignal.Price { + lossCount++ + } + } else 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 { + return 0 + } + + return profitCount / totalCount +} diff --git a/pkg/config/yaml.go b/pkg/config/yaml.go index 3e8e8bf..301f9a2 100644 --- a/pkg/config/yaml.go +++ b/pkg/config/yaml.go @@ -9,11 +9,12 @@ import ( var data = "config.yml" type Config struct { - AssetName string `yaml:"assetName"` - Dration string `yaml:"duration"` - Start string `yaml:"start"` - End string `yaml:"end"` - Simple bool `yaml:"simpleInterest"` + AssetName string `yaml:"assetName"` + Dration string `yaml:"duration"` + Start string `yaml:"start"` + End string `yaml:"end"` + Simple bool `yaml:"simpleInterest"` + Percentage float64 `yaml:"positionPersentage"` } func Yaml() (Config, error) { diff --git a/pkg/strategey/positionsize.go b/pkg/strategey/positionsize.go index 9e3f03e..3a54858 100644 --- a/pkg/strategey/positionsize.go +++ b/pkg/strategey/positionsize.go @@ -68,7 +68,7 @@ func Loss(s *execute.SignalEvents) float64 { // var totalProfit float64 = 0.0 // for _, signal := range s.Signals { // if signal.Side == "SELL" { -// totalProfit += Profit(s) +// totalProfit += LongProfit(s) // } // } // return totalProfit @@ -79,7 +79,7 @@ func Loss(s *execute.SignalEvents) float64 { // var totalLoss float64 = 0.0 // for _, signal := range s.Signals { // if signal.Side == "BUY" { -// totalLoss -= Profit(s) +// totalLoss -= LongProfit(s) // } // } // return totalLoss diff --git a/pkg/strategey/rsi_basic.go b/pkg/strategey/rsi_basic.go index 593b72d..0673ee8 100644 --- a/pkg/strategey/rsi_basic.go +++ b/pkg/strategey/rsi_basic.go @@ -98,7 +98,7 @@ func (df *DataFrameCandle) OptimizeRsi() (performance float64, bestPeriod int, b return } - if analytics.NetProfit(signalEvents) < marketDefault { + if analytics.LongNetProfit(signalEvents) < marketDefault { return } @@ -165,7 +165,7 @@ func (df *DataFrameCandle) OptimizeRsiDrawDownGoroutin() (performance float64, b return } - if analytics.NetProfit(signalEvents) < marketDefault { + if analytics.LongNetProfit(signalEvents) < marketDefault { return } diff --git a/pkg/strategey/rsi_donchain.go b/pkg/strategey/rsi_donchain.go index 6852c94..a04166b 100644 --- a/pkg/strategey/rsi_donchain.go +++ b/pkg/strategey/rsi_donchain.go @@ -121,7 +121,7 @@ func (df *DataFrameCandle) OptimizeRsiDonchainGoroutin() (performance float64, b // return // } - p := analytics.NetProfit(signalEvents) + p := analytics.LongNetProfit(signalEvents) mu.Lock() if performance == 0 || performance < p { performance = p @@ -174,7 +174,7 @@ func (df *DataFrameCandle) OptimizeRsiDonchainDrawDownGoroutin() (performance fl return } - if analytics.NetProfit(signalEvents) < marketDefault { + if analytics.LongNetProfit(signalEvents) < marketDefault { return } diff --git a/pkg/strategey/strategy.go b/pkg/strategey/strategy.go index c2d17d1..0b4f4d9 100755 --- a/pkg/strategey/strategy.go +++ b/pkg/strategey/strategy.go @@ -24,7 +24,7 @@ import ( var btcfg, _ = config.Yaml() var initialBalance float64 = 1000.00 -var riskSize float64 = 0.9 +var riskSize float64 = btcfg.Percentage var simple bool = btcfg.Simple type DataFrameCandle struct { @@ -370,7 +370,7 @@ func Result(s *execute.SignalEvents) { n := s.Signals[0] - dd := analytics.MaxDrawdownRatio(s) + dd := analytics.MaxDrawdownPercent(s) // d, _ := analytics.MaxDrawdown(s) @@ -380,15 +380,20 @@ func Result(s *execute.SignalEvents) { fmt.Println("初期残高", initialBalance) fmt.Println("最終残高", l, "USD", lr, "倍") fmt.Println("オプティマルF", risk.OptimalF(s)) - fmt.Println("勝率", analytics.WinRate(s)*100, "%") - fmt.Println("総利益", analytics.Profit(s)) - // fmt.Println("ロング利益", analytics.LongProfit(s)) - // fmt.Println("ショート利益", analytics.ShortProfit(s)) - fmt.Println("総損失", analytics.Loss(s)) + + fmt.Println("ロング利益", analytics.LongProfit(s)) + fmt.Println("ロング損失", analytics.Loss(s)) + fmt.Println("ショート利益", analytics.ShortProfit(s)) + fmt.Println("ショート損失", analytics.ShortLoss(s)) + fmt.Println("ロング勝率", analytics.WinRate(s)*100, "%") + fmt.Println("ショート勝率", analytics.ShortWinRate(s)*100, "%") + fmt.Println("ロング純利益", analytics.LongNetProfit(s)) + fmt.Println("ショート純利益", analytics.ShortNetProfit(s)) + fmt.Println("トータル純利益", analytics.TotalNetProfit(s)) + fmt.Println("トータル勝率", analytics.TotalWinRate(s)) fmt.Println("プロフィットファクター", analytics.ProfitFactor(s)) fmt.Println("最大ドローダウン金額", analytics.MaxDrawdownUSD(s), "USD ") fmt.Println("最大ドローダウン", dd*100, "% ") - fmt.Println("純利益", analytics.NetProfit(s)) fmt.Println("シャープレシオ", analytics.SharpeRatio(s, 0.02)) fmt.Println("ソルティノレシオ", analytics.SortinoRatio(s, 0.02)) fmt.Println("トータルトレード回数", analytics.TotalTrades(s)) diff --git a/pkg/strategey/supertrend.go b/pkg/strategey/supertrend.go index de033e9..96dc431 100644 --- a/pkg/strategey/supertrend.go +++ b/pkg/strategey/supertrend.go @@ -128,7 +128,7 @@ func (df *DataFrameCandle) OptimizeST() (performance float64, bestAtrPeriod int, return } - p := analytics.NetProfit(signalEvents) + p := analytics.LongNetProfit(signalEvents) mu.Lock() if performance == 0 || performance < p { performance = p