ColorJFatl StDev Strategy
This strategy is a translation of the ColorJFatl_StDev expert advisor from MQL5 into the StockSharp API. It combines the Jurik Moving Average (JMA) with standard deviation bands to generate trading signals.
Strategy Logic
- Calculate the JMA on closing prices.
- Compute the standard deviation over a configurable period.
- Build two sets of dynamic bands using multipliers
K1andK2:upper1 = JMA + K1 * StdDevupper2 = JMA + K2 * StdDevlower1 = JMA - K1 * StdDevlower2 = JMA - K2 * StdDev
- Depending on the selected signal mode, the strategy opens or closes positions:
- Point – triggers when price crosses the bands.
- Direct – uses turning points of the JMA line.
- Without – disables the corresponding signal.
Parameters
| Name | Description |
|---|---|
CandleTimeFrame |
Time frame for candle data. |
JmaLength |
Period of the Jurik Moving Average. |
JmaPhase |
Phase for the JMA calculation. |
StdPeriod |
Period for standard deviation. |
K1 |
First deviation multiplier. |
K2 |
Second deviation multiplier. |
BuyOpenMode |
Mode for opening long positions. |
SellOpenMode |
Mode for opening short positions. |
BuyCloseMode |
Mode for closing long positions. |
SellCloseMode |
Mode for closing short positions. |
Usage
The strategy subscribes to candles of the specified time frame, processes JMA and standard deviation values, and automatically submits market orders based on the defined modes.
This implementation focuses on clarity and can serve as a starting point for further enhancements or custom risk management.
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>
/// Strategy based on Jurik moving average and standard deviation bands.
/// Opens and closes positions according to signal modes.
/// </summary>
public class ColorJFatlStDevStrategy : Strategy
{
public enum SignalModes
{
Point,
Direct,
Without
}
private readonly StrategyParam<int> _jmaLength;
private readonly StrategyParam<int> _jmaPhase;
private readonly StrategyParam<int> _stdPeriod;
private readonly StrategyParam<decimal> _k1;
private readonly StrategyParam<decimal> _k2;
private readonly StrategyParam<SignalModes> _buyOpenMode;
private readonly StrategyParam<SignalModes> _sellOpenMode;
private readonly StrategyParam<SignalModes> _buyCloseMode;
private readonly StrategyParam<SignalModes> _sellCloseMode;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevJma;
private decimal? _prevPrevJma;
public int JmaLength { get => _jmaLength.Value; set => _jmaLength.Value = value; }
public int JmaPhase { get => _jmaPhase.Value; set => _jmaPhase.Value = value; }
public int StdPeriod { get => _stdPeriod.Value; set => _stdPeriod.Value = value; }
public decimal K1 { get => _k1.Value; set => _k1.Value = value; }
public decimal K2 { get => _k2.Value; set => _k2.Value = value; }
public SignalModes BuyOpenMode { get => _buyOpenMode.Value; set => _buyOpenMode.Value = value; }
public SignalModes SellOpenMode { get => _sellOpenMode.Value; set => _sellOpenMode.Value = value; }
public SignalModes BuyCloseMode { get => _buyCloseMode.Value; set => _buyCloseMode.Value = value; }
public SignalModes SellCloseMode { get => _sellCloseMode.Value; set => _sellCloseMode.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ColorJFatlStDevStrategy()
{
_jmaLength = Param(nameof(JmaLength), 5)
.SetDisplay("JMA Length", "JMA period", "Indicators");
_jmaPhase = Param(nameof(JmaPhase), -100)
.SetDisplay("JMA Phase", "JMA phase", "Indicators");
_stdPeriod = Param(nameof(StdPeriod), 9)
.SetDisplay("Std Period", "Standard deviation period", "Indicators");
_k1 = Param(nameof(K1), 0.5m)
.SetDisplay("K1", "First deviation multiplier", "Parameters");
_k2 = Param(nameof(K2), 1.0m)
.SetDisplay("K2", "Second deviation multiplier", "Parameters");
_buyOpenMode = Param(nameof(BuyOpenMode), SignalModes.Point)
.SetDisplay("Buy Open", "Mode for opening long", "Signals");
_sellOpenMode = Param(nameof(SellOpenMode), SignalModes.Point)
.SetDisplay("Sell Open", "Mode for opening short", "Signals");
_buyCloseMode = Param(nameof(BuyCloseMode), SignalModes.Point)
.SetDisplay("Buy Close", "Mode for closing long", "Signals");
_sellCloseMode = Param(nameof(SellCloseMode), SignalModes.Point)
.SetDisplay("Sell Close", "Mode for closing short", "Signals");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevJma = null;
_prevPrevJma = null;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevJma = null;
_prevPrevJma = null;
var jma = new JurikMovingAverage { Length = JmaLength, Phase = JmaPhase };
var std = new StandardDeviation { Length = StdPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(jma, std, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, jma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal jmaValue, decimal stdValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_prevJma is null || _prevPrevJma is null)
{
_prevPrevJma = _prevJma;
_prevJma = jmaValue;
return;
}
if (stdValue == 0)
{
_prevPrevJma = _prevJma;
_prevJma = jmaValue;
return;
}
var upper1 = jmaValue + K1 * stdValue;
var upper2 = jmaValue + K2 * stdValue;
var lower1 = jmaValue - K1 * stdValue;
var lower2 = jmaValue - K2 * stdValue;
var buyOpen = false;
var sellOpen = false;
var buyClose = false;
var sellClose = false;
switch (BuyOpenMode)
{
case SignalModes.Point:
buyOpen = candle.ClosePrice > upper1 || candle.ClosePrice > upper2;
break;
case SignalModes.Direct:
buyOpen = jmaValue > _prevJma && _prevJma < _prevPrevJma;
break;
}
switch (SellOpenMode)
{
case SignalModes.Point:
sellOpen = candle.ClosePrice < lower1 || candle.ClosePrice < lower2;
break;
case SignalModes.Direct:
sellOpen = jmaValue < _prevJma && _prevJma > _prevPrevJma;
break;
}
switch (BuyCloseMode)
{
case SignalModes.Point:
buyClose = candle.ClosePrice < lower1 || candle.ClosePrice < lower2;
break;
case SignalModes.Direct:
buyClose = jmaValue > _prevJma;
break;
}
switch (SellCloseMode)
{
case SignalModes.Point:
sellClose = candle.ClosePrice > upper1 || candle.ClosePrice > upper2;
break;
case SignalModes.Direct:
sellClose = jmaValue < _prevJma;
break;
}
if (buyClose && Position > 0)
SellMarket();
else if (sellClose && Position < 0)
BuyMarket();
else if (buyOpen && Position <= 0)
BuyMarket();
else if (sellOpen && Position >= 0)
SellMarket();
_prevPrevJma = _prevJma;
_prevJma = jmaValue;
}
}
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 JurikMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
SIGNAL_POINT = 0
SIGNAL_DIRECT = 1
SIGNAL_WITHOUT = 2
class color_j_fatl_st_dev_strategy(Strategy):
def __init__(self):
super(color_j_fatl_st_dev_strategy, self).__init__()
self._jma_length = self.Param("JmaLength", 5) \
.SetDisplay("JMA Length", "JMA period", "Indicators")
self._jma_phase = self.Param("JmaPhase", -100) \
.SetDisplay("JMA Phase", "JMA phase", "Indicators")
self._std_period = self.Param("StdPeriod", 9) \
.SetDisplay("Std Period", "Standard deviation period", "Indicators")
self._k1 = self.Param("K1", 0.5) \
.SetDisplay("K1", "First deviation multiplier", "Parameters")
self._k2 = self.Param("K2", 1.0) \
.SetDisplay("K2", "Second deviation multiplier", "Parameters")
self._buy_open_mode = self.Param("BuyOpenMode", SIGNAL_POINT) \
.SetDisplay("Buy Open", "Mode for opening long", "Signals")
self._sell_open_mode = self.Param("SellOpenMode", SIGNAL_POINT) \
.SetDisplay("Sell Open", "Mode for opening short", "Signals")
self._buy_close_mode = self.Param("BuyCloseMode", SIGNAL_POINT) \
.SetDisplay("Buy Close", "Mode for closing long", "Signals")
self._sell_close_mode = self.Param("SellCloseMode", SIGNAL_POINT) \
.SetDisplay("Sell Close", "Mode for closing short", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._prev_jma = None
self._prev_prev_jma = None
@property
def jma_length(self):
return self._jma_length.Value
@property
def jma_phase(self):
return self._jma_phase.Value
@property
def std_period(self):
return self._std_period.Value
@property
def k1(self):
return self._k1.Value
@property
def k2(self):
return self._k2.Value
@property
def buy_open_mode(self):
return self._buy_open_mode.Value
@property
def sell_open_mode(self):
return self._sell_open_mode.Value
@property
def buy_close_mode(self):
return self._buy_close_mode.Value
@property
def sell_close_mode(self):
return self._sell_close_mode.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(color_j_fatl_st_dev_strategy, self).OnReseted()
self._prev_jma = None
self._prev_prev_jma = None
def OnStarted2(self, time):
super(color_j_fatl_st_dev_strategy, self).OnStarted2(time)
self._prev_jma = None
self._prev_prev_jma = None
jma = JurikMovingAverage()
jma.Length = self.jma_length
jma.Phase = self.jma_phase
std = StandardDeviation()
std.Length = self.std_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(jma, std, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, jma)
self.DrawOwnTrades(area)
def process_candle(self, candle, jma_value, std_value):
if candle.State != CandleStates.Finished:
return
jma_value = float(jma_value)
std_value = float(std_value)
if self._prev_jma is None or self._prev_prev_jma is None:
self._prev_prev_jma = self._prev_jma
self._prev_jma = jma_value
return
if std_value == 0:
self._prev_prev_jma = self._prev_jma
self._prev_jma = jma_value
return
k1_val = float(self.k1)
k2_val = float(self.k2)
upper1 = jma_value + k1_val * std_value
upper2 = jma_value + k2_val * std_value
lower1 = jma_value - k1_val * std_value
lower2 = jma_value - k2_val * std_value
close_price = float(candle.ClosePrice)
buy_open = False
sell_open = False
buy_close = False
sell_close = False
bom = int(self.buy_open_mode)
som = int(self.sell_open_mode)
bcm = int(self.buy_close_mode)
scm = int(self.sell_close_mode)
if bom == SIGNAL_POINT:
buy_open = close_price > upper1 or close_price > upper2
elif bom == SIGNAL_DIRECT:
buy_open = jma_value > self._prev_jma and self._prev_jma < self._prev_prev_jma
if som == SIGNAL_POINT:
sell_open = close_price < lower1 or close_price < lower2
elif som == SIGNAL_DIRECT:
sell_open = jma_value < self._prev_jma and self._prev_jma > self._prev_prev_jma
if bcm == SIGNAL_POINT:
buy_close = close_price < lower1 or close_price < lower2
elif bcm == SIGNAL_DIRECT:
buy_close = jma_value > self._prev_jma
if scm == SIGNAL_POINT:
sell_close = close_price > upper1 or close_price > upper2
elif scm == SIGNAL_DIRECT:
sell_close = jma_value < self._prev_jma
if buy_close and self.Position > 0:
self.SellMarket()
elif sell_close and self.Position < 0:
self.BuyMarket()
elif buy_open and self.Position <= 0:
self.BuyMarket()
elif sell_open and self.Position >= 0:
self.SellMarket()
self._prev_prev_jma = self._prev_jma
self._prev_jma = jma_value
def CreateClone(self):
return color_j_fatl_st_dev_strategy()