TOPIX Core30銘柄に対してバックテスト実施・最適化

## 目的
TOPIX Core30銘柄(2024/9/22時点)に対して、テクニカル指標を使ってバックテストの実施とパラメータの最適化を試してみる

<b>※本記事は投資成績を担保するものではありません。投資はあくまで自己責任での範囲にてよろしくお願い致します。</b>

## 環境
+ Python：v3.11.5
+ Anaconda：conda v23.7.4

## 作業メモ
### 環境整備
<b>参考リンク</b>
+ https://note.com/memoran_yukiblog/n/n7bb18631e01f

#### Anaconda の導入
以前、[こちら](https://qiita.com/marumen/items/481ffac7cc0c54948652)で導入したため、そちらの記事を参照ください

#### 必要ライブラリのインストール
- Anaconda Powershell 上で以下のライブラリをインストール
  - pandas_datareader : Web上の情報を取得できる。株関連の情報取得に使うつもりだったが、yfinanceがあればいらないかも
  - yfinance : yahoo financeから株関連データを取得することができる
  - ta-lib : 様々なテクニカル分析を行うことができる[^1]
  - backtesting : バックテストを簡単に行うことができる

```
> pip.exe install pandas_datareader
> pip.exe install yfinance
> conda install -c conda-forge ta-lib
> pip.exe install backtesting
```
[^1]:これだけpipでインストールできなかったため、https://note.com/memoran_yukiblog/n/n7bb18631e01f を参考にインストール

### 動作確認
<b>参考リンク</b>
+ https://qiita.com/aguilarklyno/items/9a1c1a6f5e478c81c725
+ https://qiita.com/royal_straight_flush/items/4c390b3c5a769a26dc88

簡単にインストールしたライブラリの動作確認を行う

#### yfinance
##### ソースコード
```python
import yfinance as yf
import datetime

# 変数定義
stock_data = "3382.T"
start_date = datetime.datetime(2024, 1, 1)
end_date = datetime.datetime(2024, 9, 22)

# データ取得
ticker = yf.Ticker(stock_data)
data = yf.download(stock_data, start_date, end_date)
```

##### 結果確認
<font color="red">各種株関連データを取得できることを確認</font>
```
ticker.info

{'address1': '8-8, Nibancho',
 'address2': 'Chiyoda-ku',
 'city': 'Tokyo',
 'zip': '102-8452',
 'country': 'Japan',
 'phone': '81 3 6238 3000',
 'website': 'https://www.7andi.com',
 'industry': 'Grocery Stores',
 'industryKey': 'grocery-stores',
 'industryDisp': 'Grocery Stores',
 'sector': 'Consumer Defensive',
 'sectorKey': 'consumer-defensive',
 'sectorDisp': 'Consumer Defensive',
 'longBusinessSummary': 'Seven & i Holdings Co., Ltd. operates convenience stores, superstores, department stores, supermarkets, and specialty stores. It operates through six segments: Domestic Convenience Store operations, Overseas Convenience Store Operations, Superstore Operations, Department and Specialty Store Operations, Financial Services, and Others. The company engages in the operation of convenience stores comprising directly managed corporate stores and franchised stores; retail business that provides daily life necessities, such as food and other daily necessities and dollar merchandise and services; banking, leasing, and credit card business; and real estate business. Seven & i Holdings Co., Ltd. was incorporated in 2005 and is headquartered in Tokyo, Japan.',
 'fullTimeEmployees': 77902,
 'companyOfficers': [{'maxAge': 1,
   'name': 'Mr. Ryuichi  Isaka',
   'age': 66,
   'title': 'CEO, President & Representative Director',
   'yearBorn': 1957,
   'fiscalYear': 2024,
   'totalPay': 205000000,
   'exercisedValue': 0,
   'unexercisedValue': 0},
  {'maxAge': 1,
   'name': 'Mr. Junro  Ito',
   'age': 65,
   'title': 'Sr. Managing Ex. Officer, CSuO, GM of ESG Dev, Supervising Off. of Sup. Op. & Rep. Director',
   'yearBorn': 1958,
   'fiscalYear': 2024,
   'totalPay': 80000000,
   'exercisedValue': 0,
   'unexercisedValue': 0},
  {'maxAge': 1,
   'name': 'Mr. Fumihiko  Nagamatsu',
   'age': 66,
   'title': 'Senior Managing Executive Officer & Director',
   'yearBorn': 1957,
   'fiscalYear': 2024,
   'totalPay': 107000000,
   'exercisedValue': 0,
   'unexercisedValue': 0},
  {'maxAge': 1,
   'name': 'Mr. Yoshimichi  Maruyama',
   'age': 64,
   'title': 'CFO, Managing Exe. Officer, Head of Corporate Finance & Accounting Division and Director',
   'yearBorn': 1959,
   'fiscalYear': 2024,
   'exercisedValue': 0,
   'unexercisedValue': 0},
  {'maxAge': 1,
   'name': 'Mr. Tamaki  Wakita',
   'age': 51,
   'title': 'Operating Officer, Chief Strategy Officer, Head of Corporate planning Division & Director',
   'yearBorn': 1972,
   'fiscalYear': 2024,
   'exercisedValue': 0,
   'unexercisedValue': 0},
  {'maxAge': 1,
   'name': 'Mr. Joseph Michael  DePinto',
   'age': 61,
   'title': 'Senior Managing Executive Officer & Director',
   'yearBorn': 1962,
   'fiscalYear': 2024,
   'totalPay': 3786000000,
   'exercisedValue': 0,
   'unexercisedValue': 0},
  {'maxAge': 1,
   'name': 'Mr. Takuji  Hayashi',
   'title': 'Executive Managing Officer',
   'fiscalYear': 2024,
   'exercisedValue': 0,
   'unexercisedValue': 0},
  {'maxAge': 1,
   'name': 'Mr. Tomihiro  Saegusa',
   'title': 'Executive Managing Officer',
   'fiscalYear': 2024,
   'exercisedValue': 0,
   'unexercisedValue': 0},
  {'maxAge': 1,
   'name': 'Seiichiro  Ishibashi',
   'title': 'Executive Managing Officer',
   'fiscalYear': 2024,
   'exercisedValue': 0,
   'unexercisedValue': 0},
  {'maxAge': 1,
   'name': 'Koichiro  Otaka',
   'title': 'Managing Executive Officer',
   'fiscalYear': 2024,
   'exercisedValue': 0,
   'unexercisedValue': 0}],
 'auditRisk': 1,
 'boardRisk': 3,
 'compensationRisk': 1,
 'shareHolderRightsRisk': 4,
 'overallRisk': 2,
 'governanceEpochDate': 1726617600,
 'compensationAsOfEpochDate': 1735603200,
 'maxAge': 86400,
 'priceHint': 2,
 'previousClose': 2158.0,
 'open': 2180.0,
 'dayLow': 2151.5,
 'dayHigh': 2203.5,
 'regularMarketPreviousClose': 2158.0,
 'regularMarketOpen': 2180.0,
 'regularMarketDayLow': 2151.5,
 'regularMarketDayHigh': 2203.5,
 'dividendRate': 40.0,
 'dividendYield': 0.0186,
 'exDividendDate': 1740614400,
 'payoutRatio': 0.4882,
 'fiveYearAvgDividendYield': 2.11,
 'beta': 0.302,
 'trailingPE': 27.85474,
 'forwardPE': 16.91165,
 'volume': 9458000,
 'regularMarketVolume': 9458000,
 'averageVolume': 12713453,
 'averageVolume10days': 13733800,
 'averageDailyVolume10Day': 13733800,
 'bid': 2151.0,
 'ask': 2160.5,
 'marketCap': 5581571620864,
 'fiftyTwoWeekLow': 1600.0,
 'fiftyTwoWeekHigh': 2275.0,
 'priceToSalesTrailing12Months': 0.48300886,
 'fiftyDayAverage': 1936.68,
 'twoHundredDayAverage': 1996.5625,
 'trailingAnnualDividendRate': 113.0,
 'trailingAnnualDividendYield': 0.0523633,
 'currency': 'JPY',
 'enterpriseValue': 8568291983360,
 'profitMargins': 0.01764,
 'floatShares': 2378294860,
 'sharesOutstanding': 2594269952,
 'heldPercentInsiders': 0.13299,
 'heldPercentInstitutions': 0.4375,
 'impliedSharesOutstanding': 2594269952,
 'bookValue': 1468.282,
 'priceToBook': 1.465318,
 'lastFiscalYearEnd': 1709164800,
 'nextFiscalYearEnd': 1740700800,
 'mostRecentQuarter': 1717113600,
 'earningsQuarterlyGrowth': -0.493,
 'netIncomeToCommon': 203831001088,
 'trailingEps': 77.24,
 'forwardEps': 127.22,
 'pegRatio': 0.88,
 'lastSplitFactor': '3:1',
 'lastSplitDate': 1709078400,
 'enterpriseToRevenue': 0.741,
 'enterpriseToEbitda': 8.222,
 '52WeekChange': 0.079167366,
 'SandP52WeekChange': 0.31472707,
 'lastDividendValue': 20.0,
 'lastDividendDate': 1740614400,
 'exchange': 'JPX',
 'quoteType': 'EQUITY',
 'symbol': '3382.T',
 'underlyingSymbol': '3382.T',
 'shortName': 'SEVEN & I HOLDINGS CO LTD',
 'longName': 'Seven & i Holdings Co., Ltd.',
 'firstTradeDateEpochUtc': 946944000,
 'timeZoneFullName': 'Asia/Tokyo',
 'timeZoneShortName': 'JST',
 'uuid': 'abd08f46-ce41-3d3f-907e-462ae3d1c364',
 'messageBoardId': 'finmb_23740668',
 'gmtOffSetMilliseconds': 32400000,
 'currentPrice': 2151.5,
 'targetHighPrice': 2900.0,
 'targetLowPrice': 1910.0,
 'targetMeanPrice': 2341.9,
 'targetMedianPrice': 2350.0,
 'recommendationMean': 2.2,
 'recommendationKey': 'buy',
 'numberOfAnalystOpinions': 16,
 'totalCash': 1370588971008,
 'totalCashPerShare': 528.314,
 'ebitda': 1042100977664,
 'totalDebt': 4175185051648,
 'quickRatio': 0.604,
 'currentRatio': 0.905,
 'totalRevenue': 11555837050880,
 'debtToEquity': 104.609,
 'revenuePerShare': 4384.036,
 'returnOnAssets': 0.02931,
 'returnOnEquity': 0.055689998,
 'freeCashflow': 334798258176,
 'operatingCashflow': 591511027712,
 'earningsGrowth': -0.485,
 'revenueGrowth': 0.032,
 'grossMargins': 0.29577,
 'ebitdaMargins': 0.090179995,
 'operatingMargins': 0.0217,
 'financialCurrency': 'JPY',
 'trailingPegRatio': 1.4795}
```

```
data

	         Open	    High	    Low	        Close	    Adj Close	Volume
Date						
2024-01-04	1874.333374	1887.333374	1851.666626	1881.666626	1847.162964	6571200
2024-01-05	1908.333374	1913.333374	1877.666626	1881.666626	1847.162964	6573300
2024-01-09	1881.666626	1917.000000	1878.000000	1911.333374	1876.285767	11673600
2024-01-10	1914.666626	1920.000000	1894.333374	1904.000000	1869.086792	7838100
2024-01-11	1931.666626	1956.666626	1925.333374	1952.333374	1916.533936	10124100
...	...	...	...	...	...	...
2024-09-13	2198.000000	2201.000000	2168.000000	2168.000000	2168.000000	10290300
2024-09-17	2124.500000	2171.000000	2099.500000	2158.500000	2158.500000	11857900
2024-09-18	2167.500000	2192.500000	2145.000000	2151.500000	2151.500000	8331300
2024-09-19	2177.000000	2199.500000	2147.500000	2158.000000	2158.000000	7304000
2024-09-20	2180.000000	2203.500000	2151.500000	2151.500000	2151.500000	9458000
177 rows × 6 columns
```

#### ta-lib
今回はテクニカル指標として、ボリンジャーバンドを使いたいので、その動作を確認

##### ソースコード
```python
import yfinance as yf
import talib as ta
import datetime
import matplotlib.pyplot as plt
%matplotlib inline

# 変数定義
stock_data = "3382.T"
start_date = datetime.datetime(2024, 1, 1)
end_date = datetime.datetime(2024, 9, 22)

# データ取得
ticker = yf.Ticker(stock_data)
data = yf.download(stock_data, start_date, end_date)

# 調整済み終値
close = data['Adj Close']

# 移動平均線(ここでは25日平均)
span = 25

# ボリンジャーバンド
# 上のボリンジャーバンド, 移動平均, 下のボリンジャーバンド = ta.BBANDS(調整済み終値, 日経平均日数, 上のラインの標準偏差, 下のラインの標準偏差, 移動平均の計算方法)インの標準偏差, ○○移動平均)
data['upper'], data['middle'], data['lower'] = ta.BBANDS(close, timeperiod=25, nbdevup=2, nbdevdn=2, matype=0)
```

##### 結果確認
<font color="red">ボリンジャーバンドの情報を取得できたことを確認</font>

```python
# 日付情報
date = data.index

# グラフ表示
plt.figure(figsize=(30, 15))

plt.plot(date, close, label='Adj Close', color='#99b898')
# plt.fill_between(x軸, 塗りつぶし上側, 塗りつぶし下側, 色, 透過)
plt.fill_between(date, data['upper'], data['lower'], color='gray', alpha=0.3)
plt.legend()
```

![image1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248487/44f8954a-a30b-2087-4116-b174d9afd3dd.png)



### バックテスト実施
<b>参考リンク</b>
+ https://note.com/scilabcafe/n/n41c75c5223f4
+ https://qiita.com/Fujinoinvestor/items/f2bdaabb766db443ddc0
+ https://kernc.github.io/backtesting.py/doc/backtesting/

バックテストの評価をするため、持ちっぱなしの場合との比較を行う[^2]

[^2]:「株で儲けられるのは買ったことを忘れた人だ」という話が有名だが、それもつまらないので、今回の調査をやってみた

#### TOPIX Core30銘柄に対してバックテスト
2024年の現時点(2024/9/22)の期間を対象とする
25日平均線/&plusmn;2&sigma; のボリンジャーバンドを使用してバックテスト

##### ソースコード
```python
from backtesting import Strategy
from talib import BBANDS
from backtesting.lib import crossover
from backtesting import Backtest
import yfinance as yf
import talib as ta
import datetime

# 変数定義
# 取得データリスト
stock_list = \
[
    ["3382.T"],  # セブン＆アイ
    ["4063.T"],  # 信越化
    ["4502.T"],  # 武田
    ["4503.T"],  # アステラス
    ["4568.T"],  # 第一三共
    ["6098.T"],  # リクルート
    ["6273.T"],  # ＳＭＣ
    ["6367.T"],  # ダイキン
    ["6501.T"],  # 日立
    ["6594.T"],  # ニデック
    ["6758.T"],  # ソニーＧ
    ["6861.T"],  # キーエンス
    ["6954.T"],  # ファナック
    ["6981.T"],  # 村田製
    ["7203.T"],  # トヨタ
    ["7267.T"],  # ホンダ
    ["7741.T"],  # ＨＯＹＡ
    ["7974.T"],  # 任天堂
    ["8001.T"],  # 伊藤忠
    ["8031.T"],  # 三井物
    ["8035.T"],  # 東エレク
    ["8058.T"],  # 三菱商
    ["8306.T"],  # 三菱ＵＦＪ
    ["8316.T"],  # 三井住友ＦＧ
    ["8411.T"],  # みずほＦＧ
    ["8766.T"],  # 東京海上
    ["9432.T"],  # ＮＴＴ
    ["9433.T"],  # ＫＤＤＩ
    ["9434.T"],  # ＳＢ
    ["9984.T"]   # ＳＢＧ
]

# データ取得期間
start_date = datetime.datetime(2024, 1, 1)
end_date = datetime.datetime(2024, 9, 22)

# 標準偏差/売買高の平均
stddev = 0
volume_avg = 0

# 結果
result = []
bt = []

# 株価データ取得/売買ルールのための準備
def get_stockdata(stock_name):
    
    ticker = yf.Ticker(stock_name)
    data = yf.download(stock_name, start_date, end_date)

    # 標準偏差を計算
    stddev = ta.STDDEV(data['Adj Close'], timeperiod=25, nbdev=1)
    stddev = stddev.dropna(how='all')
    stddev = sum(stddev) / len(stddev)

    # 売買高の平均を計算
    volume_avg = data['Volume']
    volume_avg = volume_avg.dropna(how='all')
    volume_avg = sum(volume_avg) / len(volume_avg)
    
    return ticker,data

# ボリンジャーバンドを使った売買ルールクラス
class BBsigma(Strategy):
    
    timeperiod_num = 25
    stddev_num = 2

    def init(self):
        # 上のボリンジャーバンド, 移動平均, 下のボリンジャーバンド = ta.BBANDS(調整済み終値, 日経平均日数, 上のラインの標準偏差, 下のラインの標準偏差, 移動平均の計算方法)インの標準偏差, ○○移動平均)
        self.upper, self.middle, self.lower  = self.I(BBANDS, self.data.Close, timeperiod=self.timeperiod_num, nbdevup=self.stddev_num, nbdevdn=self.stddev_num, matype=0)

    def next(self): # チャートデータの行ごとに呼び出される
    # ボリンジャーバンドを使って、売買ルールを作る
        if crossover(self.data.Close, self.upper):
            # 売買高が平均以上
            if self.data.Volume > volume_avg:
                # バンド幅：狭い かつ +2σより大きくなった場合は買い
                if (self.middle + self.stddev_num * stddev) > self.upper:
                    self.position.close()
                    self.buy() # 買い
                # バンド幅：広い かつ +2σより大きくなった場合は売り
                elif (self.middle + self.stddev_num * stddev) < self.upper:
                    self.position.close()
                    self.sell() # 売り                
        if crossover(self.lower, self.data.Close):
            # 売買高が平均以上
            if self.data.Volume > volume_avg:
                # バンド幅：狭い かつ -2σより小さくなった場合は売り
                if (self.middle - self.stddev_num * stddev) < self.lower: 
                    self.position.close()
                    self.sell() # 売り
                # バンド幅：広い かつ -2σより小さくなった場合は買い
                elif (self.middle - self.stddev_num * stddev) > self.lower:
                    self.position.close()
                    self.buy() # 買い

# データリストからデータを取得し、バックテスト実施
for i in range(len(stock_list)):
    stock_name = stock_list[i][0]
    ticker, data = get_stockdata(stock_name)
    stock_list[i].append(ticker.info["shortName"])

    # バックテストを設定。バックテストクラスを初期化してインスタンスを生成
    bt_tmp = Backtest(
        data, # チャートデータ
        BBsigma, # 売買戦略
        cash=1000000, # 最初の所持金
        trade_on_close=True, # True：現在の終値で取引，False：次の時間の始値で取引
    #    exclusive_orders=True
    )

    # run()メソッドでバックテストを実行 → 結果をリストへ格納
    result_tmp = bt_tmp.run()
    result.append(result_tmp)
    bt.append(bt_tmp)
```

##### 結果確認
<font color="red">バックテストの結果 vs 持ちっぱなしの比較は11勝19敗とバックテストのやや負け越しとなった</font>

※結果の `Return` はバックテストでの利益率/ `Buy & Hold Return` は持ちっぱなしの利益率を示している

```python
# 結果の表示/比較
tech = 0
buyhold = 0

print("period = {} - {}\n".format(result[0][0], result[0][1]))

for i in range(len(stock_list)):
    print("{} -------------------".format(stock_list[i][1]))
    print("Return [%] = {}".format(result[i][6]))
    print("Buy & Hold Return [%] = {}".format(result[i][7]))
    print("Num of Trades = {}".format(result[i][17]))
    print("--------------------------------------\n")
    
    # テクニカル指標 or Buy & Hold(持ちっぱなし)のどちらがいい結果か？
    if(result[i][6] > result[i][7]):
        tech += 1
    else:
        buyhold += 1

# テクニカル指標 or Buy & Hold(持ちっぱなし)のトータル勝敗
print("Total Result ###################")
print("Technical Return = {}, Buy & Hold Return = {}".format(tech, buyhold))
```

```
period = 2024-01-04 00:00:00 - 2024-09-20 00:00:00

SEVEN & I HOLDINGS CO LTD -------------------
Return [%] = 10.366537280273437
Buy & Hold Return [%] = 14.340126476091225
Num of Trades = 9
--------------------------------------

SHIN-ETSU CHEMICAL CO -------------------
Return [%] = -4.0351
Buy & Hold Return [%] = 2.3930131004366815
Num of Trades = 12
--------------------------------------

TAKEDA PHARMACEUTICAL CO LTD -------------------
Return [%] = 9.4853
Buy & Hold Return [%] = 1.183431952662722
Num of Trades = 6
--------------------------------------

ASTELLAS PHARMA -------------------
Return [%] = -9.2705
Buy & Hold Return [%] = -0.6861063464837049
Num of Trades = 5
--------------------------------------

DAIICHI SANKYO COMPANY LIMITED -------------------
Return [%] = 11.5334
Buy & Hold Return [%] = 23.230309072781655
Num of Trades = 8
--------------------------------------

RECRUIT HOLDINGS CO LTD -------------------
Return [%] = 16.622899999999998
Buy & Hold Return [%] = 60.044912765589906
Num of Trades = 12
--------------------------------------

SMC CORP -------------------
Return [%] = 1.275
Buy & Hold Return [%] = -19.798435220793
Num of Trades = 9
--------------------------------------

DAIKIN INDUSTRIES -------------------
Return [%] = -17.6735
Buy & Hold Return [%] = -21.913043478260867
Num of Trades = 8
--------------------------------------

HITACHI -------------------
Return [%] = -9.7848
Buy & Hold Return [%] = 80.53097345132744
Num of Trades = 12
--------------------------------------

NIDEC CORPORATION -------------------
Return [%] = -18.2995
Buy & Hold Return [%] = 2.7088430618664803
Num of Trades = 6
--------------------------------------

SONY GROUP CORPORATION -------------------
Return [%] = 21.8405
Buy & Hold Return [%] = 2.0618556701030926
Num of Trades = 7
--------------------------------------

KEYENCE CORP -------------------
Return [%] = 30.218
Buy & Hold Return [%] = 13.74814325796336
Num of Trades = 9
--------------------------------------

FANUC CORPORATION -------------------
Return [%] = 14.871500000000001
Buy & Hold Return [%] = -4.180763653939101
Num of Trades = 10
--------------------------------------

MURATA MANUFACTURING CO -------------------
Return [%] = -24.5258
Buy & Hold Return [%] = -5.46767537826685
Num of Trades = 11
--------------------------------------

TOYOTA MOTOR CORP -------------------
Return [%] = -16.227800000000002
Buy & Hold Return [%] = -0.03795066413662239
Num of Trades = 7
--------------------------------------

HONDA MOTOR CO -------------------
Return [%] = 8.40985
Buy & Hold Return [%] = 3.9746158984635938
Num of Trades = 8
--------------------------------------

HOYA CORP -------------------
Return [%] = 30.771500000000003
Buy & Hold Return [%] = 16.122388937922917
Num of Trades = 9
--------------------------------------

NINTENDO CO LTD -------------------
Return [%] = 0.3338
Buy & Hold Return [%] = 6.884057971014493
Num of Trades = 7
--------------------------------------

ITOCHU CORP -------------------
Return [%] = 0.6439
Buy & Hold Return [%] = 31.528279181708786
Num of Trades = 6
--------------------------------------

MITSUI & CO -------------------
Return [%] = -35.25325
Buy & Hold Return [%] = 12.895467160037002
Num of Trades = 9
--------------------------------------

TOKYO ELECTRON -------------------
Return [%] = -7.667
Buy & Hold Return [%] = 0.9997917100604041
Num of Trades = 9
--------------------------------------

MITSUBISHI CORP -------------------
Return [%] = -16.08895
Buy & Hold Return [%] = 29.071038251366122
Num of Trades = 6
--------------------------------------

MITSUBISHI UFJ FINANCIAL GROUP  -------------------
Return [%] = -7.7583
Buy & Hold Return [%] = 20.97959183673469
Num of Trades = 8
--------------------------------------

SUMITOMO MITSUI FINANCIAL GROUP -------------------
Return [%] = -26.1336
Buy & Hold Return [%] = 30.544412607449857
Num of Trades = 6
--------------------------------------

MIZUHO FINANCIAL GROUP -------------------
Return [%] = -15.10225
Buy & Hold Return [%] = 17.66741298595563
Num of Trades = 6
--------------------------------------

TOKIO MARINE HOLDINGS INC -------------------
Return [%] = -20.8246
Buy & Hold Return [%] = 49.87175833570818
Num of Trades = 11
--------------------------------------

NIPPON TEL & TEL CORP -------------------
Return [%] = -8.770042965698241
Buy & Hold Return [%] = -13.259985119367116
Num of Trades = 9
--------------------------------------

KDDI CORPORATION -------------------
Return [%] = 17.2773
Buy & Hold Return [%] = 5.65207829338025
Num of Trades = 7
--------------------------------------

SOFTBANK CORP. -------------------
Return [%] = 18.9373
Buy & Hold Return [%] = 10.85618354784555
Num of Trades = 7
--------------------------------------

SOFTBANK GROUP CORP -------------------
Return [%] = -3.2145
Buy & Hold Return [%] = 41.53719008264463
Num of Trades = 10
--------------------------------------

Total Result ###################
Technical Return = 11, Buy & Hold Return = 19
#result[0]["_equity_curve"]
```

ちなみに、結果をプロットすることもできる
```
# SEVEN & I HOLDINGSの結果をプロット
bt[0].plot()
```
![BBANDS_TOPIXcore30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248487/722a6bee-c197-61b5-db1e-7fc637050408.png)


#### パラメータを最適化してみる
ボリンジャーバンドのパラメータを以下のように変更してみて、パフォーマンスが良いものを調査する
+ 移動平均線：5 - 70日(5日刻み。5,10,15日...と変更していく)
+ 標準偏差：1&sigma; - 3&sigma;

##### ソースコード
```python
from backtesting import Strategy
from talib import BBANDS
from backtesting.lib import crossover
from backtesting import Backtest
import yfinance as yf
import talib as ta
import datetime

# 変数定義
# 取得データリスト
stock_list = \
[
    ["3382.T"],  # セブン＆アイ
    ["4063.T"],  # 信越化
    ["4502.T"],  # 武田
    ["4503.T"],  # アステラス
    ["4568.T"],  # 第一三共
    ["6098.T"],  # リクルート
    ["6273.T"],  # ＳＭＣ
    ["6367.T"],  # ダイキン
    ["6501.T"],  # 日立
    ["6594.T"],  # ニデック
    ["6758.T"],  # ソニーＧ
    ["6861.T"],  # キーエンス
    ["6954.T"],  # ファナック
    ["6981.T"],  # 村田製
    ["7203.T"],  # トヨタ
    ["7267.T"],  # ホンダ
    ["7741.T"],  # ＨＯＹＡ
    ["7974.T"],  # 任天堂
    ["8001.T"],  # 伊藤忠
    ["8031.T"],  # 三井物
    ["8035.T"],  # 東エレク
    ["8058.T"],  # 三菱商
    ["8306.T"],  # 三菱ＵＦＪ
    ["8316.T"],  # 三井住友ＦＧ
    ["8411.T"],  # みずほＦＧ
    ["8766.T"],  # 東京海上
    ["9432.T"],  # ＮＴＴ
    ["9433.T"],  # ＫＤＤＩ
    ["9434.T"],  # ＳＢ
    ["9984.T"]   # ＳＢＧ
]

# データ取得期間
start_date = datetime.datetime(2024, 1, 1)
end_date = datetime.datetime(2024, 9, 22)

# 標準偏差/売買高の平均
stddev = 0
volume_avg = 0

# 結果
result = []
bt = []

# 株価データ取得/売買ルールのための準備
def get_stockdata(stock_name):
    
    ticker = yf.Ticker(stock_name)
    data = yf.download(stock_name, start_date, end_date)

    # 標準偏差を計算
    stddev = ta.STDDEV(data['Adj Close'], timeperiod=25, nbdev=1)
    stddev = stddev.dropna(how='all')
    stddev = sum(stddev) / len(stddev)

    # 売買高の平均を計算
    volume_avg = data['Volume']
    volume_avg = volume_avg.dropna(how='all')
    volume_avg = sum(volume_avg) / len(volume_avg)
    
    return ticker,data

# ボリンジャーバンドを使った売買ルールクラス
class BBsigma(Strategy):
    
    timeperiod_num = 25
    stddev_num = 2

    def init(self):
        # 上のボリンジャーバンド, 移動平均, 下のボリンジャーバンド = ta.BBANDS(調整済み終値, 日経平均日数, 上のラインの標準偏差, 下のラインの標準偏差, 移動平均の計算方法)インの標準偏差, ○○移動平均)
        self.upper, self.middle, self.lower  = self.I(BBANDS, self.data.Close, timeperiod=self.timeperiod_num, nbdevup=self.stddev_num, nbdevdn=self.stddev_num, matype=0)

    def next(self): # チャートデータの行ごとに呼び出される
    # ボリンジャーバンドを使って、売買ルールを作る
        if crossover(self.data.Close, self.upper):
            # 売買高が平均以上
            if self.data.Volume > volume_avg:
                # バンド幅：狭い かつ +2σより大きくなった場合は買い
                if (self.middle + self.stddev_num * stddev) > self.upper:
                    self.position.close()
                    self.buy() # 買い
                # バンド幅：広い かつ +2σより大きくなった場合は売り
                elif (self.middle + self.stddev_num * stddev) < self.upper:
                    self.position.close()
                    self.sell() # 売り                
        if crossover(self.lower, self.data.Close):
            # 売買高が平均以上
            if self.data.Volume > volume_avg:
                # バンド幅：狭い かつ -2σより小さくなった場合は売り
                if (self.middle - self.stddev_num * stddev) < self.lower: 
                    self.position.close()
                    self.sell() # 売り
                # バンド幅：広い かつ -2σより小さくなった場合は買い
                elif (self.middle - self.stddev_num * stddev) > self.lower:
                    self.position.close()
                    self.buy() # 買い

# データリストからデータを取得し、バックテスト実施
for i in range(len(stock_list)):
    stock_name = stock_list[i][0]
    ticker, data = get_stockdata(stock_name)
    stock_list[i].append(ticker.info["shortName"])

    # バックテストを設定。バックテストクラスを初期化してインスタンスを生成
    bt_tmp = Backtest(
        data, # チャートデータ
        BBsigma, # 売買戦略
        cash=1000000, # 最初の所持金
        trade_on_close=True, # True：現在の終値で取引，False：次の時間の始値で取引
    #    exclusive_orders=True
    )
    
    # optimize()メソッドを実行して最適化
    result_tmp = bt_tmp.optimize(
        timeperiod_num=range(5, 75, 5),  # 移動平均日数
        stddev_num=range(1, 4, 1),       # 標準偏差
        maximize= 'Return [%]',          # Returnを最大化する
    )

    # 結果をリストへ格納
    result.append(result_tmp)
    bt.append(bt_tmp)
```

##### 結果確認
<font color="red">→ パラメータ最適化の結果、<b>バックテストの結果 vs 持ちっぱなしの比較は27勝3敗と大きく勝ち越しできるようになった</b></font>

※最適化されたパラメータは`Condition`の内容から確認可能
`Condition = BBsigma(timeperiod_num=10,stddev_num=1)`
の場合は、移動平均線：10日/標準偏差：1&sigma; になる

※結果の `Return` はバックテストでの利益率/ `Buy & Hold Return` は持ちっぱなしの利益率を示している

```python
# 結果の表示/比較
tech = 0
buyhold = 0

print("period = {} - {}\n".format(result[0][0], result[0][1]))

for i in range(len(stock_list)):
    print("{} -------------------".format(stock_list[i][1]))
    print("Return [%] = {}".format(result[i][6]))
    print("Buy & Hold Return [%] = {}".format(result[i][7]))
    print("Num of Trades = {}".format(result[i][17]))
    print("Condition = {}".format(result[i]["_strategy"]))
    print("--------------------------------------\n")
    
    # テクニカル指標 or Buy & Hold(持ちっぱなし)のどちらがいい結果か？
    if(result[i][6] > result[i][7]):
        tech += 1
    else:
        buyhold += 1

# テクニカル指標 or Buy & Hold(持ちっぱなし)のトータル勝敗
print("Total Result ###################")
print("Technical Return = {}, Buy & Hold Return = {}".format(tech, buyhold))
```

```
period = 2024-01-04 00:00:00 - 2024-09-20 00:00:00

SEVEN & I HOLDINGS CO LTD -------------------
Return [%] = 42.8526515625
Buy & Hold Return [%] = 14.340126476091225
Num of Trades = 26
Condition = BBsigma(timeperiod_num=10,stddev_num=1)
--------------------------------------

SHIN-ETSU CHEMICAL CO -------------------
Return [%] = 75.2737
Buy & Hold Return [%] = 2.3930131004366815
Num of Trades = 28
Condition = BBsigma(timeperiod_num=10,stddev_num=1)
--------------------------------------

TAKEDA PHARMACEUTICAL CO LTD -------------------
Return [%] = 35.9751
Buy & Hold Return [%] = 1.183431952662722
Num of Trades = 14
Condition = BBsigma(timeperiod_num=50,stddev_num=1)
--------------------------------------

ASTELLAS PHARMA -------------------
Return [%] = 31.371399999999998
Buy & Hold Return [%] = -0.6861063464837049
Num of Trades = 2
Condition = BBsigma(timeperiod_num=30,stddev_num=3)
--------------------------------------

DAIICHI SANKYO COMPANY LIMITED -------------------
Return [%] = 51.757600000000004
Buy & Hold Return [%] = 23.230309072781655
Num of Trades = 10
Condition = BBsigma(timeperiod_num=55,stddev_num=1)
--------------------------------------

RECRUIT HOLDINGS CO LTD -------------------
Return [%] = 44.813399999999994
Buy & Hold Return [%] = 60.044912765589906
Num of Trades = 9
Condition = BBsigma(timeperiod_num=35,stddev_num=2)
--------------------------------------

SMC CORP -------------------
Return [%] = 9.762
Buy & Hold Return [%] = -19.798435220793
Num of Trades = 10
Condition = BBsigma(timeperiod_num=20,stddev_num=2)
--------------------------------------

DAIKIN INDUSTRIES -------------------
Return [%] = 7.31
Buy & Hold Return [%] = -21.913043478260867
Num of Trades = 2
Condition = BBsigma(timeperiod_num=25,stddev_num=3)
--------------------------------------

HITACHI -------------------
Return [%] = 46.2783
Buy & Hold Return [%] = 80.53097345132744
Num of Trades = 3
Condition = BBsigma(timeperiod_num=60,stddev_num=1)
--------------------------------------

NIDEC CORPORATION -------------------
Return [%] = 34.058
Buy & Hold Return [%] = 2.7088430618664803
Num of Trades = 2
Condition = BBsigma(timeperiod_num=70,stddev_num=3)
--------------------------------------

SONY GROUP CORPORATION -------------------
Return [%] = 64.84100000000001
Buy & Hold Return [%] = 2.0618556701030926
Num of Trades = 19
Condition = BBsigma(timeperiod_num=20,stddev_num=1)
--------------------------------------

KEYENCE CORP -------------------
Return [%] = 62.248000000000005
Buy & Hold Return [%] = 13.74814325796336
Num of Trades = 2
Condition = BBsigma(timeperiod_num=35,stddev_num=3)
--------------------------------------

FANUC CORPORATION -------------------
Return [%] = 26.5008
Buy & Hold Return [%] = -4.180763653939101
Num of Trades = 15
Condition = BBsigma(timeperiod_num=45,stddev_num=1)
--------------------------------------

MURATA MANUFACTURING CO -------------------
Return [%] = 57.4
Buy & Hold Return [%] = -5.46767537826685
Num of Trades = 2
Condition = BBsigma(timeperiod_num=15,stddev_num=3)
--------------------------------------

TOYOTA MOTOR CORP -------------------
Return [%] = 53.7126
Buy & Hold Return [%] = -0.03795066413662239
Num of Trades = 2
Condition = BBsigma(timeperiod_num=20,stddev_num=3)
--------------------------------------

HONDA MOTOR CO -------------------
Return [%] = 23.8061
Buy & Hold Return [%] = 3.9746158984635938
Num of Trades = 6
Condition = BBsigma(timeperiod_num=40,stddev_num=2)
--------------------------------------

HOYA CORP -------------------
Return [%] = 75.233
Buy & Hold Return [%] = 16.122388937922917
Num of Trades = 10
Condition = BBsigma(timeperiod_num=20,stddev_num=2)
--------------------------------------

NINTENDO CO LTD -------------------
Return [%] = 72.7342
Buy & Hold Return [%] = 6.884057971014493
Num of Trades = 5
Condition = BBsigma(timeperiod_num=70,stddev_num=2)
--------------------------------------

ITOCHU CORP -------------------
Return [%] = 93.8463
Buy & Hold Return [%] = 31.528279181708786
Num of Trades = 4
Condition = BBsigma(timeperiod_num=30,stddev_num=3)
--------------------------------------

MITSUI & CO -------------------
Return [%] = 20.1515
Buy & Hold Return [%] = 12.895467160037002
Num of Trades = 1
Condition = BBsigma(timeperiod_num=15,stddev_num=3)
--------------------------------------

TOKYO ELECTRON -------------------
Return [%] = 31.03
Buy & Hold Return [%] = 0.9997917100604041
Num of Trades = 1
Condition = BBsigma(timeperiod_num=15,stddev_num=3)
--------------------------------------

MITSUBISHI CORP -------------------
Return [%] = 32.80165
Buy & Hold Return [%] = 29.071038251366122
Num of Trades = 2
Condition = BBsigma(timeperiod_num=15,stddev_num=3)
--------------------------------------

MITSUBISHI UFJ FINANCIAL GROUP  -------------------
Return [%] = 52.01235
Buy & Hold Return [%] = 20.97959183673469
Num of Trades = 2
Condition = BBsigma(timeperiod_num=35,stddev_num=3)
--------------------------------------

SUMITOMO MITSUI FINANCIAL GROUP -------------------
Return [%] = 27.055
Buy & Hold Return [%] = 30.544412607449857
Num of Trades = 2
Condition = BBsigma(timeperiod_num=30,stddev_num=3)
--------------------------------------

MIZUHO FINANCIAL GROUP -------------------
Return [%] = 44.3695
Buy & Hold Return [%] = 17.66741298595563
Num of Trades = 3
Condition = BBsigma(timeperiod_num=55,stddev_num=3)
--------------------------------------

TOKIO MARINE HOLDINGS INC -------------------
Return [%] = 53.6467
Buy & Hold Return [%] = 49.87175833570818
Num of Trades = 4
Condition = BBsigma(timeperiod_num=70,stddev_num=2)
--------------------------------------

NIPPON TEL & TEL CORP -------------------
Return [%] = 28.788753268432615
Buy & Hold Return [%] = -13.259985119367116
Num of Trades = 5
Condition = BBsigma(timeperiod_num=60,stddev_num=1)
--------------------------------------

KDDI CORPORATION -------------------
Return [%] = 20.9369
Buy & Hold Return [%] = 5.65207829338025
Num of Trades = 9
Condition = BBsigma(timeperiod_num=20,stddev_num=2)
--------------------------------------

SOFTBANK CORP. -------------------
Return [%] = 34.06125
Buy & Hold Return [%] = 10.85618354784555
Num of Trades = 6
Condition = BBsigma(timeperiod_num=50,stddev_num=2)
--------------------------------------

SOFTBANK GROUP CORP -------------------
Return [%] = 64.9918
Buy & Hold Return [%] = 41.53719008264463
Num of Trades = 4
Condition = BBsigma(timeperiod_num=70,stddev_num=2)
--------------------------------------

Total Result ###################
Technical Return = 27, Buy & Hold Return = 3
```

結果のプロットも可能
```
# SEVEN & I HOLDINGSの結果をプロット
bt[0].plot()
```
![BBANDS_Optimize_TOPIXcore30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248487/fbdc0e27-b4f2-9cda-d059-2868704d0792.png)


## まとめ
>TOPIX Core30銘柄(2024/9/22時点)に対して、テクニカル指標を使ってバックテストの実施と条件の最適化を試してみる

とりあえずの目的は達成できた
ボリンジャーバンドを使ったバックテストとパラメータの最適化により、多くの銘柄で持ちっぱなしよりよい結果を得ることができた

今後は、以下の`backtesting.py`のドキュメントには機械学習を用いた方法がTutorialsとして載っているため、時間があればそちらも試してみたい
https://kernc.github.io/backtesting.py/doc/backtesting/