Главная
/
Примеры стратегий
Открыть на GitHub
OpenPendingorderAfterPositionGetStopLoss
Обзор
Стратегия OpenPendingorderAfterPositionGetStopLoss переносит одноимённого советника MetaTrader 5 на высокоуровневый API StockSharp. На каждом закрытом баре вычисляется наклон линии Stochastic %K выбранного таймфрейма. Если %K снижается, ниже рынка размещается sell stop, если растёт — выше рынка появляется buy stop. После срабатывания любой заявки сразу выставляются защитные стоп-лосс и тейк-профит. Когда позиция закрывается по стоп-лоссу, соответствующий отложенный ордер автоматически восстанавливается, поэтому сетка пробойных входов поддерживается без ожидания следующей свечи.
Торговые правила
Подписка на закрытые свечи заданного таймфрейма и вычисление стандартного стохастика (KPeriod, DPeriod, Slowing).
Сравнение текущего значения %K со значением два бара назад:
%K(current) < %K(two bars ago) → выставить sell stop ниже лучшего bid.
%K(current) > %K(two bars ago) → выставить buy stop выше лучшего ask.
Отложенные ордера смещаются от рынка на сумму текущего спреда и буфера MinStopDistancePoints, как в оригинальном MQL-скрипте.
После активации заявки стратегия отправляет защитный стоп-ордер и при необходимости лимит на фиксацию прибыли.
Если позиция закрылась по стоп-лоссу, соответствующий отложенный ордер немедленно создаётся заново по актуальным ценам.
Защитные ордера автоматически снимаются при выполнении тейк-профита или остановке стратегии.
Параметры
Имя
Описание
OrderVolume
Объём сделки (в лотах) для каждого отложенного ордера.
StopLossPoints
Дистанция стоп-лосса в пунктах инструмента. 0 — не использовать.
TakeProfitPoints
Дистанция тейк-профита в пунктах инструмента. 0 — не использовать.
MinStopDistancePoints
Минимальный буфер (в пунктах), добавляемый к спреду при выставлении ордера.
MaxPositions
Максимальное количество одновременно открытых позиций в одном направлении (для неттинга фактически 0 или 1).
KPeriod
Число баров для расчёта %K.
DPeriod
Длина сглаживающей линии %D.
Slowing
Дополнительное сглаживание %K перед сравнением.
PendingExpiry
Срок жизни отложенных заявок. Просроченные ордера снимаются на следующей свече.
CandleType
Таймфрейм свечей, используемый для расчётов.
Особенности реализации
Управление заявками построено на высокоуровневых методах BuyStop, SellStop, SellLimit, BuyLimit в соответствии с требованиями AGENTS.md.
Значения индикатора обрабатываются напрямую в обработчике SubscribeCandles().BindEx(...), без вызовов GetValue.
Обработчик OnOwnTradeReceived отвечает за установку и снятие защитных ордеров, повторяя логику OnTradeTransaction из оригинального эксперта.
Все комментарии в коде приведены на английском языке, а отступы выполнены табуляцией, как предписано правилами репозитория.
namespace StockSharp.Samples.Strategies;
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Trades based on the slope of the Stochastic %K line.
/// Buys when %K is rising, sells when %K is falling.
/// Uses take-profit and stop-loss protection.
/// </summary>
public class OpenPendingorderAfterPositionGetStopLossStrategy : Strategy
{
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<int> _slowing;
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<decimal> _takeProfitPct;
private readonly StrategyParam<int> _signalCooldownCandles;
private readonly StrategyParam<DataType> _candleType;
private decimal? _lastK;
private decimal? _prevK;
private decimal _entryPrice;
private int _candlesSinceTrade;
public int KPeriod
{
get => _kPeriod.Value;
set => _kPeriod.Value = value;
}
public int DPeriod
{
get => _dPeriod.Value;
set => _dPeriod.Value = value;
}
public int Slowing
{
get => _slowing.Value;
set => _slowing.Value = value;
}
public decimal StopLossPct
{
get => _stopLossPct.Value;
set => _stopLossPct.Value = value;
}
public decimal TakeProfitPct
{
get => _takeProfitPct.Value;
set => _takeProfitPct.Value = value;
}
public int SignalCooldownCandles
{
get => _signalCooldownCandles.Value;
set => _signalCooldownCandles.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public OpenPendingorderAfterPositionGetStopLossStrategy()
{
_kPeriod = Param(nameof(KPeriod), 22)
.SetDisplay("%K Period", "Number of bars for %K", "Indicators");
_dPeriod = Param(nameof(DPeriod), 7)
.SetDisplay("%D Period", "Smoothing period for %K", "Indicators");
_slowing = Param(nameof(Slowing), 2)
.SetDisplay("Slowing", "Additional smoothing factor", "Indicators");
_stopLossPct = Param(nameof(StopLossPct), 2m)
.SetDisplay("Stop Loss %", "Stop-loss as percentage of entry price", "Risk");
_takeProfitPct = Param(nameof(TakeProfitPct), 3m)
.SetDisplay("Take Profit %", "Take-profit as percentage of entry price", "Risk");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for indicator", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastK = null;
_prevK = null;
_entryPrice = 0;
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_lastK = null;
_prevK = null;
_entryPrice = 0;
_candlesSinceTrade = SignalCooldownCandles;
var rsi = new RelativeStrengthIndex { Length = KPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal currentK)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
// Check stop-loss / take-profit on existing position
if (Position != 0 && _entryPrice > 0)
{
if (Position > 0)
{
var pnlPct = (close - _entryPrice) / _entryPrice * 100m;
if (pnlPct <= -StopLossPct || pnlPct >= TakeProfitPct)
{
SellMarket();
_entryPrice = 0;
_prevK = currentK;
_lastK = currentK;
return;
}
}
else if (Position < 0)
{
var pnlPct = (_entryPrice - close) / _entryPrice * 100m;
if (pnlPct <= -StopLossPct || pnlPct >= TakeProfitPct)
{
BuyMarket();
_entryPrice = 0;
_prevK = currentK;
_lastK = currentK;
return;
}
}
}
// Need at least 2 values to determine signal transition.
if (_lastK is not decimal prevK)
{
_lastK = currentK;
return;
}
_prevK = _lastK;
_lastK = currentK;
var crossedUp = prevK <= 45m && currentK > 45m;
var crossedDown = prevK >= 55m && currentK < 55m;
if (crossedUp && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_entryPrice = close;
_candlesSinceTrade = 0;
}
else if (crossedDown && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_entryPrice = close;
_candlesSinceTrade = 0;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class open_pendingorder_after_position_get_stop_loss_strategy(Strategy):
def __init__(self):
super(open_pendingorder_after_position_get_stop_loss_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._k_period = self.Param("KPeriod", 22)
self._stop_loss_pct = self.Param("StopLossPct", 2.0)
self._take_profit_pct = self.Param("TakeProfitPct", 3.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 4)
self._last_k = None
self._entry_price = 0.0
self._candles_since_trade = 4
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def KPeriod(self):
return self._k_period.Value
@KPeriod.setter
def KPeriod(self, value):
self._k_period.Value = value
@property
def StopLossPct(self):
return self._stop_loss_pct.Value
@StopLossPct.setter
def StopLossPct(self, value):
self._stop_loss_pct.Value = value
@property
def TakeProfitPct(self):
return self._take_profit_pct.Value
@TakeProfitPct.setter
def TakeProfitPct(self, value):
self._take_profit_pct.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(open_pendingorder_after_position_get_stop_loss_strategy, self).OnReseted()
self._last_k = None
self._entry_price = 0.0
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(open_pendingorder_after_position_get_stop_loss_strategy, self).OnStarted2(time)
self._last_k = None
self._entry_price = 0.0
self._candles_since_trade = self.SignalCooldownCandles
rsi = RelativeStrengthIndex()
rsi.Length = self.KPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self._process_candle).Start()
def _process_candle(self, candle, current_k):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
k_val = float(current_k)
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
# Check stop-loss / take-profit on existing position
if self.Position != 0 and self._entry_price > 0:
if self.Position > 0:
pnl_pct = (close - self._entry_price) / self._entry_price * 100.0
if pnl_pct <= -float(self.StopLossPct) or pnl_pct >= float(self.TakeProfitPct):
self.SellMarket()
self._entry_price = 0.0
self._last_k = k_val
return
elif self.Position < 0:
pnl_pct = (self._entry_price - close) / self._entry_price * 100.0
if pnl_pct <= -float(self.StopLossPct) or pnl_pct >= float(self.TakeProfitPct):
self.BuyMarket()
self._entry_price = 0.0
self._last_k = k_val
return
if self._last_k is None:
self._last_k = k_val
return
prev_k = self._last_k
self._last_k = k_val
crossed_up = prev_k <= 45.0 and k_val > 45.0
crossed_down = prev_k >= 55.0 and k_val < 55.0
if crossed_up and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._entry_price = close
self._candles_since_trade = 0
elif crossed_down and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._entry_price = close
self._candles_since_trade = 0
def CreateClone(self):
return open_pendingorder_after_position_get_stop_loss_strategy()