🐈

Act 33. 様々なインジケータをPythonで実装してみた

2024/12/30に公開

はじめに

LSTMの学習を行おうと思ったが、まずはデータを準備する必要があることに気づいた。
一応、GMOコイン様のAPIを叩いて為替相場のデータを取得する方法についてコチラの記事に載せているので、データが欲しい人はぜひご覧あれ。

ちなみに筆者はjupyterlabを使ってコードを実行しているので、セル単位でまとめてコードを掲載しているので良しなに。

データの整形

インポート

まず記事のコードを実行するにあたり必要なライブラリは以下の通りとなる。
japanize_matplotlibに関してはインポートしなくても問題ないが、日本語表記したい場合は必要。

Act33.ipynb
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib

次にデータの読み込み。
2024年6月3日の16時~23時のデータをCSVに出力しているため、その内容を取得する。
日時をindexとして、openTimeからdatetimeというインデックス名に変更している。

Act33.ipynb
data = pd.read_csv("USD_JPY-1min-20240603-16_23.csv", index_col="openTime", parse_dates=True)
data.index.name = "datetime"

データを一部載せると以下のような感じ。
以降はこのデータを使用する。

USD_JPY-1min-20240603-16_23.csv
openTime,open,high,low,close
2024-06-03 16:00:00,157.257,157.27,157.251,157.269
2024-06-03 16:01:00,157.268,157.296,157.266,157.287
2024-06-03 16:02:00,157.287,157.289,157.273,157.273
2024-06-03 16:03:00,157.273,157.274,157.259,157.259
2024-06-03 16:04:00,157.259,157.262,157.256,157.258
2024-06-03 16:05:00,157.258,157.263,157.254,157.257
2024-06-03 16:06:00,157.257,157.28,157.257,157.28
2024-06-03 16:07:00,157.28,157.287,157.28,157.285
2024-06-03 16:08:00,157.285,157.285,157.254,157.254

単純移動平均線(SMA)

まずは最も有名な単純移動平均線から。
計算式は第5回、移動平均線 その1、『 移動平均線の役割 』を参照してね。

まずは関数から。

Act33.ipynb
# 単純移動平均(SMA)を計算する関数
def calculate_sma(data, period):
  return data["close"].rolling(window=period).mean()

関数の呼び出しは以下の通り。

Act33.ipynb
data_copy = data.copy()
data_copy["SMA_30"] = calculate_sma(data_copy, period=30)
data_copy["SMA_60"] = calculate_sma(data_copy, period=60)
fig, ax = plt.subplots(figsize=(16, 6))
ax.plot(data_copy["close"], label="終値")
ax.plot(data_copy["SMA_30"], label="30SMA")
ax.plot(data_copy["SMA_60"], label="60SMA")
plt.legend()

プロットは以下の通り。

指数平滑移動平均(EMA)

次に指数平滑移動平均について。
計算式は指数平滑移動平均線の計算方法や他の移動平均線との違いを紹介を参照してね。

まずは関数から。

Act33.ipynb
# 指数移動平均(EMA)を計算する関数
def calculate_ema(data, period):
  return data["close"].ewm(span=period, adjust=False).mean()

関数の呼び出しは以下の通り。

Act33.ipynb
data_copy = data.copy()
data_copy["EMA_30"] = calculate_ema(data=data_copy, period=30)
data_copy["EMA_60"] = calculate_ema(data=data_copy, period=60)
fig, ax = plt.subplots(figsize=(16, 6))
ax.plot(data_copy["close"], label="終値")
ax.plot(data_copy["EMA_30"], label="30EMA")
ax.plot(data_copy["EMA_60"], label="60EMA")
plt.legend()

プロットは以下の通り。

ボリンジャーバンド

次にボリンジャーバンドについて。
計算式はボリンジャーバンドとは?使い方や計算式、期間設定を参照してね。

まずは関数から。

Act33.ipynb
# ボリンジャーバンドを計算する関数
def calculate_bb(data, sigma=2, period=20):
  df = pd.DataFrame({
    "sma": data["close"].rolling(window=period).mean(),  # 移動平均
    "std": data["close"].rolling(window=period).std()   # 標準偏差
  }, index=data.index)
  df["Upper"] = df["sma"] + (sigma * df["std"])      # +Xσ
  df["Lower"] = df["sma"] - (sigma * df["std"])      # -Xσ
  return df[["Upper", "Lower"]]

