在 GitHub 上查看
ColorXPWMA Digit MMRec 策略
策略概述
ColorXPWMA Digit MMRec 策略将 MetaTrader 专家顾问 Exp_ColorXPWMA_Digit_MMRec 移植到 StockSharp 平台。核心思想是使用 ColorXPWMA Digit 指标捕捉趋势拐点,并保留原始版本的“递归资金管理”(MM Recounter) 机制。指标首先计算一条带权移动平均线,权重按 (period - index)^power 递减,然后再通过选定的移动平均方法进行平滑。平滑后的斜率被量化为三个离散颜色:2 表示上升,0 表示下降,1 表示水平。
交易信号在指定的历史 K 线 (SignalBar) 上评估:如果 SignalBar + 1 处的颜色为 2(趋势向上),但 SignalBar 上的颜色已经不再为 2,则认为上涨动能失效,此时平掉空头并(如果允许)开立多单;反之,当 SignalBar + 1 的颜色为 0(趋势向下)而 SignalBar 上颜色不再为 0 时,平掉多单并可开立空单。
指标逻辑
- Power Weighted Moving Average:最新的价格拥有更高权重,权重为
(period - index)^power。
- 平滑处理:支持多种移动平均(SMA、EMA、SMMA、LWMA、Jurik、T3、Kaufman AMA)。由于 StockSharp 没有 JurX、ParMa、VIDYA 的原生实现,这三种模式使用 EMA 近似。
- 颜色编码:根据平滑线的斜率符号生成
0/1/2 颜色序列,驱动交易信号。
- 数位控制:可将最终数值四舍五入到指定的小数位数,复刻原始指标的 “Digit” 行为。
交易规则
- 多头条件(上涨失效)
- 条件:
SignalBar + 1 处颜色为 2,SignalBar 处颜色不为 2。
- 操作:若有空头则立即平仓;若允许开多则按照资金管理结果开立新的多头头寸。
- 空头条件(下跌失效)
- 条件:
SignalBar + 1 处颜色为 0,SignalBar 处颜色不为 0。
- 操作:若有多头则平仓;若允许开空则按资金管理结果建立空头。
所有操作均在触发信号的蜡烛收盘价执行。若需要翻转持仓,会一次性提交合并数量的市价单,先平旧仓再开新仓。
资金管理 Recounter
在每次开仓前,策略会回顾近期交易结果:
- 针对多头,检查最近
BuyTotalTrigger 笔交易,如果亏损次数达到 BuyLossTrigger,下一笔多头使用 ReducedVolume;否则使用 NormalVolume。
- 针对空头,同样使用
SellTotalTrigger 与 SellLossTrigger 的组合。
这与原始 MQL 代码中的 BuyTradeMMRecounterS / SellTradeMMRecounterS 行为完全一致。
参数说明
| 分组 |
参数 |
说明 |
| General |
CandleType |
指标与信号使用的时间框架。 |
| Indicator |
IndicatorPeriod |
Power Weighted MA 的周期。 |
| Indicator |
IndicatorPower |
权重的指数,越大越强调最新数据。 |
| Indicator |
SmoothingMethod |
平滑方法。JurX、ParMa、Vidya 在 StockSharp 中采用 EMA 近似。 |
| Indicator |
SmoothingLength |
平滑移动平均的长度。 |
| Indicator |
SmoothingPhase |
某些平滑算法使用的相位参数。 |
| Indicator |
AppliedPrices |
指标所用的价格类型(收盘、开盘、最高、最低等)。 |
| Indicator |
RoundingDigits |
最终值的保留小数位数。 |
| Logic |
SignalBar |
读取颜色缓冲区时的历史位移。 |
| Permissions |
EnableBuyEntries / EnableSellEntries |
是否允许开多/开空。 |
| Permissions |
EnableBuyExits / EnableSellExits |
是否允许平多/平空。 |
| Money Management |
NormalVolume |
默认开仓数量。 |
| Money Management |
ReducedVolume |
连续亏损后使用的降低数量。 |
| Money Management |
BuyTotalTrigger, BuyLossTrigger |
多头统计窗口及亏损阈值。 |
| Money Management |
SellTotalTrigger, SellLossTrigger |
空头统计窗口及亏损阈值。 |
| Risk Management |
StopLossPoints, TakeProfitPoints |
以点数表示的止损/止盈距离,非零时通过 StartProtection 自动应用。 |
使用建议
- 推荐保持
SignalBar = 1 以确保仅根据完全收盘的蜡烛发出信号,贴近原始 EA 行为。
- 策略只保留 recounter 所需的最新绩效数据,不会无限增长内存。
- 由于 StockSharp 订单执行是异步的,策略在更新盈亏计数时假定成交价等于触发信号的收盘价,与原 MQL 策略在测试器中的假设一致。
- 若需要原汁原味的 JurX、ParMa、VIDYA 平滑方式,可自行实现对应指标并替换当前的 EMA 近似。
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// ColorXPWMA strategy using WMA crossover with money management recovery.
/// </summary>
public class ColorXpWmaDigitMmRecStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal? _prevFast;
private decimal? _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 ColorXpWmaDigitMmRecStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast WMA", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 25)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow WMA", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(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 fastVal, decimal slowVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
if (_prevFast == null || _prevSlow == null)
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
var prevAbove = _prevFast.Value > _prevSlow.Value;
var currAbove = fastVal > slowVal;
_prevFast = fastVal;
_prevSlow = slowVal;
if (!prevAbove && currAbove)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
else if (prevAbove && !currAbove)
{
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class color_xp_wma_digit_mm_rec_strategy(Strategy):
def __init__(self):
super(color_xp_wma_digit_mm_rec_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", 8) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 21) \
.SetDisplay("Slow Period", "Slow EMA 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_xp_wma_digit_mm_rec_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(color_xp_wma_digit_mm_rec_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:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif prev_above and not curr_above:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return color_xp_wma_digit_mm_rec_strategy()