Стратегия Triple MA Channel Crossover
Обзор
Triple MA Channel Crossover торгует направленные пробои в момент, когда быстрая скользящая средняя пересекает сверху или снизу две более медленные средние. Для сопровождения позиции используется ценовой канал Дончиана, который может автоматически задавать уровни стоп-лосса и тейк-профита. Алгоритм является переносом оригинального советника MetaTrader «3MACross EA» и сохраняет его настраиваемые параметры скользящих средних и риск-менеджмента.
Стратегия поддерживает поэтапное наращивание позиции, работу с фиксированными целями в пунктах и динамическое сопровождение по каналу. При активации функции безубыточности стоп переносится к цене входа с дополнительным зазором безопасности.
Торговая логика
- Вход в лонг — быстрая средняя пересекает вверх среднюю и медленную. При включенном параметре
Trade On Closeпересечение должно быть подтверждено на закрытом баре; при выключенном параметре достаточно, чтобы быстрая средняя находилась выше обеих остальных. - Вход в шорт — зеркальное условие: быстрая средняя пересекает вниз две медленные средние.
- При появлении сигнала противоположной направленности стратегия закрывает текущую позицию и при необходимости сразу переворачива
ется. Дополнительные входы в ту же сторону разрешены, пока общее число лотов не превысит
Max Positions.
Управление рисками
- Стоп-лосс и тейк-профит можно задать фиксированным расстоянием в пунктах или поручить их расчёт ценовому каналу (
Auto SL/TP). - Трейлинг-стоп и перенос в безубыток повторяют логику исходного советника: защита только ужесточается и никогда не ослабляется.
- Канал Дончиана выступает динамической зоной поддержки/сопротивления, позволяя быстро адаптировать выходы.
- Параметр
Max Positionsограничивает количество доливок и предотвращает чрезмерное наращивание позиции.
Основные параметры
| Параметр | Описание |
|---|---|
Volume |
Объём заявки для каждой доливки. |
Stop Loss (pips) |
Фиксированное расстояние стоп-лосса (0 — выключено). |
Take Profit (pips) |
Фиксированное расстояние тейк-профита (0 — выключено). |
Trailing Stop (pips) |
Дистанция трейлинг-стопа (0 — выключено). |
Trailing Step (pips) |
Минимальный шаг обновления трейлинг-стопа. |
Break Even (pips) |
Прибыль в пунктах, после которой стоп переносится в безубыток. |
Auto SL/TP |
Использовать канал Дончиана для расчёта стопов и целей. |
Trade On Close |
Требовать подтверждения пересечения на закрытом баре (иначе проверяется каждое обновление свечи). |
Max Positions |
Максимальное число доливок в одном направлении. |
Fast/Middle/Slow MA Period |
Периоды трёх скользящих средних. |
Fast/Middle/Slow MA Shift |
Сдвиг соответствующей скользящей (в барах). |
Fast/Middle/Slow MA Type |
Типы скользящих (Simple, Exponential, Smoothed, Weighted). |
Channel Period |
Глубина расчёта канала Дончиана. |
Candle Type |
Таймфрейм свечей, которые обрабатывает стратегия. |
Особенности реализации
- Перевод пунктов в цены выполняется через
Security.PriceStep. При отсутствии корректного шага цены используется значение1. - Автоматический канал никогда не отдаляет стоп или цель от цены — уровни двигаются только в сторону сокращения риска.
- Порог безубыточности использует «шаг трейлинга» как дополнительный буфер, что соответствует логике оригинального советника.
- Стратегия построена на высокоуровневом API StockSharp и может отображать на графике свечи, скользящие средние и канал.
- Для корректной работы убедитесь, что в истории достаточно данных для формирования самой медленной средней и канала.
Порядок использования
- Привяжите стратегию к нужному инструменту и выберите таймфрейм свечей;
- Настройте периоды, типы и сдвиги скользящих средних под свои требования или под параметры оригинального советника;
- Выберите режим управления риском: фиксированные цели или автоматические уровни по каналу;
- Запустите стратегию — она подпишется на свечи, рассчитает индикаторы и начнёт торговать при появлении сигналов;
- Следите за перемещениями стопов и результатами сделок через журнал и построенные графики.
Важно: автоматическая торговля связана с повышенным риском. Перед использованием на реальном счёте обязательно протестируйте стратегию на истории и в демо-среде.
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>
/// Triple moving average crossover strategy that uses a Donchian style price channel for risk management.
/// </summary>
public class TripleMaChannelCrossoverStrategy : Strategy
{
/// <summary>
/// Moving average calculation modes supported by <see cref="TripleMaChannelCrossoverStrategy"/>.
/// </summary>
public enum MovingAverageModes
{
/// <summary>
/// Simple moving average.
/// </summary>
Simple,
/// <summary>
/// Exponential moving average.
/// </summary>
Exponential,
/// <summary>
/// Smoothed moving average (SMMA).
/// </summary>
Smoothed,
/// <summary>
/// Linear weighted moving average.
/// </summary>
Weighted,
}
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<int> _breakEvenPips;
private readonly StrategyParam<bool> _useAutoTargets;
private readonly StrategyParam<bool> _tradeOnClose;
private readonly StrategyParam<int> _maxPositionCount;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _fastShift;
private readonly StrategyParam<MovingAverageModes> _fastMaType;
private readonly StrategyParam<int> _middlePeriod;
private readonly StrategyParam<int> _middleShift;
private readonly StrategyParam<MovingAverageModes> _middleMaType;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _slowShift;
private readonly StrategyParam<MovingAverageModes> _slowMaType;
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<DataType> _candleType;
private IIndicator _fastMa = null!;
private IIndicator _middleMa = null!;
private IIndicator _slowMa = null!;
private DonchianChannels _channel = null!;
private decimal _prevFast;
private decimal _prevMiddle;
private decimal _prevSlow;
private bool _hasPreviousValues;
private decimal _tickSize;
private decimal? _longStop;
private decimal? _longTake;
private decimal _longEntryPrice;
private bool _longBreakEvenActivated;
private decimal? _shortStop;
private decimal? _shortTake;
private decimal _shortEntryPrice;
private bool _shortBreakEvenActivated;
/// <summary>
/// Stop loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Minimal step for trailing stop adjustments in pips.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Profit in pips required to move the stop loss to break-even.
/// </summary>
public int BreakEvenPips
{
get => _breakEvenPips.Value;
set => _breakEvenPips.Value = value;
}
/// <summary>
/// Enable automatic SL/TP placement based on the price channel.
/// </summary>
public bool UseAutoTargets
{
get => _useAutoTargets.Value;
set => _useAutoTargets.Value = value;
}
/// <summary>
/// Trade only when the crossover is confirmed on the closed bar.
/// </summary>
public bool TradeOnClose
{
get => _tradeOnClose.Value;
set => _tradeOnClose.Value = value;
}
/// <summary>
/// Maximum number of scaled-in positions.
/// </summary>
public int MaxPositionCount
{
get => _maxPositionCount.Value;
set => _maxPositionCount.Value = value;
}
/// <summary>
/// Period for the fast moving average.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Shift for the fast moving average.
/// </summary>
public int FastShift
{
get => _fastShift.Value;
set => _fastShift.Value = value;
}
/// <summary>
/// Type of the fast moving average.
/// </summary>
public MovingAverageModes FastMaType
{
get => _fastMaType.Value;
set => _fastMaType.Value = value;
}
/// <summary>
/// Period for the middle moving average.
/// </summary>
public int MiddlePeriod
{
get => _middlePeriod.Value;
set => _middlePeriod.Value = value;
}
/// <summary>
/// Shift for the middle moving average.
/// </summary>
public int MiddleShift
{
get => _middleShift.Value;
set => _middleShift.Value = value;
}
/// <summary>
/// Type of the middle moving average.
/// </summary>
public MovingAverageModes MiddleMaType
{
get => _middleMaType.Value;
set => _middleMaType.Value = value;
}
/// <summary>
/// Period for the slow moving average.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Shift for the slow moving average.
/// </summary>
public int SlowShift
{
get => _slowShift.Value;
set => _slowShift.Value = value;
}
/// <summary>
/// Type of the slow moving average.
/// </summary>
public MovingAverageModes SlowMaType
{
get => _slowMaType.Value;
set => _slowMaType.Value = value;
}
/// <summary>
/// Lookback period for the Donchian price channel.
/// </summary>
public int ChannelPeriod
{
get => _channelPeriod.Value;
set => _channelPeriod.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="TripleMaChannelCrossoverStrategy"/>.
/// </summary>
public TripleMaChannelCrossoverStrategy()
{
_stopLossPips = Param(nameof(StopLossPips), 0)
.SetDisplay("Stop Loss (pips)", "Stop loss distance", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 145)
.SetDisplay("Take Profit (pips)", "Take profit distance", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 0)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 5)
.SetDisplay("Trailing Step (pips)", "Minimal trailing adjustment", "Risk");
_breakEvenPips = Param(nameof(BreakEvenPips), 15)
.SetDisplay("Break Even (pips)", "Profit to move stop to break-even", "Risk");
_useAutoTargets = Param(nameof(UseAutoTargets), false)
.SetDisplay("Auto SL/TP", "Use channel for stop & take", "Risk");
_tradeOnClose = Param(nameof(TradeOnClose), false)
.SetDisplay("Trade On Close", "Confirm cross on closed bar", "Signals");
_maxPositionCount = Param(nameof(MaxPositionCount), 5)
.SetGreaterThanZero()
.SetDisplay("Max Positions", "Maximum scaling steps", "Trading");
_fastPeriod = Param(nameof(FastPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Fast MA Period", "First moving average", "Moving Averages");
_fastShift = Param(nameof(FastShift), 0)
.SetDisplay("Fast MA Shift", "Bars to shift fast MA", "Moving Averages");
_fastMaType = Param(nameof(FastMaType), MovingAverageModes.Smoothed)
.SetDisplay("Fast MA Type", "Method for fast MA", "Moving Averages");
_middlePeriod = Param(nameof(MiddlePeriod), 15)
.SetGreaterThanZero()
.SetDisplay("Middle MA Period", "Second moving average", "Moving Averages");
_middleShift = Param(nameof(MiddleShift), 0)
.SetDisplay("Middle MA Shift", "Bars to shift middle MA", "Moving Averages");
_middleMaType = Param(nameof(MiddleMaType), MovingAverageModes.Smoothed)
.SetDisplay("Middle MA Type", "Method for middle MA", "Moving Averages");
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow MA Period", "Third moving average", "Moving Averages");
_slowShift = Param(nameof(SlowShift), 0)
.SetDisplay("Slow MA Shift", "Bars to shift slow MA", "Moving Averages");
_slowMaType = Param(nameof(SlowMaType), MovingAverageModes.Smoothed)
.SetDisplay("Slow MA Type", "Method for slow MA", "Moving Averages");
_channelPeriod = Param(nameof(ChannelPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("Channel Period", "Price channel lookback", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0m;
_prevMiddle = 0m;
_prevSlow = 0m;
_hasPreviousValues = false;
_longStop = null;
_longTake = null;
_longEntryPrice = 0m;
_longBreakEvenActivated = false;
_shortStop = null;
_shortTake = null;
_shortEntryPrice = 0m;
_shortBreakEvenActivated = false;
_tickSize = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastMa = CreateMovingAverage(FastMaType, FastPeriod);
_middleMa = CreateMovingAverage(MiddleMaType, MiddlePeriod);
_slowMa = CreateMovingAverage(SlowMaType, SlowPeriod);
_channel = new DonchianChannels { Length = ChannelPeriod };
_tickSize = Security.PriceStep ?? 1m;
if (_tickSize <= 0)
_tickSize = 1m;
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_fastMa, _middleMa, _slowMa, _channel, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastMa);
DrawIndicator(area, _middleMa);
DrawIndicator(area, _slowMa);
DrawIndicator(area, _channel);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue fastVal, IIndicatorValue middleVal, IIndicatorValue slowVal, IIndicatorValue channelVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fastMa.IsFormed || !_middleMa.IsFormed || !_slowMa.IsFormed || !_channel.IsFormed)
return;
var fastValue = fastVal.IsEmpty ? 0m : fastVal.GetValue<decimal>();
var middleValue = middleVal.IsEmpty ? 0m : middleVal.GetValue<decimal>();
var slowValue = slowVal.IsEmpty ? 0m : slowVal.GetValue<decimal>();
var channelValue = (DonchianChannelsValue)channelVal;
var channelUpper = channelValue.UpperBand as decimal?;
var channelLower = channelValue.LowerBand as decimal?;
UpdateLongTargets(candle, channelUpper, channelLower);
UpdateShortTargets(candle, channelUpper, channelLower);
CheckExits(candle);
var crossUp = CalculateCrossUp(fastValue, middleValue, slowValue);
var crossDown = CalculateCrossDown(fastValue, middleValue, slowValue);
if (crossUp)
{
TryEnterLong(candle, channelUpper, channelLower);
}
else if (crossDown)
{
TryEnterShort(candle, channelUpper, channelLower);
}
_prevFast = fastValue;
_prevMiddle = middleValue;
_prevSlow = slowValue;
_hasPreviousValues = true;
}
private bool CalculateCrossUp(decimal fastValue, decimal middleValue, decimal slowValue)
{
if (TradeOnClose)
{
if (!_hasPreviousValues)
return false;
var crossMiddle = _prevFast <= _prevMiddle && fastValue > middleValue;
var crossSlow = _prevFast <= _prevSlow && fastValue > slowValue;
return crossMiddle && crossSlow;
}
return fastValue > middleValue && fastValue > slowValue;
}
private bool CalculateCrossDown(decimal fastValue, decimal middleValue, decimal slowValue)
{
if (TradeOnClose)
{
if (!_hasPreviousValues)
return false;
var crossMiddle = _prevFast >= _prevMiddle && fastValue < middleValue;
var crossSlow = _prevFast >= _prevSlow && fastValue < slowValue;
return crossMiddle && crossSlow;
}
return fastValue < middleValue && fastValue < slowValue;
}
private void TryEnterLong(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
{
if (Position >= 0)
{
var maxVolume = Volume * MaxPositionCount;
var currentLong = Position;
if (currentLong >= maxVolume)
return;
var targetVolume = Math.Min(Volume, maxVolume - currentLong);
if (targetVolume <= 0m)
return;
BuyMarket();
}
else
{
BuyMarket();
ResetShortState();
}
_longEntryPrice = candle.ClosePrice;
_longBreakEvenActivated = false;
SetLongTargets(candle, channelUpper, channelLower);
}
private void TryEnterShort(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
{
if (Position <= 0)
{
var maxVolume = Volume * MaxPositionCount;
var currentShort = -Position;
if (currentShort >= maxVolume)
return;
var targetVolume = Math.Min(Volume, maxVolume - currentShort);
if (targetVolume <= 0m)
return;
SellMarket();
}
else
{
SellMarket();
ResetLongState();
}
_shortEntryPrice = candle.ClosePrice;
_shortBreakEvenActivated = false;
SetShortTargets(candle, channelUpper, channelLower);
}
private void SetLongTargets(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
{
var entryPrice = candle.ClosePrice;
var stopDistance = GetDistance(StopLossPips);
var takeDistance = GetDistance(TakeProfitPips);
var breakEvenDistance = GetDistance(BreakEvenPips);
if (UseAutoTargets)
{
if (channelLower is decimal lower)
{
var candidate = lower;
if (BreakEvenPips > 0)
candidate = Math.Max(candidate, entryPrice - breakEvenDistance);
_longStop = _longStop.HasValue ? Math.Max(_longStop.Value, candidate) : candidate;
}
else if (stopDistance > 0m)
{
_longStop = entryPrice - stopDistance;
}
if (channelUpper is decimal upper)
{
var candidate = upper;
if (BreakEvenPips > 0)
candidate = Math.Max(candidate, entryPrice + breakEvenDistance);
_longTake = _longTake.HasValue ? Math.Max(_longTake.Value, candidate) : candidate;
}
else if (takeDistance > 0m)
{
_longTake = entryPrice + takeDistance;
}
}
else
{
_longStop = stopDistance > 0m ? entryPrice - stopDistance : null;
_longTake = takeDistance > 0m ? entryPrice + takeDistance : null;
}
}
private void SetShortTargets(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
{
var entryPrice = candle.ClosePrice;
var stopDistance = GetDistance(StopLossPips);
var takeDistance = GetDistance(TakeProfitPips);
var breakEvenDistance = GetDistance(BreakEvenPips);
if (UseAutoTargets)
{
if (channelUpper is decimal upper)
{
var candidate = upper;
if (BreakEvenPips > 0)
candidate = Math.Min(candidate, entryPrice + breakEvenDistance);
_shortStop = _shortStop.HasValue ? Math.Min(_shortStop.Value, candidate) : candidate;
}
else if (stopDistance > 0m)
{
_shortStop = entryPrice + stopDistance;
}
if (channelLower is decimal lower)
{
var candidate = lower;
if (BreakEvenPips > 0)
candidate = Math.Min(candidate, entryPrice - breakEvenDistance);
_shortTake = _shortTake.HasValue ? Math.Min(_shortTake.Value, candidate) : candidate;
}
else if (takeDistance > 0m)
{
_shortTake = entryPrice - takeDistance;
}
}
else
{
_shortStop = stopDistance > 0m ? entryPrice + stopDistance : null;
_shortTake = takeDistance > 0m ? entryPrice - takeDistance : null;
}
}
private void UpdateLongTargets(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
{
if (Position <= 0)
{
ResetLongState();
return;
}
var breakEvenDistance = GetDistance(BreakEvenPips);
var trailingDistance = GetDistance(TrailingStopPips);
var trailingStep = GetDistance(TrailingStepPips);
var entryPrice = _longEntryPrice;
if (UseAutoTargets && channelLower is decimal lower)
{
var candidate = lower;
if (BreakEvenPips > 0)
candidate = Math.Max(candidate, entryPrice - breakEvenDistance);
_longStop = _longStop.HasValue ? Math.Max(_longStop.Value, candidate) : candidate;
}
if (UseAutoTargets && channelUpper is decimal upper)
{
var candidate = upper;
if (BreakEvenPips > 0)
candidate = Math.Max(candidate, entryPrice + breakEvenDistance);
_longTake = _longTake.HasValue ? Math.Max(_longTake.Value, candidate) : candidate;
}
if (trailingDistance > 0m)
{
var candidate = candle.ClosePrice - trailingDistance;
if (_longStop is decimal currentStop)
{
if (candidate - currentStop >= Math.Max(trailingStep, _tickSize))
_longStop = candidate;
}
else
{
_longStop = candidate;
}
}
if (BreakEvenPips > 0 && !_longBreakEvenActivated)
{
var activationPrice = entryPrice + breakEvenDistance + Math.Max(0m, trailingStep);
var targetStop = entryPrice + breakEvenDistance;
if (candle.ClosePrice >= activationPrice)
{
_longBreakEvenActivated = true;
_longStop = _longStop.HasValue ? Math.Max(_longStop.Value, targetStop) : targetStop;
}
}
}
private void UpdateShortTargets(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
{
if (Position >= 0)
{
ResetShortState();
return;
}
var breakEvenDistance = GetDistance(BreakEvenPips);
var trailingDistance = GetDistance(TrailingStopPips);
var trailingStep = GetDistance(TrailingStepPips);
var entryPrice = _shortEntryPrice;
if (UseAutoTargets && channelUpper is decimal upper)
{
var candidate = upper;
if (BreakEvenPips > 0)
candidate = Math.Min(candidate, entryPrice + breakEvenDistance);
_shortStop = _shortStop.HasValue ? Math.Min(_shortStop.Value, candidate) : candidate;
}
if (UseAutoTargets && channelLower is decimal lower)
{
var candidate = lower;
if (BreakEvenPips > 0)
candidate = Math.Min(candidate, entryPrice - breakEvenDistance);
_shortTake = _shortTake.HasValue ? Math.Min(_shortTake.Value, candidate) : candidate;
}
if (trailingDistance > 0m)
{
var candidate = candle.ClosePrice + trailingDistance;
if (_shortStop is decimal currentStop)
{
if (currentStop - candidate >= Math.Max(trailingStep, _tickSize))
_shortStop = candidate;
}
else
{
_shortStop = candidate;
}
}
if (BreakEvenPips > 0 && !_shortBreakEvenActivated)
{
var activationPrice = entryPrice - breakEvenDistance - Math.Max(0m, trailingStep);
var targetStop = entryPrice - breakEvenDistance;
if (candle.ClosePrice <= activationPrice)
{
_shortBreakEvenActivated = true;
_shortStop = _shortStop.HasValue ? Math.Min(_shortStop.Value, targetStop) : targetStop;
}
}
}
private void CheckExits(ICandleMessage candle)
{
if (Position > 0)
{
if (_longTake is decimal take && candle.HighPrice >= take)
{
SellMarket();
ResetLongState();
}
else if (_longStop is decimal stop && candle.LowPrice <= stop)
{
SellMarket();
ResetLongState();
}
}
else if (Position < 0)
{
if (_shortTake is decimal take && candle.LowPrice <= take)
{
BuyMarket();
ResetShortState();
}
else if (_shortStop is decimal stop && candle.HighPrice >= stop)
{
BuyMarket();
ResetShortState();
}
}
}
private decimal GetDistance(int pips)
{
return pips <= 0 ? 0m : pips * _tickSize;
}
private void ResetLongState()
{
_longStop = null;
_longTake = null;
_longEntryPrice = 0m;
_longBreakEvenActivated = false;
}
private void ResetShortState()
{
_shortStop = null;
_shortTake = null;
_shortEntryPrice = 0m;
_shortBreakEvenActivated = false;
}
private IIndicator CreateMovingAverage(MovingAverageModes mode, int length)
{
return mode switch
{
MovingAverageModes.Exponential => new ExponentialMovingAverage { Length = length },
MovingAverageModes.Weighted => new WeightedMovingAverage { Length = length },
MovingAverageModes.Smoothed => new SmoothedMovingAverage { Length = length },
_ => new SimpleMovingAverage { Length = length },
};
}
}
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 SmoothedMovingAverage, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class triple_ma_channel_crossover_strategy(Strategy):
"""Triple smoothed MA crossover with Donchian-style channel for SL/TP."""
def __init__(self):
super(triple_ma_channel_crossover_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 5).SetGreaterThanZero().SetDisplay("Fast MA", "Fast period", "MAs")
self._mid_period = self.Param("MiddlePeriod", 15).SetGreaterThanZero().SetDisplay("Middle MA", "Middle period", "MAs")
self._slow_period = self.Param("SlowPeriod", 30).SetGreaterThanZero().SetDisplay("Slow MA", "Slow period", "MAs")
self._channel_period = self.Param("ChannelPeriod", 15).SetGreaterThanZero().SetDisplay("Channel", "Highest/Lowest period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(triple_ma_channel_crossover_strategy, self).OnReseted()
self._prev_fast = 0
self._prev_mid = 0
self._prev_slow = 0
self._has_prev = False
self._entry_price = 0
self._stop_price = 0
self._take_price = 0
def OnStarted2(self, time):
super(triple_ma_channel_crossover_strategy, self).OnStarted2(time)
self._prev_fast = 0
self._prev_mid = 0
self._prev_slow = 0
self._has_prev = False
self._entry_price = 0
self._stop_price = 0
self._take_price = 0
fast = SmoothedMovingAverage()
fast.Length = self._fast_period.Value
mid = SmoothedMovingAverage()
mid.Length = self._mid_period.Value
slow = SmoothedMovingAverage()
slow.Length = self._slow_period.Value
hi = Highest()
hi.Length = self._channel_period.Value
lo = Lowest()
lo.Length = self._channel_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast, mid, slow, hi, lo, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, mid)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def OnProcess(self, candle, fast_val, mid_val, slow_val, hi_val, lo_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
mid = float(mid_val)
slow = float(slow_val)
upper = float(hi_val)
lower = float(lo_val)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
# Check exits
if self.Position > 0:
if self._take_price > 0 and high >= self._take_price:
self.SellMarket()
self._entry_price = 0
self._stop_price = 0
self._take_price = 0
elif self._stop_price > 0 and low <= self._stop_price:
self.SellMarket()
self._entry_price = 0
self._stop_price = 0
self._take_price = 0
elif self.Position < 0:
if self._take_price > 0 and low <= self._take_price:
self.BuyMarket()
self._entry_price = 0
self._stop_price = 0
self._take_price = 0
elif self._stop_price > 0 and high >= self._stop_price:
self.BuyMarket()
self._entry_price = 0
self._stop_price = 0
self._take_price = 0
if not self._has_prev:
self._prev_fast = fast
self._prev_mid = mid
self._prev_slow = slow
self._has_prev = True
return
# Cross detection
cross_up = fast > mid and fast > slow and (self._prev_fast <= self._prev_mid or self._prev_fast <= self._prev_slow)
cross_down = fast < mid and fast < slow and (self._prev_fast >= self._prev_mid or self._prev_fast >= self._prev_slow)
if cross_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._stop_price = lower if lower > 0 else 0
self._take_price = upper if upper > 0 else 0
elif cross_down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._stop_price = upper if upper > 0 else 0
self._take_price = lower if lower > 0 else 0
self._prev_fast = fast
self._prev_mid = mid
self._prev_slow = slow
def CreateClone(self):
return triple_ma_channel_crossover_strategy()