2015年5月15日金曜日

MT4プログラム 注文クラスを作成する~修正版~

いやーちゃんと動かしてみるとバグがいっぱいあるものです。
本ブログに乗っているコードは、バグが含まれている物も多々ありますのでご注意ください^^;;;

ということで以前作成した注文クラスをあれこれ修正したため、公開しておきます。
ちょこちょこと機能も追加しています。
・スプレットフィルタを追加しています。
・ロスカット値の悪い方向への変更は許可しない。
・内部で保持している玉のよく使う情報を取得する補助関数
・条件によってリトライが無限ループする為、最低でもIsStopping()を確認して停止するように。

ロスカット値が悪い方向へ変更されないため、一定時間ごとにModifyOrderAll(20)とか呼び出せば、トレイリングストップが簡単に実現可能です。

2015.05.29 さらに修正の記事を作成しています。

//------------------------------------------------------------------
// 注文関係ラッパークラス
#property copyright "Copyright 2015,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property version   "1.20"
#property strict

#include <Arrays/ArrayInt.mqh>
#include <Custom/TradingDefines.mqh>
#include <stdlib.mqh>
#include <stderror.mqh>

//------------------------------------------------------------------
// 注文関係ラッパークラス
class CTradingWrapper
{
private:
// マジックナンバー
int m_magicNumber;

// 最大ポジション数
int m_maxPosition;

// 最大スプレット
double m_spreadFilter;

// Pips倍率
int m_pipsRate;

// 対象通貨ペア
string m_symbol;

// 保有ポジション
CArrayInt m_positions ;

// タイムアウト値
uint m_timeout;

// 少数桁数
int m_digit;

// 取扱い最小値
double m_point ;
public:
//------------------------------------------------------------------
// コンストラクタ
CTradingWrapper(
string symbol,       //対象通貨ペア
int magicNumber,     //マジックナンバー
int maxPosition,     //最大ポジション数
double spreadFilter, //最大スプレット
uint timeout         //タイムアウト
);

//------------------------------------------------------------------
// デストラクタ
~CTradingWrapper();

//------------------------------------------------------------------
// 発注する
// Return   発注成功時インデックス それ以外-1
int SendOrder(
int cmd,                //売買種別
double volume,          //売買ロット
double price,           //価格
uint slippage,          //許容スリッピング(Pips単位)
uint stoploss,          //ストップロス(Pips単位)
uint takeprofit,        //利確値(Pips単位)
string comment,         //コメント
datetime expiration,    //注文有効期限
color arrowColor        //注文矢印の色
);

//------------------------------------------------------------------
// 成行き決済を行う。
// Return   発注成功時ture それ以外false
bool CloseOrder(
int index,                    //決済するインデックス
uint slippage,                //許容スリッピング
color arrowColor = clrNONE    //注文矢印の色
);

//------------------------------------------------------------------
// 全ポジションを成行き決済を行う。
// Return   発注成功時ture それ以外false
bool CloseOrderAll(
uint slippage,                //許容スリッピング
color arrowColor = clrNONE    //注文矢印の色
);

//------------------------------------------------------------------
// 注文を変更する。
// Return   発注成功時ture それ以外false
bool ModifyOrder(
int index,              //決済するインデックス
uint stoploss,          //ストップロス(Pips単位)
uint takeprofit,        //利確値(Pips単位)
color arrowColor        //注文矢印の色
);

//------------------------------------------------------------------
// すべての注文を変更する。
bool ModifyOrderAll(
uint stoploss,          //ストップロス(Pips単位)
uint takeprofit,        //利確値(Pips単位)
color arrowColor        //注文矢印の色
);

//------------------------------------------------------------------
// 現在のポジション数を取得する。
// Return   現在のポジション数を取得する。
int GetPositionCount(){ return m_positions.Total(); };

//------------------------------------------------------------------
// 最大ポジション数を取得する。
// Return   最大ポジション数を取得する。
int GetMaxPosition(){ return m_maxPosition; };

//------------------------------------------------------------------
// 通貨ペアを取得する。
// Return   最大ポジション数を取得する。
string GetSymbol(){ return m_symbol; };

//------------------------------------------------------------------
// 指定番号のチケットを取得する。
// Return   チケット番号 取得失敗時-1
int GetTicket(
int index         //インデックス
);

//------------------------------------------------------------------
// 指定番号の売買種別 を取得する。
// Return   売買種別
int GetOrderType(
int index         //インデックス
);

//------------------------------------------------------------------
// 指定番号の現在利益を取得する。
// Return   現在利益(Pips)
double GetOrderProfit(
int index         //インデックス
);

//------------------------------------------------------------------
// 指定番号の取引開始時間 を取得する。
// Return   取引開始時間
datetime GetOrderOpenTime(
int index         //インデックス
);

//------------------------------------------------------------------
// 1pipsあたりの値を取得する。
// Return   1pipあたりの値
double GetPipsValue() { return m_point * m_pipsRate; } ;

//------------------------------------------------------------------
// 指定pipsあたりの値を取得する。
// Return   指定pipあたりの値
double GetPipsValue(
double pips    // 指定Pips
){ return m_point * m_pipsRate * pips; } ;

//------------------------------------------------------------------
// ポジションリストを更新する。
void RefreshPositions();

private:

//------------------------------------------------------------------
// 設定パラメータが安全かどうか
// Return 安全ture それ以外false
bool IsSafeParameter();

};

