Files
evotraders/start-dev.sh

512 lines
14 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
#
# 大时代 Development Startup Script
# ================================
#
# 启动模式说明:
# -------------
# 本脚本只支持当前开发主路径:
# 启动 4 个独立服务 + 由 runtime_service 托管的 Gateway
# - agent_service (端口 8000): Agent 生命周期管理
# - runtime_service (端口 8003): 运行时配置和 Pipeline 执行
# - trading_service (端口 8001): 市场数据和交易操作
# - news_service (端口 8002): 新闻采集和富化
# - gateway (端口 8765): 由 runtime_service 拉起的 WebSocket 网关
#
# 用法:
# ./start-dev.sh # 启动开发环境
# ./start-dev.sh --help # 显示帮助信息
#
set -euo pipefail
# ============================================
# 配置与常量
# ============================================
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
# 服务端点配置
readonly AGENT_SERVICE_PORT=8000
readonly TRADING_SERVICE_PORT=8001
readonly NEWS_SERVICE_PORT=8002
readonly RUNTIME_SERVICE_PORT=8003
readonly GATEWAY_PORT=8765
# 颜色定义
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly CYAN='\033[0;36m'
readonly NC='\033[0m' # No Color
# 进程 ID 数组
PIDS=()
# ============================================
# 工具函数
# ============================================
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${CYAN}[STEP]${NC} $1"
}
log_debug() {
echo -e "${BLUE}[DEBUG]${NC} $1"
}
# ============================================
# 帮助信息
# ============================================
show_help() {
cat << 'EOF'
大时代 Development Startup Script
用法:
./start-dev.sh [选项]
选项:
--help, -h 显示此帮助信息
开发模式:
启动 4 个独立微服务 + 托管 Gateway各服务独立进程便于单独调试
- agent_service: http://localhost:8000 (Agent 生命周期)
- trading_service: http://localhost:8001 (市场数据)
- news_service: http://localhost:8002 (新闻服务)
- runtime_service: http://localhost:8003 (运行时管理)
- gateway: ws://localhost:8765 (由 runtime_service 托管)
环境要求:
- Python 3.9+
- 虚拟环境 (推荐 .venv)
- .env 文件 (可选但推荐)
示例:
./start-dev.sh # 启动开发环境
EOF
}
# ============================================
# 参数解析
# ============================================
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--help|-h)
show_help
exit 0
;;
*)
log_warn "未知选项: $1"
log_info "使用 --help 查看帮助信息"
exit 1
;;
esac
done
}
# ============================================
# 启动前检查
# ============================================
check_python_version() {
log_step "检查 Python 版本..."
if ! command -v python >/dev/null 2>&1; then
log_error "未找到 python 命令"
exit 1
fi
local python_version
python_version=$(python --version 2>&1 | awk '{print $2}')
log_debug "Python 版本: $python_version"
python - <<'PY' || {
import sys
if sys.version_info < (3, 9):
print(f"Python 3.9+ 是必需的,当前版本: {sys.version}")
sys.exit(1)
print(f"Python 版本检查通过: {sys.version.split()[0]}")
PY
log_error "Python 版本不符合要求 (需要 3.9+)"
exit 1
}
}
check_virtual_env() {
log_step "检查虚拟环境..."
if [[ -n "${VIRTUAL_ENV:-}" ]]; then
log_info "已激活虚拟环境: $VIRTUAL_ENV"
return 0
fi
if [[ -f "$SCRIPT_DIR/.venv/bin/activate" ]]; then
log_warn "未激活虚拟环境,自动激活 .venv"
# shellcheck disable=SC1091
source "$SCRIPT_DIR/.venv/bin/activate"
log_info "虚拟环境已激活: $VIRTUAL_ENV"
else
log_warn "未找到虚拟环境,使用系统 Python"
fi
}
check_required_commands() {
log_step "检查必要命令..."
local missing=()
if ! command -v python >/dev/null 2>&1; then
missing+=("python")
fi
if ! command -v lsof >/dev/null 2>&1; then
missing+=("lsof")
fi
if [[ ${#missing[@]} -gt 0 ]]; then
log_error "缺少必要命令: ${missing[*]}"
log_info "请安装缺失的命令后重试"
exit 1
fi
log_info "所有必要命令已安装"
}
check_python_modules() {
log_step "检查 Python 依赖模块..."
local modules=("fastapi" "uvicorn" "websockets" "yaml" "dotenv")
local missing=()
for module in "${modules[@]}"; do
if ! python -c "import $module" 2>/dev/null; then
missing+=("$module")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
log_error "缺少 Python 模块: ${missing[*]}"
log_info "请安装依赖: uv pip install -e '.[dev]' 或 pip install -r requirements.txt"
exit 1
fi
log_info "所有依赖模块已安装"
}
check_env_file() {
log_step "检查环境配置文件..."
if [[ -f "$SCRIPT_DIR/.env" ]]; then
log_info "加载环境变量: .env"
set -a
# shellcheck disable=SC1091
source "$SCRIPT_DIR/.env"
set +a
else
log_warn ".env 文件不存在,使用默认配置"
log_info "提示: 复制 env.template 到 .env 并配置您的 API 密钥"
fi
}
check_ports() {
log_step "检查端口占用情况..."
local ports=($AGENT_SERVICE_PORT $TRADING_SERVICE_PORT $NEWS_SERVICE_PORT $RUNTIME_SERVICE_PORT $GATEWAY_PORT)
local occupied=()
for port in "${ports[@]}"; do
if lsof -Pi :"$port" -sTCP:LISTEN -t >/dev/null 2>&1; then
occupied+=("$port")
fi
done
if [[ ${#occupied[@]} -gt 0 ]]; then
log_warn "以下端口已被占用: ${occupied[*]}"
log_info "尝试释放端口..."
for port in "${occupied[@]}"; do
kill_port "$port"
done
else
log_info "所有端口可用"
fi
}
kill_port() {
local port="$1"
local pids
pids=$(lsof -ti :"$port" 2>/dev/null || true)
if [[ -n "$pids" ]]; then
log_warn "释放端口 $port (PID: $pids)"
echo "$pids" | xargs kill -9 2>/dev/null || true
sleep 0.5
fi
}
check_optional_services() {
log_step "检查可选服务..."
# 检查 npm用于前端
if ! command -v npm >/dev/null 2>&1; then
log_warn "npm 未安装,前端启动功能不可用"
else
log_info "npm 已安装"
fi
# 检查 OpenClaw gateway
check_openclaw_gateway
}
check_openclaw_gateway() {
local target_host="127.0.0.1"
local target_port="18789"
if python - <<PY >/dev/null 2>&1; then
import socket
sock = socket.socket()
sock.settimeout(1.0)
sock.connect(("${target_host}", ${target_port}))
sock.close()
PY
log_info "OpenClaw gateway 可连接: ws://${target_host}:${target_port}"
else
log_warn "OpenClaw gateway 未启动: ws://${target_host}:${target_port}"
log_info " OpenClaw 面板功能将不可用"
fi
}
# ============================================
# 服务启动函数
# ============================================
start_service() {
local name="$1"
local app_path="$2"
local port="$3"
log_info "启动 ${name} (端口 ${port})..."
SERVICE_NAME="${name}" python -m uvicorn "${app_path}" \
--host 0.0.0.0 \
--port "${port}" \
--reload \
--reload-dir backend \
--log-level warning \
--no-access-log &
PIDS+=($!)
}
wait_for_runtime_service() {
log_step "等待 runtime_service 就绪..."
local runtime_url="http://127.0.0.1:${RUNTIME_SERVICE_PORT}/health"
local attempts=30
for ((i=1; i<=attempts; i++)); do
if python - <<PY >/dev/null 2>&1; then
import urllib.request
with urllib.request.urlopen("${runtime_url}", timeout=1.5) as resp:
raise SystemExit(0 if resp.status == 200 else 1)
PY
log_info "runtime_service 已就绪: ${runtime_url}"
return 0
fi
sleep 1
done
log_error "runtime_service 未在预期时间内就绪"
return 1
}
start_managed_runtime() {
log_step "通过 runtime_service 创建默认运行时..."
local runtime_api="http://127.0.0.1:${RUNTIME_SERVICE_PORT}/api/runtime/start"
if ! python - <<PY; then
import json
import os
import sys
import urllib.request
tickers_env = os.getenv("TICKERS", "")
tickers = [item.strip().upper() for item in tickers_env.split(",") if item.strip()]
if not tickers:
tickers = ["AAPL", "MSFT", "GOOGL", "AMZN", "NVDA", "META", "TSLA", "AMD", "NFLX", "AVGO", "PLTR", "COIN"]
def _env_int(name: str, default: int) -> int:
value = os.getenv(name, "").strip()
return int(value) if value else default
def _env_float(name: str, default: float) -> float:
value = os.getenv(name, "").strip()
return float(value) if value else default
payload = {
"launch_mode": "fresh",
"tickers": tickers,
"schedule_mode": os.getenv("SCHEDULE_MODE", "daily").strip() or "daily",
"interval_minutes": _env_int("INTERVAL_MINUTES", 60),
"trigger_time": os.getenv("TRIGGER_TIME", "09:30").strip() or "09:30",
"max_comm_cycles": _env_int("MAX_COMM_CYCLES", 2),
"initial_cash": _env_float("INITIAL_CASH", 100000.0),
"margin_requirement": _env_float("MARGIN_REQUIREMENT", 0.0),
"enable_memory": os.getenv("ENABLE_MEMORY", "").strip().lower() in {"1", "true", "yes", "on"},
"mode": os.getenv("RUNTIME_MODE", "live").strip() or "live",
"poll_interval": _env_int("POLL_INTERVAL", 10),
}
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(
"${runtime_api}",
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
body = json.loads(resp.read().decode("utf-8"))
except Exception as exc:
print(f"FAILED: {exc}", file=sys.stderr)
raise
print(json.dumps(body, ensure_ascii=False))
PY
log_error "通过 runtime_service 创建运行时失败"
return 1
fi
log_info "默认运行时已创建Gateway 将由 runtime_service 托管"
}
# ============================================
# 微服务模式启动
# ============================================
start_microservices_mode() {
log_step "启动微服务模式..."
echo ""
echo -e "${CYAN}==========================================${NC}"
echo -e "${CYAN} 服务端点 ${NC}"
echo -e "${CYAN}==========================================${NC}"
echo -e " agent_service: http://localhost:${AGENT_SERVICE_PORT}"
echo -e " runtime_service: http://localhost:${RUNTIME_SERVICE_PORT}"
echo -e " trading_service: http://localhost:${TRADING_SERVICE_PORT}"
echo -e " news_service: http://localhost:${NEWS_SERVICE_PORT}"
echo -e " gateway: ws://localhost:${GATEWAY_PORT} (由 runtime_service 拉起)"
echo -e "${CYAN}==========================================${NC}"
echo ""
# 设置服务 URL 环境变量
export TRADING_SERVICE_URL="${TRADING_SERVICE_URL:-http://localhost:${TRADING_SERVICE_PORT}}"
export NEWS_SERVICE_URL="${NEWS_SERVICE_URL:-http://localhost:${NEWS_SERVICE_PORT}}"
export RUNTIME_SERVICE_URL="${RUNTIME_SERVICE_URL:-http://localhost:${RUNTIME_SERVICE_PORT}}"
export OPENCLAW_SERVICE_URL="${OPENCLAW_SERVICE_URL:-http://localhost:18789}"
export ENABLE_DASHBOARD_COMPAT_EXPORTS="${ENABLE_DASHBOARD_COMPAT_EXPORTS:-true}"
log_debug "环境变量:"
log_debug " TRADING_SERVICE_URL=${TRADING_SERVICE_URL}"
log_debug " NEWS_SERVICE_URL=${NEWS_SERVICE_URL}"
log_debug " RUNTIME_SERVICE_URL=${RUNTIME_SERVICE_URL}"
log_debug " OPENCLAW_SERVICE_URL=${OPENCLAW_SERVICE_URL}"
echo ""
# 启动 4 个微服务
start_service "agent_service" "backend.apps.agent_service:app" "$AGENT_SERVICE_PORT"
start_service "runtime_service" "backend.apps.runtime_service:app" "$RUNTIME_SERVICE_PORT"
start_service "trading_service" "backend.apps.trading_service:app" "$TRADING_SERVICE_PORT"
start_service "news_service" "backend.apps.news_service:app" "$NEWS_SERVICE_PORT"
wait_for_runtime_service
start_managed_runtime
echo ""
log_info "所有服务已启动"
log_info "按 Ctrl+C 停止所有服务"
echo ""
}
# ============================================
# 清理与信号处理
# ============================================
cleanup() {
if [[ ${#PIDS[@]} -gt 0 ]]; then
echo ""
log_step "正在停止服务..."
for pid in "${PIDS[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null || true
fi
done
# 等待进程结束
wait "${PIDS[@]}" 2>/dev/null || true
log_info "所有服务已停止"
fi
}
trap cleanup EXIT INT TERM
# ============================================
# 主程序
# ============================================
main() {
# 解析命令行参数
parse_args "$@"
# 显示启动横幅
echo ""
echo -e "${CYAN}==========================================${NC}"
echo -e "${CYAN} 大时代 Development Environment ${NC}"
echo -e "${CYAN}==========================================${NC}"
echo ""
# 切换到项目根目录
cd "$SCRIPT_DIR"
log_debug "工作目录: $SCRIPT_DIR"
# 启动前检查
check_required_commands
check_python_version
check_virtual_env
check_python_modules
check_env_file
check_ports
check_optional_services
echo ""
echo -e "${GREEN}==========================================${NC}"
echo -e "${GREEN} 启动前检查完成 ${NC}"
echo -e "${GREEN}==========================================${NC}"
echo ""
start_microservices_mode
# 等待所有后台进程
wait
}
# 执行主程序
main "$@"