Pythonによるポイントアンドフィギュアの算出

# はじめに

## 回想録

投資に使われる数あるテクニカル指標のうち、一風変わったものがポイントアンドフィギュア(P&F)である。
FX投資をやっていた十年以上前に自作でPythonの算出プログラムを書いて毎朝メールで通貨ペアのP&Fを送るようにしていた。

ところが2008年に起きたリーマンショックの前にすべての金融工学やテクニカル分析が無力になり私も痛い目にあった。
その結果FXから撤退し、テクニカル分析もやらなくなった。

## そして今

投資自体はやめたわけではなく、あれから数年経ってもテクニカル分析に対する信頼性は大きく変わってはいないけど、勘でやれるほど甘くはないため再度勉強しながら取り組もうとしている。

P&Fに関しては作図はできても内容を汲み取ることができておらず宝の持ち腐れ状態だった。
そして改めて使おうと思ったが自作プログラムがどこかわからず、スクラッチから作り直そうとしたがまあまあ面倒くさい。
なので世に出回っているものを探してみた。

アカウント作成やAPIキーの取得など要らずに利用できる[stockcharts.com](https://stockcharts.com/freecharts/)は様々な銘柄、指数、通貨ペアのチャートを提供しており重宝する。
そしてP&Fを取り扱っている数少ないサイトの一つでもある。

## stockchartsで満足できないところ

致命傷に近いのが全世界の銘柄を対象にしていないつまり日本株はないということ。
それ以外は概ね満足しているのだけど細かいカスタマイズができないのが不満点かな。
あと、算出結果の信頼性というか正確さというか、他の技法と照らし合わせないとなんともいえない。

## 長い前置きだったが

ということでPythonのライブラリを[PyPi](https://pypi.org/search/?q=%22point+and+figure%22)で検索すると2,3見つかったのでそれを試してみることにした。

# Pythonによる実装

上記検索結果から以下が該当するライブラリかなと思われる。

* [pviglucci/pypf](https://github.com/pviglucci/pypf)
* [swaschke/pypnf](https://github.com/swaschke/pypnf)
* [Financial\-Graph\-Lib](https://github.com/kUNWAR-DIVYANSHU/Financial-Graph-Lib)

## PyPf

まずはPyPf。
Githubにあるドキュメントを参照してApple Incの株価でやってみる。

```python
from pypf.chart import PFChart
from pypf.instrument import YahooSecurity

i = YahooSecurity('AAPL')
c = PFChart(i)
c.create_chart()
```
結果

```shell
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
(途中略)
    raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
KeyError: "name='B', domain=None, path=None"
```

なんかよくわからないエラーが出たのでやめ。

## PyPnf

結論からいうとこちらのライブラリでうまくできたので採用することにした。

一般的にはP&Fのパラメータとしては以下の3つがある。

1. 値幅(`boxsize`)
    * 株価に依存するため株価ごとに設定するか、株価から自動算出する関数を定義
2. 何枠で転換するか(`reverse`)
    * 3枠が一般的
3. 算出手法(`method`)
    * 終値または高値/安値を使う
    * 今回は終値を採用

PyPnfのパラメータとしては以下のようにした(`scaling`についてはよくわからなかったが`abs`を採用)。

```python
# 枠転換
reversal = 3

# 算出基準 (cl:終値, h/l:高値安値, その他hlc, ohlc, l/h)
method = 'cl'

# スケール  ('abs', 'log', 'cla')
scaling = 'abs'
```

そして実際には以下のように使う(`ts`は時系列データ)。

```python
pnf = PointFigureChart(ts=ts, method=method, reversal=reversal, boxsize=boxsize, scaling=scaling)
print(pnf)
```

結果(2022/5/24終値 Alphabet Inc.)

```shell
Alphabet Inc. (GOOGL)

Point & Figure (abs|cl) 50 x 3
----  -  -  -  -  -  -  -  -  ----
2950  .  .  X  .  X  .  .  .  2950
2900  X  .  X  O  X  O  .  .  2900
2850  X  O  X  O  X  O  X  .  2850
2800  X  O  X  O  X  O  X  O  2800
2750  X  O  X  O  X  O  X  O  2750
2700  X  O  .  O  X  O  X  O  2700
2650  X  .  .  O  X  O  X  O  2650
2600  X  .  .  O  X  O  X  O  2600
2550  X  .  .  O  .  O  .  O  2550
2500  X  .  .  .  .  .  .  O  2500
2450  X  .  .  .  .  .  .  O  2450
2400  X  .  .  .  .  .  .  O  2400
2350  X  .  .  .  .  .  .  O  2350
2300  .  .  .  .  .  .  .  O  2300
2250  .  .  .  .  .  .  .  O  2250
2200  .  .  .  .  .  .  .  O  2200
----  -  -  -  -  -  -  -  -  ----
```

Google Colaboratoryによる全ソースと実行結果は以下のリンクを参照。

https://github.com/higebobo/my-colab/blob/main/pypnf_sample.ipynb

> 画面上部にある`Open in Colab`と書かれたバッジをクリックするとGoogleアカウントがあればColabにコピーされると思う。
> 時系列データをGoogleドライブに保存するオプションなどあるので興味あれば試してもらいたい。

ちなみにstockchartsのチャートは以下(リンクは[こちら](https://stockcharts.com/freecharts/pnf.php?c=GOOGL,PWUADANCNON[P][D][F1!3!50.0!!2!20]))。
銘柄によってはPyPnfと誤差が出る場合があるがGOOGLに関してはほぼ一致している。

![GOOGL.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691494/d28b1bff-ba05-3788-3d6f-fdec48e5ca48.png)

ただしデフォルトの設定ではこのようにならない。
PyPnfで定義したものと合わせるために以下のような設定をしている(丸で囲んだところが変更点)。

![GOOGL2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691494/262fd663-32b1-8ffb-8c07-d26af8c1ad49.png)

### チャート考察

弱気混合型のトリプルボトム(参考 [ポイントアンドフィギュア | 楽天証券](https://www.rakuten-sec.co.jp/MarketSpeedFX/onLineHelp/info/sp_chart/pf.html))と判断できる。

上値(青)と下値(赤)のバランスラインを描いてみると

![GOOGL3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691494/6c3c06e1-6610-71e4-bcb1-a7ae9543599f.png)

支持線の2550ドルを突き破ってブレイクアウトしているので下値方向を目指していると判断するのが自然かと思う。

### 予想株価

**(免責)**
**本記事で書く内容はあくまでも手法の紹介であり、投資を推奨したりするものではない。**
**この結果に基づいて判断した各個人の投資についての責任を私は一切負わない。**

水平カウンティングで目標株価を計算するには

```
目標株価 = 起点の価格 - 保合列数 * 枠転換数 * ボックス幅
```

となるのだが、保合列数をどう解釈するが本や説明サイトによってまちまちである。

[Point and figureを使い価格のターゲットを算出する方法 \| OANDA FX/CFD Lab\-education（オアンダ ラボ）](https://www.oanda.jp/lab-education/blog_30strategy/1700/#:~:text=%E6%96%B9%E6%B3%95%E3%81%8C%E3%81%82%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82-,%E6%B0%B4%E5%B9%B3%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0,%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%8B%E3%81%A8%E3%81%84%E3%81%86%E3%82%82%E3%81%AE%E3%81%A7%E3%81%99%E3%80%82&text=%E3%81%A4%E3%81%BE%E3%82%8A%E4%BF%9D%E3%81%A1%E5%90%88%E3%81%A3%E3%81%9F%E5%88%97,%E6%9C%9F%E5%BE%85%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%A8%E3%81%84%E3%81%86%E3%82%82%E3%81%AE%E3%81%A7%E3%81%99%E3%80%82)の図を見て解釈した場合3列かと思うが、4列に見えなくもない。

3列とした場合

```
2800 - 3 * 3 * 50 = 2350
```

4列とした場合

```
2800 - 4 * 3 * 50 = 2200
```

どちらで解釈しても目標株価は達成している。
そのためこの先はこのチャートだけではわからない。
このまま突き進むかもしれないし目標値に達したから反転するかもしれない(反転しても2750ドル付近で頭打ちになるか、あるいブレイクアウトして再び強気相場に変わるか)。
ただ右肩下がりになっているので現時点では弱気相場であることは理解できる。

ちなみに対象期間を半年前にずらして描画してみると

結果(2021/11/23終値 Alphabet Inc.)

```
Point & Figure (abs|cl) 50 x 3
----  -  -  -  ----
2950  .  .  X  2950
2900  X  .  X  2900
2850  X  O  X  2850
2800  X  O  X  2800
2750  X  O  X  2750
2700  X  O  .  2700
2650  X  .  .  2650
2600  X  .  .  2600
2550  X  .  .  2550
2500  X  .  .  2500
2450  X  .  .  2450
2400  X  .  .  2400
2350  X  .  .  2350
2300  X  .  .  2300
2250  X  .  .  2250
2200  X  .  .  2200
2150  X  .  .  2150
2100  X  .  .  2100
2050  X  .  .  2050
2000  X  .  .  2000
1950  X  .  .  1950
1900  X  .  .  1900
1850  X  .  .  1850
1800  X  .  .  1800
1750  X  .  .  1750
----  -  -  -  ----
printed 3/3 columns.
```

先に表示したチャートの真ん中あたりの保合状態に入る前の状態だが天にも昇りそうなイケイケ相場だった。
半年経つとこうも変わるものか。

このように対象期間とか自由にカスタマイズできるのがPythonを使う場合の利点だ。
P&Fに限らないがリアルタイムチャートを眺めると自分の都合のよい方向に動くように見えるが実際に過去を振り返ってみると客観的になれる。

## Financial-Graph-Lib

最後はこのライブラリ。
これも一応試してみたもののP&Fの実装に関してはやや中途半端な感が否めないため深く追求するのはやめた。

## PyPfにリベンジ

一旦撤退したPyPfだが気になったので再チャレンジしてみることにした。

ソースを眺めているとPyPfは時系列データを取得する機能とチャートを描画する機能がある。

* [instrument\.py](https://github.com/pviglucci/pypf/blob/master/pypf/instrument.py): 時系列データの取得
* [chart\.py](https://github.com/pviglucci/pypf/blob/master/pypf/chart.py): チャートの描画

エラーをこいていたのは前者だった。
データの取得元であるプロバイダとしてYahooかGoogleを指定できるのだけどもどちらも仕様が変わってプログラムが対応できなくなったと思われる。
で、あれば時系列データを取得する部分だけ実装すればよい。

そこで`Instrument`クラスを継承して独自クラス`NewYahooSecurity`を定義した(細かいオプションは無視)。

```python
class NewYahooSecurity(Instrument):
    def __init__(self, symbol, force_cache=False):
        super().__init__(symbol=symbol, period=10)

    def populate_data(self):
        self.daily_historical_data = OrderedDict()

        end = datetime.date.today()
        start = end - datetime.timedelta(weeks=self.period*52)
        df = yf.download(symbol, start=start.strftime('%Y-%m-%d'), end=end.strftime('%Y-%m-%d'))
        q = Decimal('0.01')

        for index, row in df.iterrows():
            row['Date'] = index.strftime('%Y-%m-%d')
            row['Open'] = Decimal(row['Open']).quantize(q)
            row['High'] = Decimal(row['High']).quantize(q)
            row['Low'] = Decimal(row['Low']).quantize(q)
            row['Close'] = Decimal(row['Close']).quantize(q)
            row['Volume'] = int(row['Volume'])
            self.daily_historical_data[row['Date']] = row

        self.historical_data = self.daily_historical_data
```

使い方

```python
# 時系列データの取得
instrument = NewYahooSecurity(symbol, period)

# チャートの描画
chart = PFChart(instrument, box_size=box_size, method=method, reversal=reversal)
chart.create_chart()

print(chart.chart)
```

結果(2022/5/24終値 Alpabet Inc.)

```
[*********************100%***********************]  1 of 1 completed

  GOOGL  (2022-05-23 o: 2191.75 h: 2229.76 l: 2229.76 c: 2229.76)
  1.00% box, 3 box reversal, c method
  signal: sell status: bear confirmed

3016.49|                                                    |3016.49
2986.62|              x                                     |2986.62
2957.05|          x   u d x   x   x                         |2957.05
2927.78|          x B u d u d u 1 x d                       |2927.78
2898.79|  9       u d u d u d u d x d                       |2898.79
2870.09|  x d     u d   d C d u d x d                       |2870.09
2841.67|  x d x   u     o   d   d x o         x             |2841.67
2813.54|  x d x d u             d x o         x 4           |2813.54
2785.68|  x d x d u             d x o         x d           |2785.68
2758.10|  x o u d               d x o         x d           |2758.10
2730.79|  8 o u                 o 2 o         x o           |2730.79
2703.75|  x o A                 o x o         x o           |2703.75
2676.98|  x o                   o x o x       x o           |2676.98
2650.48|  x                     o u o u 3 u   u o           |2650.48
2624.23|  x                     o u o u d u d u o           |2624.23
2598.25|  x                     o u o u d u d u o           |2598.25
2572.53|  x                     o u o   d u d u o           |2572.53
2547.06|  x                     o       d   d u o           |2547.06
2521.84|  x                                 d   o           |2521.84
2496.87|  x                                     o           |2496.87
2472.15|  7                                     o           |2472.15
2447.67|  x                                     o           |2447.67
2423.44|  x                                     o u         |2423.44
2399.44|  6                                     o u d       |2399.44
2375.69|  x                                     o u d       |2375.69
2352.16|  x                                     o u d       |2352.16
2328.88|                                        o u o u     |2328.88
2305.82|                                        o 5 o u d   |2305.82
2282.99|                                        o   o u d   |2282.99
2260.38|                                            o   d   |2260.38
2238.00|                                                d   |<< 2229.76
2215.84|                                                o   |2215.84
2193.91|                                                o   |2193.91
2172.18|                                                    |2172.18  
```

うん、なんか欲しいものとちょっと違う。ボックス幅が％で設定するようになっていて整数でない。
その部分を修正しようかと思ったけどまあいいや。あきらめよう。

一応、上記の全ソースと実行結果はこちらにアップしておく。

https://github.com/higebobo/my-colab/blob/main/pypf_sample.ipynb

# 最後に

「最強」とか「勝者のチャート」とかいろいろ謳い文句のあるポイント＆フィギュアだがその効果をまだ体験できていない。
今回は終値を用いたが高値安値を推奨しているところも見かけるし、これを変えるとチャートも割と変わる。
他の指標と合わせてもう少し勉強して活用できるようになりたい。