GitHub で見る

Very Blonde System 戦略

MetaTrader向けオリジナル「Very Blonde System」に着想を得たグリッドベースの逆張り戦略。現在価格と直近の極値との大きな乖離を探し、反対方向でトレードします。

戦略ロジック

  1. 直近 Count Bars 本のローソク足における最高値と最安値を計算する。
  2. オープンポジションがない場合:
    • 直近の高値から現在価格までの距離が Limit ティックを超えた場合、成行で買い。
    • 現在価格から直近の安値までの距離が Limit ティックを超えた場合、成行で売り。
    • ポジション取得後、Grid ティックごとに4つの追加指値注文を置き、各レベルでボリュームを倍増する。
  3. ポジションが存在する場合:
    • 合計利益が Amount 通貨単位を超えた場合、ポジションをクローズしてすべての未決注文をキャンセルする。
    • Lock Down がゼロより大きい場合、価格が有利な方向にその分のティック動いた時点でブレークイーブン保護が有効になる。価格がエントリーレベルに戻ると、すべてのポジションがクローズされる。

パラメーター

名前 説明
CountBars 高値・安値を探すローソク足の本数。
Limit トレードを開くための極値からの最小距離(ティック)。
Grid 追加グリッド注文間の距離(ティック)。
Amount すべてのポジションをクローズするための目標利益(通貨)。
LockDown ブレークイーブン保護を有効にする距離(ティック)。
CandleType 計算に使用するローソク足タイプ。

戦略は最初のエントリーに成行注文を使用し、グリッドレベルには指値注文を使用します。コード内のすべてのコメントは英語で書かれています。

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 based on distance from recent extremes.
/// Buys when price drops far below recent high and sells when price rises far above recent low.
/// Places additional limit orders forming a martingale grid and exits on total profit.
/// </summary>
public class VeryBlondeSystemStrategy : Strategy
{
	private readonly StrategyParam<int> _countBars;
	private readonly StrategyParam<decimal> _limit;
	private readonly StrategyParam<decimal> _grid;
	private readonly StrategyParam<decimal> _amount;
	private readonly StrategyParam<decimal> _lockDown;
	private readonly StrategyParam<DataType> _candleType;
	
	private decimal _entryPrice;
	private bool _isLong;
	private bool _lockActivated;
	private decimal _lockPrice;
	
	/// <summary>
	/// Number of candles to search extremes.
	/// </summary>
	public int CountBars
	{
		get => _countBars.Value;
		set => _countBars.Value = value;
	}
	
	/// <summary>
	/// Minimum distance from recent extreme in ticks to trigger entry.
	/// </summary>
	public decimal Limit
	{
		get => _limit.Value;
		set => _limit.Value = value;
	}
	
	/// <summary>
	/// Grid distance in ticks between additional orders.
	/// </summary>
	public decimal Grid
	{
		get => _grid.Value;
		set => _grid.Value = value;
	}
	
	/// <summary>
	/// Target profit to close all positions.
	/// </summary>
	public decimal Amount
	{
		get => _amount.Value;
		set => _amount.Value = value;
	}
	
	/// <summary>
	/// Breakeven activation distance in ticks.
	/// </summary>
	public decimal LockDown
	{
		get => _lockDown.Value;
		set => _lockDown.Value = value;
	}
	
	/// <summary>
	/// Type of candles for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public VeryBlondeSystemStrategy()
	{
		_countBars = Param(nameof(CountBars), 10)
		.SetDisplay("Count Bars", "Number of candles to search extremes", "General")
		.SetGreaterThanZero()
		
		.SetOptimize(5, 30, 5);
		
		_limit = Param(nameof(Limit), 500m)
		.SetDisplay("Limit", "Minimum distance from extreme in ticks", "Trading")
		.SetGreaterThanZero();
		
		_grid = Param(nameof(Grid), 35m)
		.SetDisplay("Grid", "Grid distance in ticks", "Trading")
		.SetGreaterThanZero();
		
		_amount = Param(nameof(Amount), 40m)
		.SetDisplay("Amount", "Target profit to close all positions", "Risk")
		.SetGreaterThanZero();
		
		_lockDown = Param(nameof(LockDown), 0m)
		.SetDisplay("Lock Down", "Breakeven activation distance in ticks", "Risk");
		
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles for calculations", "General");
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}
	
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		
		_entryPrice = 0m;
		_isLong = false;
		_lockActivated = false;
		_lockPrice = 0m;
	}
	
	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		
		var highest = new Highest { Length = CountBars };
		var lowest = new Lowest { Length = CountBars };
		
		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(highest, lowest, ProcessCandle)
		.Start();
		
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, highest);
			DrawIndicator(area, lowest);
			DrawOwnTrades(area);
		}
	}
	private void ProcessCandle(ICandleMessage candle, decimal high, decimal low)
	{
		if (candle.State != CandleStates.Finished)
		return;
		
		if (!IsFormedAndOnlineAndAllowTrading())
		return;
		
		var step = Security.PriceStep ?? 1m;
		
		if (Position == 0)
		{
			CheckForOpen(candle, high, low, step);
		}
		else
		{
			CheckForClose(candle, step);
		}
	}
	
	private void CheckForOpen(ICandleMessage candle, decimal high, decimal low, decimal step)
	{
		var close = candle.ClosePrice;
		
		if (high - close > Limit * step)
		{
			OpenPosition(true, close, step);
		}
		else if (close - low > Limit * step)
		{
			OpenPosition(false, close, step);
		}
	}
	
	private void OpenPosition(bool isBuy, decimal price, decimal step)
	{
		var volume = Volume;
		if (isBuy)
		{
			BuyMarket(volume);
		}
		else
		{
			SellMarket(volume);
		}
		
		_entryPrice = price;
		_isLong = isBuy;
		_lockActivated = false;
		_lockPrice = 0m;
		
	}
	
	private void CheckForClose(ICandleMessage candle, decimal step)
	{
		var currentProfit = Position * (candle.ClosePrice - _entryPrice);
		
		if (currentProfit >= Amount)
		{
			CloseAll();
			return;
		}
		
		if (LockDown <= 0m)
		return;
		
		if (_isLong)
		{
			if (!_lockActivated && candle.ClosePrice - _entryPrice > LockDown * step)
			{
				_lockActivated = true;
				_lockPrice = _entryPrice;
			}
			else if (_lockActivated && candle.ClosePrice <= _lockPrice)
			{
				CloseAll();
			}
		}
		else
		{
			if (!_lockActivated && _entryPrice - candle.ClosePrice > LockDown * step)
			{
				_lockActivated = true;
				_lockPrice = _entryPrice;
			}
			else if (_lockActivated && candle.ClosePrice >= _lockPrice)
			{
				CloseAll();
			}
		}
	}
	
	private void CloseAll()
	{
		if (Position > 0)
			SellMarket(Math.Abs(Position));
		else if (Position < 0)
			BuyMarket(Math.Abs(Position));

		_entryPrice = 0m;
		_lockActivated = false;
		_lockPrice = 0m;
	}
}