コンテンツにスキップ

【移植#74】再初期化しても同じ形か — 差分計算と全計算を500本照合

EURUSD M1の実履歴を順送りし、金色の全履歴参照と水色の差分計算が重なること、確定500本が新規全計算・独立数式とも一致したことを表示したMT5実画面

この記事の3行まとめ

  • IndicatorCounted()を0扱いにする原典の応急処置を、差分計算と全履歴計算の一致を測る監査へ作り替えました
  • 確定足だけを比べ、値がずれた位置には赤い印を出します。全再計算はリペイントする数式そのものを直す方法ではありません
  • EURUSD M1を40本順送りした実機検証では、確定500本が内部参照・新規全計算・独立数式のすべてと一致しました

今回の原典は「再初期化すると形が変わる」

投稿日順で先にある2010年3月23日の原典は、PivotへS3・R3と中間線を追加する内容でした。

元記事「Pivotのミドル(中間値)を引いてみた。」を読む

Pivotの計算と日別表示は移植#72で実装済みです。さらに、線が増えるほど「どれかに当たった」ように見える問題は移植#68で面積とヒット率を測っています。中間線だけを足すと同じ説明を繰り返すため、この日は番号を使わずに進みました。

採用したのは、翌3月24日の記事です。

元記事「再初期化でカタチが変わってしまうインジケータへの応急処置。」を読む

原典は、何本かバーが増えた後にインジケーターのプロパティを開閉すると、表示の形が変わる例を扱っています。当時のMQL4で使われていた次の値を、常に0扱いにする応急処置でした。

MQL
int counted_bars = IndicatorCounted();

// 応急処置
int counted_bars = 0;

計算済み本数を使わなければ、毎回すべてのバーを先頭から計算します。差分更新の開始位置や途中状態にバグがある場合、これで見た目がそろうことはあります。

ただし、原典も「どんなインジケーターでもうまくいくわけではない」と断っています。今回はこの注意を出発点に、毎回全計算することを対策にせず、差分結果と全計算結果が本当に同じかを数えるMQL5インジケーターへ移しました。

全再計算で直る問題と、直らない問題

再初期化後に形が変わる原因は、少なくとも2種類に分ける必要があります。

原因 例 全再計算の役割
差分更新の実装ミス 再帰状態を復元せず途中から計算する、必要な1本前を計算し直さない 不具合を一時的に隠すことはある。正解は依存範囲を直すこと
数式が未来データを使う 後から分かった高安で過去サインを置く、中央寄せ計算を売買判断へ使う 未来情報込みの形を描き直すだけ。リペイントは直らない

形成中の現在足がTickごとに動くことも、確定した過去足が後から書き換わることとは別です。そこで配布版は、合否判定から現在足を除きます。

未来情報による過去書換えの見分け方は検証#13、全期間を毎Tick計算する負荷は検証#14で扱っています。今回はその間にある、高速化した計算が再初期化後も同じ値を再現できるかへ焦点を絞りました。

MQL5ではprev_calculated=0を全再構築の合図にする

MQL5のOnCalculate()には、前回の戻り値がprev_calculatedとして渡されます。通常は、すでに変化していないバーを飛ばすために使います。

一方、深い履歴が追加されたり、履歴の欠損が埋められたりして価格データが変わると、端末はprev_calculatedを0へ戻します。初回だけ0になるとは限りません。

配布版の差分経路は、考え方を次のように分けています。

MQL
const bool reset = prev_calculated <= 0 || prev_calculated > rates_total;

int start = first_valid;
if(!reset)
   start = MathMax(first_valid, prev_calculated - 1);
  • reset=true: 出力バッファだけでなく、再帰計算の途中状態も消し、履歴から作り直す
  • 通常更新: prev_calculated - 1から再計算し、形成中バーと、そのつながりに使う直前バーを更新する
  • 全履歴参照: 毎回first_validから別バッファへ計算し直す

