388 lines
11 KiB
Bash
Executable File
388 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# 通用工具脚本 v1.0.0
|
||
# 功能:提供参数处理、配置管理、分级日志等通用功能
|
||
|
||
# 全局变量
|
||
SCRIPT_NAME="$(basename "$0")"
|
||
SCRIPT_VERSION="v1.0.0"
|
||
CONFIG_LOADED=false
|
||
|
||
# 默认配置
|
||
DEFAULT_CONFIG_FILE="/Users/zhangkun/myTestFreqAI/freqtrade/templates/freqaiprimer.json"
|
||
DEFAULT_LOG_LEVEL="info"
|
||
|
||
# 配置变量(可通过参数或.env文件覆盖)
|
||
CONFIG_FILE="${CONFIG_FILE:-$DEFAULT_CONFIG_FILE}"
|
||
LOG_LEVEL="${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}"
|
||
VERBOSE=false
|
||
DRY_RUN=false
|
||
|
||
# 获取当前日期时间
|
||
get_timestamp() {
|
||
date "+%Y-%m-%d %H:%M:%S"
|
||
}
|
||
|
||
# 获取日志级别对应的数值
|
||
# 0: debug, 1: info, 2: warn, 3: error
|
||
log_level_to_num() {
|
||
case "$1" in
|
||
debug) echo 0 ;;
|
||
info) echo 1 ;;
|
||
warn) echo 2 ;;
|
||
error) echo 3 ;;
|
||
*) echo 1 ;;
|
||
esac
|
||
}
|
||
|
||
# 获取日志级别对应的颜色
|
||
log_level_to_color() {
|
||
case "$1" in
|
||
debug) echo "\033[0;36m" ;;
|
||
info) echo "\033[0;32m" ;;
|
||
warn) echo "\033[0;33m" ;;
|
||
error) echo "\033[0;31m" ;;
|
||
*) echo "\033[0m" ;;
|
||
esac
|
||
}
|
||
|
||
# 日志函数
|
||
log() {
|
||
local level=$1
|
||
local message=$2
|
||
local current_level_num=$(log_level_to_num "$LOG_LEVEL")
|
||
local message_level_num=$(log_level_to_num "$level")
|
||
|
||
# 只输出大于等于当前日志级别的消息
|
||
if [ "$message_level_num" -ge "$current_level_num" ]; then
|
||
local color=$(log_level_to_color "$level")
|
||
local reset="\033[0m"
|
||
local timestamp=$(get_timestamp)
|
||
echo -e "$color[$timestamp] [$level] $message$reset"
|
||
fi
|
||
}
|
||
|
||
# 显示错误消息并退出
|
||
error_exit() {
|
||
log "error" "$1"
|
||
exit 1
|
||
}
|
||
|
||
# 加载环境变量
|
||
load_env() {
|
||
if [ -f ".env" ]; then
|
||
log "info" "加载环境变量文件: .env"
|
||
# 安全加载环境变量
|
||
set -a
|
||
source .env
|
||
set +a
|
||
|
||
# 检查是否有.env中设置的CONFIG_FILE
|
||
if [ ! -z "$CONFIG_FILE" ] && [ "$CONFIG_FILE" != "$DEFAULT_CONFIG_FILE" ]; then
|
||
# 如果是相对路径,则将其转换为绝对路径(相对于脚本所在目录)
|
||
if [[ ! "$CONFIG_FILE" = /* ]]; then
|
||
# 检查是否需要添加../freqtrade/templates/前缀
|
||
if [ ! -f "$CONFIG_FILE" ] && [ "$CONFIG_FILE" = "freqaiprimer.json" ]; then
|
||
CONFIG_FILE="/Users/zhangkun/myTestFreqAI/freqtrade/templates/freqaiprimer.json"
|
||
log "debug" "自动修正配置文件路径为: $CONFIG_FILE"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# 确保配置变量使用默认值(如果环境变量未设置)
|
||
LOG_LEVEL="${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}"
|
||
return 0
|
||
else
|
||
log "warn" "环境变量文件 .env 不存在"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# 函数: 获取配置文件的绝对路径
|
||
get_absolute_path() {
|
||
local path=$1
|
||
# 如果是相对路径,则转换为绝对路径
|
||
if [[ ! "$path" = /* ]]; then
|
||
# 获取脚本所在目录的绝对路径
|
||
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
echo "$script_dir/$path"
|
||
else
|
||
echo "$path"
|
||
fi
|
||
}
|
||
|
||
# 函数: 加载配置文件
|
||
load_config() {
|
||
local config_file=$1
|
||
|
||
# 直接使用传入的路径(已经是绝对路径)
|
||
if [ ! -f "$config_file" ]; then
|
||
log "warn" "配置文件 $config_file 不存在,将使用默认配置"
|
||
return 1
|
||
fi
|
||
|
||
log "info" "加载配置文件: $config_file"
|
||
|
||
# 检查是否有 jq 工具可用
|
||
if command -v jq &> /dev/null; then
|
||
# 使用 jq 解析 JSON 配置文件
|
||
CONFIG_LOADED=true
|
||
log "debug" "使用 jq 解析配置文件"
|
||
# 这里可以根据需要添加具体的配置解析逻辑
|
||
else
|
||
log "warn" "未找到 jq 工具,无法解析 JSON 配置文件"
|
||
fi
|
||
}
|
||
|
||
# 显示帮助信息
|
||
show_help() {
|
||
cat << EOF
|
||
$SCRIPT_NAME $SCRIPT_VERSION
|
||
|
||
用法: $SCRIPT_NAME [选项]
|
||
|
||
选项:
|
||
-h, --help 显示此帮助信息
|
||
-v, --version 显示脚本版本
|
||
-c, --config <文件路径> 指定配置文件路径(默认: $DEFAULT_CONFIG_FILE)
|
||
-l, --log-level <级别> 设置日志级别 (debug, info, warn, error),默认: info
|
||
--verbose 启用详细输出模式
|
||
--dry-run 试运行模式,不执行实际操作
|
||
--custom-param <值> 自定义参数
|
||
|
||
示例:
|
||
$SCRIPT_NAME --help
|
||
$SCRIPT_NAME --config custom.json --log-level debug
|
||
$SCRIPT_NAME --dry-run --verbose
|
||
EOF
|
||
}
|
||
|
||
# 解析命令行参数
|
||
parse_args() {
|
||
while [[ $# -gt 0 ]]; do
|
||
case $1 in
|
||
-h|--help)
|
||
show_help
|
||
exit 0
|
||
;;
|
||
-v|--version)
|
||
echo "$SCRIPT_NAME $SCRIPT_VERSION"
|
||
exit 0
|
||
;;
|
||
-c|--config)
|
||
CONFIG_FILE="$2"
|
||
shift 2
|
||
;;
|
||
-l|--log-level)
|
||
LOG_LEVEL="$2"
|
||
shift 2
|
||
;;
|
||
--verbose)
|
||
VERBOSE=true
|
||
shift
|
||
;;
|
||
--dry-run)
|
||
DRY_RUN=true
|
||
shift
|
||
;;
|
||
--custom-param)
|
||
CUSTOM_PARAM="$2"
|
||
shift 2
|
||
;;
|
||
*)
|
||
error_exit "未知参数: $1"
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# 函数:将JSON配置覆盖到Python文件中的hyperopt参数
|
||
override_hyperopt_params() {
|
||
local json_file=$1
|
||
local py_file=$2
|
||
local dry_run=$3
|
||
|
||
log "info" "开始从 $json_file 覆盖参数到 $py_file"
|
||
|
||
# 检查jq工具是否可用
|
||
if ! command -v jq &> /dev/null; then
|
||
log "error" "未找到 jq 工具,无法解析 JSON 配置文件"
|
||
return 1
|
||
fi
|
||
|
||
# 检查Python文件是否存在
|
||
if [ ! -f "$py_file" ]; then
|
||
log "error" "Python 文件 $py_file 不存在"
|
||
return 1
|
||
fi
|
||
|
||
# 获取脚本所在目录
|
||
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
||
# 确保路径正确
|
||
if [[ ! "$json_file" = /* ]]; then
|
||
json_file="$script_dir/$json_file"
|
||
fi
|
||
|
||
if [[ ! "$py_file" = /* ]]; then
|
||
py_file="$script_dir/$py_file"
|
||
fi
|
||
|
||
log "debug" "使用JSON文件: $json_file"
|
||
log "debug" "目标Python文件: $py_file"
|
||
|
||
# 创建临时文件用于记录修改
|
||
local changes_log=$(mktemp)
|
||
|
||
# 解析所有一级参数类别(buy, sell, stoploss等)
|
||
local param_categories=$(jq -r '.params | keys[]' "$json_file")
|
||
|
||
for category in $param_categories; do
|
||
log "debug" "处理参数类别: $category"
|
||
|
||
# 获取该类别下的所有参数
|
||
local params=$(jq -r ".params.$category | keys[]" "$json_file")
|
||
|
||
for param in $params; do
|
||
# 获取参数值
|
||
local value=$(jq -r ".params.$category.$param" "$json_file")
|
||
|
||
log "debug" " 原始值: $value"
|
||
|
||
# 检查value是否为空或null
|
||
if [ -z "$value" ] || [ "$value" = "null" ]; then
|
||
log "error" "参数 $param 的默认值为空或未定义,无法进行参数覆盖,请检查 $json_file 中的配置"
|
||
return 1
|
||
fi
|
||
|
||
# 特殊处理字符串值,添加引号
|
||
if [[ "$value" =~ ^[a-zA-Z]+$ ]] && [[ ! "$value" == "true" ]] && [[ ! "$value" == "false" ]]; then
|
||
value="\"$value\""
|
||
fi
|
||
|
||
log "debug" " 处理后的值: $value"
|
||
log "debug" " 参数: $param = $value"
|
||
|
||
# 检查参数是否存在
|
||
if grep -q "$param.*default=" "$py_file"; then
|
||
log "debug" " 找到参数: $param"
|
||
# 显示原始行
|
||
local original_line=$(grep "$param.*default=" "$py_file" | head -1)
|
||
log "debug" " 原始行: $original_line"
|
||
else
|
||
log "warn" " 参数 $param 在 $py_file 中未找到"
|
||
fi
|
||
|
||
# 构建sed命令来替换Python文件中的默认值
|
||
if [ "$dry_run" = true ]; then
|
||
# 试运行模式:显示将要进行的修改
|
||
log "info" "[DRY RUN] 将替换参数 $param 的默认值为 $value"
|
||
# 检查该参数是否存在于文件中
|
||
if grep -q "$param.*default=" "$py_file"; then
|
||
echo "$param: $value" >> "$changes_log"
|
||
else
|
||
log "warn" " 参数 $param 在 $py_file 中未找到"
|
||
fi
|
||
else
|
||
# 实际运行模式:执行替换
|
||
if grep -q "$param.*default=" "$py_file"; then
|
||
# 使用sed替换默认值,保留其他属性(完全兼容macOS)
|
||
# 创建临时文件保存替换结果
|
||
local temp_file=$(mktemp)
|
||
log "debug" " sed替换模式: s/\\($param.*default=\\)[^,}]*/\\1$value/"
|
||
log "debug" " 执行 sed 替换..."
|
||
sed "s/\\($param.*default=\\)[^,}]*/\\1$value/" "$py_file" > "$temp_file"
|
||
log "debug" " 替换结果保存到临时文件: $temp_file"
|
||
# 检查替换是否成功
|
||
if grep -q "$param.*default=$value" "$temp_file"; then
|
||
log "debug" " 替换成功,更新文件..."
|
||
else
|
||
log "warn" " 替换未成功或值未与预旧符合"
|
||
fi
|
||
# 将临时文件内容复制回原文件
|
||
mv "$temp_file" "$py_file"
|
||
log "info" " 已更新参数 $param 的默认值为 $value"
|
||
echo "$param: $value" >> "$changes_log"
|
||
else
|
||
log "warn" " 参数 $param 在 $py_file 中未找到"
|
||
fi
|
||
fi
|
||
done
|
||
done
|
||
|
||
# 显示修改摘要
|
||
local change_count=$(wc -l < "$changes_log")
|
||
log "info" "参数更新完成,共修改 $change_count 个参数"
|
||
|
||
if [ "$VERBOSE" = true ] && [ -s "$changes_log" ]; then
|
||
log "info" "修改的参数列表:"
|
||
cat "$changes_log" | while read line; do
|
||
log "info" " $line"
|
||
done
|
||
fi
|
||
|
||
# 清理临时文件
|
||
rm -f "$changes_log"
|
||
|
||
return 0
|
||
}
|
||
|
||
# 核心任务执行函数
|
||
execute_core_task() {
|
||
log "info" "开始执行核心任务"
|
||
|
||
# 默认的JSON和Python文件路径
|
||
local default_json_file="../freqtrade/templates/freqaiprimer.json"
|
||
local default_py_file="../freqtrade/templates/freqaiprimer.py"
|
||
|
||
# 如果通过--custom-param指定了特殊操作,处理它
|
||
if [ "$CUSTOM_PARAM" = "update_params" ]; then
|
||
log "info" "执行参数更新操作"
|
||
override_hyperopt_params "$default_json_file" "$default_py_file" "$DRY_RUN"
|
||
else
|
||
# 常规模式:从配置文件读取参数
|
||
if [ "$DRY_RUN" = true ]; then
|
||
log "info" "[DRY RUN] 模拟执行核心任务"
|
||
# 试运行时也执行参数更新的试运行
|
||
override_hyperopt_params "$default_json_file" "$default_py_file" "true"
|
||
else
|
||
log "info" "执行参数更新任务"
|
||
override_hyperopt_params "$default_json_file" "$default_py_file" "false"
|
||
fi
|
||
fi
|
||
|
||
# 显示当前配置信息(如果启用了详细模式)
|
||
if [ "$VERBOSE" = true ]; then
|
||
log "debug" "当前配置信息:"
|
||
log "debug" " 配置文件: $CONFIG_FILE"
|
||
log "debug" " 日志级别: $LOG_LEVEL"
|
||
log "debug" " 详细模式: $VERBOSE"
|
||
log "debug" " 试运行模式: $DRY_RUN"
|
||
if [ -n "$CUSTOM_PARAM" ]; then
|
||
log "debug" " 自定义参数: $CUSTOM_PARAM"
|
||
fi
|
||
fi
|
||
|
||
log "info" "核心任务执行完成"
|
||
}
|
||
|
||
# 主函数
|
||
main() {
|
||
# 加载环境变量
|
||
load_env
|
||
|
||
# 解析命令行参数(命令行参数优先级高于环境变量)
|
||
parse_args "$@"
|
||
|
||
# 加载配置文件
|
||
load_config "$CONFIG_FILE"
|
||
|
||
# 执行核心任务
|
||
execute_core_task
|
||
|
||
log "info" "脚本执行完成"
|
||
}
|
||
|
||
# 入口点
|
||
main "$@"
|