見出し画像

【自動売買】【PHP】テクニカル分析 を簡単に実装する方法 【ボリンジャーバンド】

前回MACDの取得方法と判定についての記事を書きましたが、

今回はボリンジャーバンドの実装と判定について書いていこうと思います。

環境の設定の設定については前回の記事をご確認ください。

https://note.mu/mochobi/n/n804946b0cca0

ボリンジャーバンドについては

https://orekabu.jp/bollinger-band/

の記事が参考になります。

1 . チャートの取得とMACD取得処理の分解

まず前回の記事にてチャートの取得からMACDの取得、判断を一つの関数として作成しました。

ボリンジャーバンドの実装にもチャートの取得が必要ですが、このままだとボリンジャーバンドの実装の際にも

チャートの取得処理が必要となってしまう(冗長化してしまう)ため、チャートの取得とMACDの取得処理関数をそれぞれ分けましょう。

function getChart() {
$ch = curl_init();
// period = 60:1分, 300:5分, 900:15分, 1800:30分, 3600:1時間,
// 18000:5時間, 86400:日足, week:週足, month:月足
$period = 86400;
$url = "https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc?periods=$period";
// curlに各種オプションを設定
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_URL => $url,
CURLOPT_FRESH_CONNECT => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
]);
// curl実行
$ret = curl_exec($ch);
// phpの配列に変換
$ret = json_decode($ret, true);
// ロウソク足の情報のみ取得
$chart = $ret["result"]["86400"];
return $chart;
}
function getMACDAnalysis($chart) {
// 最終価格のみの配列を作成
$lastPrice = array_column($chart, 1);
// MACD計算
$ret = trader_macd($lastPrice, 12, 26, 9);
$macd = array();
foreach ($ret[0] as $k => $v) {
$tmp = array();
$tmp["MACD"] = $v;
if(isset($ret[1][$k])) $tmp["SIGNAL"] = $ret[1][$k];
if(isset($ret[2][$k])) $tmp["Divergence"] = $ret[2][$k];
$macd[] = $tmp;
}
// 直近のMACD
$new_macd = $macd[count($macd) -1];
// 一個前のMACD
$before_macd = $macd[count($macd) -2];
// MACD判定結果
$result = "none";
// デッドクロス
if($before_macd["Divergence"] < 0 && $new_macd["Divergence"] > 0) $result = "ask";
if($before_macd["Divergence"] > 0 && $new_macd["Divergence"] < 0) $result = "bid";
curl_close($ch);
return $result;
}

上記のように関数を分けることで、getChartの呼び元にはchartの結果が返却され、

getMACDAnalysisの関数に呼び元で取得したchartをパラメータとして設定することで、

同様の結果を得ることができます。

また、これから説明するボリンジャーバンドのパラメータにも同様にgetChartの呼び元で取得したchartを設定することで、

MACD取得処理、ボリンジャーバンド取得処理それぞれの処理でchart取得を実装しなくても、一度作成したchartを使いまわせるようになります。

上記のプログラムで、実際にMACDが取得できているかを確認するためのphpファイルの例を記載します。

<?php
$chart = getChart();
$macd = getMACDAnalysis($chart);
header('content-type: application/json; charset=utf-8');
echo $macd;
function getChart() {
$ch = curl_init();
// period = 60:1分, 300:5分, 900:15分, 1800:30分, 3600:1時間,
// 18000:5時間, 86400:日足, week:週足, month:月足
$period = 86400;
$url = "https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc?periods=$period";
// curlに各種オプションを設定
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_URL => $url,
CURLOPT_FRESH_CONNECT => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
]);
// curl実行
$ret = curl_exec($ch);
// phpの配列に変換
$ret = json_decode($ret, true);
// ロウソク足の情報のみ取得
$chart = $ret["result"]["86400"];
return $chart;
}
function getMACDAnalysis($chart) {
// 最終価格のみの配列を作成
$lastPrice = array_column($chart, 1);
// MACD計算
$ret = trader_macd($lastPrice, 12, 26, 9);
$macd = array();
foreach ($ret[0] as $k => $v) {
$tmp = array();
$tmp["MACD"] = $v;
if(isset($ret[1][$k])) $tmp["SIGNAL"] = $ret[1][$k];
if(isset($ret[2][$k])) $tmp["Divergence"] = $ret[2][$k];
$macd[] = $tmp;
}
// 直近のMACD
$new_macd = $macd[count($macd) -1];
// 一個前のMACD
$before_macd = $macd[count($macd) -2];
// MACD判定結果
$result = "none";
// デッドクロス
if($before_macd["Divergence"] < 0 && $new_macd["Divergence"] > 0) $result = "ask";
if($before_macd["Divergence"] > 0 && $new_macd["Divergence"] < 0) $result = "bid";
curl_close($ch);
return $result;
}

