【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はセットで使うようにしても良さそう

ちゃんと公式ドキュメントを確認しないと、変なところで沼ってしまうので気をつけましょうね!

スポンサーリンク