|
34 | 34 | StrategyRuleRef, |
35 | 35 | ) |
36 | 36 | from app.services.strategy_rules import RULE_REGISTRY, list_rules as list_strategy_rules |
37 | | -from app.services.hermes_ai_extensions import ( |
38 | | - AutoTradeLoop, |
39 | | - BacktestLab, |
40 | | - NewsInsightExtractor, |
41 | | - ResearchCardGenerator, |
42 | | - WeeklyReportBuilder, |
43 | | -) |
44 | | -from app.services.risk_guardian import RiskGuardian, risk_guardian_loop |
| 37 | +from app.services.backtest_lab import BacktestLab |
45 | 38 |
|
46 | 39 | from app.routers.kline import init_kline_router, kline_cache_loop |
47 | 40 | from app.routers.first_limit_alpha import init_first_limit_alpha_router |
@@ -133,46 +126,11 @@ def _load_custom_strategy(strategy_id: str) -> CustomStrategy: |
133 | 126 | kline_cache_service=kline_cache_service, |
134 | 127 | ) |
135 | 128 |
|
136 | | -risk_guardian = RiskGuardian( |
137 | | - paper_trading=paper_trading, |
138 | | - get_realtime_price=lambda sym: _get_realtime_price(sym), |
139 | | - is_market_open=lambda: _is_a_market_open(), |
140 | | -) |
141 | | - |
142 | | -auto_trade_loop = AutoTradeLoop( |
143 | | - paper_trading=paper_trading, |
144 | | - funnel_service=service, |
145 | | - hermes_memory=hermes_memory, |
146 | | - get_realtime_price=lambda sym: _get_realtime_price(sym), |
147 | | - is_market_open=lambda: _is_a_market_open(), |
148 | | -) |
149 | | - |
150 | 129 | backtest_lab = BacktestLab( |
151 | 130 | kline_store=_kline_store, |
152 | 131 | name_lookup=_qb_name_lookup, |
153 | 132 | ) |
154 | 133 |
|
155 | | -research_card_gen = ResearchCardGenerator( |
156 | | - runtime=hermes_runtime, |
157 | | - kronos_service=kronos_service, |
158 | | - kline_store=_kline_store, |
159 | | - data_provider=provider, |
160 | | - notice_service=notice_service, |
161 | | -) |
162 | | - |
163 | | -news_insight = NewsInsightExtractor( |
164 | | - runtime=hermes_runtime, |
165 | | - notice_service=notice_service, |
166 | | - funnel_service=service, |
167 | | -) |
168 | | - |
169 | | -weekly_report = WeeklyReportBuilder( |
170 | | - runtime=hermes_runtime, |
171 | | - funnel_service=service, |
172 | | - notice_service=notice_service, |
173 | | - paper_trading=paper_trading, |
174 | | -) |
175 | | - |
176 | 134 |
|
177 | 135 | async def _broadcast_snapshot() -> None: |
178 | 136 | funnel = await service.get_funnel() |
@@ -200,39 +158,6 @@ async def _ticker_loop() -> None: |
200 | 158 | await asyncio.sleep(60) |
201 | 159 |
|
202 | 160 |
|
203 | | -async def _auto_trade_loop() -> None: |
204 | | - """盘中每 60s 执行一次 auto_trade.tick(enabled=False 时快速返回)。""" |
205 | | - await asyncio.sleep(30) |
206 | | - while True: |
207 | | - try: |
208 | | - await auto_trade_loop.tick() |
209 | | - except Exception as exc: |
210 | | - print(f"[auto_trade] loop error: {exc}") |
211 | | - await asyncio.sleep(60) |
212 | | - |
213 | | - |
214 | | -async def _weekly_report_scheduler() -> None: |
215 | | - """周五 15:30 后自动生成周报并推飞书(每周一次)。""" |
216 | | - from app.services.time_utils import now_cn |
217 | | - await asyncio.sleep(120) |
218 | | - last_run_week: str | None = None |
219 | | - while True: |
220 | | - try: |
221 | | - n = now_cn() |
222 | | - if n.weekday() == 4 and n.hour == 15 and n.minute >= 30: |
223 | | - wk = n.strftime("%Y-W%W") |
224 | | - if last_run_week != wk: |
225 | | - print(f"[weekly_report] scheduled run at {n.isoformat()}") |
226 | | - try: |
227 | | - await weekly_report.generate() |
228 | | - last_run_week = wk |
229 | | - except Exception as exc: |
230 | | - print(f"[weekly_report] run failed: {exc}") |
231 | | - except Exception as exc: |
232 | | - print(f"[weekly_report] scheduler error: {exc}") |
233 | | - await asyncio.sleep(600) |
234 | | - |
235 | | - |
236 | 161 | async def _predict_funnel_scheduler_loop() -> None: |
237 | 162 | """收盘后 16:15 自动触发预测选股扫描(每日一次)。""" |
238 | 163 | from app.services.time_utils import now_cn |
@@ -298,13 +223,10 @@ async def _startup_backfill() -> None: |
298 | 223 | app.state.monitor_task = asyncio.create_task(monitor_loop(hermes_runtime, hub)) |
299 | 224 | app.state.predict_funnel_task = asyncio.create_task(_predict_funnel_scheduler_loop()) |
300 | 225 | app.state.hot_stock_ai_task = asyncio.create_task(_hot_stock_ai_scheduler_loop()) |
301 | | - app.state.risk_guardian_task = asyncio.create_task(risk_guardian_loop(risk_guardian, interval_seconds=30)) |
302 | | - app.state.auto_trade_task = asyncio.create_task(_auto_trade_loop()) |
303 | | - app.state.weekly_report_task = asyncio.create_task(_weekly_report_scheduler()) |
304 | 226 | yield |
305 | 227 | for key in [ |
306 | 228 | "backfill_task", "ticker_task", "kline_cache_task", "hermes_task", "monitor_task", |
307 | | - "predict_funnel_task", "hot_stock_ai_task", "risk_guardian_task", "auto_trade_task", "weekly_report_task", |
| 229 | + "predict_funnel_task", "hot_stock_ai_task", |
308 | 230 | ]: |
309 | 231 | task = getattr(app.state, key, None) |
310 | 232 | if task: |
@@ -635,123 +557,6 @@ async def backtest_custom_strategy( |
635 | 557 | raise HTTPException(status_code=500, detail=f"回测失败: {exc}") |
636 | 558 |
|
637 | 559 |
|
638 | | -# ============ Hermes AI 扩展能力 ============ |
639 | | - |
640 | | -@app.get("/api/hermes-ai/risk") |
641 | | -async def get_risk_snapshot(): |
642 | | - return risk_guardian.get_snapshot() |
643 | | - |
644 | | - |
645 | | -@app.post("/api/hermes-ai/risk/config") |
646 | | -async def update_risk_config(payload: dict): |
647 | | - risk_guardian.set_config(payload or {}) |
648 | | - return {"success": True, "snapshot": risk_guardian.get_snapshot()} |
649 | | - |
650 | | - |
651 | | -@app.post("/api/hermes-ai/risk/tick") |
652 | | -async def trigger_risk_tick(): |
653 | | - result = await risk_guardian.tick() |
654 | | - return {"success": True, "result": result, "snapshot": risk_guardian.get_snapshot()} |
655 | | - |
656 | | - |
657 | | -@app.get("/api/hermes-ai/auto-trade") |
658 | | -async def get_auto_trade(): |
659 | | - return auto_trade_loop.get_snapshot() |
660 | | - |
661 | | - |
662 | | -@app.post("/api/hermes-ai/auto-trade/config") |
663 | | -async def update_auto_trade_config(payload: dict): |
664 | | - auto_trade_loop.set_config(payload or {}) |
665 | | - return {"success": True, "snapshot": auto_trade_loop.get_snapshot()} |
666 | | - |
667 | | - |
668 | | -@app.post("/api/hermes-ai/auto-trade/tick") |
669 | | -async def trigger_auto_trade_tick(): |
670 | | - result = await auto_trade_loop.tick() |
671 | | - return {"success": True, "result": result, "snapshot": auto_trade_loop.get_snapshot()} |
672 | | - |
673 | | - |
674 | | -@app.get("/api/hermes-ai/backtest") |
675 | | -async def get_backtest(): |
676 | | - snap = backtest_lab.get_snapshot() |
677 | | - if not snap: |
678 | | - return {"generated_at": None, "message": "尚未执行过回测"} |
679 | | - return snap |
680 | | - |
681 | | - |
682 | | -@app.post("/api/hermes-ai/backtest/run") |
683 | | -async def run_backtest( |
684 | | - lookback_days: int = 25, |
685 | | - hold_days: int = 3, |
686 | | - tp_pct: float = 8.0, |
687 | | - sl_pct: float = -5.0, |
688 | | - amp_threshold: float = 0.20, |
689 | | - vol_cv_threshold: float = 0.40, |
690 | | - vol_spike_ratio: float = 3.0, |
691 | | - require_limit_up: bool = True, |
692 | | - limit: int | None = None, |
693 | | -): |
694 | | - if backtest_lab._running: |
695 | | - raise HTTPException(409, "回测正在运行中") |
696 | | - try: |
697 | | - return await backtest_lab.run( |
698 | | - lookback_days=lookback_days, |
699 | | - hold_days=hold_days, |
700 | | - tp_pct=tp_pct, |
701 | | - sl_pct=sl_pct, |
702 | | - amp_threshold=amp_threshold, |
703 | | - vol_cv_threshold=vol_cv_threshold, |
704 | | - vol_spike_ratio=vol_spike_ratio, |
705 | | - require_limit_up=require_limit_up, |
706 | | - limit=limit, |
707 | | - ) |
708 | | - except Exception as exc: |
709 | | - raise HTTPException(500, f"回测失败: {exc}") |
710 | | - |
711 | | - |
712 | | -@app.post("/api/hermes-ai/research/{symbol}") |
713 | | -async def gen_research_card(symbol: str, name: str = ""): |
714 | | - cached = research_card_gen.get_cached(symbol) |
715 | | - if cached: |
716 | | - return cached |
717 | | - try: |
718 | | - return await research_card_gen.generate(symbol, name) |
719 | | - except Exception as exc: |
720 | | - raise HTTPException(500, f"研报生成失败: {exc}") |
721 | | - |
722 | | - |
723 | | -@app.get("/api/hermes-ai/news-insight") |
724 | | -async def get_news_insight(): |
725 | | - snap = news_insight.get_snapshot() |
726 | | - if not snap: |
727 | | - return {"message": "尚未生成", "generated_at": None} |
728 | | - return snap |
729 | | - |
730 | | - |
731 | | -@app.post("/api/hermes-ai/news-insight/run") |
732 | | -async def run_news_insight(trade_date: str | None = None): |
733 | | - try: |
734 | | - return await news_insight.generate(trade_date=trade_date) |
735 | | - except Exception as exc: |
736 | | - raise HTTPException(500, f"消息分析失败: {exc}") |
737 | | - |
738 | | - |
739 | | -@app.get("/api/hermes-ai/weekly-report") |
740 | | -async def get_weekly_report(): |
741 | | - snap = weekly_report.get_snapshot() |
742 | | - if not snap: |
743 | | - return {"message": "尚未生成", "generated_at": None} |
744 | | - return snap |
745 | | - |
746 | | - |
747 | | -@app.post("/api/hermes-ai/weekly-report/run") |
748 | | -async def run_weekly_report(): |
749 | | - try: |
750 | | - return await weekly_report.generate() |
751 | | - except Exception as exc: |
752 | | - raise HTTPException(500, f"周报生成失败: {exc}") |
753 | | - |
754 | | - |
755 | 560 | @app.get("/api/notice/funnel") |
756 | 561 | async def get_notice_funnel(trade_date: str | None = None): |
757 | 562 | return await notice_service.get_notice_funnel(trade_date) |
|
0 commit comments