上記のPHPファイルをブラウザなどで実行して画面にMACDの判定結果(none, ask, bid)が表示されていれば、

正常にchartが取得されて、MACDの取得判断が実施されたこととなります。

2 . ボリンジャーバンド取得の実装

それではボリンジャーバンドの実装について説明します。

php traderのボリンジャーバンド取得関数は

array trader_bbands ( array $real [, int $timePeriod [, float $nbDevUp [, float $nbDevDn [, int $mAType ]]]] )

となっています。

realにはMACDと同様に最終価格の配列を設定し、timePeriodは25日を使用することが多いので、25を設定しましょう

nbDevUpとnbDevDnは標準偏差値を設定するので、まずはどちらも1を設定します。

mATypeにはSMA(単純移動平均線)を設定しましょう。

php traderではSMAを定数化していおり、TRADER_MA_TYPE_SMAという定数を与えることでSMAを表します。

また、trader_bbandsの返却値は +σの偏差値の配列、SMAの配列、-σの偏差値の配列の順で返却されてきますので、

MACDと同様に時系列で +σ、SMA、-σを合わせるために、MACDで実施したのと同様にループを行います。

ボリンジャーバンドは価格が偏差値内に収まっているかどうかも確認したいので、時系列に合わせて一緒に最終価格を格納すると

後で判断するのに役立ちます。

作成した関数にchartを渡すのも忘れないようにしましょう

function getBband($chart) {
// 最終価格のみの配列を作成
$lastPrice = array_column($chart, 1);
// ボリンジャー算出
$ret = trader_bbands($lastPrice, 25, 1, 1, TRADER_MA_TYPE_SMA);
$bband = array();
foreach ($ret[0] as $k => $v) {
$tmp = array();
if(isset($lastPrice[strval($k)])) $tmp["lastPrice"] = $lastPrice[strval($k)];
$tmp["P1"] = $v;
if(isset($ret[1][$k])) $tmp["SMA"] = $ret[1][$k];
if(isset($ret[2][$k])) $tmp["M1"] = $ret[2][$k];
$bband[] = $tmp;
}
return $bband;
}

上記のように関数を作成した後、実行結果を表示すると以下のような結果が得られます。

[
{
"lastPrice": 86842,
"P1": 85018.996,
"SMA": 81114.88,
"M1": 77210.764
},
......
{
"lastPrice": 880358,
"P1": 1262206.731,
"SMA": 1110563,
"M1": 958919.269
},
{
"lastPrice": 933230,
"P1": 1253279.646,
"SMA": 1099332.6,
"M1": 945385.554
}
]

上の例では±1σのバンドを作成していますが、

一緒に±2σと±3σを取得するために、作成したgetBbandの関数を改造します。

function getBband($chart, $dev) {
// 最終価格のみの配列を作成
$lastPrice = array_column($chart, 1);
// ボリンジャー算出
$ret = trader_bbands($lastPrice, 25, $dev, $dev, TRADER_MA_TYPE_SMA);
$bband = array();
foreach ($ret[0] as $k => $v) {
$tmp = array();
if(isset($lastPrice[strval($k)])) $tmp["lastPrice"] = $lastPrice[strval($k)];
$tmp["P".$dev] = $v;
if(isset($ret[1][$k])) $tmp["SMA"] = $ret[1][$k];
if(isset($ret[2][$k])) $tmp["M".$dev] = $ret[2][$k];
$bband[] = $tmp;
}
return $bband;
}

このように修正してgetBbandの2番目のパラメータに偏差値を渡すことで指定した偏差値のボリンジャーバンドが作成可能です。

以下のように呼び出すと、±1σ,±2σ,±3σのボリンジャーバンドが取得できます。

$bband[0] = getBband($chart, 1);
$bband[1] = getBband($chart, 2);
$bband[2] = getBband($chart, 3);

ただし、こうした場合、±1σ,±2σ,±3σそれぞれの配列でまとまるため、時系列的で判断が難しくなるため、

同じ時系列にボリンジャーバンドをまとめる関数を作成しましょう。

