Python + GCP + TradingView で MT4連携システムを構築する完全ガイド

PythonとGoogle Cloud Platform、TradingViewを組み合わせて、MT4との連携システムを構築する方法を詳しく解説します。Firestoreを使った注文管理からWebhookまで。

公開日: 2023-02-19
更新日: 2025-01-29

PythonとGoogle Cloud Platform(GCP)、TradingViewを組み合わせて、MT4との高度な連携システムを構築する方法を解説します。

システム構成

全体アーキテクチャ

TradingView → Webhook → GCP Cloud Functions → Firestore → MT4 EA

データの流れ:

  1. TradingViewのストラテジーが売買シグナルを生成
  2. WebhookでGCPのCloud Functionsに送信
  3. Pythonでリクエストを処理してFirestoreに保存
  4. MT4のEAがFirestoreから注文情報を取得
  5. MT4で実際の売買を実行

前提記事

基本的な仮想通貨自動売買については、こちらを先にご確認ください:

GCP環境のセットアップ

1. Firestoreデータベースの準備

Firestoreとは:

  • NoSQLドキュメントデータベース
  • リアルタイム同期機能
  • 従量課金制で小規模利用は格安

セットアップ手順

  1. GCPコンソールでFirestoreを選択
  2. ネイティブモードを選択
  3. リージョンを設定(asia-southeast1推奨)

2. Cloud Functionsの準備

Cloud Functionsの基本設定:

- Runtime: Python 3.9
- Memory: 256MB
- Timeout: 60秒
- リージョン: asia-southeast1

Python Webアプリの実装

main.py

from google.cloud import firestore
import json

def hello_world(request):
    """
    TradingViewからのWebhook受信とMT4への注文情報提供
    """
    request_json = request.get_json()
    order = ""
    symbol = ""
    
    # JSONからパラメータ取得
    if request_json and 'order' in request_json:
        order = request_json['order']
    if request_json and 'symbol' in request_json:
        symbol = request_json['symbol']
    
    if order == "":
        # MT4からの注文情報取得リクエスト
        state = load_order(symbol)
        return {"order": state}
    else:
        # TradingViewからの注文保存リクエスト
        update_order(symbol, order)
        return {"res": "ok"}

def update_order(symbol, order):
    """
    注文情報をFirestoreに保存
    """
    if symbol and order:
        db = firestore.Client()
        doc_ref = db.collection("TradingView").document(symbol)
        doc_ref.set({
            "order": order,
            "timestamp": firestore.SERVER_TIMESTAMP
        })
        print(f"注文保存: {symbol} - {order}")

def load_order(symbol):
    """
    Firestoreから注文情報を取得
    """
    if symbol:
        db = firestore.Client()
        doc = db.collection("TradingView").document(symbol).get()
        
        if doc.exists:
            data = doc.to_dict()
            return data.get("order", "None")
        else:
            return "None"
    
    return "None"

requirements.txt

google-cloud-firestore
functions-framework

環境変数の設定

import os
from google.auth import credentials

# サービスアカウントキーの設定
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'path/to/service-account.json'

TradingViewの設定

Pine Script戦略の例

//@version=5
strategy("GCP連携戦略", overlay=true)

// パラメータ
rsi_period = input.int(14, "RSI期間")
rsi_oversold = input.int(30, "RSI買われすぎ")
rsi_overbought = input.int(70, "RSI売られすぎ")

// インジケータ計算
rsi = ta.rsi(close, rsi_period)

// エントリー条件
long_condition = rsi < rsi_oversold
short_condition = rsi > rsi_overbought

// 注文実行
if long_condition
    strategy.entry("Long", strategy.long, comment="buy")
    
if short_condition
    strategy.entry("Short", strategy.short, comment="sell")

// 決済条件
if strategy.position_size > 0 and rsi > rsi_overbought
    strategy.close("Long", comment="buy_close")
    
if strategy.position_size < 0 and rsi < rsi_oversold
    strategy.close("Short", comment="sell_close")

Webhookアラートの設定

アラート作成時の設定:

  1. 条件: ストラテジーの注文実行時
  2. メッセージフォーマット:
{"symbol":"{{ticker}}", "order":"{{strategy.order.comment}}"}
  1. Webhook URL: Cloud FunctionsのトリガーURL
  2. 頻度: Once Per Bar Close

注文コメントの標準化

