Forex Sky 策略
概述
Forex Sky 策略 是 MetaTrader 智能交易系统 Forex_SKY.mq4 的直接移植版本。它依旧以 MACD 动能反转为核心,并保持“每天仅允许一笔交易、每根 K 线最多一笔”的原始限制。StockSharp 版本保留了所有阈值,同时使用高层 API 完成指标绑定与订单管理。
策略会根据参数 CandleType 订阅指定周期(默认 15 分钟)的 K 线,并在每根收盘 K 线上计算经典 MACD(12/26/9)。
交易逻辑
- 做多入场:满足以下条件时市价买入:
- 当前 MACD 主线大于 0;
- 同时超过
+0.00009,确认动能足够;
- 最近三根 MACD 值中至少有一次小于等于 0,意味着指标刚刚从负区间翻正。
- 做空入场:以下任一条件成立时市价卖出:
- 当前 MACD 主线小于 0,且低于
-0.0004;最近三根 MACD 至少有一次为非负值;四根之前的 MACD 值至少为 +0.001;
- 或者 四根之前的 MACD 值达到
≥ +0.003,该条件会像原始 EA 一样直接触发做空。
- 仓位管理:策略复制了
Time0 与 CheckTodaysOrders() 的限制——每根 K 线最多一笔交易,并且每天仅交易一次。入场后由 StockSharp 的 StartProtection 自动挂出止盈止损,使保护单与仓位数量保持一致。
策略本身不会主动平仓,依赖止盈、止损或人工干预,与原版 EA 的行为完全一致。
参数
| 名称 |
默认值 |
说明 |
FastPeriod |
12 |
MACD 快速 EMA 的周期。 |
SlowPeriod |
26 |
MACD 慢速 EMA 的周期。 |
SignalPeriod |
9 |
MACD 信号线的 EMA 周期。 |
TakeProfitPoints |
100 |
止盈距离(按交易品种的最小价格单位表示)。最终价格 = 该值 × Security.PriceStep。 |
StopLossPoints |
3000 |
止损距离(最小价格单位)。 |
TradeVolume |
0.1 |
市价订单的基础手数。 |
CandleType |
15 分钟 |
用于计算 MACD 和产生信号的 K 线周期。 |
点值换算
TakeProfitPoints 与 StopLossPoints 的定义与 MetaTrader 中的 Point 完全一致。若外汇品种的 PriceStep = 0.00001(五位报价):
- 止盈距离:
100 × 0.00001 = 0.001;
- 止损距离:
3000 × 0.00001 = 0.03。
风险管理
当仓位建立后,StartProtection 会立即挂出相应的止盈与止损订单,触发时使用市价单执行,行为与 MetaTrader 保持一致。将任一参数设置为 0 可关闭对应的保护单。
迁移说明
- 使用类成员缓存最近四个 MACD 数值,避免调用指标的历史索引,实现与原始脚本等价的判断。
- 每日交易次数与每根 K 线的交易次数限制,完整复刻了
CheckTodaysOrders() 与 Time0 的检查逻辑。
- 全部注释改写为英文,指标处理使用 StockSharp 的高层
Bind 机制,无需手动读取值。
使用提示
- 根据需要调整
CandleType,原始 EA 会直接继承图表周期,此处通过参数控制。
- 因为每天仅允许一笔交易,建议选择日内波动较大的品种,或在高波动环境下适当提高 MACD 阈值。
- 日界限基于 K 线开盘时间计算,部署时请确认交易服务器时区,以免提前或延迟重置交易配额。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Forex Sky: MACD zero-line cross with EMA trend filter.
/// </summary>
public class ForexSkyStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevMacd;
private decimal _entryPrice;
public ForexSkyStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastLength = Param(nameof(FastLength), 12)
.SetDisplay("Fast EMA", "MACD fast period.", "Indicators");
_slowLength = Param(nameof(SlowLength), 26)
.SetDisplay("Slow EMA", "MACD slow period.", "Indicators");
_emaLength = Param(nameof(EmaLength), 50)
.SetDisplay("EMA Length", "Trend filter.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMacd = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMacd = 0;
_entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastLength };
var slowEma = new ExponentialMovingAverage { Length = SlowLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, ema, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0)
return;
var macd = fastVal - slowVal;
var close = candle.ClosePrice;
if (_prevMacd == 0)
{
_prevMacd = macd;
return;
}
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 2m || (macd < 0 && _prevMacd >= 0))
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 2m || (macd > 0 && _prevMacd <= 0))
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (macd > 0 && _prevMacd <= 0 && close > emaVal)
{
_entryPrice = close;
BuyMarket();
}
else if (macd < 0 && _prevMacd >= 0 && close < emaVal)
{
_entryPrice = close;
SellMarket();
}
}
_prevMacd = macd;
}
}
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 ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class forex_sky_strategy(Strategy):
"""
Forex Sky: MACD zero-line cross with EMA trend filter and ATR-based exits.
"""
def __init__(self):
super(forex_sky_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_length = self.Param("FastLength", 12) \
.SetDisplay("Fast EMA", "MACD fast period", "Indicators")
self._slow_length = self.Param("SlowLength", 26) \
.SetDisplay("Slow EMA", "MACD slow period", "Indicators")
self._ema_length = self.Param("EmaLength", 50) \
.SetDisplay("EMA Length", "Trend filter", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period", "Indicators")
self._prev_macd = 0.0
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(forex_sky_strategy, self).OnReseted()
self._prev_macd = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(forex_sky_strategy, self).OnStarted2(time)
self._prev_macd = 0.0
self._entry_price = 0.0
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_length.Value
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self._slow_length.Value
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ema, slow_ema, ema, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_val)
slow_val = float(slow_val)
ema_val = float(ema_val)
atr_val = float(atr_val)
if atr_val <= 0:
return
macd = fast_val - slow_val
close = float(candle.ClosePrice)
if self._prev_macd == 0:
self._prev_macd = macd
return
if self.Position > 0:
if (close >= self._entry_price + atr_val * 3.0 or
close <= self._entry_price - atr_val * 2.0 or
(macd < 0 and self._prev_macd >= 0)):
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (close <= self._entry_price - atr_val * 3.0 or
close >= self._entry_price + atr_val * 2.0 or
(macd > 0 and self._prev_macd <= 0)):
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if macd > 0 and self._prev_macd <= 0 and close > ema_val:
self._entry_price = close
self.BuyMarket()
elif macd < 0 and self._prev_macd >= 0 and close < ema_val:
self._entry_price = close
self.SellMarket()
self._prev_macd = macd
def CreateClone(self):
return forex_sky_strategy()