EasyLanguage研究所

マネックス証券が提供する株式トレードツール「TradeStation」専用のプログラミング言語である、EasyLanguageについて。特にオブジェクト指向型EasyLanguage(OOEL)の情報を中心に。

銘柄を変えただけで、チャートに適用している分析テクニックやストラテジーのパラメータも自動変更する方法

今回は、EasyLanguage全般に関する小ネタです。(小ネタといいつつ、結構使えると思います)

ストラテジーのバックテストで、複数の銘柄ごとに最適なパラメータを得られたけれど、以下のような悩みをお持ちの方、必見。

・チャートで銘柄を変えるごとにストラテジーのパラメータ値を変えなきゃいけないのが面倒
・ストラテジーとセットで使う分析テクニックのパラメータ値も手動で合わせなきゃいけないのが面倒

通常は、ストラテジーに入力パラメータを設定

例えば移動平均線を利用した分析テクニックやストラテジーを自作する場合、計算足数などのパラメータはInputsで入力パラメータ化する事が多いと思います。

Inputs:
    inLength(5);

Vars:
    varAvg(0);

varAvg = AverageFC( Close, inLength );
Plot1( varAvg, "移動平均線" );

基本的にはこれで良いのですが、バックテストで銘柄コードごとにinLengthの最適値が得られているなら、If記述を活用する事により、手作業で入力パラメータを設定しなくてもEasyLanguage上で現在適用中の銘柄を自動判断し、対応するパラメータを使ってくれます。

例えば、以下のように銘柄ごとの「移動平均線の計算足数」が決まっている場合・・

銘柄コード Length
1111-TS 5
2222-TS 14
3333-TS 99
(その他) 20

Symbolで現在適用されている銘柄コードを取得できるので、以下のようなEasyLanguageになります。

Vars:
    varLength(0),
    varAvg(0);

If Symbol = "1111-TS" then
    varLength = 5
Else If Symbol = "2222-TS" then
    varLength = 14
Else If Symbol = "3333-TS" then
    varLength = 99
Else
    varLength = 20;

varAvg = AverageFC( Close, varLength );
Plot1( varAvg, "移動平均線" );

こちらで書くメリットは、自作した分析テクニックやストラテジーを、どの銘柄でも、手作業による設定不要で、使える点です。

例えば、「1111-TS」の日足チャートにこの分析テクニックを適用すると、5日移動平均線が描画されますが、その状態で銘柄コードを「2222-TS」に変更すると、移動平均線が14日に自動で変更されるというわけです。

さらに、レーダースクリーンとチャートをカスタムシンボルリンクで繋げておけば、銘柄コードをクリックするごとに、最適化されたパラメータの分析テクニック&ストラテジーを適用したチャートが表示される・・といった事もできます。

色々と応用が利くので、ぜひ試してみてください。

口座の残高や買付可能額を取得できるAccountsProviderクラスについて

オブジェクト指向EasyLanguageで利用可能なAccountsProviderクラスを使うと、口座の情報を取得できます。

具体的には、トレードステーションに初期搭載されている「トレードマネージャー」の「口座状況」タブで確認できる情報が中心、といえば分かりやすいと思います。

以下、AccountsProviderクラスを使って取得できる情報をPrint出力してみた結果です。(Updateされるごとに出力しているので見辛いかもしれませんが・・)

using elsystem;
using tsdata.trading;

Vars:
	AccountsProvider oAP(NULL);
	
method void AnalysisTechnique_Initialized( elsystem.Object sender, elsystem.InitializedEventArgs args ) 
begin
	Try
		oAP = AccountsProvider.Create();
		oAP.Updated += AccountsProvider_Updated;
		oAP.Load = true;
		
	Catch (Exception ex)
		Print( "Error: ", "	",  string.format("{0} | {1}[StackTrace]{2}", "	",  ex.Source, ex.Message, ex.StackTrace) );
	End;
end;

method void AccountsProvider_Updated(  elsystem.Object sender, tsdata.trading.AccountUpdatedEventArgs args ) 
vars:
	Account oAccount;
	
