Exp TrendMagic Strategy
Overview
The Exp TrendMagic strategy is a direct port of the MetaTrader 5 expert advisor "Exp_TrendMagic". It monitors the color changes of the TrendMagic indicator, which combines a Commodity Channel Index (CCI) with an Average True Range (ATR) channel. When the indicator switches color, the strategy closes positions from the opposite side and optionally opens a new trade in the direction of the fresh trend.
The conversion keeps the original money management options, configurable signal offset (Signal Bar), and the same permission toggles for entering or exiting long and short trades.
Trading Logic
Indicator inputs
CCI(Commodity Channel Index) with configurable period and applied price.ATR(Average True Range) with configurable period.- The TrendMagic value is computed as:
- When CCI ≥ 0:
TrendMagic = Low - ATR, clamped to avoid decreasing the support line. - When CCI < 0:
TrendMagic = High + ATR, clamped to avoid increasing the resistance line.
- When CCI ≥ 0:
- The resulting line color is 0 for bullish (support below price) and 1 for bearish (resistance above price).
Signal evaluation
- The strategy stores the indicator colors in chronological order to emulate the MetaTrader buffer and uses the
Signal Baroffset to read the most recent completed bar. - If the previous color (
Signal Bar + 1) is 0 and the current color (Signal Bar) is 1, the algorithm treats this as a bullish switch: it closes any short position and, if allowed, opens a long trade. - If the previous color is 1 and the current color is 0, the algorithm closes any open long position and, if permitted, enters a short trade.
- The trade-permission flags (
Allow Buy Entry,Allow Sell Entry,Allow Buy Exit,Allow Sell Exit) follow the exact semantics of the MT5 version.
- The strategy stores the indicator colors in chronological order to emulate the MetaTrader buffer and uses the
Money management
Money Managementdetermines how much capital should be allocated per trade. Negative values are interpreted as a fixed lot size.Margin Modeselects the interpretation of the money-management value:FreeMargin/Balance: invest a share of account equity divided by price.LossFreeMargin/LossBalance: risk a share of capital relative to the stop-loss distance.Lot: treat the value as a fixed volume.
- Volumes are aligned with
VolumeStep,MinVolume, andMaxVolumeof the selected security.
Risk management
- When a new trade is placed, the strategy records the entry price and enforces the original stop-loss and take-profit distances (expressed in points, i.e., multiples of
PriceStep). - Hitting the stop-loss or take-profit immediately closes the position and clears the saved entry price.
- A throttle prevents reopening a position of the same direction before the next candle opens, reproducing the MQL "time level" guard.
- When a new trade is placed, the strategy records the entry price and enforces the original stop-loss and take-profit distances (expressed in points, i.e., multiples of
Parameters
| Parameter | Description |
|---|---|
Money Management |
Fraction of capital used for sizing (negative values become fixed volume). |
Margin Mode |
Conversion mode for money management into volume. |
Stop Loss |
Protective stop distance in price points. |
Take Profit |
Profit target in price points. |
Deviation |
Reserved for compatibility with the MT5 input (not used directly). |
Allow Buy Entry / Allow Sell Entry |
Toggle long/short entries. |
Allow Buy Exit / Allow Sell Exit |
Toggle closing short/long trades. |
Candle Type |
Main timeframe used for indicators and signal evaluation. |
CCI Period / CCI Price |
CCI length and applied price source. |
ATR Period |
ATR length. |
Signal Bar |
Index of the finished bar used for signals (0 = current, 1 = previous, etc.). |
Notes
- The strategy relies on finished candles only (
CandleStates.Finished) to mimic the MT5 tick-based implementation. - All indicator values and state variables reset when the strategy is restarted, ensuring deterministic optimisation runs.
- The
Deviationparameter is provided for full compatibility, even though StockSharp market orders do not use an explicit slippage parameter.
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>
/// TrendMagic based strategy converted from the MetaTrader version.
/// The strategy reacts to TrendMagic color changes and mirrors the
/// original money-management configuration options.
/// </summary>
public class ExpTrendMagicStrategy : Strategy
{
private readonly StrategyParam<decimal> _moneyManagement;
private readonly StrategyParam<MarginModeOptions> _marginMode;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _deviationPoints;
private readonly StrategyParam<bool> _allowBuyEntry;
private readonly StrategyParam<bool> _allowSellEntry;
private readonly StrategyParam<bool> _allowBuyExit;
private readonly StrategyParam<bool> _allowSellExit;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<AppliedPriceModes> _cciPrice;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _signalBar;
private CommodityChannelIndex _cci;
private AverageTrueRange _atr;
private List<int> _colorHistory;
private decimal? _previousTrendMagicValue;
private decimal? _entryPrice;
private TimeSpan _candleTimeFrame;
private DateTimeOffset? _nextLongTradeAllowed;
private DateTimeOffset? _nextShortTradeAllowed;
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public ExpTrendMagicStrategy()
{
_moneyManagement = Param(nameof(MoneyManagement), 0.1m)
.SetDisplay("Money Management", "Share of capital used per trade", "Trading")
.SetOptimize(0.05m, 0.5m, 0.05m);
_marginMode = Param(nameof(MarginMode), MarginModeOptions.Lot)
.SetDisplay("Margin Mode", "Mode used to translate MM into volume", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 1000m)
.SetDisplay("Stop Loss", "Protective stop in points", "Risk")
.SetOptimize(100m, 2000m, 100m);
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000m)
.SetDisplay("Take Profit", "Profit target in points", "Risk")
.SetOptimize(200m, 4000m, 200m);
_deviationPoints = Param(nameof(DeviationPoints), 10m)
.SetDisplay("Deviation", "Maximum price deviation in points", "Trading");
_allowBuyEntry = Param(nameof(AllowBuyEntry), true)
.SetDisplay("Allow Buy Entry", "Enable long entries", "Permissions");
_allowSellEntry = Param(nameof(AllowSellEntry), true)
.SetDisplay("Allow Sell Entry", "Enable short entries", "Permissions");
_allowBuyExit = Param(nameof(AllowBuyExit), true)
.SetDisplay("Allow Buy Exit", "Enable exits for short trades", "Permissions");
_allowSellExit = Param(nameof(AllowSellExit), true)
.SetDisplay("Allow Sell Exit", "Enable exits for long trades", "Permissions");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series", "Data");
_cciPeriod = Param(nameof(CciPeriod), 50)
.SetDisplay("CCI Period", "Length of the CCI", "Indicator")
.SetGreaterThanZero();
_cciPrice = Param(nameof(CciPrice), AppliedPriceModes.Median)
.SetDisplay("CCI Price", "Applied price for the CCI", "Indicator");
_atrPeriod = Param(nameof(AtrPeriod), 5)
.SetDisplay("ATR Period", "Length of the ATR", "Indicator")
.SetGreaterThanZero();
_signalBar = Param(nameof(SignalBar), 1)
.SetDisplay("Signal Bar", "Bar shift used for signals", "Indicator")
.SetNotNegative();
}
/// <summary>
/// Money management multiplier.
/// </summary>
public decimal MoneyManagement
{
get => _moneyManagement.Value;
set => _moneyManagement.Value = value;
}
/// <summary>
/// Mode used to convert the money management value into an order volume.
/// </summary>
public MarginModeOptions MarginMode
{
get => _marginMode.Value;
set => _marginMode.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Allowed deviation in price points (placeholder for compatibility).
/// </summary>
public decimal DeviationPoints
{
get => _deviationPoints.Value;
set => _deviationPoints.Value = value;
}
/// <summary>
/// Enables long entries.
/// </summary>
public bool AllowBuyEntry
{
get => _allowBuyEntry.Value;
set => _allowBuyEntry.Value = value;
}
/// <summary>
/// Enables short entries.
/// </summary>
public bool AllowSellEntry
{
get => _allowSellEntry.Value;
set => _allowSellEntry.Value = value;
}
/// <summary>
/// Enables exiting short positions.
/// </summary>
public bool AllowBuyExit
{
get => _allowBuyExit.Value;
set => _allowBuyExit.Value = value;
}
/// <summary>
/// Enables exiting long positions.
/// </summary>
public bool AllowSellExit
{
get => _allowSellExit.Value;
set => _allowSellExit.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Period used by the CCI indicator.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = Math.Max(1, value);
}
/// <summary>
/// Applied price used as the CCI input.
/// </summary>
public AppliedPriceModes CciPrice
{
get => _cciPrice.Value;
set => _cciPrice.Value = value;
}
/// <summary>
/// Period used by the ATR indicator.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = Math.Max(1, value);
}
/// <summary>
/// Bar offset used when reading TrendMagic colors.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = Math.Max(0, value);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cci = null;
_atr = null;
_colorHistory = null;
_previousTrendMagicValue = null;
_entryPrice = null;
_candleTimeFrame = TimeSpan.Zero;
_nextLongTradeAllowed = null;
_nextShortTradeAllowed = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candleTimeFrame = CandleType.Arg is TimeSpan span ? span : TimeSpan.Zero;
_colorHistory = new List<int>();
_cci = new CommodityChannelIndex
{
Length = CciPeriod,
};
_atr = new AverageTrueRange
{
Length = AtrPeriod,
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
var cci = _cci;
var atr = _atr;
if (cci == null || atr == null)
return;
// check ATR formed
var price = GetAppliedPrice(candle, CciPrice);
var cciIndicatorValue = cci.Process(new DecimalIndicatorValue(cci, price, candle.OpenTime) { IsFinal = true });
if (!cci.IsFormed || !atr.IsFormed)
return;
var atrDecimal = atrValue.ToDecimal();
var cciDecimal = cciIndicatorValue.ToDecimal();
UpdateTrendMagic(candle, cciDecimal, atrDecimal);
}
private void UpdateTrendMagic(ICandleMessage candle, decimal cciValue, decimal atrValue)
{
var color = CalculateColor(candle, cciValue, atrValue);
_colorHistory.Insert(0, color);
var maxHistory = Math.Max(2, SignalBar + 2);
if (_colorHistory.Count > maxHistory)
_colorHistory.RemoveRange(maxHistory, _colorHistory.Count - maxHistory);
if (_colorHistory.Count <= SignalBar + 1)
{
ManageRisk(candle);
return;
}
var recent = _colorHistory[SignalBar];
var older = _colorHistory[SignalBar + 1];
if (older == 0 && AllowSellExit && Position < 0m)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
}
else if (older == 1 && AllowBuyExit && Position > 0m)
{
SellMarket(Position);
_entryPrice = null;
}
if (older == 0 && recent == 1 && AllowBuyEntry)
TryEnterLong(candle);
else if (older == 1 && recent == 0 && AllowSellEntry)
TryEnterShort(candle);
ManageRisk(candle);
}
private void TryEnterLong(ICandleMessage candle)
{
if (_nextLongTradeAllowed.HasValue && candle.OpenTime < _nextLongTradeAllowed.Value)
return;
var volume = CalculateOrderVolume(candle.ClosePrice);
if (volume <= 0m)
return;
if (Position < 0m)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
}
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
_nextLongTradeAllowed = _candleTimeFrame > TimeSpan.Zero
? candle.OpenTime + _candleTimeFrame
: candle.OpenTime;
}
private void TryEnterShort(ICandleMessage candle)
{
if (_nextShortTradeAllowed.HasValue && candle.OpenTime < _nextShortTradeAllowed.Value)
return;
var volume = CalculateOrderVolume(candle.ClosePrice);
if (volume <= 0m)
return;
if (Position > 0m)
{
SellMarket(Position);
_entryPrice = null;
}
SellMarket(volume);
_entryPrice = candle.ClosePrice;
_nextShortTradeAllowed = _candleTimeFrame > TimeSpan.Zero
? candle.OpenTime + _candleTimeFrame
: candle.OpenTime;
}
private void ManageRisk(ICandleMessage candle)
{
if (Position == 0m || _entryPrice is null)
return;
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
step = 1m;
if (Position > 0m)
{
if (StopLossPoints > 0m)
{
var stopPrice = _entryPrice.Value - StopLossPoints * step;
if (candle.LowPrice <= stopPrice)
{
SellMarket(Position);
_entryPrice = null;
return;
}
}
if (TakeProfitPoints > 0m)
{
var takePrice = _entryPrice.Value + TakeProfitPoints * step;
if (candle.HighPrice >= takePrice)
{
SellMarket(Position);
_entryPrice = null;
}
}
}
else if (Position < 0m)
{
if (StopLossPoints > 0m)
{
var stopPrice = _entryPrice.Value + StopLossPoints * step;
if (candle.HighPrice >= stopPrice)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
return;
}
}
if (TakeProfitPoints > 0m)
{
var takePrice = _entryPrice.Value - TakeProfitPoints * step;
if (candle.LowPrice <= takePrice)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
}
}
}
}
private int CalculateColor(ICandleMessage candle, decimal cciValue, decimal atrValue)
{
var previous = _previousTrendMagicValue;
decimal trendMagic;
int color;
if (cciValue >= 0m)
{
trendMagic = candle.LowPrice - atrValue;
if (previous.HasValue && trendMagic < previous.Value)
trendMagic = previous.Value;
color = 0;
}
else
{
trendMagic = candle.HighPrice + atrValue;
if (previous.HasValue && trendMagic > previous.Value)
trendMagic = previous.Value;
color = 1;
}
_previousTrendMagicValue = trendMagic;
return color;
}
private decimal CalculateOrderVolume(decimal price)
{
var mm = MoneyManagement;
if (mm == 0m)
return NormalizeVolume(Volume);
if (mm < 0m)
return NormalizeVolume(Math.Abs(mm));
var security = Security;
var portfolio = Portfolio;
if (security == null || portfolio == null || price <= 0m)
return NormalizeVolume(Volume);
var capital = portfolio.CurrentValue ?? portfolio.BeginValue ?? 0m;
if (capital <= 0m)
return NormalizeVolume(Volume);
decimal volume;
switch (MarginMode)
{
case MarginModeOptions.FreeMargin:
case MarginModeOptions.Balance:
{
var amount = capital * mm;
volume = amount / price;
break;
}
case MarginModeOptions.LossFreeMargin:
case MarginModeOptions.LossBalance:
{
if (StopLossPoints <= 0m)
return NormalizeVolume(Volume);
var step = security.PriceStep ?? 0m;
if (step <= 0m)
return NormalizeVolume(Volume);
var riskPerContract = StopLossPoints * step;
if (riskPerContract <= 0m)
return NormalizeVolume(Volume);
var lossAmount = capital * mm;
volume = lossAmount / riskPerContract;
break;
}
case MarginModeOptions.Lot:
default:
volume = mm;
break;
}
return NormalizeVolume(volume);
}
private decimal NormalizeVolume(decimal volume)
{
var security = Security;
if (security == null)
return volume;
var step = security.VolumeStep ?? 0m;
if (step > 0m)
{
var steps = Math.Round(volume / step, MidpointRounding.AwayFromZero);
volume = steps * step;
}
var minVolume = security.MinVolume ?? 0m;
if (minVolume > 0m && volume < minVolume)
volume = minVolume;
var maxVolume = security.MaxVolume ?? 0m;
if (maxVolume > 0m && volume > maxVolume)
volume = maxVolume;
return volume;
}
private static decimal GetAppliedPrice(ICandleMessage candle, AppliedPriceModes mode)
{
return mode switch
{
AppliedPriceModes.Close => candle.ClosePrice,
AppliedPriceModes.Open => candle.OpenPrice,
AppliedPriceModes.High => candle.HighPrice,
AppliedPriceModes.Low => candle.LowPrice,
AppliedPriceModes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPriceModes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
AppliedPriceModes.Weighted => (candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 4m,
AppliedPriceModes.Average => (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 4m,
_ => candle.ClosePrice,
};
}
/// <summary>
/// Available money-management modes.
/// </summary>
public enum MarginModeOptions
{
/// <summary>
/// Use the account free margin share.
/// </summary>
FreeMargin,
/// <summary>
/// Use the account balance share.
/// </summary>
Balance,
/// <summary>
/// Use a fraction of free margin as risk measured via stop loss.
/// </summary>
LossFreeMargin,
/// <summary>
/// Use a fraction of balance as risk measured via stop loss.
/// </summary>
LossBalance,
/// <summary>
/// Fixed lot size.
/// </summary>
Lot,
}
/// <summary>
/// Applied price options for the CCI input.
/// </summary>
public enum AppliedPriceModes
{
/// <summary>
/// Close price.
/// </summary>
Close,
/// <summary>
/// Open price.
/// </summary>
Open,
/// <summary>
/// High price.
/// </summary>
High,
/// <summary>
/// Low price.
/// </summary>
Low,
/// <summary>
/// Median price (high + low) / 2.
/// </summary>
Median,
/// <summary>
/// Typical price (high + low + close) / 3.
/// </summary>
Typical,
/// <summary>
/// Weighted price (high + low + 2 * close) / 4.
/// </summary>
Weighted,
/// <summary>
/// Average price (open + high + low + close) / 4.
/// </summary>
Average,
}
}
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 AverageTrueRange, CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class exp_trend_magic_strategy(Strategy):
"""CCI + ATR TrendMagic strategy with color change signals and virtual SL/TP."""
# Applied price modes
PRICE_CLOSE = 0
PRICE_OPEN = 1
PRICE_HIGH = 2
PRICE_LOW = 3
PRICE_MEDIAN = 4
PRICE_TYPICAL = 5
PRICE_WEIGHTED = 6
PRICE_AVERAGE = 7
def __init__(self):
super(exp_trend_magic_strategy, self).__init__()
self._money_management = self.Param("MoneyManagement", 0.1) \
.SetDisplay("Money Management", "Share of capital used per trade", "Trading")
self._stop_loss_points = self.Param("StopLossPoints", 1000.0) \
.SetDisplay("Stop Loss", "Protective stop in points", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 2000.0) \
.SetDisplay("Take Profit", "Profit target in points", "Risk")
self._allow_buy_entry = self.Param("AllowBuyEntry", True) \
.SetDisplay("Allow Buy Entry", "Enable long entries", "Permissions")
self._allow_sell_entry = self.Param("AllowSellEntry", True) \
.SetDisplay("Allow Sell Entry", "Enable short entries", "Permissions")
self._allow_buy_exit = self.Param("AllowBuyExit", True) \
.SetDisplay("Allow Buy Exit", "Enable exits for short trades", "Permissions")
self._allow_sell_exit = self.Param("AllowSellExit", True) \
.SetDisplay("Allow Sell Exit", "Enable exits for long trades", "Permissions")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary candle series", "Data")
self._cci_period = self.Param("CciPeriod", 50) \
.SetGreaterThanZero() \
.SetDisplay("CCI Period", "Length of the CCI", "Indicator")
self._cci_price = self.Param("CciPrice", 4) \
.SetDisplay("CCI Price", "Applied price for the CCI (4=Median)", "Indicator")
self._atr_period = self.Param("AtrPeriod", 5) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "Length of the ATR", "Indicator")
self._signal_bar = self.Param("SignalBar", 1) \
.SetDisplay("Signal Bar", "Bar shift used for signals", "Indicator")
self._cci = None
self._atr = None
self._color_history = None
self._previous_trend_magic_value = None
self._entry_price = None
self._candle_time_frame = None
self._next_long_trade_allowed = None
self._next_short_trade_allowed = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def MoneyManagement(self):
return self._money_management.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def AllowBuyEntry(self):
return self._allow_buy_entry.Value
@property
def AllowSellEntry(self):
return self._allow_sell_entry.Value
@property
def AllowBuyExit(self):
return self._allow_buy_exit.Value
@property
def AllowSellExit(self):
return self._allow_sell_exit.Value
@property
def CciPeriod(self):
return self._cci_period.Value
@property
def CciPrice(self):
return self._cci_price.Value
@property
def AtrPeriod(self):
return self._atr_period.Value
@property
def SignalBar(self):
return self._signal_bar.Value
def OnReseted(self):
super(exp_trend_magic_strategy, self).OnReseted()
self._cci = None
self._atr = None
self._color_history = None
self._previous_trend_magic_value = None
self._entry_price = None
self._candle_time_frame = None
self._next_long_trade_allowed = None
self._next_short_trade_allowed = None
def OnStarted2(self, time):
super(exp_trend_magic_strategy, self).OnStarted2(time)
self._color_history = []
self._candle_time_frame = TimeSpan.FromHours(4)
self._cci = CommodityChannelIndex()
self._cci.Length = self.CciPeriod
self._atr = AverageTrueRange()
self._atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._atr, self._process_candle).Start()
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
cci = self._cci
atr = self._atr
if cci is None or atr is None:
return
price = self._get_applied_price(candle, self.CciPrice)
cci_indicator_value = process_float(cci, price, candle.OpenTime, True)
if not cci.IsFormed or not atr.IsFormed:
return
atr_decimal = float(atr_value)
cci_decimal = float(cci_indicator_value)
self._update_trend_magic(candle, cci_decimal, atr_decimal)
def _update_trend_magic(self, candle, cci_value, atr_value):
color = self._calculate_color(candle, cci_value, atr_value)
self._color_history.insert(0, color)
max_history = max(2, self.SignalBar + 2)
if len(self._color_history) > max_history:
self._color_history = self._color_history[:max_history]
if len(self._color_history) <= self.SignalBar + 1:
self._manage_risk(candle)
return
recent = self._color_history[self.SignalBar]
older = self._color_history[self.SignalBar + 1]
if older == 0 and self.AllowSellExit and self.Position < 0:
self.BuyMarket(abs(self.Position))
self._entry_price = None
elif older == 1 and self.AllowBuyExit and self.Position > 0:
self.SellMarket(self.Position)
self._entry_price = None
if older == 0 and recent == 1 and self.AllowBuyEntry:
self._try_enter_long(candle)
elif older == 1 and recent == 0 and self.AllowSellEntry:
self._try_enter_short(candle)
self._manage_risk(candle)
def _try_enter_long(self, candle):
if self._next_long_trade_allowed is not None and candle.OpenTime < self._next_long_trade_allowed:
return
volume = self._calculate_order_volume()
if volume <= 0:
return
if self.Position < 0:
self.BuyMarket(abs(self.Position))
self._entry_price = None
self.BuyMarket(volume)
self._entry_price = float(candle.ClosePrice)
self._next_long_trade_allowed = candle.OpenTime + self._candle_time_frame \
if self._candle_time_frame > TimeSpan.Zero else candle.OpenTime
def _try_enter_short(self, candle):
if self._next_short_trade_allowed is not None and candle.OpenTime < self._next_short_trade_allowed:
return
volume = self._calculate_order_volume()
if volume <= 0:
return
if self.Position > 0:
self.SellMarket(self.Position)
self._entry_price = None
self.SellMarket(volume)
self._entry_price = float(candle.ClosePrice)
self._next_short_trade_allowed = candle.OpenTime + self._candle_time_frame \
if self._candle_time_frame > TimeSpan.Zero else candle.OpenTime
def _manage_risk(self, candle):
if self.Position == 0 or self._entry_price is None:
return
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
ps = float(self.Security.PriceStep)
if ps > 0:
step = ps
if self.Position > 0:
if float(self.StopLossPoints) > 0:
stop_price = self._entry_price - float(self.StopLossPoints) * step
if float(candle.LowPrice) <= stop_price:
self.SellMarket(self.Position)
self._entry_price = None
return
if float(self.TakeProfitPoints) > 0:
take_price = self._entry_price + float(self.TakeProfitPoints) * step
if float(candle.HighPrice) >= take_price:
self.SellMarket(self.Position)
self._entry_price = None
elif self.Position < 0:
if float(self.StopLossPoints) > 0:
stop_price = self._entry_price + float(self.StopLossPoints) * step
if float(candle.HighPrice) >= stop_price:
self.BuyMarket(abs(self.Position))
self._entry_price = None
return
if float(self.TakeProfitPoints) > 0:
take_price = self._entry_price - float(self.TakeProfitPoints) * step
if float(candle.LowPrice) <= take_price:
self.BuyMarket(abs(self.Position))
self._entry_price = None
def _calculate_color(self, candle, cci_value, atr_value):
previous = self._previous_trend_magic_value
if cci_value >= 0:
trend_magic = float(candle.LowPrice) - atr_value
if previous is not None and trend_magic < previous:
trend_magic = previous
color = 0
else:
trend_magic = float(candle.HighPrice) + atr_value
if previous is not None and trend_magic > previous:
trend_magic = previous
color = 1
self._previous_trend_magic_value = trend_magic
return color
def _calculate_order_volume(self):
mm = float(self.MoneyManagement)
if mm == 0:
return float(self.Volume) if self.Volume > 0 else 1.0
if mm < 0:
return abs(mm)
return mm
def _get_applied_price(self, candle, mode):
if mode == self.PRICE_CLOSE:
return float(candle.ClosePrice)
elif mode == self.PRICE_OPEN:
return float(candle.OpenPrice)
elif mode == self.PRICE_HIGH:
return float(candle.HighPrice)
elif mode == self.PRICE_LOW:
return float(candle.LowPrice)
elif mode == self.PRICE_MEDIAN:
return (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
elif mode == self.PRICE_TYPICAL:
return (float(candle.HighPrice) + float(candle.LowPrice) + float(candle.ClosePrice)) / 3.0
elif mode == self.PRICE_WEIGHTED:
return (float(candle.HighPrice) + float(candle.LowPrice) + 2.0 * float(candle.ClosePrice)) / 4.0
elif mode == self.PRICE_AVERAGE:
return (float(candle.OpenPrice) + float(candle.HighPrice) + float(candle.LowPrice) + float(candle.ClosePrice)) / 4.0
return float(candle.ClosePrice)
def CreateClone(self):
return exp_trend_magic_strategy()