从 Point-in-Time 股票筛选到统一 Web 前端的完整链路: 筛选 → 画像 → 策略 → 回测 → Walk-forward → 绩效分析 → 报告/前端。 架构 - 数据层与策略层分离;策略代码不写 SQL,只经 data/repo.py 取数 - 所有业务阈值集中在 config/*.yml,代码零硬编码(字段写错直接报错) - 报告只做「run_id → SQL → 渲染」,不做任何计算,数字可追溯 - 前后端分离:output/ 静态站点 + hdiv web 提供的 REST API 数据安全 - 只增不删:SQL 钩子拦截 DELETE/DROP/TRUNCATE,并有源码扫描测试守护 - qlib 原有表只读,本项目数据写入 hd_ 前缀表 - 回补使用 INSERT IGNORE,保证既有行零改动 - .env 存密钥且已 gitignore;output/、logs/、.venv/ 不入库 交付物 - 30 张 hd_* 表、7 个 YAML 配置、283 项自动化测试 - 统一 Web 前端(hash 路由 SPA)+ nginx 部署配置与 launchd 托管脚本 如实声明的限制 - 策略缺少稳定的样本外超额收益(Walk-forward 7 窗口均值 -0.95%, 基准 +2.29%);其价值体现在回撤控制,而非超额收益 - 涨跌停/停牌约束仅覆盖 2019 年起;index_weight 尚未填充 - AI Agent 层(plan.md 第四版 P8)未实现 详见 docs/user-guide.md 与 docs/implementation-status.md。
110 lines
3.3 KiB
Bash
Executable File
110 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# ============================================================
|
||
# 后端服务管理脚本(macOS / Linux 通用)
|
||
#
|
||
# 用法:
|
||
# ./deploy/serve.sh start 启动(默认 127.0.0.1:8099,仅 API 模式)
|
||
# ./deploy/serve.sh start-dev 启动开发模式(API + 静态,可直接浏览器预览)
|
||
# ./deploy/serve.sh stop 停止
|
||
# ./deploy/serve.sh restart 重启
|
||
# ./deploy/serve.sh status 查看状态
|
||
# ./deploy/serve.sh log 跟踪日志
|
||
#
|
||
# 说明:生产环境建议用 systemd / launchd 托管;本脚本用于快速启停与排障。
|
||
# ============================================================
|
||
set -euo pipefail
|
||
|
||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
cd "$PROJECT_ROOT"
|
||
|
||
PY="${PROJECT_ROOT}/.venv/bin/python"
|
||
PORT="${HDDIV_PORT:-8099}"
|
||
HOST="${HDDIV_HOST:-127.0.0.1}"
|
||
MODE="${HDDIV_MODE:-api-only}" # api-only | full
|
||
LOG_DIR="${PROJECT_ROOT}/logs"
|
||
PID_FILE="${LOG_DIR}/web.pid"
|
||
MODE_FILE="${LOG_DIR}/web.mode"
|
||
LOG_FILE="${LOG_DIR}/web.log"
|
||
|
||
mkdir -p "$LOG_DIR"
|
||
|
||
is_running() {
|
||
[[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null
|
||
}
|
||
|
||
start() {
|
||
if is_running; then
|
||
echo "已在运行(PID $(cat "$PID_FILE"))"
|
||
return 0
|
||
fi
|
||
if [[ ! -x "$PY" ]]; then
|
||
echo "找不到虚拟环境:$PY" >&2
|
||
echo "请先执行:uv venv --python 3.12 .venv && uv pip install --python .venv/bin/python pandas numpy sqlalchemy pymysql pydantic pyyaml jinja2 httpx python-dotenv" >&2
|
||
exit 1
|
||
fi
|
||
local args=(--host "$HOST" --port "$PORT")
|
||
[[ "$MODE" == "api-only" ]] && args+=(--api-only)
|
||
|
||
PYTHONPATH=src nohup "$PY" -m hdiv web "${args[@]}" >> "$LOG_FILE" 2>&1 &
|
||
echo $! > "$PID_FILE"
|
||
echo "$MODE" > "$MODE_FILE"
|
||
sleep 2
|
||
if is_running; then
|
||
echo "已启动(PID $(cat "$PID_FILE")) 模式=${MODE} 监听 http://${HOST}:${PORT}"
|
||
echo "日志:$LOG_FILE"
|
||
if [[ "$MODE" == "api-only" ]]; then
|
||
echo "注意:api-only 模式下静态文件需由 nginx 提供;本机预览请用 start-dev"
|
||
else
|
||
echo "本机预览:http://${HOST}:${PORT}/"
|
||
fi
|
||
else
|
||
echo "启动失败,日志尾部:" >&2
|
||
tail -20 "$LOG_FILE" >&2
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
stop() {
|
||
SAVED_MODE="$(cat "$MODE_FILE" 2>/dev/null || echo unknown)"
|
||
if ! is_running; then
|
||
echo "未在运行"
|
||
rm -f "$PID_FILE" "$MODE_FILE"
|
||
return 0
|
||
fi
|
||
local pid; pid="$(cat "$PID_FILE")"
|
||
kill "$pid" 2>/dev/null || true
|
||
for _ in $(seq 1 20); do
|
||
kill -0 "$pid" 2>/dev/null || break
|
||
sleep 0.2
|
||
done
|
||
kill -9 "$pid" 2>/dev/null || true
|
||
rm -f "$PID_FILE" "$MODE_FILE"
|
||
echo "已停止(PID ${pid})"
|
||
}
|
||
|
||
status() {
|
||
SAVED_MODE="$(cat "$MODE_FILE" 2>/dev/null || echo unknown)"
|
||
if is_running; then
|
||
local pid; pid="$(cat "$PID_FILE")"
|
||
echo "运行中 PID=${pid} 模式=${SAVED_MODE} 监听 http://${HOST}:${PORT}"
|
||
echo -n "健康检查:"
|
||
curl -s --max-time 3 "http://${HOST}:${PORT}/api/health" || echo "(无响应)"
|
||
echo
|
||
else
|
||
echo "未在运行"
|
||
fi
|
||
}
|
||
|
||
case "${1:-}" in
|
||
start) MODE=api-only start ;;
|
||
start-dev) MODE=full start ;;
|
||
stop) stop ;;
|
||
restart) SAVED_MODE="$(cat "$MODE_FILE" 2>/dev/null || echo api-only)"
|
||
stop; MODE="${SAVED_MODE}" start ;;
|
||
status) status ;;
|
||
log) tail -f "$LOG_FILE" ;;
|
||
*)
|
||
sed -n '2,15p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
||
exit 2 ;;
|
||
esac
|