随机指标马丁格尔网格策略
概述
本策略将 MetaTrader 顾问 rmkp_9yj4qp1gn8fucubyqnvb 移植到 StockSharp 平台。算法结合随机震荡指标的反转信号与马丁格尔式的网格加仓。当上一根已完成的 K 线显示随机指标的信号线脱离超买或超卖区域时,策略立即开仓。若价格朝不利方向运行,则按照固定点差添加加仓单,每次加仓的手数都会翻倍。每一腿都有独立的止盈和跟踪止损,因此当价格回撤时可以逐步锁定收益。
交易逻辑
- 信号判定:
- 使用可配置周期的随机指标,计算 %K 与 %D 两条线。
- 当上一根 K 线中 %K 高于 %D 且 %D 低于
ZoneBuy时视为看多信号。 - 当上一根 K 线中 %K 低于 %D 且 %D 高于
ZoneSell时视为看空信号。
- 首单开仓:
- 在出现有效信号且账户当前无持仓时,以
BaseVolume的手数市价开仓。 - 记录入场价格,供后续加仓与跟踪止损使用。
- 在出现有效信号且账户当前无持仓时,以
- 马丁格尔加仓:
- 只要仓位存在,策略会监控价格是否相对最新仓位逆向移动
StepPips点。 - 若条件满足并且当前开仓数量少于
MaxOrders,则按照前一单手数的两倍下达新的市价单。
- 只要仓位存在,策略会监控价格是否相对最新仓位逆向移动
- 出场管理:
- 每个仓位的止盈距离均为
TakeProfitPips点。 - 当浮盈达到
TrailingStopPips点时启用跟踪止损,并在盈利扩大时不断上移(或下移)止损位置。 - 一旦价格回撤至跟踪止损或触及止盈,该仓位将被单独平仓,其他仓位继续运行。
- 当所有仓位都被平掉后,策略重置内部状态,重新等待新的随机指标信号。
- 每个仓位的止盈距离均为
风险控制
MaxOrders限制了网格中的最大仓位数量,所有下单量都会遵循交易品种的最小、最大手数及步长限制。- 跟踪止损可在行情反向时保护已经获得的浮盈。
- 由于马丁格尔会快速增加仓位规模,实盘前务必评估保证金占用、滑点以及风控规则。
参数
| 参数 | 说明 | 默认值 |
|---|---|---|
CandleType |
用于指标计算的 K 线数据类型。 | 15 分钟周期 |
BaseVolume |
初次开仓的手数。 | 0.1 |
TakeProfitPips |
每个仓位距离入场价的止盈点数。 | 50 |
TrailingStopPips |
启动并跟踪止损所需的点数。 | 20 |
MaxOrders |
网格中允许的最大仓位数量。 | 7 |
StepPips |
每次加仓所需的逆向波动点数。 | 7 |
KPeriod |
随机指标 %K 的回溯周期。 | 5 |
DPeriod |
随机指标 %D 的平滑周期。 | 3 |
Slowing |
对 %K 额外应用的平滑长度。 | 3 |
ZoneBuy |
允许做多信号的最高 %D 阈值。 | 30 |
ZoneSell |
允许做空信号的最低 %D 阈值。 | 70 |
其他说明
- 策略基于 StockSharp 的高层 API,实现了蜡烛图订阅、指标绑定以及交易图形展示,逻辑与原始 MT4 版本保持一致。
- 请确保交易品种的最大可下单量能够覆盖整个马丁格尔阶梯,并根据资金规模调整参数。
- 建议在真实交易前通过回测与模拟盘验证策略表现,并配合额外的风控机制。
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>
/// Stochastic based martingale averaging strategy translated from the MetaTrader expert "rmkp_9yj4qp1gn8fucubyqnvb".
/// Adds averaging orders when price moves against the latest entry and manages each leg with trailing stops and individual take profits.
/// </summary>
public class StochasticMartingaleGridStrategy : Strategy
{
private sealed class Entry
{
public decimal Price { get; set; }
public decimal Volume { get; set; }
public decimal? TrailingPrice { get; set; }
}
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<int> _maxOrders;
private readonly StrategyParam<decimal> _stepPips;
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<int> _slowing;
private readonly StrategyParam<decimal> _zoneBuy;
private readonly StrategyParam<decimal> _zoneSell;
private List<Entry> _entries;
private StochasticOscillator _stochastic;
private decimal? _previousMain;
private decimal? _previousSignal;
private decimal _pipSize;
/// <summary>
/// Initializes a new instance of the <see cref="StochasticMartingaleGridStrategy"/> class.
/// </summary>
public StochasticMartingaleGridStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used to evaluate stochastic values", "General");
_baseVolume = Param(nameof(BaseVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Base Volume", "Initial order volume", "Trading")
;
_takeProfitPips = Param(nameof(TakeProfitPips), 50m)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pips)", "Distance to the take profit target for each entry", "Risk")
;
_trailingStopPips = Param(nameof(TrailingStopPips), 20m)
.SetGreaterThanZero()
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance applied per entry", "Risk")
;
_maxOrders = Param(nameof(MaxOrders), 2)
.SetGreaterThanZero()
.SetDisplay("Max Orders", "Maximum number of simultaneous averaging entries", "Martingale");
_stepPips = Param(nameof(StepPips), 7m)
.SetGreaterThanZero()
.SetDisplay("Step (pips)", "Adverse move required before adding a new entry", "Martingale")
;
_kPeriod = Param(nameof(KPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("%K Period", "Stochastic %K lookback length", "Indicators")
;
_dPeriod = Param(nameof(DPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("%D Period", "Stochastic %D smoothing length", "Indicators")
;
_slowing = Param(nameof(Slowing), 3)
.SetGreaterThanZero()
.SetDisplay("Slowing", "Additional smoothing applied to %K", "Indicators")
;
_zoneBuy = Param(nameof(ZoneBuy), 50m)
.SetDisplay("Buy Zone", "Upper limit that allows long setups when %K is above %D", "Indicators")
;
_zoneSell = Param(nameof(ZoneSell), 50m)
.SetDisplay("Sell Zone", "Lower limit that allows short setups when %K is below %D", "Indicators")
;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initial trade volume.
/// </summary>
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.Value = value;
}
/// <summary>
/// Take profit distance in pips applied to every entry.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips applied to every entry.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Maximum number of averaging entries.
/// </summary>
public int MaxOrders
{
get => _maxOrders.Value;
set => _maxOrders.Value = value;
}
/// <summary>
/// Step in pips required to trigger a new averaging entry.
/// </summary>
public decimal StepPips
{
get => _stepPips.Value;
set => _stepPips.Value = value;
}
/// <summary>
/// Stochastic %K period.
/// </summary>
public int KPeriod
{
get => _kPeriod.Value;
set => _kPeriod.Value = value;
}
/// <summary>
/// Stochastic %D period.
/// </summary>
public int DPeriod
{
get => _dPeriod.Value;
set => _dPeriod.Value = value;
}
/// <summary>
/// Stochastic slowing period.
/// </summary>
public int Slowing
{
get => _slowing.Value;
set => _slowing.Value = value;
}
/// <summary>
/// Maximum signal level that allows long entries.
/// </summary>
public decimal ZoneBuy
{
get => _zoneBuy.Value;
set => _zoneBuy.Value = value;
}
/// <summary>
/// Minimum signal level that allows short entries.
/// </summary>
public decimal ZoneSell
{
get => _zoneSell.Value;
set => _zoneSell.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entries = null;
_stochastic = null;
_previousMain = null;
_previousSignal = null;
_pipSize = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entries = new List<Entry>();
_pipSize = Security?.PriceStep ?? 1m;
_stochastic = new StochasticOscillator
{
K = { Length = KPeriod },
D = { Length = DPeriod }
};
Indicators.Add(_stochastic);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _stochastic);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var stochResult = _stochastic.Process(candle);
if (!_stochastic.IsFormed)
return;
if (stochResult is not StochasticOscillatorValue stoch)
return;
if (stoch.K is not decimal currentMain || stoch.D is not decimal currentSignal)
return;
if (Position != 0)
{
_previousMain = currentMain;
_previousSignal = currentSignal;
return;
}
if (_previousMain is decimal prevMain && _previousSignal is decimal prevSignal)
{
// Buy: K crosses above D in oversold zone
if (prevMain <= prevSignal && currentMain > currentSignal && currentSignal < ZoneBuy)
BuyMarket();
// Sell: K crosses below D in overbought zone
else if (prevMain >= prevSignal && currentMain < currentSignal && currentSignal > ZoneSell)
SellMarket();
}
_previousMain = currentMain;
_previousSignal = currentSignal;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import StochasticOscillator, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class stochastic_martingale_grid_strategy(Strategy):
"""Stochastic-based martingale averaging strategy. Enters on K/D crossover
in oversold/overbought zones. Uses StartProtection for SL/TP."""
def __init__(self):
super(stochastic_martingale_grid_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe used to evaluate stochastic values", "General")
self._base_volume = self.Param("BaseVolume", 0.1) \
.SetGreaterThanZero() \
.SetDisplay("Base Volume", "Initial order volume", "Trading")
self._take_profit_pips = self.Param("TakeProfitPips", 50.0) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit (pips)", "Distance to the take profit target for each entry", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 20.0) \
.SetGreaterThanZero() \
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance applied per entry", "Risk")
self._max_orders = self.Param("MaxOrders", 2) \
.SetGreaterThanZero() \
.SetDisplay("Max Orders", "Maximum number of simultaneous averaging entries", "Martingale")
self._step_pips = self.Param("StepPips", 7.0) \
.SetGreaterThanZero() \
.SetDisplay("Step (pips)", "Adverse move required before adding a new entry", "Martingale")
self._k_period = self.Param("KPeriod", 5) \
.SetGreaterThanZero() \
.SetDisplay("%K Period", "Stochastic %K lookback length", "Indicators")
self._d_period = self.Param("DPeriod", 3) \
.SetGreaterThanZero() \
.SetDisplay("%D Period", "Stochastic %D smoothing length", "Indicators")
self._slowing = self.Param("Slowing", 3) \
.SetGreaterThanZero() \
.SetDisplay("Slowing", "Additional smoothing applied to %K", "Indicators")
self._zone_buy = self.Param("ZoneBuy", 50.0) \
.SetDisplay("Buy Zone", "Upper limit that allows long setups when %K is above %D", "Indicators")
self._zone_sell = self.Param("ZoneSell", 50.0) \
.SetDisplay("Sell Zone", "Lower limit that allows short setups when %K is below %D", "Indicators")
self._stochastic = None
self._previous_main = None
self._previous_signal = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BaseVolume(self):
return self._base_volume.Value
@property
def KPeriod(self):
return self._k_period.Value
@property
def DPeriod(self):
return self._d_period.Value
@property
def ZoneBuy(self):
return self._zone_buy.Value
@property
def ZoneSell(self):
return self._zone_sell.Value
def OnReseted(self):
super(stochastic_martingale_grid_strategy, self).OnReseted()
self._stochastic = None
self._previous_main = None
self._previous_signal = None
def OnStarted2(self, time):
super(stochastic_martingale_grid_strategy, self).OnStarted2(time)
self._stochastic = StochasticOscillator()
self._stochastic.K.Length = self.KPeriod
self._stochastic.D.Length = self.DPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
self.StartProtection(Unit(2, UnitTypes.Percent), Unit(1, UnitTypes.Percent))
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
stoch_result = self._stochastic.Process(CandleIndicatorValue(self._stochastic, candle))
if not self._stochastic.IsFormed:
return
k_raw = stoch_result.K if hasattr(stoch_result, 'K') else None
d_raw = stoch_result.D if hasattr(stoch_result, 'D') else None
if k_raw is None or d_raw is None:
return
current_main = float(k_raw)
current_signal = float(d_raw)
if self.Position != 0:
self._previous_main = current_main
self._previous_signal = current_signal
return
if self._previous_main is not None and self._previous_signal is not None:
prev_main = self._previous_main
prev_signal = self._previous_signal
# Buy: K crosses above D in oversold zone
if prev_main <= prev_signal and current_main > current_signal and \
current_signal < float(self.ZoneBuy):
self.BuyMarket()
# Sell: K crosses below D in overbought zone
elif prev_main >= prev_signal and current_main < current_signal and \
current_signal > float(self.ZoneSell):
self.SellMarket()
self._previous_main = current_main
self._previous_signal = current_signal
def CreateClone(self):
return stochastic_martingale_grid_strategy()