在 GitHub 上查看
Absolutely No Lag LWMA 策略
概述
该策略复刻 MetaTrader 专家顾问 Exp_AbsolutelyNoLagLwma 的交易思想。策略对所选周期的K线数据连续应用两次线性加权移动平均(LWMA),并根据平滑后的数值斜率对趋势进行着色:2 代表向上,1 表示持平,0 表示向下。颜色的变化触发买入或卖出信号。StockSharp 实现基于高级 API,自动订阅指定时间框架的K线并根据信号发送市价单。
交易逻辑
指标流程
- 按照“价格类型”参数选择用于计算的价格序列。
- 对价格应用第一个 LWMA,长度由“LWMA 长度”控制。
- 使用相同长度的第二个 LWMA 对结果再次平滑。
- 将当前平滑值与前一值比较以确定颜色:
- 2(上涨) – 当前值高于上一值。
- 1(横盘) – 当前值等于上一值。
- 0(下跌) – 当前值低于上一值。
信号判定
- 仅处理已经完成的K线。信号K线参数决定向历史回溯的距离(1 表示最近一根完成K线,2 表示再往前一根,依此类推)。策略同时记录目标K线之前一根K线的颜色,以避免重复入场。
- 看多转换:目标K线颜色为 2,且其前一根颜色不是 2。允许时开多,并平掉所有空单。
- 看空转换:目标K线颜色为 0,且其前一根颜色不是 0。允许时开空,并平掉所有多单。
仓位管理
- 采用市价单执行。当方向反转时,订单数量为
Volume + |Position|,确保旧持仓被完全反向。
- 多、空入场与平仓可分别启用或禁用,便于实现仅入场或仅离场的模式。
- 策略启动时调用
StartProtection(),以启用 StockSharp 通用的保护性逻辑。
参数说明
- LWMA Length(LWMA 长度) – 两个 LWMA 的周期长度。
- Price Type(价格类型) – 用于计算的价格数据(收盘价、开盘价、最高价、最低价、Median、Typical、Weighted、Simpl、Quarter、TrendFollow 系列、Demark 价格等)。
- Signal Bar(信号K线) – 回溯多少根完成K线来生成信号。
- Enable Long Entries(允许多头入场) – 看多信号触发时是否开多。
- Enable Short Entries(允许空头入场) – 看空信号触发时是否开空。
- Enable Long Exits(允许多头离场) – 指标转空时是否平多。
- Enable Short Exits(允许空头离场) – 指标转多时是否平空。
- Candle Type(K线类型) – 指标使用的时间框架。
- Volume(基础属性) – 新开仓时使用的数量。
其他说明
- 默认时间框架为4小时,与原版 EA 设置一致,可通过参数自由调整。
- 策略不主动下达止盈或止损单,如需风险控制可结合 StockSharp 其他组件使用。
- 根据需求暂不提供 Python 版本。
namespace StockSharp.Samples.Strategies;
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Absolutely No Lag LWMA strategy (simplified).
/// Uses two EMAs of different periods to detect trend direction changes.
/// </summary>
public class AbsolutelyNoLagLwmaStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
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 AbsolutelyNoLagLwmaStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Source candles", "General");
_fastPeriod = Param(nameof(FastPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow EMA period", "Indicators");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
decimal prevFast = 0, prevSlow = 0;
bool hasPrev = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, (ICandleMessage candle, decimal fastValue, decimal slowValue) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!hasPrev)
{
prevFast = fastValue;
prevSlow = slowValue;
hasPrev = true;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
prevFast = fastValue;
prevSlow = slowValue;
return;
}
if (prevFast <= prevSlow && fastValue > slowValue && Position <= 0)
{
BuyMarket();
}
else if (prevFast >= prevSlow && fastValue < slowValue && Position >= 0)
{
SellMarket();
}
prevFast = fastValue;
prevSlow = slowValue;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
}
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 absolutely_no_lag_lwma_strategy(Strategy):
def __init__(self):
super(absolutely_no_lag_lwma_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Source candles", "General")
self._fast_period = self.Param("FastPeriod", 9) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 21) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(absolutely_no_lag_lwma_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(absolutely_no_lag_lwma_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(fast_ema, slow_ema, self._on_process) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if not self._has_prev:
self._prev_fast = fv
self._prev_slow = sv
self._has_prev = True
return
if self._prev_fast <= self._prev_slow and fv > sv and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fv < sv and self.Position >= 0:
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def CreateClone(self):
return absolutely_no_lag_lwma_strategy()