ColorJFatl Digit ReOpen 策略
该策略使用Jurik移动平均线(JMA)来识别趋势方向。当JMA向上转折时开多并平掉所有空单;当JMA向下转折时开空并平掉所有多单。价格每朝持仓方向移动固定点数时会追加新的仓位,直至达到最大数量。
详情
- 入场:
- JMA向上转折 → 开多并平空。
- JMA向下转折 → 开空并平多。
- 加仓:
- 首次建仓后,每当价格按
PriceStep点向持仓方向移动时加仓,直到达到MaxPositions。
- 首次建仓后,每当价格按
- 离场:
- JMA反向转折时平掉当前仓位。
- 参数:
JmaLength– JMA周期。PriceStep– 加仓所需的点数移动。MaxPositions– 同方向最大仓位数。BuyPosOpen,SellPosOpen,BuyPosClose,SellPosClose– 控制各操作。CandleType– 计算所用时间框架。
- 指标:Jurik Moving Average。
- 类型:趋势跟随。
- 周期:默认4小时。
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()