2011/01/12

バックテスト(自動)時代 - ForexTester2ストラテジプログラミング③(独自ログの出力)



さて、前回は、ストラテジでインジケータを利用する方法や注意事項、苦労話を書きました。

今回は、独自にログを出力する方法について記載します。ForexTester2もバックテスト結果をExcelやCSVに出力できるのですが、独自ログのメリットは、詳細な分析や効率化、データマイニング用のストラテジ作成などではないでしょうか。


【独自ログ出力のメリットと背景】
Print文でデバッグログ出力ロジック書いていても、「Strategy Optimizer」からストラテジを
 実行してもログ出力されない。
「Strategy Optimizer」で一括で流すと、最後のパラメータでのトレード情報しか残らない。
 他はサマリの出力だけになってしまう。
 →なので効率化の為、複数通貨ペアを連続で流したい場合には独自ログ出力ロジックが
  必要になってしまう。
スィーニーのMAE/MFE分析をしたかった。→参考書籍「トレーディングシステム入門
R値(初期リスク)による期待値を求めたかった。 →参考書籍「新版 魔術師たちの心理学


●ログ出力基本ロジック
 1.ログファイルの生成
   ・グローバル変数として、

hLogFile : TextFile;
を定義。

   ・ResetStrategy関数内に以下の様に記述。

  try
     AssignFile(hLogFile,LogFileName);
     if Not(FileExists(LogFileName)) then ReWrite(hLogFile);
     Append(hLogFile);
  except
     try CloseFile(hLogFile); except end;
  end;
  try CloseFile(hLogFile); except end;
"LogFileName"は、出力するログファイル名を絶対パス名で記述します。
    この仕様では、もしファイルが無ければ自動生成します。
    
 2.ログの出力共通関数の用意
   以下の様な関数を定義します。

{ ------------------ Write Log ---------------------------------}
function WriteLog(const str:String) : Boolean;
begin
  result := True;
  try
     AssignFile(hLogFile,LogFileName);
     Append(hLogFile);
  except result := False; exit; end;
  try
     WriteLn(hLogFile,str);
  except
    try CloseFile(hLogFile); except result:=False; exit; end;
  end;
  try CloseFile(hLogFile); except end;
end;
出力ごとに改行されます。測定性能上、本当はログ一行毎にCloseFileしたくなかったのです。
   しかし、ストラテジ実行完了タイミングをとれる方法が無く、このタイミングでクローズ
   しなければForexTester2を終了させないと、ログファイルの移動/削除ができないため、
   泣く泣く毎回ファイルのクローズをするハメになりました。
   誰か、プログラム内でストラテジ実行完了タイミングを知る方法教えて!

 3.ログ出力関数の使い方

WriteLog(str);
これで、str内の文字列に改行を付与してファイル出力します。


 4.ファイルハンドルの開放
    DoneStrategy関数内に、以下の様なプログラムを書きます。

  try
  CloseFile(hLogFile);
  except end;


●独自ログ出力機能を使って、MAE/MFE分析やR期待値を出力する方法
 さて、前述のパーセントベースによるMAE/MFE分析やR期待値を出力する為のロジックです。

 1.情報を格納する構造体を定義する。
   冒頭のvarセクションより前に記載してください。

  type Stat = record
       Ticket : integer;
       OrderType : integer;
       Profit : double;
       CloseTime : TDateTime;
       OrderPrice: double;
       StopPrice : double;
       Lots : double;
       OpenPrice : double;
       ClosePrice : double;
       OrderBars : integer;
       OrderOpenBars : integer;
       OrderCloseBars : integer;
       PriceList : String;
       UpdateBars : integer;
       MAEPrice : double;
       MAEBars : integer;
       MFEPrice : double;
       MFEBars : integer;
       MAEfePrice : double;
 end;

 2.実際に情報を格納する構造体変数をグローバル変数として定義

   StatBsc:Stat;

 3.オーダー完了後呼び出す関数を用意する

   この関数を呼び出す場合は、注文をオーダした後呼び出します
   引数1(Stat):上記「2.」で定義したグローバル変数Statを指定してください
   引数2(vTicket)
        オーダ用API呼出し後取得できるOrderHandleでGetOrderInfo関数でOrderInfo
        を取得後、OrderInfo.ticketを渡してください。
   引数3(vOrderType)
        ロングポジションの場合は1、ショートポジションの場合は-1を指定してください。
   引数4~6(vOrderPrice,vStopPrice,Lots)
        注文指値/逆指値、ストップ価格、ロット数を指定してください。
        指定無い場合0でOKです。

