在 GitHub 上查看

SuperForexV2 策略

概述

SuperForexV2 是将 MetaTrader 4 专家顾问 SuperForexV2.mq4 迁移到 StockSharp 平台的版本。原始 EA 通过短周期 RSI 指标 配合固定的止盈、止损和追踪止损距离进行交易。本 C# 实现使用 StockSharp 的高级 API 重新构建该流程:策略仅处理已 收盘的K线,根据 RSI 阈值做出决策,并利用以点数为单位的风险参数管理单一净头寸。

交易逻辑

  1. 指标管线
    • 订阅可配置的K线序列(默认 15 分钟),并将每根已收盘的K线送入 RSI 指标。
    • RSI 长度可调,默认值为 MT4 脚本中的 4。
  2. 动态仓位控制
    • 在每次入场前,根据当前账户权益除以 BalanceToVolumeDivider 得到基础手数。
    • 结果会受到 InitialVolume(在无法获取账户权益时的兜底值)和 MaxVolume 的限制,并按品种的最小变动手数进行四舍五入。
  3. 入场规则
    • 当没有持仓且 RSI 低于 RsiLowerLevel 时,以市价买入。
    • 当 RSI 高于 RsiUpperLevel 时,以市价卖出。
  4. 退出与风险控制
    • 每次建仓时都会保存基于点数换算出的绝对止损与止盈价格。
    • 在每根已收盘的K线上,策略都会检查该柱是否触及这些价格,一旦触发立即以市价平仓。
    • 追踪止损复刻 MT4 逻辑:当价格至少向有利方向移动 TrailingStopPips 点后,将止损上移/下移以锁定利润。
    • RSI 穿越相反极值时也会触发平仓(例如 RSI 超过上轨时平掉多头)。
  5. 持仓约束
    • 策略仅允许同时存在一个净方向仓位,与原 EA 的“每个品种只持一单”行为保持一致。

参数

名称 说明 默认值 备注
CandleType 驱动指标计算的K线类型。 15m 时间框架 可设置任意连接器支持的 DataType
RsiPeriod RSI 回溯周期。 4 必须大于 0。
RsiUpperLevel 做空与平多所用的 RSI 上阈值。 62 对应 MT4 输入参数 Pos
RsiLowerLevel 做多与平空所用的 RSI 下阈值。 42 对应 MT4 输入参数 Neg
TakeProfitPips 止盈距离(点)。 109 设为 0 可关闭止盈。
StopLossPips 止损距离(点)。 9 设为 0 可关闭止损。
TrailingStopPips 追踪止损距离(点)。 6 设为 0 可关闭追踪止损。
InitialVolume 无法获取账户权益时使用的兜底手数。 0.1 当动态计算结果非正时同样采用该值。
MaxVolume 每次入场允许的最大手数。 100 防止仓位随权益过度放大。
BalanceToVolumeDivider 账户权益换算手数的除数。 10000 复刻 MT4 中 Lots = AccountBalance()/10000 的公式。

实现细节

  • 仅在 CandleStates.Finished 时处理数据,既与 MT4 start() 的收盘行为一致,又避免使用未完成的柱体。
  • 点数与实际价格的换算依赖品种的 PriceStep。对于 3 位或 5 位报价的外汇品种,会额外乘以 10,使得 StockSharp 中的 “pip” 与 MetaTrader 的 point 定义一致。
  • 止损、止盈与追踪止损在策略内部维护,通过比较K线的最高价与最低价进行触发,因为高层 API 不会像 MT4 一样自动管理 订单级别的保护单。
  • 动态计算出的手数会按照品种的 VolumeStepMinVolumeMaxVolume 进行对齐,保证提交的订单满足交易所约束。
  • 在已有持仓时,入场逻辑会直接返回,确保任何时刻最多只有一个净头寸。

与 MT4 版本的差异

  • StockSharp 版本基于已收盘的K线运作,无法像 MT4 那样在单个 tick 中立刻检测到止盈/止损,需要等到下一根K线确认。
  • MT4 中的 AccountFreeMargin() 检查被余额驱动的手数算法所取代;当无法获取权益时会退回到 InitialVolume,而不是 直接终止执行。
  • 策略不会向经纪商发送止盈/止损参数,而是在内部判断价格触发后以市价平仓,这是 StockSharp 高级订单管理的推荐方式。
  • 用于过滤订单的 NumeroMagico 输入在 StockSharp 中没有意义,因此被省略。
  • 原 EA 的打印信息未予保留,如需额外日志可使用 StockSharp 自带的日志工具。
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// SuperForexV2: RSI threshold reversal with ATR trailing stop.
/// Buys when RSI below lower level, sells when above upper level.
/// </summary>
public class SuperForexV2Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _upperLevel;
	private readonly StrategyParam<decimal> _lowerLevel;

	private decimal _entryPrice;
	private decimal _trailStop;

	public SuperForexV2Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_rsiLength = Param(nameof(RsiLength), 4)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period for trailing.", "Indicators");

		_upperLevel = Param(nameof(UpperLevel), 62m)
			.SetDisplay("RSI Upper", "Overbought for shorts.", "Signals");

		_lowerLevel = Param(nameof(LowerLevel), 42m)
			.SetDisplay("RSI Lower", "Oversold for longs.", "Signals");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	public decimal UpperLevel
	{
		get => _upperLevel.Value;
		set => _upperLevel.Value = value;
	}

	public decimal LowerLevel
	{
		get => _lowerLevel.Value;
		set => _lowerLevel.Value = value;
	}

	/// <inheritdoc />
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_entryPrice = 0;
		_trailStop = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_entryPrice = 0;
		_trailStop = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (atrVal <= 0)
			return;

		var close = candle.ClosePrice;

		// Trailing stop + opposite RSI exit
		if (Position > 0)
		{
			var newTrail = close - atrVal * 1.5m;
			if (newTrail > _trailStop)
				_trailStop = newTrail;

			if (close <= _trailStop || rsiVal > UpperLevel)
			{
				SellMarket();
				_entryPrice = 0;
				_trailStop = 0;
			}
		}
		else if (Position < 0)
		{
			var newTrail = close + atrVal * 1.5m;
			if (_trailStop == 0 || newTrail < _trailStop)
				_trailStop = newTrail;

			if (close >= _trailStop || rsiVal < LowerLevel)
			{
				BuyMarket();
				_entryPrice = 0;
				_trailStop = 0;
			}
		}

		// Entry on RSI levels
		if (Position == 0)
		{
			if (rsiVal < LowerLevel)
			{
				_entryPrice = close;
				_trailStop = close - atrVal * 2m;
				BuyMarket();
			}
			else if (rsiVal > UpperLevel)
			{
				_entryPrice = close;
				_trailStop = close + atrVal * 2m;
				SellMarket();
			}
		}
	}
}