関数の呼び出しは以下の通り。

Act33.ipynb
data_copy = data.copy()
data_copy["SMA_20"] = calculate_sma(data=data_copy, period=20)
data_copy[["Upper_2","Lower_2"]] = calculate_bb(data=data_copy, sigma=2, period=20)
data_copy[["Upper_3","Lower_3"]] = calculate_bb(data=data_copy, sigma=3, period=20)

fig, ax = plt.subplots(figsize=(16, 6))
ax.plot(data_copy["close"], label="終値")
ax.plot(data_copy["SMA_20"], label="中央線")
ax.plot(data_copy["Upper_2"], label="+2σ", color="green")
ax.plot(data_copy["Lower_2"], label="-2σ", color="green")
ax.plot(data_copy["Upper_3"], label="+3σ", color="red")
ax.plot(data_copy["Lower_3"], label="-3σ", color="red")
plt.legend()

プロットは以下の通り。

RSI

次にRSIについて。
計算式は第8回 RSI(Relative Strength Index)を参照してね。

まずは関数から。

Act33.ipynb
# RSIを計算する関数
def calculate_rsi(data, period):
    diff = data["close"].diff()  # 1階差分
    df = pd.DataFrame({
        "gain": np.where(diff > 0, diff, 0),  # 上昇幅
        "loss": np.where(diff < 0, -diff, 0)  # 下落幅
    }, index=data.index)

    # 平均上昇幅と下落幅(EMA)
    df["avg_gain"] = df["gain"].ewm(span=period, adjust=False).mean()  # adjust=False で過去の値に重み付け
    df["avg_loss"] = df["loss"].ewm(span=period, adjust=False).mean()

    # RS の計算 (ゼロ除算の安全対策)
    df["rs"] = np.where(df["avg_loss"] == 0, 0, df["avg_gain"] / df["avg_loss"])

    # RSI の計算
    df["RSI"] = 100 - (100 / (1 + df["rs"]))

    return df["RSI"]

関数の呼び出しは以下の通り。

Act33.ipynb
data_copy = data.copy()
data_copy["RSI"] = calculate_rsi(data=data_copy, period=14)

fig, ax = plt.subplots(2, 1, figsize=(16, 8))
ax[0].plot(data_copy["close"], label="終値")
ax[0].legend()
ax[1].plot(data_copy["RSI"], label="RSI")
ax[1].axhline(70, color="gray", label="RSI 70%")
ax[1].axhline(30, color="gray", label="RSI 30%")
ax[1].legend()

プロットは以下の通り。

MACD

次にMACDについて。
計算式は第13回 MACD(Moving Average Convergence and Divergence)を参照してね。

まずは関数から。

Act33.ipynb
# MACDを計算する関数
def calculate_macd(data, short_period=12, long_period=26, signal_period=9):
  """
  short_period: 短期EMA期間
  long_period: 長期EMA期間
  signal_period: シグナル期間
  """
  df = pd.DataFrame({
    "ema_short": data["close"].ewm(span=short_period, adjust=False).mean(),  # 短期EMA
    "ema_long": data["close"].ewm(span=long_period, adjust=False).mean()  # 長期EMA
  }, index=data.index)
  df["MACD"] = df["ema_short"] - df["ema_long"]   # MACDライン
  df["Signal"] = df["MACD"].ewm(span=signal_period, adjust=False).mean()  # シグナル
  df["MACD_Histogram"] = df["MACD"] - df["Signal"]  # MACDのヒストグラム
  return df[["MACD", "Signal", "MACD_Histogram"]]

関数の呼び出しは以下の通り。

Act33.ipynb
data_copy = data.copy()
data_copy[["MACD", "Signal", "MACD_Histogram"]] = calculate_macd(data=data_copy)

fig, ax = plt.subplots(2, 1, figsize=(16, 8))
ax[0].plot(data_copy["close"], label="終値")
ax[0].legend()
ax[1].plot(data_copy["MACD"], label="MACD", color="green")
ax[1].plot(data_copy["Signal"], label="Signal", color="red")
ax[1].bar(data_copy.index, data_copy["MACD_Histogram"], color="gray", alpha=0.5, label="ヒストグラム", width=0.0005)
ax[1].axhline(0, color="black", label="0ライン")
ax[1].legend()
plt.tight_layout()

プロットは以下の通り。