begin
	Try

		oAccount = oAP.Account[0];
		
		ClearPrintLog();
		
		Print("=======================");
		Print("口座情報");
		Print("=======================");
		Print("口座ID", "	", oAccount.AccountID);
		Print("口座名", "	",  oAccount.Name);
		Print("口座ステータス", "	",  oAccount.Status);
		Print("口座タイプ", "	",  oAccount.Type.ToString());
		Print();
		Print("=======================");
		Print("営業日開始時点");
		Print("=======================");
		Print("現金残高", "	",  oAccount.BDCashBalance);
		Print("未実現損益", "	",  oAccount.BDUnrealizedPL);
		Print("有効証拠金", "	",   oAccount.BDAccountEquity);
		Print("純資産額", "	",  oAccount.BDAccountNetWorth);
		Print("デイトレード 取引限度額", "	",  oAccount.BDDayTradingBuyingPower);
		Print("オプション取引限度額", "	",  oAccount.BDOptionBuyingPower);
		Print("オプション清算額", "	",  oAccount.BDOptionLiquidationValue);
 		Print("オーバーナイト買付可能額", "	",  oAccount.BDOvernightBuyingPower);
 		Print();
		Print("=======================");
 		Print("リアルタイム");
		Print("=======================");
		Print("口座エクイティ", "	",  oAccount.RTAccountEquity);
		Print("純資産額", "	",  oAccount.RTAccountNetWorth);
		Print("現金残高", "	",  oAccount.RTCashBalance);
		Print("約定金額合計 (保有分) ", "	",  oAccount.RTCostOfPositions);
		Print("デイトレード取引限度額", "	",  oAccount.RTDayTradingBuyingPower);
		Print("リアルタイム必要証拠金", "	",  oAccount.RTInitialMargin);
		Print("最低必要保証金額", "	",  oAccount.RTMaintenanceMargin);
		Print("オプション買付可能額", "	",  oAccount.RTOptionBuyingPower);
		Print("オプション清算価値", "	",  oAccount.RTOptionLiquidationValue);
		Print("オーバーナイト買付可能額", "	",  oAccount.RTOvernightBuyingPower);
		Print("買付可能額", "	",  oAccount.RTPurchasingPower);
		Print("実現損益", "	",  oAccount.RTRealizedPL);
		Print("未実現損益", "	",  oAccount.RTUnrealizedPL);
		Print();
		Print("=======================");
		Print("その他");
		Print("=======================");
		Print("今日のリアルタイム取引エクイティ", "	",  oAccount.TodaysRTTradeEquity);
		Print("入金予定額", "	",  oAccount.UnclearedDeposits);
		Print("未受渡金", "	",  oAccount.UnsettledFund);
		Print("口座でデイトレードが許可されるか?", "	",  oAccount.CanDayTrade);
		Print("トレーダーがパターンデイトレーダーか?", "	",  oAccount.IsDayTrader);
		Print("オプショントレード許可レベル", "	",  oAccount.OptionsApprovalLevel);
		Print("過去4日間の取引数", "	",  oAccount.FourDaysTradeCount);
		
	Catch (Exception ex)
		Print( "Error: ", "	",  string.format("{0} | {1}[StackTrace]{2}", "	",  ex.Source, ex.Message, ex.StackTrace) );
	End;
End;

リアルタイムの資金残高や買付可能額が取得できるのが使えそうなポイントかなと思います。

AccountsProviderクラスは、オブジェクトさえ用意してあげれば通常のストラテジー用EasyLanguageでも使う事ができるので、例えば資金管理も考慮に入れたストラテジー開発がしたい場合などに活用できそうです。

気になる方は、ぜひ使ってみてください。

AverageFC関数のパラメータを途中でいじったら計算がおかしくなった原因と対策

現在様々な手法でバックテストを実施しているのですが、移動平均線のパラメータ(日数)を期間の途中で切り替えるようなロジックを作りたくてストラテジーを書いてみたら、AverageFC関数の計算結果がおかしくなりましたorz

原因と解決策をメモしておきますので、参考にしていただければと思います。

発生した現象

日足チャートにおいて、期間ごとに移動平均線の日数を変更してテストしたいと思いました。2010年は5日移動平均線、2011年は10日平均線、・・といった感じ。それで、期間ごとにAverageFC関数のパラメータを変更すればいいやと思いました。

