Bash 带有 jq 的脚本不会从字符串中获取日期差异,并且在 i7 16GB RAM 上运行速度相当慢

Bash script with jq wont get date difference from strings, and runs quite slowly on i7 16GB RAM

需要在以下脚本中找出 Exposure 列的 dd:hh:mm 格式的 TradeCloseTime 和 TradeOpenTime 时间之间的差异。

此外,脚本运行速度非常慢(在 Core i7 16gb RAM 机器上,800 行 json 大约需要 4 分钟)

#!/bin/bash
echo "TradeNo, TradeOpenType, TradeCloseType, TradeOpenSource, TradeCloseSource, TradeOpenTime, TradeCloseTime, PNL, Exposure" > tradelist.csv
tradecount=$(jq -r '.performance.numberOfTrades|tonumber' D.json)
for ((i=0; i<$tradecount; i++))
do
tradeNo=$(jq -r '.trades['$i']|[.tradeNo][]|tonumber' D.json)
entrySide=$(jq -r '.trades['$i'].orders[0]|[.side][]' D.json)
exitSide=$(jq -r '.trades['$i'].orders[1]|[.side][]' D.json)
entrySource=$(jq -r '.trades['$i'].orders[0]|[.source][]' D.json)
exitSource=$(jq -r '.trades['$i'].orders[1]|[.source][]' D.json)
tradeEntryTime=$(jq -r '.trades['$i'].orders[0]|[.placedTime][]' D.json | tr -d 'Z' |  tr -s 'T' ' ')
tradeExitTime=$(jq -r '.trades['$i'].orders[1]|[.placedTime][]' D.json | tr -d 'Z' |  tr -s 'T' ' ')
profitPercentage=$(jq -r '(.trades['$i']|[.profitPercentage][0]|tonumber)*(100)' D.json)
echo $tradeNo","$entrySide","$exitSide","$entrySource","$exitSource","$tradeEntryTime","$tradeExitTime","$profitPercentage | tr -d '"' >> tradelist.csv
done

json 文件看起来像这样

