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 | 对应四个交易品种的证券对象,必须在启动前设置。 |
交易逻辑
- 指标更新:每当交易时间框架出现一根完成蜡烛,就更新该品种的快慢 SMMA,并在完成 MA Shift 设定的暖机后才允许发出信号。
- 趋势判定:对所有品种的快线求和并减去慢线和,得到全局趋势偏向,大于零表示多头,小于零表示空头。
- 开仓条件:若某个品种当前没有持仓,当全局偏向为多头且该品种快线高于慢线时做多;当全局偏向为空头且快线低于慢线时做空。
- 点数止盈:订阅 EURUSD 的 4 小时蜡烛,计算指定长度的平均高低点范围,将结果与 13 点比较取较大值作为目标。仓位达到目标点 数后立即平仓。
- 账户保护:实时检查账户的浮动盈亏,相对于策略启动时的资金达到设定的利润或亏损百分比时,立即关闭所有仓位。
使用建议
- 在运行前将四个证券参数分别指向正确的外汇工具,确保所有品种均能接收指定时间框架的数据。
- 策略一次只持有每个品种的一笔仓位,使用基础策略的 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;
}
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import SmoothedMovingAverage
class vector_strategy(Strategy):
"""Trend strategy using smoothed moving averages with equity-based exit."""
def __init__(self):
super(vector_strategy, self).__init__()
self._fast_ma_period = self.Param("FastMaPeriod", 3) \
.SetGreaterThanZero() \
.SetDisplay("Fast MA", "Fast smoothed moving average period", "Indicators")
self._slow_ma_period = self.Param("SlowMaPeriod", 7) \
.SetGreaterThanZero() \
.SetDisplay("Slow MA", "Slow smoothed moving average period", "Indicators")
self._ma_shift = self.Param("MaShift", 8) \
.SetNotNegative() \
.SetDisplay("MA Shift", "Additional warm-up bars before signals", "Indicators")
self._profit_percent = self.Param("ProfitPercent", 0.5) \
.SetNotNegative() \
.SetDisplay("Equity TP %", "Close all when floating profit reaches this percent", "Risk")
self._loss_percent = self.Param("LossPercent", 30.0) \
.SetNotNegative() \
.SetDisplay("Equity SL %", "Close all when floating loss reaches this percent", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Signal Timeframe", "Timeframe for moving averages", "General")
self._entry_price = 0.0
self._initial_balance = 0.0
self._processed_bars = 0
@property
def FastMaPeriod(self):
return self._fast_ma_period.Value
@property
def SlowMaPeriod(self):
return self._slow_ma_period.Value
@property
def MaShift(self):
return self._ma_shift.Value
@property
def ProfitPercent(self):
return self._profit_percent.Value
@property
def LossPercent(self):
return self._loss_percent.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(vector_strategy, self).OnStarted2(time)
fast = SmoothedMovingAverage()
fast.Length = self.FastMaPeriod
slow = SmoothedMovingAverage()
slow.Length = self.SlowMaPeriod
portfolio = self.Portfolio
self._initial_balance = float(portfolio.CurrentValue) if portfolio is not None and portfolio.CurrentValue is not None else 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(fast, slow, self.process_candle) \
.Start()
def process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
self._processed_bars += 1
if not self.IsFormed:
return
if self._processed_bars <= self.MaShift:
return
fast = float(fast_val)
slow = float(slow_val)
# Check equity thresholds
if self.Position != 0 and self._initial_balance > 0:
portfolio = self.Portfolio
equity = float(portfolio.CurrentValue) if portfolio is not None and portfolio.CurrentValue is not None else 0.0
floating = equity - self._initial_balance
profit_threshold = self._initial_balance * float(self.ProfitPercent) / 100.0
loss_threshold = self._initial_balance * float(self.LossPercent) / 100.0
if (profit_threshold > 0 and floating >= profit_threshold) or \
(loss_threshold > 0 and floating <= -loss_threshold):
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._entry_price = 0.0
return
# Entry/exit logic
if fast > slow and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = float(candle.ClosePrice)
elif fast < slow and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = float(candle.ClosePrice)
def OnReseted(self):
super(vector_strategy, self).OnReseted()
self._entry_price = 0.0
self._initial_balance = 0.0
self._processed_bars = 0
def CreateClone(self):
return vector_strategy()