AIトレードのデモ版を公開!|ChatGPT・Gemini・Grok対応、ソースコードを丸ごとコピー可!
こんにちは、まさやんです。
このたび、AIトレードのデモ版のソースコードの公開ページを用意しました。
今回公開するプログラムは、FX向けAIトレードシステムのデモ版です。
しかも今回は、ChatGPT、Gemini、Grokの3つのAIに対応したソースコードを丸ごと公開します。
利用には有料のAPIキーが別途必要です。ChatGPT・Gemini・Grokの中から、最低1つを各自で発行してご利用ください。
- OpenAI(ChatGPT)
- Google(Gemini)
- xAI(Grok)
API使用料は呼び出し回数により変動しますが、目安は1つのAIトレードで1日0.1~1ドル程度です。日本円で約15~150円、月20日稼働で約300~3,000円程度を想定しています。
なぜデモ版を公開するのか?
AIトレードと聞くと、どうしても次のような印象を持つ方が多いと思います。
- 本当に動くか分からない
- 中身が見えない
- ブラックボックス化されていてセキュリティ的に不安
- ソースコードが公開されておらず信用できない
実際、AIを使った売買ロジックやシグナル判定は、外から見るだけでは仕組みが分かりません。
「AIが判断しています」と言われても、利用する側からすると不安が残るのは当然です。
そこで今回は、中身を隠さない完全公開を選びました。
デモ版を公開し、さらにソースコードもすべて公開する。
これによって、少なくとも
- どんな構成で動いているのか
- どうやってAIへ指示を出しているのか
- MT4 / MT5 とどう連携しているのか
- PHP側でどのような処理をしているのか
このあたりを確認できるようにしています。
そして、実際にトレードできることを確認し、納得していただけた方に完成版の有料システムをご検討いただきたい。そうした思いから、今回は無料でソースコードを公開することにしました。
2026年4月12日 販売開始 AIトレードシステム①
「AI Signal Trader」AIがニュース・経済指標・SNSを監視し、売買判断を行う次世代型トレードツール
https://www.gogojungle.co.jp/finance/navi/articles/114430
2026年4月12日 販売開始 AIトレードシステム②
「AI Breakout Line」AIがチャートを分析し、ブレイクアウトポイントを検知して自動でエントリーするEAトレードシステム
https://www.gogojungle.co.jp/finance/navi/articles/114426
このページで公開する内容
この公開ページでは、AIトレードを実装するデモ版・サンプルファイルを公開しています。
内容としては、次のようなものです。
- MT4用ファイル
- MT5用ファイル
- PHPスクリプト
GitHubの私のページでも公開しています。GitHubは、世界中のプログラマーや開発者がソースコードを公開・共有している、世界最大級の開発プラットフォームです。
ChatGPT・Gemini・Grokに対応
今回のデモ版は、以下のAIに対応しています。
- OpenAI系
- Gemini系
- Grok系
もちろん、プログラムコードを書ける方であれば、AnthropicのClaude Codeをはじめ、他のAIモデルを組み込むことも可能です。
本当に無料でソースコードを全部公開する意味
ここが今回の大きなポイントです。
普通、この手のシステムは中身を隠しがちです。
ですが私は、あえて公開する方が価値があると考えました。
理由は3つあります。
1. 信頼性が上がる
ソースコードを見られることで、
「何をしているのか分からない」
という不安を減らせます。
2. 実際に動かせた人にのみ有料版を案内できる
AIトレードは難しそうに見えますが、実際には通常のEA自動売買より少しハードルが高い程度です。MT4・MT5に加え、PHPが動くサーバーやAI用APIの基本知識は必要になりますが、自分でプログラムを書く必要はありません。これまでEAを動かしてきた方なら、十分に扱える範囲だと思います。
3. 開発の入口として使える
私は普段、OpenAIのCodexを使ってシステム開発をしています。ほかにも、AnthropicのClaude Code、Google AI Studioなどがあり、こうしたAI開発ツールによって複雑なシステムも構築しやすくなりました。もちろん、EAやAIトレードを作るには、プログラミングの知識とMT4・MT5のEA構築スキルは必須です。
こんな方に向いています
この公開ページは、次のような方に向いています。
- AIトレードに興味がある方
- FXとAIを組み合わせたい方
- MT4 / MT5 のEAに興味がある方
- ChatGPT / Gemini / Grok を売買判定に活かしたい方
逆に、
「何も設定せず、完成品をすぐ使いたい」
という方には向いていません。
まず、以下のソースコードをコピーして、PCに保存してください
ZIPファイルでダウンロード ⇒ ai-trading-demo-free_ver1.01.zip
今回の公開内容は、あくまでデモ版・サンプル公開という位置づけです。
本デモ版は、AIとの連携が成功するとロングポジションを持つ仕様になっています。プロンプトを書き換えればAIに売買判定をさせることも可能ですが、システム全体を再構築するにはプログラミングの知識が必要になります。有料版では、PHPファイルを差し替えるだけでバージョンアップできる形を想定しています。まずは無料デモ版で、正常に動作するところまでぜひ実装してみてください。
パソコンに保存する際に、拡張子が変更できない場合、以下の手順をお試しください。
ZIPファイルでダウンロード ⇒ ai-trading-demo-free_ver1.01.zip
以下、ソースコードの全文です。MT4 MT5 PHPのコードをコピーして、パソコンに保存してください。
①MT4のソースコード
#property copyright "Copyright 2026, Masayan."
#property version "1.01"
#property strict
#property description "ai-trading-demo-free for MT4"
extern string ProfileID = "mt4demo";
extern string AIProvider = "1"; // 1.ChatGPT / 2.Gemini / 3.Xai
extern string ChatGPTApiKey = "";
extern string GeminiApiKey = "";
extern string XaiApiKey = "";
extern string RSSFeedURL = "https://fx.reform-network.net/feed/";
extern string PromptText = "If the RSS feed content can be read, please return 1 for a long signal.";
extern string SaveSettingsURL = "https://example.com/ai-trading-demo-free/save_settings.php";
extern string GetSignalURL = "https://example.com/ai-trading-demo-free/get_signal.php";
extern string UpdateSignalURL = "https://example.com/ai-trading-demo-free/update_signal.php";
extern int AI_TriggerMode = 1;
extern int AI_MinuteOffset = 0;
extern int AI_HourOffset = 0;
extern int RequestTimeoutMs = 15000;
extern int SyncIntervalMins = 60;
extern int Magic = 20260404;
extern double Lots = 0.01;
extern int Slippage = 3;
extern double StopLossPips = 0.0;
extern double TakeProfitPips = 0.0;
extern bool OnePositionOnly = true;
datetime g_last_settings_sync = 0;
datetime g_last_settings_try = 0;
datetime g_last_update_try = 0;
int g_last_signal = 0;
string g_last_status = "idle";
string g_last_message = "";
string g_last_signal_id = "";
string g_last_executed_signal_id = "";
datetime g_last_expire_unix = 0;
bool IsBacktestMode(){ return (bool)MQLInfoInteger(MQL_TESTER); }
string BuildBacktestWarningMessage(){ return "Error\nThis EA does not support backtesting.\nPHP and AI access are disabled in tester."; }
double PipValue(){
if(Digits == 3 || Digits == 5) return Point * 10.0;
return Point;
}
string SelectedApiKey(){
string provider = AIProvider;
StringToLower(provider);
if(provider == "2") return GeminiApiKey;
if(provider == "3") return XaiApiKey;
return ChatGPTApiKey;
}
bool IsTradeTriggerTime(){
datetime now = TimeCurrent();
int hour = TimeHour(now);
int minute = TimeMinute(now);
if(AI_TriggerMode == 1) return true;
if(AI_TriggerMode == 2) return (minute % 10) == (AI_MinuteOffset % 10);
if(AI_TriggerMode == 3) return minute == AI_MinuteOffset;
if(AI_TriggerMode == 4) return (hour % 4) == (AI_HourOffset % 4) && minute == AI_MinuteOffset;
if(AI_TriggerMode == 5) return hour == AI_HourOffset && minute == AI_MinuteOffset;
return false;
}
bool ShouldRunNow(datetime lastRun){
if(!IsTradeTriggerTime()) return false;
int nowKey = TimeHour(TimeCurrent()) * 100 + TimeMinute(TimeCurrent());
int lastKey = TimeHour(lastRun) * 100 + TimeMinute(lastRun);
if(TimeDay(TimeCurrent()) != TimeDay(lastRun) || TimeMonth(TimeCurrent()) != TimeMonth(lastRun) || TimeYear(TimeCurrent()) != TimeYear(lastRun)) return true;
return nowKey != lastKey;
}
string UrlEncode(string value){
uchar data[];
StringToCharArray(value, data, 0, WHOLE_ARRAY, CP_UTF8);
string out = "";
string hex = "0123456789ABCDEF";
for(int i = 0; i < ArraySize(data); i++){
int ch = data[i];
if(ch == 0) break;
if((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '-' || ch == '_' || ch == '.' || ch == '~'){
out += CharToString((uchar)ch);
}else if(ch == ' '){
out += "%20";
}else{
out += "%" + StringSubstr(hex, ch / 16, 1) + StringSubstr(hex, ch % 16, 1);
}
}
return out;
}
bool HttpPost(string url, string body, string &responseText, int &httpCode){
responseText = "";
httpCode = -1;
char post[];
StringToCharArray(body, post, 0, WHOLE_ARRAY, CP_UTF8);
char result[];
string headers = "Content-Type: application/x-www-form-urlencoded\r\n";
string resultHeaders = "";
ResetLastError();
int res = WebRequest("POST", url, headers, RequestTimeoutMs, post, result, resultHeaders);
if(res == -1){
responseText = "webrequest_error=" + IntegerToString(GetLastError());
return false;
}
httpCode = res;
responseText = CharArrayToString(result, 0, -1, CP_UTF8);
return true;
}
string ExtractValue(string text, string key){
string target = key + "=";
int pos = StringFind(text, target, 0);
if(pos < 0) return "";
int start = pos + StringLen(target);
int end = StringFind(text, "\n", start);
if(end < 0) end = StringLen(text);
return StringSubstr(text, start, end - start);
}
bool SyncSettings(){
g_last_settings_try = TimeCurrent();
string body = "";
body += "profile_id=" + UrlEncode(ProfileID);
body += "&account_number=" + IntegerToString(AccountNumber());
body += "&account_type=" + IntegerToString((int)AccountInfoInteger(ACCOUNT_TRADE_MODE));
body += "&symbol=" + UrlEncode(Symbol());
body += "&timeframe=" + IntegerToString(Period());
body += "&provider=" + UrlEncode(AIProvider);
body += "&api_key=" + UrlEncode(SelectedApiKey());
body += "&rss_feed_url=" + UrlEncode(RSSFeedURL);
body += "&prompt_text=" + UrlEncode(PromptText);
body += "&trigger_mode=" + IntegerToString(AI_TriggerMode);
body += "&minute_offset=" + IntegerToString(AI_MinuteOffset);
body += "&hour_offset=" + IntegerToString(AI_HourOffset);
body += "&magic=" + IntegerToString(Magic);
string response = "";
int httpCode = -1;
bool ok = HttpPost(SaveSettingsURL, body, response, httpCode);
if(ok && httpCode == 200){
g_last_settings_sync = TimeCurrent();
return true;
}
g_last_status = "sync_error";
g_last_message = response;
return false;
}
bool RequestSignalUpdate(){
string body = "";
body += "profile_id=" + UrlEncode(ProfileID);
body += "&account_number=" + IntegerToString(AccountNumber());
body += "&account_type=" + IntegerToString((int)AccountInfoInteger(ACCOUNT_TRADE_MODE));
body += "&symbol=" + UrlEncode(Symbol());
body += "&timeframe=" + IntegerToString(Period());
string response = "";
int httpCode = -1;
bool ok = HttpPost(UpdateSignalURL, body, response, httpCode);
g_last_update_try = TimeCurrent();
if(!ok || httpCode != 200){
g_last_status = "update_error";
g_last_message = response;
return false;
}
return true;
}
bool FetchSignal(){
string body = "";
body += "profile_id=" + UrlEncode(ProfileID);
body += "&account_number=" + IntegerToString(AccountNumber());
body += "&symbol=" + UrlEncode(Symbol());
body += "&timeframe=" + IntegerToString(Period());
body += "&provider=" + UrlEncode(AIProvider);
string response = "";
int httpCode = -1;
bool ok = HttpPost(GetSignalURL, body, response, httpCode);
if(!ok || httpCode != 200){
g_last_status = "get_error";
g_last_message = response;
return false;
}
g_last_status = ExtractValue(response, "status");
g_last_message = ExtractValue(response, "message");
g_last_signal = StrToInteger(ExtractValue(response, "signal"));
g_last_signal_id = ExtractValue(response, "signal_id");
g_last_expire_unix = (datetime)StrToInteger(ExtractValue(response, "expire_unix"));
return true;
}
bool HasOpenBuy(){
for(int i = OrdersTotal() - 1; i >= 0; i--){
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderSymbol() != Symbol()) continue;
if(OrderMagicNumber() != Magic) continue;
if(OrderType() == OP_BUY) return true;
}
return false;
}
bool OpenLongPosition(){
if(OnePositionOnly && HasOpenBuy()) return true;
double ask = NormalizeDouble(Ask, Digits);
double sl = 0.0;
double tp = 0.0;
double pip = PipValue();
if(StopLossPips > 0.0) sl = NormalizeDouble(ask - StopLossPips * pip, Digits);
if(TakeProfitPips > 0.0) tp = NormalizeDouble(ask + TakeProfitPips * pip, Digits);
int ticket = OrderSend(Symbol(), OP_BUY, Lots, ask, Slippage, sl, tp, "ai-trading-demo-free", Magic, 0, clrBlue);
if(ticket < 0){
g_last_status = "trade_error";
g_last_message = "order_send_failed=" + IntegerToString(GetLastError());
return false;
}
g_last_executed_signal_id = g_last_signal_id;
g_last_status = "long_opened";
g_last_message = "demo_force_long";
Alert("Long entry executed successfully. Operation check OK.");
return true;
}
void RefreshComment(){
string s = "ai-trading-demo-free\n";
s += "Provider=" + AIProvider + "\n";
s += "Status=" + g_last_status + "\n";
s += "Signal=" + IntegerToString(g_last_signal) + "\n";
s += "SignalID=" + g_last_signal_id + "\n";
s += "ExecutedSignalID=" + g_last_executed_signal_id + "\n";
s += "Message=" + g_last_message + "\n";
Comment(s);
}
int OnInit(){
if(IsBacktestMode()){
Comment(BuildBacktestWarningMessage());
return(INIT_SUCCEEDED);
}
SyncSettings();
RefreshComment();
return(INIT_SUCCEEDED);
}
void OnTick(){
if(IsBacktestMode()){
Comment(BuildBacktestWarningMessage());
return;
}
if((TimeCurrent() - g_last_settings_try) >= SyncIntervalMins * 60){
SyncSettings();
}
if(ShouldRunNow(g_last_update_try)){
g_last_update_try = TimeCurrent();
if(SyncSettings() && RequestSignalUpdate() && FetchSignal()){
if(g_last_status == "ready" && g_last_signal == 1 && g_last_signal_id != "" && g_last_signal_id != g_last_executed_signal_id){
OpenLongPosition();
}
}
}
RefreshComment();
}
②MT5のソースコード
コンパイル前に、5行目の#includeの行をのように、半角の<>に直してください。
#property copyright "Copyright 2026, Masayan."
#property version "1.01"
#property strict
#property description "ai-trading-demo-free for MT5"
#include <Trade\Trade.mqh>
input string ProfileID = "mt5demo";
input string AIProvider = "1"; // 1.ChatGPT / 2.Gemini / 3.Xai
input string ChatGPTApiKey = "";
input string GeminiApiKey = "";
input string XaiApiKey = "";
input string RSSFeedURL = "https://fx.reform-network.net/feed/";
input string PromptText = "If the RSS feed content can be read, please return 1 for a long signal.";
input string SaveSettingsURL = "https://example.com/ai-trading-demo-free/save_settings.php";
input string GetSignalURL = "https://example.com/ai-trading-demo-free/get_signal.php";
input string UpdateSignalURL = "https://example.com/ai-trading-demo-free/update_signal.php";
input int AI_TriggerMode = 1;
input int AI_MinuteOffset = 0;
input int AI_HourOffset = 0;
input int RequestTimeoutMs = 15000;
input int SyncIntervalMins = 60;
input int Magic = 20260405;
input double Lots = 0.01;
input int Slippage = 3;
input double StopLossPips = 0.0;
input double TakeProfitPips = 0.0;
input bool OnePositionOnly = true;
CTrade trade;
datetime g_last_settings_sync = 0;
datetime g_last_settings_try = 0;
datetime g_last_update_try = 0;
int g_last_signal = 0;
string g_last_status = "idle";
string g_last_message = "";
string g_last_signal_id = "";
string g_last_executed_signal_id = "";
datetime g_last_expire_unix = 0;
bool IsBacktestMode(){ return (bool)MQLInfoInteger(MQL_TESTER); }
string BuildBacktestWarningMessage(){ return "Error\nThis EA does not support backtesting.\nPHP and AI access are disabled in tester."; }
double PipValue(){
if(_Digits == 3 || _Digits == 5) return _Point * 10.0;
return _Point;
}
string SelectedApiKey(){
string provider = AIProvider;
StringToLower(provider);
if(provider == "2") return GeminiApiKey;
if(provider == "3") return XaiApiKey;
return ChatGPTApiKey;
}
bool IsTradeTriggerTime(){
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(AI_TriggerMode == 1) return true;
if(AI_TriggerMode == 2) return (dt.min % 10) == (AI_MinuteOffset % 10);
if(AI_TriggerMode == 3) return dt.min == AI_MinuteOffset;
if(AI_TriggerMode == 4) return (dt.hour % 4) == (AI_HourOffset % 4) && dt.min == AI_MinuteOffset;
if(AI_TriggerMode == 5) return dt.hour == AI_HourOffset && dt.min == AI_MinuteOffset;
return false;
}
bool ShouldRunNow(datetime lastRun){
if(!IsTradeTriggerTime()) return false;
MqlDateTime nowDt, lastDt;
TimeToStruct(TimeCurrent(), nowDt);
TimeToStruct(lastRun, lastDt);
if(nowDt.day != lastDt.day || nowDt.mon != lastDt.mon || nowDt.year != lastDt.year) return true;
return !(nowDt.hour == lastDt.hour && nowDt.min == lastDt.min);
}
string ToHex2(int value){
string hex = "0123456789ABCDEF";
return StringSubstr(hex, value / 16, 1) + StringSubstr(hex, value % 16, 1);
}
string UrlEncode(string value){
uchar data[];
StringToCharArray(value, data, 0, WHOLE_ARRAY, CP_UTF8);
string out = "";
for(int i = 0; i < ArraySize(data); i++){
int ch = data[i];
if(ch == 0) break;
if((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '-' || ch == '_' || ch == '.' || ch == '~'){
out += CharToString((uchar)ch);
}else if(ch == ' '){
out += "%20";
}else{
out += "%" + ToHex2(ch);
}
}
return out;
}
bool HttpPost(string url, string body, string &responseText, int &httpCode){
responseText = "";
httpCode = -1;
char post[];
StringToCharArray(body, post, 0, WHOLE_ARRAY, CP_UTF8);
char result[];
string headers = "Content-Type: application/x-www-form-urlencoded\r\n";
string resultHeaders = "";
ResetLastError();
int res = WebRequest("POST", url, headers, RequestTimeoutMs, post, result, resultHeaders);
if(res == -1){
responseText = "webrequest_error=" + IntegerToString(GetLastError());
return false;
}
httpCode = res;
responseText = CharArrayToString(result, 0, -1, CP_UTF8);
return true;
}
string ExtractValue(string text, string key){
string target = key + "=";
int pos = StringFind(text, target);
if(pos < 0) return "";
int start = pos + StringLen(target);
int end = StringFind(text, "\n", start);
if(end < 0) end = StringLen(text);
return StringSubstr(text, start, end - start);
}
bool SyncSettings(){
g_last_settings_try = TimeCurrent();
string body = "";
body += "profile_id=" + UrlEncode(ProfileID);
body += "&account_number=" + IntegerToString((int)AccountInfoInteger(ACCOUNT_LOGIN));
body += "&account_type=" + IntegerToString((int)AccountInfoInteger(ACCOUNT_TRADE_MODE));
body += "&symbol=" + UrlEncode(_Symbol);
body += "&timeframe=" + IntegerToString((int)_Period);
body += "&provider=" + UrlEncode(AIProvider);
body += "&api_key=" + UrlEncode(SelectedApiKey());
body += "&rss_feed_url=" + UrlEncode(RSSFeedURL);
body += "&prompt_text=" + UrlEncode(PromptText);
body += "&trigger_mode=" + IntegerToString(AI_TriggerMode);
body += "&minute_offset=" + IntegerToString(AI_MinuteOffset);
body += "&hour_offset=" + IntegerToString(AI_HourOffset);
body += "&magic=" + IntegerToString(Magic);
string response = "";
int httpCode = -1;
bool ok = HttpPost(SaveSettingsURL, body, response, httpCode);
if(ok && httpCode == 200){
g_last_settings_sync = TimeCurrent();
return true;
}
g_last_status = "sync_error";
g_last_message = response;
return false;
}
bool RequestSignalUpdate(){
string body = "";
body += "profile_id=" + UrlEncode(ProfileID);
body += "&account_number=" + IntegerToString((int)AccountInfoInteger(ACCOUNT_LOGIN));
body += "&account_type=" + IntegerToString((int)AccountInfoInteger(ACCOUNT_TRADE_MODE));
body += "&symbol=" + UrlEncode(_Symbol);
body += "&timeframe=" + IntegerToString((int)_Period);
string response = "";
int httpCode = -1;
bool ok = HttpPost(UpdateSignalURL, body, response, httpCode);
g_last_update_try = TimeCurrent();
if(!ok || httpCode != 200){
g_last_status = "update_error";
g_last_message = response;
return false;
}
return true;
}
bool FetchSignal(){
string body = "";
body += "profile_id=" + UrlEncode(ProfileID);
body += "&account_number=" + IntegerToString((int)AccountInfoInteger(ACCOUNT_LOGIN));
body += "&symbol=" + UrlEncode(_Symbol);
body += "&timeframe=" + IntegerToString((int)_Period);
body += "&provider=" + UrlEncode(AIProvider);
string response = "";
int httpCode = -1;
bool ok = HttpPost(GetSignalURL, body, response, httpCode);
if(!ok || httpCode != 200){
g_last_status = "get_error";
g_last_message = response;
return false;
}
g_last_status = ExtractValue(response, "status");
g_last_message = ExtractValue(response, "message");
g_last_signal = (int)StringToInteger(ExtractValue(response, "signal"));
g_last_signal_id = ExtractValue(response, "signal_id");
g_last_expire_unix = (datetime)StringToInteger(ExtractValue(response, "expire_unix"));
return true;
}
bool HasOpenBuy(){
for(int i = PositionsTotal() - 1; i >= 0; i--){
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((int)PositionGetInteger(POSITION_MAGIC) != Magic) continue;
if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) return true;
}
return false;
}
bool OpenLongPosition(){
if(OnePositionOnly && HasOpenBuy()) return true;
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick)) return false;
double price = tick.ask;
double sl = 0.0;
double tp = 0.0;
double pip = PipValue();
if(StopLossPips > 0.0) sl = NormalizeDouble(price - StopLossPips * pip, _Digits);
if(TakeProfitPips > 0.0) tp = NormalizeDouble(price + TakeProfitPips * pip, _Digits);
trade.SetExpertMagicNumber((long)Magic);
trade.SetDeviationInPoints(Slippage);
bool ok = trade.Buy(Lots, _Symbol, price, sl, tp, "ai-trading-demo-free");
if(!ok){
g_last_status = "trade_error";
g_last_message = "buy_failed";
return false;
}
g_last_executed_signal_id = g_last_signal_id;
g_last_status = "long_opened";
g_last_message = "demo_force_long";
Alert("Long entry executed successfully. Operation check OK.");
return true;
}
void RefreshComment(){
string s = "ai-trading-demo-free\n";
s += "Provider=" + AIProvider + "\n";
s += "Status=" + g_last_status + "\n";
s += "Signal=" + IntegerToString(g_last_signal) + "\n";
s += "SignalID=" + g_last_signal_id + "\n";
s += "ExecutedSignalID=" + g_last_executed_signal_id + "\n";
s += "Message=" + g_last_message + "\n";
Comment(s);
}
int OnInit(){
if(IsBacktestMode()){
Comment(BuildBacktestWarningMessage());
return(INIT_SUCCEEDED);
}
SyncSettings();
RefreshComment();
return(INIT_SUCCEEDED);
}
void OnTick(){
if(IsBacktestMode()){
Comment(BuildBacktestWarningMessage());
return;
}
if((TimeCurrent() - g_last_settings_try) >= SyncIntervalMins * 60){
SyncSettings();
}
if(ShouldRunNow(g_last_update_try)){
g_last_update_try = TimeCurrent();
if(SyncSettings() && RequestSignalUpdate() && FetchSignal()){
if(g_last_status == "ready" && g_last_signal == 1 && g_last_signal_id != "" && g_last_signal_id != g_last_executed_signal_id){
OpenLongPosition();
}
}
}
RefreshComment();
}
③get_signal.phpのソースコード
※ 保存前に、先頭の「<?php」を半角の「<」に直してください。末尾の終了タグ?>は不要です。
<?php
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
date_default_timezone_set('Asia/Tokyo');
header('Content-Type: text/plain; charset=UTF-8');
function respond_and_exit(array $lines, int $httpCode = 200): void {
http_response_code($httpCode);
foreach ($lines as $line) {
echo $line . "\n";
}
exit;
}
function post_value(string $key, string $default = ''): string {
if (!isset($_POST[$key]) || is_array($_POST[$key])) {
return $default;
}
return trim((string)$_POST[$key]);
}
function safe_profile_id(string $profileId): bool {
return (bool)preg_match('/^[a-zA-Z0-9_\-]+$/', $profileId);
}
function read_json_file(string $path): ?array {
if (!file_exists($path) || !is_readable($path)) {
return null;
}
$json = file_get_contents($path);
if ($json === false || $json === '') {
return null;
}
$data = json_decode($json, true);
return is_array($data) ? $data : null;
}
function append_access_log(string $path, array $row): void {
$dir = dirname($path);
if (!is_dir($dir)) {
@mkdir($dir, 0755, true);
}
$isNew = !file_exists($path);
$fp = fopen($path, 'a');
if ($fp === false) {
return;
}
if ($isNew) {
fputcsv($fp, ['accessed_at', 'profile_id', 'account_number', 'symbol', 'status', 'signal', 'message'], ',', '"', '\\');
}
fputcsv($fp, $row, ',', '"', '\\');
fclose($fp);
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
respond_and_exit(['status=error', 'signal=0', 'message=invalid_method', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 405);
}
$profileId = post_value('profile_id');
$accountNumber = post_value('account_number');
$symbol = post_value('symbol');
if ($profileId === '' || !safe_profile_id($profileId)) {
respond_and_exit(['status=error', 'signal=0', 'message=invalid_profile_id', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 400);
}
if ($accountNumber === '' || !preg_match('/^\d{4,12}$/', $accountNumber)) {
respond_and_exit(['status=error', 'signal=0', 'message=invalid_account_number', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 400);
}
if ($symbol === '') {
respond_and_exit(['status=error', 'signal=0', 'message=missing_symbol', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 400);
}
$baseDir = __DIR__;
$settingsFile = $baseDir . '/settings/' . $profileId . '.json';
$latestFile = $baseDir . '/signals/latest_' . $profileId . '.json';
$processingFile = $baseDir . '/signals/processing_' . $profileId . '.lock';
$accessLogFile = $baseDir . '/logs/signal_access_' . date('Ym') . '.csv';
$settings = read_json_file($settingsFile);
if ($settings === null) {
append_access_log($accessLogFile, [date('Y-m-d H:i:s'), $profileId, $accountNumber, $symbol, 'error', 0, 'settings_not_found']);
respond_and_exit(['status=error', 'signal=0', 'message=settings_not_found', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 404);
}
if (file_exists($processingFile)) {
$latest = read_json_file($latestFile);
$signal = (int)($latest['signal'] ?? 0);
$signalId = (string)($latest['signal_id'] ?? '');
$createdUnix = (int)($latest['created_unix'] ?? 0);
$expireUnix = (int)($latest['expire_unix'] ?? 0);
append_access_log($accessLogFile, [date('Y-m-d H:i:s'), $profileId, $accountNumber, $symbol, 'processing', $signal, 'processing_now']);
respond_and_exit([
'status=processing',
'signal=' . $signal,
'message=processing_now',
'signal_id=' . $signalId,
'created_unix=' . $createdUnix,
'expire_unix=' . $expireUnix
], 200);
}
$latest = read_json_file($latestFile);
if ($latest === null) {
append_access_log($accessLogFile, [date('Y-m-d H:i:s'), $profileId, $accountNumber, $symbol, 'stale', 0, 'no_signal_yet']);
respond_and_exit(['status=stale', 'signal=0', 'message=no_signal_yet', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 200);
}
$status = (string)($latest['status'] ?? 'ready');
$signal = (int)($latest['signal'] ?? 0);
$signalId = (string)($latest['signal_id'] ?? '');
$createdUnix = (int)($latest['created_unix'] ?? 0);
$expireUnix = (int)($latest['expire_unix'] ?? 0);
$message = (string)($latest['message'] ?? 'ok');
append_access_log($accessLogFile, [date('Y-m-d H:i:s'), $profileId, $accountNumber, $symbol, $status, $signal, $message]);
respond_and_exit([
'status=' . $status,
'signal=' . $signal,
'message=' . $message,
'signal_id=' . $signalId,
'created_unix=' . $createdUnix,
'expire_unix=' . $expireUnix
], 200);
④save_settings.phpのソースコード
※ 保存前に、先頭の「<?php」を半角の「<」に直してください。末尾の終了タグ?>は不要です。
<?php
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
mb_internal_encoding('UTF-8');
date_default_timezone_set('Asia/Tokyo');
header('Content-Type: text/plain; charset=UTF-8');
function respond_and_exit(array $lines, int $httpCode = 200): void {
http_response_code($httpCode);
foreach ($lines as $line) {
echo $line . "\n";
}
exit;
}
function post_value(string $key, string $default = ''): string {
if (!isset($_POST[$key]) || is_array($_POST[$key])) {
return $default;
}
return trim((string)$_POST[$key]);
}
function safe_profile_id(string $profileId): bool {
return (bool)preg_match('/^[a-zA-Z0-9_\-]+$/', $profileId);
}
function normalize_provider(string $provider): string {
$provider = strtolower(trim($provider));
if ($provider === '2') {
return 'gemini';
}
if ($provider === '3' || $provider === 'xai') {
return 'xai';
}
if ($provider === '1' || $provider === 'openai') {
return 'chatgpt';
}
if ($provider === 'gemini') {
return 'gemini';
}
return ($provider === 'xai') ? 'xai' : 'chatgpt';
}
function normalize_url(string $url): string {
$url = trim($url);
if ($url === '') {
return '';
}
return filter_var($url, FILTER_VALIDATE_URL) ? $url : '';
}
function ensure_dir(string $dir): void {
if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) {
respond_and_exit(['status=error', 'message=failed_to_create_directory'], 500);
}
}
function save_json_file(string $path, array $data): void {
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if ($json === false) {
respond_and_exit(['status=error', 'message=json_encode_failed'], 500);
}
$fp = fopen($path, 'c+');
if ($fp === false) {
respond_and_exit(['status=error', 'message=failed_to_open_file'], 500);
}
if (!flock($fp, LOCK_EX)) {
fclose($fp);
respond_and_exit(['status=error', 'message=failed_to_lock_file'], 500);
}
ftruncate($fp, 0);
rewind($fp);
$written = fwrite($fp, $json);
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
if ($written === false) {
respond_and_exit(['status=error', 'message=failed_to_write_file'], 500);
}
}
function compute_settings_hash(array $data): string {
$hashSource = [
'profile_id' => (string)($data['profile_id'] ?? ''),
'account_number' => (string)($data['account_number'] ?? ''),
'account_type' => (string)($data['account_type'] ?? ''),
'symbol' => (string)($data['symbol'] ?? ''),
'timeframe' => (int)($data['timeframe'] ?? 0),
'provider' => (string)($data['provider'] ?? ''),
'rss_feed_url' => (string)($data['rss_feed_url'] ?? ''),
'prompt_text' => (string)($data['prompt_text'] ?? ''),
'trigger_mode' => (int)($data['trigger_mode'] ?? 0),
'minute_offset' => (int)($data['minute_offset'] ?? 0),
'hour_offset' => (int)($data['hour_offset'] ?? 0),
'magic' => (int)($data['magic'] ?? 0),
];
return md5(json_encode($hashSource, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
respond_and_exit(['status=error', 'message=invalid_method'], 405);
}
$profileId = post_value('profile_id');
$accountNumber = post_value('account_number');
$accountType = post_value('account_type');
$symbol = post_value('symbol');
$timeframe = post_value('timeframe');
$provider = normalize_provider(post_value('provider'));
$apiKey = post_value('api_key');
$rssFeedUrl = normalize_url(post_value('rss_feed_url'));
$promptText = post_value('prompt_text');
$triggerMode = post_value('trigger_mode');
$minuteOffset = post_value('minute_offset');
$hourOffset = post_value('hour_offset');
$magic = post_value('magic');
if ($profileId === '' || !safe_profile_id($profileId)) {
respond_and_exit(['status=error', 'message=invalid_profile_id'], 400);
}
if ($accountNumber === '' || !preg_match('/^\d{4,12}$/', $accountNumber)) {
respond_and_exit(['status=error', 'message=invalid_account_number'], 400);
}
if ($accountType === '' || !preg_match('/^[012]$/', $accountType)) {
respond_and_exit(['status=error', 'message=invalid_account_type'], 400);
}
if ($symbol === '') {
respond_and_exit(['status=error', 'message=missing_symbol'], 400);
}
if ($timeframe === '' || !preg_match('/^\d+$/', $timeframe)) {
respond_and_exit(['status=error', 'message=invalid_timeframe'], 400);
}
if ($rssFeedUrl === '') {
respond_and_exit(['status=error', 'message=invalid_rss_feed_url'], 400);
}
if ($promptText === '') {
respond_and_exit(['status=error', 'message=missing_prompt_text'], 400);
}
if ($triggerMode === '' || !preg_match('/^[1-5]$/', $triggerMode)) {
respond_and_exit(['status=error', 'message=invalid_trigger_mode'], 400);
}
if ($minuteOffset === '' || !preg_match('/^\d{1,2}$/', $minuteOffset)) {
respond_and_exit(['status=error', 'message=invalid_minute_offset'], 400);
}
if ($hourOffset === '' || !preg_match('/^\d{1,2}$/', $hourOffset)) {
respond_and_exit(['status=error', 'message=invalid_hour_offset'], 400);
}
$minuteOffsetInt = (int)$minuteOffset;
$hourOffsetInt = (int)$hourOffset;
if ($minuteOffsetInt < 0 || $minuteOffsetInt > 59) {
respond_and_exit(['status=error', 'message=minute_offset_out_of_range'], 400);
}
if ($hourOffsetInt < 0 || $hourOffsetInt > 23) {
respond_and_exit(['status=error', 'message=hour_offset_out_of_range'], 400);
}
$aiEnabled = ($apiKey !== '' && $rssFeedUrl !== '' && $promptText !== '');
$baseDir = __DIR__;
$settingsDir = $baseDir . '/settings';
$logsDir = $baseDir . '/logs';
$signalsDir = $baseDir . '/signals';
ensure_dir($settingsDir);
ensure_dir($logsDir);
ensure_dir($signalsDir);
$now = date('Y-m-d H:i:s');
$data = [
'profile_id' => $profileId,
'account_number' => $accountNumber,
'account_type' => $accountType,
'symbol' => $symbol,
'timeframe' => (int)$timeframe,
'provider' => $provider,
'api_key' => $apiKey,
'rss_feed_url' => $rssFeedUrl,
'prompt_text' => $promptText,
'ai_enabled' => $aiEnabled,
'trigger_mode' => (int)$triggerMode,
'minute_offset' => $minuteOffsetInt,
'hour_offset' => $hourOffsetInt,
'magic' => ($magic !== '' && preg_match('/^\d+$/', $magic)) ? (int)$magic : 0,
'updated_at' => $now,
'updated_unix' => time(),
];
$data['settings_hash'] = compute_settings_hash($data);
$settingsFile = $settingsDir . '/' . $profileId . '.json';
save_json_file($settingsFile, $data);
$logFile = $logsDir . '/settings_' . date('Ym') . '.csv';
$isNew = !file_exists($logFile);
$fp = fopen($logFile, 'a');
if ($fp !== false) {
if ($isNew) {
fputcsv($fp, ['saved_at', 'profile_id', 'account_number', 'symbol', 'provider', 'ai_enabled', 'settings_hash'], ',', '"', '\\');
}
fputcsv($fp, [$now, $profileId, $accountNumber, $symbol, $provider, $aiEnabled ? 1 : 0, $data['settings_hash']], ',', '"', '\\');
fclose($fp);
}
respond_and_exit([
'status=ok',
'message=settings_saved',
'profile_id=' . $profileId,
'provider=' . $provider,
'ai_enabled=' . ($aiEnabled ? '1' : '0'),
'settings_hash=' . $data['settings_hash'],
'saved_at=' . $now
], 200);
⑤update_signal.phpのソースコード
※ 保存前に、先頭の「<?php」を半角の「<」に直してください。末尾の終了タグ?>は不要です。
<?php
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
mb_internal_encoding('UTF-8');
date_default_timezone_set('Asia/Tokyo');
header('Content-Type: text/plain; charset=UTF-8');
$CHATGPT_ENDPOINT = 'https://api.openai.com/v1/responses';
$CHATGPT_MODEL = 'gpt-5.4-nano';
$GEMINI_ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta/models/%s:generateContent';
$GEMINI_MODEL = 'gemini-2.5-flash-lite';
$XAI_ENDPOINT = 'https://api.x.ai/v1/responses';
$XAI_MODEL = 'grok-4-1-fast-reasoning';
function respond_and_exit(array $lines, int $httpCode = 200): void {
http_response_code($httpCode);
foreach ($lines as $line) {
echo $line . "\n";
}
exit;
}
function post_value(string $key, string $default = ''): string {
if (!isset($_POST[$key]) || is_array($_POST[$key])) {
return $default;
}
return trim((string)$_POST[$key]);
}
function safe_profile_id(string $profileId): bool {
return (bool)preg_match('/^[a-zA-Z0-9_\-]+$/', $profileId);
}
function normalize_provider(string $provider): string {
$provider = strtolower(trim($provider));
if ($provider === '2') {
return 'gemini';
}
if ($provider === '3' || $provider === 'xai') {
return 'xai';
}
if ($provider === '1' || $provider === 'openai') {
return 'chatgpt';
}
if ($provider === 'gemini') {
return 'gemini';
}
return ($provider === 'xai') ? 'xai' : 'chatgpt';
}
function ensure_dir(string $dir): void {
if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) {
respond_and_exit(['status=error', 'signal=0', 'message=failed_to_create_directory', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 500);
}
}
function read_json_file(string $path): ?array {
if (!file_exists($path) || !is_readable($path)) {
return null;
}
$json = file_get_contents($path);
if ($json === false || $json === '') {
return null;
}
$data = json_decode($json, true);
return is_array($data) ? $data : null;
}
function save_json_file(string $path, array $data): bool {
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if ($json === false) {
return false;
}
return file_put_contents($path, $json, LOCK_EX) !== false;
}
function append_signal_log(string $logFile, array $row): void {
$dir = dirname($logFile);
if (!is_dir($dir)) {
@mkdir($dir, 0755, true);
}
$isNew = !file_exists($logFile);
$fp = fopen($logFile, 'a');
if ($fp === false) {
return;
}
if ($isNew) {
fputcsv($fp, ['updated_at', 'profile_id', 'account_number', 'symbol', 'provider', 'status', 'signal', 'signal_id', 'message', 'rss_chars'], ',', '"', '\\');
}
fputcsv($fp, $row, ',', '"', '\\');
fclose($fp);
}
function fetch_url(string $url, int $timeoutSec): array {
if (!function_exists('curl_init')) {
return ['ok' => false, 'body' => '', 'error' => 'curl_not_available'];
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => $timeoutSec,
CURLOPT_USERAGENT => 'ai-trading-demo-free/1.00',
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$body = curl_exec($ch);
$err = curl_error($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'ok' => ($body !== false && $code >= 200 && $code < 300),
'body' => ($body !== false ? (string)$body : ''),
'error' => ($body !== false ? '' : $err),
'code' => $code,
];
}
function call_chatgpt_responses(string $apiKey, string $instructions, string $input): array {
global $CHATGPT_ENDPOINT, $CHATGPT_MODEL;
$payload = [
'model' => $CHATGPT_MODEL,
'instructions' => $instructions,
'input' => $input,
'max_output_tokens' => 50,
];
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($json === false) {
return ['ok' => false, 'text' => '', 'error' => 'json_encode_failed'];
}
$ch = curl_init($CHATGPT_ENDPOINT);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey,
],
CURLOPT_POSTFIELDS => $json,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$raw = curl_exec($ch);
$err = curl_error($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($raw === false || $code < 200 || $code >= 300) {
return ['ok' => false, 'text' => '', 'error' => 'chatgpt_http_error:' . $code . ':' . $err];
}
$data = json_decode($raw, true);
if (!is_array($data)) {
return ['ok' => false, 'text' => '', 'error' => 'chatgpt_invalid_json'];
}
$text = '';
if (!empty($data['output_text']) && is_string($data['output_text'])) {
$text = trim($data['output_text']);
}
if ($text === '' && !empty($data['output']) && is_array($data['output'])) {
foreach ($data['output'] as $out) {
if (empty($out['content']) || !is_array($out['content'])) {
continue;
}
foreach ($out['content'] as $content) {
if (isset($content['text']) && is_string($content['text'])) {
$text .= $content['text'] . "\n";
}
}
}
$text = trim($text);
}
return ['ok' => ($text !== ''), 'text' => $text, 'error' => ($text !== '' ? '' : 'chatgpt_empty_output')];
}
function call_gemini_generate_content(string $apiKey, string $instructions, string $input): array {
global $GEMINI_ENDPOINT, $GEMINI_MODEL;
$payload = [
'system_instruction' => [
'parts' => [
['text' => $instructions],
],
],
'contents' => [
[
'parts' => [
['text' => $input],
],
],
],
'generationConfig' => [
'temperature' => 0.1,
'maxOutputTokens' => 200,
],
];
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($json === false) {
return ['ok' => false, 'text' => '', 'error' => 'json_encode_failed'];
}
$endpoint = sprintf($GEMINI_ENDPOINT, rawurlencode($GEMINI_MODEL));
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-goog-api-key: ' . $apiKey,
],
CURLOPT_POSTFIELDS => $json,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$raw = curl_exec($ch);
$err = curl_error($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($raw === false || $code < 200 || $code >= 300) {
return ['ok' => false, 'text' => '', 'error' => 'gemini_http_error:' . $code . ':' . $err];
}
$data = json_decode($raw, true);
if (!is_array($data)) {
return ['ok' => false, 'text' => '', 'error' => 'gemini_invalid_json'];
}
$text = '';
$finishReason = '';
if (!empty($data['candidates']) && is_array($data['candidates'])) {
foreach ($data['candidates'] as $candidate) {
if ($finishReason === '' && !empty($candidate['finishReason']) && is_string($candidate['finishReason'])) {
$finishReason = strtolower($candidate['finishReason']);
}
if (empty($candidate['content']['parts']) || !is_array($candidate['content']['parts'])) {
continue;
}
foreach ($candidate['content']['parts'] as $part) {
if (isset($part['text']) && is_string($part['text'])) {
$text .= $part['text'] . "\n";
}
}
}
}
$text = trim($text);
if ($text !== '') {
return ['ok' => true, 'text' => $text, 'error' => ''];
}
$blockReason = '';
if (!empty($data['promptFeedback']['blockReason']) && is_string($data['promptFeedback']['blockReason'])) {
$blockReason = strtolower($data['promptFeedback']['blockReason']);
}
if ($blockReason !== '') {
return ['ok' => false, 'text' => '', 'error' => 'gemini_blocked:' . $blockReason];
}
if ($finishReason !== '') {
return ['ok' => false, 'text' => '', 'error' => 'gemini_finish_reason:' . $finishReason];
}
if (empty($data['candidates']) || !is_array($data['candidates'])) {
return ['ok' => false, 'text' => '', 'error' => 'gemini_no_candidates'];
}
return ['ok' => false, 'text' => '', 'error' => 'gemini_empty_output'];
}
function call_xai_responses(string $apiKey, string $instructions, string $input): array {
global $XAI_ENDPOINT, $XAI_MODEL;
$payload = [
'model' => $XAI_MODEL,
'store' => false,
'input' => [
[
'role' => 'system',
'content' => $instructions,
],
[
'role' => 'user',
'content' => $input,
],
],
];
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($json === false) {
return ['ok' => false, 'text' => '', 'error' => 'json_encode_failed'];
}
$ch = curl_init($XAI_ENDPOINT);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 120,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey,
],
CURLOPT_POSTFIELDS => $json,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$raw = curl_exec($ch);
$err = curl_error($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($raw === false || $code < 200 || $code >= 300) {
return ['ok' => false, 'text' => '', 'error' => 'xai_http_error:' . $code . ':' . $err];
}
$data = json_decode($raw, true);
if (!is_array($data)) {
return ['ok' => false, 'text' => '', 'error' => 'xai_invalid_json'];
}
$text = '';
if (!empty($data['output_text']) && is_string($data['output_text'])) {
$text = trim($data['output_text']);
}
if ($text === '' && !empty($data['output']) && is_array($data['output'])) {
foreach ($data['output'] as $out) {
if (empty($out['content']) || !is_array($out['content'])) {
continue;
}
foreach ($out['content'] as $content) {
if (isset($content['text']) && is_string($content['text'])) {
$text .= $content['text'] . "\n";
}
}
}
$text = trim($text);
}
return ['ok' => ($text !== ''), 'text' => $text, 'error' => ($text !== '' ? '' : 'xai_empty_output')];
}
function call_ai_response(string $provider, string $apiKey, string $instructions, string $input): array {
if ($provider === 'gemini') {
return call_gemini_generate_content($apiKey, $instructions, $input);
}
if ($provider === 'xai') {
return call_xai_responses($apiKey, $instructions, $input);
}
return call_chatgpt_responses($apiKey, $instructions, $input);
}
function default_expire_seconds(int $triggerMode): int {
switch ($triggerMode) {
case 1: return 90;
case 2: return 9 * 60;
case 3: return 50 * 60;
case 4: return 230 * 60;
case 5: return 23 * 60 * 60;
default: return 15 * 60;
}
}
function save_latest_signal(string $latestFile, string $status, int $signal, string $signalId, int $createdUnix, int $expireUnix, string $symbol, string $message, string $provider, int $rssChars): bool {
return save_json_file($latestFile, [
'status' => $status,
'signal' => $signal,
'signal_id' => $signalId,
'created_unix' => $createdUnix,
'expire_unix' => $expireUnix,
'symbol' => $symbol,
'message' => $message,
'message_ja' => $message,
'provider' => $provider,
'rss_chars' => $rssChars,
'updated_at' => date('Y-m-d H:i:s'),
]);
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
respond_and_exit(['status=error', 'signal=0', 'message=invalid_method', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 405);
}
$profileId = post_value('profile_id');
$accountNumber = post_value('account_number');
$symbol = post_value('symbol');
$timeframe = post_value('timeframe');
if ($profileId === '' || !safe_profile_id($profileId)) {
respond_and_exit(['status=error', 'signal=0', 'message=invalid_profile_id', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 400);
}
if ($accountNumber === '' || !preg_match('/^\d{4,12}$/', $accountNumber)) {
respond_and_exit(['status=error', 'signal=0', 'message=invalid_account_number', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 400);
}
if ($symbol === '') {
respond_and_exit(['status=error', 'signal=0', 'message=missing_symbol', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 400);
}
$baseDir = __DIR__;
$settingsDir = $baseDir . '/settings';
$signalsDir = $baseDir . '/signals';
$logsDir = $baseDir . '/logs';
ensure_dir($settingsDir);
ensure_dir($signalsDir);
ensure_dir($logsDir);
$settingsFile = $settingsDir . '/' . $profileId . '.json';
$latestFile = $signalsDir . '/latest_' . $profileId . '.json';
$processingFile = $signalsDir . '/processing_' . $profileId . '.lock';
$signalLogFile = $logsDir . '/signals_' . date('Ym') . '.csv';
$settings = read_json_file($settingsFile);
if ($settings === null) {
respond_and_exit(['status=error', 'signal=0', 'message=settings_not_found', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 404);
}
$latest = read_json_file($latestFile);
if (
is_array($latest)
&& (string)($latest['status'] ?? '') === 'ready'
&& (int)($latest['signal'] ?? 0) === 1
&& (int)($latest['expire_unix'] ?? 0) > time()
) {
respond_and_exit([
'status=ready',
'signal=' . (int)$latest['signal'],
'message=' . (string)($latest['message'] ?? 'ok'),
'signal_id=' . (string)($latest['signal_id'] ?? ''),
'created_unix=' . (int)($latest['created_unix'] ?? 0),
'expire_unix=' . (int)($latest['expire_unix'] ?? 0)
], 200);
}
$provider = normalize_provider((string)($settings['provider'] ?? '1'));
$apiKey = trim((string)($settings['api_key'] ?? ''));
$rssFeedUrl = trim((string)($settings['rss_feed_url'] ?? ''));
$promptText = trim((string)($settings['prompt_text'] ?? ''));
$aiEnabled = !empty($settings['ai_enabled']);
if (!$aiEnabled || $apiKey === '' || $rssFeedUrl === '' || $promptText === '') {
$now = time();
save_latest_signal($latestFile, 'stale', 0, '', $now, $now, $symbol, 'ai_disabled', $provider, 0);
respond_and_exit(['status=stale', 'signal=0', 'message=ai_disabled', 'signal_id=', 'created_unix=' . $now, 'expire_unix=' . $now], 200);
}
if (file_exists($processingFile)) {
$latest = read_json_file($latestFile);
respond_and_exit([
'status=processing',
'signal=' . (int)($latest['signal'] ?? 0),
'message=processing_now',
'signal_id=' . (string)($latest['signal_id'] ?? ''),
'created_unix=' . (int)($latest['created_unix'] ?? 0),
'expire_unix=' . (int)($latest['expire_unix'] ?? 0)
], 200);
}
$lockFp = fopen($processingFile, 'c+');
if ($lockFp === false || !flock($lockFp, LOCK_EX | LOCK_NB)) {
if (is_resource($lockFp)) {
fclose($lockFp);
}
respond_and_exit(['status=processing', 'signal=0', 'message=lock_busy', 'signal_id=', 'created_unix=0', 'expire_unix=0'], 200);
}
register_shutdown_function(function () use ($lockFp, $processingFile) {
@flock($lockFp, LOCK_UN);
@fclose($lockFp);
if (file_exists($processingFile)) {
@unlink($processingFile);
}
});
$rss = fetch_url($rssFeedUrl, 20);
if (!$rss['ok']) {
$now = time();
save_latest_signal($latestFile, 'error', 0, '', $now, $now, $symbol, 'rss_fetch_failed', $provider, 0);
respond_and_exit(['status=error', 'signal=0', 'message=rss_fetch_failed', 'signal_id=', 'created_unix=' . $now, 'expire_unix=' . $now], 200);
}
$rssBody = trim((string)$rss['body']);
if ($rssBody === '') {
$now = time();
save_latest_signal($latestFile, 'error', 0, '', $now, $now, $symbol, 'rss_empty', $provider, 0);
respond_and_exit(['status=error', 'signal=0', 'message=rss_empty', 'signal_id=', 'created_unix=' . $now, 'expire_unix=' . $now], 200);
}
$rssBody = mb_substr($rssBody, 0, 6000, 'UTF-8');
$aiInput = implode("\n", [
'symbol: ' . $symbol,
'timeframe: ' . $timeframe,
'rss_feed_url: ' . $rssFeedUrl,
'',
'rss_content:',
$rssBody,
]);
$ai = call_ai_response($provider, $apiKey, $promptText, $aiInput);
if (!$ai['ok']) {
$now = time();
$message = (string)($ai['error'] ?? 'ai_request_failed');
save_latest_signal($latestFile, 'error', 0, '', $now, $now, $symbol, $message, $provider, strlen($rssBody));
append_signal_log($signalLogFile, [date('Y-m-d H:i:s'), $profileId, $accountNumber, $symbol, $provider, 'error', 0, '', $message, strlen($rssBody)]);
respond_and_exit(['status=error', 'signal=0', 'message=' . $message, 'signal_id=', 'created_unix=' . $now, 'expire_unix=' . $now], 200);
}
$createdUnix = time();
$expireUnix = $createdUnix + default_expire_seconds((int)($settings['trigger_mode'] ?? 2));
$signalId = date('YmdHis') . '_' . substr(md5($profileId . '|' . $symbol . '|' . $createdUnix), 0, 8);
$message = 'demo_force_long';
save_latest_signal($latestFile, 'ready', 1, $signalId, $createdUnix, $expireUnix, $symbol, $message, $provider, strlen($rssBody));
append_signal_log($signalLogFile, [date('Y-m-d H:i:s'), $profileId, $accountNumber, $symbol, $provider, 'ready', 1, $signalId, $message, strlen($rssBody)]);
respond_and_exit([
'status=ready',
'signal=1',
'message=demo_force_long',
'signal_id=' . $signalId,
'created_unix=' . $createdUnix,
'expire_unix=' . $expireUnix
], 200);
設定手順①AIモデルを変更する場合は、update_signal.phpで設定する。
ChatGPTはGPT系モデルを使用します。Nanoモデルは軽量で、動作が比較的速く、トークン消費量も少ないのが特徴です。
Gemini 2.5 Flash-Liteも軽量モデルにあたり、処理負荷が低く、トークン使用量を抑えやすいモデルです。
一方、Grokは私が試した範囲では、GPTやGeminiと比べてトークン消費がやや多く、APIレスポンスもやや不安定に感じました。今後改善される可能性はありますが、現時点ではGrokはお試し用途としてご利用ください。
なお、API接続先URLや仕様は変更される場合があるため、各プラットフォームの最新仕様をご確認ください。
設定手順②PCにファイルを保存(拡張子を.phpで保存)
設定手順③レンタルサーバーにphpファイルをアップロード
レンタルサーバーはPHP対応のWeb用のレンタルサーバーを借りてください。Windows VPSとは別です。迷ったら、お試し期間付きレンタルサーバーで試してください。
設定手順④MT4 MT5にウェブサイトのURLを登録
MT4 MT5のツール→オプションから、ウェブリクエストを許可して、phpの設置したドメインを登録(https~入力 ×http:// 必ずhttps://にしてください)
設定手順⑤MT4 MT5にAPIキーを登録
有料版のMT4・MT5トレードシステムは、セキュリティ上の理由から、プロパティ欄にAPIキーを直接入力する仕様にはしていません。そのため、APIキーはPHPファイルを利用し、サーバー側で保管する方式を採用しています。有料版をご検討の方は、APIキーはサーバー側で保存する設定になる点をあらかじめご了承ください。
設定手順⑥MT4 MT5でEA稼働テスト
※MT4・MT5の仕様上、プロパティ欄に入力できる文字数には制限があり、長文のプロンプトを入力すると途中で切れてしまう場合があります。そのため、有料版ではプロンプト指示文をMT4・MT5側に直接入力するのではなく、PHPファイルで管理し、サーバー側に保存する方式を採用しています。これにより、長文のプロンプトにも対応可能です。したがって、有料版では各種プロンプトは原則としてサーバー側で保存・管理する仕様となっておりますので、あらかじめご了承ください。
無料のデモ版は、あくまでAIとの通信が正常に行えるかを確認するためのテスト版です。
売買ロジックや収益性を保証するものではなく、実運用を前提とした完成版プログラムではありません。
そのため、本デモ版を参考にして独自にシステム開発を行う場合は、API利用時に発生するトークン使用料や通信回数、運用コストなどを十分に確認したうえで、自己責任にて開発・検証を進めてください。
なお、本デモ版に関して、投資助言に該当する行為は一切行っておりません。
また、無料版に関する技術的なサポートは行っておりません。
バグや不具合等のご連絡につきましては、ゴゴジャンの管理ページにログインのうえ、開発者まさやんまでお問い合わせください。
【免責事項】
・本スクリプトに含まれるデータ、表示内容、判定結果、ならびにトレード結果等について、その正確性、完全性、有用性、収益性を保証するものではありません。
・本スクリプトを利用したことにより生じた損失、不利益、トラブル等について、開発者まさやんは一切の責任を負いません。
・本スクリプトの利用、設定変更、改変、検証、運用は、すべて利用者自身の判断と責任において行ってください。
・本スクリプトの著作権は、まさやんに帰属します。個人利用の範囲においては、複製および改変を行うことができます。ただし、無断での再配布、転載、販売、譲渡、公開等は禁止します。
ใช่ไหม?