Add file-prefix isolation and strict split-order parsing

This commit is contained in:
Hiroaki86
2026-05-30 00:56:18 +09:00
parent 3d853fa67a
commit 265d378e07
13 changed files with 513 additions and 116 deletions
+7 -3
View File
@@ -308,7 +308,11 @@ string BatchWorkingDirectory(const string bat_file)
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool StartBatchProcess(const string bat_file, const string running_file, const string label, ExternalProcessState &process)
bool StartBatchProcess(const string bat_file,
const string running_file,
const string label,
ExternalProcessState &process,
const string file_prefix)
{
STARTUPINFO_W startup_info = {};
PROCESS_INFORMATION process_info = {};
@@ -316,7 +320,7 @@ bool StartBatchProcess(const string bat_file, const string running_file, const s
startup_info.cb = (uint)sizeof(startup_info);
string cmd_exe = "C:\\Windows\\System32\\cmd.exe";
string command_line = "\"" + cmd_exe + "\" /c \"\"" + bat_file + "\"\"";
string command_line = "\"" + cmd_exe + "\" /c \"\"" + bat_file + "\" \"" + file_prefix + "\"\"";
string current_directory = BatchWorkingDirectory(bat_file);
int created = CreateProcessW(
@@ -353,7 +357,7 @@ bool StartBatchProcess(const string bat_file, const string running_file, const s
CreateRunningFile(running_file, process.process_id);
Print("[", label, "] process started. pid=", process.process_id,
" file=", bat_file, " cwd=", current_directory);
" file=", bat_file, " cwd=", current_directory, " prefix=", file_prefix);
return true;
}
+8 -2
View File
@@ -671,7 +671,7 @@ bool IsM15EntryTimingConfirmed(const int orderType, TickContext &ctx, const doub
if(!use_m15_entry_filter)
return true;
if(IsTrendStopOrderType(orderType))
if(IsTrendStopOrderType(orderType) && !use_m15_imbalance_confirmation)
return true;
MqlRates rates[];
@@ -685,6 +685,9 @@ bool IsM15EntryTimingConfirmed(const int orderType, TickContext &ctx, const doub
int last_index = copied - 1;
int prev_index = copied - 2;
if(IsTrendStopOrderType(orderType))
return IsM15ImbalanceConfirmationPassed(orderType, rates, copied, last_index);
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;
@@ -719,7 +722,7 @@ bool IsM15ZoneTimingConfirmed(const int orderType,
if(!use_m15_entry_filter)
return true;
if(IsTrendStopOrderType(orderType))
if(IsTrendStopOrderType(orderType) && !use_m15_imbalance_confirmation)
return true;
MqlRates rates[];
@@ -733,6 +736,9 @@ bool IsM15ZoneTimingConfirmed(const int orderType,
int last_index = copied - 1;
int prev_index = copied - 2;
if(IsTrendStopOrderType(orderType))
return IsM15ImbalanceConfirmationPassed(orderType, rates, copied, last_index);
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;
+155 -20
View File
@@ -119,6 +119,101 @@ bool RecordOHLC(const string filename, const datetime &times[], const double &op
return true;
}
//+------------------------------------------------------------------+
//| 文字列の前後空白を除去する関数
//+------------------------------------------------------------------+
/**
* @brief Python連携ファイルから読んだ1行をパース前に正規化します。
*/
string TrimText(const string value)
{
string text = value;
StringTrimLeft(text);
StringTrimRight(text);
return text;
}
//+------------------------------------------------------------------+
//| 10進数字か判定する関数
//+------------------------------------------------------------------+
bool IsDecimalDigit(const int ch)
{
return (ch >= 48 && ch <= 57);
}
//+------------------------------------------------------------------+
//| 厳格な整数パース
//+------------------------------------------------------------------+
/**
* @brief 数字だけで構成された整数行を読み込みます。不正文字があればfalse。
*/
bool TryParseStrictInteger(const string value, int &parsed)
{
string text = TrimText(value);
int length = StringLen(text);
if(length <= 0)
return false;
for(int i = 0; i < length; i++)
{
if(!IsDecimalDigit(StringGetCharacter(text, i)))
return false;
}
parsed = (int)StringToInteger(text);
return true;
}
//+------------------------------------------------------------------+
//| 厳格な小数パース
//+------------------------------------------------------------------+
/**
* @brief 単純な10進数行を読み込みます。指数表記や余計な文字は許可しません。
*/
bool TryParseStrictDouble(const string value, double &parsed)
{
string text = TrimText(value);
int length = StringLen(text);
if(length <= 0)
return false;
bool has_digit = false;
bool has_dot = false;
int start_index = 0;
int first_char = StringGetCharacter(text, 0);
if(first_char == 43 || first_char == 45) // + or -
{
if(length == 1)
return false;
start_index = 1;
}
for(int i = start_index; i < length; i++)
{
int ch = StringGetCharacter(text, i);
if(IsDecimalDigit(ch))
{
has_digit = true;
continue;
}
if(ch == 46 && !has_dot) // dot
{
has_dot = true;
continue;
}
return false;
}
if(!has_digit)
return false;
parsed = StringToDouble(text);
return true;
}
//+------------------------------------------------------------------+
//| バッチファイル(Pythonスクリプト)を実行する関数
//+------------------------------------------------------------------+
@@ -135,7 +230,7 @@ bool ExecuteBatchTrend()
return false;
DeleteDoneFile(done_trend_file);
return StartBatchProcess(get_trend_reply_bat, running_trend_file, "trend", g_trend_process);
return StartBatchProcess(get_trend_reply_bat, running_trend_file, "trend", g_trend_process, mt5_file_prefix);
}
//+------------------------------------------------------------------+
@@ -154,7 +249,7 @@ bool ExecuteBatchEntry()
return false;
DeleteDoneFile(done_entry_file);
return StartBatchProcess(get_entry_reply_bat, running_entry_file, "entry", g_entry_process);
return StartBatchProcess(get_entry_reply_bat, running_entry_file, "entry", g_entry_process, mt5_file_prefix);
}
@@ -182,7 +277,7 @@ bool RecordOHLCAndExecuteBatch_Trend(EAState &state)
PERIOD_H4, HISTORY_BARS))
{ Print("GetLatestOHLC(H4) failed."); return false; }
if(!RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices))
if(!RecordOHLC(ohlc_h4_file, times, open_prices, high_prices, low_prices, close_prices))
{ Print("RecordOHLC(H4) failed."); return false; }
if(!ExecuteBatchTrend())
@@ -216,7 +311,7 @@ bool RecordOHLCAndExecuteBatch_Entry(EAState &state)
PERIOD_H1, HISTORY_BARS))
{ Print("GetLatestOHLC(H1) failed."); return false; }
if(!RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices))
if(!RecordOHLC(ohlc_h1_file, times, open_prices, high_prices, low_prices, close_prices))
{ Print("RecordOHLC(H1) failed."); return false; }
if(!ExecuteBatchEntry())
@@ -272,8 +367,9 @@ void LoadTrendState(int &trend_state)
if(filehandle != INVALID_HANDLE)
{
string line = FileReadString(filehandle);
int value = (int)StringToInteger(line);
if(value >= MARKET_LOW_VOL_RANGE && value <= MARKET_TECHNICAL_ERROR_STOP)
int value = MARKET_TECHNICAL_ERROR_STOP;
if(TryParseStrictInteger(line, value) &&
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).");
@@ -325,8 +421,13 @@ bool GetTargetPrices(EAState &state)
" | 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);
if(use_split_entry_zone && cancel_old_split_pending_on_new_zone)
{
if(state.zone_res_chk == 1)
CancelStaleSplitPendingOrders(state.zone_candidate_id);
else
CancelStaleSplitPendingOrders("");
}
g_ea.load_target_flg = false; // ←変更
g_ea.last_target_update = TimeLocal(); // ←変更
@@ -359,17 +460,29 @@ void LoadTargetPrices(double &target_prices[])
if(filehandle != INVALID_HANDLE)
{
int i = 0;
bool parse_failed = false;
while(!FileIsEnding(filehandle) && i < TARGET_SIZE)
{
string line = FileReadString(filehandle);
target_prices[i] = StringToDouble(line);
double parsed = 0.0;
if(!TryParseStrictDouble(line, parsed))
{
Print("target_prices.txt invalid numeric line. index=", i, " line=", line);
parse_failed = true;
break;
}
target_prices[i] = parsed;
i++;
}
FileClose(filehandle);
if(i < TARGET_SIZE)
if(parse_failed || i < TARGET_SIZE)
{
Print("target_prices.txt line count is short. loaded=", i, " required=", TARGET_SIZE);
if(!parse_failed)
Print("target_prices.txt line count is short. loaded=", i, " required=", TARGET_SIZE);
for(int j = 0; j < TARGET_SIZE; j++)
target_prices[j] = DEFAULT_TARGET_PRICE;
target_prices[0] = 0; // 不完全なファイルは無効扱い
}
}
@@ -411,14 +524,26 @@ bool ParseTargetZoneLine(const string line, EAState &state, const int digits)
if(count < 5)
return false;
int strategy = (int)StringToInteger(parts[0]);
int strategy = 0;
if(!TryParseStrictInteger(parts[0], strategy))
return false;
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);
double zone_low = 0.0;
double zone_high = 0.0;
double zone_tp = 0.0;
double zone_sl = 0.0;
if(!TryParseStrictDouble(parts[1], zone_low) ||
!TryParseStrictDouble(parts[2], zone_high) ||
!TryParseStrictDouble(parts[3], zone_tp) ||
!TryParseStrictDouble(parts[4], zone_sl))
return false;
state.zone_low[strategy] = NormalizeDouble(zone_low, digits);
state.zone_high[strategy] = NormalizeDouble(zone_high, digits);
state.zone_tp[strategy] = NormalizeDouble(zone_tp, digits);
state.zone_sl[strategy] = NormalizeDouble(zone_sl, digits);
return true;
}
@@ -456,10 +581,11 @@ void LoadTargetZones(EAState &state)
line_index++;
if(line_index == 1)
{
int schema_version = (int)StringToInteger(line);
if(schema_version != TARGET_ZONE_SCHEMA_VERSION)
int schema_version = 0;
if(!TryParseStrictInteger(line, schema_version) ||
schema_version != TARGET_ZONE_SCHEMA_VERSION)
{
Print("target_zones schema mismatch. loaded=", schema_version,
Print("target_zones schema mismatch. loaded=", line,
" required=", TARGET_ZONE_SCHEMA_VERSION);
FileClose(filehandle);
ResetTargetZones(state);
@@ -470,7 +596,16 @@ void LoadTargetZones(EAState &state)
if(line_index == 2)
{
state.zone_res_chk = (int)StringToInteger(line);
int zone_res = 0;
if(!TryParseStrictInteger(line, zone_res))
{
Print("target_zones invalid res_chk line: ", line);
FileClose(filehandle);
ResetTargetZones(state);
return;
}
state.zone_res_chk = zone_res;
continue;
}
+2 -4
View File
@@ -370,9 +370,7 @@ bool HasExistingSplitSlot(const string slot_key)
bool CancelStaleSplitPendingOrders(const string current_candidate_id)
{
bool result = false;
if(current_candidate_id == "" || current_candidate_id == "0")
return false;
bool keep_current_candidate = (current_candidate_id != "" && current_candidate_id != "0");
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
@@ -389,7 +387,7 @@ bool CancelStaleSplitPendingOrders(const string current_candidate_id)
string comment = OrderGetString(ORDER_COMMENT);
if(!IsSplitOrderComment(comment))
continue;
if(IsCurrentSplitCandidateComment(comment, current_candidate_id))
if(keep_current_candidate && IsCurrentSplitCandidateComment(comment, current_candidate_id))
continue;
MqlTradeRequest request = {};