Exp TrendMagic-Strategie
Überblick
Die Exp TrendMagic-Strategie ist eine direkte Portierung des MetaTrader 5 Expertenberaters „Exp_TrendMagic“. Es überwacht die Farbänderungen des TrendMagic-Indikators, der einen Commodity Channel Index (CCI) mit einem Average True Range (ATR)-Kanal kombiniert. Wenn der Indikator die Farbe wechselt, schließt die Strategie Positionen auf der Gegenseite und eröffnet optional einen neuen Trade in Richtung des neuen Trends.
Bei der Konvertierung bleiben die ursprünglichen Geldverwaltungsoptionen, der konfigurierbare Signalversatz (Signal Bar) und die gleichen Berechtigungsschalter für den Einstieg oder Ausstieg aus Long- und Short-Trades erhalten.
Handelslogik
Anzeigeeingänge
CCI(Commodity Channel Index) mit konfigurierbarem Zeitraum und angewendetem Preis.ATR(Average True Range) mit konfigurierbarem Zeitraum.- Der TrendMagic-Wert wird wie folgt berechnet:
- Wenn CCI ≥ 0:
TrendMagic = Low - ATR, wird geklemmt, um eine Verringerung der Unterstützungslinie zu vermeiden. - Wenn CCI < 0:
TrendMagic = High + ATR, wird geklemmt, um eine Erhöhung der Widerstandslinie zu vermeiden.
- Wenn CCI ≥ 0:
- Die resultierende Linienfarbe ist 0 für bullisch (Unterstützung unter dem Preis) und 1 für bärisch (Widerstand über dem Preis).
Signalauswertung
- Die Strategie speichert die Indikatorfarben in chronologischer Reihenfolge, um den MetaTrader-Puffer zu emulieren, und verwendet den
Signal Bar-Offset, um den zuletzt abgeschlossenen Balken zu lesen. - Wenn die vorherige Farbe (
Signal Bar + 1) 0 ist und die aktuelle Farbe (Signal Bar) 1 ist, behandelt der Algorithmus dies als bullischen Wechsel: Er schließt jede Short-Position und eröffnet, sofern zulässig, einen Long-Trade. - Wenn die vorherige Farbe 1 und die aktuelle Farbe 0 ist, schließt der Algorithmus alle offenen Long-Positionen und geht, sofern zulässig, einen Short-Trade ein.
- Die Handelserlaubnis-Flags (
Allow Buy Entry,Allow Sell Entry,Allow Buy Exit,Allow Sell Exit) folgen der genauen Semantik der MT5-Version.
- Die Strategie speichert die Indikatorfarben in chronologischer Reihenfolge, um den MetaTrader-Puffer zu emulieren, und verwendet den
Geldmanagement
Money Managementbestimmt, wie viel Kapital pro Trade zugewiesen werden soll. Negative Werte werden als feste Losgröße interpretiert.Margin Modewählt die Interpretation des Geldverwaltungswerts aus:FreeMargin/Balance: Investieren Sie einen Anteil des Kontokapitals geteilt durch den Preis.LossFreeMargin/LossBalance: Risiko eines Kapitalanteils im Verhältnis zur Stop-Loss-Distanz.Lot: Behandeln Sie den Wert als festes Volumen.
- Die Volumina werden mit
VolumeStep,MinVolumeundMaxVolumedes ausgewählten Wertpapiers abgeglichen.
Risikomanagement
- Wenn ein neuer Trade platziert wird, zeichnet die Strategie den Einstiegspreis auf und erzwingt die ursprünglichen Stop-Loss- und Take-Profit-Abstände (ausgedrückt in Punkten, d. h. Vielfache von
PriceStep). - Durch Erreichen des Stop-Loss oder Take-Profit wird die Position sofort geschlossen und der gespeicherte Einstiegspreis gelöscht.
- Eine Drossel verhindert das erneute Öffnen einer Position in die gleiche Richtung, bevor sich die nächste Kerze öffnet, und reproduziert so den MQL „Zeitniveau“-Schutz.
- Wenn ein neuer Trade platziert wird, zeichnet die Strategie den Einstiegspreis auf und erzwingt die ursprünglichen Stop-Loss- und Take-Profit-Abstände (ausgedrückt in Punkten, d. h. Vielfache von
Parameter
| Parameter | Beschreibung |
|---|---|
Money Management |
Anteil des Kapitals, der für die Größenbestimmung verwendet wird (negative Werte werden zu einem festen Volumen). |
Margin Mode |
Umrechnungsmodus für die Geldverwaltung in Volumen. |
Stop Loss |
Schutzstoppabstand in Preispunkten. |
Take Profit |
Gewinnziel in Preispunkten. |
Deviation |
Reserviert für die Kompatibilität mit dem MT5-Eingang (nicht direkt verwendet). |
Allow Buy Entry / Allow Sell Entry |
Zwischen langen und kurzen Einträgen umschalten. |
Allow Buy Exit / Allow Sell Exit |
Schalten Sie das Schließen von Short-/Long-Trades um. |
Candle Type |
Hauptzeitrahmen für Indikatoren und Signalauswertung. |
CCI Period / CCI Price |
CCI Länge und angewendete Preisquelle. |
ATR Period |
ATR Länge. |
Signal Bar |
Index des fertigen Balkens, der für Signale verwendet wird (0 = aktuell, 1 = vorhergehend usw.). |
Notizen
- Die Strategie basiert nur auf fertigen Kerzen (
CandleStates.Finished), um die tickbasierte MT5-Implementierung nachzuahmen. - Beim Neustart der Strategie werden alle Indikatorwerte und Zustandsvariablen zurückgesetzt, um deterministische Optimierungsläufe sicherzustellen.
- Der Parameter
Deviationwird aus Gründen der vollständigen Kompatibilität bereitgestellt, auch wenn Marktaufträge von StockSharp keinen expliziten Slippage-Parameter verwenden.
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()