Стратегия Laptrend_1
Общее описание
Laptrend_1 переносит торговую логику советника Laptrend_1.mq4 на платформу StockSharp. Стратегия объединяет фильтр LabTrend на двух таймфреймах, импульс по преобразованию Фишера и фильтр силы тренда ADX на 15-минутных свечах. Сделки открываются только при совпадении направления LabTrend на Н1 и М15, подтверждении импульса Fisher Transform и росте ADX. Позиции закрываются при развороте импульса, смене LabTrend или при переходе рынка в боковой режим, когда ADX и компоненты DI сближаются.
Логика входов и выходов
- Основные данные — сигналы считаются по 15-минутным свечам, а часовые свечи используются для фильтрации тренда LabTrend.
- Канал LabTrend — реализует логику индикатора
LabTrend1_v2.1: строится канал изChannelLengthсвечей, который сужается коэффициентомRiskFactor. Закрытие выше верхней границы фиксирует восходящий тренд, ниже нижней границы — нисходящий. Для открытия позиции направления М15 и Н1 должны совпадать. - Преобразование Фишера — пользовательский индикатор
Fisher_Yur4ikна М15 отслеживает импульс. Пересечения нулевой линии переключают бычий/медвежий режим, а пересечения уровней ±0.25 формируют сигналы выхода. - Фильтр ADX — 15-минутный ADX должен расти, а доминирующий DI подтверждать направление сделки. Если ADX, +DI и –DI отличаются менее чем на
Deltaпунктов, стратегия считает рынок плоским, сбрасывает флаги импульса и закрывает позицию. - Управление позицией — перед открытием новой сделки стратегия закрывает встречные позиции и выставляет объём из параметра. Выход происходит при смене LabTrend, срабатывании сигналов Fisher или обнаружении флэта.
Управление рисками
- Stop Loss / Take Profit — задаются в пунктах инструмента (аналог «пипсов» в MetaTrader) и сравниваются с максимумами/минимумами свечей, имитируя защитные ордера оригинального советника.
- Trailing Stop — при движении цены в прибыльную сторону уровень стопа подтягивается на расстояние
TrailingStopPointsот текущего закрытия. Пробой этого уровня инициирует немедленное закрытие позиции по рынку. - Объём — все заявки используют фиксированный параметр
Volume.
Параметры
Volume– объём сделки в лотах. По умолчанию 1.AdxPeriod– период расчёта ADX. По умолчанию 14.FisherLength– длина окна для преобразования Фишера. По умолчанию 10.ChannelLength– количество свечей в канале LabTrend. По умолчанию 9.RiskFactor– коэффициент сужения канала (в оригинале диапазон 1..10). По умолчанию 3.Delta– максимальная разница между ADX и DI, при которой рынок считается флэтовым. По умолчанию 7.StopLossPoints– расстояние стоп-лосса в пунктах. По умолчанию 100.TakeProfitPoints– расстояние тейк-профита в пунктах. По умолчанию 40.TrailingStopPoints– дистанция трейлинг-стопа в пунктах. По умолчанию 100.SignalCandleType– тип свечей для расчёта сигналов (по умолчанию М15).TrendCandleType– тип свечей для фильтра по тренду (по умолчанию Н1).
Примечания
- В оригинальном MQL коде вычисления выполнялись на каждом тике; в этом переносе анализ производится на закрытии 15-минутных свечей, что делает результаты повторяемыми и сохраняет исходную логику индикаторов.
- Stop Loss, Take Profit и трейлинг реализованы как рыночные выходы при достижении соответствующих ценовых уровней свечи, поэтому отдельные защитные ордера не выставляются.
- Перед запуском убедитесь, что источник данных предоставляет обе серии свечей, указанные в параметрах (М15 и Н1).
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>
/// Conversion of the Laptrend_1 MetaTrader expert advisor.
/// Combines LabTrend channel direction, Fisher transform momentum and ADX filter on multiple timeframes.
/// </summary>
public class Laptrend1Strategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<int> _fisherLength;
private readonly StrategyParam<int> _channelLength;
private readonly StrategyParam<decimal> _risk;
private readonly StrategyParam<decimal> _delta;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<DataType> _signalCandleType;
private readonly StrategyParam<DataType> _trendCandleType;
private AverageDirectionalIndex _adx = null!;
private FisherYur4ikIndicator _fisher = null!;
private readonly LabTrendState _signalTrend = new();
private readonly LabTrendState _trendTrend = new();
private readonly Queue<decimal> _fisherHistory = new();
private bool _fisherBullish;
private bool _fisherBearish;
private bool _fisherExitLong;
private bool _fisherExitShort;
private decimal? _previousAdx;
private decimal _pointValue;
private decimal? _longEntryPrice;
private decimal? _shortEntryPrice;
private decimal? _longTrailingStop;
private decimal? _shortTrailingStop;
private int _lastPositionSign;
private bool _pendingClose;
private int _candleIndex;
private int _lastCloseCandle;
/// <summary>
/// ADX calculation period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Fisher transform length.
/// </summary>
public int FisherLength
{
get => _fisherLength.Value;
set => _fisherLength.Value = value;
}
/// <summary>
/// LabTrend channel lookback.
/// </summary>
public int ChannelLength
{
get => _channelLength.Value;
set => _channelLength.Value = value;
}
/// <summary>
/// LabTrend risk factor (1..10 in the original code).
/// </summary>
public decimal RiskFactor
{
get => _risk.Value;
set => _risk.Value = value;
}
/// <summary>
/// Maximum distance between ADX and DI values before the market is considered flat.
/// </summary>
public decimal Delta
{
get => _delta.Value;
set => _delta.Value = value;
}
/// <summary>
/// Stop loss in points (MetaTrader style).
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit in points (MetaTrader style).
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Trailing stop in points (MetaTrader style).
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Candle type used for signal calculations (default 15 minutes).
/// </summary>
public DataType SignalCandleType
{
get => _signalCandleType.Value;
set => _signalCandleType.Value = value;
}
/// <summary>
/// Higher timeframe candle type for the LabTrend filter (default 1 hour).
/// </summary>
public DataType TrendCandleType
{
get => _trendCandleType.Value;
set => _trendCandleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="Laptrend1Strategy"/>.
/// </summary>
public Laptrend1Strategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Average Directional Index length", "Indicators");
_fisherLength = Param(nameof(FisherLength), 10)
.SetGreaterThanZero()
.SetDisplay("Fisher Length", "Fisher transform window", "Indicators");
_channelLength = Param(nameof(ChannelLength), 9)
.SetGreaterThanZero()
.SetDisplay("Channel Length", "LabTrend channel lookback", "Indicators");
_risk = Param(nameof(RiskFactor), 3m)
.SetGreaterThanZero()
.SetDisplay("Risk Factor", "LabTrend risk factor", "Indicators");
_delta = Param(nameof(Delta), 7m)
.SetGreaterThanZero()
.SetDisplay("ADX Delta", "Maximum spread between ADX and DI before flat exit", "Filters");
_stopLossPoints = Param(nameof(StopLossPoints), 100m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss distance in points", "Risk Management");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 40m)
.SetNotNegative()
.SetDisplay("Take Profit", "Take profit distance in points", "Risk Management");
_trailingStopPoints = Param(nameof(TrailingStopPoints), 100m)
.SetNotNegative()
.SetDisplay("Trailing Stop", "Trailing stop distance in points", "Risk Management");
_signalCandleType = Param(nameof(SignalCandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Signal Candle", "Primary timeframe for signals", "General");
_trendCandleType = Param(nameof(TrendCandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Trend Candle", "Higher timeframe for LabTrend filter", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, SignalCandleType);
yield return (Security, TrendCandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_adx = null!;
_fisher = null!;
_signalTrend.Reset();
_trendTrend.Reset();
_fisherHistory.Clear();
_fisherBullish = false;
_fisherBearish = false;
_fisherExitLong = false;
_fisherExitShort = false;
_previousAdx = null;
_pointValue = 0m;
ResetLongState();
ResetShortState();
_lastPositionSign = 0;
_longEntryPrice = null;
_shortEntryPrice = null;
_longTrailingStop = null;
_shortTrailingStop = null;
_pendingClose = false;
_candleIndex = 0;
_lastCloseCandle = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pointValue = Security?.PriceStep ?? 0m;
if (_pointValue <= 0m)
_pointValue = 1m;
_fisherHistory.Clear();
_fisherBullish = false;
_fisherBearish = false;
_fisherExitLong = false;
_fisherExitShort = false;
_previousAdx = null;
ResetLongState();
ResetShortState();
_lastPositionSign = Math.Sign(Position);
_pendingClose = false;
_candleIndex = 0;
_lastCloseCandle = -20;
_fisher = new FisherYur4ikIndicator
{
Length = FisherLength
};
_adx = new AverageDirectionalIndex
{
Length = AdxPeriod
};
var signalSubscription = SubscribeCandles(SignalCandleType);
signalSubscription.BindEx(_fisher, _adx, ProcessSignalCandle).Start();
var trendSubscription = SubscribeCandles(TrendCandleType);
trendSubscription.Bind(ProcessTrendCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, signalSubscription);
DrawIndicator(area, _fisher);
DrawOwnTrades(area);
}
}
private void ProcessSignalCandle(ICandleMessage candle, IIndicatorValue fisherValue, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
_candleIndex++;
// Update LabTrend state on the signal timeframe.
_signalTrend.Process(candle, ChannelLength, RiskFactor);
// Keep Fisher state in sync whenever a final value is available.
if (fisherValue.IsFinal && _fisher.IsFormed)
{
var fisher = fisherValue.GetValue<decimal>();
UpdateFisherFlags(fisher);
}
var canTrade = IsFormedAndOnlineAndAllowTrading();
if (!adxValue.IsFinal || !_adx.IsFormed || adxValue is not AverageDirectionalIndexValue adxData ||
adxData.MovingAverage is not decimal adxCurrent ||
adxData.Dx.Plus is not decimal plusDi ||
adxData.Dx.Minus is not decimal minusDi)
{
return;
}
var previousAdx = _previousAdx;
_previousAdx = adxCurrent;
var adxRising = previousAdx.HasValue && adxCurrent > previousAdx.Value;
var bullDirectional = plusDi > minusDi;
var bearDirectional = minusDi > plusDi;
var flat = Math.Abs(plusDi - minusDi) < Delta &&
Math.Abs(adxCurrent - plusDi) < Delta &&
Math.Abs(adxCurrent - minusDi) < Delta;
if (flat && canTrade)
{
// Reset momentum flags and close any open trades in ranging conditions.
_fisherBullish = false;
_fisherBearish = false;
_fisherExitLong = false;
_fisherExitShort = false;
if (Position > 0)
{
_lastCloseCandle = _candleIndex;
SellMarket(Position);
}
else if (Position < 0)
{
_lastCloseCandle = _candleIndex;
BuyMarket(Math.Abs(Position));
}
return;
}
if (canTrade)
{
const int CooldownCandles = 20;
var cooldownOk = (_candleIndex - _lastCloseCandle) >= CooldownCandles;
if (Position == 0)
{
// Only enter when flat, cooldown has elapsed, and ADX is strong enough
var adxStrong = adxCurrent >= 20m;
if (cooldownOk && adxStrong && _trendTrend.IsUpTrend && _signalTrend.IsUpTrend && _fisherBullish && bullDirectional && adxRising)
{
_fisherBullish = false;
BuyMarket(Volume);
return;
}
else if (cooldownOk && adxStrong && _trendTrend.IsDownTrend && _signalTrend.IsDownTrend && _fisherBearish && bearDirectional && adxRising)
{
_fisherBearish = false;
SellMarket(Volume);
return;
}
}
else if (Position > 0)
{
if (_fisherExitLong)
{
_fisherExitLong = false;
_lastCloseCandle = _candleIndex;
SellMarket(Position);
return;
}
}
else // Position < 0
{
if (_fisherExitShort)
{
_fisherExitShort = false;
_lastCloseCandle = _candleIndex;
BuyMarket(Math.Abs(Position));
return;
}
}
}
ManagePosition(candle, canTrade);
}
private void ProcessTrendCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Track the higher timeframe trend for directional filtering.
_trendTrend.Process(candle, ChannelLength, RiskFactor);
}
private void ManagePosition(ICandleMessage candle, bool canTrade)
{
var position = Position;
var positionSign = Math.Sign(position);
if (!canTrade)
{
_lastPositionSign = positionSign;
return;
}
// If we submitted a close order, wait until position is actually flat.
if (_pendingClose)
{
if (positionSign == 0)
_pendingClose = false;
else
{
_lastPositionSign = positionSign;
return;
}
}
var step = _pointValue > 0m ? _pointValue : 1m;
var stopOffset = StopLossPoints > 0m ? StopLossPoints * step : 0m;
var takeOffset = TakeProfitPoints > 0m ? TakeProfitPoints * step : 0m;
var trailingOffset = TrailingStopPoints > 0m ? TrailingStopPoints * step : 0m;
if (positionSign > 0)
{
// Capture the entry price when switching from short or flat to long.
if (_lastPositionSign <= 0)
{
_longEntryPrice = candle.ClosePrice;
_longTrailingStop = trailingOffset > 0m ? candle.ClosePrice - trailingOffset : null;
ResetShortState();
}
if (_longEntryPrice.HasValue)
{
var entry = _longEntryPrice.Value;
var volume = position;
if (stopOffset > 0m && candle.LowPrice <= entry - stopOffset)
{
SellMarket(volume);
ResetLongState();
_lastPositionSign = 0;
_pendingClose = true;
_lastCloseCandle = _candleIndex;
return;
}
if (takeOffset > 0m && candle.HighPrice >= entry + takeOffset)
{
SellMarket(volume);
ResetLongState();
_lastPositionSign = 0;
_pendingClose = true;
_lastCloseCandle = _candleIndex;
return;
}
if (trailingOffset > 0m)
{
var candidate = candle.ClosePrice - trailingOffset;
if (!_longTrailingStop.HasValue || candidate > _longTrailingStop.Value)
_longTrailingStop = candidate;
if (_longTrailingStop.HasValue && candle.LowPrice <= _longTrailingStop.Value)
{
SellMarket(volume);
ResetLongState();
_lastPositionSign = 0;
_pendingClose = true;
return;
}
}
}
}
else if (positionSign < 0)
{
// Capture the entry price when switching from long or flat to short.
if (_lastPositionSign >= 0)
{
_shortEntryPrice = candle.ClosePrice;
_shortTrailingStop = trailingOffset > 0m ? candle.ClosePrice + trailingOffset : null;
ResetLongState();
}
if (_shortEntryPrice.HasValue)
{
var entry = _shortEntryPrice.Value;
var volume = Math.Abs(position);
if (stopOffset > 0m && candle.HighPrice >= entry + stopOffset)
{
BuyMarket(volume);
ResetShortState();
_lastPositionSign = 0;
_pendingClose = true;
_lastCloseCandle = _candleIndex;
return;
}
if (takeOffset > 0m && candle.LowPrice <= entry - takeOffset)
{
BuyMarket(volume);
ResetShortState();
_lastPositionSign = 0;
_pendingClose = true;
_lastCloseCandle = _candleIndex;
return;
}
if (trailingOffset > 0m)
{
var candidate = candle.ClosePrice + trailingOffset;
if (!_shortTrailingStop.HasValue || candidate < _shortTrailingStop.Value)
_shortTrailingStop = candidate;
if (_shortTrailingStop.HasValue && candle.HighPrice >= _shortTrailingStop.Value)
{
BuyMarket(volume);
ResetShortState();
_lastPositionSign = 0;
_pendingClose = true;
return;
}
}
}
}
else
{
ResetLongState();
ResetShortState();
}
_lastPositionSign = positionSign;
}
private void UpdateFisherFlags(decimal value)
{
_fisherHistory.Enqueue(value);
while (_fisherHistory.Count > 3)
_fisherHistory.Dequeue();
if (_fisherHistory.Count < 3)
return;
var values = _fisherHistory.ToArray();
var fx0n = values[^1];
var fx1n = values[^2];
var fx2n = values[^3];
var fx0 = (fx0n + fx1n) / 2m;
var fx1 = (fx1n + fx2n) / 2m;
if (fx1 < 0m && fx0 > 0m)
{
// Fisher crossed above zero -> bullish momentum.
_fisherBullish = true;
_fisherBearish = false;
}
else if (fx1 > 0m && fx0 < 0m)
{
// Fisher crossed below zero -> bearish momentum.
_fisherBearish = true;
_fisherBullish = false;
}
if (fx1 > 0.25m && fx0 < 0.25m)
{
// Fisher dropped back under +0.25 -> exit long.
_fisherExitLong = true;
_fisherExitShort = false;
}
else if (fx1 < -0.25m && fx0 > -0.25m)
{
// Fisher climbed back above -0.25 -> exit short.
_fisherExitShort = true;
_fisherExitLong = false;
}
}
private void ResetLongState()
{
_longEntryPrice = null;
_longTrailingStop = null;
}
private void ResetShortState()
{
_shortEntryPrice = null;
_shortTrailingStop = null;
}
private sealed class LabTrendState
{
private readonly Queue<decimal> _highs = new();
private readonly Queue<decimal> _lows = new();
private decimal _trend;
public bool IsUpTrend => _trend > 0m;
public bool IsDownTrend => _trend < 0m;
public void Reset()
{
_highs.Clear();
_lows.Clear();
_trend = 0m;
}
public override bool Equals(object obj)
{
if (obj is not LabTrendState other) return false;
return _trend == other._trend
&& _highs.Count == other._highs.Count
&& _lows.Count == other._lows.Count
&& _highs.SequenceEqual(other._highs)
&& _lows.SequenceEqual(other._lows);
}
public override int GetHashCode() => HashCode.Combine(_trend, _highs.Count, _lows.Count);
public void Process(ICandleMessage candle, int length, decimal risk)
{
var lookback = Math.Max(1, length);
_highs.Enqueue(candle.HighPrice);
_lows.Enqueue(candle.LowPrice);
if (_highs.Count > lookback)
{
_highs.Dequeue();
_lows.Dequeue();
}
if (_highs.Count < lookback)
return;
var highest = _highs.Max();
var lowest = _lows.Min();
var range = highest - lowest;
if (range <= 0m)
return;
var safeRisk = risk;
if (safeRisk < 0m)
safeRisk = 0m;
else if (safeRisk > 33m)
safeRisk = 33m;
var coefficient = (33m - safeRisk) / 100m;
var upper = highest - range * coefficient;
var lower = lowest + range * coefficient;
if (_trend <= 0m && candle.ClosePrice > upper)
_trend = 1m;
else if (_trend >= 0m && candle.ClosePrice < lower)
_trend = -1m;
}
}
private sealed class FisherYur4ikIndicator : BaseIndicator
{
public int Length { get; set; } = 10;
private readonly Queue<decimal> _medians = new();
private decimal _previousValue;
private decimal _previousFish;
protected override IIndicatorValue OnProcess(IIndicatorValue input)
{
var candle = input.GetValue<ICandleMessage>();
if (candle == null)
return new DecimalIndicatorValue(this, 0m, input.Time);
var length = Math.Max(1, Length);
var median = (candle.HighPrice + candle.LowPrice) / 2m;
_medians.Enqueue(median);
if (_medians.Count > length)
{
_medians.Dequeue();
}
if (_medians.Count < length)
{
IsFormed = false;
return new DecimalIndicatorValue(this, 0m, input.Time);
}
var highest = _medians.Max();
var lowest = _medians.Min();
var range = highest - lowest;
decimal fish;
if (range == 0m)
{
fish = _previousFish;
}
else
{
var value = 0.66m * ((median - lowest) / range - 0.5m) + 0.67m * _previousValue;
if (value > 0.999m)
value = 0.999m;
else if (value < -0.999m)
value = -0.999m;
var ratio = (1m + value) / (1m - value);
fish = 0.5m * (decimal)Math.Log((double)ratio) + 0.5m * _previousFish;
_previousValue = value;
_previousFish = fish;
}
IsFormed = _medians.Count >= length;
return new DecimalIndicatorValue(this, fish, input.Time);
}
public override void Reset()
{
base.Reset();
_medians.Clear();
_previousValue = 0m;
_previousFish = 0m;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class laptrend1_strategy(Strategy):
"""
Laptrend: EMA trend + RSI momentum with ATR-based stops.
Simplified from multi-timeframe custom indicator version.
"""
def __init__(self):
super(laptrend1_strategy, self).__init__()
self._ema_length = self.Param("EmaLength", 20).SetDisplay("EMA Length", "Trend EMA", "Indicators")
self._rsi_length = self.Param("RsiLength", 14).SetDisplay("RSI Length", "RSI period", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "ATR period", "Indicators")
self._sl_mult = self.Param("SlMultiplier", 2.0).SetDisplay("SL Mult", "ATR multiplier for SL", "Risk")
self._tp_mult = self.Param("TpMultiplier", 3.0).SetDisplay("TP Mult", "ATR multiplier for TP", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._prev_ema = 0.0
self._entry_price = 0.0
self._stop = 0.0
self._tp = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(laptrend1_strategy, self).OnReseted()
self._prev_ema = 0.0
self._entry_price = 0.0
self._stop = 0.0
self._tp = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(laptrend1_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, rsi, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
ema = float(ema_val)
rsi = float(rsi_val)
atr = float(atr_val)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self._cooldown > 0:
self._cooldown -= 1
if atr <= 0:
self._prev_ema = ema
return
if self.Position > 0:
if low <= self._stop or high >= self._tp:
self.SellMarket()
self._entry_price = 0
self._cooldown = 20
elif self.Position < 0:
if high >= self._stop or low <= self._tp:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 20
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_ema = ema
return
if self._cooldown > 0:
self._prev_ema = ema
return
if self._prev_ema == 0:
self._prev_ema = ema
return
trend_up = close > ema and self._prev_ema < ema
trend_down = close < ema and self._prev_ema > ema
if trend_up and rsi > 50 and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._stop = close - atr * self._sl_mult.Value
self._tp = close + atr * self._tp_mult.Value
self._cooldown = 20
elif trend_down and rsi < 50 and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._stop = close + atr * self._sl_mult.Value
self._tp = close - atr * self._tp_mult.Value
self._cooldown = 20
self._prev_ema = ema
def CreateClone(self):
return laptrend1_strategy()