在 GitHub 上查看
FiftyFiveMaBarComparisonStrategy
概述
该策略复刻 MetaTrader 5 的“55 MA”专家顾问,通过比较 55 周期移动平均线在两个柱上的数值,只要差值超过可配置阈值就执行交易。所有计算都基于已收盘的K线,并限制在自定义的日内时间窗口内进行,同时可以选择反转信号方向。算法保持原版EA的行为——如果没有满足做多条件,会默认开空单。
交易逻辑
- 订阅指定的K线类型,并根据所选周期、方法和价格类型计算移动平均线。
- 维护一个最近的均线数值缓冲区,即便设置了水平位移也能访问到
BarA 和 BarB 的均线值。
- 当收到位于
[StartHour, EndHour) 时段内的收盘K线时:
- 读取
BarA + MaShift 与 BarB + MaShift 处的均线值。
- 如果
BarA 处的均线值大于 BarB 处的均线值加上 DifferenceThreshold,则在未启用 ReverseSignals 时买入,启用时卖出。
- 如果
BarA 处的均线值小于 BarB 处的均线值减去 DifferenceThreshold,则在未启用 ReverseSignals 时卖出,启用时买入。
- 若差值未达到阈值,策略沿用原EA的默认行为并触发卖出。
- 始终使用策略的
Volume 以市价下单;当 CloseOppositePositions 启用时,会增加下单数量以先平掉反向持仓。
- 可选的止损和止盈通过
StartProtection 添加,距离以点(pip)表示。对于报价小数位为3或5位的品种,1 pip 等于 PriceStep 乘以 10。
参数
| 名称 |
类型 |
默认值 |
说明 |
CandleType |
DataType |
1分钟周期 |
用于计算和发出信号的K线序列。 |
StopLossPips |
int |
30 |
止损距离(单位:pip)。设为 0 表示禁用。 |
TakeProfitPips |
int |
50 |
止盈距离(单位:pip)。设为 0 表示禁用。 |
StartHour |
int |
8 |
交易窗口的起始小时(含)。 |
EndHour |
int |
21 |
交易窗口的结束小时(不含),必须大于 StartHour。 |
DifferenceThreshold |
decimal |
0.0001 |
触发方向信号所需的最小均线差值。 |
BarA |
int |
0 |
均线比较的第一个柱索引(0 表示当前K线)。 |
BarB |
int |
1 |
均线比较的第二个柱索引。 |
ReverseSignals |
bool |
false |
反转做多/做空条件。 |
CloseOppositePositions |
bool |
false |
若启用,在开新单前先平掉反向持仓。 |
MaShift |
int |
0 |
均线的水平位移,正值访问更早的均线点。 |
MaLength |
int |
55 |
移动平均线周期。 |
MaMethod |
MovingAverageMethods |
Exponential |
均线类型(Simple、Exponential、Smoothed、Weighted)。 |
AppliedPrice |
AppliedPriceTypes |
Median |
均线所使用的价格(Close、Open、High、Low、Median、Typical、Weighted)。 |
仓位管理
- 通过设置策略
Volume 控制基础下单数量;启用 CloseOppositePositions 时会自动叠加当前反向仓位的绝对值。
- 仅当止损或止盈的pip距离大于0时才会调用
StartProtection 添加保护。
备注
- 交易时间窗口基于标的物的时间,处于
[StartHour, EndHour) 之外的信号会被忽略。
- 当
MaShift 产生负索引时,策略会等待更多历史数据,与原EA在位移导致 EMPTY_VALUE 时的处理方式一致。
- 原始EA在差值未达阈值时默认卖出,本策略保留该特性;若不希望如此,可调大
DifferenceThreshold。
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// 55 MA bar comparison strategy. Compares candle body with MA direction.
/// </summary>
public class FiftyFiveMaBarComparisonStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maPeriod;
private decimal? _prevMa;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
public FiftyFiveMaBarComparisonStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_maPeriod = Param(nameof(MaPeriod), 55)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Moving average period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMa = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMa = null;
var sma = new SimpleMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevMa = maVal;
return;
}
if (_prevMa == null)
{
_prevMa = maVal;
return;
}
var close = candle.ClosePrice;
var bullishBar = candle.ClosePrice > candle.OpenPrice;
var bearishBar = candle.ClosePrice < candle.OpenPrice;
var maRising = maVal > _prevMa.Value;
var maFalling = maVal < _prevMa.Value;
_prevMa = maVal;
// Bullish bar + rising MA + close above MA → buy
if (bullishBar && maRising && close > maVal && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Bearish bar + falling MA + close below MA → sell
else if (bearishBar && maFalling && close < maVal && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
}
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 fifty_five_ma_bar_comparison_strategy(Strategy):
def __init__(self):
super(fifty_five_ma_bar_comparison_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._ma_period = self.Param("MaPeriod", 55) \
.SetDisplay("MA Period", "Moving average period", "Indicators")
self._prev_ma = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def MaPeriod(self):
return self._ma_period.Value
def OnReseted(self):
super(fifty_five_ma_bar_comparison_strategy, self).OnReseted()
self._prev_ma = None
def OnStarted2(self, time):
super(fifty_five_ma_bar_comparison_strategy, self).OnStarted2(time)
self._prev_ma = None
sma = SimpleMovingAverage()
sma.Length = self.MaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(sma, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _on_process(self, candle, ma_value):
if candle.State != CandleStates.Finished:
return
mv = float(ma_value)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_ma = mv
return
if self._prev_ma is None:
self._prev_ma = mv
return
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
bullish_bar = close > open_price
bearish_bar = close < open_price
ma_rising = mv > self._prev_ma
ma_falling = mv < self._prev_ma
self._prev_ma = mv
# Bullish bar + rising MA + close above MA
if bullish_bar and ma_rising and close > mv and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Bearish bar + falling MA + close below MA
elif bearish_bar and ma_falling and close < mv and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return fifty_five_ma_bar_comparison_strategy()