Get Trend 策略
概述
该策略是 MetaTrader 顾问 Get trend 的 StockSharp 版本。核心思想是在 15 分钟图上寻找顺势回调入场,并通过 1 小时图的趋势确认过滤逆势交易。策略同时运用平滑移动平均线与随机指标来确定入场时机。
交易逻辑
- 主图周期: 15 分钟蜡烛负责生成信号并提交订单。
- 确认周期: 1 小时蜡烛提供平滑移动平均线与收盘价,用于判断更高周期的趋势方向。
- 趋势过滤: M15 与 H1 的收盘价必须位于各自平滑均线的同一侧;同时 M15 收盘价与均线的距离需小于允许阈值,确保是在回调区域进场。
- 动量触发: 多头信号要求随机指标 %K 线在超卖区(<20)向上穿越 %D 线;空头信号则要求在超买区(>80)向下穿越。
- 仓位管理: 入场后设置固定点数的止损与止盈,且可选的移动止损在盈利达到指定距离时逐步跟随价格。
入场条件
做多
- M15 收盘价低于其平滑均线。
- H1 收盘价低于其平滑均线。
- M15 收盘价与均线的差值不超过 Price Threshold(点数)。
- 随机指标 %K 与 %D 均小于 20。
- 上一根 %K 低于 %D,当前 %K 上穿 %D。
- 当前没有多头仓位(若持有空头则先平仓再反向开多)。
做空
- M15 收盘价高于其平滑均线。
- H1 收盘价高于其平滑均线。
- M15 收盘价与均线的差值不超过 Price Threshold。
- 随机指标 %K 与 %D 均大于 80。
- 上一根 %K 高于 %D,当前 %K 下穿 %D。
- 当前没有空头仓位(若持有多头则先平仓再反向开空)。
离场规则
- 止损: 按点数设定的固定距离,基于入场价格计算。
- 止盈: 按点数设定的固定距离,基于入场价格计算。
- 移动止损: 当浮盈超过设定距离时启动,随后按设定偏移紧跟价格移动。
参数
| 参数 | 说明 | 默认值 |
|---|---|---|
M15CandleType |
主图信号使用的蜡烛类型。 | 15 分钟 |
H1CandleType |
趋势确认使用的蜡烛类型。 | 1 小时 |
MaM15Length |
M15 平滑移动平均周期。 | 99 |
MaH1Length |
H1 平滑移动平均周期。 | 184 |
StochasticLength |
随机指标 %K 周期。 | 27 |
StochasticSignalLength |
随机指标 %D 平滑周期。 | 3 |
ThresholdPoints |
允许价格偏离均线的最大点数。 | 10 |
TakeProfitPoints |
止盈距离(点数)。 | 540 |
StopLossPoints |
止损距离(点数)。 | 90 |
TrailingStopPoints |
移动止损距离(点数)。 | 20 |
TradeVolume |
开仓的基础手数/数量。 | 0.1 |
所有以点数表示的参数都会乘以品种的 PriceStep,转换为绝对价格差值。
实现细节
- 使用 StockSharp 高层 API,通过
BindEx将蜡烛订阅与指标连接,避免手动复制指标缓冲区。 - 移动止损逻辑与原始 EA 保持一致:当盈利超出阈值后不断将止损向有利方向推进。
- 反向开仓前先取消未完成订单,防止出现相反方向的挂单冲突。
- 图表展示 M15 蜡烛与平滑均线,并提供单独的随机指标面板,方便回测和监控。
使用建议
- 根据数据源调整蜡烛类型(若数据源支持其它周期或成交量蜡烛,可替换为对应的
DataType)。 - 不同市场的波动和最小跳动价差不同,请根据实际情况调节阈值、止损和止盈。
- 策略更适合趋势明显且存在均线回踩机会的交易品种。
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>
/// Trend-following strategy converted from the MetaTrader "Get trend" expert.
/// </summary>
public class GetTrendStrategy : Strategy
{
private readonly StrategyParam<DataType> _m15CandleType;
private readonly StrategyParam<DataType> _h1CandleType;
private readonly StrategyParam<int> _maM15Length;
private readonly StrategyParam<int> _maH1Length;
private readonly StrategyParam<int> _stochasticLength;
private readonly StrategyParam<int> _stochasticSignalLength;
private readonly StrategyParam<decimal> _thresholdPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _tradeVolume;
private SmoothedMovingAverage _maM15;
private SmoothedMovingAverage _maH1;
private StochasticOscillator _stochastic;
private decimal? _maH1Value;
private decimal? _lastH1Close;
private decimal? _prevStochFast;
private decimal? _prevStochSlow;
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
/// <summary>
/// Initializes a new instance of the <see cref="GetTrendStrategy"/> class.
/// </summary>
public GetTrendStrategy()
{
_m15CandleType = Param(nameof(M15CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("M15 Candle", "Primary timeframe", "General");
_h1CandleType = Param(nameof(H1CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("H1 Candle", "Higher timeframe", "General");
_maM15Length = Param(nameof(MaM15Length), 99)
.SetGreaterThanZero()
.SetDisplay("M15 MA Length", "Smoothed MA length on M15", "Indicators")
;
_maH1Length = Param(nameof(MaH1Length), 184)
.SetGreaterThanZero()
.SetDisplay("H1 MA Length", "Smoothed MA length on H1", "Indicators")
;
_stochasticLength = Param(nameof(StochasticLength), 27)
.SetGreaterThanZero()
.SetDisplay("Stochastic %K", "%K period", "Indicators")
;
_stochasticSignalLength = Param(nameof(StochasticSignalLength), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic %D", "%D smoothing period", "Indicators");
_thresholdPoints = Param(nameof(ThresholdPoints), 10m)
.SetGreaterThanZero()
.SetDisplay("Price Threshold", "Maximum distance from MA", "Filters")
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 540m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit distance", "Risk")
;
_stopLossPoints = Param(nameof(StopLossPoints), 90m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop loss distance", "Risk")
;
_trailingStopPoints = Param(nameof(TrailingStopPoints), 20m)
.SetDisplay("Trailing Stop", "Trailing stop distance", "Risk")
;
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Order volume", "Risk");
}
/// <summary>
/// Primary trading timeframe (M15 by default).
/// </summary>
public DataType M15CandleType
{
get => _m15CandleType.Value;
set => _m15CandleType.Value = value;
}
/// <summary>
/// Confirmation timeframe (H1 by default).
/// </summary>
public DataType H1CandleType
{
get => _h1CandleType.Value;
set => _h1CandleType.Value = value;
}
/// <summary>
/// Smoothed moving average length on the 15-minute chart.
/// </summary>
public int MaM15Length
{
get => _maM15Length.Value;
set => _maM15Length.Value = value;
}
/// <summary>
/// Smoothed moving average length on the hourly chart.
/// </summary>
public int MaH1Length
{
get => _maH1Length.Value;
set => _maH1Length.Value = value;
}
/// <summary>
/// %K period of the stochastic oscillator.
/// </summary>
public int StochasticLength
{
get => _stochasticLength.Value;
set => _stochasticLength.Value = value;
}
/// <summary>
/// %D period of the stochastic oscillator.
/// </summary>
public int StochasticSignalLength
{
get => _stochasticSignalLength.Value;
set => _stochasticSignalLength.Value = value;
}
/// <summary>
/// Maximum allowed distance between price and the M15 moving average in points.
/// </summary>
public decimal ThresholdPoints
{
get => _thresholdPoints.Value;
set => _thresholdPoints.Value = value;
}
/// <summary>
/// Take-profit distance expressed in points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in points.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Default order volume.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, M15CandleType), (Security, H1CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_maH1Value = null;
_lastH1Close = null;
_prevStochFast = null;
_prevStochSlow = null;
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = TradeVolume;
_maM15 = new SmoothedMovingAverage { Length = MaM15Length };
_maH1 = new SmoothedMovingAverage { Length = MaH1Length };
_stochastic = new StochasticOscillator();
_stochastic.K.Length = StochasticLength;
_stochastic.D.Length = StochasticSignalLength;
// Subscribe to 15-minute candles and bind the required indicators.
var m15Subscription = SubscribeCandles(M15CandleType);
m15Subscription
.BindEx(_maM15, _stochastic, ProcessM15Candle)
.Start();
// Subscribe to hourly candles for trend confirmation.
var h1Subscription = SubscribeCandles(H1CandleType);
h1Subscription
.Bind(_maH1, ProcessH1Candle)
.Start();
var priceArea = CreateChartArea();
if (priceArea != null)
{
DrawCandles(priceArea, m15Subscription);
DrawIndicator(priceArea, _maM15);
DrawOwnTrades(priceArea);
var oscillatorArea = CreateChartArea();
if (oscillatorArea != null)
{
oscillatorArea.Title = "Stochastic";
DrawIndicator(oscillatorArea, _stochastic);
}
}
}
private void ProcessH1Candle(ICandleMessage candle, decimal maValue)
{
if (candle.State != CandleStates.Finished)
return;
// Store the latest H1 moving average and close for trend checks.
_maH1Value = maValue;
_lastH1Close = candle.ClosePrice;
}
private void ProcessM15Candle(ICandleMessage candle, IIndicatorValue maValue, IIndicatorValue stochasticValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!maValue.IsFinal || !stochasticValue.IsFinal)
return;
var ma = maValue.ToDecimal();
var stochTyped = (StochasticOscillatorValue)stochasticValue;
if (stochTyped.K is not decimal stochFast || stochTyped.D is not decimal stochSlow)
return;
// Manage protective levels before looking for new entries.
ManageOpenPosition(candle);
if (_maH1Value is not decimal maH1 || _lastH1Close is not decimal priceH1)
goto UpdateStochastic;
var priceM15 = candle.ClosePrice;
var priceStep = Security?.PriceStep ?? 1m;
var threshold = ThresholdPoints * priceStep;
var nearLowerBand = priceM15 < ma && priceH1 < maH1 && ma - priceM15 <= threshold;
var nearUpperBand = priceM15 > ma && priceH1 > maH1 && priceM15 - ma <= threshold;
var crossUp = _prevStochFast is decimal prevFastUp && _prevStochSlow is decimal prevSlowUp && prevFastUp < prevSlowUp && stochFast > stochSlow;
var crossDown = _prevStochFast is decimal prevFastDown && _prevStochSlow is decimal prevSlowDown && prevFastDown > prevSlowDown && stochFast < stochSlow;
if (nearLowerBand && stochSlow < 20m && stochFast < 20m && crossUp && Position <= 0)
{
EnterLong(candle.ClosePrice, priceStep);
}
else if (nearUpperBand && stochSlow > 80m && stochFast > 80m && crossDown && Position >= 0)
{
EnterShort(candle.ClosePrice, priceStep);
}
UpdateStochastic:
_prevStochFast = stochFast;
_prevStochSlow = stochSlow;
}
private void EnterLong(decimal entryPrice, decimal priceStep)
{
// Cancel opposite orders and flip the position if needed.
CancelActiveOrders();
BuyMarket();
_entryPrice = entryPrice;
_takePrice = entryPrice + TakeProfitPoints * priceStep;
_stopPrice = entryPrice - StopLossPoints * priceStep;
}
private void EnterShort(decimal entryPrice, decimal priceStep)
{
// Cancel opposite orders and flip the position if needed.
CancelActiveOrders();
SellMarket();
_entryPrice = entryPrice;
_takePrice = entryPrice - TakeProfitPoints * priceStep;
_stopPrice = entryPrice + StopLossPoints * priceStep;
}
private void ManageOpenPosition(ICandleMessage candle)
{
if (Position == 0)
return;
var priceStep = Security?.PriceStep ?? 1m;
var trailingDistance = TrailingStopPoints * priceStep;
if (Position > 0)
{
if (_entryPrice is decimal entry && TrailingStopPoints > 0 && candle.ClosePrice - entry >= trailingDistance)
{
var candidate = candle.ClosePrice - trailingDistance;
_stopPrice = _stopPrice.HasValue ? Math.Max(_stopPrice.Value, candidate) : candidate;
}
if (_takePrice is decimal take && candle.HighPrice >= take)
{
SellMarket();
ResetProtection();
return;
}
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket();
ResetProtection();
}
}
else if (Position < 0)
{
if (_entryPrice is decimal entry && TrailingStopPoints > 0 && entry - candle.ClosePrice >= trailingDistance)
{
var candidate = candle.ClosePrice + trailingDistance;
_stopPrice = _stopPrice.HasValue ? Math.Min(_stopPrice.Value, candidate) : candidate;
}
if (_takePrice is decimal take && candle.LowPrice <= take)
{
BuyMarket();
ResetProtection();
return;
}
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket();
ResetProtection();
}
}
}
private void ResetProtection()
{
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import SmoothedMovingAverage, StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class get_trend_strategy(Strategy):
def __init__(self):
super(get_trend_strategy, self).__init__()
self._m15_candle_type = self.Param("M15CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._h1_candle_type = self.Param("H1CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._ma_m15_length = self.Param("MaM15Length", 99)
self._ma_h1_length = self.Param("MaH1Length", 184)
self._stochastic_length = self.Param("StochasticLength", 27)
self._stochastic_signal_length = self.Param("StochasticSignalLength", 3)
self._threshold_points = self.Param("ThresholdPoints", 10.0)
self._take_profit_points = self.Param("TakeProfitPoints", 540.0)
self._stop_loss_points = self.Param("StopLossPoints", 90.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 20.0)
self._trade_volume = self.Param("TradeVolume", 0.1)
self._ma_h1_value = None
self._last_h1_close = None
self._prev_stoch_fast = None
self._prev_stoch_slow = None
self._entry_price = None
self._stop_price = None
self._take_price = None
@property
def M15CandleType(self):
return self._m15_candle_type.Value
@M15CandleType.setter
def M15CandleType(self, value):
self._m15_candle_type.Value = value
@property
def H1CandleType(self):
return self._h1_candle_type.Value
@H1CandleType.setter
def H1CandleType(self, value):
self._h1_candle_type.Value = value
@property
def MaM15Length(self):
return self._ma_m15_length.Value
@MaM15Length.setter
def MaM15Length(self, value):
self._ma_m15_length.Value = value
@property
def MaH1Length(self):
return self._ma_h1_length.Value
@MaH1Length.setter
def MaH1Length(self, value):
self._ma_h1_length.Value = value
@property
def StochasticLength(self):
return self._stochastic_length.Value
@StochasticLength.setter
def StochasticLength(self, value):
self._stochastic_length.Value = value
@property
def StochasticSignalLength(self):
return self._stochastic_signal_length.Value
@StochasticSignalLength.setter
def StochasticSignalLength(self, value):
self._stochastic_signal_length.Value = value
@property
def ThresholdPoints(self):
return self._threshold_points.Value
@ThresholdPoints.setter
def ThresholdPoints(self, value):
self._threshold_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@TrailingStopPoints.setter
def TrailingStopPoints(self, value):
self._trailing_stop_points.Value = value
@property
def TradeVolume(self):
return self._trade_volume.Value
@TradeVolume.setter
def TradeVolume(self, value):
self._trade_volume.Value = value
def OnStarted2(self, time):
super(get_trend_strategy, self).OnStarted2(time)
self._ma_h1_value = None
self._last_h1_close = None
self._prev_stoch_fast = None
self._prev_stoch_slow = None
self._entry_price = None
self._stop_price = None
self._take_price = None
self._ma_m15 = SmoothedMovingAverage()
self._ma_m15.Length = self.MaM15Length
self._ma_h1 = SmoothedMovingAverage()
self._ma_h1.Length = self.MaH1Length
self._stochastic = StochasticOscillator()
self._stochastic.K.Length = self.StochasticLength
self._stochastic.D.Length = self.StochasticSignalLength
m15_sub = self.SubscribeCandles(self.M15CandleType)
m15_sub.BindEx(self._ma_m15, self._stochastic, self.ProcessM15Candle).Start()
h1_sub = self.SubscribeCandles(self.H1CandleType)
h1_sub.Bind(self._ma_h1, self.ProcessH1Candle).Start()
self.StartProtection(
Unit(2000.0, UnitTypes.Absolute),
Unit(1000.0, UnitTypes.Absolute))
def ProcessH1Candle(self, candle, ma_value):
if candle.State != CandleStates.Finished:
return
self._ma_h1_value = float(ma_value)
self._last_h1_close = float(candle.ClosePrice)
def ProcessM15Candle(self, candle, ma_value, stochastic_value):
if candle.State != CandleStates.Finished:
return
if not ma_value.IsFinal or not stochastic_value.IsFinal:
return
ma = float(ma_value)
stoch_k = stochastic_value.K
stoch_d = stochastic_value.D
if stoch_k is None or stoch_d is None:
return
stoch_fast = float(stoch_k)
stoch_slow = float(stoch_d)
self._manage_open_position(candle)
if self._ma_h1_value is None or self._last_h1_close is None:
self._prev_stoch_fast = stoch_fast
self._prev_stoch_slow = stoch_slow
return
ma_h1 = self._ma_h1_value
price_h1 = self._last_h1_close
price_m15 = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
threshold = float(self.ThresholdPoints) * step
near_lower = price_m15 < ma and price_h1 < ma_h1 and (ma - price_m15) <= threshold
near_upper = price_m15 > ma and price_h1 > ma_h1 and (price_m15 - ma) <= threshold
cross_up = (self._prev_stoch_fast is not None and self._prev_stoch_slow is not None and
self._prev_stoch_fast < self._prev_stoch_slow and stoch_fast > stoch_slow)
cross_down = (self._prev_stoch_fast is not None and self._prev_stoch_slow is not None and
self._prev_stoch_fast > self._prev_stoch_slow and stoch_fast < stoch_slow)
if near_lower and stoch_slow < 20.0 and stoch_fast < 20.0 and cross_up and self.Position <= 0:
self._enter_long(float(candle.ClosePrice), step)
elif near_upper and stoch_slow > 80.0 and stoch_fast > 80.0 and cross_down and self.Position >= 0:
self._enter_short(float(candle.ClosePrice), step)
self._prev_stoch_fast = stoch_fast
self._prev_stoch_slow = stoch_slow
def _enter_long(self, entry_price, price_step):
self.BuyMarket()
self._entry_price = entry_price
self._take_price = entry_price + float(self.TakeProfitPoints) * price_step
self._stop_price = entry_price - float(self.StopLossPoints) * price_step
def _enter_short(self, entry_price, price_step):
self.SellMarket()
self._entry_price = entry_price
self._take_price = entry_price - float(self.TakeProfitPoints) * price_step
self._stop_price = entry_price + float(self.StopLossPoints) * price_step
def _manage_open_position(self, candle):
if self.Position == 0:
return
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
trailing_distance = float(self.TrailingStopPoints) * step
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self.Position > 0:
if self._entry_price is not None and float(self.TrailingStopPoints) > 0.0 and close - self._entry_price >= trailing_distance:
candidate = close - trailing_distance
if self._stop_price is not None:
self._stop_price = max(self._stop_price, candidate)
else:
self._stop_price = candidate
if self._take_price is not None and high >= self._take_price:
self.SellMarket()
self._reset_protection()
return
if self._stop_price is not None and low <= self._stop_price:
self.SellMarket()
self._reset_protection()
elif self.Position < 0:
if self._entry_price is not None and float(self.TrailingStopPoints) > 0.0 and self._entry_price - close >= trailing_distance:
candidate = close + trailing_distance
if self._stop_price is not None:
self._stop_price = min(self._stop_price, candidate)
else:
self._stop_price = candidate
if self._take_price is not None and low <= self._take_price:
self.BuyMarket()
self._reset_protection()
return
if self._stop_price is not None and high >= self._stop_price:
self.BuyMarket()
self._reset_protection()
def _reset_protection(self):
self._entry_price = None
self._stop_price = None
self._take_price = None
def OnReseted(self):
super(get_trend_strategy, self).OnReseted()
self._ma_h1_value = None
self._last_h1_close = None
self._prev_stoch_fast = None
self._prev_stoch_slow = None
self._entry_price = None
self._stop_price = None
self._take_price = None
def CreateClone(self):
return get_trend_strategy()