Стратегия Improve MA & RSI Hedge
Стратегия является переносом эксперта MetaTrader «Improve» на платформу StockSharp. Она одновременно торгует двумя инструментами: основным, выбранным в настройках стратегии, и хеджирующим. Направление сделки определяется положением двух сглаженных скользящих средних (SMMA) на основном инструменте и значением индикатора RSI. Хеджирующая нога открывается в том же направлении, формируя синхронную позицию на двух инструментах и уменьшая риск отдельного актива.
Логика стратегии
- Рассчитываются две сглаженные скользящие средние (SMMA) с настраиваемыми периодами для основного инструмента.
- RSI вычисляется по тем же свечам и сравнивается с уровнями перепроданности/перекупленности.
- Длинная позиция открывается одновременно по обоим инструментам, когда медленная SMMA находится выше быстрой, а RSI ниже или равен уровню перепроданности.
- Короткая позиция открывается по обоим инструментам, когда медленная SMMA ниже быстрой, а RSI выше или равен уровню перекупленности.
- Открытые позиции удерживаются до тех пор, пока суммарная плавающая прибыль по двум ногам не превысит заданный денежный порог. После достижения цели обе позиции закрываются рыночными заявками.
Алгоритм хранит последние цены закрытия каждого инструмента и оценивает суммарную прибыль через разницу между текущей ценой и ценой входа. Стоп‑лоссов нет, поэтому позиции могут находиться в рынке долгое время, если цена не достигает цели по прибыли.
Параметры
| Параметр |
Описание |
| Volume |
Объём заявки для основного и хеджирующего инструмента. |
| Profit Target |
Денежная цель для обеих ног. При достижении значения все позиции закрываются. |
| Hedge Security |
Хеджирующий инструмент, торгуемый вместе с основным. |
| Fast MA |
Период быстрой сглаженной скользящей средней (по умолчанию 8). |
| Slow MA |
Период медленной сглаженной скользящей средней (по умолчанию 21). Должен быть больше периода быстрой. |
| RSI Period |
Длина расчёта RSI (по умолчанию 21). |
| Oversold |
Уровень RSI для входа в покупки при выполнении условия по средним (по умолчанию 30). |
| Overbought |
Уровень RSI для входа в продажи при выполнении условия по средним (по умолчанию 70). |
| Candle Type |
Таймфрейм свечей для расчётов. По умолчанию используется часовой интервал. |
Используемые индикаторы
- Smoothed Moving Average (SMMA) — два индикатора с разными периодами для оценки направления тренда.
- Relative Strength Index (RSI) — определяет зоны перепроданности и перекупленности.
Правила входа и выхода
- Покупка
- Медленная SMMA выше быстрой.
- RSI ≤ уровень перепроданности.
- Рыночные заявки на покупку отправляются по основному и хеджирующему инструментам.
- Продажа
- Медленная SMMA ниже быстрой.
- RSI ≥ уровень перекупленности.
- Рыночные заявки на продажу отправляются по обоим инструментам.
- Выход
- Когда
(прибыль по основному инструменту + прибыль по хеджу) ≥ Profit Target, обе позиции закрываются рыночными заявками.
- Дополнительных стоп‑лоссов и трейлинг‑стопов нет. При необходимости их следует реализовывать отдельно.
Дополнительные рекомендации
- Перед запуском стратегии необходимо указать как основной, так и хеджирующий инструменты, иначе будет сгенерировано исключение.
- Суммарная прибыль рассчитывается по ценам закрытия свечей, поэтому фактический результат может отличаться из‑за проскальзывания и различий в исполнении.
- Стратегия подходит для коррелированных инструментов (например, валютных пар или связанных фьючерсов), где ожидается синхронное движение.
- Рекомендуется дополнить стратегию портфельными ограничениями по риску, поскольку единственным механизмом выхода остаётся виртуальная цель по прибыли.
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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Dual smoothed moving average and RSI hedge strategy converted from Improve.mq5.
/// </summary>
public class ImproveMaRsiHedgeStrategy : Strategy
{
private readonly StrategyParam<decimal> _profitTarget;
private readonly StrategyParam<Security> _hedgeSecurity;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<DataType> _candleType;
private SmoothedMovingAverage _fastMa = null!;
private SmoothedMovingAverage _slowMa = null!;
private RelativeStrengthIndex _rsi = null!;
private decimal _baseLastClose;
private decimal _hedgeLastClose;
private decimal _baseEntryPrice;
private decimal _hedgeEntryPrice;
private bool _hasBaseClose;
private bool _hasHedgeClose;
private int _pairDirection;
/// <summary>
/// Profit target across both legs expressed in money.
/// </summary>
public decimal ProfitTarget
{
get => _profitTarget.Value;
set => _profitTarget.Value = value;
}
/// <summary>
/// Second instrument traded alongside the primary security.
/// </summary>
public Security HedgeSecurity
{
get => _hedgeSecurity.Value;
set => _hedgeSecurity.Value = value;
}
/// <summary>
/// Smoothed moving average period for the fast line.
/// </summary>
public int FastMaPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Smoothed moving average period for the slow line.
/// </summary>
public int SlowMaPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// RSI calculation length.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI oversold threshold.
/// </summary>
public decimal OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
/// <summary>
/// RSI overbought threshold.
/// </summary>
public decimal OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
/// <summary>
/// Type of candles used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ImproveMaRsiHedgeStrategy"/> class.
/// </summary>
public ImproveMaRsiHedgeStrategy()
{
_profitTarget = Param(nameof(ProfitTarget), 50m)
.SetGreaterThanZero()
.SetDisplay("Profit Target", "Combined profit target across both legs", "Risk")
;
_hedgeSecurity = Param<Security>(nameof(HedgeSecurity))
.SetDisplay("Hedge Security", "Secondary instrument to trade", "General");
_fastPeriod = Param(nameof(FastMaPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("Fast MA", "Fast smoothed MA period", "Indicators")
;
_slowPeriod = Param(nameof(SlowMaPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Slow MA", "Slow smoothed MA period", "Indicators")
;
_rsiPeriod = Param(nameof(RsiPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Length of the RSI", "Indicators")
;
_oversoldLevel = Param(nameof(OversoldLevel), 30m)
.SetDisplay("Oversold", "RSI oversold threshold", "Indicators")
;
_overboughtLevel = Param(nameof(OverboughtLevel), 70m)
.SetDisplay("Overbought", "RSI overbought threshold", "Indicators")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Time frame for calculations", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security != null)
yield return (Security, CandleType);
if (HedgeSecurity != null)
yield return (HedgeSecurity, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastMa = null!;
_slowMa = null!;
_rsi = null!;
_baseLastClose = 0m;
_hedgeLastClose = 0m;
_baseEntryPrice = 0m;
_hedgeEntryPrice = 0m;
_hasBaseClose = false;
_hasHedgeClose = false;
_pairDirection = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Security == null)
throw new InvalidOperationException("Primary security must be specified.");
if (HedgeSecurity == null)
throw new InvalidOperationException("Hedge security must be specified.");
if (FastMaPeriod >= SlowMaPeriod)
throw new InvalidOperationException("Fast MA period must be less than slow MA period.");
_fastMa = new SmoothedMovingAverage { Length = FastMaPeriod };
_slowMa = new SmoothedMovingAverage { Length = SlowMaPeriod };
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var baseSubscription = SubscribeCandles(CandleType);
baseSubscription
.Bind(_fastMa, _slowMa, _rsi, ProcessBaseCandle)
.Start();
var hedgeSubscription = SubscribeCandles(CandleType, false, HedgeSecurity);
hedgeSubscription
.Bind(ProcessHedgeCandle)
.Start();
}
private void ProcessBaseCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
_baseLastClose = candle.ClosePrice;
_hasBaseClose = true;
CheckProfitTarget();
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_fastMa.IsFormed || !_slowMa.IsFormed || !_rsi.IsFormed)
return;
if (_pairDirection != 0)
return;
if (!_hasHedgeClose)
return;
if (slowValue > fastValue && rsiValue <= OversoldLevel)
{
OpenPair(1);
}
else if (slowValue < fastValue && rsiValue >= OverboughtLevel)
{
OpenPair(-1);
}
}
private void ProcessHedgeCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_hedgeLastClose = candle.ClosePrice;
_hasHedgeClose = true;
CheckProfitTarget();
}
private void OpenPair(int direction)
{
if (direction == 0)
return;
var basePos = GetPositionValue(Security, Portfolio) ?? 0m;
var hedgePos = GetPositionValue(HedgeSecurity, Portfolio) ?? 0m;
if (basePos != 0m || hedgePos != 0m)
return;
var volume = Volume;
if (direction > 0)
{
BuyMarket(volume, Security);
BuyMarket(volume, HedgeSecurity);
}
else
{
SellMarket(volume, Security);
SellMarket(volume, HedgeSecurity);
}
_pairDirection = direction;
_baseEntryPrice = _baseLastClose;
_hedgeEntryPrice = _hedgeLastClose;
}
private void CheckProfitTarget()
{
if (_pairDirection == 0 || !_hasBaseClose || !_hasHedgeClose)
return;
var baseProfit = _pairDirection > 0
? (_baseLastClose - _baseEntryPrice) * Volume
: (_baseEntryPrice - _baseLastClose) * Volume;
var hedgeProfit = _pairDirection > 0
? (_hedgeLastClose - _hedgeEntryPrice) * Volume
: (_hedgeEntryPrice - _hedgeLastClose) * Volume;
var totalProfit = baseProfit + hedgeProfit;
if (totalProfit >= ProfitTarget)
{
ClosePair();
}
}
private void ClosePair()
{
var basePos = GetPositionValue(Security, Portfolio) ?? 0m;
if (basePos > 0)
{
SellMarket(basePos, Security);
}
else if (basePos < 0)
{
BuyMarket(-basePos, Security);
}
var hedgePos = GetPositionValue(HedgeSecurity, Portfolio) ?? 0m;
if (hedgePos > 0)
{
SellMarket(hedgePos, HedgeSecurity);
}
else if (hedgePos < 0)
{
BuyMarket(-hedgePos, HedgeSecurity);
}
_pairDirection = 0;
_baseEntryPrice = 0m;
_hedgeEntryPrice = 0m;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import SmoothedMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType
from System import TimeSpan
class improve_ma_rsi_hedge_strategy(Strategy):
def __init__(self):
super(improve_ma_rsi_hedge_strategy, self).__init__()
self._profit_target = self.Param("ProfitTarget", 50.0)
self._fast_period = self.Param("FastMaPeriod", 8)
self._slow_period = self.Param("SlowMaPeriod", 21)
self._rsi_period = self.Param("RsiPeriod", 21)
self._oversold_level = self.Param("OversoldLevel", 30.0)
self._overbought_level = self.Param("OverboughtLevel", 70.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._fast_ma = None
self._slow_ma = None
self._rsi = None
self._base_last_close = 0.0
self._base_entry_price = 0.0
self._pair_direction = 0
@property
def ProfitTarget(self):
return self._profit_target.Value
@property
def FastMaPeriod(self):
return self._fast_period.Value
@property
def SlowMaPeriod(self):
return self._slow_period.Value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@property
def OversoldLevel(self):
return self._oversold_level.Value
@property
def OverboughtLevel(self):
return self._overbought_level.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(improve_ma_rsi_hedge_strategy, self).OnStarted2(time)
self._fast_ma = SmoothedMovingAverage()
self._fast_ma.Length = self.FastMaPeriod
self._slow_ma = SmoothedMovingAverage()
self._slow_ma.Length = self.SlowMaPeriod
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._fast_ma, self._slow_ma, self._rsi, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val, rsi_val):
fast_value = float(fast_val)
slow_value = float(slow_val)
rsi_value = float(rsi_val)
self._base_last_close = float(candle.ClosePrice)
if not self._fast_ma.IsFormed or not self._slow_ma.IsFormed or not self._rsi.IsFormed:
return
if self._pair_direction != 0:
if self._pair_direction > 0:
pnl = (self._base_last_close - self._base_entry_price) * float(self.Volume)
else:
pnl = (self._base_entry_price - self._base_last_close) * float(self.Volume)
if pnl >= self.ProfitTarget:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
self._pair_direction = 0
self._base_entry_price = 0.0
return
if slow_value > fast_value and rsi_value <= self.OversoldLevel:
self.BuyMarket()
self._pair_direction = 1
self._base_entry_price = self._base_last_close
elif slow_value < fast_value and rsi_value >= self.OverboughtLevel:
self.SellMarket()
self._pair_direction = -1
self._base_entry_price = self._base_last_close
def OnReseted(self):
super(improve_ma_rsi_hedge_strategy, self).OnReseted()
self._fast_ma = None
self._slow_ma = None
self._rsi = None
self._base_last_close = 0.0
self._base_entry_price = 0.0
self._pair_direction = 0
def CreateClone(self):
return improve_ma_rsi_hedge_strategy()