首页
/
策略示例
在 GitHub 上查看
Twenty200 Time Breakout
本策略移植自 MetaTrader 专家顾问 20/200 expert v4.2 (AntS) 。它每天在指定的交易小时对比两根历史小时K线的开盘价(默认分别是当前K线向前 6 根与 2 根)。若远端开盘价高于近端开盘价超过 Short Delta 点,则开空;反之若近端开盘价高出远端超过 Long Delta 点,则开多。
交易逻辑
订阅的K线类型默认为 1 小时,可通过 Candle Type 调整。
每个交易日只允许一笔仓位;只有当当前K线的小时数等于 Trade Hour 时才会评估信号。
信号使用 LookbackFar 与 LookbackNear 指定的历史开盘价:
做空: Open[t1] - Open[t2] > Short Delta × 点值。
做多: Open[t2] - Open[t1] > Long Delta × 点值。
触发信号后按计算的手数发送市价单。止损/止盈与原EA一致,以点数输入并通过 Security.PriceStep 自动换算为价格。
同一时间只保留一个方向的仓位,下一日可以重新建仓。
仓位管理
每次收到K线更新都会用最高价/最低价判断止盈或止损是否被触发。
Max Open Hours 指定仓位的最长持有时间(默认 504 小时),超过后立即市价平仓,填写 0 可关闭该保护。
资金管理
Fixed Volume 是关闭自动手数或无法获得账户权益时使用的基础手数。
启用 Use Auto Lot 时,手数遵循原EA中庞大的分段表。在 StockSharp 里通过 volume = round(balance × Auto Lot Factor, 2)(默认系数 0.000038)进行拟合,在 300~270000+ 美元区间内与原表精度保持在 0.01 手以内。
当当前权益低于上一次记录的余额时,下一笔交易会乘以 Big Lot Multiplier,复现 EA 中的 “Big Lot” 回本模式。
手数会根据 Security.VolumeStep 做步长对齐,并限制在交易所提供的 MinVolume/MaxVolume 区间内。
与 MT4 版本的差异
MT4 程序显式写入了上千行阈值。本移植版使用线性系数 Auto Lot Factor 拟合同样的阶梯曲线,如需与其他经纪商完全一致可自行调整该系数。
止损/止盈通过在K线触及水平时发送市价平仓实现,无需依赖交易所的止损单支持,在回测与实盘中表现一致。
原策略依赖的全局变量(globalBalans、globalPosic)被内存状态替代,不再需要终端全局存储。
参数
参数
说明
Long/Short Take Profit
止盈距离(点)。
Long/Short Stop Loss
止损距离(点)。
Trade Hour
允许开仓的小时(0–23)。
Far/Near Lookback
两个历史开盘价的回溯数量。
Long/Short Delta
触发信号所需的点差。
Max Open Hours
仓位最长持有时间(小时,0 表示禁用)。
Fixed Volume
关闭自动手数时的基础仓位。
Use Auto Lot
是否根据账户权益自动计算手数。
Auto Lot Factor
拟合 MT4 手数阶梯的乘数系数。
Big Lot Multiplier
权益回撤后下一笔交易的放大系数。
Candle Type
信号使用的K线周期。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class Twenty200TimeBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _shortPeriod;
private readonly StrategyParam<int> _longPeriod;
private readonly StrategyParam<int> _cooldownCandles;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevShort;
private decimal _prevLong;
private bool _hasPrev;
private int _cooldownRemaining;
public int ShortPeriod { get => _shortPeriod.Value; set => _shortPeriod.Value = value; }
public int LongPeriod { get => _longPeriod.Value; set => _longPeriod.Value = value; }
public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public Twenty200TimeBreakoutStrategy()
{
_shortPeriod = Param(nameof(ShortPeriod), 20).SetDisplay("Short SMA", "Short SMA period", "Indicators");
_longPeriod = Param(nameof(LongPeriod), 200).SetDisplay("Long SMA", "Long SMA period", "Indicators");
_cooldownCandles = Param(nameof(CooldownCandles), 100).SetDisplay("Cooldown", "Candles between signals", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevShort = default;
_prevLong = default;
_hasPrev = default;
_cooldownRemaining = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevShort = 0;
_prevLong = 0;
_hasPrev = false;
_cooldownRemaining = 0;
var shortSma = new SimpleMovingAverage { Length = ShortPeriod };
var longSma = new SimpleMovingAverage { Length = LongPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(shortSma, longSma, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal shortSma, decimal longSma)
{
if (candle.State != CandleStates.Finished) return;
if (!_hasPrev) { _prevShort = shortSma; _prevLong = longSma; _hasPrev = true; return; }
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevShort = shortSma;
_prevLong = longSma;
return;
}
if (_prevShort <= _prevLong && shortSma > longSma && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownCandles;
}
else if (_prevShort >= _prevLong && shortSma < longSma && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_cooldownRemaining = CooldownCandles;
}
_prevShort = shortSma;
_prevLong = longSma;
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class twenty_200_time_breakout_strategy(Strategy):
def __init__(self):
super(twenty_200_time_breakout_strategy, self).__init__()
self._short_period = self.Param("ShortPeriod", 20) \
.SetDisplay("Short SMA", "Short SMA period", "Indicators")
self._long_period = self.Param("LongPeriod", 200) \
.SetDisplay("Long SMA", "Long SMA period", "Indicators")
self._cooldown_candles = self.Param("CooldownCandles", 100) \
.SetDisplay("Cooldown", "Candles between signals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_short = 0.0
self._prev_long = 0.0
self._has_prev = False
self._cooldown_remaining = 0
@property
def short_period(self):
return self._short_period.Value
@property
def long_period(self):
return self._long_period.Value
@property
def cooldown_candles(self):
return self._cooldown_candles.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(twenty_200_time_breakout_strategy, self).OnReseted()
self._prev_short = 0.0
self._prev_long = 0.0
self._has_prev = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(twenty_200_time_breakout_strategy, self).OnStarted2(time)
self._prev_short = 0.0
self._prev_long = 0.0
self._has_prev = False
self._cooldown_remaining = 0
short_sma = SimpleMovingAverage()
short_sma.Length = self.short_period
long_sma = SimpleMovingAverage()
long_sma.Length = self.long_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(short_sma, long_sma, self.process_candle).Start()
def process_candle(self, candle, short_sma, long_sma):
if candle.State != CandleStates.Finished:
return
short_val = float(short_sma)
long_val = float(long_sma)
if not self._has_prev:
self._prev_short = short_val
self._prev_long = long_val
self._has_prev = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_short = short_val
self._prev_long = long_val
return
if self._prev_short <= self._prev_long and short_val > long_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_candles
elif self._prev_short >= self._prev_long and short_val < long_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_candles
self._prev_short = short_val
self._prev_long = long_val
def CreateClone(self):
return twenty_200_time_breakout_strategy()