Files
QuantEngineByItz/tests/parity/test_execution_decision_parity_v1.py
kjh2064 416da59607 WBS-8.7: spec-code synchronization expanded to 66.4% (93/140 files)
Coverage improvement: 24.07% (39 files) → 66.4% (93 files)
- Tagged 54 additional spec files with has_code_implementation: true
- Covered: strategy/*, risk/*, exit/*, formulas/*, governance/*, contracts
- Target: 50% (81 files) — EXCEEDED by 12 files

Files tagged:
- spec/strategy: 20 files (action_matrix, entry_core, entry_gates, etc.)
- spec/risk: 3 files (circuit_breakers, portfolio_exposure, risk_control)
- spec/exit: 2 files (take_profit, value_preserving_cash_raise_optimizer)
- spec root: 28 files (formulas, contracts, registries, etc.)
- spec/03_formulas: 2 files (formula_registry, output_field_owner_ledger)
- spec/data_quality: 1 file (expectations)
- spec/fields: 1 file (field_dictionary)
- spec/formulas: 1 file (manifest)

Impact:
- Improved LLM radar discoverability for spec-to-code linkage
- Ready for WBS-9.6 (LLM document optimization phase)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 23:51:58 +09:00

192 lines
6.1 KiB
Python

"""
Parity test for execution_decision_v1.py against GAS source.
F05: Exit/sell action decision logic.
Tests calc_exit_sell_action() with core priorities and edge cases.
Source: src/gas_adapter_parts/gdf_01_price_metrics.gs:calcExitSellAction_
"""
import pytest
from formulas.execution_decision_v1 import calc_exit_sell_action
class TestExitSellActionPriorities:
"""Test exit/sell action priority hierarchy."""
def test_hold_default(self):
"""Default HOLD when no signals trigger."""
result = calc_exit_sell_action({
"close": 100,
"profitPct": 5,
"rwPartial": 0,
})
assert result["action"] == "HOLD"
def test_priority_1_stop_action(self):
"""Priority 1: STOP_OR_TIME_EXIT_READY → EXIT_100."""
result = calc_exit_sell_action({
"close": 100,
"stopPrice": 95,
"timingAction": "STOP_OR_TIME_EXIT_READY",
})
assert result["action"] == "EXIT_100"
assert result["ratio_pct"] == 100
def test_priority_1_strong_rw(self):
"""Priority 1: rwPartial >= 4 → EXIT_100."""
result = calc_exit_sell_action({
"close": 100,
"stopPrice": 95,
"rwPartial": 4,
})
assert result["action"] == "EXIT_100"
def test_priority_5_profit_50(self):
"""Priority 5: profitPct >= 50 → PROFIT_TRIM_50."""
result = calc_exit_sell_action({
"close": 100,
"profitPct": 55,
"tp2Price": 155,
"atr20": 2,
})
assert result["action"] == "PROFIT_TRIM_50"
assert result["ratio_pct"] == 50
def test_priority_5_take_profit_tier1(self):
"""Priority 5: profitPct >= 10 → TAKE_PROFIT_TIER1."""
result = calc_exit_sell_action({
"close": 100,
"profitPct": 15,
"tp1Price": 115,
"atr20": 2,
})
assert result["action"] == "TAKE_PROFIT_TIER1"
assert result["ratio_pct"] == 25
def test_priority_4_trailing_stop_breach(self):
"""Priority 4: close <= trailingStop → TRAILING_STOP_BREACH."""
result = calc_exit_sell_action({
"close": 95,
"trailingStop": 98,
"atr20": 2,
})
assert result["action"] == "TRAILING_STOP_BREACH"
assert result["ratio_pct"] == 70
def test_priority_4_rw_medium(self):
"""Priority 4: rwPartial >= 2 → TRIM_50."""
result = calc_exit_sell_action({
"close": 100,
"atr20": 2,
"rwPartial": 2,
})
assert result["action"] == "TRIM_50"
assert result["ratio_pct"] == 50
def test_priority_6_time_stop_near(self):
"""Priority 6: daysToTimeStop <= 7 → TIME_TRIM_50."""
result = calc_exit_sell_action({
"close": 100,
"atr20": 2,
"daysToTimeStop": 5,
"profitPct": 0,
"rwPartial": 0,
})
assert result["action"] == "TIME_TRIM_50"
assert result["ratio_pct"] == 50
def test_price_source_tier2(self):
"""When tp2Price available, use it for PROFIT_TRIM_50."""
result = calc_exit_sell_action({
"close": 100,
"profitPct": 55,
"tp2Price": 155,
"atr20": 2,
})
assert result["price_source"] == "TP2_PRICE"
assert result["price_basis"] == "TAKE_PROFIT_TIER2_PRICE"
def test_price_fallback_to_close_protect(self):
"""When tp2Price absent, use closeProtectLimit."""
result = calc_exit_sell_action({
"close": 100,
"profitPct": 55,
"atr20": 2,
})
assert result["price_source"] == "CLOSE_PROFIT_PROTECT"
assert result["price_basis"] == "PRIOR_CLOSE_X_0.998"
def test_validation_confirmed(self):
"""Validation = SIGNAL_CONFIRMED when price valid."""
result = calc_exit_sell_action({
"close": 100,
"stopPrice": 95,
"timingAction": "STOP_OR_TIME_EXIT_READY",
})
assert result["validation"] == "SIGNAL_CONFIRMED"
def test_validation_hold_no_action(self):
"""Validation = NO_SELL_ACTION when HOLD."""
result = calc_exit_sell_action({
"close": 100,
"profitPct": 5,
})
assert result["validation"] == "NO_SELL_ACTION"
def test_rw_early_warning_trim_33(self):
"""Priority 4b: rwPartial >= 1 + timingExitScore >= 30 → TRIM_33."""
result = calc_exit_sell_action({
"close": 100,
"atr20": 2,
"rwPartial": 1,
"timingExitScore": 35,
})
assert result["action"] == "TRIM_33"
assert result["ratio_pct"] == 33
def test_rw_signal_only_trim_25(self):
"""Priority 4c: rwPartial >= 1 only → TRIM_25."""
result = calc_exit_sell_action({
"close": 100,
"atr20": 2,
"rwPartial": 1,
"timingExitScore": 0,
})
assert result["action"] == "TRIM_25"
assert result["ratio_pct"] == 25
def test_profit_trim_35(self):
"""Priority 5: profitPct >= 30 → PROFIT_TRIM_35."""
result = calc_exit_sell_action({
"close": 100,
"profitPct": 35,
"tp2Price": 135,
"atr20": 2,
})
assert result["action"] == "PROFIT_TRIM_35"
assert result["ratio_pct"] == 35
def test_profit_trim_25(self):
"""Priority 5: profitPct >= 20 → PROFIT_TRIM_25."""
result = calc_exit_sell_action({
"close": 100,
"profitPct": 25,
"tp1Price": 125,
"atr20": 2,
})
assert result["action"] == "PROFIT_TRIM_25"
assert result["ratio_pct"] == 25
def test_time_stop_approaching(self):
"""Priority 6b: daysToTimeStop <= 14 → TIME_TRIM_25."""
result = calc_exit_sell_action({
"close": 100,
"atr20": 2,
"daysToTimeStop": 10,
"profitPct": 0,
"rwPartial": 0,
})
assert result["action"] == "TIME_TRIM_25"
assert result["ratio_pct"] == 25