MA Cross Method PriceMode 策略
概述
MA Cross Method PriceMode 策略是 MetaTrader 4 专家顾问“MA_cross_Method_PriceMode”的 StockSharp 移植版。策略比较两条可配置的移动平均线,只要快速均线穿越慢速均线就会触发交易。两条均线均保留了原始 MT4 输入:周期、平滑方法(SMA、EMA、SMMA、LWMA)、价格类型(收盘、开盘、最高、最低、中值、典型价、加权价)以及水平位移。策略可用于任何提供常规时间周期 K 线的标的。
指标
- 快速移动平均线 – 可配置周期、方法和价格类型。通过缓存已完成的指标值并在
FirstShift根柱子前读取,复现了 MetaTrader 的水平位移参数。 - 慢速移动平均线 – 同样可配置的周期、方法和价格类型,并使用相同的缓存机制模拟位移。
交易逻辑
- 订阅所选的 K 线类型,并且只处理已经收盘的 K 线,避免在未完成的柱子上重绘。
- 每根收盘柱分别把对应的价格馈送进两条移动平均线。
- 当两条均线都返回最终值后,策略评估两个条件:
- 看多交叉 – 前一根柱子上快速均线小于或等于慢速均线,而当前柱子上快速均线向上穿越慢速均线。
- 看空交叉 – 前一根柱子上快速均线大于或等于慢速均线,而当前柱子上快速均线向下穿越慢速均线。
- 出现看多交叉时,策略买入
OrderVolume合约。如果已有空头仓位,订单数量会自动增加,既平掉空头又建立新的多头头寸。 - 出现看空交叉时,策略卖出
OrderVolume合约。如果已有多头仓位,订单数量会相应增加以在建立空头之前平掉多头。 - 调用
StartProtection()以便根据需要附加 StockSharp 的保护模块(例如止损或保本模块)。
参数
| 名称 | 说明 | 默认值 |
|---|---|---|
FirstPeriod |
快速移动平均线的周期。 | 3 |
SecondPeriod |
慢速移动平均线的周期。 | 13 |
FirstMethod |
快速移动平均线采用的平滑方法(Simple、Exponential、Smoothed、LinearWeighted)。 |
Simple |
SecondMethod |
慢速移动平均线的平滑方法。 | LinearWeighted |
FirstPriceMode |
快速移动平均线使用的价格类型(Close、Open、High、Low、Median、Typical、Weighted)。 |
Close |
SecondPriceMode |
慢速移动平均线使用的价格类型。 | Median |
FirstShift |
快速移动平均线的水平位移(柱数)。 | 0 |
SecondShift |
慢速移动平均线的水平位移。 | 0 |
OrderVolume |
新仓位使用的基础下单手数。 | 0.1 |
CandleType |
策略处理的 K 线类型/周期。 | 5 分钟 K 线 |
与 MQL 版本的差异
- MetaTrader 中的订单遍历 (
OrdersTotal、OrderSelect、OrderClose) 被替换为直接读取 StockSharp 的Strategy.Position属性,并按照需要反向开仓的手数发送市价单。 - 原版利用“新柱”标志避免重复下单;在移植版中,
ProcessCandle仅在每根已完成的 K 线上调用一次,从事件驱动角度自然实现“一柱一次”的行为。 - 通过保留最近
shift + 2个指标值的精简缓存来模拟 MA 位移,无需使用被禁止的指标回溯方法(例如GetValue)。 - 策略本身不绑定经纪商风控,使用
StartProtection()可以接入 StockSharp 的任意保护模块,而不是 MetaTrader 固定的止损/止盈参数。
使用说明
- 选择与原策略一致的周期(例如 M5、H1)。也可以在参数中直接指定其他时间框架。
- 将
FirstShift或SecondShift设置为正值会让交叉信号延后相应数量的已完成柱,与 MetaTrader 中的水平偏移完全一致。 Weighted价格模式复现了 MetaTrader 中的(High + Low + 2 * Close) / 4公式;Median与Typical分别对应(High + Low) / 2和(High + Low + Close) / 3。- 所有订单均为市价单,请确保账户设置允许相应的手数及可能的滑点。
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>
/// Moving average crossover strategy converted from the MetaTrader script "MA_cross_Method_PriceMode".
/// Allows selecting the smoothing method, applied price and horizontal shift for each average.
/// </summary>
public class MaCrossMethodPriceModeStrategy : Strategy
{
private readonly StrategyParam<int> _firstPeriod;
private readonly StrategyParam<int> _secondPeriod;
private readonly StrategyParam<MaMethods> _firstMethod;
private readonly StrategyParam<MaMethods> _secondMethod;
private readonly StrategyParam<AppliedPriceModes> _firstPriceMode;
private readonly StrategyParam<AppliedPriceModes> _secondPriceMode;
private readonly StrategyParam<int> _firstShift;
private readonly StrategyParam<int> _secondShift;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<DataType> _candleType;
private DecimalLengthIndicator _firstMa = null!;
private DecimalLengthIndicator _secondMa = null!;
private readonly List<decimal> _firstValues = new();
private readonly List<decimal> _secondValues = new();
/// <summary>
/// Initializes a new instance of <see cref="MaCrossMethodPriceModeStrategy"/>.
/// </summary>
public MaCrossMethodPriceModeStrategy()
{
_firstPeriod = Param(nameof(FirstPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Fast MA Period", "Length of the first moving average.", "Indicators")
.SetOptimize(2, 50, 1);
_secondPeriod = Param(nameof(SecondPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("Slow MA Period", "Length of the second moving average.", "Indicators")
.SetOptimize(5, 100, 1);
_firstMethod = Param(nameof(FirstMethod), MaMethods.Simple)
.SetDisplay("Fast MA Method", "Smoothing method applied to the first moving average.", "Indicators")
;
_secondMethod = Param(nameof(SecondMethod), MaMethods.LinearWeighted)
.SetDisplay("Slow MA Method", "Smoothing method applied to the second moving average.", "Indicators")
;
_firstPriceMode = Param(nameof(FirstPriceMode), AppliedPriceModes.Close)
.SetDisplay("Fast MA Price", "Price source used for the first moving average.", "Indicators")
;
_secondPriceMode = Param(nameof(SecondPriceMode), AppliedPriceModes.Median)
.SetDisplay("Slow MA Price", "Price source used for the second moving average.", "Indicators")
;
_firstShift = Param(nameof(FirstShift), 0)
.SetNotNegative()
.SetDisplay("Fast MA Shift", "Horizontal shift (in bars) applied to the first moving average.", "Indicators");
_secondShift = Param(nameof(SecondShift), 0)
.SetNotNegative()
.SetDisplay("Slow MA Shift", "Horizontal shift (in bars) applied to the second moving average.", "Indicators");
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Base order volume used for new entries.", "Trading")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for price processing.", "General");
}
/// <summary>
/// Period of the first moving average.
/// </summary>
public int FirstPeriod
{
get => _firstPeriod.Value;
set => _firstPeriod.Value = value;
}
/// <summary>
/// Period of the second moving average.
/// </summary>
public int SecondPeriod
{
get => _secondPeriod.Value;
set => _secondPeriod.Value = value;
}
/// <summary>
/// Smoothing method applied to the first moving average.
/// </summary>
public MaMethods FirstMethod
{
get => _firstMethod.Value;
set => _firstMethod.Value = value;
}
/// <summary>
/// Smoothing method applied to the second moving average.
/// </summary>
public MaMethods SecondMethod
{
get => _secondMethod.Value;
set => _secondMethod.Value = value;
}
/// <summary>
/// Applied price mode for the first moving average.
/// </summary>
public AppliedPriceModes FirstPriceMode
{
get => _firstPriceMode.Value;
set => _firstPriceMode.Value = value;
}
/// <summary>
/// Applied price mode for the second moving average.
/// </summary>
public AppliedPriceModes SecondPriceMode
{
get => _secondPriceMode.Value;
set => _secondPriceMode.Value = value;
}
/// <summary>
/// Shift (in bars) applied to the first moving average values.
/// </summary>
public int FirstShift
{
get => _firstShift.Value;
set => _firstShift.Value = value;
}
/// <summary>
/// Shift (in bars) applied to the second moving average values.
/// </summary>
public int SecondShift
{
get => _secondShift.Value;
set => _secondShift.Value = value;
}
/// <summary>
/// Base order volume used for new positions.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Candle type (timeframe) processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_firstMa = null!;
_secondMa = null!;
_firstValues.Clear();
_secondValues.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_firstMa = CreateMovingAverage(FirstMethod, FirstPeriod);
_secondMa = CreateMovingAverage(SecondMethod, SecondPeriod);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_firstMa, _secondMa, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _firstMa);
DrawIndicator(area, _secondMa);
DrawOwnTrades(area);
}
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle, decimal firstDecimal, decimal secondDecimal)
{
if (candle.State != CandleStates.Finished)
return;
UpdateBuffer(_firstValues, firstDecimal, FirstShift);
UpdateBuffer(_secondValues, secondDecimal, SecondShift);
if (!TryGetShiftedValues(_firstValues, FirstShift, out var firstCurrent, out var firstPrevious))
return;
if (!TryGetShiftedValues(_secondValues, SecondShift, out var secondCurrent, out _))
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var bullishCross = IsBullishCross(firstPrevious, firstCurrent, secondCurrent);
var bearishCross = IsBearishCross(firstPrevious, firstCurrent, secondCurrent);
if (bullishCross && OrderVolume > 0m && Position <= 0m)
{
var volumeToBuy = OrderVolume + (Position < 0m ? Math.Abs(Position) : 0m);
BuyMarket(volumeToBuy);
}
else if (bearishCross && OrderVolume > 0m && Position >= 0m)
{
var volumeToSell = OrderVolume + (Position > 0m ? Position : 0m);
SellMarket(volumeToSell);
}
}
private static void UpdateBuffer(List<decimal> buffer, decimal value, int shift)
{
buffer.Add(value);
var maxCount = Math.Max(shift + 2, 2);
while (buffer.Count > maxCount)
{
buffer.RemoveAt(0);
}
}
private static bool TryGetShiftedValues(IReadOnlyList<decimal> buffer, int shift, out decimal current, out decimal previous)
{
var currentIndex = buffer.Count - 1 - shift;
var previousIndex = buffer.Count - 2 - shift;
if (previousIndex < 0 || currentIndex < 0 || currentIndex >= buffer.Count)
{
current = default;
previous = default;
return false;
}
current = buffer[currentIndex];
previous = buffer[previousIndex];
return true;
}
private static bool IsBullishCross(decimal previousFast, decimal currentFast, decimal currentSlow)
{
return (previousFast <= currentSlow && currentFast > currentSlow)
|| (previousFast < currentSlow && currentFast >= currentSlow);
}
private static bool IsBearishCross(decimal previousFast, decimal currentFast, decimal currentSlow)
{
return (previousFast >= currentSlow && currentFast < currentSlow)
|| (previousFast > currentSlow && currentFast <= currentSlow);
}
private static decimal SelectPrice(ICandleMessage candle, AppliedPriceModes mode)
{
return mode switch
{
AppliedPriceModes.Close => candle.ClosePrice,
AppliedPriceModes.Open => candle.OpenPrice,
AppliedPriceModes.High => candle.HighPrice,
AppliedPriceModes.Low => candle.LowPrice,
AppliedPriceModes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPriceModes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
AppliedPriceModes.Weighted => (candle.HighPrice + candle.LowPrice + (2m * candle.ClosePrice)) / 4m,
_ => candle.ClosePrice
};
}
private static DecimalLengthIndicator CreateMovingAverage(MaMethods method, int period)
{
return method switch
{
MaMethods.Simple => new SMA { Length = period },
MaMethods.Exponential => new EMA { Length = period },
MaMethods.Smoothed => new SmoothedMovingAverage { Length = period },
MaMethods.LinearWeighted => new WeightedMovingAverage { Length = period },
_ => new SMA { Length = period }
};
}
/// <summary>
/// Moving average smoothing methods that mirror the MetaTrader inputs.
/// </summary>
public enum MaMethods
{
Simple,
Exponential,
Smoothed,
LinearWeighted
}
/// <summary>
/// Applied price options equivalent to the MetaTrader constants.
/// </summary>
public enum AppliedPriceModes
{
Close,
Open,
High,
Low,
Median,
Typical,
Weighted
}
}
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 SimpleMovingAverage, WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_cross_method_price_mode_strategy(Strategy):
"""
MA crossover strategy with configurable periods.
"""
def __init__(self):
super(ma_cross_method_price_mode_strategy, self).__init__()
self._first_period = self.Param("FirstPeriod", 3).SetDisplay("Fast MA", "Fast MA period", "Indicators")
self._second_period = self.Param("SecondPeriod", 13).SetDisplay("Slow MA", "Slow MA period", "Indicators")
self._order_volume = self.Param("OrderVolume", 0.1).SetDisplay("Order Volume", "Base order volume", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._first_values = []
self._second_values = []
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_cross_method_price_mode_strategy, self).OnReseted()
self._first_values = []
self._second_values = []
def OnStarted2(self, time):
super(ma_cross_method_price_mode_strategy, self).OnStarted2(time)
first = SimpleMovingAverage()
first.Length = self._first_period.Value
second = WeightedMovingAverage()
second.Length = self._second_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(first, second, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, first)
self.DrawIndicator(area, second)
self.DrawOwnTrades(area)
def _process_candle(self, candle, first_val, second_val):
if candle.State != CandleStates.Finished:
return
f = float(first_val)
s = float(second_val)
self._first_values.append(f)
self._second_values.append(s)
if len(self._first_values) > 4:
self._first_values.pop(0)
if len(self._second_values) > 4:
self._second_values.pop(0)
if len(self._first_values) < 2:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
prev_f = self._first_values[-2]
curr_f = self._first_values[-1]
curr_s = self._second_values[-1]
bull = (prev_f <= curr_s and curr_f > curr_s) or (prev_f < curr_s and curr_f >= curr_s)
bear = (prev_f >= curr_s and curr_f < curr_s) or (prev_f > curr_s and curr_f <= curr_s)
vol = float(self._order_volume.Value)
pos = float(self.Position)
if bull and vol > 0 and pos <= 0:
buy_vol = vol + (Math.Abs(self.Position) if pos < 0 else 0)
self.BuyMarket(buy_vol)
elif bear and vol > 0 and pos >= 0:
sell_vol = vol + (self.Position if pos > 0 else 0)
self.SellMarket(sell_vol)
def CreateClone(self):
return ma_cross_method_price_mode_strategy()