Ma Cci Strategy
Strategy combining Moving Average and CCI indicators. Buys when price is above MA and CCI is oversold. Sells when price is below MA and CCI is overbought.
Testing indicates an average annual return of about 49%. It performs best in the crypto market.
A moving average guides the trend while CCI looks for deviations from that average. Entries happen on CCI extremes in the direction of the MA.
Ideal for swing traders entering on pullbacks. ATR stops guard against sudden whipsaws.
Details
- Entry Criteria:
- Long:
Close > MA && CCI < OversoldLevel - Short:
Close < MA && CCI > OverboughtLevel
- Long:
- Long/Short: Both
- Exit Criteria:
- CCI returns to zero line
- Stops: Percent-based using
StopLossPercent - Default Values:
MaPeriod= 20CciPeriod= 20OverboughtLevel= 100mOversoldLevel= -100mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: Moving Average, CCI
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Strategy combining Moving Average and CCI indicators.
/// Buys when price is above MA and CCI is oversold.
/// Sells when price is below MA and CCI is overbought.
/// </summary>
public class MaCciStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<int> _cooldownBars;
private decimal _cciValue;
private int _cooldown;
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// MA period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// CCI period.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// CCI overbought level.
/// </summary>
public decimal OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
/// <summary>
/// CCI oversold level.
/// </summary>
public decimal OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initialize strategy.
/// </summary>
public MaCciStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetRange(10, 50)
.SetDisplay("MA Period", "Period for Moving Average", "Indicators");
_cciPeriod = Param(nameof(CciPeriod), 20)
.SetRange(10, 30)
.SetDisplay("CCI Period", "Period for CCI calculation", "Indicators");
_overboughtLevel = Param(nameof(OverboughtLevel), 100m)
.SetDisplay("Overbought Level", "CCI level considered overbought", "Trading Levels");
_oversoldLevel = Param(nameof(OversoldLevel), -100m)
.SetDisplay("Oversold Level", "CCI level considered oversold", "Trading Levels");
_cooldownBars = Param(nameof(CooldownBars), 100)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(5, 500);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cciValue = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = MaPeriod };
var cci = new CommodityChannelIndex { Length = CciPeriod };
var subscription = SubscribeCandles(CandleType);
// CCI takes candle input - use BindEx as side handler
subscription.BindEx(cci, OnCci);
// EMA for main logic
subscription
.Bind(ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
var cciArea = CreateChartArea();
if (cciArea != null)
DrawIndicator(cciArea, cci);
}
}
private void OnCci(ICandleMessage candle, IIndicatorValue value)
{
if (!value.IsEmpty)
_cciValue = value.ToDecimal();
}
private void ProcessCandle(ICandleMessage candle, decimal maValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Buy: price above MA + CCI oversold
if (close > maValue && _cciValue < OversoldLevel && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Sell: price below MA + CCI overbought
else if (close < maValue && _cciValue > OverboughtLevel && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit long: price crosses below MA
if (Position > 0 && close < maValue)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit short: price crosses above MA
else if (Position < 0 && close > maValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
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, CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class ma_cci_strategy(Strategy):
"""
Strategy combining Moving Average and CCI indicators.
"""
def __init__(self):
super(ma_cci_strategy, self).__init__()
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._ma_period = self.Param("MaPeriod", 20) \
.SetRange(10, 50) \
.SetDisplay("MA Period", "Period for Moving Average", "Indicators")
self._cci_period = self.Param("CciPeriod", 20) \
.SetRange(10, 30) \
.SetDisplay("CCI Period", "Period for CCI calculation", "Indicators")
self._overbought_level = self.Param("OverboughtLevel", 100.0) \
.SetDisplay("Overbought Level", "CCI level considered overbought", "Trading Levels")
self._oversold_level = self.Param("OversoldLevel", -100.0) \
.SetDisplay("Oversold Level", "CCI level considered oversold", "Trading Levels")
self._cooldown_bars = self.Param("CooldownBars", 100) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General") \
.SetRange(5, 500)
self._cci_value = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(ma_cci_strategy, self).OnStarted2(time)
self._cci_value = 0.0
self._cooldown = 0
ema = ExponentialMovingAverage()
ema.Length = self._ma_period.Value
cci = CommodityChannelIndex()
cci.Length = self._cci_period.Value
subscription = self.SubscribeCandles(self.candle_type)
# CCI takes candle input - use BindEx as side handler
subscription.BindEx(cci, self.OnCci)
subscription.Bind(ema, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
cci_area = self.CreateChartArea()
if cci_area is not None:
self.DrawIndicator(cci_area, cci)
def OnCci(self, candle, value):
if not value.IsEmpty:
self._cci_value = float(value)
def ProcessCandle(self, candle, ma_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
mv = float(ma_value)
if self._cooldown > 0:
self._cooldown -= 1
return
cd = self._cooldown_bars.Value
ob = self._overbought_level.Value
os_level = self._oversold_level.Value
# Buy: price above MA + CCI oversold
if close > mv and self._cci_value < os_level and self.Position == 0:
self.BuyMarket()
self._cooldown = cd
elif close < mv and self._cci_value > ob and self.Position == 0:
self.SellMarket()
self._cooldown = cd
# Exit long: price crosses below MA
if self.Position > 0 and close < mv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > mv:
self.BuyMarket()
self._cooldown = cd
def OnReseted(self):
super(ma_cci_strategy, self).OnReseted()
self._cci_value = 0.0
self._cooldown = 0
def CreateClone(self):
return ma_cci_strategy()