Add real-time market truth layer

This commit is contained in:
Codex
2026-05-07 00:12:12 +08:00
parent 057c1debd0
commit 82c1f20879
27 changed files with 2398 additions and 39 deletions
+2
View File
@@ -48,6 +48,7 @@ from src.api.training import (
submit_training_attempt,
)
from src.services.execution import ExecutionService
from src.services.market_state import start_market_truth_runtime
def build_api_dependencies() -> dict:
@@ -409,6 +410,7 @@ class APIRequestHandler(BaseHTTPRequestHandler):
def serve_api(host: str = "127.0.0.1", port: int = 8000) -> None:
start_market_truth_runtime()
server = ThreadingHTTPServer((host, port), APIRequestHandler)
print(f"AI ICT API listening on http://{host}:{port}")
server.serve_forever()
+57 -11
View File
@@ -9,7 +9,7 @@ from src.repositories.trade_plan_candidate_repository import TradePlanCandidateR
from src.repositories.trading_plan_repository import TradingPlanRepository
from src.services.execution import ExecutionService
from src.services.journal import TradingJournalService, WeeklyReviewService
from src.services.market_state import MarketStateService
from src.services.market_state import MarketStateService, get_market_truth_supervisor
from src.services.trade_plan_candidates import SetupWatcherService
from src.services.trading_plans import ACTIVE_PLAN_STATUS, TradingPlanService
@@ -68,14 +68,20 @@ def cancel_trading_plan(plan_id: int) -> Optional[dict]:
def get_trading_market_state(instrument_id: int, timeframe: str = "1m") -> dict:
service = MarketStateService()
service = build_market_state_service()
market_state = service.build_market_state(instrument_id=instrument_id, timeframe=timeframe)
snapshot_id = service.persist_market_state_snapshot(market_state=market_state)
quality_gate = service.repository.fetch_latest_backtest_quality_gate(instrument_id=instrument_id)
incidents = service.repository.fetch_recent_market_truth_incidents(
instrument_id=instrument_id,
timeframe=timeframe,
limit=10,
)
return {
"snapshot_id": snapshot_id,
"market_state": market_state.to_dict(),
"backtest_quality_gate": quality_gate,
"recent_incidents": incidents,
}
@@ -85,6 +91,17 @@ def run_trading_watch(
setup_tf: str = "1m",
trigger_tf: str = "1m",
) -> Optional[dict]:
market_state = build_market_state_service().build_market_state(
instrument_id=instrument_id,
timeframe=trigger_tf,
)
if market_truth_degraded(market_state):
return {
"candidate_generated": False,
"status": "market_truth_blocked",
"market_state": market_state.to_dict(),
"reason_codes": market_truth_reject_codes(market_state),
}
service = SetupWatcherService()
candidate = service.build_candidate(
instrument_id=instrument_id,
@@ -135,7 +152,7 @@ def build_ticket_from_candidate(
if not candidate.get("executable"):
raise ValueError("candidate_not_executable")
signal_id = ensure_trade_signal_for_candidate(candidate_row)
ticket = ExecutionService().create_execution_ticket(
ticket = build_execution_service().create_execution_ticket(
signal_id=signal_id,
execution_mode=execution_mode,
account_equity=account_equity,
@@ -145,12 +162,12 @@ def build_ticket_from_candidate(
def get_trading_ticket_by_id(ticket_id: int) -> Optional[dict]:
row = ExecutionService().repository.fetch_execution_ticket_by_id(ticket_id)
row = build_execution_service().repository.fetch_execution_ticket_by_id(ticket_id)
return dict(row) if row else None
def get_trading_executions(instrument_id: int, limit: int = 20) -> list[dict]:
return ExecutionService().build_execution_dashboard_items(instrument_id=instrument_id, limit=limit)
return build_execution_service().build_execution_dashboard_items(instrument_id=instrument_id, limit=limit)
def rerun_ticket_risk_check(
@@ -159,7 +176,7 @@ def rerun_ticket_risk_check(
account_equity: float = 10000.0,
risk_pct: float = 0.01,
) -> Optional[dict]:
service = ExecutionService()
service = build_execution_service()
ticket = service.repository.fetch_execution_ticket_by_id(ticket_id)
if not ticket:
return None
@@ -183,7 +200,7 @@ def rerun_ticket_risk_check(
def confirm_ticket_only(ticket_id: int, confirmed_by: str) -> Optional[dict]:
service = ExecutionService()
service = build_execution_service()
ticket = service.repository.fetch_execution_ticket_by_id(ticket_id)
if not ticket:
return None
@@ -239,13 +256,13 @@ def execute_ticket(ticket_id: int, *, confirmed_by: str, execution_mode_expected
raise ValueError("ticket_execution_mode_mismatch")
if ticket.get("status") != "confirmed":
raise ValueError("execution_ticket_not_confirmed")
service = ExecutionService()
service = build_execution_service()
full = service.submit_execution_ticket(ticket_id)
return full.to_dict() if full else None
def cancel_execution(execution_id: int) -> Optional[dict]:
service = ExecutionService()
service = build_execution_service()
return service.persist_execution_observation(
trade_execution=service.repository.fetch_execution_by_id(execution_id),
observation={"source": "manual", "status": "cancelled", "close_reason": "cancelled"},
@@ -253,11 +270,11 @@ def cancel_execution(execution_id: int) -> Optional[dict]:
def close_execution(execution_id: int) -> Optional[dict]:
return ExecutionService().close_execution_by_id(execution_id)
return build_execution_service().close_execution_by_id(execution_id)
def sync_execution(execution_id: int) -> Optional[dict]:
return ExecutionService().settle_execution_by_id_from_latest_backtest(execution_id)
return build_execution_service().settle_execution_by_id_from_latest_backtest(execution_id)
def generate_trading_journal(execution_id: Optional[int] = None) -> dict:
@@ -348,3 +365,32 @@ def ensure_trade_signal_for_candidate(candidate_row: dict) -> int:
if not created:
raise ValueError("signal_not_created_from_candidate")
return int(created["id"])
def build_market_state_service() -> MarketStateService:
return MarketStateService(market_truth_supervisor=get_market_truth_supervisor())
def build_execution_service() -> ExecutionService:
return ExecutionService(market_state_service=build_market_state_service())
def market_truth_degraded(market_state) -> bool:
payload = market_state.to_dict() if hasattr(market_state, "to_dict") else dict(market_state or {})
freshness_code = str(((payload.get("freshness") or {}).get("code")) or "")
stream_code = str(((payload.get("stream_state") or {}).get("code")) or "")
gap_code = str(((payload.get("data_gap_state") or {}).get("code")) or "")
return freshness_code in {"market_data_missing", "market_data_stale"} or stream_code in {
"market_stream_disconnected",
"market_stream_stale",
} or gap_code == "market_data_gap_detected"
def market_truth_reject_codes(market_state) -> list[str]:
payload = market_state.to_dict() if hasattr(market_state, "to_dict") else dict(market_state or {})
codes = [
str(((payload.get("freshness") or {}).get("code")) or ""),
str(((payload.get("stream_state") or {}).get("code")) or ""),
str(((payload.get("data_gap_state") or {}).get("code")) or ""),
]
return [code for code in codes if code and code not in {"market_data_fresh", "market_stream_connected", "market_data_gap_clear"}]