Awesome Fx Trader
该策略复刻了 MQL/8539 中的 MetaTrader 方案,原始文件包括自定义指标 AwesomeFxTradera.mq4 与 t_ma.mq4。前者通过比较当前值与上一根柱体来给 Bill Williams Awesome Oscillator 柱状体着色,后者则在图表上绘制 34 周期的线性加权移动平均线(LWMA)及其 6 周期简单移动平均线平滑曲线。移植到 StockSharp 后,我们保持相同的计算方式,并把颜色变化转换成交易信号。
原始 MQL 逻辑
- AwesomeFxTradera.mq4 对 开盘价 分别计算 8 周期与 13 周期的指数移动平均线,它们的差值存入
ExtBuffer0。当当前值高于上一根柱体时缓冲区显示为绿色,低于则显示为红色,用来表示动量是否在加强。 - t_ma.mq4 绘制开盘价的 34 周期 LWMA(
ExtMapBuffer1),同时对该 LWMA 的数值再做 6 周期简单平均(ExtMapBuffer2)以获得平滑曲线,判断趋势是否加速或减速。
因此在 MetaTrader 中,当振荡器位于零轴之上且数值持续上升、同时价格运行在平滑 LWMA 之上时就被视为多头动量;反之则是空头动量。
StockSharp 实现
AwesomeFxTraderStrategy 订阅可配置的 K 线类型(默认 15 分钟),并使用蜡烛的开盘价驱动所有指标,以保持与原始缓冲区完全一致。
- 每根已完成的蜡烛都会重新计算快慢 EMA,其差值还原出振荡柱状体。
- 34 周期 LWMA 追踪趋势,6 周期 SMA 对 LWMA 进行平滑,比较两者可以判断趋势曲线的方向。
- 通过比较当前与上一根柱子的振荡值,重建 MQL 中的
bool up颜色逻辑。 - 入场规则:
- 当振荡器为正值、当前柱体高于上一柱体且 LWMA 位于平滑线上方时开多。
- 当振荡器为负值、当前柱体低于上一柱体且 LWMA 位于平滑线下方时开空。
- 离场/反手规则:出现相反信号时直接反转仓位。下单数量会自动加上当前仓位的绝对值,以确保先平掉旧仓再建立新方向。
原始代码未定义额外的止损或止盈,因此此版本完全依赖动量翻转离场。日志会记录每次触发交易时的指标读数。
参数
| 名称 | 默认值 | 说明 |
|---|---|---|
FastEmaPeriod |
8 | 复制振荡器所用的快 EMA 周期。 |
SlowEmaPeriod |
13 | 振荡器所用的慢 EMA 周期。 |
TrendLwmaPeriod |
34 | 取自 t_ma.mq4 的趋势 LWMA 周期。 |
TrendSmoothingPeriod |
6 | 对 LWMA 数值做平滑的 SMA 窗口长度。 |
CandleType |
15 分钟 K 线 | 用于所有计算的蜡烛数据类型。 |
所有参数均通过 StrategyParam 提供显示名称、优化区间,便于在界面中调整或回测。
文件映射
| MetaTrader 文件 | StockSharp 对应文件 | 备注 |
|---|---|---|
MQL/8539/AwesomeFxTradera.mq4 |
CS/AwesomeFxTraderStrategy.cs |
复刻基于开盘价的双 EMA 振荡器及其颜色判定。 |
MQL/8539/t_ma.mq4 |
CS/AwesomeFxTraderStrategy.cs |
实现 34 周期 LWMA 与 6 周期 SMA 平滑的趋势过滤器。 |
按需求未创建 Python 版本。
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>
/// Awesome Fx Trader strategy.
/// Recreates the MetaTrader logic that paints the Awesome Oscillator histogram and trend moving averages.
/// Goes long when the oscillator turns bullish while the linear weighted average stays above its smoother.
/// Opens shorts on the opposite momentum and trend alignment.
/// </summary>
public class AwesomeFxTraderStrategy : Strategy
{
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<int> _trendLwmaPeriod;
private readonly StrategyParam<int> _trendSmoothingPeriod;
private readonly StrategyParam<DataType> _candleType;
private EMA _fastEma;
private EMA _slowEma;
private WeightedMovingAverage _trendLwma;
private decimal _previousAo;
private bool _hasPreviousAo;
private bool _isAoIncreasing;
private decimal _previousLwma;
/// <summary>
/// Period for the fast EMA used in the oscillator.
/// </summary>
public int FastEmaPeriod
{
get => _fastEmaPeriod.Value;
set => _fastEmaPeriod.Value = value;
}
/// <summary>
/// Period for the slow EMA used in the oscillator.
/// </summary>
public int SlowEmaPeriod
{
get => _slowEmaPeriod.Value;
set => _slowEmaPeriod.Value = value;
}
/// <summary>
/// Length of the trend linear weighted moving average.
/// </summary>
public int TrendLwmaPeriod
{
get => _trendLwmaPeriod.Value;
set => _trendLwmaPeriod.Value = value;
}
/// <summary>
/// Length of the smoothing simple moving average applied to the trend LWMA.
/// </summary>
public int TrendSmoothingPeriod
{
get => _trendSmoothingPeriod.Value;
set => _trendSmoothingPeriod.Value = value;
}
/// <summary>
/// Type of candles to use for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="AwesomeFxTraderStrategy"/>.
/// </summary>
public AwesomeFxTraderStrategy()
{
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Period of the fast EMA driving the oscillator", "Awesome Oscillator")
.SetOptimize(4, 20, 1);
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Period of the slow EMA driving the oscillator", "Awesome Oscillator")
.SetOptimize(8, 40, 1);
_trendLwmaPeriod = Param(nameof(TrendLwmaPeriod), 34)
.SetGreaterThanZero()
.SetDisplay("Trend LWMA", "Length of the linear weighted trend average", "Trend Filter")
.SetOptimize(20, 80, 2);
_trendSmoothingPeriod = Param(nameof(TrendSmoothingPeriod), 6)
.SetGreaterThanZero()
.SetDisplay("Trend Smoother", "Length of the SMA applied to the trend LWMA", "Trend Filter")
.SetOptimize(3, 10, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time-frame used for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastEma = null;
_slowEma = null;
_trendLwma = null;
_previousAo = 0m;
_hasPreviousAo = false;
_isAoIncreasing = false;
_previousLwma = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEma = new EMA { Length = FastEmaPeriod };
_slowEma = new EMA { Length = SlowEmaPeriod };
_trendLwma = new WeightedMovingAverage { Length = TrendLwmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_fastEma, _slowEma, _trendLwma, ProcessCandle).Start();
var priceArea = CreateChartArea();
if (priceArea != null)
{
DrawCandles(priceArea, subscription);
DrawIndicator(priceArea, _trendLwma);
DrawOwnTrades(priceArea);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastEma, decimal slowEma, decimal lwma)
{
if (candle.State != CandleStates.Finished)
return;
var ao = fastEma - slowEma;
if (!_hasPreviousAo)
{
_previousAo = ao;
_hasPreviousAo = true;
_isAoIncreasing = ao >= 0m;
_previousLwma = lwma;
return;
}
if (ao > _previousAo)
_isAoIncreasing = true;
else if (ao < _previousAo)
_isAoIncreasing = false;
var isTrendBullish = lwma > _previousLwma;
var isTrendBearish = lwma < _previousLwma;
var bullishSignal = _isAoIncreasing && ao > 0m && isTrendBullish;
var bearishSignal = !_isAoIncreasing && ao < 0m && isTrendBearish;
if (bullishSignal && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
if (volume <= 0)
volume = 1;
BuyMarket(volume);
}
else if (bearishSignal && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
if (volume <= 0)
volume = 1;
SellMarket(volume);
}
_previousAo = ao;
_previousLwma = lwma;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, WeightedMovingAverage
class awesome_fx_trader_strategy(Strategy):
def __init__(self):
super(awesome_fx_trader_strategy, self).__init__()
self._fast_ema_period = self.Param("FastEmaPeriod", 8) \
.SetDisplay("Fast EMA", "Period of the fast EMA driving the oscillator", "Awesome Oscillator")
self._slow_ema_period = self.Param("SlowEmaPeriod", 13) \
.SetDisplay("Slow EMA", "Period of the slow EMA driving the oscillator", "Awesome Oscillator")
self._trend_lwma_period = self.Param("TrendLwmaPeriod", 34) \
.SetDisplay("Trend LWMA", "Length of the linear weighted trend average", "Trend Filter")
self._trend_smoothing_period = self.Param("TrendSmoothingPeriod", 6) \
.SetDisplay("Trend Smoother", "Length of the SMA applied to the trend LWMA", "Trend Filter")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time-frame used for calculations", "General")
self._fast_ema = None
self._slow_ema = None
self._trend_lwma = None
self._previous_ao = 0.0
self._has_previous_ao = False
self._is_ao_increasing = False
self._previous_lwma = 0.0
@property
def FastEmaPeriod(self):
return self._fast_ema_period.Value
@property
def SlowEmaPeriod(self):
return self._slow_ema_period.Value
@property
def TrendLwmaPeriod(self):
return self._trend_lwma_period.Value
@property
def TrendSmoothingPeriod(self):
return self._trend_smoothing_period.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(awesome_fx_trader_strategy, self).OnStarted2(time)
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaPeriod
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaPeriod
self._trend_lwma = WeightedMovingAverage()
self._trend_lwma.Length = self.TrendLwmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._trend_lwma, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_ema, slow_ema, lwma):
if candle.State != CandleStates.Finished:
return
fast_ema = float(fast_ema)
slow_ema = float(slow_ema)
lwma = float(lwma)
ao = fast_ema - slow_ema
if not self._has_previous_ao:
self._previous_ao = ao
self._has_previous_ao = True
self._is_ao_increasing = ao >= 0
self._previous_lwma = lwma
return
if ao > self._previous_ao:
self._is_ao_increasing = True
elif ao < self._previous_ao:
self._is_ao_increasing = False
is_trend_bullish = lwma > self._previous_lwma
is_trend_bearish = lwma < self._previous_lwma
bullish_signal = self._is_ao_increasing and ao > 0 and is_trend_bullish
bearish_signal = not self._is_ao_increasing and ao < 0 and is_trend_bearish
if bullish_signal and self.Position <= 0:
volume = self.Volume + Math.Abs(self.Position)
if volume <= 0:
volume = 1
self.BuyMarket(volume)
elif bearish_signal and self.Position >= 0:
volume = self.Volume + Math.Abs(self.Position)
if volume <= 0:
volume = 1
self.SellMarket(volume)
self._previous_ao = ao
self._previous_lwma = lwma
def OnReseted(self):
super(awesome_fx_trader_strategy, self).OnReseted()
self._fast_ema = None
self._slow_ema = None
self._trend_lwma = None
self._previous_ao = 0.0
self._has_previous_ao = False
self._is_ao_increasing = False
self._previous_lwma = 0.0
def CreateClone(self):
return awesome_fx_trader_strategy()