在 GitHub 上查看
Awesome Oscillator Trader 策略
Awesome Oscillator Trader 策略是 MetaTrader 平台 "AwesomeOscTrader" 专家的完整复刻版本。策略同时利用 Bill Williams 的 Awesome Oscillator、布林带宽度过滤以及随机指标,在波动率被压缩之后捕捉向上的爆发行情。默认针对单一货币对的 1 小时周期(如 EURUSD),与原始 EA 的使用说明保持一致。
当布林带上下轨之间的距离进入设定范围时,说明市场进入收缩状态但仍然活跃。此时策略要求 Awesome Oscillator 直方图形成一个独特的五根柱形结构:连续四根向下颜色的柱子且始终位于零轴下方,随后出现一根颜色翻转但仍在零轴下方的柱子。若同时随机指标 %K 上穿超卖阈值,则开多仓,期待挤压向上释放。相反,当出现四根位于零轴上方的正柱并在第五根变为下跌颜色,同时 %K 跌破超买阈值时,策略会开空仓。
仓位保护采用基于 ATR 的动态止损。每根 K 线都会读取 3 周期 ATR,将其乘以可配置的倍数,并按品种的最小跳动单位换算成点数。这个数值同时用作初始止损与止盈,从而完全复制 EA 中对称的退出方式。可选的 TrailingStopPips 会在价格向有利方向运行时逐步收紧止损;CloseOnReversal 配合 ProfitFilter 参数则负责在出现反向信号或直方图颜色反转时,按“全部 / 仅盈利 / 仅亏损”三种模式平仓,等价于 MT4 中的 ProfitTypeClTrd 选项。
交易规则
- 时间框架: 默认 1 小时蜡烛,可自定义。
- 过滤条件:
- 布林带宽度必须介于
BollingerSpreadLower 与 BollingerSpreadUpper(单位:点)之间。
- 随机指标 %K 与
StochasticLowerLevel(多头)或 StochasticUpperLevel(空头)对比。
- Awesome Oscillator 必须形成上述五根柱的颜色切换结构,并且当前柱的归一化幅度大于
AoStrengthLimit。
- 入场:
- 做多: 满足所有过滤条件,且当前 K 线开盘时间位于允许的交易时段内。
- 做空: 条件完全镜像。
- 离场:
- 基于 ATR 计算的止损与止盈在进场时同时设置,距离相同。
- 当
TrailingStopPips > 0 时启用追踪止损。
- 若开启
CloseOnReversal,出现反向形态或 AO 颜色翻转时按照 ProfitFilter 规则平仓。
主要参数
| 参数 |
默认值 |
说明 |
CandleType |
1 小时 |
指标使用的时间框架。 |
BollingerPeriod |
20 |
布林带周期。 |
BollingerSigma |
2.0 |
布林带标准差倍数。 |
BollingerSpreadLower |
24 点 |
最小允许的带宽。 |
BollingerSpreadUpper |
230 点 |
最大允许的带宽。 |
AoFastPeriod / AoSlowPeriod |
4 / 28 |
Awesome Oscillator 的快 / 慢周期。 |
AoStrengthLimit |
0.0 |
归一化 AO 的最小强度门槛。 |
StochasticKPeriod / StochasticDPeriod / StochasticSlowing |
1 / 4 / 1 |
随机指标参数,保持与 EA 相同。 |
StochasticLowerLevel / StochasticUpperLevel |
12 / 21 |
随机指标的超卖 / 超买阈值。 |
EntryHour / OpenHours |
16 / 13 |
允许开仓的起始小时与持续时长,支持跨越午夜。 |
RiskPercent |
0.5% |
根据账户权益计算仓位的风险百分比。 |
AtrMultiplier |
4.5 |
ATR 止损倍数。 |
TrailingStopPips |
40 点 |
追踪止损距离(设为 0 可关闭)。 |
ProfitFilter |
OnlyProfitable |
反向信号可以平仓的类别:全部 / 仅盈利 / 仅亏损。 |
MaxOpenOrders |
1 |
同时持仓数量上限。 |
实现细节
- 仅使用 StockSharp 内置的
BollingerBands、StochasticOscillator、AwesomeOscillator、AverageTrueRange 与 Highest 指标,无需手写公式。
- AO 数值在最近 100 根柱内做绝对值最大化归一,模拟 MT4 自定义指标的三个缓冲区,从而准确重现颜色逻辑。
- 头寸大小在可获取数据时会考虑
Security.StepVolume、Security.MinVolume、Security.MaxVolume 与 Security.StepPrice;若信息缺失,则退回到策略默认手数。
- 止损与止盈的触发检测在每根收盘蜡烛执行,既保留了原始 EA 的逐笔管理方式,又避免依赖经纪商挂单。
- 代码注释全部使用英文,并按照仓库规范采用制表符缩进。
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Awesome Oscillator Trader strategy: SMA 5/20 crossover (AO concept).
/// Buys when fast SMA crosses above slow SMA, sells on cross below.
/// </summary>
public class AwesomeOscillatorTraderStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public AwesomeOscillatorTraderStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast SMA", "Fast SMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 34)
.SetGreaterThanZero()
.SetDisplay("Slow SMA", "Slow SMA period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new SimpleMovingAverage { Length = FastPeriod };
var slow = new SimpleMovingAverage { Length = SlowPeriod };
decimal? prevFast = null;
decimal? prevSlow = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, (candle, fastVal, slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (prevFast.HasValue && prevSlow.HasValue)
{
var crossUp = prevFast.Value <= prevSlow.Value && fastVal > slowVal;
var crossDown = prevFast.Value >= prevSlow.Value && fastVal < slowVal;
if (crossUp && Position <= 0)
BuyMarket();
else if (crossDown && Position >= 0)
SellMarket();
}
prevFast = fastVal;
prevSlow = slowVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
}
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 awesome_oscillator_trader_strategy(Strategy):
def __init__(self):
super(awesome_oscillator_trader_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._fast_period = self.Param("FastPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("Fast SMA", "Fast SMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 34) \
.SetGreaterThanZero() \
.SetDisplay("Slow SMA", "Slow SMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(awesome_oscillator_trader_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(awesome_oscillator_trader_strategy, self).OnStarted2(time)
self._fast_ind = SimpleMovingAverage()
self._fast_ind.Length = self._fast_period.Value
self._slow_ind = SimpleMovingAverage()
self._slow_ind.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._fast_ind, self._slow_ind, self._process_candle).Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if self._prev_fast is not None and self._prev_slow is not None:
cross_up = self._prev_fast <= self._prev_slow and fast_val > slow_val
cross_down = self._prev_fast >= self._prev_slow and fast_val < slow_val
if cross_up and self.Position <= 0:
self.BuyMarket()
elif cross_down and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return awesome_oscillator_trader_strategy()