EAの更新をVPSへ手作業で運ぶのをやめた話

解説・基盤 · 1 min

git push だけでVPSのMT5に本番EAが自動配備される仕組みをPowerShellで作りました。MT5はex5を差し替えても再読み込みしない、など実際にはまった罠4つと解決策をまとめます。

このサイトのEAはVPS上のMT5で常時稼働しています。開発は手元のPCでやるので、EAを直すたびにこういう作業が発生していました。

  1. 手元でコンパイルして動作確認、gitにpush
  2. VPSにリモートデスクトップ(RDP)で入る
  3. VPS側でgit pull
  4. MetaEditorを開いて再コンパイル
  5. チャート上のEAを手動で再起動

1回あたり数分とはいえ、本番口座で動いているものなので毎回それなりに神経を使います。しかもうちの場合、EAのコードを直すのは主にAIエージェント(Claude Code)で、人間の仕事が「AIが直した結果をVPSへ運ぶ配達員」だけになっていました。これは自動化するしかない、ということで作ったのが今回の仕組みです。

できあがった形

最終形はこうなりました。開発機で1コマンド打つと、あとは全部勝手に進みます。

開発機:  deploy_ea.ps1 を実行
          ├ MetaEditor CLIで全EAをコンパイル(エラーが1つでもあれば中断)
          └ .mq5 と .ex5 を commit & push
              ↓ (GitHub)
VPS:     タスクスケジューラが5分毎に pull_deploy_ea.ps1 を実行
          ├ 新しいコミットがあれば git pull
          ├ リポジトリの .ex5 と配備先の .ex5 をハッシュ比較
          ├ 差があるものだけ MQL5\Experts へコピー(旧版は退避)
          └ 配備したターミナルを自動で再起動
MT5:     チャート・EA・パラメータ・自動売買ONを自動復元して再稼働
          └ EAが起動通知をDiscordへ送信 ← これが届いたら配備完了の合図

方式は「pull型」を選びました。VPS側からGitHubを定期的に見に行く形です。逆の「push型」(開発機からSSHでVPSへ直接送り込む)も考えましたが、WindowsのVPSにSSHサーバーを立てて受信ポートを開ける手間と管理が増えるので、受信経路が一切いらないpull型にしました。5分間隔のポーリング遅延はEAの更新では実害がありません。

コンパイルは開発機だけで行い、VPSにはコンパイル済みの.ex5だけを運びます。.ex5はブローカーにもPCにも紐づかない中間コードなので、どこでコンパイルしても動作は同じです。バイナリをgitに入れることになりますが、数百KBですし、「どのコミットのバイナリが本番で動いているか」が履歴で追えるのはむしろ利点でした。

はまった罠が4つ

すんなり動いたわけではなく、途中で4回つまずきました。この記事の価値はたぶんここです。

罠1: .ex5は同じソースでも毎回ハッシュが変わる

「.ex5の内容が変わったときだけ配備する」を実装するため、同一ソースを2回コンパイルしてSHA256を比べたら、毎回違うハッシュになりました。MQL5コンパイラがコンパイル時のメタデータをバイナリに埋め込むためです。

対策として、配備スクリプトではなく送り出し側にガードを入れました。ソースの.mq5にgit差分がないEAは、再コンパイルで.ex5が変わっていてもコミットしない(HEADの状態に戻す)。これで「うっかりdeployコマンドを2回叩いたら無意味にEAが再起動される」事故がなくなります。

罠2: PowerShellスクリプトがログも残さず死ぬ

VPS側のログがこうなって止まりました。

00:27:05 新コミット検出: afb3a72 -> 6e7664a
(この後、何も出力されない)

pull失敗ならエラーを、成功なら「pull完了」を必ず書くはずが、どちらも無い。原因はWindows PowerShell 5.1の有名な挙動でした。$ErrorActionPreference = "Stop"にした状態でgit pull 2>&1を実行すると、gitが正常時でもstderrに出す進捗表示(From github.com...など)がエラー扱いになり、スクリプトがその場で例外死します。しかもcatchしていなかったのでログに何も残らない。

対策は3点セットです。git実行区間だけエラー即死を無効化して終了コードで成否判定、スクリプト全体をtry/catchで包んで予期しない例外は必ず[FATAL]としてログに書く、そして配備判定を「pull前後の差分」ではなく**「リポジトリの.ex5 vs 配備先の.ex5」の直接比較**に変更。最後のが効いていて、途中でどんな失敗をしても次の5分後の実行で勝手に復旧します(自己修復)。

