在 GitHub 上查看
GlamTrader 策略
GlamTrader 策略 将 MetaTrader 专家顾问 GlamTrader.mq5 移植到 StockSharp 的高级 API。原版算法结合了位移移动平均线、Laguerre RSI 振荡器和 Awesome Oscillator,只在动量满足条件时打开单向仓位。移植版本完整保留决策流程与资金管理,并使用 StockSharp 提供的下单、图表和保护功能。
核心流程
- 订阅
CandleType 指定的 K 线序列(默认 M15),所有指标都基于该时间框计算。
- 按照
AppliedPrice 指定的价格源构建可配置的移动平均线,并通过 MaShift 向右位移若干柱,以复现 MetaTrader 中的缓冲区。
- 在策略内部复刻四阶 Laguerre RSI 滤波器,
LaguerreGamma 控制平滑系数,输出范围保持在 [0, 1],与原始 laguerre.mq5 指标一致。
- 计算 Awesome Oscillator(5/34 周期的中价 SMA 之差),并保存当前值与上一柱的值用于判断斜率。
- 当且仅当没有持仓时:
- 做多:移动平均线位于当前收盘价之上,Laguerre RSI 大于
0.15,Awesome Oscillator 相比上一柱上升。
- 做空:移动平均线位于当前收盘价之下,Laguerre RSI 小于
0.75,Awesome Oscillator 相比上一柱下降。
- 入场时,将止损/止盈距离从点数换算为价格偏移,自动针对三位或五位报价应用
Point * 10 的调整,完全复刻 MQL 的计算方式。
- 持仓期间复制原始的追踪止损逻辑:当价格走出
TrailingStopPips + TrailingStepPips 的利润后,把止损锁定在距离当前价 TrailingStopPips 的位置;一旦蜡烛最高/最低触及止损或止盈水平就立即离场。
入场与出场
- 策略始终只持有一笔仓位,反向信号会等待当前仓位平仓后才生效。
- 做多需要价格向上突破位移均线、Laguerre RSI 脱离超卖区(
> 0.15)并伴随 Awesome Oscillator 上升动量。
- 做空需要价格跌破位移均线、Laguerre RSI 脱离超买区(
< 0.75)并伴随 Awesome Oscillator 下行动量。
- 止损与止盈通过比较蜡烛的最高/最低价来触发,因此即便逻辑在收盘时运行,也能捕捉到盘中触及。
- 追踪止损完全遵循 MQL 规则:只有在利润超过“止损距离 + 步长”时才会移动,且不会回撤。
参数说明
| 参数 |
类型 |
默认值 |
说明 |
CandleType |
DataType |
TimeSpan.FromMinutes(15).TimeFrame() |
指标与信号使用的时间框。 |
TradeVolume |
decimal |
1 |
每次进场的下单数量。 |
StopLossBuyPips |
decimal |
50 |
多单止损距离(点)。 |
TakeProfitBuyPips |
decimal |
50 |
多单止盈距离(点)。 |
StopLossSellPips |
decimal |
50 |
空单止损距离(点)。 |
TakeProfitSellPips |
decimal |
50 |
空单止盈距离(点)。 |
TrailingStopPips |
decimal |
5 |
追踪止损距离(点),设置为 0 可关闭追踪功能。 |
TrailingStepPips |
decimal |
15 |
每次移动止损前所需的额外利润(点)。 |
MaPeriod |
int |
14 |
移动平均周期。 |
MaShift |
int |
1 |
移动平均向右位移的柱数。 |
MaMethod |
MaMethod |
LinearWeighted |
移动平均类型(简单、指数、平滑或线性加权)。 |
AppliedPrice |
AppliedPrice |
Weighted |
移动平均与 Laguerre 滤波使用的价格来源。 |
LaguerreGamma |
decimal |
0.7 |
Laguerre 滤波平滑系数(0–1)。 |
使用建议
- 选择目标证券,确保经纪模型提供
PriceStep 信息,并根据需要设置 CandleType。
- 根据品种波动调整各项点数参数。移植版会自动通过
PriceStep 归一化距离,对五位报价自动乘以 10。
- 图表辅助会在主图上绘制移动平均,并在独立区域显示 Awesome Oscillator,同时标注策略交易。
- 启动策略后,它会自动设置止损、止盈,并按照原始 MQL 逻辑执行追踪止损。
其他说明
- Laguerre RSI 的实现完全对应
laguerre.mq5,包含 CU/(CU+CD) 的归一化步骤。
- Awesome Oscillator 使用 StockSharp 自带指标,无需手动复制缓冲区。
- 策略只在完成的蜡烛上运行,避免了因逐笔数据导致的重复绘制和不确定性。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// GlamTrader strategy using EMA crossover with momentum confirmation.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class GlamTraderStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="GlamTraderStrategy"/> class.
/// </summary>
public GlamTraderStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 25)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fast = null;
_slow = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed)
{
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 80;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// EMA crossover
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 80;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 80;
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
}
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 glam_trader_strategy(Strategy):
def __init__(self):
super(glam_trader_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 25) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 100) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(glam_trader_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(glam_trader_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.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 not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 80
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 80
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return glam_trader_strategy()