恥は/dev/nullへ by 初心者

プログラミング素人がのろのろと学んだことをつづっています♪

iCloudを利用しているアプリ覚書

iPhoneiCloudに関する設定項目を見ると複数のアプリが並んでいますが、それぞれがどんな理由(用途)でiCloudを使用しているのか分かりませんでした。そこで、調べて分かったことと想像したことをメモしておくことにしました。

なお、上述した「iCloudに関する設定項目」とは、「設定」→(Apple ID)→「iCloud」にある『ICLOUDを使用しているAPP』を指します。


Spark(メールアプリ)

Sparkのウェブサイトに次の記述がありました。

クラウドへの保存
添付ファイルやメール本体をクラウドに保存できます。タップするだけで、Dropbox、Box、Googleドライブ、OneDrive、iCloud Drive にアップロードできます。

[情報源] https://sparkmailapp.com/ja/features/attachments

メールに添付するファイルメール本文を保存する場所としてiCloud Driveを使うことができるようです。

それと、これは想像ですが、もしかするとSparkの設定状況もiCloudに保管されているかもしれません。iPhoneを初期化し、バックアップから復元をせずに、SparkをAppStoreからインストールした際、Sparkが初期化前の状態に復元されました。記憶違いでなければ、「iCloudバックアップ」はオフになっていたと思います。このことから「もしかしたらiCloud Driveに設定内容が保管されていたのかな?」と想像しています。


LINE

LINEに関しては、トーク履歴のバックアップiCloud Driveを使います。よって、トーク履歴のバックアップを行う時(機種変更前やiPhoneの初期化を行う前)にだけiCloudとの接続をオンにすれば良さそうです。

[情報源] https://help.line.me/line/ios/categoryId/20009916/3/pc?lang=ja


MT5

iPhone用のMT5アプリにはメール機能があるらしく、iCloud Driveに保管してあるファイルをメールに添付することができるようです。ただ、メールの送信先は利用しているFX業者だけのようですから、ほぼ使うことは無いでしょう。よって、MT5アプリに関しては、iCloudへの接続をオフにしておくことにしました。

[情報源] https://www.metatrader5.com/en/mobile-trading/iphone/help/settings_mail


ブック

iPhoneに最初から付属している「ブック」というアプリについては、Apple社のウェブサイトに次の記述があります。

iCloudを使用すると、すべてのデバイスApple Booksのライブラリや設定を最新の状態に保つことができます。

というわけで、iCloudに接続する目的は、複数のデバイス間で「ブック」の中身を同期することのようです。

[情報源]
https://support.apple.com/ja-jp/guide/icloud/mm3941ae3362/icloud
https://support.apple.com/ja-jp/guide/icloud/mmbebdb94de8/1.0/icloud/1.0

iPhoneのユーザー辞書を保全するには?

iPhoneを初期化してゼロから設定し直した折、ユーザー辞書に登録したデータは保持されていました。普通に考えて、iCloud上のどこかに保存されていたものと思います。

しかし、iCloudに関する設定のうち、どの項目をONにしておけばユーザー辞書がiCloud上で保全されるのかが分かりませんでした。

調べた所、Apple社のウェブサイトに

iCloudを使ってほかのデバイスのユーザ辞書を最新の状態に保つ
「設定」 >「[自分の名前]」>「iCloud」と選択して、「iCloud Drive」をオンにします。

という記載がありました。

これは複数のデバイス間でユーザー辞書を同期するための説明ですが、この説明から、iCloudの各種サービスのうちiCloud Driveがユーザー辞書に関与していることが分かりました。

というわけで、iCloud Driveをオンにしておけばユーザー辞書はiCloud上で保全されそうです。

1.「設定」の中にあるApple IDに関する部分をタップ
2.「iCloud」をタップ
3.「ICLOUDを使用しているAPP」の「iCloud Drive」をオンにする

Apple IDのアイコン画像、「連絡先」の連携

iPhoneで次のようなことがあったので、メモを残しておくことにしました。


Apple IDのアイコン画像が表示されなくなった

iCloudの「連絡先」には電話帳データを保存していないので、iCloudの「連絡先」をiPhoneに連携しないようにしました。すると、「設定」の中にあるApple IDのアイコン画像が消えて、代わりに文字が表示されました。

この状況に加えて、iPhoneiCloudの「連絡先」を少しいじってみた結果、Apple IDのアイコン画像は、iCloudの「連絡先」にある自分のデータが元になっている(自分のデータに登録されている画像がApple IDのアイコンに反映している)と思われました。

そこで、改めてiCloudの「連絡先」を連携しました。

1.iPhoneの「設定」の中にあるApple IDに関する部分をタップ
2.iCloudをタップ
3.「iCLOUDを使用しているAPP」の中にある「連絡先」をONにする


なお、上述した仕組みになっている(と思われる)ので、新たにアイコン画像を登録する場合、iCloud上の「連絡先」に連携した状態でアイコン画像を登録する必要があります。


複数の連絡先を連携しても問題はない

