Sidus 策略
该策略实现了 SIDUS 移动平均系统。它使用两条线性加权移动平均线与一条指数平均线的交叉来交易。当快速 LWMA 向上穿越慢速 LWMA,或慢速 LWMA 向上穿越慢速 EMA 时建立多头;出现相反交叉时建立空头或平仓。策略通过百分比止盈和止损控制风险。
测试显示年化收益约为 25%。在外汇品种上表现最佳。
核心思想是在快慢均线重新排列时捕捉趋势变化。LWMA 对价格反应迅速,而较慢的 EMA 有助于滤除噪音。出现多头或空头排列时策略顺势入场,并依赖保护水平在行情不利时退出。
细节
- 入场条件:
- 多头:快速 LWMA 上穿慢速 LWMA,或慢速 LWMA 上穿慢速 EMA。
- 空头:快速 LWMA 下穿慢速 LWMA,或慢速 LWMA 下穿慢速 EMA。
- 方向:双向。
- 出场条件:
- 反向交叉或保护性止盈/止损。
- 止损:是,使用
StartProtection的百分比止盈和止损。 - 默认值:
- 快速 EMA 周期 = 18。
- 慢速 EMA 周期 = 28。
- 快速 LWMA 周期 = 5。
- 慢速 LWMA 周期 = 8。
- 止盈 = 2%。
- 止损 = 1%。
- 过滤器:无。
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>
/// SIDUS strategy based on moving average crossovers.
/// Buys when fast LWMA crosses above slow LWMA or when slow LWMA crosses above slow EMA.
/// Sells on opposite crossovers.
/// </summary>
public class SidusStrategy : Strategy
{
private readonly StrategyParam<int> _fastEma;
private readonly StrategyParam<int> _slowEma;
private readonly StrategyParam<int> _fastLwma;
private readonly StrategyParam<int> _slowLwma;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fastEmaIndicator;
private ExponentialMovingAverage _slowEmaIndicator;
private WeightedMovingAverage _fastLwmaIndicator;
private WeightedMovingAverage _slowLwmaIndicator;
private decimal _prevFastLwma;
private decimal _prevSlowLwma;
private decimal _prevSlowEma;
private bool _isInitialized;
public int FastEma { get => _fastEma.Value; set => _fastEma.Value = value; }
public int SlowEma { get => _slowEma.Value; set => _slowEma.Value = value; }
public int FastLwma { get => _fastLwma.Value; set => _fastLwma.Value = value; }
public int SlowLwma { get => _slowLwma.Value; set => _slowLwma.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public SidusStrategy()
{
_fastEma = Param(nameof(FastEma), 18)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Length of the fast EMA", "Sidus")
.SetOptimize(10, 30, 2);
_slowEma = Param(nameof(SlowEma), 28)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Length of the slow EMA", "Sidus")
.SetOptimize(20, 50, 2);
_fastLwma = Param(nameof(FastLwma), 5)
.SetGreaterThanZero()
.SetDisplay("Fast LWMA", "Length of the fast LWMA", "Sidus")
.SetOptimize(3, 10, 1);
_slowLwma = Param(nameof(SlowLwma), 8)
.SetGreaterThanZero()
.SetDisplay("Slow LWMA", "Length of the slow LWMA", "Sidus")
.SetOptimize(5, 15, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_prevFastLwma = 0;
_prevSlowLwma = 0;
_prevSlowEma = 0;
_isInitialized = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEmaIndicator = new ExponentialMovingAverage { Length = FastEma };
_slowEmaIndicator = new ExponentialMovingAverage { Length = SlowEma };
_fastLwmaIndicator = new WeightedMovingAverage { Length = FastLwma };
_slowLwmaIndicator = new WeightedMovingAverage { Length = SlowLwma };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastEmaIndicator, _slowEmaIndicator, _fastLwmaIndicator, _slowLwmaIndicator, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastEmaIndicator);
DrawIndicator(area, _slowEmaIndicator);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastEmaValue, decimal slowEmaValue, decimal fastLwmaValue, decimal slowLwmaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_isInitialized)
{
_prevFastLwma = fastLwmaValue;
_prevSlowLwma = slowLwmaValue;
_prevSlowEma = slowEmaValue;
_isInitialized = true;
return;
}
var buySignal =
(fastLwmaValue > slowLwmaValue && _prevFastLwma <= _prevSlowLwma) ||
(slowLwmaValue > slowEmaValue && _prevSlowLwma <= _prevSlowEma);
var sellSignal =
(fastLwmaValue < slowLwmaValue && _prevFastLwma >= _prevSlowLwma) ||
(slowLwmaValue < slowEmaValue && _prevSlowLwma >= _prevSlowEma);
if (IsFormedAndOnlineAndAllowTrading())
{
if (buySignal && Position <= 0)
BuyMarket();
else if (sellSignal && Position >= 0)
SellMarket();
}
_prevFastLwma = fastLwmaValue;
_prevSlowLwma = slowLwmaValue;
_prevSlowEma = slowEmaValue;
}
}
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, WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class sidus_strategy(Strategy):
def __init__(self):
super(sidus_strategy, self).__init__()
self._fast_ema = self.Param("FastEma", 18) \
.SetDisplay("Fast EMA", "Length of the fast EMA", "Sidus")
self._slow_ema = self.Param("SlowEma", 28) \
.SetDisplay("Slow EMA", "Length of the slow EMA", "Sidus")
self._fast_lwma = self.Param("FastLwma", 5) \
.SetDisplay("Fast LWMA", "Length of the fast LWMA", "Sidus")
self._slow_lwma = self.Param("SlowLwma", 8) \
.SetDisplay("Slow LWMA", "Length of the slow LWMA", "Sidus")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_fast_lwma = 0.0
self._prev_slow_lwma = 0.0
self._prev_slow_ema = 0.0
self._is_initialized = False
@property
def fast_ema(self):
return self._fast_ema.Value
@property
def slow_ema(self):
return self._slow_ema.Value
@property
def fast_lwma(self):
return self._fast_lwma.Value
@property
def slow_lwma(self):
return self._slow_lwma.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(sidus_strategy, self).OnReseted()
self._prev_fast_lwma = 0.0
self._prev_slow_lwma = 0.0
self._prev_slow_ema = 0.0
self._is_initialized = False
def OnStarted2(self, time):
super(sidus_strategy, self).OnStarted2(time)
fast_ema_ind = ExponentialMovingAverage()
fast_ema_ind.Length = self.fast_ema
slow_ema_ind = ExponentialMovingAverage()
slow_ema_ind.Length = self.slow_ema
fast_lwma_ind = WeightedMovingAverage()
fast_lwma_ind.Length = self.fast_lwma
slow_lwma_ind = WeightedMovingAverage()
slow_lwma_ind.Length = self.slow_lwma
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ema_ind, slow_ema_ind, fast_lwma_ind, slow_lwma_ind, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema_ind)
self.DrawIndicator(area, slow_ema_ind)
self.DrawOwnTrades(area)
def process_candle(self, candle, fast_ema_val, slow_ema_val, fast_lwma_val, slow_lwma_val):
if candle.State != CandleStates.Finished:
return
fast_ema_val = float(fast_ema_val)
slow_ema_val = float(slow_ema_val)
fast_lwma_val = float(fast_lwma_val)
slow_lwma_val = float(slow_lwma_val)
if not self._is_initialized:
self._prev_fast_lwma = fast_lwma_val
self._prev_slow_lwma = slow_lwma_val
self._prev_slow_ema = slow_ema_val
self._is_initialized = True
return
buy_signal = (
(fast_lwma_val > slow_lwma_val and self._prev_fast_lwma <= self._prev_slow_lwma) or
(slow_lwma_val > slow_ema_val and self._prev_slow_lwma <= self._prev_slow_ema)
)
sell_signal = (
(fast_lwma_val < slow_lwma_val and self._prev_fast_lwma >= self._prev_slow_lwma) or
(slow_lwma_val < slow_ema_val and self._prev_slow_lwma >= self._prev_slow_ema)
)
if buy_signal and self.Position <= 0:
self.BuyMarket()
elif sell_signal and self.Position >= 0:
self.SellMarket()
self._prev_fast_lwma = fast_lwma_val
self._prev_slow_lwma = slow_lwma_val
self._prev_slow_ema = slow_ema_val
def CreateClone(self):
return sidus_strategy()