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
{
// The bundled sample history ships this instrument next to the primary one, so the hedge leg
// has a tradable default.
private const string DefaultHedgeSecurityId = "TONUSDT@BNBFT";
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(nameof(HedgeSecurity), new Security { Id = DefaultHedgeSecurityId })
.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.BusinessEntities")
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.BusinessEntities import Security
from StockSharp.Messages import CandleStates, DataType
from System import TimeSpan
# The bundled sample history ships this instrument next to the primary one, so the hedge leg
# has a tradable default.
DEFAULT_HEDGE_SECURITY_ID = "TONUSDT@BNBFT"
class improve_ma_rsi_hedge_strategy(Strategy):
"""Dual smoothed moving average and RSI hedge strategy converted from Improve.mq5."""
def __init__(self):
super(improve_ma_rsi_hedge_strategy, self).__init__()
default_hedge = Security()
default_hedge.Id = DEFAULT_HEDGE_SECURITY_ID
self._profit_target = self.Param("ProfitTarget", 50.0)
self._hedge_security = self.Param[Security]("HedgeSecurity", default_hedge)
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._hedge_last_close = 0.0
self._base_entry_price = 0.0
self._hedge_entry_price = 0.0
self._has_base_close = False
self._has_hedge_close = False
self._pair_direction = 0
@property
def ProfitTarget(self):
return self._profit_target.Value
@property
def HedgeSecurity(self):
return self._hedge_security.Value
@HedgeSecurity.setter
def HedgeSecurity(self, value):
self._hedge_security.Value = 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 GetWorkingSecurities(self):
securities = []
if self.Security is not None:
securities.append((self.Security, self.CandleType))
if self.HedgeSecurity is not None:
securities.append((self.HedgeSecurity, self.CandleType))
return securities
def OnStarted2(self, time):
super(improve_ma_rsi_hedge_strategy, self).OnStarted2(time)
if self.Security is None:
raise Exception("Primary security must be specified.")
if self.HedgeSecurity is None:
raise Exception("Hedge security must be specified.")
if self.FastMaPeriod >= self.SlowMaPeriod:
raise Exception("Fast MA period must be less than slow MA period.")
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
base_subscription = self.SubscribeCandles(self.CandleType)
base_subscription.Bind(self._fast_ma, self._slow_ma, self._rsi, self._process_base_candle).Start()
# The hedge leg only contributes its closing price, so it needs no indicators.
hedge_subscription = self.SubscribeCandles(self.CandleType, False, self.HedgeSecurity)
hedge_subscription.Bind(self._process_hedge_candle).Start()
def _process_base_candle(self, candle, fast_val, slow_val, rsi_val):
if candle.State != CandleStates.Finished:
return
self._base_last_close = float(candle.ClosePrice)
self._has_base_close = True
self._check_profit_target()
if not self.IsFormedAndOnlineAndAllowTrading():
return
if not self._fast_ma.IsFormed or not self._slow_ma.IsFormed or not self._rsi.IsFormed:
return
# Both legs are opened together, so a new pair starts only from a flat state.
if self._pair_direction != 0:
return
# The hedge entry price comes from its own candle, so wait until one arrived.
if not self._has_hedge_close:
return
fast_value = float(fast_val)
slow_value = float(slow_val)
rsi_value = float(rsi_val)
if slow_value > fast_value and rsi_value <= self.OversoldLevel:
self._open_pair(1)
elif slow_value < fast_value and rsi_value >= self.OverboughtLevel:
self._open_pair(-1)
def _process_hedge_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._hedge_last_close = float(candle.ClosePrice)
self._has_hedge_close = True
self._check_profit_target()
def _open_pair(self, direction):
if direction == 0:
return
if self._position_of(self.Security) != 0 or self._position_of(self.HedgeSecurity) != 0:
return
volume = self.Volume
# The hedge leg mirrors the primary direction instead of offsetting it.
if direction > 0:
self.BuyMarket(volume, self.Security)
self.BuyMarket(volume, self.HedgeSecurity)
else:
self.SellMarket(volume, self.Security)
self.SellMarket(volume, self.HedgeSecurity)
self._pair_direction = direction
self._base_entry_price = self._base_last_close
self._hedge_entry_price = self._hedge_last_close
def _check_profit_target(self):
if self._pair_direction == 0 or not self._has_base_close or not self._has_hedge_close:
return
volume = float(self.Volume)
if self._pair_direction > 0:
base_profit = (self._base_last_close - self._base_entry_price) * volume
hedge_profit = (self._hedge_last_close - self._hedge_entry_price) * volume
else:
base_profit = (self._base_entry_price - self._base_last_close) * volume
hedge_profit = (self._hedge_entry_price - self._hedge_last_close) * volume
if base_profit + hedge_profit >= self.ProfitTarget:
self._close_pair()
def _close_pair(self):
base_position = self._position_of(self.Security)
if base_position > 0:
self.SellMarket(base_position, self.Security)
elif base_position < 0:
self.BuyMarket(abs(base_position), self.Security)
hedge_position = self._position_of(self.HedgeSecurity)
if hedge_position > 0:
self.SellMarket(hedge_position, self.HedgeSecurity)
elif hedge_position < 0:
self.BuyMarket(abs(hedge_position), self.HedgeSecurity)
self._pair_direction = 0
self._base_entry_price = 0.0
self._hedge_entry_price = 0.0
def _position_of(self, security):
# A leg without a position reports None, which is the same as being flat.
position = self.GetPositionValue(security, self.Portfolio)
return 0 if position is None else position
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._hedge_last_close = 0.0
self._base_entry_price = 0.0
self._hedge_entry_price = 0.0
self._has_base_close = False
self._has_hedge_close = False
self._pair_direction = 0
def CreateClone(self):
return improve_ma_rsi_hedge_strategy()