Стратегия CCI and Martin ищет резкий разворот после короткой серии однонаправленных свечей и подтверждает сигнал индикатором Commodity Channel Index. Логика повторяет оригинальный советник MetaTrader 5, но использует высокоуровневый API StockSharp. Алгоритм работает только с закрытыми свечами и может применяться к любым инструментам, для которых доступны CCI и шаг цены.
Правила входа
Покупка
Свечи -2 и -1 должны быть медвежьими (цена открытия выше цены закрытия).
Свеча 0 должна закрыться выше своей цены открытия и выше цены открытия свечи -1.
CCI на свече -1 должен быть ниже +5, ниже значения свечи -2, а CCI на свечах -2 и -3 должен последовательно снижаться. Текущее значение CCI (свеча 0) должно развернуться вверх и превысить предыдущий уровень.
Если все условия выполняются и позиция отсутствует, открывается длинная позиция.
Продажа
Свечи -2 и -1 должны быть бычьими (цена открытия ниже цены закрытия).
Свеча 0 должна закрыться ниже своей цены открытия и ниже цены открытия свечи -1.
CCI на свече -1 должен быть выше -5, выше значения свечи -2, а CCI на свечах -2 и -3 должен последовательно расти. Текущее значение CCI (свеча 0) должно развернуться вниз и опуститься ниже предыдущего уровня.
Если все условия выполняются и позиция отсутствует, открывается короткая позиция.
Изначально советник ждал 40 секунд после начала новой минуты, чтобы исключить «сырые» значения. В реализации на StockSharp анализируются только завершённые свечи, поэтому дополнительная задержка не требуется.
Управление рисками
Стоп-лосс и тейк-профит задаются в пунктах. При расчёте они переводятся в абсолютное смещение по цене: шаг цены умножается на десять для инструментов с трёх- и пятизначной котировкой, что соответствует оригинальной логике расчёта пункта.
Трейлинг-стоп активируется, когда цена проходит расстояние, равное сумме трейлинг-стопа и шага. После активации стоп переносится вслед за ценой на величину трейлинг-стопа и обновляется только при движении больше заданного шага.
Нулевые значения стоп-лосса или тейк-профита отключают соответствующие выходы. Для работы трейлинга необходимо указать положительные значения и дистанции, и шага.
Управление объёмом
Доступны два независимых механизма изменения объёма позиции.
Мартингейл умножает текущий объём на заданный коэффициент, когда количество последовательных убыточных сделок достигает порога. Увеличение объёма ограничено максимальным числом шагов. Любая прибыльная сделка возвращает объём к исходному значению.
Пошаговое увеличение прибавляет фиксированное значение к объёму либо после убыточной сделки, либо после прибыльной (режим задаётся параметром). Прирост нормализуется по шагу объёма инструмента и ограничивается максимальным объёмом. При превышении лимита или отсутствии сигнала объём возвращается к стартовому уровню.
Как и в оригинальном советнике, одновременное включение мартингейла и пошагового увеличения запрещено и контролируется кодом.
Параметры
CandleType – тип свечей для анализа.
CciPeriod – период сглаживания индикатора CCI.
InitialVolume – базовый объём сделки.
StopLossPips – стоп-лосс в пунктах.
TakeProfitPips – тейк-профит в пунктах.
TrailingStopPips – дистанция трейлинг-стопа в пунктах (0 отключает трейлинг).
TrailingStepPips – минимальное улучшение цены для переноса трейлинг-стопа.
EnableMartingale – включить мартингейл после убыточных сделок.
MartingaleCoefficient – коэффициент умножения объёма при мартингейле.
MartingaleTriggerLosses – сколько подряд убыточных сделок требуется для активации.
MartingaleMaxSteps – максимальное количество шагов мартингейла.
EnableStepAdjustments – включить пошаговое изменение объёма.
StepVolumeIncrement – величина прибавки к объёму при срабатывании правила.
StepVolumeMax – верхний предел объёма для пошагового режима.
StepAdjustmentMode – режим срабатывания (после убытка или после прибыли).
Примечания
Предполагается, что рыночные заявки исполняются близко к указанной цене. Для имитации тикового трейлинга из оригинального советника стопы пересчитываются на каждой завершённой свече.
Если шаг цены инструмента отличается от стандартных валютных котировок, пересчёт пунктов всё равно выполняется, но денежная оценка пунктов может отличаться.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// CCI based strategy with martingale-style entry.
/// Buys when CCI crosses above oversold level, sells when CCI crosses below overbought level.
/// </summary>
public class CCIAndMartinStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<decimal> _oversold;
private decimal? _prevCci;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
public decimal Overbought
{
get => _overbought.Value;
set => _overbought.Value = value;
}
public decimal Oversold
{
get => _oversold.Value;
set => _oversold.Value = value;
}
public CCIAndMartinStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_cciPeriod = Param(nameof(CciPeriod), 27)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "CCI indicator length", "Indicators");
_overbought = Param(nameof(Overbought), 100m)
.SetDisplay("Overbought", "CCI overbought level", "Indicators");
_oversold = Param(nameof(Oversold), -100m)
.SetDisplay("Oversold", "CCI oversold level", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCci = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCci = null;
var cci = new CommodityChannelIndex { Length = CciPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(cci, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, cci);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevCci = cciValue;
return;
}
if (_prevCci == null)
{
_prevCci = cciValue;
return;
}
var prev = _prevCci.Value;
_prevCci = cciValue;
// Buy signal: CCI crosses above oversold level from below
if (prev < Oversold && cciValue >= Oversold && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Sell signal: CCI crosses below overbought level from above
else if (prev > Overbought && cciValue <= Overbought && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
}
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 CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class cci_and_martin_strategy(Strategy):
def __init__(self):
super(cci_and_martin_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._cci_period = self.Param("CciPeriod", 27) \
.SetDisplay("CCI Period", "CCI indicator length", "Indicators")
self._overbought = self.Param("Overbought", 100.0) \
.SetDisplay("Overbought", "CCI overbought level", "Indicators")
self._oversold = self.Param("Oversold", -100.0) \
.SetDisplay("Oversold", "CCI oversold level", "Indicators")
self._prev_cci = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def CciPeriod(self):
return self._cci_period.Value
@property
def Overbought(self):
return self._overbought.Value
@property
def Oversold(self):
return self._oversold.Value
def OnReseted(self):
super(cci_and_martin_strategy, self).OnReseted()
self._prev_cci = None
def OnStarted2(self, time):
super(cci_and_martin_strategy, self).OnStarted2(time)
self._prev_cci = None
cci = CommodityChannelIndex()
cci.Length = self.CciPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cci, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, cci)
self.DrawOwnTrades(area)
def _on_process(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
cv = float(cci_value)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_cci = cv
return
if self._prev_cci is None:
self._prev_cci = cv
return
prev = self._prev_cci
self._prev_cci = cv
ob = float(self.Overbought)
os_level = float(self.Oversold)
# Buy signal: CCI crosses above oversold level from below
if prev < os_level and cv >= os_level and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Sell signal: CCI crosses below overbought level from above
elif prev > ob and cv <= ob and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return cci_and_martin_strategy()