【Python】RSI逆張りボットを作る|pandas-taでテクニカル指標計算
前回は移動平均線クロスボット(順張り)を作りました。
今回はRSI(相対力指数)を使った逆張りボットをPythonで作ります。
pandas-taとは?
pandas-ta(pandas Technical Analysis)は、130種類以上のテクニカル指標を簡単に計算できるライブラリです。
RSI、MACD、ボリンジャーバンド、ATR...なんでもあります。
インストール
pip install pandas-taRSIの計算
pandas-taを使う場合
import pandas as pdimport pandas_ta as ta
# DataFrameに対してRSIを計算df["rsi"] = ta.rsi(df["close"], length=14)1行で終わり! これがpandas-taの魅力です。
自分で計算する場合(参考)
def calculate_rsi(df, period=14): """RSIを自力で計算""" delta = df["close"].diff()
gain = delta.where(delta > 0, 0) loss = (-delta).where(delta < 0, 0)
avg_gain = gain.rolling(window=period).mean() avg_loss = loss.rolling(window=period).mean()
rs = avg_gain / avg_loss rsi = 100 - (100 / (1 + rs))
return rsipandas-taを使えば、こんな計算を毎回書く必要がありません。
RSIシグナルの検出
def detect_rsi_signal(df, oversold=30, overbought=70): """ RSIシグナルを検出
Returns: "buy": 売られすぎから回復(買いシグナル) "sell": 買われすぎから下落(売りシグナル) None: シグナルなし """ if len(df) < 2: return None
current_rsi = df["rsi"].iloc[-1] prev_rsi = df["rsi"].iloc[-2]
if pd.isna(current_rsi) or pd.isna(prev_rsi): return None
# 売られすぎゾーン(30以下)から回復 if prev_rsi < oversold and current_rsi >= oversold: return "buy"
# 買われすぎゾーン(70以上)から下落 if prev_rsi > overbought and current_rsi <= overbought: return "sell"
return None完成版:RSI逆張りBot
import osimport timeimport hmacimport hashlibimport jsonimport requestsimport pandas as pdimport pandas_ta as tafrom datetime import datetimefrom dotenv import load_dotenv
load_dotenv()
# === 設定 ===API_KEY = os.environ.get("GMO_FX_API_KEY")API_SECRET = os.environ.get("GMO_FX_API_SECRET")SYMBOL = "USD_JPY"ORDER_SIZE = 100RSI_PERIOD = 14RSI_OVERSOLD = 30RSI_OVERBOUGHT = 70CHECK_INTERVAL = 60
# === APIヘルパー関数 ===def create_signature(timestamp, method, path, body=""): text = timestamp + method + path + body return hmac.new( bytes(API_SECRET.encode("ascii")), bytes(text.encode("ascii")), hashlib.sha256 ).hexdigest()
def get_timestamp(): return str(int(time.time() * 1000))
def private_request(method, path, body=None): timestamp = get_timestamp() body_str = "" if body is None else json.dumps(body)
headers = { "API-KEY": API_KEY, "API-TIMESTAMP": timestamp, "API-SIGN": create_signature(timestamp, method, path, body_str), "Content-Type": "application/json" }
url = "https://forex-api.coin.z.com/private" + path
if method == "GET": response = requests.get(url, headers=headers) elif method == "POST": response = requests.post(url, headers=headers, data=body_str)
return response.json()
# === データ取得 ===def get_klines(symbol, interval="1hour"): url = "https://forex-api.coin.z.com/public/v1/klines" params = { "symbol": symbol, "interval": interval, "date": datetime.now().strftime("%Y%m%d") }
response = requests.get(url, params=params) data = response.json()
if data["status"] == 0: df = pd.DataFrame(data["data"]) df["openTime"] = pd.to_datetime(df["openTime"]) df["close"] = df["close"].astype(float) df["high"] = df["high"].astype(float) df["low"] = df["low"].astype(float) return df return None
# === テクニカル分析 ===def calculate_indicators(df): """RSIを計算""" df["rsi"] = ta.rsi(df["close"], length=RSI_PERIOD) return df
def detect_rsi_signal(df): """RSIシグナルを検出""" if len(df) < 2: return None
current_rsi = df["rsi"].iloc[-1] prev_rsi = df["rsi"].iloc[-2]
if pd.isna(current_rsi) or pd.isna(prev_rsi): return None
if prev_rsi < RSI_OVERSOLD and current_rsi >= RSI_OVERSOLD: return "buy"
if prev_rsi > RSI_OVERBOUGHT and current_rsi <= RSI_OVERBOUGHT: return "sell"
return None
# === 取引 ===def get_positions(): result = private_request("GET", f"/v1/openPositions?symbol={SYMBOL}") if result["status"] == 0: return result["data"]["list"] return []
def place_order(side): body = { "symbol": SYMBOL, "side": side, "executionType": "MARKET", "size": str(ORDER_SIZE), "timeInForce": "FAK" } result = private_request("POST", "/v1/order", body)
if result["status"] == 0: print(f"✅ 注文成功: {side} {ORDER_SIZE} {SYMBOL}") return True else: print(f"❌ 注文エラー: {result}") return False
def close_all_positions(side): positions = get_positions() for pos in positions: if (pos["side"] == "BUY" and side == "SELL") or \ (pos["side"] == "SELL" and side == "BUY"): body = { "symbol": SYMBOL, "side": side, "executionType": "MARKET", "size": pos["size"], "positionId": pos["positionId"], "timeInForce": "FAK" } private_request("POST", "/v1/closeOrder", body) print(f"✅ 決済: {pos['side']} {pos['size']}")
# === メインループ ===def run_bot(): print("=" * 50) print(f"🤖 Python RSI Reversal Bot 起動") print(f" 通貨ペア: {SYMBOL}") print(f" RSI期間: {RSI_PERIOD}") print(f" 売られすぎ: {RSI_OVERSOLD} / 買われすぎ: {RSI_OVERBOUGHT}") print("=" * 50)
current_position = None can_buy = True can_sell = True
while True: try: # ローソク足データを取得 df = get_klines(SYMBOL) if df is None: print("データ取得失敗。リトライ...") time.sleep(10) continue
# RSIを計算 df = calculate_indicators(df)
# 現在のRSI current_rsi = df["rsi"].iloc[-1]
# RSIゾーンに入ったらフラグをリセット if current_rsi < RSI_OVERSOLD: can_buy = True if current_rsi > RSI_OVERBOUGHT: can_sell = True
# シグナル検出 signal = detect_rsi_signal(df)
# 状態表示 rsi_status = "売られすぎ" if current_rsi < RSI_OVERSOLD else \ "買われすぎ" if current_rsi > RSI_OVERBOUGHT else "通常"
print(f"[{datetime.now().strftime('%H:%M:%S')}] " f"RSI: {current_rsi:.1f} ({rsi_status}) | " f"Signal: {signal or '-'} | " f"Position: {current_position or 'None'}")
# ポジションがあれば何もしない if len(get_positions()) > 0: current_position = "ACTIVE" time.sleep(CHECK_INTERVAL) continue
current_position = None
# シグナルに応じて売買 if signal == "buy" and can_buy: print("🔔 RSI売られすぎから回復!買いエントリー") place_order("BUY") can_buy = False
elif signal == "sell" and can_sell: print("🔔 RSI買われすぎから下落!売りエントリー") place_order("SELL") can_sell = False
except Exception as e: print(f"エラー: {e}")
time.sleep(CHECK_INTERVAL)
if __name__ == "__main__": run_bot()pandas-taで使える他のテクニカル指標
MACD
macd = ta.macd(df["close"])df["macd"] = macd["MACD_12_26_9"]df["macd_signal"] = macd["MACDs_12_26_9"]df["macd_hist"] = macd["MACDh_12_26_9"]ボリンジャーバンド
bbands = ta.bbands(df["close"], length=20, std=2)df["bb_upper"] = bbands["BBU_20_2.0"]df["bb_middle"] = bbands["BBM_20_2.0"]df["bb_lower"] = bbands["BBL_20_2.0"]ATR(Average True Range)
df["atr"] = ta.atr(df["high"], df["low"], df["close"], length=14)ストキャスティクス
stoch = ta.stoch(df["high"], df["low"], df["close"])df["stoch_k"] = stoch["STOCHk_14_3_3"]df["stoch_d"] = stoch["STOCHd_14_3_3"]一括で計算
# 全部まとめて計算!df.ta.strategy("All")複合条件の例
RSI単体ではダマシが多いので、複数の指標を組み合わせます。
RSI + 移動平均線フィルター
def detect_filtered_signal(df): """RSI + MAフィルター"""
# MAを計算 df["ma200"] = ta.sma(df["close"], length=200)
current_rsi = df["rsi"].iloc[-1] prev_rsi = df["rsi"].iloc[-2] current_price = df["close"].iloc[-1] ma200 = df["ma200"].iloc[-1]
# 200MAより上にいる時は買いのみ if current_price > ma200: if prev_rsi < 30 and current_rsi >= 30: return "buy"
# 200MAより下にいる時は売りのみ if current_price < ma200: if prev_rsi > 70 and current_rsi <= 70: return "sell"
return NoneRSI + ボリンジャーバンド
def detect_bb_rsi_signal(df): """RSI + ボリンジャーバンド"""
bbands = ta.bbands(df["close"], length=20, std=2) df["bb_lower"] = bbands["BBL_20_2.0"] df["bb_upper"] = bbands["BBU_20_2.0"]
current_price = df["close"].iloc[-1] current_rsi = df["rsi"].iloc[-1]
# 価格がBB下限 かつ RSI30以下 → 買い if current_price < df["bb_lower"].iloc[-1] and current_rsi < 30: return "buy"
# 価格がBB上限 かつ RSI70以上 → 売り if current_price > df["bb_upper"].iloc[-1] and current_rsi > 70: return "sell"
return NoneMQL4との比較
RSI計算
MQL4:
double rsi = iRSI(NULL, 0, 14, PRICE_CLOSE, 0);Python:
df["rsi"] = ta.rsi(df["close"], length=14)MQL4は1つの値を取得、Pythonは全履歴をまとめて計算。
Pythonのメリット
- 分析しやすい:pandasでデータ操作が簡単
- 指標が豊富:pandas-taで130種類以上
- 組み合わせが簡単:複合条件を自由に作れる
- バックテストしやすい:backtestingライブラリと連携
次のステップ
RSI逆張りボットができました!
次は、24時間安定稼働させるためのVPS(クラウド)運用を解説します。
📚 次に読む記事: VPSでFXボットを24時間稼働させる方法
自分のPCをつけっぱなしにしなくても、クラウド上でボットを動かし続ける方法を学びましょう!
🎁おすすめツール・サービス
※上記リンクにはプロモーション(広告)が含まれています
量子相場謀略|ホソノP
ブログでは書けない本音のトレード戦略とEA開発の裏側を公開。 データに基づいた検証で、再現性のある手法だけをお届けします。