Harden HIT entry and manager state handling

This commit is contained in:
Hiroaki86
2026-06-14 22:02:17 +09:00
parent 7b11d44d66
commit 52067c1973
10 changed files with 107 additions and 120 deletions
+24 -3
View File
@@ -134,7 +134,6 @@ ulong slippage = 10;
//--- 主要な定数・設定
#define POSITION_LIMIT 48
#define ENTRY_H1_LIMIT 2 // H1本数(=2時間)
#define CLOSE_H1_LIMIT 12 // H1本数(=12時間)
#define HISTORY_BARS 72
#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す
#define M15_CONFIRM_BARS 30
@@ -375,6 +374,17 @@ void ConfigurePythonGatewayPaths()
Print("Python app dir: ", python_app_dir);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
/**
* @brief The split entry/manager design requires ticket-based hedging positions.
*/
bool IsHedgingAccount()
{
return (AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
}
//+------------------------------------------------------------------+
//| 起動時の処理
//+------------------------------------------------------------------+
@@ -391,6 +401,13 @@ int OnInit()
magic_number = input_entry_magic_number;
slippage = input_slippage_points;
if(!IsHedgingAccount())
{
Print("HITEntryEA requires a retail hedging account. account_margin_mode=",
AccountInfoInteger(ACCOUNT_MARGIN_MODE));
return INIT_FAILED;
}
ConfigurePythonGatewayPaths();
PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend");
@@ -459,6 +476,11 @@ void OnTick()
// H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。
ProcessEntryUpdate(g_ea);
// Read completed H1 results before spread gating so stale split orders and state updates are not delayed.
bool entry_result_ready = IsEntryResultReady();
if(entry_result_ready)
RefreshTargetPrices(g_ea);
// M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。
ProcessM15EntryTimingUpdate();
@@ -468,10 +490,9 @@ void OnTick()
if(!IsSpreadAllowed(ctx))
return;
if(!IsEntryResultReady())
if(!entry_result_ready)
return;
RefreshTargetPrices(g_ea);
ProcessEntryDecisionIfNeeded(g_ea, ctx);
}
+4 -6
View File
@@ -94,8 +94,6 @@ void LoadSLTPInputSettings(SLTPManagerPanelSettings &settings)
bool ApplySLTPSettings(const SLTPManagerPanelSettings &settings,
const bool print_summary)
{
g_sltp_settings_valid = false;
g_sltp_manager.SetMagicNumber((ulong)input_managed_magic_number);
g_sltp_manager.SetSymbol(_Symbol);
g_sltp_manager.SetDeviationInPoints(input_slippage_points);
@@ -111,13 +109,13 @@ bool ApplySLTPSettings(const SLTPManagerPanelSettings &settings,
settings.active_step_trigger_pips,
settings.active_step_move_pips);
g_sltp_manager.SetTpProgressStopSettings(settings.use_tp_progress_stop,
settings.tp_progress_trigger_percent,
settings.tp_progress_sl_lock_percent);
settings.tp_progress_trigger_percent,
settings.tp_progress_sl_lock_percent);
g_sltp_manager.SetHighVolatilityLimitSettings(settings.use_high_volatility_limit);
if(!g_sltp_manager.ValidateSettings())
if(settings.manager_enabled && !g_sltp_manager.ValidateSettings())
{
Print("HIT position manager SLTP settings rejected. Manager is disabled until valid settings are applied.");
Print("HIT position manager SLTP settings rejected. Previous valid settings remain active.");
return false;
}
+34 -2
View File
@@ -512,14 +512,46 @@ bool IsProcessResultReady(const string done_file, const string running_file, con
if(!FileIsExist(result_file))
{
Print("[", label, "] done exists but result file is missing: ", result_file);
return false;
Print("[", label, "] done exists but result file is missing: ", result_file);
DeleteDoneFile(done_file);
DeleteRunningFile(running_file);
ResetExternalProcessState(process);
MarkProcessRetryPending(label);
return false;
}
DeleteRunningFile(running_file);
return true;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
/**
* @brief Mark a missing-result Python run for immediate retry on the next tick.
*/
void MarkProcessRetryPending(const string label)
{
if(label == "trend")
{
g_ea.trend_state = MARKET_TECHNICAL_ERROR_STOP;
g_ea.load_trend_flg = false;
g_init_trend_pending = true;
return;
}
if(label == "entry")
{
g_ea.res_chk = 0;
g_ea.zone_res_chk = 0;
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開始可能状態を返す関数
//+------------------------------------------------------------------+
@@ -25,26 +25,6 @@ bool GetTickContext(TickContext &ctx)
return true;
}
//+------------------------------------------------------------------+
//| 期限切れ注文・期限切れポジションを処理する関数
//+------------------------------------------------------------------+
/**
* @brief 期限切れの未約定注文と保有ポジションを処理します。
*
* 未約定注文はENTRY_H1_LIMIT時間、保有ポジションはCLOSE_H1_LIMIT時間を基準に判定します。
* 新規注文条件とは独立して、OnTickの早い段階で実行される想定です。
*/
void ManageExpiredTrades()
{
CleanupPendingOrderCancelAttempts();
if(OrdersTotal() > 0)
CancelExpiredOrders();
if(PositionsTotal() > 0)
CloseExpiredPositions();
}
//+------------------------------------------------------------------+
//| H4更新検知とトレンド判定Python起動を処理する関数
//+------------------------------------------------------------------+
-82
View File
@@ -1068,86 +1068,4 @@ bool CancelExpiredOrders()
}
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
+31 -5
View File
@@ -335,12 +335,14 @@ public:
continue;
double candidate_sl = 0.0;
const double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
const double evaluation_price = (type == POSITION_TYPE_BUY) ? tick.bid : tick.ask;
if (!CalcHighVolatilitySL(type,
evaluation_price,
open_m1,
open_m3,
open_m5,
evaluation_price,
position_open_price,
open_m1,
open_m3,
open_m5,
open_m10,
open_m15,
pip_size,
@@ -590,6 +592,7 @@ private:
/// @return いずれかの時間足でしきい値を超え、SL候補を出力した場合は true。
bool CalcHighVolatilitySL(const ENUM_POSITION_TYPE type,
const double current_price,
const double position_open_price,
const double open_m1,
const double open_m3,
const double open_m5,
@@ -612,13 +615,36 @@ private:
if (!has_candidate)
return false;
if (!IsProfitProtectingStopLoss(type, candidate_sl, position_open_price))
return false;
sl_value = NormalizeDouble(candidate_sl, digits);
return true;
}
/// @brief 急変時SL候補が建値より不利にならないことを確認する。
bool IsProfitProtectingStopLoss(const ENUM_POSITION_TYPE type,
const double candidate_sl,
const double open_price)
{
if (open_price <= 0.0)
return false;
const double point = SymbolInfoDouble(m_symbol, SYMBOL_POINT);
const double tolerance = point * 0.1;
if (type == POSITION_TYPE_BUY)
return (candidate_sl >= open_price - tolerance);
if (type == POSITION_TYPE_SELL)
return (candidate_sl <= open_price + tolerance);
return false;
}
/// @brief 1本の時間足始値から急変幅を評価し、有効なSL候補なら最良候補へ反映する。
void EvaluateHighVolatilityOpen(const ENUM_POSITION_TYPE type,
const double current_price,
const double current_price,
const double open_price,
const double limit_pips,
const double rate_ratio,
+4 -1
View File
@@ -4,7 +4,7 @@
`HITEntryEA.mq5` is the entry-only Expert Advisor. It runs the H4/H1 Python signal workflow, applies the M15 confirmation rules, and submits pending orders. It does not manage filled positions. Stop management, trailing, and time-based position exits are delegated to `HITPositionManagerEA.mq5`.
The two EAs coordinate through matching `_Symbol` and `input_entry_magic_number` / `input_managed_magic_number`. The operating assumption is a retail hedging account.
The two EAs coordinate through matching `_Symbol` and `input_entry_magic_number` / `input_managed_magic_number`. The operating assumption is a retail hedging account, and initialization fails on non-hedging accounts.
## 2. Indicators
@@ -45,12 +45,14 @@ The two EAs coordinate through matching `_Symbol` and `input_entry_magic_number`
- Pending orders receive `input_entry_magic_number`.
- The entry EA cancels expired pending orders, pending orders that conflict with the refreshed H4 state, and stale split-entry pending orders.
- When the range-breakout guard is enabled, upward breakout warning/confirmation cancels existing T4 Sell Limit orders and blocks new T4 entries until cooldown expiry. Downward breakout warning/confirmation cancels existing T2 Buy Limit orders and blocks new T2 entries.
- H1 Python results, candidate timestamps, and stale split-pending cleanup are processed before the spread gate; excessive spread only blocks new order submission.
- Filled-position SL updates, trailing, and time-based exits are not performed by this EA.
## 5. Risk Management
- The order limit counts only pending orders and positions matching `_Symbol` and `input_entry_magic_number`.
- Python process markers, process IDs, done files, and timeouts are monitored to prevent duplicate launches and stale-result reuse.
- If a done file exists but the expected result file is missing, the EA clears the done/running state and marks the same workflow for retry on the next tick.
- If the API TP direction contradicts the Buy/Sell price rules, the EA does not repair it; the existing price-consistency checks skip the order.
- The range-breakout guard only touches pending orders matching `_Symbol` and `input_entry_magic_number`; upward breakouts cancel T4 Sell Limit only, and downward breakouts cancel T2 Buy Limit only.
- `OrderSend` return values and `MqlTradeResult.retcode` are checked and logged.
@@ -64,6 +66,7 @@ The two EAs coordinate through matching `_Symbol` and `input_entry_magic_number`
## 7. Changelog
- 2026-06-14: Added initialization failure on non-hedging accounts. Moved H1 result loading before spread gating, added retry recovery for done-without-result Python states, and removed the unused filled-position time-exit path from the entry EA side.
- 2026-06-13: Connected the range-breakout guard from `HIT-EA_refactor_ver6.mq5` to the entry EA, including T4 Sell Limit retreat on upward breakouts, T2 Buy Limit retreat on downward breakouts, and cooldown-based new-entry blocking.
- 2026-06-13: Added `input_tp_multiplier`, `input_min_tp_points`, and `input_max_tp_points` to expand API-derived TP distances for both standard orders and per-slot split entries.
- 2026-06-07: Split entry responsibilities from `HIT-EA_refactor_ver6.mq5`; moved SLTP panel and filled-position management responsibilities to `HITPositionManagerEA.mq5`.
+4 -1
View File
@@ -4,7 +4,7 @@
`HITEntryEA.mq5` は、H4/H1の外部Python判定とM15確認を使って新規pending注文までを実行する発注専用EAです。約定後ポジションのSL移動、トレーリング、時間決済は行わず、同じマジックナンバーを監視する `HITPositionManagerEA.mq5` に委譲します。
2つのEAは `_Symbol``input_entry_magic_number` / `input_managed_magic_number` の一致で連携します。運用口座はhedging口座を前提とします。
2つのEAは `_Symbol``input_entry_magic_number` / `input_managed_magic_number` の一致で連携します。運用口座はhedging口座を前提とし、非hedging口座では初期化に失敗します。
## 2. 使用インジケータ
@@ -45,12 +45,14 @@
- pending注文には `input_entry_magic_number` を付与します。
- H4状態と矛盾するpending注文、期限切れpending注文、古い分割pending注文は発注EAが取消します。
- レンジブレイクガードがONの場合、M15監視足で上方向ブレイク警戒/確定を検出すると既存T4 Sell Limitを取消し、T4新規発注をクールダウン終了まで停止します。下方向ブレイクでは既存T2 Buy Limitを取消し、T2新規発注を停止します。
- H1 Python結果の読み込み、候補時刻更新、古い分割pending取消はスプレッド判定より前に実行し、スプレッド超過は新規発注だけを停止します。
- 約定後ポジションのSL変更、時間決済、トレーリングは発注EAでは行いません。
## 5. リスク管理
- 発注上限は口座全体ではなく `_Symbol``input_entry_magic_number` が一致するpending注文 + ポジションで判定します。
- Python連携は起動中/完了ファイルとプロセスIDを監視し、二重起動と古い結果の再利用を抑止します。
- doneファイルが存在しても結果ファイルが欠落している場合は、done/running状態を破棄し、次tickで同じ処理を再実行できる状態へ戻します。
- API由来TPの方向がBuy/Sell条件と逆の場合、EAは方向を補正せず既存の価格整合チェックで発注を見送ります。
- レンジブレイクガードは `_Symbol``input_entry_magic_number` が一致するpending注文だけを対象にし、上方向ではT4 Sell Limit、下方向ではT2 Buy Limitのみを取消します。
- `OrderSend` の戻り値と `MqlTradeResult.retcode` を確認し、失敗時はエラーまたはretcodeをログに出します。
@@ -64,6 +66,7 @@
## 7. 変更履歴
- 2026-06-14: 非hedging口座での初期化停止を追加。H1結果読み込みをスプレッド判定前へ移動し、doneのみ残るPython不整合時の再実行復旧を追加。発注EA側に残っていた未使用の約定後時間決済経路を削除。
- 2026-06-13: `HIT-EA_refactor_ver6.mq5` と同等のレンジブレイクガードを発注EAへ接続。レンジ上抜け時のT4 Sell Limit退避、レンジ下抜け時のT2 Buy Limit退避、およびクールダウン中の新規発注停止を反映。
- 2026-06-13: API由来TPをエントリー価格からの距離ベースで拡大する `input_tp_multiplier`, `input_min_tp_points`, `input_max_tp_points` を追加し、通常注文と分割slotごとの最終TP計算に反映。
- 2026-06-07: `HIT-EA_refactor_ver6.mq5` から発注責務を分離し、SLTPパネルとポジション管理を `HITPositionManagerEA.mq5` へ移管する仕様を追加。
@@ -41,6 +41,7 @@ The managed set is limited to positions whose `POSITION_SYMBOL` equals `_Symbol`
- On every tick, the EA first evaluates ticket-based time exits and then applies SLTP management.
- Time exits close target tickets when `POSITION_TIME` is older than `input_position_close_after_h1_bars * PeriodSeconds(PERIOD_H1)` by using `CTrade::PositionClose(ticket)`.
- SLTP management applies only candidates that improve the current SL in the profit-protection direction, preserving the current TP with `CTrade::PositionModify(ticket, new_sl, current_tp)`.
- High-volatility stop tightening only accepts candidates that are at or beyond breakeven: at or above entry for BUY positions and at or below entry for SELL positions.
## 5. Risk Management
@@ -49,6 +50,7 @@ The managed set is limited to positions whose `POSITION_SYMBOL` equals `_Symbol`
- Different magic numbers, different symbols, and manual-position equivalents are ignored.
- Logs include ticket, symbol, magic, position type, trade retcode, and `GetLastError()`.
- Standard breakeven and active trailing cannot be enabled together; settings validation rejects that conflict.
- When `input_sltp_manager_enabled=false`, contradictory detailed SLTP settings do not stop the EA; time exits continue to run. Detailed settings are validated when SLTP management is enabled.
## 6. Unit Test Scope
@@ -58,5 +60,6 @@ The managed set is limited to positions whose `POSITION_SYMBOL` equals `_Symbol`
## 7. Changelog
- 2026-06-14: Allowed the EA to continue time-exit management when SLTP master is off even if detailed SLTP settings conflict. Added breakeven protection to high-volatility stop tightening so loss-side SL candidates are discarded.
- 2026-06-13: Synchronized SLTP input defaults with `HIT-EA_refactor_ver6.mq5` and documented each default value.
- 2026-06-07: Split position-management responsibilities from `HIT-EA_refactor_ver6.mq5` and defined a hedging-account, ticket-based manager EA.
@@ -41,6 +41,7 @@
- `OnTick()` では、まずticket単位で保有時間決済を評価し、その後SLTP管理を実行します。
- 時間決済は `POSITION_TIME` から `input_position_close_after_h1_bars * PeriodSeconds(PERIOD_H1)` 以上経過した対象ticketを `CTrade::PositionClose(ticket)` で閉じます。
- SLTP管理は、対象ticketの現在SLより利益保護方向へ改善する候補だけを `CTrade::PositionModify(ticket, new_sl, current_tp)` で反映します。
- 急変時SL引き締めは、BUYでは建値以上、SELLでは建値以下となる候補だけを採用し、損失側へのSL候補は破棄します。
## 5. リスク管理
@@ -49,6 +50,7 @@
- magic違い、symbol違い、手動ポジション相当には触れません。
- ログにはticket、symbol、magic、position type、retcode、`GetLastError()` を含めます。
- 通常ブレークイーブンとアクティブトレーリングの同時ONは設定検証で拒否します。
- `input_sltp_manager_enabled=false` の場合、SLTP詳細設定に矛盾があってもEA全体は停止せず、時間決済を継続します。SLTP管理をONにする時点で詳細設定を検証します。
## 6. 単体テスト
@@ -58,5 +60,6 @@
## 7. 変更履歴
- 2026-06-14: SLTPマスターOFF時は詳細設定の矛盾でEA全体を停止しないよう変更。急変時SL引き締めに建値保護条件を追加し、損失側SL候補を採用しないようにした。
- 2026-06-13: SLTP関連inputの既定値を `HIT-EA_refactor_ver6.mq5` と同じ初期状態に同期し、仕様書へ各既定値を明記。
- 2026-06-07: `HIT-EA_refactor_ver6.mq5` からポジション管理責務を分離し、hedging口座専用のticket単位管理EAとして新規定義。