{-----Stat Class Functions---------------------------------------------}
function StatAtOrder(var Obj:Stat; vTicket : integer; vOrderType:integer; vOrderPrice:double; 

vStopPrice:double; Lots:double):Boolean;
begin
     result := True;

     Obj.Ticket := vTicket;
     Obj.OrderType :=vOrderType;
     Obj.Profit :=0;
     Obj.CloseTime := TimeCurrent;
     Obj.OrderPrice:=vOrderPrice;
     Obj.OpenPrice :=0;
     Obj.StopPrice := vStopPrice;
     Obj.Lots := Lots;
     Obj.ClosePrice :=0;
     Obj.OrderBars :=Bars();
     Obj.OrderOpenBars :=0;
     Obj.OrderCloseBars :=0;
     Obj.PriceList := '';
     Obj.UpdateBars := 0;
     Obj.MAEPrice := 0;
     Obj.MAEBars := 0;
     Obj.MFEPrice := 0;
     Obj.MFEBars := 0;
     Obj.MAEfePrice := 0;
end;

 4.ポジション保有中毎回呼び出し、情報を採取する関数を用意する
   この関数は、ポジション保有中は毎回呼び出してください
   引数1(Stat):上記「2.」で定義したグローバル変数Statを指定してください

function StatStore(var Obj:Stat):Boolean;
var
   price,diffPrice : double;
begin
     result := True;

     // If Order is not opened then exit;
     if Obj.OpenPrice=0 then exit;

     // Update MFE and MAE
     if Obj.OrderType>0 then
     begin
          if Bid>Obj.MFEPrice then
          begin
               Obj.MFEPrice := Bid;
               Obj.MFEBars:=Bars();
          end;
          if Bid<Obj.MAEPrice  then begin Obj.MAEPrice := Bid; Obj.MAEBars := Bars(); end;
          if (Bid<Obj.MFEPrice) AND ((Obj.MFEPrice-Bid)>Obj.MAEfePrice) then 
              Obj.MAEfePrice:=Obj.MFEPrice-Bid;
     end;
     if Obj.OrderType<0 then
     begin
          if Ask<Obj.MFEPrice  then
          begin
               Obj.MFEPrice := Ask;
               Obj.MFEBars := Bars();
          end;
          if Ask>Obj.MAEPrice then begin Obj.MAEPrice := Ask; Obj.MAEBars:=Bars(); end;
          if (Ask>Obj.MFEPrice) AND ((Ask-Obj.MFEPrice)>Obj.MAEfePrice) then 
              Obj.MAEfePrice:=Ask-Obj.MFEPrice;
     end;

     // Update Price list
     if Obj.UpdateBars < Bars() then
     begin
          price := (Close(1)-Obj.OpenPrice)*Obj.OrderType/Obj.OpenPrice*100;
          Obj.PriceList := Obj.PriceList + format(',%f',[price]);
          Obj.UpdateBars := Bars();
     end;
end;

 5.ポジションが閉じた時に呼び出す関数を用意する
   この関数は、ストップロスにひっかかった場合も含めて、ポジションが閉じた場合に
   呼び出してください。
   引数1(Stat):上記「2.」で定義したグローバル変数Statを指定してください
   

function StatAtClose(var Obj:Stat): Boolean;
begin
     result := True;

     if Not(OrderSelect(Obj.Ticket,SELECT_BY_TICKET,MODE_HISTORY)) then exit;
     Obj.Profit := OrderProfit;
     Obj.ClosePrice := OrderClosePrice;
     Obj.OrderCloseBars := Bars();
     Obj.CloseTime := OrderCloseTime;
end;

 6.オーダ閉じた後にファイルに出力する関数
   以下の関数を用意し、上記「5.」と同じタイミングで、PrintOrderData()を呼び
   出してください。
   これで、ファイルに1トレード分のデータが出力されます。
   また、LogOutというBoolean型変数を用意してください。Trueの時のみログ出力されます。
   Falseでは出力されません。
   これをストラテジのパラメータ化することにより、テスト毎にログ出力したりしなかったり
   毎回指定できる様になります。
   また現在未使用ですが、IgnoredOrderCountをグローバル変数としてinteger型で
   定義してください。

