Forex Fraus Slogger 策略
该策略复现了 MetaTrader 中的包络线反转系统。
逻辑
- 计算周期为 1 的 SMA 作为基准价格。
- 上下包络线以
EnvelopePercent的百分比偏离基准。 - 当价格收盘于上轨之上后又回到轨道内时,开空仓。
- 当价格收盘于下轨之下后又回到轨道内时,开多仓。
- 头寸由追踪止损保护。
参数
EnvelopePercent– 包络线的百分比偏移(默认 0.1)。TrailingStop– 追踪止损距离(默认 0.001)。TrailingStep– 更新追踪止损所需的最小价格移动(默认 0.0001)。ProfitTrailing– 仅在头寸盈利后启用追踪止损。UseTimeFilter– 只在指定的时间段交易。StartHour– 交易时间窗口开始。StopHour– 交易时间窗口结束。CandleType– 计算所用的蜡烛周期。
说明
- 策略通过
BuyMarket与SellMarket使用市价单。 - 当价格突破追踪止损价位时,头寸被平仓。
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>
/// Envelope reversion strategy with trailing stop.
/// </summary>
public class ForexFrausSloggerStrategy : Strategy
{
private readonly StrategyParam<decimal> _envelopePercent;
private readonly StrategyParam<decimal> _trailingStop;
private readonly StrategyParam<decimal> _trailingStep;
private readonly StrategyParam<bool> _profitTrailing;
private readonly StrategyParam<bool> _useTimeFilter;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _stopHour;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _sma = null!;
private bool _wasAboveUpper;
private bool _wasBelowLower;
private decimal _entryPrice;
private decimal _peakPrice;
private decimal _troughPrice;
private decimal _currentStop;
/// <summary>
/// Envelope percent.
/// </summary>
public decimal EnvelopePercent
{
get => _envelopePercent.Value;
set => _envelopePercent.Value = value;
}
/// <summary>
/// Trailing stop distance.
/// </summary>
public decimal TrailingStop
{
get => _trailingStop.Value;
set => _trailingStop.Value = value;
}
/// <summary>
/// Trailing step for stop adjustment.
/// </summary>
public decimal TrailingStep
{
get => _trailingStep.Value;
set => _trailingStep.Value = value;
}
/// <summary>
/// Enable trailing only after profit.
/// </summary>
public bool ProfitTrailing
{
get => _profitTrailing.Value;
set => _profitTrailing.Value = value;
}
/// <summary>
/// Use time filter.
/// </summary>
public bool UseTimeFilter
{
get => _useTimeFilter.Value;
set => _useTimeFilter.Value = value;
}
/// <summary>
/// Trading start hour.
/// </summary>
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
/// <summary>
/// Trading stop hour.
/// </summary>
public int StopHour
{
get => _stopHour.Value;
set => _stopHour.Value = value;
}
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="ForexFrausSloggerStrategy"/>.
/// </summary>
public ForexFrausSloggerStrategy()
{
_envelopePercent = Param(nameof(EnvelopePercent), 1.0m)
.SetGreaterThanZero()
.SetDisplay("Envelope %", "Envelope percent", "Parameters");
_trailingStop = Param(nameof(TrailingStop), 500m)
.SetGreaterThanZero()
.SetDisplay("Trailing Stop", "Trailing stop distance", "Risk");
_trailingStep = Param(nameof(TrailingStep), 100m)
.SetGreaterThanZero()
.SetDisplay("Trailing Step", "Minimum stop move", "Risk");
_profitTrailing = Param(nameof(ProfitTrailing), true)
.SetDisplay("Profit Trailing", "Trail only after profit", "Risk");
_useTimeFilter = Param(nameof(UseTimeFilter), false)
.SetDisplay("Use Time Filter", "Enable trading hours filter", "Parameters");
_startHour = Param(nameof(StartHour), 7)
.SetRange(0, 23)
.SetDisplay("Start Hour", "Trading start hour", "Parameters");
_stopHour = Param(nameof(StopHour), 17)
.SetRange(0, 23)
.SetDisplay("Stop Hour", "Trading stop hour", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Working candle timeframe", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sma = null!;
_wasAboveUpper = false;
_wasBelowLower = false;
_entryPrice = 0m;
_peakPrice = 0m;
_troughPrice = 0m;
_currentStop = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_sma = new ExponentialMovingAverage { Length = 14 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal basis)
{
if (candle.State != CandleStates.Finished)
return;
if (!_sma.IsFormed || !IsFormedAndOnlineAndAllowTrading())
return;
if (UseTimeFilter && !IsWithinTradingHours(candle.OpenTime))
return;
var percent = EnvelopePercent / 100m;
var upper = basis * (1m + percent);
var lower = basis * (1m - percent);
var close = candle.ClosePrice;
if (close > upper)
_wasAboveUpper = true;
if (close < lower)
_wasBelowLower = true;
if (_wasAboveUpper && close <= upper)
{
// Sell signal - go short
if (Position >= 0)
SellMarket();
_entryPrice = close;
_troughPrice = close;
_currentStop = close + TrailingStop;
_wasAboveUpper = false;
return;
}
if (_wasBelowLower && close >= lower)
{
// Buy signal - go long
if (Position <= 0)
BuyMarket();
_entryPrice = close;
_peakPrice = close;
_currentStop = close - TrailingStop;
_wasBelowLower = false;
return;
}
if (Position > 0)
{
if (candle.HighPrice >= _peakPrice + TrailingStep)
{
_peakPrice = candle.HighPrice;
var newStop = _peakPrice - TrailingStop;
if (!ProfitTrailing || newStop > _entryPrice)
_currentStop = Math.Max(_currentStop, newStop);
}
if (close <= _currentStop)
SellMarket();
}
else if (Position < 0)
{
if (_troughPrice == 0m || candle.LowPrice <= _troughPrice - TrailingStep)
{
_troughPrice = _troughPrice == 0m ? candle.LowPrice : Math.Min(_troughPrice, candle.LowPrice);
var newStop = _troughPrice + TrailingStop;
if (!ProfitTrailing || newStop < _entryPrice)
_currentStop = _currentStop == 0m ? newStop : Math.Min(_currentStop, newStop);
}
if (_currentStop != 0m && close >= _currentStop)
BuyMarket();
}
}
private bool IsWithinTradingHours(DateTimeOffset time)
{
var hour = time.Hour;
if (StartHour < StopHour)
return hour >= StartHour && hour < StopHour;
return hour >= StartHour || hour < StopHour;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class forex_fraus_slogger_strategy(Strategy):
def __init__(self):
super(forex_fraus_slogger_strategy, self).__init__()
self._envelope_percent = self.Param("EnvelopePercent", 1.0) \
.SetDisplay("Envelope %", "Envelope percent", "Parameters")
self._trailing_stop = self.Param("TrailingStop", 500.0) \
.SetDisplay("Trailing Stop", "Trailing stop distance", "Risk")
self._trailing_step = self.Param("TrailingStep", 100.0) \
.SetDisplay("Trailing Step", "Minimum stop move", "Risk")
self._profit_trailing = self.Param("ProfitTrailing", True) \
.SetDisplay("Profit Trailing", "Trail only after profit", "Risk")
self._use_time_filter = self.Param("UseTimeFilter", False) \
.SetDisplay("Use Time Filter", "Enable trading hours filter", "Parameters")
self._start_hour = self.Param("StartHour", 7) \
.SetDisplay("Start Hour", "Trading start hour", "Parameters")
self._stop_hour = self.Param("StopHour", 17) \
.SetDisplay("Stop Hour", "Trading stop hour", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Working candle timeframe", "Parameters")
self._sma = None
self._was_above_upper = False
self._was_below_lower = False
self._entry_price = 0.0
self._peak_price = 0.0
self._trough_price = 0.0
self._current_stop = 0.0
@property
def envelope_percent(self):
return self._envelope_percent.Value
@property
def trailing_stop(self):
return self._trailing_stop.Value
@property
def trailing_step(self):
return self._trailing_step.Value
@property
def profit_trailing(self):
return self._profit_trailing.Value
@property
def use_time_filter(self):
return self._use_time_filter.Value
@property
def start_hour(self):
return self._start_hour.Value
@property
def stop_hour(self):
return self._stop_hour.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(forex_fraus_slogger_strategy, self).OnReseted()
self._sma = None
self._was_above_upper = False
self._was_below_lower = False
self._entry_price = 0.0
self._peak_price = 0.0
self._trough_price = 0.0
self._current_stop = 0.0
def OnStarted2(self, time):
super(forex_fraus_slogger_strategy, self).OnStarted2(time)
self._sma = ExponentialMovingAverage()
self._sma.Length = 14
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._sma, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._sma)
self.DrawOwnTrades(area)
def process_candle(self, candle, basis):
if candle.State != CandleStates.Finished:
return
if not self._sma.IsFormed or not self.IsFormedAndOnlineAndAllowTrading():
return
if self.use_time_filter and not self._is_within_trading_hours(candle.OpenTime):
return
basis = float(basis)
pct = float(self.envelope_percent) / 100.0
upper = basis * (1.0 + pct)
lower = basis * (1.0 - pct)
close = float(candle.ClosePrice)
trailing_stop = float(self.trailing_stop)
trailing_step = float(self.trailing_step)
if close > upper:
self._was_above_upper = True
if close < lower:
self._was_below_lower = True
if self._was_above_upper and close <= upper:
if self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._trough_price = close
self._current_stop = close + trailing_stop
self._was_above_upper = False
return
if self._was_below_lower and close >= lower:
if self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._peak_price = close
self._current_stop = close - trailing_stop
self._was_below_lower = False
return
if self.Position > 0:
high = float(candle.HighPrice)
if high >= self._peak_price + trailing_step:
self._peak_price = high
new_stop = self._peak_price - trailing_stop
if not self.profit_trailing or new_stop > self._entry_price:
self._current_stop = max(self._current_stop, new_stop)
if close <= self._current_stop:
self.SellMarket()
elif self.Position < 0:
low = float(candle.LowPrice)
if self._trough_price == 0.0 or low <= self._trough_price - trailing_step:
self._trough_price = low if self._trough_price == 0.0 else min(self._trough_price, low)
new_stop = self._trough_price + trailing_stop
if not self.profit_trailing or new_stop < self._entry_price:
self._current_stop = new_stop if self._current_stop == 0.0 else min(self._current_stop, new_stop)
if self._current_stop != 0.0 and close >= self._current_stop:
self.BuyMarket()
def _is_within_trading_hours(self, time):
hour = time.Hour
sh = int(self.start_hour)
eh = int(self.stop_hour)
if sh < eh:
return hour >= sh and hour < eh
return hour >= sh or hour < eh
def CreateClone(self):
return forex_fraus_slogger_strategy()