在 GitHub 上查看
ColorJFatl Digit NN3 MMRec 策略(StockSharp 版本)
本策略是 MetaTrader 5 专家顾问 Exp_ColorJFatl_Digit_NN3_MMRec 的 StockSharp 高级 API 迁移版。原始脚本依赖自定义的 ColorJFatl_Digit 指标和复杂的补仓管理。本版本保留信号引擎,将其拆分为三个在不同周期运行的模块。
每个模块都会把蜡烛价格转换为指定的价格类型(收盘价、典型价、DeMark 价等),然后送入 Jurik 移动平均 (JMA)。通过比较当前与上一个 JMA 值的差异判断斜率方向:斜率向上表示多头环境,模块会平掉空头并在允许时开多;斜率向下则执行相反操作。三个模块共享同一账户,因此始终处理净仓位。
交易流程
- 订阅三个周期的蜡烛数据(默认:日线、8 小时、3 小时)。
- 对每根已完成蜡烛执行:
- 根据 AppliedPrices 选择价格输入。
- 用 Jurik MA 平滑价格。
- 计算当前与前一值的差,确定状态(上升、下降或保持不变)。
- 根据 SignalBar 参数将状态放入队列,实现延迟信号。
- 状态发生变化时:
- 上升:可选地平掉空头,按模块的成交量开多。
- 下降:可选地平掉多头,按模块的成交量开空。
- 其他模块的信号可根据权限标志平仓或反向。
策略默认不设置固定止损/止盈,可结合 StartProtection() 或外部风控使用。
参数说明
每个模块(A、B、C)包含以下参数:
- CandleType:蜡烛时间周期。
- JmaLength:Jurik MA 周期。
- JmaPhase:保留原脚本的参数,StockSharp 的 JMA 暂不支持调整相位。
- SignalBar:执行信号前需要等待的已完成蜡烛数。
- AppliedPrices:价格类型,支持 Close、Open、Median、Typical、Weighted、Simple、Quarter、TrendFollow、DeMark 等。
- AllowBuyOpen / AllowSellOpen:允许开多 / 开空。
- AllowBuyClose / AllowSellClose:允许在反向信号时平多 / 平空。
- Volume:开仓数量。
由于共享净仓位,策略同一时刻只会维持一个方向的持仓。如果目标方向已有仓位则不会加仓;若方向相反则先平仓再视情况开仓。
使用建议
GetWorkingSecurities() 会自动注册所需的所有蜡烛序列。
- 所有信号都在蜡烛收盘后触发,避免重绘。
- AppliedPrices 枚举完整复制了原指标的选项,包括两个 TrendFollow 价格和 DeMark 价格。
- 未移植 MQL 中的补仓算法,可通过调整 Volume 或
StartProtection() 控制风险。
- 代码中包含英文注释,方便维护及后续可能的 Python 版本移植。
扩展方向
- 若需要固定止损或止盈,可替换
StartProtection() 的参数配置。
- 复制
SignalModule 模板即可添加新的周期组合。
- 如需细分各模块持仓,可在此基础上叠加子策略或虚拟投资组合管理。
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>
/// Multi-timeframe JMA slope strategy.
/// Three modules monitor different timeframes and react to slope changes of a Jurik MA.
/// </summary>
public class ColorJFatlDigitNn3MmRecStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _jmaLength;
private JurikMovingAverage _jma;
private decimal? _prevJma;
private int _prevSignal; // -1 down, 0 neutral, 1 up
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int JmaLength
{
get => _jmaLength.Value;
set => _jmaLength.Value = value;
}
public ColorJFatlDigitNn3MmRecStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_jmaLength = Param(nameof(JmaLength), 5)
.SetGreaterThanZero()
.SetDisplay("JMA Length", "Jurik MA period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_jma = null;
_prevJma = null;
_prevSignal = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_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;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevJma = jmaValue;
return;
}
if (_prevJma == null)
{
_prevJma = jmaValue;
return;
}
var diff = jmaValue - _prevJma.Value;
var signal = diff > 0 ? 1 : diff < 0 ? -1 : _prevSignal;
_prevJma = jmaValue;
if (signal == _prevSignal)
return;
var oldSignal = _prevSignal;
_prevSignal = signal;
if (signal == 1 && oldSignal == -1)
{
// Slope turned up -- close short, open long
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
else if (signal == -1 && oldSignal == 1)
{
// Slope turned down -- close long, open short
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
}
}
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_nn3_mm_rec_strategy(Strategy):
def __init__(self):
super(color_j_fatl_digit_nn3_mm_rec_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._jma_length = self.Param("JmaLength", 5) \
.SetDisplay("JMA Length", "Jurik MA period", "Indicators")
self._prev_jma = None
self._prev_signal = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def JmaLength(self):
return self._jma_length.Value
def OnReseted(self):
super(color_j_fatl_digit_nn3_mm_rec_strategy, self).OnReseted()
self._prev_jma = None
self._prev_signal = 0
def OnStarted2(self, time):
super(color_j_fatl_digit_nn3_mm_rec_strategy, self).OnStarted2(time)
self._prev_jma = None
self._prev_signal = 0
jma = JurikMovingAverage()
jma.Length = self.JmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(jma, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, jma)
self.DrawOwnTrades(area)
def _on_process(self, candle, jma_value):
if candle.State != CandleStates.Finished:
return
jv = float(jma_value)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_jma = jv
return
if self._prev_jma is None:
self._prev_jma = jv
return
diff = jv - self._prev_jma
if diff > 0:
signal = 1
elif diff < 0:
signal = -1
else:
signal = self._prev_signal
self._prev_jma = jv
if signal == self._prev_signal:
return
old_signal = self._prev_signal
self._prev_signal = signal
if signal == 1 and old_signal == -1:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif signal == -1 and old_signal == 1:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return color_j_fatl_digit_nn3_mm_rec_strategy()