如何使用 mongodb 聚合 OHLC 条?

How to aggregate OHLC bars with mongodb?

我在导入 Mongo 数据库的一些市场数据上有 1 分钟 OHLC 柱。

每个文档如下所示:

{
    "_id" : ObjectId("5ac3163f31a0632c7642ca1c"),
    "Date" : "08/06/2007",
    "Time" : "15:01",
    "Open" : 1310,
    "High" : 1310.25,
    "Low" : 1309.5,
    "Close" : 1310,
    "Up" : 209,
    "Down" : 165,
    "Volume" : 0
}

我想构建一个函数,使我能够从这些数据中快速生成 X-bar 区间。即生成输出 5 分钟柱状图、1 小时柱状图、每日柱状图等...我还希望能够过滤出数据范围。

我一直在玩弄 Mongo 的聚合函数,但我不知所措,我应该如何处理这个问题以及我应该如何对管道操作进行排序。

我是否先按 'Date' 分组,然后按 'Time' 排序,然后再按 $first、$last、$max 和 $min 分组?

或者我是否首先创建一个新字段以某种方式组合 'Date' 和 'Time' 然后继续分组?

虽然我不需要首先以某种方式将 "Date" 和 "Time" 字段从字符串转换为日期字段,以便 Mongo 知道如何正确排序和匹配? ...但是我应该按照哪个顺序来做呢?

我仍然是 MongoDB 的新手,所以任何建议都将不胜感激。

我想,你可以简单地使用 PHP。 为降低代码的复杂性,请勿创建包含日期和时间的新字段。

    $outputs = array();
    $raw_datas = array();
    foreach($raw_datas as $data){
      $date = \DateTime::createFromFormat('D/M/Y H:i', $data["Date"]." ".$data["Time"]);
      $outputs['daily'][$date->format("D/M/Y")][] = $date; //or $date.id if you aim to use AJAX later
      $outputs['hourly'][$date->format("D/M/Y H")][] = $date; //or $date.id if you aim to use AJAX later
//      And so on...
//      ....
    }

    return $outputs;

不幸的是,如果您打算多次生成此图,则可以添加一个字段时间(包含时间戳)!

您需要同时按日期和时间(准确地说是时间间隔)进行分组。看看 https://docs.mongodb.com/ecosystem/use-cases/storing-log-data/#counting-requests-by-day-and-page for examples of the pipeline and Whosebug itself - the similar questions were answered many times, e.g. Group result by 15 minutes time interval in MongoDb

如果您需要快速 生成X-bar 间隔并且数据集足够大以致聚合速度明显变慢,您可能需要pre-aggregate 数据。该模式在 https://www.mongodb.com/blog/post/schema-design-for-time-series-data-in-mongodb and https://docs.mongodb.com/ecosystem/use-cases/pre-aggregated-reports-mmapv1/ 中有详细描述(如果您使用的是 WiredTiger 引擎,请忽略 pre-allocation 部分)

好的,我想出了一个解决方案:

db.minbars.aggregate([
   {
      $project: 
      {
         dts: 
         {
            $dateFromString: 
            {
               dateString: 
               {
                  $concat: ['$Date', '$Time']
               }
            }
         },
         Open:1, 
         High:1, 
         Low:1, 
         Close:1
      }   
   },
   {
      $match: 
      {
         dts: 
         { 
            $gte: ISODate("2016-01-01T00:00:00.000Z"), 
            $lte: ISODate("2016-12-31T00:00:00.000Z")
         }
      }
   },
   {
      $sort: { dts : 1 }
   },
   {
      $group:
      {
         _id: 
         {
            year: {$year: "$dts"},
            month: {$month: "$dts"},
            day: {$dayOfMonth: "$dts"},               
            hour: {$hour: "$dts"},
            min: 
            {
                $add: 
                [
                   {$subtract:
                   [
                      {$minute: "$dts"},
                      {$mod: [{$minute: "$dts"}, 5]}
                   ]},
                   5   
                ]
            }   
         },
         Open: {$first: "$Open"},
         High: {$max: "$High"},
         Low: {$min: "$Low"},
         Close: {$last: "$Close"}
      }
   } 
], {allowDiskUse: true})

以下是每个管道阶段的解释:

  1. 项目

使用 'dateFromString' 将 'Date' 和 'Time' 组合成一个 ISODate 对象('dts' - 代表日期时间戳)。保留其他 OHLC 字段。

  1. 匹配

根据日期过滤掉 运行ge

  1. 排序

按新的 ISODate 对象排序 ('dts')。

将具有相同年、月、日、小时和 5 分钟间隔的所有这些文档组合在一起。分钟间隔使用公式: minute = minuteIn - (minuteIn % i) + i,其中 i = 分钟间隔。我正在添加 'i',以便将第 00、01、02、03 和 04 分钟聚合到下一个 05 分钟间隔(而不是之前的 00 分钟间隔)。注意:如果您想要 1 小时、4 小时、每日柱等...那么您需要相应地调整 _id 部分。

注意: 我在这里使用 {allowDiskUse: true} 因为有一次我 运行 在排序阶段进入内存限制。

也许有人可以想出更简单的方法来做到这一点?


更新:

正如我在上面的 4) 中指出的那样,我提到我将 "i"(分钟间隔)添加到结果分钟。但是,当我这样做时,我最终在输出中显示了一个“60”分钟的间隔。您应该只有 0、5、10、15、...55 分钟柱,不应有 60 分钟柱。所以这是不正确的。

此外,如果您与交易平台(即 Thinkorswim)进行比较,您会发现标准做法是使用前 5 分钟间隔作为柱的时间戳。例如,5 分钟柱 9:25 表示这些分钟柱的聚合:9:25、9:26、9:27、9:28、9:29 .