JMA Quantum Edge Strategy
This strategy uses the Jurik Moving Average (JMA) to detect turning points. A long position is opened when the JMA turns up and is above the higher timeframe JMA. A short position is opened when the JMA turns down and is below the higher timeframe JMA. Optional stop loss and take profit are applied as percentages from the entry price.
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>
/// JMA-based strategy detecting turning points with higher timeframe filter.
/// Enters long when JMA turns up and is above the higher timeframe JMA.
/// Enters short when JMA turns down and is below the higher timeframe JMA.
/// Applies optional stop loss and take profit.
/// </summary>
public class JmaQuantumEdgeStrategy : Strategy
{
private readonly StrategyParam<int> _jmaLength;
private readonly StrategyParam<int> _higherJmaLength;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DataType> _higherCandleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<bool> _enableStopLoss;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<int> _cooldownBars;
private decimal? _prevJma;
private decimal? _prevPrevJma;
private decimal? _higherJma;
private int _barsSinceSignal;
/// <summary>
/// Main JMA period length.
/// </summary>
public int JmaLength
{
get => _jmaLength.Value;
set => _jmaLength.Value = value;
}
/// <summary>
/// Higher timeframe JMA period length.
/// </summary>
public int HigherJmaLength
{
get => _higherJmaLength.Value;
set => _higherJmaLength.Value = value;
}
/// <summary>
/// Candle type for main timeframe.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Candle type for higher timeframe.
/// </summary>
public DataType HigherCandleType
{
get => _higherCandleType.Value;
set => _higherCandleType.Value = value;
}
/// <summary>
/// Stop loss percent.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Enable stop loss.
/// </summary>
public bool EnableStopLoss
{
get => _enableStopLoss.Value;
set => _enableStopLoss.Value = value;
}
/// <summary>
/// Take profit percent.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Minimum number of finished bars between signals.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public JmaQuantumEdgeStrategy()
{
_jmaLength = Param(nameof(JmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("JMA Length", "Period for main JMA", "Parameters")
.SetOptimize(10, 50, 10);
_higherJmaLength = Param(nameof(HigherJmaLength), 40)
.SetGreaterThanZero()
.SetDisplay("Higher JMA Length", "Period for higher timeframe JMA", "Parameters")
.SetOptimize(20, 100, 10);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Main timeframe", "Parameters");
_higherCandleType = Param(nameof(HigherCandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Higher Candle Type", "Higher timeframe", "Parameters");
_stopLossPercent = Param(nameof(StopLossPercent), 1m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percent", "Risk Management")
.SetOptimize(0.5m, 3m, 0.5m);
_enableStopLoss = Param(nameof(EnableStopLoss), true)
.SetDisplay("Enable Stop Loss", "Use stop loss", "Risk Management");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Take Profit %", "Take profit percent", "Risk Management")
.SetOptimize(1m, 5m, 1m);
_cooldownBars = Param(nameof(CooldownBars), 360)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Minimum bars between signals", "Risk Management");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevJma = null;
_prevPrevJma = null;
_higherJma = null;
_barsSinceSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var jma = new JurikMovingAverage { Length = JmaLength };
var higherJma = new JurikMovingAverage { Length = HigherJmaLength };
var candleSub = SubscribeCandles(CandleType);
candleSub
.Bind(jma, ProcessCandle)
.Start();
SubscribeCandles(HigherCandleType)
.Bind(higherJma, ProcessHigherCandle)
.Start();
}
private void ProcessHigherCandle(ICandleMessage candle, decimal jmaValue)
{
if (candle.State != CandleStates.Finished)
return;
_higherJma = jmaValue;
}
private void ProcessCandle(ICandleMessage candle, decimal jmaValue)
{
if (candle.State != CandleStates.Finished)
return;
_barsSinceSignal++;
if (_prevJma is decimal prev && _prevPrevJma is decimal prev2 && _higherJma is decimal higher)
{
var turnUp = prev < prev2 && jmaValue >= prev;
var turnDown = prev > prev2 && jmaValue <= prev;
if (_barsSinceSignal >= CooldownBars)
{
if (turnUp && jmaValue > higher)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
else if (Position == 0)
BuyMarket();
_barsSinceSignal = 0;
}
else if (turnDown && jmaValue < higher)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
else if (Position == 0)
SellMarket();
_barsSinceSignal = 0;
}
}
}
_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, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import JurikMovingAverage
from StockSharp.Algo.Strategies import Strategy
class jma_quantum_edge_strategy(Strategy):
def __init__(self):
super(jma_quantum_edge_strategy, self).__init__()
self._jma_length = self.Param("JmaLength", 20) \
.SetDisplay("JMA Length", "Period for main JMA", "Parameters")
self._higher_jma_length = self.Param("HigherJmaLength", 40) \
.SetDisplay("Higher JMA Length", "Period for higher timeframe JMA", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Main timeframe", "Parameters")
self._higher_candle_type = self.Param("HigherCandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60))) \
.SetDisplay("Higher Candle Type", "Higher timeframe", "Parameters")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0) \
.SetDisplay("Stop Loss %", "Stop loss percent", "Risk Management")
self._enable_stop_loss = self.Param("EnableStopLoss", True) \
.SetDisplay("Enable Stop Loss", "Use stop loss", "Risk Management")
self._take_profit_percent = self.Param("TakeProfitPercent", 2.0) \
.SetDisplay("Take Profit %", "Take profit percent", "Risk Management")
self._cooldown_bars = self.Param("CooldownBars", 360) \
.SetDisplay("Cooldown Bars", "Minimum bars between signals", "Risk Management")
self._prev_jma = None
self._prev_prev_jma = None
self._higher_jma = None
self._bars_since_signal = 0
@property
def jma_length(self):
return self._jma_length.Value
@property
def higher_jma_length(self):
return self._higher_jma_length.Value
@property
def candle_type(self):
return self._candle_type.Value
@property
def higher_candle_type(self):
return self._higher_candle_type.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnReseted(self):
super(jma_quantum_edge_strategy, self).OnReseted()
self._prev_jma = None
self._prev_prev_jma = None
self._higher_jma = None
self._bars_since_signal = 0
def OnStarted2(self, time):
super(jma_quantum_edge_strategy, self).OnStarted2(time)
jma = JurikMovingAverage()
jma.Length = self.jma_length
higher_jma = JurikMovingAverage()
higher_jma.Length = self.higher_jma_length
candle_sub = self.SubscribeCandles(self.candle_type)
candle_sub.Bind(jma, self._process_candle).Start()
self.SubscribeCandles(self.higher_candle_type) \
.Bind(higher_jma, self._process_higher_candle).Start()
def _process_higher_candle(self, candle, jma_value):
if candle.State != CandleStates.Finished:
return
self._higher_jma = float(jma_value)
def _process_candle(self, candle, jma_value):
if candle.State != CandleStates.Finished:
return
jma_val = float(jma_value)
self._bars_since_signal += 1
if self._prev_jma is not None and self._prev_prev_jma is not None and self._higher_jma is not None:
prev = self._prev_jma
prev2 = self._prev_prev_jma
higher = self._higher_jma
turn_up = prev < prev2 and jma_val >= prev
turn_down = prev > prev2 and jma_val <= prev
if self._bars_since_signal >= int(self.cooldown_bars):
if turn_up and jma_val > higher:
if self.Position < 0:
self.BuyMarket(abs(self.Position))
elif self.Position == 0:
self.BuyMarket()
self._bars_since_signal = 0
elif turn_down and jma_val < higher:
if self.Position > 0:
self.SellMarket(abs(self.Position))
elif self.Position == 0:
self.SellMarket()
self._bars_since_signal = 0
self._prev_prev_jma = self._prev_jma
self._prev_jma = jma_val
def CreateClone(self):
return jma_quantum_edge_strategy()