上記の作業を行った折、「iPhoneの連絡先に、複数の(クラウド上の)連絡先を連携しても問題はないのだろうか?」という疑問が浮かびました。

そこで、他の電話帳(クラウド上の電話帳)をiPhoneに連携した状態で、iCloudの「連絡先」もiPhoneに連携してみました。

この結果、iPhoneの「連絡先」に、「他の電話帳に登録されている連絡先」と「iCloudの『連絡先』に登録されている連絡先」の両方が表示されました。

また、iPhoneの連絡帳に、これらのうち1つだけを表示するのか、両方を表示するのかを選ぶことも可能でした。

iPhoneのスケジュールアプリ選び

以前はRefillsの有料版を使っていましたが、2022年に有料版の提供が終了し、現在はApp Storeからダウンロードすることもできなくなりました。

そこで、iPhone用のスケジュールアプリを改めて検討してみました。検討したものは次の3つです。

iPhoneに付属しているカレンダー(以下iPhoneカレンダー」
・Refills Lite
・First Seed Calendar


iPhoneカレンダー

まずは、iPhoneに最初から搭載されている「カレンダー」について調べてみました。見た目はシンプルですが、ネット情報を見ながらいじってみると、スケジュール管理に必要なことが色々とできて便利です。


しかし、次の2点が不便だと思いました。

1.月間カレンダー表示では、各日の予定が表示されないこと
2.予定を新規作成した時にデフォルトで通知がセットされないこと


1に関しては、iPhoneを横に傾けると週間カレンダー表示になり、各日の予定が表示されます。しかし、私は画面の向きが変わることが嫌で、普段は画面の向きをロックして使っています。画面の向きをロックせずに使っている人にとっては問題ないことですが、私には少し不便です。

2に関しては、予定を新規作成する場合に、「通知」という項目が「なし」となっています。私は当初、デフォルトの通知タイミング(例えば、5分前、1時間前など)を設定画面あたりでユーザーが選べるものと考えていました。しかし、それらしき項目がありませんでした(←私が見つけられなかっただけ?)。


Refills Lite

Refills Liteを検討してみました。有料版Refillsが無くなったので、現在選べるのは広告表示のあるLiteだけです。

このアプリでは、iPhoneカレンダーと違い、月間カレンダー表示でも各日の予定が表示されます(← 他のスケジュールアプリでも大抵は表示されると思いますが)。

また、予定時刻を1分刻みで入力することも可能です。ちなみに、iPhoneカレンダーで予定時刻を入力する場合、5分刻みとなります(ただし、入力画面をタップすれば、テンキーから1分刻みで入力することができるので、iPhoneカレンダーでも1分刻みの入力は可能です)。

しかし、多少のデメリットもありました。

まず、ウィジェットがありません。ただし、この点は、iPhoneカレンダーのウィジェットで補えます。というのも、このアプリはiPhoneカレンダーを利用しており(乗っかっており)、このアプリで作成した予定はiPhoneカレンダーに反映するからです。

この場合に少し気になるのは、このアプリで入力した予定がiPhoneカレンダーに反映するまでにタイムラグが生じる場合があることです。もし即座に反映させたい場合は、iPhoneカレンダーを開いて、データ更新をする必要があります。とはいえ、これはiPhoneカレンダーをベースにしているスケジュールアプリ全てに共通することなので、Refills Lite固有の問題というわけではありません。


First Seed Calendar

First Seed Calendarというアプリも検討してみました。

iPhoneカレンダーと違い、月間カレンダー表示で各日の予定が表示されます。また、Refills Liteと違い、ウィジェットがあります。それに、このアプリには有料版があるので、使い勝手が良かった場合、有料版に移行することで広告が表示されずに済みそうです。

一方、無料版では予定時刻を5分刻みでしかセットできない点が不便だと思いました(有料版では1分刻みにすることが可能)。無料版と有料版の差別化を図ることは必要だと思いますが、このアプリはiPhoneカレンダーを利用しているのに、iPhoneカレンダーでできることが制限されている点に少しモヤっとしました。

なお、この記事では触れていませんが、カスタマイズ性が非常に高くて「かゆい所に手が届くアプリ」です。この意味で優秀だと思います。


3者の比較

ここまで、iPhoneカレンダーで不便だと感じたことを軸に比較してみましたが、まとめると以下のようになります。

iPhoneカレンダー

<不便な点>
(1)月間カレンダー表示では、各日の予定が表示されない
(2)予定を新規作成した時にデフォルトで通知がセットされない

<良い点>
作成した予定が即座にカレンダーやウィジェットに反映する

<その他>
予定時刻はデフォルトで5分刻み
1分刻みで予定時刻をセットする場合、ほんの少し手間がかかる


Refills Lite

<不便な点>
(1)ウィジェットが無い
(2)広告表示のある無料版のみ(有料版は2022年に終了)

<良い点>
(1)月間カレンダー表示でも各日の予定が表示される
(2)「時刻指定間隔」を1分にすれば、1分刻みで予定時刻を入力できる

不便な点の(1)についてはiPhoneカレンダーのウィジェットで補える。ただし、作成した予定をウィジェットに即時反映させたい場合、「カレンダー」を開いてデータ更新を手動で行う必要がある。


First Seed Calendar

<不便な点>
無料版では予定時刻を5分刻みでしかセットできない(有料版では1分刻みが可能)

<良い点>
(1)月間カレンダー表示でも各日の予定が表示される
(2)ウィジェットがある
(3)広告表示のない有料版がある

【備考】カスタマイズ性が非常に高い


結局、広告表示が気になるかどうか

私は広告表示が気になるので、First Seed Calendarの有料版を候補の1つに入れています。

しかし、広告表示を気にしなければ、iPhoneカレンダーとRefills Liteを組み合わせることで、概ね自分の求めることはできそうです。iPhoneカレンダーで不便な点はRefills Liteで補えますし、Refills Liteにウィジェットが無いことはiPhoneカレンダーのウィジェット機能で補えるからです。

というわけで、First Seed Calendarの有料版を導入するかどうかの分岐点は「広告が表示されることを気にするかどうか」になりそうです。


Refills Liteの通知設定

Refills Liteで通知の設定に少し戸惑いました。

iPhoneの「設定」→「通知」においてRefills Liteにバナーやサウンドによる通知を許可しましたが、音は鳴らずバナーも表示されませんでした。開発元のウェブサイトを見ると次の記述がありました。

Refills は iPhone / iPadの標準カレンダーを参照しており、標準カレンダー側に書き込まれた予定に登録された通知は標準カレンダーアプリケーションによって表示されます。

そこで、iPhoneカレンダーの通知設定においてサウンドやバナーをONにしました。

開発元のウェブサイトには

Refills の Google カレンダー同期機能を利用しているカレンダーに書き込まれた予定に登録された通知は Refills によって表示されます。

という記述もありましたが、説明文書によると「Lite 版はGoogleカレンダー&タスクとの同期には対応しておりません。」とのことです。

よって、iPhoneの「設定」→「通知」では、iPhoneカレンダーの通知設定だけを行いました。

それと、予定を作成した時にデフォルトで通知時刻がセットされるようにするには、Refills Liteの「その他の設定」→「予定通知の初期値」を『ポップアップ』にします。


まとめ(必要な設定)

iPhoneの「設定」→「通知」→「カレンダー」
 ⇒ 自分に必要な通知項目(バナー、サウンド、バッジ等)をONにする  

Refills Liteの「設定」→「その他の設定」→「予定通知の初期値」
 ⇒ この項目を『ポップアップ』にする


Refills Liteのリマインダとは?

Refills Liteの「設定」→「その他の設定」を見ると

(1) リマインダ通知の初期値
(2) リマインダ通知時間の初期値

という項目があり、(2)に関しては「0分前」等を設定することになっています。よって、時刻を念頭に置いた項目だと思うのですが、特定の日時を設定してサウンドを鳴らす機能が見当たりませんでした。

アプリの画面内に「タスク」というものがあったので、これのことかなとも考えました。しかし、タスクの期限には日付しか指定できない(時刻の指定ができない)ので、違うようです。

良く分からないので、リマインダーは無いものと思うことにしました。もし必要ならiPhoneに最初から入っている「リマインダー」を使うことにします。

ファイル名の日本語部分が文字化けしている?

初歩的で恥ずかしいオチですが、また同じことをやりそうなので簡単なメモを残しておくことにします。

GUI画面を起ち上げ、ターミナル(sakura)でホームディレクトリのファイルを一覧表示したところ、日本語のファイル名だけが文字化けしていました。一方、Vimを開くと日本語は普通に表示されています。

この状況から見て、日本語のフォントファイルが壊れているわけではなさそうです。
そこで、ロケールを調べてみました。

locale

を実行したところ、以下のように表示されました。

LANG=C
LANGUAGE=
LC_CTYPE="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_COLLATE="C"
LC_MONETARY="C"
LC_MESSAGES="C"
LC_PAPER="C"
LC_NAME="C"
LC_ADDRESS="C"
LC_TELEPHONE="C"
LC_MEASUREMENT="C"
LC_IDENTIFICATION="C"
LC_ALL=

これを見て、少し前にコンソールで「LANG=C」を実行して作業していたことを思い出しました。犯人は自分ですね(大抵そうです)。


ロケールを日本語にして解決です。

LANG=ja_JP.utf8

[MQL5]MT5で上位足の移動平均線を表示する

MT4からMT5へ移行して以降、MTF移動平均線(以下「MA」)を使っていませんでしたが、5分足チャートや15分足チャートを見ている時に「1時間足のMAも表示されている方が便利だな」と思ったので、上位足のMAを表示するインディケーターを、MQL5の練習を兼ねて作ってみました。



現在分かっている問題点

試しに作ってはみたものの、現在分かっている問題点として次のものがあります。

  • 取引時間外にMAが描画されない。
  • たまに(上位足ではなく)現在足のMAが表示されるケースがある。

こうした点はまだ改善の余地があります。

あと、MT4の時もそうでしたが、普通のMA(STFのMA)に比べると描画に少し時間がかかりますね。「MAを描画する対象を直近のローソク足N本にする」という機能を追加した方がよいかもしれません。


イメージ

この画像では15分足チャートに1時間足のMA(カクカクした青いライン)を表示しています。


MAの時間足を変更する

先ほどの例では1時間足のMAを表示していましたが、他の時間足に変更することもできます。たとえば、「4時間足の20本MA」を「赤色」で表示したい場合、プロパティを次のように変更します。


(備考)MAは「Moving Average」の略です。

コード

#property copyright "Copyright 2023, PHILOJUAN"
#property version   "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1

input ENUM_TIMEFRAMES maTimeFrame = PERIOD_H1; // 表示したいMAの時間足
input int             maPeriod    = 24;        // MAの期間
input color           maColor     = clrBlue;   // MAの色
input int             maWidth     = 2;         // MAの太さ

int chartTFminutes = 0;      // チャートの時間足を「分」で表した数値
int maTFminutes    = 0;      // MAの時間足を「分」で表した数値
int barsToRedraw   = 0;      // Tick受信時に再描画するbar数(maTimeFrameの現在足に相当する部分)
int barsToExclude  = 0;      // MAの計算対象としないbarの数(計算ができない部分)
bool lessThanMATF  = false;  // フラグ(チャートの時間足が「MAの時間足」より下位足)

// インディケーター用配列
double MA_1[];

// ハンドル変数
int h_ma1;

int OnInit()
{
    //----------------------
    // パラメーターチェック
    //----------------------
    if(maPeriod < 1) {
        Alert("MAの期間は1以上にしてください");
        RemoveIndicator();
        return INIT_FAILED;
    }
    if(maWidth < 1 || maWidth > 5) {
        Alert("MAの太さに指定できるのは 1 ~ 5 です");
        RemoveIndicator();
        return INIT_FAILED;
    }

    // チャートの時間足やMAの時間足を「分」に変換したものを用意
    ENUM_TIMEFRAMES currentTF = _Period;              // チャートの時間足
    chartTFminutes = TimeFrameToMinutes(currentTF);   // チャートの時間足を「分」にしたもの(int型)
    maTFminutes    = TimeFrameToMinutes(maTimeFrame); // MAの時間足を「分」にしたもの(int型)

    // チャートの時間足が「MAの時間足と同じ OR MAの時間足よりも上位足」の時
    if(chartTFminutes >= maTFminutes) {
        SetIndexBuffer(0, MA_1, INDICATOR_CALCULATIONS);
        PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_NONE);
            // chartTFminutes と maTFminutesが同じなのにMAが描画されるケース
            // が見受けられたので、実験的に上記2行を記述。
        return INIT_SUCCEEDED;
    }
        // [memo]
        //   SetIndexBufferの第3引数は、通常INDICATOR_DATA。
        //   INDICATOR_CALCULATIONSは中間計算用で描画用ではない。


    //  ★★★★ チャートの時間足がMAの時間足以上なら以下のOnInit処理は省略 ★★★★
    //*********************************************************************************** 

    lessThanMATF = true; // チャートの時間足はMAの時間足よりも下位足

    // バッファと配列を関連付け、MAの外観を設定
    SetBufferAndStyle(0, MA_1, DRAW_LINE, STYLE_SOLID, maColor, maWidth);

    //--------------------------------------------
    // barsToRedraw と barsToExclude の計算
    //--------------------------------------------
    int totalBars = Bars(_Symbol, currentTF);  // チャート上に存在するローソク足の本数

    // barsToRedraw (剰余があれば + 1)
    barsToRedraw = maTFminutes / chartTFminutes;
    if(MathMod(maTFminutes, chartTFminutes) != 0.0) barsToRedraw = barsToRedraw + 1;

    // barsToExclude (剰余があれば + 1)
    barsToExclude = maTFminutes * maPeriod / chartTFminutes;
    if(MathMod(maTFminutes * maPeriod, chartTFminutes) != 0.0) barsToExclude = barsToExclude + 1;

    //--------------------------------------------------------------------
    // MAを描画できない場合、アラートを出してインディケーターを除去
    //--------------------------------------------------------------------
    // 考えられるのは、チャート上にあるローソク足の本数が少なすぎる場合や
    // MAの計算期間が大きすぎる場合。
    bool errorA = false;
    bool errorB = false;

    if(barsToExclude > totalBars) errorA = true;
    if((barsToExclude + barsToRedraw - 1) > totalBars) errorB = true;

    if(errorA || errorB) {
        string symbolPeriod = "[" + _Symbol + "," + TimeFrameToString(currentTF) + "]";
        Alert(symbolPeriod, " ERROR! Cannot calculate moving average.");
        Print("totalBars = ", IntegerToString(totalBars));
        Print("maPeriod = ", IntegerToString(maPeriod));

        if(errorA)
            Print("[TYPE A] barsToExclude = ", IntegerToString(barsToExclude));
        if(errorB)
            Print("[TYPE B] barsToExclude + barsToRedraw - 1 = ", IntegerToString(barsToExclude + barsToRedraw - 1));

        // errorAにもerrorBにも該当しないMA期間を教示
        double maxPeriodA = 1.0 * totalBars * chartTFminutes / maTFminutes;
        double maxPeriodB = 1.0 * (totalBars - barsToRedraw + 1) * chartTFminutes / maTFminutes;
        Print("maximum MA period you can specify = ", DoubleToString(MathMin(maxPeriodA, maxPeriodB), 0));

        RemoveIndicator(); // インディケーターを除去
        return INIT_FAILED;
    }

    //--------------------------------------------
    // その他
    //--------------------------------------------
    // MAハンドルの取得
    h_ma1 = iMA(NULL, maTimeFrame, maPeriod, 0, MODE_SMA, PRICE_CLOSE);

    // MA_1[]のインデックスを「最新足から古い足へ」という順序で付け直す
    ArraySetAsSeries(MA_1, true);

    // 最古足から「barsToExclude - 1」の範囲はMAを描画しない(MAを計算できない)
    PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, barsToExclude - 1);

    // データウィンドウの見出し
    string tfString = TimeFrameToString(maTimeFrame);
    PlotIndexSetString(0, PLOT_LABEL, "MA_" + tfString + "(" + IntegerToString(maPeriod) + ")");

    // データウィンドウに表示される値の精度
    IndicatorSetInteger(INDICATOR_DIGITS, 6);

    return(INIT_SUCCEEDED);
}


