在 GitHub 上查看
Roman Direction Flip
该策略复刻了最初以 roman.mq5 发布的 MQL 专家顾问。算法始终保持持仓状态,只有在关闭上一笔交易后才会改变方向。只要当前仓位仍处于盈利状态,就继续沿用同一方向;一旦触发止损,就切换到对立方向。StockSharp 版本依赖 Level 1 行情,使用最优买卖价来模拟 MetaTrader 中基于点值的止盈止损。
策略逻辑
- 初始方向:启动时通过
StartWithBuy 参数决定首笔订单是买入还是卖出,该决定保存在 _nextTradeBuy 标志中,以便后续交易沿用。
- 入场:当策略空仓且没有未决订单时,会按照预设方向提交市价单。买入时记录当前最优卖价作为参考入场价,卖出时记录最优买价,与 MetaTrader 中买价用 ask、卖价用 bid 的执行方式保持一致。
- 监控持仓:订单成交后策略持续监听 Level 1 更新。每个更新都提供新的 bid/ask,算法据此计算以价格步长(点)表示的浮动盈亏。计算时使用
Security.PriceStep,若没有设置步长则回退为 1。
- 止盈规则:当浮动盈利达到或超过
TakeProfitSteps 时,通过 ClosePosition() 平仓,并保持 _nextTradeBuy 的当前值,使下一次交易继续沿用成功的方向。
- 止损规则:当浮动亏损达到或超过
StopLossSteps 时,立即平仓并反转 _nextTradeBuy。下一次交易由相反方向开始,复现原始 EA 中 bs 变量在亏损后翻转的行为。
- 防止重复下单:变量
_orderPending 用于阻止在前一笔订单尚未完成时重复提交。OnPositionChanged 在仓位更新后清除此标志。
这一流程确保策略几乎始终在市场中,仅在发生亏损时改变方向,表现为一种趋势跟随式的方向切换。
参数说明
OrderVolume (decimal,默认 0.1):每次市价单的下单数量,可根据实盘或回测需求调整。
TakeProfitSteps (int,默认 46):触发止盈所需的价格步长数量。步长对应 Security.PriceStep,若 tick 为 0.01,则默认值相当于 0.46 个价格单位。
StopLossSteps (int,默认 31):允许的最大不利波动(以步长计),超过后立即平仓并翻转方向。
StartWithBuy (bool,默认 true):首笔交易是否从多头开始,后续方向由上一笔交易的结果决定。
所有参数均通过 StrategyParam<T> 创建,除了布尔值外都支持优化,并使用 SetDisplay 提供 UI 元数据。
数据与执行
- 通过
SubscribeLevel1() 订阅最优买卖报价,无需蜡烛图或指标。
- 入场使用
BuyMarket/SellMarket,离场使用 ClosePosition(),最大程度贴近原 MQL 版本的即时市价执行。
- 在本地缓存最近一次的 bid/ask,模拟 MetaTrader 中基于
_Point 计算盈亏的方式。
风险控制
- 固定的止盈与止损步长使每笔交易都拥有明确的退出边界。
- 在震荡行情中,由于亏损后会立即反向入场,
OrderVolume 需结合账户风险承受能力谨慎设定。
- 策略几乎时刻持仓,易受跳空和突发报价影响,必要时可叠加额外的保护机制。
默认参数
OrderVolume = 0.1
TakeProfitSteps = 46
StopLossSteps = 31
StartWithBuy = true
筛选标签
- 类别:趋势跟随 / 方向切换
- 方向:多头与空头
- 指标:无
- 止损止盈:有(固定步长)
- 复杂度:基础
- 时间框架:Level 1 行情 / Tick
- 季节性:无
- 神经网络:无
- 背离识别:无
- 风险等级:高(几乎始终持仓)
备注
- 原始 EA 使用布尔变量
bs 保存下一笔方向,移植版本通过 _nextTradeBuy 实现同样的逻辑,并增加了防止重复下单的控制。
- 点值设定会直接影响盈亏目标。如果交易品种使用更细的报价增量,请相应调整止盈和止损的步长参数。
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Roman Direction Flip strategy. Alternates direction on momentum reversals.
/// Uses Momentum indicator zero-line crossover.
/// </summary>
public class RomanDirectionFlipStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _momPeriod;
private decimal? _prevMom;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int MomPeriod { get => _momPeriod.Value; set => _momPeriod.Value = value; }
public RomanDirectionFlipStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_momPeriod = Param(nameof(MomPeriod), 12).SetGreaterThanZero().SetDisplay("Momentum Period", "Momentum lookback", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMom = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMom = null;
var mom = new Momentum { Length = MomPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(mom, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal momVal)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) { _prevMom = momVal; return; }
if (_prevMom == null) { _prevMom = momVal; return; }
if (_prevMom.Value < 100m && momVal >= 100m && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
else if (_prevMom.Value > 100m && momVal <= 100m && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
_prevMom = momVal;
}
}
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 Momentum
from StockSharp.Algo.Strategies import Strategy
class roman_direction_flip_strategy(Strategy):
def __init__(self):
super(roman_direction_flip_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._mom_period = self.Param("MomPeriod", 12) \
.SetDisplay("Momentum Period", "Momentum lookback", "Indicators")
self._prev_mom = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def MomPeriod(self):
return self._mom_period.Value
def OnReseted(self):
super(roman_direction_flip_strategy, self).OnReseted()
self._prev_mom = None
def OnStarted2(self, time):
super(roman_direction_flip_strategy, self).OnStarted2(time)
self._prev_mom = None
mom = Momentum()
mom.Length = self.MomPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(mom, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle, mom_value):
if candle.State != CandleStates.Finished:
return
mv = float(mom_value)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_mom = mv
return
if self._prev_mom is None:
self._prev_mom = mv
return
if self._prev_mom < 100.0 and mv >= 100.0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_mom > 100.0 and mv <= 100.0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_mom = mv
def CreateClone(self):
return roman_direction_flip_strategy()