Adopt structured outputs for GPT entry generation

This commit is contained in:
Hiroaki86
2026-05-30 22:01:57 +09:00
parent 652da68675
commit bbc8ea1a80
8 changed files with 250 additions and 84 deletions
@@ -70,6 +70,36 @@ def test_call_responses_api_sends_reasoning_and_omits_temperature() -> None:
assert "temperature" not in fake_client.responses.create_params
def test_call_responses_api_sends_structured_text_format() -> None:
"""Structured Outputs用のtext.formatをResponses APIへ渡す。"""
fake_client = FakeClient(FakeResponse(output_text='{"schema_version":1,"strategies":[]}'))
response_text_format = {
"type": "json_schema",
"name": "entry_candidates",
"strict": True,
"schema": {
"type": "object",
"additionalProperties": False,
"properties": {"schema_version": {"type": "integer"}},
"required": ["schema_version"],
},
}
actual = call_responses_api(
client=fake_client, # type: ignore[arg-type]
model="gpt-5.5-2026-04-23",
reasoning_effort="low",
system_content="Return JSON.",
user_text="Classify.",
image_data_urls=[],
max_output_tokens=128,
response_text_format=response_text_format,
)
assert actual.text.startswith("{")
assert fake_client.responses.create_params["text"] == {"format": response_text_format}
def test_call_responses_api_returns_incomplete_diagnostics_for_empty_text() -> None:
"""Empty output_text keeps the API status details for live diagnosis."""
fake_client = FakeClient(
@@ -91,3 +121,4 @@ def test_call_responses_api_returns_incomplete_diagnostics_for_empty_text() -> N
assert actual.text == ""
assert "max_output_tokens" in actual.diagnostics.incomplete_details
assert not actual.diagnostics.is_completed()
@@ -6,6 +6,7 @@ from ea_py.market.target_prices import parse_lines_to_13_allow_subset, sanitize_
from ea_py.market.target_zones import (
build_candidate_id,
format_target_zones,
parse_json_to_entry_zones,
parse_lines_to_entry_zones_allow_subset,
sanitize_entry_zones,
zones_to_numeric_list,
@@ -74,6 +75,51 @@ def test_parse_lines_to_entry_zones_allow_subset_valid_zone_returns_zone_values(
assert actual[2].zone_high == 1898.0
def test_parse_json_to_entry_zones_valid_structured_output_returns_zone_values() -> None:
"""Structured OutputsのJSON候補を戦略別ゾーンへ展開する。"""
actual = parse_json_to_entry_zones(
"""
{
"schema_version": 1,
"strategies": [
{
"strategy": 2,
"decision": "use",
"entry": 1895.0,
"tp": 1910.0,
"sl": 1885.0,
"zone_low": 1892.0,
"zone_high": 1898.0,
"reason_code": "range_edge"
},
{
"strategy": 4,
"decision": "skip",
"entry": 0.0,
"tp": 0.0,
"sl": 0.0,
"zone_low": 0.0,
"zone_high": 0.0,
"reason_code": "skip_mid_range"
}
]
}
"""
)
assert actual[2].entry == 1895.0
assert actual[2].zone_low == 1892.0
assert actual[2].zone_high == 1898.0
assert actual[4].entry == 0.0
def test_parse_json_to_entry_zones_invalid_json_returns_stop_values() -> None:
"""不正JSONは全停止ゾーンへ倒す。"""
actual = parse_json_to_entry_zones("2,1895.00,1910.00,1885.00,1892.00,1898.00")
assert all(zone.entry == 0.0 for zone in actual.values())
def test_sanitize_entry_zones_invalid_unselected_strategy_zeroes_it() -> None:
"""未選択戦略のゾーンは停止値へ補正する。"""
parsed = parse_lines_to_entry_zones_allow_subset(
@@ -96,6 +142,20 @@ def test_sanitize_entry_zones_allows_near_zone_edge_when_entry_is_farther() -> N
assert actual[2].entry == 1890.0
def test_sanitize_entry_zones_rejects_low_reward_risk_ratio() -> None:
"""reward/riskが閾値未満の候補は停止値へ補正する。"""
parsed = parse_lines_to_entry_zones_allow_subset("2,1895.00,1900.00,1885.00,1892.00,1898.00")
actual = sanitize_entry_zones(
parsed,
selected_strategies=[2],
current_price=1900.0,
min_reward_risk_ratio=1.2,
)
assert actual[2].entry == 0.0
def test_zones_to_numeric_list_valid_zone_returns_legacy_13_values() -> None:
"""有効ゾーンのentry/tp/slから既存13行形式を生成する。"""
parsed = parse_lines_to_entry_zones_allow_subset("4,1910.00,1880.00,1920.00,1908.00,1912.00")