void OnDeinit(const int reason)
{
    // MAハンドルを削除(MA計算に使われていた領域を解放)
    if(lessThanMATF) IndicatorRelease(h_ma1);
}


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[])
{
    // チャートの時間足が「MAの時間足」と同じ又は上位足の場合、以下の処理をskip
    if(lessThanMATF == false) return rates_total;

    static bool executeFlag = false;

    //--------------------------------------------------------------------------------------
    // インディケーター挿入直後(OR チャートの時間足を切り替えた直後)
    //--------------------------------------------------------------------------------------
    if(prev_calculated == 0) { ; }  // インディケーター挿入直後は処理なし

    //--------------------------------------------------------------------------------------
    // インディケーター挿入直後ではなく、executeFlag が false
    //--------------------------------------------------------------------------------------
    else if(executeFlag == false) {
        ArraySetAsSeries(time, true);

        // [失敗メモ]
        // インディケーター挿入直後、MTFのMA値は計算されていない(prev_calculated == 0 の
        // 時点で、BarsCalculated(h_ma1)の戻り値は「-1」)。
        // そこで、prev_calculated == 0でない時に1回だけfor文を実行する方法を試したが、
        // 一部の区間にMAが描画されないケースがあった。

        // [現状]
        // 描画範囲のMA_1[i]が全てEMPTY_VALUEでなくなるまで、Tickのたびに、
        // else if(executeFlag == false) { }ブロックを実行(実質、1~2回)

        bool emptyValueNotExist = true;  // EMPTY_VALUEが無いことを表すフラグ

        for(int i = rates_total - barsToExclude; i >= 0; i--) {

            MA_1[i] = ValueOnSpecifiedBarTime(h_ma1, time[i]);
            if(MA_1[i] == EMPTY_VALUE) {
                emptyValueNotExist = false;
                break;
            }
        }

        if(emptyValueNotExist) executeFlag = true;
    }
    //--------------------------------------------------------------------------------------
    // インディケーター挿入直後ではなく、executeFlag が true
    //--------------------------------------------------------------------------------------
    else {
        //「MAの時間足における最新足」に相当する部分を再描画
        for(int i = barsToRedraw - 1; i >= 0; i--) {
            MA_1[i] = ValueOnSpecifiedBarTime(h_ma1, iTime(_Symbol, _Period, i));
        }
    }

    return(rates_total);
}


