Стратегия Color JFATL Digit TM
Общая информация
Color JFATL Digit TM — это перенос оригинального эксперта MetaTrader 5. Стратегия использует фильтр FATL (Fast Adaptive Trend Line), сглаженный индикатором Jurik, и реагирует на изменения «цвета» наклона линии. Каждый завершённый бар классифицируется как бычий (цвет = 2), медвежий (цвет = 0) или нейтральный (цвет = 1). Переходы между цветами запускают открытие и закрытие сделок с учётом торговых сессий и защитных стопов.
Логика работы
Воссоздание индикатора
- FATL вычисляется свёрткой выбранной цены с таблицей из 39 коэффициентов, как в оригинальном индикаторе.
- Результат сглаживается
JurikMovingAverage. Если в библиотеке StockSharp есть свойствоPhase, оно задаётся через отражение, чтобы повторить параметры MT5. - Значение округляется к шагу цены умноженному на
10^DigitRounding, что соответствует параметруDigitв MQL5. - Разница между текущим и предыдущим округлённым значением определяет цвет: рост — 2, падение — 0, отсутствие изменения — наследование предыдущего цвета (по умолчанию 1).
Генерация сигналов
- В кольцевом буфере хранятся последние цвета. Параметр
SignalBarзадаёт, сколько закрытых баров пропустить (по умолчанию 1 — предыдущий бар). - Открытие лонга: предыдущий цвет равен 2, текущий цвет < 2.
- Открытие шорта: предыдущий цвет равен 0, текущий цвет > 0.
- Выход из лонга: предыдущий цвет становится 0.
- Выход из шорта: предыдущий цвет становится 2.
- Если позиция уже открыта, новые входы игнорируются, что соответствует «однопозиционному» поведению оригинального эксперта.
- В кольцевом буфере хранятся последние цвета. Параметр
Сессии и защита позиции
- Фильтр
EnableTimeFilterполностью повторяет часовую логику MT5, включая ночные сессии (начало позже окончания). - При выходе за пределы торгового окна все позиции закрываются немедленно, как в исходной версии.
- Стоп-лосс и тейк-профит задаются в пунктах, переводятся в цену через шаг цены и передаются в
StartProtection.
- Фильтр
Параметры
OrderVolume— объём сделки.EnableTimeFilter,StartHour,StartMinute,EndHour,EndMinute— настройки торговой сессии.StopLossPoints,TakeProfitPoints— расстояния защитных приказов в пунктах (0 отключает соответствующий уровень).BuyOpenEnabled,SellOpenEnabled,BuyCloseEnabled,SellCloseEnabled— активация сигналов на вход/выход для лонгов и шортов.SignalCandleType— таймфрейм свечей для вычисления индикатора (по умолчанию H4).JmaLength,JmaPhase— параметры сглаживания Jurik (фаза применяется, если поддерживается библиотекой).AppliedPriceMode— тип цены, полностью совпадающий с набором MT5 (close, open, median, trend-follow, Demark и др.).DigitRounding— множитель для округления, аналог параметраDigitв MQL5.SignalBar— количество закрытых баров, используемых при анализе изменения цветов.
Особенности реализации
- Используются высокоуровневые подписки (
SubscribeCandles) и методыBuyMarket/SellMarket, как предписывает инструкция по конверсии. - Настройка фазы Jurik выполняется через отражение; если свойство отсутствует, применяется стандартное поведение.
- Для корректного округления необходим
Security.PriceStep. При его отсутствии значения не квантируются. - Python-версия не создавалась согласно требованиям.
Как использовать
- Подключите стратегию к инструменту и источнику данных, предоставляющему свечи
SignalCandleType. - Настройте тип цены, параметры Jurik, торговый интервал и защитные расстояния.
- Запустите стратегию — она будет управлять одной позицией, открывая и закрывая сделки по описанным цветовым сигналам и применяя стоп-лосс/тейк-профит.
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 System.Reflection;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on the Color JFATL Digit indicator with optional trading window and money management controls.
/// Detects color transitions produced by the smoothed FATL curve and opens or closes positions accordingly.
/// </summary>
public class ColorJfatlDigitTmStrategy : Strategy
{
private static readonly decimal[] FatlCoefficients =
[
0.4360409450m,
0.3658689069m,
0.2460452079m,
0.1104506886m,
-0.0054034585m,
-0.0760367731m,
-0.0933058722m,
-0.0670110374m,
-0.0190795053m,
0.0259609206m,
0.0502044896m,
0.0477818607m,
0.0249252327m,
-0.0047706151m,
-0.0272432537m,
-0.0338917071m,
-0.0244141482m,
-0.0055774838m,
0.0128149838m,
0.0226522218m,
0.0208778257m,
0.0100299086m,
-0.0036771622m,
-0.0136744850m,
-0.0160483392m,
-0.0108597376m,
-0.0016060704m,
0.0069480557m,
0.0110573605m,
0.0095711419m,
0.0040444064m,
-0.0023824623m,
-0.0067093714m,
-0.0072003400m,
-0.0047717710m,
0.0005541115m,
0.0007860160m,
0.0130129076m,
0.0040364019m,
];
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<bool> _enableTimeFilter;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _startMinute;
private readonly StrategyParam<int> _endHour;
private readonly StrategyParam<int> _endMinute;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<bool> _buyOpen;
private readonly StrategyParam<bool> _sellOpen;
private readonly StrategyParam<bool> _buyClose;
private readonly StrategyParam<bool> _sellClose;
private readonly StrategyParam<DataType> _signalCandleType;
private readonly StrategyParam<int> _jmaLength;
private readonly StrategyParam<int> _jmaPhase;
private readonly StrategyParam<AppliedPrices> _appliedPrice;
private readonly StrategyParam<int> _digitRounding;
private readonly StrategyParam<int> _signalBar;
private ExponentialMovingAverage _jma;
private readonly List<decimal> _priceBuffer = new();
private readonly List<int> _colorHistory = new();
private decimal? _previousLine;
private DateTimeOffset _nextBuyTime = DateTimeOffset.MinValue;
private DateTimeOffset _nextSellTime = DateTimeOffset.MinValue;
/// <summary>
/// Trading volume per order.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Enable or disable the session time filter.
/// </summary>
public bool EnableTimeFilter
{
get => _enableTimeFilter.Value;
set => _enableTimeFilter.Value = value;
}
/// <summary>
/// Session start hour.
/// </summary>
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
/// <summary>
/// Session start minute.
/// </summary>
public int StartMinute
{
get => _startMinute.Value;
set => _startMinute.Value = value;
}
/// <summary>
/// Session end hour.
/// </summary>
public int EndHour
{
get => _endHour.Value;
set => _endHour.Value = value;
}
/// <summary>
/// Session end minute.
/// </summary>
public int EndMinute
{
get => _endMinute.Value;
set => _endMinute.Value = value;
}
/// <summary>
/// Stop loss distance expressed in points.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in points.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Allow long entries.
/// </summary>
public bool BuyOpenEnabled
{
get => _buyOpen.Value;
set => _buyOpen.Value = value;
}
/// <summary>
/// Allow short entries.
/// </summary>
public bool SellOpenEnabled
{
get => _sellOpen.Value;
set => _sellOpen.Value = value;
}
/// <summary>
/// Allow long exits.
/// </summary>
public bool BuyCloseEnabled
{
get => _buyClose.Value;
set => _buyClose.Value = value;
}
/// <summary>
/// Allow short exits.
/// </summary>
public bool SellCloseEnabled
{
get => _sellClose.Value;
set => _sellClose.Value = value;
}
/// <summary>
/// Candle type used for signal calculation.
/// </summary>
public DataType SignalCandleType
{
get => _signalCandleType.Value;
set => _signalCandleType.Value = value;
}
/// <summary>
/// Jurik moving average length.
/// </summary>
public int JmaLength
{
get => _jmaLength.Value;
set => _jmaLength.Value = value;
}
/// <summary>
/// Jurik moving average phase parameter.
/// </summary>
public int JmaPhase
{
get => _jmaPhase.Value;
set => _jmaPhase.Value = value;
}
/// <summary>
/// Applied price mode.
/// </summary>
public AppliedPrices AppliedPriceMode
{
get => _appliedPrice.Value;
set => _appliedPrice.Value = value;
}
/// <summary>
/// Precision multiplier used for rounding indicator values.
/// </summary>
public int DigitRounding
{
get => _digitRounding.Value;
set => _digitRounding.Value = value;
}
/// <summary>
/// Number of bars to shift when evaluating color transitions.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ColorJfatlDigitTmStrategy"/> class.
/// </summary>
public ColorJfatlDigitTmStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Trade volume per position", "Risk")
.SetOptimize(0.5m, 5m, 0.5m);
_enableTimeFilter = Param(nameof(EnableTimeFilter), false)
.SetDisplay("Enable Time Filter", "Restrict trading to session hours", "Session");
_startHour = Param(nameof(StartHour), 0)
.SetDisplay("Start Hour", "Session start hour", "Session");
_startMinute = Param(nameof(StartMinute), 0)
.SetDisplay("Start Minute", "Session start minute", "Session");
_endHour = Param(nameof(EndHour), 23)
.SetDisplay("End Hour", "Session end hour", "Session");
_endMinute = Param(nameof(EndMinute), 59)
.SetDisplay("End Minute", "Session end minute", "Session");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetNotNegative()
.SetDisplay("Stop Loss (points)", "Protective stop in points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetNotNegative()
.SetDisplay("Take Profit (points)", "Take profit in points", "Risk");
_buyOpen = Param(nameof(BuyOpenEnabled), true)
.SetDisplay("Enable Buy Open", "Allow opening long positions", "Signals");
_sellOpen = Param(nameof(SellOpenEnabled), true)
.SetDisplay("Enable Sell Open", "Allow opening short positions", "Signals");
_buyClose = Param(nameof(BuyCloseEnabled), true)
.SetDisplay("Enable Buy Close", "Allow closing long positions", "Signals");
_sellClose = Param(nameof(SellCloseEnabled), true)
.SetDisplay("Enable Sell Close", "Allow closing short positions", "Signals");
_signalCandleType = Param(nameof(SignalCandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Signal Candle Type", "Timeframe used for indicator", "Indicator");
_jmaLength = Param(nameof(JmaLength), 14)
.SetGreaterThanZero()
.SetDisplay("JMA Length", "Period for Jurik moving average", "Indicator")
.SetOptimize(3, 30, 1);
_jmaPhase = Param(nameof(JmaPhase), -100)
.SetDisplay("JMA Phase", "Phase shift for Jurik moving average", "Indicator");
_appliedPrice = Param(nameof(AppliedPriceMode), AppliedPrices.Close)
.SetDisplay("Applied Price", "Price source for calculations", "Indicator");
_digitRounding = Param(nameof(DigitRounding), 0)
.SetNotNegative()
.SetDisplay("Digit Rounding", "Rounding precision multiplier", "Indicator");
_signalBar = Param(nameof(SignalBar), 1)
.SetGreaterThanZero()
.SetDisplay("Signal Bar", "Shift for analyzing colors", "Signals");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, SignalCandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_jma = null;
_priceBuffer.Clear();
_colorHistory.Clear();
_previousLine = null;
_nextBuyTime = DateTimeOffset.MinValue;
_nextSellTime = DateTimeOffset.MinValue;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = OrderVolume;
ConfigureJma();
var subscription = SubscribeCandles(SignalCandleType);
subscription.Bind(ProcessCandle).Start();
var priceStep = Security?.PriceStep ?? 0m;
Unit takeProfitUnit = null;
Unit stopLossUnit = null;
if (TakeProfitPoints > 0 && priceStep > 0m)
takeProfitUnit = new Unit(TakeProfitPoints * priceStep, UnitTypes.Absolute);
if (StopLossPoints > 0 && priceStep > 0m)
stopLossUnit = new Unit(StopLossPoints * priceStep, UnitTypes.Absolute);
StartProtection(takeProfit: takeProfitUnit, stopLoss: stopLossUnit);
}
private void ConfigureJma()
{
_jma = new ExponentialMovingAverage { Length = JmaLength };
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = GetAppliedPrice(candle);
_priceBuffer.Add(price);
if (_priceBuffer.Count > FatlCoefficients.Length)
_priceBuffer.RemoveAt(0);
if (_priceBuffer.Count < FatlCoefficients.Length)
return;
var fatl = 0m;
for (var i = 0; i < FatlCoefficients.Length; i++)
{
var value = _priceBuffer[_priceBuffer.Count - 1 - i];
fatl += FatlCoefficients[i] * value;
}
var jmaValue = _jma.Process(new DecimalIndicatorValue(_jma, fatl, candle.OpenTime) { IsFinal = true });
if (!_jma.IsFormed)
return;
var roundedLine = RoundToStep(jmaValue.ToDecimal(), GetRoundingStep());
var color = 1;
if (_previousLine.HasValue)
{
var diff = roundedLine - _previousLine.Value;
if (diff > 0m)
color = 2;
else if (diff < 0m)
color = 0;
else if (_colorHistory.Count > 0)
color = _colorHistory[0];
}
_previousLine = roundedLine;
_colorHistory.Insert(0, color);
if (_colorHistory.Count > 100)
_colorHistory.RemoveAt(_colorHistory.Count - 1);
if (_colorHistory.Count <= SignalBar)
return;
var currentColor = _colorHistory[SignalBar - 1];
var previousColor = _colorHistory[SignalBar];
var now = candle.CloseTime;
var inSession = !EnableTimeFilter || IsWithinTradingWindow(now);
if (EnableTimeFilter && !inSession)
{
ClosePositions();
return;
}
// No bound indicators, always allow trading.
var buyOpenSignal = BuyOpenEnabled && currentColor == 2 && previousColor != 2;
var sellCloseSignal = SellCloseEnabled && currentColor == 2;
var sellOpenSignal = SellOpenEnabled && currentColor == 0 && previousColor != 0;
var buyCloseSignal = BuyCloseEnabled && currentColor == 0;
if (buyCloseSignal && Position > 0)
SellMarket();
if (sellCloseSignal && Position < 0)
BuyMarket();
if (buyOpenSignal && Position == 0 && now >= _nextBuyTime)
{
BuyMarket();
_nextBuyTime = now;
}
if (sellOpenSignal && Position == 0 && now >= _nextSellTime)
{
SellMarket();
_nextSellTime = now;
}
}
private void ClosePositions()
{
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
}
private decimal GetAppliedPrice(ICandleMessage candle)
{
return AppliedPriceMode 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.HighPrice + candle.LowPrice + candle.ClosePrice) / 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 => CalculateDemarkPrice(candle),
_ => candle.ClosePrice,
};
}
private static decimal CalculateDemarkPrice(ICandleMessage candle)
{
var res = candle.HighPrice + candle.LowPrice + candle.ClosePrice;
if (candle.ClosePrice < candle.OpenPrice)
res = (res + candle.LowPrice) / 2m;
else if (candle.ClosePrice > candle.OpenPrice)
res = (res + candle.HighPrice) / 2m;
else
res = (res + candle.ClosePrice) / 2m;
return ((res - candle.LowPrice) + (res - candle.HighPrice)) / 2m;
}
private decimal GetRoundingStep()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return 0m;
var multiplier = (decimal)Math.Pow(10, DigitRounding);
return step * multiplier;
}
private static decimal RoundToStep(decimal value, decimal step)
{
if (step <= 0m)
return value;
return Math.Round(value / step, MidpointRounding.AwayFromZero) * step;
}
private bool IsWithinTradingWindow(DateTimeOffset time)
{
var hour = time.Hour;
var minute = time.Minute;
if (StartHour < EndHour)
{
if (hour == StartHour && minute >= StartMinute)
return true;
if (hour > StartHour && hour < EndHour)
return true;
if (hour > StartHour && hour == EndHour && minute < EndMinute)
return true;
}
else if (StartHour == EndHour)
{
if (hour == StartHour && minute >= StartMinute && minute < EndMinute)
return true;
}
else
{
if (hour > StartHour || (hour == StartHour && minute >= StartMinute))
return true;
if (hour < EndHour)
return true;
if (hour == EndHour && minute < EndMinute)
return true;
}
return false;
}
/// <summary>
/// Applied price options replicated from the original MQL implementation.
/// </summary>
public enum AppliedPrices
{
Close = 1,
Open,
High,
Low,
Median,
Typical,
Weighted,
Simple,
Quarter,
TrendFollow0,
TrendFollow1,
Demark,
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan, Decimal, DateTime
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class color_jfatl_digit_tm_strategy(Strategy):
"""Color JFATL Digit indicator strategy with trading window and SL/TP."""
FATL_COEFFICIENTS = [
0.4360409450, 0.3658689069, 0.2460452079, 0.1104506886,
-0.0054034585, -0.0760367731, -0.0933058722, -0.0670110374,
-0.0190795053, 0.0259609206, 0.0502044896, 0.0477818607,
0.0249252327, -0.0047706151, -0.0272432537, -0.0338917071,
-0.0244141482, -0.0055774838, 0.0128149838, 0.0226522218,
0.0208778257, 0.0100299086, -0.0036771622, -0.0136744850,
-0.0160483392, -0.0108597376, -0.0016060704, 0.0069480557,
0.0110573605, 0.0095711419, 0.0040444064, -0.0023824623,
-0.0067093714, -0.0072003400, -0.0047717710, 0.0005541115,
0.0007860160, 0.0130129076, 0.0040364019,
]
def __init__(self):
super(color_jfatl_digit_tm_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", Decimal(1)) \
.SetGreaterThanZero() \
.SetDisplay("Order Volume", "Trade volume per position", "Risk")
self._enable_time_filter = self.Param("EnableTimeFilter", False) \
.SetDisplay("Enable Time Filter", "Restrict trading to session hours", "Session")
self._start_hour = self.Param("StartHour", 0) \
.SetDisplay("Start Hour", "Session start hour", "Session")
self._start_minute = self.Param("StartMinute", 0) \
.SetDisplay("Start Minute", "Session start minute", "Session")
self._end_hour = self.Param("EndHour", 23) \
.SetDisplay("End Hour", "Session end hour", "Session")
self._end_minute = self.Param("EndMinute", 59) \
.SetDisplay("End Minute", "Session end minute", "Session")
self._stop_loss_points = self.Param("StopLossPoints", 1000) \
.SetDisplay("Stop Loss (points)", "Protective stop in points", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 2000) \
.SetDisplay("Take Profit (points)", "Take profit in points", "Risk")
self._buy_open = self.Param("BuyOpenEnabled", True) \
.SetDisplay("Enable Buy Open", "Allow opening long positions", "Signals")
self._sell_open = self.Param("SellOpenEnabled", True) \
.SetDisplay("Enable Sell Open", "Allow opening short positions", "Signals")
self._buy_close = self.Param("BuyCloseEnabled", True) \
.SetDisplay("Enable Buy Close", "Allow closing long positions", "Signals")
self._sell_close = self.Param("SellCloseEnabled", True) \
.SetDisplay("Enable Sell Close", "Allow closing short positions", "Signals")
self._candle_type = self.Param("SignalCandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Signal Candle Type", "Timeframe used for indicator", "Indicator")
self._jma_length = self.Param("JmaLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("JMA Length", "Period for Jurik moving average", "Indicator")
self._jma_phase = self.Param("JmaPhase", -100) \
.SetDisplay("JMA Phase", "Phase shift for Jurik moving average", "Indicator")
self._digit_rounding = self.Param("DigitRounding", 0) \
.SetDisplay("Digit Rounding", "Rounding precision multiplier", "Indicator")
self._signal_bar = self.Param("SignalBar", 1) \
.SetGreaterThanZero() \
.SetDisplay("Signal Bar", "Shift for analyzing colors", "Signals")
self._price_buffer = []
self._color_history = []
self._previous_line = None
self._ema_value = None
self._ema_count = 0
self._next_buy_time = DateTime.MinValue
self._next_sell_time = DateTime.MinValue
@property
def OrderVolume(self):
return self._order_volume.Value
@property
def EnableTimeFilter(self):
return self._enable_time_filter.Value
@property
def StartHour(self):
return self._start_hour.Value
@property
def StartMinute(self):
return self._start_minute.Value
@property
def EndHour(self):
return self._end_hour.Value
@property
def EndMinute(self):
return self._end_minute.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def BuyOpenEnabled(self):
return self._buy_open.Value
@property
def SellOpenEnabled(self):
return self._sell_open.Value
@property
def BuyCloseEnabled(self):
return self._buy_close.Value
@property
def SellCloseEnabled(self):
return self._sell_close.Value
@property
def CandleType(self):
return self._candle_type.Value
@property
def JmaLength(self):
return self._jma_length.Value
@property
def JmaPhase(self):
return self._jma_phase.Value
@property
def DigitRounding(self):
return self._digit_rounding.Value
@property
def SignalBar(self):
return self._signal_bar.Value
def OnStarted2(self, time):
super(color_jfatl_digit_tm_strategy, self).OnStarted2(time)
self.Volume = self.OrderVolume
self._ema_value = None
self._ema_count = 0
self._ema_length = self.JmaLength
self._ema_multiplier = 2.0 / (self._ema_length + 1)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
price_step = Decimal(0)
sec = self.Security
if sec is not None and sec.PriceStep is not None and sec.PriceStep > Decimal(0):
price_step = sec.PriceStep
tp_unit = None
sl_unit = None
if self.TakeProfitPoints > 0 and price_step > Decimal(0):
tp_unit = Unit(Decimal(self.TakeProfitPoints) * price_step, UnitTypes.Absolute)
if self.StopLossPoints > 0 and price_step > Decimal(0):
sl_unit = Unit(Decimal(self.StopLossPoints) * price_step, UnitTypes.Absolute)
self.StartProtection(takeProfit=tp_unit, stopLoss=sl_unit)
def _process_ema(self, value):
"""Manual EMA matching ExponentialMovingAverage behavior."""
self._ema_count += 1
if self._ema_value is None:
self._ema_value = value
else:
self._ema_value = value * self._ema_multiplier + self._ema_value * (1.0 - self._ema_multiplier)
return self._ema_value
def _ema_is_formed(self):
return self._ema_count >= self._ema_length
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
self._price_buffer.append(price)
coeffs_len = len(self.FATL_COEFFICIENTS)
if len(self._price_buffer) > coeffs_len:
self._price_buffer.pop(0)
if len(self._price_buffer) < coeffs_len:
return
fatl = 0.0
for i in range(coeffs_len):
fatl += self.FATL_COEFFICIENTS[i] * self._price_buffer[len(self._price_buffer) - 1 - i]
jma_val = self._process_ema(fatl)
if not self._ema_is_formed():
return
rounding_step = self._get_rounding_step()
rounded_line = self._round_to_step(jma_val, rounding_step)
color = 1
if self._previous_line is not None:
diff = rounded_line - self._previous_line
if diff > 0:
color = 2
elif diff < 0:
color = 0
elif len(self._color_history) > 0:
color = self._color_history[0]
self._previous_line = rounded_line
self._color_history.insert(0, color)
if len(self._color_history) > 100:
self._color_history.pop()
if len(self._color_history) <= self.SignalBar:
return
current_color = self._color_history[self.SignalBar - 1]
previous_color = self._color_history[self.SignalBar]
now = candle.CloseTime
in_session = (not self.EnableTimeFilter) or self._is_within_trading_window(now)
if self.EnableTimeFilter and not in_session:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
return
buy_open_signal = self.BuyOpenEnabled and current_color == 2 and previous_color != 2
sell_close_signal = self.SellCloseEnabled and current_color == 2
sell_open_signal = self.SellOpenEnabled and current_color == 0 and previous_color != 0
buy_close_signal = self.BuyCloseEnabled and current_color == 0
if buy_close_signal and self.Position > 0:
self.SellMarket()
if sell_close_signal and self.Position < 0:
self.BuyMarket()
if buy_open_signal and self.Position == 0 and now >= self._next_buy_time:
self.BuyMarket()
self._next_buy_time = now
if sell_open_signal and self.Position == 0 and now >= self._next_sell_time:
self.SellMarket()
self._next_sell_time = now
def _get_rounding_step(self):
sec = self.Security
if sec is None or sec.PriceStep is None or float(sec.PriceStep) <= 0:
return 0.0
step = float(sec.PriceStep)
multiplier = math.pow(10, self.DigitRounding)
return step * multiplier
def _round_to_step(self, value, step):
if step <= 0:
return value
return round(value / step) * step
def _is_within_trading_window(self, time):
h = time.Hour
m = time.Minute
sh = self.StartHour
sm = self.StartMinute
eh = self.EndHour
em = self.EndMinute
if sh < eh:
if h == sh and m >= sm:
return True
if h > sh and h < eh:
return True
if h > sh and h == eh and m < em:
return True
return False
elif sh == eh:
return h == sh and m >= sm and m < em
else:
if h > sh or (h == sh and m >= sm):
return True
if h < eh:
return True
if h == eh and m < em:
return True
return False
def OnReseted(self):
super(color_jfatl_digit_tm_strategy, self).OnReseted()
self._price_buffer = []
self._color_history = []
self._previous_line = None
self._ema_value = None
self._ema_count = 0
self._next_buy_time = DateTime.MinValue
self._next_sell_time = DateTime.MinValue
def CreateClone(self):
return color_jfatl_digit_tm_strategy()