Ilan Dynamic HT 策略
基于网格的马丁格尔策略,通过 RSI 信号开仓,并利用动态价格区间扩大持仓。每增加一笔交易,仓位按倍数增长,共享同一个止盈和止损。
详情
- 入场条件:
- 多头:RSI 低于
RsiMinimum - 空头:RSI 高于
RsiMaximum
- 多头:RSI 低于
- 多/空:多头和空头
- 出场条件:
- 达到统一的止盈或止损
- 止损:
TakeProfit(点)StopLoss(点)
- 默认值:
LotExponent= 1.4MaxTrades= 10DynamicPips= trueDefaultPips= 120Depth= 24Del= 3BaseVolume= 0.1RsiPeriod= 14RsiMinimum= 30RsiMaximum= 70TakeProfit= 100StopLoss= 500CandleType= TimeSpan.FromMinutes(15).TimeFrame()
- 过滤器:
- 分类:网格 / 马丁格尔
- 方向:多头和空头
- 指标:RSI, Highest, Lowest
- 止损:止盈, 止损
- 复杂度:高级
- 时间框架:中期
- 季节性:无
- 神经网络:无
- 背离:无
- 风险等级:高
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Ilan Dynamic HT Strategy.
/// </summary>
public class IlanDynamicHtStrategy : Strategy
{
private readonly StrategyParam<decimal> _lotExponent;
private readonly StrategyParam<int> _maxTrades;
private readonly StrategyParam<bool> _dynamicPips;
private readonly StrategyParam<int> _defaultPips;
private readonly StrategyParam<int> _depth;
private readonly StrategyParam<int> _del;
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiMin;
private readonly StrategyParam<decimal> _rsiMax;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi;
private Highest _highest;
private Lowest _lowest;
private decimal _avgPrice;
private decimal _lastPrice;
private decimal _totalVolume;
private int _tradeCount;
private decimal _step;
public IlanDynamicHtStrategy()
{
_lotExponent = Param(nameof(LotExponent), 1.4m)
.SetDisplay("Lot Exponent", "Multiplier for next position volume", "General");
_maxTrades = Param(nameof(MaxTrades), 4)
.SetDisplay("Max Trades", "Maximum simultaneous trades", "General");
_dynamicPips = Param(nameof(DynamicPips), true)
.SetDisplay("Dynamic Range", "Use dynamic price range", "General");
_defaultPips = Param(nameof(DefaultPips), 120)
.SetDisplay("Default Range", "Static price range in points", "General");
_depth = Param(nameof(Depth), 24)
.SetDisplay("Depth", "Number of bars for range calculation", "General");
_del = Param(nameof(Del), 3)
.SetDisplay("Divider", "Range divider factor", "General");
_baseVolume = Param(nameof(BaseVolume), 0.1m)
.SetDisplay("Base Volume", "Initial trade volume", "Trading");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetDisplay("RSI Period", "Period for RSI indicator", "Signals");
_rsiMin = Param(nameof(RsiMinimum), 20m)
.SetDisplay("RSI Minimum", "Lower RSI bound", "Signals");
_rsiMax = Param(nameof(RsiMaximum), 80m)
.SetDisplay("RSI Maximum", "Upper RSI bound", "Signals");
_takeProfit = Param(nameof(TakeProfit), 100m)
.SetDisplay("Take Profit", "Take profit in points", "Risk");
_stopLoss = Param(nameof(StopLoss), 500m)
.SetDisplay("Stop Loss", "Stop loss in points", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for processing", "General");
}
public decimal LotExponent { get => _lotExponent.Value; set => _lotExponent.Value = value; }
public int MaxTrades { get => _maxTrades.Value; set => _maxTrades.Value = value; }
public bool DynamicPips { get => _dynamicPips.Value; set => _dynamicPips.Value = value; }
public int DefaultPips { get => _defaultPips.Value; set => _defaultPips.Value = value; }
public int Depth { get => _depth.Value; set => _depth.Value = value; }
public int Del { get => _del.Value; set => _del.Value = value; }
public decimal BaseVolume { get => _baseVolume.Value; set => _baseVolume.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public decimal RsiMinimum { get => _rsiMin.Value; set => _rsiMin.Value = value; }
public decimal RsiMaximum { get => _rsiMax.Value; set => _rsiMax.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_avgPrice = 0m;
_lastPrice = 0m;
_totalVolume = 0m;
_tradeCount = 0;
_step = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_highest = new Highest { Length = Depth };
_lowest = new Lowest { Length = Depth };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _highest, _lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal highestValue, decimal lowestValue)
{
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (candle.State != CandleStates.Finished)
return;
if (DynamicPips)
{
var range = highestValue - lowestValue;
_step = range / Del;
}
else
{
_step = DefaultPips * (Security?.PriceStep ?? 1m);
}
// Entry signals
if (Position == 0)
{
if (rsiValue <= RsiMinimum)
{
OpenPosition(true, candle.ClosePrice);
}
else if (rsiValue >= RsiMaximum)
{
OpenPosition(false, candle.ClosePrice);
}
return;
}
// Add positions when price moves against us
if (_tradeCount < MaxTrades)
{
if (Position > 0 && candle.ClosePrice <= _lastPrice - _step)
{
AddPosition(true, candle.ClosePrice);
}
else if (Position < 0 && candle.ClosePrice >= _lastPrice + _step)
{
AddPosition(false, candle.ClosePrice);
}
}
var profit = Position > 0 ? candle.ClosePrice - _avgPrice : _avgPrice - candle.ClosePrice;
var takeProfit = TakeProfit * (Security?.PriceStep ?? 1m);
var stopLoss = StopLoss * (Security?.PriceStep ?? 1m);
if (profit >= takeProfit)
{
CloseAll();
}
else if (profit <= -stopLoss)
{
CloseAll();
}
}
private void OpenPosition(bool isLong, decimal price)
{
var volume = BaseVolume;
if (isLong)
BuyMarket(volume);
else
SellMarket(volume);
_avgPrice = price;
_lastPrice = price;
_totalVolume = volume;
_tradeCount = 1;
}
private void AddPosition(bool isLong, decimal price)
{
var volume = BaseVolume * (decimal)Math.Pow((double)LotExponent, _tradeCount);
if (isLong)
BuyMarket(volume);
else
SellMarket(volume);
_avgPrice = (_avgPrice * _totalVolume + price * volume) / (_totalVolume + volume);
_totalVolume += volume;
_lastPrice = price;
_tradeCount++;
}
private void CloseAll()
{
if (Position > 0)
{
SellMarket(Position);
}
else if (Position < 0)
{
BuyMarket(-Position);
}
_avgPrice = 0m;
_lastPrice = 0m;
_totalVolume = 0m;
_tradeCount = 0;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class ilan_dynamic_ht_strategy(Strategy):
def __init__(self):
super(ilan_dynamic_ht_strategy, self).__init__()
self._lot_exponent = self.Param("LotExponent", 1.4) \
.SetDisplay("Lot Exponent", "Multiplier for next position volume", "General")
self._max_trades = self.Param("MaxTrades", 4) \
.SetDisplay("Max Trades", "Maximum simultaneous trades", "General")
self._dynamic_pips = self.Param("DynamicPips", True) \
.SetDisplay("Dynamic Range", "Use dynamic price range", "General")
self._default_pips = self.Param("DefaultPips", 120) \
.SetDisplay("Default Range", "Static price range in points", "General")
self._depth = self.Param("Depth", 24) \
.SetDisplay("Depth", "Number of bars for range calculation", "General")
self._del = self.Param("Del", 3) \
.SetDisplay("Divider", "Range divider factor", "General")
self._base_volume = self.Param("BaseVolume", 0.1) \
.SetDisplay("Base Volume", "Initial trade volume", "Trading")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "Period for RSI indicator", "Signals")
self._rsi_min = self.Param("RsiMinimum", 20.0) \
.SetDisplay("RSI Minimum", "Lower RSI bound", "Signals")
self._rsi_max = self.Param("RsiMaximum", 80.0) \
.SetDisplay("RSI Maximum", "Upper RSI bound", "Signals")
self._take_profit = self.Param("TakeProfit", 100.0) \
.SetDisplay("Take Profit", "Take profit in points", "Risk")
self._stop_loss = self.Param("StopLoss", 500.0) \
.SetDisplay("Stop Loss", "Stop loss in points", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for processing", "General")
self._avg_price = 0.0
self._last_price = 0.0
self._total_volume = 0.0
self._trade_count = 0
self._step = 0.0
@property
def LotExponent(self):
return self._lot_exponent.Value
@LotExponent.setter
def LotExponent(self, value):
self._lot_exponent.Value = value
@property
def MaxTrades(self):
return self._max_trades.Value
@MaxTrades.setter
def MaxTrades(self, value):
self._max_trades.Value = value
@property
def DynamicPips(self):
return self._dynamic_pips.Value
@DynamicPips.setter
def DynamicPips(self, value):
self._dynamic_pips.Value = value
@property
def DefaultPips(self):
return self._default_pips.Value
@DefaultPips.setter
def DefaultPips(self, value):
self._default_pips.Value = value
@property
def Depth(self):
return self._depth.Value
@Depth.setter
def Depth(self, value):
self._depth.Value = value
@property
def Del(self):
return self._del.Value
@Del.setter
def Del(self, value):
self._del.Value = value
@property
def BaseVolume(self):
return self._base_volume.Value
@BaseVolume.setter
def BaseVolume(self, value):
self._base_volume.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiMinimum(self):
return self._rsi_min.Value
@RsiMinimum.setter
def RsiMinimum(self, value):
self._rsi_min.Value = value
@property
def RsiMaximum(self):
return self._rsi_max.Value
@RsiMaximum.setter
def RsiMaximum(self, value):
self._rsi_max.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(ilan_dynamic_ht_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
highest = Highest()
highest.Length = self.Depth
lowest = Lowest()
lowest.Length = self.Depth
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(rsi, highest, lowest, self.ProcessCandle) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, rsi_value, highest_value, lowest_value):
if candle.State != CandleStates.Finished:
return
rsi_val = float(rsi_value)
high_val = float(highest_value)
low_val = float(lowest_value)
close = float(candle.ClosePrice)
if self.DynamicPips:
rng = high_val - low_val
self._step = rng / float(self.Del) if self.Del != 0 else rng
else:
sec = self.Security
price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
self._step = float(self.DefaultPips) * price_step
pos = self.Position
# Entry signals
if pos == 0:
if rsi_val <= float(self.RsiMinimum):
self._open_position(True, close)
elif rsi_val >= float(self.RsiMaximum):
self._open_position(False, close)
return
# Add positions when price moves against us
if self._trade_count < self.MaxTrades:
if pos > 0 and close <= self._last_price - self._step:
self._add_position(True, close)
elif pos < 0 and close >= self._last_price + self._step:
self._add_position(False, close)
profit = close - self._avg_price if pos > 0 else self._avg_price - close
sec = self.Security
price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
tp = float(self.TakeProfit) * price_step
sl = float(self.StopLoss) * price_step
if profit >= tp:
self._close_all()
elif profit <= -sl:
self._close_all()
def _open_position(self, is_long, price):
volume = float(self.BaseVolume)
if is_long:
self.BuyMarket(volume)
else:
self.SellMarket(volume)
self._avg_price = price
self._last_price = price
self._total_volume = volume
self._trade_count = 1
def _add_position(self, is_long, price):
volume = float(self.BaseVolume) * (float(self.LotExponent) ** self._trade_count)
if is_long:
self.BuyMarket(volume)
else:
self.SellMarket(volume)
self._avg_price = (self._avg_price * self._total_volume + price * volume) / (self._total_volume + volume)
self._total_volume += volume
self._last_price = price
self._trade_count += 1
def _close_all(self):
pos = self.Position
if pos > 0:
self.SellMarket(pos)
elif pos < 0:
self.BuyMarket(-pos)
self._avg_price = 0.0
self._last_price = 0.0
self._total_volume = 0.0
self._trade_count = 0
def OnReseted(self):
super(ilan_dynamic_ht_strategy, self).OnReseted()
self._avg_price = 0.0
self._last_price = 0.0
self._total_volume = 0.0
self._trade_count = 0
self._step = 0.0
def CreateClone(self):
return ilan_dynamic_ht_strategy()