在 GitHub 上查看
MA Mirror 策略
概述
MA Mirror 策略是 MetaTrader 专家顾问 MA_MirrorEA 的移植版本。系统比较两个使用同一周期的简单移动平均线,但
分别基于蜡烛的收盘价和开盘价计算。当收盘价均线高于开盘价均线时,策略倾向于做多;当收盘价均线低于开盘价均线时,
策略倾向于做空。可配置的 MovingShift 参数允许从更早的蜡烛中读取均线数值,从而复现 MetaTrader 指标向右偏移
的视觉效果。
StockSharp 版本保持了原始 EA 的“镜像”行为:任意时刻只有一个净持仓,信号翻转时会先平掉原持仓,然后开立反向新
仓。此外,策略启动时默认持有一个虚拟的空头信号,因此只有当收盘均线首次上穿开盘均线后,才会发出第一笔真实交易。
交易逻辑
- 订阅由
CandleType 指定的蜡烛序列,并且只处理已经完成的蜡烛,避免在未收盘的数据上做决定。
- 使用蜡烛的收盘价和开盘价分别驱动两条简单移动平均线。两条均线共享同一个
MovingPeriod,方便直接比较。
- 将最新的均线数值存入环形缓冲区。缓冲区能够返回
MovingShift 根蜡烛之前的数值,从而在不调用受限指标方法的
情况下模拟 MetaTrader 的位移参数。
- 如果位移后的收盘均线高于开盘均线,则产生 买入 信号;如果低于则产生 卖出 信号;若两条均线相等,则保
持上一笔信号。
- 如果这是策略的首个信号并且不是看多信号,则保持空仓。否则,当目标信号与最近一次执行的信号不同时,先平掉当前净
持仓,再按照
TradeVolume 的数量开立新的市场订单。
- 更新内部的最新信号,使后续蜡烛在持仓方向未变化时不会重复下单。
参数
| 名称 |
类型 |
默认值 |
说明 |
CandleType |
DataType |
1 分钟周期 |
策略处理的主要蜡烛时间框架。 |
MovingPeriod |
int |
20 |
作用于收盘价和开盘价的简单移动平均线长度。 |
MovingShift |
int |
0 |
向后偏移的已完成蜡烛数量,用于读取更早的均线数值。 |
TradeVolume |
decimal |
1 |
每次下市场单使用的数量。 |
- MQL 附带的资金管理辅助功能(止损、止盈、追踪止损)没有迁移过来。StockSharp 版本始终按照固定的
TradeVolume
下单,需要额外的风控由用户自行实现。
- MetaTrader 以订单为单位管理持仓,而 StockSharp 使用净头寸模式。移植版本会在开立反向仓位之前先平掉现有头寸,
使最终持仓与原 EA 的单票结构保持一致。
- 指标计算通过 StockSharp 的蜡烛订阅 API 配合
SimpleMovingAverage 指标及内部缓冲区完成,而不是直接调用 iMA
函数。
使用建议
- 在启动策略前调整
TradeVolume,使其符合交易品种的最小手数。构造函数也会同步设置 Strategy.Volume,因此帮
助方法会以期望的手数下单。
- 如果希望读取更早蜡烛的均线数值,可增大
MovingShift,以匹配 MetaTrader 图表中向右偏移的效果。
- 将策略添加到图表后可以同时查看蜡烛、两条均线以及成交记录,从而验证翻转是否准确发生在收盘均线与开盘均线交叉的
时刻。
使用的指标
- 两条简单移动平均线(分别基于收盘价和开盘价),拥有相同的周期并支持向后偏移。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
public class MaMirrorStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _movingPeriod;
private SimpleMovingAverage _sma;
private decimal? _prevDiff;
public MaMirrorStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle type", "Timeframe processed by the strategy.", "General");
_movingPeriod = Param(nameof(MovingPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Moving period", "Length of the SMA.", "Indicator");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int MovingPeriod
{
get => _movingPeriod.Value;
set => _movingPeriod.Value = value;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_prevDiff = null;
_sma = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevDiff = null;
_sma = new SimpleMovingAverage { Length = MovingPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_sma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_sma.IsFormed)
return;
// Mirror: compare close vs SMA (of close). When close crosses above SMA = buy, below = sell.
var diff = candle.ClosePrice - smaValue;
if (_prevDiff is not null)
{
if (_prevDiff.Value <= 0 && diff > 0 && Position <= 0)
{
BuyMarket();
}
else if (_prevDiff.Value >= 0 && diff < 0 && Position >= 0)
{
SellMarket();
}
}
_prevDiff = diff;
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_mirror_strategy(Strategy):
"""
Mirror: close vs SMA crossover. Buy when close crosses above, sell below.
"""
def __init__(self):
super(ma_mirror_strategy, self).__init__()
self._moving_period = self.Param("MovingPeriod", 10).SetDisplay("SMA Period", "SMA length", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._prev_diff = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_mirror_strategy, self).OnReseted()
self._prev_diff = None
def OnStarted2(self, time):
super(ma_mirror_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self._moving_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
sma = float(sma_val)
diff = close - sma
if self._prev_diff is not None:
if self._prev_diff <= 0 and diff > 0 and self.Position <= 0:
self.BuyMarket()
elif self._prev_diff >= 0 and diff < 0 and self.Position >= 0:
self.SellMarket()
self._prev_diff = diff
def CreateClone(self):
return ma_mirror_strategy()