寻找股票图表的最小值和最大值

32

股票

有没有特定的算法可以让我找到上图中的最高点和最低点?

我有文本格式的数据,所以不需要在图片中查找。股票的问题在于它们有很多局部最小值和最大值,简单的导数无法解决。

我考虑使用数字滤波器 (z 域) 平滑曲线,但仍然存在太多局部最小值和最大值。

我还尝试使用移动平均来平滑曲线,但是我仍然有太多的最大值和最小值。

编辑:

我读了一些评论,只是不小心没有标记一些最小值和最大值。

我想我想出了一个可能有效的算法。首先找到最小点和最大点(当天的最高和最低),然后画三条线,从开盘到第一个最高点或最低点,然后从最低点到最高点或最高点到最低点,最后终止于收盘价。然后在这三个区域中找到距离该线最远的点作为我的最高点和最低点,然后重复循环。


1
相关问题:https://dev59.com/-mw15IYBdhLWcg3wFHzf - yasouser
6
如果最大值和最小值太多,你需要一个清晰的标准来区分它们。这个标准将是应用程序和数据集特定的。例如,我不确定为什么你没有在图形最左边圈出局部极小值。我建议你考虑一个因素来区分局部极小值,然后循环遍历这些因素,以达到你对数据集期望的结果。 - Mikhail
1
您好,请将您的数据文件托管,以便我们可以尝试一下这个原型算法。 - tomdemuyt
请重新表述您的最后一句话(“然后在这三个区域中找到距离该线最远的点作为我的高点和低点,然后重复循环。”)。它没有意义。 - Fantius
一个经过调整的移动平均线有什么问题吗?只需调整窗口大小,直到您获得所需数量的极值即可。这种问题没有其他令人满意的解决方案。 - Alexandre C.
10个回答

17
我通常使用移动平均和指数移动平均的组合。这种方法在实践中证明对于我的需求已经足够适用了(至少是我个人的需要)。结果通过仅调整两个参数来获得。这里是一个示例:

enter image description here

编辑

如果有用的话,这是我的Mathematica代码:

