Files
ai-exchange/src/main.py
T

241 lines
11 KiB
Python

from dataclasses import asdict, is_dataclass
from datetime import datetime
from typing import Optional
from src.api import get_health, get_recent_backtest_summaries, get_recent_signals
from src.services.backtest import (
BacktestCostConfig,
BacktestQualityGate,
BacktestService,
evaluate_backtest_quality,
summarize_backtest_outcomes,
)
from src.services.bias import BiasService
from src.services.displacement import DisplacementService
from src.services.execution import AccountRiskState, ExecutionService
from src.services.fvg import FVGService
from src.services.liquidity import LiquidityPoolService
from src.services.ranges import DealingRangeService
from src.services.reports import IntradayReportService, PostTradeReviewService, PreMarketReportService
from src.services.sessions import SessionGenerator
from src.services.signals import MINIMAL_SIGNAL_EVIDENCE_KEYS, SignalService
from src.services.structure import MSSService
from src.services.sweeps import SweepService
from src.services.swings import SwingService
def build_default_runtime_dependencies(signal_required_evidence_keys: tuple[str, ...] = MINIMAL_SIGNAL_EVIDENCE_KEYS) -> dict:
return {
"session_generator": SessionGenerator(),
"swing_service": SwingService(),
"range_service": DealingRangeService(),
"liquidity_service": LiquidityPoolService(),
"sweep_service": SweepService(),
"displacement_service": DisplacementService(),
"mss_service": MSSService(),
"fvg_service": FVGService(),
"bias_service": BiasService(),
"signal_service": SignalService(required_evidence_keys=signal_required_evidence_keys),
"execution_service": ExecutionService(),
"backtest_service": BacktestService(),
"pre_market_report_service": PreMarketReportService(),
"intraday_report_service": IntradayReportService(),
"post_trade_review_service": PostTradeReviewService(),
"get_recent_signals": get_recent_signals,
"get_recent_backtest_summaries": get_recent_backtest_summaries,
}
def run_closed_loop(
instrument_id: int = 1,
timeframe: str = "1m",
backtest_limit: int = 100,
run_execution: bool = True,
backtest_cost_config: Optional[BacktestCostConfig] = None,
backtest_quality_gate: Optional[BacktestQualityGate] = None,
signal_required_evidence_keys: tuple[str, ...] = MINIMAL_SIGNAL_EVIDENCE_KEYS,
execution_account_state: Optional[AccountRiskState] = None,
dependencies: Optional[dict] = None,
) -> dict:
deps = dependencies or build_default_runtime_dependencies(signal_required_evidence_keys=signal_required_evidence_keys)
session_generator = deps["session_generator"]
now = datetime.now(session_generator.timezone)
session_written = session_generator.generate_for_date(instrument_id=instrument_id, timeframe=timeframe, session_date=now)
swing_service = deps["swing_service"]
swings = swing_service.detect_swings(instrument_id=instrument_id, timeframe=timeframe, left_bars=2, right_bars=2, limit=500)
swings_written = swing_service.persist_swings(instrument_id=instrument_id, timeframe=timeframe, candidates=swings)
range_service = deps["range_service"]
active_range = range_service.build_active_range(instrument_id=instrument_id, timeframe=timeframe)
ranges_written = range_service.persist_active_range(instrument_id=instrument_id, timeframe=timeframe, candidate=active_range)
liquidity_service = deps["liquidity_service"]
liquidity = liquidity_service.build_candidates(instrument_id=instrument_id, timeframe=timeframe)
liquidity_written = liquidity_service.persist_candidates(instrument_id=instrument_id, timeframe=timeframe, candidates=liquidity)
sweep_service = deps["sweep_service"]
sweeps = sweep_service.detect_sweeps(instrument_id=instrument_id, timeframe=timeframe)
sweeps_written = sweep_service.persist_sweeps(instrument_id=instrument_id, timeframe=timeframe, candidates=sweeps)
displacement_service = deps["displacement_service"]
displacements = displacement_service.detect_displacement(instrument_id=instrument_id, timeframe=timeframe, limit=200)
displacement_written = displacement_service.persist_displacement(instrument_id=instrument_id, timeframe=timeframe, candidates=displacements)
mss_service = deps["mss_service"]
mss_candidates = mss_service.detect_mss(instrument_id=instrument_id, timeframe=timeframe)
mss_written = mss_service.persist_mss(instrument_id=instrument_id, timeframe=timeframe, candidates=mss_candidates)
fvg_service = deps["fvg_service"]
fvgs = fvg_service.detect_fvg(instrument_id=instrument_id, timeframe=timeframe, limit=200)
fvg_written = fvg_service.persist_fvg(instrument_id=instrument_id, timeframe=timeframe, candidates=fvgs)
fvg_updated = fvg_service.update_existing_fvgs(instrument_id=instrument_id, timeframe=timeframe)
bias_service = deps["bias_service"]
bias = bias_service.build_bias(instrument_id=instrument_id, bias_tf=timeframe, setup_tf=timeframe, trigger_tf=timeframe)
bias_id = bias_service.persist_bias(instrument_id=instrument_id, bias_tf=timeframe, setup_tf=timeframe, trigger_tf=timeframe, candidate=bias)
signal_service = deps["signal_service"]
signal = signal_service.build_signal(instrument_id=instrument_id, setup_tf=timeframe, trigger_tf=timeframe)
signals_written = signal_service.persist_signal(instrument_id=instrument_id, setup_tf=timeframe, trigger_tf=timeframe, candidate=signal)
recent_signals = deps["get_recent_signals"](instrument_id=instrument_id, limit=1)
latest_signal = recent_signals[0] if recent_signals else None
execution_id = None
execution_record = None
if run_execution and latest_signal is not None:
execution_service = deps["execution_service"]
execution_id = execution_service.create_execution(
signal_id=latest_signal["id"],
execution_mode="paper",
account_state=execution_account_state,
)
execution_record = {
"status": "open" if execution_id is not None else "rejected",
"meta": {"risk_checks": [] if execution_id is not None else ["execution_creation_failed"]},
}
backtest_service = deps["backtest_service"]
backtest_run_id, outcomes = backtest_service.run_backtest(
instrument_id=instrument_id,
limit=backtest_limit,
cost_config=backtest_cost_config,
quality_gate=backtest_quality_gate,
)
backtest_summary = summarize_backtest_outcomes(outcomes)
backtest_quality_gate_result = evaluate_backtest_quality(backtest_summary, gate=backtest_quality_gate)
latest_outcome = _to_payload(outcomes[-1]) if outcomes else None
if execution_record is not None and latest_outcome is not None:
if execution_id is not None and hasattr(execution_service, "settle_latest_execution_for_signal"):
execution_record = execution_service.settle_latest_execution_for_signal(signal_id=latest_signal["id"], execution_mode="paper")
else:
execution_record = execution_service.persist_execution_settlement(
trade_execution={"id": execution_id, **execution_record},
backtest_result=latest_outcome,
)
if execution_record is None:
execution_record = {
"id": execution_id,
"status": "open" if execution_id is not None else "rejected",
"meta": {"lifecycle_state": None, "risk_checks": [] if execution_id is not None else ["execution_creation_failed"]},
}
recent_backtests = deps["get_recent_backtest_summaries"](instrument_id=instrument_id, limit=1)
latest_backtest = recent_backtests[0] if recent_backtests else None
pre_market_report = deps["pre_market_report_service"].build_report(
bias_snapshot=_to_payload(bias),
dealing_range=_to_payload(active_range),
current_price=_derive_current_price(signal),
session_code=(latest_signal or {}).get("session_code"),
)
intraday_report = deps["intraday_report_service"].build_report(
sweep_event=_to_payload(sweeps[-1]) if sweeps else None,
structure_event=_merge_structure_payload(mss_candidates[-1]) if mss_candidates else None,
fvg_zone=_to_payload(fvgs[-1]) if fvgs else None,
trade_signal=latest_signal,
)
post_trade_report = deps["post_trade_review_service"].build_report(
trade_execution=execution_record,
trade_signal=latest_signal,
backtest_result=latest_outcome,
)
return {
"health": get_health(),
"instrument_id": instrument_id,
"timeframe": timeframe,
"writes": {
"sessions": session_written,
"swings": swings_written,
"ranges": ranges_written,
"liquidity_pools": liquidity_written,
"sweeps": sweeps_written,
"displacements": displacement_written,
"mss": mss_written,
"fvg_detected": fvg_written,
"fvg_updated": fvg_updated,
"bias_snapshot_id": bias_id,
"signals": signals_written,
"execution_id": execution_id,
"execution_status": (execution_record or {}).get("status"),
"execution_lifecycle_state": ((execution_record or {}).get("meta") or {}).get("lifecycle_state"),
"backtest_run_id": backtest_run_id,
},
"reports": {
"pre_market": pre_market_report,
"intraday": intraday_report,
"post_trade": post_trade_report,
},
"latest_signal": latest_signal,
"latest_backtest": latest_backtest,
"backtest_quality_gate": backtest_quality_gate_result,
}
def main() -> None:
health = get_health()
print("AI ICT scaffold ready")
print(f"Database URL configured: {health['database_configured']}")
print(f"Session timezone: {health['session_timezone']}")
def _to_payload(value):
if value is None:
return None
if isinstance(value, dict):
return value
if is_dataclass(value):
return asdict(value)
if hasattr(value, "__dict__"):
return {
key: val
for key, val in vars(value).items()
if not key.startswith("_")
}
return value
def _merge_structure_payload(candidate) -> Optional[dict]:
payload = _to_payload(candidate)
if payload is None:
return None
payload["event_type"] = "MSS"
return payload
def _derive_current_price(signal) -> Optional[float]:
payload = _to_payload(signal)
if payload is None or not isinstance(payload, dict):
return None
entry_low = float(payload.get("entry_low") or 0.0)
entry_high = float(payload.get("entry_high") or 0.0)
if entry_low and entry_high:
return (entry_low + entry_high) / 2
return entry_low or entry_high or None
if __name__ == "__main__":
main()