{"market":{"exchange":"BINANCE_FUTURES","coinPair":"BTC_USDT"},"strategy":{"name":"","type":"BACKTEST","candleSize":15,"lookbackDays":6,"leverageLong":1.00000000,"leverageShort":1.00000000,"strategyName":"ABC","strategyVersion":35,"runNo":"002","source":"Personal"},"strategyParameters":[{"name":"DurationInput","value":"87.0"}],"openPositionStrategy":{"actionTime":"CANDLE_CLOSE","maxPerSignal":1.00000000},"closePositionStrategy":{"actionTime":"CANDLE_CLOSE","minProfit":"NaN","stopLossValue":0.07000000,"stopLossTrailing":true,"takeProfit":0.01290000,"takeProfitDeviation":"NaN"},"performance":{"startTime":"2019-01-01T00:00:00Z","endTime":"2021-11-24T00:00:00Z","startAllocation":1000.00000000,"endAllocation":3478.58904150,"absoluteProfit":2478.58904150,"profitPerc":2.47858904,"buyHoldRatio":0.62426630,"buyHoldReturn":4.57228387,"numberOfTrades":744,"profitableTrades":0.67833109,"maxDrawdown":-0.20924885,"avgMonthlyProfit":0.05242718,"profitableMonths":0.70370370,"avgWinMonth":0.09889897,"avgLoseMonth":-0.05275563,"startPrice":null,"endPrice":57623.08000000},"trades":[{"tradeNo":0,"profit":-5.48836165,"profitPercentage":-0.00549085,"accumulatedBalance":994.51163835,"compoundProfitPerc":-0.00548836,"orders":[{"side":"Long","placedTime":"2019-09-16T21:15:00Z","placedAmount":0.09700000,"filledTime":"2019-09-16T21:15:00Z","filledAmount":0.09700000,"filledPrice":10300.49000000,"commissionPaid":0.39965901,"source":"SIGNAL"},{"side":"CloseLong","placedTime":"2019-09-17T19:15:00Z","placedAmount":0.09700000,"filledTime":"2019-09-17T19:15:00Z","filledAmount":0.09700000,"filledPrice":10252.13000000,"commissionPaid":0.39778264,"source":"SIGNAL"}]},{"tradeNo":1,"profit":-3.52735800,"profitPercentage":-0.00356403,"accumulatedBalance":990.98428035,"compoundProfitPerc":-0.00901572,"orders":[{"side":"Long","placedTime":"2019-09-19T06:00:00Z","placedAmount":0.10000000,"filledTime":"2019-09-19T06:00:00Z","filledAmount":0.10000000,"filledPrice":9893.16000000,"commissionPaid":0.39572640,"source":"SIGNAL"},{"side":"CloseLong","placedTime":"2019-09-19T06:15:00Z","placedAmount":0.10000000,"filledTime":"2019-09-19T06:15:00Z","filledAmount":0.10000000,"filledPrice":9865.79000000,"commissionPaid":0.39463160,"source":"SIGNAL"}]},{"tradeNo":2,"profit":-5.04965308,"profitPercentage":-0.00511770,"accumulatedBalance":985.93462727,"compoundProfitPerc":-0.01406537,"orders":[{"side":"Long","placedTime":"2019-09-25T10:15:00Z","placedAmount":0.11700000,"filledTime":"2019-09-25T10:15:00Z","filledAmount":0.11700000,"filledPrice":8430.00000000,"commissionPaid":0.39452400,"source":"SIGNAL"},{"side":"CloseLong","placedTime":"2019-09-25T10:30:00Z","placedAmount":0.11700000,"filledTime":"2019-09-25T10:30:00Z","filledAmount":0.11700000,"filledPrice":8393.57000000,"commissionPaid":0.39281908,"source":"SIGNAL"}]}

关于主要问题(关于计算时差),你很幸运,因为 jq 提供了内置函数 fromdateiso8601 用于将 ISO 时间转换为“the 自 Unix 纪元 (1970-01-01T00:00:00Z) 以来的秒数。

有了你的 JSON 样本,

.trades[]
| [ .orders[1].placedTime, .orders[0].placedTime]
| map(fromdateiso8601)
| .[0] - .[1]

产生三个差异:

79200
900
900

下面是一个将秒数转换为“hh:mm:ss”格式的函数:

def hhmmss:
  def l: tostring | if length < 2 then "0\(.)" else . end;
  (. % 60) as $ss
  | ((. / 60) | floor) as $mm
  | (($mm / 60) | floor) as $hh
  | ($mm % 60) as $mm
  | [$hh, $mm, $ss] | map(l) | join(":");

我更喜欢使用“入口”和“出口”的中间结构JSON。这有助于调试 jq 命令。格式化为可读性优于性能:

#!/usr/bin/env bash
echo "TradeNo,TradeOpenType,TradeCloseType,TradeOpenSource,TradeCloseSource,TradeOpenTime,TradeCloseTime,PNL,Exposure" > tradelist.csv
jq -r '
  .trades[]
  |{tradeNo,
    profitPercentage,
    entry:.orders[0],
    exit:.orders[1],
    entryTS:.orders[0].placedTime|fromdate,
    exitTS:.orders[1].placedTime|fromdate}
  |[.tradeNo,
    .entry.side,
    .exit.side,
    .entry.source,
    .exit.source,
    (.entry.placedTime|strptime("%Y-%m-%dT%H:%M:%SZ")|strftime("%Y-%m-%d %H:%M:%S")),
    (.exit.placedTime|strptime("%Y-%m-%dT%H:%M:%SZ")|strftime("%Y-%m-%d %H:%M:%S")),
    (.profitPercentage*100),
    (.exitTS-.entryTS|todate|strptime("%Y-%m-%dT%H:%M:%SZ")|strftime("%d:%H:%M"))]|@csv
  ' D.json | tr -d '"' >> tradelist.csv

警告:此格式假定曝光时间 少于 1 个月。祝你好运!

您只需一个 jq 电话即可完成所有工作(提取、转换和格式化):

#!/bin/sh

echo 'TradeNo,TradeOpenType,TradeCloseType,TradeOpenSource,TradeCloseSource,TradeOpenTime,TradeCloseTime,PNL,Exposure'

query='
  .trades[]
  | [
    .tradeNo,
    .orders[0].side,
    .orders[1].side,
    .orders[0].source,
    .orders[1].source,
    (.orders[0].placedTime | fromdate | strftime("%Y-%m-%d %H:%M:%S")),
    (.orders[1].placedTime | fromdate | strftime("%Y-%m-%d %H:%M:%S")),
    .profitPercentage * 100,
    (
      (.orders[1].placedTime | fromdate) - (.orders[0].placedTime | fromdate)
      | (. / 86400 | floor | tostring) + (. % 86400 | strftime(":%H:%M"))
    )
  ]
  |@csv
'

jq -r "$query" < D.json > tradelist.csv

JSON 的示例(清除了所有不相关的键):

{ 
  "trades": [
    {
      "tradeNo": 0,
      "profitPercentage": -0.00549085,
      "orders": [
        {
          "side": "Long",
          "placedTime": "2018-12-16T21:34:46Z",
          "source": "SIGNAL"
        },
        {
          "side": "CloseLong",
          "placedTime": "2019-09-17T19:15:00Z",
          "source": "SIGNAL"
        }
      ]
    }
  ]
}

输出:

TradeNo,TradeOpenType,TradeCloseType,TradeOpenSource,TradeCloseSource,TradeOpenTime,TradeCloseTime,PNL,Exposure
0,"Long","CloseLong","SIGNAL","SIGNAL","2018-12-16 21:34:46","2019-09-17 20:15:00",-0.549085,"274:22:40"

如果你想摆脱 jq 在生成 CSV 时添加的双引号(这是完全有效的,但你需要一个真正的解析器来读取 CSV)那么你可以替换 @csv 使用 @tsv 和 post - 使用 tr '\t' ',' 处理输出,如下所示:

query='
  ...
  |@tsv
'

jq -r "$query" < D.json | tr '\t' ',' > tradelist.csv

你会得到:

TradeNo,TradeOpenType,TradeCloseType,TradeOpenSource,TradeCloseSource,TradeOpenTime,TradeCloseTime,PNL,Exposure
0,Long,CloseLong,SIGNAL,SIGNAL,2018-12-16 21:34:46,2019-09-17 20:15:00,-0.549085,274:22:40

注意: 这种去除 CSV 中 " 的方法只有在没有 \n \t \r \ ," 输入数据中的字符。