エンジニアの備忘録

学んだ事をアウトプットしていきます

python (jupyter)で単回帰分析

①分析に必要なライブラリをインポート

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
import seaborn as sns
sns.set()

csvデータを読み込み

date = pd.read_csv("フルパス入力")

date

 

 

データフレームをフルで表示したい場合↓ 

pd.set_option ('display.max_rows', 行数指定)

pd.set_option ('display.max_columns', 列数指定)

 

③要約統計量を取得

date.describe ( )

各列ごとに平均や標準偏差、最大値、最小値、最頻値などの要約統計量を取得できる。

 

(例:値段と床面積)

   price size
count 100.000000 100.000000
mean 292289.470160 853.024200
std 77051.727525 297.941951
min 154282.128000 479.750000
25% 234280.148000 643.330000
50% 280590.716000 696.405000
75% 335723.696000 1029.322500
max 500681.128000 1842.510000
  • count: 要素の個数
  • mean: 算術平均
  • std: 標準偏差
  • min: 最小値
  • max: 最大値
  • 50%: 中央値(median)
  • 25%75%: 1/4分位数、3/4分位数

 

 

④散布図を作成

従属変数と独立変数を定義する

従属: y  = date["Price"]
独立: x1 = date["Size"]

 

散布図を表示

plt.scatter(x1,y)
plt.xlabel('Size',fontsize=20)
plt.ylabel('Price',fontsize=20)
plt.show()

↓出力結果

f:id:pmpmcherry:20211210020649p:plain

plt.scatter( )の引数の種類

x, y グラフに出力するデータ
s サイズ (デフォルト値: 20)
c 色、または、連続した色の値
marker マーカーの形 (デフォルト値: ‘o’= 円)
cmap カラーマップ。c が float 型の場合のみ利用可能です。
norm c を float 型の配列を指定した場合のみ有効。正規化を行う場合の Normalize インスタンスを指定。
vmin, vmax 正規化時の最大、最小値。 指定しない場合、データの最大・最小値となります。norm にインスタンスを指定した場合、vmin, vmax の指定は無視されます。
alpha 透明度。0(透明)~1(不透明)の間の数値を指定。
linewidths 線の太さ。
edgecolors 線の色。

 

 

⑤回帰の実行(最小二乗法)

x = sm.add_constant(x1)  //statmodelでの変数追加
results = sm.OLS(y,x).fit()    //最小二乗回帰式の計算結果が代入

              //fit( )は特定のモデルにおいて一番適合した結果を得る
results.summary()       //結果を表示

↓出力結果

OLS Regression Results

(モデルの要約に関する表)
Dep. Variable: price R-squared: 0.745
Model: OLS Adj. R-squared: 0.742
Method: Least Squares F-statistic: 285.9
Date: Fri, 10 Dec 2021 Prob (F-statistic): 8.13e-31
Time: 02:08:09 Log-Likelihood: -1198.3
No. Observations: 100 AIC: 2401.
Df Residuals: 98 BIC: 2406.
Df Model: 1    
Covariance Type: nonrobust    

OLS(最小二乗):残差の合計が最小になるような直線を決める

        =全てのデータに対して最も近くなるような直線を引く  

R²(決定係数):1に近い程、回帰のばらつきについて説明可能

F値F検定に使う数値、0に近いほどそのモデルは有意ではない

    

          

(係数に関する表)

  coef std err t P>|t| [0.025 0.975]
const 1.019e+05 1.19e+04 8.550 0.000 7.83e+04 1.26e+05
size 223.1787 13.199 16.909 0.000 196.986 249.371

左2行は切片で回帰式の a(1019)とb(223.1787)に当たる

最小二乗法 y^(ハット) = 223.1787 + 101900x₁

std(SSE):標準誤差(ばらつき)が小さいほど正しく予測がされている良い回帰

 

(検定などに使える表)

Omnibus: 6.262 Durbin-Watson: 2.267
Prob(Omnibus): 0.044 Jarque-Bera (JB): 2.938
Skew: 0.117 Prob(JB): 0.230
Kurtosis: 2.194 Cond. No. 2.75e+03

 

回帰直線を散布図の上に表示する

plt.scatter(x1,y)
yhat = x1*223.1787 + 101900
fig = plt.plot(x1, yhat, lw=4, c="orange", label="regression line" )
plt.xlabel('Size', fontsize = 20)
plt.ylabel('Price', fontsize = 20)
plt.show()

↓出力結果

f:id:pmpmcherry:20211210021744p:plain

 

補足

最小二乗法が求めている値とは、残差の二乗の合計の値を最小にしたもの。

 

 

 

 

 

 

 

 

 

 

pythonとcsvファイルのあれこれ

前提:jupyterを使いpandasで読み込むので、読み込んだデータは自動的にdateframeという形に処理される

date = pd.read_csv("   .csv")

フルパスで指定すること

エラーが出た場合とりあえず試す

① パス名の前に r を付ける(ファイル名がエスケープシーエンスとして処理されているパターン)

②後ろに ,encodaing="UTF-8"を付ける、またはshift-jis

 

[データフレームをフル表示したい場合]

pd.set_option ('display.max_rows', 行数指定)

pd.set_option ('display.max_columns', 列数指定)

統計解析で潜在リスクと潜在リターンを予測して知っておく

 

リスクを予測

標準偏差を使った予測

必要な材料は、直近20回分の1トレード損益結果。そこから資金変動幅を推定。

「過去20回のトレードで、危険率0.1%の最大DD」

= STDEV(A1:A20) * 2 * NORMSINV(1 - 0.1% / 2)

