354 lines
11 KiB
Bash
Executable File
354 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
set -e # 出错立即停止
|
||
|
||
### 函数定义区 ###
|
||
|
||
# 定义 remove_existing_containers 函数
|
||
remove_existing_containers() {
|
||
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
|
||
}
|
||
|
||
# 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. 验证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
|
||
}
|
||
|
||
# 3. 从远程URL获取币对列表(带重试机制)
|
||
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
|
||
}
|
||
|
||
# 4. 合并并去重多个币对列表
|
||
merge_and_deduplicate_pairs() {
|
||
local -a db_pairs=($1)
|
||
local -a remote_pairs=($2)
|
||
local -a default_pairs=($3)
|
||
local -a merged=()
|
||
|
||
# 合并三个来源的币对并去重
|
||
merged=($(printf "%s\n" "${db_pairs[@]}" "${remote_pairs[@]}" "${default_pairs[@]}" | sort -u | tr '\n' ' '))
|
||
|
||
echo "合并去重后的币对列表 (${#merged[@]}个): ${merged[*]}" >&2
|
||
echo "${merged[@]}"
|
||
}
|
||
|
||
# 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
|
||
}
|
||
|
||
### 主逻辑区 ###
|
||
|
||
# 检查 .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
|
||
|
||
# 解析命令行参数:获取手动指定的币对列表和远程 URL
|
||
MANUAL_PAIRS=""
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--pairs=*)
|
||
MANUAL_PAIRS="${1#*=}"
|
||
shift
|
||
;;
|
||
--pairs)
|
||
if [[ -n "$2" && "$2" != -* ]]; then
|
||
MANUAL_PAIRS="$2"
|
||
shift 2
|
||
else
|
||
echo "错误:--pairs需要指定值" >&2
|
||
exit 1
|
||
fi
|
||
;;
|
||
--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
|
||
|
||
### 核心:处理币对列表 ###
|
||
|
||
# 定义写死的币对白名单
|
||
DEFAULT_PAIRS="BTC/USDT TON/USDT DOT/USDT XRP/USDT OKB/USDT SOL/USDT DOGE/USDT BNB/USDT TRUMP/USDT SUI/USDT PEPE/USDT TRB/USDT MASK/USDT UNI/USDT KAITO/USDT"
|
||
|
||
# 1. 获取数据库币对(使用绝对路径)
|
||
db_path="/home/ubuntu/freqtrade/user_data/tradesv3.sqlite"
|
||
db_pairs=$(get_open_trades_pairs "$db_path")
|
||
|
||
# 2. 获取远程币对(从环境变量读取URL)
|
||
remote_pairs=$(get_remote_pairs "$PAIR_REMOTE_LIST_URL")
|
||
|
||
# 3. 处理手动指定的币对或使用合并逻辑
|
||
if [ -n "$MANUAL_PAIRS" ]; then
|
||
# 如果手动指定了币对,直接使用手动指定的币对
|
||
final_pairs=$(echo "$MANUAL_PAIRS" | tr ',' ' ')
|
||
echo "使用手动指定的币对: $final_pairs" >&2
|
||
else
|
||
# 如果没有手动指定,则合并三个来源:数据库 + 远程 + 默认白名单
|
||
final_pairs=$(merge_and_deduplicate_pairs "$db_pairs" "$remote_pairs" "$DEFAULT_PAIRS")
|
||
echo "使用合并后的币对列表作为最终列表" >&2
|
||
fi
|
||
|
||
# 4. 更新配置文件
|
||
update_live_json_pair_whitelist "../config_examples/live.json" "$final_pairs"
|
||
|
||
# 编辑 ../freqtrade/templates/${PARAMS_NAME}.json 文件,删除包含 "protection": {}, 的行
|
||
echo "正在编辑 ../freqtrade/templates/${PARAMS_NAME}.json 文件,删除 'protection': {} 行..." >&2
|
||
sed -i '/"protection": {},/d' "../freqtrade/templates/${PARAMS_NAME}.json"
|
||
|
||
if [ $? -eq 0 ]; then
|
||
echo "✅ 成功编辑 ../freqtrade/templates/${PARAMS_NAME}.json 文件" >&2
|
||
else
|
||
echo "❌ 编辑 ../freqtrade/templates/${PARAMS_NAME}.json 文件失败" >&2
|
||
exit 1
|
||
fi
|
||
|
||
### 启动容器 ###
|
||
|
||
# 第1步:先清理所有旧容器
|
||
echo "正在清理所有 live 旧容器..." >&2
|
||
remove_existing_containers "live"
|
||
|
||
# 第2步:生成新容器名
|
||
GIT_COMMIT_SHORT=$(git rev-parse HEAD | cut -c 1-8)
|
||
TIMESTAMP=$(date +%Y%m%d%H%M%S)
|
||
CONTAINER_NAME="freqtrade-live-${GIT_COMMIT_SHORT}-${TIMESTAMP}"
|
||
echo "准备启动容器: $CONTAINER_NAME" >&2
|
||
|
||
# 清理临时文件放置交叉感染
|
||
cd ../
|
||
source .venv/bin/activate
|
||
rm -rf user_data/models/*
|
||
rm -rf ./freqtrade/user_data/data/backtest_results/*
|
||
rm -fr ./user_data/dryrun_results/*
|
||
cd -
|
||
|
||
# 第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 \
|
||
--freqaimodel LightGBMRegressorMultiTarget \
|
||
--fee 0.0008 \
|
||
--config /freqtrade/config_examples/$CONFIG_FILE \
|
||
--config /freqtrade/templates/${PARAMS_NAME}.json \
|
||
--config /freqtrade/config_examples/live.json \
|
||
--strategy $STRATEGY_NAME \
|
||
--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
|
||
|