Consolidate Python ignore rules into root gitignore
This commit is contained in:
@@ -0,0 +1,528 @@
|
||||
#ifndef HIT_EXTERNAL_PROCESS_MQH
|
||||
#define HIT_EXTERNAL_PROCESS_MQH
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| "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;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,289 @@
|
||||
#ifndef HIT_RUNTIME_CONTROLLER_MQH
|
||||
#define HIT_RUNTIME_CONTROLLER_MQH
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| ティック情報を取得する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @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"
|
||||
"\nSplit Zone: %s N=%d ZoneID=%s"
|
||||
"\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(), EffectivePositionLimit(),
|
||||
(use_split_entry_zone ? "ON" : "OFF"), EffectiveSplitEntryCount(), g_ea.zone_candidate_id,
|
||||
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..5、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_TECHNICAL_ERROR_STOP:
|
||||
return "TECHNICAL_ERROR_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);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,287 @@
|
||||
#ifndef HIT_RUNTIME_CONTROLLER_MQH
|
||||
#define HIT_RUNTIME_CONTROLLER_MQH
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| ティック情報を取得する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @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..5、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_TECHNICAL_ERROR_STOP:
|
||||
return "TECHNICAL_ERROR_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);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef TRADING_PANEL_SYMBOL_UTILS_MQH
|
||||
#define TRADING_PANEL_SYMBOL_UTILS_MQH
|
||||
|
||||
double TradingPanelAdjustPoint(const string symbol)
|
||||
{
|
||||
// Keep the legacy XAUUSD/GOLD pip conversion for backtest reproducibility.
|
||||
if (symbol == "XAUUSD" || symbol == "GOLD")
|
||||
return 0.1;
|
||||
|
||||
const int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||
|
||||
switch (symbol_digits)
|
||||
{
|
||||
case 2:
|
||||
case 3:
|
||||
return 0.01;
|
||||
case 4:
|
||||
case 5:
|
||||
return 0.0001;
|
||||
}
|
||||
|
||||
PrintFormat("Unsupported symbol digits for pip conversion: symbol=%s digits=%d",
|
||||
symbol, symbol_digits);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
ENUM_ORDER_TYPE_FILLING TradingPanelGetOrderFillingPolicy(const string symbol)
|
||||
{
|
||||
const long filling_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
|
||||
|
||||
if ((filling_mode & SYMBOL_FILLING_IOC) != 0)
|
||||
return ORDER_FILLING_IOC;
|
||||
if ((filling_mode & SYMBOL_FILLING_FOK) != 0)
|
||||
return ORDER_FILLING_FOK;
|
||||
|
||||
return ORDER_FILLING_RETURN;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,543 @@
|
||||
#ifndef SLTP_MANAGER_PANEL_MQH
|
||||
#define SLTP_MANAGER_PANEL_MQH
|
||||
|
||||
#include <Controls/Dialog.mqh>
|
||||
#include <Controls/Button.mqh>
|
||||
#include <Controls/Edit.mqh>
|
||||
#include <Controls/Label.mqh>
|
||||
|
||||
#define HIT_SLTP_PANEL_NAME "HIT_SLTP_Manager_Panel"
|
||||
#define HIT_SLTP_PANEL_WIDTH 620
|
||||
#define HIT_SLTP_PANEL_HEIGHT 370
|
||||
#define HIT_SLTP_ROW_HEIGHT 22
|
||||
#define HIT_SLTP_LABEL_X 15
|
||||
#define HIT_SLTP_LABEL_WIDTH 125
|
||||
#define HIT_SLTP_BUTTON_X 150
|
||||
#define HIT_SLTP_BUTTON_WIDTH 74
|
||||
#define HIT_SLTP_BUTTON_HEIGHT 26
|
||||
#define HIT_SLTP_EDIT_X1 240
|
||||
#define HIT_SLTP_EDIT_X2 330
|
||||
#define HIT_SLTP_EDIT_X3 420
|
||||
#define HIT_SLTP_EDIT_X4 510
|
||||
#define HIT_SLTP_EDIT_WIDTH 80
|
||||
#define HIT_SLTP_APPLY_WIDTH 90
|
||||
#define HIT_SLTP_APPLY_HEIGHT 28
|
||||
#define HIT_SLTP_TITLE_Y 8
|
||||
#define HIT_SLTP_TITLE_WIDTH 240
|
||||
#define HIT_SLTP_MASTER_ROW_Y 38
|
||||
#define HIT_SLTP_BE_ROW_Y 82
|
||||
#define HIT_SLTP_ELAPSED_ROW_Y 128
|
||||
#define HIT_SLTP_ACTIVE_ROW_Y 174
|
||||
#define HIT_SLTP_TP_ROW_Y 220
|
||||
#define HIT_SLTP_HIGH_VOL_ROW_Y 266
|
||||
#define HIT_SLTP_APPLY_ROW_Y 302
|
||||
#define HIT_SLTP_FONT_SIZE 9
|
||||
#define HIT_SLTP_FONT_STYLE "Arial Black"
|
||||
|
||||
#define HIT_SLTP_MASTER_BTN_NAME "HIT_SLTP_MASTER_BTN"
|
||||
#define HIT_SLTP_BE_BTN_NAME "HIT_SLTP_BE_BTN"
|
||||
#define HIT_SLTP_ELAPSED_BE_BTN_NAME "HIT_SLTP_ELAPSED_BE_BTN"
|
||||
#define HIT_SLTP_ACTIVE_TRAIL_BTN_NAME "HIT_SLTP_ACTIVE_TRAIL_BTN"
|
||||
#define HIT_SLTP_TP_PROGRESS_BTN_NAME "HIT_SLTP_TP_PROGRESS_BTN"
|
||||
#define HIT_SLTP_HIGH_VOL_BTN_NAME "HIT_SLTP_HIGH_VOL_BTN"
|
||||
#define HIT_SLTP_APPLY_BTN_NAME "HIT_SLTP_APPLY_BTN"
|
||||
|
||||
#define HIT_SLTP_BE_TRIGGER_EDIT_NAME "HIT_SLTP_BE_TRIGGER"
|
||||
#define HIT_SLTP_BE_BUFFER_EDIT_NAME "HIT_SLTP_BE_BUFFER"
|
||||
#define HIT_SLTP_ELAPSED_HOURS_EDIT_NAME "HIT_SLTP_ELAPSED_HOURS"
|
||||
#define HIT_SLTP_ELAPSED_BUFFER_EDIT_NAME "HIT_SLTP_ELAPSED_BUFFER"
|
||||
#define HIT_SLTP_ACTIVE_START_EDIT_NAME "HIT_SLTP_ACTIVE_START"
|
||||
#define HIT_SLTP_ACTIVE_OFFSET_EDIT_NAME "HIT_SLTP_ACTIVE_OFFSET"
|
||||
#define HIT_SLTP_ACTIVE_STEP_TRIGGER_EDIT_NAME "HIT_SLTP_ACTIVE_STEP_TRIGGER"
|
||||
#define HIT_SLTP_ACTIVE_STEP_MOVE_EDIT_NAME "HIT_SLTP_ACTIVE_STEP_MOVE"
|
||||
#define HIT_SLTP_TP_TRIGGER_EDIT_NAME "HIT_SLTP_TP_TRIGGER"
|
||||
#define HIT_SLTP_TP_LOCK_EDIT_NAME "HIT_SLTP_TP_LOCK"
|
||||
|
||||
struct SLTPManagerPanelSettings
|
||||
{
|
||||
bool manager_enabled;
|
||||
|
||||
bool use_breakeven;
|
||||
double breakeven_trigger_pips;
|
||||
double breakeven_buffer_pips;
|
||||
|
||||
bool use_elapsed_breakeven;
|
||||
double elapsed_breakeven_hours;
|
||||
double elapsed_breakeven_buffer_pips;
|
||||
|
||||
bool use_active_trailing;
|
||||
double active_breakeven_pips;
|
||||
double active_stop_loss_offset_pips;
|
||||
double active_step_trigger_pips;
|
||||
double active_step_move_pips;
|
||||
|
||||
bool use_tp_progress_stop;
|
||||
double tp_progress_trigger_percent;
|
||||
double tp_progress_sl_lock_percent;
|
||||
|
||||
bool use_high_volatility_limit;
|
||||
};
|
||||
|
||||
class CSLTPManagerPanel
|
||||
{
|
||||
private:
|
||||
CAppDialog m_panel;
|
||||
|
||||
CButton m_button_master;
|
||||
CButton m_button_breakeven;
|
||||
CButton m_button_elapsed_breakeven;
|
||||
CButton m_button_active_trailing;
|
||||
CButton m_button_tp_progress;
|
||||
CButton m_button_high_volatility;
|
||||
CButton m_button_apply;
|
||||
|
||||
CEdit m_edit_breakeven_trigger;
|
||||
CEdit m_edit_breakeven_buffer;
|
||||
CEdit m_edit_elapsed_hours;
|
||||
CEdit m_edit_elapsed_buffer;
|
||||
CEdit m_edit_active_start;
|
||||
CEdit m_edit_active_offset;
|
||||
CEdit m_edit_active_step_trigger;
|
||||
CEdit m_edit_active_step_move;
|
||||
CEdit m_edit_tp_trigger;
|
||||
CEdit m_edit_tp_lock;
|
||||
|
||||
CLabel m_label_title;
|
||||
CLabel m_label_master;
|
||||
CLabel m_label_breakeven;
|
||||
CLabel m_label_elapsed_breakeven;
|
||||
CLabel m_label_active_trailing;
|
||||
CLabel m_label_tp_progress;
|
||||
CLabel m_label_high_volatility;
|
||||
CLabel m_label_be_trigger;
|
||||
CLabel m_label_be_buffer;
|
||||
CLabel m_label_elapsed_hours;
|
||||
CLabel m_label_elapsed_buffer;
|
||||
CLabel m_label_active_start;
|
||||
CLabel m_label_active_offset;
|
||||
CLabel m_label_active_step_trigger;
|
||||
CLabel m_label_active_step_move;
|
||||
CLabel m_label_tp_trigger;
|
||||
CLabel m_label_tp_lock;
|
||||
|
||||
SLTPManagerPanelSettings m_settings;
|
||||
|
||||
public:
|
||||
void Init(const SLTPManagerPanelSettings &initial_settings)
|
||||
{
|
||||
m_settings = initial_settings;
|
||||
}
|
||||
|
||||
bool CreatePanel()
|
||||
{
|
||||
const int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
|
||||
int panel_x = chart_width - HIT_SLTP_PANEL_WIDTH - 20;
|
||||
if (panel_x < 10)
|
||||
panel_x = 10;
|
||||
const int panel_y = 25;
|
||||
|
||||
if (!m_panel.Create(0, HIT_SLTP_PANEL_NAME, 0, panel_x, panel_y,
|
||||
panel_x + HIT_SLTP_PANEL_WIDTH + 8,
|
||||
panel_y + HIT_SLTP_PANEL_HEIGHT))
|
||||
{
|
||||
Print("SLTP panel create failed: err=", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
CreateLabel(m_label_title, "HIT_SLTP_LABEL_TITLE", HIT_SLTP_LABEL_X, HIT_SLTP_TITLE_Y,
|
||||
HIT_SLTP_TITLE_WIDTH, "HIT SLTP Manager");
|
||||
|
||||
CreateLabel(m_label_master, "HIT_SLTP_LABEL_MASTER", HIT_SLTP_LABEL_X, HIT_SLTP_MASTER_ROW_Y,
|
||||
HIT_SLTP_LABEL_WIDTH, "Manager");
|
||||
CreateButton(m_button_master, HIT_SLTP_MASTER_BTN_NAME, HIT_SLTP_BUTTON_X, HIT_SLTP_MASTER_ROW_Y,
|
||||
HIT_SLTP_BUTTON_WIDTH, HIT_SLTP_BUTTON_HEIGHT);
|
||||
|
||||
CreateLabel(m_label_breakeven, "HIT_SLTP_LABEL_BE", HIT_SLTP_LABEL_X, HIT_SLTP_BE_ROW_Y,
|
||||
HIT_SLTP_LABEL_WIDTH, "BreakEven");
|
||||
CreateButton(m_button_breakeven, HIT_SLTP_BE_BTN_NAME, HIT_SLTP_BUTTON_X, HIT_SLTP_BE_ROW_Y,
|
||||
HIT_SLTP_BUTTON_WIDTH, HIT_SLTP_BUTTON_HEIGHT);
|
||||
CreateLabel(m_label_be_trigger, "HIT_SLTP_LABEL_BE_TRG", HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_BE_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Trigger");
|
||||
CreateLabel(m_label_be_buffer, "HIT_SLTP_LABEL_BE_BUF", HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_BE_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Buffer");
|
||||
CreateEdit(m_edit_breakeven_trigger, HIT_SLTP_BE_TRIGGER_EDIT_NAME, HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_BE_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
CreateEdit(m_edit_breakeven_buffer, HIT_SLTP_BE_BUFFER_EDIT_NAME, HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_BE_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
|
||||
CreateLabel(m_label_elapsed_breakeven, "HIT_SLTP_LABEL_ELAPSED", HIT_SLTP_LABEL_X,
|
||||
HIT_SLTP_ELAPSED_ROW_Y, HIT_SLTP_LABEL_WIDTH, "Elapsed BE");
|
||||
CreateButton(m_button_elapsed_breakeven, HIT_SLTP_ELAPSED_BE_BTN_NAME, HIT_SLTP_BUTTON_X,
|
||||
HIT_SLTP_ELAPSED_ROW_Y, HIT_SLTP_BUTTON_WIDTH, HIT_SLTP_BUTTON_HEIGHT);
|
||||
CreateLabel(m_label_elapsed_hours, "HIT_SLTP_LABEL_EL_H", HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_ELAPSED_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Hours");
|
||||
CreateLabel(m_label_elapsed_buffer, "HIT_SLTP_LABEL_EL_BUF", HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_ELAPSED_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Buffer");
|
||||
CreateEdit(m_edit_elapsed_hours, HIT_SLTP_ELAPSED_HOURS_EDIT_NAME, HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_ELAPSED_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
CreateEdit(m_edit_elapsed_buffer, HIT_SLTP_ELAPSED_BUFFER_EDIT_NAME, HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_ELAPSED_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
|
||||
CreateLabel(m_label_active_trailing, "HIT_SLTP_LABEL_ACTIVE", HIT_SLTP_LABEL_X,
|
||||
HIT_SLTP_ACTIVE_ROW_Y, HIT_SLTP_LABEL_WIDTH, "Active Trail");
|
||||
CreateButton(m_button_active_trailing, HIT_SLTP_ACTIVE_TRAIL_BTN_NAME, HIT_SLTP_BUTTON_X,
|
||||
HIT_SLTP_ACTIVE_ROW_Y, HIT_SLTP_BUTTON_WIDTH, HIT_SLTP_BUTTON_HEIGHT);
|
||||
CreateLabel(m_label_active_start, "HIT_SLTP_LABEL_ACT_START", HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_ACTIVE_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Start");
|
||||
CreateLabel(m_label_active_offset, "HIT_SLTP_LABEL_ACT_OFFSET", HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_ACTIVE_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Offset");
|
||||
CreateLabel(m_label_active_step_trigger, "HIT_SLTP_LABEL_ACT_TRG", HIT_SLTP_EDIT_X3,
|
||||
HIT_SLTP_ACTIVE_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "StepTrig");
|
||||
CreateLabel(m_label_active_step_move, "HIT_SLTP_LABEL_ACT_MOVE", HIT_SLTP_EDIT_X4,
|
||||
HIT_SLTP_ACTIVE_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "StepMove");
|
||||
CreateEdit(m_edit_active_start, HIT_SLTP_ACTIVE_START_EDIT_NAME, HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_ACTIVE_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
CreateEdit(m_edit_active_offset, HIT_SLTP_ACTIVE_OFFSET_EDIT_NAME, HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_ACTIVE_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
CreateEdit(m_edit_active_step_trigger, HIT_SLTP_ACTIVE_STEP_TRIGGER_EDIT_NAME, HIT_SLTP_EDIT_X3,
|
||||
HIT_SLTP_ACTIVE_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
CreateEdit(m_edit_active_step_move, HIT_SLTP_ACTIVE_STEP_MOVE_EDIT_NAME, HIT_SLTP_EDIT_X4,
|
||||
HIT_SLTP_ACTIVE_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
|
||||
CreateLabel(m_label_tp_progress, "HIT_SLTP_LABEL_TP_PROGRESS", HIT_SLTP_LABEL_X,
|
||||
HIT_SLTP_TP_ROW_Y, HIT_SLTP_LABEL_WIDTH, "TP Progress");
|
||||
CreateButton(m_button_tp_progress, HIT_SLTP_TP_PROGRESS_BTN_NAME, HIT_SLTP_BUTTON_X,
|
||||
HIT_SLTP_TP_ROW_Y, HIT_SLTP_BUTTON_WIDTH, HIT_SLTP_BUTTON_HEIGHT);
|
||||
CreateLabel(m_label_tp_trigger, "HIT_SLTP_LABEL_TP_TRG", HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_TP_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Trig %");
|
||||
CreateLabel(m_label_tp_lock, "HIT_SLTP_LABEL_TP_LOCK", HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_TP_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Lock %");
|
||||
CreateEdit(m_edit_tp_trigger, HIT_SLTP_TP_TRIGGER_EDIT_NAME, HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_TP_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
CreateEdit(m_edit_tp_lock, HIT_SLTP_TP_LOCK_EDIT_NAME, HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_TP_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
|
||||
CreateLabel(m_label_high_volatility, "HIT_SLTP_LABEL_HIGH_VOL", HIT_SLTP_LABEL_X,
|
||||
HIT_SLTP_HIGH_VOL_ROW_Y, HIT_SLTP_LABEL_WIDTH, "High Vol");
|
||||
CreateButton(m_button_high_volatility, HIT_SLTP_HIGH_VOL_BTN_NAME, HIT_SLTP_BUTTON_X,
|
||||
HIT_SLTP_HIGH_VOL_ROW_Y, HIT_SLTP_BUTTON_WIDTH, HIT_SLTP_BUTTON_HEIGHT);
|
||||
|
||||
CreateButton(m_button_apply, HIT_SLTP_APPLY_BTN_NAME, HIT_SLTP_EDIT_X4, HIT_SLTP_APPLY_ROW_Y,
|
||||
HIT_SLTP_APPLY_WIDTH, HIT_SLTP_APPLY_HEIGHT);
|
||||
m_button_apply.Text("APPLY");
|
||||
m_button_apply.Color(clrWhite);
|
||||
m_button_apply.ColorBackground(clrSeaGreen);
|
||||
m_button_apply.ColorBorder(clrSeaGreen);
|
||||
|
||||
RefreshControls();
|
||||
m_panel.Run();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Destroy(const int reason)
|
||||
{
|
||||
m_panel.Destroy(reason);
|
||||
}
|
||||
|
||||
void SetInitialValues()
|
||||
{
|
||||
RefreshControls();
|
||||
}
|
||||
|
||||
bool HandleChartEvent(const int id,
|
||||
const long &lparam,
|
||||
const double &dparam,
|
||||
const string &sparam,
|
||||
SLTPManagerPanelSettings &applied_settings)
|
||||
{
|
||||
m_panel.ChartEvent(id, lparam, dparam, sparam);
|
||||
|
||||
if (id != CHARTEVENT_OBJECT_CLICK)
|
||||
return false;
|
||||
|
||||
if (sparam == HIT_SLTP_MASTER_BTN_NAME)
|
||||
{
|
||||
m_settings.manager_enabled = !m_settings.manager_enabled;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_BE_BTN_NAME)
|
||||
{
|
||||
m_settings.use_breakeven = !m_settings.use_breakeven;
|
||||
if (m_settings.use_breakeven)
|
||||
m_settings.use_active_trailing = false;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_ELAPSED_BE_BTN_NAME)
|
||||
{
|
||||
m_settings.use_elapsed_breakeven = !m_settings.use_elapsed_breakeven;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_ACTIVE_TRAIL_BTN_NAME)
|
||||
{
|
||||
m_settings.use_active_trailing = !m_settings.use_active_trailing;
|
||||
if (m_settings.use_active_trailing)
|
||||
m_settings.use_breakeven = false;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_TP_PROGRESS_BTN_NAME)
|
||||
{
|
||||
m_settings.use_tp_progress_stop = !m_settings.use_tp_progress_stop;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_HIGH_VOL_BTN_NAME)
|
||||
{
|
||||
m_settings.use_high_volatility_limit = !m_settings.use_high_volatility_limit;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_APPLY_BTN_NAME)
|
||||
{
|
||||
if (!ReadInputs())
|
||||
return false;
|
||||
|
||||
applied_settings = m_settings;
|
||||
Print("HIT SLTP panel settings applied.");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void CreateButton(CButton &button,
|
||||
const string name,
|
||||
const int x,
|
||||
const int y,
|
||||
const int width,
|
||||
const int height)
|
||||
{
|
||||
if (!button.Create(0, name, 0, x, y, 0, 0))
|
||||
{
|
||||
Print("SLTP button create failed: ", name, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
button.Width(width);
|
||||
button.Height(height);
|
||||
button.Font(HIT_SLTP_FONT_STYLE);
|
||||
button.FontSize(HIT_SLTP_FONT_SIZE);
|
||||
m_panel.Add(button);
|
||||
}
|
||||
|
||||
void CreateLabel(CLabel &label,
|
||||
const string name,
|
||||
const int x,
|
||||
const int y,
|
||||
const int width,
|
||||
const string text)
|
||||
{
|
||||
if (!label.Create(0, name, 0, x, y, 0, 0))
|
||||
{
|
||||
Print("SLTP label create failed: ", name, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
label.Width(width);
|
||||
label.Height(HIT_SLTP_ROW_HEIGHT);
|
||||
label.Text(text);
|
||||
label.Font(HIT_SLTP_FONT_STYLE);
|
||||
label.FontSize(HIT_SLTP_FONT_SIZE);
|
||||
label.Color(clrDimGray);
|
||||
label.ColorBackground(clrDimGray);
|
||||
label.ColorBorder(clrDimGray);
|
||||
m_panel.Add(label);
|
||||
}
|
||||
|
||||
void CreateEdit(CEdit &edit,
|
||||
const string name,
|
||||
const int x,
|
||||
const int y,
|
||||
const int width)
|
||||
{
|
||||
if (!edit.Create(0, name, 0, x, y, 0, 0))
|
||||
{
|
||||
Print("SLTP edit create failed: ", name, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
edit.Width(width);
|
||||
edit.Height(HIT_SLTP_ROW_HEIGHT);
|
||||
edit.Font(HIT_SLTP_FONT_STYLE);
|
||||
edit.FontSize(HIT_SLTP_FONT_SIZE);
|
||||
edit.Color(clrDimGray);
|
||||
edit.ColorBackground(clrWhite);
|
||||
edit.ColorBorder(clrDimGray);
|
||||
m_panel.Add(edit);
|
||||
}
|
||||
|
||||
void RefreshControls()
|
||||
{
|
||||
RefreshButtons();
|
||||
|
||||
m_edit_breakeven_trigger.Text(DoubleToString(m_settings.breakeven_trigger_pips, 1));
|
||||
m_edit_breakeven_buffer.Text(DoubleToString(m_settings.breakeven_buffer_pips, 1));
|
||||
m_edit_elapsed_hours.Text(DoubleToString(m_settings.elapsed_breakeven_hours, 1));
|
||||
m_edit_elapsed_buffer.Text(DoubleToString(m_settings.elapsed_breakeven_buffer_pips, 1));
|
||||
m_edit_active_start.Text(DoubleToString(m_settings.active_breakeven_pips, 1));
|
||||
m_edit_active_offset.Text(DoubleToString(m_settings.active_stop_loss_offset_pips, 1));
|
||||
m_edit_active_step_trigger.Text(DoubleToString(m_settings.active_step_trigger_pips, 1));
|
||||
m_edit_active_step_move.Text(DoubleToString(m_settings.active_step_move_pips, 1));
|
||||
m_edit_tp_trigger.Text(DoubleToString(m_settings.tp_progress_trigger_percent, 1));
|
||||
m_edit_tp_lock.Text(DoubleToString(m_settings.tp_progress_sl_lock_percent, 1));
|
||||
}
|
||||
|
||||
void RefreshButtons()
|
||||
{
|
||||
ApplyToggleButton(m_button_master, m_settings.manager_enabled, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_breakeven, m_settings.use_breakeven, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_elapsed_breakeven, m_settings.use_elapsed_breakeven, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_active_trailing, m_settings.use_active_trailing, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_tp_progress, m_settings.use_tp_progress_stop, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_high_volatility, m_settings.use_high_volatility_limit, "ON", "OFF");
|
||||
}
|
||||
|
||||
void ApplyToggleButton(CButton &button,
|
||||
const bool enabled,
|
||||
const string on_text,
|
||||
const string off_text)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
button.Text(on_text);
|
||||
button.Color(clrWhite);
|
||||
button.ColorBackground(clrRoyalBlue);
|
||||
button.ColorBorder(clrRoyalBlue);
|
||||
}
|
||||
else
|
||||
{
|
||||
button.Text(off_text);
|
||||
button.Color(clrRoyalBlue);
|
||||
button.ColorBackground(clrWhite);
|
||||
button.ColorBorder(clrRoyalBlue);
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadInputs()
|
||||
{
|
||||
bool ok = true;
|
||||
|
||||
ok = ReadDoubleValue(m_edit_breakeven_trigger, "BreakEven Trigger", 0.0, 10000.0,
|
||||
m_settings.breakeven_trigger_pips, m_settings.breakeven_trigger_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_breakeven_buffer, "BreakEven Buffer", 0.0, 10000.0,
|
||||
m_settings.breakeven_buffer_pips, m_settings.breakeven_buffer_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_elapsed_hours, "Elapsed BE Hours", 0.0, 10000.0,
|
||||
m_settings.elapsed_breakeven_hours, m_settings.elapsed_breakeven_hours) && ok;
|
||||
ok = ReadDoubleValue(m_edit_elapsed_buffer, "Elapsed BE Buffer", 0.0, 10000.0,
|
||||
m_settings.elapsed_breakeven_buffer_pips, m_settings.elapsed_breakeven_buffer_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_active_start, "Active Trail Start", 0.0, 10000.0,
|
||||
m_settings.active_breakeven_pips, m_settings.active_breakeven_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_active_offset, "Active Trail Offset", 0.0, 10000.0,
|
||||
m_settings.active_stop_loss_offset_pips, m_settings.active_stop_loss_offset_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_active_step_trigger, "Active Trail Step Trigger", 0.1, 10000.0,
|
||||
m_settings.active_step_trigger_pips, m_settings.active_step_trigger_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_active_step_move, "Active Trail Step Move", 0.1, 10000.0,
|
||||
m_settings.active_step_move_pips, m_settings.active_step_move_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_tp_trigger, "TP Progress Trigger", 0.1, 100.0,
|
||||
m_settings.tp_progress_trigger_percent, m_settings.tp_progress_trigger_percent) && ok;
|
||||
ok = ReadDoubleValue(m_edit_tp_lock, "TP Progress Lock", 0.0, 100.0,
|
||||
m_settings.tp_progress_sl_lock_percent, m_settings.tp_progress_sl_lock_percent) && ok;
|
||||
|
||||
if (m_settings.use_breakeven && m_settings.use_active_trailing)
|
||||
{
|
||||
MessageBox("BreakEven and Active Trail cannot both be enabled.", "SLTP panel error", MB_ICONERROR);
|
||||
m_settings.use_active_trailing = false;
|
||||
RefreshButtons();
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
RefreshControls();
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool ReadDoubleValue(CEdit &edit,
|
||||
const string label,
|
||||
const double min_value,
|
||||
const double max_value,
|
||||
const double default_value,
|
||||
double &target)
|
||||
{
|
||||
const string text = edit.Text();
|
||||
if (!IsNumericText(text))
|
||||
{
|
||||
ShowValueError(edit, label, min_value, max_value, default_value, target);
|
||||
return false;
|
||||
}
|
||||
|
||||
const double value = StringToDouble(text);
|
||||
if (value < min_value || value > max_value)
|
||||
{
|
||||
ShowValueError(edit, label, min_value, max_value, default_value, target);
|
||||
return false;
|
||||
}
|
||||
|
||||
target = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShowValueError(CEdit &edit,
|
||||
const string label,
|
||||
const double min_value,
|
||||
const double max_value,
|
||||
const double default_value,
|
||||
double &target)
|
||||
{
|
||||
MessageBox(label + " must be between " + DoubleToString(min_value, 1) +
|
||||
" and " + DoubleToString(max_value, 1) + ".",
|
||||
"SLTP panel error",
|
||||
MB_ICONERROR);
|
||||
edit.Text(DoubleToString(default_value, 1));
|
||||
target = default_value;
|
||||
}
|
||||
|
||||
bool IsNumericText(const string text)
|
||||
{
|
||||
bool has_digit = false;
|
||||
bool has_dot = false;
|
||||
bool seen_value_char = false;
|
||||
const int length = StringLen(text);
|
||||
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
const ushort ch = StringGetCharacter(text, i);
|
||||
|
||||
if (ch == 32 || ch == 9)
|
||||
continue;
|
||||
|
||||
if (ch >= 48 && ch <= 57)
|
||||
{
|
||||
has_digit = true;
|
||||
seen_value_char = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == 46 && !has_dot)
|
||||
{
|
||||
has_dot = true;
|
||||
seen_value_char = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((ch == 43 || ch == 45) && !seen_value_char)
|
||||
{
|
||||
seen_value_char = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return has_digit;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,494 @@
|
||||
#ifndef SLTP_MANAGER_PANEL_MQH
|
||||
#define SLTP_MANAGER_PANEL_MQH
|
||||
|
||||
#include <Controls/Dialog.mqh>
|
||||
#include <Controls/Button.mqh>
|
||||
#include <Controls/Edit.mqh>
|
||||
#include <Controls/Label.mqh>
|
||||
|
||||
#define HIT_SLTP_PANEL_NAME "HIT_SLTP_Manager_Panel"
|
||||
#define HIT_SLTP_PANEL_WIDTH 500
|
||||
#define HIT_SLTP_PANEL_HEIGHT 310
|
||||
#define HIT_SLTP_ROW_HEIGHT 18
|
||||
#define HIT_SLTP_LABEL_X 15
|
||||
#define HIT_SLTP_BUTTON_X 15
|
||||
#define HIT_SLTP_EDIT_X1 150
|
||||
#define HIT_SLTP_EDIT_X2 235
|
||||
#define HIT_SLTP_EDIT_X3 320
|
||||
#define HIT_SLTP_EDIT_X4 405
|
||||
#define HIT_SLTP_FONT_SIZE 9
|
||||
#define HIT_SLTP_FONT_STYLE "Arial Black"
|
||||
|
||||
#define HIT_SLTP_MASTER_BTN_NAME "HIT_SLTP_MASTER_BTN"
|
||||
#define HIT_SLTP_BE_BTN_NAME "HIT_SLTP_BE_BTN"
|
||||
#define HIT_SLTP_ELAPSED_BE_BTN_NAME "HIT_SLTP_ELAPSED_BE_BTN"
|
||||
#define HIT_SLTP_ACTIVE_TRAIL_BTN_NAME "HIT_SLTP_ACTIVE_TRAIL_BTN"
|
||||
#define HIT_SLTP_TP_PROGRESS_BTN_NAME "HIT_SLTP_TP_PROGRESS_BTN"
|
||||
#define HIT_SLTP_HIGH_VOL_BTN_NAME "HIT_SLTP_HIGH_VOL_BTN"
|
||||
#define HIT_SLTP_APPLY_BTN_NAME "HIT_SLTP_APPLY_BTN"
|
||||
|
||||
#define HIT_SLTP_BE_TRIGGER_EDIT_NAME "HIT_SLTP_BE_TRIGGER"
|
||||
#define HIT_SLTP_BE_BUFFER_EDIT_NAME "HIT_SLTP_BE_BUFFER"
|
||||
#define HIT_SLTP_ELAPSED_HOURS_EDIT_NAME "HIT_SLTP_ELAPSED_HOURS"
|
||||
#define HIT_SLTP_ELAPSED_BUFFER_EDIT_NAME "HIT_SLTP_ELAPSED_BUFFER"
|
||||
#define HIT_SLTP_ACTIVE_START_EDIT_NAME "HIT_SLTP_ACTIVE_START"
|
||||
#define HIT_SLTP_ACTIVE_OFFSET_EDIT_NAME "HIT_SLTP_ACTIVE_OFFSET"
|
||||
#define HIT_SLTP_ACTIVE_STEP_TRIGGER_EDIT_NAME "HIT_SLTP_ACTIVE_STEP_TRIGGER"
|
||||
#define HIT_SLTP_ACTIVE_STEP_MOVE_EDIT_NAME "HIT_SLTP_ACTIVE_STEP_MOVE"
|
||||
#define HIT_SLTP_TP_TRIGGER_EDIT_NAME "HIT_SLTP_TP_TRIGGER"
|
||||
#define HIT_SLTP_TP_LOCK_EDIT_NAME "HIT_SLTP_TP_LOCK"
|
||||
|
||||
struct SLTPManagerPanelSettings
|
||||
{
|
||||
bool manager_enabled;
|
||||
|
||||
bool use_breakeven;
|
||||
double breakeven_trigger_pips;
|
||||
double breakeven_buffer_pips;
|
||||
|
||||
bool use_elapsed_breakeven;
|
||||
double elapsed_breakeven_hours;
|
||||
double elapsed_breakeven_buffer_pips;
|
||||
|
||||
bool use_active_trailing;
|
||||
double active_breakeven_pips;
|
||||
double active_stop_loss_offset_pips;
|
||||
double active_step_trigger_pips;
|
||||
double active_step_move_pips;
|
||||
|
||||
bool use_tp_progress_stop;
|
||||
double tp_progress_trigger_percent;
|
||||
double tp_progress_sl_lock_percent;
|
||||
|
||||
bool use_high_volatility_limit;
|
||||
};
|
||||
|
||||
class CSLTPManagerPanel
|
||||
{
|
||||
private:
|
||||
CAppDialog m_panel;
|
||||
|
||||
CButton m_button_master;
|
||||
CButton m_button_breakeven;
|
||||
CButton m_button_elapsed_breakeven;
|
||||
CButton m_button_active_trailing;
|
||||
CButton m_button_tp_progress;
|
||||
CButton m_button_high_volatility;
|
||||
CButton m_button_apply;
|
||||
|
||||
CEdit m_edit_breakeven_trigger;
|
||||
CEdit m_edit_breakeven_buffer;
|
||||
CEdit m_edit_elapsed_hours;
|
||||
CEdit m_edit_elapsed_buffer;
|
||||
CEdit m_edit_active_start;
|
||||
CEdit m_edit_active_offset;
|
||||
CEdit m_edit_active_step_trigger;
|
||||
CEdit m_edit_active_step_move;
|
||||
CEdit m_edit_tp_trigger;
|
||||
CEdit m_edit_tp_lock;
|
||||
|
||||
CLabel m_label_title;
|
||||
CLabel m_label_master;
|
||||
CLabel m_label_breakeven;
|
||||
CLabel m_label_elapsed_breakeven;
|
||||
CLabel m_label_active_trailing;
|
||||
CLabel m_label_tp_progress;
|
||||
CLabel m_label_high_volatility;
|
||||
CLabel m_label_be_trigger;
|
||||
CLabel m_label_be_buffer;
|
||||
CLabel m_label_elapsed_hours;
|
||||
CLabel m_label_elapsed_buffer;
|
||||
CLabel m_label_active_start;
|
||||
CLabel m_label_active_offset;
|
||||
CLabel m_label_active_step_trigger;
|
||||
CLabel m_label_active_step_move;
|
||||
CLabel m_label_tp_trigger;
|
||||
CLabel m_label_tp_lock;
|
||||
|
||||
SLTPManagerPanelSettings m_settings;
|
||||
|
||||
public:
|
||||
void Init(const SLTPManagerPanelSettings &initial_settings)
|
||||
{
|
||||
m_settings = initial_settings;
|
||||
}
|
||||
|
||||
bool CreatePanel()
|
||||
{
|
||||
const int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
|
||||
int panel_x = chart_width - HIT_SLTP_PANEL_WIDTH - 20;
|
||||
if (panel_x < 10)
|
||||
panel_x = 10;
|
||||
const int panel_y = 25;
|
||||
|
||||
if (!m_panel.Create(0, HIT_SLTP_PANEL_NAME, 0, panel_x, panel_y,
|
||||
panel_x + HIT_SLTP_PANEL_WIDTH + 8,
|
||||
panel_y + HIT_SLTP_PANEL_HEIGHT))
|
||||
{
|
||||
Print("SLTP panel create failed: err=", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
CreateLabel(m_label_title, "HIT_SLTP_LABEL_TITLE", HIT_SLTP_LABEL_X, 8, 170, "HIT SLTP Manager");
|
||||
|
||||
CreateLabel(m_label_master, "HIT_SLTP_LABEL_MASTER", HIT_SLTP_LABEL_X, 34, 110, "Manager");
|
||||
CreateButton(m_button_master, HIT_SLTP_MASTER_BTN_NAME, HIT_SLTP_BUTTON_X + 70, 32, 58, 22);
|
||||
|
||||
CreateLabel(m_label_breakeven, "HIT_SLTP_LABEL_BE", HIT_SLTP_LABEL_X, 64, 110, "BreakEven");
|
||||
CreateButton(m_button_breakeven, HIT_SLTP_BE_BTN_NAME, HIT_SLTP_BUTTON_X + 70, 62, 58, 22);
|
||||
CreateLabel(m_label_be_trigger, "HIT_SLTP_LABEL_BE_TRG", HIT_SLTP_EDIT_X1, 48, 60, "Trigger");
|
||||
CreateLabel(m_label_be_buffer, "HIT_SLTP_LABEL_BE_BUF", HIT_SLTP_EDIT_X2, 48, 60, "Buffer");
|
||||
CreateEdit(m_edit_breakeven_trigger, HIT_SLTP_BE_TRIGGER_EDIT_NAME, HIT_SLTP_EDIT_X1, 64, 65);
|
||||
CreateEdit(m_edit_breakeven_buffer, HIT_SLTP_BE_BUFFER_EDIT_NAME, HIT_SLTP_EDIT_X2, 64, 65);
|
||||
|
||||
CreateLabel(m_label_elapsed_breakeven, "HIT_SLTP_LABEL_ELAPSED", HIT_SLTP_LABEL_X, 98, 110, "Elapsed BE");
|
||||
CreateButton(m_button_elapsed_breakeven, HIT_SLTP_ELAPSED_BE_BTN_NAME, HIT_SLTP_BUTTON_X + 70, 96, 58, 22);
|
||||
CreateLabel(m_label_elapsed_hours, "HIT_SLTP_LABEL_EL_H", HIT_SLTP_EDIT_X1, 82, 60, "Hours");
|
||||
CreateLabel(m_label_elapsed_buffer, "HIT_SLTP_LABEL_EL_BUF", HIT_SLTP_EDIT_X2, 82, 60, "Buffer");
|
||||
CreateEdit(m_edit_elapsed_hours, HIT_SLTP_ELAPSED_HOURS_EDIT_NAME, HIT_SLTP_EDIT_X1, 98, 65);
|
||||
CreateEdit(m_edit_elapsed_buffer, HIT_SLTP_ELAPSED_BUFFER_EDIT_NAME, HIT_SLTP_EDIT_X2, 98, 65);
|
||||
|
||||
CreateLabel(m_label_active_trailing, "HIT_SLTP_LABEL_ACTIVE", HIT_SLTP_LABEL_X, 132, 110, "Active Trail");
|
||||
CreateButton(m_button_active_trailing, HIT_SLTP_ACTIVE_TRAIL_BTN_NAME, HIT_SLTP_BUTTON_X + 70, 130, 58, 22);
|
||||
CreateLabel(m_label_active_start, "HIT_SLTP_LABEL_ACT_START", HIT_SLTP_EDIT_X1, 116, 60, "Start");
|
||||
CreateLabel(m_label_active_offset, "HIT_SLTP_LABEL_ACT_OFFSET", HIT_SLTP_EDIT_X2, 116, 60, "Offset");
|
||||
CreateLabel(m_label_active_step_trigger, "HIT_SLTP_LABEL_ACT_TRG", HIT_SLTP_EDIT_X3, 116, 70, "StepTrig");
|
||||
CreateLabel(m_label_active_step_move, "HIT_SLTP_LABEL_ACT_MOVE", HIT_SLTP_EDIT_X4, 116, 70, "StepMove");
|
||||
CreateEdit(m_edit_active_start, HIT_SLTP_ACTIVE_START_EDIT_NAME, HIT_SLTP_EDIT_X1, 132, 65);
|
||||
CreateEdit(m_edit_active_offset, HIT_SLTP_ACTIVE_OFFSET_EDIT_NAME, HIT_SLTP_EDIT_X2, 132, 65);
|
||||
CreateEdit(m_edit_active_step_trigger, HIT_SLTP_ACTIVE_STEP_TRIGGER_EDIT_NAME, HIT_SLTP_EDIT_X3, 132, 65);
|
||||
CreateEdit(m_edit_active_step_move, HIT_SLTP_ACTIVE_STEP_MOVE_EDIT_NAME, HIT_SLTP_EDIT_X4, 132, 65);
|
||||
|
||||
CreateLabel(m_label_tp_progress, "HIT_SLTP_LABEL_TP_PROGRESS", HIT_SLTP_LABEL_X, 166, 110, "TP Progress");
|
||||
CreateButton(m_button_tp_progress, HIT_SLTP_TP_PROGRESS_BTN_NAME, HIT_SLTP_BUTTON_X + 70, 164, 58, 22);
|
||||
CreateLabel(m_label_tp_trigger, "HIT_SLTP_LABEL_TP_TRG", HIT_SLTP_EDIT_X1, 150, 60, "Trig %");
|
||||
CreateLabel(m_label_tp_lock, "HIT_SLTP_LABEL_TP_LOCK", HIT_SLTP_EDIT_X2, 150, 60, "Lock %");
|
||||
CreateEdit(m_edit_tp_trigger, HIT_SLTP_TP_TRIGGER_EDIT_NAME, HIT_SLTP_EDIT_X1, 166, 65);
|
||||
CreateEdit(m_edit_tp_lock, HIT_SLTP_TP_LOCK_EDIT_NAME, HIT_SLTP_EDIT_X2, 166, 65);
|
||||
|
||||
CreateLabel(m_label_high_volatility, "HIT_SLTP_LABEL_HIGH_VOL", HIT_SLTP_LABEL_X, 200, 110, "High Vol");
|
||||
CreateButton(m_button_high_volatility, HIT_SLTP_HIGH_VOL_BTN_NAME, HIT_SLTP_BUTTON_X + 70, 198, 58, 22);
|
||||
|
||||
CreateButton(m_button_apply, HIT_SLTP_APPLY_BTN_NAME, HIT_SLTP_EDIT_X4, 246, 70, 26);
|
||||
m_button_apply.Text("APPLY");
|
||||
m_button_apply.Color(clrWhite);
|
||||
m_button_apply.ColorBackground(clrSeaGreen);
|
||||
m_button_apply.ColorBorder(clrSeaGreen);
|
||||
|
||||
RefreshControls();
|
||||
m_panel.Run();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Destroy(const int reason)
|
||||
{
|
||||
m_panel.Destroy(reason);
|
||||
}
|
||||
|
||||
void SetInitialValues()
|
||||
{
|
||||
RefreshControls();
|
||||
}
|
||||
|
||||
bool HandleChartEvent(const int id,
|
||||
const long &lparam,
|
||||
const double &dparam,
|
||||
const string &sparam,
|
||||
SLTPManagerPanelSettings &applied_settings)
|
||||
{
|
||||
m_panel.ChartEvent(id, lparam, dparam, sparam);
|
||||
|
||||
if (id != CHARTEVENT_OBJECT_CLICK)
|
||||
return false;
|
||||
|
||||
if (sparam == HIT_SLTP_MASTER_BTN_NAME)
|
||||
{
|
||||
m_settings.manager_enabled = !m_settings.manager_enabled;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_BE_BTN_NAME)
|
||||
{
|
||||
m_settings.use_breakeven = !m_settings.use_breakeven;
|
||||
if (m_settings.use_breakeven)
|
||||
m_settings.use_active_trailing = false;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_ELAPSED_BE_BTN_NAME)
|
||||
{
|
||||
m_settings.use_elapsed_breakeven = !m_settings.use_elapsed_breakeven;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_ACTIVE_TRAIL_BTN_NAME)
|
||||
{
|
||||
m_settings.use_active_trailing = !m_settings.use_active_trailing;
|
||||
if (m_settings.use_active_trailing)
|
||||
m_settings.use_breakeven = false;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_TP_PROGRESS_BTN_NAME)
|
||||
{
|
||||
m_settings.use_tp_progress_stop = !m_settings.use_tp_progress_stop;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_HIGH_VOL_BTN_NAME)
|
||||
{
|
||||
m_settings.use_high_volatility_limit = !m_settings.use_high_volatility_limit;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_APPLY_BTN_NAME)
|
||||
{
|
||||
if (!ReadInputs())
|
||||
return false;
|
||||
|
||||
applied_settings = m_settings;
|
||||
Print("HIT SLTP panel settings applied.");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void CreateButton(CButton &button,
|
||||
const string name,
|
||||
const int x,
|
||||
const int y,
|
||||
const int width,
|
||||
const int height)
|
||||
{
|
||||
if (!button.Create(0, name, 0, x, y, 0, 0))
|
||||
{
|
||||
Print("SLTP button create failed: ", name, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
button.Width(width);
|
||||
button.Height(height);
|
||||
button.Font(HIT_SLTP_FONT_STYLE);
|
||||
button.FontSize(HIT_SLTP_FONT_SIZE);
|
||||
m_panel.Add(button);
|
||||
}
|
||||
|
||||
void CreateLabel(CLabel &label,
|
||||
const string name,
|
||||
const int x,
|
||||
const int y,
|
||||
const int width,
|
||||
const string text)
|
||||
{
|
||||
if (!label.Create(0, name, 0, x, y, 0, 0))
|
||||
{
|
||||
Print("SLTP label create failed: ", name, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
label.Width(width);
|
||||
label.Height(HIT_SLTP_ROW_HEIGHT);
|
||||
label.Text(text);
|
||||
label.Font(HIT_SLTP_FONT_STYLE);
|
||||
label.FontSize(HIT_SLTP_FONT_SIZE);
|
||||
label.Color(clrDimGray);
|
||||
label.ColorBackground(clrDimGray);
|
||||
label.ColorBorder(clrDimGray);
|
||||
m_panel.Add(label);
|
||||
}
|
||||
|
||||
void CreateEdit(CEdit &edit,
|
||||
const string name,
|
||||
const int x,
|
||||
const int y,
|
||||
const int width)
|
||||
{
|
||||
if (!edit.Create(0, name, 0, x, y, 0, 0))
|
||||
{
|
||||
Print("SLTP edit create failed: ", name, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
edit.Width(width);
|
||||
edit.Height(HIT_SLTP_ROW_HEIGHT);
|
||||
edit.Font(HIT_SLTP_FONT_STYLE);
|
||||
edit.FontSize(HIT_SLTP_FONT_SIZE);
|
||||
edit.Color(clrDimGray);
|
||||
edit.ColorBackground(clrWhite);
|
||||
edit.ColorBorder(clrDimGray);
|
||||
m_panel.Add(edit);
|
||||
}
|
||||
|
||||
void RefreshControls()
|
||||
{
|
||||
RefreshButtons();
|
||||
|
||||
m_edit_breakeven_trigger.Text(DoubleToString(m_settings.breakeven_trigger_pips, 1));
|
||||
m_edit_breakeven_buffer.Text(DoubleToString(m_settings.breakeven_buffer_pips, 1));
|
||||
m_edit_elapsed_hours.Text(DoubleToString(m_settings.elapsed_breakeven_hours, 1));
|
||||
m_edit_elapsed_buffer.Text(DoubleToString(m_settings.elapsed_breakeven_buffer_pips, 1));
|
||||
m_edit_active_start.Text(DoubleToString(m_settings.active_breakeven_pips, 1));
|
||||
m_edit_active_offset.Text(DoubleToString(m_settings.active_stop_loss_offset_pips, 1));
|
||||
m_edit_active_step_trigger.Text(DoubleToString(m_settings.active_step_trigger_pips, 1));
|
||||
m_edit_active_step_move.Text(DoubleToString(m_settings.active_step_move_pips, 1));
|
||||
m_edit_tp_trigger.Text(DoubleToString(m_settings.tp_progress_trigger_percent, 1));
|
||||
m_edit_tp_lock.Text(DoubleToString(m_settings.tp_progress_sl_lock_percent, 1));
|
||||
}
|
||||
|
||||
void RefreshButtons()
|
||||
{
|
||||
ApplyToggleButton(m_button_master, m_settings.manager_enabled, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_breakeven, m_settings.use_breakeven, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_elapsed_breakeven, m_settings.use_elapsed_breakeven, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_active_trailing, m_settings.use_active_trailing, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_tp_progress, m_settings.use_tp_progress_stop, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_high_volatility, m_settings.use_high_volatility_limit, "ON", "OFF");
|
||||
}
|
||||
|
||||
void ApplyToggleButton(CButton &button,
|
||||
const bool enabled,
|
||||
const string on_text,
|
||||
const string off_text)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
button.Text(on_text);
|
||||
button.Color(clrWhite);
|
||||
button.ColorBackground(clrRoyalBlue);
|
||||
button.ColorBorder(clrRoyalBlue);
|
||||
}
|
||||
else
|
||||
{
|
||||
button.Text(off_text);
|
||||
button.Color(clrRoyalBlue);
|
||||
button.ColorBackground(clrWhite);
|
||||
button.ColorBorder(clrRoyalBlue);
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadInputs()
|
||||
{
|
||||
bool ok = true;
|
||||
|
||||
ok = ReadDoubleValue(m_edit_breakeven_trigger, "BreakEven Trigger", 0.0, 10000.0,
|
||||
m_settings.breakeven_trigger_pips, m_settings.breakeven_trigger_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_breakeven_buffer, "BreakEven Buffer", 0.0, 10000.0,
|
||||
m_settings.breakeven_buffer_pips, m_settings.breakeven_buffer_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_elapsed_hours, "Elapsed BE Hours", 0.0, 10000.0,
|
||||
m_settings.elapsed_breakeven_hours, m_settings.elapsed_breakeven_hours) && ok;
|
||||
ok = ReadDoubleValue(m_edit_elapsed_buffer, "Elapsed BE Buffer", 0.0, 10000.0,
|
||||
m_settings.elapsed_breakeven_buffer_pips, m_settings.elapsed_breakeven_buffer_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_active_start, "Active Trail Start", 0.0, 10000.0,
|
||||
m_settings.active_breakeven_pips, m_settings.active_breakeven_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_active_offset, "Active Trail Offset", 0.0, 10000.0,
|
||||
m_settings.active_stop_loss_offset_pips, m_settings.active_stop_loss_offset_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_active_step_trigger, "Active Trail Step Trigger", 0.1, 10000.0,
|
||||
m_settings.active_step_trigger_pips, m_settings.active_step_trigger_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_active_step_move, "Active Trail Step Move", 0.1, 10000.0,
|
||||
m_settings.active_step_move_pips, m_settings.active_step_move_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_tp_trigger, "TP Progress Trigger", 0.1, 100.0,
|
||||
m_settings.tp_progress_trigger_percent, m_settings.tp_progress_trigger_percent) && ok;
|
||||
ok = ReadDoubleValue(m_edit_tp_lock, "TP Progress Lock", 0.0, 100.0,
|
||||
m_settings.tp_progress_sl_lock_percent, m_settings.tp_progress_sl_lock_percent) && ok;
|
||||
|
||||
if (m_settings.use_breakeven && m_settings.use_active_trailing)
|
||||
{
|
||||
MessageBox("BreakEven and Active Trail cannot both be enabled.", "SLTP panel error", MB_ICONERROR);
|
||||
m_settings.use_active_trailing = false;
|
||||
RefreshButtons();
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
RefreshControls();
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool ReadDoubleValue(CEdit &edit,
|
||||
const string label,
|
||||
const double min_value,
|
||||
const double max_value,
|
||||
const double default_value,
|
||||
double &target)
|
||||
{
|
||||
const string text = edit.Text();
|
||||
if (!IsNumericText(text))
|
||||
{
|
||||
ShowValueError(edit, label, min_value, max_value, default_value, target);
|
||||
return false;
|
||||
}
|
||||
|
||||
const double value = StringToDouble(text);
|
||||
if (value < min_value || value > max_value)
|
||||
{
|
||||
ShowValueError(edit, label, min_value, max_value, default_value, target);
|
||||
return false;
|
||||
}
|
||||
|
||||
target = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShowValueError(CEdit &edit,
|
||||
const string label,
|
||||
const double min_value,
|
||||
const double max_value,
|
||||
const double default_value,
|
||||
double &target)
|
||||
{
|
||||
MessageBox(label + " must be between " + DoubleToString(min_value, 1) +
|
||||
" and " + DoubleToString(max_value, 1) + ".",
|
||||
"SLTP panel error",
|
||||
MB_ICONERROR);
|
||||
edit.Text(DoubleToString(default_value, 1));
|
||||
target = default_value;
|
||||
}
|
||||
|
||||
bool IsNumericText(const string text)
|
||||
{
|
||||
bool has_digit = false;
|
||||
bool has_dot = false;
|
||||
bool seen_value_char = false;
|
||||
const int length = StringLen(text);
|
||||
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
const ushort ch = StringGetCharacter(text, i);
|
||||
|
||||
if (ch == 32 || ch == 9)
|
||||
continue;
|
||||
|
||||
if (ch >= 48 && ch <= 57)
|
||||
{
|
||||
has_digit = true;
|
||||
seen_value_char = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == 46 && !has_dot)
|
||||
{
|
||||
has_dot = true;
|
||||
seen_value_char = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((ch == 43 || ch == 45) && !seen_value_char)
|
||||
{
|
||||
seen_value_char = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return has_digit;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,543 @@
|
||||
#ifndef SLTP_MANAGER_PANEL_MQH
|
||||
#define SLTP_MANAGER_PANEL_MQH
|
||||
|
||||
#include <Controls/Dialog.mqh>
|
||||
#include <Controls/Button.mqh>
|
||||
#include <Controls/Edit.mqh>
|
||||
#include <Controls/Label.mqh>
|
||||
|
||||
#define HIT_SLTP_PANEL_NAME "HIT_SLTP_Manager_Panel"
|
||||
#define HIT_SLTP_PANEL_WIDTH 620
|
||||
#define HIT_SLTP_PANEL_HEIGHT 370
|
||||
#define HIT_SLTP_ROW_HEIGHT 22
|
||||
#define HIT_SLTP_LABEL_X 15
|
||||
#define HIT_SLTP_LABEL_WIDTH 125
|
||||
#define HIT_SLTP_BUTTON_X 150
|
||||
#define HIT_SLTP_BUTTON_WIDTH 74
|
||||
#define HIT_SLTP_BUTTON_HEIGHT 26
|
||||
#define HIT_SLTP_EDIT_X1 240
|
||||
#define HIT_SLTP_EDIT_X2 330
|
||||
#define HIT_SLTP_EDIT_X3 420
|
||||
#define HIT_SLTP_EDIT_X4 510
|
||||
#define HIT_SLTP_EDIT_WIDTH 80
|
||||
#define HIT_SLTP_APPLY_WIDTH 90
|
||||
#define HIT_SLTP_APPLY_HEIGHT 28
|
||||
#define HIT_SLTP_TITLE_Y 8
|
||||
#define HIT_SLTP_TITLE_WIDTH 240
|
||||
#define HIT_SLTP_MASTER_ROW_Y 38
|
||||
#define HIT_SLTP_BE_ROW_Y 82
|
||||
#define HIT_SLTP_ELAPSED_ROW_Y 128
|
||||
#define HIT_SLTP_ACTIVE_ROW_Y 174
|
||||
#define HIT_SLTP_TP_ROW_Y 220
|
||||
#define HIT_SLTP_HIGH_VOL_ROW_Y 266
|
||||
#define HIT_SLTP_APPLY_ROW_Y 312
|
||||
#define HIT_SLTP_FONT_SIZE 9
|
||||
#define HIT_SLTP_FONT_STYLE "Arial Black"
|
||||
|
||||
#define HIT_SLTP_MASTER_BTN_NAME "HIT_SLTP_MASTER_BTN"
|
||||
#define HIT_SLTP_BE_BTN_NAME "HIT_SLTP_BE_BTN"
|
||||
#define HIT_SLTP_ELAPSED_BE_BTN_NAME "HIT_SLTP_ELAPSED_BE_BTN"
|
||||
#define HIT_SLTP_ACTIVE_TRAIL_BTN_NAME "HIT_SLTP_ACTIVE_TRAIL_BTN"
|
||||
#define HIT_SLTP_TP_PROGRESS_BTN_NAME "HIT_SLTP_TP_PROGRESS_BTN"
|
||||
#define HIT_SLTP_HIGH_VOL_BTN_NAME "HIT_SLTP_HIGH_VOL_BTN"
|
||||
#define HIT_SLTP_APPLY_BTN_NAME "HIT_SLTP_APPLY_BTN"
|
||||
|
||||
#define HIT_SLTP_BE_TRIGGER_EDIT_NAME "HIT_SLTP_BE_TRIGGER"
|
||||
#define HIT_SLTP_BE_BUFFER_EDIT_NAME "HIT_SLTP_BE_BUFFER"
|
||||
#define HIT_SLTP_ELAPSED_HOURS_EDIT_NAME "HIT_SLTP_ELAPSED_HOURS"
|
||||
#define HIT_SLTP_ELAPSED_BUFFER_EDIT_NAME "HIT_SLTP_ELAPSED_BUFFER"
|
||||
#define HIT_SLTP_ACTIVE_START_EDIT_NAME "HIT_SLTP_ACTIVE_START"
|
||||
#define HIT_SLTP_ACTIVE_OFFSET_EDIT_NAME "HIT_SLTP_ACTIVE_OFFSET"
|
||||
#define HIT_SLTP_ACTIVE_STEP_TRIGGER_EDIT_NAME "HIT_SLTP_ACTIVE_STEP_TRIGGER"
|
||||
#define HIT_SLTP_ACTIVE_STEP_MOVE_EDIT_NAME "HIT_SLTP_ACTIVE_STEP_MOVE"
|
||||
#define HIT_SLTP_TP_TRIGGER_EDIT_NAME "HIT_SLTP_TP_TRIGGER"
|
||||
#define HIT_SLTP_TP_LOCK_EDIT_NAME "HIT_SLTP_TP_LOCK"
|
||||
|
||||
struct SLTPManagerPanelSettings
|
||||
{
|
||||
bool manager_enabled;
|
||||
|
||||
bool use_breakeven;
|
||||
double breakeven_trigger_pips;
|
||||
double breakeven_buffer_pips;
|
||||
|
||||
bool use_elapsed_breakeven;
|
||||
double elapsed_breakeven_hours;
|
||||
double elapsed_breakeven_buffer_pips;
|
||||
|
||||
bool use_active_trailing;
|
||||
double active_breakeven_pips;
|
||||
double active_stop_loss_offset_pips;
|
||||
double active_step_trigger_pips;
|
||||
double active_step_move_pips;
|
||||
|
||||
bool use_tp_progress_stop;
|
||||
double tp_progress_trigger_percent;
|
||||
double tp_progress_sl_lock_percent;
|
||||
|
||||
bool use_high_volatility_limit;
|
||||
};
|
||||
|
||||
class CSLTPManagerPanel
|
||||
{
|
||||
private:
|
||||
CAppDialog m_panel;
|
||||
|
||||
CButton m_button_master;
|
||||
CButton m_button_breakeven;
|
||||
CButton m_button_elapsed_breakeven;
|
||||
CButton m_button_active_trailing;
|
||||
CButton m_button_tp_progress;
|
||||
CButton m_button_high_volatility;
|
||||
CButton m_button_apply;
|
||||
|
||||
CEdit m_edit_breakeven_trigger;
|
||||
CEdit m_edit_breakeven_buffer;
|
||||
CEdit m_edit_elapsed_hours;
|
||||
CEdit m_edit_elapsed_buffer;
|
||||
CEdit m_edit_active_start;
|
||||
CEdit m_edit_active_offset;
|
||||
CEdit m_edit_active_step_trigger;
|
||||
CEdit m_edit_active_step_move;
|
||||
CEdit m_edit_tp_trigger;
|
||||
CEdit m_edit_tp_lock;
|
||||
|
||||
CLabel m_label_title;
|
||||
CLabel m_label_master;
|
||||
CLabel m_label_breakeven;
|
||||
CLabel m_label_elapsed_breakeven;
|
||||
CLabel m_label_active_trailing;
|
||||
CLabel m_label_tp_progress;
|
||||
CLabel m_label_high_volatility;
|
||||
CLabel m_label_be_trigger;
|
||||
CLabel m_label_be_buffer;
|
||||
CLabel m_label_elapsed_hours;
|
||||
CLabel m_label_elapsed_buffer;
|
||||
CLabel m_label_active_start;
|
||||
CLabel m_label_active_offset;
|
||||
CLabel m_label_active_step_trigger;
|
||||
CLabel m_label_active_step_move;
|
||||
CLabel m_label_tp_trigger;
|
||||
CLabel m_label_tp_lock;
|
||||
|
||||
SLTPManagerPanelSettings m_settings;
|
||||
|
||||
public:
|
||||
void Init(const SLTPManagerPanelSettings &initial_settings)
|
||||
{
|
||||
m_settings = initial_settings;
|
||||
}
|
||||
|
||||
bool CreatePanel()
|
||||
{
|
||||
const int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
|
||||
int panel_x = chart_width - HIT_SLTP_PANEL_WIDTH - 20;
|
||||
if (panel_x < 10)
|
||||
panel_x = 10;
|
||||
const int panel_y = 25;
|
||||
|
||||
if (!m_panel.Create(0, HIT_SLTP_PANEL_NAME, 0, panel_x, panel_y,
|
||||
panel_x + HIT_SLTP_PANEL_WIDTH + 8,
|
||||
panel_y + HIT_SLTP_PANEL_HEIGHT))
|
||||
{
|
||||
Print("SLTP panel create failed: err=", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
CreateLabel(m_label_title, "HIT_SLTP_LABEL_TITLE", HIT_SLTP_LABEL_X, HIT_SLTP_TITLE_Y,
|
||||
HIT_SLTP_TITLE_WIDTH, "HIT SLTP Manager");
|
||||
|
||||
CreateLabel(m_label_master, "HIT_SLTP_LABEL_MASTER", HIT_SLTP_LABEL_X, HIT_SLTP_MASTER_ROW_Y,
|
||||
HIT_SLTP_LABEL_WIDTH, "Manager");
|
||||
CreateButton(m_button_master, HIT_SLTP_MASTER_BTN_NAME, HIT_SLTP_BUTTON_X, HIT_SLTP_MASTER_ROW_Y,
|
||||
HIT_SLTP_BUTTON_WIDTH, HIT_SLTP_BUTTON_HEIGHT);
|
||||
|
||||
CreateLabel(m_label_breakeven, "HIT_SLTP_LABEL_BE", HIT_SLTP_LABEL_X, HIT_SLTP_BE_ROW_Y,
|
||||
HIT_SLTP_LABEL_WIDTH, "BreakEven");
|
||||
CreateButton(m_button_breakeven, HIT_SLTP_BE_BTN_NAME, HIT_SLTP_BUTTON_X, HIT_SLTP_BE_ROW_Y,
|
||||
HIT_SLTP_BUTTON_WIDTH, HIT_SLTP_BUTTON_HEIGHT);
|
||||
CreateLabel(m_label_be_trigger, "HIT_SLTP_LABEL_BE_TRG", HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_BE_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Trigger");
|
||||
CreateLabel(m_label_be_buffer, "HIT_SLTP_LABEL_BE_BUF", HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_BE_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Buffer");
|
||||
CreateEdit(m_edit_breakeven_trigger, HIT_SLTP_BE_TRIGGER_EDIT_NAME, HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_BE_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
CreateEdit(m_edit_breakeven_buffer, HIT_SLTP_BE_BUFFER_EDIT_NAME, HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_BE_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
|
||||
CreateLabel(m_label_elapsed_breakeven, "HIT_SLTP_LABEL_ELAPSED", HIT_SLTP_LABEL_X,
|
||||
HIT_SLTP_ELAPSED_ROW_Y, HIT_SLTP_LABEL_WIDTH, "Elapsed BE");
|
||||
CreateButton(m_button_elapsed_breakeven, HIT_SLTP_ELAPSED_BE_BTN_NAME, HIT_SLTP_BUTTON_X,
|
||||
HIT_SLTP_ELAPSED_ROW_Y, HIT_SLTP_BUTTON_WIDTH, HIT_SLTP_BUTTON_HEIGHT);
|
||||
CreateLabel(m_label_elapsed_hours, "HIT_SLTP_LABEL_EL_H", HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_ELAPSED_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Hours");
|
||||
CreateLabel(m_label_elapsed_buffer, "HIT_SLTP_LABEL_EL_BUF", HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_ELAPSED_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Buffer");
|
||||
CreateEdit(m_edit_elapsed_hours, HIT_SLTP_ELAPSED_HOURS_EDIT_NAME, HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_ELAPSED_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
CreateEdit(m_edit_elapsed_buffer, HIT_SLTP_ELAPSED_BUFFER_EDIT_NAME, HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_ELAPSED_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
|
||||
CreateLabel(m_label_active_trailing, "HIT_SLTP_LABEL_ACTIVE", HIT_SLTP_LABEL_X,
|
||||
HIT_SLTP_ACTIVE_ROW_Y, HIT_SLTP_LABEL_WIDTH, "Active Trail");
|
||||
CreateButton(m_button_active_trailing, HIT_SLTP_ACTIVE_TRAIL_BTN_NAME, HIT_SLTP_BUTTON_X,
|
||||
HIT_SLTP_ACTIVE_ROW_Y, HIT_SLTP_BUTTON_WIDTH, HIT_SLTP_BUTTON_HEIGHT);
|
||||
CreateLabel(m_label_active_start, "HIT_SLTP_LABEL_ACT_START", HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_ACTIVE_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Start");
|
||||
CreateLabel(m_label_active_offset, "HIT_SLTP_LABEL_ACT_OFFSET", HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_ACTIVE_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Offset");
|
||||
CreateLabel(m_label_active_step_trigger, "HIT_SLTP_LABEL_ACT_TRG", HIT_SLTP_EDIT_X3,
|
||||
HIT_SLTP_ACTIVE_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "StepTrig");
|
||||
CreateLabel(m_label_active_step_move, "HIT_SLTP_LABEL_ACT_MOVE", HIT_SLTP_EDIT_X4,
|
||||
HIT_SLTP_ACTIVE_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "StepMove");
|
||||
CreateEdit(m_edit_active_start, HIT_SLTP_ACTIVE_START_EDIT_NAME, HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_ACTIVE_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
CreateEdit(m_edit_active_offset, HIT_SLTP_ACTIVE_OFFSET_EDIT_NAME, HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_ACTIVE_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
CreateEdit(m_edit_active_step_trigger, HIT_SLTP_ACTIVE_STEP_TRIGGER_EDIT_NAME, HIT_SLTP_EDIT_X3,
|
||||
HIT_SLTP_ACTIVE_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
CreateEdit(m_edit_active_step_move, HIT_SLTP_ACTIVE_STEP_MOVE_EDIT_NAME, HIT_SLTP_EDIT_X4,
|
||||
HIT_SLTP_ACTIVE_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
|
||||
CreateLabel(m_label_tp_progress, "HIT_SLTP_LABEL_TP_PROGRESS", HIT_SLTP_LABEL_X,
|
||||
HIT_SLTP_TP_ROW_Y, HIT_SLTP_LABEL_WIDTH, "TP Progress");
|
||||
CreateButton(m_button_tp_progress, HIT_SLTP_TP_PROGRESS_BTN_NAME, HIT_SLTP_BUTTON_X,
|
||||
HIT_SLTP_TP_ROW_Y, HIT_SLTP_BUTTON_WIDTH, HIT_SLTP_BUTTON_HEIGHT);
|
||||
CreateLabel(m_label_tp_trigger, "HIT_SLTP_LABEL_TP_TRG", HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_TP_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Trig %");
|
||||
CreateLabel(m_label_tp_lock, "HIT_SLTP_LABEL_TP_LOCK", HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_TP_ROW_Y - HIT_SLTP_ROW_HEIGHT, HIT_SLTP_EDIT_WIDTH, "Lock %");
|
||||
CreateEdit(m_edit_tp_trigger, HIT_SLTP_TP_TRIGGER_EDIT_NAME, HIT_SLTP_EDIT_X1,
|
||||
HIT_SLTP_TP_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
CreateEdit(m_edit_tp_lock, HIT_SLTP_TP_LOCK_EDIT_NAME, HIT_SLTP_EDIT_X2,
|
||||
HIT_SLTP_TP_ROW_Y, HIT_SLTP_EDIT_WIDTH);
|
||||
|
||||
CreateLabel(m_label_high_volatility, "HIT_SLTP_LABEL_HIGH_VOL", HIT_SLTP_LABEL_X,
|
||||
HIT_SLTP_HIGH_VOL_ROW_Y, HIT_SLTP_LABEL_WIDTH, "High Vol");
|
||||
CreateButton(m_button_high_volatility, HIT_SLTP_HIGH_VOL_BTN_NAME, HIT_SLTP_BUTTON_X,
|
||||
HIT_SLTP_HIGH_VOL_ROW_Y, HIT_SLTP_BUTTON_WIDTH, HIT_SLTP_BUTTON_HEIGHT);
|
||||
|
||||
CreateButton(m_button_apply, HIT_SLTP_APPLY_BTN_NAME, HIT_SLTP_EDIT_X4, HIT_SLTP_APPLY_ROW_Y,
|
||||
HIT_SLTP_APPLY_WIDTH, HIT_SLTP_APPLY_HEIGHT);
|
||||
m_button_apply.Text("APPLY");
|
||||
m_button_apply.Color(clrWhite);
|
||||
m_button_apply.ColorBackground(clrSeaGreen);
|
||||
m_button_apply.ColorBorder(clrSeaGreen);
|
||||
|
||||
RefreshControls();
|
||||
m_panel.Run();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Destroy(const int reason)
|
||||
{
|
||||
m_panel.Destroy(reason);
|
||||
}
|
||||
|
||||
void SetInitialValues()
|
||||
{
|
||||
RefreshControls();
|
||||
}
|
||||
|
||||
bool HandleChartEvent(const int id,
|
||||
const long &lparam,
|
||||
const double &dparam,
|
||||
const string &sparam,
|
||||
SLTPManagerPanelSettings &applied_settings)
|
||||
{
|
||||
m_panel.ChartEvent(id, lparam, dparam, sparam);
|
||||
|
||||
if (id != CHARTEVENT_OBJECT_CLICK)
|
||||
return false;
|
||||
|
||||
if (sparam == HIT_SLTP_MASTER_BTN_NAME)
|
||||
{
|
||||
m_settings.manager_enabled = !m_settings.manager_enabled;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_BE_BTN_NAME)
|
||||
{
|
||||
m_settings.use_breakeven = !m_settings.use_breakeven;
|
||||
if (m_settings.use_breakeven)
|
||||
m_settings.use_active_trailing = false;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_ELAPSED_BE_BTN_NAME)
|
||||
{
|
||||
m_settings.use_elapsed_breakeven = !m_settings.use_elapsed_breakeven;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_ACTIVE_TRAIL_BTN_NAME)
|
||||
{
|
||||
m_settings.use_active_trailing = !m_settings.use_active_trailing;
|
||||
if (m_settings.use_active_trailing)
|
||||
m_settings.use_breakeven = false;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_TP_PROGRESS_BTN_NAME)
|
||||
{
|
||||
m_settings.use_tp_progress_stop = !m_settings.use_tp_progress_stop;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_HIGH_VOL_BTN_NAME)
|
||||
{
|
||||
m_settings.use_high_volatility_limit = !m_settings.use_high_volatility_limit;
|
||||
RefreshButtons();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == HIT_SLTP_APPLY_BTN_NAME)
|
||||
{
|
||||
if (!ReadInputs())
|
||||
return false;
|
||||
|
||||
applied_settings = m_settings;
|
||||
Print("HIT SLTP panel settings applied.");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void CreateButton(CButton &button,
|
||||
const string name,
|
||||
const int x,
|
||||
const int y,
|
||||
const int width,
|
||||
const int height)
|
||||
{
|
||||
if (!button.Create(0, name, 0, x, y, 0, 0))
|
||||
{
|
||||
Print("SLTP button create failed: ", name, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
button.Width(width);
|
||||
button.Height(height);
|
||||
button.Font(HIT_SLTP_FONT_STYLE);
|
||||
button.FontSize(HIT_SLTP_FONT_SIZE);
|
||||
m_panel.Add(button);
|
||||
}
|
||||
|
||||
void CreateLabel(CLabel &label,
|
||||
const string name,
|
||||
const int x,
|
||||
const int y,
|
||||
const int width,
|
||||
const string text)
|
||||
{
|
||||
if (!label.Create(0, name, 0, x, y, 0, 0))
|
||||
{
|
||||
Print("SLTP label create failed: ", name, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
label.Width(width);
|
||||
label.Height(HIT_SLTP_ROW_HEIGHT);
|
||||
label.Text(text);
|
||||
label.Font(HIT_SLTP_FONT_STYLE);
|
||||
label.FontSize(HIT_SLTP_FONT_SIZE);
|
||||
label.Color(clrDimGray);
|
||||
label.ColorBackground(clrDimGray);
|
||||
label.ColorBorder(clrDimGray);
|
||||
m_panel.Add(label);
|
||||
}
|
||||
|
||||
void CreateEdit(CEdit &edit,
|
||||
const string name,
|
||||
const int x,
|
||||
const int y,
|
||||
const int width)
|
||||
{
|
||||
if (!edit.Create(0, name, 0, x, y, 0, 0))
|
||||
{
|
||||
Print("SLTP edit create failed: ", name, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
edit.Width(width);
|
||||
edit.Height(HIT_SLTP_ROW_HEIGHT);
|
||||
edit.Font(HIT_SLTP_FONT_STYLE);
|
||||
edit.FontSize(HIT_SLTP_FONT_SIZE);
|
||||
edit.Color(clrDimGray);
|
||||
edit.ColorBackground(clrWhite);
|
||||
edit.ColorBorder(clrDimGray);
|
||||
m_panel.Add(edit);
|
||||
}
|
||||
|
||||
void RefreshControls()
|
||||
{
|
||||
RefreshButtons();
|
||||
|
||||
m_edit_breakeven_trigger.Text(DoubleToString(m_settings.breakeven_trigger_pips, 1));
|
||||
m_edit_breakeven_buffer.Text(DoubleToString(m_settings.breakeven_buffer_pips, 1));
|
||||
m_edit_elapsed_hours.Text(DoubleToString(m_settings.elapsed_breakeven_hours, 1));
|
||||
m_edit_elapsed_buffer.Text(DoubleToString(m_settings.elapsed_breakeven_buffer_pips, 1));
|
||||
m_edit_active_start.Text(DoubleToString(m_settings.active_breakeven_pips, 1));
|
||||
m_edit_active_offset.Text(DoubleToString(m_settings.active_stop_loss_offset_pips, 1));
|
||||
m_edit_active_step_trigger.Text(DoubleToString(m_settings.active_step_trigger_pips, 1));
|
||||
m_edit_active_step_move.Text(DoubleToString(m_settings.active_step_move_pips, 1));
|
||||
m_edit_tp_trigger.Text(DoubleToString(m_settings.tp_progress_trigger_percent, 1));
|
||||
m_edit_tp_lock.Text(DoubleToString(m_settings.tp_progress_sl_lock_percent, 1));
|
||||
}
|
||||
|
||||
void RefreshButtons()
|
||||
{
|
||||
ApplyToggleButton(m_button_master, m_settings.manager_enabled, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_breakeven, m_settings.use_breakeven, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_elapsed_breakeven, m_settings.use_elapsed_breakeven, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_active_trailing, m_settings.use_active_trailing, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_tp_progress, m_settings.use_tp_progress_stop, "ON", "OFF");
|
||||
ApplyToggleButton(m_button_high_volatility, m_settings.use_high_volatility_limit, "ON", "OFF");
|
||||
}
|
||||
|
||||
void ApplyToggleButton(CButton &button,
|
||||
const bool enabled,
|
||||
const string on_text,
|
||||
const string off_text)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
button.Text(on_text);
|
||||
button.Color(clrWhite);
|
||||
button.ColorBackground(clrRoyalBlue);
|
||||
button.ColorBorder(clrRoyalBlue);
|
||||
}
|
||||
else
|
||||
{
|
||||
button.Text(off_text);
|
||||
button.Color(clrRoyalBlue);
|
||||
button.ColorBackground(clrWhite);
|
||||
button.ColorBorder(clrRoyalBlue);
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadInputs()
|
||||
{
|
||||
bool ok = true;
|
||||
|
||||
ok = ReadDoubleValue(m_edit_breakeven_trigger, "BreakEven Trigger", 0.0, 10000.0,
|
||||
m_settings.breakeven_trigger_pips, m_settings.breakeven_trigger_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_breakeven_buffer, "BreakEven Buffer", 0.0, 10000.0,
|
||||
m_settings.breakeven_buffer_pips, m_settings.breakeven_buffer_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_elapsed_hours, "Elapsed BE Hours", 0.0, 10000.0,
|
||||
m_settings.elapsed_breakeven_hours, m_settings.elapsed_breakeven_hours) && ok;
|
||||
ok = ReadDoubleValue(m_edit_elapsed_buffer, "Elapsed BE Buffer", 0.0, 10000.0,
|
||||
m_settings.elapsed_breakeven_buffer_pips, m_settings.elapsed_breakeven_buffer_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_active_start, "Active Trail Start", 0.0, 10000.0,
|
||||
m_settings.active_breakeven_pips, m_settings.active_breakeven_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_active_offset, "Active Trail Offset", 0.0, 10000.0,
|
||||
m_settings.active_stop_loss_offset_pips, m_settings.active_stop_loss_offset_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_active_step_trigger, "Active Trail Step Trigger", 0.1, 10000.0,
|
||||
m_settings.active_step_trigger_pips, m_settings.active_step_trigger_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_active_step_move, "Active Trail Step Move", 0.1, 10000.0,
|
||||
m_settings.active_step_move_pips, m_settings.active_step_move_pips) && ok;
|
||||
ok = ReadDoubleValue(m_edit_tp_trigger, "TP Progress Trigger", 0.1, 100.0,
|
||||
m_settings.tp_progress_trigger_percent, m_settings.tp_progress_trigger_percent) && ok;
|
||||
ok = ReadDoubleValue(m_edit_tp_lock, "TP Progress Lock", 0.0, 100.0,
|
||||
m_settings.tp_progress_sl_lock_percent, m_settings.tp_progress_sl_lock_percent) && ok;
|
||||
|
||||
if (m_settings.use_breakeven && m_settings.use_active_trailing)
|
||||
{
|
||||
MessageBox("BreakEven and Active Trail cannot both be enabled.", "SLTP panel error", MB_ICONERROR);
|
||||
m_settings.use_active_trailing = false;
|
||||
RefreshButtons();
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
RefreshControls();
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool ReadDoubleValue(CEdit &edit,
|
||||
const string label,
|
||||
const double min_value,
|
||||
const double max_value,
|
||||
const double default_value,
|
||||
double &target)
|
||||
{
|
||||
const string text = edit.Text();
|
||||
if (!IsNumericText(text))
|
||||
{
|
||||
ShowValueError(edit, label, min_value, max_value, default_value, target);
|
||||
return false;
|
||||
}
|
||||
|
||||
const double value = StringToDouble(text);
|
||||
if (value < min_value || value > max_value)
|
||||
{
|
||||
ShowValueError(edit, label, min_value, max_value, default_value, target);
|
||||
return false;
|
||||
}
|
||||
|
||||
target = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShowValueError(CEdit &edit,
|
||||
const string label,
|
||||
const double min_value,
|
||||
const double max_value,
|
||||
const double default_value,
|
||||
double &target)
|
||||
{
|
||||
MessageBox(label + " must be between " + DoubleToString(min_value, 1) +
|
||||
" and " + DoubleToString(max_value, 1) + ".",
|
||||
"SLTP panel error",
|
||||
MB_ICONERROR);
|
||||
edit.Text(DoubleToString(default_value, 1));
|
||||
target = default_value;
|
||||
}
|
||||
|
||||
bool IsNumericText(const string text)
|
||||
{
|
||||
bool has_digit = false;
|
||||
bool has_dot = false;
|
||||
bool seen_value_char = false;
|
||||
const int length = StringLen(text);
|
||||
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
const ushort ch = StringGetCharacter(text, i);
|
||||
|
||||
if (ch == 32 || ch == 9)
|
||||
continue;
|
||||
|
||||
if (ch >= 48 && ch <= 57)
|
||||
{
|
||||
has_digit = true;
|
||||
seen_value_char = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == 46 && !has_dot)
|
||||
{
|
||||
has_dot = true;
|
||||
seen_value_char = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((ch == 43 || ch == 45) && !seen_value_char)
|
||||
{
|
||||
seen_value_char = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return has_digit;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,546 @@
|
||||
#ifndef TRADING_PANEL_PANEL_MQH
|
||||
#define TRADING_PANEL_PANEL_MQH
|
||||
|
||||
#include <Controls/Dialog.mqh>
|
||||
#include <Controls/Button.mqh>
|
||||
#include <Controls/Edit.mqh>
|
||||
#include <Controls/Label.mqh>
|
||||
|
||||
#define PANEL_NAME "SettingPanel"
|
||||
#define PANEL_WIDTH 420
|
||||
#define PANEL_HIEIGHT 260
|
||||
#define ROW_HEIGHT 18
|
||||
#define LBL_X 15
|
||||
#define EDIT_X 105
|
||||
#define BTN_X 160
|
||||
#define FONT_SIZE 9
|
||||
#define FONT_STYLE "Arial Black"
|
||||
|
||||
#define HEIKIN_BTN_NAME "Use_Heikin BTN"
|
||||
#define LIMIT_BTN_NAME "Time_Limit_Close BTN"
|
||||
#define INPUT_LOCK_BTN_NAME "INPUT_LOCK BTN"
|
||||
#define BUY_TP_PLUS_BTN_NAME "Buy_TP_Plus BTN"
|
||||
#define BUY_TP_MINUS_BTN_NAME "Buy_TP_Minus BTN"
|
||||
#define SELL_TP_PLUS_BTN_NAME "Sell_TP_Plus BTN"
|
||||
#define SELL_TP_MINUS_BTN_NAME "Sell_TP_Minus BTN"
|
||||
#define BUY_TOTAL_BE_BTN_NAME "BuyTotal_BE BTN"
|
||||
#define SELL_TOTAL_BE_BTN_NAME "SellTotal_BE BTN"
|
||||
|
||||
#define EDIT_EI_NAME "ExitTimeInterval"
|
||||
#define EDIT_RANGE_NAME "RangePips"
|
||||
#define EDIT_BREAKEVEN_NAME "BreakEven"
|
||||
#define EDIT_BREAKEVEN_INIT_NAME "BreakEvenInit"
|
||||
#define EDIT_STEP_TRAIL_NAME "StepTrail"
|
||||
#define EDIT_STEP_MOVE_NAME "StepMove"
|
||||
#define EDIT_SL_NAME "StopLoss"
|
||||
#define EDIT_TP_NAME "TakeProfit"
|
||||
#define EDIT_BUY_PLUS_NAME "Buy_TP"
|
||||
#define EDIT_SELL_TP_PLUS_NAME "Sell_TP"
|
||||
#define EDIT_SL_OFFSET_NAME "StopLossOffset"
|
||||
|
||||
class CTradingPanelPanel
|
||||
{
|
||||
private:
|
||||
CAppDialog m_panel;
|
||||
|
||||
CEdit m_input_1st;
|
||||
CEdit m_input_2nd;
|
||||
CEdit m_input_3rd;
|
||||
CEdit m_input_4th;
|
||||
CEdit m_input_5th;
|
||||
CEdit m_input_6th;
|
||||
CEdit m_input_7th;
|
||||
CEdit m_input_8th;
|
||||
CEdit m_input_9th;
|
||||
CEdit m_input_10th;
|
||||
CEdit m_input_11th;
|
||||
|
||||
CButton m_button_timelimit_exit;
|
||||
CButton m_button_input_lock;
|
||||
CButton m_button_buy_tp_plus;
|
||||
CButton m_button_buy_tp_minus;
|
||||
CButton m_button_sell_tp_plus;
|
||||
CButton m_button_sell_tp_minus;
|
||||
CButton m_button_buy_total_be;
|
||||
CButton m_button_sell_total_be;
|
||||
|
||||
CLabel m_infoLabel_1st;
|
||||
CLabel m_infoLabel_2nd;
|
||||
CLabel m_infoLabel_3rd;
|
||||
CLabel m_infoLabel_4th;
|
||||
CLabel m_infoLabel_5th;
|
||||
CLabel m_infoLabel_6th;
|
||||
CLabel m_infoLabel_7th;
|
||||
CLabel m_infoLabel_9th;
|
||||
CLabel m_infoLabel_10th;
|
||||
CLabel m_infoLabel_11th;
|
||||
CLabel m_infoLabel_12th;
|
||||
CLabel m_infoLabel_13th;
|
||||
CLabel m_infoLabel_14th;
|
||||
CLabel m_infoLabel_15th;
|
||||
CLabel m_infoLabel_16th;
|
||||
|
||||
int m_exitTimeIntervalInSeconds;
|
||||
double m_range_pips;
|
||||
double m_breakeven_pips;
|
||||
double m_stop_loss_offset_pips;
|
||||
double m_step_trigger_pips;
|
||||
double m_step_move_pips;
|
||||
double m_default_sl_pips;
|
||||
double m_take_profit_pips;
|
||||
double m_buy_tp_pips;
|
||||
double m_sell_tp_pips;
|
||||
double m_stop_offset_pips;
|
||||
|
||||
public:
|
||||
void Init(const int initial_exit_time_interval_hours,
|
||||
const double initial_range_pips,
|
||||
const double initial_breakeven_pips,
|
||||
const double initial_stop_loss_offset_pips,
|
||||
const double initial_step_trigger_pips,
|
||||
const double initial_step_move_pips,
|
||||
const double initial_default_sl_pips,
|
||||
const double initial_take_profit_pips,
|
||||
const double initial_tp_edit_pips,
|
||||
const double initial_stop_offset_pips)
|
||||
{
|
||||
m_exitTimeIntervalInSeconds = initial_exit_time_interval_hours * 3600;
|
||||
m_range_pips = initial_range_pips;
|
||||
m_breakeven_pips = initial_breakeven_pips;
|
||||
m_stop_loss_offset_pips = initial_stop_loss_offset_pips;
|
||||
m_step_trigger_pips = initial_step_trigger_pips;
|
||||
m_step_move_pips = initial_step_move_pips;
|
||||
m_default_sl_pips = initial_default_sl_pips;
|
||||
m_take_profit_pips = initial_take_profit_pips;
|
||||
m_buy_tp_pips = initial_tp_edit_pips;
|
||||
m_sell_tp_pips = initial_tp_edit_pips;
|
||||
m_stop_offset_pips = initial_stop_offset_pips;
|
||||
}
|
||||
|
||||
bool CreatePanel(const bool is_input_locked)
|
||||
{
|
||||
const int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
|
||||
const int panel_x = 10;
|
||||
const int panel_y = chart_height - PANEL_HIEIGHT - 10;
|
||||
|
||||
if (!m_panel.Create(0, PANEL_NAME, 0, panel_x, panel_y,
|
||||
panel_x + PANEL_WIDTH + 8, panel_y + PANEL_HIEIGHT))
|
||||
{
|
||||
Print("Panel create failed: err=", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
CreateButton(m_button_timelimit_exit, LIMIT_BTN_NAME, BTN_X, 150, 80, ROW_HEIGHT + 5,
|
||||
"ON", clrRoyalBlue, clrWhite);
|
||||
CreateButton(m_button_input_lock, INPUT_LOCK_BTN_NAME, BTN_X, 195, 80, ROW_HEIGHT + 5,
|
||||
"UNLOCK", clrRoyalBlue, clrWhite);
|
||||
CreateButton(m_button_buy_tp_plus, BUY_TP_PLUS_BTN_NAME, BTN_X + 193, 20, 22, ROW_HEIGHT,
|
||||
"▲", clrGreen, clrWhite);
|
||||
CreateButton(m_button_buy_tp_minus, BUY_TP_MINUS_BTN_NAME, BTN_X + 219, 20, 22, ROW_HEIGHT,
|
||||
"▼", clrRed, clrWhite);
|
||||
CreateButton(m_button_sell_tp_plus, SELL_TP_PLUS_BTN_NAME, BTN_X + 193, 45, 22, ROW_HEIGHT,
|
||||
"▼", clrGreen, clrWhite);
|
||||
CreateButton(m_button_sell_tp_minus, SELL_TP_MINUS_BTN_NAME, BTN_X + 219, 45, 22, ROW_HEIGHT,
|
||||
"▲", clrRed, clrWhite);
|
||||
CreateButton(m_button_buy_total_be, BUY_TOTAL_BE_BTN_NAME, BTN_X + 100, 105, 60, ROW_HEIGHT + 5,
|
||||
"OFF", clrWhite, clrRoyalBlue);
|
||||
CreateButton(m_button_sell_total_be, SELL_TOTAL_BE_BTN_NAME, BTN_X + 180, 105, 60, ROW_HEIGHT + 5,
|
||||
"OFF", clrWhite, clrRoyalBlue);
|
||||
|
||||
CreateLabel(m_infoLabel_1st, "label_1", LBL_X, 20, "ExitTime(h)");
|
||||
CreateLabel(m_infoLabel_2nd, "label_2", LBL_X, 42, "Range");
|
||||
CreateLabel(m_infoLabel_3rd, "label_3", LBL_X, 64, "BreakEven");
|
||||
CreateLabel(m_infoLabel_4th, "label_4", LBL_X, 109, "TrailStep");
|
||||
CreateLabel(m_infoLabel_5th, "label_5", LBL_X, 131, "StepMove");
|
||||
CreateLabel(m_infoLabel_6th, "label_6", LBL_X, 153, "StopLoss");
|
||||
CreateLabel(m_infoLabel_7th, "label_7", LBL_X, 175, "TakeProfit");
|
||||
CreateLabel(m_infoLabel_9th, "label_9", BTN_X + 4, 130, "EXIT_TIME");
|
||||
CreateLabel(m_infoLabel_10th, "label_10", BTN_X + 10, 175, "SETTING");
|
||||
CreateLabel(m_infoLabel_11th, "label_11", BTN_X + 100, 20, "Buy TP");
|
||||
CreateLabel(m_infoLabel_12th, "label_12", BTN_X + 100, 45, "Sell TP");
|
||||
CreateLabel(m_infoLabel_13th, "label_13", BTN_X + 95, 85, "AllBuy_BE");
|
||||
CreateLabel(m_infoLabel_14th, "label_14", BTN_X + 175, 85, "AllSell_BE");
|
||||
CreateLabel(m_infoLabel_15th, "label_19", LBL_X, 87, "BE_Offset");
|
||||
CreateLabel(m_infoLabel_16th, "label_22", LBL_X, 197, "StopOffset");
|
||||
|
||||
CreateEdit(m_input_1st, EDIT_EI_NAME, EDIT_X, 20);
|
||||
CreateEdit(m_input_2nd, EDIT_RANGE_NAME, EDIT_X, 42);
|
||||
CreateEdit(m_input_3rd, EDIT_BREAKEVEN_NAME, EDIT_X, 64);
|
||||
CreateEdit(m_input_10th, EDIT_BREAKEVEN_INIT_NAME, EDIT_X, 86);
|
||||
CreateEdit(m_input_4th, EDIT_STEP_TRAIL_NAME, EDIT_X, 108);
|
||||
CreateEdit(m_input_5th, EDIT_STEP_MOVE_NAME, EDIT_X, 130);
|
||||
CreateEdit(m_input_6th, EDIT_SL_NAME, EDIT_X, 152);
|
||||
CreateEdit(m_input_7th, EDIT_TP_NAME, EDIT_X, 174);
|
||||
CreateEdit(m_input_8th, EDIT_BUY_PLUS_NAME, BTN_X + 151, 20);
|
||||
CreateEdit(m_input_9th, EDIT_SELL_TP_PLUS_NAME, BTN_X + 151, 45);
|
||||
CreateEdit(m_input_11th, EDIT_SL_OFFSET_NAME, EDIT_X, 196);
|
||||
|
||||
SetReadOnly(is_input_locked);
|
||||
m_panel.Run();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Destroy(const int reason)
|
||||
{
|
||||
m_panel.Destroy(reason);
|
||||
}
|
||||
|
||||
void SetInitialValues()
|
||||
{
|
||||
m_input_1st.Text(IntegerToString(m_exitTimeIntervalInSeconds / 3600));
|
||||
m_input_2nd.Text(DoubleToString(m_range_pips, 0));
|
||||
m_input_3rd.Text(DoubleToString(m_breakeven_pips, 0));
|
||||
m_input_4th.Text(DoubleToString(m_step_trigger_pips, 0));
|
||||
m_input_5th.Text(DoubleToString(m_step_move_pips, 0));
|
||||
m_input_6th.Text(DoubleToString(m_default_sl_pips, 0));
|
||||
m_input_7th.Text(DoubleToString(m_take_profit_pips, 0));
|
||||
m_input_8th.Text(DoubleToString(m_buy_tp_pips, 0));
|
||||
m_input_9th.Text(DoubleToString(m_sell_tp_pips, 0));
|
||||
m_input_10th.Text(DoubleToString(m_stop_loss_offset_pips, 0));
|
||||
m_input_11th.Text(DoubleToString(m_stop_offset_pips, 0));
|
||||
}
|
||||
|
||||
bool HandleChartEvent(const int id,
|
||||
const long &lparam,
|
||||
const double &dparam,
|
||||
const string &sparam,
|
||||
const int default_exit_time_interval_hours,
|
||||
const double default_range_pips,
|
||||
const double default_breakeven_pips,
|
||||
const double default_stop_loss_offset_pips,
|
||||
const double default_step_trigger_pips,
|
||||
const double default_step_move_pips,
|
||||
const double default_sl_pips,
|
||||
const double default_take_profit_pips,
|
||||
const double default_tp_edit_pips,
|
||||
const double default_stop_offset_pips,
|
||||
bool &io_timelimit_exit,
|
||||
bool &io_input_lock,
|
||||
bool &io_buy_total_be_flg,
|
||||
bool &io_sell_total_be_flg,
|
||||
int &active_exit_seconds,
|
||||
double &active_breakeven_pips,
|
||||
double &active_stop_loss_offset_pips,
|
||||
double &active_step_trigger_pips,
|
||||
double &active_step_move_pips,
|
||||
double &active_default_sl_pips,
|
||||
double &active_take_profit_pips,
|
||||
double &active_stop_offset_pips,
|
||||
ENUM_POSITION_TYPE &tp_side,
|
||||
double &tp_adjust_pips)
|
||||
{
|
||||
tp_side = (ENUM_POSITION_TYPE)-1;
|
||||
tp_adjust_pips = 0.0;
|
||||
|
||||
UpdateInputValues(default_exit_time_interval_hours,
|
||||
default_range_pips,
|
||||
default_breakeven_pips,
|
||||
default_stop_loss_offset_pips,
|
||||
default_step_trigger_pips,
|
||||
default_step_move_pips,
|
||||
default_sl_pips,
|
||||
default_take_profit_pips,
|
||||
default_tp_edit_pips,
|
||||
default_stop_offset_pips);
|
||||
|
||||
m_panel.ChartEvent(id, lparam, dparam, sparam);
|
||||
|
||||
if (id != CHARTEVENT_OBJECT_CLICK)
|
||||
return false;
|
||||
|
||||
if (sparam == LIMIT_BTN_NAME)
|
||||
{
|
||||
io_timelimit_exit = !io_timelimit_exit;
|
||||
ApplyToggleButton(m_button_timelimit_exit, io_timelimit_exit, "ON", "OFF");
|
||||
Print("TimeExit: ", io_timelimit_exit ? "ON" : "OFF");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == INPUT_LOCK_BTN_NAME)
|
||||
{
|
||||
io_input_lock = !io_input_lock;
|
||||
SetReadOnly(io_input_lock);
|
||||
|
||||
if (io_input_lock)
|
||||
{
|
||||
m_button_input_lock.Text("UNLOCK");
|
||||
m_button_input_lock.Color(clrWhite);
|
||||
m_button_input_lock.ColorBackground(clrRoyalBlue);
|
||||
m_button_input_lock.ColorBorder(clrRoyalBlue);
|
||||
Print("button_input_lock: パネルのインプット:ロック");
|
||||
|
||||
active_exit_seconds = m_exitTimeIntervalInSeconds;
|
||||
active_breakeven_pips = m_breakeven_pips;
|
||||
active_stop_loss_offset_pips = m_stop_loss_offset_pips;
|
||||
active_step_trigger_pips = m_step_trigger_pips;
|
||||
active_step_move_pips = m_step_move_pips;
|
||||
active_take_profit_pips = m_take_profit_pips;
|
||||
active_default_sl_pips = m_default_sl_pips;
|
||||
active_stop_offset_pips = m_stop_offset_pips;
|
||||
|
||||
Print("--------------------------------------------------------");
|
||||
Print("時間制限クローズ(時間): ", active_exit_seconds / 3600, "\n",
|
||||
"ブレークイーブン(pips): ", active_breakeven_pips, "\n",
|
||||
"ブレークイーブン建値(pips): ", active_stop_loss_offset_pips, "\n",
|
||||
"トレール更新(pips): ", active_step_trigger_pips, "\n",
|
||||
"トレール更新幅(pips): ", active_step_move_pips, "\n",
|
||||
"ストップロス(pips): ", active_default_sl_pips, "\n",
|
||||
"テイクプロフィット(pips): ", active_take_profit_pips, "\n",
|
||||
"ストップロスオフセット(pips): ", active_stop_offset_pips);
|
||||
Print("--------------------------------------------------------");
|
||||
Print("EA設定変更");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_button_input_lock.Text("LOCK");
|
||||
m_button_input_lock.Color(clrRoyalBlue);
|
||||
m_button_input_lock.ColorBackground(clrWhite);
|
||||
m_button_input_lock.ColorBorder(clrRoyalBlue);
|
||||
Print("button_input_lock: パネルのインプット:ロック解除");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == BUY_TP_PLUS_BTN_NAME)
|
||||
{
|
||||
Print("Buy_TP Plus PRESSED ", m_buy_tp_pips, " pips");
|
||||
tp_side = POSITION_TYPE_BUY;
|
||||
tp_adjust_pips = +m_buy_tp_pips;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sparam == BUY_TP_MINUS_BTN_NAME)
|
||||
{
|
||||
Print("Buy_TP Minus PRESSED ", m_buy_tp_pips, " pips");
|
||||
tp_side = POSITION_TYPE_BUY;
|
||||
tp_adjust_pips = -m_buy_tp_pips;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sparam == SELL_TP_PLUS_BTN_NAME)
|
||||
{
|
||||
Print("Sell_TP Plus PRESSED ", m_sell_tp_pips, " pips");
|
||||
tp_side = POSITION_TYPE_SELL;
|
||||
tp_adjust_pips = +m_sell_tp_pips;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sparam == SELL_TP_MINUS_BTN_NAME)
|
||||
{
|
||||
Print("Sell_TP Minus PRESSED ", m_sell_tp_pips, " pips");
|
||||
tp_side = POSITION_TYPE_SELL;
|
||||
tp_adjust_pips = -m_sell_tp_pips;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sparam == BUY_TOTAL_BE_BTN_NAME)
|
||||
{
|
||||
io_buy_total_be_flg = !io_buy_total_be_flg;
|
||||
ApplyToggleButton(m_button_buy_total_be, io_buy_total_be_flg, "ON", "OFF");
|
||||
Print("Buy_Total_BE: ", io_buy_total_be_flg ? "ON" : "OFF");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sparam == SELL_TOTAL_BE_BTN_NAME)
|
||||
{
|
||||
io_sell_total_be_flg = !io_sell_total_be_flg;
|
||||
ApplyToggleButton(m_button_sell_total_be, io_sell_total_be_flg, "ON", "OFF");
|
||||
Print("Sell_Total_BE: ", io_sell_total_be_flg ? "ON" : "OFF");
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void CreateButton(CButton &button,
|
||||
const string name,
|
||||
const int x,
|
||||
const int y,
|
||||
const int width,
|
||||
const int height,
|
||||
const string text,
|
||||
const color bg_color,
|
||||
const color text_color)
|
||||
{
|
||||
if (!button.Create(0, name, 0, x, y, 0, 0))
|
||||
{
|
||||
Print("Button create failed: ", name, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
button.Width(width);
|
||||
button.Height(height);
|
||||
button.ColorBackground(bg_color);
|
||||
button.ColorBorder(text_color == clrRoyalBlue ? clrRoyalBlue : bg_color);
|
||||
button.Text(text);
|
||||
button.Color(text_color);
|
||||
button.Font(FONT_STYLE);
|
||||
button.FontSize(FONT_SIZE);
|
||||
m_panel.Add(button);
|
||||
}
|
||||
|
||||
void CreateLabel(CLabel &label,
|
||||
const string name,
|
||||
const int x,
|
||||
const int y,
|
||||
const string text)
|
||||
{
|
||||
if (!label.Create(0, name, 0, x, y, 0, 0))
|
||||
{
|
||||
Print("Label create failed: ", name, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
label.Width(0);
|
||||
label.Height(ROW_HEIGHT);
|
||||
label.Text(text);
|
||||
label.Font(FONT_STYLE);
|
||||
label.FontSize(FONT_SIZE);
|
||||
label.ColorBackground(clrDimGray);
|
||||
label.Color(clrDimGray);
|
||||
label.ColorBorder(clrDimGray);
|
||||
m_panel.Add(label);
|
||||
}
|
||||
|
||||
void CreateEdit(CEdit &edit,
|
||||
const string name,
|
||||
const int x,
|
||||
const int y)
|
||||
{
|
||||
if (!edit.Create(0, name, 0, x, y, 0, 0))
|
||||
{
|
||||
Print("Edit create failed: ", name, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
edit.Width(37);
|
||||
edit.Height(ROW_HEIGHT);
|
||||
edit.Text("");
|
||||
edit.Font(FONT_STYLE);
|
||||
edit.FontSize(FONT_SIZE);
|
||||
edit.ColorBackground(clrWhite);
|
||||
edit.Color(clrDimGray);
|
||||
edit.ColorBorder(clrDimGray);
|
||||
m_panel.Add(edit);
|
||||
}
|
||||
|
||||
void SetReadOnly(const bool is_input_locked)
|
||||
{
|
||||
m_input_1st.ReadOnly(is_input_locked);
|
||||
m_input_2nd.ReadOnly(is_input_locked);
|
||||
m_input_3rd.ReadOnly(is_input_locked);
|
||||
m_input_4th.ReadOnly(is_input_locked);
|
||||
m_input_5th.ReadOnly(is_input_locked);
|
||||
m_input_6th.ReadOnly(is_input_locked);
|
||||
m_input_7th.ReadOnly(is_input_locked);
|
||||
m_input_8th.ReadOnly(is_input_locked);
|
||||
m_input_9th.ReadOnly(is_input_locked);
|
||||
m_input_10th.ReadOnly(is_input_locked);
|
||||
m_input_11th.ReadOnly(is_input_locked);
|
||||
}
|
||||
|
||||
void ApplyToggleButton(CButton &button,
|
||||
const bool enabled,
|
||||
const string on_text,
|
||||
const string off_text)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
button.Text(on_text);
|
||||
button.Color(clrWhite);
|
||||
button.ColorBackground(clrRoyalBlue);
|
||||
button.ColorBorder(clrRoyalBlue);
|
||||
}
|
||||
else
|
||||
{
|
||||
button.Text(off_text);
|
||||
button.Color(clrRoyalBlue);
|
||||
button.ColorBackground(clrWhite);
|
||||
button.ColorBorder(clrRoyalBlue);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateInputValues(const int default_exit_time_interval_hours,
|
||||
const double default_range_pips,
|
||||
const double default_breakeven_pips,
|
||||
const double default_stop_loss_offset_pips,
|
||||
const double default_step_trigger_pips,
|
||||
const double default_step_move_pips,
|
||||
const double default_sl_pips,
|
||||
const double default_take_profit_pips,
|
||||
const double default_tp_edit_pips,
|
||||
const double default_stop_offset_pips)
|
||||
{
|
||||
UpdateIntAsHours(m_input_1st, "ExitTimeIntervalは1〜240時間の範囲で入力してください",
|
||||
1, 240, default_exit_time_interval_hours,
|
||||
m_exitTimeIntervalInSeconds);
|
||||
UpdateIntValue(m_input_2nd, "Rangeは10〜300の範囲で入力してください",
|
||||
10, 300, default_range_pips, m_range_pips);
|
||||
UpdateIntValue(m_input_3rd, "BreakEvenは20〜200の範囲で入力してください",
|
||||
10, 200, default_breakeven_pips, m_breakeven_pips);
|
||||
|
||||
const int be_offset = (int)StringToInteger(m_input_10th.Text());
|
||||
if (be_offset >= 0 && be_offset <= 200 && be_offset <= m_breakeven_pips)
|
||||
m_stop_loss_offset_pips = be_offset;
|
||||
else
|
||||
{
|
||||
MessageBox("BreakEvenOffsetは0〜200の範囲かつ、BreakEven以下の範囲で入力してください", "エラー", MB_ICONERROR);
|
||||
m_input_10th.Text(DoubleToString(default_stop_loss_offset_pips, 0));
|
||||
m_stop_loss_offset_pips = default_stop_loss_offset_pips;
|
||||
}
|
||||
|
||||
UpdateIntValue(m_input_4th, "StepTrigerは2〜100の範囲で入力してください",
|
||||
2, 100, default_step_trigger_pips, m_step_trigger_pips);
|
||||
UpdateIntValue(m_input_5th, "StepMoveは1〜50の範囲で入力してください",
|
||||
1, 50, default_step_move_pips, m_step_move_pips);
|
||||
UpdateIntValue(m_input_6th, "StopLossは0〜999の範囲で入力してください",
|
||||
0, 999, default_sl_pips, m_default_sl_pips);
|
||||
UpdateIntValue(m_input_7th, "TakeProfitは0〜999の範囲で入力してください",
|
||||
0, 999, default_take_profit_pips, m_take_profit_pips);
|
||||
UpdateIntValue(m_input_11th, "StopLossOffsetは0〜50の範囲で入力してください",
|
||||
0, 50, default_stop_offset_pips, m_stop_offset_pips);
|
||||
UpdateIntValue(m_input_8th, "TakeProfitは0〜999の範囲で入力してください",
|
||||
0, 999, default_tp_edit_pips, m_buy_tp_pips);
|
||||
UpdateIntValue(m_input_9th, "TakeProfitは0〜999の範囲で入力してください",
|
||||
0, 999, default_tp_edit_pips, m_sell_tp_pips);
|
||||
}
|
||||
|
||||
void UpdateIntAsHours(CEdit &edit,
|
||||
const string error_message,
|
||||
const int min_value,
|
||||
const int max_value,
|
||||
const int default_hours,
|
||||
int &target_seconds)
|
||||
{
|
||||
const int value = (int)StringToInteger(edit.Text());
|
||||
if (value >= min_value && value <= max_value)
|
||||
{
|
||||
target_seconds = value * 3600;
|
||||
return;
|
||||
}
|
||||
|
||||
MessageBox(error_message, "エラー", MB_ICONERROR);
|
||||
edit.Text(IntegerToString(default_hours));
|
||||
target_seconds = default_hours * 3600;
|
||||
}
|
||||
|
||||
void UpdateIntValue(CEdit &edit,
|
||||
const string error_message,
|
||||
const int min_value,
|
||||
const int max_value,
|
||||
const double default_value,
|
||||
double &target)
|
||||
{
|
||||
const int value = (int)StringToInteger(edit.Text());
|
||||
if (value >= min_value && value <= max_value)
|
||||
{
|
||||
target = value;
|
||||
return;
|
||||
}
|
||||
|
||||
MessageBox(error_message, "エラー", MB_ICONERROR);
|
||||
edit.Text(DoubleToString(default_value, 0));
|
||||
target = default_value;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,619 @@
|
||||
#ifndef HIT_ENTRY_SIGNAL_MQH
|
||||
#define HIT_ENTRY_SIGNAL_MQH
|
||||
|
||||
//| エントリー判定が必要な場合のみ注文処理を実行する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @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の注文/ポジション数上限を確認します。
|
||||
* market_state=6は相場ボラ停止ではなく、Python/CSV/API失敗時の技術エラー停止として扱います。
|
||||
*/
|
||||
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_LOW_VOL_RANGE || state.trend_state > MARKET_HIGH_VOL_DOWN)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if(!IsM15SignalAligned(orderType, rates[prev_index], rates[last_index], en, entry_zone))
|
||||
return false;
|
||||
|
||||
return IsM15ImbalanceConfirmationPassed(orderType, rates, copied, last_index);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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 判定対象足を含めず、直前N本のM15平均実体サイズを返します。
|
||||
*
|
||||
* @param rates M15のMqlRates配列。
|
||||
* @param current_index 判定対象の配列index。
|
||||
* @param period 平均計算本数。
|
||||
* @return 平均実体サイズ。算出不能な場合は0。
|
||||
*/
|
||||
double AverageM15BodySize(const MqlRates &rates[], const int current_index, const int period)
|
||||
{
|
||||
if(period <= 0)
|
||||
return 0.0;
|
||||
if(current_index < period)
|
||||
return 0.0;
|
||||
|
||||
double total_body = 0.0;
|
||||
for(int i = current_index - period; i < current_index; i++)
|
||||
total_body += MathAbs(rates[i].close - rates[i].open);
|
||||
|
||||
return total_body / period;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15初動確認を行う注文タイプか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 初動フォローの追加確認を適用する注文タイプを返します。
|
||||
*/
|
||||
bool RequiresM15ImbalanceConfirmation(const int orderType)
|
||||
{
|
||||
return (orderType == 1 || orderType == 3);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15インバランス初動確認
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief T1/T3順張り注文に対してM15確定足の初動またはブレイクを確認します。
|
||||
*
|
||||
* H1/Pythonで決めた方向は上書きせず、発注直前にM15の方向一致と勢いだけを確認します。
|
||||
*/
|
||||
bool IsM15ImbalanceConfirmationPassed(const int orderType, const MqlRates &rates[], const int count, const int current_index)
|
||||
{
|
||||
if(!use_m15_imbalance_confirmation)
|
||||
return true;
|
||||
if(!RequiresM15ImbalanceConfirmation(orderType))
|
||||
return true;
|
||||
|
||||
if(m15_imbalance_avg_body_period <= 0 || m15_imbalance_sensitivity <= 0.0)
|
||||
{
|
||||
Print("[M15 Imbalance] invalid settings. period=", m15_imbalance_avg_body_period,
|
||||
" sensitivity=", m15_imbalance_sensitivity);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(count <= m15_imbalance_avg_body_period || current_index <= 0)
|
||||
{
|
||||
Print("[M15 Imbalance] insufficient bars. copied=", count,
|
||||
" period=", m15_imbalance_avg_body_period);
|
||||
return false;
|
||||
}
|
||||
|
||||
double avg_body = AverageM15BodySize(rates, current_index, m15_imbalance_avg_body_period);
|
||||
double min_avg_body = m15_imbalance_min_avg_body_points * Point();
|
||||
double current_body = MathAbs(rates[current_index].close - rates[current_index].open);
|
||||
|
||||
if(avg_body <= min_avg_body)
|
||||
{
|
||||
if(use_m15_imbalance_debug_log)
|
||||
Print("[M15 Imbalance] average body too small. avg=", avg_body,
|
||||
" min=", min_avg_body, " orderType=", orderType);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bullish = (rates[current_index].close > rates[current_index].open);
|
||||
bool bearish = (rates[current_index].close < rates[current_index].open);
|
||||
bool direction_ok = (orderType == 1 && bullish) || (orderType == 3 && bearish);
|
||||
bool body_ok = (current_body > avg_body * m15_imbalance_sensitivity);
|
||||
bool break_ok = false;
|
||||
|
||||
if(orderType == 1)
|
||||
break_ok = (rates[current_index].close > rates[current_index - 1].high);
|
||||
else if(orderType == 3)
|
||||
break_ok = (rates[current_index].close < rates[current_index - 1].low);
|
||||
|
||||
bool passed = (direction_ok && (body_ok || break_ok));
|
||||
if(use_m15_imbalance_debug_log)
|
||||
{
|
||||
Print("[M15 Imbalance] orderType=", orderType,
|
||||
" avg_body=", avg_body,
|
||||
" current_body=", current_body,
|
||||
" sensitivity=", m15_imbalance_sensitivity,
|
||||
" direction_ok=", direction_ok,
|
||||
" body_ok=", body_ok,
|
||||
" break_ok=", break_ok,
|
||||
" passed=", passed);
|
||||
}
|
||||
|
||||
return passed;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文タイプごとの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;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,685 @@
|
||||
#ifndef HIT_ENTRY_SIGNAL_MQH
|
||||
#define HIT_ENTRY_SIGNAL_MQH
|
||||
|
||||
//| エントリー判定が必要な場合のみ注文処理を実行する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @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の注文/ポジション数上限を確認します。
|
||||
* market_state=6は相場ボラ停止ではなく、Python/CSV/API失敗時の技術エラー停止として扱います。
|
||||
*/
|
||||
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_LOW_VOL_RANGE || state.trend_state > MARKET_HIGH_VOL_DOWN)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if(IsTargetCandidateTooOldForExecution(state))
|
||||
{
|
||||
Print("[Entry Skip] H1 target candidate is too old for new execution. age=",
|
||||
TargetCandidateAgeText(state),
|
||||
" max_age_minutes=", input_entry_max_candidate_age_minutes,
|
||||
" 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);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| H1候補価格の発注許容年齢を判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief M15確認が遅れて整った古いH1候補で新規注文しないように判定します。
|
||||
*
|
||||
* @param state EA全体の状態。H1候補価格の読込時刻を参照します。
|
||||
* @return 入力で指定した最大経過分数を超えている場合はtrue。
|
||||
*/
|
||||
bool IsTargetCandidateTooOldForExecution(EAState &state)
|
||||
{
|
||||
if(input_entry_max_candidate_age_minutes <= 0)
|
||||
return false;
|
||||
if(state.target_loaded_at <= 0)
|
||||
return false;
|
||||
|
||||
int max_age_seconds = input_entry_max_candidate_age_minutes * 60;
|
||||
if(max_age_seconds <= 0)
|
||||
return false;
|
||||
|
||||
return (TargetCandidateAgeSeconds(state) > max_age_seconds);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| H1候補価格の経過秒数を返す関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H1候補価格をEAへ読み込んでからの経過秒数を返します。
|
||||
*/
|
||||
int TargetCandidateAgeSeconds(EAState &state)
|
||||
{
|
||||
if(state.target_loaded_at <= 0)
|
||||
return -1;
|
||||
|
||||
return (int)(TimeCurrent() - state.target_loaded_at);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| H1候補価格の経過秒数をログ用文字列にする関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H1候補価格の経過秒数をログへ出しやすい文字列にします。
|
||||
*/
|
||||
string TargetCandidateAgeText(EAState &state)
|
||||
{
|
||||
int age_seconds = TargetCandidateAgeSeconds(state);
|
||||
if(age_seconds < 0)
|
||||
return "unknown";
|
||||
|
||||
return IntegerToString(age_seconds) + "s";
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 許可された注文タイプだけを順番に送信する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @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,
|
||||
" candidate_age=", TargetCandidateAgeText(state));
|
||||
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,
|
||||
" candidate_age=", TargetCandidateAgeText(state));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!IsM15EntryTimingConfirmed(orderType, ctx, en))
|
||||
{
|
||||
Print("[No ", entry_type, "] M15 timing not confirmed. cur=", cur_price, " en=", en,
|
||||
" candidate_age=", TargetCandidateAgeText(state),
|
||||
" max_age_minutes=", input_entry_max_candidate_age_minutes);
|
||||
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;
|
||||
}
|
||||
|
||||
if(!IsM15SignalAligned(orderType, rates[prev_index], rates[last_index], en, entry_zone))
|
||||
return false;
|
||||
|
||||
return IsM15ImbalanceConfirmationPassed(orderType, rates, copied, last_index);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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 判定対象足を含めず、直前N本のM15平均実体サイズを返します。
|
||||
*
|
||||
* @param rates M15のMqlRates配列。
|
||||
* @param current_index 判定対象の配列index。
|
||||
* @param period 平均計算本数。
|
||||
* @return 平均実体サイズ。算出不能な場合は0。
|
||||
*/
|
||||
double AverageM15BodySize(const MqlRates &rates[], const int current_index, const int period)
|
||||
{
|
||||
if(period <= 0)
|
||||
return 0.0;
|
||||
if(current_index < period)
|
||||
return 0.0;
|
||||
|
||||
double total_body = 0.0;
|
||||
for(int i = current_index - period; i < current_index; i++)
|
||||
total_body += MathAbs(rates[i].close - rates[i].open);
|
||||
|
||||
return total_body / period;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15初動確認を行う注文タイプか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 初動フォローの追加確認を適用する注文タイプを返します。
|
||||
*/
|
||||
bool RequiresM15ImbalanceConfirmation(const int orderType)
|
||||
{
|
||||
return (orderType == 1 || orderType == 3);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| M15インバランス初動確認
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief T1/T3順張り注文に対してM15確定足の初動またはブレイクを確認します。
|
||||
*
|
||||
* H1/Pythonで決めた方向は上書きせず、発注直前にM15の方向一致と勢いだけを確認します。
|
||||
*/
|
||||
bool IsM15ImbalanceConfirmationPassed(const int orderType, const MqlRates &rates[], const int count, const int current_index)
|
||||
{
|
||||
if(!use_m15_imbalance_confirmation)
|
||||
return true;
|
||||
if(!RequiresM15ImbalanceConfirmation(orderType))
|
||||
return true;
|
||||
|
||||
if(m15_imbalance_avg_body_period <= 0 || m15_imbalance_sensitivity <= 0.0)
|
||||
{
|
||||
Print("[M15 Imbalance] invalid settings. period=", m15_imbalance_avg_body_period,
|
||||
" sensitivity=", m15_imbalance_sensitivity);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(count <= m15_imbalance_avg_body_period || current_index <= 0)
|
||||
{
|
||||
Print("[M15 Imbalance] insufficient bars. copied=", count,
|
||||
" period=", m15_imbalance_avg_body_period);
|
||||
return false;
|
||||
}
|
||||
|
||||
double avg_body = AverageM15BodySize(rates, current_index, m15_imbalance_avg_body_period);
|
||||
double min_avg_body = m15_imbalance_min_avg_body_points * Point();
|
||||
double current_body = MathAbs(rates[current_index].close - rates[current_index].open);
|
||||
|
||||
if(avg_body <= min_avg_body)
|
||||
{
|
||||
if(use_m15_imbalance_debug_log)
|
||||
Print("[M15 Imbalance] average body too small. avg=", avg_body,
|
||||
" min=", min_avg_body, " orderType=", orderType);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bullish = (rates[current_index].close > rates[current_index].open);
|
||||
bool bearish = (rates[current_index].close < rates[current_index].open);
|
||||
bool direction_ok = (orderType == 1 && bullish) || (orderType == 3 && bearish);
|
||||
bool body_ok = (current_body > avg_body * m15_imbalance_sensitivity);
|
||||
bool break_ok = false;
|
||||
|
||||
if(orderType == 1)
|
||||
break_ok = (rates[current_index].close > rates[current_index - 1].high);
|
||||
else if(orderType == 3)
|
||||
break_ok = (rates[current_index].close < rates[current_index - 1].low);
|
||||
|
||||
bool passed = (direction_ok && (body_ok || break_ok));
|
||||
if(use_m15_imbalance_debug_log)
|
||||
{
|
||||
Print("[M15 Imbalance] orderType=", orderType,
|
||||
" avg_body=", avg_body,
|
||||
" current_body=", current_body,
|
||||
" sensitivity=", m15_imbalance_sensitivity,
|
||||
" direction_ok=", direction_ok,
|
||||
" body_ok=", body_ok,
|
||||
" break_ok=", break_ok,
|
||||
" passed=", passed);
|
||||
}
|
||||
|
||||
return passed;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文タイプごとの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;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,514 @@
|
||||
#ifndef HIT_PYTHON_SIGNAL_GATEWAY_MQH
|
||||
#define HIT_PYTHON_SIGNAL_GATEWAY_MQH
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 最新の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..5のmarket_state。6は技術エラー停止。
|
||||
*
|
||||
* ファイル未存在、読込失敗、異常値の場合は安全側として6(技術エラー停止)を設定します。
|
||||
*/
|
||||
void LoadTrendState(int &trend_state)
|
||||
{
|
||||
string filename = trend_state_file;
|
||||
trend_state = MARKET_TECHNICAL_ERROR_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_TECHNICAL_ERROR_STOP)
|
||||
trend_state = value;
|
||||
else
|
||||
Print("Invalid market_state value: ", line, ". Use technical error stop(6).");
|
||||
FileClose(filehandle);
|
||||
}
|
||||
else
|
||||
{ Print("Failed to open trend_state.txt"); trend_state = MARKET_TECHNICAL_ERROR_STOP; }
|
||||
}
|
||||
else
|
||||
{ Print("trend_state.txt not found"); trend_state = MARKET_TECHNICAL_ERROR_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]);
|
||||
|
||||
LoadTargetZones(state);
|
||||
if(use_split_entry_zone && cancel_old_split_pending_on_new_zone && state.zone_res_chk == 1)
|
||||
CancelStaleSplitPendingOrders(state.zone_candidate_id);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| target_zones.txt の状態を停止値へ初期化する関数
|
||||
//+------------------------------------------------------------------+
|
||||
void ResetTargetZones(EAState &state)
|
||||
{
|
||||
state.zone_res_chk = 0;
|
||||
state.zone_candidate_id = "0";
|
||||
|
||||
for(int t = 1; t <= 4; t++)
|
||||
{
|
||||
state.zone_low[t] = 0.0;
|
||||
state.zone_high[t] = 0.0;
|
||||
state.zone_tp[t] = 0.0;
|
||||
state.zone_sl[t] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| target_zones.txt の1戦略行を読み込む関数
|
||||
//+------------------------------------------------------------------+
|
||||
bool ParseTargetZoneLine(const string line, EAState &state, const int digits)
|
||||
{
|
||||
string parts[];
|
||||
int count = StringSplit(line, StringGetCharacter(",", 0), parts);
|
||||
if(count < 5)
|
||||
return false;
|
||||
|
||||
int strategy = (int)StringToInteger(parts[0]);
|
||||
if(strategy < 1 || strategy > 4)
|
||||
return false;
|
||||
|
||||
state.zone_low[strategy] = NormalizeDouble(StringToDouble(parts[1]), digits);
|
||||
state.zone_high[strategy] = NormalizeDouble(StringToDouble(parts[2]), digits);
|
||||
state.zone_tp[strategy] = NormalizeDouble(StringToDouble(parts[3]), digits);
|
||||
state.zone_sl[strategy] = NormalizeDouble(StringToDouble(parts[4]), digits);
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| target_zones.txt を読み込む関数
|
||||
//+------------------------------------------------------------------+
|
||||
void LoadTargetZones(EAState &state)
|
||||
{
|
||||
ResetTargetZones(state);
|
||||
|
||||
string filename = target_zones_file;
|
||||
if(!FileIsExist(filename))
|
||||
{
|
||||
Print("target_zones.txt not found. Split entry zones disabled for this candidate.");
|
||||
return;
|
||||
}
|
||||
|
||||
int filehandle = FileOpen(filename, FILE_READ | FILE_TXT);
|
||||
if(filehandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("Failed to open target_zones.txt. Error code: ", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
int line_index = 0;
|
||||
int loaded_strategies = 0;
|
||||
|
||||
while(!FileIsEnding(filehandle))
|
||||
{
|
||||
string line = FileReadString(filehandle);
|
||||
if(line == "")
|
||||
continue;
|
||||
|
||||
line_index++;
|
||||
if(line_index == 1)
|
||||
{
|
||||
int schema_version = (int)StringToInteger(line);
|
||||
if(schema_version != TARGET_ZONE_SCHEMA_VERSION)
|
||||
{
|
||||
Print("target_zones schema mismatch. loaded=", schema_version,
|
||||
" required=", TARGET_ZONE_SCHEMA_VERSION);
|
||||
FileClose(filehandle);
|
||||
ResetTargetZones(state);
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(line_index == 2)
|
||||
{
|
||||
state.zone_res_chk = (int)StringToInteger(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(line_index == 3)
|
||||
{
|
||||
state.zone_candidate_id = line;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ParseTargetZoneLine(line, state, digits))
|
||||
loaded_strategies++;
|
||||
}
|
||||
|
||||
FileClose(filehandle);
|
||||
|
||||
if(line_index < 7 || loaded_strategies < 4)
|
||||
{
|
||||
Print("target_zones.txt line count is short. loaded_lines=", line_index,
|
||||
" loaded_strategies=", loaded_strategies);
|
||||
ResetTargetZones(state);
|
||||
return;
|
||||
}
|
||||
|
||||
if(state.zone_res_chk != 1)
|
||||
ResetTargetZones(state);
|
||||
|
||||
Print("target_zones: res=", state.zone_res_chk,
|
||||
" id=", state.zone_candidate_id,
|
||||
" | T1 zone=", state.zone_low[1], "-", state.zone_high[1],
|
||||
" tp=", state.zone_tp[1], " sl=", state.zone_sl[1],
|
||||
" | T2 zone=", state.zone_low[2], "-", state.zone_high[2],
|
||||
" tp=", state.zone_tp[2], " sl=", state.zone_sl[2],
|
||||
" | T3 zone=", state.zone_low[3], "-", state.zone_high[3],
|
||||
" tp=", state.zone_tp[3], " sl=", state.zone_sl[3],
|
||||
" | T4 zone=", state.zone_low[4], "-", state.zone_high[4],
|
||||
" tp=", state.zone_tp[4], " sl=", state.zone_sl[4]);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,385 @@
|
||||
#ifndef HIT_PYTHON_SIGNAL_GATEWAY_MQH
|
||||
#define HIT_PYTHON_SIGNAL_GATEWAY_MQH
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 最新の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..5のmarket_state。6は技術エラー停止。
|
||||
*
|
||||
* ファイル未存在、読込失敗、異常値の場合は安全側として6(技術エラー停止)を設定します。
|
||||
*/
|
||||
void LoadTrendState(int &trend_state)
|
||||
{
|
||||
string filename = trend_state_file;
|
||||
trend_state = MARKET_TECHNICAL_ERROR_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_TECHNICAL_ERROR_STOP)
|
||||
trend_state = value;
|
||||
else
|
||||
Print("Invalid market_state value: ", line, ". Use technical error stop(6).");
|
||||
FileClose(filehandle);
|
||||
}
|
||||
else
|
||||
{ Print("Failed to open trend_state.txt"); trend_state = MARKET_TECHNICAL_ERROR_STOP; }
|
||||
}
|
||||
else
|
||||
{ Print("trend_state.txt not found"); trend_state = MARKET_TECHNICAL_ERROR_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);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,561 @@
|
||||
#ifndef HIT_TRADE_MANAGER_MQH
|
||||
#define HIT_TRADE_MANAGER_MQH
|
||||
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| エントリー注文を送信する関数 (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 損切価格。
|
||||
* @param volume 注文ロット。
|
||||
* @param comment_suffix 分割注文識別用のコメント接尾辞。空なら従来コメント。
|
||||
* @return 注文が正常に受理された場合はtrue、それ以外はfalse。
|
||||
*/
|
||||
bool SendOrder(int orderType, double price, double tp, double sl, double volume, string comment_suffix)
|
||||
{
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult result = {};
|
||||
|
||||
request.magic = magic_number;
|
||||
request.symbol = _Symbol;
|
||||
request.volume = volume;
|
||||
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, comment_suffix);
|
||||
|
||||
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 注文タイプ番号。
|
||||
* @param comment_suffix 分割注文識別用のコメント接尾辞。
|
||||
* @return 注文タイプとPCローカル時刻、または分割注文識別子を含むコメント文字列。
|
||||
*/
|
||||
string RequestComment(int orderType, string comment_suffix)
|
||||
{
|
||||
if(comment_suffix != "")
|
||||
return "T" + IntegerToString(orderType) + " " + comment_suffix;
|
||||
|
||||
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..5、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_TECHNICAL_ERROR_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 input指定の上限を安全な範囲に丸めて返します。
|
||||
*/
|
||||
int EffectivePositionLimit()
|
||||
{
|
||||
int limit = input_position_limit;
|
||||
if(limit < 1)
|
||||
limit = 1;
|
||||
if(limit > POSITION_LIMIT)
|
||||
limit = POSITION_LIMIT;
|
||||
|
||||
return limit;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 分割注文コメントか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsSplitOrderComment(const string comment)
|
||||
{
|
||||
return (StringFind(comment, " Z") >= 0 && StringFind(comment, "#") >= 0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 指定候補IDの分割注文コメントか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsCurrentSplitCandidateComment(const string comment, const string candidate_id)
|
||||
{
|
||||
if(candidate_id == "" || candidate_id == "0")
|
||||
return false;
|
||||
|
||||
return (StringFind(comment, "Z" + candidate_id + "#") >= 0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 分割slotが既存注文/ポジションに存在するか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
bool HasExistingSplitSlot(const string slot_key)
|
||||
{
|
||||
if(slot_key == "")
|
||||
return false;
|
||||
|
||||
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;
|
||||
if(StringFind(OrderGetString(ORDER_COMMENT), slot_key) >= 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
if(StringFind(PositionGetString(POSITION_COMMENT), slot_key) >= 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 古い分割pending注文を取消する関数
|
||||
//+------------------------------------------------------------------+
|
||||
bool CancelStaleSplitPendingOrders(const string current_candidate_id)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if(current_candidate_id == "" || current_candidate_id == "0")
|
||||
return false;
|
||||
|
||||
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;
|
||||
|
||||
string comment = OrderGetString(ORDER_COMMENT);
|
||||
if(!IsSplitOrderComment(comment))
|
||||
continue;
|
||||
if(IsCurrentSplitCandidateComment(comment, current_candidate_id))
|
||||
continue;
|
||||
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult trade_result = {};
|
||||
request.action = TRADE_ACTION_REMOVE;
|
||||
request.order = ticket;
|
||||
|
||||
if(!OrderSend(request, trade_result))
|
||||
{
|
||||
Print("Failed to delete stale split order. Ticket: ", ticket, " Error: ", GetLastError());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(trade_result.retcode == TRADE_RETCODE_DONE)
|
||||
{
|
||||
Print("Stale split order canceled. Ticket: ", ticket, " comment=", comment);
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Stale split order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 時間が経過したエントリー注文をキャンセルする関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,427 @@
|
||||
#ifndef HIT_TRADE_MANAGER_MQH
|
||||
#define HIT_TRADE_MANAGER_MQH
|
||||
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| エントリー注文を送信する関数 (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..5、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_TECHNICAL_ERROR_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;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,760 @@
|
||||
#ifndef SLTP_MANAGER_MQH
|
||||
#define SLTP_MANAGER_MQH
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
/// @brief 同一シンボル・同一マジックナンバーの建玉に対して、SLを段階的に引き上げる管理クラス。
|
||||
/// @details 建値移動、アクティブトレーリング、TP進捗率ベースのSL固定をまとめて評価し、
|
||||
/// 現在のSLより有利な候補だけを `CTrade::PositionModify` で反映する。
|
||||
class CSLTPManager
|
||||
{
|
||||
private:
|
||||
ulong m_magic;
|
||||
string m_symbol;
|
||||
ulong m_deviation_points;
|
||||
CTrade m_trade;
|
||||
|
||||
bool m_use_breakeven;
|
||||
double m_breakeven_trigger_pips;
|
||||
double m_breakeven_buffer_pips;
|
||||
|
||||
bool m_use_elapsed_breakeven;
|
||||
double m_elapsed_breakeven_hours;
|
||||
double m_elapsed_breakeven_buffer_pips;
|
||||
|
||||
bool m_use_active_trailing;
|
||||
double m_active_breakeven_pips;
|
||||
double m_active_stop_loss_offset_pips;
|
||||
double m_active_step_trigger_pips;
|
||||
double m_active_step_move_pips;
|
||||
|
||||
bool m_use_tp_progress_stop;
|
||||
double m_tp_progress_trigger_percent;
|
||||
double m_tp_progress_sl_lock_percent;
|
||||
|
||||
bool m_use_high_volatility_limit;
|
||||
|
||||
public:
|
||||
/// @brief SL/TP管理クラスの初期状態を構築する。
|
||||
/// @details デフォルトでは現在チャートのシンボルを対象にし、すべてのSL移動ロジックを無効化する。
|
||||
CSLTPManager()
|
||||
{
|
||||
m_magic = 0;
|
||||
m_symbol = _Symbol;
|
||||
m_deviation_points = 10;
|
||||
|
||||
m_use_breakeven = false;
|
||||
m_breakeven_trigger_pips = 0.0;
|
||||
m_breakeven_buffer_pips = 0.0;
|
||||
|
||||
m_use_elapsed_breakeven = false;
|
||||
m_elapsed_breakeven_hours = 0.0;
|
||||
m_elapsed_breakeven_buffer_pips = 0.0;
|
||||
|
||||
m_use_active_trailing = false;
|
||||
m_active_breakeven_pips = 0.0;
|
||||
m_active_stop_loss_offset_pips = 0.0;
|
||||
m_active_step_trigger_pips = 0.0;
|
||||
m_active_step_move_pips = 0.0;
|
||||
|
||||
m_use_tp_progress_stop = false;
|
||||
m_tp_progress_trigger_percent = 0.0;
|
||||
m_tp_progress_sl_lock_percent = 0.0;
|
||||
|
||||
m_use_high_volatility_limit = false;
|
||||
}
|
||||
|
||||
/// @brief 管理対象ポジションのマジックナンバーを設定する。
|
||||
/// @param magic 対象EAに割り当てられたマジックナンバー。
|
||||
/// @details `CTrade` にも同じマジックナンバーを渡し、注文変更ログと実行元を揃える。
|
||||
void SetMagicNumber(const ulong magic)
|
||||
{
|
||||
m_magic = magic;
|
||||
m_trade.SetExpertMagicNumber(m_magic);
|
||||
}
|
||||
|
||||
/// @brief 管理対象のシンボルを設定する。
|
||||
/// @param symbol `_Symbol` または任意の取引シンボル名。
|
||||
void SetSymbol(const string symbol)
|
||||
{
|
||||
m_symbol = symbol;
|
||||
}
|
||||
|
||||
/// @brief SL変更注文に使用する許容スリッページをポイント単位で設定する。
|
||||
/// @param deviation_points `CTrade::SetDeviationInPoints` に渡すポイント数。
|
||||
void SetDeviationInPoints(const ulong deviation_points)
|
||||
{
|
||||
m_deviation_points = deviation_points;
|
||||
m_trade.SetDeviationInPoints((int)m_deviation_points);
|
||||
}
|
||||
|
||||
/// @brief 建値移動ロジックの有効化とバッファー幅を設定する。
|
||||
/// @param use_breakeven true の場合、利益が閾値を超えた時にSLを建値付近へ移動する。
|
||||
/// @param breakeven_trigger_pips 建値移動を開始する含み益のpips。
|
||||
/// @param breakeven_buffer_pips 建値から利益方向へずらして固定するバッファーpips。
|
||||
void SetBreakevenSettings(const bool use_breakeven,
|
||||
const double breakeven_trigger_pips,
|
||||
const double breakeven_buffer_pips)
|
||||
{
|
||||
m_use_breakeven = use_breakeven;
|
||||
m_breakeven_trigger_pips = breakeven_trigger_pips;
|
||||
m_breakeven_buffer_pips = breakeven_buffer_pips;
|
||||
}
|
||||
|
||||
/// @brief 保有時間ベースの建値移動ロジックを設定する。
|
||||
/// @param use_elapsed_breakeven true の場合、保有時間と建値超過を条件にSLを建値付近へ移動する。
|
||||
/// @param elapsed_breakeven_hours 建値移動を許可するまでの保有時間。小数指定も可能。
|
||||
/// @param elapsed_breakeven_buffer_pips 建値から利益方向へずらして固定するバッファーpips。
|
||||
void SetElapsedBreakevenSettings(const bool use_elapsed_breakeven,
|
||||
const double elapsed_breakeven_hours,
|
||||
const double elapsed_breakeven_buffer_pips)
|
||||
{
|
||||
m_use_elapsed_breakeven = use_elapsed_breakeven;
|
||||
m_elapsed_breakeven_hours = elapsed_breakeven_hours;
|
||||
m_elapsed_breakeven_buffer_pips = elapsed_breakeven_buffer_pips;
|
||||
}
|
||||
|
||||
/// @brief アクティブトレーリングロジックの有効化と段階移動幅を設定する。
|
||||
/// @param use_active_trailing true の場合、利益進捗に応じてSLを段階的に更新する。
|
||||
/// @param active_breakeven_pips トレーリングを開始する含み益のpips。
|
||||
/// @param active_stop_loss_offset_pips 開始時点で建値から固定する初期pips。
|
||||
/// @param active_step_trigger_pips SLを次段階へ進めるために必要な追加利益pips。
|
||||
/// @param active_step_move_pips 1段階ごとにSLを利益方向へ動かすpips。
|
||||
void SetActiveTrailingSettings(const bool use_active_trailing,
|
||||
const double active_breakeven_pips,
|
||||
const double active_stop_loss_offset_pips,
|
||||
const double active_step_trigger_pips,
|
||||
const double active_step_move_pips)
|
||||
{
|
||||
m_use_active_trailing = use_active_trailing;
|
||||
m_active_breakeven_pips = active_breakeven_pips;
|
||||
m_active_stop_loss_offset_pips = active_stop_loss_offset_pips;
|
||||
m_active_step_trigger_pips = active_step_trigger_pips;
|
||||
m_active_step_move_pips = active_step_move_pips;
|
||||
}
|
||||
|
||||
/// @brief TP到達進捗率を基準にSLを固定するロジックを設定する。
|
||||
/// @param use_tp_progress_stop true の場合、現在価格がTP距離の一定割合へ到達した時にSLを固定する。
|
||||
/// @param tp_progress_trigger_percent SL固定を開始するTP進捗率。範囲は `(0, 100]`。
|
||||
/// @param tp_progress_sl_lock_percent TP距離のうちSLで固定する割合。範囲は `[0, 100]`。
|
||||
void SetTpProgressStopSettings(const bool use_tp_progress_stop,
|
||||
const double tp_progress_trigger_percent,
|
||||
const double tp_progress_sl_lock_percent)
|
||||
{
|
||||
m_use_tp_progress_stop = use_tp_progress_stop;
|
||||
m_tp_progress_trigger_percent = tp_progress_trigger_percent;
|
||||
m_tp_progress_sl_lock_percent = tp_progress_sl_lock_percent;
|
||||
}
|
||||
|
||||
/// @brief 高ボラティリティ時のSL引き締めロジックを有効化/無効化する。
|
||||
/// @param use_high_volatility_limit true の場合、HighVolatilityLimit() 呼び出し時に急変幅ベースでSLを更新する。
|
||||
void SetHighVolatilityLimitSettings(const bool use_high_volatility_limit)
|
||||
{
|
||||
m_use_high_volatility_limit = use_high_volatility_limit;
|
||||
}
|
||||
|
||||
/// @brief 設定値とシンボル状態が実行可能か検証する。
|
||||
/// @return すべての設定が有効で、シンボル選択とpips換算が可能な場合は true。
|
||||
/// @details 建値移動とアクティブトレーリングの同時有効化など、矛盾する設定を事前に弾く。
|
||||
bool ValidateSettings()
|
||||
{
|
||||
if (m_symbol == "")
|
||||
{
|
||||
Print("SLTPManager settings error: symbol is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SymbolSelect(m_symbol, true))
|
||||
{
|
||||
Print("SLTPManager settings error: failed to select symbol=", m_symbol,
|
||||
" err=", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_use_breakeven && m_use_active_trailing)
|
||||
{
|
||||
Print("SLTPManager settings error: use_breakeven and use_active_trailing cannot both be true.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_use_breakeven)
|
||||
{
|
||||
if (m_breakeven_trigger_pips < 0.0 || m_breakeven_buffer_pips < 0.0)
|
||||
{
|
||||
Print("SLTPManager settings error: breakeven trigger and buffer pips must be non-negative.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_use_elapsed_breakeven)
|
||||
{
|
||||
if (m_elapsed_breakeven_hours < 0.0 || m_elapsed_breakeven_buffer_pips < 0.0)
|
||||
{
|
||||
Print("SLTPManager settings error: elapsed breakeven hours and buffer pips must be non-negative.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_use_active_trailing)
|
||||
{
|
||||
if (m_active_breakeven_pips < 0.0 ||
|
||||
m_active_stop_loss_offset_pips < 0.0 ||
|
||||
m_active_step_trigger_pips <= 0.0 ||
|
||||
m_active_step_move_pips <= 0.0)
|
||||
{
|
||||
Print("SLTPManager settings error: active trailing requires non-negative breakeven/offset and positive step values.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_use_tp_progress_stop)
|
||||
{
|
||||
if (m_tp_progress_trigger_percent <= 0.0 ||
|
||||
m_tp_progress_trigger_percent > 100.0 ||
|
||||
m_tp_progress_sl_lock_percent < 0.0 ||
|
||||
m_tp_progress_sl_lock_percent > 100.0)
|
||||
{
|
||||
Print("SLTPManager settings error: TP progress percentages must be trigger=(0,100], lock=[0,100].");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (PipSize() <= 0.0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 現在保有中の対象ポジションを走査し、必要に応じてSLを更新する。
|
||||
/// @details 対象は `m_magic` と `m_symbol` が一致するポジションのみ。複数のSL候補がある場合、
|
||||
/// BUYでは最も高いSL、SELLでは最も低いSLを採用し、既存SLより不利な変更は行わない。
|
||||
void ManagePositions()
|
||||
{
|
||||
const double pip_size = PipSize();
|
||||
if (pip_size <= 0.0)
|
||||
return;
|
||||
|
||||
MqlTick tick;
|
||||
if (!SymbolInfoTick(m_symbol, tick))
|
||||
{
|
||||
Print("SLTPManager tick read failed: symbol=", m_symbol, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
const int digits = (int)SymbolInfoInteger(m_symbol, SYMBOL_DIGITS);
|
||||
const datetime current_time = tick.time;
|
||||
const int pos_total = PositionsTotal();
|
||||
|
||||
for (int i = pos_total - 1; i >= 0; --i)
|
||||
{
|
||||
const ulong ticket = PositionGetTicket(i);
|
||||
if (!PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if (!IsTargetPosition())
|
||||
continue;
|
||||
|
||||
const ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
if (type != POSITION_TYPE_BUY && type != POSITION_TYPE_SELL)
|
||||
continue;
|
||||
|
||||
const double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const double current_sl = PositionGetDouble(POSITION_SL);
|
||||
const double current_tp = PositionGetDouble(POSITION_TP);
|
||||
const double market_price = (type == POSITION_TYPE_BUY) ? tick.bid : tick.ask;
|
||||
const double profit_pips = GetProfitPips(type, open_price, market_price, pip_size);
|
||||
const datetime open_time = (datetime)PositionGetInteger(POSITION_TIME);
|
||||
|
||||
bool has_candidate = false;
|
||||
double candidate_sl = 0.0;
|
||||
double sl_value = 0.0;
|
||||
|
||||
if (CalcBreakevenSL(type, open_price, profit_pips, pip_size, digits, sl_value))
|
||||
UpdateBestCandidate(type, sl_value, has_candidate, candidate_sl);
|
||||
|
||||
if (CalcElapsedBreakevenSL(type, open_price, profit_pips, open_time, current_time, pip_size, digits, sl_value))
|
||||
UpdateBestCandidate(type, sl_value, has_candidate, candidate_sl);
|
||||
|
||||
if (CalcActiveTrailingSL(type, open_price, profit_pips, pip_size, digits, sl_value))
|
||||
UpdateBestCandidate(type, sl_value, has_candidate, candidate_sl);
|
||||
|
||||
if (CalcTpProgressSL(type, open_price, current_tp, market_price, digits, sl_value))
|
||||
UpdateBestCandidate(type, sl_value, has_candidate, candidate_sl);
|
||||
|
||||
if (!has_candidate)
|
||||
continue;
|
||||
|
||||
candidate_sl = NormalizeDouble(candidate_sl, digits);
|
||||
|
||||
if (!IsBetterStopLoss(type, candidate_sl, current_sl))
|
||||
continue;
|
||||
|
||||
if (!IsStopDistanceAllowed(type, market_price, candidate_sl))
|
||||
continue;
|
||||
|
||||
ModifyStopLoss(ticket, candidate_sl, current_tp);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief 短時間で大きな価格変動が発生した場合にSLを引き締める。
|
||||
/// @details M1/M3/M5/M10/M15 の始値から現在Bidまでの変動幅を見て、
|
||||
/// しきい値を超えた場合だけ利益方向へSL候補を作る。ManagePositions() とは独立して呼び出す。
|
||||
void HighVolatilityLimit()
|
||||
{
|
||||
if (!m_use_high_volatility_limit)
|
||||
return;
|
||||
|
||||
const double pip_size = PipSize();
|
||||
if (pip_size <= 0.0)
|
||||
return;
|
||||
|
||||
MqlTick tick;
|
||||
if (!SymbolInfoTick(m_symbol, tick))
|
||||
{
|
||||
Print("SLTPManager tick read failed: symbol=", m_symbol, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
const int digits = (int)SymbolInfoInteger(m_symbol, SYMBOL_DIGITS);
|
||||
const double open_m1 = iOpen(m_symbol, PERIOD_M1, 0);
|
||||
const double open_m3 = iOpen(m_symbol, PERIOD_M3, 0);
|
||||
const double open_m5 = iOpen(m_symbol, PERIOD_M5, 0);
|
||||
const double open_m10 = iOpen(m_symbol, PERIOD_M10, 0);
|
||||
const double open_m15 = iOpen(m_symbol, PERIOD_M15, 0);
|
||||
const int pos_total = PositionsTotal();
|
||||
|
||||
for (int i = pos_total - 1; i >= 0; --i)
|
||||
{
|
||||
const ulong ticket = PositionGetTicket(i);
|
||||
if (!PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if (!IsTargetPosition())
|
||||
continue;
|
||||
|
||||
const ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
if (type != POSITION_TYPE_BUY && type != POSITION_TYPE_SELL)
|
||||
continue;
|
||||
|
||||
double candidate_sl = 0.0;
|
||||
if (!CalcHighVolatilitySL(type,
|
||||
tick.bid,
|
||||
open_m1,
|
||||
open_m3,
|
||||
open_m5,
|
||||
open_m10,
|
||||
open_m15,
|
||||
pip_size,
|
||||
digits,
|
||||
candidate_sl))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const double current_sl = PositionGetDouble(POSITION_SL);
|
||||
const double current_tp = PositionGetDouble(POSITION_TP);
|
||||
const double market_price = (type == POSITION_TYPE_BUY) ? tick.bid : tick.ask;
|
||||
|
||||
if (!IsBetterStopLoss(type, candidate_sl, current_sl))
|
||||
continue;
|
||||
|
||||
if (!IsStopDistanceAllowed(type, market_price, candidate_sl))
|
||||
continue;
|
||||
|
||||
ModifyStopLoss(ticket, candidate_sl, current_tp);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief 現在選択中のポジションが管理対象か判定する。
|
||||
/// @return マジックナンバーとシンボルが一致する場合は true。
|
||||
bool IsTargetPosition()
|
||||
{
|
||||
if ((ulong)PositionGetInteger(POSITION_MAGIC) != m_magic)
|
||||
return false;
|
||||
if (PositionGetString(POSITION_SYMBOL) != m_symbol)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 対象シンボルの1 pip相当の価格幅を取得する。
|
||||
/// @return 1 pip の価格幅。取得できない場合は 0.0。
|
||||
/// @details GOLD/XAUUSD は 0.1、2/3桁は0.01、4/5桁は0.0001を1 pipとして扱う。
|
||||
double PipSize()
|
||||
{
|
||||
if (m_symbol == "XAUUSD" || m_symbol == "GOLD")
|
||||
return 0.1;
|
||||
|
||||
const int digits = (int)SymbolInfoInteger(m_symbol, SYMBOL_DIGITS);
|
||||
|
||||
switch (digits)
|
||||
{
|
||||
case 2:
|
||||
case 3:
|
||||
return 0.01;
|
||||
case 4:
|
||||
case 5:
|
||||
return 0.0001;
|
||||
}
|
||||
|
||||
Print("SLTPManager pip conversion error: unsupported symbol digits. symbol=", m_symbol,
|
||||
" digits=", digits);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/// @brief 現在価格から建値に対する含み益をpipsで計算する。
|
||||
/// @param type ポジション種別。BUY/SELLで計算方向を切り替える。
|
||||
/// @param open_price 建値。
|
||||
/// @param market_price BUYはBid、SELLはAskを想定した現在価格。
|
||||
/// @param pip_size `PipSize()` で取得した1 pipの価格幅。
|
||||
/// @return 利益方向なら正、損失方向なら負のpips。
|
||||
double GetProfitPips(const ENUM_POSITION_TYPE type,
|
||||
const double open_price,
|
||||
const double market_price,
|
||||
const double pip_size)
|
||||
{
|
||||
if (pip_size <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
return (market_price - open_price) / pip_size;
|
||||
|
||||
return (open_price - market_price) / pip_size;
|
||||
}
|
||||
|
||||
/// @brief 建値移動ロジックに基づくSL候補を計算する。
|
||||
/// @param type ポジション種別。
|
||||
/// @param open_price 建値。
|
||||
/// @param profit_pips 現在の含み益pips。
|
||||
/// @param pip_size 1 pipの価格幅。
|
||||
/// @param digits 対象シンボルの小数桁数。
|
||||
/// @param sl_value 計算に成功したSL候補の出力先。
|
||||
/// @return 建値移動条件を満たし、SL候補を出力した場合は true。
|
||||
/// @details BUYでは建値 + バッファー、SELLでは建値 - バッファーへSLを置き、
|
||||
/// スプレッドや手数料分を考慮した利益保護ラインとして扱う。
|
||||
bool CalcBreakevenSL(const ENUM_POSITION_TYPE type,
|
||||
const double open_price,
|
||||
const double profit_pips,
|
||||
const double pip_size,
|
||||
const int digits,
|
||||
double &sl_value)
|
||||
{
|
||||
if (!m_use_breakeven)
|
||||
return false;
|
||||
if (profit_pips < m_breakeven_trigger_pips)
|
||||
return false;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
sl_value = open_price + m_breakeven_buffer_pips * pip_size;
|
||||
else
|
||||
sl_value = open_price - m_breakeven_buffer_pips * pip_size;
|
||||
|
||||
sl_value = NormalizeDouble(sl_value, digits);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 保有時間ベースの建値移動ロジックに基づくSL候補を計算する。
|
||||
/// @param type ポジション種別。
|
||||
/// @param open_price 建値。
|
||||
/// @param profit_pips 現在の含み益pips。現在レートが建値を超えているかの判定に使う。
|
||||
/// @param open_time ポジションの約定時刻。
|
||||
/// @param current_time 評価時点のサーバー時刻。
|
||||
/// @param pip_size 1 pipの価格幅。
|
||||
/// @param digits 対象シンボルの小数桁数。
|
||||
/// @param sl_value 計算に成功したSL候補の出力先。
|
||||
/// @return 保有時間と建値超過条件を満たし、SL候補を出力した場合は true。
|
||||
/// @details 通常ブレークイーブンとは独立した候補として評価し、長時間保有ポジションの損失化を防ぐ。
|
||||
bool CalcElapsedBreakevenSL(const ENUM_POSITION_TYPE type,
|
||||
const double open_price,
|
||||
const double profit_pips,
|
||||
const datetime open_time,
|
||||
const datetime current_time,
|
||||
const double pip_size,
|
||||
const int digits,
|
||||
double &sl_value)
|
||||
{
|
||||
if (!m_use_elapsed_breakeven)
|
||||
return false;
|
||||
if (open_time <= 0 || current_time <= open_time)
|
||||
return false;
|
||||
if (profit_pips <= 0.0)
|
||||
return false;
|
||||
|
||||
const double required_seconds = m_elapsed_breakeven_hours * 3600.0;
|
||||
const double held_seconds = (double)(current_time - open_time);
|
||||
if (held_seconds < required_seconds)
|
||||
return false;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
sl_value = open_price + m_elapsed_breakeven_buffer_pips * pip_size;
|
||||
else
|
||||
sl_value = open_price - m_elapsed_breakeven_buffer_pips * pip_size;
|
||||
|
||||
sl_value = NormalizeDouble(sl_value, digits);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief アクティブトレーリングロジックに基づくSL候補を計算する。
|
||||
/// @param type ポジション種別。
|
||||
/// @param open_price 建値。
|
||||
/// @param profit_pips 現在の含み益pips。
|
||||
/// @param pip_size 1 pipの価格幅。
|
||||
/// @param digits 対象シンボルの小数桁数。
|
||||
/// @param sl_value 計算に成功したSL候補の出力先。
|
||||
/// @return トレーリング開始条件を満たし、SL候補を出力した場合は true。
|
||||
/// @details 開始利益からの超過分をステップ数へ変換し、固定済み利益幅を段階的に増やす。
|
||||
bool CalcActiveTrailingSL(const ENUM_POSITION_TYPE type,
|
||||
const double open_price,
|
||||
const double profit_pips,
|
||||
const double pip_size,
|
||||
const int digits,
|
||||
double &sl_value)
|
||||
{
|
||||
if (!m_use_active_trailing)
|
||||
return false;
|
||||
if (profit_pips < m_active_breakeven_pips)
|
||||
return false;
|
||||
|
||||
int trail_steps = (int)MathFloor((profit_pips - m_active_breakeven_pips) / m_active_step_trigger_pips);
|
||||
if (trail_steps < 0)
|
||||
trail_steps = 0;
|
||||
|
||||
const double locked_pips = m_active_stop_loss_offset_pips + trail_steps * m_active_step_move_pips;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
sl_value = open_price + locked_pips * pip_size;
|
||||
else
|
||||
sl_value = open_price - locked_pips * pip_size;
|
||||
|
||||
sl_value = NormalizeDouble(sl_value, digits);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief TP進捗率ベースのSL候補を計算する。
|
||||
/// @param type ポジション種別。
|
||||
/// @param open_price 建値。
|
||||
/// @param current_tp 現在設定されているTP価格。未設定の場合は 0.0。
|
||||
/// @param market_price BUYはBid、SELLはAskを想定した現在価格。
|
||||
/// @param digits 対象シンボルの小数桁数。
|
||||
/// @param sl_value 計算に成功したSL候補の出力先。
|
||||
/// @return TP方向と進捗率が有効で、SL候補を出力した場合は true。
|
||||
bool CalcTpProgressSL(const ENUM_POSITION_TYPE type,
|
||||
const double open_price,
|
||||
const double current_tp,
|
||||
const double market_price,
|
||||
const int digits,
|
||||
double &sl_value)
|
||||
{
|
||||
if (!m_use_tp_progress_stop)
|
||||
return false;
|
||||
if (current_tp == 0.0)
|
||||
return false;
|
||||
|
||||
double tp_distance = 0.0;
|
||||
double progressed_distance = 0.0;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
{
|
||||
if (current_tp <= open_price)
|
||||
return false;
|
||||
|
||||
tp_distance = current_tp - open_price;
|
||||
progressed_distance = market_price - open_price;
|
||||
sl_value = open_price + tp_distance * (m_tp_progress_sl_lock_percent / 100.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (current_tp >= open_price)
|
||||
return false;
|
||||
|
||||
tp_distance = open_price - current_tp;
|
||||
progressed_distance = open_price - market_price;
|
||||
sl_value = open_price - tp_distance * (m_tp_progress_sl_lock_percent / 100.0);
|
||||
}
|
||||
|
||||
if (tp_distance <= 0.0)
|
||||
return false;
|
||||
|
||||
const double progress_percent = (progressed_distance / tp_distance) * 100.0;
|
||||
if (progress_percent < m_tp_progress_trigger_percent)
|
||||
return false;
|
||||
|
||||
sl_value = NormalizeDouble(sl_value, digits);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 高ボラティリティ判定に基づくSL候補を計算する。
|
||||
/// @return いずれかの時間足でしきい値を超え、SL候補を出力した場合は true。
|
||||
bool CalcHighVolatilitySL(const ENUM_POSITION_TYPE type,
|
||||
const double current_bid,
|
||||
const double open_m1,
|
||||
const double open_m3,
|
||||
const double open_m5,
|
||||
const double open_m10,
|
||||
const double open_m15,
|
||||
const double pip_size,
|
||||
const int digits,
|
||||
double &sl_value)
|
||||
{
|
||||
bool has_candidate = false;
|
||||
double candidate_sl = 0.0;
|
||||
const double rate_ratio = 0.9;
|
||||
|
||||
EvaluateHighVolatilityOpen(type, current_bid, open_m1, 100.0, rate_ratio, pip_size, digits, has_candidate, candidate_sl);
|
||||
EvaluateHighVolatilityOpen(type, current_bid, open_m3, 150.0, rate_ratio, pip_size, digits, has_candidate, candidate_sl);
|
||||
EvaluateHighVolatilityOpen(type, current_bid, open_m5, 200.0, rate_ratio, pip_size, digits, has_candidate, candidate_sl);
|
||||
EvaluateHighVolatilityOpen(type, current_bid, open_m10, 300.0, rate_ratio, pip_size, digits, has_candidate, candidate_sl);
|
||||
EvaluateHighVolatilityOpen(type, current_bid, open_m15, 400.0, rate_ratio, pip_size, digits, has_candidate, candidate_sl);
|
||||
|
||||
if (!has_candidate)
|
||||
return false;
|
||||
|
||||
sl_value = NormalizeDouble(candidate_sl, digits);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 1本の時間足始値から急変幅を評価し、有効なSL候補なら最良候補へ反映する。
|
||||
void EvaluateHighVolatilityOpen(const ENUM_POSITION_TYPE type,
|
||||
const double current_bid,
|
||||
const double open_price,
|
||||
const double limit_pips,
|
||||
const double rate_ratio,
|
||||
const double pip_size,
|
||||
const int digits,
|
||||
bool &has_candidate,
|
||||
double &candidate_sl)
|
||||
{
|
||||
if (open_price <= 0.0 || pip_size <= 0.0)
|
||||
return;
|
||||
|
||||
double movement_pips = 0.0;
|
||||
double sl_value = 0.0;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
{
|
||||
movement_pips = (current_bid - open_price) / pip_size;
|
||||
sl_value = open_price + limit_pips * rate_ratio * pip_size;
|
||||
}
|
||||
else if (type == POSITION_TYPE_SELL)
|
||||
{
|
||||
movement_pips = (open_price - current_bid) / pip_size;
|
||||
sl_value = open_price - limit_pips * rate_ratio * pip_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (movement_pips < limit_pips)
|
||||
return;
|
||||
|
||||
sl_value = NormalizeDouble(sl_value, digits);
|
||||
UpdateBestCandidate(type, sl_value, has_candidate, candidate_sl);
|
||||
}
|
||||
|
||||
/// @brief 複数ロジックから得たSL候補のうち、最も有利な値を保持する。
|
||||
/// @param type ポジション種別。BUYは高いSL、SELLは低いSLを優先する。
|
||||
/// @param sl_value 新しく評価するSL候補。
|
||||
/// @param has_candidate 既に候補が存在するかを示す入出力フラグ。
|
||||
/// @param candidate_sl 現在の最良SL候補の入出力値。
|
||||
void UpdateBestCandidate(const ENUM_POSITION_TYPE type,
|
||||
const double sl_value,
|
||||
bool &has_candidate,
|
||||
double &candidate_sl)
|
||||
{
|
||||
if (!has_candidate)
|
||||
{
|
||||
candidate_sl = sl_value;
|
||||
has_candidate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
candidate_sl = MathMax(candidate_sl, sl_value);
|
||||
else
|
||||
candidate_sl = MathMin(candidate_sl, sl_value);
|
||||
}
|
||||
|
||||
/// @brief 新しいSLが既存SLより利益保護方向へ改善しているか判定する。
|
||||
/// @param type ポジション種別。
|
||||
/// @param new_sl 新しく設定したいSL価格。
|
||||
/// @param current_sl 現在のSL価格。未設定の場合は 0.0。
|
||||
/// @return 未設定または利益保護方向へ十分に改善している場合は true。
|
||||
/// @details 小数誤差による微小な再変更を避けるため、pointの10%を許容差として使う。
|
||||
bool IsBetterStopLoss(const ENUM_POSITION_TYPE type,
|
||||
const double new_sl,
|
||||
const double current_sl)
|
||||
{
|
||||
const double point = SymbolInfoDouble(m_symbol, SYMBOL_POINT);
|
||||
const double tolerance = point * 0.1;
|
||||
|
||||
if (current_sl == 0.0)
|
||||
return true;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
return (new_sl > current_sl + tolerance);
|
||||
|
||||
return (new_sl < current_sl - tolerance);
|
||||
}
|
||||
|
||||
/// @brief ブローカーのストップレベルとフリーズレベルに対してSL変更が可能か判定する。
|
||||
/// @param type ポジション種別。
|
||||
/// @param market_price BUYはBid、SELLはAskを想定した現在価格。
|
||||
/// @param new_sl 新しく設定したいSL価格。
|
||||
/// @return 現在価格から必要距離を確保できている場合は true。
|
||||
bool IsStopDistanceAllowed(const ENUM_POSITION_TYPE type,
|
||||
const double market_price,
|
||||
const double new_sl)
|
||||
{
|
||||
const double point = SymbolInfoDouble(m_symbol, SYMBOL_POINT);
|
||||
const int stops_level = (int)SymbolInfoInteger(m_symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
const int freeze_level = (int)SymbolInfoInteger(m_symbol, SYMBOL_TRADE_FREEZE_LEVEL);
|
||||
const double stop_gap = stops_level * point;
|
||||
const double freeze_gap = freeze_level * point;
|
||||
|
||||
if (point <= 0.0)
|
||||
return false;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
{
|
||||
if (new_sl > market_price - stop_gap)
|
||||
return false;
|
||||
if (freeze_level > 0 && new_sl > market_price - freeze_gap)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (new_sl < market_price + stop_gap)
|
||||
return false;
|
||||
if (freeze_level > 0 && new_sl < market_price + freeze_gap)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 対象チケットのSLを更新し、TPは現在値を維持する。
|
||||
/// @param ticket 変更対象ポジションのチケット番号。
|
||||
/// @param new_sl 新しいSL価格。
|
||||
/// @param current_tp 変更前のTP価格。
|
||||
/// @return `CTrade::PositionModify` が成功した場合は true。
|
||||
/// @details 失敗時はretcode、GetLastError、設定しようとしたSLをログ出力する。
|
||||
bool ModifyStopLoss(const ulong ticket,
|
||||
const double new_sl,
|
||||
const double current_tp)
|
||||
{
|
||||
ResetLastError();
|
||||
if (!m_trade.PositionModify(ticket, new_sl, current_tp))
|
||||
{
|
||||
Print("SLTPManager SL modify failed: ticket=", ticket,
|
||||
" symbol=", m_symbol,
|
||||
" magic=", m_magic,
|
||||
" retcode=", m_trade.ResultRetcode(),
|
||||
" err=", GetLastError(),
|
||||
" new_sl=", DoubleToString(new_sl, (int)SymbolInfoInteger(m_symbol, SYMBOL_DIGITS)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user