Стратегия является прямым портом советника MetaTrader "FT CCI MA". Торговые решения принимаются на закрытии каждой свечи, используя линейно-взвешенную скользящую среднюю (LWMA) в сочетании с порогами индикатора CCI и необязательным фильтром торговой сессии. Реализация на StockSharp сохраняет исходные параметры и их значения по умолчанию, но опирается на высокоуровневый API (подписка на свечи, привязка индикаторов, управление защитой позиции).
Особенности реализации:
LWMA рассчитывается по взвешенной цене (High + Low + 2 * Close) / 4, что соответствует режиму PRICE_WEIGHTED в MetaTrader.
CCI использует типичную цену (High + Low + Close) / 3, как в PRICE_TYPICAL.
Сигналы оцениваются по завершённой свече — то же поведение, что и в оригинале, где сделки открывались в начале следующей свечи на данных предыдущей.
Защитные ордера воспроизводят тейк-профит и стоп-лосс в пипсах.
Правила торговли
Вход в длинную позицию
Цена закрытия выше LWMA и CCI ниже CciLevelBuy (по умолчанию -100), или
Цена закрытия ниже LWMA и CCI ниже CciLevelDown (по умолчанию -200).
Сделка возможна только при нулевой или короткой текущей позиции.
Вход в короткую позицию
Цена закрытия ниже LWMA и CCI выше CciLevelSell (по умолчанию 100), или
Цена закрытия выше LWMA и CCI выше CciLevelUp (по умолчанию 200).
Сделка возможна только при нулевой или длинной текущей позиции.
Фильтр по времени
При включенном UseTimeFilter стратегия проверяет час из candle.CloseTime.
Если текущий час вне допустимого интервала, все позиции и активные заявки немедленно закрываются.
Управление рисками
StartProtection задаёт абсолютные уровни стоп-лосса и тейк-профита, переводя пипсы в цену через Security.PriceStep.
Объём ордера учитывает текущую нетто-позицию, поэтому разворот автоматически закрывает противоположную сторону.
Параметры
Имя
Описание
Значение по умолчанию
OrderVolume
Торговый объём в лотах.
1
StopLossPips
Дистанция стоп-лосса в пипсах (0 отключает).
150
TakeProfitPips
Дистанция тейк-профита в пипсах (0 отключает).
150
UseTimeFilter
Включает фильтр торговой сессии.
true
StartHour
Час начала торговли (0–23).
10
EndHour
Час окончания торговли (0–23). При значении меньше StartHour интервал пересекает полночь.
5
CciPeriod
Период индикатора CCI.
14
CciLevelUp
Верхний порог для агрессивных продаж (+200).
200
CciLevelDown
Нижний порог для агрессивных покупок (-200).
-200
CciLevelBuy
Мягкий порог для покупок, когда цена выше MA (-100).
-100
CciLevelSell
Мягкий порог для продаж, когда цена ниже MA (+100).
100
MaPeriod
Период LWMA.
200
MaShift
Горизонтальный сдвиг LWMA в барах; используется значение MaShift баров назад.
0
CandleType
Тип/таймфрейм свечей для расчётов.
1 час
Детали реализации
Пипсы – Базовый размер шага берётся из Security.PriceStep. Для инструментов с 3 или 5 знаками размер умножается на 10, чтобы привести 0.00001 к 0.0001, как в MetaTrader.
Фильтр сессии – Учтены оба сценария из исходного кода: внутридневной (StartHour < EndHour) и ночной (StartHour > EndHour). При равных значениях торговля отключается, как и в оригинале.
Привязка индикаторов – Используется SubscribeCandles().Bind(...), поэтому дополнительные буферы не требуются; история LWMA хранится только ради опционального сдвига.
Управление ордерами – Перед открытием позиции вызывается CancelActiveOrders(), что соответствует очистке стакана заявок в MQL-версии.
Python-версии нет – В каталоге присутствует только C#-реализация.
Использование
Подключите стратегию к инструменту и задайте CandleType с нужным таймфреймом.
Настройте объём и параметры в пипсах с учётом спецификаций брокера.
При необходимости включите фильтр торговых часов.
Запустите стратегию — она самостоятельно подпишется на свечи, применит логику входов и выставит защитные уровни.
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>
/// FT CCI MA strategy using EMA crossover with trend filter.
/// Buys when fast EMA crosses above slow EMA and price above trend EMA.
/// </summary>
public class FtCciMaStrategy : 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 FtCciMaStrategy()
{
_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 ft_cci_ma_strategy(Strategy):
def __init__(self):
super(ft_cci_ma_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(ft_cci_ma_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(ft_cci_ma_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 ft_cci_ma_strategy()