在 GitHub 上查看
Spazm 波动性突破策略
摘要
- 将 MetaTrader 4 智能交易系统 Spazm (8683) 转换到 StockSharp 高层 API。
- 通过比较收盘价与由波动性决定的上下包络带来捕捉突破,同时跟踪最近的摆动高点和低点。
- 可选地在图表上绘制连接多头与空头枢轴点的线段,重现原始 MQL 指标的可视化效果。
数据准备流程
- 根据参数
CandleType 订阅目标证券的蜡烛图序列。
- 每根已完成的蜡烛提供一个原始波动样本:
- 默认情况下样本等于
High - Low。
- 启用
UseOpenCloseRange 时,改用实体大小 |Open - Close|。
- 样本转换为价格步长数量(通过
PriceStep),从而消除不同品种的价格刻度差异。
- 依据
UseWeightedVolatility 选择合适的平滑指标:
false → 使用长度为 VolatilityPeriod 的简单移动平均。
true → 使用同长度的线性加权移动平均以强调最新数据。
- 平滑后的范围(以步长计)乘以
VolatilityMultiplier 并换算回价格单位,得到自适应的突破阈值。
- 在预热阶段(前
VolatilityPeriod * 3 根蜡烛)策略持续更新最近的最高价、最低价及其时间戳,并依据二者孰新来确定初始趋势方向。
参数
| 参数 |
默认值 |
说明 |
Volume |
1 |
每次开仓或反手时提交的市场订单数量。 |
VolatilityMultiplier |
5 |
对平均波动范围的放大倍数,用于构建突破通道。 |
VolatilityPeriod |
24 |
波动性估计与初始摆动扫描所需的蜡烛数量。 |
UseWeightedVolatility |
false |
是否改用线性加权移动平均。 |
UseOpenCloseRange |
false |
是否使用开收盘差值代替高低价差。 |
StopLossMultiplier |
0 |
以突破阈值为基准计算止损距离的倍数,至少为三个价格步长;设为 0 时禁用止损。 |
DrawSwingLines |
true |
是否绘制连接最近多空枢轴点的线段。 |
CandleType |
4 小时时间框架 |
用于计算的蜡烛类型或时间框架。 |
交易逻辑
- 初始化阶段
- 在前
VolatilityPeriod * 3 根蜡烛内持续更新 _highestPrice、_lowestPrice 以及对应时间。
- 当样本数量达到要求时,根据哪个极值更靠近当前时间来决定初始趋势:最近低点 → 看多;最近高点 → 看空。
- 同时把这些极值保存为首个枢轴点,以便开始绘制趋势线。
- 波动性估计
- 每根完成蜡烛将其范围送入所选的移动平均,生成当前阈值。
- 阈值至少为一个价格步长,避免出现零带宽。
- 摆动维护
- 无论趋势方向如何,新的绝对高点或低点都会刷新存储的摆动极值。
- 当趋势翻转时,将对应极值记录为枢轴点,并在启用可视化时与对向枢轴连线。
- 突破条件
- 看多阶段:收盘价跌破
_highestPrice - threshold 时,以 Volume + |Position| 的数量卖出,从而一次性反手至净空头。
- 看空阶段:收盘价突破
_lowestPrice + threshold 时,对称地反手为多头。
- 止损管理
- 若
StopLossMultiplier > 0,则依据 threshold * StopLossMultiplier(至少三个步长)计算出虚拟止损价位。
- 当蜡烛的最低价跌破多头止损,或最高价突破空头止损,即刻以市价单平仓。
- 基础设施
- 调用
StartProtection() 立即开启 StockSharp 的安全保护机制。
- 所有决策均基于已完成的蜡烛,符合原始 EA 的“按柱更新”模式。
与 MQL 版本的差异
- 原脚本按 tick 级别运行,本移植版本使用完成的蜡烛数据,这是 StockSharp 高层 API 的推荐方式。
- 无法获取经纪商的最小止损距离(如
MODE_STOPLEVEL),因此策略对止损偏移采用三步长的保守下限。
- 反手操作通过一次
BuyMarket / SellMarket 调用同时完成平仓与开仓,而非逐单遍历。
- 图表呈现依赖
DrawLine 方法,但连接顺序与 MQL 中的趋势线保持一致。
使用建议
- 确认标的证券提供正确的
PriceStep。若缺失,代码将回退到 1,可能需要手动调整。
- 时间框架越小,波动性均值越容易受噪声影响。建议采用接近原 EA 的 H4 周期。
- 将
StopLossMultiplier 保持为零可复制原策略的无止损行为;提高该值可引入风险控制。
- 策略不设定固定止盈目标,仅通过趋势反转或止损触发离场。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Volatility breakout strategy that tracks swing extremes and reverses
/// when price breaks beyond an ATR-based volatility band.
/// </summary>
public class SpazmVolatilityBreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _multiplier;
private decimal _swingHigh;
private decimal _swingLow;
private bool _trendUp;
private bool _initialized;
public SpazmVolatilityBreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis.", "General");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "Period for ATR volatility.", "Indicators");
_multiplier = Param(nameof(Multiplier), 2.0m)
.SetDisplay("Multiplier", "ATR multiplier for breakout threshold.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_swingHigh = 0;
_swingLow = decimal.MaxValue;
_trendUp = true;
_initialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (atrValue <= 0)
return;
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
var threshold = atrValue * Multiplier;
if (!_initialized)
{
_swingHigh = high;
_swingLow = low;
_initialized = true;
return;
}
if (_trendUp)
{
// Track swing high
if (high > _swingHigh)
_swingHigh = high;
// Reversal: price breaks below swing high minus threshold
if (close < _swingHigh - threshold)
{
_trendUp = false;
_swingLow = low;
// Enter short on trend reversal
if (Position > 0)
SellMarket();
if (Position == 0)
SellMarket();
}
}
else
{
// Track swing low
if (low < _swingLow)
_swingLow = low;
// Reversal: price breaks above swing low plus threshold
if (close > _swingLow + threshold)
{
_trendUp = true;
_swingHigh = high;
// Enter long on trend reversal
if (Position < 0)
BuyMarket();
if (Position == 0)
BuyMarket();
}
}
}
}
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 AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class spazm_volatility_breakout_strategy(Strategy):
def __init__(self):
super(spazm_volatility_breakout_strategy, self).__init__()
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "Period for ATR volatility", "Indicators")
self._multiplier = self.Param("Multiplier", 2.0).SetDisplay("Multiplier", "ATR multiplier for breakout threshold", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(spazm_volatility_breakout_strategy, self).OnReseted()
self._swing_high = 0
self._swing_low = 999999999
self._trend_up = True
self._initialized = False
def OnStarted2(self, time):
super(spazm_volatility_breakout_strategy, self).OnStarted2(time)
self._swing_high = 0
self._swing_low = 999999999
self._trend_up = True
self._initialized = False
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
def OnProcess(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
if atr_val <= 0:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
threshold = atr_val * self._multiplier.Value
if not self._initialized:
self._swing_high = high
self._swing_low = low
self._initialized = True
return
if self._trend_up:
if high > self._swing_high:
self._swing_high = high
if close < self._swing_high - threshold:
self._trend_up = False
self._swing_low = low
if self.Position > 0:
self.SellMarket()
if self.Position == 0:
self.SellMarket()
else:
if low < self._swing_low:
self._swing_low = low
if close > self._swing_low + threshold:
self._trend_up = True
self._swing_high = high
if self.Position < 0:
self.BuyMarket()
if self.Position == 0:
self.BuyMarket()
def CreateClone(self):
return spazm_volatility_breakout_strategy()