e-TurboFx Classic 策略
概述
e-TurboFx Classic 策略将 MQL/7262/e-TurboFx.mq4 中的 MetaTrader 4 智能交易系统完整移植到 StockSharp。策略寻找一连串实体不断扩大的强势 K 线,并在动能衰竭时反向入场。移植版本使用 StockSharp 的高级策略 API、K 线订阅以及内置的保护性订单管理。
交易逻辑
- 订阅所选的 K 线类型,只处理已经收盘的 K 线。
- 计算 K 线实体大小(
|close - open|),用于判断实体是否继续扩大。 - 维护两个计数器:
- 空头序列:统计连续收阴并且实体大于前一根阴线的数量。
- 多头序列:统计连续收阳并且实体大于前一根阳线的数量。
- 出现十字星(开盘价等于收盘价)或策略已经持仓时,立即重置两个计数器,保持与原始 EA 同样的「一次只持有一笔交易」规则。
- 做多: 当空头序列长度达到参数
SequenceLength时,发送市价买单,并重置计数器。 - 做空: 当多头序列长度达到
SequenceLength时,发送市价卖单,并重置计数器。 - 可选的止盈、止损以点数表示,并在 StockSharp 中转换为价格步长。
策略等待单方向的极端冲刺,每一根新 K 线的实体都在扩大,随后尝试在动能耗尽时捕捉反转。
实现细节
- 通过
SubscribeCandles().Bind(ProcessCandle)处理收盘 K 线,无需手动管理指标。 StartProtection将止盈和止损从点数转换为价格步长(UnitTypes.Step)。- 所有参数都通过
Param(...)注册,便于界面调整和参数优化。 - 仅在交易品种提供有效的
PriceStep时启用止盈止损,否则建议将其保持为0。 - 持仓期间会暂停信号检测并清空计数器,避免重复开仓,完全符合原始脚本的逻辑。
参数
| 参数 | 说明 | 默认值 |
|---|---|---|
SequenceLength |
触发交易所需的连续扩张 K 线数量。 | 3 |
TakeProfitSteps |
以价格步长表示的止盈距离,0 表示不使用止盈。 |
120 |
StopLossSteps |
以价格步长表示的止损距离,0 表示不使用止损。 |
70 |
TradeVolume |
每次市价单的下单数量,并同步到 Volume 属性。 |
0.1 |
CandleType |
分析所用的 K 线周期,默认为 1 小时。 | 1 hour |
使用建议
- 确保订阅的 K 线数据连续无缺失,切换品种或周期后需等待新数据重新构建序列。
- 在噪声较大的周期上,实体扩张很容易被打断,可适当降低
SequenceLength。 - 建议在回测或模拟环境中验证不同参数组合,评估滑点与交易成本的影响。
- 实盘前务必进行充分测试,确认保护性订单与经纪商规则兼容。
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>
/// Momentum exhaustion strategy converted from the original e-TurboFx MQL4 expert adviser.
/// </summary>
public class ETurboFxClassicStrategy : Strategy
{
private readonly StrategyParam<int> _sequenceLength;
private readonly StrategyParam<decimal> _takeProfitSteps;
private readonly StrategyParam<decimal> _stopLossSteps;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<DataType> _candleType;
private int _bearishSequence;
private int _bullishSequence;
private decimal _previousBearishBody;
private decimal _previousBullishBody;
/// <summary>
/// Number of consecutive candles required to trigger a signal.
/// </summary>
public int SequenceLength
{
get => _sequenceLength.Value;
set => _sequenceLength.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps (ticks).
/// A value of zero disables the take profit order.
/// </summary>
public decimal TakeProfitSteps
{
get => _takeProfitSteps.Value;
set => _takeProfitSteps.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps (ticks).
/// A value of zero disables the protective stop.
/// </summary>
public decimal StopLossSteps
{
get => _stopLossSteps.Value;
set => _stopLossSteps.Value = value;
}
/// <summary>
/// Order volume sent with each market entry.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set
{
_tradeVolume.Value = value;
Volume = value;
}
}
/// <summary>
/// Candle type analysed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ETurboFxClassicStrategy" /> class.
/// </summary>
public ETurboFxClassicStrategy()
{
_sequenceLength = Param(nameof(SequenceLength), 3)
.SetGreaterThanZero()
.SetDisplay("Sequence Length", "Number of consecutive finished candles analysed for pattern detection", "Trading Rules")
.SetOptimize(2, 6, 1);
_takeProfitSteps = Param(nameof(TakeProfitSteps), 120m)
.SetNotNegative()
.SetDisplay("Take Profit (steps)", "Take profit distance in price steps (ticks)", "Risk Management")
.SetOptimize(60m, 180m, 20m);
_stopLossSteps = Param(nameof(StopLossSteps), 70m)
.SetNotNegative()
.SetDisplay("Stop Loss (steps)", "Stop loss distance in price steps (ticks)", "Risk Management")
.SetOptimize(40m, 120m, 10m);
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Order volume used for market entries", "Trading Rules")
.SetOptimize(0.1m, 0.5m, 0.1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Time frame of the candles analysed by the strategy", "Market Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
Volume = TradeVolume;
var takeProfitUnit = CreateStepUnit(TakeProfitSteps);
var stopLossUnit = CreateStepUnit(StopLossSteps);
if (takeProfitUnit != null || stopLossUnit != null)
{
// Configure protection block only once when the strategy starts.
StartProtection(
takeProfit: takeProfitUnit,
stopLoss: stopLossUnit,
isStopTrailing: false,
useMarketOrders: true);
}
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private Unit CreateStepUnit(decimal steps)
{
if (steps <= 0)
return null;
return new Unit(steps, UnitTypes.Absolute);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// no indicators to check
if (Position != 0)
{
// Ignore new signals while a position is active and rebuild the sequences afterwards.
ResetState();
return;
}
var bodySize = Math.Abs(candle.ClosePrice - candle.OpenPrice);
if (candle.ClosePrice < candle.OpenPrice)
{
HandleBearishCandle(bodySize);
}
else if (candle.ClosePrice > candle.OpenPrice)
{
HandleBullishCandle(bodySize);
}
else
{
// Flat candles break both sequences because momentum stalled.
ResetState();
}
}
private void HandleBearishCandle(decimal bodySize)
{
ResetBullishSequence();
if (bodySize <= 0)
{
ResetBearishSequence();
return;
}
if (_bearishSequence == 0 || bodySize > _previousBearishBody)
{
// Body expanded compared to the previous bearish candle.
_bearishSequence++;
}
else
{
// Restart the sequence when the body fails to expand.
_bearishSequence = 1;
}
_previousBearishBody = bodySize;
if (_bearishSequence >= SequenceLength)
{
// A string of expanding bearish candles hints a bullish reversal.
BuyMarket();
ResetBearishSequence();
}
}
private void HandleBullishCandle(decimal bodySize)
{
ResetBearishSequence();
if (bodySize <= 0)
{
ResetBullishSequence();
return;
}
if (_bullishSequence == 0 || bodySize > _previousBullishBody)
{
// Body expanded compared to the previous bullish candle.
_bullishSequence++;
}
else
{
// Restart the sequence when the body fails to expand.
_bullishSequence = 1;
}
_previousBullishBody = bodySize;
if (_bullishSequence >= SequenceLength)
{
// A string of expanding bullish candles hints a bearish reversal.
SellMarket();
ResetBullishSequence();
}
}
private void ResetBearishSequence()
{
_bearishSequence = 0;
_previousBearishBody = 0m;
}
private void ResetBullishSequence()
{
_bullishSequence = 0;
_previousBullishBody = 0m;
}
private void ResetState()
{
ResetBearishSequence();
ResetBullishSequence();
}
}
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.Strategies import Strategy
class e_turbo_fx_classic_strategy(Strategy):
"""Momentum exhaustion strategy. Detects sequences of expanding candle bodies
and enters on expected reversal. Uses StartProtection for SL/TP."""
def __init__(self):
super(e_turbo_fx_classic_strategy, self).__init__()
self._sequence_length = self.Param("SequenceLength", 3) \
.SetGreaterThanZero() \
.SetDisplay("Sequence Length", "Number of consecutive finished candles analysed for pattern detection", "Trading Rules")
self._take_profit_steps = self.Param("TakeProfitSteps", 120.0) \
.SetDisplay("Take Profit (steps)", "Take profit distance in price steps (ticks)", "Risk Management")
self._stop_loss_steps = self.Param("StopLossSteps", 70.0) \
.SetDisplay("Stop Loss (steps)", "Stop loss distance in price steps (ticks)", "Risk Management")
self._trade_volume = self.Param("TradeVolume", 0.1) \
.SetGreaterThanZero() \
.SetDisplay("Trade Volume", "Order volume used for market entries", "Trading Rules")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Time frame of the candles analysed by the strategy", "Market Data")
self._bearish_sequence = 0
self._bullish_sequence = 0
self._previous_bearish_body = 0.0
self._previous_bullish_body = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def SequenceLength(self):
return self._sequence_length.Value
@property
def TakeProfitSteps(self):
return self._take_profit_steps.Value
@property
def StopLossSteps(self):
return self._stop_loss_steps.Value
@property
def TradeVolume(self):
return self._trade_volume.Value
def OnReseted(self):
super(e_turbo_fx_classic_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(e_turbo_fx_classic_strategy, self).OnStarted2(time)
self._reset_state()
self.Volume = float(self.TradeVolume)
tp_steps = float(self.TakeProfitSteps)
sl_steps = float(self.StopLossSteps)
tp_unit = Unit(tp_steps, UnitTypes.Absolute) if tp_steps > 0 else None
sl_unit = Unit(sl_steps, UnitTypes.Absolute) if sl_steps > 0 else None
if tp_unit is not None or sl_unit is not None:
self.StartProtection(tp_unit, sl_unit)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self.Position != 0:
self._reset_state()
return
body_size = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
if float(candle.ClosePrice) < float(candle.OpenPrice):
self._handle_bearish_candle(body_size)
elif float(candle.ClosePrice) > float(candle.OpenPrice):
self._handle_bullish_candle(body_size)
else:
self._reset_state()
def _handle_bearish_candle(self, body_size):
self._reset_bullish_sequence()
if body_size <= 0:
self._reset_bearish_sequence()
return
if self._bearish_sequence == 0 or body_size > self._previous_bearish_body:
self._bearish_sequence += 1
else:
self._bearish_sequence = 1
self._previous_bearish_body = body_size
if self._bearish_sequence >= self.SequenceLength:
self.BuyMarket()
self._reset_bearish_sequence()
def _handle_bullish_candle(self, body_size):
self._reset_bearish_sequence()
if body_size <= 0:
self._reset_bullish_sequence()
return
if self._bullish_sequence == 0 or body_size > self._previous_bullish_body:
self._bullish_sequence += 1
else:
self._bullish_sequence = 1
self._previous_bullish_body = body_size
if self._bullish_sequence >= self.SequenceLength:
self.SellMarket()
self._reset_bullish_sequence()
def _reset_bearish_sequence(self):
self._bearish_sequence = 0
self._previous_bearish_body = 0.0
def _reset_bullish_sequence(self):
self._bullish_sequence = 0
self._previous_bullish_body = 0.0
def _reset_state(self):
self._reset_bearish_sequence()
self._reset_bullish_sequence()
def CreateClone(self):
return e_turbo_fx_classic_strategy()