VR-ZVER v2 Strategy
The VR-ZVER v2 strategy is a StockSharp port of the classic MetaTrader expert advisor. It keeps the triple confirmation idea of the original script: every trade must be supported by moving averages, the stochastic oscillator, and RSI. Only when all enabled filters agree does the strategy place a market order.
Trading Logic
- Signals are evaluated when a candle closes. Intrabar fluctuations are only used to validate stops or targets.
- Three exponential moving averages (fast, slow, very slow) must be stacked in the same order to validate the trend when the MA filter is enabled.
- The stochastic filter waits for a %K/%D crossover near configurable upper and lower bands.
- The RSI filter requires the oscillator to leave a neutral zone (below the lower band for longs, above the upper band for shorts).
- A signal is accepted only when every enabled filter votes in the same direction. If any filter disagrees, nothing is traded.
- The strategy opens one position at a time. It does not hedge or build grids; when flat it waits for the next aligned signal.
Position Management
- A take-profit and stop-loss are expressed in pips. The initial stop is set to two-thirds of the configured distance, reproducing the original EA behaviour.
- A breakeven trigger (also in pips) moves the stop to the entry price once the trade has gained the specified distance.
- Trailing stops use a distance and an additional step. The step prevents the stop from being updated on every small uptick and matches the MT5 trailing logic.
- Long and short trades share the same management rules and react symmetrically to highs/lows of the candle.
Position Sizing
FixedVolumegreater than zero opens every order with a fixed size.- When
FixedVolumeis set to zero, the strategy computes the volume fromRiskPercent, the current portfolio value, and the stop distance. Price step and step price are used to convert the pip distance into monetary risk. - Volumes are rounded to respect
VolumeMin,VolumeMax, andVolumeStepconstraints of the instrument. Orders are skipped if the calculated size is too small.
Parameters
| Name | Description |
|---|---|
CandleType |
Time frame used for signal generation (default 15-minute candles). |
FixedVolume, RiskPercent |
Choose between fixed or risk-based sizing. |
StopLossPips, TakeProfitPips |
Base protective distances in pips. |
TrailingStopPips, TrailingStepPips, BreakevenPips |
Trade management thresholds. |
AllowLongs, AllowShorts |
Enable or disable individual directions. |
UseMovingAverageFilter, FastMaPeriod, SlowMaPeriod, VerySlowMaPeriod |
Triple EMA trend filter. |
UseStochastic, StochasticKPeriod, StochasticDPeriod, StochasticSmooth, StochasticUpperLevel, StochasticLowerLevel |
Stochastic confirmation settings. |
UseRsi, RsiPeriod, RsiUpperLevel, RsiLowerLevel |
RSI confirmation band. |
Notes
- Pip conversion emulates the original EA: five- and three-digit symbols multiply the price step by ten before calculating pip values.
- The StockSharp port only uses market orders. The locking and pending order features of the MetaTrader version are intentionally omitted to keep the implementation consistent with the high-level API.
- Attach the strategy to a chart if you want to see the EMA, stochastic, and RSI overlays; they are drawn automatically when a chart area is available.
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the VR-ZVER v2 expert advisor with triple EMA confirmation and stochastic/RSI filters.
/// </summary>
public class VrZverV2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _fixedVolume;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _trailingStepPips;
private readonly StrategyParam<decimal> _breakevenPips;
private readonly StrategyParam<bool> _allowLongs;
private readonly StrategyParam<bool> _allowShorts;
private readonly StrategyParam<bool> _useMovingAverageFilter;
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<int> _verySlowMaPeriod;
private readonly StrategyParam<bool> _useStochastic;
private readonly StrategyParam<int> _stochasticKPeriod;
private readonly StrategyParam<int> _stochasticDPeriod;
private readonly StrategyParam<int> _stochasticSmooth;
private readonly StrategyParam<decimal> _stochasticUpperLevel;
private readonly StrategyParam<decimal> _stochasticLowerLevel;
private readonly StrategyParam<bool> _useRsi;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiUpperLevel;
private readonly StrategyParam<decimal> _rsiLowerLevel;
private ExponentialMovingAverage _fastMa;
private ExponentialMovingAverage _slowMa;
private ExponentialMovingAverage _verySlowMa;
private StochasticOscillator _stochastic;
private RelativeStrengthIndex _rsi;
private decimal _pipSize;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal? _trailingStop;
private bool _breakevenActivated;
public VrZverV2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Time frame for signal generation", "General");
_fixedVolume = Param(nameof(FixedVolume), 1m)
.SetDisplay("Fixed Volume", "Use fixed volume when greater than zero", "Risk");
_riskPercent = Param(nameof(RiskPercent), 10m)
.SetDisplay("Risk %", "Risk percentage used when fixed volume is zero", "Risk");
_stopLossPips = Param(nameof(StopLossPips), 10000m)
.SetDisplay("Stop Loss (pips)", "Full stop distance expressed in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 15000m)
.SetDisplay("Take Profit (pips)", "Profit target distance in pips", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 8000m)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 3000m)
.SetDisplay("Trailing Step (pips)", "Additional distance before trailing updates", "Risk");
_breakevenPips = Param(nameof(BreakevenPips), 5000m)
.SetDisplay("Breakeven (pips)", "Move stop to entry after this profit", "Risk");
_allowLongs = Param(nameof(AllowLongs), true)
.SetDisplay("Allow Longs", "Permit buy trades", "General");
_allowShorts = Param(nameof(AllowShorts), true)
.SetDisplay("Allow Shorts", "Permit sell trades", "General");
_useMovingAverageFilter = Param(nameof(UseMovingAverageFilter), true)
.SetDisplay("Use MA Filter", "Require triple EMA alignment", "Indicators");
_fastMaPeriod = Param(nameof(FastMaPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Length of the fast EMA", "Indicators");
_slowMaPeriod = Param(nameof(SlowMaPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Length of the slow EMA", "Indicators");
_verySlowMaPeriod = Param(nameof(VerySlowMaPeriod), 7)
.SetGreaterThanZero()
.SetDisplay("Very Slow EMA", "Length of the very slow EMA", "Indicators");
_useStochastic = Param(nameof(UseStochastic), false)
.SetDisplay("Use Stochastic", "Enable stochastic confirmation", "Indicators");
_stochasticKPeriod = Param(nameof(StochasticKPeriod), 42)
.SetGreaterThanZero()
.SetDisplay("Stochastic %K", "Number of periods for %K", "Indicators");
_stochasticDPeriod = Param(nameof(StochasticDPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Stochastic %D", "Smoothing period for %D", "Indicators");
_stochasticSmooth = Param(nameof(StochasticSmooth), 7)
.SetGreaterThanZero()
.SetDisplay("Stochastic Smooth", "Final smoothing for stochastic", "Indicators");
_stochasticUpperLevel = Param(nameof(StochasticUpperLevel), 60m)
.SetDisplay("Stochastic Upper", "Upper threshold for short signals", "Indicators");
_stochasticLowerLevel = Param(nameof(StochasticLowerLevel), 40m)
.SetDisplay("Stochastic Lower", "Lower threshold for long signals", "Indicators");
_useRsi = Param(nameof(UseRsi), false)
.SetDisplay("Use RSI", "Enable RSI filter", "Indicators");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Length of the RSI", "Indicators");
_rsiUpperLevel = Param(nameof(RsiUpperLevel), 60m)
.SetDisplay("RSI Upper", "Upper threshold for short entries", "Indicators");
_rsiLowerLevel = Param(nameof(RsiLowerLevel), 40m)
.SetDisplay("RSI Lower", "Lower threshold for long entries", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal FixedVolume
{
get => _fixedVolume.Value;
set => _fixedVolume.Value = value;
}
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
public decimal TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
public decimal BreakevenPips
{
get => _breakevenPips.Value;
set => _breakevenPips.Value = value;
}
public bool AllowLongs
{
get => _allowLongs.Value;
set => _allowLongs.Value = value;
}
public bool AllowShorts
{
get => _allowShorts.Value;
set => _allowShorts.Value = value;
}
public bool UseMovingAverageFilter
{
get => _useMovingAverageFilter.Value;
set => _useMovingAverageFilter.Value = value;
}
public int FastMaPeriod
{
get => _fastMaPeriod.Value;
set => _fastMaPeriod.Value = value;
}
public int SlowMaPeriod
{
get => _slowMaPeriod.Value;
set => _slowMaPeriod.Value = value;
}
public int VerySlowMaPeriod
{
get => _verySlowMaPeriod.Value;
set => _verySlowMaPeriod.Value = value;
}
public bool UseStochastic
{
get => _useStochastic.Value;
set => _useStochastic.Value = value;
}
public int StochasticKPeriod
{
get => _stochasticKPeriod.Value;
set => _stochasticKPeriod.Value = value;
}
public int StochasticDPeriod
{
get => _stochasticDPeriod.Value;
set => _stochasticDPeriod.Value = value;
}
public int StochasticSmooth
{
get => _stochasticSmooth.Value;
set => _stochasticSmooth.Value = value;
}
public decimal StochasticUpperLevel
{
get => _stochasticUpperLevel.Value;
set => _stochasticUpperLevel.Value = value;
}
public decimal StochasticLowerLevel
{
get => _stochasticLowerLevel.Value;
set => _stochasticLowerLevel.Value = value;
}
public bool UseRsi
{
get => _useRsi.Value;
set => _useRsi.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public decimal RsiUpperLevel
{
get => _rsiUpperLevel.Value;
set => _rsiUpperLevel.Value = value;
}
public decimal RsiLowerLevel
{
get => _rsiLowerLevel.Value;
set => _rsiLowerLevel.Value = value;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_pipSize = 0m;
_fastMa = null;
_slowMa = null;
_verySlowMa = null;
_stochastic = null;
_rsi = null;
ResetTradeState();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Prepare pip size once the security is available.
_pipSize = CalculatePipSize();
// Clear any leftover state from previous runs.
ResetTradeState();
// Instantiate indicators with the configured lengths.
_fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };
_verySlowMa = new ExponentialMovingAverage { Length = VerySlowMaPeriod };
if (UseStochastic)
{
_stochastic = new StochasticOscillator();
_stochastic.K.Length = StochasticKPeriod;
_stochastic.D.Length = StochasticDPeriod;
}
if (UseRsi)
{
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
}
// Subscribe to candle updates and bind the three EMAs.
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastMa, _slowMa, _verySlowMa, ProcessCandle)
.Start();
// Draw indicators and trades when a chart area is available.
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastMa);
DrawIndicator(area, _slowMa);
DrawIndicator(area, _verySlowMa);
if (_stochastic != null)
DrawIndicator(area, _stochastic);
if (_rsi != null)
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastMaValue, decimal slowMaValue, decimal verySlowMaValue)
{
if (candle.State != CandleStates.Finished)
return;
// Process stochastic and RSI manually only when enabled.
IIndicatorValue stochasticValue = null;
if (UseStochastic && _stochastic != null)
stochasticValue = _stochastic.Process(candle);
decimal rsiValue = 50m;
if (UseRsi && _rsi != null)
{
var rsiResult = _rsi.Process(new DecimalIndicatorValue(_rsi, candle.ClosePrice, candle.CloseTime) { IsFinal = true });
rsiValue = rsiResult.IsFormed ? rsiResult.ToDecimal() : 50m;
}
if (UseMovingAverageFilter && (!_fastMa.IsFormed || !_slowMa.IsFormed || !_verySlowMa.IsFormed))
return;
if (UseStochastic && !_stochastic.IsFormed)
return;
if (UseRsi && !_rsi.IsFormed)
return;
// Manage the active position before evaluating new signals.
UpdateRiskManagement(candle);
// Aggregate votes from all enabled filters.
var filters = 0;
var upVotes = 0;
var downVotes = 0;
if (UseMovingAverageFilter)
{
filters++;
if (fastMaValue > slowMaValue && slowMaValue > verySlowMaValue)
upVotes++;
else if (fastMaValue < slowMaValue && slowMaValue < verySlowMaValue)
downVotes++;
}
if (UseStochastic && stochasticValue != null)
{
if (stochasticValue is not IStochasticOscillatorValue stoch)
return;
if (stoch.K is not decimal stochK || stoch.D is not decimal stochD)
return;
filters++;
if (stochD < stochK && StochasticLowerLevel > stochK)
upVotes++;
if (stochD > stochK && StochasticUpperLevel < stochK)
downVotes++;
}
if (UseRsi)
{
filters++;
if (rsiValue < RsiLowerLevel)
upVotes++;
if (rsiValue > RsiUpperLevel)
downVotes++;
}
if (filters == 0)
return;
var longSignal = AllowLongs && upVotes == filters;
var shortSignal = AllowShorts && downVotes == filters;
// Only open a new trade when there is no active position.
if (Position == 0)
{
if (longSignal)
TryEnterLong(candle);
else if (shortSignal)
TryEnterShort(candle);
}
}
private void TryEnterLong(ICandleMessage candle)
{
var volume = CalculateEntryVolume();
if (volume <= 0m)
return;
// Enter a long position at market price.
BuyMarket(volume);
// Store trade prices for later risk management.
_entryPrice = candle.ClosePrice;
_breakevenActivated = false;
_trailingStop = null;
var stopOffset = StopLossPips > 0m ? StopLossPips * _pipSize : 0m;
var takeOffset = TakeProfitPips > 0m ? TakeProfitPips * _pipSize : 0m;
_stopPrice = stopOffset > 0m ? _entryPrice - stopOffset : null;
_takePrice = takeOffset > 0m ? _entryPrice + takeOffset : null;
}
private void TryEnterShort(ICandleMessage candle)
{
var volume = CalculateEntryVolume();
if (volume <= 0m)
return;
// Enter a short position at market price.
SellMarket(volume);
// Store trade prices for later risk management.
_entryPrice = candle.ClosePrice;
_breakevenActivated = false;
_trailingStop = null;
var stopOffset = StopLossPips > 0m ? StopLossPips * _pipSize : 0m;
var takeOffset = TakeProfitPips > 0m ? TakeProfitPips * _pipSize : 0m;
_stopPrice = stopOffset > 0m ? _entryPrice + stopOffset : null;
_takePrice = takeOffset > 0m ? _entryPrice - takeOffset : null;
}
private void UpdateRiskManagement(ICandleMessage candle)
{
// Manage long positions first.
if (Position > 0)
{
HandleBreakevenLong(candle);
HandleTrailingLong(candle);
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket(Math.Abs(Position));
ResetTradeState();
}
else if (_takePrice is decimal take && candle.HighPrice >= take)
{
SellMarket(Math.Abs(Position));
ResetTradeState();
}
}
// Manage short positions in the same fashion.
else if (Position < 0)
{
HandleBreakevenShort(candle);
HandleTrailingShort(candle);
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(Math.Abs(Position));
ResetTradeState();
}
else if (_takePrice is decimal take && candle.LowPrice <= take)
{
BuyMarket(Math.Abs(Position));
ResetTradeState();
}
}
// Reset helper state when flat.
else
{
ResetTradeState();
}
}
// Move the stop to breakeven for long trades once profit reaches the threshold.
private void HandleBreakevenLong(ICandleMessage candle)
{
if (_breakevenActivated || BreakevenPips <= 0m)
return;
var trigger = _entryPrice + BreakevenPips * _pipSize;
if (candle.HighPrice >= trigger)
{
_breakevenActivated = true;
UpdateLongStop(_entryPrice);
}
}
// Move the stop to breakeven for short trades once profit reaches the threshold.
private void HandleBreakevenShort(ICandleMessage candle)
{
if (_breakevenActivated || BreakevenPips <= 0m)
return;
var trigger = _entryPrice - BreakevenPips * _pipSize;
if (candle.LowPrice <= trigger)
{
_breakevenActivated = true;
UpdateShortStop(_entryPrice);
}
}
// Update trailing logic for long trades using distance and step thresholds.
private void HandleTrailingLong(ICandleMessage candle)
{
if (TrailingStopPips <= 0m)
return;
var distance = TrailingStopPips * _pipSize;
if (distance <= 0m)
return;
var step = TrailingStepPips * _pipSize;
var desiredStop = candle.ClosePrice - distance;
if (_trailingStop is null)
{
var activationPrice = _entryPrice + distance + step;
if (candle.HighPrice >= activationPrice)
{
_trailingStop = desiredStop;
UpdateLongStop(desiredStop);
}
}
else if (desiredStop > _trailingStop.Value + step)
{
_trailingStop = desiredStop;
UpdateLongStop(desiredStop);
}
}
// Update trailing logic for short trades using distance and step thresholds.
private void HandleTrailingShort(ICandleMessage candle)
{
if (TrailingStopPips <= 0m)
return;
var distance = TrailingStopPips * _pipSize;
if (distance <= 0m)
return;
var step = TrailingStepPips * _pipSize;
var desiredStop = candle.ClosePrice + distance;
if (_trailingStop is null)
{
var activationPrice = _entryPrice - distance - step;
if (candle.LowPrice <= activationPrice)
{
_trailingStop = desiredStop;
UpdateShortStop(desiredStop);
}
}
else if (desiredStop < _trailingStop.Value - step)
{
_trailingStop = desiredStop;
UpdateShortStop(desiredStop);
}
}
// Ensure the long stop can only move upward.
private void UpdateLongStop(decimal newLevel)
{
if (_stopPrice is null || newLevel > _stopPrice.Value)
_stopPrice = newLevel;
}
// Ensure the short stop can only move downward.
private void UpdateShortStop(decimal newLevel)
{
if (_stopPrice is null || newLevel < _stopPrice.Value)
_stopPrice = newLevel;
}
// Determine trade size using either fixed volume or risk-based sizing.
private decimal CalculateEntryVolume()
{
if (FixedVolume > 0m)
return AdjustVolume(FixedVolume);
var stopOffset = StopLossPips > 0m ? StopLossPips * _pipSize : 0m;
if (stopOffset <= 0m)
return AdjustVolume(Volume);
var riskVolume = GetRiskVolume(stopOffset);
return AdjustVolume(riskVolume);
}
// Translate the configured risk percentage into lots based on stop distance.
private decimal GetRiskVolume(decimal stopOffset)
{
if (stopOffset <= 0m)
return 0m;
var priceStep = Security?.PriceStep ?? 0m;
var stepPrice = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? 0m;
if (priceStep <= 0m || stepPrice <= 0m)
return 0m;
var lossPerUnit = stopOffset / priceStep * stepPrice;
if (lossPerUnit <= 0m)
return 0m;
var equity = Portfolio?.CurrentValue ?? 0m;
if (equity <= 0m)
return 0m;
var riskAmount = equity * RiskPercent / 100m;
if (riskAmount <= 0m)
return 0m;
return riskAmount / lossPerUnit;
}
// Normalize the requested volume to instrument constraints.
private decimal AdjustVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
var security = Security;
if (security == null)
return volume;
var step = security.VolumeStep ?? 0m;
if (step > 0m)
{
var steps = Math.Floor(volume / step);
var adjusted = steps * step;
if (adjusted <= 0m)
adjusted = step;
return adjusted;
}
return volume > 0m ? volume : 0m;
}
// Mimic the MetaTrader pip conversion used in the original script.
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return 1m;
// For crypto and large-price instruments, scale pip size
// so that pip-based parameters produce meaningful price offsets.
return step;
}
// Clear cached state values when no position is active.
private void ResetTradeState()
{
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
_trailingStop = null;
_breakevenActivated = false;
}
}
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, Decimal
from StockSharp.Messages import DataType, CandleStates, Level1Fields
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class vr_zver_v2_strategy(Strategy):
"""Port of the VR-ZVER v2 expert advisor with triple EMA confirmation and stochastic/RSI filters."""
def __init__(self):
super(vr_zver_v2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Time frame for signal generation", "General")
self._fixed_volume = self.Param("FixedVolume", Decimal(1)) \
.SetDisplay("Fixed Volume", "Use fixed volume when greater than zero", "Risk")
self._risk_percent = self.Param("RiskPercent", Decimal(10)) \
.SetDisplay("Risk %", "Risk percentage used when fixed volume is zero", "Risk")
self._stop_loss_pips = self.Param("StopLossPips", Decimal(10000)) \
.SetDisplay("Stop Loss (pips)", "Full stop distance expressed in pips", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", Decimal(15000)) \
.SetDisplay("Take Profit (pips)", "Profit target distance in pips", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", Decimal(8000)) \
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk")
self._trailing_step_pips = self.Param("TrailingStepPips", Decimal(3000)) \
.SetDisplay("Trailing Step (pips)", "Additional distance before trailing updates", "Risk")
self._breakeven_pips = self.Param("BreakevenPips", Decimal(5000)) \
.SetDisplay("Breakeven (pips)", "Move stop to entry after this profit", "Risk")
self._allow_longs = self.Param("AllowLongs", True) \
.SetDisplay("Allow Longs", "Permit buy trades", "General")
self._allow_shorts = self.Param("AllowShorts", True) \
.SetDisplay("Allow Shorts", "Permit sell trades", "General")
self._use_ma_filter = self.Param("UseMovingAverageFilter", True) \
.SetDisplay("Use MA Filter", "Require triple EMA alignment", "Indicators")
self._fast_period = self.Param("FastMaPeriod", 3).SetGreaterThanZero() \
.SetDisplay("Fast EMA", "Length of the fast EMA", "Indicators")
self._slow_period = self.Param("SlowMaPeriod", 5).SetGreaterThanZero() \
.SetDisplay("Slow EMA", "Length of the slow EMA", "Indicators")
self._very_slow_period = self.Param("VerySlowMaPeriod", 7).SetGreaterThanZero() \
.SetDisplay("Very Slow EMA", "Length of the very slow EMA", "Indicators")
self._use_stochastic = self.Param("UseStochastic", False) \
.SetDisplay("Use Stochastic", "Enable stochastic confirmation", "Indicators")
self._stoch_k_period = self.Param("StochasticKPeriod", 42).SetGreaterThanZero() \
.SetDisplay("Stochastic %K", "Number of periods for %K", "Indicators")
self._stoch_d_period = self.Param("StochasticDPeriod", 5).SetGreaterThanZero() \
.SetDisplay("Stochastic %D", "Smoothing period for %D", "Indicators")
self._stoch_smooth = self.Param("StochasticSmooth", 7).SetGreaterThanZero() \
.SetDisplay("Stochastic Smooth", "Final smoothing for stochastic", "Indicators")
self._stoch_upper = self.Param("StochasticUpperLevel", Decimal(60)) \
.SetDisplay("Stochastic Upper", "Upper threshold for short signals", "Indicators")
self._stoch_lower = self.Param("StochasticLowerLevel", Decimal(40)) \
.SetDisplay("Stochastic Lower", "Lower threshold for long signals", "Indicators")
self._use_rsi = self.Param("UseRsi", False) \
.SetDisplay("Use RSI", "Enable RSI filter", "Indicators")
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero() \
.SetDisplay("RSI Period", "Length of the RSI", "Indicators")
self._rsi_upper = self.Param("RsiUpperLevel", Decimal(60)) \
.SetDisplay("RSI Upper", "Upper threshold for short entries", "Indicators")
self._rsi_lower = self.Param("RsiLowerLevel", Decimal(40)) \
.SetDisplay("RSI Lower", "Lower threshold for long entries", "Indicators")
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(vr_zver_v2_strategy, self).OnReseted()
self._pip_size = Decimal(0)
self._fast_ma = None
self._slow_ma = None
self._very_slow_ma = None
self._stochastic = None
self._rsi = None
self._reset_trade()
def OnStarted2(self, time):
super(vr_zver_v2_strategy, self).OnStarted2(time)
self._pip_size = self._calculate_pip_size()
self._reset_trade()
self._fast_ma = ExponentialMovingAverage()
self._fast_ma.Length = self._fast_period.Value
self._slow_ma = ExponentialMovingAverage()
self._slow_ma.Length = self._slow_period.Value
self._very_slow_ma = ExponentialMovingAverage()
self._very_slow_ma.Length = self._very_slow_period.Value
self._stochastic = None
if self._use_stochastic.Value:
self._stochastic = StochasticOscillator()
self._stochastic.K.Length = self._stoch_k_period.Value
self._stochastic.D.Length = self._stoch_d_period.Value
self._rsi = None
if self._use_rsi.Value:
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self._rsi_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._fast_ma, self._slow_ma, self._very_slow_ma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, self._fast_ma)
self.DrawIndicator(area, self._slow_ma)
self.DrawIndicator(area, self._very_slow_ma)
if self._stochastic is not None:
self.DrawIndicator(area, self._stochastic)
if self._rsi is not None:
self.DrawIndicator(area, self._rsi)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val, very_slow_val):
if candle.State != CandleStates.Finished:
return
# Process stochastic and RSI manually only when enabled.
stoch_value = None
if self._use_stochastic.Value and self._stochastic is not None:
stoch_value = self._stochastic.Process(candle)
rsi_val = Decimal(50)
if self._use_rsi.Value and self._rsi is not None:
rsi_result = process_float(self._rsi, candle.ClosePrice, candle.CloseTime, True)
if rsi_result.IsFormed:
rsi_val = rsi_result.ToDecimal()
else:
rsi_val = Decimal(50)
if self._use_ma_filter.Value and (not self._fast_ma.IsFormed or not self._slow_ma.IsFormed or not self._very_slow_ma.IsFormed):
return
if self._use_stochastic.Value and not self._stochastic.IsFormed:
return
if self._use_rsi.Value and not self._rsi.IsFormed:
return
# Manage the active position before evaluating new signals.
self._update_risk_management(candle)
# Aggregate votes from all enabled filters.
filters = 0
up_votes = 0
down_votes = 0
if self._use_ma_filter.Value:
filters += 1
if fast_val > slow_val and slow_val > very_slow_val:
up_votes += 1
elif fast_val < slow_val and slow_val < very_slow_val:
down_votes += 1
if self._use_stochastic.Value and stoch_value is not None:
try:
stoch_k = stoch_value.K
stoch_d = stoch_value.D
if stoch_k is None or stoch_d is None:
return
filters += 1
if stoch_d < stoch_k and self._stoch_lower.Value > stoch_k:
up_votes += 1
if stoch_d > stoch_k and self._stoch_upper.Value < stoch_k:
down_votes += 1
except:
return
if self._use_rsi.Value:
filters += 1
if rsi_val < self._rsi_lower.Value:
up_votes += 1
if rsi_val > self._rsi_upper.Value:
down_votes += 1
if filters == 0:
return
long_signal = self._allow_longs.Value and up_votes == filters
short_signal = self._allow_shorts.Value and down_votes == filters
# Only open a new trade when there is no active position.
if self.Position == 0:
if long_signal:
self._try_enter_long(candle)
elif short_signal:
self._try_enter_short(candle)
def _try_enter_long(self, candle):
volume = self._calculate_entry_volume()
if volume <= 0:
return
self.BuyMarket(volume)
self._entry_price = candle.ClosePrice
self._be_activated = False
self._trail_stop = None
stop_offset = Decimal.Multiply(self._stop_loss_pips.Value, self._pip_size) if self._stop_loss_pips.Value > 0 else Decimal(0)
take_offset = Decimal.Multiply(self._take_profit_pips.Value, self._pip_size) if self._take_profit_pips.Value > 0 else Decimal(0)
self._stop_price = Decimal.Subtract(self._entry_price, stop_offset) if stop_offset > 0 else None
self._take_price = Decimal.Add(self._entry_price, take_offset) if take_offset > 0 else None
def _try_enter_short(self, candle):
volume = self._calculate_entry_volume()
if volume <= 0:
return
self.SellMarket(volume)
self._entry_price = candle.ClosePrice
self._be_activated = False
self._trail_stop = None
stop_offset = Decimal.Multiply(self._stop_loss_pips.Value, self._pip_size) if self._stop_loss_pips.Value > 0 else Decimal(0)
take_offset = Decimal.Multiply(self._take_profit_pips.Value, self._pip_size) if self._take_profit_pips.Value > 0 else Decimal(0)
self._stop_price = Decimal.Add(self._entry_price, stop_offset) if stop_offset > 0 else None
self._take_price = Decimal.Subtract(self._entry_price, take_offset) if take_offset > 0 else None
def _update_risk_management(self, candle):
if self.Position > 0:
self._handle_breakeven_long(candle)
self._handle_trailing_long(candle)
if self._stop_price is not None and candle.LowPrice <= self._stop_price:
self.SellMarket(Math.Abs(self.Position))
self._reset_trade()
elif self._take_price is not None and candle.HighPrice >= self._take_price:
self.SellMarket(Math.Abs(self.Position))
self._reset_trade()
elif self.Position < 0:
self._handle_breakeven_short(candle)
self._handle_trailing_short(candle)
if self._stop_price is not None and candle.HighPrice >= self._stop_price:
self.BuyMarket(Math.Abs(self.Position))
self._reset_trade()
elif self._take_price is not None and candle.LowPrice <= self._take_price:
self.BuyMarket(Math.Abs(self.Position))
self._reset_trade()
else:
self._reset_trade()
def _handle_breakeven_long(self, candle):
if self._be_activated or self._breakeven_pips.Value <= 0:
return
trigger = Decimal.Add(self._entry_price, Decimal.Multiply(self._breakeven_pips.Value, self._pip_size))
if candle.HighPrice >= trigger:
self._be_activated = True
self._update_long_stop(self._entry_price)
def _handle_breakeven_short(self, candle):
if self._be_activated or self._breakeven_pips.Value <= 0:
return
trigger = Decimal.Subtract(self._entry_price, Decimal.Multiply(self._breakeven_pips.Value, self._pip_size))
if candle.LowPrice <= trigger:
self._be_activated = True
self._update_short_stop(self._entry_price)
def _handle_trailing_long(self, candle):
if self._trailing_stop_pips.Value <= 0:
return
distance = Decimal.Multiply(self._trailing_stop_pips.Value, self._pip_size)
if distance <= 0:
return
step = Decimal.Multiply(self._trailing_step_pips.Value, self._pip_size)
desired_stop = Decimal.Subtract(candle.ClosePrice, distance)
if self._trail_stop is None:
activation_price = Decimal.Add(Decimal.Add(self._entry_price, distance), step)
if candle.HighPrice >= activation_price:
self._trail_stop = desired_stop
self._update_long_stop(desired_stop)
elif desired_stop > Decimal.Add(self._trail_stop, step):
self._trail_stop = desired_stop
self._update_long_stop(desired_stop)
def _handle_trailing_short(self, candle):
if self._trailing_stop_pips.Value <= 0:
return
distance = Decimal.Multiply(self._trailing_stop_pips.Value, self._pip_size)
if distance <= 0:
return
step = Decimal.Multiply(self._trailing_step_pips.Value, self._pip_size)
desired_stop = Decimal.Add(candle.ClosePrice, distance)
if self._trail_stop is None:
activation_price = Decimal.Subtract(Decimal.Subtract(self._entry_price, distance), step)
if candle.LowPrice <= activation_price:
self._trail_stop = desired_stop
self._update_short_stop(desired_stop)
elif desired_stop < Decimal.Subtract(self._trail_stop, step):
self._trail_stop = desired_stop
self._update_short_stop(desired_stop)
def _update_long_stop(self, new_level):
if self._stop_price is None or new_level > self._stop_price:
self._stop_price = new_level
def _update_short_stop(self, new_level):
if self._stop_price is None or new_level < self._stop_price:
self._stop_price = new_level
def _calculate_entry_volume(self):
if self._fixed_volume.Value > 0:
return self._adjust_volume(self._fixed_volume.Value)
stop_offset = Decimal.Multiply(self._stop_loss_pips.Value, self._pip_size) if self._stop_loss_pips.Value > 0 else Decimal(0)
if stop_offset <= 0:
return self._adjust_volume(self.Volume)
risk_volume = self._get_risk_volume(stop_offset)
return self._adjust_volume(risk_volume)
def _get_risk_volume(self, stop_offset):
if stop_offset <= 0:
return Decimal(0)
sec = self.Security
price_step = sec.PriceStep if sec is not None and sec.PriceStep is not None else Decimal(0)
step_price = Decimal(0)
try:
sp = self.GetSecurityValue[Decimal](Level1Fields.StepPrice)
if sp is not None:
step_price = sp
except:
step_price = Decimal(0)
if price_step <= 0 or step_price <= 0:
return Decimal(0)
loss_per_unit = Decimal.Multiply(Decimal.Divide(stop_offset, price_step), step_price)
if loss_per_unit <= 0:
return Decimal(0)
portfolio = self.Portfolio
equity = portfolio.CurrentValue if portfolio is not None and portfolio.CurrentValue is not None else Decimal(0)
if equity <= 0:
return Decimal(0)
risk_amount = Decimal.Divide(Decimal.Multiply(equity, self._risk_percent.Value), Decimal(100))
if risk_amount <= 0:
return Decimal(0)
return Decimal.Divide(risk_amount, loss_per_unit)
def _adjust_volume(self, volume):
if volume <= 0:
return Decimal(0)
sec = self.Security
if sec is None:
return volume
step = sec.VolumeStep if sec.VolumeStep is not None else Decimal(0)
if step > 0:
steps = Math.Floor(Decimal.Divide(volume, step))
adjusted = Decimal.Multiply(steps, step)
if adjusted <= 0:
adjusted = step
return adjusted
return volume if volume > 0 else Decimal(0)
def _calculate_pip_size(self):
sec = self.Security
step = sec.PriceStep if sec is not None and sec.PriceStep is not None else Decimal(0)
if step <= 0:
return Decimal(1)
return step
def _reset_trade(self):
self._entry_price = Decimal(0)
self._stop_price = None
self._take_price = None
self._trail_stop = None
self._be_activated = False
def CreateClone(self):
return vr_zver_v2_strategy()