XDPO蜡烛策略
该策略源自 MQL5 专家 Exp_XDPOCandle 的移植。它对开盘价和收盘价进行两次指数移动平均平滑,构建出合成蜡烛。合成蜡烛的颜色(多头、空头或中性)用于驱动交易决策。
策略逻辑
- 对每根市场蜡烛执行两次平滑:
- 第一次使用长度为
FastLength的 EMA; - 第二次对第一次的结果使用长度为
SlowLength的 EMA。
- 第一次使用长度为
- 若平滑后的收盘价高于开盘价,蜡烛视为多头;
- 若平滑后的收盘价低于开盘价,蜡烛视为空头;
- 当出现多头蜡烛且前一根蜡烛非多头时,开多仓;当出现空头蜡烛且前一根蜡烛非空头时,开空仓;
- 通过反向市价单自动平掉相反方向的持仓。
参数
| 名称 | 描述 |
|---|---|
FastLength |
第一次EMA的长度 |
SlowLength |
第二次EMA的长度 |
CandleType |
使用的蜡烛类型和时间框架 |
使用方法
- 在 StockSharp 环境中将策略附加到某个证券;
- 按需调整参数,默认值与原专家设置一致;
- 启动策略,它会订阅指定的蜡烛并在颜色变化时进行交易。
说明
- 风险管理通过默认的
StartProtection()完成,可根据需要调整Volume和其他保护参数; - 当前仅提供 C# 版本,尚无 Python 版本。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// XDPO candle strategy that trades on color changes of double smoothed candles.
/// </summary>
public class XdpoCandleStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<DataType> _candleType;
private decimal? _emaOpen1;
private decimal? _emaOpen2;
private decimal? _emaClose1;
private decimal? _emaClose2;
private int? _previousColor;
/// <summary>
/// Length of the first exponential moving average.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Length of the second exponential moving average.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="XdpoCandleStrategy"/>.
/// </summary>
public XdpoCandleStrategy()
{
_fastLength = Param(nameof(FastLength), 12)
.SetGreaterThanZero()
.SetDisplay("Fast Length", "Length of the first EMA", "Parameters")
;
_slowLength = Param(nameof(SlowLength), 5)
.SetGreaterThanZero()
.SetDisplay("Slow Length", "Length of the second EMA", "Parameters")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return new[] { (Security, CandleType) };
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_emaOpen1 = _emaOpen2 = _emaClose1 = _emaClose2 = null;
_previousColor = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var warmup = new StockSharp.Algo.Indicators.ExponentialMovingAverage { Length = FastLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(warmup, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal _warmupVal)
{
if (candle.State != CandleStates.Finished)
return;
var open1 = CalcEma(candle.OpenPrice, ref _emaOpen1, FastLength);
var open2 = CalcEma(open1, ref _emaOpen2, SlowLength);
var close1 = CalcEma(candle.ClosePrice, ref _emaClose1, FastLength);
var close2 = CalcEma(close1, ref _emaClose2, SlowLength);
var color = open2 < close2 ? 2 : open2 > close2 ? 0 : 1;
var goLong = color == 2 && _previousColor != 2;
var goShort = color == 0 && _previousColor != 0;
if (IsFormedAndOnlineAndAllowTrading())
{
if (goLong && Position <= 0)
BuyMarket();
else if (goShort && Position >= 0)
SellMarket();
}
_previousColor = color;
}
private static decimal CalcEma(decimal price, ref decimal? prev, int length)
{
var k = 2m / (length + 1m);
var result = prev.HasValue ? price * k + prev.Value * (1m - k) : price;
prev = result;
return result;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class xdpo_candle_strategy(Strategy):
def __init__(self):
super(xdpo_candle_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 12) \
.SetDisplay("Fast Length", "Length of the first EMA", "Parameters")
self._slow_length = self.Param("SlowLength", 5) \
.SetDisplay("Slow Length", "Length of the second EMA", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._ema_open1 = None
self._ema_open2 = None
self._ema_close1 = None
self._ema_close2 = None
self._previous_color = None
@property
def fast_length(self):
return self._fast_length.Value
@property
def slow_length(self):
return self._slow_length.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(xdpo_candle_strategy, self).OnReseted()
self._ema_open1 = None
self._ema_open2 = None
self._ema_close1 = None
self._ema_close2 = None
self._previous_color = None
def _calc_ema(self, price, prev, length):
k = 2.0 / (length + 1.0)
if prev is not None:
result = price * k + prev * (1.0 - k)
else:
result = price
return result
def OnStarted2(self, time):
super(xdpo_candle_strategy, self).OnStarted2(time)
warmup = ExponentialMovingAverage()
warmup.Length = self.fast_length
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(warmup, self.process_candle).Start()
def process_candle(self, candle, warmup_val):
if candle.State != CandleStates.Finished:
return
fast_len = int(self.fast_length)
slow_len = int(self.slow_length)
open_price = float(candle.OpenPrice)
close_price = float(candle.ClosePrice)
open1 = self._calc_ema(open_price, self._ema_open1, fast_len)
self._ema_open1 = open1
open2 = self._calc_ema(open1, self._ema_open2, slow_len)
self._ema_open2 = open2
close1 = self._calc_ema(close_price, self._ema_close1, fast_len)
self._ema_close1 = close1
close2 = self._calc_ema(close1, self._ema_close2, slow_len)
self._ema_close2 = close2
if open2 < close2:
color = 2
elif open2 > close2:
color = 0
else:
color = 1
go_long = color == 2 and self._previous_color != 2
go_short = color == 0 and self._previous_color != 0
if go_long and self.Position <= 0:
self.BuyMarket()
elif go_short and self.Position >= 0:
self.SellMarket()
self._previous_color = color
def CreateClone(self):
return xdpo_candle_strategy()