HMA 季節性ダイバージェンス戦略
この戦略は、ハル移動平均線(HMA)と季節性の建玉クラスタリングを組み合わせ、価格とマーケットポジショニングのダイバージェンスを見つけます。価格が上昇する建玉の方向に一時的に逆行する場合、トレンド継続の可能性が高いと想定しています。このシステムはロングとショートの両方を取引するよう設計されており、HMAの傾きでモメンタムを測り、季節性の建玉データで参加レベルを測定します。
テストでは年平均リターンが約40%を示しています。暗号資産市場で最もよく機能します。
トレードセットアップは、HMAが直前バーと比較して変化し、季節性の建玉がその動きを確認する一方で、価格が逆方向に動いたときに発生します。この価格とポジショニングの強気または弱気のダイバージェンスは、より大きなトレンドの中での短期的な押し目・戻りの終わりを示すことが多いです。この条件が整うのを待ってからエントリーし、リスク管理のためにボラティリティベースのストップを置きます。
HMAの傾きが反転したとき、モメンタムが転換したことを示すためポジションをクローズします。ストップレベルはAverage True Range(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= 9。OI_Cluster_Seasonal= 5年間のクラスターレベルにおける季節性の建玉。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()