その時点での最大値をシステムのリスクと見る。

 

連敗数を使って予測

必要な材料は勝率。確率的連敗最悪値 = 最大DDと予想する。

 = CEILING(LOG(1%, 1 - B2), 1)

1%は危険率、セル(B2)にはシステムの勝率。

最大DD = SL注文の金額ORpips数 * 取引通貨量 * N回(整数のCEILINGが入る)

増し玉、複数注文するシステムはその回数分を金額かpipsに乗算する

★システム運用が1本やリスク分散なしの場合は危険率を0.1%に落として、より安定性を高めたほうが無難

↓早見表

  50% 60% 70% 80% 90%
危険率(1%) 7 6 4 3 2
危険率(0.1%) 10 8 6 5 3

リターンを予測

回帰分析

①相関関係の高い期間を見つける

=CORREL(A1:A15, B1:B15)を使って相関係数を調べる。

左のセルには過去Nヶ月分の平均損益、右には未来のNヶ月分の平均損益を入れる。

係数値は高0.7以上・中0.4以上・低0.2以上。

=CORRELに←Nヶ月間の平均損益 = 合計利益 / トレード数 − 合計損失 / トレード数

f:id:pmpmcherry:20211120202150p:plain

当然未来は分からないので、このように疑似的な未来を用いる。
相関係数の高かった期間から期待利益(1トレード当たり)の下限値を出す

下限値はExcelの回帰分析ツールでXトレード結果とYトレード回数を使って出す。

③月の累積利益を出す

相関係数が高かった期間とその下限値2つの材料が揃えば、大体の月リターンが分かる。

累積利益(pips/月)(金額/月) = 期待利益(1トレード当たり)の下限値 * トレード数 / 期間 

 

EAを停止させる為の判断条件は数値に頼った方が良い

前置き

いくつかシステムの動きを見ていて気付いた事は、全ての相場状況に適したシステムは存在しないという事。

一般的に相場は大きく分けるとレンジ相場とトレンド相場に分類されるといわれている。

レンジでは損を受け入れる代わりに、トレンド相場で利益を取るトレンドフォロー型のシステムで全てのトレンド相場で利益を取れる方法は存在するが、そこは複雑な兼ね合いがまた必要になってくる。

その2つに分類された相場の中にも、異なるレンジとトレンドの形があるから。

f:id:pmpmcherry:20211118164828p:plain

4つの相場

基本的に相場はこの4つのタイプに当てはまる。

乱高下といわれるのは①に近いし、もっとも手を出したくない相場は②で、皆が望む理想のトレンドは④。

こういうサイクルをずっと繰り返してきているのが為替相場

システムトレードとは相場に合わせてシステムを使い分ける作業なのではないかと思う。

その為には「このシステムは最近調子が悪くなってきたな」という危険シグナルをいち早く察知して、システムを停止させる明確な基準が必要。

最近までは底が2度割れたら一旦様子見というシンプルな方法しかないと思っていたが、確率統計によってそれをいち早く明確に、数値で基準化できる方法があるらしい。

数値という根拠で基準化できるメリットは計り知れない。

いい加減なルールではいざというとき破る可能性がある。

実際に相場に身を置いて危機的状況に陥ったら「Xが発生したらYをする」というルールを持っていたとしても、心が弱ったときには合理的にルール通り動けない人の方が多いから。

これは身を持って分かっているので「このシステムはもう駄目だ」という判断をするときは積極的に活用していきたい。

参考にした本はパンローリング社出版の売買システム判別法。

T検定

異なる2種類の母集団の平均値を比較して変調を検出すること。

A「直近10トレードの結果から推定される母集団の平均値」とB「それ以前の結果から推定される母集団の平均値」という風に比較する。

Aのサンプル数が多いと、より精度の高い母集団の平均値が推定できる。

Aのサンプル数が多いほど統計的推定の精度は上がるが、変調に気付く検出スピードが落ちる。

 

=TTEST(P47:P225,P227:P245,2,3) 

#P値 

左2つに過去トレードの結果が入ったセルを指定。

右2つに直近10トレードの結果が入ったセルを指定。

P値が0.05(危険率5%の判定をする場合)よりも小さくなったとき、システムに変調があったと判断する。

※母集団が正規分布と仮定しているので、損失がSL指定により一定値に集中する場合は、発生頻度がそこに集中するため正規分布との乖離が大きくなる

 

二項分布

システムの直近トレード数の勝率だけを使い変調を検出する。

f:id:pmpmcherry:20211118210741p:plain

計算式=BINOMDIST(勝ち数,サンプル数,勝率,TRUE)

勝率90%~40%のシステムが10回中、勝ち数がN回の発生確率をグラフにした。

(ある勝ち数以下の発生する確率)

勝率50%のシステムで10回中に勝ち数5回以下の確率は62%、

勝率80%のシステムで10回中に勝ち数5回以下の確率は15%、

