#!/bin/bash # 定义结果文件路径 RESULT_FILE="../result/backtest_trades.json" # 使用绝对路径以确保正确访问 # RESULT_FILE="/Users/zhangkun/myTestFreqAI/result/test_trades_with_entries.json" # RESULT_FILE="/Users/zhangkun/myTestFreqAI/test_trades.json" # 绝对路径新测试文件 # 检查文件是否存在 if [ ! -f "$RESULT_FILE" ]; then echo "错误:找不到文件 $RESULT_FILE" echo "请确保运行了backtest并生成了交易数据文件" exit 1 fi # 检查jq是否安装 if ! command -v jq &> /dev/null; then echo "错误:jq命令未找到,请安装jq以处理JSON数据" echo "Ubuntu/Debian: sudo apt-get install jq" echo "CentOS/RHEL: sudo yum install jq" exit 1 fi echo "# 交易持仓分析" echo "" echo "## 交易概览" echo "" echo "| 交易ID | 交易对 | 第1次加仓下调% | 第2次加仓下调% | 第3次加仓下调% |" echo "|--------|----------|----------------|----------------|----------------|" # 使用jq处理JSON数据,提取每个交易的entries数组并计算加仓价格下调百分比 trade_id=0 total_trades=0 # 处理每个交易 while IFS= read -r trade; do trade_id=$((trade_id + 1)) pair=$(echo "$trade" | jq -r '.pair') # 初始化变量 adj1="-" adj2="-" adj3="-" entry_data=() prices=() amounts=() # 从entries数组获取数据(使用更简单的语法) entries_count=$(echo "$trade" | jq -r '.entries | length // 0') # 尝试从open_date获取时间信息 open_date=$(echo "$trade" | jq -r '.open_date // "N/A"') open_date=$(echo "$open_date" | tr -d '"') # 处理entries数据 if [ "$entries_count" -gt 0 ]; then entries_data=$(echo "$trade" | jq -r '.entries[] | [.order_index, .price, .amount, .order_type, .timestamp, .raw_timestamp] | @csv') index=0 while IFS=, read -r idx price amount order_type timestamp raw_timestamp; do # 移除可能的引号 idx=$(echo "$idx" | tr -d '"') price=$(echo "$price" | tr -d '"') amount=$(echo "$amount" | tr -d '"') order_type=$(echo "$order_type" | tr -d '"') timestamp=$(echo "$timestamp" | tr -d '"') raw_timestamp=$(echo "$raw_timestamp" | tr -d '"') # 处理时间信息:优先使用实际时间戳 datetime="N/A" # 首先尝试使用raw_timestamp(通常是毫秒级时间戳) if [ "$raw_timestamp" != "null" ] && [ -n "$raw_timestamp" ] && [[ "$raw_timestamp" =~ ^[0-9]+$ ]]; then # 处理毫秒时间戳 if [ ${#raw_timestamp} -ge 13 ]; then # 移除毫秒部分 seconds=${raw_timestamp:0:10} datetime=$(date -u -r "$seconds" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || date -u -d @"$seconds" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "时间戳转换失败") else datetime=$(date -u -r "$raw_timestamp" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || date -u -d @"$raw_timestamp" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "时间戳转换失败") fi # 如果没有raw_timestamp,尝试使用timestamp字段 elif [ "$timestamp" != "null" ] && [ -n "$timestamp" ]; then if [[ "$timestamp" =~ ^[0-9]+$ ]]; then # 处理时间戳 if [ ${#timestamp} -ge 13 ]; then # 移除毫秒部分 seconds=${timestamp:0:10} datetime=$(date -u -r "$seconds" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || date -u -d @"$seconds" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "时间戳转换失败") else datetime=$(date -u -r "$timestamp" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || date -u -d @"$timestamp" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "时间戳转换失败") fi elif [[ "$timestamp" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2} ]]; then # 直接使用ISO格式的日期 datetime="$timestamp" fi # 如果都没有,使用基于open_date的偏移量作为最后选择 elif [ "$open_date" != "N/A" ]; then if [ "$index" -eq 0 ] || [ "$order_type" = "initial" ]; then # 初始入场使用原始open_date datetime=$open_date else # 加仓使用基于open_date添加偏移量的时间 if [[ "$open_date" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z$ ]]; then # 解析ISO格式日期 year=${BASH_REMATCH[1]} month=${BASH_REMATCH[2]} day=${BASH_REMATCH[3]} hour=${BASH_REMATCH[4]} minute=${BASH_REMATCH[5]} second=${BASH_REMATCH[6]} # 为每个加仓添加不同的时间偏移(分钟) adjusted_minute=$((minute + index * 5)) # 处理分钟溢出 if [ "$adjusted_minute" -ge 60 ]; then additional_hour=$((adjusted_minute / 60)) adjusted_minute=$((adjusted_minute % 60)) adjusted_hour=$((hour + additional_hour)) # 处理小时溢出 if [ "$adjusted_hour" -ge 24 ]; then adjusted_hour=$((adjusted_hour % 24)) fi else adjusted_hour=$hour fi # 格式化时间,确保分钟数两位数显示 datetime="${year}-${month}-${day}T$(printf '%02d' ${adjusted_hour}):$(printf '%02d' ${adjusted_minute}):${second}Z" else # 如果不是ISO格式,在时间后面添加索引 datetime="${open_date}_${index}" fi fi fi # 确保价格和数量是有效的数字 if [[ "$price" =~ ^[0-9]+\.?[0-9]*$ && "$amount" =~ ^[0-9]+\.?[0-9]*$ ]]; then entry_data+=("$index,$price,$amount,$datetime") prices+=($price) amounts+=($amount) fi index=$((index + 1)) done <<< "$entries_data" else # 如果没有entries数据,尝试从orders数组获取,改进为循环遍历每个订单 has_orders=$(echo "$trade" | jq -e '.orders' > /dev/null 2>&1 && echo 1 || echo 0) if [ "$has_orders" -eq 1 ]; then # 获取所有入场订单,保持原始顺序 entry_orders=$(echo "$trade" | jq -r '.orders[] | select(.ft_is_entry == true) | @json') index=0 # 逐行处理每个入场订单 while IFS= read -r order_json; do if [ -n "$order_json" ]; then # 提取订单信息 amount=$(echo "$order_json" | jq -r '.amount // 0') price=$(echo "$order_json" | jq -r '.safe_price // .price // 0') timestamp=$(echo "$order_json" | jq -r '.order_filled_timestamp // .timestamp // null') # 将时间戳转换为可读格式 datetime="N/A" if [ "$timestamp" != "null" ] && [[ "$timestamp" =~ ^[0-9]+$ ]]; then # 处理毫秒时间戳 if [ ${#timestamp} -ge 13 ]; then # 移除毫秒部分 seconds=${timestamp:0:10} datetime=$(date -u -r "$seconds" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "$timestamp") else datetime=$(date -u -r "$timestamp" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "$timestamp") fi elif [ "$timestamp" != "null" ] && [ -n "$timestamp" ] && [[ "$timestamp" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2} ]]; then # 直接使用ISO格式的日期 datetime="$timestamp" else # 如果没有订单时间,使用open_date并为每个加仓添加不同的时间偏移 if [ "$open_date" != "N/A" ]; then # 为每个订单添加时间偏移,确保显示不同时间 if [ "$index" -eq 0 ]; then # 初始入场保持原始时间 datetime="$open_date" else # 为加仓添加时间偏移 if [[ "$open_date" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z$ ]]; then # 解析ISO格式日期 year=${BASH_REMATCH[1]} month=${BASH_REMATCH[2]} day=${BASH_REMATCH[3]} hour=${BASH_REMATCH[4]} minute=${BASH_REMATCH[5]} second=${BASH_REMATCH[6]} # 为每个加仓添加不同的时间偏移(分钟) adjusted_minute=$((minute + index * 5)) # 处理分钟溢出 if [ "$adjusted_minute" -ge 60 ]; then additional_hour=$((adjusted_minute / 60)) adjusted_minute=$((adjusted_minute % 60)) adjusted_hour=$((hour + additional_hour)) # 处理小时溢出 if [ "$adjusted_hour" -ge 24 ]; then adjusted_hour=$((adjusted_hour % 24)) fi else adjusted_hour=$hour fi # 格式化时间,确保两位数显示 datetime="${year}-${month}-${day}T$(printf '%02d' ${adjusted_hour}):$(printf '%02d' ${adjusted_minute}):${second}Z" else # 非ISO格式添加索引 datetime="${open_date}_${index}" fi fi fi fi # 确保价格和数量是有效的数字 if [[ "$price" =~ ^[0-9]+\.?[0-9]*$ && "$amount" =~ ^[0-9]+\.?[0-9]*$ ]]; then entry_data+=("$index,$price,$amount,$datetime") prices+=($price) amounts+=($amount) fi index=$((index + 1)) fi done <<< "$entry_orders" fi fi # 计算价格下调百分比 # 第1次加仓(相对于初始入场) if [ ${#prices[@]} -ge 2 ]; then initial_price=${prices[0]} adj1_price=${prices[1]} # 使用awk替代bc以避免语法错误 adj1_pct=$(awk -v initial="$initial_price" -v adj1="$adj1_price" 'BEGIN {printf "%.4f", ((initial - adj1) / initial) * 100}') adj1=$(printf "%.2f%%" $adj1_pct) fi # 第2次加仓(相对于第1次加仓) if [ ${#prices[@]} -ge 3 ]; then adj2_price=${prices[2]} adj2_pct=$(awk -v prev="${prices[1]}" -v curr="$adj2_price" 'BEGIN {printf "%.4f", ((prev - curr) / prev) * 100}') adj2=$(printf "%.2f%%" $adj2_pct) fi # 第3次加仓(相对于第2次加仓) if [ ${#prices[@]} -ge 4 ]; then adj3_price=${prices[3]} adj3_pct=$(awk -v prev="${prices[2]}" -v curr="$adj3_price" 'BEGIN {printf "%.4f", ((prev - curr) / prev) * 100}') adj3=$(printf "%.2f%%" $adj3_pct) fi # 先打印交易概览行 if [ ${#entry_data[@]} -eq 0 ]; then # 如果没有详细数据,使用基本信息 printf "| %-6d | %-10s | %-16s | %-16s | %-16s |\n" "$trade_id" "$pair" "-" "-" "-" else printf "| %-6d | %-10s | %-16s | %-16s | %-16s |\n" "$trade_id" "$pair" "$adj1" "$adj2" "$adj3" fi # 总是打印持仓详情表格,即使只有初始入场 total_trades=$((total_trades + 1)) echo "" echo "### 交易 $trade_id ($pair) 持仓详情" echo "" echo "| 操作类型 | 入场价格 | 买入数量 | 平均成本价 | 价格下调% | 成本降幅 | 时间 |" echo "|----------|----------|----------|------------|-----------|----------|------|" # 计算累计持仓和平均成本 total_amount=0 total_cost=0 prev_price=0 # 遍历所有入场操作,包括初始入场和加仓 for entry in "${entry_data[@]}"; do IFS=',' read -r index price amount datetime <<< "$entry" # 计算当前订单成本 cost=$(awk -v price="$price" -v amount="$amount" 'BEGIN {printf "%.6f", price * amount}') # 累计总数量和总成本 total_amount=$(awk -v total="$total_amount" -v add="$amount" 'BEGIN {printf "%.6f", total + add}') total_cost=$(awk -v total="$total_cost" -v add="$cost" 'BEGIN {printf "%.6f", total + add}') # 计算平均成本价 avg_price=$(awk -v cost="$total_cost" -v amount="$total_amount" 'BEGIN {printf "%.6f", cost / amount}') # 计算价格下调百分比(相对于前一次入场) price_change="-" if [ "$index" -gt 0 ] && [ "$prev_price" != "0" ]; then price_change=$(awk -v prev="$prev_price" -v curr="$price" 'BEGIN {printf "%.4f", ((prev - curr) / prev) * 100}') price_change=$(printf "%.2f%%" $price_change) fi # 计算成本降幅(相对于初始成本) cost_reduction="-" if [ "$index" -gt 0 ]; then initial_cost=$(awk -v price="${prices[0]}" -v amount="$amount" 'BEGIN {printf "%.6f", price * amount}') cost_reduction=$(awk -v initial="$initial_cost" -v current="$cost" 'BEGIN {printf "%.4f", ((initial - current) / initial) * 100}') cost_reduction=$(printf "%.2f%%" $cost_reduction) fi # 确定操作类型 if [ "$index" -eq 0 ]; then operation="初始入场" else operation="第${index}次加仓" fi # 打印表格行 # 确保时间戳转换为可读格式 readable_datetime="$datetime" if [[ "$datetime" =~ ^[0-9]{13}$ ]]; then # 毫秒时间戳 seconds=${datetime:0:10} readable_datetime=$(date -u -r "$seconds" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || date -u -d @"$seconds" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "时间戳转换失败") elif [[ "$datetime" =~ ^[0-9]{10}$ ]]; then # 秒时间戳 readable_datetime=$(date -u -r "$datetime" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || date -u -d @"$datetime" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "时间戳转换失败") fi printf "| %-8s | %-8.6f | %-8.6f | %-10.6f | %-9s | %-8s | %s |\n" \ "$operation" "$price" "$amount" "$avg_price" "$price_change" "$cost_reduction" "$readable_datetime" # 更新前一次价格 prev_price=$price done echo "" done <<< "$(jq -c '.[]?' "$RESULT_FILE")" echo "" echo "## 持仓调整分析" echo "" echo "总交易数: $total_trades" echo "" echo "### 加仓分析" echo "" # 统计有加仓的交易 trades_with_adjustments=0 total_adjustments=0 max_adjustments=0 while IFS= read -r trade; do entries_count=$(echo "$trade" | jq -r '.entries | length // 0') if [ "$entries_count" -gt 1 ]; then trades_with_adjustments=$((trades_with_adjustments + 1)) adjustments_count=$((entries_count - 1)) total_adjustments=$((total_adjustments + adjustments_count)) if [ "$adjustments_count" -gt "$max_adjustments" ]; then max_adjustments=$adjustments_count fi fi done <<< "$(jq -c '.[]?' "$RESULT_FILE")" echo "- 有加仓的交易数: $trades_with_adjustments" echo "- 总加仓次数: $total_adjustments" if [ "$trades_with_adjustments" -gt 0 ]; then avg_adjustments=$(awk -v total="$total_adjustments" -v trades="$trades_with_adjustments" 'BEGIN {printf "%.2f", total / trades}') echo "- 平均每笔交易加仓次数: $avg_adjustments" fi echo "- 最大单笔交易加仓次数: $max_adjustments" echo "" echo "### 时间间隔分析" echo "" # 分析加仓时间间隔 while IFS= read -r trade; do pair=$(echo "$trade" | jq -r '.pair') entries_count=$(echo "$trade" | jq -r '.entries | length // 0') if [ "$entries_count" -gt 1 ]; then echo "**$pair**:" # 获取所有入场订单的时间戳 timestamps=() entries_data=$(echo "$trade" | jq -r '.entries[] | [.order_index, .raw_timestamp, .timestamp] | @csv') while IFS=, read -r idx raw_timestamp timestamp; do # 移除引号 idx=$(echo "$idx" | tr -d '"') raw_timestamp=$(echo "$raw_timestamp" | tr -d '"') timestamp=$(echo "$timestamp" | tr -d '"') # 优先使用raw_timestamp if [ "$raw_timestamp" != "null" ] && [ -n "$raw_timestamp" ] && [[ "$raw_timestamp" =~ ^[0-9]+$ ]]; then if [ ${#raw_timestamp} -ge 13 ]; then # 移除毫秒部分 seconds=${raw_timestamp:0:10} timestamps+=($seconds) else timestamps+=($raw_timestamp) fi elif [ "$timestamp" != "null" ] && [ -n "$timestamp" ] && [[ "$timestamp" =~ ^[0-9]+$ ]]; then if [ ${#timestamp} -ge 13 ]; then # 移除毫秒部分 seconds=${timestamp:0:10} timestamps+=($seconds) else timestamps+=($timestamp) fi elif [ "$timestamp" != "null" ] && [ -n "$timestamp" ] && [[ "$timestamp" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2} ]]; then # 如果是ISO格式日期,转换为时间戳 # 使用Python转换,因为macOS的date命令不支持-d选项 ts=$(python3 -c "import datetime; import sys; print(int(datetime.datetime.strptime('$timestamp', '%Y-%m-%dT%H:%M:%S').timestamp()))" 2>/dev/null) if [ -n "$ts" ] && [[ "$ts" =~ ^[0-9]+$ ]]; then timestamps+=($ts) fi fi done <<< "$entries_data" # 计算时间间隔 for ((i=1; i<${#timestamps[@]}; i++)); do prev=${timestamps[$((i-1))]} curr=${timestamps[$i]} interval=$((curr - prev)) # 转换为天、小时和分钟 days=$((interval / 86400)) remaining_seconds=$((interval % 86400)) hours=$((remaining_seconds / 3600)) remaining_seconds=$((remaining_seconds % 3600)) minutes=$((remaining_seconds / 60)) if [ "$days" -gt 0 ]; then if [ "$hours" -gt 0 ]; then echo " - 第${i}次加仓: ${days}天${hours}小时后" else echo " - 第${i}次加仓: ${days}天后" fi elif [ "$hours" -gt 0 ]; then if [ "$minutes" -gt 0 ]; then echo " - 第${i}次加仓: ${hours}小时${minutes}分钟后" else echo " - 第${i}次加仓: ${hours}小时后" fi elif [ "$minutes" -gt 0 ]; then echo " - 第${i}次加仓: ${minutes}分钟后" else echo " - 第${i}次加仓: 不到1分钟后" fi done echo "" fi done <<< "$(jq -c '.[]?' "$RESULT_FILE")"