454 lines
20 KiB
Bash
Executable File
454 lines
20 KiB
Bash
Executable File
#!/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")"
|