在 GitHub 上查看
Averaging By Signal 策略
概述
Averaging By Signal Strategy 将 MetaTrader 顾问 AveragingBySignal.mq4 迁移到 StockSharp 的高层 API。原始 EA 通过快慢均线交叉发出信号,并结合马丁式加仓、共享的篮子止盈以及仅对第一笔订单生效的可选移动止损。本移植版在 C# 中重建这些模块,同时适配 StockSharp 的净额模式和指标系统。
交易逻辑
- 根据参数
CandleType 订阅指定周期的K线,并使用所选周期与算法 (FastPeriod/FastMethod, SlowPeriod/SlowMethod) 计算两条均线。
- 仅处理已完成的K线:每当一根K线收盘时,对比两条均线的前值和当前值以检测交叉。
- 产生信号:
- 快线自下而上穿越慢线 → 看多信号;
- 快线自上而下跌破慢线 → 看空信号;
- 其余情况保持观望。
- 出现新的多头信号且当前没有多头篮子时,按照头寸管理模块给出的基准手数买入。
- 出现新的空头信号且当前没有空头篮子时,卖出开仓。
- 加仓规则:
LayerDistancePips 控制下一层加仓的最小逆向距离(单位为 MetaTrader pips);
- 若
AveragingBySignal = true,则只有在同向信号重新出现时才允许加仓;若为 false,达到价位即可加仓;
- 空头加仓遵循对称逻辑;
- 每层手数由
LotSizing 模式计算,并受 MaxLayers 限制。
- 篮子管理:
- 所有成交按 FIFO 存储,以便恢复多空篮子的加权平均价;
- 平均价加/减
TakeProfitPips 形成共享止盈,一旦收盘价触达该水平即平掉整个篮子;
- 若启用
EnableTrailing 且篮子中仅有一笔订单,在浮盈达到 TrailingStartPips 后启动移动止损,并在价格每前进 TrailingStepPips 后上调止损。
- 策略运行在净额账户中:当方向反转时,新订单会先抵消旧仓位再开启新的篮子。
手数与点值
InitialVolume 为首单手数;当 LotSizing = Multiplier 时,第 n 层的手数为 InitialVolume * Multiplier^n,与 MQL 中的 LotType 相同。
- 请求的手数会根据交易品种的
VolumeStep、MinVolume、MaxVolume 自动调整,确保委托合法。
- 点值通过
Security.PriceStep 计算,并复制原EA的奇数位调整:五位报价转换为 0.0001 点。
参数
| 名称 |
类型 |
默认值 |
说明 |
CandleType |
DataType |
1小时K线 |
指标使用的时间框架。 |
InitialVolume |
decimal |
0.1 |
篮子首单手数。 |
LotSizing |
LotSizingMode |
Multiplier |
固定手数或倍数加仓模式。 |
Multiplier |
decimal |
2 |
在倍数模式下的手数放大倍数。 |
FastPeriod |
int |
28 |
快线周期。 |
FastMethod |
MovingAverageMethod |
LinearWeighted |
快线算法。 |
SlowPeriod |
int |
50 |
慢线周期。 |
SlowMethod |
MovingAverageMethod |
Smoothed |
慢线算法。 |
TakeProfitPips |
int |
15 |
共享止盈距离(0 代表关闭)。 |
AveragingBySignal |
bool |
true |
是否要求新信号才能加仓。 |
LayerDistancePips |
decimal |
10 |
加仓前需要的最小逆向幅度(pips)。 |
MaxLayers |
int |
10 |
同向最大订单数(含首单)。 |
EnableTrailing |
bool |
false |
启用单笔订单的移动止损。 |
TrailingStartPips |
decimal |
10 |
启动移动止损所需的浮盈。 |
TrailingStepPips |
decimal |
1 |
每次上调止损所需的额外前进幅度。 |
与原版的差异
- MetaTrader 允许对冲持仓,而 StockSharp 使用净额模式;因此方向切换时,新订单会先平掉反向仓位。
- 共享止盈通过一次性平仓实现,而非对每张单独调用
OrderModify。
- 移动止损基于收盘价触发。原版在每个tick更新止损,因此本移植可能稍晚触发,但阈值保持一致。
- MQL 中的保证金检测与滑点处理已移除,因为这些校验由 StockSharp 连接器负责。
使用建议
- 确保证券元数据(
PriceStep、VolumeStep、最小/最大手数)正确,以获得准确的点值和手数换算。
- 请保持
FastPeriod 严格小于 SlowPeriod,否则策略会自动停止以避免无效的交叉条件。
- 若希望以纯网格方式加仓,可关闭
AveragingBySignal。
- 因为退出逻辑基于收盘价,较低周期能更快响应,但同时会增加噪音和加仓频率。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Averaging By Signal strategy: WMA crossover with averaging logic.
/// Buys when fast WMA crosses above slow WMA, sells on cross below.
/// </summary>
public class AveragingBySignalStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public AveragingBySignalStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast WMA", "Fast WMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow WMA", "Slow WMA period", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_hasPrev = false;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (_hasPrev)
{
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
BuyMarket();
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
SellMarket();
}
else
{
if (fastValue > slowValue && Position <= 0)
BuyMarket();
else if (fastValue < slowValue && Position >= 0)
SellMarket();
}
_prevFast = fastValue;
_prevSlow = slowValue;
_hasPrev = true;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class averaging_by_signal_strategy(Strategy):
def __init__(self):
super(averaging_by_signal_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._fast_period = self.Param("FastPeriod", 10)
self._slow_period = self.Param("SlowPeriod", 30)
self._prev_fast = 0.0
self._prev_slow = 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 FastPeriod(self):
return self._fast_period.Value
@FastPeriod.setter
def FastPeriod(self, value):
self._fast_period.Value = value
@property
def SlowPeriod(self):
return self._slow_period.Value
@SlowPeriod.setter
def SlowPeriod(self, value):
self._slow_period.Value = value
def OnReseted(self):
super(averaging_by_signal_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(averaging_by_signal_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
fast = ExponentialMovingAverage()
fast.Length = self.FastPeriod
slow = ExponentialMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, self._process_candle).Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if self._has_prev:
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
self.SellMarket()
else:
if fast_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif fast_val < slow_val and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
self._has_prev = True
def CreateClone(self):
return averaging_by_signal_strategy()