//--------------------------------------------------------------------
// 指定したbarの発生時刻におけるインディケーターの値を返す関数
//--------------------------------------------------------------------
double ValueOnSpecifiedBarTime(int handle, datetime startTime)
{
    double buffer[1];

    if(CopyBuffer(handle, 0, startTime, 1, buffer) > 0)
        return(buffer[0]);

    else return(EMPTY_VALUE);
    // else行は、CopyBufferの戻り値が -1(エラー)又は 0 の場合
}


//--------------------------------------------------------------------
// バッファと配列を関連付け、インディケーターの外観を設定する関数
//--------------------------------------------------------------------
void SetBufferAndStyle(int bufNumber,
                       double &bufArray[],
                       ENUM_DRAW_TYPE drawType,    // DRAW_LINEや DRAW_NONE
                       ENUM_LINE_STYLE drawStyle,
                       color drawColor,
                       int   drawWidth
                      )
{
    SetIndexBuffer(bufNumber, bufArray, INDICATOR_DATA);
    PlotIndexSetInteger(bufNumber, PLOT_DRAW_TYPE , drawType);
    PlotIndexSetInteger(bufNumber, PLOT_LINE_STYLE, drawStyle);
    PlotIndexSetInteger(bufNumber, PLOT_LINE_COLOR, drawColor);
    PlotIndexSetInteger(bufNumber, PLOT_LINE_WIDTH, drawWidth);
}


