Add safe-stop diagnostics to H1 entry logs
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user