🙄

前日計算結果に依存しないSQLでの指数平滑移動平均(EMA)計算

2023/02/06に公開


持続的に株価指標を計算していくために通常とは少し異なる設計をした備忘です。私にとって実用可能かが焦点であり数学的正しさや正確さは不明です。

指数平滑移動平均(EMA)とは

指数平滑移動平均線の計算方法や他の移動平均線との違いを紹介より引用

指数平滑移動平均線は全てのデータを計算対象とするもので、古いデータが計算から外れることなく、常に新しいデータを加えながら計算するのが特徴です。

指数平滑移動平均線の最初の値は、単純移動平均と同じ計算、つまりn日間の終値の合計をnで割って求めます。そして2日目以降は、前日EMA+平滑化定数α×(当日終値-前日EMA)で求めます。なお、平滑化定数α=2÷(n+1)です。

計算式をより単純化すると、前日EMAにn-1を掛けた値と、当日の終値を2倍にした値を合計し、それをn+1で割るということです。つまり、前日EMAと当日の終値のデータにより計算できます。

全てのデータを計算に利用するという性質上、自分の見ているEMAと他者が計算したEMAとで値が異なる可能性があるということです。

前日計算結果に依存しない計算

要件

  • 過去に計算した計算結果を参照したくない
    • 別のデータ群にのみ依存するようにして、誤りが含まれた場合の影響範囲を限定したい
    • 複数日のEMAを並列的に一括で計算したい
  • 計算に全データを用いたくない
    • 計算オーダーにデータ量依存を含みたくない
  • Trading View等のツール上の値と大きな違いがない状態にしたい

検討内容

  • EMAは特性上過去の値ほど計算結果に与える影響力が小さくなっていく
  • ある時点の値の影響力をw_tとすると、直感により影響力減衰のイメージをw_t = w^tの近似と仮定して、さらに\lim_{t\to-\infty} w_t = 0と仮定
  • 仮定が正しければ、計算したい時点から一定期間より過去の値は無視できるほど影響力が小さい
  • そのため、ある時点の計算に使用するデータを一定期間のみに限ったとしても大きな違いがない計算結果が得られるはず

実装

-- postgresql
span := 20; -- 計算したいEMA 12日EMAであれば12
cumu_period := span * 5; -- 計算に使用する"一定期間"日数
target_date := '2022-12-12'::date; -- EMAを計算したい市場日付
source_date := '2022-07-21'::date; -- cumu_period分だけ前の市場日付
----------------
with
  recursive w_calc_ema as (
    select
      cumu_period as cumulate_span,
      t3.ticker_symbol,
      t3.market_date,
      t3.close_value,
      avg(t3.close_value) over(partition by t3.ticker_symbol order by t3.market_date asc rows between cumu_period+span-1 preceding and cumu_period preceding) as technical_value,
      count(*) over(partition by t3.ticker_symbol order by t3.market_date asc rows between cumu_period+span-1 preceding and cumu_period preceding) as count_valid
    from
      daily_historical t3
    where
      t3.ticker_symbol = 'ZZZ'
      and t3.market_date >= source_date
    union all
    select
      t4.cumulate_span-1 as cumulate_span,
      t4.ticker_symbol,
      t4.market_date,
      t4.close_value,
      (t4.technical_value*(span-1)+(lag(t4.close_value,t4.cumulate_span-1) over(partition by t4.ticker_symbol order by t4.market_date asc))*2)/(span+1) as technical_value,
      t4.count_valid
    from -- e=yesterday tech_val, l=span, c=close: today tech_val = e*(1-(2/l+1))+c*(2/l+1) = (e*(l-1)+c*2)/(l+1)
      w_calc_ema t4
    where
      t4.cumulate_span > 0
  )
select
  'EMA' as technical_name,
  t5.ticker_symbol,
  t5.market_date,
  t5.technical_value
from
  w_calc_ema t5
where
  t5.cumulate_span = 0
  and t5.market_date = target_date
  and t5.count_valid = span
  and t5.technical_value is not null
;

検証結果

計算に使用する"一定期間"を固定すると、EMA日数が増えるにつれて計算結果に無視できない違いが出てくることを確認した。そのため上記コードには適用済みだが、EMA日数に応じて"一定期間"を線形に増加するようにしたところ概ね実用に耐える値となった。
ただ、それでもEMA日数を多くするにつれて計算結果に違いが出てくる傾向が見受けられた。私の環境では26日EMAまでしか使用しないため、上記コードの内容で実用に耐えると判断した。

実際の運用

S&P500のMACD導出に上記コードと同様のコードで導出したEMA値を使用していますが、MACDも問題なく導出,使用できています。

Discussion