feat: Add evaluation hooks, skill adaptation and team pipeline config

- Add EvaluationHook for post-execution agent evaluation
- Add SkillAdaptationHook for dynamic skill adaptation
- Add team/ directory with team coordination logic
- Add TEAM_PIPELINE.yaml for smoke_fullstack pipeline config
- Update RuntimeView, TraderView and RuntimeSettingsPanel UI
- Add runtimeApi and websocket services
- Add runtime_state.json to smoke_fullstack state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 18:52:12 +08:00
parent f4a2b7f3af
commit 4b5ac86b83
87 changed files with 5042 additions and 744 deletions

View File

@@ -4,7 +4,7 @@ Scheduler - Market-aware trigger system for trading cycles
"""
import asyncio
import logging
from datetime import datetime, timedelta
from datetime import datetime, time, timedelta
from typing import Any, Callable, Optional
from zoneinfo import ZoneInfo
@@ -28,17 +28,21 @@ class Scheduler:
mode: str = "daily",
trigger_time: Optional[str] = None,
interval_minutes: Optional[int] = None,
heartbeat_interval: Optional[int] = None,
config: Optional[dict] = None,
):
self.mode = mode
self.trigger_time = trigger_time or "09:30" # NYSE timezone
self.trigger_now = self.trigger_time == "now"
self.interval_minutes = interval_minutes or 60
self.heartbeat_interval = heartbeat_interval # e.g. 3600 = 1 hour
self.config = config or {}
self.running = False
self._task: Optional[asyncio.Task] = None
self._heartbeat_task: Optional[asyncio.Task] = None
self._callback: Optional[Callable] = None
self._heartbeat_callback: Optional[Callable] = None
def _now_nyse(self) -> datetime:
"""Get current time in NYSE timezone"""
@@ -53,6 +57,15 @@ class Scheduler:
)
return len(valid_days) > 0
def _is_trading_hours(self, now: datetime) -> bool:
"""Check if current time is within NYSE trading hours (9:30-16:00 ET)."""
market_time = now.time()
return time(9, 30) <= market_time <= time(16, 0)
def set_heartbeat_callback(self, callback: Callable) -> None:
"""Register callback for heartbeat triggers."""
self._heartbeat_callback = callback
def _next_trading_day(self, from_date: datetime) -> datetime:
"""Find the next trading day from given date"""
check_date = from_date
@@ -72,6 +85,13 @@ class Scheduler:
self._callback = callback
self._schedule_task()
# Start heartbeat loop if configured
if self.heartbeat_interval and self._heartbeat_callback:
self._heartbeat_task = asyncio.create_task(self._run_heartbeat_loop())
logger.info(
f"Heartbeat loop started: interval={self.heartbeat_interval}s",
)
logger.info(
f"Scheduler started: mode={self.mode}, timezone=America/New_York",
)
@@ -132,6 +152,30 @@ class Scheduler:
return changed
async def _run_heartbeat_loop(self):
"""Run heartbeat checks on a separate interval during trading hours."""
while self.running:
now = self._now_nyse()
if self._is_trading_day(now) and self._is_trading_hours(now):
if self._heartbeat_callback:
try:
current_date = now.strftime("%Y-%m-%d")
logger.debug(
f"[Heartbeat] Triggering heartbeat check for {current_date}",
)
await self._heartbeat_callback(date=current_date)
except Exception as e:
logger.error(
f"[Heartbeat] Callback failed: {e}",
exc_info=True,
)
else:
logger.warning(
"[Heartbeat] Callback not set, skipping heartbeat",
)
await asyncio.sleep(self.heartbeat_interval)
async def _run_daily(self, callback: Callable):
"""Run once per trading day at specified time (NYSE timezone)"""
first_run = True
@@ -206,6 +250,9 @@ class Scheduler:
if self._task:
self._task.cancel()
self._task = None
if self._heartbeat_task:
self._heartbeat_task.cancel()
self._heartbeat_task = None
logger.info("Scheduler stopped")