在 GitHub 上查看
Color Schaff JCCX Trend Cycle MMRec Duplex 策略
概述
- 将 MetaTrader 中的双向专家 "ColorSchaffJCCXTrendCycle_MMRec_Duplex" 迁移到 StockSharp 平台。
- 基于 Jurik 移动平均构建两套 Schaff Trend Cycle 流水线,分别跟踪多头与空头的动量反转。
- 内置精简版 MMRec 资金管理逻辑,在连续亏损时自动减小手数。
- 长短方向可使用不同的时间框架与价格输入,方便做非对称配置。
指标结构
- JCCX 近似:价格先经过 Jurik 均线得到去趋势序列,再将该序列及其绝对值分别用 Jurik 均线平滑,得到与原始 JCCX 类似的输出。
- MACD 层:快慢 JCCX 输出之差形成基础动量。
- 双重随机变换:滚动最高/最低窗口将动量压缩到 -100..+100 范围内,得到最终的 Schaff Trend Cycle 值。
- Phase 调节:
Phase 参数映射为 0.05–0.95 的平滑系数,用于模拟 Jurik 指标中的相位控制。
长、短两个模块分别执行上述流程,因此可独立选择蜡烛类型与价格来源。
交易规则
多头模块
- 开仓:当长周期 STC 上穿 0(当前值 > 0 且延迟值 ≤ 0)时买入,若存在空头仓位会先平仓。
- 平仓:长周期 STC 跌破 0 且允许平仓时卖出。
- 止盈止损:可选的止损与止盈距离(单位为价格步长)在每根完成的蜡烛上通过最高价/最低价检测。
空头模块
- 开仓:短周期 STC 下穿 0(当前值 < 0 且延迟值 ≥ 0)时卖出做空,如有多头仓位则先平仓。
- 平仓:短周期 STC 回到 0 上方且允许平仓时买入。
- 止盈止损:空头部分执行对称的止损/止盈检查。
SignalBar 表示在评估信号前需要跳过的已完成蜡烛数量,默认 1 即复现原策略使用上一根蜡烛的方式。
资金管理(MMRec)
- 分别维护多头与空头最近交易结果的队列。
TotalTrigger 限制队列长度,仅保留最新 N 笔结果。
LossTrigger 指定在该窗口中出现多少次亏损后切换到 SmallVolume。
- 当亏损次数不足时使用默认的
NormalVolume。
参数
| 分组 |
参数 |
说明 |
默认值 |
| Long |
LongCandleType |
多头模块使用的蜡烛类型/时间框架。 |
8 小时 |
| Long |
LongFastLength |
多头 JCCX 的快周期。 |
23 |
| Long |
LongSlowLength |
多头 JCCX 的慢周期。 |
50 |
| Long |
LongSmoothLength |
对分子/分母进行 Jurik 平滑的周期。 |
8 |
| Long |
LongPhase |
映射到平滑系数的 Phase 参数。 |
100 |
| Long |
LongCycle |
随机变换的窗口长度。 |
10 |
| Long |
LongSignalBar |
评估信号前的延迟蜡烛数。 |
1 |
| Long |
LongAppliedPrice |
多头模块使用的价格类型。 |
Close |
| Long |
LongAllowOpen / LongAllowClose |
是否允许开多/平多。 |
true |
| Long |
LongTotalTrigger |
多头 MMRec 队列的最大长度。 |
5 |
| Long |
LongLossTrigger |
切换到小手数所需的亏损次数。 |
3 |
| Long |
LongSmallVolume / LongNormalVolume |
多头的减小/默认手数。 |
0.01 / 0.1 |
| Long |
LongStopLoss / LongTakeProfit |
多头止损/止盈距离(价格步长)。 |
1000 / 2000 |
| Short |
同上(参数名前缀为 Short)。 |
|
|
风险提示
- 策略使用
Security.PriceStep 来换算点数,请确保标的正确设置价格步长。
- 止盈止损在蜡烛收盘后检查,低时间框架能提供更精细的控制。
- MMRec 通过比较开仓与当前蜡烛收盘价估计盈亏,实盘滑点可能导致实际结果有所偏差。
使用建议
- 初始可让长短两侧参数一致,以接近原版 EA,然后再尝试不对称配置。
- 将
SignalBar 设为 0 可提高响应速度,增大该值则能过滤噪声。
Phase 与平滑周期需要联合优化,以平衡响应速度与稳定性。
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class ColorSchaffJccxTrendCycleMmrecDuplexStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal? _prevFast, _prevSlow;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public ColorSchaffJccxTrendCycleMmrecDuplexStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 12).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 26).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null; _prevSlow = null;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fast); DrawIndicator(area, slow); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) { _prevFast = fast; _prevSlow = slow; return; }
if (_prevFast == null || _prevSlow == null) { _prevFast = fast; _prevSlow = slow; return; }
var prevAbove = _prevFast.Value > _prevSlow.Value;
var currAbove = fast > slow;
_prevFast = fast; _prevSlow = slow;
if (!prevAbove && currAbove && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
else if (prevAbove && !currAbove && Position >= 0) { if (Position > 0) SellMarket(); 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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class color_schaff_jccx_trend_cycle_mmrec_duplex_strategy(Strategy):
def __init__(self):
super(color_schaff_jccx_trend_cycle_mmrec_duplex_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 12) \
.SetDisplay("Fast EMA", "Fast period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 26) \
.SetDisplay("Slow EMA", "Slow period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(color_schaff_jccx_trend_cycle_mmrec_duplex_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(color_schaff_jccx_trend_cycle_mmrec_duplex_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast = ExponentialMovingAverage()
fast.Length = self.FastPeriod
slow = ExponentialMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
if not prev_above and curr_above and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif prev_above and not curr_above and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return color_schaff_jccx_trend_cycle_mmrec_duplex_strategy()