見出し画像

逆張りナンピンEAをつくる(6)

今回も引き続き、ナンピン丸こと逆張りナンピンEAをつくっていく。

振り返り

今までのコードの振り返りをする。

新しいバーの出現時のみに、注文と損切りをするかどうかを判断する。という全体のロジックはできている。注文時のいろいろができてないので今回はそこをやる。

損切りの判断のロジックは自分が持っているポジションの方向とADXをDI+/-を見ながら考えるとする。

注文のロジック

ナンピンと損切りに使うであろう注文の単位を計算する。今回は確定した最新のローソク足を含めた過去100本のローソク足の終値の最大値と最小値の差の100分の1をその単位とする。以下の関数がその関数

void GetOrderUnit()
 {
  int ref_count = 100;
  
  double max_price = ArrayMaximum(close_buffer, 1, ref_count);
  double min_price = ArrayMinimum(close_buffer, 1, ref_count);
  double order_unit = (max_price - min_price) / ref_count;
  
  return order_unit;
 }

ref_count は外に出して定数かしたほうがいい気もする。あと、等差じゃなくてフィボナッチ使ったほうがいいかもしらへん。けど、現段階でそれは重要じゃないので飛ばす。

次にポジションを取る方向を考える。名前の通り逆張りなんでDI+/- をつかってどちらかのポジションを取る。

まずはトレンドを表すenumを定義する

enum TREND
 {
  UP_TREND = 1,
  DOWN_TREND = -1
 };

次に、DIの値を格納する配列の作成、添え字を0が最新にする、プログラムが終了するときに配列を開放する。

double plus_di_buffer[];
double minus_di_buffer[];
ArraySetAsSeries(plus_di_buffer, true);
ArraySetAsSeries(minus_di_buffer, true);
ArrayFree(plus_di_buffer, true);
ArrayFree(minus_di_buffer, true);

DIのインディケーターを初期化しようとドキュメントを見ていると知らない単語が出てきた。

注意事項
バッファ番号は 0 - MAIN_LINE、1 - PLUSDI_LINE、2 - MINUSDI_LINE です。

CopyBuffer では、バッファ番号を指定してバッファをしていすることができるみたい。

int  CopyBuffer(
 int      indicator_handle,    // 指標ハンドル
 int      buffer_num,          // 指標バッファ番号
 int      start_pos,           // 開始位置
 int      count,               // 複製する量
 double    buffer[]            // 受け取り側の配列
);

今まで、何も考えずに0を入れてたところだった。ADXインディケーターを使って、DIの値を取り出す。

CopyBuffer(adx_handle, MAIN_LINE,   0, 2, adx_buffer);
CopyBuffer(adx_handle, PLUSDI_LINE, 0, 2, plusdi_buffer);
CopyBuffer(adx_handle, MINUSDI_LINE,0, 2, minusdi_buffer);

それを使ってトレンドを判断する。ロジックはとても簡易的なもの。

TREND GetCurrentTrend()
 {
  TREND trend = (plusdi_buffer[1] > minusdi_buffer[1]) ? UP_TREND : DOWN_TREND;
  
  return trend;
 }

注文には CTrade.OrderOpen を使う。

