添付されているライブラリTIlibは、利用するだけならそれほど難しくないのですが、仕組みを理解したり、同じようなマクロを書いたりするのは難しいです。ここでは、このTIlibについて解説します。
TIlibはオブジェクト指向風に記述されています。継承を利用していないので、オブジェクト指向というよりは抽象データ型というほうが正確かもしれません。テクニカル指標の名前に_newの付いた関数を実行すると配列が返ります。テクニカル指標の名前をクラス名、この関数をコンストラクタ、返る配列をインスタンスと考えてください。ほとんどのコンストラクタは引数を持ち、日数などのパラメータを指定することができます。
この配列の最初の要素が前日の値を、次の要素が当日の値を保持しています。指標が複数ある場合には偶数番目の要素が前日の値を、奇数番目の要素が当日の値を保持します。過去のデータが足りずに計算できない場合には指標の値はnullになります。
それ以外の要素は翌日以降の値を計算するために必要な値を保持します。コンストラクタで生成した配列を、同じクラス名に_nextの付いた関数(nextメソッドと呼びます)に渡すと、格納された値を利用して翌日の値を計算して配列の値を更新します。
単純移動平均を表示する以下は単純移動平均を表示するマクロです。Protraは最初にチャートの左端の日付でこのマクロを実行します。その際にはMA_new(5)が実行されて、その日の時点の5日間単純移動平均が格納された配列が返ります。その日も含めて過去に5日以上データがないと値はnullになります。次にProtraはその翌日の日付でこのマクロを実行します。その際にはMA_next($MA5)が実行されて、次の値が計算されます。MA_drawは前日の値から当日の値へ指定された色で線を引きます。
if !$MA5
$MA5 = MA_new(5)
else
MA_next($MA5)
MA_draw($MA5, $Colors[0])
end
コンストラクタで@作用素を使用すると、それはnextメソッドに引き継がれます。10日前の5日間単純移動平均を求めるために$MA5 = {-10}MA_new(5)としてコンストラクタを実行したら、nextメソッドをMA_next($MA5)として実行するだけで9日前の値が計算されます。 また、上記の例ではProtraが日付を進めたあとにnextメソッドを実行していますが、以下のように日付を進めない状態で連続して実行しても、次々と次の日の値が求まります。
i = 0
while i < 10
MA_next($MA5)
i = i + 1
end
二つの指標の交差を調べる
オブジェクト指向風に実装されているのはテクニカル指標だけではありません。以下は単純移動平均の交差で売買するシステムの一部です。Crossoverクラスは二つの指標が交差したかを調べるために使います。
if !$__INIT__
$MA_fast = MA_new(10)
$MA_slow = MA_new(40)
$Cross = Crossover_new(0, 0.0)
$__INIT__ = 1
else
MA_next($MA_fast)
MA_next($MA_slow)
end
ma_fast = MA_value($MA_fast)
ma_slow = MA_value($MA_slow)
...
if ma_fast == null || ma_slow == null || !price
else
cross = Crossover_next($Cross, ma_fast, ma_slow)
コンストラクタは二つの引数を取り、交差にどれくらい敏感に反応するかを指定します。両方に0を指定すると、交差したときにすぐに反応します。最初の引数は日数で、交差してから指定した日数の間再び交差しない場合に反応します。二番目の引数は閾値で、交差してから指定した値以上差が開くと反応します。
nextメソッドには、コンストラクタの生成したインスタンスと二つの指数を指定します。インスタンスには二つの指数の差が保持されます。次回以降にnextメソッドが呼ばれたときに、指数の差が逆転していてコンストラクタで指定した条件を満たしていると、0以外の値が返ります。0より大きいときは最初の指数が二番目の指数の上に交差しており、0より小さいときは下に交差しています。
実装や使い方が似ているクラスにReversalがあります。このクラスは指標が反転したかを調べるために使います。
コンストラクタはインスタンスとなる配列の要素を確保したあと、当日の値を計算します。MA_newの実装を以下に示します。指標を計算するクラスのインスタンスは、当日と前日の値のほかに@作用素の値とIndexの値を保持します。これはコンストラクタで指定された@作用素をnextメソッドに引き継ぐためと、nextメソッドで処理系がIndexを進めたかどうかを判断するためです。
// コンストラクタ
def MA_new(days)
obj = [6]
obj[0] = null // 前日の値
obj[1] = null // 当日の値
obj[2] = MAutil_new(days) // 単純移動平均を計算するためのクラス
obj[3] = {-days}LatestClose // n日以前のもっとも最近に売買が成立したときの終値
obj[4] = -days // MA_nextで用いる@作用素の値
obj[5] = Index // 処理系がIndexを進めたかどうかを判断するためにIndexを記録
// days日前に@作用素を指定して、days日分進めることで当日の値を計算する。
while obj[4] < 0
MA_next(obj)
end
obj[4] = at // コンストラクタに渡された@作用素の値
obj[5] = {-at}Index // @作用素を打ち消した本来のIndex
return obj
end
nextメソッドは最初に計算で用いる@作用素の値(obj[4])を計算します。保存してあるIndex(obj[5])とIndexを比較して、もし処理系がIndexを進めていないなら、@作用素を一つ進めて翌日の値を計算できるようにします。
// nextメソッド
def MA_next(obj)
obj[4] = obj[4] + 1 + obj[5] - Index // 計算で用いる@作用素の値を計算
obj[5] = Index // Indexの値を保存
obj[0] = obj[1] // 前日の値を保存
if {obj[4]}Close // 売買が成立しているか?
obj[3] = {obj[4]}Close // 成立していたら終値を更新
end
if !obj[3] // まったく売買が成立していなければ計算しない
return
end
MAutil_next(obj[2], obj[3]) // 終値の単純移動平均を計算
obj[1] = obj[2][0] // MAutil_nextの計算した値を当日の値に代入
end
MAutilクラスはさまざまな値の単純移動平均を計算するほかに、計算した値を何日間か保存するためだけにも用います。たとえばSmoothed ROC(13-21)では、13日間の指数移動平均を21日分保存するためにMAutilを用いています。
// コンストラクタ
def MAutil_new(days)
obj = [6]
obj[0] = null // データの平均
obj[1] = 0 // データの合計
obj[2] = days // 日数
obj[3] = 0 // バッファの一番古い要素の位置を計算するためのカウンタ
obj[4] = [days] // データを保存するバッファ
obj[5] = null // バッファからこぼれた一番古い要素
return obj
end
nextメソッドの最初の引数はコンストラクタの返したインスタンスで、二番目の引数は単純移動平均を計算する値です。
// nextメソッド
def MAutil_next(obj, val)
if val == null
return
end
oldest = obj[3] % obj[2] // バッファの一番古い要素の位置
if obj[4][oldest] != null // 古い値が存在する?
obj[5] = obj[4][oldest] // 古い値の退避
obj[1] = obj[1] - obj[5] // 合計から古い値を引く
end
obj[1] = obj[1] + val // 合計に新しい値を加える
obj[4][oldest] = val // 古い値を新しい値で置き換える
obj[3] = obj[3] + 1 // カウンタをインクリメント
if obj[3] >= obj[2] // 日数分のデータがそろったか?
obj[0] = (float)obj[1] / obj[2] // 平均を計算
end
end