取引戦略のための徹底的なテストとパラメーターの選択の必要性を正当化する必要はないと思います... Matlabの理由を説明した方が良いでしょう。
MetaTraderトレーディングターミナルには、トレーディングストラテジーをテストおよびセットアップするための組み込みシステムがあります。これにより、特定の履歴セクションでストラテジーを実行し、グラフィック表示および特定の履歴セクションで特定のストラテジーのパフォーマンス特性を備えたタブレットの形でトレーディング結果を見ることができます。 これがNova戦略にとってどのようなものか、以下を参照してください。


私の意見では、このテストシステムには多くの重大な欠点があります。
1)実際のティックデータは使用できません。シミュレーションできるのは、サーバーに保存されている分、5分、15分などのデータを使用する場合のみです。 時間枠;
2)戦略の最適なパラメーターを選択するには、利用可能な最適化手順のかなり少ないセットで十分です。
3)わずかに多様ですが、最適化できる取引戦略ターゲットのセットがまだ不十分です。
4)取引戦略を開発するための環境は、最小限のプログラミングに適していますが、さまざまな機能や利用可能なツールには感心しません。
しかし、ティック(または少なくとも毎秒)データを使用して、取引状況の正確なモデリング、あらゆるレベルの数学的複雑性の取引戦略の作成、戦略の品質を評価するための独自の基準の記述、さまざまな最適化手順を使用した最適なパラメーターの検索などを行うことができます。
Matlabを戦略の開発とテストのプラットフォームとして使用すると、これらすべてが可能になります。 MetaTraderから初期データが必要になります(私の例では、毎秒27通貨ペアの現在の2番目の価格)。 その後、MetaTraderは、たとえばDLLの形で実装された既製の取引戦略での作業に引き続き使用されますが、システムを作成する最も創造的な部分は、より数学的な環境で行われます。
データはカスタムインジケーターによって収集されます。MQL4のコードは次のようになります。
//+------------------------------------------------------------------+ //| pack_collector.mq4 | //| Copyright 2014, JamaGava | //| | //+------------------------------------------------------------------+ #property copyright #property link #property version #property strict int filehandleData; int filehandleHead; int filehandleVolHead; int filehandleVolData; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); filehandleData = FileOpen(,FILE_WRITE|FILE_CSV); filehandleHead = FileOpen(,FILE_WRITE|FILE_CSV); filehandleVolHead = FileOpen(,FILE_WRITE|FILE_CSV); filehandleVolData = FileOpen(,FILE_WRITE|FILE_CSV); FileWrite(filehandleHead, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ); FileWrite(filehandleVolHead, , , , , , , , , , , , , , , , , , , , , , , , , , , , ); FileClose(filehandleHead); FileClose(filehandleVolHead); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); FileClose(filehandleData); } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { FileWrite(filehandleData, MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK), MarketInfo(, MODE_BID), MarketInfo(, MODE_ASK) ); FileWrite(filehandleVolData, iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0), iVolume(, PERIOD_H1,0) ); } //+------------------------------------------------------------------+
そのため、データが収集され、CSVファイルに保存されます。 行には、現在の相場が列に含まれています。各通貨ペアの一連の入札相場と売相場の相場です。 合計、列は考慮される通貨ペアの2倍です。
コンピューターでMetaTraderを継続的に実行する能力がないと仮定すると、データはブロック単位で送信されます。ダウンロード、プログラムの終了、上書きされないようにファイル名を変更、再起動などが可能です。
現時点では、16週間にわたってデータを収集しています。 さらに、データのサイズは、収集が月曜日に「午前中」に開始され、「昼食後」に金曜日に終了したという事実により異なります。 したがって、すべての配列が異なるサイズのデータブロックを持つという事実に我慢する必要があります。 Matlabでは、次のようにロードします:
スクリプトloadPrices.m:
global PRICES; N = 16; PRICES = cell( N, 1 ); fNames = { 'C:\matlabR2008a_win\work\frx\DATA\1\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\2\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\3\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\4\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\5\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\6\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\7\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\8\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\9\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\10\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\11\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\12\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\13\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\14\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\15\tickPack.csv'; 'C:\matlabR2008a_win\work\frx\DATA\16\tickPack.csv'; }; for i = 1:N PRICES{ i } = load( fNames{ i } ); display( i ); end
その結果、すべてのデータブロックはグローバル変数PRICESに格納されます。これは16セルです。
ここで、主なアイデアは、入力としてパラメーターのみを受け入れるように戦略を作成することです。 すべてのソースデータはグローバル変数を介して渡されます。 戦略の実行中、トランザクションが終了するたびに、戦略の作業の品質を特徴付けるグローバル変数が調整されます。 これらの特性を最適化するために、グローバル変数が初期化される「シェル」を作成し、戦略のどのパフォーマンスインジケータがパラメータとして返され、実際には戦略が呼び出されます。
スクリプトtestStrat.m:
function [R] = testStrat( P ) global DrawDown; global Recovery; global Equity; global DealsNumber; global MeanDeal; global DealsSigma; global Sharp; global ProfitFactor; global ProfitDealsNumber; global ProfitDealsRate; global ProfitMean; global LossDealsNumber; global LossDealsRate; global LossMean; global returnParamIndex; global stratName; initRates( 0 ); feval( stratName, P ); switch( returnParamIndex ) case 1 R = DrawDown; case 2 R = -Recovery; case 3 R = -Equity; case 4 R = -DealsNumber; case 5 R = -MeanDeal; case 6 R = DealsSigma; case 7 R = -Sharp; case 8 R = -ProfitFactor; case 9 R = -ProfitDealsNumber; case 10 R = -ProfitDealsRate; case 11 R = -ProfitMean; case 12 R = LossDealsNumber; case 13 R = LossDealsRate; case 14 R = LossMean; case 0 R = struct( ... 'DrawDown', DrawDown, ... 'Recovery', Recovery, ... 'Equity', Equity, ... 'DealsNumber', DealsNumber, ... 'MeanDeal', MeanDeal, ... 'DealsSigma', DealsSigma, ... 'Sharp', Sharp, ... 'ProfitFactor', ProfitFactor, ... 'ProfitDealsNumber', ProfitDealsNumber, ... 'ProfitDealsRate', ProfitDealsRate, ... 'ProfitMean', ProfitMean, ... 'LossDealsNumber', LossDealsNumber, ... 'LossDealsRate', LossDealsRate, ... 'LossMean', LossMean ... ); end
戦略を実装するスクリプトの名前は、グローバル変数stratNameを介して渡されます。 返される戦略の特性は、変数returnParamIndexによって決まります。値が0の場合、構造内のすべての特性が返されます。
「大きいほど良い」という値はすべてマイナスであることに注意してください。これは最適化を普遍化するために行われます。常にターゲットパラメータを最小化します。
実際には、システムの特性:
DrawDown-最大ドローダウン(前の最大値と現在の値の最大差);
回復-回復係数(最終ドローダウンに対する最大ドローダウンの比率);
株式-総賞金;
DealsNumber-トランザクションの数。
MeanDeal-平均取引収入;
DealsSigma-各トランザクションの平均値からの収入の標準偏差。
シャープ-シャープレシオ(平均値からのトランザクション収入の標準偏差に対する平均トランザクション収入の比率);
ProfitFactor-利益要因(総利益に対する総利益の比率);
ProfitDealsNumber-勝ちトレードの数。
ProfitDealsRate-勝ちトレードのシェア。
ProfitMean-平均勝率(成功したトランザクションの場合);
LossDealsNumber-負けトレードの数。
LossDealsRate-負けトレードのシェア。
LossMean-平均損失(負けトレードの場合)。
これらのインジケーターは、initRates.m関数によって初期化されます
function [z] = initRates( mode ) global DrawDown; global Recovery; global Equity; global DealsNumber; global MeanDeal; global DealsSigma; global Sharp; global ProfitFactor; global ProfitDealsNumber; global ProfitDealsRate; global ProfitMean; global LossDealsNumber; global LossDealsRate; global LossMean; global maxEquity; global dealSqr; global totalProfit; global totalLoss; z = mode; maxEquity = 0; dealSqr = 0; totalProfit = 0; totalLoss = 0; if mode == 0 DrawDown = 0; Recovery = 0; Equity = 0; DealsNumber = 0; MeanDeal = 0; DealsSigma = 0; Sharp = 0; ProfitFactor = 0; ProfitDealsNumber = 0; ProfitDealsRate = 0; ProfitMean = 0; LossDealsNumber = 0; LossDealsRate = 0; LossMean = 0; else DrawDown = 1000; Recovery = -1000; Equity = -1000; DealsNumber = 0; MeanDeal = -1000; DealsSigma = 1000; Sharp = -1000; ProfitFactor = 0; ProfitDealsNumber = 0; ProfitDealsRate = 0; ProfitMean = 0; LossDealsNumber = 100000; LossDealsRate = 1000; LossMean = 1000; end
さらに、0と明らかに悪い値の両方を初期化できます(入力パラメーターによって決定されます)。 特定の戦略を検討する場合、不適切な初期化が必要な理由を次に示します。 いくつかのヘルパー変数が初期化されることに注意してください。
maxEquity、dealSqr、totalProfit、totalLoss、次のリストで使用されます。
戦略の実装中、トランザクションが終了するたびに、戦略の特性を更新します。 これを行うには、updateStratRates.m関数を使用します。
function [z] = updateStratRates( dealResult ) z = sign( dealResult ); global DrawDown; global Recovery; global Equity; global DealsNumber; global MeanDeal; global DealsSigma; global Sharp; global ProfitFactor; global ProfitDealsNumber; global ProfitDealsRate; global ProfitMean; global LossDealsNumber; global LossDealsRate; global LossMean; global maxEquity; global dealSqr; global totalProfit; global totalLoss; Equity = Equity + dealResult; if Equity > maxEquity maxEquity = Equity; end down = maxEquity - Equity; if down > DrawDown DrawDown = down; end Recovery = Equity / DrawDown; DealsNumber = DealsNumber + 1; MeanDeal = Equity / DealsNumber; dealSqr = dealSqr + dealResult^2; DealsSigma = sqrt( (dealSqr / DealsNumber) - MeanDeal^2 ); Sharp = MeanDeal / DealsSigma; if dealResult > 0 ProfitDealsNumber = ProfitDealsNumber + 1; totalProfit = totalProfit + dealResult; ProfitMean = totalProfit / ProfitDealsNumber; else LossDealsNumber = LossDealsNumber + 1; totalLoss = totalLoss + dealResult; LossMean = totalLoss / LossDealsNumber; end ProfitFactor = -totalProfit / totalLoss; ProfitDealsRate = ProfitDealsNumber / DealsNumber; LossDealsRate = 1 - ProfitDealsRate;
実際、これがテスト戦略のインフラストラクチャ全体です。 これで、一連のパラメーターを入力として受け取る関数として戦略を記述でき、開始時にinitRatesを呼び出し、各終了トランザクションの結果でupdateStratRatesを呼び出します。
ソースデータは、たとえば次のように取得できます。
global pairIndex;
次に、取引システムtwoEMAの詳細について説明します。この例では、すべての仕組みを示します。
このシステムの考え方は、2つの指数移動平均を使用することです。 市場への参入には、次の条件が適用されます。
すべての条件が満たされている場合、購入/販売が実行されます。
1)少なくとも一定の数の4桁のポイントで、遅いものより上/下の速い移動平均。
2)時間枠の始値が速い移動平均よりも高い/低い。
3)現在開いているトランザクションはありません。
いずれかの条件が満たされた場合の終了:
1)現在のトランザクションの確立された利益値の達成。
2)現在のトランザクションの設定損失値の達成(トランザクションで成長するにつれてストップロスを追跡し、現在の価格に引き上げられ、損益分岐点まで);
3)条件1と2が現れ、トランザクションを反対方向に開く。
トランザクションの入力パラメーターは次のとおりです。秒単位の時間枠の長さ、0.0001単位の移動平均のパラメーター、トランザクションを開くための移動平均間の「ギャップ」、4桁の損益の修正値。
さらに、戦略の2つの操作モード、デバッグとデモを提供します。
デモモードでは、デバッグモードとは対照的に、クローズされたトランザクションだけでなく、オープントランザクションも評価されます。オープントランザクションでは、トランザクションの最大利益、トランザクションの最大損失、およびトランザクションを閉じるときの結果の利益/損失が推定されます。
したがって、各トランザクションは、従来の株式折れ線グラフではなく、標準の「バー」で表すことができます。 利益は4桁のポイントで考慮されます。
twoEMA.m:
function [Z] = twoEma( P ) %{ +---------------------------------------------------------------+ ! ! ! Parameters: ! ! timeframe = P(1) ! ! E1 - slow EMA with alpha = P(2) ! ! E2 - fast EMA with alpha = P(3) ! ! ! ! Open: ! ! long deal: (openPrice >= E2 > E1) & abs(E1 - E2) >= P(4) ! ! short deal: (openPrice <= E2 < E1) & abs(E1 - E2) >= P(4) ! ! ! ! Close: ! ! by trailing Stop-loss or by Take profit ! ! ! +---------------------------------------------------------------+ %} global pairIndex; global DATA; global dataBlocksNumber; global WithPlot; % global EQ; % Equity global eqCounter; % % if or( P( 1 ) < 0, ... or( P( 4 ) * 0.0001 <= 0, ... or( P( 5 ) * 0.0001 <= 0, ... or( P( 6 ) * 0.0001 <= 0, ... or( ... or( P( 2 ) * 0.0001 <= 0, P( 2 ) * 0.0001 >= 1 ), ... or( P( 3 ) * 0.0001 <= P( 2 ) * 0.0001, P( 3 ) * 0.0001 >= 1 ) ... ) ... ) ... ) ... ) ... ) initRates( 1 ); % , % Z = 1; return; end initRates( 0 ); % % currEQopen = 0; currEQmin = 0; currEQmax = 0; currEQclose = 0; TF = round( P( 1 ) ); % for currBlock = 1:dataBlocksNumber currData = DATA{ currBlock }; dataSize = size( currData ); dataLen = dataSize( 1 ); tfNumb = ceil( dataLen / TF ); % bid ask bidPrices = currData( :, 2 * ( pairIndex - 1) + 1 ); askPrices = currData( :, 2 * pairIndex ); % dealDir = 0; openDealPrice = 0; stopLoss = 0; takeProfit = 0; currDeal = 0; signal = 0; E1 = bidPrices( 1 ); E2 = bidPrices( 1 ); currTime = 1; for t = 2:tfNumb % ... currTime = currTime + TF; % ( % ) barLen = TF; if t == tfNumb barLen = dataLen - TF * ( tfNumb - 1 ); end for tick = 2:(barLen - 1) % ... if dealDir == 1 % % currDeal = bidPrices( currTime + tick ) - openDealPrice; currEQmin = min( currDeal, currEQmin ); currEQmax = max( currDeal, currEQmax ); currEQclose = currDeal; % - stopLoss = max( stopLoss, bidPrices( currTime + tick ) - P( 5 ) * 0.0001 ); % - - if or( bidPrices( currTime + tick ) <= stopLoss, ... bidPrices( currTime + tick ) >= takeProfit ... ) dealDir = 0; % updateStratRates( currDeal ); % , % if WithPlot [EQ, eqCounter] = updateSeries( EQ, eqCounter, [currEQopen, currEQmax, currEQmin, currEQclose] ); end end end if dealDir == -1 % currDeal = openDealPrice - askPrices( currTime + tick ); currEQmin = min( currDeal, currEQmin ); currEQmax = max( currDeal, currEQmin ); currEQclose = currDeal; % - stopLoss = min( stopLoss, askPrices( currTime + tick ) + P( 5 ) * 0.0001 ); % - - if or( askPrices( currTime + tick ) >= stopLoss, ... askPrices( currTime + tick ) <= takeProfit ... ) dealDir = 0; % updateStratRates( currDeal ); % , % if WithPlot [EQ, eqCounter] = updateSeries( EQ, eqCounter, [currEQopen, currEQmax, currEQmin, currEQclose] ); end end end end % E1 = E1 + P( 2 ) * 0.0001 * ( bidPrices( currTime ) - E1 ); E2 = E2 + P( 3 ) * 0.0001 * ( bidPrices( currTime ) - E2 ); % signal = 0; if and( and( bidPrices( currTime ) >= E2, E2 > E1 ), abs( E1 - E2 ) >= P( 4 ) * 0.0001 ) signal = 1; end if and( and( bidPrices( currTime ) <= E2, E2 < E1 ), abs( E1 - E2 ) >= P( 4 ) * 0.0001 ) signal = -1; end % , % if or( ... and( dealDir == 1, signal == -1 ), ... and( dealDir == -1, signal == 1 ) ... ) dealDir = 0; % updateStratRates( currDeal ); % , % if WithPlot [EQ, eqCounter] = updateSeries( EQ, eqCounter, [currEQopen, currEQmax, currEQmin, currEQclose] ); end end % if and( dealDir == 0, signal == 1 ) dealDir = 1; openDealPrice = askPrices( currTime ); % % - - stopLoss = bidPrices( currTime + tick ) - P( 5 ) * 0.0001; takeProfit = bidPrices( currTime + tick ) + P( 6 ) * 0.0001; % Equity currEQopen = askPrices( currTime + tick ) - bidPrices( currTime + tick ); currEQmin = askPrices( currTime + tick ) - bidPrices( currTime + tick ); currEQmax = askPrices( currTime + tick ) - bidPrices( currTime + tick ); currEQclose = askPrices( currTime + tick ) - bidPrices( currTime + tick ); end % if and( dealDir == 0, signal == -1 ) dealDir = -1; openDealPrice = bidPrices( currTime ); % % - - stopLoss = askPrices( currTime + tick ) + P( 5 ) * 0.0001; takeProfit = askPrices( currTime + tick ) - P( 6 ) * 0.0001; % Equity currEQopen = askPrices( currTime + tick ) - bidPrices( currTime + tick ); currEQmin = askPrices( currTime + tick ) - bidPrices( currTime + tick ); currEQmax = askPrices( currTime + tick ) - bidPrices( currTime + tick ); currEQclose = askPrices( currTime + tick ) - bidPrices( currTime + tick ); end end % if dealDir ~= 0 % updateStratRates( currDeal ); % , % if WithPlot [EQ, eqCounter] = updateSeries( EQ, eqCounter, [currEQopen, currEQmax, currEQmin, currEQclose] ); end end end Z = 0;
上記のリストでは、updateSeriesヘルパー関数が使用されています。これは一種の「push_back」です。
関数updateSeries.m:
function [S, I] = updateSeries(s, i, v) if i == 0 S = v; I = 1; else I = i + 1; S = [s; v]; end
そして最後に、これらすべてを一緒に使用する方法:
スクリプトmainScript.m:
その結果、取引システムの公平性は次のようになります。

また、特徴は次のとおりです。
ドローダウン:0.0105
回復:12.6103
純資産:0.1320
取引番号:47
平均値:0.0028
DealsSigma:0.0056
シャープ:0.5034
ProfitFactor:3.3393
ProfitDealsNumber:34
ProfitDealsRate:0.7234
利益平均:0.0055
LossDealsNumber:13
LossDealsRate:0.2766
損失平均:-0.0043
それはもっと良かったかもしれません...それは驚くべきことではありませんが、少なくともfminsearchよりもインテリジェントな最適な検索関数を使用する必要があります。
しかし、スーパーシステムを作成するという目標はありませんでした。 また、Matlabを使用する可能性が示されています。