KlPrice Reversal 策略
该策略是 MQL5 专家 exp_i-KlPrice.mq5 的 C# 版本。它基于归一化价格振荡指标实现反转交易系统。振荡器将当前价格与由移动平均线和平均真实波幅(ATR)构建的平滑价格带进行比较。穿越预设边界会产生交易信号。
工作原理
简单移动平均线(SMA)平滑收盘价。
平均真实波幅(ATR)评估市场波动。
振荡器计算公式:
jres = 100 * (Close - (SMA - ATR)) / (2 * ATR) - 50振荡器数值分为五个区域:
- 4 – 高于上方阈值
- 3 – 介于零和上方阈值之间
- 2 – 介于上方和下方阈值之间
- 1 – 介于下方阈值和零之间
- 0 – 低于下方阈值
当振荡器离开区域 4 时开多单;离开区域 0 时开空单;当振荡器穿越零轴时平仓。
参数
| 名称 | 描述 |
|---|---|
CandleType |
计算所用的K线周期。 |
PriceMaLength |
平滑价格的 SMA 周期。 |
AtrLength |
ATR 周期,用于估算价格带。 |
UpLevel |
振荡器上方阈值。 |
DownLevel |
振荡器下方阈值。 |
EnableBuy |
允许开多头。 |
EnableSell |
允许开空头。 |
使用方法
- 创建
KlPriceReversalStrategy实例。 - 设置所需参数。
- 将策略连接到投资组合和证券。
- 启动策略以接收信号并发送订单。
策略通过 BuyMarket 和 SellMarket 提交市价单,并通过 StartProtection() 启用仓位保护。
说明
- 实现使用 StockSharp 内置指标
SimpleMovingAverage和AverageTrueRange,以近似原始 MQL 指标。 - 所有计算仅基于已完成的K线。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// KlPrice reversal strategy.
/// Calculates a normalized oscillator based on price position relative to EMA and ATR.
/// Buys when leaving overbought zone, sells when leaving oversold zone.
/// </summary>
public class KlPriceReversalStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _priceMaLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _upLevel;
private readonly StrategyParam<decimal> _downLevel;
private decimal _prevColor;
private bool _isFirst;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int PriceMaLength { get => _priceMaLength.Value; set => _priceMaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
public decimal UpLevel { get => _upLevel.Value; set => _upLevel.Value = value; }
public decimal DownLevel { get => _downLevel.Value; set => _downLevel.Value = value; }
public KlPriceReversalStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Time frame for calculations", "General");
_priceMaLength = Param(nameof(PriceMaLength), 100)
.SetGreaterThanZero()
.SetDisplay("Price MA Length", "EMA period for price smoothing", "Parameters");
_atrLength = Param(nameof(AtrLength), 20)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period for range estimation", "Parameters");
_upLevel = Param(nameof(UpLevel), 50m)
.SetDisplay("Upper Level", "Upper threshold for signals", "Parameters");
_downLevel = Param(nameof(DownLevel), -50m)
.SetDisplay("Lower Level", "Lower threshold for signals", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevColor = 2m;
_isFirst = true;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_isFirst = true;
_prevColor = 2m;
var priceMa = new ExponentialMovingAverage { Length = PriceMaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(priceMa, atr, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue maValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!maValue.IsFormed || !atrValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ma = maValue.ToDecimal();
var tr = atrValue.ToDecimal();
if (tr == 0m)
return;
var dwband = ma - tr;
var jres = 100m * (candle.ClosePrice - dwband) / (2m * tr) - 50m;
var color = 2m;
if (jres > UpLevel)
color = 4m;
else if (jres > 0m)
color = 3m;
if (jres < DownLevel)
color = 0m;
else if (jres < 0m)
color = 1m;
if (!_isFirst)
{
// Buy: leaving overbought (was 4, now < 4)
if (_prevColor == 4m && color < 4m && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
// Sell: leaving oversold (was 0, now > 0)
else if (_prevColor == 0m && color > 0m && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
}
_prevColor = color;
_isFirst = false;
}
}
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, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class kl_price_reversal_strategy(Strategy):
def __init__(self):
super(kl_price_reversal_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Time frame for calculations", "General")
self._price_ma_length = self.Param("PriceMaLength", 100) \
.SetDisplay("Price MA Length", "EMA period for price smoothing", "Parameters")
self._atr_length = self.Param("AtrLength", 20) \
.SetDisplay("ATR Length", "ATR period for range estimation", "Parameters")
self._up_level = self.Param("UpLevel", 50.0) \
.SetDisplay("Upper Level", "Upper threshold for signals", "Parameters")
self._down_level = self.Param("DownLevel", -50.0) \
.SetDisplay("Lower Level", "Lower threshold for signals", "Parameters")
self._prev_color = 2.0
self._is_first = True
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def PriceMaLength(self):
return self._price_ma_length.Value
@PriceMaLength.setter
def PriceMaLength(self, value):
self._price_ma_length.Value = value
@property
def AtrLength(self):
return self._atr_length.Value
@AtrLength.setter
def AtrLength(self, value):
self._atr_length.Value = value
@property
def UpLevel(self):
return self._up_level.Value
@UpLevel.setter
def UpLevel(self, value):
self._up_level.Value = value
@property
def DownLevel(self):
return self._down_level.Value
@DownLevel.setter
def DownLevel(self, value):
self._down_level.Value = value
def OnStarted2(self, time):
super(kl_price_reversal_strategy, self).OnStarted2(time)
self._is_first = True
self._prev_color = 2.0
price_ma = ExponentialMovingAverage()
price_ma.Length = self.PriceMaLength
atr = AverageTrueRange()
atr.Length = self.AtrLength
self.SubscribeCandles(self.CandleType) \
.BindEx(price_ma, atr, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, ma_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not ma_value.IsFormed or not atr_value.IsFormed:
return
ma = float(ma_value)
tr = float(atr_value)
if tr == 0:
return
close = float(candle.ClosePrice)
dwband = ma - tr
jres = 100.0 * (close - dwband) / (2.0 * tr) - 50.0
up = float(self.UpLevel)
dn = float(self.DownLevel)
color = 2.0
if jres > up:
color = 4.0
elif jres > 0:
color = 3.0
if jres < dn:
color = 0.0
elif jres < 0:
color = 1.0
if not self._is_first:
if self._prev_color == 4.0 and color < 4.0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_color == 0.0 and color > 0.0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_color = color
self._is_first = False
def OnReseted(self):
super(kl_price_reversal_strategy, self).OnReseted()
self._prev_color = 2.0
self._is_first = True
def CreateClone(self):
return kl_price_reversal_strategy()