Color Trend CF Strategy
This strategy is a conversion of the MQL expert Exp_ColorTrend_CF. It uses two exponential moving averages to detect trend changes. The fast EMA reacts quickly to price movement, while the slow EMA acts as a trend filter. A long position is opened when the fast EMA crosses above the slow EMA. A short position is opened when the fast EMA crosses below the slow EMA.
Parameters
Period– base period for the fast EMA; the slow EMA uses double this value.StopLoss– stop-loss distance in price units.TakeProfit– take-profit distance in price units.AllowBuyOpen– permission to open long positions.AllowSellOpen– permission to open short positions.AllowBuyClose– permission to close long positions on sell signal.AllowSellClose– permission to close short positions on buy signal.CandleType– timeframe for indicator calculation.
Trading Logic
- Subscribe to candles of the selected timeframe.
- Calculate fast and slow EMAs.
- When fast EMA crosses above slow EMA:
- Close short positions if allowed.
- Open long position if allowed.
- When fast EMA crosses below slow EMA:
- Close long positions if allowed.
- Open short position if allowed.
- For open positions apply stop-loss and take-profit levels.
This implementation uses StockSharp high level API with indicator binding.
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on crossing of two exponential moving averages.
/// Shorter EMA represents rising pressure while longer EMA acts as trend
/// filter.
/// </summary>
public class ColorTrendCfStrategy : Strategy {
private readonly StrategyParam<int> _period;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<bool> _allowBuyOpen;
private readonly StrategyParam<bool> _allowSellOpen;
private readonly StrategyParam<bool> _allowBuyClose;
private readonly StrategyParam<bool> _allowSellClose;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private bool _isLong;
/// <summary>
/// Base period for the fast EMA. Slow EMA uses double value.
/// </summary>
public int Period {
get => _period.Value;
set => _period.Value = value;
}
/// <summary>
/// Stop loss distance in price units.
/// </summary>
public decimal StopLoss {
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Take profit distance in price units.
/// </summary>
public decimal TakeProfit {
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Whether long entries are allowed.
/// </summary>
public bool AllowBuyOpen {
get => _allowBuyOpen.Value;
set => _allowBuyOpen.Value = value;
}
/// <summary>
/// Whether short entries are allowed.
/// </summary>
public bool AllowSellOpen {
get => _allowSellOpen.Value;
set => _allowSellOpen.Value = value;
}
/// <summary>
/// Whether closing long positions on sell signals is allowed.
/// </summary>
public bool AllowBuyClose {
get => _allowBuyClose.Value;
set => _allowBuyClose.Value = value;
}
/// <summary>
/// Whether closing short positions on buy signals is allowed.
/// </summary>
public bool AllowSellClose {
get => _allowSellClose.Value;
set => _allowSellClose.Value = value;
}
/// <summary>
/// Candle type used for indicator calculation.
/// </summary>
public DataType CandleType {
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="ColorTrendCfStrategy"/>.
/// </summary>
public ColorTrendCfStrategy() {
_period = Param(nameof(Period), 30)
.SetGreaterThanZero()
.SetDisplay("CF Period", "Base period for fast EMA",
"Indicator")
.SetOptimize(10, 60, 10);
_stopLoss =
Param(nameof(StopLoss), 1000m)
.SetDisplay("Stop Loss", "Stop loss in price units", "Risk")
.SetOptimize(100m, 2000m, 100m);
_takeProfit =
Param(nameof(TakeProfit), 2000m)
.SetDisplay("Take Profit", "Take profit in price units", "Risk")
.SetOptimize(100m, 4000m, 100m);
_allowBuyOpen = Param(nameof(AllowBuyOpen), true)
.SetDisplay("Allow Buy", "Permission to open long",
"Permissions");
_allowSellOpen =
Param(nameof(AllowSellOpen), true)
.SetDisplay("Allow Sell", "Permission to open short",
"Permissions");
_allowBuyClose =
Param(nameof(AllowBuyClose), true)
.SetDisplay("Close Long", "Allow closing long positions",
"Permissions");
_allowSellClose =
Param(nameof(AllowSellClose), true)
.SetDisplay("Close Short", "Allow closing short positions",
"Permissions");
_candleType =
Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for indicator",
"General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)>
GetWorkingSecurities() {
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted() {
base.OnReseted();
_entryPrice = 0m;
_isLong = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time) {
base.OnStarted2(time);
var fastEma = new EMA { Length = Period };
var slowEma = new EMA { Length = Period * 2 };
var subscription = SubscribeCandles(CandleType);
var prevFast = 0m;
var prevSlow = 0m;
var initialized = false;
subscription
.Bind(fastEma, slowEma,
(candle, fast, slow) => {
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!initialized) {
prevFast = fast;
prevSlow = slow;
initialized = true;
return;
}
var crossUp = prevFast <= prevSlow && fast > slow;
var crossDown = prevFast >= prevSlow && fast < slow;
prevFast = fast;
prevSlow = slow;
if (crossUp) {
if (AllowSellClose && Position < 0)
BuyMarket(Math.Abs(Position));
if (AllowBuyOpen && Position <= 0) {
_entryPrice = candle.ClosePrice;
_isLong = true;
BuyMarket(Volume + Math.Abs(Position));
}
} else if (crossDown) {
if (AllowBuyClose && Position > 0)
SellMarket(Position);
if (AllowSellOpen && Position >= 0) {
_entryPrice = candle.ClosePrice;
_isLong = false;
SellMarket(Volume + Math.Abs(Position));
}
}
if (_entryPrice != 0m) {
if (_isLong && Position > 0) {
var stop = _entryPrice - StopLoss;
var take = _entryPrice + TakeProfit;
if (candle.LowPrice <= stop ||
candle.HighPrice >= take)
SellMarket(Position);
} else if (!_isLong && Position < 0) {
var stop = _entryPrice + StopLoss;
var take = _entryPrice - TakeProfit;
if (candle.HighPrice >= stop ||
candle.LowPrice <= take)
BuyMarket(Math.Abs(Position));
}
}
})
.Start();
StartProtection(null, null);
}
}
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 color_trend_cf_strategy(Strategy):
def __init__(self):
super(color_trend_cf_strategy, self).__init__()
self._period = self.Param("Period", 30) \
.SetDisplay("CF Period", "Base period for fast EMA", "Indicator")
self._stop_loss = self.Param("StopLoss", 1000.0) \
.SetDisplay("Stop Loss", "Stop loss in price units", "Risk")
self._take_profit = self.Param("TakeProfit", 2000.0) \
.SetDisplay("Take Profit", "Take profit in price units", "Risk")
self._allow_buy_open = self.Param("AllowBuyOpen", True) \
.SetDisplay("Allow Buy", "Permission to open long", "Permissions")
self._allow_sell_open = self.Param("AllowSellOpen", True) \
.SetDisplay("Allow Sell", "Permission to open short", "Permissions")
self._allow_buy_close = self.Param("AllowBuyClose", True) \
.SetDisplay("Close Long", "Allow closing long positions", "Permissions")
self._allow_sell_close = self.Param("AllowSellClose", True) \
.SetDisplay("Close Short", "Allow closing short positions", "Permissions")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for indicator", "General")
self._entry_price = 0.0
self._is_long = False
self._prev_fast = 0.0
self._prev_slow = 0.0
self._initialized = False
@property
def Period(self):
return self._period.Value
@Period.setter
def Period(self, value):
self._period.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def AllowBuyOpen(self):
return self._allow_buy_open.Value
@AllowBuyOpen.setter
def AllowBuyOpen(self, value):
self._allow_buy_open.Value = value
@property
def AllowSellOpen(self):
return self._allow_sell_open.Value
@AllowSellOpen.setter
def AllowSellOpen(self, value):
self._allow_sell_open.Value = value
@property
def AllowBuyClose(self):
return self._allow_buy_close.Value
@AllowBuyClose.setter
def AllowBuyClose(self, value):
self._allow_buy_close.Value = value
@property
def AllowSellClose(self):
return self._allow_sell_close.Value
@AllowSellClose.setter
def AllowSellClose(self, value):
self._allow_sell_close.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(color_trend_cf_strategy, self).OnStarted2(time)
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.Period
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.Period * 2
self.SubscribeCandles(self.CandleType) \
.Bind(fast_ema, slow_ema, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast = float(fast_value)
slow = float(slow_value)
if not self._initialized:
self._prev_fast = fast
self._prev_slow = slow
self._initialized = True
return
cross_up = self._prev_fast <= self._prev_slow and fast > slow
cross_down = self._prev_fast >= self._prev_slow and fast < slow
self._prev_fast = fast
self._prev_slow = slow
if cross_up:
if self.AllowSellClose and self.Position < 0:
self.BuyMarket(abs(self.Position))
if self.AllowBuyOpen and self.Position <= 0:
self._entry_price = float(candle.ClosePrice)
self._is_long = True
self.BuyMarket()
elif cross_down:
if self.AllowBuyClose and self.Position > 0:
self.SellMarket(self.Position)
if self.AllowSellOpen and self.Position >= 0:
self._entry_price = float(candle.ClosePrice)
self._is_long = False
self.SellMarket()
if self._entry_price != 0.0:
if self._is_long and self.Position > 0:
stop = self._entry_price - float(self.StopLoss)
take = self._entry_price + float(self.TakeProfit)
if float(candle.LowPrice) <= stop or float(candle.HighPrice) >= take:
self.SellMarket(self.Position)
elif not self._is_long and self.Position < 0:
stop = self._entry_price + float(self.StopLoss)
take = self._entry_price - float(self.TakeProfit)
if float(candle.HighPrice) >= stop or float(candle.LowPrice) <= take:
self.BuyMarket(abs(self.Position))
def OnReseted(self):
super(color_trend_cf_strategy, self).OnReseted()
self._entry_price = 0.0
self._is_long = False
self._prev_fast = 0.0
self._prev_slow = 0.0
self._initialized = False
def CreateClone(self):
return color_trend_cf_strategy()