在 GitHub 上查看

Vector 策略

概述

Vector 策略来源于 MetaTrader 5 的 "Vector" 专家顾问,是一个多品种趋势系统,同时交易四个主要外汇货币对:EURUSD、GBPUSD、USDCHF 和 USDJPY。 策略基于每个品种的中位价计算平滑移动平均线(SMMA),当所有货币对的总体趋势一致时同步开仓。离场依靠四小时波动率驱动的动 态点数目标以及账户层面的浮动盈亏保护。

核心思想

  • 使用基于中位价的平滑移动平均线衡量每个货币对的趋势方向。
  • 将所有品种的快线与慢线求和,得到全局的多头或空头倾向。
  • 当全局倾向与单个品种的快慢线信号一致时,对该品种开出一笔市价单。
  • 根据 EURUSD 最近 50 根 4 小时完成蜡烛的平均波动范围计算动态点数止盈,最少为 13 点。
  • 当浮动盈利或浮动亏损超过设定的百分比阈值时,同时平掉所有仓位以保护权益。

参数

参数 说明
Fast MA 每个品种快线 SMMA 的周期。
Slow MA 每个品种慢线 SMMA 的周期。
MA Shift 在开始评估信号前需要等待的完成蜡烛数量,对应原始 EA 的移位设置。
Equity Take Profit % 浮动盈利达到该百分比时平掉所有仓位。
Equity Stop Loss % 浮动亏损达到该百分比时执行紧急止损。
Signal Timeframe 计算平滑均线的交易时间框架,默认 15 分钟。
Range Timeframe 计算动态点数目标的时间框架,默认 4 小时。
Range Period 参与平均波动计算的高周期蜡烛数量。
EURUSD / GBPUSD / USDCHF / USDJPY 对应四个交易品种的证券对象,必须在启动前设置。

交易逻辑

  1. 指标更新:每当交易时间框架出现一根完成蜡烛,就更新该品种的快慢 SMMA,并在完成 MA Shift 设定的暖机后才允许发出信号。
  2. 趋势判定:对所有品种的快线求和并减去慢线和,得到全局趋势偏向,大于零表示多头,小于零表示空头。
  3. 开仓条件:若某个品种当前没有持仓,当全局偏向为多头且该品种快线高于慢线时做多;当全局偏向为空头且快线低于慢线时做空。
  4. 点数止盈:订阅 EURUSD 的 4 小时蜡烛,计算指定长度的平均高低点范围,将结果与 13 点比较取较大值作为目标。仓位达到目标点 数后立即平仓。
  5. 账户保护:实时检查账户的浮动盈亏,相对于策略启动时的资金达到设定的利润或亏损百分比时,立即关闭所有仓位。

使用建议

  • 在运行前将四个证券参数分别指向正确的外汇工具,确保所有品种均能接收指定时间框架的数据。
  • 策略一次只持有每个品种的一笔仓位,使用基础策略的 Volume 参数作为下单数量。
  • 动态点数目标使用 EURUSD 的波动率,与原始 EA 保持一致。如需适配其他市场,可调整 Range Timeframe 或 Range Period。
  • 策略依赖浮动盈亏管理,推荐在连续运行的环境中使用,例如自动化实盘或回测引擎。
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>
/// Trend strategy using smoothed moving averages. Simplified from multi-currency Vector.
/// </summary>
public class VectorStrategy : Strategy
{
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _maShift;
	private readonly StrategyParam<decimal> _profitPercent;
	private readonly StrategyParam<decimal> _lossPercent;
	private readonly StrategyParam<DataType> _candleType;

	private SmoothedMovingAverage _fastMa;
	private SmoothedMovingAverage _slowMa;
	private decimal _entryPrice;
	private decimal _initialBalance;
	private int _processedBars;

	/// <summary>
	/// Fast smoothed moving average period.
	/// </summary>
	public int FastMaPeriod
	{
		get => _fastMaPeriod.Value;
		set => _fastMaPeriod.Value = value;
	}

	/// <summary>
	/// Slow smoothed moving average period.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowMaPeriod.Value;
		set => _slowMaPeriod.Value = value;
	}

	/// <summary>
	/// Additional warm-up shift in bars.
	/// </summary>
	public int MaShift
	{
		get => _maShift.Value;
		set => _maShift.Value = value;
	}

	/// <summary>
	/// Floating profit target percent of balance.
	/// </summary>
	public decimal ProfitPercent
	{
		get => _profitPercent.Value;
		set => _profitPercent.Value = value;
	}

	/// <summary>
	/// Floating loss limit percent of balance.
	/// </summary>
	public decimal LossPercent
	{
		get => _lossPercent.Value;
		set => _lossPercent.Value = value;
	}

	/// <summary>
	/// Candle type for signals.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public VectorStrategy()
	{
		_fastMaPeriod = Param(nameof(FastMaPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA", "Fast smoothed moving average period", "Indicators")
			.SetOptimize(3, 15, 1);

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 7)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA", "Slow smoothed moving average period", "Indicators")
			.SetOptimize(5, 25, 1);

		_maShift = Param(nameof(MaShift), 8)
			.SetNotNegative()
			.SetDisplay("MA Shift", "Additional warm-up bars before signals", "Indicators");

		_profitPercent = Param(nameof(ProfitPercent), 0.5m)
			.SetNotNegative()
			.SetDisplay("Equity TP %", "Close all when floating profit reaches this percent", "Risk");

		_lossPercent = Param(nameof(LossPercent), 30m)
			.SetNotNegative()
			.SetDisplay("Equity SL %", "Close all when floating loss reaches this percent", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Signal Timeframe", "Timeframe for moving averages", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, CandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_fastMa = null;
		_slowMa = null;
		_entryPrice = 0m;
		_initialBalance = 0m;
		_processedBars = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_fastMa = new SmoothedMovingAverage { Length = FastMaPeriod };
		_slowMa = new SmoothedMovingAverage { Length = SlowMaPeriod };
		_initialBalance = Portfolio?.CurrentValue ?? 0m;

		SubscribeCandles(CandleType)
			.Bind(_fastMa, _slowMa, ProcessCandle)
			.Start();
	}

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

		_processedBars++;

		if (!IsFormed)
			return;

		if (_processedBars <= MaShift)
			return;

		// Check equity thresholds
		if (Position != 0 && _initialBalance > 0m)
		{
			var equity = Portfolio?.CurrentValue ?? 0m;
			var floating = equity - _initialBalance;
			var profitThreshold = _initialBalance * ProfitPercent / 100m;
			var lossThreshold = _initialBalance * LossPercent / 100m;

			if ((profitThreshold > 0m && floating >= profitThreshold) ||
				(lossThreshold > 0m && floating <= -lossThreshold))
			{
				if (Position > 0) SellMarket();
				else if (Position < 0) BuyMarket();
				_entryPrice = 0m;
				return;
			}
		}

		// Entry/exit logic
		if (fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
			_entryPrice = candle.ClosePrice;
		}
		else if (fastValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
			_entryPrice = candle.ClosePrice;
		}
	}
}