feat(docker): 优化镜像支持中文字体及调度运行模式
- 基础镜像中添加多款中文字体,支持中文显示 - 主镜像安装中文字体并设置上海时区环境变量 - Dockerfile中创建日志目录并修改默认启动命令为定时调用调度器脚本 - 构建脚本支持动态镜像名,自动构建基础镜像,完善运行容器示例 - docker-compose修改为仅启动调度器服务,挂载相关配置、密钥、数据和日志目录 - 依赖更新,丰富金融数据、技术分析、绘图、机器学习及环境变量支持库 - 调度脚本参数调整,支持立即运行并退出及非后台模式运行切换 - 报告绘图中优先使用基础镜像预装的中文字体配置,提高字体兼容性和显示效果
This commit is contained in:
16
Dockerfile
16
Dockerfile
@@ -3,6 +3,11 @@ FROM index-base:latest
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 安装中文字体(使用清华源加速)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
fonts-wqy-microhei \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 复制依赖文件
|
||||
COPY requirements.txt .
|
||||
|
||||
@@ -11,9 +16,14 @@ RUN uv pip install --system -r requirements.txt
|
||||
# 仅复制除 data 目录外的应用代码, data 在 dockerignore 中已经被排除
|
||||
COPY . .
|
||||
|
||||
# 创建日志目录
|
||||
RUN mkdir -p /app/logs
|
||||
|
||||
# 暴露端口
|
||||
# 设置时区为上海
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
# 暴露端口(如需Web服务)
|
||||
EXPOSE 80
|
||||
|
||||
# 运行应用
|
||||
# CMD ["python", "update_data.py"]
|
||||
# 运行定时任务调度器(默认daemon模式)
|
||||
CMD ["python", "scripts/daily_scheduler.py", "--time", "09:00"]
|
||||
@@ -64,6 +64,13 @@ RUN apt-get update && apt-get install -y \
|
||||
libopengl0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 安装中文字体
|
||||
RUN apt-get update && apt-get install -y \
|
||||
fonts-noto-cjk \
|
||||
fonts-wqy-zenhei \
|
||||
fonts-wqy-microhei \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 安装Playwright浏览器
|
||||
RUN playwright install chromium
|
||||
RUN playwright install-deps
|
||||
|
||||
@@ -1,20 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Value Investing App - 构建和推送脚本
|
||||
# ETF策略 - 构建和推送脚本
|
||||
# 使用 docker 构建镜像并推送到私有仓库
|
||||
|
||||
set -e # 遇到错误立即退出
|
||||
|
||||
# 配置变量
|
||||
IMAGE_NAME="index-app"
|
||||
IMAGE_NAME="${1:-etf-scheduler}"
|
||||
REGISTRY="192.168.0.115:5000"
|
||||
TAG="latest"
|
||||
FULL_IMAGE_NAME="${REGISTRY}/${IMAGE_NAME}:${TAG}"
|
||||
|
||||
echo "========================================="
|
||||
echo "Value Investing App 构建和推送脚本"
|
||||
echo "ETF策略 构建和推送脚本"
|
||||
echo "========================================="
|
||||
echo "镜像名称: ${IMAGE_NAME}"
|
||||
echo ""
|
||||
|
||||
# 检查并构建基础镜像(如果不存在)
|
||||
echo "0. 检查基础镜像..."
|
||||
if ! docker images | grep -q "index-base"; then
|
||||
echo " 基础镜像不存在,开始构建..."
|
||||
if [ -f "Dockerfile_base" ]; then
|
||||
docker build --platform linux/arm64 -f Dockerfile_base -t index-base:latest .
|
||||
echo " ✅ 基础镜像构建成功"
|
||||
else
|
||||
echo " ⚠️ 未找到 Dockerfile_base,跳过基础镜像构建"
|
||||
fi
|
||||
else
|
||||
echo " ✅ 基础镜像已存在"
|
||||
fi
|
||||
|
||||
# 构建镜像
|
||||
echo "1. 构建 Docker 镜像..."
|
||||
@@ -64,8 +79,12 @@ echo "========================================="
|
||||
echo "镜像地址: ${FULL_IMAGE_NAME}"
|
||||
echo ""
|
||||
echo "可以使用以下命令运行容器:"
|
||||
echo "docker run -d -p 5000:5000 --name value-investing-container ${FULL_IMAGE_NAME}"
|
||||
echo "docker run -d --name etf-scheduler-container \\"
|
||||
echo " -v /path/to/.env:/app/.env \\"
|
||||
echo " -v /path/to/hk_ecs.pem:/app/hk_ecs.pem \\"
|
||||
echo " -v /path/to/data:/app/data \\"
|
||||
echo " ${FULL_IMAGE_NAME}"
|
||||
echo ""
|
||||
echo "或者在其他机器上拉取镜像:"
|
||||
echo "docker pull ${FULL_IMAGE_NAME}"
|
||||
echo "docker pull k3d-quant-registry:5000/${IMAGE_NAME}:${TAG}"
|
||||
echo "========================================="
|
||||
@@ -1,32 +1,26 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: etf_postgres
|
||||
etf-scheduler:
|
||||
image: 192.168.0.115:5000/etf-scheduler:latest
|
||||
container_name: etf-scheduler
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: etf_db
|
||||
POSTGRES_USER: admin
|
||||
POSTGRES_PASSWORD: admin
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
|
||||
ports:
|
||||
- "5432:5432"
|
||||
- TZ=Asia/Shanghai
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- etf_network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U admin -d etf_db"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
etf_network:
|
||||
driver: bridge
|
||||
# 挂载环境变量文件(必需)
|
||||
- ./.env:/app/.env:ro
|
||||
# 挂载 SSH 私钥(必需,用于 yfinance 数据下载)
|
||||
- ./hk_ecs.pem:/app/hk_ecs.pem:ro
|
||||
# 挂载数据目录(持久化)
|
||||
- ./data:/app/data
|
||||
# 挂载日志目录
|
||||
- ./logs:/app/logs
|
||||
# 挂载 results 目录(保存报告)
|
||||
- ./results:/app/results
|
||||
# 默认daemon模式运行,只需简单命令即可
|
||||
# command: ["python", "scripts/daily_scheduler.py"]
|
||||
# 如需立即执行一次并退出:
|
||||
# command: ["python", "scripts/daily_scheduler.py", "--run-now"]
|
||||
# 如需执行一次后进入定时循环:
|
||||
# command: ["python", "scripts/daily_scheduler.py", "--no-daemon"]
|
||||
|
||||
@@ -1,29 +1,53 @@
|
||||
# 核心依赖
|
||||
# ==================== 核心依赖 ====================
|
||||
pandas>=1.5.0
|
||||
numpy>=1.24.0
|
||||
|
||||
# 数据库连接
|
||||
# ==================== 数据库连接 ====================
|
||||
psycopg2-binary>=2.9.0
|
||||
sqlalchemy>=2.0.0
|
||||
|
||||
# 日志
|
||||
# ==================== 日志 ====================
|
||||
loguru>=0.7.0
|
||||
|
||||
|
||||
# HTTP请求
|
||||
# ==================== HTTP请求 ====================
|
||||
requests>=2.28.0
|
||||
urllib3>=1.26.0
|
||||
|
||||
# 进度条
|
||||
# ==================== 进度条 ====================
|
||||
tqdm>=4.65.0
|
||||
|
||||
|
||||
# 时间处理
|
||||
# ==================== 时间处理 ====================
|
||||
python-dateutil>=2.8.0
|
||||
schedule
|
||||
akshare
|
||||
TA-Lib
|
||||
tabulate
|
||||
schedule>=1.2.0
|
||||
|
||||
# ==================== 金融数据 ====================
|
||||
tushare>=1.2.0
|
||||
yfinance>=0.2.0
|
||||
akshare>=1.10.0
|
||||
|
||||
# ==================== 技术分析 ====================
|
||||
TA-Lib>=0.4.0
|
||||
|
||||
# ==================== 表格输出 ====================
|
||||
tabulate>=0.9.0
|
||||
|
||||
# ==================== 自动化测试 ====================
|
||||
playwright>=1.45.1
|
||||
retry>=0.9.2
|
||||
|
||||
# ==================== 重试机制 ====================
|
||||
retry>=0.9.2
|
||||
|
||||
# ==================== 环境变量 ====================
|
||||
python-dotenv>=1.0.0
|
||||
|
||||
# ==================== 阿里云OSS ====================
|
||||
oss2>=2.18.0
|
||||
|
||||
# ==================== YAML配置 ====================
|
||||
PyYAML>=6.0
|
||||
|
||||
# ==================== 绘图 ====================
|
||||
matplotlib>=3.7.0
|
||||
|
||||
# ==================== 机器学习 ====================
|
||||
scikit-learn>=1.3.0
|
||||
@@ -258,7 +258,7 @@ def main():
|
||||
"--time",
|
||||
type=str,
|
||||
default="09:00",
|
||||
help="执行时间 (HH:MM),默认15:30",
|
||||
help="执行时间 (HH:MM),默认09:00",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--config",
|
||||
@@ -269,12 +269,12 @@ def main():
|
||||
parser.add_argument(
|
||||
"--run-now",
|
||||
action="store_true",
|
||||
help="立即执行一次(不启动定时任务)",
|
||||
help="立即执行一次并退出(不启动定时任务)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--daemon",
|
||||
"--no-daemon",
|
||||
action="store_true",
|
||||
help="后台运行(持续执行定时任务)",
|
||||
help="非后台模式:执行一次后进入定时循环",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -282,19 +282,19 @@ def main():
|
||||
(project_root / "logs").mkdir(exist_ok=True)
|
||||
|
||||
if args.run_now:
|
||||
# 立即执行一次
|
||||
# 立即执行一次并退出
|
||||
daily_task(args.config)
|
||||
elif args.daemon:
|
||||
# 后台运行模式
|
||||
setup_schedule(args.time, args.config)
|
||||
run_scheduler_loop()
|
||||
else:
|
||||
# 默认:设置定时任务并执行一次(用于测试)
|
||||
elif args.no_daemon:
|
||||
# 非后台模式:执行一次后进入定时循环
|
||||
setup_schedule(args.time, args.config)
|
||||
logger.info("执行一次任务用于测试...")
|
||||
daily_task(args.config)
|
||||
logger.info("测试完成,启动定时任务循环(按 Ctrl+C 停止)...")
|
||||
run_scheduler_loop()
|
||||
else:
|
||||
# 默认:后台daemon模式
|
||||
setup_schedule(args.time, args.config)
|
||||
run_scheduler_loop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -292,7 +292,14 @@ def _plot_report_chart(
|
||||
metrics: dict = None,
|
||||
):
|
||||
"""绘制报告图表"""
|
||||
plt.rcParams["font.sans-serif"] = ["Arial Unicode MS", "SimHei", "DejaVu Sans"]
|
||||
# 设置中文字体(优先使用基础镜像中已存在的字体)
|
||||
plt.rcParams["font.sans-serif"] = [
|
||||
"WenQuanYi Zen Hei", # 基础镜像已安装
|
||||
"WenQuanYi Micro Hei", # 将要安装
|
||||
"DejaVu Sans",
|
||||
"SimHei",
|
||||
"Arial Unicode MS"
|
||||
]
|
||||
plt.rcParams["axes.unicode_minus"] = False
|
||||
|
||||
strategy_nav = backtest_result["轮动策略净值"]
|
||||
|
||||
Reference in New Issue
Block a user