//------------------------------------------------------------------
// コンストラクタ
CTradingWrapper::CTradingWrapper(
string symbol,           //対象通貨ペア
int magicNumber = 0,     //マジックナンバー
int maxPosition = 3,     //最大ポジション数
double spreadFilter = 1, //最大スプレット
uint timeout = 1000      //タイムアウト(ms)
)
{
m_symbol = symbol;
m_magicNumber = magicNumber;
m_maxPosition = maxPosition;
m_timeout = timeout;
m_positions.Resize(m_maxPosition);

//Pips計算 小数点桁数が3or5の場合、Point()*10=1pips
m_digit = (int)MarketInfo(m_symbol, MODE_DIGITS);
m_point = (double)MarketInfo(Symbol(), MODE_POINT);

m_pipsRate = m_digit == 3 || m_digit == 5 ? 10 : 1;

for( int i = 0 ; i < OrdersTotal(); i++)
{
if( OrderSelect(i, SELECT_BY_POS) )
{
if( OrderMagicNumber() == m_magicNumber && OrderSymbol() == m_symbol)
{
m_positions.Add(OrderTicket());
}
}
}
m_spreadFilter = GetPipsValue(spreadFilter);
}

//------------------------------------------------------------------
// デストラクタ
CTradingWrapper::~CTradingWrapper()
{
}

//------------------------------------------------------------------
// 発注する
// Return   発注成功時インデックス それ以外-1
int CTradingWrapper::SendOrder(
int cmd,                      //売買種別
double volume,                //売買ロット
double price,                 //価格(成行き時は現在値を自動設定)
uint slippage,                //許容スリッピング(Pips単位)
uint stoploss = 0,            //ストップロス(Pips単位)
uint takeprofit = 0,          //利確値(Pips単位)
string comment = NULL,        //コメント
datetime expiration = 0,      //注文有効期限
color arrowColor = clrNONE    //注文矢印の色
)
{
if( !this.IsSafeParameter() ) return -1;
if( m_positions.Total() >= m_positions.Max() ) return -1 ;

// 計算用 負数フラグ
int flag = cmd == OP_BUY || cmd == OP_BUYLIMIT || cmd == OP_BUYSTOP ? 1 : -1;

uint start = GetTickCount();
while(!IsStopped())
{
if( (GetTickCount() - start ) > m_timeout ) return -1;

double bid = MarketInfo(m_symbol, MODE_BID);
double ask = MarketInfo(m_symbol, MODE_ASK);

// スプレットが大きすぎる場合は取引しない。
if( NormalizeDouble(MathAbs(ask - bid), m_digit) > m_spreadFilter ) return -1;

if( cmd == OP_SELL ) price = bid;
if( cmd == OP_BUY ) price = ask;

double closeTarget = cmd == OP_BUY || cmd == OP_BUYLIMIT || cmd == OP_BUYSTOP ? ask : bid;

//pips値からストップロス値、利益確定値を取得する。
double stoplossValue = 0 ;
double takeprofitValue = 0;
if( stoploss != 0 )
{
stoplossValue = NormalizeDouble(closeTarget - this.GetPipsValue(stoploss) * flag, m_digit );
}
if( takeprofit != 0)
{
takeprofitValue = NormalizeDouble(closeTarget + this.GetPipsValue(takeprofit) * flag, m_digit );
}

if( IsTradeAllowed() )
{
if( AccountFreeMarginCheck(m_symbol, cmd, volume) < 0 )
{
int errorCode = GetLastError();
Print("AccountFreeMarginCheck Error[",errorCode ,"]");
return -1;
}

int tiket = ::OrderSend(m_symbol, cmd, volume, price,
slippage * m_pipsRate, stoplossValue, takeprofitValue,
comment, m_magicNumber, expiration, arrowColor);

if( tiket >= 0 )
{
m_positions.Add(tiket);
return m_positions.Total() - 1;
}
else
{
int errorCode = GetLastError();

// リトライしても仕方がないエラーの時は終了してしまう。
if( errorCode == ERR_INVALID_PRICE ||
errorCode == ERR_INVALID_STOPS ||
errorCode == ERR_INVALID_TRADE_VOLUME ||
errorCode == ERR_NOT_ENOUGH_MONEY )
{
return -1;
}
}
}
Sleep(100);
}
Print("SendOrder Timeout");
return -1;
}

