Cloudzs Trade 2 Strategy
Overview
The Cloudzs Trade 2 Strategy is a StockSharp port of the MetaTrader 4 expert advisor cloudzs_trade_2. The original robot combines stochastic oscillator reversals with a double-fractal confirmation filter and uses aggressive trailing logic to protect open positions. This C# version recreates the signal flow and the trade management rules while exposing the parameters as StrategyParam objects so they can be optimised or adjusted from the StockSharp UI.
The strategy watches a single candle series (configurable timeframe) and evaluates two independent conditions:
- Stochastic reversal – triggers when the %D line leaves an extreme zone (>= 80 for sells, <= 20 for buys) while confirming that %D crossed the %K line on the previous candle, closely matching the original MQL logic.
- Double fractal confirmation – waits until two consecutive fractal signals of the same type appear (two upper fractals for sells or two lower fractals for buys).
If either condition generates a buy or sell request, the strategy enters in that direction (provided no trade is active and the previous exit was on a different day). When already in a trade, the same conditions can be used to exit early if CloseOnOpposite is enabled.
Parameters
| Name | Description | Default |
|---|---|---|
LotSplitter |
Coefficient used to approximate trade volume from the current account value. | 0.1 |
MaxVolume |
Upper bound for the calculated volume (0 disables the cap). | 0 |
TakeProfitOffset |
Fixed take-profit distance in absolute price units. | 0 |
TrailingStopOffset |
Trailing stop distance in price units. | 0.01 |
StopLossOffset |
Fixed stop-loss distance in price units. | 0.05 |
MinProfitOffset |
Minimum profit to keep after a favourable excursion once ProfitPointsOffset was reached. |
0 |
ProfitPointsOffset |
Required favourable move before MinProfitOffset is enforced. |
0 |
%K Period / %D Period / Slowing |
Stochastic oscillator configuration. | 8 / 8 / 4 |
Method |
Original MT4 stochastic method identifier (informational, not used because StockSharp exposes a single implementation). | 3 |
PriceMode |
Original MT4 price mode identifier (informational only). | 1 |
UseStochasticCondition |
Enable stochastic based signal generation. | true |
UseFractalCondition |
Enable fractal based signal generation. | true |
CloseOnOpposite |
Close the active position when the opposite signal appears. | true |
CandleType |
Timeframe/data type used for calculations. | 15-minute time frame |
Trading Signals
Long Entry
- %D line is below or equal to 20 and crosses below %K (matching the prior-candle comparison from MT4).
- OR two sequential lower fractals are detected.
- No open position and the last exit happened on a different calendar day.
Short Entry
- %D line is above or equal to 80 and crosses above %K.
- OR two sequential upper fractals appear.
- No open position and the last exit happened on a different calendar day.
Exit Rules
- Hard stop-loss or take-profit levels are reached (if configured).
- Trailing stop moves in the trade's favour and price touches the updated stop level.
- After the position experiences
ProfitPointsOffsetfavourable movement, a pullback toMinProfitOffsetcloses the trade. - Optional early reversal: if
CloseOnOppositeis true and the opposite signal fires, the trade is closed.
Risk Management
- Stop-loss and take-profit distances mimic the raw pip offsets from the MT4 code (interpreted here as price differences).
- Trailing stops are updated using the close price and only move in the profitable direction.
- The
LotSplitterparameter tries to follow the original volume formula, scaling the trade size by account value and limiting it withMaxVolume.
Notes and Limitations
- The StockSharp
StochasticOscillatorexposes a single smoothing implementation; therefore theMethodandPriceModeparameters are kept for reference but do not change the indicator behaviour. - The original MT4 script worked tick-by-tick. This port evaluates signals on finished candles to align with StockSharp best practices.
- Volume calculation relies on available portfolio values; if no account information exists, it falls back to the
LotSplittervalue.
Usage
- Add the strategy to your StockSharp project and select the instrument you want to trade.
- Configure the candle timeframe and adjust the stochastic/fractal settings if needed.
- Provide realistic stop-loss/take-profit offsets that match the instrument's tick size.
- Start the strategy in Designer, Runner, or via API and monitor the log messages for signal information.
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()