This strategy is a port of the MetaTrader expert Exp_ColorBulls. It relies on the Color Bulls indicator, which calculates the difference between the candle's high price and a moving average. The resulting value is smoothed by another moving average and displayed as a histogram with different colors for rising and falling values.
The strategy reacts to color changes of this histogram:
When the indicator switches from rising (green) to falling (magenta), a long position is opened.
When the indicator switches from falling to rising, a short position is opened.
Opposite positions are closed automatically before entering new ones.
Only completed candles are processed and market orders are used for entries and exits.
Parameters
Fast MA Length – period of the moving average applied to high prices.
Smooth Length – period of the moving average used to smooth the bulls value.
Candle Type – timeframe of candles used for calculations.
Notes
This example demonstrates integration of a custom indicator with the high‑level StockSharp API. Stop‑loss and take‑profit management is not included.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on Color Bulls indicator.
/// Opens long when bulls value switches from rising to falling.
/// Opens short when value switches from falling to rising.
/// </summary>
public class ColorBullsStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _smoothLength;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevValue;
private int _prevColor;
/// <summary>
/// Period for moving average applied to high prices.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Period for smoothing bulls value.
/// </summary>
public int SmoothLength
{
get => _smoothLength.Value;
set => _smoothLength.Value = value;
}
/// <summary>
/// Candle type used for calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes parameters.
/// </summary>
public ColorBullsStrategy()
{
_fastLength = Param(nameof(FastLength), 12)
.SetGreaterThanZero()
.SetDisplay("Fast MA Length", "Period of high price moving average", "Indicator")
.SetOptimize(5, 20, 1);
_smoothLength = Param(nameof(SmoothLength), 5)
.SetGreaterThanZero()
.SetDisplay("Smooth Length", "Period of smoothing moving average", "Indicator")
.SetOptimize(3, 15, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevValue = 0m;
_prevColor = 1;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var highMa = new ExponentialMovingAverage { Length = FastLength };
var bullsMa = new ExponentialMovingAverage { Length = SmoothLength };
Indicators.Add(highMa);
Indicators.Add(bullsMa);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var highInput = new DecimalIndicatorValue(highMa, candle.HighPrice, candle.OpenTime) { IsFinal = true };
var maValue = highMa.Process(highInput);
if (!highMa.IsFormed)
return;
var bulls = candle.HighPrice - maValue.ToDecimal();
var bullsInput = new DecimalIndicatorValue(bullsMa, bulls, candle.OpenTime) { IsFinal = true };
var smooth = bullsMa.Process(bullsInput).ToDecimal();
if (!bullsMa.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var color = smooth > _prevValue ? 0 : smooth < _prevValue ? 2 : 1;
if (_prevColor == 0 && color == 2)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
else if (_prevColor == 2 && color == 0)
{
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
_prevColor = color;
_prevValue = smooth;
}
}
}
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
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class color_bulls_strategy(Strategy):
def __init__(self):
super(color_bulls_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 12) \
.SetDisplay("Fast MA Length", "Period of high price moving average", "Indicator")
self._smooth_length = self.Param("SmoothLength", 5) \
.SetDisplay("Smooth Length", "Period of smoothing moving average", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_value = 0.0
self._prev_color = 1
self._high_ma = None
self._bulls_ma = None
@property
def fast_length(self):
return self._fast_length.Value
@property
def smooth_length(self):
return self._smooth_length.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(color_bulls_strategy, self).OnReseted()
self._prev_value = 0.0
self._prev_color = 1
self._high_ma = None
self._bulls_ma = None
def OnStarted2(self, time):
super(color_bulls_strategy, self).OnStarted2(time)
self._high_ma = ExponentialMovingAverage()
self._high_ma.Length = self.fast_length
self._bulls_ma = ExponentialMovingAverage()
self._bulls_ma.Length = self.smooth_length
self.Indicators.Add(self._high_ma)
self.Indicators.Add(self._bulls_ma)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
ma_value = process_float(self._high_ma, candle.HighPrice, candle.OpenTime, True)
if not self._high_ma.IsFormed:
return
bulls = float(candle.HighPrice) - float(ma_value)
smooth = float(process_float(self._bulls_ma, bulls, candle.OpenTime, True))
if not self._bulls_ma.IsFormed:
return
if smooth > self._prev_value:
color = 0
elif smooth < self._prev_value:
color = 2
else:
color = 1
if self._prev_color == 0 and color == 2:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif self._prev_color == 2 and color == 0:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
self._prev_color = color
self._prev_value = smooth
def CreateClone(self):
return color_bulls_strategy()