diff --git a/Experts/MyProject/HIT-EA.mq5 b/Experts/MyProject/HIT-EA.mq5 deleted file mode 100644 index 2a9b77f..0000000 --- a/Experts/MyProject/HIT-EA.mq5 +++ /dev/null @@ -1,935 +0,0 @@ -//+------------------------------------------------------------------+ -//| HIT_EA.mq5 | -//| Copyright 2026, nanpin-martin.com | -//| https://www.nanpin-martin.com | -//+------------------------------------------------------------------+ -#property copyright "Copyright 2026, nanpin-martin.com" -#property link "https://nanpin-martin.com/" -#property version "1.00" - -//+------------------------------------------------------------------+ -//| バッチファイル実行用の設定 -//+------------------------------------------------------------------+ - -#import "Shell32.dll" -int ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd); -#import - -// バッチファイルのパス -string get_trend_reply_bat = "C:\\ea_py\\get_trend_reply.bat"; // H4: trend -string get_entry_reply_bat = "C:\\ea_py\\get_entry_reply.bat"; // H1: entry - -// プロセス完了ファイルの設定 -string done_trend_file = "process_done_trend.txt"; -string done_entry_file = "process_done_entry.txt"; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| インプットの設定 -//+------------------------------------------------------------------+ - -//--- 入力パラメータ -input double lot_size = 0.01; // ロット数 -input double spread_limit = 20; // 許容スプレッド(point) -input int magic_number = 10001; // マジックナンバー -input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) -ulong slippage = 10; // スリッページ - -//--- 主要な定数・設定 -#define POSITION_LIMIT 48 -#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) -#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) -#define HISTORY_BARS 72 -// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) -#define TARGET_SIZE 13 -#define DEFAULT_TARGET_PRICE 0.0 - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| EAの状態をまとめる構造体 -//+------------------------------------------------------------------+ - -struct EAState - { - int trend_state; // ★追加:トレンドを格納する変数 - int res_chk; // GPTの回答が正しく取得できたか - double en_price[5]; // エントリー基準価格 - double tp_price[5]; // 利益確定価格 - double sl_price[5]; // ロスカット基準価格 - bool load_trend_flg; // ★変更:トレンドを更新するタイミングか - bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか - int chk_cnt; // エントリー判定の試行回数 - datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 - datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 - datetime last_chk; - }; - -EAState g_ea; // EA全体の状態をここで管理 - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| 必要な関数プロトタイプ宣言 -//| プロセス完了ファイル("process_done.txt")関連 -//+------------------------------------------------------------------+ - -// "process_done.txt"を作成する関数 -void CreateDoneFile(const string name); - -// "process_done.txt"を削除する関数 -void DeleteDoneFile(const string name); - -// "process_done.txt"の存在を確認し、存在する場合はtrueを返す関数 -bool CheckDoneFile(const string name); - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| 必要な関数プロトタイプ宣言 -//| OHLCデータの取得からターゲット価格の取得まで -//+------------------------------------------------------------------+ - -//| 最新のOHLCデータを取得する関数 -bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count); - -//| "ohlc.csv"を出力する関数 -void RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[]); - -//| バッチファイル(Pythonスクリプト)を実行する関数 -void ExecuteBatchTrend(); -void ExecuteBatchEntry(); - -// | "ohlc.csv"を出力後、バッチファイルの実行する関数 -bool RecordOHLCAndExecuteBatch_Trend(EAState &state); -bool RecordOHLCAndExecuteBatch_Entry(EAState &state); - -//| ターゲット価格を取得する関数 -bool GetTargetPrices(EAState &state); - -//| "target_prices.txt"を読み込む関数 -void LoadTargetPrices(double &target_prices[]); - -//| "xxx"を読み込む関数 -bool GetTrendState(EAState &state); -void LoadTrendState(int &trend_state); - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| 必要な関数プロトタイプ宣言 -//| エントリー注文関連 -//+------------------------------------------------------------------+ - -//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit) -bool SendOrder(int orderType, double price, double tp, double sl); - -//| 注文の執行ポリシーを取得する関数 -ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol); - -//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与) -string RequestComment(int orderType); - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| 必要な関数プロトタイプ宣言 -//| 時間制限処理関連 -//+------------------------------------------------------------------+ - -//| 時間が経過したエントリー注文をキャンセルする関数 -bool CancelExpiredOrders(); - -//| 時間が経過したポジションをクローズする関数 -bool CloseExpiredPositions(); - -//| 指定された時間に最も近いバーのインデックスを返す関数 -// int GetBarIndexByTime(datetime time, int default_index = 0); -int GetBarShiftByTime(datetime time, ENUM_TIMEFRAMES tf, int default_shift = -1); - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| 起動時の処理 -//+------------------------------------------------------------------+ -void OnInit() -// プロセス完了ファイル("process_done.txt)"を作成 - { - CreateDoneFile(done_trend_file); - CreateDoneFile(done_entry_file); - } - -//+------------------------------------------------------------------+ -//| ティック毎の処理 -//+------------------------------------------------------------------+ -void OnTick() - { - static int init_order_flg = initial_order; - -// ───────────────────────────────────────────── -// (1) H4更新検知:トレンド用 -// ───────────────────────────────────────────── - int current_bars_H4 = iBars(NULL, PERIOD_H4); - static int pre_bars_H4 = current_bars_H4; - int bars_H4_change = current_bars_H4 - pre_bars_H4; - - if(bars_H4_change > 0 || init_order_flg == 1) - { - RecordOHLCAndExecuteBatch_Trend(g_ea); - } - pre_bars_H4 = current_bars_H4; - -// trend done待ち(なければ以降をスキップ) - if(!CheckDoneFile(done_trend_file)) - return; - -// トレンド取得 - GetTrendState(g_ea); - -// ───────────────────────────────────────────── -// (2) H1更新検知:エントリー用 -// ───────────────────────────────────────────── - static bool bars_H1_check = false; - - int current_bars_H1 = iBars(NULL, PERIOD_H1); - static int pre_bars_H1 = current_bars_H1; - int bars_H1_change = current_bars_H1 - pre_bars_H1; - - if(bars_H1_change > 0 || init_order_flg == 1) - { - init_order_flg = 0; - bars_H1_check = true; - g_ea.chk_cnt = 0; - g_ea.last_chk = 0; - RecordOHLCAndExecuteBatch_Entry(g_ea); - } - pre_bars_H1 = current_bars_H1; - -// ── ティック情報・コメント表示 ── - MqlTick last_tick; - if(!SymbolInfoTick(_Symbol, last_tick)) - { - return; - } - double Ask = last_tick.ask; - double Bid = last_tick.bid; - double Spread = MathRound((Ask - Bid) / Point()) * Point(); - - int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - - string message = StringFormat( - " \nAsk: %." + IntegerToString(digits) + "f" - "\nBid: %." + IntegerToString(digits) + "f" - "\nSpread: %." + IntegerToString(digits) + "f" - "\n\nLast Trend Update:\n %s" - "\n\nTrend State: %.0f" // ★追加 - "\n\nLast Target Update:\n %s" - "\n\n[T1 Buy Stop ] en: %." + IntegerToString(digits) + "f tp: %." + IntegerToString(digits) + "f sl: %." + IntegerToString(digits) + "f" - "\n[T2 Buy Limit] en: %." + IntegerToString(digits) + "f tp: %." + IntegerToString(digits) + "f sl: %." + IntegerToString(digits) + "f" - "\n[T3 Sell Stop] en: %." + IntegerToString(digits) + "f tp: %." + IntegerToString(digits) + "f sl: %." + IntegerToString(digits) + "f" - "\n[T4 SellLimit] en: %." + IntegerToString(digits) + "f tp: %." + IntegerToString(digits) + "f sl: %." + IntegerToString(digits) + "f\n", - Ask, Bid, Spread, - TimeToString(g_ea.last_trend_update, TIME_DATE | TIME_MINUTES), - g_ea.trend_state, // ★追加 - TimeToString(g_ea.last_target_update, TIME_DATE | TIME_MINUTES), - g_ea.en_price[1], g_ea.tp_price[1], g_ea.sl_price[1], - g_ea.en_price[2], g_ea.tp_price[2], g_ea.sl_price[2], - g_ea.en_price[3], g_ea.tp_price[3], g_ea.sl_price[3], - g_ea.en_price[4], g_ea.tp_price[4], g_ea.sl_price[4] - ); - Comment(message); - -// スプレッド制限 - if(Spread > spread_limit * Point()) - return; - -// entry done待ち - if(!CheckDoneFile(done_entry_file)) - return; - -// ターゲット取得 - GetTargetPrices(g_ea); - -// 期限切れ注文キャンセル・ポジクローズ - if(OrdersTotal() > 0) - CancelExpiredOrders(); - if(PositionsTotal() > 0) - CloseExpiredPositions(); - -// ───────────────────────────────────────────── -// エントリー条件判定&注文送信(H1更新タイミングで1回) -// ───────────────────────────────────────────── -// if(bars_H4_check && TimeCurrent() - g_ea.last_chk >= 60) -// { -// bars_H4_check = false; -// g_ea.last_chk = TimeCurrent(); - if(bars_H1_check && TimeCurrent() - g_ea.last_chk >= 60) - { - bars_H1_check = false; - g_ea.last_chk = TimeCurrent(); - - if(g_ea.res_chk == 0) - { - Print("[Entry Skip] res_chk=0 (range). No entry orders are sent."); - g_ea.chk_cnt = 0; - } - else - { - int used = OrdersTotal() + PositionsTotal(); - if(used >= POSITION_LIMIT) - { - Print("Position limit exceeded: used=", used, " limit=", POSITION_LIMIT); - } - else - { - int sent_success = 0; // ★ 送信成功数(= SendOrder true の回数) - - // ★ 1..4 を順に判定して、条件OKなら送る - for(int t=1; t<=4; t++) - { - // ポジション上限チェック(途中で到達したら打ち切り) - used = OrdersTotal() + PositionsTotal(); - if(used >= POSITION_LIMIT) - { - Print("Position limit reached while sending. used=", used, " limit=", POSITION_LIMIT); - break; - } - - string entry_type; - double cur_price; // ログ用(Ask/Bid) - - if(t==1) - { - entry_type="Buy Stop"; - cur_price=Ask; - } - else - if(t==2) - { - entry_type="Buy Limit"; - cur_price=Ask; - } - else - if(t==3) - { - entry_type="Sell Stop"; - cur_price=Bid; - } - else - { - entry_type="Sell Limit"; // t==4 - cur_price=Bid; - } - - double en = g_ea.en_price[t]; - double tp = g_ea.tp_price[t]; - double sl = g_ea.sl_price[t]; - - bool ok=false; - - if(t==1) - ok = (Ask < en && tp > en && sl < en); // buy-stop - else - if(t==2) - ok = (Ask > en && tp > en && sl < en); // buy-limit - else - if(t==3) - ok = (Bid > en && tp < en && sl > en); // sell-stop - else - ok = (Bid < en && tp < en && sl > en); // sell-limit - - if(ok) - { - Print("[", entry_type, " Order Try at ", cur_price, "] en=", en, " tp=", tp, " sl=", sl); - - if(SendOrder(t, en, tp, sl)) - { - sent_success++; - Print("[", entry_type, " Order Sent] ticket ok. en=", en, " tp=", tp, " sl=", sl); - } - else - { - Print("[", entry_type, " Order Failed] en=", en, " tp=", tp, " sl=", sl); - } - } - else - { - Print("[No ", entry_type, "] cur=", cur_price, " en=", en, " tp=", tp, " sl=", sl); - } - } - - if(sent_success > 0) - { - g_ea.chk_cnt = 0; // 1件でも通ったら終了 - } - else - { - g_ea.chk_cnt += 1; - Print("[Retry] no order sent. chk_cnt=", g_ea.chk_cnt, "/10"); - - if(g_ea.chk_cnt < 10) - { - // bars_H4_check = true; // 60秒後に再度このブロックに入る - bars_H1_check = true; // ★ GPT-EAの bars_H4_check → bars_H1_check に変更 - } - else - { - Print("[Retry End] reached max tries. reset chk_cnt."); - g_ea.chk_cnt = 0; - } - } - } - } - } - } - -//+------------------------------------------------------------------+ -//| "process_done.txt"を作成する関数 -//+------------------------------------------------------------------+ -void CreateDoneFile(const string name) - { - if(!FileIsExist(name)) - { - int h = FileOpen(name, FILE_WRITE|FILE_TXT); - if(h != INVALID_HANDLE) - FileClose(h); - else - Print("Failed to create file: ", name, " err=", GetLastError()); - } - } - -//+------------------------------------------------------------------+ -//| "process_done.txt"を削除する関数 -//+------------------------------------------------------------------+ -void DeleteDoneFile(const string name) - { - if(FileIsExist(name)) - FileDelete(name); - } - - -//+------------------------------------------------------------------+ -//| "process_done.txt"の存在を確認し、存在する場合はtrueを返す関数 -//+------------------------------------------------------------------+ -bool CheckDoneFile(const string name) - { - return FileIsExist(name); - } - -//+------------------------------------------------------------------+ -//| 最新のOHLCデータを取得する関数 -//+------------------------------------------------------------------+ -bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count) - { - ArrayResize(times, 0); - ArrayResize(open_prices, 0); - ArrayResize(high_prices, 0); - ArrayResize(low_prices, 0); - ArrayResize(close_prices, 0); - - if(bars_count < 2) - return false; - -// 時間データを取得 - if(CopyTime(_Symbol, tf, 0, bars_count, times) <= 0) - { - Print(__FUNCTION__, ": Failed to copy time data (bars=", bars_count, ") err=", GetLastError()); - return false; - } - -// OHLCデータを取得 - MqlRates rates[]; - if(CopyRates(_Symbol, tf, 0, bars_count, rates) <= 0) - { - Print(__FUNCTION__, ": Failed to copy rates data (bars=", bars_count, ") err=", GetLastError()); - return false; - } - -// 必要なデータを配列に格納 - for(int i = 0; i < ArraySize(rates); i++) - { - ArrayResize(open_prices, i + 1); - ArrayResize(high_prices, i + 1); - ArrayResize(low_prices, i + 1); - ArrayResize(close_prices, i + 1); - - open_prices[i] = rates[i].open; - high_prices[i] = rates[i].high; - low_prices[i] = rates[i].low; - close_prices[i] = rates[i].close; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| "ohlc.csv"を出力する関数 -//+------------------------------------------------------------------+ -void RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[]) - { -// 出力先ファイル名 -// string filename = "ohlc.csv"; - -// ファイルを "書き込みモード" でオープン (テキスト/ANSI) - int fileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI); - -// ファイルが開けたかチェック - if(fileHandle == INVALID_HANDLE) - { - Print(__FUNCTION__, " : Failed to open file: ", GetLastError()); - return; - } - -// 配列サイズを取得 (times, open, high, low, close の要素数は同じ前提) - int size = ArraySize(times); - -// ヘッダー行を追加 - FileWrite(fileHandle, "Time,Open,High,Low,Close"); - -// 小数点の桁数を取得 - int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - -// フォーマット文字列を動的に生成 - string formatString = StringFormat("%%s,%%.%df,%%.%df,%%.%df,%%.%df", digits, digits, digits, digits); - -// 1行ずつ "時刻, Open, High, Low, Close" の形式で書き込み - for(int i = 0; i < size; i++) - { - // 時間をフォーマット - string timeStr = TimeToString(times[i], TIME_DATE | TIME_MINUTES); - - // 1行分の文字列を生成 (小数点以下の桁数を `digits` に調整) - string line = StringFormat(formatString, - timeStr, open_prices[i], high_prices[i], low_prices[i], close_prices[i]); - - // ファイルに書き込み (改行付き) - FileWrite(fileHandle, line); - } - -// 書き込み終了後、ファイルを閉じる - FileClose(fileHandle); - } - -//+------------------------------------------------------------------+ -//| バッチファイル(Pythonスクリプト)を実行する関数 -//+------------------------------------------------------------------+ -void ExecuteBatchTrend() - { - if(CheckDoneFile(done_trend_file)) - { - DeleteDoneFile(done_trend_file); - ShellExecuteW(0, "open", get_trend_reply_bat, "", "", 1); - } - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -void ExecuteBatchEntry() - { - if(CheckDoneFile(done_entry_file)) - { - DeleteDoneFile(done_entry_file); - ShellExecuteW(0, "open", get_entry_reply_bat, "", "", 1); - } - } - - -//+------------------------------------------------------------------+ -//| "ohlc.csv"を出力後、バッチファイルの実行する関数 -//+------------------------------------------------------------------+ -bool RecordOHLCAndExecuteBatch_Trend(EAState &state) - { - datetime times[]; - double open_prices[], high_prices[], low_prices[], close_prices[]; - - if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, - PERIOD_H4, HISTORY_BARS)) - { Print("GetLatestOHLC(H4) failed."); return false; } - - RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices); - ExecuteBatchTrend(); - - state.load_trend_flg = true; - return true; - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -bool RecordOHLCAndExecuteBatch_Entry(EAState &state) - { - datetime times[]; - double open_prices[], high_prices[], low_prices[], close_prices[]; - - if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, - PERIOD_H1, HISTORY_BARS)) - { Print("GetLatestOHLC(H1) failed."); return false; } - - RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices); - ExecuteBatchEntry(); - - state.load_target_flg = true; - return true; - } - - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -bool GetTrendState(EAState &state) - { - bool ProcessDone = CheckDoneFile(done_trend_file); - if(ProcessDone && g_ea.load_trend_flg) - { - int trend_state; - LoadTrendState(trend_state); - state.trend_state = trend_state; - Print("trend_state: ", state.trend_state); - g_ea.load_trend_flg = false; - g_ea.last_trend_update = TimeLocal(); - } - return true; - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -void LoadTrendState(int &trend_state) - { - string filename = "trend_state.txt"; - if(FileIsExist(filename)) - { - int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); - if(filehandle != INVALID_HANDLE) - { - string line = FileReadString(filehandle); - trend_state = (int)StringToInteger(line); - FileClose(filehandle); - } - else - { Print("Failed to open trend_state.txt"); trend_state = 0; } - } - else - { Print("trend_state.txt not found"); trend_state = 0; } - } - -//+------------------------------------------------------------------+ -//| ターゲット価格を取得する関数 -//+------------------------------------------------------------------+ -bool GetTargetPrices(EAState &state) - { - bool ProcessDone = CheckDoneFile(done_entry_file); // ←変更 - if(ProcessDone && g_ea.load_target_flg) // ←変更 - { - double target_prices[]; - LoadTargetPrices(target_prices); - - int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - - // 先頭は res_chk - state.res_chk = (int)target_prices[0]; - - // ★ 1..4 の (en,tp,sl) を読む:合計12個 - for(int t=1; t<=4; t++) - { - int base = 1 + (t-1)*3; // 1,4,7,10 - state.en_price[t] = NormalizeDouble(target_prices[base + 0], digits); - state.tp_price[t] = NormalizeDouble(target_prices[base + 1], digits); - state.sl_price[t] = NormalizeDouble(target_prices[base + 2], digits); - } - - // ログ(任意) - Print("target_prices: res=", state.res_chk, - " | T1 en=", state.en_price[1], " tp=", state.tp_price[1], " sl=", state.sl_price[1], - " | T2 en=", state.en_price[2], " tp=", state.tp_price[2], " sl=", state.sl_price[2], - " | T3 en=", state.en_price[3], " tp=", state.tp_price[3], " sl=", state.sl_price[3], - " | T4 en=", state.en_price[4], " tp=", state.tp_price[4], " sl=", state.sl_price[4]); - - g_ea.load_target_flg = false; // ←変更 - g_ea.last_target_update = TimeLocal(); // ←変更 - } - return true; - } -//+------------------------------------------------------------------+ -//+------------------------------------------------------------------+ -//| "target_prices.txt"を読み込む関数 -//+------------------------------------------------------------------+ -void LoadTargetPrices(double &target_prices[]) - { - ArrayResize(target_prices, TARGET_SIZE); - string filename = "target_prices.txt"; - - if(FileIsExist(filename)) - { - int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); - if(filehandle != INVALID_HANDLE) - { - int i = 0; - while(!FileIsEnding(filehandle) && i < TARGET_SIZE) - { - string line = FileReadString(filehandle); - target_prices[i] = StringToDouble(line); - i++; - } - FileClose(filehandle); - } - else - { - Print("Failed to open file. Error code: ", GetLastError()); - for(int i = 0; i < TARGET_SIZE; i++) - { - target_prices[i] = DEFAULT_TARGET_PRICE; - } - } - } - else - { - Print("File does not exist: ", filename); - for(int i = 0; i < TARGET_SIZE; i++) - { - target_prices[i] = DEFAULT_TARGET_PRICE; - } - } - } - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit) -//+------------------------------------------------------------------+ -bool SendOrder(int orderType, double price, double tp, double sl) - { - MqlTradeRequest request = {}; - MqlTradeResult result = {}; - - request.magic = magic_number; - request.symbol = _Symbol; - request.volume = lot_size; - request.deviation = slippage; - request.type_filling = GetOrderFillingPolicy(_Symbol); - request.price = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.comment = RequestComment(orderType); - - string orderTypeStr = ""; - -// 指値または逆指値注文 - request.action = TRADE_ACTION_PENDING; - - switch(orderType) - { - case 1: // 順張り買い (buy-stop) - request.type = ORDER_TYPE_BUY_STOP; - orderTypeStr = "buy-stop"; - break; - - case 2: // 逆張り買い (buy-limit) - request.type = ORDER_TYPE_BUY_LIMIT; - orderTypeStr = "buy-limit"; - break; - - case 3: // 順張り売り (sell-stop) - request.type = ORDER_TYPE_SELL_STOP; - orderTypeStr = "sell-stop"; - break; - - case 4: // 逆張り売り (sell-limit) - request.type = ORDER_TYPE_SELL_LIMIT; - orderTypeStr = "sell-limit"; - break; - - default: // 不正なorderType - Print(__FUNCTION__, ": invalid orderType=", orderType); - return false; - } - -// 注文送信 - if(!OrderSend(request, result)) - { - Print(__FUNCTION__, ": OrderSend failed. err=", GetLastError()); - return false; - } - -// 注文成功確認 - if(result.retcode == TRADE_RETCODE_PLACED || - result.retcode == TRADE_RETCODE_DONE || - result.retcode == TRADE_RETCODE_DONE_PARTIAL) - { - Print(request.comment + " - " + orderTypeStr + " order success (ticket=", result.order, ")"); - return true; - } - else - { - Print(__FUNCTION__, ": OrderSend retcode=", result.retcode); - return false; - } - } - -//+------------------------------------------------------------------+ -//| 注文の執行ポリシーを取得する関数 -//+------------------------------------------------------------------+ -ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol) - { - long fill_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); - if((fill_mode & SYMBOL_FILLING_IOC) != 0) - return ORDER_FILLING_IOC; - if((fill_mode & SYMBOL_FILLING_FOK) != 0) - return ORDER_FILLING_FOK; - return ORDER_FILLING_RETURN; - } - -//+------------------------------------------------------------------+ -//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与) -//+------------------------------------------------------------------+ -string RequestComment(int orderType) - { - datetime localTime = TimeLocal(); - string timeStr = TimeToString(localTime, TIME_DATE|TIME_MINUTES); - -// 先頭に 1,2,3,4 を付ける(例: "1 | PC Time: 2026.02.14 19:05") - return IntegerToString(orderType) + " | PC Time: " + timeStr; - } - -//+------------------------------------------------------------------+ -//| 時間が経過したエントリー注文をキャンセルする関数 -//+------------------------------------------------------------------+ -bool CancelExpiredOrders() - { - bool result = false; - -// 未決済注文の数を取得 - for(int i = OrdersTotal() - 1; i >= 0; i--) - { - ulong ticket = OrderGetTicket(i); - if(OrderSelect(ticket)) // 注文を選択 - { - if(OrderGetString(ORDER_SYMBOL) != _Symbol) - continue; - if(OrderGetInteger(ORDER_MAGIC) != magic_number) - continue; - - datetime open_time = (datetime)OrderGetInteger(ORDER_TIME_SETUP); - - int open_shift = GetBarShiftByTime(open_time, PERIOD_H1, -1); // H1 - if(open_shift < 0) - continue; // 時刻解決できないなら触らない - - if(open_shift >= ENTRY_H1_LIMIT) // H1本数(=1本 ≒ 1時間) - { - MqlTradeRequest request = {}; - MqlTradeResult trade_result = {}; - request.action=TRADE_ACTION_REMOVE; // 取引操作タイプ - request.order = ticket; // 注文チケット - if(!OrderSend(request, trade_result)) // 削除リクエスト送信 - { - Print("Failed to delete order. Ticket: ", ticket, " Error: ", GetLastError()); - } - else - { - if(trade_result.retcode == TRADE_RETCODE_DONE) - { - Print("Order canceled successfully due to a time limit. Ticket: ", ticket); - result = true; // 削除成功フラグ - } - else - { - Print("Order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode); - } - } - } - } - } - return result; - } - -//+------------------------------------------------------------------+ -//| 時間が経過したポジションをクローズする関数 -//+------------------------------------------------------------------+ -bool CloseExpiredPositions() - { - bool result = false; - -// ポジションを逆順でループ - for(int i = PositionsTotal() - 1; i >= 0; i--) - { - string position_symbol = PositionGetSymbol(i); - if(position_symbol!="") - { - // Magic Number とシンボルでフィルタリング - if(position_symbol != _Symbol) - continue; - if(PositionGetInteger(POSITION_MAGIC) == magic_number) - { - // ポジションのエントリー時刻を取得 - datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); - - int entry_shift = GetBarShiftByTime(entry_time, PERIOD_H1, -1); - if(entry_shift < 0) - continue; - - if(entry_shift >= CLOSE_H1_LIMIT) // H1本数(=12本 ≒ 12時間) - { - MqlTradeRequest request = {}; - MqlTradeResult trade_result = {}; - - // ポジションタイプに応じてリクエストを設定 - int position_type = (int)PositionGetInteger(POSITION_TYPE); - request.action = TRADE_ACTION_DEAL; - request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号 - request.symbol = position_symbol; // シンボル - request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ - request.price = (position_type == POSITION_TYPE_BUY) - ? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ - : SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ - request.deviation = slippage; - request.type = (position_type == POSITION_TYPE_BUY) - ? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ - : ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ - request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー - request.magic = magic_number; - - // 注文送信 - if(!OrderSend(request, trade_result)) - { - Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError()); - continue; - } - - // 結果の確認 - if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL) - { - Print("Position closed successfully due to a time limit. Ticket: ", request.position); - result = true; // 少なくとも1つ成功した場合 - } - else - { - Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode); - } - } - } - } - else - { - Print("Failed to select position at index ", i, ". Error: ", GetLastError()); - } - } - return result; - } - - -//+------------------------------------------------------------------+ -//| 指定された時間に最も近いバーのインデックスを返す関数 -//+------------------------------------------------------------------+ -int GetBarShiftByTime(datetime time, ENUM_TIMEFRAMES tf, int default_shift = -1) - { - if(time <= 0) - return default_shift; - int shift = iBarShift(_Symbol, tf, time, false); // 近傍バーで解決する - if(shift < 0) - return default_shift; - return shift; - } -//+------------------------------------------------------------------+ diff --git a/Experts/MyProject/HIT-EA_refactor.mq5 b/Experts/MyProject/HIT-EA_refactor.mq5 deleted file mode 100644 index 7c1daae..0000000 --- a/Experts/MyProject/HIT-EA_refactor.mq5 +++ /dev/null @@ -1,1496 +0,0 @@ -//+------------------------------------------------------------------+ -//| HIT_EA.mq5 | -//| Copyright 2026, nanpin-martin.com | -//| https://www.nanpin-martin.com | -//+------------------------------------------------------------------+ -#property copyright "Copyright 2026, nanpin-martin.com" -#property link "https://nanpin-martin.com/" -#property version "1.01" - -//+------------------------------------------------------------------+ -//| バッチファイル実行用の設定 -//+------------------------------------------------------------------+ - -#import "Shell32.dll" -int ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd); -#import - -// バッチファイルのパス -string get_trend_reply_bat = "C:\\ea_py\\get_trend_reply.bat"; // H4: trend -string get_entry_reply_bat = "C:\\ea_py\\get_entry_reply.bat"; // H1: entry - -// プロセス完了ファイルの設定 -string done_trend_file = "process_done_trend.txt"; -string done_entry_file = "process_done_entry.txt"; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| インプットの設定 -//+------------------------------------------------------------------+ - -//--- 入力パラメータ -input double lot_size = 0.01; // ロット数 -input double spread_limit = 60; // 許容スプレッド(point) -input int magic_number = 10001; // マジックナンバー -input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) -ulong slippage = 10; // スリッページ - -//--- 主要な定数・設定 -#define POSITION_LIMIT 48 -#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) -#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) -#define HISTORY_BARS 72 -#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す -// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) -#define TARGET_SIZE 13 -#define DEFAULT_TARGET_PRICE 0.0 - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| EAの状態をまとめる構造体 -//+------------------------------------------------------------------+ - -struct EAState - { - int trend_state; // ★追加:トレンドを格納する変数 - int res_chk; // GPTの回答が正しく取得できたか - double en_price[5]; // エントリー基準価格 - double tp_price[5]; // 利益確定価格 - double sl_price[5]; // ロスカット基準価格 - bool load_trend_flg; // ★変更:トレンドを更新するタイミングか - bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか - int chk_cnt; // エントリー判定の試行回数 - datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 - datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 - datetime last_chk; - }; - -EAState g_ea; // EA全体の状態をここで管理 - -// initial_order 用の初回処理フラグ -// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 -bool g_init_trend_pending = false; -bool g_init_entry_pending = false; - -// H4/H1のバー更新状態 -int g_pre_bars_H4 = 0; -int g_pre_bars_H1 = 0; -bool g_bars_H1_check = false; - -// ティックごとの価格情報 -struct TickContext - { - double ask; - double bid; - double spread; - double spread_points; - int digits; - }; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| 起動時の処理 -//+------------------------------------------------------------------+ -/** - * @brief EA起動時の初期化処理を行います。 - * - * Python連携用のdoneファイルを作成し、`initial_order` が有効な場合は - * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 - * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 - */ -void OnInit() -// プロセス完了ファイル("process_done_trend.txt" または "process_done_entry.txt")を作成 - { - CreateDoneFile(done_trend_file); - CreateDoneFile(done_entry_file); - - // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 - g_init_trend_pending = (initial_order == 1); - g_init_entry_pending = (initial_order == 1); - } - -//+------------------------------------------------------------------+ -//| ティック毎の処理 -//+------------------------------------------------------------------+ -/** - * @brief ティック受信ごとにEA全体の処理を制御します。 - * - * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 - * H1エントリー価格更新、ステータス表示、新規注文判定の順です。 - * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 - */ -void OnTick() - { - TickContext ctx; - if(!GetTickContext(ctx)) - return; - - // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 - ManageExpiredTrades(); - - // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 - ProcessTrendUpdate(g_ea); - - // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 - if(!IsTrendResultReady()) - return; - - RefreshTrendState(g_ea); - - // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 - ProcessEntryUpdate(g_ea); - - UpdateStatusComment(ctx); - - // ここから下は新規注文に関する処理。 - if(!IsSpreadAllowed(ctx)) - return; - - if(!IsEntryResultReady()) - return; - - RefreshTargetPrices(g_ea); - ProcessEntryDecisionIfNeeded(g_ea, ctx); - } - -//+------------------------------------------------------------------+ -//| ティック情報を取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief 現在のAsk/Bid/スプレッド情報を取得してTickContextへ格納します。 - * - * @param ctx 取得したティック情報を格納する構造体参照。 - * @return 取得成功時はtrue、ティック情報を取得できない場合はfalse。 - */ -bool GetTickContext(TickContext &ctx) - { - MqlTick last_tick; - if(!SymbolInfoTick(_Symbol, last_tick)) - return false; - - ctx.ask = last_tick.ask; - ctx.bid = last_tick.bid; - ctx.spread_points = MathRound((ctx.ask - ctx.bid) / Point()); - ctx.spread = ctx.spread_points * Point(); - ctx.digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - - return true; - } - -//+------------------------------------------------------------------+ -//| 期限切れ注文・期限切れポジションを処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief 期限切れの未約定注文と保有ポジションを処理します。 - * - * 未約定注文はENTRY_H1_LIMIT、保有ポジションはCLOSE_H1_LIMITを基準に判定します。 - * 新規注文条件とは独立して、OnTickの早い段階で実行される想定です。 - */ -void ManageExpiredTrades() - { - if(OrdersTotal() > 0) - CancelExpiredOrders(); - - if(PositionsTotal() > 0) - CloseExpiredPositions(); - } - -//+------------------------------------------------------------------+ -//| H4更新検知とトレンド判定Python起動を処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4新バーまたは初回起動を検知し、トレンド判定Pythonを起動します。 - * - * @param state EA全体の状態。トレンド更新開始フラグを更新します。 - * - * Python処理中はdoneファイルが存在しないため、CSVの上書きと二重起動を抑止します。 - */ -void ProcessTrendUpdate(EAState &state) - { - int current_bars_H4 = iBars(NULL, PERIOD_H4); - if(g_pre_bars_H4 == 0) - g_pre_bars_H4 = current_bars_H4; - - int bars_H4_change = current_bars_H4 - g_pre_bars_H4; - bool trend_trigger = (bars_H4_change > 0 || g_init_trend_pending); - - if(!trend_trigger) - return; - - if(RecordOHLCAndExecuteBatch_Trend(state)) - { - g_init_trend_pending = false; - g_pre_bars_H4 = current_bars_H4; - } - } - -//+------------------------------------------------------------------+ -//| トレンド判定Pythonの完了状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief H4トレンド判定Pythonの完了状態を確認します。 - * - * @return `process_done_trend.txt` が存在する場合はtrue、未完了の場合はfalse。 - */ -bool IsTrendResultReady() - { - return CheckDoneFile(done_trend_file); - } - -//+------------------------------------------------------------------+ -//| トレンド判定結果をEA状態へ反映する関数 -//+------------------------------------------------------------------+ -/** - * @brief 完了済みのH4トレンド判定結果をEA状態へ反映します。 - * - * @param state EA全体の状態。`trend_state` と最終更新時刻が更新されます。 - */ -void RefreshTrendState(EAState &state) - { - GetTrendState(state); - } - -//+------------------------------------------------------------------+ -//| H1更新検知とエントリー価格生成Python起動を処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief H1新バーまたは初回起動を検知し、エントリー価格生成Pythonを起動します。 - * - * @param state EA全体の状態。ターゲット価格更新開始フラグや判定リトライ状態を更新します。 - * - * Python処理中はdoneファイルが存在しないため、H1 CSVの上書きと二重起動を抑止します。 - */ -void ProcessEntryUpdate(EAState &state) - { - int current_bars_H1 = iBars(NULL, PERIOD_H1); - if(g_pre_bars_H1 == 0) - g_pre_bars_H1 = current_bars_H1; - - int bars_H1_change = current_bars_H1 - g_pre_bars_H1; - bool entry_trigger = (bars_H1_change > 0 || g_init_entry_pending); - - if(!entry_trigger) - return; - - if(RecordOHLCAndExecuteBatch_Entry(state)) - { - g_init_entry_pending = false; - g_bars_H1_check = true; - state.chk_cnt = 0; - state.last_chk = 0; - g_pre_bars_H1 = current_bars_H1; - } - } - -//+------------------------------------------------------------------+ -//| チャートコメント文字列を作成する関数 -//+------------------------------------------------------------------+ -/** - * @brief チャート左上に表示するステータスメッセージを組み立てます。 - * - * @param ctx 現在のAsk/Bid/スプレッド情報。 - * @return Comment()へ渡す表示用文字列。 - */ -string BuildStatusMessage(TickContext &ctx) - { - string message = StringFormat( - " \nAsk: %." + IntegerToString(ctx.digits) + "f" - "\nBid: %." + IntegerToString(ctx.digits) + "f" - "\nSpread: %." + IntegerToString(ctx.digits) + "f" - "\nSpread Points: %.0f" - "\n\nLast Trend Update:\n %s" - "\n\nTrend State: %d" - "\n\nLast Target Update:\n %s" - "\n\nMy Used Count: %d / %d" - "\n\n[T1 Buy Stop ] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" - "\n[T2 Buy Limit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" - "\n[T3 Sell Stop] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" - "\n[T4 SellLimit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f\n", - ctx.ask, ctx.bid, ctx.spread, ctx.spread_points, - TimeToString(g_ea.last_trend_update, TIME_DATE | TIME_MINUTES), - g_ea.trend_state, - TimeToString(g_ea.last_target_update, TIME_DATE | TIME_MINUTES), - CountMyUsed(), POSITION_LIMIT, - g_ea.en_price[1], g_ea.tp_price[1], g_ea.sl_price[1], - g_ea.en_price[2], g_ea.tp_price[2], g_ea.sl_price[2], - g_ea.en_price[3], g_ea.tp_price[3], g_ea.sl_price[3], - g_ea.en_price[4], g_ea.tp_price[4], g_ea.sl_price[4] - ); - return message; - } - -//+------------------------------------------------------------------+ -//| チャートコメントを更新する関数 -//+------------------------------------------------------------------+ -/** - * @brief チャート上のステータスコメントを更新します。 - * - * @param ctx 現在のAsk/Bid/スプレッド情報。 - */ -void UpdateStatusComment(TickContext &ctx) - { - Comment(BuildStatusMessage(ctx)); - } - -//+------------------------------------------------------------------+ -//| スプレッドが許容範囲内か判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief 現在スプレッドが入力パラメータの許容範囲内か判定します。 - * - * @param ctx 現在のスプレッド情報。 - * @return 許容範囲内ならtrue、超過している場合はfalse。 - */ -bool IsSpreadAllowed(TickContext &ctx) - { - return (ctx.spread <= spread_limit * Point()); - } - -//+------------------------------------------------------------------+ -//| エントリー価格生成Pythonの完了状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief H1エントリー価格生成Pythonの完了状態を確認します。 - * - * @return `process_done_entry.txt` が存在する場合はtrue、未完了の場合はfalse。 - */ -bool IsEntryResultReady() - { - return CheckDoneFile(done_entry_file); - } - -//+------------------------------------------------------------------+ -//| ターゲット価格をEA状態へ反映する関数 -//+------------------------------------------------------------------+ -/** - * @brief 完了済みのH1エントリー価格生成結果をEA状態へ反映します。 - * - * @param state EA全体の状態。`res_chk` と各注文タイプのen/tp/slが更新されます。 - */ -void RefreshTargetPrices(EAState &state) - { - GetTargetPrices(state); - } - -//+------------------------------------------------------------------+ -//| エントリー判定が必要な場合のみ注文処理を実行する関数 -//+------------------------------------------------------------------+ -/** - * @brief エントリー判定タイミングに到達している場合のみ注文処理を実行します。 - * - * @param state EA全体の状態。 - * @param ctx 現在のAsk/Bid/スプレッド情報。 - * - * 判定前チェック、許可注文の送信、リトライ状態更新をまとめて制御します。 - */ -void ProcessEntryDecisionIfNeeded(EAState &state, TickContext &ctx) - { - if(!ShouldRunEntryDecision(state)) - return; - - g_bars_H1_check = false; - state.last_chk = TimeCurrent(); - - if(!ValidateEntryPreconditions(state)) - return; - - int sent_success = SendAllowedEntryOrders(state, ctx); - UpdateEntryRetryState(state, sent_success); - } - -//+------------------------------------------------------------------+ -//| エントリー判定を実行するタイミングか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief エントリー判定を実行するタイミングか確認します。 - * - * @param state EA全体の状態。前回判定時刻を参照します。 - * @return H1更新後、かつ前回判定から60秒以上経過していればtrue。 - */ -bool ShouldRunEntryDecision(EAState &state) - { - return (g_bars_H1_check && TimeCurrent() - state.last_chk >= 60); - } - -//+------------------------------------------------------------------+ -//| エントリー判定前の共通チェックを行う関数 -//+------------------------------------------------------------------+ -/** - * @brief 新規注文前の共通条件を検証します。 - * - * @param state EA全体の状態。 - * @return 注文判定を続行できる場合はtrue、停止すべき場合はfalse。 - * - * `res_chk`、`trend_state`、対象EAの注文/ポジション数上限を確認します。 - */ -bool ValidateEntryPreconditions(EAState &state) - { - if(state.res_chk != 1) - { - Print("[Entry Skip] target_prices invalid. res_chk=", state.res_chk); - state.chk_cnt = 0; - return false; - } - - if(state.trend_state == 0) - { - Print("[Entry Skip] trend_state=0 (range). No entry orders are sent."); - state.chk_cnt = 0; - return false; - } - - if(state.trend_state != 1 && state.trend_state != 2) - { - Print("[Entry Skip] invalid trend_state=", state.trend_state); - state.chk_cnt = 0; - return false; - } - - int used = CountMyUsed(); - if(used >= POSITION_LIMIT) - { - Print("Position limit exceeded: used=", used, " limit=", POSITION_LIMIT); - return false; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| 許可された注文タイプだけを順番に送信する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4トレンド方向で許可された注文タイプだけを順番に送信します。 - * - * @param state EA全体の状態。各注文タイプのen/tp/slを参照します。 - * @param ctx 現在のAsk/Bid/スプレッド情報。 - * @return 送信成功した注文数。 - */ -int SendAllowedEntryOrders(EAState &state, TickContext &ctx) - { - int sent_success = 0; - - for(int t = 1; t <= 4; t++) - { - int used = CountMyUsed(); - if(used >= POSITION_LIMIT) - { - Print("Position limit reached while sending. used=", used, " limit=", POSITION_LIMIT); - break; - } - - if(!IsOrderTypeAllowedByTrend(t, state.trend_state)) - { - Print("[Skip] orderType=", t, " not allowed by trend_state=", state.trend_state); - continue; - } - - if(TrySendEntryOrder(t, state, ctx)) - sent_success++; - } - - return sent_success; - } - -//+------------------------------------------------------------------+ -//| 注文タイプ1件分の価格検証と注文送信を行う関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプ1件分の価格検証と注文送信を行います。 - * - * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 - * @param state EA全体の状態。対象注文タイプのen/tp/slを参照します。 - * @param ctx 現在のAsk/Bid情報。 - * @return 注文送信に成功した場合はtrue、それ以外はfalse。 - */ -bool TrySendEntryOrder(const int orderType, EAState &state, TickContext &ctx) - { - string entry_type = EntryTypeName(orderType); - double cur_price = CurrentPriceForOrderType(orderType, ctx); - - double en = state.en_price[orderType]; - double tp = state.tp_price[orderType]; - double sl = state.sl_price[orderType]; - - if(!HasValidTargetPrices(en, tp, sl)) - { - Print("[Skip] invalid target prices. orderType=", orderType, - " en=", en, " tp=", tp, " sl=", sl); - return false; - } - - bool ok = IsTargetPriceOrderConditionMatched(orderType, ctx, en, tp, sl); - if(!ok) - { - Print("[No ", entry_type, "] cur=", cur_price, " en=", en, " tp=", tp, " sl=", sl); - return false; - } - - Print("[", entry_type, " Order Try at ", cur_price, "] en=", en, " tp=", tp, " sl=", sl); - - if(SendOrder(orderType, en, tp, sl)) - { - Print("[", entry_type, " Order Sent] ticket ok. en=", en, " tp=", tp, " sl=", sl); - return true; - } - - Print("[", entry_type, " Order Failed] en=", en, " tp=", tp, " sl=", sl); - return false; - } - -//+------------------------------------------------------------------+ -//| 注文タイプ名を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプ番号に対応する表示名を返します。 - * - * @param orderType 注文タイプ番号。 - * @return 注文タイプの表示名。不正値の場合は"Unknown"。 - */ -string EntryTypeName(const int orderType) - { - switch(orderType) - { - case 1: - return "Buy Stop"; - case 2: - return "Buy Limit"; - case 3: - return "Sell Stop"; - case 4: - return "Sell Limit"; - default: - return "Unknown"; - } - } - -//+------------------------------------------------------------------+ -//| 注文タイプに応じた現在価格を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプに応じて価格比較に使う現在価格を返します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @return 買い系注文ではAsk、売り系注文ではBid。 - */ -double CurrentPriceForOrderType(const int orderType, TickContext &ctx) - { - if(orderType == 1 || orderType == 2) - return ctx.ask; - - return ctx.bid; - } - -//+------------------------------------------------------------------+ -//| 注文タイプごとの価格整合条件を判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプごとの現在価格・エントリー・TP・SLの大小関係を検証します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @param en エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return 注文タイプの価格条件を満たす場合はtrue。 - */ -bool IsTargetPriceOrderConditionMatched(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) - { - switch(orderType) - { - case 1: // Buy Stop - return (ctx.ask < en && tp > en && sl < en); - - case 2: // Buy Limit - return (ctx.ask > en && tp > en && sl < en); - - case 3: // Sell Stop - return (ctx.bid > en && tp < en && sl > en); - - case 4: // Sell Limit - return (ctx.bid < en && tp < en && sl > en); - - default: - return false; - } - } - -//+------------------------------------------------------------------+ -//| 注文送信結果に応じてリトライ状態を更新する関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文送信結果に応じてエントリー判定のリトライ状態を更新します。 - * - * @param state EA全体の状態。`chk_cnt` を更新します。 - * @param sent_success 今回送信に成功した注文数。 - * - * 注文が1件も送信されなかった場合、最大10回まで60秒間隔で再判定します。 - */ -void UpdateEntryRetryState(EAState &state, const int sent_success) - { - if(sent_success > 0) - { - state.chk_cnt = 0; - return; - } - - state.chk_cnt += 1; - Print("[Retry] no order sent. chk_cnt=", state.chk_cnt, "/10"); - - if(state.chk_cnt < 10) - { - g_bars_H1_check = true; // 60秒後に再度エントリー判定を実行する - return; - } - - Print("[Retry End] reached max tries. reset chk_cnt."); - state.chk_cnt = 0; - } - -//+------------------------------------------------------------------+ -//| "process_done.txt"を作成する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したdoneファイルを作成します。 - * - * @param name 作成するファイル名。MT5のMQL5\Files配下を基準に扱います。 - */ -void CreateDoneFile(const string name) - { - if(!FileIsExist(name)) - { - int h = FileOpen(name, FILE_WRITE|FILE_TXT); - if(h != INVALID_HANDLE) - FileClose(h); - else - Print("Failed to create file: ", name, " err=", GetLastError()); - } - } - -//+------------------------------------------------------------------+ -//| "process_done.txt"を削除する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したdoneファイルを削除します。 - * - * @param name 削除するファイル名。存在しない場合は何もしません。 - */ -void DeleteDoneFile(const string name) - { - if(FileIsExist(name)) - FileDelete(name); - } - - -//+------------------------------------------------------------------+ -//| "process_done.txt"の存在を確認し、存在する場合はtrueを返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したdoneファイルの存在有無を確認します。 - * - * @param name 確認するファイル名。 - * @return ファイルが存在する場合はtrue、存在しない場合はfalse。 - */ -bool CheckDoneFile(const string name) - { - return FileIsExist(name); - } - -//+------------------------------------------------------------------+ -//| 最新のOHLCデータを取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定時間足のOHLCデータを取得します。 - * - * @param times 取得したバー時刻を格納する配列。 - * @param open_prices 取得したOpen価格を格納する配列。 - * @param high_prices 取得したHigh価格を格納する配列。 - * @param low_prices 取得したLow価格を格納する配列。 - * @param close_prices 取得したClose価格を格納する配列。 - * @param tf 取得対象の時間足。 - * @param bars_count 取得するバー本数。 - * @return 取得に成功した場合はtrue、失敗した場合はfalse。 - * - * `OHLC_START_SHIFT=1` のため、形成中のバーではなく確定足から取得します。 - */ -bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count) - { - ArrayResize(times, 0); - ArrayResize(open_prices, 0); - ArrayResize(high_prices, 0); - ArrayResize(low_prices, 0); - ArrayResize(close_prices, 0); - - if(bars_count < 2) - return false; - -// OHLCデータを取得 -// CopyTime と CopyRates を別々に呼ばず、MqlRates の time/open/high/low/close を同一配列から取得する。 -// OHLC_START_SHIFT=1 のため、形成中の0本目ではなく確定足からPythonへ渡す。 - MqlRates rates[]; - int copied = CopyRates(_Symbol, tf, OHLC_START_SHIFT, bars_count, rates); - if(copied <= 0) - { - Print(__FUNCTION__, ": Failed to copy rates data (bars=", bars_count, ") err=", GetLastError()); - return false; - } - - ArrayResize(times, copied); - ArrayResize(open_prices, copied); - ArrayResize(high_prices, copied); - ArrayResize(low_prices, copied); - ArrayResize(close_prices, copied); - -// 必要なデータを配列に格納 - for(int i = 0; i < copied; i++) - { - times[i] = rates[i].time; - open_prices[i] = rates[i].open; - high_prices[i] = rates[i].high; - low_prices[i] = rates[i].low; - close_prices[i] = rates[i].close; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| "ohlc.csv"を出力する関数 -//+------------------------------------------------------------------+ -/** - * @brief OHLC配列をPython入力用CSVとして出力します。 - * - * @param filename 出力するCSVファイル名。 - * @param times バー時刻配列。 - * @param open_prices Open価格配列。 - * @param high_prices High価格配列。 - * @param low_prices Low価格配列。 - * @param close_prices Close価格配列。 - */ -void RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[]) - { -// 出力先ファイル名 -// string filename = "ohlc.csv"; - -// ファイルを "書き込みモード" でオープン (テキスト/ANSI) - int fileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI); - -// ファイルが開けたかチェック - if(fileHandle == INVALID_HANDLE) - { - Print(__FUNCTION__, " : Failed to open file: ", GetLastError()); - return; - } - -// 配列サイズを取得 (times, open, high, low, close の要素数は同じ前提) - int size = ArraySize(times); - -// ヘッダー行を追加 - FileWrite(fileHandle, "Time,Open,High,Low,Close"); - -// 小数点の桁数を取得 - int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - -// フォーマット文字列を動的に生成 - string formatString = StringFormat("%%s,%%.%df,%%.%df,%%.%df,%%.%df", digits, digits, digits, digits); - -// 1行ずつ "時刻, Open, High, Low, Close" の形式で書き込み - for(int i = 0; i < size; i++) - { - // 時間をフォーマット - string timeStr = TimeToString(times[i], TIME_DATE | TIME_MINUTES); - - // 1行分の文字列を生成 (小数点以下の桁数を `digits` に調整) - string line = StringFormat(formatString, - timeStr, open_prices[i], high_prices[i], low_prices[i], close_prices[i]); - - // ファイルに書き込み (改行付き) - FileWrite(fileHandle, line); - } - -// 書き込み終了後、ファイルを閉じる - FileClose(fileHandle); - } - -//+------------------------------------------------------------------+ -//| バッチファイル(Pythonスクリプト)を実行する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4トレンド判定用バッチファイルを起動します。 - * - * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 - * - * 起動直前に`process_done_trend.txt`を削除し、ShellExecuteW失敗時はdoneファイルを戻します。 - */ -bool ExecuteBatchTrend() - { - if(!CheckDoneFile(done_trend_file)) - { - Print("Trend batch is still running. Skip execute."); - return false; - } - - DeleteDoneFile(done_trend_file); - int ret = ShellExecuteW(0, "open", get_trend_reply_bat, "", "", 1); - if(ret <= 32) - { - Print("ShellExecuteW trend failed. ret=", ret, " file=", get_trend_reply_bat); - CreateDoneFile(done_trend_file); - return false; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief H1エントリー価格生成用バッチファイルを起動します。 - * - * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 - * - * 起動直前に`process_done_entry.txt`を削除し、ShellExecuteW失敗時はdoneファイルを戻します。 - */ -bool ExecuteBatchEntry() - { - if(!CheckDoneFile(done_entry_file)) - { - Print("Entry batch is still running. Skip execute."); - return false; - } - - DeleteDoneFile(done_entry_file); - int ret = ShellExecuteW(0, "open", get_entry_reply_bat, "", "", 1); - if(ret <= 32) - { - Print("ShellExecuteW entry failed. ret=", ret, " file=", get_entry_reply_bat); - CreateDoneFile(done_entry_file); - return false; - } - - return true; - } - - -//+------------------------------------------------------------------+ -//| "ohlc.csv"を出力後、バッチファイルの実行する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4 OHLCをCSV出力し、トレンド判定Pythonを起動します。 - * - * @param state EA全体の状態。トレンド読込待ちフラグを更新します。 - * @return CSV出力とバッチ起動が開始できた場合はtrue。 - */ -bool RecordOHLCAndExecuteBatch_Trend(EAState &state) - { - if(!CheckDoneFile(done_trend_file)) - { - Print("Trend Python is still running. Skip H4 CSV update."); - return false; - } - - datetime times[]; - double open_prices[], high_prices[], low_prices[], close_prices[]; - - if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, - PERIOD_H4, HISTORY_BARS)) - { Print("GetLatestOHLC(H4) failed."); return false; } - - RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices); - - if(!ExecuteBatchTrend()) - return false; - - state.load_trend_flg = true; - return true; - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief H1 OHLCをCSV出力し、エントリー価格生成Pythonを起動します。 - * - * @param state EA全体の状態。ターゲット価格読込待ちフラグを更新します。 - * @return CSV出力とバッチ起動が開始できた場合はtrue。 - */ -bool RecordOHLCAndExecuteBatch_Entry(EAState &state) - { - if(!CheckDoneFile(done_entry_file)) - { - Print("Entry Python is still running. Skip H1 CSV update."); - return false; - } - - datetime times[]; - double open_prices[], high_prices[], low_prices[], close_prices[]; - - if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, - PERIOD_H1, HISTORY_BARS)) - { Print("GetLatestOHLC(H1) failed."); return false; } - - RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices); - - if(!ExecuteBatchEntry()) - return false; - - state.load_target_flg = true; - return true; - } - - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief Pythonが出力したトレンド判定結果を必要なタイミングで読み込みます。 - * - * @param state EA全体の状態。`trend_state` と更新フラグを更新します。 - * @return 現状は常にtrue。 - */ -bool GetTrendState(EAState &state) - { - bool ProcessDone = CheckDoneFile(done_trend_file); - if(ProcessDone && g_ea.load_trend_flg) - { - int trend_state; - LoadTrendState(trend_state); - state.trend_state = trend_state; - Print("trend_state: ", state.trend_state); - g_ea.load_trend_flg = false; - g_ea.last_trend_update = TimeLocal(); - } - return true; - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief `trend_state.txt` からH4トレンド判定値を読み込みます。 - * - * @param trend_state 読み込んだ値を格納する参照。0=RANGE、1=UP、2=DOWN。 - * - * ファイル未存在、読込失敗、異常値の場合は安全側として0を設定します。 - */ -void LoadTrendState(int &trend_state) - { - string filename = "trend_state.txt"; - trend_state = 0; - - if(FileIsExist(filename)) - { - int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); - if(filehandle != INVALID_HANDLE) - { - string line = FileReadString(filehandle); - int value = (int)StringToInteger(line); - if(value == 0 || value == 1 || value == 2) - trend_state = value; - else - Print("Invalid trend_state value: ", line, ". Use 0."); - FileClose(filehandle); - } - else - { Print("Failed to open trend_state.txt"); trend_state = 0; } - } - else - { Print("trend_state.txt not found"); trend_state = 0; } - } - -//+------------------------------------------------------------------+ -//| ターゲット価格を取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief Pythonが出力したエントリー価格群を必要なタイミングで読み込みます。 - * - * @param state EA全体の状態。`res_chk` と4タイプ分のen/tp/slを更新します。 - * @return 現状は常にtrue。 - */ -bool GetTargetPrices(EAState &state) - { - bool ProcessDone = CheckDoneFile(done_entry_file); // ←変更 - if(ProcessDone && g_ea.load_target_flg) // ←変更 - { - double target_prices[]; - LoadTargetPrices(target_prices); - - int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - - // 先頭は res_chk - state.res_chk = (int)target_prices[0]; - - // ★ 1..4 の (en,tp,sl) を読む:合計12個 - for(int t=1; t<=4; t++) - { - int base = 1 + (t-1)*3; // 1,4,7,10 - state.en_price[t] = NormalizeDouble(target_prices[base + 0], digits); - state.tp_price[t] = NormalizeDouble(target_prices[base + 1], digits); - state.sl_price[t] = NormalizeDouble(target_prices[base + 2], digits); - } - - // ログ(任意) - Print("target_prices: res=", state.res_chk, - " | T1 en=", state.en_price[1], " tp=", state.tp_price[1], " sl=", state.sl_price[1], - " | T2 en=", state.en_price[2], " tp=", state.tp_price[2], " sl=", state.sl_price[2], - " | T3 en=", state.en_price[3], " tp=", state.tp_price[3], " sl=", state.sl_price[3], - " | T4 en=", state.en_price[4], " tp=", state.tp_price[4], " sl=", state.sl_price[4]); - - g_ea.load_target_flg = false; // ←変更 - g_ea.last_target_update = TimeLocal(); // ←変更 - } - return true; - } -//+------------------------------------------------------------------+ -//+------------------------------------------------------------------+ -//| "target_prices.txt"を読み込む関数 -//+------------------------------------------------------------------+ -/** - * @brief `target_prices.txt` から13個の数値を読み込みます。 - * - * @param target_prices 読み込んだ数値を格納する配列。先頭がres_chk、以降は4タイプ分のen/tp/sl。 - * - * 行数不足の場合は`res_chk=0`として、ターゲット価格を無効扱いにします。 - */ -void LoadTargetPrices(double &target_prices[]) - { - ArrayResize(target_prices, TARGET_SIZE); - for(int i = 0; i < TARGET_SIZE; i++) - target_prices[i] = DEFAULT_TARGET_PRICE; - - string filename = "target_prices.txt"; - - if(FileIsExist(filename)) - { - int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); - if(filehandle != INVALID_HANDLE) - { - int i = 0; - while(!FileIsEnding(filehandle) && i < TARGET_SIZE) - { - string line = FileReadString(filehandle); - target_prices[i] = StringToDouble(line); - i++; - } - FileClose(filehandle); - - if(i < TARGET_SIZE) - { - Print("target_prices.txt line count is short. loaded=", i, " required=", TARGET_SIZE); - target_prices[0] = 0; // 不完全なファイルは無効扱い - } - } - else - { - Print("Failed to open file. Error code: ", GetLastError()); - } - } - else - { - Print("File does not exist: ", filename); - } - } - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit) -//+------------------------------------------------------------------+ -/** - * @brief 指定された注文タイプでペンディング注文を送信します。 - * - * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 - * @param price エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return 注文が正常に受理された場合はtrue、それ以外はfalse。 - */ -bool SendOrder(int orderType, double price, double tp, double sl) - { - MqlTradeRequest request = {}; - MqlTradeResult result = {}; - - request.magic = magic_number; - request.symbol = _Symbol; - request.volume = lot_size; - request.deviation = slippage; - request.type_filling = GetOrderFillingPolicy(_Symbol); - request.price = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.comment = RequestComment(orderType); - - string orderTypeStr = ""; - -// 指値または逆指値注文 - request.action = TRADE_ACTION_PENDING; - - switch(orderType) - { - case 1: // 順張り買い (buy-stop) - request.type = ORDER_TYPE_BUY_STOP; - orderTypeStr = "buy-stop"; - break; - - case 2: // 逆張り買い (buy-limit) - request.type = ORDER_TYPE_BUY_LIMIT; - orderTypeStr = "buy-limit"; - break; - - case 3: // 順張り売り (sell-stop) - request.type = ORDER_TYPE_SELL_STOP; - orderTypeStr = "sell-stop"; - break; - - case 4: // 逆張り売り (sell-limit) - request.type = ORDER_TYPE_SELL_LIMIT; - orderTypeStr = "sell-limit"; - break; - - default: // 不正なorderType - Print(__FUNCTION__, ": invalid orderType=", orderType); - return false; - } - -// 注文送信 - if(!OrderSend(request, result)) - { - Print(__FUNCTION__, ": OrderSend failed. err=", GetLastError()); - return false; - } - -// 注文成功確認 - if(result.retcode == TRADE_RETCODE_PLACED || - result.retcode == TRADE_RETCODE_DONE || - result.retcode == TRADE_RETCODE_DONE_PARTIAL) - { - Print(request.comment + " - " + orderTypeStr + " order success (ticket=", result.order, ")"); - return true; - } - else - { - Print(__FUNCTION__, ": OrderSend retcode=", result.retcode); - return false; - } - } - -//+------------------------------------------------------------------+ -//| 注文の執行ポリシーを取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief シンボルに対応する注文執行ポリシーを取得します。 - * - * @param symbol 対象シンボル。 - * @return 利用可能な執行ポリシー。IOC、FOK、RETURNの順で選択します。 - */ -ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol) - { - long fill_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); - if((fill_mode & SYMBOL_FILLING_IOC) != 0) - return ORDER_FILLING_IOC; - if((fill_mode & SYMBOL_FILLING_FOK) != 0) - return ORDER_FILLING_FOK; - return ORDER_FILLING_RETURN; - } - -//+------------------------------------------------------------------+ -//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与) -//+------------------------------------------------------------------+ -/** - * @brief 注文コメント文字列を生成します。 - * - * @param orderType 注文タイプ番号。 - * @return 注文タイプとPCローカル時刻を含むコメント文字列。 - */ -string RequestComment(int orderType) - { - datetime localTime = TimeLocal(); - string timeStr = TimeToString(localTime, TIME_DATE|TIME_MINUTES); - -// 先頭に 1,2,3,4 を付ける(例: "1 | PC Time: 2026.02.14 19:05") - return IntegerToString(orderType) + " | PC Time: " + timeStr; - } - -//+------------------------------------------------------------------+ -//| H4トレンド方向に対して許可された注文タイプか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4トレンド方向に対して指定注文タイプが許可されるか判定します。 - * - * @param orderType 注文タイプ番号。 - * @param trend_state H4トレンド判定。0=RANGE、1=UP、2=DOWN。 - * @return 許可される注文タイプならtrue。レンジまたは異常値ではfalse。 - */ -bool IsOrderTypeAllowedByTrend(const int orderType, const int trend_state) - { - if(trend_state == 1) - return (orderType == 1 || orderType == 2); // UP: Buy Stop / Buy Limit のみ - - if(trend_state == 2) - return (orderType == 3 || orderType == 4); // DOWN: Sell Stop / Sell Limit のみ - - return false; // RANGEまたは異常値は新規注文しない - } - -//+------------------------------------------------------------------+ -//| target_prices.txt の価格が有効か判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief エントリー価格、利確価格、損切価格が有効値か判定します。 - * - * @param en エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return すべて0より大きい場合はtrue。 - */ -bool HasValidTargetPrices(const double en, const double tp, const double sl) - { - return (en > 0.0 && tp > 0.0 && sl > 0.0); - } - -//+------------------------------------------------------------------+ -//| 自EAの未約定注文数を数える関数 -//+------------------------------------------------------------------+ -/** - * @brief このEAが管理対象とする未約定注文数を数えます。 - * - * @return `_Symbol` と `magic_number` が一致する未約定注文数。 - */ -int CountMyPendingOrders() - { - int count = 0; - - for(int i = OrdersTotal() - 1; i >= 0; i--) - { - ulong ticket = OrderGetTicket(i); - if(ticket == 0) - continue; - - if(!OrderSelect(ticket)) - continue; - - if(OrderGetString(ORDER_SYMBOL) != _Symbol) - continue; - - if((int)OrderGetInteger(ORDER_MAGIC) != magic_number) - continue; - - count++; - } - - return count; - } - -//+------------------------------------------------------------------+ -//| 自EAのポジション数を数える関数 -//+------------------------------------------------------------------+ -/** - * @brief このEAが管理対象とする保有ポジション数を数えます。 - * - * @return `_Symbol` と `magic_number` が一致するポジション数。 - */ -int CountMyPositions() - { - int count = 0; - - for(int i = PositionsTotal() - 1; i >= 0; i--) - { - string symbol = PositionGetSymbol(i); - if(symbol == "") - continue; - - if(symbol != _Symbol) - continue; - - if((int)PositionGetInteger(POSITION_MAGIC) != magic_number) - continue; - - count++; - } - - return count; - } - -//+------------------------------------------------------------------+ -//| 自EAの注文+ポジション数を数える関数 -//+------------------------------------------------------------------+ -/** - * @brief このEAが使用中の注文数とポジション数の合計を返します。 - * - * @return 未約定注文数 + 保有ポジション数。 - */ -int CountMyUsed() - { - return CountMyPendingOrders() + CountMyPositions(); - } - - -//+------------------------------------------------------------------+ -//| 時間が経過したエントリー注文をキャンセルする関数 -//+------------------------------------------------------------------+ -/** - * @brief H1経過本数が上限を超えた未約定注文をキャンセルします。 - * - * @return 1件以上キャンセルに成功した場合はtrue。 - * - * 対象は`_Symbol` と `magic_number` が一致する未約定注文のみです。 - */ -bool CancelExpiredOrders() - { - bool result = false; - -// 未決済注文の数を取得 - for(int i = OrdersTotal() - 1; i >= 0; i--) - { - ulong ticket = OrderGetTicket(i); - if(OrderSelect(ticket)) // 注文を選択 - { - if(OrderGetString(ORDER_SYMBOL) != _Symbol) - continue; - if(OrderGetInteger(ORDER_MAGIC) != magic_number) - continue; - - datetime open_time = (datetime)OrderGetInteger(ORDER_TIME_SETUP); - - int open_shift = GetBarShiftByTime(open_time, PERIOD_H1, -1); // H1 - if(open_shift < 0) - continue; // 時刻解決できないなら触らない - - if(open_shift >= ENTRY_H1_LIMIT) // H1本数(=1本 ≒ 1時間) - { - MqlTradeRequest request = {}; - MqlTradeResult trade_result = {}; - request.action=TRADE_ACTION_REMOVE; // 取引操作タイプ - request.order = ticket; // 注文チケット - if(!OrderSend(request, trade_result)) // 削除リクエスト送信 - { - Print("Failed to delete order. Ticket: ", ticket, " Error: ", GetLastError()); - } - else - { - if(trade_result.retcode == TRADE_RETCODE_DONE) - { - Print("Order canceled successfully due to a time limit. Ticket: ", ticket); - result = true; // 削除成功フラグ - } - else - { - Print("Order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode); - } - } - } - } - } - return result; - } - -//+------------------------------------------------------------------+ -//| 時間が経過したポジションをクローズする関数 -//+------------------------------------------------------------------+ -/** - * @brief H1経過本数が上限を超えた保有ポジションをクローズします。 - * - * @return 1件以上クローズに成功した場合はtrue。 - * - * 対象は`_Symbol` と `magic_number` が一致するポジションのみです。 - */ -bool CloseExpiredPositions() - { - bool result = false; - -// ポジションを逆順でループ - for(int i = PositionsTotal() - 1; i >= 0; i--) - { - string position_symbol = PositionGetSymbol(i); - if(position_symbol!="") - { - // Magic Number とシンボルでフィルタリング - if(position_symbol != _Symbol) - continue; - if(PositionGetInteger(POSITION_MAGIC) == magic_number) - { - // ポジションのエントリー時刻を取得 - datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); - - int entry_shift = GetBarShiftByTime(entry_time, PERIOD_H1, -1); - if(entry_shift < 0) - continue; - - if(entry_shift >= CLOSE_H1_LIMIT) // H1本数(=12本 ≒ 12時間) - { - MqlTradeRequest request = {}; - MqlTradeResult trade_result = {}; - - // ポジションタイプに応じてリクエストを設定 - int position_type = (int)PositionGetInteger(POSITION_TYPE); - request.action = TRADE_ACTION_DEAL; - request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号 - request.symbol = position_symbol; // シンボル - request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ - request.price = (position_type == POSITION_TYPE_BUY) - ? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ - : SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ - request.deviation = slippage; - request.type = (position_type == POSITION_TYPE_BUY) - ? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ - : ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ - request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー - request.magic = magic_number; - - // 注文送信 - if(!OrderSend(request, trade_result)) - { - Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError()); - continue; - } - - // 結果の確認 - if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL) - { - Print("Position closed successfully due to a time limit. Ticket: ", request.position); - result = true; // 少なくとも1つ成功した場合 - } - else - { - Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode); - } - } - } - } - else - { - Print("Failed to select position at index ", i, ". Error: ", GetLastError()); - } - } - return result; - } - - -//+------------------------------------------------------------------+ -//| 指定された時間に最も近いバーのインデックスを返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定時刻に最も近いバーシフトを取得します。 - * - * @param time 判定対象の時刻。 - * @param tf 判定対象の時間足。 - * @param default_shift 取得失敗時に返す値。 - * @return 取得できたバーシフト。失敗時はdefault_shift。 - * - * `iBarShift(..., false)` を使い、厳密一致ではなく近傍バーで解決します。 - */ -int GetBarShiftByTime(datetime time, ENUM_TIMEFRAMES tf, int default_shift = -1) - { - if(time <= 0) - return default_shift; - int shift = iBarShift(_Symbol, tf, time, false); // 近傍バーで解決する - if(shift < 0) - return default_shift; - return shift; - } -//+------------------------------------------------------------------+ diff --git a/Experts/MyProject/HIT-EA_refactor_ver2.mq5 b/Experts/MyProject/HIT-EA_refactor_ver2.mq5 deleted file mode 100644 index 00430eb..0000000 --- a/Experts/MyProject/HIT-EA_refactor_ver2.mq5 +++ /dev/null @@ -1,2122 +0,0 @@ -//+------------------------------------------------------------------+ -//| HIT_EA.mq5 | -//| Copyright 2026, nanpin-martin.com | -//| https://www.nanpin-martin.com | -//+------------------------------------------------------------------+ -#property copyright "Copyright 2026, nanpin-martin.com" -#property link "https://nanpin-martin.com/" -#property version "1.01" - -//+------------------------------------------------------------------+ -//| バッチファイル実行用の設定 -//+------------------------------------------------------------------+ - -// Windowsプロセス起動・終了確認用 -#define CREATE_NO_WINDOW 0x08000000 -#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 -#define SYNCHRONIZE 0x00100000 -#define WAIT_OBJECT_0 0 -#define WAIT_TIMEOUT 258 -#define STILL_ACTIVE 259 - -struct STARTUPINFO_W - { - uint cb; - string lpReserved; - string lpDesktop; - string lpTitle; - uint dwX; - uint dwY; - uint dwXSize; - uint dwYSize; - uint dwXCountChars; - uint dwYCountChars; - uint dwFillAttribute; - uint dwFlags; - ushort wShowWindow; - ushort cbReserved2; - long lpReserved2; - long hStdInput; - long hStdOutput; - long hStdError; - }; - -struct PROCESS_INFORMATION - { - long hProcess; - long hThread; - uint dwProcessId; - uint dwThreadId; - }; - -#import "kernel32.dll" -int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); -int WaitForSingleObject(long hHandle, uint dwMilliseconds); -int GetExitCodeProcess(long hProcess, uint &lpExitCode); -int CloseHandle(long hObject); -long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); -#import - -// バッチファイルのパス -string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend -string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry - -// プロセス完了ファイルの設定 -string done_trend_file = "process_done_trend.txt"; -string done_entry_file = "process_done_entry.txt"; - -// Python出力ファイル -string trend_state_file = "trend_state.txt"; -string target_prices_file = "target_prices.txt"; - -// Python実行中を判定するための管理ファイル -string running_trend_file = "process_running_trend.txt"; -string running_entry_file = "process_running_entry.txt"; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| インプットの設定 -//+------------------------------------------------------------------+ - -//--- 入力パラメータ -input double lot_size = 0.01; // ロット数 -input double spread_limit = 60; // 許容スプレッド(point) -input int magic_number = 10001; // マジックナンバー -input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) -ulong slippage = 10; // スリッページ - -//--- 主要な定数・設定 -#define POSITION_LIMIT 48 -#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) -#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) -#define HISTORY_BARS 72 -#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す -// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) -#define TARGET_SIZE 13 -#define DEFAULT_TARGET_PRICE 0.0 -#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| EAの状態をまとめる構造体 -//+------------------------------------------------------------------+ - -struct EAState - { - int trend_state; // ★追加:トレンドを格納する変数 - int res_chk; // GPTの回答が正しく取得できたか - double en_price[5]; // エントリー基準価格 - double tp_price[5]; // 利益確定価格 - double sl_price[5]; // ロスカット基準価格 - bool load_trend_flg; // ★変更:トレンドを更新するタイミングか - bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか - int chk_cnt; // エントリー判定の試行回数 - datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 - datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 - datetime last_chk; - }; - -EAState g_ea; // EA全体の状態をここで管理 - -// initial_order 用の初回処理フラグ -// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 -bool g_init_trend_pending = false; -bool g_init_entry_pending = false; - -// H4/H1のバー更新状態 -int g_pre_bars_H4 = 0; -int g_pre_bars_H1 = 0; -bool g_bars_H1_check = false; - -// ティックごとの価格情報 -struct TickContext - { - double ask; - double bid; - double spread; - double spread_points; - int digits; - }; - -// 外部Pythonプロセスの状態 -struct ExternalProcessState - { - long handle; - uint process_id; - bool active; - bool exit_code_ready; - uint exit_code; - datetime started_at; - }; - -ExternalProcessState g_trend_process; -ExternalProcessState g_entry_process; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| 起動時の処理 -//+------------------------------------------------------------------+ -/** - * @brief EA起動時の初期化処理を行います。 - * - * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は - * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 - * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 - */ -void OnInit() -// プロセス完了ファイルと実行中ファイルの状態を初期化 - { - PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); - PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); - - // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 - g_init_trend_pending = (initial_order == 1); - g_init_entry_pending = (initial_order == 1); - } - -//+------------------------------------------------------------------+ -//| 終了時の処理 -//+------------------------------------------------------------------+ -void OnDeinit(const int reason) - { - ResetExternalProcessState(g_trend_process); - ResetExternalProcessState(g_entry_process); - } - -//+------------------------------------------------------------------+ -//| ティック毎の処理 -//+------------------------------------------------------------------+ -/** - * @brief ティック受信ごとにEA全体の処理を制御します。 - * - * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 - * H1エントリー価格更新、ステータス表示、新規注文判定の順です。 - * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 - */ -void OnTick() - { - TickContext ctx; - if(!GetTickContext(ctx)) - return; - - // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 - ManageExpiredTrades(); - - // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 - RecoverTimedOutPythonProcesses(); - - // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 - ProcessTrendUpdate(g_ea); - - // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 - if(!IsTrendResultReady()) - return; - - RefreshTrendState(g_ea); - - // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 - ProcessEntryUpdate(g_ea); - - UpdateStatusComment(ctx); - - // ここから下は新規注文に関する処理。 - if(!IsSpreadAllowed(ctx)) - return; - - if(!IsEntryResultReady()) - return; - - RefreshTargetPrices(g_ea); - ProcessEntryDecisionIfNeeded(g_ea, ctx); - } - -//+------------------------------------------------------------------+ -//| ティック情報を取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief 現在のAsk/Bid/スプレッド情報を取得してTickContextへ格納します。 - * - * @param ctx 取得したティック情報を格納する構造体参照。 - * @return 取得成功時はtrue、ティック情報を取得できない場合はfalse。 - */ -bool GetTickContext(TickContext &ctx) - { - MqlTick last_tick; - if(!SymbolInfoTick(_Symbol, last_tick)) - return false; - - ctx.ask = last_tick.ask; - ctx.bid = last_tick.bid; - ctx.spread_points = MathRound((ctx.ask - ctx.bid) / Point()); - ctx.spread = ctx.spread_points * Point(); - ctx.digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - - return true; - } - -//+------------------------------------------------------------------+ -//| 期限切れ注文・期限切れポジションを処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief 期限切れの未約定注文と保有ポジションを処理します。 - * - * 未約定注文はENTRY_H1_LIMIT時間、保有ポジションはCLOSE_H1_LIMIT時間を基準に判定します。 - * 新規注文条件とは独立して、OnTickの早い段階で実行される想定です。 - */ -void ManageExpiredTrades() - { - if(OrdersTotal() > 0) - CancelExpiredOrders(); - - if(PositionsTotal() > 0) - CloseExpiredPositions(); - } - -//+------------------------------------------------------------------+ -//| H4更新検知とトレンド判定Python起動を処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4新バーまたは初回起動を検知し、トレンド判定Pythonを起動します。 - * - * @param state EA全体の状態。トレンド更新開始フラグを更新します。 - * - * Python処理中はdoneファイルが存在しないため、CSVの上書きと二重起動を抑止します。 - */ -void ProcessTrendUpdate(EAState &state) - { - int current_bars_H4 = iBars(NULL, PERIOD_H4); - if(g_pre_bars_H4 == 0) - g_pre_bars_H4 = current_bars_H4; - - int bars_H4_change = current_bars_H4 - g_pre_bars_H4; - bool trend_trigger = (bars_H4_change > 0 || g_init_trend_pending); - - if(!trend_trigger) - return; - - if(RecordOHLCAndExecuteBatch_Trend(state)) - { - g_init_trend_pending = false; - g_pre_bars_H4 = current_bars_H4; - } - } - -//+------------------------------------------------------------------+ -//| トレンド判定Pythonの完了状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief H4トレンド判定Pythonの完了状態を確認します。 - * - * @return `process_done_trend.txt` が存在する場合はtrue、未完了の場合はfalse。 - */ -bool IsTrendResultReady() - { - return IsProcessResultReady(done_trend_file, running_trend_file, trend_state_file, "trend", g_trend_process); - } - -//+------------------------------------------------------------------+ -//| トレンド判定結果をEA状態へ反映する関数 -//+------------------------------------------------------------------+ -/** - * @brief 完了済みのH4トレンド判定結果をEA状態へ反映します。 - * - * @param state EA全体の状態。`trend_state` と最終更新時刻が更新されます。 - */ -void RefreshTrendState(EAState &state) - { - GetTrendState(state); - } - -//+------------------------------------------------------------------+ -//| H1更新検知とエントリー価格生成Python起動を処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief H1新バーまたは初回起動を検知し、エントリー価格生成Pythonを起動します。 - * - * @param state EA全体の状態。ターゲット価格更新開始フラグや判定リトライ状態を更新します。 - * - * Python処理中はdoneファイルが存在しないため、H1 CSVの上書きと二重起動を抑止します。 - */ -void ProcessEntryUpdate(EAState &state) - { - int current_bars_H1 = iBars(NULL, PERIOD_H1); - if(g_pre_bars_H1 == 0) - g_pre_bars_H1 = current_bars_H1; - - int bars_H1_change = current_bars_H1 - g_pre_bars_H1; - bool entry_trigger = (bars_H1_change > 0 || g_init_entry_pending); - - if(!entry_trigger) - return; - - if(RecordOHLCAndExecuteBatch_Entry(state)) - { - g_init_entry_pending = false; - g_bars_H1_check = true; - state.chk_cnt = 0; - state.last_chk = 0; - g_pre_bars_H1 = current_bars_H1; - } - } - -//+------------------------------------------------------------------+ -//| チャートコメント文字列を作成する関数 -//+------------------------------------------------------------------+ -/** - * @brief チャート左上に表示するステータスメッセージを組み立てます。 - * - * @param ctx 現在のAsk/Bid/スプレッド情報。 - * @return Comment()へ渡す表示用文字列。 - */ -string BuildStatusMessage(TickContext &ctx) - { - string message = StringFormat( - " \nAsk: %." + IntegerToString(ctx.digits) + "f" - "\nBid: %." + IntegerToString(ctx.digits) + "f" - "\nSpread: %." + IntegerToString(ctx.digits) + "f" - "\nSpread Points: %.0f" - "\n\nLast Trend Update:\n %s" - "\n\nTrend State: %d" - "\n\nLast Target Update:\n %s" - "\n\nMy Used Count: %d / %d" - "\n\n[T1 Buy Stop ] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" - "\n[T2 Buy Limit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" - "\n[T3 Sell Stop] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" - "\n[T4 SellLimit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f\n", - ctx.ask, ctx.bid, ctx.spread, ctx.spread_points, - TimeToString(g_ea.last_trend_update, TIME_DATE | TIME_MINUTES), - g_ea.trend_state, - TimeToString(g_ea.last_target_update, TIME_DATE | TIME_MINUTES), - CountMyUsed(), POSITION_LIMIT, - g_ea.en_price[1], g_ea.tp_price[1], g_ea.sl_price[1], - g_ea.en_price[2], g_ea.tp_price[2], g_ea.sl_price[2], - g_ea.en_price[3], g_ea.tp_price[3], g_ea.sl_price[3], - g_ea.en_price[4], g_ea.tp_price[4], g_ea.sl_price[4] - ); - return message; - } - -//+------------------------------------------------------------------+ -//| チャートコメントを更新する関数 -//+------------------------------------------------------------------+ -/** - * @brief チャート上のステータスコメントを更新します。 - * - * @param ctx 現在のAsk/Bid/スプレッド情報。 - */ -void UpdateStatusComment(TickContext &ctx) - { - Comment(BuildStatusMessage(ctx)); - } - -//+------------------------------------------------------------------+ -//| スプレッドが許容範囲内か判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief 現在スプレッドが入力パラメータの許容範囲内か判定します。 - * - * @param ctx 現在のスプレッド情報。 - * @return 許容範囲内ならtrue、超過している場合はfalse。 - */ -bool IsSpreadAllowed(TickContext &ctx) - { - return (ctx.spread <= spread_limit * Point()); - } - -//+------------------------------------------------------------------+ -//| エントリー価格生成Pythonの完了状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief H1エントリー価格生成Pythonの完了状態を確認します。 - * - * @return `process_done_entry.txt` が存在する場合はtrue、未完了の場合はfalse。 - */ -bool IsEntryResultReady() - { - return IsProcessResultReady(done_entry_file, running_entry_file, target_prices_file, "entry", g_entry_process); - } - -//+------------------------------------------------------------------+ -//| ターゲット価格をEA状態へ反映する関数 -//+------------------------------------------------------------------+ -/** - * @brief 完了済みのH1エントリー価格生成結果をEA状態へ反映します。 - * - * @param state EA全体の状態。`res_chk` と各注文タイプのen/tp/slが更新されます。 - */ -void RefreshTargetPrices(EAState &state) - { - GetTargetPrices(state); - } - -//+------------------------------------------------------------------+ -//| エントリー判定が必要な場合のみ注文処理を実行する関数 -//+------------------------------------------------------------------+ -/** - * @brief エントリー判定タイミングに到達している場合のみ注文処理を実行します。 - * - * @param state EA全体の状態。 - * @param ctx 現在のAsk/Bid/スプレッド情報。 - * - * 判定前チェック、許可注文の送信、リトライ状態更新をまとめて制御します。 - */ -void ProcessEntryDecisionIfNeeded(EAState &state, TickContext &ctx) - { - if(!ShouldRunEntryDecision(state)) - return; - - g_bars_H1_check = false; - state.last_chk = TimeCurrent(); - - if(!ValidateEntryPreconditions(state)) - return; - - int sent_success = SendAllowedEntryOrders(state, ctx); - UpdateEntryRetryState(state, sent_success); - } - -//+------------------------------------------------------------------+ -//| エントリー判定を実行するタイミングか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief エントリー判定を実行するタイミングか確認します。 - * - * @param state EA全体の状態。前回判定時刻を参照します。 - * @return H1更新後、かつ前回判定から60秒以上経過していればtrue。 - */ -bool ShouldRunEntryDecision(EAState &state) - { - return (g_bars_H1_check && TimeCurrent() - state.last_chk >= 60); - } - -//+------------------------------------------------------------------+ -//| エントリー判定前の共通チェックを行う関数 -//+------------------------------------------------------------------+ -/** - * @brief 新規注文前の共通条件を検証します。 - * - * @param state EA全体の状態。 - * @return 注文判定を続行できる場合はtrue、停止すべき場合はfalse。 - * - * `res_chk`、`trend_state`、対象EAの注文/ポジション数上限を確認します。 - */ -bool ValidateEntryPreconditions(EAState &state) - { - if(state.res_chk != 1) - { - Print("[Entry Skip] target_prices invalid. res_chk=", state.res_chk); - state.chk_cnt = 0; - return false; - } - - if(state.trend_state == 0) - { - Print("[Entry Skip] trend_state=0 (range). No entry orders are sent."); - state.chk_cnt = 0; - return false; - } - - if(state.trend_state != 1 && state.trend_state != 2) - { - Print("[Entry Skip] invalid trend_state=", state.trend_state); - state.chk_cnt = 0; - return false; - } - - int used = CountMyUsed(); - if(used >= POSITION_LIMIT) - { - Print("Position limit exceeded: used=", used, " limit=", POSITION_LIMIT); - return false; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| 許可された注文タイプだけを順番に送信する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4トレンド方向で許可された注文タイプだけを順番に送信します。 - * - * @param state EA全体の状態。各注文タイプのen/tp/slを参照します。 - * @param ctx 現在のAsk/Bid/スプレッド情報。 - * @return 送信成功した注文数。 - */ -int SendAllowedEntryOrders(EAState &state, TickContext &ctx) - { - int sent_success = 0; - - for(int t = 1; t <= 4; t++) - { - int used = CountMyUsed(); - if(used >= POSITION_LIMIT) - { - Print("Position limit reached while sending. used=", used, " limit=", POSITION_LIMIT); - break; - } - - if(!IsOrderTypeAllowedByTrend(t, state.trend_state)) - { - Print("[Skip] orderType=", t, " not allowed by trend_state=", state.trend_state); - continue; - } - - if(TrySendEntryOrder(t, state, ctx)) - sent_success++; - } - - return sent_success; - } - -//+------------------------------------------------------------------+ -//| 注文タイプ1件分の価格検証と注文送信を行う関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプ1件分の価格検証と注文送信を行います。 - * - * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 - * @param state EA全体の状態。対象注文タイプのen/tp/slを参照します。 - * @param ctx 現在のAsk/Bid情報。 - * @return 注文送信に成功した場合はtrue、それ以外はfalse。 - */ -bool TrySendEntryOrder(const int orderType, EAState &state, TickContext &ctx) - { - string entry_type = EntryTypeName(orderType); - double cur_price = CurrentPriceForOrderType(orderType, ctx); - - double en = state.en_price[orderType]; - double tp = state.tp_price[orderType]; - double sl = state.sl_price[orderType]; - - if(!HasValidTargetPrices(en, tp, sl)) - { - Print("[Skip] invalid target prices. orderType=", orderType, - " en=", en, " tp=", tp, " sl=", sl); - return false; - } - - bool ok = IsTargetPriceOrderConditionMatched(orderType, ctx, en, tp, sl); - if(!ok) - { - Print("[No ", entry_type, "] cur=", cur_price, " en=", en, " tp=", tp, " sl=", sl); - return false; - } - - if(!MeetsTradeDistanceRules(orderType, ctx, en, tp, sl)) - return false; - - Print("[", entry_type, " Order Try at ", cur_price, "] en=", en, " tp=", tp, " sl=", sl); - - if(SendOrder(orderType, en, tp, sl)) - { - Print("[", entry_type, " Order Sent] ticket ok. en=", en, " tp=", tp, " sl=", sl); - return true; - } - - Print("[", entry_type, " Order Failed] en=", en, " tp=", tp, " sl=", sl); - return false; - } - -//+------------------------------------------------------------------+ -//| 注文タイプ名を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプ番号に対応する表示名を返します。 - * - * @param orderType 注文タイプ番号。 - * @return 注文タイプの表示名。不正値の場合は"Unknown"。 - */ -string EntryTypeName(const int orderType) - { - switch(orderType) - { - case 1: - return "Buy Stop"; - case 2: - return "Buy Limit"; - case 3: - return "Sell Stop"; - case 4: - return "Sell Limit"; - default: - return "Unknown"; - } - } - -//+------------------------------------------------------------------+ -//| 注文タイプに応じた現在価格を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプに応じて価格比較に使う現在価格を返します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @return 買い系注文ではAsk、売り系注文ではBid。 - */ -double CurrentPriceForOrderType(const int orderType, TickContext &ctx) - { - if(orderType == 1 || orderType == 2) - return ctx.ask; - - return ctx.bid; - } - -//+------------------------------------------------------------------+ -//| 注文タイプごとの価格整合条件を判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプごとの現在価格・エントリー・TP・SLの大小関係を検証します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @param en エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return 注文タイプの価格条件を満たす場合はtrue。 - */ -bool IsTargetPriceOrderConditionMatched(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) - { - switch(orderType) - { - case 1: // Buy Stop - return (ctx.ask < en && tp > en && sl < en); - - case 2: // Buy Limit - return (ctx.ask > en && tp > en && sl < en); - - case 3: // Sell Stop - return (ctx.bid > en && tp < en && sl > en); - - case 4: // Sell Limit - return (ctx.bid < en && tp < en && sl > en); - - default: - return false; - } - } - -//+------------------------------------------------------------------+ -//| brokerの最小距離制約を満たすか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief pending価格、TP、SLがstop level / freeze levelの最小距離を満たすか判定します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @param en エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return 最小距離を満たす場合はtrue。 - */ -bool MeetsTradeDistanceRules(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) - { - int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); - int freeze_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL); - int min_level = stop_level; - if(freeze_level > min_level) - min_level = freeze_level; - double min_distance = min_level * Point(); - - if(min_distance <= 0.0) - return true; - - double cur_price = CurrentPriceForOrderType(orderType, ctx); - string entry_type = EntryTypeName(orderType); - - if(MathAbs(en - cur_price) < min_distance) - { - Print("[Skip] ", entry_type, " entry is too close. cur=", cur_price, - " en=", en, " min_distance=", min_distance); - return false; - } - - if(MathAbs(tp - en) < min_distance) - { - Print("[Skip] ", entry_type, " TP is too close. en=", en, - " tp=", tp, " min_distance=", min_distance); - return false; - } - - if(MathAbs(en - sl) < min_distance) - { - Print("[Skip] ", entry_type, " SL is too close. en=", en, - " sl=", sl, " min_distance=", min_distance); - return false; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| 注文送信結果に応じてリトライ状態を更新する関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文送信結果に応じてエントリー判定のリトライ状態を更新します。 - * - * @param state EA全体の状態。`chk_cnt` を更新します。 - * @param sent_success 今回送信に成功した注文数。 - * - * 注文が1件も送信されなかった場合、最大10回まで60秒間隔で再判定します。 - */ -void UpdateEntryRetryState(EAState &state, const int sent_success) - { - if(sent_success > 0) - { - state.chk_cnt = 0; - return; - } - - state.chk_cnt += 1; - Print("[Retry] no order sent. chk_cnt=", state.chk_cnt, "/10"); - - if(state.chk_cnt < 10) - { - g_bars_H1_check = true; // 60秒後に再度エントリー判定を実行する - return; - } - - Print("[Retry End] reached max tries. reset chk_cnt."); - state.chk_cnt = 0; - } - -//+------------------------------------------------------------------+ -//| "process_done.txt"を作成する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したdoneファイルを作成します。 - * - * @param name 作成するファイル名。MT5のMQL5\Files配下を基準に扱います。 - */ -void CreateDoneFile(const string name) - { - if(!FileIsExist(name)) - { - int h = FileOpen(name, FILE_WRITE|FILE_TXT); - if(h != INVALID_HANDLE) - FileClose(h); - else - Print("Failed to create file: ", name, " err=", GetLastError()); - } - } - -//+------------------------------------------------------------------+ -//| "process_done.txt"を削除する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したdoneファイルを削除します。 - * - * @param name 削除するファイル名。存在しない場合は何もしません。 - */ -void DeleteDoneFile(const string name) - { - if(FileIsExist(name)) - FileDelete(name); - } - - -//+------------------------------------------------------------------+ -//| "process_done.txt"の存在を確認し、存在する場合はtrueを返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したdoneファイルの存在有無を確認します。 - * - * @param name 確認するファイル名。 - * @return ファイルが存在する場合はtrue、存在しない場合はfalse。 - */ -bool CheckDoneFile(const string name) - { - return FileIsExist(name); - } - -//+------------------------------------------------------------------+ -//| Python実行中ファイルを作成する関数 -//+------------------------------------------------------------------+ -/** - * @brief Python起動時刻を実行中ファイルへ記録します。 - * - * @param name 作成するrunningファイル名。 - */ -void CreateRunningFile(const string name, const uint process_id) - { - int h = FileOpen(name, FILE_WRITE | FILE_TXT); - if(h != INVALID_HANDLE) - { - FileWrite(h, IntegerToString((long)TimeCurrent())); - FileWrite(h, IntegerToString((long)process_id)); - FileClose(h); - } - else - Print("Failed to create running file: ", name, " err=", GetLastError()); - } - -//+------------------------------------------------------------------+ -//| Python実行中ファイルを削除する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したrunningファイルを削除します。 - * - * @param name 削除するrunningファイル名。 - */ -void DeleteRunningFile(const string name) - { - if(FileIsExist(name)) - FileDelete(name); - } - -//+------------------------------------------------------------------+ -//| Python実行開始時刻を読み込む関数 -//+------------------------------------------------------------------+ -/** - * @brief runningファイルに記録されたPython起動時刻を読み込みます。 - * - * @param name 読み込むrunningファイル名。 - * @return 読み込めた起動時刻。失敗時は0。 - */ -datetime LoadRunningStartedAt(const string name) - { - if(!FileIsExist(name)) - return 0; - - int h = FileOpen(name, FILE_READ | FILE_TXT); - if(h == INVALID_HANDLE) - { - Print("Failed to open running file: ", name, " err=", GetLastError()); - return 0; - } - - string line = FileReadString(h); - FileClose(h); - return (datetime)StringToInteger(line); - } - -//+------------------------------------------------------------------+ -//| Python実行プロセスIDを読み込む関数 -//+------------------------------------------------------------------+ -/** - * @brief runningファイルに記録されたプロセスIDを読み込みます。 - * - * @param name 読み込むrunningファイル名。 - * @return 読み込めたプロセスID。旧形式または失敗時は0。 - */ -uint LoadRunningProcessId(const string name) - { - if(!FileIsExist(name)) - return 0; - - int h = FileOpen(name, FILE_READ | FILE_TXT); - if(h == INVALID_HANDLE) - { - Print("Failed to open running file: ", name, " err=", GetLastError()); - return 0; - } - - FileReadString(h); // started_at - if(FileIsEnding(h)) - { - FileClose(h); - return 0; - } - - string line = FileReadString(h); - FileClose(h); - return (uint)StringToInteger(line); - } - -//+------------------------------------------------------------------+ -//| Python実行中ファイルがタイムアウトしているか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief runningファイルの起動時刻からPython待ち上限を超えているか判定します。 - * - * @param name 判定するrunningファイル名。 - * @return タイムアウトしている場合はtrue。 - */ -bool IsRunningFileTimedOut(const string name) - { - datetime started_at = LoadRunningStartedAt(name); - if(started_at <= 0) - return true; - - return (TimeCurrent() - started_at >= PYTHON_TIMEOUT_SECONDS); - } - -//+------------------------------------------------------------------+ -//| 外部プロセス状態を初期値に戻す関数 -//+------------------------------------------------------------------+ -void ResetExternalProcessState(ExternalProcessState &process) - { - if(process.handle != 0) - CloseHandle(process.handle); - - process.handle = 0; - process.process_id = 0; - process.active = false; - process.exit_code_ready = false; - process.exit_code = 0; - process.started_at = 0; - } - -//+------------------------------------------------------------------+ -//| runningファイルのPIDからプロセスハンドルを復元する関数 -//+------------------------------------------------------------------+ -bool AttachRunningProcess(const string running_file, const string label, ExternalProcessState &process) - { - if(process.active && process.handle != 0) - return true; - - uint process_id = LoadRunningProcessId(running_file); - if(process_id == 0) - return false; - - long handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, 0, process_id); - if(handle == 0) - { - Print("[", label, "] failed to open running process. pid=", process_id, " err=", GetLastError()); - return false; - } - - process.handle = handle; - process.process_id = process_id; - process.active = true; - process.exit_code_ready = false; - process.exit_code = STILL_ACTIVE; - process.started_at = LoadRunningStartedAt(running_file); - return true; - } - -//+------------------------------------------------------------------+ -//| 外部プロセスの終了状態を更新する関数 -//+------------------------------------------------------------------+ -bool UpdateExternalProcessStatus(ExternalProcessState &process, const string label) - { - if(!process.active || process.handle == 0) - return true; - - int wait_result = WaitForSingleObject(process.handle, 0); - if(wait_result == WAIT_TIMEOUT) - return false; - - uint exit_code = 1; - if(wait_result == WAIT_OBJECT_0) - { - if(GetExitCodeProcess(process.handle, exit_code) == 0) - { - Print("[", label, "] failed to get process exit code. err=", GetLastError()); - exit_code = 1; - } - } - else - { - Print("[", label, "] WaitForSingleObject failed. result=", wait_result, " err=", GetLastError()); - exit_code = 1; - } - - CloseHandle(process.handle); - process.handle = 0; - process.active = false; - process.exit_code_ready = true; - process.exit_code = exit_code; - Print("[", label, "] process finished. pid=", process.process_id, " exit_code=", exit_code); - return true; - } - -//+------------------------------------------------------------------+ -//| 外部プロセスがタイムアウトしているか判定する関数 -//+------------------------------------------------------------------+ -bool IsExternalProcessTimedOut(ExternalProcessState &process, const string running_file) - { - datetime started_at = process.started_at; - if(started_at <= 0) - started_at = LoadRunningStartedAt(running_file); - if(started_at <= 0) - return true; - - return (TimeCurrent() - started_at >= PYTHON_TIMEOUT_SECONDS); - } - -//+------------------------------------------------------------------+ -//| バッチファイルをプロセスハンドル付きで起動する関数 -//+------------------------------------------------------------------+ -bool StartBatchProcess(const string bat_file, const string running_file, const string label, ExternalProcessState &process) - { - STARTUPINFO_W startup_info = {}; - PROCESS_INFORMATION process_info = {}; - - startup_info.cb = (uint)sizeof(startup_info); - - string cmd_exe = "C:\\Windows\\System32\\cmd.exe"; - string command_line = "\"" + cmd_exe + "\" /c \"\"" + bat_file + "\"\""; - - int created = CreateProcessW( - cmd_exe, - command_line, - 0, - 0, - 0, - CREATE_NO_WINDOW, - 0, - "C:\\ea_py", - startup_info, - process_info - ); - - if(created == 0 || process_info.hProcess == 0) - { - Print("[", label, "] CreateProcessW failed. file=", bat_file, " err=", GetLastError()); - ResetExternalProcessState(process); - DeleteRunningFile(running_file); - return false; - } - - if(process_info.hThread != 0) - CloseHandle(process_info.hThread); - - process.handle = process_info.hProcess; - process.process_id = process_info.dwProcessId; - process.active = true; - process.exit_code_ready = false; - process.exit_code = STILL_ACTIVE; - process.started_at = TimeCurrent(); - - CreateRunningFile(running_file, process.process_id); - Print("[", label, "] process started. pid=", process.process_id, " file=", bat_file); - return true; - } - -//+------------------------------------------------------------------+ -//| EA起動時のdoneファイル状態を整える関数 -//+------------------------------------------------------------------+ -/** - * @brief EA起動時にPython実行中状態を壊さない範囲でdoneファイルを準備します。 - * - * @param done_file 完了判定ファイル名。 - * @param running_file 実行中判定ファイル名。 - * @param label ログ表示用ラベル。 - */ -void PrepareDoneFileOnInit(const string done_file, const string running_file, const string label) - { - if(CheckDoneFile(done_file)) - { - DeleteRunningFile(running_file); - return; - } - - if(FileIsExist(running_file)) - { - if(IsRunningFileTimedOut(running_file)) - { - Print("[", label, "] stale running file found on init. Remove marker without creating done."); - DeleteRunningFile(running_file); - } - else - Print("[", label, "] Python seems to be running on init. Keep waiting."); - return; - } - - Print("[", label, "] done file not found on init. Keep as not-ready until the next trigger."); - } - -//+------------------------------------------------------------------+ -//| Python完了状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief doneファイルがあれば完了扱いにし、runningファイルを片付けます。 - * - * @param done_file 完了判定ファイル名。 - * @param running_file 実行中判定ファイル名。 - * @return 完了済みの場合はtrue。 - */ -bool IsProcessResultReady(const string done_file, const string running_file, const string result_file, const string label, ExternalProcessState &process) - { - if(process.active || FileIsExist(running_file)) - { - if(process.active || AttachRunningProcess(running_file, label, process)) - { - if(!UpdateExternalProcessStatus(process, label)) - return false; - - DeleteRunningFile(running_file); - if(process.exit_code_ready && process.exit_code != 0) - { - Print("[", label, "] process failed. exit_code=", process.exit_code); - return false; - } - } - else if(!CheckDoneFile(done_file)) - { - return false; - } - } - - if(!CheckDoneFile(done_file)) - return false; - - if(!FileIsExist(result_file)) - { - Print("[", label, "] done exists but result file is missing: ", result_file); - return false; - } - - DeleteRunningFile(running_file); - return true; - } - -//+------------------------------------------------------------------+ -//| Python開始可能状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief done/runningファイル状態からPythonを新規起動できるか判定します。 - * - * @param done_file 完了判定ファイル名。 - * @param running_file 実行中判定ファイル名。 - * @param label ログ表示用ラベル。 - * @return 起動可能な場合はtrue。 - */ -bool IsProcessStartAllowed(const string done_file, const string running_file, const string label, ExternalProcessState &process) - { - if(process.active || FileIsExist(running_file)) - { - if(process.active || AttachRunningProcess(running_file, label, process)) - { - if(!UpdateExternalProcessStatus(process, label)) - { - if(IsExternalProcessTimedOut(process, running_file)) - Print("[", label, "] Python timed out, but the process is still running. Keep waiting."); - else - Print("[", label, "] Python is still running. Skip execute."); - return false; - } - - DeleteRunningFile(running_file); - if(process.exit_code_ready && process.exit_code != 0) - Print("[", label, "] previous process failed. exit_code=", process.exit_code, ". Retry is allowed."); - } - else - { - if(CheckDoneFile(done_file)) - { - DeleteRunningFile(running_file); - return true; - } - - if(IsRunningFileTimedOut(running_file)) - { - Print("[", label, "] stale running marker without live process. Retry is allowed."); - DeleteRunningFile(running_file); - return true; - } - - Print("[", label, "] running marker exists, but process cannot be verified yet. Skip execute."); - return false; - } - } - - if(CheckDoneFile(done_file)) - { - DeleteRunningFile(running_file); - return true; - } - - Print("[", label, "] done file missing without running marker. Start new process."); - return true; - } - -//+------------------------------------------------------------------+ -//| タイムアウトしたPython待ちを復旧する関数 -//+------------------------------------------------------------------+ -/** - * @brief Pythonがdoneファイルを返さない状態を検知し、再実行できる状態へ戻します。 - */ -void RecoverTimedOutPythonProcesses() - { - if(RecoverTimedOutProcess(done_trend_file, running_trend_file, "trend", g_trend_process)) - { - g_ea.load_trend_flg = false; - g_init_trend_pending = true; - } - - if(RecoverTimedOutProcess(done_entry_file, running_entry_file, "entry", g_entry_process)) - { - g_ea.load_target_flg = false; - g_bars_H1_check = false; - g_ea.chk_cnt = 0; - g_init_entry_pending = true; - } - } - -//+------------------------------------------------------------------+ -//| 個別Python処理のタイムアウトを復旧する関数 -//+------------------------------------------------------------------+ -/** - * @brief 1種類のPython処理について、実行中ファイルのタイムアウトを検知します。 - * - * @param done_file 完了判定ファイル名。 - * @param running_file 実行中判定ファイル名。 - * @param label ログ表示用ラベル。 - * @return タイムアウト復旧を行った場合はtrue。 - */ -bool RecoverTimedOutProcess(const string done_file, const string running_file, const string label, ExternalProcessState &process) - { - if(CheckDoneFile(done_file) && !process.active) - { - DeleteRunningFile(running_file); - return false; - } - - if(!process.active && !FileIsExist(running_file)) - return false; - - if(process.active || AttachRunningProcess(running_file, label, process)) - { - if(!UpdateExternalProcessStatus(process, label)) - { - if(IsExternalProcessTimedOut(process, running_file)) - Print("[", label, "] Python exceeded ", PYTHON_TIMEOUT_SECONDS, - " seconds, but the process is still running. Keep waiting."); - return false; - } - - DeleteRunningFile(running_file); - if(process.exit_code_ready && process.exit_code != 0) - { - Print("[", label, "] Python process failed. Retry on next trigger."); - return true; - } - - if(!CheckDoneFile(done_file)) - { - Print("[", label, "] Python process ended without done file. Retry on next trigger."); - return true; - } - - return false; - } - - if(!IsRunningFileTimedOut(running_file)) - return false; - - Print("[", label, "] Python did not finish within ", - PYTHON_TIMEOUT_SECONDS, " seconds, and no live process was found. Retry is allowed."); - DeleteRunningFile(running_file); - return true; - } - -//+------------------------------------------------------------------+ -//| 最新のOHLCデータを取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定時間足のOHLCデータを取得します。 - * - * @param times 取得したバー時刻を格納する配列。 - * @param open_prices 取得したOpen価格を格納する配列。 - * @param high_prices 取得したHigh価格を格納する配列。 - * @param low_prices 取得したLow価格を格納する配列。 - * @param close_prices 取得したClose価格を格納する配列。 - * @param tf 取得対象の時間足。 - * @param bars_count 取得するバー本数。 - * @return 取得に成功した場合はtrue、失敗した場合はfalse。 - * - * `OHLC_START_SHIFT=1` のため、形成中のバーではなく確定足から取得します。 - */ -bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count) - { - ArrayResize(times, 0); - ArrayResize(open_prices, 0); - ArrayResize(high_prices, 0); - ArrayResize(low_prices, 0); - ArrayResize(close_prices, 0); - - if(bars_count < 2) - return false; - -// OHLCデータを取得 -// CopyTime と CopyRates を別々に呼ばず、MqlRates の time/open/high/low/close を同一配列から取得する。 -// OHLC_START_SHIFT=1 のため、形成中の0本目ではなく確定足からPythonへ渡す。 - MqlRates rates[]; - int copied = CopyRates(_Symbol, tf, OHLC_START_SHIFT, bars_count, rates); - if(copied <= 0) - { - Print(__FUNCTION__, ": Failed to copy rates data (bars=", bars_count, ") err=", GetLastError()); - return false; - } - - ArrayResize(times, copied); - ArrayResize(open_prices, copied); - ArrayResize(high_prices, copied); - ArrayResize(low_prices, copied); - ArrayResize(close_prices, copied); - -// 必要なデータを配列に格納 - for(int i = 0; i < copied; i++) - { - times[i] = rates[i].time; - open_prices[i] = rates[i].open; - high_prices[i] = rates[i].high; - low_prices[i] = rates[i].low; - close_prices[i] = rates[i].close; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| "ohlc.csv"を出力する関数 -//+------------------------------------------------------------------+ -/** - * @brief OHLC配列をPython入力用CSVとして出力します。 - * - * @param filename 出力するCSVファイル名。 - * @param times バー時刻配列。 - * @param open_prices Open価格配列。 - * @param high_prices High価格配列。 - * @param low_prices Low価格配列。 - * @param close_prices Close価格配列。 - * @return CSV出力に成功した場合はtrue。 - */ -bool RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[]) - { -// 出力先ファイル名 -// string filename = "ohlc.csv"; - -// ファイルを "書き込みモード" でオープン (テキスト/ANSI) - int fileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI); - -// ファイルが開けたかチェック - if(fileHandle == INVALID_HANDLE) - { - Print(__FUNCTION__, " : Failed to open file: ", GetLastError()); - return false; - } - -// 配列サイズを取得 (times, open, high, low, close の要素数は同じ前提) - int size = ArraySize(times); - -// ヘッダー行を追加 - FileWrite(fileHandle, "Time,Open,High,Low,Close"); - -// 小数点の桁数を取得 - int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - -// フォーマット文字列を動的に生成 - string formatString = StringFormat("%%s,%%.%df,%%.%df,%%.%df,%%.%df", digits, digits, digits, digits); - -// 1行ずつ "時刻, Open, High, Low, Close" の形式で書き込み - for(int i = 0; i < size; i++) - { - // 時間をフォーマット - string timeStr = TimeToString(times[i], TIME_DATE | TIME_MINUTES); - - // 1行分の文字列を生成 (小数点以下の桁数を `digits` に調整) - string line = StringFormat(formatString, - timeStr, open_prices[i], high_prices[i], low_prices[i], close_prices[i]); - - // ファイルに書き込み (改行付き) - FileWrite(fileHandle, line); - } - -// 書き込み終了後、ファイルを閉じる - FileClose(fileHandle); - return true; - } - -//+------------------------------------------------------------------+ -//| バッチファイル(Pythonスクリプト)を実行する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4トレンド判定用バッチファイルを起動します。 - * - * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 - * - * 起動直前に`process_done_trend.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。 - */ -bool ExecuteBatchTrend() - { - if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process)) - return false; - - DeleteDoneFile(done_trend_file); - return StartBatchProcess(get_trend_reply_bat, running_trend_file, "trend", g_trend_process); - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief H1エントリー価格生成用バッチファイルを起動します。 - * - * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 - * - * 起動直前に`process_done_entry.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。 - */ -bool ExecuteBatchEntry() - { - if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process)) - return false; - - DeleteDoneFile(done_entry_file); - return StartBatchProcess(get_entry_reply_bat, running_entry_file, "entry", g_entry_process); - } - - -//+------------------------------------------------------------------+ -//| "ohlc.csv"を出力後、バッチファイルの実行する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4 OHLCをCSV出力し、トレンド判定Pythonを起動します。 - * - * @param state EA全体の状態。トレンド読込待ちフラグを更新します。 - * @return CSV出力とバッチ起動が開始できた場合はtrue。 - */ -bool RecordOHLCAndExecuteBatch_Trend(EAState &state) - { - if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process)) - { - Print("Trend Python is still running. Skip H4 CSV update."); - return false; - } - - datetime times[]; - double open_prices[], high_prices[], low_prices[], close_prices[]; - - if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, - PERIOD_H4, HISTORY_BARS)) - { Print("GetLatestOHLC(H4) failed."); return false; } - - if(!RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices)) - { Print("RecordOHLC(H4) failed."); return false; } - - if(!ExecuteBatchTrend()) - return false; - - state.load_trend_flg = true; - return true; - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief H1 OHLCをCSV出力し、エントリー価格生成Pythonを起動します。 - * - * @param state EA全体の状態。ターゲット価格読込待ちフラグを更新します。 - * @return CSV出力とバッチ起動が開始できた場合はtrue。 - */ -bool RecordOHLCAndExecuteBatch_Entry(EAState &state) - { - if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process)) - { - Print("Entry Python is still running. Skip H1 CSV update."); - return false; - } - - datetime times[]; - double open_prices[], high_prices[], low_prices[], close_prices[]; - - if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, - PERIOD_H1, HISTORY_BARS)) - { Print("GetLatestOHLC(H1) failed."); return false; } - - if(!RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices)) - { Print("RecordOHLC(H1) failed."); return false; } - - if(!ExecuteBatchEntry()) - return false; - - state.load_target_flg = true; - return true; - } - - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief Pythonが出力したトレンド判定結果を必要なタイミングで読み込みます。 - * - * @param state EA全体の状態。`trend_state` と更新フラグを更新します。 - * @return 現状は常にtrue。 - */ -bool GetTrendState(EAState &state) - { - bool ProcessDone = CheckDoneFile(done_trend_file); - if(ProcessDone && g_ea.load_trend_flg) - { - int trend_state; - LoadTrendState(trend_state); - state.trend_state = trend_state; - Print("trend_state: ", state.trend_state); - g_ea.load_trend_flg = false; - g_ea.last_trend_update = TimeLocal(); - } - return true; - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief `trend_state.txt` からH4トレンド判定値を読み込みます。 - * - * @param trend_state 読み込んだ値を格納する参照。0=RANGE、1=UP、2=DOWN。 - * - * ファイル未存在、読込失敗、異常値の場合は安全側として0を設定します。 - */ -void LoadTrendState(int &trend_state) - { - string filename = trend_state_file; - trend_state = 0; - - if(FileIsExist(filename)) - { - int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); - if(filehandle != INVALID_HANDLE) - { - string line = FileReadString(filehandle); - int value = (int)StringToInteger(line); - if(value == 0 || value == 1 || value == 2) - trend_state = value; - else - Print("Invalid trend_state value: ", line, ". Use 0."); - FileClose(filehandle); - } - else - { Print("Failed to open trend_state.txt"); trend_state = 0; } - } - else - { Print("trend_state.txt not found"); trend_state = 0; } - } - -//+------------------------------------------------------------------+ -//| ターゲット価格を取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief Pythonが出力したエントリー価格群を必要なタイミングで読み込みます。 - * - * @param state EA全体の状態。`res_chk` と4タイプ分のen/tp/slを更新します。 - * @return 現状は常にtrue。 - */ -bool GetTargetPrices(EAState &state) - { - bool ProcessDone = CheckDoneFile(done_entry_file); // ←変更 - if(ProcessDone && g_ea.load_target_flg) // ←変更 - { - double target_prices[]; - LoadTargetPrices(target_prices); - - int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - - // 先頭は res_chk - state.res_chk = (int)target_prices[0]; - - // ★ 1..4 の (en,tp,sl) を読む:合計12個 - for(int t=1; t<=4; t++) - { - int base = 1 + (t-1)*3; // 1,4,7,10 - state.en_price[t] = NormalizeDouble(target_prices[base + 0], digits); - state.tp_price[t] = NormalizeDouble(target_prices[base + 1], digits); - state.sl_price[t] = NormalizeDouble(target_prices[base + 2], digits); - } - - // ログ(任意) - Print("target_prices: res=", state.res_chk, - " | T1 en=", state.en_price[1], " tp=", state.tp_price[1], " sl=", state.sl_price[1], - " | T2 en=", state.en_price[2], " tp=", state.tp_price[2], " sl=", state.sl_price[2], - " | T3 en=", state.en_price[3], " tp=", state.tp_price[3], " sl=", state.sl_price[3], - " | T4 en=", state.en_price[4], " tp=", state.tp_price[4], " sl=", state.sl_price[4]); - - g_ea.load_target_flg = false; // ←変更 - g_ea.last_target_update = TimeLocal(); // ←変更 - } - return true; - } -//+------------------------------------------------------------------+ -//+------------------------------------------------------------------+ -//| "target_prices.txt"を読み込む関数 -//+------------------------------------------------------------------+ -/** - * @brief `target_prices.txt` から13個の数値を読み込みます。 - * - * @param target_prices 読み込んだ数値を格納する配列。先頭がres_chk、以降は4タイプ分のen/tp/sl。 - * - * 行数不足の場合は`res_chk=0`として、ターゲット価格を無効扱いにします。 - */ -void LoadTargetPrices(double &target_prices[]) - { - ArrayResize(target_prices, TARGET_SIZE); - for(int i = 0; i < TARGET_SIZE; i++) - target_prices[i] = DEFAULT_TARGET_PRICE; - - string filename = target_prices_file; - - if(FileIsExist(filename)) - { - int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); - if(filehandle != INVALID_HANDLE) - { - int i = 0; - while(!FileIsEnding(filehandle) && i < TARGET_SIZE) - { - string line = FileReadString(filehandle); - target_prices[i] = StringToDouble(line); - i++; - } - FileClose(filehandle); - - if(i < TARGET_SIZE) - { - Print("target_prices.txt line count is short. loaded=", i, " required=", TARGET_SIZE); - target_prices[0] = 0; // 不完全なファイルは無効扱い - } - } - else - { - Print("Failed to open file. Error code: ", GetLastError()); - } - } - else - { - Print("File does not exist: ", filename); - } - } - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit) -//+------------------------------------------------------------------+ -/** - * @brief 指定された注文タイプでペンディング注文を送信します。 - * - * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 - * @param price エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return 注文が正常に受理された場合はtrue、それ以外はfalse。 - */ -bool SendOrder(int orderType, double price, double tp, double sl) - { - MqlTradeRequest request = {}; - MqlTradeResult result = {}; - - request.magic = magic_number; - request.symbol = _Symbol; - request.volume = lot_size; - request.deviation = slippage; - request.type_filling = GetOrderFillingPolicy(_Symbol); - request.price = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.comment = RequestComment(orderType); - - string orderTypeStr = ""; - -// 指値または逆指値注文 - request.action = TRADE_ACTION_PENDING; - ApplyPendingOrderExpiration(request); - - switch(orderType) - { - case 1: // 順張り買い (buy-stop) - request.type = ORDER_TYPE_BUY_STOP; - orderTypeStr = "buy-stop"; - break; - - case 2: // 逆張り買い (buy-limit) - request.type = ORDER_TYPE_BUY_LIMIT; - orderTypeStr = "buy-limit"; - break; - - case 3: // 順張り売り (sell-stop) - request.type = ORDER_TYPE_SELL_STOP; - orderTypeStr = "sell-stop"; - break; - - case 4: // 逆張り売り (sell-limit) - request.type = ORDER_TYPE_SELL_LIMIT; - orderTypeStr = "sell-limit"; - break; - - default: // 不正なorderType - Print(__FUNCTION__, ": invalid orderType=", orderType); - return false; - } - -// 注文送信 - if(!OrderSend(request, result)) - { - Print(__FUNCTION__, ": OrderSend failed. err=", GetLastError()); - return false; - } - -// 注文成功確認 - if(result.retcode == TRADE_RETCODE_PLACED || - result.retcode == TRADE_RETCODE_DONE || - result.retcode == TRADE_RETCODE_DONE_PARTIAL) - { - Print(request.comment + " - " + orderTypeStr + " order success (ticket=", result.order, ")"); - return true; - } - else - { - Print(__FUNCTION__, ": OrderSend retcode=", result.retcode); - return false; - } - } - -//+------------------------------------------------------------------+ -//| 注文の執行ポリシーを取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief シンボルに対応する注文執行ポリシーを取得します。 - * - * @param symbol 対象シンボル。 - * @return 利用可能な執行ポリシー。IOC、FOK、RETURNの順で選択します。 - */ -ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol) - { - long fill_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); - if((fill_mode & SYMBOL_FILLING_IOC) != 0) - return ORDER_FILLING_IOC; - if((fill_mode & SYMBOL_FILLING_FOK) != 0) - return ORDER_FILLING_FOK; - return ORDER_FILLING_RETURN; - } - -//+------------------------------------------------------------------+ -//| ペンディング注文の有効期限を設定する関数 -//+------------------------------------------------------------------+ -/** - * @brief brokerが対応している範囲でpending orderにサーバー側の期限を設定します。 - * - * @param request 更新対象の取引リクエスト。 - */ -void ApplyPendingOrderExpiration(MqlTradeRequest &request) - { - int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); - long expiration_mode = SymbolInfoInteger(request.symbol, SYMBOL_EXPIRATION_MODE); - - if(expiration_seconds > 0 && - (expiration_mode & SYMBOL_EXPIRATION_SPECIFIED) != 0) - { - request.type_time = ORDER_TIME_SPECIFIED; - request.expiration = TimeCurrent() + expiration_seconds; - return; - } - - if(expiration_seconds > 0 && - (expiration_mode & SYMBOL_EXPIRATION_SPECIFIED_DAY) != 0) - { - request.type_time = ORDER_TIME_SPECIFIED_DAY; - request.expiration = TimeCurrent() + expiration_seconds; - return; - } - - if((expiration_mode & SYMBOL_EXPIRATION_DAY) != 0) - { - request.type_time = ORDER_TIME_DAY; - return; - } - - request.type_time = ORDER_TIME_GTC; - } - -//+------------------------------------------------------------------+ -//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与) -//+------------------------------------------------------------------+ -/** - * @brief 注文コメント文字列を生成します。 - * - * @param orderType 注文タイプ番号。 - * @return 注文タイプとPCローカル時刻を含むコメント文字列。 - */ -string RequestComment(int orderType) - { - datetime localTime = TimeLocal(); - string timeStr = TimeToString(localTime, TIME_DATE|TIME_MINUTES); - -// 先頭に 1,2,3,4 を付ける(例: "1 | PC Time: 2026.02.14 19:05") - return IntegerToString(orderType) + " | PC Time: " + timeStr; - } - -//+------------------------------------------------------------------+ -//| H4トレンド方向に対して許可された注文タイプか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4トレンド方向に対して指定注文タイプが許可されるか判定します。 - * - * @param orderType 注文タイプ番号。 - * @param trend_state H4トレンド判定。0=RANGE、1=UP、2=DOWN。 - * @return 許可される注文タイプならtrue。レンジまたは異常値ではfalse。 - */ -bool IsOrderTypeAllowedByTrend(const int orderType, const int trend_state) - { - if(trend_state == 1) - return (orderType == 1 || orderType == 2); // UP: Buy Stop / Buy Limit のみ - - if(trend_state == 2) - return (orderType == 3 || orderType == 4); // DOWN: Sell Stop / Sell Limit のみ - - return false; // RANGEまたは異常値は新規注文しない - } - -//+------------------------------------------------------------------+ -//| target_prices.txt の価格が有効か判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief エントリー価格、利確価格、損切価格が有効値か判定します。 - * - * @param en エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return すべて0より大きい場合はtrue。 - */ -bool HasValidTargetPrices(const double en, const double tp, const double sl) - { - return (en > 0.0 && tp > 0.0 && sl > 0.0); - } - -//+------------------------------------------------------------------+ -//| 自EAの未約定注文数を数える関数 -//+------------------------------------------------------------------+ -/** - * @brief このEAが管理対象とする未約定注文数を数えます。 - * - * @return `_Symbol` と `magic_number` が一致する未約定注文数。 - */ -int CountMyPendingOrders() - { - int count = 0; - - for(int i = OrdersTotal() - 1; i >= 0; i--) - { - ulong ticket = OrderGetTicket(i); - if(ticket == 0) - continue; - - if(!OrderSelect(ticket)) - continue; - - if(OrderGetString(ORDER_SYMBOL) != _Symbol) - continue; - - if((int)OrderGetInteger(ORDER_MAGIC) != magic_number) - continue; - - count++; - } - - return count; - } - -//+------------------------------------------------------------------+ -//| 自EAのポジション数を数える関数 -//+------------------------------------------------------------------+ -/** - * @brief このEAが管理対象とする保有ポジション数を数えます。 - * - * @return `_Symbol` と `magic_number` が一致するポジション数。 - */ -int CountMyPositions() - { - int count = 0; - - for(int i = PositionsTotal() - 1; i >= 0; i--) - { - string symbol = PositionGetSymbol(i); - if(symbol == "") - continue; - - if(symbol != _Symbol) - continue; - - if((int)PositionGetInteger(POSITION_MAGIC) != magic_number) - continue; - - count++; - } - - return count; - } - -//+------------------------------------------------------------------+ -//| 自EAの注文+ポジション数を数える関数 -//+------------------------------------------------------------------+ -/** - * @brief このEAが使用中の注文数とポジション数の合計を返します。 - * - * @return 未約定注文数 + 保有ポジション数。 - */ -int CountMyUsed() - { - return CountMyPendingOrders() + CountMyPositions(); - } - - -//+------------------------------------------------------------------+ -//| 時間が経過したエントリー注文をキャンセルする関数 -//+------------------------------------------------------------------+ -/** - * @brief 実時間でENTRY_H1_LIMIT時間を超えた未約定注文をキャンセルします。 - * - * @return 1件以上キャンセルに成功した場合はtrue。 - * - * 対象は`_Symbol` と `magic_number` が一致する未約定注文のみです。 - */ -bool CancelExpiredOrders() - { - bool result = false; - -// 未決済注文の数を取得 - for(int i = OrdersTotal() - 1; i >= 0; i--) - { - ulong ticket = OrderGetTicket(i); - if(OrderSelect(ticket)) // 注文を選択 - { - if(OrderGetString(ORDER_SYMBOL) != _Symbol) - continue; - if(OrderGetInteger(ORDER_MAGIC) != magic_number) - continue; - - datetime open_time = (datetime)OrderGetInteger(ORDER_TIME_SETUP); - - int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); - if(open_time <= 0 || expiration_seconds <= 0) - continue; - - if(TimeCurrent() - open_time >= expiration_seconds) - { - MqlTradeRequest request = {}; - MqlTradeResult trade_result = {}; - request.action=TRADE_ACTION_REMOVE; // 取引操作タイプ - request.order = ticket; // 注文チケット - if(!OrderSend(request, trade_result)) // 削除リクエスト送信 - { - Print("Failed to delete order. Ticket: ", ticket, " Error: ", GetLastError()); - } - else - { - if(trade_result.retcode == TRADE_RETCODE_DONE) - { - Print("Order canceled successfully due to a time limit. Ticket: ", ticket); - result = true; // 削除成功フラグ - } - else - { - Print("Order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode); - } - } - } - } - } - return result; - } - -//+------------------------------------------------------------------+ -//| 時間が経過したポジションをクローズする関数 -//+------------------------------------------------------------------+ -/** - * @brief 実時間でCLOSE_H1_LIMIT時間を超えた保有ポジションをクローズします。 - * - * @return 1件以上クローズに成功した場合はtrue。 - * - * 対象は`_Symbol` と `magic_number` が一致するポジションのみです。 - */ -bool CloseExpiredPositions() - { - bool result = false; - -// ポジションを逆順でループ - for(int i = PositionsTotal() - 1; i >= 0; i--) - { - string position_symbol = PositionGetSymbol(i); - if(position_symbol!="") - { - // Magic Number とシンボルでフィルタリング - if(position_symbol != _Symbol) - continue; - if(PositionGetInteger(POSITION_MAGIC) == magic_number) - { - // ポジションのエントリー時刻を取得 - datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); - - int expiration_seconds = CLOSE_H1_LIMIT * PeriodSeconds(PERIOD_H1); - if(entry_time <= 0 || expiration_seconds <= 0) - continue; - - if(TimeCurrent() - entry_time >= expiration_seconds) - { - MqlTradeRequest request = {}; - MqlTradeResult trade_result = {}; - - // ポジションタイプに応じてリクエストを設定 - int position_type = (int)PositionGetInteger(POSITION_TYPE); - request.action = TRADE_ACTION_DEAL; - request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号 - request.symbol = position_symbol; // シンボル - request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ - request.price = (position_type == POSITION_TYPE_BUY) - ? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ - : SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ - request.deviation = slippage; - request.type = (position_type == POSITION_TYPE_BUY) - ? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ - : ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ - request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー - request.magic = magic_number; - - // 注文送信 - if(!OrderSend(request, trade_result)) - { - Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError()); - continue; - } - - // 結果の確認 - if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL) - { - Print("Position closed successfully due to a time limit. Ticket: ", request.position); - result = true; // 少なくとも1つ成功した場合 - } - else - { - Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode); - } - } - } - } - else - { - Print("Failed to select position at index ", i, ". Error: ", GetLastError()); - } - } - return result; - } - - -//+------------------------------------------------------------------+ -//| 指定された時間に最も近いバーのインデックスを返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定時刻に最も近いバーシフトを取得します。 - * - * @param time 判定対象の時刻。 - * @param tf 判定対象の時間足。 - * @param default_shift 取得失敗時に返す値。 - * @return 取得できたバーシフト。失敗時はdefault_shift。 - * - * `iBarShift(..., false)` を使い、厳密一致ではなく近傍バーで解決します。 - */ -int GetBarShiftByTime(datetime time, ENUM_TIMEFRAMES tf, int default_shift = -1) - { - if(time <= 0) - return default_shift; - int shift = iBarShift(_Symbol, tf, time, false); // 近傍バーで解決する - if(shift < 0) - return default_shift; - return shift; - } -//+------------------------------------------------------------------+ diff --git a/Experts/MyProject/HIT-EA_refactor_ver3.mq5 b/Experts/MyProject/HIT-EA_refactor_ver3.mq5 deleted file mode 100644 index 958a263..0000000 --- a/Experts/MyProject/HIT-EA_refactor_ver3.mq5 +++ /dev/null @@ -1,2377 +0,0 @@ -//+------------------------------------------------------------------+ -//| HIT_EA.mq5 | -//| Copyright 2026, nanpin-martin.com | -//| https://www.nanpin-martin.com | -//+------------------------------------------------------------------+ -#property copyright "Copyright 2026, nanpin-martin.com" -#property link "https://nanpin-martin.com/" -#property version "1.01" - -//+------------------------------------------------------------------+ -//| バッチファイル実行用の設定 -//+------------------------------------------------------------------+ - -// Windowsプロセス起動・終了確認用 -#define CREATE_NO_WINDOW 0x08000000 -#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 -#define SYNCHRONIZE 0x00100000 -#define WAIT_OBJECT_0 0 -#define WAIT_TIMEOUT 258 -#define STILL_ACTIVE 259 - -struct STARTUPINFO_W - { - uint cb; - string lpReserved; - string lpDesktop; - string lpTitle; - uint dwX; - uint dwY; - uint dwXSize; - uint dwYSize; - uint dwXCountChars; - uint dwYCountChars; - uint dwFillAttribute; - uint dwFlags; - ushort wShowWindow; - ushort cbReserved2; - long lpReserved2; - long hStdInput; - long hStdOutput; - long hStdError; - }; - -struct PROCESS_INFORMATION - { - long hProcess; - long hThread; - uint dwProcessId; - uint dwThreadId; - }; - -#import "kernel32.dll" -int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); -int WaitForSingleObject(long hHandle, uint dwMilliseconds); -int GetExitCodeProcess(long hProcess, uint &lpExitCode); -int CloseHandle(long hObject); -long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); -#import - -// バッチファイルのパス -string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend -string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry - -// プロセス完了ファイルの設定 -string done_trend_file = "process_done_trend.txt"; -string done_entry_file = "process_done_entry.txt"; - -// Python出力ファイル -string trend_state_file = "trend_state.txt"; -string target_prices_file = "target_prices.txt"; - -// Python実行中を判定するための管理ファイル -string running_trend_file = "process_running_trend.txt"; -string running_entry_file = "process_running_entry.txt"; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| インプットの設定 -//+------------------------------------------------------------------+ - -//--- 入力パラメータ -input double lot_size = 0.01; // ロット数 -input double spread_limit = 60; // 許容スプレッド(point) -input int magic_number = 10001; // マジックナンバー -input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) -input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認 -input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可 -ulong slippage = 10; // スリッページ - -//--- 主要な定数・設定 -#define POSITION_LIMIT 48 -#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) -#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) -#define HISTORY_BARS 72 -#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す -#define M15_CONFIRM_BARS 20 -#define M15_MIN_ENTRY_ZONE_POINTS 10 -#define M15_MIN_BODY_RATIO 0.25 -#define M15_REJECTION_WICK_RATIO 0.35 -#define ENTRY_RETRY_SECONDS 60 -#define ENTRY_RETRY_LIMIT 10 -// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) -#define TARGET_SIZE 13 -#define DEFAULT_TARGET_PRICE 0.0 -#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) - -#define MARKET_LOW_VOL_RANGE 0 -#define MARKET_HIGH_VOL_RANGE 1 -#define MARKET_LOW_VOL_UP 2 -#define MARKET_HIGH_VOL_UP 3 -#define MARKET_LOW_VOL_DOWN 4 -#define MARKET_HIGH_VOL_DOWN 5 -#define MARKET_ABNORMAL_VOL_STOP 6 - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| EAの状態をまとめる構造体 -//+------------------------------------------------------------------+ - -struct EAState - { - int trend_state; // H4 market_state(0..6)を格納する変数 - int res_chk; // GPTの回答が正しく取得できたか - double en_price[5]; // エントリー基準価格 - double tp_price[5]; // 利益確定価格 - double sl_price[5]; // ロスカット基準価格 - bool load_trend_flg; // ★変更:トレンドを更新するタイミングか - bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか - int chk_cnt; // エントリー判定の試行回数 - datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 - datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 - datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻 - datetime last_chk; - }; - -EAState g_ea; // EA全体の状態をここで管理 - -// initial_order 用の初回処理フラグ -// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 -bool g_init_trend_pending = false; -bool g_init_entry_pending = false; - -// H4/H1のバー更新状態 -int g_pre_bars_H4 = 0; -int g_pre_bars_H1 = 0; -int g_pre_bars_M15 = 0; -bool g_bars_H1_check = false; -bool g_bars_M15_check = false; - -// ティックごとの価格情報 -struct TickContext - { - double ask; - double bid; - double spread; - double spread_points; - int digits; - }; - -// 外部Pythonプロセスの状態 -struct ExternalProcessState - { - long handle; - uint process_id; - bool active; - bool exit_code_ready; - uint exit_code; - datetime started_at; - }; - -ExternalProcessState g_trend_process; -ExternalProcessState g_entry_process; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| 起動時の処理 -//+------------------------------------------------------------------+ -/** - * @brief EA起動時の初期化処理を行います。 - * - * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は - * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 - * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 - */ -void OnInit() -// プロセス完了ファイルと実行中ファイルの状態を初期化 - { - PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); - PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); - - // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 - g_init_trend_pending = (initial_order == 1); - g_init_entry_pending = (initial_order == 1); - } - -//+------------------------------------------------------------------+ -//| 終了時の処理 -//+------------------------------------------------------------------+ -void OnDeinit(const int reason) - { - ResetExternalProcessState(g_trend_process); - ResetExternalProcessState(g_entry_process); - } - -//+------------------------------------------------------------------+ -//| ティック毎の処理 -//+------------------------------------------------------------------+ -/** - * @brief ティック受信ごとにEA全体の処理を制御します。 - * - * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 - * H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。 - * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 - */ -void OnTick() - { - TickContext ctx; - if(!GetTickContext(ctx)) - return; - - // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 - ManageExpiredTrades(); - - // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 - RecoverTimedOutPythonProcesses(); - - // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 - ProcessTrendUpdate(g_ea); - - // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 - if(!IsTrendResultReady()) - return; - - RefreshTrendState(g_ea); - - // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 - ProcessEntryUpdate(g_ea); - - // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 - ProcessM15EntryTimingUpdate(); - - UpdateStatusComment(ctx); - - // ここから下は新規注文に関する処理。 - if(!IsSpreadAllowed(ctx)) - return; - - if(!IsEntryResultReady()) - return; - - RefreshTargetPrices(g_ea); - ProcessEntryDecisionIfNeeded(g_ea, ctx); - } - -//+------------------------------------------------------------------+ -//| ティック情報を取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief 現在のAsk/Bid/スプレッド情報を取得してTickContextへ格納します。 - * - * @param ctx 取得したティック情報を格納する構造体参照。 - * @return 取得成功時はtrue、ティック情報を取得できない場合はfalse。 - */ -bool GetTickContext(TickContext &ctx) - { - MqlTick last_tick; - if(!SymbolInfoTick(_Symbol, last_tick)) - return false; - - ctx.ask = last_tick.ask; - ctx.bid = last_tick.bid; - ctx.spread_points = MathRound((ctx.ask - ctx.bid) / Point()); - ctx.spread = ctx.spread_points * Point(); - ctx.digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - - return true; - } - -//+------------------------------------------------------------------+ -//| 期限切れ注文・期限切れポジションを処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief 期限切れの未約定注文と保有ポジションを処理します。 - * - * 未約定注文はENTRY_H1_LIMIT時間、保有ポジションはCLOSE_H1_LIMIT時間を基準に判定します。 - * 新規注文条件とは独立して、OnTickの早い段階で実行される想定です。 - */ -void ManageExpiredTrades() - { - if(OrdersTotal() > 0) - CancelExpiredOrders(); - - if(PositionsTotal() > 0) - CloseExpiredPositions(); - } - -//+------------------------------------------------------------------+ -//| H4更新検知とトレンド判定Python起動を処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4新バーまたは初回起動を検知し、トレンド判定Pythonを起動します。 - * - * @param state EA全体の状態。トレンド更新開始フラグを更新します。 - * - * Python処理中はdoneファイルが存在しないため、CSVの上書きと二重起動を抑止します。 - */ -void ProcessTrendUpdate(EAState &state) - { - int current_bars_H4 = iBars(NULL, PERIOD_H4); - if(g_pre_bars_H4 == 0) - g_pre_bars_H4 = current_bars_H4; - - int bars_H4_change = current_bars_H4 - g_pre_bars_H4; - bool trend_trigger = (bars_H4_change > 0 || g_init_trend_pending); - - if(!trend_trigger) - return; - - if(RecordOHLCAndExecuteBatch_Trend(state)) - { - g_init_trend_pending = false; - g_pre_bars_H4 = current_bars_H4; - } - } - -//+------------------------------------------------------------------+ -//| トレンド判定Pythonの完了状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief H4トレンド判定Pythonの完了状態を確認します。 - * - * @return `process_done_trend.txt` が存在する場合はtrue、未完了の場合はfalse。 - */ -bool IsTrendResultReady() - { - return IsProcessResultReady(done_trend_file, running_trend_file, trend_state_file, "trend", g_trend_process); - } - -//+------------------------------------------------------------------+ -//| トレンド判定結果をEA状態へ反映する関数 -//+------------------------------------------------------------------+ -/** - * @brief 完了済みのH4トレンド判定結果をEA状態へ反映します。 - * - * @param state EA全体の状態。`trend_state` と最終更新時刻が更新されます。 - */ -void RefreshTrendState(EAState &state) - { - GetTrendState(state); - } - -//+------------------------------------------------------------------+ -//| H1更新検知とエントリー価格生成Python起動を処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief H1新バーまたは初回起動を検知し、エントリー価格生成Pythonを起動します。 - * - * @param state EA全体の状態。ターゲット価格更新開始フラグや判定リトライ状態を更新します。 - * - * Python処理中はdoneファイルが存在しないため、H1 CSVの上書きと二重起動を抑止します。 - */ -void ProcessEntryUpdate(EAState &state) - { - int current_bars_H1 = iBars(NULL, PERIOD_H1); - if(g_pre_bars_H1 == 0) - g_pre_bars_H1 = current_bars_H1; - - int bars_H1_change = current_bars_H1 - g_pre_bars_H1; - bool entry_trigger = (bars_H1_change > 0 || g_init_entry_pending); - - if(!entry_trigger) - return; - - if(RecordOHLCAndExecuteBatch_Entry(state)) - { - g_init_entry_pending = false; - g_bars_H1_check = true; - state.chk_cnt = 0; - state.last_chk = 0; - g_pre_bars_H1 = current_bars_H1; - } - } - -//+------------------------------------------------------------------+ -//| M15更新検知とエントリータイミング判定トリガーを処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief M15新バーを検知し、H1候補価格の発注判定を実行できる状態にします。 - * - * M15は方向判定を上書きする足ではなく、H4/H1で決めた候補価格を発注する - * タイミング確認として使います。初回は直近の確定M15足で一度だけ判定可能にします。 - */ -void ProcessM15EntryTimingUpdate() - { - int current_bars_M15 = iBars(NULL, PERIOD_M15); - if(current_bars_M15 <= 0) - return; - - if(g_pre_bars_M15 == 0) - { - g_pre_bars_M15 = current_bars_M15; - g_bars_M15_check = true; - return; - } - - int bars_M15_change = current_bars_M15 - g_pre_bars_M15; - if(bars_M15_change <= 0) - return; - - g_bars_M15_check = true; - g_pre_bars_M15 = current_bars_M15; - } - -//+------------------------------------------------------------------+ -//| チャートコメント文字列を作成する関数 -//+------------------------------------------------------------------+ -/** - * @brief チャート左上に表示するステータスメッセージを組み立てます。 - * - * @param ctx 現在のAsk/Bid/スプレッド情報。 - * @return Comment()へ渡す表示用文字列。 - */ -string BuildStatusMessage(TickContext &ctx) - { - string message = StringFormat( - " \nAsk: %." + IntegerToString(ctx.digits) + "f" - "\nBid: %." + IntegerToString(ctx.digits) + "f" - "\nSpread: %." + IntegerToString(ctx.digits) + "f" - "\nSpread Points: %.0f" - "\n\nLast Trend Update:\n %s" - "\n\nMarket State: %d (%s)" - "\n\nLast Target Update:\n %s" - "\n\nMy Used Count: %d / %d" - "\n\n[T1 Buy Stop ] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" - "\n[T2 Buy Limit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" - "\n[T3 Sell Stop] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" - "\n[T4 SellLimit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f\n", - ctx.ask, ctx.bid, ctx.spread, ctx.spread_points, - TimeToString(g_ea.last_trend_update, TIME_DATE | TIME_MINUTES), - g_ea.trend_state, MarketStateName(g_ea.trend_state), - TimeToString(g_ea.last_target_update, TIME_DATE | TIME_MINUTES), - CountMyUsed(), POSITION_LIMIT, - g_ea.en_price[1], g_ea.tp_price[1], g_ea.sl_price[1], - g_ea.en_price[2], g_ea.tp_price[2], g_ea.sl_price[2], - g_ea.en_price[3], g_ea.tp_price[3], g_ea.sl_price[3], - g_ea.en_price[4], g_ea.tp_price[4], g_ea.sl_price[4] - ); - return message; - } - -//+------------------------------------------------------------------+ -//| チャートコメントを更新する関数 -//+------------------------------------------------------------------+ -/** - * @brief チャート上のステータスコメントを更新します。 - * - * @param ctx 現在のAsk/Bid/スプレッド情報。 - */ -void UpdateStatusComment(TickContext &ctx) - { - Comment(BuildStatusMessage(ctx)); - } - -//+------------------------------------------------------------------+ -//| market_stateの表示名を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief H4 market_stateの人間可読名を返します。 - * - * @param market_state Pythonが出力したH4環境分類(0..6)。 - * @return 表示用ラベル。 - */ -string MarketStateName(const int market_state) - { - switch(market_state) - { - case MARKET_LOW_VOL_RANGE: - return "LOW_VOL_RANGE"; - case MARKET_HIGH_VOL_RANGE: - return "HIGH_VOL_RANGE"; - case MARKET_LOW_VOL_UP: - return "LOW_VOL_UP"; - case MARKET_HIGH_VOL_UP: - return "HIGH_VOL_UP"; - case MARKET_LOW_VOL_DOWN: - return "LOW_VOL_DOWN"; - case MARKET_HIGH_VOL_DOWN: - return "HIGH_VOL_DOWN"; - case MARKET_ABNORMAL_VOL_STOP: - return "ABNORMAL_VOL_STOP"; - default: - return "UNKNOWN"; - } - } - -//+------------------------------------------------------------------+ -//| スプレッドが許容範囲内か判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief 現在スプレッドが入力パラメータの許容範囲内か判定します。 - * - * @param ctx 現在のスプレッド情報。 - * @return 許容範囲内ならtrue、超過している場合はfalse。 - */ -bool IsSpreadAllowed(TickContext &ctx) - { - return (ctx.spread <= spread_limit * Point()); - } - -//+------------------------------------------------------------------+ -//| エントリー価格生成Pythonの完了状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief H1エントリー価格生成Pythonの完了状態を確認します。 - * - * @return `process_done_entry.txt` が存在する場合はtrue、未完了の場合はfalse。 - */ -bool IsEntryResultReady() - { - return IsProcessResultReady(done_entry_file, running_entry_file, target_prices_file, "entry", g_entry_process); - } - -//+------------------------------------------------------------------+ -//| ターゲット価格をEA状態へ反映する関数 -//+------------------------------------------------------------------+ -/** - * @brief 完了済みのH1エントリー価格生成結果をEA状態へ反映します。 - * - * @param state EA全体の状態。`res_chk` と各注文タイプのen/tp/slが更新されます。 - */ -void RefreshTargetPrices(EAState &state) - { - GetTargetPrices(state); - } - -//+------------------------------------------------------------------+ -//| エントリー判定が必要な場合のみ注文処理を実行する関数 -//+------------------------------------------------------------------+ -/** - * @brief エントリー判定タイミングに到達している場合のみ注文処理を実行します。 - * - * @param state EA全体の状態。 - * @param ctx 現在のAsk/Bid/スプレッド情報。 - * - * 判定前チェック、許可注文の送信、リトライ状態更新をまとめて制御します。 - */ -void ProcessEntryDecisionIfNeeded(EAState &state, TickContext &ctx) - { - if(!ShouldRunEntryDecision(state)) - return; - - g_bars_H1_check = false; - g_bars_M15_check = false; - state.last_chk = TimeCurrent(); - - if(!ValidateEntryPreconditions(state)) - return; - - int sent_success = SendAllowedEntryOrders(state, ctx); - UpdateEntryRetryState(state, sent_success); - } - -//+------------------------------------------------------------------+ -//| エントリー判定を実行するタイミングか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief エントリー判定を実行するタイミングか確認します。 - * - * @param state EA全体の状態。前回判定時刻を参照します。 - * @return H1候補が有効で、M15確定足が更新され、前回判定から一定秒数以上経過していればtrue。 - */ -bool ShouldRunEntryDecision(EAState &state) - { - return (g_bars_H1_check && g_bars_M15_check && TimeCurrent() - state.last_chk >= ENTRY_RETRY_SECONDS); - } - -//+------------------------------------------------------------------+ -//| エントリー判定前の共通チェックを行う関数 -//+------------------------------------------------------------------+ -/** - * @brief 新規注文前の共通条件を検証します。 - * - * @param state EA全体の状態。 - * @return 注文判定を続行できる場合はtrue、停止すべき場合はfalse。 - * - * `res_chk`、market_state、対象EAの注文/ポジション数上限を確認します。 - */ -bool ValidateEntryPreconditions(EAState &state) - { - if(state.res_chk != 1) - { - Print("[Entry Skip] target_prices invalid. res_chk=", state.res_chk); - state.chk_cnt = 0; - return false; - } - - if(state.trend_state == MARKET_ABNORMAL_VOL_STOP) - { - Print("[Entry Skip] market_state=6 (abnormal volatility). No new entry orders are sent."); - state.chk_cnt = 0; - return false; - } - - if(state.trend_state < MARKET_LOW_VOL_RANGE || state.trend_state > MARKET_ABNORMAL_VOL_STOP) - { - Print("[Entry Skip] invalid market_state=", state.trend_state); - state.chk_cnt = 0; - return false; - } - - if(IsTargetCandidateExpired(state)) - { - Print("[Entry Skip] H1 target candidate expired. loaded_at=", - TimeToString(state.target_loaded_at, TIME_DATE | TIME_SECONDS)); - state.chk_cnt = 0; - return false; - } - - int used = CountMyUsed(); - if(used >= POSITION_LIMIT) - { - Print("Position limit exceeded: used=", used, " limit=", POSITION_LIMIT); - return false; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| H1候補価格の有効期限を判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief H1候補価格がENTRY_H1_LIMIT時間を超えて古くなっていないか判定します。 - * - * @param state EA全体の状態。H1候補価格の読込時刻を参照します。 - * @return 候補価格が期限切れの場合はtrue。 - */ -bool IsTargetCandidateExpired(EAState &state) - { - if(state.target_loaded_at <= 0) - return false; - - int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); - if(expiration_seconds <= 0) - return false; - - return (TimeCurrent() - state.target_loaded_at >= expiration_seconds); - } - -//+------------------------------------------------------------------+ -//| 許可された注文タイプだけを順番に送信する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4 market_stateで許可された注文タイプだけを順番に送信します。 - * - * @param state EA全体の状態。各注文タイプのen/tp/slを参照します。 - * @param ctx 現在のAsk/Bid/スプレッド情報。 - * @return 送信成功した注文数。 - */ -int SendAllowedEntryOrders(EAState &state, TickContext &ctx) - { - int sent_success = 0; - - for(int t = 1; t <= 4; t++) - { - int used = CountMyUsed(); - if(used >= POSITION_LIMIT) - { - Print("Position limit reached while sending. used=", used, " limit=", POSITION_LIMIT); - break; - } - - if(!IsOrderTypeAllowedByTrend(t, state.trend_state)) - { - Print("[Skip] orderType=", t, " not allowed by market_state=", state.trend_state, - " (", MarketStateName(state.trend_state), ")"); - continue; - } - - if(TrySendEntryOrder(t, state, ctx)) - sent_success++; - } - - return sent_success; - } - -//+------------------------------------------------------------------+ -//| 注文タイプ1件分の価格検証と注文送信を行う関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプ1件分の価格検証と注文送信を行います。 - * - * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 - * @param state EA全体の状態。対象注文タイプのen/tp/slを参照します。 - * @param ctx 現在のAsk/Bid情報。 - * @return 注文送信に成功した場合はtrue、それ以外はfalse。 - */ -bool TrySendEntryOrder(const int orderType, EAState &state, TickContext &ctx) - { - string entry_type = EntryTypeName(orderType); - double cur_price = CurrentPriceForOrderType(orderType, ctx); - - double en = state.en_price[orderType]; - double tp = state.tp_price[orderType]; - double sl = state.sl_price[orderType]; - - if(!HasValidTargetPrices(en, tp, sl)) - { - Print("[Skip] invalid target prices. orderType=", orderType, - " en=", en, " tp=", tp, " sl=", sl); - return false; - } - - bool ok = IsTargetPriceOrderConditionMatched(orderType, ctx, en, tp, sl); - if(!ok) - { - Print("[No ", entry_type, "] cur=", cur_price, " en=", en, " tp=", tp, " sl=", sl); - return false; - } - - if(!IsM15EntryTimingConfirmed(orderType, ctx, en)) - { - Print("[No ", entry_type, "] M15 timing not confirmed. cur=", cur_price, " en=", en); - return false; - } - - if(!MeetsTradeDistanceRules(orderType, ctx, en, tp, sl)) - return false; - - Print("[", entry_type, " Order Try at ", cur_price, "] en=", en, " tp=", tp, " sl=", sl); - - if(SendOrder(orderType, en, tp, sl)) - { - Print("[", entry_type, " Order Sent] ticket ok. en=", en, " tp=", tp, " sl=", sl); - return true; - } - - Print("[", entry_type, " Order Failed] en=", en, " tp=", tp, " sl=", sl); - return false; - } - -//+------------------------------------------------------------------+ -//| 注文タイプ名を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプ番号に対応する表示名を返します。 - * - * @param orderType 注文タイプ番号。 - * @return 注文タイプの表示名。不正値の場合は"Unknown"。 - */ -string EntryTypeName(const int orderType) - { - switch(orderType) - { - case 1: - return "Buy Stop"; - case 2: - return "Buy Limit"; - case 3: - return "Sell Stop"; - case 4: - return "Sell Limit"; - default: - return "Unknown"; - } - } - -//+------------------------------------------------------------------+ -//| 注文タイプに応じた現在価格を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプに応じて価格比較に使う現在価格を返します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @return 買い系注文ではAsk、売り系注文ではBid。 - */ -double CurrentPriceForOrderType(const int orderType, TickContext &ctx) - { - if(orderType == 1 || orderType == 2) - return ctx.ask; - - return ctx.bid; - } - -//+------------------------------------------------------------------+ -//| 注文タイプごとの価格整合条件を判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプごとの現在価格・エントリー・TP・SLの大小関係を検証します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @param en エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return 注文タイプの価格条件を満たす場合はtrue。 - */ -bool IsTargetPriceOrderConditionMatched(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) - { - switch(orderType) - { - case 1: // Buy Stop - return (ctx.ask < en && tp > en && sl < en); - - case 2: // Buy Limit - return (ctx.ask > en && tp > en && sl < en); - - case 3: // Sell Stop - return (ctx.bid > en && tp < en && sl > en); - - case 4: // Sell Limit - return (ctx.bid < en && tp < en && sl > en); - - default: - return false; - } - } - -//+------------------------------------------------------------------+ -//| M15確定足によるエントリータイミング確認 -//+------------------------------------------------------------------+ -/** - * @brief H1候補価格に対してM15の発注タイミングが整っているか判定します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @param en H1で決めたエントリー候補価格。 - * @return M15確認条件を満たす場合はtrue。 - * - * H4/H1の方向判断は維持し、M15では「候補価格に近い」「直近確定足が - * 順張り/反転の根拠を持つ」ことだけを確認します。 - */ -bool IsM15EntryTimingConfirmed(const int orderType, TickContext &ctx, const double en) - { - if(!use_m15_entry_filter) - return true; - - MqlRates rates[]; - int copied = CopyRates(_Symbol, PERIOD_M15, OHLC_START_SHIFT, M15_CONFIRM_BARS, rates); - if(copied < 3) - { - Print("[M15 Filter] insufficient M15 bars. copied=", copied); - return false; - } - - int last_index = copied - 1; - int prev_index = copied - 2; - - double avg_range = AverageM15Range(rates, copied); - double min_zone = M15_MIN_ENTRY_ZONE_POINTS * Point(); - double entry_zone = avg_range * m15_entry_zone_atr_multiplier; - if(entry_zone < min_zone) - entry_zone = min_zone; - - double cur_price = CurrentPriceForOrderType(orderType, ctx); - if(MathAbs(cur_price - en) > entry_zone) - { - Print("[M15 Filter] ", EntryTypeName(orderType), " is not near entry zone. cur=", - cur_price, " en=", en, " zone=", entry_zone); - return false; - } - - return IsM15SignalAligned(orderType, rates[prev_index], rates[last_index], en, entry_zone); - } - -//+------------------------------------------------------------------+ -//| M15平均レンジを計算する関数 -//+------------------------------------------------------------------+ -/** - * @brief M15確定足の平均レンジを計算します。 - * - * @param rates M15のMqlRates配列。 - * @param count 使用する本数。 - * @return 平均レンジ。算出不能な場合は最小ゾーン幅を返します。 - */ -double AverageM15Range(const MqlRates &rates[], const int count) - { - double total_range = 0.0; - int used = 0; - - for(int i = 0; i < count; i++) - { - double range = rates[i].high - rates[i].low; - if(range <= 0.0) - continue; - - total_range += range; - used++; - } - - if(used <= 0) - return M15_MIN_ENTRY_ZONE_POINTS * Point(); - - return total_range / used; - } - -//+------------------------------------------------------------------+ -//| 注文タイプごとのM15シグナル方向を判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプごとにM15確定足の勢い・反転根拠を確認します。 - * - * @param orderType 注文タイプ番号。 - * @param prev_bar 1本前のM15確定足。 - * @param last_bar 直近のM15確定足。 - * @param en H1で決めたエントリー候補価格。 - * @param entry_zone M15平均レンジから算出した候補価格付近の許容幅。 - * @return 注文タイプに沿ったM15根拠があればtrue。 - */ -bool IsM15SignalAligned(const int orderType, const MqlRates &prev_bar, const MqlRates &last_bar, const double en, const double entry_zone) - { - double range = SafeBarRange(last_bar); - double body_ratio = MathAbs(last_bar.close - last_bar.open) / range; - double upper_wick_ratio = (last_bar.high - MathMax(last_bar.open, last_bar.close)) / range; - double lower_wick_ratio = (MathMin(last_bar.open, last_bar.close) - last_bar.low) / range; - - bool bullish = (last_bar.close > last_bar.open); - bool bearish = (last_bar.close < last_bar.open); - bool strong_body = (body_ratio >= M15_MIN_BODY_RATIO); - bool bullish_break = (last_bar.close > prev_bar.high); - bool bearish_break = (last_bar.close < prev_bar.low); - bool lower_rejection = (lower_wick_ratio >= M15_REJECTION_WICK_RATIO); - bool upper_rejection = (upper_wick_ratio >= M15_REJECTION_WICK_RATIO); - - switch(orderType) - { - case 1: // Buy Stop: M15の上方向モメンタムを確認 - return (bullish && (bullish_break || strong_body) && last_bar.close <= en + entry_zone); - - case 2: // Buy Limit: 候補価格付近で下ヒゲ反転または買い戻しを確認 - return (last_bar.low <= en + entry_zone && bullish && - (lower_rejection || bullish_break || strong_body)); - - case 3: // Sell Stop: M15の下方向モメンタムを確認 - return (bearish && (bearish_break || strong_body) && last_bar.close >= en - entry_zone); - - case 4: // Sell Limit: 候補価格付近で上ヒゲ反転または売り戻しを確認 - return (last_bar.high >= en - entry_zone && bearish && - (upper_rejection || bearish_break || strong_body)); - - default: - return false; - } - } - -//+------------------------------------------------------------------+ -//| ローソク足レンジを安全に取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief ゼロ除算を避けるため、最小値を持つローソク足レンジを返します。 - * - * @param bar 対象ローソク足。 - * @return high-low。0以下の場合はPoint()を返します。 - */ -double SafeBarRange(const MqlRates &bar) - { - double range = bar.high - bar.low; - if(range <= 0.0) - return Point(); - - return range; - } - -//+------------------------------------------------------------------+ -//| brokerの最小距離制約を満たすか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief pending価格、TP、SLがstop level / freeze levelの最小距離を満たすか判定します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @param en エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return 最小距離を満たす場合はtrue。 - */ -bool MeetsTradeDistanceRules(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) - { - int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); - int freeze_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL); - int min_level = stop_level; - if(freeze_level > min_level) - min_level = freeze_level; - double min_distance = min_level * Point(); - - if(min_distance <= 0.0) - return true; - - double cur_price = CurrentPriceForOrderType(orderType, ctx); - string entry_type = EntryTypeName(orderType); - - if(MathAbs(en - cur_price) < min_distance) - { - Print("[Skip] ", entry_type, " entry is too close. cur=", cur_price, - " en=", en, " min_distance=", min_distance); - return false; - } - - if(MathAbs(tp - en) < min_distance) - { - Print("[Skip] ", entry_type, " TP is too close. en=", en, - " tp=", tp, " min_distance=", min_distance); - return false; - } - - if(MathAbs(en - sl) < min_distance) - { - Print("[Skip] ", entry_type, " SL is too close. en=", en, - " sl=", sl, " min_distance=", min_distance); - return false; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| 注文送信結果に応じてリトライ状態を更新する関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文送信結果に応じてエントリー判定のリトライ状態を更新します。 - * - * @param state EA全体の状態。`chk_cnt` を更新します。 - * @param sent_success 今回送信に成功した注文数。 - * - * 注文が1件も送信されなかった場合、最大10回まで60秒間隔で再判定します。 - */ -void UpdateEntryRetryState(EAState &state, const int sent_success) - { - if(sent_success > 0) - { - state.chk_cnt = 0; - return; - } - - state.chk_cnt += 1; - Print("[Retry] no order sent. chk_cnt=", state.chk_cnt, "/", ENTRY_RETRY_LIMIT, - " (wait next M15 bar)"); - - if(state.chk_cnt < ENTRY_RETRY_LIMIT) - { - g_bars_H1_check = true; // 次のM15確定足で再度エントリー判定を実行する - return; - } - - Print("[Retry End] reached max tries. reset chk_cnt."); - state.chk_cnt = 0; - } - -//+------------------------------------------------------------------+ -//| "process_done.txt"を作成する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したdoneファイルを作成します。 - * - * @param name 作成するファイル名。MT5のMQL5\Files配下を基準に扱います。 - */ -void CreateDoneFile(const string name) - { - if(!FileIsExist(name)) - { - int h = FileOpen(name, FILE_WRITE|FILE_TXT); - if(h != INVALID_HANDLE) - FileClose(h); - else - Print("Failed to create file: ", name, " err=", GetLastError()); - } - } - -//+------------------------------------------------------------------+ -//| "process_done.txt"を削除する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したdoneファイルを削除します。 - * - * @param name 削除するファイル名。存在しない場合は何もしません。 - */ -void DeleteDoneFile(const string name) - { - if(FileIsExist(name)) - FileDelete(name); - } - - -//+------------------------------------------------------------------+ -//| "process_done.txt"の存在を確認し、存在する場合はtrueを返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したdoneファイルの存在有無を確認します。 - * - * @param name 確認するファイル名。 - * @return ファイルが存在する場合はtrue、存在しない場合はfalse。 - */ -bool CheckDoneFile(const string name) - { - return FileIsExist(name); - } - -//+------------------------------------------------------------------+ -//| Python実行中ファイルを作成する関数 -//+------------------------------------------------------------------+ -/** - * @brief Python起動時刻を実行中ファイルへ記録します。 - * - * @param name 作成するrunningファイル名。 - */ -void CreateRunningFile(const string name, const uint process_id) - { - int h = FileOpen(name, FILE_WRITE | FILE_TXT); - if(h != INVALID_HANDLE) - { - FileWrite(h, IntegerToString((long)TimeCurrent())); - FileWrite(h, IntegerToString((long)process_id)); - FileClose(h); - } - else - Print("Failed to create running file: ", name, " err=", GetLastError()); - } - -//+------------------------------------------------------------------+ -//| Python実行中ファイルを削除する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したrunningファイルを削除します。 - * - * @param name 削除するrunningファイル名。 - */ -void DeleteRunningFile(const string name) - { - if(FileIsExist(name)) - FileDelete(name); - } - -//+------------------------------------------------------------------+ -//| Python実行開始時刻を読み込む関数 -//+------------------------------------------------------------------+ -/** - * @brief runningファイルに記録されたPython起動時刻を読み込みます。 - * - * @param name 読み込むrunningファイル名。 - * @return 読み込めた起動時刻。失敗時は0。 - */ -datetime LoadRunningStartedAt(const string name) - { - if(!FileIsExist(name)) - return 0; - - int h = FileOpen(name, FILE_READ | FILE_TXT); - if(h == INVALID_HANDLE) - { - Print("Failed to open running file: ", name, " err=", GetLastError()); - return 0; - } - - string line = FileReadString(h); - FileClose(h); - return (datetime)StringToInteger(line); - } - -//+------------------------------------------------------------------+ -//| Python実行プロセスIDを読み込む関数 -//+------------------------------------------------------------------+ -/** - * @brief runningファイルに記録されたプロセスIDを読み込みます。 - * - * @param name 読み込むrunningファイル名。 - * @return 読み込めたプロセスID。旧形式または失敗時は0。 - */ -uint LoadRunningProcessId(const string name) - { - if(!FileIsExist(name)) - return 0; - - int h = FileOpen(name, FILE_READ | FILE_TXT); - if(h == INVALID_HANDLE) - { - Print("Failed to open running file: ", name, " err=", GetLastError()); - return 0; - } - - FileReadString(h); // started_at - if(FileIsEnding(h)) - { - FileClose(h); - return 0; - } - - string line = FileReadString(h); - FileClose(h); - return (uint)StringToInteger(line); - } - -//+------------------------------------------------------------------+ -//| Python実行中ファイルがタイムアウトしているか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief runningファイルの起動時刻からPython待ち上限を超えているか判定します。 - * - * @param name 判定するrunningファイル名。 - * @return タイムアウトしている場合はtrue。 - */ -bool IsRunningFileTimedOut(const string name) - { - datetime started_at = LoadRunningStartedAt(name); - if(started_at <= 0) - return true; - - return (TimeCurrent() - started_at >= PYTHON_TIMEOUT_SECONDS); - } - -//+------------------------------------------------------------------+ -//| 外部プロセス状態を初期値に戻す関数 -//+------------------------------------------------------------------+ -void ResetExternalProcessState(ExternalProcessState &process) - { - if(process.handle != 0) - CloseHandle(process.handle); - - process.handle = 0; - process.process_id = 0; - process.active = false; - process.exit_code_ready = false; - process.exit_code = 0; - process.started_at = 0; - } - -//+------------------------------------------------------------------+ -//| runningファイルのPIDからプロセスハンドルを復元する関数 -//+------------------------------------------------------------------+ -bool AttachRunningProcess(const string running_file, const string label, ExternalProcessState &process) - { - if(process.active && process.handle != 0) - return true; - - uint process_id = LoadRunningProcessId(running_file); - if(process_id == 0) - return false; - - long handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, 0, process_id); - if(handle == 0) - { - Print("[", label, "] failed to open running process. pid=", process_id, " err=", GetLastError()); - return false; - } - - process.handle = handle; - process.process_id = process_id; - process.active = true; - process.exit_code_ready = false; - process.exit_code = STILL_ACTIVE; - process.started_at = LoadRunningStartedAt(running_file); - return true; - } - -//+------------------------------------------------------------------+ -//| 外部プロセスの終了状態を更新する関数 -//+------------------------------------------------------------------+ -bool UpdateExternalProcessStatus(ExternalProcessState &process, const string label) - { - if(!process.active || process.handle == 0) - return true; - - int wait_result = WaitForSingleObject(process.handle, 0); - if(wait_result == WAIT_TIMEOUT) - return false; - - uint exit_code = 1; - if(wait_result == WAIT_OBJECT_0) - { - if(GetExitCodeProcess(process.handle, exit_code) == 0) - { - Print("[", label, "] failed to get process exit code. err=", GetLastError()); - exit_code = 1; - } - } - else - { - Print("[", label, "] WaitForSingleObject failed. result=", wait_result, " err=", GetLastError()); - exit_code = 1; - } - - CloseHandle(process.handle); - process.handle = 0; - process.active = false; - process.exit_code_ready = true; - process.exit_code = exit_code; - Print("[", label, "] process finished. pid=", process.process_id, " exit_code=", exit_code); - return true; - } - -//+------------------------------------------------------------------+ -//| 外部プロセスがタイムアウトしているか判定する関数 -//+------------------------------------------------------------------+ -bool IsExternalProcessTimedOut(ExternalProcessState &process, const string running_file) - { - datetime started_at = process.started_at; - if(started_at <= 0) - started_at = LoadRunningStartedAt(running_file); - if(started_at <= 0) - return true; - - return (TimeCurrent() - started_at >= PYTHON_TIMEOUT_SECONDS); - } - -//+------------------------------------------------------------------+ -//| バッチファイルをプロセスハンドル付きで起動する関数 -//+------------------------------------------------------------------+ -bool StartBatchProcess(const string bat_file, const string running_file, const string label, ExternalProcessState &process) - { - STARTUPINFO_W startup_info = {}; - PROCESS_INFORMATION process_info = {}; - - startup_info.cb = (uint)sizeof(startup_info); - - string cmd_exe = "C:\\Windows\\System32\\cmd.exe"; - string command_line = "\"" + cmd_exe + "\" /c \"\"" + bat_file + "\"\""; - - int created = CreateProcessW( - cmd_exe, - command_line, - 0, - 0, - 0, - CREATE_NO_WINDOW, - 0, - "C:\\ea_py", - startup_info, - process_info - ); - - if(created == 0 || process_info.hProcess == 0) - { - Print("[", label, "] CreateProcessW failed. file=", bat_file, " err=", GetLastError()); - ResetExternalProcessState(process); - DeleteRunningFile(running_file); - return false; - } - - if(process_info.hThread != 0) - CloseHandle(process_info.hThread); - - process.handle = process_info.hProcess; - process.process_id = process_info.dwProcessId; - process.active = true; - process.exit_code_ready = false; - process.exit_code = STILL_ACTIVE; - process.started_at = TimeCurrent(); - - CreateRunningFile(running_file, process.process_id); - Print("[", label, "] process started. pid=", process.process_id, " file=", bat_file); - return true; - } - -//+------------------------------------------------------------------+ -//| EA起動時のdoneファイル状態を整える関数 -//+------------------------------------------------------------------+ -/** - * @brief EA起動時にPython実行中状態を壊さない範囲でdoneファイルを準備します。 - * - * @param done_file 完了判定ファイル名。 - * @param running_file 実行中判定ファイル名。 - * @param label ログ表示用ラベル。 - */ -void PrepareDoneFileOnInit(const string done_file, const string running_file, const string label) - { - if(CheckDoneFile(done_file)) - { - DeleteRunningFile(running_file); - return; - } - - if(FileIsExist(running_file)) - { - if(IsRunningFileTimedOut(running_file)) - { - Print("[", label, "] stale running file found on init. Remove marker without creating done."); - DeleteRunningFile(running_file); - } - else - Print("[", label, "] Python seems to be running on init. Keep waiting."); - return; - } - - Print("[", label, "] done file not found on init. Keep as not-ready until the next trigger."); - } - -//+------------------------------------------------------------------+ -//| Python完了状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief doneファイルがあれば完了扱いにし、runningファイルを片付けます。 - * - * @param done_file 完了判定ファイル名。 - * @param running_file 実行中判定ファイル名。 - * @return 完了済みの場合はtrue。 - */ -bool IsProcessResultReady(const string done_file, const string running_file, const string result_file, const string label, ExternalProcessState &process) - { - if(process.active || FileIsExist(running_file)) - { - if(process.active || AttachRunningProcess(running_file, label, process)) - { - if(!UpdateExternalProcessStatus(process, label)) - return false; - - DeleteRunningFile(running_file); - if(process.exit_code_ready && process.exit_code != 0) - { - Print("[", label, "] process failed. exit_code=", process.exit_code); - return false; - } - } - else if(!CheckDoneFile(done_file)) - { - return false; - } - } - - if(!CheckDoneFile(done_file)) - return false; - - if(!FileIsExist(result_file)) - { - Print("[", label, "] done exists but result file is missing: ", result_file); - return false; - } - - DeleteRunningFile(running_file); - return true; - } - -//+------------------------------------------------------------------+ -//| Python開始可能状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief done/runningファイル状態からPythonを新規起動できるか判定します。 - * - * @param done_file 完了判定ファイル名。 - * @param running_file 実行中判定ファイル名。 - * @param label ログ表示用ラベル。 - * @return 起動可能な場合はtrue。 - */ -bool IsProcessStartAllowed(const string done_file, const string running_file, const string label, ExternalProcessState &process) - { - if(process.active || FileIsExist(running_file)) - { - if(process.active || AttachRunningProcess(running_file, label, process)) - { - if(!UpdateExternalProcessStatus(process, label)) - { - if(IsExternalProcessTimedOut(process, running_file)) - Print("[", label, "] Python timed out, but the process is still running. Keep waiting."); - else - Print("[", label, "] Python is still running. Skip execute."); - return false; - } - - DeleteRunningFile(running_file); - if(process.exit_code_ready && process.exit_code != 0) - Print("[", label, "] previous process failed. exit_code=", process.exit_code, ". Retry is allowed."); - } - else - { - if(CheckDoneFile(done_file)) - { - DeleteRunningFile(running_file); - return true; - } - - if(IsRunningFileTimedOut(running_file)) - { - Print("[", label, "] stale running marker without live process. Retry is allowed."); - DeleteRunningFile(running_file); - return true; - } - - Print("[", label, "] running marker exists, but process cannot be verified yet. Skip execute."); - return false; - } - } - - if(CheckDoneFile(done_file)) - { - DeleteRunningFile(running_file); - return true; - } - - Print("[", label, "] done file missing without running marker. Start new process."); - return true; - } - -//+------------------------------------------------------------------+ -//| タイムアウトしたPython待ちを復旧する関数 -//+------------------------------------------------------------------+ -/** - * @brief Pythonがdoneファイルを返さない状態を検知し、再実行できる状態へ戻します。 - */ -void RecoverTimedOutPythonProcesses() - { - if(RecoverTimedOutProcess(done_trend_file, running_trend_file, "trend", g_trend_process)) - { - g_ea.load_trend_flg = false; - g_init_trend_pending = true; - } - - if(RecoverTimedOutProcess(done_entry_file, running_entry_file, "entry", g_entry_process)) - { - g_ea.load_target_flg = false; - g_bars_H1_check = false; - g_bars_M15_check = false; - g_ea.chk_cnt = 0; - g_init_entry_pending = true; - } - } - -//+------------------------------------------------------------------+ -//| 個別Python処理のタイムアウトを復旧する関数 -//+------------------------------------------------------------------+ -/** - * @brief 1種類のPython処理について、実行中ファイルのタイムアウトを検知します。 - * - * @param done_file 完了判定ファイル名。 - * @param running_file 実行中判定ファイル名。 - * @param label ログ表示用ラベル。 - * @return タイムアウト復旧を行った場合はtrue。 - */ -bool RecoverTimedOutProcess(const string done_file, const string running_file, const string label, ExternalProcessState &process) - { - if(CheckDoneFile(done_file) && !process.active) - { - DeleteRunningFile(running_file); - return false; - } - - if(!process.active && !FileIsExist(running_file)) - return false; - - if(process.active || AttachRunningProcess(running_file, label, process)) - { - if(!UpdateExternalProcessStatus(process, label)) - { - if(IsExternalProcessTimedOut(process, running_file)) - Print("[", label, "] Python exceeded ", PYTHON_TIMEOUT_SECONDS, - " seconds, but the process is still running. Keep waiting."); - return false; - } - - DeleteRunningFile(running_file); - if(process.exit_code_ready && process.exit_code != 0) - { - Print("[", label, "] Python process failed. Retry on next trigger."); - return true; - } - - if(!CheckDoneFile(done_file)) - { - Print("[", label, "] Python process ended without done file. Retry on next trigger."); - return true; - } - - return false; - } - - if(!IsRunningFileTimedOut(running_file)) - return false; - - Print("[", label, "] Python did not finish within ", - PYTHON_TIMEOUT_SECONDS, " seconds, and no live process was found. Retry is allowed."); - DeleteRunningFile(running_file); - return true; - } - -//+------------------------------------------------------------------+ -//| 最新のOHLCデータを取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定時間足のOHLCデータを取得します。 - * - * @param times 取得したバー時刻を格納する配列。 - * @param open_prices 取得したOpen価格を格納する配列。 - * @param high_prices 取得したHigh価格を格納する配列。 - * @param low_prices 取得したLow価格を格納する配列。 - * @param close_prices 取得したClose価格を格納する配列。 - * @param tf 取得対象の時間足。 - * @param bars_count 取得するバー本数。 - * @return 取得に成功した場合はtrue、失敗した場合はfalse。 - * - * `OHLC_START_SHIFT=1` のため、形成中のバーではなく確定足から取得します。 - */ -bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count) - { - ArrayResize(times, 0); - ArrayResize(open_prices, 0); - ArrayResize(high_prices, 0); - ArrayResize(low_prices, 0); - ArrayResize(close_prices, 0); - - if(bars_count < 2) - return false; - -// OHLCデータを取得 -// CopyTime と CopyRates を別々に呼ばず、MqlRates の time/open/high/low/close を同一配列から取得する。 -// OHLC_START_SHIFT=1 のため、形成中の0本目ではなく確定足からPythonへ渡す。 - MqlRates rates[]; - int copied = CopyRates(_Symbol, tf, OHLC_START_SHIFT, bars_count, rates); - if(copied <= 0) - { - Print(__FUNCTION__, ": Failed to copy rates data (bars=", bars_count, ") err=", GetLastError()); - return false; - } - - ArrayResize(times, copied); - ArrayResize(open_prices, copied); - ArrayResize(high_prices, copied); - ArrayResize(low_prices, copied); - ArrayResize(close_prices, copied); - -// 必要なデータを配列に格納 - for(int i = 0; i < copied; i++) - { - times[i] = rates[i].time; - open_prices[i] = rates[i].open; - high_prices[i] = rates[i].high; - low_prices[i] = rates[i].low; - close_prices[i] = rates[i].close; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| "ohlc.csv"を出力する関数 -//+------------------------------------------------------------------+ -/** - * @brief OHLC配列をPython入力用CSVとして出力します。 - * - * @param filename 出力するCSVファイル名。 - * @param times バー時刻配列。 - * @param open_prices Open価格配列。 - * @param high_prices High価格配列。 - * @param low_prices Low価格配列。 - * @param close_prices Close価格配列。 - * @return CSV出力に成功した場合はtrue。 - */ -bool RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[]) - { -// 出力先ファイル名 -// string filename = "ohlc.csv"; - -// ファイルを "書き込みモード" でオープン (テキスト/ANSI) - int fileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI); - -// ファイルが開けたかチェック - if(fileHandle == INVALID_HANDLE) - { - Print(__FUNCTION__, " : Failed to open file: ", GetLastError()); - return false; - } - -// 配列サイズを取得 (times, open, high, low, close の要素数は同じ前提) - int size = ArraySize(times); - -// ヘッダー行を追加 - FileWrite(fileHandle, "Time,Open,High,Low,Close"); - -// 小数点の桁数を取得 - int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - -// フォーマット文字列を動的に生成 - string formatString = StringFormat("%%s,%%.%df,%%.%df,%%.%df,%%.%df", digits, digits, digits, digits); - -// 1行ずつ "時刻, Open, High, Low, Close" の形式で書き込み - for(int i = 0; i < size; i++) - { - // 時間をフォーマット - string timeStr = TimeToString(times[i], TIME_DATE | TIME_MINUTES); - - // 1行分の文字列を生成 (小数点以下の桁数を `digits` に調整) - string line = StringFormat(formatString, - timeStr, open_prices[i], high_prices[i], low_prices[i], close_prices[i]); - - // ファイルに書き込み (改行付き) - FileWrite(fileHandle, line); - } - -// 書き込み終了後、ファイルを閉じる - FileClose(fileHandle); - return true; - } - -//+------------------------------------------------------------------+ -//| バッチファイル(Pythonスクリプト)を実行する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4トレンド判定用バッチファイルを起動します。 - * - * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 - * - * 起動直前に`process_done_trend.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。 - */ -bool ExecuteBatchTrend() - { - if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process)) - return false; - - DeleteDoneFile(done_trend_file); - return StartBatchProcess(get_trend_reply_bat, running_trend_file, "trend", g_trend_process); - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief H1エントリー価格生成用バッチファイルを起動します。 - * - * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 - * - * 起動直前に`process_done_entry.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。 - */ -bool ExecuteBatchEntry() - { - if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process)) - return false; - - DeleteDoneFile(done_entry_file); - return StartBatchProcess(get_entry_reply_bat, running_entry_file, "entry", g_entry_process); - } - - -//+------------------------------------------------------------------+ -//| "ohlc.csv"を出力後、バッチファイルの実行する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4 OHLCをCSV出力し、トレンド判定Pythonを起動します。 - * - * @param state EA全体の状態。トレンド読込待ちフラグを更新します。 - * @return CSV出力とバッチ起動が開始できた場合はtrue。 - */ -bool RecordOHLCAndExecuteBatch_Trend(EAState &state) - { - if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process)) - { - Print("Trend Python is still running. Skip H4 CSV update."); - return false; - } - - datetime times[]; - double open_prices[], high_prices[], low_prices[], close_prices[]; - - if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, - PERIOD_H4, HISTORY_BARS)) - { Print("GetLatestOHLC(H4) failed."); return false; } - - if(!RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices)) - { Print("RecordOHLC(H4) failed."); return false; } - - if(!ExecuteBatchTrend()) - return false; - - state.load_trend_flg = true; - return true; - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief H1 OHLCをCSV出力し、エントリー価格生成Pythonを起動します。 - * - * @param state EA全体の状態。ターゲット価格読込待ちフラグを更新します。 - * @return CSV出力とバッチ起動が開始できた場合はtrue。 - */ -bool RecordOHLCAndExecuteBatch_Entry(EAState &state) - { - if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process)) - { - Print("Entry Python is still running. Skip H1 CSV update."); - return false; - } - - datetime times[]; - double open_prices[], high_prices[], low_prices[], close_prices[]; - - if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, - PERIOD_H1, HISTORY_BARS)) - { Print("GetLatestOHLC(H1) failed."); return false; } - - if(!RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices)) - { Print("RecordOHLC(H1) failed."); return false; } - - if(!ExecuteBatchEntry()) - return false; - - state.load_target_flg = true; - return true; - } - - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief Pythonが出力したトレンド判定結果を必要なタイミングで読み込みます。 - * - * @param state EA全体の状態。H4 market_stateと更新フラグを更新します。 - * @return 現状は常にtrue。 - */ -bool GetTrendState(EAState &state) - { - bool ProcessDone = CheckDoneFile(done_trend_file); - if(ProcessDone && g_ea.load_trend_flg) - { - int trend_state; - LoadTrendState(trend_state); - state.trend_state = trend_state; - Print("market_state: ", state.trend_state, " (", MarketStateName(state.trend_state), ")"); - g_ea.load_trend_flg = false; - g_ea.last_trend_update = TimeLocal(); - } - return true; - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief `trend_state.txt` からH4 market_state判定値を読み込みます。 - * - * @param trend_state 読み込んだ値を格納する参照。0..6のmarket_state。 - * - * ファイル未存在、読込失敗、異常値の場合は安全側として6(新規停止)を設定します。 - */ -void LoadTrendState(int &trend_state) - { - string filename = trend_state_file; - trend_state = MARKET_ABNORMAL_VOL_STOP; - - if(FileIsExist(filename)) - { - int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); - if(filehandle != INVALID_HANDLE) - { - string line = FileReadString(filehandle); - int value = (int)StringToInteger(line); - if(value >= MARKET_LOW_VOL_RANGE && value <= MARKET_ABNORMAL_VOL_STOP) - trend_state = value; - else - Print("Invalid market_state value: ", line, ". Use 6."); - FileClose(filehandle); - } - else - { Print("Failed to open trend_state.txt"); trend_state = MARKET_ABNORMAL_VOL_STOP; } - } - else - { Print("trend_state.txt not found"); trend_state = MARKET_ABNORMAL_VOL_STOP; } - } - -//+------------------------------------------------------------------+ -//| ターゲット価格を取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief Pythonが出力したエントリー価格群を必要なタイミングで読み込みます。 - * - * @param state EA全体の状態。`res_chk` と4タイプ分のen/tp/slを更新します。 - * @return 現状は常にtrue。 - */ -bool GetTargetPrices(EAState &state) - { - bool ProcessDone = CheckDoneFile(done_entry_file); // ←変更 - if(ProcessDone && g_ea.load_target_flg) // ←変更 - { - double target_prices[]; - LoadTargetPrices(target_prices); - - int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - - // 先頭は res_chk - state.res_chk = (int)target_prices[0]; - - // ★ 1..4 の (en,tp,sl) を読む:合計12個 - for(int t=1; t<=4; t++) - { - int base = 1 + (t-1)*3; // 1,4,7,10 - state.en_price[t] = NormalizeDouble(target_prices[base + 0], digits); - state.tp_price[t] = NormalizeDouble(target_prices[base + 1], digits); - state.sl_price[t] = NormalizeDouble(target_prices[base + 2], digits); - } - - // ログ(任意) - Print("target_prices: res=", state.res_chk, - " | T1 en=", state.en_price[1], " tp=", state.tp_price[1], " sl=", state.sl_price[1], - " | T2 en=", state.en_price[2], " tp=", state.tp_price[2], " sl=", state.sl_price[2], - " | T3 en=", state.en_price[3], " tp=", state.tp_price[3], " sl=", state.sl_price[3], - " | T4 en=", state.en_price[4], " tp=", state.tp_price[4], " sl=", state.sl_price[4]); - - g_ea.load_target_flg = false; // ←変更 - g_ea.last_target_update = TimeLocal(); // ←変更 - g_ea.target_loaded_at = TimeCurrent(); - } - return true; - } -//+------------------------------------------------------------------+ -//+------------------------------------------------------------------+ -//| "target_prices.txt"を読み込む関数 -//+------------------------------------------------------------------+ -/** - * @brief `target_prices.txt` から13個の数値を読み込みます。 - * - * @param target_prices 読み込んだ数値を格納する配列。先頭がres_chk、以降は4タイプ分のen/tp/sl。 - * - * 行数不足の場合は`res_chk=0`として、ターゲット価格を無効扱いにします。 - */ -void LoadTargetPrices(double &target_prices[]) - { - ArrayResize(target_prices, TARGET_SIZE); - for(int i = 0; i < TARGET_SIZE; i++) - target_prices[i] = DEFAULT_TARGET_PRICE; - - string filename = target_prices_file; - - if(FileIsExist(filename)) - { - int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); - if(filehandle != INVALID_HANDLE) - { - int i = 0; - while(!FileIsEnding(filehandle) && i < TARGET_SIZE) - { - string line = FileReadString(filehandle); - target_prices[i] = StringToDouble(line); - i++; - } - FileClose(filehandle); - - if(i < TARGET_SIZE) - { - Print("target_prices.txt line count is short. loaded=", i, " required=", TARGET_SIZE); - target_prices[0] = 0; // 不完全なファイルは無効扱い - } - } - else - { - Print("Failed to open file. Error code: ", GetLastError()); - } - } - else - { - Print("File does not exist: ", filename); - } - } - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit) -//+------------------------------------------------------------------+ -/** - * @brief 指定された注文タイプでペンディング注文を送信します。 - * - * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 - * @param price エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return 注文が正常に受理された場合はtrue、それ以外はfalse。 - */ -bool SendOrder(int orderType, double price, double tp, double sl) - { - MqlTradeRequest request = {}; - MqlTradeResult result = {}; - - request.magic = magic_number; - request.symbol = _Symbol; - request.volume = lot_size; - request.deviation = slippage; - request.type_filling = GetOrderFillingPolicy(_Symbol); - request.price = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.comment = RequestComment(orderType); - - string orderTypeStr = ""; - -// 指値または逆指値注文 - request.action = TRADE_ACTION_PENDING; - ApplyPendingOrderExpiration(request); - - switch(orderType) - { - case 1: // 順張り買い (buy-stop) - request.type = ORDER_TYPE_BUY_STOP; - orderTypeStr = "buy-stop"; - break; - - case 2: // 逆張り買い (buy-limit) - request.type = ORDER_TYPE_BUY_LIMIT; - orderTypeStr = "buy-limit"; - break; - - case 3: // 順張り売り (sell-stop) - request.type = ORDER_TYPE_SELL_STOP; - orderTypeStr = "sell-stop"; - break; - - case 4: // 逆張り売り (sell-limit) - request.type = ORDER_TYPE_SELL_LIMIT; - orderTypeStr = "sell-limit"; - break; - - default: // 不正なorderType - Print(__FUNCTION__, ": invalid orderType=", orderType); - return false; - } - -// 注文送信 - if(!OrderSend(request, result)) - { - Print(__FUNCTION__, ": OrderSend failed. err=", GetLastError()); - return false; - } - -// 注文成功確認 - if(result.retcode == TRADE_RETCODE_PLACED || - result.retcode == TRADE_RETCODE_DONE || - result.retcode == TRADE_RETCODE_DONE_PARTIAL) - { - Print(request.comment + " - " + orderTypeStr + " order success (ticket=", result.order, ")"); - return true; - } - else - { - Print(__FUNCTION__, ": OrderSend retcode=", result.retcode); - return false; - } - } - -//+------------------------------------------------------------------+ -//| 注文の執行ポリシーを取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief シンボルに対応する注文執行ポリシーを取得します。 - * - * @param symbol 対象シンボル。 - * @return 利用可能な執行ポリシー。IOC、FOK、RETURNの順で選択します。 - */ -ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol) - { - long fill_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); - if((fill_mode & SYMBOL_FILLING_IOC) != 0) - return ORDER_FILLING_IOC; - if((fill_mode & SYMBOL_FILLING_FOK) != 0) - return ORDER_FILLING_FOK; - return ORDER_FILLING_RETURN; - } - -//+------------------------------------------------------------------+ -//| ペンディング注文の有効期限を設定する関数 -//+------------------------------------------------------------------+ -/** - * @brief brokerが対応している範囲でpending orderにサーバー側の期限を設定します。 - * - * @param request 更新対象の取引リクエスト。 - */ -void ApplyPendingOrderExpiration(MqlTradeRequest &request) - { - int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); - long expiration_mode = SymbolInfoInteger(request.symbol, SYMBOL_EXPIRATION_MODE); - - if(expiration_seconds > 0 && - (expiration_mode & SYMBOL_EXPIRATION_SPECIFIED) != 0) - { - request.type_time = ORDER_TIME_SPECIFIED; - request.expiration = TimeCurrent() + expiration_seconds; - return; - } - - if(expiration_seconds > 0 && - (expiration_mode & SYMBOL_EXPIRATION_SPECIFIED_DAY) != 0) - { - request.type_time = ORDER_TIME_SPECIFIED_DAY; - request.expiration = TimeCurrent() + expiration_seconds; - return; - } - - if((expiration_mode & SYMBOL_EXPIRATION_DAY) != 0) - { - request.type_time = ORDER_TIME_DAY; - return; - } - - request.type_time = ORDER_TIME_GTC; - } - -//+------------------------------------------------------------------+ -//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与) -//+------------------------------------------------------------------+ -/** - * @brief 注文コメント文字列を生成します。 - * - * @param orderType 注文タイプ番号。 - * @return 注文タイプとPCローカル時刻を含むコメント文字列。 - */ -string RequestComment(int orderType) - { - datetime localTime = TimeLocal(); - string timeStr = TimeToString(localTime, TIME_DATE|TIME_MINUTES); - -// 先頭に 1,2,3,4 を付ける(例: "1 | PC Time: 2026.02.14 19:05") - return IntegerToString(orderType) + " | PC Time: " + timeStr; - } - -//+------------------------------------------------------------------+ -//| H4 market_stateに対して許可された注文タイプか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4 market_stateに対して指定注文タイプが許可されるか判定します。 - * - * @param orderType 注文タイプ番号。 - * @param trend_state H4 market_state(0..6)。 - * @return 許可される注文タイプならtrue。異常ボラまたは異常値ではfalse。 - */ -bool IsOrderTypeAllowedByTrend(const int orderType, const int trend_state) - { - switch(trend_state) - { - case MARKET_LOW_VOL_RANGE: - case MARKET_HIGH_VOL_RANGE: - return (orderType == 2 || orderType == 4); // Range: Buy Limit / Sell Limitのみ - - case MARKET_LOW_VOL_UP: - case MARKET_HIGH_VOL_UP: - return (orderType == 1 || orderType == 2); // Up: Buy Stop / Buy Limitのみ - - case MARKET_LOW_VOL_DOWN: - case MARKET_HIGH_VOL_DOWN: - return (orderType == 3 || orderType == 4); // Down: Sell Stop / Sell Limitのみ - - case MARKET_ABNORMAL_VOL_STOP: - default: - return false; // 異常ボラまたは異常値は新規注文しない - } - } - -//+------------------------------------------------------------------+ -//| target_prices.txt の価格が有効か判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief エントリー価格、利確価格、損切価格が有効値か判定します。 - * - * @param en エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return すべて0より大きい場合はtrue。 - */ -bool HasValidTargetPrices(const double en, const double tp, const double sl) - { - return (en > 0.0 && tp > 0.0 && sl > 0.0); - } - -//+------------------------------------------------------------------+ -//| 自EAの未約定注文数を数える関数 -//+------------------------------------------------------------------+ -/** - * @brief このEAが管理対象とする未約定注文数を数えます。 - * - * @return `_Symbol` と `magic_number` が一致する未約定注文数。 - */ -int CountMyPendingOrders() - { - int count = 0; - - for(int i = OrdersTotal() - 1; i >= 0; i--) - { - ulong ticket = OrderGetTicket(i); - if(ticket == 0) - continue; - - if(!OrderSelect(ticket)) - continue; - - if(OrderGetString(ORDER_SYMBOL) != _Symbol) - continue; - - if((int)OrderGetInteger(ORDER_MAGIC) != magic_number) - continue; - - count++; - } - - return count; - } - -//+------------------------------------------------------------------+ -//| 自EAのポジション数を数える関数 -//+------------------------------------------------------------------+ -/** - * @brief このEAが管理対象とする保有ポジション数を数えます。 - * - * @return `_Symbol` と `magic_number` が一致するポジション数。 - */ -int CountMyPositions() - { - int count = 0; - - for(int i = PositionsTotal() - 1; i >= 0; i--) - { - string symbol = PositionGetSymbol(i); - if(symbol == "") - continue; - - if(symbol != _Symbol) - continue; - - if((int)PositionGetInteger(POSITION_MAGIC) != magic_number) - continue; - - count++; - } - - return count; - } - -//+------------------------------------------------------------------+ -//| 自EAの注文+ポジション数を数える関数 -//+------------------------------------------------------------------+ -/** - * @brief このEAが使用中の注文数とポジション数の合計を返します。 - * - * @return 未約定注文数 + 保有ポジション数。 - */ -int CountMyUsed() - { - return CountMyPendingOrders() + CountMyPositions(); - } - - -//+------------------------------------------------------------------+ -//| 時間が経過したエントリー注文をキャンセルする関数 -//+------------------------------------------------------------------+ -/** - * @brief 実時間でENTRY_H1_LIMIT時間を超えた未約定注文をキャンセルします。 - * - * @return 1件以上キャンセルに成功した場合はtrue。 - * - * 対象は`_Symbol` と `magic_number` が一致する未約定注文のみです。 - */ -bool CancelExpiredOrders() - { - bool result = false; - -// 未決済注文の数を取得 - for(int i = OrdersTotal() - 1; i >= 0; i--) - { - ulong ticket = OrderGetTicket(i); - if(OrderSelect(ticket)) // 注文を選択 - { - if(OrderGetString(ORDER_SYMBOL) != _Symbol) - continue; - if(OrderGetInteger(ORDER_MAGIC) != magic_number) - continue; - - datetime open_time = (datetime)OrderGetInteger(ORDER_TIME_SETUP); - - int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); - if(open_time <= 0 || expiration_seconds <= 0) - continue; - - if(TimeCurrent() - open_time >= expiration_seconds) - { - MqlTradeRequest request = {}; - MqlTradeResult trade_result = {}; - request.action=TRADE_ACTION_REMOVE; // 取引操作タイプ - request.order = ticket; // 注文チケット - if(!OrderSend(request, trade_result)) // 削除リクエスト送信 - { - Print("Failed to delete order. Ticket: ", ticket, " Error: ", GetLastError()); - } - else - { - if(trade_result.retcode == TRADE_RETCODE_DONE) - { - Print("Order canceled successfully due to a time limit. Ticket: ", ticket); - result = true; // 削除成功フラグ - } - else - { - Print("Order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode); - } - } - } - } - } - return result; - } - -//+------------------------------------------------------------------+ -//| 時間が経過したポジションをクローズする関数 -//+------------------------------------------------------------------+ -/** - * @brief 実時間でCLOSE_H1_LIMIT時間を超えた保有ポジションをクローズします。 - * - * @return 1件以上クローズに成功した場合はtrue。 - * - * 対象は`_Symbol` と `magic_number` が一致するポジションのみです。 - */ -bool CloseExpiredPositions() - { - bool result = false; - -// ポジションを逆順でループ - for(int i = PositionsTotal() - 1; i >= 0; i--) - { - string position_symbol = PositionGetSymbol(i); - if(position_symbol!="") - { - // Magic Number とシンボルでフィルタリング - if(position_symbol != _Symbol) - continue; - if(PositionGetInteger(POSITION_MAGIC) == magic_number) - { - // ポジションのエントリー時刻を取得 - datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); - - int expiration_seconds = CLOSE_H1_LIMIT * PeriodSeconds(PERIOD_H1); - if(entry_time <= 0 || expiration_seconds <= 0) - continue; - - if(TimeCurrent() - entry_time >= expiration_seconds) - { - MqlTradeRequest request = {}; - MqlTradeResult trade_result = {}; - - // ポジションタイプに応じてリクエストを設定 - int position_type = (int)PositionGetInteger(POSITION_TYPE); - request.action = TRADE_ACTION_DEAL; - request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号 - request.symbol = position_symbol; // シンボル - request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ - request.price = (position_type == POSITION_TYPE_BUY) - ? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ - : SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ - request.deviation = slippage; - request.type = (position_type == POSITION_TYPE_BUY) - ? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ - : ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ - request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー - request.magic = magic_number; - - // 注文送信 - if(!OrderSend(request, trade_result)) - { - Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError()); - continue; - } - - // 結果の確認 - if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL) - { - Print("Position closed successfully due to a time limit. Ticket: ", request.position); - result = true; // 少なくとも1つ成功した場合 - } - else - { - Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode); - } - } - } - } - else - { - Print("Failed to select position at index ", i, ". Error: ", GetLastError()); - } - } - return result; - } diff --git a/Experts/MyProject/HIT-EA_refactor_ver4.mq5 b/Experts/MyProject/HIT-EA_refactor_ver4.mq5 deleted file mode 100644 index 958a263..0000000 --- a/Experts/MyProject/HIT-EA_refactor_ver4.mq5 +++ /dev/null @@ -1,2377 +0,0 @@ -//+------------------------------------------------------------------+ -//| HIT_EA.mq5 | -//| Copyright 2026, nanpin-martin.com | -//| https://www.nanpin-martin.com | -//+------------------------------------------------------------------+ -#property copyright "Copyright 2026, nanpin-martin.com" -#property link "https://nanpin-martin.com/" -#property version "1.01" - -//+------------------------------------------------------------------+ -//| バッチファイル実行用の設定 -//+------------------------------------------------------------------+ - -// Windowsプロセス起動・終了確認用 -#define CREATE_NO_WINDOW 0x08000000 -#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 -#define SYNCHRONIZE 0x00100000 -#define WAIT_OBJECT_0 0 -#define WAIT_TIMEOUT 258 -#define STILL_ACTIVE 259 - -struct STARTUPINFO_W - { - uint cb; - string lpReserved; - string lpDesktop; - string lpTitle; - uint dwX; - uint dwY; - uint dwXSize; - uint dwYSize; - uint dwXCountChars; - uint dwYCountChars; - uint dwFillAttribute; - uint dwFlags; - ushort wShowWindow; - ushort cbReserved2; - long lpReserved2; - long hStdInput; - long hStdOutput; - long hStdError; - }; - -struct PROCESS_INFORMATION - { - long hProcess; - long hThread; - uint dwProcessId; - uint dwThreadId; - }; - -#import "kernel32.dll" -int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); -int WaitForSingleObject(long hHandle, uint dwMilliseconds); -int GetExitCodeProcess(long hProcess, uint &lpExitCode); -int CloseHandle(long hObject); -long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); -#import - -// バッチファイルのパス -string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend -string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry - -// プロセス完了ファイルの設定 -string done_trend_file = "process_done_trend.txt"; -string done_entry_file = "process_done_entry.txt"; - -// Python出力ファイル -string trend_state_file = "trend_state.txt"; -string target_prices_file = "target_prices.txt"; - -// Python実行中を判定するための管理ファイル -string running_trend_file = "process_running_trend.txt"; -string running_entry_file = "process_running_entry.txt"; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| インプットの設定 -//+------------------------------------------------------------------+ - -//--- 入力パラメータ -input double lot_size = 0.01; // ロット数 -input double spread_limit = 60; // 許容スプレッド(point) -input int magic_number = 10001; // マジックナンバー -input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) -input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認 -input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可 -ulong slippage = 10; // スリッページ - -//--- 主要な定数・設定 -#define POSITION_LIMIT 48 -#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) -#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) -#define HISTORY_BARS 72 -#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す -#define M15_CONFIRM_BARS 20 -#define M15_MIN_ENTRY_ZONE_POINTS 10 -#define M15_MIN_BODY_RATIO 0.25 -#define M15_REJECTION_WICK_RATIO 0.35 -#define ENTRY_RETRY_SECONDS 60 -#define ENTRY_RETRY_LIMIT 10 -// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) -#define TARGET_SIZE 13 -#define DEFAULT_TARGET_PRICE 0.0 -#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) - -#define MARKET_LOW_VOL_RANGE 0 -#define MARKET_HIGH_VOL_RANGE 1 -#define MARKET_LOW_VOL_UP 2 -#define MARKET_HIGH_VOL_UP 3 -#define MARKET_LOW_VOL_DOWN 4 -#define MARKET_HIGH_VOL_DOWN 5 -#define MARKET_ABNORMAL_VOL_STOP 6 - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| EAの状態をまとめる構造体 -//+------------------------------------------------------------------+ - -struct EAState - { - int trend_state; // H4 market_state(0..6)を格納する変数 - int res_chk; // GPTの回答が正しく取得できたか - double en_price[5]; // エントリー基準価格 - double tp_price[5]; // 利益確定価格 - double sl_price[5]; // ロスカット基準価格 - bool load_trend_flg; // ★変更:トレンドを更新するタイミングか - bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか - int chk_cnt; // エントリー判定の試行回数 - datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 - datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 - datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻 - datetime last_chk; - }; - -EAState g_ea; // EA全体の状態をここで管理 - -// initial_order 用の初回処理フラグ -// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 -bool g_init_trend_pending = false; -bool g_init_entry_pending = false; - -// H4/H1のバー更新状態 -int g_pre_bars_H4 = 0; -int g_pre_bars_H1 = 0; -int g_pre_bars_M15 = 0; -bool g_bars_H1_check = false; -bool g_bars_M15_check = false; - -// ティックごとの価格情報 -struct TickContext - { - double ask; - double bid; - double spread; - double spread_points; - int digits; - }; - -// 外部Pythonプロセスの状態 -struct ExternalProcessState - { - long handle; - uint process_id; - bool active; - bool exit_code_ready; - uint exit_code; - datetime started_at; - }; - -ExternalProcessState g_trend_process; -ExternalProcessState g_entry_process; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| 起動時の処理 -//+------------------------------------------------------------------+ -/** - * @brief EA起動時の初期化処理を行います。 - * - * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は - * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 - * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 - */ -void OnInit() -// プロセス完了ファイルと実行中ファイルの状態を初期化 - { - PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); - PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); - - // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 - g_init_trend_pending = (initial_order == 1); - g_init_entry_pending = (initial_order == 1); - } - -//+------------------------------------------------------------------+ -//| 終了時の処理 -//+------------------------------------------------------------------+ -void OnDeinit(const int reason) - { - ResetExternalProcessState(g_trend_process); - ResetExternalProcessState(g_entry_process); - } - -//+------------------------------------------------------------------+ -//| ティック毎の処理 -//+------------------------------------------------------------------+ -/** - * @brief ティック受信ごとにEA全体の処理を制御します。 - * - * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 - * H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。 - * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 - */ -void OnTick() - { - TickContext ctx; - if(!GetTickContext(ctx)) - return; - - // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 - ManageExpiredTrades(); - - // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 - RecoverTimedOutPythonProcesses(); - - // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 - ProcessTrendUpdate(g_ea); - - // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 - if(!IsTrendResultReady()) - return; - - RefreshTrendState(g_ea); - - // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 - ProcessEntryUpdate(g_ea); - - // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 - ProcessM15EntryTimingUpdate(); - - UpdateStatusComment(ctx); - - // ここから下は新規注文に関する処理。 - if(!IsSpreadAllowed(ctx)) - return; - - if(!IsEntryResultReady()) - return; - - RefreshTargetPrices(g_ea); - ProcessEntryDecisionIfNeeded(g_ea, ctx); - } - -//+------------------------------------------------------------------+ -//| ティック情報を取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief 現在のAsk/Bid/スプレッド情報を取得してTickContextへ格納します。 - * - * @param ctx 取得したティック情報を格納する構造体参照。 - * @return 取得成功時はtrue、ティック情報を取得できない場合はfalse。 - */ -bool GetTickContext(TickContext &ctx) - { - MqlTick last_tick; - if(!SymbolInfoTick(_Symbol, last_tick)) - return false; - - ctx.ask = last_tick.ask; - ctx.bid = last_tick.bid; - ctx.spread_points = MathRound((ctx.ask - ctx.bid) / Point()); - ctx.spread = ctx.spread_points * Point(); - ctx.digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - - return true; - } - -//+------------------------------------------------------------------+ -//| 期限切れ注文・期限切れポジションを処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief 期限切れの未約定注文と保有ポジションを処理します。 - * - * 未約定注文はENTRY_H1_LIMIT時間、保有ポジションはCLOSE_H1_LIMIT時間を基準に判定します。 - * 新規注文条件とは独立して、OnTickの早い段階で実行される想定です。 - */ -void ManageExpiredTrades() - { - if(OrdersTotal() > 0) - CancelExpiredOrders(); - - if(PositionsTotal() > 0) - CloseExpiredPositions(); - } - -//+------------------------------------------------------------------+ -//| H4更新検知とトレンド判定Python起動を処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4新バーまたは初回起動を検知し、トレンド判定Pythonを起動します。 - * - * @param state EA全体の状態。トレンド更新開始フラグを更新します。 - * - * Python処理中はdoneファイルが存在しないため、CSVの上書きと二重起動を抑止します。 - */ -void ProcessTrendUpdate(EAState &state) - { - int current_bars_H4 = iBars(NULL, PERIOD_H4); - if(g_pre_bars_H4 == 0) - g_pre_bars_H4 = current_bars_H4; - - int bars_H4_change = current_bars_H4 - g_pre_bars_H4; - bool trend_trigger = (bars_H4_change > 0 || g_init_trend_pending); - - if(!trend_trigger) - return; - - if(RecordOHLCAndExecuteBatch_Trend(state)) - { - g_init_trend_pending = false; - g_pre_bars_H4 = current_bars_H4; - } - } - -//+------------------------------------------------------------------+ -//| トレンド判定Pythonの完了状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief H4トレンド判定Pythonの完了状態を確認します。 - * - * @return `process_done_trend.txt` が存在する場合はtrue、未完了の場合はfalse。 - */ -bool IsTrendResultReady() - { - return IsProcessResultReady(done_trend_file, running_trend_file, trend_state_file, "trend", g_trend_process); - } - -//+------------------------------------------------------------------+ -//| トレンド判定結果をEA状態へ反映する関数 -//+------------------------------------------------------------------+ -/** - * @brief 完了済みのH4トレンド判定結果をEA状態へ反映します。 - * - * @param state EA全体の状態。`trend_state` と最終更新時刻が更新されます。 - */ -void RefreshTrendState(EAState &state) - { - GetTrendState(state); - } - -//+------------------------------------------------------------------+ -//| H1更新検知とエントリー価格生成Python起動を処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief H1新バーまたは初回起動を検知し、エントリー価格生成Pythonを起動します。 - * - * @param state EA全体の状態。ターゲット価格更新開始フラグや判定リトライ状態を更新します。 - * - * Python処理中はdoneファイルが存在しないため、H1 CSVの上書きと二重起動を抑止します。 - */ -void ProcessEntryUpdate(EAState &state) - { - int current_bars_H1 = iBars(NULL, PERIOD_H1); - if(g_pre_bars_H1 == 0) - g_pre_bars_H1 = current_bars_H1; - - int bars_H1_change = current_bars_H1 - g_pre_bars_H1; - bool entry_trigger = (bars_H1_change > 0 || g_init_entry_pending); - - if(!entry_trigger) - return; - - if(RecordOHLCAndExecuteBatch_Entry(state)) - { - g_init_entry_pending = false; - g_bars_H1_check = true; - state.chk_cnt = 0; - state.last_chk = 0; - g_pre_bars_H1 = current_bars_H1; - } - } - -//+------------------------------------------------------------------+ -//| M15更新検知とエントリータイミング判定トリガーを処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief M15新バーを検知し、H1候補価格の発注判定を実行できる状態にします。 - * - * M15は方向判定を上書きする足ではなく、H4/H1で決めた候補価格を発注する - * タイミング確認として使います。初回は直近の確定M15足で一度だけ判定可能にします。 - */ -void ProcessM15EntryTimingUpdate() - { - int current_bars_M15 = iBars(NULL, PERIOD_M15); - if(current_bars_M15 <= 0) - return; - - if(g_pre_bars_M15 == 0) - { - g_pre_bars_M15 = current_bars_M15; - g_bars_M15_check = true; - return; - } - - int bars_M15_change = current_bars_M15 - g_pre_bars_M15; - if(bars_M15_change <= 0) - return; - - g_bars_M15_check = true; - g_pre_bars_M15 = current_bars_M15; - } - -//+------------------------------------------------------------------+ -//| チャートコメント文字列を作成する関数 -//+------------------------------------------------------------------+ -/** - * @brief チャート左上に表示するステータスメッセージを組み立てます。 - * - * @param ctx 現在のAsk/Bid/スプレッド情報。 - * @return Comment()へ渡す表示用文字列。 - */ -string BuildStatusMessage(TickContext &ctx) - { - string message = StringFormat( - " \nAsk: %." + IntegerToString(ctx.digits) + "f" - "\nBid: %." + IntegerToString(ctx.digits) + "f" - "\nSpread: %." + IntegerToString(ctx.digits) + "f" - "\nSpread Points: %.0f" - "\n\nLast Trend Update:\n %s" - "\n\nMarket State: %d (%s)" - "\n\nLast Target Update:\n %s" - "\n\nMy Used Count: %d / %d" - "\n\n[T1 Buy Stop ] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" - "\n[T2 Buy Limit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" - "\n[T3 Sell Stop] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" - "\n[T4 SellLimit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f\n", - ctx.ask, ctx.bid, ctx.spread, ctx.spread_points, - TimeToString(g_ea.last_trend_update, TIME_DATE | TIME_MINUTES), - g_ea.trend_state, MarketStateName(g_ea.trend_state), - TimeToString(g_ea.last_target_update, TIME_DATE | TIME_MINUTES), - CountMyUsed(), POSITION_LIMIT, - g_ea.en_price[1], g_ea.tp_price[1], g_ea.sl_price[1], - g_ea.en_price[2], g_ea.tp_price[2], g_ea.sl_price[2], - g_ea.en_price[3], g_ea.tp_price[3], g_ea.sl_price[3], - g_ea.en_price[4], g_ea.tp_price[4], g_ea.sl_price[4] - ); - return message; - } - -//+------------------------------------------------------------------+ -//| チャートコメントを更新する関数 -//+------------------------------------------------------------------+ -/** - * @brief チャート上のステータスコメントを更新します。 - * - * @param ctx 現在のAsk/Bid/スプレッド情報。 - */ -void UpdateStatusComment(TickContext &ctx) - { - Comment(BuildStatusMessage(ctx)); - } - -//+------------------------------------------------------------------+ -//| market_stateの表示名を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief H4 market_stateの人間可読名を返します。 - * - * @param market_state Pythonが出力したH4環境分類(0..6)。 - * @return 表示用ラベル。 - */ -string MarketStateName(const int market_state) - { - switch(market_state) - { - case MARKET_LOW_VOL_RANGE: - return "LOW_VOL_RANGE"; - case MARKET_HIGH_VOL_RANGE: - return "HIGH_VOL_RANGE"; - case MARKET_LOW_VOL_UP: - return "LOW_VOL_UP"; - case MARKET_HIGH_VOL_UP: - return "HIGH_VOL_UP"; - case MARKET_LOW_VOL_DOWN: - return "LOW_VOL_DOWN"; - case MARKET_HIGH_VOL_DOWN: - return "HIGH_VOL_DOWN"; - case MARKET_ABNORMAL_VOL_STOP: - return "ABNORMAL_VOL_STOP"; - default: - return "UNKNOWN"; - } - } - -//+------------------------------------------------------------------+ -//| スプレッドが許容範囲内か判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief 現在スプレッドが入力パラメータの許容範囲内か判定します。 - * - * @param ctx 現在のスプレッド情報。 - * @return 許容範囲内ならtrue、超過している場合はfalse。 - */ -bool IsSpreadAllowed(TickContext &ctx) - { - return (ctx.spread <= spread_limit * Point()); - } - -//+------------------------------------------------------------------+ -//| エントリー価格生成Pythonの完了状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief H1エントリー価格生成Pythonの完了状態を確認します。 - * - * @return `process_done_entry.txt` が存在する場合はtrue、未完了の場合はfalse。 - */ -bool IsEntryResultReady() - { - return IsProcessResultReady(done_entry_file, running_entry_file, target_prices_file, "entry", g_entry_process); - } - -//+------------------------------------------------------------------+ -//| ターゲット価格をEA状態へ反映する関数 -//+------------------------------------------------------------------+ -/** - * @brief 完了済みのH1エントリー価格生成結果をEA状態へ反映します。 - * - * @param state EA全体の状態。`res_chk` と各注文タイプのen/tp/slが更新されます。 - */ -void RefreshTargetPrices(EAState &state) - { - GetTargetPrices(state); - } - -//+------------------------------------------------------------------+ -//| エントリー判定が必要な場合のみ注文処理を実行する関数 -//+------------------------------------------------------------------+ -/** - * @brief エントリー判定タイミングに到達している場合のみ注文処理を実行します。 - * - * @param state EA全体の状態。 - * @param ctx 現在のAsk/Bid/スプレッド情報。 - * - * 判定前チェック、許可注文の送信、リトライ状態更新をまとめて制御します。 - */ -void ProcessEntryDecisionIfNeeded(EAState &state, TickContext &ctx) - { - if(!ShouldRunEntryDecision(state)) - return; - - g_bars_H1_check = false; - g_bars_M15_check = false; - state.last_chk = TimeCurrent(); - - if(!ValidateEntryPreconditions(state)) - return; - - int sent_success = SendAllowedEntryOrders(state, ctx); - UpdateEntryRetryState(state, sent_success); - } - -//+------------------------------------------------------------------+ -//| エントリー判定を実行するタイミングか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief エントリー判定を実行するタイミングか確認します。 - * - * @param state EA全体の状態。前回判定時刻を参照します。 - * @return H1候補が有効で、M15確定足が更新され、前回判定から一定秒数以上経過していればtrue。 - */ -bool ShouldRunEntryDecision(EAState &state) - { - return (g_bars_H1_check && g_bars_M15_check && TimeCurrent() - state.last_chk >= ENTRY_RETRY_SECONDS); - } - -//+------------------------------------------------------------------+ -//| エントリー判定前の共通チェックを行う関数 -//+------------------------------------------------------------------+ -/** - * @brief 新規注文前の共通条件を検証します。 - * - * @param state EA全体の状態。 - * @return 注文判定を続行できる場合はtrue、停止すべき場合はfalse。 - * - * `res_chk`、market_state、対象EAの注文/ポジション数上限を確認します。 - */ -bool ValidateEntryPreconditions(EAState &state) - { - if(state.res_chk != 1) - { - Print("[Entry Skip] target_prices invalid. res_chk=", state.res_chk); - state.chk_cnt = 0; - return false; - } - - if(state.trend_state == MARKET_ABNORMAL_VOL_STOP) - { - Print("[Entry Skip] market_state=6 (abnormal volatility). No new entry orders are sent."); - state.chk_cnt = 0; - return false; - } - - if(state.trend_state < MARKET_LOW_VOL_RANGE || state.trend_state > MARKET_ABNORMAL_VOL_STOP) - { - Print("[Entry Skip] invalid market_state=", state.trend_state); - state.chk_cnt = 0; - return false; - } - - if(IsTargetCandidateExpired(state)) - { - Print("[Entry Skip] H1 target candidate expired. loaded_at=", - TimeToString(state.target_loaded_at, TIME_DATE | TIME_SECONDS)); - state.chk_cnt = 0; - return false; - } - - int used = CountMyUsed(); - if(used >= POSITION_LIMIT) - { - Print("Position limit exceeded: used=", used, " limit=", POSITION_LIMIT); - return false; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| H1候補価格の有効期限を判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief H1候補価格がENTRY_H1_LIMIT時間を超えて古くなっていないか判定します。 - * - * @param state EA全体の状態。H1候補価格の読込時刻を参照します。 - * @return 候補価格が期限切れの場合はtrue。 - */ -bool IsTargetCandidateExpired(EAState &state) - { - if(state.target_loaded_at <= 0) - return false; - - int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); - if(expiration_seconds <= 0) - return false; - - return (TimeCurrent() - state.target_loaded_at >= expiration_seconds); - } - -//+------------------------------------------------------------------+ -//| 許可された注文タイプだけを順番に送信する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4 market_stateで許可された注文タイプだけを順番に送信します。 - * - * @param state EA全体の状態。各注文タイプのen/tp/slを参照します。 - * @param ctx 現在のAsk/Bid/スプレッド情報。 - * @return 送信成功した注文数。 - */ -int SendAllowedEntryOrders(EAState &state, TickContext &ctx) - { - int sent_success = 0; - - for(int t = 1; t <= 4; t++) - { - int used = CountMyUsed(); - if(used >= POSITION_LIMIT) - { - Print("Position limit reached while sending. used=", used, " limit=", POSITION_LIMIT); - break; - } - - if(!IsOrderTypeAllowedByTrend(t, state.trend_state)) - { - Print("[Skip] orderType=", t, " not allowed by market_state=", state.trend_state, - " (", MarketStateName(state.trend_state), ")"); - continue; - } - - if(TrySendEntryOrder(t, state, ctx)) - sent_success++; - } - - return sent_success; - } - -//+------------------------------------------------------------------+ -//| 注文タイプ1件分の価格検証と注文送信を行う関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプ1件分の価格検証と注文送信を行います。 - * - * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 - * @param state EA全体の状態。対象注文タイプのen/tp/slを参照します。 - * @param ctx 現在のAsk/Bid情報。 - * @return 注文送信に成功した場合はtrue、それ以外はfalse。 - */ -bool TrySendEntryOrder(const int orderType, EAState &state, TickContext &ctx) - { - string entry_type = EntryTypeName(orderType); - double cur_price = CurrentPriceForOrderType(orderType, ctx); - - double en = state.en_price[orderType]; - double tp = state.tp_price[orderType]; - double sl = state.sl_price[orderType]; - - if(!HasValidTargetPrices(en, tp, sl)) - { - Print("[Skip] invalid target prices. orderType=", orderType, - " en=", en, " tp=", tp, " sl=", sl); - return false; - } - - bool ok = IsTargetPriceOrderConditionMatched(orderType, ctx, en, tp, sl); - if(!ok) - { - Print("[No ", entry_type, "] cur=", cur_price, " en=", en, " tp=", tp, " sl=", sl); - return false; - } - - if(!IsM15EntryTimingConfirmed(orderType, ctx, en)) - { - Print("[No ", entry_type, "] M15 timing not confirmed. cur=", cur_price, " en=", en); - return false; - } - - if(!MeetsTradeDistanceRules(orderType, ctx, en, tp, sl)) - return false; - - Print("[", entry_type, " Order Try at ", cur_price, "] en=", en, " tp=", tp, " sl=", sl); - - if(SendOrder(orderType, en, tp, sl)) - { - Print("[", entry_type, " Order Sent] ticket ok. en=", en, " tp=", tp, " sl=", sl); - return true; - } - - Print("[", entry_type, " Order Failed] en=", en, " tp=", tp, " sl=", sl); - return false; - } - -//+------------------------------------------------------------------+ -//| 注文タイプ名を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプ番号に対応する表示名を返します。 - * - * @param orderType 注文タイプ番号。 - * @return 注文タイプの表示名。不正値の場合は"Unknown"。 - */ -string EntryTypeName(const int orderType) - { - switch(orderType) - { - case 1: - return "Buy Stop"; - case 2: - return "Buy Limit"; - case 3: - return "Sell Stop"; - case 4: - return "Sell Limit"; - default: - return "Unknown"; - } - } - -//+------------------------------------------------------------------+ -//| 注文タイプに応じた現在価格を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプに応じて価格比較に使う現在価格を返します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @return 買い系注文ではAsk、売り系注文ではBid。 - */ -double CurrentPriceForOrderType(const int orderType, TickContext &ctx) - { - if(orderType == 1 || orderType == 2) - return ctx.ask; - - return ctx.bid; - } - -//+------------------------------------------------------------------+ -//| 注文タイプごとの価格整合条件を判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプごとの現在価格・エントリー・TP・SLの大小関係を検証します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @param en エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return 注文タイプの価格条件を満たす場合はtrue。 - */ -bool IsTargetPriceOrderConditionMatched(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) - { - switch(orderType) - { - case 1: // Buy Stop - return (ctx.ask < en && tp > en && sl < en); - - case 2: // Buy Limit - return (ctx.ask > en && tp > en && sl < en); - - case 3: // Sell Stop - return (ctx.bid > en && tp < en && sl > en); - - case 4: // Sell Limit - return (ctx.bid < en && tp < en && sl > en); - - default: - return false; - } - } - -//+------------------------------------------------------------------+ -//| M15確定足によるエントリータイミング確認 -//+------------------------------------------------------------------+ -/** - * @brief H1候補価格に対してM15の発注タイミングが整っているか判定します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @param en H1で決めたエントリー候補価格。 - * @return M15確認条件を満たす場合はtrue。 - * - * H4/H1の方向判断は維持し、M15では「候補価格に近い」「直近確定足が - * 順張り/反転の根拠を持つ」ことだけを確認します。 - */ -bool IsM15EntryTimingConfirmed(const int orderType, TickContext &ctx, const double en) - { - if(!use_m15_entry_filter) - return true; - - MqlRates rates[]; - int copied = CopyRates(_Symbol, PERIOD_M15, OHLC_START_SHIFT, M15_CONFIRM_BARS, rates); - if(copied < 3) - { - Print("[M15 Filter] insufficient M15 bars. copied=", copied); - return false; - } - - int last_index = copied - 1; - int prev_index = copied - 2; - - double avg_range = AverageM15Range(rates, copied); - double min_zone = M15_MIN_ENTRY_ZONE_POINTS * Point(); - double entry_zone = avg_range * m15_entry_zone_atr_multiplier; - if(entry_zone < min_zone) - entry_zone = min_zone; - - double cur_price = CurrentPriceForOrderType(orderType, ctx); - if(MathAbs(cur_price - en) > entry_zone) - { - Print("[M15 Filter] ", EntryTypeName(orderType), " is not near entry zone. cur=", - cur_price, " en=", en, " zone=", entry_zone); - return false; - } - - return IsM15SignalAligned(orderType, rates[prev_index], rates[last_index], en, entry_zone); - } - -//+------------------------------------------------------------------+ -//| M15平均レンジを計算する関数 -//+------------------------------------------------------------------+ -/** - * @brief M15確定足の平均レンジを計算します。 - * - * @param rates M15のMqlRates配列。 - * @param count 使用する本数。 - * @return 平均レンジ。算出不能な場合は最小ゾーン幅を返します。 - */ -double AverageM15Range(const MqlRates &rates[], const int count) - { - double total_range = 0.0; - int used = 0; - - for(int i = 0; i < count; i++) - { - double range = rates[i].high - rates[i].low; - if(range <= 0.0) - continue; - - total_range += range; - used++; - } - - if(used <= 0) - return M15_MIN_ENTRY_ZONE_POINTS * Point(); - - return total_range / used; - } - -//+------------------------------------------------------------------+ -//| 注文タイプごとのM15シグナル方向を判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文タイプごとにM15確定足の勢い・反転根拠を確認します。 - * - * @param orderType 注文タイプ番号。 - * @param prev_bar 1本前のM15確定足。 - * @param last_bar 直近のM15確定足。 - * @param en H1で決めたエントリー候補価格。 - * @param entry_zone M15平均レンジから算出した候補価格付近の許容幅。 - * @return 注文タイプに沿ったM15根拠があればtrue。 - */ -bool IsM15SignalAligned(const int orderType, const MqlRates &prev_bar, const MqlRates &last_bar, const double en, const double entry_zone) - { - double range = SafeBarRange(last_bar); - double body_ratio = MathAbs(last_bar.close - last_bar.open) / range; - double upper_wick_ratio = (last_bar.high - MathMax(last_bar.open, last_bar.close)) / range; - double lower_wick_ratio = (MathMin(last_bar.open, last_bar.close) - last_bar.low) / range; - - bool bullish = (last_bar.close > last_bar.open); - bool bearish = (last_bar.close < last_bar.open); - bool strong_body = (body_ratio >= M15_MIN_BODY_RATIO); - bool bullish_break = (last_bar.close > prev_bar.high); - bool bearish_break = (last_bar.close < prev_bar.low); - bool lower_rejection = (lower_wick_ratio >= M15_REJECTION_WICK_RATIO); - bool upper_rejection = (upper_wick_ratio >= M15_REJECTION_WICK_RATIO); - - switch(orderType) - { - case 1: // Buy Stop: M15の上方向モメンタムを確認 - return (bullish && (bullish_break || strong_body) && last_bar.close <= en + entry_zone); - - case 2: // Buy Limit: 候補価格付近で下ヒゲ反転または買い戻しを確認 - return (last_bar.low <= en + entry_zone && bullish && - (lower_rejection || bullish_break || strong_body)); - - case 3: // Sell Stop: M15の下方向モメンタムを確認 - return (bearish && (bearish_break || strong_body) && last_bar.close >= en - entry_zone); - - case 4: // Sell Limit: 候補価格付近で上ヒゲ反転または売り戻しを確認 - return (last_bar.high >= en - entry_zone && bearish && - (upper_rejection || bearish_break || strong_body)); - - default: - return false; - } - } - -//+------------------------------------------------------------------+ -//| ローソク足レンジを安全に取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief ゼロ除算を避けるため、最小値を持つローソク足レンジを返します。 - * - * @param bar 対象ローソク足。 - * @return high-low。0以下の場合はPoint()を返します。 - */ -double SafeBarRange(const MqlRates &bar) - { - double range = bar.high - bar.low; - if(range <= 0.0) - return Point(); - - return range; - } - -//+------------------------------------------------------------------+ -//| brokerの最小距離制約を満たすか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief pending価格、TP、SLがstop level / freeze levelの最小距離を満たすか判定します。 - * - * @param orderType 注文タイプ番号。 - * @param ctx 現在のAsk/Bid情報。 - * @param en エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return 最小距離を満たす場合はtrue。 - */ -bool MeetsTradeDistanceRules(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) - { - int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); - int freeze_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL); - int min_level = stop_level; - if(freeze_level > min_level) - min_level = freeze_level; - double min_distance = min_level * Point(); - - if(min_distance <= 0.0) - return true; - - double cur_price = CurrentPriceForOrderType(orderType, ctx); - string entry_type = EntryTypeName(orderType); - - if(MathAbs(en - cur_price) < min_distance) - { - Print("[Skip] ", entry_type, " entry is too close. cur=", cur_price, - " en=", en, " min_distance=", min_distance); - return false; - } - - if(MathAbs(tp - en) < min_distance) - { - Print("[Skip] ", entry_type, " TP is too close. en=", en, - " tp=", tp, " min_distance=", min_distance); - return false; - } - - if(MathAbs(en - sl) < min_distance) - { - Print("[Skip] ", entry_type, " SL is too close. en=", en, - " sl=", sl, " min_distance=", min_distance); - return false; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| 注文送信結果に応じてリトライ状態を更新する関数 -//+------------------------------------------------------------------+ -/** - * @brief 注文送信結果に応じてエントリー判定のリトライ状態を更新します。 - * - * @param state EA全体の状態。`chk_cnt` を更新します。 - * @param sent_success 今回送信に成功した注文数。 - * - * 注文が1件も送信されなかった場合、最大10回まで60秒間隔で再判定します。 - */ -void UpdateEntryRetryState(EAState &state, const int sent_success) - { - if(sent_success > 0) - { - state.chk_cnt = 0; - return; - } - - state.chk_cnt += 1; - Print("[Retry] no order sent. chk_cnt=", state.chk_cnt, "/", ENTRY_RETRY_LIMIT, - " (wait next M15 bar)"); - - if(state.chk_cnt < ENTRY_RETRY_LIMIT) - { - g_bars_H1_check = true; // 次のM15確定足で再度エントリー判定を実行する - return; - } - - Print("[Retry End] reached max tries. reset chk_cnt."); - state.chk_cnt = 0; - } - -//+------------------------------------------------------------------+ -//| "process_done.txt"を作成する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したdoneファイルを作成します。 - * - * @param name 作成するファイル名。MT5のMQL5\Files配下を基準に扱います。 - */ -void CreateDoneFile(const string name) - { - if(!FileIsExist(name)) - { - int h = FileOpen(name, FILE_WRITE|FILE_TXT); - if(h != INVALID_HANDLE) - FileClose(h); - else - Print("Failed to create file: ", name, " err=", GetLastError()); - } - } - -//+------------------------------------------------------------------+ -//| "process_done.txt"を削除する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したdoneファイルを削除します。 - * - * @param name 削除するファイル名。存在しない場合は何もしません。 - */ -void DeleteDoneFile(const string name) - { - if(FileIsExist(name)) - FileDelete(name); - } - - -//+------------------------------------------------------------------+ -//| "process_done.txt"の存在を確認し、存在する場合はtrueを返す関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したdoneファイルの存在有無を確認します。 - * - * @param name 確認するファイル名。 - * @return ファイルが存在する場合はtrue、存在しない場合はfalse。 - */ -bool CheckDoneFile(const string name) - { - return FileIsExist(name); - } - -//+------------------------------------------------------------------+ -//| Python実行中ファイルを作成する関数 -//+------------------------------------------------------------------+ -/** - * @brief Python起動時刻を実行中ファイルへ記録します。 - * - * @param name 作成するrunningファイル名。 - */ -void CreateRunningFile(const string name, const uint process_id) - { - int h = FileOpen(name, FILE_WRITE | FILE_TXT); - if(h != INVALID_HANDLE) - { - FileWrite(h, IntegerToString((long)TimeCurrent())); - FileWrite(h, IntegerToString((long)process_id)); - FileClose(h); - } - else - Print("Failed to create running file: ", name, " err=", GetLastError()); - } - -//+------------------------------------------------------------------+ -//| Python実行中ファイルを削除する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定したrunningファイルを削除します。 - * - * @param name 削除するrunningファイル名。 - */ -void DeleteRunningFile(const string name) - { - if(FileIsExist(name)) - FileDelete(name); - } - -//+------------------------------------------------------------------+ -//| Python実行開始時刻を読み込む関数 -//+------------------------------------------------------------------+ -/** - * @brief runningファイルに記録されたPython起動時刻を読み込みます。 - * - * @param name 読み込むrunningファイル名。 - * @return 読み込めた起動時刻。失敗時は0。 - */ -datetime LoadRunningStartedAt(const string name) - { - if(!FileIsExist(name)) - return 0; - - int h = FileOpen(name, FILE_READ | FILE_TXT); - if(h == INVALID_HANDLE) - { - Print("Failed to open running file: ", name, " err=", GetLastError()); - return 0; - } - - string line = FileReadString(h); - FileClose(h); - return (datetime)StringToInteger(line); - } - -//+------------------------------------------------------------------+ -//| Python実行プロセスIDを読み込む関数 -//+------------------------------------------------------------------+ -/** - * @brief runningファイルに記録されたプロセスIDを読み込みます。 - * - * @param name 読み込むrunningファイル名。 - * @return 読み込めたプロセスID。旧形式または失敗時は0。 - */ -uint LoadRunningProcessId(const string name) - { - if(!FileIsExist(name)) - return 0; - - int h = FileOpen(name, FILE_READ | FILE_TXT); - if(h == INVALID_HANDLE) - { - Print("Failed to open running file: ", name, " err=", GetLastError()); - return 0; - } - - FileReadString(h); // started_at - if(FileIsEnding(h)) - { - FileClose(h); - return 0; - } - - string line = FileReadString(h); - FileClose(h); - return (uint)StringToInteger(line); - } - -//+------------------------------------------------------------------+ -//| Python実行中ファイルがタイムアウトしているか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief runningファイルの起動時刻からPython待ち上限を超えているか判定します。 - * - * @param name 判定するrunningファイル名。 - * @return タイムアウトしている場合はtrue。 - */ -bool IsRunningFileTimedOut(const string name) - { - datetime started_at = LoadRunningStartedAt(name); - if(started_at <= 0) - return true; - - return (TimeCurrent() - started_at >= PYTHON_TIMEOUT_SECONDS); - } - -//+------------------------------------------------------------------+ -//| 外部プロセス状態を初期値に戻す関数 -//+------------------------------------------------------------------+ -void ResetExternalProcessState(ExternalProcessState &process) - { - if(process.handle != 0) - CloseHandle(process.handle); - - process.handle = 0; - process.process_id = 0; - process.active = false; - process.exit_code_ready = false; - process.exit_code = 0; - process.started_at = 0; - } - -//+------------------------------------------------------------------+ -//| runningファイルのPIDからプロセスハンドルを復元する関数 -//+------------------------------------------------------------------+ -bool AttachRunningProcess(const string running_file, const string label, ExternalProcessState &process) - { - if(process.active && process.handle != 0) - return true; - - uint process_id = LoadRunningProcessId(running_file); - if(process_id == 0) - return false; - - long handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, 0, process_id); - if(handle == 0) - { - Print("[", label, "] failed to open running process. pid=", process_id, " err=", GetLastError()); - return false; - } - - process.handle = handle; - process.process_id = process_id; - process.active = true; - process.exit_code_ready = false; - process.exit_code = STILL_ACTIVE; - process.started_at = LoadRunningStartedAt(running_file); - return true; - } - -//+------------------------------------------------------------------+ -//| 外部プロセスの終了状態を更新する関数 -//+------------------------------------------------------------------+ -bool UpdateExternalProcessStatus(ExternalProcessState &process, const string label) - { - if(!process.active || process.handle == 0) - return true; - - int wait_result = WaitForSingleObject(process.handle, 0); - if(wait_result == WAIT_TIMEOUT) - return false; - - uint exit_code = 1; - if(wait_result == WAIT_OBJECT_0) - { - if(GetExitCodeProcess(process.handle, exit_code) == 0) - { - Print("[", label, "] failed to get process exit code. err=", GetLastError()); - exit_code = 1; - } - } - else - { - Print("[", label, "] WaitForSingleObject failed. result=", wait_result, " err=", GetLastError()); - exit_code = 1; - } - - CloseHandle(process.handle); - process.handle = 0; - process.active = false; - process.exit_code_ready = true; - process.exit_code = exit_code; - Print("[", label, "] process finished. pid=", process.process_id, " exit_code=", exit_code); - return true; - } - -//+------------------------------------------------------------------+ -//| 外部プロセスがタイムアウトしているか判定する関数 -//+------------------------------------------------------------------+ -bool IsExternalProcessTimedOut(ExternalProcessState &process, const string running_file) - { - datetime started_at = process.started_at; - if(started_at <= 0) - started_at = LoadRunningStartedAt(running_file); - if(started_at <= 0) - return true; - - return (TimeCurrent() - started_at >= PYTHON_TIMEOUT_SECONDS); - } - -//+------------------------------------------------------------------+ -//| バッチファイルをプロセスハンドル付きで起動する関数 -//+------------------------------------------------------------------+ -bool StartBatchProcess(const string bat_file, const string running_file, const string label, ExternalProcessState &process) - { - STARTUPINFO_W startup_info = {}; - PROCESS_INFORMATION process_info = {}; - - startup_info.cb = (uint)sizeof(startup_info); - - string cmd_exe = "C:\\Windows\\System32\\cmd.exe"; - string command_line = "\"" + cmd_exe + "\" /c \"\"" + bat_file + "\"\""; - - int created = CreateProcessW( - cmd_exe, - command_line, - 0, - 0, - 0, - CREATE_NO_WINDOW, - 0, - "C:\\ea_py", - startup_info, - process_info - ); - - if(created == 0 || process_info.hProcess == 0) - { - Print("[", label, "] CreateProcessW failed. file=", bat_file, " err=", GetLastError()); - ResetExternalProcessState(process); - DeleteRunningFile(running_file); - return false; - } - - if(process_info.hThread != 0) - CloseHandle(process_info.hThread); - - process.handle = process_info.hProcess; - process.process_id = process_info.dwProcessId; - process.active = true; - process.exit_code_ready = false; - process.exit_code = STILL_ACTIVE; - process.started_at = TimeCurrent(); - - CreateRunningFile(running_file, process.process_id); - Print("[", label, "] process started. pid=", process.process_id, " file=", bat_file); - return true; - } - -//+------------------------------------------------------------------+ -//| EA起動時のdoneファイル状態を整える関数 -//+------------------------------------------------------------------+ -/** - * @brief EA起動時にPython実行中状態を壊さない範囲でdoneファイルを準備します。 - * - * @param done_file 完了判定ファイル名。 - * @param running_file 実行中判定ファイル名。 - * @param label ログ表示用ラベル。 - */ -void PrepareDoneFileOnInit(const string done_file, const string running_file, const string label) - { - if(CheckDoneFile(done_file)) - { - DeleteRunningFile(running_file); - return; - } - - if(FileIsExist(running_file)) - { - if(IsRunningFileTimedOut(running_file)) - { - Print("[", label, "] stale running file found on init. Remove marker without creating done."); - DeleteRunningFile(running_file); - } - else - Print("[", label, "] Python seems to be running on init. Keep waiting."); - return; - } - - Print("[", label, "] done file not found on init. Keep as not-ready until the next trigger."); - } - -//+------------------------------------------------------------------+ -//| Python完了状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief doneファイルがあれば完了扱いにし、runningファイルを片付けます。 - * - * @param done_file 完了判定ファイル名。 - * @param running_file 実行中判定ファイル名。 - * @return 完了済みの場合はtrue。 - */ -bool IsProcessResultReady(const string done_file, const string running_file, const string result_file, const string label, ExternalProcessState &process) - { - if(process.active || FileIsExist(running_file)) - { - if(process.active || AttachRunningProcess(running_file, label, process)) - { - if(!UpdateExternalProcessStatus(process, label)) - return false; - - DeleteRunningFile(running_file); - if(process.exit_code_ready && process.exit_code != 0) - { - Print("[", label, "] process failed. exit_code=", process.exit_code); - return false; - } - } - else if(!CheckDoneFile(done_file)) - { - return false; - } - } - - if(!CheckDoneFile(done_file)) - return false; - - if(!FileIsExist(result_file)) - { - Print("[", label, "] done exists but result file is missing: ", result_file); - return false; - } - - DeleteRunningFile(running_file); - return true; - } - -//+------------------------------------------------------------------+ -//| Python開始可能状態を返す関数 -//+------------------------------------------------------------------+ -/** - * @brief done/runningファイル状態からPythonを新規起動できるか判定します。 - * - * @param done_file 完了判定ファイル名。 - * @param running_file 実行中判定ファイル名。 - * @param label ログ表示用ラベル。 - * @return 起動可能な場合はtrue。 - */ -bool IsProcessStartAllowed(const string done_file, const string running_file, const string label, ExternalProcessState &process) - { - if(process.active || FileIsExist(running_file)) - { - if(process.active || AttachRunningProcess(running_file, label, process)) - { - if(!UpdateExternalProcessStatus(process, label)) - { - if(IsExternalProcessTimedOut(process, running_file)) - Print("[", label, "] Python timed out, but the process is still running. Keep waiting."); - else - Print("[", label, "] Python is still running. Skip execute."); - return false; - } - - DeleteRunningFile(running_file); - if(process.exit_code_ready && process.exit_code != 0) - Print("[", label, "] previous process failed. exit_code=", process.exit_code, ". Retry is allowed."); - } - else - { - if(CheckDoneFile(done_file)) - { - DeleteRunningFile(running_file); - return true; - } - - if(IsRunningFileTimedOut(running_file)) - { - Print("[", label, "] stale running marker without live process. Retry is allowed."); - DeleteRunningFile(running_file); - return true; - } - - Print("[", label, "] running marker exists, but process cannot be verified yet. Skip execute."); - return false; - } - } - - if(CheckDoneFile(done_file)) - { - DeleteRunningFile(running_file); - return true; - } - - Print("[", label, "] done file missing without running marker. Start new process."); - return true; - } - -//+------------------------------------------------------------------+ -//| タイムアウトしたPython待ちを復旧する関数 -//+------------------------------------------------------------------+ -/** - * @brief Pythonがdoneファイルを返さない状態を検知し、再実行できる状態へ戻します。 - */ -void RecoverTimedOutPythonProcesses() - { - if(RecoverTimedOutProcess(done_trend_file, running_trend_file, "trend", g_trend_process)) - { - g_ea.load_trend_flg = false; - g_init_trend_pending = true; - } - - if(RecoverTimedOutProcess(done_entry_file, running_entry_file, "entry", g_entry_process)) - { - g_ea.load_target_flg = false; - g_bars_H1_check = false; - g_bars_M15_check = false; - g_ea.chk_cnt = 0; - g_init_entry_pending = true; - } - } - -//+------------------------------------------------------------------+ -//| 個別Python処理のタイムアウトを復旧する関数 -//+------------------------------------------------------------------+ -/** - * @brief 1種類のPython処理について、実行中ファイルのタイムアウトを検知します。 - * - * @param done_file 完了判定ファイル名。 - * @param running_file 実行中判定ファイル名。 - * @param label ログ表示用ラベル。 - * @return タイムアウト復旧を行った場合はtrue。 - */ -bool RecoverTimedOutProcess(const string done_file, const string running_file, const string label, ExternalProcessState &process) - { - if(CheckDoneFile(done_file) && !process.active) - { - DeleteRunningFile(running_file); - return false; - } - - if(!process.active && !FileIsExist(running_file)) - return false; - - if(process.active || AttachRunningProcess(running_file, label, process)) - { - if(!UpdateExternalProcessStatus(process, label)) - { - if(IsExternalProcessTimedOut(process, running_file)) - Print("[", label, "] Python exceeded ", PYTHON_TIMEOUT_SECONDS, - " seconds, but the process is still running. Keep waiting."); - return false; - } - - DeleteRunningFile(running_file); - if(process.exit_code_ready && process.exit_code != 0) - { - Print("[", label, "] Python process failed. Retry on next trigger."); - return true; - } - - if(!CheckDoneFile(done_file)) - { - Print("[", label, "] Python process ended without done file. Retry on next trigger."); - return true; - } - - return false; - } - - if(!IsRunningFileTimedOut(running_file)) - return false; - - Print("[", label, "] Python did not finish within ", - PYTHON_TIMEOUT_SECONDS, " seconds, and no live process was found. Retry is allowed."); - DeleteRunningFile(running_file); - return true; - } - -//+------------------------------------------------------------------+ -//| 最新のOHLCデータを取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief 指定時間足のOHLCデータを取得します。 - * - * @param times 取得したバー時刻を格納する配列。 - * @param open_prices 取得したOpen価格を格納する配列。 - * @param high_prices 取得したHigh価格を格納する配列。 - * @param low_prices 取得したLow価格を格納する配列。 - * @param close_prices 取得したClose価格を格納する配列。 - * @param tf 取得対象の時間足。 - * @param bars_count 取得するバー本数。 - * @return 取得に成功した場合はtrue、失敗した場合はfalse。 - * - * `OHLC_START_SHIFT=1` のため、形成中のバーではなく確定足から取得します。 - */ -bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count) - { - ArrayResize(times, 0); - ArrayResize(open_prices, 0); - ArrayResize(high_prices, 0); - ArrayResize(low_prices, 0); - ArrayResize(close_prices, 0); - - if(bars_count < 2) - return false; - -// OHLCデータを取得 -// CopyTime と CopyRates を別々に呼ばず、MqlRates の time/open/high/low/close を同一配列から取得する。 -// OHLC_START_SHIFT=1 のため、形成中の0本目ではなく確定足からPythonへ渡す。 - MqlRates rates[]; - int copied = CopyRates(_Symbol, tf, OHLC_START_SHIFT, bars_count, rates); - if(copied <= 0) - { - Print(__FUNCTION__, ": Failed to copy rates data (bars=", bars_count, ") err=", GetLastError()); - return false; - } - - ArrayResize(times, copied); - ArrayResize(open_prices, copied); - ArrayResize(high_prices, copied); - ArrayResize(low_prices, copied); - ArrayResize(close_prices, copied); - -// 必要なデータを配列に格納 - for(int i = 0; i < copied; i++) - { - times[i] = rates[i].time; - open_prices[i] = rates[i].open; - high_prices[i] = rates[i].high; - low_prices[i] = rates[i].low; - close_prices[i] = rates[i].close; - } - - return true; - } - -//+------------------------------------------------------------------+ -//| "ohlc.csv"を出力する関数 -//+------------------------------------------------------------------+ -/** - * @brief OHLC配列をPython入力用CSVとして出力します。 - * - * @param filename 出力するCSVファイル名。 - * @param times バー時刻配列。 - * @param open_prices Open価格配列。 - * @param high_prices High価格配列。 - * @param low_prices Low価格配列。 - * @param close_prices Close価格配列。 - * @return CSV出力に成功した場合はtrue。 - */ -bool RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[]) - { -// 出力先ファイル名 -// string filename = "ohlc.csv"; - -// ファイルを "書き込みモード" でオープン (テキスト/ANSI) - int fileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI); - -// ファイルが開けたかチェック - if(fileHandle == INVALID_HANDLE) - { - Print(__FUNCTION__, " : Failed to open file: ", GetLastError()); - return false; - } - -// 配列サイズを取得 (times, open, high, low, close の要素数は同じ前提) - int size = ArraySize(times); - -// ヘッダー行を追加 - FileWrite(fileHandle, "Time,Open,High,Low,Close"); - -// 小数点の桁数を取得 - int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - -// フォーマット文字列を動的に生成 - string formatString = StringFormat("%%s,%%.%df,%%.%df,%%.%df,%%.%df", digits, digits, digits, digits); - -// 1行ずつ "時刻, Open, High, Low, Close" の形式で書き込み - for(int i = 0; i < size; i++) - { - // 時間をフォーマット - string timeStr = TimeToString(times[i], TIME_DATE | TIME_MINUTES); - - // 1行分の文字列を生成 (小数点以下の桁数を `digits` に調整) - string line = StringFormat(formatString, - timeStr, open_prices[i], high_prices[i], low_prices[i], close_prices[i]); - - // ファイルに書き込み (改行付き) - FileWrite(fileHandle, line); - } - -// 書き込み終了後、ファイルを閉じる - FileClose(fileHandle); - return true; - } - -//+------------------------------------------------------------------+ -//| バッチファイル(Pythonスクリプト)を実行する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4トレンド判定用バッチファイルを起動します。 - * - * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 - * - * 起動直前に`process_done_trend.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。 - */ -bool ExecuteBatchTrend() - { - if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process)) - return false; - - DeleteDoneFile(done_trend_file); - return StartBatchProcess(get_trend_reply_bat, running_trend_file, "trend", g_trend_process); - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief H1エントリー価格生成用バッチファイルを起動します。 - * - * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 - * - * 起動直前に`process_done_entry.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。 - */ -bool ExecuteBatchEntry() - { - if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process)) - return false; - - DeleteDoneFile(done_entry_file); - return StartBatchProcess(get_entry_reply_bat, running_entry_file, "entry", g_entry_process); - } - - -//+------------------------------------------------------------------+ -//| "ohlc.csv"を出力後、バッチファイルの実行する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4 OHLCをCSV出力し、トレンド判定Pythonを起動します。 - * - * @param state EA全体の状態。トレンド読込待ちフラグを更新します。 - * @return CSV出力とバッチ起動が開始できた場合はtrue。 - */ -bool RecordOHLCAndExecuteBatch_Trend(EAState &state) - { - if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process)) - { - Print("Trend Python is still running. Skip H4 CSV update."); - return false; - } - - datetime times[]; - double open_prices[], high_prices[], low_prices[], close_prices[]; - - if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, - PERIOD_H4, HISTORY_BARS)) - { Print("GetLatestOHLC(H4) failed."); return false; } - - if(!RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices)) - { Print("RecordOHLC(H4) failed."); return false; } - - if(!ExecuteBatchTrend()) - return false; - - state.load_trend_flg = true; - return true; - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief H1 OHLCをCSV出力し、エントリー価格生成Pythonを起動します。 - * - * @param state EA全体の状態。ターゲット価格読込待ちフラグを更新します。 - * @return CSV出力とバッチ起動が開始できた場合はtrue。 - */ -bool RecordOHLCAndExecuteBatch_Entry(EAState &state) - { - if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process)) - { - Print("Entry Python is still running. Skip H1 CSV update."); - return false; - } - - datetime times[]; - double open_prices[], high_prices[], low_prices[], close_prices[]; - - if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, - PERIOD_H1, HISTORY_BARS)) - { Print("GetLatestOHLC(H1) failed."); return false; } - - if(!RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices)) - { Print("RecordOHLC(H1) failed."); return false; } - - if(!ExecuteBatchEntry()) - return false; - - state.load_target_flg = true; - return true; - } - - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief Pythonが出力したトレンド判定結果を必要なタイミングで読み込みます。 - * - * @param state EA全体の状態。H4 market_stateと更新フラグを更新します。 - * @return 現状は常にtrue。 - */ -bool GetTrendState(EAState &state) - { - bool ProcessDone = CheckDoneFile(done_trend_file); - if(ProcessDone && g_ea.load_trend_flg) - { - int trend_state; - LoadTrendState(trend_state); - state.trend_state = trend_state; - Print("market_state: ", state.trend_state, " (", MarketStateName(state.trend_state), ")"); - g_ea.load_trend_flg = false; - g_ea.last_trend_update = TimeLocal(); - } - return true; - } - -//+------------------------------------------------------------------+ -//| | -//+------------------------------------------------------------------+ -/** - * @brief `trend_state.txt` からH4 market_state判定値を読み込みます。 - * - * @param trend_state 読み込んだ値を格納する参照。0..6のmarket_state。 - * - * ファイル未存在、読込失敗、異常値の場合は安全側として6(新規停止)を設定します。 - */ -void LoadTrendState(int &trend_state) - { - string filename = trend_state_file; - trend_state = MARKET_ABNORMAL_VOL_STOP; - - if(FileIsExist(filename)) - { - int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); - if(filehandle != INVALID_HANDLE) - { - string line = FileReadString(filehandle); - int value = (int)StringToInteger(line); - if(value >= MARKET_LOW_VOL_RANGE && value <= MARKET_ABNORMAL_VOL_STOP) - trend_state = value; - else - Print("Invalid market_state value: ", line, ". Use 6."); - FileClose(filehandle); - } - else - { Print("Failed to open trend_state.txt"); trend_state = MARKET_ABNORMAL_VOL_STOP; } - } - else - { Print("trend_state.txt not found"); trend_state = MARKET_ABNORMAL_VOL_STOP; } - } - -//+------------------------------------------------------------------+ -//| ターゲット価格を取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief Pythonが出力したエントリー価格群を必要なタイミングで読み込みます。 - * - * @param state EA全体の状態。`res_chk` と4タイプ分のen/tp/slを更新します。 - * @return 現状は常にtrue。 - */ -bool GetTargetPrices(EAState &state) - { - bool ProcessDone = CheckDoneFile(done_entry_file); // ←変更 - if(ProcessDone && g_ea.load_target_flg) // ←変更 - { - double target_prices[]; - LoadTargetPrices(target_prices); - - int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); - - // 先頭は res_chk - state.res_chk = (int)target_prices[0]; - - // ★ 1..4 の (en,tp,sl) を読む:合計12個 - for(int t=1; t<=4; t++) - { - int base = 1 + (t-1)*3; // 1,4,7,10 - state.en_price[t] = NormalizeDouble(target_prices[base + 0], digits); - state.tp_price[t] = NormalizeDouble(target_prices[base + 1], digits); - state.sl_price[t] = NormalizeDouble(target_prices[base + 2], digits); - } - - // ログ(任意) - Print("target_prices: res=", state.res_chk, - " | T1 en=", state.en_price[1], " tp=", state.tp_price[1], " sl=", state.sl_price[1], - " | T2 en=", state.en_price[2], " tp=", state.tp_price[2], " sl=", state.sl_price[2], - " | T3 en=", state.en_price[3], " tp=", state.tp_price[3], " sl=", state.sl_price[3], - " | T4 en=", state.en_price[4], " tp=", state.tp_price[4], " sl=", state.sl_price[4]); - - g_ea.load_target_flg = false; // ←変更 - g_ea.last_target_update = TimeLocal(); // ←変更 - g_ea.target_loaded_at = TimeCurrent(); - } - return true; - } -//+------------------------------------------------------------------+ -//+------------------------------------------------------------------+ -//| "target_prices.txt"を読み込む関数 -//+------------------------------------------------------------------+ -/** - * @brief `target_prices.txt` から13個の数値を読み込みます。 - * - * @param target_prices 読み込んだ数値を格納する配列。先頭がres_chk、以降は4タイプ分のen/tp/sl。 - * - * 行数不足の場合は`res_chk=0`として、ターゲット価格を無効扱いにします。 - */ -void LoadTargetPrices(double &target_prices[]) - { - ArrayResize(target_prices, TARGET_SIZE); - for(int i = 0; i < TARGET_SIZE; i++) - target_prices[i] = DEFAULT_TARGET_PRICE; - - string filename = target_prices_file; - - if(FileIsExist(filename)) - { - int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); - if(filehandle != INVALID_HANDLE) - { - int i = 0; - while(!FileIsEnding(filehandle) && i < TARGET_SIZE) - { - string line = FileReadString(filehandle); - target_prices[i] = StringToDouble(line); - i++; - } - FileClose(filehandle); - - if(i < TARGET_SIZE) - { - Print("target_prices.txt line count is short. loaded=", i, " required=", TARGET_SIZE); - target_prices[0] = 0; // 不完全なファイルは無効扱い - } - } - else - { - Print("Failed to open file. Error code: ", GetLastError()); - } - } - else - { - Print("File does not exist: ", filename); - } - } - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit) -//+------------------------------------------------------------------+ -/** - * @brief 指定された注文タイプでペンディング注文を送信します。 - * - * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 - * @param price エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return 注文が正常に受理された場合はtrue、それ以外はfalse。 - */ -bool SendOrder(int orderType, double price, double tp, double sl) - { - MqlTradeRequest request = {}; - MqlTradeResult result = {}; - - request.magic = magic_number; - request.symbol = _Symbol; - request.volume = lot_size; - request.deviation = slippage; - request.type_filling = GetOrderFillingPolicy(_Symbol); - request.price = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); - request.comment = RequestComment(orderType); - - string orderTypeStr = ""; - -// 指値または逆指値注文 - request.action = TRADE_ACTION_PENDING; - ApplyPendingOrderExpiration(request); - - switch(orderType) - { - case 1: // 順張り買い (buy-stop) - request.type = ORDER_TYPE_BUY_STOP; - orderTypeStr = "buy-stop"; - break; - - case 2: // 逆張り買い (buy-limit) - request.type = ORDER_TYPE_BUY_LIMIT; - orderTypeStr = "buy-limit"; - break; - - case 3: // 順張り売り (sell-stop) - request.type = ORDER_TYPE_SELL_STOP; - orderTypeStr = "sell-stop"; - break; - - case 4: // 逆張り売り (sell-limit) - request.type = ORDER_TYPE_SELL_LIMIT; - orderTypeStr = "sell-limit"; - break; - - default: // 不正なorderType - Print(__FUNCTION__, ": invalid orderType=", orderType); - return false; - } - -// 注文送信 - if(!OrderSend(request, result)) - { - Print(__FUNCTION__, ": OrderSend failed. err=", GetLastError()); - return false; - } - -// 注文成功確認 - if(result.retcode == TRADE_RETCODE_PLACED || - result.retcode == TRADE_RETCODE_DONE || - result.retcode == TRADE_RETCODE_DONE_PARTIAL) - { - Print(request.comment + " - " + orderTypeStr + " order success (ticket=", result.order, ")"); - return true; - } - else - { - Print(__FUNCTION__, ": OrderSend retcode=", result.retcode); - return false; - } - } - -//+------------------------------------------------------------------+ -//| 注文の執行ポリシーを取得する関数 -//+------------------------------------------------------------------+ -/** - * @brief シンボルに対応する注文執行ポリシーを取得します。 - * - * @param symbol 対象シンボル。 - * @return 利用可能な執行ポリシー。IOC、FOK、RETURNの順で選択します。 - */ -ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol) - { - long fill_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); - if((fill_mode & SYMBOL_FILLING_IOC) != 0) - return ORDER_FILLING_IOC; - if((fill_mode & SYMBOL_FILLING_FOK) != 0) - return ORDER_FILLING_FOK; - return ORDER_FILLING_RETURN; - } - -//+------------------------------------------------------------------+ -//| ペンディング注文の有効期限を設定する関数 -//+------------------------------------------------------------------+ -/** - * @brief brokerが対応している範囲でpending orderにサーバー側の期限を設定します。 - * - * @param request 更新対象の取引リクエスト。 - */ -void ApplyPendingOrderExpiration(MqlTradeRequest &request) - { - int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); - long expiration_mode = SymbolInfoInteger(request.symbol, SYMBOL_EXPIRATION_MODE); - - if(expiration_seconds > 0 && - (expiration_mode & SYMBOL_EXPIRATION_SPECIFIED) != 0) - { - request.type_time = ORDER_TIME_SPECIFIED; - request.expiration = TimeCurrent() + expiration_seconds; - return; - } - - if(expiration_seconds > 0 && - (expiration_mode & SYMBOL_EXPIRATION_SPECIFIED_DAY) != 0) - { - request.type_time = ORDER_TIME_SPECIFIED_DAY; - request.expiration = TimeCurrent() + expiration_seconds; - return; - } - - if((expiration_mode & SYMBOL_EXPIRATION_DAY) != 0) - { - request.type_time = ORDER_TIME_DAY; - return; - } - - request.type_time = ORDER_TIME_GTC; - } - -//+------------------------------------------------------------------+ -//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与) -//+------------------------------------------------------------------+ -/** - * @brief 注文コメント文字列を生成します。 - * - * @param orderType 注文タイプ番号。 - * @return 注文タイプとPCローカル時刻を含むコメント文字列。 - */ -string RequestComment(int orderType) - { - datetime localTime = TimeLocal(); - string timeStr = TimeToString(localTime, TIME_DATE|TIME_MINUTES); - -// 先頭に 1,2,3,4 を付ける(例: "1 | PC Time: 2026.02.14 19:05") - return IntegerToString(orderType) + " | PC Time: " + timeStr; - } - -//+------------------------------------------------------------------+ -//| H4 market_stateに対して許可された注文タイプか判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief H4 market_stateに対して指定注文タイプが許可されるか判定します。 - * - * @param orderType 注文タイプ番号。 - * @param trend_state H4 market_state(0..6)。 - * @return 許可される注文タイプならtrue。異常ボラまたは異常値ではfalse。 - */ -bool IsOrderTypeAllowedByTrend(const int orderType, const int trend_state) - { - switch(trend_state) - { - case MARKET_LOW_VOL_RANGE: - case MARKET_HIGH_VOL_RANGE: - return (orderType == 2 || orderType == 4); // Range: Buy Limit / Sell Limitのみ - - case MARKET_LOW_VOL_UP: - case MARKET_HIGH_VOL_UP: - return (orderType == 1 || orderType == 2); // Up: Buy Stop / Buy Limitのみ - - case MARKET_LOW_VOL_DOWN: - case MARKET_HIGH_VOL_DOWN: - return (orderType == 3 || orderType == 4); // Down: Sell Stop / Sell Limitのみ - - case MARKET_ABNORMAL_VOL_STOP: - default: - return false; // 異常ボラまたは異常値は新規注文しない - } - } - -//+------------------------------------------------------------------+ -//| target_prices.txt の価格が有効か判定する関数 -//+------------------------------------------------------------------+ -/** - * @brief エントリー価格、利確価格、損切価格が有効値か判定します。 - * - * @param en エントリー価格。 - * @param tp 利確価格。 - * @param sl 損切価格。 - * @return すべて0より大きい場合はtrue。 - */ -bool HasValidTargetPrices(const double en, const double tp, const double sl) - { - return (en > 0.0 && tp > 0.0 && sl > 0.0); - } - -//+------------------------------------------------------------------+ -//| 自EAの未約定注文数を数える関数 -//+------------------------------------------------------------------+ -/** - * @brief このEAが管理対象とする未約定注文数を数えます。 - * - * @return `_Symbol` と `magic_number` が一致する未約定注文数。 - */ -int CountMyPendingOrders() - { - int count = 0; - - for(int i = OrdersTotal() - 1; i >= 0; i--) - { - ulong ticket = OrderGetTicket(i); - if(ticket == 0) - continue; - - if(!OrderSelect(ticket)) - continue; - - if(OrderGetString(ORDER_SYMBOL) != _Symbol) - continue; - - if((int)OrderGetInteger(ORDER_MAGIC) != magic_number) - continue; - - count++; - } - - return count; - } - -//+------------------------------------------------------------------+ -//| 自EAのポジション数を数える関数 -//+------------------------------------------------------------------+ -/** - * @brief このEAが管理対象とする保有ポジション数を数えます。 - * - * @return `_Symbol` と `magic_number` が一致するポジション数。 - */ -int CountMyPositions() - { - int count = 0; - - for(int i = PositionsTotal() - 1; i >= 0; i--) - { - string symbol = PositionGetSymbol(i); - if(symbol == "") - continue; - - if(symbol != _Symbol) - continue; - - if((int)PositionGetInteger(POSITION_MAGIC) != magic_number) - continue; - - count++; - } - - return count; - } - -//+------------------------------------------------------------------+ -//| 自EAの注文+ポジション数を数える関数 -//+------------------------------------------------------------------+ -/** - * @brief このEAが使用中の注文数とポジション数の合計を返します。 - * - * @return 未約定注文数 + 保有ポジション数。 - */ -int CountMyUsed() - { - return CountMyPendingOrders() + CountMyPositions(); - } - - -//+------------------------------------------------------------------+ -//| 時間が経過したエントリー注文をキャンセルする関数 -//+------------------------------------------------------------------+ -/** - * @brief 実時間でENTRY_H1_LIMIT時間を超えた未約定注文をキャンセルします。 - * - * @return 1件以上キャンセルに成功した場合はtrue。 - * - * 対象は`_Symbol` と `magic_number` が一致する未約定注文のみです。 - */ -bool CancelExpiredOrders() - { - bool result = false; - -// 未決済注文の数を取得 - for(int i = OrdersTotal() - 1; i >= 0; i--) - { - ulong ticket = OrderGetTicket(i); - if(OrderSelect(ticket)) // 注文を選択 - { - if(OrderGetString(ORDER_SYMBOL) != _Symbol) - continue; - if(OrderGetInteger(ORDER_MAGIC) != magic_number) - continue; - - datetime open_time = (datetime)OrderGetInteger(ORDER_TIME_SETUP); - - int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); - if(open_time <= 0 || expiration_seconds <= 0) - continue; - - if(TimeCurrent() - open_time >= expiration_seconds) - { - MqlTradeRequest request = {}; - MqlTradeResult trade_result = {}; - request.action=TRADE_ACTION_REMOVE; // 取引操作タイプ - request.order = ticket; // 注文チケット - if(!OrderSend(request, trade_result)) // 削除リクエスト送信 - { - Print("Failed to delete order. Ticket: ", ticket, " Error: ", GetLastError()); - } - else - { - if(trade_result.retcode == TRADE_RETCODE_DONE) - { - Print("Order canceled successfully due to a time limit. Ticket: ", ticket); - result = true; // 削除成功フラグ - } - else - { - Print("Order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode); - } - } - } - } - } - return result; - } - -//+------------------------------------------------------------------+ -//| 時間が経過したポジションをクローズする関数 -//+------------------------------------------------------------------+ -/** - * @brief 実時間でCLOSE_H1_LIMIT時間を超えた保有ポジションをクローズします。 - * - * @return 1件以上クローズに成功した場合はtrue。 - * - * 対象は`_Symbol` と `magic_number` が一致するポジションのみです。 - */ -bool CloseExpiredPositions() - { - bool result = false; - -// ポジションを逆順でループ - for(int i = PositionsTotal() - 1; i >= 0; i--) - { - string position_symbol = PositionGetSymbol(i); - if(position_symbol!="") - { - // Magic Number とシンボルでフィルタリング - if(position_symbol != _Symbol) - continue; - if(PositionGetInteger(POSITION_MAGIC) == magic_number) - { - // ポジションのエントリー時刻を取得 - datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); - - int expiration_seconds = CLOSE_H1_LIMIT * PeriodSeconds(PERIOD_H1); - if(entry_time <= 0 || expiration_seconds <= 0) - continue; - - if(TimeCurrent() - entry_time >= expiration_seconds) - { - MqlTradeRequest request = {}; - MqlTradeResult trade_result = {}; - - // ポジションタイプに応じてリクエストを設定 - int position_type = (int)PositionGetInteger(POSITION_TYPE); - request.action = TRADE_ACTION_DEAL; - request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号 - request.symbol = position_symbol; // シンボル - request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ - request.price = (position_type == POSITION_TYPE_BUY) - ? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ - : SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ - request.deviation = slippage; - request.type = (position_type == POSITION_TYPE_BUY) - ? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ - : ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ - request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー - request.magic = magic_number; - - // 注文送信 - if(!OrderSend(request, trade_result)) - { - Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError()); - continue; - } - - // 結果の確認 - if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL) - { - Print("Position closed successfully due to a time limit. Ticket: ", request.position); - result = true; // 少なくとも1つ成功した場合 - } - else - { - Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode); - } - } - } - } - else - { - Print("Failed to select position at index ", i, ". Error: ", GetLastError()); - } - } - return result; - } diff --git a/Experts/MyProject/HIT-EA_refactor_ver5.mq5 b/Experts/MyProject/HIT-EA_refactor_ver5.mq5 deleted file mode 100644 index 724829b..0000000 --- a/Experts/MyProject/HIT-EA_refactor_ver5.mq5 +++ /dev/null @@ -1,441 +0,0 @@ -//+------------------------------------------------------------------+ -//| HIT_EA.mq5 | -//| Copyright 2026, nanpin-martin.com | -//| https://www.nanpin-martin.com | -//+------------------------------------------------------------------+ -#property copyright "Copyright 2026, nanpin-martin.com" -#property link "https://nanpin-martin.com/" -#property version "1.01" - -#include -#include - -//+------------------------------------------------------------------+ -//| バッチファイル実行用の設定 -//+------------------------------------------------------------------+ - -// Windowsプロセス起動・終了確認用 -#define CREATE_NO_WINDOW 0x08000000 -#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 -#define SYNCHRONIZE 0x00100000 -#define WAIT_OBJECT_0 0 -#define WAIT_TIMEOUT 258 -#define STILL_ACTIVE 259 - -struct STARTUPINFO_W - { - uint cb; - string lpReserved; - string lpDesktop; - string lpTitle; - uint dwX; - uint dwY; - uint dwXSize; - uint dwYSize; - uint dwXCountChars; - uint dwYCountChars; - uint dwFillAttribute; - uint dwFlags; - ushort wShowWindow; - ushort cbReserved2; - long lpReserved2; - long hStdInput; - long hStdOutput; - long hStdError; - }; - -struct PROCESS_INFORMATION - { - long hProcess; - long hThread; - uint dwProcessId; - uint dwThreadId; - }; - -#import "kernel32.dll" -int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); -int WaitForSingleObject(long hHandle, uint dwMilliseconds); -int GetExitCodeProcess(long hProcess, uint &lpExitCode); -int CloseHandle(long hObject); -long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); -#import - -// バッチファイルのパス -string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend -string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry - -// プロセス完了ファイルの設定 -string done_trend_file = "process_done_trend.txt"; -string done_entry_file = "process_done_entry.txt"; - -// Python出力ファイル -string trend_state_file = "trend_state.txt"; -string target_prices_file = "target_prices.txt"; - -// Python実行中を判定するための管理ファイル -string running_trend_file = "process_running_trend.txt"; -string running_entry_file = "process_running_entry.txt"; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| インプットの設定 -//+------------------------------------------------------------------+ - -//--- 入力パラメータ -input double lot_size = 0.01; // ロット数 -input double spread_limit = 60; // 許容スプレッド(point) -input int magic_number = 10001; // マジックナンバー -input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) -input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認 -input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可 -input bool use_m15_imbalance_confirmation = true; // T1/T3でM15初動確認を追加 -input int m15_imbalance_avg_body_period = 20; // M15平均実体計算期間 -input double m15_imbalance_sensitivity = 2.0; // M15初動検知感度 -input double m15_imbalance_min_avg_body_points = 1.0; // 平均実体の最小値(point) -input bool use_m15_imbalance_debug_log = false; // M15初動確認ログ -input int input_entry_max_candidate_age_minutes = 45; // H1候補価格を新規発注に使う最大経過分数 -input bool input_sltp_manager_enabled = false; // UI連動SLTP管理を有効化 -input bool input_sltp_show_panel = true; // SLTP操作パネルを表示 -input bool input_sltp_use_breakeven = false; // 通常ブレークイーブンを有効化 -input double input_sltp_breakeven_trigger_pips = 30.0; // 通常BE開始pips -input double input_sltp_breakeven_buffer_pips = 3.0; // 通常BE固定バッファpips -input bool input_sltp_use_elapsed_breakeven = false; // 保有時間BEを有効化 -input double input_sltp_elapsed_breakeven_hours = 4.0; // 保有時間BE開始時間 -input double input_sltp_elapsed_breakeven_buffer_pips = 3.0; // 保有時間BE固定バッファpips -input bool input_sltp_use_active_trailing = false; // アクティブトレーリングを有効化 -input double input_sltp_active_breakeven_pips = 30.0; // アクティブ開始pips -input double input_sltp_active_stop_loss_offset_pips = 5.0; // 初期固定pips -input double input_sltp_active_step_trigger_pips = 10.0; // ステップ更新間隔pips -input double input_sltp_active_step_move_pips = 5.0; // 1ステップ固定追加pips -input bool input_sltp_use_tp_progress_stop = false; // TP進捗SLを有効化 -input double input_sltp_tp_progress_trigger_percent = 70.0; // TP進捗SL開始率 -input double input_sltp_tp_progress_sl_lock_percent = 30.0; // TP距離固定率 -input bool input_sltp_use_high_volatility_limit = false; // 急変時SL引き締めを有効化 -ulong slippage = 10; // スリッページ - -//--- 主要な定数・設定 -#define POSITION_LIMIT 48 -#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) -#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) -#define HISTORY_BARS 72 -#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す -#define M15_CONFIRM_BARS 30 -#define M15_MIN_ENTRY_ZONE_POINTS 10 -#define M15_MIN_BODY_RATIO 0.25 -#define M15_REJECTION_WICK_RATIO 0.35 -#define ENTRY_RETRY_SECONDS 60 -#define ENTRY_RETRY_LIMIT 10 -// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) -#define TARGET_SIZE 13 -#define DEFAULT_TARGET_PRICE 0.0 -#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) - -#define MARKET_LOW_VOL_RANGE 0 -#define MARKET_HIGH_VOL_RANGE 1 -#define MARKET_LOW_VOL_UP 2 -#define MARKET_HIGH_VOL_UP 3 -#define MARKET_LOW_VOL_DOWN 4 -#define MARKET_HIGH_VOL_DOWN 5 -#define MARKET_TECHNICAL_ERROR_STOP 6 - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| EAの状態をまとめる構造体 -//+------------------------------------------------------------------+ - -struct EAState - { - int trend_state; // H4 market_state(0..5、6は技術エラー停止)を格納する変数 - int res_chk; // GPTの回答が正しく取得できたか - double en_price[5]; // エントリー基準価格 - double tp_price[5]; // 利益確定価格 - double sl_price[5]; // ロスカット基準価格 - bool load_trend_flg; // ★変更:トレンドを更新するタイミングか - bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか - int chk_cnt; // エントリー判定の試行回数 - datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 - datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 - datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻 - datetime last_chk; - }; - -EAState g_ea; // EA全体の状態をここで管理 - -// initial_order 用の初回処理フラグ -// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 -bool g_init_trend_pending = false; -bool g_init_entry_pending = false; - -// H4/H1のバー更新状態 -int g_pre_bars_H4 = 0; -int g_pre_bars_H1 = 0; -int g_pre_bars_M15 = 0; -bool g_bars_H1_check = false; -bool g_bars_M15_check = false; - -// ティックごとの価格情報 -struct TickContext - { - double ask; - double bid; - double spread; - double spread_points; - int digits; - }; - -// 外部Pythonプロセスの状態 -struct ExternalProcessState - { - long handle; - uint process_id; - bool active; - bool exit_code_ready; - uint exit_code; - datetime started_at; - }; - -ExternalProcessState g_trend_process; -ExternalProcessState g_entry_process; - -CSLTPManager g_sltp_manager; -CSLTPManagerPanel g_sltp_panel; -SLTPManagerPanelSettings g_sltp_settings; -bool g_sltp_settings_valid = false; -bool g_sltp_panel_created = false; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| 起動時の処理 -//+------------------------------------------------------------------+ -/** - * @brief EA起動時の初期化処理を行います。 - * - * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は - * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 - * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 - */ -int OnInit() -// プロセス完了ファイルと実行中ファイルの状態を初期化 - { - PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); - PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); - - // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 - g_init_trend_pending = (initial_order == 1); - g_init_entry_pending = (initial_order == 1); - - if(!InitializeSLTPManager()) - return INIT_FAILED; - - return INIT_SUCCEEDED; - } - -//+------------------------------------------------------------------+ -//| 終了時の処理 -//+------------------------------------------------------------------+ -void OnDeinit(const int reason) - { - ResetExternalProcessState(g_trend_process); - ResetExternalProcessState(g_entry_process); - - if(g_sltp_panel_created) - g_sltp_panel.Destroy(reason); - - // Clear chart status text left by Comment() when the EA is removed. - Comment(""); - ChartRedraw(0); - } - -//+------------------------------------------------------------------+ -//| ティック毎の処理 -//+------------------------------------------------------------------+ -/** - * @brief ティック受信ごとにEA全体の処理を制御します。 - * - * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 - * H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。 - * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 - */ -void OnTick() - { - TickContext ctx; - if(!GetTickContext(ctx)) - return; - - // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 - ManageExpiredTrades(); - - // Existing-position protection stays active even while Python results are pending. - ManageSLTPPositions(); - - // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 - RecoverTimedOutPythonProcesses(); - - // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 - ProcessTrendUpdate(g_ea); - - // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 - if(!IsTrendResultReady()) - return; - - RefreshTrendState(g_ea); - - // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 - ProcessEntryUpdate(g_ea); - - // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 - ProcessM15EntryTimingUpdate(); - - UpdateStatusComment(ctx); - - // ここから下は新規注文に関する処理。 - if(!IsSpreadAllowed(ctx)) - return; - - if(!IsEntryResultReady()) - return; - - RefreshTargetPrices(g_ea); - ProcessEntryDecisionIfNeeded(g_ea, ctx); - } - -//+------------------------------------------------------------------+ -//| SLTP manager panel and settings bridge -//+------------------------------------------------------------------+ -string SLTPBoolText(const bool value) - { - return value ? "ON" : "OFF"; - } - -void LoadSLTPInputSettings(SLTPManagerPanelSettings &settings) - { - settings.manager_enabled = input_sltp_manager_enabled; - - settings.use_breakeven = input_sltp_use_breakeven; - settings.breakeven_trigger_pips = input_sltp_breakeven_trigger_pips; - settings.breakeven_buffer_pips = input_sltp_breakeven_buffer_pips; - - settings.use_elapsed_breakeven = input_sltp_use_elapsed_breakeven; - settings.elapsed_breakeven_hours = input_sltp_elapsed_breakeven_hours; - settings.elapsed_breakeven_buffer_pips = input_sltp_elapsed_breakeven_buffer_pips; - - settings.use_active_trailing = input_sltp_use_active_trailing; - settings.active_breakeven_pips = input_sltp_active_breakeven_pips; - settings.active_stop_loss_offset_pips = input_sltp_active_stop_loss_offset_pips; - settings.active_step_trigger_pips = input_sltp_active_step_trigger_pips; - settings.active_step_move_pips = input_sltp_active_step_move_pips; - - settings.use_tp_progress_stop = input_sltp_use_tp_progress_stop; - settings.tp_progress_trigger_percent = input_sltp_tp_progress_trigger_percent; - settings.tp_progress_sl_lock_percent = input_sltp_tp_progress_sl_lock_percent; - - settings.use_high_volatility_limit = input_sltp_use_high_volatility_limit; - } - -bool ApplySLTPSettings(const SLTPManagerPanelSettings &settings, - const bool print_summary) - { - g_sltp_settings_valid = false; - - g_sltp_manager.SetMagicNumber((ulong)magic_number); - g_sltp_manager.SetSymbol(_Symbol); - g_sltp_manager.SetDeviationInPoints(slippage); - g_sltp_manager.SetBreakevenSettings(settings.use_breakeven, - settings.breakeven_trigger_pips, - settings.breakeven_buffer_pips); - g_sltp_manager.SetElapsedBreakevenSettings(settings.use_elapsed_breakeven, - settings.elapsed_breakeven_hours, - settings.elapsed_breakeven_buffer_pips); - g_sltp_manager.SetActiveTrailingSettings(settings.use_active_trailing, - settings.active_breakeven_pips, - settings.active_stop_loss_offset_pips, - settings.active_step_trigger_pips, - settings.active_step_move_pips); - g_sltp_manager.SetTpProgressStopSettings(settings.use_tp_progress_stop, - settings.tp_progress_trigger_percent, - settings.tp_progress_sl_lock_percent); - g_sltp_manager.SetHighVolatilityLimitSettings(settings.use_high_volatility_limit); - - if(!g_sltp_manager.ValidateSettings()) - { - Print("HIT SLTP settings rejected. Manager is disabled until valid settings are applied."); - return false; - } - - g_sltp_settings = settings; - g_sltp_settings_valid = true; - - if(print_summary) - { - Print("HIT SLTP settings: Manager=", SLTPBoolText(settings.manager_enabled), - " BE=", SLTPBoolText(settings.use_breakeven), - " ElapsedBE=", SLTPBoolText(settings.use_elapsed_breakeven), - " ActiveTrail=", SLTPBoolText(settings.use_active_trailing), - " TPProgress=", SLTPBoolText(settings.use_tp_progress_stop), - " HighVol=", SLTPBoolText(settings.use_high_volatility_limit)); - } - - return true; - } - -bool InitializeSLTPManager() - { - SLTPManagerPanelSettings initial_settings; - LoadSLTPInputSettings(initial_settings); - - if(!ApplySLTPSettings(initial_settings, true)) - return false; - - g_sltp_panel.Init(g_sltp_settings); - if(input_sltp_show_panel) - { - if(!g_sltp_panel.CreatePanel()) - { - Print("HIT SLTP panel is unavailable. EA continues with input-based SLTP settings."); - return true; - } - - g_sltp_panel.SetInitialValues(); - g_sltp_panel_created = true; - } - - return true; - } - -void ManageSLTPPositions() - { - if(!g_sltp_settings_valid) - return; - if(!g_sltp_settings.manager_enabled) - return; - - g_sltp_manager.ManagePositions(); - g_sltp_manager.HighVolatilityLimit(); - } - -void OnChartEvent(const int id, - const long &lparam, - const double &dparam, - const string &sparam) - { - if(!g_sltp_panel_created) - return; - - SLTPManagerPanelSettings candidate_settings = g_sltp_settings; - if(g_sltp_panel.HandleChartEvent(id, lparam, dparam, sparam, candidate_settings)) - { - if(!ApplySLTPSettings(candidate_settings, true)) - Print("HIT SLTP panel apply failed. Please review panel values."); - } - } - - -// Function implementations are kept in project headers so the EA entry points stay compact. -#include -#include -#include -#include -#include diff --git a/Experts/MyProject/HIT-EA_refactor_ver5.mq5.bak_codex_20260513_002404 b/Experts/MyProject/HIT-EA_refactor_ver5.mq5.bak_codex_20260513_002404 deleted file mode 100644 index 042e45b..0000000 --- a/Experts/MyProject/HIT-EA_refactor_ver5.mq5.bak_codex_20260513_002404 +++ /dev/null @@ -1,440 +0,0 @@ -//+------------------------------------------------------------------+ -//| HIT_EA.mq5 | -//| Copyright 2026, nanpin-martin.com | -//| https://www.nanpin-martin.com | -//+------------------------------------------------------------------+ -#property copyright "Copyright 2026, nanpin-martin.com" -#property link "https://nanpin-martin.com/" -#property version "1.01" - -#include -#include - -//+------------------------------------------------------------------+ -//| バッチファイル実行用の設定 -//+------------------------------------------------------------------+ - -// Windowsプロセス起動・終了確認用 -#define CREATE_NO_WINDOW 0x08000000 -#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 -#define SYNCHRONIZE 0x00100000 -#define WAIT_OBJECT_0 0 -#define WAIT_TIMEOUT 258 -#define STILL_ACTIVE 259 - -struct STARTUPINFO_W - { - uint cb; - string lpReserved; - string lpDesktop; - string lpTitle; - uint dwX; - uint dwY; - uint dwXSize; - uint dwYSize; - uint dwXCountChars; - uint dwYCountChars; - uint dwFillAttribute; - uint dwFlags; - ushort wShowWindow; - ushort cbReserved2; - long lpReserved2; - long hStdInput; - long hStdOutput; - long hStdError; - }; - -struct PROCESS_INFORMATION - { - long hProcess; - long hThread; - uint dwProcessId; - uint dwThreadId; - }; - -#import "kernel32.dll" -int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); -int WaitForSingleObject(long hHandle, uint dwMilliseconds); -int GetExitCodeProcess(long hProcess, uint &lpExitCode); -int CloseHandle(long hObject); -long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); -#import - -// バッチファイルのパス -string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend -string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry - -// プロセス完了ファイルの設定 -string done_trend_file = "process_done_trend.txt"; -string done_entry_file = "process_done_entry.txt"; - -// Python出力ファイル -string trend_state_file = "trend_state.txt"; -string target_prices_file = "target_prices.txt"; - -// Python実行中を判定するための管理ファイル -string running_trend_file = "process_running_trend.txt"; -string running_entry_file = "process_running_entry.txt"; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| インプットの設定 -//+------------------------------------------------------------------+ - -//--- 入力パラメータ -input double lot_size = 0.01; // ロット数 -input double spread_limit = 60; // 許容スプレッド(point) -input int magic_number = 10001; // マジックナンバー -input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) -input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認 -input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可 -input bool use_m15_imbalance_confirmation = true; // T1/T3でM15初動確認を追加 -input int m15_imbalance_avg_body_period = 20; // M15平均実体計算期間 -input double m15_imbalance_sensitivity = 2.0; // M15初動検知感度 -input double m15_imbalance_min_avg_body_points = 1.0; // 平均実体の最小値(point) -input bool use_m15_imbalance_debug_log = false; // M15初動確認ログ -input bool input_sltp_manager_enabled = false; // UI連動SLTP管理を有効化 -input bool input_sltp_show_panel = true; // SLTP操作パネルを表示 -input bool input_sltp_use_breakeven = false; // 通常ブレークイーブンを有効化 -input double input_sltp_breakeven_trigger_pips = 30.0; // 通常BE開始pips -input double input_sltp_breakeven_buffer_pips = 3.0; // 通常BE固定バッファpips -input bool input_sltp_use_elapsed_breakeven = false; // 保有時間BEを有効化 -input double input_sltp_elapsed_breakeven_hours = 4.0; // 保有時間BE開始時間 -input double input_sltp_elapsed_breakeven_buffer_pips = 3.0; // 保有時間BE固定バッファpips -input bool input_sltp_use_active_trailing = false; // アクティブトレーリングを有効化 -input double input_sltp_active_breakeven_pips = 30.0; // アクティブ開始pips -input double input_sltp_active_stop_loss_offset_pips = 5.0; // 初期固定pips -input double input_sltp_active_step_trigger_pips = 10.0; // ステップ更新間隔pips -input double input_sltp_active_step_move_pips = 5.0; // 1ステップ固定追加pips -input bool input_sltp_use_tp_progress_stop = false; // TP進捗SLを有効化 -input double input_sltp_tp_progress_trigger_percent = 70.0; // TP進捗SL開始率 -input double input_sltp_tp_progress_sl_lock_percent = 30.0; // TP距離固定率 -input bool input_sltp_use_high_volatility_limit = false; // 急変時SL引き締めを有効化 -ulong slippage = 10; // スリッページ - -//--- 主要な定数・設定 -#define POSITION_LIMIT 48 -#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) -#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) -#define HISTORY_BARS 72 -#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す -#define M15_CONFIRM_BARS 30 -#define M15_MIN_ENTRY_ZONE_POINTS 10 -#define M15_MIN_BODY_RATIO 0.25 -#define M15_REJECTION_WICK_RATIO 0.35 -#define ENTRY_RETRY_SECONDS 60 -#define ENTRY_RETRY_LIMIT 10 -// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) -#define TARGET_SIZE 13 -#define DEFAULT_TARGET_PRICE 0.0 -#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) - -#define MARKET_LOW_VOL_RANGE 0 -#define MARKET_HIGH_VOL_RANGE 1 -#define MARKET_LOW_VOL_UP 2 -#define MARKET_HIGH_VOL_UP 3 -#define MARKET_LOW_VOL_DOWN 4 -#define MARKET_HIGH_VOL_DOWN 5 -#define MARKET_TECHNICAL_ERROR_STOP 6 - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| EAの状態をまとめる構造体 -//+------------------------------------------------------------------+ - -struct EAState - { - int trend_state; // H4 market_state(0..5、6は技術エラー停止)を格納する変数 - int res_chk; // GPTの回答が正しく取得できたか - double en_price[5]; // エントリー基準価格 - double tp_price[5]; // 利益確定価格 - double sl_price[5]; // ロスカット基準価格 - bool load_trend_flg; // ★変更:トレンドを更新するタイミングか - bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか - int chk_cnt; // エントリー判定の試行回数 - datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 - datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 - datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻 - datetime last_chk; - }; - -EAState g_ea; // EA全体の状態をここで管理 - -// initial_order 用の初回処理フラグ -// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 -bool g_init_trend_pending = false; -bool g_init_entry_pending = false; - -// H4/H1のバー更新状態 -int g_pre_bars_H4 = 0; -int g_pre_bars_H1 = 0; -int g_pre_bars_M15 = 0; -bool g_bars_H1_check = false; -bool g_bars_M15_check = false; - -// ティックごとの価格情報 -struct TickContext - { - double ask; - double bid; - double spread; - double spread_points; - int digits; - }; - -// 外部Pythonプロセスの状態 -struct ExternalProcessState - { - long handle; - uint process_id; - bool active; - bool exit_code_ready; - uint exit_code; - datetime started_at; - }; - -ExternalProcessState g_trend_process; -ExternalProcessState g_entry_process; - -CSLTPManager g_sltp_manager; -CSLTPManagerPanel g_sltp_panel; -SLTPManagerPanelSettings g_sltp_settings; -bool g_sltp_settings_valid = false; -bool g_sltp_panel_created = false; - -//+------------------------------------------------------------------+ - -//+------------------------------------------------------------------+ -//| 起動時の処理 -//+------------------------------------------------------------------+ -/** - * @brief EA起動時の初期化処理を行います。 - * - * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は - * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 - * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 - */ -int OnInit() -// プロセス完了ファイルと実行中ファイルの状態を初期化 - { - PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); - PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); - - // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 - g_init_trend_pending = (initial_order == 1); - g_init_entry_pending = (initial_order == 1); - - if(!InitializeSLTPManager()) - return INIT_FAILED; - - return INIT_SUCCEEDED; - } - -//+------------------------------------------------------------------+ -//| 終了時の処理 -//+------------------------------------------------------------------+ -void OnDeinit(const int reason) - { - ResetExternalProcessState(g_trend_process); - ResetExternalProcessState(g_entry_process); - - if(g_sltp_panel_created) - g_sltp_panel.Destroy(reason); - - // Clear chart status text left by Comment() when the EA is removed. - Comment(""); - ChartRedraw(0); - } - -//+------------------------------------------------------------------+ -//| ティック毎の処理 -//+------------------------------------------------------------------+ -/** - * @brief ティック受信ごとにEA全体の処理を制御します。 - * - * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 - * H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。 - * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 - */ -void OnTick() - { - TickContext ctx; - if(!GetTickContext(ctx)) - return; - - // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 - ManageExpiredTrades(); - - // Existing-position protection stays active even while Python results are pending. - ManageSLTPPositions(); - - // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 - RecoverTimedOutPythonProcesses(); - - // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 - ProcessTrendUpdate(g_ea); - - // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 - if(!IsTrendResultReady()) - return; - - RefreshTrendState(g_ea); - - // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 - ProcessEntryUpdate(g_ea); - - // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 - ProcessM15EntryTimingUpdate(); - - UpdateStatusComment(ctx); - - // ここから下は新規注文に関する処理。 - if(!IsSpreadAllowed(ctx)) - return; - - if(!IsEntryResultReady()) - return; - - RefreshTargetPrices(g_ea); - ProcessEntryDecisionIfNeeded(g_ea, ctx); - } - -//+------------------------------------------------------------------+ -//| SLTP manager panel and settings bridge -//+------------------------------------------------------------------+ -string SLTPBoolText(const bool value) - { - return value ? "ON" : "OFF"; - } - -void LoadSLTPInputSettings(SLTPManagerPanelSettings &settings) - { - settings.manager_enabled = input_sltp_manager_enabled; - - settings.use_breakeven = input_sltp_use_breakeven; - settings.breakeven_trigger_pips = input_sltp_breakeven_trigger_pips; - settings.breakeven_buffer_pips = input_sltp_breakeven_buffer_pips; - - settings.use_elapsed_breakeven = input_sltp_use_elapsed_breakeven; - settings.elapsed_breakeven_hours = input_sltp_elapsed_breakeven_hours; - settings.elapsed_breakeven_buffer_pips = input_sltp_elapsed_breakeven_buffer_pips; - - settings.use_active_trailing = input_sltp_use_active_trailing; - settings.active_breakeven_pips = input_sltp_active_breakeven_pips; - settings.active_stop_loss_offset_pips = input_sltp_active_stop_loss_offset_pips; - settings.active_step_trigger_pips = input_sltp_active_step_trigger_pips; - settings.active_step_move_pips = input_sltp_active_step_move_pips; - - settings.use_tp_progress_stop = input_sltp_use_tp_progress_stop; - settings.tp_progress_trigger_percent = input_sltp_tp_progress_trigger_percent; - settings.tp_progress_sl_lock_percent = input_sltp_tp_progress_sl_lock_percent; - - settings.use_high_volatility_limit = input_sltp_use_high_volatility_limit; - } - -bool ApplySLTPSettings(const SLTPManagerPanelSettings &settings, - const bool print_summary) - { - g_sltp_settings_valid = false; - - g_sltp_manager.SetMagicNumber((ulong)magic_number); - g_sltp_manager.SetSymbol(_Symbol); - g_sltp_manager.SetDeviationInPoints(slippage); - g_sltp_manager.SetBreakevenSettings(settings.use_breakeven, - settings.breakeven_trigger_pips, - settings.breakeven_buffer_pips); - g_sltp_manager.SetElapsedBreakevenSettings(settings.use_elapsed_breakeven, - settings.elapsed_breakeven_hours, - settings.elapsed_breakeven_buffer_pips); - g_sltp_manager.SetActiveTrailingSettings(settings.use_active_trailing, - settings.active_breakeven_pips, - settings.active_stop_loss_offset_pips, - settings.active_step_trigger_pips, - settings.active_step_move_pips); - g_sltp_manager.SetTpProgressStopSettings(settings.use_tp_progress_stop, - settings.tp_progress_trigger_percent, - settings.tp_progress_sl_lock_percent); - g_sltp_manager.SetHighVolatilityLimitSettings(settings.use_high_volatility_limit); - - if(!g_sltp_manager.ValidateSettings()) - { - Print("HIT SLTP settings rejected. Manager is disabled until valid settings are applied."); - return false; - } - - g_sltp_settings = settings; - g_sltp_settings_valid = true; - - if(print_summary) - { - Print("HIT SLTP settings: Manager=", SLTPBoolText(settings.manager_enabled), - " BE=", SLTPBoolText(settings.use_breakeven), - " ElapsedBE=", SLTPBoolText(settings.use_elapsed_breakeven), - " ActiveTrail=", SLTPBoolText(settings.use_active_trailing), - " TPProgress=", SLTPBoolText(settings.use_tp_progress_stop), - " HighVol=", SLTPBoolText(settings.use_high_volatility_limit)); - } - - return true; - } - -bool InitializeSLTPManager() - { - SLTPManagerPanelSettings initial_settings; - LoadSLTPInputSettings(initial_settings); - - if(!ApplySLTPSettings(initial_settings, true)) - return false; - - g_sltp_panel.Init(g_sltp_settings); - if(input_sltp_show_panel) - { - if(!g_sltp_panel.CreatePanel()) - { - Print("HIT SLTP panel is unavailable. EA continues with input-based SLTP settings."); - return true; - } - - g_sltp_panel.SetInitialValues(); - g_sltp_panel_created = true; - } - - return true; - } - -void ManageSLTPPositions() - { - if(!g_sltp_settings_valid) - return; - if(!g_sltp_settings.manager_enabled) - return; - - g_sltp_manager.ManagePositions(); - g_sltp_manager.HighVolatilityLimit(); - } - -void OnChartEvent(const int id, - const long &lparam, - const double &dparam, - const string &sparam) - { - if(!g_sltp_panel_created) - return; - - SLTPManagerPanelSettings candidate_settings = g_sltp_settings; - if(g_sltp_panel.HandleChartEvent(id, lparam, dparam, sparam, candidate_settings)) - { - if(!ApplySLTPSettings(candidate_settings, true)) - Print("HIT SLTP panel apply failed. Please review panel values."); - } - } - - -// Function implementations are kept in project headers so the EA entry points stay compact. -#include -#include -#include -#include -#include diff --git a/docs/MyProject/HIT-EA_spec_en.md b/docs/MyProject/HIT-EA_spec_en.md index b6b5404..99387cf 100644 --- a/docs/MyProject/HIT-EA_spec_en.md +++ b/docs/MyProject/HIT-EA_spec_en.md @@ -108,6 +108,8 @@ When no prefix is provided, such as manual Python execution, Python keeps using - Running marker: `_process_running_entry.txt` - Batch file: `\MQL5\python_for_ea\bat\get_entry_reply.bat` +H1 candidate generation uses the OpenAI Responses API. The default model is `gpt-5.5`, the default `reasoning.effort` is `low`, and the default `text.verbosity` is `low`. `temperature` is not set. Runtime overrides are available through `OPENAI_MODEL`, `OPENAI_REASONING_EFFORT`, and `OPENAI_TEXT_VERBOSITY`. Output is validated with `text.format` Structured Outputs JSON Schema; incomplete responses, schema mismatches, price-rule failures, and insufficient reward/risk fall back to safe-stop values. + During `OnInit()`, the EA resolves `MQL5\python_for_ea` from `TerminalInfoString(TERMINAL_DATA_PATH)` and builds absolute batch-file paths. Each batch file resolves the Python project root from its own location, so runtime execution no longer depends on `C:\ea_py`. `target_prices.txt` must contain 13 lines: @@ -217,6 +219,11 @@ After an H4 `market_state` reload, the EA cancels existing pending orders whose ## 10. Changelog +### 2026-06-06 + +- Updated the Python-side OpenAI default model to the official `gpt-5.5` slug and made Responses API calls send explicit `reasoning.effort` and `text.verbosity`. For GPT-5.5, the output contract is now documented as primarily enforced by Structured Outputs rather than duplicated prompt schema text. +- Added compatibility declarations to `HIT-EA_refactor_ver5.mq5` so it compiles against the current shared headers, covering split-entry zones, Python linkage filenames, and pending-cancel retry tracking. Since `use_split_entry_zone = false` by default, the normal ver5 entry behavior remains unchanged. + ### 2026-06-01 - Added `debug_entry.txt` safe-stop diagnostics with the stop stage and reason so normal candidate skips can be distinguished from guarded failure fallbacks. diff --git a/docs/MyProject/HIT-EA_spec_ja.md b/docs/MyProject/HIT-EA_spec_ja.md index 6bacb22..2c98b92 100644 --- a/docs/MyProject/HIT-EA_spec_ja.md +++ b/docs/MyProject/HIT-EA_spec_ja.md @@ -108,6 +108,8 @@ EAは `OnInit()` で `_Symbol` と `magic_number` から `HIT_ - 実行中フラグ: `_process_running_entry.txt` - 起動バッチ: `\MQL5\python_for_ea\bat\get_entry_reply.bat` +H1候補生成はOpenAI Responses APIを使用する。既定モデルは `gpt-5.5`、既定 `reasoning.effort` は `low`、既定 `text.verbosity` は `low` とする。`temperature` は指定せず、`OPENAI_MODEL`、`OPENAI_REASONING_EFFORT`、`OPENAI_TEXT_VERBOSITY` で実行時上書きできる。出力は `text.format` のStructured Outputs JSON Schemaで検証し、未完了応答、schema不一致、価格条件違反、reward/risk不足は安全側停止値へ倒す。 + EAは `OnInit()` で `TerminalInfoString(TERMINAL_DATA_PATH)` から `MQL5\python_for_ea` を解決し、各batファイルの絶対パスを組み立てる。batファイルは自身の位置からPythonプロジェクトルートを解決するため、`C:\ea_py` には依存しない。 `target_prices.txt` は13行構成とする。 @@ -217,6 +219,11 @@ H4 `market_state` の再読込後は、更新後の状態で許可されない ## 10. 変更履歴 +### 2026-06-06 + +- Python側のOpenAI既定モデルを公式slugの `gpt-5.5` へ更新し、Responses API呼び出しで `reasoning.effort` と `text.verbosity` を明示する仕様にした。GPT-5.5向けに、出力形式はプロンプト重複指定ではなくStructured Outputsを主契約として扱う。 +- `HIT-EA_refactor_ver5.mq5` が現行の共有ヘッダーを使用してもコンパイルできるよう、split-entry zone、Python連携ファイル名、pending取消リトライ管理に必要な互換宣言を追加した。既定では `use_split_entry_zone = false` のため、ver5の通常エントリー挙動は維持する。 + ### 2026-06-01 - H1候補生成が安全側停止値へフォールバックした場合も `debug_entry.txt` へ停止段階と理由を追記し、通常の候補見送りと障害停止を判別できるようにした。 diff --git a/python_for_ea/README.md b/python_for_ea/README.md index 31f370f..ac0dce8 100644 --- a/python_for_ea/README.md +++ b/python_for_ea/README.md @@ -133,7 +133,9 @@ EA側で分割エントリーを有効にした場合は、この予測ゾーン ## OpenAI 設定 H1 エントリー候補生成では OpenAI API を使用します。 -Responses APIの `text.format` にJSON Schemaを渡し、GPT出力を自然文やCSV風テキストではなく +既定モデルは `gpt-5.5` です。Responses APIへ `reasoning.effort` と +`text.verbosity` を明示して送り、`temperature` は使用しません。 +`text.format` にはJSON Schemaを渡し、GPT出力を自然文やCSV風テキストではなく `schema_version` と `strategies` を持つ構造化JSONに固定します。 JSONが不正、API応答が未完了、価格条件やreward/risk条件に合わない場合は停止値へ倒します。 @@ -146,13 +148,14 @@ $env:OPENAI_API_KEY = "..." 任意: ```powershell -$env:OPENAI_MODEL = "..." +$env:OPENAI_MODEL = "gpt-5.5" $env:OPENAI_REASONING_EFFORT = "low" +$env:OPENAI_TEXT_VERBOSITY = "low" $env:MT5_EA_FILE_PREFIX = "HIT_GOLD_10001" $env:MT5_PRICE_DIGITS = "2" ``` -`OPENAI_MODEL` 未設定時は `src/ea_py/constants.py` の `DEFAULT_GPT_MODEL` を使います。`OPENAI_REASONING_EFFORT` は `none`, `low`, `medium`, `high`, `xhigh` のいずれかです。`MT5_EA_FILE_PREFIX` と `MT5_PRICE_DIGITS` は手動実行時だけ指定すればよく、EA/bat経由では自動設定されます。 +`OPENAI_MODEL` 未設定時は `src/ea_py/constants.py` の `DEFAULT_GPT_MODEL` を使います。`OPENAI_REASONING_EFFORT` は `none`, `low`, `medium`, `high`, `xhigh` のいずれかです。H1候補生成の既定は低遅延と再現性確認のため `low` ですが、品質優先の検証では `medium` も評価してください。`OPENAI_TEXT_VERBOSITY` は `low`, `medium`, `high` のいずれかで、既定はJSON専用応答に合わせて `low` です。`MT5_EA_FILE_PREFIX` と `MT5_PRICE_DIGITS` は手動実行時だけ指定すればよく、EA/bat経由では自動設定されます。 ## 主なモジュール diff --git a/python_for_ea/docs/architecture/folder-structure.md b/python_for_ea/docs/architecture/folder-structure.md index 5b2b34f..d388ac1 100644 --- a/python_for_ea/docs/architecture/folder-structure.md +++ b/python_for_ea/docs/architecture/folder-structure.md @@ -4,7 +4,7 @@ このドキュメントは、MT5データフォルダ配下の `MQL5\python_for_ea` のフォルダ構成に関する正本である。 -現在のプロジェクトは、MT5 HIT-EAから出力されたOHLC CSVをPythonで解析し、OpenAI APIを使ってGOLD/XAUUSD向けの相場環境とエントリー候補価格を返す補助アプリケーションである。 +現在のプロジェクトは、MT5 HIT-EAから出力されたOHLC CSVをPythonで解析し、GOLD/XAUUSD向けのH4相場環境とH1エントリー候補価格を返す補助アプリケーションである。H4相場環境はルールベースで判定し、H1エントリー候補価格のみOpenAI APIを使う。 MT5 EAから `bat/` 経由でルート直下のPythonスクリプトが呼ばれているため、現時点ではルート直下の `get_trend_reply.py` と `get_entry_reply.py` を実行入口として維持する。 @@ -197,7 +197,7 @@ EAファイルはこのPythonプロジェクトの外部連携先として扱う | モジュール | 責務 | |---|---| -| `src/ea_py/config.py` | 環境変数、モデル名、デバッグ設定などの設定読み込み。 | +| `src/ea_py/config.py` | 環境変数、GPT-5.5モデル名、reasoning effort、text verbosity、デバッグ設定などの設定読み込み。 | | `src/ea_py/paths.py` | MT5データフォルダ、入力CSV、出力ファイルパスの組み立て。 | | `src/ea_py/constants.py` | `market_state`、ATR期間、出力サイズなどの定数。 | | `src/ea_py/types.py` | OHLCバー、OHLC要約、13行出力などの型定義。 | @@ -212,7 +212,7 @@ EAファイルはこのPythonプロジェクトの外部連携先として扱う | `src/ea_py/market/imbalance.py` | H1インバランス初動判定。 | | `src/ea_py/prompts/trend_prompt.py` | H4相場環境判定プロンプトの生成。 | | `src/ea_py/prompts/entry_prompt.py` | H1候補価格生成プロンプトの生成。 | -| `src/ea_py/openai_client.py` | OpenAI API呼び出しの薄いラッパー。 | +| `src/ea_py/openai_client.py` | GPT-5.5向けのResponses API呼び出し、reasoning/text設定、Structured Outputs指定をまとめる薄いラッパー。 | | `src/ea_py/pipelines/trend_pipeline.py` | H4判定処理全体のオーケストレーション。 | | `src/ea_py/pipelines/entry_pipeline.py` | H1候補生成処理全体のオーケストレーション。 | diff --git a/python_for_ea/src/ea_py/config.py b/python_for_ea/src/ea_py/config.py index 5ca5003..e001a49 100644 --- a/python_for_ea/src/ea_py/config.py +++ b/python_for_ea/src/ea_py/config.py @@ -5,7 +5,14 @@ from __future__ import annotations from dataclasses import dataclass import os -from ea_py.constants import DEBUG_PRINT, DEFAULT_GPT_MODEL, DEFAULT_REASONING_EFFORT, VALID_REASONING_EFFORTS +from ea_py.constants import ( + DEBUG_PRINT, + DEFAULT_GPT_MODEL, + DEFAULT_REASONING_EFFORT, + DEFAULT_TEXT_VERBOSITY, + VALID_REASONING_EFFORTS, + VALID_TEXT_VERBOSITIES, +) @dataclass(frozen=True) @@ -15,6 +22,7 @@ class RuntimeConfig: api_key: str model: str reasoning_effort: str + text_verbosity: str debug_print: bool @@ -22,6 +30,7 @@ def load_runtime_config( *, model: str | None = None, reasoning_effort: str | None = None, + text_verbosity: str | None = None, debug_print: bool = DEBUG_PRINT, ) -> RuntimeConfig: """OpenAI呼び出しに必要な実行時設定を読み込む。 @@ -30,9 +39,9 @@ def load_runtime_config( `OPENAI_API_KEY` 環境変数からのみ取得する。 未設定の場合は `RuntimeError` を送出し、上位パイプラインで停止値を出力する。 - `model` / `reasoning_effort` / `debug_print` は呼び出し側から上書き可能だが、 + `model` / `reasoning_effort` / `text_verbosity` / `debug_print` は呼び出し側から上書き可能だが、 通常は `constants.py` のデフォルト値を使う。 - `OPENAI_MODEL` と `OPENAI_REASONING_EFFORT` が設定されている場合は、 + `OPENAI_MODEL` / `OPENAI_REASONING_EFFORT` / `OPENAI_TEXT_VERBOSITY` が設定されている場合は、 引数未指定時の実行時上書きとして扱う。 """ api_key = os.getenv("OPENAI_API_KEY") @@ -41,16 +50,24 @@ def load_runtime_config( selected_model = model or os.getenv("OPENAI_MODEL") or DEFAULT_GPT_MODEL selected_reasoning_effort = reasoning_effort or os.getenv("OPENAI_REASONING_EFFORT") or DEFAULT_REASONING_EFFORT + selected_text_verbosity = text_verbosity or os.getenv("OPENAI_TEXT_VERBOSITY") or DEFAULT_TEXT_VERBOSITY if selected_reasoning_effort not in VALID_REASONING_EFFORTS: allowed = ", ".join(sorted(VALID_REASONING_EFFORTS)) raise RuntimeError( f"OPENAI_REASONING_EFFORT が不正です: {selected_reasoning_effort!r}. " f"allowed={allowed}" ) + if selected_text_verbosity not in VALID_TEXT_VERBOSITIES: + allowed = ", ".join(sorted(VALID_TEXT_VERBOSITIES)) + raise RuntimeError( + f"OPENAI_TEXT_VERBOSITY が不正です: {selected_text_verbosity!r}. " + f"allowed={allowed}" + ) return RuntimeConfig( api_key=api_key, model=selected_model, reasoning_effort=selected_reasoning_effort, + text_verbosity=selected_text_verbosity, debug_print=debug_print, ) diff --git a/python_for_ea/src/ea_py/constants.py b/python_for_ea/src/ea_py/constants.py index 8bccf58..d3162bd 100644 --- a/python_for_ea/src/ea_py/constants.py +++ b/python_for_ea/src/ea_py/constants.py @@ -4,9 +4,11 @@ from __future__ import annotations MT_ENCODING = "utf-16 LE" -DEFAULT_GPT_MODEL = "gpt-5.5-2026-04-23" +DEFAULT_GPT_MODEL = "gpt-5.5" DEFAULT_REASONING_EFFORT = "low" VALID_REASONING_EFFORTS = frozenset({"none", "low", "medium", "high", "xhigh"}) +DEFAULT_TEXT_VERBOSITY = "low" +VALID_TEXT_VERBOSITIES = frozenset({"low", "medium", "high"}) DEBUG_PRINT = True ENTRY_RESPONSE_SCHEMA_VERSION = 1 diff --git a/python_for_ea/src/ea_py/io/debug_logs.py b/python_for_ea/src/ea_py/io/debug_logs.py index b329513..0b93bec 100644 --- a/python_for_ea/src/ea_py/io/debug_logs.py +++ b/python_for_ea/src/ea_py/io/debug_logs.py @@ -60,6 +60,7 @@ def append_debug_entry( path: Path, model: str, reasoning_effort: str, + text_verbosity: str, max_output_tokens: int, api_diagnostics: str, timeframe: str, @@ -79,6 +80,7 @@ def append_debug_entry( file.write(f"DEBUG TIME : {now_str()}\n") file.write(f"MODEL : {model}\n") file.write(f"REASONING : {reasoning_effort}\n") + file.write(f"VERBOSITY : {text_verbosity}\n") file.write(f"MAX TOKENS : {max_output_tokens}\n") file.write(f"TIMEFRAME : {timeframe}\n") file.write(f"MARKET_STATE(H4) : {trend_state} ({MARKET_STATE_LABELS.get(trend_state, 'UNKNOWN')})\n") diff --git a/python_for_ea/src/ea_py/openai_client.py b/python_for_ea/src/ea_py/openai_client.py index 8e72137..c5f897e 100644 --- a/python_for_ea/src/ea_py/openai_client.py +++ b/python_for_ea/src/ea_py/openai_client.py @@ -94,6 +94,7 @@ def call_responses_api( image_data_urls: Sequence[str], max_output_tokens: int, response_text_format: Mapping[str, Any] | None = None, + text_verbosity: str | None = None, ) -> ResponsesApiResult: """Responses APIへテキストとチャート画像を送り、出力テキストを返す。 @@ -103,6 +104,7 @@ def call_responses_api( `response_text_format` が指定された場合は Responses API の `text.format` へ渡し、 JSON Schemaなどの構造化出力をAPI側でも強制する。 + `text_verbosity` はGPT-5.5の最終出力長を制御し、reasoning品質とは独立して扱う。 戻り値は `response.output_text` をstripした文字列とAPI診断情報。 API例外や空/不正な出力の安全側処理は、この薄いラッパーではなく @@ -129,8 +131,13 @@ def call_responses_api( "reasoning": {"effort": reasoning_effort}, "max_output_tokens": max_output_tokens, } + text_config: dict[str, Any] = {} if response_text_format is not None: - create_params["text"] = {"format": dict(response_text_format)} + text_config["format"] = dict(response_text_format) + if text_verbosity is not None: + text_config["verbosity"] = text_verbosity + if text_config: + create_params["text"] = text_config response = client.responses.create(**create_params) diagnostics = _extract_response_diagnostics(response) diff --git a/python_for_ea/src/ea_py/pipelines/entry_pipeline.py b/python_for_ea/src/ea_py/pipelines/entry_pipeline.py index 9f0edc6..9837ab2 100644 --- a/python_for_ea/src/ea_py/pipelines/entry_pipeline.py +++ b/python_for_ea/src/ea_py/pipelines/entry_pipeline.py @@ -323,6 +323,7 @@ def run_pipeline() -> None: image_data_urls=images_data_urls, max_output_tokens=max_tokens, response_text_format=response_text_format, + text_verbosity=config.text_verbosity, ) gpt_reply = gpt_result.text api_diagnostics = gpt_result.diagnostics.to_log_text() @@ -378,6 +379,7 @@ def run_pipeline() -> None: path=paths.debug_reason, model=config.model, reasoning_effort=config.reasoning_effort, + text_verbosity=config.text_verbosity, max_output_tokens=max_tokens, api_diagnostics=api_diagnostics, timeframe=ENTRY_TIMEFRAME, diff --git a/python_for_ea/src/ea_py/prompts/entry_prompt.py b/python_for_ea/src/ea_py/prompts/entry_prompt.py index 9798a41..0e730bc 100644 --- a/python_for_ea/src/ea_py/prompts/entry_prompt.py +++ b/python_for_ea/src/ea_py/prompts/entry_prompt.py @@ -320,13 +320,11 @@ def build_common_rules_block( - エントリー後12時間以内に利確・損切に到達しなければ、その時点の価格でクローズ。 【出力ルール(最重要)】 -- Responses APIのJSON Schemaに一致するJSONオブジェクトだけを出力してください。 +- Responses APIのStructured Outputs(JSON Schema)で検証されるJSONオブジェクトだけを出力してください。 - schema_version は {ENTRY_RESPONSE_SCHEMA_VERSION}。 - strategies には対象戦略だけを入れ、順序は {out_order}。 -- 各strategy要素は strategy, decision, entry, tp, sl, zone_low, zone_high, reason_code を必ず含めてください。 -- decision は "use" または "skip" のみ。 -- reason_code は次のいずれかのみ: {", ".join(ENTRY_REASON_CODES)} -- 自然文、Markdown、余分なキー、空行は一切出力しないでください。 +- decision は "use" または "skip"、reason_code は次のいずれかのみ: {", ".join(ENTRY_REASON_CODES)} +- 自然文、Markdown、空行は一切出力しないでください。 """.strip() diff --git a/python_for_ea/tests/unit/test_config.py b/python_for_ea/tests/unit/test_config.py index 3cec046..e01f9d9 100644 --- a/python_for_ea/tests/unit/test_config.py +++ b/python_for_ea/tests/unit/test_config.py @@ -7,17 +7,33 @@ import pytest from ea_py.config import load_runtime_config +def test_load_runtime_config_uses_gpt55_defaults(monkeypatch: pytest.MonkeyPatch) -> None: + """Default OpenAI settings follow the GPT-5.5 Responses API contract.""" + monkeypatch.setenv("OPENAI_API_KEY", "test-key") + monkeypatch.delenv("OPENAI_MODEL", raising=False) + monkeypatch.delenv("OPENAI_REASONING_EFFORT", raising=False) + monkeypatch.delenv("OPENAI_TEXT_VERBOSITY", raising=False) + + actual = load_runtime_config() + + assert actual.model == "gpt-5.5" + assert actual.reasoning_effort == "low" + assert actual.text_verbosity == "low" + + def test_load_runtime_config_reads_model_and_reasoning_from_env(monkeypatch: pytest.MonkeyPatch) -> None: """OpenAI model and reasoning effort can be overridden without code edits.""" monkeypatch.setenv("OPENAI_API_KEY", "test-key") monkeypatch.setenv("OPENAI_MODEL", "gpt-test") monkeypatch.setenv("OPENAI_REASONING_EFFORT", "low") + monkeypatch.setenv("OPENAI_TEXT_VERBOSITY", "medium") actual = load_runtime_config() assert actual.api_key == "test-key" assert actual.model == "gpt-test" assert actual.reasoning_effort == "low" + assert actual.text_verbosity == "medium" def test_load_runtime_config_rejects_invalid_reasoning_effort(monkeypatch: pytest.MonkeyPatch) -> None: @@ -27,3 +43,13 @@ def test_load_runtime_config_rejects_invalid_reasoning_effort(monkeypatch: pytes with pytest.raises(RuntimeError, match="OPENAI_REASONING_EFFORT"): load_runtime_config() + + +def test_load_runtime_config_rejects_invalid_text_verbosity(monkeypatch: pytest.MonkeyPatch) -> None: + """Invalid text verbosity should fail before an API call is attempted.""" + monkeypatch.setenv("OPENAI_API_KEY", "test-key") + monkeypatch.delenv("OPENAI_REASONING_EFFORT", raising=False) + monkeypatch.setenv("OPENAI_TEXT_VERBOSITY", "tiny") + + with pytest.raises(RuntimeError, match="OPENAI_TEXT_VERBOSITY"): + load_runtime_config() diff --git a/python_for_ea/tests/unit/test_openai_client.py b/python_for_ea/tests/unit/test_openai_client.py index c0ed537..adf5c12 100644 --- a/python_for_ea/tests/unit/test_openai_client.py +++ b/python_for_ea/tests/unit/test_openai_client.py @@ -23,7 +23,7 @@ class FakeResponse: def __init__(self, *, output_text: str, incomplete_details: object | None = None) -> None: self.output_text = output_text - self.model = "gpt-5.5-2026-04-23" + self.model = "gpt-5.5" self.status = "completed" self.incomplete_details = incomplete_details self.error = None @@ -50,23 +50,25 @@ class FakeClient: self.responses = FakeResponsesResource(response) -def test_call_responses_api_sends_reasoning_and_omits_temperature() -> None: - """GPT-5.5 requests should use reasoning controls instead of temperature.""" +def test_call_responses_api_sends_reasoning_text_controls_and_omits_temperature() -> None: + """GPT-5.5 requests should use reasoning/text controls instead of temperature.""" fake_client = FakeClient(FakeResponse(output_text=" 0\n")) actual = call_responses_api( client=fake_client, # type: ignore[arg-type] - model="gpt-5.5-2026-04-23", + model="gpt-5.5", reasoning_effort="none", system_content="Return one number.", user_text="Classify.", image_data_urls=[], max_output_tokens=128, + text_verbosity="low", ) assert actual.text == "0" assert fake_client.responses.create_params["reasoning"] == {"effort": "none"} assert fake_client.responses.create_params["max_output_tokens"] == 128 + assert fake_client.responses.create_params["text"] == {"verbosity": "low"} assert "temperature" not in fake_client.responses.create_params @@ -87,17 +89,21 @@ def test_call_responses_api_sends_structured_text_format() -> None: actual = call_responses_api( client=fake_client, # type: ignore[arg-type] - model="gpt-5.5-2026-04-23", + model="gpt-5.5", reasoning_effort="low", system_content="Return JSON.", user_text="Classify.", image_data_urls=[], max_output_tokens=128, response_text_format=response_text_format, + text_verbosity="low", ) assert actual.text.startswith("{") - assert fake_client.responses.create_params["text"] == {"format": response_text_format} + assert fake_client.responses.create_params["text"] == { + "format": response_text_format, + "verbosity": "low", + } def test_call_responses_api_returns_incomplete_diagnostics_for_empty_text() -> None: @@ -111,7 +117,7 @@ def test_call_responses_api_returns_incomplete_diagnostics_for_empty_text() -> N actual = call_responses_api( client=fake_client, # type: ignore[arg-type] - model="gpt-5.5-2026-04-23", + model="gpt-5.5", reasoning_effort="low", system_content="Return one number.", user_text="Classify.", diff --git a/python_for_ea/work/get_entry_reply.py b/python_for_ea/work/get_entry_reply.py index 03d66c6..88f850e 100644 --- a/python_for_ea/work/get_entry_reply.py +++ b/python_for_ea/work/get_entry_reply.py @@ -22,7 +22,9 @@ YOUR_API_KEY = os.getenv("OPENAI_API_KEY") if YOUR_API_KEY is None: raise RuntimeError("OPENAI_API_KEY が環境変数に設定されていません。") -gpt_model = "gpt-5.4" +gpt_model = "gpt-5.5" +reasoning_effort = os.getenv("OPENAI_REASONING_EFFORT", "low") +text_verbosity = os.getenv("OPENAI_TEXT_VERBOSITY", "low") # ========================= # 入出力ファイル @@ -355,7 +357,8 @@ def call_gpt(client, model, system_content: str, user_text: str, images_data_url {"role": "system", "content": system_content}, {"role": "user", "content": content_parts}, ], - temperature=0.0, + reasoning={"effort": reasoning_effort}, + text={"verbosity": text_verbosity}, max_output_tokens=max_output_tokens, ) return resp.output_text or "" @@ -440,6 +443,8 @@ def write_debug_entry( f.write("=" * 60 + "\n") f.write(f"DEBUG TIME : {_now_str()}\n") f.write(f"MODEL : {gpt_model}\n") + f.write(f"REASONING : {reasoning_effort}\n") + f.write(f"VERBOSITY : {text_verbosity}\n") f.write(f"TIMEFRAME : {timeframe}\n") f.write(f"TREND_STATE(H4) : {trend_state} (0=RANGE,1=UP,2=DOWN)\n") f.write(f"SELECTED_STRATEGY : {','.join(str(x) for x in selected_strategies)}\n") @@ -627,4 +632,4 @@ def _write_outputs(numeric_list): # 実行 # ========================= if __name__ == "__main__": - run_pipeline() \ No newline at end of file + run_pipeline() diff --git a/python_for_ea/work/get_trend_reply.py b/python_for_ea/work/get_trend_reply.py index 8209105..68edaa8 100644 --- a/python_for_ea/work/get_trend_reply.py +++ b/python_for_ea/work/get_trend_reply.py @@ -22,7 +22,9 @@ YOUR_API_KEY = os.getenv("OPENAI_API_KEY") if YOUR_API_KEY is None: raise RuntimeError("OPENAI_API_KEY が環境変数に設定されていません。") -gpt_model = "gpt-5.4" +gpt_model = "gpt-5.5" +reasoning_effort = os.getenv("OPENAI_REASONING_EFFORT", "low") +text_verbosity = os.getenv("OPENAI_TEXT_VERBOSITY", "low") # ========================= # 入出力ファイル @@ -62,6 +64,8 @@ def write_debug_trend(path: str, current_price: float, numeric_summary: str, gpt f.write("=" * 60 + "\n") f.write(f"DEBUG TIME : {_now_str()}\n") f.write(f"MODEL : {gpt_model}\n") + f.write(f"REASONING : {reasoning_effort}\n") + f.write(f"VERBOSITY : {text_verbosity}\n") f.write(f"CURRENT PRICE : {current_price:.2f}\n") f.write("=" * 60 + "\n\n") f.write("---- NUMERIC SUMMARY START ----\n") @@ -289,7 +293,8 @@ def call_gpt_trend(client, model, system_content: str, user_text: str, image_dat {"role": "system", "content": system_content}, {"role": "user", "content": content_parts}, ], - temperature=0.0, + reasoning={"effort": reasoning_effort}, + text={"verbosity": text_verbosity}, max_output_tokens=max_output_tokens, ) return (resp.output_text or "").strip() @@ -485,4 +490,4 @@ def run_pipeline(): # 実行 # ========================= if __name__ == "__main__": - run_pipeline() \ No newline at end of file + run_pipeline()