f[sym_] := Module[{l},
  (*get data*)
  l = FinancialData[sym, "Jan. 1, 2010"][[All, 2]];
  (*perform averages*)
  l1 = ExponentialMovingAverage[MovingAverage[l, 10], .2];
  (*calculate ma and min positions in the averaged list*)
  l2 = {#[[1]], l1[[#[[1]]]]} & /@ 
    MapIndexed[If[#1[[1]] < #1[[2]] > #1[[3]], #2, Sequence @@ {}] &, 
     Partition[l1, 3, 1]];
  l3 = {#[[1]], l1[[#[[1]]]]} & /@ 
    MapIndexed[If[#1[[1]] > #1[[2]] < #1[[3]], #2, Sequence @@ {}] &, 
     Partition[l1, 3, 1]];
  (*correlate with max and mins positions in the original list*)
  maxs = First /@ (Ordering[-l[[#[[1]] ;; #[[2]]]]] + #[[1]] - 
        1 & /@ ({4 + #[[1]] - 5, 4 + #[[1]] + 5} & /@ l2));
  mins = Last /@ (Ordering[-l[[#[[1]] ;; #[[2]]]]] + #[[1]] - 
        1 & /@ ({4 + #[[1]] - 5, 4 + #[[1]] + 5} & /@ l3));
  (*Show the plots*)
  Show[{
    ListPlot[l, Joined -> True, PlotRange -> All, 
     PlotLabel -> 
      Style[Framed[sym], 16, Blue, Background -> Lighter[Yellow]]],
    ListLinePlot[ExponentialMovingAverage[MovingAverage[l, 10], .2]], 
    ListPlot[{#, l[[#]]} & /@ maxs, 
     PlotStyle -> Directive[PointSize[Large], Red]],
    ListPlot[{#, l[[#]]} & /@ mins, 
     PlotStyle -> Directive[PointSize[Large], Black]]}, 
   ImageSize -> 400]
  ]

1
+1. MA + EWMA(甚至是简单的MA)确实是完成任务的正确工具。在这样的任务上不要试图过于聪明,简单的低通滤波器是你最好的朋友,因为你无法准确地定义什么是最小值和最大值。 - Alexandre C.
@Alexandre 我怀疑是否存在“正确的工具” :) 这些图表总是由精通图表解释艺术的人来“解读”。 - Dr. belisarius
1
这行代码在普通英语/Python中的意思是什么? l2 = {#[[1]], l1[[#[[1]]]]} & /@ MapIndexed[If[#1[[1]] < #1[[2]] > #1[[3]], #2, Sequence @@ {}] &, Partition[l1, 3, 1]]; - manav
@manav: (从右到左) Partition[l1, 3, 1] 将 l1 分成大小为 3 的列表 ({a,b,c}, {b,c,d}, {c,d,e}...), 我们得到了大小为 3 的列表的列表,我们对其应用一个转换器( MapIndexed[If[#1[[1]] < #1[[2]] > #1[[3]], #2, Sequence @@ {}]),它将返回中间元素最高的 3 元组的索引或空列表,从而得到这些元组的索引列表,{#[[1]], l1[[#[[1]]]]} & /@ 应用于所有这些列表,然后得到一个元组列表,其中第一个元组元素是此索引,第二个元组元素是此索引处 l1 的值。 - ambientlight

5
你会注意到很多答案都采用了某种低通滤波的导数。可以理解为一种移动平均。在基本层面上,FFT、方形窗口移动平均和指数移动平均都非常相似。然而,在所有移动平均中,哪一个是最好的呢?
答案:高斯移动平均;也就是你所知道的正态分布。
原因:高斯滤波器是唯一不会产生“虚假”最大值的滤波器;即不存在最初并不存在的最大值。这在连续和离散数据上已被理论证明(但请确保在离散数据上使用离散高斯)。随着高斯sigma的增加,局部最大值和最小值将以最直观的方式合并。因此,如果你想每天只有一个局部最大值,那么将sigma设置为1,等等。

4

我不知道你所说的“简单导数”是什么意思。我理解它的意思是你已经测试了梯度下降,但由于存在大量的局部极值而不尽如人意。如果是这样,你需要看一下模拟退火

退火是一种通过加热和冷却处理来调质金属的冶金过程。(...) 这些不规则性是由于原子被困在结构中错误的位置造成的。在退火过程中,金属被加热然后允许缓慢冷却。加热给予原子所需的能量以使其脱离,而缓慢冷却期允许它们移动到结构中的正确位置。
(...)
然而,为了避免局部最优解,算法将有概率朝着错误的方向迈出一步:换句话说,采取增加最小化问题的值或减少最大化问题的值的步骤。为了模拟退火过程,该概率将部分取决于算法中的“温度”参数,该参数初始化为较高值并在每次迭代时降低。因此,算法最初有很高的概率远离附近(可能是局部的)最优解。随着迭代次数的增加,该概率将减小,算法将收敛于未能逃脱的(希望全局的)最优解。 (source,删节和强调属于我)
我知道你绘制的圆形正是局部最优解的表示,因此也是你想要找到的。但是,根据我对“如此多的局部最小值和最大值,简单的导数方法行不通”的理解,这也正是你所面临的问题。我猜你很可能会遇到曲线在两个圆点之间产生太多“折返”的情况。
所有似乎区分你圈出的最优点与曲线上其他点的是它们的“全局性”,确切地说:要找到比你在左边圈出的第一个点更低的点,你必须在x坐标上向任意方向走得比其相邻点更远。这就是模拟退火的作用:根据温度参数,您可以控制允许自己进行的跳跃的大小。必须有一个值使您捕获“大”局部最优解,但错过“小”最优解。我的建议并不是革命性的:有几个例子(例如 1 2)人们从这些嘈杂的数据中获得了不错的结果。

模拟退火的整个目的在于避免局部最优解,而这正是OP 想要找到的。 - Jean-François Corbett
我注意到他想要跳过一些局部最优解,从他画的曲线来看,我理解他想要找到其他的最优解。我认为我的建议对于这个问题非常相关,并且已经详细编辑了我的答案来进行论证和解释。请您查看一下。 - Francois G
+1 分已扣除。调整温度参数是关键。 - Jean-François Corbett
我刚才在所有圈出来的点上犯了一个错误。我本意是要圈出所有局部最小值和最大值。 - SamFisher83

2

只需精确但可调整地定义最小值和最大值,然后进行调整,直到找到正确的最小值和最大值。例如,您可以通过用该值及其左右N个值的平均值替换每个值来首先使图形平滑。通过增加N,您可以减少发现的最小值和最大值的数量。

然后,您可以将最小值定义为一个点,在该点上,如果您跳过A个值的左右侧,则接下来的B个值都显示出一致的增长趋势。通过增加B,您可以找到更少的最小值和最大值。通过调整A,您可以调整允许最小值或最大值达到多么'平坦'。

一旦使用了可调整的算法,您可以随意调整它,直到看起来正确为止。


2
您可以使用样条插值法来创建一个连续的逼近多项式,以逼近您原始的函数[具有所需的次数]。在获得这个多项式之后,使用基本微积分在其上[生成的多项式]寻找局部最小值/最大值。
需要注意的是,样条插值法给出的逼近多项式既“平滑”又尽可能接近原始函数,因此局部最小值/最大值应该非常接近原始函数中的真实值。
为了提高准确性,在找到生成的多项式中的局部最小值/最大值后,对于每个代表局部最小值/最大值的x0,您应该查找所有x0-delta < x < x0 + delta的x,以找到此点表示的真实最小值/最大值。

1

我经常发现人类主观感知到的极值(即股票图表中唯一的极值,大多数是随机噪声)通常可以在傅里叶带通滤波之后找到。您可以尝试以下算法:

  1. 执行FFT
  2. 在频率空间中进行带通滤波。根据您想要极值看起来不错的数据范围(即感兴趣的时间尺度),选择带通参数。
  3. 执行反FFT。
  4. 选择结果曲线的局部最大值。

第二步的参数似乎相当主观,但再次强调,主观性是股票图表分析的本质。


1

这里是将@Dr. belisarius的代码转换为Python的结果:

import pandas as pd
import numpy as np

def emasma_maxmin(l: pd.Series, sma_period=10, ema_factor=.2):
    """
    mathematica's ema(sma(sma_period), ema_factor) minmax in python
    :param l: prices as pd.Series
    :param sma_period
    :param ema_factor
    :return: a tuple that contains max and min index lists
    """
    l1 = l.rolling(window=sma_period).mean().ewm(com=int(1/ema_factor)-1).mean().dropna()
    l1_triples = [w.to_list() for w in l1.rolling(window=3) if len(w.to_list()) == 3]
    # utilize the fact that original indexes are preserved in pd.Series
    l1_indexes = l1.index[:-2]
    # index is from ordinal l that corresponds to the first element of the triple
    l2 = [index for (index, (fst, snd, trd)) in zip(l1_indexes, l1_triples) if fst < snd > trd]
    l3 = [index for (index, (fst, snd, trd)) in zip(l1_indexes, l1_triples) if fst > snd < trd]
    max_ranges = [(-np.array([l[index] for index in range(index - sma_period + 1, index + 1)])).argsort() + (index - sma_period + 1) for index in l2]
    min_ranges = [(-np.array([l[index] for index in range(index - sma_period + 1, index + 1)])).argsort() + (index - sma_period + 1) for index in l3]
    return (
        [r[0] for r in max_ranges if len(r) > 0],
        [r[-1] for r in min_ranges if len(r) > 0],
    )


您可以通过类似以下方式(使用mplfinance)来可视化结果:
import pandas as pd
import numpy as np
import mplfinance as mpf

# read OHLC csv
msft = pd.read_csv("MSFT1440.csv", parse_dates=True)
df = msft.loc[msft['date'] >= '2021-01-01']
max_indexes, _ = emasma_maxmin(df.high)
_, min_indexes = emasma_maxmin(df.low)
maxs, mins = set(max_indexes), set(min_indexes)
# color minmax candlesticks
mco = ['green' if index in maxs else 'red' if index in mins else None
       for index in df.index.values]
# generate vertical lines at minmax
vlines = [pd.to_datetime(str(df['date'][index]))
          for index in df.index.values if index in max_indexes or index in min_indexes]
vline_colors = ['green' if index in maxs else 'red'
                for index in df.index.values if index in max_indexes or index in min_indexes]

df.set_index(pd.DatetimeIndex(df['date']), inplace=True)
mpf.plot(df,
         type='candle',
         marketcolor_overrides=mco,
         vlines=dict(vlines=vlines, linewidths=0.25, colors=vline_colors))

它产生:

enter image description here


请问您能解释一下max_rangesmin_ranges的意思吗?以及在列表中使用-符号并对其进行argsort操作的含义是什么?谢谢! - hieu le

0

正如belisarius所提到的,最好的方法似乎是通过平滑数据来过滤。通过足够的平滑处理,寻找斜率变化应该可以确定局部极小值和极大值(导数在这里会有帮助)。我会使用一个中心滑动窗口进行运行中位数/平均值计算,或者使用持续EMA(或类似的IIR滤波器)。


0

这段 Python 代码可以在范围为5的区间内检测本地极值。df 应该包含 OHLC 列。

df['H_5'], df['L_5'] =  df['H'].shift(-5), df['L'].shift(-5)
df['MAXH5'] = df['H'].rolling(window=5).max()
df['MINL5'] = df['L'].rolling(window=5).min()
df['MAXH_5'] = df['H_5'].rolling(window=5).max()
df['MINL_5'] = df['L_5'].rolling(window=5).min()
df.eval(" maximum5 = (MAXH5==H) & (MAXH_5==H)   ")
df.eval(" minimum5 = (MINL5==L) & (MINL_5==L)   ")
df.eval(" is_extremum_range5 = maximum5 | minimum5  ")

结果在列is_extremum_range5中 = {True| False}


-2

费马定理将帮助您找到局部最小值和最大值。


2
我引用原帖中的话:“有这么多局部最小值和最大值,简单的导数方法行不通。” - Francois G

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接