罠3: MT5は.ex5を外から差し替えても再読み込みしない

いちばんの誤算はこれでした。MT5には「EAを再コンパイルするとチャート上のEAが自動で再初期化される」挙動(REASON_RECOMPILE)があるので、.ex5ファイルを新しいものに上書きすれば同じことが起きると思っていたんです。

実際に本番で試した結果: ファイルは更新されたのに、稼働中のEAは古いまま動き続けました。自動再初期化が起きるのはMetaEditorでの再コンパイル時だけで、外部からのファイルコピーでは起きません。EAは起動時にメモリへ読み込まれ、ディスク上のファイルが変わっても関知しないようです。

対策は割り切って、配備が発生したターミナルをスクリプトが丸ごと再起動することにしました。乱暴に聞こえますが、MT5はチャート・EA・入力パラメータ・自動売買ONを起動時に全部復元してくれるので、従来手動でやっていた「EA再起動」と意味は同じです。ポジションはそもそもサーバー側にあるので影響ありません。再起動はプロセスに正常終了を要求(WM_CLOSE)→60秒待って応答がなければ強制終了→起動、の順で行います。もちろん、配備が無い通常の5分毎の実行では何もしません。

罠4: タスクスケジューラの登録方法でウィンドウが閉じられない

罠3の再起動には続きがあります。タスクスケジューラのタスクを普通に登録すると、タスクは非対話セッションで実行されるため、RDPセッション側で動いているMT5のウィンドウに正常終了を要求できません(ウィンドウハンドルが見えない)。この場合60秒待って強制終了にフォールバックはしますが、毎回強制終了は避けたい。

タスクを/IT(対話)オプション付きで登録すると、ログオン中のユーザーセッション内で実行されるようになり、ウィンドウを正常に閉じられます。「ログオン中しか動かない」制約が付きますが、MT5自体がログオンセッションで動いているので、実質的に制約になりません。RDPを切断(サインアウトではなく)した状態でもセッションは生きているので、ちゃんと動きます。これは実際にRDPを切った状態で配備→再起動→Discord通知まで通ることを確認しました。

安全装置

本番口座に自動でバイナリを流し込む仕組みなので、安全装置は多めに入れています。

  • コンパイルエラーが1つでもあれば配備自体が始まらない(壊れたバイナリは物理的に届かない)
  • 上書き前の旧.ex5はタイムスタンプ付きで退避(緊急時はコピーで即ロールバック)
  • 正式なロールバックはgit revertしてpushするだけ(旧バイナリが同じパイプラインで自動配備される)
  • VPS側のSSH鍵はGitHubの**Deploy Key(read-only)**に分離(VPSが侵害されてもリポジトリを改竄できない)
  • EAは起動時にDiscordへWebhook通知を送るので、スマホに通知が来たら配備完了という確認がタダで手に入る

AIエージェントに配備を忘れさせない

冒頭に書いた通り、EAのコードを直すのは主にAIエージェントです。となると「AIが修正だけして配備を忘れる」が新しい事故パターンになります。

これはエージェント側の設定で潰しました。Claude Codeのフック機能で、EAソース(.mq5)が編集された瞬間に「作業完了前に必ず配備スクリプトを実行すること」というリマインダーをエージェントのコンテキストへ自動注入します。プロジェクトのルールファイルにも同じ規約を明文化してあるので、二重で効きます。実際、この記事を書く前の検証でも、AIがEAを編集→フックが発火→AIが自分で配備コマンドを実行→Discordに起動通知、まで人間の操作ゼロで回りました。

まとめ

  • EA更新の反映が「1コマンド→5分待つ→スマホのDiscord通知を見る」だけになった
  • VPSへのRDPログインは日常運用から消えた
  • 手作業5ステップで毎回あった「コンパイル忘れ」「再起動忘れ」のリスクも消えた

仕組み自体はPowerShellスクリプト2本とタスクスケジューラだけの素朴なものです。それでも、MT5の再読み込みの仕様やPowerShell 5.1の落とし穴など、実際に本番で動かして初めて分かることが多かったので、同じ構成を作る人の時間節約になれば嬉しいです。