#!/bin/bash # 环境检查 - 只支持Linux if [[ "$(uname)" != "Linux" ]]; then echo "错误:此脚本仅支持Linux环境,不支持macOS或其他操作系统。" echo "请在Linux系统上运行此脚本。" exit 1 fi # Function to extract the value of a parameter get_param_value() { local param="$1" shift local args=($@) local i=0 while [ $i -lt ${#args[@]} ]; do case "${args[$i]}" in $param=*) echo "${args[$i]#*=}" return 0 ;; $param) # Check if the next argument exists and does not start with a dash if [ $((i + 1)) -lt ${#args[@]} ] && [[ "${args[$((i + 1))]}" != -* ]]; then echo "${args[$((i + 1))]}" return 0 fi # 如果参数存在但没有值,返回特殊标识"PARAM_WITHOUT_VALUE" echo "PARAM_WITHOUT_VALUE" return 0 ;; esac i=$((i + 1)) done # 如果参数不存在,返回空字符串 echo "" return 0 } # 检查 .env 文件 if [ ! -f ".env" ]; then echo "⚠️ 本地缺少 .env 文件,请创建并配置。示例内容如下:" echo "" echo "STRATEGY_NAME=TheForceV7" echo "CONFIG_FILE=basic.json" echo "TEST_BRANCH=theforce-noai-test" echo "DRYRUN_BRANCH=theforce-noai-dryrun" echo "" exit 1 fi # 加载 .env 文件中的变量 if [ -f .env ]; then set -a source .env set +a fi # 设置默认值 STRATEGY_NAME=${STRATEGY_NAME:-TheForceV7} CONFIG_FILE=${CONFIG_FILE:-basic.json} TEST_BRANCH=${TEST_BRANCH:-develop} # --- 新增:解析多个 --spaces 参数 --- # 提取所有 --spaces 后的值(支持多个 --spaces 参数) SPACES_VALUES=() for arg in "$@"; do if [[ "$prev_arg" == "--spaces" ]] || [[ "$prev_arg" == "--space" ]]; then if [[ ! "$arg" =~ ^-- ]]; then SPACES_VALUES+=("$arg") fi fi prev_arg="$arg" done # 如果有多个 --spaces 值,合并为空格分隔的字符串 if [ ${#SPACES_VALUES[@]} -gt 0 ]; then SPACES="${SPACES_VALUES[@]}" else # 使用get_param_value作为备选方案(兼容旧格式) SPACES_ARG=$(get_param_value "--spaces" "$@") if [ -z "$SPACES_ARG" ]; then SPACES_ARG=$(get_param_value "--space" "$@") if [ -z "$SPACES_ARG" ]; then SPACES="buy" else SPACES="$SPACES_ARG" fi else SPACES="$SPACES_ARG" fi fi echo "Using hyperopt spaces: $SPACES" EPOCHS_ARG=$(get_param_value "--epochs" "$@") if [ -z "$EPOCHS_ARG" ]; then EPOCHS=100 else EPOCHS="$EPOCHS_ARG" fi echo "Using epochs: $EPOCHS" RANDOM_STATE_ARG=$(get_param_value "--random-state" "$@") if [ -z "$RANDOM_STATE_ARG" ]; then RANDOM_STATE=19 else RANDOM_STATE="$RANDOM_STATE_ARG" fi echo "Using random-state: $RANDOM_STATE" # --- 新增:解析 --jobs 或 -j 参数 --- JOBS_ARG=$(get_param_value "--jobs" "$@") if [ -z "$JOBS_ARG" ]; then JOBS_ARG=$(get_param_value "-j" "$@") if [ -z "$JOBS_ARG" ]; then JOBS=4 else JOBS="$JOBS_ARG" fi else JOBS="$JOBS_ARG" fi echo "Using jobs: $JOBS" # Initialize variables START_DATE_RAW="" END_DATE_RAW="" PAIRS_ARG="" PAIR_REMOTE_LIST_URL="" STRATEGY_ARG="" CONFIG_ARG="" # 首先尝试从位置参数获取日期(无论是否有命名参数) # 保存原始参数列表 ORIG_ARGS=("$@") # 检查前两个参数是否是日期格式(8位数字) if [ $# -gt 0 ] && [[ "$1" =~ ^[0-9]{8}$ ]]; then START_DATE_RAW="$1" if [ $# -gt 1 ] && [[ "$2" =~ ^[0-9]{8}$ ]]; then END_DATE_RAW="$2" # 移除已处理的日期参数 shift 2 fi fi # 处理所有参数,包括命名参数 while [ $# -gt 0 ]; do case "$1" in --start-date) if [ $# -gt 1 ] && [[ "$2" != -* ]]; then START_DATE_RAW="$2" shift 2 else shift fi ;; --end-date) if [ $# -gt 1 ] && [[ "$2" != -* ]]; then END_DATE_RAW="$2" shift 2 else shift fi ;; --pairs) if [ $# -gt 1 ] && [[ "$2" != -* ]]; then PAIRS_ARG="$2" shift 2 else shift fi ;; --pairRemoteList) if [ $# -gt 1 ] && [[ "$2" != -* ]]; then PAIR_REMOTE_LIST_URL="$2" shift 2 else shift fi ;; --strategy | -t) if [ $# -gt 1 ] && [[ "$2" != -* ]]; then STRATEGY_ARG="$2" shift 2 else shift fi ;; --config) if [ $# -gt 1 ] && [[ "$2" != -* ]]; then CONFIG_ARG="$2" shift 2 else shift fi ;; --spaces | --space | --epochs | --random-state | --jobs | -j) # 这些参数已经在前面处理过了,跳过参数及其值 shift if [[ $# -gt 0 ]] && [[ ! "$1" =~ ^-- ]]; then shift fi ;; *) shift ;; esac done # 恢复原始参数列表以便后续处理 set -- "${ORIG_ARGS[@]}" # 参数顺序说明 # 支持的调用方式: # 1. 位置参数: $0 [开始日期] [结束日期] [其他参数] # 2. 命名参数: $0 --start-date=YYYYMMDD --end-date=YYYYMMDD [其他参数] # 3. 混合模式: $0 [开始日期] [结束日期] --space sell [其他参数] # 处理策略名和交易对的位置参数(如果有的话) # 这里需要重新分析参数,因为前两个位置参数可能是日期 POSITIONAL_ARGS=() while [ $# -gt 0 ]; do case "$1" in --*) # 跳过命名参数 shift if [[ "$1" != -* && $# -gt 0 ]]; then shift fi ;; *) POSITIONAL_ARGS+=("$1") shift ;; esac done # 现在POSITIONAL_ARGS中只包含位置参数(非命名参数) # 前两个参数可能是日期,但我们已经在上面处理了 # 检查是否有剩余的位置参数用于策略名和交易对 if [ ${#POSITIONAL_ARGS[@]} -gt 2 ]; then # 第3个位置参数可能是策略名 if [[ "${POSITIONAL_ARGS[2]}" != */* && ! "${POSITIONAL_ARGS[2]}" =~ ^[0-9]{8}$ ]]; then STRATEGY_ARG="${POSITIONAL_ARGS[2]}" echo "检测到策略参数: $STRATEGY_ARG" fi fi if [ ${#POSITIONAL_ARGS[@]} -gt 3 ]; then # 第4个位置参数可能是交易对或URL FOURTH_PARAM="${POSITIONAL_ARGS[3]}" if [[ "$FOURTH_PARAM" =~ ^[0-9]{8}$ ]]; then echo "错误:第4个参数应该是交易对或URL,而不是日期" exit 1 elif [[ "$FOURTH_PARAM" == http://* ]] || [[ "$FOURTH_PARAM" == https://* ]]; then PAIR_REMOTE_LIST_URL="$FOURTH_PARAM" echo "检测到远程交易对列表URL: $PAIR_REMOTE_LIST_URL" elif [[ "$FOURTH_PARAM" == */* ]]; then PAIRS_ARG="$FOURTH_PARAM" echo "检测到交易对参数: $PAIRS_ARG" fi fi # Set default values if parameters not provided # Linux 专用的date命令 (UTC时间) echo "[DEBUG] 开始日期原始值: '$START_DATE_RAW'" echo "[DEBUG] 结束日期原始值: '$END_DATE_RAW'" if [ -z "$START_DATE_RAW" ]; then START_DATE_RAW=$(date -u +"%Y-%m-%d %H:%M:%S") echo "[DEBUG] 使用默认开始日期: $START_DATE_RAW" fi if [ -z "$END_DATE_RAW" ]; then END_DATE_RAW=$(date -u +"%Y-%m-%d %H:%M:%S") echo "[DEBUG] 使用默认结束日期: $END_DATE_RAW" fi # Parse dates - Linux 专用实现 # 支持的格式: # 1. 20250501 (YYYYMMDD) # 2. 2025-10-01 13:48 (YYYY-MM-DD HH:MM) # 3. 2025-10-01 13:48:30 (YYYY-MM-DD HH:MM:SS) # 解析起始日期 if [[ "$START_DATE_RAW" =~ ^[0-9]{8}$ ]]; then # 处理YYYYMMDD格式 YEAR=${START_DATE_RAW:0:4} MONTH=${START_DATE_RAW:4:2} DAY=${START_DATE_RAW:6:2} # Linux/GNU date 命令语法 START_DATE=$(date -u -d "$YEAR-$MONTH-$DAY" +"%s") echo "[DEBUG] 解析起始日期: $START_DATE_RAW -> $YEAR-$MONTH-$DAY -> 时间戳: $START_DATE" if [ -z "$START_DATE" ]; then echo "错误:无效的起始日期格式 $START_DATE_RAW,请使用 YYYYMMDD 或 YYYY-MM-DD HH:MM" exit 1 fi elif [[ "$START_DATE_RAW" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2} ]]; then # 处理YYYY-MM-DD HH:MM 或 YYYY-MM-DD HH:MM:SS格式 START_DATE=$(date -u -d "$START_DATE_RAW" +"%s") echo "[DEBUG] 解析起始日期: $START_DATE_RAW -> 时间戳: $START_DATE" if [ -z "$START_DATE" ]; then echo "错误:无效的起始日期格式 $START_DATE_RAW,请使用 YYYYMMDD 或 YYYY-MM-DD HH:MM" exit 1 fi else echo "错误:不支持的起始日期格式 $START_DATE_RAW,请使用 YYYYMMDD 或 YYYY-MM-DD HH:MM" exit 1 fi # 解析结束日期 if [[ "$END_DATE_RAW" =~ ^[0-9]{8}$ ]]; then # 处理YYYYMMDD格式 YEAR=${END_DATE_RAW:0:4} MONTH=${END_DATE_RAW:4:2} DAY=${END_DATE_RAW:6:2} # Linux/GNU date 命令语法 END_DATE=$(date -u -d "$YEAR-$MONTH-$DAY" +"%s") echo "[DEBUG] 解析结束日期: $END_DATE_RAW -> $YEAR-$MONTH-$DAY -> 时间戳: $END_DATE" if [ -z "$END_DATE" ]; then echo "错误:无效的结束日期格式 $END_DATE_RAW,请使用 YYYYMMDD 或 YYYY-MM-DD HH:MM" exit 1 fi elif [[ "$END_DATE_RAW" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2} ]]; then # 处理YYYY-MM-DD HH:MM 或 YYYY-MM-DD HH:MM:SS格式 END_DATE=$(date -u -d "$END_DATE_RAW" +"%s") echo "[DEBUG] 解析结束日期: $END_DATE_RAW -> 时间戳: $END_DATE" if [ -z "$END_DATE" ]; then echo "错误:无效的结束日期格式 $END_DATE_RAW,请使用 YYYYMMDD 或 YYYY-MM-DD HH:MM" exit 1 fi else echo "错误:不支持的结束日期格式 $END_DATE_RAW,请使用 YYYYMMDD 或 YYYY-MM-DD HH:MM" exit 1 fi # 处理交易对参数:优先级为 --pairRemoteList > --pairs > 默认值 if [ -n "$PAIR_REMOTE_LIST_URL" ]; then # 从远程API获取交易对列表 echo "Fetching pairs from remote URL: $PAIR_REMOTE_LIST_URL" pairs_json=$(curl -s "$PAIR_REMOTE_LIST_URL") # 检查API响应是否成功 if [[ $? -ne 0 || -z "$pairs_json" ]]; then echo "Error: Failed to fetch pairs from remote URL, using --pairs parameter or default" if [ -n "$PAIRS_ARG" ]; then PAIRS_FLAG="--pairs $PAIRS_ARG" echo "Using pairs from --pairs parameter: $PAIRS_ARG" else # 使用默认的交易对列表 DEFAULT_PAIRS="BTC/USDT TON/USDT DOT/USDT XRP/USDT OKB/USDT SOL/USDT DOGE/USDT RIO/USDT LTC/USDT SUI/USDT PEPE/USDT TRB/USDT FIL/USDT UNI/USDT KAITO/USDT" PAIRS_FLAG="--pairs $DEFAULT_PAIRS" echo "Using default pairs: $DEFAULT_PAIRS" fi else # 解析JSON并提取交易对,将连字符替换为斜杠 remote_pairs=$(echo "$pairs_json" | python3 -c " import sys, json data = json.load(sys.stdin) pairs = [pair.replace('-', '/') for pair in data.get('pairlist', [])] print(' '.join(pairs) if pairs else '')") # 如果解析成功且有交易对 if [[ -n "$remote_pairs" ]]; then PAIRS_FLAG="--pairs $remote_pairs" echo "Successfully fetched $(echo "$remote_pairs" | wc -w) pairs from remote URL" echo "Pairs: $remote_pairs" else echo "Error: Failed to parse or empty pairlist from remote URL, using --pairs parameter or default" if [ -n "$PAIRS_ARG" ]; then PAIRS_FLAG="--pairs $PAIRS_ARG" echo "Using pairs from --pairs parameter: $PAIRS_ARG" else # 使用默认的交易对列表 DEFAULT_PAIRS="BTC/USDT TON/USDT DOT/USDT XRP/USDT OKB/USDT SOL/USDT DOGE/USDT RIO/USDT LTC/USDT SUI/USDT PEPE/USDT TRB/USDT FIL/USDT UNI/USDT KAITO/USDT" PAIRS_FLAG="--pairs $DEFAULT_PAIRS" echo "Using default pairs: $DEFAULT_PAIRS" fi fi fi elif [ -n "$PAIRS_ARG" ]; then # 使用 --pairs 参数提供的交易对 PAIRS_FLAG="--pairs $PAIRS_ARG" echo "Using pairs from --pairs parameter: $PAIRS_ARG" else # 没有提供任何交易对参数,使用默认值 DEFAULT_PAIRS="BTC/USDT TON/USDT DOT/USDT XRP/USDT OKB/USDT SOL/USDT DOGE/USDT RIO/USDT LTC/USDT SUI/USDT PEPE/USDT TRB/USDT FIL/USDT UNI/USDT KAITO/USDT" PAIRS_FLAG="--pairs $DEFAULT_PAIRS" echo "No pairs parameter provided, using default pairs: $DEFAULT_PAIRS" fi # 如果命令行提供了有效的策略参数,覆盖.env文件设置 if [ -n "$STRATEGY_ARG" ] && [ "$STRATEGY_ARG" != "PARAM_WITHOUT_VALUE" ]; then # 策略类名保持不变(如MyStrategy),但文件名使用小写 STRATEGY_CLASS_NAME="$STRATEGY_ARG" STRATEGY_FILE_NAME="$(echo "$STRATEGY_ARG" | tr '[:upper:]' '[:lower:]').py" STRATEGY_NAME="$STRATEGY_FILE_NAME" echo "Overriding strategy with command line parameter: $STRATEGY_CLASS_NAME" echo "Strategy class name: $STRATEGY_CLASS_NAME" echo "Strategy file name: $STRATEGY_FILE_NAME" # 自动匹配策略对应的配置文件(使用小写) STRATEGY_CONFIG_LOWER="$(echo "$STRATEGY_ARG" | tr '[:upper:]' '[:lower:]').json" echo "Checking auto-matched config file: freqtrade/templates/$STRATEGY_CONFIG_LOWER" # 检查小写配置文件 if [ -f "../freqtrade/templates/$STRATEGY_CONFIG_LOWER" ]; then CONFIG_FILE="$STRATEGY_CONFIG_LOWER" echo "Auto-matched config file for strategy: $CONFIG_FILE" else echo "Warning: Auto-matched config file '$STRATEGY_CONFIG_LOWER' not found in templates directory" echo "Available config files in templates directory:" ls ../freqtrade/templates/*.json 2>/dev/null | sed 's|.*/||' || echo "No JSON config files found" echo "Using current config: $CONFIG_FILE" fi fi # 如果命令行提供了配置参数,覆盖.env文件设置 if [ -n "$CONFIG_ARG" ]; then CONFIG_FILE="$CONFIG_ARG" echo "Overriding config with command line parameter: $CONFIG_FILE" fi # 检查指定的配置文件是否存在,如果不存在则使用默认配置 echo "Checking config file: freqtrade/templates/$CONFIG_FILE" if [ ! -f "../freqtrade/templates/$CONFIG_FILE" ]; then echo "Warning: Config file freqtrade/templates/$CONFIG_FILE not found, using default configuration" CONFIG_FILE="basic.json" else echo "Config file found: freqtrade/templates/$CONFIG_FILE" fi # 最后确认STRATEGY_NAME有值 if [ -z "$STRATEGY_NAME" ]; then echo "Warning: STRATEGY_NAME is empty, using default TheForceV7" STRATEGY_NAME="TheForceV7" fi # 确保STRATEGY_CLASS_NAME有值 if [ -z "$STRATEGY_CLASS_NAME" ]; then echo "Warning: STRATEGY_CLASS_NAME is empty, setting to STRATEGY_NAME value" STRATEGY_CLASS_NAME="$STRATEGY_NAME" fi # 在参数处理完成后输出最终使用的值 # 显示完整使用帮助 echo "正确用法示例:" echo "位置参数模式:" echo " $0 20230901 20231001 MartinGale BTC/USDT" echo "命名参数模式:" echo " $0 --start-date=20230901 --end-date=20231001 -t MartinGale --spaces \"buy sell\" --epochs 200 --random-state 42" echo "Using strategy: $STRATEGY_NAME" echo "Using config: $CONFIG_FILE" echo "Using testBranch: $TEST_BRANCH" echo "Using hyperopt spaces: $SPACES" echo "Using epochs: $EPOCHS" echo "Using random-state: $RANDOM_STATE" # 直接在当前目录工作,不使用不存在的.venv cd ../ # 安全地删除文件,只删除存在的文件 find user_data/models -type f -delete 2>/dev/null touch user_data/models/.gitkeep find ./freqtrade/user_data/data/backtest_results -type f -delete 2>/dev/null touch ./freqtrade/user_data/data/backtest_results/.gitkeep find ./user_data/dryrun_results -type f -delete 2>/dev/null touch ./user_data/dryrun_results/.gitkeep find result -type f -delete 2>/dev/null touch result/.gitkeep # 🔧 新增:清空 hyperopt_results 目录中的所有结果文件(执行 hyperopt 前清理旧结果) echo "清空旧的 hyperopt 结果文件..." find user_data/hyperopt_results -type f \( -name "*.fthypt" -o -name "*.pkl" -o -name ".last_result.json" \) -delete 2>/dev/null echo "✓ hyperopt_results 目录已清空" PARAMS_NAME=freqaiprimer echo "docker-compose run --rm freqtrade hyperopt $PAIRS_FLAG \ --logfile /freqtrade/user_data/logs/freqtrade.log \ --freqaimodel LightGBMRegressorMultiTarget \ --strategy $STRATEGY_CLASS_NAME \ --config /freqtrade/config_examples/$CONFIG_FILE \ --config /freqtrade/templates/${PARAMS_NAME}.json \ --enable-protections \ --strategy-path /freqtrade/templates \ --timerange ${START_DATE}-${END_DATE} \ --random-state $RANDOM_STATE \ --epochs $EPOCHS \ -j $JOBS \ --hyperopt-loss CalmarHyperOptLoss \ --spaces $SPACES \ --fee 0.001" docker-compose run --rm freqtrade hyperopt $PAIRS_FLAG \ --logfile /freqtrade/user_data/logs/freqtrade.log \ --freqaimodel LightGBMRegressorMultiTarget \ --strategy $STRATEGY_CLASS_NAME \ --config /freqtrade/config_examples/$CONFIG_FILE \ --config /freqtrade/templates/${PARAMS_NAME}.json \ --enable-protections \ --strategy-path /freqtrade/templates \ --timerange ${START_DATE}-${END_DATE} \ --random-state $RANDOM_STATE \ --epochs $EPOCHS \ -j $JOBS \ --hyperopt-loss CalmarHyperOptLoss \ --spaces $SPACES \ --fee 0.001