【Python】TA-LibのMACD計算でNaNが返ってきていた-原因はresample-
今回は、PythonでFXチャートのMACDシグナルクロスの検証を試そうと思った時に少し沼ったお話です。
MACDシグナルクロスの件数が少ない
1分足の米ドル円データは取得してDB化していたので、そのデータを使って15分足のMACDのシグナルクロスを検出するPythonコードをAIに作ってもらいました。
Pythonにはテクニカル分析に使うTA-Libと言う便利ライブラリがあるっぽいので、それを使ってます。
(生成されたコードはもう少し変数定義とか代入とかあったけど、掲載用に圧縮しています)
import sqlite3
import pandas as pd
import talib as ta
# SQLiteからデータをロードする
conn = sqlite3.connect("exchange_rate.db")
df = pd.read_sql_query("SELECT time, open, low, high, close FROM rate_1m WHERE pair_id = 1", conn)
conn.close()
df['datetime'] = pd.to_datetime(df['time'], unit='s', utc=True).dt.tz_convert('Asia/Tokyo')
df.set_index('datetime', inplace=True)
# 指定された時間足にリサンプルする
df = df.resample('15min').agg({'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last'}).copy()
# MACD、シグナル、ヒストグラムを算出
df['macd'], df['macdsignal'], df['macdhist'] = ta.MACD(df['close'], fastperiod=12, slowperiod=26, signalperiod=9)
# MACD、シグナルのクロス判定
df['macd_gc'] = (df['macdhist'].shift(1) < 0) & (df['macdhist'] >= 0)
df['macd_dc'] = (df['macdhist'].shift(1) > 0) & (df['macdhist'] <= 0)
## 交差したポイントだけを抽出
gc_points = df[df['macd_gc']].copy()
dc_points = df[df['macd_dc']].copy()
print(f"全データ数: {len(df)}")
print(f"ゴールデンクロス検出数: {len(gc_points)}")
print(f"デッドクロス検出数: {len(dc_points)}")
パッと見、変な事はしてなくて大丈夫そうだったので、そのまま実行!
全データ数: 62504
ゴールデンクロス検出数: 16
デッドクロス検出数: 15
??? 少なくない? データは20ヶ月分くらいあるはずなのに、クロスが31件は流石に……
試しに、resampleを5分足("5min")に変えてみて実行。
全データ数: 187512
ゴールデンクロス検出数: 40
デッドクロス検出数: 39
やっぱり少ない。
クロス判定のコードは間違ってなさそうだけど……ナゼ?
MACD計算結果がNaNになっている
試しにresampleを外して(resampleの行をコメントアウト)、1分足のままで実行してみます。
全データ数: 650385
ゴールデンクロス検出数: 26811
デッドクロス検出数: 26811
あれ? これはちゃんと検出できてそう。と言うことは、クロスの判定自体には問題なし。
と言うことは、resampleかMACDの計算がうまく行ってない?
試しに、15分足に戻してMACD計算後のデータの最初と最後を50行ずつ出力してみます。(下記コードをクロス判定後に追加)
(なぜ50行かというと、MACDの計算でslowの26本とsignalの9本の35本が最低限必要だから)
print(df.head(50)) # 確認用 - 先頭データ確認
print(df.tail(50)) # 確認用 - 末尾データ確認
結果
open high low close macd macdsignal macdhist macd_gc macd_dc
datetime
2024-04-30 08:00:00+09:00 156.261002 156.272003 156.162994 156.218002 NaN NaN NaN False False
2024-04-30 08:15:00+09:00 156.218994 156.274994 156.186996 156.190994 NaN NaN NaN False False
2024-04-30 08:30:00+09:00 156.199997 156.384995 156.186996 156.337006 NaN NaN NaN False False
~~~ 中略 ~~~
2024-04-30 19:45:00+09:00 156.917999 156.925995 156.886002 156.921997 0.051980 0.060658 -0.008678 False False
2024-04-30 20:00:00+09:00 156.916000 156.951004 156.895004 156.929993 0.050865 0.058700 -0.007835 False False
2024-04-30 20:15:00+09:00 156.929993 156.945007 156.912994 156.931000 0.049491 0.056858 -0.007367 False False
open high low close macd macdsignal macdhist macd_gc macd_dc
datetime
2026-02-09 21:30:00+09:00 156.520004 156.557007 156.479996 156.490997 NaN NaN NaN False False
2026-02-09 21:45:00+09:00 156.490997 156.516998 156.347000 156.378998 NaN NaN NaN False False
2026-02-09 22:00:00+09:00 156.371994 156.391998 156.106995 156.136993 NaN NaN NaN False False
~~~ 中略 ~~~
2026-02-10 09:15:00+09:00 156.162003 156.223007 156.139008 156.203003 NaN NaN NaN False False
2026-02-10 09:30:00+09:00 156.207001 156.294006 156.199005 156.216995 NaN NaN NaN False False
2026-02-10 09:45:00+09:00 156.214005 156.281998 156.003998 156.044998 NaN NaN NaN False False
全データ数: 62504
ゴールデンクロス検出数: 16
デッドクロス検出数: 15
最初の方は問題ないけど、最後の方のMACDがNaNになってる!
どこからNaNになってるんだ?
と言うことで、一度全データをCSVファイルにして確認してみました。(下記コードを最後に追加)
df.to_csv("result.csv", index=False)
出力されたCSVを見てみると……
152.875,152.91000366210938,152.83799743652344,152.8780059814453,0.01014420187129872,0.0029054911300681855,0.007238710741230534,False,False
152.87899780273438,152.91299438476562,152.85699462890625,152.88800048828125,0.008873507048576812,0.004099094313769911,0.004774412734806901,False,False
152.88800048828125,152.90899658203125,152.8459930419922,152.85000610351562,0.004745934746921421,0.0042284624004002135,0.0005174723465212073,False,False
152.8509979248047,152.89599609375,152.843994140625,152.88400268554688,0.0041699751313331035,0.004216764946586791,-4.678981525368768e-05,False,True
152.88800048828125,152.9340057373047,152.87899780273438,152.89700317382812,0.004708279624395573,0.0043150678821485475,0.0003932117422470252,True,False
152.89300537109375,153.00799560546875,152.83999633789062,153.00799560546875,0.01393046966154543,0.006238148238027924,0.007692321423517506,False,False
152.94900512695312,152.94900512695312,152.94900512695312,152.94900512695312,0.016291287277283573,0.008248776045879053,0.00804251123140452,False,False
,,,,,,,False,False
,,,,,,,False,False
,,,,,,,False,False
,,,,,,,False,False
,,,,,,,False,False
,,,,,,,False,False
,,,,,,,False,False
,,,,,,,False,False
途中でNaN(空データ)になっていました。
…… と言うか、足データにNaNがある? ……
いや、DBにする時に欠損データは排除しているから …… これデータがない時間が勝手に作られてる?
(この事実に気付くまでに40分くらい掛かりました)
原因はresample
1分足のデータを直接は問題なし。MACD計算前にやっているのは …… resampleだけ!
はい! 原因はresampleでした。resampleは欠損データがある場合は、NaN埋めする仕様みたいです。
処理するデータにNaNが入っているせいで、MACDの計算ができず、シグナルクロス検知を正しく行えなかったのですね。
対応方法
原因が分かれば、あとは簡単です。追加されてしまった不要なデータを削除しちゃいましょう。dropnaというメソッドを使えば、でNaNとなっているデータを削除できます。(AIに聞いたら教えてくれた)
df = df.resample('15min').agg({'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last'}).dropna().copy()
これでもう一度実行してみます。(計算後のデータの最初と最後5行も確認用に出力)
open high low close macd macdsignal macdhist macd_gc macd_dc
datetime
2024-04-30 08:00:00+09:00 156.261002 156.272003 156.162994 156.218002 NaN NaN NaN False False
2024-04-30 08:15:00+09:00 156.218994 156.274994 156.186996 156.190994 NaN NaN NaN False False
2024-04-30 08:30:00+09:00 156.199997 156.384995 156.186996 156.337006 NaN NaN NaN False False
2024-04-30 08:45:00+09:00 156.337997 156.386993 156.276001 156.330994 NaN NaN NaN False False
2024-04-30 09:00:00+09:00 156.330994 156.423996 156.160004 156.360992 NaN NaN NaN False False
open high low close macd macdsignal macdhist macd_gc macd_dc
datetime
2026-02-10 08:45:00+09:00 156.063004 156.147995 156.050995 156.125000 0.016381 -0.025450 0.041831 False False
2026-02-10 09:00:00+09:00 156.126007 156.225998 156.059998 156.171997 0.032736 -0.013813 0.046549 False False
2026-02-10 09:15:00+09:00 156.162003 156.223007 156.139008 156.203003 0.047651 -0.001520 0.049171 False False
2026-02-10 09:30:00+09:00 156.207001 156.294006 156.199005 156.216995 0.059909 0.010766 0.049143 False False
2026-02-10 09:45:00+09:00 156.214005 156.281998 156.003998 156.044998 0.055110 0.019634 0.035475 False False
全データ数: 43541
ゴールデンクロス検出数: 1673
デッドクロス検出数: 1672
ちゃんとシグナルクロスを検出してくれました 🎉
最終的なコード
今回の対応での最終コードの全体を一応載せておきます。
import sqlite3
import pandas as pd
import talib as ta
# SQLiteからデータをロードする
conn = sqlite3.connect("exchange_rate.db")
df = pd.read_sql_query("SELECT time, open, low, high, close FROM rate_1m WHERE pair_id = 1", conn)
conn.close()
df['datetime'] = pd.to_datetime(df['time'], unit='s', utc=True).dt.tz_convert('Asia/Tokyo')
df.set_index('datetime', inplace=True)
# 指定された時間足にリサンプルする
df = df.resample('15min').agg({'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last'}).dropna().copy()
# MACD、シグナル、ヒストグラムを算出
df['macd'], df['macdsignal'], df['macdhist'] = ta.MACD(df['close'], fastperiod=12, slowperiod=26, signalperiod=9)
# MACD、シグナルのクロス判定
df['macd_gc'] = (df['macdhist'].shift(1) < 0) & (df['macdhist'] >= 0)
df['macd_dc'] = (df['macdhist'].shift(1) > 0) & (df['macdhist'] <= 0)
## 交差したポイントだけを抽出
gc_points = df[df['macd_gc']].copy()
dc_points = df[df['macd_dc']].copy()
print(f"全データ数: {len(df)}")
print(f"ゴールデンクロス検出数: {len(gc_points)}")
print(f"デッドクロス検出数: {len(dc_points)}")
まとめ
今回のまとめです。
resampleはデータが存在しない(欠損している)時間部分はNaNで埋めてしまう- MACDなどの計算させるなら、
dropnaで欠損データを除去する前処理が必須 - 何なら、resampleとdropnaはセットで使うようにしても良さそう
ちゃんと公式ドキュメントを確認しないと、変なところで沼ってしまうので気をつけましょうね!

