Exp TrendMagic 策略
概述
Exp TrendMagic 策略是 MetaTrader 5 智能交易系统 “Exp_TrendMagic” 的等价移植版本。策略持续监控 TrendMagic 指标的颜色切换,该指标由 CCI(商品通道指数)与 ATR(平均真实波幅)通道组合而成。当颜色发生转变时,系统会平掉相反方向的持仓,并在允许的情况下按新趋势方向开仓。
移植版本完全保留了原始 EA 的资金管理选项、信号偏移 (Signal Bar) 设置以及多种多空开/平仓权限开关。
交易逻辑
指标输入
CCI:可配置周期与价格源。ATR:可配置周期,用于估算波动幅度。- TrendMagic 计算方式:
- 当 CCI ≥ 0:
TrendMagic = Low - ATR,并保持支撑线不会向下折返。 - 当 CCI < 0:
TrendMagic = High + ATR,并保持阻力线不会向上抬升。
- 当 CCI ≥ 0:
- 线段颜色编码:0 代表看涨(支撑位于价格下方),1 代表看跌(阻力位于价格上方)。
信号判定
- 策略将颜色序列存入缓冲区,以复现 MetaTrader 指标缓存的行为,并通过
Signal Bar偏移读取最近完成的 K 线信号。 - 若上一根颜色(
Signal Bar + 1)为 0,而当前颜色(Signal Bar)为 1,则视为趋势由空转多:先平掉空头,再按权限开多单。 - 若上一根颜色为 1,当前颜色为 0,则视为趋势由多转空:先平掉多头,再按权限开空单。
Allow Buy/Sell Entry与Allow Buy/Sell Exit四个开关严格对应原 EA 的开仓/平仓许可。
- 策略将颜色序列存入缓冲区,以复现 MetaTrader 指标缓存的行为,并通过
资金管理
Money Management控制每次交易使用的资金比例。若取负值,则被视作固定手数。Margin Mode指定资金管理值的解释方式:FreeMargin/Balance:按账户权益的百分比下单,再除以价格得到手数。LossFreeMargin/LossBalance:按账户权益的百分比衡量可承受亏损,再除以止损距离得到手数。Lot:将参数直接视为固定下单量。
- 计算得到的手数会自动贴合交易品种的
VolumeStep、MinVolume与MaxVolume。
风控机制
- 新仓位建立后,记录开仓价并按照点数(
PriceStep的倍数)执行与 MT5 相同的止损、止盈逻辑。 - 触发止损或止盈时立即平仓,并清空记录的入场价格。
- 内置的时间节流装置禁止在下一根 K 线到来之前重复开同方向的仓位,从而复刻 MT5 中的 “时间限制” 检查。
- 新仓位建立后,记录开仓价并按照点数(
参数说明
| 参数 | 说明 |
|---|---|
Money Management |
交易资金占比(负值表示固定手数)。 |
Margin Mode |
资金管理的计算方式。 |
Stop Loss |
止损距离(点数)。 |
Take Profit |
止盈距离(点数)。 |
Deviation |
与 MT5 输入保持一致的滑点占位参数。 |
Allow Buy/Sell Entry |
控制多/空开仓许可。 |
Allow Buy/Sell Exit |
控制平空/平多许可。 |
Candle Type |
指标与信号所用的主时间框。 |
CCI Period / CCI Price |
CCI 周期与应用价格。 |
ATR Period |
ATR 周期。 |
Signal Bar |
读取信号的已完成 K 线索引。 |
其他说明
- 策略只处理已经完成的 K 线(
CandleStates.Finished),以保持与原 MT5 按 tick 驱动的逻辑一致。 - 每次启动或回测重置时,所有指标与内部状态都会清空,便于获得确定性的优化结果。
Deviation参数在 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()