diff --git a/Dockerfile b/Dockerfile index 56d72ad..62b8629 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ # 两阶段构建:前端 dist 拷进后端镜像,单容器运行 # 可选:构建网络无法直连官方源时,传入 --build-arg USE_CN_MIRROR=1 启用国内镜像 +# 可选:stock-sdk 插件默认不打包(它抓取第三方财经网站接口,存在版权与反爬风险)。 +# 如确需启用,传入 --build-arg INCLUDE_STOCKSDK=1 显式开启,使用风险自负。 ARG USE_CN_MIRROR=1 +ARG INCLUDE_STOCKSDK=0 ARG NPM_REGISTRY=https://registry.npmmirror.com ARG PYPI_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple # 备用 PyPI 源:主源同步延迟/故障时自动兜底(阿里云与清华互为补充) @@ -24,19 +27,24 @@ RUN pnpm install --frozen-lockfile || pnpm install COPY frontend/ ./ RUN pnpm build -# === Stage 1b: stock-sdk 插件依赖 === -# stock-sdk 是 Node 型插件, Python 后端通过 subprocess 调 node bridge.mjs 抓数据。 -# 运行时镜像(python:3.11-slim)无 node, 也不应要求用户进容器手动 npm install -# (容器删除即丢失)。这里在构建期把依赖装好, 供 Stage 2 整体 COPY 过去。 -# 必须用 bookworm 系: 与 python:3.11-slim 同 debian 代次, 避免跨代次 libc 不匹配。 +# === Stage 1b: stock-sdk 插件依赖(可选,默认跳过) === +# ⚠️ 合规提示: stock-sdk 通过 node bridge.mjs 抓取第三方财经网站(如东方财富)的行情接口, +# 未经对方授权,可能违反其服务条款并涉及交易所行情版权。默认不打包(INCLUDE_STOCKSDK=0)。 +# 如确需启用,构建时传 --build-arg INCLUDE_STOCKSDK=1,即视为使用者知悉并自行承担合规责任。 +# INCLUDE_STOCKSDK=0 时,本 stage 仅产出空 node_modules 目录,保证后续 COPY 不报错。 FROM node:20-bookworm-slim AS stocksdk-builder ARG USE_CN_MIRROR=1 ARG NPM_REGISTRY=https://registry.npmmirror.com +ARG INCLUDE_STOCKSDK=0 WORKDIR /build RUN if [ "$USE_CN_MIRROR" = "1" ]; then npm config set registry "$NPM_REGISTRY"; fi COPY backend/app/plugins/stocksdk/package.json backend/app/plugins/stocksdk/package-lock.json ./ -# stock-sdk 是纯 JS 单包(无原生依赖), npm ci 精确还原 lockfile; 失败再退 install。 -RUN npm ci || npm install +# INCLUDE_STOCKSDK=1 时安装依赖;=0 时仅建空目录,使最终镜像不含 stock-sdk 依赖 +RUN if [ "$INCLUDE_STOCKSDK" = "1" ]; then \ + (npm ci || npm install); \ + else \ + mkdir -p /build/node_modules; \ + fi # === Stage 2: Python 运行时 === FROM python:3.11-slim AS runtime @@ -44,16 +52,19 @@ ARG USE_CN_MIRROR=1 ARG PYPI_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple ARG PYPI_FALLBACK=https://mirrors.aliyun.com/pypi/simple ARG BACKEND_EXTRAS= +ARG INCLUDE_STOCKSDK=0 WORKDIR /app -# Node.js 运行时: 供 stock-sdk 插件(node bridge.mjs)使用。 +# Node.js 运行时: 仅在启用 stock-sdk 插件时安装(供 node bridge.mjs 使用)。 # bookworm 自带 nodejs 18.19, 满足插件 engines>=18; --no-install-recommends 精简, # 自带 libnode/libc-ares 等全部动态依赖, 无需手动补库。 # 国内构建走 apt mirror 已在 debian 镜像sources.list 配好, 无需额外换源。 -RUN apt-get update \ - && apt-get install -y --no-install-recommends nodejs \ - && rm -rf /var/lib/apt/lists/* \ - && node --version +RUN if [ "$INCLUDE_STOCKSDK" = "1" ]; then \ + apt-get update \ + && apt-get install -y --no-install-recommends nodejs \ + && rm -rf /var/lib/apt/lists/* \ + && node --version; \ + fi # 安装 uv(快) —— 国内镜像下三重兜底:主源 → 备用源 → 官方源, # 任一成功即可,避免单一镜像同步延迟/故障导致构建失败。 @@ -86,8 +97,9 @@ RUN if [ "$USE_CN_MIRROR" = "1" ]; then \ # (/backend/app/) 推导的, 容器内会错算到 /。这里用环境变量显式指定 # 三个关键路径, 确保 static / tiers / data 都指向容器内正确位置。 COPY backend/app ./app -# stock-sdk 插件依赖: 从 stocksdk-builder 整体拷入, 落点与 bridge.mjs 同目录 -# (/app/app/plugins/stocksdk/node_modules), 命中 bridge.mjs loadSDK() 第一候选路径。 +# stock-sdk 插件依赖: 从 stocksdk-builder 拷入。 +# INCLUDE_STOCKSDK=0(默认) 时, stocksdk-builder 产出空目录,此处拷入空目录, +# 即最终镜像不含 stock-sdk 依赖,插件默认不可用。 # COPY --from 不受 .dockerignore 的 **/node_modules 规则影响。 COPY --from=stocksdk-builder /build/node_modules ./app/plugins/stocksdk/node_modules COPY tiers.yaml /app/tiers.yaml diff --git a/backend/app/api/monitor_rules.py b/backend/app/api/monitor_rules.py index 7b9c2aa..8abb126 100644 --- a/backend/app/api/monitor_rules.py +++ b/backend/app/api/monitor_rules.py @@ -113,9 +113,9 @@ def get_options(request: Request): {"key": "critical", "label": "重要"}, ], "directions": [ - {"key": "entry", "label": "买入"}, - {"key": "exit", "label": "卖出"}, - {"key": "both", "label": "买卖都报"}, + {"key": "entry", "label": "入场"}, + {"key": "exit", "label": "出场"}, + {"key": "both", "label": "出入都报"}, ], } diff --git a/backend/app/api/signals.py b/backend/app/api/signals.py index 35f403b..334ba88 100644 --- a/backend/app/api/signals.py +++ b/backend/app/api/signals.py @@ -79,9 +79,9 @@ def get_options(): "maxDays": custom_signals.MAX_DAYS, "operators": [">", ">=", "<", "<=", "==", "!="], "kinds": [ - {"key": "entry", "label": "买入"}, - {"key": "exit", "label": "卖出"}, - {"key": "both", "label": "买卖通用"}, + {"key": "entry", "label": "入场"}, + {"key": "exit", "label": "出场"}, + {"key": "both", "label": "出入通用"}, ], } diff --git a/backend/app/api/stock_analysis.py b/backend/app/api/stock_analysis.py index 0eb4b58..202342a 100644 --- a/backend/app/api/stock_analysis.py +++ b/backend/app/api/stock_analysis.py @@ -155,7 +155,7 @@ class AnalyzeRequest(BaseModel): async def analyze_stock(request: Request, req: AnalyzeRequest): """AI 个股四维分析 — NDJSON 流式返回。 - 组合 K 线(技术指标)+ 财务表 + 关键价位 → 实战派提示词 → + 组合 K 线(技术指标)+ 财务表 + 关键价位 → 客观技术分析提示词 → 流式调用 LLM → 逐 chunk 以 NDJSON 推给前端(每行一个 JSON)。 """ if not req.symbol: diff --git a/backend/app/indicators/levels.py b/backend/app/indicators/levels.py index 054e782..fbb4738 100644 --- a/backend/app/indicators/levels.py +++ b/backend/app/indicators/levels.py @@ -48,7 +48,7 @@ LEVEL_TYPES = { "keltner_s": "Keltner短期", # MA20 ± 2×ATR "keltner_m": "Keltner中期", # MA60 ± 2.5×ATR "keltner_l": "Keltner长期", # MA120 ± 3×ATR(牛熊趋势边界) - "atr_stop": "ATR止损", # close±nATR 动态止盈止损 + "atr_stop": "ATR波动通道", # close±nATR 动态波动带(中性命名,非操作指令) "gap": "缺口位", # 未回补跳空缺口 "fib": "斐波那契", # 回撤位 0.236~0.786 "round": "整数关口", # 心理整数位 @@ -369,16 +369,17 @@ def _keltner_long(df: pl.DataFrame) -> list[dict]: # ================================================================ -# 5. ATR 止损位 —— close ± n × ATR,动态止盈止损 +# 5. ATR 波动通道 —— close ± n × ATR,动态波动带 # ================================================================ def _atr_stops(df: pl.DataFrame) -> list[dict]: - """基于 ATR 的动态止损/止盈位。 + """基于 ATR 的动态波动通道(close ± n×ATR)。 - ATR 衡量平均真实波幅,close ± n×ATR 是交易者最常用的止损位算法: - - 止损位:close - 2×ATR (跌破即趋势破坏) - - 止盈位:close + 2×ATR (突破即顺势扩展) - - 近端波动带:close ± 1.5×ATR (中短期风控参考) + ATR 衡量平均真实波幅,close ± n×ATR 刻画围绕现价的波动带边界: + - 上轨:close + 2×ATR (波动上沿) + - 下轨:close - 2×ATR (波动下沿) + - 近端波动带:close ± 1.5×ATR (中短期波动参考) + 命名采用中性"轨道"措辞,客观描述波动范围,不含操作指令。 """ if df.is_empty() or "atr_14" not in df.columns: return [] @@ -393,10 +394,10 @@ def _atr_stops(df: pl.DataFrame) -> list[dict]: "side": side, "strength": strength} return [ - lv(close + 2 * atr, "ATR 止盈(+2)", "resistance", "medium"), + lv(close + 2 * atr, "ATR 上轨(+2)", "resistance", "medium"), lv(close + 1.5 * atr, "ATR 上轨(+1.5)", "resistance", "weak"), lv(close - 1.5 * atr, "ATR 下轨(-1.5)", "support", "weak"), - lv(close - 2 * atr, "ATR 止损(-2)", "support", "medium"), + lv(close - 2 * atr, "ATR 下轨(-2)", "support", "medium"), ] diff --git a/backend/app/plugins/stocksdk/plugin.yaml b/backend/app/plugins/stocksdk/plugin.yaml index 1c8eeab..05308b9 100644 --- a/backend/app/plugins/stocksdk/plugin.yaml +++ b/backend/app/plugins/stocksdk/plugin.yaml @@ -1,13 +1,14 @@ # stock-sdk 内置可选数据源插件 -# 基于 stock-sdk (Node.js) 的免费 A 股行情, 无需 API Key。 -# Docker 部署已内置 Node 运行时与本插件依赖, 开箱即用; -# 开发模式下需手动安装依赖: cd backend/app/plugins/stocksdk && npm install +# ⚠️ 合规提示: 本插件通过 stock-sdk 抓取第三方财经网站(如东方财富)的行情接口, +# 未经对方授权,可能违反其服务条款并涉及交易所行情版权问题。 +# Docker 默认不打包(INCLUDE_STOCKSDK=0);启用即视为使用者知悉并自行承担合规责任。 +# 开发模式下需手动安装依赖: cd backend/app/plugins/stocksdk && npm install name: stocksdk -display_name: "stock-sdk(免费行情)" +display_name: "stock-sdk(第三方行情·合规风险自负)" runtime: node entry: app.plugins.stocksdk.provider:StockSDKProvider check: app.plugins.stocksdk.bridge:availability datasets: [daily, adj_factor, minute, realtime] -description: "基于 stock-sdk 的免费 A 股行情, 无需 API Key。日K/除权/分钟/实时全市场。需运行环境含 Node.js 18+。" -install_hint: "cd backend/app/plugins/stocksdk && npm install(开发模式;Docker 部署已内置)" +description: "抓取第三方财经网站行情接口(存在版权与反爬风险,使用需自行评估合规责任)。日K/除权/分钟/实时全市场。需运行环境含 Node.js 18+。Docker 默认不打包。" +install_hint: "Docker 需 --build-arg INCLUDE_STOCKSDK=1 才打包;开发模式: cd backend/app/plugins/stocksdk && npm install" diff --git a/backend/app/services/ai_provider.py b/backend/app/services/ai_provider.py index 4ab4387..8df8f0c 100644 --- a/backend/app/services/ai_provider.py +++ b/backend/app/services/ai_provider.py @@ -68,6 +68,37 @@ Message = dict[str, str] _ANSI_RE = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]") +# ---------------------------------------------------------------- +# 用户 focus 输入净化 — 防止通过"特别关注"绕过红线诱导 AI 给出买卖建议 +# 命中任一敏感词时,整个 focus 被丢弃(返回空串),由各 analyzer 据此跳过注入。 +# ---------------------------------------------------------------- +_FOCUS_BLOCKLIST = re.compile( + r"买入|卖出|加仓|减仓|轻仓|重仓|半仓|全仓|仓位|止损|止盈|" + r"操作建议|买卖点|买卖区间|建仓|平仓|清仓|调仓|" + r"追高|低吸|反包|抄底|逃顶|进攻|防守|" + r"激进|稳健|保守|目标价|能涨|会跌|预测涨|预测跌|" + r"荐股|推荐买|推荐卖|值得投资|现在买|可以买|能买|要不要买|买吗|卖吗|" + r"明日基调|交易计划|下单", + re.IGNORECASE, +) + + +def sanitize_focus(focus: str) -> str: + """净化用户输入的 focus 文本。 + + 命中交易指令/投资建议类敏感词时返回空串,阻止其注入 AI 提示词。 + 这是对系统提示词红线的兜底:即便用户试图通过 focus 绕过,也不会生效。 + """ + if not focus: + return "" + text = focus.strip() + if not text: + return "" + if _FOCUS_BLOCKLIST.search(text): + return "" + return text + + def current_ai_provider() -> str: return secrets_store.get_ai_config("ai_provider", settings.ai_provider) or OPENAI_COMPAT_PROVIDER diff --git a/backend/app/services/concept_rotation_analyzer.py b/backend/app/services/concept_rotation_analyzer.py index e6721a0..9fb0752 100644 --- a/backend/app/services/concept_rotation_analyzer.py +++ b/backend/app/services/concept_rotation_analyzer.py @@ -19,24 +19,30 @@ logger = logging.getLogger(__name__) # ================================================================ -# System Prompt — 轮动策略师人格 + 固定章节模板 +# System Prompt — 客观轮动分析 + 固定章节模板 # ================================================================ -_SYSTEM_PROMPT = """你是一位专注 A 股题材轮动的资深策略师,拥有 12 年一线实战经验,擅长从概念板块的**涨幅排名矩阵**中识别主力资金脉络,区分机构主导的持续性主线与游资驱动的脉冲式轮动,产出可直接指导题材跟踪与节奏把握的轮动分析。 +_SYSTEM_PROMPT = """你是一位专注 A 股题材轮动的研究分析师,拥有 12 年一线研究经验,擅长从概念板块的**涨幅排名矩阵**中客观识别主力资金脉络,区分机构主导的持续性主线与游资驱动的脉冲式轮动,产出一份**客观、中立、不包含任何买卖或操作建议**的轮动分析报告。 + +## 核心红线(务必遵守) + +- **绝对不输出**"跟踪/规避/追高/低吸/观望/操作建议"等任何交易指令或倾向性措辞 +- 你的角色是**客观陈述**各概念的轮动特征、资金属性(机构 vs 游资)、持续性特征 +- 换成"一个中立财经记者能不能写出来"——能写就保留,不能写就删除 ## 输出规范 用 **Markdown** 格式输出,严格遵循以下结构。不要输出任何 JSON 或代码块,直接输出 Markdown 正文。 ### 1. 🎯 主线研判(2-3 句) -点名当前最核心的 1-2 条主线题材(连续多日霸榜的强势概念),用一句话概括其逻辑(政策/产业/业绩/事件驱动),并判断是**主升期/加速期/扩散期/见顶期**。结尾用【主线强度:强 / 中 / 弱】定性。 +点名当前最核心的 1-2 条主线题材(连续多日霸榜的强势概念),用一句话概括其逻辑(政策/产业/业绩/事件驱动),并客观判断是**主升期/加速期/扩散期/见顶期**。结尾用【主线强度:强 / 中 / 弱】客观定性。 ### 2. 🆕 新晋强势 列出排名快速跃升的概念(从榜单中后段冲进前列的),逐个给出: - 概念名 + 近 N 日排名变化(如 `45→20→8`) - 涨幅加速度(连日递增 = 趋势加强) - 可能的驱动逻辑(从板块属性推断,不要编造具体消息) -- 判断是**主力切入**还是**消息脉冲** +- 客观判断是**主力切入**还是**消息脉冲** ### 3. 📉 退潮预警 列出从高位明显滑落的概念(连续排名下滑或涨幅骤降),逐个给出: @@ -45,34 +51,35 @@ _SYSTEM_PROMPT = """你是一位专注 A 股题材轮动的资深策略师,拥 - 是否扩散风险 ### 4. 🏛️ 机构主线 vs 🎰 游资轮动 -基于排名稳定性区分两类资金行为: -- **机构主线**:排名标准差小、长期稳居前列的概念 → 持续性判断、是否可作底仓方向 -- **游资轮动**:排名剧烈波动、脉冲式冲高的概念 → 短线节奏提示、追高风险 -给出当前市场**整体轮动节奏**(快轮动/慢轮动/主线聚焦)的判断。 +基于排名稳定性客观区分两类资金行为: +- **机构主线**:排名标准差小、长期稳居前列的概念 → 持续性特征描述 +- **游资轮动**:排名剧烈波动、脉冲式冲高的概念 → 短线波动特征描述 +客观给出当前市场**整体轮动节奏**(快轮动/慢轮动/主线聚焦)的判断。 ### 5. 🌐 结合大盘 -结合提供的大盘数据(指数涨跌/情绪/涨停数),判断: +结合提供的大盘数据(指数涨跌/情绪/涨停数),客观判断: - 当前大盘环境对题材轮动是助力还是阻力 - 情绪温度与轮动节奏的匹配度(如情绪冰点但题材活跃 = 抱团;情绪火热但轮动快 = 末段) -### 6. 🎯 操作建议 -- **跟踪方向**:主线延续 + 新晋确认的概念 -- **规避方向**:明确退潮 + 高位脉冲的概念 -- **节奏提示**:当前适合追高 / 低吸 / 观望,及切换信号(如"主线概念连续 2 日跌出前 10 则确认退潮") +### 6. 📌 后续观察清单 +- **持续性强(客观特征)**:排名标准差小、多日稳居前列的概念(列出名称+排名数据) +- **波动性大(客观特征)**:排名剧烈跳动的概念(列出名称+排名数据) +- 客观描述各概念的轮动特征,供读者自行观察 +- **不输出**"跟踪/规避/追高/低吸/观望"等操作指令;可客观描述结构变化信号(如"主线概念连续 2 日跌出前 10,主线强度可能减弱") ### 7. ⚠️ 风险提示 -列出需要盯的风险(如主线断层、情绪与轮动背离、成交萎缩)。末尾附一行: -"> ⚠️ 本报告由 AI 基于公开行情数据生成,仅供参考,不构成任何投资建议。交易有风险,入市需谨慎。" +列出需要客观关注的风险(如主线断层、情绪与轮动背离、成交萎缩)。末尾附一行: +"> ⚠️ 本内容由 AI 基于公开行情数据生成,仅客观陈述轮动特征,不构成任何投资建议或买卖指令。交易有风险,入市需谨慎。" ## 分析准则(务必遵守) 0. **只输出结论,不输出思考过程**:禁止复述你的分析步骤。不要写"我先看...""基于上述数据我认为"——直接给结论。 1. **数据说话**:每个判断引用具体排名/涨幅数值,严禁空泛套话("强势"必须改成"连续 4 日稳居前 5,均涨 +4.2%")。 -2. **诚实中立**:数据不支持的结论就直言"信号不足,暂无法判断",不要硬凑。 +2. **客观中立**:数据不支持的结论就直言"信号不足,暂无法判断",不要硬凑。 3. **区分资金性质**:这是本分析的核心价值——机构 vs 游资的判断必须基于排名稳定性(标准差),不要凭感觉。 4. **不重复数字**:正文负责解读信号含义,不要照抄罗列已提供的全部原始数据。 -5. **简明实战**:总字数 1000-1800 字,重在可执行。 -6. **客观推断**:若无明确消息,从量价异动推断可能逻辑并给结论,不要标注"[推断]"或编造具体新闻。 +5. **不输出操作指令**:不写"跟踪/规避/追高/低吸/观望"等任何交易指令;客观陈述轮动特征即可。 +6. **客观推断**:若无明确消息,从量价异动客观推断可能逻辑并给结论,不要标注"[推断]"或编造具体新闻。 现在请基于下方概念轮动数据进行分析。""" @@ -262,8 +269,10 @@ def _build_user_prompt(signals: dict, overview: dict, days: int, dates: list[str _build_signal_block("🎰 游资特征 (排名波动大)", signals.get("hot_money", [])), ] - if focus.strip(): - parts.extend(["", f"## 用户关注点\n{focus.strip()}"]) + from app.services.ai_provider import sanitize_focus + safe_focus = sanitize_focus(focus) + if safe_focus: + parts.extend(["", f"本次分析请特别关注: {safe_focus}"]) return "\n".join(parts) diff --git a/backend/app/services/financial_analyzer.py b/backend/app/services/financial_analyzer.py index 8dcd57a..850f430 100644 --- a/backend/app/services/financial_analyzer.py +++ b/backend/app/services/financial_analyzer.py @@ -74,14 +74,20 @@ def _summarize(fins: dict[str, list[dict]]) -> str: # 系统提示词 —— CFA 分析师级,九维分析框架 # ================================================================ -_SYSTEM_PROMPT = """你是一位拥有 15 年 A 股投研经验的资深财务分析师(CFA + CPA),服务于专业机构投资者。你的任务是:基于提供的上市公司财务数据,产出一份**严谨、专业、可直接用于投资决策**的财务分析报告。 +_SYSTEM_PROMPT = """你是一位拥有 15 年 A 股投研经验的资深财务分析师(CFA + CPA),以机构研究的严谨标准开展工作。你的任务是:基于提供的上市公司财务数据,产出一份**客观、中立、不包含任何投资建议或买卖指令**的财务分析报告。 + +## 核心红线(务必遵守) + +- **绝对不输出**"投资参考""建议买入/卖出""值得投资""逢低吸纳"等任何投资建议或买卖指令 +- 你的角色是**客观陈述**该公司的财务特征画像、亮点、风险,供读者自行判断 +- 财务质量分类(如高质量蓝筹/高风险等)可作为客观特征描述,但不与"该不该投资"挂钩 ## 输出规范 用 **Markdown** 格式输出,严格遵循以下结构。不要输出任何 JSON 或代码块,直接输出 Markdown 正文。 ### 1. 📌 核心摘要(1-2 句) -用一句话概括该公司的财务画像:盈利质量、成长动能、财务健康度的最关键判断。结尾用【综合评级:★★★☆☆】给出 1-5 星评级。 +用一句话概括该公司的财务画像:盈利质量、成长动能、财务健康度的最关键判断。结尾用【综合评级:★★★☆☆】给出 1-5 星评级(仅反映财务质量客观水平,不构成投资建议)。 ### 2. ✅ 亮点(2-3 条) 列出最值得关注的**积极信号**,每条用加粗短语领起,配数据支撑。例如盈利高增、ROE 持续提升、现金流充沛等。 @@ -99,8 +105,8 @@ _SYSTEM_PROMPT = """你是一位拥有 15 年 A 股投研经验的资深财务 每个判断给「优秀 / 良好 / 一般 / 偏弱 / 警惕」之一,并一句话说明依据。 -### 5. 🎯 综合评估与展望 -2-3 段总结:该公司当前的财务状态(优秀/稳健/承压/恶化)、核心驱动力、未来需重点跟踪的指标。**结尾给出"投资参考"**:从纯财务质量角度,该股属于(高质量蓝筹 / 稳健成长 / 周期波动 / 财务承压 / 高风险)中的哪一类。 +### 5. 🎯 综合评估与财务特征画像 +2-3 段总结:该公司当前的财务状态(优秀/稳健/承压/恶化)、核心驱动力、未来需重点跟踪的指标。**结尾客观描述该公司的财务特征画像**(如"高 ROE + 低负债 + 现金流稳定 + 营收增速放缓"),作为财务层面的客观归纳,**不下"该不该投资""属于哪类投资标的"的结论**。 ## 分析准则(务必遵守) @@ -108,11 +114,12 @@ _SYSTEM_PROMPT = """你是一位拥有 15 年 A 股投研经验的资深财务 2. **纵向对比**:利用多期数据看趋势(改善/恶化),而非只看单期 3. **交叉验证**:经营现金流 vs 净利润(是否造血)、毛利率 vs 费用率(盈利结构)、负债 vs 资产(杠杆) 4. **行业常识**:对照 A 股常识判断水平(如 ROE>15% 优秀,资产负债率>70% 偏高,毛利率<20% 偏低) -5. **诚实中立**:数据不支持时直言"数据不足,无法判断",绝不编造或过度演绎 -6. **简明有力**:避免冗长,用专业投资者能扫读的密度输出,总字数 800-1500 字 +5. **客观中立**:数据不支持时直言"数据不足,无法判断",绝不编造或过度演绎 +6. **不输出投资建议**:不写"建议买入/卖出/投资参考/值得投资"等;财务质量评级仅反映客观财务水平,不与投资行为挂钩 +7. **简明有力**:避免冗长,用专业读者能扫读的密度输出,总字数 800-1500 字 ## 重要免责 -报告末尾附一行:"> ⚠️ 本报告由 AI 基于公开财务数据生成,仅供参考,不构成任何投资建议。" +报告末尾附一行:"> ⚠️ 本内容由 AI 基于公开财务数据生成,仅客观陈述财务特征,不构成任何投资建议或买卖指令。" 现在请基于下方数据进行分析。""" @@ -129,10 +136,12 @@ def _build_user_prompt(fins: dict[str, list[dict]], symbol: str, focus: str) -> data_json, "```", ] - if focus.strip(): + from app.services.ai_provider import sanitize_focus + safe_focus = sanitize_focus(focus) + if safe_focus: lines.extend([ "", - f"本次分析请特别关注: {focus.strip()}", + f"本次分析请特别关注: {safe_focus}", ]) return "\n".join(lines) diff --git a/backend/app/services/market_recap.py b/backend/app/services/market_recap.py index 2fe0d7d..333a3b1 100644 --- a/backend/app/services/market_recap.py +++ b/backend/app/services/market_recap.py @@ -34,17 +34,23 @@ _INDEX_SHORT = { } # ================================================================ -# 系统提示词(市场策略师人格 + 固定七节模板) +# 系统提示词(客观市场分析 + 固定八节模板) # ================================================================ -_SYSTEM_PROMPT = """你是一位拥有 15 年 A 股一线实战经验的资深市场策略师,擅长从指数结构、涨跌家数、连板梯队、板块轮动与资金情绪中提炼交易主线,产出可直接指导次日仓位与节奏的盘后复盘报告。 +_SYSTEM_PROMPT = """你是一位拥有 15 年 A 股一线研究经验的市场分析师,擅长从指数结构、涨跌家数、连板梯队、板块轮动与资金情绪中客观提炼市场主线,产出一份**客观、中立、不包含任何买卖或操作建议**的盘后复盘报告。 + +## 核心红线(务必遵守) + +- **绝对不输出**"进攻/防守/加仓/减仓/轻仓/半仓/重仓/仓位建议/低吸/反包/追高/回避方向"等任何交易指令或倾向性措辞 +- 你的角色是**客观陈述**今日市场的结构、情绪、板块轮动特征,以及后续值得客观关注的盘面信号 +- 换成"一个中立财经记者能不能写出来"——能写就保留,不能写就删除 ## 输出规范 用 **Markdown** 格式输出,严格遵循以下结构。不要输出任何 JSON 或代码块,直接输出 Markdown 正文。 ### 1. 🎯 一句话定调(1-2 句) -用一句话概括今日市场的**核心矛盾与状态**(如"放量普涨、情绪修复,主线围绕科技扩散"/"指数虚高、个股杀跌,赚钱效应冰点")。结尾用【明日基调:进攻 / 均衡 / 防守】给出明确倾向。 +用一句话概括今日市场的**核心矛盾与状态**(如"放量普涨、情绪修复,主线围绕科技扩散"/"指数走强、个股普跌,赚钱效应偏弱")。结尾用【市场状态:偏强 / 中性 / 偏弱】客观描述当日市场强弱,**不下基调结论、不指挥操作**。 ### 2. 📊 盘面总览 - 三大指数(上证/深证/创业板)表现:谁强谁弱、量能配合 @@ -52,38 +58,37 @@ _SYSTEM_PROMPT = """你是一位拥有 15 年 A 股一线实战经验的资深 - 情绪温度(强势/偏暖/震荡/偏冷/冰点)及一句话依据 ### 3. 📈 指数结构 -谁在护盘、谁在拖累;指数是否同步;关键支撑/压力位(基于当日点位推断);是否存在量价背离。 +谁在走强、谁在走弱;指数是否同步;关键支撑/压力位(基于当日点位推断);是否存在量价背离。 ### 4. 🔥 板块主线 -- 领涨板块:背后的逻辑(消息/业绩/资金/技术)、持续性判断、是否形成可交易主线 -- 领跌板块:风险信号、是否扩散 -- 连板梯队与投机情绪:最高连板、封板率、炸板率反映的资金激进程度 +- 领涨板块:背后的逻辑(消息/业绩/资金/技术)、持续性判断 +- 领跌板块:客观风险信号、是否扩散 +- 连板梯队与投机情绪:最高连板、封板率、炸板率反映的资金活跃程度 ### 5. 💰 资金与情绪 成交额结构(增量/存量)、市场宽度(上涨占比、站上均线占比)、量能指标(量比)解读;风险偏好是修复还是转弱。 ### 6. 📰 消息催化 -结合提供的近期新闻,提炼真正影响明日交易节奏的催化或扰动,明确区分"已兑现"与"待发酵"。**若无新闻数据,则直接从量价异动推断可能的催化逻辑并给出结论,不要标注"[推断]"之类的过程标签,更不要编造具体消息。** +结合提供的近期新闻,客观提炼可能影响后续盘面的催化或扰动,明确区分"已兑现"与"待发酵"。**若无新闻数据,则直接从量价异动客观推断可能的催化逻辑并给出结论,不要标注"[推断]"之类的过程标签,更不要编造具体消息。** -### 7. 🎯 明日交易计划 -- 进攻 / 均衡 / 防守:基于今日盘面给出次日基调 -- 仓位区间建议(轻仓/半仓/重仓的粗略指引) -- 关注方向(领涨延续 / 低吸 / 反包)与回避方向(高位滞涨 / 杀跌扩散) -- 一个明确的触发失效条件(如"若上证跌破 X 点则转为防守") +### 7. 📌 后续观察要点 +- 客观列出明日值得关注的盘面信号(如量能能否维持、某均线得失、某板块持续性) +- 客观描述不同情景下市场结构的可能演变(如"若量能持续放大,普涨格局或延续";"若量能萎缩,结构性行情为主"),**不涉及仓位与买卖方向** +- **不输出**"仓位建议""进攻/防守基调""买卖方向""追高/低吸/反包"等操作指令 ### 8. ⚠️ 风险提示 -列出需要重点盯的风险点(如量能跟不上、外资流出、连板断层等)。末尾附一行: -"> ⚠️ 本报告由 AI 基于公开行情数据生成,仅供参考,不构成任何投资建议。交易有风险,入市需谨慎。" +列出需要客观关注的风险点(如量能跟不上、外资流出、连板断层等)。末尾附一行: +"> ⚠️ 本内容由 AI 基于公开行情数据生成,仅客观陈述市场状态,不构成任何投资建议或买卖指令。交易有风险,入市需谨慎。" ## 分析准则(务必遵守) 0. **只输出结论,不输出思考过程**:禁止复述你的分析步骤或方法论。不要写"我先按...做结构化复盘""接下来看...""基于上述数据我认为"这类元话语——直接给结论。读者要的是复盘结果,不是你怎么推导出来的。 1. **数据说话**:每个判断引用具体数值,严禁空泛套话("情绪回暖"必须改成"涨停 68 家较前日 +22,封板率 75%") -2. **诚实中立**:看多就写多,看空就写空,不要骑墙;数据不支持时直言无法判断 +2. **客观中立**:看多就客观陈述多头特征,看空就客观陈述空头特征,不下基调、不骑墙;数据不支持时直言无法判断 3. **结构优先**:先看指数同步性与量能结构,再看板块与情绪,最后才是消息 4. **不重复数字**:正文负责解读表格数据背后的含义,不要照抄罗列已提供的大段原始数字 -5. **风险前置**:任何进攻建议都要配触发失效条件 -6. **简明实战**:用交易员能扫读的密度输出,总字数 1200-2000 字,重在可执行 +5. **不输出操作指令**:不写"仓位建议""进攻/防守""买卖方向"等任何交易指令 +6. **简明客观**:用读者能扫读的密度输出,总字数 1200-2000 字,重在客观信息密度 现在请基于下方数据进行复盘。""" @@ -215,8 +220,10 @@ def _build_user_prompt(overview: dict, news: list[dict], focus: str) -> str: "消息催化一节请直接从量价异动给出可能的催化逻辑结论,不要编造具体消息,也不要复述本说明。)", ]) - if focus.strip(): - parts.extend(["", f"本次复盘请特别关注: {focus.strip()}"]) + from app.services.ai_provider import sanitize_focus + safe_focus = sanitize_focus(focus) + if safe_focus: + parts.extend(["", f"本次复盘请特别关注: {safe_focus}"]) return "\n".join(parts) diff --git a/backend/app/services/stock_analyzer.py b/backend/app/services/stock_analyzer.py index db45c78..298739f 100644 --- a/backend/app/services/stock_analyzer.py +++ b/backend/app/services/stock_analyzer.py @@ -2,13 +2,13 @@ 职责: 组合一只股票的 K 线(含已算好的技术指标)+ 财务表 + 关键价位 → - 拼装"实战派交易员"级系统提示词 → 流式调用 LLM → 逐 chunk 吐给前端。 + 拼装客观技术分析系统提示词 → 流式调用 LLM → 逐 chunk 吐给前端。 与 financial_analyzer.py 的区别(刻意区分,非复用): - - 角色:A 股实战派交易员 / 技术分析师(非 CFA 财务分析师) + - 角色:客观技术分析师(非 CFA 财务分析师) - 数据源:K 线 + 技术指标为主,财务表为辅(财务分析以财务表为主) - - 输出框架:技术面→基本面→财务面→消息面(四维),落点是买卖区间与操作建议 - (财务分析的落点是财务质量评级) + - 输出框架:技术面→基本面→财务面→消息面(四维),落点是客观技术状态与风险提示 + (财务分析的落点是财务质量评级)。注意:本服务不输出买卖建议、操作指令。 不知道: HTTP、前端、配置持久化。 """ @@ -111,69 +111,78 @@ def _load_financials(data_dir: Path, symbol: str) -> dict[str, list[dict]]: # ================================================================ -# 系统提示词 —— 实战派交易员四维框架(与财务分析明确区分) +# 系统提示词 —— 客观技术分析四维框架(与财务分析明确区分) +# 重要原则:只描述"指标/价位处于什么客观状态",不输出"应该怎么操作"。 +# 本报告定位为客观行情分析,不提供任何买卖建议、仓位建议或交易指令。 # ================================================================ -_SYSTEM_PROMPT = """你是一位拥有 15 年 A 股一线实战经验的资深交易员兼技术分析师,擅长从 K 线、量价、关键价位与基本面交叉验证中把握买卖时机。你的任务是:基于提供的个股数据,产出一份**实战、可直接指导交易决策**的综合分析报告。 +_SYSTEM_PROMPT = """你是一位拥有 15 年 A 股一线研究经验的技术分析分析师,擅长从 K 线、量价、关键价位与基本面交叉验证中客观解读个股的技术状态。你的任务是:基于提供的个股数据,产出一份**客观、中立、不包含任何买卖或操作建议**的技术分析报告。 + +## 核心红线(务必遵守) + +- **绝对不输出**"买入/卖出/加仓/减仓/观望/轻仓/重仓/止损/止盈/建议买入区间/操作建议"等任何交易指令或倾向性措辞 +- **绝对不**按"激进型/稳健型/保守型"给用户分别的操作建议 +- 你的角色是**客观陈述**该股当前的技术状态、价位结构、量价特征与潜在风险,让读者自行判断 +- 换成"一个中立财经记者能不能写出来"——能写就保留,不能写就删除 ## 输出规范 用 **Markdown** 格式输出,严格遵循以下结构。不要输出任何 JSON 或代码块,直接输出 Markdown 正文。 ### 1. 🎯 一句话定调(1-2 句) -用一句话概括该股当前的**技术状态与交易属性**(如"高位放量滞涨,需警惕回调"/"底部筹码集中,放量突破在即")。结尾用【操作建议:观望 / 轻仓试探 / 逢低吸纳 / 持有 / 减仓 / 规避】给出明确倾向。 +用一句话概括该股当前的**技术状态**(如"近期高位放量滞涨,量能持续性存疑"/"价格在 60 日均线上方运行,均线呈多头排列")。结尾用【当前状态:企稳 / 反弹 / 震荡 / 调整 / 走弱】客观描述技术形态,**不评价好坏、不下操作结论**。 ### 2. 📈 技术面分析(核心维度) -这是你的主战场,务必深入: +这是你的主战场,务必深入,只陈述客观事实: - **趋势判断**:均线多头/空头排列、20/60 日均线方向、价格在均线之上/下 -- **形态结构**:近期是否有突破/破位/双底/双顶/旗形等关键形态 +- **形态结构**:近期是否出现突破/破位/双底/双顶/旗形等关键形态 - **指标信号**:MACD 金叉/死叉/背离、KDJ 超买超卖、RSI 强弱、布林通道位置 - **量价配合**:放量上涨/缩量回调/量价背离/换手率异动 -每条结论必须引用具体数值(如"MACD 在 6/12 出现金叉,DIF 0.32 上穿 DEA 0.18")。 +每条结论必须引用具体数值(如"MACD 在 6/12 出现金叉,DIF 0.32 上穿 DEA 0.18"),客观陈述,不下买卖定性。 -### 3. 💰 关键价位(买卖区间) -基于提供的关键价位数据,明确指出: -- **上方压力位**(逐档列出,标注强度):第一压力、第二压力 -- **下方支撑位**(逐档列出,标注强度):第一支撑、第二支撑 -- 给出**建议买入区间**与**止损位**(基于支撑位) -用数据说话,引用提供的压力/支撑(成交密集区)/枢轴点数值。 +### 3. 💰 关键价位(客观价位结构) +基于提供的关键价位数据,客观列出价位结构: +- **上方压力位**(逐档列出,标注来源):第一压力、第二压力 +- **下方支撑位**(逐档列出,标注来源):第一支撑、第二支撑 +- 用数据说话,引用提供的压力/支撑(成交密集区)/枢轴点数值 +- **注意:只客观列出价位及其技术含义(如"此处为前期成交密集区"),不输出"建议买入区间""止损位""止盈位"等操作指令** ### 4. 🏭 基本面与财务面(辅助验证) 简要点评(2-4 句,不展开长篇): -- 盈利质量(ROE / 毛利率水平)、成长性(营收/利润增速) -- 与技术面的**交叉验证**:好公司 + 技术面走坏 → 仍需谨慎;差公司 + 技术面强势 → 警惕炒作风险 +- 盈利质量(ROE / 毛利率水平)、成长性(营收/利润增速)的客观水平 +- 与技术面的**客观对照**:好公司 + 技术面走坏 → 客观陈述两者背离;差公司 + 技术面强势 → 客观提示炒作可能性 +- 不下"逢低吸纳""规避"等结论 **当用户消息中标注了"该标的暂无财务数据"时**,本节请输出: -> 📌 财务面分析能力正在接入中。当前版本(Free)未同步该标的的财务报表,基本面维度暂无法评估。 -> 技术面分析不依赖财务数据,以下结论依然有效;升级套餐或等待财务数据同步后可补充本维度。 +> 📌 财务面分析能力正在接入中。当前未同步该标的的财务报表,基本面维度暂无法评估。 +> 技术面分析不依赖财务数据,以下结论依然有效;待财务数据同步后可补充本维度。 **绝对不要**在无数据时编造 ROE / 增速等数字。 ### 5. 📰 消息面(价量异动推断) -**注意:本期无直接新闻数据输入。** 请基于 K 线的**异动信号**进行推断(如: -- 涨停/连板/炸板 → 可能有利好或资金炒作 -- 放量暴跌 → 可能有未公开利空 -- 突破放量 → 可能有催化剂 -明确标注"[推断]",告诉用户这是基于价量的推测,真实消息面数据待接入。若无明显异动,直说"近期价量平稳,无明显消息面信号"。 +**注意:本期无直接新闻数据输入。** 请基于 K 线的**异动信号**进行客观推断(如: +- 涨停/连板/炸板 → 可能存在利好或资金关注 +- 放量暴跌 → 可能存在未公开扰动 +- 突破放量 → 可能存在催化剂 +明确标注"[推断]",告诉读者这是基于价量的客观推测,真实消息面数据待接入。若无明显异动,直说"近期价量平稳,无明显异动信号"。 -### 6. ⚖️ 综合研判与操作建议 -2-3 段: -- 该股当前处于(底部启动 / 上升途中 / 高位震荡 / 下跌趋势 / 底部企稳)哪个阶段 -- 风险收益比评估(距支撑位的空间 vs 距压力位的空间) -- **明确操作建议**:激进型 / 稳健型 / 保守型 分别怎么应对 -- **需要重点盯的信号**(如跌破 X 支撑止损、站上 Y 压力加仓) +### 6. ⚖️ 综合研判与风险提示 +2-3 段,只做客观描述,不下操作结论: +- 客观描述该股当前所处的技术阶段(底部企稳 / 上升途中 / 高位震荡 / 下跌趋势) +- 客观评估当前价位的"空间不对称性"(距上方压力位与下方支撑位的距离),不评价好坏 +- 客观列出后续值得关注的量价信号(如量能能否维持、某均线得失、是否放量突破压力位),**不附任何操作结论** ## 分析准则(务必遵守) -1. **技术面优先**:作为交易员,技术面和量价是主要依据,基本面是验证手段,主次分明 +1. **技术面优先**:技术面和量价是主要分析对象,基本面是交叉验证手段,主次分明 2. **数据说话**:每个判断引用具体数值,严禁空泛套话("走势良好"必须改成"连续 3 日站稳 20 日均线且放量") -3. **诚实中立**:看多就写多,看空就写空,不要模棱两可骑墙;数据不支持时直言无法判断 -4. **价位精确**:买卖区间必须落到具体价格,基于提供的关键价位数据推演 -5. **风险前置**:任何买入建议都要配止损位;提示潜在风险不回避 -6. **简明实战**:用交易员能扫读的密度输出,总字数 1000-1800 字,重在可执行 +3. **客观中立**:看多就客观陈述多头特征,看空就客观陈述空头特征,不下"该买/该卖"结论;数据不支持时直言无法判断 +4. **价位精确**:压力位/支撑位必须落到具体价格,基于提供的关键价位数据陈述 +5. **不输出操作指令**:不写"买入/卖出/止损/加仓/减仓/仓位建议/操作建议"等任何交易指令;提示潜在风险但不下操作结论 +6. **简明客观**:用读者能扫读的密度输出,总字数 1000-1800 字,重在客观信息密度 ## 重要免责 -报告末尾附一行:"> ⚠️ 本报告由 AI 基于公开行情与财务数据生成,仅供参考,不构成任何投资建议。交易有风险,入市需谨慎。" +报告末尾附一行:"> ⚠️ 本内容由 AI 基于公开行情与财务数据生成,仅客观陈述技术状态,不构成任何投资建议或买卖指令。交易有风险,入市需谨慎。" 现在请基于下方数据进行分析。""" @@ -218,8 +227,10 @@ def _build_user_prompt( "请按系统提示词第 4 节的说明,在基本面/财务面维度给出\"接入中\"的友好提示,不要编造数据。)", ]) - if focus.strip(): - parts.extend(["", f"本次分析请特别关注: {focus.strip()}"]) + from app.services.ai_provider import sanitize_focus + safe_focus = sanitize_focus(focus) + if safe_focus: + parts.extend(["", f"本次分析请特别关注: {safe_focus}"]) return "\n".join(parts) diff --git a/backend/app/strategy/monitor.py b/backend/app/strategy/monitor.py index 6ebcf18..8ec8c34 100644 --- a/backend/app/strategy/monitor.py +++ b/backend/app/strategy/monitor.py @@ -150,7 +150,7 @@ class StrategyMonitorService: strategy_id=strategy_id, symbol=sym, name=name, - message=f"买入信号触发", + message=f"入场信号触发", price=price, change_pct=pct, signals=hit_sigs, @@ -167,7 +167,7 @@ class StrategyMonitorService: strategy_id=strategy_id, symbol=sym, name=name, - message=f"卖出信号触发", + message=f"出场信号触发", price=price, change_pct=pct, signals=hit_sigs, diff --git a/docs/features.md b/docs/features.md index 54b6c04..3fa5cce 100644 --- a/docs/features.md +++ b/docs/features.md @@ -89,8 +89,8 @@ 以「行情 + 关键价位」为主体的单标的决策页: - **专用日 K 图表**:主图 + 成交量 + 滑块,默认近 6 个月 -- **9 类关键价位**(纯函数实时计算,毫秒级):压力支撑 · 成交密集区 · 枢轴点 · 前高前低 · Keltner 通道 · ATR 止损 · 缺口位 · 斐波那契 · 整数关口 -- **AI 四维分析**:技术 / 基本面 / 财务 / 消息面流式生成,实战派交易员视角 +- **9 类关键价位**(纯函数实时计算,毫秒级):压力支撑 · 成交密集区 · 枢轴点 · 前高前低 · Keltner 通道 · ATR 波动通道 · 缺口位 · 斐波那契 · 整数关口 +- **AI 四维分析**:技术 / 基本面 / 财务 / 消息面流式生成,客观技术分析视角(不输出买卖建议) --- diff --git a/docs/plugin-development.md b/docs/plugin-development.md index 7148c41..840c2dc 100644 --- a/docs/plugin-development.md +++ b/docs/plugin-development.md @@ -3,7 +3,7 @@ 数据源插件是可选的行情数据来源(stock-sdk、akshare 等),作为独立模块放在 `backend/app/plugins/` 下。用户**手动安装依赖**后才可用(开发模式);不安装完全不影响主功能。 -> 💡 **Docker 部署已预装**:内置插件(如 stock-sdk)的 Node 运行时与 `node_modules` 已在镜像构建期装好,Docker 下无需手动 `npm install`,开箱即用。下方"手动安装依赖"仅适用于开发模式。 +> ⚠️ **Docker 默认不打包 stock-sdk**(合规考虑:它抓取第三方财经网站接口,存在版权与反爬风险)。如需在 Docker 中启用,构建时传 `--build-arg INCLUDE_STOCKSDK=1`,使用风险自负。下方"手动安装依赖"适用于开发模式及自定义 Docker 构建。 ## 快速上手 @@ -34,9 +34,9 @@ install_hint: "pip install xxx" # 未装依赖时显示的安装提示 | runtime | 含义 | 典型场景 | |---|---|---| | `python` | 纯 Python 依赖, `pip install` | akshare、tushare | -| `node` | 需要 Node.js 运行时, `npm install` | stock-sdk(已内置,见下) | +| `node` | 需要 Node.js 运行时, `npm install` | stock-sdk(Docker 默认不打包,见 [deployment.md](./deployment.md)) | -> stock-sdk 在 Docker 镜像里已预装 Node 运行时与依赖;开发模式下才需手动 `npm install`。 +> stock-sdk 在 Docker 中默认不打包(合规考虑);如需启用,构建时传 `--build-arg INCLUDE_STOCKSDK=1`,开发模式下需手动 `npm install`。 | `none` | 无额外依赖 | 纯 HTTP API 源 | `runtime` 字段当前仅用于 UI 展示, 实际依赖检测由 `check` 函数负责。 diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 1ae30f5..bbda075 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -38,7 +38,6 @@ import { Sparkles, Layers3, Landmark, - Cable, RadioTower, CheckCircle2, BookOpenCheck, @@ -80,7 +79,6 @@ const nav = [ { to: '/monitor', label: '监控中心', icon: RadioTower }, { to: '/review', label: '复盘', icon: BookOpenCheck }, { to: '/indices', label: '指数', icon: BarChart3 }, - { to: '/trading', label: '交易', icon: Cable }, { to: '/data', label: '数据', icon: Database }, ] as const diff --git a/frontend/src/components/monitor/RuleEditor.tsx b/frontend/src/components/monitor/RuleEditor.tsx index 1cfcd06..2dc951d 100644 --- a/frontend/src/components/monitor/RuleEditor.tsx +++ b/frontend/src/components/monitor/RuleEditor.tsx @@ -392,7 +392,7 @@ export function RuleEditor({ rule, preset, simple, onClose, onSaved }: Props) {

