わたねこコーリング

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

CloudWatch コンソールログイン通知でメールが来ない件

小ハマり系小ネタでつ。先日 AWS セキュリティ強化の一環で今さらですが、コンソールログインのメール通知を

dev.classmethod.jp

を読んで実施したのですが、ログインしてみてもさっぱりメールが来ない。CloudTrail にはちゃんとイベントが記録されているのに… と、イベントをよく見たらリージョンが us-east-1 となっている?

f:id:mariyudu:20181115153633p:plain:w500

どゆこと? と、「ConsoleLogin イベント リージョン」あたりでぐぐるとまたもクラスメソッドさんのページがヒット(助かってますぅ)。

dev.classmethod.jp

これによるとコンソールのリージョン設定やサインイン時の URL に依存するようです。当方コンソールは東京にセットしてありますが、サインイン URL を https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home にしても https://s3.console.aws.amazon.com/s3/home にしても CloudTrail にはリージョンは全て us-east-1 で記録されてます。うーむ…

結局、理由はよく分からないまま、メール通知設定での SNS トピック作成および CloudWatch ルール作成の時だけリージョンをバージニア北部に変えてやりなおしたところ、無事メールが届くようになりましたとさ。ちゃんちゃん。

気象庁の各地気圧データから箱ひげ図を描いてみる

【注意】この記事に掲載したプログラムコードは、最新の matplotlib では正常動作しない箇所があります。詳しくは下記記事を参照して下さい。

mariyudu.hatenablog.com


昨年末あたりから Jupyter Notebook を触り始めてすっかりハマってます。Python の豊富な DS ライブラリを使ってのデータ加工・集計・分析・可視化等を Web U/I 上でトライ&エラーできる柔軟さが素晴らしすぐる。発想にまかせて自由気ままにデータいじりするにはまだまだスキル不足なので、何かしらお題を設けてはアウトプットするルーティンワークを続けていこうかと。今回はその一環で、気象庁サイトから日本各地の1年間の気圧データ(CSV)から、月単位の箱ひげ図を作成して、各地毎の気圧変動特性を概観してみようという試み。

気圧データ取得は、気象庁過去の気象データ・ダウンロードにて、下記要領で。

  1. 「地点を選ぶ」にて、地図上から目的の都市をクリックしてひとつ選択。
  2. 「項目を選ぶ」にて、データの種類=時別値・項目=現地気圧、と選択。
  3. 「期間を選ぶ」にて、連続した期間で表示する(2017年1月1日〜2017年12月31日)と選択。
  4. CSV ファイルをダウンロード」をクリック。

今回は、札幌・仙台・東京・名古屋・大阪・松山・福岡・那覇の8地点をそれぞれデータ取得しました。それを Jupyter からアップロードして、下記コードを実行!

import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

# データファイル
datafiles = ['kiatsu-sapporo.csv', 'kiatsu-sendai.csv', 'kiatsu-tokyo.csv', 'kiatsu-nagoya.csv', 'kiatsu-osaka.csv', 'kiatsu-matsuyama.csv', 'kiatsu-fukuoka.csv', 'kiatsu-naha.csv']

# データファイル数に合わせた大きさのグラフ領域を作成
rows = len(datafiles)
fig = plt.figure(figsize=(8,3*rows), dpi=100)
yrange = [960, 1040]

for (n, datafile) in enumerate(datafiles):
    # CSV を読み出してデータフレーム作成(1列目を datetime 型のインデックスにするのがミソ)
    df = pd.read_csv(datafile, skiprows=5, header=None, names=['datetime', 'pressure','dmy1','dmy2'], index_col=0, parse_dates=True)
    datas = []
    # 月毎の2次元配列に纏める
    for m in range(1, 13):
        datas.append(df[df.index.month == m].pressure)
    # 箱ひげ図プロット
    ax = fig.add_subplot(rows, 1, n+1)
    bp = ax.boxplot(datas, whis='range') # 外れ値描画なしで
    plt.title(datafile)
    plt.grid()
    plt.subplots_adjust(hspace=0.4)
    plt.ylim(yrange)

結果、作画した箱ひげ図はこんな感じ↓。matplotlib の boxplot() はデフォルトだと、四分位範囲 ✕ 1.5 外の値を外れ値にするようなのですが、今回のデータは偶然誤差の類を考慮しなくても良いかなと思い、ひげ長をレンジ全体にしました。