//------------------------------------------------------------------
// 成行き決済を行う。
// Return   発注成功時ture それ以外false
bool CTradingWrapper::CloseOrder(
int index,                    //決済するインデックス
uint slippage,                //許容スリッピング
color arrowColor = clrNONE    //注文矢印の色
)
{
uint start = GetTickCount();
while(!IsStopped())
{
if( (GetTickCount() - start ) > m_timeout ) return false;

if( IsTradeAllowed() )
{
// オーダーが選択できない状態(すでに存在しない)場合は、リストから削除して終了する。
int targetTicket = m_positions.At(index);
if( !OrderSelect(targetTicket, SELECT_BY_TICKET) )
{
m_positions.Delete(index);
return false;
}

if( OrderClose(OrderTicket(),OrderLots(), OrderClosePrice(), slippage * m_pipsRate, arrowColor) )
{
m_positions.Delete(index);
return true;
}
else
{
int errorCode = GetLastError();

// リトライしても仕方がないエラーの時は終了してしまう。
if( errorCode == ERR_INVALID_TICKET)
{
m_positions.Delete(index);
return false;
}
}
}
Sleep(100);
}

Print("CloseOrder Timeout");

return false;
}

//------------------------------------------------------------------
// 全ポジションを成行き決済を行う。
// Return   発注成功時ture それ以外false
bool CTradingWrapper::CloseOrderAll(
uint slippage,                // 許容スリッピング
color arrowColor = clrNONE    // 注文矢印の色
)
{
RefreshPositions();
bool result = true;
for( int i = m_positions.Total() - 1; i >= 0; i--)
{
result &= this.CloseOrder(i, slippage, arrowColor);
}

return result;
}

//------------------------------------------------------------------
// 注文を変更する。
// Return   発注成功時ture それ以外false
bool CTradingWrapper::ModifyOrder(
int index,                 //決済するインデックス
uint stoploss,             //ストップロス(Pips単位)
uint takeprofit = 0,       //利確値(Pips単位)0は変更なし
color arrowColor = clrNONE //注文矢印の色
)
{
uint start = GetTickCount();
while(!IsStopped())
{
if( (GetTickCount() - start ) > m_timeout ) return false;

if( IsTradeAllowed() )
{
// オーダーが選択できない状態(すでに存在しない)場合は、リストから削除して終了する。
int targetTicket = m_positions.At(index);
if( !OrderSelect(targetTicket, SELECT_BY_TICKET) )
{
m_positions.Delete(index);
return false;
}

// 実際のポジション以外は無視
int cmd = OrderType();
if( cmd != OP_BUY && cmd != OP_SELL )
{
return false;
}

double bid = MarketInfo(m_symbol, MODE_BID);
double ask = MarketInfo(m_symbol, MODE_ASK);

// 計算用 負数フラグ
int flag = cmd == OP_BUY  ? 1 : -1;

double closeTarget = cmd == OP_BUY ? ask : bid;
//pips値からストップロス値、利益確定値を取得する。
double stoplossValue = 0 ;
double takeprofitValue = 0;
if( stoploss != 0 )
{
stoplossValue = NormalizeDouble(closeTarget - this.GetPipsValue(stoploss) * flag, m_digit );
}
else
{
// 0の場合変更しない。
double stop = OrderStopLoss();
if( stop > 0 ) stoplossValue = stop;
}

// 注文ストップ値は下方方向への変更は許可しない。
if((cmd == OP_BUY && stoplossValue <= OrderStopLoss() ) ||
(cmd == OP_SELL && stoplossValue >= OrderStopLoss() ) ) stoplossValue = 0 ;

if( takeprofit != 0)
{
closeTarget = OrderOpenPrice();
takeprofitValue = NormalizeDouble(closeTarget + this.GetPipsValue(takeprofit) * flag, m_digit );
}
else
{
// 0の場合変更しない。
double profit = OrderTakeProfit();
if( stoplossValue > 0 && profit > 0 ) takeprofitValue = profit;
}

// 変更がない場合はコマンドを実行しない。
if( stoplossValue == 0 && takeprofitValue == 0 ) return true;

if( OrderModify(targetTicket, OrderOpenPrice(), stoplossValue, takeprofitValue, 0, arrowColor ))
{
return true;
}
else
{
int errorCode = GetLastError();

// 変更なしだった
if( errorCode == ERR_NO_RESULT ) return true;

// リトライしても仕方がないエラーの時は終了してしまう。
if( errorCode == ERR_INVALID_PRICE ||
errorCode == ERR_INVALID_STOPS ||
errorCode == ERR_INVALID_TRADE_VOLUME ||
errorCode == ERR_NOT_ENOUGH_MONEY)
{
return false;
}
}
}
}
Print("ModifyOrder Timeout");
return false;
}

//------------------------------------------------------------------
// すべての注文を変更する。
bool CTradingWrapper::ModifyOrderAll(
uint stoploss,             //ストップロス(Pips単位)
uint takeprofit = 0,       //利確値(Pips単位)
color arrowColor = clrNONE //注文矢印の色
)
{
RefreshPositions();
bool result = true;
for( int i = m_positions.Total() - 1; i >= 0; i--)
{
result &= this.ModifyOrder(i, stoploss, takeprofit, arrowColor);
}

return result;
}

