首页
/
策略示例
在 GitHub 上查看
Send Close Order 策略
Send Close Order 是 2009 年由 Vladimir Hlystov 编写的 MetaTrader 4 专家顾问 “SendCloseOrder” 的移植版本。原始脚本基于比尔·威廉姆斯分形在图表上绘制四条趋势线,只要价格触及这些预测水平就会开仓或平仓。StockSharp 版本保留了决策流程,并自动维护这些线条,可用于任何蜡烛序列。
交易逻辑
分形识别 —— 每根收盘蜡烛都会推动一个包含五根蜡烛的滑动窗口。当窗口填满后,会按照比尔·威廉姆斯的条件检查中间那根蜡烛,并按时间顺序保存确认的高点和低点。
趋势线重建
Sell line 连接最近的两个上行分形,它们之间存在一个下行分形,形成阻力线。
Close #1 为 Sell line 向上平移 15 个价格步长(15 × Security.PriceStep),作为多头离场轨道。
Buy line 连接最近的两个下行分形,它们之间存在一个上行分形,形成支撑线。
Close #2 为 Buy line 向下平移 15 个价格步长,作为空头离场轨道。
信号评估 —— 将四条线外推到当前收盘蜡烛的时间戳。如果预测价格落在蜡烛的最高价/最低价区间内(容差为两个价格步长),就会触发对应操作。
订单管理
触及 Close #1 或 Close #2 时调用 ClosePosition() 立即平掉全部仓位。
触及 Sell 或 Buy 线时,以 TradeVolume 的数量发送市价单,只要结果仓位的绝对值不超过 MaxOrders × TradeVolume。如果存在反向仓位,系统会先对冲再叠加新的方向,模拟 MetaTrader 的对冲模式。
参数
名称
默认值
说明
EnableSellLine
true
当价格触及阻力线时允许交易。
EnableBuyLine
true
当价格触及支撑线时允许交易。
EnableCloseLongLine
true
多头在上移的阻力线(Close #1)上止盈。
EnableCloseShortLine
true
空头在下移的支撑线(Close #2)上止盈。
MaxOrders
1
单方向允许叠加的最大入场次数。
TradeVolume
0.1
每次市价单的下单量。
CandleType
1 小时
用于分形计算的蜡烛周期。
StockSharp 端每当出现新分形都会自动重新计算四条线;在 MetaTrader 中需要手工删除并重画。
策略在净头寸模型下运行,默认不支持同时持有多空两个篮子。
触发检测使用收盘蜡烛的最高价和最低价,并额外允许两个价格步长的容差,而不是依赖逐笔的 Bid/Ask 报价。
不再创建图形对象(趋势线和文字),专注于交易信号。
使用说明
该策略适用于任何提供蜡烛数据且 PriceStep 有效的品种;若交易所未提供步长,则使用 0.0001 作为回退值。
调高 MaxOrders 可以模拟原版 EA 的加仓行为,设置 TradeVolume 时请考虑品种的最小交易单位。
线条偏移固定为 15 个点,如需修改 MetaTrader 中的参数请同步调整源代码。
当前仅提供 C# 实现,如需 Python 版本可在后续添加。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// SendCloseOrder: Fractal high/low breakout with EMA filter and ATR stops.
/// </summary>
public class SendCloseOrderStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _entryPrice;
private decimal _fractalHigh;
private decimal _fractalLow;
private decimal _prev2High;
private decimal _prev1High;
private decimal _prev2Low;
private decimal _prev1Low;
private int _barCount;
public SendCloseOrderStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 50)
.SetDisplay("EMA Length", "Trend filter.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_fractalHigh = 0;
_fractalLow = 0;
_prev2High = 0;
_prev1High = 0;
_prev2Low = 0;
_prev1Low = 0;
_barCount = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_fractalHigh = 0;
_fractalLow = 0;
_prev2High = 0;
_prev1High = 0;
_prev2Low = 0;
_prev1Low = 0;
_barCount = 0;
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
_barCount++;
var high = candle.HighPrice;
var low = candle.LowPrice;
var close = candle.ClosePrice;
// Detect fractal high: prev1High > prev2High and prev1High > current high
if (_barCount > 3 && _prev1High > _prev2High && _prev1High > high)
_fractalHigh = _prev1High;
// Detect fractal low: prev1Low < prev2Low and prev1Low < current low
if (_barCount > 3 && _prev1Low < _prev2Low && _prev1Low < low)
_fractalLow = _prev1Low;
_prev2High = _prev1High;
_prev1High = high;
_prev2Low = _prev1Low;
_prev1Low = low;
if (_fractalHigh == 0 || _fractalLow == 0 || atrVal <= 0)
return;
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 1.5m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 1.5m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (close > _fractalHigh && close > emaVal)
{
_entryPrice = close;
BuyMarket();
}
else if (close < _fractalLow && close < emaVal)
{
_entryPrice = close;
SellMarket();
}
}
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class send_close_order_strategy(Strategy):
def __init__(self):
super(send_close_order_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._ema_length = self.Param("EmaLength", 50) \
.SetDisplay("EMA Length", "Trend filter.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._entry_price = 0.0
self._fractal_high = 0.0
self._fractal_low = 0.0
self._prev2_high = 0.0
self._prev1_high = 0.0
self._prev2_low = 0.0
self._prev1_low = 0.0
self._bar_count = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(send_close_order_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._fractal_high = 0.0
self._fractal_low = 0.0
self._prev2_high = 0.0
self._prev1_high = 0.0
self._prev2_low = 0.0
self._prev1_low = 0.0
self._bar_count = 0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
ev = float(ema_val)
av = float(atr_val)
self._bar_count += 1
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
# Detect fractal high: prev1High > prev2High and prev1High > current high
if self._bar_count > 3 and self._prev1_high > self._prev2_high and self._prev1_high > high:
self._fractal_high = self._prev1_high
# Detect fractal low: prev1Low < prev2Low and prev1Low < current low
if self._bar_count > 3 and self._prev1_low < self._prev2_low and self._prev1_low < low:
self._fractal_low = self._prev1_low
self._prev2_high = self._prev1_high
self._prev1_high = high
self._prev2_low = self._prev1_low
self._prev1_low = low
if self._fractal_high == 0 or self._fractal_low == 0 or av <= 0:
return
if self.Position > 0:
if close >= self._entry_price + av * 3.0 or close <= self._entry_price - av * 1.5:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 3.0 or close >= self._entry_price + av * 1.5:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if close > self._fractal_high and close > ev:
self._entry_price = close
self.BuyMarket()
elif close < self._fractal_low and close < ev:
self._entry_price = close
self.SellMarket()
def OnReseted(self):
super(send_close_order_strategy, self).OnReseted()
self._entry_price = 0.0
self._fractal_high = 0.0
self._fractal_low = 0.0
self._prev2_high = 0.0
self._prev1_high = 0.0
self._prev2_low = 0.0
self._prev1_low = 0.0
self._bar_count = 0
def CreateClone(self):
return send_close_order_strategy()