From 13eaa74309f5208c3d8c802f7bf6729eb662cd1f Mon Sep 17 00:00:00 2001 From: Hiroaki86 Date: Thu, 4 Jun 2026 19:56:13 +0900 Subject: [PATCH] Add safe-stop diagnostics to H1 entry logs --- docs/MyProject/HIT-EA_spec_en.md | 5 + ..._spec_en.md.bak_codex_20260601_debug_entry | 274 ++++++++++++++++ docs/MyProject/HIT-EA_spec_ja.md | 5 + ..._spec_ja.md.bak_codex_20260601_debug_entry | 274 ++++++++++++++++ python_for_ea/src/ea_py/io/debug_logs.py | 28 ++ ...bug_logs.py.bak_codex_20260601_debug_entry | 105 ++++++ .../src/ea_py/pipelines/entry_pipeline.py | 126 +++++++- ...pipeline.py.bak_codex_20260601_debug_entry | 305 ++++++++++++++++++ python_for_ea/tests/unit/test_debug_logs.py | 31 ++ 9 files changed, 1139 insertions(+), 14 deletions(-) create mode 100644 docs/MyProject/HIT-EA_spec_en.md.bak_codex_20260601_debug_entry create mode 100644 docs/MyProject/HIT-EA_spec_ja.md.bak_codex_20260601_debug_entry create mode 100644 python_for_ea/src/ea_py/io/debug_logs.py.bak_codex_20260601_debug_entry create mode 100644 python_for_ea/src/ea_py/pipelines/entry_pipeline.py.bak_codex_20260601_debug_entry create mode 100644 python_for_ea/tests/unit/test_debug_logs.py diff --git a/docs/MyProject/HIT-EA_spec_en.md b/docs/MyProject/HIT-EA_spec_en.md index b9f2b1c..b6b5404 100644 --- a/docs/MyProject/HIT-EA_spec_en.md +++ b/docs/MyProject/HIT-EA_spec_en.md @@ -210,12 +210,17 @@ After an H4 `market_state` reload, the EA cancels existing pending orders whose - If `trend_state.txt` is missing, unreadable, not strictly parseable as an integer, or outside `0..6`, the EA uses `MARKET_TECHNICAL_ERROR_STOP`. - If `target_prices.txt` is missing, unreadable, shorter than 13 lines, or contains a non-strict numeric line, the EA uses `res_chk = 0`. - When split entries are enabled, missing `target_zones.txt`, schema mismatch, fewer than 7 lines, `res_chk != 1`, a valid-zone `candidate_id` that is not a strict 12-digit timestamp, or non-strict numeric values stop new split orders. +- When Python falls back to H1 safe-stop values, it appends `RESULT: SAFE_STOP`, the stop stage, reason, H4 state, and candidate ID to `debug_entry.txt`. This distinguishes a normal GPT skip from API, configuration, or data failures. - If a Python process exits with a non-zero code, the code is logged and the next trigger may retry. - If a Python process remains active beyond `PYTHON_TIMEOUT_SECONDS`, the EA runs `taskkill /T /F` against the cmd/bat process tree, clears done/running files, and allows the next trigger to retry. - If only a stale running marker remains, the EA attempts PID recovery. If recovery fails and the marker is timed out, it removes the stale marker. ## 10. Changelog +### 2026-06-01 + +- Added `debug_entry.txt` safe-stop diagnostics with the stop stage and reason so normal candidate skips can be distinguished from guarded failure fallbacks. + ### 2026-05-31 - Changed startup initialization to rebuild H4/H1 results regardless of `initial_order`, while holding the technical-error stop state until the H4 result reloads. diff --git a/docs/MyProject/HIT-EA_spec_en.md.bak_codex_20260601_debug_entry b/docs/MyProject/HIT-EA_spec_en.md.bak_codex_20260601_debug_entry new file mode 100644 index 0000000..b9f2b1c --- /dev/null +++ b/docs/MyProject/HIT-EA_spec_en.md.bak_codex_20260601_debug_entry @@ -0,0 +1,274 @@ +# HIT-EA Specification (English) + +## 1. Overview + +- This EA sends confirmed H4 OHLC data to an external Python process for market-state analysis and confirmed H1 OHLC data for entry candidate generation. +- The MQL5 side reads Python output files and places pending orders only after checking the H4 market state, H1 candidate prices, M15 confirmation filter, spread, and broker distance constraints. +- For backtest reproducibility, OHLC export uses `OHLC_START_SHIFT = 1`, so the forming bar is excluded and only closed bars are passed to Python. +- For live resilience, the EA tracks external-process running files, process IDs, exit codes, and timeouts to prevent duplicate launches and CSV overwrites. + +## 2. Indicators + +- No standard or custom indicators are used. +- OHLC data is retrieved with `CopyRates(_Symbol, timeframe, OHLC_START_SHIFT, HISTORY_BARS, rates)`. +- M15 entry confirmation uses candle shape, average range, and distance to the candidate price for T2/T4. For T1/T3 it uses M15 impulse confirmation when that option is enabled. + +## 3. Parameters + +### input + +- `lot_size = 0.01`: Order volume. +- `spread_limit = 60`: Maximum allowed spread in points. +- `magic_number = 10001`: Magic number used to identify this EA's orders and positions. +- `initial_order = 0`: Retained for compatibility with older settings. Startup always rebuilds H4/H1 results and remains in technical-error stop state until the H4 result is reloaded. +- `input_position_limit = 10`: Practical limit for this EA's pending orders plus positions. Values above the internal `POSITION_LIMIT` are clamped. +- `use_split_entry_zone = false`: Enables split entries based on H1 predicted zones. +- `split_entry_count = 3`: Number of split entry orders, clamped to 1..10. +- `split_lot_mode = SPLIT_LOT_TOTAL`: Split lot mode. `SPLIT_LOT_TOTAL` divides the total lot across slots, and `SPLIT_LOT_FIXED` uses a fixed lot per order. +- `split_total_lot_size = 0.09`: Total lot used by total-split mode. +- `split_fixed_lot_size = 0.01`: Per-order lot used by fixed-split mode. +- `cancel_old_split_pending_on_new_zone = true`: Cancels older split pending orders when a new H1 zone set is loaded. +- `input_cancel_retry_cooldown_seconds = 60`: Wait time before retrying a failed pending-order cancellation for the same ticket. The effective value is clamped to 1..3600 seconds. +- `use_m15_entry_filter = true`: Enables M15 closed-bar entry timing confirmation. +- `m15_entry_zone_atr_multiplier = 1.50`: Allowed proximity multiplier based on the M15 average range. +- `use_m15_imbalance_confirmation = true`: Adds M15 impulse confirmation for T1/T3 trend-following candidates. +- `m15_imbalance_avg_body_period = 20`: Average-body period used by M15 impulse confirmation. +- `m15_imbalance_sensitivity = 2.0`: Required multiple of the average body for M15 impulse confirmation. +- `m15_imbalance_min_avg_body_points = 1.0`: Minimum M15 average body in points. +- `use_m15_imbalance_debug_log = false`: Enables detailed M15 impulse-confirmation logs. +- `input_entry_max_candidate_age_minutes = 120`: Maximum age, in minutes, for using an H1 candidate for new execution. This blocks delayed entries when M15 confirmation appears too late. +- `input_sltp_manager_enabled = false`: Enables UI-driven existing-position SL management through `CSLTPManager`. +- `input_sltp_show_panel = true`: Shows the SLTP control panel on the chart. +- `input_sltp_use_breakeven = false`: Enables normal break-even. +- `input_sltp_breakeven_trigger_pips = 30.0`: Profit threshold for normal break-even. +- `input_sltp_breakeven_buffer_pips = 3.0`: Profit-side entry buffer used by normal break-even. +- `input_sltp_use_elapsed_breakeven = false`: Enables elapsed-time break-even. +- `input_sltp_elapsed_breakeven_hours = 4.0`: Holding time required before elapsed-time break-even is allowed. +- `input_sltp_elapsed_breakeven_buffer_pips = 3.0`: Profit-side entry buffer used by elapsed-time break-even. +- `input_sltp_use_active_trailing = false`: Enables active step trailing. +- `input_sltp_active_breakeven_pips = 30.0`: Profit threshold that starts active trailing. +- `input_sltp_active_stop_loss_offset_pips = 5.0`: Initial locked profit offset from entry. +- `input_sltp_active_step_trigger_pips = 10.0`: Additional profit interval required for each SL step. +- `input_sltp_active_step_move_pips = 5.0`: SL movement added per completed step. +- `input_sltp_use_tp_progress_stop = false`: Enables TP-progress-based SL locking. +- `input_sltp_tp_progress_trigger_percent = 70.0`: TP progress required before the SL lock is applied. +- `input_sltp_tp_progress_sl_lock_percent = 30.0`: Entry-to-TP distance percentage locked by SL. +- `input_sltp_use_high_volatility_limit = false`: Enables short-term high-volatility SL tightening. + +### Main Constants + +- `POSITION_LIMIT = 48`: Maximum number of this EA's pending orders plus positions. +- `ENTRY_H1_LIMIT = 2`: Pending-order lifetime in H1 bars. +- `CLOSE_H1_LIMIT = 12`: Position lifetime in H1 bars. +- `HISTORY_BARS = 72`: Number of OHLC bars exported to Python. +- `OHLC_START_SHIFT = 1`: Shift used to export closed bars only. +- `M15_CONFIRM_BARS = 30`: Number of M15 bars used for average-range calculation. +- `ENTRY_RETRY_SECONDS = 60`: Retry interval for entry checks. +- `ENTRY_RETRY_LIMIT = 10`: Maximum retry count for one H1 candidate set. +- `TARGET_SIZE = 13`: Number of numeric values expected in `target_prices.txt`. +- `TARGET_ZONE_SCHEMA_VERSION = 2`: Schema version used by `target_zones.txt`. +- `PYTHON_TIMEOUT_SECONDS = 600`: External Python wait threshold. + +### Python Entry-Candidate Guard + +- After H1 candidate generation, Python invalidates any `entry` that is too far from the current price. +- The maximum distance is strategy-specific: T1/T3 Stop candidates use `max(H1 EATR * 1.50, 5.00)`, and T2/T4 Limit candidates use `max(H1 EATR * 1.00, 5.00)`. A strategy beyond its limit is rewritten to `0.00,0.00,0.00`. +- This post-filter is intended to reduce delayed entries from deep limit orders or distant breakout waits after M15 confirmation arrives late. + +## 4. File Layout + +- `Experts/MyProject/HIT-EA_refactor_ver6.mq5`: EA entry point. Keeps `OnInit`, `OnDeinit`, `OnTick`, inputs, global state, and DLL imports. +- `Include/MyLib/Common/HITRuntimeController.mqh`: Tick flow, H4/H1/M15 update control, status comments, and spread checks. +- `Include/MyLib/Signals/HITEntrySignal.mqh`: Entry preconditions, order-type price validation, M15 filter, and retry-state updates. +- `Include/MyLib/Common/HITExternalProcess.mqh`: done/running files, external process handles, PID recovery, exit-code checks, and timeout recovery. +- `Include/MyLib/Signals/HITPythonSignalGateway.mqh`: OHLC CSV export, Python batch launch, and `trend_state.txt` / `target_prices.txt` / `target_zones.txt` loading. +- `Include/MyLib/Trading/HITTradeManager.mqh`: Order sending, filling policy, pending-order expiration, order/position counting, expired order cancellation, and expired position closing. +- `Include/MyLib/Trading/SLTPManager.mqh`: Existing-position break-even, elapsed-time break-even, active trailing, TP-progress SL, and high-volatility SL management. +- `Include/MyLib/Panel/SLTPManagerPanel.mqh`: Chart panel that toggles `CSLTPManager` features, validates numeric inputs, and applies settings to the EA. + +## 5. Python Integration Files + +During `OnInit()`, the EA builds a `HIT__` prefix from `_Symbol` and `magic_number`, then applies it to all MT5 `Files` linkage names. Example: `HIT_GOLD_10001_ohlc_H4.csv`. The same prefix is passed to the batch file as the first argument, and the batch file exposes it to Python as `MT5_EA_FILE_PREFIX`. The EA also passes the symbol's `SYMBOL_DIGITS` as the second argument, exposed to Python as `MT5_PRICE_DIGITS`. + +When no prefix is provided, such as manual Python execution, Python keeps using the legacy names like `ohlc_H4.csv`. + +### H4 Trend Analysis + +- MQL5 output: `_ohlc_H4.csv` +- Python output: `_trend_state.txt` +- Done marker: `_process_done_trend.txt` +- Running marker: `_process_running_trend.txt` +- Batch file: `\MQL5\python_for_ea\bat\get_trend_reply.bat` + +### H1 Entry Candidate Generation + +- MQL5 output: `_ohlc_H1.csv` +- Python output: `_target_prices.txt`, `_target_zones.txt` +- Done marker: `_process_done_entry.txt` +- Running marker: `_process_running_entry.txt` +- Batch file: `\MQL5\python_for_ea\bat\get_entry_reply.bat` + +During `OnInit()`, the EA resolves `MQL5\python_for_ea` from `TerminalInfoString(TERMINAL_DATA_PATH)` and builds absolute batch-file paths. Each batch file resolves the Python project root from its own location, so runtime execution no longer depends on `C:\ea_py`. + +`target_prices.txt` must contain 13 lines: + +```text +Line 1: res_chk +Lines 2-4: T1 Buy Stop entry/tp/sl +Lines 5-7: T2 Buy Limit entry/tp/sl +Lines 8-10: T3 Sell Stop entry/tp/sl +Lines 11-13: T4 Sell Limit entry/tp/sl +``` + +`target_zones.txt` must contain 7 lines for split entries: + +```text +Line 1: schema_version (2) +Line 2: res_chk +Line 3: candidate_id derived from the closed H1 bar time +Line 4: T1 Buy Stop strategy, zone_low, zone_high, tp, sl +Line 5: T2 Buy Limit strategy, zone_low, zone_high, tp, sl +Line 6: T3 Sell Stop strategy, zone_low, zone_high, tp, sl +Line 7: T4 Sell Limit strategy, zone_low, zone_high, tp, sl +``` + +Python creates `process_done_entry.txt` only after both `target_prices.txt` and `target_zones.txt` have been written. Prices in `target_zones.txt` are formatted with `MT5_PRICE_DIGITS`; manual runs without this value use a conservative 5 decimal places. When split entries are disabled, the EA keeps using the legacy 13-line `target_prices.txt` path. + +## 6. Entry Conditions + +New entries are allowed only when all conditions below are satisfied: + +- Spread is less than or equal to `spread_limit`. +- H4 trend analysis is complete and `trend_state.txt` can be loaded. +- H1 entry candidate generation is complete and `target_prices.txt` can be loaded. +- `res_chk = 1`. +- The H1 candidate set has not expired under `ENTRY_H1_LIMIT`, measured from the closed H1 bar time that produced the candidate. +- The H1 candidate set has not exceeded `input_entry_max_candidate_age_minutes`, measured from the closed H1 bar time that produced the candidate. +- The selected order type has valid `entry/tp/sl` values greater than `0.0`. +- The order type is allowed by the H4 `market_state`. +- The order-type price relationship and broker minimum distance rules are satisfied. +- When `use_m15_entry_filter = true`, T2/T4 satisfy the M15 direction, rejection, and proximity checks. T1/T3 satisfy M15 impulse confirmation when `use_m15_imbalance_confirmation = true`. +- This EA's pending orders plus positions are below `input_position_limit`. +- When split entries are enabled, `target_zones.txt` has `res_chk = 1`, the predicted zone prices are valid, split lots fit `SYMBOL_VOLUME_MIN/MAX/STEP`, and no duplicate order or position exists for the same `candidate_id` + order type + slot. + +### market_state and Allowed Order Types + +- `0 MARKET_LOW_VOL_RANGE`: Allows T2 Buy Limit / T4 Sell Limit. +- `1 MARKET_HIGH_VOL_RANGE`: Allows T2 Buy Limit / T4 Sell Limit. +- `2 MARKET_LOW_VOL_UP`: Allows T1 Buy Stop / T2 Buy Limit. +- `3 MARKET_HIGH_VOL_UP`: Allows T1 Buy Stop / T2 Buy Limit. +- `4 MARKET_LOW_VOL_DOWN`: Allows T3 Sell Stop / T4 Sell Limit. +- `5 MARKET_HIGH_VOL_DOWN`: Allows T3 Sell Stop / T4 Sell Limit. +- `6 MARKET_TECHNICAL_ERROR_STOP`: Blocks new entries. + +After an H4 `market_state` reload, the EA cancels existing pending orders whose order types are not allowed by the refreshed state. For `6 MARKET_TECHNICAL_ERROR_STOP`, it cancels all pending orders managed by this EA. + +### Order-Type Price Rules + +- T1 Buy Stop: `Ask < entry`, `tp > entry`, `sl < entry` +- T2 Buy Limit: `Ask > entry`, `tp > entry`, `sl < entry` +- T3 Sell Stop: `Bid > entry`, `tp < entry`, `sl > entry` +- T4 Sell Limit: `Bid < entry`, `tp < entry`, `sl > entry` + +## 7. Exit Conditions + +- Pending orders are cancelled after `ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1)` has elapsed from the H1 candidate time restored from the comment. Legacy orders without that token fall back to setup time. +- Positions are closed after `CLOSE_H1_LIMIT * PeriodSeconds(PERIOD_H1)` has elapsed from entry time. +- Time-limit handling runs early in `OnTick` and is independent of Python readiness or spread checks. +- On EA removal, `OnDeinit()` releases external-process state and the SLTP panel, then clears the chart status text with `Comment("")` and `ChartRedraw(0)`. +- When `input_sltp_manager_enabled` or the panel `Manager` toggle is ON, the EA runs `CSLTPManager::ManagePositions()` and `HighVolatilityLimit()` after time-limit handling. Existing-position SL protection therefore continues while Python results are pending or new entries are skipped. +- Normal break-even and active trailing are mutually exclusive. The panel turns one mode off when the other is enabled, and `CSLTPManager::ValidateSettings()` also validates the exclusion. + +## 7.1 SLTP Control Panel + +- The panel is created when `input_sltp_show_panel = true` and appears near the chart's upper-right area. +- If panel creation fails, the EA logs the failure and continues with the already-applied input-based SLTP settings. +- The `Manager` button toggles all SLTP management. When OFF, panel values remain stored but `OnTick` does not modify SL values. +- The `BreakEven`, `Elapsed BE`, `Active Trail`, `TP Progress`, and `High Vol` buttons toggle each `CSLTPManager` feature. +- Numeric fields are validated when `APPLY` is pressed. Invalid or out-of-range values show a `MessageBox`, are logged, and revert to the previous valid value. +- Settings are copied into `CSLTPManager` only after `APPLY` succeeds. The EA then calls `ValidateSettings()`; if validation fails, SLTP management is stopped and the reason is logged. + +## 8. Risk Management + +- Managed orders and positions are limited to matching `_Symbol` and `magic_number`. +- `input_position_limit` counts only this EA's orders and positions, not the full account. The hard internal limit is `POSITION_LIMIT`. +- The EA selects the filling policy from `SYMBOL_FILLING_MODE`, preferring IOC, then FOK, then RETURN. +- Before sending either single or split orders, the EA verifies the requested lot against `SYMBOL_VOLUME_MIN/MAX/STEP`. +- Pending orders receive server-side expiration from the H1 candidate time when the broker supports it. Orders are skipped if that expiration is already in the past. +- `OrderSend` return values and `MqlTradeResult.retcode` are checked. Failures are logged with `GetLastError()` or the retcode. +- Single and split order comments keep a valid `candidate_id` for candidate-origin expiration handling. +- Split-entry order comments also include order type and slot number to prevent duplicate execution of the same strategy slot. +- When `cancel_old_split_pending_on_new_zone = true`, a new valid `candidate_id` cancels older split pending orders, and an invalid or stopped `target_zones.txt` cancels all existing split pending orders for this EA/symbol/magic. +- After an H4 state update, existing pending orders that contradict the refreshed `market_state` are cancelled. +- A failed pending-order cancellation is not retried for the same ticket until `input_cancel_retry_cooldown_seconds` has elapsed, preventing excessive per-tick trade-server requests. +- SLTP management only targets existing positions matching `_Symbol` and `magic_number`, and it preserves each position's current TP. +- SL changes are sent only when the candidate improves protection relative to the current SL and satisfies broker stop-level and freeze-level constraints. +- `HighVolatilityLimit()` evaluates BUY positions with Bid and SELL positions with Ask, and treats GOLD/XAUUSD suffix symbols as 1 pip = 0.1. + +## 9. Error Handling + +- If `trend_state.txt` is missing, unreadable, not strictly parseable as an integer, or outside `0..6`, the EA uses `MARKET_TECHNICAL_ERROR_STOP`. +- If `target_prices.txt` is missing, unreadable, shorter than 13 lines, or contains a non-strict numeric line, the EA uses `res_chk = 0`. +- When split entries are enabled, missing `target_zones.txt`, schema mismatch, fewer than 7 lines, `res_chk != 1`, a valid-zone `candidate_id` that is not a strict 12-digit timestamp, or non-strict numeric values stop new split orders. +- If a Python process exits with a non-zero code, the code is logged and the next trigger may retry. +- If a Python process remains active beyond `PYTHON_TIMEOUT_SECONDS`, the EA runs `taskkill /T /F` against the cmd/bat process tree, clears done/running files, and allows the next trigger to retry. +- If only a stale running marker remains, the EA attempts PID recovery. If recovery fails and the marker is timed out, it removes the stale marker. + +## 10. Changelog + +### 2026-05-31 + +- Changed startup initialization to rebuild H4/H1 results regardless of `initial_order`, while holding the technical-error stop state until the H4 result reloads. +- Added cancellation of existing pending orders that contradict a refreshed H4 `market_state`. +- Added per-ticket cooldown after failed pending-order cancellation to prevent excessive per-tick trade-server requests. +- Added order type to split-slot identifiers so different strategies using the same `candidate_id` and slot cannot collide. +- Kept valid `candidate_id` tokens in both single and split order comments and changed pending expiration to use the H1 candidate origin time. +- Required strict 12-digit timestamp `candidate_id` values for valid `target_zones.txt`; malformed values now stop safely. + +### 2026-05-30 + +- Added the `HIT__` linkage-file prefix contract on both the MT5 and Python sides to avoid collisions across multiple EAs or symbols in the same terminal. +- Updated T1/T3 trend-following candidates so `use_m15_imbalance_confirmation = true` requires M15 impulse confirmation. +- Added strict numeric parsing requirements for `trend_state.txt`, `target_prices.txt`, and `target_zones.txt`, with invalid content handled as a safe stop state. +- Added stale split-pending cancellation behavior when `target_zones.txt` is invalid or stopped. +- Synchronized current defaults for `input_entry_max_candidate_age_minutes`, `ENTRY_H1_LIMIT`, SLTP buffers, and the Python entry-distance guard. +- Added process-tree termination and retry recovery for Python timeouts. +- Changed H1 candidate freshness checks to use the closed H1 bar time restored from `candidate_id`, instead of the Python result load time. +- Added `MT5_PRICE_DIGITS` so Python formats `target_zones.txt` prices with the active symbol's decimal precision. +- Added single-order lot validation, OHLC CSV NaN/inf and consistency checks, Ask-based SELL high-volatility SL evaluation, and GOLD/XAUUSD suffix handling. + +### 2026-05-27 + +- Moved the Python helper application specification from `C:\ea_py` to `MQL5\python_for_ea` under the MT5 data folder. +- Updated the EA startup path model so batch paths are built from `TerminalInfoString(TERMINAL_DATA_PATH)`, and the external process working directory is derived from the batch-file location. +- Updated Python path discovery so the default MT5 linkage is the adjacent `MQL5\Files` directory, with `MT5_FILES_DIR` or `MT5_DATA_PATH` available for explicit overrides. + +### 2026-05-24 + +- Added Python-side `target_zones.txt` output and made `process_done_entry.txt` appear only after both `target_prices.txt` and `target_zones.txt` are written. +- Added `use_split_entry_zone`, `split_entry_count`, `split_lot_mode`, `split_total_lot_size`, `split_fixed_lot_size`, and `input_position_limit`. +- Added split-zone pending order placement with total-lot or fixed-lot sizing. +- Added `candidate_id` + slot identification to prevent duplicate split orders. + +### 2026-05-13 + +- Added a Python-side H1 EATR distance guard that invalidates candidates too far from the current price before they are passed to MT5. +- Added `input_entry_max_candidate_age_minutes` on the EA side so stale H1 candidates are not executed when M15 confirmation arrives late. +- Added H1 candidate age to entry skip logs to make delayed entries, M15 rejection, and price mismatch easier to diagnose. + +### 2026-05-05 + +- Added `Comment("")` and `ChartRedraw(0)` to `OnDeinit()` so the chart status comment is removed when the EA is deleted. +- Connected `CSLTPManager` and the dedicated chart panel `SLTPManagerPanel.mqh` to `HIT-EA_refactor_ver5.mq5`, allowing UI control of break-even, elapsed-time break-even, active trailing, TP-progress SL, and high-volatility SL tightening. +- Added SLTP management immediately after time-limit handling in `OnTick`, so existing-position protection continues independently of Python readiness and spread checks. +- Added SLTP panel inputs, mutual exclusion for normal break-even versus active trailing, and numeric validation on `APPLY`. +- Split the function implementations from `HIT-EA_refactor_ver5.mq5` into purpose-specific `.mqh` files. +- Kept `OnInit`, `OnDeinit`, `OnTick`, inputs, and global state in the EA source while moving behavior functions under `Include/MyLib/`. +- Updated the specification to match the current `CreateProcessW` Python process tracking, M15 filter, `market_state` range `0..6`, and `spread_limit = 60`. + +### 2026-05-02 + +- Added entry suppression when `res_chk = 0`. +- Moved time-limit handling before Python readiness and spread checks. +- Split startup flags for H4 and H1 initial processing. +- Switched OHLC retrieval to `CopyRates` and exported closed bars only. diff --git a/docs/MyProject/HIT-EA_spec_ja.md b/docs/MyProject/HIT-EA_spec_ja.md index 1d450ed..6bacb22 100644 --- a/docs/MyProject/HIT-EA_spec_ja.md +++ b/docs/MyProject/HIT-EA_spec_ja.md @@ -210,12 +210,17 @@ H4 `market_state` の再読込後は、更新後の状態で許可されない - `trend_state.txt` が存在しない、読み込めない、整数として厳格にパースできない、または `0..6` 以外の場合は `MARKET_TECHNICAL_ERROR_STOP` として扱う。 - `target_prices.txt` が存在しない、読み込めない、13行未満、またはいずれかの行が数値として厳格にパースできない場合は `res_chk = 0` として扱う。 - 分割エントリー有効時に `target_zones.txt` が存在しない、schema不一致、7行未満、`res_chk != 1`、有効ゾーンの `candidate_id` が厳格な12桁時刻ではない、またはいずれかの数値行を厳格にパースできない場合は新規注文を停止する。 +- Python側がH1候補生成を安全側停止値へ倒した場合は、`debug_entry.txt` に `RESULT: SAFE_STOP`、停止段階、理由、H4状態、候補IDを追記する。これにより通常のGPT見送りとAPI・設定・データ異常を区別できる。 - Pythonプロセスが異常終了した場合、終了コードをログへ出力し、次回トリガーで再実行できる状態に戻す。 - Pythonプロセスが `PYTHON_TIMEOUT_SECONDS` を超えても実行中の場合、`taskkill /T /F` でcmd/bat配下のプロセスツリーを終了し、done/runningファイルを整理して次回トリガーで再実行できる状態に戻す。 - `running` ファイルだけが残っている場合は、PIDからプロセス復元を試みる。復元できずタイムアウト済みなら古いマーカーとして削除する。 ## 10. 変更履歴 +### 2026-06-01 + +- H1候補生成が安全側停止値へフォールバックした場合も `debug_entry.txt` へ停止段階と理由を追記し、通常の候補見送りと障害停止を判別できるようにした。 + ### 2026-05-31 - 起動時は `initial_order` の値にかかわらずH4/H1結果を再生成し、H4再読込までは技術エラー停止値を維持するfail-closed初期化へ変更した。 diff --git a/docs/MyProject/HIT-EA_spec_ja.md.bak_codex_20260601_debug_entry b/docs/MyProject/HIT-EA_spec_ja.md.bak_codex_20260601_debug_entry new file mode 100644 index 0000000..1d450ed --- /dev/null +++ b/docs/MyProject/HIT-EA_spec_ja.md.bak_codex_20260601_debug_entry @@ -0,0 +1,274 @@ +# HIT-EA 仕様書(日本語) + +## 1. 概要 + +- 本EAは、H4の確定足OHLCから外部Pythonで相場状態を判定し、H1の確定足OHLCからエントリー候補価格を生成する。 +- MQL5側はPythonの出力ファイルを読み込み、H4相場状態、H1候補価格、M15確定足フィルタ、スプレッド、ブローカー距離制約を確認したうえでペンディング注文を発行する。 +- バックテスト再現性のため、Pythonへ渡すOHLCは `OHLC_START_SHIFT = 1` により形成中バーを除外し、確定足だけを使用する。 +- ライブ実行時の耐性として、外部プロセスの `running` ファイル、プロセスID、終了コード、タイムアウトを監視し、二重起動とCSV上書きを抑止する。 + +## 2. 使用インジケータ + +- 標準インジケータおよびカスタムインジケータは未使用。 +- OHLCは `CopyRates(_Symbol, timeframe, OHLC_START_SHIFT, HISTORY_BARS, rates)` で取得する。 +- M15エントリー確認は、T2/T4では直近確定足と1本前の確定足のローソク足形状、平均レンジ、候補価格との距離から判定し、T1/T3では必要に応じてM15初動確認を行う。 + +## 3. パラメータ設定 + +### input + +- `lot_size = 0.01`: 注文ロット。 +- `spread_limit = 60`: 許容スプレッド(point)。 +- `magic_number = 10001`: EA識別用マジックナンバー。 +- `initial_order = 0`: 旧設定ファイルとの互換性維持用。値にかかわらず起動時はH4/H1を再生成し、H4再読込までは技術エラー停止状態を維持する。 +- `use_m15_entry_filter = true`: M15確定足によるエントリータイミング確認を使用するか。 +- `m15_entry_zone_atr_multiplier = 1.50`: M15平均レンジに対する候補価格接近許容倍率。 +- `use_m15_imbalance_confirmation = true`: T1/T3の順張り候補に対して、M15確定足の初動確認を追加するか。 +- `m15_imbalance_avg_body_period = 20`: M15初動確認で使う平均実体計算期間。 +- `m15_imbalance_sensitivity = 2.0`: M15初動確認で現在足実体が平均実体を上回る必要倍率。 +- `m15_imbalance_min_avg_body_points = 1.0`: M15平均実体の最小値(point)。 +- `use_m15_imbalance_debug_log = false`: M15初動確認の詳細ログを出すか。 +- `input_entry_max_candidate_age_minutes = 120`: H1候補価格を新規発注に使用できる最大経過分数。古い候補でM15条件が後から整った場合の遅延エントリーを抑止する。 +- `input_position_limit = 10`: 自EAの未約定注文 + ポジション数の実運用上限。内部上限 `POSITION_LIMIT` を超える指定は丸める。 +- `use_split_entry_zone = false`: H1予測ゾーンを使った分割エントリーを有効化するか。 +- `split_entry_count = 3`: 分割エントリー本数。1〜10に丸めて使用する。 +- `split_lot_mode = SPLIT_LOT_TOTAL`: 分割ロット配分モード。`SPLIT_LOT_TOTAL` は総ロットをN分割、`SPLIT_LOT_FIXED` は各注文に固定ロットを使う。 +- `split_total_lot_size = 0.09`: 総量分割モードの合計ロット。 +- `split_fixed_lot_size = 0.01`: 固定ロットモードの1注文ロット。 +- `cancel_old_split_pending_on_new_zone = true`: 新しいH1ゾーン読込時に、同じEAの古い分割pending注文を取消するか。 +- `input_cancel_retry_cooldown_seconds = 60`: pending注文の取消に失敗した場合、同一ticketへ取消要求を再送するまでの待機秒数。1〜3600秒に丸めて使用する。 +- `input_sltp_manager_enabled = false`: UI連動の `CSLTPManager` による既存ポジションSL管理を有効化するか。 +- `input_sltp_show_panel = true`: チャート上にSLTP操作パネルを表示するか。 +- `input_sltp_use_breakeven = false`: 通常ブレークイーブンを有効化するか。 +- `input_sltp_breakeven_trigger_pips = 30.0`: 通常ブレークイーブンを開始する含み益pips。 +- `input_sltp_breakeven_buffer_pips = 3.0`: 通常ブレークイーブン時に建値から利益方向へずらすpips。 +- `input_sltp_use_elapsed_breakeven = false`: 保有時間ベースのブレークイーブンを有効化するか。 +- `input_sltp_elapsed_breakeven_hours = 4.0`: 保有時間ベースのブレークイーブンを許可するまでの保有時間。 +- `input_sltp_elapsed_breakeven_buffer_pips = 3.0`: 保有時間ベースのブレークイーブン時に建値から利益方向へずらすpips。 +- `input_sltp_use_active_trailing = false`: アクティブトレーリングを有効化するか。 +- `input_sltp_active_breakeven_pips = 30.0`: アクティブトレーリング開始pips。 +- `input_sltp_active_stop_loss_offset_pips = 5.0`: 開始時に建値から固定する利益pips。 +- `input_sltp_active_step_trigger_pips = 10.0`: SLを1段進めるために必要な追加利益pips。 +- `input_sltp_active_step_move_pips = 5.0`: 1段ごとにSLを利益方向へ動かすpips。 +- `input_sltp_use_tp_progress_stop = false`: TP進捗率に応じたSL固定を有効化するか。 +- `input_sltp_tp_progress_trigger_percent = 70.0`: TP進捗率SLを開始する進捗率。 +- `input_sltp_tp_progress_sl_lock_percent = 30.0`: 建値からTPまでの距離のうちSLで固定する割合。 +- `input_sltp_use_high_volatility_limit = false`: 短時間急変時のSL引き締めを有効化するか。 + +### 主要定数 + +- `POSITION_LIMIT = 48`: 自EAの未約定注文 + ポジション数の上限。 +- `ENTRY_H1_LIMIT = 2`: 未約定注文の有効期限(H1本数)。 +- `CLOSE_H1_LIMIT = 12`: ポジションの時間制限クローズ(H1本数)。 +- `HISTORY_BARS = 72`: Pythonへ渡すOHLC本数。 +- `OHLC_START_SHIFT = 1`: 確定足からOHLCを取得するためのシフト。 +- `M15_CONFIRM_BARS = 30`: M15平均レンジ計算に使う足数。 +- `ENTRY_RETRY_SECONDS = 60`: エントリー判定リトライ間隔。 +- `ENTRY_RETRY_LIMIT = 10`: H1候補価格に対する最大リトライ回数。 +- `TARGET_SIZE = 13`: `target_prices.txt` から読み込む数値数。 +- `TARGET_ZONE_SCHEMA_VERSION = 2`: `target_zones.txt` の形式バージョン。 +- `PYTHON_TIMEOUT_SECONDS = 600`: Python完了待ちの監視上限。 + +### Python側エントリー候補ガード + +- H1候補価格生成後、Python側で現在価格から遠すぎる `entry` を無効化する。 +- 距離上限は戦略別に計算し、T1/T3のStop系は `max(H1 EATR * 1.50, 5.00)`、T2/T4のLimit系は `max(H1 EATR * 1.00, 5.00)` とする。上限を超えた戦略は `0.00,0.00,0.00` に補正する。 +- このガードは、深い指値や遠いブレイク待ちがM15確認後に遅れて発注されることを抑えるための後処理である。 + +## 4. ファイル構成 + +- `Experts/MyProject/HIT-EA_refactor_ver6.mq5`: EAエントリーポイント。`OnInit`, `OnDeinit`, `OnTick`、input、グローバル状態、DLL importを保持する。 +- `Include/MyLib/Common/HITRuntimeController.mqh`: ティック処理、H4/H1/M15更新制御、ステータス表示、スプレッド判定。 +- `Include/MyLib/Signals/HITEntrySignal.mqh`: エントリー前提条件、注文タイプ別価格判定、M15フィルタ、リトライ状態更新。 +- `Include/MyLib/Common/HITExternalProcess.mqh`: done/runningファイル、外部プロセスハンドル、PID復元、終了コード確認、タイムアウト復旧。 +- `Include/MyLib/Signals/HITPythonSignalGateway.mqh`: OHLC CSV出力、Pythonバッチ起動、`trend_state.txt` / `target_prices.txt` / `target_zones.txt` 読込。 +- `Include/MyLib/Trading/HITTradeManager.mqh`: 注文送信、執行ポリシー、有効期限設定、注文/ポジション数集計、期限切れ取消/クローズ。 +- `Include/MyLib/Trading/SLTPManager.mqh`: 既存ポジションに対するブレークイーブン、時間経過BE、アクティブトレーリング、TP進捗SL、急変時SL引き締めを管理する。 +- `Include/MyLib/Panel/SLTPManagerPanel.mqh`: `CSLTPManager` の各設定をチャート上で切り替え、数値入力を検証してEAへ反映する操作パネル。 + +## 5. Python連携ファイル + +EAは `OnInit()` で `_Symbol` と `magic_number` から `HIT__` 形式の接頭辞を作り、MT5 `Files` 配下の連携ファイル名へ付与する。例: `HIT_GOLD_10001_ohlc_H4.csv`。batには同じ接頭辞を第1引数で渡し、bat側が `MT5_EA_FILE_PREFIX` としてPythonへ引き継ぐ。第2引数には `_Symbol` の `SYMBOL_DIGITS` を渡し、bat側が `MT5_PRICE_DIGITS` としてPythonへ引き継ぐ。 + +手動実行などで接頭辞が指定されない場合、Pythonは従来どおり `ohlc_H4.csv` などの旧ファイル名を使う。 + +### H4トレンド判定 + +- MQL5出力: `_ohlc_H4.csv` +- Python出力: `_trend_state.txt` +- 完了フラグ: `_process_done_trend.txt` +- 実行中フラグ: `_process_running_trend.txt` +- 起動バッチ: `\MQL5\python_for_ea\bat\get_trend_reply.bat` + +### H1エントリー価格生成 + +- MQL5出力: `_ohlc_H1.csv` +- Python出力: `_target_prices.txt`, `_target_zones.txt` +- 完了フラグ: `_process_done_entry.txt` +- 実行中フラグ: `_process_running_entry.txt` +- 起動バッチ: `\MQL5\python_for_ea\bat\get_entry_reply.bat` + +EAは `OnInit()` で `TerminalInfoString(TERMINAL_DATA_PATH)` から `MQL5\python_for_ea` を解決し、各batファイルの絶対パスを組み立てる。batファイルは自身の位置からPythonプロジェクトルートを解決するため、`C:\ea_py` には依存しない。 + +`target_prices.txt` は13行構成とする。 + +```text +1行目: res_chk +2-4行目: T1 Buy Stop の entry/tp/sl +5-7行目: T2 Buy Limit の entry/tp/sl +8-10行目: T3 Sell Stop の entry/tp/sl +11-13行目: T4 Sell Limit の entry/tp/sl +``` + +`target_zones.txt` は分割エントリー用の7行構成とする。 + +```text +1行目: schema_version(2) +2行目: res_chk +3行目: candidate_id(H1確定足時刻由来) +4行目: T1 Buy Stop の strategy, zone_low, zone_high, tp, sl +5行目: T2 Buy Limit の strategy, zone_low, zone_high, tp, sl +6行目: T3 Sell Stop の strategy, zone_low, zone_high, tp, sl +7行目: T4 Sell Limit の strategy, zone_low, zone_high, tp, sl +``` + +Pythonは `target_prices.txt` と `target_zones.txt` の両方を書き終えてから `process_done_entry.txt` を作成する。`target_zones.txt` の価格小数桁は `MT5_PRICE_DIGITS` に合わせ、未指定時は安全側で5桁を使う。分割エントリーが無効な場合、EAは従来どおり `target_prices.txt` を使用する。 + +## 6. エントリー条件 + +新規エントリーは、以下をすべて満たす場合のみ行う。 + +- スプレッドが `spread_limit` 以下。 +- H4トレンド判定結果が完了済みで、`trend_state.txt` を読み込める。 +- H1候補価格生成が完了済みで、`target_prices.txt` を読み込める。 +- `res_chk = 1`。 +- H1候補価格が、元になったH1確定足時刻から `ENTRY_H1_LIMIT` 内で期限切れではない。 +- H1候補価格が、元になったH1確定足時刻から `input_entry_max_candidate_age_minutes` 分を超えていない。 +- 対象注文タイプの `entry/tp/sl` がすべて `0.0` より大きい。 +- H4 `market_state` に対して注文タイプが許可されている。 +- 注文タイプごとの価格整合条件とブローカー最小距離制約を満たす。 +- `use_m15_entry_filter = true` の場合、T2/T4はM15確定足の方向・反発・接近条件を満たす。T1/T3は `use_m15_imbalance_confirmation = true` の場合にM15初動確認を満たす。 +- 自EAの注文 + ポジション数が `input_position_limit` 未満。 +- 分割エントリー有効時は `target_zones.txt` の `res_chk = 1`、予測ゾーンの価格整合、分割ロットの `SYMBOL_VOLUME_MIN/MAX/STEP` 適合、同一 `candidate_id` + 注文タイプ + slot の重複注文/ポジション不存在を満たす。 + +### market_state と注文タイプ + +- `0 MARKET_LOW_VOL_RANGE`: T2 Buy Limit / T4 Sell Limit を許可。 +- `1 MARKET_HIGH_VOL_RANGE`: T2 Buy Limit / T4 Sell Limit を許可。 +- `2 MARKET_LOW_VOL_UP`: T1 Buy Stop / T2 Buy Limit を許可。 +- `3 MARKET_HIGH_VOL_UP`: T1 Buy Stop / T2 Buy Limit を許可。 +- `4 MARKET_LOW_VOL_DOWN`: T3 Sell Stop / T4 Sell Limit を許可。 +- `5 MARKET_HIGH_VOL_DOWN`: T3 Sell Stop / T4 Sell Limit を許可。 +- `6 MARKET_TECHNICAL_ERROR_STOP`: 新規注文を停止。 + +H4 `market_state` の再読込後は、更新後の状態で許可されない注文タイプの既存pending注文を取消する。`6 MARKET_TECHNICAL_ERROR_STOP` の場合は対象EAのpending注文をすべて取消する。 + +### 注文タイプごとの価格整合条件 + +- T1 Buy Stop: `Ask < entry`, `tp > entry`, `sl < entry` +- T2 Buy Limit: `Ask > entry`, `tp > entry`, `sl < entry` +- T3 Sell Stop: `Bid > entry`, `tp < entry`, `sl > entry` +- T4 Sell Limit: `Bid < entry`, `tp < entry`, `sl > entry` + +## 7. エグジット条件 + +- 未約定注文は、コメントからH1候補時刻を復元できる場合はその時刻から、復元できない旧注文は注文作成時刻から `ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1)` 以上経過した場合に取消する。 +- 保有ポジションは、建玉時刻から `CLOSE_H1_LIMIT * PeriodSeconds(PERIOD_H1)` 以上経過した場合にクローズする。 +- 時間制限処理は `OnTick` の早い段階で実行し、Python完了待ちやスプレッド判定に依存させない。 +- EA削除時は `OnDeinit()` で外部プロセス状態とSLTPパネルを解放し、`Comment("")` と `ChartRedraw(0)` によりチャート上のステータス表示を消去する。 +- `input_sltp_manager_enabled` またはパネルの `Manager` がONの場合、時間制限処理後に `CSLTPManager::ManagePositions()` と `HighVolatilityLimit()` を実行する。これにより、Python結果待ちや新規注文停止中でも既存ポジションのSL保護を継続する。 +- 通常ブレークイーブンとアクティブトレーリングは同時有効化しない。パネル上では片方をONにするともう片方をOFFにし、`CSLTPManager::ValidateSettings()` でも排他条件を検証する。 + +## 7.1 SLTP操作パネル + +- パネルは `input_sltp_show_panel = true` の場合に作成され、チャート右上に表示される。 +- パネル生成に失敗した場合はログへ出力し、EA本体はinputで反映済みのSLTP設定のまま継続する。 +- `Manager` ボタンでSLTP管理全体のON/OFFを切り替える。OFFの場合、パネル値は保持するが `OnTick` でSL変更は行わない。 +- `BreakEven`, `Elapsed BE`, `Active Trail`, `TP Progress`, `High Vol` ボタンで各機能を切り替える。 +- 数値入力欄は `APPLY` 押下時に範囲検証される。範囲外または数値以外の場合は `MessageBox` とログで通知し、直前の有効値へ戻す。 +- `APPLY` 成功時のみEA内部の `CSLTPManager` 設定へ反映する。反映後は `ValidateSettings()` を通し、失敗した場合はSLTP管理を停止してログへ理由を出力する。 + +## 8. リスク管理 + +- 注文/ポジション管理対象は `_Symbol` と `magic_number` が一致するものに限定する。 +- `input_position_limit` は口座全体ではなく、自EA対象分のみを数える。内部の絶対上限は `POSITION_LIMIT` とする。 +- 送信時は `SYMBOL_FILLING_MODE` を参照し、IOC、FOK、RETURNの順で利用可能な執行ポリシーを選択する。 +- 通常注文と分割注文の送信前に、ロットが `SYMBOL_VOLUME_MIN/MAX/STEP` を満たすことを検証する。 +- ペンディング注文には、ブローカーが対応する場合にH1候補時刻を起点とするサーバー側の期限を設定する。発注時点で期限切れなら送信しない。 +- `OrderSend` の戻り値と `MqlTradeResult.retcode` を確認し、失敗時は `GetLastError()` またはretcodeをログへ出力する。 +- 通常注文と分割注文のコメントには有効な `candidate_id` を入れ、候補時刻起点の期限管理に使用する。 +- 分割エントリーの注文コメントには注文タイプとslot番号も入れ、同一戦略slotの二重発注を抑止する。 +- `cancel_old_split_pending_on_new_zone = true` の場合、新しい有効 `candidate_id` を読んだ時は旧candidateの分割pending注文を取消し、`target_zones.txt` が無効または停止値の場合は既存の分割pending注文をすべて取消する。 +- H4状態更新後は、更新後の `market_state` と矛盾する既存pending注文を取消する。 +- pending取消に失敗したticketは `input_cancel_retry_cooldown_seconds` の間再送せず、毎tickの過剰な取引サーバー要求を防止する。 +- SLTP管理は `_Symbol` と `magic_number` が一致する既存ポジションのみを対象とし、TPは既存値を維持する。 +- SL変更は既存SLより利益保護方向へ改善する場合だけ実行し、ブローカーのstop level / freeze levelを満たさない候補は送信しない。 +- `HighVolatilityLimit()` はBUYをBid、SELLをAsk基準で評価し、GOLD/XAUUSDはサフィックス付きシンボルでも1 pip = 0.1として扱う。 + +## 9. 異常系の扱い + +- `trend_state.txt` が存在しない、読み込めない、整数として厳格にパースできない、または `0..6` 以外の場合は `MARKET_TECHNICAL_ERROR_STOP` として扱う。 +- `target_prices.txt` が存在しない、読み込めない、13行未満、またはいずれかの行が数値として厳格にパースできない場合は `res_chk = 0` として扱う。 +- 分割エントリー有効時に `target_zones.txt` が存在しない、schema不一致、7行未満、`res_chk != 1`、有効ゾーンの `candidate_id` が厳格な12桁時刻ではない、またはいずれかの数値行を厳格にパースできない場合は新規注文を停止する。 +- Pythonプロセスが異常終了した場合、終了コードをログへ出力し、次回トリガーで再実行できる状態に戻す。 +- Pythonプロセスが `PYTHON_TIMEOUT_SECONDS` を超えても実行中の場合、`taskkill /T /F` でcmd/bat配下のプロセスツリーを終了し、done/runningファイルを整理して次回トリガーで再実行できる状態に戻す。 +- `running` ファイルだけが残っている場合は、PIDからプロセス復元を試みる。復元できずタイムアウト済みなら古いマーカーとして削除する。 + +## 10. 変更履歴 + +### 2026-05-31 + +- 起動時は `initial_order` の値にかかわらずH4/H1結果を再生成し、H4再読込までは技術エラー停止値を維持するfail-closed初期化へ変更した。 +- H4 `market_state` 更新後に、更新状態と矛盾する既存pending注文を取消する仕様を追加した。 +- pending取消失敗時にticket単位のクールダウンを設け、毎tickの取消再送による取引サーバーへの過剰リクエストを抑止した。 +- 分割slot識別子へ注文タイプを追加し、同一 `candidate_id` の異なる戦略がslotを共有しても衝突しないようにした。 +- 通常注文と分割注文のコメントへ有効な `candidate_id` を保持し、pending期限をH1候補時刻起点で管理するようにした。 +- 有効な `target_zones.txt` の `candidate_id` は厳格な12桁数字時刻を要求し、不正値は安全側で停止するようにした。 + +### 2026-05-30 + +- 同一Terminal内の複数EA/複数シンボルでPython連携ファイルが衝突しないよう、`HIT__` 接頭辞をMT5/Python双方のファイル名へ適用する仕様に更新した。 +- T1/T3の順張り候補でも `use_m15_imbalance_confirmation = true` の場合はM15初動確認を必須とする仕様に更新した。 +- `trend_state.txt`、`target_prices.txt`、`target_zones.txt` の数値読込を厳格化し、不正値は安全側の停止値として扱う仕様を追加した。 +- `target_zones.txt` が無効または停止値の場合に、古い分割pending注文を全取消できる仕様を追加した。 +- 現行コードに合わせて、`input_entry_max_candidate_age_minutes`、`ENTRY_H1_LIMIT`、SLTPバッファ、Python側距離ガードの既定値を修正した。 +- Pythonタイムアウト時に実行中プロセスツリーを強制終了して再試行できる仕様を追加した。 +- H1候補の鮮度判定をPython結果の読込時刻ではなく、`candidate_id` から復元したH1確定足時刻基準へ変更した。 +- `MT5_PRICE_DIGITS` によりPython側の `target_zones.txt` 出力桁数をシンボル桁数へ合わせる仕様を追加した。 +- 通常注文ロットの `SYMBOL_VOLUME_MIN/MAX/STEP` 検証、OHLC CSVのNaN/inf・OHLC整合性検証、SELL高ボラSLのAsk基準評価、GOLD/XAUUSDサフィックス対応を追加した。 + +### 2026-05-27 + +- Python補助アプリの配置を `C:\ea_py` からMT5データフォルダ配下の `MQL5\python_for_ea` へ移行する仕様に変更した。 +- EA側は `TerminalInfoString(TERMINAL_DATA_PATH)` からbatパスを組み立て、外部プロセスの作業ディレクトリはbatファイルの配置から導出するようにした。 +- Python側は `MQL5\python_for_ea` の親にある `MQL5\Files` を既定の連携先として自動検出し、必要に応じて `MT5_FILES_DIR` または `MT5_DATA_PATH` で上書きできる仕様にした。 + +### 2026-05-24 + +- Python側で `target_zones.txt` を追加出力し、既存 `target_prices.txt` と両方を書き終えてから `process_done_entry.txt` を作成する仕様にした。 +- EA側に `use_split_entry_zone`、`split_entry_count`、`split_lot_mode`、`split_total_lot_size`、`split_fixed_lot_size`、`input_position_limit` を追加した。 +- 分割エントリー有効時は、H1予測ゾーンをN本に分割し、総ロット分割または固定ロットでpending注文を出せるようにした。 +- `candidate_id` + slot番号で分割注文を識別し、同一slotの二重発注を抑止する仕様を追加した。 + +### 2026-05-13 + +- Python側にH1 EATRベースの候補距離ガードを追加し、現在価格から遠すぎる候補をMT5へ渡す前に無効化する仕様を追加。 +- EA側に `input_entry_max_candidate_age_minutes` を追加し、M15確認が遅れて整った古いH1候補で新規発注しないようにした。 +- エントリー見送りログへH1候補の経過秒数を追加し、遅延・M15未確認・価格不整合の原因を追跡しやすくした。 + +### 2026-05-05 + +- EA削除後にチャート左上のステータスコメントが残らないよう、`OnDeinit()` で `Comment("")` と `ChartRedraw(0)` を実行する終了処理を追加。 +- `HIT-EA_refactor_ver5.mq5` に `CSLTPManager` と専用チャートパネル `SLTPManagerPanel.mqh` を接続し、UIからブレークイーブン、時間経過BE、アクティブトレーリング、TP進捗SL、急変時SL引き締めを操作できるようにした。 +- SLTP管理を `OnTick` の時間制限処理直後に実行し、Python結果待ちやスプレッド判定に依存せず既存ポジション保護を継続する仕様を追加。 +- SLTPパネル用inputと、通常ブレークイーブン/アクティブトレーリングの排他検証、`APPLY` 時の数値検証を追加。 +- `HIT-EA_refactor_ver5.mq5` の関数群を用途別 `.mqh` に分割。 +- `OnInit`, `OnDeinit`, `OnTick`、input、グローバル状態はEA本体に残し、動作ロジックは `Include/MyLib/` 配下へ移動。 +- 現行コードに合わせて、`CreateProcessW` ベースのPythonプロセス監視、M15フィルタ、`market_state` 0..6、`spread_limit = 60` を仕様へ反映。 + +### 2026-05-02 + +- `res_chk=0` 時にエントリー送信を抑止する分岐を追加。 +- 時間制限処理を、Python完了待ち・スプレッド制限より前に移動。 +- `initial_order` の初回処理フラグをH4用・H1用に分離。 +- OHLC取得を `CopyRates` 中心に変更し、確定足のみをPythonへ渡す仕様に変更。 diff --git a/python_for_ea/src/ea_py/io/debug_logs.py b/python_for_ea/src/ea_py/io/debug_logs.py index 8c0ea90..b329513 100644 --- a/python_for_ea/src/ea_py/io/debug_logs.py +++ b/python_for_ea/src/ea_py/io/debug_logs.py @@ -103,3 +103,31 @@ def append_debug_entry( file.write("---- REASON START ----\n") file.write((reason_text or "").strip() + "\n") file.write("---- REASON END ----\n\n") + + +def append_debug_entry_stop( + *, + path: Path, + stage: str, + reason: str, + trend_state: int, + selected_strategies: Sequence[int] = (), + candidate_id: str = "", + current_price: float | None = None, + imbalance_summary: str = "", +) -> None: + """H1候補生成が安全側停止値へ倒れた理由を追記する。""" + with path.open("a", encoding="utf-8") as file: + file.write("=" * 60 + "\n") + file.write(f"DEBUG TIME : {now_str()}\n") + file.write("RESULT : SAFE_STOP\n") + file.write(f"STAGE : {stage}\n") + file.write(f"REASON : {reason}\n") + file.write(f"MARKET_STATE(H4) : {trend_state} ({MARKET_STATE_LABELS.get(trend_state, 'UNKNOWN')})\n") + file.write(f"SELECTED_STRATEGY : {','.join(str(x) for x in selected_strategies) or '-'}\n") + file.write(f"CANDIDATE_ID : {candidate_id or '-'}\n") + if current_price is not None: + file.write(f"CURRENT PRICE : {current_price:.2f}\n") + if imbalance_summary: + file.write(f"IMBALANCE(H1) : {imbalance_summary}\n") + file.write("=" * 60 + "\n\n") diff --git a/python_for_ea/src/ea_py/io/debug_logs.py.bak_codex_20260601_debug_entry b/python_for_ea/src/ea_py/io/debug_logs.py.bak_codex_20260601_debug_entry new file mode 100644 index 0000000..8c0ea90 --- /dev/null +++ b/python_for_ea/src/ea_py/io/debug_logs.py.bak_codex_20260601_debug_entry @@ -0,0 +1,105 @@ +"""デバッグ理由ログをMT5 Files配下へ追記する。""" + +from __future__ import annotations + +from datetime import datetime +from pathlib import Path +from typing import Sequence + +from ea_py.constants import MARKET_STATE_LABELS + + +def now_str() -> str: + """デバッグログ用の現在時刻文字列を返す。""" + return datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + +def append_debug_trend( + *, + path: Path, + model: str, + reasoning_effort: str, + max_output_tokens: int, + api_diagnostics: str, + current_price: float, + numeric_summary: str, + direction_numeric: str, + market_state: int, + classification_reason: str, + reason_text: str, +) -> None: + """H4トレンド判定の数値要約、方向出力、分類理由を追記する。""" + with path.open("a", encoding="utf-8") as file: + file.write("=" * 60 + "\n") + file.write(f"DEBUG TIME : {now_str()}\n") + file.write(f"MODEL : {model}\n") + file.write(f"REASONING : {reasoning_effort}\n") + file.write(f"MAX TOKENS : {max_output_tokens}\n") + file.write(f"CURRENT PRICE : {current_price:.2f}\n") + file.write(f"MARKET STATE : {market_state} ({MARKET_STATE_LABELS.get(market_state, 'UNKNOWN')})\n") + file.write("=" * 60 + "\n\n") + file.write("---- API DIAGNOSTICS START ----\n") + file.write((api_diagnostics or "").strip() + "\n") + file.write("---- API DIAGNOSTICS END ----\n\n") + file.write("---- NUMERIC SUMMARY START ----\n") + file.write((numeric_summary or "").strip() + "\n") + file.write("---- NUMERIC SUMMARY END ----\n\n") + file.write("---- DIRECTION NUMERIC (0/1/2) START ----\n") + file.write((direction_numeric or "").strip() + "\n") + file.write("---- DIRECTION NUMERIC (0/1/2) END ----\n\n") + file.write("---- MARKET STATE CLASSIFICATION START ----\n") + file.write((classification_reason or "").strip() + "\n") + file.write("---- MARKET STATE CLASSIFICATION END ----\n\n") + file.write("---- REASON START ----\n") + file.write((reason_text or "").strip() + "\n") + file.write("---- REASON END ----\n\n") + + +def append_debug_entry( + *, + path: Path, + model: str, + reasoning_effort: str, + max_output_tokens: int, + api_diagnostics: str, + timeframe: str, + current_price: float, + trend_state: int, + selected_strategies: Sequence[int], + imbalance_summary: str, + numeric_summary: str, + numeric_lines: str, + post_filter_summary: str, + sanitized_numeric_list: Sequence[int | float] | None, + reason_text: str, +) -> None: + """H1エントリー候補生成の数値行と理由を追記する。""" + with path.open("a", encoding="utf-8") as file: + file.write("=" * 60 + "\n") + file.write(f"DEBUG TIME : {now_str()}\n") + file.write(f"MODEL : {model}\n") + file.write(f"REASONING : {reasoning_effort}\n") + file.write(f"MAX TOKENS : {max_output_tokens}\n") + file.write(f"TIMEFRAME : {timeframe}\n") + file.write(f"MARKET_STATE(H4) : {trend_state} ({MARKET_STATE_LABELS.get(trend_state, 'UNKNOWN')})\n") + file.write(f"SELECTED_STRATEGY : {','.join(str(x) for x in selected_strategies)}\n") + file.write(f"IMBALANCE(H1) : {imbalance_summary}\n") + file.write(f"CURRENT PRICE : {current_price:.2f}\n") + file.write("=" * 60 + "\n\n") + file.write("---- API DIAGNOSTICS START ----\n") + file.write((api_diagnostics or "").strip() + "\n") + file.write("---- API DIAGNOSTICS END ----\n\n") + file.write("---- NUMERIC SUMMARY START ----\n") + file.write((numeric_summary or "").strip() + "\n") + file.write("---- NUMERIC SUMMARY END ----\n\n") + file.write("---- GPT NUMERIC LINES START ----\n") + file.write((numeric_lines or "").strip() + "\n") + file.write("---- GPT NUMERIC LINES END ----\n\n") + file.write("---- POST FILTER START ----\n") + file.write((post_filter_summary or "").strip() + "\n") + if sanitized_numeric_list is not None: + file.write("sanitized_numeric_list=" + ",".join(str(x) for x in sanitized_numeric_list) + "\n") + file.write("---- POST FILTER END ----\n\n") + file.write("---- REASON START ----\n") + file.write((reason_text or "").strip() + "\n") + file.write("---- REASON END ----\n\n") diff --git a/python_for_ea/src/ea_py/pipelines/entry_pipeline.py b/python_for_ea/src/ea_py/pipelines/entry_pipeline.py index 13c7c6e..9f0edc6 100644 --- a/python_for_ea/src/ea_py/pipelines/entry_pipeline.py +++ b/python_for_ea/src/ea_py/pipelines/entry_pipeline.py @@ -26,7 +26,7 @@ from ea_py.constants import ( TECHNICAL_ERROR_STOP, USE_IMBALANCE_FILTER, ) -from ea_py.io.debug_logs import append_debug_entry +from ea_py.io.debug_logs import append_debug_entry, append_debug_entry_stop from ea_py.io.mt5_files import read_int_file, write_results_then_done from ea_py.io.ohlc_csv import read_ohlc_csv from ea_py.market.volatility import summarize_ohlc @@ -48,7 +48,7 @@ from ea_py.market.imbalance import ( format_imbalance_summary, ) from ea_py.openai_client import call_responses_api, create_openai_client -from ea_py.paths import build_entry_paths +from ea_py.paths import EntryPaths, build_entry_paths from ea_py.prompts.entry_prompt import ( build_imbalance_guidance, build_caution_block, @@ -67,6 +67,41 @@ logger = logging.getLogger(__name__) TREND_STOP_STRATEGIES = frozenset({1, 3}) +def exception_summary(error: Exception) -> str: + """秘密情報を追加せず、例外型とメッセージを1行へ丸める。""" + return " ".join(f"{type(error).__name__}: {error}".splitlines()).strip() + + +def write_safe_stop( + *, + paths: EntryPaths, + stage: str, + reason: str, + trend_state: int, + selected_strategies: Sequence[int] = (), + candidate_id: str = "", + current_price: float | None = None, + imbalance_summary: str = "", +) -> None: + """停止値を出力し、ライブ診断用の理由ログも可能な限り残す。""" + if DEBUG_PRINT: + try: + append_debug_entry_stop( + path=paths.debug_reason, + stage=stage, + reason=reason, + trend_state=trend_state, + selected_strategies=selected_strategies, + candidate_id=candidate_id, + current_price=current_price, + imbalance_summary=imbalance_summary, + ) + except Exception: + logger.exception("debug_entry.txt safe-stop write error") + + write_entry_output(stop_numeric_list()) + + def write_entry_output(numeric_list: NumericList, zone_text: str | None = None) -> None: """target_prices.txtとtarget_zones.txtを書き、完了後にprocess_done_entry.txtを作る。""" paths = build_entry_paths() @@ -124,19 +159,37 @@ def run_pipeline() -> None: trend_state, MARKET_STATE_LABELS.get(trend_state, "UNKNOWN"), ) - write_entry_output(stop_numeric_list()) + write_safe_stop( + paths=paths, + stage="market_state", + reason="H4 market_state blocks new entries.", + trend_state=trend_state, + selected_strategies=selected_strategies, + ) return try: ohlc_all = read_ohlc_csv(paths.input_csv) - except Exception: + except Exception as error: logger.exception("CSV 読み込みエラー") - write_entry_output(stop_numeric_list()) + write_safe_stop( + paths=paths, + stage="ohlc_csv", + reason=exception_summary(error), + trend_state=trend_state, + selected_strategies=selected_strategies, + ) return if len(ohlc_all) < CANDLE_LONG: logger.error("データ本数不足: len(ohlc)=%s (need >= %s)", len(ohlc_all), CANDLE_LONG) - write_entry_output(stop_numeric_list()) + write_safe_stop( + paths=paths, + stage="ohlc_bars", + reason=f"Insufficient H1 bars: actual={len(ohlc_all)} required={CANDLE_LONG}.", + trend_state=trend_state, + selected_strategies=selected_strategies, + ) return ohlc_short = ohlc_all[-CANDLE_SHORT:] @@ -164,7 +217,16 @@ def run_pipeline() -> None: MARKET_STATE_LABELS.get(trend_state, "UNKNOWN"), imbalance_summary, ) - write_entry_output(stop_numeric_list()) + write_safe_stop( + paths=paths, + stage="h1_imbalance", + reason="H1 imbalance conflicts with the allowed H4 strategies.", + trend_state=trend_state, + selected_strategies=selected_strategies, + candidate_id=candidate_id, + current_price=current_price, + imbalance_summary=imbalance_summary, + ) return try: @@ -186,9 +248,18 @@ def run_pipeline() -> None: png_file_to_data_url(paths.tmp_short_chart), png_file_to_data_url(paths.tmp_long_chart), ] - except Exception: + except Exception as error: logger.exception("画像生成エラー") - write_entry_output(stop_numeric_list()) + write_safe_stop( + paths=paths, + stage="chart_render", + reason=exception_summary(error), + trend_state=trend_state, + selected_strategies=selected_strategies, + candidate_id=candidate_id, + current_price=current_price, + imbalance_summary=imbalance_summary, + ) return numeric_summary = build_numeric_summary( @@ -219,9 +290,18 @@ def run_pipeline() -> None: try: config = load_runtime_config(debug_print=DEBUG_PRINT) - except RuntimeError: + except RuntimeError as error: logger.exception("OpenAI 設定エラー") - write_entry_output(stop_numeric_list()) + write_safe_stop( + paths=paths, + stage="openai_config", + reason=exception_summary(error), + trend_state=trend_state, + selected_strategies=selected_strategies, + candidate_id=candidate_id, + current_price=current_price, + imbalance_summary=imbalance_summary, + ) return system_content = build_system_content_block_debug() if config.debug_print else build_system_content_block() @@ -248,11 +328,29 @@ def run_pipeline() -> None: api_diagnostics = gpt_result.diagnostics.to_log_text() if not gpt_result.diagnostics.is_completed(): logger.error("OpenAI response incomplete or failed: %s", api_diagnostics) - write_entry_output(stop_numeric_list()) + write_safe_stop( + paths=paths, + stage="openai_response", + reason=api_diagnostics, + trend_state=trend_state, + selected_strategies=selected_strategies, + candidate_id=candidate_id, + current_price=current_price, + imbalance_summary=imbalance_summary, + ) return - except Exception: + except Exception as error: logger.exception("OpenAI APIエラー") - write_entry_output(stop_numeric_list()) + write_safe_stop( + paths=paths, + stage="openai_api", + reason=exception_summary(error), + trend_state=trend_state, + selected_strategies=selected_strategies, + candidate_id=candidate_id, + current_price=current_price, + imbalance_summary=imbalance_summary, + ) return if config.debug_print: diff --git a/python_for_ea/src/ea_py/pipelines/entry_pipeline.py.bak_codex_20260601_debug_entry b/python_for_ea/src/ea_py/pipelines/entry_pipeline.py.bak_codex_20260601_debug_entry new file mode 100644 index 0000000..13c7c6e --- /dev/null +++ b/python_for_ea/src/ea_py/pipelines/entry_pipeline.py.bak_codex_20260601_debug_entry @@ -0,0 +1,305 @@ +"""H1 OHLCとH4 market_stateからtarget_pricesを生成するパイプライン。""" + +from __future__ import annotations + +import logging +from collections.abc import Sequence + +from ea_py.charting.candlestick import ohlc_to_candlestick_png_file, png_file_to_data_url +from ea_py.config import load_runtime_config +from ea_py.constants import ( + CANDLE_LONG, + CANDLE_SHORT, + DEBUG_PRINT, + ENTRY_MAX_DISTANCE_LIMIT_EATR_MULTIPLIER, + ENTRY_MAX_DISTANCE_MIN_PRICE, + ENTRY_MAX_DISTANCE_STOP_EATR_MULTIPLIER, + ENTRY_MAX_OUTPUT_TOKENS, + ENTRY_TIMEFRAME, + IMBALANCE_AVG_BODY_PERIOD, + IMBALANCE_MIN_AVG_BODY_SIZE, + IMBALANCE_SENSITIVITY, + INSTRUMENT, + MARKET_STATE_LABELS, + MIN_ENTRY_REWARD_RISK_RATIO, + MT_ENCODING, + TECHNICAL_ERROR_STOP, + USE_IMBALANCE_FILTER, +) +from ea_py.io.debug_logs import append_debug_entry +from ea_py.io.mt5_files import read_int_file, write_results_then_done +from ea_py.io.ohlc_csv import read_ohlc_csv +from ea_py.market.volatility import summarize_ohlc +from ea_py.market.target_prices import ( + stop_numeric_list, + strategies_by_trend, +) +from ea_py.market.target_zones import ( + build_candidate_id, + format_target_zones, + parse_json_to_entry_zones, + sanitize_entry_zones, + stop_entry_zones, + zones_to_numeric_list, +) +from ea_py.market.imbalance import ( + adjust_strategies_for_imbalance, + detect_imbalance_signal, + format_imbalance_summary, +) +from ea_py.openai_client import call_responses_api, create_openai_client +from ea_py.paths import build_entry_paths +from ea_py.prompts.entry_prompt import ( + build_imbalance_guidance, + build_caution_block, + build_common_rules_block, + build_entry_response_text_format, + build_header, + build_market_state_guidance, + build_numeric_summary, + build_system_content_block, + build_system_content_block_debug, +) +from ea_py.types import NumericList + +logger = logging.getLogger(__name__) + +TREND_STOP_STRATEGIES = frozenset({1, 3}) + + +def write_entry_output(numeric_list: NumericList, zone_text: str | None = None) -> None: + """target_prices.txtとtarget_zones.txtを書き、完了後にprocess_done_entry.txtを作る。""" + paths = build_entry_paths() + content = "".join(f"{number}\n" for number in numeric_list) + zone_content = zone_text if zone_text is not None else format_target_zones(stop_entry_zones(), "0") + write_results_then_done( + result_files=[ + (paths.output_prices, content, MT_ENCODING), + (paths.output_zones, zone_content, MT_ENCODING), + ], + done_path=paths.done_entry, + ) + + +def build_entry_distance_limits(h1_eatr: float, selected_strategies: Sequence[int]) -> dict[int, float]: + """H1 EATRから戦略別のエントリー許容距離を作る。""" + limits: dict[int, float] = {} + for strategy in selected_strategies: + multiplier = ( + ENTRY_MAX_DISTANCE_STOP_EATR_MULTIPLIER + if strategy in TREND_STOP_STRATEGIES + else ENTRY_MAX_DISTANCE_LIMIT_EATR_MULTIPLIER + ) + limits[strategy] = max(h1_eatr * multiplier, ENTRY_MAX_DISTANCE_MIN_PRICE) + return limits + + +def run_pipeline() -> None: + """H1 OHLCとH4 market_stateからMT5向けtarget_pricesを生成する。 + + 実行契約: + - `trend_state.txt` のH4 `market_state` を読み、許可戦略だけを選ぶ。 + - `ohlc_H1.csv` から短期・中期チャート画像と数値要約を作る。 + - OpenAIには選択済み戦略の候補価格だけを依頼し、返却行を13値形式へ展開する。 + - GPT出力は `sanitize_numeric_list` で検証し、`target_prices.txt` を書いた後に + `process_done_entry.txt` を作成する。 + + 安全側フォールバック: + H4が技術エラー停止値、CSV読込失敗、H1データ不足、画像生成失敗、OpenAI設定/API失敗、 + GPT出力不正、または全候補が価格条件違反の場合は13値すべてを停止値にする。 + Pythonは候補価格を作るだけで、注文送信やポジション操作は行わない。 + """ + paths = build_entry_paths() + trend_state = read_int_file( + paths.trend_state, + encoding=MT_ENCODING, + allowed_values=MARKET_STATE_LABELS.keys(), + default=TECHNICAL_ERROR_STOP, + ) + selected_strategies = strategies_by_trend(trend_state) + + if trend_state == TECHNICAL_ERROR_STOP or not selected_strategies: + logger.info( + "market_state=%s (%s). New entries stopped.", + trend_state, + MARKET_STATE_LABELS.get(trend_state, "UNKNOWN"), + ) + write_entry_output(stop_numeric_list()) + return + + try: + ohlc_all = read_ohlc_csv(paths.input_csv) + except Exception: + logger.exception("CSV 読み込みエラー") + write_entry_output(stop_numeric_list()) + return + + if len(ohlc_all) < CANDLE_LONG: + logger.error("データ本数不足: len(ohlc)=%s (need >= %s)", len(ohlc_all), CANDLE_LONG) + write_entry_output(stop_numeric_list()) + return + + ohlc_short = ohlc_all[-CANDLE_SHORT:] + ohlc_long = ohlc_all[-CANDLE_LONG:] + current_price = float(ohlc_short[-1]["Close"]) + candidate_id = build_candidate_id(str(ohlc_short[-1].get("DateTime", ""))) + imbalance_analysis = detect_imbalance_signal( + ohlc_all, + avg_body_period=IMBALANCE_AVG_BODY_PERIOD, + sensitivity=IMBALANCE_SENSITIVITY, + min_avg_body_size=IMBALANCE_MIN_AVG_BODY_SIZE, + ) + imbalance_summary = format_imbalance_summary(imbalance_analysis) + selected_strategies = adjust_strategies_for_imbalance( + selected_strategies, + trend_state=trend_state, + analysis=imbalance_analysis, + use_filter=USE_IMBALANCE_FILTER, + ) + + if not selected_strategies: + logger.info( + "H1 imbalance conflicts with market_state=%s (%s). New entries stopped. %s", + trend_state, + MARKET_STATE_LABELS.get(trend_state, "UNKNOWN"), + imbalance_summary, + ) + write_entry_output(stop_numeric_list()) + return + + try: + ohlc_to_candlestick_png_file( + ohlc_data=ohlc_short, + save_path=paths.tmp_short_chart, + instrument=INSTRUMENT, + timeframe=ENTRY_TIMEFRAME, + dark=True, + ) + ohlc_to_candlestick_png_file( + ohlc_data=ohlc_long, + save_path=paths.tmp_long_chart, + instrument=INSTRUMENT, + timeframe=ENTRY_TIMEFRAME, + dark=True, + ) + images_data_urls = [ + png_file_to_data_url(paths.tmp_short_chart), + png_file_to_data_url(paths.tmp_long_chart), + ] + except Exception: + logger.exception("画像生成エラー") + write_entry_output(stop_numeric_list()) + return + + numeric_summary = build_numeric_summary( + current_price=current_price, + ohlc_short=ohlc_short, + ohlc_long=ohlc_long, + ) + short_summary = summarize_ohlc(ohlc_short) + h1_eatr = float(short_summary.get("eatr", 0.0)) + max_entry_distance = build_entry_distance_limits(h1_eatr, selected_strategies) + post_filter_summary = ( + "entry_distance_guard=" + f"max_distance_by_strategy={max_entry_distance}, " + f"h1_eatr={h1_eatr:.2f}, " + f"stop_multiplier={ENTRY_MAX_DISTANCE_STOP_EATR_MULTIPLIER:.2f}, " + f"limit_multiplier={ENTRY_MAX_DISTANCE_LIMIT_EATR_MULTIPLIER:.2f}, " + f"floor={ENTRY_MAX_DISTANCE_MIN_PRICE:.2f}, " + f"min_reward_risk={MIN_ENTRY_REWARD_RISK_RATIO:.2f}" + ) + header = build_header( + current_price=current_price, + numeric_summary=numeric_summary, + trend_state=trend_state, + ) + market_state_guidance = build_market_state_guidance(trend_state) + imbalance_guidance = build_imbalance_guidance(imbalance_analysis) + caution = build_caution_block() + + try: + config = load_runtime_config(debug_print=DEBUG_PRINT) + except RuntimeError: + logger.exception("OpenAI 設定エラー") + write_entry_output(stop_numeric_list()) + return + + system_content = build_system_content_block_debug() if config.debug_print else build_system_content_block() + common_rules = build_common_rules_block(selected_strategies, max_entry_distance) + response_text_format = build_entry_response_text_format(selected_strategies) + max_tokens = ENTRY_MAX_OUTPUT_TOKENS + + user_text = "\n\n".join([header, market_state_guidance, imbalance_guidance, common_rules, caution]).strip() + + api_diagnostics = "" + try: + client = create_openai_client(config.api_key) + gpt_result = call_responses_api( + client=client, + model=config.model, + reasoning_effort=config.reasoning_effort, + system_content=system_content, + user_text=user_text, + image_data_urls=images_data_urls, + max_output_tokens=max_tokens, + response_text_format=response_text_format, + ) + gpt_reply = gpt_result.text + api_diagnostics = gpt_result.diagnostics.to_log_text() + if not gpt_result.diagnostics.is_completed(): + logger.error("OpenAI response incomplete or failed: %s", api_diagnostics) + write_entry_output(stop_numeric_list()) + return + except Exception: + logger.exception("OpenAI APIエラー") + write_entry_output(stop_numeric_list()) + return + + if config.debug_print: + logger.info( + "---- TREND ---- %s selected=%s\n---- GPT REPLY START ----\n%s\n---- GPT REPLY END ----", + trend_state, + selected_strategies, + gpt_reply, + ) + + entry_zones = parse_json_to_entry_zones(gpt_reply) + entry_zones = sanitize_entry_zones( + entry_zones, + selected_strategies, + current_price, + max_entry_distance=max_entry_distance, + min_reward_risk_ratio=MIN_ENTRY_REWARD_RISK_RATIO, + ) + numeric_list = zones_to_numeric_list(entry_zones) + zone_text = format_target_zones(entry_zones, candidate_id) + + if config.debug_print: + try: + append_debug_entry( + path=paths.debug_reason, + model=config.model, + reasoning_effort=config.reasoning_effort, + max_output_tokens=max_tokens, + api_diagnostics=api_diagnostics, + timeframe=ENTRY_TIMEFRAME, + current_price=current_price, + trend_state=trend_state, + selected_strategies=selected_strategies, + imbalance_summary=imbalance_summary, + numeric_summary=numeric_summary, + numeric_lines=gpt_reply, + post_filter_summary=post_filter_summary, + sanitized_numeric_list=numeric_list, + reason_text=f"STRUCTURED_JSON\n{gpt_reply}\n\nTARGET_ZONES\n{zone_text}".strip(), + ) + except Exception: + logger.exception("debug_entry.txt write error") + + write_entry_output(numeric_list, zone_text) + + +def main() -> None: + """ログ設定を行ってH1エントリー候補生成パイプラインを起動する。""" + logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(name)s:%(message)s") + run_pipeline() diff --git a/python_for_ea/tests/unit/test_debug_logs.py b/python_for_ea/tests/unit/test_debug_logs.py new file mode 100644 index 0000000..0e6b893 --- /dev/null +++ b/python_for_ea/tests/unit/test_debug_logs.py @@ -0,0 +1,31 @@ +"""Debug log output tests.""" + +from __future__ import annotations + +from pathlib import Path + +from ea_py.io.debug_logs import append_debug_entry_stop + + +def test_append_debug_entry_stop_records_safe_stop_reason(tmp_path: Path) -> None: + """Safe-stop diagnostics distinguish a guarded stop from a normal GPT skip.""" + path = tmp_path / "debug_entry.txt" + + append_debug_entry_stop( + path=path, + stage="openai_api", + reason="RateLimitError: retry later", + trend_state=2, + selected_strategies=[1, 2], + candidate_id="202606012100", + current_price=4535.17, + imbalance_summary="signal=NONE", + ) + + actual = path.read_text(encoding="utf-8") + assert "RESULT : SAFE_STOP" in actual + assert "STAGE : openai_api" in actual + assert "REASON : RateLimitError: retry later" in actual + assert "MARKET_STATE(H4) : 2 (LOW_VOL_UP)" in actual + assert "SELECTED_STRATEGY : 1,2" in actual + assert "CANDIDATE_ID : 202606012100" in actual