在 GitHub 上查看
会合线随机指标策略
概述
会合线随机指标策略 复刻了 MetaTrader 专家顾问 Expert_AML_Stoch 的思想,通过 StockSharp 高层 API 实现。策略利用看涨/看跌会合线蜡烛形态作为方向线索,并使用随机振荡指标的 %D 信号线进行动量确认。借助高阶 API,逻辑保持模块化,便于在 StockSharp Designer 中优化或接入自定义组合管理。
交易逻辑
蜡烛形态过滤
- 连续监测最近两根收盘蜡烛是否构成会合线形态。
- 看涨条件:先出现一根实体较长的阴线,随后一根实体较长的阳线,其收盘价与前一根的收盘价差距不超过平均实体的 10%。
- 看跌条件:先出现实体较长的阳线,再出现实体较长的阴线,且两根蜡烛的收盘价差距满足同样的 10% 限制。
- 为避免弱信号,使用可配置的简单移动平均对蜡烛实体长度进行平滑,等价于 MQL 中的
AvgBody 计算。
随机指标确认
- 仅使用随机指标的 %D 线进行过滤,与原版 EA 保持一致。
- 看涨入场需要 %D 低于可调的超卖阈值(默认 30)。
- 看跌入场要求 %D 高于可调的超买阈值(默认 70)。
离场与反向
- 空头仓位在 %D 向上穿越下轨(默认 20)或上轨(默认 80)时平仓。
- 多头仓位在 %D 向下跌破同样的上下阈值时退出。
- 当出现反向信号时,策略自动以足够的手数平掉现有仓位并建立新仓,确保持仓方向正确。
交易量处理
- 若
Volume 参数为正,则直接使用该值;否则默认使用 1 手,以模拟原 EA 的固定手数资金管理。
参数说明
| 名称 |
描述 |
默认值 |
备注 |
CandleType |
参与计算的主 K 线数据类型。 |
15 分钟 |
可选择任意 DataType,包括不同周期或成交量 K 线。 |
StochasticLength |
原始 %K 计算的回溯周期。 |
3 |
对应 MetaTrader 的 %K period。 |
StochasticSmoothing |
%K 平滑周期(MetaTrader 中的 slowing)。 |
25 |
控制内部平滑强度。 |
StochasticSignal |
%D 信号线的平滑周期。 |
36 |
对应 MetaTrader 的 %D period。 |
BodyAveragePeriod |
计算蜡烛实体平均值的周期长度。 |
3 |
抑制噪声形态。 |
LongEntryLevel |
看涨入场时允许的最大 %D 数值。 |
30 |
等同于超卖阈值。 |
ShortEntryLevel |
看跌入场所需的最小 %D 数值。 |
70 |
等同于超买阈值。 |
ExitLowerLevel |
触发离场的下阈值。 |
20 |
多空共用,用于检测向上突破。 |
ExitUpperLevel |
触发离场的上阈值。 |
80 |
多空共用,用于检测向下跌破。 |
所有参数都通过 StrategyParam<T> 暴露,既可以在 Designer 中优化,也可以在代码中动态调整。
信号定义
- 开多:识别到看涨会合线且上一根蜡烛的 %D 低于
LongEntryLevel,若当前无多单则开仓,若存在空单则先平空再开多。
- 开空:识别到看跌会合线且 %D 高于
ShortEntryLevel,若当前无空单则开仓,若存在多单则先平多再开空。
- 平多:%D 自上而下穿越
ExitUpperLevel 或 ExitLowerLevel。
- 平空:%D 自下而上突破
ExitLowerLevel 或 ExitUpperLevel。
实现细节
- 使用
SubscribeCandles + BindEx 将随机指标值直接传递给处理函数,无需手动管理指示器集合。
- 蜡烛实体平均值通过
SimpleMovingAverage 和 DecimalIndicatorValue 计算,完整复现 MQL 中的 AvgBody 逻辑。
- 所有源码注释均为英文,缩进严格使用制表符,符合根目录
AGENTS.md 的规范。
- 若创建了图表区域,策略会自动绘制 K 线与随机指标,方便实时监控。
使用建议
- 参数优化:针对不同品种或周期执行滚动优化,调整 %D 阈值和平均周期。
- 风险控制:可结合 StockSharp 的
StartProtection 或外部资金管理模块设定止盈/止损。
- 数据质量:会合线对开收盘价敏感,建议过滤流动性差或缺口较大的交易时段。
- 多周期应用:可将
CandleType 切换为更长或更短周期,以匹配不同交易风格。
通过该策略,交易者可以将传统蜡烛形态与动量确认结合起来,获得更加客观的入场与离场信号。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Meeting Lines + Stochastic strategy.
/// Buys on bullish meeting lines with low stochastic, sells on bearish meeting lines with high stochastic.
/// </summary>
public class MeetingLinesStochasticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<decimal> _stochLow;
private readonly StrategyParam<decimal> _stochHigh;
private ICandleMessage _prevCandle;
private ICandleMessage _prevPrevCandle;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int StochPeriod { get => _stochPeriod.Value; set => _stochPeriod.Value = value; }
public decimal StochLow { get => _stochLow.Value; set => _stochLow.Value = value; }
public decimal StochHigh { get => _stochHigh.Value; set => _stochHigh.Value = value; }
public MeetingLinesStochasticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_stochPeriod = Param(nameof(StochPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Stochastic K period", "Indicators");
_stochLow = Param(nameof(StochLow), 30m)
.SetDisplay("Stoch Low", "Stochastic oversold level", "Signals");
_stochHigh = Param(nameof(StochHigh), 70m)
.SetDisplay("Stoch High", "Stochastic overbought level", "Signals");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCandle = null;
_prevPrevCandle = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCandle = null;
_prevPrevCandle = null;
var stoch = new StochasticOscillator { K = { Length = StochPeriod }, D = { Length = 3 } };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(stoch, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished) return;
var stochTyped = stochValue as StochasticOscillatorValue;
if (stochTyped?.K is not decimal kValue) { UpdateState(candle); return; }
if (_prevCandle != null && _prevPrevCandle != null)
{
var avgBody = (Math.Abs(_prevCandle.ClosePrice - _prevCandle.OpenPrice) +
Math.Abs(_prevPrevCandle.ClosePrice - _prevPrevCandle.OpenPrice)) / 2m;
if (avgBody > 0)
{
// Bullish meeting lines: prev bearish, current bullish, closes near
var prevBearish = _prevCandle.OpenPrice > _prevCandle.ClosePrice &&
(_prevCandle.OpenPrice - _prevCandle.ClosePrice) > avgBody * 0.5m;
var currBullish = candle.ClosePrice > candle.OpenPrice &&
(candle.ClosePrice - candle.OpenPrice) > avgBody * 0.5m;
var closesNear = Math.Abs(candle.ClosePrice - _prevCandle.ClosePrice) < avgBody * 0.3m;
if (prevBearish && currBullish && closesNear && kValue < StochLow && Position <= 0)
BuyMarket();
// Bearish meeting lines: prev bullish, current bearish, closes near
var prevBullish = _prevCandle.ClosePrice > _prevCandle.OpenPrice &&
(_prevCandle.ClosePrice - _prevCandle.OpenPrice) > avgBody * 0.5m;
var currBearish = candle.OpenPrice > candle.ClosePrice &&
(candle.OpenPrice - candle.ClosePrice) > avgBody * 0.5m;
var closesNear2 = Math.Abs(candle.ClosePrice - _prevCandle.ClosePrice) < avgBody * 0.3m;
if (prevBullish && currBearish && closesNear2 && kValue > StochHigh && Position >= 0)
SellMarket();
}
}
UpdateState(candle);
}
private void UpdateState(ICandleMessage candle)
{
_prevPrevCandle = _prevCandle;
_prevCandle = candle;
}
}
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 StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class meeting_lines_stochastic_strategy(Strategy):
def __init__(self):
super(meeting_lines_stochastic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._stoch_period = self.Param("StochPeriod", 14)
self._stoch_low = self.Param("StochLow", 30.0)
self._stoch_high = self.Param("StochHigh", 70.0)
self._prev_candle = None
self._prev_prev_candle = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def StochPeriod(self):
return self._stoch_period.Value
@StochPeriod.setter
def StochPeriod(self, value):
self._stoch_period.Value = value
@property
def StochLow(self):
return self._stoch_low.Value
@StochLow.setter
def StochLow(self, value):
self._stoch_low.Value = value
@property
def StochHigh(self):
return self._stoch_high.Value
@StochHigh.setter
def StochHigh(self, value):
self._stoch_high.Value = value
def OnReseted(self):
super(meeting_lines_stochastic_strategy, self).OnReseted()
self._prev_candle = None
self._prev_prev_candle = None
def OnStarted2(self, time):
super(meeting_lines_stochastic_strategy, self).OnStarted2(time)
self._prev_candle = None
self._prev_prev_candle = None
stoch = StochasticOscillator()
stoch.K.Length = self.StochPeriod
stoch.D.Length = 3
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(stoch, self._process_candle).Start()
def _process_candle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
k_val = stoch_value.K
if k_val is None:
self._update_state(candle)
return
k_value = float(k_val)
if self._prev_candle is not None and self._prev_prev_candle is not None:
avg_body = (abs(float(self._prev_candle.ClosePrice) - float(self._prev_candle.OpenPrice))
+ abs(float(self._prev_prev_candle.ClosePrice) - float(self._prev_prev_candle.OpenPrice))) / 2.0
if avg_body > 0:
# Bullish meeting lines
prev_bearish = (float(self._prev_candle.OpenPrice) > float(self._prev_candle.ClosePrice)
and (float(self._prev_candle.OpenPrice) - float(self._prev_candle.ClosePrice)) > avg_body * 0.5)
curr_bullish = (float(candle.ClosePrice) > float(candle.OpenPrice)
and (float(candle.ClosePrice) - float(candle.OpenPrice)) > avg_body * 0.5)
closes_near = abs(float(candle.ClosePrice) - float(self._prev_candle.ClosePrice)) < avg_body * 0.3
if prev_bearish and curr_bullish and closes_near and k_value < self.StochLow and self.Position <= 0:
self.BuyMarket()
# Bearish meeting lines
prev_bullish = (float(self._prev_candle.ClosePrice) > float(self._prev_candle.OpenPrice)
and (float(self._prev_candle.ClosePrice) - float(self._prev_candle.OpenPrice)) > avg_body * 0.5)
curr_bearish = (float(candle.OpenPrice) > float(candle.ClosePrice)
and (float(candle.OpenPrice) - float(candle.ClosePrice)) > avg_body * 0.5)
closes_near2 = abs(float(candle.ClosePrice) - float(self._prev_candle.ClosePrice)) < avg_body * 0.3
if prev_bullish and curr_bearish and closes_near2 and k_value > self.StochHigh and self.Position >= 0:
self.SellMarket()
self._update_state(candle)
def _update_state(self, candle):
self._prev_prev_candle = self._prev_candle
self._prev_candle = candle
def CreateClone(self):
return meeting_lines_stochastic_strategy()