Стратегия Exp TrendMagic
Общее описание
Exp TrendMagic — это точная конверсия эксперта MetaTrader 5 «Exp_TrendMagic». Стратегия отслеживает смену цвета индикатора TrendMagic, который объединяет CCI (Commodity Channel Index) и ATR (Average True Range). При изменении цвета закрываются позиции противоположного направления и, при разрешении, открывается сделка по новому тренду.
Перенос сохраняет все параметры управления капиталом, сдвиг сигнальной свечи (Signal Bar) и набор флагов, регулирующих открытия и закрытия длинных/коротких позиций.
Логика работы
Индикаторы
CCIс настраиваемым периодом и типом цены.ATRс настраиваемым периодом.- Расчёт TrendMagic:
- При CCI ≥ 0:
TrendMagic = Low - ATRс ограничением, чтобы линия поддержки не снижалась. - При CCI < 0:
TrendMagic = High + ATRс ограничением, чтобы линия сопротивления не росла.
- При CCI ≥ 0:
- Цвет линии: 0 — бычий режим (поддержка ниже цены), 1 — медвежий режим (сопротивление выше цены).
Определение сигналов
- Цвета индикатора складываются в буфер, воспроизводящий работу MT5, а чтение значений сдвигается на
Signal Bar. - Если цвет на предыдущей свече (
Signal Bar + 1) равен 0, а на текущей (Signal Bar) равен 1, стратегия закрывает короткие позиции и, при разрешении, открывает длинную. - Если предыдущий цвет 1, а текущий 0, закрываются длинные позиции и, при необходимости, открывается короткая.
- Флаги
Allow Buy/Sell EntryиAllow Buy/Sell Exitполностью повторяют семантику оригинального эксперта.
- Цвета индикатора складываются в буфер, воспроизводящий работу MT5, а чтение значений сдвигается на
Управление капиталом
Money Managementзадаёт долю капитала, используемую на сделку. Отрицательное значение трактуется как фиксированный объём.Margin Modeопределяет способ перевода значения в объём заявки:FreeMargin/Balance— доля свободных средств или баланса делится на цену.LossFreeMargin/LossBalance— доля капитала, допускаемая к риску, делится на величину стоп-лосса.Lot— фиксированный объём.
- Результирующий объём приводится к шагу, минимуму и максимуму инструмента.
Риск-менеджмент
- После открытия позиции запоминается цена входа и применяется стоп-лосс/тейк-профит в пунктах (кратных
PriceStep). - При достижении уровней позиция закрывается немедленно, а сохранённая цена сбрасывается.
- Встроенная задержка запрещает повторное открытие сделки того же направления до следующей свечи, повторяя MQL-функцию
TradeTimeLevel.
- После открытия позиции запоминается цена входа и применяется стоп-лосс/тейк-профит в пунктах (кратных
Параметры
| Параметр | Описание |
|---|---|
Money Management |
Доля капитала или фиксированный объём при отрицательном значении. |
Margin Mode |
Метод пересчёта доли капитала в объём. |
Stop Loss |
Дистанция стоп-лосса в пунктах. |
Take Profit |
Дистанция тейк-профита в пунктах. |
Deviation |
Параметр для совместимости с MT5 (в StockSharp не используется напрямую). |
Allow Buy/Sell Entry |
Разрешение на открытие длинных/коротких позиций. |
Allow Buy/Sell Exit |
Разрешение на закрытие коротких/длинных позиций. |
Candle Type |
Тип свечей, используемых для расчётов. |
CCI Period / CCI Price |
Период и тип цены для CCI. |
ATR Period |
Период ATR. |
Signal Bar |
Номер завершённой свечи, по которой считывается сигнал. |
Дополнительные замечания
- Стратегия работает только с закрытыми свечами (
CandleStates.Finished), как и оригинальная версия на MT5. - При перезапуске все индикаторы и внутренние переменные сбрасываются, что обеспечивает повторяемость оптимизации.
- Параметр
Deviationоставлен для соответствия интерфейсу MT5, хотя в StockSharp он не влияет на размещение заявок.
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()