Esta estrategia es un port directo del asesor experto de MetaTrader "FT CCI MA". Opera al cierre de cada vela terminada, combinando una media móvil ponderada lineal (LWMA) con umbrales del Commodity Channel Index (CCI) y un filtro de sesión de trading opcional. La implementación de StockSharp mantiene los mismos nombres de parámetros y valores por defecto, permitiéndote reproducir el comportamiento original mientras te beneficias de la API de alto nivel (suscripciones a velas, vinculación de indicadores, protección de posición).
Notas clave de diseño:
La LWMA trabaja sobre el precio ponderado (High + Low + 2 * Close) / 4, coincidiendo con el modo PRICE_WEIGHTED de MetaTrader.
El CCI usa el precio típico (High + Low + Close) / 3, como en PRICE_TYPICAL.
Todas las decisiones se evalúan en la barra recién cerrada, lo que refleja el EA original que esperaba al inicio de la siguiente barra antes de actuar sobre la anterior.
La protección de posición replica el take-profit y stop-loss del EA en unidades de pip.
Reglas de trading
Entradas largas
Precio de cierre por encima de la LWMA y CCI por debajo de CciLevelBuy (por defecto -100), o bien
Precio de cierre por debajo de la LWMA y CCI por debajo de CciLevelDown (por defecto -200).
Entrar solo si la posición neta actual es plana o corta.
Entradas cortas
Precio de cierre por debajo de la LWMA y CCI por encima de CciLevelSell (por defecto 100), o bien
Precio de cierre por encima de la LWMA y CCI por encima de CciLevelUp (por defecto 200).
Entrar solo si la posición neta actual es plana o larga.
Filtro de tiempo
Cuando UseTimeFilter está habilitado, la estrategia verifica la hora de candle.CloseTime.
Si la hora está fuera de la ventana activa, todas las posiciones y órdenes se cancelan/cierran inmediatamente.
Controles de riesgo
StartProtection establece distancias absolutas de stop-loss y take-profit usando el tamaño de pip derivado de Security.PriceStep.
El volumen de la orden se neta de modo que abrir en la dirección opuesta cierra automáticamente la exposición anterior.
Parámetros
Nombre
Descripción
Por defecto
OrderVolume
Tamaño del trade en lotes.
1
StopLossPips
Distancia de stop-loss expresada en pips (0 deshabilita).
150
TakeProfitPips
Distancia de take-profit en pips (0 deshabilita).
150
UseTimeFilter
Habilita el filtro de sesión.
true
StartHour
Hora de inicio de la sesión en tiempo de exchange (0-23).
10
EndHour
Hora de fin de la sesión en tiempo de exchange (0-23). Cuando es menor que la hora de inicio, la sesión cruza la medianoche.
5
CciPeriod
Longitud del Commodity Channel Index.
14
CciLevelUp
Umbral corto agresivo (+200).
200
CciLevelDown
Umbral largo agresivo (-200).
-200
CciLevelBuy
Umbral largo suave cuando el precio está por encima de la MA (-100).
-100
CciLevelSell
Umbral corto suave cuando el precio está por debajo de la MA (+100).
100
MaPeriod
Longitud de la LWMA.
200
MaShift
Desplazamiento horizontal de la LWMA en barras. La vela actual se compara con el valor MaShift barras atrás.
0
CandleType
Tipo de datos de vela/marco temporal usado para los cálculos.
1 hour time frame
Detalles de implementación
Cálculo de pip – El tamaño de pip es igual a Security.PriceStep. Para símbolos forex de 3 o 5 decimales se multiplica por 10 para traducir 0.00001 al pip 0.0001 usado por el EA.
Filtro de sesión – Implementa los dos escenarios del código fuente MQL: ventanas intradía (StartHour < EndHour) y ventanas nocturnas (StartHour > EndHour). Cuando StartHour == EndHour, el trading está deshabilitado, coincidiendo con la lógica original.
Vinculación de indicadores – Usa SubscribeCandles().Bind(...) para que el CCI y la LWMA reciban actualizaciones automáticas sin buffering manual. Los valores se almacenan solo para soportar el desplazamiento opcional de la LWMA.
Gestión de órdenes – CancelActiveOrders() se ejecuta antes de cada orden de mercado, reflejando el comportamiento del EA de mantener un libro de órdenes limpio.
Sin versión Python – Solo se proporciona la estrategia C#, según lo solicitado.
Uso
Adjuntar la estrategia a un instrumento y configurar CandleType al marco temporal deseado.
Elegir el volumen y los parámetros de pip apropiados para el instrumento (recordar alinear las definiciones de pip del broker con la conversión incorporada).
Habilitar o deshabilitar el filtro de sesión según las horas de trading.
Iniciar la estrategia; se suscribirá a velas, aplicará la lógica del indicador y gestionará órdenes/stops automáticamente.
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()