GitHub で見る

VR Setka 3 戦略

VR Setka 3 戦略はグリッドベースの取引アプローチを実装しています。戦略は現在の市場価格周辺に対称的な買いと売りの指値注文を配置します。注文が約定すると、アクティブな方向のすべてのポジションの平均エントリー価格を使用してテイクプロフィットレベルが再計算されます。新しいグリッド注文は間隔を増やしながら配置され、オプションで出来高も増加させます(マーチンゲール)。

パラメーター

  • Start Offset – 最初の指値注文ペアの現在価格からの初期距離。
  • Take Profit – すべてのポジションが利益で閉じられる平均エントリー価格からの距離。
  • Grid Distance – グリッドレベル間の基本ステップ。
  • Step Distance – 各後続グリッドレベルに追加される追加距離。
  • Use Martingale – 有効にすると、各新しいグリッド注文が乗数を使って出来高を増加させます。
  • Martingale Multiplier – マーチンゲールがアクティブな場合の出来高増加係数。
  • Volume – 最初のレベルの基本注文出来高。
  • Candle Type – 戦略操作の同期に使用する時間軸。

アルゴリズム

  1. 開始時、戦略は現在価格の下に buy limit、上に sell limit を配置します。
  2. 一方が約定すると、反対側の注文がキャンセルされます。
  3. 戦略は平均価格 ± Take Profit の共通テイクプロフィットレベルを再計算します。
  4. 価格がポジションに逆行する場合、平均価格から Grid Distance + Step Distance × レベル の位置に新しい指値注文が配置されます。マーチンゲールが有効な場合、出来高が増加します。
  5. 価格がテイクプロフィットレベルに達すると、その方向のすべてのポジションが閉じられ、グリッドがリセットされます。

注記

  • 戦略は両方向に同時にポジションを開きません。
  • マーチンゲールはポジションサイズを急速に増加させるため、適切なリスク管理が必要です。
  • 選択したローソク足タイプが利用可能な限り、StockSharp がサポートするあらゆる銘柄で動作します。
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>
/// Grid strategy inspired by VR SETKA 3.
/// Places limit orders at grid levels. When filled, places next level.
/// Closes position on take profit.
/// </summary>
public class VRSetka3Strategy : Strategy
{
	private readonly StrategyParam<decimal> _startOffset;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _gridDistance;
	private readonly StrategyParam<decimal> _stepDistance;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _buyAvgPrice;
	private decimal _buyVolume;
	private decimal _sellAvgPrice;
	private decimal _sellVolume;
	private int _buyCount;
	private int _sellCount;
	private bool _hasBuyPending;
	private bool _hasSellPending;

	public decimal StartOffset
	{
		get => _startOffset.Value;
		set => _startOffset.Value = value;
	}

	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	public decimal GridDistance
	{
		get => _gridDistance.Value;
		set => _gridDistance.Value = value;
	}

	public decimal StepDistance
	{
		get => _stepDistance.Value;
		set => _stepDistance.Value = value;
	}

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

	public VRSetka3Strategy()
	{
		_startOffset = Param(nameof(StartOffset), 100m)
			.SetGreaterThanZero()
			.SetDisplay("Start Offset", "Offset for first limit orders", "Parameters");

		_takeProfit = Param(nameof(TakeProfit), 300m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Distance for profit taking", "Parameters");

		_gridDistance = Param(nameof(GridDistance), 300m)
			.SetGreaterThanZero()
			.SetDisplay("Grid Distance", "Base distance between grid levels", "Parameters");

		_stepDistance = Param(nameof(StepDistance), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Step Distance", "Additional distance for next levels", "Parameters");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

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

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

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

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

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

		var price = candle.ClosePrice;

		// Check take profit for long grid
		if (_buyVolume > 0 && price >= _buyAvgPrice + TakeProfit)
		{
			SellMarket();
			ResetState();
			return;
		}

		// Check take profit for short grid
		if (_sellVolume > 0 && price <= _sellAvgPrice - TakeProfit)
		{
			BuyMarket();
			ResetState();
			return;
		}

		// Place grid orders
		if (_buyVolume > 0 && !_hasBuyPending)
		{
			var level = _buyAvgPrice - (GridDistance + StepDistance * _buyCount);
			if (level > 0)
			{
				BuyLimit(level);
				_hasBuyPending = true;
			}
		}
		else if (_sellVolume > 0 && !_hasSellPending)
		{
			var level = _sellAvgPrice + (GridDistance + StepDistance * _sellCount);
			SellLimit(level);
			_hasSellPending = true;
		}
		else if (_buyVolume == 0 && _sellVolume == 0)
		{
			if (!_hasBuyPending)
			{
				var buyPrice = price - StartOffset;
				if (buyPrice > 0)
				{
					BuyLimit(buyPrice);
					_hasBuyPending = true;
				}
			}

			if (!_hasSellPending)
			{
				var sellPrice = price + StartOffset;
				SellLimit(sellPrice);
				_hasSellPending = true;
			}
		}
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		if (trade.Order.Side == Sides.Buy)
		{
			_buyAvgPrice = (_buyAvgPrice * _buyVolume + trade.Trade.Price * trade.Trade.Volume) / (_buyVolume + trade.Trade.Volume);
			_buyVolume += trade.Trade.Volume;
			_buyCount++;
			_hasBuyPending = false;
		}
		else if (trade.Order.Side == Sides.Sell)
		{
			_sellAvgPrice = (_sellAvgPrice * _sellVolume + trade.Trade.Price * trade.Trade.Volume) / (_sellVolume + trade.Trade.Volume);
			_sellVolume += trade.Trade.Volume;
			_sellCount++;
			_hasSellPending = false;
		}
	}

	private void ResetState()
	{
		_buyAvgPrice = _sellAvgPrice = 0;
		_buyVolume = _sellVolume = 0;
		_buyCount = _sellCount = 0;
		_hasBuyPending = _hasSellPending = false;
	}
}