Improvisando Strategy
Improvisando mixes a basic EMA trend filter with RSI swings. The goal is to follow the prevailing direction indicated by the EMA while entering only when the RSI crosses the neutral 50 line. The original design also experimented with MACD style momentum but this simplified version focuses on clarity and ease of tuning.
The user can enable long and/or short trades separately.
Details
- Entry Criteria:
- Long:
Close > EMAandRSI > 50 - Short:
Close < EMAandRSI < 50
- Long:
- Long/Short: Configurable
- Exit Criteria:
- Opposite signal
- Stops: None
- Default Values:
EmaLength= 10RsiLength= 14
- Filters:
- Category: Trend following
- Direction: Configurable
- Indicators: EMA, RSI
- Stops: No
- Complexity: Low
- Timeframe: Short-term
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Improvisando Strategy.
/// Uses EMA trend + RSI filter + candle pattern (engulfing) for entries.
/// Exits via take profit or EMA crossback.
/// </summary>
public class ImprovisandoStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleTypeParam;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _cooldownBars;
private ExponentialMovingAverage _ema;
private RelativeStrengthIndex _rsi;
private decimal _prevClose;
private decimal _prevOpen;
private int _cooldownRemaining;
private decimal? _entryPrice;
public ImprovisandoStrategy()
{
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
_emaLength = Param(nameof(EmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period", "Moving Averages");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "RSI");
_cooldownBars = Param(nameof(CooldownBars), 15)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ema = null;
_rsi = null;
_prevClose = 0;
_prevOpen = 0;
_cooldownRemaining = 0;
_entryPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ema = new ExponentialMovingAverage { Length = EmaLength };
_rsi = new RelativeStrengthIndex { Length = RsiLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, _rsi, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal ema, decimal rsi)
{
if (candle.State != CandleStates.Finished)
return;
if (!_ema.IsFormed || !_rsi.IsFormed)
{
_prevClose = candle.ClosePrice;
_prevOpen = candle.OpenPrice;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevClose = candle.ClosePrice;
_prevOpen = candle.OpenPrice;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevClose = candle.ClosePrice;
_prevOpen = candle.OpenPrice;
return;
}
var close = candle.ClosePrice;
var open = candle.OpenPrice;
// Engulfing patterns
var prevBearish = _prevClose < _prevOpen && _prevClose > 0;
var prevBullish = _prevClose > _prevOpen && _prevClose > 0;
// Bullish engulfing: previous red, current green, close above prev open
var buyPattern = prevBearish && close > open && close > _prevOpen;
// Bearish engulfing: previous green, current red, close below prev open
var sellPattern = prevBullish && close < open && close < _prevOpen;
// Exit long: price crosses below EMA or take profit
if (Position > 0)
{
var tp = _entryPrice.HasValue && close > _entryPrice.Value * 1.02m;
if (close < ema || tp)
{
SellMarket(Math.Abs(Position));
_entryPrice = null;
_cooldownRemaining = CooldownBars;
_prevClose = close;
_prevOpen = open;
return;
}
}
// Exit short: price crosses above EMA or take profit
else if (Position < 0)
{
var tp = _entryPrice.HasValue && close < _entryPrice.Value * 0.98m;
if (close > ema || tp)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
_cooldownRemaining = CooldownBars;
_prevClose = close;
_prevOpen = open;
return;
}
}
// Buy: engulfing + above EMA + RSI not overbought
if (buyPattern && close > ema && rsi < 65 && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_entryPrice = close;
_cooldownRemaining = CooldownBars;
}
// Sell: engulfing + below EMA + RSI not oversold
else if (sellPattern && close < ema && rsi > 35 && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_entryPrice = close;
_cooldownRemaining = CooldownBars;
}
_prevClose = close;
_prevOpen = open;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class improvisando_strategy(Strategy):
"""Improvisando Strategy. EMA trend + RSI filter + engulfing pattern."""
def __init__(self):
super(improvisando_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "EMA period", "Moving Averages")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period", "RSI")
self._cooldown_bars = self.Param("CooldownBars", 15) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._ema = None
self._rsi = None
self._prev_close = 0.0
self._prev_open = 0.0
self._cooldown_remaining = 0
self._entry_price = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(improvisando_strategy, self).OnReseted()
self._ema = None
self._rsi = None
self._prev_close = 0.0
self._prev_open = 0.0
self._cooldown_remaining = 0
self._entry_price = None
def OnStarted2(self, time):
super(improvisando_strategy, self).OnStarted2(time)
self._ema = ExponentialMovingAverage()
self._ema.Length = int(self._ema_length.Value)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._ema, self._rsi, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, ema_val, rsi_val):
if candle.State != CandleStates.Finished:
return
if not self._ema.IsFormed or not self._rsi.IsFormed:
self._prev_close = float(candle.ClosePrice)
self._prev_open = float(candle.OpenPrice)
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = float(candle.ClosePrice)
self._prev_open = float(candle.OpenPrice)
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_close = float(candle.ClosePrice)
self._prev_open = float(candle.OpenPrice)
return
close = float(candle.ClosePrice)
opn = float(candle.OpenPrice)
ema = float(ema_val)
rsi = float(rsi_val)
cooldown = int(self._cooldown_bars.Value)
prev_bearish = self._prev_close < self._prev_open and self._prev_close > 0
prev_bullish = self._prev_close > self._prev_open and self._prev_close > 0
buy_pattern = prev_bearish and close > opn and close > self._prev_open
sell_pattern = prev_bullish and close < opn and close < self._prev_open
# Exit long
if self.Position > 0:
tp = self._entry_price is not None and close > self._entry_price * 1.02
if close < ema or tp:
self.SellMarket(Math.Abs(self.Position))
self._entry_price = None
self._cooldown_remaining = cooldown
self._prev_close = close
self._prev_open = opn
return
# Exit short
elif self.Position < 0:
tp = self._entry_price is not None and close < self._entry_price * 0.98
if close > ema or tp:
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = None
self._cooldown_remaining = cooldown
self._prev_close = close
self._prev_open = opn
return
# Buy: engulfing + above EMA + RSI not overbought
if buy_pattern and close > ema and rsi < 65 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._entry_price = close
self._cooldown_remaining = cooldown
# Sell: engulfing + below EMA + RSI not oversold
elif sell_pattern and close < ema and rsi > 35 and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._entry_price = close
self._cooldown_remaining = cooldown
self._prev_close = close
self._prev_open = opn
def CreateClone(self):
return improvisando_strategy()