計算が過去何本へ依存するかは、数式ごとに違います。-1を機械的にコピーすればよいわけではありません。たとえば過去N本の確定状態を後から修正する式なら、その依存範囲まで開始位置を戻す必要があります。

入力のtime[]やclose[]がどちら向きに並ぶかも暗黙の前提にしません。公式仕様が勧めるとおり、配布版は価格配列とインジケーターバッファを、古い足から新しい足へ進む向きに明示してから計算しています。

比較用の数式

監査対象には、履歴先頭から引き継ぐ状態を持つ、単純な因果的オシレーターを使いました。まず直近N本の高値と安値の間で、終値の位置を-1〜+1へ正規化します。

Text Only
x[t] = 2 × (Close[t] − Lowest[t,N])
           ÷ (Highest[t,N] − Lowest[t,N]) − 1

次に、前のバーの状態を使って平滑化し、対数変換します。

Text Only
s[t] = α × x[t] + (1 − α) × s[t−1]
y[t] = 0.5 × ln((1 + s[t]) ÷ (1 − s[t]))

ゼロ幅ではx[t]=0とし、対数の定義域を外れないようs[t]を-0.999〜+0.999へ制限します。未来のバーは参照しません。

チャートでは、次の3系列を表示します。

  • 太い金色: 履歴先頭から毎回作る全計算の参照値
  • 細い水色: prev_calculatedを使う差分計算の候補値
  • 赤い印: 許容差を超えた確定バー

2本が一致すると、金色の縁の上に水色が重なります。掲載画像に赤い印がないのは、比較した確定500本に不一致がなかったためです。

万能なブラックボックス検査ではありません

配布版が直接監査するのは、同じファイル内に実装したこの数式です。名前を指定するだけで、任意の第三者インジケーターの内部状態を検査するものではありません。自作インジケーターへ応用するときは、計算部分を対象ロジックへ置き換え、差分経路と独立した全計算経路を残してください。

パラメーター

パラメーター 初期値 役割
InpLookback 20 高値・安値を探す期間
InpSmoothing 0.33 再帰平滑の係数。0より大きく1以下
InpAuditBars 500 比較する確定バー数
InpTolerance 1e-10 不一致とみなす絶対誤差
InpMode AUDIT_INCREMENTAL 候補系列を差分計算。診断用に毎回全計算へも切替可能

参照系列は、どちらのモードでも全履歴を計算します。そのため、このインジケーター自体は正しさを調べる監査用であり、多数のチャートへ常時入れる軽量な売買指標ではありません。

74_Reinit_Consistency_Auditor_v1_00.mq5 をダウンロード

実履歴を40本順送りして検証

専用MT5で配布インジケーターと独立検証EAをコンパイルし、どちらも0エラー・0警告でした。

検証では、EURUSD M1の確定履歴940本を使いました。最初の900本でインジケーターを初期化し、残り40本は、各バーの始値・高値・安値・終値を4つのTickとして時刻順に送っています。合計160回の通常更新を通すことで、初回全計算だけが一致した状態を合格にしないためです。

Tickの送信にはCustomTicksAdd()を使いました。公式仕様では、この関数はカスタム銘柄へサーバー配信のようにTickを渡し、チャートやMQLプログラムへイベントを発生させます。

最初の試行では、バー履歴をCustomRatesUpdate()で1本ずつ追記しました。しかしBuild 6182では、40回すべてでprev_calculatedが0へ戻り、差分計算を一度も通りませんでした。履歴が変更されたときに0へ戻るOnCalculate()の仕様どおりで、再初期化テストには使えても、通常の新Tickを再現する入力にはなりません。

値が一致しただけで「差分計算も正しい」と推測せず、差分呼び出し回数とリセット回数を別に観測したことがポイントです。

500本の三重照合結果

直近の形成中バーを除き、確定500本を次の3方向から照合しました。

  1. 差分候補と、同じインジケーター内の全履歴参照
  2. 差分候補と、prev_calculated=0から始まる全計算モードの新規ハンドル
  3. 差分候補と、独立検証EAが元OHLCから別に計算した値

