Открыть на GitHub

FT CCI MA (порт на StockSharp)

Обзор

Стратегия является прямым портом советника MetaTrader "FT CCI MA". Торговые решения принимаются на закрытии каждой свечи, используя линейно-взвешенную скользящую среднюю (LWMA) в сочетании с порогами индикатора CCI и необязательным фильтром торговой сессии. Реализация на StockSharp сохраняет исходные параметры и их значения по умолчанию, но опирается на высокоуровневый API (подписка на свечи, привязка индикаторов, управление защитой позиции).

Особенности реализации:

  • LWMA рассчитывается по взвешенной цене (High + Low + 2 * Close) / 4, что соответствует режиму PRICE_WEIGHTED в MetaTrader.
  • CCI использует типичную цену (High + Low + Close) / 3, как в PRICE_TYPICAL.
  • Сигналы оцениваются по завершённой свече — то же поведение, что и в оригинале, где сделки открывались в начале следующей свечи на данных предыдущей.
  • Защитные ордера воспроизводят тейк-профит и стоп-лосс в пипсах.

Правила торговли

  1. Вход в длинную позицию
    • Цена закрытия выше LWMA и CCI ниже CciLevelBuy (по умолчанию -100), или
    • Цена закрытия ниже LWMA и CCI ниже CciLevelDown (по умолчанию -200).
    • Сделка возможна только при нулевой или короткой текущей позиции.
  2. Вход в короткую позицию
    • Цена закрытия ниже LWMA и CCI выше CciLevelSell (по умолчанию 100), или
    • Цена закрытия выше LWMA и CCI выше CciLevelUp (по умолчанию 200).
    • Сделка возможна только при нулевой или длинной текущей позиции.
  3. Фильтр по времени
    • При включенном UseTimeFilter стратегия проверяет час из candle.CloseTime.
    • Если текущий час вне допустимого интервала, все позиции и активные заявки немедленно закрываются.
  4. Управление рисками
    • 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#-реализация.

Использование

  1. Подключите стратегию к инструменту и задайте CandleType с нужным таймфреймом.
  2. Настройте объём и параметры в пипсах с учётом спецификаций брокера.
  3. При необходимости включите фильтр торговых часов.
  4. Запустите стратегию — она самостоятельно подпишется на свечи, применит логику входов и выставит защитные уровни.
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;
	}
}