Merge pull request #4 from HirorihirorihK/codex/20260606_gpt_model_change
Codex/20260606 gpt model change
This commit is contained in:
@@ -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;
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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 <MyLib/Trading/SLTPManager.mqh>
|
||||
#include <MyLib/Panel/SLTPManagerPanel.mqh>
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| バッチファイル実行用の設定
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
// 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 <MyLib/Common/HITRuntimeController.mqh>
|
||||
#include <MyLib/Signals/HITEntrySignal.mqh>
|
||||
#include <MyLib/Common/HITExternalProcess.mqh>
|
||||
#include <MyLib/Signals/HITPythonSignalGateway.mqh>
|
||||
#include <MyLib/Trading/HITTradeManager.mqh>
|
||||
@@ -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 <MyLib/Trading/SLTPManager.mqh>
|
||||
#include <MyLib/Panel/SLTPManagerPanel.mqh>
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| バッチファイル実行用の設定
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
// 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 <MyLib/Common/HITRuntimeController.mqh>
|
||||
#include <MyLib/Signals/HITEntrySignal.mqh>
|
||||
#include <MyLib/Common/HITExternalProcess.mqh>
|
||||
#include <MyLib/Signals/HITPythonSignalGateway.mqh>
|
||||
#include <MyLib/Trading/HITTradeManager.mqh>
|
||||
@@ -108,6 +108,8 @@ When no prefix is provided, such as manual Python execution, Python keeps using
|
||||
- Running marker: `<prefix>_process_running_entry.txt`
|
||||
- Batch file: `<TerminalDataPath>\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.
|
||||
|
||||
@@ -108,6 +108,8 @@ EAは `OnInit()` で `_Symbol` と `magic_number` から `HIT_<sanitized symbol>
|
||||
- 実行中フラグ: `<prefix>_process_running_entry.txt`
|
||||
- 起動バッチ: `<TerminalDataPath>\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` へ停止段階と理由を追記し、通常の候補見送りと障害停止を判別できるようにした。
|
||||
|
||||
@@ -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経由では自動設定されます。
|
||||
|
||||
## 主なモジュール
|
||||
|
||||
|
||||
@@ -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候補生成処理全体のオーケストレーション。 |
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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()
|
||||
run_pipeline()
|
||||
|
||||
@@ -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()
|
||||
run_pipeline()
|
||||
|
||||
Reference in New Issue
Block a user