ADX

次にADXについて。
計算式はDMI・ADXとは|基本的な使い方・設定方法などを詳しく解説を参照してね。

まずは関数から。

Act33.ipynb
# TrueRangeを計算する関数
def calculate_tr(data):
  df = pd.DataFrame({
    "prev_close": data["close"].shift(1)
  }, index=data.index)
  df["tr1"] = data["high"] - data["low"]
  df["tr2"] = (data["high"] - df["prev_close"]).abs()
  df["tr3"] = (data["low"] - df["prev_close"]).abs()
  df["tr"] = df[["tr1", "tr2", "tr3"]].max(axis=1)
  
  return df["tr"]

# ADXを計算する関数(価格のトレンドの強さを測定するテクニカル指標)
def calculate_adx(data, period):
  df = pd.DataFrame({
    "tr": calculate_tr(data=data)   # True Range(一日の最大値動き)
  }, index=data.index)

  # +DM, -DM(Directional Movement: 方向性運動量)
  df["+dm"] = np.where(
    (data["high"] - data["high"].shift(1)) > (data["low"].shift(1) - data["low"]),  # 左記の条件を満たす場合は⓵を、そうでない場合は⓶を返す
    np.maximum(data["high"] - data["high"].shift(1), 0),    # ⓵
    0                                                       # ⓶
  )
  df["-dm"] = np.where(
    (data["low"].shift(1) - data["low"]) > (data["high"] - data["high"].shift(1)),  # 左記の条件を満たす場合は⓵を、そうでない場合は⓶を返す
    np.maximum(data["low"].shift(1) - data["low"], 0),      # ⓵
    0                                                       # ⓶
  )

  # TR, +DM, -DMのEMA
  df["tr_smooth"] = df["tr"].ewm(span=period, adjust=False).mean()
  df["+dm_smooth"] = df["+dm"].ewm(span=period, adjust=False).mean()
  df["-dm_smooth"] = df["-dm"].ewm(span=period, adjust=False).mean()

  # +DI, -DI(Directional Indicator: 方向性指標)
  df["+DI"] = (df["+dm_smooth"] / df["tr_smooth"]) * 100
  df["-DI"] = (df["-dm_smooth"] / df["tr_smooth"]) * 100

  # DX(Directional Index:方向性指数)
  df["dx"] = (abs(df["+DI"] - df["-DI"]) / (df["+DI"] + df["-DI"])) * 100
  
  # ADX
  df["ADX"] = df["dx"].ewm(span=period, adjust=False).mean()
  return df[["ADX", "+DI", "-DI"]]

関数の呼び出しは以下の通り。

Act33.ipynb
data_copy = data.copy()
data_copy[["ADX", "+DI", "-DI"]] = calculate_adx(data_copy, period=14)

fig, ax = plt.subplots(2, 1, figsize=(16, 8))
ax[0].plot(data_copy["close"], label="終値")
ax[0].legend()
ax[1].bar(data_copy.index, data_copy["ADX"], label="ADX(トレンドの有無と強さ)", color="gray", width=0.0005, alpha=0.7)
ax[1].plot(data_copy["+DI"], label="+DI(買い勢力の強さ)", color="green")
ax[1].plot(data_copy["-DI"], label="-DI(売り勢力の強さ)", color="red")
ax[1].axhline(25, color="black", label="閾値")
ax[1].legend()

プロットは以下の通り。

ストキャスティクス

次にストキャスティクスについて。
計算式はストキャスティクスとは|計算式や設定方法・見方・使い方などを詳しく紹介を参照してね。

まずは関数から。

Act33.ipynb
# ストキャスティクスを計算する関数
def calculate_stochastic(data, k_period, d_period):
  df = pd.DataFrame({
    "lowest": data["low"].rolling(window=k_period).min(),
    "highest": data["high"].rolling(window=k_period).max()
  }, index=data.index)
  # %Kの計算
  df["%K"] = (data["close"] - df["lowest"]) / (df["highest"] - df["lowest"]) * 100

  # %Dの計算(%Kの平均)
  df["%D"] = df["%K"].rolling(window=d_period).mean()

  return df[["%K", "%D"]]

関数の呼び出しは以下の通り。

Act33.ipynb
data_copy = data.copy()
data_copy[["%K", "%D"]] = calculate_stochastic(data=data_copy, k_period=14, d_period=3)

