From fcaedfb3586941c3fdf6755b6deedd5ead105edf Mon Sep 17 00:00:00 2001 From: eezd Date: Sat, 17 Oct 2026 15:12:10 +0800 Subject: [PATCH] Show recent filled plans in the demo analysis prompt List each plan filled in the last 24 hours with its zone, entry, fill and close times, close reason and net result, so the model can apply the skill rules on consecutive stops, re-entry after a stop and the daily entry cap. --- backend/src/alphapulse/trading/analyst.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/backend/src/alphapulse/trading/analyst.py b/backend/src/alphapulse/trading/analyst.py index 6ae0294..267963a 100644 --- a/backend/src/alphapulse/trading/analyst.py +++ b/backend/src/alphapulse/trading/analyst.py @@ -7,7 +7,7 @@ import asyncio import json import logging from dataclasses import dataclass -from datetime import UTC, datetime +from datetime import UTC, datetime, timedelta from decimal import Decimal from pydantic import ValidationError @@ -21,6 +21,7 @@ from alphapulse.core.db import SessionLocal from alphapulse.models import AiProvider, TradingAccount, TradingAnalysis, TradingEvent, TradingOrder, TradingSymbol from alphapulse.trading import plan as rules from alphapulse.trading.client import ExchangeError, mark_price, symbol_rules +from alphapulse.trading.executor import CLOSE_REASON_LABELS from alphapulse.trading.plan import PlanContext, PlanError, TradePlan, atr, check_setup, parse_bars from alphapulse.trading.snapshot import read_snapshot @@ -30,6 +31,8 @@ READ_TOOLS = ("search_news", "get_news", "search_symbols", "get_klines", "get_ti # 最后一轮只开放 submit_trade_plan,逼模型给出结论 MAX_ROUNDS = 6 RECENT_DECISIONS = 2 +# skill 的连续止损暂停、止损后冷却、每日开仓上限要靠这段成交记录判断 +RECENT_TRADES_HOURS = 24 DIRECTION_LABELS = {"long": "做多", "short": "做空"} CONFIDENCE_LABELS = {"high": "高", "medium": "中", "low": "低"} ACTION_LABELS = {"plan": "新计划", "keep": "维持当前计划", "exit": "结束当前计划", "wait": "观望"} @@ -107,6 +110,15 @@ def plan_brief(decision: dict, sizing: dict | None) -> dict: return brief +def trade_brief(a: TradingAnalysis) -> str: + setup = a.decision["setup"] + head = (f"#{a.id} {DIRECTION_LABELS[a.decision['direction']]},区间 {setup['zone_low']}–{setup['zone_high']}," + f"挂单 {a.sizing['entry']['price']},{local(a.first_fill_at)} 成交") + if a.plan_status != "closed": + return f"{head},{'持仓中' if a.plan_status == 'open' else '平仓中'}" + return f"{head},{local(a.closed_at)} {CLOSE_REASON_LABELS.get(a.close_reason, a.close_reason)},净 {Decimal(a.result['net']):.2f} USDT" + + async def recent_atr(symbol: str) -> Decimal: """最近已收盘 5m K 线的 ATR14,用于计算止损""" bars = parse_bars(await market_client.get("um", "klines", {"symbol": symbol, "interval": rules.ATR_INTERVAL, "limit": rules.ATR_PERIOD + 2})) @@ -152,6 +164,12 @@ async def prepare(symbol: str, reasons: list[str], analysis_id: int) -> Prepared .where(TradingAnalysis.symbol == symbol, TradingAnalysis.status == "done", TradingAnalysis.id != analysis_id) .order_by(TradingAnalysis.id.desc()).limit(RECENT_DECISIONS) )).all() + trades = (await session.scalars( + select(TradingAnalysis) + .where(TradingAnalysis.symbol == symbol, TradingAnalysis.dry_run.is_(False), + TradingAnalysis.first_fill_at >= datetime.now(UTC) - timedelta(hours=RECENT_TRADES_HOURS)) + .order_by(TradingAnalysis.first_fill_at.desc()) + )).all() current = await session.scalar( select(TradingAnalysis) .where(TradingAnalysis.symbol == symbol, TradingAnalysis.plan_status.in_(("pending", "open", "closing")), TradingAnalysis.dry_run.is_(False)) @@ -214,6 +232,9 @@ async def prepare(symbol: str, reasons: list[str], analysis_id: int) -> Prepared "## 最近决策(新的在前)", *([f"- {local(a.started_at)}{'(试运行)' if a.dry_run else ''}:{dump(plan_brief(a.decision, a.sizing))}" for a in recent] or ["无"]), "", + f"## 近 {RECENT_TRADES_HOURS} 小时已成交的计划(新的在前,共 {len(trades)} 次开仓)", + *([f"- {trade_brief(a)}" for a in trades] or ["无"]), + "", "## 预取的行情(实盘数据,已取,不要用相同参数重复调用)", await prefetch(symbol), ]