Условное открытие позиций полностью повторяет логику оригинального скрипта MetaTrader «Open a buy position if there's no open position». Если соответствующий переключатель активирован, стратегия отправляет рыночную заявку только при отсутствии позиции в выбранном направлении. Это исключает дублирующие входы и удерживает позицию в соответствии с заданным флагом.
Порт на StockSharp использует высокоуровневую подписку на свечи и встроенный механизм защиты, поэтому остается независимым от конкретного брокера. Расстояния до стоп-лосса и тейк-профита задаются в пунктах (шаги цены), что позволяет применять стратегию к любому инструменту.
Логика стратегии
Подписаться на выбранный тип свечей и использовать его как регулярный триггер.
После закрытия каждой свечи проверить текущую чистую позицию.
Если переключатель на покупку включен и позиция отсутствует или короткая, отправить рыночную заявку на покупку.
Если переключатель на продажу включен и позиция отсутствует или длинная, отправить рыночную заявку на продажу.
Вызов StartProtection автоматически формирует защитные заявки и переводит пунктовые расстояния в реальные ценовые смещения.
Поскольку StockSharp оперирует чистыми позициями, одновременное включение обеих сторон сначала попытается открыть длинную сделку, а затем — при отсутствии позиции после исполнения — короткую. Это соответствует идее MQL-скрипта, не допускавшего нескольких ордеров в одном направлении.
Параметры
Название
Значение по умолчанию
Описание
Volume
1
Объем каждой рыночной заявки.
StopLossPips
100
Расстояние до стоп-лосса в шагах цены; 0 отключает уровень.
TakeProfitPips
200
Расстояние до тейк-профита в шагах цены; 0 отключает уровень.
EnableBuy
false
При true стратегия может открыть длинную позицию, если нет длинного экспонирования.
EnableSell
false
При true стратегия может открыть короткую позицию, если нет короткого экспонирования.
CandleType
таймфрейм 1 минута
Серия свечей, определяющая момент проверки.
Примечания
Расстояния конвертируются в абсолютные цены с помощью PriceStep. При отсутствии данных по шагу используется исходное значение пунктов.
StartProtection автоматически выставляет защитные заявки после каждой сделки, поэтому дополнительное управление ордерами не требуется.
Стратегия предназначена для параметрического ручного запуска и может служить шаблоном для дискретионной торговли.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "Conditional Position Opener" MetaTrader expert.
/// Uses Momentum indicator to conditionally open long or short positions.
/// Opens long when momentum is positive, short when negative.
/// </summary>
public class ConditionalPositionOpenerStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _momentumPeriod;
private Momentum _momentum;
private decimal? _prevMomentum;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int MomentumPeriod
{
get => _momentumPeriod.Value;
set => _momentumPeriod.Value = value;
}
public ConditionalPositionOpenerStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signal generation", "General");
_momentumPeriod = Param(nameof(MomentumPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Momentum Period", "Momentum indicator period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMomentum = null;
_momentum = new Momentum { Length = MomentumPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_momentum, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal momentumValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_momentum.IsFormed)
{
_prevMomentum = momentumValue;
return;
}
if (_prevMomentum is null)
{
_prevMomentum = momentumValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// Cross above 101 (positive momentum)
var crossUp = _prevMomentum.Value <= 101m && momentumValue > 101m;
// Cross below 99 (negative momentum)
var crossDown = _prevMomentum.Value >= 99m && momentumValue < 99m;
if (crossUp)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (crossDown)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevMomentum = momentumValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_momentum = null;
_prevMomentum = null;
base.OnReseted();
}
}