function summarizeBband($bband){
$ret = array();
// 各バンドのループ
foreach ($bband as $band) {
// バンド内のループ
foreach ($band as $k => $v) {
if(isset($ret[$k])) {
$ret[$k] += $v;
// 各時系列の全ての項目が揃ったら項目内を並び替え
if(count($ret[$k]) == 8 ){
// 項目内を見やすいように並び替え
$sort = array_fill_keys(array('P3', 'P2', 'P1', 'lastPrice','SMA', "M1", "M2", "M3"), null);
$ret[$k] = array_merge($sort, $ret[$k]);
}
}else{
$ret[$k] = $v;
}
}
}

return $ret;
}

各偏差値(±1σ,±2σ,±3σ)のボリンジャーバンドでループし、さらにその中で各時系列の情報でループしています。

各時系列の情報のループでは同じ添字=同じ時系列の情報が成立するため、

添字が同じものをまとめていくことで一つの時系列で±1σ,±2σ,±3σがまとめられていいきます。

また、各配列を+3σ,+2σ,+1σ,最終価格, SMA,-1σ,-2σ,-3σの順番でまとめたいので

時系列のデータが全て揃ったタイミングで配列内の項目を並び替えています。

このプログラムを実装し、以下の通りに呼び出すことで、ボリンジャーバンドで判定するための結果を得ることができます。

$bband[0] = getBband($chart, 1);
$bband[1] = getBband($chart, 2);
$bband[2] = getBband($chart, 3);
$bband = summarizeBband($bband);

得られる結果は以下のとおりです。

[
{
"P3": 92827.228,
"P2": 88923.112,
"P1": 85018.996,
"lastPrice": 86842,
"SMA": 81114.88,
"M1": 77210.764,
"M2": 73306.648,
"M3": 69402.532
},
.....
{
"P3": 1565494.192,
"P2": 1413850.461,
"P1": 1262206.731,
"lastPrice": 880358,
"SMA": 1110563,
"M1": 958919.269,
"M2": 807275.539,
"M3": 655631.808
},
{
"P3": 1561173.739,
"P2": 1407226.693,
"P1": 1253279.646,
"lastPrice": 933230,
"SMA": 1099332.6,
"M1": 945385.554,
"M2": 791438.507,
"M3": 637491.461
}
]

3 . ボリンジャーバンド判定の実装

ボリンジャーバンドの取得ができたら前回のMACDの記事と同様にボリンジャーバンドでnone, ask, bidを判定する

処理を作成しましょう。

今回は例として、最終価格が-1σを下に抜けて、-2σをタッチしそうな場合ask、+1αを上に抜けて+2σをタッチしそうな場合にbid、

それ以外はnoneとして判断する関数を作成してみます。

function getBbandAnalysis($bband){
// 直近のボリンジャーバンドのみ取得
$last_band = $bband[count($bband) -1];
// 見やすくするために使用する値は別の変数に入れておく
$lp = $last_band["lastPrice"];
$p1 = $last_band["P1"];
$p2 = $last_band["P2"];
$m1 = $last_band["M1"];
$m2 = $last_band["M2"];
// p2, m2に近づいた際の売買判定基準値を決めておく
$wFact = 0.1;
// 判断ロジック
$result = "none";
if($m1 > $lp && ($m2 - $m2 * $wFact) > $lp) $result = "ask";
if($p1 < $lp && ($p2 - $p2 * $wFact) < $lp) $result = "bid";
return $result;
}

今回も実際の売買判定の際には直近のボリンジャーバンドのみを使用するため、

ボリンジャーバンド配列の一番最後のデータのみ別変数に格納しておきます。

また、-1σと+1αを抜けたかどうか判定するのは最終価格の大小を比較すれば可能ですが、

「タッチしそう」という基準は感覚的なものとなるため、閾値となる値を決めておかなければなりません。

今回の閾値は-2σと+2αの10%の範囲内を「タッチしそう」という基準を設定としてサンプルを作成しています。

($m2 - $m2 * $wFact)

とすることで-2σから10%を引いた価格が算出できるので、最終価格がこれよりも小さかった場合に

askと判定するようにしています。

4 . 最後に

ボリンジャーバンドでは様々な基準で売買の判定が実装できると思います。

今回の例では±2σ付近で反発して売買を入れる判断を載せていますが、±2σを抜けた場合や、

時系列の過去を見てバンドウォークしている場合などもロジックで組むことができるので、

試してみてはいかがでしょうか。

