首页
/
策略示例
在 GitHub 上查看
MA on Momentum Min Profit 策略
概述
该策略复刻 MetaTrader 5 智能交易系统 MA on Momentum Min Profit.mq5 。当 Momentum 指标向上穿越自身的动量均线并且上一根 K 线仍低于 100 水平时买入;当 Momentum 向下跌破均线且上一根 K 线高于 100 时卖出。实现中保留了原始版本的资金止损和以点数表示的固定止盈。
交易逻辑
订阅 CandleType 指定的 K 线并计算 Momentum 指标。
使用 MomentumMovingAverageType 与 MomentumMovingAveragePeriod 对 Momentum 序列进行平滑。
通过上一根 K 线的动量值检测交叉,避免重复信号。
沿用 MQL 版本的附加功能:
反转买卖信号;
在开仓前平掉反向持仓或者跳过进场;
强制只保留一张净头寸;
支持在当前尚未收盘的 K 线上触发信号。
风险控制:
资金止损:PnL + Position * (close - PositionPrice) 不得低于 StopLossMoney;
将 TakeProfitPoints 乘以 Security.PriceStep 得到价格止盈距离。
参数
参数
类型
默认值
说明
CandleType
DataType
TimeSpan.FromMinutes(5).TimeFrame()
用于计算 Momentum 的 K 线类型。
MomentumPeriod
int
14
Momentum 指标的回溯长度。
MomentumMovingAveragePeriod
int
6
动量均线的周期。
MomentumMovingAverageType
MomentumMovingAverageType
Smoothed
动量均线算法(Simple、Exponential、Smoothed、Weighted)。
ReverseSignals
bool
false
反向执行买卖信号。
CloseOpposite
bool
true
开仓前关闭反向持仓。
OnlyOnePosition
bool
true
仅保留一张净头寸。
UseCurrentCandle
bool
false
在未收盘的当前 K 线上计算信号。
StopLossMoney
decimal
15
账户层面的资金回撤阈值。
TakeProfitPoints
decimal
460
以点数表示的止盈距离(乘以 PriceStep)。
MomentumReference
decimal
100
来自原策略的动量基准线。
实现说明
平滑处理使用 StockSharp 自带的 SMA/EMA/SMMA/WMA 指标,对应 LengthIndicator<decimal> 实例。
原策略的下单队列与 magic number 过滤在 StockSharp 中转换为净头寸逻辑:当启用 CloseOpposite 时会发送一张市价单,同时平掉反向仓位并建立新的方向。
资金止损通过调用 CloseAll() 平掉全部仓位,与 MQL 版本监控 Commission + Swap + Profit 的方式一致。
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>
/// Momentum crossing its own moving average strategy.
/// Converted from MetaTrader 5 (MA on Momentum Min Profit.mq5).
/// </summary>
public class MaOnMomentumMinProfitStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _momentumPeriod;
private readonly StrategyParam<int> _maPeriod;
private Momentum _momentum;
private readonly Queue<decimal> _momentumHistory = new();
private decimal? _prevMomentum;
private decimal? _prevSignal;
/// <summary>
/// Candle type used for signal calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Momentum period.
/// </summary>
public int MomentumPeriod
{
get => _momentumPeriod.Value;
set => _momentumPeriod.Value = value;
}
/// <summary>
/// Moving average period applied to momentum values.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Initialize <see cref="MaOnMomentumMinProfitStrategy"/>.
/// </summary>
public MaOnMomentumMinProfitStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Type of candles used for the momentum calculation", "General");
_momentumPeriod = Param(nameof(MomentumPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Momentum Period", "Lookback for the momentum indicator", "Momentum");
_maPeriod = Param(nameof(MaPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period of the moving average applied to momentum", "Momentum");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMomentum = null;
_prevSignal = null;
_momentumHistory.Clear();
_momentum = new Momentum { Length = MomentumPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_momentum, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal momentumValue)
{
if (candle.State != CandleStates.Finished)
return;
_momentumHistory.Enqueue(momentumValue);
while (_momentumHistory.Count > MaPeriod)
_momentumHistory.Dequeue();
if (!_momentum.IsFormed)
return;
if (_momentumHistory.Count < MaPeriod)
{
_prevMomentum = momentumValue;
return;
}
// Calculate SMA of momentum
var sum = 0m;
var history = _momentumHistory.ToArray();
foreach (var v in history)
sum += v;
var signalValue = sum / history.Length;
if (_prevMomentum is null || _prevSignal is null)
{
_prevMomentum = momentumValue;
_prevSignal = signalValue;
return;
}
var crossUp = _prevMomentum < _prevSignal && momentumValue > signalValue;
var crossDown = _prevMomentum > _prevSignal && momentumValue < signalValue;
var volume = Volume;
if (volume <= 0)
volume = 1;
var minSpread = 0.5m;
if (crossUp && Math.Abs(momentumValue - signalValue) >= minSpread)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (crossDown && Math.Abs(momentumValue - signalValue) >= minSpread)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevMomentum = momentumValue;
_prevSignal = signalValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_prevMomentum = null;
_prevSignal = null;
_momentum = null;
_momentumHistory.Clear();
base.OnReseted();
}
}
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 Momentum
from StockSharp.Algo.Strategies import Strategy
class ma_on_momentum_min_profit_strategy(Strategy):
def __init__(self):
super(ma_on_momentum_min_profit_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._momentum_period = self.Param("MomentumPeriod", 20)
self._ma_period = self.Param("MaPeriod", 10)
self._mom_history = []
self._prev_mom = 0.0
self._prev_signal = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def MomentumPeriod(self):
return self._momentum_period.Value
@MomentumPeriod.setter
def MomentumPeriod(self, value):
self._momentum_period.Value = value
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
def OnReseted(self):
super(ma_on_momentum_min_profit_strategy, self).OnReseted()
self._mom_history = []
self._prev_mom = 0.0
self._prev_signal = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(ma_on_momentum_min_profit_strategy, self).OnStarted2(time)
self._mom_history = []
self._prev_mom = 0.0
self._prev_signal = 0.0
self._has_prev = False
mom = Momentum()
mom.Length = self.MomentumPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(mom, self._process_candle).Start()
def _process_candle(self, candle, mom_value):
if candle.State != CandleStates.Finished:
return
mom_val = float(mom_value)
ma_len = self.MaPeriod
self._mom_history.append(mom_val)
while len(self._mom_history) > ma_len:
self._mom_history.pop(0)
if len(self._mom_history) < ma_len:
self._prev_mom = mom_val
return
signal_val = sum(self._mom_history) / ma_len
if self._has_prev:
cross_up = self._prev_mom < self._prev_signal and mom_val > signal_val
cross_down = self._prev_mom > self._prev_signal and mom_val < signal_val
min_spread = 0.5
if cross_up and abs(mom_val - signal_val) >= min_spread and self.Position <= 0:
self.BuyMarket()
elif cross_down and abs(mom_val - signal_val) >= min_spread and self.Position >= 0:
self.SellMarket()
self._prev_mom = mom_val
self._prev_signal = signal_val
self._has_prev = True
def CreateClone(self):
return ma_on_momentum_min_profit_strategy()