Стратегия представляет собой портирование эксперта MQL5 «cheduecoglioni» на платформу StockSharp. Алгоритм постоянно поддерживает присутствие в рынке, чередуя короткие и длинные позиции. Каждая сделка сопровождается фиксированными уровнями тейк-профита и стоп-лосса, задаваемыми в пунктах и преобразуемыми в абсолютное расстояние с учётом точности инструмента.
Торговые правила
Стратегия подписывается на выбранную серию свечей (по умолчанию 1 минута) и выполняет логику только после полного закрытия свечи, что заменяет тиковый цикл оригинального эксперта.
При отсутствии позиции и заявок, ожидающих исполнения, отправляется рыночный ордер в направлении, хранящемся в состоянии _nextSide. Первая сделка после запуска — продажа, как и в MQL5 версии.
После открытия позиции алгоритм ждёт её завершения защитными ордерами или вручную. Как только позиция снова становится нулевой, направление для следующего входа переключается на противоположное.
Функция StartProtection автоматически добавляет уровни стоп-лосса и тейк-профита, чтобы каждая сделка имела заданные параметры риска и прибыли.
Параметры
Trade Volume – объём одной рыночной сделки, аналог параметра InpLots в исходнике.
Take Profit (pips) – расстояние до тейк-профита в пунктах, переводится в цену на основе рассчитанного размера пункта.
Stop Loss (pips) – расстояние до стоп-лосса, преобразуется тем же способом.
Candle Type – тип свечей, которые управляют торговым циклом. Можно указать любой поддерживаемый DataType.
Особенности реализации
Размер пункта берётся из Security.PriceStep. Для инструментов с 3 или 5 знаками после запятой добавляется множитель 10, чтобы перейти от дробного пункта к стандартному, как в оригинальном эксперте.
Флаг ожидания предотвращает повторную отправку рыночных ордеров, пока предыдущий не исполнен. В случае отказа брокера метод OnOrderFailed очищает флаг, и следующий бар сможет повторить попытку.
Метод OnPositionChanged отслеживает направление открытой позиции и переключает _nextSide после выхода в ноль, воспроизводя поведение MQL5-советника.
Защитные ордера управляются StartProtection с использованием рыночных выходов, что соответствует моментальной установке стоп-лосса и тейк-профита при выставлении заявки в исходном коде.
Примечания
Python-версия намеренно не создавалась.
Тесты проекта не изменялись.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Alternates buy and sell market orders with fixed stop loss and take profit distances.
/// </summary>
public class CheduecoglioniAlternatingStrategy : Strategy
{
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<DataType> _candleType;
private decimal _pipSize;
private Sides _nextSide;
private Sides? _activeSide;
/// <summary>
/// Initializes a new instance of the <see cref="CheduecoglioniAlternatingStrategy"/> class.
/// </summary>
public CheduecoglioniAlternatingStrategy()
{
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetDisplay("Trade Volume", "Volume per trade", "General")
.SetGreaterThanZero();
_takeProfitPips = Param(nameof(TakeProfitPips), 10m)
.SetDisplay("Take Profit (pips)", "Distance to take profit", "Risk")
.SetGreaterThanZero();
_stopLossPips = Param(nameof(StopLossPips), 10m)
.SetDisplay("Stop Loss (pips)", "Distance to stop loss", "Risk")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Source candles for timing", "General");
}
/// <summary>
/// Volume used for each market order.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Take profit distance expressed in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Stop loss distance expressed in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Candle type that triggers trading decisions.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
Volume = TradeVolume;
_nextSide = Sides.Sell;
_activeSide = null;
_pipSize = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = TradeVolume; // Align the base volume with the strategy parameter.
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep <= 0m)
{
var decimals = Security?.Decimals ?? 4;
priceStep = (decimal)Math.Pow(10, -decimals);
}
_pipSize = priceStep;
var secDecimals = Security?.Decimals;
if (secDecimals is int digits && (digits == 3 || digits == 5))
{
_pipSize *= 10m; // Convert from fractional pip to full pip for FX symbols.
}
if (_pipSize <= 0m)
{
_pipSize = 1m; // Fallback to a neutral value if the instrument metadata is missing.
}
StartProtection(
takeProfit: new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute),
stopLoss: new Unit(StopLossPips * _pipSize, UnitTypes.Absolute),
useMarketOrders: true);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Strategy has no bound indicators, always allow trading.
if (Position != 0)
return; // Skip if a position exists.
var volume = TradeVolume;
if (volume <= 0m)
return;
if (_nextSide == Sides.Buy)
BuyMarket();
else
SellMarket();
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
if (Position > 0)
{
_activeSide = Sides.Buy;
return;
}
if (Position < 0)
{
_activeSide = Sides.Sell;
return;
}
if (_activeSide.HasValue)
{
_nextSide = _activeSide == Sides.Buy ? Sides.Sell : Sides.Buy; // Alternate direction after a flat position.
_activeSide = null;
}
}
}
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, Unit, UnitTypes, Sides
from StockSharp.Algo.Strategies import Strategy
class cheduecoglioni_alternating_strategy(Strategy):
"""
Alternates buy and sell market orders with fixed stop loss and take profit distances.
"""
def __init__(self):
super(cheduecoglioni_alternating_strategy, self).__init__()
self._take_profit_pips = self.Param("TakeProfitPips", 10.0) \
.SetDisplay("Take Profit (pips)", "Distance to take profit", "Risk")
self._stop_loss_pips = self.Param("StopLossPips", 10.0) \
.SetDisplay("Stop Loss (pips)", "Distance to stop loss", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Source candles for timing", "General")
self._pip_size = 0.0
self._next_side = Sides.Sell
self._active_side = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(cheduecoglioni_alternating_strategy, self).OnReseted()
self._next_side = Sides.Sell
self._active_side = None
self._pip_size = 0.0
def OnStarted2(self, time):
super(cheduecoglioni_alternating_strategy, self).OnStarted2(time)
price_step = 1.0
if self.Security is not None and self.Security.PriceStep is not None and float(self.Security.PriceStep) > 0:
price_step = float(self.Security.PriceStep)
else:
decimals = 4
if self.Security is not None and self.Security.Decimals is not None:
decimals = int(self.Security.Decimals)
price_step = 10.0 ** (-decimals)
self._pip_size = price_step
if self.Security is not None and self.Security.Decimals is not None:
digits = int(self.Security.Decimals)
if digits == 3 or digits == 5:
self._pip_size *= 10.0
if self._pip_size <= 0:
self._pip_size = 1.0
self.StartProtection(
takeProfit=Unit(self._take_profit_pips.Value * self._pip_size, UnitTypes.Absolute),
stopLoss=Unit(self._stop_loss_pips.Value * self._pip_size, UnitTypes.Absolute),
useMarketOrders=True)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.on_process).Start()
def on_process(self, candle):
if candle.State != CandleStates.Finished:
return
if self.Position != 0:
return
if self._next_side == Sides.Buy:
self.BuyMarket()
else:
self.SellMarket()
def OnPositionReceived(self, position):
super(cheduecoglioni_alternating_strategy, self).OnPositionReceived(position)
if self.Position > 0:
self._active_side = Sides.Buy
return
if self.Position < 0:
self._active_side = Sides.Sell
return
if self._active_side is not None:
if self._active_side == Sides.Buy:
self._next_side = Sides.Sell
else:
self._next_side = Sides.Buy
self._active_side = None
def CreateClone(self):
return cheduecoglioni_alternating_strategy()