Start
/
Strategie-Beispiele
Auf GitHub ansehen
HMA Saisonale Divergenz-Strategie
Diese Strategie kombiniert den Hull Moving Average (HMA) mit saisonal geclustertem Open Interest, um Divergenzen zwischen Preis und Marktpositionierung zu finden. Sie geht davon aus, dass eine Trendfortsetzung wahrscheinlich ist, wenn sich der Preis vorübergehend gegen die Richtung eines steigenden Open Interest bewegt. Das System ist darauf ausgelegt, sowohl Long- als auch Short-Positionen zu handeln, wobei die HMA-Steigung zur Beurteilung des Momentums und die saisonalen Open-Interest-Daten zur Messung der Partizipationsniveaus verwendet werden.
Tests zeigen eine durchschnittliche jährliche Rendite von etwa 40%. Es funktioniert am besten auf dem Kryptomarkt.
Ein Trade-Setup entsteht, wenn sich die HMA gegenüber der vorherigen Kerze ändert, während das saisonale Open Interest die Bewegung bestätigt, der Preis jedoch in die entgegengesetzte Richtung druckt. Diese bullische oder bärische Divergenz zwischen Preis und Positionierung signalisiert oft das Ende eines kurzfristigen Rücksetzers innerhalb eines größeren Trends. Die Strategie wartet auf diese Bedingungen, bevor sie eintritt, und platziert einen volatilitätsbasierten Stop zur Risikosteuerung.
Positionen werden geschlossen, wenn die HMA-Steigung umkehrt, was darauf hinweist, dass das Momentum gedreht hat. Da das Stop-Niveau ein Vielfaches der Average True Range (ATR) verwendet, passt sich das Risiko der Marktvolatilität an. Dies hilft, vorzeitige Ausstiege in Expansionsphasen zu verhindern, und hält Verluste begrenzt, wenn die Volatilität nachlässt.
Details
Einstiegskriterien :
Long : HMA(t) > HMA(t-1) && OI_Cluster_Seasonal(t) > OI_Cluster_Seasonal(t-1) && Price(t) < Price(t-1) (bullische Divergenz).
Short : HMA(t) < HMA(t-1) && OI_Cluster_Seasonal(t) < OI_Cluster_Seasonal(t-1) && Price(t) > Price(t-1) (bärische Divergenz).
Long/Short : Beide Seiten.
Ausstiegskriterien :
Long : HMA(t) < HMA(t-1) (HMA beginnt zu fallen).
Short : HMA(t) > HMA(t-1) (HMA beginnt zu steigen).
Stops : Ja, Stop-Loss bei N * ATR vom Einstieg.
Standardwerte :
HMA period = 9.
OI_Cluster_Seasonal = saisonales OI auf Cluster-Niveaus über fünf Jahre.
N = 2 (Stop-Loss = 2 * ATR).
Filter :
Kategorie: Trendfolge
Richtung: Beide
Indikatoren: Mehrere
Stops: Ja
Komplexität: Komplex
Zeitrahmen: Mittelfristig
Saisonalität: Ja
Neuronale Netze: Ja
Divergenz: Ja
Risikolevel: Hoch
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()