Add file-prefix isolation and strict split-order parsing
This commit is contained in:
+13
-9
@@ -36,11 +36,13 @@ MT5 EA からは `bat/` 経由でルート直下の互換スクリプトを起
|
||||
|
||||
パスの組み立ては `src/ea_py/paths.py` で定義しています。特殊な配置では `MT5_FILES_DIR` または `MT5_DATA_PATH` 環境変数で上書きできます。
|
||||
|
||||
EAからbat経由で起動する場合は、第1引数で渡された `HIT_<symbol>_<magic_number>` 接頭辞が `MT5_EA_FILE_PREFIX` として設定されます。Pythonはこの接頭辞をすべての連携ファイル名へ付けるため、同じTerminal内で複数EAや複数シンボルを動かしてもファイルが衝突しません。手動実行で接頭辞が未設定の場合は、従来のファイル名を使います。
|
||||
|
||||
### 入力
|
||||
|
||||
```text
|
||||
ohlc_H4.csv
|
||||
ohlc_H1.csv
|
||||
[<prefix>_]ohlc_H4.csv
|
||||
[<prefix>_]ohlc_H1.csv
|
||||
```
|
||||
|
||||
CSV は UTF-8 読み込みで、次の列を必須とします。
|
||||
@@ -52,11 +54,11 @@ Time,Open,High,Low,Close
|
||||
### 出力
|
||||
|
||||
```text
|
||||
trend_state.txt
|
||||
target_prices.txt
|
||||
target_zones.txt
|
||||
process_done_trend.txt
|
||||
process_done_entry.txt
|
||||
[<prefix>_]trend_state.txt
|
||||
[<prefix>_]target_prices.txt
|
||||
[<prefix>_]target_zones.txt
|
||||
[<prefix>_]process_done_trend.txt
|
||||
[<prefix>_]process_done_entry.txt
|
||||
```
|
||||
|
||||
MT5 が読む結果ファイルは `utf-16 LE` で出力します。結果ファイルを書き終えたあとに done ファイルを作成します。古い done ファイルは結果書き込み前に削除されます。
|
||||
@@ -81,7 +83,7 @@ MT5 が読む結果ファイルは `utf-16 LE` で出力します。結果ファ
|
||||
|
||||
## H1 エントリー候補生成
|
||||
|
||||
`get_entry_reply.py` は `trend_state.txt` と H1 OHLC から `target_prices.txt` を生成します。
|
||||
`get_entry_reply.py` は `trend_state.txt` と H1 OHLC から `target_prices.txt` と `target_zones.txt` を生成します。
|
||||
|
||||
処理の概要:
|
||||
|
||||
@@ -140,9 +142,10 @@ $env:OPENAI_API_KEY = "..."
|
||||
```powershell
|
||||
$env:OPENAI_MODEL = "..."
|
||||
$env:OPENAI_REASONING_EFFORT = "low"
|
||||
$env:MT5_EA_FILE_PREFIX = "HIT_GOLD_10001"
|
||||
```
|
||||
|
||||
`OPENAI_MODEL` 未設定時は `src/ea_py/constants.py` の `DEFAULT_GPT_MODEL` を使います。`OPENAI_REASONING_EFFORT` は `none`, `low`, `medium`, `high`, `xhigh` のいずれかです。
|
||||
`OPENAI_MODEL` 未設定時は `src/ea_py/constants.py` の `DEFAULT_GPT_MODEL` を使います。`OPENAI_REASONING_EFFORT` は `none`, `low`, `medium`, `high`, `xhigh` のいずれかです。`MT5_EA_FILE_PREFIX` は手動実行時だけ指定すればよく、EA/bat経由では自動設定されます。
|
||||
|
||||
## 主なモジュール
|
||||
|
||||
@@ -156,6 +159,7 @@ src/ea_py/charting/candlestick.py ローソク足 PNG 生成
|
||||
src/ea_py/market/volatility.py True Range / EATR / 数値要約
|
||||
src/ea_py/market/trend_state.py H4 方向判定と market_state 合成
|
||||
src/ea_py/market/target_prices.py GPT 出力のパースと価格検証
|
||||
src/ea_py/market/target_zones.py 分割エントリー用ゾーン出力
|
||||
src/ea_py/market/imbalance.py H1 インバランス初動判定
|
||||
src/ea_py/openai_client.py OpenAI Responses API ラッパー
|
||||
src/ea_py/pipelines/ H4/H1 パイプライン
|
||||
|
||||
@@ -5,6 +5,7 @@ chcp 65001 >nul
|
||||
set PYTHONIOENCODING=utf-8
|
||||
set "APP_DIR=%~dp0.."
|
||||
set PY_FILE=get_entry_reply.py
|
||||
if not "%~1"=="" set "MT5_EA_FILE_PREFIX=%~1"
|
||||
for %%I in ("%APP_DIR%") do set "APP_DIR=%%~fI"
|
||||
|
||||
cd /d "%APP_DIR%"
|
||||
|
||||
@@ -5,6 +5,7 @@ chcp 65001 >nul
|
||||
set PYTHONIOENCODING=utf-8
|
||||
set "APP_DIR=%~dp0.."
|
||||
set PY_FILE=get_trend_reply.py
|
||||
if not "%~1"=="" set "MT5_EA_FILE_PREFIX=%~1"
|
||||
for %%I in ("%APP_DIR%") do set "APP_DIR=%%~fI"
|
||||
|
||||
cd /d "%APP_DIR%"
|
||||
|
||||
@@ -35,9 +35,11 @@ MT5 EAから `bat/` 経由でルート直下のPythonスクリプトが呼ばれ
|
||||
│ │ └─ candlestick.py
|
||||
│ ├─ market/
|
||||
│ │ ├─ __init__.py
|
||||
│ │ ├─ imbalance.py
|
||||
│ │ ├─ target_prices.py
|
||||
│ │ ├─ target_zones.py
|
||||
│ │ ├─ volatility.py
|
||||
│ │ ├─ trend_state.py
|
||||
│ │ └─ target_prices.py
|
||||
│ │ └─ trend_state.py
|
||||
│ ├─ prompts/
|
||||
│ │ ├─ __init__.py
|
||||
│ │ ├─ trend_prompt.py
|
||||
@@ -49,8 +51,12 @@ MT5 EAから `bat/` 経由でルート直下のPythonスクリプトが呼ばれ
|
||||
│ └─ entry_pipeline.py
|
||||
├─ tests/
|
||||
│ └─ unit/
|
||||
│ ├─ test_trend_state.py
|
||||
│ ├─ test_config.py
|
||||
│ ├─ test_imbalance.py
|
||||
│ ├─ test_openai_client.py
|
||||
│ ├─ test_paths.py
|
||||
│ ├─ test_target_prices.py
|
||||
│ ├─ test_trend_state.py
|
||||
│ └─ test_volatility.py
|
||||
├─ bat/
|
||||
│ ├─ get_trend_reply.bat
|
||||
@@ -86,23 +92,26 @@ MT5 EAから `bat/` 経由でルート直下のPythonスクリプトが呼ばれ
|
||||
|
||||
MT5側の `MQL5\Files` 配下に、Pythonとの連携ファイルが置かれる。
|
||||
|
||||
EA/bat経由では `HIT_<symbol>_<magic_number>` 形式の接頭辞が `MT5_EA_FILE_PREFIX` としてPythonへ渡され、すべての連携ファイル名へ付与される。接頭辞が未設定の手動実行では旧ファイル名を維持する。
|
||||
|
||||
代表例:
|
||||
|
||||
```text
|
||||
C:/Users/new/AppData/Roaming/MetaQuotes/Terminal/{terminal_ID}/MQL5/Files/
|
||||
├─ ohlc_H4.csv
|
||||
├─ ohlc_H1.csv
|
||||
├─ trend_state.txt
|
||||
├─ target_prices.txt
|
||||
├─ process_done_trend.txt
|
||||
├─ process_done_entry.txt
|
||||
├─ process_running_trend.txt
|
||||
├─ process_running_entry.txt
|
||||
├─ debug_trend.txt
|
||||
├─ debug_entry.txt
|
||||
├─ tmp_chart_trend.png
|
||||
├─ tmp_chart_short.png
|
||||
└─ tmp_chart_long.png
|
||||
├─ HIT_GOLD_10001_ohlc_H4.csv
|
||||
├─ HIT_GOLD_10001_ohlc_H1.csv
|
||||
├─ HIT_GOLD_10001_trend_state.txt
|
||||
├─ HIT_GOLD_10001_target_prices.txt
|
||||
├─ HIT_GOLD_10001_target_zones.txt
|
||||
├─ HIT_GOLD_10001_process_done_trend.txt
|
||||
├─ HIT_GOLD_10001_process_done_entry.txt
|
||||
├─ HIT_GOLD_10001_process_running_trend.txt
|
||||
├─ HIT_GOLD_10001_process_running_entry.txt
|
||||
├─ HIT_GOLD_10001_debug_trend.txt
|
||||
├─ HIT_GOLD_10001_debug_entry.txt
|
||||
├─ HIT_GOLD_10001_tmp_chart_trend.png
|
||||
├─ HIT_GOLD_10001_tmp_chart_short.png
|
||||
└─ HIT_GOLD_10001_tmp_chart_long.png
|
||||
```
|
||||
|
||||
これらは実行時生成物であり、原則として `MQL5\python_for_ea` 配下へコピーして正本化しない。
|
||||
@@ -115,7 +124,7 @@ MQL5 EAファイルは、MT5データフォルダ配下に存在する。
|
||||
|
||||
```text
|
||||
C:/Users/new/AppData/Roaming/MetaQuotes/Terminal/{terminal_ID}/MQL5/Experts/MyProject/
|
||||
└─ HIT-EA_refactor_ver3.mq5
|
||||
└─ HIT-EA_refactor_ver6.mq5
|
||||
```
|
||||
|
||||
EAファイルはこのPythonプロジェクトの外部連携先として扱う。Python側の出力形式を変更する場合は、EA側の読込処理も同時に確認する。
|
||||
@@ -154,9 +163,11 @@ EAファイルはこのPythonプロジェクトの外部連携先として扱う
|
||||
│ │ └─ candlestick.py
|
||||
│ ├─ market/
|
||||
│ │ ├─ __init__.py
|
||||
│ │ ├─ imbalance.py
|
||||
│ │ ├─ target_prices.py
|
||||
│ │ ├─ target_zones.py
|
||||
│ │ ├─ volatility.py
|
||||
│ │ ├─ trend_state.py
|
||||
│ │ └─ target_prices.py
|
||||
│ │ └─ trend_state.py
|
||||
│ ├─ prompts/
|
||||
│ │ ├─ __init__.py
|
||||
│ │ ├─ trend_prompt.py
|
||||
@@ -168,8 +179,12 @@ EAファイルはこのPythonプロジェクトの外部連携先として扱う
|
||||
│ └─ entry_pipeline.py
|
||||
├─ tests/
|
||||
│ └─ unit/
|
||||
│ ├─ test_trend_state.py
|
||||
│ ├─ test_config.py
|
||||
│ ├─ test_imbalance.py
|
||||
│ ├─ test_openai_client.py
|
||||
│ ├─ test_paths.py
|
||||
│ ├─ test_target_prices.py
|
||||
│ ├─ test_trend_state.py
|
||||
│ └─ test_volatility.py
|
||||
└─ docs/
|
||||
├─ architecture/
|
||||
@@ -193,6 +208,8 @@ EAファイルはこのPythonプロジェクトの外部連携先として扱う
|
||||
| `src/ea_py/market/volatility.py` | True Range、Exponential ATR、ボラティリティ分類。 |
|
||||
| `src/ea_py/market/trend_state.py` | H4方向判定結果とボラティリティ分類の合成。 |
|
||||
| `src/ea_py/market/target_prices.py` | GPT出力のパース、価格整合性チェック、13行形式への変換。 |
|
||||
| `src/ea_py/market/target_zones.py` | GPT出力の予測ゾーンを分割エントリー用7行形式へ変換。 |
|
||||
| `src/ea_py/market/imbalance.py` | H1インバランス初動判定。 |
|
||||
| `src/ea_py/prompts/trend_prompt.py` | H4相場環境判定プロンプトの生成。 |
|
||||
| `src/ea_py/prompts/entry_prompt.py` | H1候補価格生成プロンプトの生成。 |
|
||||
| `src/ea_py/openai_client.py` | OpenAI API呼び出しの薄いラッパー。 |
|
||||
|
||||
@@ -11,6 +11,7 @@ DEFAULT_USER_NAME = "new"
|
||||
DEFAULT_TERMINAL_ID = "5BDB0B60344C088C2FA5CA35699BAAFD"
|
||||
ENV_MT5_FILES_DIR = "MT5_FILES_DIR"
|
||||
ENV_MT5_DATA_PATH = "MT5_DATA_PATH"
|
||||
ENV_MT5_EA_FILE_PREFIX = "MT5_EA_FILE_PREFIX"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -21,6 +22,7 @@ class Mt5PathSettings:
|
||||
data_path: Path | None = None
|
||||
user_name: str | None = None
|
||||
terminal_id: str | None = None
|
||||
file_prefix: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -68,6 +70,39 @@ def files_dir_from_data_path(data_path: Path) -> Path:
|
||||
return resolved / "MQL5" / "Files"
|
||||
|
||||
|
||||
def sanitize_file_prefix(prefix: str) -> str:
|
||||
"""EAから渡されたファイル接頭辞を安全なファイル名トークンへ丸める。"""
|
||||
stripped = prefix.strip()
|
||||
if not stripped:
|
||||
return ""
|
||||
|
||||
safe_chars = []
|
||||
for char in stripped:
|
||||
if (char.isascii() and char.isalnum()) or char in {"_", "-"}:
|
||||
safe_chars.append(char)
|
||||
else:
|
||||
safe_chars.append("_")
|
||||
|
||||
return "".join(safe_chars)
|
||||
|
||||
|
||||
def mt5_file_prefix(settings: Mt5PathSettings | None = None) -> str:
|
||||
"""MT5連携ファイル名へ付けるEA固有接頭辞を返す。"""
|
||||
if settings and settings.file_prefix is not None:
|
||||
return sanitize_file_prefix(settings.file_prefix)
|
||||
|
||||
return sanitize_file_prefix(os.getenv(ENV_MT5_EA_FILE_PREFIX, ""))
|
||||
|
||||
|
||||
def prefixed_file_name(base_name: str, settings: Mt5PathSettings | None = None) -> str:
|
||||
"""接頭辞がある場合だけ `prefix_base_name` 形式にする。"""
|
||||
prefix = mt5_file_prefix(settings)
|
||||
if not prefix:
|
||||
return base_name
|
||||
|
||||
return f"{prefix}_{base_name}"
|
||||
|
||||
|
||||
def terminal_files_dir(settings: Mt5PathSettings | None = None) -> Path:
|
||||
"""MT5のMQL5/Filesディレクトリを返す。
|
||||
|
||||
@@ -127,11 +162,11 @@ def build_trend_paths(settings: Mt5PathSettings | None = None) -> TrendPaths:
|
||||
"""H4トレンド判定用の入出力パスをまとめて返す。"""
|
||||
base_dir = terminal_files_dir(settings)
|
||||
return TrendPaths(
|
||||
input_csv=base_dir / "ohlc_H4.csv",
|
||||
trend_state=base_dir / "trend_state.txt",
|
||||
done_trend=base_dir / "process_done_trend.txt",
|
||||
tmp_chart=base_dir / "tmp_chart_trend.png",
|
||||
debug_reason=base_dir / "debug_trend.txt",
|
||||
input_csv=base_dir / prefixed_file_name("ohlc_H4.csv", settings),
|
||||
trend_state=base_dir / prefixed_file_name("trend_state.txt", settings),
|
||||
done_trend=base_dir / prefixed_file_name("process_done_trend.txt", settings),
|
||||
tmp_chart=base_dir / prefixed_file_name("tmp_chart_trend.png", settings),
|
||||
debug_reason=base_dir / prefixed_file_name("debug_trend.txt", settings),
|
||||
)
|
||||
|
||||
|
||||
@@ -139,12 +174,12 @@ def build_entry_paths(settings: Mt5PathSettings | None = None) -> EntryPaths:
|
||||
"""H1エントリー候補生成用の入出力パスをまとめて返す。"""
|
||||
base_dir = terminal_files_dir(settings)
|
||||
return EntryPaths(
|
||||
input_csv=base_dir / "ohlc_H1.csv",
|
||||
output_prices=base_dir / "target_prices.txt",
|
||||
output_zones=base_dir / "target_zones.txt",
|
||||
trend_state=base_dir / "trend_state.txt",
|
||||
done_entry=base_dir / "process_done_entry.txt",
|
||||
tmp_short_chart=base_dir / "tmp_chart_short.png",
|
||||
tmp_long_chart=base_dir / "tmp_chart_long.png",
|
||||
debug_reason=base_dir / "debug_entry.txt",
|
||||
input_csv=base_dir / prefixed_file_name("ohlc_H1.csv", settings),
|
||||
output_prices=base_dir / prefixed_file_name("target_prices.txt", settings),
|
||||
output_zones=base_dir / prefixed_file_name("target_zones.txt", settings),
|
||||
trend_state=base_dir / prefixed_file_name("trend_state.txt", settings),
|
||||
done_entry=base_dir / prefixed_file_name("process_done_entry.txt", settings),
|
||||
tmp_short_chart=base_dir / prefixed_file_name("tmp_chart_short.png", settings),
|
||||
tmp_long_chart=base_dir / prefixed_file_name("tmp_chart_long.png", settings),
|
||||
debug_reason=base_dir / prefixed_file_name("debug_entry.txt", settings),
|
||||
)
|
||||
|
||||
@@ -7,11 +7,16 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from ea_py.paths import (
|
||||
ENV_MT5_EA_FILE_PREFIX,
|
||||
ENV_MT5_DATA_PATH,
|
||||
ENV_MT5_FILES_DIR,
|
||||
Mt5PathSettings,
|
||||
build_entry_paths,
|
||||
build_trend_paths,
|
||||
discover_mql5_dir,
|
||||
files_dir_from_data_path,
|
||||
prefixed_file_name,
|
||||
sanitize_file_prefix,
|
||||
terminal_files_dir,
|
||||
)
|
||||
|
||||
@@ -89,3 +94,44 @@ def test_terminal_files_dir_accepts_environment_data_path(
|
||||
actual = terminal_files_dir()
|
||||
|
||||
assert actual == data_path / "MQL5" / "Files"
|
||||
|
||||
|
||||
def test_prefixed_file_name_uses_legacy_name_when_prefix_is_empty(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""接頭辞未指定時は既存のMT5連携ファイル名を維持する。"""
|
||||
monkeypatch.delenv(ENV_MT5_EA_FILE_PREFIX, raising=False)
|
||||
|
||||
assert prefixed_file_name("target_prices.txt") == "target_prices.txt"
|
||||
|
||||
|
||||
def test_sanitize_file_prefix_replaces_unsafe_characters() -> None:
|
||||
"""Symbols containing dots or suffixes are safe for filesystem names."""
|
||||
assert sanitize_file_prefix("HIT_XAUUSD.pro_10001") == "HIT_XAUUSD_pro_10001"
|
||||
|
||||
|
||||
def test_build_paths_apply_file_prefix_from_settings(tmp_path: Path) -> None:
|
||||
"""同一Files配下でEAごとの連携ファイル名を分離できる。"""
|
||||
settings = Mt5PathSettings(files_dir=tmp_path, file_prefix="HIT_GOLD_10001")
|
||||
|
||||
trend_paths = build_trend_paths(settings)
|
||||
entry_paths = build_entry_paths(settings)
|
||||
|
||||
assert trend_paths.input_csv == tmp_path / "HIT_GOLD_10001_ohlc_H4.csv"
|
||||
assert trend_paths.trend_state == tmp_path / "HIT_GOLD_10001_trend_state.txt"
|
||||
assert entry_paths.input_csv == tmp_path / "HIT_GOLD_10001_ohlc_H1.csv"
|
||||
assert entry_paths.output_prices == tmp_path / "HIT_GOLD_10001_target_prices.txt"
|
||||
assert entry_paths.output_zones == tmp_path / "HIT_GOLD_10001_target_zones.txt"
|
||||
|
||||
|
||||
def test_build_paths_apply_file_prefix_from_environment(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""batから渡されたMT5_EA_FILE_PREFIXでPython側ファイル名を合わせる。"""
|
||||
monkeypatch.setenv(ENV_MT5_FILES_DIR, str(tmp_path))
|
||||
monkeypatch.setenv(ENV_MT5_EA_FILE_PREFIX, "HIT_GOLD_10001")
|
||||
|
||||
actual = build_entry_paths()
|
||||
|
||||
assert actual.done_entry == tmp_path / "HIT_GOLD_10001_process_done_entry.txt"
|
||||
|
||||
Reference in New Issue
Block a user