 | レベル: 上級 木村 桂 (kimuc@jp.ibm.com), ソフトウェア事業, IBM
2006年 2月 10日 本連載は鳥海氏のコラムを補足する形で、特にカブロボの標準ライブラリでは提供されていない各種テクニカル分析を Java 言語で実装し、ロボットに組み込んでみる、というサンプルを紹介していきく予定です。
カブロボ 実践編
昨年末にカブロボ・コンテストの第二回目がスタートしました。仮想の元金500万円を、投資アルゴリズムを持ったソフトウェアロボットによる自動売買でいくらまで増やせるか、という社会的にも技術的にも非常に興味の高いソフトウェアコンテストです。特筆すべきはその参加形態で、Web 上でいくつかの質問に答えることで作成される簡易ロボットから、その投資アルゴリズムを Java 言語でゼロから作成する自作ロボットまで、参加者のスキルに併せて選択できることにあります。
この IBM developerWorks においても第一回大会入賞者である鳥海不二夫氏による連載コラムが紹介され、特に自作ロボットを作成する参加者にとっては非常に有益な情報が提供されました。
本連載は鳥海氏のコラムを補足する形で、特にカブロボの標準ライブラリでは提供されていない各種テクニカル分析を Java 言語で実装し、ロボットに組み込んでみる、というサンプルを紹介していきく予定です。 まずは鳥海氏のコラム(特に第三回)をご覧ください。
なお、あらかじめお断りしておきますが、本連載で紹介する内容はあくまでアルゴリズムの紹介であり、売買タイミングの紹介ではありません。投資判断は自己責任にてお願いします。
今回は RCI を用いたテクニカル分析を紹介します。
RCI(Rank Correlation Index : 順位相関指数)
RCI はカブロボ SDK でも標準で用意されている RSI というテクニカル分析に似た、オシレーター系分析の一つです。価格と日付という2つの順位に注目し、これら2つがどの程度連動して動いているのか?を指数化します。 指数化された結果は -100% から +100% の間になります。
極端な例ですが、RCI が 100% になるというのは、特定期間中ずっと上がり続けていることを意味します。また -100% はずっと下がり続けていることを意味しています。
したがって、+100% 付近になると「買われすぎ」を示していることになり、「そろそろ売り時」というサインとみなせます。逆に -100% 付近になると「そろそろ買い時」というサインとみなせます。 この数値の変化を頼りに売買を行います。
例として5日間の RCI を求めることにします。以下の(表1)をご覧ください。これはある株銘柄の5日間の価格(終値)の変動を表にしたものです:
表1
| 日付 | 日付順位 | 終値 | 価格順位 | 順位差平方 | RCI |
|---|
| 2006/01/17 |
| 11,545 |
|
|
| | 2006/01/18 |
| 11,520 |
|
|
| | 2006/01/19 |
| 11,539 |
|
|
| | 2006/01/20 |
| 11,528 |
|
|
| | 2006/01/23 |
| 11,439 |
|
|
|
この表に順位を書き加えたものが(表2)です。日付については新しい順に、価格については高い順に順序付けをします。
表2
| 日付 | 日付順位 | 終値 | 価格順位 | 順位差平方 | RCI |
|---|
| 2006/01/17 | 5 | 11,545 | 1 |
|
| | 2006/01/18 | 4 | 11,520 | 4 |
|
| | 2006/01/19 | 3 | 11,539 | 2 |
|
| | 2006/01/20 | 2 | 11,528 | 3 |
|
| | 2006/01/23 | 1 | 11,439 | 5 |
|
|
この2つの順位の差を平方(2乗)した結果をそれぞれ求めます。例えば 2006/01/17 の場合は 5 - 1 = 4 の2乗で 16 になりました(表3)。
表3
| 日付 | 日付順位 | 終値 | 価格順位 | 順位差平方 | RCI |
|---|
| 2006/01/17 | 5 | 11,545 | 1 | 16 |
| | 2006/01/18 | 4 | 11,520 | 4 | 0 |
| | 2006/01/19 | 3 | 11,539 | 2 | 1 |
| | 2006/01/20 | 2 | 11,528 | 3 | 1 |
| | 2006/01/23 | 1 | 11,439 | 5 | 16 |
|
RCI は評価期間を N とすると、
[ 1 - 6 × (順位差平方の合計) ÷ { N × ( N × N - 1 ) } ] × 100
という式で求めることができます。
今回は N = 5 で、順位差平方の合計は 16 + 0 + 1 + 1 + 16 = 34 ですからこれを代入すると、2006/01/23 は RCI = -70.0% になります(表4)。
表4
| 日付 | 日付順位 | 終値 | 価格順位 | 順位差平方 | RCI |
|---|
| 2006/01/17 | 5 | 11,545 | 1 | 16 |
| | 2006/01/18 | 4 | 11,520 | 4 | 0 |
| | 2006/01/19 | 3 | 11,539 | 2 | 1 |
| | 2006/01/20 | 2 | 11,528 | 3 | 1 |
| | 2006/01/23 | 1 | 11,439 | 5 | 16 | -70.0% |
翌日も同様に計算して、2006/01/24 は RCI = -60.0% になります(表5)。これが RCI の求め方です。
表5
| 日付 | 日付順位 | 終値 | 価格順位 | 順位差平方 | RCI |
|---|
| 2006/01/17 |
| 11,545 |
|
|
| | 2006/01/18 | 5 | 11,520 | 3 | 4 |
| | 2006/01/19 | 4 | 11,539 | 1 | 9 |
| | 2006/01/20 | 3 | 11,528 | 2 | 1 |
| | 2006/01/23 | 2 | 11,439 | 5 | 9 | -70.0% | | 2006/01/24 | 1 | 11,466 | 4 | 9 | -60.0% |
アルゴリズム化
では、この RCI を用いてカブロボを作っていきます。ここがカブロボである意味最も面白く、そして難しい点なのですが、カブロボで実際の売買判断をさせるには更に2点大事なことを決める必要があります:
- RCI がどういう条件を満たしたら、どういう注文を発行するのか?
- 1 で発行した注文は、どういう条件で決済するのか?
ここには正解どころか、模範解答と呼べる方法も存在していません。実際には膨大な量のシミュレーションをこなしたりするのではないでしょうか? 本連載はテクニカル分析の精度を高めるための内容は対象外としますので、あまり難しくならないように適当に決めてしまうことにします。興味のある方は是非色々試してみていただきたいです:
- 各営業日ごとに 100銘柄全ての RCI を N = 12 で求め、その絶対値が最も大きな銘柄を1売買単位だけ注文する(マイナスだったら買い、プラスだったら売り)。
- 発行した注文は翌営業日の RCI 計算前に全て無条件で決済する。
要するに RCI が最もゼロから乖離している銘柄を売られ過ぎ/買われ過ぎと判断して注文を行い、翌日には決済する、というアルゴリズムでロボットを作成してみることにします。果たしてうまくいくでしょうか?
Java のプログラムで実装
では、実際にカブロボの環境で実際に動作するロボットを Java で実装した結果が以下になります。なお、開発環境や動作確認の準備等は鳥海様のコラムを参照ください。ここでは run メソッド内のロジックの紹介のみ行います:
適当な名前のロボットを作成し、その run メソッドを以下のように定義します。また RCI を求めたり、その途中計算で順位付けを行う private 関数を用意したので、それらも載せておきます。
public void run( InvestmentAgent arg0 ){
// TODO Auto-generated method stub
int RCIDAY = 12; // ここで指定した日数分のサンプルを用いて RCI を求める (0)
Stock[] stocks = arg0.getStocks(); // 銘柄リストの取得
int N = stocks.length; // 銘柄リスト数(今回のルールでは常に 100 のはず)
double rci, minrci, maxrci;
int i, j, minidx, maxidx;
//. ポートフォリオを取得
InformationManager infoManager = arg0.getInformationManager();
Portfolio portfolio = arg0.getPortfolio();
Map holdingMap = portfolio.getHoldings();
// 前営業日に注文した株があれば決済する (1)
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 ); // 注文の発行
}
// 各銘柄について RCIDAY 日間の標準偏差を求め、偏差値が最低となっている銘柄を確定させる。
Calendar cdt = Time.getTime(); // 当日の日付
for( i = 0, minidx = -1, minrci = 0.0, maxidx = -1, maxrci = 0.0; i < N; i ++ ){ // (2)
long cp[] = new long[RCIDAY];
// 直近の日付から RCIDAY 日分の銘柄指標データを取得 (3)
List list = infoManager.getIndexInformation( stocks[i], cdt, -1 * RCIDAY );
for( j = 0; j < RCIDAY; j ++ ){
// ( RCIDAY - j )日前のデータ
IndexInformation indexInformation = ( IndexInformation )list.get( j );
// ( RCIDAY - j )日前の終値
cp[j] = indexInformation.getClosingPrice();
}
rci = getRCI( cp, RCIDAY ); // この銘柄の直近 RCIDAY 日間の RCI (4)
// 必要であればここで RCI 値の確認を行う (5)
// System.out.println( stocks[i].getName() + "(" + stocks[i].getCode() + ")\tRCI: " + rci );
// RCI が最低となっている銘柄を探す (6)
if( rci < minrci ){
minrci = rci;
minidx = i;
}
// RCI が最高となっている銘柄を探す
if( rci > maxrci ){
maxrci = rci;
maxidx = i;
}
}
if( minidx > -1 || maxidx > -1 ){
// (7)
int idx, tradetype;
if( maxrci> 0.0 - minrci ){
idx = maxidx;
rci = maxrci;
tradetype = StockOrder.SELL;
}else{
idx = minidx;
rci = minrci;
tradetype = StockOrder.BUY;
}
// (8)
System.out.println( "注文銘柄: " + stocks[idx].getName()
+ "(" + stocks[idx].getCode() + ")\tRCI: " + rci );
// (9)
SimpleStockOrder stockOrder = new SimpleStockOrder();
stockOrder.setStock( stocks[idx] ); // 注文の銘柄を設定
stockOrder.setLimitType( StockOrder.MARKET ); // 成り行き注文
stockOrder.setTradeType( tradetype ); // 買い/売り注文を設定
stockOrder.setQuantity( 1 ); // 注文を1単位株に設定
arg0.order( stockOrder ); // 注文の発行
}
}
//. RCI
private double getRCI( long[] v, int num ){
int dateo[] = new int[num];
int priceo[] = new int[num];
long sum = 0;
double rci;
for( int i = 0; i < num; i ++ ){
dateo[i] = num - i;
}
GetValueOrder( v, num, priceo );
for( int i = 0; i < num; i ++ ){
sum += ( dateo[i] - priceo[i] ) * ( dateo[i] - priceo[i] );
}
rci = 100.0 * ( 1.0 - 6.0 * sum / ( num * ( num * num - 1.0 ) ) );
return rci;
}
//. 順位付け
private void GetValueOrder( long[] v, int num, int[] o ){
int i, j, to;
for( i = 0; i < num; i ++ ){
to = 0;
for( j = 0; j < num; j ++ ){
// 自分を含め、自分よりも大きいものがあった場合のみインクリメントする。
// 同値の場合はインデックスの小さい方の順位を上げる
if( ( v[i] < v[j] ) || ( v[i] == v[j] & i >= j ) ){
to ++;
}
}
o[i] = to;
}
}
|
以下、簡単にプログラムの内容を解説します。
(0)まず RCI 計算に用いるサンプル数を決定します。この例では 12 に設定していますが、ここを変更してもかまいません。
(1)ポートフォリオを取得した後、その内容を調べて前営業日に成立した注文があるかどうかを確認します。存在していた場合は、その銘柄を決済するため反対売買を行います。
(2)次に各銘柄の RCI を求めます。ここでは各銘柄のうち RCI の値が最大のものと最低のものとを両方探します。 具体的には (0) で指定した日数分の終値を取得し(3)、そこから RCI を計算します(4)。
ここまでの計算結果をデバッグ用に出力する場合は (5) をコメントアウトしてください。ただこの場合、各営業日ごとに 100 銘柄全ての偏差値を出力することになるため、出力量がかなり多くなります。
そして RCI の最低値と最大値を求めます(6)。
これらを再度集計し、RCI の値が 0 から最も乖離している1つの銘柄を注文銘柄として特定します(7)。特定できたらこれを画面に出力し(8)、実際に1売買単位だけ成行注文します(9)。 これがこのプログラムの一連の処理が完成しました。
シミュレーション結果
このプログラムを実行すると以下のような結果になります。シミュレート期間は 2005 年の4月です:
■1日目2005/04/01 金 終了時の所持金額
総資産 = 手持ち資金+所有株式時価総額 = 5,000,000円
手持ち資金(運転可能金額 + ロック金額) = 5,000,000円
運転可能金額 = 5,000,000円
ロック資金 = 0円
単純計算年率利益率 0.0%
○ロボットの銘柄評価
注文銘柄: 荏原(6361) RCI: -95.1048951048951
購入: 6361 荏原 1単位 成行
【注文処理結果】
銘柄名|銘柄コード| 枚数| 購入価格| 前日比
------------------------------+----------+----------+----------+----------
荏原| 6361| 1000| 471| -3
【ポートフォリオ】
銘柄名|銘柄コード| 枚数| 購入価格| 前日比
------------------------------+----------+----------+----------+----------
荏原| 6361| 1000| 471| -3
実行時間: 2.079 秒.
■2日目2005/04/02 土 終了時の所持金額
No Data
実行時間: 0.0 秒.
■3日目2005/04/03 日 終了時の所持金額
No Data
実行時間: 0.0 秒.
■4日目2005/04/04 月 終了時の所持金額
総資産 = 手持ち資金+所有株式時価総額 = 5,003,000円
手持ち資金(運転可能金額 + ロック金額) = 4,529,000円
運転可能金額 = 4,529,000円
ロック資金 = 0円
単純計算年率利益率 21.0%
○ロボットの銘柄評価
売却: 6361 荏原 1単位 成行
注文銘柄: エーザイ(4523) RCI: -96.5034965034965
購入: 4523 エーザイ 1単位 成行
【注文処理結果】
銘柄名|銘柄コード| 枚数| 購入価格| 前日比
------------------------------+----------+----------+----------+----------
荏原| 6361| -1000| 474| 5
エーザイ| 4523| 100| 3,650| -120
【ポートフォリオ】
銘柄名|銘柄コード| 枚数| 購入価格| 前日比
------------------------------+----------+----------+----------+----------
エーザイ| 4523| 100| 3,650| -120
実行時間: 1.406 秒.
:
:
(略)
:
:
■28日目2005/04/28 木 終了時の所持金額
総資産 = 手持ち資金+所有株式時価総額 = 5,011,100円
手持ち資金(運転可能金額 + ロック金額) = 4,526,100円
運転可能金額 = 4,526,100円
ロック資金 = 0円
単純計算年率利益率 4.0%
○ロボットの銘柄評価
売却: 9433 KDDI 1単位 成行
注文銘柄: KDDI(9433) RCI: -97.20279720279721
購入: 9433 KDDI 1単位 成行
【注文処理結果】
銘柄名|銘柄コード| 枚数| 購入価格| 前日比
------------------------------+----------+----------+----------+----------
KDDI| 9433| -1| 490,000| -1,000
KDDI| 9433| 1| 490,000| -1,000
【ポートフォリオ】
銘柄名|銘柄コード| 枚数| 購入価格| 前日比
------------------------------+----------+----------+----------+----------
KDDI| 9433| 1| 490,000| -1,000
実行時間: 1.625 秒.
■29日目2005/04/29 金 終了時の所持金額
No Data
実行時間: 0.0 秒.
■30日目2005/04/30 土 終了時の所持金額
No Data
実行時間: 0.0 秒.
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
【最終結果】
あなたのロボットによる、該当期間の運用成績は、こちらです。
総資産 = 手持ち資金+所有株式時価総額 = 5,012,100円
単純計算年率利益率 4.0%
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
|
最初の営業日に RCI = -95.1 の荏原を買い、第二営業日には荏原を売って RCI = -96.5 のエーザイを買い、・・・と繰り返し売買しています。 そして最終営業日には RCI = -97.2 の KDDI を買って、最終結果は・・・ 12,100 円のプラスになりました。ふう、よかった。
次回予告
次回は標準偏差を用いたテクニカル分析をカブロボで実装する方法について紹介する予定です。
著者について  | |  | 木村 桂: 日本IBM ソフトウェア事業 |
記事の評価
|  |