Tacticoでは、スクリプト言語を用いて機能を拡張することができます。これによって、
といったことができるようになります。
Tacticoの前身であるOmegaChartにも同様の機能はありましたが、そのときに分かったメリット・デメリットを踏まえたうえで改良を加えたスクリプトになっています。
OmegaChartユーザの方には申し訳ありませんが、スクリプトの互換性はありません。形式は似ているのでわずかな書き換えで動くと思いますが、そのままでは動かないのでご了承ください。要望次第では、互換性を高めた特別なスクリプト動作モードを用意するかもしれません。
この非互換は、主に静的な「型」の概念を導入したことが理由です。
このドキュメントはスクリプト言語の入門という位置づけですが、一般のプログラム言語についての理解がある程度あることを前提としています。Java, C#, Perl, Rubyのいずれか1つでも経験があれば比較的容易に理解できると思いますが、ひとつも知らないと理解は難しいかもしれません。
この言語の特徴を簡単にまとめると次のようになっています。
以下では、簡単な構文からはじめて言語の詳細に入っていきます。このドキュメントだけでなく、標準添付のテクニカル指標の定義(Tacticoをインストールしたディレクトリの下のextension\basictechnical.xml)を参照しながらだと理解が早いと思います。
一般のプログラム言語と同様に、四則演算、比較演算、論理演算があります。
例
3 + 5 => 8 1 < 2 => true 1==2 || 2>3 => false
これらに関しては、概ねJavaやC#と同じです。型も考慮すると以下のようになります。
四則演算 | int, int -> int | +, -, *, /, % |
double, double -> double | +, -, *, / | |
int -> int | - (符号反転) | |
double -> double | - (符号反転) | |
比較演算 | int, int -> bool double, double -> bool |
>, >=, <, <=, ==, != |
論理演算 | bool, bool -> bool | &&, || |
bool -> bool | ! (否定) |
ここに書いたように、内部的には型を区別しています。基本の型には整数(int)、浮動小数(double)、真偽(bool)があり、他にオブジェクト型もあります。doubleを要求する関数にint型の値が渡されたときのみ自動的に変換されますが、それ以外の型の相違はすべてスクリプトをコンパイルするときのエラーになります。
整数を対象とした割り算は整数で商を求めるのでやや注意が必要です。"1%"を表現するために1/100と書いてしまうと整数同士の割り算になってしまい結果が0になります。これを避けるには、小数であることを明示して1/100.0とするか、1*0.01とすればOKです。
C言語のような三項演算子もあります。
a? b : c
の値は、aがtrueのときb, falseのときcになります。aはbool型でなければならず、bとcは同じ型でなければなりません。
Tacticoスクリプトにもオブジェクトがあります。
一般のオブジェクト指向プログラム言語と同様に、クラスにメソッドが定義されており、オブジェクトのメソッドを呼び出すことができます。呼び出す演算子はJavaやC#と同様に . (ピリオド)です。
株価分析に特化した言語なので、最もよく使うオブジェクトは日足・分足などの「足」を表すQuoteオブジェクトでしょう。
慣習として、オブジェクトの型名は大文字で始めます。
q.ma(5)
qはQuoteオブジェクトを参照する変数という前提で、maというメソッドを5という引数とともに呼び出しています。maはMoving Averageの略で、移動平均を取得するメソッドです。引数の5は取得する長さなので、5日移動平均がわかる、ということになります。
配列もオブジェクトの一種です。上のQuoteオブジェクトに対し
q.open(5)
とすると、5日分の始値の配列が得られます。型の表記としてはdouble[]になります。数値の配列には平均を取得するメソッドavgが定義されているため、
q.open(5).avg()
とすると5日分の始値の平均が得られます。
また、配列には要素を取得する特殊なメソッド [] があるので、
q.open(5)[2]
とすると、5日分の始値の3番目の要素(配列の添え字は0から始まります)が得られます。
上にも出てきたとおり、関数やメソッドを呼び出すためには () が必要です。関数を呼び出すためには () は省略できません。
関数には、以下の種類があります。
最後のものは次で説明します。
lambdaは、名前のない関数を定義するための特殊な構文です。Lisp系の言語を知っている方はおなじみでしょう。C#では無名delegate、Rubyでは引数に渡すブロックやクロージャ、JavaScriptではfunctionオブジェクトに相当する概念です。
lambda ( (引数型1 引数名1, 引数型2 引数名2, ... ) 式の値 )
という書き方をします。例えば、
lambda ( (int x) x * 2 )
は、「引数に渡されたint型の値を2倍にする関数」です。
実は配列には、「関数を受け取り、配列の各要素に適用した結果を配列にして返す」というメソッド map が用意されています。仮に、 x が[1,2,3]という3個の数値を持った配列だとすると、
x.map( lambda ( (int x) x * 2 ) )
は [2, 4, 6] という結果になります。同様に、
x.map( lambda ( (int x) x * x ) )
は それぞれを2乗する関数を適用した結果を返すので、[1, 4, 9] となります。
lambdaは関数を定義するためのものでしたが、letはローカル変数を定義するための特殊構文です。
let ( (変数名1 = 変数値1, 変数名2 = 変数値2, ... ) 式の値 )
という書き方をします。例えば、
let ( ( a = 3, b = 5) a * b )
は15という結果になります。これが効果を発揮するのは同じ値を複数回使うときで、関数fを実行した結果の2乗を求めるときは
let ( ( a = f() ) a * a )
のように書くことができます。
f() * f()
としてもよいのですが、letを使うとfの呼び出しが1回で済むため、効率的です。
ここまでの知識を使うと、ボリンジャーバンドが定義できるようになりました。
double bollinger_unit(Quote prices, int len) { let( (ma = prices.ma(len) ) sdev(prices.close(len).map(lambda( (double v) v - ma )) ) ) }
これはボリンジャーバンドの幅を求める関数です。
まずletを使い、移動平均をローカル変数 ma に求め、終値の配列の各要素にmapを適用してmaとの差の配列に変換し、最後にsdev関数でその標準偏差を求めます。
nilは、値が無効であることを示す特殊な値です。JavaやC#のnullに近い概念ですが、「nilを含む演算の結果は常にnilになる」という点が大きく異なります。
整数やbool値の演算についても例外ではなく、nilは何と演算してもnilになります。このとき、エラーにはならないことに注意が必要です。その点では、浮動小数値のNaNに近いふるまいをします。
このような仕組みになっている理由は、以下のようになります。例えば30分間の移動平均と現在値の差を求める場合を考えてみます。分足を示すオブジェクトをqとすると、30分間の移動平均は、
q.ma(30)
となります。さらに、30分移動平均と終値との差を求めるには
q.ma(30) - q.close()
となりますが、「q.ma(30)」は取引開始から最初の30分間は当然算出は不可能であるため、nilを返します。すると、「q.ma(30) - q.close()」は 「nil - q.close()」 となり、q.close()が何かの値を持ったとしても全体が必ずnilになります。
仮に、nilの演算でエラーが起きるようになるような言語仕様であったり、無効な値としてq.ma()が0など特別な値を返すような仕組みであったなら、q.ma(30) - q.close()と書くところで足の長さが十分かどうかをチェックするための条件分岐が必要になってしまい、記述が煩雑になってしまいます。
私は、検討の結果、このような動作であるほうがテクニカル指標の記述には好都合であると判断しました。そして、これは既存のプログラム言語でなく独自のスクリプトを用意した理由の中でも大きなものとなっています。
Tacticoのスクリプトは文が書けないため、C言語のforやwhile文のような形で繰り返しの処理をすることはできません。
そのかわり、配列に対して関数を引数にメソッドを呼び出して類似のことができます。一つは先に紹介したmapですが、もう一つQuoteオブジェクトにのみ用意された特殊なメソッド each があります。
eachは、足の位置を1日ずつずらしながら関数を実行します。たとえば、
q.each(5, lambda((Quote q) q.close(25).avg()))
は、過去5日間のそれぞれについて25日移動平均を計算し、結果として長さ5の配列が生成されます。eachの最初の引数は繰り返しを行う長さ、2番目の引数は日にちをずらしたそれぞれのQuoteについて実行する関数です。
他にも、関数を引数にとる関数を呼ぶことで通常なら繰り返し処理が必要になる処理が実行できます。詳細は関数のリファレンスをご覧ください。
多くのプログラム言語と同様、&&, ||の論理演算ではショートカットが行われます。つまりさきに演算子の左辺を評価して、その結果右辺の評価が不要であれば右辺はスキップします。
A && B => A がfalseなら即時全体がfalseになります A || B => A がtrueなら即時全体がtrueになります
したがって、各辺のtrue/falseのどちらになる頻度が高いか、重い関数を呼び出しているかどうか、ということを考慮して、A || B, B || A のどちらの記述をするかを選択すると(実行結果は変わりませんが)パフォーマンスが向上します。複雑な式を書くときには留意してください。
以上でスクリプト言語の解説は終わりです。使用方法についての質問、期待どおりの動きでない場合の原因究明のサポート、組み込み関数の追加の要望、などに関しては
などでお願いします。
テクニカル指標をいろいろと分析して試行錯誤するのは楽しいものです。ご意見お待ちしています。
(C) 2010 Lagarto Technology, Inc. All rights reserved.