Exp BlauCMI
Обзор
Стратегия воспроизводит советник MetaTrader 5 Exp_BlauCMI на высокоуровневом API StockSharp. Индикатор Blau Candle Momentum Index (CMI) представляет собой отношение тройного сглаженного момента к его абсолютному значению. Расчёт выполняется по выбранным свечам. Покупки открываются, когда осциллятор разворачивается вверх после нисходящего движения, продажи — при развороте вниз. Все заявки отправляются только после закрытия свечей, что полностью повторяет логику оригинального эксперта.
Логика индикатора
- Через параметры
Momentum PriceиReference Priceзадаются два ценовых ряда. Сырой момент вычисляется как разница между текущим значением первой цены и отложенным значением второй цены. Глубина смещения задаётся параметромMomentum Depth. - Момент и его абсолютное значение проходят через три последовательных сглаживания (
First/Second/Third Smoothing). Для всех этапов используется один и тот же тип среднего: простое, экспоненциальное, сглаженное (RMA) или линейно-взвешенное. - Blau CMI равен
100 * smoothedMomentum / smoothedAbsMomentum. Индикатор начинает выдавать сигналы, когда третье сглаживание накопит достаточно баров. - Параметр
Signal Shiftопределяет, сколько закрытых свечей назад анализируется разворот (значение 1 соответствует оригинальному советнику и использует последнюю закрытую свечу).
Торговые правила
- Вход в лонг — допускается при включённом флаге
Allow Long Entry, если выполняется последовательностьValue[Signal Shift - 1] < Value[Signal Shift - 2]иValue[Signal Shift] > Value[Signal Shift - 1], то есть осциллятор развернулся вверх. При наличии короткой позиции она сначала закрывается, если разрешёнAllow Short Exit. - Вход в шорт — допускается при включённом
Allow Short Entry, когда индикатор разворачивается вниз (Value[Signal Shift - 1] > Value[Signal Shift - 2]иValue[Signal Shift] < Value[Signal Shift - 1]). При наличии длинной позиции она закрывается, если включёнAllow Long Exit. - Выход из лонга — при появлении условия для короткого входа позиция закрывается, если разрешён
Allow Long Exit. - Выход из шорта — при появлении условия для длинного входа позиция закрывается, если разрешён
Allow Short Exit. - Все сделки выполняются рыночными ордерами объёмом
Order Volume. К каждой позиции автоматически привязываются стоп-лосс и тейк-профит черезStartProtectionи действуют до закрытия позиции.
Параметры
Candle Type— тип свечей (таймфрейм и т.п.), используемый для расчёта и сигналов. По умолчанию 4-часовые свечи.Smoothing Method— общий метод усреднения для всех трёх этапов (Simple, Exponential, Smoothed, Linear Weighted).Momentum Depth— число баров между сравниваемыми ценами в исходном моменте.First/Second/Third Smoothing— длины трёх этапов сглаживания момента и его абсолютного значения.Signal Shift— количество закрытых свечей, на которых оценивается разворот (минимум 1).Momentum Price— применяемая цена для «передней» части момента.Reference Price— цена для отложенной части момента.Allow Long Entry,Allow Short Entry— разрешение на открытие позиций в соответствующем направлении.Allow Long Exit,Allow Short Exit— разрешение на закрытие позиций по противоположным сигналам.Stop-Loss Points,Take-Profit Points— расстояние до защитных ордеров в шагах цены (Security.PriceStep). Значение 0 отключает соответствующий ордер.Order Volume— объём рыночных заявок. Значение также присваивается свойствуStrategy.Volume.
Дополнительные замечания
- Поддерживаются четыре типа сглаживания, которые реализованы стандартными индикаторами StockSharp: SMA, EMA, RMA (Smoothed) и WMA.
- Константа цены Demark повторяет реализацию MT5: сначала усредняются экстремумы и закрытие свечи, затем вычисляются расстояния до high/low.
- Расчёты выполняются только по завершённым свечам, поэтому стратегия реагирует один раз на бар и соответствует логике
IsNewBarв оригинале. - Параметры
Stop-Loss PointsиTake-Profit Pointsтрактуются как количество шагов цены, что полностью совместимо с точечными значениями MQL5.
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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the Exp_BlauCMI MetaTrader strategy using the Blau Candle Momentum Index.
/// </summary>
public class ExpBlauCmiStrategy : Strategy
{
/// <summary>
/// Price sources supported by the strategy.
/// </summary>
public enum AppliedPrices
{
Close = 1,
Open,
High,
Low,
Median,
Typical,
Weighted,
Simple,
Quarter,
TrendFollow0,
TrendFollow1,
Demark
}
/// <summary>
/// Smoothing modes used in the multi-stage averages.
/// </summary>
public enum SmoothingMethods
{
Simple,
Exponential,
Smoothed,
LinearWeighted
}
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<SmoothingMethods> _smoothingMethod;
private readonly StrategyParam<int> _momentumLength;
private readonly StrategyParam<int> _firstSmoothingLength;
private readonly StrategyParam<int> _secondSmoothingLength;
private readonly StrategyParam<int> _thirdSmoothingLength;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<AppliedPrices> _priceForClose;
private readonly StrategyParam<AppliedPrices> _priceForOpen;
private readonly StrategyParam<bool> _allowLongEntry;
private readonly StrategyParam<bool> _allowShortEntry;
private readonly StrategyParam<bool> _allowLongExit;
private readonly StrategyParam<bool> _allowShortExit;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<decimal> _orderVolume;
private DecimalLengthIndicator _momentumStage1 = null!;
private DecimalLengthIndicator _momentumStage2 = null!;
private DecimalLengthIndicator _momentumStage3 = null!;
private DecimalLengthIndicator _absStage1 = null!;
private DecimalLengthIndicator _absStage2 = null!;
private DecimalLengthIndicator _absStage3 = null!;
private readonly List<decimal> _priceBuffer = new();
private readonly List<decimal> _indicatorHistory = new();
private decimal _priceStep;
private decimal _stopLossDistance;
private decimal _takeProfitDistance;
/// <summary>
/// Initializes a new instance of the <see cref="ExpBlauCmiStrategy"/> class.
/// </summary>
public ExpBlauCmiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for BlauCMI calculations", "General");
_smoothingMethod = Param(nameof(MomentumSmoothing), SmoothingMethods.Exponential)
.SetDisplay("Smoothing Method", "Averaging mode for the BlauCMI stages", "Indicator");
_momentumLength = Param(nameof(MomentumLength), 1)
.SetGreaterThanZero()
.SetDisplay("Momentum Depth", "Bars between compared prices", "Indicator");
_firstSmoothingLength = Param(nameof(FirstSmoothingLength), 20)
.SetGreaterThanZero()
.SetDisplay("First Smoothing", "Length of the first BlauCMI smoothing", "Indicator");
_secondSmoothingLength = Param(nameof(SecondSmoothingLength), 5)
.SetGreaterThanZero()
.SetDisplay("Second Smoothing", "Length of the second BlauCMI smoothing", "Indicator");
_thirdSmoothingLength = Param(nameof(ThirdSmoothingLength), 3)
.SetGreaterThanZero()
.SetDisplay("Third Smoothing", "Length of the third BlauCMI smoothing", "Indicator");
_signalBar = Param(nameof(SignalBar), 1)
.SetGreaterThanZero()
.SetDisplay("Signal Shift", "Number of closed bars used for signals", "Trading");
_priceForClose = Param(nameof(PriceForClose), AppliedPrices.Close)
.SetDisplay("Momentum Price", "Price type for the leading leg", "Indicator");
_priceForOpen = Param(nameof(PriceForOpen), AppliedPrices.Open)
.SetDisplay("Reference Price", "Price type compared against the delayed bar", "Indicator");
_allowLongEntry = Param(nameof(AllowLongEntry), true)
.SetDisplay("Allow Long Entry", "Enable opening long trades", "Trading");
_allowShortEntry = Param(nameof(AllowShortEntry), true)
.SetDisplay("Allow Short Entry", "Enable opening short trades", "Trading");
_allowLongExit = Param(nameof(AllowLongExit), true)
.SetDisplay("Allow Long Exit", "Enable closing long trades on opposite signals", "Trading");
_allowShortExit = Param(nameof(AllowShortExit), true)
.SetDisplay("Allow Short Exit", "Enable closing short trades on opposite signals", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetRange(0, 100000)
.SetDisplay("Stop-Loss Points", "Distance to stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetRange(0, 100000)
.SetDisplay("Take-Profit Points", "Distance to take-profit in price steps", "Risk");
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Contract volume used for entries", "Trading");
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Averaging method for momentum smoothing stages.
/// </summary>
public SmoothingMethods MomentumSmoothing
{
get => _smoothingMethod.Value;
set => _smoothingMethod.Value = value;
}
/// <summary>
/// Bars between the compared prices when computing raw momentum.
/// </summary>
public int MomentumLength
{
get => _momentumLength.Value;
set => _momentumLength.Value = value;
}
/// <summary>
/// Length of the first momentum smoothing stage.
/// </summary>
public int FirstSmoothingLength
{
get => _firstSmoothingLength.Value;
set => _firstSmoothingLength.Value = value;
}
/// <summary>
/// Length of the second momentum smoothing stage.
/// </summary>
public int SecondSmoothingLength
{
get => _secondSmoothingLength.Value;
set => _secondSmoothingLength.Value = value;
}
/// <summary>
/// Length of the third momentum smoothing stage.
/// </summary>
public int ThirdSmoothingLength
{
get => _thirdSmoothingLength.Value;
set => _thirdSmoothingLength.Value = value;
}
/// <summary>
/// Index of the closed bar that produces trading signals.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Applied price for the front leg of momentum.
/// </summary>
public AppliedPrices PriceForClose
{
get => _priceForClose.Value;
set => _priceForClose.Value = value;
}
/// <summary>
/// Applied price for the delayed leg of momentum.
/// </summary>
public AppliedPrices PriceForOpen
{
get => _priceForOpen.Value;
set => _priceForOpen.Value = value;
}
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool AllowLongEntry
{
get => _allowLongEntry.Value;
set => _allowLongEntry.Value = value;
}
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool AllowShortEntry
{
get => _allowShortEntry.Value;
set => _allowShortEntry.Value = value;
}
/// <summary>
/// Allow closing long positions when an opposite signal appears.
/// </summary>
public bool AllowLongExit
{
get => _allowLongExit.Value;
set => _allowLongExit.Value = value;
}
/// <summary>
/// Allow closing short positions when an opposite signal appears.
/// </summary>
public bool AllowShortExit
{
get => _allowShortExit.Value;
set => _allowShortExit.Value = value;
}
/// <summary>
/// Stop-loss distance measured in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance measured in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Order volume used for market entries.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_priceBuffer.Clear();
_indicatorHistory.Clear();
_priceStep = 0m;
_stopLossDistance = 0m;
_takeProfitDistance = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security?.PriceStep ?? 1m;
_stopLossDistance = StopLossPoints > 0 ? StopLossPoints * _priceStep : 0m;
_takeProfitDistance = TakeProfitPoints > 0 ? TakeProfitPoints * _priceStep : 0m;
StartProtection(
TakeProfitPoints > 0 ? new Unit(_takeProfitDistance, UnitTypes.Absolute) : null,
StopLossPoints > 0 ? new Unit(_stopLossDistance, UnitTypes.Absolute) : null);
Volume = Math.Abs(OrderVolume);
_momentumStage1 = CreateMovingAverage(MomentumSmoothing, FirstSmoothingLength);
_absStage1 = CreateMovingAverage(MomentumSmoothing, FirstSmoothingLength);
_momentumStage2 = CreateMovingAverage(MomentumSmoothing, SecondSmoothingLength);
_absStage2 = CreateMovingAverage(MomentumSmoothing, SecondSmoothingLength);
_momentumStage3 = CreateMovingAverage(MomentumSmoothing, ThirdSmoothingLength);
_absStage3 = CreateMovingAverage(MomentumSmoothing, ThirdSmoothingLength);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private DecimalLengthIndicator CreateMovingAverage(SmoothingMethods method, int length)
{
var normalized = Math.Max(1, length);
return method switch
{
SmoothingMethods.Simple => new SMA { Length = normalized },
SmoothingMethods.Smoothed => new SmoothedMovingAverage { Length = normalized },
SmoothingMethods.LinearWeighted => new WeightedMovingAverage { Length = normalized },
_ => new EMA { Length = normalized }
};
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var frontPrice = GetAppliedPrice(candle, PriceForClose);
var referencePrice = GetAppliedPrice(candle, PriceForOpen);
var momentumDepth = Math.Max(1, MomentumLength);
_priceBuffer.Add(referencePrice);
while (_priceBuffer.Count > momentumDepth)
try { _priceBuffer.RemoveAt(0); } catch { break; }
if (_priceBuffer.Count < momentumDepth)
return;
var delayedPrice = _priceBuffer[0];
var momentum = frontPrice - delayedPrice;
var absMomentum = Math.Abs(momentum);
var time = candle.ServerTime;
var stage1 = _momentumStage1.Process(new DecimalIndicatorValue(_momentumStage1, momentum, time) { IsFinal = true }).ToDecimal();
var absStage1 = _absStage1.Process(new DecimalIndicatorValue(_absStage1, absMomentum, time) { IsFinal = true }).ToDecimal();
var stage2 = _momentumStage2.Process(new DecimalIndicatorValue(_momentumStage2, stage1, time) { IsFinal = true }).ToDecimal();
var absStage2 = _absStage2.Process(new DecimalIndicatorValue(_absStage2, absStage1, time) { IsFinal = true }).ToDecimal();
var stage3Value = _momentumStage3.Process(new DecimalIndicatorValue(_momentumStage3, stage2, time) { IsFinal = true });
var absStage3Value = _absStage3.Process(new DecimalIndicatorValue(_absStage3, absStage2, time) { IsFinal = true });
if (!stage3Value.IsFormed || !absStage3Value.IsFormed)
return;
var denominator = absStage3Value.ToDecimal();
if (denominator == 0m)
return;
var cmi = 100m * stage3Value.ToDecimal() / denominator;
_indicatorHistory.Add(cmi);
var required = SignalBar + 3;
if (_indicatorHistory.Count > required)
_indicatorHistory.RemoveRange(0, _indicatorHistory.Count - required);
var index = _indicatorHistory.Count - 1 - SignalBar;
if (index < 2)
return;
var value0 = _indicatorHistory[index];
var value1 = _indicatorHistory[index - 1];
var value2 = _indicatorHistory[index - 2];
var buySignal = value1 < value2 && value0 > value1;
var sellSignal = value1 > value2 && value0 < value1;
if (Position > 0 && AllowLongExit && sellSignal)
{
SellMarket();
}
if (Position < 0 && AllowShortExit && buySignal)
{
BuyMarket();
}
if (Position != 0)
return;
if (buySignal && AllowLongEntry)
{
BuyMarket();
}
else if (sellSignal && AllowShortEntry)
{
SellMarket();
}
}
private static decimal GetAppliedPrice(ICandleMessage candle, AppliedPrices price)
{
return price switch
{
AppliedPrices.Close => candle.ClosePrice,
AppliedPrices.Open => candle.OpenPrice,
AppliedPrices.High => candle.HighPrice,
AppliedPrices.Low => candle.LowPrice,
AppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPrices.Typical => (candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 3m,
AppliedPrices.Weighted => (2m * candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
AppliedPrices.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
AppliedPrices.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
AppliedPrices.TrendFollow0 => candle.ClosePrice > candle.OpenPrice
? candle.HighPrice
: candle.ClosePrice < candle.OpenPrice
? candle.LowPrice
: candle.ClosePrice,
AppliedPrices.TrendFollow1 => candle.ClosePrice > candle.OpenPrice
? (candle.HighPrice + candle.ClosePrice) / 2m
: candle.ClosePrice < candle.OpenPrice
? (candle.LowPrice + candle.ClosePrice) / 2m
: candle.ClosePrice,
AppliedPrices.Demark =>
GetDemarkPrice(candle),
_ => candle.ClosePrice
};
}
private static decimal GetDemarkPrice(ICandleMessage candle)
{
var baseValue = candle.HighPrice + candle.LowPrice + candle.ClosePrice;
if (candle.ClosePrice < candle.OpenPrice)
baseValue = (baseValue + candle.LowPrice) / 2m;
else if (candle.ClosePrice > candle.OpenPrice)
baseValue = (baseValue + candle.HighPrice) / 2m;
else
baseValue = (baseValue + candle.ClosePrice) / 2m;
return ((baseValue - candle.LowPrice) + (baseValue - candle.HighPrice)) / 2m;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import (
ExponentialMovingAverage,
SimpleMovingAverage,
SmoothedMovingAverage,
WeightedMovingAverage,
)
from indicator_extensions import *
class exp_blau_cmi_strategy(Strategy):
"""Blau Candle Momentum Index: triple-smoothed momentum ratio with configurable price sources."""
def __init__(self):
super(exp_blau_cmi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe used for BlauCMI calculations", "General")
# 0=EMA, 1=SMA, 2=Smoothed, 3=LWMA
self._smoothing_method = self.Param("MomentumSmoothing", 0) \
.SetDisplay("Smoothing Method", "Averaging mode for BlauCMI stages", "Indicator")
self._momentum_length = self.Param("MomentumLength", 1) \
.SetGreaterThanZero() \
.SetDisplay("Momentum Depth", "Bars between compared prices", "Indicator")
self._first_smoothing_length = self.Param("FirstSmoothingLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("First Smoothing", "Length of first BlauCMI smoothing", "Indicator")
self._second_smoothing_length = self.Param("SecondSmoothingLength", 5) \
.SetGreaterThanZero() \
.SetDisplay("Second Smoothing", "Length of second BlauCMI smoothing", "Indicator")
self._third_smoothing_length = self.Param("ThirdSmoothingLength", 3) \
.SetGreaterThanZero() \
.SetDisplay("Third Smoothing", "Length of third BlauCMI smoothing", "Indicator")
self._signal_bar = self.Param("SignalBar", 1) \
.SetGreaterThanZero() \
.SetDisplay("Signal Shift", "Number of closed bars used for signals", "Trading")
# Price types: 1=Close, 2=Open, 3=High, 4=Low, 5=Median, 6=Typical, 7=Weighted
self._price_for_close = self.Param("PriceForClose", 1) \
.SetDisplay("Momentum Price", "Price type for the leading leg", "Indicator")
self._price_for_open = self.Param("PriceForOpen", 2) \
.SetDisplay("Reference Price", "Price type compared against the delayed bar", "Indicator")
self._allow_long_entry = self.Param("AllowLongEntry", True) \
.SetDisplay("Allow Long Entry", "Enable opening long trades", "Trading")
self._allow_short_entry = self.Param("AllowShortEntry", True) \
.SetDisplay("Allow Short Entry", "Enable opening short trades", "Trading")
self._allow_long_exit = self.Param("AllowLongExit", True) \
.SetDisplay("Allow Long Exit", "Enable closing long on opposite signals", "Trading")
self._allow_short_exit = self.Param("AllowShortExit", True) \
.SetDisplay("Allow Short Exit", "Enable closing short on opposite signals", "Trading")
self._stop_loss_points = self.Param("StopLossPoints", 1000) \
.SetDisplay("Stop-Loss Points", "Distance to stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 2000) \
.SetDisplay("Take-Profit Points", "Distance to take-profit in price steps", "Risk")
self._order_volume = self.Param("OrderVolume", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Order Volume", "Contract volume used for entries", "Trading")
self._price_buffer = []
self._indicator_history = []
self._price_step = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def SmoothingMethod(self):
return int(self._smoothing_method.Value)
@property
def MomentumLength(self):
return int(self._momentum_length.Value)
@property
def FirstSmoothingLength(self):
return int(self._first_smoothing_length.Value)
@property
def SecondSmoothingLength(self):
return int(self._second_smoothing_length.Value)
@property
def ThirdSmoothingLength(self):
return int(self._third_smoothing_length.Value)
@property
def SignalBar(self):
return int(self._signal_bar.Value)
@property
def PriceForClose(self):
return int(self._price_for_close.Value)
@property
def PriceForOpen(self):
return int(self._price_for_open.Value)
@property
def AllowLongEntry(self):
return self._allow_long_entry.Value
@property
def AllowShortEntry(self):
return self._allow_short_entry.Value
@property
def AllowLongExit(self):
return self._allow_long_exit.Value
@property
def AllowShortExit(self):
return self._allow_short_exit.Value
@property
def StopLossPoints(self):
return int(self._stop_loss_points.Value)
@property
def TakeProfitPoints(self):
return int(self._take_profit_points.Value)
@property
def OrderVolume(self):
return float(self._order_volume.Value)
def _create_ma(self, method, length):
n = max(1, length)
if method == 1:
ma = SimpleMovingAverage()
elif method == 2:
ma = SmoothedMovingAverage()
elif method == 3:
ma = WeightedMovingAverage()
else:
ma = ExponentialMovingAverage()
ma.Length = n
return ma
def _get_applied_price(self, candle, price_type):
c = float(candle.ClosePrice)
o = float(candle.OpenPrice)
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if price_type == 1:
return c
elif price_type == 2:
return o
elif price_type == 3:
return h
elif price_type == 4:
return lo
elif price_type == 5:
return (h + lo) / 2.0
elif price_type == 6:
return (c + h + lo) / 3.0
elif price_type == 7:
return (2.0 * c + h + lo) / 4.0
return c
def OnStarted2(self, time):
super(exp_blau_cmi_strategy, self).OnStarted2(time)
self._price_buffer = []
self._indicator_history = []
sec = self.Security
self._price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
sl_dist = self.StopLossPoints * self._price_step if self.StopLossPoints > 0 else 0.0
tp_dist = self.TakeProfitPoints * self._price_step if self.TakeProfitPoints > 0 else 0.0
tp = Unit(tp_dist, UnitTypes.Absolute) if tp_dist > 0 else None
sl = Unit(sl_dist, UnitTypes.Absolute) if sl_dist > 0 else None
if tp is not None and sl is not None:
self.StartProtection(takeProfit=tp, stopLoss=sl)
elif tp is not None:
self.StartProtection(takeProfit=tp)
elif sl is not None:
self.StartProtection(stopLoss=sl)
self._m_stage1 = self._create_ma(self.SmoothingMethod, self.FirstSmoothingLength)
self._a_stage1 = self._create_ma(self.SmoothingMethod, self.FirstSmoothingLength)
self._m_stage2 = self._create_ma(self.SmoothingMethod, self.SecondSmoothingLength)
self._a_stage2 = self._create_ma(self.SmoothingMethod, self.SecondSmoothingLength)
self._m_stage3 = self._create_ma(self.SmoothingMethod, self.ThirdSmoothingLength)
self._a_stage3 = self._create_ma(self.SmoothingMethod, self.ThirdSmoothingLength)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
front_price = self._get_applied_price(candle, self.PriceForClose)
ref_price = self._get_applied_price(candle, self.PriceForOpen)
momentum_depth = max(1, self.MomentumLength)
self._price_buffer.append(ref_price)
while len(self._price_buffer) > momentum_depth:
self._price_buffer.pop(0)
if len(self._price_buffer) < momentum_depth:
return
delayed_price = self._price_buffer[0]
momentum = front_price - delayed_price
abs_momentum = abs(momentum)
t = candle.ServerTime
def _proc(ind, val):
return float(process_float(ind, Decimal(val), t, True).Value)
s1 = _proc(self._m_stage1, momentum)
a1 = _proc(self._a_stage1, abs_momentum)
s2 = _proc(self._m_stage2, s1)
a2 = _proc(self._a_stage2, a1)
s3_val = process_float(self._m_stage3, Decimal(s2), t, True)
a3_val = process_float(self._a_stage3, Decimal(a2), t, True)
if not s3_val.IsFormed or not a3_val.IsFormed:
return
denominator = float(a3_val.Value)
if denominator == 0:
return
cmi = 100.0 * float(s3_val.Value) / denominator
self._indicator_history.append(cmi)
required = self.SignalBar + 3
if len(self._indicator_history) > required:
self._indicator_history = self._indicator_history[-required:]
index = len(self._indicator_history) - 1 - self.SignalBar
if index < 2:
return
v0 = self._indicator_history[index]
v1 = self._indicator_history[index - 1]
v2 = self._indicator_history[index - 2]
buy_signal = v1 < v2 and v0 > v1
sell_signal = v1 > v2 and v0 < v1
if self.Position > 0 and self.AllowLongExit and sell_signal:
self.SellMarket()
if self.Position < 0 and self.AllowShortExit and buy_signal:
self.BuyMarket()
if self.Position != 0:
return
if buy_signal and self.AllowLongEntry:
self.BuyMarket()
elif sell_signal and self.AllowShortEntry:
self.SellMarket()
def OnReseted(self):
super(exp_blau_cmi_strategy, self).OnReseted()
self._price_buffer = []
self._indicator_history = []
self._price_step = 0.0
def CreateClone(self):
return exp_blau_cmi_strategy()