- 策略监控自动评估策略的买卖信号。entry=买入信号,exit=卖出信号,both=两者都报。作用范围建议用「全市场」。 + 策略监控自动评估策略的出入场信号。entry=入场信号,exit=出场信号,both=两者都报。作用范围建议用「全市场」。

)} @@ -415,7 +415,7 @@ export function RuleEditor({ rule, preset, simple, onClose, onSaved }: Props) { - {/* Webhook 推送 — 飞书 / 企业微信 可用, QMT/ptrade 待定 */} + {/* Webhook 推送 — 飞书 / 企业微信 */}
Webhook 推送 @@ -458,19 +458,6 @@ export function RuleEditor({ rule, preset, simple, onClose, onSaved }: Props) { )} - {/* QMT (待定) */} - - - {/* ptrade (待定) */} -
{/* 勾选了某渠道但该渠道地址未配置 → 提示前往设置 */} diff --git a/frontend/src/components/screener/StrategyBuilderDialog.tsx b/frontend/src/components/screener/StrategyBuilderDialog.tsx index 2e7c49c..2da0737 100644 --- a/frontend/src/components/screener/StrategyBuilderDialog.tsx +++ b/frontend/src/components/screener/StrategyBuilderDialog.tsx @@ -436,7 +436,7 @@ export function StrategyBuilderDialog({ open, onClose, onSavedId, mode = 'create
交易信号
{entrySignals.length > 0 && (
- 买入 + 入场
{entrySignals.map((s: string) => {s})}
@@ -444,7 +444,7 @@ export function StrategyBuilderDialog({ open, onClose, onSavedId, mode = 'create )} {exitSignals.length > 0 && (
- 卖出 + 出场
{exitSignals.map((s: string) => {s})}
diff --git a/frontend/src/components/screener/StrategySettingsDialog.tsx b/frontend/src/components/screener/StrategySettingsDialog.tsx index 2332a29..bb56fbe 100644 --- a/frontend/src/components/screener/StrategySettingsDialog.tsx +++ b/frontend/src/components/screener/StrategySettingsDialog.tsx @@ -513,36 +513,36 @@ export function StrategySettingsDialog({ strategyId, onClose, onSaved, onAiModif
- 买入 {entrySignals.length > 0 ? `${entrySignals.length} 个触发器` : '无'} - 卖出 {exitSignals.length > 0 ? `${exitSignals.length} 个触发器` : '无'} + 入场 {entrySignals.length > 0 ? `${entrySignals.length} 个触发器` : '无'} + 出场 {exitSignals.length > 0 ? `${exitSignals.length} 个触发器` : '无'}
} > -
任一买点满足即进入候选。
+
任一入场点满足即进入候选。
} > -
任一卖点满足即触发卖出。
+
任一出场点满足即触发出场。
- 买卖触发器保存后对回测和监控生效;选股扫描仍按策略本身的筛选规则,不受此影响。 + 出入场触发器保存后对回测和监控生效;选股扫描仍按策略本身的筛选规则,不受此影响。
{detail.alerts.length > 0 && ( diff --git a/frontend/src/components/signals/CustomSignalDialog.tsx b/frontend/src/components/signals/CustomSignalDialog.tsx index 0cc420d..8285a7e 100644 --- a/frontend/src/components/signals/CustomSignalDialog.tsx +++ b/frontend/src/components/signals/CustomSignalDialog.tsx @@ -112,9 +112,9 @@ export function CustomSignalDialog({ open, signal, defaultKind = 'exit', onClose
diff --git a/frontend/src/components/stock-analysis/AnalysisKChart.tsx b/frontend/src/components/stock-analysis/AnalysisKChart.tsx index 5851ae8..d21d67d 100644 --- a/frontend/src/components/stock-analysis/AnalysisKChart.tsx +++ b/frontend/src/components/stock-analysis/AnalysisKChart.tsx @@ -52,7 +52,7 @@ export const LEVEL_GROUPS: { key: LevelType; label: string; color: string }[] = { key: 'keltner_s',label: 'Keltner短期', color: '#06B6D4' }, // 青(MA20±2ATR 曲线) { key: 'keltner_m',label: 'Keltner中期', color: '#22D3EE' }, // 浅青(MA60±2.5ATR 曲线) { key: 'keltner_l',label: 'Keltner长期', color: '#67E8F9' }, // 更浅青(MA120±3ATR 曲线) - { key: 'atr_stop', label: 'ATR止损', color: '#EF4444' }, // 红(警示) + { key: 'atr_stop', label: 'ATR波动通道', color: '#EF4444' }, // 红(警示) { key: 'gap', label: '缺口位', color: '#EC4899' }, // 粉 { key: 'fib', label: '斐波那契', color: '#F59E0B' }, // 金 { key: 'round', label: '整数关口', color: '#71717A' }, // 灰(心理位,弱视觉) @@ -72,8 +72,8 @@ const CURVE_DEFS: { alignedKey: string; group: LevelType; endLabel: string; colo { alignedKey: 'keltner_m_lower',group: 'keltner_m', endLabel: 'Keltner中下', color: '#22D3EE', dashed: true }, { alignedKey: 'keltner_l_upper',group: 'keltner_l', endLabel: 'Keltner长上', color: '#67E8F9', dashed: true }, { alignedKey: 'keltner_l_lower',group: 'keltner_l', endLabel: 'Keltner长下', color: '#67E8F9', dashed: true }, - { alignedKey: 'atr_stop', group: 'atr_stop', endLabel: 'ATR止损', color: '#EF4444', dashed: true }, - { alignedKey: 'atr_tp', group: 'atr_stop', endLabel: 'ATR止盈', color: '#F87171', dashed: true }, + { alignedKey: 'atr_stop', group: 'atr_stop', endLabel: 'ATR下轨', color: '#EF4444', dashed: true }, + { alignedKey: 'atr_tp', group: 'atr_stop', endLabel: 'ATR上轨', color: '#F87171', dashed: true }, ] // ===== 预留:标记 / 区间(后续新闻面、事件区间用) ===== @@ -272,7 +272,7 @@ export function AnalysisKChart({ }) } - // 带状曲线指标(布林带 / Keltner通道 / ATR止损) —— 跟随行情漂移的曲线 + // 带状曲线指标(布林带 / Keltner通道 / ATR波动通道) —— 跟随行情漂移的曲线 // 单一数据源 CURVE_DEFS 驱动:每条曲线带 endLabel(右侧端点标签),显示最新数值 for (const def of CURVE_DEFS) { if (!activeTypes.has(def.group)) continue diff --git a/frontend/src/lib/signals.ts b/frontend/src/lib/signals.ts index da5207b..b67241c 100644 --- a/frontend/src/lib/signals.ts +++ b/frontend/src/lib/signals.ts @@ -127,7 +127,7 @@ export const BUILTIN_SIGNAL_DEFINITIONS: BuiltinSignalDefinition[] = [ name: '放量', kind: 'both', category: '量价', - description: '成交量显著放大,可作为买入确认、卖出确认或告警条件。', + description: '成交量显著放大,可作为入场确认、出场确认或告警条件。', }, { id: 'signal_limit_up', diff --git a/frontend/src/lib/voiceBroadcast.ts b/frontend/src/lib/voiceBroadcast.ts index e586de8..a2f86b8 100644 --- a/frontend/src/lib/voiceBroadcast.ts +++ b/frontend/src/lib/voiceBroadcast.ts @@ -106,7 +106,7 @@ function fmtPctText(pct: number): string { * 设计原则: 必念个股名称 (不念代码), source 分类拼接: * strategy(≤5只单条): "[名称] 进入/移出 策略「策略名」 [涨跌幅]" * strategy(>5只批量): 直接念 message (后端已含名称列表) - * signal: "[名称] 买入/卖出信号触发 [涨跌幅]" + * signal: "[名称] 入场/出场信号触发 [涨跌幅]" * price/market/其他: "[名称] [message条件摘要] [涨跌幅]" */ function buildSingleText(a: AlertEvent): string { @@ -130,7 +130,7 @@ function buildSingleText(a: AlertEvent): string { return parts.join(' ') } - // 信号类: message 形如 "买入信号触发"/"卖出信号触发" + // 信号类: message 形如 "入场信号触发"/"出场信号触发" if (a.source === 'signal') { const parts = [name] if (a.message) parts.push(a.message) diff --git a/frontend/src/pages/Backtest.tsx b/frontend/src/pages/Backtest.tsx index 4227f3e..88328fa 100644 --- a/frontend/src/pages/Backtest.tsx +++ b/frontend/src/pages/Backtest.tsx @@ -16,7 +16,7 @@ const MODES: Record = { strategy: { title: '策略回测', subtitle: '验证完整选股和交易规则', - hint: '看净值曲线、回撤、胜率和交易明细,适合判断策略是否可执行。', + hint: '看净值曲线、回撤、胜率和交易明细,适合评估策略的历史表现。', }, optimizer: { title: '参数优化', diff --git a/frontend/src/pages/Onboarding.tsx b/frontend/src/pages/Onboarding.tsx index d071a55..fb9da0d 100644 --- a/frontend/src/pages/Onboarding.tsx +++ b/frontend/src/pages/Onboarding.tsx @@ -187,7 +187,7 @@ function DisclaimerStep({ onNext }: { onNext: () => void }) {

本项目为个人开源项目, void }) { TickFlow - 官方项目,与 TickFlow 官方无任何隶属或合作关系。 + 官方项目。本项目独立维护,当前支持 TickFlow 数据服务。

仅供学习研究使用,不构成任何投资建议。股市有风险,使用本项目产生的任何盈亏由使用者自行承担。

- 本项目面向个人学习与研究,不建议用于商业用途。如确有商用需求,请自行评估相关风险与合规要求。 + 本项目基于 MIT 协议开源。使用本项目时,请遵守数据源(TickFlow 等)的服务条款;stock-sdk 等第三方接口插件存在版权与反爬风险,使用需自行评估合规责任。

diff --git a/frontend/src/pages/Review.tsx b/frontend/src/pages/Review.tsx index 2b11b75..ede6109 100644 --- a/frontend/src/pages/Review.tsx +++ b/frontend/src/pages/Review.tsx @@ -309,7 +309,7 @@ export function Review() { value={focus} onChange={(e) => setFocus(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && !isGenerating) generate() }} - placeholder="可选:补充复盘关注点,如「明日是否加仓半导体」「量能是否持续」" + placeholder="可选:补充复盘关注点,如「半导体板块持续性如何」「量能是否持续」" className="flex-1 bg-transparent text-sm text-foreground outline-none placeholder:text-muted/60" /> {focus && ( diff --git a/frontend/src/pages/StockAnalysis.tsx b/frontend/src/pages/StockAnalysis.tsx index 31cf406..0228aa4 100644 --- a/frontend/src/pages/StockAnalysis.tsx +++ b/frontend/src/pages/StockAnalysis.tsx @@ -21,7 +21,7 @@ import { * * 与财务分析页的区别: * - 以【行情 + 关键价位】为视觉主体(专用日 K 图表,不复用个股对话框图表) - * - AI 分析输出买卖区间 / 操作建议(非财务质量评级) + * - AI 分析输出客观技术状态与风险提示(非买卖建议、非财务质量评级) * - 报告胶囊用蓝色系,与财务分析(紫色)并存 */ export function StockAnalysis() { diff --git a/frontend/src/pages/Trading.tsx b/frontend/src/pages/Trading.tsx deleted file mode 100644 index 9148ff1..0000000 --- a/frontend/src/pages/Trading.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Cable } from 'lucide-react' -import { PageHeader } from '@/components/PageHeader' -import { EmptyState } from '@/components/EmptyState' - -// 后续实现计划(本轮为占位): -// -// 一、信号 → 交易 的桥接 -// 监控通知产生的买卖信号(StrategyAlert),通过可插拔的输出通道分发到 -// 支持外部信号的交易软件。核心是在 alert_handler 层做多通道分发。 -// -// 二、支持的交易软件(按接入难度) -// 1. QMT / miniQMT(迅投)—— 个人 A 股实盘首选。 -// XtQuant 的 xttrader.order_stock() 下单,信号来源不限(文件/HTTP)。 -// 2. 掘金量化(MyQuant)—— 本地终端 + Python SDK,事件驱动接收信号。 -// 3. Ptrade(恒生)—— 内置策略引擎,外部信号经 API/文件喂入。 -// 4. vnpy(VeighNa)—— 开源框架,自写策略模块接收信号再调 Gateway 下单。 -// -// 三、信号输出通道(可插拔) -// alert_handler 分发: -// ├─ SSE → 前端通知(已有) -// ├─ 本地文件(JSON/CSV) → QMT 脚本轮询读取 ← 最简单,优先做 -// ├─ Webhook POST → 外部交易脚本 -// └─ 直连 xttrader(需本机装 QMT) -// -// 四、信号 → 交易指令 的字段补全 -// 现有 StrategyAlert(symbol/type/strategy_id/price)是「信号层」, -// 下单还需补:volume(数量,A股100的倍数)、price_type(市价/限价)、account。 -const PLAN: { title: string; desc: string }[] = [ - { - title: 'QMT / miniQMT', - desc: '个人 A 股实盘首选。XtQuant 的 xttrader 下单,信号经文件或 HTTP 喂入即可。国内个人量化实盘事实标准。', - }, - { - title: '掘金量化 (MyQuant)', - desc: '本地终端 + Python SDK,事件驱动接收外部信号下单,本土化程度高。', - }, - { - title: 'Ptrade (恒生)', - desc: '内置 Python 策略引擎,外部信号经 API/文件喂入,灵活性低于 QMT。', - }, - { - title: 'vnpy (VeighNa)', - desc: '开源交易框架,Gateway 丰富(期货/股票/加密货币),需自建执行端,搭建成本较高。', - }, - { - title: '信号输出通道', - desc: 'alert_handler 多通道分发:本地文件(最简,优先)、Webhook POST、直连 xttrader。与具体交易软件解耦。', - }, -] - -export function Trading() { - return ( -
- - -
-
- - -
-

后续实现规划

-
    - {PLAN.map((item) => ( -
  • - -
    -

    {item.title}

    -

    {item.desc}

    -
    -
  • - ))} -
-
-
-
-
- ) -} diff --git a/frontend/src/pages/backtest/StrategyBacktest.tsx b/frontend/src/pages/backtest/StrategyBacktest.tsx index 20bb944..93a24c4 100644 --- a/frontend/src/pages/backtest/StrategyBacktest.tsx +++ b/frontend/src/pages/backtest/StrategyBacktest.tsx @@ -179,8 +179,8 @@ const STRATEGY_GROUPS: { id: StrategyGroup; label: string }[] = [ const ADVANCED_TABS: { id: AdvancedSettingsTab; label: string }[] = [ { id: 'params', label: '策略参数' }, { id: 'filter', label: '基础过滤' }, - { id: 'entry', label: '买入触发器' }, - { id: 'exit', label: '卖出触发器' }, + { id: 'entry', label: '入场触发器' }, + { id: 'exit', label: '出场触发器' }, { id: 'scoring', label: '评分权重' }, { id: 'risk', label: '风控' }, { id: 'range', label: '回测范围' }, @@ -2135,8 +2135,8 @@ export function StrategyBacktest() { {settingsTab === 'entry' && ( updateOverride('entry_signals', next)} />} > updateOverride('exit_signals', next)} />} > { return effortLabel ? `${modelLabel} · ${effortLabel}` : modelLabel } -const PRESETS: { label: string; provider?: string; url: string; model: string; codexCommand?: string; website: string; websiteLabel: string; description: string; partner?: boolean; promo?: string; custom?: boolean }[] = [ +const PRESETS: { label: string; provider?: string; url: string; model: string; codexCommand?: string; website: string; websiteLabel: string; description: string; custom?: boolean }[] = [ { label: '自定义', url: '', model: '', website: '', websiteLabel: '', description: '不自动填充任何配置,完全手动填写 API 地址、模型和密钥。', custom: true }, { label: 'DeepSeek', url: 'https://api.deepseek.com', model: 'deepseek-v4-pro', website: 'https://www.deepseek.com/', websiteLabel: 'deepseek.com', description: 'DeepSeek 官方 OpenAI 兼容接口。' }, { label: '通义千问', url: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-3.6plus', website: 'https://tongyi.aliyun.com/', websiteLabel: 'tongyi.aliyun.com', description: '阿里云 DashScope 兼容模式接口。' }, { label: '智谱 GLM', url: 'https://open.bigmodel.cn/api/paas/v4', model: 'glm-5.2', website: 'https://open.bigmodel.cn/', websiteLabel: 'open.bigmodel.cn', description: '智谱 AI 官方 OpenAI 兼容接口。' }, { label: 'Kimi', url: 'https://api.moonshot.cn/v1', model: 'kimi-k2.7-code', website: 'https://platform.moonshot.cn/', websiteLabel: 'platform.moonshot.cn', description: '月之暗面 Moonshot 官方 OpenAI 兼容接口,支持超长上下文。' }, { label: 'Codex CLI', provider: CODEX_PROVIDER, url: '', model: DEFAULT_CODEX_MODEL, codexCommand: CODEX_COMMAND, website: 'https://developers.openai.com/codex/noninteractive', websiteLabel: 'codex exec', description: '调用本机 Codex CLI 的 codex exec, 适合已登录 ChatGPT/Codex 的本地环境。' }, - { label: '炸鸡中转站', url: 'https://api.zhaji.dev/v1', model: 'gpt-5.5', website: 'https://api.zhaji.dev', websiteLabel: 'api.zhaji.dev', description: 'OpenAI 兼容中转服务,适合直接使用国际模型。', partner: true, promo: '通过链接邀请注册赠送免费额度 · 国际模型最低0.02倍率' }, + { label: '炸鸡中转站', url: 'https://api.zhaji.dev/v1', model: 'gpt-5.5', website: 'https://api.zhaji.dev', websiteLabel: 'api.zhaji.dev', description: 'OpenAI 兼容中转服务,适合直接使用国际模型。' }, ] export function SettingsAIPanel() { @@ -258,7 +258,6 @@ export function SettingsAIPanel() {
{p.label} {p.provider === CODEX_PROVIDER && } - {p.partner && 赞助}
))} @@ -267,7 +266,6 @@ export function SettingsAIPanel() {
{selectedPreset.description} - {selectedPreset.promo && {selectedPreset.promo}}
{selectedPreset.website && ( = { entry: '买入', exit: '卖出', both: '买卖通用' } +const KIND_LABEL: Record = { entry: '入场', exit: '出场', both: '出入通用' } const KIND_CLASS: Record = { entry: 'bg-accent/10 text-accent', exit: 'bg-warning/10 text-warning', diff --git a/frontend/src/pages/settings/MenuSettings.tsx b/frontend/src/pages/settings/MenuSettings.tsx index 14d7303..d0b3a72 100644 --- a/frontend/src/pages/settings/MenuSettings.tsx +++ b/frontend/src/pages/settings/MenuSettings.tsx @@ -42,7 +42,6 @@ const BUILTIN_PAGES: NavEntry[] = [ { id: '/review', label: '复盘', type: 'builtin', visible: true }, { id: '/financials', label: '财务分析', type: 'builtin', visible: true }, { id: '/indices', label: '指数', type: 'builtin', visible: true }, - { id: '/trading', label: '交易', type: 'builtin', visible: true }, { id: '/monitor', label: '监控中心', type: 'builtin', visible: true }, { id: '/data', label: '数据', type: 'builtin', visible: true }, ] diff --git a/frontend/src/pages/settings/Monitoring.tsx b/frontend/src/pages/settings/Monitoring.tsx index 6493a95..504843b 100644 --- a/frontend/src/pages/settings/Monitoring.tsx +++ b/frontend/src/pages/settings/Monitoring.tsx @@ -475,7 +475,7 @@ export function SettingsMonitoringPanel({ highlight }: { highlight?: string } =
{/* 推送通知 — 监控告警的外部推送渠道 (全局配置)。 - 飞书 / 企业微信已实现; QMT/ptrade 待定。 + 飞书 / 企业微信。 每个渠道合并成一行: 勾选=新建规则默认推送, 点行展开地址配置。 */}

@@ -739,21 +739,6 @@ export function SettingsMonitoringPanel({ highlight }: { highlight?: string } = )} - {/* 占位渠道 — 不可点 */} - {[ - { name: 'QMT', hint: '量化交易终端', status: '待定' }, - { name: 'ptrade', hint: '量化交易终端', status: '待定' }, - ].map(ch => ( -

- - {ch.name} - {ch.hint} - {ch.status} -
- ))}
diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index c7df674..474eecb 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -15,7 +15,6 @@ const Backtest = lazy(() => import('./pages/Backtest').then(m => ({ default: m.B const Financials = lazy(() => import('./pages/Financials').then(m => ({ default: m.Financials }))) const Data = lazy(() => import('./pages/Data').then(m => ({ default: m.Data }))) const Monitor = lazy(() => import('./pages/Monitor').then(m => ({ default: m.Monitor }))) -const Trading = lazy(() => import('./pages/Trading').then(m => ({ default: m.Trading }))) const Dashboard = lazy(() => import('./pages/Dashboard').then(m => ({ default: m.Dashboard }))) const AnalysisDetail = lazy(() => import('./pages/AnalysisDetail').then(m => ({ default: m.AnalysisDetail }))) const ConceptAnalysis = lazy(() => import('./pages/ConceptAnalysis').then(m => ({ default: m.ConceptAnalysis }))) @@ -81,7 +80,6 @@ export const router = createBrowserRouter([ { path: 'financials', element: }, { path: 'data', element: }, { path: 'monitor', element: }, - { path: 'trading', element: }, { path: 'limit-ladder', element: }, { path: 'indices', element: }, { path: 'branding', element: },