function StatGetPrintStr(var Obj:Stat) : String;
var
   MAE,MFE,MAEfe,FTE,ETD,RR,RPriceUnit,RPer : double;
   tradeBars : integer;
   closetime : String;
begin
     result := '';
     MAE := (Abs(Obj.MAEPrice-Obj.OpenPrice)/Obj.OpenPrice)*(-1)*100;
     MFE := Abs(Obj.MFEPrice-Obj.OpenPrice)/Obj.OpenPrice*100;
     ETD := Abs(Obj.MFEPrice-Obj.ClosePrice)/Obj.MFEPrice*100;
     FTE := (Obj.ClosePrice-Obj.OpenPrice)*(Obj.OrderType)/Obj.OpenPrice*100;
     MAEfe := (Obj.MAEfePrice)/Obj.MFEPrice*(-1)*100;
     RR := 0;
     RPriceUnit := 0;
     RPer := 0;
     if Obj.StopPrice<>0 then
     begin
          RR := (Obj.ClosePrice-Obj.OpenPrice)*(Obj.OrderType)/Abs(Obj.OpenPrice-Obj.StopPrice)*100;
          RPriceUnit := Abs(Obj.OpenPrice-Obj.StopPrice);
          RPer := (Abs(Obj.OpenPrice-Obj.StopPrice)/Obj.OpenPrice)*100*Obj.Lots*10;
     end;
     tradeBars := Obj.OrderCloseBars-Obj.OrderOpenBars;
     closetime := FormatDateTime('yyyy/mm/dd hh:nn:ss',Obj.CloseTime);

     result := format('1,<<%s>>,%d,%s,%d,%d,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%d%s',[
                   Currency,
                   Obj.Ticket,
                   closetime,
                   Obj.OrderType,
                   IgnoredOrderCount,
                   Obj.Lots,
                   Obj.Profit,
                   MAE,
                   MFE,
                   ETD,
                   FTE,
                   MAEfe,
                   RPriceUnit,
                   RPer,
                   RR,
                   tradeBars,
                   Obj.PriceList
                   ]);
end;

function PrintOrderData() : Boolean;
var
   str : String;
begin
     result := True;

     if StatBsc.OpenPrice<>0 then
     begin
          str := StatGetPrintStr(StatBsc);
          if LogOut then WriteLog(str);
     end;
end;

●上記MAE/MFE分析やR期待値を出力結果フォーマット
 以下のフォーマットがカンマ(,)区切りのCSV形式で出力されます。

 ---------------------------------
 Flg Currency Tickets CloseTime Type IgnoreCount Lots 損益($)
 MAE MFE ETD 損益(%) MAEfe RPriceUnit Rper RR Bars
  価格推移
---------------------------------

 flg:1固定。これはExcelで集計する際に利用するためだけの内容です。
 Currency:通貨ペアを"<<USDJPY>>"の様な形式で出力されます。
 IgnoreCount:現在未使用。
 Type:ロングポジションであれば1、ショートポジションであれば-1が出力されます。
 MAE/MFE/ETD/損益(%)/MAEfe:パーセンテージベースで出力されます。
 RPriceUnit:ストップロスと発注額の価格差の絶対値が出力されます。
 RPer:パーセンテージベースの、ストップロスと発注額の価格差絶対値が出力されます。
 RR:パーセンテージベースのR値が出力されます。
 Bars:ポジション発注約定から手仕舞いまでのバー数が出力されます
 価格推移:ポジション保有中のデフォルトTimfremeバー毎のClose価格が発注価格
       からのパーセンテージベースで出力されます。



ということで、バグ報告お願い!!!!



で、この情報を集計したりするExcelファイルをVectorに公開したいのですが、まだ審査とおってません。。
#だれか、もっとリアルタイムでファイルを公開する方法教えて!!

今はVectorのライブラリ登録アカウント申請している最中なので時間かかりますが、
ライブラリ投稿でも1wk程度かかるとのこと。
今後もブログ投稿時に、集計用Excelをリアルタイムに公開したいんです!

スィーニーのMAE/MFE分析や、R値については、きっとブログに書かれる日が来るでしょう。。

そういや、今回英語には悩まなかった!!!
やったー!!


そして、「FXシステムトレード初心者奮闘記」のストラテジプログラミング集がまたまた続くのでした。
P.S.そうは言ってもForexTester2気に入っています。

0 件のコメント:

コメントを投稿