Keras環境向け 学習および評価データを作成するPythonプログラム
Keras環境向けに学習および評価データを作成するPythonプログラムを作成しました
以前に、Neural Network Console向けの学習および評価データを作成するPythonプログラムを紹介しました。
しかし、評価データの作成方法に問題があることが分かりました。
そこで、改めて、AIの学習および評価データを作成するPythonプログラムを用意しました。
以前のものと比べて、主な変更点は、以下の通りです。
変更点
問題の修正
評価データの標準化に使用する平均値と標準偏差を算出する際、学習データのみを参照するように修正した
機能追加
ラベル生成関数を記述した外部ファイルをインポートできるようにした
その他
株価、等の参照先をYahoo Financeのみに変更した
出来高を説明変数に加えた
選択可能なテクニカル指標からストキャスティクスを削除した
コマンドライン引数の見直しを行った
Neural Network Console向けのヘッダ生成機能を削除した
AIの学習および評価データを作成するPythonプログラムのソースコード
以下に、Keras環境向けのAIの学習および評価データを作成するPythonプログラムのソースコードを記載します。
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import datetime
import yfinance as yf
import argparse
import talib as ta
from sklearn.preprocessing import StandardScaler
import joblib
import os
import sys
import importlib.util
from curl_cffi import requests
# 株価データをダウンロード
def DlStockData(Ticker, StartDate, EndDate, Columns):
# シンボル変換
if 'N225' == Ticker: TmpTicker = '^N225'
elif 'DOW' == Ticker: TmpTicker = '^DJI'
elif 'SP500' == Ticker: TmpTicker = '^GSPC'
elif 'NAS' == Ticker: TmpTicker = '^IXIC'
elif 'FTSE' == Ticker: TmpTicker = '^FTSE'
elif 'HSI' == Ticker: TmpTicker = '^HSI' # 香港ハンセン株価指数
elif 'SHA' == Ticker: TmpTicker = '000001.SS' # 上海総合指数
elif 'VIX' == Ticker: TmpTicker = '^VIX'
elif 'USD' == Ticker: TmpTicker = 'USDJPY=X'
elif 'EUR' == Ticker: TmpTicker = 'EURJPY=X'
elif 'CNY' == Ticker: TmpTicker = 'CNYJPY=X' # 元(中国)
elif 'IRX' == Ticker: TmpTicker = '^IRX' # 米13週国債
elif 'TNX' == Ticker: TmpTicker = '^TNX' # 米10年国債
elif 'SOX' == Ticker: TmpTicker = '^SOX' # フィラデルフィア半導体株指数
elif 'WTI' == Ticker: TmpTicker = 'CL=F' # W & T Offshore, Inc.
else: TmpTicker = Ticker + '.T'
# 最終日が1日前にズレる問題を補正
EndDate4Yahoo = (pd.to_datetime(EndDate) + datetime.timedelta(days = 1)).strftime('%Y-%m-%d')
# 株価データをダウンロード
# エラー対策でSessionを追加: YFRateLimitError('Too Many Requests. Rate limited. Try after a while.')
Session = requests.Session(impersonate = 'chrome')
Data = yf.download(TmpTicker, StartDate, EndDate4Yahoo, auto_adjust = True, multi_level_index = False, progress = False, session = Session)
# 株価データのマルチカラムをシングルカラムに変更
if 1 < Data.columns.nlevels:
TmpColumns = []
for ColName in Data.columns.values:
TmpColumns.append(ColName[0])
Data.columns = TmpColumns
# Datetimeインデックスを日付けのみに変更
Data.index = Data.index.date
# カラムの設定
# カラムが1個 → Close
# カラムが4個 → Open, High, Low, Close
# カラムが5個 → Open, High, Low, Close, Volume
TmpColumns = ['Close']
if 1 < len(Columns): TmpColumns = ['Open', 'High', 'Low'] + TmpColumns
if 5 == len(Columns): TmpColumns += ['Volume']
# カラムの順序を入れ替え
# Close, High, Low, Open, Volume → Open, High, Low, CLose, Volume
Data = Data.reindex(columns = TmpColumns)
# Volumeが0の対策 → 翌営業日のVolumeを当日のVolumeにコピー
if 5 == len(Columns):
for i in reversed(range(len(Data) - 1)):
if 0 == Data.iloc[i, Data.columns.get_loc('Volume')]:
Data.iloc[i, Data.columns.get_loc('Volume')] = Data.iloc[i + 1, Data.columns.get_loc('Volume')]
# カラム名を変更
Data = Data.set_axis(Columns, axis = 1)
return Data
# 単純移動平均(短期、中期、長期)
def FuncSMA(Data, Ticker, Columns):
WinShort: int = 5
WinMiddle: int = 25
WinLong: int = 75
RefCol = Ticker + '_C'
SMA_S = Data[RefCol].rolling(window = WinShort).mean().to_frame(Columns[0])
SMA_M = Data[RefCol].rolling(window = WinMiddle).mean().to_frame(Columns[1])
SMA_L = Data[RefCol].rolling(window = WinLong).mean().to_frame(Columns[2])
return pd.concat([SMA_S, SMA_M, SMA_L], axis = 1)
# 単純移動平均(短期、長期、差分)
def FuncSMADiff(Data, Ticker, Columns):
WinShort: int = 5
WinLong: int = 75
RefCol = Ticker + '_C'
SMA_S = Data[RefCol].rolling(window = WinShort).mean()
SMA_L = Data[RefCol].rolling(window = WinLong).mean()
SMA_Diff = SMA_S - SMA_L
# SeriesをDataFrameに変換
SMA_S = SMA_S.to_frame(Columns[0])
SMA_L = SMA_L.to_frame(Columns[1])
SMA_Diff = SMA_Diff.to_frame(Columns[2])
return pd.concat([SMA_S, SMA_L, SMA_Diff], axis = 1)
# 単純移動平均
# A: (短期 - 中期) / 中期
# B: (中期 - 長期) / 長期
def FuncSMARatio(Data, Ticker, Columns):
SMA = FuncSMA(Data, Ticker, ['SMA_S', 'SMA_M', 'SMA_L'])
Result = pd.DataFrame({
Columns[0]: (SMA['SMA_S'] - SMA['SMA_M']) / SMA['SMA_M'],
Columns[1]: (SMA['SMA_M'] - SMA['SMA_L']) / SMA['SMA_L']
})
return Result
# ボリンジャーバンド
def FuncBB(Data, Ticker, Columns):
WinBB: int = 20
RefCol = Ticker + '_C'
SMABB = Data[RefCol].rolling(window = WinBB).mean()
StdDevBB = Data[RefCol].rolling(window = WinBB).std(ddof = 0)
BBM2Sig = (SMABB - 2 * StdDevBB).to_frame(Columns[0])
BBM1Sig = (SMABB - StdDevBB).to_frame(Columns[1])
BBSMA = SMABB.to_frame(Columns[2])
BBP1Sig = (SMABB + StdDevBB).to_frame(Columns[3])
BBP2Sig = (SMABB + 2 * StdDevBB).to_frame(Columns[4])
return pd.concat([BBM2Sig, BBM1Sig, BBSMA, BBP1Sig, BBP2Sig], axis = 1)
# ボリンジャーバンド(バンド幅、バンド幅の割合)
def FuncBBWidth(Data, Ticker, Columns):
BB = FuncBB(Data, Ticker, ['BB_M2Sig', 'BB_M1Sig', 'BB_SMA', 'BB_P1Sig', 'BB_P2Sig'])
Result = pd.DataFrame({
Columns[0]: BB['BB_P2Sig'] - BB['BB_M2Sig'],
Columns[1]: (BB['BB_P2Sig'] - BB['BB_M2Sig']) / BB['BB_SMA']
})
return Result
# RSI
def FuncRSI(Data, Ticker, Columns):
SpanRSI: int = 14
RefCol = Ticker + '_C'
# RSI
RSI = ta.RSI(Data[RefCol], timeperiod = SpanRSI).to_frame(Columns[0])
return RSI
# MACD
def FuncMACD(Data, Ticker, Columns):
SpanShort: int = 12
SpanLong: int = 26
WinSignal: int = 9
RefCol = Ticker + '_C'
EMAShort = Data[RefCol].ewm(span = SpanShort).mean()
EMALong = Data[RefCol].ewm(span = SpanLong).mean()
# MACD
MACD = (EMAShort - EMALong).to_frame(Columns[0])
return MACD
# 一目均衡表
def FuncICHIMOKU(Data, Ticker, NoLagFlag, Columns):
WinBase: int = 26
WinConv: int = 9
Span1Shift: int = 25
WinSpan2: int = 52
Span2Shift: int = 25
DelayShift: int = -25
RefCol = Ticker + '_C'
RefColHigh = Ticker + '_H'
RefColLow = Ticker + '_L'
# 基準線
MaxBaseLine = Data[RefColHigh].rolling(WinBase).max()
MinBaseLine = Data[RefColLow].rolling(WinBase).min()
BaseLineData = ((MaxBaseLine + MinBaseLine) / 2).to_frame(Columns[0])
# 転換線
MaxConvLine = Data[RefColHigh].rolling(WinConv).max()
MinConvLine = Data[RefColLow].rolling(WinConv).min()
ConvLineData = ((MaxConvLine + MinConvLine) / 2).to_frame(Columns[1])
# 先行スパン1
Span1Data = ((BaseLineData[Columns[0]] + ConvLineData[Columns[1]]) / 2).shift(Span1Shift).to_frame(Columns[2])
# 先行スパン2
MaxSpan2Line = Data[RefColHigh].rolling(WinSpan2).max()
MinSpan2Line = Data[RefColLow].rolling(WinSpan2).min()
Span2Data = ((MaxSpan2Line + MinSpan2Line) / 2).shift(Span2Shift).to_frame(Columns[3])
# 遅行スパン
LagginSpanData = Data[RefCol].shift(DelayShift).to_frame('ICHI_Lag')
# 遅行スパンのNaNに最後の終値をコピー
LastCloseRow: int = len(LagginSpanData) + DelayShift - 1
for i in range(len(LagginSpanData) + DelayShift, len(LagginSpanData)):
LagginSpanData.iat[i, 0] = LagginSpanData.iat[LastCloseRow, 0]
if NoLagFlag:
# 遅行スパンなし
return pd.concat([BaseLineData, ConvLineData, Span1Data, Span2Data], axis = 1)
else:
# 遅行スパンあり
return pd.concat([BaseLineData, ConvLineData, Span1Data, Span2Data, LagginSpanData], axis = 1)
# ストキャスティクス
def FuncStochastic(Data, Ticker, Columns):
SpanK: int = 14
SpanD: int = 3
RefCol = Ticker + '_C'
RefColHigh = Ticker + '_H'
RefColLow = Ticker + '_L'
FastK, FastD = ta.STOCHF(Data[RefColHigh], Data[RefColLow], Data[RefCol], fastk_period = SpanK, fastd_period = SpanD)
# SeriesをDataFrameに変換
FastK = FastK.to_frame(Columns[0])
FastD = FastD.to_frame(Columns[1])
return pd.concat([FastK, FastD], axis = 1)
# モメンタム
def FuncMomentum(Data, Ticker, Columns):
Span: int = 5
RefCol = Ticker + '_C'
Momentum = ta.MOM(Data[RefCol], timeperiod = Span)
# SeriesをDataFrameに変換
Momentum = Momentum.to_frame(Columns[0])
return Momentum
# 移動平均乖離率
def FuncSMADeviation(Data, Ticker, Columns):
Span: int = 5
RefCol = Ticker + '_C'
SMA = Data[RefCol].rolling(window = Span).mean()
SMADev = ((Data[RefCol] - SMA) / SMA) * 100
# SeriesをDataFrameに変換
SMADev = SMADev.to_frame(Columns[0])
return SMADev
# サイコロジカルライン
def FuncPsychological(Data, Ticker, Columns):
Span: int = 12
RefCol = Ticker + '_C'
UpDays = (Data[RefCol].diff() > 0).astype(int)
Psychological = UpDays.rolling(window = Span).sum() / Span * 100
# SeriesをDataFrameに変換
Psychological = Psychological.to_frame(Columns[0])
return Psychological
# ATR
def FuncATR(Data, Ticker, Columns):
Span: int = 14
RefCol = Ticker + '_C'
RefColHigh = Ticker + '_H'
RefColLow = Ticker + '_L'
Atr = ta.ATR(Data[RefColHigh], Data[RefColLow], Data[RefCol], timeperiod = Span)
# SeriesをDataFrameに変換
Atr = Atr.to_frame(Columns[0])
return Atr
# ADX
def FuncADX(Data, Ticker, Columns):
Span: int = 14
RefCol = Ticker + '_C'
RefColHigh = Ticker + '_H'
RefColLow = Ticker + '_L'
Adx = ta.ADX(
Data[RefColHigh],
Data[RefColLow],
Data[RefCol],
timeperiod = Span
)
# SeriesをDataFrameに変換
Adx = Adx.to_frame(Columns[0])
return Adx
# Volatility
def FuncVolatility(Data, Ticker, Columns):
Span: int = 5
RefCol = Ticker + '_C'
Vol = Data[RefCol].pct_change().rolling(Span).std()
# SeriesをDataFrameに変換
Vol = Vol.to_frame(Columns[0])
return Vol
# 直近高値からの下落率
def FuncDropRate(Data, Ticker, Columns):
Span: int = 20
RefCol = Ticker + '_C'
DropRate = Data[RefCol] / Data[RefCol].rolling(Span).max()
# SeriesをDataFrameに変換
DropRate = DropRate.to_frame(Columns[0])
return DropRate
# 相対強度(日経平均株価/ダウ平均株価)
def FuncN225DOW(StartDate, EndDate, Columns):
ColN225 = ['N225_C']
ColDOW = ['DOW_C']
DataN225 = DlStockData('N225', StartDate, EndDate, ColN225)
DataDOW = DlStockData('DOW', StartDate, EndDate, ColDOW)
# インデックスを日付型に変換
DataN225.index = pd.to_datetime(DataN225.index)
DataDOW.index = pd.to_datetime(DataDOW.index)
# インデックスを基準にDataFrameを合体
TmpData = pd.concat([DataN225, DataDOW], axis = 1)
# Ratio = TmpData[ColN225] / TmpData[ColDOW]
Ratio = TmpData.iloc[:, 0] / TmpData.iloc[:, 1]
# SeriesをDataFrameに変換
Ratio = Ratio.to_frame(Columns[0])
return Ratio
# n日間の騰落率(n = 3, 5)
def FuncRateChange(Data, Ticker, Columns):
Span_S: int = 3
Span_M: int = 5
RefCol = Ticker + '_C'
Data_S = Data[RefCol].pct_change(Span_S)
Data_M = Data[RefCol].pct_change(Span_M)
Result = pd.DataFrame({
Columns[0]: Data_S,
Columns[1]: Data_M,
})
return Result
# データがNaNの場合は一つ上のデータをコピーする
# 最初の行は対象外
# 一つ上のデータがNaNの場合はそのまま
def FillNaN(Data):
OutData = Data.copy()
for i in range(1, OutData.shape[0]): # 2行目以降
for Col in OutData.columns:
if pd.isna(OutData.iloc[i, OutData.columns.get_loc(Col)]):
UpData = OutData.iloc[i - 1, OutData.columns.get_loc(Col)]
if pd.notna(UpData):
OutData.iloc[i, OutData.columns.get_loc(Col)] = UpData
return OutData
# 説明変数と目的変数を分割
def VarDiv(Data, ColName = 'Label'):
# 目的変数の抽出
ColumnsResponse = [col for col in Data.columns if col.startswith(ColName)]
# 説明変数の抽出
ColumnsExplanatory = [col for col in Data.columns if col not in ColumnsResponse]
return Data[ColumnsExplanatory], Data[ColumnsResponse]
# RNN向けにデータを横に並べる関数
# 例えば、Timesteps = 3の場合
# a0, b0 → a0, b0, a1, b1, a2, b2
# a1, b1 a1, b1, a2, b2, a3, b3
# a2, b2 a2, b2, a3, b3, a4, b4
# a3, b3
# a4, b4
def DataCopyShift(Data, Timesteps):
# 出力用のDataFrame変数
OutData = Data.copy()
for i in range(Timesteps - 1):
# Dataをコピー
TmpData = Data.copy()
# TmpDataを上方向にi + 1行シフト(i = 0, 1, ...)
TmpData = TmpData.shift(-(i + 1))
# OutDataの右側に結合
OutData = pd.concat([OutData, TmpData], axis = 1)
# NaNを含む行を削除
OutData = OutData.dropna()
return OutData
def main():
# コマンドライン引数の処理
parser = argparse.ArgumentParser(description = '株価、等から学習および評価データを作成')
parser.add_argument('--a', required = False, nargs = '+', help = 'ラベル生成関数向け引数')
parser.add_argument('--i', required = True, nargs = '+', help = '証券コード(4桁の数字 or N225), SMA, SMADIFF, SMARATIO, BB, BBWIDTH, RSI, MACD, ICHI, STOCH, MOMENTUM, DEVIATION, PSYCHO, ATR, ADX, VOL, DROP, N225DOW, RATECHANGE, DOW, SP500, NAS, FTSE, HSI, SHA, VIX, USD, EUR, CNY, IRX, TNX, SOX, WTI')
parser.add_argument('--s', required = False, default = '2000-01-01', help = 'ダウンロード開始日(Default: 2000-01-01)')
parser.add_argument('--e', required = False, default = '2024-12-31', help = 'ダウンロード終了日(Default: 2024-12-31)')
parser.add_argument('--n', required = False, type = int, default = 250, help = '評価データの個数(Default: 250)')
parser.add_argument('--r', required = False, nargs = '+', type = int, default = 1, help = 'RNN向けtimesteps処理(Default: 1)')
parser.add_argument('--fp', required = True, help = 'ラベル生成関数を記述したPythonファイル名')
parser.add_argument('--fs', required = False, default = 'StdScaler.joblib', help = 'スケーラ保存ファイル名(Default: StdScaler.joblib)')
parser.add_argument('--ft', required = False, default = 'training.csv', help = '学習データ保存ファイル名(Default: training.csv)')
parser.add_argument('--fv', required = False, default = 'validation.csv', help = '評価データ保存ファイル名(Default: validation.csv)')
parser.add_argument('--nostd', required = False, action = 'store_true', help = '標準化無効(Default: 標準化有効)')
parser.add_argument('--debug', required = False, action = 'store_true', help = 'デバッグモード')
args = parser.parse_args()
# importファイルの絶対パスとモジュール名を設定
ImportPath = os.path.abspath(args.fp)
ModuleName = os.path.splitext(os.path.basename(ImportPath))[0]
# モジュールを読み込み
try:
MySpec = importlib.util.spec_from_file_location(ModuleName, ImportPath)
MyModule = importlib.util.module_from_spec(MySpec)
MySpec.loader.exec_module(MyModule)
except Exception as e:
print('モジュールの読み込みに失敗しました:', e)
sys.exit()
# モジュール内の関数の存在を確認
if not hasattr(MyModule, 'GetLabel'):
print('GetLabel関数が存在しません')
sys.exit()
# 指定期間の表示
DayStart = pd.to_datetime(args.s).strftime('%Y-%m-%d')
DayEnd = pd.to_datetime(args.e).strftime('%Y-%m-%d')
print('ダウンロード開始日:', DayStart)
print('ダウンロード終了日:', DayEnd, '\n')
# --iオプションで指定された引数に対するインデックスリストの作成
# Idx: インデックス名
# Columns: カラム名
# Args: 引数
IdxList = []
Delimiter = '_'
for i in args.i:
# # 文字列にデリミタを含むか確認
# if Delimiter in i: idx = i[:i.find(Delimiter)] # 文字列からデリミタ以降を削除
# else: idx = i
# # インデックスリストに文字列を追加
# if not idx in IdxList: IdxList.append(idx)
TmpDict = {}
TmpIndex = i.split(Delimiter)
TmpDict['Idx'] = TmpIndex[0]
if 1 < len(TmpIndex):
if TmpIndex[1] in {'O', 'H', 'L', 'C', 'V'}:
TmpDict['Columns'] = i
else:
TmpDict['Args'] = TmpIndex[1]
IdxList.append(TmpDict)
# 株価データのダウンロード
ColumnsDL = [IdxList[0]['Idx'] + '_O', IdxList[0]['Idx'] + '_H', IdxList[0]['Idx'] + '_L', IdxList[0]['Idx'] + '_C', IdxList[0]['Idx'] + '_V']
StockData = DlStockData(IdxList[0]['Idx'], DayStart, DayEnd, ColumnsDL)
# 株価データのダウンロード結果確認
if 0 == StockData.shape[0]:
print('株価データが正しくダウンロードできませんでした')
sys.exit()
# 説明変数に加えるテクニカル分析の指標、等のリスト
# Func: 実行する関数名
# Args: 関数の引数 その1
# Columns: 関数の引数 その2
IdxDict = {
'SMA': {
# 単純移動平均(短期、中期、長期)
'Func': FuncSMA,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['SMA_S', 'SMA_M', 'SMA_L']
},
'SMADIFF': {
# 単純移動平均(短期、長期、(短期-長期))
'Func': FuncSMADiff,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['SMA_S', 'SMA_L', 'SMA_DIFF']
},
'SMARATIO': {
# 単純移動平均((短期 - 中期) / 中期, (中期 - 長期) / 長期)
'Func': FuncSMARatio,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['SMA_A', 'SMA_B']
},
'BB': {
# ボリンジャーバンド
'Func': FuncBB,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['BB_M2Sig', 'BB_M1Sig', 'BB_SMA', 'BB_P1Sig', 'BB_P2Sig']
},
'BBWIDTH': {
# ボリンジャーバンド(バンド幅、バンド幅の割合)
'Func': FuncBBWidth,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['BB_A', 'BB_B']
},
'RSI': {
# Relative Strength Index
'Func': FuncRSI,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['RSI']
},
'MACD': {
# MACD
'Func': FuncMACD,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['MACD']
},
'ICHI': {
# 一目均衡表(ただし、遅行スパンは含まず)
'Func': FuncICHIMOKU,
'Args': [StockData, IdxList[0]['Idx'], True],
'Columns': ['ICHI_Base', 'ICHI_Conv', 'ICHI_Span1', 'ICHI_Span2']
},
'STOCH': {
# ストキャスティクス
'Func': FuncStochastic,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['STOCH_K', 'STOCH_D']
},
'MOMENTUM': {
# モメンタム
'Func': FuncMomentum,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['MOMENTUM']
},
'DEVIATION': {
# SMA乖離率
'Func': FuncSMADeviation,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['DEVIATION']
},
'PSYCHO': {
# サイコロジカルライン
'Func': FuncPsychological,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['PSYCHO']
},
'ATR': {
# ATR
'Func': FuncATR,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['ATR']
},
'ADX': {
# ADX
'Func': FuncADX,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['ADX']
},
'VOL': {
# Volatility
'Func': FuncVolatility,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['VOL']
},
'DROP': {
# 直近高値からの下落率
'Func': FuncDropRate,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['DROP']
},
'N225DOW': {
# 相対強度(日経平均株価/ダウ平均株価)
'Func': FuncN225DOW,
'Args': [DayStart, DayEnd],
'Columns': ['N225DOW']
},
'RATECHANGE': {
# n日間の騰落率(n = 3, 5)
'Func': FuncRateChange,
'Args': [StockData, IdxList[0]['Idx']],
'Columns': ['RATE_A', 'RATE_B']
},
'DOW': {
# ダウ平均株価
'Func': DlStockData,
'Args': ['DOW', DayStart, DayEnd],
'Columns': ['DOW_O', 'DOW_H', 'DOW_L', 'DOW_C', 'DOW_V']
},
'SP500': {
# S&P500
'Func': DlStockData,
'Args': ['SP500', DayStart, DayEnd],
'Columns': ['SP500_O', 'SP500_H', 'SP500_L', 'SP500_C', 'SP500_V']
},
'NAS': {
# ナスダック総合指数
'Func': DlStockData,
'Args': ['NAS', DayStart, DayEnd],
'Columns': ['NAS_O', 'NAS_H', 'NAS_L', 'NAS_C', 'NAS_V']
},
'FTSE': {
# FTSE100種総合株価指数
'Func': DlStockData,
'Args': ['FTSE', DayStart, DayEnd],
'Columns': ['FTSE_O', 'FTSE_H', 'FTSE_L', 'FTSE_C', 'FTSE_V']
},
'HSI': {
# 香港ハンセン株価指数
'Func': DlStockData,
'Args': ['HSI', DayStart, DayEnd],
'Columns': ['HSI_O', 'HSI_H', 'HSI_L', 'HSI_C', 'HSI_V']
},
'SHA': {
# 上海総合指数
'Func': DlStockData,
'Args': ['SHA', DayStart, DayEnd],
'Columns': ['SHA_O', 'SHA_H', 'SHA_L', 'SHA_C', 'SHA_V']
},
'VIX': {
# Volatility Index指数
'Func': DlStockData,
'Args': ['VIX', DayStart, DayEnd],
'Columns': ['VIX_C']
},
'USD': {
# ドル円為替レート
'Func': DlStockData,
'Args': ['USD', DayStart, DayEnd],
# 'Columns': ['USD_O', 'USD_H', 'USD_L', 'USD_C']
'Columns': ['USD_C']
},
'EUR': {
# ユーロ円為替レート
'Func': DlStockData,
'Args': ['EUR', DayStart, DayEnd],
'Columns': ['EUR_O', 'EUR_H', 'EUR_L', 'EUR_C']
},
'CNY': {
# 中国元円為替レート
'Func': DlStockData,
'Args': ['CNY', DayStart, DayEnd],
'Columns': ['CNY_O', 'CNY_H', 'CNY_L', 'CNY_C']
},
'IRX': {
# 米13週国債
'Func': DlStockData,
'Args': ['IRX', DayStart, DayEnd],
'Columns': ['IRX_C']
},
'TNX': {
# 米10年国債
'Func': DlStockData,
'Args': ['TNX', DayStart, DayEnd],
'Columns': ['TNX_C']
},
'SOX': {
# フィラデルフィア半導体株指数
'Func': DlStockData,
'Args': ['SOX', DayStart, DayEnd],
'Columns': ['SOX_C']
},
'WTI': {
# West Texas Intermediate
'Func': DlStockData,
'Args': ['WTI', DayStart, DayEnd],
'Columns': ['WTI_C']
}
}
# 説明変数に加えるテクニカル分析の指標、等の算出
OutData = StockData.copy()
for index in IdxList:
idx = index['Idx']
if idx in IdxDict:
# 関数の実行
TmpFunc = IdxDict[idx]['Func']
TmpArgs = [*IdxDict[idx]['Args'], IdxDict[idx]['Columns']]
TmpData = TmpFunc(*TmpArgs)
# Argsの処理
# R: 変化率
TmpArgs = index.get('Args')
if TmpArgs:
if 'R' == TmpArgs:
# 変化率の算出
TmpData = TmpData.pct_change()
# カラム名にサフィックスを追加
TmpData = TmpData.add_suffix('_' + TmpArgs)
# カラム名の差分を抽出
# --iオプションでDOW_C DOW_Vと実行すると、2回DOWがダウンロードされてしまう
TmpColumns = TmpData.columns.difference(OutData.columns)
# TrainDataの横にデータを結合
OutData = OutData.join(TmpData[TmpColumns])
# NaNを一つ上のデータで上書き
OutData = FillNaN(OutData)
# ラベルの作成
#
# ラベル生成関数GetLabelの引数は以下の通り
# StockData(必須): 株価データ + テクニカル分析の指標、等を含むデータ
# Ticker(必須): 参照する証券コード
# その他: 必要であれば--aオプションで指定(Ex. --a 1 2 3)
#
# 注意点
# --aオプションで指定した引数はstr型となるため、GetLabel関数内でintやfloat型に変更が必要
LabelArgs = [OutData, IdxList[0]['Idx']]
if args.a: LabelArgs += args.a
LabelData = MyModule.GetLabel(*LabelArgs)
# 指定されたカラムのみ抽出
#
# 株価データから指定されたカラム名のみを選択 その1
# --i N225_C → N225_Cのみ選択
# --i N225 → N225_O, N225_H, N225_L, N225_C, N225_Vを選択
TmpColumns = [IdxList[0]['Columns']] if 'Columns' in IdxList[0] else ColumnsDL
# 株価データから指定されたカラム名のみを選択 その2
# IdxListにColumnsが存在 → Columnsを参照
# IdxListにArgsが存在 → Idx + '_' + Argsを追加
# その他 → IdxDictのColumnsを追加
if 1 < len(IdxList):
for index in IdxList[1:]:
TmpIdx = index.get('Idx')
TmpCol = index.get('Columns')
TmpArgs = index.get('Args')
TmpDict = IdxDict.get(TmpIdx)
if TmpCol:
TmpColumns.append(TmpCol)
elif TmpArgs:
TmpColumns.append(TmpIdx + '_' + TmpArgs)
else:
TmpColumns = TmpColumns + TmpDict['Columns']
# DataFrameのカラム名とリストを比較して、存在するカラムだけを保持
ColumnsStock = [col for col in TmpColumns if col in OutData.columns.tolist()]
OutData = OutData[ColumnsStock]
# OutData + LabelDataからNaNを含む行を削除
TmpData = pd.concat([OutData, LabelData], axis = 1)
TmpData = TmpData.dropna()
# 再びOutDataとLabelDataを分割
OutData, LabelData = VarDiv(TmpData, 'Label')
# データの個数を確認
print('評価データの個数:', args.n)
if len(OutData) <= args.n:
print('データの個数が足りません!!', args.n)
sys.exit()
# 学習および評価データに分割
TrainData = OutData[: -args.n]
ValidData = OutData[len(OutData) - args.n:]
# ラベルの分割
TrainLabel = LabelData[: len(TrainData)]
ValidLabel = LabelData[len(TrainData):]
# ラベルの分布確認
CountsTrainLavel = TrainLabel.value_counts().sort_index()
CountsValidLavel = ValidLabel.value_counts().sort_index()
print(f'\n学習データのラベルの分布(全データ数: {len(TrainLabel)})')
print(CountsTrainLavel)
print(f'\n評価データのラベルの分布(全データ数: {len(ValidLabel)})')
print(CountsValidLavel, '\n')
# 学習および評価データの標準化
if not args.nostd:
# スケーラの設定
Scaler = StandardScaler()
# 学習データの標準化(Fit & Transform)
StdTrainData = Scaler.fit_transform(TrainData)
StdTrainData = pd.DataFrame(StdTrainData, columns = TrainData.columns)
print('平均値:', Scaler.mean_)
print('標準偏差:', Scaler.scale_, '\n')
# スケーラのファイルへの保存
# デバッグモード時はスキップ
if not args.debug:
joblib.dump(Scaler, args.fs)
print('スケーラファイル名:', args.fs, '\n')
# 評価データの標準化(Transform)
TmpStdData = Scaler.transform(ValidData)
StdValidData = pd.DataFrame(TmpStdData, columns = ValidData.columns) # Numpy → DataFrame
else:
StdTrainData = TrainData.copy()
StdValidData = ValidData.copy()
# インデックスをリセット
StdTrainData = StdTrainData.reset_index(drop = True)
StdValidData = StdValidData.reset_index(drop = True)
# RNN向けtimesteps処理
print('RNN timesteps:', args.r, '\n')
RNNTrainData = StdTrainData.copy()
RNNValidData = StdValidData.copy()
RNNTrainLabel = TrainLabel.copy()
RNNValidLabel = ValidLabel.copy()
# ラベルのインデックスをリセット
RNNTrainLabel = RNNTrainLabel.reset_index(drop = True)
RNNValidLabel = RNNValidLabel.reset_index(drop = True)
# 対象期間のズレ補正変数
ShiftIndex = 1
if list == type(args.r):
for i in args.r:
# 説明変数
RNNTrainData = DataCopyShift(RNNTrainData, i)
RNNValidData = DataCopyShift(RNNValidData, i)
# 目的変数
RNNTrainLabel = RNNTrainLabel[i - 1:]
RNNValidLabel = RNNValidLabel[i - 1:]
# ラベルのインデックスをリセット
RNNTrainLabel = RNNTrainLabel.reset_index(drop = True)
RNNValidLabel = RNNValidLabel.reset_index(drop = True)
# 対象期間のズレを補正
ShiftIndex += i - 1
# 説明変数と目的変数を合体
RNNTrainData = pd.concat([RNNTrainData, RNNTrainLabel], axis = 1)
RNNValidData = pd.concat([RNNValidData, RNNValidLabel], axis = 1)
# 分割およびRNN向けtimesteps処理後の学習および評価データの期間
print('学習データの期間:', TrainData.index[ShiftIndex - 1], '-', TrainData.index[-1])
print('評価データの期間:', ValidData.index[ShiftIndex - 1], '-', ValidData.index[-1], '\n')
# 学習および評価データをcsvファイルに出力
if not args.debug:
# 学習データ
print('学習データファイル名(csv file):', args.ft)
RNNTrainData.to_csv(args.ft, index = False, float_format = '%.4f')
# 評価データ
print('評価データファイル名(csv file):', args.fv)
RNNValidData.to_csv(args.fv, index = False, float_format = '%.4f')
else:
# デバッグモード
print(TrainData)
print(TrainLabel)
print()
print(ValidData)
print(ValidLabel)
print()
print(RNNTrainData)
print(RNNValidData)
if __name__ == '__main__':
main()セルフデバッグ済みですが、何らかのバグが残っている可能性があります。
その点については、ご了承ください。
m(_ _)m
Pythonプログラムで使用可能なオプション
Pythonプログラムで使用可能なオプションは、以下の通りです
-h, --help: ヘルプメッセージの出力
--a: ラベル生成関数向け引数
--i: 説明変数に加える指標(下記から選択)
証券コード(4桁の数字) or N225
詳細の指定(_O, _H, _L, _C, _V)
SMA: 単純移動平均
詳細の指定(_S, _M, _L)
SMADIFF: 短期、長期、(短期 - 長期)
詳細の指定(_S, _L, _DIFF)
SMARATIO: (短期 - 中期) / 中期、(中期 - 長期) / 長期
詳細の指定(_A, _B)
BB: ボリンジャーバンド
詳細の指定(_M2Sig, _M1Sig, _SMA, _P1Sig, _P2Sig)
BBWIDTH: バンド幅、バンド幅の割合
詳細の指定(_A, _B)
RSI: Relative Strength Index
MACD: MACD
ICHI: 一目均衡表(ただし、遅行スパンは含まず)
詳細の指定(_Base, _Conv, _Span1, _Span2)
STOCH: ストキャスティクス
詳細の指定(_STOCH_K, _STOCH_D)
MOMENTUM: モメンタム
DEVIATION: SMA乖離率
PSYCHO: サイコロジカルライン
ATR: ATR
ADX: ADX
VOL: ボラティリティ
DROP: 直近高値からの下落率
N225DOW: 相対強度(日経平均株価/ダウ平均株価)
DOW: ダウ平均株価
詳細の指定(_O, _H, _L, _C, _V)
SP500: S&P500
詳細の指定(_O, _H, _L, _C, _V)
NAS: ナスダック総合指数
詳細の指定(_O, _H, _L, _C, _V)
FTSE: FTSE100種総合株価指数
詳細の指定(_O, _H, _L, _C, _V)
HSI: 香港ハンセン株価指数
詳細の指定(_O, _H, _L, _C, _V)
SHA: 上海総合指数
詳細の指定(_O, _H, _L, _C, _V)
VIX: Volatility Index指数(終値のみ)
USD: ドル円為替レート
詳細の指定(_O, _H, _L, _C)
EUR: ユーロ円為替レート
詳細の指定(_O, _H, _L, _C)
CNY: 中国元円為替レート
詳細の指定(_O, _H, _L, _C)
IRX: 米13週国債(終値のみ)
TNX: 米10年国債(終値のみ)
SOX: フィラデルフィア半導体株指数
WTI: West Texas Intermediate
--s: ダウンロード開始日(Default: 2000-01-01)
--e: ダウンロード終了日(Default: 2024-12-31)
--n: 評価データの個数(Default: 250)
--r: RNN向けtimesteps処理(Default: 1)
--fp: ラベル生成関数を記述したPythonファイル名
--fs: スケーラ保存ファイル名(Default: StdScaler.joblib)
--ft: 学習データ保存ファイル名(Default: training.csv)
--fv: 評価データ保存ファイル名(Default: validation.csv)
--prediction: 推論データ作成機能有効、ファイル名指定(Default: prediction.csv)
--debug: デバッグモード
--iオプションに対する補足
--iオプションで指定する指標に詳細の指定が記載されている場合、詳細を指定することが可能です
例えば、N225の終値のみを指定する場合は--i N225_Cとします
--iオプションに対する注意点
--iオプションで最初に指定すべきは証券コード or N225の何れかです
--iオプションで指定するテクニカル分析の指標、等は、指定する順番に依存して説明変数の並びが変化します
--iオプションの指定順に関する詳細を、説明します。
例えば、--i N225 SMA DOWと指定した場合、説明変数の並びが以下のようになります。
N225(始値、高値、安値、終値、出来高), SMA(5日、25日、75日), DOW(始値、高値、安値、終値、出来高)
一方で、--i N225 DOW SMAと指定した場合は、以下の通りです。
N225(始値、高値、安値、終値、出来高), DOW(始値、高値、安値、終値、出来高), SMA(5日、25日、75日)
さらに、新規機能として、変化率の算出を加えました。
変化率の指定方法は、既存のパラメータ名に"_R"を追加します。
例えば、RSIの変化率を説明変数として使用する場合は、RSI_Rと指定します。
--i RSI_R
RNN向けtimesteps処理に関する補足
RNN向けtimesteps処理は、次の様に動作します。
まず、学習データ(あるいは、評価データ)を以下のように定義します。

