Strengthen Python timeout recovery and candidate freshness
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
@@ -9,6 +10,7 @@ import pandas as pd
|
||||
from ea_py.types import OhlcBar
|
||||
|
||||
REQUIRED_COLUMNS = ("Time", "Open", "High", "Low", "Close")
|
||||
PRICE_COLUMNS = ("Open", "High", "Low", "Close")
|
||||
|
||||
|
||||
def read_ohlc_csv(path: Path) -> list[OhlcBar]:
|
||||
@@ -18,7 +20,8 @@ def read_ohlc_csv(path: Path) -> list[OhlcBar]:
|
||||
`Time` は文字列、価格列はfloatへ変換し、内部表現では既存プロンプトと
|
||||
チャート生成処理に合わせて `Time` を `DateTime` キーへ写す。
|
||||
|
||||
必須列が欠けている場合や価格列のfloat変換に失敗した場合は例外を送出する。
|
||||
必須列が欠けている場合、価格列のfloat変換に失敗した場合、
|
||||
NaN/infやOHLC整合性違反がある場合は例外を送出する。
|
||||
上位パイプラインはその例外を捕捉し、MT5へ停止値を返す。
|
||||
"""
|
||||
df = pd.read_csv(path, encoding="utf-8")
|
||||
@@ -34,14 +37,31 @@ def read_ohlc_csv(path: Path) -> list[OhlcBar]:
|
||||
df["Close"] = df["Close"].astype(float)
|
||||
|
||||
ohlc: list[OhlcBar] = []
|
||||
for _, row in df.iterrows():
|
||||
for index, row in df.iterrows():
|
||||
prices = {column: float(row[column]) for column in PRICE_COLUMNS}
|
||||
invalid_columns = [column for column, value in prices.items() if not math.isfinite(value)]
|
||||
if invalid_columns:
|
||||
joined = ", ".join(invalid_columns)
|
||||
raise ValueError(f"OHLC CSV row {index} has non-finite price(s): {joined}")
|
||||
|
||||
high = prices["High"]
|
||||
low = prices["Low"]
|
||||
open_price = prices["Open"]
|
||||
close_price = prices["Close"]
|
||||
if high < low:
|
||||
raise ValueError(f"OHLC CSV row {index} has High < Low")
|
||||
if high < max(open_price, close_price):
|
||||
raise ValueError(f"OHLC CSV row {index} has High below Open/Close")
|
||||
if low > min(open_price, close_price):
|
||||
raise ValueError(f"OHLC CSV row {index} has Low above Open/Close")
|
||||
|
||||
ohlc.append(
|
||||
{
|
||||
"DateTime": row["Time"],
|
||||
"Open": float(row["Open"]),
|
||||
"High": float(row["High"]),
|
||||
"Low": float(row["Low"]),
|
||||
"Close": float(row["Close"]),
|
||||
"Open": open_price,
|
||||
"High": high,
|
||||
"Low": low,
|
||||
"Close": close_price,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ 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"
|
||||
ENV_MT5_PRICE_DIGITS = "MT5_PRICE_DIGITS"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -23,6 +24,7 @@ class Mt5PathSettings:
|
||||
user_name: str | None = None
|
||||
terminal_id: str | None = None
|
||||
file_prefix: str | None = None
|
||||
price_digits: int | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -103,6 +105,30 @@ def prefixed_file_name(base_name: str, settings: Mt5PathSettings | None = None)
|
||||
return f"{prefix}_{base_name}"
|
||||
|
||||
|
||||
def sanitize_price_digits(value: int | str | None) -> int | None:
|
||||
"""MT5から渡された価格桁数を安全な範囲へ丸める。"""
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
digits = int(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
if 0 <= digits <= 10:
|
||||
return digits
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def mt5_price_digits(settings: Mt5PathSettings | None = None) -> int | None:
|
||||
"""MT5連携価格の小数桁数を返す。未指定や不正値はNone。"""
|
||||
if settings and settings.price_digits is not None:
|
||||
return sanitize_price_digits(settings.price_digits)
|
||||
|
||||
return sanitize_price_digits(os.getenv(ENV_MT5_PRICE_DIGITS))
|
||||
|
||||
|
||||
def terminal_files_dir(settings: Mt5PathSettings | None = None) -> Path:
|
||||
"""MT5のMQL5/Filesディレクトリを返す。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user