StellarLite ICT EA 策略
概述
StellarLite ICT EA 策略把 "Stellar Lite" 挑战的人工主观流程迁移到 StockSharp 框架中。策略整合了 ICT (Inner Circle Trader) 的 Silver Bullet 与 2022 Model 两种入场模板,并自动执行原始 MetaTrader EA 中的分批止盈、保本和跟踪止损管理。
核心流程
- 高周期方向判断:在高周期蜡烛上计算移动平均线。当均线向交易方向倾斜且收盘价位于均线上方/下方时,才允许进入下一步分析。
- 流动性扫单验证:在可配置窗口内寻找最近高点或低点的扫单。Silver Bullet 需要沿着交易方向扫单,而 2022 Model 需要先出现反方向的诱导扫单。
- 结构转变 (MSS):最近三根完成蜡烛必须证明结构突破——多头需要收盘价创出上一根高点,空头则需要收盘价跌破上一根低点。
- 公平价值缺口 (FVG):扫描最近十根蜡烛是否存在由动量蜡烛形成的看涨/看跌缺口,且当前收盘价必须落在该缺口内部。
- 窄幅波动过滤 (NDOG/NWOG):当前蜡烛的高低价差必须小于
AtrThreshold × ATR,以确认市场处于压缩状态。 - 入场、止损与目标:入场价位于缺口中位或根据斐波那契 OTE 比例计算。止损放在近期流动性点之外,三组止盈按照设定的风险回报比计算。
- 仓位管理:根据风险百分比或策略 Volume 计算头寸大小。TP1、TP2、TP3 触发时分别平仓 50%、25%、25%,TP1 后可选地移动止损至保本并加上偏移量,TP2 后启动跟踪止损,若触发 TP3 或触发止损则清空剩余仓位。
参数
- Entry Candle (
CandleType) – 触发信号的低周期蜡烛类型。 - Higher Timeframe (
HigherTimeframeType) – 用于方向判定的高周期蜡烛类型。 - Higher MA Period (
HigherMaPeriod) – 高周期均线周期。 - ATR Period (
AtrPeriod) – ATR 指标的回溯长度。 - Liquidity Lookback (
LiquidityLookback) – 搜索流动性池的蜡烛数量。 - ATR Threshold (
AtrThreshold) – 蜡烛允许的最大波幅(相对于 ATR 的倍数)。 - TP1/TP2/TP3 Risk Reward (
Tp1Ratio,Tp2Ratio,Tp3Ratio) – 各级止盈的风险回报倍数。 - TP1/TP2/TP3 Close % (
Tp1Percent,Tp2Percent,Tp3Percent) – 各级止盈的平仓比例。 - Break Even After TP1 (
MoveToBreakEven) – TP1 后是否移动止损到保本。 - Break Even Offset (
BreakEvenOffset) – 保本止损的额外价格步进数量。 - Trailing Distance (
TrailingDistance) – TP2 之后启动的跟踪止损步进。 - Use Silver Bullet / Use 2022 Model (
UseSilverBullet,Use2022Model) – 是否启用对应模板。 - Use OTE Entry (
UseOteEntry) – 是否使用 OTE 回撤区间计算入场价。 - Risk % (
RiskPercent) – 每笔交易的账户风险百分比,用于计算手数。 - OTE Lower (
OteLowerLevel) – OTE 区间的斐波那契比例。
使用建议
- 策略仅在完成蜡烛上运作,需要行情源提供收盘价与最小价格步长/手数信息。
- 若无法获取投资组合市值或最小变动价值,则退回到策略的
Volume设置计算手数。 - 流动性与 MSS 逻辑依赖最近 20 根蜡烛缓存,启动后需要等待数据累积。
- 分批平仓会遵循交易品种的最小手数,如果比例过小将跳过执行。
- 跟踪止损仅向盈利方向移动,不会放宽现有风险控制。
文件
CS/StellarLiteIctEaStrategy.cs– 策略代码实现。README.md– 英文说明。README_zh.md– 中文说明。README_ru.md– 俄文说明。
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>
/// Stellar Lite ICT strategy that combines Silver Bullet and 2022 model setups.
/// The strategy reads ICT style order flow concepts on finished candles
/// and places partial take profits with adaptive stop management.
/// </summary>
public class StellarLiteIctEaStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DataType> _higherTimeframeType;
private readonly StrategyParam<int> _higherMaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _liquidityLookback;
private readonly StrategyParam<decimal> _atrThreshold;
private readonly StrategyParam<decimal> _tp1Ratio;
private readonly StrategyParam<decimal> _tp2Ratio;
private readonly StrategyParam<decimal> _tp3Ratio;
private readonly StrategyParam<decimal> _tp1Percent;
private readonly StrategyParam<decimal> _tp2Percent;
private readonly StrategyParam<decimal> _tp3Percent;
private readonly StrategyParam<bool> _moveToBreakEven;
private readonly StrategyParam<decimal> _breakEvenOffset;
private readonly StrategyParam<decimal> _trailingDistance;
private readonly StrategyParam<bool> _useSilverBullet;
private readonly StrategyParam<bool> _use2022Model;
private readonly StrategyParam<bool> _useOteEntry;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<decimal> _oteLowerLevel;
private SimpleMovingAverage _higherMa;
private AverageTrueRange _atr;
private decimal? _lastHtfMa;
private decimal? _previousHtfMa;
private Sides? _currentBias;
private readonly ICandleMessage[] _history = new ICandleMessage[20];
private int _historyCount;
private decimal _latestAtr;
/// <summary>
/// Initializes a new instance of the <see cref="StellarLiteIctEaStrategy"/>.
/// </summary>
public StellarLiteIctEaStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Entry Candle", "Primary timeframe used for entries", "General");
_higherTimeframeType = Param(nameof(HigherTimeframeType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Higher Timeframe", "Timeframe used for directional bias", "General");
_higherMaPeriod = Param(nameof(HigherMaPeriod), 20)
.SetDisplay("Higher MA Period", "Moving average length for higher timeframe bias", "Bias")
;
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "Average True Range lookback", "Volatility")
;
_liquidityLookback = Param(nameof(LiquidityLookback), 20)
.SetDisplay("Liquidity Lookback", "Number of candles to detect liquidity pools", "Structure")
;
_atrThreshold = Param(nameof(AtrThreshold), 2.0m)
.SetDisplay("ATR Threshold", "Maximum candle range relative to ATR", "Structure")
;
_tp1Ratio = Param(nameof(Tp1Ratio), 1m)
.SetDisplay("TP1 Risk Reward", "Risk reward multiplier for the first target", "Targets")
;
_tp2Ratio = Param(nameof(Tp2Ratio), 2m)
.SetDisplay("TP2 Risk Reward", "Risk reward multiplier for the second target", "Targets")
;
_tp3Ratio = Param(nameof(Tp3Ratio), 3m)
.SetDisplay("TP3 Risk Reward", "Risk reward multiplier for the final target", "Targets")
;
_tp1Percent = Param(nameof(Tp1Percent), 50m)
.SetDisplay("TP1 Close %", "Percentage of volume closed at the first target", "Targets")
;
_tp2Percent = Param(nameof(Tp2Percent), 25m)
.SetDisplay("TP2 Close %", "Percentage of volume closed at the second target", "Targets")
;
_tp3Percent = Param(nameof(Tp3Percent), 25m)
.SetDisplay("TP3 Close %", "Percentage of volume closed at the final target", "Targets")
;
_moveToBreakEven = Param(nameof(MoveToBreakEven), true)
.SetDisplay("Break Even After TP1", "Move the stop to break even after the first partial", "Protection");
_breakEvenOffset = Param(nameof(BreakEvenOffset), 1m)
.SetDisplay("Break Even Offset", "Additional price steps added to the break even stop", "Protection")
;
_trailingDistance = Param(nameof(TrailingDistance), 10m)
.SetDisplay("Trailing Distance", "Price steps used after TP2 for trailing stop", "Protection")
;
_useSilverBullet = Param(nameof(UseSilverBullet), true)
.SetDisplay("Use Silver Bullet", "Enable the Silver Bullet setup", "Structure");
_use2022Model = Param(nameof(Use2022Model), true)
.SetDisplay("Use 2022 Model", "Enable the 2022 model setup", "Structure");
_useOteEntry = Param(nameof(UseOteEntry), true)
.SetDisplay("Use OTE Entry", "Place entries inside the optimal trade entry zone", "Structure");
_riskPercent = Param(nameof(RiskPercent), 0.25m)
.SetDisplay("Risk %", "Risk percentage of account equity used to size trades", "Risk")
;
_oteLowerLevel = Param(nameof(OteLowerLevel), 0.618m)
.SetDisplay("OTE Lower", "Lower Fibonacci level used for the entry", "Structure")
;
}
/// <summary>
/// Primary candle type used to generate entries.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Higher timeframe candle type that provides directional bias.
/// </summary>
public DataType HigherTimeframeType
{
get => _higherTimeframeType.Value;
set => _higherTimeframeType.Value = value;
}
/// <summary>
/// Higher timeframe moving average period.
/// </summary>
public int HigherMaPeriod
{
get => _higherMaPeriod.Value;
set => _higherMaPeriod.Value = value;
}
/// <summary>
/// ATR calculation period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Number of candles used to search for liquidity pools.
/// </summary>
public int LiquidityLookback
{
get => _liquidityLookback.Value;
set => _liquidityLookback.Value = value;
}
/// <summary>
/// Maximum allowed candle range relative to ATR to confirm consolidation.
/// </summary>
public decimal AtrThreshold
{
get => _atrThreshold.Value;
set => _atrThreshold.Value = value;
}
/// <summary>
/// Risk reward multiplier for the first target.
/// </summary>
public decimal Tp1Ratio
{
get => _tp1Ratio.Value;
set => _tp1Ratio.Value = value;
}
/// <summary>
/// Risk reward multiplier for the second target.
/// </summary>
public decimal Tp2Ratio
{
get => _tp2Ratio.Value;
set => _tp2Ratio.Value = value;
}
/// <summary>
/// Risk reward multiplier for the third target.
/// </summary>
public decimal Tp3Ratio
{
get => _tp3Ratio.Value;
set => _tp3Ratio.Value = value;
}
/// <summary>
/// Percentage of the position closed at TP1.
/// </summary>
public decimal Tp1Percent
{
get => _tp1Percent.Value;
set => _tp1Percent.Value = value;
}
/// <summary>
/// Percentage of the position closed at TP2.
/// </summary>
public decimal Tp2Percent
{
get => _tp2Percent.Value;
set => _tp2Percent.Value = value;
}
/// <summary>
/// Percentage of the position closed at TP3.
/// </summary>
public decimal Tp3Percent
{
get => _tp3Percent.Value;
set => _tp3Percent.Value = value;
}
/// <summary>
/// Enables moving the stop to break even after TP1.
/// </summary>
public bool MoveToBreakEven
{
get => _moveToBreakEven.Value;
set => _moveToBreakEven.Value = value;
}
/// <summary>
/// Additional price steps added to the break even stop.
/// </summary>
public decimal BreakEvenOffset
{
get => _breakEvenOffset.Value;
set => _breakEvenOffset.Value = value;
}
/// <summary>
/// Distance in price steps for the trailing stop activated after TP2.
/// </summary>
public decimal TrailingDistance
{
get => _trailingDistance.Value;
set => _trailingDistance.Value = value;
}
/// <summary>
/// Enables the Silver Bullet setup.
/// </summary>
public bool UseSilverBullet
{
get => _useSilverBullet.Value;
set => _useSilverBullet.Value = value;
}
/// <summary>
/// Enables the 2022 model setup.
/// </summary>
public bool Use2022Model
{
get => _use2022Model.Value;
set => _use2022Model.Value = value;
}
/// <summary>
/// Enables the optimal trade entry calculation.
/// </summary>
public bool UseOteEntry
{
get => _useOteEntry.Value;
set => _useOteEntry.Value = value;
}
/// <summary>
/// Risk percentage used for dynamic position sizing.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Lower bound of the OTE retracement window.
/// </summary>
public decimal OteLowerLevel
{
get => _oteLowerLevel.Value;
set => _oteLowerLevel.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, HigherTimeframeType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_higherMa = null;
_atr = null;
_lastHtfMa = null;
_previousHtfMa = null;
_currentBias = null;
Array.Clear(_history, 0, _history.Length);
_historyCount = 0;
_latestAtr = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_higherMa = new SimpleMovingAverage { Length = HigherMaPeriod };
_atr = new AverageTrueRange { Length = AtrPeriod };
Indicators.Add(_atr);
var mainSubscription = SubscribeCandles(CandleType);
mainSubscription
.Bind(ProcessMainCandle)
.Start();
var higherSubscription = SubscribeCandles(HigherTimeframeType);
higherSubscription
.Bind(_higherMa, ProcessHigherCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, mainSubscription);
DrawOwnTrades(area);
}
}
private void ProcessHigherCandle(ICandleMessage candle, decimal maValue)
{
if (candle.State != CandleStates.Finished)
return;
_previousHtfMa = _lastHtfMa;
_lastHtfMa = maValue;
if (_previousHtfMa is not decimal prev || _lastHtfMa is not decimal current)
return;
if (candle.ClosePrice > current && current > prev)
{
_currentBias = Sides.Buy;
}
else if (candle.ClosePrice < current && current < prev)
{
_currentBias = Sides.Sell;
}
else
{
_currentBias = null;
}
}
private void ProcessMainCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var atrValue = _atr.Process(candle);
StoreCandle(candle);
if (!_atr.IsFormed)
return;
_latestAtr = atrValue.ToDecimal();
if (Position != 0)
return;
if (_currentBias is not Sides bias)
return;
if (_historyCount < 3)
return;
// Simplified ICT entry: market structure shift + bias alignment
var prev = _history[1];
var prev2 = _history[2];
if (prev == null || prev2 == null)
return;
if (bias == Sides.Buy)
{
// Bullish MSS: current close breaks above previous high after a down move
if (candle.ClosePrice > prev.HighPrice && prev.ClosePrice < prev2.OpenPrice)
BuyMarket();
}
else
{
// Bearish MSS: current close breaks below previous low after an up move
if (candle.ClosePrice < prev.LowPrice && prev.ClosePrice > prev2.OpenPrice)
SellMarket();
}
}
private void StoreCandle(ICandleMessage candle)
{
for (var i = _history.Length - 1; i > 0; i--)
{
_history[i] = _history[i - 1];
}
_history[0] = candle;
if (_historyCount < _history.Length)
_historyCount++;
}
}
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, Sides, Unit, UnitTypes
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class stellar_lite_ict_ea_strategy(Strategy):
"""Stellar Lite ICT strategy with higher timeframe MA bias and market structure shift entries."""
def __init__(self):
super(stellar_lite_ict_ea_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Entry Candle", "Primary timeframe used for entries", "General")
self._higher_timeframe_type = self.Param("HigherTimeframeType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Higher Timeframe", "Timeframe used for directional bias", "General")
self._higher_ma_period = self.Param("HigherMaPeriod", 20) \
.SetDisplay("Higher MA Period", "Moving average length for higher timeframe bias", "Bias")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Average True Range lookback", "Volatility")
self._last_htf_ma = None
self._previous_htf_ma = None
self._current_bias = None
self._history = [None] * 20
self._history_count = 0
self._latest_atr = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def HigherTimeframeType(self):
return self._higher_timeframe_type.Value
@HigherTimeframeType.setter
def HigherTimeframeType(self, value):
self._higher_timeframe_type.Value = value
@property
def HigherMaPeriod(self):
return self._higher_ma_period.Value
@property
def AtrPeriod(self):
return self._atr_period.Value
def OnReseted(self):
super(stellar_lite_ict_ea_strategy, self).OnReseted()
self._last_htf_ma = None
self._previous_htf_ma = None
self._current_bias = None
self._history = [None] * 20
self._history_count = 0
self._latest_atr = 0.0
def OnStarted2(self, time):
super(stellar_lite_ict_ea_strategy, self).OnStarted2(time)
higher_ma = SimpleMovingAverage()
higher_ma.Length = self.HigherMaPeriod
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
main_subscription = self.SubscribeCandles(self.CandleType)
main_subscription.Bind(atr, self._process_main_candle).Start()
higher_subscription = self.SubscribeCandles(self.HigherTimeframeType)
higher_subscription.Bind(higher_ma, self._process_higher_candle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent))
def _process_higher_candle(self, candle, ma_value):
if candle.State != CandleStates.Finished:
return
self._previous_htf_ma = self._last_htf_ma
self._last_htf_ma = float(ma_value)
if self._previous_htf_ma is None or self._last_htf_ma is None:
return
prev = self._previous_htf_ma
current = self._last_htf_ma
close = float(candle.ClosePrice)
if close > current and current > prev:
self._current_bias = Sides.Buy
elif close < current and current < prev:
self._current_bias = Sides.Sell
else:
self._current_bias = None
def _process_main_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
self._store_candle(candle)
self._latest_atr = float(atr_value)
if self.Position != 0:
return
if self._current_bias is None:
return
if self._history_count < 3:
return
prev = self._history[1]
prev2 = self._history[2]
if prev is None or prev2 is None:
return
bias = self._current_bias
if bias == Sides.Buy:
if float(candle.ClosePrice) > float(prev.HighPrice) and float(prev.ClosePrice) < float(prev2.OpenPrice):
self.BuyMarket()
else:
if float(candle.ClosePrice) < float(prev.LowPrice) and float(prev.ClosePrice) > float(prev2.OpenPrice):
self.SellMarket()
def _store_candle(self, candle):
i = len(self._history) - 1
while i > 0:
self._history[i] = self._history[i - 1]
i -= 1
self._history[0] = candle
if self._history_count < len(self._history):
self._history_count += 1
def CreateClone(self):
return stellar_lite_ict_ea_strategy()