The strategy reproduces the logic of the MetaTrader 4 expert advisor Signal-COunt-with array.mq4.
It monitors Donchian channel extremes for a configurable set of price offsets and counts how often
the indicator output changes, becomes empty or returns to a signal value. The implementation keeps
the diagnostic focus of the original script: no trades are executed. Instead, the strategy prints
detailed statistics whenever a new high/low is registered or when per-candle logging is enabled.
Concept
Replace the original iCustom lookup of super_signals_v2_alert with a Donchian channel that
provides the highest high and lowest low over ChannelPeriod candles.
Evaluate a grid of offsets (GapStart, GapStep, GapCount) that emulate the multiple indicator
configurations tested by the MQL script.
For each offset track six counters that mirror the original arrays, including transitions into and
out of the sentinel value (2147483647 for empty upper readings and -2147483646 for empty lower
readings).
Output a textual table with the accumulated counters so the user can inspect how often each buffer
produces a fresh signal, returns to empty or leaves the zero default state.
Parameters
Parameter
Default
Description
CandleType
5-minute time frame
Candle series used for the Donchian calculations.
ChannelPeriod
24
Number of candles used to determine the Donchian highs and lows.
GapStart
0
First offset (in multiples of the price step) applied to the virtual signal values.
GapStep
1
Step size (in price steps) between consecutive offsets.
GapCount
8
Number of offsets to evaluate (matches the original 0..7 loop).
LogOnEachCandle
false
When enabled, forces logging after every finished candle.
Counters
Every offset maintains two rows: index 0 represents the upper Donchian buffer (bullish signal) and
index 1 represents the lower buffer (bearish signal). The following statistics are collected:
Changed – increments whenever the raw indicator value differs from the previous observation.
Empty – counts how often the buffer returned the positive sentinel (2147483647).
NegEmpty – counts occurrences of the negative sentinel (-2147483646), mainly for the lower buffer.
Zero – tracks transitions from the default zero state to any non-zero value.
NewFromEmpty – increments when a real price-based signal replaces the sentinel value.
BackToEmpty – increments when the buffer falls back to its sentinel after holding a non-sentinel value.
These counters correspond one-to-one with the arrays maintained in the original Expert Advisor
(GetInd_iCustom_changed, GetInd_iCustom_maxInt, GetInd_iCustom_minInt, etc.).
Logging
The strategy prints diagnostics through AddInfoLog in two situations:
Whenever the Donchian upper band rises or the lower band falls, indicating a fresh extreme.
Every finished candle when LogOnEachCandle is set to true.
Each log entry starts with the candle time and then lists the counters for each offset, making it easy
to compare behaviour across different virtual indicator configurations.
Usage Notes
Attach the strategy to any security; it relies only on historical candles and does not submit orders.
Adjust ChannelPeriod to match the volatility of the instrument you are studying. A longer period
mimics wider swing detection similar to the MT4 indicator.
Increase GapCount if you need to observe more offsets. The arrays resize automatically on start.
Combine the diagnostics with chart drawings (candles plus Donchian channel) to visually align the
printed statistics with market structure.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class SignalCountWithArrayStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cciPeriod;
private decimal? _prevCci;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }
public SignalCountWithArrayStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_cciPeriod = Param(nameof(CciPeriod), 14).SetGreaterThanZero().SetDisplay("CCI Period", "CCI lookback", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(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); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal cciVal)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) { _prevCci = cciVal; return; }
if (_prevCci == null) { _prevCci = cciVal; return; }
if (_prevCci.Value < 0m && cciVal >= 0m && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
else if (_prevCci.Value > 0m && cciVal <= 0m && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
_prevCci = cciVal;
}
}
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
from datatype_extensions import *
from indicator_extensions import *
class signal_count_with_array_strategy(Strategy):
def __init__(self):
super(signal_count_with_array_strategy, self).__init__()
self._cci_period = self.Param("CciPeriod", 14).SetGreaterThanZero().SetDisplay("CCI Period", "CCI lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(signal_count_with_array_strategy, self).OnReseted()
self._prev_cci = None
def OnStarted2(self, time):
super(signal_count_with_array_strategy, self).OnStarted2(time)
self._prev_cci = None
cci = CommodityChannelIndex()
cci.Length = self._cci_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(cci, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle, cci_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_cci = cci_val
return
if self._prev_cci is None:
self._prev_cci = cci_val
return
if self._prev_cci < 0 and cci_val >= 0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_cci > 0 and cci_val <= 0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_cci = cci_val
def CreateClone(self):
return signal_count_with_array_strategy()