feat: 세션 전환 시 리스크 파라미터 동적 재로딩 (#327) #344
@@ -4,6 +4,7 @@ from datetime import UTC, date, datetime
|
|||||||
from unittest.mock import ANY, AsyncMock, MagicMock, patch
|
from unittest.mock import ANY, AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import src.main as main_module
|
||||||
|
|
||||||
from src.config import Settings
|
from src.config import Settings
|
||||||
from src.context.layer import ContextLayer
|
from src.context.layer import ContextLayer
|
||||||
@@ -17,6 +18,7 @@ from src.main import (
|
|||||||
KILL_SWITCH,
|
KILL_SWITCH,
|
||||||
_SESSION_RISK_LAST_BY_MARKET,
|
_SESSION_RISK_LAST_BY_MARKET,
|
||||||
_SESSION_RISK_OVERRIDES_BY_MARKET,
|
_SESSION_RISK_OVERRIDES_BY_MARKET,
|
||||||
|
_SESSION_RISK_PROFILES_MAP,
|
||||||
_STOPLOSS_REENTRY_COOLDOWN_UNTIL,
|
_STOPLOSS_REENTRY_COOLDOWN_UNTIL,
|
||||||
_apply_staged_exit_override_for_hold,
|
_apply_staged_exit_override_for_hold,
|
||||||
_compute_kr_atr_value,
|
_compute_kr_atr_value,
|
||||||
@@ -105,6 +107,8 @@ def _reset_kill_switch_state() -> None:
|
|||||||
_RUNTIME_EXIT_PEAKS.clear()
|
_RUNTIME_EXIT_PEAKS.clear()
|
||||||
_SESSION_RISK_LAST_BY_MARKET.clear()
|
_SESSION_RISK_LAST_BY_MARKET.clear()
|
||||||
_SESSION_RISK_OVERRIDES_BY_MARKET.clear()
|
_SESSION_RISK_OVERRIDES_BY_MARKET.clear()
|
||||||
|
_SESSION_RISK_PROFILES_MAP.clear()
|
||||||
|
main_module._SESSION_RISK_PROFILES_RAW = "__reset__"
|
||||||
_STOPLOSS_REENTRY_COOLDOWN_UNTIL.clear()
|
_STOPLOSS_REENTRY_COOLDOWN_UNTIL.clear()
|
||||||
yield
|
yield
|
||||||
KILL_SWITCH.clear_block()
|
KILL_SWITCH.clear_block()
|
||||||
@@ -112,6 +116,8 @@ def _reset_kill_switch_state() -> None:
|
|||||||
_RUNTIME_EXIT_PEAKS.clear()
|
_RUNTIME_EXIT_PEAKS.clear()
|
||||||
_SESSION_RISK_LAST_BY_MARKET.clear()
|
_SESSION_RISK_LAST_BY_MARKET.clear()
|
||||||
_SESSION_RISK_OVERRIDES_BY_MARKET.clear()
|
_SESSION_RISK_OVERRIDES_BY_MARKET.clear()
|
||||||
|
_SESSION_RISK_PROFILES_MAP.clear()
|
||||||
|
main_module._SESSION_RISK_PROFILES_RAW = "__reset__"
|
||||||
_STOPLOSS_REENTRY_COOLDOWN_UNTIL.clear()
|
_STOPLOSS_REENTRY_COOLDOWN_UNTIL.clear()
|
||||||
|
|
||||||
|
|
||||||
@@ -234,6 +240,76 @@ def test_stoploss_cooldown_minutes_uses_session_override() -> None:
|
|||||||
assert value == 45
|
assert value == 45
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_market_setting_ignores_profile_when_reload_disabled() -> None:
|
||||||
|
settings = Settings(
|
||||||
|
KIS_APP_KEY="k",
|
||||||
|
KIS_APP_SECRET="s",
|
||||||
|
KIS_ACCOUNT_NO="12345678-01",
|
||||||
|
GEMINI_API_KEY="g",
|
||||||
|
US_MIN_PRICE=5.0,
|
||||||
|
SESSION_RISK_RELOAD_ENABLED=False,
|
||||||
|
SESSION_RISK_PROFILES_JSON='{"US_PRE": {"US_MIN_PRICE": 9.5}}',
|
||||||
|
)
|
||||||
|
market = MagicMock()
|
||||||
|
market.code = "US_NASDAQ"
|
||||||
|
|
||||||
|
with patch("src.main.get_session_info", return_value=MagicMock(session_id="US_PRE")):
|
||||||
|
value = _resolve_market_setting(
|
||||||
|
market=market,
|
||||||
|
settings=settings,
|
||||||
|
key="US_MIN_PRICE",
|
||||||
|
default=5.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert value == pytest.approx(5.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_market_setting_falls_back_on_invalid_profile_json() -> None:
|
||||||
|
settings = Settings(
|
||||||
|
KIS_APP_KEY="k",
|
||||||
|
KIS_APP_SECRET="s",
|
||||||
|
KIS_ACCOUNT_NO="12345678-01",
|
||||||
|
GEMINI_API_KEY="g",
|
||||||
|
US_MIN_PRICE=5.0,
|
||||||
|
SESSION_RISK_PROFILES_JSON="{invalid-json",
|
||||||
|
)
|
||||||
|
market = MagicMock()
|
||||||
|
market.code = "US_NASDAQ"
|
||||||
|
|
||||||
|
with patch("src.main.get_session_info", return_value=MagicMock(session_id="US_PRE")):
|
||||||
|
value = _resolve_market_setting(
|
||||||
|
market=market,
|
||||||
|
settings=settings,
|
||||||
|
key="US_MIN_PRICE",
|
||||||
|
default=5.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert value == pytest.approx(5.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_market_setting_coerces_bool_string_override() -> None:
|
||||||
|
settings = Settings(
|
||||||
|
KIS_APP_KEY="k",
|
||||||
|
KIS_APP_SECRET="s",
|
||||||
|
KIS_ACCOUNT_NO="12345678-01",
|
||||||
|
GEMINI_API_KEY="g",
|
||||||
|
OVERNIGHT_EXCEPTION_ENABLED=True,
|
||||||
|
SESSION_RISK_PROFILES_JSON='{"US_AFTER": {"OVERNIGHT_EXCEPTION_ENABLED": "false"}}',
|
||||||
|
)
|
||||||
|
market = MagicMock()
|
||||||
|
market.code = "US_NASDAQ"
|
||||||
|
|
||||||
|
with patch("src.main.get_session_info", return_value=MagicMock(session_id="US_AFTER")):
|
||||||
|
value = _resolve_market_setting(
|
||||||
|
market=market,
|
||||||
|
settings=settings,
|
||||||
|
key="OVERNIGHT_EXCEPTION_ENABLED",
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert value is False
|
||||||
|
|
||||||
|
|
||||||
def test_estimate_pred_down_prob_from_rsi_uses_linear_mapping() -> None:
|
def test_estimate_pred_down_prob_from_rsi_uses_linear_mapping() -> None:
|
||||||
assert _estimate_pred_down_prob_from_rsi(None) == 0.5
|
assert _estimate_pred_down_prob_from_rsi(None) == 0.5
|
||||||
assert _estimate_pred_down_prob_from_rsi(0.0) == 0.0
|
assert _estimate_pred_down_prob_from_rsi(0.0) == 0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user