Estrategia Exp TrendMagic
Descripción general
La estrategia Exp TrendMagic es una adaptación directa del asesor experto MetaTrader 5 "Exp_TrendMagic". Supervisa los cambios de color del indicador TrendMagic, que combina un índice de canal de productos básicos (CCI) con un canal de rango verdadero promedio (ATR). Cuando el indicador cambia de color, la estrategia cierra posiciones desde el lado opuesto y, opcionalmente, abre una nueva operación en la dirección de la nueva tendencia.
La conversión mantiene las opciones originales de administración de dinero, la compensación de señal configurable (Signal Bar) y los mismos permisos para ingresar o salir de operaciones largas y cortas.
Lógica de trading
Entradas de indicadores
CCI(Índice de canales de productos básicos) con período configurable y precio aplicado.ATR(Rango verdadero promedio) con período configurable.- El valor de TrendMagic se calcula como:
- Cuando CCI ≥ 0:
TrendMagic = Low - ATR, se sujeta para evitar disminuir la línea de soporte. - Cuando CCI < 0:
TrendMagic = High + ATR, se sujeta para evitar aumentar la línea de resistencia.
- Cuando CCI ≥ 0:
- El color de línea resultante es 0 para alcista (soporte por debajo del precio) y 1 para bajista (resistencia por encima del precio).
Evaluación de señal
- La estrategia almacena los colores del indicador en orden cronológico para emular el búfer MetaTrader y utiliza el desplazamiento
Signal Barpara leer la barra completada más reciente. - Si el color anterior (
Signal Bar + 1) es 0 y el color actual (Signal Bar) es 1, el algoritmo trata esto como un cambio alcista: cierra cualquier posición corta y, si se permite, abre una operación larga. - Si el color anterior es 1 y el color actual es 0, el algoritmo cierra cualquier posición larga abierta y, si se permite, ingresa una operación corta.
- Los indicadores de permiso comercial (
Allow Buy Entry,Allow Sell Entry,Allow Buy Exit,Allow Sell Exit) siguen la semántica exacta de la versión MT5.
- La estrategia almacena los colores del indicador en orden cronológico para emular el búfer MetaTrader y utiliza el desplazamiento
Gestión del dinero
Money Managementdetermina cuánto capital se debe asignar por operación. Los valores negativos se interpretan como un tamaño de lote fijo.Margin Modeselecciona la interpretación del valor de gestión del dinero:FreeMargin/Balance: invierte una parte del capital de la cuenta dividida por el precio.LossFreeMargin/LossBalance: arriesgar una parte del capital en relación con la distancia del stop-loss.Lot: trata el valor como un volumen fijo.
- Los volúmenes están alineados con
VolumeStep,MinVolumeyMaxVolumedel valor seleccionado.
Gestión de riesgos
- Cuando se realiza una nueva operación, la estrategia registra el precio de entrada y aplica las distancias originales de stop-loss y take-profit (expresadas en puntos, es decir, múltiplos de
PriceStep). - Al alcanzar el stop-loss o el take-profit se cierra inmediatamente la posición y se borra el precio de entrada guardado.
- Un acelerador impide reabrir una posición de la misma dirección antes de que se abra la siguiente vela, reproduciendo la guardia de "nivel de tiempo" MQL.
- Cuando se realiza una nueva operación, la estrategia registra el precio de entrada y aplica las distancias originales de stop-loss y take-profit (expresadas en puntos, es decir, múltiplos de
Parámetros
| Parámetro | Descripción |
|---|---|
Money Management |
Fracción de capital utilizada para el dimensionamiento (los valores negativos se convierten en volumen fijo). |
Margin Mode |
Modo de conversión para la gestión del dinero en volumen. |
Stop Loss |
Distancia de parada protectora en puntos de precio. |
Take Profit |
Objetivo de beneficio en puntos de precio. |
Deviation |
Reservado por compatibilidad con la entrada MT5 (no se usa directamente). |
Allow Buy Entry / Allow Sell Entry |
Alternar entradas largas/cortas. |
Allow Buy Exit / Allow Sell Exit |
Alternar el cierre de operaciones cortas/largas. |
Candle Type |
Principal marco temporal utilizado para los indicadores y la evaluación de señales. |
CCI Period / CCI Price |
CCI longitud y fuente de precio aplicada. |
ATR Period |
ATR longitud. |
Signal Bar |
Índice de la barra terminada utilizada para las señales (0 = actual, 1 = anterior, etc.). |
Notas
- La estrategia se basa únicamente en velas terminadas (
CandleStates.Finished) para imitar la implementación basada en ticks MT5. - Todos los valores de los indicadores y las variables de estado se restablecen cuando se reinicia la estrategia, lo que garantiza ejecuciones de optimización deterministas.
- El parámetro
Deviationse proporciona para compatibilidad total, aunque las órdenes de mercado StockSharp no utilizan un parámetro de deslizamiento explícito.
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()