(書いてみたコード:これは間違いです)

If Date < 1170101 and Date < 1020101 then Begin
    varAvg = AverageFC( Close, 5 );
End Else If Date >= 1020101 and Date < 1030101 then Begin
    varAvg = AverageFC( Close, 10 );

{
    以下、期間ごとに続く・・
}
End;

しかし、いざやってみると売買サインがうまく表示されません。

はて・・?と思って以下のようなコードを書いてデバッグ用の出力をしてみたところ、ちょうどパラメータの値が変わる所でAverageFC関数の計算結果がおかしくなっていました。

Vars:
    varAvgLength(0);

If Date <= 1170101 then Begin
    varAvgLength = 5;
End Else Begin
    varAvgLength = 100;
End;

Print( Date:7:0, Spaces(2), AverageFC( Close, varAvgLength );

出力結果
f:id:eltraders:20170628152733p:plain

1170104(=2017年01月04日)以降から、明らかに数値がおかしい(苦笑)

原因

AverageFC関数に限らず、"FC"付きの関数は「高速計算方法を使用する」とマニュアルに記載されています。

このシリーズ関数は、Average とまったく同じ値を返しますが、高速計算方法を使用するため、使用するメモリーが非 FC バージョンよりわずかに多い点が異なります。

(トレードステーション:AverageFC関数のページより)


で、関数のEasyLanguageコードは「ストラテジー」や「インジケーター」と同様に、『EasyLanguage開発環境』からファイルを開いて確認できるので、「関数」のAverage関数とAverageFC関数のEasyLanguageコードを見てました。すると、それぞれSummation関数SummationFC関数という値の総和を求める関数を呼び出していました。

この2つはどちらも、指定した足数の指定した値の総和(合計値)を求める関数で、この計算結果を足数で割る事で平均値を計算しています。例えば過去10足の終値の総和を求めるなら Summation( Close, 10 ) といった感じです。

Average関数もAverageFC関数も、それぞれこの総和を求める関数以外はほとんど同じコードなので、今回の原因はSummation関数とSummationFC関数の違いにあるようです。

それで、このSummation関数とSummationFC関数のコードも確認してみたところ、Summation関数では毎回すべての計算を行なうのに対して、SummationFC関数では前回の計算結果に「新しい値を加えて」「最も古い値を引く」という処理をしています。

つまり例えば100日移動平均線を計算する場合、Summation関数の場合は各足ごとに、現在の足から100日前までの足をすべて足す計算処理をしているのに対して・・

SummationFC関数の場合は、最初の足だけ100日分計算して、次の足からは前の計算結果に現在の足を足して、最も古い100日前の値を引くことで、過去100日分の総和を求めているわけです。

Summation関数のコード(一部)

for Value1 = 0 to Length - 1 
	begin
	Sum = Sum + Price[Value1] ;
	end ;

SummationFC関数のコード(一部)

Sum = Sum[1] + Price - Price[Length] ;

Summation関数の方が、forで繰り返し処理を行なっている分、計算速度が遅くなってしまうというわけです。

で、今回急に計算値が小さくなってしまったのは、それまで5足分の価格の総和を保持していたSum変数が、パラメータを100に変更した後もそのままの値だったため、「 ( 過去5本の足の総和 ) + ( 現在の足の価格 ) - ( 100本前の足の価格 ) / 100」という意味のない計算をしてしまうため、計算がおかしくなってしまったのでした。

SummationFC関数の入力パラメータLengthのコメントにも「このパラメータは一定値であるとする」と但し書きがありますので、SummationFC関数は「関数のパラメータが途中で変わる事はない」前提で、計算を大幅に減らすような工夫がなされている、ということです。


解決策

この動作の解決策については、単純にFC無しの標準関数(Average関数など)を使えばOKです。

計算処理は遅くなってしまいますが、FC系関数の仕様がこうなってる以上仕方ないですね。

処理速度など気になるなら、SummationFC関数のコードを改造して独自の計算関数を作ってみると良いかもしれません。

以上、AverageFC関数の挙動に関するメモでした。