Стратегия является портом советника MetaTrader Trend Line By Angle на StockSharp. В оригинале сделки открывались вручную нажатием кнопок, а управление риском реализовано множеством фильтров. Перенос автоматизирует работу за счёт MACD, но сохраняет все элементы защиты:
MACD 12/26/9 рассчитывается на свечах SignalCandleType. Пересечение вверх открывает длинные позиции, пересечение вниз — короткие.
Объём набирается порциями до значения TradeVolume * MaxEntries, что повторяет последовательные клики по кнопкам в MQL.
На торговом таймфрейме рассчитываются полосы Боллинджера 20/2. Касание верхней полосы закрывает лонги, касание нижней — шорты.
Стоп-лосс, тейк-профит, трейлинг и перевод в безубыток задаются в пунктах и пересчитываются через PriceStep инструмента.
Дополнительно действуют денежный и процентный тейк-профиты, а также денежный трейлинг общей прибыли.
Логика работы
Подготовка индикаторов. MovingAverageConvergenceDivergenceSignal подключается к подписке SignalCandleType, BollingerBands — к CandleType.
Формирование сигналов. После закрытия каждой свечи проверяется последнее пересечение MACD. При смене знака стратегия закрывает встречные позиции и открывает новые в сторону сигнала.
Пошаговое наращивание. Пока суммарный объём меньше TradeVolume * MaxEntries, стратегия добавляет лоты рыночными заявками.
Контроль риска. На каждой свече пересчитываются стопы, трейлинг и уровни безубытка. Касание полос Боллинджера принудительно закрывает позицию.
Защита счёта. Сначала выполняются проверки денежных/процентных целей и трейлинга прибыли, и только затем анализируются новые сигналы.
Особенности управления капиталом
Суммарная прибыль = реализованная PnL + плавающая прибыль, оценённая по цене закрытия свечи и параметрам шага цены.
Перевод в безубыток: при профите свыше BreakEvenTriggerPips стоп переносится на Entry ± BreakEvenOffsetPips.
Трейлинг: при росте цены более чем на TrailingStopPips стоп подтягивается ближе к цене. Для лонгов уровень только повышается, для шортов только понижается.
Денежный трейлинг: после достижения MoneyTrailTrigger фиксируется максимум прибыли; просадка более MoneyTrailStop закрывает все сделки.
Параметры
Параметр
Описание
TradeVolume
Объём одной заявки.
MaxEntries
Максимальное число заявок, формирующих позицию.
StopLossPips
Расстояние стоп-лосса в пунктах.
TakeProfitPips
Расстояние тейк-профита в пунктах.
TrailingStopPips
Размер трейлинг-стопа в пунктах.
UseBreakEven
Включение перевода в безубыток.
BreakEvenTriggerPips
Порог активации безубытка.
BreakEvenOffsetPips
Дополнительный запас при переносе стопа.
UseBollingerExit
Использовать выходы по полосам Боллинджера.
BollingerPeriod / BollingerDeviation
Настройки полос Боллинджера.
UseProfitMoneyTarget / ProfitMoneyTarget
Денежная цель и её значение.
UseProfitPercentTarget / ProfitPercentTarget
Процентная цель и её значение.
EnableMoneyTrail
Включение денежного трейлинга прибыли.
MoneyTrailTrigger
Минимальная прибыль для запуска трейлинга.
MoneyTrailStop
Допустимая просадка от максимума прибыли.
MacdFastPeriod, MacdSlowPeriod, MacdSignalPeriod
Параметры MACD.
CandleType
Таймфрейм исполнения.
SignalCandleType
Таймфрейм расчёта MACD.
Практические рекомендации
Перед запуском убедитесь, что у инструмента задан корректный PriceStep и StepPrice, иначе расчёт пунктов и стоимости шага будет неточным.
Если провайдер портфеля не возвращает Portfolio.CurrentValue или Portfolio.BeginValue, процентный тейк-профит автоматически отключается.
В исходном C# файле все комментарии сделаны на английском языке для единообразия и будущей поддержки.
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 TrendLineByAngleStrategy : 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 TrendLineByAngleStrategy()
{
_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 trend_line_by_angle_strategy(Strategy):
def __init__(self):
super(trend_line_by_angle_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(trend_line_by_angle_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(trend_line_by_angle_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 trend_line_by_angle_strategy()