在 GitHub 上查看
Sample Check Pending Order 策略
Sample Check Pending Order 策略始终在市场上保持一张买入止损单和一张卖出止损单。原始的 MetaTrader 5 专家顾问由 Tungman 编写,会检查经纪商是否接受指定手数、确认双向都有足够的可用保证金,然后在当前买价和卖价附近挂出新的挂单,并设置一天的有效期。本实现把同样的流程迁移到 StockSharp,高度依赖高级别下单 API 和 Level 1 行情。
交易逻辑
- 行情处理
- 策略订阅 Level 1 数据流,缓存最新买一价与卖一价。
- 只有在获得双向报价并且
IsFormedAndOnlineAndAllowTrading() 返回 true 时才继续执行交易逻辑。
- 手数校验
- 每个行情更新都会根据
Security.MinVolume、Security.MaxVolume 与 Security.VolumeStep 校验参数 OrderVolume。
- 手数必须落在允许范围内并且是步长的整数倍,否则记录日志并阻止提交订单,复刻 MT5 辅助函数的行为。
- 保证金预检
- 在下单前估算多头和空头方向所需的保证金。计算使用最新的买卖价、合约乘数以及用户配置的
AccountLeverage。
- 如果当前或初始账户权益不足以覆盖任一方向,则跳过当前行情推送,与
CheckMoneyForTrade 的保护逻辑一致。
- 挂单管理
- 当没有活跃的买入止损单时,在当前卖价(四舍五入到最接近的最小报价单位)挂出新的买入止损单;卖出止损单则使用当前买价。
- 每张挂单都带有一个本地维护的到期时间(
ExpirationMinutes,默认一天)。后续行情会检查是否到期,到期后立即撤单并等待下一次行情重新补单。
- 风险控制
- 通过
StartProtection 在成交后自动放置绝对距离的止损与止盈,距离由 StopLossPoints 和 TakeProfitPoints 决定,等价于 MT5 里下单时设置的 SL/TP 参数。
这样就形成了一套极简的突破策略:市场始终被两张止损单“夹住”。当其中一张被触发,另一侧继续留在市场中,下一次行情刷新时会再次补齐缺失的一侧。
参数
| 参数 |
说明 |
OrderVolume |
每张止损单的手数,必须满足经纪商限制与手数步长。 |
StopLossPoints |
成交后止损距离(点数),会转换为绝对价格。 |
TakeProfitPoints |
成交后止盈距离(点数)。 |
ExpirationMinutes |
每张挂单的有效时间。到期后自动撤单并在下一次行情重建。 |
AccountLeverage |
估算保证金时使用的账户杠杆。 |
所有点数都会通过 Security.PriceStep 转换为真实的价格偏移。如果交易品种缺少价格步长或合约乘数,则使用 1 作为兜底值以确保公式可用。日志会提示任何异常配置,方便快速调整。
实现细节
- 订单生命周期:策略保存
BuyStop 与 SellStop 返回的 Order 对象,并在订单进入 Done、Canceled 或 Failed 状态时清理引用,避免把历史订单当成活动订单。
- 有效期控制:由于不同市场对止损单有效期的支持不一,这里采用本地计时方式,在超时后调用
CancelOrder 撤单。
- 保证金估算:使用账户权益与配置的杠杆进行粗略估算,既贴近
OrderCalcMargin 的效果,又不依赖特定交易所接口。
- 高级 API 使用:全部逻辑基于
SubscribeLevel1、BuyStop、SellStop 与 StartProtection 实现,符合转换指南并保持代码简洁。
本文档刻意提供尽可能详细的说明,便于读者理解转换过程并根据自身经纪商的要求调整参数。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Sample Check Pending Order strategy: CCI trend following.
/// Buys when CCI crosses above zero, sells when CCI crosses below zero.
/// </summary>
public class SampleCheckPendingOrderStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _period;
private readonly StrategyParam<decimal> _cciLevel;
private readonly StrategyParam<int> _signalCooldownCandles;
private decimal _prevCci;
private int _candlesSinceTrade;
private bool _hasPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int Period { get => _period.Value; set => _period.Value = value; }
public decimal CciLevel { get => _cciLevel.Value; set => _cciLevel.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public SampleCheckPendingOrderStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_period = Param(nameof(Period), 30)
.SetGreaterThanZero()
.SetDisplay("Period", "CCI period", "Indicators");
_cciLevel = Param(nameof(CciLevel), 100m)
.SetDisplay("CCI Level", "CCI crossover threshold", "Signals");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCci = 0;
_candlesSinceTrade = SignalCooldownCandles;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCci = 0;
_candlesSinceTrade = SignalCooldownCandles;
_hasPrev = false;
var cci = new CommodityChannelIndex { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(cci, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
if (_hasPrev)
{
if (_prevCci <= -CciLevel && cciValue > -CciLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (_prevCci >= CciLevel && cciValue < CciLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
_prevCci = cciValue;
_hasPrev = true;
}
}
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 CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class sample_check_pending_order_strategy(Strategy):
def __init__(self):
super(sample_check_pending_order_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._period = self.Param("Period", 30)
self._cci_level = self.Param("CciLevel", 100.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 4)
self._prev_cci = 0.0
self._candles_since_trade = 4
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def Period(self):
return self._period.Value
@Period.setter
def Period(self, value):
self._period.Value = value
@property
def CciLevel(self):
return self._cci_level.Value
@CciLevel.setter
def CciLevel(self, value):
self._cci_level.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(sample_check_pending_order_strategy, self).OnReseted()
self._prev_cci = 0.0
self._candles_since_trade = self.SignalCooldownCandles
self._has_prev = False
def OnStarted2(self, time):
super(sample_check_pending_order_strategy, self).OnStarted2(time)
self._prev_cci = 0.0
self._candles_since_trade = self.SignalCooldownCandles
self._has_prev = False
cci = CommodityChannelIndex()
cci.Length = self.Period
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cci, self._process_candle).Start()
def _process_candle(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
cci_val = float(cci_value)
if self._has_prev:
if self._prev_cci <= -self.CciLevel and cci_val > -self.CciLevel and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif self._prev_cci >= self.CciLevel and cci_val < self.CciLevel and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
self._prev_cci = cci_val
self._has_prev = True
def CreateClone(self):
return sample_check_pending_order_strategy()