在 GitHub 上查看

VR Steals 2 策略

该策略是 MetaTrader 5 专家顾问“VR---STEALS-2”的 StockSharp 版本,用于演示在没有指标的情况下如何管理持仓。

工作原理

  1. 启动后立即通过 BuyMarket 买入并记录成交价。
  2. 通过 SubscribeCandles 订阅蜡烛数据(默认 1 分钟)。
  3. 每根完成的蜡烛执行以下检查:
    • 当价格向有利方向移动 Breakeven 个价位后,止损移动到入场价上方 BreakevenOffset 个价位。
    • 当价格达到入场价加 TakeProfit 个价位时,通过 SellMarket 平仓。
    • 如果价格跌至止损价(初始为入场价下方 StopLoss 个价位,或已移动的保本止损),则平仓。
  4. 平仓后策略不会再次入场。

参数

名称 说明 默认值
TakeProfit 到止盈水平的价位数。 50
StopLoss 初始止损距离的价位数。 50
Breakeven 激活保本止损所需的盈利价位数。 20
BreakevenOffset 保本止损相对于入场价的偏移。 9
CandleType 用于处理价格的蜡烛类型。 1 分钟

策略使用 StartProtection() 启动内置的持仓保护。

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>
/// Opens positions based on SMA trend direction, manages with fixed take profit,
/// stop loss and breakeven levels.
/// </summary>
public class VRSteals2Strategy : Strategy
{
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _breakeven;
	private readonly StrategyParam<decimal> _breakevenOffset;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _entryPrice;
	private decimal _stopPrice;
	private bool _breakevenActivated;
	private decimal _previousFast;
	private decimal _previousSlow;
	private bool _maInitialized;

	public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
	public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
	public decimal Breakeven { get => _breakeven.Value; set => _breakeven.Value = value; }
	public decimal BreakevenOffset { get => _breakevenOffset.Value; set => _breakevenOffset.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public VRSteals2Strategy()
	{
		_takeProfit = Param(nameof(TakeProfit), 50m)
			.SetDisplay("Take Profit", "Distance to take profit in steps", "General");
		_stopLoss = Param(nameof(StopLoss), 50m)
			.SetDisplay("Stop Loss", "Distance to stop loss in steps", "General");
		_breakeven = Param(nameof(Breakeven), 20m)
			.SetDisplay("Breakeven", "Distance to activate breakeven in steps", "General");
		_breakevenOffset = Param(nameof(BreakevenOffset), 9m)
			.SetDisplay("Breakeven Offset", "Offset applied when breakeven is triggered", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to process", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0m;
		_stopPrice = 0m;
		_breakevenActivated = false;
		_previousFast = 0m;
		_previousSlow = 0m;
		_maInitialized = false;
	}

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

		_entryPrice = 0m;
		_breakevenActivated = false;

		var fastSma = new SimpleMovingAverage { Length = 8 };
		var slowSma = new SimpleMovingAverage { Length = 34 };

		var sub = SubscribeCandles(CandleType);
		sub.Bind(fastSma, slowSma, (candle, fast, slow) =>
		{
			if (candle.State != CandleStates.Finished)
				return;

			var price = candle.ClosePrice;
			var step = Security?.PriceStep ?? 1m;

			// Manage existing long position
			if (Position > 0)
			{
				if (!_breakevenActivated && Breakeven > 0m && price >= _entryPrice + Breakeven * step)
				{
					_stopPrice = _entryPrice + BreakevenOffset * step;
					_breakevenActivated = true;
				}

				if (TakeProfit > 0m && price >= _entryPrice + TakeProfit * step)
				{
					SellMarket();
					_entryPrice = 0m;
					_breakevenActivated = false;
					return;
				}

				var stop = _breakevenActivated
					? _stopPrice
					: (StopLoss > 0m ? _entryPrice - StopLoss * step : decimal.MinValue);

				if (price <= stop)
				{
					SellMarket();
					_entryPrice = 0m;
					_breakevenActivated = false;
					return;
				}
			}
			// Manage existing short position
			else if (Position < 0)
			{
				if (!_breakevenActivated && Breakeven > 0m && price <= _entryPrice - Breakeven * step)
				{
					_stopPrice = _entryPrice - BreakevenOffset * step;
					_breakevenActivated = true;
				}

				if (TakeProfit > 0m && price <= _entryPrice - TakeProfit * step)
				{
					BuyMarket();
					_entryPrice = 0m;
					_breakevenActivated = false;
					return;
				}

				var stop = _breakevenActivated
					? _stopPrice
					: (StopLoss > 0m ? _entryPrice + StopLoss * step : decimal.MaxValue);

				if (price >= stop)
				{
					BuyMarket();
					_entryPrice = 0m;
					_breakevenActivated = false;
					return;
				}
			}

			// Entry signals based on SMA cross
			if (Position == 0)
			{
				if (_maInitialized && _previousFast <= _previousSlow && fast > slow)
				{
					BuyMarket();
					_entryPrice = price;
					_breakevenActivated = false;
				}
				else if (_maInitialized && _previousFast >= _previousSlow && fast < slow)
				{
					SellMarket();
					_entryPrice = price;
					_breakevenActivated = false;
				}
			}

			_previousFast = fast;
			_previousSlow = slow;
			_maInitialized = true;
		}).Start();

		StartProtection(
			new Unit(2000m, UnitTypes.Absolute),
			new Unit(1000m, UnitTypes.Absolute));
	}
}