1

我正在用 Nodejs 开发一个金融应用程序。我想知道是否可以直接在 Mongo Shell 中计算简单的移动平均线,即过去 N 天的平均价格,而不是在 Node js 中读取和计算它。

文件样本。

[{code:'0001',price:0.10,date:'2014-07-04T00:00:00.000Z'},
{code:'0001',price:0.12,date:'2014-07-05T00:00:00.000Z'},{code:'0001',price:0.13,date:'2014-07-06T00:00:00.000Z'},
{code:'0001',price:0.12,date:'2014-07-07T00:00:00.000Z'}]
4

2 回答 2

2

如果您有大量文档,您应该使用 DB 服务器而不是 JS 来完成工作。

您没有说您是直接使用猫鼬还是节点驱动程序。我假设您使用的是猫鼬,因为这是大多数人的方式。

所以你的模型是:

// models/stocks.js
const mongoose = require("mongoose");
const conn = mongoose.createConnection('mongodb://localhost/stocksdb');

const StockSchema = new mongoose.Schema(
  {
    price: Number,
    code: String,
    date: Date,
  },
  { timestamps: true }
);

module.exports = conn.model("Stock", StockSchema, "stocks");

您正确地建议聚合框架将是一个很好的方法。首先,如果我们要处理日期范围之间的返回值,那么数据库中的记录需要是日期对象。从您的示例文档中,您可能已经放置了字符串。插入带有日期的对象的示例是:

db.stocks.insertMany([{code:'0001',price:0.10,date:ISODate('2014-07-04T00:00:00.000Z')}, {code:'0001',price:0.12,date:ISODate('2014-07-05T00:00:00.000Z')},{code:'0001',price:0.13,date:ISODate('2014-07-06T00:00:00.000Z')}, {code:'0001',price:0.12,date:ISODate('2014-07-07T00:00:00.000Z')}])

聚合管道函数接受具有一个或多个管道阶段的数组。

我们应该使用的第一个管道阶段是$match$ match docs,这会将文档过滤到仅我们感兴趣的对性能很重要的记录

{ $match: { 
     date: { 
       $gte:  new Date('2014-07-03'),
       $lte:  new Date('2014-07-07')
      }
  }
}

此阶段将仅将 2014 年 7 月 3 日至 7 日(含)的文档发送到下一阶段(在本例中为所有示例文档)

下一阶段是您可以获得平均值的阶段。我们需要根据一个字段、多个字段或所有字段将值组合在一起。

由于您没有指定要平均的字段,因此我将为所有字段举一个示例。为此,我们使用$group对象$group docs

        {
            $group: {
                _id: null,
                average: {
                    $avg: '$price'
                }
             }
         }

这将获取所有文件并显示所有价格的平均值。

对于您的示例文档,这会导致

{ _id: null, avg: 0.1175 }

检查答案:

(0.10 + 0.12 + 0.12 + 0.13) / 4 = 0.1175

仅供参考:我不会依赖使用 javascript 完成的任何关键计算,例如使用浮点数的数字。如果您对此感到担心,请参阅https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html了解更多详细信息。

为了完整起见,这里是完整的聚合查询

const Stock = require("./models/stocks");

Stock.aggregate([{ $match: { 
                date: { 
                        $gte:  new Date('2014-07-03'),
                        $lte:  new Date('2014-07-07')
                    }
            }},
         {
            $group: {
                _id: null,
                avg: {
                    $avg: '$price'
                }
             }
         }])
 .then(console.log)
 .catch(error => console.error(error))
于 2018-08-18T13:42:30.637 回答
0

不确定你的移动平均公式,但我会这样做:

var moving_average = null
db.test.find().forEach(function(doc) {
    if (moving_average==null) {
        moving_average = doc.price;
    } 
    else {
        moving_average = (moving_average+doc.price)/2;
    } 
})

输出:

> moving_average
0.3

如果你想定义 N 天来做平均值,只需修改find的参数:

db.test.find({ "date": { $lt: "2014-07-10T00:00:00.000Z" }, "date": { $gt: "2014-07-07T00:00:00.000Z" } })

如果你想在一行中执行上面的 shell 代码,你可以假设moving_average 是未定义的,并在分配第一个值之前检查它。

于 2018-08-18T08:42:01.383 回答