// 使用する注文コメント
comment = "buy"        // ロングエントリー
comment = "buy_close"  // ロング決済
comment = "sell"       // ショートエントリー
comment = "sell_close" // ショート決済

MT4 EA の実装

基本構造

//+------------------------------------------------------------------+
//| GCP TradingView連携EA                                            |
//+------------------------------------------------------------------+
#property strict

// パラメータ
input string web_hook_url = "https://your-project.cloudfunctions.net/webhook";
input string input_symbol = ""; // 通貨ペア(空白の場合は自動)
input int magic_number = 124455;
input double qty = 0.1; // ロット数
input int input_spread = 30; // スプレッド制限
input int input_slippage = 20; // スリッページ制限

メイン処理

void OnTick() {
    string order = "";
    static datetime prev_time = iTime(NULL, 0, 0);
    
    // スプレッドチェック
    if(Ask - Bid <= input_spread * _Point) {
        
        // 1分足の更新時のみ実行
        if(prev_time == iTime(NULL, 0, 0)) {
            return;
        }
        
        // 秒数制限(サーバー負荷対策)
        if(Seconds() < 2) {
            return;
        }
        
        prev_time = iTime(NULL, 0, 0);
        order = web_request();
    }
    
    // ポジション管理
    if(position_count(OP_BUY) > 0) {
        if(order == "buy_close") {
            position_close(OP_BUY);
        }
    }
    
    if(position_count(OP_SELL) > 0) {
        if(order == "sell_close") {
            position_close(OP_SELL);
        }
    }
    
    // エントリー処理
    if(order == "buy") {
        position_entry(OP_BUY);
    }
    if(order == "sell") {
        position_entry(OP_SELL);
    }
}

Web リクエスト処理

string web_request() {
    static string prev_state = "";
    string url = web_hook_url;
    string headers;
    string data;
    char post[], result[];
    
    // 通貨ペア設定
    string symbol = Symbol();
    if(input_symbol != "") {
        symbol = input_symbol;
    }
    
    // リクエスト作成
    headers = "Content-Type: application/json\r\n";
    data = "{\"symbol\":\"" + symbol + "\"}";
    
    ArrayResize(post, StringToCharArray(data, post, 0, WHOLE_ARRAY, CP_UTF8) - 1);
    
    // HTTP POST実行
    int rest = WebRequest("POST", url, headers, 5000, post, result, headers);
    
    string order = CharArrayToString(result);
    StringReplace(order, "\"", "");
    
    if(rest != 200) {
        Print(Symbol(), " エラー: ", CharArrayToString(result));
        return "";
    } else {
        // 同じ状態の重複実行防止
        if(prev_state == CharArrayToString(result)) {
            return "";
        }
        
        if(prev_state == "") {
            prev_state = CharArrayToString(result);
            return "";
        }
        
        prev_state = CharArrayToString(result);
        
        if(order == "buy" || order == "buy_close" || 
           order == "sell" || order == "sell_close") {
            Print(Symbol(), " 注文受信: ", CharArrayToString(result));
            return order;
        }
    }
    
    return "";
}

ポジション管理関数

int position_count(int side) {
    int count = 0;
    for(int i = OrdersTotal() - 1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
            if(OrderType() == side && 
               OrderSymbol() == Symbol() && 
               OrderMagicNumber() == magic_number) {
                count++;
            }
        }
    }
    return count;
}

void position_entry(int side) {
    if(side == OP_BUY) {
        OrderSend(NULL, side, qty, Ask, 0, 0, 0, NULL, magic_number, 0, clrGreen);
    }
    if(side == OP_SELL) {
        OrderSend(NULL, side, qty, Bid, 0, 0, 0, NULL, magic_number, 0, clrRed);
    }
}

void position_close(int side) {
    for(int i = OrdersTotal() - 1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
            if(OrderType() == side && 
               OrderSymbol() == Symbol() && 
               OrderMagicNumber() == magic_number) {
                OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), 0, clrBlue);
            }
        }
    }
}

セキュリティ対策

1. API認証の実装

import hmac
import hashlib
import base64

