#!/bin/bash set -e # 出错立即停止 ### 函数定义区 ### # 1. 从SQLite数据库获取当前开放交易的币对列表 get_open_trades_pairs() { local db_path="$1" local pairs=() if [ ! -f "$db_path" ]; then echo "⚠️ 交易数据库文件 $db_path 不存在,开放交易对列表为空" >&2 echo "" return fi local query_result=$(sqlite3 -header -csv "$db_path" "SELECT id, pair, open_date, amount, open_rate, max_rate, close_profit, stake_amount FROM trades WHERE is_open = 1 ORDER BY open_date DESC LIMIT 10;") pairs=($(echo "$query_result" | awk -F ',' 'NR > 1 {print $2}' | tr -d '"')) echo "从数据库获取到开放交易对: ${pairs[*]}" >&2 echo "${pairs[@]}" } # 2. 从远程接口获取币对列表(带重试机制) get_remote_pairs() { local remote_url="$1" local pairs=() local max_retries=5 local retry_delay=3 local attempt=1 if [ -z "$remote_url" ]; then echo "⚠️ 未提供远程币对列表URL,远程币对列表为空" >&2 echo "" return fi # 重试循环 while [ $attempt -le $max_retries ]; do echo "正在从远程URL获取币对列表 (尝试 $attempt/$max_retries): $remote_url" >&2 local pairs_json=$(curl -s "$remote_url") if [ $? -eq 0 ] && [ -n "$pairs_json" ]; then # curl 成功且有返回内容,尝试解析 local parsed_pairs=$(echo "$pairs_json" | python3 -c " import sys, json try: data = json.load(sys.stdin) pairs = [pair.replace('-', '/') for pair in data.get('pairlist', [])] print(' '.join(pairs) if pairs else '') except: pass ") if [ -n "$parsed_pairs" ]; then pairs=($parsed_pairs) echo "✓ 从远程成功获取到币对: ${pairs[*]}" >&2 echo "${pairs[*]}" return 0 fi fi # 如果不是最后一次尝试,等待后重试 if [ $attempt -lt $max_retries ]; then echo "⚠️ 获取失败,${retry_delay}秒后重试..." >&2 sleep $retry_delay fi attempt=$((attempt + 1)) done # 所有重试都失败 echo "✗ 远程币对列表获取失败(已重试${max_retries}次),使用空列表" >&2 echo "" return 1 } # 3. 合并并去重两个币对列表 merge_and_deduplicate_pairs() { local -a db_pairs=($1) local -a remote_pairs=($2) local -a merged=() merged=($(printf "%s\n" "${db_pairs[@]}" "${remote_pairs[@]}" | sort -u | tr '\n' ' ')) echo "合并去重后的币对列表: ${merged[*]}" >&2 echo "${merged[@]}" } # 4. 验证JSON文件格式是否有效 validate_json_file() { local json_path="$1" if ! command -v jq &>/dev/null; then echo "❌ 未安装jq工具,请先安装jq" >&2 exit 1 fi if [ ! -f "$json_path" ]; then echo "❌ JSON配置文件 $json_path 不存在" >&2 exit 1 fi if ! jq . "$json_path" >/dev/null 2>&1; then echo "❌ JSON文件格式错误:$json_path" >&2 exit 1 fi } # 5. 更新live.json中的pair_whitelist update_live_json_pair_whitelist() { local json_path="$1" local -a pairlist=($2) validate_json_file "$json_path" local json_array=$(printf '%s\n' "${pairlist[@]}" | jq -R . | jq -s .) echo "正在更新 $json_path 中的exchange.pair_whitelist..." >&2 jq --argjson pairs "$json_array" '.exchange.pair_whitelist = $pairs' "$json_path" >"$json_path.tmp" && mv "$json_path.tmp" "$json_path" if [ $? -eq 0 ]; then echo "✅ 成功更新 $json_path,新的pair_whitelist包含 ${#pairlist[@]} 个币对" >&2 else echo "❌ 更新 $json_path 失败" >&2 exit 1 fi } # 6. 停止并移除指定名称的容器 remove_existing_container() { local mode="$1" # dryrun 或 live echo "正在查找并移除所有 'freqtrade-${mode}-*' 的容器..." >&2 # 查找所有名称以 freqtrade-${mode}- 开头的容器 CONTAINERS=$(docker ps -aq --filter "name=freqtrade-${mode}-") if [ -z "$CONTAINERS" ]; then echo "✅ 未找到任何 'freqtrade-${mode}-*' 的容器" >&2 return fi echo "找到以下容器:" >&2 docker ps -a --filter "name=freqtrade-${mode}-" --format "table {{.ID}}\t{{.Names}}\t{{.Status}}" >&2 echo "正在自动移除所有符合条件的容器..." >&2 # 停止并移除容器 for CONTAINER in $CONTAINERS; do CONTAINER_NAME=$(docker ps -a --filter "id=$CONTAINER" --format "{{.Names}}") echo "正在停止容器 $CONTAINER_NAME..." >&2 docker stop "$CONTAINER" >/dev/null 2>&1 echo "正在移除容器 $CONTAINER_NAME..." >&2 if docker rm "$CONTAINER" >/dev/null 2>&1; then echo "✅ 已成功移除容器 $CONTAINER_NAME" >&2 else echo "⚠️ 移除容器 $CONTAINER_NAME 失败" >&2 fi done echo "✅ 容器清理完成" >&2 } ### 主逻辑区 ### # 检查 .env 文件 if [ ! -f ".env" ]; then echo "⚠️ 本地缺少 .env 文件,请创建并配置" >&2 exit 1 fi # 加载 .env 变量 export $(grep -v '^#' .env | xargs) # 设置默认远程币对列表URL,支持命令行参数和环境变量覆盖 DEFAULT_PAIR_REMOTE_URL="http://pairlist.xl.home/api/pairlist?mute=true&count=30" # 优先级:环境变量 > 默认值 PAIR_REMOTE_LIST_URL="${PAIR_REMOTE_LIST_URL:-$DEFAULT_PAIR_REMOTE_URL}" # 检查必要环境变量 if [ -z "$TEST_BRANCH" ] || [ -z "$DRYRUN_BRANCH" ]; then echo "⚠️ .env 文件缺少 TEST_BRANCH 或 DRYRUN_BRANCH 配置" >&2 exit 1 fi # 检查当前分支 current_branch=$(git rev-parse --abbrev-ref HEAD) if [[ "$current_branch" != "$TEST_BRANCH" && "$current_branch" != "$DRYRUN_BRANCH" ]]; then echo "⚠️ 错误:当前分支 '$current_branch' 不符合配置要求" >&2 exit 1 else echo "✅ 当前分支 '$current_branch' 符合配置要求" >&2 fi # dryrun分支清理 if [[ "$current_branch" == *"dryrun"* ]]; then echo "当前为dryrun分支,执行git reset --hard清理工作区..." >&2 git reset --hard || { echo "⚠️ git reset失败" exit 1 } echo "✅ Git工作区已清理" >&2 fi # 解析命令行参数:如果提供则覆盖默认值 while [[ $# -gt 0 ]]; do case "$1" in --pairRemoteList=*) PAIR_REMOTE_LIST_URL="${1#*=}" shift ;; --pairRemoteList) if [[ -n "$2" && "$2" != -* ]]; then PAIR_REMOTE_LIST_URL="$2" shift 2 else echo "错误:--pairRemoteList需要指定值" >&2 exit 1 fi ;; *) shift ;; esac done # 加载策略配置 STRATEGY_NAME=${STRATEGY_NAME:-TheForceV7} CONFIG_FILE=${CONFIG_FILE:-basic.json} PARAMS_NAME=$(echo "$STRATEGY_NAME" | tr '[:upper:]' '[:lower:]') echo "使用策略: $STRATEGY_NAME" >&2 echo "使用配置: $CONFIG_FILE" >&2 echo "测试分支: $TEST_BRANCH" >&2 echo "远程币对列表URL: $PAIR_REMOTE_LIST_URL" >&2 # 显示当前使用的URL ### 核心:处理币对列表 ### # 1. 获取数据库币对(使用绝对路径) db_path="/home/ubuntu/freqtrade/user_data/tradesv3.sqlite" db_pairs=$(get_open_trades_pairs "$db_path") # 2. 获取远程币对 remote_pairs=$(get_remote_pairs "$PAIR_REMOTE_LIST_URL") # 3. 合并去重 merged_pairs=$(merge_and_deduplicate_pairs "$db_pairs" "$remote_pairs") # 4. 更新配置文件 update_live_json_pair_whitelist "../config_examples/live.json" "$merged_pairs" ### 启动容器 ### # 第1步:先清理所有旧容器 echo "正在清理所有 dryrun 旧容器..." >&2 remove_existing_container "dryrun" # 第2步:生成新容器名 GIT_COMMIT_SHORT=$(git rev-parse HEAD | cut -c 1-8) TIMESTAMP=$(date +%Y%m%d%H%M%S) CONTAINER_NAME="freqtrade-dryrun-${GIT_COMMIT_SHORT}-${TIMESTAMP}" echo "准备启动容器: $CONTAINER_NAME" >&2 # 第3步:启动新容器 echo "正在启动新容器..." >&2 # 临时禁用 set -e,以便捕获 docker run 的错误 set +e docker run -d --restart=always \ --name "${CONTAINER_NAME}" \ -p 8080:8080 \ --add-host "www.okx.com:104.18.43.174" \ --add-host "api.okx.com:104.18.43.174" \ -v "$(pwd)/../user_data:/freqtrade/user_data" \ -v "$(pwd)/../config_examples:/freqtrade/config_examples" \ -v "$(pwd)/../freqtrade/templates:/freqtrade/templates" \ -v "$(pwd)/../freqtrade/exchange:/freqtrade/exchange" \ -v "$(pwd)/../freqtrade/plugins/protections:/freqtrade/freqtrade/plugins/protections" \ -v "$(pwd)/../ccxt/async_support/okx.py:/home/ftuser/.local/lib/python3.12/site-packages/ccxt/async_support/okx.py" \ -v "$(pwd)/../freqtrade/freqai/data_kitchen.py:/freqtrade/freqai/data_kitchen.py" \ -v "$(pwd)/../freqtrade/freqai/data_drawer.py:/freqtrade/freqai/data_drawer.py" \ -v "$(pwd)/../freqtrade/freqai/freqai_interface.py:/freqtrade/freqai/freqai_interface.py" \ freqtradeorg/freqtrade:develop_freqai_withredis \ trade \ --logfile /freqtrade/user_data/logs/freqtrade.log \ --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite \ --dry-run \ --freqaimodel LightGBMRegressorMultiTarget \ --config /freqtrade/config_examples/$CONFIG_FILE \ --config /freqtrade/config_examples/dryrun.json \ --strategy $STRATEGY_NAME \ --fee 0.0008 \ --strategy-path /freqtrade/templates RUN_RESULT=$? set -e # 恢复 set -e if [ $RUN_RESULT -eq 0 ]; then echo "✅ 容器 $CONTAINER_NAME 启动成功" >&2 echo "⚡ 查看日志: docker logs -f $CONTAINER_NAME" >&2 else echo "❌ 容器启动失败 (退出码: $RUN_RESULT),请查看上方错误信息" >&2 exit 1 fi