在 GitHub 上查看
日线 STP 入场框架策略(StockSharp 版本)
策略简介
该策略基于 MetaTrader EA “Daily STP Entry Frame”,使用 StockSharp 高级 API 重新实现。系统在每天开盘后,根据上一交易日的高点和低点预先放置突破方向的止损挂单,并通过多重过滤条件确保当前价格已经接近关键区间。策略特别适用于外汇品种,默认把“基点”视为五位报价下的 0.0001。
工作流程
- 记录昨日区间:订阅日线级别的 K 线,持续保存上一根完成蜡烛的最高价和最低价。
- 实时监控:订阅 Level1 行情,获取即时的买价、卖价与最新成交价,用于下单与仓位管理。
- 挂单条件:当新交易日开始且满足以下约束时提交挂单:
- 最新价与昨日高/低的距离不少于
ThresholdPoints;
- 当天开盘价位于突破方向所需的一侧;
- 买入止损价 = 昨日高点 +
SpreadPoints / 2;卖出止损价 = 昨日低点 − SpreadPoints / 2。
- 风险过滤:若账户回撤超过
MaximumDrawdownPercent,或时间过滤(周末、时段、指定日)不满足,则不会创建新挂单。
- 仓位管理:成交后执行以下保护措施:
- 固定止损与止盈(单位为基点,自动转换为价格距离);
- 按
CloseAfterSeconds 设置的秒数强制平仓;
- 当
TrailingSlope < 1 时启用拖尾止损,按原 EA 的斜率公式动态推进保护价位。
- 日终处理:到达
NoNewOrdersHour(周五使用专门的 NoNewOrdersHourFriday)或跨过自然日时,立即撤销尚未成交的挂单。
入场与风险规则
- 做多条件
SideFilter 为 0(双向)或 1(仅多)。
- 昨日高点 − 当前价格 ≥
ThresholdPoints。
- 今日开盘价低于昨日高点。
- 目标买入价需与当前卖价保持最小距离。
- 做空条件
SideFilter 为 0(双向)或 -1(仅空)。
- 当前价格 − 昨日低点 ≥
ThresholdPoints。
- 今日开盘价高于昨日低点。
- 目标卖出价需与当前买价保持最小距离。
- 资金管理
- 依据账户累计盈利的
PercentOfProfit 百分比计算仓位。
- 挂单手数始终限制在
MinVolume 与 MaxVolume 之间,并根据 VolumeStep 进行对齐。
- 当回撤超过
MaximumDrawdownPercent 时停止生成新挂单。
- 保护机制
- 止损、止盈均以基点定义,并按照品种的点值转换为实际价格。
- 拖尾止损遵循原 EA 的公式:多头
Stop = Bid - StopLoss - Slope * (Bid - Entry),空头逻辑对称。
CloseAfterSeconds 可实现到时强制平仓。
主要参数
| 参数 |
说明 |
CandleType |
计算昨日区间的时间框架(默认日线)。 |
StopLossPoints / TakeProfitPoints |
止损、止盈距离(基点)。 |
TrailingSlope |
拖尾比例,≥1 表示禁用。 |
SideFilter |
-1 仅空、0 双向、1 仅多。 |
ThresholdPoints |
距离门槛,决定是否挂单。 |
SpreadPoints |
用于补偿点差的附加位移。 |
SlippagePoints |
验证最小距离时的额外缓冲。 |
NoNewOrdersHour / NoNewOrdersHourFriday |
正常日与周五撤单时间。 |
EarliestOrderHour |
允许开始挂单的最早小时。 |
DayFilter |
6 表示全周,其余 0–5 对应周日到周五。 |
CloseAfterSeconds |
仓位存续秒数,0 表示关闭功能。 |
PercentOfProfit |
按盈利比例调整仓位。 |
MinVolume / MaxVolume |
挂单数量上下限。 |
MaximumDrawdownPercent |
最大允许回撤百分比。 |
实现细节
- 若合约的
Decimals 为 3 或 5,基点按 PriceStep * 10 计算,与原始 EA 一致。
- 每个交易日更替时自动撤销旧挂单,避免重复下单。
- 日终撤单逻辑保留了周五单独设置的截止时间。
- Equity 邮件提醒被日志输出取代,便于在 StockSharp Designer/Runner 中查看。
- 即使已有持仓仍允许新的挂单保持激活,忠实重现原策略风格。
使用建议
- 在真实交易前,通过 StockSharp Designer 进行参数回测与优化。
- 确保所选品种已正确设置
PriceStep、StepPrice 与 VolumeStep,否则点值换算会出现误差。
- 可结合平台的组合风控、滑点模拟等功能强化风险控制。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Daily breakout strategy that enters on price crossing previous session extremes.
/// Converted from "Daily STP Entry Frame" MetaTrader expert.
/// Uses market orders when price breaks above previous high or below previous low.
/// </summary>
public class DailyStpEntryFrameStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private decimal _pipSize;
private decimal _stopLossOffset;
private decimal _takeProfitOffset;
private decimal? _previousDayHigh;
private decimal? _previousDayLow;
private decimal _currentDayHigh;
private decimal _currentDayLow;
private DateTime? _currentTradingDay;
private bool _tradedToday;
private decimal _entryPrice;
private decimal? _longStop;
private decimal? _longTake;
private decimal? _shortStop;
private decimal? _shortTake;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
public DailyStpEntryFrameStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Time-frame for monitoring", "General");
_stopLossPoints = Param(nameof(StopLossPoints), 80m)
.SetNotNegative()
.SetDisplay("Stop-Loss (points)", "Stop-loss distance", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 200m)
.SetNotNegative()
.SetDisplay("Take-Profit (points)", "Take-profit distance", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_pipSize = 0m;
_stopLossOffset = 0m;
_takeProfitOffset = 0m;
_previousDayHigh = null;
_previousDayLow = null;
_currentDayHigh = 0m;
_currentDayLow = 0m;
_currentTradingDay = null;
_tradedToday = false;
_entryPrice = 0m;
_longStop = null;
_longTake = null;
_shortStop = null;
_shortTake = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = Security?.PriceStep ?? 0.01m;
if (_pipSize <= 0) _pipSize = 0.01m;
_stopLossOffset = StopLossPoints * _pipSize;
_takeProfitOffset = TakeProfitPoints * _pipSize;
_previousDayHigh = null;
_previousDayLow = null;
_currentTradingDay = null;
_tradedToday = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var date = candle.OpenTime.Date;
// Track daily high/low
if (_currentTradingDay != date)
{
// Save previous day's range
if (_currentTradingDay != null)
{
_previousDayHigh = _currentDayHigh;
_previousDayLow = _currentDayLow;
}
_currentTradingDay = date;
_currentDayHigh = candle.HighPrice;
_currentDayLow = candle.LowPrice;
_tradedToday = false;
}
else
{
_currentDayHigh = Math.Max(_currentDayHigh, candle.HighPrice);
_currentDayLow = Math.Min(_currentDayLow, candle.LowPrice);
}
// Manage existing position
ManagePosition(candle);
// Check for breakout entries
if (_previousDayHigh is null || _previousDayLow is null)
return;
if (_tradedToday || Position != 0)
return;
var close = candle.ClosePrice;
// Breakout above previous day high => buy
if (close > _previousDayHigh.Value)
{
_entryPrice = close;
_longStop = _stopLossOffset > 0 ? close - _stopLossOffset : null;
_longTake = _takeProfitOffset > 0 ? close + _takeProfitOffset : null;
_shortStop = null;
_shortTake = null;
BuyMarket();
_tradedToday = true;
}
// Breakout below previous day low => sell
else if (close < _previousDayLow.Value)
{
_entryPrice = close;
_shortStop = _stopLossOffset > 0 ? close + _stopLossOffset : null;
_shortTake = _takeProfitOffset > 0 ? close - _takeProfitOffset : null;
_longStop = null;
_longTake = null;
SellMarket();
_tradedToday = true;
}
}
private void ManagePosition(ICandleMessage candle)
{
if (Position > 0)
{
if (_longStop is decimal stop && candle.LowPrice <= stop)
{
SellMarket();
_longStop = null;
_longTake = null;
return;
}
if (_longTake is decimal take && candle.HighPrice >= take)
{
SellMarket();
_longStop = null;
_longTake = null;
}
}
else if (Position < 0)
{
if (_shortStop is decimal stop && candle.HighPrice >= stop)
{
BuyMarket();
_shortStop = null;
_shortTake = null;
return;
}
if (_shortTake is decimal take && candle.LowPrice <= take)
{
BuyMarket();
_shortStop = null;
_shortTake = null;
}
}
}
}
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 daily_stp_entry_frame_strategy(Strategy):
def __init__(self):
super(daily_stp_entry_frame_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Time-frame for monitoring", "General")
self._stop_loss_points = self.Param("StopLossPoints", 80.0) \
.SetDisplay("Stop-Loss (points)", "Stop-loss distance", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 200.0) \
.SetDisplay("Take-Profit (points)", "Take-profit distance", "Risk")
self._prev_day_high = None
self._prev_day_low = None
self._cur_day_high = 0.0
self._cur_day_low = 0.0
self._current_day = None
self._traded_today = False
self._entry_price = 0.0
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def StopLossPoints(self):
return float(self._stop_loss_points.Value)
@property
def TakeProfitPoints(self):
return float(self._take_profit_points.Value)
def OnStarted2(self, time):
super(daily_stp_entry_frame_strategy, self).OnStarted2(time)
self._pip_size = 0.01
sec = self.Security
if sec is not None:
ps = sec.PriceStep
if ps is not None and float(ps) > 0:
self._pip_size = float(ps)
self._sl_offset = self.StopLossPoints * self._pip_size
self._tp_offset = self.TakeProfitPoints * self._pip_size
self._prev_day_high = None
self._prev_day_low = None
self._current_day = None
self._traded_today = False
self._entry_price = 0.0
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
dt = candle.OpenTime
day = dt.Date
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._current_day is None or self._current_day != day:
if self._current_day is not None:
self._prev_day_high = self._cur_day_high
self._prev_day_low = self._cur_day_low
self._current_day = day
self._cur_day_high = high
self._cur_day_low = low
self._traded_today = False
else:
if high > self._cur_day_high:
self._cur_day_high = high
if low < self._cur_day_low:
self._cur_day_low = low
# manage position
if self.Position > 0:
if self._long_stop is not None and low <= self._long_stop:
self.SellMarket()
self._long_stop = None
self._long_take = None
return
if self._long_take is not None and high >= self._long_take:
self.SellMarket()
self._long_stop = None
self._long_take = None
return
elif self.Position < 0:
if self._short_stop is not None and high >= self._short_stop:
self.BuyMarket()
self._short_stop = None
self._short_take = None
return
if self._short_take is not None and low <= self._short_take:
self.BuyMarket()
self._short_stop = None
self._short_take = None
return
# entries
if self._prev_day_high is None or self._prev_day_low is None:
return
if self._traded_today or self.Position != 0:
return
if close > self._prev_day_high:
self._entry_price = close
self._long_stop = close - self._sl_offset if self._sl_offset > 0 else None
self._long_take = close + self._tp_offset if self._tp_offset > 0 else None
self._short_stop = None
self._short_take = None
self.BuyMarket()
self._traded_today = True
elif close < self._prev_day_low:
self._entry_price = close
self._short_stop = close + self._sl_offset if self._sl_offset > 0 else None
self._short_take = close - self._tp_offset if self._tp_offset > 0 else None
self._long_stop = None
self._long_take = None
self.SellMarket()
self._traded_today = True
def OnReseted(self):
super(daily_stp_entry_frame_strategy, self).OnReseted()
self._prev_day_high = None
self._prev_day_low = None
self._cur_day_high = 0.0
self._cur_day_low = 0.0
self._current_day = None
self._traded_today = False
self._entry_price = 0.0
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
def CreateClone(self):
return daily_stp_entry_frame_strategy()