MPM动量策略
该策略是对原始MQL专家顾问mpm-1_8.mq4的简化转换。策略等待一系列同方向的K线,
当满足条件时在同方向开仓。平均真实波动范围(ATR)用于评估K线大小并跟踪止损。
参数
| 名称 | 说明 |
|---|---|
ProgressiveCandles |
触发交易所需的连续K线数量。 |
ProgressiveSize |
相对于ATR的最小K线实体大小。 |
StopRatio |
用于跟踪止损的ATR比例。 |
AtrPeriod |
ATR指标的周期。 |
CandleType |
策略使用的K线类型。 |
ProfitPerLot |
每手的盈利目标。 |
BreakEvenPerLot |
达到保本所需的盈利。 |
LossPerLot |
每手可接受的最大亏损。 |
逻辑
- 在每根收盘K线上比较其实体大小与ATR。
- 当K线实体超过
ProgressiveSize阈值时,记录多头或空头计数。 - 当同方向连续出现
ProgressiveCandles根K线后,发送市价单。 - 止损价格按
StopRatio×ATR进行跟踪。 - 当触及止损或达到盈利/亏损目标时平仓。
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>
/// MPM momentum strategy converted from MQL.
/// </summary>
public class MpmStrategy : Strategy
{
private readonly StrategyParam<int> _progressiveCandles;
private readonly StrategyParam<decimal> _progressiveSize;
private readonly StrategyParam<decimal> _stopRatio;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _profitPerLot;
private readonly StrategyParam<decimal> _breakEvenPerLot;
private readonly StrategyParam<decimal> _lossPerLot;
private int _bullCount;
private int _bearCount;
private decimal _entryPrice;
private decimal _stopPrice;
public int ProgressiveCandles
{
get => _progressiveCandles.Value;
set => _progressiveCandles.Value = value;
}
public decimal ProgressiveSize
{
get => _progressiveSize.Value;
set => _progressiveSize.Value = value;
}
public decimal StopRatio
{
get => _stopRatio.Value;
set => _stopRatio.Value = value;
}
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal ProfitPerLot
{
get => _profitPerLot.Value;
set => _profitPerLot.Value = value;
}
public decimal BreakEvenPerLot
{
get => _breakEvenPerLot.Value;
set => _breakEvenPerLot.Value = value;
}
public decimal LossPerLot
{
get => _lossPerLot.Value;
set => _lossPerLot.Value = value;
}
public MpmStrategy()
{
_progressiveCandles = Param(nameof(ProgressiveCandles), 3)
.SetDisplay("Progressive Candles", "Number of consecutive candles", "Signal");
_progressiveSize = Param(nameof(ProgressiveSize), 0.9m)
.SetDisplay("Progressive Size", "Minimal body size relative to ATR", "Signal");
_stopRatio = Param(nameof(StopRatio), 1.5m)
.SetDisplay("Stop Ratio", "Trailing stop ratio", "Risk");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "Average True Range period", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_profitPerLot = Param(nameof(ProfitPerLot), 2000m)
.SetDisplay("Profit Per Lot", "Profit target per lot", "Risk");
_breakEvenPerLot = Param(nameof(BreakEvenPerLot), 800m)
.SetDisplay("BreakEven Per Lot", "Break even profit per lot", "Risk");
_lossPerLot = Param(nameof(LossPerLot), 1200m)
.SetDisplay("Loss Per Lot", "Maximum loss per lot", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_bullCount = 0;
_bearCount = 0;
_entryPrice = 0m;
_stopPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
// Count consecutive bullish or bearish candles with sufficient body
if (candle.ClosePrice > candle.OpenPrice && body >= atr * ProgressiveSize)
{
_bullCount++;
_bearCount = 0;
}
else if (candle.ClosePrice < candle.OpenPrice && body >= atr * ProgressiveSize)
{
_bearCount++;
_bullCount = 0;
}
else
{
_bullCount = 0;
_bearCount = 0;
}
// Open long position after sequence of bullish candles
if (Position <= 0 && _bullCount >= ProgressiveCandles)
{
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice - atr * StopRatio;
BuyMarket();
return;
}
// Open short position after sequence of bearish candles
if (Position >= 0 && _bearCount >= ProgressiveCandles)
{
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice + atr * StopRatio;
SellMarket();
return;
}
if (Position > 0)
{
var profitPerLot = candle.ClosePrice - _entryPrice;
if (profitPerLot >= ProfitPerLot || profitPerLot >= BreakEvenPerLot || profitPerLot <= -LossPerLot)
{
SellMarket();
return;
}
var newStop = candle.ClosePrice - atr * StopRatio;
if (newStop > _stopPrice)
_stopPrice = newStop;
if (candle.ClosePrice <= _stopPrice)
SellMarket();
}
else if (Position < 0)
{
var profitPerLot = _entryPrice - candle.ClosePrice;
if (profitPerLot >= ProfitPerLot || profitPerLot >= BreakEvenPerLot || profitPerLot <= -LossPerLot)
{
BuyMarket();
return;
}
var newStop = candle.ClosePrice + atr * StopRatio;
if (newStop < _stopPrice)
_stopPrice = newStop;
if (candle.ClosePrice >= _stopPrice)
BuyMarket();
}
}
}
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
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class mpm_strategy(Strategy):
def __init__(self):
super(mpm_strategy, self).__init__()
self._progressive_candles = self.Param("ProgressiveCandles", 3) \
.SetDisplay("Progressive Candles", "Number of consecutive candles", "Signal")
self._progressive_size = self.Param("ProgressiveSize", 0.9) \
.SetDisplay("Progressive Size", "Minimal body size relative to ATR", "Signal")
self._stop_ratio = self.Param("StopRatio", 1.5) \
.SetDisplay("Stop Ratio", "Trailing stop ratio", "Risk")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Average True Range period", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._profit_per_lot = self.Param("ProfitPerLot", 2000.0) \
.SetDisplay("Profit Per Lot", "Profit target per lot", "Risk")
self._break_even_per_lot = self.Param("BreakEvenPerLot", 800.0) \
.SetDisplay("BreakEven Per Lot", "Break even profit per lot", "Risk")
self._loss_per_lot = self.Param("LossPerLot", 1200.0) \
.SetDisplay("Loss Per Lot", "Maximum loss per lot", "Risk")
self._bull_count = 0
self._bear_count = 0
self._entry_price = 0.0
self._stop_price = 0.0
@property
def ProgressiveCandles(self):
return self._progressive_candles.Value
@ProgressiveCandles.setter
def ProgressiveCandles(self, value):
self._progressive_candles.Value = value
@property
def ProgressiveSize(self):
return self._progressive_size.Value
@ProgressiveSize.setter
def ProgressiveSize(self, value):
self._progressive_size.Value = value
@property
def StopRatio(self):
return self._stop_ratio.Value
@StopRatio.setter
def StopRatio(self, value):
self._stop_ratio.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def ProfitPerLot(self):
return self._profit_per_lot.Value
@ProfitPerLot.setter
def ProfitPerLot(self, value):
self._profit_per_lot.Value = value
@property
def BreakEvenPerLot(self):
return self._break_even_per_lot.Value
@BreakEvenPerLot.setter
def BreakEvenPerLot(self, value):
self._break_even_per_lot.Value = value
@property
def LossPerLot(self):
return self._loss_per_lot.Value
@LossPerLot.setter
def LossPerLot(self, value):
self._loss_per_lot.Value = value
def OnStarted2(self, time):
super(mpm_strategy, self).OnStarted2(time)
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
atr = float(atr_value)
body = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
if candle.ClosePrice > candle.OpenPrice and body >= atr * float(self.ProgressiveSize):
self._bull_count += 1
self._bear_count = 0
elif candle.ClosePrice < candle.OpenPrice and body >= atr * float(self.ProgressiveSize):
self._bear_count += 1
self._bull_count = 0
else:
self._bull_count = 0
self._bear_count = 0
price = float(candle.ClosePrice)
if self.Position <= 0 and self._bull_count >= self.ProgressiveCandles:
self._entry_price = price
self._stop_price = self._entry_price - atr * float(self.StopRatio)
self.BuyMarket()
return
if self.Position >= 0 and self._bear_count >= self.ProgressiveCandles:
self._entry_price = price
self._stop_price = self._entry_price + atr * float(self.StopRatio)
self.SellMarket()
return
if self.Position > 0:
profit_per_lot = price - self._entry_price
if (profit_per_lot >= float(self.ProfitPerLot)
or profit_per_lot >= float(self.BreakEvenPerLot)
or profit_per_lot <= -float(self.LossPerLot)):
self.SellMarket()
return
new_stop = price - atr * float(self.StopRatio)
if new_stop > self._stop_price:
self._stop_price = new_stop
if price <= self._stop_price:
self.SellMarket()
elif self.Position < 0:
profit_per_lot = self._entry_price - price
if (profit_per_lot >= float(self.ProfitPerLot)
or profit_per_lot >= float(self.BreakEvenPerLot)
or profit_per_lot <= -float(self.LossPerLot)):
self.BuyMarket()
return
new_stop = price + atr * float(self.StopRatio)
if new_stop < self._stop_price:
self._stop_price = new_stop
if price >= self._stop_price:
self.BuyMarket()
def OnReseted(self):
super(mpm_strategy, self).OnReseted()
self._bull_count = 0
self._bear_count = 0
self._entry_price = 0.0
self._stop_price = 0.0
def CreateClone(self):
return mpm_strategy()