MACD PatternTrader セッション戦略
概要
この戦略は、MetaTraderエキスパートアドバイザー MacdPatternTraderAll0.01 の直接変換です。6つの異なるMACDベースのエントリーパターン、オプションの取引時間フィルタリング、部分的な利益確定、およびマーチンゲールポジションサイジングオプションを使用して単一の銘柄を取引します。すべての計算は、設定されたCandleTypeによって提供される完成したローソク足で実行されます。
取引ロジック
- すべての完成したローソク足において、戦略は6つのMACDインジケーターを更新します(各パターンには独自の速いEMA、遅いEMAの長さと1期間のシグナルラインがあります)。
- 取引時間フィルタリングが有効な場合、新しい取引は
SessionStartとSessionEndの間でのみ評価されます。リスク管理は常にアクティブです。 - 各MACDパターンは、現在のMACD値と前の2つの値の間の非常に特定の値の関係をチェックして、モメンタムリバーサルを検出します。パターンがトリガーされると、対応する方向に成行注文を送信し、内部ストップロスとテイクプロフィットレベルを設定します。
- ストップロスは、設定可能なルックバックの最近の極値(ショートの最高値、ロングの最低値)に価格ステップで測定されたオフセットを加減したものとして計算されます。テイクプロフィットは、元のエキスパートの再帰的なターゲット検索を再現するためにブロック内の古いローソク足グループをスキャンします。
- 一度に管理されるのは1つのネットポジションのみです。反対方向に新しいシグナルが現れると、現在のポジションがクローズされ、マーチンゲール調整済みボリュームで逆ポジションがオープンされます。
- アクティブなポジションは
ManageActivePositionによって監視されます。ロジックはオリジナルの部分クローズルーティンをエミュレートします:- ロングの場合:利益が
ProfitThreshold(5通貨単位)を超え、前の終値が中期EMAを上回ると、ポジションの3分の1が売却されます。利益が続き、前の高値が長期SMAと非常に遅いEMAの平均を上回ると、残りのポジションの半分がクローズされます。 - ショートの場合:対称ルールが、利益目標と移動平均フィルターが満たされたときに、3分の1、次に残りの半分をクローズします。
- ロングの場合:利益が
- リスク管理は取引ウィンドウに関係なくすべてのローソク足で実行されます。価格がローソク足内(高値/安値に基づく)で保存されたストップロスまたはテイクプロフィットレベルを突破すると、ポジション全体が突破価格でフラット化されます。
- 取引が完全にクローズされた後、実現PnLが評価されます。
UseMartingaleが有効な場合、損失取引は次の注文ボリュームを2倍にし、一方で有益な出口はボリュームをベースLotSizeにリセットします。
主要パターン
- パターン1:
Pattern1MaxThresholdを超えて下落し始めるMACDスパイクと、Pattern1MinThresholdを下回ってバウンスするドロップを検出します。 - パターン2: 最小限の偏差でのゼロライン周辺のMACD交差を探します。
- パターン3: 2段階の閾値(
Pattern3MaxThreshold、Pattern3SecondaryMax、Pattern3MinThreshold、Pattern3SecondaryMin)を使用して、両側での3段階のロールオーバーを検出します。また、元のbars_bupの累積を模倣するために、二次最大値を超えた連続バーをカウントします。 - パターン4: MACDが主要閾値を超えるが前のバーがより狭い二次範囲内にある場合に取引し、リバーサルを予期します。
- パターン5:
Pattern5PrimaryMax/Minと二次制限によって定義される狭い範囲内でのMACDの素早いフリップに応答します。 - パターン6: 取引をトリガーする前に複数の連続したMACD偏差を要求するために、カウンター(
Pattern6MaxBars、Pattern6MinBars、Pattern6CountBars)を使用します。
リスク管理
- 内部ストップロスとテイクプロフィットターゲットは各エントリーに対して再計算されます。ストップは価格ステップで測定されたオフセットに加えた価格の極値を使用します。テイクプロフィットは極値が改善しなくなるまでローソク足の連続ブロックを検索し、MQLエキスパートの再帰的ロジックを再現します。
- 部分エグジットは元の最小ロットサイズ(0.01)を尊重し、各方向で何回の部分クローズが実行されたかを追跡します。
- 戦略はブローカー側の保護注文を配置しません;代わりに、設定された価格でポジションをクローズするためにローソク足の高値と安値を監視します。
パラメーター
| パラメーター | 説明 | デフォルト |
|---|---|---|
CandleType |
インジケーターと取引シグナルに使用するローソク足シリーズ。 | 1時間足 |
LotSize |
マーチンゲール調整前のベース取引ボリューム。 | 0.1 |
UseTimeFilter |
SessionStartとSessionEndの間のみ取引を有効にします。 |
true |
SessionStart / SessionEnd |
取引ウィンドウ(地元の取引所時間)。 | 07:00 / 17:00 |
UseMartingale |
損失取引後にLotSizeを2倍にします。 |
true |
Ema1Period, Ema2Period, SmaPeriod, Ema3Period |
部分エグジットに使用する移動平均。 | 7, 21, 98, 365 |
| パターン固有パラメーター | 各パターンは、元のエキスパートの入力に合致する独自の有効化フラグ、ストップロス/テイクプロフィットルックバック、オフセット、EMA長さ、および閾値値を持ちます。 | コンストラクターのデフォルト値を参照 |
すべての閾値とEMA長さはStrategyParamオブジェクトを通じて公開され、最適化またはファインチューニングが可能です。
注意事項
- 戦略は、オフセットと利益を口座通貨に変換するために銘柄が
PriceStepとPriceStepCostを提供することを前提としています。利用できない場合は価格差が直接使用されます。 - ストップとターゲットは内部でシミュレートされます;バーのクローズ時に評価されます。リアルタイムでのイントラバー実行はMetaTraderの動作と異なる場合があります。
- マーチンゲールメカニズムは損失連続後にエクスポージャーを急速に増加させる可能性があります—注意して使用してください。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Multi-pattern MACD strategy converted from the "MacdPatternTrader" expert.
/// </summary>
public class MacdPatternTraderSessionStrategy : Strategy
{
private readonly StrategyParam<decimal> _minPartialVolume;
private readonly StrategyParam<decimal> _profitThreshold;
private readonly StrategyParam<int> _historyLimit;
private readonly StrategyParam<bool> _pattern1Enabled;
private readonly StrategyParam<int> _pattern1StopLossBars;
private readonly StrategyParam<int> _pattern1TakeProfitBars;
private readonly StrategyParam<int> _pattern1Offset;
private readonly StrategyParam<int> _pattern1FastEma;
private readonly StrategyParam<int> _pattern1SlowEma;
private readonly StrategyParam<decimal> _pattern1MaxThreshold;
private readonly StrategyParam<decimal> _pattern1MinThreshold;
private readonly StrategyParam<bool> _pattern2Enabled;
private readonly StrategyParam<int> _pattern2StopLossBars;
private readonly StrategyParam<int> _pattern2TakeProfitBars;
private readonly StrategyParam<int> _pattern2Offset;
private readonly StrategyParam<int> _pattern2FastEma;
private readonly StrategyParam<int> _pattern2SlowEma;
private readonly StrategyParam<decimal> _pattern2MaxThreshold;
private readonly StrategyParam<decimal> _pattern2MinThreshold;
private readonly StrategyParam<bool> _pattern3Enabled;
private readonly StrategyParam<int> _pattern3StopLossBars;
private readonly StrategyParam<int> _pattern3TakeProfitBars;
private readonly StrategyParam<int> _pattern3Offset;
private readonly StrategyParam<int> _pattern3FastEma;
private readonly StrategyParam<int> _pattern3SlowEma;
private readonly StrategyParam<decimal> _pattern3MaxThreshold;
private readonly StrategyParam<decimal> _pattern3SecondaryMax;
private readonly StrategyParam<decimal> _pattern3MinThreshold;
private readonly StrategyParam<decimal> _pattern3SecondaryMin;
private readonly StrategyParam<bool> _pattern4Enabled;
private readonly StrategyParam<int> _pattern4StopLossBars;
private readonly StrategyParam<int> _pattern4TakeProfitBars;
private readonly StrategyParam<int> _pattern4Offset;
private readonly StrategyParam<int> _pattern4FastEma;
private readonly StrategyParam<int> _pattern4SlowEma;
private readonly StrategyParam<decimal> _pattern4MaxThreshold;
private readonly StrategyParam<decimal> _pattern4SecondaryMax;
private readonly StrategyParam<decimal> _pattern4MinThreshold;
private readonly StrategyParam<decimal> _pattern4SecondaryMin;
private readonly StrategyParam<bool> _pattern5Enabled;
private readonly StrategyParam<int> _pattern5StopLossBars;
private readonly StrategyParam<int> _pattern5TakeProfitBars;
private readonly StrategyParam<int> _pattern5Offset;
private readonly StrategyParam<int> _pattern5FastEma;
private readonly StrategyParam<int> _pattern5SlowEma;
private readonly StrategyParam<decimal> _pattern5PrimaryMax;
private readonly StrategyParam<decimal> _pattern5MaxThreshold;
private readonly StrategyParam<decimal> _pattern5SecondaryMax;
private readonly StrategyParam<decimal> _pattern5PrimaryMin;
private readonly StrategyParam<decimal> _pattern5MinThreshold;
private readonly StrategyParam<decimal> _pattern5SecondaryMin;
private readonly StrategyParam<bool> _pattern6Enabled;
private readonly StrategyParam<int> _pattern6StopLossBars;
private readonly StrategyParam<int> _pattern6TakeProfitBars;
private readonly StrategyParam<int> _pattern6Offset;
private readonly StrategyParam<int> _pattern6FastEma;
private readonly StrategyParam<int> _pattern6SlowEma;
private readonly StrategyParam<decimal> _pattern6MaxThreshold;
private readonly StrategyParam<decimal> _pattern6MinThreshold;
private readonly StrategyParam<int> _pattern6MaxBars;
private readonly StrategyParam<int> _pattern6MinBars;
private readonly StrategyParam<int> _pattern6CountBars;
private readonly StrategyParam<int> _ema1Period;
private readonly StrategyParam<int> _ema2Period;
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<int> _ema3Period;
private readonly StrategyParam<decimal> _lotSize;
private readonly StrategyParam<bool> _useTimeFilter;
private readonly StrategyParam<TimeSpan> _sessionStart;
private readonly StrategyParam<TimeSpan> _sessionEnd;
private readonly StrategyParam<bool> _useMartingale;
private readonly StrategyParam<DataType> _candleType;
private readonly List<ICandleMessage> _history = new();
private MovingAverageConvergenceDivergenceSignal _macd1 = null!;
private MovingAverageConvergenceDivergenceSignal _macd2 = null!;
private MovingAverageConvergenceDivergenceSignal _macd3 = null!;
private MovingAverageConvergenceDivergenceSignal _macd4 = null!;
private MovingAverageConvergenceDivergenceSignal _macd5 = null!;
private MovingAverageConvergenceDivergenceSignal _macd6 = null!;
private ExponentialMovingAverage _ema1 = null!;
private ExponentialMovingAverage _ema2 = null!;
private SimpleMovingAverage _sma1 = null!;
private ExponentialMovingAverage _ema3 = null!;
private decimal? _macd1Prev1;
private decimal? _macd1Prev2;
private decimal? _macd1Prev3;
private decimal? _macd2Prev1;
private decimal? _macd2Prev2;
private decimal? _macd2Prev3;
private decimal? _macd3Prev1;
private decimal? _macd3Prev2;
private decimal? _macd3Prev3;
private decimal? _macd4Prev1;
private decimal? _macd4Prev2;
private decimal? _macd4Prev3;
private decimal? _macd5Prev1;
private decimal? _macd5Prev2;
private decimal? _macd5Prev3;
private decimal? _macd6Prev1;
private decimal? _macd6Prev2;
private decimal? _macd6Prev3;
private decimal? _ema1Prev;
private decimal? _ema2Prev;
private decimal? _smaPrev;
private decimal? _ema3Prev;
private decimal _pointSize;
private decimal _pointValue;
private decimal _currentVolume;
private decimal _entryPrice;
private decimal _openVolume;
private decimal _realizedPnL;
private int _entryDirection;
private decimal? _currentStopLoss;
private decimal? _currentTakeProfit;
private int _longPartialStage;
private int _shortPartialStage;
private int _barsBup;
private int _pattern6ShortCounter;
private bool _pattern6ShortBlocked;
private int _pattern6LongCounter;
private bool _pattern6LongBlocked;
private bool _pattern6ShortReady;
private bool _pattern6LongReady;
/// <summary>
/// Initializes a new instance of the <see cref="MacdPatternTraderSessionStrategy"/> class.
/// </summary>
public MacdPatternTraderSessionStrategy()
{
_minPartialVolume = Param(nameof(MinPartialVolume), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Min Partial Volume", "Minimum volume executed during partial exits", "Money Management")
;
_profitThreshold = Param(nameof(ProfitThreshold), 5m)
.SetGreaterThanZero()
.SetDisplay("Profit Threshold", "Profit threshold required before partial profit taking", "Money Management")
;
_historyLimit = Param(nameof(HistoryLimit), 1024)
.SetGreaterThanZero()
.SetDisplay("History Limit", "Maximum number of recent candles stored for pattern analysis", "General")
;
_pattern1Enabled = Param(nameof(Pattern1Enabled), true)
.SetDisplay("Pattern 1 Enabled", "Enable MACD pattern 1", "Pattern 1");
_pattern1StopLossBars = Param(nameof(Pattern1StopLossBars), 22)
.SetGreaterThanZero()
.SetDisplay("Pattern 1 SL Bars", "Stop loss lookback", "Pattern 1");
_pattern1TakeProfitBars = Param(nameof(Pattern1TakeProfitBars), 32)
.SetGreaterThanZero()
.SetDisplay("Pattern 1 TP Bars", "Take profit scan length", "Pattern 1");
_pattern1Offset = Param(nameof(Pattern1Offset), 40)
.SetGreaterThanZero()
.SetDisplay("Pattern 1 Offset", "Stop loss offset in points", "Pattern 1");
_pattern1FastEma = Param(nameof(Pattern1FastEma), 24)
.SetGreaterThanZero()
.SetDisplay("Pattern 1 Fast EMA", "Fast EMA period", "Pattern 1");
_pattern1SlowEma = Param(nameof(Pattern1SlowEma), 13)
.SetGreaterThanZero()
.SetDisplay("Pattern 1 Slow EMA", "Slow EMA period", "Pattern 1");
_pattern1MaxThreshold = Param(nameof(Pattern1MaxThreshold), 0.0095m)
.SetDisplay("Pattern 1 Max", "Upper MACD threshold", "Pattern 1");
_pattern1MinThreshold = Param(nameof(Pattern1MinThreshold), -0.0045m)
.SetDisplay("Pattern 1 Min", "Lower MACD threshold", "Pattern 1");
_pattern2Enabled = Param(nameof(Pattern2Enabled), true)
.SetDisplay("Pattern 2 Enabled", "Enable MACD pattern 2", "Pattern 2");
_pattern2StopLossBars = Param(nameof(Pattern2StopLossBars), 2)
.SetGreaterThanZero()
.SetDisplay("Pattern 2 SL Bars", "Stop loss lookback", "Pattern 2");
_pattern2TakeProfitBars = Param(nameof(Pattern2TakeProfitBars), 2)
.SetGreaterThanZero()
.SetDisplay("Pattern 2 TP Bars", "Take profit scan length", "Pattern 2");
_pattern2Offset = Param(nameof(Pattern2Offset), 50)
.SetGreaterThanZero()
.SetDisplay("Pattern 2 Offset", "Stop loss offset in points", "Pattern 2");
_pattern2FastEma = Param(nameof(Pattern2FastEma), 17)
.SetGreaterThanZero()
.SetDisplay("Pattern 2 Fast EMA", "Fast EMA period", "Pattern 2");
_pattern2SlowEma = Param(nameof(Pattern2SlowEma), 7)
.SetGreaterThanZero()
.SetDisplay("Pattern 2 Slow EMA", "Slow EMA period", "Pattern 2");
_pattern2MaxThreshold = Param(nameof(Pattern2MaxThreshold), 0.0045m)
.SetDisplay("Pattern 2 Max", "Upper MACD threshold", "Pattern 2");
_pattern2MinThreshold = Param(nameof(Pattern2MinThreshold), -0.0035m)
.SetDisplay("Pattern 2 Min", "Lower MACD threshold", "Pattern 2");
_pattern3Enabled = Param(nameof(Pattern3Enabled), true)
.SetDisplay("Pattern 3 Enabled", "Enable MACD pattern 3", "Pattern 3");
_pattern3StopLossBars = Param(nameof(Pattern3StopLossBars), 8)
.SetGreaterThanZero()
.SetDisplay("Pattern 3 SL Bars", "Stop loss lookback", "Pattern 3");
_pattern3TakeProfitBars = Param(nameof(Pattern3TakeProfitBars), 12)
.SetGreaterThanZero()
.SetDisplay("Pattern 3 TP Bars", "Take profit scan length", "Pattern 3");
_pattern3Offset = Param(nameof(Pattern3Offset), 2)
.SetGreaterThanZero()
.SetDisplay("Pattern 3 Offset", "Stop loss offset in points", "Pattern 3");
_pattern3FastEma = Param(nameof(Pattern3FastEma), 32)
.SetGreaterThanZero()
.SetDisplay("Pattern 3 Fast EMA", "Fast EMA period", "Pattern 3");
_pattern3SlowEma = Param(nameof(Pattern3SlowEma), 2)
.SetGreaterThanZero()
.SetDisplay("Pattern 3 Slow EMA", "Slow EMA period", "Pattern 3");
_pattern3MaxThreshold = Param(nameof(Pattern3MaxThreshold), 0.0015m)
.SetDisplay("Pattern 3 Max", "Upper MACD threshold", "Pattern 3");
_pattern3SecondaryMax = Param(nameof(Pattern3SecondaryMax), 0.004m)
.SetDisplay("Pattern 3 Secondary Max", "Secondary upper MACD threshold", "Pattern 3");
_pattern3MinThreshold = Param(nameof(Pattern3MinThreshold), -0.005m)
.SetDisplay("Pattern 3 Min", "Lower MACD threshold", "Pattern 3");
_pattern3SecondaryMin = Param(nameof(Pattern3SecondaryMin), -0.0005m)
.SetDisplay("Pattern 3 Secondary Min", "Secondary lower MACD threshold", "Pattern 3");
_pattern4Enabled = Param(nameof(Pattern4Enabled), true)
.SetDisplay("Pattern 4 Enabled", "Enable MACD pattern 4", "Pattern 4");
_pattern4StopLossBars = Param(nameof(Pattern4StopLossBars), 10)
.SetGreaterThanZero()
.SetDisplay("Pattern 4 SL Bars", "Stop loss lookback", "Pattern 4");
_pattern4TakeProfitBars = Param(nameof(Pattern4TakeProfitBars), 32)
.SetGreaterThanZero()
.SetDisplay("Pattern 4 TP Bars", "Take profit scan length", "Pattern 4");
_pattern4Offset = Param(nameof(Pattern4Offset), 45)
.SetGreaterThanZero()
.SetDisplay("Pattern 4 Offset", "Stop loss offset in points", "Pattern 4");
_pattern4FastEma = Param(nameof(Pattern4FastEma), 4)
.SetGreaterThanZero()
.SetDisplay("Pattern 4 Fast EMA", "Fast EMA period", "Pattern 4");
_pattern4SlowEma = Param(nameof(Pattern4SlowEma), 9)
.SetGreaterThanZero()
.SetDisplay("Pattern 4 Slow EMA", "Slow EMA period", "Pattern 4");
_pattern4MaxThreshold = Param(nameof(Pattern4MaxThreshold), 0.0165m)
.SetDisplay("Pattern 4 Max", "Upper MACD threshold", "Pattern 4");
_pattern4SecondaryMax = Param(nameof(Pattern4SecondaryMax), 0.0001m)
.SetDisplay("Pattern 4 Secondary Max", "Secondary upper MACD threshold", "Pattern 4");
_pattern4MinThreshold = Param(nameof(Pattern4MinThreshold), -0.0005m)
.SetDisplay("Pattern 4 Min", "Lower MACD threshold", "Pattern 4");
_pattern4SecondaryMin = Param(nameof(Pattern4SecondaryMin), -0.0006m)
.SetDisplay("Pattern 4 Secondary Min", "Secondary lower MACD threshold", "Pattern 4");
_pattern5Enabled = Param(nameof(Pattern5Enabled), true)
.SetDisplay("Pattern 5 Enabled", "Enable MACD pattern 5", "Pattern 5");
_pattern5StopLossBars = Param(nameof(Pattern5StopLossBars), 8)
.SetGreaterThanZero()
.SetDisplay("Pattern 5 SL Bars", "Stop loss lookback", "Pattern 5");
_pattern5TakeProfitBars = Param(nameof(Pattern5TakeProfitBars), 47)
.SetGreaterThanZero()
.SetDisplay("Pattern 5 TP Bars", "Take profit scan length", "Pattern 5");
_pattern5Offset = Param(nameof(Pattern5Offset), 45)
.SetGreaterThanZero()
.SetDisplay("Pattern 5 Offset", "Stop loss offset in points", "Pattern 5");
_pattern5FastEma = Param(nameof(Pattern5FastEma), 6)
.SetGreaterThanZero()
.SetDisplay("Pattern 5 Fast EMA", "Fast EMA period", "Pattern 5");
_pattern5SlowEma = Param(nameof(Pattern5SlowEma), 2)
.SetGreaterThanZero()
.SetDisplay("Pattern 5 Slow EMA", "Slow EMA period", "Pattern 5");
_pattern5PrimaryMax = Param(nameof(Pattern5PrimaryMax), 0.0005m)
.SetDisplay("Pattern 5 Primary Max", "Initial ceiling trigger", "Pattern 5");
_pattern5MaxThreshold = Param(nameof(Pattern5MaxThreshold), 0.0015m)
.SetDisplay("Pattern 5 Max", "Upper MACD threshold", "Pattern 5");
_pattern5SecondaryMax = Param(nameof(Pattern5SecondaryMax), 0m)
.SetDisplay("Pattern 5 Secondary Max", "Secondary upper MACD", "Pattern 5");
_pattern5PrimaryMin = Param(nameof(Pattern5PrimaryMin), -0.0005m)
.SetDisplay("Pattern 5 Primary Min", "Initial floor trigger", "Pattern 5");
_pattern5MinThreshold = Param(nameof(Pattern5MinThreshold), -0.003m)
.SetDisplay("Pattern 5 Min", "Lower MACD threshold", "Pattern 5");
_pattern5SecondaryMin = Param(nameof(Pattern5SecondaryMin), 0m)
.SetDisplay("Pattern 5 Secondary Min", "Secondary lower MACD", "Pattern 5");
_pattern6Enabled = Param(nameof(Pattern6Enabled), true)
.SetDisplay("Pattern 6 Enabled", "Enable MACD pattern 6", "Pattern 6");
_pattern6StopLossBars = Param(nameof(Pattern6StopLossBars), 26)
.SetGreaterThanZero()
.SetDisplay("Pattern 6 SL Bars", "Stop loss lookback", "Pattern 6");
_pattern6TakeProfitBars = Param(nameof(Pattern6TakeProfitBars), 42)
.SetGreaterThanZero()
.SetDisplay("Pattern 6 TP Bars", "Take profit scan length", "Pattern 6");
_pattern6Offset = Param(nameof(Pattern6Offset), 20)
.SetGreaterThanZero()
.SetDisplay("Pattern 6 Offset", "Stop loss offset in points", "Pattern 6");
_pattern6FastEma = Param(nameof(Pattern6FastEma), 4)
.SetGreaterThanZero()
.SetDisplay("Pattern 6 Fast EMA", "Fast EMA period", "Pattern 6");
_pattern6SlowEma = Param(nameof(Pattern6SlowEma), 8)
.SetGreaterThanZero()
.SetDisplay("Pattern 6 Slow EMA", "Slow EMA period", "Pattern 6");
_pattern6MaxThreshold = Param(nameof(Pattern6MaxThreshold), 0.0005m)
.SetDisplay("Pattern 6 Max", "Upper MACD threshold", "Pattern 6");
_pattern6MinThreshold = Param(nameof(Pattern6MinThreshold), -0.001m)
.SetDisplay("Pattern 6 Min", "Lower MACD threshold", "Pattern 6");
_pattern6MaxBars = Param(nameof(Pattern6MaxBars), 5)
.SetGreaterThanZero()
.SetDisplay("Pattern 6 Max Bars", "Maximum counted bars", "Pattern 6");
_pattern6MinBars = Param(nameof(Pattern6MinBars), 5)
.SetGreaterThanZero()
.SetDisplay("Pattern 6 Min Bars", "Minimum counted bars", "Pattern 6");
_pattern6CountBars = Param(nameof(Pattern6CountBars), 4)
.SetGreaterThanZero()
.SetDisplay("Pattern 6 Count Bars", "Trigger counter threshold", "Pattern 6");
_ema1Period = Param(nameof(Ema1Period), 7)
.SetGreaterThanZero()
.SetDisplay("EMA1 Period", "First EMA for management", "Management");
_ema2Period = Param(nameof(Ema2Period), 21)
.SetGreaterThanZero()
.SetDisplay("EMA2 Period", "Second EMA for management", "Management");
_smaPeriod = Param(nameof(SmaPeriod), 98)
.SetGreaterThanZero()
.SetDisplay("SMA Period", "SMA for management", "Management");
_ema3Period = Param(nameof(Ema3Period), 365)
.SetGreaterThanZero()
.SetDisplay("EMA3 Period", "Slow EMA for management", "Management");
_lotSize = Param(nameof(LotSize), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Lot Size", "Base trading volume", "Trading");
_useTimeFilter = Param(nameof(UseTimeFilter), false)
.SetDisplay("Use Time Filter", "Enable trading window", "Trading");
_sessionStart = Param(nameof(SessionStart), new TimeSpan(7, 0, 0))
.SetDisplay("Session Start", "Trading start time", "Trading");
_sessionEnd = Param(nameof(SessionEnd), new TimeSpan(17, 0, 0))
.SetDisplay("Session End", "Trading end time", "Trading");
_useMartingale = Param(nameof(UseMartingale), true)
.SetDisplay("Use Martingale", "Double volume after losses", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Base candle type", "Trading");
}
/// <summary>
/// Minimum volume executed during partial exits.
/// </summary>
public decimal MinPartialVolume
{
get => _minPartialVolume.Value;
set => _minPartialVolume.Value = value;
}
/// <summary>
/// Profit threshold required before partial profit taking.
/// </summary>
public decimal ProfitThreshold
{
get => _profitThreshold.Value;
set => _profitThreshold.Value = value;
}
/// <summary>
/// Maximum number of recent candles stored for pattern analysis.
/// </summary>
public int HistoryLimit
{
get => _historyLimit.Value;
set => _historyLimit.Value = value;
}
/// <summary>
/// Enable or disable the first MACD pattern.
/// </summary>
public bool Pattern1Enabled
{
get => _pattern1Enabled.Value;
set => _pattern1Enabled.Value = value;
}
/// <summary>
/// Stop loss lookback for pattern 1.
/// </summary>
public int Pattern1StopLossBars
{
get => _pattern1StopLossBars.Value;
set => _pattern1StopLossBars.Value = value;
}
/// <summary>
/// Take profit lookback for pattern 1.
/// </summary>
public int Pattern1TakeProfitBars
{
get => _pattern1TakeProfitBars.Value;
set => _pattern1TakeProfitBars.Value = value;
}
/// <summary>
/// Stop loss offset for pattern 1.
/// </summary>
public int Pattern1Offset
{
get => _pattern1Offset.Value;
set => _pattern1Offset.Value = value;
}
/// <summary>
/// Fast EMA length for pattern 1 MACD.
/// </summary>
public int Pattern1FastEma
{
get => _pattern1FastEma.Value;
set => _pattern1FastEma.Value = value;
}
/// <summary>
/// Slow EMA length for pattern 1 MACD.
/// </summary>
public int Pattern1SlowEma
{
get => _pattern1SlowEma.Value;
set => _pattern1SlowEma.Value = value;
}
/// <summary>
/// Upper MACD trigger for pattern 1.
/// </summary>
public decimal Pattern1MaxThreshold
{
get => _pattern1MaxThreshold.Value;
set => _pattern1MaxThreshold.Value = value;
}
/// <summary>
/// Lower MACD trigger for pattern 1.
/// </summary>
public decimal Pattern1MinThreshold
{
get => _pattern1MinThreshold.Value;
set => _pattern1MinThreshold.Value = value;
}
/// <summary>
/// Enable or disable the second MACD pattern.
/// </summary>
public bool Pattern2Enabled
{
get => _pattern2Enabled.Value;
set => _pattern2Enabled.Value = value;
}
/// <summary>
/// Stop loss lookback for pattern 2.
/// </summary>
public int Pattern2StopLossBars
{
get => _pattern2StopLossBars.Value;
set => _pattern2StopLossBars.Value = value;
}
/// <summary>
/// Take profit lookback for pattern 2.
/// </summary>
public int Pattern2TakeProfitBars
{
get => _pattern2TakeProfitBars.Value;
set => _pattern2TakeProfitBars.Value = value;
}
/// <summary>
/// Stop loss offset for pattern 2.
/// </summary>
public int Pattern2Offset
{
get => _pattern2Offset.Value;
set => _pattern2Offset.Value = value;
}
/// <summary>
/// Fast EMA length for pattern 2 MACD.
/// </summary>
public int Pattern2FastEma
{
get => _pattern2FastEma.Value;
set => _pattern2FastEma.Value = value;
}
/// <summary>
/// Slow EMA length for pattern 2 MACD.
/// </summary>
public int Pattern2SlowEma
{
get => _pattern2SlowEma.Value;
set => _pattern2SlowEma.Value = value;
}
/// <summary>
/// Upper MACD trigger for pattern 2.
/// </summary>
public decimal Pattern2MaxThreshold
{
get => _pattern2MaxThreshold.Value;
set => _pattern2MaxThreshold.Value = value;
}
/// <summary>
/// Lower MACD trigger for pattern 2.
/// </summary>
public decimal Pattern2MinThreshold
{
get => _pattern2MinThreshold.Value;
set => _pattern2MinThreshold.Value = value;
}
/// <summary>
/// Enable or disable the third MACD pattern.
/// </summary>
public bool Pattern3Enabled
{
get => _pattern3Enabled.Value;
set => _pattern3Enabled.Value = value;
}
/// <summary>
/// Stop loss lookback for pattern 3.
/// </summary>
public int Pattern3StopLossBars
{
get => _pattern3StopLossBars.Value;
set => _pattern3StopLossBars.Value = value;
}
/// <summary>
/// Take profit lookback for pattern 3.
/// </summary>
public int Pattern3TakeProfitBars
{
get => _pattern3TakeProfitBars.Value;
set => _pattern3TakeProfitBars.Value = value;
}
/// <summary>
/// Stop loss offset for pattern 3.
/// </summary>
public int Pattern3Offset
{
get => _pattern3Offset.Value;
set => _pattern3Offset.Value = value;
}
/// <summary>
/// Fast EMA length for pattern 3 MACD.
/// </summary>
public int Pattern3FastEma
{
get => _pattern3FastEma.Value;
set => _pattern3FastEma.Value = value;
}
/// <summary>
/// Slow EMA length for pattern 3 MACD.
/// </summary>
public int Pattern3SlowEma
{
get => _pattern3SlowEma.Value;
set => _pattern3SlowEma.Value = value;
}
/// <summary>
/// Primary upper MACD threshold for pattern 3.
/// </summary>
public decimal Pattern3MaxThreshold
{
get => _pattern3MaxThreshold.Value;
set => _pattern3MaxThreshold.Value = value;
}
/// <summary>
/// Secondary upper MACD threshold for pattern 3.
/// </summary>
public decimal Pattern3SecondaryMax
{
get => _pattern3SecondaryMax.Value;
set => _pattern3SecondaryMax.Value = value;
}
/// <summary>
/// Primary lower MACD threshold for pattern 3.
/// </summary>
public decimal Pattern3MinThreshold
{
get => _pattern3MinThreshold.Value;
set => _pattern3MinThreshold.Value = value;
}
/// <summary>
/// Secondary lower MACD threshold for pattern 3.
/// </summary>
public decimal Pattern3SecondaryMin
{
get => _pattern3SecondaryMin.Value;
set => _pattern3SecondaryMin.Value = value;
}
/// <summary>
/// Enable or disable the fourth MACD pattern.
/// </summary>
public bool Pattern4Enabled
{
get => _pattern4Enabled.Value;
set => _pattern4Enabled.Value = value;
}
/// <summary>
/// Stop loss lookback for pattern 4.
/// </summary>
public int Pattern4StopLossBars
{
get => _pattern4StopLossBars.Value;
set => _pattern4StopLossBars.Value = value;
}
/// <summary>
/// Take profit lookback for pattern 4.
/// </summary>
public int Pattern4TakeProfitBars
{
get => _pattern4TakeProfitBars.Value;
set => _pattern4TakeProfitBars.Value = value;
}
/// <summary>
/// Stop loss offset for pattern 4.
/// </summary>
public int Pattern4Offset
{
get => _pattern4Offset.Value;
set => _pattern4Offset.Value = value;
}
/// <summary>
/// Fast EMA length for pattern 4 MACD.
/// </summary>
public int Pattern4FastEma
{
get => _pattern4FastEma.Value;
set => _pattern4FastEma.Value = value;
}
/// <summary>
/// Slow EMA length for pattern 4 MACD.
/// </summary>
public int Pattern4SlowEma
{
get => _pattern4SlowEma.Value;
set => _pattern4SlowEma.Value = value;
}
/// <summary>
/// Primary upper MACD threshold for pattern 4.
/// </summary>
public decimal Pattern4MaxThreshold
{
get => _pattern4MaxThreshold.Value;
set => _pattern4MaxThreshold.Value = value;
}
/// <summary>
/// Secondary upper MACD threshold for pattern 4.
/// </summary>
public decimal Pattern4SecondaryMax
{
get => _pattern4SecondaryMax.Value;
set => _pattern4SecondaryMax.Value = value;
}
/// <summary>
/// Primary lower MACD threshold for pattern 4.
/// </summary>
public decimal Pattern4MinThreshold
{
get => _pattern4MinThreshold.Value;
set => _pattern4MinThreshold.Value = value;
}
/// <summary>
/// Secondary lower MACD threshold for pattern 4.
/// </summary>
public decimal Pattern4SecondaryMin
{
get => _pattern4SecondaryMin.Value;
set => _pattern4SecondaryMin.Value = value;
}
/// <summary>
/// Enable or disable the fifth MACD pattern.
/// </summary>
public bool Pattern5Enabled
{
get => _pattern5Enabled.Value;
set => _pattern5Enabled.Value = value;
}
/// <summary>
/// Stop loss lookback for pattern 5.
/// </summary>
public int Pattern5StopLossBars
{
get => _pattern5StopLossBars.Value;
set => _pattern5StopLossBars.Value = value;
}
/// <summary>
/// Take profit lookback for pattern 5.
/// </summary>
public int Pattern5TakeProfitBars
{
get => _pattern5TakeProfitBars.Value;
set => _pattern5TakeProfitBars.Value = value;
}
/// <summary>
/// Stop loss offset for pattern 5.
/// </summary>
public int Pattern5Offset
{
get => _pattern5Offset.Value;
set => _pattern5Offset.Value = value;
}
/// <summary>
/// Fast EMA length for pattern 5 MACD.
/// </summary>
public int Pattern5FastEma
{
get => _pattern5FastEma.Value;
set => _pattern5FastEma.Value = value;
}
/// <summary>
/// Slow EMA length for pattern 5 MACD.
/// </summary>
public int Pattern5SlowEma
{
get => _pattern5SlowEma.Value;
set => _pattern5SlowEma.Value = value;
}
/// <summary>
/// Primary trigger level for the bullish leg in pattern 5.
/// </summary>
public decimal Pattern5PrimaryMax
{
get => _pattern5PrimaryMax.Value;
set => _pattern5PrimaryMax.Value = value;
}
/// <summary>
/// Upper MACD threshold for pattern 5.
/// </summary>
public decimal Pattern5MaxThreshold
{
get => _pattern5MaxThreshold.Value;
set => _pattern5MaxThreshold.Value = value;
}
/// <summary>
/// Secondary upper MACD threshold for pattern 5.
/// </summary>
public decimal Pattern5SecondaryMax
{
get => _pattern5SecondaryMax.Value;
set => _pattern5SecondaryMax.Value = value;
}
/// <summary>
/// Primary trigger level for the bearish leg in pattern 5.
/// </summary>
public decimal Pattern5PrimaryMin
{
get => _pattern5PrimaryMin.Value;
set => _pattern5PrimaryMin.Value = value;
}
/// <summary>
/// Lower MACD threshold for pattern 5.
/// </summary>
public decimal Pattern5MinThreshold
{
get => _pattern5MinThreshold.Value;
set => _pattern5MinThreshold.Value = value;
}
/// <summary>
/// Secondary lower MACD threshold for pattern 5.
/// </summary>
public decimal Pattern5SecondaryMin
{
get => _pattern5SecondaryMin.Value;
set => _pattern5SecondaryMin.Value = value;
}
/// <summary>
/// Enable or disable the sixth MACD pattern.
/// </summary>
public bool Pattern6Enabled
{
get => _pattern6Enabled.Value;
set => _pattern6Enabled.Value = value;
}
/// <summary>
/// Stop loss lookback for pattern 6.
/// </summary>
public int Pattern6StopLossBars
{
get => _pattern6StopLossBars.Value;
set => _pattern6StopLossBars.Value = value;
}
/// <summary>
/// Take profit lookback for pattern 6.
/// </summary>
public int Pattern6TakeProfitBars
{
get => _pattern6TakeProfitBars.Value;
set => _pattern6TakeProfitBars.Value = value;
}
/// <summary>
/// Stop loss offset for pattern 6.
/// </summary>
public int Pattern6Offset
{
get => _pattern6Offset.Value;
set => _pattern6Offset.Value = value;
}
/// <summary>
/// Fast EMA length for pattern 6 MACD.
/// </summary>
public int Pattern6FastEma
{
get => _pattern6FastEma.Value;
set => _pattern6FastEma.Value = value;
}
/// <summary>
/// Slow EMA length for pattern 6 MACD.
/// </summary>
public int Pattern6SlowEma
{
get => _pattern6SlowEma.Value;
set => _pattern6SlowEma.Value = value;
}
/// <summary>
/// Upper MACD threshold for pattern 6.
/// </summary>
public decimal Pattern6MaxThreshold
{
get => _pattern6MaxThreshold.Value;
set => _pattern6MaxThreshold.Value = value;
}
/// <summary>
/// Lower MACD threshold for pattern 6.
/// </summary>
public decimal Pattern6MinThreshold
{
get => _pattern6MinThreshold.Value;
set => _pattern6MinThreshold.Value = value;
}
/// <summary>
/// Maximum counted bars for pattern 6.
/// </summary>
public int Pattern6MaxBars
{
get => _pattern6MaxBars.Value;
set => _pattern6MaxBars.Value = value;
}
/// <summary>
/// Minimum counted bars for pattern 6.
/// </summary>
public int Pattern6MinBars
{
get => _pattern6MinBars.Value;
set => _pattern6MinBars.Value = value;
}
/// <summary>
/// Counter threshold for pattern 6 triggers.
/// </summary>
public int Pattern6CountBars
{
get => _pattern6CountBars.Value;
set => _pattern6CountBars.Value = value;
}
/// <summary>
/// EMA used in partial exit logic.
/// </summary>
public int Ema1Period
{
get => _ema1Period.Value;
set => _ema1Period.Value = value;
}
/// <summary>
/// Second EMA for partial exit logic.
/// </summary>
public int Ema2Period
{
get => _ema2Period.Value;
set => _ema2Period.Value = value;
}
/// <summary>
/// SMA used for partial exits.
/// </summary>
public int SmaPeriod
{
get => _smaPeriod.Value;
set => _smaPeriod.Value = value;
}
/// <summary>
/// Slow EMA used for partial exits.
/// </summary>
public int Ema3Period
{
get => _ema3Period.Value;
set => _ema3Period.Value = value;
}
/// <summary>
/// Base lot size for orders.
/// </summary>
public decimal LotSize
{
get => _lotSize.Value;
set => _lotSize.Value = value;
}
/// <summary>
/// Enable trading window control.
/// </summary>
public bool UseTimeFilter
{
get => _useTimeFilter.Value;
set => _useTimeFilter.Value = value;
}
/// <summary>
/// Session start time.
/// </summary>
public TimeSpan SessionStart
{
get => _sessionStart.Value;
set => _sessionStart.Value = value;
}
/// <summary>
/// Session end time.
/// </summary>
public TimeSpan SessionEnd
{
get => _sessionEnd.Value;
set => _sessionEnd.Value = value;
}
/// <summary>
/// Use martingale sizing when a trade loses.
/// </summary>
public bool UseMartingale
{
get => _useMartingale.Value;
set => _useMartingale.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_history.Clear();
_macd1Prev1 = null;
_macd1Prev2 = null;
_macd1Prev3 = null;
_macd2Prev1 = null;
_macd2Prev2 = null;
_macd2Prev3 = null;
_macd3Prev1 = null;
_macd3Prev2 = null;
_macd3Prev3 = null;
_macd4Prev1 = null;
_macd4Prev2 = null;
_macd4Prev3 = null;
_macd5Prev1 = null;
_macd5Prev2 = null;
_macd5Prev3 = null;
_macd6Prev1 = null;
_macd6Prev2 = null;
_macd6Prev3 = null;
_ema1Prev = null;
_ema2Prev = null;
_smaPrev = null;
_ema3Prev = null;
_pointSize = 0m;
_pointValue = 0m;
_currentVolume = 0m;
_entryPrice = 0m;
_openVolume = 0m;
_realizedPnL = 0m;
_entryDirection = 0;
_currentStopLoss = null;
_currentTakeProfit = null;
_longPartialStage = 0;
_shortPartialStage = 0;
_barsBup = 0;
_pattern6ShortCounter = 0;
_pattern6ShortBlocked = false;
_pattern6LongCounter = 0;
_pattern6LongBlocked = false;
_pattern6ShortReady = false;
_pattern6LongReady = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pointSize = Security?.PriceStep ?? 0.0001m;
_pointValue = 1m;
_currentVolume = LotSize;
Volume = LotSize;
_macd1 = CreateMacd(Pattern1FastEma, Pattern1SlowEma);
_macd2 = CreateMacd(Pattern2FastEma, Pattern2SlowEma);
_macd3 = CreateMacd(Pattern3FastEma, Pattern3SlowEma);
_macd4 = CreateMacd(Pattern4FastEma, Pattern4SlowEma);
_macd5 = CreateMacd(Pattern5FastEma, Pattern5SlowEma);
_macd6 = CreateMacd(Pattern6FastEma, Pattern6SlowEma);
_ema1 = new EMA { Length = Ema1Period };
_ema2 = new EMA { Length = Ema2Period };
_sma1 = new SMA { Length = SmaPeriod };
_ema3 = new EMA { Length = Ema3Period };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandleRaw)
.Start();
}
private static MovingAverageConvergenceDivergenceSignal CreateMacd(int fast, int slow)
{
var innerMacd = new MovingAverageConvergenceDivergence(new EMA { Length = slow }, new EMA { Length = fast });
return new MovingAverageConvergenceDivergenceSignal(innerMacd, new EMA { Length = 1 });
}
private void ProcessCandleRaw(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var candleValue = new CandleIndicatorValue(_macd1, candle) { IsFinal = true };
var macd1Value = _macd1.Process(candleValue);
var macd2Value = _macd2.Process(new CandleIndicatorValue(_macd2, candle) { IsFinal = true });
var macd3Value = _macd3.Process(new CandleIndicatorValue(_macd3, candle) { IsFinal = true });
var macd4Value = _macd4.Process(new CandleIndicatorValue(_macd4, candle) { IsFinal = true });
var macd5Value = _macd5.Process(new CandleIndicatorValue(_macd5, candle) { IsFinal = true });
var macd6Value = _macd6.Process(new CandleIndicatorValue(_macd6, candle) { IsFinal = true });
var ema1Value = _ema1.Process(new CandleIndicatorValue(_ema1, candle) { IsFinal = true });
var ema2Value = _ema2.Process(new CandleIndicatorValue(_ema2, candle) { IsFinal = true });
var sma1Value = _sma1.Process(new CandleIndicatorValue(_sma1, candle) { IsFinal = true });
var ema3Value = _ema3.Process(new CandleIndicatorValue(_ema3, candle) { IsFinal = true });
ProcessCandle(candle, macd1Value, macd2Value, macd3Value, macd4Value, macd5Value, macd6Value, ema1Value, ema2Value, sma1Value, ema3Value);
}
private void ProcessCandle(
ICandleMessage candle,
IIndicatorValue macd1Value,
IIndicatorValue macd2Value,
IIndicatorValue macd3Value,
IIndicatorValue macd4Value,
IIndicatorValue macd5Value,
IIndicatorValue macd6Value,
IIndicatorValue ema1Value,
IIndicatorValue ema2Value,
IIndicatorValue sma1Value,
IIndicatorValue ema3Value)
{
_history.Add(candle);
if (_history.Count > HistoryLimit)
_history.RemoveAt(0);
if (macd1Value is not MovingAverageConvergenceDivergenceSignalValue macd1 ||
macd2Value is not MovingAverageConvergenceDivergenceSignalValue macd2 ||
macd3Value is not MovingAverageConvergenceDivergenceSignalValue macd3 ||
macd4Value is not MovingAverageConvergenceDivergenceSignalValue macd4 ||
macd5Value is not MovingAverageConvergenceDivergenceSignalValue macd5 ||
macd6Value is not MovingAverageConvergenceDivergenceSignalValue macd6)
return;
if (macd1.Macd is not decimal macd1Current ||
macd2.Macd is not decimal macd2Current ||
macd3.Macd is not decimal macd3Current ||
macd4.Macd is not decimal macd4Current ||
macd5.Macd is not decimal macd5Current ||
macd6.Macd is not decimal macd6Current)
{
UpdatePreviousIndicators(ema1Value.ToDecimal(), ema2Value.ToDecimal(), sma1Value.ToDecimal(), ema3Value.ToDecimal());
return;
}
var ema1Current = ema1Value.ToDecimal();
var ema2Current = ema2Value.ToDecimal();
var smaCurrent = sma1Value.ToDecimal();
var ema3Current = ema3Value.ToDecimal();
var macd1Ready = TryGetMacdSeries(ref _macd1Prev1, ref _macd1Prev2, ref _macd1Prev3, macd1Current, out var macd1Curr, out var macd1Last, out var macd1Last3);
var macd2Ready = TryGetMacdSeries(ref _macd2Prev1, ref _macd2Prev2, ref _macd2Prev3, macd2Current, out var macd2Curr, out var macd2Last, out var macd2Last3);
var macd3Ready = TryGetMacdSeries(ref _macd3Prev1, ref _macd3Prev2, ref _macd3Prev3, macd3Current, out var macd3Curr, out var macd3Last, out var macd3Last3);
var macd4Ready = TryGetMacdSeries(ref _macd4Prev1, ref _macd4Prev2, ref _macd4Prev3, macd4Current, out var macd4Curr, out var macd4Last, out var macd4Last3);
var macd5Ready = TryGetMacdSeries(ref _macd5Prev1, ref _macd5Prev2, ref _macd5Prev3, macd5Current, out var macd5Curr, out var macd5Last, out var macd5Last3);
var macd6Ready = TryGetMacdSeries(ref _macd6Prev1, ref _macd6Prev2, ref _macd6Prev3, macd6Current, out var macd6Curr, out var macd6Last, out var macd6Last3);
var ema1Prev = _ema1Prev;
var ema2Prev = _ema2Prev;
var smaPrev = _smaPrev;
var ema3Prev = _ema3Prev;
if (!macd1Ready || !macd2Ready || !macd3Ready || !macd4Ready || !macd5Ready || !macd6Ready ||
!_macd1.IsFormed || !_macd2.IsFormed || !_macd3.IsFormed || !_macd4.IsFormed || !_macd5.IsFormed || !_macd6.IsFormed ||
!_ema1.IsFormed || !_ema2.IsFormed || !_sma1.IsFormed || !_ema3.IsFormed ||
ema1Prev is null || ema2Prev is null || smaPrev is null || ema3Prev is null)
{
UpdatePreviousIndicators(ema1Current, ema2Current, smaCurrent, ema3Current);
return;
}
CheckRiskManagement(candle);
var inSession = !UseTimeFilter || IsInSession(candle.OpenTime.TimeOfDay);
var canTrade = inSession;
if (canTrade)
{
ProcessPattern6(candle, macd6Curr, macd6Last, macd6Last3);
ProcessPattern5(candle, macd5Curr, macd5Last, macd5Last3);
ProcessPattern4(candle, macd4Curr, macd4Last, macd4Last3);
ProcessPattern3(candle, macd3Curr, macd3Last, macd3Last3);
ProcessPattern2(candle, macd2Curr, macd2Last, macd2Last3);
ProcessPattern1(candle, macd1Curr, macd1Last, macd1Last3);
}
if (inSession)
ManageActivePosition(candle, ema1Prev.Value, ema2Prev.Value, smaPrev.Value, ema3Prev.Value);
UpdatePreviousIndicators(ema1Current, ema2Current, smaCurrent, ema3Current);
}
private void ProcessPattern1(ICandleMessage candle, decimal macdCurr, decimal macdLast, decimal macdLast3)
{
if (!Pattern1Enabled)
return;
if (macdCurr > Pattern1MaxThreshold && macdCurr < macdLast && macdLast > macdLast3 && macdCurr > 0m && macdLast3 < Pattern1MaxThreshold && Position >= 0)
{
var stop = CalculateStopPrice(isLong: false, Pattern1StopLossBars, Pattern1Offset);
var take = CalculateTakePrice(isLong: false, Pattern1TakeProfitBars);
if (stop.HasValue && take.HasValue)
{
EnterShort(candle, stop.Value, take.Value);
_shortPartialStage = 0;
}
}
if (macdCurr < Pattern1MinThreshold && macdCurr > macdLast && macdLast < macdLast3 && macdCurr < 0m && macdLast3 > Pattern1MinThreshold && Position <= 0)
{
var stop = CalculateStopPrice(isLong: true, Pattern1StopLossBars, Pattern1Offset);
var take = CalculateTakePrice(isLong: true, Pattern1TakeProfitBars);
if (stop.HasValue && take.HasValue)
{
EnterLong(candle, stop.Value, take.Value);
_longPartialStage = 0;
}
}
}
private void ProcessPattern2(ICandleMessage candle, decimal macdCurr, decimal macdLast, decimal macdLast3)
{
if (!Pattern2Enabled)
return;
if (macdCurr > 0m && macdCurr > macdLast && macdLast < macdLast3 && macdCurr > Pattern2MinThreshold && macdCurr < 0m && Position >= 0)
{
var stop = CalculateStopPrice(isLong: false, Pattern2StopLossBars, Pattern2Offset);
var take = CalculateTakePrice(isLong: false, Pattern2TakeProfitBars);
if (stop.HasValue && take.HasValue)
{
EnterShort(candle, stop.Value, take.Value);
_shortPartialStage = 0;
}
}
if (macdCurr < 0m && macdCurr < macdLast && macdLast > macdLast3 && macdCurr < Pattern2MaxThreshold && macdCurr > 0m && Position <= 0)
{
var stop = CalculateStopPrice(isLong: true, Pattern2StopLossBars, Pattern2Offset);
var take = CalculateTakePrice(isLong: true, Pattern2TakeProfitBars);
if (stop.HasValue && take.HasValue)
{
EnterLong(candle, stop.Value, take.Value);
_longPartialStage = 0;
}
}
}
private void ProcessPattern3(ICandleMessage candle, decimal macdCurr, decimal macdLast, decimal macdLast3)
{
if (!Pattern3Enabled)
return;
var secondaryMax = Pattern3SecondaryMax;
var primaryMax = Pattern3MaxThreshold;
var secondaryMin = Pattern3SecondaryMin;
var primaryMin = Pattern3MinThreshold;
if (macdCurr > secondaryMax)
_barsBup++;
if (macdCurr < primaryMax && macdCurr < macdLast && macdLast > macdLast3 && macdLast > primaryMax && macdLast > secondaryMax && Position >= 0)
{
var stop = CalculateStopPrice(isLong: false, Pattern3StopLossBars, Pattern3Offset);
var take = CalculateTakePrice(isLong: false, Pattern3TakeProfitBars);
if (stop.HasValue && take.HasValue)
{
EnterShort(candle, stop.Value, take.Value);
_shortPartialStage = 0;
_barsBup = 0;
}
}
if (macdCurr > primaryMin && macdCurr > macdLast && macdLast < macdLast3 && macdLast < primaryMin && macdLast < secondaryMin && Position <= 0)
{
var stop = CalculateStopPrice(isLong: true, Pattern3StopLossBars, Pattern3Offset);
var take = CalculateTakePrice(isLong: true, Pattern3TakeProfitBars);
if (stop.HasValue && take.HasValue)
{
EnterLong(candle, stop.Value, take.Value);
_longPartialStage = 0;
}
}
}
private void ProcessPattern4(ICandleMessage candle, decimal macdCurr, decimal macdLast, decimal macdLast3)
{
if (!Pattern4Enabled)
return;
if (macdCurr > Pattern4MaxThreshold && macdCurr < macdLast && macdLast > macdLast3 && macdLast < Pattern4SecondaryMax && Position >= 0)
{
var stop = CalculateStopPrice(isLong: false, Pattern4StopLossBars, Pattern4Offset);
var take = CalculateTakePrice(isLong: false, Pattern4TakeProfitBars);
if (stop.HasValue && take.HasValue)
{
EnterShort(candle, stop.Value, take.Value);
_shortPartialStage = 0;
}
}
if (macdCurr < Pattern4MinThreshold && macdCurr > macdLast && macdLast < macdLast3 && macdLast > Pattern4SecondaryMin && Position <= 0)
{
var stop = CalculateStopPrice(isLong: true, Pattern4StopLossBars, Pattern4Offset);
var take = CalculateTakePrice(isLong: true, Pattern4TakeProfitBars);
if (stop.HasValue && take.HasValue)
{
EnterLong(candle, stop.Value, take.Value);
_longPartialStage = 0;
}
}
}
private void ProcessPattern5(ICandleMessage candle, decimal macdCurr, decimal macdLast, decimal macdLast3)
{
if (!Pattern5Enabled)
return;
if (macdCurr < Pattern5PrimaryMin && macdCurr > Pattern5MinThreshold && macdCurr < macdLast && macdLast > macdLast3 && macdLast > Pattern5MinThreshold && Position >= 0)
{
var stop = CalculateStopPrice(isLong: false, Pattern5StopLossBars, Pattern5Offset);
var take = CalculateTakePrice(isLong: false, Pattern5TakeProfitBars);
if (stop.HasValue && take.HasValue)
{
EnterShort(candle, stop.Value, take.Value);
_shortPartialStage = 0;
}
}
if (macdCurr > Pattern5PrimaryMax && macdCurr < Pattern5MaxThreshold && macdCurr > macdLast && macdLast < macdLast3 && macdLast < Pattern5MaxThreshold && Position <= 0)
{
var stop = CalculateStopPrice(isLong: true, Pattern5StopLossBars, Pattern5Offset);
var take = CalculateTakePrice(isLong: true, Pattern5TakeProfitBars);
if (stop.HasValue && take.HasValue)
{
EnterLong(candle, stop.Value, take.Value);
_longPartialStage = 0;
}
}
}
private void ProcessPattern6(ICandleMessage candle, decimal macdCurr, decimal macdLast, decimal macdLast3)
{
if (!Pattern6Enabled)
return;
if (macdCurr < Pattern6MaxThreshold)
_pattern6ShortBlocked = false;
if (macdCurr > Pattern6MaxThreshold && _pattern6ShortCounter <= Pattern6MaxBars && !_pattern6ShortBlocked)
_pattern6ShortCounter++;
if (_pattern6ShortCounter > Pattern6MaxBars)
{
_pattern6ShortCounter = 0;
_pattern6ShortBlocked = true;
}
if (_pattern6ShortCounter < Pattern6MinBars && macdCurr < Pattern6MaxThreshold)
_pattern6ShortCounter = 0;
if (macdCurr < Pattern6MaxThreshold && _pattern6ShortCounter > Pattern6CountBars)
_pattern6ShortReady = true;
if (_pattern6ShortReady && Position >= 0)
{
var stop = CalculateStopPrice(isLong: false, Pattern6StopLossBars, Pattern6Offset);
var take = CalculateTakePrice(isLong: false, Pattern6TakeProfitBars);
if (stop.HasValue && take.HasValue)
{
EnterShort(candle, stop.Value, take.Value);
_pattern6ShortReady = false;
_pattern6ShortCounter = 0;
_pattern6ShortBlocked = false;
_shortPartialStage = 0;
}
}
if (macdCurr > Pattern6MinThreshold)
_pattern6LongBlocked = false;
if (macdCurr < Pattern6MinThreshold && _pattern6LongCounter <= Pattern6MaxBars && !_pattern6LongBlocked)
_pattern6LongCounter++;
if (_pattern6LongCounter > Pattern6MaxBars)
{
_pattern6LongCounter = 0;
_pattern6LongBlocked = true;
}
if (_pattern6LongCounter < Pattern6MinBars && macdCurr > Pattern6MinThreshold)
_pattern6LongCounter = 0;
if (macdCurr > Pattern6MinThreshold && _pattern6LongCounter > Pattern6CountBars)
_pattern6LongReady = true;
if (_pattern6LongReady && Position <= 0)
{
var stop = CalculateStopPrice(isLong: true, Pattern6StopLossBars, Pattern6Offset);
var take = CalculateTakePrice(isLong: true, Pattern6TakeProfitBars);
if (stop.HasValue && take.HasValue)
{
EnterLong(candle, stop.Value, take.Value);
_pattern6LongReady = false;
_pattern6LongCounter = 0;
_pattern6LongBlocked = false;
_longPartialStage = 0;
}
}
}
private void EnterLong(ICandleMessage candle, decimal stopPrice, decimal takePrice)
{
var closeShortVolume = Position < 0 ? Math.Abs(Position) : 0m;
if (closeShortVolume > 0m && _entryDirection < 0)
RegisterClose(closeShortVolume, candle.ClosePrice);
var newVolume = _currentVolume;
var totalVolume = newVolume + closeShortVolume;
if (totalVolume <= 0m)
return;
BuyMarket(totalVolume);
_entryDirection = 1;
_entryPrice = candle.ClosePrice;
_openVolume = newVolume;
_realizedPnL = 0m;
_currentStopLoss = stopPrice;
_currentTakeProfit = takePrice;
_longPartialStage = 0;
_shortPartialStage = 0;
}
private void EnterShort(ICandleMessage candle, decimal stopPrice, decimal takePrice)
{
var closeLongVolume = Position > 0 ? Position : 0m;
if (closeLongVolume > 0m && _entryDirection > 0)
RegisterClose(closeLongVolume, candle.ClosePrice);
var newVolume = _currentVolume;
var totalVolume = newVolume + closeLongVolume;
if (totalVolume <= 0m)
return;
SellMarket(totalVolume);
_entryDirection = -1;
_entryPrice = candle.ClosePrice;
_openVolume = newVolume;
_realizedPnL = 0m;
_currentStopLoss = stopPrice;
_currentTakeProfit = takePrice;
_longPartialStage = 0;
_shortPartialStage = 0;
_barsBup = 0;
}
private void ManageActivePosition(ICandleMessage candle, decimal ema1Prev, decimal ema2Prev, decimal smaPrev, decimal ema3Prev)
{
if (_entryDirection == 0 || _openVolume <= 0m)
return;
var previousCandle = GetCandle(1);
if (previousCandle is null)
return;
var profit = CalculateOpenProfit(candle.ClosePrice);
if (_entryDirection > 0)
{
if (profit > ProfitThreshold && previousCandle.ClosePrice > ema2Prev && _longPartialStage == 0)
{
var volume = NormalizeVolume(_openVolume / 3m);
if (volume > 0m)
{
SellMarket(volume);
RegisterClose(volume, candle.ClosePrice);
_longPartialStage = 1;
}
}
else if (profit > ProfitThreshold && previousCandle.HighPrice > (smaPrev + ema3Prev) / 2m && _longPartialStage == 1)
{
var volume = NormalizeVolume(_openVolume / 2m);
if (volume > 0m)
{
SellMarket(volume);
RegisterClose(volume, candle.ClosePrice);
_longPartialStage = 2;
}
}
}
else if (_entryDirection < 0)
{
if (profit > ProfitThreshold && previousCandle.ClosePrice < ema2Prev && _shortPartialStage == 0)
{
var volume = NormalizeVolume(_openVolume / 3m);
if (volume > 0m)
{
BuyMarket(volume);
RegisterClose(volume, candle.ClosePrice);
_shortPartialStage = 1;
}
}
else if (profit > ProfitThreshold && previousCandle.LowPrice < (smaPrev + ema3Prev) / 2m && _shortPartialStage == 1)
{
var volume = NormalizeVolume(_openVolume / 2m);
if (volume > 0m)
{
BuyMarket(volume);
RegisterClose(volume, candle.ClosePrice);
_shortPartialStage = 2;
}
}
}
}
private bool CheckRiskManagement(ICandleMessage candle)
{
if (_entryDirection == 0 || _openVolume <= 0m)
return false;
if (_entryDirection > 0)
{
var stopLoss = _currentStopLoss;
var takeProfit = _currentTakeProfit;
if (stopLoss.HasValue && candle.LowPrice <= stopLoss.Value)
{
SellMarket(_openVolume);
RegisterClose(_openVolume, stopLoss.Value);
return true;
}
if (takeProfit.HasValue && candle.HighPrice >= takeProfit.Value)
{
SellMarket(_openVolume);
RegisterClose(_openVolume, takeProfit.Value);
return true;
}
}
else if (_entryDirection < 0)
{
var stopLoss = _currentStopLoss;
var takeProfit = _currentTakeProfit;
if (stopLoss.HasValue && candle.HighPrice >= stopLoss.Value)
{
BuyMarket(_openVolume);
RegisterClose(_openVolume, stopLoss.Value);
return true;
}
if (takeProfit.HasValue && candle.LowPrice <= takeProfit.Value)
{
BuyMarket(_openVolume);
RegisterClose(_openVolume, takeProfit.Value);
return true;
}
}
return false;
}
private void RegisterClose(decimal volume, decimal executionPrice)
{
if (_entryDirection == 0 || volume <= 0m)
return;
var actualVolume = Math.Min(volume, _openVolume);
if (actualVolume <= 0m)
return;
var profit = CalculateProfit(executionPrice, actualVolume);
_realizedPnL += profit;
_openVolume -= actualVolume;
if (_openVolume <= 0m)
CompleteTrade();
}
private decimal CalculateOpenProfit(decimal price)
{
if (_entryDirection == 0 || _openVolume <= 0m)
return 0m;
var diff = (price - _entryPrice) * _entryDirection;
if (_pointSize == 0m)
return diff * _openVolume;
return diff / _pointSize * _pointValue * _openVolume;
}
private decimal CalculateProfit(decimal exitPrice, decimal volume)
{
var diff = (exitPrice - _entryPrice) * _entryDirection;
if (_pointSize == 0m)
return diff * volume;
return diff / _pointSize * _pointValue * volume;
}
private void CompleteTrade()
{
if (UseMartingale && _realizedPnL < 0m)
_currentVolume *= 2m;
else
_currentVolume = LotSize;
_entryDirection = 0;
_entryPrice = 0m;
_openVolume = 0m;
_realizedPnL = 0m;
_currentStopLoss = null;
_currentTakeProfit = null;
_longPartialStage = 0;
_shortPartialStage = 0;
}
private void UpdatePreviousIndicators(decimal ema1, decimal ema2, decimal sma, decimal ema3)
{
_ema1Prev = ema1;
_ema2Prev = ema2;
_smaPrev = sma;
_ema3Prev = ema3;
}
private static bool TryGetMacdSeries(ref decimal? prev1, ref decimal? prev2, ref decimal? prev3, decimal current, out decimal macdCurr, out decimal macdLast, out decimal macdLast3)
{
macdCurr = 0m;
macdLast = 0m;
macdLast3 = 0m;
if (!prev1.HasValue || !prev2.HasValue || !prev3.HasValue)
{
prev3 = prev2;
prev2 = prev1;
prev1 = current;
return false;
}
macdCurr = prev1.Value;
macdLast = prev2.Value;
macdLast3 = prev3.Value;
prev3 = prev2;
prev2 = prev1;
prev1 = current;
return true;
}
private decimal NormalizeVolume(decimal volume)
{
var normalized = Math.Round(volume, 2, MidpointRounding.AwayFromZero);
return normalized < MinPartialVolume ? MinPartialVolume : normalized;
}
private decimal? CalculateStopPrice(bool isLong, int stopBars, int offsetPoints)
{
if (stopBars <= 0)
return null;
var offset = offsetPoints * _pointSize;
if (isLong)
{
var lowest = GetLowestLow(stopBars);
return lowest.HasValue ? lowest.Value - offset : null;
}
else
{
var highest = GetHighestHigh(stopBars);
return highest.HasValue ? highest.Value + offset : null;
}
}
private decimal? CalculateTakePrice(bool isLong, int takeBars)
{
if (takeBars <= 0)
return null;
return GetChunkExtreme(isLong, takeBars, 0);
}
private decimal? GetChunkExtreme(bool isLong, int length, int offset)
{
var startIndex = _history.Count - 1 - offset;
var endIndex = startIndex - (length - 1);
if (startIndex < 0 || endIndex < 0)
return null;
decimal extreme = isLong ? decimal.MinValue : decimal.MaxValue;
for (var i = startIndex; i >= endIndex; i--)
{
var candle = _history[i];
if (candle is null)
continue;
var value = isLong ? candle.HighPrice : candle.LowPrice;
if (isLong)
{
if (value > extreme)
extreme = value;
}
else
{
if (value < extreme)
extreme = value;
}
}
if (extreme == decimal.MinValue || extreme == decimal.MaxValue)
return null;
var nextOffset = offset + length;
var nextExtreme = GetChunkExtreme(isLong, length, nextOffset);
if (nextExtreme.HasValue)
{
if (isLong)
{
if (nextExtreme.Value > extreme)
return nextExtreme;
}
else
{
if (nextExtreme.Value < extreme)
return nextExtreme;
}
}
return extreme;
}
private decimal? GetHighestHigh(int bars)
{
if (bars <= 0 || _history.Count == 0)
return null;
decimal? result = null;
var end = Math.Max(0, _history.Count - bars);
for (var i = _history.Count - 1; i >= end; i--)
{
var value = _history[i].HighPrice;
if (result is null || value > result.Value)
result = value;
}
return result;
}
private decimal? GetLowestLow(int bars)
{
if (bars <= 0 || _history.Count == 0)
return null;
decimal? result = null;
var end = Math.Max(0, _history.Count - bars);
for (var i = _history.Count - 1; i >= end; i--)
{
var value = _history[i].LowPrice;
if (result is null || value < result.Value)
result = value;
}
return result;
}
private ICandleMessage GetCandle(int shift)
{
var index = _history.Count - 1 - shift;
return index >= 0 ? _history[index] : null;
}
private bool IsInSession(TimeSpan time)
{
var start = SessionStart;
var end = SessionEnd;
return start <= end ? time >= start && time <= end : time >= start || time <= end;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import (MovingAverageConvergenceDivergence, MovingAverageConvergenceDivergenceSignal,
ExponentialMovingAverage, SimpleMovingAverage, CandleIndicatorValue)
from StockSharp.Algo.Strategies import Strategy
class macd_pattern_trader_session_strategy(Strategy):
def __init__(self):
super(macd_pattern_trader_session_strategy, self).__init__()
self._min_partial_volume = self.Param("MinPartialVolume", 0.01)
self._profit_threshold = self.Param("ProfitThreshold", 5.0)
self._history_limit = self.Param("HistoryLimit", 1024)
self._p1_enabled = self.Param("Pattern1Enabled", True)
self._p1_sl_bars = self.Param("Pattern1StopLossBars", 22)
self._p1_tp_bars = self.Param("Pattern1TakeProfitBars", 32)
self._p1_offset = self.Param("Pattern1Offset", 40)
self._p1_fast = self.Param("Pattern1FastEma", 24)
self._p1_slow = self.Param("Pattern1SlowEma", 13)
self._p1_max = self.Param("Pattern1MaxThreshold", 0.0095)
self._p1_min = self.Param("Pattern1MinThreshold", -0.0045)
self._p2_enabled = self.Param("Pattern2Enabled", True)
self._p2_sl_bars = self.Param("Pattern2StopLossBars", 2)
self._p2_tp_bars = self.Param("Pattern2TakeProfitBars", 2)
self._p2_offset = self.Param("Pattern2Offset", 50)
self._p2_fast = self.Param("Pattern2FastEma", 17)
self._p2_slow = self.Param("Pattern2SlowEma", 7)
self._p2_max = self.Param("Pattern2MaxThreshold", 0.0045)
self._p2_min = self.Param("Pattern2MinThreshold", -0.0035)
self._p3_enabled = self.Param("Pattern3Enabled", True)
self._p3_sl_bars = self.Param("Pattern3StopLossBars", 8)
self._p3_tp_bars = self.Param("Pattern3TakeProfitBars", 12)
self._p3_offset = self.Param("Pattern3Offset", 2)
self._p3_fast = self.Param("Pattern3FastEma", 32)
self._p3_slow = self.Param("Pattern3SlowEma", 2)
self._p3_max = self.Param("Pattern3MaxThreshold", 0.0015)
self._p3_sec_max = self.Param("Pattern3SecondaryMax", 0.004)
self._p3_min = self.Param("Pattern3MinThreshold", -0.005)
self._p3_sec_min = self.Param("Pattern3SecondaryMin", -0.0005)
self._p4_enabled = self.Param("Pattern4Enabled", True)
self._p4_sl_bars = self.Param("Pattern4StopLossBars", 10)
self._p4_tp_bars = self.Param("Pattern4TakeProfitBars", 32)
self._p4_offset = self.Param("Pattern4Offset", 45)
self._p4_fast = self.Param("Pattern4FastEma", 4)
self._p4_slow = self.Param("Pattern4SlowEma", 9)
self._p4_max = self.Param("Pattern4MaxThreshold", 0.0165)
self._p4_sec_max = self.Param("Pattern4SecondaryMax", 0.0001)
self._p4_min = self.Param("Pattern4MinThreshold", -0.0005)
self._p4_sec_min = self.Param("Pattern4SecondaryMin", -0.0006)
self._p5_enabled = self.Param("Pattern5Enabled", True)
self._p5_sl_bars = self.Param("Pattern5StopLossBars", 8)
self._p5_tp_bars = self.Param("Pattern5TakeProfitBars", 47)
self._p5_offset = self.Param("Pattern5Offset", 45)
self._p5_fast = self.Param("Pattern5FastEma", 6)
self._p5_slow = self.Param("Pattern5SlowEma", 2)
self._p5_pri_max = self.Param("Pattern5PrimaryMax", 0.0005)
self._p5_max = self.Param("Pattern5MaxThreshold", 0.0015)
self._p5_pri_min = self.Param("Pattern5PrimaryMin", -0.0005)
self._p5_min = self.Param("Pattern5MinThreshold", -0.003)
self._p6_enabled = self.Param("Pattern6Enabled", True)
self._p6_sl_bars = self.Param("Pattern6StopLossBars", 26)
self._p6_tp_bars = self.Param("Pattern6TakeProfitBars", 42)
self._p6_offset = self.Param("Pattern6Offset", 20)
self._p6_fast = self.Param("Pattern6FastEma", 4)
self._p6_slow = self.Param("Pattern6SlowEma", 8)
self._p6_max = self.Param("Pattern6MaxThreshold", 0.0005)
self._p6_min = self.Param("Pattern6MinThreshold", -0.001)
self._p6_max_bars = self.Param("Pattern6MaxBars", 5)
self._p6_min_bars = self.Param("Pattern6MinBars", 5)
self._p6_count_bars = self.Param("Pattern6CountBars", 4)
self._ema1_period = self.Param("Ema1Period", 7)
self._ema2_period = self.Param("Ema2Period", 21)
self._sma_period = self.Param("SmaPeriod", 98)
self._ema3_period = self.Param("Ema3Period", 365)
self._lot_size = self.Param("LotSize", 0.1)
self._use_time_filter = self.Param("UseTimeFilter", False)
self._session_start = self.Param("SessionStart", 7)
self._session_end = self.Param("SessionEnd", 17)
self._use_martingale = self.Param("UseMartingale", True)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._history = []
self._point_size = 0.0001
self._current_volume = 0.1
self._entry_price = 0.0
self._open_volume = 0.0
self._realized_pnl = 0.0
self._entry_direction = 0
self._current_stop = None
self._current_take = None
self._long_partial_stage = 0
self._short_partial_stage = 0
self._bars_bup = 0
self._p6_short_counter = 0
self._p6_short_blocked = False
self._p6_long_counter = 0
self._p6_long_blocked = False
self._p6_short_ready = False
self._p6_long_ready = False
self._macd_prevs = {}
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def LotSize(self):
return self._lot_size.Value
@LotSize.setter
def LotSize(self, value):
self._lot_size.Value = value
@property
def UseMartingale(self):
return self._use_martingale.Value
@UseMartingale.setter
def UseMartingale(self, value):
self._use_martingale.Value = value
def OnStarted2(self, time):
super(macd_pattern_trader_session_strategy, self).OnStarted2(time)
self._point_size = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0001
if self._point_size <= 0.0:
self._point_size = 0.0001
self._current_volume = float(self.LotSize)
self._history = []
self._entry_price = 0.0
self._open_volume = 0.0
self._realized_pnl = 0.0
self._entry_direction = 0
self._current_stop = None
self._current_take = None
self._long_partial_stage = 0
self._short_partial_stage = 0
self._bars_bup = 0
self._p6_short_counter = 0
self._p6_short_blocked = False
self._p6_long_counter = 0
self._p6_long_blocked = False
self._p6_short_ready = False
self._p6_long_ready = False
self._macds = []
for i in range(6):
fast = int(getattr(self, '_p%d_fast' % (i+1)).Value)
slow = int(getattr(self, '_p%d_slow' % (i+1)).Value)
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = fast
macd.Macd.LongMa.Length = slow
macd.SignalMa.Length = 1
self._macds.append(macd)
self._ema1 = ExponentialMovingAverage()
self._ema1.Length = int(self._ema1_period.Value)
self._ema2 = ExponentialMovingAverage()
self._ema2.Length = int(self._ema2_period.Value)
self._sma1 = SimpleMovingAverage()
self._sma1.Length = int(self._sma_period.Value)
self._ema3 = ExponentialMovingAverage()
self._ema3.Length = int(self._ema3_period.Value)
self._macd_prevs = {}
for i in range(6):
self._macd_prevs[i] = [None, None, None]
self._ema1_prev = None
self._ema2_prev = None
self._sma_prev = None
self._ema3_prev = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandleRaw).Start()
self.StartProtection(
Unit(2000.0, UnitTypes.Absolute),
Unit(1000.0, UnitTypes.Absolute))
def ProcessCandleRaw(self, candle):
if candle.State != CandleStates.Finished:
return
macd_values = []
for m in self._macds:
cv = CandleIndicatorValue(m, candle)
cv.IsFinal = True
val = m.Process(cv)
macd_values.append(val)
cv1 = CandleIndicatorValue(self._ema1, candle)
cv1.IsFinal = True
ema1_val = self._ema1.Process(cv1)
cv2 = CandleIndicatorValue(self._ema2, candle)
cv2.IsFinal = True
ema2_val = self._ema2.Process(cv2)
cv3 = CandleIndicatorValue(self._sma1, candle)
cv3.IsFinal = True
sma1_val = self._sma1.Process(cv3)
cv4 = CandleIndicatorValue(self._ema3, candle)
cv4.IsFinal = True
ema3_val = self._ema3.Process(cv4)
self._process_candle(candle, macd_values, ema1_val, ema2_val, sma1_val, ema3_val)
def _process_candle(self, candle, macd_values, ema1_val, ema2_val, sma1_val, ema3_val):
self._history.append(candle)
limit = int(self._history_limit.Value)
if len(self._history) > limit:
self._history.pop(0)
macd_currents = []
for i, val in enumerate(macd_values):
macd_line = val.Macd if hasattr(val, 'Macd') else None
if macd_line is not None:
macd_currents.append(float(macd_line))
else:
macd_currents.append(None)
ema1_cur = float(ema1_val) if ema1_val is not None else 0.0
ema2_cur = float(ema2_val) if ema2_val is not None else 0.0
sma_cur = float(sma1_val) if sma1_val is not None else 0.0
ema3_cur = float(ema3_val) if ema3_val is not None else 0.0
all_ready = True
series = []
for i in range(6):
mc = macd_currents[i]
if mc is None:
all_ready = False
break
prevs = self._macd_prevs[i]
if prevs[0] is None or prevs[1] is None or prevs[2] is None:
prevs[2] = prevs[1]
prevs[1] = prevs[0]
prevs[0] = mc
all_ready = False
continue
curr = prevs[0]
last = prevs[1]
last3 = prevs[2]
prevs[2] = prevs[1]
prevs[1] = prevs[0]
prevs[0] = mc
series.append((curr, last, last3))
if not all_ready or len(series) < 6:
self._update_prev_indicators(ema1_cur, ema2_cur, sma_cur, ema3_cur)
return
for m in self._macds:
if not m.IsFormed:
self._update_prev_indicators(ema1_cur, ema2_cur, sma_cur, ema3_cur)
return
if not self._ema1.IsFormed or not self._ema2.IsFormed or not self._sma1.IsFormed or not self._ema3.IsFormed:
self._update_prev_indicators(ema1_cur, ema2_cur, sma_cur, ema3_cur)
return
if self._ema1_prev is None or self._ema2_prev is None or self._sma_prev is None or self._ema3_prev is None:
self._update_prev_indicators(ema1_cur, ema2_cur, sma_cur, ema3_cur)
return
self._check_risk(candle)
use_tf = bool(self._use_time_filter.Value)
in_session = not use_tf or self._is_in_session(candle)
if in_session:
self._process_pattern6(candle, series[5][0], series[5][1], series[5][2])
self._process_pattern5(candle, series[4][0], series[4][1], series[4][2])
self._process_pattern4(candle, series[3][0], series[3][1], series[3][2])
self._process_pattern3(candle, series[2][0], series[2][1], series[2][2])
self._process_pattern2(candle, series[1][0], series[1][1], series[1][2])
self._process_pattern1(candle, series[0][0], series[0][1], series[0][2])
if in_session:
self._manage_position(candle, self._ema1_prev, self._ema2_prev, self._sma_prev, self._ema3_prev)
self._update_prev_indicators(ema1_cur, ema2_cur, sma_cur, ema3_cur)
def _process_pattern1(self, candle, curr, last, last3):
if not bool(self._p1_enabled.Value):
return
p1_max = float(self._p1_max.Value)
p1_min = float(self._p1_min.Value)
if curr > p1_max and curr < last and last > last3 and curr > 0.0 and last3 < p1_max and self.Position >= 0:
stop = self._calc_stop(False, int(self._p1_sl_bars.Value), int(self._p1_offset.Value))
take = self._calc_take(False, int(self._p1_tp_bars.Value))
if stop is not None and take is not None:
self._enter_short(candle, stop, take)
self._short_partial_stage = 0
if curr < p1_min and curr > last and last < last3 and curr < 0.0 and last3 > p1_min and self.Position <= 0:
stop = self._calc_stop(True, int(self._p1_sl_bars.Value), int(self._p1_offset.Value))
take = self._calc_take(True, int(self._p1_tp_bars.Value))
if stop is not None and take is not None:
self._enter_long(candle, stop, take)
self._long_partial_stage = 0
def _process_pattern2(self, candle, curr, last, last3):
if not bool(self._p2_enabled.Value):
return
p2_max = float(self._p2_max.Value)
p2_min = float(self._p2_min.Value)
if curr > 0.0 and curr > last and last < last3 and curr > p2_min and curr < 0.0 and self.Position >= 0:
stop = self._calc_stop(False, int(self._p2_sl_bars.Value), int(self._p2_offset.Value))
take = self._calc_take(False, int(self._p2_tp_bars.Value))
if stop is not None and take is not None:
self._enter_short(candle, stop, take)
self._short_partial_stage = 0
if curr < 0.0 and curr < last and last > last3 and curr < p2_max and curr > 0.0 and self.Position <= 0:
stop = self._calc_stop(True, int(self._p2_sl_bars.Value), int(self._p2_offset.Value))
take = self._calc_take(True, int(self._p2_tp_bars.Value))
if stop is not None and take is not None:
self._enter_long(candle, stop, take)
self._long_partial_stage = 0
def _process_pattern3(self, candle, curr, last, last3):
if not bool(self._p3_enabled.Value):
return
sec_max = float(self._p3_sec_max.Value)
pri_max = float(self._p3_max.Value)
sec_min = float(self._p3_sec_min.Value)
pri_min = float(self._p3_min.Value)
if curr > sec_max:
self._bars_bup += 1
if curr < pri_max and curr < last and last > last3 and last > pri_max and last > sec_max and self.Position >= 0:
stop = self._calc_stop(False, int(self._p3_sl_bars.Value), int(self._p3_offset.Value))
take = self._calc_take(False, int(self._p3_tp_bars.Value))
if stop is not None and take is not None:
self._enter_short(candle, stop, take)
self._short_partial_stage = 0
self._bars_bup = 0
if curr > pri_min and curr > last and last < last3 and last < pri_min and last < sec_min and self.Position <= 0:
stop = self._calc_stop(True, int(self._p3_sl_bars.Value), int(self._p3_offset.Value))
take = self._calc_take(True, int(self._p3_tp_bars.Value))
if stop is not None and take is not None:
self._enter_long(candle, stop, take)
self._long_partial_stage = 0
def _process_pattern4(self, candle, curr, last, last3):
if not bool(self._p4_enabled.Value):
return
p4_max = float(self._p4_max.Value)
p4_sec_max = float(self._p4_sec_max.Value)
p4_min = float(self._p4_min.Value)
p4_sec_min = float(self._p4_sec_min.Value)
if curr > p4_max and curr < last and last > last3 and last < p4_sec_max and self.Position >= 0:
stop = self._calc_stop(False, int(self._p4_sl_bars.Value), int(self._p4_offset.Value))
take = self._calc_take(False, int(self._p4_tp_bars.Value))
if stop is not None and take is not None:
self._enter_short(candle, stop, take)
self._short_partial_stage = 0
if curr < p4_min and curr > last and last < last3 and last > p4_sec_min and self.Position <= 0:
stop = self._calc_stop(True, int(self._p4_sl_bars.Value), int(self._p4_offset.Value))
take = self._calc_take(True, int(self._p4_tp_bars.Value))
if stop is not None and take is not None:
self._enter_long(candle, stop, take)
self._long_partial_stage = 0
def _process_pattern5(self, candle, curr, last, last3):
if not bool(self._p5_enabled.Value):
return
pri_min = float(self._p5_pri_min.Value)
min_th = float(self._p5_min.Value)
pri_max = float(self._p5_pri_max.Value)
max_th = float(self._p5_max.Value)
if curr < pri_min and curr > min_th and curr < last and last > last3 and last > min_th and self.Position >= 0:
stop = self._calc_stop(False, int(self._p5_sl_bars.Value), int(self._p5_offset.Value))
take = self._calc_take(False, int(self._p5_tp_bars.Value))
if stop is not None and take is not None:
self._enter_short(candle, stop, take)
self._short_partial_stage = 0
if curr > pri_max and curr < max_th and curr > last and last < last3 and last < max_th and self.Position <= 0:
stop = self._calc_stop(True, int(self._p5_sl_bars.Value), int(self._p5_offset.Value))
take = self._calc_take(True, int(self._p5_tp_bars.Value))
if stop is not None and take is not None:
self._enter_long(candle, stop, take)
self._long_partial_stage = 0
def _process_pattern6(self, candle, curr, last, last3):
if not bool(self._p6_enabled.Value):
return
p6_max = float(self._p6_max.Value)
p6_min = float(self._p6_min.Value)
max_bars = int(self._p6_max_bars.Value)
min_bars = int(self._p6_min_bars.Value)
count_bars = int(self._p6_count_bars.Value)
if curr < p6_max:
self._p6_short_blocked = False
if curr > p6_max and self._p6_short_counter <= max_bars and not self._p6_short_blocked:
self._p6_short_counter += 1
if self._p6_short_counter > max_bars:
self._p6_short_counter = 0
self._p6_short_blocked = True
if self._p6_short_counter < min_bars and curr < p6_max:
self._p6_short_counter = 0
if curr < p6_max and self._p6_short_counter > count_bars:
self._p6_short_ready = True
if self._p6_short_ready and self.Position >= 0:
stop = self._calc_stop(False, int(self._p6_sl_bars.Value), int(self._p6_offset.Value))
take = self._calc_take(False, int(self._p6_tp_bars.Value))
if stop is not None and take is not None:
self._enter_short(candle, stop, take)
self._p6_short_ready = False
self._p6_short_counter = 0
self._p6_short_blocked = False
self._short_partial_stage = 0
if curr > p6_min:
self._p6_long_blocked = False
if curr < p6_min and self._p6_long_counter <= max_bars and not self._p6_long_blocked:
self._p6_long_counter += 1
if self._p6_long_counter > max_bars:
self._p6_long_counter = 0
self._p6_long_blocked = True
if self._p6_long_counter < min_bars and curr > p6_min:
self._p6_long_counter = 0
if curr > p6_min and self._p6_long_counter > count_bars:
self._p6_long_ready = True
if self._p6_long_ready and self.Position <= 0:
stop = self._calc_stop(True, int(self._p6_sl_bars.Value), int(self._p6_offset.Value))
take = self._calc_take(True, int(self._p6_tp_bars.Value))
if stop is not None and take is not None:
self._enter_long(candle, stop, take)
self._p6_long_ready = False
self._p6_long_counter = 0
self._p6_long_blocked = False
self._long_partial_stage = 0
def _enter_long(self, candle, stop_price, take_price):
self.BuyMarket()
self._entry_direction = 1
self._entry_price = float(candle.ClosePrice)
self._open_volume = self._current_volume
self._realized_pnl = 0.0
self._current_stop = stop_price
self._current_take = take_price
self._long_partial_stage = 0
self._short_partial_stage = 0
def _enter_short(self, candle, stop_price, take_price):
self.SellMarket()
self._entry_direction = -1
self._entry_price = float(candle.ClosePrice)
self._open_volume = self._current_volume
self._realized_pnl = 0.0
self._current_stop = stop_price
self._current_take = take_price
self._long_partial_stage = 0
self._short_partial_stage = 0
self._bars_bup = 0
def _manage_position(self, candle, ema1_prev, ema2_prev, sma_prev, ema3_prev):
if self._entry_direction == 0 or self._open_volume <= 0.0:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
profit_th = float(self._profit_threshold.Value)
if self._entry_direction > 0:
profit = (close - self._entry_price) * self._open_volume
if profit > profit_th and close > ema2_prev and self._long_partial_stage == 0:
self.SellMarket()
self._long_partial_stage = 1
elif profit > profit_th and high > (sma_prev + ema3_prev) / 2.0 and self._long_partial_stage == 1:
self.SellMarket()
self._long_partial_stage = 2
elif self._entry_direction < 0:
profit = (self._entry_price - close) * self._open_volume
if profit > profit_th and close < ema2_prev and self._short_partial_stage == 0:
self.BuyMarket()
self._short_partial_stage = 1
elif profit > profit_th and low < (sma_prev + ema3_prev) / 2.0 and self._short_partial_stage == 1:
self.BuyMarket()
self._short_partial_stage = 2
def _check_risk(self, candle):
if self._entry_direction == 0 or self._open_volume <= 0.0:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self._entry_direction > 0:
if self._current_stop is not None and low <= self._current_stop:
self.SellMarket()
self._complete_trade(self._current_stop)
return
if self._current_take is not None and high >= self._current_take:
self.SellMarket()
self._complete_trade(self._current_take)
return
elif self._entry_direction < 0:
if self._current_stop is not None and high >= self._current_stop:
self.BuyMarket()
self._complete_trade(self._current_stop)
return
if self._current_take is not None and low <= self._current_take:
self.BuyMarket()
self._complete_trade(self._current_take)
return
def _complete_trade(self, exit_price):
if self._entry_direction != 0 and self._open_volume > 0.0:
diff = (exit_price - self._entry_price) * self._entry_direction
self._realized_pnl = diff * self._open_volume
if self.UseMartingale and self._realized_pnl < 0.0:
self._current_volume *= 2.0
else:
self._current_volume = float(self.LotSize)
self._entry_direction = 0
self._entry_price = 0.0
self._open_volume = 0.0
self._realized_pnl = 0.0
self._current_stop = None
self._current_take = None
self._long_partial_stage = 0
self._short_partial_stage = 0
def _calc_stop(self, is_long, stop_bars, offset_points):
if stop_bars <= 0:
return None
offset = offset_points * self._point_size
if is_long:
lowest = self._get_lowest_low(stop_bars)
return lowest - offset if lowest is not None else None
else:
highest = self._get_highest_high(stop_bars)
return highest + offset if highest is not None else None
def _calc_take(self, is_long, take_bars):
if take_bars <= 0:
return None
return self._get_chunk_extreme(is_long, take_bars, 0)
def _get_chunk_extreme(self, is_long, length, offset):
start_idx = len(self._history) - 1 - offset
end_idx = start_idx - (length - 1)
if start_idx < 0 or end_idx < 0:
return None
if is_long:
extreme = -1e18
else:
extreme = 1e18
for i in range(start_idx, end_idx - 1, -1):
c = self._history[i]
if is_long:
v = float(c.HighPrice)
if v > extreme:
extreme = v
else:
v = float(c.LowPrice)
if v < extreme:
extreme = v
if extreme == -1e18 or extreme == 1e18:
return None
next_offset = offset + length
next_extreme = self._get_chunk_extreme(is_long, length, next_offset)
if next_extreme is not None:
if is_long and next_extreme > extreme:
return next_extreme
elif not is_long and next_extreme < extreme:
return next_extreme
return extreme
def _get_highest_high(self, bars):
if bars <= 0 or len(self._history) == 0:
return None
result = None
end = max(0, len(self._history) - bars)
for i in range(len(self._history) - 1, end - 1, -1):
v = float(self._history[i].HighPrice)
if result is None or v > result:
result = v
return result
def _get_lowest_low(self, bars):
if bars <= 0 or len(self._history) == 0:
return None
result = None
end = max(0, len(self._history) - bars)
for i in range(len(self._history) - 1, end - 1, -1):
v = float(self._history[i].LowPrice)
if result is None or v < result:
result = v
return result
def _update_prev_indicators(self, ema1, ema2, sma, ema3):
self._ema1_prev = ema1
self._ema2_prev = ema2
self._sma_prev = sma
self._ema3_prev = ema3
def _is_in_session(self, candle):
hour = candle.OpenTime.TimeOfDay.Hours
start = int(self._session_start.Value)
end = int(self._session_end.Value)
if start <= end:
return hour >= start and hour <= end
return hour >= start or hour <= end
def OnReseted(self):
super(macd_pattern_trader_session_strategy, self).OnReseted()
self._history = []
self._point_size = 0.0001
self._current_volume = 0.1
self._entry_price = 0.0
self._open_volume = 0.0
self._realized_pnl = 0.0
self._entry_direction = 0
self._current_stop = None
self._current_take = None
self._long_partial_stage = 0
self._short_partial_stage = 0
self._bars_bup = 0
self._p6_short_counter = 0
self._p6_short_blocked = False
self._p6_long_counter = 0
self._p6_long_blocked = False
self._p6_short_ready = False
self._p6_long_ready = False
self._macd_prevs = {}
self._ema1_prev = None
self._ema2_prev = None
self._sma_prev = None
self._ema3_prev = None
def CreateClone(self):
return macd_pattern_trader_session_strategy()