Cloudzs Trade 2 策略
概述
Cloudzs Trade 2 策略是 MetaTrader 4 专家顾问 cloudzs_trade_2 在 StockSharp 平台上的移植版本。原始策略通过组合两套条件——站在极值区的随机指标反转与连续两个同向分形——来决定进出场,同时使用主动的追踪止损机制保护浮动利润。本 C# 实现保持了原有的交易逻辑,并使用 StrategyParam 暴露所有参数,方便在 Designer 中优化或通过界面调整。
策略仅跟踪一组蜡烛数据(时间框架可配置),并独立计算以下两个信号来源:
- 随机指标反转:%D 线位于极值区(>=80 表示超买,<=20 表示超卖)并与上一根蜡烛的 %K 线交叉,从而触发做空或做多信号。
- 双分形确认:当市场连续出现两个向上分形(做空)或两个向下分形(做多)时触发信号。
当任意条件产生买入或卖出信号时,如果当前没有持仓且上一笔交易在不同日期平仓,则策略按该方向开仓。若已持仓并启用 CloseOnOpposite,当出现反向信号时也会提前平仓。
参数
| 参数 | 说明 | 默认值 |
|---|---|---|
LotSplitter |
根据账户价值估算下单量的系数。 | 0.1 |
MaxVolume |
下单量上限,0 表示不限制。 | 0 |
TakeProfitOffset |
以价格单位表示的固定止盈距离。 | 0 |
TrailingStopOffset |
追踪止损距离。 | 0.01 |
StopLossOffset |
固定止损距离。 | 0.05 |
MinProfitOffset |
当已实现的最大盈利达到 ProfitPointsOffset 后,回撤到该水平即平仓。 |
0 |
ProfitPointsOffset |
触发最小利润保护前所需的最小盈利。 | 0 |
%K Period / %D Period / Slowing |
随机指标周期设置。 | 8 / 8 / 4 |
Method |
来自 MT4 的平滑方法编号(仅用于记录,StockSharp 中不起作用)。 | 3 |
PriceMode |
MT4 的价格模式编号(仅用于记录)。 | 1 |
UseStochasticCondition |
是否启用随机指标条件。 | true |
UseFractalCondition |
是否启用分形条件。 | true |
CloseOnOpposite |
是否在出现反向信号时平仓。 | true |
CandleType |
计算所使用的蜡烛类型与时间框架。 | 15 分钟 |
交易规则
多头信号
- %D 线低于或等于 20,并在上一根蜡烛上向下穿越 %K 线;
- 或 连续出现两个向下分形;
- 当前无持仓,上一笔交易的平仓日期不同于当前蜡烛日期。
空头信号
- %D 线高于或等于 80,并在上一根蜡烛上向上穿越 %K 线;
- 或 连续出现两个向上分形;
- 当前无持仓,上一笔交易在不同日期结束。
离场规则
- 触发固定止损或止盈;
- 价格触及追踪止损;
- 在获得
ProfitPointsOffset的盈利后,如价格回撤至MinProfitOffset,立即平仓; - 启用
CloseOnOpposite时,反向信号会关闭现有仓位。
风险控制
- 止损与止盈距离按照原始 EA 的“点差”概念实现,即直接使用价格差。
- 追踪止损始终沿盈利方向移动,并基于收盘价更新。
- 交易量根据账户市值估算;若无法获取账户信息,则使用
LotSplitter默认值。
注意事项
- StockSharp 提供的随机指标只有一种平滑方式,因此
Method与PriceMode仅用于保留原策略信息,不会改变计算结果。 - 原策略按 tick 数据运行,为了符合 StockSharp 的最佳实践,移植版仅处理收盘后的蜡烛。
使用步骤
- 将策略加入 StockSharp 项目,并指定交易标的。
- 设置蜡烛时间框架以及随机指标、风险管理等参数。
- 根据交易品种的最小价格波动单位调整止损/止盈距离。
- 在 Designer、Runner 或 API 环境中启动策略,并关注日志了解信号和订单执行情况。
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()