HMA季节性背离策略
该策略将Hull移动平均线与季节性持仓聚合结合,用来发现价格与市场定位的背离。当价格短暂背离持仓上升的方向时,通常意味着趋势将继续。本系统可双向交易,通过HMA斜率评估动量,季节性持仓数据衡量参与度。
测试表明年均收益约为 40%,该策略在加密市场表现最佳。
当HMA发生变化并得到季节性持仓确认,但价格向相反方向运行时形成入场信号。这种多空背离常暗示短期回调结束。策略等待这些条件出现并根据波动率设置止损。
当HMA斜率反转时平仓。止损水平按平均真实波幅(ATR)的倍数计算,可随波动调整风险。
详情
- 入场条件:
- 多头:
HMA(t) > HMA(t-1)且OI_Cluster_Seasonal(t) > OI_Cluster_Seasonal(t-1)且Price(t) < Price(t-1)(多头背离) - 空头:
HMA(t) < HMA(t-1)且OI_Cluster_Seasonal(t) < OI_Cluster_Seasonal(t-1)且Price(t) > Price(t-1)(空头背离)
- 多头:
- 多空方向: 双向
- 退出条件:
- 多头:
HMA(t) < HMA(t-1)(HMA开始下降) - 空头:
HMA(t) > HMA(t-1)(HMA开始上升)
- 多头:
- 止损: 是,止损设置为
N * ATR - 默认值:
HMA period= 9OI_Cluster_Seasonal= 五年聚合的季节性持仓N= 2(止损 =2 * ATR)
- 过滤器:
- 类型: 趋势跟随
- 方向: 双向
- 指标: 多个
- 止损: 是
- 复杂度: 复杂
- 时间框架: 中期
- 季节性: 是
- 神经网络: 是
- 背离: 是
- 风险等级: 高
namespace StockSharp.Samples.Strategies;
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;
/// <summary>
/// Moving average crossover strategy.
/// Enters long when fast MA crosses above slow MA.
/// Enters short when fast MA crosses below slow MA.
/// Implements stop-loss as a percentage of entry price.
/// </summary>
public class MaCrossoverStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private bool _isLongPosition;
/// <summary>
/// Fast MA period length.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow MA period length.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// The type of candles to use for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MaCrossoverStrategy()
{
_fastLength = Param(nameof(FastLength), 100)
.SetGreaterThanZero()
.SetDisplay("Fast MA Length", "Period of the fast moving average", "MA Settings")
.SetOptimize(5, 20, 5);
_slowLength = Param(nameof(SlowLength), 400)
.SetGreaterThanZero()
.SetDisplay("Slow MA Length", "Period of the slow moving average", "MA Settings")
.SetOptimize(20, 100, 10);
_stopLossPercent = Param(nameof(StopLossPercent), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
.SetOptimize(1.0m, 5.0m, 1.0m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
// Initialize variables
_entryPrice = 0;
_isLongPosition = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
var fastMa = new ExponentialMovingAverage { Length = FastLength };
var slowMa = new ExponentialMovingAverage { Length = SlowLength };
// Create and setup subscription for candles
var subscription = SubscribeCandles(CandleType);
// Previous values for crossover detection
var previousFastValue = 0m;
var previousSlowValue = 0m;
var wasFastLessThanSlow = false;
var isInitialized = false;
subscription
.Bind(fastMa, slowMa, (candle, fastValue, slowValue) =>
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Initialize on first complete values
if (!isInitialized && fastMa.IsFormed && slowMa.IsFormed)
{
previousFastValue = fastValue;
previousSlowValue = slowValue;
wasFastLessThanSlow = fastValue < slowValue;
isInitialized = true;
LogInfo($"Strategy initialized. Fast MA: {fastValue}, Slow MA: {slowValue}");
return;
}
if (!isInitialized)
return;
// Current crossover state
var isFastLessThanSlow = fastValue < slowValue;
LogInfo($"Candle: {candle.OpenTime}, Close: {candle.ClosePrice}, Fast MA: {fastValue}, Slow MA: {slowValue}");
// Check for crossovers
if (wasFastLessThanSlow != isFastLessThanSlow)
{
// Crossover happened
if (!isFastLessThanSlow) // Fast MA crossed above Slow MA
{
// Buy signal
if (Position <= 0)
{
_entryPrice = candle.ClosePrice;
_isLongPosition = true;
BuyMarket(Volume + Math.Abs(Position));
}
}
else // Fast MA crossed below Slow MA
{
// Sell signal
if (Position >= 0)
{
_entryPrice = candle.ClosePrice;
_isLongPosition = false;
SellMarket(Volume + Math.Abs(Position));
}
}
// Update the crossover state
wasFastLessThanSlow = isFastLessThanSlow;
}
// Update previous values
previousFastValue = fastValue;
previousSlowValue = slowValue;
})
.Start();
// Setup chart if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastMa);
DrawIndicator(area, slowMa);
DrawOwnTrades(area);
}
}
private void CheckStopLoss(decimal currentPrice)
{
if (_entryPrice == 0)
return;
var stopLossThreshold = _stopLossPercent.Value / 100.0m;
if (_isLongPosition && Position > 0)
{
// For long positions, exit if price falls below entry price - stop percentage
var stopPrice = _entryPrice * (1.0m - stopLossThreshold);
if (currentPrice <= stopPrice)
{
SellMarket(Math.Abs(Position));
LogInfo($"Long stop-loss triggered at {currentPrice}. Entry was {_entryPrice}, Stop level: {stopPrice}");
}
}
else if (!_isLongPosition && Position < 0)
{
// For short positions, exit if price rises above entry price + stop percentage
var stopPrice = _entryPrice * (1.0m + stopLossThreshold);
if (currentPrice >= stopPrice)
{
BuyMarket(Math.Abs(Position));
LogInfo($"Short stop-loss triggered at {currentPrice}. Entry was {_entryPrice}, Stop level: {stopPrice}");
}
}
}
}
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 ma_crossover_strategy(Strategy):
"""
Moving average crossover strategy.
Enters long when fast MA crosses above slow MA.
Enters short when fast MA crosses below slow MA.
"""
def __init__(self):
super(ma_crossover_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 100).SetDisplay("Fast MA Length", "Period of the fast moving average", "MA Settings")
self._slow_length = self.Param("SlowLength", 400).SetDisplay("Slow MA Length", "Period of the slow moving average", "MA Settings")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._entry_price = 0.0
self._is_long_position = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_crossover_strategy, self).OnReseted()
self._entry_price = 0.0
self._is_long_position = False
def OnStarted2(self, time):
super(ma_crossover_strategy, self).OnStarted2(time)
fast_ma = ExponentialMovingAverage()
fast_ma.Length = self._fast_length.Value
slow_ma = ExponentialMovingAverage()
slow_ma.Length = self._slow_length.Value
self._was_fast_less = False
self._is_initialized = False
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ma, slow_ma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ma)
self.DrawIndicator(area, slow_ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
if not self._is_initialized:
self._was_fast_less = fv < sv
self._is_initialized = True
return
is_fast_less = fv < sv
if self._was_fast_less != is_fast_less:
if not is_fast_less:
if self.Position <= 0:
self._entry_price = float(candle.ClosePrice)
self._is_long_position = True
self.BuyMarket()
else:
if self.Position >= 0:
self._entry_price = float(candle.ClosePrice)
self._is_long_position = False
self.SellMarket()
self._was_fast_less = is_fast_less
def CreateClone(self):
return ma_crossover_strategy()