//--------------------------------------------------------------------
// インディケーターを除去する関数
//--------------------------------------------------------------------
void RemoveIndicator()
{
    string shortname = "indicator_to_remove";
    IndicatorSetString(INDICATOR_SHORTNAME, shortname);
    ChartIndicatorDelete(0, 0, shortname);
}


//--------------------------------------------------------------------
// 時間足を「H1」等の短縮表現に変換する関数
//--------------------------------------------------------------------
// 例 PERIOD_H4   →   H4
string TimeFrameToString(ENUM_TIMEFRAMES intTF)
{
    if(intTF == PERIOD_CURRENT) intTF = Period();

    return StringSubstr(EnumToString(intTF), 7);
}


//--------------------------------------------------------------------
// MQL5の時間軸をint型の「分」に変換する関数
//--------------------------------------------------------------------
// [備考] MQL4と違い、PERIOD_H1以上は分数に対応していない
int TimeFrameToMinutes(ENUM_TIMEFRAMES timeframe)
{
    int minutes = 0;

    if(timeframe == PERIOD_CURRENT) timeframe = _Period;

    if(timeframe < PERIOD_H1)
        // MT5の現行の仕様なら、単に minutes = timeframe; でもOK。
        minutes = (int)StringToInteger(StringSubstr(EnumToString(timeframe), 8));
    else if(timeframe < PERIOD_D1)
        minutes = 60 * (int)StringToInteger(StringSubstr(EnumToString(timeframe), 8));
    else {
        switch(timeframe) {
            case PERIOD_D1:
                minutes = 1440;
                break;
            case PERIOD_W1:
                minutes = 10080;
                break;
            case PERIOD_MN1:
                minutes = 43200;  // 43200分は 30日に相当(MQL4のMN1を踏襲)
                break;
            default:
                minutes = -1;   // ERROR
        }
    }
    return minutes;
}


