2025-01-13

【備忘録】On-Balance Volume (OBV)

昨年 8 月 5 日の東京市場の歴史的な暴落を経験して、今さらながら先達が開発したテクニカル分析の手法を、自分でもプログラムを作って分析できるようにしようと思い、いろいろ調べています。今回は OBV です。

OBV は、各日の総出来高に、その日の価格が高いか低いかによって、プラスまたはマイナスの値が割り当てられます。具体的には、当日の終値が前日の終値より高ければその日の出来高はプラス、低ければマイナスとして前日の OBV に加えて算出します(マイナスの場合は引き算)。

OBV は、一般的に値動きを確認するために利用されます。定義は単純で下記の通りです。(数式表現に MathJax を利用しているため、スマホのブラウザなどでの閲覧では意図したとおりに表示されない場合があります。)

\[ \begin{aligned} &\text{OBV} = \text{OBV}_{prev} + \begin{cases} \text{volume,} & \text{if close} > \text{close}_{prev} \\ \text{0,} & \text{if close} = \text{close}_{prev} \\ -\text{volume,} & \text{if close} < \text{close}_{prev} \\ \end{cases} \\ \end{aligned} \]

この指標は、Joseph Granville 氏が 1963 年に出版した著書 Granville's New Key to Stock Market Profits で On-Balance Volume と名付けて紹介して普及させました。日本では「累積騰落出来高」とも呼ばれているようです。

Wikipedia より引用・編集、追記

下記の OS 環境で動作確認をしています。

Fedora Linux 41 Workstation x86_64
Python 3.13.1
jupyterlab 4.3.4
matplotlib 3.10.0
mplfinance 0.12.10b0
yfinance 0.2.51

以下の作業は JupyterLab 上でおこなっています。

まず、必要なパッケージをインポートして、yfinance でサンプルとする銘柄の日足データを3ヶ月分取得します。

import matplotlib.pyplot as plt
import mplfinance as mpf
import re
import yfinance as yf

pattern = re.compile(r'(.+)\.T')
#pattern = re.compile(r'\^(.+)')

symbol = '8306.T'
ticker = yf.Ticker(symbol)

df = ticker.history(period='3mo')
df.head()

 

次に OBV を計算ですが、一行一行算出する処理に流用できるようにしたかったので、Pandas らしくない愚直なコーディングをしています。

def calc_obv(r):
    if r == 0:
        return 0

    obv_prev = list_obv[r - 1]
    close_prev = df.iloc[r - 1]['Close']
    close_curr = df.iloc[r]['Close']
    volume_curr = df.iloc[r]['Volume']

    if close_prev < close_curr:
        v_sign = +1
    elif close_prev > close_curr:
        v_sign = -1
    else:
        v_sign = 0

    return obv_prev + volume_curr * v_sign


list_obv = list()
for r in range(len(df)):
    list_obv.append(calc_obv(r))

最後に mplfinance によるプロットです。ローソク足チャートをプロットするために mplfiance を利用していますが、matplotlib から細かい調節をしたいので、mplfinance だけで済ませようとせずに下記のような回りくどい書き方をしています。

plt.rcParams["font.size"] = 16
fig = plt.figure(figsize=(12, 6))

ax = dict()
n = 3
gs = fig.add_gridspec(
    n, 1,
    wspace=0.0, hspace=0.0,
    height_ratios=[2 if i == 0 else 1 for i in range(n)]
)
for i, axis in enumerate(gs.subplots(sharex='col')):
    ax[i] = axis
    ax[i].grid()

apds  = [
    mpf.make_addplot(
        list_obv,
        width=1,
        color='magenta',
        ax=ax[2]
    ),
] 

mpf.plot(
    df,
    type='candle',
    style='default',
    volume=ax[1],
    datetime_format='%m/%d',
    addplot=apds,
    xrotation=0,
    ax=ax[0],
)

ax[2].set_ylabel('OBV')

try:
    ax[0].set_title('Daily chart for %s (%s)' % (ticker.info['longName'], symbol))
except KeyError:
    ax[0].set_title('Daily chart for %s' % symbol)

plt.tight_layout()

m = pattern.match(symbol)
if m:
    plt.savefig('daily_chart_%s.png' % m.group(1))
else:
    plt.savefig('daily_chart_%s.png' % symbol)

plt.show()

大局的なトレンド、言い換えれば、離れた日の間でこの指標の絶対値の比較をするのは意味がなさそうです。そうではなくて、その時々における価格の動きの方向性あるいは売買の勢いを確認するのに向いているように思います。

関連サイト

  1. bitWalk's: 【備忘録】パラボリック SAR [2024-09-06]
  2. bitWalk's: 【備忘録】モメンタムとボリンジャーバンド [2024-08-10]
  3. bitWalk's: 【備忘録】フィボナッチ・リトレースメント [2024-08-12]
  4. bitWalk's: 【備忘録】一目均衡表 [2024-08-15]
  5. bitWalk's: 【備忘録】Wilder の RSI [2024-09-05]

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加