三均线通道交叉策略
概述
三均线通道交叉策略(Triple MA Channel Crossover)在快速均线穿越中速与慢速均线时寻找趋势方向的突破,并借助 唐奇安式价格通道进行风控。该实现基于 MetaTrader 的原始“3MACross EA”,保留了可配置的均线结构、仓位分级、止损/ 止盈与移动保护逻辑。
策略可以按设定的步长逐步加仓,支持以点数设定的固定风控,也可以让止损/止盈跟随价格通道自动调整。当触发保本条 件时,系统会把止损推至入场价上方(或下方)并留出安全缓冲。
交易逻辑
- 入场条件
- 做多: 快速均线同时上穿中速与慢速均线。如果启用“收盘确认”,则只有在 K 线收盘后确认交叉时才会入场;如 果关闭该选项,只要快速均线保持在另外两条均线之上即视为有效信号。
- 做空: 快速均线同时下穿中速与慢速均线,确认逻辑与做多相同。
- 若已持有反向仓位,则先平仓再反向开仓;同向信号允许分批加仓,直至达到“最大仓位数”。
- 出场条件
- 价格触及配置的固定止盈点或通道给出的目标价。
- 价格触及动态止损,包括固定点差、跟踪止损、保本位或通道止损等。
- 如果启用跟踪止损,只有当价格按设定的“跟踪步长”继续向有利方向运行后才会更新止损。
风险控制
- 止损与止盈既可以使用固定点差,也可以在开启
Auto SL/TP时完全由价格通道决定。 - 跟踪止损与保本逻辑与原 EA 一致,只会向有利方向收紧,不会放宽。
- 唐奇安通道提供自然的支撑/阻力区间,可用于自动设置保护性价位。
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 |
是否只在收盘后确认交叉信号。关闭后每根 K 线都会检查均线位置。 |
Max Positions |
同向最多允许的加仓次数。 |
Fast/Middle/Slow MA Period |
三条均线的周期。 |
Fast/Middle/Slow MA Shift |
对应均线向前/向后偏移的柱数。 |
Fast/Middle/Slow MA Type |
均线类型(简单、指数、平滑、加权)。 |
Channel Period |
唐奇安通道的回溯周期。 |
Candle Type |
策略使用的 K 线周期。 |
实现说明
- 点差换算使用
Security.PriceStep。若品种未提供最小变动价位,则退化为每点一个价格单位。 - 自动通道管理只会让止损/止盈向当前价格靠拢,不会放宽原有保护。
- 保本逻辑会将“跟踪步长”作为额外缓冲,与原 EA 的处理方式一致。
- 策略基于 StockSharp 高阶 API 编写,自动绘制均线与价格通道,便于图形化分析。
- 请确保历史数据覆盖足够长的时间,以便慢速均线与通道在发出信号前已经形成。
使用步骤
- 将策略绑定到目标合约,并设置需要的 K 线周期;
- 根据原 EA 或个人需求配置三条均线的周期、类型与偏移;
- 在固定点差风控与通道自动止损/止盈之间进行选择;
- 启动策略,系统会订阅对应的 K 线、计算指标并在条件满足时自动下单;
- 通过日志与图表监控跟踪止损与保本止损的调整情况。
风险提示: 程序化交易具有较高风险。务必先在历史回测与模拟环境中充分验证,再考虑应用到真实账户。
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()