移动平均交易系统策略 (2518)
概述
该策略是 MetaTrader "Moving Average Trade System" 智能交易系统的 StockSharp 版本。策略使用四条基于 K 线中位价的简单移动平均线(SMA)来判断趋势。当中期与长期均线完成确认交叉,同时快速均线给出趋势一致的信号后,策略会按新趋势方向建立或翻转仓位,并通过以价格步长为单位的止盈、止损和移动止损进行风险控制。
交易逻辑
指标
SMA(5):快速均线,使用中位价。SMA(20):中速均线,使用中位价。SMA(40):信号均线,使用中位价。SMA(60):慢速均线,使用中位价。
开多条件
SMA(5) > SMA(20) > SMA(40)。SMA(40)比SMA(60)至少高出SlopeThresholdSteps个价格步长。- 当前柱
SMA(40)向上穿越SMA(60)(上一柱SMA(40)小于或等于慢速均线)。 - 如果当前持有空头,策略会一次性买入足够数量以平空并建立目标多头仓位。
开空条件
SMA(5) < SMA(20) < SMA(40)。SMA(40)比SMA(60)至少低出SlopeThresholdSteps个价格步长。- 当前柱
SMA(40)向下穿越SMA(60)(上一柱SMA(40)大于或等于慢速均线)。 - 如果当前持有多头,策略会一次性卖出足够数量以平多并建立目标空头仓位。
风险管理(仅在当前柱没有新入场信号时评估):
- 趋势反向退出:当
SMA(40) <= SMA(60)时平掉多头;当SMA(40) >= SMA(60)时平掉空头。 - 止盈:价格达到入场价加/减
TakeProfitSteps个价格步长时离场。 - 止损:价格向不利方向波动
StopLossSteps个价格步长时离场。 - 移动止损:价格有利运行后,以
TrailingStopSteps个价格步长跟踪保护,针对多头使用入场后最高价,空头使用入场后最低价。
- 趋势反向退出:当
所有止盈、止损与移动止损的距离均以 价格步长(PriceStep) 为单位。如果标的未提供价格步长,策略会默认使用 1。
参数
| 参数 | 说明 | 默认值 | 可优化 |
|---|---|---|---|
Volume |
建仓时使用的下单数量。 | 1 |
否 |
TakeProfitSteps |
止盈距离(按价格步长计)。 | 50 |
是 |
StopLossSteps |
止损距离(按价格步长计)。 | 50 |
是 |
TrailingStopSteps |
移动止损偏移量(价格步长,0 表示关闭移动止损)。 |
11 |
是 |
SlopeThresholdSteps |
SMA(40) 与 SMA(60) 之间的最小间距,用于过滤噪声(按价格步长计)。 |
1 |
是 |
FastPeriod |
快速 SMA 的周期。 | 5 |
是 |
MediumPeriod |
中速 SMA 的周期。 | 20 |
是 |
SignalPeriod |
与慢速均线比较的信号 SMA 周期。 | 40 |
是 |
SlowPeriod |
描述背景趋势的慢速 SMA 周期。 | 60 |
是 |
CandleType |
计算指标使用的 K 线类型。 | 1 小时 |
否 |
实现细节
- 通过高阶
BindAPI 绑定指标,实现事件驱动的计算方式,无需直接访问指标缓冲区。 - 所有 SMA 均使用中位价,完全贴合原始 MetaTrader 算法的设定。
- 在
OnNewMyTrade中记录成交价,用于实时更新止盈、止损和移动止损水平。 - 当需要反向持仓时,策略发送一笔市场单,同时完成平仓与开仓,复制了原始 EA 在对冲账户上的行为。
- C# 源码中的所有注释均为英文,以满足仓库规范。
使用建议
- 根据标的合约规模或最小交易量调整
Volume参数。 - 依据市场波动性调整止盈止损距离(默认值对应外汇中 50 点止盈/止损以及 11 点移动止损)。
- 将
SlopeThresholdSteps设为0可去掉额外的距离过滤,使策略对任何SMA(40)与SMA(60)的交叉作出反应。 - 在回测或实时交易前,请确保标的提供有效的
PriceStep;若无,策略会默认把 1 个价格单位视为一个步长。
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 Trade System originally written for MetaTrader.
/// Uses four simple moving averages on median price to detect medium-term trend reversals.
/// </summary>
public class MovingAverageTradeSystemStrategy : Strategy
{
private readonly StrategyParam<decimal> _takeProfitSteps;
private readonly StrategyParam<decimal> _stopLossSteps;
private readonly StrategyParam<decimal> _trailingStopSteps;
private readonly StrategyParam<decimal> _slopeThresholdSteps;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _mediumPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _smaFast;
private SimpleMovingAverage _smaMedium;
private SimpleMovingAverage _smaSignal;
private SimpleMovingAverage _smaSlow;
private decimal? _previousSignal;
private decimal? _previousSlow;
private decimal? _longEntryPrice;
private decimal? _longTakeProfit;
private decimal? _longStopLoss;
private decimal _longHigh;
private decimal? _shortEntryPrice;
private decimal? _shortTakeProfit;
private decimal? _shortStopLoss;
private decimal _shortLow;
/// <summary>
/// Desired take profit distance in price steps.
/// </summary>
public decimal TakeProfitSteps
{
get => _takeProfitSteps.Value;
set => _takeProfitSteps.Value = value;
}
/// <summary>
/// Desired stop loss distance in price steps.
/// </summary>
public decimal StopLossSteps
{
get => _stopLossSteps.Value;
set => _stopLossSteps.Value = value;
}
/// <summary>
/// Trailing stop offset in price steps.
/// </summary>
public decimal TrailingStopSteps
{
get => _trailingStopSteps.Value;
set => _trailingStopSteps.Value = value;
}
/// <summary>
/// Minimum separation between the signal SMA and the slow SMA (in price steps) to validate breakouts.
/// </summary>
public decimal SlopeThresholdSteps
{
get => _slopeThresholdSteps.Value;
set => _slopeThresholdSteps.Value = value;
}
/// <summary>
/// Fast SMA period (originally 5).
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Medium SMA period (originally 20).
/// </summary>
public int MediumPeriod
{
get => _mediumPeriod.Value;
set => _mediumPeriod.Value = value;
}
/// <summary>
/// Signal SMA period that must cross the slow SMA upward or downward (originally 40).
/// </summary>
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
/// <summary>
/// Slow SMA period defining the background trend (originally 60).
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Candle type for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="MovingAverageTradeSystemStrategy"/> class.
/// </summary>
public MovingAverageTradeSystemStrategy()
{
_takeProfitSteps = Param(nameof(TakeProfitSteps), 50m)
.SetGreaterThanZero()
.SetDisplay("Take Profit (steps)", "Distance to take profit in price steps", "Risk Management")
.SetOptimize(10m, 200m, 10m);
_stopLossSteps = Param(nameof(StopLossSteps), 50m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (steps)", "Distance to stop loss in price steps", "Risk Management")
.SetOptimize(10m, 200m, 10m);
_trailingStopSteps = Param(nameof(TrailingStopSteps), 11m)
.SetNotNegative()
.SetDisplay("Trailing Stop (steps)", "Trailing stop offset in price steps", "Risk Management")
.SetOptimize(0m, 100m, 5m);
_slopeThresholdSteps = Param(nameof(SlopeThresholdSteps), 10m)
.SetNotNegative()
.SetDisplay("Slope Threshold", "Minimum SMA40 vs SMA60 distance in steps", "Signals")
.SetOptimize(0m, 10m, 1m);
_fastPeriod = Param(nameof(FastPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Fast SMA", "Fast SMA length", "Signals")
.SetOptimize(3, 20, 1);
_mediumPeriod = Param(nameof(MediumPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Medium SMA", "Medium SMA length", "Signals")
.SetOptimize(10, 60, 1);
_signalPeriod = Param(nameof(SignalPeriod), 40)
.SetGreaterThanZero()
.SetDisplay("Signal SMA", "Crossing SMA length", "Signals")
.SetOptimize(20, 80, 1);
_slowPeriod = Param(nameof(SlowPeriod), 60)
.SetGreaterThanZero()
.SetDisplay("Slow SMA", "Slow SMA length", "Signals")
.SetOptimize(30, 120, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle type for calculations", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousSignal = null;
_previousSlow = null;
ResetLongState();
ResetShortState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create the moving averages on median price to match the original indicator setup.
_smaFast = new SMA { Length = FastPeriod };
_smaMedium = new SMA { Length = MediumPeriod };
_smaSignal = new SMA { Length = SignalPeriod };
_smaSlow = new SMA { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_smaFast, _smaMedium, _smaSignal, _smaSlow, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _smaFast);
DrawIndicator(area, _smaMedium);
DrawIndicator(area, _smaSignal);
DrawIndicator(area, _smaSlow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaFast, decimal smaMedium, decimal smaSignal, decimal smaSlow)
{
if (candle.State != CandleStates.Finished)
return;
if (!_smaFast.IsFormed || !_smaMedium.IsFormed || !_smaSignal.IsFormed || !_smaSlow.IsFormed)
{
_previousSignal = smaSignal;
_previousSlow = smaSlow;
return;
}
var previousSignal = _previousSignal;
var previousSlow = _previousSlow;
_previousSignal = smaSignal;
_previousSlow = smaSlow;
if (previousSignal is null || previousSlow is null)
return;
var priceStep = GetPriceStep();
var slopeThreshold = SlopeThresholdSteps * priceStep;
var bullishStructure = smaFast > smaMedium && smaMedium > smaSlow;
var bearishStructure = smaFast < smaMedium && smaMedium < smaSlow;
var bullishSlope = (smaSignal - smaSlow) >= slopeThreshold;
var bearishSlope = (smaSlow - smaSignal) >= slopeThreshold;
var bullishCross = previousSignal.Value <= previousSlow.Value && smaSignal > smaSlow;
var bearishCross = previousSignal.Value >= previousSlow.Value && smaSignal < smaSlow;
var buySignal = bullishStructure && bullishSlope && bullishCross;
var sellSignal = bearishStructure && bearishSlope && bearishCross;
if (buySignal && Position <= 0m)
{
// Flip the position by buying enough volume to cover shorts and add the desired long exposure.
var volume = Volume + (Position < 0m ? Math.Abs(Position) : 0m);
if (volume > 0m)
BuyMarket();
}
else if (sellSignal && Position >= 0m)
{
// Flip the position by selling enough volume to cover longs and add the desired short exposure.
var volume = Volume + (Position > 0m ? Position : 0m);
if (volume > 0m)
SellMarket();
}
else
{
// Manage open positions when no new entry is triggered this bar.
if (Position > 0m)
{
if (smaSignal <= smaSlow)
{
SellMarket();
}
else
{
ManageLongPosition(candle, priceStep);
}
}
else if (Position < 0m)
{
if (smaSignal >= smaSlow)
{
BuyMarket();
}
else
{
ManageShortPosition(candle, priceStep);
}
}
}
}
private void ManageLongPosition(ICandleMessage candle, decimal priceStep)
{
if (!_longEntryPrice.HasValue)
return;
_longHigh = Math.Max(_longHigh, candle.HighPrice);
if (_longTakeProfit.HasValue && candle.HighPrice >= _longTakeProfit.Value)
{
SellMarket();
return;
}
if (_longStopLoss.HasValue && candle.LowPrice <= _longStopLoss.Value)
{
SellMarket();
return;
}
if (TrailingStopSteps <= 0m)
return;
var trailingLevel = _longHigh - TrailingStopSteps * priceStep;
if (candle.ClosePrice <= trailingLevel)
SellMarket();
}
private void ManageShortPosition(ICandleMessage candle, decimal priceStep)
{
if (!_shortEntryPrice.HasValue)
return;
_shortLow = Math.Min(_shortLow, candle.LowPrice);
if (_shortTakeProfit.HasValue && candle.LowPrice <= _shortTakeProfit.Value)
{
BuyMarket();
return;
}
if (_shortStopLoss.HasValue && candle.HighPrice >= _shortStopLoss.Value)
{
BuyMarket();
return;
}
if (TrailingStopSteps <= 0m)
return;
var trailingLevel = _shortLow + TrailingStopSteps * priceStep;
if (candle.ClosePrice >= trailingLevel)
BuyMarket();
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
var price = trade.Trade?.Price;
if (price is null)
return;
if (Position > 0m)
{
// After switching to long reset the short tracking and configure profit/stop levels.
ResetShortState();
SetupLongState(price.Value);
}
else if (Position < 0m)
{
// After switching to short reset the long tracking and configure profit/stop levels.
ResetLongState();
SetupShortState(price.Value);
}
else
{
// Flat position clears both trackers.
ResetLongState();
ResetShortState();
}
}
private void SetupLongState(decimal entryPrice)
{
var priceStep = GetPriceStep();
_longEntryPrice = entryPrice;
_longHigh = entryPrice;
_longTakeProfit = TakeProfitSteps > 0m ? entryPrice + TakeProfitSteps * priceStep : null;
_longStopLoss = StopLossSteps > 0m ? entryPrice - StopLossSteps * priceStep : null;
}
private void SetupShortState(decimal entryPrice)
{
var priceStep = GetPriceStep();
_shortEntryPrice = entryPrice;
_shortLow = entryPrice;
_shortTakeProfit = TakeProfitSteps > 0m ? entryPrice - TakeProfitSteps * priceStep : null;
_shortStopLoss = StopLossSteps > 0m ? entryPrice + StopLossSteps * priceStep : null;
}
private void ResetLongState()
{
_longEntryPrice = null;
_longTakeProfit = null;
_longStopLoss = null;
_longHigh = 0m;
}
private void ResetShortState()
{
_shortEntryPrice = null;
_shortTakeProfit = null;
_shortStopLoss = null;
_shortLow = 0m;
}
private decimal GetPriceStep()
{
var step = Security?.PriceStep;
return step is null || step == 0m ? 1m : step.Value;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class moving_average_trade_system_strategy(Strategy):
def __init__(self):
super(moving_average_trade_system_strategy, self).__init__()
self._take_profit_steps = self.Param("TakeProfitSteps", 50.0)
self._stop_loss_steps = self.Param("StopLossSteps", 50.0)
self._trailing_stop_steps = self.Param("TrailingStopSteps", 11.0)
self._slope_threshold_steps = self.Param("SlopeThresholdSteps", 10.0)
self._fast_period = self.Param("FastPeriod", 5)
self._medium_period = self.Param("MediumPeriod", 20)
self._signal_period = self.Param("SignalPeriod", 40)
self._slow_period = self.Param("SlowPeriod", 60)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._prev_signal = None
self._prev_slow = None
self._long_entry_price = None
self._long_take_profit = None
self._long_stop_loss = None
self._long_high = 0.0
self._short_entry_price = None
self._short_take_profit = None
self._short_stop_loss = None
self._short_low = 0.0
self._sma_fast = None
self._sma_medium = None
self._sma_signal = None
self._sma_slow = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(moving_average_trade_system_strategy, self).OnReseted()
self._prev_signal = None
self._prev_slow = None
self._reset_long_state()
self._reset_short_state()
def OnStarted2(self, time):
super(moving_average_trade_system_strategy, self).OnStarted2(time)
self._sma_fast = SimpleMovingAverage()
self._sma_fast.Length = self._fast_period.Value
self._sma_medium = SimpleMovingAverage()
self._sma_medium.Length = self._medium_period.Value
self._sma_signal = SimpleMovingAverage()
self._sma_signal.Length = self._signal_period.Value
self._sma_slow = SimpleMovingAverage()
self._sma_slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._sma_fast, self._sma_medium, self._sma_signal, self._sma_slow, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._sma_fast)
self.DrawIndicator(area, self._sma_medium)
self.DrawIndicator(area, self._sma_signal)
self.DrawIndicator(area, self._sma_slow)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, medium_val, signal_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
medium = float(medium_val)
sig = float(signal_val)
slow = float(slow_val)
if not self._sma_fast.IsFormed or not self._sma_medium.IsFormed or not self._sma_signal.IsFormed or not self._sma_slow.IsFormed:
self._prev_signal = sig
self._prev_slow = slow
return
prev_signal = self._prev_signal
prev_slow = self._prev_slow
self._prev_signal = sig
self._prev_slow = slow
if prev_signal is None or prev_slow is None:
return
price_step = self._get_price_step()
slope_threshold = float(self._slope_threshold_steps.Value) * price_step
bullish_structure = fast > medium and medium > slow
bearish_structure = fast < medium and medium < slow
bullish_slope = (sig - slow) >= slope_threshold
bearish_slope = (slow - sig) >= slope_threshold
bullish_cross = prev_signal <= prev_slow and sig > slow
bearish_cross = prev_signal >= prev_slow and sig < slow
buy_signal = bullish_structure and bullish_slope and bullish_cross
sell_signal = bearish_structure and bearish_slope and bearish_cross
if buy_signal and self.Position <= 0:
self.BuyMarket()
elif sell_signal and self.Position >= 0:
self.SellMarket()
else:
if self.Position > 0:
if sig <= slow:
self.SellMarket()
else:
self._manage_long(candle, price_step)
elif self.Position < 0:
if sig >= slow:
self.BuyMarket()
else:
self._manage_short(candle, price_step)
def _manage_long(self, candle, price_step):
if self._long_entry_price is None:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._long_high = max(self._long_high, high)
if self._long_take_profit is not None and high >= self._long_take_profit:
self.SellMarket()
return
if self._long_stop_loss is not None and low <= self._long_stop_loss:
self.SellMarket()
return
trailing = float(self._trailing_stop_steps.Value)
if trailing <= 0:
return
trailing_level = self._long_high - trailing * price_step
if close <= trailing_level:
self.SellMarket()
def _manage_short(self, candle, price_step):
if self._short_entry_price is None:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._short_low = min(self._short_low, low)
if self._short_take_profit is not None and low <= self._short_take_profit:
self.BuyMarket()
return
if self._short_stop_loss is not None and high >= self._short_stop_loss:
self.BuyMarket()
return
trailing = float(self._trailing_stop_steps.Value)
if trailing <= 0:
return
trailing_level = self._short_low + trailing * price_step
if close >= trailing_level:
self.BuyMarket()
def OnOwnTradeReceived(self, trade):
super(moving_average_trade_system_strategy, self).OnOwnTradeReceived(trade)
t = trade.Trade
if t is None:
return
price = float(t.Price)
if self.Position > 0:
self._reset_short_state()
self._setup_long_state(price)
elif self.Position < 0:
self._reset_long_state()
self._setup_short_state(price)
else:
self._reset_long_state()
self._reset_short_state()
def _setup_long_state(self, entry_price):
price_step = self._get_price_step()
self._long_entry_price = entry_price
self._long_high = entry_price
tp = float(self._take_profit_steps.Value)
sl = float(self._stop_loss_steps.Value)
self._long_take_profit = entry_price + tp * price_step if tp > 0 else None
self._long_stop_loss = entry_price - sl * price_step if sl > 0 else None
def _setup_short_state(self, entry_price):
price_step = self._get_price_step()
self._short_entry_price = entry_price
self._short_low = entry_price
tp = float(self._take_profit_steps.Value)
sl = float(self._stop_loss_steps.Value)
self._short_take_profit = entry_price - tp * price_step if tp > 0 else None
self._short_stop_loss = entry_price + sl * price_step if sl > 0 else None
def _reset_long_state(self):
self._long_entry_price = None
self._long_take_profit = None
self._long_stop_loss = None
self._long_high = 0.0
def _reset_short_state(self):
self._short_entry_price = None
self._short_take_profit = None
self._short_stop_loss = None
self._short_low = 0.0
def _get_price_step(self):
sec = self.Security
if sec is not None:
step = sec.PriceStep
if step is not None and float(step) != 0.0:
return float(step)
return 1.0
def CreateClone(self):
return moving_average_trade_system_strategy()