iTime()関数の落とし穴?

OnCalculate()関数の中でローソク足の発生時刻を取得する際

else if(executeFlag == false) { 

ブロックの中ではtime[ ]を使い、

else {

ブロックの中ではiTime()を使っています。これは以下の問題があったからです。

time[ ]を使う場合、インデックスを時系列にするためにArraySetAsSeries()を実行する必要があります。一方、iTime()を使えばその必要はありません。そこで、両方のブロックにおいてiTime()を使おうとしました。しかし、「else if」ブロック内でiTime()を使用したところ、15分足チャートではMAが普通に表示されましたが、5分足チャートでは表示されませんでした。

そこで、MAの計算に失敗したローソク足を調べると次のようになっていました。

iTime() → 1970.01.01 00:00
time[]  → 2019.10.30 04:55


次に全ローソク足の発生時刻をiTime()で出力しました。すると、現在足から見て249999本前まではiTime()が適切な発生時刻を返していましたが、それよりも古いローソク足ではiTime()の戻り値が全て「1970.01.01 00:00」となっていました。

これが原因で古いローソク足時点のMAを計算できなかったようです。そこで全ローソク足に処理を行う場面ではtime[]を使い、新しいローソク足に対して処理を行う場面ではiTime()を使用することにしました。


取引時間外にMTFのMAが表示されないのは何故か?

相場が動いていない日曜日のことです。15分足チャートにこのインディケーターを入れ、1時間足のMAを表示させようとしました。しかし、チャートに1時間足のMAは表示されませんでした。

当初は「取引時間外だからTickの受信がない。だから、OnCalculate関数の中に記述されたコードが実行されず、MAが描画されないのだろう。」と考えました。しかし、MT5に標準で搭載されている「MACD」というインディケーターをチャートに入れるとMACDがチャートに描画されました。

MACD」のコードを見たらOnCalculate関数を使っている部分があったので、取引時間外にインディケーターをチャートに入れても、少なくとも1回はOnCalculate関数が実行されるようです。

よって、「1時間足のMAが表示されなかったのはOnCalculate関数が実行されないからではない」と言えます。


考えた結果、「チャートの時間足のMA値は計算されているけれど、それ以外の時間足のMA値は計算されていないのでは?」という可能性が頭に浮かびました。この可能性を確認するために以下の実験をしてみました。

まず、h_ma2という変数を用意して、OnInit関数の中に次の記述をしました。

h_ma2 = iMA(NULL, 0, maPeriod, 0, MODE_SMA, PRICE_CLOSE);  // 第2引数が 0 なのでチャートの時間足のMAを計算します

次に、OnCalculate関数の先頭に次の記述をしました。

printf("BarsCalculated of h_ma1 = %d", BarsCalculated(h_ma1));
printf("BarsCalculated of h_ma2 = %d", BarsCalculated(h_ma2));

ファイルをコンパイルしてチャートに入れてみると、エキスパートに次のように表示されました。

BarsCalculated of h_ma1 = -1
BarsCalculated of h_ma2 = 250176


この結果から2つのことが分かりました。

1. OnCalculate関数が1回実行された
2. チャートの時間足のMA値は計算されているが、1時間足のMA値はまだ計算されていない


というわけで、

・取引時間外にチャートにインディケーターを入れた場合、OnCalculateは少なくとも1回は実行される。
・この時点で、チャートの時間足のMA値は計算済みなので、画面に表示される。
・この時点で、MTFのMA値はまだ計算されていないので、画面に表示されない。

ということのようです。

なお、OnCalculate関数の実行回数については不明な部分があります。もしインディケーターをチャートに入れたままずっと放置しておけば、たとえば10分に1回とか1時間に1回とか、何らかのタイミングでOnCalculateが実行されるのかもしれません。私は数分くらい様子を見ただけで、インディケーターをチャートから除去してしまったので、その辺りが分かりません。そこで「少なくとも1回は」と表現した次第です。


ついでに、MA_1[ ]の要素数についても調べてみました。OnInit関数の最後に

printf("array size of MA_1 (OnInit) = %d", ArraySize(MA_1));

と記述して、OnCalculateの先頭に

printf("array size of MA_1 (OnCalculate) = %d", ArraySize(MA_1));

と記述したところ、次のように表示されました。

array size of MA_1 (OnInit) = 0
array size of MA_1 (OnCalculate) = 250176

これを見ると、OnInit関数の時点では、MA_1[ ]の要素数がゼロなので、MA_1[ ]を使えませんね。 仮にiMA関数を使わずにMA値を計算しMA_1[ ]にデータを入れようとしても、array out of rangeになってしまいます。

また、MA_1[ ]はSetIndexBuffer関数により0番バッファに関連づけられていますから(ターミナルの管理下に置かれていますから)、ArrayResizeで要素数を変更することもできません。

というわけで、「Tick受信が無い時間帯」「(SetIndexBuffer関数によって)ターミナルの管理下に置かれた配列」を使ってMTFの」MAを画面に表示させるのは難しいようです。

ただ、OnCalculate関数が実行された時点ではMA_1[ ]の要素数が250176となっていますので、この段階なら、iMA関数に頼らずに上位足のMA値を計算し、その結果をMA_1[ ]に代入すればMTFのMAが表示されるかもしれません。たとえば、もし5分足チャートに1時間足のMAを表示するのであれば、〇時55分に発生した5分足の終値は1時間足の終値と同じですから、それを集計するとか。あるいは、iClose関数で1時間足の終値を取得できればそれを使っても良いかもしれません。しかし、そこまで作り込むメリットを余り感じませんね(^^;。

VimでMQL5をコンパイルする

参考にした記事

この記事では、下記リンクの記事に記載されているPowerShellスクリプトを使わせていただいています。

How to Code & Compile MQL5 in Visual Studio - A Complete Guide - Other - 13 July 2018 - Traders' Blogs


この記事を書いた理由

基本的には上記「参考にした記事」の通りにやればコンパイルはできるのですが、私なりに「こうしたい」と思う部分もありました。
そこで少しだけ変更したのですが、PoweShellを知らない上、Vimの関数を書いたことがなかったので、軽微な作業の割に少し時間がかかりました。

そういった過去を思い出したので、記事として残しておくことにしました。

手順1:PoweShellスクリプトを用意

MetaEditorの実行ファイルを呼び出して「.mq5」ファイルをコンパイルするためのファイルを用意します。 コードは以下のとおりです。これを「Compile-mql5.ps1」というファイル名で保存します。

# 参考にした記事
#    https://www.mql5.com/en/blogs/post/719548
#    上記の記事に掲載されているコードを一部変更して使わせていただいています。


# gets the File To Compile as an external parameter... Defaults to a Test file...
# 次の行において環境に応じて変わる部分は「〇〇〇」となっています。
Param( $FileToCompile = "C:\Users\〇〇〇\AppData\Roaming\MetaQuotes\Terminal\〇〇〇\MQL5\Experts\Test.mq5")

# cleans the terminal screen and sets the log file name...
Clear-Host
$LogFile = $FileToCompile + ".log"

# before continue check if the Compile File has any spaces in it...
if ($FileToCompile.Contains(" ")) {
    "";"";
    Write-Host "ERROR!  Impossible to Compile! Your Filename or Path contains SPACES!" -ForegroundColor Red;
    "";
    Write-Host $FileToCompile -ForegroundColor Red;
    "";"";
    return;
}

# 【備考1】元のコードでは、ここにMT5を終了させるためのコードがありました。

#fires up the Metaeditor compiler...
# 次の行において環境に応じて変わる部分は「〇〇〇」となっています。
& "C:\Program Files\〇〇〇\metaeditor64.exe" /compile:"$FileToCompile" /log:"$LogFile" /inc:"C:\Users\〇〇〇\AppData\Roaming\MetaQuotes\Terminal\〇〇〇\MQL5" | Out-Null

#get some clean real state and tells the user what is being compiled (just the file name, no path)...
""

#【前行について】
# 元のコードではコンソール出力時に5行の空行を入れていました
# "";"";"";"";""    ← 元のコード
# しかし、1行の空行で良いと感じたので、"" だけにしました

$JustTheFileName = Split-Path $FileToCompile -Leaf
Write-Host "Compiling (as MQL5).....: $JustTheFileName"
""

#reads the log file. Eliminates the blank lines. Skip the first line because it is useless.
$Log = Get-Content -Path $LogFile | Where-Object {$_ -ne ""} | Select-Object -Skip 1

#Green color for successful Compilation. Otherwise (error/warning), Red!
$WhichColor = "Red"
$Log | ForEach-Object { if ($_.Contains("0 errors, 0 warnings")) { $WhichColor="Green" } }

#runs through all the log lines...
$Log | ForEach-Object {
     #ignores the ": information: error generating code" line when ME was successful
     if (-Not $_.Contains("information:")) {
           #common log line... just print it...
           Write-Host $_ -ForegroundColor $WhichColor
     }
}

# 【備考2】元のコードでは、ここにMT5を起動させるためのコードがありました。

# 以下の行は元のコードにありませんでしたが、logファイルを削除するために追加しました。
# (logファイルを残しておきたい場合、以下の行を記述する必要はありません。)
Remove-Item -Path $LogFile

上記コードでは削除してありますが(「備考 1」と「備考 2」という箇所)、元々これらの箇所には、MT5を終了するコードと起動するコードが書かれていました。

MT5を終了するコード

# first of all, kill MT Terminal (if running)... otherwise it will not see the new compiled version of the code...
Get-Process -Name terminal64 -ErrorAction SilentlyContinue | Where-Object {$_.Id -gt 0} | Stop-Process

MT5を起動するコード

# get the MT Terminal back if all went well...
if ( $WhichColor -eq "Green") { & "C:\Program Files\〇〇〇\terminal64.exe" }


私の想像ですが、MT5を終了しコンパイル後に再起動する目的は、コンパイル後の実行ファイル(インディケーター等)をチャートに反映させることだと思います。

MetaEditorを使ってファイルをコンパイルすると、コンパイル後の実行ファイルをチャートに反映させてくれます(恐らくMetaEditorからMT5にシグナルを送っているのだと思います)。しかし、上記コードではそれができません。そこで、MT5を終了し、コンパイル後に再起動することで同じ目的を達成しようとしているのだと思います。

それはそれで便利なのですが、私は再起動に時間がかかることが嫌でした。そこで、ファイルをチャートから手動で削除して、再びチャートに追加しています。勿論これも手間ですから、良い方法ではありません。ただ、「MT5を再起動する」と「ファイルをチャートに入れ直す」をどちらも試してみた結果、こちらの方が私にとって違和感が少なかったというだけのことです。


手順2:Vimの設定ファイルを修正

Vimの設定ファイル(_vimrc)に、PowerShell上で「Compile-mql5.ps1」を実行する関数を記述します。

以下の関数は、ユーザー「neko」の「ドキュメント」フォルダの中に「vim」フォルダがあり、そこに「Compile-mql5.ps1」を置いてあるという想定です。Vimの関数について知識が無いので、ネットを見つつ「こんな感じかしら?」と書いています。どこか間違っていたら教えてください。)

function CompileMQL()
    setlocal shell=\"C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe\"
                   \ shellcmdflag=-c
                   \ shellquote=\"
                   \ shellxquote=
    if expand("%:e") == "mq5"
        w
        bo terminal ++rows=12
                  \ ++shell C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe
                  \ -ExecutionPolicy RemoteSigned
                  \ -File C:\Users\neko\Documents\vim\Compile-mql5.ps1 %
    else
        echo "This file is not mq5."
    endif
endfunction

内容は単純で、ファイルの拡張子がmq5ならコンパイルを実行し、そうでない場合は「This file is not mq5.」と表示します。

7行目にある

w

は無くても良いのですが、ファイルを編集した後、上書き保存を忘れてしまうことが時々あるので、念のため記述してあります。

コンパイルする

コンパイルするには、コンパイルしたい「.mq5」ファイルをVimで開いている状態でCompileMQL関数を実行します。

:call CompileMQL()


ただ、これも少し手間なので、Vimの設定ファイルに次の記述をして、普段余り使うことのない「F9」キーに割り当てています。

nnoremap <F9> :call CompileMQL()<CR>


コンパイルを実行すると、Vimの下部にウィンドウが開いて結果を表示します。

コンパイル時にエラー等が無ければ、次のように表示されます。
ちなみに、「Result」の「R」に付いている枠は、vimのカーソルです。

何らかのエラー等がある場合は、エラー等の内容が赤字で表示されます。