chore(build): 精简 Dockerfile 可选构建阶段, 清理文档与引导页冗余引用

This commit is contained in:
shy3130
2026-10-11 15:42:55 +08:00
parent 6a2a9bb2a7
commit 350ffbf612
17 changed files with 32 additions and 123 deletions
+1 -36
View File
@@ -1,9 +1,6 @@
# 两阶段构建:前端 dist 拷进后端镜像,单容器运行
# 可选:构建网络无法直连官方源时,传入 --build-arg USE_CN_MIRROR=1 启用国内镜像
# 可选: 插件默认不打包(它抓取第三方财经网站接口,存在版权与反爬风险)。
# 如确需启用,传入 --build-arg INCLUDE_=1 显式开启,使用风险自负。
ARG USE_CN_MIRROR=1
ARG INCLUDE_=0
ARG NPM_REGISTRY=https://registry.npmmirror.com
ARG PYPI_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
# 备用 PyPI 源:主源同步延迟/故障时自动兜底(阿里云与清华互为补充)
@@ -28,25 +25,6 @@ RUN pnpm install --frozen-lockfile || pnpm install
COPY frontend/ ./
RUN pnpm build
# === Stage 1b: 插件依赖(可选,默认跳过) ===
# ⚠️ 合规提示: 通过 node bridge.mjs 抓取第三方财经网站(如东方财富)的行情接口,
# 未经对方授权,可能违反其服务条款并涉及交易所行情版权。默认不打包(INCLUDE_=0)。
# 如确需启用,构建时传 --build-arg INCLUDE_=1,即视为使用者知悉并自行承担合规责任。
# INCLUDE_=0 时,本 stage 仅产出空 node_modules 目录,保证后续 COPY 不报错。
FROM node:20-bookworm-slim AS -builder
ARG USE_CN_MIRROR=1
ARG NPM_REGISTRY=https://registry.npmmirror.com
ARG INCLUDE_=0
WORKDIR /build
RUN if [ "$USE_CN_MIRROR" = "1" ]; then npm config set registry "$NPM_REGISTRY"; fi
COPY backend/app/plugins//package.json backend/app/plugins//package-lock.json ./
# INCLUDE_=1 时安装依赖;=0 时仅建空目录,使最终镜像不含 依赖
RUN if [ "$INCLUDE_" = "1" ]; then \
(npm ci || npm install); \
else \
mkdir -p /build/node_modules; \
fi
# === Stage 1c: Codex CLI ===
# 固定版本保证镜像可复现;只复制安装产物到运行镜像,不保留 npm。
FROM node:20-bookworm-slim AS codex-builder
@@ -68,26 +46,18 @@ 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_=0
WORKDIR /app
# Node.js 运行时: 仅在启用 插件时安装(供 node bridge.mjs 使用)。
# Codex CLI 从官方 npm 包提取原生二进制,不依赖运行时 Node.js。
# bookworm 自带 nodejs 18.19, 满足插件 engines>=18; --no-install-recommends 精简,
# 自带 libnode/libc-ares 等全部动态依赖, 无需手动补库。
# 国内构建换 apt 阿里云镜像 (bookworm deb822 格式)。官方 python slim 镜像的
# sources 指向 deb.debian.org, 部分国内网络下 apt 极慢(实测阿里云 ECS 拉官方源
# 单阶段可达 8 分钟以上); USE_CN_MIRROR 与 npm/pypi 的换源开关注一脉相承。
# tesseract-ocr: 自选截图导入(始终安装); nodejs: 仅 INCLUDE_=1 时安装
# tesseract-ocr: 自选截图导入(始终安装)
RUN if [ "$USE_CN_MIRROR" = "1" ]; then \
sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources /etc/apt/sources.list 2>/dev/null || true; \
fi \
&& apt-get update \
&& apt-get install -y --no-install-recommends tesseract-ocr tesseract-ocr-eng \
&& if [ "$INCLUDE_" = "1" ]; then \
apt-get install -y --no-install-recommends nodejs \
&& node --version; \
fi \
&& rm -rf /var/lib/apt/lists/* \
&& tesseract --version
@@ -122,11 +92,6 @@ RUN if [ "$USE_CN_MIRROR" = "1" ]; then \
# (<root>/backend/app/) 推导的, 容器内会错算到 /。这里用环境变量显式指定
# 三个关键路径, 确保 static / tiers / data 都指向容器内正确位置。
COPY backend/app ./app
# 插件依赖: 从 -builder 拷入。
# INCLUDE_=0(默认) 时, -builder 产出空目录,此处拷入空目录,
# 即最终镜像不含 依赖,插件默认不可用。
# COPY --from 不受 .dockerignore 的 **/node_modules 规则影响。
COPY --from=-builder /build/node_modules ./app/plugins//node_modules
COPY tiers.yaml /app/tiers.yaml
ENV STATIC_DIR=/app/static \
TIERS_YAML=/app/tiers.yaml \
+3 -7
View File
@@ -367,7 +367,7 @@ flowchart TB
| **后端** | ![Python](https://img.shields.io/badge/Python_3.11+-3776AB?logo=python&logoColor=white) ![FastAPI](https://img.shields.io/badge/FastAPI-009688?logo=fastapi&logoColor=white) ![Pydantic](https://img.shields.io/badge/Pydantic_v2-E92063?logo=pydantic&logoColor=white) APScheduler · sse-starlette |
| **数据** | ![Polars](https://img.shields.io/badge/Polars-CD882D?logo=polars&logoColor=white)(计算)· ![DuckDB](https://img.shields.io/badge/DuckDB-FFF100?logo=duckdb&logoColor=black)(查询)· Parquet(存储) |
| **回测** | 自研仓位模拟引擎(T+1/费用/滑点/分钟回放)· vectorbt(部分路径) |
| **数据源** | [TickFlow](https://tickflow.org/auth/register?ref=V3KDKGXPEA) 官方 SDK · fuyao(同花顺 REST) · 插件化扩展( 示例插件 · YAML 自定义源) |
| **数据源** | [TickFlow](https://tickflow.org/auth/register?ref=V3KDKGXPEA) 官方 SDK · fuyao(同花顺 REST) · 插件化扩展(YAML 自定义源) |
| **AI**(可选) | ![OpenAI兼容](https://img.shields.io/badge/OpenAI兼容-412991?logo=openai&logoColor=white) DeepSeek / 通义 / Ollama 等 · 策略生成 / 报告 / **对话助手**(助手依赖工具调用能力, 需 OpenAI 兼容接口) |
| **MCP** | [mcp-server](./mcp-server/README.md)(官方 SDK 2.x, stdio) — 12 工具按 scope 暴露,权限裁决复用开放网关 |
| **前端** | ![React 18](https://img.shields.io/badge/React_18-61DAFB?logo=react&logoColor=black) ![Vite](https://img.shields.io/badge/Vite-646CFF?logo=vite&logoColor=white) ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white) ![Tailwind](https://img.shields.io/badge/Tailwind-06B6D4?logo=tailwindcss&logoColor=white) Tanstack Query · [Lightweight Charts](https://www.tradingview.com/lightweight-charts/)(TradingView 开源) · ![ECharts](https://img.shields.io/badge/ECharts-AA344D?logo=apacheecharts&logoColor=white) · dnd-kit |
@@ -521,7 +521,7 @@ docker run -d --name tsp -p 3018:3018 -v ${PWD}/data:/app/data ghcr.io/shy3130/t
- 需要配置时:从 [.env.example](./.env.example) 复制出 `.env`,命令里加 `--env-file .env`。
- 跑自己改过的代码:fork 后到仓库 **Actions** 页启用 workflow(fork 默认禁用),构建出的 `ghcr.io/<你的用户名>/tick-stock-panel` 用法相同。
- 想用 compose 编排(挂载 `.env` / `tiers.yaml`):参考 [docker-compose.yml](./docker-compose.yml),把 `build:` 段换成 `image: ghcr.io/shy3130/tick-stock-panel:latest`。
- 现成镜像默认不含 插件与老 CPU 兼容内核(合规与体积考虑),有此需求请用方式 B 自构建,详见 [docs/deployment.md](./docs/deployment.md)。
- 现成镜像默认不含老 CPU 兼容内核(体积考虑),有此需求请用方式 B 自构建,详见 [docs/deployment.md](./docs/deployment.md)。
### 方式 B:Docker Compose(本地构建,全套挂载)
@@ -549,8 +549,6 @@ CODEX_CLI_VERSION=0.144.3 docker compose up --build
> Codex CLI 模式允许 TickFlow 容器读取本机 Codex 登录凭据,仅应在受信任的本机环境启用。凭据目录以只读方式挂载,不会写入镜像。
镜像默认**不含** 插件(合规考虑);确需启用执行 `docker compose build --build-arg INCLUDE_=1` 后再 `docker compose up -d`,详见 [docs/deployment.md](./docs/deployment.md)。
</details>
> 📖 Docker 进阶、老 CPU 兼容、访问密码设置等见 [docs/deployment.md](./docs/deployment.md)。
@@ -651,7 +649,7 @@ PORT=3018 # 服务端口
| [docs/strategy-iteration.md](./docs/strategy-iteration.md) | AI 策略迭代协议:台账 / 证据包 / 门槛判定 / 提示词卡片 |
| [docs/mining.md](./docs/mining.md) | 因子与策略挖掘口径、防泄漏、任务隔离和发布边界 |
| [docs/market-phase.md](./docs/market-phase.md) | 市场情绪周期 6 阶段与概念/行业主线识别的口径与设计 |
| [docs/plugin-development.md](./docs/plugin-development.md) | 数据源插件开发规范(以 / fuyao 为参考实现) |
| [docs/plugin-development.md](./docs/plugin-development.md) | 数据源插件开发规范(以 fuyao 为参考实现) |
| [docs/secondary-development.md](./docs/secondary-development.md) | 代码二次开发、前端插槽、后端策略接口与 AI 开发模板 |
| [backend/app/strategy/prompts/strategy-guide.md](./backend/app/strategy/prompts/strategy-guide.md) | 策略开发完整规范(AI 生成与手写) |
@@ -693,8 +691,6 @@ PORT=3018 # 服务端口
内置数据源插件 [fuyao](https://fuyao.aicubes.cn/docs/api-reference/) 提供同花顺 REST 数据接口(行情 / 财务 / 龙虎榜 / 盘前风向标 / 交易日历等),需自备 API Key,使用前请遵守其服务条款
数据源插件 [](https://.linkdiary.cn) 遵循其各自的 ISC 协议。
## 社区
本开源项目已链接并认可 [LINUX DO 社区](https://linux.do)。
+1 -1
View File
@@ -424,7 +424,7 @@ def _realtime_allowed() -> bool:
def _minute_history_days() -> int | None:
"""当前分钟源的 1 分钟历史深度(交易日); None = 深历史(tickflow 基准)。
provider 可选类属性 minute_history_days 声明 (如 = 5,
provider 可选类属性 minute_history_days 声明 (如浅源 = 5,
免费分时接口只保留最近 5 个交易日); 未声明或走 tickflow 时视为深历史。
前端分时档位/默认值据此收窄。
"""
+1 -1
View File
@@ -811,7 +811,7 @@ def _normalize_minute(df_in, default_symbol: str | None = None) -> pl.DataFrame:
# datetime 列:优先用 timestamp(毫秒精度),其次 trade_time
if "timestamp" in df.columns:
# TickFlow 毫秒时间戳为 UTC 基准; 契约要求北京墙钟 naive
# (与 provider 归一口径一致, 见 CONTRIBUTING §3.3)
# (统一归一口径, 见 CONTRIBUTING §3.3)
df = df.with_columns(
pl.from_epoch(pl.col("timestamp").cast(pl.Int64), time_unit="ms")
.dt.replace_time_zone("UTC")
+7 -3
View File
@@ -413,11 +413,15 @@ def test_loader_probe_plugin_key_dispatch(monkeypatch):
assert loader.probe_plugin_key("fuyao", "bad") == (False, "bad")
def test_loader_probe_plugin_key_unsupported_plugin():
def test_loader_probe_plugin_key_unsupported_plugin(monkeypatch):
from app.data_providers.custom import loader
# 未声明 api_key_env → 不支持界面配 Key
ok, reason = loader.probe_plugin_key("", "x")
# 未声明 api_key_env 的插件 → 不支持界面配 Key (桩 manifest 驱动, 不依赖具体内置插件)
monkeypatch.setattr(
loader, "plugin_manifest",
lambda name: {"name": name} if name == "no_key_plugin" else None,
)
ok, reason = loader.probe_plugin_key("no_key_plugin", "x")
assert ok is False and "不支持" in reason
ok, reason = loader.probe_plugin_key("no_such_plugin", "x")
assert ok is False and "不存在" in reason
+3 -9
View File
@@ -1,7 +1,7 @@
"""分钟源历史深度能力 (minute_history_days) 契约测试。
provider 可选类属性 minute_history_days 声明 1 分钟历史深度(交易日):
- = 5 (免费分时接口仅保留最近 5 个交易日)
- 浅源可声明如 5 (免费分时接口仅保留最近 5 个交易日)
- 未声明 / 走 tickflow → None (深历史)
preferences GET 带出该字段, 前端分时档位据此收窄 (浅源默认 5日, 深源默认 20日)。
"""
@@ -20,16 +20,10 @@ def _mock_resolver(monkeypatch, provider, fallback, err=None):
)
def test__declares_five_day_history():
from app.plugins..provider import Provider
assert Provider.minute_history_days == 5
def test_history_days_from_custom_provider(monkeypatch):
"""自定义浅源 → 声明值; 前端据此只显示 1/5 日档。"""
_mock_resolver(monkeypatch, SimpleNamespace(minute_history_days=5), False)
monkeypatch.setattr(preferences, "get_minute_data_provider", lambda: "")
monkeypatch.setattr(preferences, "get_minute_data_provider", lambda: "shallow_demo")
assert settings._minute_history_days() == 5
@@ -50,7 +44,7 @@ def test_history_days_none_for_tickflow(monkeypatch):
def test_history_days_none_when_resolver_fails(monkeypatch):
"""resolver 异常 (registry 损坏) → 降级 None, 不抛 500。"""
_mock_resolver(monkeypatch, None, True, err="registry broken")
monkeypatch.setattr(preferences, "get_minute_data_provider", lambda: "")
monkeypatch.setattr(preferences, "get_minute_data_provider", lambda: "shallow_demo")
assert settings._minute_history_days() is None
+5 -32
View File
@@ -2,12 +2,11 @@
对应设计文档 §4 测试矩阵 (docs/superpowers/specs/2026-07-18-minute-provider-unification-design.md)。
覆盖三个阻断问题:
1. 默认 freq 漂移 (5m → 1m)
2. 自定义源异常直接 500 (无 try/except)
3. 插件化路由重复 + asset_type 未透传
覆盖的阻断问题 (历史回归):
1. 自定义分钟源异常直接 500 (无 try/except)
2. 插件化路由重复 + asset_type 未透传
mock 范式沿用 test__provider.py (monkeypatch 模块属性)。
mock 范式: monkeypatch 模块属性。
"""
from __future__ import annotations
@@ -19,8 +18,6 @@ from unittest.mock import MagicMock
import httpx
import polars as pl
from app.plugins. import provider as sp
from app.plugins..provider import Provider
from app.services import kline_sync
@@ -100,30 +97,6 @@ def test_custom_minute_provider_returns_1m_k(monkeypatch):
assert kwargs.get("asset_type") == "stock"
# ---------- 测试 2: 收到 freq=1m → bridge job period="1" ----------
def test__get_minute_receives_freq_1m(monkeypatch):
"""§4 测试 2: Provider.get_minute(freq="1m") → bridge job period == "1"。
bridge.mjs opMinute 用 String(period), 1m → "1"。
"""
captured: dict = {}
def fake_run_job(job, timeout=None):
captured["job"] = job
# 返回空结果, 测试只验证 job.period
return {"ok": True, "op": job["op"], "rows": {}}
monkeypatch.setattr(sp.bridge, "run_job", fake_run_job)
Provider().get_minute(
["600519.SH"], None, None, freq="1m",
)
assert captured["job"]["op"] == "minute"
assert captured["job"]["period"] == "1"
# ---------- 测试 3: 自定义源异常 + TickFlow 也失败 → 返回空 (非 500) ----------
def test_custom_provider_exception_no_500(monkeypatch):
@@ -265,7 +238,7 @@ def test_on_chunk_done_wrapped_to_3_args(monkeypatch):
def provider_get_minute_side_effect(symbols, *, start_time, end_time,
asset_type, freq, on_chunk_done):
# 模拟 provider 实现内部以 2 参调用 on_chunk_done
# (如 GenericHTTPProvider/provider.py:127 / Provider/provider.py:166)
# (如 GenericHTTPProvider/provider.py:127)
if on_chunk_done is not None:
on_chunk_done(1, 3)
return _mock_minute_df()
+1 -14
View File
@@ -16,7 +16,7 @@ docker run -d --name tsp -p 3018:3018 -v ${PWD}/data:/app/data ghcr.io/shy3130/t
```
- 需要配置时:从 `.env.example` 复制出 `.env`,命令里加 `--env-file .env`。
- 镜像默认**不含** 插件(合规考虑),也不含 `legacy-cpu` / `backtest` extras —— 老 CPU(无 AVX2)或需要 vectorbt 回测时,请用方式 B 通过 `BACKEND_EXTRAS` 自构建。
- 镜像默认不含 `legacy-cpu` / `backtest` extras —— 老 CPU(无 AVX2)或需要 vectorbt 回测时,请用方式 B 通过 `BACKEND_EXTRAS` 自构建。
- 跑自己改过的代码:fork 后到仓库 Actions 页启用 workflow(fork 默认禁用),构建出的 `ghcr.io/<你的用户名>/tick-stock-panel` 用法相同。
- 想要 compose 全套挂载(`.env` / `tiers.yaml` / 数据卷):参考根目录 `docker-compose.yml`,把 `build:` 段换成 `image: ghcr.io/shy3130/tick-stock-panel:latest`。
@@ -41,19 +41,6 @@ docker compose up --build
Docker 采用两阶段构建,前端 dist 拷进后端镜像,**单容器**运行,数据完全在自己手里。
> ⚠️ ** 插件默认不打包(合规考虑)**
>
> 数据源本质是抓取第三方财经网站(如东方财富)的行情接口,未经对方授权,可能违反其服务条款并涉及交易所行情版权问题。**出于合规考虑,Docker 默认构建不再内置 插件依赖**。
>
> - **默认行为**:`docker compose up --build` 构建出的镜像**不含** ,插件不可用。
> - **如确需启用**(自行承担合规责任):
> ```bash
> docker compose build --build-arg INCLUDE_=1
> docker compose up -d
> ```
> - 启用后镜像会额外内置 Node.js 运行时并预装 依赖,插件开箱即用。
> - **建议优先使用 TickFlow 等正规授权数据源。**
更新到新版本:
```bash
+1 -1
View File
@@ -163,7 +163,7 @@
### 数据源插件化
内置 [TickFlow](https://tickflow.org) 官方 SDK(日 K / 分钟 K / 指数 / 财务 / 实时行情),同时支持接入第三方数据源:YAML 声明自有 HTTP 接口,或开发插件源(参考实现 ,Docker 镜像已内置)。多个数据集(日K / 除权因子 / 实时行情 / 分钟K / 五档盘口 / 财务,注册表持续扩展)按源声明自由组合,在「设置 → 数据源」切换。
内置 [TickFlow](https://tickflow.org) 官方 SDK(日 K / 分钟 K / 指数 / 财务 / 实时行情),同时支持接入第三方数据源:YAML 声明自有 HTTP 接口,或开发插件源。多个数据集(日K / 除权因子 / 实时行情 / 分钟K / 五档盘口 / 财务,注册表持续扩展)按源声明自由组合,在「设置 → 数据源」切换。
详见 [custom-data-source.md](./custom-data-source.md) 与 [plugin-development.md](./plugin-development.md)。
+2 -11
View File
@@ -1,6 +1,6 @@
# 数据源插件开发指南
数据源插件是可选的行情数据来源(fuyao、、akshare 等),作为独立模块放在
数据源插件是可选的行情数据来源(fuyao、akshare 等),作为独立模块放在
`backend/app/plugins/` 下。services 层(kline_sync / quote_service / financial_sync)
全部通过统一路由点分流:插件声明了某数据集就走插件,未声明自动回退 TickFlow。
因此**一个合格的插件只需要正确实现契约,不需要改动任何 service / API 代码**;
@@ -57,13 +57,8 @@ TickFlow 的「先探后存」语义:
| runtime | 含义 | 典型场景 |
|---|---|---|
| `python` | 纯 Python 依赖, `pip install` | akshare、tushare |
| `node` | 需要 Node.js 运行时, `npm install` | |
| `none` | 无额外依赖 | 纯 HTTP API 源 |
> ⚠️ 在 Docker 中默认不打包(合规考虑:它抓取第三方财经网站接口,存在版权与
> 反爬风险)。如需启用,构建时传 `--build-arg INCLUDE_=1`,使用风险自负。
> 详见 [deployment.md](./deployment.md)。
`runtime` 字段当前仅用于 UI 展示, 实际依赖检测由 `check` 函数负责。
### check 函数
@@ -218,7 +213,7 @@ provider 不应自行切换或回退到其他数据源。
记日志;完全无法识别的口径 → 拒收并回退 TickFlow。契约仍要求源头写对,守卫只是兜底。
可选类属性 `minute_history_days = 5` 声明 1 分钟历史深度(交易日);未声明视为
深历史(TickFlow 基准)。浅源(如 免费分时仅保留最近 5 个交易日)声明后,
深历史(TickFlow 基准)。浅源(如免费分时仅保留最近 5 个交易日的源)声明后,
个股分时档位自动收窄为可行选项并默认 5 日,深源默认 20 日。
> **全量分钟 (full_minute) 数据集契约**:声明 `full_minute` 数据集并把
@@ -318,10 +313,6 @@ uv run --extra dev python -m ruff check app/plugins/<your_plugin>/ tests/test_<y
- `client.py` — httpx 客户端(X-api-key 认证 + 统一信封解包 + 分页 + 页间隔限频 + 单标的日K + dump 预签名下载, S3 下载不带 Key 头)
- `provider.py` — Provider 实现(实测/文档双字段名映射、百分数→小数制、volume 股→手、上海零点戳 +8h 时区、dump 按 release 版本缓存、软失败、Key 探测)
- `tests/test_fuyao_provider.py` — 73 个契约测试, 是新插件的测试范本
- **`backend/app/plugins//`** — Node 型插件, 通过 subprocess 桥接调用
- `bridge.py` — Python↔Node 桥接 + availability 检测
- `bridge.mjs` — Node 端(并发池、重试、SDK 解析)
- `provider.py` — Provider 实现(归一化、分批、错误降级)
## 路由机制(无需关心, 仅参考)
+1 -1
View File
@@ -78,7 +78,7 @@ function buildOption(data: MinuteKlineRow[], prevClose: number | undefined, avgP
const volNeutral = 'rgba(161,161,170,0.5)'
// 量柱着色基准: 前一分钟 close; 第一根用昨收。
// 不用 row.open — 历史日无真实分钟 open(为 null), close-vs-open 会全偏。
// 不用 row.open — 部分分钟源历史日无真实 open(为 null), close-vs-open 会全偏。
let prevRef: number | null = prevClose ?? null
for (let i = 0; i < data.length; i++) {
const timeKey = formatMinuteTime(data[i].datetime)
@@ -76,7 +76,7 @@ function buildModel(sessions: MinuteKlineSession[]) {
const dayValues: (number | null)[] = []
const dayAverages: (number | null)[] = []
// 量柱着色基准: 前一分钟 close; 当日第一根用 session 昨收。
// 不用 row.open — 历史日无真实分钟 open(为 null), close-vs-open 会全偏。
// 不用 row.open — 部分分钟源历史日无真实 open(为 null), close-vs-open 会全偏。
let prevRef: number | null = session.prev_close
for (const time of FULL_DAY_TIMES) {
const point = rowsByTime.get(time)
@@ -210,7 +210,7 @@ export function StockPreviewDialogContent({ symbol, name, onClose, triggerInfo,
const { data: prefs } = usePreferences()
const intradayRefetchMs = (prefs?.minute_intraday_refresh_interval ?? 6) * 1000
// 分时档位按分钟源历史深度收窄: 浅源(如 =5日)只显示可行档位、默认 5日;
// 分时档位按分钟源历史深度收窄: 浅源(如仅 5 日)只显示可行档位、默认 5日;
// 深源(tickflow/未声明)全档位、默认 20日。用户已保存的可行选择优先保留。
const minuteHistoryDays = prefs?.minute_history_days ?? null
const dayOptions = useMemo<number[]>(
+1 -1
View File
@@ -247,7 +247,7 @@ export interface AiStockReport {
// ===== Kline =====
export interface MinuteKlineRow {
datetime: string
/** 分钟开盘价; 部分数据源( 历史日)无真实分钟 open, 为 null */
/** 分钟开盘价; 部分数据源历史日无真实分钟 open, 为 null */
open: number | null
high: number
low: number
+1 -2
View File
@@ -556,7 +556,6 @@ function DataSourceStep({ onNext, onBack }: { onNext: () => void; onBack: () =>
const ROUTE_PRIORITY = [
{ name: 'tickflow', display: 'TickFlow' },
{ name: 'fuyao', display: 'fuyao' },
{ name: '', display: '' },
]
function ResultStep({ onNext, onBack }: { onNext: () => void; onBack: () => void }) {
@@ -620,7 +619,7 @@ function ResultStep({ onNext, onBack }: { onNext: () => void; onBack: () => void
</div>
<p className="mt-2.5 text-sm text-secondary leading-relaxed">
进入本步时已按默认优先级
<span className="text-foreground font-medium"> TickFlow → fuyao → </span>
<span className="text-foreground font-medium"> TickFlow → fuyao </span>
自动设置各数据集的路由:前者可用的能力归前者,没有则顺延下一个可用源。后续可随时在
<span className="text-foreground font-medium"> 设置 → 数据源 </span>按数据集改选。
</p>
+1 -1
View File
@@ -67,7 +67,7 @@ hiddenimports += collect_submodules("polars")
# entry 又是 importlib 字符串动态导入 — 两条链路静态分析都看不见, 必须双声明:
# datas 把清单 yaml 落盘 (frozen 下 plugins_dir() 恰好解析到 _internal/app/plugins),
# hiddenimports 把 provider/client 塞进 PYZ。
# 注: 出于合规暂不进桌面包 (与 GHCR 镜像口径一致), 需要时仿照添加。
# 注: 其他内置插件如需进桌面包, 仿照 fuyao 双声明添加。
datas += [
(str(ROOT / "backend" / "app" / "plugins" / "fuyao" / "plugin.yaml"), "app/plugins/fuyao"),
]
+1 -1
View File
@@ -121,7 +121,7 @@ docker run -d --name tsp -p 3018:3018 -v ${PWD}/data:/app/data ghcr.io/shy3130/t
启动完成后访问 `http://localhost:3018`。需要配置时,从 `.env.example` 复制出 `.env`,并在命令中追加 `--env-file .env`。
现成镜像默认不含 插件和老 CPU 兼容内核;有这些需求时请改用 Docker 部署并按 [docs/deployment.md](./docs/deployment.md) 传入构建参数。
现成镜像默认不含老 CPU 兼容内核;有这些需求时请改用 Docker 部署并按 [docs/deployment.md](./docs/deployment.md) 传入构建参数。
### 2.6 Docker 部署