CCI Estratégia MA v1.5
Esta estratégia recria o consultor especialista MetaTrader "CCI_MA v1.5" dentro do StockSharp API de alto nível. O robô original espera que o índice de canal de commodities (CCI) cruze uma média móvel simples calculada nos próprios valores de CCI e usa um CCI secundário para supervisionar saídas em torno dos limites de ±100. A porta StockSharp mantém a mesma ordem de sinal, gerenciamento de dinheiro opcional e distâncias de parada/alvo baseadas em pontos, enquanto adapta tudo às assinaturas de velas e ligações de indicadores.
Como funciona
- Fonte de dados – Uma série de velas definidas pelo usuário (velas de 15 minutos por padrão) alimenta ambos os CCIs. Os indicadores leem o preço de fechamento da vela para espelhar a configuração
PRICE_CLOSE de MetaTrader.
- Indicadores principais – O
CommodityChannelIndex primário (parâmetro CciPeriod) fornece a leitura do impulso. Um SimpleMovingAverage com período MaPeriod é aplicado ao fluxo de valores CCI para formar a linha de gatilho. Um CCI (SignalCciPeriod) secundário supervisiona reversões de sobrecompra e sobrevenda em torno de ±100.
- Lógica de entrada – Uma negociação longa é aberta na barra após um cruzamento ascendente: a vela concluída anteriormente (
prevCci) deve ficar acima da média móvel CCI enquanto a vela anterior (prev2Cci) estava abaixo. Um sinal curto é o cruzamento simétrico para baixo. As posições opostas existentes são fechadas e invertidas adicionando o valor absoluto da posição atual ao novo tamanho da ordem, correspondendo ao comportamento da versão MQL.
- Lógica de saída – As posições compradas são liquidadas quando a supervisão CCI cai de acima de +100 para abaixo de +100 ou quando a primária CCI volta abaixo de sua média móvel (novamente avaliada nas duas velas finalizadas anteriormente). Os shorts saem nas condições inversas. As paradas de proteção emulam as distâncias baseadas em pontos de MetaTrader: a estratégia deriva um tamanho de pip do instrumento
PriceStep (multiplicando por 10 para cotações de três ou cinco dígitos) e compara os extremos da vela com entry ± distance em cada vela concluída.
- Dimensionamento da posição –
LotVolume define o tamanho base do pedido. Se UseMoneyManagement estiver ativado, a estratégia o multiplica por um fator inteiro igual a floor(balance / DepositPerLot), limitado por MaxMultiplier, reproduzindo a escada de depósito do consultor especialista. O volume do pedido está alinhado com as restrições do instrumento VolumeStep, MinVolume e MaxVolume antes do envio.
Parâmetros
- Tipo de vela – Tipo de dados de vela que alimenta todos os cálculos de indicadores.
- CCI Período – Duração do oscilador CCI primário.
- Período de saída CCI – Duração do CCI de supervisão usado para saídas de limite.
- CCI Período MA – Período da média móvel simples aplicada ao primário CCI.
- Volume do lote – Volume base de negociação antes do dimensionamento da gestão de dinheiro.
- Ativar gerenciamento de dinheiro – ativa o escalonamento do volume do lote com base em depósito.
- Depósito por lote – Incremento de saldo necessário para aumentar o multiplicador do lote em um (usado apenas quando o gerenciamento de dinheiro está ativo).
- Max Multiplier – Multiplicador máximo que a gestão de dinheiro pode atingir.
- Stop Loss (pips) – Distância em pips para o stop de proteção; definido como zero para desativar.
- Take Profit (pips) – Distância em pips para a meta de lucro; definido como zero para desativar.
A estratégia espera por duas velas totalmente fechadas antes de emitir a primeira ordem, para que as comparações cruzadas de duas barras correspondam exatamente à execução atrasada do especialista MQL. As verificações de stop-loss e take-profit são executadas em velas finalizadas usando seus extremos máximo/mínimo, o que se aproxima das ordens de proteção do lado do servidor de MetaTrader enquanto permanece dentro do StockSharp API de alto nível.
namespace StockSharp.Samples.Strategies;
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;
/// <summary>
/// Commodity Channel Index strategy converted from the MetaTrader "CCI_MA v1.5" expert advisor.
/// Uses a primary CCI with a manually computed SMA of CCI values as a signal line.
/// A secondary CCI provides overbought/oversold exit confirmation.
/// </summary>
public class CciMaV15Strategy : Strategy
{
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<int> _signalCciPeriod;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private CommodityChannelIndex _cci;
private CommodityChannelIndex _signalCci;
private readonly List<decimal> _cciHistory = new();
private decimal? _prevCciMa;
private decimal? _prevCci;
private decimal? _prevSignalCci;
/// <summary>
/// Primary CCI period.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// Secondary CCI period for exit signals.
/// </summary>
public int SignalCciPeriod
{
get => _signalCciPeriod.Value;
set => _signalCciPeriod.Value = value;
}
/// <summary>
/// SMA period applied to the primary CCI values.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Stop loss distance in absolute points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance in absolute points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public CciMaV15Strategy()
{
_cciPeriod = Param(nameof(CciPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "Length of the primary CCI", "CCI")
.SetOptimize(7, 35, 7);
_signalCciPeriod = Param(nameof(SignalCciPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Exit CCI Period", "Length of the secondary CCI", "CCI")
.SetOptimize(7, 35, 7);
_maPeriod = Param(nameof(MaPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("CCI MA Period", "SMA length applied to the CCI", "CCI")
.SetOptimize(3, 21, 3);
_stopLossPoints = Param(nameof(StopLossPoints), 500m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Protective stop distance in absolute points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
.SetNotNegative()
.SetDisplay("Take Profit", "Profit target distance in absolute points", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Market data series", "General");
Volume = 1;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cci = null;
_signalCci = null;
_cciHistory.Clear();
_prevCciMa = null;
_prevCci = null;
_prevSignalCci = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
_cci = new CommodityChannelIndex { Length = CciPeriod };
_signalCci = new CommodityChannelIndex { Length = SignalCciPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_cci, _signalCci, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var indArea = CreateChartArea();
if (indArea != null)
{
DrawIndicator(indArea, _cci);
DrawIndicator(indArea, _signalCci);
}
}
var tp = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
var sl = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
if (tp != null || sl != null)
StartProtection(tp, sl);
base.OnStarted2(time);
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue, decimal signalCciValue)
{
if (candle.State != CandleStates.Finished)
return;
// Maintain CCI history for manual SMA calculation
_cciHistory.Add(cciValue);
if (_cciHistory.Count > MaPeriod)
_cciHistory.RemoveAt(0);
// Compute SMA of CCI
decimal? cciMa = null;
if (_cciHistory.Count >= MaPeriod)
{
decimal sum = 0;
for (int i = 0; i < _cciHistory.Count; i++)
sum += _cciHistory[i];
cciMa = sum / _cciHistory.Count;
}
if (cciMa == null || _prevCci == null || _prevCciMa == null || _prevSignalCci == null)
{
_prevCci = cciValue;
_prevCciMa = cciMa;
_prevSignalCci = signalCciValue;
return;
}
// Exit logic: secondary CCI overbought/oversold reversal
if (Position > 0 && _prevSignalCci > 100 && signalCciValue <= 100)
{
SellMarket(Position);
}
else if (Position < 0 && _prevSignalCci < -100 && signalCciValue >= -100)
{
BuyMarket(Math.Abs(Position));
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevCci = cciValue;
_prevCciMa = cciMa;
_prevSignalCci = signalCciValue;
return;
}
// Entry: CCI crosses above its MA (buy) or below (sell)
if (_prevCci < _prevCciMa && cciValue > cciMa.Value && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
}
else if (_prevCci > _prevCciMa && cciValue < cciMa.Value && Position >= 0)
{
if (Position > 0)
SellMarket(Position);
SellMarket(Volume);
}
_prevCci = cciValue;
_prevCciMa = cciMa;
_prevSignalCci = signalCciValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Indicators import CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class cci_ma_v15_strategy(Strategy):
"""
CCI MA v1.5 strategy. Uses primary CCI with SMA signal line and secondary CCI for exits.
"""
def __init__(self):
super(cci_ma_v15_strategy, self).__init__()
self._cci_period = self.Param("CciPeriod", 14).SetDisplay("CCI Period", "Length of the primary CCI", "CCI")
self._signal_cci_period = self.Param("SignalCciPeriod", 14).SetDisplay("Exit CCI Period", "Length of the secondary CCI", "CCI")
self._ma_period = self.Param("MaPeriod", 9).SetDisplay("CCI MA Period", "SMA length applied to the CCI", "CCI")
self._stop_loss_points = self.Param("StopLossPoints", 500.0).SetDisplay("Stop Loss", "Protective stop distance in absolute points", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 500.0).SetDisplay("Take Profit", "Profit target distance in absolute points", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Market data series", "General")
self._cci_history = []
self._prev_cci_ma = None
self._prev_cci = None
self._prev_signal_cci = None
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(cci_ma_v15_strategy, self).OnReseted()
self._cci_history = []
self._prev_cci_ma = None
self._prev_cci = None
self._prev_signal_cci = None
def OnStarted2(self, time):
super(cci_ma_v15_strategy, self).OnStarted2(time)
cci = CommodityChannelIndex()
cci.Length = self._cci_period.Value
signal_cci = CommodityChannelIndex()
signal_cci.Length = self._signal_cci_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(cci, signal_cci, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
ind_area = self.CreateChartArea()
if ind_area is not None:
self.DrawIndicator(ind_area, cci)
self.DrawIndicator(ind_area, signal_cci)
tp = Unit(self._take_profit_points.Value, UnitTypes.Absolute) if self._take_profit_points.Value > 0 else None
sl = Unit(self._stop_loss_points.Value, UnitTypes.Absolute) if self._stop_loss_points.Value > 0 else None
if tp is not None or sl is not None:
self.StartProtection(tp, sl)
def on_process(self, candle, cci_value, signal_cci_value):
if candle.State != CandleStates.Finished:
return
self._cci_history.append(float(cci_value))
ma_period = self._ma_period.Value
if len(self._cci_history) > ma_period:
self._cci_history.pop(0)
cci_ma = None
if len(self._cci_history) >= ma_period:
cci_ma = sum(self._cci_history) / len(self._cci_history)
if cci_ma is None or self._prev_cci is None or self._prev_cci_ma is None or self._prev_signal_cci is None:
self._prev_cci = float(cci_value)
self._prev_cci_ma = cci_ma
self._prev_signal_cci = float(signal_cci_value)
return
# Exit logic
if self.Position > 0 and self._prev_signal_cci > 100 and float(signal_cci_value) <= 100:
self.SellMarket()
elif self.Position < 0 and self._prev_signal_cci < -100 and float(signal_cci_value) >= -100:
self.BuyMarket()
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_cci = float(cci_value)
self._prev_cci_ma = cci_ma
self._prev_signal_cci = float(signal_cci_value)
return
# Entry logic
if self._prev_cci < self._prev_cci_ma and float(cci_value) > cci_ma and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_cci > self._prev_cci_ma and float(cci_value) < cci_ma and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_cci = float(cci_value)
self._prev_cci_ma = cci_ma
self._prev_signal_cci = float(signal_cci_value)
def CreateClone(self):
return cci_ma_v15_strategy()