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>Completed-candle momentum smoothed by a Jurik moving average.</summary>
public class ColorJMomentumStrategy : Strategy
{
private readonly StrategyParam<int> _momentumLength, _jmaLength;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent, _takeProfitPercent;
private readonly StrategyParam<bool> _enableStopLoss, _enableLong, _enableShort;
private JurikMovingAverage _jma;
private decimal? _previous, _beforePrevious;
private Order _pending;
public int MomentumLength { get => _momentumLength.Value; set => _momentumLength.Value = value; }
public int JmaLength { get => _jmaLength.Value; set => _jmaLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }
public bool EnableStopLoss { get => _enableStopLoss.Value; set => _enableStopLoss.Value = value; }
public bool EnableLong { get => _enableLong.Value; set => _enableLong.Value = value; }
public bool EnableShort { get => _enableShort.Value; set => _enableShort.Value = value; }
public ColorJMomentumStrategy()
{
_momentumLength = Param(nameof(MomentumLength), 8).SetGreaterThanZero()
.SetDisplay("Momentum Length", "Momentum lookback", "Indicator");
_jmaLength = Param(nameof(JmaLength), 8).SetGreaterThanZero()
.SetDisplay("JMA Length", "Smoothing length", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Signal candle timeframe", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 1m).SetNotNegative()
.SetDisplay("Stop Loss %", "Optional native percent risk distance", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 2m).SetNotNegative()
.SetDisplay("Take Profit %", "Native percent target distance", "Risk");
_enableStopLoss = Param(nameof(EnableStopLoss), true)
.SetDisplay("Enable Stop Loss", "Activate initial stop", "Risk");
_enableLong = Param(nameof(EnableLong), true)
.SetDisplay("Enable Long", "Allow long entries", "Trading");
_enableShort = Param(nameof(EnableShort), true)
.SetDisplay("Enable Short", "Allow short entries", "Trading");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_jma = null;
_previous = _beforePrevious = null;
_pending = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previous = _beforePrevious = null;
_pending = null;
var momentum = new Momentum { Length = MomentumLength };
_jma = new JurikMovingAverage { Length = JmaLength };
Indicators.Add(_jma);
SubscribeCandles(CandleType).Bind(momentum, ProcessCandle).Start();
StartProtection(
takeProfit: new Unit(TakeProfitPercent, UnitTypes.Percent),
stopLoss: new Unit(EnableStopLoss ? StopLossPercent : 0m, UnitTypes.Percent),
useMarketOrders: true);
}
private void ProcessCandle(ICandleMessage candle, decimal momentum)
{
if (candle.State != CandleStates.Finished)
return;
var result = _jma.Process(momentum, candle.OpenTime, true);
if (!result.IsFormed)
return;
var value = result.ToDecimal();
var up = _beforePrevious is decimal before && _previous is decimal previous
&& previous < before && value > previous;
var down = _beforePrevious is decimal beforeDown && _previous is decimal previousDown
&& previousDown > beforeDown && value < previousDown;
_beforePrevious = _previous;
_previous = value;
if (!IsFormedAndOnlineAndAllowTrading()
|| (_pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed)))
return;
if (up)
{
if (Position < 0m)
_pending = BuyMarket(-Position + (EnableLong ? Volume : 0m));
else if (Position == 0m && EnableLong)
_pending = BuyMarket(Volume);
}
else if (down)
{
if (Position > 0m)
_pending = SellMarket(Position + (EnableShort ? Volume : 0m));
else if (Position == 0m && EnableShort)
_pending = SellMarket(Volume);
}
}
}
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, OrderStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import Momentum, JurikMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import process_float
class color_j_momentum_strategy(Strategy):
"""Jurik-smoothed momentum signals on completed candles, with native protection."""
def __init__(self):
super(color_j_momentum_strategy, self).__init__()
self._momentum_length = self.Param("MomentumLength", 8).SetGreaterThanZero() \
.SetDisplay("Momentum Length", "Momentum lookback", "Indicator")
self._jma_length = self.Param("JmaLength", 8).SetGreaterThanZero() \
.SetDisplay("JMA Length", "Smoothing length", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Signal candle timeframe", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetNotNegative() \
.SetDisplay("Stop Loss %", "Native percent risk distance", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 2.0).SetNotNegative() \
.SetDisplay("Take Profit %", "Native percent target distance", "Risk")
self._enable_stop_loss = self.Param("EnableStopLoss", True) \
.SetDisplay("Enable Stop Loss", "Activate initial stop", "Risk")
self._enable_long = self.Param("EnableLong", True) \
.SetDisplay("Enable Long", "Allow long entries", "Trading")
self._enable_short = self.Param("EnableShort", True) \
.SetDisplay("Enable Short", "Allow short entries", "Trading")
self._reset_history()
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _reset_history(self):
self._jma = None
self._previous = self._before_previous = None
self._pending = None
def OnReseted(self):
super(color_j_momentum_strategy, self).OnReseted()
self._reset_history()
def OnStarted2(self, time):
super(color_j_momentum_strategy, self).OnStarted2(time)
self._reset_history()
momentum = Momentum()
momentum.Length = int(self._momentum_length.Value)
self._jma = JurikMovingAverage()
self._jma.Length = int(self._jma_length.Value)
self.Indicators.Add(self._jma)
self.SubscribeCandles(self.CandleType).Bind(momentum, self.ProcessCandle).Start()
self.StartProtection(
takeProfit=Unit(self._take_profit_percent.Value, UnitTypes.Percent),
stopLoss=Unit(self._stop_loss_percent.Value if self._enable_stop_loss.Value else 0.0, UnitTypes.Percent),
useMarketOrders=True)
def ProcessCandle(self, candle, momentum):
if candle.State != CandleStates.Finished:
return
result = process_float(self._jma, momentum, candle.OpenTime, True)
if not result.IsFormed:
return
value = float(result)
up = self._before_previous is not None and self._previous < self._before_previous and value > self._previous
down = self._before_previous is not None and self._previous > self._before_previous and value < self._previous
self._before_previous, self._previous = self._previous, value
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed):
return
if up:
if self.Position < 0:
self._pending = self.BuyMarket(-self.Position + (self.Volume if self._enable_long.Value else 0))
elif self.Position == 0 and self._enable_long.Value:
self._pending = self.BuyMarket(self.Volume)
elif down:
if self.Position > 0:
self._pending = self.SellMarket(self.Position + (self.Volume if self._enable_short.Value else 0))
elif self.Position == 0 and self._enable_short.Value:
self._pending = self.SellMarket(self.Volume)
def CreateClone(self):
return color_j_momentum_strategy()