SurefireThing 策略
概述
SurefireThing 策略是 MetaTrader 4 专家顾问 Surefirething 的 StockSharp 高级 API 移植版本。策略仅在收盘后的完整 K 线数据上工作,根据前一交易日的价格区间计算挂单水平,并在每天结束时清空持仓。核心思想是利用前收盘价周围的对称限价挂单,捕捉可能的均值回归。
交易逻辑
- 每当检测到新的交易日,策略会尝试平掉当前仓位并撤销仍然有效的挂单。
- 取上一交易日最后一根完成的 K 线,计算其范围
(High - Low)并乘以RangeMultiplier(默认 1.1,与原版 EA 相同)。 - 将调整后的范围的一半加到前收盘价上得到卖出限价单价位,同样的距离减去前收盘价得到买入限价单价位。
- 止损与止盈参数以价格最小变动单位表示。当品种提供有效的
Security.Step时,策略会把这些距离转换为绝对价格并通过StartProtection自动创建保护单。 - 每个交易日仅提交一次挂单。若挂单成交则由保护单负责离场,否则订单会一直保留到下一次日内重置。
参数
| 参数 | 说明 | 默认值 |
|---|---|---|
OrderVolume |
每次挂单的下单量。 | 0.1 |
TakeProfitPoints |
止盈距离,单位为价格步长。若可用则自动换算为绝对价格。 | 10 |
StopLossPoints |
止损距离,单位为价格步长。换算方式与止盈相同。 | 15 |
RangeMultiplier |
用于放大前一根 K 线区间的倍数。 | 1.1 |
CandleType |
策略处理的主要时间框架。默认使用 1 分钟 K 线,可按需要调整以匹配原始图表。 | TimeSpan.FromMinutes(1) |
实现细节
- 高级 API:通过
SubscribeCandles(CandleType)订阅 K 线,只在ProcessCandle中处理已完成的 K 线。 - 日内重置:利用 K 线时间戳判断是否跨日,
CloseForNewDay在新交易日开始时关闭仓位并撤单。 - 保护逻辑:
ConfigureProtection将点值参数转换为Unit,并启用StartProtection,使得成交后自动生成止损/止盈保护单。 - 订单管理:使用
_buyLimitOrder与_sellLimitOrder保存挂单引用,在CancelPendingOrder及OnOrderChanged中清理已完成或撤销的订单。 - 价格规整:在提交订单前通过
Security.ShrinkPrice将计算出的价格收敛到品种的最小跳动单位。
使用建议
- 根据原版 EA 所使用的图表时间框架调整
CandleType,以保持相同的参考 K 线。 - 若标的波动率差异较大,可调节
RangeMultiplier以控制挂单距离。 - 如果经纪商限制最小止损距离,请确认
TakeProfitPoints与StopLossPoints在换算为绝对价格后满足规则。 - 策略假定存在连续的日内数据;遇到周末或假期缺口时,会在下一根可用 K 线上完成重置并重新挂单。
using System;
using System.Collections.Generic;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Daily range breakout strategy.
/// Calculates buy/sell levels from previous day's range.
/// Buys when price drops below the lower level, sells when above the upper level.
/// Closes position at end of day.
/// </summary>
public class SurefireThingStrategy : Strategy
{
private readonly StrategyParam<decimal> _rangeMultiplier;
private readonly StrategyParam<DataType> _candleType;
private DateTime? _currentDay;
private decimal _buyLevel;
private decimal _sellLevel;
private bool _levelsReady;
private decimal _prevDayClose;
private decimal _prevDayHigh;
private decimal _prevDayLow;
private decimal _dayHigh;
private decimal _dayLow;
private decimal _dayClose;
private bool _hasPrevDay;
private bool _tradedToday;
public decimal RangeMultiplier
{
get => _rangeMultiplier.Value;
set => _rangeMultiplier.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public SurefireThingStrategy()
{
_rangeMultiplier = Param(nameof(RangeMultiplier), 0.5m)
.SetDisplay("Range Mult", "Multiplier for range-based levels", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle series", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_currentDay = null;
_buyLevel = 0m;
_sellLevel = 0m;
_levelsReady = false;
_prevDayClose = 0m;
_prevDayHigh = 0m;
_prevDayLow = 0m;
_dayHigh = 0m;
_dayLow = 0m;
_dayClose = 0m;
_hasPrevDay = false;
_tradedToday = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_currentDay = null;
_levelsReady = false;
_hasPrevDay = false;
_tradedToday = false;
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;
var day = candle.OpenTime.Date;
// New day detected
if (_currentDay == null || day > _currentDay.Value)
{
// Close position at end of previous day
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
// Save previous day stats
if (_currentDay != null)
{
_prevDayClose = _dayClose;
_prevDayHigh = _dayHigh;
_prevDayLow = _dayLow;
_hasPrevDay = true;
}
// Calculate new levels
if (_hasPrevDay)
{
var range = _prevDayHigh - _prevDayLow;
if (range > 0)
{
var halfRange = range * RangeMultiplier;
_buyLevel = _prevDayClose - halfRange;
_sellLevel = _prevDayClose + halfRange;
_levelsReady = true;
}
}
_currentDay = day;
_dayHigh = candle.HighPrice;
_dayLow = candle.LowPrice;
_dayClose = candle.ClosePrice;
_tradedToday = false;
}
else
{
if (candle.HighPrice > _dayHigh) _dayHigh = candle.HighPrice;
if (candle.LowPrice < _dayLow) _dayLow = candle.LowPrice;
_dayClose = candle.ClosePrice;
}
if (!_levelsReady)
return;
var price = candle.ClosePrice;
// Only one trade per day per direction
if (!_tradedToday && Position == 0)
{
if (price <= _buyLevel)
{
BuyMarket();
_tradedToday = true;
}
else if (price >= _sellLevel)
{
SellMarket();
_tradedToday = 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, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class surefire_thing_strategy(Strategy):
"""Daily range breakout strategy. Calculates buy/sell levels from previous day's range.
Buys when price drops below the lower level, sells when above the upper level.
Closes position at end of day."""
def __init__(self):
super(surefire_thing_strategy, self).__init__()
self._range_multiplier = self.Param("RangeMultiplier", 0.5) \
.SetDisplay("Range Mult", "Multiplier for range-based levels", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle series", "General")
self._current_day = None
self._buy_level = 0.0
self._sell_level = 0.0
self._levels_ready = False
self._prev_day_close = 0.0
self._prev_day_high = 0.0
self._prev_day_low = 0.0
self._day_high = 0.0
self._day_low = 0.0
self._day_close = 0.0
self._has_prev_day = False
self._traded_today = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RangeMultiplier(self):
return self._range_multiplier.Value
def OnReseted(self):
super(surefire_thing_strategy, self).OnReseted()
self._current_day = None
self._buy_level = 0.0
self._sell_level = 0.0
self._levels_ready = False
self._prev_day_close = 0.0
self._prev_day_high = 0.0
self._prev_day_low = 0.0
self._day_high = 0.0
self._day_low = 0.0
self._day_close = 0.0
self._has_prev_day = False
self._traded_today = False
def OnStarted2(self, time):
super(surefire_thing_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
day = candle.OpenTime.Date
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
# New day detected
if self._current_day is None or day > self._current_day:
# Close position at end of previous day
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
# Save previous day stats
if self._current_day is not None:
self._prev_day_close = self._day_close
self._prev_day_high = self._day_high
self._prev_day_low = self._day_low
self._has_prev_day = True
# Calculate new levels
if self._has_prev_day:
day_range = self._prev_day_high - self._prev_day_low
if day_range > 0:
half_range = day_range * float(self.RangeMultiplier)
self._buy_level = self._prev_day_close - half_range
self._sell_level = self._prev_day_close + half_range
self._levels_ready = True
self._current_day = day
self._day_high = high
self._day_low = low
self._day_close = close
self._traded_today = False
else:
if high > self._day_high:
self._day_high = high
if low < self._day_low:
self._day_low = low
self._day_close = close
if not self._levels_ready:
return
# Only one trade per day
if not self._traded_today and self.Position == 0:
if close <= self._buy_level:
self.BuyMarket()
self._traded_today = True
elif close >= self._sell_level:
self.SellMarket()
self._traded_today = True
def CreateClone(self):
return surefire_thing_strategy()