Стратегия Trailing Stop EA
Эта стратегия управляет уже открытой позицией, используя трейлинг-стоп. Она подписывается на поток сделок и смещает уровень стопа по мере движения цены в выгодном направлении. При развороте рынка и достижении уровня стопа позиция закрывается.
Детали
- Вход: стратегия не открывает позиции; предполагается, что позиция уже существует.
- Лонг: после роста цены на величину трейлинг-стопа уровень стопа переносится вверх.
- Шорт: при падении цены уровень стопа переносится вниз.
- Выход: позиция закрывается при достижении ценой уровня стопа.
- Индикаторы: отсутствуют.
- Таймфрейм: тик, реакция на каждую сделку.
- Стопы: только трейлинг-стоп.
Параметры
TrailingPoints— расстояние трейлинг-стопа в пунктах (шага цены). По умолчанию: 200.
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>
/// Trailing stop strategy with EMA crossover entry.
/// Opens positions based on fast/slow EMA crossover and manages them with trailing stop protection.
/// </summary>
public class TrailingStopEAStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<decimal> _trailingPct;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _slowEma;
private decimal _prevFast;
private decimal _prevSlow;
private bool _isFirst = true;
private DateTimeOffset _lastTradeTime;
public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
public decimal TrailingPct { get => _trailingPct.Value; set => _trailingPct.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public TrailingStopEAStrategy()
{
_fastLength = Param(nameof(FastLength), 10)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA length", "Indicators");
_slowLength = Param(nameof(SlowLength), 30)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA length", "Indicators");
_trailingPct = Param(nameof(TrailingPct), 2m)
.SetDisplay("Trailing %", "Trailing stop percentage", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_slowEma = default;
_prevFast = 0;
_prevSlow = 0;
_isFirst = true;
_lastTradeTime = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_isFirst = true;
var fastEma = new ExponentialMovingAverage { Length = FastLength };
_slowEma = new ExponentialMovingAverage { Length = SlowLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, ProcessCandle)
.Start();
StartProtection(
new Unit(TrailingPct * 2, UnitTypes.Percent),
new Unit(TrailingPct, UnitTypes.Percent),
isStopTrailing: true
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, _slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast)
{
if (candle.State != CandleStates.Finished)
return;
var slowResult = _slowEma.Process(candle.ClosePrice, candle.OpenTime, true);
if (!slowResult.IsFormed)
return;
var slow = slowResult.ToDecimal();
if (_isFirst)
{
_prevFast = fast;
_prevSlow = slow;
_isFirst = false;
return;
}
var cooldown = TimeSpan.FromHours(24);
var canTrade = _lastTradeTime == default || (candle.OpenTime - _lastTradeTime) >= cooldown;
// EMA cross up -> buy
if (_prevFast <= _prevSlow && fast > slow && Position <= 0 && canTrade)
{
if (Position < 0) BuyMarket();
BuyMarket();
_lastTradeTime = candle.OpenTime;
}
// EMA cross down -> sell
else if (_prevFast >= _prevSlow && fast < slow && Position >= 0 && canTrade)
{
if (Position > 0) SellMarket();
SellMarket();
_lastTradeTime = candle.OpenTime;
}
_prevFast = fast;
_prevSlow = slow;
}
}
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
from indicator_extensions import *
class trailing_stop_ea_strategy(Strategy):
"""Fast/slow EMA crossover with StartProtection trailing stop."""
def __init__(self):
super(trailing_stop_ea_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 10).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA length", "Indicators")
self._slow_length = self.Param("SlowLength", 30).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA length", "Indicators")
self._trailing_pct = self.Param("TrailingPct", 2.0).SetDisplay("Trailing %", "Trailing stop percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_first = True
@property
def CandleType(self):
return self._candle_type.Value
def OnReseted(self):
super(trailing_stop_ea_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_first = True
def OnStarted2(self, time):
super(trailing_stop_ea_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_first = True
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_length.Value
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self._slow_length.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast_ema, self._process_candle).Start()
pct = float(self._trailing_pct.Value)
self.StartProtection(
Unit(pct * 2, UnitTypes.Percent),
Unit(pct, UnitTypes.Percent),
True
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, self._slow_ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val):
if candle.State != CandleStates.Finished:
return
slow_result = process_float(self._slow_ema, candle.ClosePrice, candle.OpenTime, True)
if not slow_result.IsFormed:
return
slow = float(slow_result)
fast = float(fast_val)
if self._is_first:
self._prev_fast = fast
self._prev_slow = slow
self._is_first = False
return
if self._prev_fast <= self._prev_slow and fast > slow and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fast < slow and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_fast = fast
self._prev_slow = slow
def CreateClone(self):
return trailing_stop_ea_strategy()