私と私の猫の他は誰でも隠し事を持っている

野良プログラマ発、日々のアウトプット

PHP Trader 関数を使ってみる - ATR トレーリングストップの巻

忘れた頃にやってくる PHP Trader 関数シリーズ。今回は ATR (真の値幅の移動平均)を算出する trader_atr() を取り上げるのですが、ただ計算させるだけじゃつまらないので計算結果を元に損切りラインを導出して売却指示をさせてみます。所謂「ATR トレーリングストップ」ですね。

ATR トレーリングストップについては言葉はよく聞くのですが、計算ロジックまで明示された情報は少ないのですな。あれこれ調べた結果、保持中銘柄の損切りラインは以下のように算出するようです(間違ってたらすんまそん)。

  • 当該銘柄について、直近の所定日数(10日くらいが定石みたい)の真の値幅平均(以下 ATR)をとる。
  • 当該銘柄について、保持期間中の最高値を得る。
  • 最高値 - ATR × 係数をその日の損切りラインとする(係数は3〜4くらいが定石みたい)。

という訳で以下、サンプルコードです。いつものように k-db.com さんから頂いた今年の日経平均日足データを元に、ATR が算出可能になった日を買付日と想定します。んで、以後の日付毎に「終値損切りラインを割ったら翌日に売却」のルールで指示を出力する、というものです。

<?php
define('ATR_DAYS', 10); // ATR 日数
define('ATR_N', 4); // ATR 係数

// CSV ファイルから株価データを読み込んで、配列に格納
$data = file('indices_I101_1d_2017.csv', FILE_IGNORE_NEW_LINES);

//見出し行を省く
unset($data[0]);

// 各行のカンマ区切りを配列に分割
$data = array_map(function($item){ return explode(',', $item); }, $data);

// 日付昇順でソート
usort($data, function($a, $b){ return ($a[0] < $b[0]) ? -1 : 1; });

// ATR 算出
$atrs = trader_atr(
	array_column($data, 2), // 高値
	array_column($data, 3), // 安値
	array_column($data, 4), // 終値
	ATR_DAYS
);

// トレーリングストップによる売買指示を出力
$highest = 0;
$losscutLine = null;
echo "日付,最高値,終値,損切りライン,指示\n";
foreach ($atrs as $idx => $atr) {
	$price = $data[$idx];
	$highest = max($highest, $price[2]); // 最高値更新
	$losscutLine = round($highest - $atr * ATR_N, 2); // 損切りライン算出
	echo implode(",", [
		$price[0],
		$highest,
		$price[4],
		$losscutLine,
		$price[4] < $losscutLine ? '売却' : '保持'
	]) . "\n";
}

実行結果はこんな感じ↓。

日付,最高値,終値,損切りライン,指示
2017-01-19,19122.39,19072.25,18271.88,保持
2017-01-20,19176.86,19137.91,18356.74,保持
2017-01-23,19176.86,18891.03,18335.46,保持
2017-01-24,19176.86,18787.99,18366.58,保持
2017-01-25,19176.86,19057.5,18309.53,保持
2017-01-26,19405.23,19402.39,18485.54,保持
2017-01-27,19486.68,19467.4,18625.24,保持
2017-01-30,19486.68,19368.85,18642.75,保持
2017-01-31,19486.68,19041.34,18596.14,保持
2017-02-01,19486.68,19148.08,18589.35,保持
(中略)
2017-04-03,19668.01,18983.23,18884.87,保持
2017-04-04,19668.01,18810.25,18851.34,売却
2017-04-05,19668.01,18861.27,18865.81,売却
2017-04-06,19668.01,18597.06,18814.58,売却
2017-04-07,19668.01,18664.63,18792.61,売却
2017-04-10,19668.01,18797.88,18805.68,売却
(後略)

2ヶ月半ほど保持の指示が続いた後で、4月4日に売却指示が出ました。1月19日に始値(19,082.83円)で買付して、売却指示翌日の4月5日の始値(18,900.70円)で処分したとして、約1%の損失です。実際は銘柄毎に直近期間をバックテストして係数をフィッティングするようなので、お仕着せな係数ではまぁこんなもんかも知れません。

ちなみにこの trader_atr() 関数ですが、計算結果が単純移動平均では無さそうなのでソースコードを確認してみたのですが、平均値 = (前日の平均値 × (日数 - 1) + 当日の値幅) / 日数、という加重計算がされているようです。この関数では trader_ma() のように移動平均の種類が指定できる訳ではないので、その辺が不満な場合は trader_tr() 関数で真の値幅を得てから好きな方式の平均値を求めるのが良いかと。