From 2cbbcc217fc8cc1be5c0131a72754b2c4fe6a9f4 Mon Sep 17 00:00:00 2001 From: "T.K" Date: Wed, 7 Feb 2024 07:13:02 +0900 Subject: [PATCH] =?UTF-8?q?=E3=82=BD=E3=83=AB=E3=83=86=E3=82=A3=E3=83=8E?= =?UTF-8?q?=E3=83=AC=E3=82=B7=E3=82=AA=E3=81=AE=E8=BF=BD=E5=8A=A0=EF=BD=A4?= =?UTF-8?q?=E3=81=9D=E3=81=AE=E4=BB=96=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/main.go | 53 +-- config.yml | 8 +- main.go | 16 +- pkg/analytics/calc_total_trade.go | 52 +++ pkg/analytics/sharp_ratio.go | 35 ++ pkg/charts/candle_chart.go | 2 +- pkg/management/risk/market_index.go | 10 +- pkg/strategey/bb.go | 34 +- pkg/strategey/donchain.go | 4 +- pkg/strategey/donchain_choppy.go | 119 +++---- pkg/strategey/ema.go | 4 +- pkg/strategey/ema_choppy.go | 119 +++---- pkg/strategey/macd.go | 4 +- pkg/strategey/{rsi.go => rsi_basic.go} | 32 +- pkg/strategey/rsi_divergence.go | 48 +++ pkg/strategey/rsi_donchain.go | 427 ++++++++++++------------- pkg/strategey/rsi_sltp_ratio.go | 171 ++++++++++ pkg/strategey/strategy.go | 80 ++++- pkg/strategey/supertorend_choppy.go | 130 ++++---- pkg/strategey/supertrend.go | 19 +- pkg/strategey/supertrend_donchain.go | 209 ++++++++++++ pkg/strategey/supertrend_rsi.go | 186 +++++++++++ 22 files changed, 1233 insertions(+), 529 deletions(-) rename pkg/strategey/{rsi.go => rsi_basic.go} (89%) create mode 100644 pkg/strategey/rsi_divergence.go create mode 100644 pkg/strategey/rsi_sltp_ratio.go create mode 100644 pkg/strategey/supertrend_donchain.go create mode 100644 pkg/strategey/supertrend_rsi.go diff --git a/cmd/main.go b/cmd/main.go index f9c6392..37fd2f9 100755 --- a/cmd/main.go +++ b/cmd/main.go @@ -6,6 +6,7 @@ import ( "net/http" "time" chart "v1/pkg/charts" + data "v1/pkg/data/utils" // "v1/pkg/analytics/metrics" // "v1/pkg/db/models" // p "v1/pkg/management/position" @@ -147,32 +148,32 @@ func main() { // query.GetCloseData("BTCUSDT", "4h") //DBにデータを追加するための関数 - // var assets_names []string = []string{"RUNEUSDT", "BTCUSDT", "AAVEUSDT", "ORDIUSDT", "SANUSDT", "LTCUSDT", "OKBUSDT", "ASTRUSDT", "MNTUSDT", "FTMUSDT", "SNXUSDT", "DYDXUSDT", "BONKUSDT", "LUNAUSDT", "MAGICUSDT", "XLMUSDT", "DOGEUSDT", "TRSUSDT", "LINKUSDT", "TONUSDT", "ISPUSDT", "BONKUSDT", "GMXUSDT", "INJUSDT", "ETHUSDT", "SOLUSDT", "AVAXUSDT", "MATICUSDT", "ATOMUSDT", "UNIUSDT", "ARBUSDT", "OPUSDT", "PEPEUSDT", "SEIUSDT", "SUIUSDT", "TIAUSDT", "WLDUSDT", "XRPUSDT", "NEARUSDT", "DOTUSDT", "APTUSDT", "XMRUSDT", "LDOUSDT", "FILUSDT", "KASUSDT", "STXUSDT", "RNDRUSDT", "GRTUSDT"} - - // var durations []string = []string{"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h"} - // paths := data.GetRelativePaths() - - // groupedPaths := data.GroupAssetNamePaths(paths) - - // asset_data, err := data.LoadOHLCV(groupedPaths, assets_names, durations) - // if err != nil { - // log.Fatalf("Error loading OHLCV data: %v", err) - // } - - // // // data.SaveAssetDatasCSV(asset_data) - - // // DBに接続する関数を呼び出し - // db, err := data.ConnectDB("./db/kline.db") - // if err != nil { - // log.Fatal(err) - // } - // // DBをクローズするのを遅延実行 - // defer db.Close() - // // データをDBに保存する関数を呼び出し - // err = data.SaveAssetDatasDB(db, asset_data) - // if err != nil { - // log.Fatal(err) - // } + var assets_names []string = []string{"RUNEUSDT", "BTCUSDT", "AAVEUSDT", "ORDIUSDT", "SANUSDT", "LTCUSDT", "OKBUSDT", "ASTRUSDT", "MNTUSDT", "FTMUSDT", "SNXUSDT", "DYDXUSDT", "BONKUSDT", "LUNAUSDT", "MAGICUSDT", "XLMUSDT", "DOGEUSDT", "TRSUSDT", "LINKUSDT", "TONUSDT", "ISPUSDT", "BONKUSDT", "GMXUSDT", "INJUSDT", "ETHUSDT", "SOLUSDT", "AVAXUSDT", "MATICUSDT", "ATOMUSDT", "UNIUSDT", "ARBUSDT", "OPUSDT", "PEPEUSDT", "SEIUSDT", "SUIUSDT", "TIAUSDT", "WLDUSDT", "XRPUSDT", "NEARUSDT", "DOTUSDT", "APTUSDT", "XMRUSDT", "LDOUSDT", "FILUSDT", "KASUSDT", "STXUSDT", "RNDRUSDT", "GRTUSDT"} + + var durations []string = []string{"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h"} + paths := data.GetRelativePaths() + + groupedPaths := data.GroupAssetNamePaths(paths) + + asset_data, err := data.LoadOHLCV(groupedPaths, assets_names, durations) + if err != nil { + log.Fatalf("Error loading OHLCV data: %v", err) + } + + // // data.SaveAssetDatasCSV(asset_data) + + // DBに接続する関数を呼び出し + db, err := data.ConnectDB("./db/kline.db") + if err != nil { + log.Fatal(err) + } + // DBをクローズするのを遅延実行 + defer db.Close() + // データをDBに保存する関数を呼び出し + err = data.SaveAssetDatasDB(db, asset_data) + if err != nil { + log.Fatal(err) + } //ここまで // for key, paths := range groupedPaths { diff --git a/config.yml b/config.yml index c6d7e11..2b01feb 100644 --- a/config.yml +++ b/config.yml @@ -2,7 +2,7 @@ #バックテストの基本設定 startとendの月は必ず二桁になるように。 -assetName: "SOLUSDT" -duration: "15m" -start: "2021-04" -end: "2022-011" \ No newline at end of file +assetName: "BTCUSDT" +duration: "5m" +start: "20220101" +end: "20220109" \ No newline at end of file diff --git a/main.go b/main.go index e70c486..de9dfc6 100644 --- a/main.go +++ b/main.go @@ -10,10 +10,20 @@ func main() { start := time.Now() - strategey.RunEmaOptimize() - // strategey.RunBacktestDonchainChoppy() - // strategey.RunBacktestSuperTrend() + // strategey.RunEmaOptimize() + // strategey.RunSTOptimize() + // strategey.RunDonchainOptimize() + // strategey.DonchainBacktest() + // strategey.SuperTrendBacktest() + // strategey.EmaBacktest() + // strategey.RunBacktestST() + // strategey.RunRsi2Optimize() + + strategey.RunBacktestMacd() + + // strategey.RunBacktestMacd() + // strategey.EmaBacktest() // assetName := "TIAUSDT" // duration := "4h" diff --git a/pkg/analytics/calc_total_trade.go b/pkg/analytics/calc_total_trade.go index cadd34f..295257b 100644 --- a/pkg/analytics/calc_total_trade.go +++ b/pkg/analytics/calc_total_trade.go @@ -196,3 +196,55 @@ func AverageLosingHoldingBars(s *execute.SignalEvents) float64 { } return float64(totalBars) / float64(losingTrades) } + +func MaxWinCount(s *execute.SignalEvents) int { + if s == nil { + return 0 + } + var maxWinStreak, winStreak int + var buyPrice float64 + + for _, signal := range s.Signals { + if signal.Side == "BUY" { + buyPrice = signal.Price + } else if signal.Side == "SELL" && buyPrice != 0 { + if signal.Price > buyPrice { + winStreak++ + if winStreak > maxWinStreak { + maxWinStreak = winStreak + } + } else { + winStreak = 0 + } + buyPrice = 0 // Reset buy price after a sell + } + } + + return maxWinStreak +} + +func MaxLoseCount(s *execute.SignalEvents) int { + if s == nil { + return 0 + } + var maxLoseStreak, loseStreak int + var buyPrice float64 + + for _, signal := range s.Signals { + if signal.Side == "BUY" { + buyPrice = signal.Price + } else if signal.Side == "SELL" && buyPrice != 0 { + if signal.Price < buyPrice { + loseStreak++ + if loseStreak > maxLoseStreak { + maxLoseStreak = loseStreak + } + } else { + loseStreak = 0 + } + buyPrice = 0 // Reset buy price after a sell + } + } + + return maxLoseStreak +} diff --git a/pkg/analytics/sharp_ratio.go b/pkg/analytics/sharp_ratio.go index 5d1e7ab..51f4194 100755 --- a/pkg/analytics/sharp_ratio.go +++ b/pkg/analytics/sharp_ratio.go @@ -63,3 +63,38 @@ func SharpeRatio(s *execute.SignalEvents, riskFreeRate float64) float64 { return sharpeRatio } + +func downsideDeviation(data []float64, target float64) float64 { + var sqDiffSum float64 + for _, value := range data { + diff := math.Min(value-target, 0) // Only consider negative deviations + sqDiffSum += diff * diff + } + variance := sqDiffSum / float64(len(data)) + return math.Sqrt(variance) +} + +// Modified function to calculate the Sortino Ratio +func SortinoRatio(s *execute.SignalEvents, riskFreeRate float64) float64 { + + if s == nil { + return 0.0 + } + // Calculate the returns + returns := calculateReturns(s) + + // Calculate the excess returns + excessReturns := make([]float64, len(returns)) + for i, ret := range returns { + excessReturns[i] = ret - riskFreeRate + } + + // Calculate the mean and downside deviation of the excess returns + meanExcessReturn := mean(excessReturns) + downsideDeviationExcessReturn := downsideDeviation(excessReturns, 0) + + // Calculate the Sortino Ratio + sortinoRatio := meanExcessReturn / downsideDeviationExcessReturn + + return sortinoRatio +} diff --git a/pkg/charts/candle_chart.go b/pkg/charts/candle_chart.go index 6de6413..e60103a 100644 --- a/pkg/charts/candle_chart.go +++ b/pkg/charts/candle_chart.go @@ -406,7 +406,7 @@ func klineWithChoppy() *charts.Kline { lowdata[i] = k.data[2] closedata[i] = k.data[1] } - index := risk.ChoppySlice(closedata, highdata, lowdata) + index := risk.ChoppySlice(70, closedata, highdata, lowdata) choppyEma13 := risk.ChoppyEma(index, 13) choppyIndex := make([]opts.LineData, len(index)) diff --git a/pkg/management/risk/market_index.go b/pkg/management/risk/market_index.go index bd21ccc..8e32ebd 100644 --- a/pkg/management/risk/market_index.go +++ b/pkg/management/risk/market_index.go @@ -22,9 +22,9 @@ func ChoppyMarketIndex(close []float64, high []float64, low []float64) float64 { return index } -func ChoppySlice(close []float64, high []float64, low []float64) []float64 { +func ChoppySlice(d int, close []float64, high []float64, low []float64) []float64 { - if len(close) < 30 || len(high) < 30 || len(low) < 30 { + if len(close) < d || len(high) < d || len(low) < d { return nil } @@ -32,17 +32,17 @@ func ChoppySlice(close []float64, high []float64, low []float64) []float64 { for i := 1; i < len(close); i++ { - if i < 30 { + if i < d { continue } // iが30以上のときだけChoppyIndexの計算を行う // 現在の終値と30日前の終値の差の絶対値を計算する - direction := math.Abs(close[i] - close[i-30]) + direction := math.Abs(close[i] - close[i-d]) // 過去30日間の最高値と最安値の差を計算する - volatility := Max(high[len(high)-30:]) - Min(low[len(low)-30:]) + volatility := Max(high[len(high)-d:]) - Min(low[len(low)-d:]) // 市場の方向性とボラティリティのバランスを計算する choppySlice = append(choppySlice, direction/volatility*100.0) diff --git a/pkg/strategey/bb.go b/pkg/strategey/bb.go index e5c0883..dbadd1c 100644 --- a/pkg/strategey/bb.go +++ b/pkg/strategey/bb.go @@ -2,21 +2,15 @@ package strategey import ( "fmt" - "log" "runtime" "sync" "v1/pkg/analytics" - "v1/pkg/config" "v1/pkg/execute" "v1/pkg/trader" "github.com/markcheno/go-talib" ) -func getStrageyNameBb() string { - return "BB" -} - func (df *DataFrameCandle) BbStrategy(n int, k float64, account *trader.Account) *execute.SignalEvents { var StrategyName = "BB" @@ -57,9 +51,9 @@ func (df *DataFrameCandle) OptimizeBbGoroutin() (performance float64, bestN int, bestN = 20 bestK = 2.0 - a := trader.NewAccount(1000) + // a := trader.NewAccount(1000) - marketDefault, _ := BuyAndHoldingStrategy(a) + // marketDefault, _ := BuyAndHoldingStrategy(a) var mu sync.Mutex var wg sync.WaitGroup @@ -80,9 +74,9 @@ func (df *DataFrameCandle) OptimizeBbGoroutin() (performance float64, bestN int, return } - if analytics.NetProfit(signalEvents) < marketDefault { - return - } + // if analytics.NetProfit(signalEvents) < marketDefault { + // return + // } // if analytics.WinRate(signalEvents) < 0.50 { // return @@ -114,23 +108,7 @@ func (df *DataFrameCandle) OptimizeBbGoroutin() (performance float64, bestN int, } func RunBacktestBb() { - var err error - - // account := trader.NewAccount(1000) - btcfg, err := config.Yaml() - if err != nil { - log.Fatalf("error: %v", err) - } - - fmt.Println(btcfg.AssetName) - - assetName := btcfg.AssetName - duration := btcfg.Dration - // limit := btcfg.Limit - - account := trader.NewAccount(1000) - - df, _ := GetCandleData(assetName, duration) + df, account, _ := RadyBacktest() performance, bestN, bestK := df.OptimizeBbGoroutin() diff --git a/pkg/strategey/donchain.go b/pkg/strategey/donchain.go index 420625d..db610b2 100644 --- a/pkg/strategey/donchain.go +++ b/pkg/strategey/donchain.go @@ -15,7 +15,7 @@ import ( // return "DBO" // } -func (df *DataFrameCandleCsv) DonchainStrategy(period int, account *trader.Account) *execute.SignalEvents { +func (df *DataFrameCandle) DonchainStrategy(period int, account *trader.Account) *execute.SignalEvents { var StrategyName = "DBO" lenCandles := len(df.Candles) @@ -63,7 +63,7 @@ func (df *DataFrameCandleCsv) DonchainStrategy(period int, account *trader.Accou return signalEvents } -func (df *DataFrameCandleCsv) OptimizeDonchainGoroutin() (performance float64, bestPeriod int) { +func (df *DataFrameCandle) OptimizeDonchainGoroutin() (performance float64, bestPeriod int) { bestPeriod = 40 var mu sync.Mutex diff --git a/pkg/strategey/donchain_choppy.go b/pkg/strategey/donchain_choppy.go index 3eb0bc0..3df29f3 100644 --- a/pkg/strategey/donchain_choppy.go +++ b/pkg/strategey/donchain_choppy.go @@ -8,13 +8,15 @@ import ( "v1/pkg/indicator/indicators" "v1/pkg/management/risk" "v1/pkg/trader" + + "github.com/markcheno/go-talib" ) // func getStrageyNameDonchain() string { // return "DBO" // } -func (df *DataFrameCandleCsv) DonchainChoppyStrategy(period int, choppy int, account *trader.Account) *execute.SignalEvents { +func (df *DataFrameCandle) DonchainChoppyStrategy(period int, choppy int, duration int, account *trader.Account) *execute.SignalEvents { var StrategyName = "DBO_CHOPPY" lenCandles := len(df.Candles) @@ -27,12 +29,16 @@ func (df *DataFrameCandleCsv) DonchainChoppyStrategy(period int, choppy int, acc donchain := indicators.Donchain(df.Highs(), df.Lows(), period) // atr := talib.Atr(df.Highs(), df.Low(), df.Closes(), 21) + ema := talib.Ema(df.Hlc3(), 89) + close := df.Closes() buySize := 0.0 + buyPrice := 0.0 + slRatio := 0.9 isHolding := false - index := risk.ChoppySlice(df.Closes(), df.Highs(), df.Lows()) + index := risk.ChoppySlice(duration, df.Closes(), df.Highs(), df.Lows()) choppyEma := risk.ChoppyEma(index, choppy) for i := 30; i < lenCandles; i++ { @@ -41,16 +47,17 @@ func (df *DataFrameCandleCsv) DonchainChoppyStrategy(period int, choppy int, acc continue } - if close[i] > donchain.High[i-1] && choppyEma[i] > 50 && !isHolding { + if close[i] > donchain.High[i-1] && choppyEma[i] > 50 && close[i] > ema[i] && !isHolding { buySize = account.TradeSize(riskSize) / df.Candles[i].Close + buyPrice = close[i] accountBalance := account.GetBalance() if account.Buy(df.Candles[i].Close, buySize) { signalEvents.Buy(StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, buySize, accountBalance, false) isHolding = true } } - if close[i] < donchain.Low[i-1] && isHolding { + if (close[i] < donchain.Low[i-1] || (close[i] <= buyPrice*slRatio)) && isHolding { accountBalance := account.GetBalance() if account.Sell(df.Candles[i].Close) { signalEvents.Sell(StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, buySize, accountBalance, false) @@ -66,7 +73,7 @@ func (df *DataFrameCandleCsv) DonchainChoppyStrategy(period int, choppy int, acc } -func (df *DataFrameCandleCsv) OptimizeDonchainChoppyGoroutin() (performance float64, bestPeriod int, bestChoppy int) { +func (df *DataFrameCandle) OptimizeDonchainChoppyGoroutin() (performance float64, bestPeriod int, bestChoppy int, bestDuration int) { bestPeriod = 40 bestChoppy = 13 @@ -76,72 +83,76 @@ func (df *DataFrameCandleCsv) OptimizeDonchainChoppyGoroutin() (performance floa // a := trader.NewAccount(1000) // marketDefault, _ := BuyAndHoldingStrategy(a) - limit := 1000 + limit := 3000 slots := make(chan struct{}, limit) - for period := 5; period < 100; period += 1 { - for choppy := 5; choppy < 18; choppy += 1 { - wg.Add(1) - slots <- struct{}{} - - go func(period int, choppy int) { - defer wg.Done() - account := trader.NewAccount(1000) - signalEvents := df.DonchainChoppyStrategy(period, choppy, account) - - if signalEvents == nil { - return - } - - if analytics.TotalTrades(signalEvents) < 5 { + for period := 5; period < 250; period += 10 { + for duration := 10; duration < 200; duration += 10 { + for choppy := 6; choppy < 18; choppy += 2 { + wg.Add(1) + slots <- struct{}{} + + go func(period int, choppy int, duration int) { + defer wg.Done() + account := trader.NewAccount(1000) + signalEvents := df.DonchainChoppyStrategy(period, choppy, duration, account) + + if signalEvents == nil { + return + } + + if analytics.TotalTrades(signalEvents) < 30 { + <-slots + return + } + + // if analytics.NetProfit(signalEvents) < marketDefault { + // // <-slots + // return + // } + + // if analytics.SQN(signalEvents) < 3.2 { + // <-slots + // return + // } + + // if analytics.ProfitFactor(signalEvents) < 3 { + // <-slots + // return + // } + + // pf := analytics.SortinoRatio(signalEvents, 0.02) + pf := analytics.SQN(signalEvents) + mu.Lock() + if performance < pf { + performance = pf + bestPeriod = period + bestChoppy = choppy + bestDuration = duration + } <-slots - return - } - - // if analytics.NetProfit(signalEvents) < marketDefault { - // // <-slots - // return - // } - - // if analytics.WinRate(signalEvents) < 0.45 { - // <-slots - // return - // } - - // if analytics.ProfitFactor(signalEvents) < 3 { - // <-slots - // return - // } - - pf := analytics.SQN(signalEvents) - mu.Lock() - if performance < pf { - performance = pf - bestPeriod = period - bestChoppy = choppy - } - <-slots - mu.Unlock() - }(period, choppy) + mu.Unlock() + }(period, choppy, duration) + } } } wg.Wait() - fmt.Println("最高SQN", performance, "最適なピリオド", bestPeriod, "最適なチョッピー", bestChoppy) + fmt.Println("最高パフォーマンス", performance, "最適なピリオド", bestPeriod, "最適なチョッピー", bestChoppy, "最適なチョッピー期間", bestDuration) - return performance, bestPeriod, bestChoppy + return performance, bestPeriod, bestChoppy, bestDuration } func RunDonchainOptimize() { df, account, _ := RadyBacktest() - p, bestPeriod, bestChoppy := df.OptimizeDonchainChoppyGoroutin() + p, bestPeriod, bestChoppy, bestDuration := df.OptimizeDonchainChoppyGoroutin() if p > 0 { - df.Signal = df.DonchainChoppyStrategy(bestPeriod, bestChoppy, account) + df.Signal = df.DonchainChoppyStrategy(bestPeriod, bestChoppy, bestDuration, account) Result(df.Signal) } @@ -152,6 +163,6 @@ func DonchainBacktest() { df, account, _ := RadyBacktest() - df.Signal = df.DonchainChoppyStrategy(40, 13, account) + df.Signal = df.DonchainChoppyStrategy(215, 12, 60, account) Result(df.Signal) } diff --git a/pkg/strategey/ema.go b/pkg/strategey/ema.go index c54f709..1bff7e3 100644 --- a/pkg/strategey/ema.go +++ b/pkg/strategey/ema.go @@ -13,7 +13,7 @@ import ( "github.com/markcheno/go-talib" ) -func (df *DataFrameCandleCsv) EmaStrategy(period1, period2 int, account *trader.Account) *execute.SignalEvents { +func (df *DataFrameCandle) EmaStrategy(period1, period2 int, account *trader.Account) *execute.SignalEvents { var StrategyName = "EMA" lenCandles := len(df.Candles) @@ -61,7 +61,7 @@ func (df *DataFrameCandleCsv) EmaStrategy(period1, period2 int, account *trader. return signalEvents } -func (df *DataFrameCandleCsv) OptimizeEma() (performance float64, bestPeriod1 int, bestPeriod2 int) { +func (df *DataFrameCandle) OptimizeEma() (performance float64, bestPeriod1 int, bestPeriod2 int) { runtime.GOMAXPROCS(10) bestPeriod1 = 5 bestPeriod2 = 21 diff --git a/pkg/strategey/ema_choppy.go b/pkg/strategey/ema_choppy.go index 352cba4..32d6de2 100644 --- a/pkg/strategey/ema_choppy.go +++ b/pkg/strategey/ema_choppy.go @@ -12,7 +12,7 @@ import ( "github.com/markcheno/go-talib" ) -func (df *DataFrameCandleCsv) EmaChoppyStrategy(period1, period2 int, choppy int, account *trader.Account) *execute.SignalEvents { +func (df *DataFrameCandle) EmaChoppyStrategy(period1, period2 int, choppy int, duration int, account *trader.Account) *execute.SignalEvents { var StrategyName = "EMA_CHOPPY" lenCandles := len(df.Candles) @@ -29,7 +29,7 @@ func (df *DataFrameCandleCsv) EmaChoppyStrategy(period1, period2 int, choppy int buyPrice := 0.0 slRatio := 0.9 - index := risk.ChoppySlice(df.Closes(), df.Highs(), df.Lows()) + index := risk.ChoppySlice(duration, df.Closes(), df.Highs(), df.Lows()) choppyEma := risk.ChoppyEma(index, choppy) isBuyHolding := false @@ -64,13 +64,13 @@ func (df *DataFrameCandleCsv) EmaChoppyStrategy(period1, period2 int, choppy int return signalEvents } -func (df *DataFrameCandleCsv) OptimizeEmaChoppy() (performance float64, bestPeriod1 int, bestPeriod2 int, bestChoppy int) { +func (df *DataFrameCandle) OptimizeEmaChoppy() (performance float64, bestPeriod1 int, bestPeriod2 int, bestChoppy int, bestDuration int) { runtime.GOMAXPROCS(10) bestPeriod1 = 5 bestPeriod2 = 21 - bestChoppy = 13 + bestDuration = 30 - limit := 1000 + limit := 3000 slots := make(chan struct{}, limit) // a := trader.NewAccount(1000) @@ -79,78 +79,81 @@ func (df *DataFrameCandleCsv) OptimizeEmaChoppy() (performance float64, bestPeri var mu sync.Mutex var wg sync.WaitGroup - for period1 := 1; period1 < 34; period1 += 2 { - for period2 := 5; period2 < 250; period2 += 3 { + for period1 := 3; period1 < 34; period1 += 2 { + for period2 := 13; period2 < 89; period2 += 4 { for choppy := 5; choppy < 18; choppy += 1 { - - wg.Add(1) - slots <- struct{}{} - - go func(period1 int, period2 int, choppy int) { - defer wg.Done() - account := trader.NewAccount(1000) // Move this line inside the goroutine - signalEvents := df.EmaChoppyStrategy(period1, period2, choppy, account) - - if signalEvents == nil { - return - } - - if analytics.TotalTrades(signalEvents) < 5 { + for duration := 10; duration < 200; duration += 10 { + + wg.Add(1) + slots <- struct{}{} + + go func(period1 int, period2 int, choppy int, duration int) { + defer wg.Done() + account := trader.NewAccount(1000) // Move this line inside the goroutine + signalEvents := df.EmaChoppyStrategy(period1, period2, choppy, duration, account) + + if signalEvents == nil { + return + } + + if analytics.TotalTrades(signalEvents) < 30 { + <-slots + return + } + + // if analytics.NetProfit(signalEvents) < marketDefault { + // <-slots + // return + // } + + // if analytics.SQN(signalEvents) < 3.2 { + // <-slots + // return + // } + + // if analytics.PayOffRatio(signalEvents) < 1 { + // <-slots + + // return + // } + + // pf := analytics.SortinoRatio(signalEvents, 0.02) + p := analytics.SQN(signalEvents) + mu.Lock() + if performance == 0 || performance < p { + performance = p + bestPeriod1 = period1 + bestPeriod2 = period2 + bestChoppy = choppy + bestDuration = duration + + } <-slots - return - } - - // if analytics.NetProfit(signalEvents) < marketDefault { - // <-slots - // return - // } - - // if analytics.WinRate(signalEvents) < 0.50 { - // <-slots - - // return - // } - - // if analytics.PayOffRatio(signalEvents) < 1 { - // <-slots - - // return - // } - - p := analytics.SQN(signalEvents) - mu.Lock() - if performance == 0 || performance < p { - performance = p - bestPeriod1 = period1 - bestPeriod2 = period2 - bestChoppy = choppy - - } - <-slots - mu.Unlock() + mu.Unlock() - }(period1, period2, choppy) + }(period1, period2, 13, duration) + } } } } wg.Wait() - fmt.Println("最高SQN", performance, "最適な短期線", bestPeriod1, "最適な長期線", bestPeriod2, "最適なチョッピー", bestChoppy) + fmt.Println("最高パフォーマンス", performance, "最適な短期線", bestPeriod1, "最適な長期線", bestPeriod2, "最適なチョッピーEMA", bestChoppy, "最適なチョッピー期間", bestDuration) - return performance, bestPeriod1, bestPeriod2, bestChoppy + return performance, bestPeriod1, bestPeriod2, bestChoppy, bestDuration } func RunEmaOptimize() { df, account, _ := RadyBacktest() - performance, bestPeriod1, bestPeriod2, bestChoppy := df.OptimizeEmaChoppy() + performance, bestPeriod1, bestPeriod2, bestChoppy, bestDuration := df.OptimizeEmaChoppy() if performance > 0 { - df.Signal = df.EmaChoppyStrategy(bestPeriod1, bestPeriod2, bestChoppy, account) + df.Signal = df.EmaChoppyStrategy(bestPeriod1, bestPeriod2, bestChoppy, bestDuration, account) Result(df.Signal) } @@ -161,7 +164,7 @@ func EmaBacktest() { df, account, _ := RadyBacktest() - df.Signal = df.EmaChoppyStrategy(5, 21, 12, account) + df.Signal = df.EmaChoppyStrategy(3, 33, 13, 30, account) Result(df.Signal) } diff --git a/pkg/strategey/macd.go b/pkg/strategey/macd.go index dbb58ca..413300e 100644 --- a/pkg/strategey/macd.go +++ b/pkg/strategey/macd.go @@ -152,10 +152,12 @@ func RunBacktestMacd() { assetName := btcfg.AssetName duration := btcfg.Dration + start := btcfg.Start + end := btcfg.End account := trader.NewAccount(1000) - df, _ := GetCandleData(assetName, duration) + df, _ := GetCandleData(assetName, duration, start, end) performance, bestMacdFastPeriod, bestMacdSlowPeriod, bestMacdSignalPeriod := df.OptimizeMacd() diff --git a/pkg/strategey/rsi.go b/pkg/strategey/rsi_basic.go similarity index 89% rename from pkg/strategey/rsi.go rename to pkg/strategey/rsi_basic.go index 176b8cd..593b72d 100644 --- a/pkg/strategey/rsi.go +++ b/pkg/strategey/rsi_basic.go @@ -2,12 +2,10 @@ package strategey import ( "fmt" - "log" "math" "runtime" "sync" "v1/pkg/analytics" - "v1/pkg/config" "v1/pkg/execute" "v1/pkg/trader" @@ -15,10 +13,6 @@ import ( "github.com/markcheno/go-talib" ) -func getStrageyNameRSI() string { - return "RSI" -} - func (df *DataFrameCandle) RsiStrategy(period int, buyThread float64, sellThread float64, account *trader.Account) *execute.SignalEvents { var StrategyName = "RSI" @@ -198,31 +192,7 @@ func (df *DataFrameCandle) OptimizeRsiDrawDownGoroutin() (performance float64, b func RunBacktestRsi() { - var err error - - // account := trader.NewAccount(1000) - btcfg, err := config.Yaml() - if err != nil { - log.Fatalf("error: %v", err) - } - - fmt.Println(btcfg.AssetName) - - strategyName := getStrageyNameRSI() - assetName := btcfg.AssetName - duration := btcfg.Dration - // limit := btcfg.Limit - - account := trader.NewAccount(1000) - - df, _ := GetCandleData(assetName, duration) - - tableName := strategyName + "_" + assetName + "_" + duration - - _, err = execute.CreateDBTable(tableName) - if err != nil { - log.Fatal(err) - } + df, account, _ := RadyBacktest() performance, bestPeriod, bestBuyThread, bestSellThread := df.OptimizeRsi() diff --git a/pkg/strategey/rsi_divergence.go b/pkg/strategey/rsi_divergence.go new file mode 100644 index 0000000..ce92a7e --- /dev/null +++ b/pkg/strategey/rsi_divergence.go @@ -0,0 +1,48 @@ +package strategey + +// 過去n日間の高値を取得する +// その高値を記録したときのRSIの数値も取得する +// 現在の終値が、過去n日間の高値を突破する +// 現在のRSIの数値が高値のときのRSIよりも小さい場合、ダイバージェンスシグナルをTrueにする +// ダイバージェンスがTrueになったときに、rsi[i-1] > rsi[i]になったらエントリーする + +// func Max(array []float64) float64 { +// // 配列の長さをチェックする +// if len(array) == 0 { +// return 0.0 // エラーを返す +// } +// // 最大値を初期化する +// max := array[0] +// // 配列の要素をループする +// for _, value := range array { +// // 最大値を更新する +// if value > max { +// max = value +// } +// } +// // 最大値を返す +// return max +// } + +// func (df *DataFrameCandle) RsiDG(period int, duration int, account *trader.Account) *execute.SignalEvents { + +// var StrategyName = "RSI_DIVERGENCE" +// lenCandles := len(df.Candles) +// if lenCandles <= period { +// return nil +// } + +// signalEvents := execute.NewSignalEvents() + +// high := df.Highs() +// hl3 := df.Hlc3() +// rsi := talib.Rsi(hl3, period) + +// h := Max(high[len(high)-duration:]) + +// buySize := 0.0 +// buyPrice := 0.0 +// isBuyHolding := false +// dgSignal := false + +// } diff --git a/pkg/strategey/rsi_donchain.go b/pkg/strategey/rsi_donchain.go index 935fdc6..6852c94 100644 --- a/pkg/strategey/rsi_donchain.go +++ b/pkg/strategey/rsi_donchain.go @@ -1,244 +1,215 @@ package strategey -// import ( -// "fmt" -// "log" -// "math" -// "runtime" -// "sync" -// "v1/pkg/analytics" -// "v1/pkg/config" -// "v1/pkg/execute" -// "v1/pkg/indicator/indicators" - -// "v1/pkg/trader" - -// "github.com/markcheno/go-talib" -// ) - -// func getStrageyNameRSIDonchain() string { -// return "RSI_Donchain" -// } - -// func (df *DataFrameCandle) RsiDonchainStrategy(rsiPeriod int, donchainPeriod int, buyThread float64, account *trader.Account) *execute.SignalEvents { - -// var StrategyName = "RSI_DONCHAIN" -// lenCandles := len(df.Candles) -// if lenCandles <= donchainPeriod { -// return nil -// } - -// signalEvents := execute.NewSignalEvents() -// close := df.Closes() - -// values := talib.Rsi(close, rsiPeriod) - -// donchain := indicators.Donchain(df.Highs(), df.Low(), donchainPeriod) - -// buySize := 0.0 -// buyPrice := 0.0 -// slRatio := 0.1 - -// isBuyHolding := false -// for i := 1; i < lenCandles; i++ { -// if values[i-1] == 0 || values[i-1] == 100 { -// continue -// } - -// if values[i-1] < buyThread && values[i] >= buyThread && !isBuyHolding { -// accountBalance := account.GetBalance() -// buySize = account.TradeSize(riskSize) / df.Candles[i].Close -// buyPrice = df.Candles[i].Close -// if account.Buy(df.Candles[i].Close, buySize) { - -// signalEvents.Buy(StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, buySize, accountBalance, false) -// isBuyHolding = true - -// } -// } - -// if close[i] > donchain.High[i-1] || (df.Candles[i].Close < buyPrice-buyPrice*slRatio) || values[i-1] > 90 && isBuyHolding { -// accountBalance := account.GetBalance() -// if account.Sell(df.Candles[i].Close) { -// signalEvents.Sell(StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, buySize, accountBalance, false) -// isBuyHolding = false -// buySize = 0.0 -// account.PositionSize = buySize - -// } -// } - -// } - -// return signalEvents -// } - -// func (df *DataFrameCandle) OptimizeRsiDonchainGoroutin() (performance float64, bestRsiPeriod int, bestDonchainPeriod int, bestBuyThread float64) { -// runtime.GOMAXPROCS(12) - -// bestRsiPeriod = 13 -// bestDonchainPeriod = 20 -// bestBuyThread = 20.0 - -// limit := 1000 -// slots := make(chan struct{}, limit) - -// // var pool = sync.Pool{ -// // New: func() interface{} { -// // return trader.NewAccount(1000) -// // }, -// // } - -// // a := trader.NewAccount(1000) - -// // marketDefault, _ := BuyAndHoldingStrategy(a) -// var mu sync.Mutex -// var wg sync.WaitGroup - -// for rsiPeriod := 2; rsiPeriod < 14; rsiPeriod++ { -// for buyThread := 30.0; buyThread > 8.0; buyThread -= 1 { -// for donchainPeriod := 20; donchainPeriod < 100; donchainPeriod += 5 { -// wg.Add(1) -// slots <- struct{}{} -// go func(rsiPeriod int, buyThread float64, donchainPeriod int) { -// defer wg.Done() -// account := trader.NewAccount(1000) // Move this line inside the goroutine -// // account := pool.Get().(*trader.Account) -// // defer pool.Put(account) -// signalEvents := df.RsiDonchainStrategy(rsiPeriod, donchainPeriod, buyThread, account) - -// if signalEvents == nil { -// return -// } - -// if analytics.TotalTrades(signalEvents) < 10 { -// return -// } - -// // if analytics.NetProfit(signalEvents) < marketDefault { -// // return -// // } - -// // if analytics.WinRate(signalEvents) < 0.50 { -// // return -// // } - -// // if analytics.GainPainRatio(signalEvents) < 1 { -// // return -// // } +import ( + "fmt" + "math" + "runtime" + "sync" + "v1/pkg/analytics" + "v1/pkg/execute" + "v1/pkg/indicator/indicators" + + "v1/pkg/trader" + + "github.com/markcheno/go-talib" +) + +func (df *DataFrameCandle) RsiDonchainStrategy(rsiPeriod int, donchainPeriod int, buyThread float64, account *trader.Account) *execute.SignalEvents { + + var StrategyName = "RSI_DONCHAIN" + lenCandles := len(df.Candles) + if lenCandles <= donchainPeriod { + return nil + } + + signalEvents := execute.NewSignalEvents() + close := df.Closes() + + values := talib.Rsi(close, rsiPeriod) + + donchain := indicators.Donchain(df.Highs(), df.Lows(), donchainPeriod) + + buySize := 0.0 + buyPrice := 0.0 + slRatio := 0.1 + + isBuyHolding := false + for i := 1; i < lenCandles; i++ { + if values[i-1] == 0 || values[i-1] == 100 { + continue + } + + if values[i-1] < buyThread && values[i] >= buyThread && !isBuyHolding { + accountBalance := account.GetBalance() + buySize = account.TradeSize(riskSize) / df.Candles[i].Close + buyPrice = df.Candles[i].Close + if account.Buy(df.Candles[i].Close, buySize) { + + signalEvents.Buy(StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, buySize, accountBalance, false) + isBuyHolding = true + + } + } + + if close[i] > donchain.High[i-1] || (df.Candles[i].Close < buyPrice-buyPrice*slRatio) || values[i-1] > 90 && isBuyHolding { + accountBalance := account.GetBalance() + if account.Sell(df.Candles[i].Close) { + signalEvents.Sell(StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, buySize, accountBalance, false) + isBuyHolding = false + buySize = 0.0 + account.PositionSize = buySize + + } + } + + } + + return signalEvents +} + +func (df *DataFrameCandle) OptimizeRsiDonchainGoroutin() (performance float64, bestRsiPeriod int, bestDonchainPeriod int, bestBuyThread float64) { + runtime.GOMAXPROCS(12) + + bestRsiPeriod = 13 + bestDonchainPeriod = 20 + bestBuyThread = 20.0 + + limit := 1000 + slots := make(chan struct{}, limit) + + // var pool = sync.Pool{ + // New: func() interface{} { + // return trader.NewAccount(1000) + // }, + // } + + // a := trader.NewAccount(1000) + + // marketDefault, _ := BuyAndHoldingStrategy(a) + var mu sync.Mutex + var wg sync.WaitGroup + + for rsiPeriod := 2; rsiPeriod < 14; rsiPeriod++ { + for buyThread := 30.0; buyThread > 8.0; buyThread -= 1 { + for donchainPeriod := 20; donchainPeriod < 100; donchainPeriod += 5 { + wg.Add(1) + slots <- struct{}{} + go func(rsiPeriod int, buyThread float64, donchainPeriod int) { + defer wg.Done() + account := trader.NewAccount(1000) // Move this line inside the goroutine + // account := pool.Get().(*trader.Account) + // defer pool.Put(account) + signalEvents := df.RsiDonchainStrategy(rsiPeriod, donchainPeriod, buyThread, account) + + if signalEvents == nil { + return + } + + if analytics.TotalTrades(signalEvents) < 10 { + return + } -// p := analytics.NetProfit(signalEvents) -// mu.Lock() -// if performance == 0 || performance < p { -// performance = p -// bestRsiPeriod = rsiPeriod -// bestBuyThread = buyThread -// bestDonchainPeriod = donchainPeriod -// } -// <-slots -// mu.Unlock() -// }(rsiPeriod, buyThread, donchainPeriod) -// } -// } -// } + // if analytics.NetProfit(signalEvents) < marketDefault { + // return + // } -// wg.Wait() + // if analytics.WinRate(signalEvents) < 0.50 { + // return + // } + + // if analytics.GainPainRatio(signalEvents) < 1 { + // return + // } -// fmt.Println("最高利益", performance, "最適なRSI", bestRsiPeriod, "最適な買いライン", bestBuyThread, "最適なドンチャン", bestDonchainPeriod) + p := analytics.NetProfit(signalEvents) + mu.Lock() + if performance == 0 || performance < p { + performance = p + bestRsiPeriod = rsiPeriod + bestBuyThread = buyThread + bestDonchainPeriod = donchainPeriod + } + <-slots + mu.Unlock() + }(rsiPeriod, buyThread, donchainPeriod) + } + } + } + + wg.Wait() + + fmt.Println("最高利益", performance, "最適なRSI", bestRsiPeriod, "最適な買いライン", bestBuyThread, "最適なドンチャン", bestDonchainPeriod) + + return performance, bestRsiPeriod, bestDonchainPeriod, bestBuyThread +} -// return performance, bestRsiPeriod, bestDonchainPeriod, bestBuyThread -// } +func (df *DataFrameCandle) OptimizeRsiDonchainDrawDownGoroutin() (performance float64, bestPeriod int, bestBuyThread, bestSellThread float64) { + runtime.GOMAXPROCS(10) + + bestPeriod = 13 + bestBuyThread, bestSellThread = 20.0, 80.0 + + performance = math.MaxFloat64 -// func (df *DataFrameCandle) OptimizeRsiDonchainDrawDownGoroutin() (performance float64, bestPeriod int, bestBuyThread, bestSellThread float64) { -// runtime.GOMAXPROCS(10) + a := trader.NewAccount(1000) + + marketDefault, _ := BuyAndHoldingStrategy(a) + var mu sync.Mutex + var wg sync.WaitGroup -// bestPeriod = 13 -// bestBuyThread, bestSellThread = 20.0, 80.0 + for period := 2; period < 28; period++ { + for buyThread := 30.0; buyThread > 10; buyThread -= 1 { + for sellThread := 70.0; sellThread < 96; sellThread += 1 { + wg.Add(1) + go func(period int, buyThread, sellThread float64) { + defer wg.Done() + account := trader.NewAccount(1000) // Move this line inside the goroutine + signalEvents := df.RsiStrategy(period, buyThread, sellThread, account) + + if signalEvents == nil { + return + } + + if analytics.TotalTrades(signalEvents) < 5 { + return + } + + if analytics.NetProfit(signalEvents) < marketDefault { + return + } + + dd := analytics.MaxDrawdownUSD(signalEvents) + mu.Lock() + if performance > dd { + performance = dd + bestPeriod = period + bestBuyThread = buyThread + bestSellThread = sellThread + } + mu.Unlock() + }(period, buyThread, sellThread) + } + } + } + + wg.Wait() + + fmt.Println("ドローダウン", performance, "最適なピリオド", bestPeriod, "最適な買いライン", bestBuyThread, "最適な売りライン", bestSellThread) -// performance = math.MaxFloat64 + return performance, bestPeriod, bestBuyThread, bestSellThread +} -// a := trader.NewAccount(1000) +func RunBacktestRsiDonchain() { -// marketDefault, _ := BuyAndHoldingStrategy(a) -// var mu sync.Mutex -// var wg sync.WaitGroup + df, account, _ := RadyBacktest() -// for period := 2; period < 28; period++ { -// for buyThread := 30.0; buyThread > 10; buyThread -= 1 { -// for sellThread := 70.0; sellThread < 96; sellThread += 1 { -// wg.Add(1) -// go func(period int, buyThread, sellThread float64) { -// defer wg.Done() -// account := trader.NewAccount(1000) // Move this line inside the goroutine -// signalEvents := df.RsiStrategy(period, buyThread, sellThread, account) + performance, bestRsiPeriod, bestDonchainPeriod, bestBuyThread := df.OptimizeRsiDonchainGoroutin() -// if signalEvents == nil { -// return -// } + if performance > 0 { -// if analytics.TotalTrades(signalEvents) < 5 { -// return -// } + df.Signal = df.RsiDonchainStrategy(bestRsiPeriod, bestDonchainPeriod, bestBuyThread, account) + Result(df.Signal) -// if analytics.NetProfit(signalEvents) < marketDefault { -// return -// } + } -// dd := analytics.MaxDrawdownUSD(signalEvents) -// mu.Lock() -// if performance > dd { -// performance = dd -// bestPeriod = period -// bestBuyThread = buyThread -// bestSellThread = sellThread -// } -// mu.Unlock() -// }(period, buyThread, sellThread) -// } -// } -// } - -// wg.Wait() - -// fmt.Println("ドローダウン", performance, "最適なピリオド", bestPeriod, "最適な買いライン", bestBuyThread, "最適な売りライン", bestSellThread) - -// return performance, bestPeriod, bestBuyThread, bestSellThread -// } - -// func RunBacktestRsiDonchain() { - -// var err error - -// // account := trader.NewAccount(1000) -// btcfg, err := config.Yaml() -// if err != nil { -// log.Fatalf("error: %v", err) -// } - -// fmt.Println(btcfg.AssetName) - -// strategyName := getStrageyNameRSIDonchain() -// assetName := btcfg.AssetName -// duration := btcfg.Dration - -// account := trader.NewAccount(1000) - -// df, _ := GetCandleData(assetName, duration) - -// tableName := strategyName + "_" + assetName + "_" + duration - -// _, err = execute.CreateDBTable(tableName) -// if err != nil { -// log.Fatal(err) -// } - -// performance, bestRsiPeriod, bestDonchainPeriod, bestBuyThread := df.OptimizeRsiDonchainGoroutin() - -// if performance > 0 { - -// df.Signal = df.RsiDonchainStrategy(bestRsiPeriod, bestDonchainPeriod, bestBuyThread, account) -// Result(df.Signal) - -// } - -// } +} diff --git a/pkg/strategey/rsi_sltp_ratio.go b/pkg/strategey/rsi_sltp_ratio.go new file mode 100644 index 0000000..a424562 --- /dev/null +++ b/pkg/strategey/rsi_sltp_ratio.go @@ -0,0 +1,171 @@ +package strategey + +import ( + "fmt" + "runtime" + "sync" + "v1/pkg/analytics" + "v1/pkg/execute" + "v1/pkg/management/risk" + + "v1/pkg/trader" + + "github.com/markcheno/go-talib" +) + +func (df *DataFrameCandle) RsiStrategy2(period int, buyThread float64, tpRatio float64, slRatio float64, account *trader.Account) *execute.SignalEvents { + + var StrategyName = "RSI_SL&TP_RATIO" + lenCandles := len(df.Candles) + if lenCandles <= period { + return nil + } + + signalEvents := execute.NewSignalEvents() + + hl3 := df.Hlc3() + + h := df.Highs() + l := df.Lows() + c := df.Closes() + + values := talib.Rsi(hl3, period) + + buySize := 0.0 + buyPrice := 0.0 + isBuyHolding := false + + atr := talib.Atr(h, l, c, 13) + ema := talib.Ema(hl3, 200) + + index := risk.ChoppySlice(70, df.Closes(), df.Highs(), df.Lows()) + choppyEma := risk.ChoppyEma(index, 11) + + for i := 1; i < lenCandles; i++ { + if values[i-1] == 0 || values[i-1] == 100 || i >= len(choppyEma) { + continue + } + + sl := atr[i] * slRatio + tp := atr[i] * tpRatio + + if values[i-1] < buyThread && values[i] >= buyThread && choppyEma[i] > 40 && c[i] < ema[i] && !isBuyHolding { + accountBalance := account.GetBalance() + buySize = account.TradeSize(riskSize) / df.Candles[i].Close + buyPrice = df.Candles[i].Close + if account.Buy(df.Candles[i].Close, buySize) { + + signalEvents.Buy(StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, buySize, accountBalance, false) + isBuyHolding = true + + } + } + + if (df.Candles[i].Close > buyPrice+tp || df.Candles[i].Close < buyPrice-sl) && isBuyHolding { + accountBalance := account.GetBalance() + if account.Sell(df.Candles[i].Close) { + signalEvents.Sell(StrategyName, df.AssetName, df.Duration, df.Candles[i].Date, df.Candles[i].Close, buySize, accountBalance, false) + isBuyHolding = false + buySize = 0.0 + account.PositionSize = buySize + + } + } + + } + + return signalEvents +} +func (df *DataFrameCandle) OptimizeRsi2() (performance float64, bestPeriod int, bestBuyThread, bestTpRatio, bestSlRatio float64) { + runtime.GOMAXPROCS(10) + + bestPeriod = 13 + bestBuyThread = 20.0 + bestTpRatio = 3 + bestSlRatio = 1 + + limit := 3000 + slots := make(chan struct{}, limit) + + // a := trader.NewAccount(1000) + // marketDefault, _ := BuyAndHoldingStrategy(a) + + var mu sync.Mutex + var wg sync.WaitGroup + + for period := 8; period < 18; period++ { + for buyThread := 25.0; buyThread > 8.0; buyThread -= 2 { + for slRatio := 1.0; slRatio < 5.0; slRatio += 0.5 { + for tpRatio := 2.0; tpRatio < 15.0; tpRatio += 1.0 { + wg.Add(1) + slots <- struct{}{} + go func(period int, buyThread, tpRatio, slRatio float64) { + defer wg.Done() + account := trader.NewAccount(1000) // Move this line inside the goroutine + signalEvents := df.RsiStrategy2(period, buyThread, tpRatio, slRatio, account) + + if signalEvents == nil { + <-slots + return + } + + if analytics.TotalTrades(signalEvents) < 35 { + <-slots + return + } + + // if analytics.NetProfit(signalEvents) < marketDefault { + // return + // } + + // if analytics.PayOffRatio(signalEvents) < 1.50 { + // <-slots + // return + // } + + // if analytics.SQN(signalEvents) < 3.2 { + // <-slots + // return + // } + + p := analytics.SortinoRatio(signalEvents, 0.02) + mu.Lock() + if performance == 0 || performance < p { + performance = p + bestPeriod = period + bestBuyThread = buyThread + bestTpRatio = tpRatio + bestSlRatio = slRatio + } + + mu.Unlock() + <-slots + }(period, buyThread, tpRatio, slRatio) + } + } + } + } + + wg.Wait() + + fmt.Println("最高パフォーマンス", performance, "最適なピリオド", bestPeriod, "最適な買いライン", bestBuyThread, "最適なTPレシオ", bestTpRatio, "最適なSLレシオ", bestSlRatio) + + return performance, bestPeriod, bestBuyThread, bestTpRatio, bestSlRatio +} + +func RunRsi2Optimize() { + + df, account, _ := RadyBacktest() + + performance, bestPeriod, bestBuyThread, bestTpRatio, bestSlRatio := df.OptimizeRsi2() + + if performance > 0 { + + df.Signal = df.RsiStrategy2(bestPeriod, bestBuyThread, bestTpRatio, bestSlRatio, account) + Result(df.Signal) + + } else { + fmt.Println("マイナス利益です") + } + +} diff --git a/pkg/strategey/strategy.go b/pkg/strategey/strategy.go index a75b471..1fc9a3d 100755 --- a/pkg/strategey/strategy.go +++ b/pkg/strategey/strategy.go @@ -23,14 +23,14 @@ import ( var initialBalance float64 = 1000.00 var riskSize float64 = 0.9 -type DataFrameCandleCsv struct { +type DataFrameCandle struct { AssetName string Duration string Candles []data.Candle Signal *execute.SignalEvents } -type DataFrameCandle struct { +type DataFrameCandleCsv struct { AssetName string Duration string Candles []data.Candle @@ -71,7 +71,7 @@ type Strategy struct { Arbitrage bool } -func GetCsvDataFrame(assetName string, duration string, start, end string) (*DataFrameCandleCsv, error) { +func GetCsvDataFrame(assetName string, duration string, start, end string) (*DataFrameCandle, error) { // get the list of csv files from the directory dir := fmt.Sprintf("pkg/data/spot/monthly/klines/%s/%s", assetName, duration) files, err := os.ReadDir(dir) @@ -170,8 +170,8 @@ func GetCsvDataFrame(assetName string, duration string, start, end string) (*Dat candles = append(candles, candle) } - // create a DataFrameCandleCsv from the slice of data.Candle - dfCandle := &DataFrameCandleCsv{ + // create a DataFrameCandle from the slice of data.Candle + dfCandle := &DataFrameCandle{ AssetName: assetName, Duration: duration, Candles: candles, @@ -180,7 +180,7 @@ func GetCsvDataFrame(assetName string, duration string, start, end string) (*Dat return dfCandle, nil } -func GetCandleData(assetName string, duration string) (*DataFrameCandle, error) { +func GetCandleData(assetName string, duration string, start string, end string) (*DataFrameCandle, error) { db, err := sql.Open("sqlite3", "db/kline.db") if err != nil { @@ -188,7 +188,16 @@ func GetCandleData(assetName string, duration string) (*DataFrameCandle, error) } defer db.Close() - query := fmt.Sprintf("SELECT * FROM %s_%s", assetName, duration) + var query string + if strings.TrimSpace(start) != "" && strings.TrimSpace(end) != "" { + // startとendが空文字でない場合は、WHERE句を追加する + startTime, _ := time.Parse("20060102", start) + endTime, _ := time.Parse("20060102", end) + query = fmt.Sprintf("SELECT * FROM %s_%s WHERE date BETWEEN '%s' AND '%s'", assetName, duration, startTime.Format(time.RFC3339), endTime.Format(time.RFC3339)) + } else { + // startとendが空文字の場合は、WHERE句を追加しない + query = fmt.Sprintf("SELECT * FROM %s_%s", assetName, duration) + } rows, err := db.Query(query) if err != nil { log.Fatal(err) @@ -322,13 +331,13 @@ func (df *DataFrameCandleCsv) Hlc3() []float64 { return s } -func RadyBacktest() (*DataFrameCandleCsv, *trader.Account, error) { +func RadyBacktest() (*DataFrameCandle, *trader.Account, error) { var err error btcfg, err := config.Yaml() if err != nil { - return &DataFrameCandleCsv{}, &trader.Account{}, nil + return &DataFrameCandle{}, &trader.Account{}, nil } account := trader.NewAccount(1000) @@ -337,9 +346,9 @@ func RadyBacktest() (*DataFrameCandleCsv, *trader.Account, error) { start := btcfg.Start end := btcfg.End - df, err := GetCsvDataFrame(assetName, duration, start, end) + df, err := GetCandleData(assetName, duration, start, end) if err != nil { - return &DataFrameCandleCsv{}, &trader.Account{}, err + return &DataFrameCandle{}, &trader.Account{}, err } return df, account, nil @@ -381,6 +390,7 @@ func Result(s *execute.SignalEvents) { 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)) fmt.Println("勝ちトレード回数", analytics.WinningTrades(s)) fmt.Println("負けトレード回数", analytics.LosingTrades(s)) @@ -391,13 +401,59 @@ func Result(s *execute.SignalEvents) { 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)) fmt.Println("負けトレードの平均バー数", analytics.AverageLosingHoldingBars(s)) fmt.Printf("バイアンドホールドした時の利益: %f, 倍率: %f\n", profit, multiple) fmt.Println("1トレードの最大損失と日時", ml, mt) // fmt.Println("バルサラの破産確率", analytics.BalsaraAxum(s)) - // fmt.Println(s) + fmt.Println(s) fmt.Println("--------------------------------------------") } + +func GetCandleData_old(assetName string, duration string) (*DataFrameCandle, error) { + + db, err := sql.Open("sqlite3", "db/kline.db") + if err != nil { + log.Fatal(err) + } + defer db.Close() + + query := fmt.Sprintf("SELECT * FROM %s_%s", assetName, duration) + rows, err := db.Query(query) + if err != nil { + log.Fatal(err) + } + defer rows.Close() + + var candles []data.Candle + for rows.Next() { + var k data.Candle + var dateStr string + err := rows.Scan(&dateStr, &k.Open, &k.High, &k.Low, &k.Close, &k.Volume) + if err != nil { + log.Fatal(err) + } + k.Date, err = dbquery.ConvertRFC3339ToTime(dateStr) + if err != nil { + log.Fatal(err) + } + k.AssetName = assetName + k.Duration = duration + candles = append(candles, k) + } + if err := rows.Err(); err != nil { + log.Fatal(err) + } + + dfCandle := &DataFrameCandle{ + AssetName: assetName, + Duration: duration, + Candles: candles, + } + + return dfCandle, nil +} diff --git a/pkg/strategey/supertorend_choppy.go b/pkg/strategey/supertorend_choppy.go index c41dd42..ae90bce 100644 --- a/pkg/strategey/supertorend_choppy.go +++ b/pkg/strategey/supertorend_choppy.go @@ -11,7 +11,7 @@ import ( "v1/pkg/trader" ) -func (df *DataFrameCandleCsv) SuperTrendChoppyStrategy(atrPeriod int, factor float64, choppy int, account *trader.Account) *execute.SignalEvents { +func (df *DataFrameCandle) SuperTrendChoppyStrategy(atrPeriod int, factor float64, choppy int, duration int, account *trader.Account) *execute.SignalEvents { var StrategyName = "SUPERTREND_CHOPPY" // var err error @@ -27,6 +27,7 @@ func (df *DataFrameCandleCsv) SuperTrendChoppyStrategy(atrPeriod int, factor flo h := df.Highs() l := df.Lows() c := df.Closes() + // hlc3 := df.Hlc3() superTrend, _ := indicators.SuperTrend(atrPeriod, factor, h, l, c) @@ -34,13 +35,13 @@ func (df *DataFrameCandleCsv) SuperTrendChoppyStrategy(atrPeriod int, factor flo // stLow := superTrend.UpperBand st := superTrend.SuperTrend - // rsiValue := talib.Rsi(df.Closes(), 14) + // rsi := talib.Rsi(hlc3, 14) buySize := 0.0 buyPrice := 0.0 slRatio := 0.9 - index := risk.ChoppySlice(c, h, l) + index := risk.ChoppySlice(duration, c, h, l) choppyEma := risk.ChoppyEma(index, choppy) isBuyHolding := false @@ -51,19 +52,17 @@ func (df *DataFrameCandleCsv) SuperTrendChoppyStrategy(atrPeriod int, factor flo // fmt.Printf("Skipping iteration %d due to insufficient data.\n", i) continue } - if c[i-1] < st[i-1] && c[i] >= st[i] && choppyEma[i] > 50 && !isBuyHolding { + if (c[i-1] < st[i-1] && c[i] >= st[i]) && choppyEma[i] > 50 && !isBuyHolding { accountBalance := account.GetBalance() buySize = account.TradeSize(riskSize) / c[i] buyPrice = c[i] if account.Buy(c[i], buySize) { - signalEvents.Buy(StrategyName, df.AssetName, df.Duration, t[i], c[i], buySize, accountBalance, false) isBuyHolding = true - } } - if c[i-1] > st[i-1] && c[i] <= st[i] || (c[i] <= buyPrice*slRatio) && isBuyHolding { + if (c[i-1] > st[i-1] && c[i] <= st[i] || (c[i] <= buyPrice*slRatio)) && isBuyHolding { accountBalance := account.GetBalance() if account.Sell(c[i]) { signalEvents.Sell(StrategyName, df.AssetName, df.Duration, t[i], c[i], buySize, accountBalance, false) @@ -79,13 +78,14 @@ func (df *DataFrameCandleCsv) SuperTrendChoppyStrategy(atrPeriod int, factor flo return signalEvents } -func (df *DataFrameCandleCsv) OptimizeSuperTrend() (performance float64, bestAtrPeriod int, bestFactor float64, bestChoppy int) { +func (df *DataFrameCandle) OptimizeSuperTrend() (performance float64, bestAtrPeriod int, bestFactor float64, bestChoppy int, bestDuration int) { runtime.GOMAXPROCS(10) bestAtrPeriod = 21 bestFactor = 3.0 bestChoppy = 13 + bestDuration = 30 - limit := 1000 + limit := 3000 slots := make(chan struct{}, limit) // a := trader.NewAccount(1000) @@ -94,78 +94,80 @@ func (df *DataFrameCandleCsv) OptimizeSuperTrend() (performance float64, bestAtr var mu sync.Mutex var wg sync.WaitGroup - for atrPeriod := 9; atrPeriod < 40; atrPeriod += 1 { - for factor := 2.0; factor < 8.0; factor += 0.2 { - for choppy := 5; choppy < 18; choppy += 1 { - - wg.Add(1) - slots <- struct{}{} - - go func(atrPeriod int, factor float64, choppy int) { - defer wg.Done() - account := trader.NewAccount(1000) // Move this line inside the goroutine - signalEvents := df.SuperTrendChoppyStrategy(atrPeriod, factor, choppy, account) - - if signalEvents == nil { - return - } - - if analytics.TotalTrades(signalEvents) < 5 { + for atrPeriod := 5; atrPeriod < 40; atrPeriod += 2 { + for factor := 2.0; factor < 8.0; factor += 0.5 { + for choppy := 5; choppy < 18; choppy += 2 { + for duration := 10; duration < 200; duration += 10 { + + wg.Add(1) + slots <- struct{}{} + + go func(atrPeriod int, factor float64, choppy int, duration int) { + defer wg.Done() + account := trader.NewAccount(1000) // Move this line inside the goroutine + signalEvents := df.SuperTrendChoppyStrategy(atrPeriod, factor, choppy, duration, account) + + if signalEvents == nil { + return + } + + if analytics.TotalTrades(signalEvents) < 30 { + <-slots + return + } + + // if analytics.NetProfit(signalEvents) < marketDefault { + // <-slots + // return + // } + + // if analytics.SQN(signalEvents) < 3.2 { + // <-slots + // return + // } + + // if analytics.PayOffRatio(signalEvents) < 1 { + // <-slots + // return + // } + + // pf := analytics.SortinoRatio(signalEvents, 0.02) + p := analytics.SQN(signalEvents) + mu.Lock() + if performance == 0 || performance < p { + performance = p + bestAtrPeriod = atrPeriod + bestFactor = factor + bestChoppy = choppy + bestDuration = duration + + } <-slots - return - } - - // if analytics.NetProfit(signalEvents) < marketDefault { - // <-slots - // return - // } - - // if analytics.WinRate(signalEvents) < 0.50 { - // <-slots - - // return - // } - - // if analytics.PayOffRatio(signalEvents) < 1 { - // <-slots - - // return - // } - - p := analytics.SQN(signalEvents) - mu.Lock() - if performance == 0 || performance < p { - performance = p - bestAtrPeriod = atrPeriod - bestFactor = factor - bestChoppy = choppy - - } - <-slots - mu.Unlock() + mu.Unlock() - }(atrPeriod, factor, choppy) + }(atrPeriod, factor, choppy, duration) + } } } } wg.Wait() - fmt.Println("最高のSQN", performance, "最適なATR", bestAtrPeriod, "最適なファクター", bestFactor, "最適なチョッピー", bestChoppy) + fmt.Println("最高のパフォーマンス", performance, "最適なATR", bestAtrPeriod, "最適なファクター", bestFactor, "最適なチョッピー", bestChoppy, "最適なチョッピー期間", bestDuration) - return performance, bestAtrPeriod, bestFactor, bestChoppy + return performance, bestAtrPeriod, bestFactor, bestChoppy, bestDuration } -func RunBacktestSuperTrend() { +func RunSTOptimize() { df, account, _ := RadyBacktest() - performance, bestAtrPeriod, bestFactor, bestChoppy := df.OptimizeSuperTrend() + performance, bestAtrPeriod, bestFactor, bestChoppy, bestDuration := df.OptimizeSuperTrend() if performance > 0 { - df.Signal = df.SuperTrendChoppyStrategy(bestAtrPeriod, bestFactor, bestChoppy, account) + df.Signal = df.SuperTrendChoppyStrategy(bestAtrPeriod, bestFactor, bestChoppy, bestDuration, account) Result(df.Signal) } @@ -176,7 +178,7 @@ func SuperTrendBacktest() { df, account, _ := RadyBacktest() - df.Signal = df.SuperTrendChoppyStrategy(21, 3.0, 13, account) + df.Signal = df.SuperTrendChoppyStrategy(7, 2.5, 11, 30, account) Result(df.Signal) } diff --git a/pkg/strategey/supertrend.go b/pkg/strategey/supertrend.go index 811c794..de033e9 100644 --- a/pkg/strategey/supertrend.go +++ b/pkg/strategey/supertrend.go @@ -12,7 +12,7 @@ import ( "v1/pkg/trader" ) -func (df *DataFrameCandleCsv) SuperTrend(atrPeriod int, factor float64, account *trader.Account) *execute.SignalEvents { +func (df *DataFrameCandle) SuperTrend(atrPeriod int, factor float64, account *trader.Account) *execute.SignalEvents { var StrategyName = "SUPERTREND" // var err error @@ -61,7 +61,7 @@ func (df *DataFrameCandleCsv) SuperTrend(atrPeriod int, factor float64, account } } - if c[i-1] > st[i-1] && c[i] <= st[i] || (c[i] <= buyPrice*slRatio) && isBuyHolding { + if (c[i-1] > st[i-1] && c[i] <= st[i] || (c[i] <= buyPrice*slRatio)) && isBuyHolding { accountBalance := account.GetBalance() if account.Sell(c[i]) { signalEvents.Sell(StrategyName, df.AssetName, df.Duration, t[i], c[i], buySize, accountBalance, false) @@ -77,7 +77,7 @@ func (df *DataFrameCandleCsv) SuperTrend(atrPeriod int, factor float64, account return signalEvents } -func (df *DataFrameCandleCsv) OptimizeST() (performance float64, bestAtrPeriod int, bestFactor float64) { +func (df *DataFrameCandle) OptimizeST() (performance float64, bestAtrPeriod int, bestFactor float64) { runtime.GOMAXPROCS(10) bestAtrPeriod = 21 bestFactor = 3.0 @@ -122,13 +122,13 @@ func (df *DataFrameCandleCsv) OptimizeST() (performance float64, bestAtrPeriod i // return // } - // if analytics.PayOffRatio(signalEvents) < 1 { - // <-slots + if analytics.SQN(signalEvents) < 3.2 { + <-slots - // return - // } + return + } - p := analytics.SQN(signalEvents) + p := analytics.NetProfit(signalEvents) mu.Lock() if performance == 0 || performance < p { performance = p @@ -145,8 +145,7 @@ func (df *DataFrameCandleCsv) OptimizeST() (performance float64, bestAtrPeriod i wg.Wait() - fmt.Println("最高のSQN", performance, "最適なATR", bestAtrPeriod, "最適なファクター", bestFactor) - + fmt.Println("最高パフォーマンス", performance, "最適なATR", bestAtrPeriod, "最適なファクター", bestFactor) return performance, bestAtrPeriod, bestFactor } diff --git a/pkg/strategey/supertrend_donchain.go b/pkg/strategey/supertrend_donchain.go new file mode 100644 index 0000000..3803885 --- /dev/null +++ b/pkg/strategey/supertrend_donchain.go @@ -0,0 +1,209 @@ +package strategey + +import ( + "fmt" + "runtime" + "sync" + "v1/pkg/analytics" + "v1/pkg/execute" + "v1/pkg/indicator/indicators" + "v1/pkg/management/risk" + "v1/pkg/trader" + + "github.com/markcheno/go-talib" +) + +func (df *DataFrameCandle) STDonchain(atrPeriod int, factor float64, dcPeriod int, duration int, account *trader.Account) *execute.SignalEvents { + + var StrategyName = "STDONCHAIN" + // var err error + + lenCandles := len(df.Candles) + + if lenCandles <= atrPeriod { + return nil + } + + signalEvents := execute.NewSignalEvents() + t := df.Time() + h := df.Highs() + l := df.Lows() + c := df.Closes() + + superTrend, _ := indicators.SuperTrend(atrPeriod, factor, h, l, c) + + // stUp := superTrend.UpperBand + // stLow := superTrend.UpperBand + st := superTrend.SuperTrend + + // rsiValue := talib.Rsi(df.Closes(), 14) + + buySize := 0.0 + buyPrice := 0.0 + slRatio := 0.9 + + index := risk.ChoppySlice(duration, c, h, l) + choppyEma := risk.ChoppyEma(index, 11) + + donchain := indicators.Donchain(h, l, dcPeriod) + ema := talib.Ema(df.Hlc3(), 89) + + isBuyHolding := false + + for i := 1; i < len(choppyEma); i++ { + + if i < atrPeriod { + // fmt.Printf("Skipping iteration %d due to insufficient data.\n", i) + continue + } + if c[i-1] < st[i-1] && c[i] >= st[i] && choppyEma[i] > 50 && c[i] > ema[i] && !isBuyHolding { + + accountBalance := account.GetBalance() + buySize = account.TradeSize(riskSize) / c[i] + buyPrice = c[i] + if account.Buy(c[i], buySize) { + + signalEvents.Buy(StrategyName, df.AssetName, df.Duration, t[i], c[i], buySize, accountBalance, false) + isBuyHolding = true + + } + } + + if c[i] > donchain.High[i-1] && choppyEma[i] > 50 && c[i] > ema[i] && !isBuyHolding { + + accountBalance := account.GetBalance() + buySize = account.TradeSize(riskSize) / c[i] + buyPrice = c[i] + if account.Buy(c[i], buySize) { + + signalEvents.Buy(StrategyName, df.AssetName, df.Duration, t[i], c[i], buySize, accountBalance, false) + isBuyHolding = true + + } + } + + if (c[i-1] > st[i-1] && c[i] <= st[i] || c[i] < donchain.Low[i-1] || (c[i] <= buyPrice*slRatio)) && isBuyHolding { + accountBalance := account.GetBalance() + if account.Sell(c[i]) { + signalEvents.Sell(StrategyName, df.AssetName, df.Duration, t[i], c[i], buySize, accountBalance, false) + isBuyHolding = false + buySize = 0.0 + account.PositionSize = buySize + + } + } + } + + // fmt.Println(signalEvents) + return signalEvents +} + +func (df *DataFrameCandle) OptimizeSTDonchain() (performance float64, bestAtrPeriod int, bestFactor float64, bestDc int, bestDuration int) { + runtime.GOMAXPROCS(10) + bestAtrPeriod = 21 + bestFactor = 3.0 + bestDc = 40 + bestDuration = 30 + + limit := 1000 + slots := make(chan struct{}, limit) + + // var accountPool = sync.Pool{ + // New: func() interface{} { + // return trader.NewAccount(1000) + // }, + // } + + // a := trader.NewAccount(1000) + // marketDefault, _ := BuyAndHoldingStrategy(a) + + var mu sync.Mutex + var wg sync.WaitGroup + + for atrPeriod := 5; atrPeriod < 40; atrPeriod += 2 { + for factor := 2.0; factor < 8.0; factor += 0.5 { + for dc := 10; dc < 60; dc += 10 { + for duration := 30; duration < 150; duration += 10 { + + wg.Add(1) + slots <- struct{}{} + + go func(atrPeriod int, factor float64, dc int, duration int) { + defer wg.Done() + // account := accountPool.Get().(*trader.Account) + // defer accountPool.Put(account) + account := trader.NewAccount(1000) + signalEvents := df.STDonchain(atrPeriod, factor, dc, duration, account) + + if analytics.TotalTrades(signalEvents) < 30 { + <-slots + return + } + + // if analytics.NetProfit(signalEvents) < marketDefault { + // <-slots + // return + // } + + // if analytics.SQN(signalEvents) < 3.2 { + // <-slots + // return + // } + + // if analytics.PayOffRatio(signalEvents) < 1 { + // <-slots + + // return + // } + + // pf := analytics.SortinoRatio(signalEvents, 0.02) + p := analytics.SQN(signalEvents) + mu.Lock() + if performance == 0 || performance < p { + performance = p + bestAtrPeriod = atrPeriod + bestFactor = factor + bestDc = dc + bestDuration = duration + + } + <-slots + mu.Unlock() + + }(atrPeriod, factor, dc, duration) + + } + } + } + } + + wg.Wait() + + fmt.Println("最高のパフォーマンス", performance, "最適なATR", bestAtrPeriod, "最適なファクター", bestFactor, "最適なドンチャン", bestDc, "最適なチョッピー期間", bestDuration) + + return performance, bestAtrPeriod, bestFactor, bestDc, bestDuration +} + +func RunSTDonchainOptimize() { + + df, account, _ := RadyBacktest() + + performance, bestAtrPeriod, bestFactor, bestDc, bestDuration := df.OptimizeSTDonchain() + + if performance > 0 { + + df.Signal = df.STDonchain(bestAtrPeriod, bestFactor, bestDc, bestDuration, account) + Result(df.Signal) + + } + +} + +func STDonchainBacktest() { + + df, account, _ := RadyBacktest() + + df.Signal = df.STDonchain(13, 3.0, 60, 30, account) + Result(df.Signal) + +} diff --git a/pkg/strategey/supertrend_rsi.go b/pkg/strategey/supertrend_rsi.go new file mode 100644 index 0000000..23d21b3 --- /dev/null +++ b/pkg/strategey/supertrend_rsi.go @@ -0,0 +1,186 @@ +package strategey + +import ( + "fmt" + "runtime" + "sync" + "v1/pkg/analytics" + "v1/pkg/execute" + "v1/pkg/indicator/indicators" + "v1/pkg/management/risk" + "v1/pkg/trader" + + "github.com/markcheno/go-talib" +) + +func (df *DataFrameCandle) SuperTrendRSI(atrPeriod int, factor float64, choppy int, duration int, account *trader.Account) *execute.SignalEvents { + + var StrategyName = "SUPERTREND_RSI" + // var err error + + lenCandles := len(df.Candles) + + if lenCandles <= atrPeriod { + return nil + } + + signalEvents := execute.NewSignalEvents() + t := df.Time() + h := df.Highs() + l := df.Lows() + c := df.Closes() + hlc3 := df.Hlc3() + + superTrend, _ := indicators.SuperTrend(atrPeriod, factor, h, l, c) + + // stUp := superTrend.UpperBand + // stLow := superTrend.UpperBand + st := superTrend.SuperTrend + + rsi := talib.Rsi(hlc3, 14) + + buySize := 0.0 + buyPrice := 0.0 + slRatio := 0.9 + + index := risk.ChoppySlice(duration, c, h, l) + choppyEma := risk.ChoppyEma(index, choppy) + + isBuyHolding := false + + for i := 1; i < len(choppyEma); i++ { + + if i < atrPeriod { + // fmt.Printf("Skipping iteration %d due to insufficient data.\n", i) + continue + } + if (c[i-1] < st[i-1] && c[i] >= st[i] || rsi[i-1] < 20 && rsi[i] >= 20) && choppyEma[i] > 50 && !isBuyHolding { + + accountBalance := account.GetBalance() + buySize = account.TradeSize(riskSize) / c[i] + buyPrice = c[i] + if account.Buy(c[i], buySize) { + signalEvents.Buy(StrategyName, df.AssetName, df.Duration, t[i], c[i], buySize, accountBalance, false) + isBuyHolding = true + } + } + if (c[i-1] > st[i-1] && c[i] <= st[i] || (c[i] <= buyPrice*slRatio)) && isBuyHolding { + accountBalance := account.GetBalance() + if account.Sell(c[i]) { + signalEvents.Sell(StrategyName, df.AssetName, df.Duration, t[i], c[i], buySize, accountBalance, false) + isBuyHolding = false + buySize = 0.0 + account.PositionSize = buySize + + } + } + } + + // fmt.Println(signalEvents) + return signalEvents +} + +func (df *DataFrameCandle) OptimizeSuperTrendRSI() (performance float64, bestAtrPeriod int, bestFactor float64, bestChoppy int, bestDuration int) { + runtime.GOMAXPROCS(10) + bestAtrPeriod = 21 + bestFactor = 3.0 + bestChoppy = 13 + bestDuration = 30 + + limit := 3000 + slots := make(chan struct{}, limit) + + // a := trader.NewAccount(1000) + // marketDefault, _ := BuyAndHoldingStrategy(a) + + var mu sync.Mutex + var wg sync.WaitGroup + + for atrPeriod := 5; atrPeriod < 40; atrPeriod += 2 { + for factor := 2.0; factor < 8.0; factor += 0.5 { + for choppy := 5; choppy < 18; choppy += 2 { + for duration := 10; duration < 200; duration += 10 { + + wg.Add(1) + slots <- struct{}{} + + go func(atrPeriod int, factor float64, choppy int, duration int) { + defer wg.Done() + account := trader.NewAccount(1000) // Move this line inside the goroutine + signalEvents := df.SuperTrendRSI(atrPeriod, factor, choppy, duration, account) + + if signalEvents == nil { + return + } + + if analytics.TotalTrades(signalEvents) < 30 { + <-slots + return + } + + // if analytics.NetProfit(signalEvents) < marketDefault { + // <-slots + // return + // } + + // if analytics.SQN(signalEvents) < 3.2 { + // <-slots + // return + // } + + // if analytics.PayOffRatio(signalEvents) < 1 { + // <-slots + // return + // } + + // pf := analytics.SortinoRatio(signalEvents, 0.02) + p := analytics.SQN(signalEvents) + mu.Lock() + if performance == 0 || performance < p { + performance = p + bestAtrPeriod = atrPeriod + bestFactor = factor + bestChoppy = choppy + bestDuration = duration + + } + <-slots + mu.Unlock() + + }(atrPeriod, factor, choppy, duration) + + } + } + } + } + + wg.Wait() + + fmt.Println("最高のパフォーマンス", performance, "最適なATR", bestAtrPeriod, "最適なファクター", bestFactor, "最適なチョッピー", bestChoppy, "最適なチョッピー期間", bestDuration) + + return performance, bestAtrPeriod, bestFactor, bestChoppy, bestDuration +} + +func RunSTRSIOptimize() { + + df, account, _ := RadyBacktest() + + performance, bestAtrPeriod, bestFactor, bestChoppy, bestDuration := df.OptimizeSuperTrendRSI() + + if performance > 0 { + + df.Signal = df.SuperTrendRSI(bestAtrPeriod, bestFactor, bestChoppy, bestDuration, account) + Result(df.Signal) + + } + +} + +func SuperTrendRSIBacktest() { + + df, account, _ := RadyBacktest() + + df.Signal = df.SuperTrendChoppyStrategy(13, 5.0, 8, 20, account) + Result(df.Signal) + +}