家族に頭痛持ちがいて低気圧の日は辛そうなので、日本のどの辺が気圧変動が穏やかなのかなー、と思ったのが動機でした。九州以北は概ね冬季の変動が大きいのに較べて沖縄は比較的穏やかそう。どこも10月にレンジが広いのは台風のせいですが、それでも東京が沖縄より振れ幅が大きいのは意外でした。

さーて次は何をやろうかな…

コンパクトな PHP テンプレートエンジン、Latte を使ってみた

Yet Another PHP Template Engine な話。とある PHP システムを作っていて、動的に与えられるテンプレート構文にデータを適用させて、断片的な HTML コードを生成させたい、という要件がありまして、

  • Twig や Blade だと大袈裟というか、かさばりそうなのでなるだけコンパクトなやつ
  • ちゃんとメンテされてて PHP7 でも安心して使えそうなの
  • コードがモダンで、PHP5.4 未満な残滓が無いと嬉しい

なテンプレートエンジンを探してたら Latte という OSS プロダクトを発見。どうやらこれ、Nette という MVC フレームワークの構成ライブラリですが、単体でも問題なく使えるようです。

Latte - amazing template engine for PHP | Nette Framework

で、上記ページを読みながら評価してみました。インストールは compser いっぱつ。

composer require latte/latte

テスト用にこんなスクリプトを書いてみたです(↓)。

<?php
require 'vendor/autoload.php';

$duos = [
	'Simon & Garfunkel' => ['Paul Simon', 'Art Garfunkel'],
	'Peter & Gordon' => ['Peter Assher', 'Gordon Waller'],
];

$template = '<dl n:foreach="$duos as $name => $duo">
	<dt>{$name}</dt>
	<dd>
		<ul>
			<li n:foreach="$duo as $member">{$member}</li>
		</ul>
	</dd>
</dl>';

$latte = new Latte\Engine;
$latte->setLoader(new Latte\Loaders\StringLoader([
	'main' => $template
]));
$latte->render('main', ['duos' => $duos]);

はい、ちゃんとレンダリングされました(↓)。

<dl>
	<dt>Simon &amp; Garfunkel</dt>
	<dd>
		<ul>
			<li>Paul Simon</li>
			<li>Art Garfunkel</li>
		</ul>
	</dd>
</dl><dl>
	<dt>Peter &amp; Gordon</dt>
	<dd>
		<ul>
			<li>Peter Assher</li>
			<li>Gordon Waller</li>
		</ul>
	</dd>
</dl>

デフォルトで HTML エンティティ変換されてるのがイイですね。テンプレートでおや?と思うのが、Vue.js テンプレートのディレクティブみたいな書き方ができること。これを Latte ではマクロと呼ぶようです。分岐やループは

<?php
$template = '{foreach $duos as $name => $duo}
	<dl>{$name}</dl>
	<dd>
		<ul>
			{foreach $duo as $member}
				<li>{$member}</li>
			{/foreach}
		</ul>
	</dd>
{/foreach}';

のように Twig っぽい書き方もできるのすが、マクロを使うことでよりコンパクトなテンプレートが書けますね。

それから Latte の特色として、コンテキスト・アウェアネス指向な件。要は、レンダリングの際に埋め込み場所に応じたセキュリティ対策が考慮されている、ということらしいです。

<?php
$latte->setLoader(new Latte\Loaders\StringLoader([
	'main' => '<p>param is {$param}</p>
<a href="{$param}">Click Here</a>'
]));
$latte->render('main', ['param' => "localtion.href='http://google.com'"]);

を実行すると、

<p>param is localtion.href='http://google.com'</p>
<a href="">Click Here</a>

となり、p タグ内には展開される文字列が、a タグの href 属性内ではオミットされて、スクリプトインジェクションを抑止できます。どうですか? 自分はカナーリ気に入りました、Latte。

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() 関数で真の値幅を得てから好きな方式の平均値を求めるのが良いかと。

「ジブリの呪い」を検証してみた

先日こんなツイート↓

で知ったのですが、投資家筋では有名な「ジブリの呪い」てなモノがあるそうですな。この類の事象をアノマリーと言うらしく、昨年読んだこの本(↓)でも各種アノマリーの検証例が載っており興味深かったです。