def verify_webhook(request):
    """
    Webhook署名検証
    """
    signature = request.headers.get('X-Signature')
    body = request.get_data()
    
    expected_signature = hmac.new(
        WEBHOOK_SECRET.encode(),
        body,
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(signature, expected_signature)

2. リクエスト制限

from functools import wraps
import time

request_timestamps = {}

def rate_limit(requests_per_minute=10):
    def decorator(func):
        @wraps(func)
        def wrapper(request):
            client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', 
                                          request.environ.get('REMOTE_ADDR'))
            
            current_time = time.time()
            
            if client_ip in request_timestamps:
                timestamps = request_timestamps[client_ip]
                timestamps = [t for t in timestamps if current_time - t < 60]
                
                if len(timestamps) >= requests_per_minute:
                    return {"error": "Rate limit exceeded"}, 429
                    
                timestamps.append(current_time)
                request_timestamps[client_ip] = timestamps
            else:
                request_timestamps[client_ip] = [current_time]
            
            return func(request)
        return wrapper
    return decorator

3. データ検証

def validate_order_data(data):
    """
    注文データの検証
    """
    valid_orders = ["buy", "sell", "buy_close", "sell_close"]
    valid_symbols = ["USDJPY", "EURJPY", "GBPJPY", "BTCUSD"]
    
    if data.get("order") not in valid_orders:
        return False
        
    if data.get("symbol") not in valid_symbols:
        return False
        
    return True

監視・ログ機能

Cloud Functions ログ

import logging
from google.cloud import logging as cloud_logging

# Cloud Logging設定
client = cloud_logging.Client()
client.setup_logging()

def log_trade_event(symbol, order, timestamp):
    """
    取引イベントのログ記録
    """
    logging.info(f"取引実行: {symbol} - {order} at {timestamp}")
    
    # Firestoreにも記録
    db = firestore.Client()
    doc_ref = db.collection("TradingLogs").document()
    doc_ref.set({
        "symbol": symbol,
        "order": order,
        "timestamp": timestamp,
        "status": "executed"
    })

MT4側のログ

void log_order_event(string action, string symbol, double lots, double price) {
    string log_message = StringFormat(
        "取引実行: %s %s %.2f @ %.5f",
        action, symbol, lots, price
    );
    
    Print(log_message);
    
    // ファイルにも記録
    int file_handle = FileOpen("trading_log.txt", FILE_WRITE | FILE_READ | FILE_TXT);
    if(file_handle != INVALID_HANDLE) {
        FileSeek(file_handle, 0, SEEK_END);
        FileWriteString(file_handle, TimeToString(TimeCurrent()) + ": " + log_message + "\n");
        FileClose(file_handle);
    }
}

デプロイと運用

Cloud Functions デプロイ

# GCP CLIでのデプロイ
gcloud functions deploy tradingview-webhook \
  --runtime python39 \
  --trigger-http \
  --allow-unauthenticated \
  --region asia-southeast1 \
  --memory 256MB

MT4設定

  1. WebRequestの許可設定

    • ツール → オプション → エキスパートアドバイザ
    • "WebRequestを許可する"をチェック
    • Cloud FunctionsのURLを許可リストに追加
  2. EA設定

    • web_hook_urlにCloud FunctionsのURL
    • 適切なmagic_numberを設定
    • spread、slippage制限を調整

トラブルシューティング

よくある問題

1. Webhookが届かない

  • TradingViewのアラート設定確認
  • Cloud FunctionsのURL確認
  • ログでリクエスト受信状況確認

2. MT4で注文が実行されない

  • WebRequest許可設定確認
  • magic_number重複確認
  • spread制限の調整

3. Firestoreへの書き込みエラー

  • 認証情報確認
  • データ形式確認
  • 権限設定確認

デバッグ方法

# Cloud Functions デバッグ
import json

def debug_request(request):
    print(f"Headers: {dict(request.headers)}")
    print(f"Body: {request.get_data()}")
    print(f"JSON: {request.get_json()}")
    
    return {"debug": "ok"}

まとめ

この連携システムにより、以下が実現できます:

メリット:

  • TradingViewの高度な分析機能活用
  • クラウドベースの安定稼働
  • 複数MT4との同時連携
  • リアルタイムでの注文同期

注意点:

  • ネットワーク遅延の考慮
  • APIクォータの管理
  • セキュリティ対策の徹底
  • 適切な監視体制の構築

このシステムを基に、さらなる機能拡張や最適化も可能です。安全で効率的な自動売買環境を構築してください。

この記事が役に立ったらシェアしてください

📚 プログラミング・開発 の関連記事