//------------------------------------------------------------------
// 設定パラメータが安全かどうか
// Return 安全ture それ以外false
bool CTradingWrapper::IsSafeParameter()
{
if( m_symbol == NULL ) return false;
if( m_maxPosition == 0 ) return false;
return true;
}

//------------------------------------------------------------------
// ポジションリストを更新する。
void CTradingWrapper::RefreshPositions()
{
m_positions.Clear();
for( int i = 0 ; i < OrdersTotal(); i++)
{
if( OrderSelect(i, SELECT_BY_POS) )
{
if( OrderMagicNumber() == m_magicNumber && OrderSymbol() == m_symbol)
{
m_positions.Add(OrderTicket());
}
}
}
}

//------------------------------------------------------------------
// 指定番号のチケットを取得する。
// Return   チケット番号 取得失敗時-1
int CTradingWrapper::GetTicket(
int index         //インデックス
)
{
if(index < 0 || index >= m_positions.Total() ) return -1;
return m_positions.At(index);
}

//------------------------------------------------------------------
// 指定番号の売買種別 を取得する。
// Return   売買種別
int CTradingWrapper::GetOrderType(
int index         //インデックス
)
{
int ticket = GetTicket(index);
if( ticket < 0 ) return OP_NONE;
if( OrderSelect(ticket, SELECT_BY_TICKET) ) return OrderType();

return OP_NONE;
}

//------------------------------------------------------------------
// 指定番号の現在利益を取得する。
// Return   現在利益(Pips)
double CTradingWrapper::GetOrderProfit(
int index         //インデックス
)
{
int ticket = GetTicket(index);
if( ticket < 0 ) return 0;
if( OrderSelect(ticket, SELECT_BY_TICKET) )
{
if( OrderType() == OP_BUY )
{
return (OrderClosePrice() - OrderOpenPrice()) / GetPipsValue();
}
else
{
return (OrderOpenPrice() - OrderClosePrice()) / GetPipsValue();
}
}

return 0;
}

//------------------------------------------------------------------
// 指定番号の取引開始時間 を取得する。
// Return   取引開始時間
datetime CTradingWrapper::GetOrderOpenTime(
int index         //インデックス
)
{
int ticket = GetTicket(index);
if( ticket < 0 ) return 0;
if( OrderSelect(ticket, SELECT_BY_TICKET) )
{
return OrderOpenTime();
}
return 0;
}

2015年5月14日木曜日

東京時間がもっとわかりやすくしてほしい。MT4レンジインジケータを改造してみた。

矩形レンジ.PNG
この前作成した東京時間を表示するインジケータですが、友人から東京時間をもっとわかりやすくしたよと改造プログラムを頂きました。線の色が変わってくるぐらいじゃ見えにくいとのことで矩形を表示するようにしたそうです。

ちなみに友人の環境だと、OnInit内のPeriodを取得する処理を入れてしまうと、
M15とH1を切り替えた時にチャートが消える現象が発生するとのことでしたが、私の環境では発生しませんでした。さておき、環境によってOnInit内でPeriod関数が正しく値を返してこない事があるようです。
描画できない時間足の確認はOnCalculateでやったほうがよさそうですね。

このインジケータですがオブジェクトをたくさん生成して少し重たい為、MT4メニューのツール→オプションで変更できるチャートタブ内ヒストリー内最大バー数とチャートの最大バー数を5000にしておくことをお勧めします。
オプション.PNG

//+------------------------------------------------------------------+
//|                                                     MyRange2.mq4 |
//|                                                           Heecho |
//+------------------------------------------------------------------+
#property copyright "Heecho"
#property version   "1.00"
#property strict
#property indicator_chart_window

#define RangeRectangleName "RANGERECT"

#define SecOfDay 60 * 60 * 24

//バッファーを指定する。
#property indicator_buffers 2

//プロット数を指定する。
#property indicator_plots   2

#property indicator_label1  "HIGH"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_DOT
#property indicator_width1  1

#property indicator_label2  "LOW"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrAqua
#property indicator_style2  STYLE_DOT
#property indicator_width2  1

// 入力パラメータ 開始サーバー時間
input int StartHour = 3;

// 入力パラメータ 終了サーバー時間
input int EndHour = 9;

// 入力パラメータ 矩形背景色
input color RectBackColor = clrLavender;

// バッファー
double highBuffer[];
double lowBuffer[];

//------------------------------------------------------------------
//初期化
int OnInit()
{
if ( StartHour < 0 || StartHour > 23 )
{
return INIT_PARAMETERS_INCORRECT;
}
if ( EndHour < 0 || EndHour > 23 )
{
return INIT_PARAMETERS_INCORRECT;
}
if ( StartHour == EndHour )
{
return INIT_PARAMETERS_INCORRECT;
}

SetIndexBuffer( 0, highBuffer );
SetIndexBuffer( 1, lowBuffer );

string short_name = "TR(";
short_name += IntegerToString( StartHour ) + "," + IntegerToString( EndHour ) + ")";
IndicatorShortName( short_name );

return INIT_SUCCEEDED;
}