bool  OrderOpen(
 const string          symbol,         // シンボル
 ENUM_ORDER_TYPE       order_type,     // 注文の種類
 double                volume,         // 注文のボリューム
 double                limit_price,    // ストップリミット価格
 double                price,          // 実行価格
 double                sl,             // 決済逆指値
 double                tp,             // 決済指値
 ENUM_ORDER_TYPE_TIME  type_time,      // 期限によっての種類
 datetime              expiration,     // 期限
 const string          comment=""      // コメント

ここまでを合わせると以下の通り

void Order()
 {
  ORDER_TYPE order_type;
  double     volume;

  if(position_info.Select(_Symbol))
    {
     //-- Average Order
     order_type = position_info.PositionType() == POSITION_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
     volume = position_info.RequestVolume() * 2;
    }
  else
    {
      //-- First Order
     order_unit = GetOrderUnit();

     order_type = GetCurrentTrend() == DOWN_TREND ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
     volume = FIRST_VOLUME;
    }

  trade.OrderOpen(
     _Symbol,
     order_type,
     volume
  );
 }

オーダーがある場合は、そのオーダと同じ向きで前回注文の量の2倍を注文する。ない場合は、トレンドと逆向きで指定された初期の量を注文する。

RequestVolume vs ResultVolume

前回の注文の量(Volume)を取得するときの関数が2つあった。

RequestVolume は以下の通り

直近のリクエストで使用された(ロット単位の)取引高を取得します。

ResultVolumeは以下の通り

約定または注文のボリュームを取得します。

注文した量と注文が通った量の二つを別に取得できるみたい。今回は指値ではないので注文したらすぐに通るという前提で考えている。大きい量を取引したり、取引量の少ない銘柄は想定していない。

テイクプロフィット

ここにテイクプロフィットを追加する。

テイクプロフィットに関して調べていると興味深い記事があったのでメモ

OpenOrderを使用すると、(引数の順番的に)指値を指定しないとテイクプロフィットが指定できない。注文してから、テイクプロフィットを指定することもできなそうだった。ので、新しいローソク足が確定したかにかかわらず、最新の取引の価格を見て利確するべきかを判断する。

void OnTick()
 {
  CopyClose(_Symbol, _Period, 0, 101, close_buffer);
  if(ShouldTakeProfit())
    {
     Print("Take Profit");
     CloseAllPostion();
    }
    
  if(IsNewBar())
    {
     Print("New Bar: ", TimeCurrent());
     OnNewBar();
    }
 }
bool ShouldTakeProfit()
 {
  double close_price = close_buffer[0];
  ENUM_POSITION_TYPE position_type;
  if(position_info.Select(_Symbol) == false)
    {
     return false;
    }
  position_type = position_info.PositionType();
  if(
     (position_type == POSITION_TYPE_BUY && close_price < first_order_price + order_unit * 5)
     || (position_type == POSITION_TYPE_SELL && close_price > first_order_price - order_unit * 5)
  )
    {
     return false;
    }
  return true;
 }

利確をするかどうかは、ロングの時は最初の注文よりも5注文単位増えたとき、ショートの時は5注文単位減った時。

損切りと利確を同じ処理で扱うために、新しいローソク足が確定した時の処理を変更。

void OnNewBar()
 {
  Print("A new bar appeared");
//-- Update Buffers
  CopyBuffer(adx_handle, MAIN_LINE,   0, 2, adx_buffer);
  CopyBuffer(adx_handle, PLUSDI_LINE, 0, 2, plusdi_buffer);
  CopyBuffer(adx_handle, MINUSDI_LINE,0, 2, minusdi_buffer);
//-- Order
  if(ShouldOrder())
    {
     Print("Should Order");
     Order();
     UpdatePosition();
    }
//-- Cut loss
  if(ShuoldCutLoss())
    {
     Print("Cut loss");
     CloseAllPostion();
    }
  return;
 }

両者を CloseAllPosition にした。

ロスカットするかどうかは、暫定的に以下のようなロジックにした。

bool ShuoldCutLoss()
 {
  double close_price = close_buffer[1];
  ENUM_POSITION_TYPE position_type;
  if(position_info.Select(_Symbol) == false)
    {
     return false;
    }
  position_type = position_info.PositionType();
  if(
     (position_type == POSITION_TYPE_BUY && close_price < first_order_price - order_unit * 20)
     || (position_type == POSITION_TYPE_SELL && close_price > first_order_price + order_unit * 20)
  )
    {
     return false;
    }
  return true;
 }

20単位分の損失方向に価格変更しているときは損切りする。ここは後でトレンドを用いて損切りを判断する方法に置き換える。

最後に

今回は注文と損切り、利確の部分を実装した。途中で、確定した注文の種別や量を取得しようと思ったところで思うようにいかなかった。

最初に注文した時の価格ではなく、それを実行したときの最終取引価格になっている。ここを正確に、自身の注文の価格を使いたい場合には適切にAPIをたたく必要がありそう。

以下に参考になりそうな記事をまとめておく。

次回は

テストとリファクタリングを中心にしていく。




この記事が気に入ったらサポートをしてみませんか?