config: agent_panel block (enabled/surfaces/min_quality/daily budget)
New optional config.json block for the upcoming LLM agent-panel feature:
enabled, telegram, dashboard (per-surface toggles), min_quality,
max_panels_per_day, reuse_ttl_seconds. Absent block → {} → feature dark.
Validation mirrors the deepseek block. Module 23 opened with parse +
reject tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SisdfkxnXNL9iZsXxN3T15
This commit is contained in:
+31
-1
@@ -86,7 +86,7 @@ _VALID_LOG_LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
|
|||||||
_SCANNABLE_TFS = ("M5", "M15", "H1", "H4")
|
_SCANNABLE_TFS = ("M5", "M15", "H1", "H4")
|
||||||
|
|
||||||
_KNOWN_TOP = {"telegram", "data_providers", "scanning", "pairs", "log_level",
|
_KNOWN_TOP = {"telegram", "data_providers", "scanning", "pairs", "log_level",
|
||||||
"oanda", "deepseek"}
|
"oanda", "deepseek", "agent_panel"}
|
||||||
_KNOWN_OANDA = {
|
_KNOWN_OANDA = {
|
||||||
"account_id", "api_token", "base_url", "lot_size",
|
"account_id", "api_token", "base_url", "lot_size",
|
||||||
"quality_gate", "max_hold_bars",
|
"quality_gate", "max_hold_bars",
|
||||||
@@ -94,6 +94,10 @@ _KNOWN_OANDA = {
|
|||||||
_KNOWN_DEEPSEEK = {
|
_KNOWN_DEEPSEEK = {
|
||||||
"api_key", "model", "enabled", "timeout_seconds", "daily_request_cap",
|
"api_key", "model", "enabled", "timeout_seconds", "daily_request_cap",
|
||||||
}
|
}
|
||||||
|
_KNOWN_AGENT_PANEL = {
|
||||||
|
"enabled", "telegram", "dashboard", "min_quality",
|
||||||
|
"max_panels_per_day", "reuse_ttl_seconds",
|
||||||
|
}
|
||||||
_KNOWN_TELEGRAM = {"token", "chat_id", "channel_id", "alert_min_quality"}
|
_KNOWN_TELEGRAM = {"token", "chat_id", "channel_id", "alert_min_quality"}
|
||||||
_KNOWN_SCANNING = {"pairs", "timeframes"}
|
_KNOWN_SCANNING = {"pairs", "timeframes"}
|
||||||
_KNOWN_PAIR_KEYS = {"blocked_hours_utc"}
|
_KNOWN_PAIR_KEYS = {"blocked_hours_utc"}
|
||||||
@@ -118,6 +122,7 @@ class AppConfig:
|
|||||||
pair_overrides: Dict[str, Dict[str, Any]] = field(default_factory=dict)
|
pair_overrides: Dict[str, Dict[str, Any]] = field(default_factory=dict)
|
||||||
oanda_settings: Dict[str, Any] = field(default_factory=dict)
|
oanda_settings: Dict[str, Any] = field(default_factory=dict)
|
||||||
deepseek_settings: Dict[str, Any] = field(default_factory=dict)
|
deepseek_settings: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
agent_panel_settings: Dict[str, Any] = field(default_factory=dict)
|
||||||
log_level: str = "INFO"
|
log_level: str = "INFO"
|
||||||
|
|
||||||
|
|
||||||
@@ -276,6 +281,31 @@ def load_app_config(path: str = CONFIG_PATH) -> AppConfig:
|
|||||||
"config.json: deepseek.model must be a non-empty string")
|
"config.json: deepseek.model must be a non-empty string")
|
||||||
app.deepseek_settings = dict(deepseek)
|
app.deepseek_settings = dict(deepseek)
|
||||||
|
|
||||||
|
panel = raw.get("agent_panel", {})
|
||||||
|
_check_keys(panel, _KNOWN_AGENT_PANEL, '"agent_panel"')
|
||||||
|
for flag in ("enabled", "telegram", "dashboard"):
|
||||||
|
if flag in panel and not isinstance(panel[flag], bool):
|
||||||
|
raise AppConfigError(
|
||||||
|
f"config.json: agent_panel.{flag} must be true or false")
|
||||||
|
if "min_quality" in panel and panel["min_quality"] not in _VALID_QUALITIES:
|
||||||
|
raise AppConfigError(
|
||||||
|
f'config.json: agent_panel.min_quality "{panel["min_quality"]}" '
|
||||||
|
f"not in {_VALID_QUALITIES}")
|
||||||
|
if "max_panels_per_day" in panel:
|
||||||
|
mpd = panel["max_panels_per_day"]
|
||||||
|
if isinstance(mpd, bool) or not isinstance(mpd, int) or mpd < 1:
|
||||||
|
raise AppConfigError(
|
||||||
|
"config.json: agent_panel.max_panels_per_day must be a "
|
||||||
|
"positive integer")
|
||||||
|
if "reuse_ttl_seconds" in panel:
|
||||||
|
ttl = panel["reuse_ttl_seconds"]
|
||||||
|
if isinstance(ttl, bool) or not isinstance(ttl, (int, float)) \
|
||||||
|
or ttl < 0:
|
||||||
|
raise AppConfigError(
|
||||||
|
"config.json: agent_panel.reuse_ttl_seconds must be a "
|
||||||
|
"non-negative number")
|
||||||
|
app.agent_panel_settings = dict(panel)
|
||||||
|
|
||||||
level = raw.get("log_level", app.log_level)
|
level = raw.get("log_level", app.log_level)
|
||||||
if level not in _VALID_LOG_LEVELS:
|
if level not in _VALID_LOG_LEVELS:
|
||||||
raise AppConfigError(
|
raise AppConfigError(
|
||||||
|
|||||||
@@ -40,5 +40,13 @@
|
|||||||
"timeout_seconds": 12,
|
"timeout_seconds": 12,
|
||||||
"daily_request_cap": 500
|
"daily_request_cap": 500
|
||||||
},
|
},
|
||||||
|
"agent_panel": {
|
||||||
|
"enabled": false,
|
||||||
|
"telegram": true,
|
||||||
|
"dashboard": false,
|
||||||
|
"min_quality": "B",
|
||||||
|
"max_panels_per_day": 20,
|
||||||
|
"reuse_ttl_seconds": 3600
|
||||||
|
},
|
||||||
"log_level": "INFO"
|
"log_level": "INFO"
|
||||||
}
|
}
|
||||||
|
|||||||
+36
@@ -6943,6 +6943,42 @@ def _():
|
|||||||
assert "must be > 0" in str(e)
|
assert "must be > 0" in str(e)
|
||||||
|
|
||||||
|
|
||||||
|
print("\n═══ Module 23: LLM agent panel ═══")
|
||||||
|
|
||||||
|
|
||||||
|
@test("app_config: agent_panel block parses; absent block → {}")
|
||||||
|
def _():
|
||||||
|
app = load_app_config(_write_json({"agent_panel": {
|
||||||
|
"enabled": True, "telegram": True, "dashboard": False,
|
||||||
|
"min_quality": "A", "max_panels_per_day": 10,
|
||||||
|
"reuse_ttl_seconds": 600}}))
|
||||||
|
assert app.agent_panel_settings["enabled"] is True
|
||||||
|
assert app.agent_panel_settings["min_quality"] == "A"
|
||||||
|
assert app.agent_panel_settings["max_panels_per_day"] == 10
|
||||||
|
assert load_app_config(_write_json({})).agent_panel_settings == {}
|
||||||
|
assert AppConfig().agent_panel_settings == {}
|
||||||
|
|
||||||
|
|
||||||
|
@test("app_config: agent_panel rejects unknown keys and bad types")
|
||||||
|
def _():
|
||||||
|
for payload in (
|
||||||
|
{"agent_panel": {"enabld": True}}, # unknown key
|
||||||
|
{"agent_panel": {"enabled": "yes"}}, # not a bool
|
||||||
|
{"agent_panel": {"telegram": 1}}, # not a bool
|
||||||
|
{"agent_panel": {"dashboard": "no"}}, # not a bool
|
||||||
|
{"agent_panel": {"min_quality": "Z"}}, # invalid grade
|
||||||
|
{"agent_panel": {"max_panels_per_day": 0}}, # not positive
|
||||||
|
{"agent_panel": {"max_panels_per_day": True}}, # bool is not an int
|
||||||
|
{"agent_panel": {"reuse_ttl_seconds": -5}}, # negative
|
||||||
|
{"agent_panel": "oops"}, # not an object
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
load_app_config(_write_json(payload))
|
||||||
|
assert False, f"should have raised for {payload}"
|
||||||
|
except AppConfigError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════
|
||||||
# SUMMARY
|
# SUMMARY
|
||||||
# ══════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
Reference in New Issue
Block a user