//------------------------------------------------------------------
//終了処理
void OnDeinit( const int reason )
{
//オブジェクトを作成する。
long chartId = ChartID();

int total = ObjectsTotal( chartId );
//生成したオブジェクトを削除する。
//0から削除するとインデックス位置がずれて
//正しく削除できないため、後ろから削除するようにする。
for( int i = total - 1; i >= 0 ; i--)
{
string name = ObjectName( chartId, i );

// 先頭文字列がRangeRectangleNameと一致する場合、削除する。
if ( StringFind( name, RangeRectangleName ) == 0 )
{
ObjectDelete( chartId, name );
}
}
}

//------------------------------------------------------------------
//計算イベント
int OnCalculate
( const int rates_total       // 各レート要素数
, const int prev_calculated   // 計算済み要素数
, const datetime& time[]      // 要素ごとの時間配列
, const double& open[]        // オープン価格配列
, const double& high[]        // 高値配列
, const double& low[]         // 安値配列
, const double& close[]       // クローズ価格配列
, const long& tick_volume[]   // ティック数(要素の更新回数)
, const long& volume[]        // 実ボリューム(?)
, const int& spread[]         // スプレット
)
{
// 時間足以下のみ有効
int priod = Period();
if ( priod > PERIOD_H1 )
{
return rates_total;
}

// 開始時間から終了時間までの高値安値を更新する。
double highValue = 0;
double lowValue = 100000;

long chartID = ChartID();
for ( int i = rates_total - prev_calculated - 1 ; i >= 0; i-- )
{
MqlDateTime tm;
TimeToStruct( time[ i ], tm );
tm.hour = StartHour;
tm.min = 0;
tm.sec = 0;
datetime startTime = StructToTime( tm );
tm.hour = EndHour;
datetime endTime = StructToTime( tm );
if ( StartHour > EndHour )
{
int hour = TimeHour( time[ i ] );
if ( hour < StartHour )
{
startTime -= SecOfDay;
}

if ( hour < 24 )
{
endTime += SecOfDay;
}
}

string rectName = GetRectangleName( startTime );
int rectID = ObjectFind( chartID, rectName );
if ( rectID < 0 )
{
CreateRectangleObject( rectName, startTime, endTime, RectBackColor );
}

if ( i < rates_total - 1 )
{
highValue = highBuffer[ i + 1 ];
lowValue = lowBuffer[ i + 1 ];
}

int hour = TimeHour( time[ i ] );
if ( hour == StartHour
&& TimeMinute( time[ i ] ) == 0
&& TimeSeconds( time[ i ] ) == 0
)
{
highValue = high[ i ];
lowValue = low[ i ];
rectID = ObjectFind( chartID, rectName );
ObjectMove( chartID, rectName, 0, startTime, highValue );
ObjectMove( chartID, rectName, 1, endTime, lowValue );
}
else if ( ( StartHour < EndHour &&  StartHour <= hour && hour < EndHour )
|| ( StartHour > EndHour && ( StartHour <= hour || hour < EndHour ) )
)
{
bool changed = false;
if ( high[ i ] > highValue )
{
highValue = high[ i ];
changed = true;
}
if ( low[ i ] < lowValue )
{
lowValue = low[ i ];
changed = true;
}
if ( changed != false )
{
// 高値安値が切り替わったので過去にさかのぼって更新する。
for ( int j = i + 1; j < rates_total; j++ )
{
int currentHour = TimeHour( time[ j ]);
if ( StartHour <= hour && currentHour < StartHour )
{
break;
}
if ( StartHour > hour && currentHour < StartHour && TimeDay( time[ j ] ) != TimeDay( time[ i ] ) )
{
break;
}

highBuffer[ j ] = highValue;
lowBuffer[ j ] = lowValue;
}

rectID = ObjectFind( chartID, rectName );
ObjectMove( chartID, rectName, 0, startTime, highValue );
ObjectMove( chartID, rectName, 1, endTime, lowValue );
}
}
highBuffer[ i ] = highValue;
lowBuffer[ i ] = lowValue;
}

return rates_total - 1;
}

//------------------------------------------------------------------
//矩形名を取得する。
string GetRectangleName
(
datetime time        //現在時刻
)
{
return RangeRectangleName + TimeToString( time );
}

//------------------------------------------------------------------
//矩形を生成する。
bool CreateRectangleObject
( string name
, datetime startTime    // 開始時間
, datetime endTime      // 終了時間
, color backColor       // 背景色
)
{
//オブジェクトを作成する。
long chartId = ChartID();

//チャート縦幅いっぱいに表示する。
double min = 0;
double max = ChartGetDouble( chartId, CHART_PRICE_MAX ) * 4;

if ( ObjectCreate( chartId, name, OBJ_RECTANGLE, 0, startTime, max, endTime, min ) == false )
{
return false;
}
ObjectSetInteger( chartId, name, OBJPROP_COLOR, backColor );

return true;
}