結果は次のとおりです。

検査項目 結果
使用したM1バー 940
順送りしたバー / Tick 40 / 160
比較した確定バー 500
差分候補 vs 内部の全履歴参照 不一致0
差分候補 vs 新規の全計算 不一致0
差分候補 vs 独立数式 不一致0
新規の全計算 vs 独立数式 不一致0
比較時刻・OHLCのずれ 0
incremental_calls 160
reset_calls 1
最後の候補計算本数 1
最後の全履歴参照本数 921
最大絶対誤差 0
注文数・ポジション数 開始前後で変化なし

全履歴参照が940本ではなく921本なのは、20期間の計算に必要な最初の19本には有効な出力がないためです。最後のTickでは候補系列が現在の1本だけを更新した一方、参照系列は有効な921本を作り直しました。

新規ハンドル直後は、BarsCalculated()で計算済み本数を確認し、CopyBuffer()の戻り値が要求件数と一致してから比較しています。準備途中の空値や短い配列を、0差として数えていません。

自作インジケーターで確認する順番

再初期化で形が変わるコードを直すときは、先に毎回全計算へ逃がすより、次の順番で差を狭めると原因を追いやすくなります。

  1. 入力配列と出力バッファの向きを明示する
  2. prev_calculated=0で、出力とすべての再帰状態を同時に初期化する
  3. 通常更新の開始位置を、その数式が必要とする依存範囲まで戻す
  4. 形成中バーを除いた同じ時刻どうしを比較する
  5. 差分経路とは別に、履歴先頭から作る参照実装を用意する
  6. 新規インスタンスと値を照合し、計算済み件数・取得件数も確認する
  7. それでも過去値が変わるなら、未来バーや後から確定する状態を数式が使っていないか調べる

再帰式では、表示バッファだけ消して内部状態を残すと、再計算の起点が変わります。反対に、通常Tickのたびに全状態を消すと正しく見えても負荷だけが増え、差分実装の欠陥は残ります。

注意点

  • 今回の0差は、掲載した数式、EURUSD M1の確定500本、指定したパラメーターと許容差での結果です。別の数式や履歴まで一般化できません
  • 配信元が過去のOHLCを訂正した場合、同じ時刻でも入力自体が変わります。入力変更と計算状態の不整合を分けてください
  • 現在足はTickで変化するため、売買判定の再現性を確かめるなら確定足を基準にします
  • 全履歴参照は意図的に重い処理です。監査が終わったら、本番版から外すか低頻度の診断だけに限定してください
  • このオシレーターは計算経路を比較する教材で、エントリーや決済の優位性を検証したものではありません
  • 配布版は売買、通知、ファイル出力、DLL、WebRequestを行いません

使い方

  1. MQ5をMT5のデータフォルダー内のMQL5/Indicatorsへ置き、MetaEditorでコンパイルします。
  2. 調べたい銘柄・時間足のチャートへ装着します。
  3. サブウィンドウの短名でMATCH件数を確認します。
  4. 赤い印が出たら、そのバーより前に必要な状態と、差分計算の開始位置を調べます。
  5. InpMode=AUDIT_FULL_EACH_CALLへ切り替えると候補系列も毎回全計算になります。これは原因の切り分け用で、常用設定ではありません。

まとめ

原典の「計算済み本数を0にして全部やり直す」という応急処置を、今回は合否を測れる形へ変えました。

再初期化後の形が正しいかは、見た目だけでは決まりません。同じ入力、同じ確定時刻、同じ許容差で、差分計算・全計算・独立実装を比べて初めて確認できます。

全計算は答えではなく、比較の物差しです。差が見つかったら、prev_calculatedが0へ戻る条件、状態の初期化、依存範囲、未来データの順に切り分けてください。

あわせて読みたい記事


前後の記事

前: 【移植#73】最適化9パスの勝率をCSVへ — OnTesterとフレームで取りこぼさない

次: 【移植#75】TIIの20越え・80割れ通知 — 同じ名前でも式が違う

— SPONSORED —