计算移动平均线的最有效数据结构是什么?

时间:2013-02-05 19:43:32

标签: performance perl data-structures quantitative-finance

在股票市场上工作时,需要计算包括28天平均值,14天平均值等计算指标。

此外,每天平均需要更新,以包括最近一天的收盘价/高/低价/成交量。

现在,数组需要循环才能找到sum,average,max和min值。

我认为一个队列(以动态数组或链表的形式或者你能想到的任何其他东西)看起来像是基于进入/退出的FIFO方法的完美数据结构。

问题:

  1. 队列的效率和可扩展性如何与哈希相比?
  2. 当运行Perl脚本时,队列将不在内存中,因此必须进行初始化和处理(从CSV文件初始化的值),我知道这是非常不相关的,但是哪种数据结构最好使用Perl脚本,该脚本按日计划执行?

6 个答案:

答案 0 :(得分:2)

使用PDL,您可以获取一个更大的“数组”(1D矩阵),然后对该切片进行统计,然后再取另一个切片并重复。 PDL有许多内置的统计函数,如果还不够,还有附加PDL::Stats

对于Perl,PDL就像MatLab或NumPy(我们认为更好!)。它针对数值阵列的“循环”进行了高度优化。我把“循环”放在引号中,因为这些循环是在C级实现的(听起来很快啊?)。看一看!

答案 1 :(得分:1)

此处信息不足。您是否处理了整天的报价以获得每日高/低/最后?

您是否按照每个工具进行平均值?

对于任何移动平均线,您只需使用构成平均值总和的值列表。随着新值的增加,一个值下降,并重新计算。

如果您通过仪器进行操作并在运行中计算,那么就是列表的散列。

答案 2 :(得分:1)

最有效的是什么。

  • 哈希是无序的。他们可以通过字符串键在几乎恒定的时间内查找值。查找计算量很大,并且至少比数组查找慢一个数量级。散列的执行效果取决于“桶”的数量和键的数量。但是,哈希查找比循环遍历数组更快,以便为所有非平凡情况找到元素。哈希需要比阵列更多的空间。
  • Perl中的
  • 数组具有数组(随机访问)和双向链表(通过push,pop,shift,unshift)的特征。它们易于使用,而且速度足够快。如果要添加/删除多个元素,请使用切片或splice函数。 splicepush .. unshift的概括,并且比循环更快。
  • 字符串可用于存储整数数组。这非常有效,但也非常有限(仅限整数)。

    my $string = "";
    my $i = ~ 0; # a really big number
    $string .= chr $i; # get character from integer
    # Access elements via `substr`:
    my $j = ord substr $string, -1, 1; # last element; ord gets an int from a char
    

    使用字符串具有数组(随机访问)和单个链表的特征(使用.=附加简单)。其他操作也相当快(substr有很多用途)。

实用程序员将使用数组来处理大多数顺序数据。他还可以利用List::UtilList::MoreUtils中提供sumaveragemaxmin等函数的高效函数(用C语言编写)速度)。

当您构建值列表并且只需要固定数量时,请在添加新元素时执行此操作:

push @array, $new_value;
shift @array if @array > $max_length; # keep constant length

这节省空间,但可能比简单地构建列表和执行

splice @array, 0, -$max_length; # remove all but $max_length last elems

要仅访问数组的某个部分(不分配新变量),请使用切片:

use List::Util qw/sum/;
my $last_24_sum = sum @array[-24 .. $#$array];  sum the last 24 elems

如果要使用哈希,但在编译时知道所有可能的字段,则可以为字段定义常量名称,而是使用数组。所以不要做

my $hashref = { foo => $x, bar => $y }; # requires a lot of space
$hashref->{foo}; # slooow

但是

use constant {
    EL_FOO => 0, # make sure the integer range is continouus
    EL_BAR => 1, #   Perl doesn't have native enums
};
my $arrayref = [$x, $y];
$arrayref->[EL_FOO]; # faster!

代替。

使用深层嵌套数据时,有时可以通过缓存嵌套引用来获得回报,而不是在每次访问时重新计算它们:

# disputable
for my $i (...) {
  for my $j (...)
    do_something_with $x->[$i][$j][$_] for 1 .. 1e3;
  }
}
# possibly better
for my $i (...) {
  for my $j (...) {
    my $aref = $x->[$i][$j];
    do_something_with $aref->[$_] for 1 .. 1e3;
  }
}

答案 3 :(得分:1)

如果您每天计算一次这些东西......使用最简单的数据结构进行编码!计算真的需要这么多时间吗?如果是,请继续阅读。

总和和平均值可能更容易。如果添加的是整数,则可以使用FIFO并将总和保存在变量中。无论何时插入或删除元素,都要相应地更新总和(加或减)。

如果添加浮点值,则上述方法可能会导致累积错误。如果值具有非常不同的大小和/或系列非常长,则可能发生这种情况。在这种情况下,您需要更复杂的东西(见下文)。

对于max和min,最有效的数据结构是max/min-heaps。请注意,您可以将它们嵌入到数组中。您需要将它们与FIFO队列的元素进行交叉引用,以便立即找到每次都必须删除的元素。

最通用的解决方案是增强的自平衡树。增强数据结构在"算法简介"的第14章中进行了解释。由Cormen,Leiserson,Rivest和Stein。基本上,树将在每个节点中包含数据序列的一个元素。每个节点还包含其子树的和,最小值和最大值。每次更新节点时,都必须更新从该节点到根节点的所有路径中的sum,max和min。在根中你有全局和,最大和最小。

您可以找到扩充自平衡树here的C ++实现。

虽然,因为你只需要固定数量元素的总和,最小值和最大值,并且你总是在一端插入而在另一端删除,你可以使它更简单。您只需要一个循环缓冲区和一个嵌入数组的树(参见how to embed such tree in an array)。树将包含部分和,最小和最大值,如前面描述的增强树。优点是您不需要重新平衡树,因为您从不在序列的中间插入/删除树,并且树总是具有相同的大小。

为了获得过去28天,过去14天,最后一周和过去3天(例如)的统计信息,您将为每个时段使用循环缓冲区和数组嵌入树:一个用于过去3天,另一个用于前4天(7减3)天,另一个用于前7天,依此类推。每天,您将获取每个缓冲区的最后一个数据并将其插入下一个缓冲区。

答案 4 :(得分:0)

我只使用一个简单的%hash,其中键是时间戳的字符串化或数字化表示,值是您的数据与该时间序列项相关联的值。您可以对哈希的键进行排序,例如

#numeric
@srtdKeys = sort{$a<=>$b}(keys(%hash));

#string
@srtdKeys = sort(keys(%hash));

并遍历最后的28或14等,然后从散列中检索值。

答案 5 :(得分:0)

如果您在每次运行时从头开始重建数据,也许您应该查看Statistics::Descriptive

很遗憾你是以CSV形式获得的。通过访问数据库服务器,您可以通过几个SQL查询获取此信息。