 | レベル: 上級 木村 桂 (kimuc@jp.ibm.com), ソフトウェア事業, IBM
2006年 2月 24日 本連載も3回目になりました。今回も含めて、今までに紹介してきた RCI や標準偏差のアルゴリズムでは注文の翌日に決済をしています。つまり1営業日しか保有していません。このように、ごく短期間に大きく値が動くことを期待したアルゴリズムなのですが、本来 RCI や標準偏差は「そろそろ上がってもいい頃かな?」という判断をする考え方なので、もっと中長期的な視野で売買する時に使う手法といえます。
生物学的投資(?)
連載の三回目である今回は少し趣を変えてみます。具体的にはより短期売買向きな考え方に基づく方法を紹介してみます。また、今までは RCI や標準偏差といった、数学的な統計を元に売買判断をするテクニカル分析を紹介してきましたが、今回は数学からも少し離れ、どちらかというと生物学的(?)な分析をしてみます。
人はみな狼男? という仮説
今回は月齢を売買判断に取り入れてみます。月齢というと堅苦しく聞こえるかもしれませんが、要するに満月だの、新月だの、三日月だのというアレです。結論を先に言うと、今回は「新月および満月の日には投資をしてみる」というアルゴリズムを実装してみます。
多くの人は「なんで投資と月齢が関係あるの?」と不思議に思っているかもしれません。 ごもっともです。今まで紹介してきたケースと異なり、この方法には数学的な根拠はありません。ただその一方で「全く関係ない」とは言い切れない、とも思っています。満月の日は殺人事件や交通事故は他の日と比べて無視できないほど高くなる、という統計があります(参考文献(1))。また負傷した人の血液も他の人比べて止まりにくくなっているようです。生体的にも潮の満ち干きが存在し、多かれ少なかれ人は満月の日には狼男のように興奮しやすくなっているのかもしれません。ということは、株式相場においても満月の前後に人は興奮しやすくなり、狼狽的な判断をしやすくなってしまうのではないでしょうか?
こんな「いやいや、まさか何言ってるの?」的な推測を大胆にも仮説化して、「だったらその流れに載って売買してみよう」というのが今回の内容になります。
月齢を使った売買アルゴリズム
ところで、月齢について簡単に紹介しておきます。月齢は0(ゼロ)から約29.53の間で少しずつ大きくなり、29.53 になるともう一度ゼロに戻ります。
新月を月齢ゼロとし、三日月→半月と少しずつ大きくなりながら月齢も加算されてゆき、満月になると月齢は約 14.765 になります。ここからは月が少しずつ小さくなりながら月齢は増えてゆき、次の新月の時に月齢は約 29.53 になります。ここで再度月齢をゼロにリセットして、また月とともに少しずつ増えていきます。これが月齢です。月齢については計算アルゴリズムが確立しているため、それを用います。
本来、満月の日(あるいは満月に近い日)にだけ、直前に最も大きく動いた銘柄を(その興奮が冷めないうちに)売買する、というアルゴリズムにしたかったのですが、カブロボは1ヶ月に2度以上の売買を行うことが運用ルールになっています。満月は月に1回程度しかないため、このアルゴリズムではルール上不充分です。したがって満月と新月、両方のタイミングを売買ポイントとします。
以上の点を考慮して、今回は以下のようなアルゴリズムを株ロボット上に実装することにします:
- まず前営業日に注文がなされていた場合は、無条件にその決済注文を行う(つまり注文の翌日に決済する)。
- 各銘柄ごとに株価のサンプルとして最近2日間の終値を取得し、その銘柄の直近の上昇率(下落率)を求める。
- 全銘柄でこれらの計算を行った値を比較し、直前に最も上昇(下落)した銘柄を1つ決定する。
- 当日の月齢を求めて、満月か新月が迫っていることが分かった場合は、3 で決定した銘柄を1売買単位だけ注文する。直前に上昇していた場合は買い、下落していた場合は売り注文とする。
1. の部分は過去2回の紹介記事のものと全く同じです。 また、本来は先に月齢を求めておいて、売買該当日だった場合だけ各銘柄の上昇・下落率を計算するアルゴリズムの方が効率はいいのですが、月齢を気にしなかった場合はどの銘柄の上昇・下落率が大きかったのかを確認したい場合を考慮して、今回のアルゴリズムとしました。
Java のプログラムで実装
ここまでに決めたアルゴリズムを実際の Java プログラムで記述してみたのが、以下のソースコードになります。ここでは run メソッドの中身と、月齢を求めるためのプライベート関数の内容、そしてスタティック変数一覧を紹介しています:
static double lastma = -1.0; // 前営業日の月齢を一時記憶するための変数
static int idx_sb = 0; // 前日に行った売買の種類
public void run( InvestmentAgent arg0 ) {
// TODO Auto-generated method stub
Calendar cdt = Time.getTime(); // 日付を取得
double j_today = getJulian( cdt ); // 当日のユリウス通日
double j_tomorrow = j_today + 1.0; // 翌日のユリウス通日
double ma_today = getMoonAge( j_today ); // 当日の月齢
double ma_tomorrow = getMoonAge( j_tomorrow ); // 翌日の月齢
Stock[] stocks = arg0.getStocks(); // 銘柄リストの取得
int i, idx;
double idx_pr, pricerate;
//. ポートフォリオを取得
InformationManager infoManager = arg0.getInformationManager();
Portfolio portfolio = arg0.getPortfolio();
Map holdingMap = portfolio.getHoldings();
// 前営業日に注文した株があれば決済する
if( holdingMap.size() > 0 ){
//. 前営業日に注文した銘柄の情報を取得
Iterator itr = holdingMap.values().iterator();
Holding holding = ( Holding )itr.next();
int tradetype;
// 決済なので、注文した内容が買いであれば売り、売りであれば買いの注文を行う
if( holding.getNumber() > 0 ){
tradetype = StockOrder.SELL;
}else{
tradetype = StockOrder.BUY;
}
// 決済注文
SimpleStockOrder stockOrder = new SimpleStockOrder();
stockOrder.setStock( holding.getStock() ); // 注文の銘柄を設定
stockOrder.setLimitType( StockOrder.MARKET ); // 成り行き注文
stockOrder.setTradeType( tradetype ); // 買い/売り注文を設定
stockOrder.setQuantity( 1 ); // 注文を1単位株に設定
arg0.order( stockOrder ); // 注文の発行
}
// 直近の2日で最も株価が変化した銘柄を探す
for( i = 0, idx = -1, idx_sb = 0, idx_pr = 0.0; i < stocks.length; i++ ){
// 直近の日付から2日分の銘柄指標データを取得
List list = infoManager.getIndexInformation( stocks[i], cdt, -2 );
IndexInformation index1 = ( IndexInformation )list.get(0); // 一つ前のデータ
IndexInformation index2 = ( IndexInformation )list.get(1); // 直近データ
if( index2.getClosingPrice() > index1.getClosingPrice() ){
// 値上がり → 買いの候補
// 値上がり率
pricerate = ( ( double )index2.getClosingPrice() - index1.getClosingPrice() )
/ index2.getClosingPrice();
// 株価変動率の最も大きなものかどうかを調べる
if( idx_pr < pricerate ){
idx = i;
idx_pr = pricerate;
idx_sb = StockOrder.BUY;
}
}else{
// 値下がり → 売りの候補
// 値下がり率
pricerate = ( ( double )index1.getClosingPrice() - index2.getClosingPrice() )
/ index2.getClosingPrice();
// 株価変動率の最も大きなものかどうかを調べる
if( idx_pr < pricerate ){
idx = i;
idx_pr = pricerate;
idx_sb = StockOrder.SELL;
}
}
}
// 売買を行うのは新月か満月の夜のみに限定する
if( ( lastma > 27.0 && ma_today <= 3.0 ) || ( lastma < 14.765 && 14.765 <= ma_today ) ){
// 新月 or 満月
SimpleStockOrder stockOrder = new SimpleStockOrder();
stockOrder.setStock( stocks[idx] ); // 注文銘柄を設定
stockOrder.setTradeType( idx_sb ); // 買い/売り注文を設定
stockOrder.setQuantity( 1 ); // 1単位
arg0.order( stockOrder ); // 注文の発行
}
lastma = ma_today;
}
// 月齢計算
// 新月の月齢: 0.0
// 満月の月齢: 14.0 - 15.6
public double getMoonAge( double julian ){
double newmoon = getNewMoon( julian );
return ( julian - newmoon );
}
// 直前の新月日の計算
public double getNewMoon( double j ){
double k = Math.floor( ( j - 2451550.09765 ) / 29.530589 );
double t = k / 1236.85;
double nm = 2451550.09765
+ 29.530589 * k
+ 0.0001337 * t * t
- 0.40720 * Math.sin( ( 201.5643 + 385.8169 * k ) * 0.017453292519943 )
+ 0.17241 * Math.sin( ( 2.5534 + 29.1054 * k ) * 0.017453292519943 );
return ( nm ); // 月齢は j - nm
}
// ユリウス通日の計算
public double getJulian( Calendar d ){
double j = d.getTime().getTime() / 86400000.0 + 2440587.5;
return j;
}
|
今回はあまり技術的な内容ではないため、プログラムの詳細説明は省略します。簡単に説明すると満月・新月の判断は当日と翌日の月齢を求めて、これら2つの月齢が満月あるいは新月の条件となる月齢をまたいでいるかどうかを調べています。
シミュレーション結果
このプログラムを実行すると以下のような結果になります。シミュレート期間は 2005 年の4月です:
■1日目2005/04/01 金 終了時の所持金額
総資産 = 手持ち資金+所有株式時価総額 = 5,000,000円
手持ち資金(運転可能金額 + ロック金額) = 5,000,000円
運転可能金額 = 5,000,000円
ロック資金 = 0円
単純計算年率利益率 0.0%
○ロボットの銘柄評価
購入: 2914 JT 1単位 成行
【注文処理結果】
銘柄名|銘柄コード| 枚数| 購入価格| 前日比
------------------------------+----------+----------+----------+----------
JT| 2914| 1| 1,240,000| -20,000
【ポートフォリオ】
銘柄名|銘柄コード| 枚数| 購入価格| 前日比
------------------------------+----------+----------+----------+----------
JT| 2914| 1| 1,240,000| -20,000
実行時間: 2.328 秒.
■2日目2005/04/02 土 終了時の所持金額
No Data
実行時間: 0.0 秒.
■3日目2005/04/03 日 終了時の所持金額
No Data
実行時間: 0.0 秒.
■4日目2005/04/04 月 終了時の所持金額
総資産 = 手持ち資金+所有株式時価総額 = 5,020,000円
手持ち資金(運転可能金額 + ロック金額) = 3,760,000円
運転可能金額 = 3,760,000円
ロック資金 = 0円
単純計算年率利益率 145.0%
○ロボットの銘柄評価
売却: 2914 JT 1単位 成行
【注文処理結果】
銘柄名|銘柄コード| 枚数| 購入価格| 前日比
------------------------------+----------+----------+----------+----------
JT| 2914| -1| 1,260,000| 10,000
実行時間: 1.61 秒.
:
:
(略)
:
:
■28日目2005/04/28 木 終了時の所持金額
総資産 = 手持ち資金+所有株式時価総額 = 5,022,000円
手持ち資金(運転可能金額 + ロック金額) = 5,022,000円
運転可能金額 = 5,022,000円
ロック資金 = 0円
単純計算年率利益率 8.0%
○ロボットの銘柄評価
実行時間: 1.75 秒.
■29日目2005/04/29 金 終了時の所持金額
No Data
実行時間: 0.0 秒.
■30日目2005/04/30 土 終了時の所持金額
No Data
実行時間: 0.0 秒.
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
【最終結果】
あなたのロボットによる、該当期間の運用成績は、こちらです。
総資産 = 手持ち資金+所有株式時価総額 = 5,022,000円
単純計算年率利益率 7.0%
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
|
実際のところ、この期間中には3度の売買しか行っていないのですが、最終的には 22,000 円の利益がでました。他の期間でもシミュレートしてみましたが、このアルゴリズムは結構安定して利益を出してくれています。「当たるも八卦、当たらぬも八卦」程度のつもりで作ってみた今回のロボットでしたが、意外な結果になりました。月の魔力は侮れません。
What's Next?
カブロボの投資アルゴリズム、特にテクニカル分析(さすがに今回のはテクニカルと呼ぶのに抵抗もありますが・・・)についての解説も3回を迎えることができました。三日坊主ならぬ、三週坊主はなんとかクリアできそうです。
さて次の紹介内容は・・・ と考えたのですが、筆者が一方的に内容を決めて紹介するよりも、読者の皆様から「こういう手法を実装してみてほしい」という要望をいただきたいと思いました。あまりに複雑な内容になりすぎて私自身の能力を超えてしまうかもしれませんが、いただいた要望も考慮に取り入れた上で内容を検討させていただくことにします。要望がありましたら、ぜひフィードバックとして送ってください。
要望をお待ちしつつも、とりあえず次回分としては MACD を、少し通常とは改良を加えた形で紹介してみる予定です。要望分につきましては次々回以降で実現できれば、と考えております。
参考文献
- (1)Arnold L. Lieber 「月の魔力」
著者について  | |  | 木村 桂: 日本IBM ソフトウェア事業 |
記事の評価
|  |