ロボット運用のプロが分析してわかった 最強の株式投資法

ロボット運用のプロが分析してわかった 最強の株式投資法

そこで、これに倣い「ジブリの呪い」を自分なりに分析してみようと思います。放映日を挟んで日経平均を信用売買して下落分の利益を狙うという方針で、何処を売買日に選んだら最も利益が見込めるのかをプログラムで集計してみます。仕様はざっくりと

  • 上記ツイート中にあるページに掲載されている放映日で、2000年以降を対象に(それ以前の日経平均データ持ってないので)。
  • 売付日は放映日とその前の営業日5日間を、買付日は放映日とその後の営業日5日間を対象に。
  • 上記売買の全組み合わせ毎にプロフィット・ファクター(総利益÷総損失、以下PF)を集計し、最大となる売買日を探す。
  • 売買は終値で。手数料とかは無視。

てな感じで。以下、得られた結果から抜粋した PF ベスト3です。

売付日 買付日 PF 勝率
0 3 1.39 53.1%
0 2 1.38 49.6%
-1 3 1.26 50.4%

つまり放映日に空売りして、その3営業日後に買い戻すのが最も良さそうです。が、PF・勝率いずれもこの数字では殆ど有意性を感じられないですね… いちおう Excel で結果の可視化もしてみます。

f:id:mariyudu:20170209214633p:plain


さて、よく調べるとこの「ジブリの呪い」、米雇用統計発表と重なると魔力を発するらしいです。という訳で、放映日を第一金曜日のものに絞って(26件ありました)再計算してみると…

売付日 買付日 PF 勝率
0 3 2.15 61.5%
-1 3 2.05 53.8%
-3 3 1.88 61.5%

f:id:mariyudu:20170209214852p:plain

ほほう、なんだかそれっぽい結果になりました。放映日か直前あたりで売り・3営業日後あたりで買うのが好成績という傾向は共通してるみたい。これならちょっと試してみようかな、という気になってしまいますw 蛇足ですが、本記事は過去のデータを集計しただけのものであり、今後を予測するものではありません。投資はあくまで自己判断・自己責任で!

PHP Trader 関数を使ってみる - ボリンジャーバンドの巻

前回の続きで、今回は trader_bbands 関数ボリンジャーバンドを算出してみます。ボリンジャーバンドは、移動平均標準偏差を重ね合わせただけの指標ですが、直観的だしトレンドやボラティリティがひと目で分かって便利なので、自分もよく使っています。

trader_bbands のコーリングシーケンスは、

trader_bbands(<株価が格納された1次元の配列>, <移動平均日数>, <+σの係数>, <-σの係数>, <移動平均の種類>)

です。株式相場では通常、25日の単純移動平均を元に計算するようです。返り値は +Nσ値・移動平均・-Nσ値が格納された2次元配列です。

それでは、前回同様、k-db.com さんから頂いた今年の日経平均日足データを元にボリンジャーバンドを算出して表示するというシナリオのサンプルプログラムです。