説明変数がData 0, 目的変数がLabelです。
1行ごとに1日分のデータ(Data 0)とラベル(Label)が存在し、それが17日分存在している状態です。
この学習データに対して--r 2とした結果が以下となります。

1行ごとに2日分のデータ(Data 0, 1)が含まれており、これを1回分のデータとして構成しています。
また、--r 2 3とした結果が以下となります。

--r 2を設定した結果に対して、さらに--r 3の設定を行った状態となります。
これは、1回分のデータとして、2日分(--r 2)を対象に、さらに3日分(--r 3)とした構成です。
--rオプションの想定される使用イメージは、以下の通りです。
1回の学習データとして1日分のデータを使用するRNNのtimesteps設定に合わせて構成を変更する(--r timesteps)
1回の学習データとしてN日分のデータを使用するRNNのtimesteps設定に合わせて構成を変更する(--r N timesteps)
ラベル生成関数の記述方法
例えば、翌営業日のローソクが陽線か陰線かのラベルを生成する関数のソースコードは、下記となります。
import pandas as pd
# ラベルを作成(二値分類用)
# 翌営業日の日経平均株価のローソク足が陽線か陰線か
def GetLabel(Data, Ticker):
# 参照カラム名
RefCol_O = Ticker + '_O'
RefCol_C = Ticker + '_C'
# Openの列を抽出
TmpOpen = Data[RefCol_O]
# Closeの列を抽出
TmpClose = Data[RefCol_C]
# 終値と始値の差分を抽出
LabelData = TmpClose - TmpOpen
# SeriesをDataFrameに変換
LabelData = LabelData.to_frame('Label')
# LabelDataを上方向に1行シフト(翌営業日を参照するため)
LabelData = LabelData.shift(-1)
# 終値と始値の差分が0より大きいならラベル1、0以下はラベル0
LabelData = (0 < LabelData) * 1
return LabelData関数名GetLabelは固定です。
引数については、DataおよびTickerは固定ですが、それ以外の引数を使用する場合は、その後に追加することができます。
例えば、以下の通りです。
def GetLabel(Data, Ticker, a, b):
x = int(a) + float(b)
...
return c上記の場合、Pythonプログラムの実行時に--aオプションでaおよびbを指定します。
--a 10 1.5
a = 10, b = 1.5となります。
ただし、a, bはstr型となるため、GetLabel関数内でint(a)やfloat(b)といった型変換を行う必要があります。
Pythonプログラムの実行方法
上記のPythonプログラムを保存したファイル名をfile.pyとします。
また、ラベル生成関数を保存したファイル名をlabel.pyとします。
ご注意くださいlabel.pyはfile.pyと同じ階層のフォルダに存在する必要があります
例えば、下記の条件に基づいた学習データおよび評価データを作成する場合の実行方法は次の通りです。
説明変数の要素
トヨタ自動車(7203)の終値と出来高
移動平均
MACD
ダウ平均株価の出来高
株価データ取得期間: 2010年6月1日から2024年5月31日
RNN向けtimesteps処理: 2日分、3 timesteps
> python file.py --fp label.py --i 7203_C 7203_V SMA MACD DOW_V --s 2010-6-1 --e 2024-5-31 --r 2 3
ダウンロード開始日: 2010-06-01
ダウンロード終了日: 2024-05-31
評価データの個数: 250
学習データのラベルの分布
Label 1452
dtype: int64
評価データのラベルの分布
Label 122
dtype: int64
平均値: [1.32076765e+03 3.81098747e+07 1.31995317e+03 1.31591751e+03
1.30621407e+03 2.76759140e+00 2.37218614e+08]
標準偏差: [4.31788549e+02 1.95601885e+07 4.31587052e+02 4.30871371e+02
4.29922132e+02 2.02982452e+01 1.37952506e+08]
スケーラファイル名: StdScaler.joblib
学習データの期間: 2010-09-16 - 2023-05-09
評価データの期間: 2023-05-15 - 2024-05-28
学習データファイル名(csv file): training.csv
評価データファイル名(csv file): validation.csvダウンロードの開始日を2010年6月1日としたにもかかわらず、学習データの開始日が2010年9月16日となっている点について、補足します。
上記の例では、単純移動平均の75日を算出する際に、2010年6月1日から74日分は算出できないため、その分のデータが削除されているからです。
また、評価データの終了日が2024年5月31日より前となっている理由は、--rオプションにより有効な期間が3営業日分だけ短くなったからです。
修正履歴
2025年5月7日
YFRateLimitError('Too Many Requests. Rate limited. Try after a while.')に対応
2025年7月2日
学習および評価データとして、終値のみとか出来高のみといった一部のデータを指定可能な機能を追加
これに伴い、オプションの見直しを実施(--cオプションを削除し、--iオプションに統合)
2025年7月12日
データ数とラベル数が異なる場合の不具合を修正
2025年8月27日
データ数とラベル数が異なる場合の不具合を再修正
推論データ作成機能を追加(--prediction)
--fpオプションでの同一階層にファイルを保存する制約を解消
2025年9月10日
データ中の欠損値(NaN)を削除から、一つ上のデータのコピーに変更
推論データ作成機能を削除(--prediction)
2025年11月5日
独自のカスタムメトリックを追加
--m val_myscoreで実行可能
2025年12月3日
--rオプション使用時の日付のズレを修正
2026年4月25日
--iオプションで指定可能な項目を追加
2026年5月20日
--iオプションで指定可能な項目を追加
2026年5月27日
--iオプション使用時のバグ修正