fig, ax = plt.subplots(2, 1, figsize=(16, 8))
ax[0].plot(data_copy["close"], label="終値")
ax[0].legend()
ax[1].plot(data_copy["%K"], label="%K", color="blue")
ax[1].plot(data_copy["%D"], label="%D", color="red")
ax[1].axhline(80, color="gray", label="80%ライン", alpha=0.7)
ax[1].axhline(20, color="gray", label="20%ライン", alpha=0.7)
ax[1].legend()

プロットは以下の通り。

ATR

次にATRについて。
計算式はATRを活用した利益確定目標や損切りの幅を決めるトレードアイデアを参照してね。

まずは関数から。
calculate_tr関数が必要。ADXのところに記載されているコードを参照

Act33.ipynb
# ATR(Average True Range)を計算する関数(ボラティリティを測定する指標)
def calculate_atr(data, period):
  df = pd.DataFrame({
    "tr": calculate_tr(data=data)
  }, index=data.index)
  df["ATR"] = df["tr"].ewm(span=period, adjust=False).mean()
  return df["ATR"]

関数の呼び出しは以下の通り。

Act33.ipynb
data_copy = data.copy()
data_copy["ATR"] = calculate_atr(data=data_copy, period=14)

fig, ax = plt.subplots(2, 1, figsize=(16, 8))
ax[0].plot(data_copy["close"], label="終値")
ax[0].legend()
ax[1].plot(data_copy["ATR"], label="ATR", color="blue")
ax[1].legend()

プロットは以下の通り。

CCI

次にCCIについて。
計算式はCCIとは|基本的な見方・使い方・計算式などを詳しく解説を参照してね。

まずは関数から。

Act33.ipynb
# CCIを計算する関数
def calculate_cci(data, period):
  # Typical Price(高値、安値、終値の平均値)を計算
  df = pd.DataFrame({
    "tp": data[["high", "low", "close"]].mean(axis=1)
  }, index=data.index)
  df["ma"] = df["tp"].rolling(window=period).mean()
  # 移動平均偏差を計算
  df["md"] = abs(df["tp"] - df["ma"]).rolling(window=period).mean()

  df["CCI"] = (df["tp"] - df["ma"]) / (0.015 * df["md"])
  return df["CCI"]

関数の呼び出しは以下の通り。

Act33.ipynb
data_copy = data.copy()
data_copy["CCI"] = calculate_cci(data_copy, period=14)

fig, ax = plt.subplots(2, 1, figsize=(16, 8))
ax[0].plot(data_copy["close"], label="終値")
ax[0].legend()
ax[1].plot(data_copy["CCI"], label="CCI", color="green")
ax[1].axhline(100, color="gray", alpha=0.7, label="100(買われすぎ)ライン")
ax[1].axhline(-100, color="gray", alpha=0.7, label="-100(売られすぎ)ライン")
ax[1].legend()

プロットは以下の通り。

対数収益率

次に対数収益率について。
計算式は【金融工学】なぜ対数収益率が使われるのかを参照してね。

対数収益率は初めて聞いたため良く分からず。
詳しい人が居たらこの数値を算出することで何が分かるのか教えてほしいです…。

まずは関数から。

Act33.ipynb
# リターン(対数収益率)を計算する関数
def calculate_returns(data):
  df = pd.DataFrame({
    "close": data["close"],
    "close_shift_1": data["close"].shift(1)   # 1時点前のclose
  }, index=data.index)

  # リターン(対数収益率)を計算
  df["Returns"] = (np.log(df["close"]) - np.log(df["close_shift_1"]))

  return df["Returns"]

関数の呼び出しは以下の通り。

Act33.ipynb
data_copy = data.copy()
data_copy["Returns"] = calculate_returns(data=data_copy)

fig, ax = plt.subplots(2, 1, figsize=(16, 8))
ax[0].plot(data_copy["close"], label="終値")
ax[0].legend()
ax[1].plot(data_copy["Returns"], label="リターン(対数収益率)", color="green")
ax[1].axhline(0, color="black", alpha=0.7, label="0ライン")
ax[1].legend()

プロットは以下の通り。

さいごに

自分が知る限りの有名どころを計算する関数を作ってみた。
結構雑だから皆さんの使いやすいように修正してね。

次回はこの関数で求めたデータを使ってLSTMの学習を行ってみようと思う。

ではまた!

Discussion