The Lavika100 Strategy is a faithful port of the MetaTrader 5 expert advisor "Lavika cent". The system combines a one-hour (H1) and a four-hour (H4) RAVI momentum filter to decide when to open trades. It keeps the original money management choices (fixed lot or risk percentage), one-position discipline, optional signal reversal and automatic stop management. The StockSharp version adheres to the high level API guidelines: candle subscriptions drive the workflow, indicators are accessed through binders, and protective orders are configured with StartProtection.
Workflow
Data subscriptions – the strategy subscribes to H1 candles for the execution timeframe and H4 candles for the trend filter. The SimpleMovingAverage indicator is applied to the open prices to emulate the MT5 iMA(..., PRICE_OPEN) calls.
RAVI momentum – two moving averages on every timeframe (fast/slow) generate a "RAVI" percentage: (fast - slow) / slow * 100. The H1 value needs to be positive before any trade is considered.
Trend pattern detection – the most recent four RAVI values on H4 are inspected:
A rising sequence (r0 > r1, r1 < r2, r2 < r3) triggers a long signal.
A falling sequence (r0 < r1, r1 > r2, r2 > r3) triggers a short signal. This mirrors the behaviour of the original code even though the expert only flipped direction via the Reverse flag.
Signal reversal and flattening – depending on the ReverseSignals and CloseOpposite parameters the algorithm either opens in the detected direction or reverses it, closing any opposite position beforehand.
Money management – volume is taken from FixedVolume or scaled by risk via the RiskPercent method (portfolio value * percent / stop distance).
Protection – stop-loss, take-profit, trailing stop and trailing step are activated via StartProtection as soon as the strategy starts and the parameters are non-zero.
Trading Rules
Long entry – H1 RAVI is positive and the H4 series shows a rising pattern. The strategy closes an existing short position when CloseOpposite=true before buying.
Short entry – H1 RAVI is positive and the H4 series shows a falling pattern. When ReverseSignals=true the directions swap, matching the MT5 "Reverse" toggle.
Single position – with OnlyOnePosition=true any non-flat exposure blocks additional entries until the position is closed.
Volume sizing – risk-percentage mode uses the instrument PriceStep/StepPrice pair to convert price distance to monetary value, respecting VolumeStep, VolumeMin and VolumeMax.
Parameters
Name
Description
H1CandleType
Timeframe for the execution logic (default 1 hour).
H4CandleType
Higher timeframe used by the trend filter (default 4 hours).
H1FastPeriod / H1SlowPeriod
Moving average lengths for the H1 RAVI.
H4FastPeriod / H4SlowPeriod
Moving average lengths for the H4 RAVI.
StopLossPoints
Stop-loss distance in pip-based points.
TakeProfitPoints
Take-profit distance in pip-based points.
TrailingStopPoints
Trailing stop distance. Set to zero to disable trailing.
TrailingStepPoints
Minimum step for trailing updates. Must be positive when trailing is enabled.
FixedVolume
Lot size used in fixed mode.
RiskPercent
Percent of portfolio value to risk when MoneyMode equals RiskPercent.
MoneyMode
Switch between FixedLot and RiskPercent.
OnlyOnePosition
Allow only a single open position.
ReverseSignals
Flip long/short actions (default true to match the EA setting).
CloseOpposite
Close an opposite position before placing a new order.
Conversion Notes
Pip conversion mimics the MT5 expert: three- and five-digit quotes multiply PriceStep by ten to obtain a pip-sized increment.
The RAVI history is stored without custom collections—just four nullable fields—which respects the repository restrictions against manual buffers.
Money management avoids indicator GetValue calls and uses StockSharp market metadata to map percentage risk to volume.
StartProtection is only called when at least one of the protective distances is positive, ensuring safe execution during backtests and live trading.
Usage Tips
Provide a Forex-style instrument with correctly configured PriceStep, StepPrice, VolumeStep, VolumeMin and VolumeMax.
When using risk-based sizing, define a non-zero StopLossPoints; otherwise the calculated volume will be zero.
Because the original EA contained a logic quirk where both patterns set the buy flag, keep ReverseSignals=true if you need to reproduce its exact trades.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class Lavika100Strategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public Lavika100Strategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class lavika100_strategy(Strategy):
def __init__(self):
super(lavika100_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(lavika100_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(lavika100_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return lavika100_strategy()