//------------------------------------------------------------------
//矩形を削除する。
bool DeleteRectangleObject(
datetime startTime   //開始時刻
)
{
//オブジェクトを作成する。
long chartId = ChartID();
string name = GetRectangleName( startTime );

if( ObjectDelete( chartId, name) )
{
}

return true;
}


2015年5月13日水曜日

ストキャス値のデッドクロスで買う!EA開発には教科書とは違う発想が必要かも?

ちょっとEAの動作検証でバグってあれっ?って思った事を書きたいと思います。

買われすぎ売られすぎのオシレータとして有名どころでストキャスティクスがあります。
実際どうなの?と思い、EAにさくっと組み込んでバックテストを行っていました。しばらく作業していて売りと買いが逆転している事に気が付き直してみると・・・・。

EURUSD 15分足でのストキャスティクス売買結果 2014/1/1~2014/12/31の1年間

EAを考える2.PNG
20以下のゴールデンクロスで売り、80以上のデッドクロスで買い

EAを考える.PNG
20以下のゴールデンクロスで買い、80以上のデッドクロスで売り

手じまいは単純に+-30に利益確定と損切を注文時に設定しています。
また、始値で前回のクロスが確定した段階で売り買いしています。

どっちも負けてるのですが、バグって売り買い逆転していた方が、何やら希望が持てるバックテスト結果になっています。これはストキャスのクロスが確定した瞬間にはもう値動きが十分に終わってしまっていて、そのあとの戻りをとっているためのようです。

と思ってブログを書いていたらFX-ONにもっと良い記事が載っていました^^
mmadvtさんが書かれている「オシレーターの買われすぎで買って、売られすぎで売るという戦略
 昨日のバグの件といい、mmadvtさんの記事といい、こういった違う考え方が必要なんだなと改めて考えさせれました。

 移動平均についても、ストキャスと移動平均を組み合わせて考える際、ストキャスのシグナル発生時に移動平均と反対方向に売買したほうが結果がいいという実験結果が出たりすることもあり
 たとえば、15分足のストキャスのデッドクロスで売りとした場合、1時間足の移動平均が買いの時にポジションを持った方が勝てる確率が高くなったりします。2014年のEURUSDがとにかく売り相場だった為発生している現象のようです。
 相場っておもしろいなーと思う事とばかりです。

 さぁ頑張ってEAつくるぞー!

 ちなみに昨日アップした記事のプログラムにバグがありました。
