Estrategia ColorJFatl Digit ReOpen
Esta estrategia usa una Jurik Moving Average (JMA) para identificar la dirección de la tendencia. Se abre una posición larga cuando la JMA gira hacia arriba y se cierran todas las posiciones cortas. Se abre una posición corta cuando la JMA gira hacia abajo y se cierran todas las posiciones largas. Se agregan posiciones adicionales cada vez que el precio se mueve un número fijo de puntos en la dirección de la operación, hasta un máximo.
Detalles
- Entrada:
- JMA cambia dirección hacia arriba → abrir largo y cerrar cortos.
- JMA cambia dirección hacia abajo → abrir corto y cerrar largos.
- Re-entrada:
- Después de la posición inicial, se abren nuevas posiciones cada
PriceSteppuntos en la dirección de la operación hasta alcanzarMaxPositions.
- Después de la posición inicial, se abren nuevas posiciones cada
- Salida:
- El giro opuesto de la JMA cierra las posiciones actuales.
- Parámetros:
JmaLength– período de JMA.PriceStep– movimiento de precio en puntos requerido para re-entrada.MaxPositions– número máximo de posiciones simultáneas.BuyPosOpen,SellPosOpen,BuyPosClose,SellPosClose– habilitar o deshabilitar acciones.CandleType– marco temporal para cálculos.
- Indicador: Jurik Moving Average.
- Tipo: Seguimiento de tendencia.
- Marco temporal: 4 horas por defecto.
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>
/// Jurik moving average trend strategy with re-entry at fixed price steps.
/// Opens long when JMA turns up and closes short positions.
/// Opens short when JMA turns down and closes long positions.
/// Adds additional positions when price moves by a defined step in favor of the trade.
/// </summary>
public class ColorJFatlDigitReOpenStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _jmaLength;
private readonly StrategyParam<int> _priceStepParam;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<bool> _buyPosOpen;
private readonly StrategyParam<bool> _sellPosOpen;
private readonly StrategyParam<bool> _buyPosClose;
private readonly StrategyParam<bool> _sellPosClose;
private decimal? _prevJma;
private int _prevDirection;
private decimal? _lastEntryPrice;
private int _positionsOpened;
private decimal _priceStep;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Jurik moving average length.
/// </summary>
public int JmaLength { get => _jmaLength.Value; set => _jmaLength.Value = value; }
/// <summary>
/// Re-entry price step in points.
/// </summary>
public int PriceStep { get => _priceStepParam.Value; set => _priceStepParam.Value = value; }
/// <summary>
/// Maximum number of positions in one direction.
/// </summary>
public int MaxPositions { get => _maxPositions.Value; set => _maxPositions.Value = value; }
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool BuyPosOpen { get => _buyPosOpen.Value; set => _buyPosOpen.Value = value; }
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool SellPosOpen { get => _sellPosOpen.Value; set => _sellPosOpen.Value = value; }
/// <summary>
/// Allow closing long positions.
/// </summary>
public bool BuyPosClose { get => _buyPosClose.Value; set => _buyPosClose.Value = value; }
/// <summary>
/// Allow closing short positions.
/// </summary>
public bool SellPosClose { get => _sellPosClose.Value; set => _sellPosClose.Value = value; }
/// <summary>
/// Initialize strategy parameters.
/// </summary>
public ColorJFatlDigitReOpenStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for calculations", "General");
_jmaLength = Param(nameof(JmaLength), 5)
.SetGreaterThanZero()
.SetDisplay("JMA Length", "Jurik moving average length", "Indicators")
.SetOptimize(2, 20, 1);
_priceStepParam = Param(nameof(PriceStep), 300)
.SetGreaterThanZero()
.SetDisplay("Price Step", "Price step in points for re-entry", "Risk Management");
_maxPositions = Param(nameof(MaxPositions), 1)
.SetGreaterThanZero()
.SetDisplay("Max Positions", "Maximum number of positions", "Risk Management");
_buyPosOpen = Param(nameof(BuyPosOpen), true)
.SetDisplay("Open Long", "Allow long entries", "Trading");
_sellPosOpen = Param(nameof(SellPosOpen), true)
.SetDisplay("Open Short", "Allow short entries", "Trading");
_buyPosClose = Param(nameof(BuyPosClose), true)
.SetDisplay("Close Long", "Allow closing longs", "Trading");
_sellPosClose = Param(nameof(SellPosClose), true)
.SetDisplay("Close Short", "Allow closing shorts", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevJma = null;
_prevDirection = 0;
_lastEntryPrice = null;
_positionsOpened = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = (Security?.PriceStep ?? 0m) * PriceStep;
var jma = new JurikMovingAverage { Length = JmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(jma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, jma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal jmaValue)
{
if (candle.State != CandleStates.Finished)
return;
var direction = _prevJma is decimal prev ? (jmaValue > prev ? 1 : jmaValue < prev ? -1 : 0) : 0;
// close positions on opposite signals
if (direction == -1 && Position > 0 && BuyPosClose)
{
SellMarket();
_positionsOpened = 0;
_lastEntryPrice = null;
}
else if (direction == 1 && Position < 0 && SellPosClose)
{
BuyMarket();
_positionsOpened = 0;
_lastEntryPrice = null;
}
// initial entries
if (direction == 1 && _prevDirection != 1 && BuyPosOpen && Position <= 0)
{
BuyMarket();
_positionsOpened = 1;
_lastEntryPrice = candle.ClosePrice;
}
else if (direction == -1 && _prevDirection != -1 && SellPosOpen && Position >= 0)
{
SellMarket();
_positionsOpened = 1;
_lastEntryPrice = candle.ClosePrice;
}
// re-entry logic
else if (Position > 0 && BuyPosOpen && _positionsOpened < MaxPositions && _lastEntryPrice is decimal lastBuy && candle.ClosePrice - lastBuy >= _priceStep)
{
BuyMarket();
_positionsOpened++;
_lastEntryPrice = candle.ClosePrice;
}
else if (Position < 0 && SellPosOpen && _positionsOpened < MaxPositions && _lastEntryPrice is decimal lastSell && lastSell - candle.ClosePrice >= _priceStep)
{
SellMarket();
_positionsOpened++;
_lastEntryPrice = candle.ClosePrice;
}
_prevDirection = direction;
_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
from StockSharp.Algo.Strategies import Strategy
class color_j_fatl_digit_re_open_strategy(Strategy):
def __init__(self):
super(color_j_fatl_digit_re_open_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._jma_length = self.Param("JmaLength", 5)
self._price_step_param = self.Param("PriceStep", 300)
self._max_positions = self.Param("MaxPositions", 1)
self._buy_pos_open = self.Param("BuyPosOpen", True)
self._sell_pos_open = self.Param("SellPosOpen", True)
self._buy_pos_close = self.Param("BuyPosClose", True)
self._sell_pos_close = self.Param("SellPosClose", True)
self._prev_jma = None
self._prev_direction = 0
self._last_entry_price = None
self._positions_opened = 0
self._price_step = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def JmaLength(self):
return self._jma_length.Value
@JmaLength.setter
def JmaLength(self, value):
self._jma_length.Value = value
@property
def PriceStep(self):
return self._price_step_param.Value
@PriceStep.setter
def PriceStep(self, value):
self._price_step_param.Value = value
@property
def MaxPositions(self):
return self._max_positions.Value
@MaxPositions.setter
def MaxPositions(self, value):
self._max_positions.Value = value
@property
def BuyPosOpen(self):
return self._buy_pos_open.Value
@BuyPosOpen.setter
def BuyPosOpen(self, value):
self._buy_pos_open.Value = value
@property
def SellPosOpen(self):
return self._sell_pos_open.Value
@SellPosOpen.setter
def SellPosOpen(self, value):
self._sell_pos_open.Value = value
@property
def BuyPosClose(self):
return self._buy_pos_close.Value
@BuyPosClose.setter
def BuyPosClose(self, value):
self._buy_pos_close.Value = value
@property
def SellPosClose(self):
return self._sell_pos_close.Value
@SellPosClose.setter
def SellPosClose(self, value):
self._sell_pos_close.Value = value
def OnStarted2(self, time):
super(color_j_fatl_digit_re_open_strategy, self).OnStarted2(time)
sec_step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0
self._price_step = sec_step * float(self.PriceStep)
jma = JurikMovingAverage()
jma.Length = self.JmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(jma, self.ProcessCandle).Start()
def ProcessCandle(self, candle, jma_value):
if candle.State != CandleStates.Finished:
return
jma_val = float(jma_value)
close = float(candle.ClosePrice)
if self._prev_jma is not None:
if jma_val > self._prev_jma:
direction = 1
elif jma_val < self._prev_jma:
direction = -1
else:
direction = 0
else:
direction = 0
if direction == -1 and self.Position > 0 and self.BuyPosClose:
self.SellMarket()
self._positions_opened = 0
self._last_entry_price = None
elif direction == 1 and self.Position < 0 and self.SellPosClose:
self.BuyMarket()
self._positions_opened = 0
self._last_entry_price = None
if direction == 1 and self._prev_direction != 1 and self.BuyPosOpen and self.Position <= 0:
self.BuyMarket()
self._positions_opened = 1
self._last_entry_price = close
elif direction == -1 and self._prev_direction != -1 and self.SellPosOpen and self.Position >= 0:
self.SellMarket()
self._positions_opened = 1
self._last_entry_price = close
elif (self.Position > 0 and self.BuyPosOpen
and self._positions_opened < int(self.MaxPositions)
and self._last_entry_price is not None
and close - self._last_entry_price >= self._price_step):
self.BuyMarket()
self._positions_opened += 1
self._last_entry_price = close
elif (self.Position < 0 and self.SellPosOpen
and self._positions_opened < int(self.MaxPositions)
and self._last_entry_price is not None
and self._last_entry_price - close >= self._price_step):
self.SellMarket()
self._positions_opened += 1
self._last_entry_price = close
self._prev_direction = direction
self._prev_jma = jma_val
def OnReseted(self):
super(color_j_fatl_digit_re_open_strategy, self).OnReseted()
self._prev_jma = None
self._prev_direction = 0
self._last_entry_price = None
self._positions_opened = 0
def CreateClone(self):
return color_j_fatl_digit_re_open_strategy()