サンプル数を増やせば精度はあがるが(ry

変調と判断する基準値は次のようになる。

勝率 50% 60% 70% 80% 90%
基準値 1 2 4 5 6
  危険率1%        
勝率 50% 60% 70% 80% 90%
基準値 0 1 2 4 5

 

 

 

 

確率と統計を使ってEAのパフォーマンスチェック

読んでみた個人的な感想

著者、山本克二さんの「売買システム判別法」の本の内容が面白かったので、重要な内容を自分用にまとめておく。

書いてある事は非常に為になる内容だった。ハズレの多いトレードの本では間違いなく良書。

著者はトレード会社(他人)などが作ったシステムを使っていて、それらを使えるEAなのかを確率統計を使い判別する術を教えてくれている。

他人の物を使うという事は、正体が分からないブラックボックスを使う事になるのだが、本人いわくシステムの中身がどういった物かを知る必要は無いらしい。

大事なのは、そのシステムが利益を生み出す事が可能なのかを判別する力

確かにその力があればブラックボックスを知らなくても利益は出せるという事実は本当なのかも知れない。

しかし誰かのシステムで稼ぐという事はその「誰か」に依存している事になる。

じゃあその複数の誰かがいつか消えたりなど何かあった場合どうするのか?

共倒れのリスクが一生付き纏う以上、やっぱり他人に依存するのは自分はしたくない。

ブラックボックスを知る事は、どういった内容のシステムが機能して機能しないのか本質を理解しないといけないので、自らシステム開発するトレーダーにとっては必要な過程なのだと思う。

ただ10年以上前の本だから環境は違ったのかもしれない。

現在では依存しようとする人をカモにする投資分野でのビジネスや悪質な商材販売が増えすぎてるから余計厳しそう。

信頼区間

自分の成績から信頼区間を求めることで、95%の確率で将来の成績はこの幅に収まるだろう、という事が分かる。

直近30回の95%信頼区間が1000円 <= µ <= 2000円 →次の30回の平均損益はこの値の範囲内に収まる。

中央値 =( 下限値 + 上限値 ) / 2

サンプル回数が増える程、大数の法則によって信頼区間の精度が上がる。

f:id:pmpmcherry:20211116181004p:plain

計算式

①サンプル数のトレード結果をB1,B30に入力

②マイナスなら下限値、プラスなら上限値を求める

③95%信頼区間(1-95%)(90%なら0.10)

④サンプル数(今回は30回)

↑損益と回数から損益の下限値・上限値を演算

 

↓勝率のパターン

上限: A3+1.96*SQRT(A3*(1-A3)/(B3-1))
下限: A3-1.96*SQRT(A3*(1-A3)/(B3-1))
 
A3=勝率(例:0.4)
B3=トレード数(例:100回)
 

仮説検定

信頼区間では95%に焦点が当たっていたが、仮説検定では5%の間違える可能性を計算式に取り入れている(計算式は省く)

↓偶然に起こりうるPFの範囲

PF(5%) 4.26 2.56 2.11 1.90 1.77 1.68 1.61 1.56 1.52 1.49 1.46 1.44 1.42 1.40 1.38
PF(1%) 6.57 3.17 2.48 2.16 1.98 1.86 1.77 1.70 1.65 1.61 1.57 1.54 1.51 1.49 1.47
サンプル数 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150

損益プラマイ0に収束するPF1のシステムを使っても、最初の10回はビギナーズラック(偶然)によって大儲けが出来てしまう事が起きてしまう。

余談だがFXを始めた当初は数回程度のトレードで50万円儲けた事がある。

稼げると錯覚したものだが、まさしくそれは少ない試行回数で起きた偶然の出来事であり、その後はしっかりと実力値であるPFに収束して破産している。

話を戻して、この仮説検定のメインは偶然の外で起きた事に重きを置いている。

システムの結果が偶然の範囲外を示した=実力があるのではないか

といった風に捉える。

短い時間で回数の多い裁量トレードをした場合なんかは有効活用できると思った。

 

 

 

 

 

個人用MQL言語メモ

 

iLowest ()

成行買い(ドルストレート)で現在足の安値10pips下に損切りを置きたい場合

double point = Point();  
double buy_stop_loss = Low[0] - (100 * point);

0.00001(クロス円なら0.001)×100=10pipsになるので、それを現在足の安値から引いた値が損切り位置になる。

f:id:pmpmcherry:20211109105026p:plain

ちゃんと1.35487→1.35477にセットされている。

1e-05=0.00001なので問題ないが、表記が何故こうなるのかは分からない(クロス円は0.001で表示された)

 

本題のiLowest ()を使うと、上記と似たような方法で過去x本の足の最安値に置きたい場合に使えるが、

iLowest()は指定した期間内で最安値を持っている足のインデックス数を返してくれるだけであり、戻り値はintになる。

成行買いの損切りを1日以内の最安値に置きたい場合は、取得した値をLow[ ] に入れてあげれば良い。

int count_bars = 24;
int lowest_shift = iLowest
(NULL, PERIOD_D1, MODE_LOWER, count_bars, 0 );
double buy_stop_loss = Low [lowest_shift];

定率による資金管理とロット算出

1回ごとのトレードにより増減した口座残高から、指定した〇%分の損失許容額を出して、その額と損切りpips数からロット数を算出する。

extern bool fix_unit_management = true;
extern double equity_percent    = 2;      //2%
extern double stop_loss         = 500;    //損切り50pips
if ( fix_unit_management == true ) {

      double risk_amount = AccountEquity() * (equity_percent / 100);
      double tick_value  =  MarketInfo( Symbol(), MODE_TICKVALUE );
      double calc_lots  = ( risk_amount / stop_loss ) / tick_value;
      double lot_size   = calc_lots;
      printf ("この取引のロット = %f " , lot_size );
 }

f:id:pmpmcherry:20211110063933p:plain

約10万円の口座なので0.4ロット(クロス円)

ロット数のステップは0.01単位なので、

関数NormalizeDouble ()を使って小数点2桁以降は丸めておきます。(0.040065→0.04)

 

 

逆マーチンゲール

OrderProfit ( ) は、OrderSelect ( )で選択した注文の損益(手数料等を除く)を返す。

OrdersHistoryTotal ( ) は、ターミナル内で指定した期間の決済済注文数を返す。

 

 

 

 

 

 

一目とBBでEA作成テスト

今回は学習用に一般的なインジケータを2つ使ってEAを作成してみます(完全に自分用です)

ちなみに主観で数値を変えれるインジケータは信用していません。

 

 

作成前の準備

定義付けをしていきます

 

⑴順張りか逆張り

・順張りで

⑵エントリー基準

・BB(ボリンジャーバンド)のσ2、ミドルバンドの期間20を使用

一目均衡表(遅行線のみ)デフォルト値を使用

・遅行線がBB+σ2を超えたらロングエントリー

・遅行線がBB-σ2を超えたらショートエントリー

⑶クローズ基準

・遅行線がσ2内に潜ったら決済

⑷リミットとストップ

・なし

⑸ポジション情報

・0.1ロット(10000通貨)でエントリー

・複数ポジションを持たない

・増玉はしない

⑹エントリー・クローズ判定のタイミングは?

ローソク足終値が確定した瞬間

 

セーフティをセットしておく

リアル口座でEAが作動しないように、デモ口座でのみ作動するようセーフティを追加しておく。

int OnInit()
{
   if ( IsDemo() == false ) {  //デモ口座の場合
      Print ("※デモ口座でのみ作動します")
      return INIT_FAILED;     //処理終了
   }

   return( INIT_SUCCEEDED );      // 戻り値:初期化成功
}

関数IsDemoでEAがデモアカウントで実行されているかチェック。

デモアカウントの場合戻り値はtrueを返すので、

falseになる=デモアカウント以外で実行された→処理終了

INIT_FAILED=初期化失敗の為EAの継続不可能

 

 

処理タイミングを作成

⑹で定義した、ローソク足終値が確定した瞬間の処理を追加。

ローソク足確定時にだけ処理する関数を作成します。

//+------------------------------------------------------------------+
//| ローソク足確定時の処理
//+------------------------------------------------------------------+

void TaskPeriod() {
   static   datetime lasttime;  //最後に記録した時間軸時間

   datetime current_time = iTime( Symbol(), Period(), 0 );
 //現在の時間軸時間を取得

   if ( lasttime == current_time ) {  //時間に変化がない場合
      return; //処理終了
   }
   lasttime = current_time;  //最後に記録した時間軸の時間を保存


   printf( "[%d]ローソク足確定%s", __LINE__, TimeToStr(Time[0] ) );
}

static修飾子が付いているlasttimeは、ファイルロード時に静的領域へ確保される。ロード時に一度だけ行われ、アンロードされるまで保持される。

それに対してcurrent_timeはスタック領域へ確保される。

current_timeには関数iTimeによって表示チャートの時間軸、表示通貨ペア、最新のバーが格納されている。

つまり時間に変化があった場合、そのcurrent_timeを最後に記録した時間軸時間として書き換えていく動作が行われている。

 

Ontick() 関数イベント処理に追加。

f:id:pmpmcherry:20211104155144p:plain

1時間足チャートにロードしてログを確認してみると、現在チャートの更新時間毎に取得出来ている。

 

datetime time = 0;
 
if(time !=Time[0]) {
     time = Time[0];  
}

timeとTime[0](最新ローソク足のオープン時間)を取得したとき、等しくなければ代入。

多分こういう書き方でも問題ない?かと。

Time[ ]  =チャートの各バーのオープン時間が含まれている時系列配列。

最新のバーのインデックスは[0]になり、古いバーは[Bars-1]になる。

 

★エントリー判定処理を作成

⑵エントリー基準のエントリー判定だけ追加します。

・BB(ボリンジャーバンド)のσ2、ミドルバンドの期間20を使用

一目均衡表(遅行線のみ)デフォルト値を使用

・遅行線がBB+σ2を超えたらロングエントリー

・遅行線がBB-σ2を超えたらショートエントリー

 

画像で見るとこの↓の瞬間にロングの判定が満たされる。

f:id:pmpmcherry:20211104181549p:plain

やることは、

インジケータ情報を取得し、このAの位置関係を不等号を使ってロングエントリーの判定をします。

bool Bands_IchimokuCrossJudge () {
   
   bool ret = false;

   double right_ichimoku_rate;//確定した遅行線
   double right_bands_upper_rate;//確定したσ2上ライン
   double right_bands_lower_rate;//確定したσ2下ライン

   double left_ichimoku_rate; //前回の遅行線
   double left_bands_upper_rate;////前回のσ2上ライン
   double left_bands_lower_rate;////前回のσ2下ライン

   //確定した遅行線を取得
   right_ichimoku_rate = iIchimoku( NULL,
                                    PERIOD_CURRENT,
                                    9,
                                    26,
                                    52,
                                    MODE_CHIKOUSPAN,
                                  26
                                  );

   //確定したσ2上ラインを取得
   right_bands_upper_rate = iBands(
                              NULL,
                              PERIOD_CURRENT,
                              20,
                              2,
                              0,
                              PRICE_CLOSE,
                              MODE_UPPER,
                            26
                               );

   //確定したσ2下ラインを取得
   right_bands_lower_rate = iBands(
                              NULL,
                              PERIOD_CURRENT,
                              20,
                              2,
                              0,
                              PRICE_CLOSE,
                              MODE_LOWER,
                            26
                               );            

 //前回の遅行線を取得
   left_ichimoku_rate = iIchimoku( NULL,
                                    PERIOD_CURRENT,
                                    9,
                                    26,
                                    52,
                                    MODE_CHIKOUSPAN,
                                  27
                                  );        

//前回のσ2上ラインを取得
   left_bands_upper_rate = iBands(
                              NULL,
                              PERID_CURRENT,
                              20,
                              2,
                              0,
                              PRICE_CLOSE,
                              MODE_UPPER,
                            27
                               );

   //確定したσ2下ラインを取得
   left_bands_lower_rate = iBands(
                              NULL,
                              PERIOD_CURRENT,
                              20,
                              2,
                              0,
                              PRICE_CLOSE,
                              MODE_LOWER,
                            27
                               );  

 

//遅行線がσ2ラインを上抜け
if ( right_ichimoku_rate > right_bands_upper_rate
  && left_ichimoku_rate  <= left_bands_upper_rate )
   {
      ret = true;
   }

  if ( ret == true ) {
 
  }
   return ret;
   
}  
 
 

 

シフトのインデックスが26とぶっ飛んでますが、これは一目均衡表の遅行スパンは最新のローソク足から26本オフセットした位置に表示されているからです。

なのでボリンジャーバンドも26本オフセットした位置の値と比較しないといけないのでシフト26で合わせてあげます。

次に下抜けエントリー判定を追加していきます。

これも画像で設計したものをそのまま落とし込みます。

f:id:pmpmcherry:20211104223832p:plain

//遅行線がσ2ラインを下抜け
   else if ( right_ichimoku_rate < right_bands_lower_rate
          && left_ichimoku_rate  >= left_bands_lower_rate )
{
      ret = true;
}

 

★エントリー判定結果にenum列挙を使用する

上抜けと下抜けの結果での判断が出来ないので、enum列挙を追加します。

//enum列挙型宣言
enum ENUM_ICHIMOKU_CROSS {
   MAC_NULL = 0,  //無し
   MAC_UP_CHANGE,     //上抜け
   MAC_DOWN_CHANGE     //下抜け
};
ENUM_ICHIMOKU_CROSS Bands_IchimokuCrossJudge () {
   
   ENUM_ICHIMOKU_CROSS ret = MAC_NULL;

  ~中略~

//遅行線がσ2ラインを上抜け
if ( right_ichimoku_rate > right_bands_upper_rate
  && left_ichimoku_rate  <= left_bands_upper_rate )
   {
      ret = MAC_UP_CHANGE;
   }
//遅行線がσ2ラインを下抜け
   else if ( right_ichimoku_rate < right_bands_lower_rate
          && left_ichimoku_rate  >= left_bands_lower_rate )
{
      ret = MAC_DOWN_CHANGE;
}

 

EA識別用のマジックナンバーを追加

現在2021/11/05作成している日付をマジックナンバーとして追加します。

#defineで定義したモノを定数に変換します。

#define   MAGIC_NO     20211105          //EA識別用マジックナンバー

 

静的グローバル変数にロット用変数を追加

念のためにstatic修飾子を付けて外部からアクセスされないようにしておきます。

static double temp_lot = 0.1  //0.1ロット

 

 

◆新規エントリー関数を作成

trueならロング、falseならショートという感じにします。

bool EA_EntryOrder (bool in_long) {  //true→ロング、false→ショート
 
   bool    ret        = false;       // 戻り値
   int     order_type = OP_BUY;      // 注文タイプ
   double  order_lot  = temp_lot;    // ロット
   double  order_rate = Ask;         // オーダープライスレート
   
   if ( in_long == true ) {          // Longエントリー
   order_type = OP_BUY;
      order_rate = Ask;

   } else {                          // Shortエントリー
      order_type = OP_SELL;
      order_rate = Bid;
   }
   
   int ea_ticket_no = -1 ;            //チケットNo

   ea_ticket_no = OrderSend (                           // 新規エントリー注文
                              Symbol(),                 // 通貨ペア
                              order_type,               // オーダータイプ
                              temp_lot,                 // ロット[0.01単位]
                              order_rate,               // オーダープライスレート
                              50,                       // スリップ上限    (int)[分解能 0.1pips]
                              0,                        // ストップレート
                              0,                        // リミットレート
                              "遅行スパンとσ2がクロス", // オーダーコメント
                              MAGIC_NO                  // マジックナンバー(識別用)
                            );    
}
if ( ea_ticket_no != -1 ) { //オーダー正常完了
ret = true;
}

OrderSendの仮引数である有効期限 , 色 ,は省略しています。

注文成功時に、OrderSendの戻り値はサーバーによって割り当てられたチケット番号、intで返してくれます。

エラーの場合は-1が返ってくるので、注文が受け付けられなかったとき(-1が返ってきた場合)用のエラー情報を出力させるようにします。

エラー詳細を取得する関数ErrorDescriptionを使えるようにする為に、stdlib.mqhを冒頭にインクルードします。

//ライブラリインクルード
#include <stdlib.mqh>
 else {  //オーダーエラーの場合

  int get_error_code      = GetLastError ();                    //エラーコード取得
  string error_detail_str = ErrorDescription (get_error_code);  //エラー詳細取得

   //エラーログ出力
   printf ("[%d]エントリーオーダーエラー。エラーコード=%d エラー内容=%s"
            , __LINE__, get_error_code, error_detail_str );
   }        

 

■エントリーオーダー中間判定処理を追加

★エントリー判定処理 (Bands_IchimokuCrossJudge)

↓ ■中継(中間処理)

◆新規エントリー(EA_EntryOrder)

 

この本当にエントリーしていいのかを判定する中間処理の部分を追加します。

//+------------------------------------------------------------------+
//| エントリーオーダー判定 
//+------------------------------------------------------------------+

void judgeEntry ( ENUM_ICHIMOKU_CROSS in_ichimoku_cross ) {

   bool entry_bool = false; // エントリー判定
   bool entry_long = false; // ロングエントリー判定
   
   if ( entry_long == MAC_UP_CHANGE ) {   // 上抜け
      entry_bool = true;
      entry_long = true;

   } else if ( entry_long == MAC_DOWN_CHANGE ) {  //下抜け
      entry_bool = true;
      entry_long = false;
   }

   if ( entry_bool == true ) {
      EA_EntryOrder (entry_long);    //新規エントリー
   }

 

最初に定義した

⑹エントリー・クローズ判定のタイミングは? = JudgeEntry() 

→ ローソク足終値が確定した瞬間    = TaskPeriod(

準備が整ったので、TaskPeriod()に追加します。

void TaskPeriod() {
ENUM_ICHIMOKU_CROSS temp_cross;           //判定結果
    temp_cross = Bands_IchimokuCrossJudge(); //遅行線とσ2ラインのクロス判定
    JudgeEntry (temp_cross);             //エントリーオーダー判定

これで遅行線が上か下にクロスしたら、その判定結果をtemp_crossに。

その判定結果をJudgeEntryの引数に渡して、新規エントリー情報を執行していいかチェックを行い、サーバーへ新規注文を出すという流れが完成しました。

一度動作チェックをしてみます。

f:id:pmpmcherry:20211106210420p:plain

条件通りエントリーは行われています。

しかし⑸ポジション情報で定義した増し玉をしない・複数ポジションを持たないという内容に反しているので、増し玉をしないように処理を追加していきます。

ポジション情報を管理する関数を作成(複数エントリー防止)

構造体を使いポジション情報用の静的グローバル変数を用意します。

リミット・ストップの指定はないが、後々設定したいとき追加できるように一応準備しておきます

構造体(struct)は幾つかの異なる型(void)をまとめて1つのデータ型として扱う事ができ、とても便利なので積極的に使っていきます。

配列や文字列を含む構造体を複雑な構造体、そうでないのは単純な構造体といいます。今回は後者です。

構造体のメンバにアクセスするには構造体型変数の後ろにピリオドを付けて、アクセスしたいメンバ名を書きます。

// 構造体型宣言
struct struct_PositionInfo {
    int ticket_no;        //チケットNo
    int order_dir;         //オーダーの方向
    double set_limit;  //損切り価格
    double set_stop;  //利食い価格
}
static struct_PositionInfo _SetPositionInfoDate;  
//ポジション情報構造体データ
//+------------------------------------------------------------------+
//| ポジション情報を取得 (増玉しない為の関数なので、エントリーオーダー判定へ)
//+------------------------------------------------------------------+

bool GetPosiInfo ( struct_PositionInfo &in_st ) {

    bool ret = false;
    int  position_total = OrdersTotal();                   // 保有しているポジション数取得

    // 全ポジション分ループ
    for ( int icount = 0; icount < position_total; icount++ ) {

    // インデックス指定でポジションを選択
        if ( OrderSelect (icount, SELECT_BY_POS ) == true )    

        if ( OederMagicNumber() != MAGIC_NO ) {            // マジックナンバー不一致判定
            continue;         // 次のループ処理へ
        }
       
        if ( OrderSymbol () != Symbol() ) {                // 通貨ペア不一致判定
            continue;         // 次のループ処理へ
        }

        in_st.ticket_no  = OrderTicket();                  // チケット番号を取得
        in_st.order_dir  = OrderType();                    // オーダータイプを取得
        in_st.set_limit  = OrderTakeProfit();              // リミットを取得
        in_st.set_stop   = OrderStopLoss();                // ストップを取得

        ret = true;
       
        break;                 // ループ処理中断
        }
    }
    return ret;

}

エントリーオーダー判定へ処理を追加

増玉、複数ポジションを持たない為の関数なので、作成済みのエントリーオーダー判定に処理を追加します。

void JudgeEntry ( ENUM_ICHIMOKU_CROSS in_ichimoku_cross ) {

   bool entry_bool = false; // エントリー判定
   bool entry_long = false; // ロングエントリー判定
   
   if ( in_ichimoku_cross == MAC_UP_CHANGE ) {          // 上抜け
      entry_bool = true;
      entry_long = true;

   } else if ( in_ichimoku_cross == MAC_DOWN_CHANGE ) {  //下抜け
      entry_bool = true;
      entry_long = false;
   }

   GetPosiInfo (_SetPositionInfoDate );       //ポジション情報を取得
                                               
   if (_SetPositionInfoDate.ticket_no > 0 ) {  //ポジション保有中の場合
       entry_bool = false;                     //エントリー禁止
   }
   if ( entry_bool == true ) {   //新規エントリー
      EA_EntryOrder (entry_long);
   }

}

複数ポジションを持たなくなったか動作チェックします。

f:id:pmpmcherry:20211107022910p:plain

1回エントリーした以降、エントリー条件を満たしていても執行されていません。

クローズ処理が無いので、一度持ったポジションをずっと保有した状態が続いています。

 

参照渡しについて

その前に自分が理解するまでに時間がかかった参照渡しについて書き残して置きます。

ネット上でどこ調べても、ふわっと分かるが納得いかなくてモヤモヤしてたんですが、

これは、関数内で仮引数の値を変更すると、呼び出し元で指定した実引数を変更する事を意味します。

この1文で腑に落ちました。

小さくて見にくいが、こういう認識で間違ってないはず。

f:id:pmpmcherry:20211107031233p:plain

特に構造体と参照渡しが混ざってて中々理解出来ませんでした。

 

ポジション情報を表示するデバッグ用コメントを作成

デバッグ用なので完成時の消し忘れに注意。

//+------------------------------------------------------------------+
//| デバッグ用コメント表示
//+------------------------------------------------------------------+

void DispDebugInfo ( struct_PositionInfo &in_st ) {

    string temp_str = "";     //表示する文字列

    temp_str  += StringFormat ("チケットNo     :%d¥n" , in_st.ticket_no );
    temp_str  += StringFormat ("オーダータイプ :%d¥n" , in_st.order_dir );
    temp_str  += StringFormat ("リミット価格   :%s¥n" , DoubleToStr(in_st.set_limit, Digits) );
  temp_str += StringFormat ("ストップ価格   :%s¥n"  , DoubleToStr(in_st.set_stop, Digits) );

    Comment ( temp_str );  //コメント表示
}

Comment()はチャート上の左上に、定義したコメントを表示します。

Comment()は1つの文字列しか表示できないので、複数行表示させるには¥nという改行コードを書きます。

DoubleToStr()は指定した値の小数点以下の桁数分(8桁まで)を文字列で表示します。

表示したい内容と型から判断して下記に当てはめます。

指定子 表示する変数
%d 整数
%f 実数
%e 実数(指数表記)
%s 文字列

 

▲ポジション決済処理を作成

 

//+------------------------------------------------------------------+
//| 注文決済
//+------------------------------------------------------------------+

bool EA_Close_Order (int in_ticket) {

    bool ret = false;   //結果
    bool select_bool;   //ポジション選択結果

    // ポジションを選択
    select_bool = OrderSelect (in_ticket, SELECT_BY_TICKET );
   
    // ポジション選択失敗時
    if ( select_bool == false ) {
        printf ("[%d]不明なチケットNo = %d", __LINE__, in_ticket );
        return ret;   // 処理終了
    }
    // ポジションがクローズ済みの場合
    if ( OrderCloseTime() != 0 ) {
        printf ("[%d]ポジションクローズ済み チケットNo = %d", __LINE__, in_ticket );
        return ret;   // 処理終了
    }

    bool   close_bool;        // 注文結果
    int    get_order_type;    // エントリー方向
    double close_rate = 0;    // 決済価格
    double close_lot  = 0;    // 決済数量

    get_order_type = OrderType ();   // 注文タイプ取得
    close_lot      = OrderLots ();   // ロット数

    if ( get_order_type == OP_BUY ) {            // 買いの場合
        close_rate = Bid;

    } else if ( get_order_type == OP_SELL ) {   // 売りの場合
        close_rate = Ask;

    } else {           // エントリー指値注文の場合
        return ret;    // 処理終了
    }

    close_bool = OrderClose (                  // 決済オーダー
                             in_ticket,        // チケットNo
                             close_lot,        // ロット数
                             close_rate,       // クローズ価格
                             20,               // スリップ上限    (int)[分解能 0.1pips]
                             clrWhite          // 色
                            );

    if (close_bool == false ) { //クローズ失敗

        int get_error_code      = GetLastError ();                      // エラーコード取得
        string error_detail_str = ErrorDescription ( get_error_code );  // エラー詳細取得

        // エラーログ出力
        printf ("[%d]オーダーエラー。エラーコード = %d  エラー内容 = %s"
                , __LINE__, get_error_code, error_detail_str );

    } else {
        ret = true;   // 戻り値設定 クローズ成功
    }

       return ret; // 戻り値を返す
}

ポジション情報取得で行ったOrderSelectはインデックス指定だったのに対して、

今回チケット指定なのは他関数との依存度を低める為です。

結合度が高い関数は単体で使用できます。

 

▲クローズ条件の判定を作成

決済の条件は

・遅行線がσ2内に潜ったら決済

でした。

これも整理しやすいよう一応図で表します。

とはいってもエントリー判定でやった事を似た要領でやるだけです。

f:id:pmpmcherry:20211107224614p:plain

enum列挙にクローズ判定用の定数を追加。

//enum列挙型宣言
      enum ENUM_ICHIMOKU_CROSS {
         MAC_NULL = 0,      //無し
         MAC_UP_CHANGE,     //上抜けでロング
         MAC_DOWN_CHANGE,   //下抜けでショート
         MAC_CLOSE_DOWN,    //ロング保有中下抜けでクローズ
         MAC_CLOSE_UP       //ショート保有中上抜けでクローズ
     
      };
   //+------------------------------------------------------------------+
      //| 遅行線のσ2クローズクロス判定
      //+------------------------------------------------------------------+
     
      ENUM_ICHIMOKU_CROSS Bands_Close_IchimokuCrossJudge () {
         
         ENUM_ICHIMOKU_CROSS ret = MAC_NULL;
     
         
    ~中略~
       
     
      //遅行線がσ2ラインを下抜け
      if ( right_ichimoku_rate < right_bands_upper_rate
        && left_ichimoku_rate  > left_bands_upper_rate )
         {
            ret = MAC_CLOSE_DOWN;
         }
      //遅行線がσ2ラインを下抜け
         else if ( right_ichimoku_rate > right_bands_lower_rate
                && left_ichimoku_rate  < left_bands_lower_rate )
      {
       ret = MAC_CLOSE_UP;
      }
         return ret;      
      }  

 

 

▲ポジションの決済判定処理を作成

//+------------------------------------------------------------------+
      //| 決済オーダー判定
      //+------------------------------------------------------------------+
     
      void JudgeClose ( ENUM_ICHIMOKU_CROSS in_ichimoku_cross ) {
     
          bool close_bool = false;      //決済判定
         
          if ( _SetPositionInfoDate.ticket_no > 0 )  {   //ポジション保有中の場合
     
              if (_SetPositionInfoDate.order_dir == OP_BUY ) { //買いポジ保有中で
                  if ( in_ichimoku_cross == MAC_CLOSE_DOWN ) { //遅行線が下抜け
                      close_bool = true;
                  }
     
              } else if ( _SetPositionInfoDate.order_dir == OP_SELL ) {  //売りポジ保有中で
                  if ( in_ichimoku_cross == MAC_CLOSE_UP ) {             //遅行線が上抜け
                      close_bool = true;
                  }
              }  
           }
           if (close_bool == true ) {
           EA_Close_Order ( _SetPositionInfoDate.ticket_no );  //決済処理
           }
      }

 

作成したJudgeClose()は判定タイミング処理のTaskPeriod()に追加。

   // ----- 処理はこれ以降に追加 -----------
          ENUM_ICHIMOKU_CROSS temp_cross;                 //判定結果
          temp_cross = Bands_IchimokuCrossJudge();        //遅行線とσ2のエントリークロス判定
          JudgeEntry (temp_cross);                        //エントリーオーダー判定
          temp_cross = Bands_Close_IchimokuCrossJudge();  //遅行線とσ2のクローズクロス判定
          JudgeClose (temp_cross);                        //クローズオーダー判定

決済が行われるか確認します。

f:id:pmpmcherry:20211108013039p:plain

売りエントリー後のσ2上抜けで決済されています。

 

ポジション情報をリセットする

決済後にこのままEAを動かしてエントリー判定を満たしても、新しい注文は執行されません。

その為にポジション情報をクリアします。

今回は正常に決済されたタイミング+現在チャートの足更新時のタイミングでクリアにします。

//+------------------------------------------------------------------+
//| ポジション情報をクリア(決済済みの場合)
//+------------------------------------------------------------------+      
     
void ClearPosiInfo (struct_PositionInfo &in_st ) {

    if ( in_st.ticket_no > 0 ) {      //ポジション保有中の場合

        bool select_bool = false;     //ポジション選択結果
   
        //ポジションを選択
        select_bool = OrderSelect ( in_st.ticket_no, SELECT_BY_TICKET );

        //ポジション選択失敗時
        if ( select_bool == false ) {
            printf ("[%d]不明なチケットNo = %d" , __LINE__, in_st.ticket_no );
        }
        //ポジションがクローズ済みの場合
        if ( OrderCloseTime () > 0 ) {
            ZeroMemory (in_st);  //ゼロクリア
        }
    }
}

関数ZeroMemory()は参照によって渡された変数をクリアにします。

簡単な構造体をクリアするのに使えます。

このポジション情報をクリアにする関数を先ほど決めたタイミングに追加します。

↓ JudgeCloseに追加

if (close_bool == true ) {
               bool close_clear = false;
               close_clear = EA_Close_Order ( _SetPositionInfoDate.ticket_no );  //決済処理

               if ( close_clear == true ) {
                   ClearPosiInfo (_SetPositionInfoDate);   //ポジション情報をクリア
               }
           }

↓ TaskPeriodに追加

// ----- 処理はこれ以降に追加 -----------
          ENUM_ICHIMOKU_CROSS temp_cross;                 //判定結果
          temp_cross = Bands_IchimokuCrossJudge();        //遅行線とσ2のエントリークロス判定
          JudgeEntry (temp_cross);                        //エントリーオーダー判定
          temp_cross = Bands_Close_IchimokuCrossJudge();  //遅行線とσ2のクローズクロス判定
          JudgeClose (temp_cross);                        //クローズオーダー判定
          ClearPosiInfo (_SetPositionInfoDate);           //決済済みの場合情報クリア

 

最後に

最初に定義した内容は全て書いたと思うので、最後にちゃんと動作するか確認します。

f:id:pmpmcherry:20211108015255p:plain

ポジション決済後も続いてエントリー出来るようになっています。

とりあえずはこれで完成です。

 

追記:クローズエントリー判定の条件を満たせないパターンが発見できたので修正します。

 

f:id:pmpmcherry:20211108052029p:plain

インジケータの値は基本的に最新の値[0]の変動によって1つ前の値[1]が変動します。

このEAではシフト0と2で比較しています。

画像のように赤ラインの瞬間はエントリー条件のσ2を上抜けと判定出来てロングエントリーしてますが、その次には足1個前以上が変動によりσ2内に潜っており下記クローズ条件を満たすことが出来ないまま保有が続いています。

        && left_ichimoku_rate  > left_bands_upper_rate )

次のように書き換えました

if ( (right_ichimoku_rate < right_bands_upper_rate
 && left_ichimoku_rate > left_bands_upper_rate)
 ||( right_ichimoku_rate < right_bands_upper_rate
 &&  right_ichimoku_rate < left_ichimoku_rate ) ) {

     ret = MAC_CLOSE_DOWN;
 }
//遅行線がσ2ラインを下抜け
 else if ( (right_ichimoku_rate > right_bands_lower_rate
 && left_ichimoku_rate < left_bands_lower_rate)
||(  right_ichimoku_rate > right_bands_lower_rate
 && right_ichimoku_rate > left_ichimoku_rate) )
{
ret = MAC_CLOSE_UP;

}

 

 

f:id:pmpmcherry:20211108055913p:plain

確認して見るとちゃんとクローズされてます。

 

追記:某MQL4サイトを参考にしてます