Estratégia de comércio 2 da Cloudzs
Visão geral
A Estratégia Cloudzs Trade 2 é uma versão StockSharp do MetaTrader 4 consultor especialista cloudzs_trade_2. O robô original combina reversões de osciladores estocásticos com um filtro de confirmação de fractal duplo e usa lógica de rastreamento agressiva para proteger posições abertas. Esta versão C# recria o fluxo de sinal e as regras de gerenciamento comercial enquanto expõe os parâmetros como objetos StrategyParam para que possam ser otimizados ou ajustados na IU do StockSharp.
A estratégia observa uma única série de velas (período configurável) e avalia duas condições independentes:
- Stochastic reversão – é acionada quando a linha %D sai de uma zona extrema (>= 80 para vendas, <= 20 para compras) enquanto confirma que %D cruzou a linha %K na vela anterior, correspondendo de perto à lógica original MQL.
- Confirmação de fractal duplo – espera até que dois sinais fractais consecutivos do mesmo tipo apareçam (dois fractais superiores para vendas ou dois fractais inferiores para compras).
Se alguma das condições gerar uma solicitação de compra ou venda, a estratégia entra nessa direção (desde que nenhuma negociação esteja ativa e a saída anterior tenha sido em um dia diferente). Quando já estiver em uma negociação, as mesmas condições podem ser usadas para sair mais cedo se CloseOnOpposite estiver habilitado.
Parâmetros
| Nome | Descrição | Padrão |
|---|---|---|
LotSplitter |
Coeficiente usado para aproximar o volume comercial do valor da conta corrente. | 0.1 |
MaxVolume |
Limite superior para o volume calculado (0 desativa o limite). | 0 |
TakeProfitOffset |
Distância fixa de lucro em unidades de preço absoluto. | 0 |
TrailingStopOffset |
Distância do trailing stop em unidades de preço. | 0.01 |
StopLossOffset |
Distância fixa de stop-loss em unidades de preço. | 0.05 |
MinProfitOffset |
Lucro mínimo a ser mantido após uma excursão favorável quando ProfitPointsOffset for alcançado. |
0 |
ProfitPointsOffset |
Movimento favorável necessário antes que MinProfitOffset seja aplicado. |
0 |
%K Period / %D Period / Slowing |
Configuração do oscilador Stochastic. | 8 / 8 / 4 |
Method |
Identificador de método estocástico MT4 original (informativo, não usado porque StockSharp expõe uma única implementação). | 3 |
PriceMode |
Identificador original do modo de preço MT4 (apenas informativo). | 1 |
UseStochasticCondition |
Habilite a geração de sinal com base estocástica. | true |
UseFractalCondition |
Habilite a geração de sinal baseada em fractal. | true |
CloseOnOpposite |
Feche a posição ativa quando o sinal oposto aparecer. | true |
CandleType |
Período/tipo de dados usado para cálculos. | 15-minute time frame |
Sinais de negociação
Entrada longa
- A linha %D está abaixo ou igual a 20 e cruza abaixo de %K (correspondendo à comparação da vela anterior do MT4).
- OU dois fractais inferiores sequenciais são detectados.
- Nenhuma posição aberta e a última saída ocorreu em um dia diferente.
Entrada curta
- A linha %D está acima ou igual a 80 e cruza acima de %K.
- OU aparecem dois fractais superiores sequenciais.
- Nenhuma posição aberta e a última saída ocorreu em um dia diferente.
Regras de saída
- Os níveis de hard stop-loss ou take-profit são alcançados (se configurados).
- O trailing stop se move a favor da negociação e o preço toca o nível de stop atualizado.
- Após a posição experimentar um movimento favorável de
ProfitPointsOffset, um retrocesso paraMinProfitOffsetfecha a negociação. - Reversão antecipada opcional: se
CloseOnOppositefor verdadeiro e o sinal oposto for acionado, a negociação será fechada.
Gestão de risco
- As distâncias de stop-loss e take-profit imitam as compensações brutas de pip do código MT4 (interpretadas aqui como diferenças de preço).
- Os trailing stops são atualizados usando o preço de fechamento e se movem apenas na direção lucrativa.
- O parâmetro
LotSplittertenta seguir a fórmula de volume original, dimensionando o tamanho da negociação por valor da conta e limitando-o comMaxVolume.
Notas e Limitações
- O StockSharp
StochasticOscillatorexpõe uma única implementação de suavização; portanto, os parâmetrosMethodePriceModesão mantidos como referência, mas não alteram o comportamento do indicador. - O script MT4 original funcionou passo a passo. Esta porta avalia sinais em velas finalizadas para se alinhar com StockSharp práticas recomendadas.
- O cálculo do volume depende dos valores disponíveis da carteira; se não existirem informações da conta, ela retornará ao valor
LotSplitter.
Uso
- Adicione a estratégia ao seu projeto StockSharp e selecione o instrumento que deseja negociar.
- Configure o período da vela e ajuste as configurações estocásticas/fractais, se necessário.
- Forneça compensações realistas de stop-loss/take-profit que correspondam ao tamanho do tick do instrumento.
- Inicie a estratégia no Designer, Runner ou via API e monitore as mensagens de log para obter informações de sinal.
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>
/// Port of the "cloudzs trade 2" MetaTrader 4 expert advisor.
/// Combines stochastic reversals with fractal confirmations and mirrors the original trailing logic.
/// </summary>
public class CloudzsTrade2Strategy : Strategy
{
private readonly StrategyParam<decimal> _lotSplitter;
private readonly StrategyParam<decimal> _maxVolume;
private readonly StrategyParam<decimal> _takeProfitOffset;
private readonly StrategyParam<decimal> _trailingStopOffset;
private readonly StrategyParam<decimal> _stopLossOffset;
private readonly StrategyParam<decimal> _minProfitOffset;
private readonly StrategyParam<decimal> _profitPointsOffset;
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<int> _slowingPeriod;
private readonly StrategyParam<int> _method;
private readonly StrategyParam<int> _priceMode;
private readonly StrategyParam<bool> _useStochasticCondition;
private readonly StrategyParam<bool> _useFractalCondition;
private readonly StrategyParam<bool> _closeOnOpposite;
private readonly StrategyParam<DataType> _candleType;
private StochasticOscillator _stochastic;
private decimal _previousK;
private decimal _previousD;
private decimal _lastK;
private decimal _lastD;
private bool _hasPrevious;
private bool _hasLast;
private decimal _high1;
private decimal _high2;
private decimal _high3;
private decimal _high4;
private decimal _high5;
private decimal _low1;
private decimal _low2;
private decimal _low3;
private decimal _low4;
private decimal _low5;
private FractalTypes? _latestFractal;
private FractalTypes? _previousFractal;
private int _fractalSeedCount;
private decimal? _stopPrice;
private decimal? _takeProfitPrice;
private decimal _entryPrice;
private decimal _maxFavorableMove;
private DateTime? _lastExitDate;
private enum FractalTypes
{
Up,
Down
}
/// <summary>
/// Lot coefficient used to estimate order size from account value.
/// </summary>
public decimal LotSplitter
{
get => _lotSplitter.Value;
set => _lotSplitter.Value = value;
}
/// <summary>
/// Maximum allowed order size.
/// </summary>
public decimal MaxVolume
{
get => _maxVolume.Value;
set => _maxVolume.Value = value;
}
/// <summary>
/// Take profit distance expressed in price units.
/// </summary>
public decimal TakeProfitOffset
{
get => _takeProfitOffset.Value;
set => _takeProfitOffset.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in price units.
/// </summary>
public decimal TrailingStopOffset
{
get => _trailingStopOffset.Value;
set => _trailingStopOffset.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price units.
/// </summary>
public decimal StopLossOffset
{
get => _stopLossOffset.Value;
set => _stopLossOffset.Value = value;
}
/// <summary>
/// Minimum profit required to keep the position open after reaching the <see cref="ProfitPointsOffset"/> threshold.
/// </summary>
public decimal MinProfitOffset
{
get => _minProfitOffset.Value;
set => _minProfitOffset.Value = value;
}
/// <summary>
/// Profit cushion that must be reached before <see cref="MinProfitOffset"/> becomes active.
/// </summary>
public decimal ProfitPointsOffset
{
get => _profitPointsOffset.Value;
set => _profitPointsOffset.Value = value;
}
/// <summary>
/// Lookback period for the stochastic oscillator.
/// </summary>
public int KPeriod
{
get => _kPeriod.Value;
set => _kPeriod.Value = value;
}
/// <summary>
/// Smoothing period for the %D line.
/// </summary>
public int DPeriod
{
get => _dPeriod.Value;
set => _dPeriod.Value = value;
}
/// <summary>
/// Smoothing period for the %K line.
/// </summary>
public int SlowingPeriod
{
get => _slowingPeriod.Value;
set => _slowingPeriod.Value = value;
}
/// <summary>
/// Original stochastic method identifier (kept for reference).
/// </summary>
public int Method
{
get => _method.Value;
set => _method.Value = value;
}
/// <summary>
/// Original price mode identifier (kept for reference).
/// </summary>
public int PriceMode
{
get => _priceMode.Value;
set => _priceMode.Value = value;
}
/// <summary>
/// Enable stochastic based entry logic.
/// </summary>
public bool UseStochasticCondition
{
get => _useStochasticCondition.Value;
set => _useStochasticCondition.Value = value;
}
/// <summary>
/// Enable fractal based entry logic.
/// </summary>
public bool UseFractalCondition
{
get => _useFractalCondition.Value;
set => _useFractalCondition.Value = value;
}
/// <summary>
/// Close the active position when the opposite signal appears.
/// </summary>
public bool CloseOnOpposite
{
get => _closeOnOpposite.Value;
set => _closeOnOpposite.Value = value;
}
/// <summary>
/// Candle series used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="CloudzsTrade2Strategy"/> class.
/// </summary>
public CloudzsTrade2Strategy()
{
_lotSplitter = Param(nameof(LotSplitter), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Lot Splitter", "Coefficient used to derive order size", "Trading");
_maxVolume = Param(nameof(MaxVolume), 0m)
.SetDisplay("Max Volume", "Maximum volume limit (0 disables the cap)", "Trading");
_takeProfitOffset = Param(nameof(TakeProfitOffset), 0m)
.SetDisplay("Take Profit", "Take profit distance in price units", "Risk");
_trailingStopOffset = Param(nameof(TrailingStopOffset), 0.01m)
.SetDisplay("Trailing Stop", "Trailing stop distance in price units", "Risk");
_stopLossOffset = Param(nameof(StopLossOffset), 0.05m)
.SetDisplay("Stop Loss", "Stop loss distance in price units", "Risk");
_minProfitOffset = Param(nameof(MinProfitOffset), 0m)
.SetDisplay("Min Profit", "Minimum profit to keep after pullback", "Risk");
_profitPointsOffset = Param(nameof(ProfitPointsOffset), 0m)
.SetDisplay("Profit Points", "Favorable move required before min profit rule", "Risk");
_kPeriod = Param(nameof(KPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("%K Period", "Base length of the stochastic oscillator", "Indicators");
_dPeriod = Param(nameof(DPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("%D Period", "Smoothing length for the stochastic signal", "Indicators");
_slowingPeriod = Param(nameof(SlowingPeriod), 4)
.SetGreaterThanZero()
.SetDisplay("Slowing", "Additional smoothing length for %K", "Indicators");
_method = Param(nameof(Method), 3)
.SetDisplay("Method", "Original MQL MA method identifier", "Indicators");
_priceMode = Param(nameof(PriceMode), 1)
.SetDisplay("Price Mode", "Original MQL price mode identifier", "Indicators");
_useStochasticCondition = Param(nameof(UseStochasticCondition), true)
.SetDisplay("Use Stochastic", "Enable stochastic reversal filter", "Signals");
_useFractalCondition = Param(nameof(UseFractalCondition), true)
.SetDisplay("Use Fractals", "Enable double fractal confirmation", "Signals");
_closeOnOpposite = Param(nameof(CloseOnOpposite), true)
.SetDisplay("Close On Opposite", "Exit when the opposite signal fires", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe for the strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_stochastic = null;
_previousK = 0m;
_previousD = 0m;
_lastK = 0m;
_lastD = 0m;
_hasPrevious = false;
_hasLast = false;
_high1 = _high2 = _high3 = _high4 = _high5 = 0m;
_low1 = _low2 = _low3 = _low4 = _low5 = 0m;
_latestFractal = null;
_previousFractal = null;
_fractalSeedCount = 0;
_stopPrice = null;
_takeProfitPrice = null;
_entryPrice = 0m;
_maxFavorableMove = 0m;
_lastExitDate = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_stochastic = new StochasticOscillator
{
K = { Length = KPeriod },
D = { Length = DPeriod }
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_stochastic, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _stochastic);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochasticValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateFractals(candle);
var stochasticSignal = UseStochasticCondition ? EvaluateStochasticSignal(stochasticValue) : 0;
var fractalSignal = UseFractalCondition ? EvaluateFractalSignal() : 0;
var combinedSignal = 0;
if (stochasticSignal == 2 || fractalSignal == 2)
combinedSignal = 2;
else if (stochasticSignal == 1 || fractalSignal == 1)
combinedSignal = 1;
ManageOpenPosition(candle, combinedSignal);
if (Position != 0)
return;
if (_lastExitDate.HasValue && _lastExitDate.Value == candle.OpenTime.Date)
return;
if (combinedSignal == 0)
return;
var volume = CalculateOrderVolume(candle.ClosePrice);
if (volume <= 0m)
return;
Volume = volume;
if (combinedSignal == 1)
{
BuyMarket();
InitializeTargets(candle.ClosePrice, true);
}
else if (combinedSignal == 2)
{
SellMarket();
InitializeTargets(candle.ClosePrice, false);
}
}
private decimal CalculateOrderVolume(decimal price)
{
if (price <= 0m)
return LotSplitter;
var accountValue = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
if (accountValue <= 0m)
accountValue = LotSplitter;
var estimated = LotSplitter * accountValue / price;
var normalized = Math.Floor(estimated * 10m) / 10m;
if (normalized <= 0m)
normalized = LotSplitter;
if (MaxVolume > 0m && normalized > MaxVolume)
normalized = MaxVolume;
return normalized;
}
private int EvaluateStochasticSignal(IIndicatorValue stochasticValue)
{
if (_stochastic is null || stochasticValue is not StochasticOscillatorValue typed)
return 0;
if (typed.K is not decimal currentK || typed.D is not decimal currentD)
return 0;
if (!_hasLast)
{
_lastK = currentK;
_lastD = currentD;
_hasLast = true;
return 0;
}
if (!_hasPrevious)
{
_previousK = _lastK;
_previousD = _lastD;
_lastK = currentK;
_lastD = currentD;
_hasPrevious = true;
return 0;
}
var sellSignal = _lastD >= 80m && _previousD <= _previousK && _lastD >= _lastK;
var buySignal = _lastD <= 20m && _previousD >= _previousK && _lastD <= _lastK;
_previousK = _lastK;
_previousD = _lastD;
_lastK = currentK;
_lastD = currentD;
if (sellSignal)
return 2;
if (buySignal)
return 1;
return 0;
}
private void UpdateFractals(ICandleMessage candle)
{
_high1 = _high2;
_high2 = _high3;
_high3 = _high4;
_high4 = _high5;
_high5 = candle.HighPrice;
_low1 = _low2;
_low2 = _low3;
_low3 = _low4;
_low4 = _low5;
_low5 = candle.LowPrice;
if (_fractalSeedCount < 5)
{
_fractalSeedCount++;
return;
}
var upFractal = _high3 > _high1 && _high3 > _high2 && _high3 > _high4 && _high3 > _high5;
var downFractal = _low3 < _low1 && _low3 < _low2 && _low3 < _low4 && _low3 < _low5;
if (upFractal)
RegisterFractal(FractalTypes.Up);
if (downFractal)
RegisterFractal(FractalTypes.Down);
}
private void RegisterFractal(FractalTypes type)
{
_previousFractal = _latestFractal;
_latestFractal = type;
}
private int EvaluateFractalSignal()
{
if (_latestFractal is null || _previousFractal is null)
return 0;
if (_latestFractal == FractalTypes.Up && _previousFractal == FractalTypes.Up)
return 2;
if (_latestFractal == FractalTypes.Down && _previousFractal == FractalTypes.Down)
return 1;
return 0;
}
private void ManageOpenPosition(ICandleMessage candle, int combinedSignal)
{
if (Position == 0)
return;
if (Position > 0)
ManageLongPosition(candle, combinedSignal);
else
ManageShortPosition(candle, combinedSignal);
}
private void ManageLongPosition(ICandleMessage candle, int combinedSignal)
{
UpdateTrailingStop(candle, true);
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket();
FinalizeExit(candle);
return;
}
if (_takeProfitPrice is decimal take && candle.HighPrice >= take)
{
SellMarket();
FinalizeExit(candle);
return;
}
var currentGain = candle.ClosePrice - _entryPrice;
var favorable = candle.HighPrice - _entryPrice;
if (favorable > _maxFavorableMove)
_maxFavorableMove = favorable;
if (ProfitPointsOffset > 0m && _maxFavorableMove >= ProfitPointsOffset && currentGain <= MinProfitOffset)
{
SellMarket();
FinalizeExit(candle);
return;
}
if (CloseOnOpposite && combinedSignal == 2)
{
SellMarket();
FinalizeExit(candle);
}
}
private void ManageShortPosition(ICandleMessage candle, int combinedSignal)
{
UpdateTrailingStop(candle, false);
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket();
FinalizeExit(candle);
return;
}
if (_takeProfitPrice is decimal take && candle.LowPrice <= take)
{
BuyMarket();
FinalizeExit(candle);
return;
}
var currentGain = _entryPrice - candle.ClosePrice;
var favorable = _entryPrice - candle.LowPrice;
if (favorable > _maxFavorableMove)
_maxFavorableMove = favorable;
if (ProfitPointsOffset > 0m && _maxFavorableMove >= ProfitPointsOffset && currentGain <= MinProfitOffset)
{
BuyMarket();
FinalizeExit(candle);
return;
}
if (CloseOnOpposite && combinedSignal == 1)
{
BuyMarket();
FinalizeExit(candle);
}
}
private void UpdateTrailingStop(ICandleMessage candle, bool isLong)
{
if (TrailingStopOffset <= 0m)
return;
if (isLong)
{
var potentialStop = candle.ClosePrice - TrailingStopOffset;
if (_stopPrice is null || potentialStop > _stopPrice)
{
if (potentialStop > _entryPrice)
_stopPrice = potentialStop;
}
}
else
{
var potentialStop = candle.ClosePrice + TrailingStopOffset;
if (_stopPrice is null || potentialStop < _stopPrice)
{
if (potentialStop < _entryPrice)
_stopPrice = potentialStop;
}
}
}
private void InitializeTargets(decimal entryPrice, bool isLong)
{
_entryPrice = entryPrice;
_maxFavorableMove = 0m;
_stopPrice = StopLossOffset > 0m
? isLong ? entryPrice - StopLossOffset : entryPrice + StopLossOffset
: null;
_takeProfitPrice = TakeProfitOffset > 0m
? isLong ? entryPrice + TakeProfitOffset : entryPrice - TakeProfitOffset
: null;
}
private void FinalizeExit(ICandleMessage candle)
{
_stopPrice = null;
_takeProfitPrice = null;
_entryPrice = 0m;
_maxFavorableMove = 0m;
_lastExitDate = candle.OpenTime.Date;
}
}
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 StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class cloudzs_trade2_strategy(Strategy):
"""Stochastic reversals combined with fractal confirmations. Mirrors the
original cloudzs trade 2 MetaTrader expert with trailing stop management."""
# Fractal type constants
FRACTAL_UP = 0
FRACTAL_DOWN = 1
def __init__(self):
super(cloudzs_trade2_strategy, self).__init__()
self._lot_splitter = self.Param("LotSplitter", 0.1) \
.SetGreaterThanZero() \
.SetDisplay("Lot Splitter", "Coefficient used to derive order size", "Trading")
self._max_volume = self.Param("MaxVolume", 0.0) \
.SetDisplay("Max Volume", "Maximum volume limit (0 disables the cap)", "Trading")
self._take_profit_offset = self.Param("TakeProfitOffset", 0.0) \
.SetDisplay("Take Profit", "Take profit distance in price units", "Risk")
self._trailing_stop_offset = self.Param("TrailingStopOffset", 0.01) \
.SetDisplay("Trailing Stop", "Trailing stop distance in price units", "Risk")
self._stop_loss_offset = self.Param("StopLossOffset", 0.05) \
.SetDisplay("Stop Loss", "Stop loss distance in price units", "Risk")
self._min_profit_offset = self.Param("MinProfitOffset", 0.0) \
.SetDisplay("Min Profit", "Minimum profit to keep after pullback", "Risk")
self._profit_points_offset = self.Param("ProfitPointsOffset", 0.0) \
.SetDisplay("Profit Points", "Favorable move required before min profit rule", "Risk")
self._k_period = self.Param("KPeriod", 8) \
.SetGreaterThanZero() \
.SetDisplay("%K Period", "Base length of the stochastic oscillator", "Indicators")
self._d_period = self.Param("DPeriod", 8) \
.SetGreaterThanZero() \
.SetDisplay("%D Period", "Smoothing length for the stochastic signal", "Indicators")
self._slowing_period = self.Param("SlowingPeriod", 4) \
.SetGreaterThanZero() \
.SetDisplay("Slowing", "Additional smoothing length for %K", "Indicators")
self._method = self.Param("Method", 3) \
.SetDisplay("Method", "Original MQL MA method identifier", "Indicators")
self._price_mode = self.Param("PriceMode", 1) \
.SetDisplay("Price Mode", "Original MQL price mode identifier", "Indicators")
self._use_stochastic_condition = self.Param("UseStochasticCondition", True) \
.SetDisplay("Use Stochastic", "Enable stochastic reversal filter", "Signals")
self._use_fractal_condition = self.Param("UseFractalCondition", True) \
.SetDisplay("Use Fractals", "Enable double fractal confirmation", "Signals")
self._close_on_opposite = self.Param("CloseOnOpposite", True) \
.SetDisplay("Close On Opposite", "Exit when the opposite signal fires", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary timeframe for the strategy", "General")
self._stochastic = None
self._previous_k = 0.0
self._previous_d = 0.0
self._last_k = 0.0
self._last_d = 0.0
self._has_previous = False
self._has_last = False
self._high1 = 0.0
self._high2 = 0.0
self._high3 = 0.0
self._high4 = 0.0
self._high5 = 0.0
self._low1 = 0.0
self._low2 = 0.0
self._low3 = 0.0
self._low4 = 0.0
self._low5 = 0.0
self._latest_fractal = None
self._previous_fractal = None
self._fractal_seed_count = 0
self._stop_price = None
self._take_profit_price = None
self._entry_price = 0.0
self._max_favorable_move = 0.0
self._last_exit_date = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def LotSplitter(self):
return self._lot_splitter.Value
@property
def MaxVolume(self):
return self._max_volume.Value
@property
def TakeProfitOffset(self):
return self._take_profit_offset.Value
@property
def TrailingStopOffset(self):
return self._trailing_stop_offset.Value
@property
def StopLossOffset(self):
return self._stop_loss_offset.Value
@property
def MinProfitOffset(self):
return self._min_profit_offset.Value
@property
def ProfitPointsOffset(self):
return self._profit_points_offset.Value
@property
def KPeriod(self):
return self._k_period.Value
@property
def DPeriod(self):
return self._d_period.Value
@property
def SlowingPeriod(self):
return self._slowing_period.Value
@property
def UseStochasticCondition(self):
return self._use_stochastic_condition.Value
@property
def UseFractalCondition(self):
return self._use_fractal_condition.Value
@property
def CloseOnOpposite(self):
return self._close_on_opposite.Value
def OnReseted(self):
super(cloudzs_trade2_strategy, self).OnReseted()
self._stochastic = None
self._previous_k = 0.0
self._previous_d = 0.0
self._last_k = 0.0
self._last_d = 0.0
self._has_previous = False
self._has_last = False
self._high1 = 0.0
self._high2 = 0.0
self._high3 = 0.0
self._high4 = 0.0
self._high5 = 0.0
self._low1 = 0.0
self._low2 = 0.0
self._low3 = 0.0
self._low4 = 0.0
self._low5 = 0.0
self._latest_fractal = None
self._previous_fractal = None
self._fractal_seed_count = 0
self._stop_price = None
self._take_profit_price = None
self._entry_price = 0.0
self._max_favorable_move = 0.0
self._last_exit_date = None
def OnStarted2(self, time):
super(cloudzs_trade2_strategy, self).OnStarted2(time)
self._stochastic = StochasticOscillator()
self._stochastic.K.Length = self.KPeriod
self._stochastic.D.Length = self.DPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._stochastic, self._process_candle).Start()
def _process_candle(self, candle, stochastic_value):
if candle.State != CandleStates.Finished:
return
self._update_fractals(candle)
stochastic_signal = self._evaluate_stochastic_signal(stochastic_value) \
if self.UseStochasticCondition else 0
fractal_signal = self._evaluate_fractal_signal() \
if self.UseFractalCondition else 0
combined_signal = 0
if stochastic_signal == 2 or fractal_signal == 2:
combined_signal = 2
elif stochastic_signal == 1 or fractal_signal == 1:
combined_signal = 1
self._manage_open_position(candle, combined_signal)
if self.Position != 0:
return
if self._last_exit_date is not None and self._last_exit_date == candle.OpenTime.Date:
return
if combined_signal == 0:
return
volume = self._calculate_order_volume(float(candle.ClosePrice))
if volume <= 0:
return
self.Volume = volume
if combined_signal == 1:
self.BuyMarket()
self._initialize_targets(float(candle.ClosePrice), True)
elif combined_signal == 2:
self.SellMarket()
self._initialize_targets(float(candle.ClosePrice), False)
def _evaluate_stochastic_signal(self, stochastic_value):
if self._stochastic is None:
return 0
k_raw = stochastic_value.K
d_raw = stochastic_value.D
if k_raw is None or d_raw is None:
return 0
current_k = float(k_raw)
current_d = float(d_raw)
if not self._has_last:
self._last_k = current_k
self._last_d = current_d
self._has_last = True
return 0
if not self._has_previous:
self._previous_k = self._last_k
self._previous_d = self._last_d
self._last_k = current_k
self._last_d = current_d
self._has_previous = True
return 0
sell_signal = self._last_d >= 80 and self._previous_d <= self._previous_k and self._last_d >= self._last_k
buy_signal = self._last_d <= 20 and self._previous_d >= self._previous_k and self._last_d <= self._last_k
self._previous_k = self._last_k
self._previous_d = self._last_d
self._last_k = current_k
self._last_d = current_d
if sell_signal:
return 2
if buy_signal:
return 1
return 0
def _update_fractals(self, candle):
self._high1 = self._high2
self._high2 = self._high3
self._high3 = self._high4
self._high4 = self._high5
self._high5 = float(candle.HighPrice)
self._low1 = self._low2
self._low2 = self._low3
self._low3 = self._low4
self._low4 = self._low5
self._low5 = float(candle.LowPrice)
if self._fractal_seed_count < 5:
self._fractal_seed_count += 1
return
up_fractal = self._high3 > self._high1 and self._high3 > self._high2 and \
self._high3 > self._high4 and self._high3 > self._high5
down_fractal = self._low3 < self._low1 and self._low3 < self._low2 and \
self._low3 < self._low4 and self._low3 < self._low5
if up_fractal:
self._register_fractal(self.FRACTAL_UP)
if down_fractal:
self._register_fractal(self.FRACTAL_DOWN)
def _register_fractal(self, fractal_type):
self._previous_fractal = self._latest_fractal
self._latest_fractal = fractal_type
def _evaluate_fractal_signal(self):
if self._latest_fractal is None or self._previous_fractal is None:
return 0
if self._latest_fractal == self.FRACTAL_UP and self._previous_fractal == self.FRACTAL_UP:
return 2 # sell
if self._latest_fractal == self.FRACTAL_DOWN and self._previous_fractal == self.FRACTAL_DOWN:
return 1 # buy
return 0
def _manage_open_position(self, candle, combined_signal):
if self.Position == 0:
return
if self.Position > 0:
self._manage_long_position(candle, combined_signal)
else:
self._manage_short_position(candle, combined_signal)
def _manage_long_position(self, candle, combined_signal):
self._update_trailing_stop(candle, True)
if self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket()
self._finalize_exit(candle)
return
if self._take_profit_price is not None and float(candle.HighPrice) >= self._take_profit_price:
self.SellMarket()
self._finalize_exit(candle)
return
current_gain = float(candle.ClosePrice) - self._entry_price
favorable = float(candle.HighPrice) - self._entry_price
if favorable > self._max_favorable_move:
self._max_favorable_move = favorable
pp_offset = float(self.ProfitPointsOffset)
mp_offset = float(self.MinProfitOffset)
if pp_offset > 0 and self._max_favorable_move >= pp_offset and current_gain <= mp_offset:
self.SellMarket()
self._finalize_exit(candle)
return
if self.CloseOnOpposite and combined_signal == 2:
self.SellMarket()
self._finalize_exit(candle)
def _manage_short_position(self, candle, combined_signal):
self._update_trailing_stop(candle, False)
if self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket()
self._finalize_exit(candle)
return
if self._take_profit_price is not None and float(candle.LowPrice) <= self._take_profit_price:
self.BuyMarket()
self._finalize_exit(candle)
return
current_gain = self._entry_price - float(candle.ClosePrice)
favorable = self._entry_price - float(candle.LowPrice)
if favorable > self._max_favorable_move:
self._max_favorable_move = favorable
pp_offset = float(self.ProfitPointsOffset)
mp_offset = float(self.MinProfitOffset)
if pp_offset > 0 and self._max_favorable_move >= pp_offset and current_gain <= mp_offset:
self.BuyMarket()
self._finalize_exit(candle)
return
if self.CloseOnOpposite and combined_signal == 1:
self.BuyMarket()
self._finalize_exit(candle)
def _update_trailing_stop(self, candle, is_long):
trailing = float(self.TrailingStopOffset)
if trailing <= 0:
return
if is_long:
potential_stop = float(candle.ClosePrice) - trailing
if self._stop_price is None or potential_stop > self._stop_price:
if potential_stop > self._entry_price:
self._stop_price = potential_stop
else:
potential_stop = float(candle.ClosePrice) + trailing
if self._stop_price is None or potential_stop < self._stop_price:
if potential_stop < self._entry_price:
self._stop_price = potential_stop
def _initialize_targets(self, entry_price, is_long):
self._entry_price = entry_price
self._max_favorable_move = 0.0
sl_offset = float(self.StopLossOffset)
tp_offset = float(self.TakeProfitOffset)
if sl_offset > 0:
self._stop_price = entry_price - sl_offset if is_long else entry_price + sl_offset
else:
self._stop_price = None
if tp_offset > 0:
self._take_profit_price = entry_price + tp_offset if is_long else entry_price - tp_offset
else:
self._take_profit_price = None
def _finalize_exit(self, candle):
self._stop_price = None
self._take_profit_price = None
self._entry_price = 0.0
self._max_favorable_move = 0.0
self._last_exit_date = candle.OpenTime.Date
def _calculate_order_volume(self, price):
if price <= 0:
return float(self.LotSplitter)
lot_splitter = float(self.LotSplitter)
estimated = lot_splitter
normalized = Math.Floor(estimated * 10.0) / 10.0
if normalized <= 0:
normalized = lot_splitter
max_vol = float(self.MaxVolume)
if max_vol > 0 and normalized > max_vol:
normalized = max_vol
return normalized
def CreateClone(self):
return cloudzs_trade2_strategy()