在 GitHub 上查看

Roman Direction Flip

该策略复刻了最初以 roman.mq5 发布的 MQL 专家顾问。算法始终保持持仓状态,只有在关闭上一笔交易后才会改变方向。只要当前仓位仍处于盈利状态,就继续沿用同一方向;一旦触发止损,就切换到对立方向。StockSharp 版本依赖 Level 1 行情,使用最优买卖价来模拟 MetaTrader 中基于点值的止盈止损。

策略逻辑

  1. 初始方向:启动时通过 StartWithBuy 参数决定首笔订单是买入还是卖出,该决定保存在 _nextTradeBuy 标志中,以便后续交易沿用。
  2. 入场:当策略空仓且没有未决订单时,会按照预设方向提交市价单。买入时记录当前最优卖价作为参考入场价,卖出时记录最优买价,与 MetaTrader 中买价用 ask、卖价用 bid 的执行方式保持一致。
  3. 监控持仓:订单成交后策略持续监听 Level 1 更新。每个更新都提供新的 bid/ask,算法据此计算以价格步长(点)表示的浮动盈亏。计算时使用 Security.PriceStep,若没有设置步长则回退为 1
  4. 止盈规则:当浮动盈利达到或超过 TakeProfitSteps 时,通过 ClosePosition() 平仓,并保持 _nextTradeBuy 的当前值,使下一次交易继续沿用成功的方向。
  5. 止损规则:当浮动亏损达到或超过 StopLossSteps 时,立即平仓并反转 _nextTradeBuy。下一次交易由相反方向开始,复现原始 EA 中 bs 变量在亏损后翻转的行为。
  6. 防止重复下单:变量 _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;
	}
}