Стратегия Hull Trend OSMA
Стратегия представляет собой конвертацию эксперта MetaTrader «Exp_HullTrendOSMA».
Описание
Используется индикатор Hull Trend OSMA, который вычисляет Hull Moving Average и его сглаженную версию. Разница между ними образует осциллятор. Если осциллятор растёт на двух подряд завершённых свечах, открывается длинная позиция. Если осциллятор падает на двух подряд завершённых свечах, открывается короткая позиция. При каждом сигнале закрываются позиции противоположного направления.
Параметры
- Hull Period – период Hull Moving Average.
- Signal Period – период сглаживающей скользящей средней осциллятора.
- Take Profit – расстояние до тейк‑профита в ценовых единицах.
- Stop Loss – расстояние до стоп‑лосса в ценовых единицах.
- Candle Type – таймфрейм используемых свечей (по умолчанию 8 часов).
Примечания
- Используется высокоуровневый API StockSharp с автоматической подпиской на свечи.
- Входы и выходы выполняются рыночными заявками.
- Защитные стоп и тейк‑профит активируются один раз при старте стратегии.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on Hull Trend OSMA indicator.
/// Opens long when oscillator rises twice in a row, short when it falls twice in a row.
/// </summary>
public class HullTrendOsmaStrategy : Strategy
{
private readonly StrategyParam<int> _hullPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prev1;
private decimal? _prev2;
private Order _pending;
public int HullPeriod { get => _hullPeriod.Value; set => _hullPeriod.Value = value; }
public int SignalPeriod { get => _signalPeriod.Value; set => _signalPeriod.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public HullTrendOsmaStrategy()
{
_hullPeriod = Param(nameof(HullPeriod), 20)
.SetDisplay("Hull Period", "Period for Hull Moving Average", "Indicators");
_signalPeriod = Param(nameof(SignalPeriod), 5)
.SetDisplay("Signal Period", "Period for signal SMA", "Indicators");
_takeProfit = Param(nameof(TakeProfit), 3m)
.SetDisplay("Take Profit %", "Take profit percentage", "Risk");
_stopLoss = Param(nameof(StopLoss), 2m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).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();
_prev1 = null;
_prev2 = null;
_pending = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pending = null;
var hma = new HullMovingAverage { Length = HullPeriod };
var signal = new SimpleMovingAverage { Length = SignalPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(hma, (candle, hmaValue) =>
{
if (candle.State != CandleStates.Finished || !hma.IsFormed) return;
var sig = signal.Process(hmaValue, candle.OpenTime, true);
if (sig.IsFormed && !sig.IsEmpty) ProcessCandle(candle, hmaValue, sig.ToDecimal());
}).Start();
StartProtection(
takeProfit: new Unit(TakeProfit, UnitTypes.Absolute),
stopLoss: new Unit(StopLoss, UnitTypes.Absolute),
useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, hma);
DrawIndicator(area, signal);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal hmaValue, decimal signalValue)
{
if (candle.State != CandleStates.Finished)
return;
var osma = hmaValue - signalValue;
if (_prev1 is null || _prev2 is null)
{
_prev2 = _prev1;
_prev1 = osma;
return;
}
var prev = _prev1.Value;
var prevPrev = _prev2.Value;
var isRising = prev > prevPrev && osma >= prev;
var isFalling = prev < prevPrev && osma <= prev;
if (IsFormedAndOnlineAndAllowTrading() && (_pending is null || _pending.State is OrderStates.Done or OrderStates.Failed))
{
if (isRising && Position <= 0m) _pending = BuyMarket(Volume - Math.Min(0m, Position));
else if (isFalling && Position >= 0m) _pending = SellMarket(Volume + Math.Max(0m, Position));
}
_prev2 = _prev1;
_prev1 = osma;
}
}
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, Unit, UnitTypes, OrderStates
from StockSharp.Algo.Indicators import HullMovingAverage, SimpleMovingAverage
from indicator_extensions import process_float
from StockSharp.Algo.Strategies import Strategy
class hull_trend_osma_strategy(Strategy):
def __init__(self):
super(hull_trend_osma_strategy, self).__init__()
self._hull_period = self.Param("HullPeriod", 20) \
.SetDisplay("Hull Period", "Period for Hull Moving Average", "Indicators")
self._signal_period = self.Param("SignalPeriod", 5) \
.SetDisplay("Signal Period", "Period for signal SMA", "Indicators")
self._take_profit = self.Param("TakeProfit", 3.0) \
.SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._stop_loss = self.Param("StopLoss", 2.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev1 = None
self._prev2 = None
self._pending = None
self._hma = None
self._signal = None
@property
def HullPeriod(self):
return self._hull_period.Value
@HullPeriod.setter
def HullPeriod(self, value):
self._hull_period.Value = value
@property
def SignalPeriod(self):
return self._signal_period.Value
@SignalPeriod.setter
def SignalPeriod(self, value):
self._signal_period.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(hull_trend_osma_strategy, self).OnStarted2(time)
self._prev1 = None
self._prev2 = None
self._hma = HullMovingAverage()
self._hma.Length = self.HullPeriod
self._signal = SimpleMovingAverage()
self._signal.Length = self.SignalPeriod
self.SubscribeCandles(self.CandleType) \
.Bind(self._hma, self.ProcessCandle) \
.Start()
self.StartProtection(
takeProfit=Unit(float(self.TakeProfit), UnitTypes.Absolute),
stopLoss=Unit(float(self.StopLoss), UnitTypes.Absolute),
useMarketOrders=True
)
def ProcessCandle(self, candle, hma_val):
if candle.State != CandleStates.Finished:
return
if not self._hma.IsFormed:
return
smooth = process_float(self._signal, hma_val, candle.OpenTime, True)
if not smooth.IsFormed:
return
osma = float(hma_val) - float(smooth)
if self._prev1 is None or self._prev2 is None:
self._prev2 = self._prev1
self._prev1 = osma
return
prev = self._prev1
prev_prev = self._prev2
is_rising = prev > prev_prev and osma >= prev
is_falling = prev < prev_prev and osma <= prev
if self.IsFormedAndOnlineAndAllowTrading() and (self._pending is None or self._pending.State in (OrderStates.Done, OrderStates.Failed)):
if is_rising and self.Position <= 0:
self._pending = self.BuyMarket(self.Volume - min(0, self.Position))
elif is_falling and self.Position >= 0:
self._pending = self.SellMarket(self.Volume + max(0, self.Position))
self._prev2 = self._prev1
self._prev1 = osma
def OnReseted(self):
super(hull_trend_osma_strategy, self).OnReseted()
self._prev1 = None
self._prev2 = None
self._hma = None
self._signal = None
def CreateClone(self):
return hull_trend_osma_strategy()