GitHub で見る

LazyBot V1 戦略

概要

LazyBot V1 は、オリジナルの MetaTrader 5 エキスパート アドバイザーから変換されたデイリー ブレークアウト戦略です。毎取引日、前日の価格帯付近で保留中のストップ注文を発注し、オープンポジションを保護するためにトレーリングストップを使用します。この変換では、キャンドルのサブスクリプションと自動注文管理を備えた高レベルの StockSharp API を利用します。

取引ロジック

  1. 設定された時間枠(デフォルトでは毎日)のローソク足が完了するまで待ちます。
  2. 新しい日には、オプションで現在のサーバー時間が許可された取引ウィンドウ内にあることを確認し、週末をスキップします。
  3. 戦略によって作成された既存のブレイクアウト指値注文をキャンセルします。
  4. 前日の高値より上に買いストップを設定し、前日の安値より下に売りストップを設定します。 Breakout Offset (pips) パラメータは、両方のブレイクアウト レベルに追加の距離を追加します。
  5. いずれかの注文がトリガーされると、保護ストップロスを固定距離に保ち、設定されたピップ距離を超えて価格が取引に有利に進むたびにそれを追跡します。
  6. 固定ロットサイズまたはリスクベースのサイジングモジュールを使用して、次の注文の数量を再計算します。

パラメーター

名前 説明
キャンドルの種類 基準ローソクを収集するために使用される時間枠 (デフォルトでは毎日)。
ボット名 追跡を容易にするために注文コメントに値が書き込まれます。
ストップロス (pips) 最初の停止と後続の停止の両方に使用される距離。
ブレイクアウト オフセット (pips) 未決注文を出すときに前の高値/安値に適用される追加距離。
最大スプレッド (pips) 新しいブレイクアウト注文を作成する前に許可される最大スプレッド。チェックを無効にするには、0 に設定します。
取引時間を使用する 元の EA と同様の開始時間フィルターを有効にします。
開始時間 新しい注文ができる最初の 1 時間 (含む)。
終了時間 新しい注文のスケジュールが停止される時間。開始時間と等しい場合、フィルターは単純な下限として機能します。
使用リスク% リスクベースのボリューム計算を有効にします。
リスク% Use Risk % が有効な場合、ポジションのサイズ設定に使用されるポートフォリオの資本の割合。
固定ボリューム リスクサイジングが無効な場合に使用される固定注文量。ゼロの場合、戦略はグローバル Volume プロパティに戻ります (デフォルトは 0.01)。

リスク管理

  • トレーリング ストップは、ストップロス Stop Loss (pips) を最良の買値/売値から遠ざけ、より良い価格に達した場合にのみ引き締めることにより、MetaTrader のトレーリング ロジックを反映しています。
  • スプレッド フィルターは、市場が広すぎる場合に新しいブレイクアウト注文を送信することから戦略を保護します。
  • リスクベースのサイジングでは、許容される金銭的リスク (equity * Risk %) を価格単位で表されるストップ距離で除算し、固定ロットサイズを下回ることはありません。

追加メモ

  • 注文コメントは BotName;SymbolId;YYYYMMDD の形式に従っており、異なる日に作成された未決注文を簡単に区別できます。
  • この戦略は、レベル 1 データをサブスクライブして、フィルターの現在のスプレッドを評価し、最新の買値/売値でトレイル ストップを評価します。
  • トレーリングストップは、元の EA の動作と一致するように、ローソク足の更新ごとと約定直後に再適用されます。
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "LazyBot V1" MetaTrader expert.
/// Daily breakout strategy using previous N-bar high/low range.
/// Buys when price breaks above previous high, sells when below previous low.
/// </summary>
public class LazyBotV1Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _lookback;

	private readonly Queue<decimal> _highs = new();
	private readonly Queue<decimal> _lows = new();

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

	public int Lookback
	{
		get => _lookback.Value;
		set => _lookback.Value = value;
	}

	public LazyBotV1Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for breakout detection", "General");

		_lookback = Param(nameof(Lookback), 30)
			.SetGreaterThanZero()
			.SetDisplay("Lookback", "Number of bars for high/low range", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_highs.Clear();
		_lows.Clear();

		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;

		// Build range from previous bars (not including current)
		if (_highs.Count >= Lookback)
		{
			var highs = _highs.ToArray();
			var lows = _lows.ToArray();
			decimal highest = decimal.MinValue;
			decimal lowest = decimal.MaxValue;
			foreach (var h in highs)
				if (h > highest) highest = h;
			foreach (var l in lows)
				if (l < lowest) lowest = l;

			var close = candle.ClosePrice;
			var volume = Volume;
			if (volume <= 0)
				volume = 1;
			var padding = (highest - lowest) * 0.05m;

			if (close > highest + padding)
			{
				if (Position <= 0)
					BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
			}
			else if (close < lowest - padding)
			{
				if (Position >= 0)
					SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
			}
		}

		_highs.Enqueue(candle.HighPrice);
		_lows.Enqueue(candle.LowPrice);

		if (_highs.Count > Lookback)
		{
			_highs.Dequeue();
			_lows.Dequeue();
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_highs.Clear();
		_lows.Clear();

		base.OnReseted();
	}
}