Стратегия MA CCI
Стратегия объединяет скользящую среднюю и индикатор CCI. Покупка совершается, когда цена выше MA и CCI в зоне перепроданности. Продажа — когда цена ниже MA и CCI в зоне перекупленности.
Тестирование показывает среднегодичную доходность около 49%. Стратегию лучше запускать на крипторынке.
Скользящая средняя задаёт тренд, а CCI ищет отклонения от этого среднего значения. Входы происходят на экстремальных значениях CCI по направлению MA.
Подходит свинг-трейдерам, входящим на откатах. Процентный стоп защищает от резких разворотов.
Подробности
- Условия входа:
- Лонг:
Close > MA && CCI < OversoldLevel - Шорт:
Close < MA && CCI > OverboughtLevel
- Лонг:
- Длинные/короткие: обе стороны
- Условия выхода:
- CCI возвращается к нулевой линии
- Стопы: процентные через
StopLossPercent - Значения по умолчанию:
MaPeriod= 50CciPeriod= 20OverboughtLevel= 100mOversoldLevel= -100mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame() SMA за 20 свечей движется вместе с CCI за те же 20 свечей, поэтому цена почти никогда не держится выше неё при CCI ниже -100: в архиве за март 2024 года таких свечей не больше одной на инструмент. Поэтому трендовая SMA по умолчанию строится по 50 свечам. Сигнал на вход против открытой позиции разворачивает её.
- Фильтры:
- Категория: Средняя обратная
- Направление: Оба
- Индикаторы: Скользящая средняя, CCI
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Среднесрочный
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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>
/// MA CCI strategy.
/// A close above the MaPeriod SMA with CCI below OversoldLevel goes long and a close below it with CCI above OverboughtLevel goes short,
/// reversing an opposite position. The position closes once CCI returns to the zero line, and a percent stop limits the loss.
/// </summary>
public class MaCciStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// Period of the trend SMA.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Period of CCI.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// CCI level for shorts.
/// </summary>
public decimal OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
/// <summary>
/// CCI level for longs.
/// </summary>
public decimal OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MaCciStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period of the trend SMA", "Indicators");
_cciPeriod = Param(nameof(CciPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "Period of CCI", "Indicators");
_overboughtLevel = Param(nameof(OverboughtLevel), 100m)
.SetDisplay("Overbought Level", "CCI level for shorts", "Indicators");
_oversoldLevel = Param(nameof(OversoldLevel), -100m)
.SetDisplay("Oversold Level", "CCI level for longs", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = MaPeriod };
var cci = new CommodityChannelIndex { Length = CciPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(sma, cci, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, cci);
}
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaValue, IIndicatorValue cciValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!smaValue.IsFormed || !cciValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ma = smaValue.GetValue<decimal>();
var cci = cciValue.GetValue<decimal>();
var close = candle.ClosePrice;
if (close > ma && cci < OversoldLevel && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (close < ma && cci > OverboughtLevel && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && cci >= 0)
SellMarket(Position);
else if (Position < 0 && cci <= 0)
BuyMarket(-Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import SimpleMovingAverage, CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class ma_cci_strategy(Strategy):
"""
MA CCI strategy.
A close above the MaPeriod SMA with CCI below OversoldLevel goes long and a close below it with CCI above OverboughtLevel goes short,
reversing an opposite position. The position closes once CCI returns to the zero line, and a percent stop limits the loss.
"""
def __init__(self):
super(ma_cci_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 50).SetGreaterThanZero().SetDisplay("MA Period", "Period of the trend SMA", "Indicators")
self._cci_period = self.Param("CciPeriod", 20).SetGreaterThanZero().SetDisplay("CCI Period", "Period of CCI", "Indicators")
self._overbought_level = self.Param("OverboughtLevel", 100.0).SetDisplay("Overbought Level", "CCI level for shorts", "Indicators")
self._oversold_level = self.Param("OversoldLevel", -100.0).SetDisplay("Oversold Level", "CCI level for longs", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(ma_cci_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
cci = CommodityChannelIndex()
cci.Length = self._cci_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(sma, cci, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, cci)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, sma_value, cci_value):
if candle.State != CandleStates.Finished:
return
if not sma_value.IsFormed or not cci_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
ma = sma_value.GetValue[Decimal](None)
cci = cci_value.GetValue[Decimal](None)
close = candle.ClosePrice
zero = Decimal(0)
if close > ma and cci < Decimal(self._oversold_level.Value) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif close < ma and cci > Decimal(self._overbought_level.Value) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and cci >= zero:
self.SellMarket(self.Position)
elif self.Position < 0 and cci <= zero:
self.BuyMarket(-self.Position)
def CreateClone(self):
return ma_cci_strategy()