Virtual Trailing Stop
This strategy emulates a virtual trailing stop for both long and short positions. It does not generate entry signals; orders should be opened externally or manually. Once a position exists, the strategy maintains a trailing stop that follows price as it moves in a favorable direction. If price hits the stop level, the position is closed by market.
Parameters
StopLoss– fixed stop-loss distance in price steps.TakeProfit– fixed take-profit distance in price steps.TrailingStop– distance from current price to the trailing stop.TrailingStart– minimal profit in price steps before trailing begins.TrailingStep– minimal additional profit required to move the trailing level.CandleType– candle series used to process price data.
Notes
The strategy subscribes to candles of the specified type and evaluates the trailing logic on closed candles only.
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;
/// <summary>
/// Virtual trailing stop strategy.
/// Uses EMA crossover for entries with trailing stop protection.
/// </summary>
public class VirtualTrailingStopStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<decimal> _trailingPct;
private readonly StrategyParam<DataType> _candleType;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public decimal TrailingPct { get => _trailingPct.Value; set => _trailingPct.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public VirtualTrailingStopStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicator");
_trailingPct = Param(nameof(TrailingPct), 2m)
.SetDisplay("Trailing %", "Trailing stop distance percentage", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(
takeProfit: null,
stopLoss: new Unit(TrailingPct, UnitTypes.Percent),
isStopTrailing: true,
useMarketOrders: true);
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal)
{
if (candle.State != CandleStates.Finished)
return;
if (fastVal > slowVal && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
else if (fastVal < slowVal && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class virtual_trailing_stop_strategy(Strategy):
def __init__(self):
super(virtual_trailing_stop_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 10) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicator")
self._trailing_pct = self.Param("TrailingPct", 2.0) \
.SetDisplay("Trailing %", "Trailing stop distance percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def trailing_pct(self):
return self._trailing_pct.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(virtual_trailing_stop_strategy, self).OnStarted2(time)
self.StartProtection(
takeProfit=None,
stopLoss=Unit(self.trailing_pct, UnitTypes.Percent),
isStopTrailing=True,
useMarketOrders=True)
fast = ExponentialMovingAverage()
fast.Length = self.fast_period
slow = ExponentialMovingAverage()
slow.Length = self.slow_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_val)
slow_val = float(slow_val)
if fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return virtual_trailing_stop_strategy()