MT4プログラムの小ネタ 時間別に横線を引く。/線の途中で色を変える。」指摘してくれた友人に感謝です。
 ちなみに開始時間を 冬時間/夏時間の場合、取引がない時間帯にしてしまうと、線が正しく引けません。あー判定プログラムを入れれば対応できるんだろうなーと思いつつ現在放置中・・・(^^;;



2015年5月12日火曜日

MT4プログラムの小ネタ 時間別に横線を引く。/線の途中で色を変える。

線の途中で色を変える.PNG

MT4でチャート上に線を引きたい。線の途中で色を変えたい。ラインを使って高値安値を複数表現すると線がつながってしまってチャートが汚くなる! TrendLineオブジェクトだとずーっと表示されてしまうので邪魔!などの問題点・・どうやって解決すればよいのでしょうか?

答えとしてはバッファをたくさん作るということのようです。

MT4のライン描画用に確保したバッファですが、初期値のままにしておくと描画しないという特徴があります。これを利用して複数バッファをまたいで同じラインを描画すると、線の途中で途切れさせたり、色を変えたりできます。

サンプルとして「東京時間をぶっ壊せ!MT4で時間レンジブレイク向けインジケータを作ってみた。」を東京時間とそうでない場合色を変えてみました。

//------------------------------------------------------------------
// 時間帯別レンジ表示

#property copyright "Copyright 2015,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property version   "1.10"
#property strict
#property indicator_chart_window

// 正式なデータではない値
#define NONDATAVALUE 10000

//バッファーを指定する。
#property indicator_buffers 4

//プロット数を指定する。
#property indicator_plots   4

#property indicator_label1  "HIGH"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAqua
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

#property indicator_label2  "LOW"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrIndianRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1

#property indicator_label3  "ZONEHIGH"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrYellow
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1

#property indicator_label4  "ZONELOW"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrWhite
#property indicator_style4  STYLE_SOLID
#property indicator_width4  1

// 入力パラメータ 開始サーバー時間
input int StartHour = 3;

// 入力パラメータ 終了サーバー時間
input int EndHour = 9;

// バッファー
double highBuffer[];
double lowBuffer[];
double zoneHighBuffer[];
double zoneLowBuffer[];

//------------------------------------------------------------------
//初期化
int OnInit()
{
// 時間足以下のみ有効
int priod = Period();
if( priod > PERIOD_H1 ) return INIT_PARAMETERS_INCORRECT;

if( StartHour < 0 || StartHour > 23 ) return INIT_PARAMETERS_INCORRECT;
if( EndHour < 0 || EndHour > 23 ) return INIT_PARAMETERS_INCORRECT;
if( StartHour == EndHour ) return INIT_PARAMETERS_INCORRECT;

SetIndexBuffer(0, highBuffer);
SetIndexBuffer(1, lowBuffer);
SetIndexBuffer(2, zoneHighBuffer);
SetIndexBuffer(3, zoneLowBuffer);

string short_name = "TR(";
short_name += IntegerToString(StartHour) + "," + IntegerToString(EndHour) + ")";
IndicatorShortName(short_name);

return(INIT_SUCCEEDED);
}

//------------------------------------------------------------------
//計算イベント
int OnCalculate(const int rates_total,          //各レート要素数
const int prev_calculated,      //計算済み要素数
const datetime &time[],         //要素ごとの時間配列
const double &open[],           //オープン価格配列
const double &high[],           //高値配列
const double &low[],            //安値配列
const double &close[],          //クローズ価格配列
const long &tick_volume[],      //ティック数(要素の更新回数)
const long &volume[],           //実ボリューム(?)
const int &spread[])            //スプレット
{
// 開始時間から終了時間までの高値安値を更新する。
double highValue = 0;
double lowValue = 100000;

for( int i = rates_total - prev_calculated - 1 ; i >= 0; i-- )
{
if( i < rates_total - 1 )
{
highValue = highBuffer[i + 1] > NONDATAVALUE ? zoneHighBuffer[i + 1] : highBuffer[i + 1];
lowValue = lowBuffer[i + 1] > NONDATAVALUE ? zoneLowBuffer[i + 1] : lowBuffer[i + 1];
}

int hour = TimeHour(time[i]);

if( hour == StartHour && TimeMinute(time[i]) == 0 && TimeSeconds(time[i]) == 0 )
{
highValue = high[i];
lowValue = low[i];
zoneHighBuffer[i] = highValue;
zoneLowBuffer[i] = lowValue;

}
else if( ( StartHour < EndHour &&  StartHour <= hour && hour < EndHour) ||
( StartHour > EndHour && (StartHour <= hour || hour < EndHour)))
{
bool changeFlag = false;
if( high[i] > highValue )
{
highValue = high[i];
changeFlag = true;
}
if( low[i] < lowValue )
{
lowValue = low[i];
changeFlag = true;
}
if( changeFlag )
{
// 高値安値が切り替わったので過去にさかのぼって更新する。
for( int j = i + 1; j < rates_total; j++ )
{
int currentHour = TimeHour(time[j]);
if( StartHour <= hour && currentHour < StartHour ) break;
if( StartHour > hour && currentHour < StartHour && TimeDay(Time[j]) != TimeDay(time[i]) ) break ;

zoneHighBuffer[j] = highValue;
zoneLowBuffer[j] = lowValue;
}
}
zoneHighBuffer[i] = highValue;
zoneLowBuffer[i] = lowValue;
}
else
{
// 線をつなげるために切り替え時間までは描画する。
if( hour == EndHour && TimeMinute(time[i]) == 0 && TimeSeconds(time[i]) == 0 )
{
zoneHighBuffer[i] = highValue;
zoneLowBuffer[i] = lowValue;
}
highBuffer[i] = highValue;
lowBuffer[i] = lowValue;
}
}

return(rates_total - 1);
}


高値を例にとると、highBufferとzoneHighBufferでバッファを使い分けることで色変えを実現しています。東京時間の間はzoneHighBufferを更新して、highBuffer側は初期値のままにしています。そのため、東京時間ではhighBufferは描画されず、zoneHighBufferのみ描画されています。東京時間以外では逆のことを行っています。こうすることで、単純な横線と色変えを実現してみました。

2015.5.12 月が切り替わった際、表示が崩れる不具合修正
過去データを更新する際に、日付の切り替わりを<で比較していましたが、23時から6時とかの設定をされると、表示が崩れる障害がありました。
TimeDay(Time[j]) < TimeDay(time[i])

TimeDay(Time[j]) != TimeDay(time[i])
と修正しました。

2015年5月11日月曜日

東京時間をぶっ壊せ!MT4で時間レンジブレイク向けインジケータを作ってみた。

派手なタイトルをつけてみましたが、ようは東京時間の高値安値を表示するだけのインジケータです。(笑)

2015.07.21追記
改良版のプログラムが以後にいくつか乗っていますので、そちらも参考にしてみてください。単純にラインを引くだけなら、時間別に横線を引くやつの方が見た目が良いです^^;;
・MT4プログラムの小ネタ 時間別に横線を引く。/線の途中で色を変える。

もっとわかりやすくしてほしい。MT4レンジインジケータを改造してみた。

MT4インジケータ 各国の取引時間を表示する。

EURUSDを見ていると東京時間はうろうろした後、ヨーロッパ勢が参加すると一方方向に動き出す傾向があるように思われます。そこで、東京時間をブレイクした方向に取引する際わかりやすいようにグラフ上に線を引くインジケータを作ってみました。

入力パラメータであるStartHourとEndHourはサーバー時間ベースです。私が使用しているOANDAですと現在GMT+3で表示されていますので、3で9時を表して9で15時を表しています。

TimeRange.PNG

こうやって見ると確かにブレイク後しばらくはその方向動いているように思われます。
単純にブレイクした方向にポジションを持って2時間後に決済という戦略でも、そこそこ取れそうに思われます。長期では確認していませんが、ありかも?と思わせるグラフになりました。

なお、移動平均の方向も合わせて確認するのに「シンプルだけど効果あり!MT4でDrawSimpleLineインジケータを作ってみた。」で作成したDSLを合わせて表示しています。

東京時間ブレイクでポジションオープン、DSLを使って早めに切るのであれば短期差分の方向性が変わった際に手じまい、少し粘るなら短期長期がクロスした場合手じまいといった戦略でしょうか?ちょっと機会をみてEAで検証したいと思います。


FX-ONブログランキングにご協力よろしくお願いいたしますm(_ _ )m

//------------------------------------------------------------------
// 時間帯別レンジ表示

#property copyright "Copyright 2015,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property version   "1.00"
#property strict
#property indicator_chart_window

//バッファーを指定する。
#property indicator_buffers 2

//プロット数を指定する。
#property indicator_plots   2

#property indicator_label1  "HIGH"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAqua
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

#property indicator_label2  "LOW"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrIndianRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1

// 入力パラメータ 開始サーバー時間
input int StartHour = 3;

// 入力パラメータ 終了サーバー時間
input int EndHour = 9;

// バッファー
double highBuffer[];
double lowBuffer[];

//------------------------------------------------------------------
//初期化
int OnInit()
{
// 時間足以下のみ有効
int priod = Period();
if( priod > PERIOD_H1 ) return INIT_PARAMETERS_INCORRECT;

if( StartHour < 0 || StartHour > 23 ) return INIT_PARAMETERS_INCORRECT;
if( EndHour < 0 || EndHour > 23 ) return INIT_PARAMETERS_INCORRECT;
if( StartHour == EndHour ) return INIT_PARAMETERS_INCORRECT;

SetIndexBuffer(0, highBuffer);
SetIndexBuffer(1, lowBuffer);

string short_name = "TR(";
short_name += IntegerToString(StartHour) + "," + IntegerToString(EndHour) + ")";
IndicatorShortName(short_name);

return(INIT_SUCCEEDED);
}

//------------------------------------------------------------------
//計算イベント
int OnCalculate(const int rates_total,          //各レート要素数
const int prev_calculated,      //計算済み要素数
const datetime &time[],         //要素ごとの時間配列
const double &open[],           //オープン価格配列
const double &high[],           //高値配列
const double &low[],            //安値配列
const double &close[],          //クローズ価格配列
const long &tick_volume[],      //ティック数(要素の更新回数)
const long &volume[],           //実ボリューム(?)
const int &spread[])            //スプレット
{
// 開始時間から終了時間までの高値安値を更新する。
double highValue = 0;
double lowValue = 100000;

for( int i = rates_total - prev_calculated - 1 ; i >= 0; i-- )
{
if( i < rates_total - 1 )
{
highValue = highBuffer[i + 1];
lowValue = lowBuffer[i + 1];
}

int hour = TimeHour(time[i]);

if( hour == StartHour && TimeMinute(time[i]) == 0 && TimeSeconds(time[i]) == 0 )
{
highValue = high[i];
lowValue = low[i];
}
else if( ( StartHour < EndHour &&  StartHour <= hour && hour < EndHour) ||
( StartHour > EndHour && (StartHour <= hour || hour < EndHour)))
{
bool changeFlag = false;
if( high[i] > highValue )
{
highValue = high[i];
changeFlag = true;
}
if( low[i] < lowValue )
{
lowValue = low[i];
changeFlag = true;
}
if( changeFlag )
{
// 高値安値が切り替わったので過去にさかのぼって更新する。
for( int j = i + 1; j < rates_total; j++ )
{
int currentHour = TimeHour(time[j]);
if( StartHour <= hour && currentHour < StartHour ) break;
if( StartHour > hour && currentHour < StartHour && TimeDay(time[j]) != TimeDay(time[i]) ) break ;

highBuffer[j] = highValue;
lowBuffer[j] = lowValue;
}
}
}
highBuffer[i] = highValue;
lowBuffer[i] = lowValue;
}

return(rates_total - 1);
}


指定範囲内の場合、高値安値を更新するというプログラムになっています。
処理の割にはコードが複雑になってしまいました。
ぱっとアルゴリズムが思い浮かばなかったため、かなりがりがり力技で対応しています。うーん修行が足りません。