StockSharp/StockSharp · error · ArgumentException

Indicator cannot be composite.

Error message

Indicator cannot be composite.

What it means

Thrown by ValidateIndicators when an indicator argument implements IComplexIndicator (line 592-593). The scalar Bind/BindWithEmpty overloads extract exactly one decimal per indicator via indicator.Source, which is meaningless for a composite that emits several values (e.g. Bollinger Bands: UpBand/LowBand/MovingAverage), so composites are explicitly rejected with 'Indicator cannot be composite.'

Source

Thrown at Algo.Strategies/Strategy_HighLevelSubscriptions.cs:593

				iv1.ToDecimal(indicator1.Source),
				iv2.ToDecimal(indicator2.Source),
				iv3.ToDecimal(indicator3.Source),
				iv4.ToDecimal(indicator4.Source),
				iv5.ToDecimal(indicator5.Source),
				iv6.ToDecimal(indicator6.Source),
				iv7.ToDecimal(indicator7.Source),
				iv8.ToDecimal(indicator8.Source)), false);
		}

		private static void ValidateIndicators(params IIndicator[] indicators)
		{
			foreach (var ind in indicators)
			{
				if (ind is null)
					throw new ArgumentNullException(nameof(indicators));

				if (ind is IComplexIndicator)
					throw new ArgumentException(LocalizedStrings.IndicatorNotComposite, nameof(indicators));
			}
		}

		public ISubscriptionHandler<T> Bind(IIndicator[] indicators, Action<T, decimal[]> callback)
		{
			if (callback is null)
				throw new ArgumentNullException(nameof(callback));

			if (indicators is null)
				throw new ArgumentNullException(nameof(indicators));

			if (indicators.Any(i => i is IComplexIndicator))
				throw new ArgumentException(LocalizedStrings.IndicatorNotComposite, nameof(indicators));

			return BindEx(indicators, (v, ivs) => callback(v, [.. ivs.Select((val, idx) => val.ToDecimal(indicators[idx].Source))]), false);
		}

		public ISubscriptionHandler<T> BindWithEmpty(IIndicator[] indicators, Action<T, decimal?[]> callback)

View on GitHub (pinned to 601a191de6)

Solutions

  1. Bind one of the composite's inner child indicators instead (e.g. bb.UpBand, bb.LowBand, or bb.MovingAverage), which are themselves non-composite IIndicator.
  2. Use BindEx(IIndicator, Action<T, IIndicatorValue>, bool) to receive the full IIndicatorValue and read whichever output you need.
  3. For multiple child outputs at once, use BindEx(IIndicator[], ...) and downcast each IIndicatorValue to the composite's typed value interface.

Example fix

// before
var bb = new BollingerBands();
handler.Bind(bb, (c, v) => OnPrice(c, v)); // ArgumentException: Indicator cannot be composite.

// after - bind a single inner band
handler.Bind(bb.UpBand, (c, v) => OnPrice(c, v));

// or read full composite value via BindEx
handler.BindEx(bb, (c, iv) => {
    var bv = (IBollingerBandsValue)iv;
    OnBands(c, bv.UpBand, bv.LowBand, bv.MovingAverage);
}, allowEmpty: true);
Defensive patterns

Strategy: type-guard

Validate before calling

// Reject composites before calling a scalar Bind overload.
static void EnsureScalar(params IIndicator[] inds)
{
    foreach (var i in inds)
        if (i is IComplexIndicator)
            throw new InvalidOperationException($"{i?.GetType().Name} is composite; bind an inner child or use BindEx.");
}

EnsureScalar(ind1, ind2);
handler.Bind(ind1, ind2, cb);

Type guard

static bool IsScalarIndicator(IIndicator i)
    => i is not null && i is not IComplexIndicator;

Prevention

When it happens

Trigger: Passing any BaseComplexIndicator subclass directly into a positional Bind/BindWithEmpty: BollingerBands, StochasticOscillator, Ichimoku, Alligator, KeltnerChannels, DonchianChannels, Envelope, AverageDirectionalIndex, and ~30 others.

Common situations: Treating a composite indicator like a single-value indicator; assuming the fluent binder will pick a default band; upgrading a strategy from a single SMA to Bollinger Bands without switching to BindEx.

Related errors


AI-assisted analysis of StockSharp/StockSharp@601a191de6 (2026-08-13). Data as JSON: /api/errors/e0586f7c5df99b67. Report an issue: GitHub.