Adopt structured outputs for GPT entry generation
This commit is contained in:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user