在 GitHub 上查看
Two Pending Orders 2
概览
本策略是 MetaTrader 智能交易系统 “Two pending orders 2” 的 StockSharp 版本。策略始终在市场价格的上下各保持一个对称的挂单,并在首个成交方向上按照配置的止损、止盈与移动止损规则管理仓位。转换完全基于 StockSharp 的高级 API,实现原策略的核心思想,同时保留所有关键参数以便调优。
交易逻辑
- 策略订阅所选周期的K线(默认使用日线)。每当一根K线收盘,就触发新的决策周期。
- 所有过期的挂单会被取消;在下达新挂单前也会先撤销旧单,确保市场中仅保留最新的价格水平。
- 如果当前点差处于允许范围内,且激活的仓位/挂单数量尚未达到上限,则根据模式放置两笔对称挂单:
- Stop 模式(默认)在现价上方放置买入止损单,在现价下方放置卖出止损单。
- Limit 模式 在现价下方放置买入限价单,在现价上方放置卖出限价单。
- 开启 Reverse Levels 选项会互换挂单方向,对应原EA的“反向”开关。
- 挂单价格在当前买/卖价基础上偏移 Pending Indent 参数指定的点数。当价格距离现有仓位小于 Min Step 时,挂单会被跳过。
- 挂单可以设置过期时间,超过指定分钟数后会被自动撤销。
仓位管理
- 挂单成交后,策略会跟踪对应方向的平均入场价与持仓量。反向成交会优先减仓或平仓,再视情况开新仓。
- 多头仓位在以下任一条件满足时平仓:
- 价格下破平均入场价减去止损距离。
- 价格上破平均入场价加上止盈距离。
- 盈利超过移动止损激活阈值后,若价格回落触发移动止损(按设定步长移动)。
- 空头仓位使用完全对称的价格比较规则。
- 启用 Only One Position 后,策略会等待当前仓位完全平仓后才重新挂单。
参数说明
| 名称 |
说明 |
StopLossPoints |
止损距离(点)。设为 0 表示不使用止损。 |
TakeProfitPoints |
止盈距离(点)。设为 0 表示不使用止盈。 |
MaxPositions |
同时允许存在的仓位与挂单总数上限。 |
MinStepPoints |
新挂单与当前仓位入场价之间要求的最小距离。 |
TrailingActivatePoints |
移动止损的激活阈值(点)。设为 0 表示关闭移动止损。 |
TrailingStopPoints |
移动止损激活后与当前价格之间的距离。 |
TrailingStepPoints |
每次调整移动止损所需的最小价格改善幅度。 |
TradeMode |
允许的交易方向:Buy(仅多头)、Sell(仅空头)或 BuySell(双向)。 |
PendingType |
挂单类型:Stop 或 Limit。 |
PendingExpirationMinutes |
挂单有效期(分钟)。设为 0 表示不设置过期时间。 |
PendingIndentPoints |
计算挂单价格时使用的点差偏移。 |
PendingMaxSpreadPoints |
允许的最大买卖价差(点)。设为 0 表示关闭过滤。 |
OnlyOnePosition |
为 true 时限制同一时间仅持有一个方向的仓位。 |
ReverseLevels |
反转挂单方向,对应原EA的反向模式。 |
CandleType |
用于触发信号评估的K线周期(默认日线)。 |
备注
- 所有距离参数均以“点”为单位,并根据标的的最小价位(tick size)自动换算为价格。
- 策略使用 StockSharp 的
BuyStop、SellStop、BuyLimit、SellLimit 等高阶方法下单,并在每次重新决策前调用 CancelActiveOrders 撤销旧挂单。
- 移动止损逻辑在每根完成的K线上评估。若需要更细粒度的移动止损,可选择更短周期的
CandleType。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "Two pending orders 2" MetaTrader expert.
/// Places symmetric breakout levels around recent range and enters on breakout.
/// Uses high/low of N bars as breakout boundaries.
/// </summary>
public class TwoPendingOrders2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _lookback;
private readonly Queue<decimal> _highs = new();
private readonly Queue<decimal> _lows = new();
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int Lookback
{
get => _lookback.Value;
set => _lookback.Value = value;
}
public TwoPendingOrders2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for breakout detection", "General");
_lookback = Param(nameof(Lookback), 10)
.SetGreaterThanZero()
.SetDisplay("Lookback", "Number of bars for high/low range", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highs.Clear();
_lows.Clear();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_highs.Count < Lookback)
{
EnqueueCandle(candle);
return;
}
decimal highest = decimal.MinValue;
decimal lowest = decimal.MaxValue;
var highs = _highs.ToArray();
var lows = _lows.ToArray();
foreach (var h in highs)
if (h > highest) highest = h;
foreach (var l in lows)
if (l < lowest) lowest = l;
var close = candle.ClosePrice;
var volume = Volume;
if (volume <= 0)
volume = 1;
var range = highest - lowest;
var breakoutPadding = range * 0.05m;
// Breakout above range
if (close > highest + breakoutPadding)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
// Breakout below range
else if (close < lowest - breakoutPadding)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
EnqueueCandle(candle);
}
private void EnqueueCandle(ICandleMessage candle)
{
_highs.Enqueue(candle.HighPrice);
_lows.Enqueue(candle.LowPrice);
if (_highs.Count > Lookback)
{
_highs.Dequeue();
_lows.Dequeue();
}
}
/// <inheritdoc />
protected override void OnReseted()
{
_highs.Clear();
_lows.Clear();
base.OnReseted();
}
}
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.Strategies import Strategy
class two_pending_orders2_strategy(Strategy):
def __init__(self):
super(two_pending_orders2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._lookback = self.Param("Lookback", 10)
self._highs = []
self._lows = []
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def Lookback(self):
return self._lookback.Value
@Lookback.setter
def Lookback(self, value):
self._lookback.Value = value
def OnReseted(self):
super(two_pending_orders2_strategy, self).OnReseted()
self._highs = []
self._lows = []
def OnStarted2(self, time):
super(two_pending_orders2_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
lookback = self.Lookback
if len(self._highs) < lookback:
self._enqueue_candle(candle)
return
highest = max(self._highs)
lowest = min(self._lows)
close = float(candle.ClosePrice)
range_val = highest - lowest
breakout_padding = range_val * 0.05
if close > highest + breakout_padding:
if self.Position <= 0:
self.BuyMarket()
elif close < lowest - breakout_padding:
if self.Position >= 0:
self.SellMarket()
self._enqueue_candle(candle)
def _enqueue_candle(self, candle):
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
lookback = self.Lookback
while len(self._highs) > lookback:
self._highs.pop(0)
self._lows.pop(0)
def CreateClone(self):
return two_pending_orders2_strategy()