また、以前の記事の内容と組み合わせてボリンジャーバンドの判定結果とMACDの判定結果が同じになったら

売買を行うことで、安定して利益を生むシステムが作成できると思います。

今回はMACDとボリンジャーバンドの関数を合わせて

https://note.mu/mochobi/n/nfa3b0cd1ed65

に差し込めるロジックを最後に記載したいと思います。

chart, macd, ボリンジャーバンドそれぞれの関数を分離しているので自動売買のメインロジックで各関数を呼び出すのを忘れないようにしてください。

function getChart() {
$ch = curl_init();
// period = 60:1分, 300:5分, 900:15分, 1800:30分, 3600:1時間,
// 18000:5時間, 86400:日足, week:週足, month:月足
$period = 86400;
$url = "https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc?periods=$period";
// curlに各種オプションを設定
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_URL => $url,
CURLOPT_FRESH_CONNECT => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
]);
// curl実行
$ret = curl_exec($ch);
// phpの配列に変換
$ret = json_decode($ret, true);
// ロウソク足の情報のみ取得
$chart = $ret["result"]["86400"];
return $chart;
}
function getMACDAnalysis($chart) {
// 最終価格のみの配列を作成
$lastPrice = array_column($chart, 1);
// MACD計算
$ret = trader_macd($lastPrice, 12, 26, 9);
$macd = array();
foreach ($ret[0] as $k => $v) {
$tmp = array();
$tmp["MACD"] = $v;
if(isset($ret[1][$k])) $tmp["SIGNAL"] = $ret[1][$k];
if(isset($ret[2][$k])) $tmp["Divergence"] = $ret[2][$k];
$macd[] = $tmp;
}
// 直近のMACD
$new_macd = $macd[count($macd) -1];
// 一個前のMACD
$before_macd = $macd[count($macd) -2];
// MACD判定結果
$result = "none";
// デッドクロス
if($before_macd["Divergence"] < 0 && $new_macd["Divergence"] > 0) $result = "ask";
if($before_macd["Divergence"] > 0 && $new_macd["Divergence"] < 0) $result = "bid";
curl_close($ch);
return $result;
}
function getBband($chart, $dev) {
// 最終価格のみの配列を作成
$lastPrice = array_column($chart, 1);
// ボリンジャー算出
$ret = trader_bbands($lastPrice, 25, $dev, $dev, TRADER_MA_TYPE_SMA);
$bband = array();
foreach ($ret[0] as $k => $v) {
$tmp = array();
if(isset($lastPrice[strval($k)])) $tmp["lastPrice"] = $lastPrice[strval($k)];
$tmp["P".$dev] = $v;
if(isset($ret[1][$k])) $tmp["SMA"] = $ret[1][$k];
if(isset($ret[2][$k])) $tmp["M".$dev] = $ret[2][$k];
$bband[] = $tmp;
}
return $bband;
}
function summarizeBband($bband){
$ret = array();
// 各バンドのループ
foreach ($bband as $band) {
// バンド内のループ
foreach ($band as $k => $v) {
if(isset($ret[$k])) {
$ret[$k] += $v;
// 各時系列の全ての項目が揃ったら項目内を並び替え
if(count($ret[$k]) == 8 ){
// 項目内を見やすいように並び替え
$sort = array_fill_keys(array('P3', 'P2', 'P1', 'lastPrice','SMA', "M1", "M2", "M3"), null);
$ret[$k] = array_merge($sort, $ret[$k]);
}
}else{
$ret[$k] = $v;
}
}
}

return $ret;
}
function getBbandAnalysis($bband){
// 直近のボリンジャーバンドのみ取得
$last_band = $bband[count($bband) -1];
// 見やすくするために使用する値は別の変数に入れておく
$lp = $last_band["lastPrice"];
$p1 = $last_band["P1"];
$p2 = $last_band["P2"];
$m1 = $last_band["M1"];
$m2 = $last_band["M2"];
// p2, m2に近づいた際の売買判定基準値を決めておく
$wFact = 0.1;
// 判断ロジック
$result = "none";
if($m1 > $lp && ($m2 - $m2 * $wFact) > $lp) $result = "ask";
if($p1 < $lp && ($p2 - $p2 * $wFact) < $lp) $result = "bid";
return $result;
}

最後までお読みいただきありがとうございました。

プログラムのことに関しては言語問わず対応できます。主にこれからプログラムを覚えていきたい+仮想通貨で自動売買をしたい人向けに記事を書いていきます。