OpenPendingorderAfterPositionGetStopLoss
概览
OpenPendingorderAfterPositionGetStopLoss 策略将同名的 MetaTrader 5 专家顾问移植到 StockSharp 的高级 API。策略在所选时间框架的每根收盘 K 线上计算 Stochastic 指标的 %K 斜率:当 %K 向下倾斜时,在市场价下方放置 sell stop;当 %K 向上倾斜时,在市场价上方放置 buy stop。每当挂单成交,都会立即生成对应的止损和止盈保护单。如果仓位因为止损而关闭,策略会立刻重新挂出相同方向的待触发订单,从而维持原有的突破网格结构。
交易逻辑
- 订阅所选时间框架的收盘蜡烛并计算经典的随机指标(
KPeriod、DPeriod、Slowing)。 - 将当前 %K 值与两根蜡烛之前的值比较:
%K(current) < %K(two bars ago)→ 在最佳买价下方提交 sell stop。%K(current) > %K(two bars ago)→ 在最佳卖价上方提交 buy stop。
- 挂单价格在实时点差的基础上再加上
MinStopDistancePoints指定的缓冲,与原始 MQL 策略保持一致。 - 挂单成交后,策略会发送止损 stop 单和可选的止盈 limit 单。
- 当止损触发并关闭仓位时,会立即根据最新行情重新放置相同方向的挂单。
- 当止盈成交或策略停止时,所有保护性订单都会被自动撤销。
参数
| 名称 | 说明 |
|---|---|
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事件安装和撤销保护性订单,从而复现原始 EA 中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()