mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 16:44:15 +08:00
fix(compliance): reinforce research-only boundaries (#108)
Co-authored-by: shy3130 <shy3130@users.noreply.github.com>
This commit is contained in:
+24
-12
@@ -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 \
|
||||
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
|
||||
&& node --version; \
|
||||
fi
|
||||
|
||||
# 安装 uv(快) —— 国内镜像下三重兜底:主源 → 备用源 → 官方源,
|
||||
# 任一成功即可,避免单一镜像同步延迟/故障导致构建失败。
|
||||
@@ -86,8 +97,9 @@ RUN if [ "$USE_CN_MIRROR" = "1" ]; then \
|
||||
# (<root>/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
|
||||
|
||||
@@ -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": "出入都报"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -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": "出入通用"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
# stock-sdk 内置可选数据源插件
|
||||
# 基于 stock-sdk (Node.js) 的免费 A 股行情, 无需 API Key。
|
||||
# Docker 部署已内置 Node 运行时与本插件依赖, 开箱即用;
|
||||
# ⚠️ 合规提示: 本插件通过 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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
+2
-2
@@ -89,8 +89,8 @@
|
||||
以「行情 + 关键价位」为主体的单标的决策页:
|
||||
|
||||
- **专用日 K 图表**:主图 + 成交量 + 滑块,默认近 6 个月
|
||||
- **9 类关键价位**(纯函数实时计算,毫秒级):压力支撑 · 成交密集区 · 枢轴点 · 前高前低 · Keltner 通道 · ATR 止损 · 缺口位 · 斐波那契 · 整数关口
|
||||
- **AI 四维分析**:技术 / 基本面 / 财务 / 消息面流式生成,实战派交易员视角
|
||||
- **9 类关键价位**(纯函数实时计算,毫秒级):压力支撑 · 成交密集区 · 枢轴点 · 前高前低 · Keltner 通道 · ATR 波动通道 · 缺口位 · 斐波那契 · 整数关口
|
||||
- **AI 四维分析**:技术 / 基本面 / 财务 / 消息面流式生成,客观技术分析视角(不输出买卖建议)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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` 函数负责。
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -392,7 +392,7 @@ export function RuleEditor({ rule, preset, simple, onClose, onSaved }: Props) {
|
||||
</label>
|
||||
</div>
|
||||
<p className="text-[10px] leading-4 text-muted/70">
|
||||
策略监控自动评估策略的买卖信号。entry=买入信号,exit=卖出信号,both=两者都报。作用范围建议用「全市场」。
|
||||
策略监控自动评估策略的出入场信号。entry=入场信号,exit=出场信号,both=两者都报。作用范围建议用「全市场」。
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -415,7 +415,7 @@ export function RuleEditor({ rule, preset, simple, onClose, onSaved }: Props) {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Webhook 推送 — 飞书 / 企业微信 可用, QMT/ptrade 待定 */}
|
||||
{/* Webhook 推送 — 飞书 / 企业微信 */}
|
||||
<div className="rounded-btn border border-border/40 bg-base/40 p-3 space-y-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-[11px] font-medium text-foreground">Webhook 推送</span>
|
||||
@@ -458,19 +458,6 @@ export function RuleEditor({ rule, preset, simple, onClose, onSaved }: Props) {
|
||||
)}
|
||||
</label>
|
||||
|
||||
{/* QMT (待定) */}
|
||||
<label className="flex items-center gap-2 cursor-not-allowed opacity-50">
|
||||
<input type="checkbox" disabled className="h-3 w-3 accent-accent" />
|
||||
<span className="text-[11px] text-secondary">QMT</span>
|
||||
<span className="rounded bg-muted/10 px-1 py-px text-[9px] text-muted">待定</span>
|
||||
</label>
|
||||
|
||||
{/* ptrade (待定) */}
|
||||
<label className="flex items-center gap-2 cursor-not-allowed opacity-50">
|
||||
<input type="checkbox" disabled className="h-3 w-3 accent-accent" />
|
||||
<span className="text-[11px] text-secondary">ptrade</span>
|
||||
<span className="rounded bg-muted/10 px-1 py-px text-[9px] text-muted">待定</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* 勾选了某渠道但该渠道地址未配置 → 提示前往设置 */}
|
||||
|
||||
@@ -436,7 +436,7 @@ export function StrategyBuilderDialog({ open, onClose, onSavedId, mode = 'create
|
||||
<div className="text-[10px] text-muted/50 uppercase tracking-wider">交易信号</div>
|
||||
{entrySignals.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[10px] text-emerald-400 w-10 shrink-0">买入</span>
|
||||
<span className="text-[10px] text-emerald-400 w-10 shrink-0">入场</span>
|
||||
<div className="flex flex-wrap gap-0.5">
|
||||
{entrySignals.map((s: string) => <span key={s} className="px-1.5 py-0.5 rounded bg-emerald-400/10 text-emerald-400 text-[10px] font-mono">{s}</span>)}
|
||||
</div>
|
||||
@@ -444,7 +444,7 @@ export function StrategyBuilderDialog({ open, onClose, onSavedId, mode = 'create
|
||||
)}
|
||||
{exitSignals.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[10px] text-danger w-10 shrink-0">卖出</span>
|
||||
<span className="text-[10px] text-danger w-10 shrink-0">出场</span>
|
||||
<div className="flex flex-wrap gap-0.5">
|
||||
{exitSignals.map((s: string) => <span key={s} className="px-1.5 py-0.5 rounded bg-danger/10 text-danger text-[10px] font-mono">{s}</span>)}
|
||||
</div>
|
||||
|
||||
@@ -513,36 +513,36 @@ export function StrategySettingsDialog({ strategyId, onClose, onSaved, onAiModif
|
||||
<span className="text-[10px] text-muted">天</span>
|
||||
</div>
|
||||
<div className="text-[11px] text-muted pt-1 border-t border-border/10">
|
||||
<span className="text-secondary">买入 </span><span className="text-foreground/70">{entrySignals.length > 0 ? `${entrySignals.length} 个触发器` : '无'}</span>
|
||||
<span className="text-secondary ml-3">卖出 </span><span className="text-foreground/70">{exitSignals.length > 0 ? `${exitSignals.length} 个触发器` : '无'}</span>
|
||||
<span className="text-secondary">入场 </span><span className="text-foreground/70">{entrySignals.length > 0 ? `${entrySignals.length} 个触发器` : '无'}</span>
|
||||
<span className="text-secondary ml-3">出场 </span><span className="text-foreground/70">{exitSignals.length > 0 ? `${exitSignals.length} 个触发器` : '无'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
icon={TrendingUp}
|
||||
title="买入触发器"
|
||||
title="入场触发器"
|
||||
accent="text-accent"
|
||||
defaultOpen={false}
|
||||
extra={<SignalTriggerActions kind="entry" signals={entrySignals} onChange={setEntrySignals} buttonClassName="rounded-md border border-border bg-base p-1 text-muted transition-colors cursor-pointer" iconClassName="h-3 w-3" />}
|
||||
>
|
||||
<SignalPicker signals={entrySignals} onChange={setEntrySignals} kind="entry" variant="dialog" />
|
||||
<div className="text-[10px] leading-4 text-muted/70">任一买点满足即进入候选。</div>
|
||||
<div className="text-[10px] leading-4 text-muted/70">任一入场点满足即进入候选。</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
icon={TrendingUp}
|
||||
title="卖出触发器"
|
||||
title="出场触发器"
|
||||
accent="text-warning"
|
||||
defaultOpen={false}
|
||||
extra={<SignalTriggerActions kind="exit" signals={exitSignals} onChange={setExitSignals} buttonClassName="rounded-md border border-border bg-base p-1 text-muted transition-colors cursor-pointer" iconClassName="h-3 w-3" />}
|
||||
>
|
||||
<SignalPicker signals={exitSignals} onChange={setExitSignals} kind="exit" variant="dialog" />
|
||||
<div className="text-[10px] leading-4 text-muted/70">任一卖点满足即触发卖出。</div>
|
||||
<div className="text-[10px] leading-4 text-muted/70">任一出场点满足即触发出场。</div>
|
||||
</Section>
|
||||
|
||||
<div className="rounded-xl border border-amber-400/20 bg-amber-400/[0.04] px-3 py-2 text-[10px] leading-4 text-muted">
|
||||
买卖触发器保存后对<b className="text-secondary">回测和监控</b>生效;选股扫描仍按策略本身的筛选规则,不受此影响。
|
||||
出入场触发器保存后对<b className="text-secondary">回测和监控</b>生效;选股扫描仍按策略本身的筛选规则,不受此影响。
|
||||
</div>
|
||||
|
||||
{detail.alerts.length > 0 && (
|
||||
|
||||
@@ -112,9 +112,9 @@ export function CustomSignalDialog({ open, signal, defaultKind = 'exit', onClose
|
||||
<label className="space-y-1.5">
|
||||
<span className="text-[11px] text-muted">类型</span>
|
||||
<select value={draft.kind} onChange={e => setDraft(d => ({ ...d, kind: e.target.value as CustomSignal['kind'] }))} className="h-9 w-full rounded-btn border border-border bg-base px-3 text-xs text-foreground">
|
||||
<option value="entry">买入</option>
|
||||
<option value="exit">卖出</option>
|
||||
<option value="both">买卖通用</option>
|
||||
<option value="entry">入场</option>
|
||||
<option value="exit">出场</option>
|
||||
<option value="both">出入通用</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -127,7 +127,7 @@ export const BUILTIN_SIGNAL_DEFINITIONS: BuiltinSignalDefinition[] = [
|
||||
name: '放量',
|
||||
kind: 'both',
|
||||
category: '量价',
|
||||
description: '成交量显著放大,可作为买入确认、卖出确认或告警条件。',
|
||||
description: '成交量显著放大,可作为入场确认、出场确认或告警条件。',
|
||||
},
|
||||
{
|
||||
id: 'signal_limit_up',
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -16,7 +16,7 @@ const MODES: Record<Tab, { title: string; subtitle: string; hint: string }> = {
|
||||
strategy: {
|
||||
title: '策略回测',
|
||||
subtitle: '验证完整选股和交易规则',
|
||||
hint: '看净值曲线、回撤、胜率和交易明细,适合判断策略是否可执行。',
|
||||
hint: '看净值曲线、回撤、胜率和交易明细,适合评估策略的历史表现。',
|
||||
},
|
||||
optimizer: {
|
||||
title: '参数优化',
|
||||
|
||||
@@ -187,7 +187,7 @@ function DisclaimerStep({ onNext }: { onNext: () => void }) {
|
||||
<p>
|
||||
本项目为<strong className="text-warning">个人开源项目</strong>,<span className="text-warning">非</span>
|
||||
<a
|
||||
href="https://tickflow.org/auth/register?ref=V3KDKGXPEA"
|
||||
href="https://tickflow.org"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-accent hover:underline font-medium inline-flex items-baseline gap-0.5"
|
||||
@@ -195,13 +195,13 @@ function DisclaimerStep({ onNext }: { onNext: () => void }) {
|
||||
TickFlow
|
||||
<ExternalLink className="h-3 w-3 self-center" />
|
||||
</a>
|
||||
<span className="text-warning">官方项目</span>,与 TickFlow 官方无任何隶属或合作关系。
|
||||
<span className="text-warning">官方项目</span>。本项目独立维护,当前支持 TickFlow 数据服务。
|
||||
</p>
|
||||
<p>
|
||||
仅供学习研究使用,不构成任何投资建议。股市有风险,使用本项目产生的任何盈亏由使用者自行承担。
|
||||
</p>
|
||||
<p>
|
||||
本项目面向个人学习与研究,不建议用于<strong className="text-warning">商业用途</strong>。如确有商用需求,请自行评估相关风险与合规要求。
|
||||
本项目基于 MIT 协议开源。使用本项目时,请遵守数据源(TickFlow 等)的服务条款;stock-sdk 等第三方接口插件存在版权与反爬风险,使用需自行评估合规责任。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
*
|
||||
* 与财务分析页的区别:
|
||||
* - 以【行情 + 关键价位】为视觉主体(专用日 K 图表,不复用个股对话框图表)
|
||||
* - AI 分析输出买卖区间 / 操作建议(非财务质量评级)
|
||||
* - AI 分析输出客观技术状态与风险提示(非买卖建议、非财务质量评级)
|
||||
* - 报告胶囊用蓝色系,与财务分析(紫色)并存
|
||||
*/
|
||||
export function StockAnalysis() {
|
||||
|
||||
@@ -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 (
|
||||
<div className="flex flex-col h-full">
|
||||
<PageHeader title="交易" subtitle="信号自动下单桥接 · 开发中" />
|
||||
|
||||
<div className="flex-1 overflow-auto px-5 py-6">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<EmptyState
|
||||
icon={Cable}
|
||||
title="交易桥接开发中"
|
||||
hint="本页面将把监控产生的买卖信号,自动推送给支持外部信号的交易软件(QMT/掘金/Ptrade 等)执行下单。当前为占位页面,下方为后续实现规划。"
|
||||
/>
|
||||
|
||||
<section className="mt-6 rounded-card border border-border bg-surface p-5">
|
||||
<h3 className="text-sm font-semibold text-foreground">后续实现规划</h3>
|
||||
<ul className="mt-3 space-y-3">
|
||||
{PLAN.map((item) => (
|
||||
<li key={item.title} className="flex gap-3">
|
||||
<span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-accent" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">{item.title}</p>
|
||||
<p className="mt-0.5 text-xs leading-relaxed text-secondary">{item.desc}</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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' && (
|
||||
<ConfigSection
|
||||
title="买入触发器"
|
||||
hint="任一买点满足即可进入候选"
|
||||
title="入场触发器"
|
||||
hint="任一入场点满足即可进入候选"
|
||||
actions={<SignalTriggerActions kind="entry" signals={entrySignals} onChange={next => updateOverride('entry_signals', next)} />}
|
||||
>
|
||||
<SignalPicker
|
||||
@@ -2149,8 +2149,8 @@ export function StrategyBacktest() {
|
||||
|
||||
{settingsTab === 'exit' && (
|
||||
<ConfigSection
|
||||
title="卖出触发器"
|
||||
hint="任一卖点满足即触发卖出"
|
||||
title="出场触发器"
|
||||
hint="任一出场点满足即触发出场"
|
||||
actions={<SignalTriggerActions kind="exit" signals={exitSignals} onChange={next => updateOverride('exit_signals', next)} />}
|
||||
>
|
||||
<SignalPicker
|
||||
|
||||
@@ -42,14 +42,14 @@ const codexModelLabel = (model?: string, effort?: string) => {
|
||||
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() {
|
||||
<div className="flex items-center gap-1.5 text-xs font-medium">
|
||||
<span>{p.label}</span>
|
||||
{p.provider === CODEX_PROVIDER && <Terminal className="h-3 w-3" />}
|
||||
{p.partner && <span className="rounded-full border border-orange-400/30 bg-orange-400/10 px-1.5 py-px text-[9px] text-orange-400">赞助</span>}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
@@ -267,7 +266,6 @@ export function SettingsAIPanel() {
|
||||
<div className="mt-3 rounded-btn border border-border/30 bg-base/30 px-3 py-2 text-[11px] leading-relaxed">
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
|
||||
<span className="text-secondary">{selectedPreset.description}</span>
|
||||
{selectedPreset.promo && <span className="text-amber-400">{selectedPreset.promo}</span>}
|
||||
</div>
|
||||
{selectedPreset.website && (
|
||||
<a href={selectedPreset.website} target="_blank" rel="noreferrer"
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Skeleton } from '@/components/data/Skeleton'
|
||||
|
||||
type SignalSection = 'builtin' | 'custom'
|
||||
|
||||
const KIND_LABEL: Record<SignalKind, string> = { entry: '买入', exit: '卖出', both: '买卖通用' }
|
||||
const KIND_LABEL: Record<SignalKind, string> = { entry: '入场', exit: '出场', both: '出入通用' }
|
||||
const KIND_CLASS: Record<SignalKind, string> = {
|
||||
entry: 'bg-accent/10 text-accent',
|
||||
exit: 'bg-warning/10 text-warning',
|
||||
|
||||
@@ -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 },
|
||||
]
|
||||
|
||||
@@ -475,7 +475,7 @@ export function SettingsMonitoringPanel({ highlight }: { highlight?: string } =
|
||||
</div>
|
||||
|
||||
{/* 推送通知 — 监控告警的外部推送渠道 (全局配置)。
|
||||
飞书 / 企业微信已实现; QMT/ptrade 待定。
|
||||
飞书 / 企业微信。
|
||||
每个渠道合并成一行: 勾选=新建规则默认推送, 点行展开地址配置。 */}
|
||||
<Card icon={Webhook} title="推送通知">
|
||||
<p className="text-xs text-secondary mb-3">
|
||||
@@ -739,21 +739,6 @@ export function SettingsMonitoringPanel({ highlight }: { highlight?: string } =
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 占位渠道 — 不可点 */}
|
||||
{[
|
||||
{ name: 'QMT', hint: '量化交易终端', status: '待定' },
|
||||
{ name: 'ptrade', hint: '量化交易终端', status: '待定' },
|
||||
].map(ch => (
|
||||
<div
|
||||
key={ch.name}
|
||||
className="flex items-center gap-2 rounded-btn border border-border/40 bg-base/20 px-2.5 py-2 opacity-60"
|
||||
>
|
||||
<input type="checkbox" disabled className="h-3 w-3 accent-accent" />
|
||||
<span className="text-[11px] text-secondary">{ch.name}</span>
|
||||
<span className="text-[9px] text-muted">{ch.hint}</span>
|
||||
<span className="ml-auto rounded bg-muted/10 px-1 py-px text-[9px] text-muted">{ch.status}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -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: <Financials /> },
|
||||
{ path: 'data', element: <Data /> },
|
||||
{ path: 'monitor', element: <Monitor /> },
|
||||
{ path: 'trading', element: <Trading /> },
|
||||
{ path: 'limit-ladder', element: <LimitUpLadder /> },
|
||||
{ path: 'indices', element: <Indices /> },
|
||||
{ path: 'branding', element: <Branding /> },
|
||||
|
||||
Reference in New Issue
Block a user