<?php
// CSV ファイルから株価データを読み込んで、配列に格納
$data = file('indices_I101_1d_2016.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; });
// 終値だけ収集(兼実数変換)
$closePrices = array_map(function($item){ return +$item[4]; }, $data);
// 25日単純移動平均に基づくボリンジャーバンドを計算
$bbands = trader_bbands($closePrices, 25, 1, 1, TRADER_MA_TYPE_SMA);
// CSV 形式で整形・出力
echo "日付,株価,25日SMA,+1σ,-1σ\n";
foreach ($data as $n => $data) {
	echo implode(",", [
		$data[0],
		$closePrices[$n],
		@$bbands[1][$n],
		@$bbands[0][$n],
		@$bbands[2][$n],
	]) . "\n";
}

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

日付,株価,,25日SMA,+1σ,-1σ
2016-01-04,18450.98,,,
2016-01-05,18374,,,
2016-01-06,18191.32,,,
(中略)
2016-02-03,17191.25,,,
2016-02-04,17044.99,,,
2016-02-05,16819.59,,,
2016-02-08,17004.3,17296.788,17863.05,16730.525
2016-02-09,16085.44,17202.166,17765.29,16639.042
2016-02-10,15713.39,17095.742,17678.419,16513.065
(後略)

PHP Trader 関数を使ってみる - 移動平均の巻

明日からの週明け市場は日経平均1万7千円台回復か? 等と今日の産経ニュースに載ってましたが、はてさてどうなりますか。という訳でご無沙汰しております、細々とシストレなぞもすなる野良プログラマ Mariyudu です。

プログラマさんがシストレする時にどんな言語が便利なのかは知りませんが、個人的には使い慣れた PHP を使ってます。んで、PHP には株式や FX 等のテクニカル分析用の Technical Analysis for Traders という数学関数群が人知れず用意されてまして、これはどうやら TA-Lib というライブラリをラップしたもののようです。残念なことに、php.net のマニュアルはあまり親切とは言えないし、ユーザも少ないのか情報に乏しいようなので、当方が試行錯誤して使ってみた経過なぞをアウトプットしてみます。

まずインストールから。Trader 関数は PECL モジュール化されてるので、sudo pecl install trader でインスコできるでしょう(たぶん)。CentOS ユーザには Remi リポジトリに用意されているので、sudo yum --enablerepo=remi install php-pecl-trader でも OK(当方はこちら)。

今回は手始めに移動平均を算出する関数、trader_ma を使ってみます。コーリングシーケンスは

trader_ma(<株価が格納された1次元の配列>, <集計日数>, <移動平均の種類>)

となります。第三パラメータには TRADER_MA_TYPE_SMA (単純移動平均)、TRADER_MA_TYPE_EMA (指数平滑移動平均)、TRADER_MA_TYPE_WMA (加重移動平均線)等の多種が用意されています。正直、単純移動平均なら自分で書いた方が早いくらいですが、EMA や WMA はそれなりの計算手順を踏むので一発で計算できるこの関数は便利かも。返り値は、第一パラメータの配列に応じるかたちで移動平均値が格納された配列です。例えば5日移動平均だと、最低5日ぶんの直近株価データが必要なので、返り値配列のインデックス[0]〜[3]には何もセットされず、[4]から計算結果がセットされている、という感じ。

以下、サンプルプログラムです。各種株価データを提供されている、k-db.com さんの所から今年の日経平均日足データを入手して、それを元に移動平均を算出して表示するというシナリオです。

<?php
// 入手した CSV ファイルから株価データを読み込んで、配列に格納
$data = file('indices_I101_1d_2016.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; });
// trader_ma の入力用に、終値だけ収集(要実数変換)
$closePrices = array_map(function($item){ return +$item[4]; }, $data);
// 5日・25日・75日の指数平滑移動平均を計算
$emas = [5 => [], 25 => [], 75 => []];
foreach ($emas as $d => &$ema) {
	$ema = trader_ma($closePrices, $d, TRADER_MA_TYPE_EMA);
}
// 出力
echo "日付,株価,5日EMA,25日EMA,75日EMA\n";
foreach ($data as $n => $data) {
	echo implode(",", [
		$data[0],
		$closePrices[$n],
		@$emas[5][$n],
		@$emas[25][$n],
		@$emas[75][$n],
	]) . "\n";
}

これを実行した結果はこんな感じ↓。

日付,株価,5日EMA,25日EMA,75日EMA
2016-01-04,18450.98,,,
2016-01-05,18374,,,
2016-01-06,18191.32,,,
2016-01-07,17767.34,,,
2016-01-08,17697.96,18096.32,,
2016-01-12,17218.96,17803.867,,
(中略)
2016-08-29,16737.49,16579.888,16517.979,16433.468
2016-08-30,16725.36,16628.379,16533.931,16441.15
2016-08-31,16887.4,16714.719,16561.121,16452.893
2016-09-01,16926.84,16785.426,16589.253,16465.365
2016-09-02,16925.68,16832.177,16615.132,16477.479

実はこれ、巷の株情報サイトに載っている値とはちょっと違っています。指数平滑移動平均は 𝑓(前日の計算結果, 当日株価) という計算式なので、理論上は過去全ての株価データを与えてやらないと正しい計算値にならないことになります。当方であれこれした所では10年分くらいのデータがあればそれ程大きな差異は出ないようですが。

ということで、また折を見てボリンジャーバンドMACD 等、他の指数計算関数も紹介してみようと思います。